]> git.proxmox.com Git - mirror_qemu.git/blobdiff - qga/commands-win32.c
qdev: add qdev_add_vm_change_state_handler()
[mirror_qemu.git] / qga / commands-win32.c
index 2d4839474849369c86d0fc27dfeab062ffa4d7d4..6b67f16faf122b009e6f24b428b2b32ebc17c06f 100644 (file)
  * This work is licensed under the terms of the GNU GPL, version 2 or later.
  * See the COPYING file in the top-level directory.
  */
-
-#ifndef _WIN32_WINNT
-#   define _WIN32_WINNT 0x0600
-#endif
-
 #include "qemu/osdep.h"
+
 #include <wtypes.h>
 #include <powrprof.h>
 #include <winsock2.h>
 #include <winioctl.h>
 #include <ntddscsi.h>
 #include <setupapi.h>
+#include <cfgmgr32.h>
 #include <initguid.h>
 #endif
 #include <lm.h>
 #include <wtsapi32.h>
 #include <wininet.h>
 
-#include "qga/guest-agent-core.h"
-#include "qga/vss-win32.h"
+#include "guest-agent-core.h"
+#include "vss-win32.h"
 #include "qga-qapi-commands.h"
 #include "qapi/error.h"
 #include "qapi/qmp/qerror.h"
@@ -89,6 +86,12 @@ static OpenFlags guest_file_open_modes[] = {
     {"a+b", FILE_GENERIC_APPEND|GENERIC_READ, OPEN_ALWAYS  }
 };
 
