On Thu, Sep 24, 2026 at 10:41 PM <[email protected]> wrote:

> > 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 <[email protected]>
> > Message-ID: <[email protected]>
> >
> > diff --git a/qga/commands-win32.c b/qga/commands-win32.c
> > index 2bf5581aaa72..0dc94d808e35 100644
> > --- a/qga/commands-win32.c
> > +++ b/qga/commands-win32.c
> > @@ -28,6 +28,8 @@
> >  #include <wtsapi32.h>
> >  #include <wininet.h>
> >  #include <pdh.h>
> > +#include <dismapi.h>
> > +#include <winver.h>
> >
> >  #include "guest-agent-core.h"
> >  #include "vss-win32.h"
> > @@ -1981,12 +1983,15 @@ done:
> >      g_free(rawpasswddata);
> >  }
> >
> > +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);
> >  }
> >
> >  /* MINGW is missing two fields: IncomingFrames & OutgoingFrames */
> > @@ -2455,6 +2460,684 @@ GuestDeviceInfoList *qmp_guest_get_devices(Error
> **errp)
> >      return head;
> >  }
> >
> > +static void error_setg_dism(Error **errp, HRESULT hr, const char *msg)
> > +{
> > +    if (HRESULT_FACILITY(hr) == 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 = 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 = GetProcAddress(module, name);
> > +
> > +    if (function == 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 != NULL && !FreeLibrary(api->module)) {
> > +        slog("failed to unload DismApi.dll, error=%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 = LoadLibraryExW(L"DismApi.dll", NULL,
> > +                                 LOAD_LIBRARY_SEARCH_SYSTEM32);
> > +    if (api->module == NULL) {
> > +        error_setg_win32(errp, GetLastError(),
> > +                         "failed to load DismApi.dll");
> > +        return false;
> > +    }
> > +
> > +    api->initialize = (QGADismInitializeFunc *)
> > +        ga_get_dism_function(api->module, "DismInitialize", errp);
> > +    if (api->initialize == NULL) {
> > +        goto fail;
> > +    }
> > +    api->shutdown = (QGADismShutdownFunc *)
> > +        ga_get_dism_function(api->module, "DismShutdown", errp);
> > +    if (api->shutdown == NULL) {
> > +        goto fail;
> > +    }
> > +    api->open_session = (QGADismOpenSessionFunc *)
> > +        ga_get_dism_function(api->module, "DismOpenSession", errp);
> > +    if (api->open_session == NULL) {
> > +        goto fail;
> > +    }
> > +    api->close_session = (QGADismCloseSessionFunc *)
> > +        ga_get_dism_function(api->module, "DismCloseSession", errp);
> > +    if (api->close_session == NULL) {
> > +        goto fail;
> > +    }
> > +    api->get_drivers = (QGADismGetDriversFunc *)
> > +        ga_get_dism_function(api->module, "DismGetDrivers", errp);
> > +    if (api->get_drivers == NULL) {
> > +        goto fail;
> > +    }
> > +    api->delete = (QGADismDeleteFunc *)
> > +        ga_get_dism_function(api->module, "DismDelete", errp);
> > +    if (api->delete == 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 = 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 = true;
> > +    return true;
> > +}
> > +
> > +static void ga_cleanup_dism_api(void)
> > +{
> > +    HRESULT hr;
> > +
> > +    if (dism_initialized) {
> > +        hr = dism_api.shutdown();
> > +        if (FAILED(hr)) {
> > +            ga_log_dism_error(hr, "failed to shut down DISM");
> > +        }
> > +        dism_initialized = 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 = NULL;
> > +    char *ret;
> > +
> > +    if (str == NULL || str[0] == L'\0') {
> > +        error_setg(errp, "driver %s is missing", name);
> > +        return NULL;
> > +    }
> > +
> > +    ret = g_utf16_to_utf8(str, -1, NULL, NULL, &gerr);
> > +    if (ret == 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 = path;
> > +
> > +    while (*path != L'\0') {
> > +        if (*path == L'\\' || *path == L'/') {
> > +            name = path + 1;
> > +        }
> > +        path++;
> > +    }
> > +    return name;
> > +}
> > +
> > +static GuestDriverPackageList *ga_get_driver_packages(Error **errp)
> > +{
> > +    GuestDriverPackageList *head = NULL, **tail = &head;
> > +    DismSession session = DISM_SESSION_DEFAULT;
> > +    DismDriverPackage *drivers = NULL;
> > +    Error *local_err = NULL;
> > +    HRESULT hr;
> > +    UINT count = 0;
> > +    UINT i;
> > +
> > +    if (!ga_ensure_dism_api(&local_err)) {
> > +        goto out;
> > +    }
> > +
> > +    hr = 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/desktop/dism/dismgetdrivers-function
> > +     */
> > +    hr = 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 = 0; i < count; i++) {
> > +        g_autoptr(GuestDriverPackage) package = NULL;
> > +
> > +        package = g_new0(GuestDriverPackage, 1);
> > +        package->name =
> ga_utf16_to_utf8_required(drivers[i].PublishedName,
> > +                                                  "package name",
> &local_err);
> > +        if (package->name == NULL) {
> > +            goto out;
> > +        }
> > +
> > +        if (drivers[i].OriginalFileName != NULL &&
> > +            drivers[i].OriginalFileName[0] != L'\0') {
> > +            const WCHAR *original_name = ga_windows_path_basename(
> > +                drivers[i].OriginalFileName);
> > +
> > +            if (original_name[0] != L'\0') {
> > +                package->original_name = g_utf16_to_utf8(
> > +                    original_name, -1, NULL, NULL, NULL);
> > +            }
> > +        }
> > +
> > +        package->version = g_strdup_printf("%u.%u.%u.%u",
> > +                                           drivers[i].MajorVersion,
> > +                                           drivers[i].MinorVersion,
> > +                                           drivers[i].Build,
> > +                                           drivers[i].Revision);
> > +
> > +        if (drivers[i].ProviderName != NULL &&
> > +            drivers[i].ProviderName[0] != L'\0') {
> > +            package->vendor = g_utf16_to_utf8(drivers[i].ProviderName,
> -1,
> > +                                              NULL, NULL, NULL);
> > +        }
> > +
> > +        QAPI_LIST_APPEND(tail, g_steal_pointer(&package));
> > +    }
> > +
> > +out:
> > +    if (drivers != NULL) {
> > +        hr = dism_api.delete(drivers);
> > +        if (FAILED(hr)) {
> > +            if (local_err == 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 != DISM_SESSION_DEFAULT) {
> > +        hr = dism_api.close_session(session);
> > +        if (FAILED(hr)) {
> > +            if (local_err == 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 != NULL) {
> > +        qapi_free_GuestDriverPackageList(head);
> > +        head = NULL;
> > +        error_propagate(errp, local_err);
> > +    }
> > +    return head;
> > +}
> > +
> > +static bool ga_driver_service_status(DWORD state,
> > +                                     GuestDriverServiceStatus *status)
> > +{
> > +    switch (state) {
> > +    case SERVICE_STOPPED:
> > +        *status = GUEST_DRIVER_SERVICE_STATUS_STOPPED;
> > +        return true;
> > +    case SERVICE_START_PENDING:
> > +        *status = GUEST_DRIVER_SERVICE_STATUS_START_PENDING;
> > +        return true;
> > +    case SERVICE_STOP_PENDING:
> > +        *status = GUEST_DRIVER_SERVICE_STATUS_STOP_PENDING;
> > +        return true;
> > +    case SERVICE_RUNNING:
> > +        *status = GUEST_DRIVER_SERVICE_STATUS_RUNNING;
> > +        return true;
> > +    case SERVICE_CONTINUE_PENDING:
> > +        *status = GUEST_DRIVER_SERVICE_STATUS_CONTINUE_PENDING;
> > +        return true;
> > +    case SERVICE_PAUSE_PENDING:
> > +        *status = GUEST_DRIVER_SERVICE_STATUS_PAUSE_PENDING;
> > +        return true;
> > +    case SERVICE_PAUSED:
> > +        *status = 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 = NULL;
> > +    g_autofree WCHAR *expanded = NULL;
> > +    const WCHAR *start = path;
> > +    const WCHAR *end;
> > +    const WCHAR *remainder = NULL;
> > +    size_t prefix_len;
> > +    size_t path_len;
> > +    size_t windows_len;
> > +    DWORD expanded_len;
> > +    UINT windows_dir_len;
> > +
> > +    while (*start != L'\0' && iswspace(*start)) {
> > +        start++;
> > +    }
> > +    end = start + wcslen(start);
> > +    while (end > start && iswspace(end[-1])) {
> > +        end--;
> > +    }
> > +    if (end - start >= 2 && start[0] == L'"' && end[-1] == L'"') {
> > +        start++;
> > +        end--;
> > +    }
> > +    if (end == start) {
> > +        return NULL;
> > +    }
> > +
> > +    path_len = end - start;
> > +    trimmed = g_new(WCHAR, path_len + 1);
> > +    memcpy(trimmed, start, path_len * sizeof(WCHAR));
> > +    trimmed[path_len] = L'\0';
> > +
> > +    expanded_len = ExpandEnvironmentStringsW(trimmed, NULL, 0);
> > +    if (expanded_len == 0) {
> > +        return NULL;
> > +    }
> > +    expanded = g_new(WCHAR, expanded_len);
> > +    path_len = ExpandEnvironmentStringsW(trimmed, expanded,
> expanded_len);
> > +    if (path_len == 0 || path_len > expanded_len) {
> > +        return NULL;
> > +    }
> > +
> > +    prefix_len = wcslen(L"\\SystemRoot");
> > +    if (_wcsnicmp(expanded, L"\\SystemRoot", prefix_len) == 0 &&
> > +        (expanded[prefix_len] == L'\0' ||
> > +         expanded[prefix_len] == L'\\' ||
> > +         expanded[prefix_len] == L'/')) {
> > +        remainder = expanded + prefix_len;
> > +    } else {
> > +        prefix_len = wcslen(L"SystemRoot");
> > +        if (_wcsnicmp(expanded, L"SystemRoot", prefix_len) == 0 &&
> > +            (expanded[prefix_len] == L'\0' ||
> > +             expanded[prefix_len] == L'\\' ||
> > +             expanded[prefix_len] == L'/')) {
> > +            remainder = expanded + prefix_len;
> > +        }
> > +    }
> > +
> > +    windows_dir_len = GetWindowsDirectoryW(windows_dir,
> > +                                            ARRAY_SIZE(windows_dir));
> > +    if (windows_dir_len == 0 || windows_dir_len >=
> ARRAY_SIZE(windows_dir)) {
> > +        return NULL;
> > +    }
> > +    windows_len = windows_dir_len;
> > +
> > +    if (remainder != NULL) {
> > +        WCHAR *ret = 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) == 0) {
> > +        return g_memdup2(expanded + 4,
> > +                         (wcslen(expanded + 4) + 1) * sizeof(WCHAR));
> > +    }
> > +
> > +    if ((iswalpha(expanded[0]) && expanded[1] == L':') ||
> > +        expanded[0] == L'\\') {
> > +        return g_steal_pointer(&expanded);
> > +    }
> > +
> > +    path_len = wcslen(expanded);
> > +    start = expanded;
> > +    while (*start == L'\\' || *start == L'/') {
> > +        start++;
> > +        path_len--;
> > +    }
> > +    {
> > +        WCHAR *ret = g_new(WCHAR, windows_len + path_len + 2);
> > +
> > +        memcpy(ret, windows_dir, windows_len * sizeof(WCHAR));
> > +        ret[windows_len] = 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 = NULL;
> > +    static const QGAVersionTranslation fallback = { 0x0409, 0x04b0 };
> > +    UINT translations_len = 0;
> > +    UINT count = 0;
> > +    UINT i;
> > +
> > +    if (VerQueryValueW(version_info, L"\\VarFileInfo\\Translation",
> > +                       (void **)&translations, &translations_len) &&
> > +        translations != NULL) {
> > +        count = translations_len / sizeof(*translations);
> > +    }
> > +
> > +    for (i = 0; i <= count; i++) {
> > +        const QGAVersionTranslation *translation;
> > +        WCHAR query[64];
> > +        WCHAR *company = NULL;
> > +        UINT company_len = 0;
> > +        char *vendor;
> > +
> > +        translation = 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 <= 1) {
> > +            continue;
> > +        }
> > +        vendor = g_utf16_to_utf8(company, -1, NULL, NULL, NULL);
> > +        if (vendor != NULL && vendor[0] != '\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 = NULL;
> > +    VS_FIXEDFILEINFO *fixed_info = NULL;
> > +    PVOID old_redirection = NULL;
> > +    DWORD ignored;
> > +    DWORD version_size;
> > +    UINT fixed_info_len;
> > +    bool redirection_disabled;
> > +    bool version_loaded = false;
> > +
> > +    redirection_disabled =
> > +        Wow64DisableWow64FsRedirection(&old_redirection);
> > +
> > +    version_size = GetFileVersionInfoSizeW(path, &ignored);
> > +    if (version_size != 0) {
> > +        version_info = g_malloc(version_size);
> > +        version_loaded = 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 >= sizeof(*fixed_info) &&
> > +        fixed_info->dwSignature == VS_FFI_SIGNATURE) {
> > +        service->version = 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 = 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 = NULL;
> > +    g_autofree WCHAR *resolved_path = NULL;
> > +    DWORD size = 0;
> > +    DWORD err;
> > +    bool success = true;
> > +
> > +    handle = OpenServiceW(manager, name, SERVICE_QUERY_CONFIG);
> > +    if (handle == NULL) {
> > +        slog("failed to open configuration for driver service '%s', "
> > +             "error=%lu", service->name, GetLastError());
> > +        return true;
> > +    }
> > +
> > +    QueryServiceConfigW(handle, NULL, 0, &size);
> > +    err = GetLastError();
> > +    if (err != ERROR_INSUFFICIENT_BUFFER) {
> > +        slog("failed to get configuration size for driver service '%s',
> "
> > +             "error=%lu", service->name, err);
> > +        goto out;
> > +    }
> > +
> > +    config = g_malloc(size);
> > +    if (!QueryServiceConfigW(handle, config, size, &size)) {
> > +        slog("failed to get configuration for driver service '%s', "
> > +             "error=%lu", service->name, GetLastError());
> > +        goto out;
> > +    }
> > +
> > +    if (config->lpBinaryPathName == NULL ||
> > +        config->lpBinaryPathName[0] == L'\0') {
> > +        goto out;
> > +    }
> > +
> > +    service->driver_path = g_utf16_to_utf8(config->lpBinaryPathName, -1,
> > +                                           NULL, NULL, NULL);
> > +
> > +    resolved_path = ga_resolve_driver_path(config->lpBinaryPathName);
> > +    if (resolved_path != NULL &&
> > +        !ga_get_driver_file_metadata(resolved_path, service, errp)) {
> > +        success = false;
> > +    }
> > +
> > +out:
> > +    CloseServiceHandle(handle);
> > +    return success;
> > +}
> > +
> > +static GuestDriverServiceList *ga_get_driver_services(Error **errp)
> > +{
> > +    GuestDriverServiceList *head = NULL, **tail = &head;
> > +    g_autofree BYTE *buffer = NULL;
> > +    SC_HANDLE manager = NULL;
> > +    DWORD buffer_size = 256 * 1024;
> > +    DWORD bytes_needed;
> > +    DWORD services_returned;
> > +    DWORD resume = 0;
> > +    DWORD err;
> > +    bool complete;
> > +    bool success = false;
> > +
> > +    manager = OpenSCManagerW(NULL, NULL, SC_MANAGER_ENUMERATE_SERVICE);
> > +    if (manager == NULL) {
> > +        error_setg_win32(errp, GetLastError(),
> > +                         "failed to open service control manager");
> > +        goto out;
> > +    }
> > +
> > +    buffer = g_malloc(buffer_size);
> > +    do {
> > +        ENUM_SERVICE_STATUS_PROCESSW *services;
> > +        DWORD i;
> > +
> > +        bytes_needed = 0;
> > +        services_returned = 0;
> > +        complete = EnumServicesStatusExW(manager, SC_ENUM_PROCESS_INFO,
> > +                                         SERVICE_DRIVER,
> SERVICE_STATE_ALL,
> > +                                         buffer, buffer_size,
> &bytes_needed,
> > +                                         &services_returned, &resume,
> NULL);
> > +        if (!complete) {
> > +            err = GetLastError();
> > +            if (err != ERROR_MORE_DATA) {
> > +                error_setg_win32(errp, err,
> > +                                 "failed to enumerate driver services");
> > +                goto out;
> > +            }
> > +            if (services_returned == 0) {
> > +                error_setg(errp, "driver service enumeration made no "
> > +                           "progress (buffer needs %lu bytes)",
> bytes_needed);
> > +                goto out;
> > +            }
> > +        }
> > +
> > +        services = (ENUM_SERVICE_STATUS_PROCESSW *)buffer;
> > +        for (i = 0; i < services_returned; i++) {
> > +            g_autoptr(GuestDriverService) service = NULL;
> > +
> > +            service = g_new0(GuestDriverService, 1);
> > +            service->name = ga_utf16_to_utf8_required(
> > +                services[i].lpServiceName, "service name", errp);
> > +            if (service->name == NULL) {
> > +                goto out;
> > +            }
> > +            if (!ga_driver_service_status(
> > +                    services[i].ServiceStatusProcess.dwCurrentState,
> > +                    &service->status)) {
> > +                error_setg(errp, "driver service '%s' has unknown state
> %lu",
> > +                           service->name,
> > +
>  services[i].ServiceStatusProcess.dwCurrentState);
> > +                goto out;
> > +            }
>
> An unknown dwCurrentState aborts the entire service enumeration. The states
> listed in GuestDriverServiceStatus cover all documented SCM values today,
> but a
> future Windows version could add new states.
>
> Consider skipping the service with a slog warning instead, or adding an
> "unknown" enum value to GuestDriverServiceStatus.
>
> > +
> > +            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 = true;
> > +
> > +out:
> > +    if (manager != NULL) {
> > +        CloseServiceHandle(manager);
> > +    }
> > +    if (!success) {
> > +        qapi_free_GuestDriverServiceList(head);
> > +        head = NULL;
> > +    }
> > +    return head;
> > +}
> > +
> > +GuestDriverInfo *qmp_guest_get_drivers(Error **errp)
> > +{
> > +    g_autoptr(GuestDriverInfo) info = g_new0(GuestDriverInfo, 1);
> > +    Error *local_err = NULL;
> > +
> > +    info->packages = ga_get_driver_packages(&local_err);
> > +    if (local_err) {
> > +        error_propagate(errp, local_err);
> > +        return NULL;
> > +    }
>
> If DismApi.dll is missing or DismInitialize fails, it returns an error,
> even if
> listing services could work. That's probably ok, but worth noting.
>

what if we add an "error" field to the package structure, then we can
return services and package with an error about DismInitialize


>
> > +
> > +    info->services = 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 cfa2157efb23..15860d938d4b 100644
> > --- a/qga/meson.build
> > +++ b/qga/meson.build
> > @@ -94,8 +94,9 @@ qga_ss = qga_ss.apply({})
> >  gen_tlb = []
> >  qga_libs = []
> >  if host_os == 'windows'
> > -  qga_libs += ['-lws2_32', '-lwinmm', '-lpowrprof', '-lwtsapi32',
> '-lwininet', '-liphlpapi', '-lnetapi32',
> > -               '-lsetupapi', '-lcfgmgr32', '-luserenv', '-lpdh' ]
> > +  qga_libs += ['-lws2_32', '-lwinmm', '-lpowrprof', '-lwtsapi32',
> > +               '-lwininet', '-liphlpapi', '-lnetapi32', '-lsetupapi',
> > +               '-lcfgmgr32', '-luserenv', '-lpdh', '-lversion' ]
> >    if have_qga_vss
> >      qga_libs += ['-lole32', '-loleaut32', '-lshlwapi',
> '-Wl,--enable-stdcall-fixup']
> >      subdir('vss-win32')
> > diff --git a/qga/qapi-schema.json b/qga/qapi-schema.json
> > index 8cd0ba048b8d..b20cf7135a4c 100644
> > --- a/qga/qapi-schema.json
> > +++ b/qga/qapi-schema.json
> > @@ -1590,6 +1590,95 @@
> >    'returns': ['GuestDeviceInfo'],
> >    'if': 'CONFIG_WIN32' }
> >
> > +##
> > +# @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:
> >  #
>
> otherwise, lgtm
>
> --
> Marc-André Lureau <[email protected]>
>
>

Reply via email to