qemu-ga: add guest-get-osinfo command

Add a new 'guest-get-osinfo' command for reporting basic information of
the guest operating system. This includes machine architecture,
version and release of the kernel and several fields from os-release
file if it is present (as defined in [1]).

[1] https://www.freedesktop.org/software/systemd/man/os-release.html

Signed-off-by: Vinzenz Feenstra <vfeenstr@redhat.com>
Signed-off-by: Tomáš Golembiovský <tgolembi@redhat.com>
* moved declarations to beginning of functions
* dropped unecessary initialization of struct utsname
Signed-off-by: Michael Roth <mdroth@linux.vnet.ibm.com>
This commit is contained in:
Tomáš Golembiovský 2017-07-14 16:28:56 +02:00 committed by Michael Roth
parent cbcd9ba1b7
commit 9848f79740
3 changed files with 391 additions and 0 deletions

View File

@ -13,6 +13,7 @@
#include "qemu/osdep.h"
#include <sys/ioctl.h>
#include <sys/utsname.h>
#include <sys/wait.h>
#include <dirent.h>
#include "qga/guest-agent-core.h"
@ -2592,3 +2593,137 @@ GuestUserList *qmp_guest_get_users(Error **errp)
}
#endif
/* Replace escaped special characters with theire real values. The replacement
* is done in place -- returned value is in the original string.
*/
static void ga_osrelease_replace_special(gchar *value)
{
gchar *p, *p2, quote;
/* Trim the string at first space or semicolon if it is not enclosed in
* single or double quotes. */
if ((value[0] != '"') || (value[0] == '\'')) {
p = strchr(value, ' ');
if (p != NULL) {
*p = 0;
}
p = strchr(value, ';');
if (p != NULL) {
*p = 0;
}
return;
}
quote = value[0];
p2 = value;
p = value + 1;
while (*p != 0) {
if (*p == '\\') {
p++;
switch (*p) {
case '$':
case '\'':
case '"':
case '\\':
case '`':
break;
default:
/* Keep literal backslash followed by whatever is there */
p--;
break;
}
} else if (*p == quote) {
*p2 = 0;
break;
}
*(p2++) = *(p++);
}
}
static GKeyFile *ga_parse_osrelease(const char *fname)
{
gchar *content = NULL;
gchar *content2 = NULL;
GError *err = NULL;
GKeyFile *keys = g_key_file_new();
const char *group = "[os-release]\n";
if (!g_file_get_contents(fname, &content, NULL, &err)) {
slog("failed to read '%s', error: %s", fname, err->message);
goto fail;
}
if (!g_utf8_validate(content, -1, NULL)) {
slog("file is not utf-8 encoded: %s", fname);
goto fail;
}
content2 = g_strdup_printf("%s%s", group, content);
if (!g_key_file_load_from_data(keys, content2, -1, G_KEY_FILE_NONE,
&err)) {
slog("failed to parse file '%s', error: %s", fname, err->message);
goto fail;
}
g_free(content);
g_free(content2);
return keys;
fail:
g_error_free(err);
g_free(content);
g_free(content2);
g_key_file_free(keys);
return NULL;
}
GuestOSInfo *qmp_guest_get_osinfo(Error **errp)
{
GuestOSInfo *info = NULL;
struct utsname kinfo;
GKeyFile *osrelease;
info = g_new0(GuestOSInfo, 1);
if (uname(&kinfo) != 0) {
error_setg_errno(errp, errno, "uname failed");
} else {
info->has_kernel_version = true;
info->kernel_version = g_strdup(kinfo.version);
info->has_kernel_release = true;
info->kernel_release = g_strdup(kinfo.release);
info->has_machine = true;
info->machine = g_strdup(kinfo.machine);
}
osrelease = ga_parse_osrelease("/etc/os-release");
if (osrelease == NULL) {
osrelease = ga_parse_osrelease("/usr/lib/os-release");
}
if (osrelease != NULL) {
char *value;
#define GET_FIELD(field, osfield) do { \
value = g_key_file_get_value(osrelease, "os-release", osfield, NULL); \
if (value != NULL) { \
ga_osrelease_replace_special(value); \
info->has_ ## field = true; \
info->field = value; \
} \
} while (0)
GET_FIELD(id, "ID");
GET_FIELD(name, "NAME");
GET_FIELD(pretty_name, "PRETTY_NAME");
GET_FIELD(version, "VERSION");
GET_FIELD(version_id, "VERSION_ID");
GET_FIELD(variant, "VARIANT");
GET_FIELD(variant_id, "VARIANT_ID");
#undef GET_FIELD
g_key_file_free(osrelease);
}
return info;
}

