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:
parent
cbcd9ba1b7
commit
9848f79740
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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' }
|
||||
|
Loading…
Reference in New Issue
Block a user