summaryrefslogtreecommitdiffstats
path: root/src/qga
diff options
context:
space:
mode:
Diffstat (limited to 'src/qga')
-rw-r--r--src/qga/Makefile.objs8
-rw-r--r--src/qga/channel-posix.c274
-rw-r--r--src/qga/channel-win32.c360
-rw-r--r--src/qga/channel.h33
-rw-r--r--src/qga/commands-posix.c2524
-rw-r--r--src/qga/commands-win32.c1376
-rw-r--r--src/qga/commands.c463
-rw-r--r--src/qga/guest-agent-command-state.c73
-rw-r--r--src/qga/guest-agent-core.h50
-rw-r--r--src/qga/installer/qemu-ga.wxs185
-rw-r--r--src/qga/main.c1403
-rw-r--r--src/qga/qapi-schema.json999
-rw-r--r--src/qga/service-win32.c192
-rw-r--r--src/qga/service-win32.h31
-rw-r--r--src/qga/vss-win32.c166
-rw-r--r--src/qga/vss-win32.h27
-rw-r--r--src/qga/vss-win32/Makefile.objs23
-rw-r--r--src/qga/vss-win32/install.cpp465
-rw-r--r--src/qga/vss-win32/provider.cpp534
-rw-r--r--src/qga/vss-win32/qga-vss.def13
-rw-r--r--src/qga/vss-win32/qga-vss.idl20
-rw-r--r--src/qga/vss-win32/qga-vss.tlbbin0 -> 1528 bytes
-rw-r--r--src/qga/vss-win32/requester.cpp505
-rw-r--r--src/qga/vss-win32/requester.h46
-rw-r--r--src/qga/vss-win32/vss-common.h129
25 files changed, 9899 insertions, 0 deletions
diff --git a/src/qga/Makefile.objs b/src/qga/Makefile.objs
new file mode 100644
index 0000000..1c5986c
--- /dev/null
+++ b/src/qga/Makefile.objs
@@ -0,0 +1,8 @@
+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
+qga-obj-$(CONFIG_WIN32) += vss-win32.o
+qga-obj-y += qapi-generated/qga-qapi-types.o qapi-generated/qga-qapi-visit.o
+qga-obj-y += qapi-generated/qga-qmp-marshal.o
+
+qga-vss-dll-obj-$(CONFIG_QGA_VSS) += vss-win32/
diff --git a/src/qga/channel-posix.c b/src/qga/channel-posix.c
new file mode 100644
index 0000000..50d9dd3
--- /dev/null
+++ b/src/qga/channel-posix.c
@@ -0,0 +1,274 @@
+#include <glib.h>
+#include <termios.h>
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include "qemu/osdep.h"
+#include "qemu/sockets.h"
+#include "qga/channel.h"
+
+#ifdef CONFIG_SOLARIS
+#include <stropts.h>
+#endif
+
+#define GA_CHANNEL_BAUDRATE_DEFAULT B38400 /* for isa-serial channels */
+
+struct GAChannel {
+ GIOChannel *listen_channel;
+ GIOChannel *client_channel;
+ GAChannelMethod method;
+ GAChannelCallback event_cb;
+ gpointer user_data;
+};
+
+static int ga_channel_client_add(GAChannel *c, int fd);
+
+static gboolean ga_channel_listen_accept(GIOChannel *channel,
+ GIOCondition condition, gpointer data)
+{
+ GAChannel *c = data;
+ int ret, client_fd;
+ bool accepted = false;
+ struct sockaddr_un addr;
+ socklen_t addrlen = sizeof(addr);
+
+ g_assert(channel != NULL);
+
+ client_fd = qemu_accept(g_io_channel_unix_get_fd(channel),
+ (struct sockaddr *)&addr, &addrlen);
+ if (client_fd == -1) {
+ g_warning("error converting fd to gsocket: %s", strerror(errno));
+ goto out;
+ }
+ qemu_set_nonblock(client_fd);
+ ret = ga_channel_client_add(c, client_fd);
+ if (ret) {
+ g_warning("error setting up connection");
+ close(client_fd);
+ goto out;
+ }
+ accepted = true;
+
+out:
+ /* only accept 1 connection at a time */
+ return !accepted;
+}
+
+/* start polling for readable events on listen fd, new==true
+ * indicates we should use the existing s->listen_channel
+ */
+static void ga_channel_listen_add(GAChannel *c, int listen_fd, bool create)
+{
+ if (create) {
+ c->listen_channel = g_io_channel_unix_new(listen_fd);
+ }
+ g_io_add_watch(c->listen_channel, G_IO_IN, ga_channel_listen_accept, c);
+}
+
+static void ga_channel_listen_close(GAChannel *c)
+{
+ g_assert(c->method == GA_CHANNEL_UNIX_LISTEN);
+ g_assert(c->listen_channel);
+ g_io_channel_shutdown(c->listen_channel, true, NULL);
+ g_io_channel_unref(c->listen_channel);
+ c->listen_channel = NULL;
+}
+
+/* cleanup state for closed connection/session, start accepting new
+ * connections if we're in listening mode
+ */
+static void ga_channel_client_close(GAChannel *c)
+{
+ g_assert(c->client_channel);
+ g_io_channel_shutdown(c->client_channel, true, NULL);
+ g_io_channel_unref(c->client_channel);
+ c->client_channel = NULL;
+ if (c->method == GA_CHANNEL_UNIX_LISTEN && c->listen_channel) {
+ ga_channel_listen_add(c, 0, false);
+ }
+}
+
+static gboolean ga_channel_client_event(GIOChannel *channel,
+ GIOCondition condition, gpointer data)
+{
+ GAChannel *c = data;
+ gboolean client_cont;
+
+ g_assert(c);
+ if (c->event_cb) {
+ client_cont = c->event_cb(condition, c->user_data);
+ if (!client_cont) {
+ ga_channel_client_close(c);
+ return false;
+ }
+ }
+ return true;
+}
+
+static int ga_channel_client_add(GAChannel *c, int fd)
+{
+ GIOChannel *client_channel;
+ GError *err = NULL;
+
+ g_assert(c && !c->client_channel);
+ client_channel = g_io_channel_unix_new(fd);
+ g_assert(client_channel);
+ g_io_channel_set_encoding(client_channel, NULL, &err);
+ if (err != NULL) {
+ g_warning("error setting channel encoding to binary");
+ g_error_free(err);
+ return -1;
+ }
+ g_io_add_watch(client_channel, G_IO_IN | G_IO_HUP,
+ ga_channel_client_event, c);
+ c->client_channel = client_channel;
+ return 0;
+}
+
+static gboolean ga_channel_open(GAChannel *c, const gchar *path, GAChannelMethod method)
+{
+ int ret;
+ c->method = method;
+
+ switch (c->method) {
+ case GA_CHANNEL_VIRTIO_SERIAL: {
+ int fd = qemu_open(path, O_RDWR | O_NONBLOCK
+#ifndef CONFIG_SOLARIS
+ | O_ASYNC
+#endif
+ );
+ if (fd == -1) {
+ g_critical("error opening channel: %s", strerror(errno));
+ return false;
+ }
+#ifdef CONFIG_SOLARIS
+ ret = ioctl(fd, I_SETSIG, S_OUTPUT | S_INPUT | S_HIPRI);
+ if (ret == -1) {
+ g_critical("error setting event mask for channel: %s",
+ strerror(errno));
+ close(fd);
+ return false;
+ }
+#endif
+ ret = ga_channel_client_add(c, fd);
+ if (ret) {
+ g_critical("error adding channel to main loop");
+ close(fd);
+ return false;
+ }
+ break;
+ }
+ case GA_CHANNEL_ISA_SERIAL: {
+ struct termios tio;
+ int fd = qemu_open(path, O_RDWR | O_NOCTTY | O_NONBLOCK);
+ if (fd == -1) {
+ g_critical("error opening channel: %s", strerror(errno));
+ return false;
+ }
+ tcgetattr(fd, &tio);
+ /* set up serial port for non-canonical, dumb byte streaming */
+ tio.c_iflag &= ~(IGNBRK | BRKINT | IGNPAR | PARMRK | INPCK | ISTRIP |
+ INLCR | IGNCR | ICRNL | IXON | IXOFF | IXANY |
+ IMAXBEL);
+ tio.c_oflag = 0;
+ tio.c_lflag = 0;
+ tio.c_cflag |= GA_CHANNEL_BAUDRATE_DEFAULT;
+ /* 1 available byte min or reads will block (we'll set non-blocking
+ * elsewhere, else we have to deal with read()=0 instead)
+ */
+ tio.c_cc[VMIN] = 1;
+ tio.c_cc[VTIME] = 0;
+ /* flush everything waiting for read/xmit, it's garbage at this point */
+ tcflush(fd, TCIFLUSH);
+ tcsetattr(fd, TCSANOW, &tio);
+ ret = ga_channel_client_add(c, fd);
+ if (ret) {
+ g_critical("error adding channel to main loop");
+ close(fd);
+ return false;
+ }
+ break;
+ }
+ case GA_CHANNEL_UNIX_LISTEN: {
+ Error *local_err = NULL;
+ int fd = unix_listen(path, NULL, strlen(path), &local_err);
+ if (local_err != NULL) {
+ g_critical("%s", error_get_pretty(local_err));
+ error_free(local_err);
+ return false;
+ }
+ ga_channel_listen_add(c, fd, true);
+ break;
+ }
+ default:
+ g_critical("error binding/listening to specified socket");
+ return false;
+ }
+
+ return true;
+}
+
+GIOStatus ga_channel_write_all(GAChannel *c, const gchar *buf, gsize size)
+{
+ GError *err = NULL;
+ gsize written = 0;
+ GIOStatus status = G_IO_STATUS_NORMAL;
+
+ while (size) {
+ g_debug("sending data, count: %d", (int)size);
+ status = g_io_channel_write_chars(c->client_channel, buf, size,
+ &written, &err);
+ if (status == G_IO_STATUS_NORMAL) {
+ size -= written;
+ buf += written;
+ } else if (status != G_IO_STATUS_AGAIN) {
+ g_warning("error writing to channel: %s", err->message);
+ return status;
+ }
+ }
+
+ do {
+ status = g_io_channel_flush(c->client_channel, &err);
+ } while (status == G_IO_STATUS_AGAIN);
+
+ if (status != G_IO_STATUS_NORMAL) {
+ g_warning("error flushing channel: %s", err->message);
+ }
+
+ return status;
+}
+
+GIOStatus ga_channel_read(GAChannel *c, gchar *buf, gsize size, gsize *count)
+{
+ return g_io_channel_read_chars(c->client_channel, buf, size, count, NULL);
+}
+
+GAChannel *ga_channel_new(GAChannelMethod method, const gchar *path,
+ GAChannelCallback cb, gpointer opaque)
+{
+ GAChannel *c = g_new0(GAChannel, 1);
+ c->event_cb = cb;
+ c->user_data = opaque;
+
+ if (!ga_channel_open(c, path, method)) {
+ g_critical("error opening channel");
+ ga_channel_free(c);
+ return NULL;
+ }
+
+ return c;
+}
+
+void ga_channel_free(GAChannel *c)
+{
+ if (c->method == GA_CHANNEL_UNIX_LISTEN
+ && c->listen_channel) {
+ ga_channel_listen_close(c);
+ }
+ if (c->client_channel) {
+ ga_channel_client_close(c);
+ }
+ g_free(c);
+}
diff --git a/src/qga/channel-win32.c b/src/qga/channel-win32.c
new file mode 100644
index 0000000..0452b9f
--- /dev/null
+++ b/src/qga/channel-win32.c
@@ -0,0 +1,360 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdbool.h>
+#include <glib.h>
+#include <windows.h>
+#include <errno.h>
+#include <io.h>
+#include "qga/guest-agent-core.h"
+#include "qga/channel.h"
+
+typedef struct GAChannelReadState {
+ guint thread_id;
+ uint8_t *buf;
+ size_t buf_size;
+ size_t cur; /* current buffer start */
+ size_t pending; /* pending buffered bytes to read */
+ OVERLAPPED ov;
+ bool ov_pending; /* whether on async read is outstanding */
+} GAChannelReadState;
+
+struct GAChannel {
+ HANDLE handle;
+ GAChannelCallback cb;
+ gpointer user_data;
+ GAChannelReadState rstate;
+ GIOCondition pending_events; /* TODO: use GAWatch.pollfd.revents */
+ GSource *source;
+};
+
+typedef struct GAWatch {
+ GSource source;
+ GPollFD pollfd;
+ GAChannel *channel;
+ GIOCondition events_mask;
+} GAWatch;
+
+/*
+ * Called by glib prior to polling to set up poll events if polling is needed.
+ *
+ */
+static gboolean ga_channel_prepare(GSource *source, gint *timeout_ms)
+{
+ GAWatch *watch = (GAWatch *)source;
+ GAChannel *c = (GAChannel *)watch->channel;
+ GAChannelReadState *rs = &c->rstate;
+ DWORD count_read, count_to_read = 0;
+ bool success;
+ GIOCondition new_events = 0;
+
+ g_debug("prepare");
+ /* go ahead and submit another read if there's room in the buffer
+ * and no previous reads are outstanding
+ */
+ if (!rs->ov_pending) {
+ if (rs->cur + rs->pending >= rs->buf_size) {
+ if (rs->cur) {
+ memmove(rs->buf, rs->buf + rs->cur, rs->pending);
+ rs->cur = 0;
+ }
+ }
+ count_to_read = rs->buf_size - rs->cur - rs->pending;
+ }
+
+ if (rs->ov_pending || count_to_read <= 0) {
+ goto out;
+ }
+
+ /* submit the read */
+ success = ReadFile(c->handle, rs->buf + rs->cur + rs->pending,
+ count_to_read, &count_read, &rs->ov);
+ if (success) {
+ rs->pending += count_read;
+ rs->ov_pending = false;
+ } else {
+ if (GetLastError() == ERROR_IO_PENDING) {
+ rs->ov_pending = true;
+ } else {
+ new_events |= G_IO_ERR;
+ }
+ }
+
+out:
+ /* dont block forever, iterate the main loop every once and a while */
+ *timeout_ms = 500;
+ /* if there's data in the read buffer, or another event is pending,
+ * skip polling and issue user cb.
+ */
+ if (rs->pending) {
+ new_events |= G_IO_IN;
+ }
+ c->pending_events |= new_events;
+ return !!c->pending_events;
+}
+
+/*
+ * Called by glib after an outstanding read request is completed.
+ */
+static gboolean ga_channel_check(GSource *source)
+{
+ GAWatch *watch = (GAWatch *)source;
+ GAChannel *c = (GAChannel *)watch->channel;
+ GAChannelReadState *rs = &c->rstate;
+ DWORD count_read, error;
+ BOOL success;
+
+ GIOCondition new_events = 0;
+
+ g_debug("check");
+
+ /* failing this implies we issued a read that completed immediately,
+ * yet no data was placed into the buffer (and thus we did not skip
+ * polling). but since EOF is not obtainable until we retrieve an
+ * overlapped result, it must be the case that there was data placed
+ * into the buffer, or an error was generated by Readfile(). in either
+ * case, we should've skipped the polling for this round.
+ */
+ g_assert(rs->ov_pending);
+
+ success = GetOverlappedResult(c->handle, &rs->ov, &count_read, FALSE);
+ if (success) {
+ g_debug("thread: overlapped result, count_read: %d", (int)count_read);
+ rs->pending += count_read;
+ new_events |= G_IO_IN;
+ } else {
+ error = GetLastError();
+ if (error == 0 || error == ERROR_HANDLE_EOF ||
+ error == ERROR_NO_SYSTEM_RESOURCES ||
+ error == ERROR_OPERATION_ABORTED) {
+ /* note: On WinXP SP3 with rhel6ga virtio-win-1.1.16 vioser drivers,
+ * ENSR seems to be synonymous with when we'd normally expect
+ * ERROR_HANDLE_EOF. So treat it as such. Microsoft's
+ * recommendation for ERROR_NO_SYSTEM_RESOURCES is to
+ * retry the read, so this happens to work out anyway. On newer
+ * virtio-win driver, this seems to be replaced with EOA, so
+ * handle that in the same fashion.
+ */
+ new_events |= G_IO_HUP;
+ } else if (error != ERROR_IO_INCOMPLETE) {
+ g_critical("error retrieving overlapped result: %d", (int)error);
+ new_events |= G_IO_ERR;
+ }
+ }
+
+ if (new_events) {
+ rs->ov_pending = 0;
+ }
+ c->pending_events |= new_events;
+
+ return !!c->pending_events;
+}
+
+/*
+ * Called by glib after either prepare or check routines signal readiness
+ */
+static gboolean ga_channel_dispatch(GSource *source, GSourceFunc unused,
+ gpointer user_data)
+{
+ GAWatch *watch = (GAWatch *)source;
+ GAChannel *c = (GAChannel *)watch->channel;
+ GAChannelReadState *rs = &c->rstate;
+ gboolean success;
+
+ g_debug("dispatch");
+ success = c->cb(watch->pollfd.revents, c->user_data);
+
+ if (c->pending_events & G_IO_ERR) {
+ g_critical("channel error, removing source");
+ return false;
+ }
+
+ /* TODO: replace rs->pending with watch->revents */
+ c->pending_events &= ~G_IO_HUP;
+ if (!rs->pending) {
+ c->pending_events &= ~G_IO_IN;
+ } else {
+ c->pending_events = 0;
+ }
+ return success;
+}
+
+static void ga_channel_finalize(GSource *source)
+{
+ g_debug("finalize");
+}
+
+GSourceFuncs ga_channel_watch_funcs = {
+ ga_channel_prepare,
+ ga_channel_check,
+ ga_channel_dispatch,
+ ga_channel_finalize
+};
+
+static GSource *ga_channel_create_watch(GAChannel *c)
+{
+ GSource *source = g_source_new(&ga_channel_watch_funcs, sizeof(GAWatch));
+ GAWatch *watch = (GAWatch *)source;
+
+ watch->channel = c;
+ watch->pollfd.fd = (gintptr) c->rstate.ov.hEvent;
+ g_source_add_poll(source, &watch->pollfd);
+
+ return source;
+}
+
+GIOStatus ga_channel_read(GAChannel *c, char *buf, size_t size, gsize *count)
+{
+ GAChannelReadState *rs = &c->rstate;
+ GIOStatus status;
+ size_t to_read = 0;
+
+ if (c->pending_events & G_IO_ERR) {
+ return G_IO_STATUS_ERROR;
+ }
+
+ *count = to_read = MIN(size, rs->pending);
+ if (to_read) {
+ memcpy(buf, rs->buf + rs->cur, to_read);
+ rs->cur += to_read;
+ rs->pending -= to_read;
+ status = G_IO_STATUS_NORMAL;
+ } else {
+ status = G_IO_STATUS_AGAIN;
+ }
+
+ return status;
+}
+
+static GIOStatus ga_channel_write(GAChannel *c, const char *buf, size_t size,
+ size_t *count)
+{
+ GIOStatus status;
+ OVERLAPPED ov = {0};
+ BOOL ret;
+ DWORD written;
+
+ ov.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
+ ret = WriteFile(c->handle, buf, size, &written, &ov);
+ if (!ret) {
+ if (GetLastError() == ERROR_IO_PENDING) {
+ /* write is pending */
+ ret = GetOverlappedResult(c->handle, &ov, &written, TRUE);
+ if (!ret) {
+ if (!GetLastError()) {
+ status = G_IO_STATUS_AGAIN;
+ } else {
+ status = G_IO_STATUS_ERROR;
+ }
+ } else {
+ /* write is complete */
+ status = G_IO_STATUS_NORMAL;
+ *count = written;
+ }
+ } else {
+ status = G_IO_STATUS_ERROR;
+ }
+ } else {
+ /* write returned immediately */
+ status = G_IO_STATUS_NORMAL;
+ *count = written;
+ }
+
+ if (ov.hEvent) {
+ CloseHandle(ov.hEvent);
+ ov.hEvent = NULL;
+ }
+ return status;
+}
+
+GIOStatus ga_channel_write_all(GAChannel *c, const char *buf, size_t size)
+{
+ GIOStatus status = G_IO_STATUS_NORMAL;
+ size_t count = 0;
+
+ while (size) {
+ status = ga_channel_write(c, buf, size, &count);
+ if (status == G_IO_STATUS_NORMAL) {
+ size -= count;
+ buf += count;
+ } else if (status != G_IO_STATUS_AGAIN) {
+ break;
+ }
+ }
+
+ return status;
+}
+
+static gboolean ga_channel_open(GAChannel *c, GAChannelMethod method,
+ const gchar *path)
+{
+ COMMTIMEOUTS comTimeOut = {0};
+ gchar newpath[MAXPATHLEN] = {0};
+ comTimeOut.ReadIntervalTimeout = 1;
+
+ if (method != GA_CHANNEL_VIRTIO_SERIAL && method != GA_CHANNEL_ISA_SERIAL) {
+ g_critical("unsupported communication method");
+ return false;
+ }
+
+ if (method == GA_CHANNEL_ISA_SERIAL){
+ snprintf(newpath, sizeof(newpath), "\\\\.\\%s", path);
+ }else {
+ g_strlcpy(newpath, path, sizeof(newpath));
+ }
+
+ c->handle = CreateFile(newpath, GENERIC_READ | GENERIC_WRITE, 0, NULL,
+ OPEN_EXISTING,
+ FILE_FLAG_NO_BUFFERING | FILE_FLAG_OVERLAPPED, NULL);
+ if (c->handle == INVALID_HANDLE_VALUE) {
+ g_critical("error opening path %s", newpath);
+ return false;
+ }
+
+ if (method == GA_CHANNEL_ISA_SERIAL && !SetCommTimeouts(c->handle,&comTimeOut)) {
+ g_critical("error setting timeout for com port: %lu",GetLastError());
+ CloseHandle(c->handle);
+ return false;
+ }
+
+ return true;
+}
+
+GAChannel *ga_channel_new(GAChannelMethod method, const gchar *path,
+ GAChannelCallback cb, gpointer opaque)
+{
+ GAChannel *c = g_new0(GAChannel, 1);
+ SECURITY_ATTRIBUTES sec_attrs;
+
+ if (!ga_channel_open(c, method, path)) {
+ g_critical("error opening channel");
+ g_free(c);
+ return NULL;
+ }
+
+ c->cb = cb;
+ c->user_data = opaque;
+
+ sec_attrs.nLength = sizeof(SECURITY_ATTRIBUTES);
+ sec_attrs.lpSecurityDescriptor = NULL;
+ sec_attrs.bInheritHandle = false;
+
+ c->rstate.buf_size = QGA_READ_COUNT_DEFAULT;
+ c->rstate.buf = g_malloc(QGA_READ_COUNT_DEFAULT);
+ c->rstate.ov.hEvent = CreateEvent(&sec_attrs, FALSE, FALSE, NULL);
+
+ c->source = ga_channel_create_watch(c);
+ g_source_attach(c->source, NULL);
+ return c;
+}
+
+void ga_channel_free(GAChannel *c)
+{
+ if (c->source) {
+ g_source_destroy(c->source);
+ }
+ if (c->rstate.ov.hEvent) {
+ CloseHandle(c->rstate.ov.hEvent);
+ }
+ g_free(c->rstate.buf);
+ g_free(c);
+}
diff --git a/src/qga/channel.h b/src/qga/channel.h
new file mode 100644
index 0000000..3704ea9
--- /dev/null
+++ b/src/qga/channel.h
@@ -0,0 +1,33 @@
+/*
+ * QEMU Guest Agent channel declarations
+ *
+ * Copyright IBM Corp. 2012
+ *
+ * Authors:
+ * Michael Roth <mdroth@linux.vnet.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+#ifndef QGA_CHANNEL_H
+#define QGA_CHANNEL_H
+
+#include <glib.h>
+
+typedef struct GAChannel GAChannel;
+
+typedef enum {
+ GA_CHANNEL_VIRTIO_SERIAL,
+ GA_CHANNEL_ISA_SERIAL,
+ GA_CHANNEL_UNIX_LISTEN,
+} GAChannelMethod;
+
+typedef gboolean (*GAChannelCallback)(GIOCondition condition, gpointer opaque);
+
+GAChannel *ga_channel_new(GAChannelMethod method, const gchar *path,
+ GAChannelCallback cb, gpointer opaque);
+void ga_channel_free(GAChannel *c);
+GIOStatus ga_channel_read(GAChannel *c, gchar *buf, gsize size, gsize *count);
+GIOStatus ga_channel_write_all(GAChannel *c, const gchar *buf, gsize size);
+
+#endif
diff --git a/src/qga/commands-posix.c b/src/qga/commands-posix.c
new file mode 100644
index 0000000..e6c9f51
--- /dev/null
+++ b/src/qga/commands-posix.c
@@ -0,0 +1,2524 @@
+/*
+ * QEMU Guest Agent POSIX-specific command implementations
+ *
+ * Copyright IBM Corp. 2011
+ *
+ * Authors:
+ * Michael Roth <mdroth@linux.vnet.ibm.com>
+ * Michal Privoznik <mprivozn@redhat.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include <glib.h>
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/wait.h>
+#include <sys/sysmacros.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <inttypes.h>
+#include "qga/guest-agent-core.h"
+#include "qga-qmp-commands.h"
+#include "qapi/qmp/qerror.h"
+#include "qemu/queue.h"
+#include "qemu/host-utils.h"
+#include "qemu/sockets.h"
+
+#ifndef CONFIG_HAS_ENVIRON
+#ifdef __APPLE__
+#include <crt_externs.h>
+#define environ (*_NSGetEnviron())
+#else
+extern char **environ;
+#endif
+#endif
+
+#if defined(__linux__)
+#include <mntent.h>
+#include <linux/fs.h>
+#include <ifaddrs.h>
+#include <arpa/inet.h>
+#include <sys/socket.h>
+#include <net/if.h>
+
+#ifdef FIFREEZE
+#define CONFIG_FSFREEZE
+#endif
+#ifdef FITRIM
+#define CONFIG_FSTRIM
+#endif
+#endif
+
+static void ga_wait_child(pid_t pid, int *status, Error **errp)
+{
+ pid_t rpid;
+
+ *status = 0;
+
+ do {
+ rpid = waitpid(pid, status, 0);
+ } while (rpid == -1 && errno == EINTR);
+
+ if (rpid == -1) {
+ error_setg_errno(errp, errno, "failed to wait for child (pid: %d)",
+ pid);
+ return;
+ }
+
+ g_assert(rpid == pid);
+}
+
+void qmp_guest_shutdown(bool has_mode, const char *mode, Error **errp)
+{
+ const char *shutdown_flag;
+ Error *local_err = NULL;
+ pid_t pid;
+ int status;
+
+ slog("guest-shutdown called, mode: %s", mode);
+ if (!has_mode || strcmp(mode, "powerdown") == 0) {
+ shutdown_flag = "-P";
+ } else if (strcmp(mode, "halt") == 0) {
+ shutdown_flag = "-H";
+ } else if (strcmp(mode, "reboot") == 0) {
+ shutdown_flag = "-r";
+ } else {
+ error_setg(errp,
+ "mode is invalid (valid values are: halt|powerdown|reboot");
+ return;
+ }
+
+ pid = fork();
+ if (pid == 0) {
+ /* child, start the shutdown */
+ setsid();
+ reopen_fd_to_null(0);
+ reopen_fd_to_null(1);
+ reopen_fd_to_null(2);
+
+ execle("/sbin/shutdown", "shutdown", "-h", shutdown_flag, "+0",
+ "hypervisor initiated shutdown", (char*)NULL, environ);
+ _exit(EXIT_FAILURE);
+ } else if (pid < 0) {
+ error_setg_errno(errp, errno, "failed to create child process");
+ return;
+ }
+
+ ga_wait_child(pid, &status, &local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ return;
+ }
+
+ if (!WIFEXITED(status)) {
+ error_setg(errp, "child process has terminated abnormally");
+ return;
+ }
+
+ if (WEXITSTATUS(status)) {
+ error_setg(errp, "child process has failed to shutdown");
+ return;
+ }
+
+ /* succeeded */
+}
+
+int64_t qmp_guest_get_time(Error **errp)
+{
+ int ret;
+ qemu_timeval tq;
+ int64_t time_ns;
+
+ ret = qemu_gettimeofday(&tq);
+ if (ret < 0) {
+ error_setg_errno(errp, errno, "Failed to get time");
+ return -1;
+ }
+
+ time_ns = tq.tv_sec * 1000000000LL + tq.tv_usec * 1000;
+ return time_ns;
+}
+
+void qmp_guest_set_time(bool has_time, int64_t time_ns, Error **errp)
+{
+ int ret;
+ int status;
+ pid_t pid;
+ Error *local_err = NULL;
+ struct timeval tv;
+
+ /* If user has passed a time, validate and set it. */
+ if (has_time) {
+ GDate date = { 0, };
+
+ /* year-2038 will overflow in case time_t is 32bit */
+ if (time_ns / 1000000000 != (time_t)(time_ns / 1000000000)) {
+ error_setg(errp, "Time %" PRId64 " is too large", time_ns);
+ return;
+ }
+
+ tv.tv_sec = time_ns / 1000000000;
+ tv.tv_usec = (time_ns % 1000000000) / 1000;
+ g_date_set_time_t(&date, tv.tv_sec);
+ if (date.year < 1970 || date.year >= 2070) {
+ error_setg_errno(errp, errno, "Invalid time");
+ return;
+ }
+
+ ret = settimeofday(&tv, NULL);
+ if (ret < 0) {
+ error_setg_errno(errp, errno, "Failed to set time to guest");
+ return;
+ }
+ }
+
+ /* Now, if user has passed a time to set and the system time is set, we
+ * just need to synchronize the hardware clock. However, if no time was
+ * passed, user is requesting the opposite: set the system time from the
+ * hardware clock (RTC). */
+ pid = fork();
+ if (pid == 0) {
+ setsid();
+ reopen_fd_to_null(0);
+ reopen_fd_to_null(1);
+ reopen_fd_to_null(2);
+
+ /* Use '/sbin/hwclock -w' to set RTC from the system time,
+ * or '/sbin/hwclock -s' to set the system time from RTC. */
+ execle("/sbin/hwclock", "hwclock", has_time ? "-w" : "-s",
+ NULL, environ);
+ _exit(EXIT_FAILURE);
+ } else if (pid < 0) {
+ error_setg_errno(errp, errno, "failed to create child process");
+ return;
+ }
+
+ ga_wait_child(pid, &status, &local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ return;
+ }
+
+ if (!WIFEXITED(status)) {
+ error_setg(errp, "child process has terminated abnormally");
+ return;
+ }
+
+ if (WEXITSTATUS(status)) {
+ error_setg(errp, "hwclock failed to set hardware clock to system time");
+ return;
+ }
+}
+
+typedef enum {
+ RW_STATE_NEW,
+ RW_STATE_READING,
+ RW_STATE_WRITING,
+} RwState;
+
+typedef struct GuestFileHandle {
+ uint64_t id;
+ FILE *fh;
+ RwState state;
+ QTAILQ_ENTRY(GuestFileHandle) next;
+} GuestFileHandle;
+
+static struct {
+ QTAILQ_HEAD(, GuestFileHandle) filehandles;
+} guest_file_state = {
+ .filehandles = QTAILQ_HEAD_INITIALIZER(guest_file_state.filehandles),
+};
+
+static int64_t guest_file_handle_add(FILE *fh, Error **errp)
+{
+ GuestFileHandle *gfh;
+ int64_t handle;
+
+ handle = ga_get_fd_handle(ga_state, errp);
+ if (handle < 0) {
+ return -1;
+ }
+
+ gfh = g_new0(GuestFileHandle, 1);
+ gfh->id = handle;
+ gfh->fh = fh;
+ QTAILQ_INSERT_TAIL(&guest_file_state.filehandles, gfh, next);
+
+ return handle;
+}
+
+static GuestFileHandle *guest_file_handle_find(int64_t id, Error **errp)
+{
+ GuestFileHandle *gfh;
+
+ QTAILQ_FOREACH(gfh, &guest_file_state.filehandles, next)
+ {
+ if (gfh->id == id) {
+ return gfh;
+ }
+ }
+
+ error_setg(errp, "handle '%" PRId64 "' has not been found", id);
+ return NULL;
+}
+
+typedef const char * const ccpc;
+
+#ifndef O_BINARY
+#define O_BINARY 0
+#endif
+
+/* http://pubs.opengroup.org/onlinepubs/9699919799/functions/fopen.html */
+static const struct {
+ ccpc *forms;
+ int oflag_base;
+} guest_file_open_modes[] = {
+ { (ccpc[]){ "r", NULL }, O_RDONLY },
+ { (ccpc[]){ "rb", NULL }, O_RDONLY | O_BINARY },
+ { (ccpc[]){ "w", NULL }, O_WRONLY | O_CREAT | O_TRUNC },
+ { (ccpc[]){ "wb", NULL }, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY },
+ { (ccpc[]){ "a", NULL }, O_WRONLY | O_CREAT | O_APPEND },
+ { (ccpc[]){ "ab", NULL }, O_WRONLY | O_CREAT | O_APPEND | O_BINARY },
+ { (ccpc[]){ "r+", NULL }, O_RDWR },
+ { (ccpc[]){ "rb+", "r+b", NULL }, O_RDWR | O_BINARY },
+ { (ccpc[]){ "w+", NULL }, O_RDWR | O_CREAT | O_TRUNC },
+ { (ccpc[]){ "wb+", "w+b", NULL }, O_RDWR | O_CREAT | O_TRUNC | O_BINARY },
+ { (ccpc[]){ "a+", NULL }, O_RDWR | O_CREAT | O_APPEND },
+ { (ccpc[]){ "ab+", "a+b", NULL }, O_RDWR | O_CREAT | O_APPEND | O_BINARY }
+};
+
+static int
+find_open_flag(const char *mode_str, Error **errp)
+{
+ unsigned mode;
+
+ for (mode = 0; mode < ARRAY_SIZE(guest_file_open_modes); ++mode) {
+ ccpc *form;
+
+ form = guest_file_open_modes[mode].forms;
+ while (*form != NULL && strcmp(*form, mode_str) != 0) {
+ ++form;
+ }
+ if (*form != NULL) {
+ break;
+ }
+ }
+
+ if (mode == ARRAY_SIZE(guest_file_open_modes)) {
+ error_setg(errp, "invalid file open mode '%s'", mode_str);
+ return -1;
+ }
+ return guest_file_open_modes[mode].oflag_base | O_NOCTTY | O_NONBLOCK;
+}
+
+#define DEFAULT_NEW_FILE_MODE (S_IRUSR | S_IWUSR | \
+ S_IRGRP | S_IWGRP | \
+ S_IROTH | S_IWOTH)
+
+static FILE *
+safe_open_or_create(const char *path, const char *mode, Error **errp)
+{
+ Error *local_err = NULL;
+ int oflag;
+
+ oflag = find_open_flag(mode, &local_err);
+ if (local_err == NULL) {
+ int fd;
+
+ /* If the caller wants / allows creation of a new file, we implement it
+ * with a two step process: open() + (open() / fchmod()).
+ *
+ * First we insist on creating the file exclusively as a new file. If
+ * that succeeds, we're free to set any file-mode bits on it. (The
+ * motivation is that we want to set those file-mode bits independently
+ * of the current umask.)
+ *
+ * If the exclusive creation fails because the file already exists
+ * (EEXIST is not possible for any other reason), we just attempt to
+ * open the file, but in this case we won't be allowed to change the
+ * file-mode bits on the preexistent file.
+ *
+ * The pathname should never disappear between the two open()s in
+ * practice. If it happens, then someone very likely tried to race us.
+ * In this case just go ahead and report the ENOENT from the second
+ * open() to the caller.
+ *
+ * If the caller wants to open a preexistent file, then the first
+ * open() is decisive and its third argument is ignored, and the second
+ * open() and the fchmod() are never called.
+ */
+ fd = open(path, oflag | ((oflag & O_CREAT) ? O_EXCL : 0), 0);
+ if (fd == -1 && errno == EEXIST) {
+ oflag &= ~(unsigned)O_CREAT;
+ fd = open(path, oflag);
+ }
+
+ if (fd == -1) {
+ error_setg_errno(&local_err, errno, "failed to open file '%s' "
+ "(mode: '%s')", path, mode);
+ } else {
+ qemu_set_cloexec(fd);
+
+ if ((oflag & O_CREAT) && fchmod(fd, DEFAULT_NEW_FILE_MODE) == -1) {
+ error_setg_errno(&local_err, errno, "failed to set permission "
+ "0%03o on new file '%s' (mode: '%s')",
+ (unsigned)DEFAULT_NEW_FILE_MODE, path, mode);
+ } else {
+ FILE *f;
+
+ f = fdopen(fd, mode);
+ if (f == NULL) {
+ error_setg_errno(&local_err, errno, "failed to associate "
+ "stdio stream with file descriptor %d, "
+ "file '%s' (mode: '%s')", fd, path, mode);
+ } else {
+ return f;
+ }
+ }
+
+ close(fd);
+ if (oflag & O_CREAT) {
+ unlink(path);
+ }
+ }
+ }
+
+ error_propagate(errp, local_err);
+ return NULL;
+}
+
+int64_t qmp_guest_file_open(const char *path, bool has_mode, const char *mode,
+ Error **errp)
+{
+ FILE *fh;
+ Error *local_err = NULL;
+ int64_t handle;
+
+ if (!has_mode) {
+ mode = "r";
+ }
+ slog("guest-file-open called, filepath: %s, mode: %s", path, mode);
+ fh = safe_open_or_create(path, mode, &local_err);
+ if (local_err != NULL) {
+ error_propagate(errp, local_err);
+ return -1;
+ }
+
+ /* set fd non-blocking to avoid common use cases (like reading from a
+ * named pipe) from hanging the agent
+ */
+ qemu_set_nonblock(fileno(fh));
+
+ handle = guest_file_handle_add(fh, errp);
+ if (handle < 0) {
+ fclose(fh);
+ return -1;
+ }
+
+ slog("guest-file-open, handle: %" PRId64, handle);
+ return handle;
+}
+
+void qmp_guest_file_close(int64_t handle, Error **errp)
+{
+ GuestFileHandle *gfh = guest_file_handle_find(handle, errp);
+ int ret;
+
+ slog("guest-file-close called, handle: %" PRId64, handle);
+ if (!gfh) {
+ return;
+ }
+
+ ret = fclose(gfh->fh);
+ if (ret == EOF) {
+ error_setg_errno(errp, errno, "failed to close handle");
+ return;
+ }
+
+ QTAILQ_REMOVE(&guest_file_state.filehandles, gfh, next);
+ g_free(gfh);
+}
+
+struct GuestFileRead *qmp_guest_file_read(int64_t handle, bool has_count,
+ int64_t count, Error **errp)
+{
+ GuestFileHandle *gfh = guest_file_handle_find(handle, errp);
+ GuestFileRead *read_data = NULL;
+ guchar *buf;
+ FILE *fh;
+ size_t read_count;
+
+ if (!gfh) {
+ return NULL;
+ }
+
+ if (!has_count) {
+ count = QGA_READ_COUNT_DEFAULT;
+ } else if (count < 0) {
+ error_setg(errp, "value '%" PRId64 "' is invalid for argument count",
+ count);
+ return NULL;
+ }
+
+ fh = gfh->fh;
+
+ /* explicitly flush when switching from writing to reading */
+ if (gfh->state == RW_STATE_WRITING) {
+ int ret = fflush(fh);
+ if (ret == EOF) {
+ error_setg_errno(errp, errno, "failed to flush file");
+ return NULL;
+ }
+ gfh->state = RW_STATE_NEW;
+ }
+
+ buf = g_malloc0(count+1);
+ read_count = fread(buf, 1, count, fh);
+ if (ferror(fh)) {
+ error_setg_errno(errp, errno, "failed to read file");
+ slog("guest-file-read failed, handle: %" PRId64, handle);
+ } else {
+ buf[read_count] = 0;
+ read_data = g_new0(GuestFileRead, 1);
+ read_data->count = read_count;
+ read_data->eof = feof(fh);
+ if (read_count) {
+ read_data->buf_b64 = g_base64_encode(buf, read_count);
+ }
+ gfh->state = RW_STATE_READING;
+ }
+ g_free(buf);
+ clearerr(fh);
+
+ return read_data;
+}
+
+GuestFileWrite *qmp_guest_file_write(int64_t handle, const char *buf_b64,
+ bool has_count, int64_t count,
+ Error **errp)
+{
+ GuestFileWrite *write_data = NULL;
+ guchar *buf;
+ gsize buf_len;
+ int write_count;
+ GuestFileHandle *gfh = guest_file_handle_find(handle, errp);
+ FILE *fh;
+
+ if (!gfh) {
+ return NULL;
+ }
+
+ fh = gfh->fh;
+
+ if (gfh->state == RW_STATE_READING) {
+ int ret = fseek(fh, 0, SEEK_CUR);
+ if (ret == -1) {
+ error_setg_errno(errp, errno, "failed to seek file");
+ return NULL;
+ }
+ gfh->state = RW_STATE_NEW;
+ }
+
+ buf = g_base64_decode(buf_b64, &buf_len);
+
+ if (!has_count) {
+ count = buf_len;
+ } else if (count < 0 || count > buf_len) {
+ error_setg(errp, "value '%" PRId64 "' is invalid for argument count",
+ count);
+ g_free(buf);
+ return NULL;
+ }
+
+ write_count = fwrite(buf, 1, count, fh);
+ if (ferror(fh)) {
+ error_setg_errno(errp, errno, "failed to write to file");
+ slog("guest-file-write failed, handle: %" PRId64, handle);
+ } else {
+ write_data = g_new0(GuestFileWrite, 1);
+ write_data->count = write_count;
+ write_data->eof = feof(fh);
+ gfh->state = RW_STATE_WRITING;
+ }
+ g_free(buf);
+ clearerr(fh);
+
+ return write_data;
+}
+
+struct GuestFileSeek *qmp_guest_file_seek(int64_t handle, int64_t offset,
+ int64_t whence_code, Error **errp)
+{
+ GuestFileHandle *gfh = guest_file_handle_find(handle, errp);
+ GuestFileSeek *seek_data = NULL;
+ FILE *fh;
+ int ret;
+ int whence;
+
+ if (!gfh) {
+ return NULL;
+ }
+
+ /* We stupidly exposed 'whence':'int' in our qapi */
+ switch (whence_code) {
+ case QGA_SEEK_SET:
+ whence = SEEK_SET;
+ break;
+ case QGA_SEEK_CUR:
+ whence = SEEK_CUR;
+ break;
+ case QGA_SEEK_END:
+ whence = SEEK_END;
+ break;
+ default:
+ error_setg(errp, "invalid whence code %"PRId64, whence_code);
+ return NULL;
+ }
+
+ fh = gfh->fh;
+ ret = fseek(fh, offset, whence);
+ if (ret == -1) {
+ error_setg_errno(errp, errno, "failed to seek file");
+ if (errno == ESPIPE) {
+ /* file is non-seekable, stdio shouldn't be buffering anyways */
+ gfh->state = RW_STATE_NEW;
+ }
+ } else {
+ seek_data = g_new0(GuestFileSeek, 1);
+ seek_data->position = ftell(fh);
+ seek_data->eof = feof(fh);
+ gfh->state = RW_STATE_NEW;
+ }
+ clearerr(fh);
+
+ return seek_data;
+}
+
+void qmp_guest_file_flush(int64_t handle, Error **errp)
+{
+ GuestFileHandle *gfh = guest_file_handle_find(handle, errp);
+ FILE *fh;
+ int ret;
+
+ if (!gfh) {
+ return;
+ }
+
+ fh = gfh->fh;
+ ret = fflush(fh);
+ if (ret == EOF) {
+ error_setg_errno(errp, errno, "failed to flush file");
+ } else {
+ gfh->state = RW_STATE_NEW;
+ }
+}
+
+/* linux-specific implementations. avoid this if at all possible. */
+#if defined(__linux__)
+
+#if defined(CONFIG_FSFREEZE) || defined(CONFIG_FSTRIM)
+typedef struct FsMount {
+ char *dirname;
+ char *devtype;
+ unsigned int devmajor, devminor;
+ QTAILQ_ENTRY(FsMount) next;
+} FsMount;
+
+typedef QTAILQ_HEAD(FsMountList, FsMount) FsMountList;
+
+static void free_fs_mount_list(FsMountList *mounts)
+{
+ FsMount *mount, *temp;
+
+ if (!mounts) {
+ return;
+ }
+
+ QTAILQ_FOREACH_SAFE(mount, mounts, next, temp) {
+ QTAILQ_REMOVE(mounts, mount, next);
+ g_free(mount->dirname);
+ g_free(mount->devtype);
+ g_free(mount);
+ }
+}
+
+static int dev_major_minor(const char *devpath,
+ unsigned int *devmajor, unsigned int *devminor)
+{
+ struct stat st;
+
+ *devmajor = 0;
+ *devminor = 0;
+
+ if (stat(devpath, &st) < 0) {
+ slog("failed to stat device file '%s': %s", devpath, strerror(errno));
+ return -1;
+ }
+ if (S_ISDIR(st.st_mode)) {
+ /* It is bind mount */
+ return -2;
+ }
+ if (S_ISBLK(st.st_mode)) {
+ *devmajor = major(st.st_rdev);
+ *devminor = minor(st.st_rdev);
+ return 0;
+ }
+ return -1;
+}
+
+/*
+ * Walk the mount table and build a list of local file systems
+ */
+static void build_fs_mount_list_from_mtab(FsMountList *mounts, Error **errp)
+{
+ struct mntent *ment;
+ FsMount *mount;
+ char const *mtab = "/proc/self/mounts";
+ FILE *fp;
+ unsigned int devmajor, devminor;
+
+ fp = setmntent(mtab, "r");
+ if (!fp) {
+ error_setg(errp, "failed to open mtab file: '%s'", mtab);
+ return;
+ }
+
+ while ((ment = getmntent(fp))) {
+ /*
+ * An entry which device name doesn't start with a '/' is
+ * either a dummy file system or a network file system.
+ * Add special handling for smbfs and cifs as is done by
+ * coreutils as well.
+ */
+ if ((ment->mnt_fsname[0] != '/') ||
+ (strcmp(ment->mnt_type, "smbfs") == 0) ||
+ (strcmp(ment->mnt_type, "cifs") == 0)) {
+ continue;
+ }
+ if (dev_major_minor(ment->mnt_fsname, &devmajor, &devminor) == -2) {
+ /* Skip bind mounts */
+ continue;
+ }
+
+ mount = g_new0(FsMount, 1);
+ mount->dirname = g_strdup(ment->mnt_dir);
+ mount->devtype = g_strdup(ment->mnt_type);
+ mount->devmajor = devmajor;
+ mount->devminor = devminor;
+
+ QTAILQ_INSERT_TAIL(mounts, mount, next);
+ }
+
+ endmntent(fp);
+}
+
+static void decode_mntname(char *name, int len)
+{
+ int i, j = 0;
+ for (i = 0; i <= len; i++) {
+ if (name[i] != '\\') {
+ name[j++] = name[i];
+ } else if (name[i + 1] == '\\') {
+ name[j++] = '\\';
+ i++;
+ } else if (name[i + 1] >= '0' && name[i + 1] <= '3' &&
+ name[i + 2] >= '0' && name[i + 2] <= '7' &&
+ name[i + 3] >= '0' && name[i + 3] <= '7') {
+ name[j++] = (name[i + 1] - '0') * 64 +
+ (name[i + 2] - '0') * 8 +
+ (name[i + 3] - '0');
+ i += 3;
+ } else {
+ name[j++] = name[i];
+ }
+ }
+}
+
+static void build_fs_mount_list(FsMountList *mounts, Error **errp)
+{
+ FsMount *mount;
+ char const *mountinfo = "/proc/self/mountinfo";
+ FILE *fp;
+ char *line = NULL, *dash;
+ size_t n;
+ char check;
+ unsigned int devmajor, devminor;
+ int ret, dir_s, dir_e, type_s, type_e, dev_s, dev_e;
+
+ fp = fopen(mountinfo, "r");
+ if (!fp) {
+ build_fs_mount_list_from_mtab(mounts, errp);
+ return;
+ }
+
+ while (getline(&line, &n, fp) != -1) {
+ ret = sscanf(line, "%*u %*u %u:%u %*s %n%*s%n%c",
+ &devmajor, &devminor, &dir_s, &dir_e, &check);
+ if (ret < 3) {
+ continue;
+ }
+ dash = strstr(line + dir_e, " - ");
+ if (!dash) {
+ continue;
+ }
+ ret = sscanf(dash, " - %n%*s%n %n%*s%n%c",
+ &type_s, &type_e, &dev_s, &dev_e, &check);
+ if (ret < 1) {
+ continue;
+ }
+ line[dir_e] = 0;
+ dash[type_e] = 0;
+ dash[dev_e] = 0;
+ decode_mntname(line + dir_s, dir_e - dir_s);
+ decode_mntname(dash + dev_s, dev_e - dev_s);
+ if (devmajor == 0) {
+ /* btrfs reports major number = 0 */
+ if (strcmp("btrfs", dash + type_s) != 0 ||
+ dev_major_minor(dash + dev_s, &devmajor, &devminor) < 0) {
+ continue;
+ }
+ }
+
+ mount = g_new0(FsMount, 1);
+ mount->dirname = g_strdup(line + dir_s);
+ mount->devtype = g_strdup(dash + type_s);
+ mount->devmajor = devmajor;
+ mount->devminor = devminor;
+
+ QTAILQ_INSERT_TAIL(mounts, mount, next);
+ }
+ free(line);
+
+ fclose(fp);
+}
+#endif
+
+#if defined(CONFIG_FSFREEZE)
+
+static char *get_pci_driver(char const *syspath, int pathlen, Error **errp)
+{
+ char *path;
+ char *dpath;
+ char *driver = NULL;
+ char buf[PATH_MAX];
+ ssize_t len;
+
+ path = g_strndup(syspath, pathlen);
+ dpath = g_strdup_printf("%s/driver", path);
+ len = readlink(dpath, buf, sizeof(buf) - 1);
+ if (len != -1) {
+ buf[len] = 0;
+ driver = g_strdup(basename(buf));
+ }
+ g_free(dpath);
+ g_free(path);
+ return driver;
+}
+
+static int compare_uint(const void *_a, const void *_b)
+{
+ unsigned int a = *(unsigned int *)_a;
+ unsigned int b = *(unsigned int *)_b;
+
+ return a < b ? -1 : a > b ? 1 : 0;
+}
+
+/* Walk the specified sysfs and build a sorted list of host or ata numbers */
+static int build_hosts(char const *syspath, char const *host, bool ata,
+ unsigned int *hosts, int hosts_max, Error **errp)
+{
+ char *path;
+ DIR *dir;
+ struct dirent *entry;
+ int i = 0;
+
+ path = g_strndup(syspath, host - syspath);
+ dir = opendir(path);
+ if (!dir) {
+ error_setg_errno(errp, errno, "opendir(\"%s\")", path);
+ g_free(path);
+ return -1;
+ }
+
+ while (i < hosts_max) {
+ entry = readdir(dir);
+ if (!entry) {
+ break;
+ }
+ if (ata && sscanf(entry->d_name, "ata%d", hosts + i) == 1) {
+ ++i;
+ } else if (!ata && sscanf(entry->d_name, "host%d", hosts + i) == 1) {
+ ++i;
+ }
+ }
+
+ qsort(hosts, i, sizeof(hosts[0]), compare_uint);
+
+ g_free(path);
+ closedir(dir);
+ return i;
+}
+
+/* Store disk device info specified by @sysfs into @fs */
+static void build_guest_fsinfo_for_real_device(char const *syspath,
+ GuestFilesystemInfo *fs,
+ Error **errp)
+{
+ unsigned int pci[4], host, hosts[8], tgt[3];
+ int i, nhosts = 0, pcilen;
+ GuestDiskAddress *disk;
+ GuestPCIAddress *pciaddr;
+ GuestDiskAddressList *list = NULL;
+ bool has_ata = false, has_host = false, has_tgt = false;
+ char *p, *q, *driver = NULL;
+
+ p = strstr(syspath, "/devices/pci");
+ if (!p || sscanf(p + 12, "%*x:%*x/%x:%x:%x.%x%n",
+ pci, pci + 1, pci + 2, pci + 3, &pcilen) < 4) {
+ g_debug("only pci device is supported: sysfs path \"%s\"", syspath);
+ return;
+ }
+
+ driver = get_pci_driver(syspath, (p + 12 + pcilen) - syspath, errp);
+ if (!driver) {
+ goto cleanup;
+ }
+
+ p = strstr(syspath, "/target");
+ if (p && sscanf(p + 7, "%*u:%*u:%*u/%*u:%u:%u:%u",
+ tgt, tgt + 1, tgt + 2) == 3) {
+ has_tgt = true;
+ }
+
+ p = strstr(syspath, "/ata");
+ if (p) {
+ q = p + 4;
+ has_ata = true;
+ } else {
+ p = strstr(syspath, "/host");
+ q = p + 5;
+ }
+ if (p && sscanf(q, "%u", &host) == 1) {
+ has_host = true;
+ nhosts = build_hosts(syspath, p, has_ata, hosts,
+ sizeof(hosts) / sizeof(hosts[0]), errp);
+ if (nhosts < 0) {
+ goto cleanup;
+ }
+ }
+
+ pciaddr = g_malloc0(sizeof(*pciaddr));
+ pciaddr->domain = pci[0];
+ pciaddr->bus = pci[1];
+ pciaddr->slot = pci[2];
+ pciaddr->function = pci[3];
+
+ disk = g_malloc0(sizeof(*disk));
+ disk->pci_controller = pciaddr;
+
+ list = g_malloc0(sizeof(*list));
+ list->value = disk;
+
+ if (strcmp(driver, "ata_piix") == 0) {
+ /* a host per ide bus, target*:0:<unit>:0 */
+ if (!has_host || !has_tgt) {
+ g_debug("invalid sysfs path '%s' (driver '%s')", syspath, driver);
+ goto cleanup;
+ }
+ for (i = 0; i < nhosts; i++) {
+ if (host == hosts[i]) {
+ disk->bus_type = GUEST_DISK_BUS_TYPE_IDE;
+ disk->bus = i;
+ disk->unit = tgt[1];
+ break;
+ }
+ }
+ if (i >= nhosts) {
+ g_debug("no host for '%s' (driver '%s')", syspath, driver);
+ goto cleanup;
+ }
+ } else if (strcmp(driver, "sym53c8xx") == 0) {
+ /* scsi(LSI Logic): target*:0:<unit>:0 */
+ if (!has_tgt) {
+ g_debug("invalid sysfs path '%s' (driver '%s')", syspath, driver);
+ goto cleanup;
+ }
+ disk->bus_type = GUEST_DISK_BUS_TYPE_SCSI;
+ disk->unit = tgt[1];
+ } else if (strcmp(driver, "virtio-pci") == 0) {
+ if (has_tgt) {
+ /* virtio-scsi: target*:0:0:<unit> */
+ disk->bus_type = GUEST_DISK_BUS_TYPE_SCSI;
+ disk->unit = tgt[2];
+ } else {
+ /* virtio-blk: 1 disk per 1 device */
+ disk->bus_type = GUEST_DISK_BUS_TYPE_VIRTIO;
+ }
+ } else if (strcmp(driver, "ahci") == 0) {
+ /* ahci: 1 host per 1 unit */
+ if (!has_host || !has_tgt) {
+ g_debug("invalid sysfs path '%s' (driver '%s')", syspath, driver);
+ goto cleanup;
+ }
+ for (i = 0; i < nhosts; i++) {
+ if (host == hosts[i]) {
+ disk->unit = i;
+ disk->bus_type = GUEST_DISK_BUS_TYPE_SATA;
+ break;
+ }
+ }
+ if (i >= nhosts) {
+ g_debug("no host for '%s' (driver '%s')", syspath, driver);
+ goto cleanup;
+ }
+ } else {
+ g_debug("unknown driver '%s' (sysfs path '%s')", driver, syspath);
+ goto cleanup;
+ }
+
+ list->next = fs->disk;
+ fs->disk = list;
+ g_free(driver);
+ return;
+
+cleanup:
+ if (list) {
+ qapi_free_GuestDiskAddressList(list);
+ }
+ g_free(driver);
+}
+
+static void build_guest_fsinfo_for_device(char const *devpath,
+ GuestFilesystemInfo *fs,
+ Error **errp);
+
+/* Store a list of slave devices of virtual volume specified by @syspath into
+ * @fs */
+static void build_guest_fsinfo_for_virtual_device(char const *syspath,
+ GuestFilesystemInfo *fs,
+ Error **errp)
+{
+ DIR *dir;
+ char *dirpath;
+ struct dirent *entry;
+
+ dirpath = g_strdup_printf("%s/slaves", syspath);
+ dir = opendir(dirpath);
+ if (!dir) {
+ error_setg_errno(errp, errno, "opendir(\"%s\")", dirpath);
+ g_free(dirpath);
+ return;
+ }
+
+ for (;;) {
+ errno = 0;
+ entry = readdir(dir);
+ if (entry == NULL) {
+ if (errno) {
+ error_setg_errno(errp, errno, "readdir(\"%s\")", dirpath);
+ }
+ break;
+ }
+
+ if (entry->d_type == DT_LNK) {
+ char *path;
+
+ g_debug(" slave device '%s'", entry->d_name);
+ path = g_strdup_printf("%s/slaves/%s", syspath, entry->d_name);
+ build_guest_fsinfo_for_device(path, fs, errp);
+ g_free(path);
+
+ if (*errp) {
+ break;
+ }
+ }
+ }
+
+ g_free(dirpath);
+ closedir(dir);
+}
+
+/* Dispatch to functions for virtual/real device */
+static void build_guest_fsinfo_for_device(char const *devpath,
+ GuestFilesystemInfo *fs,
+ Error **errp)
+{
+ char *syspath = realpath(devpath, NULL);
+
+ if (!syspath) {
+ error_setg_errno(errp, errno, "realpath(\"%s\")", devpath);
+ return;
+ }
+
+ if (!fs->name) {
+ fs->name = g_strdup(basename(syspath));
+ }
+
+ g_debug(" parse sysfs path '%s'", syspath);
+
+ if (strstr(syspath, "/devices/virtual/block/")) {
+ build_guest_fsinfo_for_virtual_device(syspath, fs, errp);
+ } else {
+ build_guest_fsinfo_for_real_device(syspath, fs, errp);
+ }
+
+ free(syspath);
+}
+
+/* Return a list of the disk device(s)' info which @mount lies on */
+static GuestFilesystemInfo *build_guest_fsinfo(struct FsMount *mount,
+ Error **errp)
+{
+ GuestFilesystemInfo *fs = g_malloc0(sizeof(*fs));
+ char *devpath = g_strdup_printf("/sys/dev/block/%u:%u",
+ mount->devmajor, mount->devminor);
+
+ fs->mountpoint = g_strdup(mount->dirname);
+ fs->type = g_strdup(mount->devtype);
+ build_guest_fsinfo_for_device(devpath, fs, errp);
+
+ g_free(devpath);
+ return fs;
+}
+
+GuestFilesystemInfoList *qmp_guest_get_fsinfo(Error **errp)
+{
+ FsMountList mounts;
+ struct FsMount *mount;
+ GuestFilesystemInfoList *new, *ret = NULL;
+ Error *local_err = NULL;
+
+ QTAILQ_INIT(&mounts);
+ build_fs_mount_list(&mounts, &local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ return NULL;
+ }
+
+ QTAILQ_FOREACH(mount, &mounts, next) {
+ g_debug("Building guest fsinfo for '%s'", mount->dirname);
+
+ new = g_malloc0(sizeof(*ret));
+ new->value = build_guest_fsinfo(mount, &local_err);
+ new->next = ret;
+ ret = new;
+ if (local_err) {
+ error_propagate(errp, local_err);
+ qapi_free_GuestFilesystemInfoList(ret);
+ ret = NULL;
+ break;
+ }
+ }
+
+ free_fs_mount_list(&mounts);
+ return ret;
+}
+
+
+typedef enum {
+ FSFREEZE_HOOK_THAW = 0,
+ FSFREEZE_HOOK_FREEZE,
+} FsfreezeHookArg;
+
+static const char *fsfreeze_hook_arg_string[] = {
+ "thaw",
+ "freeze",
+};
+
+static void execute_fsfreeze_hook(FsfreezeHookArg arg, Error **errp)
+{
+ int status;
+ pid_t pid;
+ const char *hook;
+ const char *arg_str = fsfreeze_hook_arg_string[arg];
+ Error *local_err = NULL;
+
+ hook = ga_fsfreeze_hook(ga_state);
+ if (!hook) {
+ return;
+ }
+ if (access(hook, X_OK) != 0) {
+ error_setg_errno(errp, errno, "can't access fsfreeze hook '%s'", hook);
+ return;
+ }
+
+ slog("executing fsfreeze hook with arg '%s'", arg_str);
+ pid = fork();
+ if (pid == 0) {
+ setsid();
+ reopen_fd_to_null(0);
+ reopen_fd_to_null(1);
+ reopen_fd_to_null(2);
+
+ execle(hook, hook, arg_str, NULL, environ);
+ _exit(EXIT_FAILURE);
+ } else if (pid < 0) {
+ error_setg_errno(errp, errno, "failed to create child process");
+ return;
+ }
+
+ ga_wait_child(pid, &status, &local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ return;
+ }
+
+ if (!WIFEXITED(status)) {
+ error_setg(errp, "fsfreeze hook has terminated abnormally");
+ return;
+ }
+
+ status = WEXITSTATUS(status);
+ if (status) {
+ error_setg(errp, "fsfreeze hook has failed with status %d", status);
+ return;
+ }
+}
+
+/*
+ * Return status of freeze/thaw
+ */
+GuestFsfreezeStatus qmp_guest_fsfreeze_status(Error **errp)
+{
+ if (ga_is_frozen(ga_state)) {
+ return GUEST_FSFREEZE_STATUS_FROZEN;
+ }
+
+ return GUEST_FSFREEZE_STATUS_THAWED;
+}
+
+int64_t qmp_guest_fsfreeze_freeze(Error **errp)
+{
+ return qmp_guest_fsfreeze_freeze_list(false, NULL, errp);
+}
+
+/*
+ * Walk list of mounted file systems in the guest, and freeze the ones which
+ * are real local file systems.
+ */
+int64_t qmp_guest_fsfreeze_freeze_list(bool has_mountpoints,
+ strList *mountpoints,
+ Error **errp)
+{
+ int ret = 0, i = 0;
+ strList *list;
+ FsMountList mounts;
+ struct FsMount *mount;
+ Error *local_err = NULL;
+ int fd;
+
+ slog("guest-fsfreeze called");
+
+ execute_fsfreeze_hook(FSFREEZE_HOOK_FREEZE, &local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ return -1;
+ }
+
+ QTAILQ_INIT(&mounts);
+ build_fs_mount_list(&mounts, &local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ return -1;
+ }
+
+ /* cannot risk guest agent blocking itself on a write in this state */
+ ga_set_frozen(ga_state);
+
+ QTAILQ_FOREACH_REVERSE(mount, &mounts, FsMountList, next) {
+ /* To issue fsfreeze in the reverse order of mounts, check if the
+ * mount is listed in the list here */
+ if (has_mountpoints) {
+ for (list = mountpoints; list; list = list->next) {
+ if (strcmp(list->value, mount->dirname) == 0) {
+ break;
+ }
+ }
+ if (!list) {
+ continue;
+ }
+ }
+
+ fd = qemu_open(mount->dirname, O_RDONLY);
+ if (fd == -1) {
+ error_setg_errno(errp, errno, "failed to open %s", mount->dirname);
+ goto error;
+ }
+
+ /* we try to cull filesytems we know won't work in advance, but other
+ * filesytems may not implement fsfreeze for less obvious reasons.
+ * these will report EOPNOTSUPP. we simply ignore these when tallying
+ * the number of frozen filesystems.
+ *
+ * any other error means a failure to freeze a filesystem we
+ * expect to be freezable, so return an error in those cases
+ * and return system to thawed state.
+ */
+ ret = ioctl(fd, FIFREEZE);
+ if (ret == -1) {
+ if (errno != EOPNOTSUPP) {
+ error_setg_errno(errp, errno, "failed to freeze %s",
+ mount->dirname);
+ close(fd);
+ goto error;
+ }
+ } else {
+ i++;
+ }
+ close(fd);
+ }
+
+ free_fs_mount_list(&mounts);
+ return i;
+
+error:
+ free_fs_mount_list(&mounts);
+ qmp_guest_fsfreeze_thaw(NULL);
+ return 0;
+}
+
+/*
+ * Walk list of frozen file systems in the guest, and thaw them.
+ */
+int64_t qmp_guest_fsfreeze_thaw(Error **errp)
+{
+ int ret;
+ FsMountList mounts;
+ FsMount *mount;
+ int fd, i = 0, logged;
+ Error *local_err = NULL;
+
+ QTAILQ_INIT(&mounts);
+ build_fs_mount_list(&mounts, &local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ return 0;
+ }
+
+ QTAILQ_FOREACH(mount, &mounts, next) {
+ logged = false;
+ fd = qemu_open(mount->dirname, O_RDONLY);
+ if (fd == -1) {
+ continue;
+ }
+ /* we have no way of knowing whether a filesystem was actually unfrozen
+ * as a result of a successful call to FITHAW, only that if an error
+ * was returned the filesystem was *not* unfrozen by that particular
+ * call.
+ *
+ * since multiple preceding FIFREEZEs require multiple calls to FITHAW
+ * to unfreeze, continuing issuing FITHAW until an error is returned,
+ * in which case either the filesystem is in an unfreezable state, or,
+ * more likely, it was thawed previously (and remains so afterward).
+ *
+ * also, since the most recent successful call is the one that did
+ * the actual unfreeze, we can use this to provide an accurate count
+ * of the number of filesystems unfrozen by guest-fsfreeze-thaw, which
+ * may * be useful for determining whether a filesystem was unfrozen
+ * during the freeze/thaw phase by a process other than qemu-ga.
+ */
+ do {
+ ret = ioctl(fd, FITHAW);
+ if (ret == 0 && !logged) {
+ i++;
+ logged = true;
+ }
+ } while (ret == 0);
+ close(fd);
+ }
+
+ ga_unset_frozen(ga_state);
+ free_fs_mount_list(&mounts);
+
+ execute_fsfreeze_hook(FSFREEZE_HOOK_THAW, errp);
+
+ return i;
+}
+
+static void guest_fsfreeze_cleanup(void)
+{
+ Error *err = NULL;
+
+ if (ga_is_frozen(ga_state) == GUEST_FSFREEZE_STATUS_FROZEN) {
+ qmp_guest_fsfreeze_thaw(&err);
+ if (err) {
+ slog("failed to clean up frozen filesystems: %s",
+ error_get_pretty(err));
+ error_free(err);
+ }
+ }
+}
+#endif /* CONFIG_FSFREEZE */
+
+#if defined(CONFIG_FSTRIM)
+/*
+ * Walk list of mounted file systems in the guest, and trim them.
+ */
+GuestFilesystemTrimResponse *
+qmp_guest_fstrim(bool has_minimum, int64_t minimum, Error **errp)
+{
+ GuestFilesystemTrimResponse *response;
+ GuestFilesystemTrimResultList *list;
+ GuestFilesystemTrimResult *result;
+ int ret = 0;
+ FsMountList mounts;
+ struct FsMount *mount;
+ int fd;
+ Error *local_err = NULL;
+ struct fstrim_range r;
+
+ slog("guest-fstrim called");
+
+ QTAILQ_INIT(&mounts);
+ build_fs_mount_list(&mounts, &local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ return NULL;
+ }
+
+ response = g_malloc0(sizeof(*response));
+
+ QTAILQ_FOREACH(mount, &mounts, next) {
+ result = g_malloc0(sizeof(*result));
+ result->path = g_strdup(mount->dirname);
+
+ list = g_malloc0(sizeof(*list));
+ list->value = result;
+ list->next = response->paths;
+ response->paths = list;
+
+ fd = qemu_open(mount->dirname, O_RDONLY);
+ if (fd == -1) {
+ result->error = g_strdup_printf("failed to open: %s",
+ strerror(errno));
+ result->has_error = true;
+ continue;
+ }
+
+ /* We try to cull filesytems we know won't work in advance, but other
+ * filesytems may not implement fstrim for less obvious reasons. These
+ * will report EOPNOTSUPP; while in some other cases ENOTTY will be
+ * reported (e.g. CD-ROMs).
+ * Any other error means an unexpected error.
+ */
+ r.start = 0;
+ r.len = -1;
+ r.minlen = has_minimum ? minimum : 0;
+ ret = ioctl(fd, FITRIM, &r);
+ if (ret == -1) {
+ result->has_error = true;
+ if (errno == ENOTTY || errno == EOPNOTSUPP) {
+ result->error = g_strdup("trim not supported");
+ } else {
+ result->error = g_strdup_printf("failed to trim: %s",
+ strerror(errno));
+ }
+ close(fd);
+ continue;
+ }
+
+ result->has_minimum = true;
+ result->minimum = r.minlen;
+ result->has_trimmed = true;
+ result->trimmed = r.len;
+ close(fd);
+ }
+
+ free_fs_mount_list(&mounts);
+ return response;
+}
+#endif /* CONFIG_FSTRIM */
+
+
+#define LINUX_SYS_STATE_FILE "/sys/power/state"
+#define SUSPEND_SUPPORTED 0
+#define SUSPEND_NOT_SUPPORTED 1
+
+static void bios_supports_mode(const char *pmutils_bin, const char *pmutils_arg,
+ const char *sysfile_str, Error **errp)
+{
+ Error *local_err = NULL;
+ char *pmutils_path;
+ pid_t pid;
+ int status;
+
+ pmutils_path = g_find_program_in_path(pmutils_bin);
+
+ pid = fork();
+ if (!pid) {
+ char buf[32]; /* hopefully big enough */
+ ssize_t ret;
+ int fd;
+
+ setsid();
+ reopen_fd_to_null(0);
+ reopen_fd_to_null(1);
+ reopen_fd_to_null(2);
+
+ if (pmutils_path) {
+ execle(pmutils_path, pmutils_bin, pmutils_arg, NULL, environ);
+ }
+
+ /*
+ * If we get here either pm-utils is not installed or execle() has
+ * failed. Let's try the manual method if the caller wants it.
+ */
+
+ if (!sysfile_str) {
+ _exit(SUSPEND_NOT_SUPPORTED);
+ }
+
+ fd = open(LINUX_SYS_STATE_FILE, O_RDONLY);
+ if (fd < 0) {
+ _exit(SUSPEND_NOT_SUPPORTED);
+ }
+
+ ret = read(fd, buf, sizeof(buf)-1);
+ if (ret <= 0) {
+ _exit(SUSPEND_NOT_SUPPORTED);
+ }
+ buf[ret] = '\0';
+
+ if (strstr(buf, sysfile_str)) {
+ _exit(SUSPEND_SUPPORTED);
+ }
+
+ _exit(SUSPEND_NOT_SUPPORTED);
+ } else if (pid < 0) {
+ error_setg_errno(errp, errno, "failed to create child process");
+ goto out;
+ }
+
+ ga_wait_child(pid, &status, &local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ goto out;
+ }
+
+ if (!WIFEXITED(status)) {
+ error_setg(errp, "child process has terminated abnormally");
+ goto out;
+ }
+
+ switch (WEXITSTATUS(status)) {
+ case SUSPEND_SUPPORTED:
+ goto out;
+ case SUSPEND_NOT_SUPPORTED:
+ error_setg(errp,
+ "the requested suspend mode is not supported by the guest");
+ goto out;
+ default:
+ error_setg(errp,
+ "the helper program '%s' returned an unexpected exit status"
+ " code (%d)", pmutils_path, WEXITSTATUS(status));
+ goto out;
+ }
+
+out:
+ g_free(pmutils_path);
+}
+
+static void guest_suspend(const char *pmutils_bin, const char *sysfile_str,
+ Error **errp)
+{
+ Error *local_err = NULL;
+ char *pmutils_path;
+ pid_t pid;
+ int status;
+
+ pmutils_path = g_find_program_in_path(pmutils_bin);
+
+ pid = fork();
+ if (pid == 0) {
+ /* child */
+ int fd;
+
+ setsid();
+ reopen_fd_to_null(0);
+ reopen_fd_to_null(1);
+ reopen_fd_to_null(2);
+
+ if (pmutils_path) {
+ execle(pmutils_path, pmutils_bin, NULL, environ);
+ }
+
+ /*
+ * If we get here either pm-utils is not installed or execle() has
+ * failed. Let's try the manual method if the caller wants it.
+ */
+
+ if (!sysfile_str) {
+ _exit(EXIT_FAILURE);
+ }
+
+ fd = open(LINUX_SYS_STATE_FILE, O_WRONLY);
+ if (fd < 0) {
+ _exit(EXIT_FAILURE);
+ }
+
+ if (write(fd, sysfile_str, strlen(sysfile_str)) < 0) {
+ _exit(EXIT_FAILURE);
+ }
+
+ _exit(EXIT_SUCCESS);
+ } else if (pid < 0) {
+ error_setg_errno(errp, errno, "failed to create child process");
+ goto out;
+ }
+
+ ga_wait_child(pid, &status, &local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ goto out;
+ }
+
+ if (!WIFEXITED(status)) {
+ error_setg(errp, "child process has terminated abnormally");
+ goto out;
+ }
+
+ if (WEXITSTATUS(status)) {
+ error_setg(errp, "child process has failed to suspend");
+ goto out;
+ }
+
+out:
+ g_free(pmutils_path);
+}
+
+void qmp_guest_suspend_disk(Error **errp)
+{
+ Error *local_err = NULL;
+
+ bios_supports_mode("pm-is-supported", "--hibernate", "disk", &local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ return;
+ }
+
+ guest_suspend("pm-hibernate", "disk", errp);
+}
+
+void qmp_guest_suspend_ram(Error **errp)
+{
+ Error *local_err = NULL;
+
+ bios_supports_mode("pm-is-supported", "--suspend", "mem", &local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ return;
+ }
+
+ guest_suspend("pm-suspend", "mem", errp);
+}
+
+void qmp_guest_suspend_hybrid(Error **errp)
+{
+ Error *local_err = NULL;
+
+ bios_supports_mode("pm-is-supported", "--suspend-hybrid", NULL,
+ &local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ return;
+ }
+
+ guest_suspend("pm-suspend-hybrid", NULL, errp);
+}
+
+static GuestNetworkInterfaceList *
+guest_find_interface(GuestNetworkInterfaceList *head,
+ const char *name)
+{
+ for (; head; head = head->next) {
+ if (strcmp(head->value->name, name) == 0) {
+ break;
+ }
+ }
+
+ return head;
+}
+
+/*
+ * Build information about guest interfaces
+ */
+GuestNetworkInterfaceList *qmp_guest_network_get_interfaces(Error **errp)
+{
+ GuestNetworkInterfaceList *head = NULL, *cur_item = NULL;
+ struct ifaddrs *ifap, *ifa;
+
+ if (getifaddrs(&ifap) < 0) {
+ error_setg_errno(errp, errno, "getifaddrs failed");
+ goto error;
+ }
+
+ for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
+ GuestNetworkInterfaceList *info;
+ GuestIpAddressList **address_list = NULL, *address_item = NULL;
+ char addr4[INET_ADDRSTRLEN];
+ char addr6[INET6_ADDRSTRLEN];
+ int sock;
+ struct ifreq ifr;
+ unsigned char *mac_addr;
+ void *p;
+
+ g_debug("Processing %s interface", ifa->ifa_name);
+
+ info = guest_find_interface(head, ifa->ifa_name);
+
+ if (!info) {
+ info = g_malloc0(sizeof(*info));
+ info->value = g_malloc0(sizeof(*info->value));
+ info->value->name = g_strdup(ifa->ifa_name);
+
+ if (!cur_item) {
+ head = cur_item = info;
+ } else {
+ cur_item->next = info;
+ cur_item = info;
+ }
+ }
+
+ if (!info->value->has_hardware_address &&
+ ifa->ifa_flags & SIOCGIFHWADDR) {
+ /* we haven't obtained HW address yet */
+ sock = socket(PF_INET, SOCK_STREAM, 0);
+ if (sock == -1) {
+ error_setg_errno(errp, errno, "failed to create socket");
+ goto error;
+ }
+
+ memset(&ifr, 0, sizeof(ifr));
+ pstrcpy(ifr.ifr_name, IF_NAMESIZE, info->value->name);
+ if (ioctl(sock, SIOCGIFHWADDR, &ifr) == -1) {
+ error_setg_errno(errp, errno,
+ "failed to get MAC address of %s",
+ ifa->ifa_name);
+ close(sock);
+ goto error;
+ }
+
+ close(sock);
+ mac_addr = (unsigned char *) &ifr.ifr_hwaddr.sa_data;
+
+ info->value->hardware_address =
+ g_strdup_printf("%02x:%02x:%02x:%02x:%02x:%02x",
+ (int) mac_addr[0], (int) mac_addr[1],
+ (int) mac_addr[2], (int) mac_addr[3],
+ (int) mac_addr[4], (int) mac_addr[5]);
+
+ info->value->has_hardware_address = true;
+ }
+
+ if (ifa->ifa_addr &&
+ ifa->ifa_addr->sa_family == AF_INET) {
+ /* interface with IPv4 address */
+ p = &((struct sockaddr_in *)ifa->ifa_addr)->sin_addr;
+ if (!inet_ntop(AF_INET, p, addr4, sizeof(addr4))) {
+ error_setg_errno(errp, errno, "inet_ntop failed");
+ goto error;
+ }
+
+ address_item = g_malloc0(sizeof(*address_item));
+ address_item->value = g_malloc0(sizeof(*address_item->value));
+ address_item->value->ip_address = g_strdup(addr4);
+ address_item->value->ip_address_type = GUEST_IP_ADDRESS_TYPE_IPV4;
+
+ if (ifa->ifa_netmask) {
+ /* Count the number of set bits in netmask.
+ * This is safe as '1' and '0' cannot be shuffled in netmask. */
+ p = &((struct sockaddr_in *)ifa->ifa_netmask)->sin_addr;
+ address_item->value->prefix = ctpop32(((uint32_t *) p)[0]);
+ }
+ } else if (ifa->ifa_addr &&
+ ifa->ifa_addr->sa_family == AF_INET6) {
+ /* interface with IPv6 address */
+ p = &((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr;
+ if (!inet_ntop(AF_INET6, p, addr6, sizeof(addr6))) {
+ error_setg_errno(errp, errno, "inet_ntop failed");
+ goto error;
+ }
+
+ address_item = g_malloc0(sizeof(*address_item));
+ address_item->value = g_malloc0(sizeof(*address_item->value));
+ address_item->value->ip_address = g_strdup(addr6);
+ address_item->value->ip_address_type = GUEST_IP_ADDRESS_TYPE_IPV6;
+
+ if (ifa->ifa_netmask) {
+ /* Count the number of set bits in netmask.
+ * This is safe as '1' and '0' cannot be shuffled in netmask. */
+ p = &((struct sockaddr_in6 *)ifa->ifa_netmask)->sin6_addr;
+ address_item->value->prefix =
+ ctpop32(((uint32_t *) p)[0]) +
+ ctpop32(((uint32_t *) p)[1]) +
+ ctpop32(((uint32_t *) p)[2]) +
+ ctpop32(((uint32_t *) p)[3]);
+ }
+ }
+
+ if (!address_item) {
+ continue;
+ }
+
+ address_list = &info->value->ip_addresses;
+
+ while (*address_list && (*address_list)->next) {
+ address_list = &(*address_list)->next;
+ }
+
+ if (!*address_list) {
+ *address_list = address_item;
+ } else {
+ (*address_list)->next = address_item;
+ }
+
+ info->value->has_ip_addresses = true;
+
+
+ }
+
+ freeifaddrs(ifap);
+ return head;
+
+error:
+ freeifaddrs(ifap);
+ qapi_free_GuestNetworkInterfaceList(head);
+ return NULL;
+}
+
+#define SYSCONF_EXACT(name, errp) sysconf_exact((name), #name, (errp))
+
+static long sysconf_exact(int name, const char *name_str, Error **errp)
+{
+ long ret;
+
+ errno = 0;
+ ret = sysconf(name);
+ if (ret == -1) {
+ if (errno == 0) {
+ error_setg(errp, "sysconf(%s): value indefinite", name_str);
+ } else {
+ error_setg_errno(errp, errno, "sysconf(%s)", name_str);
+ }
+ }
+ return ret;
+}
+
+/* Transfer online/offline status between @vcpu and the guest system.
+ *
+ * On input either @errp or *@errp must be NULL.
+ *
+ * In system-to-@vcpu direction, the following @vcpu fields are accessed:
+ * - R: vcpu->logical_id
+ * - W: vcpu->online
+ * - W: vcpu->can_offline
+ *
+ * In @vcpu-to-system direction, the following @vcpu fields are accessed:
+ * - R: vcpu->logical_id
+ * - R: vcpu->online
+ *
+ * Written members remain unmodified on error.
+ */
+static void transfer_vcpu(GuestLogicalProcessor *vcpu, bool sys2vcpu,
+ Error **errp)
+{
+ char *dirpath;
+ int dirfd;
+
+ 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 */
+
+ res = close(fd);
+ g_assert(res == 0);
+ }
+
+ res = close(dirfd);
+ g_assert(res == 0);
+ }
+
+ g_free(dirpath);
+}
+
+GuestLogicalProcessorList *qmp_guest_get_vcpus(Error **errp)
+{
+ int64_t current;
+ GuestLogicalProcessorList *head, **link;
+ long sc_max;
+ Error *local_err = NULL;
+
+ current = 0;
+ head = NULL;
+ link = &head;
+ sc_max = SYSCONF_EXACT(_SC_NPROCESSORS_CONF, &local_err);
+
+ 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;
+ }
+
+ if (local_err == NULL) {
+ /* there's no guest with zero VCPUs */
+ g_assert(head != NULL);
+ return head;
+ }
+
+ qapi_free_GuestLogicalProcessorList(head);
+ error_propagate(errp, local_err);
+ return NULL;
+}
+
+int64_t qmp_guest_set_vcpus(GuestLogicalProcessorList *vcpus, Error **errp)
+{
+ int64_t processed;
+ Error *local_err = NULL;
+
+ processed = 0;
+ while (vcpus != NULL) {
+ transfer_vcpu(vcpus->value, false, &local_err);
+ if (local_err != NULL) {
+ break;
+ }
+ ++processed;
+ vcpus = vcpus->next;
+ }
+
+ if (local_err != NULL) {
+ if (processed == 0) {
+ error_propagate(errp, local_err);
+ } else {
+ error_free(local_err);
+ }
+ }
+
+ return processed;
+}
+
+void qmp_guest_set_user_password(const char *username,
+ const char *password,
+ bool crypted,
+ Error **errp)
+{
+ Error *local_err = NULL;
+ char *passwd_path = NULL;
+ pid_t pid;
+ int status;
+ int datafd[2] = { -1, -1 };
+ char *rawpasswddata = NULL;
+ size_t rawpasswdlen;
+ char *chpasswddata = NULL;
+ size_t chpasswdlen;
+
+ rawpasswddata = (char *)g_base64_decode(password, &rawpasswdlen);
+ rawpasswddata = g_renew(char, rawpasswddata, rawpasswdlen + 1);
+ rawpasswddata[rawpasswdlen] = '\0';
+
+ if (strchr(rawpasswddata, '\n')) {
+ error_setg(errp, "forbidden characters in raw password");
+ goto out;
+ }
+
+ if (strchr(username, '\n') ||
+ strchr(username, ':')) {
+ error_setg(errp, "forbidden characters in username");
+ goto out;
+ }
+
+ chpasswddata = g_strdup_printf("%s:%s\n", username, rawpasswddata);
+ chpasswdlen = strlen(chpasswddata);
+
+ passwd_path = g_find_program_in_path("chpasswd");
+
+ if (!passwd_path) {
+ error_setg(errp, "cannot find 'passwd' program in PATH");
+ goto out;
+ }
+
+ if (pipe(datafd) < 0) {
+ error_setg(errp, "cannot create pipe FDs");
+ goto out;
+ }
+
+ pid = fork();
+ if (pid == 0) {
+ close(datafd[1]);
+ /* child */
+ setsid();
+ dup2(datafd[0], 0);
+ reopen_fd_to_null(1);
+ reopen_fd_to_null(2);
+
+ if (crypted) {
+ execle(passwd_path, "chpasswd", "-e", NULL, environ);
+ } else {
+ execle(passwd_path, "chpasswd", NULL, environ);
+ }
+ _exit(EXIT_FAILURE);
+ } else if (pid < 0) {
+ error_setg_errno(errp, errno, "failed to create child process");
+ goto out;
+ }
+ close(datafd[0]);
+ datafd[0] = -1;
+
+ if (qemu_write_full(datafd[1], chpasswddata, chpasswdlen) != chpasswdlen) {
+ error_setg_errno(errp, errno, "cannot write new account password");
+ goto out;
+ }
+ close(datafd[1]);
+ datafd[1] = -1;
+
+ ga_wait_child(pid, &status, &local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ goto out;
+ }
+
+ if (!WIFEXITED(status)) {
+ error_setg(errp, "child process has terminated abnormally");
+ goto out;
+ }
+
+ if (WEXITSTATUS(status)) {
+ error_setg(errp, "child process has failed to set user password");
+ goto out;
+ }
+
+out:
+ g_free(chpasswddata);
+ g_free(rawpasswddata);
+ g_free(passwd_path);
+ if (datafd[0] != -1) {
+ close(datafd[0]);
+ }
+ if (datafd[1] != -1) {
+ close(datafd[1]);
+ }
+}
+
+static void ga_read_sysfs_file(int dirfd, const char *pathname, char *buf,
+ int size, Error **errp)
+{
+ int fd;
+ int res;
+
+ errno = 0;
+ fd = openat(dirfd, pathname, O_RDONLY);
+ if (fd == -1) {
+ error_setg_errno(errp, errno, "open sysfs file \"%s\"", pathname);
+ return;
+ }
+
+ res = pread(fd, buf, size, 0);
+ if (res == -1) {
+ error_setg_errno(errp, errno, "pread sysfs file \"%s\"", pathname);
+ } else if (res == 0) {
+ error_setg(errp, "pread sysfs file \"%s\": unexpected EOF", pathname);
+ }
+ close(fd);
+}
+
+static void ga_write_sysfs_file(int dirfd, const char *pathname,
+ const char *buf, int size, Error **errp)
+{
+ int fd;
+
+ errno = 0;
+ fd = openat(dirfd, pathname, O_WRONLY);
+ if (fd == -1) {
+ error_setg_errno(errp, errno, "open sysfs file \"%s\"", pathname);
+ return;
+ }
+
+ if (pwrite(fd, buf, size, 0) == -1) {
+ error_setg_errno(errp, errno, "pwrite sysfs file \"%s\"", pathname);
+ }
+
+ close(fd);
+}
+
+/* Transfer online/offline status between @mem_blk and the guest system.
+ *
+ * On input either @errp or *@errp must be NULL.
+ *
+ * In system-to-@mem_blk direction, the following @mem_blk fields are accessed:
+ * - R: mem_blk->phys_index
+ * - W: mem_blk->online
+ * - W: mem_blk->can_offline
+ *
+ * In @mem_blk-to-system direction, the following @mem_blk fields are accessed:
+ * - R: mem_blk->phys_index
+ * - R: mem_blk->online
+ *- R: mem_blk->can_offline
+ * Written members remain unmodified on error.
+ */
+static void transfer_memory_block(GuestMemoryBlock *mem_blk, bool sys2memblk,
+ GuestMemoryBlockResponse *result,
+ Error **errp)
+{
+ char *dirpath;
+ int dirfd;
+ char *status;
+ Error *local_err = NULL;
+
+ if (!sys2memblk) {
+ DIR *dp;
+
+ if (!result) {
+ error_setg(errp, "Internal error, 'result' should not be NULL");
+ return;
+ }
+ errno = 0;
+ dp = opendir("/sys/devices/system/memory/");
+ /* if there is no 'memory' directory in sysfs,
+ * we think this VM does not support online/offline memory block,
+ * any other solution?
+ */
+ if (!dp && errno == ENOENT) {
+ result->response =
+ GUEST_MEMORY_BLOCK_RESPONSE_TYPE_OPERATION_NOT_SUPPORTED;
+ goto out1;
+ }
+ closedir(dp);
+ }
+
+ dirpath = g_strdup_printf("/sys/devices/system/memory/memory%" PRId64 "/",
+ mem_blk->phys_index);
+ dirfd = open(dirpath, O_RDONLY | O_DIRECTORY);
+ if (dirfd == -1) {
+ if (sys2memblk) {
+ error_setg_errno(errp, errno, "open(\"%s\")", dirpath);
+ } else {
+ if (errno == ENOENT) {
+ result->response = GUEST_MEMORY_BLOCK_RESPONSE_TYPE_NOT_FOUND;
+ } else {
+ result->response =
+ GUEST_MEMORY_BLOCK_RESPONSE_TYPE_OPERATION_FAILED;
+ }
+ }
+ g_free(dirpath);
+ goto out1;
+ }
+ g_free(dirpath);
+
+ status = g_malloc0(10);
+ ga_read_sysfs_file(dirfd, "state", status, 10, &local_err);
+ if (local_err) {
+ /* treat with sysfs file that not exist in old kernel */
+ if (errno == ENOENT) {
+ error_free(local_err);
+ if (sys2memblk) {
+ mem_blk->online = true;
+ mem_blk->can_offline = false;
+ } else if (!mem_blk->online) {
+ result->response =
+ GUEST_MEMORY_BLOCK_RESPONSE_TYPE_OPERATION_NOT_SUPPORTED;
+ }
+ } else {
+ if (sys2memblk) {
+ error_propagate(errp, local_err);
+ } else {
+ result->response =
+ GUEST_MEMORY_BLOCK_RESPONSE_TYPE_OPERATION_FAILED;
+ }
+ }
+ goto out2;
+ }
+
+ if (sys2memblk) {
+ char removable = '0';
+
+ mem_blk->online = (strncmp(status, "online", 6) == 0);
+
+ ga_read_sysfs_file(dirfd, "removable", &removable, 1, &local_err);
+ if (local_err) {
+ /* if no 'removable' file, it doesn't support offline mem blk */
+ if (errno == ENOENT) {
+ error_free(local_err);
+ mem_blk->can_offline = false;
+ } else {
+ error_propagate(errp, local_err);
+ }
+ } else {
+ mem_blk->can_offline = (removable != '0');
+ }
+ } else {
+ if (mem_blk->online != (strncmp(status, "online", 6) == 0)) {
+ char *new_state = mem_blk->online ? g_strdup("online") :
+ g_strdup("offline");
+
+ ga_write_sysfs_file(dirfd, "state", new_state, strlen(new_state),
+ &local_err);
+ g_free(new_state);
+ if (local_err) {
+ error_free(local_err);
+ result->response =
+ GUEST_MEMORY_BLOCK_RESPONSE_TYPE_OPERATION_FAILED;
+ goto out2;
+ }
+
+ result->response = GUEST_MEMORY_BLOCK_RESPONSE_TYPE_SUCCESS;
+ result->has_error_code = false;
+ } /* otherwise pretend successful re-(on|off)-lining */
+ }
+ g_free(status);
+ close(dirfd);
+ return;
+
+out2:
+ g_free(status);
+ close(dirfd);
+out1:
+ if (!sys2memblk) {
+ result->has_error_code = true;
+ result->error_code = errno;
+ }
+}
+
+GuestMemoryBlockList *qmp_guest_get_memory_blocks(Error **errp)
+{
+ GuestMemoryBlockList *head, **link;
+ Error *local_err = NULL;
+ struct dirent *de;
+ DIR *dp;
+
+ head = NULL;
+ link = &head;
+
+ dp = opendir("/sys/devices/system/memory/");
+ if (!dp) {
+ /* it's ok if this happens to be a system that doesn't expose
+ * memory blocks via sysfs, but otherwise we should report
+ * an error
+ */
+ if (errno != ENOENT) {
+ error_setg_errno(errp, errno, "Can't open directory"
+ "\"/sys/devices/system/memory/\"\n");
+ }
+ return NULL;
+ }
+
+ /* Note: the phys_index of memory block may be discontinuous,
+ * this is because a memblk is the unit of the Sparse Memory design, which
+ * allows discontinuous memory ranges (ex. NUMA), so here we should
+ * traverse the memory block directory.
+ */
+ while ((de = readdir(dp)) != NULL) {
+ GuestMemoryBlock *mem_blk;
+ GuestMemoryBlockList *entry;
+
+ if ((strncmp(de->d_name, "memory", 6) != 0) ||
+ !(de->d_type & DT_DIR)) {
+ continue;
+ }
+
+ mem_blk = g_malloc0(sizeof *mem_blk);
+ /* The d_name is "memoryXXX", phys_index is block id, same as XXX */
+ mem_blk->phys_index = strtoul(&de->d_name[6], NULL, 10);
+ mem_blk->has_can_offline = true; /* lolspeak ftw */
+ transfer_memory_block(mem_blk, true, NULL, &local_err);
+
+ entry = g_malloc0(sizeof *entry);
+ entry->value = mem_blk;
+
+ *link = entry;
+ link = &entry->next;
+ }
+
+ closedir(dp);
+ if (local_err == NULL) {
+ /* there's no guest with zero memory blocks */
+ if (head == NULL) {
+ error_setg(errp, "guest reported zero memory blocks!");
+ }
+ return head;
+ }
+
+ qapi_free_GuestMemoryBlockList(head);
+ error_propagate(errp, local_err);
+ return NULL;
+}
+
+GuestMemoryBlockResponseList *
+qmp_guest_set_memory_blocks(GuestMemoryBlockList *mem_blks, Error **errp)
+{
+ GuestMemoryBlockResponseList *head, **link;
+ Error *local_err = NULL;
+
+ head = NULL;
+ link = &head;
+
+ while (mem_blks != NULL) {
+ GuestMemoryBlockResponse *result;
+ GuestMemoryBlockResponseList *entry;
+ GuestMemoryBlock *current_mem_blk = mem_blks->value;
+
+ result = g_malloc0(sizeof(*result));
+ result->phys_index = current_mem_blk->phys_index;
+ transfer_memory_block(current_mem_blk, false, result, &local_err);
+ if (local_err) { /* should never happen */
+ goto err;
+ }
+ entry = g_malloc0(sizeof *entry);
+ entry->value = result;
+
+ *link = entry;
+ link = &entry->next;
+ mem_blks = mem_blks->next;
+ }
+
+ return head;
+err:
+ qapi_free_GuestMemoryBlockResponseList(head);
+ error_propagate(errp, local_err);
+ return NULL;
+}
+
+GuestMemoryBlockInfo *qmp_guest_get_memory_block_info(Error **errp)
+{
+ Error *local_err = NULL;
+ char *dirpath;
+ int dirfd;
+ char *buf;
+ GuestMemoryBlockInfo *info;
+
+ dirpath = g_strdup_printf("/sys/devices/system/memory/");
+ dirfd = open(dirpath, O_RDONLY | O_DIRECTORY);
+ if (dirfd == -1) {
+ error_setg_errno(errp, errno, "open(\"%s\")", dirpath);
+ g_free(dirpath);
+ return NULL;
+ }
+ g_free(dirpath);
+
+ buf = g_malloc0(20);
+ ga_read_sysfs_file(dirfd, "block_size_bytes", buf, 20, &local_err);
+ close(dirfd);
+ if (local_err) {
+ g_free(buf);
+ error_propagate(errp, local_err);
+ return NULL;
+ }
+
+ info = g_new0(GuestMemoryBlockInfo, 1);
+ info->size = strtol(buf, NULL, 16); /* the unit is bytes */
+
+ g_free(buf);
+
+ return info;
+}
+
+#else /* defined(__linux__) */
+
+void qmp_guest_suspend_disk(Error **errp)
+{
+ error_setg(errp, QERR_UNSUPPORTED);
+}
+
+void qmp_guest_suspend_ram(Error **errp)
+{
+ error_setg(errp, QERR_UNSUPPORTED);
+}
+
+void qmp_guest_suspend_hybrid(Error **errp)
+{
+ error_setg(errp, QERR_UNSUPPORTED);
+}
+
+GuestNetworkInterfaceList *qmp_guest_network_get_interfaces(Error **errp)
+{
+ error_setg(errp, QERR_UNSUPPORTED);
+ return NULL;
+}
+
+GuestLogicalProcessorList *qmp_guest_get_vcpus(Error **errp)
+{
+ error_setg(errp, QERR_UNSUPPORTED);
+ return NULL;
+}
+
+int64_t qmp_guest_set_vcpus(GuestLogicalProcessorList *vcpus, Error **errp)
+{
+ error_setg(errp, QERR_UNSUPPORTED);
+ return -1;
+}
+
+void qmp_guest_set_user_password(const char *username,
+ const char *password,
+ bool crypted,
+ Error **errp)
+{
+ error_setg(errp, QERR_UNSUPPORTED);
+}
+
+GuestMemoryBlockList *qmp_guest_get_memory_blocks(Error **errp)
+{
+ error_setg(errp, QERR_UNSUPPORTED);
+ return NULL;
+}
+
+GuestMemoryBlockResponseList *
+qmp_guest_set_memory_blocks(GuestMemoryBlockList *mem_blks, Error **errp)
+{
+ error_setg(errp, QERR_UNSUPPORTED);
+ return NULL;
+}
+
+GuestMemoryBlockInfo *qmp_guest_get_memory_block_info(Error **errp)
+{
+ error_setg(errp, QERR_UNSUPPORTED);
+ return NULL;
+}
+
+#endif
+
+#if !defined(CONFIG_FSFREEZE)
+
+GuestFilesystemInfoList *qmp_guest_get_fsinfo(Error **errp)
+{
+ error_setg(errp, QERR_UNSUPPORTED);
+ return NULL;
+}
+
+GuestFsfreezeStatus qmp_guest_fsfreeze_status(Error **errp)
+{
+ error_setg(errp, QERR_UNSUPPORTED);
+
+ return 0;
+}
+
+int64_t qmp_guest_fsfreeze_freeze(Error **errp)
+{
+ error_setg(errp, QERR_UNSUPPORTED);
+
+ return 0;
+}
+
+int64_t qmp_guest_fsfreeze_freeze_list(bool has_mountpoints,
+ strList *mountpoints,
+ Error **errp)
+{
+ error_setg(errp, QERR_UNSUPPORTED);
+
+ return 0;
+}
+
+int64_t qmp_guest_fsfreeze_thaw(Error **errp)
+{
+ error_setg(errp, QERR_UNSUPPORTED);
+
+ return 0;
+}
+#endif /* CONFIG_FSFREEZE */
+
+#if !defined(CONFIG_FSTRIM)
+GuestFilesystemTrimResponse *
+qmp_guest_fstrim(bool has_minimum, int64_t minimum, Error **errp)
+{
+ error_setg(errp, QERR_UNSUPPORTED);
+ return NULL;
+}
+#endif
+
+/* add unsupported commands to the blacklist */
+GList *ga_command_blacklist_init(GList *blacklist)
+{
+#if !defined(__linux__)
+ {
+ const char *list[] = {
+ "guest-suspend-disk", "guest-suspend-ram",
+ "guest-suspend-hybrid", "guest-network-get-interfaces",
+ "guest-get-vcpus", "guest-set-vcpus",
+ "guest-get-memory-blocks", "guest-set-memory-blocks",
+ "guest-get-memory-block-size", NULL};
+ char **p = (char **)list;
+
+ while (*p) {
+ blacklist = g_list_append(blacklist, g_strdup(*p++));
+ }
+ }
+#endif
+
+#if !defined(CONFIG_FSFREEZE)
+ {
+ const char *list[] = {
+ "guest-get-fsinfo", "guest-fsfreeze-status",
+ "guest-fsfreeze-freeze", "guest-fsfreeze-freeze-list",
+ "guest-fsfreeze-thaw", "guest-get-fsinfo", NULL};
+ char **p = (char **)list;
+
+ while (*p) {
+ blacklist = g_list_append(blacklist, g_strdup(*p++));
+ }
+ }
+#endif
+
+#if !defined(CONFIG_FSTRIM)
+ blacklist = g_list_append(blacklist, g_strdup("guest-fstrim"));
+#endif
+
+ return blacklist;
+}
+
+/* register init/cleanup routines for stateful command groups */
+void ga_command_state_init(GAState *s, GACommandState *cs)
+{
+#if defined(CONFIG_FSFREEZE)
+ ga_command_state_add(cs, NULL, guest_fsfreeze_cleanup);
+#endif
+}
diff --git a/src/qga/commands-win32.c b/src/qga/commands-win32.c
new file mode 100644
index 0000000..0654fe4
--- /dev/null
+++ b/src/qga/commands-win32.c
@@ -0,0 +1,1376 @@
+/*
+ * QEMU Guest Agent win32-specific command implementations
+ *
+ * Copyright IBM Corp. 2012
+ *
+ * Authors:
+ * Michael Roth <mdroth@linux.vnet.ibm.com>
+ * Gal Hammer <ghammer@redhat.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include <glib.h>
+#include <wtypes.h>
+#include <powrprof.h>
+#include <stdio.h>
+#include <string.h>
+#include <winsock2.h>
+#include <ws2tcpip.h>
+#include <iptypes.h>
+#include <iphlpapi.h>
+#ifdef CONFIG_QGA_NTDDSCSI
+#include <winioctl.h>
+#include <ntddscsi.h>
+#include <setupapi.h>
+#include <initguid.h>
+#endif
+#include <lm.h>
+
+#include "qga/guest-agent-core.h"
+#include "qga/vss-win32.h"
+#include "qga-qmp-commands.h"
+#include "qapi/qmp/qerror.h"
+#include "qemu/queue.h"
+#include "qemu/host-utils.h"
+
+#ifndef SHTDN_REASON_FLAG_PLANNED
+#define SHTDN_REASON_FLAG_PLANNED 0x80000000
+#endif
+
+/* multiple of 100 nanoseconds elapsed between windows baseline
+ * (1/1/1601) and Unix Epoch (1/1/1970), accounting for leap years */
+#define W32_FT_OFFSET (10000000ULL * 60 * 60 * 24 * \
+ (365 * (1970 - 1601) + \
+ (1970 - 1601) / 4 - 3))
+
+#define INVALID_SET_FILE_POINTER ((DWORD)-1)
+
+typedef struct GuestFileHandle {
+ int64_t id;
+ HANDLE fh;
+ QTAILQ_ENTRY(GuestFileHandle) next;
+} GuestFileHandle;
+
+static struct {
+ QTAILQ_HEAD(, GuestFileHandle) filehandles;
+} guest_file_state = {
+ .filehandles = QTAILQ_HEAD_INITIALIZER(guest_file_state.filehandles),
+};
+
+#define FILE_GENERIC_APPEND (FILE_GENERIC_WRITE & ~FILE_WRITE_DATA)
+
+typedef struct OpenFlags {
+ const char *forms;
+ DWORD desired_access;
+ DWORD creation_disposition;
+} OpenFlags;
+static OpenFlags guest_file_open_modes[] = {
+ {"r", GENERIC_READ, OPEN_EXISTING},
+ {"rb", GENERIC_READ, OPEN_EXISTING},
+ {"w", GENERIC_WRITE, CREATE_ALWAYS},
+ {"wb", GENERIC_WRITE, CREATE_ALWAYS},
+ {"a", FILE_GENERIC_APPEND, OPEN_ALWAYS },
+ {"r+", GENERIC_WRITE|GENERIC_READ, OPEN_EXISTING},
+ {"rb+", GENERIC_WRITE|GENERIC_READ, OPEN_EXISTING},
+ {"r+b", GENERIC_WRITE|GENERIC_READ, OPEN_EXISTING},
+ {"w+", GENERIC_WRITE|GENERIC_READ, CREATE_ALWAYS},
+ {"wb+", GENERIC_WRITE|GENERIC_READ, CREATE_ALWAYS},
+ {"w+b", GENERIC_WRITE|GENERIC_READ, CREATE_ALWAYS},
+ {"a+", FILE_GENERIC_APPEND|GENERIC_READ, OPEN_ALWAYS },
+ {"ab+", FILE_GENERIC_APPEND|GENERIC_READ, OPEN_ALWAYS },
+ {"a+b", FILE_GENERIC_APPEND|GENERIC_READ, OPEN_ALWAYS }
+};
+
+static OpenFlags *find_open_flag(const char *mode_str)
+{
+ int mode;
+ Error **errp = NULL;
+
+ for (mode = 0; mode < ARRAY_SIZE(guest_file_open_modes); ++mode) {
+ OpenFlags *flags = guest_file_open_modes + mode;
+
+ if (strcmp(flags->forms, mode_str) == 0) {
+ return flags;
+ }
+ }
+
+ error_setg(errp, "invalid file open mode '%s'", mode_str);
+ return NULL;
+}
+
+static int64_t guest_file_handle_add(HANDLE fh, Error **errp)
+{
+ GuestFileHandle *gfh;
+ int64_t handle;
+
+ handle = ga_get_fd_handle(ga_state, errp);
+ if (handle < 0) {
+ return -1;
+ }
+ gfh = g_new0(GuestFileHandle, 1);
+ gfh->id = handle;
+ gfh->fh = fh;
+ QTAILQ_INSERT_TAIL(&guest_file_state.filehandles, gfh, next);
+
+ return handle;
+}
+
+static GuestFileHandle *guest_file_handle_find(int64_t id, Error **errp)
+{
+ GuestFileHandle *gfh;
+ QTAILQ_FOREACH(gfh, &guest_file_state.filehandles, next) {
+ if (gfh->id == id) {
+ return gfh;
+ }
+ }
+ error_setg(errp, "handle '%" PRId64 "' has not been found", id);
+ return NULL;
+}
+
+static void handle_set_nonblocking(HANDLE fh)
+{
+ DWORD file_type, pipe_state;
+ file_type = GetFileType(fh);
+ if (file_type != FILE_TYPE_PIPE) {
+ return;
+ }
+ /* If file_type == FILE_TYPE_PIPE, according to MSDN
+ * the specified file is socket or named pipe */
+ if (!GetNamedPipeHandleState(fh, &pipe_state, NULL,
+ NULL, NULL, NULL, 0)) {
+ return;
+ }
+ /* The fd is named pipe fd */
+ if (pipe_state & PIPE_NOWAIT) {
+ return;
+ }
+
+ pipe_state |= PIPE_NOWAIT;
+ SetNamedPipeHandleState(fh, &pipe_state, NULL, NULL);
+}
+
+int64_t qmp_guest_file_open(const char *path, bool has_mode,
+ const char *mode, Error **errp)
+{
+ int64_t fd;
+ 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;
+
+ if (!has_mode) {
+ mode = "r";
+ }
+ slog("guest-file-open called, filepath: %s, mode: %s", path, mode);
+ guest_flags = find_open_flag(mode);
+ if (guest_flags == NULL) {
+ error_setg(errp, "invalid file open mode");
+ return -1;
+ }
+
+ fh = CreateFile(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;
+ }
+
+ /* set fd non-blocking to avoid common use cases (like reading from a
+ * named pipe) from hanging the agent
+ */
+ handle_set_nonblocking(fh);
+
+ fd = guest_file_handle_add(fh, errp);
+ if (fd < 0) {
+ CloseHandle(fh);
+ error_setg(errp, "failed to add handle to qmp handle table");
+ return -1;
+ }
+
+ slog("guest-file-open, handle: % " PRId64, fd);
+ return fd;
+}
+
+void qmp_guest_file_close(int64_t handle, Error **errp)
+{
+ bool ret;
+ GuestFileHandle *gfh = guest_file_handle_find(handle, errp);
+ slog("guest-file-close called, handle: %" PRId64, handle);
+ if (gfh == NULL) {
+ return;
+ }
+ ret = CloseHandle(gfh->fh);
+ if (!ret) {
+ error_setg_win32(errp, GetLastError(), "failed close handle");
+ return;
+ }
+
+ QTAILQ_REMOVE(&guest_file_state.filehandles, gfh, next);
+ g_free(gfh);
+}
+
+static void acquire_privilege(const char *name, Error **errp)
+{
+ HANDLE token = NULL;
+ TOKEN_PRIVILEGES priv;
+ Error *local_err = NULL;
+
+ if (OpenProcessToken(GetCurrentProcess(),
+ TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY, &token))
+ {
+ if (!LookupPrivilegeValue(NULL, name, &priv.Privileges[0].Luid)) {
+ error_setg(&local_err, QERR_QGA_COMMAND_FAILED,
+ "no luid for requested privilege");
+ goto out;
+ }
+
+ priv.PrivilegeCount = 1;
+ priv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
+
+ if (!AdjustTokenPrivileges(token, FALSE, &priv, 0, NULL, 0)) {
+ error_setg(&local_err, QERR_QGA_COMMAND_FAILED,
+ "unable to acquire requested privilege");
+ goto out;
+ }
+
+ } else {
+ error_setg(&local_err, QERR_QGA_COMMAND_FAILED,
+ "failed to open privilege token");
+ }
+
+out:
+ if (token) {
+ CloseHandle(token);
+ }
+ if (local_err) {
+ error_propagate(errp, local_err);
+ }
+}
+
+static void execute_async(DWORD WINAPI (*func)(LPVOID), LPVOID opaque,
+ Error **errp)
+{
+ Error *local_err = NULL;
+
+ HANDLE thread = CreateThread(NULL, 0, func, opaque, 0, NULL);
+ if (!thread) {
+ error_setg(&local_err, QERR_QGA_COMMAND_FAILED,
+ "failed to dispatch asynchronous command");
+ error_propagate(errp, local_err);
+ }
+}
+
+void qmp_guest_shutdown(bool has_mode, const char *mode, Error **errp)
+{
+ Error *local_err = NULL;
+ UINT shutdown_flag = EWX_FORCE;
+
+ slog("guest-shutdown called, mode: %s", mode);
+
+ if (!has_mode || strcmp(mode, "powerdown") == 0) {
+ shutdown_flag |= EWX_POWEROFF;
+ } else if (strcmp(mode, "halt") == 0) {
+ shutdown_flag |= EWX_SHUTDOWN;
+ } else if (strcmp(mode, "reboot") == 0) {
+ shutdown_flag |= EWX_REBOOT;
+ } else {
+ error_setg(errp, QERR_INVALID_PARAMETER_VALUE, "mode",
+ "halt|powerdown|reboot");
+ return;
+ }
+
+ /* Request a shutdown privilege, but try to shut down the system
+ anyway. */
+ acquire_privilege(SE_SHUTDOWN_NAME, &local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ return;
+ }
+
+ if (!ExitWindowsEx(shutdown_flag, SHTDN_REASON_FLAG_PLANNED)) {
+ slog("guest-shutdown failed: %lu", GetLastError());
+ error_setg(errp, QERR_UNDEFINED_ERROR);
+ }
+}
+
+GuestFileRead *qmp_guest_file_read(int64_t handle, bool has_count,
+ int64_t count, Error **errp)
+{
+ GuestFileRead *read_data = NULL;
+ guchar *buf;
+ HANDLE fh;
+ bool is_ok;
+ DWORD read_count;
+ GuestFileHandle *gfh = guest_file_handle_find(handle, errp);
+
+ if (!gfh) {
+ return NULL;
+ }
+ if (!has_count) {
+ count = QGA_READ_COUNT_DEFAULT;
+ } else if (count < 0) {
+ error_setg(errp, "value '%" PRId64
+ "' is invalid for argument count", count);
+ return NULL;
+ }
+
+ fh = gfh->fh;
+ buf = g_malloc0(count+1);
+ is_ok = ReadFile(fh, buf, count, &read_count, NULL);
+ if (!is_ok) {
+ error_setg_win32(errp, GetLastError(), "failed to read file");
+ slog("guest-file-read failed, handle %" PRId64, handle);
+ } else {
+ buf[read_count] = 0;
+ read_data = g_new0(GuestFileRead, 1);
+ read_data->count = (size_t)read_count;
+ read_data->eof = read_count == 0;
+
+ if (read_count != 0) {
+ read_data->buf_b64 = g_base64_encode(buf, read_count);
+ }
+ }
+ g_free(buf);
+
+ return read_data;
+}
+
+GuestFileWrite *qmp_guest_file_write(int64_t handle, const char *buf_b64,
+ bool has_count, int64_t count,
+ Error **errp)
+{
+ GuestFileWrite *write_data = NULL;
+ guchar *buf;
+ gsize buf_len;
+ bool is_ok;
+ DWORD write_count;
+ GuestFileHandle *gfh = guest_file_handle_find(handle, errp);
+ HANDLE fh;
+
+ if (!gfh) {
+ return NULL;
+ }
+ fh = gfh->fh;
+ buf = g_base64_decode(buf_b64, &buf_len);
+
+ if (!has_count) {
+ count = buf_len;
+ } else if (count < 0 || count > buf_len) {
+ error_setg(errp, "value '%" PRId64
+ "' is invalid for argument count", count);
+ goto done;
+ }
+
+ is_ok = WriteFile(fh, buf, count, &write_count, NULL);
+ if (!is_ok) {
+ error_setg_win32(errp, GetLastError(), "failed to write to file");
+ slog("guest-file-write-failed, handle: %" PRId64, handle);
+ } else {
+ write_data = g_new0(GuestFileWrite, 1);
+ write_data->count = (size_t) write_count;
+ }
+
+done:
+ g_free(buf);
+ return write_data;
+}
+
+GuestFileSeek *qmp_guest_file_seek(int64_t handle, int64_t offset,
+ int64_t whence_code, Error **errp)
+{
+ GuestFileHandle *gfh;
+ GuestFileSeek *seek_data;
+ HANDLE fh;
+ LARGE_INTEGER new_pos, off_pos;
+ off_pos.QuadPart = offset;
+ BOOL res;
+ int whence;
+
+ gfh = guest_file_handle_find(handle, errp);
+ if (!gfh) {
+ return NULL;
+ }
+
+ /* We stupidly exposed 'whence':'int' in our qapi */
+ switch (whence_code) {
+ case QGA_SEEK_SET:
+ whence = SEEK_SET;
+ break;
+ case QGA_SEEK_CUR:
+ whence = SEEK_CUR;
+ break;
+ case QGA_SEEK_END:
+ whence = SEEK_END;
+ break;
+ default:
+ error_setg(errp, "invalid whence code %"PRId64, whence_code);
+ return NULL;
+ }
+
+ fh = gfh->fh;
+ res = SetFilePointerEx(fh, off_pos, &new_pos, whence);
+ if (!res) {
+ error_setg_win32(errp, GetLastError(), "failed to seek file");
+ return NULL;
+ }
+ seek_data = g_new0(GuestFileSeek, 1);
+ seek_data->position = new_pos.QuadPart;
+ return seek_data;
+}
+
+void qmp_guest_file_flush(int64_t handle, Error **errp)
+{
+ HANDLE fh;
+ GuestFileHandle *gfh = guest_file_handle_find(handle, errp);
+ if (!gfh) {
+ return;
+ }
+
+ fh = gfh->fh;
+ if (!FlushFileBuffers(fh)) {
+ error_setg_win32(errp, GetLastError(), "failed to flush file");
+ }
+}
+
+#ifdef CONFIG_QGA_NTDDSCSI
+
+static STORAGE_BUS_TYPE win2qemu[] = {
+ [BusTypeUnknown] = GUEST_DISK_BUS_TYPE_UNKNOWN,
+ [BusTypeScsi] = GUEST_DISK_BUS_TYPE_SCSI,
+ [BusTypeAtapi] = GUEST_DISK_BUS_TYPE_IDE,
+ [BusTypeAta] = GUEST_DISK_BUS_TYPE_IDE,
+ [BusType1394] = GUEST_DISK_BUS_TYPE_IEEE1394,
+ [BusTypeSsa] = GUEST_DISK_BUS_TYPE_SSA,
+ [BusTypeFibre] = GUEST_DISK_BUS_TYPE_SSA,
+ [BusTypeUsb] = GUEST_DISK_BUS_TYPE_USB,
+ [BusTypeRAID] = GUEST_DISK_BUS_TYPE_RAID,
+#if (_WIN32_WINNT >= 0x0600)
+ [BusTypeiScsi] = GUEST_DISK_BUS_TYPE_ISCSI,
+ [BusTypeSas] = GUEST_DISK_BUS_TYPE_SAS,
+ [BusTypeSata] = GUEST_DISK_BUS_TYPE_SATA,
+ [BusTypeSd] = GUEST_DISK_BUS_TYPE_SD,
+ [BusTypeMmc] = GUEST_DISK_BUS_TYPE_MMC,
+#endif
+#if (_WIN32_WINNT >= 0x0601)
+ [BusTypeVirtual] = GUEST_DISK_BUS_TYPE_VIRTUAL,
+ [BusTypeFileBackedVirtual] = GUEST_DISK_BUS_TYPE_FILE_BACKED_VIRTUAL,
+#endif
+};
+
+static GuestDiskBusType find_bus_type(STORAGE_BUS_TYPE bus)
+{
+ if (bus > ARRAY_SIZE(win2qemu) || (int)bus < 0) {
+ return GUEST_DISK_BUS_TYPE_UNKNOWN;
+ }
+ return win2qemu[(int)bus];
+}
+
+DEFINE_GUID(GUID_DEVINTERFACE_VOLUME,
+ 0x53f5630dL, 0xb6bf, 0x11d0, 0x94, 0xf2,
+ 0x00, 0xa0, 0xc9, 0x1e, 0xfb, 0x8b);
+
+static GuestPCIAddress *get_pci_info(char *guid, Error **errp)
+{
+ HDEVINFO dev_info;
+ SP_DEVINFO_DATA dev_info_data;
+ DWORD size = 0;
+ int i;
+ char dev_name[MAX_PATH];
+ char *buffer = NULL;
+ GuestPCIAddress *pci = NULL;
+ char *name = g_strdup(&guid[4]);
+
+ 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,
+ DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
+ if (dev_info == INVALID_HANDLE_VALUE) {
+ error_setg_win32(errp, GetLastError(), "failed to get devices tree");
+ goto out;
+ }
+
+ 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;
+ while (!SetupDiGetDeviceRegistryProperty(dev_info, &dev_info_data,
+ SPDRP_PHYSICAL_DEVICE_OBJECT_NAME,
+ &data, (PBYTE)buffer, size,
+ &size2)) {
+ size = MAX(size, size2);
+ if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
+ g_free(buffer);
+ /* Double the size to avoid problems on
+ * W2k MBCS systems per KB 888609.
+ * https://support.microsoft.com/en-us/kb/259695 */
+ buffer = g_malloc(size * 2);
+ } else {
+ error_setg_win32(errp, GetLastError(),
+ "failed to get device name");
+ goto out;
+ }
+ }
+
+ if (g_strcmp0(buffer, dev_name)) {
+ continue;
+ }
+
+ /* There is no need to allocate buffer in the next functions. The size
+ * is known and ULONG according to
+ * https://support.microsoft.com/en-us/kb/253232
+ * https://msdn.microsoft.com/en-us/library/windows/hardware/ff543095(v=vs.85).aspx
+ */
+ if (!SetupDiGetDeviceRegistryProperty(dev_info, &dev_info_data,
+ SPDRP_BUSNUMBER, &data, (PBYTE)&bus, size, NULL)) {
+ break;
+ }
+
+ /* 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;
+ }
+
+ /* 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;
+ }
+
+ /* SetupApi gives us the same information as driver with
+ * IoGetDeviceProperty. According to Microsoft
+ * https://support.microsoft.com/en-us/kb/253232
+ * FunctionNumber = (USHORT)((propertyAddress) & 0x0000FFFF);
+ * DeviceNumber = (USHORT)(((propertyAddress) >> 16) & 0x0000FFFF);
+ * SPDRP_ADDRESS is propertyAddress, so we do the same.*/
+
+ func = addr & 0x0000FFFF;
+ dev = (addr >> 16) & 0x0000FFFF;
+ pci = g_malloc0(sizeof(*pci));
+ pci->domain = dev;
+ pci->slot = slot;
+ pci->function = func;
+ pci->bus = bus;
+ break;
+ }
+out:
+ g_free(buffer);
+ g_free(name);
+ return pci;
+}
+
+static int get_disk_bus_type(HANDLE vol_h, Error **errp)
+{
+ STORAGE_PROPERTY_QUERY query;
+ STORAGE_DEVICE_DESCRIPTOR *dev_desc, buf;
+ DWORD received;
+
+ 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)) {
+ error_setg_win32(errp, GetLastError(), "failed to get bus type");
+ return -1;
+ }
+
+ return dev_desc->BusType;
+}
+
+/* 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)
+{
+ GuestDiskAddressList *list = NULL;
+ GuestDiskAddress *disk;
+ SCSI_ADDRESS addr, *scsi_ad;
+ DWORD len;
+ int bus;
+ HANDLE vol_h;
+
+ scsi_ad = &addr;
+ char *name = g_strndup(guid, strlen(guid)-1);
+
+ 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_free;
+ }
+
+ bus = get_disk_bus_type(vol_h, errp);
+ if (bus < 0) {
+ goto out_close;
+ }
+
+ disk = g_malloc0(sizeof(*disk));
+ disk->bus_type = find_bus_type(bus);
+ if (bus == BusTypeScsi || bus == BusTypeAta || bus == BusTypeRAID
+#if (_WIN32_WINNT >= 0x0600)
+ /* This bus type is not supported before Windows Server 2003 SP1 */
+ || bus == BusTypeSas
+#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,
+ 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:
+ g_free(name);
+ return list;
+}
+
+#else
+
+static GuestDiskAddressList *build_guest_disk_info(char *guid, Error **errp)
+{
+ return NULL;
+}
+
+#endif /* CONFIG_QGA_NTDDSCSI */
+
+static GuestFilesystemInfo *build_guest_fsinfo(char *guid, Error **errp)
+{
+ DWORD info_size;
+ char mnt, *mnt_point;
+ char fs_name[32];
+ char vol_info[MAX_PATH+1];
+ size_t len;
+ GuestFilesystemInfo *fs = NULL;
+
+ GetVolumePathNamesForVolumeName(guid, (LPCH)&mnt, 0, &info_size);
+ if (GetLastError() != ERROR_MORE_DATA) {
+ error_setg_win32(errp, GetLastError(), "failed to get volume name");
+ return NULL;
+ }
+
+ mnt_point = g_malloc(info_size + 1);
+ if (!GetVolumePathNamesForVolumeName(guid, mnt_point, info_size,
+ &info_size)) {
+ error_setg_win32(errp, GetLastError(), "failed to get volume name");
+ goto free;
+ }
+
+ len = strlen(mnt_point);
+ mnt_point[len] = '\\';
+ mnt_point[len+1] = 0;
+ if (!GetVolumeInformation(mnt_point, vol_info, sizeof(vol_info), NULL, NULL,
+ NULL, (LPSTR)&fs_name, sizeof(fs_name))) {
+ if (GetLastError() != ERROR_NOT_READY) {
+ error_setg_win32(errp, GetLastError(), "failed to get volume info");
+ }
+ goto free;
+ }
+
+ fs_name[sizeof(fs_name) - 1] = 0;
+ fs = g_malloc(sizeof(*fs));
+ fs->name = g_strdup(guid);
+ if (len == 0) {
+ fs->mountpoint = g_strdup("System Reserved");
+ } else {
+ fs->mountpoint = g_strndup(mnt_point, len);
+ }
+ fs->type = g_strdup(fs_name);
+ fs->disk = build_guest_disk_info(guid, errp);
+free:
+ g_free(mnt_point);
+ return fs;
+}
+
+GuestFilesystemInfoList *qmp_guest_get_fsinfo(Error **errp)
+{
+ HANDLE vol_h;
+ GuestFilesystemInfoList *new, *ret = NULL;
+ char guid[256];
+
+ vol_h = FindFirstVolume(guid, sizeof(guid));
+ if (vol_h == INVALID_HANDLE_VALUE) {
+ error_setg_win32(errp, GetLastError(), "failed to find any volume");
+ return NULL;
+ }
+
+ do {
+ GuestFilesystemInfo *info = build_guest_fsinfo(guid, errp);
+ if (info == NULL) {
+ continue;
+ }
+ new = g_malloc(sizeof(*ret));
+ new->value = info;
+ new->next = ret;
+ ret = new;
+ } while (FindNextVolume(vol_h, guid, sizeof(guid)));
+
+ if (GetLastError() != ERROR_NO_MORE_FILES) {
+ error_setg_win32(errp, GetLastError(), "failed to find next volume");
+ }
+
+ FindVolumeClose(vol_h);
+ return ret;
+}
+
+/*
+ * Return status of freeze/thaw
+ */
+GuestFsfreezeStatus qmp_guest_fsfreeze_status(Error **errp)
+{
+ if (!vss_initialized()) {
+ error_setg(errp, QERR_UNSUPPORTED);
+ return 0;
+ }
+
+ if (ga_is_frozen(ga_state)) {
+ return GUEST_FSFREEZE_STATUS_FROZEN;
+ }
+
+ return GUEST_FSFREEZE_STATUS_THAWED;
+}
+
+/*
+ * Freeze local file systems using Volume Shadow-copy Service.
+ * The frozen state is limited for up to 10 seconds by VSS.
+ */
+int64_t qmp_guest_fsfreeze_freeze(Error **errp)
+{
+ int i;
+ Error *local_err = NULL;
+
+ if (!vss_initialized()) {
+ error_setg(errp, QERR_UNSUPPORTED);
+ return 0;
+ }
+
+ slog("guest-fsfreeze called");
+
+ /* cannot risk guest agent blocking itself on a write in this state */
+ ga_set_frozen(ga_state);
+
+ qga_vss_fsfreeze(&i, &local_err, true);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ goto error;
+ }
+
+ return i;
+
+error:
+ local_err = NULL;
+ qmp_guest_fsfreeze_thaw(&local_err);
+ if (local_err) {
+ g_debug("cleanup thaw: %s", error_get_pretty(local_err));
+ error_free(local_err);
+ }
+ 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.
+ */
+int64_t qmp_guest_fsfreeze_thaw(Error **errp)
+{
+ int i;
+
+ if (!vss_initialized()) {
+ error_setg(errp, QERR_UNSUPPORTED);
+ return 0;
+ }
+
+ qga_vss_fsfreeze(&i, errp, false);
+
+ ga_unset_frozen(ga_state);
+ return i;
+}
+
+static void guest_fsfreeze_cleanup(void)
+{
+ Error *err = NULL;
+
+ if (!vss_initialized()) {
+ return;
+ }
+
+ if (ga_is_frozen(ga_state) == GUEST_FSFREEZE_STATUS_FROZEN) {
+ qmp_guest_fsfreeze_thaw(&err);
+ if (err) {
+ slog("failed to clean up frozen filesystems: %s",
+ error_get_pretty(err));
+ error_free(err);
+ }
+ }
+
+ vss_deinit(true);
+}
+
+/*
+ * Walk list of mounted file systems in the guest, and discard unused
+ * areas.
+ */
+GuestFilesystemTrimResponse *
+qmp_guest_fstrim(bool has_minimum, int64_t minimum, Error **errp)
+{
+ error_setg(errp, QERR_UNSUPPORTED);
+ return NULL;
+}
+
+typedef enum {
+ GUEST_SUSPEND_MODE_DISK,
+ GUEST_SUSPEND_MODE_RAM
+} GuestSuspendMode;
+
+static void check_suspend_mode(GuestSuspendMode mode, Error **errp)
+{
+ SYSTEM_POWER_CAPABILITIES sys_pwr_caps;
+ Error *local_err = NULL;
+
+ ZeroMemory(&sys_pwr_caps, sizeof(sys_pwr_caps));
+ if (!GetPwrCapabilities(&sys_pwr_caps)) {
+ error_setg(&local_err, QERR_QGA_COMMAND_FAILED,
+ "failed to determine guest suspend capabilities");
+ goto out;
+ }
+
+ switch (mode) {
+ case GUEST_SUSPEND_MODE_DISK:
+ if (!sys_pwr_caps.SystemS4) {
+ error_setg(&local_err, QERR_QGA_COMMAND_FAILED,
+ "suspend-to-disk not supported by OS");
+ }
+ break;
+ case GUEST_SUSPEND_MODE_RAM:
+ if (!sys_pwr_caps.SystemS3) {
+ error_setg(&local_err, QERR_QGA_COMMAND_FAILED,
+ "suspend-to-ram not supported by OS");
+ }
+ break;
+ default:
+ error_setg(&local_err, QERR_INVALID_PARAMETER_VALUE, "mode",
+ "GuestSuspendMode");
+ }
+
+out:
+ if (local_err) {
+ error_propagate(errp, local_err);
+ }
+}
+
+static DWORD WINAPI do_suspend(LPVOID opaque)
+{
+ GuestSuspendMode *mode = opaque;
+ DWORD ret = 0;
+
+ if (!SetSuspendState(*mode == GUEST_SUSPEND_MODE_DISK, TRUE, TRUE)) {
+ slog("failed to suspend guest, %lu", GetLastError());
+ ret = -1;
+ }
+ g_free(mode);
+ return ret;
+}
+
+void qmp_guest_suspend_disk(Error **errp)
+{
+ Error *local_err = NULL;
+ GuestSuspendMode *mode = g_new(GuestSuspendMode, 1);
+
+ *mode = GUEST_SUSPEND_MODE_DISK;
+ check_suspend_mode(*mode, &local_err);
+ acquire_privilege(SE_SHUTDOWN_NAME, &local_err);
+ execute_async(do_suspend, mode, &local_err);
+
+ if (local_err) {
+ error_propagate(errp, local_err);
+ g_free(mode);
+ }
+}
+
+void qmp_guest_suspend_ram(Error **errp)
+{
+ Error *local_err = NULL;
+ GuestSuspendMode *mode = g_new(GuestSuspendMode, 1);
+
+ *mode = GUEST_SUSPEND_MODE_RAM;
+ check_suspend_mode(*mode, &local_err);
+ acquire_privilege(SE_SHUTDOWN_NAME, &local_err);
+ execute_async(do_suspend, mode, &local_err);
+
+ if (local_err) {
+ error_propagate(errp, local_err);
+ g_free(mode);
+ }
+}
+
+void qmp_guest_suspend_hybrid(Error **errp)
+{
+ error_setg(errp, QERR_UNSUPPORTED);
+}
+
+static IP_ADAPTER_ADDRESSES *guest_get_adapters_addresses(Error **errp)
+{
+ IP_ADAPTER_ADDRESSES *adptr_addrs = NULL;
+ ULONG adptr_addrs_len = 0;
+ DWORD ret;
+
+ /* Call the first time to get the adptr_addrs_len. */
+ GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_INCLUDE_PREFIX,
+ NULL, adptr_addrs, &adptr_addrs_len);
+
+ adptr_addrs = g_malloc(adptr_addrs_len);
+ ret = GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_INCLUDE_PREFIX,
+ NULL, adptr_addrs, &adptr_addrs_len);
+ if (ret != ERROR_SUCCESS) {
+ error_setg_win32(errp, ret, "failed to get adapters addresses");
+ g_free(adptr_addrs);
+ adptr_addrs = NULL;
+ }
+ return adptr_addrs;
+}
+
+static char *guest_wctomb_dup(WCHAR *wstr)
+{
+ char *str;
+ size_t i;
+
+ i = wcslen(wstr) + 1;
+ str = g_malloc(i);
+ WideCharToMultiByte(CP_ACP, WC_COMPOSITECHECK,
+ wstr, -1, str, i, NULL, NULL);
+ return str;
+}
+
+static char *guest_addr_to_str(IP_ADAPTER_UNICAST_ADDRESS *ip_addr,
+ Error **errp)
+{
+ char addr_str[INET6_ADDRSTRLEN + INET_ADDRSTRLEN];
+ DWORD len;
+ int ret;
+
+ if (ip_addr->Address.lpSockaddr->sa_family == AF_INET ||
+ ip_addr->Address.lpSockaddr->sa_family == AF_INET6) {
+ len = sizeof(addr_str);
+ ret = WSAAddressToString(ip_addr->Address.lpSockaddr,
+ ip_addr->Address.iSockaddrLength,
+ NULL,
+ addr_str,
+ &len);
+ if (ret != 0) {
+ error_setg_win32(errp, WSAGetLastError(),
+ "failed address presentation form conversion");
+ return NULL;
+ }
+ return g_strdup(addr_str);
+ }
+ return NULL;
+}
+
+#if (_WIN32_WINNT >= 0x0600)
+static int64_t guest_ip_prefix(IP_ADAPTER_UNICAST_ADDRESS *ip_addr)
+{
+ /* For Windows Vista/2008 and newer, use the OnLinkPrefixLength
+ * field to obtain the prefix.
+ */
+ return ip_addr->OnLinkPrefixLength;
+}
+#else
+/* When using the Windows XP and 2003 build environment, do the best we can to
+ * figure out the prefix.
+ */
+static IP_ADAPTER_INFO *guest_get_adapters_info(void)
+{
+ IP_ADAPTER_INFO *adptr_info = NULL;
+ ULONG adptr_info_len = 0;
+ DWORD ret;
+
+ /* Call the first time to get the adptr_info_len. */
+ GetAdaptersInfo(adptr_info, &adptr_info_len);
+
+ adptr_info = g_malloc(adptr_info_len);
+ ret = GetAdaptersInfo(adptr_info, &adptr_info_len);
+ if (ret != ERROR_SUCCESS) {
+ g_free(adptr_info);
+ adptr_info = NULL;
+ }
+ return adptr_info;
+}
+
+static int64_t guest_ip_prefix(IP_ADAPTER_UNICAST_ADDRESS *ip_addr)
+{
+ int64_t prefix = -1; /* Use for AF_INET6 and unknown/undetermined values. */
+ IP_ADAPTER_INFO *adptr_info, *info;
+ IP_ADDR_STRING *ip;
+ struct in_addr *p;
+
+ if (ip_addr->Address.lpSockaddr->sa_family != AF_INET) {
+ return prefix;
+ }
+ adptr_info = guest_get_adapters_info();
+ if (adptr_info == NULL) {
+ return prefix;
+ }
+
+ /* Match up the passed in ip_addr with one found in adaptr_info.
+ * The matching one in adptr_info will have the netmask.
+ */
+ p = &((struct sockaddr_in *)ip_addr->Address.lpSockaddr)->sin_addr;
+ for (info = adptr_info; info; info = info->Next) {
+ for (ip = &info->IpAddressList; ip; ip = ip->Next) {
+ if (p->S_un.S_addr == inet_addr(ip->IpAddress.String)) {
+ prefix = ctpop32(inet_addr(ip->IpMask.String));
+ goto out;
+ }
+ }
+ }
+out:
+ g_free(adptr_info);
+ return prefix;
+}
+#endif
+
+GuestNetworkInterfaceList *qmp_guest_network_get_interfaces(Error **errp)
+{
+ IP_ADAPTER_ADDRESSES *adptr_addrs, *addr;
+ IP_ADAPTER_UNICAST_ADDRESS *ip_addr = NULL;
+ GuestNetworkInterfaceList *head = NULL, *cur_item = NULL;
+ GuestIpAddressList *head_addr, *cur_addr;
+ GuestNetworkInterfaceList *info;
+ GuestIpAddressList *address_item = NULL;
+ unsigned char *mac_addr;
+ char *addr_str;
+ WORD wsa_version;
+ WSADATA wsa_data;
+ int ret;
+
+ adptr_addrs = guest_get_adapters_addresses(errp);
+ if (adptr_addrs == NULL) {
+ return NULL;
+ }
+
+ /* Make WSA APIs available. */
+ wsa_version = MAKEWORD(2, 2);
+ ret = WSAStartup(wsa_version, &wsa_data);
+ if (ret != 0) {
+ error_setg_win32(errp, ret, "failed socket startup");
+ goto out;
+ }
+
+ for (addr = adptr_addrs; addr; addr = addr->Next) {
+ info = g_malloc0(sizeof(*info));
+
+ if (cur_item == NULL) {
+ head = cur_item = info;
+ } else {
+ cur_item->next = info;
+ cur_item = info;
+ }
+
+ info->value = g_malloc0(sizeof(*info->value));
+ info->value->name = guest_wctomb_dup(addr->FriendlyName);
+
+ if (addr->PhysicalAddressLength != 0) {
+ mac_addr = addr->PhysicalAddress;
+
+ info->value->hardware_address =
+ g_strdup_printf("%02x:%02x:%02x:%02x:%02x:%02x",
+ (int) mac_addr[0], (int) mac_addr[1],
+ (int) mac_addr[2], (int) mac_addr[3],
+ (int) mac_addr[4], (int) mac_addr[5]);
+
+ info->value->has_hardware_address = true;
+ }
+
+ head_addr = NULL;
+ cur_addr = NULL;
+ for (ip_addr = addr->FirstUnicastAddress;
+ ip_addr;
+ ip_addr = ip_addr->Next) {
+ addr_str = guest_addr_to_str(ip_addr, errp);
+ if (addr_str == NULL) {
+ continue;
+ }
+
+ address_item = g_malloc0(sizeof(*address_item));
+
+ if (!cur_addr) {
+ head_addr = cur_addr = address_item;
+ } else {
+ cur_addr->next = address_item;
+ cur_addr = address_item;
+ }
+
+ address_item->value = g_malloc0(sizeof(*address_item->value));
+ address_item->value->ip_address = addr_str;
+ address_item->value->prefix = guest_ip_prefix(ip_addr);
+ if (ip_addr->Address.lpSockaddr->sa_family == AF_INET) {
+ address_item->value->ip_address_type =
+ GUEST_IP_ADDRESS_TYPE_IPV4;
+ } else if (ip_addr->Address.lpSockaddr->sa_family == AF_INET6) {
+ address_item->value->ip_address_type =
+ GUEST_IP_ADDRESS_TYPE_IPV6;
+ }
+ }
+ if (head_addr) {
+ info->value->has_ip_addresses = true;
+ info->value->ip_addresses = head_addr;
+ }
+ }
+ WSACleanup();
+out:
+ g_free(adptr_addrs);
+ return head;
+}
+
+int64_t qmp_guest_get_time(Error **errp)
+{
+ SYSTEMTIME ts = {0};
+ int64_t time_ns;
+ FILETIME tf;
+
+ GetSystemTime(&ts);
+ if (ts.wYear < 1601 || ts.wYear > 30827) {
+ error_setg(errp, "Failed to get time");
+ return -1;
+ }
+
+ if (!SystemTimeToFileTime(&ts, &tf)) {
+ error_setg(errp, "Failed to convert system time: %d", (int)GetLastError());
+ return -1;
+ }
+
+ time_ns = ((((int64_t)tf.dwHighDateTime << 32) | tf.dwLowDateTime)
+ - W32_FT_OFFSET) * 100;
+
+ return time_ns;
+}
+
+void qmp_guest_set_time(bool has_time, int64_t time_ns, Error **errp)
+{
+ Error *local_err = NULL;
+ SYSTEMTIME ts;
+ FILETIME tf;
+ LONGLONG time;
+
+ if (!has_time) {
+ /* Unfortunately, Windows libraries don't provide an easy way to access
+ * RTC yet:
+ *
+ * https://msdn.microsoft.com/en-us/library/aa908981.aspx
+ */
+ error_setg(errp, "Time argument is required on this platform");
+ return;
+ }
+
+ /* Validate time passed by user. */
+ if (time_ns < 0 || time_ns / 100 > INT64_MAX - W32_FT_OFFSET) {
+ error_setg(errp, "Time %" PRId64 "is invalid", time_ns);
+ return;
+ }
+
+ time = time_ns / 100 + W32_FT_OFFSET;
+
+ tf.dwLowDateTime = (DWORD) time;
+ tf.dwHighDateTime = (DWORD) (time >> 32);
+
+ if (!FileTimeToSystemTime(&tf, &ts)) {
+ error_setg(errp, "Failed to convert system time %d",
+ (int)GetLastError());
+ return;
+ }
+
+ acquire_privilege(SE_SYSTEMTIME_NAME, &local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ return;
+ }
+
+ if (!SetSystemTime(&ts)) {
+ error_setg(errp, "Failed to set time to guest: %d", (int)GetLastError());
+ return;
+ }
+}
+
+GuestLogicalProcessorList *qmp_guest_get_vcpus(Error **errp)
+{
+ error_setg(errp, QERR_UNSUPPORTED);
+ return NULL;
+}
+
+int64_t qmp_guest_set_vcpus(GuestLogicalProcessorList *vcpus, Error **errp)
+{
+ error_setg(errp, QERR_UNSUPPORTED);
+ return -1;
+}
+
+static gchar *
+get_net_error_message(gint error)
+{
+ HMODULE module = NULL;
+ gchar *retval = NULL;
+ wchar_t *msg = NULL;
+ int flags, nchars;
+
+ flags = FORMAT_MESSAGE_ALLOCATE_BUFFER
+ |FORMAT_MESSAGE_IGNORE_INSERTS
+ |FORMAT_MESSAGE_FROM_SYSTEM;
+
+ if (error >= NERR_BASE && error <= MAX_NERR) {
+ module = LoadLibraryExW(L"netmsg.dll", NULL, LOAD_LIBRARY_AS_DATAFILE);
+
+ if (module != NULL) {
+ flags |= FORMAT_MESSAGE_FROM_HMODULE;
+ }
+ }
+
+ FormatMessageW(flags, module, error, 0, (LPWSTR)&msg, 0, NULL);
+
+ if (msg != NULL) {
+ nchars = wcslen(msg);
+
+ if (nchars > 2 && msg[nchars-1] == '\n' && msg[nchars-2] == '\r') {
+ msg[nchars-2] = '\0';
+ }
+
+ retval = g_utf16_to_utf8(msg, -1, NULL, NULL, NULL);
+
+ LocalFree(msg);
+ }
+
+ if (module != NULL) {
+ FreeLibrary(module);
+ }
+
+ return retval;
+}
+
+void qmp_guest_set_user_password(const char *username,
+ const char *password,
+ bool crypted,
+ Error **errp)
+{
+ NET_API_STATUS nas;
+ char *rawpasswddata = NULL;
+ size_t rawpasswdlen;
+ wchar_t *user, *wpass;
+ USER_INFO_1003 pi1003 = { 0, };
+
+ if (crypted) {
+ error_setg(errp, QERR_UNSUPPORTED);
+ return;
+ }
+
+ rawpasswddata = (char *)g_base64_decode(password, &rawpasswdlen);
+ rawpasswddata = g_renew(char, rawpasswddata, rawpasswdlen + 1);
+ rawpasswddata[rawpasswdlen] = '\0';
+
+ user = g_utf8_to_utf16(username, -1, NULL, NULL, NULL);
+ wpass = g_utf8_to_utf16(rawpasswddata, -1, NULL, NULL, NULL);
+
+ pi1003.usri1003_password = wpass;
+ nas = NetUserSetInfo(NULL, user,
+ 1003, (LPBYTE)&pi1003,
+ NULL);
+
+ if (nas != NERR_Success) {
+ gchar *msg = get_net_error_message(nas);
+ error_setg(errp, "failed to set password: %s", msg);
+ g_free(msg);
+ }
+
+ g_free(user);
+ g_free(wpass);
+ g_free(rawpasswddata);
+}
+
+GuestMemoryBlockList *qmp_guest_get_memory_blocks(Error **errp)
+{
+ error_setg(errp, QERR_UNSUPPORTED);
+ return NULL;
+}
+
+GuestMemoryBlockResponseList *
+qmp_guest_set_memory_blocks(GuestMemoryBlockList *mem_blks, Error **errp)
+{
+ error_setg(errp, QERR_UNSUPPORTED);
+ return NULL;
+}
+
+GuestMemoryBlockInfo *qmp_guest_get_memory_block_info(Error **errp)
+{
+ error_setg(errp, QERR_UNSUPPORTED);
+ return NULL;
+}
+
+/* add unsupported commands to the blacklist */
+GList *ga_command_blacklist_init(GList *blacklist)
+{
+ const char *list_unsupported[] = {
+ "guest-suspend-hybrid",
+ "guest-get-vcpus", "guest-set-vcpus",
+ "guest-get-memory-blocks", "guest-set-memory-blocks",
+ "guest-get-memory-block-size",
+ "guest-fsfreeze-freeze-list",
+ "guest-fstrim", NULL};
+ char **p = (char **)list_unsupported;
+
+ while (*p) {
+ blacklist = g_list_append(blacklist, g_strdup(*p++));
+ }
+
+ if (!vss_init(true)) {
+ g_debug("vss_init failed, vss commands are going to be disabled");
+ const char *list[] = {
+ "guest-get-fsinfo", "guest-fsfreeze-status",
+ "guest-fsfreeze-freeze", "guest-fsfreeze-thaw", NULL};
+ p = (char **)list;
+
+ while (*p) {
+ blacklist = g_list_append(blacklist, g_strdup(*p++));
+ }
+ }
+
+ return blacklist;
+}
+
+/* register init/cleanup routines for stateful command groups */
+void ga_command_state_init(GAState *s, GACommandState *cs)
+{
+ if (!vss_initialized()) {
+ ga_command_state_add(cs, NULL, guest_fsfreeze_cleanup);
+ }
+}
diff --git a/src/qga/commands.c b/src/qga/commands.c
new file mode 100644
index 0000000..bb73e7d
--- /dev/null
+++ b/src/qga/commands.c
@@ -0,0 +1,463 @@
+/*
+ * QEMU Guest Agent common/cross-platform command implementations
+ *
+ * Copyright IBM Corp. 2012
+ *
+ * Authors:
+ * Michael Roth <mdroth@linux.vnet.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include <glib.h>
+#include "qga/guest-agent-core.h"
+#include "qga-qmp-commands.h"
+#include "qapi/qmp/qerror.h"
+
+/* Maximum captured guest-exec out_data/err_data - 16MB */
+#define GUEST_EXEC_MAX_OUTPUT (16*1024*1024)
+/* Allocation and I/O buffer for reading guest-exec out_data/err_data - 4KB */
+#define GUEST_EXEC_IO_SIZE (4*1024)
+
+/* Note: in some situations, like with the fsfreeze, logging may be
+ * temporarilly disabled. if it is necessary that a command be able
+ * to log for accounting purposes, check ga_logging_enabled() beforehand,
+ * and use the QERR_QGA_LOGGING_DISABLED to generate an error
+ */
+void slog(const gchar *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ g_logv("syslog", G_LOG_LEVEL_INFO, fmt, ap);
+ va_end(ap);
+}
+
+int64_t qmp_guest_sync_delimited(int64_t id, Error **errp)
+{
+ ga_set_response_delimited(ga_state);
+ return id;
+}
+
+int64_t qmp_guest_sync(int64_t id, Error **errp)
+{
+ return id;
+}
+
+void qmp_guest_ping(Error **errp)
+{
+ slog("guest-ping called");
+}
+
+static void qmp_command_info(QmpCommand *cmd, void *opaque)
+{
+ GuestAgentInfo *info = opaque;
+ GuestAgentCommandInfo *cmd_info;
+ GuestAgentCommandInfoList *cmd_info_list;
+
+ cmd_info = g_new0(GuestAgentCommandInfo, 1);
+ cmd_info->name = g_strdup(qmp_command_name(cmd));
+ cmd_info->enabled = qmp_command_is_enabled(cmd);
+ cmd_info->success_response = qmp_has_success_response(cmd);
+
+ cmd_info_list = g_new0(GuestAgentCommandInfoList, 1);
+ cmd_info_list->value = cmd_info;
+ cmd_info_list->next = info->supported_commands;
+ info->supported_commands = cmd_info_list;
+}
+
+struct GuestAgentInfo *qmp_guest_info(Error **errp)
+{
+ GuestAgentInfo *info = g_new0(GuestAgentInfo, 1);
+
+ info->version = g_strdup(QEMU_VERSION);
+ qmp_for_each_command(qmp_command_info, info);
+ return info;
+}
+
+struct GuestExecIOData {
+ guchar *data;
+ gsize size;
+ gsize length;
+ gint closed;
+ bool truncated;
+ const char *name;
+};
+typedef struct GuestExecIOData GuestExecIOData;
+
+struct GuestExecInfo {
+ GPid pid;
+ int64_t pid_numeric;
+ gint status;
+ bool has_output;
+ gint finished;
+ GuestExecIOData in;
+ GuestExecIOData out;
+ GuestExecIOData err;
+ QTAILQ_ENTRY(GuestExecInfo) next;
+};
+typedef struct GuestExecInfo GuestExecInfo;
+
+static struct {
+ QTAILQ_HEAD(, GuestExecInfo) processes;
+} guest_exec_state = {
+ .processes = QTAILQ_HEAD_INITIALIZER(guest_exec_state.processes),
+};
+
+static int64_t gpid_to_int64(GPid pid)
+{
+#ifdef G_OS_WIN32
+ return GetProcessId(pid);
+#else
+ return (int64_t)pid;
+#endif
+}
+
+static GuestExecInfo *guest_exec_info_add(GPid pid)
+{
+ GuestExecInfo *gei;
+
+ gei = g_new0(GuestExecInfo, 1);
+ gei->pid = pid;
+ gei->pid_numeric = gpid_to_int64(pid);
+ QTAILQ_INSERT_TAIL(&guest_exec_state.processes, gei, next);
+
+ return gei;
+}
+
+static GuestExecInfo *guest_exec_info_find(int64_t pid_numeric)
+{
+ GuestExecInfo *gei;
+
+ QTAILQ_FOREACH(gei, &guest_exec_state.processes, next) {
+ if (gei->pid_numeric == pid_numeric) {
+ return gei;
+ }
+ }
+
+ return NULL;
+}
+
+GuestExecStatus *qmp_guest_exec_status(int64_t pid, Error **err)
+{
+ GuestExecInfo *gei;
+ GuestExecStatus *ges;
+
+ slog("guest-exec-status called, pid: %u", (uint32_t)pid);
+
+ gei = guest_exec_info_find(pid);
+ if (gei == NULL) {
+ error_setg(err, QERR_INVALID_PARAMETER, "pid");
+ return NULL;
+ }
+
+ ges = g_new0(GuestExecStatus, 1);
+
+ bool finished = g_atomic_int_get(&gei->finished);
+
+ /* need to wait till output channels are closed
+ * to be sure we captured all output at this point */
+ if (gei->has_output) {
+ finished = finished && g_atomic_int_get(&gei->out.closed);
+ finished = finished && g_atomic_int_get(&gei->err.closed);
+ }
+
+ ges->exited = finished;
+ if (finished) {
+ /* Glib has no portable way to parse exit status.
+ * On UNIX, we can get either exit code from normal termination
+ * or signal number.
+ * On Windows, it is either the same exit code or the exception
+ * value for an unhandled exception that caused the process
+ * to terminate.
+ * See MSDN for GetExitCodeProcess() and ntstatus.h for possible
+ * well-known codes, e.g. C0000005 ACCESS_DENIED - analog of SIGSEGV
+ * References:
+ * https://msdn.microsoft.com/en-us/library/windows/desktop/ms683189(v=vs.85).aspx
+ * https://msdn.microsoft.com/en-us/library/aa260331(v=vs.60).aspx
+ */
+#ifdef G_OS_WIN32
+ /* Additionally WIN32 does not provide any additional information
+ * on whetherthe child exited or terminated via signal.
+ * We use this simple range check to distingish application exit code
+ * (usually value less then 256) and unhandled exception code with
+ * ntstatus (always value greater then 0xC0000005). */
+ if ((uint32_t)gei->status < 0xC0000000U) {
+ ges->has_exitcode = true;
+ ges->exitcode = gei->status;
+ } else {
+ ges->has_signal = true;
+ ges->signal = gei->status;
+ }
+#else
+ if (WIFEXITED(gei->status)) {
+ ges->has_exitcode = true;
+ ges->exitcode = WEXITSTATUS(gei->status);
+ } else if (WIFSIGNALED(gei->status)) {
+ ges->has_signal = true;
+ ges->signal = WTERMSIG(gei->status);
+ }
+#endif
+ if (gei->out.length > 0) {
+ ges->has_out_data = true;
+ ges->out_data = g_base64_encode(gei->out.data, gei->out.length);
+ g_free(gei->out.data);
+ ges->has_out_truncated = gei->out.truncated;
+ }
+
+ if (gei->err.length > 0) {
+ ges->has_err_data = true;
+ ges->err_data = g_base64_encode(gei->err.data, gei->err.length);
+ g_free(gei->err.data);
+ ges->has_err_truncated = gei->err.truncated;
+ }
+
+ QTAILQ_REMOVE(&guest_exec_state.processes, gei, next);
+ g_free(gei);
+ }
+
+ return ges;
+}
+
+/* Get environment variables or arguments array for execve(). */
+static char **guest_exec_get_args(const strList *entry, bool log)
+{
+ const strList *it;
+ int count = 1, i = 0; /* reserve for NULL terminator */
+ char **args;
+ char *str; /* for logging array of arguments */
+ size_t str_size = 1;
+
+ for (it = entry; it != NULL; it = it->next) {
+ count++;
+ str_size += 1 + strlen(it->value);
+ }
+
+ str = g_malloc(str_size);
+ *str = 0;
+ args = g_malloc(count * sizeof(char *));
+ for (it = entry; it != NULL; it = it->next) {
+ args[i++] = it->value;
+ pstrcat(str, str_size, it->value);
+ if (it->next) {
+ pstrcat(str, str_size, " ");
+ }
+ }
+ args[i] = NULL;
+
+ if (log) {
+ slog("guest-exec called: \"%s\"", str);
+ }
+ g_free(str);
+
+ return args;
+}
+
+static void guest_exec_child_watch(GPid pid, gint status, gpointer data)
+{
+ GuestExecInfo *gei = (GuestExecInfo *)data;
+
+ g_debug("guest_exec_child_watch called, pid: %d, status: %u",
+ (int32_t)gpid_to_int64(pid), (uint32_t)status);
+
+ gei->status = status;
+ gei->finished = true;
+
+ g_spawn_close_pid(pid);
+}
+
+/** Reset ignored signals back to default. */
+static void guest_exec_task_setup(gpointer data)
+{
+#if !defined(G_OS_WIN32)
+ struct sigaction sigact;
+
+ memset(&sigact, 0, sizeof(struct sigaction));
+ sigact.sa_handler = SIG_DFL;
+
+ if (sigaction(SIGPIPE, &sigact, NULL) != 0) {
+ slog("sigaction() failed to reset child process's SIGPIPE: %s",
+ strerror(errno));
+ }
+#endif
+}
+
+static gboolean guest_exec_input_watch(GIOChannel *ch,
+ GIOCondition cond, gpointer p_)
+{
+ GuestExecIOData *p = (GuestExecIOData *)p_;
+ gsize bytes_written = 0;
+ GIOStatus status;
+ GError *gerr = NULL;
+
+ /* nothing left to write */
+ if (p->size == p->length) {
+ goto done;
+ }
+
+ status = g_io_channel_write_chars(ch, (gchar *)p->data + p->length,
+ p->size - p->length, &bytes_written, &gerr);
+
+ /* can be not 0 even if not G_IO_STATUS_NORMAL */
+ if (bytes_written != 0) {
+ p->length += bytes_written;
+ }
+
+ /* continue write, our callback will be called again */
+ if (status == G_IO_STATUS_NORMAL || status == G_IO_STATUS_AGAIN) {
+ return true;
+ }
+
+ if (gerr) {
+ g_warning("qga: i/o error writing to input_data channel: %s",
+ gerr->message);
+ g_error_free(gerr);
+ }
+
+done:
+ g_io_channel_shutdown(ch, true, NULL);
+ g_io_channel_unref(ch);
+ g_atomic_int_set(&p->closed, 1);
+ g_free(p->data);
+
+ return false;
+}
+
+static gboolean guest_exec_output_watch(GIOChannel *ch,
+ GIOCondition cond, gpointer p_)
+{
+ GuestExecIOData *p = (GuestExecIOData *)p_;
+ gsize bytes_read;
+ GIOStatus gstatus;
+
+ if (cond == G_IO_HUP || cond == G_IO_ERR) {
+ goto close;
+ }
+
+ if (p->size == p->length) {
+ gpointer t = NULL;
+ if (!p->truncated && p->size < GUEST_EXEC_MAX_OUTPUT) {
+ t = g_try_realloc(p->data, p->size + GUEST_EXEC_IO_SIZE);
+ }
+ if (t == NULL) {
+ /* ignore truncated output */
+ gchar buf[GUEST_EXEC_IO_SIZE];
+
+ p->truncated = true;
+ gstatus = g_io_channel_read_chars(ch, buf, sizeof(buf),
+ &bytes_read, NULL);
+ if (gstatus == G_IO_STATUS_EOF || gstatus == G_IO_STATUS_ERROR) {
+ goto close;
+ }
+
+ return true;
+ }
+ p->size += GUEST_EXEC_IO_SIZE;
+ p->data = t;
+ }
+
+ /* Calling read API once.
+ * On next available data our callback will be called again */
+ gstatus = g_io_channel_read_chars(ch, (gchar *)p->data + p->length,
+ p->size - p->length, &bytes_read, NULL);
+ if (gstatus == G_IO_STATUS_EOF || gstatus == G_IO_STATUS_ERROR) {
+ goto close;
+ }
+
+ p->length += bytes_read;
+
+ return true;
+
+close:
+ g_io_channel_unref(ch);
+ g_atomic_int_set(&p->closed, 1);
+ return false;
+}
+
+GuestExec *qmp_guest_exec(const char *path,
+ bool has_arg, strList *arg,
+ bool has_env, strList *env,
+ bool has_input_data, const char *input_data,
+ bool has_capture_output, bool capture_output,
+ Error **err)
+{
+ GPid pid;
+ GuestExec *ge = NULL;
+ GuestExecInfo *gei;
+ char **argv, **envp;
+ strList arglist;
+ gboolean ret;
+ GError *gerr = NULL;
+ gint in_fd, out_fd, err_fd;
+ GIOChannel *in_ch, *out_ch, *err_ch;
+ GSpawnFlags flags;
+ bool has_output = (has_capture_output && capture_output);
+
+ arglist.value = (char *)path;
+ arglist.next = has_arg ? arg : NULL;
+
+ argv = guest_exec_get_args(&arglist, true);
+ envp = has_env ? guest_exec_get_args(env, false) : NULL;
+
+ flags = G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD;
+#if GLIB_CHECK_VERSION(2, 33, 2)
+ flags |= G_SPAWN_SEARCH_PATH_FROM_ENVP;
+#endif
+ if (!has_output) {
+ flags |= G_SPAWN_STDOUT_TO_DEV_NULL | G_SPAWN_STDERR_TO_DEV_NULL;
+ }
+
+ ret = g_spawn_async_with_pipes(NULL, argv, envp, flags,
+ guest_exec_task_setup, NULL, &pid, has_input_data ? &in_fd : NULL,
+ has_output ? &out_fd : NULL, has_output ? &err_fd : NULL, &gerr);
+ if (!ret) {
+ error_setg(err, QERR_QGA_COMMAND_FAILED, gerr->message);
+ g_error_free(gerr);
+ goto done;
+ }
+
+ ge = g_new0(GuestExec, 1);
+ ge->pid = gpid_to_int64(pid);
+
+ gei = guest_exec_info_add(pid);
+ gei->has_output = has_output;
+ g_child_watch_add(pid, guest_exec_child_watch, gei);
+
+ if (has_input_data) {
+ gei->in.data = g_base64_decode(input_data, &gei->in.size);
+#ifdef G_OS_WIN32
+ in_ch = g_io_channel_win32_new_fd(in_fd);
+#else
+ in_ch = g_io_channel_unix_new(in_fd);
+#endif
+ g_io_channel_set_encoding(in_ch, NULL, NULL);
+ g_io_channel_set_buffered(in_ch, false);
+ g_io_channel_set_flags(in_ch, G_IO_FLAG_NONBLOCK, NULL);
+ g_io_add_watch(in_ch, G_IO_OUT, guest_exec_input_watch, &gei->in);
+ }
+
+ if (has_output) {
+#ifdef G_OS_WIN32
+ out_ch = g_io_channel_win32_new_fd(out_fd);
+ err_ch = g_io_channel_win32_new_fd(err_fd);
+#else
+ out_ch = g_io_channel_unix_new(out_fd);
+ err_ch = g_io_channel_unix_new(err_fd);
+#endif
+ g_io_channel_set_encoding(out_ch, NULL, NULL);
+ g_io_channel_set_encoding(err_ch, NULL, NULL);
+ g_io_channel_set_buffered(out_ch, false);
+ g_io_channel_set_buffered(err_ch, false);
+ g_io_add_watch(out_ch, G_IO_IN | G_IO_HUP,
+ guest_exec_output_watch, &gei->out);
+ g_io_add_watch(err_ch, G_IO_IN | G_IO_HUP,
+ guest_exec_output_watch, &gei->err);
+ }
+
+done:
+ g_free(argv);
+ g_free(envp);
+
+ return ge;
+}
diff --git a/src/qga/guest-agent-command-state.c b/src/qga/guest-agent-command-state.c
new file mode 100644
index 0000000..128c549
--- /dev/null
+++ b/src/qga/guest-agent-command-state.c
@@ -0,0 +1,73 @@
+/*
+ * QEMU Guest Agent command state interfaces
+ *
+ * Copyright IBM Corp. 2011
+ *
+ * Authors:
+ * Michael Roth <mdroth@linux.vnet.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+#include <glib.h>
+#include "qga/guest-agent-core.h"
+
+struct GACommandState {
+ GSList *groups;
+};
+
+typedef struct GACommandGroup {
+ void (*init)(void);
+ void (*cleanup)(void);
+} GACommandGroup;
+
+/* handle init/cleanup for stateful guest commands */
+
+void ga_command_state_add(GACommandState *cs,
+ void (*init)(void),
+ void (*cleanup)(void))
+{
+ GACommandGroup *cg = g_new0(GACommandGroup, 1);
+ cg->init = init;
+ cg->cleanup = cleanup;
+ cs->groups = g_slist_append(cs->groups, cg);
+}
+
+static void ga_command_group_init(gpointer opaque, gpointer unused)
+{
+ GACommandGroup *cg = opaque;
+
+ g_assert(cg);
+ if (cg->init) {
+ cg->init();
+ }
+}
+
+void ga_command_state_init_all(GACommandState *cs)
+{
+ g_assert(cs);
+ g_slist_foreach(cs->groups, ga_command_group_init, NULL);
+}
+
+static void ga_command_group_cleanup(gpointer opaque, gpointer unused)
+{
+ GACommandGroup *cg = opaque;
+
+ g_assert(cg);
+ if (cg->cleanup) {
+ cg->cleanup();
+ }
+}
+
+void ga_command_state_cleanup_all(GACommandState *cs)
+{
+ g_assert(cs);
+ g_slist_foreach(cs->groups, ga_command_group_cleanup, NULL);
+}
+
+GACommandState *ga_command_state_new(void)
+{
+ GACommandState *cs = g_new0(GACommandState, 1);
+ cs->groups = NULL;
+ return cs;
+}
diff --git a/src/qga/guest-agent-core.h b/src/qga/guest-agent-core.h
new file mode 100644
index 0000000..238dc6b
--- /dev/null
+++ b/src/qga/guest-agent-core.h
@@ -0,0 +1,50 @@
+/*
+ * QEMU Guest Agent core declarations
+ *
+ * Copyright IBM Corp. 2011
+ *
+ * Authors:
+ * Adam Litke <aglitke@linux.vnet.ibm.com>
+ * Michael Roth <mdroth@linux.vnet.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+#include "qapi/qmp/dispatch.h"
+#include "qemu-common.h"
+
+#define QGA_READ_COUNT_DEFAULT 4096
+
+/* Mapping of whence codes used by guest-file-seek. */
+enum {
+ QGA_SEEK_SET = 0,
+ QGA_SEEK_CUR = 1,
+ QGA_SEEK_END = 2,
+};
+
+typedef struct GAState GAState;
+typedef struct GACommandState GACommandState;
+extern GAState *ga_state;
+
+GList *ga_command_blacklist_init(GList *blacklist);
+void ga_command_state_init(GAState *s, GACommandState *cs);
+void ga_command_state_add(GACommandState *cs,
+ void (*init)(void),
+ void (*cleanup)(void));
+void ga_command_state_init_all(GACommandState *cs);
+void ga_command_state_cleanup_all(GACommandState *cs);
+GACommandState *ga_command_state_new(void);
+bool ga_logging_enabled(GAState *s);
+void ga_disable_logging(GAState *s);
+void ga_enable_logging(GAState *s);
+void GCC_FMT_ATTR(1, 2) slog(const gchar *fmt, ...);
+void ga_set_response_delimited(GAState *s);
+bool ga_is_frozen(GAState *s);
+void ga_set_frozen(GAState *s);
+void ga_unset_frozen(GAState *s);
+const char *ga_fsfreeze_hook(GAState *s);
+int64_t ga_get_fd_handle(GAState *s, Error **errp);
+
+#ifndef _WIN32
+void reopen_fd_to_null(int fd);
+#endif
diff --git a/src/qga/installer/qemu-ga.wxs b/src/qga/installer/qemu-ga.wxs
new file mode 100644
index 0000000..9473875
--- /dev/null
+++ b/src/qga/installer/qemu-ga.wxs
@@ -0,0 +1,185 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
+ <?ifndef env.QEMU_GA_VERSION ?>
+ <?error Environment variable QEMU_GA_VERSION undefined?>
+ <?endif?>
+
+ <?ifndef env.QEMU_GA_DISTRO ?>
+ <?error Environment variable QEMU_GA_DISTRO undefined?>
+ <?endif?>
+
+ <?ifndef env.QEMU_GA_MANUFACTURER ?>
+ <?error Environment variable QEMU_GA_MANUFACTURER undefined?>
+ <?endif?>
+
+ <?ifndef var.Arch?>
+ <?error Define Arch to 32 or 64?>
+ <?endif?>
+
+ <?ifndef var.Mingw_bin?>
+ <?if $(var.Arch) = "64"?>
+ <?define Mingw_bin=/usr/x86_64-w64-mingw32/sys-root/mingw/bin ?>
+ <?endif?>
+ <?if $(var.Arch) = "32"?>
+ <?define Mingw_bin=/usr/i686-w64-mingw32/sys-root/mingw/bin ?>
+ <?endif?>
+ <?endif?>
+
+ <?if $(var.Arch) = "64"?>
+ <?define ArchLib=libgcc_s_seh-1.dll?>
+ <?define GaProgramFilesFolder="ProgramFiles64Folder" ?>
+ <?endif?>
+
+ <?if $(var.Arch) = "32"?>
+ <?define ArchLib=libgcc_s_sjlj-1.dll?>
+ <?define GaProgramFilesFolder="ProgramFilesFolder" ?>
+ <?endif?>
+
+ <?ifndef var.ArchLib ?>
+ <?error Unexpected Arch value $(var.Arch)?>
+ <?endif?>
+
+ <Product
+ Name="QEMU guest agent"
+ Id="*"
+ UpgradeCode="{EB6B8302-C06E-4BEC-ADAC-932C68A3A98D}"
+ Manufacturer="$(env.QEMU_GA_MANUFACTURER)"
+ Version="$(env.QEMU_GA_VERSION)"
+ Language="1033">
+ <?if $(var.Arch) = 32 ?>
+ <Condition Message="Error: 32-bit version of Qemu GA can not be installed on 64-bit Windows.">NOT VersionNT64</Condition>
+ <?endif?>
+ <Package
+ Manufacturer="$(env.QEMU_GA_MANUFACTURER)"
+ InstallerVersion="200"
+ Languages="1033"
+ Compressed="yes"
+ InstallScope="perMachine"
+ />
+ <Media Id="1" Cabinet="qemu_ga.$(env.QEMU_GA_VERSION).cab" EmbedCab="yes" />
+ <Property Id="WHSLogo">1</Property>
+ <MajorUpgrade
+ DowngradeErrorMessage="Error: A newer version of QEMU guest agent is already installed."
+ />
+
+ <Directory Id="TARGETDIR" Name="SourceDir">
+ <Directory Id="$(var.GaProgramFilesFolder)" Name="QEMU Guest Agent">
+ <Directory Id="qemu_ga_directory" Name="Qemu-ga">
+ <Component Id="qemu_ga" Guid="{908B7199-DE2A-4DC6-A8D0-27A5AE444FEA}">
+ <File Id="qemu_ga.exe" Name="qemu-ga.exe" Source="$(env.BUILD_DIR)/qemu-ga.exe" KeyPath="yes" DiskId="1"/>
+ <ServiceInstall
+ Id="ServiceInstaller"
+ Type="ownProcess"
+ Vital="yes"
+ Name="QEMU-GA"
+ DisplayName="QEMU Guest Agent"
+ Description="QEMU Guest Agent"
+ Start="auto"
+ Account="LocalSystem"
+ ErrorControl="ignore"
+ Interactive="no"
+ Arguments="-d"
+ >
+ </ServiceInstall>
+ <ServiceControl Id="StartService" Start="install" Stop="both" Remove="uninstall" Name="QEMU-GA" Wait="no" />
+ </Component>
+ <?ifdef var.InstallVss?>
+ <Component Id="qga_vss_dll" Guid="{CB19C453-FABB-4BB1-ABAB-6B74F687BFBB}">
+ <File Id="qga_vss.dll" Name="qga-vss.dll" Source="$(env.BUILD_DIR)/qga/vss-win32/qga-vss.dll" KeyPath="yes" DiskId="1"/>
+ </Component>
+ <Component Id="qga_vss_tlb" Guid="{D8D584B1-59C2-4FB7-A91F-636FF7BFA66E}">
+ <File Id="qga_vss.tlb" Name="qga-vss.tlb" Source="$(env.BUILD_DIR)/qga/vss-win32/qga-vss.tlb" KeyPath="yes" DiskId="1"/>
+ </Component>
+ <?endif?>
+ <?if $(var.Arch) = "32"?>
+ <Component Id="gspawn-helper-console" Guid="{446185B3-87BE-43D2-96B8-0FEFD9E8696D}">
+ <File Id="gspawn-win32-helper-console.exe" Name="gspawn-win32-helper-console.exe" Source="$(var.Mingw_bin)/gspawn-win32-helper-console.exe" KeyPath="yes" DiskId="1"/>
+ </Component>
+ <Component Id="gspawn-helper" Guid="{CD67A5A3-2DB1-4DA1-A67A-8D71E797B466}">
+ <File Id="gspawn-win32-helper.exe" Name="gspawn-win32-helper.exe" Source="$(var.Mingw_bin)/gspawn-win32-helper.exe" KeyPath="yes" DiskId="1"/>
+ </Component>
+ <?endif?>
+ <?if $(var.Arch) = "64"?>
+ <Component Id="gspawn-helper-console" Guid="{9E615A9F-349A-4992-A5C2-C10BAD173660}">
+ <File Id="gspawn-win64-helper-console.exe" Name="gspawn-win64-helper-console.exe" Source="$(var.Mingw_bin)/gspawn-win64-helper-console.exe" KeyPath="yes" DiskId="1"/>
+ </Component>
+ <Component Id="gspawn-helper" Guid="{D201AD22-1846-4E4F-B6E1-C7A908ED2457}">
+ <File Id="gspawn-win64-helper.exe" Name="gspawn-win64-helper.exe" Source="$(var.Mingw_bin)/gspawn-win64-helper.exe" KeyPath="yes" DiskId="1"/>
+ </Component>
+ <?endif?>
+ <Component Id="iconv" Guid="{35EE3558-D34B-4F0A-B8BD-430FF0775246}">
+ <File Id="iconv.dll" Name="iconv.dll" Source="$(var.Mingw_bin)/iconv.dll" KeyPath="yes" DiskId="1"/>
+ </Component>
+ <Component Id="libgcc_arch_lib" Guid="{ADD4D07D-4515-4AB6-AF3E-C904961B4BB0}">
+ <File Id="libgcc_arch_lib" Name="$(var.ArchLib)" Source="$(var.Mingw_bin)/$(var.ArchLib)" KeyPath="yes" DiskId="1"/>
+ </Component>
+ <Component Id="libglib" Guid="{D31BFD83-2773-4B65-B45A-E0D2ADA58679}">
+ <File Id="libglib_2.0_0.dll" Name="libglib-2.0-0.dll" Source="$(var.Mingw_bin)/libglib-2.0-0.dll" KeyPath="yes" DiskId="1"/>
+ </Component>
+ <Component Id="libintl" Guid="{A641BC2D-A907-4A94-9149-F30ED430878F}">
+ <File Id="libintl_8.dll" Name="libintl-8.dll" Source="$(var.Mingw_bin)/libintl-8.dll" KeyPath="yes" DiskId="1"/>
+ </Component>
+ <Component Id="libssp" Guid="{7880087B-02B4-4EF6-A5D3-D18F8E3D90E1}">
+ <File Id="libssp_0.dll" Name="libssp-0.dll" Source="$(var.Mingw_bin)/libssp-0.dll" KeyPath="yes" DiskId="1"/>
+ </Component>
+ <Component Id="libwinpthread" Guid="{6C117C78-0F47-4B07-8F34-6BEE11643829}">
+ <File Id="libwinpthread_1.dll" Name="libwinpthread-1.dll" Source="$(var.Mingw_bin)/libwinpthread-1.dll" KeyPath="yes" DiskId="1"/>
+ </Component>
+ <Component Id="registry_entries" Guid="{D075D109-51CA-11E3-9F8B-000C29858960}">
+ <RegistryKey Root="HKLM"
+ Key="Software\$(env.QEMU_GA_MANUFACTURER)\$(env.QEMU_GA_DISTRO)\Tools\QemuGA">
+ <RegistryValue Type="string" Name="ProductID" Value="fb0a0d66-c7fb-4e2e-a16b-c4a3bfe8d13b" />
+ <RegistryValue Type="string" Name="Version" Value="$(env.QEMU_GA_VERSION)" />
+ </RegistryKey>
+ </Component>
+ </Directory>
+ </Directory>
+ </Directory>
+
+ <Property Id="cmd" Value="cmd.exe"/>
+ <Property Id="REINSTALLMODE" Value="amus"/>
+
+ <?ifdef var.InstallVss?>
+ <CustomAction Id="RegisterCom"
+ ExeCommand='/c "[qemu_ga_directory]qemu-ga.exe" -s vss-install'
+ Execute="deferred"
+ Property="cmd"
+ Impersonate="no"
+ Return="check"
+ >
+ </CustomAction>
+ <CustomAction Id="UnRegisterCom"
+ ExeCommand='/c "[qemu_ga_directory]qemu-ga.exe" -s vss-uninstall'
+ Execute="deferred"
+ Property="cmd"
+ Impersonate="no"
+ Return="check"
+ >
+ </CustomAction>
+ <?endif?>
+
+ <Feature Id="QEMUFeature" Title="QEMU Guest Agent" Level="1">
+ <ComponentRef Id="qemu_ga" />
+ <?ifdef var.InstallVss?>
+ <ComponentRef Id="qga_vss_dll" />
+ <ComponentRef Id="qga_vss_tlb" />
+ <?endif?>
+ <ComponentRef Id="gspawn-helper-console" />
+ <ComponentRef Id="gspawn-helper" />
+ <ComponentRef Id="iconv" />
+ <ComponentRef Id="libgcc_arch_lib" />
+ <ComponentRef Id="libglib" />
+ <ComponentRef Id="libintl" />
+ <ComponentRef Id="libssp" />
+ <ComponentRef Id="libwinpthread" />
+ <ComponentRef Id="registry_entries" />
+ </Feature>
+
+ <InstallExecuteSequence>
+ <?ifdef var.InstallVss?>
+ <Custom Action="UnRegisterCom" After="StopServices">Installed</Custom>
+ <Custom Action="RegisterCom" After="InstallServices">NOT REMOVE</Custom>
+ <?endif?>
+ </InstallExecuteSequence>
+ </Product>
+</Wix>
diff --git a/src/qga/main.c b/src/qga/main.c
new file mode 100644
index 0000000..f83a97d
--- /dev/null
+++ b/src/qga/main.c
@@ -0,0 +1,1403 @@
+/*
+ * QEMU Guest Agent
+ *
+ * Copyright IBM Corp. 2011
+ *
+ * Authors:
+ * Adam Litke <aglitke@linux.vnet.ibm.com>
+ * Michael Roth <mdroth@linux.vnet.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdbool.h>
+#include <glib.h>
+#include <getopt.h>
+#include <glib/gstdio.h>
+#ifndef _WIN32
+#include <syslog.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+#endif
+#include "qapi/qmp/json-streamer.h"
+#include "qapi/qmp/json-parser.h"
+#include "qapi/qmp/qint.h"
+#include "qapi/qmp/qjson.h"
+#include "qga/guest-agent-core.h"
+#include "qemu/module.h"
+#include "signal.h"
+#include "qapi/qmp/qerror.h"
+#include "qapi/qmp/dispatch.h"
+#include "qga/channel.h"
+#include "qemu/bswap.h"
+#ifdef _WIN32
+#include "qga/service-win32.h"
+#include "qga/vss-win32.h"
+#endif
+#ifdef __linux__
+#include <linux/fs.h>
+#ifdef FIFREEZE
+#define CONFIG_FSFREEZE
+#endif
+#endif
+
+#ifndef _WIN32
+#define QGA_VIRTIO_PATH_DEFAULT "/dev/virtio-ports/org.qemu.guest_agent.0"
+#define QGA_STATE_RELATIVE_DIR "run"
+#define QGA_SERIAL_PATH_DEFAULT "/dev/ttyS0"
+#else
+#define QGA_VIRTIO_PATH_DEFAULT "\\\\.\\Global\\org.qemu.guest_agent.0"
+#define QGA_STATE_RELATIVE_DIR "qemu-ga"
+#define QGA_SERIAL_PATH_DEFAULT "COM1"
+#endif
+#ifdef CONFIG_FSFREEZE
+#define QGA_FSFREEZE_HOOK_DEFAULT CONFIG_QEMU_CONFDIR "/fsfreeze-hook"
+#endif
+#define QGA_SENTINEL_BYTE 0xFF
+#define QGA_CONF_DEFAULT CONFIG_QEMU_CONFDIR G_DIR_SEPARATOR_S "qemu-ga.conf"
+
+static struct {
+ const char *state_dir;
+ const char *pidfile;
+} dfl_pathnames;
+
+typedef struct GAPersistentState {
+#define QGA_PSTATE_DEFAULT_FD_COUNTER 1000
+ int64_t fd_counter;
+} GAPersistentState;
+
+struct GAState {
+ JSONMessageParser parser;
+ GMainLoop *main_loop;
+ GAChannel *channel;
+ bool virtio; /* fastpath to check for virtio to deal with poll() quirks */
+ GACommandState *command_state;
+ GLogLevelFlags log_level;
+ FILE *log_file;
+ bool logging_enabled;
+#ifdef _WIN32
+ GAService service;
+#endif
+ bool delimit_response;
+ bool frozen;
+ GList *blacklist;
+ char *state_filepath_isfrozen;
+ struct {
+ const char *log_filepath;
+ const char *pid_filepath;
+ } deferred_options;
+#ifdef CONFIG_FSFREEZE
+ const char *fsfreeze_hook;
+#endif
+ gchar *pstate_filepath;
+ GAPersistentState pstate;
+};
+
+struct GAState *ga_state;
+
+/* commands that are safe to issue while filesystems are frozen */
+static const char *ga_freeze_whitelist[] = {
+ "guest-ping",
+ "guest-info",
+ "guest-sync",
+ "guest-sync-delimited",
+ "guest-fsfreeze-status",
+ "guest-fsfreeze-thaw",
+ NULL
+};
+
+#ifdef _WIN32
+DWORD WINAPI service_ctrl_handler(DWORD ctrl, DWORD type, LPVOID data,
+ LPVOID ctx);
+VOID WINAPI service_main(DWORD argc, TCHAR *argv[]);
+#endif
+
+static void
+init_dfl_pathnames(void)
+{
+ g_assert(dfl_pathnames.state_dir == NULL);
+ g_assert(dfl_pathnames.pidfile == NULL);
+ dfl_pathnames.state_dir = qemu_get_local_state_pathname(
+ QGA_STATE_RELATIVE_DIR);
+ dfl_pathnames.pidfile = qemu_get_local_state_pathname(
+ QGA_STATE_RELATIVE_DIR G_DIR_SEPARATOR_S "qemu-ga.pid");
+}
+
+static void quit_handler(int sig)
+{
+ /* if we're frozen, don't exit unless we're absolutely forced to,
+ * because it's basically impossible for graceful exit to complete
+ * unless all log/pid files are on unfreezable filesystems. there's
+ * also a very likely chance killing the agent before unfreezing
+ * the filesystems is a mistake (or will be viewed as one later).
+ */
+ if (ga_is_frozen(ga_state)) {
+ return;
+ }
+ 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);
+ }
+}
+
+#ifndef _WIN32
+static gboolean register_signal_handlers(void)
+{
+ struct sigaction sigact;
+ int ret;
+
+ memset(&sigact, 0, sizeof(struct sigaction));
+ sigact.sa_handler = quit_handler;
+
+ ret = sigaction(SIGINT, &sigact, NULL);
+ if (ret == -1) {
+ g_error("error configuring signal handler: %s", strerror(errno));
+ }
+ ret = sigaction(SIGTERM, &sigact, NULL);
+ if (ret == -1) {
+ g_error("error configuring signal handler: %s", strerror(errno));
+ }
+
+ sigact.sa_handler = SIG_IGN;
+ if (sigaction(SIGPIPE, &sigact, NULL) != 0) {
+ g_error("error configuring SIGPIPE signal handler: %s",
+ strerror(errno));
+ }
+
+ return true;
+}
+
+/* TODO: use this in place of all post-fork() fclose(std*) callers */
+void reopen_fd_to_null(int fd)
+{
+ int nullfd;
+
+ nullfd = open("/dev/null", O_RDWR);
+ if (nullfd < 0) {
+ return;
+ }
+
+ dup2(nullfd, fd);
+
+ if (nullfd != fd) {
+ close(nullfd);
+ }
+}
+#endif
+
+static void usage(const char *cmd)
+{
+ printf(
+"Usage: %s [-m <method> -p <path>] [<options>]\n"
+"QEMU Guest Agent %s\n"
+"\n"
+" -m, --method transport method: one of unix-listen, virtio-serial, or\n"
+" isa-serial (virtio-serial is the default)\n"
+" -p, --path device/socket path (the default for virtio-serial is:\n"
+" %s,\n"
+" the default for isa-serial is:\n"
+" %s)\n"
+" -l, --logfile set logfile path, logs to stderr by default\n"
+" -f, --pidfile specify pidfile (default is %s)\n"
+#ifdef CONFIG_FSFREEZE
+" -F, --fsfreeze-hook\n"
+" enable fsfreeze hook. Accepts an optional argument that\n"
+" specifies script to run on freeze/thaw. Script will be\n"
+" called with 'freeze'/'thaw' arguments accordingly.\n"
+" (default is %s)\n"
+" If using -F with an argument, do not follow -F with a\n"
+" space.\n"
+" (for example: -F/var/run/fsfreezehook.sh)\n"
+#endif
+" -t, --statedir specify dir to store state information (absolute paths\n"
+" only, default is %s)\n"
+" -v, --verbose log extra debugging information\n"
+" -V, --version print version information and exit\n"
+" -d, --daemonize become a daemon\n"
+#ifdef _WIN32
+" -s, --service service commands: install, uninstall, vss-install, vss-uninstall\n"
+#endif
+" -b, --blacklist comma-separated list of RPCs to disable (no spaces, \"?\"\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"
+" -h, --help display this help and exit\n"
+"\n"
+"Report bugs to <mdroth@linux.vnet.ibm.com>\n"
+ , cmd, QEMU_VERSION, QGA_VIRTIO_PATH_DEFAULT, QGA_SERIAL_PATH_DEFAULT,
+ dfl_pathnames.pidfile,
+#ifdef CONFIG_FSFREEZE
+ QGA_FSFREEZE_HOOK_DEFAULT,
+#endif
+ dfl_pathnames.state_dir);
+}
+
+static const char *ga_log_level_str(GLogLevelFlags level)
+{
+ switch (level & G_LOG_LEVEL_MASK) {
+ case G_LOG_LEVEL_ERROR:
+ return "error";
+ case G_LOG_LEVEL_CRITICAL:
+ return "critical";
+ case G_LOG_LEVEL_WARNING:
+ return "warning";
+ case G_LOG_LEVEL_MESSAGE:
+ return "message";
+ case G_LOG_LEVEL_INFO:
+ return "info";
+ case G_LOG_LEVEL_DEBUG:
+ return "debug";
+ default:
+ return "user";
+ }
+}
+
+bool ga_logging_enabled(GAState *s)
+{
+ return s->logging_enabled;
+}
+
+void ga_disable_logging(GAState *s)
+{
+ s->logging_enabled = false;
+}
+
+void ga_enable_logging(GAState *s)
+{
+ s->logging_enabled = true;
+}
+
+static void ga_log(const gchar *domain, GLogLevelFlags level,
+ const gchar *msg, gpointer opaque)
+{
+ GAState *s = opaque;
+ GTimeVal time;
+ const char *level_str = ga_log_level_str(level);
+
+ if (!ga_logging_enabled(s)) {
+ return;
+ }
+
+ level &= G_LOG_LEVEL_MASK;
+#ifndef _WIN32
+ if (g_strcmp0(domain, "syslog") == 0) {
+ syslog(LOG_INFO, "%s: %s", level_str, msg);
+ } else if (level & s->log_level) {
+#else
+ if (level & s->log_level) {
+#endif
+ g_get_current_time(&time);
+ fprintf(s->log_file,
+ "%lu.%lu: %s: %s\n", time.tv_sec, time.tv_usec, level_str, msg);
+ fflush(s->log_file);
+ }
+}
+
+void ga_set_response_delimited(GAState *s)
+{
+ s->delimit_response = true;
+}
+
+static FILE *ga_open_logfile(const char *logfile)
+{
+ FILE *f;
+
+ f = fopen(logfile, "a");
+ if (!f) {
+ return NULL;
+ }
+
+ qemu_set_cloexec(fileno(f));
+ return f;
+}
+
+#ifndef _WIN32
+static bool ga_open_pidfile(const char *pidfile)
+{
+ int pidfd;
+ char pidstr[32];
+
+ pidfd = qemu_open(pidfile, O_CREAT|O_WRONLY, S_IRUSR|S_IWUSR);
+ if (pidfd == -1 || lockf(pidfd, F_TLOCK, 0)) {
+ g_critical("Cannot lock pid file, %s", strerror(errno));
+ if (pidfd != -1) {
+ close(pidfd);
+ }
+ return false;
+ }
+
+ if (ftruncate(pidfd, 0)) {
+ g_critical("Failed to truncate pid file");
+ goto fail;
+ }
+ snprintf(pidstr, sizeof(pidstr), "%d\n", getpid());
+ if (write(pidfd, pidstr, strlen(pidstr)) != strlen(pidstr)) {
+ g_critical("Failed to write pid file");
+ goto fail;
+ }
+
+ /* keep pidfile open & locked forever */
+ return true;
+
+fail:
+ unlink(pidfile);
+ close(pidfd);
+ return false;
+}
+#else /* _WIN32 */
+static bool ga_open_pidfile(const char *pidfile)
+{
+ return true;
+}
+#endif
+
+static gint ga_strcmp(gconstpointer str1, gconstpointer str2)
+{
+ return strcmp(str1, str2);
+}
+
+/* disable commands that aren't safe for fsfreeze */
+static void ga_disable_non_whitelisted(QmpCommand *cmd, void *opaque)
+{
+ bool whitelisted = false;
+ int i = 0;
+ const char *name = qmp_command_name(cmd);
+
+ while (ga_freeze_whitelist[i] != NULL) {
+ if (strcmp(name, ga_freeze_whitelist[i]) == 0) {
+ whitelisted = true;
+ }
+ i++;
+ }
+ if (!whitelisted) {
+ g_debug("disabling command: %s", name);
+ qmp_disable_command(name);
+ }
+}
+
+/* [re-]enable all commands, except those explicitly blacklisted by user */
+static void ga_enable_non_blacklisted(QmpCommand *cmd, void *opaque)
+{
+ GList *blacklist = opaque;
+ const char *name = qmp_command_name(cmd);
+
+ if (g_list_find_custom(blacklist, name, ga_strcmp) == NULL &&
+ !qmp_command_is_enabled(cmd)) {
+ g_debug("enabling command: %s", name);
+ qmp_enable_command(name);
+ }
+}
+
+static bool ga_create_file(const char *path)
+{
+ int fd = open(path, O_CREAT | O_WRONLY, S_IWUSR | S_IRUSR);
+ if (fd == -1) {
+ g_warning("unable to open/create file %s: %s", path, strerror(errno));
+ return false;
+ }
+ close(fd);
+ return true;
+}
+
+static bool ga_delete_file(const char *path)
+{
+ int ret = unlink(path);
+ if (ret == -1) {
+ g_warning("unable to delete file: %s: %s", path, strerror(errno));
+ return false;
+ }
+
+ return true;
+}
+
+bool ga_is_frozen(GAState *s)
+{
+ return s->frozen;
+}
+
+void ga_set_frozen(GAState *s)
+{
+ if (ga_is_frozen(s)) {
+ return;
+ }
+ /* disable all non-whitelisted (for frozen state) commands */
+ qmp_for_each_command(ga_disable_non_whitelisted, NULL);
+ g_warning("disabling logging due to filesystem freeze");
+ ga_disable_logging(s);
+ s->frozen = true;
+ if (!ga_create_file(s->state_filepath_isfrozen)) {
+ g_warning("unable to create %s, fsfreeze may not function properly",
+ s->state_filepath_isfrozen);
+ }
+}
+
+void ga_unset_frozen(GAState *s)
+{
+ if (!ga_is_frozen(s)) {
+ return;
+ }
+
+ /* if we delayed creation/opening of pid/log files due to being
+ * in a frozen state at start up, do it now
+ */
+ if (s->deferred_options.log_filepath) {
+ s->log_file = ga_open_logfile(s->deferred_options.log_filepath);
+ if (!s->log_file) {
+ s->log_file = stderr;
+ }
+ s->deferred_options.log_filepath = NULL;
+ }
+ ga_enable_logging(s);
+ g_warning("logging re-enabled due to filesystem unfreeze");
+ if (s->deferred_options.pid_filepath) {
+ if (!ga_open_pidfile(s->deferred_options.pid_filepath)) {
+ g_warning("failed to create/open pid file");
+ }
+ s->deferred_options.pid_filepath = NULL;
+ }
+
+ /* enable all disabled, non-blacklisted commands */
+ qmp_for_each_command(ga_enable_non_blacklisted, s->blacklist);
+ s->frozen = false;
+ if (!ga_delete_file(s->state_filepath_isfrozen)) {
+ g_warning("unable to delete %s, fsfreeze may not function properly",
+ s->state_filepath_isfrozen);
+ }
+}
+
+#ifdef CONFIG_FSFREEZE
+const char *ga_fsfreeze_hook(GAState *s)
+{
+ return s->fsfreeze_hook;
+}
+#endif
+
+static void become_daemon(const char *pidfile)
+{
+#ifndef _WIN32
+ pid_t pid, sid;
+
+ pid = fork();
+ if (pid < 0) {
+ exit(EXIT_FAILURE);
+ }
+ if (pid > 0) {
+ exit(EXIT_SUCCESS);
+ }
+
+ if (pidfile) {
+ if (!ga_open_pidfile(pidfile)) {
+ g_critical("failed to create pidfile");
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ umask(S_IRWXG | S_IRWXO);
+ sid = setsid();
+ if (sid < 0) {
+ goto fail;
+ }
+ if ((chdir("/")) < 0) {
+ goto fail;
+ }
+
+ reopen_fd_to_null(STDIN_FILENO);
+ reopen_fd_to_null(STDOUT_FILENO);
+ reopen_fd_to_null(STDERR_FILENO);
+ return;
+
+fail:
+ if (pidfile) {
+ unlink(pidfile);
+ }
+ g_critical("failed to daemonize");
+ exit(EXIT_FAILURE);
+#endif
+}
+
+static int send_response(GAState *s, QObject *payload)
+{
+ const char *buf;
+ QString *payload_qstr, *response_qstr;
+ GIOStatus status;
+
+ g_assert(payload && s->channel);
+
+ payload_qstr = qobject_to_json(payload);
+ if (!payload_qstr) {
+ return -EINVAL;
+ }
+
+ if (s->delimit_response) {
+ s->delimit_response = false;
+ response_qstr = qstring_new();
+ qstring_append_chr(response_qstr, QGA_SENTINEL_BYTE);
+ qstring_append(response_qstr, qstring_get_str(payload_qstr));
+ QDECREF(payload_qstr);
+ } else {
+ response_qstr = payload_qstr;
+ }
+
+ qstring_append_chr(response_qstr, '\n');
+ buf = qstring_get_str(response_qstr);
+ status = ga_channel_write_all(s->channel, buf, strlen(buf));
+ QDECREF(response_qstr);
+ if (status != G_IO_STATUS_NORMAL) {
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static void process_command(GAState *s, QDict *req)
+{
+ QObject *rsp = NULL;
+ int ret;
+
+ g_assert(req);
+ g_debug("processing command");
+ rsp = qmp_dispatch(QOBJECT(req));
+ if (rsp) {
+ ret = send_response(s, rsp);
+ if (ret) {
+ g_warning("error sending response: %s", strerror(ret));
+ }
+ qobject_decref(rsp);
+ }
+}
+
+/* handle requests/control events coming in over the channel */
+static void process_event(JSONMessageParser *parser, GQueue *tokens)
+{
+ GAState *s = container_of(parser, GAState, parser);
+ QDict *qdict;
+ Error *err = NULL;
+ int ret;
+
+ g_assert(s && parser);
+
+ g_debug("process_event: called");
+ qdict = qobject_to_qdict(json_parser_parse_err(tokens, NULL, &err));
+ if (err || !qdict) {
+ QDECREF(qdict);
+ qdict = qdict_new();
+ if (!err) {
+ g_warning("failed to parse event: unknown error");
+ error_setg(&err, QERR_JSON_PARSING);
+ } else {
+ g_warning("failed to parse event: %s", error_get_pretty(err));
+ }
+ qdict_put_obj(qdict, "error", qmp_build_error_object(err));
+ error_free(err);
+ }
+
+ /* handle host->guest commands */
+ if (qdict_haskey(qdict, "execute")) {
+ process_command(s, qdict);
+ } else {
+ if (!qdict_haskey(qdict, "error")) {
+ QDECREF(qdict);
+ qdict = qdict_new();
+ g_warning("unrecognized payload format");
+ error_setg(&err, QERR_UNSUPPORTED);
+ qdict_put_obj(qdict, "error", qmp_build_error_object(err));
+ error_free(err);
+ }
+ ret = send_response(s, QOBJECT(qdict));
+ if (ret < 0) {
+ g_warning("error sending error response: %s", strerror(-ret));
+ }
+ }
+
+ QDECREF(qdict);
+}
+
+/* false return signals GAChannel to close the current client connection */
+static gboolean channel_event_cb(GIOCondition condition, gpointer data)
+{
+ GAState *s = data;
+ gchar buf[QGA_READ_COUNT_DEFAULT+1];
+ gsize count;
+ GError *err = NULL;
+ GIOStatus status = ga_channel_read(s->channel, buf, QGA_READ_COUNT_DEFAULT, &count);
+ if (err != NULL) {
+ g_warning("error reading channel: %s", err->message);
+ g_error_free(err);
+ return false;
+ }
+ switch (status) {
+ case G_IO_STATUS_ERROR:
+ g_warning("error reading channel");
+ return false;
+ case G_IO_STATUS_NORMAL:
+ buf[count] = 0;
+ g_debug("read data, count: %d, data: %s", (int)count, buf);
+ json_message_parser_feed(&s->parser, (char *)buf, (int)count);
+ break;
+ case G_IO_STATUS_EOF:
+ g_debug("received EOF");
+ if (!s->virtio) {
+ return false;
+ }
+ /* fall through */
+ case G_IO_STATUS_AGAIN:
+ /* virtio causes us to spin here when no process is attached to
+ * host-side chardev. sleep a bit to mitigate this
+ */
+ if (s->virtio) {
+ usleep(100*1000);
+ }
+ return true;
+ default:
+ g_warning("unknown channel read status, closing");
+ return false;
+ }
+ return true;
+}
+
+static gboolean channel_init(GAState *s, const gchar *method, const gchar *path)
+{
+ GAChannelMethod channel_method;
+
+ if (strcmp(method, "virtio-serial") == 0) {
+ s->virtio = true; /* virtio requires special handling in some cases */
+ channel_method = GA_CHANNEL_VIRTIO_SERIAL;
+ } else if (strcmp(method, "isa-serial") == 0) {
+ channel_method = GA_CHANNEL_ISA_SERIAL;
+ } else if (strcmp(method, "unix-listen") == 0) {
+ channel_method = GA_CHANNEL_UNIX_LISTEN;
+ } else {
+ g_critical("unsupported channel method/type: %s", method);
+ return false;
+ }
+
+ s->channel = ga_channel_new(channel_method, path, channel_event_cb, s);
+ if (!s->channel) {
+ g_critical("failed to create guest agent channel");
+ return false;
+ }
+
+ return true;
+}
+
+#ifdef _WIN32
+DWORD WINAPI service_ctrl_handler(DWORD ctrl, DWORD type, LPVOID data,
+ LPVOID ctx)
+{
+ DWORD ret = NO_ERROR;
+ GAService *service = &ga_state->service;
+
+ switch (ctrl)
+ {
+ case SERVICE_CONTROL_STOP:
+ case SERVICE_CONTROL_SHUTDOWN:
+ quit_handler(SIGTERM);
+ service->status.dwCurrentState = SERVICE_STOP_PENDING;
+ SetServiceStatus(service->status_handle, &service->status);
+ break;
+
+ default:
+ ret = ERROR_CALL_NOT_IMPLEMENTED;
+ }
+ return ret;
+}
+
+VOID WINAPI service_main(DWORD argc, TCHAR *argv[])
+{
+ GAService *service = &ga_state->service;
+
+ service->status_handle = RegisterServiceCtrlHandlerEx(QGA_SERVICE_NAME,
+ service_ctrl_handler, NULL);
+
+ if (service->status_handle == 0) {
+ g_critical("Failed to register extended requests function!\n");
+ return;
+ }
+
+ service->status.dwServiceType = SERVICE_WIN32;
+ service->status.dwCurrentState = SERVICE_RUNNING;
+ service->status.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;
+ service->status.dwWin32ExitCode = NO_ERROR;
+ service->status.dwServiceSpecificExitCode = NO_ERROR;
+ service->status.dwCheckPoint = 0;
+ service->status.dwWaitHint = 0;
+ SetServiceStatus(service->status_handle, &service->status);
+
+ g_main_loop_run(ga_state->main_loop);
+
+ service->status.dwCurrentState = SERVICE_STOPPED;
+ SetServiceStatus(service->status_handle, &service->status);
+}
+#endif
+
+static void set_persistent_state_defaults(GAPersistentState *pstate)
+{
+ g_assert(pstate);
+ pstate->fd_counter = QGA_PSTATE_DEFAULT_FD_COUNTER;
+}
+
+static void persistent_state_from_keyfile(GAPersistentState *pstate,
+ GKeyFile *keyfile)
+{
+ g_assert(pstate);
+ g_assert(keyfile);
+ /* if any fields are missing, either because the file was tampered with
+ * by agents of chaos, or because the field wasn't present at the time the
+ * file was created, the best we can ever do is start over with the default
+ * values. so load them now, and ignore any errors in accessing key-value
+ * pairs
+ */
+ set_persistent_state_defaults(pstate);
+
+ if (g_key_file_has_key(keyfile, "global", "fd_counter", NULL)) {
+ pstate->fd_counter =
+ g_key_file_get_integer(keyfile, "global", "fd_counter", NULL);
+ }
+}
+
+static void persistent_state_to_keyfile(const GAPersistentState *pstate,
+ GKeyFile *keyfile)
+{
+ g_assert(pstate);
+ g_assert(keyfile);
+
+ g_key_file_set_integer(keyfile, "global", "fd_counter", pstate->fd_counter);
+}
+
+static gboolean write_persistent_state(const GAPersistentState *pstate,
+ const gchar *path)
+{
+ GKeyFile *keyfile = g_key_file_new();
+ GError *gerr = NULL;
+ gboolean ret = true;
+ gchar *data = NULL;
+ gsize data_len;
+
+ g_assert(pstate);
+
+ persistent_state_to_keyfile(pstate, keyfile);
+ data = g_key_file_to_data(keyfile, &data_len, &gerr);
+ if (gerr) {
+ g_critical("failed to convert persistent state to string: %s",
+ gerr->message);
+ ret = false;
+ goto out;
+ }
+
+ g_file_set_contents(path, data, data_len, &gerr);
+ if (gerr) {
+ g_critical("failed to write persistent state to %s: %s",
+ path, gerr->message);
+ ret = false;
+ goto out;
+ }
+
+out:
+ if (gerr) {
+ g_error_free(gerr);
+ }
+ if (keyfile) {
+ g_key_file_free(keyfile);
+ }
+ g_free(data);
+ return ret;
+}
+
+static gboolean read_persistent_state(GAPersistentState *pstate,
+ const gchar *path, gboolean frozen)
+{
+ GKeyFile *keyfile = NULL;
+ GError *gerr = NULL;
+ struct stat st;
+ gboolean ret = true;
+
+ g_assert(pstate);
+
+ if (stat(path, &st) == -1) {
+ /* it's okay if state file doesn't exist, but any other error
+ * indicates a permissions issue or some other misconfiguration
+ * that we likely won't be able to recover from.
+ */
+ if (errno != ENOENT) {
+ g_critical("unable to access state file at path %s: %s",
+ path, strerror(errno));
+ ret = false;
+ goto out;
+ }
+
+ /* file doesn't exist. initialize state to default values and
+ * attempt to save now. (we could wait till later when we have
+ * modified state we need to commit, but if there's a problem,
+ * such as a missing parent directory, we want to catch it now)
+ *
+ * there is a potential scenario where someone either managed to
+ * update the agent from a version that didn't use a key store
+ * while qemu-ga thought the filesystem was frozen, or
+ * deleted the key store prior to issuing a fsfreeze, prior
+ * to restarting the agent. in this case we go ahead and defer
+ * initial creation till we actually have modified state to
+ * write, otherwise fail to recover from freeze.
+ */
+ set_persistent_state_defaults(pstate);
+ if (!frozen) {
+ ret = write_persistent_state(pstate, path);
+ if (!ret) {
+ g_critical("unable to create state file at path %s", path);
+ ret = false;
+ goto out;
+ }
+ }
+ ret = true;
+ goto out;
+ }
+
+ keyfile = g_key_file_new();
+ g_key_file_load_from_file(keyfile, path, 0, &gerr);
+ if (gerr) {
+ g_critical("error loading persistent state from path: %s, %s",
+ path, gerr->message);
+ ret = false;
+ goto out;
+ }
+
+ persistent_state_from_keyfile(pstate, keyfile);
+
+out:
+ if (keyfile) {
+ g_key_file_free(keyfile);
+ }
+ if (gerr) {
+ g_error_free(gerr);
+ }
+
+ return ret;
+}
+
+int64_t ga_get_fd_handle(GAState *s, Error **errp)
+{
+ int64_t handle;
+
+ g_assert(s->pstate_filepath);
+ /* we blacklist commands and avoid operations that potentially require
+ * writing to disk when we're in a frozen state. this includes opening
+ * new files, so we should never get here in that situation
+ */
+ g_assert(!ga_is_frozen(s));
+
+ handle = s->pstate.fd_counter++;
+
+ /* This should never happen on a reasonable timeframe, as guest-file-open
+ * would have to be issued 2^63 times */
+ if (s->pstate.fd_counter == INT64_MAX) {
+ abort();
+ }
+
+ if (!write_persistent_state(&s->pstate, s->pstate_filepath)) {
+ error_setg(errp, "failed to commit persistent state to disk");
+ return -1;
+ }
+
+ return handle;
+}
+
+static void ga_print_cmd(QmpCommand *cmd, void *opaque)
+{
+ printf("%s\n", qmp_command_name(cmd));
+}
+
+static GList *split_list(const gchar *str, const gchar *delim)
+{
+ GList *list = NULL;
+ int i;
+ gchar **strv;
+
+ strv = g_strsplit(str, delim, -1);
+ for (i = 0; strv[i]; i++) {
+ list = g_list_prepend(list, strv[i]);
+ }
+ g_free(strv);
+
+ return list;
+}
+
+typedef struct GAConfig {
+ char *channel_path;
+ char *method;
+ char *log_filepath;
+ char *pid_filepath;
+#ifdef CONFIG_FSFREEZE
+ char *fsfreeze_hook;
+#endif
+ char *state_dir;
+#ifdef _WIN32
+ const char *service;
+#endif
+ gchar *bliststr; /* blacklist may point to this string */
+ GList *blacklist;
+ int daemonize;
+ GLogLevelFlags log_level;
+ int dumpconf;
+} GAConfig;
+
+static void config_load(GAConfig *config)
+{
+ GError *gerr = NULL;
+ GKeyFile *keyfile;
+ const char *conf = g_getenv("QGA_CONF") ?: QGA_CONF_DEFAULT;
+
+ /* read system config */
+ keyfile = g_key_file_new();
+ if (!g_key_file_load_from_file(keyfile, conf, 0, &gerr)) {
+ goto end;
+ }
+ if (g_key_file_has_key(keyfile, "general", "daemon", NULL)) {
+ config->daemonize =
+ g_key_file_get_boolean(keyfile, "general", "daemon", &gerr);
+ }
+ if (g_key_file_has_key(keyfile, "general", "method", NULL)) {
+ config->method =
+ g_key_file_get_string(keyfile, "general", "method", &gerr);
+ }
+ if (g_key_file_has_key(keyfile, "general", "path", NULL)) {
+ config->channel_path =
+ g_key_file_get_string(keyfile, "general", "path", &gerr);
+ }
+ if (g_key_file_has_key(keyfile, "general", "logfile", NULL)) {
+ config->log_filepath =
+ g_key_file_get_string(keyfile, "general", "logfile", &gerr);
+ }
+ if (g_key_file_has_key(keyfile, "general", "pidfile", NULL)) {
+ config->pid_filepath =
+ g_key_file_get_string(keyfile, "general", "pidfile", &gerr);
+ }
+#ifdef CONFIG_FSFREEZE
+ if (g_key_file_has_key(keyfile, "general", "fsfreeze-hook", NULL)) {
+ config->fsfreeze_hook =
+ g_key_file_get_string(keyfile,
+ "general", "fsfreeze-hook", &gerr);
+ }
+#endif
+ if (g_key_file_has_key(keyfile, "general", "statedir", NULL)) {
+ config->state_dir =
+ g_key_file_get_string(keyfile, "general", "statedir", &gerr);
+ }
+ if (g_key_file_has_key(keyfile, "general", "verbose", NULL) &&
+ g_key_file_get_boolean(keyfile, "general", "verbose", &gerr)) {
+ /* enable all log levels */
+ config->log_level = G_LOG_LEVEL_MASK;
+ }
+ if (g_key_file_has_key(keyfile, "general", "blacklist", NULL)) {
+ config->bliststr =
+ g_key_file_get_string(keyfile, "general", "blacklist", &gerr);
+ config->blacklist = g_list_concat(config->blacklist,
+ split_list(config->bliststr, ","));
+ }
+
+end:
+ g_key_file_free(keyfile);
+ if (gerr &&
+ !(gerr->domain == G_FILE_ERROR && gerr->code == G_FILE_ERROR_NOENT)) {
+ g_critical("error loading configuration from path: %s, %s",
+ QGA_CONF_DEFAULT, gerr->message);
+ exit(EXIT_FAILURE);
+ }
+ g_clear_error(&gerr);
+}
+
+static gchar *list_join(GList *list, const gchar separator)
+{
+ GString *str = g_string_new("");
+
+ while (list) {
+ str = g_string_append(str, (gchar *)list->data);
+ list = g_list_next(list);
+ if (list) {
+ str = g_string_append_c(str, separator);
+ }
+ }
+
+ return g_string_free(str, FALSE);
+}
+
+static void config_dump(GAConfig *config)
+{
+ GError *error = NULL;
+ GKeyFile *keyfile;
+ gchar *tmp;
+
+ keyfile = g_key_file_new();
+ g_assert(keyfile);
+
+ g_key_file_set_boolean(keyfile, "general", "daemon", config->daemonize);
+ g_key_file_set_string(keyfile, "general", "method", config->method);
+ g_key_file_set_string(keyfile, "general", "path", config->channel_path);
+ if (config->log_filepath) {
+ g_key_file_set_string(keyfile, "general", "logfile",
+ config->log_filepath);
+ }
+ g_key_file_set_string(keyfile, "general", "pidfile", config->pid_filepath);
+#ifdef CONFIG_FSFREEZE
+ if (config->fsfreeze_hook) {
+ g_key_file_set_string(keyfile, "general", "fsfreeze-hook",
+ config->fsfreeze_hook);
+ }
+#endif
+ 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);
+ tmp = list_join(config->blacklist, ',');
+ g_key_file_set_string(keyfile, "general", "blacklist", tmp);
+ g_free(tmp);
+
+ tmp = g_key_file_to_data(keyfile, NULL, &error);
+ printf("%s", tmp);
+
+ g_free(tmp);
+ g_key_file_free(keyfile);
+}
+
+static void config_parse(GAConfig *config, int argc, char **argv)
+{
+ const char *sopt = "hVvdm:p:l:f:F::b:s:t:D";
+ int opt_ind = 0, ch;
+ const struct option lopt[] = {
+ { "help", 0, NULL, 'h' },
+ { "version", 0, NULL, 'V' },
+ { "dump-conf", 0, NULL, 'D' },
+ { "logfile", 1, NULL, 'l' },
+ { "pidfile", 1, NULL, 'f' },
+#ifdef CONFIG_FSFREEZE
+ { "fsfreeze-hook", 2, NULL, 'F' },
+#endif
+ { "verbose", 0, NULL, 'v' },
+ { "method", 1, NULL, 'm' },
+ { "path", 1, NULL, 'p' },
+ { "daemonize", 0, NULL, 'd' },
+ { "blacklist", 1, NULL, 'b' },
+#ifdef _WIN32
+ { "service", 1, NULL, 's' },
+#endif
+ { "statedir", 1, NULL, 't' },
+ { NULL, 0, NULL, 0 }
+ };
+
+ while ((ch = getopt_long(argc, argv, sopt, lopt, &opt_ind)) != -1) {
+ switch (ch) {
+ case 'm':
+ g_free(config->method);
+ config->method = g_strdup(optarg);
+ break;
+ case 'p':
+ g_free(config->channel_path);
+ config->channel_path = g_strdup(optarg);
+ break;
+ case 'l':
+ g_free(config->log_filepath);
+ config->log_filepath = g_strdup(optarg);
+ break;
+ case 'f':
+ g_free(config->pid_filepath);
+ config->pid_filepath = g_strdup(optarg);
+ break;
+#ifdef CONFIG_FSFREEZE
+ case 'F':
+ g_free(config->fsfreeze_hook);
+ config->fsfreeze_hook = g_strdup(optarg ?: QGA_FSFREEZE_HOOK_DEFAULT);
+ break;
+#endif
+ case 't':
+ g_free(config->state_dir);
+ config->state_dir = g_strdup(optarg);
+ break;
+ case 'v':
+ /* enable all log levels */
+ config->log_level = G_LOG_LEVEL_MASK;
+ break;
+ case 'V':
+ printf("QEMU Guest Agent %s\n", QEMU_VERSION);
+ exit(EXIT_SUCCESS);
+ case 'd':
+ config->daemonize = 1;
+ break;
+ case 'D':
+ config->dumpconf = 1;
+ break;
+ case 'b': {
+ if (is_help_option(optarg)) {
+ qmp_for_each_command(ga_print_cmd, NULL);
+ exit(EXIT_SUCCESS);
+ }
+ config->blacklist = g_list_concat(config->blacklist,
+ split_list(optarg, ","));
+ break;
+ }
+#ifdef _WIN32
+ case 's':
+ config->service = optarg;
+ if (strcmp(config->service, "install") == 0) {
+ if (ga_install_vss_provider()) {
+ exit(EXIT_FAILURE);
+ }
+ if (ga_install_service(config->channel_path,
+ config->log_filepath, config->state_dir)) {
+ exit(EXIT_FAILURE);
+ }
+ exit(EXIT_SUCCESS);
+ } else if (strcmp(config->service, "uninstall") == 0) {
+ ga_uninstall_vss_provider();
+ exit(ga_uninstall_service());
+ } else if (strcmp(config->service, "vss-install") == 0) {
+ if (ga_install_vss_provider()) {
+ exit(EXIT_FAILURE);
+ }
+ exit(EXIT_SUCCESS);
+ } else if (strcmp(config->service, "vss-uninstall") == 0) {
+ ga_uninstall_vss_provider();
+ exit(EXIT_SUCCESS);
+ } else {
+ printf("Unknown service command.\n");
+ exit(EXIT_FAILURE);
+ }
+ break;
+#endif
+ case 'h':
+ usage(argv[0]);
+ exit(EXIT_SUCCESS);
+ case '?':
+ g_print("Unknown option, try '%s --help' for more information.\n",
+ argv[0]);
+ exit(EXIT_FAILURE);
+ }
+ }
+}
+
+static void config_free(GAConfig *config)
+{
+ g_free(config->method);
+ g_free(config->log_filepath);
+ g_free(config->pid_filepath);
+ g_free(config->state_dir);
+ g_free(config->channel_path);
+ g_free(config->bliststr);
+#ifdef CONFIG_FSFREEZE
+ g_free(config->fsfreeze_hook);
+#endif
+ g_free(config);
+}
+
+static bool check_is_frozen(GAState *s)
+{
+#ifndef _WIN32
+ /* check if a previous instance of qemu-ga exited with filesystems' state
+ * marked as frozen. this could be a stale value (a non-qemu-ga process
+ * or reboot may have since unfrozen them), but better to require an
+ * uneeded unfreeze than to risk hanging on start-up
+ */
+ struct stat st;
+ if (stat(s->state_filepath_isfrozen, &st) == -1) {
+ /* it's okay if the file doesn't exist, but if we can't access for
+ * some other reason, such as permissions, there's a configuration
+ * that needs to be addressed. so just bail now before we get into
+ * more trouble later
+ */
+ if (errno != ENOENT) {
+ g_critical("unable to access state file at path %s: %s",
+ s->state_filepath_isfrozen, strerror(errno));
+ return EXIT_FAILURE;
+ }
+ } else {
+ g_warning("previous instance appears to have exited with frozen"
+ " filesystems. deferring logging/pidfile creation and"
+ " disabling non-fsfreeze-safe commands until"
+ " guest-fsfreeze-thaw is issued, or filesystems are"
+ " manually unfrozen and the file %s is removed",
+ s->state_filepath_isfrozen);
+ return true;
+ }
+#endif
+ return false;
+}
+
+static int run_agent(GAState *s, GAConfig *config)
+{
+ ga_state = s;
+
+ g_log_set_default_handler(ga_log, s);
+ g_log_set_fatal_mask(NULL, G_LOG_LEVEL_ERROR);
+ ga_enable_logging(s);
+
+#ifdef _WIN32
+ /* On win32 the state directory is application specific (be it the default
+ * or a user override). We got past the command line parsing; let's create
+ * the directory (with any intermediate directories). If we run into an
+ * error later on, we won't try to clean up the directory, it is considered
+ * persistent.
+ */
+ 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;
+ }
+#endif
+
+ if (ga_is_frozen(s)) {
+ if (config->daemonize) {
+ /* delay opening/locking of pidfile till filesystems are unfrozen */
+ s->deferred_options.pid_filepath = config->pid_filepath;
+ become_daemon(NULL);
+ }
+ if (config->log_filepath) {
+ /* delay opening the log file till filesystems are unfrozen */
+ s->deferred_options.log_filepath = config->log_filepath;
+ }
+ ga_disable_logging(s);
+ qmp_for_each_command(ga_disable_non_whitelisted, NULL);
+ } else {
+ if (config->daemonize) {
+ become_daemon(config->pid_filepath);
+ }
+ if (config->log_filepath) {
+ FILE *log_file = ga_open_logfile(config->log_filepath);
+ if (!log_file) {
+ g_critical("unable to open specified log file: %s",
+ strerror(errno));
+ return EXIT_FAILURE;
+ }
+ s->log_file = log_file;
+ }
+ }
+
+ /* load persistent state from disk */
+ if (!read_persistent_state(&s->pstate,
+ s->pstate_filepath,
+ ga_is_frozen(s))) {
+ g_critical("failed to load persistent state");
+ return EXIT_FAILURE;
+ }
+
+ config->blacklist = ga_command_blacklist_init(config->blacklist);
+ if (config->blacklist) {
+ GList *l = config->blacklist;
+ s->blacklist = config->blacklist;
+ do {
+ g_debug("disabling command: %s", (char *)l->data);
+ qmp_disable_command(l->data);
+ l = g_list_next(l);
+ } while (l);
+ }
+ s->command_state = ga_command_state_new();
+ ga_command_state_init(s, s->command_state);
+ ga_command_state_init_all(s->command_state);
+ json_message_parser_init(&s->parser, process_event);
+ ga_state = s;
+#ifndef _WIN32
+ if (!register_signal_handlers()) {
+ g_critical("failed to register signal handlers");
+ return EXIT_FAILURE;
+ }
+#endif
+
+ s->main_loop = g_main_loop_new(NULL, false);
+ if (!channel_init(ga_state, config->method, config->channel_path)) {
+ g_critical("failed to initialize guest agent channel");
+ return EXIT_FAILURE;
+ }
+#ifndef _WIN32
+ g_main_loop_run(ga_state->main_loop);
+#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);
+ }
+#endif
+
+ return EXIT_SUCCESS;
+}
+
+static void free_blacklist_entry(gpointer entry, gpointer unused)
+{
+ g_free(entry);
+}
+
+int main(int argc, char **argv)
+{
+ int ret = EXIT_SUCCESS;
+ GAState *s = g_new0(GAState, 1);
+ GAConfig *config = g_new0(GAConfig, 1);
+
+ config->log_level = G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL;
+
+ module_call_init(MODULE_INIT_QAPI);
+
+ init_dfl_pathnames();
+ config_load(config);
+ config_parse(config, argc, argv);
+
+ if (config->pid_filepath == NULL) {
+ config->pid_filepath = g_strdup(dfl_pathnames.pidfile);
+ }
+
+ if (config->state_dir == NULL) {
+ config->state_dir = g_strdup(dfl_pathnames.state_dir);
+ }
+
+ if (config->method == NULL) {
+ config->method = g_strdup("virtio-serial");
+ }
+
+ if (config->channel_path == NULL) {
+ if (strcmp(config->method, "virtio-serial") == 0) {
+ /* try the default path for the virtio-serial port */
+ config->channel_path = g_strdup(QGA_VIRTIO_PATH_DEFAULT);
+ } else if (strcmp(config->method, "isa-serial") == 0) {
+ /* try the default path for the serial port - COM1 */
+ config->channel_path = g_strdup(QGA_SERIAL_PATH_DEFAULT);
+ } else {
+ g_critical("must specify a path for this channel");
+ ret = EXIT_FAILURE;
+ goto end;
+ }
+ }
+
+ 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);
+
+end:
+ if (s->command_state) {
+ ga_command_state_cleanup_all(s->command_state);
+ }
+ if (s->channel) {
+ ga_channel_free(s->channel);
+ }
+ g_list_foreach(config->blacklist, free_blacklist_entry, NULL);
+ g_free(s->pstate_filepath);
+ g_free(s->state_filepath_isfrozen);
+
+ if (config->daemonize) {
+ unlink(config->pid_filepath);
+ }
+
+ config_free(config);
+
+ return ret;
+}
diff --git a/src/qga/qapi-schema.json b/src/qga/qapi-schema.json
new file mode 100644
index 0000000..01c9ee4
--- /dev/null
+++ b/src/qga/qapi-schema.json
@@ -0,0 +1,999 @@
+# *-*- Mode: Python -*-*
+
+##
+#
+# General note concerning the use of guest agent interfaces:
+#
+# "unsupported" is a higher-level error than the errors that individual
+# commands might document. The caller should always be prepared to receive
+# QERR_UNSUPPORTED, even if the given command doesn't specify it, or doesn't
+# document any failure mode at all.
+#
+##
+
+##
+# @guest-sync-delimited:
+#
+# Echo back a unique integer value, and prepend to response a
+# leading sentinel byte (0xFF) the client can check scan for.
+#
+# This is used by clients talking to the guest agent over the
+# wire to ensure the stream is in sync and doesn't contain stale
+# data from previous client. It must be issued upon initial
+# connection, and after any client-side timeouts (including
+# timeouts on receiving a response to this command).
+#
+# After issuing this request, all guest agent responses should be
+# ignored until the response containing the unique integer value
+# the client passed in is returned. Receival of the 0xFF sentinel
+# byte must be handled as an indication that the client's
+# lexer/tokenizer/parser state should be flushed/reset in
+# preparation for reliably receiving the subsequent response. As
+# an optimization, clients may opt to ignore all data until a
+# sentinel value is receiving to avoid unnecessary processing of
+# stale data.
+#
+# Similarly, clients should also precede this *request*
+# with a 0xFF byte to make sure the guest agent flushes any
+# partially read JSON data from a previous client connection.
+#
+# @id: randomly generated 64-bit integer
+#
+# Returns: The unique integer id passed in by the client
+#
+# Since: 1.1
+##
+{ 'command': 'guest-sync-delimited',
+ 'data': { 'id': 'int' },
+ 'returns': 'int' }
+
+##
+# @guest-sync:
+#
+# Echo back a unique integer value
+#
+# This is used by clients talking to the guest agent over the
+# wire to ensure the stream is in sync and doesn't contain stale
+# data from previous client. All guest agent responses should be
+# ignored until the provided unique integer value is returned,
+# and it is up to the client to handle stale whole or
+# partially-delivered JSON text in such a way that this response
+# can be obtained.
+#
+# In cases where a partial stale response was previously
+# received by the client, this cannot always be done reliably.
+# One particular scenario being if qemu-ga responses are fed
+# character-by-character into a JSON parser. In these situations,
+# using guest-sync-delimited may be optimal.
+#
+# For clients that fetch responses line by line and convert them
+# to JSON objects, guest-sync should be sufficient, but note that
+# in cases where the channel is dirty some attempts at parsing the
+# response may result in a parser error.
+#
+# Such clients should also precede this command
+# with a 0xFF byte to make sure the guest agent flushes any
+# partially read JSON data from a previous session.
+#
+# @id: randomly generated 64-bit integer
+#
+# Returns: The unique integer id passed in by the client
+#
+# Since: 0.15.0
+##
+{ 'command': 'guest-sync',
+ 'data': { 'id': 'int' },
+ 'returns': 'int' }
+
+##
+# @guest-ping:
+#
+# Ping the guest agent, a non-error return implies success
+#
+# Since: 0.15.0
+##
+{ 'command': 'guest-ping' }
+
+##
+# @guest-get-time:
+#
+# Get the information about guest's System Time relative to
+# the Epoch of 1970-01-01 in UTC.
+#
+# Returns: Time in nanoseconds.
+#
+# Since 1.5
+##
+{ 'command': 'guest-get-time',
+ 'returns': 'int' }
+
+##
+# @guest-set-time:
+#
+# Set guest time.
+#
+# When a guest is paused or migrated to a file then loaded
+# from that file, the guest OS has no idea that there
+# was a big gap in the time. Depending on how long the
+# gap was, NTP might not be able to resynchronize the
+# guest.
+#
+# This command tries to set guest's System Time to the
+# given value, then sets the Hardware Clock (RTC) to the
+# current System Time. This will make it easier for a guest
+# to resynchronize without waiting for NTP. If no @time is
+# specified, then the time to set is read from RTC. However,
+# this may not be supported on all platforms (i.e. Windows).
+# If that's the case users are advised to always pass a
+# value.
+#
+# @time: #optional time of nanoseconds, relative to the Epoch
+# of 1970-01-01 in UTC.
+#
+# Returns: Nothing on success.
+#
+# Since: 1.5
+##
+{ 'command': 'guest-set-time',
+ 'data': { '*time': 'int' } }
+
+##
+# @GuestAgentCommandInfo:
+#
+# Information about guest agent commands.
+#
+# @name: name of the command
+#
+# @enabled: whether command is currently enabled by guest admin
+#
+# @success-response: whether command returns a response on success
+# (since 1.7)
+#
+# Since 1.1.0
+##
+{ 'struct': 'GuestAgentCommandInfo',
+ 'data': { 'name': 'str', 'enabled': 'bool', 'success-response': 'bool' } }
+
+##
+# @GuestAgentInfo
+#
+# Information about guest agent.
+#
+# @version: guest agent version
+#
+# @supported_commands: Information about guest agent commands
+#
+# Since 0.15.0
+##
+{ 'struct': 'GuestAgentInfo',
+ 'data': { 'version': 'str',
+ 'supported_commands': ['GuestAgentCommandInfo'] } }
+##
+# @guest-info:
+#
+# Get some information about the guest agent.
+#
+# Returns: @GuestAgentInfo
+#
+# Since: 0.15.0
+##
+{ 'command': 'guest-info',
+ 'returns': 'GuestAgentInfo' }
+
+##
+# @guest-shutdown:
+#
+# Initiate guest-activated shutdown. Note: this is an asynchronous
+# shutdown request, with no guarantee of successful shutdown.
+#
+# @mode: #optional "halt", "powerdown" (default), or "reboot"
+#
+# This command does NOT return a response on success. Success condition
+# is indicated by the VM exiting with a zero exit status or, when
+# running with --no-shutdown, by issuing the query-status QMP command
+# to confirm the VM status is "shutdown".
+#
+# Since: 0.15.0
+##
+{ 'command': 'guest-shutdown', 'data': { '*mode': 'str' },
+ 'success-response': false }
+
+##
+# @guest-file-open:
+#
+# Open a file in the guest and retrieve a file handle for it
+#
+# @filepath: Full path to the file in the guest to open.
+#
+# @mode: #optional open mode, as per fopen(), "r" is the default.
+#
+# Returns: Guest file handle on success.
+#
+# Since: 0.15.0
+##
+{ 'command': 'guest-file-open',
+ 'data': { 'path': 'str', '*mode': 'str' },
+ 'returns': 'int' }
+
+##
+# @guest-file-close:
+#
+# Close an open file in the guest
+#
+# @handle: filehandle returned by guest-file-open
+#
+# Returns: Nothing on success.
+#
+# Since: 0.15.0
+##
+{ 'command': 'guest-file-close',
+ 'data': { 'handle': 'int' } }
+
+##
+# @GuestFileRead
+#
+# Result of guest agent file-read operation
+#
+# @count: number of bytes read (note: count is *before*
+# base64-encoding is applied)
+#
+# @buf-b64: base64-encoded bytes read
+#
+# @eof: whether EOF was encountered during read operation.
+#
+# Since: 0.15.0
+##
+{ 'struct': 'GuestFileRead',
+ 'data': { 'count': 'int', 'buf-b64': 'str', 'eof': 'bool' } }
+
+##
+# @guest-file-read:
+#
+# Read from an open file in the guest. Data will be base64-encoded
+#
+# @handle: filehandle returned by guest-file-open
+#
+# @count: #optional maximum number of bytes to read (default is 4KB)
+#
+# Returns: @GuestFileRead on success.
+#
+# Since: 0.15.0
+##
+{ 'command': 'guest-file-read',
+ 'data': { 'handle': 'int', '*count': 'int' },
+ 'returns': 'GuestFileRead' }
+
+##
+# @GuestFileWrite
+#
+# Result of guest agent file-write operation
+#
+# @count: number of bytes written (note: count is actual bytes
+# written, after base64-decoding of provided buffer)
+#
+# @eof: whether EOF was encountered during write operation.
+#
+# Since: 0.15.0
+##
+{ 'struct': 'GuestFileWrite',
+ 'data': { 'count': 'int', 'eof': 'bool' } }
+
+##
+# @guest-file-write:
+#
+# Write to an open file in the guest.
+#
+# @handle: filehandle returned by guest-file-open
+#
+# @buf-b64: base64-encoded string representing data to be written
+#
+# @count: #optional bytes to write (actual bytes, after base64-decode),
+# default is all content in buf-b64 buffer after base64 decoding
+#
+# Returns: @GuestFileWrite on success.
+#
+# Since: 0.15.0
+##
+{ 'command': 'guest-file-write',
+ 'data': { 'handle': 'int', 'buf-b64': 'str', '*count': 'int' },
+ 'returns': 'GuestFileWrite' }
+
+
+##
+# @GuestFileSeek
+#
+# Result of guest agent file-seek operation
+#
+# @position: current file position
+#
+# @eof: whether EOF was encountered during file seek
+#
+# Since: 0.15.0
+##
+{ 'struct': 'GuestFileSeek',
+ 'data': { 'position': 'int', 'eof': 'bool' } }
+
+##
+# @guest-file-seek:
+#
+# Seek to a position in the file, as with fseek(), and return the
+# current file position afterward. Also encapsulates ftell()'s
+# functionality, with offset=0 and whence=1.
+#
+# @handle: filehandle returned by guest-file-open
+#
+# @offset: bytes to skip over in the file stream
+#
+# @whence: 0 for SEEK_SET, 1 for SEEK_CUR, or 2 for SEEK_END
+#
+# Returns: @GuestFileSeek on success.
+#
+# Since: 0.15.0
+##
+{ 'command': 'guest-file-seek',
+ 'data': { 'handle': 'int', 'offset': 'int', 'whence': 'int' },
+ 'returns': 'GuestFileSeek' }
+
+##
+# @guest-file-flush:
+#
+# Write file changes bufferred in userspace to disk/kernel buffers
+#
+# @handle: filehandle returned by guest-file-open
+#
+# Returns: Nothing on success.
+#
+# Since: 0.15.0
+##
+{ 'command': 'guest-file-flush',
+ 'data': { 'handle': 'int' } }
+
+##
+# @GuestFsFreezeStatus
+#
+# An enumeration of filesystem freeze states
+#
+# @thawed: filesystems thawed/unfrozen
+#
+# @frozen: all non-network guest filesystems frozen
+#
+# Since: 0.15.0
+##
+{ 'enum': 'GuestFsfreezeStatus',
+ 'data': [ 'thawed', 'frozen' ] }
+
+##
+# @guest-fsfreeze-status:
+#
+# Get guest fsfreeze state. error state indicates
+#
+# Returns: GuestFsfreezeStatus ("thawed", "frozen", etc., as defined below)
+#
+# Note: This may fail to properly report the current state as a result of
+# some other guest processes having issued an fs freeze/thaw.
+#
+# Since: 0.15.0
+##
+{ 'command': 'guest-fsfreeze-status',
+ 'returns': 'GuestFsfreezeStatus' }
+
+##
+# @guest-fsfreeze-freeze:
+#
+# Sync and freeze all freezable, local guest filesystems
+#
+# Returns: Number of file systems currently frozen. On error, all filesystems
+# will be thawed.
+#
+# Since: 0.15.0
+##
+{ 'command': 'guest-fsfreeze-freeze',
+ 'returns': 'int' }
+
+##
+# @guest-fsfreeze-freeze-list:
+#
+# Sync and freeze specified guest filesystems
+#
+# @mountpoints: #optional an array of mountpoints of filesystems to be frozen.
+# If omitted, every mounted filesystem is frozen.
+#
+# Returns: Number of file systems currently frozen. On error, all filesystems
+# will be thawed.
+#
+# Since: 2.2
+##
+{ 'command': 'guest-fsfreeze-freeze-list',
+ 'data': { '*mountpoints': ['str'] },
+ 'returns': 'int' }
+
+##
+# @guest-fsfreeze-thaw:
+#
+# Unfreeze all frozen guest filesystems
+#
+# Returns: Number of file systems thawed by this call
+#
+# Note: if return value does not match the previous call to
+# guest-fsfreeze-freeze, this likely means some freezable
+# filesystems were unfrozen before this call, and that the
+# filesystem state may have changed before issuing this
+# command.
+#
+# Since: 0.15.0
+##
+{ 'command': 'guest-fsfreeze-thaw',
+ 'returns': 'int' }
+
+##
+# @GuestFilesystemTrimResult
+#
+# @path: path that was trimmed
+# @error: an error message when trim failed
+# @trimmed: bytes trimmed for this path
+# @minimum: reported effective minimum for this path
+#
+# Since: 2.4
+##
+{ 'struct': 'GuestFilesystemTrimResult',
+ 'data': {'path': 'str',
+ '*trimmed': 'int', '*minimum': 'int', '*error': 'str'} }
+
+##
+# @GuestFilesystemTrimResponse
+#
+# @paths: list of @GuestFilesystemTrimResult per path that was trimmed
+#
+# Since: 2.4
+##
+{ 'struct': 'GuestFilesystemTrimResponse',
+ 'data': {'paths': ['GuestFilesystemTrimResult']} }
+
+##
+# @guest-fstrim:
+#
+# Discard (or "trim") blocks which are not in use by the filesystem.
+#
+# @minimum:
+# Minimum contiguous free range to discard, in bytes. Free ranges
+# smaller than this may be ignored (this is a hint and the guest
+# may not respect it). By increasing this value, the fstrim
+# operation will complete more quickly for filesystems with badly
+# fragmented free space, although not all blocks will be discarded.
+# The default value is zero, meaning "discard every free block".
+#
+# Returns: A @GuestFilesystemTrimResponse which contains the
+# status of all trimmed paths. (since 2.4)
+#
+# Since: 1.2
+##
+{ 'command': 'guest-fstrim',
+ 'data': { '*minimum': 'int' },
+ 'returns': 'GuestFilesystemTrimResponse' }
+
+##
+# @guest-suspend-disk
+#
+# Suspend guest to disk.
+#
+# This command tries to execute the scripts provided by the pm-utils package.
+# If it's not available, the suspend operation will be performed by manually
+# writing to a sysfs file.
+#
+# For the best results it's strongly recommended to have the pm-utils
+# package installed in the guest.
+#
+# This command does NOT return a response on success. There is a high chance
+# the command succeeded if the VM exits with a zero exit status or, when
+# running with --no-shutdown, by issuing the query-status QMP command to
+# to confirm the VM status is "shutdown". However, the VM could also exit
+# (or set its status to "shutdown") due to other reasons.
+#
+# The following errors may be returned:
+# If suspend to disk is not supported, Unsupported
+#
+# Notes: It's strongly recommended to issue the guest-sync command before
+# sending commands when the guest resumes
+#
+# Since: 1.1
+##
+{ 'command': 'guest-suspend-disk', 'success-response': false }
+
+##
+# @guest-suspend-ram
+#
+# Suspend guest to ram.
+#
+# This command tries to execute the scripts provided by the pm-utils package.
+# If it's not available, the suspend operation will be performed by manually
+# writing to a sysfs file.
+#
+# For the best results it's strongly recommended to have the pm-utils
+# package installed in the guest.
+#
+# IMPORTANT: guest-suspend-ram requires QEMU to support the 'system_wakeup'
+# command. Thus, it's *required* to query QEMU for the presence of the
+# 'system_wakeup' command before issuing guest-suspend-ram.
+#
+# This command does NOT return a response on success. There are two options
+# to check for success:
+# 1. Wait for the SUSPEND QMP event from QEMU
+# 2. Issue the query-status QMP command to confirm the VM status is
+# "suspended"
+#
+# The following errors may be returned:
+# If suspend to ram is not supported, Unsupported
+#
+# Notes: It's strongly recommended to issue the guest-sync command before
+# sending commands when the guest resumes
+#
+# Since: 1.1
+##
+{ 'command': 'guest-suspend-ram', 'success-response': false }
+
+##
+# @guest-suspend-hybrid
+#
+# Save guest state to disk and suspend to ram.
+#
+# This command requires the pm-utils package to be installed in the guest.
+#
+# IMPORTANT: guest-suspend-hybrid requires QEMU to support the 'system_wakeup'
+# command. Thus, it's *required* to query QEMU for the presence of the
+# 'system_wakeup' command before issuing guest-suspend-hybrid.
+#
+# This command does NOT return a response on success. There are two options
+# to check for success:
+# 1. Wait for the SUSPEND QMP event from QEMU
+# 2. Issue the query-status QMP command to confirm the VM status is
+# "suspended"
+#
+# The following errors may be returned:
+# If hybrid suspend is not supported, Unsupported
+#
+# Notes: It's strongly recommended to issue the guest-sync command before
+# sending commands when the guest resumes
+#
+# Since: 1.1
+##
+{ 'command': 'guest-suspend-hybrid', 'success-response': false }
+
+##
+# @GuestIpAddressType:
+#
+# An enumeration of supported IP address types
+#
+# @ipv4: IP version 4
+#
+# @ipv6: IP version 6
+#
+# Since: 1.1
+##
+{ 'enum': 'GuestIpAddressType',
+ 'data': [ 'ipv4', 'ipv6' ] }
+
+##
+# @GuestIpAddress:
+#
+# @ip-address: IP address
+#
+# @ip-address-type: Type of @ip-address (e.g. ipv4, ipv6)
+#
+# @prefix: Network prefix length of @ip-address
+#
+# Since: 1.1
+##
+{ 'struct': 'GuestIpAddress',
+ 'data': {'ip-address': 'str',
+ 'ip-address-type': 'GuestIpAddressType',
+ 'prefix': 'int'} }
+
+##
+# @GuestNetworkInterface:
+#
+# @name: The name of interface for which info are being delivered
+#
+# @hardware-address: Hardware address of @name
+#
+# @ip-addresses: List of addresses assigned to @name
+#
+# Since: 1.1
+##
+{ 'struct': 'GuestNetworkInterface',
+ 'data': {'name': 'str',
+ '*hardware-address': 'str',
+ '*ip-addresses': ['GuestIpAddress'] } }
+
+##
+# @guest-network-get-interfaces:
+#
+# Get list of guest IP addresses, MAC addresses
+# and netmasks.
+#
+# Returns: List of GuestNetworkInfo on success.
+#
+# Since: 1.1
+##
+{ 'command': 'guest-network-get-interfaces',
+ 'returns': ['GuestNetworkInterface'] }
+
+##
+# @GuestLogicalProcessor:
+#
+# @logical-id: Arbitrary guest-specific unique identifier of the VCPU.
+#
+# @online: Whether the VCPU is enabled.
+#
+# @can-offline: #optional Whether offlining the VCPU is possible. This member
+# is always filled in by the guest agent when the structure is
+# returned, and always ignored on input (hence it can be omitted
+# then).
+#
+# Since: 1.5
+##
+{ 'struct': 'GuestLogicalProcessor',
+ 'data': {'logical-id': 'int',
+ 'online': 'bool',
+ '*can-offline': 'bool'} }
+
+##
+# @guest-get-vcpus:
+#
+# Retrieve the list of the guest's logical processors.
+#
+# This is a read-only operation.
+#
+# Returns: The list of all VCPUs the guest knows about. Each VCPU is put on the
+# list exactly once, but their order is unspecified.
+#
+# Since: 1.5
+##
+{ 'command': 'guest-get-vcpus',
+ 'returns': ['GuestLogicalProcessor'] }
+
+##
+# @guest-set-vcpus:
+#
+# Attempt to reconfigure (currently: enable/disable) logical processors inside
+# the guest.
+#
+# The input list is processed node by node in order. In each node @logical-id
+# is used to look up the guest VCPU, for which @online specifies the requested
+# state. The set of distinct @logical-id's is only required to be a subset of
+# the guest-supported identifiers. There's no restriction on list length or on
+# repeating the same @logical-id (with possibly different @online field).
+# Preferably the input list should describe a modified subset of
+# @guest-get-vcpus' return value.
+#
+# Returns: The length of the initial sublist that has been successfully
+# processed. The guest agent maximizes this value. Possible cases:
+#
+# 0: if the @vcpus list was empty on input. Guest state
+# has not been changed. Otherwise,
+#
+# Error: processing the first node of @vcpus failed for the
+# reason returned. Guest state has not been changed.
+# Otherwise,
+#
+# < length(@vcpus): more than zero initial nodes have been processed,
+# but not the entire @vcpus list. Guest state has
+# changed accordingly. To retrieve the error
+# (assuming it persists), repeat the call with the
+# successfully processed initial sublist removed.
+# Otherwise,
+#
+# length(@vcpus): call successful.
+#
+# Since: 1.5
+##
+{ 'command': 'guest-set-vcpus',
+ 'data': {'vcpus': ['GuestLogicalProcessor'] },
+ 'returns': 'int' }
+
+##
+# @GuestDiskBusType
+#
+# An enumeration of bus type of disks
+#
+# @ide: IDE disks
+# @fdc: floppy disks
+# @scsi: SCSI disks
+# @virtio: virtio disks
+# @xen: Xen disks
+# @usb: USB disks
+# @uml: UML disks
+# @sata: SATA disks
+# @sd: SD cards
+# @unknown: Unknown bus type
+# @ieee1394: Win IEEE 1394 bus type
+# @ssa: Win SSA bus type
+# @fibre: Win fiber channel bus type
+# @raid: Win RAID bus type
+# @iscsi: Win iScsi bus type
+# @sas: Win serial-attaches SCSI bus type
+# @mmc: Win multimedia card (MMC) bus type
+# @virtual: Win virtual bus type
+# @file-backed virtual: Win file-backed bus type
+#
+# Since: 2.2; 'Unknown' and all entries below since 2.4
+##
+{ 'enum': 'GuestDiskBusType',
+ 'data': [ 'ide', 'fdc', 'scsi', 'virtio', 'xen', 'usb', 'uml', 'sata',
+ 'sd', 'unknown', 'ieee1394', 'ssa', 'fibre', 'raid', 'iscsi',
+ 'sas', 'mmc', 'virtual', 'file-backed-virtual' ] }
+
+
+##
+# @GuestPCIAddress:
+#
+# @domain: domain id
+# @bus: bus id
+# @slot: slot id
+# @function: function id
+#
+# Since: 2.2
+##
+{ 'struct': 'GuestPCIAddress',
+ 'data': {'domain': 'int', 'bus': 'int',
+ 'slot': 'int', 'function': 'int'} }
+
+##
+# @GuestDiskAddress:
+#
+# @pci-controller: controller's PCI address
+# @type: bus type
+# @bus: bus id
+# @target: target id
+# @unit: unit id
+#
+# Since: 2.2
+##
+{ 'struct': 'GuestDiskAddress',
+ 'data': {'pci-controller': 'GuestPCIAddress',
+ 'bus-type': 'GuestDiskBusType',
+ 'bus': 'int', 'target': 'int', 'unit': 'int'} }
+
+##
+# @GuestFilesystemInfo
+#
+# @name: disk name
+# @mountpoint: mount point path
+# @type: file system type string
+# @disk: an array of disk hardware information that the volume lies on,
+# which may be empty if the disk type is not supported
+#
+# Since: 2.2
+##
+{ 'struct': 'GuestFilesystemInfo',
+ 'data': {'name': 'str', 'mountpoint': 'str', 'type': 'str',
+ 'disk': ['GuestDiskAddress']} }
+
+##
+# @guest-get-fsinfo:
+#
+# Returns: The list of filesystems information mounted in the guest.
+# The returned mountpoints may be specified to
+# @guest-fsfreeze-freeze-list.
+# Network filesystems (such as CIFS and NFS) are not listed.
+#
+# Since: 2.2
+##
+{ 'command': 'guest-get-fsinfo',
+ 'returns': ['GuestFilesystemInfo'] }
+
+##
+# @guest-set-user-password
+#
+# @username: the user account whose password to change
+# @password: the new password entry string, base64 encoded
+# @crypted: true if password is already crypt()d, false if raw
+#
+# If the @crypted flag is true, it is the caller's responsibility
+# to ensure the correct crypt() encryption scheme is used. This
+# command does not attempt to interpret or report on the encryption
+# scheme. Refer to the documentation of the guest operating system
+# in question to determine what is supported.
+#
+# Not all guest operating systems will support use of the
+# @crypted flag, as they may require the clear-text password
+#
+# The @password parameter must always be base64 encoded before
+# transmission, even if already crypt()d, to ensure it is 8-bit
+# safe when passed as JSON.
+#
+# Returns: Nothing on success.
+#
+# Since 2.3
+##
+{ 'command': 'guest-set-user-password',
+ 'data': { 'username': 'str', 'password': 'str', 'crypted': 'bool' } }
+
+# @GuestMemoryBlock:
+#
+# @phys-index: Arbitrary guest-specific unique identifier of the MEMORY BLOCK.
+#
+# @online: Whether the MEMORY BLOCK is enabled in guest.
+#
+# @can-offline: #optional Whether offlining the MEMORY BLOCK is possible.
+# This member is always filled in by the guest agent when the
+# structure is returned, and always ignored on input (hence it
+# can be omitted then).
+#
+# Since: 2.3
+##
+{ 'struct': 'GuestMemoryBlock',
+ 'data': {'phys-index': 'uint64',
+ 'online': 'bool',
+ '*can-offline': 'bool'} }
+
+##
+# @guest-get-memory-blocks:
+#
+# Retrieve the list of the guest's memory blocks.
+#
+# This is a read-only operation.
+#
+# Returns: The list of all memory blocks the guest knows about.
+# Each memory block is put on the list exactly once, but their order
+# is unspecified.
+#
+# Since: 2.3
+##
+{ 'command': 'guest-get-memory-blocks',
+ 'returns': ['GuestMemoryBlock'] }
+
+##
+# @GuestMemoryBlockResponseType
+#
+# An enumeration of memory block operation result.
+#
+# @success: the operation of online/offline memory block is successful.
+# @not-found: can't find the corresponding memoryXXX directory in sysfs.
+# @operation-not-supported: for some old kernels, it does not support
+# online or offline memory block.
+# @operation-failed: the operation of online/offline memory block fails,
+# because of some errors happen.
+#
+# Since: 2.3
+##
+{ 'enum': 'GuestMemoryBlockResponseType',
+ 'data': ['success', 'not-found', 'operation-not-supported',
+ 'operation-failed'] }
+
+##
+# @GuestMemoryBlockResponse:
+#
+# @phys-index: same with the 'phys-index' member of @GuestMemoryBlock.
+#
+# @response: the result of memory block operation.
+#
+# @error-code: #optional the error number.
+# When memory block operation fails, we assign the value of
+# 'errno' to this member, it indicates what goes wrong.
+# When the operation succeeds, it will be omitted.
+#
+# Since: 2.3
+##
+{ 'struct': 'GuestMemoryBlockResponse',
+ 'data': { 'phys-index': 'uint64',
+ 'response': 'GuestMemoryBlockResponseType',
+ '*error-code': 'int' }}
+
+##
+# @guest-set-memory-blocks:
+#
+# Attempt to reconfigure (currently: enable/disable) state of memory blocks
+# inside the guest.
+#
+# The input list is processed node by node in order. In each node @phys-index
+# is used to look up the guest MEMORY BLOCK, for which @online specifies the
+# requested state. The set of distinct @phys-index's is only required to be a
+# subset of the guest-supported identifiers. There's no restriction on list
+# length or on repeating the same @phys-index (with possibly different @online
+# field).
+# Preferably the input list should describe a modified subset of
+# @guest-get-memory-blocks' return value.
+#
+# Returns: The operation results, it is a list of @GuestMemoryBlockResponse,
+# which is corresponding to the input list.
+#
+# Note: it will return NULL if the @mem-blks list was empty on input,
+# or there is an error, and in this case, guest state will not be
+# changed.
+#
+# Since: 2.3
+##
+{ 'command': 'guest-set-memory-blocks',
+ 'data': {'mem-blks': ['GuestMemoryBlock'] },
+ 'returns': ['GuestMemoryBlockResponse'] }
+
+# @GuestMemoryBlockInfo:
+#
+# @size: the size (in bytes) of the guest memory blocks,
+# which are the minimal units of memory block online/offline
+# operations (also called Logical Memory Hotplug).
+#
+# Since: 2.3
+##
+{ 'struct': 'GuestMemoryBlockInfo',
+ 'data': {'size': 'uint64'} }
+
+##
+# @guest-get-memory-block-info:
+#
+# Get information relating to guest memory blocks.
+#
+# Returns: memory block size in bytes.
+# Returns: @GuestMemoryBlockInfo
+#
+# Since 2.3
+##
+{ 'command': 'guest-get-memory-block-info',
+ 'returns': 'GuestMemoryBlockInfo' }
+
+# @GuestExecStatus:
+#
+# @exited: true if process has already terminated.
+# @exitcode: #optional process exit code if it was normally terminated.
+# @signal: #optional signal number (linux) or unhandled exception code
+# (windows) if the process was abnormally terminated.
+# @out-data: #optional base64-encoded stdout of the process
+# @err-data: #optional base64-encoded stderr of the process
+# Note: @out-data and @err-data are present only
+# if 'capture-output' was specified for 'guest-exec'
+# @out-truncated: #optional true if stdout was not fully captured
+# due to size limitation.
+# @err-truncated: #optional true if stderr was not fully captured
+# due to size limitation.
+#
+# Since: 2.5
+##
+{ 'struct': 'GuestExecStatus',
+ 'data': { 'exited': 'bool', '*exitcode': 'int', '*signal': 'int',
+ '*out-data': 'str', '*err-data': 'str',
+ '*out-truncated': 'bool', '*err-truncated': 'bool' }}
+##
+# @guest-exec-status
+#
+# Check status of process associated with PID retrieved via guest-exec.
+# Reap the process and associated metadata if it has exited.
+#
+# @pid: pid returned from guest-exec
+#
+# Returns: GuestExecStatus on success.
+#
+# Since 2.5
+##
+{ 'command': 'guest-exec-status',
+ 'data': { 'pid': 'int' },
+ 'returns': 'GuestExecStatus' }
+
+##
+# @GuestExec:
+# @pid: pid of child process in guest OS
+#
+#Since: 2.5
+##
+{ 'struct': 'GuestExec',
+ 'data': { 'pid': 'int'} }
+
+##
+# @guest-exec:
+#
+# Execute a command in the guest
+#
+# @path: path or executable name to execute
+# @arg: #optional argument list to pass to executable
+# @env: #optional environment variables to pass to executable
+# @input-data: #optional data to be passed to process stdin (base64 encoded)
+# @capture-output: #optional bool flag to enable capture of
+# stdout/stderr of running process. defaults to false.
+#
+# Returns: PID on success.
+#
+# Since: 2.5
+##
+{ 'command': 'guest-exec',
+ 'data': { 'path': 'str', '*arg': ['str'], '*env': ['str'],
+ '*input-data': 'str', '*capture-output': 'bool' },
+ 'returns': 'GuestExec' }
diff --git a/src/qga/service-win32.c b/src/qga/service-win32.c
new file mode 100644
index 0000000..aef41f0
--- /dev/null
+++ b/src/qga/service-win32.c
@@ -0,0 +1,192 @@
+/*
+ * QEMU Guest Agent helpers for win32 service management
+ *
+ * Copyright IBM Corp. 2012
+ *
+ * Authors:
+ * Gal Hammer <ghammer@redhat.com>
+ * Michael Roth <mdroth@linux.vnet.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+#include <stdlib.h>
+#include <stdio.h>
+#include <glib.h>
+#include <windows.h>
+#include "qga/service-win32.h"
+
+static int printf_win_error(const char *text)
+{
+ DWORD err = GetLastError();
+ char *message;
+ int n;
+
+ FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
+ FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
+ NULL,
+ err,
+ MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+ (char *)&message, 0,
+ NULL);
+ n = fprintf(stderr, "%s. (Error: %d) %s", text, (int)err, message);
+ LocalFree(message);
+
+ return n;
+}
+
+/* Windows command line escaping. Based on
+ * <http://blogs.msdn.com/b/oldnewthing/archive/2010/09/17/10063629.aspx> and
+ * <http://msdn.microsoft.com/en-us/library/windows/desktop/17w5ykft%28v=vs.85%29.aspx>.
+ *
+ * The caller is responsible for initializing @buffer; prior contents are lost.
+ */
+static const char *win_escape_arg(const char *to_escape, GString *buffer)
+{
+ size_t backslash_count;
+ const char *c;
+
+ /* open with a double quote */
+ g_string_assign(buffer, "\"");
+
+ backslash_count = 0;
+ for (c = to_escape; *c != '\0'; ++c) {
+ switch (*c) {
+ case '\\':
+ /* The meaning depends on the first non-backslash character coming
+ * up.
+ */
+ ++backslash_count;
+ break;
+
+ case '"':
+ /* We must escape each pending backslash, then escape the double
+ * quote. This creates a case of "odd number of backslashes [...]
+ * followed by a double quotation mark".
+ */
+ while (backslash_count) {
+ --backslash_count;
+ g_string_append(buffer, "\\\\");
+ }
+ g_string_append(buffer, "\\\"");
+ break;
+
+ default:
+ /* Any pending backslashes are without special meaning, flush them.
+ * "Backslashes are interpreted literally, unless they immediately
+ * precede a double quotation mark."
+ */
+ while (backslash_count) {
+ --backslash_count;
+ g_string_append_c(buffer, '\\');
+ }
+ g_string_append_c(buffer, *c);
+ }
+ }
+
+ /* We're about to close with a double quote in string delimiter role.
+ * Double all pending backslashes, creating a case of "even number of
+ * backslashes [...] followed by a double quotation mark".
+ */
+ while (backslash_count) {
+ --backslash_count;
+ g_string_append(buffer, "\\\\");
+ }
+ g_string_append_c(buffer, '"');
+
+ return buffer->str;
+}
+
+int ga_install_service(const char *path, const char *logfile,
+ const char *state_dir)
+{
+ int ret = EXIT_FAILURE;
+ SC_HANDLE manager;
+ SC_HANDLE service;
+ TCHAR module_fname[MAX_PATH];
+ GString *esc;
+ GString *cmdline;
+ SERVICE_DESCRIPTION desc = { (char *)QGA_SERVICE_DESCRIPTION };
+
+ if (GetModuleFileName(NULL, module_fname, MAX_PATH) == 0) {
+ printf_win_error("No full path to service's executable");
+ return EXIT_FAILURE;
+ }
+
+ esc = g_string_new("");
+ cmdline = g_string_new("");
+
+ g_string_append_printf(cmdline, "%s -d",
+ win_escape_arg(module_fname, esc));
+
+ if (path) {
+ g_string_append_printf(cmdline, " -p %s", win_escape_arg(path, esc));
+ }
+ if (logfile) {
+ g_string_append_printf(cmdline, " -l %s -v",
+ win_escape_arg(logfile, esc));
+ }
+ if (state_dir) {
+ g_string_append_printf(cmdline, " -t %s",
+ win_escape_arg(state_dir, esc));
+ }
+
+ g_debug("service's cmdline: %s", cmdline->str);
+
+ manager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
+ if (manager == NULL) {
+ printf_win_error("No handle to service control manager");
+ goto out_strings;
+ }
+
+ service = CreateService(manager, QGA_SERVICE_NAME, QGA_SERVICE_DISPLAY_NAME,
+ SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS, SERVICE_AUTO_START,
+ SERVICE_ERROR_NORMAL, cmdline->str, NULL, NULL, NULL, NULL, NULL);
+ if (service == NULL) {
+ printf_win_error("Failed to install service");
+ goto out_manager;
+ }
+
+ ChangeServiceConfig2(service, SERVICE_CONFIG_DESCRIPTION, &desc);
+ fprintf(stderr, "Service was installed successfully.\n");
+ ret = EXIT_SUCCESS;
+ CloseServiceHandle(service);
+
+out_manager:
+ CloseServiceHandle(manager);
+
+out_strings:
+ g_string_free(cmdline, TRUE);
+ g_string_free(esc, TRUE);
+ return ret;
+}
+
+int ga_uninstall_service(void)
+{
+ SC_HANDLE manager;
+ SC_HANDLE service;
+
+ manager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
+ if (manager == NULL) {
+ printf_win_error("No handle to service control manager");
+ return EXIT_FAILURE;
+ }
+
+ service = OpenService(manager, QGA_SERVICE_NAME, DELETE);
+ if (service == NULL) {
+ printf_win_error("No handle to service");
+ CloseServiceHandle(manager);
+ return EXIT_FAILURE;
+ }
+
+ if (DeleteService(service) == FALSE) {
+ printf_win_error("Failed to delete service");
+ } else {
+ fprintf(stderr, "Service was deleted successfully.\n");
+ }
+
+ CloseServiceHandle(service);
+ CloseServiceHandle(manager);
+
+ return EXIT_SUCCESS;
+}
diff --git a/src/qga/service-win32.h b/src/qga/service-win32.h
new file mode 100644
index 0000000..3b9e870
--- /dev/null
+++ b/src/qga/service-win32.h
@@ -0,0 +1,31 @@
+/*
+ * QEMU Guest Agent helpers for win32 service management
+ *
+ * Copyright IBM Corp. 2012
+ *
+ * Authors:
+ * Gal Hammer <ghammer@redhat.com>
+ * Michael Roth <mdroth@linux.vnet.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+#ifndef QGA_SERVICE_H
+#define QGA_SERVICE_H
+
+#include <windows.h>
+
+#define QGA_SERVICE_DISPLAY_NAME "QEMU Guest Agent"
+#define QGA_SERVICE_NAME "qemu-ga"
+#define QGA_SERVICE_DESCRIPTION "Enables integration with QEMU machine emulator and virtualizer."
+
+typedef struct GAService {
+ SERVICE_STATUS status;
+ SERVICE_STATUS_HANDLE status_handle;
+} GAService;
+
+int ga_install_service(const char *path, const char *logfile,
+ const char *state_dir);
+int ga_uninstall_service(void);
+
+#endif
diff --git a/src/qga/vss-win32.c b/src/qga/vss-win32.c
new file mode 100644
index 0000000..2142b49
--- /dev/null
+++ b/src/qga/vss-win32.c
@@ -0,0 +1,166 @@
+/*
+ * QEMU Guest Agent VSS utility functions
+ *
+ * Copyright Hitachi Data Systems Corp. 2013
+ *
+ * Authors:
+ * Tomoki Sekiyama <tomoki.sekiyama@hds.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include <stdio.h>
+#include <windows.h>
+#include "qga/guest-agent-core.h"
+#include "qga/vss-win32.h"
+#include "qga/vss-win32/requester.h"
+
+#define QGA_VSS_DLL "qga-vss.dll"
+
+static HMODULE provider_lib;
+
+/* Call a function in qga-vss.dll with the specified name */
+static HRESULT call_vss_provider_func(const char *func_name)
+{
+ FARPROC WINAPI func;
+
+ g_assert(provider_lib);
+
+ func = GetProcAddress(provider_lib, func_name);
+ if (!func) {
+ char *msg;
+ FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
+ FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(),
+ MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+ (char *)&msg, 0, NULL);
+ fprintf(stderr, "failed to load %s from %s: %s",
+ func_name, QGA_VSS_DLL, msg);
+ LocalFree(msg);
+ return E_FAIL;
+ }
+
+ return func();
+}
+
+/* Check whether this OS version supports VSS providers */
+static bool vss_check_os_version(void)
+{
+ OSVERSIONINFO OSver;
+
+ OSver.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
+ GetVersionEx(&OSver);
+ if ((OSver.dwMajorVersion == 5 && OSver.dwMinorVersion >= 2) ||
+ OSver.dwMajorVersion > 5) {
+ BOOL wow64 = false;
+#ifndef _WIN64
+ /* Provider doesn't work under WOW64 (32bit agent on 64bit OS) */
+ if (!IsWow64Process(GetCurrentProcess(), &wow64)) {
+ fprintf(stderr, "failed to IsWow64Process (Error: %lx\n)\n",
+ GetLastError());
+ return false;
+ }
+ if (wow64) {
+ fprintf(stderr, "Warning: Running under WOW64\n");
+ }
+#endif
+ return !wow64;
+ }
+ return false;
+}
+
+/* Load qga-vss.dll */
+bool vss_init(bool init_requester)
+{
+ if (!vss_check_os_version()) {
+ /* Do nothing if OS doesn't support providers. */
+ fprintf(stderr, "VSS provider is not supported in this OS version: "
+ "fsfreeze is disabled.\n");
+ return false;
+ }
+
+ provider_lib = LoadLibraryA(QGA_VSS_DLL);
+ if (!provider_lib) {
+ char *msg;
+ FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
+ FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(),
+ MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+ (char *)&msg, 0, NULL);
+ fprintf(stderr, "failed to load %s: %sfsfreeze is disabled\n",
+ QGA_VSS_DLL, msg);
+ LocalFree(msg);
+ return false;
+ }
+
+ if (init_requester) {
+ HRESULT hr = call_vss_provider_func("requester_init");
+ if (FAILED(hr)) {
+ fprintf(stderr, "fsfreeze is disabled.\n");
+ vss_deinit(false);
+ return false;
+ }
+ }
+
+ return true;
+}
+
+/* Unload qga-provider.dll */
+void vss_deinit(bool deinit_requester)
+{
+ if (deinit_requester) {
+ call_vss_provider_func("requester_deinit");
+ }
+ FreeLibrary(provider_lib);
+ provider_lib = NULL;
+}
+
+bool vss_initialized(void)
+{
+ return !!provider_lib;
+}
+
+int ga_install_vss_provider(void)
+{
+ HRESULT hr;
+
+ if (!vss_init(false)) {
+ fprintf(stderr, "Installation of VSS provider is skipped. "
+ "fsfreeze will be disabled.\n");
+ return 0;
+ }
+ hr = call_vss_provider_func("COMRegister");
+ vss_deinit(false);
+
+ return SUCCEEDED(hr) ? 0 : EXIT_FAILURE;
+}
+
+void ga_uninstall_vss_provider(void)
+{
+ if (!vss_init(false)) {
+ fprintf(stderr, "Removal of VSS provider is skipped.\n");
+ return;
+ }
+ call_vss_provider_func("COMUnregister");
+ vss_deinit(false);
+}
+
+/* Call VSS requester and freeze/thaw filesystems and applications */
+void qga_vss_fsfreeze(int *nr_volume, Error **errp, bool freeze)
+{
+ const char *func_name = freeze ? "requester_freeze" : "requester_thaw";
+ QGAVSSRequesterFunc func;
+ ErrorSet errset = {
+ .error_setg_win32 = error_setg_win32_internal,
+ .errp = errp,
+ };
+
+ g_assert(errp); /* requester.cpp requires it */
+ func = (QGAVSSRequesterFunc)GetProcAddress(provider_lib, func_name);
+ if (!func) {
+ error_setg_win32(errp, GetLastError(), "failed to load %s from %s",
+ func_name, QGA_VSS_DLL);
+ return;
+ }
+
+ func(nr_volume, &errset);
+}
diff --git a/src/qga/vss-win32.h b/src/qga/vss-win32.h
new file mode 100644
index 0000000..298927d
--- /dev/null
+++ b/src/qga/vss-win32.h
@@ -0,0 +1,27 @@
+/*
+ * QEMU Guest Agent VSS utility declarations
+ *
+ * Copyright Hitachi Data Systems Corp. 2013
+ *
+ * Authors:
+ * Tomoki Sekiyama <tomoki.sekiyama@hds.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#ifndef VSS_WIN32_H
+#define VSS_WIN32_H
+
+#include "qapi/error.h"
+
+bool vss_init(bool init_requester);
+void vss_deinit(bool deinit_requester);
+bool vss_initialized(void);
+
+int ga_install_vss_provider(void);
+void ga_uninstall_vss_provider(void);
+
+void qga_vss_fsfreeze(int *nr_volume, Error **errp, bool freeze);
+
+#endif
diff --git a/src/qga/vss-win32/Makefile.objs b/src/qga/vss-win32/Makefile.objs
new file mode 100644
index 0000000..7c96c6b
--- /dev/null
+++ b/src/qga/vss-win32/Makefile.objs
@@ -0,0 +1,23 @@
+# rules to build qga-vss.dll
+
+qga-vss-dll-obj-y += requester.o provider.o install.o
+
+obj-qga-vss-dll-obj-y = $(addprefix $(obj)/, $(qga-vss-dll-obj-y))
+$(obj-qga-vss-dll-obj-y): QEMU_CXXFLAGS = $(filter-out -Wstrict-prototypes -Wmissing-prototypes -Wnested-externs -Wold-style-declaration -Wold-style-definition -Wredundant-decls -fstack-protector-all -fstack-protector-strong, $(QEMU_CFLAGS)) -Wno-unknown-pragmas -Wno-delete-non-virtual-dtor
+
+$(obj)/qga-vss.dll: LDFLAGS = -shared -Wl,--add-stdcall-alias,--enable-stdcall-fixup -lole32 -loleaut32 -lshlwapi -luuid -static
+$(obj)/qga-vss.dll: $(obj-qga-vss-dll-obj-y) $(SRC_PATH)/$(obj)/qga-vss.def
+ $(call quiet-command,$(CXX) -o $@ $(qga-vss-dll-obj-y) $(SRC_PATH)/qga/vss-win32/qga-vss.def $(CXXFLAGS) $(LDFLAGS)," LINK $(TARGET_DIR)$@")
+
+
+# rules to build qga-provider.tlb
+# Currently, only native build is supported because building .tlb
+# (TypeLibrary) from .idl requires WindowsSDK and MIDL (and cl.exe in VC++).
+MIDL=$(WIN_SDK)/Bin/midl
+
+$(obj)/qga-vss.tlb: $(SRC_PATH)/$(obj)/qga-vss.idl
+ifeq ($(WIN_SDK),"")
+ $(call quiet-command,cp $(dir $<)qga-vss.tlb $@, " COPY $(TARGET_DIR)$@")
+else
+ $(call quiet-command,$(MIDL) -tlb $@ -I $(WIN_SDK)/Include $<," MIDL $(TARGET_DIR)$@")
+endif
diff --git a/src/qga/vss-win32/install.cpp b/src/qga/vss-win32/install.cpp
new file mode 100644
index 0000000..b0e4426
--- /dev/null
+++ b/src/qga/vss-win32/install.cpp
@@ -0,0 +1,465 @@
+/*
+ * QEMU Guest Agent win32 VSS Provider installer
+ *
+ * Copyright Hitachi Data Systems Corp. 2013
+ *
+ * Authors:
+ * Tomoki Sekiyama <tomoki.sekiyama@hds.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include <stdio.h>
+#include <string.h>
+
+#include "vss-common.h"
+#include "inc/win2003/vscoordint.h"
+
+#include <comadmin.h>
+#include <wbemidl.h>
+#include <comdef.h>
+#include <comutil.h>
+
+extern HINSTANCE g_hinstDll;
+
+const GUID CLSID_COMAdminCatalog = { 0xF618C514, 0xDFB8, 0x11d1,
+ {0xA2, 0xCF, 0x00, 0x80, 0x5F, 0xC7, 0x92, 0x35} };
+const GUID IID_ICOMAdminCatalog2 = { 0x790C6E0B, 0x9194, 0x4cc9,
+ {0x94, 0x26, 0xA4, 0x8A, 0x63, 0x18, 0x56, 0x96} };
+const GUID CLSID_WbemLocator = { 0x4590f811, 0x1d3a, 0x11d0,
+ {0x89, 0x1f, 0x00, 0xaa, 0x00, 0x4b, 0x2e, 0x24} };
+const GUID IID_IWbemLocator = { 0xdc12a687, 0x737f, 0x11cf,
+ {0x88, 0x4d, 0x00, 0xaa, 0x00, 0x4b, 0x2e, 0x24} };
+
+void errmsg(DWORD err, const char *text)
+{
+ /*
+ * `text' contains function call statement when errmsg is called via chk().
+ * To make error message more readable, we cut off the text after '('.
+ * If text doesn't contains '(', negative precision is given, which is
+ * treated as though it were missing.
+ */
+ char *msg = NULL, *nul = strchr(text, '(');
+ int len = nul ? nul - text : -1;
+
+ FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
+ FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
+ NULL, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+ (char *)&msg, 0, NULL);
+ fprintf(stderr, "%.*s. (Error: %lx) %s\n", len, text, err, msg);
+ LocalFree(msg);
+}
+
+static void errmsg_dialog(DWORD err, const char *text, const char *opt = "")
+{
+ char *msg, buf[512];
+
+ FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
+ FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
+ NULL, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+ (char *)&msg, 0, NULL);
+ snprintf(buf, sizeof(buf), "%s%s. (Error: %lx) %s", text, opt, err, msg);
+ MessageBox(NULL, buf, "Error from " QGA_PROVIDER_NAME, MB_OK|MB_ICONERROR);
+ LocalFree(msg);
+}
+
+#define _chk(hr, status, msg, err_label) \
+ do { \
+ hr = (status); \
+ if (FAILED(hr)) { \
+ errmsg(hr, msg); \
+ goto err_label; \
+ } \
+ } while (0)
+
+#define chk(status) _chk(hr, status, "Failed to " #status, out)
+
+#if !defined(__MINGW64_VERSION_MAJOR) || !defined(__MINGW64_VERSION_MINOR) || \
+ __MINGW64_VERSION_MAJOR * 100 + __MINGW64_VERSION_MINOR < 301
+void __stdcall _com_issue_error(HRESULT hr)
+{
+ errmsg(hr, "Unexpected error in COM");
+}
+#endif
+
+template<class T>
+HRESULT put_Value(ICatalogObject *pObj, LPCWSTR name, T val)
+{
+ return pObj->put_Value(_bstr_t(name), _variant_t(val));
+}
+
+/* Lookup Administrators group name from winmgmt */
+static HRESULT GetAdminName(_bstr_t *name)
+{
+ HRESULT hr;
+ COMPointer<IWbemLocator> pLoc;
+ COMPointer<IWbemServices> pSvc;
+ COMPointer<IEnumWbemClassObject> pEnum;
+ COMPointer<IWbemClassObject> pWobj;
+ ULONG returned;
+ _variant_t var;
+
+ chk(CoCreateInstance(CLSID_WbemLocator, NULL, CLSCTX_INPROC_SERVER,
+ IID_IWbemLocator, (LPVOID *)pLoc.replace()));
+ chk(pLoc->ConnectServer(_bstr_t(L"ROOT\\CIMV2"), NULL, NULL, NULL,
+ 0, 0, 0, pSvc.replace()));
+ chk(CoSetProxyBlanket(pSvc, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE,
+ NULL, RPC_C_AUTHN_LEVEL_CALL,
+ RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE));
+ chk(pSvc->ExecQuery(_bstr_t(L"WQL"),
+ _bstr_t(L"select * from Win32_Account where "
+ "SID='S-1-5-32-544' and localAccount=TRUE"),
+ WBEM_FLAG_RETURN_IMMEDIATELY | WBEM_FLAG_FORWARD_ONLY,
+ NULL, pEnum.replace()));
+ if (!pEnum) {
+ hr = E_FAIL;
+ errmsg(hr, "Failed to query for Administrators");
+ goto out;
+ }
+ chk(pEnum->Next(WBEM_INFINITE, 1, pWobj.replace(), &returned));
+ if (returned == 0) {
+ hr = E_FAIL;
+ errmsg(hr, "No Administrators found");
+ goto out;
+ }
+
+ chk(pWobj->Get(_bstr_t(L"Name"), 0, &var, 0, 0));
+ try {
+ *name = var;
+ } catch(...) {
+ hr = E_FAIL;
+ errmsg(hr, "Failed to get name of Administrators");
+ goto out;
+ }
+
+out:
+ return hr;
+}
+
+/* Find and iterate QGA VSS provider in COM+ Application Catalog */
+static HRESULT QGAProviderFind(
+ HRESULT (*found)(ICatalogCollection *, int, void *), void *arg)
+{
+ HRESULT hr;
+ COMInitializer initializer;
+ COMPointer<IUnknown> pUnknown;
+ COMPointer<ICOMAdminCatalog2> pCatalog;
+ COMPointer<ICatalogCollection> pColl;
+ COMPointer<ICatalogObject> pObj;
+ _variant_t var;
+ long i, n;
+
+ chk(CoCreateInstance(CLSID_COMAdminCatalog, NULL, CLSCTX_INPROC_SERVER,
+ IID_IUnknown, (void **)pUnknown.replace()));
+ chk(pUnknown->QueryInterface(IID_ICOMAdminCatalog2,
+ (void **)pCatalog.replace()));
+ chk(pCatalog->GetCollection(_bstr_t(L"Applications"),
+ (IDispatch **)pColl.replace()));
+ chk(pColl->Populate());
+
+ chk(pColl->get_Count(&n));
+ for (i = n - 1; i >= 0; i--) {
+ chk(pColl->get_Item(i, (IDispatch **)pObj.replace()));
+ chk(pObj->get_Value(_bstr_t(L"Name"), &var));
+ if (var == _variant_t(QGA_PROVIDER_LNAME)) {
+ if (FAILED(found(pColl, i, arg))) {
+ goto out;
+ }
+ }
+ }
+ chk(pColl->SaveChanges(&n));
+
+out:
+ return hr;
+}
+
+/* Count QGA VSS provider in COM+ Application Catalog */
+static HRESULT QGAProviderCount(ICatalogCollection *coll, int i, void *arg)
+{
+ (*(int *)arg)++;
+ return S_OK;
+}
+
+/* Remove QGA VSS provider from COM+ Application Catalog Collection */
+static HRESULT QGAProviderRemove(ICatalogCollection *coll, int i, void *arg)
+{
+ HRESULT hr;
+
+ fprintf(stderr, "Removing COM+ Application: %s\n", QGA_PROVIDER_NAME);
+ chk(coll->Remove(i));
+out:
+ return hr;
+}
+
+/* Unregister this module from COM+ Applications Catalog */
+STDAPI COMUnregister(void)
+{
+ HRESULT hr;
+
+ DllUnregisterServer();
+ chk(QGAProviderFind(QGAProviderRemove, NULL));
+out:
+ return hr;
+}
+
+/* Register this module to COM+ Applications Catalog */
+STDAPI COMRegister(void)
+{
+ HRESULT hr;
+ COMInitializer initializer;
+ COMPointer<IUnknown> pUnknown;
+ COMPointer<ICOMAdminCatalog2> pCatalog;
+ COMPointer<ICatalogCollection> pApps, pRoles, pUsersInRole;
+ COMPointer<ICatalogObject> pObj;
+ long n;
+ _bstr_t name;
+ _variant_t key;
+ CHAR dllPath[MAX_PATH], tlbPath[MAX_PATH];
+ bool unregisterOnFailure = false;
+ int count = 0;
+
+ if (!g_hinstDll) {
+ errmsg(E_FAIL, "Failed to initialize DLL");
+ return E_FAIL;
+ }
+
+ chk(QGAProviderFind(QGAProviderCount, (void *)&count));
+ if (count) {
+ errmsg(E_ABORT, "QGA VSS Provider is already installed");
+ return E_ABORT;
+ }
+
+ chk(CoCreateInstance(CLSID_COMAdminCatalog, NULL, CLSCTX_INPROC_SERVER,
+ IID_IUnknown, (void **)pUnknown.replace()));
+ chk(pUnknown->QueryInterface(IID_ICOMAdminCatalog2,
+ (void **)pCatalog.replace()));
+
+ /* Install COM+ Component */
+
+ chk(pCatalog->GetCollection(_bstr_t(L"Applications"),
+ (IDispatch **)pApps.replace()));
+ chk(pApps->Populate());
+ chk(pApps->Add((IDispatch **)&pObj));
+ chk(put_Value(pObj, L"Name", QGA_PROVIDER_LNAME));
+ chk(put_Value(pObj, L"Description", QGA_PROVIDER_LNAME));
+ chk(put_Value(pObj, L"ApplicationAccessChecksEnabled", true));
+ chk(put_Value(pObj, L"Authentication", short(6)));
+ chk(put_Value(pObj, L"AuthenticationCapability", short(2)));
+ chk(put_Value(pObj, L"ImpersonationLevel", short(2)));
+ chk(pApps->SaveChanges(&n));
+
+ /* The app should be deleted if something fails after SaveChanges */
+ unregisterOnFailure = true;
+
+ chk(pObj->get_Key(&key));
+
+ if (!GetModuleFileName(g_hinstDll, dllPath, sizeof(dllPath))) {
+ hr = HRESULT_FROM_WIN32(GetLastError());
+ errmsg(hr, "GetModuleFileName failed");
+ goto out;
+ }
+ n = strlen(dllPath);
+ if (n < 3) {
+ hr = E_FAIL;
+ errmsg(hr, "Failed to lookup dll");
+ goto out;
+ }
+ strcpy(tlbPath, dllPath);
+ strcpy(tlbPath+n-3, "tlb");
+ fprintf(stderr, "Registering " QGA_PROVIDER_NAME ":\n");
+ fprintf(stderr, " %s\n", dllPath);
+ fprintf(stderr, " %s\n", tlbPath);
+ if (!PathFileExists(tlbPath)) {
+ hr = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
+ errmsg(hr, "Failed to lookup tlb");
+ goto out;
+ }
+
+ chk(pCatalog->CreateServiceForApplication(
+ _bstr_t(QGA_PROVIDER_LNAME), _bstr_t(QGA_PROVIDER_LNAME),
+ _bstr_t(L"SERVICE_AUTO_START"), _bstr_t(L"SERVICE_ERROR_NORMAL"),
+ _bstr_t(L""), _bstr_t(L".\\localsystem"), _bstr_t(L""), FALSE));
+ chk(pCatalog->InstallComponent(_bstr_t(QGA_PROVIDER_LNAME),
+ _bstr_t(dllPath), _bstr_t(tlbPath),
+ _bstr_t("")));
+
+ /* Setup roles of the applicaion */
+
+ chk(pApps->GetCollection(_bstr_t(L"Roles"), key,
+ (IDispatch **)pRoles.replace()));
+ chk(pRoles->Populate());
+ chk(pRoles->Add((IDispatch **)pObj.replace()));
+ chk(put_Value(pObj, L"Name", L"Administrators"));
+ chk(put_Value(pObj, L"Description", L"Administrators group"));
+ chk(pRoles->SaveChanges(&n));
+ chk(pObj->get_Key(&key));
+
+ /* Setup users in the role */
+
+ chk(pRoles->GetCollection(_bstr_t(L"UsersInRole"), key,
+ (IDispatch **)pUsersInRole.replace()));
+ chk(pUsersInRole->Populate());
+
+ chk(pUsersInRole->Add((IDispatch **)pObj.replace()));
+ chk(GetAdminName(&name));
+ chk(put_Value(pObj, L"User", _bstr_t(".\\") + name));
+
+ chk(pUsersInRole->Add((IDispatch **)pObj.replace()));
+ chk(put_Value(pObj, L"User", L"SYSTEM"));
+ chk(pUsersInRole->SaveChanges(&n));
+
+out:
+ if (unregisterOnFailure && FAILED(hr)) {
+ COMUnregister();
+ }
+
+ return hr;
+}
+
+
+static BOOL CreateRegistryKey(LPCTSTR key, LPCTSTR value, LPCTSTR data)
+{
+ HKEY hKey;
+ LONG ret;
+ DWORD size;
+
+ ret = RegCreateKeyEx(HKEY_CLASSES_ROOT, key, 0, NULL,
+ REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &hKey, NULL);
+ if (ret != ERROR_SUCCESS) {
+ goto out;
+ }
+
+ if (data != NULL) {
+ size = strlen(data) + 1;
+ } else {
+ size = 0;
+ }
+
+ ret = RegSetValueEx(hKey, value, 0, REG_SZ, (LPBYTE)data, size);
+ RegCloseKey(hKey);
+
+out:
+ if (ret != ERROR_SUCCESS) {
+ /* As we cannot printf within DllRegisterServer(), show a dialog. */
+ errmsg_dialog(ret, "Cannot add registry", key);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+/* Register this dll as a VSS provider */
+STDAPI DllRegisterServer(void)
+{
+ COMInitializer initializer;
+ COMPointer<IVssAdmin> pVssAdmin;
+ HRESULT hr = E_FAIL;
+ char dllPath[MAX_PATH];
+ char key[256];
+
+ if (!g_hinstDll) {
+ errmsg_dialog(hr, "Module instance is not available");
+ goto out;
+ }
+
+ /* Add this module to registery */
+
+ sprintf(key, "CLSID\\%s", g_szClsid);
+ if (!CreateRegistryKey(key, NULL, g_szClsid)) {
+ goto out;
+ }
+
+ if (!GetModuleFileName(g_hinstDll, dllPath, sizeof(dllPath))) {
+ errmsg_dialog(GetLastError(), "GetModuleFileName failed");
+ goto out;
+ }
+
+ sprintf(key, "CLSID\\%s\\InprocServer32", g_szClsid);
+ if (!CreateRegistryKey(key, NULL, dllPath)) {
+ goto out;
+ }
+
+ if (!CreateRegistryKey(key, "ThreadingModel", "Apartment")) {
+ goto out;
+ }
+
+ sprintf(key, "CLSID\\%s\\ProgID", g_szClsid);
+ if (!CreateRegistryKey(key, NULL, g_szProgid)) {
+ goto out;
+ }
+
+ if (!CreateRegistryKey(g_szProgid, NULL, QGA_PROVIDER_NAME)) {
+ goto out;
+ }
+
+ sprintf(key, "%s\\CLSID", g_szProgid);
+ if (!CreateRegistryKey(key, NULL, g_szClsid)) {
+ goto out;
+ }
+
+ hr = CoCreateInstance(CLSID_VSSCoordinator, NULL, CLSCTX_ALL,
+ IID_IVssAdmin, (void **)pVssAdmin.replace());
+ if (FAILED(hr)) {
+ errmsg_dialog(hr, "CoCreateInstance(VSSCoordinator) failed");
+ goto out;
+ }
+
+ hr = pVssAdmin->RegisterProvider(g_gProviderId, CLSID_QGAVSSProvider,
+ const_cast<WCHAR*>(QGA_PROVIDER_LNAME),
+ VSS_PROV_SOFTWARE,
+ const_cast<WCHAR*>(QGA_PROVIDER_VERSION),
+ g_gProviderVersion);
+ if (FAILED(hr)) {
+ errmsg_dialog(hr, "RegisterProvider failed");
+ }
+
+out:
+ if (FAILED(hr)) {
+ DllUnregisterServer();
+ }
+
+ return hr;
+}
+
+/* Unregister this VSS hardware provider from the system */
+STDAPI DllUnregisterServer(void)
+{
+ TCHAR key[256];
+ COMInitializer initializer;
+ COMPointer<IVssAdmin> pVssAdmin;
+
+ HRESULT hr = CoCreateInstance(CLSID_VSSCoordinator,
+ NULL, CLSCTX_ALL, IID_IVssAdmin,
+ (void **)pVssAdmin.replace());
+ if (SUCCEEDED(hr)) {
+ hr = pVssAdmin->UnregisterProvider(g_gProviderId);
+ } else {
+ errmsg(hr, "CoCreateInstance(VSSCoordinator) failed");
+ }
+
+ sprintf(key, "CLSID\\%s", g_szClsid);
+ SHDeleteKey(HKEY_CLASSES_ROOT, key);
+ SHDeleteKey(HKEY_CLASSES_ROOT, g_szProgid);
+
+ return S_OK; /* Uninstall should never fail */
+}
+
+
+/* Support function to convert ASCII string into BSTR (used in _bstr_t) */
+namespace _com_util
+{
+ BSTR WINAPI ConvertStringToBSTR(const char *ascii) {
+ int len = strlen(ascii);
+ BSTR bstr = SysAllocStringLen(NULL, len);
+
+ if (!bstr) {
+ return NULL;
+ }
+
+ if (mbstowcs(bstr, ascii, len) == (size_t)-1) {
+ fprintf(stderr, "Failed to convert string '%s' into BSTR", ascii);
+ bstr[0] = 0;
+ }
+ return bstr;
+ }
+}
diff --git a/src/qga/vss-win32/provider.cpp b/src/qga/vss-win32/provider.cpp
new file mode 100644
index 0000000..d5129f8
--- /dev/null
+++ b/src/qga/vss-win32/provider.cpp
@@ -0,0 +1,534 @@
+/*
+ * QEMU Guest Agent win32 VSS Provider implementations
+ *
+ * Copyright Hitachi Data Systems Corp. 2013
+ *
+ * Authors:
+ * Tomoki Sekiyama <tomoki.sekiyama@hds.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include <stdio.h>
+#include "vss-common.h"
+#include "inc/win2003/vscoordint.h"
+#include "inc/win2003/vsprov.h"
+
+#define VSS_TIMEOUT_MSEC (60*1000)
+
+static long g_nComObjsInUse;
+HINSTANCE g_hinstDll;
+
+/* VSS common GUID's */
+
+const CLSID CLSID_VSSCoordinator = { 0xE579AB5F, 0x1CC4, 0x44b4,
+ {0xBE, 0xD9, 0xDE, 0x09, 0x91, 0xFF, 0x06, 0x23} };
+const IID IID_IVssAdmin = { 0x77ED5996, 0x2F63, 0x11d3,
+ {0x8A, 0x39, 0x00, 0xC0, 0x4F, 0x72, 0xD8, 0xE3} };
+
+const IID IID_IVssHardwareSnapshotProvider = { 0x9593A157, 0x44E9, 0x4344,
+ {0xBB, 0xEB, 0x44, 0xFB, 0xF9, 0xB0, 0x6B, 0x10} };
+const IID IID_IVssSoftwareSnapshotProvider = { 0x609e123e, 0x2c5a, 0x44d3,
+ {0x8f, 0x01, 0x0b, 0x1d, 0x9a, 0x47, 0xd1, 0xff} };
+const IID IID_IVssProviderCreateSnapshotSet = { 0x5F894E5B, 0x1E39, 0x4778,
+ {0x8E, 0x23, 0x9A, 0xBA, 0xD9, 0xF0, 0xE0, 0x8C} };
+const IID IID_IVssProviderNotifications = { 0xE561901F, 0x03A5, 0x4afe,
+ {0x86, 0xD0, 0x72, 0xBA, 0xEE, 0xCE, 0x70, 0x04} };
+
+const IID IID_IVssEnumObject = { 0xAE1C7110, 0x2F60, 0x11d3,
+ {0x8A, 0x39, 0x00, 0xC0, 0x4F, 0x72, 0xD8, 0xE3} };
+
+
+void LockModule(BOOL lock)
+{
+ if (lock) {
+ InterlockedIncrement(&g_nComObjsInUse);
+ } else {
+ InterlockedDecrement(&g_nComObjsInUse);
+ }
+}
+
+/* Empty enumerator for VssObject */
+
+class CQGAVSSEnumObject : public IVssEnumObject
+{
+public:
+ STDMETHODIMP QueryInterface(REFIID riid, void **ppObj);
+ STDMETHODIMP_(ULONG) AddRef();
+ STDMETHODIMP_(ULONG) Release();
+
+ /* IVssEnumObject Methods */
+ STDMETHODIMP Next(
+ ULONG celt, VSS_OBJECT_PROP *rgelt, ULONG *pceltFetched);
+ STDMETHODIMP Skip(ULONG celt);
+ STDMETHODIMP Reset(void);
+ STDMETHODIMP Clone(IVssEnumObject **ppenum);
+
+ /* CQGAVSSEnumObject Methods */
+ CQGAVSSEnumObject();
+ ~CQGAVSSEnumObject();
+
+private:
+ long m_nRefCount;
+};
+
+CQGAVSSEnumObject::CQGAVSSEnumObject()
+{
+ m_nRefCount = 0;
+ LockModule(TRUE);
+}
+
+CQGAVSSEnumObject::~CQGAVSSEnumObject()
+{
+ LockModule(FALSE);
+}
+
+STDMETHODIMP CQGAVSSEnumObject::QueryInterface(REFIID riid, void **ppObj)
+{
+ if (riid == IID_IUnknown || riid == IID_IVssEnumObject) {
+ *ppObj = static_cast<void*>(static_cast<IVssEnumObject*>(this));
+ AddRef();
+ return S_OK;
+ }
+ *ppObj = NULL;
+ return E_NOINTERFACE;
+}
+
+STDMETHODIMP_(ULONG) CQGAVSSEnumObject::AddRef()
+{
+ return InterlockedIncrement(&m_nRefCount);
+}
+
+STDMETHODIMP_(ULONG) CQGAVSSEnumObject::Release()
+{
+ long nRefCount = InterlockedDecrement(&m_nRefCount);
+ if (m_nRefCount == 0) {
+ delete this;
+ }
+ return nRefCount;
+}
+
+STDMETHODIMP CQGAVSSEnumObject::Next(
+ ULONG celt, VSS_OBJECT_PROP *rgelt, ULONG *pceltFetched)
+{
+ *pceltFetched = 0;
+ return S_FALSE;
+}
+
+STDMETHODIMP CQGAVSSEnumObject::Skip(ULONG celt)
+{
+ return S_FALSE;
+}
+
+STDMETHODIMP CQGAVSSEnumObject::Reset(void)
+{
+ return S_OK;
+}
+
+STDMETHODIMP CQGAVSSEnumObject::Clone(IVssEnumObject **ppenum)
+{
+ return E_NOTIMPL;
+}
+
+
+/* QGAVssProvider */
+
+class CQGAVssProvider :
+ public IVssSoftwareSnapshotProvider,
+ public IVssProviderCreateSnapshotSet,
+ public IVssProviderNotifications
+{
+public:
+ STDMETHODIMP QueryInterface(REFIID riid, void **ppObj);
+ STDMETHODIMP_(ULONG) AddRef();
+ STDMETHODIMP_(ULONG) Release();
+
+ /* IVssSoftwareSnapshotProvider Methods */
+ STDMETHODIMP SetContext(LONG lContext);
+ STDMETHODIMP GetSnapshotProperties(
+ VSS_ID SnapshotId, VSS_SNAPSHOT_PROP *pProp);
+ STDMETHODIMP Query(
+ VSS_ID QueriedObjectId, VSS_OBJECT_TYPE eQueriedObjectType,
+ VSS_OBJECT_TYPE eReturnedObjectsType, IVssEnumObject **ppEnum);
+ STDMETHODIMP DeleteSnapshots(
+ VSS_ID SourceObjectId, VSS_OBJECT_TYPE eSourceObjectType,
+ BOOL bForceDelete, LONG *plDeletedSnapshots,
+ VSS_ID *pNondeletedSnapshotID);
+ STDMETHODIMP BeginPrepareSnapshot(
+ VSS_ID SnapshotSetId, VSS_ID SnapshotId,
+ VSS_PWSZ pwszVolumeName, LONG lNewContext);
+ STDMETHODIMP IsVolumeSupported(
+ VSS_PWSZ pwszVolumeName, BOOL *pbSupportedByThisProvider);
+ STDMETHODIMP IsVolumeSnapshotted(
+ VSS_PWSZ pwszVolumeName, BOOL *pbSnapshotsPresent,
+ LONG *plSnapshotCompatibility);
+ STDMETHODIMP SetSnapshotProperty(
+ VSS_ID SnapshotId, VSS_SNAPSHOT_PROPERTY_ID eSnapshotPropertyId,
+ VARIANT vProperty);
+ STDMETHODIMP RevertToSnapshot(VSS_ID SnapshotId);
+ STDMETHODIMP QueryRevertStatus(VSS_PWSZ pwszVolume, IVssAsync **ppAsync);
+
+ /* IVssProviderCreateSnapshotSet Methods */
+ STDMETHODIMP EndPrepareSnapshots(VSS_ID SnapshotSetId);
+ STDMETHODIMP PreCommitSnapshots(VSS_ID SnapshotSetId);
+ STDMETHODIMP CommitSnapshots(VSS_ID SnapshotSetId);
+ STDMETHODIMP PostCommitSnapshots(
+ VSS_ID SnapshotSetId, LONG lSnapshotsCount);
+ STDMETHODIMP PreFinalCommitSnapshots(VSS_ID SnapshotSetId);
+ STDMETHODIMP PostFinalCommitSnapshots(VSS_ID SnapshotSetId);
+ STDMETHODIMP AbortSnapshots(VSS_ID SnapshotSetId);
+
+ /* IVssProviderNotifications Methods */
+ STDMETHODIMP OnLoad(IUnknown *pCallback);
+ STDMETHODIMP OnUnload(BOOL bForceUnload);
+
+ /* CQGAVssProvider Methods */
+ CQGAVssProvider();
+ ~CQGAVssProvider();
+
+private:
+ long m_nRefCount;
+};
+
+CQGAVssProvider::CQGAVssProvider()
+{
+ m_nRefCount = 0;
+ LockModule(TRUE);
+}
+
+CQGAVssProvider::~CQGAVssProvider()
+{
+ LockModule(FALSE);
+}
+
+STDMETHODIMP CQGAVssProvider::QueryInterface(REFIID riid, void **ppObj)
+{
+ if (riid == IID_IUnknown) {
+ *ppObj = static_cast<void*>(this);
+ AddRef();
+ return S_OK;
+ }
+ if (riid == IID_IVssSoftwareSnapshotProvider) {
+ *ppObj = static_cast<void*>(
+ static_cast<IVssSoftwareSnapshotProvider*>(this));
+ AddRef();
+ return S_OK;
+ }
+ if (riid == IID_IVssProviderCreateSnapshotSet) {
+ *ppObj = static_cast<void*>(
+ static_cast<IVssProviderCreateSnapshotSet*>(this));
+ AddRef();
+ return S_OK;
+ }
+ if (riid == IID_IVssProviderNotifications) {
+ *ppObj = static_cast<void*>(
+ static_cast<IVssProviderNotifications*>(this));
+ AddRef();
+ return S_OK;
+ }
+ *ppObj = NULL;
+ return E_NOINTERFACE;
+}
+
+STDMETHODIMP_(ULONG) CQGAVssProvider::AddRef()
+{
+ return InterlockedIncrement(&m_nRefCount);
+}
+
+STDMETHODIMP_(ULONG) CQGAVssProvider::Release()
+{
+ long nRefCount = InterlockedDecrement(&m_nRefCount);
+ if (m_nRefCount == 0) {
+ delete this;
+ }
+ return nRefCount;
+}
+
+
+/*
+ * IVssSoftwareSnapshotProvider methods
+ */
+
+STDMETHODIMP CQGAVssProvider::SetContext(LONG lContext)
+{
+ return S_OK;
+}
+
+STDMETHODIMP CQGAVssProvider::GetSnapshotProperties(
+ VSS_ID SnapshotId, VSS_SNAPSHOT_PROP *pProp)
+{
+ return VSS_E_OBJECT_NOT_FOUND;
+}
+
+STDMETHODIMP CQGAVssProvider::Query(
+ VSS_ID QueriedObjectId, VSS_OBJECT_TYPE eQueriedObjectType,
+ VSS_OBJECT_TYPE eReturnedObjectsType, IVssEnumObject **ppEnum)
+{
+ try {
+ *ppEnum = new CQGAVSSEnumObject;
+ } catch (...) {
+ return E_OUTOFMEMORY;
+ }
+ (*ppEnum)->AddRef();
+ return S_OK;
+}
+
+STDMETHODIMP CQGAVssProvider::DeleteSnapshots(
+ VSS_ID SourceObjectId, VSS_OBJECT_TYPE eSourceObjectType,
+ BOOL bForceDelete, LONG *plDeletedSnapshots, VSS_ID *pNondeletedSnapshotID)
+{
+ *plDeletedSnapshots = 0;
+ *pNondeletedSnapshotID = SourceObjectId;
+ return S_OK;
+}
+
+STDMETHODIMP CQGAVssProvider::BeginPrepareSnapshot(
+ VSS_ID SnapshotSetId, VSS_ID SnapshotId,
+ VSS_PWSZ pwszVolumeName, LONG lNewContext)
+{
+ return S_OK;
+}
+
+STDMETHODIMP CQGAVssProvider::IsVolumeSupported(
+ VSS_PWSZ pwszVolumeName, BOOL *pbSupportedByThisProvider)
+{
+ HANDLE hEventFrozen;
+
+ /* Check if a requester is qemu-ga by whether an event is created */
+ hEventFrozen = OpenEvent(EVENT_ALL_ACCESS, FALSE, EVENT_NAME_FROZEN);
+ if (!hEventFrozen) {
+ *pbSupportedByThisProvider = FALSE;
+ return S_OK;
+ }
+ CloseHandle(hEventFrozen);
+
+ *pbSupportedByThisProvider = TRUE;
+ return S_OK;
+}
+
+STDMETHODIMP CQGAVssProvider::IsVolumeSnapshotted(VSS_PWSZ pwszVolumeName,
+ BOOL *pbSnapshotsPresent, LONG *plSnapshotCompatibility)
+{
+ *pbSnapshotsPresent = FALSE;
+ *plSnapshotCompatibility = 0;
+ return S_OK;
+}
+
+STDMETHODIMP CQGAVssProvider::SetSnapshotProperty(VSS_ID SnapshotId,
+ VSS_SNAPSHOT_PROPERTY_ID eSnapshotPropertyId, VARIANT vProperty)
+{
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP CQGAVssProvider::RevertToSnapshot(VSS_ID SnapshotId)
+{
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP CQGAVssProvider::QueryRevertStatus(
+ VSS_PWSZ pwszVolume, IVssAsync **ppAsync)
+{
+ return E_NOTIMPL;
+}
+
+
+/*
+ * IVssProviderCreateSnapshotSet methods
+ */
+
+STDMETHODIMP CQGAVssProvider::EndPrepareSnapshots(VSS_ID SnapshotSetId)
+{
+ return S_OK;
+}
+
+STDMETHODIMP CQGAVssProvider::PreCommitSnapshots(VSS_ID SnapshotSetId)
+{
+ return S_OK;
+}
+
+STDMETHODIMP CQGAVssProvider::CommitSnapshots(VSS_ID SnapshotSetId)
+{
+ HRESULT hr = S_OK;
+ HANDLE hEventFrozen, hEventThaw, hEventTimeout;
+
+ hEventFrozen = OpenEvent(EVENT_ALL_ACCESS, FALSE, EVENT_NAME_FROZEN);
+ if (!hEventFrozen) {
+ return E_FAIL;
+ }
+
+ hEventThaw = OpenEvent(EVENT_ALL_ACCESS, FALSE, EVENT_NAME_THAW);
+ if (!hEventThaw) {
+ CloseHandle(hEventFrozen);
+ return E_FAIL;
+ }
+
+ hEventTimeout = OpenEvent(EVENT_ALL_ACCESS, FALSE, EVENT_NAME_TIMEOUT);
+ if (!hEventTimeout) {
+ CloseHandle(hEventFrozen);
+ CloseHandle(hEventThaw);
+ return E_FAIL;
+ }
+
+ /* Send event to qemu-ga to notify filesystem is frozen */
+ SetEvent(hEventFrozen);
+
+ /* Wait until the snapshot is taken by the host. */
+ if (WaitForSingleObject(hEventThaw, VSS_TIMEOUT_MSEC) != WAIT_OBJECT_0) {
+ /* Send event to qemu-ga to notify the provider is timed out */
+ SetEvent(hEventTimeout);
+ hr = E_ABORT;
+ }
+
+ CloseHandle(hEventThaw);
+ CloseHandle(hEventFrozen);
+ CloseHandle(hEventTimeout);
+ return hr;
+}
+
+STDMETHODIMP CQGAVssProvider::PostCommitSnapshots(
+ VSS_ID SnapshotSetId, LONG lSnapshotsCount)
+{
+ return S_OK;
+}
+
+STDMETHODIMP CQGAVssProvider::PreFinalCommitSnapshots(VSS_ID SnapshotSetId)
+{
+ return S_OK;
+}
+
+STDMETHODIMP CQGAVssProvider::PostFinalCommitSnapshots(VSS_ID SnapshotSetId)
+{
+ return S_OK;
+}
+
+STDMETHODIMP CQGAVssProvider::AbortSnapshots(VSS_ID SnapshotSetId)
+{
+ return S_OK;
+}
+
+/*
+ * IVssProviderNotifications methods
+ */
+
+STDMETHODIMP CQGAVssProvider::OnLoad(IUnknown *pCallback)
+{
+ return S_OK;
+}
+
+STDMETHODIMP CQGAVssProvider::OnUnload(BOOL bForceUnload)
+{
+ return S_OK;
+}
+
+
+/*
+ * CQGAVssProviderFactory class
+ */
+
+class CQGAVssProviderFactory : public IClassFactory
+{
+public:
+ STDMETHODIMP QueryInterface(REFIID riid, void **ppv);
+ STDMETHODIMP_(ULONG) AddRef();
+ STDMETHODIMP_(ULONG) Release();
+ STDMETHODIMP CreateInstance(
+ IUnknown *pUnknownOuter, REFIID iid, void **ppv);
+ STDMETHODIMP LockServer(BOOL lock) { return E_NOTIMPL; }
+
+ CQGAVssProviderFactory();
+ ~CQGAVssProviderFactory();
+
+private:
+ long m_nRefCount;
+};
+
+CQGAVssProviderFactory::CQGAVssProviderFactory()
+{
+ m_nRefCount = 0;
+ LockModule(TRUE);
+}
+
+CQGAVssProviderFactory::~CQGAVssProviderFactory()
+{
+ LockModule(FALSE);
+}
+
+STDMETHODIMP CQGAVssProviderFactory::QueryInterface(REFIID riid, void **ppv)
+{
+ if (riid == IID_IUnknown || riid == IID_IClassFactory) {
+ *ppv = static_cast<void*>(this);
+ AddRef();
+ return S_OK;
+ }
+ *ppv = NULL;
+ return E_NOINTERFACE;
+}
+
+STDMETHODIMP_(ULONG) CQGAVssProviderFactory::AddRef()
+{
+ return InterlockedIncrement(&m_nRefCount);
+}
+
+STDMETHODIMP_(ULONG) CQGAVssProviderFactory::Release()
+{
+ long nRefCount = InterlockedDecrement(&m_nRefCount);
+ if (m_nRefCount == 0) {
+ delete this;
+ }
+ return nRefCount;
+}
+
+STDMETHODIMP CQGAVssProviderFactory::CreateInstance(
+ IUnknown *pUnknownOuter, REFIID iid, void **ppv)
+{
+ CQGAVssProvider *pObj;
+
+ if (pUnknownOuter) {
+ return CLASS_E_NOAGGREGATION;
+ }
+ try {
+ pObj = new CQGAVssProvider;
+ } catch (...) {
+ return E_OUTOFMEMORY;
+ }
+ HRESULT hr = pObj->QueryInterface(iid, ppv);
+ if (FAILED(hr)) {
+ delete pObj;
+ }
+ return hr;
+}
+
+
+/*
+ * DLL functions
+ */
+
+STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID *ppv)
+{
+ CQGAVssProviderFactory *factory;
+ try {
+ factory = new CQGAVssProviderFactory;
+ } catch (...) {
+ return E_OUTOFMEMORY;
+ }
+ factory->AddRef();
+ HRESULT hr = factory->QueryInterface(riid, ppv);
+ factory->Release();
+ return hr;
+}
+
+STDAPI DllCanUnloadNow()
+{
+ return g_nComObjsInUse == 0 ? S_OK : S_FALSE;
+}
+
+EXTERN_C
+BOOL WINAPI DllMain(HINSTANCE hinstDll, DWORD dwReason, LPVOID lpReserved)
+{
+ if (dwReason == DLL_PROCESS_ATTACH) {
+ g_hinstDll = hinstDll;
+ DisableThreadLibraryCalls(hinstDll);
+ }
+ return TRUE;
+}
diff --git a/src/qga/vss-win32/qga-vss.def b/src/qga/vss-win32/qga-vss.def
new file mode 100644
index 0000000..927782c
--- /dev/null
+++ b/src/qga/vss-win32/qga-vss.def
@@ -0,0 +1,13 @@
+LIBRARY "QGA-PROVIDER.DLL"
+
+EXPORTS
+ COMRegister PRIVATE
+ COMUnregister PRIVATE
+ DllCanUnloadNow PRIVATE
+ DllGetClassObject PRIVATE
+ DllRegisterServer PRIVATE
+ DllUnregisterServer PRIVATE
+ requester_init PRIVATE
+ requester_deinit PRIVATE
+ requester_freeze PRIVATE
+ requester_thaw PRIVATE
diff --git a/src/qga/vss-win32/qga-vss.idl b/src/qga/vss-win32/qga-vss.idl
new file mode 100644
index 0000000..17abca0
--- /dev/null
+++ b/src/qga/vss-win32/qga-vss.idl
@@ -0,0 +1,20 @@
+import "oaidl.idl";
+import "ocidl.idl";
+
+[
+ uuid(103B8142-6CE5-48A7-BDE1-794D3192FCF1),
+ version(1.0),
+ helpstring("QGAVSSProvider Type Library")
+]
+library QGAVSSHWProviderLib
+{
+ importlib("stdole2.tlb");
+ [
+ uuid(6E6A3492-8D4D-440C-9619-5E5D0CC31CA8),
+ helpstring("QGAVSSProvider Class")
+ ]
+ coclass QGAVSSHWProvider
+ {
+ [default] interface IUnknown;
+ };
+};
diff --git a/src/qga/vss-win32/qga-vss.tlb b/src/qga/vss-win32/qga-vss.tlb
new file mode 100644
index 0000000..226452a
--- /dev/null
+++ b/src/qga/vss-win32/qga-vss.tlb
Binary files differ
diff --git a/src/qga/vss-win32/requester.cpp b/src/qga/vss-win32/requester.cpp
new file mode 100644
index 0000000..9b3e310
--- /dev/null
+++ b/src/qga/vss-win32/requester.cpp
@@ -0,0 +1,505 @@
+/*
+ * QEMU Guest Agent win32 VSS Requester implementations
+ *
+ * Copyright Hitachi Data Systems Corp. 2013
+ *
+ * Authors:
+ * Tomoki Sekiyama <tomoki.sekiyama@hds.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include <stdio.h>
+#include "vss-common.h"
+#include "requester.h"
+#include "assert.h"
+#include "inc/win2003/vswriter.h"
+#include "inc/win2003/vsbackup.h"
+
+/* Max wait time for frozen event (VSS can only hold writes for 10 seconds) */
+#define VSS_TIMEOUT_FREEZE_MSEC 10000
+
+/* Call QueryStatus every 10 ms while waiting for frozen event */
+#define VSS_TIMEOUT_EVENT_MSEC 10
+
+#define err_set(e, err, fmt, ...) \
+ ((e)->error_setg_win32((e)->errp, __FILE__, __LINE__, __func__, \
+ err, fmt, ## __VA_ARGS__))
+/* Bad idea, works only when (e)->errp != NULL: */
+#define err_is_set(e) ((e)->errp && *(e)->errp)
+/* To lift this restriction, error_propagate(), like we do in QEMU code */
+
+/* Handle to VSSAPI.DLL */
+static HMODULE hLib;
+
+/* Functions in VSSAPI.DLL */
+typedef HRESULT(STDAPICALLTYPE * t_CreateVssBackupComponents)(
+ OUT IVssBackupComponents**);
+typedef void(APIENTRY * t_VssFreeSnapshotProperties)(IN VSS_SNAPSHOT_PROP*);
+static t_CreateVssBackupComponents pCreateVssBackupComponents;
+static t_VssFreeSnapshotProperties pVssFreeSnapshotProperties;
+
+/* Variables used while applications and filesystes are frozen by VSS */
+static struct QGAVSSContext {
+ IVssBackupComponents *pVssbc; /* VSS requester interface */
+ IVssAsync *pAsyncSnapshot; /* async info of VSS snapshot operation */
+ HANDLE hEventFrozen; /* notify fs/writer freeze from provider */
+ HANDLE hEventThaw; /* request provider to thaw */
+ HANDLE hEventTimeout; /* notify timeout in provider */
+ int cFrozenVols; /* number of frozen volumes */
+} vss_ctx;
+
+STDAPI requester_init(void)
+{
+ COMInitializer initializer; /* to call CoInitializeSecurity */
+ HRESULT hr = CoInitializeSecurity(
+ NULL, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_PKT_PRIVACY,
+ RPC_C_IMP_LEVEL_IDENTIFY, NULL, EOAC_NONE, NULL);
+ if (FAILED(hr)) {
+ fprintf(stderr, "failed to CoInitializeSecurity (error %lx)\n", hr);
+ return hr;
+ }
+
+ hLib = LoadLibraryA("VSSAPI.DLL");
+ if (!hLib) {
+ fprintf(stderr, "failed to load VSSAPI.DLL\n");
+ return HRESULT_FROM_WIN32(GetLastError());
+ }
+
+ pCreateVssBackupComponents = (t_CreateVssBackupComponents)
+ GetProcAddress(hLib,
+#ifdef _WIN64 /* 64bit environment */
+ "?CreateVssBackupComponents@@YAJPEAPEAVIVssBackupComponents@@@Z"
+#else /* 32bit environment */
+ "?CreateVssBackupComponents@@YGJPAPAVIVssBackupComponents@@@Z"
+#endif
+ );
+ if (!pCreateVssBackupComponents) {
+ fprintf(stderr, "failed to get proc address from VSSAPI.DLL\n");
+ return HRESULT_FROM_WIN32(GetLastError());
+ }
+
+ pVssFreeSnapshotProperties = (t_VssFreeSnapshotProperties)
+ GetProcAddress(hLib, "VssFreeSnapshotProperties");
+ if (!pVssFreeSnapshotProperties) {
+ fprintf(stderr, "failed to get proc address from VSSAPI.DLL\n");
+ return HRESULT_FROM_WIN32(GetLastError());
+ }
+
+ return S_OK;
+}
+
+static void requester_cleanup(void)
+{
+ if (vss_ctx.hEventFrozen) {
+ CloseHandle(vss_ctx.hEventFrozen);
+ vss_ctx.hEventFrozen = NULL;
+ }
+ if (vss_ctx.hEventThaw) {
+ CloseHandle(vss_ctx.hEventThaw);
+ vss_ctx.hEventThaw = NULL;
+ }
+ if (vss_ctx.hEventTimeout) {
+ CloseHandle(vss_ctx.hEventTimeout);
+ vss_ctx.hEventTimeout = NULL;
+ }
+ if (vss_ctx.pAsyncSnapshot) {
+ vss_ctx.pAsyncSnapshot->Release();
+ vss_ctx.pAsyncSnapshot = NULL;
+ }
+ if (vss_ctx.pVssbc) {
+ vss_ctx.pVssbc->Release();
+ vss_ctx.pVssbc = NULL;
+ }
+ vss_ctx.cFrozenVols = 0;
+}
+
+STDAPI requester_deinit(void)
+{
+ requester_cleanup();
+
+ pCreateVssBackupComponents = NULL;
+ pVssFreeSnapshotProperties = NULL;
+ if (hLib) {
+ FreeLibrary(hLib);
+ hLib = NULL;
+ }
+
+ return S_OK;
+}
+
+static HRESULT WaitForAsync(IVssAsync *pAsync)
+{
+ HRESULT ret, hr;
+
+ do {
+ hr = pAsync->Wait();
+ if (FAILED(hr)) {
+ ret = hr;
+ break;
+ }
+ hr = pAsync->QueryStatus(&ret, NULL);
+ if (FAILED(hr)) {
+ ret = hr;
+ break;
+ }
+ } while (ret == VSS_S_ASYNC_PENDING);
+
+ return ret;
+}
+
+static void AddComponents(ErrorSet *errset)
+{
+ unsigned int cWriters, i;
+ VSS_ID id, idInstance, idWriter;
+ BSTR bstrWriterName = NULL;
+ VSS_USAGE_TYPE usage;
+ VSS_SOURCE_TYPE source;
+ unsigned int cComponents, c1, c2, j;
+ COMPointer<IVssExamineWriterMetadata> pMetadata;
+ COMPointer<IVssWMComponent> pComponent;
+ PVSSCOMPONENTINFO info;
+ HRESULT hr;
+
+ hr = vss_ctx.pVssbc->GetWriterMetadataCount(&cWriters);
+ if (FAILED(hr)) {
+ err_set(errset, hr, "failed to get writer metadata count");
+ goto out;
+ }
+
+ for (i = 0; i < cWriters; i++) {
+ hr = vss_ctx.pVssbc->GetWriterMetadata(i, &id, pMetadata.replace());
+ if (FAILED(hr)) {
+ err_set(errset, hr, "failed to get writer metadata of %d/%d",
+ i, cWriters);
+ goto out;
+ }
+
+ hr = pMetadata->GetIdentity(&idInstance, &idWriter,
+ &bstrWriterName, &usage, &source);
+ if (FAILED(hr)) {
+ err_set(errset, hr, "failed to get identity of writer %d/%d",
+ i, cWriters);
+ goto out;
+ }
+
+ hr = pMetadata->GetFileCounts(&c1, &c2, &cComponents);
+ if (FAILED(hr)) {
+ err_set(errset, hr, "failed to get file counts of %S",
+ bstrWriterName);
+ goto out;
+ }
+
+ for (j = 0; j < cComponents; j++) {
+ hr = pMetadata->GetComponent(j, pComponent.replace());
+ if (FAILED(hr)) {
+ err_set(errset, hr,
+ "failed to get component %d/%d of %S",
+ j, cComponents, bstrWriterName);
+ goto out;
+ }
+
+ hr = pComponent->GetComponentInfo(&info);
+ if (FAILED(hr)) {
+ err_set(errset, hr,
+ "failed to get component info %d/%d of %S",
+ j, cComponents, bstrWriterName);
+ goto out;
+ }
+
+ if (info->bSelectable) {
+ hr = vss_ctx.pVssbc->AddComponent(idInstance, idWriter,
+ info->type,
+ info->bstrLogicalPath,
+ info->bstrComponentName);
+ if (FAILED(hr)) {
+ err_set(errset, hr, "failed to add component %S(%S)",
+ info->bstrComponentName, bstrWriterName);
+ goto out;
+ }
+ }
+ SysFreeString(bstrWriterName);
+ bstrWriterName = NULL;
+ pComponent->FreeComponentInfo(info);
+ info = NULL;
+ }
+ }
+out:
+ if (bstrWriterName) {
+ SysFreeString(bstrWriterName);
+ }
+ if (pComponent && info) {
+ pComponent->FreeComponentInfo(info);
+ }
+}
+
+void requester_freeze(int *num_vols, ErrorSet *errset)
+{
+ COMPointer<IVssAsync> pAsync;
+ HANDLE volume;
+ HRESULT hr;
+ LONG ctx;
+ GUID guidSnapshotSet = GUID_NULL;
+ SECURITY_DESCRIPTOR sd;
+ SECURITY_ATTRIBUTES sa;
+ WCHAR short_volume_name[64], *display_name = short_volume_name;
+ DWORD wait_status;
+ int num_fixed_drives = 0, i;
+
+ if (vss_ctx.pVssbc) { /* already frozen */
+ *num_vols = 0;
+ return;
+ }
+
+ CoInitialize(NULL);
+
+ /* Allow unrestricted access to events */
+ InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION);
+ SetSecurityDescriptorDacl(&sd, TRUE, NULL, FALSE);
+ sa.nLength = sizeof(sa);
+ sa.lpSecurityDescriptor = &sd;
+ sa.bInheritHandle = FALSE;
+
+ vss_ctx.hEventFrozen = CreateEvent(&sa, TRUE, FALSE, EVENT_NAME_FROZEN);
+ if (!vss_ctx.hEventFrozen) {
+ err_set(errset, GetLastError(), "failed to create event %s",
+ EVENT_NAME_FROZEN);
+ goto out;
+ }
+ vss_ctx.hEventThaw = CreateEvent(&sa, TRUE, FALSE, EVENT_NAME_THAW);
+ if (!vss_ctx.hEventThaw) {
+ err_set(errset, GetLastError(), "failed to create event %s",
+ EVENT_NAME_THAW);
+ goto out;
+ }
+ vss_ctx.hEventTimeout = CreateEvent(&sa, TRUE, FALSE, EVENT_NAME_TIMEOUT);
+ if (!vss_ctx.hEventTimeout) {
+ err_set(errset, GetLastError(), "failed to create event %s",
+ EVENT_NAME_TIMEOUT);
+ goto out;
+ }
+
+ assert(pCreateVssBackupComponents != NULL);
+ hr = pCreateVssBackupComponents(&vss_ctx.pVssbc);
+ if (FAILED(hr)) {
+ err_set(errset, hr, "failed to create VSS backup components");
+ goto out;
+ }
+
+ hr = vss_ctx.pVssbc->InitializeForBackup();
+ if (FAILED(hr)) {
+ err_set(errset, hr, "failed to initialize for backup");
+ goto out;
+ }
+
+ hr = vss_ctx.pVssbc->SetBackupState(true, true, VSS_BT_FULL, false);
+ if (FAILED(hr)) {
+ err_set(errset, hr, "failed to set backup state");
+ goto out;
+ }
+
+ /*
+ * Currently writable snapshots are not supported.
+ * To prevent the final commit (which requires to write to snapshots),
+ * ATTR_NO_AUTORECOVERY and ATTR_TRANSPORTABLE are specified here.
+ */
+ ctx = VSS_CTX_APP_ROLLBACK | VSS_VOLSNAP_ATTR_TRANSPORTABLE |
+ VSS_VOLSNAP_ATTR_NO_AUTORECOVERY | VSS_VOLSNAP_ATTR_TXF_RECOVERY;
+ hr = vss_ctx.pVssbc->SetContext(ctx);
+ if (hr == (HRESULT)VSS_E_UNSUPPORTED_CONTEXT) {
+ /* Non-server version of Windows doesn't support ATTR_TRANSPORTABLE */
+ ctx &= ~VSS_VOLSNAP_ATTR_TRANSPORTABLE;
+ hr = vss_ctx.pVssbc->SetContext(ctx);
+ }
+ if (FAILED(hr)) {
+ err_set(errset, hr, "failed to set backup context");
+ goto out;
+ }
+
+ hr = vss_ctx.pVssbc->GatherWriterMetadata(pAsync.replace());
+ if (SUCCEEDED(hr)) {
+ hr = WaitForAsync(pAsync);
+ }
+ if (FAILED(hr)) {
+ err_set(errset, hr, "failed to gather writer metadata");
+ goto out;
+ }
+
+ AddComponents(errset);
+ if (err_is_set(errset)) {
+ goto out;
+ }
+
+ hr = vss_ctx.pVssbc->StartSnapshotSet(&guidSnapshotSet);
+ if (FAILED(hr)) {
+ err_set(errset, hr, "failed to start snapshot set");
+ 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) {
+ 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());
+ if (SUCCEEDED(hr)) {
+ hr = WaitForAsync(pAsync);
+ }
+ if (FAILED(hr)) {
+ err_set(errset, hr, "failed to prepare for backup");
+ goto out;
+ }
+
+ hr = vss_ctx.pVssbc->GatherWriterStatus(pAsync.replace());
+ if (SUCCEEDED(hr)) {
+ hr = WaitForAsync(pAsync);
+ }
+ if (FAILED(hr)) {
+ err_set(errset, hr, "failed to gather writer status");
+ goto out;
+ }
+
+ /*
+ * Start VSS quiescing operations.
+ * CQGAVssProvider::CommitSnapshots will kick vss_ctx.hEventFrozen
+ * after the applications and filesystems are frozen.
+ */
+ hr = vss_ctx.pVssbc->DoSnapshotSet(&vss_ctx.pAsyncSnapshot);
+ if (FAILED(hr)) {
+ err_set(errset, hr, "failed to do snapshot set");
+ goto out;
+ }
+
+ /* Need to call QueryStatus several times to make VSS provider progress */
+ for (i = 0; i < VSS_TIMEOUT_FREEZE_MSEC/VSS_TIMEOUT_EVENT_MSEC; i++) {
+ HRESULT hr2 = vss_ctx.pAsyncSnapshot->QueryStatus(&hr, NULL);
+ if (FAILED(hr2)) {
+ err_set(errset, hr, "failed to do snapshot set");
+ goto out;
+ }
+ if (hr != VSS_S_ASYNC_PENDING) {
+ err_set(errset, E_FAIL,
+ "DoSnapshotSet exited without Frozen event");
+ goto out;
+ }
+ wait_status = WaitForSingleObject(vss_ctx.hEventFrozen,
+ VSS_TIMEOUT_EVENT_MSEC);
+ if (wait_status != WAIT_TIMEOUT) {
+ break;
+ }
+ }
+ if (wait_status != WAIT_OBJECT_0) {
+ err_set(errset, E_FAIL,
+ "couldn't receive Frozen event from VSS provider");
+ goto out;
+ }
+
+ *num_vols = vss_ctx.cFrozenVols = num_fixed_drives;
+ return;
+
+out:
+ if (vss_ctx.pVssbc) {
+ vss_ctx.pVssbc->AbortBackup();
+ }
+ requester_cleanup();
+ CoUninitialize();
+}
+
+
+void requester_thaw(int *num_vols, ErrorSet *errset)
+{
+ COMPointer<IVssAsync> pAsync;
+
+ if (!vss_ctx.hEventThaw) {
+ /*
+ * In this case, DoSnapshotSet is aborted or not started,
+ * and no volumes must be frozen. We return without an error.
+ */
+ *num_vols = 0;
+ return;
+ }
+
+ /* Tell the provider that the snapshot is finished. */
+ SetEvent(vss_ctx.hEventThaw);
+
+ assert(vss_ctx.pVssbc);
+ assert(vss_ctx.pAsyncSnapshot);
+
+ HRESULT hr = WaitForAsync(vss_ctx.pAsyncSnapshot);
+ switch (hr) {
+ case VSS_S_ASYNC_FINISHED:
+ hr = vss_ctx.pVssbc->BackupComplete(pAsync.replace());
+ if (SUCCEEDED(hr)) {
+ hr = WaitForAsync(pAsync);
+ }
+ if (FAILED(hr)) {
+ err_set(errset, hr, "failed to complete backup");
+ }
+ break;
+
+ case (HRESULT)VSS_E_OBJECT_NOT_FOUND:
+ /*
+ * On Windows earlier than 2008 SP2 which does not support
+ * VSS_VOLSNAP_ATTR_NO_AUTORECOVERY context, the final commit is not
+ * skipped and VSS is aborted by VSS_E_OBJECT_NOT_FOUND. However, as
+ * the system had been frozen until fsfreeze-thaw command was issued,
+ * we ignore this error.
+ */
+ vss_ctx.pVssbc->AbortBackup();
+ break;
+
+ case VSS_E_UNEXPECTED_PROVIDER_ERROR:
+ if (WaitForSingleObject(vss_ctx.hEventTimeout, 0) != WAIT_OBJECT_0) {
+ err_set(errset, hr, "unexpected error in VSS provider");
+ break;
+ }
+ /* fall through if hEventTimeout is signaled */
+
+ case (HRESULT)VSS_E_HOLD_WRITES_TIMEOUT:
+ err_set(errset, hr, "couldn't hold writes: "
+ "fsfreeze is limited up to 10 seconds");
+ break;
+
+ default:
+ err_set(errset, hr, "failed to do snapshot set");
+ }
+
+ if (err_is_set(errset)) {
+ vss_ctx.pVssbc->AbortBackup();
+ }
+ *num_vols = vss_ctx.cFrozenVols;
+ requester_cleanup();
+
+ CoUninitialize();
+}
diff --git a/src/qga/vss-win32/requester.h b/src/qga/vss-win32/requester.h
new file mode 100644
index 0000000..c3093cf
--- /dev/null
+++ b/src/qga/vss-win32/requester.h
@@ -0,0 +1,46 @@
+/*
+ * QEMU Guest Agent VSS requester declarations
+ *
+ * Copyright Hitachi Data Systems Corp. 2013
+ *
+ * Authors:
+ * Tomoki Sekiyama <tomoki.sekiyama@hds.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#ifndef VSS_WIN32_REQUESTER_H
+#define VSS_WIN32_REQUESTER_H
+
+#include <basetyps.h> /* STDAPI */
+#include "qemu/compiler.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct Error;
+
+/* Callback to set Error; used to avoid linking glib to the DLL */
+typedef void (*ErrorSetFunc)(struct Error **errp,
+ const char *src, int line, const char *func,
+ int win32_err, const char *fmt, ...)
+ GCC_FMT_ATTR(6, 7);
+typedef struct ErrorSet {
+ ErrorSetFunc error_setg_win32;
+ struct Error **errp; /* restriction: must not be null */
+} 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);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/qga/vss-win32/vss-common.h b/src/qga/vss-win32/vss-common.h
new file mode 100644
index 0000000..ce14e14
--- /dev/null
+++ b/src/qga/vss-win32/vss-common.h
@@ -0,0 +1,129 @@
+/*
+ * QEMU Guest Agent win32 VSS common declarations
+ *
+ * Copyright Hitachi Data Systems Corp. 2013
+ *
+ * Authors:
+ * Tomoki Sekiyama <tomoki.sekiyama@hds.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#ifndef VSS_WIN32_H
+#define VSS_WIN32_H
+
+#define __MIDL_user_allocate_free_DEFINED__
+#include "config-host.h"
+#include <windows.h>
+#include <shlwapi.h>
+
+/* Reduce warnings to include vss.h */
+
+/* Ignore annotations for MS IDE */
+#define __in IN
+#define __out OUT
+#define __RPC_unique_pointer
+#define __RPC_string
+#define __RPC__deref_inout_opt
+#define __RPC__out
+#ifndef __RPC__out_ecount_part
+#define __RPC__out_ecount_part(x, y)
+#endif
+#define _declspec(x)
+#undef uuid
+#define uuid(x)
+
+/* Undef some duplicated error codes redefined in vss.h */
+#undef VSS_E_BAD_STATE
+#undef VSS_E_PROVIDER_NOT_REGISTERED
+#undef VSS_E_PROVIDER_VETO
+#undef VSS_E_OBJECT_NOT_FOUND
+#undef VSS_E_VOLUME_NOT_SUPPORTED
+#undef VSS_E_VOLUME_NOT_SUPPORTED_BY_PROVIDER
+#undef VSS_E_OBJECT_ALREADY_EXISTS
+#undef VSS_E_UNEXPECTED_PROVIDER_ERROR
+#undef VSS_E_INVALID_XML_DOCUMENT
+#undef VSS_E_MAXIMUM_NUMBER_OF_VOLUMES_REACHED
+#undef VSS_E_MAXIMUM_NUMBER_OF_SNAPSHOTS_REACHED
+
+/*
+ * VSS headers must be installed from Microsoft VSS SDK 7.2 available at:
+ * http://www.microsoft.com/en-us/download/details.aspx?id=23490
+ */
+#include "inc/win2003/vss.h"
+
+/* Macros to convert char definitions to wchar */
+#define _L(a) L##a
+#define L(a) _L(a)
+
+/* Constants for QGA VSS Provider */
+
+#define QGA_PROVIDER_NAME "QEMU Guest Agent VSS Provider"
+#define QGA_PROVIDER_LNAME L(QGA_PROVIDER_NAME)
+#define QGA_PROVIDER_VERSION L(QEMU_VERSION)
+
+#define EVENT_NAME_FROZEN "Global\\QGAVSSEvent-frozen"
+#define EVENT_NAME_THAW "Global\\QGAVSSEvent-thaw"
+#define EVENT_NAME_TIMEOUT "Global\\QGAVSSEvent-timeout"
+
+const GUID g_gProviderId = { 0x3629d4ed, 0xee09, 0x4e0e,
+ {0x9a, 0x5c, 0x6d, 0x8b, 0xa2, 0x87, 0x2a, 0xef} };
+const GUID g_gProviderVersion = { 0x11ef8b15, 0xcac6, 0x40d6,
+ {0x8d, 0x5c, 0x8f, 0xfc, 0x16, 0x3f, 0x24, 0xca} };
+
+const CLSID CLSID_QGAVSSProvider = { 0x6e6a3492, 0x8d4d, 0x440c,
+ {0x96, 0x19, 0x5e, 0x5d, 0x0c, 0xc3, 0x1c, 0xa8} };
+
+const TCHAR g_szClsid[] = TEXT("{6E6A3492-8D4D-440C-9619-5E5D0CC31CA8}");
+const TCHAR g_szProgid[] = TEXT("QGAVSSProvider");
+
+/* Enums undefined in VSS SDK 7.2 but defined in newer Windows SDK */
+enum __VSS_VOLUME_SNAPSHOT_ATTRIBUTES {
+ VSS_VOLSNAP_ATTR_NO_AUTORECOVERY = 0x00000002,
+ VSS_VOLSNAP_ATTR_TXF_RECOVERY = 0x02000000
+};
+
+
+/* COM pointer utility; call ->Release() when it goes out of scope */
+template <class T>
+class COMPointer {
+ COMPointer(const COMPointer<T> &p) { } /* no copy */
+ T *p;
+public:
+ COMPointer &operator=(T *new_p)
+ {
+ /* Assignment of a new T* (or NULL) causes release of previous p */
+ if (p && p != new_p) {
+ p->Release();
+ }
+ p = new_p;
+ return *this;
+ }
+ /* Replace by assignment to the pointer of p */
+ T **replace(void)
+ {
+ *this = NULL;
+ return &p;
+ }
+ /* Make COMPointer be used like T* */
+ operator T*() { return p; }
+ T *operator->(void) { return p; }
+ T &operator*(void) { return *p; }
+ operator bool() { return !!p; }
+
+ COMPointer(T *p = NULL) : p(p) { }
+ ~COMPointer() { *this = NULL; } /* Automatic release */
+};
+
+/*
+ * COM initializer; this should declared before COMPointer to uninitialize COM
+ * after releasing COM objects.
+ */
+class COMInitializer {
+public:
+ COMInitializer() { CoInitialize(NULL); }
+ ~COMInitializer() { CoUninitialize(); }
+};
+
+#endif
OpenPOWER on IntegriCloud