View File

@ -1642,3 +1642,194 @@ GuestUserList *qmp_guest_get_users(Error **err)
return NULL;
#endif
}
typedef struct _ga_matrix_lookup_t {
int major;
int minor;
char const *version;
char const *version_id;
} ga_matrix_lookup_t;
static ga_matrix_lookup_t const WIN_VERSION_MATRIX[2][8] = {
{
/* Desktop editions */
{ 5, 0, "Microsoft Windows 2000", "2000"},
{ 5, 1, "Microsoft Windows XP", "xp"},
{ 6, 0, "Microsoft Windows Vista", "vista"},
{ 6, 1, "Microsoft Windows 7" "7"},
{ 6, 2, "Microsoft Windows 8", "8"},
{ 6, 3, "Microsoft Windows 8.1", "8.1"},
{10, 0, "Microsoft Windows 10", "10"},
{ 0, 0, 0}
},{
/* Server editions */
{ 5, 2, "Microsoft Windows Server 2003", "2003"},
{ 6, 0, "Microsoft Windows Server 2008", "2008"},
{ 6, 1, "Microsoft Windows Server 2008 R2", "2008r2"},
{ 6, 2, "Microsoft Windows Server 2012", "2012"},
{ 6, 3, "Microsoft Windows Server 2012 R2", "2012r2"},
{10, 0, "Microsoft Windows Server 2016", "2016"},
{ 0, 0, 0},
{ 0, 0, 0}
}
};
static void ga_get_win_version(RTL_OSVERSIONINFOEXW *info, Error **errp)
{
typedef NTSTATUS(WINAPI * rtl_get_version_t)(
RTL_OSVERSIONINFOEXW *os_version_info_ex);
info->dwOSVersionInfoSize = sizeof(RTL_OSVERSIONINFOEXW);
HMODULE module = GetModuleHandle("ntdll");
PVOID fun = GetProcAddress(module, "RtlGetVersion");
if (fun == NULL) {
error_setg(errp, QERR_QGA_COMMAND_FAILED,
"Failed to get address of RtlGetVersion");
return;
}
rtl_get_version_t rtl_get_version = (rtl_get_version_t)fun;
rtl_get_version(info);
return;
}
static char *ga_get_win_name(OSVERSIONINFOEXW const *os_version, bool id)
{
DWORD major = os_version->dwMajorVersion;
DWORD minor = os_version->dwMinorVersion;
int tbl_idx = (os_version->wProductType != VER_NT_WORKSTATION);
ga_matrix_lookup_t const *table = WIN_VERSION_MATRIX[tbl_idx];
while (table->version != NULL) {
if (major == table->major && minor == table->minor) {
if (id) {
return g_strdup(table->version_id);
} else {
return g_strdup(table->version);
}
}
++table;
}
slog("failed to lookup Windows version: major=%lu, minor=%lu",
major, minor);
return g_strdup("N/A");
}
static char *ga_get_win_product_name(Error **errp)
{
HKEY key = NULL;
DWORD size = 128;
char *result = g_malloc0(size);
LONG err = ERROR_SUCCESS;
err = RegOpenKeyA(HKEY_LOCAL_MACHINE,
"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion",
&key);
if (err != ERROR_SUCCESS) {
error_setg_win32(errp, err, "failed to open registry key");
goto fail;
}
err = RegQueryValueExA(key, "ProductName", NULL, NULL,
(LPBYTE)result, &size);
if (err == ERROR_MORE_DATA) {
slog("ProductName longer than expected (%lu bytes), retrying",
size);
g_free(result);
result = NULL;
if (size > 0) {
result = g_malloc0(size);
err = RegQueryValueExA(key, "ProductName", NULL, NULL,
(LPBYTE)result, &size);
}
}
if (err != ERROR_SUCCESS) {
error_setg_win32(errp, err, "failed to retrive ProductName");
goto fail;
}
return result;
fail:
g_free(result);
return NULL;
}
static char *ga_get_current_arch(void)
{
SYSTEM_INFO info;
GetNativeSystemInfo(&info);
char *result = NULL;
switch (info.wProcessorArchitecture) {
case PROCESSOR_ARCHITECTURE_AMD64:
result = g_strdup("x86_64");
break;
case PROCESSOR_ARCHITECTURE_ARM:
result = g_strdup("arm");
break;
case PROCESSOR_ARCHITECTURE_IA64:
result = g_strdup("ia64");
break;
case PROCESSOR_ARCHITECTURE_INTEL:
result = g_strdup("x86");
break;
case PROCESSOR_ARCHITECTURE_UNKNOWN:
default:
slog("unknown processor architecture 0x%0x",
info.wProcessorArchitecture);
result = g_strdup("unknown");
break;
}
return result;
}
GuestOSInfo *qmp_guest_get_osinfo(Error **errp)
{
Error *local_err = NULL;
OSVERSIONINFOEXW os_version = {0};
bool server;
char *product_name;
GuestOSInfo *info;
ga_get_win_version(&os_version, &local_err);
if (local_err) {
error_propagate(errp, local_err);
return NULL;
}
server = os_version.wProductType != VER_NT_WORKSTATION;
product_name = ga_get_win_product_name(&local_err);
if (product_name == NULL) {
error_propagate(errp, local_err);
return NULL;
}
info = g_new0(GuestOSInfo, 1);
info->has_kernel_version = true;
info->kernel_version = g_strdup_printf("%lu.%lu",
os_version.dwMajorVersion,
os_version.dwMinorVersion);
info->has_kernel_release = true;
info->kernel_release = g_strdup_printf("%lu",
os_version.dwBuildNumber);
info->has_machine = true;
info->machine = ga_get_current_arch();
info->has_id = true;
info->id = g_strdup("mswindows");
info->has_name = true;
info->name = g_strdup("Microsoft Windows");
info->has_pretty_name = true;
info->pretty_name = product_name;
info->has_version = true;
info->version = ga_get_win_name(&os_version, false);
info->has_version_id = true;
info->version_id = ga_get_win_name(&os_version, true);
info->has_variant = true;
info->variant = g_strdup(server ? "server" : "client");
info->has_variant_id = true;
info->variant_id = g_strdup(server ? "server" : "client");
return info;
}

