qemu-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Qemu-devel] [PATCH v2] qemu-ga: add guest-get-osinfo command


From: Vinzenz 'evilissimo' Feenstra
Subject: [Qemu-devel] [PATCH v2] qemu-ga: add guest-get-osinfo command
Date: Thu, 16 Mar 2017 15:50:45 +0100

From: Vinzenz Feenstra <address@hidden>

Add a new 'guest-get-osinfo' command for reporting basic information of
the guest operating system (hereafter just 'OS'). This information
includes the type of the OS, the version, and the architecture.
Additionally reported would be a name, distribution type and kernel
version where applicable.

Here an example for a Fedora 25 VM:

$ virsh -c qemu:////system qemu-agent-command F25 \
    '{ "execute": "guest-get-osinfo" }'
  {"return":{"arch":"x86_64","codename":"Server Edition","version":"25",
   "kernel":"4.8.6-300.fc25.x86_64","type":"linux","distribution":"Fedora"}}

And an example for a Windows 2012 R2 VM:

$ virsh -c qemu:////system qemu-agent-command Win2k12R2 \
    '{ "execute": "guest-get-osinfo" }'
  {"return":{"arch":"x86_64","codename":"Win 2012 R2",
   "version":"6.3","kernel":"","type":"windows","distribution":""}}

Signed-off-by: Vinzenz Feenstra <address@hidden>
---
 qga/commands-posix.c | 189 +++++++++++++++++++++++++++++++++++++++++++++++++++
 qga/commands-win32.c | 104 ++++++++++++++++++++++++++++
 qga/qapi-schema.json |  40 +++++++++++
 3 files changed, 333 insertions(+)

diff --git a/qga/commands-posix.c b/qga/commands-posix.c
index 73d93eb..381c01a 100644
--- a/qga/commands-posix.c
+++ b/qga/commands-posix.c
@@ -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"
@@ -2356,6 +2357,188 @@ GuestMemoryBlockInfo 
*qmp_guest_get_memory_block_info(Error **errp)
     return info;
 }
 