+#define debug_error(msg) do { \
+    char *suffix = g_win32_error_message(GetLastError()); \
+    g_debug("%s: %s", (msg), suffix); \
+    g_free(suffix); \
+} while (0)
+
 static OpenFlags *find_open_flag(const char *mode_str)
 {
     int mode;
@@ -160,13 +163,15 @@ static void handle_set_nonblocking(HANDLE fh)
 int64_t qmp_guest_file_open(const char *path, bool has_mode,
                             const char *mode, Error **errp)
 {
-    int64_t fd;
+    int64_t fd = -1;
     HANDLE fh;
     HANDLE templ_file = NULL;
     DWORD share_mode = FILE_SHARE_READ;
     DWORD flags_and_attr = FILE_ATTRIBUTE_NORMAL;
     LPSECURITY_ATTRIBUTES sa_attr = NULL;
     OpenFlags *guest_flags;
+    GError *gerr = NULL;
+    wchar_t *w_path = NULL;
 
     if (!has_mode) {
         mode = "r";
@@ -175,16 +180,21 @@ int64_t qmp_guest_file_open(const char *path, bool has_mode,
     guest_flags = find_open_flag(mode);
     if (guest_flags == NULL) {
         error_setg(errp, "invalid file open mode");
-        return -1;
+        goto done;
+    }
+
+    w_path = g_utf8_to_utf16(path, -1, NULL, NULL, &gerr);
+    if (!w_path) {
+        goto done;
     }
 
-    fh = CreateFile(path, guest_flags->desired_access, share_mode, sa_attr,
+    fh = CreateFileW(w_path, guest_flags->desired_access, share_mode, sa_attr,
                     guest_flags->creation_disposition, flags_and_attr,
                     templ_file);
     if (fh == INVALID_HANDLE_VALUE) {
         error_setg_win32(errp, GetLastError(), "failed to open file '%s'",
                          path);
-        return -1;
+        goto done;
     }
 
     /* set fd non-blocking to avoid common use cases (like reading from a
@@ -196,10 +206,17 @@ int64_t qmp_guest_file_open(const char *path, bool has_mode,
     if (fd < 0) {
         CloseHandle(fh);
         error_setg(errp, "failed to add handle to qmp handle table");
-        return -1;
+        goto done;
     }
 
     slog("guest-file-open, handle: % " PRId64, fd);
+
+done:
+    if (gerr) {
+        error_setg(errp, QERR_QGA_COMMAND_FAILED, gerr->message);
+        g_error_free(gerr);
+    }
+    g_free(w_path);
     return fd;
 }
 
@@ -318,7 +335,7 @@ GuestFileRead *qmp_guest_file_read(int64_t handle, bool has_count,
     }
     if (!has_count) {
         count = QGA_READ_COUNT_DEFAULT;
-    } else if (count < 0) {
+    } else if (count < 0 || count >= UINT32_MAX) {
         error_setg(errp, "value '%" PRId64
                    "' is invalid for argument count", count);
         return NULL;
@@ -440,7 +457,7 @@ void qmp_guest_file_flush(int64_t handle, Error **errp)
 
 #ifdef CONFIG_QGA_NTDDSCSI
 
-static STORAGE_BUS_TYPE win2qemu[] = {
+static GuestDiskBusType win2qemu[] = {
     [BusTypeUnknown] = GUEST_DISK_BUS_TYPE_UNKNOWN,
     [BusTypeScsi] = GUEST_DISK_BUS_TYPE_SCSI,
     [BusTypeAtapi] = GUEST_DISK_BUS_TYPE_IDE,
@@ -450,13 +467,11 @@ static STORAGE_BUS_TYPE win2qemu[] = {
     [BusTypeFibre] = GUEST_DISK_BUS_TYPE_SSA,
     [BusTypeUsb] = GUEST_DISK_BUS_TYPE_USB,
     [BusTypeRAID] = GUEST_DISK_BUS_TYPE_RAID,
-#if (_WIN32_WINNT >= 0x0600)
     [BusTypeiScsi] = GUEST_DISK_BUS_TYPE_ISCSI,
     [BusTypeSas] = GUEST_DISK_BUS_TYPE_SAS,
     [BusTypeSata] = GUEST_DISK_BUS_TYPE_SATA,
     [BusTypeSd] =  GUEST_DISK_BUS_TYPE_SD,
     [BusTypeMmc] = GUEST_DISK_BUS_TYPE_MMC,
-#endif
 #if (_WIN32_WINNT >= 0x0601)
     [BusTypeVirtual] = GUEST_DISK_BUS_TYPE_VIRTUAL,
     [BusTypeFileBackedVirtual] = GUEST_DISK_BUS_TYPE_FILE_BACKED_VIRTUAL,
@@ -465,192 +480,483 @@ static STORAGE_BUS_TYPE win2qemu[] = {
 
 static GuestDiskBusType find_bus_type(STORAGE_BUS_TYPE bus)
 {
-    if (bus > ARRAY_SIZE(win2qemu) || (int)bus < 0) {
+    if (bus >= ARRAY_SIZE(win2qemu) || (int)bus < 0) {
         return GUEST_DISK_BUS_TYPE_UNKNOWN;
     }
     return win2qemu[(int)bus];
 }
 
-DEFINE_GUID(GUID_DEVINTERFACE_VOLUME,
-        0x53f5630dL, 0xb6bf, 0x11d0, 0x94, 0xf2,
+DEFINE_GUID(GUID_DEVINTERFACE_DISK,
+        0x53f56307L, 0xb6bf, 0x11d0, 0x94, 0xf2,
+        0x00, 0xa0, 0xc9, 0x1e, 0xfb, 0x8b);
+DEFINE_GUID(GUID_DEVINTERFACE_STORAGEPORT,
+        0x2accfe60L, 0xc130, 0x11d2, 0xb0, 0x82,
         0x00, 0xa0, 0xc9, 0x1e, 0xfb, 0x8b);
 
-static GuestPCIAddress *get_pci_info(char *guid, Error **errp)
+static GuestPCIAddress *get_pci_info(int number, Error **errp)
 {
     HDEVINFO dev_info;
     SP_DEVINFO_DATA dev_info_data;
-    DWORD size = 0;
+    SP_DEVICE_INTERFACE_DATA dev_iface_data;
+    HANDLE dev_file;
     int i;
-    char dev_name[MAX_PATH];
-    char *buffer = NULL;
     GuestPCIAddress *pci = NULL;
-    char *name = g_strdup(&guid[4]);
+    bool partial_pci = false;
 
-    if (!QueryDosDevice(name, dev_name, ARRAY_SIZE(dev_name))) {
-        error_setg_win32(errp, GetLastError(), "failed to get dos device name");
-        goto out;
-    }
+    pci = g_malloc0(sizeof(*pci));
+    pci->domain = -1;
+    pci->slot = -1;
+    pci->function = -1;
+    pci->bus = -1;
 
-    dev_info = SetupDiGetClassDevs(&GUID_DEVINTERFACE_VOLUME, 0, 0,
+    dev_info = SetupDiGetClassDevs(&GUID_DEVINTERFACE_DISK, 0, 0,
                                    DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
     if (dev_info == INVALID_HANDLE_VALUE) {
         error_setg_win32(errp, GetLastError(), "failed to get devices tree");
         goto out;
     }
 
+    g_debug("enumerating devices");
     dev_info_data.cbSize = sizeof(SP_DEVINFO_DATA);
+    dev_iface_data.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
     for (i = 0; SetupDiEnumDeviceInfo(dev_info, i, &dev_info_data); i++) {
-        DWORD addr, bus, slot, func, dev, data, size2;
-        while (!SetupDiGetDeviceRegistryProperty(dev_info, &dev_info_data,
-                                            SPDRP_PHYSICAL_DEVICE_OBJECT_NAME,
-                                            &data, (PBYTE)buffer, size,
-                                            &size2)) {
-            size = MAX(size, size2);
-            if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
-                g_free(buffer);
-                /* Double the size to avoid problems on
-                 * W2k MBCS systems per KB 888609.
-                 * https://support.microsoft.com/en-us/kb/259695 */
-                buffer = g_malloc(size * 2);
-            } else {
+        PSP_DEVICE_INTERFACE_DETAIL_DATA pdev_iface_detail_data = NULL;
+        STORAGE_DEVICE_NUMBER sdn;
+        char *parent_dev_id = NULL;
+        HDEVINFO parent_dev_info;
+        SP_DEVINFO_DATA parent_dev_info_data;
+        DWORD j;
+        DWORD size = 0;
+
+        g_debug("getting device path");
+        if (SetupDiEnumDeviceInterfaces(dev_info, &dev_info_data,
+                                        &GUID_DEVINTERFACE_DISK, 0,
+                                        &dev_iface_data)) {
+            while (!SetupDiGetDeviceInterfaceDetail(dev_info, &dev_iface_data,
+                                                    pdev_iface_detail_data,
+                                                    size, &size,
+                                                    &dev_info_data)) {
+                if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
+                    pdev_iface_detail_data = g_malloc(size);
+                    pdev_iface_detail_data->cbSize =
+                        sizeof(*pdev_iface_detail_data);
+                } else {
+                    error_setg_win32(errp, GetLastError(),
+                                     "failed to get device interfaces");
+                    goto free_dev_info;
+                }
+            }
+
+            dev_file = CreateFile(pdev_iface_detail_data->DevicePath, 0,
+                                  FILE_SHARE_READ, NULL, OPEN_EXISTING, 0,
+                                  NULL);
+            g_free(pdev_iface_detail_data);
+
+            if (!DeviceIoControl(dev_file, IOCTL_STORAGE_GET_DEVICE_NUMBER,
+                                 NULL, 0, &sdn, sizeof(sdn), &size, NULL)) {
+                CloseHandle(dev_file);
                 error_setg_win32(errp, GetLastError(),
-                        "failed to get device name");
+                                 "failed to get device slot number");
                 goto free_dev_info;
             }
-        }
 
-        if (g_strcmp0(buffer, dev_name)) {
-            continue;
+            CloseHandle(dev_file);
+            if (sdn.DeviceNumber != number) {
+                continue;
+            }
+        } else {
+            error_setg_win32(errp, GetLastError(),
+                             "failed to get device interfaces");
+            goto free_dev_info;
         }
 
-        /* There is no need to allocate buffer in the next functions. The size
-         * is known and ULONG according to
-         * https://support.microsoft.com/en-us/kb/253232
-         * https://msdn.microsoft.com/en-us/library/windows/hardware/ff543095(v=vs.85).aspx
-         */
-        if (!SetupDiGetDeviceRegistryProperty(dev_info, &dev_info_data,
-                   SPDRP_BUSNUMBER, &data, (PBYTE)&bus, size, NULL)) {
-            break;
+        g_debug("found device slot %d. Getting storage controller", number);
+        {
+            CONFIGRET cr;
+            DEVINST dev_inst, parent_dev_inst;
+            ULONG dev_id_size = 0;
+
+            size = 0;
+            while (!SetupDiGetDeviceInstanceId(dev_info, &dev_info_data,
+                                               parent_dev_id, size, &size)) {
+                if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
+                    parent_dev_id = g_malloc(size);
+                } else {
+                    error_setg_win32(errp, GetLastError(),
+                                     "failed to get device instance ID");
+                    goto out;
+                }
+            }
+
+            /*
+             * CM API used here as opposed to
+             * SetupDiGetDeviceProperty(..., DEVPKEY_Device_Parent, ...)
+             * which exports are only available in mingw-w64 6+
+             */
+            cr = CM_Locate_DevInst(&dev_inst, parent_dev_id, 0);
+            if (cr != CR_SUCCESS) {
+                g_error("CM_Locate_DevInst failed with code %lx", cr);
+                error_setg_win32(errp, GetLastError(),
+                                 "failed to get device instance");
+                goto out;
+            }
+            cr = CM_Get_Parent(&parent_dev_inst, dev_inst, 0);
+            if (cr != CR_SUCCESS) {
+                g_error("CM_Get_Parent failed with code %lx", cr);
+                error_setg_win32(errp, GetLastError(),
+                                 "failed to get parent device instance");
+                goto out;
+            }
+
+            cr = CM_Get_Device_ID_Size(&dev_id_size, parent_dev_inst, 0);
+            if (cr != CR_SUCCESS) {
+                g_error("CM_Get_Device_ID_Size failed with code %lx", cr);
+                error_setg_win32(errp, GetLastError(),
+                                 "failed to get parent device ID length");
+                goto out;
+            }
+
+            ++dev_id_size;
+            if (dev_id_size > size) {
+                g_free(parent_dev_id);
+                parent_dev_id = g_malloc(dev_id_size);
+            }
+
+            cr = CM_Get_Device_ID(parent_dev_inst, parent_dev_id, dev_id_size,
+                                  0);
+            if (cr != CR_SUCCESS) {
+                g_error("CM_Get_Device_ID failed with code %lx", cr);
+                error_setg_win32(errp, GetLastError(),
+                                 "failed to get parent device ID");
+                goto out;
+            }
         }
 
-        /* The function retrieves the device's address. This value will be
-         * transformed into device function and number */
-        if (!SetupDiGetDeviceRegistryProperty(dev_info, &dev_info_data,
-                   SPDRP_ADDRESS, &data, (PBYTE)&addr, size, NULL)) {
-            break;
+        g_debug("querying storage controller %s for PCI information",
+                parent_dev_id);
+        parent_dev_info =
+            SetupDiGetClassDevs(&GUID_DEVINTERFACE_STORAGEPORT, parent_dev_id,
+                                NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
+        g_free(parent_dev_id);
+
+        if (parent_dev_info == INVALID_HANDLE_VALUE) {
+            error_setg_win32(errp, GetLastError(),
+                             "failed to get parent device");
+            goto out;
         }
 
-        /* This call returns UINumber of DEVICE_CAPABILITIES structure.
-         * This number is typically a user-perceived slot number. */
-        if (!SetupDiGetDeviceRegistryProperty(dev_info, &dev_info_data,
-                   SPDRP_UI_NUMBER, &data, (PBYTE)&slot, size, NULL)) {
-            break;
+        parent_dev_info_data.cbSize = sizeof(SP_DEVINFO_DATA);
+        if (!SetupDiEnumDeviceInfo(parent_dev_info, 0, &parent_dev_info_data)) {
+            error_setg_win32(errp, GetLastError(),
+                           "failed to get parent device data");
+            goto out;
         }
 
-        /* SetupApi gives us the same information as driver with
-         * IoGetDeviceProperty. According to Microsoft
-         * https://support.microsoft.com/en-us/kb/253232
-         * FunctionNumber = (USHORT)((propertyAddress) & 0x0000FFFF);
-         * DeviceNumber = (USHORT)(((propertyAddress) >> 16) & 0x0000FFFF);
-         * SPDRP_ADDRESS is propertyAddress, so we do the same.*/
-
-        func = addr & 0x0000FFFF;
-        dev = (addr >> 16) & 0x0000FFFF;
-        pci = g_malloc0(sizeof(*pci));
-        pci->domain = dev;
-        pci->slot = slot;
-        pci->function = func;
-        pci->bus = bus;
+        for (j = 0;
+             SetupDiEnumDeviceInfo(parent_dev_info, j, &parent_dev_info_data);
+             j++) {
+            DWORD addr, bus, ui_slot, type;
+            int func, slot;
+
+            /*
+             * There is no need to allocate buffer in the next functions. The
+             * size is known and ULONG according to
+             * https://msdn.microsoft.com/en-us/library/windows/hardware/ff543095(v=vs.85).aspx
+             */
+            if (!SetupDiGetDeviceRegistryProperty(
+                  parent_dev_info, &parent_dev_info_data, SPDRP_BUSNUMBER,
+                  &type, (PBYTE)&bus, size, NULL)) {
+                debug_error("failed to get PCI bus");
+                bus = -1;
+                partial_pci = true;
+            }
+
+            /*
+             * The function retrieves the device's address. This value will be
+             * transformed into device function and number
+             */
+            if (!SetupDiGetDeviceRegistryProperty(
+                    parent_dev_info, &parent_dev_info_data, SPDRP_ADDRESS,
+                    &type, (PBYTE)&addr, size, NULL)) {
+                debug_error("failed to get PCI address");
+                addr = -1;
+                partial_pci = true;
+            }
+
+            /*
+             * This call returns UINumber of DEVICE_CAPABILITIES structure.
+             * This number is typically a user-perceived slot number.
+             */
+            if (!SetupDiGetDeviceRegistryProperty(
+                    parent_dev_info, &parent_dev_info_data, SPDRP_UI_NUMBER,
+                    &type, (PBYTE)&ui_slot, size, NULL)) {
+                debug_error("failed to get PCI slot");
+                ui_slot = -1;
+                partial_pci = true;
+            }
+
+            /*
+             * SetupApi gives us the same information as driver with
+             * IoGetDeviceProperty. According to Microsoft:
+             *
+             *   FunctionNumber = (USHORT)((propertyAddress) & 0x0000FFFF)
+             *   DeviceNumber = (USHORT)(((propertyAddress) >> 16) & 0x0000FFFF)
+             *   SPDRP_ADDRESS is propertyAddress, so we do the same.
+             *
+             * https://docs.microsoft.com/en-us/windows/desktop/api/setupapi/nf-setupapi-setupdigetdeviceregistrypropertya
+             */
+            if (partial_pci) {
+                pci->domain = -1;
+                pci->slot = -1;
+                pci->function = -1;
+                pci->bus = -1;
+                continue;
+            } else {
+                func = ((int)addr == -1) ? -1 : addr & 0x0000FFFF;
+                slot = ((int)addr == -1) ? -1 : (addr >> 16) & 0x0000FFFF;
+                if ((int)ui_slot != slot) {
+                    g_debug("mismatch with reported slot values: %d vs %d",
+                            (int)ui_slot, slot);
+                }
+                pci->domain = 0;
+                pci->slot = (int)ui_slot;
+                pci->function = func;
+                pci->bus = (int)bus;
+                break;
+            }
+        }
+        SetupDiDestroyDeviceInfoList(parent_dev_info);
         break;
     }
 
 free_dev_info:
     SetupDiDestroyDeviceInfoList(dev_info);
 out:
-    g_free(buffer);
-    g_free(name);
     return pci;
 }
 
-static int get_disk_bus_type(HANDLE vol_h, Error **errp)
+static void get_disk_properties(HANDLE vol_h, GuestDiskAddress *disk,
+    Error **errp)
 {
     STORAGE_PROPERTY_QUERY query;
     STORAGE_DEVICE_DESCRIPTOR *dev_desc, buf;
     DWORD received;
+    ULONG size = sizeof(buf);
 
     dev_desc = &buf;
-    dev_desc->Size = sizeof(buf);
     query.PropertyId = StorageDeviceProperty;
     query.QueryType = PropertyStandardQuery;
 
     if (!DeviceIoControl(vol_h, IOCTL_STORAGE_QUERY_PROPERTY, &query,
                          sizeof(STORAGE_PROPERTY_QUERY), dev_desc,
-                         dev_desc->Size, &received, NULL)) {
+                         size, &received, NULL)) {
         error_setg_win32(errp, GetLastError(), "failed to get bus type");
-        return -1;
+        return;
+    }
+    disk->bus_type = find_bus_type(dev_desc->BusType);
+    g_debug("bus type %d", disk->bus_type);
+
+    /* Query once more. Now with long enough buffer. */
+    size = dev_desc->Size;
+    dev_desc = g_malloc0(size);
+    if (!DeviceIoControl(vol_h, IOCTL_STORAGE_QUERY_PROPERTY, &query,
+                         sizeof(STORAGE_PROPERTY_QUERY), dev_desc,
+                         size, &received, NULL)) {
+        error_setg_win32(errp, GetLastError(), "failed to get serial number");
+        g_debug("failed to get serial number");
+        goto out_free;
     }
+    if (dev_desc->SerialNumberOffset > 0) {
+        const char *serial;
+        size_t len;
 
-    return dev_desc->BusType;
+        if (dev_desc->SerialNumberOffset >= received) {
+            error_setg(errp, "failed to get serial number: offset outside the buffer");
+            g_debug("serial number offset outside the buffer");
+            goto out_free;
+        }
+        serial = (char *)dev_desc + dev_desc->SerialNumberOffset;
+        len = received - dev_desc->SerialNumberOffset;
+        g_debug("serial number \"%s\"", serial);
+        if (*serial != 0) {
+            disk->serial = g_strndup(serial, len);
+            disk->has_serial = true;
+        }
+    }
+out_free:
+    g_free(dev_desc);
+
+    return;
 }
 
-/* VSS provider works with volumes, thus there is no difference if
- * the volume consist of spanned disks. Info about the first disk in the
- * volume is returned for the spanned disk group (LVM) */
-static GuestDiskAddressList *build_guest_disk_info(char *guid, Error **errp)
+static void get_single_disk_info(int disk_number,
+                                 GuestDiskAddress *disk, Error **errp)
 {
-    GuestDiskAddressList *list = NULL;
-    GuestDiskAddress *disk;
     SCSI_ADDRESS addr, *scsi_ad;
     DWORD len;
-    int bus;
-    HANDLE vol_h;
+    HANDLE disk_h;
+    Error *local_err = NULL;
 
     scsi_ad = &addr;
-    char *name = g_strndup(guid, strlen(guid)-1);
 
-    vol_h = CreateFile(name, 0, FILE_SHARE_READ, NULL, OPEN_EXISTING,
+    g_debug("getting disk info for: %s", disk->dev);
+    disk_h = CreateFile(disk->dev, 0, FILE_SHARE_READ, NULL, OPEN_EXISTING,
                        0, NULL);
-    if (vol_h == INVALID_HANDLE_VALUE) {
-        error_setg_win32(errp, GetLastError(), "failed to open volume");
-        goto out_free;
+    if (disk_h == INVALID_HANDLE_VALUE) {
+        error_setg_win32(errp, GetLastError(), "failed to open disk");
+        return;
     }
 
-    bus = get_disk_bus_type(vol_h, errp);
-    if (bus < 0) {
-        goto out_close;
+    get_disk_properties(disk_h, disk, &local_err);
+    if (local_err) {
+        error_propagate(errp, local_err);
+        goto err_close;
     }
 
-    disk = g_malloc0(sizeof(*disk));
-    disk->bus_type = find_bus_type(bus);
-    if (bus == BusTypeScsi || bus == BusTypeAta || bus == BusTypeRAID
-#if (_WIN32_WINNT >= 0x0600)
+    g_debug("bus type %d", disk->bus_type);
+    /* always set pci_controller as required by schema. get_pci_info() should
+     * report -1 values for non-PCI buses rather than fail. fail the command
+     * if that doesn't hold since that suggests some other unexpected
+     * breakage
+     */
+    disk->pci_controller = get_pci_info(disk_number, &local_err);
+    if (local_err) {
+        error_propagate(errp, local_err);
+        goto err_close;
+    }
+    if (disk->bus_type == GUEST_DISK_BUS_TYPE_SCSI
+            || disk->bus_type == GUEST_DISK_BUS_TYPE_IDE
+            || disk->bus_type == GUEST_DISK_BUS_TYPE_RAID
             /* This bus type is not supported before Windows Server 2003 SP1 */
-            || bus == BusTypeSas
-#endif
+            || disk->bus_type == GUEST_DISK_BUS_TYPE_SAS
         ) {
         /* We are able to use the same ioctls for different bus types
          * according to Microsoft docs
          * https://technet.microsoft.com/en-us/library/ee851589(v=ws.10).aspx */
-        if (DeviceIoControl(vol_h, IOCTL_SCSI_GET_ADDRESS, NULL, 0, scsi_ad,
+        g_debug("getting SCSI info");
+        if (DeviceIoControl(disk_h, IOCTL_SCSI_GET_ADDRESS, NULL, 0, scsi_ad,
                             sizeof(SCSI_ADDRESS), &len, NULL)) {
             disk->unit = addr.Lun;
             disk->target = addr.TargetId;
             disk->bus = addr.PathId;
-            disk->pci_controller = get_pci_info(name, errp);
         }
         /* We do not set error in this case, because we still have enough
          * information about volume. */
-    } else {
-         disk->pci_controller = NULL;
     }
 
-    list = g_malloc0(sizeof(*list));
-    list->value = disk;
-    list->next = NULL;
-out_close:
-    CloseHandle(vol_h);
-out_free:
+err_close:
+    CloseHandle(disk_h);
+    return;
+}
+
+/* VSS provider works with volumes, thus there is no difference if
+ * the volume consist of spanned disks. Info about the first disk in the
+ * volume is returned for the spanned disk group (LVM) */
+static GuestDiskAddressList *build_guest_disk_info(char *guid, Error **errp)
+{
+    Error *local_err = NULL;
+    GuestDiskAddressList *list = NULL, *cur_item = NULL;
+    GuestDiskAddress *disk = NULL;
+    int i;
+    HANDLE vol_h;
+    DWORD size;
+    PVOLUME_DISK_EXTENTS extents = NULL;
+
+    /* strip final backslash */
+    char *name = g_strdup(guid);
+    if (g_str_has_suffix(name, "\\")) {
+        name[strlen(name) - 1] = 0;
+    }
+
+    g_debug("opening %s", name);
+    vol_h = CreateFile(name, 0, FILE_SHARE_READ, NULL, OPEN_EXISTING,
+                       0, NULL);
+    if (vol_h == INVALID_HANDLE_VALUE) {
+        error_setg_win32(errp, GetLastError(), "failed to open volume");
+        goto out;
+    }
+
+    /* Get list of extents */
+    g_debug("getting disk extents");
+    size = sizeof(VOLUME_DISK_EXTENTS);
+    extents = g_malloc0(size);
+    if (!DeviceIoControl(vol_h, IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS, NULL,
+                         0, extents, size, &size, NULL)) {
+        DWORD last_err = GetLastError();
+        if (last_err == ERROR_MORE_DATA) {
+            /* Try once more with big enough buffer */
+            g_free(extents);
+            extents = g_malloc0(size);
+            if (!DeviceIoControl(
+                    vol_h, IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS, NULL,
+                    0, extents, size, NULL, NULL)) {
+                error_setg_win32(errp, GetLastError(),
+                    "failed to get disk extents");
+                goto out;
+            }
+        } else if (last_err == ERROR_INVALID_FUNCTION) {
+            /* Possibly CD-ROM or a shared drive. Try to pass the volume */
+            g_debug("volume not on disk");
+            disk = g_malloc0(sizeof(GuestDiskAddress));
+            disk->has_dev = true;
+            disk->dev = g_strdup(name);
+            get_single_disk_info(0xffffffff, disk, &local_err);
+            if (local_err) {
+                g_debug("failed to get disk info, ignoring error: %s",
+                    error_get_pretty(local_err));
+                error_free(local_err);
+                goto out;
+            }
+            list = g_malloc0(sizeof(*list));
+            list->value = disk;
+            disk = NULL;
+            list->next = NULL;
+            goto out;
+        } else {
+            error_setg_win32(errp, GetLastError(),
+                "failed to get disk extents");
+            goto out;
+        }
+    }
+    g_debug("Number of extents: %lu", extents->NumberOfDiskExtents);
+
+    /* Go through each extent */
+    for (i = 0; i < extents->NumberOfDiskExtents; i++) {
+        disk = g_malloc0(sizeof(GuestDiskAddress));
+
+        /* Disk numbers directly correspond to numbers used in UNCs
+         *
+         * See documentation for DISK_EXTENT:
+         * https://docs.microsoft.com/en-us/windows/desktop/api/winioctl/ns-winioctl-_disk_extent
+         *
+         * See also Naming Files, Paths and Namespaces:
+         * https://docs.microsoft.com/en-us/windows/desktop/FileIO/naming-a-file#win32-device-namespaces
+         */
+        disk->has_dev = true;
+        disk->dev = g_strdup_printf("\\\\.\\PhysicalDrive%lu",
+                                    extents->Extents[i].DiskNumber);
+
+        get_single_disk_info(extents->Extents[i].DiskNumber, disk, &local_err);
+        if (local_err) {
+            error_propagate(errp, local_err);
+            goto out;
+        }
+        cur_item = g_malloc0(sizeof(*list));
+        cur_item->value = disk;
+        disk = NULL;
+        cur_item->next = list;
+        list = cur_item;
+    }
+
+
+out:
+    if (vol_h != INVALID_HANDLE_VALUE) {
+        CloseHandle(vol_h);
+    }
+    qapi_free_GuestDiskAddress(disk);
+    g_free(extents);
     g_free(name);
+
     return list;
 }
 
@@ -670,6 +976,7 @@ static GuestFilesystemInfo *build_guest_fsinfo(char *guid, Error **errp)
     char fs_name[32];
     char vol_info[MAX_PATH+1];
     size_t len;
+    uint64_t i64FreeBytesToCaller, i64TotalBytes, i64FreeBytes;
     GuestFilesystemInfo *fs = NULL;
 
     GetVolumePathNamesForVolumeName(guid, (LPCH)&mnt, 0, &info_size);
@@ -699,10 +1006,21 @@ static GuestFilesystemInfo *build_guest_fsinfo(char *guid, Error **errp)
     fs_name[sizeof(fs_name) - 1] = 0;
     fs = g_malloc(sizeof(*fs));
     fs->name = g_strdup(guid);
+    fs->has_total_bytes = false;
+    fs->has_used_bytes = false;
     if (len == 0) {
         fs->mountpoint = g_strdup("System Reserved");
     } else {
         fs->mountpoint = g_strndup(mnt_point, len);
+        if (GetDiskFreeSpaceEx(fs->mountpoint,
+                               (PULARGE_INTEGER) & i64FreeBytesToCaller,
+                               (PULARGE_INTEGER) & i64TotalBytes,
+                               (PULARGE_INTEGER) & i64FreeBytes)) {
+            fs->used_bytes = i64TotalBytes - i64FreeBytes;
+            fs->total_bytes = i64TotalBytes;
+            fs->has_total_bytes = true;
+            fs->has_used_bytes = true;
+        }
     }
     fs->type = g_strdup(fs_name);
     fs->disk = build_guest_disk_info(guid, errp);
@@ -764,6 +1082,13 @@ GuestFsfreezeStatus qmp_guest_fsfreeze_status(Error **errp)
  * The frozen state is limited for up to 10 seconds by VSS.
  */
 int64_t qmp_guest_fsfreeze_freeze(Error **errp)
+{
+    return qmp_guest_fsfreeze_freeze_list(false, NULL, errp);
+}
+
+int64_t qmp_guest_fsfreeze_freeze_list(bool has_mountpoints,
+                                       strList *mountpoints,
+                                       Error **errp)
 {
     int i;
     Error *local_err = NULL;
@@ -778,7 +1103,7 @@ int64_t qmp_guest_fsfreeze_freeze(Error **errp)
     /* cannot risk guest agent blocking itself on a write in this state */
     ga_set_frozen(ga_state);
 
-    qga_vss_fsfreeze(&i, true, &local_err);
+    qga_vss_fsfreeze(&i, true, mountpoints, &local_err);
     if (local_err) {
         error_propagate(errp, local_err);
         goto error;
@@ -796,15 +1121,6 @@ error:
     return 0;
 }
 
-int64_t qmp_guest_fsfreeze_freeze_list(bool has_mountpoints,
-                                       strList *mountpoints,
-                                       Error **errp)
-{
-    error_setg(errp, QERR_UNSUPPORTED);
-
-    return 0;
-}
-
 /*
  * Thaw local file systems using Volume Shadow-copy Service.
  */
@@ -817,7 +1133,7 @@ int64_t qmp_guest_fsfreeze_thaw(Error **errp)
         return 0;
     }
 
-    qga_vss_fsfreeze(&i, false, errp);
+    qga_vss_fsfreeze(&i, false, NULL, errp);
 
     ga_unset_frozen(ga_state);
     return i;
@@ -853,6 +1169,19 @@ qmp_guest_fstrim(bool has_minimum, int64_t minimum, Error **errp)
     GuestFilesystemTrimResponse *resp;
     HANDLE handle;
     WCHAR guid[MAX_PATH] = L"";
+    OSVERSIONINFO osvi;
+    BOOL win8_or_later;
+
+    ZeroMemory(&osvi, sizeof(OSVERSIONINFO));
+    osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
+    GetVersionEx(&osvi);
+    win8_or_later = (osvi.dwMajorVersion > 6 ||
+                          ((osvi.dwMajorVersion == 6) &&
+                           (osvi.dwMinorVersion >= 2)));
+    if (!win8_or_later) {
+        error_setg(errp, "fstrim is only supported for Win8+");
+        return NULL;
+    }
 
     handle = FindFirstVolumeW(guid, ARRAYSIZE(guid));
     if (handle == INVALID_HANDLE_VALUE) {
@@ -1092,7 +1421,6 @@ static char *guest_addr_to_str(IP_ADAPTER_UNICAST_ADDRESS *ip_addr,
     return NULL;
 }
 
-#if (_WIN32_WINNT >= 0x0600)
 static int64_t guest_ip_prefix(IP_ADAPTER_UNICAST_ADDRESS *ip_addr)
 {
     /* For Windows Vista/2008 and newer, use the OnLinkPrefixLength
@@ -1100,60 +1428,6 @@ static int64_t guest_ip_prefix(IP_ADAPTER_UNICAST_ADDRESS *ip_addr)
      */
     return ip_addr->OnLinkPrefixLength;
 }
-#else
-/* When using the Windows XP and 2003 build environment, do the best we can to
- * figure out the prefix.
- */
-static IP_ADAPTER_INFO *guest_get_adapters_info(void)
-{
-    IP_ADAPTER_INFO *adptr_info = NULL;
-    ULONG adptr_info_len = 0;
-    DWORD ret;
-
-    /* Call the first time to get the adptr_info_len. */
-    GetAdaptersInfo(adptr_info, &adptr_info_len);
-
-    adptr_info = g_malloc(adptr_info_len);
-    ret = GetAdaptersInfo(adptr_info, &adptr_info_len);
-    if (ret != ERROR_SUCCESS) {
-        g_free(adptr_info);
-        adptr_info = NULL;
-    }
-    return adptr_info;
-}
-
-static int64_t guest_ip_prefix(IP_ADAPTER_UNICAST_ADDRESS *ip_addr)
-{
-    int64_t prefix = -1; /* Use for AF_INET6 and unknown/undetermined values. */
-    IP_ADAPTER_INFO *adptr_info, *info;
-    IP_ADDR_STRING *ip;
-    struct in_addr *p;
-
-    if (ip_addr->Address.lpSockaddr->sa_family != AF_INET) {
-        return prefix;
-    }
-    adptr_info = guest_get_adapters_info();
-    if (adptr_info == NULL) {
-        return prefix;
-    }
-
-    /* Match up the passed in ip_addr with one found in adaptr_info.
-     * The matching one in adptr_info will have the netmask.
-     */
-    p = &((struct sockaddr_in *)ip_addr->Address.lpSockaddr)->sin_addr;
-    for (info = adptr_info; info; info = info->Next) {
-        for (ip = &info->IpAddressList; ip; ip = ip->Next) {
-            if (p->S_un.S_addr == inet_addr(ip->IpAddress.String)) {
-                prefix = ctpop32(inet_addr(ip->IpMask.String));
-                goto out;
-            }
-        }
-    }
-out:
-    g_free(adptr_info);
-    return prefix;
-}
-#endif
 
 #define INTERFACE_PATH_BUF_SZ 512
 
@@ -1621,7 +1895,6 @@ GList *ga_command_blacklist_init(GList *blacklist)
         "guest-set-vcpus",
         "guest-get-memory-blocks", "guest-set-memory-blocks",
         "guest-get-memory-block-size",
-        "guest-fsfreeze-freeze-list",
         NULL};
     char **p = (char **)list_unsupported;
 
@@ -1675,7 +1948,6 @@ typedef struct _GA_WTSINFOA {
 
 GuestUserList *qmp_guest_get_users(Error **err)
 {
-#if (_WIN32_WINNT >= 0x0600)
 #define QGA_NANOSECONDS 10000000
 
     GHashTable *cache = NULL;
@@ -1745,10 +2017,6 @@ GuestUserList *qmp_guest_get_users(Error **err)
     }
     g_hash_table_destroy(cache);
     return head;
-#else
-    error_setg(err, QERR_UNSUPPORTED);
-    return NULL;
-#endif
 }
 
 typedef struct _ga_matrix_lookup_t {
@@ -1776,12 +2044,24 @@ static ga_matrix_lookup_t const WIN_VERSION_MATRIX[2][8] = {
         { 6, 1, "Microsoft Windows Server 2008 R2",     "2008r2"},
         { 6, 2, "Microsoft Windows Server 2012",        "2012"},
         { 6, 3, "Microsoft Windows Server 2012 R2",     "2012r2"},
-        {10, 0, "Microsoft Windows Server 2016",        "2016"},
+        { 0, 0, 0},
         { 0, 0, 0},
         { 0, 0, 0}
     }
 };
 
+typedef struct _ga_win_10_0_server_t {
+    int final_build;
+    char const *version;
+    char const *version_id;
+} ga_win_10_0_server_t;
+
+static ga_win_10_0_server_t const WIN_10_0_SERVER_VERSION_MATRIX[3] = {
+    {14393, "Microsoft Windows Server 2016",    "2016"},
+    {17763, "Microsoft Windows Server 2019",    "2019"},
+    {0, 0}
+};
+
 static void ga_get_win_version(RTL_OSVERSIONINFOEXW *info, Error **errp)
 {
     typedef NTSTATUS(WINAPI * rtl_get_version_t)(
@@ -1806,10 +2086,23 @@ static char *ga_get_win_name(OSVERSIONINFOEXW const *os_version, bool id)
 {
     DWORD major = os_version->dwMajorVersion;
     DWORD minor = os_version->dwMinorVersion;
+    DWORD build = os_version->dwBuildNumber;
     int tbl_idx = (os_version->wProductType != VER_NT_WORKSTATION);
     ga_matrix_lookup_t const *table = WIN_VERSION_MATRIX[tbl_idx];
+    ga_win_10_0_server_t const *win_10_0_table = WIN_10_0_SERVER_VERSION_MATRIX;
     while (table->version != NULL) {
-        if (major == table->major && minor == table->minor) {
+        if (major == 10 && minor == 0 && tbl_idx) {
+            while (win_10_0_table->version != NULL) {
+                if (build <= win_10_0_table->final_build) {
+                    if (id) {
+                        return g_strdup(win_10_0_table->version_id);
+                    } else {
+                        return g_strdup(win_10_0_table->version);
+                    }
+                }
+                win_10_0_table++;
+            }
+        } else if (major == table->major && minor == table->minor) {
             if (id) {
                 return g_strdup(table->version_id);
             } else {