From nobody Thu May 2 18:07:48 2024 Delivered-To: importer@patchew.org Received-SPF: pass (zoho.com: domain of gnu.org designates 208.118.235.17 as permitted sender) client-ip=208.118.235.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists.gnu.org; Authentication-Results: mx.zoho.com; spf=pass (zoho.com: domain of gnu.org designates 208.118.235.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; Return-Path: Received: from lists.gnu.org (lists.gnu.org [208.118.235.17]) by mx.zohomail.com with SMTPS id 1490096445111693.5140020883427; Tue, 21 Mar 2017 04:40:45 -0700 (PDT) Received: from localhost ([::1]:38822 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1cqI9b-0006eS-7n for importer@patchew.org; Tue, 21 Mar 2017 07:40:43 -0400 Received: from eggs.gnu.org ([2001:4830:134:3::10]:36388) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1cqI96-0006eL-UO for qemu-devel@nongnu.org; Tue, 21 Mar 2017 07:40:14 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1cqI92-00087n-Pt for qemu-devel@nongnu.org; Tue, 21 Mar 2017 07:40:13 -0400 Received: from mx1.redhat.com ([209.132.183.28]:47470) by eggs.gnu.org with esmtps (TLS1.0:DHE_RSA_AES_256_CBC_SHA1:32) (Exim 4.71) (envelope-from ) id 1cqI92-00084x-GV for qemu-devel@nongnu.org; Tue, 21 Mar 2017 07:40:08 -0400 Received: from smtp.corp.redhat.com (int-mx04.intmail.prod.int.phx2.redhat.com [10.5.11.14]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mx1.redhat.com (Postfix) with ESMTPS id 8CB9CC0567A2; Tue, 21 Mar 2017 11:40:07 +0000 (UTC) Received: from localhost.localdomain.com (dhcp131-51.brq.redhat.com [10.34.131.51]) by smtp.corp.redhat.com (Postfix) with ESMTP id 8916777A03; Tue, 21 Mar 2017 11:40:05 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.3.2 mx1.redhat.com 8CB9CC0567A2 Authentication-Results: ext-mx08.extmail.prod.ext.phx2.redhat.com; dmarc=none (p=none dis=none) header.from=redhat.com Authentication-Results: ext-mx08.extmail.prod.ext.phx2.redhat.com; spf=pass smtp.mailfrom=vfeenstr@redhat.com DKIM-Filter: OpenDKIM Filter v2.11.0 mx1.redhat.com 8CB9CC0567A2 From: Vinzenz 'evilissimo' Feenstra To: qemu-devel@nongnu.org Date: Thu, 16 Mar 2017 11:23:51 +0100 Message-Id: <20170316102351.25056-1-vfeenstr@redhat.com> X-Scanned-By: MIMEDefang 2.79 on 10.5.11.14 X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.5.16 (mx1.redhat.com [10.5.110.32]); Tue, 21 Mar 2017 11:40:07 +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] 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+importer=patchew.org@nongnu.org Sender: "Qemu-devel" X-ZohoMail: RSF_0 Z_629925259 SPT_0 Content-Transfer-Encoding: quoted-printable MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" 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 | 206 +++++++++++++++++++++++++++++++++++++++++++++++= ++++ qga/commands-win32.c | 105 ++++++++++++++++++++++++++ qga/qapi-schema.json | 40 ++++++++++ 3 files changed, 351 insertions(+) diff --git a/qga/commands-posix.c b/qga/commands-posix.c index 73d93eb..70ec3fb 100644 --- a/qga/commands-posix.c +++ b/qga/commands-posix.c @@ -13,6 +13,7 @@ =20 #include "qemu/osdep.h" #include +#include #include #include #include "qga/guest-agent-core.h" @@ -2356,6 +2357,205 @@ GuestMemoryBlockInfo *qmp_guest_get_memory_block_in= fo(Error **errp) return info; } =20 +static void ga_strip_end(char *value) +{ + size_t value_length =3D strlen(value); + while (value_length > 0) { + switch (value[value_length - 1]) { + default: + value_length =3D 0; + break; + case ' ': case '\n': case '\t': case '\'': case '"': + value[value_length - 1] =3D 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 =3D=3D '"') { + ++value; + } + + if (sscanf(value, "%[^(] (%[^)])", version, codename) =3D=3D 2) { + /* eg. VERSION=3D"16.04.1 LTS (Xenial Xerus)" */ + info->codename =3D strdup(codename); + info->version =3D strdup(version); + } else if (sscanf(value, "%[^,] %[^\"]\"", version, codename) =3D= =3D 2) { + /* eg. VERSION=3D"12.04.5 LTS, Precise Pangolin" */ + info->codename =3D strdup(codename); + info->version =3D strdup(version); + } else { + /* Just use the rest */ + info->version =3D strdup(version); + info->codename =3D strdup(""); + } + } +} + +static void ga_parse_debian_version(FILE *fp, GuestOSInfo *info) +{ + char *line =3D NULL; + size_t n =3D 0; + + if (getline(&line, &n, fp) !=3D -1) { + ga_strip_end(line); + info->version =3D strdup(line); + info->codename =3D strdup(""); + info->distribution =3D strdup("Debian GNU/Linux"); + } + free(line); +} + +static void ga_parse_redhat_release(FILE *fp, GuestOSInfo *info) +{ + char *line =3D NULL; + size_t n =3D 0; + + if (getline(&line, &n, fp) !=3D -1) { + char *value =3D strstr(line, " release "); + if (value !=3D NULL) { + *value =3D 0; + info->distribution =3D strdup(line); + value +=3D 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 =3D NULL; + size_t n =3D 0; + while (getline(&line, &n, fp) !=3D -1) { + char *value =3D strstr(line, "=3D"); + if (value !=3D NULL) { + *value =3D 0; + ++value; + ga_strip_end(value); + + size_t len =3D strlen(line); + if (len =3D=3D 9 && strcmp(line, "VERSION_ID") =3D=3D 0) { + info->version =3D strdup(value); + } else if (len =3D=3D 7 && strcmp(line, "VERSION") =3D=3D 0) { + ga_parse_version_id(value, info); + } else if (len =3D=3D 4 && strcmp(line, "NAME") =3D=3D 0) { + info->distribution =3D strdup(value); + } + } + } + free(line); +} + +static char *ga_stripped_strdup(char const *value) +{ + char *result =3D NULL; + while (value && *value =3D=3D '"') { + ++value; + } + result =3D strdup(value); + ga_strip_end(result); + return result; +} + +static void ga_parse_lsb_release(FILE *fp, GuestOSInfo *info) +{ + char *line =3D NULL; + size_t n =3D 0; + + while (getline(&line, &n, fp) !=3D -1) { + char *value =3D strstr(line, "=3D"); + if (value !=3D NULL) { + *value =3D 0; + ++value; + ga_strip_end(value); + + size_t len =3D strlen(line); + if (len =3D=3D 15 && strcmp(line, "DISTRIB_RELEASE") =3D=3D 0)= { + info->version =3D ga_stripped_strdup(value); + } else if (len =3D=3D 16 && strcmp(line, "DISTRIB_CODENAME") = =3D=3D 0) { + info->codename =3D ga_stripped_strdup(value); + } else if (len =3D=3D 10 && strcmp(line, "DISTRIB_ID") =3D=3D = 0) { + info->distribution =3D ga_stripped_strdup(value); + } + } + } +} + +static void ga_get_linux_distribution_info(GuestOSInfo *info) +{ + FILE *fp =3D NULL; + fp =3D fopen("/etc/os-release", "r"); + if (fp !=3D NULL) { + ga_parse_os_release(fp, info); + goto cleanup; + } + fp =3D fopen("/usr/lib/os-release", "r"); + if (fp !=3D NULL) { + ga_parse_os_release(fp, info); + goto cleanup; + } + fp =3D fopen("/etc/lsb-release", "r"); + if (fp !=3D NULL) { + ga_parse_lsb_release(fp, info); + goto cleanup; + } + fp =3D fopen("/etc/redhat-release", "r"); + if (fp !=3D NULL) { + ga_parse_redhat_release(fp, info); + goto cleanup; + } + fp =3D fopen("/etc/gentoo-release", "r"); + if (fp !=3D NULL) { + ga_parse_redhat_release(fp, info); + goto cleanup; + } + fp =3D fopen("/etc/debian_version", "r"); + if (fp !=3D NULL) { + ga_parse_debian_version(fp, info); + goto cleanup; + } + + if (fp =3D=3D NULL) { + info->distribution =3D strdup("Unknown"); + info->version =3D strdup(""); + info->codename =3D strdup(""); + } + +cleanup: + if (fp !=3D NULL) { + fclose(fp); + } + +} + +GuestOSInfo *qmp_guest_get_osinfo(Error **errp) +{ + GuestOSInfo *info =3D g_new0(GuestOSInfo, 1); + struct utsname kinfo; + + ga_get_linux_distribution_info(info); + + if (!info->codename || !info->distribution || !info->version) { + qapi_free_GuestOSInfo(info); + return NULL; + } + info->type =3D GUESTOS_TYPE_LINUX; + uname(&kinfo); + info->kernel =3D g_strdup(kinfo.release); + info->arch =3D g_strdup(kinfo.machine); + + return info; +} + #else /* defined(__linux__) */ =20 void qmp_guest_suspend_disk(Error **errp) @@ -2418,6 +2618,12 @@ GuestMemoryBlockInfo *qmp_guest_get_memory_block_inf= o(Error **errp) return NULL; } =20 +GuestOSInfo *qmp_guest_get_osinfo(Error **errp) +{ + error_setg(errp, QERR_UNSUPPORTED); + return NULL; +} + #endif =20 #if !defined(CONFIG_FSFREEZE) diff --git a/qga/commands-win32.c b/qga/commands-win32.c index 19d72b2..b11cfde 100644 --- a/qga/commands-win32.c +++ b/qga/commands-win32.c @@ -1536,3 +1536,108 @@ void ga_command_state_init(GAState *s, GACommandSta= te *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] =3D { + { + { 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 =3D GetModuleHandle("ntdll"); + PVOID fun =3D GetProcAddress(module, "RtlGetVersion"); + rtl_get_version_t rtl_get_version =3D (rtl_get_version_t)fun; + rtl_get_version(info); +} + +static char *ga_get_win_ver(void) +{ + OSVERSIONINFOEXW os_version; + ga_get_version(&os_version); + char buf[64] =3D {}; + int major =3D (int)os_version.dwMajorVersion; + int minor =3D (int)os_version.dwMinorVersion; + sprintf(buf, "%d.%d", major, minor); + return strdup(buf); +} + +static char *ga_get_win_name(void) +{ + OSVERSIONINFOEXW os_version; + ga_get_version(&os_version); + int major =3D (int)os_version.dwMajorVersion; + int minor =3D (int)os_version.dwMinorVersion; + int tbl_idx =3D (os_version.wProductType !=3D VER_NT_WORKSTATION); + ga_matrix_lookup_t const *table =3D WIN_VERSION_MATRIX[tbl_idx]; + while (table->name !=3D NULL) { + if (major =3D=3D table->major && minor =3D=3D table->minor) { + return strdup(table->name); + } + ++table; + } + return strdup("N/A"); +} + +static char *ga_get_current_arch(void) +{ + SYSTEM_INFO info; + GetNativeSystemInfo(&info); + char *result =3D NULL; + switch (info.wProcessorArchitecture) { + case PROCESSOR_ARCHITECTURE_AMD64: + result =3D strdup("x86_64");; + break; + case PROCESSOR_ARCHITECTURE_ARM: + result =3D strdup("arm");; + break; + case PROCESSOR_ARCHITECTURE_IA64: + result =3D strdup("ia64");; + break; + case PROCESSOR_ARCHITECTURE_INTEL: + result =3D strdup("x86");; + break; + case PROCESSOR_ARCHITECTURE_UNKNOWN: default: + result =3D strdup("N/A");; + break; + } + return result; +} + +GuestOSInfo *qmp_guest_get_osinfo(Error **errp) +{ + GuestOSInfo *info =3D g_new0(GuestOSInfo, 1); + info->type =3D GUESTOS_TYPE_WINDOWS; + info->version =3D ga_get_win_ver(); + info->codename =3D ga_get_win_name(); + info->arch =3D ga_get_current_arch(); + /* Not available on Windows */ + info->kernel =3D strdup(""); + info->distribution =3D strdup(""); + return info; +} diff --git a/qga/qapi-schema.json b/qga/qapi-schema.json index a02dbf2..e31cb17 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.8 +## +{ '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.8 +## +{ '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.8 +## +{ 'command': 'guest-get-osinfo', + 'returns': 'GuestOSInfo' } --=20 2.9.3