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 "qemu/osdep.h"
|
||||||
#include <sys/ioctl.h>
|
#include <sys/ioctl.h>
|
||||||
|
#include <sys/utsname.h>
|
||||||
#include <sys/wait.h>
|
#include <sys/wait.h>
|
||||||
#include <dirent.h>
|
#include <dirent.h>
|
||||||
#include "qga/guest-agent-core.h"
|
#include "qga/guest-agent-core.h"
|
||||||
|
@ -2592,3 +2593,137 @@ GuestUserList *qmp_guest_get_users(Error **errp)
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#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;
|
return NULL;
|
||||||
#endif
|
#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',
|
{ 'command': 'guest-get-timezone',
|
||||||
'returns': 'GuestTimezone' }
|
'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