Blame SOURCES/qemuga-qemu-ga-add-guest-get-osinfo-command.patch

3f2699
From 2db11b426c4e16b64926f78baea594792c183f57 Mon Sep 17 00:00:00 2001
3f2699
From: Miroslav Rezanina <mrezanin@redhat.com>
3f2699
Date: Thu, 19 Apr 2018 12:33:46 -0300
3f2699
Subject: [PATCH 5/7] qemu-ga: add guest-get-osinfo command
3f2699
MIME-Version: 1.0
3f2699
Content-Type: text/plain; charset=UTF-8
3f2699
Content-Transfer-Encoding: 8bit
3f2699
3f2699
RH-Author: Miroslav Rezanina <mrezanin@redhat.com>
3f2699
Message-id: <032a36a732e654dc0d6e2043a47eb294db3ac75f.1524139831.git.mrezanin@redhat.com>
3f2699
Patchwork-id: 79704
3f2699
O-Subject: [RHEL-7.5.z qemu-guest-agent PATCH 5/7] qemu-ga: add guest-get-osinfo command
3f2699
Bugzilla: 1598210
3f2699
RH-Acked-by: Marc-André Lureau <marcandre.lureau@redhat.com>
3f2699
RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
3f2699
RH-Acked-by: Tomáš Golembiovský <tgolembi@redhat.com>
3f2699
3f2699
From: Tomáš Golembiovský <tgolembi@redhat.com>
3f2699
3f2699
Add a new 'guest-get-osinfo' command for reporting basic information of
3f2699
the guest operating system. This includes machine architecture,
3f2699
version and release of the kernel and several fields from os-release
3f2699
file if it is present (as defined in [1]).
3f2699
3f2699
[1] https://www.freedesktop.org/software/systemd/man/os-release.html
3f2699
3f2699
Signed-off-by: Vinzenz Feenstra <vfeenstr@redhat.com>
3f2699
Signed-off-by: Tomáš Golembiovský <tgolembi@redhat.com>
3f2699
* moved declarations to beginning of functions
3f2699
* dropped unecessary initialization of struct utsname
3f2699
Signed-off-by: Michael Roth <mdroth@linux.vnet.ibm.com>
3f2699
(cherry picked from commit 9848f79740dc3bd2e0515470d24b8bec53904473)
3f2699
Signed-off-by: Wainer dos Santos Moschetta <wainersm@redhat.com>
3f2699
---
3f2699
 qga/commands-posix.c | 135 ++++++++++++++++++++++++++++++++++++
3f2699
 qga/commands-win32.c | 191 +++++++++++++++++++++++++++++++++++++++++++++++++++
3f2699
 qga/qapi-schema.json |  65 ++++++++++++++++++
3f2699
 3 files changed, 391 insertions(+)
3f2699
3f2699
diff --git a/qga/commands-posix.c b/qga/commands-posix.c
3f2699
index 70348aa8c8..add8a51f2b 100644
3f2699
--- a/qga/commands-posix.c
3f2699
+++ b/qga/commands-posix.c
3f2699
@@ -13,6 +13,7 @@
3f2699
 
3f2699
 #include "qemu/osdep.h"
3f2699
 #include <sys/ioctl.h>
3f2699
+#include <sys/utsname.h>
3f2699
 #include <sys/wait.h>
3f2699
 #include <dirent.h>
3f2699
 #include "qga/guest-agent-core.h"
3f2699
@@ -2587,3 +2588,137 @@ GuestUserList *qmp_guest_get_users(Error **errp)
3f2699
 }
3f2699
 
3f2699
 #endif
