diff options
-rwxr-xr-x | configure | 21 | ||||
-rw-r--r-- | qga/Makefile.objs | 1 | ||||
-rw-r--r-- | qga/channel-win32.c | 3 | ||||
-rw-r--r-- | qga/commands-posix.c | 152 | ||||
-rw-r--r-- | qga/commands-win32.c | 337 | ||||
-rw-r--r-- | qga/installer/qemu-ga.wxs | 2 | ||||
-rw-r--r-- | qga/main.c | 258 | ||||
-rw-r--r-- | qga/qapi-schema.json | 5 | ||||
-rw-r--r-- | qga/service-win32.h | 4 | ||||
-rw-r--r-- | qga/vss-win32.c | 5 | ||||
-rw-r--r-- | qga/vss-win32.h | 3 | ||||
-rw-r--r-- | qga/vss-win32/requester.cpp | 92 | ||||
-rw-r--r-- | qga/vss-win32/requester.h | 13 |
13 files changed, 686 insertions, 210 deletions
@@ -474,6 +474,7 @@ libxml2="" docker="no" debug_mutex="no" libpmem="" +libudev="no" # cross compilers defaults, can be overridden with --cross-cc-ARCH cross_cc_aarch64="aarch64-linux-gnu-gcc" @@ -870,6 +871,7 @@ Linux) vhost_vsock="yes" QEMU_INCLUDES="-I\$(SRC_PATH)/linux-headers -I$(pwd)/linux-headers $QEMU_INCLUDES" supported_os="yes" + libudev="yes" ;; esac @@ -5609,6 +5611,17 @@ if test "$libnfs" != "no" ; then fi fi +########################################## +# Do we have libudev +if test "$libudev" != "no" ; then + if $pkg_config libudev && test "$static" != "yes"; then + libudev="yes" + libudev_libs=$($pkg_config --libs libudev) + else + libudev="no" + fi +fi + # Now we've finished running tests it's OK to add -Werror to the compiler flags if test "$werror" = "yes"; then QEMU_CFLAGS="-Werror $QEMU_CFLAGS" @@ -6033,6 +6046,7 @@ echo "VxHS block device $vxhs" echo "capstone $capstone" echo "docker $docker" echo "libpmem support $libpmem" +echo "libudev $libudev" if test "$sdl_too_old" = "yes"; then echo "-> Your SDL version is too old - please upgrade to have SDL support" @@ -6128,7 +6142,7 @@ if test "$mingw32" = "yes" ; then echo "WIN_SDK=\"$win_sdk\"" >> $config_host_mak fi if test "$guest_agent_ntddscsi" = "yes" ; then - echo "CONFIG_QGA_NTDDDISK=y" >> $config_host_mak + echo "CONFIG_QGA_NTDDSCSI=y" >> $config_host_mak fi if test "$guest_agent_msi" = "yes"; then echo "QEMU_GA_MSI_ENABLED=yes" >> $config_host_mak @@ -6868,6 +6882,11 @@ if test "$docker" != "no"; then echo "HAVE_USER_DOCKER=y" >> $config_host_mak fi +if test "$libudev" != "no"; then + echo "CONFIG_LIBUDEV=y" >> $config_host_mak + echo "LIBUDEV_LIBS=$libudev_libs" >> $config_host_mak +fi + # use included Linux headers if test "$linux" = "yes" ; then mkdir -p linux-headers diff --git a/qga/Makefile.objs b/qga/Makefile.objs index ed08c5917c..80e6bb3c2e 100644 --- a/qga/Makefile.objs +++ b/qga/Makefile.objs @@ -1,3 +1,4 @@ +commands-posix.o-libs := $(LIBUDEV_LIBS) qga-obj-y = commands.o guest-agent-command-state.o main.o qga-obj-$(CONFIG_POSIX) += commands-posix.o channel-posix.o qga-obj-$(CONFIG_WIN32) += commands-win32.o channel-win32.o service-win32.o diff --git a/qga/channel-win32.c b/qga/channel-win32.c index b3597a8a0f..c86f4388db 100644 --- a/qga/channel-win32.c +++ b/qga/channel-win32.c @@ -302,7 +302,8 @@ static gboolean ga_channel_open(GAChannel *c, GAChannelMethod method, OPEN_EXISTING, FILE_FLAG_NO_BUFFERING | FILE_FLAG_OVERLAPPED, NULL); if (c->handle == INVALID_HANDLE_VALUE) { - g_critical("error opening path %s", newpath); + g_critical("error opening path %s: %s", newpath, + g_win32_error_message(GetLastError())); return false; } diff --git a/qga/commands-posix.c b/qga/commands-posix.c index 37e8a2d791..1877976522 100644 --- a/qga/commands-posix.c +++ b/qga/commands-posix.c @@ -48,6 +48,10 @@ extern char **environ; #include <net/if.h> #include <sys/statvfs.h> +#ifdef CONFIG_LIBUDEV +#include <libudev.h> +#endif + #ifdef FIFREEZE #define CONFIG_FSFREEZE #endif @@ -872,6 +876,10 @@ static void build_guest_fsinfo_for_real_device(char const *syspath, GuestDiskAddressList *list = NULL; bool has_ata = false, has_host = false, has_tgt = false; char *p, *q, *driver = NULL; +#ifdef CONFIG_LIBUDEV + struct udev *udev = NULL; + struct udev_device *udevice = NULL; +#endif p = strstr(syspath, "/devices/pci"); if (!p || sscanf(p + 12, "%*x:%*x/%x:%x:%x.%x%n", @@ -936,6 +944,26 @@ static void build_guest_fsinfo_for_real_device(char const *syspath, list = g_malloc0(sizeof(*list)); list->value = disk; +#ifdef CONFIG_LIBUDEV + udev = udev_new(); + udevice = udev_device_new_from_syspath(udev, syspath); + if (udev == NULL || udevice == NULL) { + g_debug("failed to query udev"); + } else { + const char *devnode, *serial; + devnode = udev_device_get_devnode(udevice); + if (devnode != NULL) { + disk->dev = g_strdup(devnode); + disk->has_dev = true; + } + serial = udev_device_get_property_value(udevice, "ID_SERIAL"); + if (serial != NULL && *serial != 0) { + disk->serial = g_strdup(serial); + disk->has_serial = true; + } + } +#endif + if (strcmp(driver, "ata_piix") == 0) { /* a host per ide bus, target*:0:<unit>:0 */ if (!has_host || !has_tgt) { @@ -995,14 +1023,19 @@ static void build_guest_fsinfo_for_real_device(char const *syspath, list->next = fs->disk; fs->disk = list; - g_free(driver); - return; + goto out; cleanup: if (list) { qapi_free_GuestDiskAddressList(list); } +out: g_free(driver); +#ifdef CONFIG_LIBUDEV + udev_unref(udev); + udev_device_unref(udevice); +#endif + return; } static void build_guest_fsinfo_for_device(char const *devpath, @@ -2035,61 +2068,56 @@ static long sysconf_exact(int name, const char *name_str, Error **errp) * Written members remain unmodified on error. */ static void transfer_vcpu(GuestLogicalProcessor *vcpu, bool sys2vcpu, - Error **errp) + char *dirpath, Error **errp) { - char *dirpath; + int fd; + int res; int dirfd; + static const char fn[] = "online"; - dirpath = g_strdup_printf("/sys/devices/system/cpu/cpu%" PRId64 "/", - vcpu->logical_id); dirfd = open(dirpath, O_RDONLY | O_DIRECTORY); if (dirfd == -1) { error_setg_errno(errp, errno, "open(\"%s\")", dirpath); - } else { - static const char fn[] = "online"; - int fd; - int res; - - fd = openat(dirfd, fn, sys2vcpu ? O_RDONLY : O_RDWR); - if (fd == -1) { - if (errno != ENOENT) { - error_setg_errno(errp, errno, "open(\"%s/%s\")", dirpath, fn); - } else if (sys2vcpu) { - vcpu->online = true; - vcpu->can_offline = false; - } else if (!vcpu->online) { - error_setg(errp, "logical processor #%" PRId64 " can't be " - "offlined", vcpu->logical_id); - } /* otherwise pretend successful re-onlining */ - } else { - unsigned char status; - - res = pread(fd, &status, 1, 0); - if (res == -1) { - error_setg_errno(errp, errno, "pread(\"%s/%s\")", dirpath, fn); - } else if (res == 0) { - error_setg(errp, "pread(\"%s/%s\"): unexpected EOF", dirpath, - fn); - } else if (sys2vcpu) { - vcpu->online = (status != '0'); - vcpu->can_offline = true; - } else if (vcpu->online != (status != '0')) { - status = '0' + vcpu->online; - if (pwrite(fd, &status, 1, 0) == -1) { - error_setg_errno(errp, errno, "pwrite(\"%s/%s\")", dirpath, - fn); - } - } /* otherwise pretend successful re-(on|off)-lining */ + return; + } - res = close(fd); - g_assert(res == 0); - } + fd = openat(dirfd, fn, sys2vcpu ? O_RDONLY : O_RDWR); + if (fd == -1) { + if (errno != ENOENT) { + error_setg_errno(errp, errno, "open(\"%s/%s\")", dirpath, fn); + } else if (sys2vcpu) { + vcpu->online = true; + vcpu->can_offline = false; + } else if (!vcpu->online) { + error_setg(errp, "logical processor #%" PRId64 " can't be " + "offlined", vcpu->logical_id); + } /* otherwise pretend successful re-onlining */ + } else { + unsigned char status; + + res = pread(fd, &status, 1, 0); + if (res == -1) { + error_setg_errno(errp, errno, "pread(\"%s/%s\")", dirpath, fn); + } else if (res == 0) { + error_setg(errp, "pread(\"%s/%s\"): unexpected EOF", dirpath, + fn); + } else if (sys2vcpu) { + vcpu->online = (status != '0'); + vcpu->can_offline = true; + } else if (vcpu->online != (status != '0')) { + status = '0' + vcpu->online; + if (pwrite(fd, &status, 1, 0) == -1) { + error_setg_errno(errp, errno, "pwrite(\"%s/%s\")", dirpath, + fn); + } + } /* otherwise pretend successful re-(on|off)-lining */ - res = close(dirfd); + res = close(fd); g_assert(res == 0); } - g_free(dirpath); + res = close(dirfd); + g_assert(res == 0); } GuestLogicalProcessorList *qmp_guest_get_vcpus(Error **errp) @@ -2107,17 +2135,21 @@ GuestLogicalProcessorList *qmp_guest_get_vcpus(Error **errp) while (local_err == NULL && current < sc_max) { GuestLogicalProcessor *vcpu; GuestLogicalProcessorList *entry; - - vcpu = g_malloc0(sizeof *vcpu); - vcpu->logical_id = current++; - vcpu->has_can_offline = true; /* lolspeak ftw */ - transfer_vcpu(vcpu, true, &local_err); - - entry = g_malloc0(sizeof *entry); - entry->value = vcpu; - - *link = entry; - link = &entry->next; + int64_t id = current++; + char *path = g_strdup_printf("/sys/devices/system/cpu/cpu%" PRId64 "/", + id); + + if (g_file_test(path, G_FILE_TEST_EXISTS)) { + vcpu = g_malloc0(sizeof *vcpu); + vcpu->logical_id = id; + vcpu->has_can_offline = true; /* lolspeak ftw */ + transfer_vcpu(vcpu, true, path, &local_err); + entry = g_malloc0(sizeof *entry); + entry->value = vcpu; + *link = entry; + link = &entry->next; + } + g_free(path); } if (local_err == NULL) { @@ -2138,7 +2170,11 @@ int64_t qmp_guest_set_vcpus(GuestLogicalProcessorList *vcpus, Error **errp) processed = 0; while (vcpus != NULL) { - transfer_vcpu(vcpus->value, false, &local_err); + char *path = g_strdup_printf("/sys/devices/system/cpu/cpu%" PRId64 "/", + vcpus->value->logical_id); + + transfer_vcpu(vcpus->value, false, path, &local_err); + g_free(path); if (local_err != NULL) { break; } diff --git a/qga/commands-win32.c b/qga/commands-win32.c index 98d9735389..ef1d7d48d2 100644 --- a/qga/commands-win32.c +++ b/qga/commands-win32.c @@ -89,6 +89,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 +166,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 +183,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; } - fh = CreateFile(path, guest_flags->desired_access, share_mode, sa_attr, + w_path = g_utf8_to_utf16(path, -1, NULL, NULL, &gerr); + if (!w_path) { + goto done; + } + + 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 +209,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; } @@ -465,15 +485,32 @@ 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]; } +/* XXX: The following function is BROKEN! + * + * It does not work and probably has never worked. When we query for list of + * disks we get cryptic names like "\Device\0000001d" instead of + * "\PhysicalDriveX" or "\HarddiskX". Whether the names can be translated one + * way or the other for comparison is an open question. + * + * When we query volume names (the original version) we are able to match those + * but then the property queries report error "Invalid function". (duh!) + */ + +/* DEFINE_GUID(GUID_DEVINTERFACE_VOLUME, 0x53f5630dL, 0xb6bf, 0x11d0, 0x94, 0xf2, 0x00, 0xa0, 0xc9, 0x1e, 0xfb, 0x8b); +*/ +DEFINE_GUID(GUID_DEVINTERFACE_DISK, + 0x53f56307L, 0xb6bf, 0x11d0, 0x94, 0xf2, + 0x00, 0xa0, 0xc9, 0x1e, 0xfb, 0x8b); + static GuestPCIAddress *get_pci_info(char *guid, Error **errp) { @@ -484,23 +521,38 @@ static GuestPCIAddress *get_pci_info(char *guid, Error **errp) char dev_name[MAX_PATH]; char *buffer = NULL; GuestPCIAddress *pci = NULL; - char *name = g_strdup(&guid[4]); + char *name = NULL; + bool partial_pci = false; + pci = g_malloc0(sizeof(*pci)); + pci->domain = -1; + pci->slot = -1; + pci->function = -1; + pci->bus = -1; + + if (g_str_has_prefix(guid, "\\\\.\\") || + g_str_has_prefix(guid, "\\\\?\\")) { + name = g_strdup(guid + 4); + } else { + name = g_strdup(guid); + } if (!QueryDosDevice(name, dev_name, ARRAY_SIZE(dev_name))) { error_setg_win32(errp, GetLastError(), "failed to get dos device name"); goto out; } - 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); for (i = 0; SetupDiEnumDeviceInfo(dev_info, i, &dev_info_data); i++) { - DWORD addr, bus, slot, func, dev, data, size2; + DWORD addr, bus, slot, data, size2; + int func, dev; while (!SetupDiGetDeviceRegistryProperty(dev_info, &dev_info_data, SPDRP_PHYSICAL_DEVICE_OBJECT_NAME, &data, (PBYTE)buffer, size, @@ -522,6 +574,7 @@ static GuestPCIAddress *get_pci_info(char *guid, Error **errp) if (g_strcmp0(buffer, dev_name)) { continue; } + g_debug("found device %s", dev_name); /* There is no need to allocate buffer in the next functions. The size * is known and ULONG according to @@ -530,21 +583,27 @@ static GuestPCIAddress *get_pci_info(char *guid, Error **errp) */ if (!SetupDiGetDeviceRegistryProperty(dev_info, &dev_info_data, SPDRP_BUSNUMBER, &data, (PBYTE)&bus, size, NULL)) { - break; + debug_error("failed to get 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(dev_info, &dev_info_data, SPDRP_ADDRESS, &data, (PBYTE)&addr, size, NULL)) { - break; + debug_error("failed to get 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(dev_info, &dev_info_data, SPDRP_UI_NUMBER, &data, (PBYTE)&slot, size, NULL)) { - break; + debug_error("failed to get slot"); + slot = -1; + partial_pci = true; } /* SetupApi gives us the same information as driver with @@ -554,13 +613,19 @@ static GuestPCIAddress *get_pci_info(char *guid, Error **errp) * 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; + if (partial_pci) { + pci->domain = -1; + pci->slot = -1; + pci->function = -1; + pci->bus = -1; + } else { + func = ((int) addr == -1) ? -1 : addr & 0x0000FFFF; + dev = ((int) addr == -1) ? -1 : (addr >> 16) & 0x0000FFFF; + pci->domain = dev; + pci->slot = (int) slot; + pci->function = func; + pci->bus = (int) bus; + } break; } @@ -572,85 +637,228 @@ out: 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(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 + 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->dev, &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 #if (_WIN32_WINNT >= 0x0600) /* This bus type is not supported before Windows Server 2003 SP1 */ - || bus == BusTypeSas + || disk->bus_type == GUEST_DISK_BUS_TYPE_SAS #endif ) { /* 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 pci-controller 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, NULL, NULL)) { + DWORD last_err = GetLastError(); + if (last_err == ERROR_MORE_DATA) { + /* Try once more with big enough buffer */ + size = sizeof(VOLUME_DISK_EXTENTS) + + extents->NumberOfDiskExtents*sizeof(DISK_EXTENT); + 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"); + return NULL; + } + } 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(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(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: + qapi_free_GuestDiskAddress(disk); + g_free(extents); g_free(name); + return list; } @@ -777,6 +985,13 @@ GuestFsfreezeStatus qmp_guest_fsfreeze_status(Error **errp) */ 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; @@ -790,7 +1005,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; @@ -808,15 +1023,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. */ @@ -829,7 +1035,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; @@ -1646,7 +1852,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; diff --git a/qga/installer/qemu-ga.wxs b/qga/installer/qemu-ga.wxs index f751a7e9f7..64bf90bd85 100644 --- a/qga/installer/qemu-ga.wxs +++ b/qga/installer/qemu-ga.wxs @@ -78,7 +78,7 @@ Account="LocalSystem" ErrorControl="ignore" Interactive="no" - Arguments="-d" + Arguments="-d --retry-path" > </ServiceInstall> <ServiceControl Id="StartService" Start="install" Stop="both" Remove="uninstall" Name="QEMU-GA" Wait="no" /> diff --git a/qga/main.c b/qga/main.c index c399320d3c..87a0711c14 100644 --- a/qga/main.c +++ b/qga/main.c @@ -34,6 +34,7 @@ #include "qemu/systemd.h" #include "qemu-version.h" #ifdef _WIN32 +#include <dbt.h> #include "qga/service-win32.h" #include "qga/vss-win32.h" #endif @@ -58,6 +59,7 @@ #endif #define QGA_SENTINEL_BYTE 0xFF #define QGA_CONF_DEFAULT CONFIG_QEMU_CONFDIR G_DIR_SEPARATOR_S "qemu-ga.conf" +#define QGA_RETRY_INTERVAL 5 static struct { const char *state_dir; @@ -69,6 +71,8 @@ typedef struct GAPersistentState { int64_t fd_counter; } GAPersistentState; +typedef struct GAConfig GAConfig; + struct GAState { JSONMessageParser parser; GMainLoop *main_loop; @@ -80,6 +84,7 @@ struct GAState { bool logging_enabled; #ifdef _WIN32 GAService service; + HANDLE wakeup_event; #endif bool delimit_response; bool frozen; @@ -94,6 +99,9 @@ struct GAState { #endif gchar *pstate_filepath; GAPersistentState pstate; + GAConfig *config; + int socket_activation; + bool force_exit; }; struct GAState *ga_state; @@ -113,8 +121,11 @@ static const char *ga_freeze_whitelist[] = { #ifdef _WIN32 DWORD WINAPI service_ctrl_handler(DWORD ctrl, DWORD type, LPVOID data, LPVOID ctx); +DWORD WINAPI handle_serial_device_events(DWORD type, LPVOID data); VOID WINAPI service_main(DWORD argc, TCHAR *argv[]); #endif +static int run_agent(GAState *s); +static void stop_agent(GAState *s, bool requested); static void init_dfl_pathnames(void) @@ -151,7 +162,7 @@ static void quit_handler(int sig) WaitForSingleObject(hEventTimeout, 0); CloseHandle(hEventTimeout); } - qga_vss_fsfreeze(&i, false, &err); + qga_vss_fsfreeze(&i, false, NULL, &err); if (err) { g_debug("Error unfreezing filesystems prior to exiting: %s", error_get_pretty(err)); @@ -163,9 +174,7 @@ static void quit_handler(int sig) } g_debug("received signal num %d, quitting", sig); - if (g_main_loop_is_running(ga_state->main_loop)) { - g_main_loop_quit(ga_state->main_loop); - } + stop_agent(ga_state, true); } #ifndef _WIN32 @@ -250,6 +259,10 @@ QEMU_COPYRIGHT "\n" " to list available RPCs)\n" " -D, --dump-conf dump a qemu-ga config file based on current config\n" " options / command-line parameters to stdout\n" +" -r, --retry-path attempt re-opening path if it's unavailable or closed\n" +" due to an error which may be recoverable in the future\n" +" (virtio-serial driver re-install, serial device hot\n" +" plug/unplug, etc.)\n" " -h, --help display this help and exit\n" "\n" QEMU_HELP_BOTTOM "\n" @@ -609,6 +622,7 @@ static gboolean channel_event_cb(GIOCondition condition, gpointer data) switch (status) { case G_IO_STATUS_ERROR: g_warning("error reading channel"); + stop_agent(s, false); return false; case G_IO_STATUS_NORMAL: buf[count] = 0; @@ -666,6 +680,36 @@ static gboolean channel_init(GAState *s, const gchar *method, const gchar *path, } #ifdef _WIN32 +DWORD WINAPI handle_serial_device_events(DWORD type, LPVOID data) +{ + DWORD ret = NO_ERROR; + PDEV_BROADCAST_HDR broadcast_header = (PDEV_BROADCAST_HDR)data; + + if (broadcast_header->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE) { + switch (type) { + /* Device inserted */ + case DBT_DEVICEARRIVAL: + /* Start QEMU-ga's service */ + if (!SetEvent(ga_state->wakeup_event)) { + ret = GetLastError(); + } + break; + /* Device removed */ + case DBT_DEVICEQUERYREMOVE: + case DBT_DEVICEREMOVEPENDING: + case DBT_DEVICEREMOVECOMPLETE: + /* Stop QEMU-ga's service */ + if (!ResetEvent(ga_state->wakeup_event)) { + ret = GetLastError(); + } + break; + default: + ret = ERROR_CALL_NOT_IMPLEMENTED; + } + } + return ret; +} + DWORD WINAPI service_ctrl_handler(DWORD ctrl, DWORD type, LPVOID data, LPVOID ctx) { @@ -677,9 +721,13 @@ DWORD WINAPI service_ctrl_handler(DWORD ctrl, DWORD type, LPVOID data, case SERVICE_CONTROL_STOP: case SERVICE_CONTROL_SHUTDOWN: quit_handler(SIGTERM); + SetEvent(ga_state->wakeup_event); service->status.dwCurrentState = SERVICE_STOP_PENDING; SetServiceStatus(service->status_handle, &service->status); break; + case SERVICE_CONTROL_DEVICEEVENT: + handle_serial_device_events(type, data); + break; default: ret = ERROR_CALL_NOT_IMPLEMENTED; @@ -706,10 +754,24 @@ VOID WINAPI service_main(DWORD argc, TCHAR *argv[]) service->status.dwServiceSpecificExitCode = NO_ERROR; service->status.dwCheckPoint = 0; service->status.dwWaitHint = 0; + DEV_BROADCAST_DEVICEINTERFACE notification_filter; + ZeroMemory(¬ification_filter, sizeof(notification_filter)); + notification_filter.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE; + notification_filter.dbcc_size = sizeof(DEV_BROADCAST_DEVICEINTERFACE); + notification_filter.dbcc_classguid = GUID_VIOSERIAL_PORT; + + service->device_notification_handle = + RegisterDeviceNotification(service->status_handle, + ¬ification_filter, DEVICE_NOTIFY_SERVICE_HANDLE); + if (!service->device_notification_handle) { + g_critical("Failed to register device notification handle!\n"); + return; + } SetServiceStatus(service->status_handle, &service->status); - g_main_loop_run(ga_state->main_loop); + run_agent(ga_state); + UnregisterDeviceNotification(service->device_notification_handle); service->status.dwCurrentState = SERVICE_STOPPED; SetServiceStatus(service->status_handle, &service->status); } @@ -905,7 +967,7 @@ static GList *split_list(const gchar *str, const gchar *delim) return list; } -typedef struct GAConfig { +struct GAConfig { char *channel_path; char *method; char *log_filepath; @@ -922,7 +984,8 @@ typedef struct GAConfig { int daemonize; GLogLevelFlags log_level; int dumpconf; -} GAConfig; + bool retry_path; +}; static void config_load(GAConfig *config) { @@ -971,6 +1034,10 @@ static void config_load(GAConfig *config) /* enable all log levels */ config->log_level = G_LOG_LEVEL_MASK; } + if (g_key_file_has_key(keyfile, "general", "retry-path", NULL)) { + config->retry_path = + g_key_file_get_boolean(keyfile, "general", "retry-path", &gerr); + } if (g_key_file_has_key(keyfile, "general", "blacklist", NULL)) { config->bliststr = g_key_file_get_string(keyfile, "general", "blacklist", &gerr); @@ -1032,6 +1099,8 @@ static void config_dump(GAConfig *config) g_key_file_set_string(keyfile, "general", "statedir", config->state_dir); g_key_file_set_boolean(keyfile, "general", "verbose", config->log_level == G_LOG_LEVEL_MASK); + g_key_file_set_boolean(keyfile, "general", "retry-path", + config->retry_path); tmp = list_join(config->blacklist, ','); g_key_file_set_string(keyfile, "general", "blacklist", tmp); g_free(tmp); @@ -1050,7 +1119,7 @@ static void config_dump(GAConfig *config) static void config_parse(GAConfig *config, int argc, char **argv) { - const char *sopt = "hVvdm:p:l:f:F::b:s:t:D"; + const char *sopt = "hVvdm:p:l:f:F::b:s:t:Dr"; int opt_ind = 0, ch; const struct option lopt[] = { { "help", 0, NULL, 'h' }, @@ -1070,6 +1139,7 @@ static void config_parse(GAConfig *config, int argc, char **argv) { "service", 1, NULL, 's' }, #endif { "statedir", 1, NULL, 't' }, + { "retry-path", 0, NULL, 'r' }, { NULL, 0, NULL, 0 } }; @@ -1114,6 +1184,9 @@ static void config_parse(GAConfig *config, int argc, char **argv) case 'D': config->dumpconf = 1; break; + case 'r': + config->retry_path = true; + break; case 'b': { if (is_help_option(optarg)) { qmp_for_each_command(&ga_commands, ga_print_cmd, NULL); @@ -1211,9 +1284,21 @@ static bool check_is_frozen(GAState *s) return false; } -static int run_agent(GAState *s, GAConfig *config, int socket_activation) +static GAState *initialize_agent(GAConfig *config, int socket_activation) { - ga_state = s; + GAState *s = g_new0(GAState, 1); + + g_assert(ga_state == NULL); + + s->log_level = config->log_level; + s->log_file = stderr; +#ifdef CONFIG_FSFREEZE + s->fsfreeze_hook = config->fsfreeze_hook; +#endif + s->pstate_filepath = g_strdup_printf("%s/qga.state", config->state_dir); + s->state_filepath_isfrozen = g_strdup_printf("%s/qga.state.isfrozen", + config->state_dir); + s->frozen = check_is_frozen(s); g_log_set_default_handler(ga_log, s); g_log_set_fatal_mask(NULL, G_LOG_LEVEL_ERROR); @@ -1229,7 +1314,7 @@ static int run_agent(GAState *s, GAConfig *config, int socket_activation) if (g_mkdir_with_parents(config->state_dir, S_IRWXU) == -1) { g_critical("unable to create (an ancestor of) the state directory" " '%s': %s", config->state_dir, strerror(errno)); - return EXIT_FAILURE; + return NULL; } #endif @@ -1254,7 +1339,7 @@ static int run_agent(GAState *s, GAConfig *config, int socket_activation) if (!log_file) { g_critical("unable to open specified log file: %s", strerror(errno)); - return EXIT_FAILURE; + return NULL; } s->log_file = log_file; } @@ -1265,7 +1350,7 @@ static int run_agent(GAState *s, GAConfig *config, int socket_activation) s->pstate_filepath, ga_is_frozen(s))) { g_critical("failed to load persistent state"); - return EXIT_FAILURE; + return NULL; } config->blacklist = ga_command_blacklist_init(config->blacklist); @@ -1286,36 +1371,116 @@ static int run_agent(GAState *s, GAConfig *config, int socket_activation) #ifndef _WIN32 if (!register_signal_handlers()) { g_critical("failed to register signal handlers"); - return EXIT_FAILURE; + return NULL; } #endif s->main_loop = g_main_loop_new(NULL, false); - if (!channel_init(ga_state, config->method, config->channel_path, - socket_activation ? FIRST_SOCKET_ACTIVATION_FD : -1)) { + s->config = config; + s->socket_activation = socket_activation; + +#ifdef _WIN32 + s->wakeup_event = CreateEvent(NULL, TRUE, FALSE, TEXT("WakeUp")); + if (s->wakeup_event == NULL) { + g_critical("CreateEvent failed"); + return NULL; + } +#endif + + ga_state = s; + return s; +} + +static void cleanup_agent(GAState *s) +{ +#ifdef _WIN32 + CloseHandle(s->wakeup_event); +#endif + if (s->command_state) { + ga_command_state_cleanup_all(s->command_state); + ga_command_state_free(s->command_state); + json_message_parser_destroy(&s->parser); + } + g_free(s->pstate_filepath); + g_free(s->state_filepath_isfrozen); + if (s->main_loop) { + g_main_loop_unref(s->main_loop); + } + g_free(s); + ga_state = NULL; +} + +static int run_agent_once(GAState *s) +{ + if (!channel_init(s, s->config->method, s->config->channel_path, + s->socket_activation ? FIRST_SOCKET_ACTIVATION_FD : -1)) { g_critical("failed to initialize guest agent channel"); return EXIT_FAILURE; } -#ifndef _WIN32 + g_main_loop_run(ga_state->main_loop); + + if (s->channel) { + ga_channel_free(s->channel); + } + + return EXIT_SUCCESS; +} + +static void wait_for_channel_availability(GAState *s) +{ + g_warning("waiting for channel path..."); +#ifndef _WIN32 + sleep(QGA_RETRY_INTERVAL); #else - if (config->daemonize) { - SERVICE_TABLE_ENTRY service_table[] = { - { (char *)QGA_SERVICE_NAME, service_main }, { NULL, NULL } }; - StartServiceCtrlDispatcher(service_table); - } else { - g_main_loop_run(ga_state->main_loop); + DWORD dwWaitResult; + + dwWaitResult = WaitForSingleObject(s->wakeup_event, INFINITE); + + switch (dwWaitResult) { + case WAIT_OBJECT_0: + break; + case WAIT_TIMEOUT: + break; + default: + g_critical("WaitForSingleObject failed"); } #endif +} - return EXIT_SUCCESS; +static int run_agent(GAState *s) +{ + int ret = EXIT_SUCCESS; + + s->force_exit = false; + + do { + ret = run_agent_once(s); + if (s->config->retry_path && !s->force_exit) { + g_warning("agent stopped unexpectedly, restarting..."); + wait_for_channel_availability(s); + } + } while (s->config->retry_path && !s->force_exit); + + return ret; +} + +static void stop_agent(GAState *s, bool requested) +{ + if (!s->force_exit) { + s->force_exit = requested; + } + + if (g_main_loop_is_running(s->main_loop)) { + g_main_loop_quit(s->main_loop); + } } int main(int argc, char **argv) { int ret = EXIT_SUCCESS; - GAState *s = g_new0(GAState, 1); + GAState *s; GAConfig *config = g_new0(GAConfig, 1); int socket_activation; @@ -1383,44 +1548,37 @@ int main(int argc, char **argv) } } - s->log_level = config->log_level; - s->log_file = stderr; -#ifdef CONFIG_FSFREEZE - s->fsfreeze_hook = config->fsfreeze_hook; -#endif - s->pstate_filepath = g_strdup_printf("%s/qga.state", config->state_dir); - s->state_filepath_isfrozen = g_strdup_printf("%s/qga.state.isfrozen", - config->state_dir); - s->frozen = check_is_frozen(s); - if (config->dumpconf) { config_dump(config); goto end; } - ret = run_agent(s, config, socket_activation); - -end: - if (s->command_state) { - ga_command_state_cleanup_all(s->command_state); - ga_command_state_free(s->command_state); - json_message_parser_destroy(&s->parser); + s = initialize_agent(config, socket_activation); + if (!s) { + g_critical("error initializing guest agent"); + goto end; } - if (s->channel) { - ga_channel_free(s->channel); + +#ifdef _WIN32 + if (config->daemonize) { + SERVICE_TABLE_ENTRY service_table[] = { + { (char *)QGA_SERVICE_NAME, service_main }, { NULL, NULL } }; + StartServiceCtrlDispatcher(service_table); + } else { + ret = run_agent(s); } - g_free(s->pstate_filepath); - g_free(s->state_filepath_isfrozen); +#else + ret = run_agent(s); +#endif + cleanup_agent(s); + +end: if (config->daemonize) { unlink(config->pid_filepath); } config_free(config); - if (s->main_loop) { - g_main_loop_unref(s->main_loop); - } - g_free(s); return ret; } diff --git a/qga/qapi-schema.json b/qga/qapi-schema.json index dfbc4a5e32..c6725b3ec8 100644 --- a/qga/qapi-schema.json +++ b/qga/qapi-schema.json @@ -834,13 +834,16 @@ # @bus: bus id # @target: target id # @unit: unit id +# @serial: serial number (since: 3.1) +# @dev: device node (POSIX) or device UNC (Windows) (since: 3.1) # # Since: 2.2 ## { 'struct': 'GuestDiskAddress', 'data': {'pci-controller': 'GuestPCIAddress', 'bus-type': 'GuestDiskBusType', - 'bus': 'int', 'target': 'int', 'unit': 'int'} } + 'bus': 'int', 'target': 'int', 'unit': 'int', + '*serial': 'str', '*dev': 'str'} } ## # @GuestFilesystemInfo: diff --git a/qga/service-win32.h b/qga/service-win32.h index 89e99dfede..7b16d69b57 100644 --- a/qga/service-win32.h +++ b/qga/service-win32.h @@ -20,9 +20,13 @@ #define QGA_SERVICE_NAME "qemu-ga" #define QGA_SERVICE_DESCRIPTION "Enables integration with QEMU machine emulator and virtualizer." +static const GUID GUID_VIOSERIAL_PORT = { 0x6fde7521, 0x1b65, 0x48ae, +{ 0xb6, 0x28, 0x80, 0xbe, 0x62, 0x1, 0x60, 0x26 } }; + typedef struct GAService { SERVICE_STATUS status; SERVICE_STATUS_HANDLE status_handle; + HDEVNOTIFY device_notification_handle; } GAService; int ga_install_service(const char *path, const char *logfile, diff --git a/qga/vss-win32.c b/qga/vss-win32.c index a541f3ae01..f444a25a70 100644 --- a/qga/vss-win32.c +++ b/qga/vss-win32.c @@ -147,7 +147,8 @@ void ga_uninstall_vss_provider(void) } /* Call VSS requester and freeze/thaw filesystems and applications */ -void qga_vss_fsfreeze(int *nr_volume, bool freeze, Error **errp) +void qga_vss_fsfreeze(int *nr_volume, bool freeze, + strList *mountpoints, Error **errp) { const char *func_name = freeze ? "requester_freeze" : "requester_thaw"; QGAVSSRequesterFunc func; @@ -164,5 +165,5 @@ void qga_vss_fsfreeze(int *nr_volume, bool freeze, Error **errp) return; } - func(nr_volume, &errset); + func(nr_volume, mountpoints, &errset); } diff --git a/qga/vss-win32.h b/qga/vss-win32.h index 4f8e39aa5c..ce2abe5a72 100644 --- a/qga/vss-win32.h +++ b/qga/vss-win32.h @@ -22,6 +22,7 @@ bool vss_initialized(void); int ga_install_vss_provider(void); void ga_uninstall_vss_provider(void); -void qga_vss_fsfreeze(int *nr_volume, bool freeze, Error **errp); +void qga_vss_fsfreeze(int *nr_volume, bool freeze, + strList *mountpints, Error **errp); #endif diff --git a/qga/vss-win32/requester.cpp b/qga/vss-win32/requester.cpp index 3d9c9716c0..5378c55d23 100644 --- a/qga/vss-win32/requester.cpp +++ b/qga/vss-win32/requester.cpp @@ -234,7 +234,7 @@ out: } } -void requester_freeze(int *num_vols, ErrorSet *errset) +void requester_freeze(int *num_vols, void *mountpoints, ErrorSet *errset) { COMPointer<IVssAsync> pAsync; HANDLE volume; @@ -246,6 +246,7 @@ void requester_freeze(int *num_vols, ErrorSet *errset) WCHAR short_volume_name[64], *display_name = short_volume_name; DWORD wait_status; int num_fixed_drives = 0, i; + int num_mount_points = 0; if (vss_ctx.pVssbc) { /* already frozen */ *num_vols = 0; @@ -337,39 +338,73 @@ void requester_freeze(int *num_vols, ErrorSet *errset) goto out; } - volume = FindFirstVolumeW(short_volume_name, sizeof(short_volume_name)); - if (volume == INVALID_HANDLE_VALUE) { - err_set(errset, hr, "failed to find first volume"); - goto out; - } - for (;;) { - if (GetDriveTypeW(short_volume_name) == DRIVE_FIXED) { + if (mountpoints) { + PWCHAR volume_name_wchar; + for (volList *list = (volList *)mountpoints; list; list = list->next) { + size_t len = strlen(list->value) + 1; + size_t converted = 0; VSS_ID pid; - hr = vss_ctx.pVssbc->AddToSnapshotSet(short_volume_name, + + volume_name_wchar = new wchar_t[len]; + mbstowcs_s(&converted, volume_name_wchar, len, + list->value, _TRUNCATE); + + hr = vss_ctx.pVssbc->AddToSnapshotSet(volume_name_wchar, g_gProviderId, &pid); if (FAILED(hr)) { - WCHAR volume_path_name[PATH_MAX]; - if (GetVolumePathNamesForVolumeNameW( - short_volume_name, volume_path_name, - sizeof(volume_path_name), NULL) && *volume_path_name) { - display_name = volume_path_name; - } err_set(errset, hr, "failed to add %S to snapshot set", - display_name); - FindVolumeClose(volume); + volume_name_wchar); + delete volume_name_wchar; goto out; } - num_fixed_drives++; + num_mount_points++; + + delete volume_name_wchar; } - if (!FindNextVolumeW(volume, short_volume_name, - sizeof(short_volume_name))) { - FindVolumeClose(volume); - break; + + if (num_mount_points == 0) { + /* If there is no valid mount points, just exit. */ + goto out; } } - if (num_fixed_drives == 0) { - goto out; /* If there is no fixed drive, just exit. */ + if (!mountpoints) { + volume = FindFirstVolumeW(short_volume_name, sizeof(short_volume_name)); + if (volume == INVALID_HANDLE_VALUE) { + err_set(errset, hr, "failed to find first volume"); + goto out; + } + + for (;;) { + if (GetDriveTypeW(short_volume_name) == DRIVE_FIXED) { + VSS_ID pid; + hr = vss_ctx.pVssbc->AddToSnapshotSet(short_volume_name, + g_gProviderId, &pid); + if (FAILED(hr)) { + WCHAR volume_path_name[PATH_MAX]; + if (GetVolumePathNamesForVolumeNameW( + short_volume_name, volume_path_name, + sizeof(volume_path_name), NULL) && + *volume_path_name) { + display_name = volume_path_name; + } + err_set(errset, hr, "failed to add %S to snapshot set", + display_name); + FindVolumeClose(volume); + goto out; + } + num_fixed_drives++; + } + if (!FindNextVolumeW(volume, short_volume_name, + sizeof(short_volume_name))) { + FindVolumeClose(volume); + break; + } + } + + if (num_fixed_drives == 0) { + goto out; /* If there is no fixed drive, just exit. */ + } } hr = vss_ctx.pVssbc->PrepareForBackup(pAsync.replace()); @@ -435,7 +470,12 @@ void requester_freeze(int *num_vols, ErrorSet *errset) goto out; } - *num_vols = vss_ctx.cFrozenVols = num_fixed_drives; + if (mountpoints) { + *num_vols = vss_ctx.cFrozenVols = num_mount_points; + } else { + *num_vols = vss_ctx.cFrozenVols = num_fixed_drives; + } + return; out: @@ -449,7 +489,7 @@ out1: } -void requester_thaw(int *num_vols, ErrorSet *errset) +void requester_thaw(int *num_vols, void *mountpints, ErrorSet *errset) { COMPointer<IVssAsync> pAsync; diff --git a/qga/vss-win32/requester.h b/qga/vss-win32/requester.h index 2a39d734a2..5a8e8faf0c 100644 --- a/qga/vss-win32/requester.h +++ b/qga/vss-win32/requester.h @@ -34,9 +34,16 @@ typedef struct ErrorSet { STDAPI requester_init(void); STDAPI requester_deinit(void); -typedef void (*QGAVSSRequesterFunc)(int *, ErrorSet *); -void requester_freeze(int *num_vols, ErrorSet *errset); -void requester_thaw(int *num_vols, ErrorSet *errset); +typedef struct volList volList; + +struct volList { + volList *next; + char *value; +}; + +typedef void (*QGAVSSRequesterFunc)(int *, void *, ErrorSet *); +void requester_freeze(int *num_vols, void *volList, ErrorSet *errset); +void requester_thaw(int *num_vols, void *volList, ErrorSet *errset); #ifdef __cplusplus } |