+static void ga_strip_end(char *value)
+{
+    size_t value_length = strlen(value);
+    while (value_length > 0) {
+        switch (value[value_length - 1]) {
+        default:
+            value_length = 0;
+            break;
+        case ' ': case '\n': case '\t': case '\'': case '"':
+            value[value_length - 1] = 0;
+            --value_length;
+            break;
+        }
+    }
+}
+
+static void ga_parse_version_id(char const *value, GuestOSInfo *info)
+{
+    if (strlen(value) < 128) {
+        char codename[128];
+        char version[128];
+
+        if (*value == '"') {
+            ++value;
+        }
+
+        if (sscanf(value, "%[^(] (%[^)])", version, codename) == 2) {
+            /* eg. VERSION="16.04.1 LTS (Xenial Xerus)" */
+            info->codename = g_strdup(codename);
+            info->version = g_strdup(version);
+        } else if (sscanf(value, "%[^,] %[^\"]\"", version, codename) == 2) {
+            /* eg. VERSION="12.04.5 LTS, Precise Pangolin" */
+            info->codename = g_strdup(codename);
+            info->version = g_strdup(version);
+        } else {
+            /* Just use the rest */
+            info->version = g_strdup(version);
+        }
+    }
+}
+
+static void ga_parse_debian_version(FILE *fp, GuestOSInfo *info)
+{
+    char *line = NULL;
+    size_t n = 0;
+
+    if (getline(&line, &n, fp) != -1) {
+        ga_strip_end(line);
+        info->version = g_strdup(line);
+        info->distribution = g_strdup("Debian GNU/Linux");
+    }
+    free(line);
+}
+
+static void ga_parse_redhat_release(FILE *fp, GuestOSInfo *info)
+{
+    char *line = NULL;
+    size_t n = 0;
+
+    if (getline(&line, &n, fp) != -1) {
+        char *value = strstr(line, " release ");
+        if (value != NULL) {
+            *value = 0;
+            info->distribution = g_strdup(line);
+            value += 9;
+            ga_strip_end(value);
+            ga_parse_version_id(value, info);
+        }
+    }
+    free(line);
+}
+
+static void ga_parse_os_release(FILE *fp, GuestOSInfo *info)
+{
+    char *line = NULL;
+    size_t n = 0;
+
+    while (getline(&line, &n, fp) != -1) {
+        char *value = strstr(line, "=");
+        if (value != NULL) {
+            *value = 0;
+            ++value;
+            ga_strip_end(value);
+
+            size_t len = strlen(line);
+            if (len == 9 && strcmp(line, "VERSION_ID") == 0) {
+                info->version = g_strdup(value);
+            } else if (len == 7 && strcmp(line, "VERSION") == 0) {
+                ga_parse_version_id(value, info);
+            } else if (len == 4 && strcmp(line, "NAME") == 0) {
+                info->distribution = g_strdup(value);
+            }
+        }
+    }
+    free(line);
+}
+
+static char *ga_stripped_strdup(char const *value)
+{
+    char *result = NULL;
+    while (value && *value == '"') {
+        ++value;
+    }
+    result = g_strdup(value);
+    ga_strip_end(result);
+    return result;
+}
+
+static void ga_parse_lsb_release(FILE *fp, GuestOSInfo *info)
+{
+    char *line = NULL;
+    size_t n = 0;
+
+    while (getline(&line, &n, fp) != -1) {
+        char *value = strstr(line, "=");
+        if (value != NULL) {
+            *value = 0;
+            ++value;
+            ga_strip_end(value);
+
+            size_t len = strlen(line);
+            if (len == 15 && strcmp(line, "DISTRIB_RELEASE") == 0) {
+                info->version = ga_stripped_strdup(value);
+            } else if (len == 16 && strcmp(line, "DISTRIB_CODENAME") == 0) {
+                info->codename = ga_stripped_strdup(value);
+            } else if (len == 10 && strcmp(line, "DISTRIB_ID") == 0) {
+                info->distribution = ga_stripped_strdup(value);
+            }
+        }
+    }
+}
+
+typedef struct _ga_distribution_info_t {
+    char const *file_path;
+    void(*implementation)(FILE *fp, GuestOSInfo *info);
+} ga_distribution_info_t;
+
+static ga_distribution_info_t const GA_DISTIBUTION_INFO[] = {
+    {"/etc/os-release", ga_parse_os_release},
+    {"/usr/lib/os-release", ga_parse_os_release},
+    {"/etc/lsb-release", ga_parse_lsb_release},
+    {"/etc/redhat-release", ga_parse_redhat_release},
+    {"/etc/gentoo-release", ga_parse_redhat_release},
+    {"/etc/debian_version", ga_parse_debian_version},
+    {0, 0}
+};
+
+static void ga_get_linux_distribution_info(GuestOSInfo *info)
+{
+    FILE *fp = NULL;
+    ga_distribution_info_t const *distrib = GA_DISTIBUTION_INFO;
+    while (distrib->file_path) {
+        fp = fopen(distrib->file_path, "r");
+        if (fp != NULL) {
+            distrib->implementation(fp, info);
+            fclose(fp);
+            break;
+        }
+        ++distrib;
+    }
+}
+
+GuestOSInfo *qmp_guest_get_osinfo(Error **errp)
+{
+    GuestOSInfo *info = g_new0(GuestOSInfo, 1);
+    memset(info, 0, sizeof(GuestOSInfo));
+    struct utsname kinfo;
+
+    ga_get_linux_distribution_info(info);
+
+    if (!info->codename || !info->distribution || !info->version) {
+        qapi_free_GuestOSInfo(info);
+        return NULL;
+    }
+    info->type = GUESTOS_TYPE_LINUX;
+    uname(&kinfo);
+    info->kernel = g_strdup(kinfo.release);
+    info->arch = g_strdup(kinfo.machine);
+
+    return info;
+}
+
 #else /* defined(__linux__) */
 
 void qmp_guest_suspend_disk(Error **errp)
@@ -2418,6 +2601,12 @@ GuestMemoryBlockInfo 
*qmp_guest_get_memory_block_info(Error **errp)
     return NULL;
 }
 
+GuestOSInfo *qmp_guest_get_osinfo(Error **errp)
+{
+    error_setg(errp, QERR_UNSUPPORTED);
+    return NULL;
+}
+
 #endif
 
 #if !defined(CONFIG_FSFREEZE)
diff --git a/qga/commands-win32.c b/qga/commands-win32.c
index 19d72b2..97bba7c 100644
--- a/qga/commands-win32.c
+++ b/qga/commands-win32.c
@@ -1536,3 +1536,107 @@ void ga_command_state_init(GAState *s, GACommandState 
*cs)
         ga_command_state_add(cs, NULL, guest_fsfreeze_cleanup);
     }
 }
