From patchwork Thu Mar 16 14:50:45 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Vinzenz 'evilissimo' Feenstra X-Patchwork-Id: 741923 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from lists.gnu.org (lists.gnu.org [IPv6:2001:4830:134:3::11]) (using TLSv1 with cipher AES256-SHA (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 3vp3Qh0Wn1z9s7l for ; Wed, 22 Mar 2017 19:54:35 +1100 (AEDT) Received: from localhost ([::1]:49658 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1cqc2K-0005CL-Q6 for incoming@patchwork.ozlabs.org; Wed, 22 Mar 2017 04:54:32 -0400 Received: from eggs.gnu.org ([2001:4830:134:3::10]:46699) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1cqc1u-0005Al-0k for qemu-devel@nongnu.org; Wed, 22 Mar 2017 04:54:07 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1cqc1s-0001Bw-EM for qemu-devel@nongnu.org; Wed, 22 Mar 2017 04:54:06 -0400 Received: from mx1.redhat.com ([209.132.183.28]:46874) by eggs.gnu.org with esmtps (TLS1.0:DHE_RSA_AES_256_CBC_SHA1:32) (Exim 4.71) (envelope-from ) id 1cqc1s-0001Bj-4u for qemu-devel@nongnu.org; Wed, 22 Mar 2017 04:54:04 -0400 Received: from smtp.corp.redhat.com (int-mx03.intmail.prod.int.phx2.redhat.com [10.5.11.13]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mx1.redhat.com (Postfix) with ESMTPS id EFC0667BA8; Wed, 22 Mar 2017 08:54:03 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.3.2 mx1.redhat.com EFC0667BA8 Authentication-Results: ext-mx04.extmail.prod.ext.phx2.redhat.com; dmarc=none (p=none dis=none) header.from=redhat.com Authentication-Results: ext-mx04.extmail.prod.ext.phx2.redhat.com; spf=pass smtp.mailfrom=vfeenstr@redhat.com DKIM-Filter: OpenDKIM Filter v2.11.0 mx1.redhat.com EFC0667BA8 Received: from localhost.localdomain.com (dhcp131-51.brq.redhat.com [10.34.131.51]) by smtp.corp.redhat.com (Postfix) with ESMTP id B97CE17977; Wed, 22 Mar 2017 08:54:02 +0000 (UTC) From: Vinzenz 'evilissimo' Feenstra To: qemu-devel@nongnu.org Date: Thu, 16 Mar 2017 15:50:45 +0100 Message-Id: <20170316145045.10840-2-vfeenstr@redhat.com> In-Reply-To: <20170316145045.10840-1-vfeenstr@redhat.com> References: <0342808A-63C6-40BE-91AB-B6088AD62CD2@redhat.com> <20170316145045.10840-1-vfeenstr@redhat.com> X-Scanned-By: MIMEDefang 2.79 on 10.5.11.13 X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.5.16 (mx1.redhat.com [10.5.110.28]); Wed, 22 Mar 2017 08:54:04 +0000 (UTC) X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic] [fuzzy] X-Received-From: 209.132.183.28 Subject: [Qemu-devel] [PATCH v2] qemu-ga: add guest-get-osinfo command X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: mdroth@linux.vnet.ibm.com, Vinzenz Feenstra Errors-To: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Sender: "Qemu-devel" From: Vinzenz Feenstra 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 --- 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 +#include #include #include #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' }