3f2699
+
3f2699
+/* Replace escaped special characters with theire real values. The replacement
3f2699
+ * is done in place -- returned value is in the original string.
3f2699
+ */
3f2699
+static void ga_osrelease_replace_special(gchar *value)
3f2699
+{
3f2699
+    gchar *p, *p2, quote;
3f2699
+
3f2699
+    /* Trim the string at first space or semicolon if it is not enclosed in
3f2699
+     * single or double quotes. */
3f2699
+    if ((value[0] != '"') || (value[0] == '\'')) {
3f2699
+        p = strchr(value, ' ');
3f2699
+        if (p != NULL) {
3f2699
+            *p = 0;
3f2699
+        }
3f2699
+        p = strchr(value, ';');
3f2699
+        if (p != NULL) {
3f2699
+            *p = 0;
3f2699
+        }
3f2699
+        return;
3f2699
+    }
3f2699
+
3f2699
+    quote = value[0];
3f2699
+    p2 = value;
3f2699
+    p = value + 1;
3f2699
+    while (*p != 0) {
3f2699
+        if (*p == '\\') {
3f2699
+            p++;
3f2699
+            switch (*p) {
3f2699
+            case '$':
3f2699
+            case '\'':
3f2699
+            case '"':
3f2699
+            case '\\':
3f2699
+            case '`':
3f2699
+                break;
3f2699
+            default:
3f2699
+                /* Keep literal backslash followed by whatever is there */
3f2699
+                p--;
3f2699
+                break;
3f2699
+            }
3f2699
+        } else if (*p == quote) {
3f2699
+            *p2 = 0;
3f2699
+            break;
3f2699
+        }
3f2699
+        *(p2++) = *(p++);
3f2699
+    }
3f2699
+}
3f2699
+
3f2699
+static GKeyFile *ga_parse_osrelease(const char *fname)
3f2699
+{
3f2699
+    gchar *content = NULL;
3f2699
+    gchar *content2 = NULL;
3f2699
+    GError *err = NULL;
3f2699
+    GKeyFile *keys = g_key_file_new();
3f2699
+    const char *group = "[os-release]\n";
3f2699
+
3f2699
+    if (!g_file_get_contents(fname, &content, NULL, &err)) {
3f2699
+        slog("failed to read '%s', error: %s", fname, err->message);
3f2699
+        goto fail;
3f2699
+    }
3f2699
+
3f2699
+    if (!g_utf8_validate(content, -1, NULL)) {
3f2699
+        slog("file is not utf-8 encoded: %s", fname);
3f2699
+        goto fail;
3f2699
+    }
3f2699
+    content2 = g_strdup_printf("%s%s", group, content);
3f2699
+
3f2699
+    if (!g_key_file_load_from_data(keys, content2, -1, G_KEY_FILE_NONE,
3f2699
+                                   &err)) {
3f2699
+        slog("failed to parse file '%s', error: %s", fname, err->message);
3f2699
+        goto fail;
3f2699
+    }
3f2699
+
3f2699
+    g_free(content);
3f2699
+    g_free(content2);
3f2699
+    return keys;
3f2699
+
3f2699
+fail:
3f2699
+    g_error_free(err);
3f2699
+    g_free(content);
3f2699
+    g_free(content2);
3f2699
+    g_key_file_free(keys);
3f2699
+    return NULL;
3f2699
+}
3f2699
+
3f2699
+GuestOSInfo *qmp_guest_get_osinfo(Error **errp)
3f2699
+{
3f2699
+    GuestOSInfo *info = NULL;
3f2699
+    struct utsname kinfo;
3f2699
+    GKeyFile *osrelease;
3f2699
+
3f2699
+    info = g_new0(GuestOSInfo, 1);
3f2699
+
3f2699
+    if (uname(&kinfo) != 0) {
3f2699
+        error_setg_errno(errp, errno, "uname failed");
3f2699
+    } else {
3f2699
+        info->has_kernel_version = true;
3f2699
+        info->kernel_version = g_strdup(kinfo.version);
3f2699
+        info->has_kernel_release = true;
3f2699
+        info->kernel_release = g_strdup(kinfo.release);
3f2699
+        info->has_machine = true;
3f2699
+        info->machine = g_strdup(kinfo.machine);
3f2699
+    }
3f2699
+
3f2699
+    osrelease = ga_parse_osrelease("/etc/os-release");
3f2699
+    if (osrelease == NULL) {
3f2699
+        osrelease = ga_parse_osrelease("/usr/lib/os-release");
3f2699
+    }
3f2699
+
3f2699
+    if (osrelease != NULL) {
3f2699
+        char *value;
3f2699
+
3f2699
+#define GET_FIELD(field, osfield) do { \
3f2699
+    value = g_key_file_get_value(osrelease, "os-release", osfield, NULL); \
3f2699
+    if (value != NULL) { \
3f2699
+        ga_osrelease_replace_special(value); \
3f2699
+        info->has_ ## field = true; \
3f2699
+        info->field = value; \
3f2699
+    } \
3f2699
+} while (0)
3f2699
+        GET_FIELD(id, "ID");
3f2699
+        GET_FIELD(name, "NAME");
3f2699
+        GET_FIELD(pretty_name, "PRETTY_NAME");
3f2699
+        GET_FIELD(version, "VERSION");
3f2699
+        GET_FIELD(version_id, "VERSION_ID");
3f2699
+        GET_FIELD(variant, "VARIANT");
3f2699
+        GET_FIELD(variant_id, "VARIANT_ID");
3f2699
+#undef GET_FIELD
3f2699
+
3f2699
+        g_key_file_free(osrelease);
3f2699
+    }
3f2699
+
3f2699
+    return info;
3f2699
+}
3f2699
diff --git a/qga/commands-win32.c b/qga/commands-win32.c
3f2699
index fa99a8f846..9ae9836df5 100644
3f2699
--- a/qga/commands-win32.c
3f2699
+++ b/qga/commands-win32.c
3f2699
@@ -1639,3 +1639,194 @@ GuestUserList *qmp_guest_get_users(Error **err)
3f2699
     return NULL;
