diff options
Diffstat (limited to 'qga')
| -rw-r--r-- | qga/Makefile.objs | 8 | ||||
| -rw-r--r-- | qga/channel-posix.c | 275 | ||||
| -rw-r--r-- | qga/channel-win32.c | 360 | ||||
| -rw-r--r-- | qga/channel.h | 33 | ||||
| -rw-r--r-- | qga/commands-posix.c | 2490 | ||||
| -rw-r--r-- | qga/commands-win32.c | 1261 | ||||
| -rw-r--r-- | qga/commands.c | 72 | ||||
| -rw-r--r-- | qga/guest-agent-command-state.c | 73 | ||||
| -rw-r--r-- | qga/guest-agent-core.h | 43 | ||||
| -rw-r--r-- | qga/installer/qemu-ga.wxs | 145 | ||||
| -rw-r--r-- | qga/main.c | 1206 | ||||
| -rw-r--r-- | qga/qapi-schema.json | 931 | ||||
| -rw-r--r-- | qga/service-win32.c | 192 | ||||
| -rw-r--r-- | qga/service-win32.h | 31 | ||||
| -rw-r--r-- | qga/vss-win32.c | 166 | ||||
| -rw-r--r-- | qga/vss-win32.h | 27 | ||||
| -rw-r--r-- | qga/vss-win32/Makefile.objs | 23 | ||||
| -rw-r--r-- | qga/vss-win32/install.cpp | 465 | ||||
| -rw-r--r-- | qga/vss-win32/provider.cpp | 534 | ||||
| -rw-r--r-- | qga/vss-win32/qga-vss.def | 13 | ||||
| -rw-r--r-- | qga/vss-win32/qga-vss.idl | 20 | ||||
| -rw-r--r-- | qga/vss-win32/qga-vss.tlb | bin | 0 -> 1528 bytes | |||
| -rw-r--r-- | qga/vss-win32/requester.cpp | 503 | ||||
| -rw-r--r-- | qga/vss-win32/requester.h | 43 | ||||
| -rw-r--r-- | qga/vss-win32/vss-common.h | 129 | 
25 files changed, 9043 insertions, 0 deletions
diff --git a/qga/Makefile.objs b/qga/Makefile.objs new file mode 100644 index 00000000..1c5986c0 --- /dev/null +++ b/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/qga/channel-posix.c b/qga/channel-posix.c new file mode 100644 index 00000000..8aad4fee --- /dev/null +++ b/qga/channel-posix.c @@ -0,0 +1,275 @@ +#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) { +        status = g_io_channel_write_chars(c->client_channel, buf, size, +                                          &written, &err); +        g_debug("sending data, count: %d", (int)size); +        if (err != NULL) { +            g_warning("error writing to channel: %s", err->message); +            return G_IO_STATUS_ERROR; +        } +        if (status != G_IO_STATUS_NORMAL) { +            break; +        } +        size -= written; +    } + +    if (status == G_IO_STATUS_NORMAL) { +        status = g_io_channel_flush(c->client_channel, &err); +        if (err != NULL) { +            g_warning("error flushing channel: %s", err->message); +            return G_IO_STATUS_ERROR; +        } +    } + +    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_malloc0(sizeof(GAChannel)); +    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/qga/channel-win32.c b/qga/channel-win32.c new file mode 100644 index 00000000..04fa5e4d --- /dev/null +++ b/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; + +    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_malloc0(sizeof(GAChannel)); +    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/qga/channel.h b/qga/channel.h new file mode 100644 index 00000000..3704ea9c --- /dev/null +++ b/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/qga/commands-posix.c b/qga/commands-posix.c new file mode 100644 index 00000000..675f4b4c --- /dev/null +++ b/qga/commands-posix.c @@ -0,0 +1,2490 @@ +/* + * 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 <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" + +#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 struct GuestFileHandle { +    uint64_t id; +    FILE *fh; +    QTAILQ_ENTRY(GuestFileHandle) next; +} GuestFileHandle; + +static struct { +    QTAILQ_HEAD(, GuestFileHandle) filehandles; +} guest_file_state; + +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_malloc0(sizeof(GuestFileHandle)); +    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; +} + +static int guest_file_toggle_flags(int fd, int flags, bool set, Error **err) +{ +    int ret, old_flags; + +    old_flags = fcntl(fd, F_GETFL); +    if (old_flags == -1) { +        error_setg_errno(err, errno, QERR_QGA_COMMAND_FAILED, +                         "failed to fetch filehandle flags"); +        return -1; +    } + +    ret = fcntl(fd, F_SETFL, set ? (old_flags | flags) : (old_flags & ~flags)); +    if (ret == -1) { +        error_setg_errno(err, errno, QERR_QGA_COMMAND_FAILED, +                         "failed to set filehandle flags"); +        return -1; +    } + +    return ret; +} + +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 +     */ +    if (guest_file_toggle_flags(fileno(fh), O_NONBLOCK, true, errp) < 0) { +        fclose(fh); +        return -1; +    } + +    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; +    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_malloc0(sizeof(GuestFileRead)); +        read_data->count = read_count; +        read_data->eof = feof(fh); +        if (read_count) { +            read_data->buf_b64 = g_base64_encode(buf, read_count); +        } +    } +    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; +    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_malloc0(sizeof(GuestFileWrite)); +        write_data->count = write_count; +        write_data->eof = feof(fh); +    } +    g_free(buf); +    clearerr(fh); + +    return write_data; +} + +struct GuestFileSeek *qmp_guest_file_seek(int64_t handle, int64_t offset, +                                          int64_t whence, Error **errp) +{ +    GuestFileHandle *gfh = guest_file_handle_find(handle, errp); +    GuestFileSeek *seek_data = NULL; +    FILE *fh; +    int ret; + +    if (!gfh) { +        return NULL; +    } + +    fh = gfh->fh; +    ret = fseek(fh, offset, whence); +    if (ret == -1) { +        error_setg_errno(errp, errno, "failed to seek file"); +    } else { +        seek_data = g_new0(GuestFileSeek, 1); +        seek_data->position = ftell(fh); +        seek_data->eof = feof(fh); +    } +    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"); +    } +} + +static void guest_file_init(void) +{ +    QTAILQ_INIT(&guest_file_state.filehandles); +} + +/* 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_malloc0(sizeof(FsMount)); +        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_malloc0(sizeof(FsMount)); +        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 does'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) { +        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, *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, *p++); +        } +    } +#endif + +#if !defined(CONFIG_FSTRIM) +    blacklist = g_list_append(blacklist, (char *)"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 +    ga_command_state_add(cs, guest_file_init, NULL); +} diff --git a/qga/commands-win32.c b/qga/commands-win32.c new file mode 100644 index 00000000..a7822d5f --- /dev/null +++ b/qga/commands-win32.c @@ -0,0 +1,1261 @@ +/* + * 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 "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; + + +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",   GENERIC_WRITE,              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+",  GENERIC_WRITE|GENERIC_READ, OPEN_ALWAYS  }, +    {"ab+", GENERIC_WRITE|GENERIC_READ, OPEN_ALWAYS  }, +    {"a+b", GENERIC_WRITE|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_malloc0(sizeof(GuestFileHandle)); +    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; +} + +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; +    } + +    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_malloc0(sizeof(GuestFileRead)); +        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_malloc0(sizeof(GuestFileWrite)); +        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, Error **errp) +{ +    GuestFileHandle *gfh; +    GuestFileSeek *seek_data; +    HANDLE fh; +    LARGE_INTEGER new_pos, off_pos; +    off_pos.QuadPart = offset; +    BOOL res; +    gfh = guest_file_handle_find(handle, errp); +    if (!gfh) { +        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"); +    } +} + +static void guest_file_init(void) +{ +    QTAILQ_INIT(&guest_file_state.filehandles); +} + +#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_malloc(sizeof(GuestSuspendMode)); + +    *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_malloc(sizeof(GuestSuspendMode)); + +    *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; +} + +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; +} + +/* 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-set-user-password", +        "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, *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, *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); +    } +    ga_command_state_add(cs, guest_file_init, NULL); +} diff --git a/qga/commands.c b/qga/commands.c new file mode 100644 index 00000000..78349679 --- /dev/null +++ b/qga/commands.c @@ -0,0 +1,72 @@ +/* + * 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" + +/* 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_malloc0(sizeof(GuestAgentCommandInfo)); +    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_malloc0(sizeof(GuestAgentCommandInfoList)); +    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_malloc0(sizeof(GuestAgentInfo)); + +    info->version = g_strdup(QEMU_VERSION); +    qmp_for_each_command(qmp_command_info, info); +    return info; +} diff --git a/qga/guest-agent-command-state.c b/qga/guest-agent-command-state.c new file mode 100644 index 00000000..969da232 --- /dev/null +++ b/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_malloc0(sizeof(GACommandGroup)); +    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_malloc0(sizeof(GACommandState)); +    cs->groups = NULL; +    return cs; +} diff --git a/qga/guest-agent-core.h b/qga/guest-agent-core.h new file mode 100644 index 00000000..e92c6aba --- /dev/null +++ b/qga/guest-agent-core.h @@ -0,0 +1,43 @@ +/* + * 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 + +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/qga/installer/qemu-ga.wxs b/qga/installer/qemu-ga.wxs new file mode 100644 index 00000000..2c43f1b5 --- /dev/null +++ b/qga/installer/qemu-ga.wxs @@ -0,0 +1,145 @@ +<?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> +    <Property Id="PREVIOUSVERSIONSINSTALLED" /> +    <Upgrade Id="{EB6B8302-C06E-4bec-ADAC-932C68A3A98D}"> +      <UpgradeVersion +        Minimum="1.0.0.0" Maximum="$(env.QEMU_GA_VERSION)" +        Property="PREVIOUSVERSIONSINSTALLED" +        IncludeMinimum="yes" IncludeMaximum="no" /> +    </Upgrade> + +    <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="../../qemu-ga.exe" KeyPath="yes" DiskId="1"/> +            <?ifdef var.InstallVss ?> +            <File Id="qga_vss.dll" Name="qga-vss.dll" Source="../vss-win32/qga-vss.dll" KeyPath="no" DiskId="1"/> +            <File Id="qga_vss.tlb" Name="qga-vss.tlb" Source="../vss-win32/qga-vss.tlb" KeyPath="no" DiskId="1"/> +            <?endif?> +            <File Id="iconv.dll" Name="iconv.dll" Source="$(var.Mingw_bin)/iconv.dll" KeyPath="no" DiskId="1"/> +            <File Id="libgcc_arch_lib" Name="$(var.ArchLib)" Source="$(var.Mingw_bin)/$(var.ArchLib)" KeyPath="no" DiskId="1"/> +            <File Id="libglib_2.0_0.dll" Name="libglib-2.0-0.dll" Source="$(var.Mingw_bin)/libglib-2.0-0.dll" KeyPath="no" DiskId="1"/> +            <File Id="libintl_8.dll" Name="libintl-8.dll" Source="$(var.Mingw_bin)/libintl-8.dll" KeyPath="no" DiskId="1"/> +            <File Id="libssp_0.dll" Name="libssp-0.dll" Source="$(var.Mingw_bin)/libssp-0.dll" KeyPath="no" DiskId="1"/> +            <File Id="libwinpthread_1.dll" Name="libwinpthread-1.dll" Source="$(var.Mingw_bin)/libwinpthread-1.dll" KeyPath="no" 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> + +          <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"/> + +    <?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" /> +      <ComponentRef Id="registry_entries" /> +    </Feature> + +    <InstallExecuteSequence> +      <RemoveExistingProducts Before="InstallInitialize" /> +      <?ifdef var.InstallVss ?> +      <Custom Action="RegisterCom" After="InstallServices">NOT Installed</Custom> +      <Custom Action="UnRegisterCom" After="StopServices">Installed</Custom> +      <?endif?> +    </InstallExecuteSequence> +  </Product> +</Wix> diff --git a/qga/main.c b/qga/main.c new file mode 100644 index 00000000..791982ef --- /dev/null +++ b/qga/main.c @@ -0,0 +1,1206 @@ +/* + * 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 + +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; +    const char *state_filepath_isfrozen; +    struct { +        const char *log_filepath; +        const char *pid_filepath; +    } deferred_options; +#ifdef CONFIG_FSFREEZE +    const char *fsfreeze_hook; +#endif +    const 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)); +    } + +    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" +"  -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, QList *tokens) +{ +    GAState *s = container_of(parser, GAState, parser); +    QObject *obj; +    QDict *qdict; +    Error *err = NULL; +    int ret; + +    g_assert(s && parser); + +    g_debug("process_event: called"); +    obj = json_parser_parse_err(tokens, NULL, &err); +    if (err || !obj || qobject_type(obj) != QTYPE_QDICT) { +        qobject_decref(obj); +        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); +    } else { +        qdict = qobject_to_qdict(obj); +    } + +    g_assert(qdict); + +    /* 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 (method == NULL) { +        method = "virtio-serial"; +    } + +    if (path == NULL) { +        if (strcmp(method, "virtio-serial") == 0 ) { +            /* try the default path for the virtio-serial port */ +            path = QGA_VIRTIO_PATH_DEFAULT; +        } else if (strcmp(method, "isa-serial") == 0){ +            /* try the default path for the serial port - COM1 */ +            path = QGA_SERIAL_PATH_DEFAULT; +        } else { +            g_critical("must specify a path for this channel"); +            return false; +        } +    } + +    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)); +} + +int main(int argc, char **argv) +{ +    const char *sopt = "hVvdm:p:l:f:F::b:s:t:"; +    const char *method = NULL, *path = NULL; +    const char *log_filepath = NULL; +    const char *pid_filepath; +#ifdef CONFIG_FSFREEZE +    const char *fsfreeze_hook = NULL; +#endif +    const char *state_dir; +#ifdef _WIN32 +    const char *service = NULL; +#endif +    const struct option lopt[] = { +        { "help", 0, NULL, 'h' }, +        { "version", 0, NULL, 'V' }, +        { "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 } +    }; +    int opt_ind = 0, ch, daemonize = 0, i, j, len; +    GLogLevelFlags log_level = G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL; +    GList *blacklist = NULL; +    GAState *s; + +    module_call_init(MODULE_INIT_QAPI); + +    init_dfl_pathnames(); +    pid_filepath = dfl_pathnames.pidfile; +    state_dir = dfl_pathnames.state_dir; + +    while ((ch = getopt_long(argc, argv, sopt, lopt, &opt_ind)) != -1) { +        switch (ch) { +        case 'm': +            method = optarg; +            break; +        case 'p': +            path = optarg; +            break; +        case 'l': +            log_filepath = optarg; +            break; +        case 'f': +            pid_filepath = optarg; +            break; +#ifdef CONFIG_FSFREEZE +        case 'F': +            fsfreeze_hook = optarg ? optarg : QGA_FSFREEZE_HOOK_DEFAULT; +            break; +#endif +        case 't': +             state_dir = optarg; +             break; +        case 'v': +            /* enable all log levels */ +            log_level = G_LOG_LEVEL_MASK; +            break; +        case 'V': +            printf("QEMU Guest Agent %s\n", QEMU_VERSION); +            return 0; +        case 'd': +            daemonize = 1; +            break; +        case 'b': { +            if (is_help_option(optarg)) { +                qmp_for_each_command(ga_print_cmd, NULL); +                return 0; +            } +            for (j = 0, i = 0, len = strlen(optarg); i < len; i++) { +                if (optarg[i] == ',') { +                    optarg[i] = 0; +                    blacklist = g_list_append(blacklist, &optarg[j]); +                    j = i + 1; +                } +            } +            if (j < i) { +                blacklist = g_list_append(blacklist, &optarg[j]); +            } +            break; +        } +#ifdef _WIN32 +        case 's': +            service = optarg; +            if (strcmp(service, "install") == 0) { +                const char *fixed_state_dir; + +                /* If the user passed the "-t" option, we save that state dir +                 * in the service. Otherwise we let the service fetch the state +                 * dir from the environment when it starts. +                 */ +                fixed_state_dir = (state_dir == dfl_pathnames.state_dir) ? +                                  NULL : +                                  state_dir; +                if (ga_install_vss_provider()) { +                    return EXIT_FAILURE; +                } +                if (ga_install_service(path, log_filepath, fixed_state_dir)) { +                    return EXIT_FAILURE; +                } +                return 0; +            } else if (strcmp(service, "uninstall") == 0) { +                ga_uninstall_vss_provider(); +                return ga_uninstall_service(); +            } else if (strcmp(service, "vss-install") == 0) { +                if (ga_install_vss_provider()) { +                    return EXIT_FAILURE; +                } +                return EXIT_SUCCESS; +            } else if (strcmp(service, "vss-uninstall") == 0) { +                ga_uninstall_vss_provider(); +                return EXIT_SUCCESS; +            } else { +                printf("Unknown service command.\n"); +                return EXIT_FAILURE; +            } +            break; +#endif +        case 'h': +            usage(argv[0]); +            return 0; +        case '?': +            g_print("Unknown option, try '%s --help' for more information.\n", +                    argv[0]); +            return EXIT_FAILURE; +        } +    } + +#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(state_dir, S_IRWXU) == -1) { +        g_critical("unable to create (an ancestor of) the state directory" +                   " '%s': %s", state_dir, strerror(errno)); +        return EXIT_FAILURE; +    } +#endif + +    s = g_malloc0(sizeof(GAState)); +    s->log_level = log_level; +    s->log_file = stderr; +#ifdef CONFIG_FSFREEZE +    s->fsfreeze_hook = fsfreeze_hook; +#endif +    g_log_set_default_handler(ga_log, s); +    g_log_set_fatal_mask(NULL, G_LOG_LEVEL_ERROR); +    ga_enable_logging(s); +    s->state_filepath_isfrozen = g_strdup_printf("%s/qga.state.isfrozen", +                                                 state_dir); +    s->pstate_filepath = g_strdup_printf("%s/qga.state", state_dir); +    s->frozen = false; + +#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); +        s->frozen = true; +    } +#endif + +    if (ga_is_frozen(s)) { +        if (daemonize) { +            /* delay opening/locking of pidfile till filesystems are unfrozen */ +            s->deferred_options.pid_filepath = pid_filepath; +            become_daemon(NULL); +        } +        if (log_filepath) { +            /* delay opening the log file till filesystems are unfrozen */ +            s->deferred_options.log_filepath = log_filepath; +        } +        ga_disable_logging(s); +        qmp_for_each_command(ga_disable_non_whitelisted, NULL); +    } else { +        if (daemonize) { +            become_daemon(pid_filepath); +        } +        if (log_filepath) { +            FILE *log_file = ga_open_logfile(log_filepath); +            if (!log_file) { +                g_critical("unable to open specified log file: %s", +                           strerror(errno)); +                goto out_bad; +            } +            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"); +        goto out_bad; +    } + +    blacklist = ga_command_blacklist_init(blacklist); +    if (blacklist) { +        s->blacklist = blacklist; +        do { +            g_debug("disabling command: %s", (char *)blacklist->data); +            qmp_disable_command(blacklist->data); +            blacklist = g_list_next(blacklist); +        } while (blacklist); +    } +    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"); +        goto out_bad; +    } +#endif + +    s->main_loop = g_main_loop_new(NULL, false); +    if (!channel_init(ga_state, method, path)) { +        g_critical("failed to initialize guest agent channel"); +        goto out_bad; +    } +#ifndef _WIN32 +    g_main_loop_run(ga_state->main_loop); +#else +    if (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 + +    ga_command_state_cleanup_all(ga_state->command_state); +    ga_channel_free(ga_state->channel); + +    if (daemonize) { +        unlink(pid_filepath); +    } +    return 0; + +out_bad: +    if (daemonize) { +        unlink(pid_filepath); +    } +    return EXIT_FAILURE; +} diff --git a/qga/qapi-schema.json b/qga/qapi-schema.json new file mode 100644 index 00000000..18e3cc37 --- /dev/null +++ b/qga/qapi-schema.json @@ -0,0 +1,931 @@ +# *-*- 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. +# +## + +## +# +# 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, just Set offset=0, whence=SEEK_CUR. +# +# @handle: filehandle returned by guest-file-open +# +# @offset: bytes to skip over in the file stream +# +# @whence: SEEK_SET, SEEK_CUR, or SEEK_END, as with fseek() +# +# 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. +# +# Note 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' } diff --git a/qga/service-win32.c b/qga/service-win32.c new file mode 100644 index 00000000..aef41f04 --- /dev/null +++ b/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/qga/service-win32.h b/qga/service-win32.h new file mode 100644 index 00000000..3b9e8702 --- /dev/null +++ b/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/qga/vss-win32.c b/qga/vss-win32.c new file mode 100644 index 00000000..0e409573 --- /dev/null +++ b/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_set = (ErrorSetFunc)error_set_win32, +        .errp = (void **)errp, +        .err_class = ERROR_CLASS_GENERIC_ERROR +    }; + +    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/qga/vss-win32.h b/qga/vss-win32.h new file mode 100644 index 00000000..298927df --- /dev/null +++ b/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/qga/vss-win32/Makefile.objs b/qga/vss-win32/Makefile.objs new file mode 100644 index 00000000..7c96c6b2 --- /dev/null +++ b/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/qga/vss-win32/install.cpp b/qga/vss-win32/install.cpp new file mode 100644 index 00000000..b0e4426c --- /dev/null +++ b/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/qga/vss-win32/provider.cpp b/qga/vss-win32/provider.cpp new file mode 100644 index 00000000..d5129f8f --- /dev/null +++ b/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/qga/vss-win32/qga-vss.def b/qga/vss-win32/qga-vss.def new file mode 100644 index 00000000..927782c3 --- /dev/null +++ b/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/qga/vss-win32/qga-vss.idl b/qga/vss-win32/qga-vss.idl new file mode 100644 index 00000000..17abca0d --- /dev/null +++ b/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/qga/vss-win32/qga-vss.tlb b/qga/vss-win32/qga-vss.tlb Binary files differnew file mode 100644 index 00000000..226452a1 --- /dev/null +++ b/qga/vss-win32/qga-vss.tlb diff --git a/qga/vss-win32/requester.cpp b/qga/vss-win32/requester.cpp new file mode 100644 index 00000000..922e74dd --- /dev/null +++ b/qga/vss-win32/requester.cpp @@ -0,0 +1,503 @@ +/* + * 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_set((e)->errp, err, (e)->err_class, fmt, ## __VA_ARGS__)) +#define err_is_set(e) ((e)->errp && *(e)->errp) + + +/* 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/qga/vss-win32/requester.h b/qga/vss-win32/requester.h new file mode 100644 index 00000000..374f9b8d --- /dev/null +++ b/qga/vss-win32/requester.h @@ -0,0 +1,43 @@ +/* + * 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 + +/* Callback to set Error; used to avoid linking glib to the DLL */ +typedef void (*ErrorSetFunc)(void **errp, int win32_err, int err_class, +                             const char *fmt, ...) GCC_FMT_ATTR(4, 5); +typedef struct ErrorSet { +    ErrorSetFunc error_set; +    void **errp; +    int err_class; +} 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/qga/vss-win32/vss-common.h b/qga/vss-win32/vss-common.h new file mode 100644 index 00000000..ce14e142 --- /dev/null +++ b/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  | 