+
+typedef struct _ga_matrix_lookup_t {
+    int major;
+    int minor;
+    char const *name;
+} ga_matrix_lookup_t;
+
+static ga_matrix_lookup_t const WIN_VERSION_MATRIX[2][8] = {
+    {
+        { 5, 0, "Win 2000"},
+        { 5, 1, "Win XP"},
+        { 6, 0, "Win Vista"},
+        { 6, 1, "Win 7"},
+
+        { 6, 2, "Win 8"},
+        { 6, 3, "Win 8.1"},
+        {10, 0, "Win 10"},
+        { 0, 0, 0}
+    },{
+        { 5, 2, "Win 2003"},
+        { 6, 0, "Win 2008"},
+        { 6, 1, "Win 2008 R2"},
+        { 6, 2, "Win 2012"},
+        { 6, 3, "Win 2012 R2"},
+        {10, 0, "Win 2016"},
+        { 0, 0, 0},
+        { 0, 0, 0}
+    }
+};
+
+static void ga_get_version(OSVERSIONINFOEXW *info)
+{
+    typedef NTSTATUS(WINAPI * rtl_get_version_t)(
+        OSVERSIONINFOEXW *os_version_info_ex);
+    HMODULE module = GetModuleHandle("ntdll");
+    PVOID fun = GetProcAddress(module, "RtlGetVersion");
+    rtl_get_version_t rtl_get_version = (rtl_get_version_t)fun;
+    rtl_get_version(info);
+}
+
+static char *ga_get_win_ver(void)
+{
+    OSVERSIONINFOEXW os_version;
+    ga_get_version(&os_version);
+    int major = (int)os_version.dwMajorVersion;
+    int minor = (int)os_version.dwMinorVersion;
+    return g_strdup_printf("%d.%d", major, minor);
+}
+
+static char *ga_get_win_name(void)
+{
+    OSVERSIONINFOEXW os_version;
+    ga_get_version(&os_version);
+    int major = (int)os_version.dwMajorVersion;
+    int minor = (int)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->name != NULL) {
+        if (major == table->major && minor == table->minor) {
+            return g_strdup(table->name);
+        }
+        ++table;
+    }
+    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:
+        /* Stays NULL in unknown cases */
+        break;
+    }
+    return result;
+}
+
+GuestOSInfo *qmp_guest_get_osinfo(Error **errp)
+{
+    GuestOSInfo *info = g_new0(GuestOSInfo, 1);
+    info->type = GUESTOS_TYPE_WINDOWS;
+    info->version = ga_get_win_ver();
+    info->codename = ga_get_win_name();
+    info->arch = ga_get_current_arch();
+    /* Not available on Windows */
+    info->kernel = NULL;
+    info->distribution = NULL;
+    return info;
+}
diff --git a/qga/qapi-schema.json b/qga/qapi-schema.json
index a02dbf2..c5019a4 100644
--- a/qga/qapi-schema.json
+++ b/qga/qapi-schema.json
@@ -1042,3 +1042,43 @@
   'data':    { 'path': 'str', '*arg': ['str'], '*env': ['str'],
                '*input-data': 'str', '*capture-output': 'bool' },
   'returns': 'GuestExec' }
+
+##
+# @GuestOSType:
+#
+# @linux:    Indicator for linux distributions
+# @windows:  Indicator for windows versions
+# @other:    Indicator for any other operating system that is not yet
+#            explicitly supported
+#
+# Since: 2.10
+##
+{ 'enum': 'GuestOSType', 'data': ['linux', 'windows', 'other'] }
+
+##
+# @GuestOSInfo:
+#
+# @version:      OS version, e.g. 25 for FC25 etc.
+# @distribution: Fedora, Ubuntu, Debian, CentOS...
+# @codename:     Code name of the OS. e.g. Ubuntu has Xenial Xerus etc.
+# @arch:         Architecture of the OS e.g. x86, x86_64, ppc64, aarch64...
+# @type:         Specifies the type of the OS.
+# @kernel:       Linux kernel version (Might be used by other OS types too).
+#                May be empty.
+# Since: 2.10
+##
+{ 'struct': 'GuestOSInfo',
+  'data': { '*version': 'str', '*distribution': 'str', '*codename': 'str',
+            '*arch': 'str', 'type': 'GuestOSType', '*kernel': 'str'} }
+
+##
+# @guest-get-osinfo:
+#
+# Retrieve guest operating system information
+#
+# Returns: operating system information on success
+#
+# Since 2.10
+##
+{ 'command': 'guest-get-osinfo',
+  'returns': 'GuestOSInfo' }
-- 
2.9.3




reply via email to

[Prev in Thread] Current Thread [Next in Thread]