View File

@ -1126,3 +1126,68 @@
##
{ 'command': 'guest-get-timezone',
'returns': 'GuestTimezone' }
##
# @GuestOSInfo:
#
# @kernel-release:
# * POSIX: release field returned by uname(2)
# * Windows: version number of the OS
# @kernel-version:
# * POSIX: version field returned by uname(2)
# * Windows: build number of the OS
# @machine:
# * POSIX: machine field returned by uname(2)
# * Windows: one of x86, x86_64, arm, ia64
# @id:
# * POSIX: as defined by os-release(5)
# * Windows: contains string "mswindows"
# @name:
# * POSIX: as defined by os-release(5)
# * Windows: contains string "Microsoft Windows"
# @pretty-name:
# * POSIX: as defined by os-release(5)
# * Windows: product name, e.g. "Microsoft Windows 10 Enterprise"
# @version:
# * POSIX: as defined by os-release(5)
# * Windows: long version string, e.g. "Microsoft Windows Server 2008"
# @version-id:
# * POSIX: as defined by os-release(5)
# * Windows: short version identifier, e.g. "7" or "20012r2"
# @variant:
# * POSIX: as defined by os-release(5)
# * Windows: contains string "server" or "client"
# @variant-id:
# * POSIX: as defined by os-release(5)
# * Windows: contains string "server" or "client"
#
# Notes:
#
# On POSIX systems the fields @id, @name, @pretty-name, @version, @version-id,
# @variant and @variant-id follow the definition specified in os-release(5).
# Refer to the manual page for exact description of the fields. Their values
# are taken from the os-release file. If the file is not present in the system,
# or the values are not present in the file, the fields are not included.
#
# On Windows the values are filled from information gathered from the system.
#
# Since: 2.10
##
{ 'struct': 'GuestOSInfo',
'data': {
'*kernel-release': 'str', '*kernel-version': 'str',
'*machine': 'str', '*id': 'str', '*name': 'str',
'*pretty-name': 'str', '*version': 'str', '*version-id': 'str',
'*variant': 'str', '*variant-id': 'str' } }
##
# @guest-get-osinfo:
#
# Retrieve guest operating system information
#
# Returns: @GuestOSInfo
#
# Since: 2.10
##
{ 'command': 'guest-get-osinfo',
'returns': 'GuestOSInfo' }