Hi

On Fri, Sep 25, 2026 at 10:54 AM Kostiantyn Kostiuk <[email protected]> wrote:
>
>
>
>
> 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
>

Let's have two commands in that case. I think it makes sense anyway,
there is not much reason to aggregate the two results.

>>
>>
>> > +
>> > +    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