mirror of https://github.com/xemu-project/xemu.git
qemu-ga patch queue for soft-freeze
* support for --retry-path option for recovering from communication path failures * support for serial/device name in guest-get-fsinfo for linux/w32 * support for freezing individual mount points in guest-fsfreeze-* * fixes for unicode paths on w32, not-present vcpus in guest-get-vcpus, buffer overflow in guest-get-fsinfo for w32, and other minor fixes v3: * remove redundant check for --static in configure * correct authorship on "qga-win: add debugging information" v2: * set libudev=off in configure for static builds -----BEGIN PGP SIGNATURE----- iQFOBAABCgA4FiEEzqzJ4VU066u4LT+gM1PJzvEItYQFAlvZuKYaHG1kcm90aEBs aW51eC52bmV0LmlibS5jb20ACgkQM1PJzvEItYT4Agf+NdHTXor+hT8A8D/Tk2bf 3lU3F/PsdS+jY19IPrvXzBAZ2Hh96rHPRceTJKw4AbUHtTN6mYK2Hz1FQw5Pauya u3rmqZfW4P4noyeLgHR3bnVJ5729lJEtJ2DBKIbX3fYpYCVAvUubZesL/dSnFUhf DdMvYXaZl3O943E+RgheM/y1SxYr4lB69Nrk6SMtg0jxGYWJt594JttJRJ97ShUv 6Y4NPZev5caUy+0ozSJopi92TEh2oIe71pJ97Ap0quKI3ENSYgc2OylnGnxUzJZl FdAs994WtEZJeTUaBxrMGytl3TQzosMEtPMhXZJn1P0Odyx0ziQCQy+zVbo5+XPY vQ== =drMH -----END PGP SIGNATURE----- Merge remote-tracking branch 'remotes/mdroth/tags/qga-pull-2018-10-30-v3-tag' into staging qemu-ga patch queue for soft-freeze * support for --retry-path option for recovering from communication path failures * support for serial/device name in guest-get-fsinfo for linux/w32 * support for freezing individual mount points in guest-fsfreeze-* * fixes for unicode paths on w32, not-present vcpus in guest-get-vcpus, buffer overflow in guest-get-fsinfo for w32, and other minor fixes v3: * remove redundant check for --static in configure * correct authorship on "qga-win: add debugging information" v2: * set libudev=off in configure for static builds # gpg: Signature made Wed 31 Oct 2018 14:13:58 GMT # gpg: using RSA key 3353C9CEF108B584 # gpg: Good signature from "Michael Roth <flukshun@gmail.com>" # gpg: aka "Michael Roth <mdroth@utexas.edu>" # gpg: aka "Michael Roth <mdroth@linux.vnet.ibm.com>" # Primary key fingerprint: CEAC C9E1 5534 EBAB B82D 3FA0 3353 C9CE F108 B584 * remotes/mdroth/tags/qga-pull-2018-10-30-v3-tag: (24 commits) qga-win: changing --retry-path option behavior qga-win: report specific error when failing to open channel qga-win: install service with --retry-path set by default qga: add --retry-path option for re-initializing channel on failure qga: move w32 service handling out of run_agent() qga: hang GAConfig/socket_activation off of GAState global qga: group agent init/cleanup init separate routines qga: fix an off-by-one issue qga-win: demystify namespace stripping qga-win: return disk device in guest-get-fsinfo qga-win: handle multi-disk volumes qga-win: refactor disk info qga-win: report disk serial number qga-win: refactor disk properties (bus) qga-win: add debugging information build: rename CONFIG_QGA_NTDDDISK to CONFIG_QGA_NTDDSCSI qga-win: fsinfo: pci-info: allow partial info qga-win: prevent crash when executing fsinfo command qga: linux: return disk device in guest-get-fsinfo qga: linux: report disk serial number ... Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
commit
f96a3165ab
|
@ -474,6 +474,7 @@ libxml2=""
|
||||||
docker="no"
|
docker="no"
|
||||||
debug_mutex="no"
|
debug_mutex="no"
|
||||||
libpmem=""
|
libpmem=""
|
||||||
|
libudev="no"
|
||||||
|
|
||||||
# cross compilers defaults, can be overridden with --cross-cc-ARCH
|
# cross compilers defaults, can be overridden with --cross-cc-ARCH
|
||||||
cross_cc_aarch64="aarch64-linux-gnu-gcc"
|
cross_cc_aarch64="aarch64-linux-gnu-gcc"
|
||||||
|
@ -870,6 +871,7 @@ Linux)
|
||||||
vhost_vsock="yes"
|
vhost_vsock="yes"
|
||||||
QEMU_INCLUDES="-I\$(SRC_PATH)/linux-headers -I$(pwd)/linux-headers $QEMU_INCLUDES"
|
QEMU_INCLUDES="-I\$(SRC_PATH)/linux-headers -I$(pwd)/linux-headers $QEMU_INCLUDES"
|
||||||
supported_os="yes"
|
supported_os="yes"
|
||||||
|
libudev="yes"
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
|
@ -5609,6 +5611,17 @@ if test "$libnfs" != "no" ; then
|
||||||
fi
|
fi
|
||||||
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
|
# Now we've finished running tests it's OK to add -Werror to the compiler flags
|
||||||
if test "$werror" = "yes"; then
|
if test "$werror" = "yes"; then
|
||||||
QEMU_CFLAGS="-Werror $QEMU_CFLAGS"
|
QEMU_CFLAGS="-Werror $QEMU_CFLAGS"
|
||||||
|
@ -6033,6 +6046,7 @@ echo "VxHS block device $vxhs"
|
||||||
echo "capstone $capstone"
|
echo "capstone $capstone"
|
||||||
echo "docker $docker"
|
echo "docker $docker"
|
||||||
echo "libpmem support $libpmem"
|
echo "libpmem support $libpmem"
|
||||||
|
echo "libudev $libudev"
|
||||||
|
|
||||||
if test "$sdl_too_old" = "yes"; then
|
if test "$sdl_too_old" = "yes"; then
|
||||||
echo "-> Your SDL version is too old - please upgrade to have SDL support"
|
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
|
echo "WIN_SDK=\"$win_sdk\"" >> $config_host_mak
|
||||||
fi
|
fi
|
||||||
if test "$guest_agent_ntddscsi" = "yes" ; then
|
if test "$guest_agent_ntddscsi" = "yes" ; then
|
||||||
echo "CONFIG_QGA_NTDDDISK=y" >> $config_host_mak
|
echo "CONFIG_QGA_NTDDSCSI=y" >> $config_host_mak
|
||||||
fi
|
fi
|
||||||
if test "$guest_agent_msi" = "yes"; then
|
if test "$guest_agent_msi" = "yes"; then
|
||||||
echo "QEMU_GA_MSI_ENABLED=yes" >> $config_host_mak
|
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
|
echo "HAVE_USER_DOCKER=y" >> $config_host_mak
|
||||||
fi
|
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
|
# use included Linux headers
|
||||||
if test "$linux" = "yes" ; then
|
if test "$linux" = "yes" ; then
|
||||||
mkdir -p linux-headers
|
mkdir -p linux-headers
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
commands-posix.o-libs := $(LIBUDEV_LIBS)
|
||||||
qga-obj-y = commands.o guest-agent-command-state.o main.o
|
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_POSIX) += commands-posix.o channel-posix.o
|
||||||
qga-obj-$(CONFIG_WIN32) += commands-win32.o channel-win32.o service-win32.o
|
qga-obj-$(CONFIG_WIN32) += commands-win32.o channel-win32.o service-win32.o
|
||||||
|
|
|
@ -302,7 +302,8 @@ static gboolean ga_channel_open(GAChannel *c, GAChannelMethod method,
|
||||||
OPEN_EXISTING,
|
OPEN_EXISTING,
|
||||||
FILE_FLAG_NO_BUFFERING | FILE_FLAG_OVERLAPPED, NULL);
|
FILE_FLAG_NO_BUFFERING | FILE_FLAG_OVERLAPPED, NULL);
|
||||||
if (c->handle == INVALID_HANDLE_VALUE) {
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -48,6 +48,10 @@ extern char **environ;
|
||||||
#include <net/if.h>
|
#include <net/if.h>
|
||||||
#include <sys/statvfs.h>
|
#include <sys/statvfs.h>
|
||||||
|
|
||||||
|
#ifdef CONFIG_LIBUDEV
|
||||||
|
#include <libudev.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef FIFREEZE
|
#ifdef FIFREEZE
|
||||||
#define CONFIG_FSFREEZE
|
#define CONFIG_FSFREEZE
|
||||||
#endif
|
#endif
|
||||||
|
@ -872,6 +876,10 @@ static void build_guest_fsinfo_for_real_device(char const *syspath,
|
||||||
GuestDiskAddressList *list = NULL;
|
GuestDiskAddressList *list = NULL;
|
||||||
bool has_ata = false, has_host = false, has_tgt = false;
|
bool has_ata = false, has_host = false, has_tgt = false;
|
||||||
char *p, *q, *driver = NULL;
|
char *p, *q, *driver = NULL;
|
||||||
|
#ifdef CONFIG_LIBUDEV
|
||||||
|
struct udev *udev = NULL;
|
||||||
|
struct udev_device *udevice = NULL;
|
||||||
|
#endif
|
||||||
|
|
||||||
p = strstr(syspath, "/devices/pci");
|
p = strstr(syspath, "/devices/pci");
|
||||||
if (!p || sscanf(p + 12, "%*x:%*x/%x:%x:%x.%x%n",
|
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 = g_malloc0(sizeof(*list));
|
||||||
list->value = disk;
|
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) {
|
if (strcmp(driver, "ata_piix") == 0) {
|
||||||
/* a host per ide bus, target*:0:<unit>:0 */
|
/* a host per ide bus, target*:0:<unit>:0 */
|
||||||
if (!has_host || !has_tgt) {
|
if (!has_host || !has_tgt) {
|
||||||
|
@ -995,14 +1023,19 @@ static void build_guest_fsinfo_for_real_device(char const *syspath,
|
||||||
|
|
||||||
list->next = fs->disk;
|
list->next = fs->disk;
|
||||||
fs->disk = list;
|
fs->disk = list;
|
||||||
g_free(driver);
|
goto out;
|
||||||
return;
|
|
||||||
|
|
||||||
cleanup:
|
cleanup:
|
||||||
if (list) {
|
if (list) {
|
||||||
qapi_free_GuestDiskAddressList(list);
|
qapi_free_GuestDiskAddressList(list);
|
||||||
}
|
}
|
||||||
|
out:
|
||||||
g_free(driver);
|
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,
|
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.
|
* Written members remain unmodified on error.
|
||||||
*/
|
*/
|
||||||
static void transfer_vcpu(GuestLogicalProcessor *vcpu, bool sys2vcpu,
|
static void transfer_vcpu(GuestLogicalProcessor *vcpu, bool sys2vcpu,
|
||||||
Error **errp)
|
char *dirpath, Error **errp)
|
||||||
{
|
{
|
||||||
char *dirpath;
|
int fd;
|
||||||
|
int res;
|
||||||
int dirfd;
|
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);
|
dirfd = open(dirpath, O_RDONLY | O_DIRECTORY);
|
||||||
if (dirfd == -1) {
|
if (dirfd == -1) {
|
||||||
error_setg_errno(errp, errno, "open(\"%s\")", dirpath);
|
error_setg_errno(errp, errno, "open(\"%s\")", dirpath);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
} else {
|
||||||
static const char fn[] = "online";
|
unsigned char status;
|
||||||
int fd;
|
|
||||||
int res;
|
|
||||||
|
|
||||||
fd = openat(dirfd, fn, sys2vcpu ? O_RDONLY : O_RDWR);
|
res = pread(fd, &status, 1, 0);
|
||||||
if (fd == -1) {
|
if (res == -1) {
|
||||||
if (errno != ENOENT) {
|
error_setg_errno(errp, errno, "pread(\"%s/%s\")", dirpath, fn);
|
||||||
error_setg_errno(errp, errno, "open(\"%s/%s\")", dirpath, fn);
|
} else if (res == 0) {
|
||||||
} else if (sys2vcpu) {
|
error_setg(errp, "pread(\"%s/%s\"): unexpected EOF", dirpath,
|
||||||
vcpu->online = true;
|
fn);
|
||||||
vcpu->can_offline = false;
|
} else if (sys2vcpu) {
|
||||||
} else if (!vcpu->online) {
|
vcpu->online = (status != '0');
|
||||||
error_setg(errp, "logical processor #%" PRId64 " can't be "
|
vcpu->can_offline = true;
|
||||||
"offlined", vcpu->logical_id);
|
} else if (vcpu->online != (status != '0')) {
|
||||||
} /* otherwise pretend successful re-onlining */
|
status = '0' + vcpu->online;
|
||||||
} else {
|
if (pwrite(fd, &status, 1, 0) == -1) {
|
||||||
unsigned char status;
|
error_setg_errno(errp, errno, "pwrite(\"%s/%s\")", dirpath,
|
||||||
|
fn);
|
||||||
|
}
|
||||||
|
} /* otherwise pretend successful re-(on|off)-lining */
|
||||||
|
|
||||||
res = pread(fd, &status, 1, 0);
|
res = close(fd);
|
||||||
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(fd);
|
|
||||||
g_assert(res == 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
res = close(dirfd);
|
|
||||||
g_assert(res == 0);
|
g_assert(res == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
g_free(dirpath);
|
res = close(dirfd);
|
||||||
|
g_assert(res == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
GuestLogicalProcessorList *qmp_guest_get_vcpus(Error **errp)
|
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) {
|
while (local_err == NULL && current < sc_max) {
|
||||||
GuestLogicalProcessor *vcpu;
|
GuestLogicalProcessor *vcpu;
|
||||||
GuestLogicalProcessorList *entry;
|
GuestLogicalProcessorList *entry;
|
||||||
|
int64_t id = current++;
|
||||||
|
char *path = g_strdup_printf("/sys/devices/system/cpu/cpu%" PRId64 "/",
|
||||||
|
id);
|
||||||
|
|
||||||
vcpu = g_malloc0(sizeof *vcpu);
|
if (g_file_test(path, G_FILE_TEST_EXISTS)) {
|
||||||
vcpu->logical_id = current++;
|
vcpu = g_malloc0(sizeof *vcpu);
|
||||||
vcpu->has_can_offline = true; /* lolspeak ftw */
|
vcpu->logical_id = id;
|
||||||
transfer_vcpu(vcpu, true, &local_err);
|
vcpu->has_can_offline = true; /* lolspeak ftw */
|
||||||
|
transfer_vcpu(vcpu, true, path, &local_err);
|
||||||
entry = g_malloc0(sizeof *entry);
|
entry = g_malloc0(sizeof *entry);
|
||||||
entry->value = vcpu;
|
entry->value = vcpu;
|
||||||
|
*link = entry;
|
||||||
*link = entry;
|
link = &entry->next;
|
||||||
link = &entry->next;
|
}
|
||||||
|
g_free(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (local_err == NULL) {
|
if (local_err == NULL) {
|
||||||
|
@ -2138,7 +2170,11 @@ int64_t qmp_guest_set_vcpus(GuestLogicalProcessorList *vcpus, Error **errp)
|
||||||
|
|
||||||
processed = 0;
|
processed = 0;
|
||||||
while (vcpus != NULL) {
|
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) {
|
if (local_err != NULL) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -89,6 +89,12 @@ static OpenFlags guest_file_open_modes[] = {
|
||||||
{"a+b", FILE_GENERIC_APPEND|GENERIC_READ, OPEN_ALWAYS }
|
{"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)
|
static OpenFlags *find_open_flag(const char *mode_str)
|
||||||
{
|
{
|
||||||
int mode;
|
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,
|
int64_t qmp_guest_file_open(const char *path, bool has_mode,
|
||||||
const char *mode, Error **errp)
|
const char *mode, Error **errp)
|
||||||
{
|
{
|
||||||
int64_t fd;
|
int64_t fd = -1;
|
||||||
HANDLE fh;
|
HANDLE fh;
|
||||||
HANDLE templ_file = NULL;
|
HANDLE templ_file = NULL;
|
||||||
DWORD share_mode = FILE_SHARE_READ;
|
DWORD share_mode = FILE_SHARE_READ;
|
||||||
DWORD flags_and_attr = FILE_ATTRIBUTE_NORMAL;
|
DWORD flags_and_attr = FILE_ATTRIBUTE_NORMAL;
|
||||||
LPSECURITY_ATTRIBUTES sa_attr = NULL;
|
LPSECURITY_ATTRIBUTES sa_attr = NULL;
|
||||||
OpenFlags *guest_flags;
|
OpenFlags *guest_flags;
|
||||||
|
GError *gerr = NULL;
|
||||||
|
wchar_t *w_path = NULL;
|
||||||
|
|
||||||
if (!has_mode) {
|
if (!has_mode) {
|
||||||
mode = "r";
|
mode = "r";
|
||||||
|
@ -175,16 +183,21 @@ int64_t qmp_guest_file_open(const char *path, bool has_mode,
|
||||||
guest_flags = find_open_flag(mode);
|
guest_flags = find_open_flag(mode);
|
||||||
if (guest_flags == NULL) {
|
if (guest_flags == NULL) {
|
||||||
error_setg(errp, "invalid file open mode");
|
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,
|
guest_flags->creation_disposition, flags_and_attr,
|
||||||
templ_file);
|
templ_file);
|
||||||
if (fh == INVALID_HANDLE_VALUE) {
|
if (fh == INVALID_HANDLE_VALUE) {
|
||||||
error_setg_win32(errp, GetLastError(), "failed to open file '%s'",
|
error_setg_win32(errp, GetLastError(), "failed to open file '%s'",
|
||||||
path);
|
path);
|
||||||
return -1;
|
goto done;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* set fd non-blocking to avoid common use cases (like reading from a
|
/* 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) {
|
if (fd < 0) {
|
||||||
CloseHandle(fh);
|
CloseHandle(fh);
|
||||||
error_setg(errp, "failed to add handle to qmp handle table");
|
error_setg(errp, "failed to add handle to qmp handle table");
|
||||||
return -1;
|
goto done;
|
||||||
}
|
}
|
||||||
|
|
||||||
slog("guest-file-open, handle: % " PRId64, fd);
|
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;
|
return fd;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -465,15 +485,32 @@ static STORAGE_BUS_TYPE win2qemu[] = {
|
||||||
|
|
||||||
static GuestDiskBusType find_bus_type(STORAGE_BUS_TYPE bus)
|
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 GUEST_DISK_BUS_TYPE_UNKNOWN;
|
||||||
}
|
}
|
||||||
return win2qemu[(int)bus];
|
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,
|
DEFINE_GUID(GUID_DEVINTERFACE_VOLUME,
|
||||||
0x53f5630dL, 0xb6bf, 0x11d0, 0x94, 0xf2,
|
0x53f5630dL, 0xb6bf, 0x11d0, 0x94, 0xf2,
|
||||||
0x00, 0xa0, 0xc9, 0x1e, 0xfb, 0x8b);
|
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)
|
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 dev_name[MAX_PATH];
|
||||||
char *buffer = NULL;
|
char *buffer = NULL;
|
||||||
GuestPCIAddress *pci = 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))) {
|
if (!QueryDosDevice(name, dev_name, ARRAY_SIZE(dev_name))) {
|
||||||
error_setg_win32(errp, GetLastError(), "failed to get dos device name");
|
error_setg_win32(errp, GetLastError(), "failed to get dos device name");
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
dev_info = SetupDiGetClassDevs(&GUID_DEVINTERFACE_VOLUME, 0, 0,
|
dev_info = SetupDiGetClassDevs(&GUID_DEVINTERFACE_DISK, 0, 0,
|
||||||
DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
|
DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
|
||||||
if (dev_info == INVALID_HANDLE_VALUE) {
|
if (dev_info == INVALID_HANDLE_VALUE) {
|
||||||
error_setg_win32(errp, GetLastError(), "failed to get devices tree");
|
error_setg_win32(errp, GetLastError(), "failed to get devices tree");
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
g_debug("enumerating devices");
|
||||||
dev_info_data.cbSize = sizeof(SP_DEVINFO_DATA);
|
dev_info_data.cbSize = sizeof(SP_DEVINFO_DATA);
|
||||||
for (i = 0; SetupDiEnumDeviceInfo(dev_info, i, &dev_info_data); i++) {
|
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,
|
while (!SetupDiGetDeviceRegistryProperty(dev_info, &dev_info_data,
|
||||||
SPDRP_PHYSICAL_DEVICE_OBJECT_NAME,
|
SPDRP_PHYSICAL_DEVICE_OBJECT_NAME,
|
||||||
&data, (PBYTE)buffer, size,
|
&data, (PBYTE)buffer, size,
|
||||||
|
@ -522,6 +574,7 @@ static GuestPCIAddress *get_pci_info(char *guid, Error **errp)
|
||||||
if (g_strcmp0(buffer, dev_name)) {
|
if (g_strcmp0(buffer, dev_name)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
g_debug("found device %s", dev_name);
|
||||||
|
|
||||||
/* There is no need to allocate buffer in the next functions. The size
|
/* There is no need to allocate buffer in the next functions. The size
|
||||||
* is known and ULONG according to
|
* 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,
|
if (!SetupDiGetDeviceRegistryProperty(dev_info, &dev_info_data,
|
||||||
SPDRP_BUSNUMBER, &data, (PBYTE)&bus, size, NULL)) {
|
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
|
/* The function retrieves the device's address. This value will be
|
||||||
* transformed into device function and number */
|
* transformed into device function and number */
|
||||||
if (!SetupDiGetDeviceRegistryProperty(dev_info, &dev_info_data,
|
if (!SetupDiGetDeviceRegistryProperty(dev_info, &dev_info_data,
|
||||||
SPDRP_ADDRESS, &data, (PBYTE)&addr, size, NULL)) {
|
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 call returns UINumber of DEVICE_CAPABILITIES structure.
|
||||||
* This number is typically a user-perceived slot number. */
|
* This number is typically a user-perceived slot number. */
|
||||||
if (!SetupDiGetDeviceRegistryProperty(dev_info, &dev_info_data,
|
if (!SetupDiGetDeviceRegistryProperty(dev_info, &dev_info_data,
|
||||||
SPDRP_UI_NUMBER, &data, (PBYTE)&slot, size, NULL)) {
|
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
|
/* 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);
|
* DeviceNumber = (USHORT)(((propertyAddress) >> 16) & 0x0000FFFF);
|
||||||
* SPDRP_ADDRESS is propertyAddress, so we do the same.*/
|
* SPDRP_ADDRESS is propertyAddress, so we do the same.*/
|
||||||
|
|
||||||
func = addr & 0x0000FFFF;
|
if (partial_pci) {
|
||||||
dev = (addr >> 16) & 0x0000FFFF;
|
pci->domain = -1;
|
||||||
pci = g_malloc0(sizeof(*pci));
|
pci->slot = -1;
|
||||||
pci->domain = dev;
|
pci->function = -1;
|
||||||
pci->slot = slot;
|
pci->bus = -1;
|
||||||
pci->function = func;
|
} else {
|
||||||
pci->bus = bus;
|
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;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -572,25 +637,119 @@ out:
|
||||||
return pci;
|
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_PROPERTY_QUERY query;
|
||||||
STORAGE_DEVICE_DESCRIPTOR *dev_desc, buf;
|
STORAGE_DEVICE_DESCRIPTOR *dev_desc, buf;
|
||||||
DWORD received;
|
DWORD received;
|
||||||
|
ULONG size = sizeof(buf);
|
||||||
|
|
||||||
dev_desc = &buf;
|
dev_desc = &buf;
|
||||||
dev_desc->Size = sizeof(buf);
|
|
||||||
query.PropertyId = StorageDeviceProperty;
|
query.PropertyId = StorageDeviceProperty;
|
||||||
query.QueryType = PropertyStandardQuery;
|
query.QueryType = PropertyStandardQuery;
|
||||||
|
|
||||||
if (!DeviceIoControl(vol_h, IOCTL_STORAGE_QUERY_PROPERTY, &query,
|
if (!DeviceIoControl(vol_h, IOCTL_STORAGE_QUERY_PROPERTY, &query,
|
||||||
sizeof(STORAGE_PROPERTY_QUERY), dev_desc,
|
sizeof(STORAGE_PROPERTY_QUERY), dev_desc,
|
||||||
dev_desc->Size, &received, NULL)) {
|
size, &received, NULL)) {
|
||||||
error_setg_win32(errp, GetLastError(), "failed to get bus type");
|
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;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void get_single_disk_info(GuestDiskAddress *disk, Error **errp)
|
||||||
|
{
|
||||||
|
SCSI_ADDRESS addr, *scsi_ad;
|
||||||
|
DWORD len;
|
||||||
|
HANDLE disk_h;
|
||||||
|
Error *local_err = NULL;
|
||||||
|
|
||||||
|
scsi_ad = &addr;
|
||||||
|
|
||||||
|
g_debug("getting disk info for: %s", disk->dev);
|
||||||
|
disk_h = CreateFile(disk->dev, 0, FILE_SHARE_READ, NULL, OPEN_EXISTING,
|
||||||
|
0, NULL);
|
||||||
|
if (disk_h == INVALID_HANDLE_VALUE) {
|
||||||
|
error_setg_win32(errp, GetLastError(), "failed to open disk");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return dev_desc->BusType;
|
get_disk_properties(disk_h, disk, &local_err);
|
||||||
|
if (local_err) {
|
||||||
|
error_propagate(errp, local_err);
|
||||||
|
goto err_close;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 */
|
||||||
|
|| 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 */
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
/* We do not set error in this case, because we still have enough
|
||||||
|
* information about volume. */
|
||||||
|
}
|
||||||
|
|
||||||
|
err_close:
|
||||||
|
CloseHandle(disk_h);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* VSS provider works with volumes, thus there is no difference if
|
/* VSS provider works with volumes, thus there is no difference if
|
||||||
|
@ -598,59 +757,108 @@ static int get_disk_bus_type(HANDLE vol_h, Error **errp)
|
||||||
* volume is returned for the spanned disk group (LVM) */
|
* volume is returned for the spanned disk group (LVM) */
|
||||||
static GuestDiskAddressList *build_guest_disk_info(char *guid, Error **errp)
|
static GuestDiskAddressList *build_guest_disk_info(char *guid, Error **errp)
|
||||||
{
|
{
|
||||||
GuestDiskAddressList *list = NULL;
|
Error *local_err = NULL;
|
||||||
GuestDiskAddress *disk;
|
GuestDiskAddressList *list = NULL, *cur_item = NULL;
|
||||||
SCSI_ADDRESS addr, *scsi_ad;
|
GuestDiskAddress *disk = NULL;
|
||||||
DWORD len;
|
int i;
|
||||||
int bus;
|
|
||||||
HANDLE vol_h;
|
HANDLE vol_h;
|
||||||
|
DWORD size;
|
||||||
|
PVOLUME_DISK_EXTENTS extents = NULL;
|
||||||
|
|
||||||
scsi_ad = &addr;
|
/* strip final backslash */
|
||||||
char *name = g_strndup(guid, strlen(guid)-1);
|
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,
|
vol_h = CreateFile(name, 0, FILE_SHARE_READ, NULL, OPEN_EXISTING,
|
||||||
0, NULL);
|
0, NULL);
|
||||||
if (vol_h == INVALID_HANDLE_VALUE) {
|
if (vol_h == INVALID_HANDLE_VALUE) {
|
||||||
error_setg_win32(errp, GetLastError(), "failed to open volume");
|
error_setg_win32(errp, GetLastError(), "failed to open volume");
|
||||||
goto out_free;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
bus = get_disk_bus_type(vol_h, errp);
|
/* Get list of extents */
|
||||||
if (bus < 0) {
|
g_debug("getting disk extents");
|
||||||
goto out_close;
|
size = sizeof(VOLUME_DISK_EXTENTS);
|
||||||
}
|
extents = g_malloc0(size);
|
||||||
|
if (!DeviceIoControl(vol_h, IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS, NULL,
|
||||||
disk = g_malloc0(sizeof(*disk));
|
0, extents, size, NULL, NULL)) {
|
||||||
disk->bus_type = find_bus_type(bus);
|
DWORD last_err = GetLastError();
|
||||||
if (bus == BusTypeScsi || bus == BusTypeAta || bus == BusTypeRAID
|
if (last_err == ERROR_MORE_DATA) {
|
||||||
#if (_WIN32_WINNT >= 0x0600)
|
/* Try once more with big enough buffer */
|
||||||
/* This bus type is not supported before Windows Server 2003 SP1 */
|
size = sizeof(VOLUME_DISK_EXTENTS)
|
||||||
|| bus == BusTypeSas
|
+ extents->NumberOfDiskExtents*sizeof(DISK_EXTENT);
|
||||||
#endif
|
g_free(extents);
|
||||||
) {
|
extents = g_malloc0(size);
|
||||||
/* We are able to use the same ioctls for different bus types
|
if (!DeviceIoControl(
|
||||||
* according to Microsoft docs
|
vol_h, IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS, NULL,
|
||||||
* https://technet.microsoft.com/en-us/library/ee851589(v=ws.10).aspx */
|
0, extents, size, NULL, NULL)) {
|
||||||
if (DeviceIoControl(vol_h, IOCTL_SCSI_GET_ADDRESS, NULL, 0, scsi_ad,
|
error_setg_win32(errp, GetLastError(),
|
||||||
sizeof(SCSI_ADDRESS), &len, NULL)) {
|
"failed to get disk extents");
|
||||||
disk->unit = addr.Lun;
|
return NULL;
|
||||||
disk->target = addr.TargetId;
|
}
|
||||||
disk->bus = addr.PathId;
|
} else if (last_err == ERROR_INVALID_FUNCTION) {
|
||||||
disk->pci_controller = get_pci_info(name, errp);
|
/* 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;
|
||||||
}
|
}
|
||||||
/* We do not set error in this case, because we still have enough
|
}
|
||||||
* information about volume. */
|
g_debug("Number of extents: %lu", extents->NumberOfDiskExtents);
|
||||||
} else {
|
|
||||||
disk->pci_controller = NULL;
|
/* 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
list = g_malloc0(sizeof(*list));
|
|
||||||
list->value = disk;
|
out:
|
||||||
list->next = NULL;
|
qapi_free_GuestDiskAddress(disk);
|
||||||
out_close:
|
g_free(extents);
|
||||||
CloseHandle(vol_h);
|
|
||||||
out_free:
|
|
||||||
g_free(name);
|
g_free(name);
|
||||||
|
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -776,6 +984,13 @@ GuestFsfreezeStatus qmp_guest_fsfreeze_status(Error **errp)
|
||||||
* The frozen state is limited for up to 10 seconds by VSS.
|
* The frozen state is limited for up to 10 seconds by VSS.
|
||||||
*/
|
*/
|
||||||
int64_t qmp_guest_fsfreeze_freeze(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;
|
int i;
|
||||||
Error *local_err = NULL;
|
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 */
|
/* cannot risk guest agent blocking itself on a write in this state */
|
||||||
ga_set_frozen(ga_state);
|
ga_set_frozen(ga_state);
|
||||||
|
|
||||||
qga_vss_fsfreeze(&i, true, &local_err);
|
qga_vss_fsfreeze(&i, true, mountpoints, &local_err);
|
||||||
if (local_err) {
|
if (local_err) {
|
||||||
error_propagate(errp, local_err);
|
error_propagate(errp, local_err);
|
||||||
goto error;
|
goto error;
|
||||||
|
@ -808,15 +1023,6 @@ error:
|
||||||
return 0;
|
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.
|
* Thaw local file systems using Volume Shadow-copy Service.
|
||||||
*/
|
*/
|
||||||
|
@ -829,7 +1035,7 @@ int64_t qmp_guest_fsfreeze_thaw(Error **errp)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
qga_vss_fsfreeze(&i, false, errp);
|
qga_vss_fsfreeze(&i, false, NULL, errp);
|
||||||
|
|
||||||
ga_unset_frozen(ga_state);
|
ga_unset_frozen(ga_state);
|
||||||
return i;
|
return i;
|
||||||
|
@ -1646,7 +1852,6 @@ GList *ga_command_blacklist_init(GList *blacklist)
|
||||||
"guest-set-vcpus",
|
"guest-set-vcpus",
|
||||||
"guest-get-memory-blocks", "guest-set-memory-blocks",
|
"guest-get-memory-blocks", "guest-set-memory-blocks",
|
||||||
"guest-get-memory-block-size",
|
"guest-get-memory-block-size",
|
||||||
"guest-fsfreeze-freeze-list",
|
|
||||||
NULL};
|
NULL};
|
||||||
char **p = (char **)list_unsupported;
|
char **p = (char **)list_unsupported;
|
||||||
|
|
||||||
|
|
|
@ -78,7 +78,7 @@
|
||||||
Account="LocalSystem"
|
Account="LocalSystem"
|
||||||
ErrorControl="ignore"
|
ErrorControl="ignore"
|
||||||
Interactive="no"
|
Interactive="no"
|
||||||
Arguments="-d"
|
Arguments="-d --retry-path"
|
||||||
>
|
>
|
||||||
</ServiceInstall>
|
</ServiceInstall>
|
||||||
<ServiceControl Id="StartService" Start="install" Stop="both" Remove="uninstall" Name="QEMU-GA" Wait="no" />
|
<ServiceControl Id="StartService" Start="install" Stop="both" Remove="uninstall" Name="QEMU-GA" Wait="no" />
|
||||||
|
|
268
qga/main.c
268
qga/main.c
|
@ -34,6 +34,7 @@
|
||||||
#include "qemu/systemd.h"
|
#include "qemu/systemd.h"
|
||||||
#include "qemu-version.h"
|
#include "qemu-version.h"
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
|
#include <dbt.h>
|
||||||
#include "qga/service-win32.h"
|
#include "qga/service-win32.h"
|
||||||
#include "qga/vss-win32.h"
|
#include "qga/vss-win32.h"
|
||||||
#endif
|
#endif
|
||||||
|
@ -58,6 +59,7 @@
|
||||||
#endif
|
#endif
|
||||||
#define QGA_SENTINEL_BYTE 0xFF
|
#define QGA_SENTINEL_BYTE 0xFF
|
||||||
#define QGA_CONF_DEFAULT CONFIG_QEMU_CONFDIR G_DIR_SEPARATOR_S "qemu-ga.conf"
|
#define QGA_CONF_DEFAULT CONFIG_QEMU_CONFDIR G_DIR_SEPARATOR_S "qemu-ga.conf"
|
||||||
|
#define QGA_RETRY_INTERVAL 5
|
||||||
|
|
||||||
static struct {
|
static struct {
|
||||||
const char *state_dir;
|
const char *state_dir;
|
||||||
|
@ -69,6 +71,8 @@ typedef struct GAPersistentState {
|
||||||
int64_t fd_counter;
|
int64_t fd_counter;
|
||||||
} GAPersistentState;
|
} GAPersistentState;
|
||||||
|
|
||||||
|
typedef struct GAConfig GAConfig;
|
||||||
|
|
||||||
struct GAState {
|
struct GAState {
|
||||||
JSONMessageParser parser;
|
JSONMessageParser parser;
|
||||||
GMainLoop *main_loop;
|
GMainLoop *main_loop;
|
||||||
|
@ -80,6 +84,7 @@ struct GAState {
|
||||||
bool logging_enabled;
|
bool logging_enabled;
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
GAService service;
|
GAService service;
|
||||||
|
HANDLE wakeup_event;
|
||||||
#endif
|
#endif
|
||||||
bool delimit_response;
|
bool delimit_response;
|
||||||
bool frozen;
|
bool frozen;
|
||||||
|
@ -94,6 +99,9 @@ struct GAState {
|
||||||
#endif
|
#endif
|
||||||
gchar *pstate_filepath;
|
gchar *pstate_filepath;
|
||||||
GAPersistentState pstate;
|
GAPersistentState pstate;
|
||||||
|
GAConfig *config;
|
||||||
|
int socket_activation;
|
||||||
|
bool force_exit;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct GAState *ga_state;
|
struct GAState *ga_state;
|
||||||
|
@ -113,8 +121,11 @@ static const char *ga_freeze_whitelist[] = {
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
DWORD WINAPI service_ctrl_handler(DWORD ctrl, DWORD type, LPVOID data,
|
DWORD WINAPI service_ctrl_handler(DWORD ctrl, DWORD type, LPVOID data,
|
||||||
LPVOID ctx);
|
LPVOID ctx);
|
||||||
|
DWORD WINAPI handle_serial_device_events(DWORD type, LPVOID data);
|
||||||
VOID WINAPI service_main(DWORD argc, TCHAR *argv[]);
|
VOID WINAPI service_main(DWORD argc, TCHAR *argv[]);
|
||||||
#endif
|
#endif
|
||||||
|
static int run_agent(GAState *s);
|
||||||
|
static void stop_agent(GAState *s, bool requested);
|
||||||
|
|
||||||
static void
|
static void
|
||||||
init_dfl_pathnames(void)
|
init_dfl_pathnames(void)
|
||||||
|
@ -151,7 +162,7 @@ static void quit_handler(int sig)
|
||||||
WaitForSingleObject(hEventTimeout, 0);
|
WaitForSingleObject(hEventTimeout, 0);
|
||||||
CloseHandle(hEventTimeout);
|
CloseHandle(hEventTimeout);
|
||||||
}
|
}
|
||||||
qga_vss_fsfreeze(&i, false, &err);
|
qga_vss_fsfreeze(&i, false, NULL, &err);
|
||||||
if (err) {
|
if (err) {
|
||||||
g_debug("Error unfreezing filesystems prior to exiting: %s",
|
g_debug("Error unfreezing filesystems prior to exiting: %s",
|
||||||
error_get_pretty(err));
|
error_get_pretty(err));
|
||||||
|
@ -163,9 +174,7 @@ static void quit_handler(int sig)
|
||||||
}
|
}
|
||||||
g_debug("received signal num %d, quitting", sig);
|
g_debug("received signal num %d, quitting", sig);
|
||||||
|
|
||||||
if (g_main_loop_is_running(ga_state->main_loop)) {
|
stop_agent(ga_state, true);
|
||||||
g_main_loop_quit(ga_state->main_loop);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifndef _WIN32
|
#ifndef _WIN32
|
||||||
|
@ -250,6 +259,10 @@ QEMU_COPYRIGHT "\n"
|
||||||
" to list available RPCs)\n"
|
" to list available RPCs)\n"
|
||||||
" -D, --dump-conf dump a qemu-ga config file based on current config\n"
|
" -D, --dump-conf dump a qemu-ga config file based on current config\n"
|
||||||
" options / command-line parameters to stdout\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"
|
" -h, --help display this help and exit\n"
|
||||||
"\n"
|
"\n"
|
||||||
QEMU_HELP_BOTTOM "\n"
|
QEMU_HELP_BOTTOM "\n"
|
||||||
|
@ -609,6 +622,7 @@ static gboolean channel_event_cb(GIOCondition condition, gpointer data)
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case G_IO_STATUS_ERROR:
|
case G_IO_STATUS_ERROR:
|
||||||
g_warning("error reading channel");
|
g_warning("error reading channel");
|
||||||
|
stop_agent(s, false);
|
||||||
return false;
|
return false;
|
||||||
case G_IO_STATUS_NORMAL:
|
case G_IO_STATUS_NORMAL:
|
||||||
buf[count] = 0;
|
buf[count] = 0;
|
||||||
|
@ -666,6 +680,36 @@ static gboolean channel_init(GAState *s, const gchar *method, const gchar *path,
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef _WIN32
|
#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,
|
DWORD WINAPI service_ctrl_handler(DWORD ctrl, DWORD type, LPVOID data,
|
||||||
LPVOID ctx)
|
LPVOID ctx)
|
||||||
{
|
{
|
||||||
|
@ -677,9 +721,13 @@ DWORD WINAPI service_ctrl_handler(DWORD ctrl, DWORD type, LPVOID data,
|
||||||
case SERVICE_CONTROL_STOP:
|
case SERVICE_CONTROL_STOP:
|
||||||
case SERVICE_CONTROL_SHUTDOWN:
|
case SERVICE_CONTROL_SHUTDOWN:
|
||||||
quit_handler(SIGTERM);
|
quit_handler(SIGTERM);
|
||||||
|
SetEvent(ga_state->wakeup_event);
|
||||||
service->status.dwCurrentState = SERVICE_STOP_PENDING;
|
service->status.dwCurrentState = SERVICE_STOP_PENDING;
|
||||||
SetServiceStatus(service->status_handle, &service->status);
|
SetServiceStatus(service->status_handle, &service->status);
|
||||||
break;
|
break;
|
||||||
|
case SERVICE_CONTROL_DEVICEEVENT:
|
||||||
|
handle_serial_device_events(type, data);
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
ret = ERROR_CALL_NOT_IMPLEMENTED;
|
ret = ERROR_CALL_NOT_IMPLEMENTED;
|
||||||
|
@ -706,10 +754,24 @@ VOID WINAPI service_main(DWORD argc, TCHAR *argv[])
|
||||||
service->status.dwServiceSpecificExitCode = NO_ERROR;
|
service->status.dwServiceSpecificExitCode = NO_ERROR;
|
||||||
service->status.dwCheckPoint = 0;
|
service->status.dwCheckPoint = 0;
|
||||||
service->status.dwWaitHint = 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);
|
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;
|
service->status.dwCurrentState = SERVICE_STOPPED;
|
||||||
SetServiceStatus(service->status_handle, &service->status);
|
SetServiceStatus(service->status_handle, &service->status);
|
||||||
}
|
}
|
||||||
|
@ -905,7 +967,7 @@ static GList *split_list(const gchar *str, const gchar *delim)
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef struct GAConfig {
|
struct GAConfig {
|
||||||
char *channel_path;
|
char *channel_path;
|
||||||
char *method;
|
char *method;
|
||||||
char *log_filepath;
|
char *log_filepath;
|
||||||
|
@ -922,7 +984,8 @@ typedef struct GAConfig {
|
||||||
int daemonize;
|
int daemonize;
|
||||||
GLogLevelFlags log_level;
|
GLogLevelFlags log_level;
|
||||||
int dumpconf;
|
int dumpconf;
|
||||||
} GAConfig;
|
bool retry_path;
|
||||||
|
};
|
||||||
|
|
||||||
static void config_load(GAConfig *config)
|
static void config_load(GAConfig *config)
|
||||||
{
|
{
|
||||||
|
@ -971,6 +1034,10 @@ static void config_load(GAConfig *config)
|
||||||
/* enable all log levels */
|
/* enable all log levels */
|
||||||
config->log_level = G_LOG_LEVEL_MASK;
|
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)) {
|
if (g_key_file_has_key(keyfile, "general", "blacklist", NULL)) {
|
||||||
config->bliststr =
|
config->bliststr =
|
||||||
g_key_file_get_string(keyfile, "general", "blacklist", &gerr);
|
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_string(keyfile, "general", "statedir", config->state_dir);
|
||||||
g_key_file_set_boolean(keyfile, "general", "verbose",
|
g_key_file_set_boolean(keyfile, "general", "verbose",
|
||||||
config->log_level == G_LOG_LEVEL_MASK);
|
config->log_level == G_LOG_LEVEL_MASK);
|
||||||
|
g_key_file_set_boolean(keyfile, "general", "retry-path",
|
||||||
|
config->retry_path);
|
||||||
tmp = list_join(config->blacklist, ',');
|
tmp = list_join(config->blacklist, ',');
|
||||||
g_key_file_set_string(keyfile, "general", "blacklist", tmp);
|
g_key_file_set_string(keyfile, "general", "blacklist", tmp);
|
||||||
g_free(tmp);
|
g_free(tmp);
|
||||||
|
@ -1050,7 +1119,7 @@ static void config_dump(GAConfig *config)
|
||||||
|
|
||||||
static void config_parse(GAConfig *config, int argc, char **argv)
|
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;
|
int opt_ind = 0, ch;
|
||||||
const struct option lopt[] = {
|
const struct option lopt[] = {
|
||||||
{ "help", 0, NULL, 'h' },
|
{ "help", 0, NULL, 'h' },
|
||||||
|
@ -1070,6 +1139,7 @@ static void config_parse(GAConfig *config, int argc, char **argv)
|
||||||
{ "service", 1, NULL, 's' },
|
{ "service", 1, NULL, 's' },
|
||||||
#endif
|
#endif
|
||||||
{ "statedir", 1, NULL, 't' },
|
{ "statedir", 1, NULL, 't' },
|
||||||
|
{ "retry-path", 0, NULL, 'r' },
|
||||||
{ NULL, 0, NULL, 0 }
|
{ NULL, 0, NULL, 0 }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1114,6 +1184,9 @@ static void config_parse(GAConfig *config, int argc, char **argv)
|
||||||
case 'D':
|
case 'D':
|
||||||
config->dumpconf = 1;
|
config->dumpconf = 1;
|
||||||
break;
|
break;
|
||||||
|
case 'r':
|
||||||
|
config->retry_path = true;
|
||||||
|
break;
|
||||||
case 'b': {
|
case 'b': {
|
||||||
if (is_help_option(optarg)) {
|
if (is_help_option(optarg)) {
|
||||||
qmp_for_each_command(&ga_commands, ga_print_cmd, NULL);
|
qmp_for_each_command(&ga_commands, ga_print_cmd, NULL);
|
||||||
|
@ -1211,9 +1284,21 @@ static bool check_is_frozen(GAState *s)
|
||||||
return false;
|
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_default_handler(ga_log, s);
|
||||||
g_log_set_fatal_mask(NULL, G_LOG_LEVEL_ERROR);
|
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) {
|
if (g_mkdir_with_parents(config->state_dir, S_IRWXU) == -1) {
|
||||||
g_critical("unable to create (an ancestor of) the state directory"
|
g_critical("unable to create (an ancestor of) the state directory"
|
||||||
" '%s': %s", config->state_dir, strerror(errno));
|
" '%s': %s", config->state_dir, strerror(errno));
|
||||||
return EXIT_FAILURE;
|
return NULL;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -1254,7 +1339,7 @@ static int run_agent(GAState *s, GAConfig *config, int socket_activation)
|
||||||
if (!log_file) {
|
if (!log_file) {
|
||||||
g_critical("unable to open specified log file: %s",
|
g_critical("unable to open specified log file: %s",
|
||||||
strerror(errno));
|
strerror(errno));
|
||||||
return EXIT_FAILURE;
|
return NULL;
|
||||||
}
|
}
|
||||||
s->log_file = log_file;
|
s->log_file = log_file;
|
||||||
}
|
}
|
||||||
|
@ -1265,7 +1350,7 @@ static int run_agent(GAState *s, GAConfig *config, int socket_activation)
|
||||||
s->pstate_filepath,
|
s->pstate_filepath,
|
||||||
ga_is_frozen(s))) {
|
ga_is_frozen(s))) {
|
||||||
g_critical("failed to load persistent state");
|
g_critical("failed to load persistent state");
|
||||||
return EXIT_FAILURE;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
config->blacklist = ga_command_blacklist_init(config->blacklist);
|
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
|
#ifndef _WIN32
|
||||||
if (!register_signal_handlers()) {
|
if (!register_signal_handlers()) {
|
||||||
g_critical("failed to register signal handlers");
|
g_critical("failed to register signal handlers");
|
||||||
return EXIT_FAILURE;
|
return NULL;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
s->main_loop = g_main_loop_new(NULL, false);
|
s->main_loop = g_main_loop_new(NULL, false);
|
||||||
|
|
||||||
if (!channel_init(ga_state, config->method, config->channel_path,
|
s->config = config;
|
||||||
socket_activation ? FIRST_SOCKET_ACTIVATION_FD : -1)) {
|
s->socket_activation = socket_activation;
|
||||||
g_critical("failed to initialize guest agent channel");
|
|
||||||
return EXIT_FAILURE;
|
#ifdef _WIN32
|
||||||
}
|
s->wakeup_event = CreateEvent(NULL, TRUE, FALSE, TEXT("WakeUp"));
|
||||||
#ifndef _WIN32
|
if (s->wakeup_event == NULL) {
|
||||||
g_main_loop_run(ga_state->main_loop);
|
g_critical("CreateEvent failed");
|
||||||
#else
|
return NULL;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
#endif
|
#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;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_main_loop_run(ga_state->main_loop);
|
||||||
|
|
||||||
|
if (s->channel) {
|
||||||
|
ga_channel_free(s->channel);
|
||||||
|
}
|
||||||
|
|
||||||
return EXIT_SUCCESS;
|
return EXIT_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void wait_for_channel_availability(GAState *s)
|
||||||
|
{
|
||||||
|
g_warning("waiting for channel path...");
|
||||||
|
#ifndef _WIN32
|
||||||
|
sleep(QGA_RETRY_INTERVAL);
|
||||||
|
#else
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
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 main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
int ret = EXIT_SUCCESS;
|
int ret = EXIT_SUCCESS;
|
||||||
GAState *s = g_new0(GAState, 1);
|
GAState *s;
|
||||||
GAConfig *config = g_new0(GAConfig, 1);
|
GAConfig *config = g_new0(GAConfig, 1);
|
||||||
int socket_activation;
|
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) {
|
if (config->dumpconf) {
|
||||||
config_dump(config);
|
config_dump(config);
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = run_agent(s, config, socket_activation);
|
s = initialize_agent(config, socket_activation);
|
||||||
|
if (!s) {
|
||||||
|
g_critical("error initializing guest agent");
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
#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);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
ret = run_agent(s);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
cleanup_agent(s);
|
||||||
|
|
||||||
end:
|
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);
|
|
||||||
}
|
|
||||||
if (s->channel) {
|
|
||||||
ga_channel_free(s->channel);
|
|
||||||
}
|
|
||||||
g_free(s->pstate_filepath);
|
|
||||||
g_free(s->state_filepath_isfrozen);
|
|
||||||
|
|
||||||
if (config->daemonize) {
|
if (config->daemonize) {
|
||||||
unlink(config->pid_filepath);
|
unlink(config->pid_filepath);
|
||||||
}
|
}
|
||||||
|
|
||||||
config_free(config);
|
config_free(config);
|
||||||
if (s->main_loop) {
|
|
||||||
g_main_loop_unref(s->main_loop);
|
|
||||||
}
|
|
||||||
g_free(s);
|
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
|
@ -834,13 +834,16 @@
|
||||||
# @bus: bus id
|
# @bus: bus id
|
||||||
# @target: target id
|
# @target: target id
|
||||||
# @unit: unit id
|
# @unit: unit id
|
||||||
|
# @serial: serial number (since: 3.1)
|
||||||
|
# @dev: device node (POSIX) or device UNC (Windows) (since: 3.1)
|
||||||
#
|
#
|
||||||
# Since: 2.2
|
# Since: 2.2
|
||||||
##
|
##
|
||||||
{ 'struct': 'GuestDiskAddress',
|
{ 'struct': 'GuestDiskAddress',
|
||||||
'data': {'pci-controller': 'GuestPCIAddress',
|
'data': {'pci-controller': 'GuestPCIAddress',
|
||||||
'bus-type': 'GuestDiskBusType',
|
'bus-type': 'GuestDiskBusType',
|
||||||
'bus': 'int', 'target': 'int', 'unit': 'int'} }
|
'bus': 'int', 'target': 'int', 'unit': 'int',
|
||||||
|
'*serial': 'str', '*dev': 'str'} }
|
||||||
|
|
||||||
##
|
##
|
||||||
# @GuestFilesystemInfo:
|
# @GuestFilesystemInfo:
|
||||||
|
|
|
@ -20,9 +20,13 @@
|
||||||
#define QGA_SERVICE_NAME "qemu-ga"
|
#define QGA_SERVICE_NAME "qemu-ga"
|
||||||
#define QGA_SERVICE_DESCRIPTION "Enables integration with QEMU machine emulator and virtualizer."
|
#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 {
|
typedef struct GAService {
|
||||||
SERVICE_STATUS status;
|
SERVICE_STATUS status;
|
||||||
SERVICE_STATUS_HANDLE status_handle;
|
SERVICE_STATUS_HANDLE status_handle;
|
||||||
|
HDEVNOTIFY device_notification_handle;
|
||||||
} GAService;
|
} GAService;
|
||||||
|
|
||||||
int ga_install_service(const char *path, const char *logfile,
|
int ga_install_service(const char *path, const char *logfile,
|
||||||
|
|
|
@ -147,7 +147,8 @@ void ga_uninstall_vss_provider(void)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Call VSS requester and freeze/thaw filesystems and applications */
|
/* 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";
|
const char *func_name = freeze ? "requester_freeze" : "requester_thaw";
|
||||||
QGAVSSRequesterFunc func;
|
QGAVSSRequesterFunc func;
|
||||||
|
@ -164,5 +165,5 @@ void qga_vss_fsfreeze(int *nr_volume, bool freeze, Error **errp)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
func(nr_volume, &errset);
|
func(nr_volume, mountpoints, &errset);
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@ bool vss_initialized(void);
|
||||||
int ga_install_vss_provider(void);
|
int ga_install_vss_provider(void);
|
||||||
void ga_uninstall_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
|
#endif
|
||||||
|
|
|
@ -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;
|
COMPointer<IVssAsync> pAsync;
|
||||||
HANDLE volume;
|
HANDLE volume;
|
||||||
|
@ -246,6 +246,7 @@ void requester_freeze(int *num_vols, ErrorSet *errset)
|
||||||
WCHAR short_volume_name[64], *display_name = short_volume_name;
|
WCHAR short_volume_name[64], *display_name = short_volume_name;
|
||||||
DWORD wait_status;
|
DWORD wait_status;
|
||||||
int num_fixed_drives = 0, i;
|
int num_fixed_drives = 0, i;
|
||||||
|
int num_mount_points = 0;
|
||||||
|
|
||||||
if (vss_ctx.pVssbc) { /* already frozen */
|
if (vss_ctx.pVssbc) { /* already frozen */
|
||||||
*num_vols = 0;
|
*num_vols = 0;
|
||||||
|
@ -337,39 +338,73 @@ void requester_freeze(int *num_vols, ErrorSet *errset)
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
volume = FindFirstVolumeW(short_volume_name, sizeof(short_volume_name));
|
if (mountpoints) {
|
||||||
if (volume == INVALID_HANDLE_VALUE) {
|
PWCHAR volume_name_wchar;
|
||||||
err_set(errset, hr, "failed to find first volume");
|
for (volList *list = (volList *)mountpoints; list; list = list->next) {
|
||||||
goto out;
|
size_t len = strlen(list->value) + 1;
|
||||||
}
|
size_t converted = 0;
|
||||||
for (;;) {
|
|
||||||
if (GetDriveTypeW(short_volume_name) == DRIVE_FIXED) {
|
|
||||||
VSS_ID pid;
|
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);
|
g_gProviderId, &pid);
|
||||||
if (FAILED(hr)) {
|
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",
|
err_set(errset, hr, "failed to add %S to snapshot set",
|
||||||
display_name);
|
volume_name_wchar);
|
||||||
FindVolumeClose(volume);
|
delete volume_name_wchar;
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
num_fixed_drives++;
|
num_mount_points++;
|
||||||
|
|
||||||
|
delete volume_name_wchar;
|
||||||
}
|
}
|
||||||
if (!FindNextVolumeW(volume, short_volume_name,
|
|
||||||
sizeof(short_volume_name))) {
|
if (num_mount_points == 0) {
|
||||||
FindVolumeClose(volume);
|
/* If there is no valid mount points, just exit. */
|
||||||
break;
|
goto out;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (num_fixed_drives == 0) {
|
if (!mountpoints) {
|
||||||
goto out; /* If there is no fixed drive, just exit. */
|
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());
|
hr = vss_ctx.pVssbc->PrepareForBackup(pAsync.replace());
|
||||||
|
@ -435,7 +470,12 @@ void requester_freeze(int *num_vols, ErrorSet *errset)
|
||||||
goto out;
|
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;
|
return;
|
||||||
|
|
||||||
out:
|
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;
|
COMPointer<IVssAsync> pAsync;
|
||||||
|
|
||||||
|
|
|
@ -34,9 +34,16 @@ typedef struct ErrorSet {
|
||||||
STDAPI requester_init(void);
|
STDAPI requester_init(void);
|
||||||
STDAPI requester_deinit(void);
|
STDAPI requester_deinit(void);
|
||||||
|
|
||||||
typedef void (*QGAVSSRequesterFunc)(int *, ErrorSet *);
|
typedef struct volList volList;
|
||||||
void requester_freeze(int *num_vols, ErrorSet *errset);
|
|
||||||
void requester_thaw(int *num_vols, ErrorSet *errset);
|
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
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue