From nobody Wed Nov 5 15:49:06 2025 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 1496756025270576.9750181198173; Tue, 6 Jun 2017 06:33:45 -0700 (PDT) Received: from localhost ([::1]:38463 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1dIEc3-0006WR-Hh for importer@patchew.org; Tue, 06 Jun 2017 09:33:35 -0400 Received: from eggs.gnu.org ([2001:4830:134:3::10]:47591) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1dIEap-0005eE-QW for qemu-devel@nongnu.org; Tue, 06 Jun 2017 09:32:21 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1dIEam-0002Z2-EP for qemu-devel@nongnu.org; Tue, 06 Jun 2017 09:32:19 -0400 Received: from mail-wm0-f48.google.com ([74.125.82.48]:36998) by eggs.gnu.org with esmtps (TLS1.0:RSA_AES_128_CBC_SHA1:16) (Exim 4.71) (envelope-from ) id 1dIEam-0002YY-4E for qemu-devel@nongnu.org; Tue, 06 Jun 2017 09:32:16 -0400 Received: by mail-wm0-f48.google.com with SMTP id d73so49014321wma.0 for ; Tue, 06 Jun 2017 06:32:15 -0700 (PDT) Received: from fiorina.brq.redhat.com (nat-pool-brq-t.redhat.com. [213.175.37.10]) by smtp.gmail.com with ESMTPSA id v22sm15714016wrd.38.2017.06.06.06.32.11 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Tue, 06 Jun 2017 06:32:12 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=t1bXl4Q4dKhwbwoVgAO3F1Pd9ZdEWiEIl/nX8QPpTh8=; b=e0FEYvb30cIO1Q9wNLsGnTfsKtM52HVU9cp2EwkYx/94Avw9c9mA/UWWiBBRymSHK8 uBT4DNt6N5xgFjKdCFyIrrIvdNwpDmbtL0seYmiR4M5Sb4p/zN6m6J2RO0TRqnNjkSg4 d6H6piWCnDvJr1EOcefWvxsKWmaHw9KClNoz5sR8v8PQHSnw3kEOeI3jmyu+eP4ofOBT lYTcid6ARlXDs3U+UBHqSOwMxokS3CyY0HiPq8dwl/+qC8zKvLeKDsuWGMC5dJJJ84Iw LmRFre6Y/iJdfE8+Uta8ZQlkB5HePCETsKYNCZpG0HID5PKqqVJe7p3WWzJKnZQzXRYi nEwg== X-Gm-Message-State: AODbwcDPwbGfC0QS2BKGf037SsYKAaPuExrYDTj31lzRi+RH0A9DRoSG 2plS96MdLwq9OW08 X-Received: by 10.28.184.85 with SMTP id i82mr11418245wmf.64.1496755934175; Tue, 06 Jun 2017 06:32:14 -0700 (PDT) From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Golembiovsk=C3=BD?= To: =?UTF-8?q?Marc-Andr=C3=A9=20Lureau?= , Eric Blake , Michael Roth , Vinzenz 'evilissimo' Feenstra Date: Tue, 6 Jun 2017 15:32:07 +0200 Message-Id: X-Mailer: git-send-email 2.13.0 In-Reply-To: References: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic] [fuzzy] X-Received-From: 74.125.82.48 Subject: [Qemu-devel] [PATCH v4 1/1] 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: =?UTF-8?q?Tom=C3=A1=C5=A1=20Golembiovsk=C3=BD?= , qemu-devel@nongnu.org Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: "Qemu-devel" X-ZohoMail: RSF_0 Z_629925259 SPT_0 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 Signed-off-by: Tom=C3=A1=C5=A1 Golembiovsk=C3=BD --- configure | 2 +- qga/commands-posix.c | 155 ++++++++++++++++++++++++++++++++++++++++++++++ qga/commands-win32.c | 170 +++++++++++++++++++++++++++++++++++++++++++++++= ++++ qga/qapi-schema.json | 57 +++++++++++++++++ 4 files changed, 383 insertions(+), 1 deletion(-) diff --git a/configure b/configure index 21944eaa05..94e294e350 100755 --- a/configure +++ b/configure @@ -3131,7 +3131,7 @@ if test "$mingw32" =3D yes; then else glib_req_ver=3D2.22 fi -glib_modules=3Dgthread-2.0 +glib_modules=3D"gthread-2.0 gio-2.0" if test "$modules" =3D yes; then glib_modules=3D"$glib_modules gmodule-2.0" fi diff --git a/qga/commands-posix.c b/qga/commands-posix.c index 284ecc6d7e..f43a1b201e 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 @@ -24,6 +25,7 @@ #include "qemu/sockets.h" #include "qemu/base64.h" #include "qemu/cutils.h" +#include =20 #ifndef CONFIG_HAS_ENVIRON #ifdef __APPLE__ @@ -2579,3 +2581,156 @@ GuestUserList *qmp_guest_get_users(Error **err) g_hash_table_destroy(cache); return head; } + +static GHashTable* ga_parse_osrelease(const char *fname) +{ + GFile *f; + GFileInputStream *fis =3D NULL; + GDataInputStream* dis =3D NULL; + GError *err =3D NULL; + gsize length; + char *line; + char *value; + GHashTable *tbl =3D NULL; + + /* Open the file */ + f =3D g_file_new_for_path(fname); + fis =3D g_file_read(f, NULL, &err); + if (err !=3D NULL) { + slog("failed to open file '%s', error: %s", fname, err->message); + g_error_free(err); + g_object_unref(f); + return NULL; + } + dis =3D g_data_input_stream_new(G_INPUT_STREAM(fis)); + + err =3D NULL; + tbl =3D g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); + /* Go through all lines */ + line =3D NULL; + while (err =3D=3D NULL) { + g_free(line); + line =3D g_data_input_stream_read_line_utf8(dis, &length, NULL, &e= rr); + if (err !=3D NULL) { + slog("failed to open file '%s', error: %s", fname, err->messag= e); + g_error_free(err); + break; + } else if (line =3D=3D NULL) { + /* EOF */ + break; + } else if((length =3D=3D 0) || (line[0] =3D=3D '#')) { + continue; + } + + value =3D strchr(line, '=3D'); + if (value =3D=3D NULL) { + continue; + } + value[0] =3D 0; + value++; + + if ((value[0] =3D=3D '"') || (value[0] =3D=3D '\'')) { + char *p; + char end =3D value[0]; + value++; + p =3D value; + while (*p !=3D 0) { + if (*p =3D=3D '\\') { + switch(*(p+1)) { + case '\'': + case '"': + case '$': + case '`': + memmove(p, p+1, strlen(p+1)); + break; + default: + /* Keep literal backslash */ + p++; + break; + } + if (*p !=3D 0) { + p++; + } + continue; + } + if (*p =3D=3D end) { + *p =3D 0; + break; + } + p++; + } + } + g_hash_table_insert(tbl, g_strdup(line), g_strdup(value)); + } + g_free(line); + + g_object_unref(fis); + g_object_unref(dis); + g_object_unref(f); + + return tbl; +} + +GuestOSInfo *qmp_guest_get_osinfo(Error **errp) +{ + GuestOSInfo *info =3D g_new0(GuestOSInfo, 1); + memset(info, 0, sizeof(GuestOSInfo)); + + struct utsname kinfo; + uname(&kinfo); + + info->kernel_version =3D g_strdup(kinfo.version); + info->kernel_release =3D g_strdup(kinfo.release); + info->machine_hardware =3D g_strdup(kinfo.machine); + + GHashTable *osrelease =3D ga_parse_osrelease("/etc/os-release"); + if (osrelease =3D=3D NULL) { + slog("could not read /etc/os-release"); + osrelease =3D ga_parse_osrelease("/usr/lib/os-release"); + if (osrelease =3D=3D NULL) { + slog("could not read /usr/lib/os-release"); + } + } + + if (osrelease !=3D NULL) { + char *value; + value =3D g_hash_table_lookup(osrelease, "ID"); + if (value !=3D NULL) { + info->has_id =3D true; + info->id =3D g_strdup(value); + } + value =3D g_hash_table_lookup(osrelease, "PRETTY_NAME"); + if (value !=3D NULL) { + info->has_pretty_name =3D true; + info->pretty_name =3D g_strdup(value); + } + value =3D g_hash_table_lookup(osrelease, "NAME"); + if (value !=3D NULL) { + info->has_name =3D true; + info->name =3D g_strdup(value); + } + value =3D g_hash_table_lookup(osrelease, "VERSION"); + if (value !=3D NULL) { + info->has_version =3D true; + info->version =3D g_strdup(value); + } + value =3D g_hash_table_lookup(osrelease, "VERSION_ID"); + if (value !=3D NULL) { + info->has_version_id =3D true; + info->version_id =3D g_strdup(value); + } + value =3D g_hash_table_lookup(osrelease, "VARIANT"); + if (value !=3D NULL) { + info->has_variant =3D true; + info->variant =3D g_strdup(value); + } + value =3D g_hash_table_lookup(osrelease, "VARIANT_ID"); + if (value !=3D NULL) { + info->has_variant_id =3D true; + info->variant_id =3D g_strdup(value); + } + g_hash_table_destroy(osrelease); + } + + return info; +} diff --git a/qga/commands-win32.c b/qga/commands-win32.c index 439d229225..f9867baf1f 100644 --- a/qga/commands-win32.c +++ b/qga/commands-win32.c @@ -1639,3 +1639,173 @@ GuestUserList *qmp_guest_get_users(Error **err) return NULL; #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] =3D { + { + /* 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) +{ + info->dwOSVersionInfoSize =3D sizeof(RTL_OSVERSIONINFOEXW); + + typedef NTSTATUS(WINAPI * rtl_get_version_t)( + RTL_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_name(OSVERSIONINFOEXW const *os_version, bool id) +{ + DWORD major =3D os_version->dwMajorVersion; + DWORD minor =3D 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->version !=3D NULL) { + if (major =3D=3D table->major && minor =3D=3D table->minor) { + if (id) { + return g_strdup(table->version_id); + } else { + return g_strdup(table->version); + } + } + ++table; + } + slog("failed to lookup Windows version: major=3D%lu, minor=3D%lu", + major, minor); + return g_strdup("N/A"); +} + +static char *ga_get_win_product_name(void) +{ + HKEY key =3D NULL; + DWORD size =3D 128; + char *result =3D g_malloc0(size); + memset(result, 0, size); + LONG err =3D ERROR_SUCCESS; + + err =3D RegOpenKeyA(HKEY_LOCAL_MACHINE, + "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", + &key); + if (err =3D=3D ERROR_SUCCESS) { + err =3D RegQueryValueExA(key, "ProductName", NULL, NULL, + (LPBYTE)result, &size); + if (err =3D=3D ERROR_MORE_DATA) { + slog("ProductName longer than expected (%lu bytes), retrying", + size); + g_free(result); + result =3D NULL; + if (size > 0) { + result =3D g_malloc0(size); + err =3D RegQueryValueExA(key, "ProductName", NULL, NULL, + (LPBYTE)result, &size); + if (err !=3D ERROR_SUCCESS) { + g_free(result); + result =3D NULL; + } + } + } + if (err !=3D ERROR_SUCCESS) { + slog("failed to retrive ProductName"); + } + } + return result; +} + +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 g_strdup("x86_64"); + break; + case PROCESSOR_ARCHITECTURE_ARM: + result =3D g_strdup("arm"); + break; + case PROCESSOR_ARCHITECTURE_IA64: + result =3D g_strdup("ia64"); + break; + case PROCESSOR_ARCHITECTURE_INTEL: + result =3D g_strdup("x86"); + break; + case PROCESSOR_ARCHITECTURE_UNKNOWN: + default: + slog("unknown processor architecture 0x%0x", + info.wProcessorArchitecture); + result =3D g_strdup("unknown"); + break; + } + return result; +} + +GuestOSInfo *qmp_guest_get_osinfo(Error **errp) +{ + OSVERSIONINFOEXW os_version; + ga_get_win_version(&os_version); + + bool server =3D os_version.wProductType !=3D VER_NT_WORKSTATION; + char *product_name =3D ga_get_win_product_name(); + if (product_name =3D=3D NULL) { + Error *local_err =3D NULL; + error_setg(&local_err, QERR_QGA_COMMAND_FAILED, + "failed to retrieve ProductName from registry"); + error_propagate(errp, local_err); + return NULL; + } + + GuestOSInfo *info =3D g_new0(GuestOSInfo, 1); + + info->kernel_version =3D g_strdup_printf("%lu.%lu", + os_version.dwMajorVersion, + os_version.dwMinorVersion); + info->kernel_release =3D g_strdup_printf("%lu", + os_version.dwBuildNumber); + info->machine_hardware =3D ga_get_current_arch(); + + info->has_id =3D true; + info->id =3D g_strdup("mswindows"); + info->has_name =3D true; + info->name =3D g_strdup("Microsoft Windows"); + info->has_pretty_name =3D true; + info->pretty_name =3D product_name; + info->has_version =3D true; + info->version =3D ga_get_win_name(&os_version, false); + info->has_version_id =3D true; + info->version_id =3D ga_get_win_name(&os_version, true); + info->has_variant =3D true; + info->variant =3D g_strdup(server ? "server" : "client"); + info->has_variant_id =3D true; + info->variant_id =3D g_strdup(server ? "server" : "client"); + + return info; +} diff --git a/qga/qapi-schema.json b/qga/qapi-schema.json index 03743ab905..d72903ff8a 100644 --- a/qga/qapi-schema.json +++ b/qga/qapi-schema.json @@ -1126,3 +1126,60 @@ ## { 'command': 'guest-get-timezone', 'returns': 'GuestTimezone' } + +## +# @GuestOSInfo: +# +# Notes: +# +# The fields @id, @name, @pretty-name, @version, @version-id, @variant and +# @variant-id follow the definition specified in os-release(5). Refer to t= he +# manual page for exact description of the fields. +# +# On POSIX systems: +# +# kernel-release: release field returned by uname(2) +# kernel-version': version field returned by uname(2) +# machine-hardware': machine field returned by uname(2) +# id: as defined by os-release(5) +# name: as defined by os-release(5) +# pretty-name: as defined by os-release(5) +# version: as defined by os-release(5) +# version-id: as defined by os-release(5) +# variant: as defined by os-release(5) +# variant-id: as defined by os-release(5) +#=20 +# +# On Windows: +#=20 +# kernel-release: version number of the OS +# kernel-version': build number of the OS +# machine-hardware': architecture of the hardware +# id: contains string "mswindows" +# name: contains string "Microsoft Windows" +# pretty-name: product name, e.g. "Microsoft Windows 10 Enterprise" +# version: long version string, e.g. "Microsoft Windows Server 2008" +# version-id: short version identifier, e.g. "7" or "20012r2" +# variant: contains string "server" or "client" +# variant-id: contains string "server" or "client" +# +# Since: 2.10 +## +{ 'struct': 'GuestOSInfo', + 'data': { + 'kernel-release': 'str', 'kernel-version': 'str', + 'machine-hardware': '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: operating system information on success +# +# Since 2.10 +## +{ 'command': 'guest-get-osinfo', + 'returns': 'GuestOSInfo' } --=20 2.13.0