diff options
Diffstat (limited to 'qga')
-rw-r--r-- | qga/channel-posix.c | 246 | ||||
-rw-r--r-- | qga/channel-win32.c | 340 | ||||
-rw-r--r-- | qga/channel.h | 33 | ||||
-rw-r--r-- | qga/commands-posix.c (renamed from qga/guest-agent-commands.c) | 59 | ||||
-rw-r--r-- | qga/commands-win32.c | 130 | ||||
-rw-r--r-- | qga/commands.c | 73 | ||||
-rw-r--r-- | qga/guest-agent-core.h | 3 | ||||
-rw-r--r-- | qga/service-win32.c | 114 | ||||
-rw-r--r-- | qga/service-win32.h | 30 |
9 files changed, 969 insertions, 59 deletions
diff --git a/qga/channel-posix.c b/qga/channel-posix.c new file mode 100644 index 0000000000..40f7658ccd --- /dev/null +++ b/qga/channel-posix.c @@ -0,0 +1,246 @@ +#include <glib.h> +#include <termios.h> +#include "qemu_socket.h" +#include "qga/channel.h" + +#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; + } + fcntl(client_fd, F_SETFL, O_NONBLOCK); + ret = ga_channel_client_add(c, client_fd); + if (ret) { + g_warning("error setting up connection"); + 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 | O_ASYNC); + if (fd == -1) { + g_critical("error opening channel: %s", strerror(errno)); + exit(EXIT_FAILURE); + } + ret = ga_channel_client_add(c, fd); + if (ret) { + g_critical("error adding channel to main loop"); + 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)); + exit(EXIT_FAILURE); + } + 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_error("error adding channel to main loop"); + } + break; + } + case GA_CHANNEL_UNIX_LISTEN: { + int fd = unix_listen(path, NULL, strlen(path)); + if (fd == -1) { + g_critical("error opening path: %s", strerror(errno)); + 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 0000000000..190251bb57 --- /dev/null +++ b/qga/channel-win32.c @@ -0,0 +1,340 @@ +#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; + } + + 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) +{ + if (!method == GA_CHANNEL_VIRTIO_SERIAL) { + g_critical("unsupported communication method"); + return false; + } + + c->handle = CreateFile(path, 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"); + 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 0000000000..3704ea9c86 --- /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/guest-agent-commands.c b/qga/commands-posix.c index a09c8ca230..126127aae4 100644 --- a/qga/guest-agent-commands.c +++ b/qga/commands-posix.c @@ -1,5 +1,5 @@ /* - * QEMU Guest Agent commands + * QEMU Guest Agent POSIX-specific command implementations * * Copyright IBM Corp. 2011 * @@ -30,63 +30,6 @@ static GAState *ga_state; -/* 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 - */ -static void slog(const char *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(int64_t id, Error **errp) -{ - return id; -} - -void qmp_guest_ping(Error **err) -{ - slog("guest-ping called"); -} - -struct GuestAgentInfo *qmp_guest_info(Error **err) -{ - GuestAgentInfo *info = g_malloc0(sizeof(GuestAgentInfo)); - GuestAgentCommandInfo *cmd_info; - GuestAgentCommandInfoList *cmd_info_list; - char **cmd_list_head, **cmd_list; - - info->version = g_strdup(QGA_VERSION); - - cmd_list_head = cmd_list = qmp_get_command_list(); - if (*cmd_list_head == NULL) { - goto out; - } - - while (*cmd_list) { - cmd_info = g_malloc0(sizeof(GuestAgentCommandInfo)); - cmd_info->name = strdup(*cmd_list); - cmd_info->enabled = qmp_command_is_enabled(cmd_info->name); - - 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; - - g_free(*cmd_list); - cmd_list++; - } - -out: - g_free(cmd_list_head); - return info; -} - void qmp_guest_shutdown(bool has_mode, const char *mode, Error **err) { int ret; diff --git a/qga/commands-win32.c b/qga/commands-win32.c new file mode 100644 index 0000000000..4aa0f0d1e4 --- /dev/null +++ b/qga/commands-win32.c @@ -0,0 +1,130 @@ +/* + * QEMU Guest Agent win32-specific 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 "qerror.h" + +#ifndef SHTDN_REASON_FLAG_PLANNED +#define SHTDN_REASON_FLAG_PLANNED 0x80000000 +#endif + +void qmp_guest_shutdown(bool has_mode, const char *mode, Error **err) +{ + HANDLE token; + TOKEN_PRIVILEGES priv; + 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_set(err, QERR_INVALID_PARAMETER_VALUE, "mode", + "halt|powerdown|reboot"); + return; + } + + /* Request a shutdown privilege, but try to shut down the system + anyway. */ + if (OpenProcessToken(GetCurrentProcess(), + TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY, &token)) + { + LookupPrivilegeValue(NULL, SE_SHUTDOWN_NAME, + &priv.Privileges[0].Luid); + + priv.PrivilegeCount = 1; + priv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; + + AdjustTokenPrivileges(token, FALSE, &priv, 0, NULL, 0); + } + + if (!ExitWindowsEx(shutdown_flag, SHTDN_REASON_FLAG_PLANNED)) { + slog("guest-shutdown failed: %d", GetLastError()); + error_set(err, QERR_UNDEFINED_ERROR); + } +} + +int64_t qmp_guest_file_open(const char *path, bool has_mode, const char *mode, Error **err) +{ + error_set(err, QERR_UNSUPPORTED); + return 0; +} + +void qmp_guest_file_close(int64_t handle, Error **err) +{ + error_set(err, QERR_UNSUPPORTED); +} + +GuestFileRead *qmp_guest_file_read(int64_t handle, bool has_count, + int64_t count, Error **err) +{ + error_set(err, QERR_UNSUPPORTED); + return 0; +} + +GuestFileWrite *qmp_guest_file_write(int64_t handle, const char *buf_b64, + bool has_count, int64_t count, Error **err) +{ + error_set(err, QERR_UNSUPPORTED); + return 0; +} + +GuestFileSeek *qmp_guest_file_seek(int64_t handle, int64_t offset, + int64_t whence, Error **err) +{ + error_set(err, QERR_UNSUPPORTED); + return 0; +} + +void qmp_guest_file_flush(int64_t handle, Error **err) +{ + error_set(err, QERR_UNSUPPORTED); +} + +/* + * Return status of freeze/thaw + */ +GuestFsfreezeStatus qmp_guest_fsfreeze_status(Error **err) +{ + error_set(err, QERR_UNSUPPORTED); + return 0; +} + +/* + * 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(Error **err) +{ + error_set(err, QERR_UNSUPPORTED); + return 0; +} + +/* + * Walk list of frozen file systems in the guest, and thaw them. + */ +int64_t qmp_guest_fsfreeze_thaw(Error **err) +{ + error_set(err, QERR_UNSUPPORTED); + return 0; +} + +/* register init/cleanup routines for stateful command groups */ +void ga_command_state_init(GAState *s, GACommandState *cs) +{ +} diff --git a/qga/commands.c b/qga/commands.c new file mode 100644 index 0000000000..b27407d5d7 --- /dev/null +++ b/qga/commands.c @@ -0,0 +1,73 @@ +/* + * 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 "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(int64_t id, Error **errp) +{ + return id; +} + +void qmp_guest_ping(Error **err) +{ + slog("guest-ping called"); +} + +struct GuestAgentInfo *qmp_guest_info(Error **err) +{ + GuestAgentInfo *info = g_malloc0(sizeof(GuestAgentInfo)); + GuestAgentCommandInfo *cmd_info; + GuestAgentCommandInfoList *cmd_info_list; + char **cmd_list_head, **cmd_list; + + info->version = g_strdup(QGA_VERSION); + + cmd_list_head = cmd_list = qmp_get_command_list(); + if (*cmd_list_head == NULL) { + goto out; + } + + while (*cmd_list) { + cmd_info = g_malloc0(sizeof(GuestAgentCommandInfo)); + cmd_info->name = strdup(*cmd_list); + cmd_info->enabled = qmp_command_is_enabled(cmd_info->name); + + 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; + + g_free(*cmd_list); + cmd_list++; + } + +out: + g_free(cmd_list_head); + return info; +} diff --git a/qga/guest-agent-core.h b/qga/guest-agent-core.h index e42b91d364..b5dfa5bbda 100644 --- a/qga/guest-agent-core.h +++ b/qga/guest-agent-core.h @@ -14,7 +14,7 @@ #include "qemu-common.h" #define QGA_VERSION "1.0" -#define QGA_READ_COUNT_DEFAULT 4 << 10 +#define QGA_READ_COUNT_DEFAULT 4096 typedef struct GAState GAState; typedef struct GACommandState GACommandState; @@ -29,3 +29,4 @@ 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 slog(const gchar *fmt, ...); diff --git a/qga/service-win32.c b/qga/service-win32.c new file mode 100644 index 0000000000..09054565d3 --- /dev/null +++ b/qga/service-win32.c @@ -0,0 +1,114 @@ +/* + * 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 = printf("%s. (Error: %d) %s", text, err, message); + LocalFree(message); + + return n; +} + +int ga_install_service(const char *path, const char *logfile) +{ + SC_HANDLE manager; + SC_HANDLE service; + TCHAR cmdline[MAX_PATH]; + + if (GetModuleFileName(NULL, cmdline, MAX_PATH) == 0) { + printf_win_error("No full path to service's executable"); + return EXIT_FAILURE; + } + + _snprintf(cmdline, MAX_PATH - strlen(cmdline), "%s -d", cmdline); + + if (path) { + _snprintf(cmdline, MAX_PATH - strlen(cmdline), "%s -p %s", cmdline, path); + } + if (logfile) { + _snprintf(cmdline, MAX_PATH - strlen(cmdline), "%s -l %s -v", + cmdline, logfile); + } + + g_debug("service's cmdline: %s", cmdline); + + manager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS); + if (manager == NULL) { + printf_win_error("No handle to service control manager"); + return EXIT_FAILURE; + } + + service = CreateService(manager, QGA_SERVICE_NAME, QGA_SERVICE_DISPLAY_NAME, + SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS, SERVICE_AUTO_START, + SERVICE_ERROR_NORMAL, cmdline, NULL, NULL, NULL, NULL, NULL); + + if (service) { + SERVICE_DESCRIPTION desc = { (char *)QGA_SERVICE_DESCRIPTION }; + ChangeServiceConfig2(service, SERVICE_CONFIG_DESCRIPTION, &desc); + + printf("Service was installed successfully.\n"); + } else { + printf_win_error("Failed to install service"); + } + + CloseServiceHandle(service); + CloseServiceHandle(manager); + + return (service == NULL); +} + +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 { + printf("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 0000000000..99dfc53348 --- /dev/null +++ b/qga/service-win32.h @@ -0,0 +1,30 @@ +/* + * 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); +int ga_uninstall_service(void); + +#endif |