From nobody Sat Sep 26 19:11:54 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=quarantine dis=none) header.from=redhat.com ARC-Seal: i=1; a=rsa-sha256; t=1790264869; cv=none; d=zohomail.com; s=zohoarc; b=ehCvme3LdaThpZQjTl19VT/1ORRyIokgsCeKxZG30VBuHXN5/neaW40SVIR21sx5FLSW3YFfsk7Gb7imp+K4nwU8u5WXHdglgXAUF58QxjHoIsQfUUl8dFm/DXBzUdlevy2h0Tyv2O5J1FOyDOHuqYtRuRLehCcCGHPulr9tPH0= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1790264869; h=Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=THrTuwZeegpA+RjsM+kxw+5CUyfp8I7WQFZr5eWUOQU=; b=UwEtolZcOixn3RWd9hAso7iZuxQnhQbYNGDJxUbUELBCDRv1mQpe/WeCQRoN6V4WhtUkqDdROHmaZ0YTfoNJsJlvbzFT2T/Jl6YszC8o6ZbgWuYjzV7V2rM1XOoO7KSs8KDKYGssU62rcy2WwJmRDkjff3iFcQBJ3ZxWyVg0oGU= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=quarantine dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1790264869375712.0808368150076; Thu, 24 Sep 2026 08:47:49 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1x9lfF-0004N3-AG; Thu, 24 Sep 2026 11:47:13 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1x9lf8-0004Ma-94 for qemu-devel@nongnu.org; Thu, 24 Sep 2026 11:47:06 -0400 Received: from us-smtp-delivery-124.mimecast.com ([170.10.133.124]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1x9lf4-0002NT-Nq for qemu-devel@nongnu.org; Thu, 24 Sep 2026 11:47:06 -0400 Received: from mx-prod-mc-08.mail-002.prod.us-west-2.aws.redhat.com (ec2-35-165-154-97.us-west-2.compute.amazonaws.com [35.165.154.97]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-329-QjWW2VCANgSl9XUyt-IRXQ-1; Thu, 24 Sep 2026 11:45:37 -0400 Received: from mx-prod-int-08.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-08.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.111]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-08.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id B6699183450E for ; Thu, 24 Sep 2026 15:45:36 +0000 (UTC) Received: from eashurov-thinkpadx1carbongen12.raanaii.csb (unknown [10.47.238.53]) by mx-prod-int-08.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 3723B18005B5; Thu, 24 Sep 2026 15:45:33 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1790264821; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding; bh=THrTuwZeegpA+RjsM+kxw+5CUyfp8I7WQFZr5eWUOQU=; b=J8+EozLZ9kbDKrbIF6L6kOGzzt2fs1oo43O79H7vSBvNtaV82Rn9LInMgx7nFHjorPN59e cfy95vHNklWW9zowbpPrk4LuxEL4vWtZJ+skpqrPx7jFXfMaMagR1fKsVLgOu1dZ5MoXqf GjfSTBwebY9u2pBO1RteRAEvhpp+GU8= X-MC-Unique: QjWW2VCANgSl9XUyt-IRXQ-1 X-Mimecast-MFC-AGG-ID: QjWW2VCANgSl9XUyt-IRXQ_1790264737 From: Elizabeth Ashurov To: qemu-devel@nongnu.org Cc: kkostiuk@redhat.com, dholler@redhat.com, armbru@redhat.com, fdeutsch@redhat.com, yvugenfi@redhat.com, Elizabeth Ashurov Subject: [PATCH] qga: Add guest-get-drivers command for Windows Date: Thu, 24 Sep 2026 18:44:24 +0300 Message-ID: <20260924154424.3786199-1-eashurov@redhat.com> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.111 Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=170.10.133.124; envelope-from=eashurov@redhat.com; helo=us-smtp-delivery-124.mimecast.com X-Spam_score_int: -13 X-Spam_score: -1.4 X-Spam_bar: - X-Spam_report: (-1.4 / 5.0 requ) BAYES_00=-1.9, DKIMWL_WL_HIGH=-0.001, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_NONE=-0.0001, RCVD_IN_MSPIKE_H3=0.001, RCVD_IN_MSPIKE_WL=0.001, SPF_HELO_PASS=-0.001, SPF_PASS=-0.001, URI_TRY_3LD=0.745 autolearn=no autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @redhat.com) X-ZM-MESSAGEID: 1790264872131158500 Content-Type: text/plain; charset="utf-8" Add a new guest-get-drivers command that reports two independent lists of Windows driver information: - packages: out-of-box driver packages enumerated with the DISM API (DismGetDrivers). Each entry includes the published INF name, original INF name, version, and provider. - services: registered driver services enumerated with the Service Control Manager (EnumServicesStatusExW). Each entry includes the service name, configured binary path, current SCM status, and FileVersion and CompanyName metadata from the driver binary. Load DismApi.dll dynamically with LoadLibraryExW so that its absence does not prevent qemu-ga from starting. Initialize DISM lazily once per QGA process and open a separate online DISM session for each command invocation, with process cleanup registered through ga_command_state_add. Normalize driver binary paths from the service database before reading version resources, including SystemRoot, environment-variable, NT-prefix, and relative path forms. Link against version.dll (-lversion) for the GetFileVersionInfo and VerQueryValue APIs. Signed-off-by: Elizabeth Ashurov --- qga/commands-win32.c | 683 +++++++++++++++++++++++++++++++++++++++++++ qga/meson.build | 5 +- qga/qapi-schema.json | 89 ++++++ 3 files changed, 775 insertions(+), 2 deletions(-) diff --git a/qga/commands-win32.c b/qga/commands-win32.c index 2bf5581aaa..0dc94d808e 100644 --- a/qga/commands-win32.c +++ b/qga/commands-win32.c @@ -28,6 +28,8 @@ #include #include #include +#include +#include =20 #include "guest-agent-core.h" #include "vss-win32.h" @@ -1981,12 +1983,15 @@ done: g_free(rawpasswddata); } =20 +static void ga_cleanup_dism_api(void); + /* register init/cleanup routines for stateful command groups */ void ga_command_state_init(GAState *s, GACommandState *cs) { if (!vss_initialized()) { ga_command_state_add(cs, NULL, guest_fsfreeze_cleanup); } + ga_command_state_add(cs, NULL, ga_cleanup_dism_api); } =20 /* MINGW is missing two fields: IncomingFrames & OutgoingFrames */ @@ -2455,6 +2460,684 @@ GuestDeviceInfoList *qmp_guest_get_devices(Error **= errp) return head; } =20 +static void error_setg_dism(Error **errp, HRESULT hr, const char *msg) +{ + if (HRESULT_FACILITY(hr) =3D=3D FACILITY_WIN32) { + error_setg_win32(errp, HRESULT_CODE(hr), "%s", msg); + } else { + error_setg(errp, "%s (HRESULT 0x%08" PRIx32 ")", msg, + (uint32_t)hr); + } +} + +static void ga_log_dism_error(HRESULT hr, const char *msg) +{ + Error *local_err =3D NULL; + + error_setg_dism(&local_err, hr, msg); + slog("%s", error_get_pretty(local_err)); + error_free(local_err); +} + +typedef HRESULT WINAPI QGADismInitializeFunc( + DismLogLevel log_level, PCWSTR log_file_path, PCWSTR scratch_directory= ); +typedef HRESULT WINAPI QGADismShutdownFunc(void); +typedef HRESULT WINAPI QGADismOpenSessionFunc( + PCWSTR image_path, PCWSTR windows_directory, PCWSTR system_drive, + DismSession *session); +typedef HRESULT WINAPI QGADismCloseSessionFunc(DismSession session); +typedef HRESULT WINAPI QGADismGetDriversFunc( + DismSession session, WINBOOL all_drivers, + DismDriverPackage **driver_package, unsigned int *driver_count); +typedef HRESULT WINAPI QGADismDeleteFunc(void *dism_structure); + +typedef struct QGADismApi { + HMODULE module; + QGADismInitializeFunc *initialize; + QGADismShutdownFunc *shutdown; + QGADismOpenSessionFunc *open_session; + QGADismCloseSessionFunc *close_session; + QGADismGetDriversFunc *get_drivers; + QGADismDeleteFunc *delete; +} QGADismApi; + +static QGADismApi dism_api; +static bool dism_initialized; + +static FARPROC ga_get_dism_function(HMODULE module, const char *name, + Error **errp) +{ + FARPROC function =3D GetProcAddress(module, name); + + if (function =3D=3D NULL) { + error_setg_win32(errp, GetLastError(), + "failed to resolve %s from DismApi.dll", name); + } + return function; +} + +static void ga_unload_dism_api(QGADismApi *api) +{ + if (api->module !=3D NULL && !FreeLibrary(api->module)) { + slog("failed to unload DismApi.dll, error=3D%lu", GetLastError()); + } + memset(api, 0, sizeof(*api)); +} + +/* + * Load DISM from System32 and resolve its entry points at runtime, so a + * missing DismApi.dll prevents only this command rather than QGA startup. + */ +static bool ga_load_dism_api(QGADismApi *api, Error **errp) +{ + api->module =3D LoadLibraryExW(L"DismApi.dll", NULL, + LOAD_LIBRARY_SEARCH_SYSTEM32); + if (api->module =3D=3D NULL) { + error_setg_win32(errp, GetLastError(), + "failed to load DismApi.dll"); + return false; + } + + api->initialize =3D (QGADismInitializeFunc *) + ga_get_dism_function(api->module, "DismInitialize", errp); + if (api->initialize =3D=3D NULL) { + goto fail; + } + api->shutdown =3D (QGADismShutdownFunc *) + ga_get_dism_function(api->module, "DismShutdown", errp); + if (api->shutdown =3D=3D NULL) { + goto fail; + } + api->open_session =3D (QGADismOpenSessionFunc *) + ga_get_dism_function(api->module, "DismOpenSession", errp); + if (api->open_session =3D=3D NULL) { + goto fail; + } + api->close_session =3D (QGADismCloseSessionFunc *) + ga_get_dism_function(api->module, "DismCloseSession", errp); + if (api->close_session =3D=3D NULL) { + goto fail; + } + api->get_drivers =3D (QGADismGetDriversFunc *) + ga_get_dism_function(api->module, "DismGetDrivers", errp); + if (api->get_drivers =3D=3D NULL) { + goto fail; + } + api->delete =3D (QGADismDeleteFunc *) + ga_get_dism_function(api->module, "DismDelete", errp); + if (api->delete =3D=3D NULL) { + goto fail; + } + + return true; + +fail: + ga_unload_dism_api(api); + return false; +} + +static bool ga_ensure_dism_api(Error **errp) +{ + HRESULT hr; + + if (dism_initialized) { + return true; + } + + if (!ga_load_dism_api(&dism_api, errp)) { + return false; + } + + hr =3D dism_api.initialize(DismLogErrors, NULL, NULL); + if (FAILED(hr)) { + error_setg_dism(errp, hr, "failed to initialize DISM"); + ga_unload_dism_api(&dism_api); + return false; + } + + dism_initialized =3D true; + return true; +} + +static void ga_cleanup_dism_api(void) +{ + HRESULT hr; + + if (dism_initialized) { + hr =3D dism_api.shutdown(); + if (FAILED(hr)) { + ga_log_dism_error(hr, "failed to shut down DISM"); + } + dism_initialized =3D false; + } + + ga_unload_dism_api(&dism_api); +} + +static char *ga_utf16_to_utf8_required(const WCHAR *str, const char *name, + Error **errp) +{ + g_autoptr(GError) gerr =3D NULL; + char *ret; + + if (str =3D=3D NULL || str[0] =3D=3D L'\0') { + error_setg(errp, "driver %s is missing", name); + return NULL; + } + + ret =3D g_utf16_to_utf8(str, -1, NULL, NULL, &gerr); + if (ret =3D=3D NULL) { + error_setg(errp, "failed to convert driver %s to UTF-8: %s", + name, gerr->message); + } + return ret; +} + +static const WCHAR *ga_windows_path_basename(const WCHAR *path) +{ + const WCHAR *name =3D path; + + while (*path !=3D L'\0') { + if (*path =3D=3D L'\\' || *path =3D=3D L'/') { + name =3D path + 1; + } + path++; + } + return name; +} + +static GuestDriverPackageList *ga_get_driver_packages(Error **errp) +{ + GuestDriverPackageList *head =3D NULL, **tail =3D &head; + DismSession session =3D DISM_SESSION_DEFAULT; + DismDriverPackage *drivers =3D NULL; + Error *local_err =3D NULL; + HRESULT hr; + UINT count =3D 0; + UINT i; + + if (!ga_ensure_dism_api(&local_err)) { + goto out; + } + + hr =3D dism_api.open_session(DISM_ONLINE_IMAGE, NULL, NULL, &session); + if (FAILED(hr)) { + error_setg_dism(&local_err, hr, + "failed to open online DISM session"); + goto out; + } + + /* + * FALSE retrieves only out-of-box drivers, meaning drivers that were + * not originally included in the Windows image. See: + * https://learn.microsoft.com/en-us/windows-hardware/manufacture/desk= top/dism/dismgetdrivers-function + */ + hr =3D dism_api.get_drivers(session, FALSE, &drivers, &count); + if (FAILED(hr)) { + error_setg_dism(&local_err, hr, + "failed to enumerate driver packages"); + goto out; + } + + for (i =3D 0; i < count; i++) { + g_autoptr(GuestDriverPackage) package =3D NULL; + + package =3D g_new0(GuestDriverPackage, 1); + package->name =3D ga_utf16_to_utf8_required(drivers[i].PublishedNa= me, + "package name", &local_e= rr); + if (package->name =3D=3D NULL) { + goto out; + } + + if (drivers[i].OriginalFileName !=3D NULL && + drivers[i].OriginalFileName[0] !=3D L'\0') { + const WCHAR *original_name =3D ga_windows_path_basename( + drivers[i].OriginalFileName); + + if (original_name[0] !=3D L'\0') { + package->original_name =3D g_utf16_to_utf8( + original_name, -1, NULL, NULL, NULL); + } + } + + package->version =3D g_strdup_printf("%u.%u.%u.%u", + drivers[i].MajorVersion, + drivers[i].MinorVersion, + drivers[i].Build, + drivers[i].Revision); + + if (drivers[i].ProviderName !=3D NULL && + drivers[i].ProviderName[0] !=3D L'\0') { + package->vendor =3D g_utf16_to_utf8(drivers[i].ProviderName, -= 1, + NULL, NULL, NULL); + } + + QAPI_LIST_APPEND(tail, g_steal_pointer(&package)); + } + +out: + if (drivers !=3D NULL) { + hr =3D dism_api.delete(drivers); + if (FAILED(hr)) { + if (local_err =3D=3D NULL) { + error_setg_dism(&local_err, hr, + "failed to release driver package data"); + } else { + ga_log_dism_error(hr, + "failed to release driver package data"); + } + } + } + if (session !=3D DISM_SESSION_DEFAULT) { + hr =3D dism_api.close_session(session); + if (FAILED(hr)) { + if (local_err =3D=3D NULL) { + error_setg_dism(&local_err, hr, + "failed to close online DISM session"); + } else { + ga_log_dism_error(hr, + "failed to close online DISM session"); + } + } + } + if (local_err !=3D NULL) { + qapi_free_GuestDriverPackageList(head); + head =3D NULL; + error_propagate(errp, local_err); + } + return head; +} + +static bool ga_driver_service_status(DWORD state, + GuestDriverServiceStatus *status) +{ + switch (state) { + case SERVICE_STOPPED: + *status =3D GUEST_DRIVER_SERVICE_STATUS_STOPPED; + return true; + case SERVICE_START_PENDING: + *status =3D GUEST_DRIVER_SERVICE_STATUS_START_PENDING; + return true; + case SERVICE_STOP_PENDING: + *status =3D GUEST_DRIVER_SERVICE_STATUS_STOP_PENDING; + return true; + case SERVICE_RUNNING: + *status =3D GUEST_DRIVER_SERVICE_STATUS_RUNNING; + return true; + case SERVICE_CONTINUE_PENDING: + *status =3D GUEST_DRIVER_SERVICE_STATUS_CONTINUE_PENDING; + return true; + case SERVICE_PAUSE_PENDING: + *status =3D GUEST_DRIVER_SERVICE_STATUS_PAUSE_PENDING; + return true; + case SERVICE_PAUSED: + *status =3D GUEST_DRIVER_SERVICE_STATUS_PAUSED; + return true; + default: + return false; + } +} + +/* Convert an SCM driver path into a path usable by Win32 file APIs. */ +static WCHAR *ga_resolve_driver_path(const WCHAR *path) +{ + WCHAR windows_dir[MAX_PATH + 1]; + g_autofree WCHAR *trimmed =3D NULL; + g_autofree WCHAR *expanded =3D NULL; + const WCHAR *start =3D path; + const WCHAR *end; + const WCHAR *remainder =3D NULL; + size_t prefix_len; + size_t path_len; + size_t windows_len; + DWORD expanded_len; + UINT windows_dir_len; + + while (*start !=3D L'\0' && iswspace(*start)) { + start++; + } + end =3D start + wcslen(start); + while (end > start && iswspace(end[-1])) { + end--; + } + if (end - start >=3D 2 && start[0] =3D=3D L'"' && end[-1] =3D=3D L'"')= { + start++; + end--; + } + if (end =3D=3D start) { + return NULL; + } + + path_len =3D end - start; + trimmed =3D g_new(WCHAR, path_len + 1); + memcpy(trimmed, start, path_len * sizeof(WCHAR)); + trimmed[path_len] =3D L'\0'; + + expanded_len =3D ExpandEnvironmentStringsW(trimmed, NULL, 0); + if (expanded_len =3D=3D 0) { + return NULL; + } + expanded =3D g_new(WCHAR, expanded_len); + path_len =3D ExpandEnvironmentStringsW(trimmed, expanded, expanded_len= ); + if (path_len =3D=3D 0 || path_len > expanded_len) { + return NULL; + } + + prefix_len =3D wcslen(L"\\SystemRoot"); + if (_wcsnicmp(expanded, L"\\SystemRoot", prefix_len) =3D=3D 0 && + (expanded[prefix_len] =3D=3D L'\0' || + expanded[prefix_len] =3D=3D L'\\' || + expanded[prefix_len] =3D=3D L'/')) { + remainder =3D expanded + prefix_len; + } else { + prefix_len =3D wcslen(L"SystemRoot"); + if (_wcsnicmp(expanded, L"SystemRoot", prefix_len) =3D=3D 0 && + (expanded[prefix_len] =3D=3D L'\0' || + expanded[prefix_len] =3D=3D L'\\' || + expanded[prefix_len] =3D=3D L'/')) { + remainder =3D expanded + prefix_len; + } + } + + windows_dir_len =3D GetWindowsDirectoryW(windows_dir, + ARRAY_SIZE(windows_dir)); + if (windows_dir_len =3D=3D 0 || windows_dir_len >=3D ARRAY_SIZE(window= s_dir)) { + return NULL; + } + windows_len =3D windows_dir_len; + + if (remainder !=3D NULL) { + WCHAR *ret =3D g_new(WCHAR, windows_len + wcslen(remainder) + 1); + + memcpy(ret, windows_dir, windows_len * sizeof(WCHAR)); + wcscpy(ret + windows_len, remainder); + return ret; + } + + if (wcsncmp(expanded, L"\\??\\", 4) =3D=3D 0) { + return g_memdup2(expanded + 4, + (wcslen(expanded + 4) + 1) * sizeof(WCHAR)); + } + + if ((iswalpha(expanded[0]) && expanded[1] =3D=3D L':') || + expanded[0] =3D=3D L'\\') { + return g_steal_pointer(&expanded); + } + + path_len =3D wcslen(expanded); + start =3D expanded; + while (*start =3D=3D L'\\' || *start =3D=3D L'/') { + start++; + path_len--; + } + { + WCHAR *ret =3D g_new(WCHAR, windows_len + path_len + 2); + + memcpy(ret, windows_dir, windows_len * sizeof(WCHAR)); + ret[windows_len] =3D L'\\'; + wcscpy(ret + windows_len + 1, start); + return ret; + } +} + +typedef struct QGAVersionTranslation { + WORD language; + WORD code_page; +} QGAVersionTranslation; + +/* Return CompanyName from a localized file-version string table. */ +static char *ga_get_file_vendor(const void *version_info) +{ + QGAVersionTranslation *translations =3D NULL; + static const QGAVersionTranslation fallback =3D { 0x0409, 0x04b0 }; + UINT translations_len =3D 0; + UINT count =3D 0; + UINT i; + + if (VerQueryValueW(version_info, L"\\VarFileInfo\\Translation", + (void **)&translations, &translations_len) && + translations !=3D NULL) { + count =3D translations_len / sizeof(*translations); + } + + for (i =3D 0; i <=3D count; i++) { + const QGAVersionTranslation *translation; + WCHAR query[64]; + WCHAR *company =3D NULL; + UINT company_len =3D 0; + char *vendor; + + translation =3D i < count ? &translations[i] : &fallback; + swprintf(query, ARRAY_SIZE(query), + L"\\StringFileInfo\\%04x%04x\\CompanyName", + translation->language, translation->code_page); + if (!VerQueryValueW(version_info, query, (void **)&company, + &company_len) || company_len <=3D 1) { + continue; + } + vendor =3D g_utf16_to_utf8(company, -1, NULL, NULL, NULL); + if (vendor !=3D NULL && vendor[0] !=3D '\0') { + return vendor; + } + g_free(vendor); + } + return NULL; +} + +/* Add optional FileVersion and CompanyName data from a driver binary. */ +static bool ga_get_driver_file_metadata(const WCHAR *path, + GuestDriverService *service, + Error **errp) +{ + g_autofree void *version_info =3D NULL; + VS_FIXEDFILEINFO *fixed_info =3D NULL; + PVOID old_redirection =3D NULL; + DWORD ignored; + DWORD version_size; + UINT fixed_info_len; + bool redirection_disabled; + bool version_loaded =3D false; + + redirection_disabled =3D + Wow64DisableWow64FsRedirection(&old_redirection); + + version_size =3D GetFileVersionInfoSizeW(path, &ignored); + if (version_size !=3D 0) { + version_info =3D g_malloc(version_size); + version_loaded =3D GetFileVersionInfoW(path, 0, version_size, + version_info); + } + + if (redirection_disabled && + !Wow64RevertWow64FsRedirection(old_redirection)) { + error_setg_win32(errp, GetLastError(), + "failed to restore WOW64 filesystem redirection " + "for driver service '%s'", service->name); + return false; + } + + if (!version_loaded) { + return true; + } + + if (VerQueryValueW(version_info, L"\\", (void **)&fixed_info, + &fixed_info_len) && + fixed_info_len >=3D sizeof(*fixed_info) && + fixed_info->dwSignature =3D=3D VS_FFI_SIGNATURE) { + service->version =3D g_strdup_printf( + "%u.%u.%u.%u", + HIWORD(fixed_info->dwFileVersionMS), + LOWORD(fixed_info->dwFileVersionMS), + HIWORD(fixed_info->dwFileVersionLS), + LOWORD(fixed_info->dwFileVersionLS)); + } + + service->vendor =3D ga_get_file_vendor(version_info); + return true; +} + +/* + * Add optional configuration and binary metadata for one driver service. + * Ordinary per-service lookup failures leave optional fields unset. + */ +static bool ga_get_driver_service_config(SC_HANDLE manager, + const WCHAR *name, + GuestDriverService *service, + Error **errp) +{ + SC_HANDLE handle; + g_autofree QUERY_SERVICE_CONFIGW *config =3D NULL; + g_autofree WCHAR *resolved_path =3D NULL; + DWORD size =3D 0; + DWORD err; + bool success =3D true; + + handle =3D OpenServiceW(manager, name, SERVICE_QUERY_CONFIG); + if (handle =3D=3D NULL) { + slog("failed to open configuration for driver service '%s', " + "error=3D%lu", service->name, GetLastError()); + return true; + } + + QueryServiceConfigW(handle, NULL, 0, &size); + err =3D GetLastError(); + if (err !=3D ERROR_INSUFFICIENT_BUFFER) { + slog("failed to get configuration size for driver service '%s', " + "error=3D%lu", service->name, err); + goto out; + } + + config =3D g_malloc(size); + if (!QueryServiceConfigW(handle, config, size, &size)) { + slog("failed to get configuration for driver service '%s', " + "error=3D%lu", service->name, GetLastError()); + goto out; + } + + if (config->lpBinaryPathName =3D=3D NULL || + config->lpBinaryPathName[0] =3D=3D L'\0') { + goto out; + } + + service->driver_path =3D g_utf16_to_utf8(config->lpBinaryPathName, -1, + NULL, NULL, NULL); + + resolved_path =3D ga_resolve_driver_path(config->lpBinaryPathName); + if (resolved_path !=3D NULL && + !ga_get_driver_file_metadata(resolved_path, service, errp)) { + success =3D false; + } + +out: + CloseServiceHandle(handle); + return success; +} + +static GuestDriverServiceList *ga_get_driver_services(Error **errp) +{ + GuestDriverServiceList *head =3D NULL, **tail =3D &head; + g_autofree BYTE *buffer =3D NULL; + SC_HANDLE manager =3D NULL; + DWORD buffer_size =3D 256 * 1024; + DWORD bytes_needed; + DWORD services_returned; + DWORD resume =3D 0; + DWORD err; + bool complete; + bool success =3D false; + + manager =3D OpenSCManagerW(NULL, NULL, SC_MANAGER_ENUMERATE_SERVICE); + if (manager =3D=3D NULL) { + error_setg_win32(errp, GetLastError(), + "failed to open service control manager"); + goto out; + } + + buffer =3D g_malloc(buffer_size); + do { + ENUM_SERVICE_STATUS_PROCESSW *services; + DWORD i; + + bytes_needed =3D 0; + services_returned =3D 0; + complete =3D EnumServicesStatusExW(manager, SC_ENUM_PROCESS_INFO, + SERVICE_DRIVER, SERVICE_STATE_ALL, + buffer, buffer_size, &bytes_neede= d, + &services_returned, &resume, NULL= ); + if (!complete) { + err =3D GetLastError(); + if (err !=3D ERROR_MORE_DATA) { + error_setg_win32(errp, err, + "failed to enumerate driver services"); + goto out; + } + if (services_returned =3D=3D 0) { + error_setg(errp, "driver service enumeration made no " + "progress (buffer needs %lu bytes)", bytes_need= ed); + goto out; + } + } + + services =3D (ENUM_SERVICE_STATUS_PROCESSW *)buffer; + for (i =3D 0; i < services_returned; i++) { + g_autoptr(GuestDriverService) service =3D NULL; + + service =3D g_new0(GuestDriverService, 1); + service->name =3D ga_utf16_to_utf8_required( + services[i].lpServiceName, "service name", errp); + if (service->name =3D=3D NULL) { + goto out; + } + if (!ga_driver_service_status( + services[i].ServiceStatusProcess.dwCurrentState, + &service->status)) { + error_setg(errp, "driver service '%s' has unknown state %l= u", + service->name, + services[i].ServiceStatusProcess.dwCurrentState= ); + goto out; + } + + if (!ga_get_driver_service_config(manager, + services[i].lpServiceName, + service, errp)) { + goto out; + } + QAPI_LIST_APPEND(tail, g_steal_pointer(&service)); + } + } while (!complete); + success =3D true; + +out: + if (manager !=3D NULL) { + CloseServiceHandle(manager); + } + if (!success) { + qapi_free_GuestDriverServiceList(head); + head =3D NULL; + } + return head; +} + +GuestDriverInfo *qmp_guest_get_drivers(Error **errp) +{ + g_autoptr(GuestDriverInfo) info =3D g_new0(GuestDriverInfo, 1); + Error *local_err =3D NULL; + + info->packages =3D ga_get_driver_packages(&local_err); + if (local_err) { + error_propagate(errp, local_err); + return NULL; + } + + info->services =3D ga_get_driver_services(&local_err); + if (local_err) { + error_propagate(errp, local_err); + return NULL; + } + + return g_steal_pointer(&info); +} + char *qga_get_host_name(Error **errp) { wchar_t tmp[MAX_COMPUTERNAME_LENGTH + 1]; diff --git a/qga/meson.build b/qga/meson.build index cfa2157efb..15860d938d 100644 --- a/qga/meson.build +++ b/qga/meson.build @@ -94,8 +94,9 @@ qga_ss =3D qga_ss.apply({}) gen_tlb =3D [] qga_libs =3D [] if host_os =3D=3D 'windows' - qga_libs +=3D ['-lws2_32', '-lwinmm', '-lpowrprof', '-lwtsapi32', '-lwin= inet', '-liphlpapi', '-lnetapi32', - '-lsetupapi', '-lcfgmgr32', '-luserenv', '-lpdh' ] + qga_libs +=3D ['-lws2_32', '-lwinmm', '-lpowrprof', '-lwtsapi32', + '-lwininet', '-liphlpapi', '-lnetapi32', '-lsetupapi', + '-lcfgmgr32', '-luserenv', '-lpdh', '-lversion' ] if have_qga_vss qga_libs +=3D ['-lole32', '-loleaut32', '-lshlwapi', '-Wl,--enable-std= call-fixup'] subdir('vss-win32') diff --git a/qga/qapi-schema.json b/qga/qapi-schema.json index 8cd0ba048b..b20cf7135a 100644 --- a/qga/qapi-schema.json +++ b/qga/qapi-schema.json @@ -1590,6 +1590,95 @@ 'returns': ['GuestDeviceInfo'], 'if': 'CONFIG_WIN32' } =20 +## +# @GuestDriverServiceStatus: +# +# @stopped: the service is stopped +# +# @start-pending: the service is starting +# +# @stop-pending: the service is stopping +# +# @running: the service is running +# +# @continue-pending: the service is resuming +# +# @pause-pending: the service is pausing +# +# @paused: the service is paused +# +# Since: 11.2 +## +{ 'enum': 'GuestDriverServiceStatus', + 'data': [ 'stopped', 'start-pending', 'stop-pending', 'running', + 'continue-pending', 'pause-pending', 'paused' ], + 'if': 'CONFIG_WIN32' } + +## +# @GuestDriverPackage: +# +# An out-of-box driver package installed in the guest. +# +# @name: published INF name +# +# @original-name: base name of the original INF file +# +# @version: four-part version from the package INF DriverVer entry +# +# @vendor: package provider from the INF metadata +# +# Since: 11.2 +## +{ 'struct': 'GuestDriverPackage', + 'data': { 'name': 'str', '*original-name': 'str', 'version': 'str', + '*vendor': 'str' }, + 'if': 'CONFIG_WIN32' } + +## +# @GuestDriverService: +# +# @name: service name +# +# @driver-path: driver binary path configured in the service database +# +# @version: four-part FileVersion from the driver binary +# +# @vendor: CompanyName from the driver binary +# +# @status: current service status +# +# Since: 11.2 +## +{ 'struct': 'GuestDriverService', + 'data': { 'name': 'str', '*driver-path': 'str', '*version': 'str', + '*vendor': 'str', 'status': 'GuestDriverServiceStatus' }, + 'if': 'CONFIG_WIN32' } + +## +# @GuestDriverInfo: +# +# @packages: out-of-box driver packages installed in the guest +# +# @services: registered driver services +# +# Since: 11.2 +## +{ 'struct': 'GuestDriverInfo', + 'data': { 'packages': ['GuestDriverPackage'], + 'services': ['GuestDriverService'] }, + 'if': 'CONFIG_WIN32' } + +## +# @guest-get-drivers: +# +# Retrieve information about Windows driver packages and services. +# +# Since: 11.2 +## +{ 'command': 'guest-get-drivers', + 'returns': 'GuestDriverInfo', + 'if': 'CONFIG_WIN32' } + ## # @GuestAuthorizedKeys: # --=20 2.55.0