3f2699
 #endif
3f2699
 }
3f2699
+
3f2699
+typedef struct _ga_matrix_lookup_t {
3f2699
+    int major;
3f2699
+    int minor;
3f2699
+    char const *version;
3f2699
+    char const *version_id;
3f2699
+} ga_matrix_lookup_t;
3f2699
+
3f2699
+static ga_matrix_lookup_t const WIN_VERSION_MATRIX[2][8] = {
3f2699
+    {
3f2699
+        /* Desktop editions */
3f2699
+        { 5, 0, "Microsoft Windows 2000",   "2000"},
3f2699
+        { 5, 1, "Microsoft Windows XP",     "xp"},
3f2699
+        { 6, 0, "Microsoft Windows Vista",  "vista"},
3f2699
+        { 6, 1, "Microsoft Windows 7"       "7"},
3f2699
+        { 6, 2, "Microsoft Windows 8",      "8"},
3f2699
+        { 6, 3, "Microsoft Windows 8.1",    "8.1"},
3f2699
+        {10, 0, "Microsoft Windows 10",     "10"},
3f2699
+        { 0, 0, 0}
3f2699
+    },{
3f2699
+        /* Server editions */
3f2699
+        { 5, 2, "Microsoft Windows Server 2003",        "2003"},
3f2699
+        { 6, 0, "Microsoft Windows Server 2008",        "2008"},
3f2699
+        { 6, 1, "Microsoft Windows Server 2008 R2",     "2008r2"},
3f2699
+        { 6, 2, "Microsoft Windows Server 2012",        "2012"},
3f2699
+        { 6, 3, "Microsoft Windows Server 2012 R2",     "2012r2"},
3f2699
+        {10, 0, "Microsoft Windows Server 2016",        "2016"},
3f2699
+        { 0, 0, 0},
3f2699
+        { 0, 0, 0}
3f2699
+    }
3f2699
+};
3f2699
+
3f2699
+static void ga_get_win_version(RTL_OSVERSIONINFOEXW *info, Error **errp)
3f2699
+{
3f2699
+    typedef NTSTATUS(WINAPI * rtl_get_version_t)(
3f2699
+        RTL_OSVERSIONINFOEXW *os_version_info_ex);
3f2699
+
3f2699
+    info->dwOSVersionInfoSize = sizeof(RTL_OSVERSIONINFOEXW);
3f2699
+
3f2699
+    HMODULE module = GetModuleHandle("ntdll");
3f2699
+    PVOID fun = GetProcAddress(module, "RtlGetVersion");
3f2699
+    if (fun == NULL) {
3f2699
+        error_setg(errp, QERR_QGA_COMMAND_FAILED,
3f2699
+            "Failed to get address of RtlGetVersion");
3f2699
+        return;
3f2699
+    }
3f2699
+
3f2699
+    rtl_get_version_t rtl_get_version = (rtl_get_version_t)fun;
3f2699
+    rtl_get_version(info);
3f2699
+    return;
3f2699
+}
3f2699
+
3f2699
+static char *ga_get_win_name(OSVERSIONINFOEXW const *os_version, bool id)
3f2699
+{
3f2699
+    DWORD major = os_version->dwMajorVersion;
3f2699
+    DWORD minor = os_version->dwMinorVersion;
3f2699
+    int tbl_idx = (os_version->wProductType != VER_NT_WORKSTATION);
3f2699
+    ga_matrix_lookup_t const *table = WIN_VERSION_MATRIX[tbl_idx];
3f2699
+    while (table->version != NULL) {
3f2699
+        if (major == table->major && minor == table->minor) {
3f2699
+            if (id) {
3f2699
+                return g_strdup(table->version_id);
3f2699
+            } else {
3f2699
+                return g_strdup(table->version);
3f2699
+            }
3f2699
+        }
3f2699
+        ++table;
3f2699
+    }
3f2699
+    slog("failed to lookup Windows version: major=%lu, minor=%lu",
3f2699
+        major, minor);
3f2699
+    return g_strdup("N/A");
3f2699
+}
3f2699
+
3f2699
+static char *ga_get_win_product_name(Error **errp)
3f2699
+{
3f2699
+    HKEY key = NULL;
3f2699
+    DWORD size = 128;
3f2699
+    char *result = g_malloc0(size);
3f2699
+    LONG err = ERROR_SUCCESS;
3f2699
+
3f2699
+    err = RegOpenKeyA(HKEY_LOCAL_MACHINE,
3f2699
+                      "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion",
3f2699
+                      &key);
3f2699
+    if (err != ERROR_SUCCESS) {
3f2699
+        error_setg_win32(errp, err, "failed to open registry key");
3f2699
+        goto fail;
3f2699
+    }
3f2699
+
3f2699
+    err = RegQueryValueExA(key, "ProductName", NULL, NULL,
3f2699
+                            (LPBYTE)result, &size);
3f2699
+    if (err == ERROR_MORE_DATA) {
3f2699
+        slog("ProductName longer than expected (%lu bytes), retrying",
3f2699
+                size);
3f2699
+        g_free(result);
3f2699
+        result = NULL;
3f2699
+        if (size > 0) {
3f2699
+            result = g_malloc0(size);
3f2699
+            err = RegQueryValueExA(key, "ProductName", NULL, NULL,
3f2699
+                                    (LPBYTE)result, &size);
3f2699
+        }
3f2699
+    }
3f2699
+    if (err != ERROR_SUCCESS) {
3f2699
+        error_setg_win32(errp, err, "failed to retrive ProductName");
3f2699
+        goto fail;
3f2699
+    }
3f2699
+
3f2699
+    return result;
3f2699
+
3f2699
+fail:
3f2699
+    g_free(result);
3f2699
+    return NULL;
3f2699
+}
3f2699
+
3f2699
+static char *ga_get_current_arch(void)
3f2699
+{
3f2699
+    SYSTEM_INFO info;
3f2699
+    GetNativeSystemInfo(&info;;
3f2699
+    char *result = NULL;
3f2699
+    switch (info.wProcessorArchitecture) {
3f2699
+    case PROCESSOR_ARCHITECTURE_AMD64:
3f2699
+        result = g_strdup("x86_64");
3f2699
+        break;
3f2699
+    case PROCESSOR_ARCHITECTURE_ARM:
3f2699
+        result = g_strdup("arm");
3f2699
+        break;
3f2699
+    case PROCESSOR_ARCHITECTURE_IA64:
3f2699
+        result = g_strdup("ia64");
3f2699
+        break;
3f2699
+    case PROCESSOR_ARCHITECTURE_INTEL:
3f2699
+        result = g_strdup("x86");
3f2699
+        break;
3f2699
+    case PROCESSOR_ARCHITECTURE_UNKNOWN:
3f2699
+    default:
3f2699
+        slog("unknown processor architecture 0x%0x",
3f2699
+            info.wProcessorArchitecture);
3f2699
+        result = g_strdup("unknown");
3f2699
+        break;
3f2699
+    }
3f2699
+    return result;
3f2699
+}
3f2699
+
3f2699
+GuestOSInfo *qmp_guest_get_osinfo(Error **errp)
3f2699
+{
3f2699
+    Error *local_err = NULL;
3f2699
+    OSVERSIONINFOEXW os_version = {0};
3f2699
+    bool server;
3f2699
+    char *product_name;
3f2699
+    GuestOSInfo *info;
3f2699
+
3f2699
+    ga_get_win_version(&os_version, &local_err);
3f2699
+    if (local_err) {
3f2699
+        error_propagate(errp, local_err);
3f2699
+        return NULL;
3f2699
+    }
3f2699
+
3f2699
+    server = os_version.wProductType != VER_NT_WORKSTATION;
3f2699
+    product_name = ga_get_win_product_name(&local_err);
3f2699
+    if (product_name == NULL) {
3f2699
+        error_propagate(errp, local_err);
3f2699
+        return NULL;
3f2699
+    }
3f2699
+
3f2699
+    info = g_new0(GuestOSInfo, 1);
3f2699
+
3f2699
+    info->has_kernel_version = true;
3f2699
+    info->kernel_version = g_strdup_printf("%lu.%lu",
3f2699
+        os_version.dwMajorVersion,
3f2699
+        os_version.dwMinorVersion);
3f2699
+    info->has_kernel_release = true;
3f2699
+    info->kernel_release = g_strdup_printf("%lu",
3f2699
+        os_version.dwBuildNumber);
3f2699
+    info->has_machine = true;
3f2699
+    info->machine = ga_get_current_arch();
3f2699
+
3f2699
+    info->has_id = true;
3f2699
+    info->id = g_strdup("mswindows");
3f2699
+    info->has_name = true;
3f2699
+    info->name = g_strdup("Microsoft Windows");
3f2699
+    info->has_pretty_name = true;
3f2699
+    info->pretty_name = product_name;
3f2699
+    info->has_version = true;
3f2699
+    info->version = ga_get_win_name(&os_version, false);
3f2699
+    info->has_version_id = true;
3f2699
+    info->version_id = ga_get_win_name(&os_version, true);
3f2699
+    info->has_variant = true;
3f2699
+    info->variant = g_strdup(server ? "server" : "client");
3f2699
+    info->has_variant_id = true;
3f2699
+    info->variant_id = g_strdup(server ? "server" : "client");
3f2699
+
3f2699
+    return info;
3f2699
+}
3f2699
diff --git a/qga/qapi-schema.json b/qga/qapi-schema.json
3f2699
index 5af73fedae..43ce3feeaa 100644
3f2699
--- a/qga/qapi-schema.json
3f2699
+++ b/qga/qapi-schema.json
3f2699
@@ -1104,3 +1104,68 @@
3f2699
 ##
3f2699
 { 'command': 'guest-get-timezone',
3f2699
   'returns': 'GuestTimezone' }
3f2699
+
3f2699
+##
3f2699
+# @GuestOSInfo:
3f2699
+#
3f2699
+# @kernel-release:
3f2699
+#     * POSIX: release field returned by uname(2)
3f2699
+#     * Windows: version number of the OS
3f2699
+# @kernel-version:
3f2699
+#     * POSIX: version field returned by uname(2)
3f2699
+#     * Windows: build number of the OS
3f2699
+# @machine:
3f2699
+#     * POSIX: machine field returned by uname(2)
3f2699
+#     * Windows: one of x86, x86_64, arm, ia64
3f2699
+# @id:
3f2699
+#     * POSIX: as defined by os-release(5)
3f2699
+#     * Windows: contains string "mswindows"
3f2699
+# @name:
3f2699
+#     * POSIX: as defined by os-release(5)
3f2699
+#     * Windows: contains string "Microsoft Windows"
3f2699
+# @pretty-name:
3f2699
+#     * POSIX: as defined by os-release(5)
3f2699
+#     * Windows: product name, e.g. "Microsoft Windows 10 Enterprise"
3f2699
+# @version:
3f2699
+#     * POSIX: as defined by os-release(5)
3f2699
+#     * Windows: long version string, e.g. "Microsoft Windows Server 2008"
3f2699
+# @version-id:
3f2699
+#     * POSIX: as defined by os-release(5)
3f2699
+#     * Windows: short version identifier, e.g. "7" or "20012r2"
3f2699
+# @variant:
3f2699
+#     * POSIX: as defined by os-release(5)
3f2699
+#     * Windows: contains string "server" or "client"
3f2699
+# @variant-id:
3f2699
+#     * POSIX: as defined by os-release(5)
3f2699
+#     * Windows: contains string "server" or "client"
3f2699
+#
3f2699
+# Notes:
3f2699
+#
3f2699
+# On POSIX systems the fields @id, @name, @pretty-name, @version, @version-id,
3f2699
+# @variant and @variant-id follow the definition specified in os-release(5).
3f2699
+# Refer to the manual page for exact description of the fields. Their values
3f2699
+# are taken from the os-release file. If the file is not present in the system,
3f2699
+# or the values are not present in the file, the fields are not included.
3f2699
+#
3f2699
+# On Windows the values are filled from information gathered from the system.
3f2699
+#
3f2699
+# Since: 2.10
3f2699
+##
3f2699
+{ 'struct': 'GuestOSInfo',
3f2699
+  'data': {
3f2699
+      '*kernel-release': 'str', '*kernel-version': 'str',
3f2699
+      '*machine': 'str', '*id': 'str', '*name': 'str',
3f2699
+      '*pretty-name': 'str', '*version': 'str', '*version-id': 'str',
3f2699
+      '*variant': 'str', '*variant-id': 'str' } }
3f2699
+
3f2699
+##
3f2699
+# @guest-get-osinfo:
3f2699
+#
3f2699
+# Retrieve guest operating system information
3f2699
+#
3f2699
+# Returns: @GuestOSInfo
3f2699
+#
3f2699
+# Since: 2.10
3f2699
+##
3f2699
+{ 'command': 'guest-get-osinfo',
3f2699
+  'returns': 'GuestOSInfo' }
3f2699
-- 
3f2699
2.13.6
3f2699