* [Qemu-devel] qemu-ga: add support for Windows
@ 2012-01-23 14:21 Michael Roth
2012-01-23 14:21 ` [Qemu-devel] [PATCH 1/7] qemu-ga: move channel/transport functionality into wrapper class Michael Roth
` (6 more replies)
0 siblings, 7 replies; 11+ messages in thread
From: Michael Roth @ 2012-01-23 14:21 UTC (permalink / raw)
To: qemu-devel; +Cc: ghammer, aliguori
These patches apply/work on top of:
[PATCH] qemu-ga: Add schema documentation for types
[PATCH] qemu-ga: add guest-set-support-level command
[PATCH] main-loop: Fix SetEvent() on uninitialized handle on win32
[PATCH] main-loop: For tools, initialize timers as part of qemu_init_main_loop()
And can also be obtained from:
git://github.com/mdroth/qemu.git qga-win32-v1
These patches add support for Windows to the QEMU guest agent. With these
patches the following guest agent commands are supported on Windows:
guest-ping
guest-info
guest-sync
guest-shutdown
The guest-file* commands can essentially be enabled for Windows as-is, but since
mingw does not honor the O_NONBLOCK flag, they'll need to be reworked if we're
to retain the current non-blocking behavior.
The rest of the commands are currently stubbed out for Windows (qemu-ga will
return an "unsupported" error), but it should be easy to implement these going
forward with basic Windows support/infrastructure in place.
The build was tested using Fedora 15 with a MinGW cross-build target via:
configure --enable-guest-agent --cross-prefix=i686-pc-mingw32-
make qemu-ga.exe
The executable was tested using Windows XP SP3, and partially tested using
Windows Server 2008 and Windows 7 (no I/O for the latter 2, having issues with
virtio-win drivers atm). GLib 2.28+ for Windows is required. You can install
qemu-ga as a start-on-boot service by running:
./qemu-ga --service install
And start/stop manually via:
net start qemu-ga
net stop qemu-ga
Many thanks to Gal Hammer for contributing the service integration and shutdown
code.
Makefile.objs | 8 +-
configure | 2 +-
qemu-ga.c | 413 ++++++++++++++-----------------
qga/channel-posix.c | 246 ++++++++++++++++++
qga/channel-win32.c | 337 +++++++++++++++++++++++++
qga/channel.h | 33 +++
qga/commands-posix.c | 528 ++++++++++++++++++++++++++++++++++++++
qga/commands-win32.c | 130 ++++++++++
qga/commands.c | 86 +++++++
qga/guest-agent-commands.c | 598 --------------------------------------------
qga/guest-agent-core.h | 3 +-
qga/service-win32.c | 114 +++++++++
qga/service-win32.h | 30 +++
14 files changed, 1698 insertions(+), 832 deletions(-)
^ permalink raw reply [flat|nested] 11+ messages in thread
* [Qemu-devel] [PATCH 1/7] qemu-ga: move channel/transport functionality into wrapper class
2012-01-23 14:21 [Qemu-devel] qemu-ga: add support for Windows Michael Roth
@ 2012-01-23 14:21 ` Michael Roth
2012-01-23 20:24 ` Anthony Liguori
2012-01-23 14:21 ` [Qemu-devel] [PATCH 2/7] qemu-ga: separate out common commands from posix-specific ones Michael Roth
` (5 subsequent siblings)
6 siblings, 1 reply; 11+ messages in thread
From: Michael Roth @ 2012-01-23 14:21 UTC (permalink / raw)
To: qemu-devel; +Cc: ghammer, aliguori
This is monstly in preparation for the win32 port, which won't use
GIO channels for reasons that will be made clearer later. Here the
GAChannel class is just a loose wrapper around GIOChannel
calls/callbacks, but we also roll the logic/configuration for handling
FDs and also for managing unix socket connections into it, which makes
the abstraction much more complete and further aids in the win32 port
since isa-serial/unix-listen will not be supported initially.
There's also a bit of refactoring in the main logic to consolidate the
exit paths so we can do common cleanup for things like pid files, which
weren't always cleaned up previously.
Signed-off-by: Michael Roth <mdroth@linux.vnet.ibm.com>
---
Makefile.objs | 1 +
qemu-ga.c | 306 ++++++++++++------------------------------------
qga/channel-posix.c | 246 ++++++++++++++++++++++++++++++++++++++
qga/channel.h | 33 +++++
qga/guest-agent-core.h | 2 +-
5 files changed, 355 insertions(+), 233 deletions(-)
create mode 100644 qga/channel-posix.c
create mode 100644 qga/channel.h
diff --git a/Makefile.objs b/Makefile.objs
index f94c881..72141cc 100644
--- a/Makefile.objs
+++ b/Makefile.objs
@@ -417,6 +417,7 @@ common-obj-y += qmp.o hmp.o
# guest agent
qga-nested-y = guest-agent-commands.o guest-agent-command-state.o
+qga-nested-y += channel-posix.o
qga-obj-y = $(addprefix qga/, $(qga-nested-y))
qga-obj-y += qemu-ga.o qemu-sockets.o module.o qemu-option.o
qga-obj-$(CONFIG_WIN32) += oslib-win32.o
diff --git a/qemu-ga.c b/qemu-ga.c
index 8e5b075..5f89ab9 100644
--- a/qemu-ga.c
+++ b/qemu-ga.c
@@ -15,9 +15,7 @@
#include <stdbool.h>
#include <glib.h>
#include <getopt.h>
-#include <termios.h>
#include <syslog.h>
-#include "qemu_socket.h"
#include "json-streamer.h"
#include "json-parser.h"
#include "qint.h"
@@ -29,19 +27,15 @@
#include "error_int.h"
#include "qapi/qmp-core.h"
#include "qga-qapi-types.h"
+#include "qga/channel.h"
#define QGA_VIRTIO_PATH_DEFAULT "/dev/virtio-ports/org.qemu.guest_agent.0"
#define QGA_PIDFILE_DEFAULT "/var/run/qemu-ga.pid"
-#define QGA_BAUDRATE_DEFAULT B38400 /* for isa-serial channels */
-#define QGA_TIMEOUT_DEFAULT 30*1000 /* ms */
struct GAState {
JSONMessageParser parser;
GMainLoop *main_loop;
- GIOChannel *conn_channel;
- GIOChannel *listen_channel;
- const char *path;
- const char *method;
+ GAChannel *channel;
bool virtio; /* fastpath to check for virtio to deal with poll() quirks */
GACommandState *command_state;
GLogLevelFlags log_level;
@@ -60,7 +54,7 @@ static void quit_handler(int sig)
}
}
-static void register_signal_handlers(void)
+static gboolean register_signal_handlers(void)
{
struct sigaction sigact;
int ret;
@@ -71,12 +65,14 @@ static void register_signal_handlers(void)
ret = sigaction(SIGINT, &sigact, NULL);
if (ret == -1) {
g_error("error configuring signal handler: %s", strerror(errno));
- exit(EXIT_FAILURE);
+ return false;
}
ret = sigaction(SIGTERM, &sigact, NULL);
if (ret == -1) {
g_error("error configuring signal handler: %s", strerror(errno));
+ return false;
}
+ return true;
}
static void usage(const char *cmd)
@@ -101,8 +97,6 @@ static void usage(const char *cmd)
, cmd, QGA_VERSION, QGA_VIRTIO_PATH_DEFAULT, QGA_PIDFILE_DEFAULT);
}
-static void conn_channel_close(GAState *s);
-
static GuestAgentSupportLevel ga_support_level;
static int ga_cmp_support_levels(GuestAgentSupportLevel a,
@@ -250,40 +244,13 @@ fail:
exit(EXIT_FAILURE);
}
-static int conn_channel_send_buf(GIOChannel *channel, const char *buf,
- gsize count)
+static int send_response(GAState *s, QObject *payload)
{
- GError *err = NULL;
- gsize written = 0;
- GIOStatus status;
-
- while (count) {
- status = g_io_channel_write_chars(channel, buf, count, &written, &err);
- g_debug("sending data, count: %d", (int)count);
- if (err != NULL) {
- g_warning("error sending newline: %s", err->message);
- return err->code;
- }
- if (status == G_IO_STATUS_ERROR || status == G_IO_STATUS_EOF) {
- return -EPIPE;
- }
-
- if (status == G_IO_STATUS_NORMAL) {
- count -= written;
- }
- }
-
- return 0;
-}
-
-static int conn_channel_send_payload(GIOChannel *channel, QObject *payload)
-{
- int ret = 0;
const char *buf;
QString *payload_qstr;
- GError *err = NULL;
+ GIOStatus status;
- g_assert(payload && channel);
+ g_assert(payload && s->channel);
payload_qstr = qobject_to_json(payload);
if (!payload_qstr) {
@@ -292,24 +259,13 @@ static int conn_channel_send_payload(GIOChannel *channel, QObject *payload)
qstring_append_chr(payload_qstr, '\n');
buf = qstring_get_str(payload_qstr);
- ret = conn_channel_send_buf(channel, buf, strlen(buf));
- if (ret) {
- goto out_free;
- }
-
- g_io_channel_flush(channel, &err);
- if (err != NULL) {
- g_warning("error flushing payload: %s", err->message);
- ret = err->code;
- goto out_free;
- }
-
-out_free:
+ status = ga_channel_write_all(s->channel, buf, strlen(buf));
QDECREF(payload_qstr);
- if (err) {
- g_error_free(err);
+ if (status != G_IO_STATUS_NORMAL) {
+ return -EIO;
}
- return ret;
+
+ return 0;
}
static void process_command(GAState *s, QDict *req)
@@ -321,9 +277,9 @@ static void process_command(GAState *s, QDict *req)
g_debug("processing command");
rsp = qmp_dispatch(QOBJECT(req));
if (rsp) {
- ret = conn_channel_send_payload(s->conn_channel, rsp);
+ ret = send_response(s, rsp);
if (ret) {
- g_warning("error sending payload: %s", strerror(ret));
+ g_warning("error sending response: %s", strerror(ret));
}
qobject_decref(rsp);
} else {
@@ -373,38 +329,42 @@ static void process_event(JSONMessageParser *parser, QList *tokens)
qdict_put_obj(qdict, "error", error_get_qobject(err));
error_free(err);
}
- ret = conn_channel_send_payload(s->conn_channel, QOBJECT(qdict));
+ ret = send_response(s, QOBJECT(qdict));
if (ret) {
- g_warning("error sending payload: %s", strerror(ret));
+ g_warning("error sending error response: %s", strerror(ret));
}
}
QDECREF(qdict);
}
-static gboolean conn_channel_read(GIOChannel *channel, GIOCondition condition,
- gpointer data)
+/* false return signals GAChannel to close the current client connection */
+static gboolean channel_event_cb(GIOCondition condition, gpointer data)
{
GAState *s = data;
- gchar buf[1024];
+ gchar buf[QGA_READ_COUNT_DEFAULT+1];
gsize count;
GError *err = NULL;
- memset(buf, 0, 1024);
- GIOStatus status = g_io_channel_read_chars(channel, buf, 1024,
- &count, &err);
+ GIOStatus status = ga_channel_read(s->channel, buf, QGA_READ_COUNT_DEFAULT, &count);
if (err != NULL) {
g_warning("error reading channel: %s", err->message);
- conn_channel_close(s);
g_error_free(err);
return false;
}
switch (status) {
case G_IO_STATUS_ERROR:
- g_warning("problem");
+ 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;
+ }
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
@@ -413,180 +373,49 @@ static gboolean conn_channel_read(GIOChannel *channel, GIOCondition condition,
usleep(100*1000);
}
return true;
- case G_IO_STATUS_EOF:
- g_debug("received EOF");
- conn_channel_close(s);
- if (s->virtio) {
- return true;
- }
- return false;
default:
g_warning("unknown channel read status, closing");
- conn_channel_close(s);
return false;
}
return true;
}
-static int conn_channel_add(GAState *s, int fd)
+static gboolean channel_init(GAState *s, const gchar *method, const gchar *path)
{
- GIOChannel *conn_channel;
- GError *err = NULL;
+ GAChannelMethod channel_method;
- g_assert(s && !s->conn_channel);
- conn_channel = g_io_channel_unix_new(fd);
- g_assert(conn_channel);
- g_io_channel_set_encoding(conn_channel, NULL, &err);
- if (err != NULL) {
- g_warning("error setting channel encoding to binary");
- g_error_free(err);
- return -1;
+ if (method == NULL) {
+ method = "virtio-serial";
}
- g_io_add_watch(conn_channel, G_IO_IN | G_IO_HUP,
- conn_channel_read, s);
- s->conn_channel = conn_channel;
- return 0;
-}
-static gboolean listen_channel_accept(GIOChannel *channel,
- GIOCondition condition, gpointer data)
-{
- GAState *s = data;
- g_assert(channel != NULL);
- int ret, conn_fd;
- bool accepted = false;
- struct sockaddr_un addr;
- socklen_t addrlen = sizeof(addr);
-
- conn_fd = qemu_accept(g_io_channel_unix_get_fd(s->listen_channel),
- (struct sockaddr *)&addr, &addrlen);
- if (conn_fd == -1) {
- g_warning("error converting fd to gsocket: %s", strerror(errno));
- goto out;
- }
- fcntl(conn_fd, F_SETFL, O_NONBLOCK);
- ret = conn_channel_add(s, conn_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 int listen_channel_add(GAState *s, int listen_fd, bool new)
-{
- if (new) {
- s->listen_channel = g_io_channel_unix_new(listen_fd);
- }
- g_io_add_watch(s->listen_channel, G_IO_IN,
- listen_channel_accept, s);
- return 0;
-}
-
-/* cleanup state for closed connection/session, start accepting new
- * connections if we're in listening mode
- */
-static void conn_channel_close(GAState *s)
-{
- if (strcmp(s->method, "unix-listen") == 0) {
- g_io_channel_shutdown(s->conn_channel, true, NULL);
- listen_channel_add(s, 0, false);
- } else if (strcmp(s->method, "virtio-serial") == 0) {
- /* we spin on EOF for virtio-serial, so back off a bit. also,
- * dont close the connection in this case, it'll resume normal
- * operation when another process connects to host chardev
- */
- usleep(100*1000);
- goto out_noclose;
- }
- g_io_channel_unref(s->conn_channel);
- s->conn_channel = NULL;
-out_noclose:
- return;
-}
-
-static void init_guest_agent(GAState *s)
-{
- struct termios tio;
- int ret, fd;
-
- if (s->method == NULL) {
- /* try virtio-serial as our default */
- s->method = "virtio-serial";
- }
-
- if (s->path == NULL) {
- if (strcmp(s->method, "virtio-serial") != 0) {
+ if (path == NULL) {
+ if (strcmp(method, "virtio-serial") != 0) {
g_critical("must specify a path for this channel");
- exit(EXIT_FAILURE);
+ return false;
}
/* try the default path for the virtio-serial port */
- s->path = QGA_VIRTIO_PATH_DEFAULT;
+ path = QGA_VIRTIO_PATH_DEFAULT;
}
- if (strcmp(s->method, "virtio-serial") == 0) {
- s->virtio = true;
- fd = qemu_open(s->path, O_RDWR | O_NONBLOCK | O_ASYNC);
- if (fd == -1) {
- g_critical("error opening channel: %s", strerror(errno));
- exit(EXIT_FAILURE);
- }
- ret = conn_channel_add(s, fd);
- if (ret) {
- g_critical("error adding channel to main loop");
- exit(EXIT_FAILURE);
- }
- } else if (strcmp(s->method, "isa-serial") == 0) {
- fd = qemu_open(s->path, O_RDWR | O_NOCTTY);
- 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 |= QGA_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 = conn_channel_add(s, fd);
- if (ret) {
- g_error("error adding channel to main loop");
- }
- } else if (strcmp(s->method, "unix-listen") == 0) {
- fd = unix_listen(s->path, NULL, strlen(s->path));
- if (fd == -1) {
- g_critical("error opening path: %s", strerror(errno));
- exit(EXIT_FAILURE);
- }
- ret = listen_channel_add(s, fd, true);
- if (ret) {
- g_critical("error binding/listening to specified socket");
- exit(EXIT_FAILURE);
- }
+ 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", s->method);
- exit(EXIT_FAILURE);
+ g_critical("unsupported channel method/type: %s", method);
+ return false;
}
- json_message_parser_init(&s->parser, process_event);
- s->main_loop = g_main_loop_new(NULL, 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;
}
int main(int argc, char **argv)
@@ -689,9 +518,6 @@ int main(int argc, char **argv)
ga_set_support_level(default_support_level);
s = g_malloc0(sizeof(GAState));
- s->conn_channel = NULL;
- s->path = path;
- s->method = method;
s->log_file = log_file;
s->log_level = log_level;
g_log_set_default_handler(ga_log, s);
@@ -700,15 +526,31 @@ int main(int argc, char **argv)
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;
+ if (!register_signal_handlers()) {
+ g_critical("failed to register signal handlers");
+ goto out_bad;
+ }
- init_guest_agent(ga_state);
- register_signal_handlers();
-
+ 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;
+ }
g_main_loop_run(ga_state->main_loop);
ga_command_state_cleanup_all(ga_state->command_state);
- unlink(pidfile);
+ ga_channel_free(ga_state->channel);
+ if (daemonize) {
+ unlink(pidfile);
+ }
return 0;
+
+out_bad:
+ if (daemonize) {
+ unlink(pidfile);
+ }
+ return EXIT_FAILURE;
}
diff --git a/qga/channel-posix.c b/qga/channel-posix.c
new file mode 100644
index 0000000..40f7658
--- /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.h b/qga/channel.h
new file mode 100644
index 0000000..3704ea9
--- /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-core.h b/qga/guest-agent-core.h
index 7327e73..891ed06 100644
--- a/qga/guest-agent-core.h
+++ b/qga/guest-agent-core.h
@@ -22,7 +22,7 @@
#define QGA_SUPPORT_LEVEL_MAJOR_MIN 1
#define QGA_SUPPORT_LEVEL_MINOR_MIN 0
#define QGA_SUPPORT_LEVEL_MICRO_MIN 0
-#define QGA_READ_COUNT_DEFAULT 4 << 10
+#define QGA_READ_COUNT_DEFAULT 4096
typedef struct GAState GAState;
typedef struct GACommandState GACommandState;
--
1.7.4.1
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [Qemu-devel] [PATCH 2/7] qemu-ga: separate out common commands from posix-specific ones
2012-01-23 14:21 [Qemu-devel] qemu-ga: add support for Windows Michael Roth
2012-01-23 14:21 ` [Qemu-devel] [PATCH 1/7] qemu-ga: move channel/transport functionality into wrapper class Michael Roth
@ 2012-01-23 14:21 ` Michael Roth
2012-01-23 14:21 ` [Qemu-devel] [PATCH 3/7] qemu-ga: rename guest-agent-commands.c -> commands-posix.c Michael Roth
` (4 subsequent siblings)
6 siblings, 0 replies; 11+ messages in thread
From: Michael Roth @ 2012-01-23 14:21 UTC (permalink / raw)
To: qemu-devel; +Cc: ghammer, aliguori
Many of the current RPC implementations are very much POSIX-specific
and require complete re-writes for Windows. There are however a small
set of core guest agent commands that are common to both, and other
commands such as guest-file-* which *may* be portable. So we introduce
commands.c for the latter, and will rename guest-agent-commands.c to
commands-posix.c in a future commit. Windows implementations will go in
commands-win32.c, eventually.
Signed-off-by: Michael Roth <mdroth@linux.vnet.ibm.com>
---
Makefile.objs | 2 +-
qga/commands.c | 86 ++++++++++++++++++++++++++++++++++++++++++++
qga/guest-agent-commands.c | 72 +------------------------------------
qga/guest-agent-core.h | 1 +
4 files changed, 89 insertions(+), 72 deletions(-)
create mode 100644 qga/commands.c
diff --git a/Makefile.objs b/Makefile.objs
index 72141cc..685ef31 100644
--- a/Makefile.objs
+++ b/Makefile.objs
@@ -416,7 +416,7 @@ common-obj-y += qmp.o hmp.o
######################################################################
# guest agent
-qga-nested-y = guest-agent-commands.o guest-agent-command-state.o
+qga-nested-y = commands.o guest-agent-commands.o guest-agent-command-state.o
qga-nested-y += channel-posix.o
qga-obj-y = $(addprefix qga/, $(qga-nested-y))
qga-obj-y += qemu-ga.o qemu-sockets.o module.o qemu-option.o
diff --git a/qga/commands.c b/qga/commands.c
new file mode 100644
index 0000000..a12530e
--- /dev/null
+++ b/qga/commands.c
@@ -0,0 +1,86 @@
+/*
+ * 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);
+ info->support_level = g_malloc0(sizeof(GuestAgentSupportLevel));
+ *info->support_level = ga_get_support_level();
+
+ 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_set_support_level(int64_t major, int64_t minor, bool has_micro,
+ int64_t micro, Error **errp)
+{
+ GuestAgentSupportLevel level = {
+ major,
+ minor,
+ has_micro ? micro : 0
+ };
+ ga_set_support_level(level);
+}
diff --git a/qga/guest-agent-commands.c b/qga/guest-agent-commands.c
index 656dde8..126127a 100644
--- a/qga/guest-agent-commands.c
+++ b/qga/guest-agent-commands.c
@@ -1,5 +1,5 @@
/*
- * QEMU Guest Agent commands
+ * QEMU Guest Agent POSIX-specific command implementations
*
* Copyright IBM Corp. 2011
*
@@ -30,76 +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);
- info->support_level = g_malloc0(sizeof(GuestAgentSupportLevel));
- *info->support_level = ga_get_support_level();
-
- 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_set_support_level(int64_t major, int64_t minor, bool has_micro,
- int64_t micro, Error **errp)
-{
- GuestAgentSupportLevel level = {
- major,
- minor,
- has_micro ? micro : 0
- };
- ga_set_support_level(level);
-}
-
void qmp_guest_shutdown(bool has_mode, const char *mode, Error **err)
{
int ret;
diff --git a/qga/guest-agent-core.h b/qga/guest-agent-core.h
index 891ed06..992c8c6 100644
--- a/qga/guest-agent-core.h
+++ b/qga/guest-agent-core.h
@@ -40,3 +40,4 @@ void ga_enable_logging(GAState *s);
bool ga_has_support_level(int major, int minor, int micro);
void ga_set_support_level(GuestAgentSupportLevel level);
GuestAgentSupportLevel ga_get_support_level(void);
+void slog(const gchar *fmt, ...);
--
1.7.4.1
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [Qemu-devel] [PATCH 3/7] qemu-ga: rename guest-agent-commands.c -> commands-posix.c
2012-01-23 14:21 [Qemu-devel] qemu-ga: add support for Windows Michael Roth
2012-01-23 14:21 ` [Qemu-devel] [PATCH 1/7] qemu-ga: move channel/transport functionality into wrapper class Michael Roth
2012-01-23 14:21 ` [Qemu-devel] [PATCH 2/7] qemu-ga: separate out common commands from posix-specific ones Michael Roth
@ 2012-01-23 14:21 ` Michael Roth
2012-01-23 14:21 ` [Qemu-devel] [PATCH 4/7] qemu-ga: fixes for win32 build of qemu-ga Michael Roth
` (3 subsequent siblings)
6 siblings, 0 replies; 11+ messages in thread
From: Michael Roth @ 2012-01-23 14:21 UTC (permalink / raw)
To: qemu-devel; +Cc: ghammer, aliguori
Signed-off-by: Michael Roth <mdroth@linux.vnet.ibm.com>
---
Makefile.objs | 2 +-
qga/commands-posix.c | 528 ++++++++++++++++++++++++++++++++++++++++++++
qga/guest-agent-commands.c | 528 --------------------------------------------
3 files changed, 529 insertions(+), 529 deletions(-)
create mode 100644 qga/commands-posix.c
delete mode 100644 qga/guest-agent-commands.c
diff --git a/Makefile.objs b/Makefile.objs
index 685ef31..5bc883a 100644
--- a/Makefile.objs
+++ b/Makefile.objs
@@ -416,7 +416,7 @@ common-obj-y += qmp.o hmp.o
######################################################################
# guest agent
-qga-nested-y = commands.o guest-agent-commands.o guest-agent-command-state.o
+qga-nested-y = commands.o commands-posix.o guest-agent-command-state.o
qga-nested-y += channel-posix.o
qga-obj-y = $(addprefix qga/, $(qga-nested-y))
qga-obj-y += qemu-ga.o qemu-sockets.o module.o qemu-option.o
diff --git a/qga/commands-posix.c b/qga/commands-posix.c
new file mode 100644
index 0000000..126127a
--- /dev/null
+++ b/qga/commands-posix.c
@@ -0,0 +1,528 @@
+/*
+ * QEMU Guest Agent POSIX-specific command implementations
+ *
+ * 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>
+
+#if defined(__linux__)
+#include <mntent.h>
+#include <linux/fs.h>
+
+#if defined(__linux__) && defined(FIFREEZE)
+#define CONFIG_FSFREEZE
+#endif
+#endif
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include "qga/guest-agent-core.h"
+#include "qga-qmp-commands.h"
+#include "qerror.h"
+#include "qemu-queue.h"
+
+static GAState *ga_state;
+
+void qmp_guest_shutdown(bool has_mode, const char *mode, Error **err)
+{
+ int ret;
+ const char *shutdown_flag;
+
+ 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_set(err, QERR_INVALID_PARAMETER_VALUE, "mode",
+ "halt|powerdown|reboot");
+ return;
+ }
+
+ ret = fork();
+ if (ret == 0) {
+ /* child, start the shutdown */
+ setsid();
+ fclose(stdin);
+ fclose(stdout);
+ fclose(stderr);
+
+ ret = execl("/sbin/shutdown", "shutdown", shutdown_flag, "+0",
+ "hypervisor initiated shutdown", (char*)NULL);
+ if (ret) {
+ slog("guest-shutdown failed: %s", strerror(errno));
+ }
+ exit(!!ret);
+ } else if (ret < 0) {
+ error_set(err, QERR_UNDEFINED_ERROR);
+ }
+}
+
+typedef struct GuestFileHandle {
+ uint64_t id;
+ FILE *fh;
+ QTAILQ_ENTRY(GuestFileHandle) next;
+} GuestFileHandle;
+
+static struct {
+ QTAILQ_HEAD(, GuestFileHandle) filehandles;
+} guest_file_state;
+
+static void guest_file_handle_add(FILE *fh)
+{
+ GuestFileHandle *gfh;
+
+ gfh = g_malloc0(sizeof(GuestFileHandle));
+ gfh->id = fileno(fh);
+ gfh->fh = fh;
+ QTAILQ_INSERT_TAIL(&guest_file_state.filehandles, gfh, next);
+}
+
+static GuestFileHandle *guest_file_handle_find(int64_t id)
+{
+ GuestFileHandle *gfh;
+
+ QTAILQ_FOREACH(gfh, &guest_file_state.filehandles, next)
+ {
+ if (gfh->id == id) {
+ return gfh;
+ }
+ }
+
+ return NULL;
+}
+
+int64_t qmp_guest_file_open(const char *path, bool has_mode, const char *mode, Error **err)
+{
+ FILE *fh;
+ int fd;
+ int64_t ret = -1;
+
+ if (!has_mode) {
+ mode = "r";
+ }
+ slog("guest-file-open called, filepath: %s, mode: %s", path, mode);
+ fh = fopen(path, mode);
+ if (!fh) {
+ error_set(err, QERR_OPEN_FILE_FAILED, path);
+ return -1;
+ }
+
+ /* set fd non-blocking to avoid common use cases (like reading from a
+ * named pipe) from hanging the agent
+ */
+ fd = fileno(fh);
+ ret = fcntl(fd, F_GETFL);
+ ret = fcntl(fd, F_SETFL, ret | O_NONBLOCK);
+ if (ret == -1) {
+ error_set(err, QERR_QGA_COMMAND_FAILED, "fcntl() failed");
+ fclose(fh);
+ return -1;
+ }
+
+ guest_file_handle_add(fh);
+ slog("guest-file-open, handle: %d", fd);
+ return fd;
+}
+
+void qmp_guest_file_close(int64_t handle, Error **err)
+{
+ GuestFileHandle *gfh = guest_file_handle_find(handle);
+ int ret;
+
+ slog("guest-file-close called, handle: %ld", handle);
+ if (!gfh) {
+ error_set(err, QERR_FD_NOT_FOUND, "handle");
+ return;
+ }
+
+ ret = fclose(gfh->fh);
+ if (ret == -1) {
+ error_set(err, QERR_QGA_COMMAND_FAILED, "fclose() failed");
+ 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 **err)
+{
+ GuestFileHandle *gfh = guest_file_handle_find(handle);
+ GuestFileRead *read_data = NULL;
+ guchar *buf;
+ FILE *fh;
+ size_t read_count;
+
+ if (!gfh) {
+ error_set(err, QERR_FD_NOT_FOUND, "handle");
+ return NULL;
+ }
+
+ if (!has_count) {
+ count = QGA_READ_COUNT_DEFAULT;
+ } else if (count < 0) {
+ error_set(err, QERR_INVALID_PARAMETER, "count");
+ return NULL;
+ }
+
+ fh = gfh->fh;
+ buf = g_malloc0(count+1);
+ read_count = fread(buf, 1, count, fh);
+ if (ferror(fh)) {
+ slog("guest-file-read failed, handle: %ld", handle);
+ error_set(err, QERR_QGA_COMMAND_FAILED, "fread() failed");
+ } 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 **err)
+{
+ GuestFileWrite *write_data = NULL;
+ guchar *buf;
+ gsize buf_len;
+ int write_count;
+ GuestFileHandle *gfh = guest_file_handle_find(handle);
+ FILE *fh;
+
+ if (!gfh) {
+ error_set(err, QERR_FD_NOT_FOUND, "handle");
+ 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) {
+ g_free(buf);
+ error_set(err, QERR_INVALID_PARAMETER, "count");
+ return NULL;
+ }
+
+ write_count = fwrite(buf, 1, count, fh);
+ if (ferror(fh)) {
+ slog("guest-file-write failed, handle: %ld", handle);
+ error_set(err, QERR_QGA_COMMAND_FAILED, "fwrite() error");
+ } 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 **err)
+{
+ GuestFileHandle *gfh = guest_file_handle_find(handle);
+ GuestFileSeek *seek_data = NULL;
+ FILE *fh;
+ int ret;
+
+ if (!gfh) {
+ error_set(err, QERR_FD_NOT_FOUND, "handle");
+ return NULL;
+ }
+
+ fh = gfh->fh;
+ ret = fseek(fh, offset, whence);
+ if (ret == -1) {
+ error_set(err, QERR_QGA_COMMAND_FAILED, strerror(errno));
+ } else {
+ seek_data = g_malloc0(sizeof(GuestFileRead));
+ seek_data->position = ftell(fh);
+ seek_data->eof = feof(fh);
+ }
+ clearerr(fh);
+
+ return seek_data;
+}
+
+void qmp_guest_file_flush(int64_t handle, Error **err)
+{
+ GuestFileHandle *gfh = guest_file_handle_find(handle);
+ FILE *fh;
+ int ret;
+
+ if (!gfh) {
+ error_set(err, QERR_FD_NOT_FOUND, "handle");
+ return;
+ }
+
+ fh = gfh->fh;
+ ret = fflush(fh);
+ if (ret == EOF) {
+ error_set(err, QERR_QGA_COMMAND_FAILED, strerror(errno));
+ }
+}
+
+static void guest_file_init(void)
+{
+ QTAILQ_INIT(&guest_file_state.filehandles);
+}
+
+#if defined(CONFIG_FSFREEZE)
+static void disable_logging(void)
+{
+ ga_disable_logging(ga_state);
+}
+
+static void enable_logging(void)
+{
+ ga_enable_logging(ga_state);
+}
+
+typedef struct GuestFsfreezeMount {
+ char *dirname;
+ char *devtype;
+ QTAILQ_ENTRY(GuestFsfreezeMount) next;
+} GuestFsfreezeMount;
+
+struct {
+ GuestFsfreezeStatus status;
+ QTAILQ_HEAD(, GuestFsfreezeMount) mount_list;
+} guest_fsfreeze_state;
+
+/*
+ * Walk the mount table and build a list of local file systems
+ */
+static int guest_fsfreeze_build_mount_list(void)
+{
+ struct mntent *ment;
+ GuestFsfreezeMount *mount, *temp;
+ char const *mtab = MOUNTED;
+ FILE *fp;
+
+ QTAILQ_FOREACH_SAFE(mount, &guest_fsfreeze_state.mount_list, next, temp) {
+ QTAILQ_REMOVE(&guest_fsfreeze_state.mount_list, mount, next);
+ g_free(mount->dirname);
+ g_free(mount->devtype);
+ g_free(mount);
+ }
+
+ fp = setmntent(mtab, "r");
+ if (!fp) {
+ g_warning("fsfreeze: unable to read mtab");
+ return -1;
+ }
+
+ 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;
+ }
+
+ mount = g_malloc0(sizeof(GuestFsfreezeMount));
+ mount->dirname = g_strdup(ment->mnt_dir);
+ mount->devtype = g_strdup(ment->mnt_type);
+
+ QTAILQ_INSERT_TAIL(&guest_fsfreeze_state.mount_list, mount, next);
+ }
+
+ endmntent(fp);
+
+ return 0;
+}
+
+/*
+ * Return status of freeze/thaw
+ */
+GuestFsfreezeStatus qmp_guest_fsfreeze_status(Error **err)
+{
+ return guest_fsfreeze_state.status;
+}
+
+/*
+ * 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)
+{
+ int ret = 0, i = 0;
+ struct GuestFsfreezeMount *mount, *temp;
+ int fd;
+ char err_msg[512];
+
+ slog("guest-fsfreeze called");
+
+ if (guest_fsfreeze_state.status == GUEST_FSFREEZE_STATUS_FROZEN) {
+ return 0;
+ }
+
+ ret = guest_fsfreeze_build_mount_list();
+ if (ret < 0) {
+ return ret;
+ }
+
+ /* cannot risk guest agent blocking itself on a write in this state */
+ disable_logging();
+
+ QTAILQ_FOREACH_SAFE(mount, &guest_fsfreeze_state.mount_list, next, temp) {
+ fd = qemu_open(mount->dirname, O_RDONLY);
+ if (fd == -1) {
+ sprintf(err_msg, "failed to open %s, %s", mount->dirname, strerror(errno));
+ error_set(err, QERR_QGA_COMMAND_FAILED, err_msg);
+ 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, so we simply ignore them. when
+ * thawing, these filesystems will return an EINVAL instead, due to
+ * not being in a frozen state. Other filesystem-specific
+ * errors may result in EINVAL, however, so the user should check the
+ * number * of filesystems returned here against those returned by the
+ * thaw operation to determine whether everything completed
+ * successfully
+ */
+ ret = ioctl(fd, FIFREEZE);
+ if (ret < 0 && errno != EOPNOTSUPP) {
+ sprintf(err_msg, "failed to freeze %s, %s", mount->dirname, strerror(errno));
+ error_set(err, QERR_QGA_COMMAND_FAILED, err_msg);
+ close(fd);
+ goto error;
+ }
+ close(fd);
+
+ i++;
+ }
+
+ guest_fsfreeze_state.status = GUEST_FSFREEZE_STATUS_FROZEN;
+ return i;
+
+error:
+ if (i > 0) {
+ 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 **err)
+{
+ int ret;
+ GuestFsfreezeMount *mount, *temp;
+ int fd, i = 0;
+ bool has_error = false;
+
+ QTAILQ_FOREACH_SAFE(mount, &guest_fsfreeze_state.mount_list, next, temp) {
+ fd = qemu_open(mount->dirname, O_RDONLY);
+ if (fd == -1) {
+ has_error = true;
+ continue;
+ }
+ ret = ioctl(fd, FITHAW);
+ if (ret < 0 && errno != EOPNOTSUPP && errno != EINVAL) {
+ has_error = true;
+ close(fd);
+ continue;
+ }
+ close(fd);
+ i++;
+ }
+
+ if (has_error) {
+ guest_fsfreeze_state.status = GUEST_FSFREEZE_STATUS_ERROR;
+ } else {
+ guest_fsfreeze_state.status = GUEST_FSFREEZE_STATUS_THAWED;
+ }
+ enable_logging();
+ return i;
+}
+
+static void guest_fsfreeze_init(void)
+{
+ guest_fsfreeze_state.status = GUEST_FSFREEZE_STATUS_THAWED;
+ QTAILQ_INIT(&guest_fsfreeze_state.mount_list);
+}
+
+static void guest_fsfreeze_cleanup(void)
+{
+ int64_t ret;
+ Error *err = NULL;
+
+ if (guest_fsfreeze_state.status == GUEST_FSFREEZE_STATUS_FROZEN) {
+ ret = qmp_guest_fsfreeze_thaw(&err);
+ if (ret < 0 || err) {
+ slog("failed to clean up frozen filesystems");
+ }
+ }
+}
+#else
+/*
+ * 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;
+}
+#endif
+
+/* register init/cleanup routines for stateful command groups */
+void ga_command_state_init(GAState *s, GACommandState *cs)
+{
+ ga_state = s;
+#if defined(CONFIG_FSFREEZE)
+ ga_command_state_add(cs, guest_fsfreeze_init, guest_fsfreeze_cleanup);
+#endif
+ ga_command_state_add(cs, guest_file_init, NULL);
+}
diff --git a/qga/guest-agent-commands.c b/qga/guest-agent-commands.c
deleted file mode 100644
index 126127a..0000000
--- a/qga/guest-agent-commands.c
+++ /dev/null
@@ -1,528 +0,0 @@
-/*
- * QEMU Guest Agent POSIX-specific command implementations
- *
- * 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>
-
-#if defined(__linux__)
-#include <mntent.h>
-#include <linux/fs.h>
-
-#if defined(__linux__) && defined(FIFREEZE)
-#define CONFIG_FSFREEZE
-#endif
-#endif
-
-#include <sys/types.h>
-#include <sys/ioctl.h>
-#include "qga/guest-agent-core.h"
-#include "qga-qmp-commands.h"
-#include "qerror.h"
-#include "qemu-queue.h"
-
-static GAState *ga_state;
-
-void qmp_guest_shutdown(bool has_mode, const char *mode, Error **err)
-{
- int ret;
- const char *shutdown_flag;
-
- 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_set(err, QERR_INVALID_PARAMETER_VALUE, "mode",
- "halt|powerdown|reboot");
- return;
- }
-
- ret = fork();
- if (ret == 0) {
- /* child, start the shutdown */
- setsid();
- fclose(stdin);
- fclose(stdout);
- fclose(stderr);
-
- ret = execl("/sbin/shutdown", "shutdown", shutdown_flag, "+0",
- "hypervisor initiated shutdown", (char*)NULL);
- if (ret) {
- slog("guest-shutdown failed: %s", strerror(errno));
- }
- exit(!!ret);
- } else if (ret < 0) {
- error_set(err, QERR_UNDEFINED_ERROR);
- }
-}
-
-typedef struct GuestFileHandle {
- uint64_t id;
- FILE *fh;
- QTAILQ_ENTRY(GuestFileHandle) next;
-} GuestFileHandle;
-
-static struct {
- QTAILQ_HEAD(, GuestFileHandle) filehandles;
-} guest_file_state;
-
-static void guest_file_handle_add(FILE *fh)
-{
- GuestFileHandle *gfh;
-
- gfh = g_malloc0(sizeof(GuestFileHandle));
- gfh->id = fileno(fh);
- gfh->fh = fh;
- QTAILQ_INSERT_TAIL(&guest_file_state.filehandles, gfh, next);
-}
-
-static GuestFileHandle *guest_file_handle_find(int64_t id)
-{
- GuestFileHandle *gfh;
-
- QTAILQ_FOREACH(gfh, &guest_file_state.filehandles, next)
- {
- if (gfh->id == id) {
- return gfh;
- }
- }
-
- return NULL;
-}
-
-int64_t qmp_guest_file_open(const char *path, bool has_mode, const char *mode, Error **err)
-{
- FILE *fh;
- int fd;
- int64_t ret = -1;
-
- if (!has_mode) {
- mode = "r";
- }
- slog("guest-file-open called, filepath: %s, mode: %s", path, mode);
- fh = fopen(path, mode);
- if (!fh) {
- error_set(err, QERR_OPEN_FILE_FAILED, path);
- return -1;
- }
-
- /* set fd non-blocking to avoid common use cases (like reading from a
- * named pipe) from hanging the agent
- */
- fd = fileno(fh);
- ret = fcntl(fd, F_GETFL);
- ret = fcntl(fd, F_SETFL, ret | O_NONBLOCK);
- if (ret == -1) {
- error_set(err, QERR_QGA_COMMAND_FAILED, "fcntl() failed");
- fclose(fh);
- return -1;
- }
-
- guest_file_handle_add(fh);
- slog("guest-file-open, handle: %d", fd);
- return fd;
-}
-
-void qmp_guest_file_close(int64_t handle, Error **err)
-{
- GuestFileHandle *gfh = guest_file_handle_find(handle);
- int ret;
-
- slog("guest-file-close called, handle: %ld", handle);
- if (!gfh) {
- error_set(err, QERR_FD_NOT_FOUND, "handle");
- return;
- }
-
- ret = fclose(gfh->fh);
- if (ret == -1) {
- error_set(err, QERR_QGA_COMMAND_FAILED, "fclose() failed");
- 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 **err)
-{
- GuestFileHandle *gfh = guest_file_handle_find(handle);
- GuestFileRead *read_data = NULL;
- guchar *buf;
- FILE *fh;
- size_t read_count;
-
- if (!gfh) {
- error_set(err, QERR_FD_NOT_FOUND, "handle");
- return NULL;
- }
-
- if (!has_count) {
- count = QGA_READ_COUNT_DEFAULT;
- } else if (count < 0) {
- error_set(err, QERR_INVALID_PARAMETER, "count");
- return NULL;
- }
-
- fh = gfh->fh;
- buf = g_malloc0(count+1);
- read_count = fread(buf, 1, count, fh);
- if (ferror(fh)) {
- slog("guest-file-read failed, handle: %ld", handle);
- error_set(err, QERR_QGA_COMMAND_FAILED, "fread() failed");
- } 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 **err)
-{
- GuestFileWrite *write_data = NULL;
- guchar *buf;
- gsize buf_len;
- int write_count;
- GuestFileHandle *gfh = guest_file_handle_find(handle);
- FILE *fh;
-
- if (!gfh) {
- error_set(err, QERR_FD_NOT_FOUND, "handle");
- 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) {
- g_free(buf);
- error_set(err, QERR_INVALID_PARAMETER, "count");
- return NULL;
- }
-
- write_count = fwrite(buf, 1, count, fh);
- if (ferror(fh)) {
- slog("guest-file-write failed, handle: %ld", handle);
- error_set(err, QERR_QGA_COMMAND_FAILED, "fwrite() error");
- } 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 **err)
-{
- GuestFileHandle *gfh = guest_file_handle_find(handle);
- GuestFileSeek *seek_data = NULL;
- FILE *fh;
- int ret;
-
- if (!gfh) {
- error_set(err, QERR_FD_NOT_FOUND, "handle");
- return NULL;
- }
-
- fh = gfh->fh;
- ret = fseek(fh, offset, whence);
- if (ret == -1) {
- error_set(err, QERR_QGA_COMMAND_FAILED, strerror(errno));
- } else {
- seek_data = g_malloc0(sizeof(GuestFileRead));
- seek_data->position = ftell(fh);
- seek_data->eof = feof(fh);
- }
- clearerr(fh);
-
- return seek_data;
-}
-
-void qmp_guest_file_flush(int64_t handle, Error **err)
-{
- GuestFileHandle *gfh = guest_file_handle_find(handle);
- FILE *fh;
- int ret;
-
- if (!gfh) {
- error_set(err, QERR_FD_NOT_FOUND, "handle");
- return;
- }
-
- fh = gfh->fh;
- ret = fflush(fh);
- if (ret == EOF) {
- error_set(err, QERR_QGA_COMMAND_FAILED, strerror(errno));
- }
-}
-
-static void guest_file_init(void)
-{
- QTAILQ_INIT(&guest_file_state.filehandles);
-}
-
-#if defined(CONFIG_FSFREEZE)
-static void disable_logging(void)
-{
- ga_disable_logging(ga_state);
-}
-
-static void enable_logging(void)
-{
- ga_enable_logging(ga_state);
-}
-
-typedef struct GuestFsfreezeMount {
- char *dirname;
- char *devtype;
- QTAILQ_ENTRY(GuestFsfreezeMount) next;
-} GuestFsfreezeMount;
-
-struct {
- GuestFsfreezeStatus status;
- QTAILQ_HEAD(, GuestFsfreezeMount) mount_list;
-} guest_fsfreeze_state;
-
-/*
- * Walk the mount table and build a list of local file systems
- */
-static int guest_fsfreeze_build_mount_list(void)
-{
- struct mntent *ment;
- GuestFsfreezeMount *mount, *temp;
- char const *mtab = MOUNTED;
- FILE *fp;
-
- QTAILQ_FOREACH_SAFE(mount, &guest_fsfreeze_state.mount_list, next, temp) {
- QTAILQ_REMOVE(&guest_fsfreeze_state.mount_list, mount, next);
- g_free(mount->dirname);
- g_free(mount->devtype);
- g_free(mount);
- }
-
- fp = setmntent(mtab, "r");
- if (!fp) {
- g_warning("fsfreeze: unable to read mtab");
- return -1;
- }
-
- 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;
- }
-
- mount = g_malloc0(sizeof(GuestFsfreezeMount));
- mount->dirname = g_strdup(ment->mnt_dir);
- mount->devtype = g_strdup(ment->mnt_type);
-
- QTAILQ_INSERT_TAIL(&guest_fsfreeze_state.mount_list, mount, next);
- }
-
- endmntent(fp);
-
- return 0;
-}
-
-/*
- * Return status of freeze/thaw
- */
-GuestFsfreezeStatus qmp_guest_fsfreeze_status(Error **err)
-{
- return guest_fsfreeze_state.status;
-}
-
-/*
- * 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)
-{
- int ret = 0, i = 0;
- struct GuestFsfreezeMount *mount, *temp;
- int fd;
- char err_msg[512];
-
- slog("guest-fsfreeze called");
-
- if (guest_fsfreeze_state.status == GUEST_FSFREEZE_STATUS_FROZEN) {
- return 0;
- }
-
- ret = guest_fsfreeze_build_mount_list();
- if (ret < 0) {
- return ret;
- }
-
- /* cannot risk guest agent blocking itself on a write in this state */
- disable_logging();
-
- QTAILQ_FOREACH_SAFE(mount, &guest_fsfreeze_state.mount_list, next, temp) {
- fd = qemu_open(mount->dirname, O_RDONLY);
- if (fd == -1) {
- sprintf(err_msg, "failed to open %s, %s", mount->dirname, strerror(errno));
- error_set(err, QERR_QGA_COMMAND_FAILED, err_msg);
- 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, so we simply ignore them. when
- * thawing, these filesystems will return an EINVAL instead, due to
- * not being in a frozen state. Other filesystem-specific
- * errors may result in EINVAL, however, so the user should check the
- * number * of filesystems returned here against those returned by the
- * thaw operation to determine whether everything completed
- * successfully
- */
- ret = ioctl(fd, FIFREEZE);
- if (ret < 0 && errno != EOPNOTSUPP) {
- sprintf(err_msg, "failed to freeze %s, %s", mount->dirname, strerror(errno));
- error_set(err, QERR_QGA_COMMAND_FAILED, err_msg);
- close(fd);
- goto error;
- }
- close(fd);
-
- i++;
- }
-
- guest_fsfreeze_state.status = GUEST_FSFREEZE_STATUS_FROZEN;
- return i;
-
-error:
- if (i > 0) {
- 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 **err)
-{
- int ret;
- GuestFsfreezeMount *mount, *temp;
- int fd, i = 0;
- bool has_error = false;
-
- QTAILQ_FOREACH_SAFE(mount, &guest_fsfreeze_state.mount_list, next, temp) {
- fd = qemu_open(mount->dirname, O_RDONLY);
- if (fd == -1) {
- has_error = true;
- continue;
- }
- ret = ioctl(fd, FITHAW);
- if (ret < 0 && errno != EOPNOTSUPP && errno != EINVAL) {
- has_error = true;
- close(fd);
- continue;
- }
- close(fd);
- i++;
- }
-
- if (has_error) {
- guest_fsfreeze_state.status = GUEST_FSFREEZE_STATUS_ERROR;
- } else {
- guest_fsfreeze_state.status = GUEST_FSFREEZE_STATUS_THAWED;
- }
- enable_logging();
- return i;
-}
-
-static void guest_fsfreeze_init(void)
-{
- guest_fsfreeze_state.status = GUEST_FSFREEZE_STATUS_THAWED;
- QTAILQ_INIT(&guest_fsfreeze_state.mount_list);
-}
-
-static void guest_fsfreeze_cleanup(void)
-{
- int64_t ret;
- Error *err = NULL;
-
- if (guest_fsfreeze_state.status == GUEST_FSFREEZE_STATUS_FROZEN) {
- ret = qmp_guest_fsfreeze_thaw(&err);
- if (ret < 0 || err) {
- slog("failed to clean up frozen filesystems");
- }
- }
-}
-#else
-/*
- * 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;
-}
-#endif
-
-/* register init/cleanup routines for stateful command groups */
-void ga_command_state_init(GAState *s, GACommandState *cs)
-{
- ga_state = s;
-#if defined(CONFIG_FSFREEZE)
- ga_command_state_add(cs, guest_fsfreeze_init, guest_fsfreeze_cleanup);
-#endif
- ga_command_state_add(cs, guest_file_init, NULL);
-}
--
1.7.4.1
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [Qemu-devel] [PATCH 4/7] qemu-ga: fixes for win32 build of qemu-ga
2012-01-23 14:21 [Qemu-devel] qemu-ga: add support for Windows Michael Roth
` (2 preceding siblings ...)
2012-01-23 14:21 ` [Qemu-devel] [PATCH 3/7] qemu-ga: rename guest-agent-commands.c -> commands-posix.c Michael Roth
@ 2012-01-23 14:21 ` Michael Roth
2012-01-23 14:21 ` [Qemu-devel] [PATCH 5/7] qemu-ga: add initial win32 support Michael Roth
` (2 subsequent siblings)
6 siblings, 0 replies; 11+ messages in thread
From: Michael Roth @ 2012-01-23 14:21 UTC (permalink / raw)
To: qemu-devel; +Cc: ghammer, aliguori
Various stubs and #ifdefs to compile for Windows using mingw
cross-build. Still has 1 linker error due to a dependency on the
forthcoming win32 versions of the GAChannel/transport class.
Signed-off-by: Michael Roth <mdroth@linux.vnet.ibm.com>
---
Makefile | 2 +-
Makefile.objs | 9 +++--
configure | 2 +-
qemu-ga.c | 16 +++++++++
qga/commands-win32.c | 91 ++++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 114 insertions(+), 6 deletions(-)
create mode 100644 qga/commands-win32.c
diff --git a/Makefile b/Makefile
index 917fb9b..7c5e1fd 100644
--- a/Makefile
+++ b/Makefile
@@ -197,7 +197,7 @@ QGALIB_GEN=$(addprefix $(qapi-dir)/, qga-qapi-types.h qga-qapi-visit.h qga-qmp-c
$(QGALIB_OBJ): $(QGALIB_GEN) $(GENERATED_HEADERS)
$(qga-obj-y) qemu-ga.o: $(QGALIB_GEN) $(GENERATED_HEADERS)
-qemu-ga$(EXESUF): qemu-ga.o $(qga-obj-y) $(qapi-obj-y) $(tools-obj-y) $(qobject-obj-y) $(version-obj-y) $(QGALIB_OBJ)
+qemu-ga$(EXESUF): qemu-ga.o $(qga-obj-y) $(tools-obj-y) $(qapi-obj-y) $(qobject-obj-y) $(version-obj-y) $(QGALIB_OBJ)
QEMULIBS=libhw32 libhw64 libuser libdis libdis-user
diff --git a/Makefile.objs b/Makefile.objs
index 5bc883a..d14218b 100644
--- a/Makefile.objs
+++ b/Makefile.objs
@@ -416,12 +416,13 @@ common-obj-y += qmp.o hmp.o
######################################################################
# guest agent
-qga-nested-y = commands.o commands-posix.o guest-agent-command-state.o
-qga-nested-y += channel-posix.o
+qga-nested-y = commands.o guest-agent-command-state.o
+qga-nested-$(CONFIG_POSIX) += commands-posix.o channel-posix.o
+qga-nested-$(CONFIG_WIN32) += commands-win32.o
qga-obj-y = $(addprefix qga/, $(qga-nested-y))
-qga-obj-y += qemu-ga.o qemu-sockets.o module.o qemu-option.o
+qga-obj-y += qemu-ga.o module.o
qga-obj-$(CONFIG_WIN32) += oslib-win32.o
-qga-obj-$(CONFIG_POSIX) += oslib-posix.o
+qga-obj-$(CONFIG_POSIX) += oslib-posix.o qemu-sockets.o qemu-option.o
vl.o: QEMU_CFLAGS+=$(GPROF_CFLAGS)
diff --git a/configure b/configure
index 467e87b..e065b4a 100755
--- a/configure
+++ b/configure
@@ -504,7 +504,7 @@ if test "$mingw32" = "yes" ; then
bindir="\${prefix}"
sysconfdir="\${prefix}"
confsuffix=""
- guest_agent="no"
+ libs_qga="-lws2_32 -lwinmm $lib_qga"
fi
werror=""
diff --git a/qemu-ga.c b/qemu-ga.c
index 5f89ab9..c6e9dd6 100644
--- a/qemu-ga.c
+++ b/qemu-ga.c
@@ -15,7 +15,9 @@
#include <stdbool.h>
#include <glib.h>
#include <getopt.h>
+#ifndef _WIN32
#include <syslog.h>
+#endif
#include "json-streamer.h"
#include "json-parser.h"
#include "qint.h"
@@ -45,6 +47,7 @@ struct GAState {
static struct GAState *ga_state;
+#ifndef _WIN32
static void quit_handler(int sig)
{
g_debug("received signal num %d, quitting", sig);
@@ -74,6 +77,7 @@ static gboolean register_signal_handlers(void)
}
return true;
}
+#endif
static void usage(const char *cmd)
{
@@ -88,7 +92,9 @@ static void usage(const char *cmd)
" -f, --pidfile specify pidfile (default is %s)\n"
" -v, --verbose log extra debugging information\n"
" -V, --version print version information and exit\n"
+#ifndef _WIN32
" -d, --daemonize become a daemon\n"
+#endif
" -b, --blacklist comma-separated list of RPCs to disable (no spaces, \"?\""
" to list available RPCs)\n"
" -h, --help display this help and exit\n"
@@ -183,9 +189,13 @@ static void ga_log(const gchar *domain, GLogLevelFlags level,
}
level &= G_LOG_LEVEL_MASK;
+#ifndef _WIN32
if (domain && strcmp(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);
@@ -193,6 +203,7 @@ static void ga_log(const gchar *domain, GLogLevelFlags level,
}
}
+#ifndef _WIN32
static void become_daemon(const char *pidfile)
{
pid_t pid, sid;
@@ -243,6 +254,7 @@ fail:
g_critical("failed to daemonize");
exit(EXIT_FAILURE);
}
+#endif
static int send_response(GAState *s, QObject *payload)
{
@@ -511,10 +523,12 @@ int main(int argc, char **argv)
}
}
+#ifndef _WIN32
if (daemonize) {
g_debug("starting daemon");
become_daemon(pidfile);
}
+#endif
ga_set_support_level(default_support_level);
s = g_malloc0(sizeof(GAState));
@@ -528,10 +542,12 @@ int main(int argc, char **argv)
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)) {
diff --git a/qga/commands-win32.c b/qga/commands-win32.c
new file mode 100644
index 0000000..d96f1ad
--- /dev/null
+++ b/qga/commands-win32.c
@@ -0,0 +1,91 @@
+/*
+ * 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"
+
+void qmp_guest_shutdown(bool has_mode, const char *mode, Error **err)
+{
+ error_set(err, QERR_UNSUPPORTED);
+}
+
+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)
+{
+}
--
1.7.4.1
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [Qemu-devel] [PATCH 5/7] qemu-ga: add initial win32 support
2012-01-23 14:21 [Qemu-devel] qemu-ga: add support for Windows Michael Roth
` (3 preceding siblings ...)
2012-01-23 14:21 ` [Qemu-devel] [PATCH 4/7] qemu-ga: fixes for win32 build of qemu-ga Michael Roth
@ 2012-01-23 14:21 ` Michael Roth
2012-01-23 14:21 ` [Qemu-devel] [PATCH 6/7] qemu-ga: add Windows service integration Michael Roth
2012-01-23 14:21 ` [Qemu-devel] [PATCH 7/7] qemu-ga: add win32 guest-shutdown command Michael Roth
6 siblings, 0 replies; 11+ messages in thread
From: Michael Roth @ 2012-01-23 14:21 UTC (permalink / raw)
To: qemu-devel; +Cc: ghammer, aliguori
This adds a win32 channel implementation that makes qemu-ga functional
on Windows using virtio-serial (unix-listen/isa-serial not currently
implemented). Unlike with the posix implementation, we do not use
GIOChannel for the following reasons:
- glib calls stat() on an fd to check whether S_IFCHR is set, which is
the case for virtio-serial on win32. Because of that, a one-time
check to determine whether the channel is readable is done by making
a call to PeekConsoleInput(), which reports the underlying handle is
not a valid console handle, and thus we can never read from the
channel.
- if one goes as far as to "trick" glib into thinking it is a normal
file descripter, the buffering is done in such a way that data
written to the output stream will subsequently result in that same
data being read back as if it were input, causing an error loop.
furthermore, a forced flush of the channel only moves the data into a
secondary buffer managed by glib, so there's no way to prevent output
from getting read back as input.
The implementation here ties into the glib main loop by implementing a
custom GSource that continually submits asynchronous/overlapped I/O to
fill an GAChannel-managed read buffer, and tells glib to poll the
corresponding event handle for a completion whenever there is no
data/RPC in the read buffer to notify the main application about.
Signed-off-by: Michael Roth <mdroth@linux.vnet.ibm.com>
---
Makefile.objs | 2 +-
qemu-ga.c | 4 +
qga/channel-win32.c | 337 +++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 342 insertions(+), 1 deletions(-)
create mode 100644 qga/channel-win32.c
diff --git a/Makefile.objs b/Makefile.objs
index d14218b..93a4fb9 100644
--- a/Makefile.objs
+++ b/Makefile.objs
@@ -418,7 +418,7 @@ common-obj-y += qmp.o hmp.o
qga-nested-y = commands.o guest-agent-command-state.o
qga-nested-$(CONFIG_POSIX) += commands-posix.o channel-posix.o
-qga-nested-$(CONFIG_WIN32) += commands-win32.o
+qga-nested-$(CONFIG_WIN32) += commands-win32.o channel-win32.o
qga-obj-y = $(addprefix qga/, $(qga-nested-y))
qga-obj-y += qemu-ga.o module.o
qga-obj-$(CONFIG_WIN32) += oslib-win32.o
diff --git a/qemu-ga.c b/qemu-ga.c
index c6e9dd6..100a61c 100644
--- a/qemu-ga.c
+++ b/qemu-ga.c
@@ -31,7 +31,11 @@
#include "qga-qapi-types.h"
#include "qga/channel.h"
+#ifndef _WIN32
#define QGA_VIRTIO_PATH_DEFAULT "/dev/virtio-ports/org.qemu.guest_agent.0"
+#else
+#define QGA_VIRTIO_PATH_DEFAULT "\\\\.\\Global\\org.qemu.guest_agent.0"
+#endif
#define QGA_PIDFILE_DEFAULT "/var/run/qemu-ga.pid"
struct GAState {
diff --git a/qga/channel-win32.c b/qga/channel-win32.c
new file mode 100644
index 0000000..0a16e02
--- /dev/null
+++ b/qga/channel-win32.c
@@ -0,0 +1,337 @@
+#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 one 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) {
+ /* note: On WinXP SP3 with virtio-win 0.1-15 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.
+ */
+ 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);
+}
--
1.7.4.1
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [Qemu-devel] [PATCH 6/7] qemu-ga: add Windows service integration
2012-01-23 14:21 [Qemu-devel] qemu-ga: add support for Windows Michael Roth
` (4 preceding siblings ...)
2012-01-23 14:21 ` [Qemu-devel] [PATCH 5/7] qemu-ga: add initial win32 support Michael Roth
@ 2012-01-23 14:21 ` Michael Roth
2012-01-23 14:21 ` [Qemu-devel] [PATCH 7/7] qemu-ga: add win32 guest-shutdown command Michael Roth
6 siblings, 0 replies; 11+ messages in thread
From: Michael Roth @ 2012-01-23 14:21 UTC (permalink / raw)
To: qemu-devel; +Cc: ghammer, aliguori
This allows qemu-ga to function as a Windows service:
- to install the service (will auto-start on boot):
qemu-ga --service install
- to start the service:
net start qemu-ga
- to stop the service:
net stop qemu-ga
- to uninstall service:
qemu-ga --service uninstall
Original patch by Gal Hammer <ghammer@redhat.com>
Signed-off-by: Michael Roth <mdroth@linux.vnet.ibm.com>
---
Makefile.objs | 2 +-
qemu-ga.c | 103 ++++++++++++++++++++++++++++++++++++++++++++--
qga/service-win32.c | 114 +++++++++++++++++++++++++++++++++++++++++++++++++++
qga/service-win32.h | 30 +++++++++++++
4 files changed, 244 insertions(+), 5 deletions(-)
create mode 100644 qga/service-win32.c
create mode 100644 qga/service-win32.h
diff --git a/Makefile.objs b/Makefile.objs
index 93a4fb9..fa01a1e 100644
--- a/Makefile.objs
+++ b/Makefile.objs
@@ -418,7 +418,7 @@ common-obj-y += qmp.o hmp.o
qga-nested-y = commands.o guest-agent-command-state.o
qga-nested-$(CONFIG_POSIX) += commands-posix.o channel-posix.o
-qga-nested-$(CONFIG_WIN32) += commands-win32.o channel-win32.o
+qga-nested-$(CONFIG_WIN32) += commands-win32.o channel-win32.o service-win32.o
qga-obj-y = $(addprefix qga/, $(qga-nested-y))
qga-obj-y += qemu-ga.o module.o
qga-obj-$(CONFIG_WIN32) += oslib-win32.o
diff --git a/qemu-ga.c b/qemu-ga.c
index 100a61c..f6b3acf 100644
--- a/qemu-ga.c
+++ b/qemu-ga.c
@@ -30,6 +30,10 @@
#include "qapi/qmp-core.h"
#include "qga-qapi-types.h"
#include "qga/channel.h"
+#ifdef _WIN32
+#include "qga/service-win32.h"
+#include <windows.h>
+#endif
#ifndef _WIN32
#define QGA_VIRTIO_PATH_DEFAULT "/dev/virtio-ports/org.qemu.guest_agent.0"
@@ -47,11 +51,19 @@ struct GAState {
GLogLevelFlags log_level;
FILE *log_file;
bool logging_enabled;
+#ifdef _WIN32
+ GAService service;
+#endif
};
static struct GAState *ga_state;
-#ifndef _WIN32
+#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 quit_handler(int sig)
{
g_debug("received signal num %d, quitting", sig);
@@ -61,6 +73,7 @@ static void quit_handler(int sig)
}
}
+#ifndef _WIN32
static gboolean register_signal_handlers(void)
{
struct sigaction sigact;
@@ -96,8 +109,9 @@ static void usage(const char *cmd)
" -f, --pidfile specify pidfile (default is %s)\n"
" -v, --verbose log extra debugging information\n"
" -V, --version print version information and exit\n"
-#ifndef _WIN32
" -d, --daemonize become a daemon\n"
+#ifdef _WIN32
+" -s, --service service commands: install, uninstall\n"
#endif
" -b, --blacklist comma-separated list of RPCs to disable (no spaces, \"?\""
" to list available RPCs)\n"
@@ -434,10 +448,64 @@ static gboolean channel_init(GAState *s, const gchar *method, const gchar *path)
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
+
int main(int argc, char **argv)
{
- const char *sopt = "hVvdm:p:l:f:b:";
+ const char *sopt = "hVvdm:p:l:f:b:s:";
const char *method = NULL, *path = NULL, *pidfile = QGA_PIDFILE_DEFAULT;
+ const char *log_file_name = NULL;
+#ifdef _WIN32
+ const char *service = NULL;
+#endif
const struct option lopt[] = {
{ "help", 0, NULL, 'h' },
{ "version", 0, NULL, 'V' },
@@ -448,6 +516,9 @@ int main(int argc, char **argv)
{ "path", 0, NULL, 'p' },
{ "daemonize", 0, NULL, 'd' },
{ "blacklist", 0, NULL, 'b' },
+#ifdef _WIN32
+ { "service", 0, NULL, 's' },
+#endif
{ NULL, 0, NULL, 0 }
};
int opt_ind = 0, ch, daemonize = 0, i, j, len;
@@ -471,7 +542,8 @@ int main(int argc, char **argv)
path = optarg;
break;
case 'l':
- log_file = fopen(optarg, "a");
+ log_file_name = optarg;
+ log_file = fopen(log_file_name, "a");
if (!log_file) {
g_critical("unable to open specified log file: %s",
strerror(errno));
@@ -517,6 +589,19 @@ int main(int argc, char **argv)
}
break;
}
+#ifdef _WIN32
+ case 's':
+ service = optarg;
+ if (strcmp(service, "install") == 0) {
+ return ga_install_service(path, log_file_name);
+ } else if (strcmp(service, "uninstall") == 0) {
+ return ga_uninstall_service();
+ } else {
+ printf("Unknown service command.\n");
+ return EXIT_FAILURE;
+ }
+ break;
+#endif
case 'h':
usage(argv[0]);
return 0;
@@ -558,7 +643,17 @@ int main(int argc, char **argv)
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);
diff --git a/qga/service-win32.c b/qga/service-win32.c
new file mode 100644
index 0000000..0905456
--- /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 0000000..99dfc53
--- /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
--
1.7.4.1
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [Qemu-devel] [PATCH 7/7] qemu-ga: add win32 guest-shutdown command
2012-01-23 14:21 [Qemu-devel] qemu-ga: add support for Windows Michael Roth
` (5 preceding siblings ...)
2012-01-23 14:21 ` [Qemu-devel] [PATCH 6/7] qemu-ga: add Windows service integration Michael Roth
@ 2012-01-23 14:21 ` Michael Roth
6 siblings, 0 replies; 11+ messages in thread
From: Michael Roth @ 2012-01-23 14:21 UTC (permalink / raw)
To: qemu-devel; +Cc: ghammer, aliguori
Implement guest-shutdown RPC for Windows. Functionally this should be
equivalent to the posix implementation.
Original patch by Gal Hammer <ghammer@redhat.com>
Signed-off-by: Michael Roth <mdroth@linux.vnet.ibm.com>
---
qga/commands-win32.c | 41 ++++++++++++++++++++++++++++++++++++++++-
1 files changed, 40 insertions(+), 1 deletions(-)
diff --git a/qga/commands-win32.c b/qga/commands-win32.c
index d96f1ad..4aa0f0d 100644
--- a/qga/commands-win32.c
+++ b/qga/commands-win32.c
@@ -15,9 +15,48 @@
#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)
{
- error_set(err, QERR_UNSUPPORTED);
+ 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)
--
1.7.4.1
^ permalink raw reply related [flat|nested] 11+ messages in thread
* Re: [Qemu-devel] [PATCH 1/7] qemu-ga: move channel/transport functionality into wrapper class
2012-01-23 14:21 ` [Qemu-devel] [PATCH 1/7] qemu-ga: move channel/transport functionality into wrapper class Michael Roth
@ 2012-01-23 20:24 ` Anthony Liguori
2012-01-23 20:39 ` Anthony Liguori
2012-01-23 20:56 ` Michael Roth
0 siblings, 2 replies; 11+ messages in thread
From: Anthony Liguori @ 2012-01-23 20:24 UTC (permalink / raw)
To: Michael Roth; +Cc: ghammer, qemu-devel
On 01/23/2012 08:21 AM, Michael Roth wrote:
> This is monstly in preparation for the win32 port, which won't use
> GIO channels for reasons that will be made clearer later. Here the
> GAChannel class is just a loose wrapper around GIOChannel
> calls/callbacks, but we also roll the logic/configuration for handling
> FDs and also for managing unix socket connections into it, which makes
> the abstraction much more complete and further aids in the win32 port
> since isa-serial/unix-listen will not be supported initially.
>
> There's also a bit of refactoring in the main logic to consolidate the
> exit paths so we can do common cleanup for things like pid files, which
> weren't always cleaned up previously.
>
> Signed-off-by: Michael Roth<mdroth@linux.vnet.ibm.com>
This doesn't apply very well. Can you rebase?
Regards,
Anthony Liguori
> ---
> Makefile.objs | 1 +
> qemu-ga.c | 306 ++++++++++++------------------------------------
> qga/channel-posix.c | 246 ++++++++++++++++++++++++++++++++++++++
> qga/channel.h | 33 +++++
> qga/guest-agent-core.h | 2 +-
> 5 files changed, 355 insertions(+), 233 deletions(-)
> create mode 100644 qga/channel-posix.c
> create mode 100644 qga/channel.h
>
> diff --git a/Makefile.objs b/Makefile.objs
> index f94c881..72141cc 100644
> --- a/Makefile.objs
> +++ b/Makefile.objs
> @@ -417,6 +417,7 @@ common-obj-y += qmp.o hmp.o
> # guest agent
>
> qga-nested-y = guest-agent-commands.o guest-agent-command-state.o
> +qga-nested-y += channel-posix.o
> qga-obj-y = $(addprefix qga/, $(qga-nested-y))
> qga-obj-y += qemu-ga.o qemu-sockets.o module.o qemu-option.o
> qga-obj-$(CONFIG_WIN32) += oslib-win32.o
> diff --git a/qemu-ga.c b/qemu-ga.c
> index 8e5b075..5f89ab9 100644
> --- a/qemu-ga.c
> +++ b/qemu-ga.c
> @@ -15,9 +15,7 @@
> #include<stdbool.h>
> #include<glib.h>
> #include<getopt.h>
> -#include<termios.h>
> #include<syslog.h>
> -#include "qemu_socket.h"
> #include "json-streamer.h"
> #include "json-parser.h"
> #include "qint.h"
> @@ -29,19 +27,15 @@
> #include "error_int.h"
> #include "qapi/qmp-core.h"
> #include "qga-qapi-types.h"
> +#include "qga/channel.h"
>
> #define QGA_VIRTIO_PATH_DEFAULT "/dev/virtio-ports/org.qemu.guest_agent.0"
> #define QGA_PIDFILE_DEFAULT "/var/run/qemu-ga.pid"
> -#define QGA_BAUDRATE_DEFAULT B38400 /* for isa-serial channels */
> -#define QGA_TIMEOUT_DEFAULT 30*1000 /* ms */
>
> struct GAState {
> JSONMessageParser parser;
> GMainLoop *main_loop;
> - GIOChannel *conn_channel;
> - GIOChannel *listen_channel;
> - const char *path;
> - const char *method;
> + GAChannel *channel;
> bool virtio; /* fastpath to check for virtio to deal with poll() quirks */
> GACommandState *command_state;
> GLogLevelFlags log_level;
> @@ -60,7 +54,7 @@ static void quit_handler(int sig)
> }
> }
>
> -static void register_signal_handlers(void)
> +static gboolean register_signal_handlers(void)
> {
> struct sigaction sigact;
> int ret;
> @@ -71,12 +65,14 @@ static void register_signal_handlers(void)
> ret = sigaction(SIGINT,&sigact, NULL);
> if (ret == -1) {
> g_error("error configuring signal handler: %s", strerror(errno));
> - exit(EXIT_FAILURE);
> + return false;
> }
> ret = sigaction(SIGTERM,&sigact, NULL);
> if (ret == -1) {
> g_error("error configuring signal handler: %s", strerror(errno));
> + return false;
> }
> + return true;
> }
>
> static void usage(const char *cmd)
> @@ -101,8 +97,6 @@ static void usage(const char *cmd)
> , cmd, QGA_VERSION, QGA_VIRTIO_PATH_DEFAULT, QGA_PIDFILE_DEFAULT);
> }
>
> -static void conn_channel_close(GAState *s);
> -
> static GuestAgentSupportLevel ga_support_level;
>
> static int ga_cmp_support_levels(GuestAgentSupportLevel a,
> @@ -250,40 +244,13 @@ fail:
> exit(EXIT_FAILURE);
> }
>
> -static int conn_channel_send_buf(GIOChannel *channel, const char *buf,
> - gsize count)
> +static int send_response(GAState *s, QObject *payload)
> {
> - GError *err = NULL;
> - gsize written = 0;
> - GIOStatus status;
> -
> - while (count) {
> - status = g_io_channel_write_chars(channel, buf, count,&written,&err);
> - g_debug("sending data, count: %d", (int)count);
> - if (err != NULL) {
> - g_warning("error sending newline: %s", err->message);
> - return err->code;
> - }
> - if (status == G_IO_STATUS_ERROR || status == G_IO_STATUS_EOF) {
> - return -EPIPE;
> - }
> -
> - if (status == G_IO_STATUS_NORMAL) {
> - count -= written;
> - }
> - }
> -
> - return 0;
> -}
> -
> -static int conn_channel_send_payload(GIOChannel *channel, QObject *payload)
> -{
> - int ret = 0;
> const char *buf;
> QString *payload_qstr;
> - GError *err = NULL;
> + GIOStatus status;
>
> - g_assert(payload&& channel);
> + g_assert(payload&& s->channel);
>
> payload_qstr = qobject_to_json(payload);
> if (!payload_qstr) {
> @@ -292,24 +259,13 @@ static int conn_channel_send_payload(GIOChannel *channel, QObject *payload)
>
> qstring_append_chr(payload_qstr, '\n');
> buf = qstring_get_str(payload_qstr);
> - ret = conn_channel_send_buf(channel, buf, strlen(buf));
> - if (ret) {
> - goto out_free;
> - }
> -
> - g_io_channel_flush(channel,&err);
> - if (err != NULL) {
> - g_warning("error flushing payload: %s", err->message);
> - ret = err->code;
> - goto out_free;
> - }
> -
> -out_free:
> + status = ga_channel_write_all(s->channel, buf, strlen(buf));
> QDECREF(payload_qstr);
> - if (err) {
> - g_error_free(err);
> + if (status != G_IO_STATUS_NORMAL) {
> + return -EIO;
> }
> - return ret;
> +
> + return 0;
> }
>
> static void process_command(GAState *s, QDict *req)
> @@ -321,9 +277,9 @@ static void process_command(GAState *s, QDict *req)
> g_debug("processing command");
> rsp = qmp_dispatch(QOBJECT(req));
> if (rsp) {
> - ret = conn_channel_send_payload(s->conn_channel, rsp);
> + ret = send_response(s, rsp);
> if (ret) {
> - g_warning("error sending payload: %s", strerror(ret));
> + g_warning("error sending response: %s", strerror(ret));
> }
> qobject_decref(rsp);
> } else {
> @@ -373,38 +329,42 @@ static void process_event(JSONMessageParser *parser, QList *tokens)
> qdict_put_obj(qdict, "error", error_get_qobject(err));
> error_free(err);
> }
> - ret = conn_channel_send_payload(s->conn_channel, QOBJECT(qdict));
> + ret = send_response(s, QOBJECT(qdict));
> if (ret) {
> - g_warning("error sending payload: %s", strerror(ret));
> + g_warning("error sending error response: %s", strerror(ret));
> }
> }
>
> QDECREF(qdict);
> }
>
> -static gboolean conn_channel_read(GIOChannel *channel, GIOCondition condition,
> - gpointer data)
> +/* false return signals GAChannel to close the current client connection */
> +static gboolean channel_event_cb(GIOCondition condition, gpointer data)
> {
> GAState *s = data;
> - gchar buf[1024];
> + gchar buf[QGA_READ_COUNT_DEFAULT+1];
> gsize count;
> GError *err = NULL;
> - memset(buf, 0, 1024);
> - GIOStatus status = g_io_channel_read_chars(channel, buf, 1024,
> -&count,&err);
> + GIOStatus status = ga_channel_read(s->channel, buf, QGA_READ_COUNT_DEFAULT,&count);
> if (err != NULL) {
> g_warning("error reading channel: %s", err->message);
> - conn_channel_close(s);
> g_error_free(err);
> return false;
> }
> switch (status) {
> case G_IO_STATUS_ERROR:
> - g_warning("problem");
> + 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;
> + }
> 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
> @@ -413,180 +373,49 @@ static gboolean conn_channel_read(GIOChannel *channel, GIOCondition condition,
> usleep(100*1000);
> }
> return true;
> - case G_IO_STATUS_EOF:
> - g_debug("received EOF");
> - conn_channel_close(s);
> - if (s->virtio) {
> - return true;
> - }
> - return false;
> default:
> g_warning("unknown channel read status, closing");
> - conn_channel_close(s);
> return false;
> }
> return true;
> }
>
> -static int conn_channel_add(GAState *s, int fd)
> +static gboolean channel_init(GAState *s, const gchar *method, const gchar *path)
> {
> - GIOChannel *conn_channel;
> - GError *err = NULL;
> + GAChannelMethod channel_method;
>
> - g_assert(s&& !s->conn_channel);
> - conn_channel = g_io_channel_unix_new(fd);
> - g_assert(conn_channel);
> - g_io_channel_set_encoding(conn_channel, NULL,&err);
> - if (err != NULL) {
> - g_warning("error setting channel encoding to binary");
> - g_error_free(err);
> - return -1;
> + if (method == NULL) {
> + method = "virtio-serial";
> }
> - g_io_add_watch(conn_channel, G_IO_IN | G_IO_HUP,
> - conn_channel_read, s);
> - s->conn_channel = conn_channel;
> - return 0;
> -}
>
> -static gboolean listen_channel_accept(GIOChannel *channel,
> - GIOCondition condition, gpointer data)
> -{
> - GAState *s = data;
> - g_assert(channel != NULL);
> - int ret, conn_fd;
> - bool accepted = false;
> - struct sockaddr_un addr;
> - socklen_t addrlen = sizeof(addr);
> -
> - conn_fd = qemu_accept(g_io_channel_unix_get_fd(s->listen_channel),
> - (struct sockaddr *)&addr,&addrlen);
> - if (conn_fd == -1) {
> - g_warning("error converting fd to gsocket: %s", strerror(errno));
> - goto out;
> - }
> - fcntl(conn_fd, F_SETFL, O_NONBLOCK);
> - ret = conn_channel_add(s, conn_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 int listen_channel_add(GAState *s, int listen_fd, bool new)
> -{
> - if (new) {
> - s->listen_channel = g_io_channel_unix_new(listen_fd);
> - }
> - g_io_add_watch(s->listen_channel, G_IO_IN,
> - listen_channel_accept, s);
> - return 0;
> -}
> -
> -/* cleanup state for closed connection/session, start accepting new
> - * connections if we're in listening mode
> - */
> -static void conn_channel_close(GAState *s)
> -{
> - if (strcmp(s->method, "unix-listen") == 0) {
> - g_io_channel_shutdown(s->conn_channel, true, NULL);
> - listen_channel_add(s, 0, false);
> - } else if (strcmp(s->method, "virtio-serial") == 0) {
> - /* we spin on EOF for virtio-serial, so back off a bit. also,
> - * dont close the connection in this case, it'll resume normal
> - * operation when another process connects to host chardev
> - */
> - usleep(100*1000);
> - goto out_noclose;
> - }
> - g_io_channel_unref(s->conn_channel);
> - s->conn_channel = NULL;
> -out_noclose:
> - return;
> -}
> -
> -static void init_guest_agent(GAState *s)
> -{
> - struct termios tio;
> - int ret, fd;
> -
> - if (s->method == NULL) {
> - /* try virtio-serial as our default */
> - s->method = "virtio-serial";
> - }
> -
> - if (s->path == NULL) {
> - if (strcmp(s->method, "virtio-serial") != 0) {
> + if (path == NULL) {
> + if (strcmp(method, "virtio-serial") != 0) {
> g_critical("must specify a path for this channel");
> - exit(EXIT_FAILURE);
> + return false;
> }
> /* try the default path for the virtio-serial port */
> - s->path = QGA_VIRTIO_PATH_DEFAULT;
> + path = QGA_VIRTIO_PATH_DEFAULT;
> }
>
> - if (strcmp(s->method, "virtio-serial") == 0) {
> - s->virtio = true;
> - fd = qemu_open(s->path, O_RDWR | O_NONBLOCK | O_ASYNC);
> - if (fd == -1) {
> - g_critical("error opening channel: %s", strerror(errno));
> - exit(EXIT_FAILURE);
> - }
> - ret = conn_channel_add(s, fd);
> - if (ret) {
> - g_critical("error adding channel to main loop");
> - exit(EXIT_FAILURE);
> - }
> - } else if (strcmp(s->method, "isa-serial") == 0) {
> - fd = qemu_open(s->path, O_RDWR | O_NOCTTY);
> - 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 |= QGA_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 = conn_channel_add(s, fd);
> - if (ret) {
> - g_error("error adding channel to main loop");
> - }
> - } else if (strcmp(s->method, "unix-listen") == 0) {
> - fd = unix_listen(s->path, NULL, strlen(s->path));
> - if (fd == -1) {
> - g_critical("error opening path: %s", strerror(errno));
> - exit(EXIT_FAILURE);
> - }
> - ret = listen_channel_add(s, fd, true);
> - if (ret) {
> - g_critical("error binding/listening to specified socket");
> - exit(EXIT_FAILURE);
> - }
> + 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", s->method);
> - exit(EXIT_FAILURE);
> + g_critical("unsupported channel method/type: %s", method);
> + return false;
> }
>
> - json_message_parser_init(&s->parser, process_event);
> - s->main_loop = g_main_loop_new(NULL, 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;
> }
>
> int main(int argc, char **argv)
> @@ -689,9 +518,6 @@ int main(int argc, char **argv)
>
> ga_set_support_level(default_support_level);
> s = g_malloc0(sizeof(GAState));
> - s->conn_channel = NULL;
> - s->path = path;
> - s->method = method;
> s->log_file = log_file;
> s->log_level = log_level;
> g_log_set_default_handler(ga_log, s);
> @@ -700,15 +526,31 @@ int main(int argc, char **argv)
> 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;
> + if (!register_signal_handlers()) {
> + g_critical("failed to register signal handlers");
> + goto out_bad;
> + }
>
> - init_guest_agent(ga_state);
> - register_signal_handlers();
> -
> + 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;
> + }
> g_main_loop_run(ga_state->main_loop);
>
> ga_command_state_cleanup_all(ga_state->command_state);
> - unlink(pidfile);
> + ga_channel_free(ga_state->channel);
>
> + if (daemonize) {
> + unlink(pidfile);
> + }
> return 0;
> +
> +out_bad:
> + if (daemonize) {
> + unlink(pidfile);
> + }
> + return EXIT_FAILURE;
> }
> diff --git a/qga/channel-posix.c b/qga/channel-posix.c
> new file mode 100644
> index 0000000..40f7658
> --- /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.h b/qga/channel.h
> new file mode 100644
> index 0000000..3704ea9
> --- /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-core.h b/qga/guest-agent-core.h
> index 7327e73..891ed06 100644
> --- a/qga/guest-agent-core.h
> +++ b/qga/guest-agent-core.h
> @@ -22,7 +22,7 @@
> #define QGA_SUPPORT_LEVEL_MAJOR_MIN 1
> #define QGA_SUPPORT_LEVEL_MINOR_MIN 0
> #define QGA_SUPPORT_LEVEL_MICRO_MIN 0
> -#define QGA_READ_COUNT_DEFAULT 4<< 10
> +#define QGA_READ_COUNT_DEFAULT 4096
>
> typedef struct GAState GAState;
> typedef struct GACommandState GACommandState;
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [Qemu-devel] [PATCH 1/7] qemu-ga: move channel/transport functionality into wrapper class
2012-01-23 20:24 ` Anthony Liguori
@ 2012-01-23 20:39 ` Anthony Liguori
2012-01-23 20:56 ` Michael Roth
1 sibling, 0 replies; 11+ messages in thread
From: Anthony Liguori @ 2012-01-23 20:39 UTC (permalink / raw)
To: Anthony Liguori; +Cc: ghammer, Michael Roth, qemu-devel
On 01/23/2012 02:24 PM, Anthony Liguori wrote:
> On 01/23/2012 08:21 AM, Michael Roth wrote:
>> This is monstly in preparation for the win32 port, which won't use
>> GIO channels for reasons that will be made clearer later. Here the
>> GAChannel class is just a loose wrapper around GIOChannel
>> calls/callbacks, but we also roll the logic/configuration for handling
>> FDs and also for managing unix socket connections into it, which makes
>> the abstraction much more complete and further aids in the win32 port
>> since isa-serial/unix-listen will not be supported initially.
>>
>> There's also a bit of refactoring in the main logic to consolidate the
>> exit paths so we can do common cleanup for things like pid files, which
>> weren't always cleaned up previously.
>>
>> Signed-off-by: Michael Roth<mdroth@linux.vnet.ibm.com>
>
> This doesn't apply very well. Can you rebase?
Your header is missing [PATCH 0/7] so i didn't see it. How about sending a
proper PULL request with the accumulated patches?
Regards,
Anthony Liguori
>
> Regards,
>
> Anthony Liguori
>
>> ---
>> Makefile.objs | 1 +
>> qemu-ga.c | 306 ++++++++++++------------------------------------
>> qga/channel-posix.c | 246 ++++++++++++++++++++++++++++++++++++++
>> qga/channel.h | 33 +++++
>> qga/guest-agent-core.h | 2 +-
>> 5 files changed, 355 insertions(+), 233 deletions(-)
>> create mode 100644 qga/channel-posix.c
>> create mode 100644 qga/channel.h
>>
>> diff --git a/Makefile.objs b/Makefile.objs
>> index f94c881..72141cc 100644
>> --- a/Makefile.objs
>> +++ b/Makefile.objs
>> @@ -417,6 +417,7 @@ common-obj-y += qmp.o hmp.o
>> # guest agent
>>
>> qga-nested-y = guest-agent-commands.o guest-agent-command-state.o
>> +qga-nested-y += channel-posix.o
>> qga-obj-y = $(addprefix qga/, $(qga-nested-y))
>> qga-obj-y += qemu-ga.o qemu-sockets.o module.o qemu-option.o
>> qga-obj-$(CONFIG_WIN32) += oslib-win32.o
>> diff --git a/qemu-ga.c b/qemu-ga.c
>> index 8e5b075..5f89ab9 100644
>> --- a/qemu-ga.c
>> +++ b/qemu-ga.c
>> @@ -15,9 +15,7 @@
>> #include<stdbool.h>
>> #include<glib.h>
>> #include<getopt.h>
>> -#include<termios.h>
>> #include<syslog.h>
>> -#include "qemu_socket.h"
>> #include "json-streamer.h"
>> #include "json-parser.h"
>> #include "qint.h"
>> @@ -29,19 +27,15 @@
>> #include "error_int.h"
>> #include "qapi/qmp-core.h"
>> #include "qga-qapi-types.h"
>> +#include "qga/channel.h"
>>
>> #define QGA_VIRTIO_PATH_DEFAULT "/dev/virtio-ports/org.qemu.guest_agent.0"
>> #define QGA_PIDFILE_DEFAULT "/var/run/qemu-ga.pid"
>> -#define QGA_BAUDRATE_DEFAULT B38400 /* for isa-serial channels */
>> -#define QGA_TIMEOUT_DEFAULT 30*1000 /* ms */
>>
>> struct GAState {
>> JSONMessageParser parser;
>> GMainLoop *main_loop;
>> - GIOChannel *conn_channel;
>> - GIOChannel *listen_channel;
>> - const char *path;
>> - const char *method;
>> + GAChannel *channel;
>> bool virtio; /* fastpath to check for virtio to deal with poll() quirks */
>> GACommandState *command_state;
>> GLogLevelFlags log_level;
>> @@ -60,7 +54,7 @@ static void quit_handler(int sig)
>> }
>> }
>>
>> -static void register_signal_handlers(void)
>> +static gboolean register_signal_handlers(void)
>> {
>> struct sigaction sigact;
>> int ret;
>> @@ -71,12 +65,14 @@ static void register_signal_handlers(void)
>> ret = sigaction(SIGINT,&sigact, NULL);
>> if (ret == -1) {
>> g_error("error configuring signal handler: %s", strerror(errno));
>> - exit(EXIT_FAILURE);
>> + return false;
>> }
>> ret = sigaction(SIGTERM,&sigact, NULL);
>> if (ret == -1) {
>> g_error("error configuring signal handler: %s", strerror(errno));
>> + return false;
>> }
>> + return true;
>> }
>>
>> static void usage(const char *cmd)
>> @@ -101,8 +97,6 @@ static void usage(const char *cmd)
>> , cmd, QGA_VERSION, QGA_VIRTIO_PATH_DEFAULT, QGA_PIDFILE_DEFAULT);
>> }
>>
>> -static void conn_channel_close(GAState *s);
>> -
>> static GuestAgentSupportLevel ga_support_level;
>>
>> static int ga_cmp_support_levels(GuestAgentSupportLevel a,
>> @@ -250,40 +244,13 @@ fail:
>> exit(EXIT_FAILURE);
>> }
>>
>> -static int conn_channel_send_buf(GIOChannel *channel, const char *buf,
>> - gsize count)
>> +static int send_response(GAState *s, QObject *payload)
>> {
>> - GError *err = NULL;
>> - gsize written = 0;
>> - GIOStatus status;
>> -
>> - while (count) {
>> - status = g_io_channel_write_chars(channel, buf, count,&written,&err);
>> - g_debug("sending data, count: %d", (int)count);
>> - if (err != NULL) {
>> - g_warning("error sending newline: %s", err->message);
>> - return err->code;
>> - }
>> - if (status == G_IO_STATUS_ERROR || status == G_IO_STATUS_EOF) {
>> - return -EPIPE;
>> - }
>> -
>> - if (status == G_IO_STATUS_NORMAL) {
>> - count -= written;
>> - }
>> - }
>> -
>> - return 0;
>> -}
>> -
>> -static int conn_channel_send_payload(GIOChannel *channel, QObject *payload)
>> -{
>> - int ret = 0;
>> const char *buf;
>> QString *payload_qstr;
>> - GError *err = NULL;
>> + GIOStatus status;
>>
>> - g_assert(payload&& channel);
>> + g_assert(payload&& s->channel);
>>
>> payload_qstr = qobject_to_json(payload);
>> if (!payload_qstr) {
>> @@ -292,24 +259,13 @@ static int conn_channel_send_payload(GIOChannel
>> *channel, QObject *payload)
>>
>> qstring_append_chr(payload_qstr, '\n');
>> buf = qstring_get_str(payload_qstr);
>> - ret = conn_channel_send_buf(channel, buf, strlen(buf));
>> - if (ret) {
>> - goto out_free;
>> - }
>> -
>> - g_io_channel_flush(channel,&err);
>> - if (err != NULL) {
>> - g_warning("error flushing payload: %s", err->message);
>> - ret = err->code;
>> - goto out_free;
>> - }
>> -
>> -out_free:
>> + status = ga_channel_write_all(s->channel, buf, strlen(buf));
>> QDECREF(payload_qstr);
>> - if (err) {
>> - g_error_free(err);
>> + if (status != G_IO_STATUS_NORMAL) {
>> + return -EIO;
>> }
>> - return ret;
>> +
>> + return 0;
>> }
>>
>> static void process_command(GAState *s, QDict *req)
>> @@ -321,9 +277,9 @@ static void process_command(GAState *s, QDict *req)
>> g_debug("processing command");
>> rsp = qmp_dispatch(QOBJECT(req));
>> if (rsp) {
>> - ret = conn_channel_send_payload(s->conn_channel, rsp);
>> + ret = send_response(s, rsp);
>> if (ret) {
>> - g_warning("error sending payload: %s", strerror(ret));
>> + g_warning("error sending response: %s", strerror(ret));
>> }
>> qobject_decref(rsp);
>> } else {
>> @@ -373,38 +329,42 @@ static void process_event(JSONMessageParser *parser,
>> QList *tokens)
>> qdict_put_obj(qdict, "error", error_get_qobject(err));
>> error_free(err);
>> }
>> - ret = conn_channel_send_payload(s->conn_channel, QOBJECT(qdict));
>> + ret = send_response(s, QOBJECT(qdict));
>> if (ret) {
>> - g_warning("error sending payload: %s", strerror(ret));
>> + g_warning("error sending error response: %s", strerror(ret));
>> }
>> }
>>
>> QDECREF(qdict);
>> }
>>
>> -static gboolean conn_channel_read(GIOChannel *channel, GIOCondition condition,
>> - gpointer data)
>> +/* false return signals GAChannel to close the current client connection */
>> +static gboolean channel_event_cb(GIOCondition condition, gpointer data)
>> {
>> GAState *s = data;
>> - gchar buf[1024];
>> + gchar buf[QGA_READ_COUNT_DEFAULT+1];
>> gsize count;
>> GError *err = NULL;
>> - memset(buf, 0, 1024);
>> - GIOStatus status = g_io_channel_read_chars(channel, buf, 1024,
>> -&count,&err);
>> + GIOStatus status = ga_channel_read(s->channel, buf,
>> QGA_READ_COUNT_DEFAULT,&count);
>> if (err != NULL) {
>> g_warning("error reading channel: %s", err->message);
>> - conn_channel_close(s);
>> g_error_free(err);
>> return false;
>> }
>> switch (status) {
>> case G_IO_STATUS_ERROR:
>> - g_warning("problem");
>> + 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;
>> + }
>> 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
>> @@ -413,180 +373,49 @@ static gboolean conn_channel_read(GIOChannel *channel,
>> GIOCondition condition,
>> usleep(100*1000);
>> }
>> return true;
>> - case G_IO_STATUS_EOF:
>> - g_debug("received EOF");
>> - conn_channel_close(s);
>> - if (s->virtio) {
>> - return true;
>> - }
>> - return false;
>> default:
>> g_warning("unknown channel read status, closing");
>> - conn_channel_close(s);
>> return false;
>> }
>> return true;
>> }
>>
>> -static int conn_channel_add(GAState *s, int fd)
>> +static gboolean channel_init(GAState *s, const gchar *method, const gchar *path)
>> {
>> - GIOChannel *conn_channel;
>> - GError *err = NULL;
>> + GAChannelMethod channel_method;
>>
>> - g_assert(s&& !s->conn_channel);
>> - conn_channel = g_io_channel_unix_new(fd);
>> - g_assert(conn_channel);
>> - g_io_channel_set_encoding(conn_channel, NULL,&err);
>> - if (err != NULL) {
>> - g_warning("error setting channel encoding to binary");
>> - g_error_free(err);
>> - return -1;
>> + if (method == NULL) {
>> + method = "virtio-serial";
>> }
>> - g_io_add_watch(conn_channel, G_IO_IN | G_IO_HUP,
>> - conn_channel_read, s);
>> - s->conn_channel = conn_channel;
>> - return 0;
>> -}
>>
>> -static gboolean listen_channel_accept(GIOChannel *channel,
>> - GIOCondition condition, gpointer data)
>> -{
>> - GAState *s = data;
>> - g_assert(channel != NULL);
>> - int ret, conn_fd;
>> - bool accepted = false;
>> - struct sockaddr_un addr;
>> - socklen_t addrlen = sizeof(addr);
>> -
>> - conn_fd = qemu_accept(g_io_channel_unix_get_fd(s->listen_channel),
>> - (struct sockaddr *)&addr,&addrlen);
>> - if (conn_fd == -1) {
>> - g_warning("error converting fd to gsocket: %s", strerror(errno));
>> - goto out;
>> - }
>> - fcntl(conn_fd, F_SETFL, O_NONBLOCK);
>> - ret = conn_channel_add(s, conn_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 int listen_channel_add(GAState *s, int listen_fd, bool new)
>> -{
>> - if (new) {
>> - s->listen_channel = g_io_channel_unix_new(listen_fd);
>> - }
>> - g_io_add_watch(s->listen_channel, G_IO_IN,
>> - listen_channel_accept, s);
>> - return 0;
>> -}
>> -
>> -/* cleanup state for closed connection/session, start accepting new
>> - * connections if we're in listening mode
>> - */
>> -static void conn_channel_close(GAState *s)
>> -{
>> - if (strcmp(s->method, "unix-listen") == 0) {
>> - g_io_channel_shutdown(s->conn_channel, true, NULL);
>> - listen_channel_add(s, 0, false);
>> - } else if (strcmp(s->method, "virtio-serial") == 0) {
>> - /* we spin on EOF for virtio-serial, so back off a bit. also,
>> - * dont close the connection in this case, it'll resume normal
>> - * operation when another process connects to host chardev
>> - */
>> - usleep(100*1000);
>> - goto out_noclose;
>> - }
>> - g_io_channel_unref(s->conn_channel);
>> - s->conn_channel = NULL;
>> -out_noclose:
>> - return;
>> -}
>> -
>> -static void init_guest_agent(GAState *s)
>> -{
>> - struct termios tio;
>> - int ret, fd;
>> -
>> - if (s->method == NULL) {
>> - /* try virtio-serial as our default */
>> - s->method = "virtio-serial";
>> - }
>> -
>> - if (s->path == NULL) {
>> - if (strcmp(s->method, "virtio-serial") != 0) {
>> + if (path == NULL) {
>> + if (strcmp(method, "virtio-serial") != 0) {
>> g_critical("must specify a path for this channel");
>> - exit(EXIT_FAILURE);
>> + return false;
>> }
>> /* try the default path for the virtio-serial port */
>> - s->path = QGA_VIRTIO_PATH_DEFAULT;
>> + path = QGA_VIRTIO_PATH_DEFAULT;
>> }
>>
>> - if (strcmp(s->method, "virtio-serial") == 0) {
>> - s->virtio = true;
>> - fd = qemu_open(s->path, O_RDWR | O_NONBLOCK | O_ASYNC);
>> - if (fd == -1) {
>> - g_critical("error opening channel: %s", strerror(errno));
>> - exit(EXIT_FAILURE);
>> - }
>> - ret = conn_channel_add(s, fd);
>> - if (ret) {
>> - g_critical("error adding channel to main loop");
>> - exit(EXIT_FAILURE);
>> - }
>> - } else if (strcmp(s->method, "isa-serial") == 0) {
>> - fd = qemu_open(s->path, O_RDWR | O_NOCTTY);
>> - 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 |= QGA_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 = conn_channel_add(s, fd);
>> - if (ret) {
>> - g_error("error adding channel to main loop");
>> - }
>> - } else if (strcmp(s->method, "unix-listen") == 0) {
>> - fd = unix_listen(s->path, NULL, strlen(s->path));
>> - if (fd == -1) {
>> - g_critical("error opening path: %s", strerror(errno));
>> - exit(EXIT_FAILURE);
>> - }
>> - ret = listen_channel_add(s, fd, true);
>> - if (ret) {
>> - g_critical("error binding/listening to specified socket");
>> - exit(EXIT_FAILURE);
>> - }
>> + 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", s->method);
>> - exit(EXIT_FAILURE);
>> + g_critical("unsupported channel method/type: %s", method);
>> + return false;
>> }
>>
>> - json_message_parser_init(&s->parser, process_event);
>> - s->main_loop = g_main_loop_new(NULL, 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;
>> }
>>
>> int main(int argc, char **argv)
>> @@ -689,9 +518,6 @@ int main(int argc, char **argv)
>>
>> ga_set_support_level(default_support_level);
>> s = g_malloc0(sizeof(GAState));
>> - s->conn_channel = NULL;
>> - s->path = path;
>> - s->method = method;
>> s->log_file = log_file;
>> s->log_level = log_level;
>> g_log_set_default_handler(ga_log, s);
>> @@ -700,15 +526,31 @@ int main(int argc, char **argv)
>> 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;
>> + if (!register_signal_handlers()) {
>> + g_critical("failed to register signal handlers");
>> + goto out_bad;
>> + }
>>
>> - init_guest_agent(ga_state);
>> - register_signal_handlers();
>> -
>> + 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;
>> + }
>> g_main_loop_run(ga_state->main_loop);
>>
>> ga_command_state_cleanup_all(ga_state->command_state);
>> - unlink(pidfile);
>> + ga_channel_free(ga_state->channel);
>>
>> + if (daemonize) {
>> + unlink(pidfile);
>> + }
>> return 0;
>> +
>> +out_bad:
>> + if (daemonize) {
>> + unlink(pidfile);
>> + }
>> + return EXIT_FAILURE;
>> }
>> diff --git a/qga/channel-posix.c b/qga/channel-posix.c
>> new file mode 100644
>> index 0000000..40f7658
>> --- /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.h b/qga/channel.h
>> new file mode 100644
>> index 0000000..3704ea9
>> --- /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-core.h b/qga/guest-agent-core.h
>> index 7327e73..891ed06 100644
>> --- a/qga/guest-agent-core.h
>> +++ b/qga/guest-agent-core.h
>> @@ -22,7 +22,7 @@
>> #define QGA_SUPPORT_LEVEL_MAJOR_MIN 1
>> #define QGA_SUPPORT_LEVEL_MINOR_MIN 0
>> #define QGA_SUPPORT_LEVEL_MICRO_MIN 0
>> -#define QGA_READ_COUNT_DEFAULT 4<< 10
>> +#define QGA_READ_COUNT_DEFAULT 4096
>>
>> typedef struct GAState GAState;
>> typedef struct GACommandState GACommandState;
>
>
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [Qemu-devel] [PATCH 1/7] qemu-ga: move channel/transport functionality into wrapper class
2012-01-23 20:24 ` Anthony Liguori
2012-01-23 20:39 ` Anthony Liguori
@ 2012-01-23 20:56 ` Michael Roth
1 sibling, 0 replies; 11+ messages in thread
From: Michael Roth @ 2012-01-23 20:56 UTC (permalink / raw)
To: Anthony Liguori; +Cc: ghammer, qemu-devel
On 01/23/2012 02:24 PM, Anthony Liguori wrote:
> On 01/23/2012 08:21 AM, Michael Roth wrote:
>> This is monstly in preparation for the win32 port, which won't use
>> GIO channels for reasons that will be made clearer later. Here the
>> GAChannel class is just a loose wrapper around GIOChannel
>> calls/callbacks, but we also roll the logic/configuration for handling
>> FDs and also for managing unix socket connections into it, which makes
>> the abstraction much more complete and further aids in the win32 port
>> since isa-serial/unix-listen will not be supported initially.
>>
>> There's also a bit of refactoring in the main logic to consolidate the
>> exit paths so we can do common cleanup for things like pid files, which
>> weren't always cleaned up previously.
>>
>> Signed-off-by: Michael Roth<mdroth@linux.vnet.ibm.com>
>
> This doesn't apply very well. Can you rebase?
I think the issue is that it depends on the 4 patches I mentioned that
are currently floating around on the list (the first 2 for patchability,
the latter 2 for functional/performance issues):
[PATCH] qemu-ga: Add schema documentation for types
[PATCH] qemu-ga: add guest-set-support-level command
[PATCH] main-loop: Fix SetEvent() on uninitialized handle on win32
[PATCH] main-loop: For tools, initialize timers as part of
qemu_init_main_loop()
I think the first 2 are more likely to get merged along with Luiz's
guest-suspend series, so I was hesitant to re-include them here as 1
unit (since that would break Luiz's patches).
The git repo has the whole series though. Can also resubmit in a
different form if preferred.
>
> Regards,
>
> Anthony Liguori
>
>> ---
>> Makefile.objs | 1 +
>> qemu-ga.c | 306 ++++++++++++------------------------------------
>> qga/channel-posix.c | 246 ++++++++++++++++++++++++++++++++++++++
>> qga/channel.h | 33 +++++
>> qga/guest-agent-core.h | 2 +-
>> 5 files changed, 355 insertions(+), 233 deletions(-)
>> create mode 100644 qga/channel-posix.c
>> create mode 100644 qga/channel.h
>>
>> diff --git a/Makefile.objs b/Makefile.objs
>> index f94c881..72141cc 100644
>> --- a/Makefile.objs
>> +++ b/Makefile.objs
>> @@ -417,6 +417,7 @@ common-obj-y += qmp.o hmp.o
>> # guest agent
>>
>> qga-nested-y = guest-agent-commands.o guest-agent-command-state.o
>> +qga-nested-y += channel-posix.o
>> qga-obj-y = $(addprefix qga/, $(qga-nested-y))
>> qga-obj-y += qemu-ga.o qemu-sockets.o module.o qemu-option.o
>> qga-obj-$(CONFIG_WIN32) += oslib-win32.o
>> diff --git a/qemu-ga.c b/qemu-ga.c
>> index 8e5b075..5f89ab9 100644
>> --- a/qemu-ga.c
>> +++ b/qemu-ga.c
>> @@ -15,9 +15,7 @@
>> #include<stdbool.h>
>> #include<glib.h>
>> #include<getopt.h>
>> -#include<termios.h>
>> #include<syslog.h>
>> -#include "qemu_socket.h"
>> #include "json-streamer.h"
>> #include "json-parser.h"
>> #include "qint.h"
>> @@ -29,19 +27,15 @@
>> #include "error_int.h"
>> #include "qapi/qmp-core.h"
>> #include "qga-qapi-types.h"
>> +#include "qga/channel.h"
>>
>> #define QGA_VIRTIO_PATH_DEFAULT
>> "/dev/virtio-ports/org.qemu.guest_agent.0"
>> #define QGA_PIDFILE_DEFAULT "/var/run/qemu-ga.pid"
>> -#define QGA_BAUDRATE_DEFAULT B38400 /* for isa-serial channels */
>> -#define QGA_TIMEOUT_DEFAULT 30*1000 /* ms */
>>
>> struct GAState {
>> JSONMessageParser parser;
>> GMainLoop *main_loop;
>> - GIOChannel *conn_channel;
>> - GIOChannel *listen_channel;
>> - const char *path;
>> - const char *method;
>> + GAChannel *channel;
>> bool virtio; /* fastpath to check for virtio to deal with poll()
>> quirks */
>> GACommandState *command_state;
>> GLogLevelFlags log_level;
>> @@ -60,7 +54,7 @@ static void quit_handler(int sig)
>> }
>> }
>>
>> -static void register_signal_handlers(void)
>> +static gboolean register_signal_handlers(void)
>> {
>> struct sigaction sigact;
>> int ret;
>> @@ -71,12 +65,14 @@ static void register_signal_handlers(void)
>> ret = sigaction(SIGINT,&sigact, NULL);
>> if (ret == -1) {
>> g_error("error configuring signal handler: %s", strerror(errno));
>> - exit(EXIT_FAILURE);
>> + return false;
>> }
>> ret = sigaction(SIGTERM,&sigact, NULL);
>> if (ret == -1) {
>> g_error("error configuring signal handler: %s", strerror(errno));
>> + return false;
>> }
>> + return true;
>> }
>>
>> static void usage(const char *cmd)
>> @@ -101,8 +97,6 @@ static void usage(const char *cmd)
>> , cmd, QGA_VERSION, QGA_VIRTIO_PATH_DEFAULT, QGA_PIDFILE_DEFAULT);
>> }
>>
>> -static void conn_channel_close(GAState *s);
>> -
>> static GuestAgentSupportLevel ga_support_level;
>>
>> static int ga_cmp_support_levels(GuestAgentSupportLevel a,
>> @@ -250,40 +244,13 @@ fail:
>> exit(EXIT_FAILURE);
>> }
>>
>> -static int conn_channel_send_buf(GIOChannel *channel, const char *buf,
>> - gsize count)
>> +static int send_response(GAState *s, QObject *payload)
>> {
>> - GError *err = NULL;
>> - gsize written = 0;
>> - GIOStatus status;
>> -
>> - while (count) {
>> - status = g_io_channel_write_chars(channel, buf, count,&written,&err);
>> - g_debug("sending data, count: %d", (int)count);
>> - if (err != NULL) {
>> - g_warning("error sending newline: %s", err->message);
>> - return err->code;
>> - }
>> - if (status == G_IO_STATUS_ERROR || status == G_IO_STATUS_EOF) {
>> - return -EPIPE;
>> - }
>> -
>> - if (status == G_IO_STATUS_NORMAL) {
>> - count -= written;
>> - }
>> - }
>> -
>> - return 0;
>> -}
>> -
>> -static int conn_channel_send_payload(GIOChannel *channel, QObject
>> *payload)
>> -{
>> - int ret = 0;
>> const char *buf;
>> QString *payload_qstr;
>> - GError *err = NULL;
>> + GIOStatus status;
>>
>> - g_assert(payload&& channel);
>> + g_assert(payload&& s->channel);
>>
>> payload_qstr = qobject_to_json(payload);
>> if (!payload_qstr) {
>> @@ -292,24 +259,13 @@ static int conn_channel_send_payload(GIOChannel
>> *channel, QObject *payload)
>>
>> qstring_append_chr(payload_qstr, '\n');
>> buf = qstring_get_str(payload_qstr);
>> - ret = conn_channel_send_buf(channel, buf, strlen(buf));
>> - if (ret) {
>> - goto out_free;
>> - }
>> -
>> - g_io_channel_flush(channel,&err);
>> - if (err != NULL) {
>> - g_warning("error flushing payload: %s", err->message);
>> - ret = err->code;
>> - goto out_free;
>> - }
>> -
>> -out_free:
>> + status = ga_channel_write_all(s->channel, buf, strlen(buf));
>> QDECREF(payload_qstr);
>> - if (err) {
>> - g_error_free(err);
>> + if (status != G_IO_STATUS_NORMAL) {
>> + return -EIO;
>> }
>> - return ret;
>> +
>> + return 0;
>> }
>>
>> static void process_command(GAState *s, QDict *req)
>> @@ -321,9 +277,9 @@ static void process_command(GAState *s, QDict *req)
>> g_debug("processing command");
>> rsp = qmp_dispatch(QOBJECT(req));
>> if (rsp) {
>> - ret = conn_channel_send_payload(s->conn_channel, rsp);
>> + ret = send_response(s, rsp);
>> if (ret) {
>> - g_warning("error sending payload: %s", strerror(ret));
>> + g_warning("error sending response: %s", strerror(ret));
>> }
>> qobject_decref(rsp);
>> } else {
>> @@ -373,38 +329,42 @@ static void process_event(JSONMessageParser
>> *parser, QList *tokens)
>> qdict_put_obj(qdict, "error", error_get_qobject(err));
>> error_free(err);
>> }
>> - ret = conn_channel_send_payload(s->conn_channel, QOBJECT(qdict));
>> + ret = send_response(s, QOBJECT(qdict));
>> if (ret) {
>> - g_warning("error sending payload: %s", strerror(ret));
>> + g_warning("error sending error response: %s", strerror(ret));
>> }
>> }
>>
>> QDECREF(qdict);
>> }
>>
>> -static gboolean conn_channel_read(GIOChannel *channel, GIOCondition
>> condition,
>> - gpointer data)
>> +/* false return signals GAChannel to close the current client
>> connection */
>> +static gboolean channel_event_cb(GIOCondition condition, gpointer data)
>> {
>> GAState *s = data;
>> - gchar buf[1024];
>> + gchar buf[QGA_READ_COUNT_DEFAULT+1];
>> gsize count;
>> GError *err = NULL;
>> - memset(buf, 0, 1024);
>> - GIOStatus status = g_io_channel_read_chars(channel, buf, 1024,
>> -&count,&err);
>> + GIOStatus status = ga_channel_read(s->channel, buf,
>> QGA_READ_COUNT_DEFAULT,&count);
>> if (err != NULL) {
>> g_warning("error reading channel: %s", err->message);
>> - conn_channel_close(s);
>> g_error_free(err);
>> return false;
>> }
>> switch (status) {
>> case G_IO_STATUS_ERROR:
>> - g_warning("problem");
>> + 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;
>> + }
>> 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
>> @@ -413,180 +373,49 @@ static gboolean conn_channel_read(GIOChannel
>> *channel, GIOCondition condition,
>> usleep(100*1000);
>> }
>> return true;
>> - case G_IO_STATUS_EOF:
>> - g_debug("received EOF");
>> - conn_channel_close(s);
>> - if (s->virtio) {
>> - return true;
>> - }
>> - return false;
>> default:
>> g_warning("unknown channel read status, closing");
>> - conn_channel_close(s);
>> return false;
>> }
>> return true;
>> }
>>
>> -static int conn_channel_add(GAState *s, int fd)
>> +static gboolean channel_init(GAState *s, const gchar *method, const
>> gchar *path)
>> {
>> - GIOChannel *conn_channel;
>> - GError *err = NULL;
>> + GAChannelMethod channel_method;
>>
>> - g_assert(s&& !s->conn_channel);
>> - conn_channel = g_io_channel_unix_new(fd);
>> - g_assert(conn_channel);
>> - g_io_channel_set_encoding(conn_channel, NULL,&err);
>> - if (err != NULL) {
>> - g_warning("error setting channel encoding to binary");
>> - g_error_free(err);
>> - return -1;
>> + if (method == NULL) {
>> + method = "virtio-serial";
>> }
>> - g_io_add_watch(conn_channel, G_IO_IN | G_IO_HUP,
>> - conn_channel_read, s);
>> - s->conn_channel = conn_channel;
>> - return 0;
>> -}
>>
>> -static gboolean listen_channel_accept(GIOChannel *channel,
>> - GIOCondition condition, gpointer data)
>> -{
>> - GAState *s = data;
>> - g_assert(channel != NULL);
>> - int ret, conn_fd;
>> - bool accepted = false;
>> - struct sockaddr_un addr;
>> - socklen_t addrlen = sizeof(addr);
>> -
>> - conn_fd = qemu_accept(g_io_channel_unix_get_fd(s->listen_channel),
>> - (struct sockaddr *)&addr,&addrlen);
>> - if (conn_fd == -1) {
>> - g_warning("error converting fd to gsocket: %s", strerror(errno));
>> - goto out;
>> - }
>> - fcntl(conn_fd, F_SETFL, O_NONBLOCK);
>> - ret = conn_channel_add(s, conn_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 int listen_channel_add(GAState *s, int listen_fd, bool new)
>> -{
>> - if (new) {
>> - s->listen_channel = g_io_channel_unix_new(listen_fd);
>> - }
>> - g_io_add_watch(s->listen_channel, G_IO_IN,
>> - listen_channel_accept, s);
>> - return 0;
>> -}
>> -
>> -/* cleanup state for closed connection/session, start accepting new
>> - * connections if we're in listening mode
>> - */
>> -static void conn_channel_close(GAState *s)
>> -{
>> - if (strcmp(s->method, "unix-listen") == 0) {
>> - g_io_channel_shutdown(s->conn_channel, true, NULL);
>> - listen_channel_add(s, 0, false);
>> - } else if (strcmp(s->method, "virtio-serial") == 0) {
>> - /* we spin on EOF for virtio-serial, so back off a bit. also,
>> - * dont close the connection in this case, it'll resume normal
>> - * operation when another process connects to host chardev
>> - */
>> - usleep(100*1000);
>> - goto out_noclose;
>> - }
>> - g_io_channel_unref(s->conn_channel);
>> - s->conn_channel = NULL;
>> -out_noclose:
>> - return;
>> -}
>> -
>> -static void init_guest_agent(GAState *s)
>> -{
>> - struct termios tio;
>> - int ret, fd;
>> -
>> - if (s->method == NULL) {
>> - /* try virtio-serial as our default */
>> - s->method = "virtio-serial";
>> - }
>> -
>> - if (s->path == NULL) {
>> - if (strcmp(s->method, "virtio-serial") != 0) {
>> + if (path == NULL) {
>> + if (strcmp(method, "virtio-serial") != 0) {
>> g_critical("must specify a path for this channel");
>> - exit(EXIT_FAILURE);
>> + return false;
>> }
>> /* try the default path for the virtio-serial port */
>> - s->path = QGA_VIRTIO_PATH_DEFAULT;
>> + path = QGA_VIRTIO_PATH_DEFAULT;
>> }
>>
>> - if (strcmp(s->method, "virtio-serial") == 0) {
>> - s->virtio = true;
>> - fd = qemu_open(s->path, O_RDWR | O_NONBLOCK | O_ASYNC);
>> - if (fd == -1) {
>> - g_critical("error opening channel: %s", strerror(errno));
>> - exit(EXIT_FAILURE);
>> - }
>> - ret = conn_channel_add(s, fd);
>> - if (ret) {
>> - g_critical("error adding channel to main loop");
>> - exit(EXIT_FAILURE);
>> - }
>> - } else if (strcmp(s->method, "isa-serial") == 0) {
>> - fd = qemu_open(s->path, O_RDWR | O_NOCTTY);
>> - 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 |= QGA_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 = conn_channel_add(s, fd);
>> - if (ret) {
>> - g_error("error adding channel to main loop");
>> - }
>> - } else if (strcmp(s->method, "unix-listen") == 0) {
>> - fd = unix_listen(s->path, NULL, strlen(s->path));
>> - if (fd == -1) {
>> - g_critical("error opening path: %s", strerror(errno));
>> - exit(EXIT_FAILURE);
>> - }
>> - ret = listen_channel_add(s, fd, true);
>> - if (ret) {
>> - g_critical("error binding/listening to specified socket");
>> - exit(EXIT_FAILURE);
>> - }
>> + 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", s->method);
>> - exit(EXIT_FAILURE);
>> + g_critical("unsupported channel method/type: %s", method);
>> + return false;
>> }
>>
>> - json_message_parser_init(&s->parser, process_event);
>> - s->main_loop = g_main_loop_new(NULL, 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;
>> }
>>
>> int main(int argc, char **argv)
>> @@ -689,9 +518,6 @@ int main(int argc, char **argv)
>>
>> ga_set_support_level(default_support_level);
>> s = g_malloc0(sizeof(GAState));
>> - s->conn_channel = NULL;
>> - s->path = path;
>> - s->method = method;
>> s->log_file = log_file;
>> s->log_level = log_level;
>> g_log_set_default_handler(ga_log, s);
>> @@ -700,15 +526,31 @@ int main(int argc, char **argv)
>> 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;
>> + if (!register_signal_handlers()) {
>> + g_critical("failed to register signal handlers");
>> + goto out_bad;
>> + }
>>
>> - init_guest_agent(ga_state);
>> - register_signal_handlers();
>> -
>> + 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;
>> + }
>> g_main_loop_run(ga_state->main_loop);
>>
>> ga_command_state_cleanup_all(ga_state->command_state);
>> - unlink(pidfile);
>> + ga_channel_free(ga_state->channel);
>>
>> + if (daemonize) {
>> + unlink(pidfile);
>> + }
>> return 0;
>> +
>> +out_bad:
>> + if (daemonize) {
>> + unlink(pidfile);
>> + }
>> + return EXIT_FAILURE;
>> }
>> diff --git a/qga/channel-posix.c b/qga/channel-posix.c
>> new file mode 100644
>> index 0000000..40f7658
>> --- /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.h b/qga/channel.h
>> new file mode 100644
>> index 0000000..3704ea9
>> --- /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-core.h b/qga/guest-agent-core.h
>> index 7327e73..891ed06 100644
>> --- a/qga/guest-agent-core.h
>> +++ b/qga/guest-agent-core.h
>> @@ -22,7 +22,7 @@
>> #define QGA_SUPPORT_LEVEL_MAJOR_MIN 1
>> #define QGA_SUPPORT_LEVEL_MINOR_MIN 0
>> #define QGA_SUPPORT_LEVEL_MICRO_MIN 0
>> -#define QGA_READ_COUNT_DEFAULT 4<< 10
>> +#define QGA_READ_COUNT_DEFAULT 4096
>>
>> typedef struct GAState GAState;
>> typedef struct GACommandState GACommandState;
>
^ permalink raw reply [flat|nested] 11+ messages in thread
end of thread, other threads:[~2012-01-23 20:57 UTC | newest]
Thread overview: 11+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2012-01-23 14:21 [Qemu-devel] qemu-ga: add support for Windows Michael Roth
2012-01-23 14:21 ` [Qemu-devel] [PATCH 1/7] qemu-ga: move channel/transport functionality into wrapper class Michael Roth
2012-01-23 20:24 ` Anthony Liguori
2012-01-23 20:39 ` Anthony Liguori
2012-01-23 20:56 ` Michael Roth
2012-01-23 14:21 ` [Qemu-devel] [PATCH 2/7] qemu-ga: separate out common commands from posix-specific ones Michael Roth
2012-01-23 14:21 ` [Qemu-devel] [PATCH 3/7] qemu-ga: rename guest-agent-commands.c -> commands-posix.c Michael Roth
2012-01-23 14:21 ` [Qemu-devel] [PATCH 4/7] qemu-ga: fixes for win32 build of qemu-ga Michael Roth
2012-01-23 14:21 ` [Qemu-devel] [PATCH 5/7] qemu-ga: add initial win32 support Michael Roth
2012-01-23 14:21 ` [Qemu-devel] [PATCH 6/7] qemu-ga: add Windows service integration Michael Roth
2012-01-23 14:21 ` [Qemu-devel] [PATCH 7/7] qemu-ga: add win32 guest-shutdown command Michael Roth
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.