* [Qemu-devel] [PATCH v3][ 1/7] guest agent: worker thread class
2011-06-01 17:54 [Qemu-devel] [QAPI+QGA 3/3] QEMU Guest Agent (virtagent) Michael Roth
@ 2011-06-01 17:54 ` Michael Roth
2011-06-01 17:54 ` [Qemu-devel] [PATCH v3][ 2/7] guest agent: command state class Michael Roth
` (7 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Michael Roth @ 2011-06-01 17:54 UTC (permalink / raw)
To: qemu-devel; +Cc: aliguori, Jes.Sorensen, agl, mdroth, lcapitulino
Signed-off-by: Michael Roth <mdroth@linux.vnet.ibm.com>
---
qga/guest-agent-worker.c | 179 ++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 179 insertions(+), 0 deletions(-)
create mode 100644 qga/guest-agent-worker.c
diff --git a/qga/guest-agent-worker.c b/qga/guest-agent-worker.c
new file mode 100644
index 0000000..e5fc845
--- /dev/null
+++ b/qga/guest-agent-worker.c
@@ -0,0 +1,179 @@
+/*
+ * QEMU Guest Agent worker thread interfaces
+ *
+ * Copyright IBM Corp. 2011
+ *
+ * Authors:
+ * Michael Roth <mdroth@linux.vnet.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+#include <glib.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdbool.h>
+#include <pthread.h>
+#include <errno.h>
+#include <string.h>
+#include "error.h"
+#include "qga/guest-agent-core.h"
+
+struct GAWorker {
+ pthread_t thread;
+ ga_worker_func execute;
+ pthread_mutex_t input_mutex;
+ pthread_cond_t input_avail_cond;
+ void *input;
+ bool input_avail;
+ pthread_mutex_t output_mutex;
+ pthread_cond_t output_avail_cond;
+ void *output;
+ Error *output_error;
+ bool output_avail;
+};
+
+static void *worker_run(void *worker_p)
+{
+ GAWorker *worker = worker_p;
+ Error *err = NULL;
+ void *input = NULL, *output = NULL;
+
+ while (1) {
+ /* wait for input */
+ pthread_mutex_lock(&worker->input_mutex);
+ while (!worker->input_avail) {
+ pthread_cond_wait(&worker->input_avail_cond, &worker->input_mutex);
+ }
+ input = worker->input;
+ worker->input = NULL;
+ worker->input_avail = false;
+ pthread_mutex_unlock(&worker->input_mutex);
+
+ /* process input. input points to shared data, so if we ever add
+ * asynchronous dispatch, we'll need to copy the input instead
+ */
+ worker->execute(input, &output, &err);
+
+ /* signal waiters */
+ pthread_mutex_lock(&worker->output_mutex);
+ worker->output = output;
+ worker->output_error = err;
+ worker->output_avail = true;
+ pthread_cond_signal(&worker->output_avail_cond);
+ pthread_mutex_unlock(&worker->output_mutex);
+ }
+
+ return NULL;
+}
+
+static void ga_worker_set_input(GAWorker *worker, void *input)
+{
+ pthread_mutex_lock(&worker->input_mutex);
+
+ /* provide input for thread, and signal it */
+ worker->input = input;
+ worker->input_avail = true;
+ pthread_cond_signal(&worker->input_avail_cond);
+
+ pthread_mutex_unlock(&worker->input_mutex);
+}
+
+static bool ga_worker_get_output(GAWorker *worker, void **output, int timeout)
+{
+ struct timespec ts;
+ GTimeVal tv;
+ bool timed_out = false;
+ int ret;
+
+ pthread_mutex_lock(&worker->output_mutex);
+
+ while (!worker->output_avail) {
+ if (timeout > 0) {
+ g_get_current_time(&tv);
+ g_time_val_add(&tv, timeout * 1000);
+ ts.tv_sec = tv.tv_sec;
+ ts.tv_nsec = tv.tv_usec * 1000;
+ ret = pthread_cond_timedwait(&worker->output_avail_cond,
+ &worker->output_mutex, &ts);
+ if (ret == ETIMEDOUT) {
+ timed_out = true;
+ goto out;
+ }
+ } else {
+ ret = pthread_cond_wait(&worker->output_avail_cond,
+ &worker->output_mutex);
+ }
+ }
+
+ /* handle output from thread */
+ worker->output_avail = false;
+ *output = worker->output;
+ worker->output = NULL;
+
+out:
+ pthread_mutex_unlock(&worker->output_mutex);
+ return timed_out;
+}
+
+bool ga_worker_dispatch(GAWorker *worker, void *input, void *output,
+ int timeout, Error **errp)
+{
+ ga_worker_set_input(worker, input);
+ return ga_worker_get_output(worker, output, timeout);
+}
+
+static void ga_worker_start(GAWorker *worker)
+{
+ int ret;
+
+ pthread_cond_init(&worker->input_avail_cond, NULL);
+ pthread_cond_init(&worker->output_avail_cond, NULL);
+ pthread_mutex_init(&worker->input_mutex, NULL);
+ pthread_mutex_init(&worker->output_mutex, NULL);
+ worker->output_avail = false;
+ worker->input_avail = false;
+
+ ret = pthread_create(&worker->thread, NULL, worker_run, worker);
+ if (ret == -1) {
+ g_error("error: %s", strerror(errno));
+ }
+}
+
+static void ga_worker_stop(GAWorker *worker)
+{
+ int ret;
+ void *status;
+
+ ret = pthread_cancel(worker->thread);
+ if (ret == -1) {
+ g_error("pthread_cancel() failed: %s", strerror(errno));
+ }
+
+ ret = pthread_join(worker->thread, &status);
+ if (ret) {
+ g_error("pthread_join() failed: %s", strerror(ret));
+ }
+
+ pthread_mutex_destroy(&worker->input_mutex);
+ pthread_mutex_destroy(&worker->output_mutex);
+ pthread_cond_destroy(&worker->input_avail_cond);
+ pthread_cond_destroy(&worker->input_avail_cond);
+}
+
+GAWorker *ga_worker_new(ga_worker_func func)
+{
+ GAWorker *worker = g_malloc0(sizeof(GAWorker));
+
+ g_assert(func);
+ worker->execute = func;
+ ga_worker_start(worker);
+
+ return worker;
+}
+
+void ga_worker_cleanup(GAWorker *worker)
+{
+ ga_worker_stop(worker);
+ g_free(worker);
+}
--
1.7.0.4
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [Qemu-devel] [PATCH v3][ 2/7] guest agent: command state class
2011-06-01 17:54 [Qemu-devel] [QAPI+QGA 3/3] QEMU Guest Agent (virtagent) Michael Roth
2011-06-01 17:54 ` [Qemu-devel] [PATCH v3][ 1/7] guest agent: worker thread class Michael Roth
@ 2011-06-01 17:54 ` Michael Roth
2011-06-01 17:54 ` [Qemu-devel] [PATCH v3][ 3/7] guest agent: qemu-ga daemon Michael Roth
` (6 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Michael Roth @ 2011-06-01 17:54 UTC (permalink / raw)
To: qemu-devel; +Cc: aliguori, Jes.Sorensen, agl, mdroth, lcapitulino
Signed-off-by: Michael Roth <mdroth@linux.vnet.ibm.com>
---
qga/guest-agent-command-state.c | 73 +++++++++++++++++++++++++++++++++++++++
1 files changed, 73 insertions(+), 0 deletions(-)
create mode 100644 qga/guest-agent-command-state.c
diff --git a/qga/guest-agent-command-state.c b/qga/guest-agent-command-state.c
new file mode 100644
index 0000000..997355e
--- /dev/null
+++ b/qga/guest-agent-command-state.c
@@ -0,0 +1,73 @@
+/*
+ * QEMU Guest Agent command state interfaces
+ *
+ * Copyright IBM Corp. 2011
+ *
+ * Authors:
+ * Michael Roth <mdroth@linux.vnet.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+#include <glib.h>
+#include "guest-agent.h"
+
+struct GACommandState {
+ GSList *groups;
+};
+
+typedef struct GACommandGroup {
+ void (*init)(void);
+ void (*cleanup)(void);
+} GACommandGroup;
+
+/* handle init/cleanup for stateful guest commands */
+
+void ga_command_state_add(GACommandState *cs,
+ void (*init)(void),
+ void (*cleanup)(void))
+{
+ GACommandGroup *cg = g_malloc0(sizeof(GACommandGroup));
+ cg->init = init;
+ cg->cleanup = cleanup;
+ cs->groups = g_slist_append(cs->groups, cg);
+}
+
+static void ga_command_group_init(gpointer opaque, gpointer unused)
+{
+ GACommandGroup *cg = opaque;
+
+ g_assert(cg);
+ if (cg->init) {
+ cg->init();
+ }
+}
+
+void ga_command_state_init_all(GACommandState *cs)
+{
+ g_assert(cs);
+ g_slist_foreach(cs->groups, ga_command_group_init, NULL);
+}
+
+static void ga_command_group_cleanup(gpointer opaque, gpointer unused)
+{
+ GACommandGroup *cg = opaque;
+
+ g_assert(cg);
+ if (cg->cleanup) {
+ cg->cleanup();
+ }
+}
+
+void ga_command_state_cleanup_all(GACommandState *cs)
+{
+ g_assert(cs);
+ g_slist_foreach(cs->groups, ga_command_group_cleanup, NULL);
+}
+
+GACommandState *ga_command_state_new(void)
+{
+ GACommandState *cs = g_malloc0(sizeof(GACommandState));
+ cs->groups = NULL;
+ return cs;
+}
--
1.7.0.4
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [Qemu-devel] [PATCH v3][ 3/7] guest agent: qemu-ga daemon
2011-06-01 17:54 [Qemu-devel] [QAPI+QGA 3/3] QEMU Guest Agent (virtagent) Michael Roth
2011-06-01 17:54 ` [Qemu-devel] [PATCH v3][ 1/7] guest agent: worker thread class Michael Roth
2011-06-01 17:54 ` [Qemu-devel] [PATCH v3][ 2/7] guest agent: command state class Michael Roth
@ 2011-06-01 17:54 ` Michael Roth
2011-06-01 17:54 ` [Qemu-devel] [PATCH v3][ 4/7] guest agent: add error class for QERR_QGA_LOGGING_FAILED Michael Roth
` (5 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Michael Roth @ 2011-06-01 17:54 UTC (permalink / raw)
To: qemu-devel; +Cc: aliguori, Jes.Sorensen, agl, mdroth, lcapitulino
This is the actual guest daemon, it listens for requests over a
virtio-serial/isa-serial/unix socket channel and routes them through
to dispatch routines, and writes the results back to the channel in
a manner similar to QMP.
A shorthand invocation:
qemu-ga -d
Is equivalent to:
qemu-ga -c virtio-serial -p /dev/virtio-ports/org.qemu.guest_agent \
-p /var/run/qemu-guest-agent.pid -d
Signed-off-by: Michael Roth <mdroth@linux.vnet.ibm.com>
---
qemu-ga.c | 635 ++++++++++++++++++++++++++++++++++++++++++++++++
qga/guest-agent-core.h | 39 +++
2 files changed, 674 insertions(+), 0 deletions(-)
create mode 100644 qemu-ga.c
create mode 100644 qga/guest-agent-core.h
diff --git a/qemu-ga.c b/qemu-ga.c
new file mode 100644
index 0000000..0438e76
--- /dev/null
+++ b/qemu-ga.c
@@ -0,0 +1,635 @@
+/*
+ * QEMU Guest Agent
+ *
+ * Copyright IBM Corp. 2011
+ *
+ * Authors:
+ * Adam Litke <aglitke@linux.vnet.ibm.com>
+ * Michael Roth <mdroth@linux.vnet.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdbool.h>
+#include <glib.h>
+#include <gio/gio.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"
+#include "qjson.h"
+#include "qga/guest-agent-core.h"
+#include "qga-qmp-commands.h"
+#include "module.h"
+
+#define QGA_VIRTIO_PATH_DEFAULT "/dev/virtio-ports/org.qemu.guest_agent"
+#define QGA_PIDFILE_DEFAULT "/var/run/qemu-va.pid"
+#define QGA_BAUDRATE_DEFAULT B38400 /* for isa-serial channels */
+#define QGA_TIMEOUT_DEFAULT 30*1000 /* ms */
+
+struct GAState {
+ const char *proxy_path;
+ JSONMessageParser parser;
+ GMainLoop *main_loop;
+ guint conn_id;
+ GSocket *conn_sock;
+ GIOChannel *conn_channel;
+ guint listen_id;
+ GSocket *listen_sock;
+ GIOChannel *listen_channel;
+ const char *path;
+ const char *method;
+ bool virtio; /* fastpath to check for virtio to deal with poll() quirks */
+ GACommandState *command_state;
+ GAWorker *worker;
+ int timeout_ms;
+ GLogLevelFlags log_level;
+ FILE *log_file;
+ bool logging_enabled;
+};
+
+static void usage(const char *cmd)
+{
+ printf(
+"Usage: %s -c <channel_opts>\n"
+"QEMU Guest Agent %s\n"
+"\n"
+" -c, --channel channel method: one of unix-connect, virtio-serial, or\n"
+" isa-serial (virtio-serial is the default)\n"
+" -p, --path channel path (%s is the default for virtio-serial)\n"
+" -l, --logfile set logfile path, logs to stderr by default\n"
+" -f, --pidfile specify pidfile (default is %s)\n"
+" -v, --verbose log extra debugging information\n"
+" -V, --version print version information and exit\n"
+" -d, --daemonize become a daemon\n"
+" -h, --help display this help and exit\n"
+"\n"
+"Report bugs to <mdroth@linux.vnet.ibm.com>\n"
+ , cmd, QGA_VERSION, QGA_VIRTIO_PATH_DEFAULT, QGA_PIDFILE_DEFAULT);
+}
+
+static void conn_channel_close(GAState *s);
+
+static const char *ga_log_level_str(GLogLevelFlags level)
+{
+ switch (level & G_LOG_LEVEL_MASK) {
+ case G_LOG_LEVEL_ERROR:
+ return "error";
+ case G_LOG_LEVEL_CRITICAL:
+ return "critical";
+ case G_LOG_LEVEL_WARNING:
+ return "warning";
+ case G_LOG_LEVEL_MESSAGE:
+ return "message";
+ case G_LOG_LEVEL_INFO:
+ return "info";
+ case G_LOG_LEVEL_DEBUG:
+ return "debug";
+ default:
+ return "user";
+ }
+}
+
+bool ga_logging_enabled(GAState *s)
+{
+ return s->logging_enabled;
+}
+
+static void ga_log(const gchar *domain, GLogLevelFlags level,
+ const gchar *msg, gpointer opaque)
+{
+ GAState *s = opaque;
+ GTimeVal time;
+ const char *level_str = ga_log_level_str(level);
+
+ if (!ga_logging_enabled(s)) {
+ return;
+ }
+
+ level &= G_LOG_LEVEL_MASK;
+ if (g_strcmp0(domain, "syslog") == 0) {
+ syslog(LOG_INFO, "%s: %s", level_str, msg);
+ } else if (level & s->log_level) {
+ g_get_current_time(&time);
+ fprintf(s->log_file,
+ "%lu.%lu: %s: %s\n", time.tv_sec, time.tv_usec, level_str, msg);
+ fflush(s->log_file);
+ }
+}
+
+int ga_get_timeout(GAState *s)
+{
+ return s->timeout_ms;
+}
+
+static void become_daemon(const char *pidfile)
+{
+ pid_t pid, sid;
+ int pidfd;
+ char *pidstr = NULL;
+
+ pid = fork();
+ if (pid < 0) {
+ exit(EXIT_FAILURE);
+ }
+ if (pid > 0) {
+ exit(EXIT_SUCCESS);
+ }
+
+ pidfd = open(pidfile, O_CREAT|O_RDWR, S_IRUSR|S_IWUSR);
+ if (!pidfd || lockf(pidfd, F_TLOCK, 0)) {
+ g_error("Cannot lock pid file");
+ }
+
+ if (ftruncate(pidfd, 0) || lseek(pidfd, 0, SEEK_SET)) {
+ g_critical("Cannot truncate pid file");
+ goto fail;
+ }
+ if (asprintf(&pidstr, "%d", getpid()) == -1) {
+ g_critical("Cannot allocate memory");
+ goto fail;
+ }
+ if (write(pidfd, pidstr, strlen(pidstr)) != strlen(pidstr)) {
+ g_critical("Failed to write pid file");
+ goto fail;
+ }
+
+ umask(0);
+ sid = setsid();
+ if (sid < 0) {
+ goto fail;
+ }
+ if ((chdir("/")) < 0) {
+ goto fail;
+ }
+
+ close(STDIN_FILENO);
+ close(STDOUT_FILENO);
+ close(STDERR_FILENO);
+ return;
+
+fail:
+ if (pidstr) {
+ free(pidstr);
+ }
+ unlink(pidfile);
+ g_error("failed to daemonize");
+}
+
+static int conn_channel_send_payload(GIOChannel *channel, QObject *payload)
+{
+ gsize count, written = 0;
+ int ret;
+ const char *buf;
+ QString *payload_qstr;
+ GIOStatus status;
+ GError *err = NULL;
+
+ if (!payload || !channel) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ payload_qstr = qobject_to_json(payload);
+ if (!payload_qstr) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ buf = qstring_get_str(payload_qstr);
+ count = strlen(buf);
+
+ while (count) {
+ g_debug("sending data, count: %d", (int)count);
+ status = g_io_channel_write_chars(channel, buf, count, &written, &err);
+ if (err != NULL) {
+ g_warning("error sending payload: %s", err->message);
+ ret = err->code;
+ goto out_free;
+ }
+ if (status == G_IO_STATUS_NORMAL) {
+ count -= written;
+ } else if (status == G_IO_STATUS_ERROR || status == G_IO_STATUS_EOF) {
+ ret = -EPIPE;
+ goto out_free;
+ }
+ }
+
+ status = g_io_channel_write_chars(channel, (char *)"\n", 1, &written, &err);
+ if (err != NULL) {
+ g_warning("error sending newline: %s", err->message);
+ ret = err->code;
+ goto out_free;
+ } else if (status == G_IO_STATUS_ERROR || status == G_IO_STATUS_EOF) {
+ ret = -EPIPE;
+ 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;
+ }
+
+ ret = 0;
+
+out_free:
+ QDECREF(payload_qstr);
+out:
+ return ret;
+}
+
+static void process_command_worker(void *input, void *output, Error **errp)
+{
+ QDict *req = input;
+ QObject **rsp = output;
+
+ g_assert(req && rsp);
+ *rsp = qmp_dispatch(QOBJECT(req));
+}
+
+static void process_command(GAState *s, QDict *req)
+{
+ QObject *rsp = NULL;
+ Error *err = NULL;
+ bool timeout;
+ int ret;
+
+ g_assert(req);
+ g_debug("processing command");
+ timeout = ga_worker_dispatch(s->worker, req, &rsp, s->timeout_ms, &err);
+ if (timeout) {
+ g_warning("command timed out");
+ } else if (rsp) {
+ if (err) {
+ g_warning("command failed: %s", error_get_pretty(err));
+ }
+ g_debug("response: %s", qstring_get_str(qobject_to_json(rsp)));
+ ret = conn_channel_send_payload(s->conn_channel, rsp);
+ if (ret) {
+ g_warning("error sending payload: %s", strerror(ret));
+ }
+ qobject_decref(rsp);
+ } else {
+ g_warning("error getting response");
+ if (err) {
+ g_warning("dispatch failed: %s", error_get_pretty(err));
+ }
+ }
+}
+
+/* handle requests/control events coming in over the channel */
+static void process_event(JSONMessageParser *parser, QList *tokens)
+{
+ GAState *s = container_of(parser, GAState, parser);
+ QObject *obj;
+ QDict *qdict;
+ Error *err = NULL;
+
+ g_assert(s && parser);
+
+ g_debug("process_event: called");
+ obj = json_parser_parse_err(tokens, NULL, &err);
+ if (!obj || qobject_type(obj) != QTYPE_QDICT) {
+ g_warning("failed to parse event");
+ return;
+ } else {
+ g_debug("parse successful");
+ qdict = qobject_to_qdict(obj);
+ g_assert(qdict);
+ }
+
+ /* handle host->guest commands */
+ if (qdict_haskey(qdict, "execute")) {
+ process_command(s, qdict);
+ } else {
+ g_warning("unrecognized payload format, ignoring");
+ }
+
+ QDECREF(qdict);
+}
+
+static gboolean conn_channel_read(GIOChannel *channel, GIOCondition condition,
+ gpointer data)
+{
+ GAState *s = data;
+ gchar buf[1024];
+ gsize count;
+ GError *err = NULL;
+ memset(buf, 0, 1024);
+ GIOStatus status = g_io_channel_read_chars(channel, buf, 1024,
+ &count, &err);
+ if (err != NULL) {
+ g_warning("error reading channel: %s", err->message);
+ conn_channel_close(s);
+ return false;
+ }
+ switch (status) {
+ case G_IO_STATUS_ERROR:
+ g_warning("problem");
+ return false;
+ case G_IO_STATUS_NORMAL:
+ g_debug("read data, count: %d, data: %s", (int)count, buf);
+ json_message_parser_feed(&s->parser, (char *)buf, (int)count);
+ case G_IO_STATUS_AGAIN:
+ /* virtio causes us to spin here when no process is attached to
+ * host-side chardev. sleep a bit to mitigate this
+ */
+ if (s->virtio) {
+ usleep(100*1000);
+ }
+ return true;
+ 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)
+{
+ GIOChannel *conn_channel;
+ guint conn_id;
+ GError *err = NULL;
+
+ 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");
+ return -1;
+ }
+ conn_id = g_io_add_watch(conn_channel, G_IO_IN | G_IO_HUP,
+ conn_channel_read, s);
+ if (err != NULL) {
+ g_warning("error adding io watch: %s", err->message);
+ return -1;
+ }
+ s->conn_channel = conn_channel;
+ s->conn_id = conn_id;
+ return 0;
+}
+
+static gboolean listen_channel_accept(GIOChannel *channel,
+ GIOCondition condition, gpointer data)
+{
+ GAState *s = data;
+ GError *err = NULL;
+ g_assert(channel != NULL);
+ int ret;
+ bool accepted = false;
+
+ s->conn_sock = g_socket_accept(s->listen_sock, NULL, &err);
+ if (err != NULL) {
+ g_warning("error converting fd to gsocket: %s", err->message);
+ goto out;
+ }
+ ret = conn_channel_add(s, g_socket_get_fd(s->conn_sock));
+ 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, listen_fd=0
+ * indicates we should use the existing s->listen_channel
+ */
+static int listen_channel_add(GAState *s, int listen_fd)
+{
+ GError *err = NULL;
+ guint listen_id;
+
+ if (listen_fd) {
+ s->listen_channel = g_io_channel_unix_new(listen_fd);
+ if (s->listen_sock) {
+ g_object_unref(s->listen_sock);
+ }
+ s->listen_sock = g_socket_new_from_fd(listen_fd, &err);
+ if (err != NULL) {
+ g_warning("error converting fd to gsocket: %s", err->message);
+ return -1;
+ }
+ }
+ listen_id = g_io_add_watch(s->listen_channel, G_IO_IN,
+ listen_channel_accept, s);
+ if (err != NULL) {
+ g_warning("error adding io watch: %s", err->message);
+ return -1;
+ }
+ 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);
+ g_object_unref(s->conn_sock);
+ s->conn_sock = NULL;
+ listen_channel_add(s, 0);
+ } 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;
+ s->conn_id = 0;
+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) {
+ g_error("must specify a path for this channel");
+ }
+ /* try the default path for the virtio-serial port */
+ s->path = QGA_VIRTIO_PATH_DEFAULT;
+ }
+
+ if (strcmp(s->method, "virtio-serial") == 0) {
+ s->virtio = true;
+ fd = qemu_open(s->path, O_RDWR);
+ if (fd == -1) {
+ g_error("error opening channel: %s", strerror(errno));
+ }
+ ret = fcntl(fd, F_GETFL);
+ if (ret < 0) {
+ g_error("error getting channel flags: %s", strerror(errno));
+ }
+ ret = fcntl(fd, F_SETFL, ret | O_NONBLOCK | O_ASYNC);
+ if (ret < 0) {
+ g_error("error setting channel flags: %s", strerror(errno));
+ }
+ ret = conn_channel_add(s, fd);
+ if (ret) {
+ g_error("error adding channel to main loop");
+ }
+ } else if (strcmp(s->method, "isa-serial") == 0) {
+ fd = qemu_open(s->path, O_RDWR | O_NOCTTY);
+ if (fd == -1) {
+ g_error("error opening channel: %s", strerror(errno));
+ }
+ 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_error("error opening path: %s", strerror(errno));
+ }
+ ret = listen_channel_add(s, fd);
+ if (ret) {
+ g_error("error binding/listening to specified socket");
+ }
+ } else {
+ g_error("unsupported channel method/type: %s", s->method);
+ }
+
+ json_message_parser_init(&s->parser, process_event);
+ s->main_loop = g_main_loop_new(NULL, false);
+}
+
+int main(int argc, char **argv)
+{
+ const char *sopt = "hVvdc:p:l:f:";
+ const char *method = NULL, *path = NULL, *pidfile = QGA_PIDFILE_DEFAULT;
+ struct option lopt[] = {
+ { "help", 0, NULL, 'h' },
+ { "version", 0, NULL, 'V' },
+ { "logfile", 0, NULL, 'l' },
+ { "pidfile", 0, NULL, 'f' },
+ { "verbose", 0, NULL, 'v' },
+ { "channel", 0, NULL, 'c' },
+ { "path", 0, NULL, 'p' },
+ { "daemonize", 0, NULL, 'd' },
+ { NULL, 0, NULL, 0 }
+ };
+ int opt_ind = 0, ch, daemonize = 0;
+ GLogLevelFlags log_level = G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL;
+ FILE *log_file = stderr;
+ GAState *s;
+
+ g_type_init();
+ g_thread_init(NULL);
+
+ while ((ch = getopt_long(argc, argv, sopt, lopt, &opt_ind)) != -1) {
+ switch (ch) {
+ case 'c':
+ method = optarg;
+ break;
+ case 'p':
+ path = optarg;
+ break;
+ case 'l':
+ log_file = fopen(optarg, "a");
+ if (!log_file) {
+ g_error("unable to open specified log file: %s",
+ strerror(errno));
+ }
+ break;
+ case 'f':
+ pidfile = optarg;
+ break;
+ case 'v':
+ /* enable all log levels */
+ log_level = G_LOG_LEVEL_MASK;
+ break;
+ case 'V':
+ printf("QEMU Guest Agent %s\n", QGA_VERSION);
+ return 0;
+ case 'd':
+ daemonize = 1;
+ break;
+ case 'h':
+ usage(argv[0]);
+ return 0;
+ case '?':
+ g_error("Unknown option, try '%s --help' for more information.",
+ argv[0]);
+ }
+ }
+
+ if (daemonize) {
+ g_debug("starting daemon");
+ become_daemon(pidfile);
+ }
+
+ s = g_malloc0(sizeof(GAState));
+ s->conn_id = 0;
+ s->conn_channel = NULL;
+ s->path = path;
+ s->method = method;
+ s->command_state = ga_command_state_new();
+ ga_command_state_init(s, s->command_state);
+ ga_command_state_init_all(s->command_state);
+ s->worker = ga_worker_new(process_command_worker);
+ s->timeout_ms = QGA_TIMEOUT_DEFAULT;
+ s->log_file = log_file;
+ s->log_level = log_level;
+ g_log_set_default_handler(ga_log, s);
+ g_log_set_fatal_mask(NULL, G_LOG_LEVEL_ERROR);
+ s->logging_enabled = true;
+
+ module_call_init(MODULE_INIT_QAPI);
+ init_guest_agent(s);
+
+ g_main_loop_run(s->main_loop);
+
+ ga_command_state_cleanup_all(s->command_state);
+ ga_worker_cleanup(s->worker);
+
+ return 0;
+}
diff --git a/qga/guest-agent-core.h b/qga/guest-agent-core.h
new file mode 100644
index 0000000..e9bef27
--- /dev/null
+++ b/qga/guest-agent-core.h
@@ -0,0 +1,39 @@
+/*
+ * QEMU Guest Agent core declarations
+ *
+ * Copyright IBM Corp. 2011
+ *
+ * Authors:
+ * Adam Litke <aglitke@linux.vnet.ibm.com>
+ * Michael Roth <mdroth@linux.vnet.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+#include "qapi/qmp-core.h"
+#include "qemu-common.h"
+#include "qdict.h"
+
+#define QGA_VERSION "1.0"
+
+typedef struct GAState GAState;
+typedef struct GACommandState GACommandState;
+typedef struct GAWorker GAWorker;
+typedef void (*ga_worker_func)(void *input, void *output, Error **errp);
+
+QObject *qga_dispatch(QObject *obj, Error **errp);
+void ga_command_state_init(GAState *s, GACommandState *cs);
+void ga_command_state_add(GACommandState *cs,
+ void (*init)(void),
+ void (*cleanup)(void));
+void ga_command_state_init_all(GACommandState *cs);
+void ga_command_state_cleanup_all(GACommandState *cs);
+GACommandState *ga_command_state_new(void);
+GAWorker *ga_worker_new(ga_worker_func func);
+void ga_worker_cleanup(GAWorker *worker);
+bool ga_worker_dispatch(GAWorker *worker, void *input, void *output,
+ int timeout, Error **errp);
+int ga_get_timeout(GAState *s);
+bool ga_logging_enabled(GAState *s);
+void ga_disable_logging(GAState *s);
+void ga_enable_logging(GAState *s);
--
1.7.0.4
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [Qemu-devel] [PATCH v3][ 4/7] guest agent: add error class for QERR_QGA_LOGGING_FAILED
2011-06-01 17:54 [Qemu-devel] [QAPI+QGA 3/3] QEMU Guest Agent (virtagent) Michael Roth
` (2 preceding siblings ...)
2011-06-01 17:54 ` [Qemu-devel] [PATCH v3][ 3/7] guest agent: qemu-ga daemon Michael Roth
@ 2011-06-01 17:54 ` Michael Roth
2011-06-01 17:54 ` [Qemu-devel] [PATCH v3][ 5/7] guest agent: add guest agent RPCs/commands Michael Roth
` (4 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Michael Roth @ 2011-06-01 17:54 UTC (permalink / raw)
To: qemu-devel; +Cc: aliguori, Jes.Sorensen, agl, mdroth, lcapitulino
Signed-off-by: Michael Roth <mdroth@linux.vnet.ibm.com>
---
qerror.c | 4 ++++
qerror.h | 3 +++
2 files changed, 7 insertions(+), 0 deletions(-)
diff --git a/qerror.c b/qerror.c
index c18641f..059981b 100644
--- a/qerror.c
+++ b/qerror.c
@@ -209,6 +209,10 @@ static const QErrorStringTable qerror_table[] = {
.error_fmt = QERR_VNC_SERVER_FAILED,
.desc = "Could not start VNC server on %(target)",
},
+ {
+ .error_fmt = QERR_QGA_LOGGING_FAILED,
+ .desc = "failed to write log statement due to logging being disabled",
+ },
{}
};
diff --git a/qerror.h b/qerror.h
index 8b971fd..22e1b09 100644
--- a/qerror.h
+++ b/qerror.h
@@ -178,4 +178,7 @@ QError *qobject_to_qerror(const QObject *obj);
#define QERR_FEATURE_DISABLED \
"{ 'class': 'FeatureDisabled', 'data': { 'name': %s } }"
+#define QERR_QGA_LOGGING_FAILED \
+ "{ 'class': 'QgaLoggingFailed', 'data': {} }"
+
#endif /* QERROR_H */
--
1.7.0.4
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [Qemu-devel] [PATCH v3][ 5/7] guest agent: add guest agent RPCs/commands
2011-06-01 17:54 [Qemu-devel] [QAPI+QGA 3/3] QEMU Guest Agent (virtagent) Michael Roth
` (3 preceding siblings ...)
2011-06-01 17:54 ` [Qemu-devel] [PATCH v3][ 4/7] guest agent: add error class for QERR_QGA_LOGGING_FAILED Michael Roth
@ 2011-06-01 17:54 ` Michael Roth
2011-06-01 17:54 ` [Qemu-devel] [PATCH v3][ 6/7] guest agent: add guest agent commands schema file Michael Roth
` (3 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Michael Roth @ 2011-06-01 17:54 UTC (permalink / raw)
To: qemu-devel; +Cc: aliguori, Jes.Sorensen, agl, mdroth, lcapitulino
This adds the initial set of QMP/QAPI commands provided by the guest
agent:
guest-sync
guest-ping
guest-info
guest-file-open
guest-file-read
guest-file-write
guest-file-seek
guest-file-close
guest-fsfreeze-freeze
guest-fsfreeze-thaw
guest-fsfreeze-status
The input/output specification for these commands are documented in the
schema.
Signed-off-by: Michael Roth <mdroth@linux.vnet.ibm.com>
---
qga/guest-agent-commands.c | 497 ++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 497 insertions(+), 0 deletions(-)
create mode 100644 qga/guest-agent-commands.c
diff --git a/qga/guest-agent-commands.c b/qga/guest-agent-commands.c
new file mode 100644
index 0000000..88865ee
--- /dev/null
+++ b/qga/guest-agent-commands.c
@@ -0,0 +1,497 @@
+/*
+ * QEMU Guest Agent commands
+ *
+ * Copyright IBM Corp. 2011
+ *
+ * Authors:
+ * Michael Roth <mdroth@linux.vnet.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include <glib.h>
+#include <mntent.h>
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <linux/fs.h>
+//#include "guest-agent.h"
+#include "qga/guest-agent-core.h"
+#include "qga-qmp-commands.h"
+
+static GAState *ga_state;
+
+static bool logging_enabled(void)
+{
+ return ga_logging_enabled(ga_state);
+}
+
+static void disable_logging(void)
+{
+ ga_disable_logging(ga_state);
+}
+
+static void enable_logging(void)
+{
+ ga_enable_logging(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 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));
+
+ info->version = g_strdup(QGA_VERSION);
+ info->timeout_ms = ga_get_timeout(ga_state);
+
+ return info;
+}
+
+void qmp_guest_shutdown(const char *shutdown_mode, Error **err)
+{
+ int ret;
+ const char *shutdown_flag;
+
+ if (!logging_enabled()) {
+ error_set(err, QERR_QGA_LOGGING_FAILED);
+ return;
+ }
+
+ slog("guest-shutdown called, shutdown_mode: %s", shutdown_mode);
+ if (strcmp(shutdown_mode, "halt") == 0) {
+ shutdown_flag = "-H";
+ } else if (strcmp(shutdown_mode, "powerdown") == 0) {
+ shutdown_flag = "-P";
+ } else if (strcmp(shutdown_mode, "reboot") == 0) {
+ shutdown_flag = "-r";
+ } else {
+ error_set(err, QERR_INVALID_PARAMETER_VALUE, "shutdown_mode",
+ "halt|powerdown|reboot");
+ return;
+ }
+
+ ret = fork();
+ if (ret == 0) {
+ /* child, start the shutdown */
+ setsid();
+ fclose(stdin);
+ fclose(stdout);
+ fclose(stderr);
+
+ sleep(5);
+ ret = execl("/sbin/shutdown", "shutdown", shutdown_flag, "+0",
+ "hypervisor initiated shutdown", (char*)NULL);
+ exit(!!ret);
+ } else if (ret < 0) {
+ error_set(err, QERR_UNDEFINED_ERROR);
+ }
+}
+
+typedef struct GuestFileHandle {
+ uint64_t id;
+ FILE *fh;
+} GuestFileHandle;
+
+static struct {
+ GSList *filehandles;
+ uint64_t last_id;
+} guest_file_state;
+
+static int64_t guest_file_handle_add(FILE *fh)
+{
+ GuestFileHandle *gfh;
+
+ gfh = g_malloc(sizeof(GuestFileHandle));
+ gfh->id = guest_file_state.last_id++;
+ gfh->fh = fh;
+ guest_file_state.filehandles = g_slist_append(guest_file_state.filehandles,
+ gfh);
+ return gfh->id;
+}
+
+static gint guest_file_handle_match(gconstpointer elem, gconstpointer id_p)
+{
+ const uint64_t *id = id_p;
+ const GuestFileHandle *gfh = elem;
+
+ g_assert(gfh);
+ return (gfh->id != *id);
+}
+
+static FILE *guest_file_handle_find(int64_t id)
+{
+ GSList *elem = g_slist_find_custom(guest_file_state.filehandles, &id,
+ guest_file_handle_match);
+ GuestFileHandle *gfh;
+
+ if (elem) {
+ g_assert(elem->data);
+ gfh = elem->data;
+ return gfh->fh;
+ }
+
+ return NULL;
+}
+
+static void guest_file_handle_remove(int64_t id)
+{
+ GSList *elem = g_slist_find_custom(guest_file_state.filehandles, &id,
+ guest_file_handle_match);
+ gpointer data = elem->data;
+
+ if (!data) {
+ return;
+ }
+ guest_file_state.filehandles = g_slist_remove(guest_file_state.filehandles,
+ data);
+ g_free(data);
+}
+
+int64_t qmp_guest_file_open(const char *filename, const char *mode, Error **err)
+{
+ FILE *fh;
+ int fd, ret;
+ int64_t id = -1;
+
+ if (!logging_enabled()) {
+ error_set(err, QERR_QGA_LOGGING_FAILED);
+ goto out;
+ }
+ slog("guest-file-open called, filename: %s, mode: %s", filename, mode);
+ fh = fopen(filename, mode);
+ if (!fh) {
+ error_set(err, QERR_OPEN_FILE_FAILED, filename);
+ goto out;
+ }
+
+ /* 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_OPEN_FILE_FAILED, filename);
+ fclose(fh);
+ goto out;
+ }
+
+ id = guest_file_handle_add(fh);
+ slog("guest-file-open, filehandle: %ld", id);
+out:
+ return id;
+}
+
+struct GuestFileRead *qmp_guest_file_read(int64_t filehandle, int64_t count,
+ Error **err)
+{
+ GuestFileRead *read_data;
+ guchar *buf;
+ FILE *fh = guest_file_handle_find(filehandle);
+ size_t read_count;
+
+ if (!fh) {
+ error_set(err, QERR_FD_NOT_FOUND, "filehandle");
+ return NULL;
+ }
+
+ read_data = g_malloc0(sizeof(GuestFileRead));
+ buf = g_malloc(count);
+
+ read_count = fread(buf, 1, count, fh);
+ buf[read_count] = 0;
+ read_data->count = read_count;
+ read_data->eof = feof(fh);
+ if (read_count) {
+ read_data->buf = g_base64_encode(buf, read_count);
+ }
+ g_free(buf);
+ /* clear error and eof. error is generally due to EAGAIN from non-blocking
+ * mode, and no real way to differenitate from a real error since we only
+ * get boolean error flag from ferror()
+ */
+ clearerr(fh);
+
+ return read_data;
+}
+
+GuestFileWrite *qmp_guest_file_write(int64_t filehandle, const char *data_b64,
+ int64_t count, Error **err)
+{
+ GuestFileWrite *write_data;
+ guchar *data;
+ gsize data_len;
+ int write_count;
+ FILE *fh = guest_file_handle_find(filehandle);
+
+ if (!fh) {
+ error_set(err, QERR_FD_NOT_FOUND, "filehandle");
+ return NULL;
+ }
+
+ write_data = g_malloc0(sizeof(GuestFileWrite));
+ data = g_base64_decode(data_b64, &data_len);
+ write_count = fwrite(data, 1, MIN(count, data_len), fh);
+ write_data->count = write_count;
+ write_data->eof = feof(fh);
+ g_free(data);
+ clearerr(fh);
+
+ return write_data;
+}
+
+struct GuestFileSeek *qmp_guest_file_seek(int64_t filehandle, int64_t offset,
+ int64_t whence, Error **err)
+{
+ GuestFileSeek *seek_data;
+ FILE *fh = guest_file_handle_find(filehandle);
+ int ret;
+
+ if (!fh) {
+ error_set(err, QERR_FD_NOT_FOUND, "filehandle");
+ return NULL;
+ }
+
+ seek_data = g_malloc0(sizeof(GuestFileRead));
+ ret = fseek(fh, offset, whence);
+ if (ret == -1) {
+ error_set(err, QERR_UNDEFINED_ERROR);
+ g_free(seek_data);
+ return NULL;
+ }
+ seek_data->position = ftell(fh);
+ seek_data->eof = feof(fh);
+ clearerr(fh);
+
+ return seek_data;
+}
+
+void qmp_guest_file_close(int64_t filehandle, Error **err)
+{
+ FILE *fh = guest_file_handle_find(filehandle);
+
+ if (!logging_enabled()) {
+ error_set(err, QERR_QGA_LOGGING_FAILED);
+ return;
+ }
+ slog("guest-file-close called, filehandle: %ld", filehandle);
+ if (!fh) {
+ error_set(err, QERR_FD_NOT_FOUND, "filehandle");
+ return;
+ }
+
+ fclose(fh);
+ guest_file_handle_remove(filehandle);
+}
+
+/*
+ * Walk the mount table and build a list of local file systems
+ */
+
+struct direntry {
+ char *dirname;
+ char *devtype;
+ struct direntry *next;
+};
+
+struct {
+ struct direntry *mount_list;
+ enum {
+ FREEZE_ERROR = -1,
+ FREEZE_THAWED = 0,
+ FREEZE_INPROGRESS = 1,
+ FREEZE_FROZEN = 2,
+ FREEZE_THAWINPROGRESS = 3,
+ } status;
+} guest_fsfreeze_state;
+
+static int guest_fsfreeze_build_mount_list(void)
+{
+ struct mntent *mnt;
+ struct direntry *entry;
+ struct direntry *next;
+ char const *mtab = MOUNTED;
+ FILE *fp;
+
+ fp = setmntent(mtab, "r");
+ if (!fp) {
+ g_warning("fsfreeze: unable to read mtab");
+ goto fail;
+ }
+
+ while ((mnt = 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 ((mnt->mnt_fsname[0] != '/') ||
+ (strcmp(mnt->mnt_type, "smbfs") == 0) ||
+ (strcmp(mnt->mnt_type, "cifs") == 0)) {
+ continue;
+ }
+
+ entry = g_malloc(sizeof(struct direntry));
+ entry->dirname = qemu_strdup(mnt->mnt_dir);
+ entry->devtype = qemu_strdup(mnt->mnt_type);
+ entry->next = guest_fsfreeze_state.mount_list;
+
+ guest_fsfreeze_state.mount_list = entry;
+ }
+
+ endmntent(fp);
+
+ return 0;
+
+fail:
+ while(guest_fsfreeze_state.mount_list) {
+ next = guest_fsfreeze_state.mount_list->next;
+ g_free(guest_fsfreeze_state.mount_list->dirname);
+ g_free(guest_fsfreeze_state.mount_list->devtype);
+ g_free(guest_fsfreeze_state.mount_list);
+ guest_fsfreeze_state.mount_list = next;
+ }
+
+ return -1;
+}
+
+/*
+ * Return status of freeze/thaw
+ */
+int64_t 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 direntry *entry;
+ int fd;
+
+ if (!logging_enabled()) {
+ error_set(err, QERR_QGA_LOGGING_FAILED);
+ goto out;
+ }
+
+ slog("guest-fsfreeze called");
+
+ if (guest_fsfreeze_state.status != FREEZE_THAWED) {
+ ret = 0;
+ goto out;
+ }
+
+ ret = guest_fsfreeze_build_mount_list();
+ if (ret < 0) {
+ goto out;
+ }
+
+ guest_fsfreeze_state.status = FREEZE_INPROGRESS;
+
+ /* cannot risk guest agent blocking itself on a write in this state */
+ disable_logging();
+
+ entry = guest_fsfreeze_state.mount_list;
+ while(entry) {
+ fd = qemu_open(entry->dirname, O_RDONLY);
+ if (fd == -1) {
+ ret = errno;
+ goto error;
+ }
+ ret = ioctl(fd, FIFREEZE);
+ close(fd);
+ if (ret < 0 && ret != EOPNOTSUPP) {
+ goto error;
+ }
+
+ entry = entry->next;
+ i++;
+ }
+
+ guest_fsfreeze_state.status = FREEZE_FROZEN;
+ ret = i;
+out:
+ return ret;
+error:
+ if (i > 0) {
+ guest_fsfreeze_state.status = FREEZE_ERROR;
+ }
+ goto out;
+}
+
+/*
+ * Walk list of frozen file systems in the guest, and thaw them.
+ */
+int64_t qmp_guest_fsfreeze_thaw(Error **err)
+{
+ int ret;
+ struct direntry *entry;
+ int fd, i = 0;
+
+ if (guest_fsfreeze_state.status != FREEZE_FROZEN &&
+ guest_fsfreeze_state.status != FREEZE_INPROGRESS) {
+ ret = 0;
+ goto out_enable_logging;
+ }
+
+ while((entry = guest_fsfreeze_state.mount_list)) {
+ fd = qemu_open(entry->dirname, O_RDONLY);
+ if (fd == -1) {
+ ret = -1;
+ goto out;
+ }
+ ret = ioctl(fd, FITHAW);
+ close(fd);
+ if (ret < 0 && ret != EOPNOTSUPP) {
+ ret = -1;
+ goto out;
+ }
+
+ guest_fsfreeze_state.mount_list = entry->next;
+ g_free(entry->dirname);
+ g_free(entry->devtype);
+ g_free(entry);
+ i++;
+ }
+
+ guest_fsfreeze_state.status = FREEZE_THAWED;
+ ret = i;
+out_enable_logging:
+ enable_logging();
+out:
+ return ret;
+}
+
+/* register init/cleanup routines for stateful command groups */
+void ga_command_state_init(GAState *s, GACommandState *cs)
+{
+ ga_state = s;
+}
--
1.7.0.4
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [Qemu-devel] [PATCH v3][ 6/7] guest agent: add guest agent commands schema file
2011-06-01 17:54 [Qemu-devel] [QAPI+QGA 3/3] QEMU Guest Agent (virtagent) Michael Roth
` (4 preceding siblings ...)
2011-06-01 17:54 ` [Qemu-devel] [PATCH v3][ 5/7] guest agent: add guest agent RPCs/commands Michael Roth
@ 2011-06-01 17:54 ` Michael Roth
2011-06-01 17:54 ` [Qemu-devel] [PATCH v3][ 7/7] guest agent: Makefile, build qemu-ga Michael Roth
` (2 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Michael Roth @ 2011-06-01 17:54 UTC (permalink / raw)
To: qemu-devel; +Cc: aliguori, Jes.Sorensen, agl, mdroth, lcapitulino
Signed-off-by: Michael Roth <mdroth@linux.vnet.ibm.com>
---
qapi-schema-guest.json | 198 ++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 198 insertions(+), 0 deletions(-)
create mode 100644 qapi-schema-guest.json
diff --git a/qapi-schema-guest.json b/qapi-schema-guest.json
new file mode 100644
index 0000000..be79c49
--- /dev/null
+++ b/qapi-schema-guest.json
@@ -0,0 +1,198 @@
+# *-*- Mode: Python -*-*
+
+##
+# @guest-sync:
+#
+# Echo back a unique integer value
+#
+# This is used by clients talking to the guest agent over the
+# wire to ensure the stream is in sync and doesn't contain stale
+# data from previous client. All guest agent responses should be
+# ignored until the provided unique integer value is returned,
+# and it is up to the client to handle stale whole or
+# partially-delivered JSON text in such a way that this response
+# can be obtained.
+#
+# Such clients should also preceed this command
+# with a 0xFF byte to make such the guest agent flushes any
+# partially read JSON data from a previous session.
+#
+# @id: randomly generated 64-bit integer
+#
+# Returns: The unique integer id passed in by the client
+#
+# Since: 0.15.0
+##
+{ 'command': 'guest-sync'
+ 'data': { 'id': 'int' },
+ 'returns': 'int' }
+
+##
+# @guest-ping:
+#
+# Ping the guest agent, a non-error return implies success
+#
+# Since: 0.15.0
+##
+{ 'command': 'guest-ping' }
+
+##
+# @guest-info:
+#
+# Get some information about the guest agent.
+#
+# Since: 0.15.0
+##
+{ 'type': 'GuestAgentInfo', 'data': {'version': 'str', 'timeout_ms': 'int'} }
+{ 'command': 'guest-info',
+ 'returns': 'GuestAgentInfo' }
+
+##
+# @guest-shutdown:
+#
+# Initiate guest-activated shutdown
+#
+# @shutdown_mode: "halt", "powerdown", or "reboot"
+#
+# Returns: Nothing on success
+#
+# Since: 0.15.0
+##
+{ 'command': 'guest-shutdown', 'data': { 'shutdown_mode': 'str' } }
+
+##
+# @guest-file-open:
+#
+# Open a file in the guest and retrieve a file handle for it
+#
+# @filename: Full path to the file in the guest to open.
+#
+# @mode: #optional open mode, as per fopen(), "r" is the default.
+#
+# Returns: Guest file handle on success.
+# If @filename cannot be opened, OpenFileFailed
+#
+# Since: 0.15.0
+##
+{ 'command': 'guest-file-open',
+ 'data': { 'filename': 'str', 'mode': 'str' },
+ 'returns': 'int' }
+
+##
+# @guest-file-read:
+#
+# Read from an open file in the guest
+#
+# @filehandle: filehandle returned by guest-file-open
+#
+# @count: maximum number of bytes to read
+#
+# Returns: GuestFileRead on success.
+# If @filehandle cannot be found, OpenFileFailed
+#
+# Since: 0.15.0
+##
+{ 'type': 'GuestFileRead',
+ 'data': { 'count': 'int', 'buf': 'str', 'eof': 'bool' } }
+
+{ 'command': 'guest-file-read',
+ 'data': { 'filehandle': 'int', 'count': 'int' },
+ 'returns': 'GuestFileRead' }
+
+##
+# @guest-file-write:
+#
+# Write to an open file in the guest
+#
+# @filehandle: filehandle returned by guest-file-open
+#
+# @data_b64: base64-encoded string representing data to be written
+#
+# @count: bytes to write (actual bytes, after b64-decode)
+#
+# Returns: GuestFileWrite on success.
+# If @filehandle cannot be found, OpenFileFailed
+#
+# Since: 0.15.0
+##
+{ 'type': 'GuestFileWrite',
+ 'data': { 'count': 'int', 'eof': 'bool' } }
+{ 'command': 'guest-file-write',
+ 'data': { 'filehandle': 'int', 'data_b64': 'str', 'count': 'int' },
+ 'returns': 'GuestFileWrite' }
+
+##
+# @guest-file-seek:
+#
+# Seek to a position in the file, as with fseek(), and return the
+# current file position afterward. Also encapsulates ftell()'s
+# functionality, just Set offset=0, whence=SEEK_CUR.
+#
+# @filehandle: filehandle returned by guest-file-open
+#
+# @offset: bytes to skip over in the file stream
+#
+# @whence: SEEK_SET, SEEK_CUR, or SEEK_END, as with fseek()
+#
+# Returns: GuestFileSeek on success.
+# If @filename cannot be opened, OpenFileFailed
+#
+# Since: 0.15.0
+##
+{ 'type': 'GuestFileSeek',
+ 'data': { 'position': 'int', 'eof': 'bool' } }
+
+{ 'command': 'guest-file-seek',
+ 'data': { 'filehandle': 'int', 'offset': 'int', 'whence': 'int' },
+ 'returns': 'GuestFileSeek' }
+
+##
+# @guest-file-close:
+#
+# Close an open file in the guest
+#
+# @filehandle: filehandle returned by guest-file-open
+#
+# Returns: Nothing on success.
+# If @filename cannot be opened, OpenFileFailed
+#
+# Since: 0.15.0
+##
+{ 'command': 'guest-file-close',
+ 'data': { 'filehandle': 'int' } }
+
+##
+# @guest-fsfreeze-status:
+#
+# get guest fsfreeze state
+#
+# Returns: Status of fsfreeze state
+#
+# Since: 0.15.0
+##
+{ 'command': 'guest-fsfreeze-status',
+ 'returns': 'int' }
+
+##
+# @guest-fsfreeze-freeze:
+#
+# Sync and freeze all non-network guest filesystems
+#
+# Returns: Number of file systems frozen
+#
+# Since: 0.15.0
+##
+{ 'command': 'guest-fsfreeze-freeze',
+ 'returns': 'int' }
+
+##
+# @guest-fsfreeze-thaw:
+#
+# Unfreeze frozen guest fileystems
+#
+# Returns: Number of file systems thawed
+#
+# Since: 0.15.0
+##
+{ 'command': 'guest-fsfreeze-thaw',
+ 'returns': 'int' }
--
1.7.0.4
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [Qemu-devel] [PATCH v3][ 7/7] guest agent: Makefile, build qemu-ga
2011-06-01 17:54 [Qemu-devel] [QAPI+QGA 3/3] QEMU Guest Agent (virtagent) Michael Roth
` (5 preceding siblings ...)
2011-06-01 17:54 ` [Qemu-devel] [PATCH v3][ 6/7] guest agent: add guest agent commands schema file Michael Roth
@ 2011-06-01 17:54 ` Michael Roth
2011-06-02 15:25 ` [Qemu-devel] [RESEND][PATCH v3][ 5/7] guest agent: add guest agent RPCs/commands Michael Roth
2011-06-02 15:25 ` [Qemu-devel] [RESEND][PATCH v3][ 6/7] guest agent: add guest agent commands schema file Michael Roth
8 siblings, 0 replies; 10+ messages in thread
From: Michael Roth @ 2011-06-01 17:54 UTC (permalink / raw)
To: qemu-devel; +Cc: aliguori, Jes.Sorensen, agl, mdroth, lcapitulino
This allows us to build qemu-ga with "make qemu-ga". It pulls in the
qemu-tools deps, but does not currently build by default. This may
change to avoid bitrot and help with host-side-only unit tests.
Signed-off-by: Michael Roth <mdroth@linux.vnet.ibm.com>
---
Makefile | 22 +++++++++++++++++-----
qemu-ga.c | 10 ++++++++++
2 files changed, 27 insertions(+), 5 deletions(-)
diff --git a/Makefile b/Makefile
index 9049ff2..f7a7260 100644
--- a/Makefile
+++ b/Makefile
@@ -123,7 +123,7 @@ version-obj-$(CONFIG_WIN32) += version.o
######################################################################
qemu-img.o: qemu-img-cmds.h
-qemu-img.o qemu-tool.o qemu-nbd.o qemu-io.o cmd.o: $(GENERATED_HEADERS)
+qemu-img.o qemu-tool.o qemu-nbd.o qemu-io.o cmd.o qemu-ga.o: $(GENERATED_HEADERS)
qemu-img$(EXESUF): qemu-img.o qemu-tool.o qemu-error.o $(oslib-obj-y) $(trace-obj-y) $(block-obj-y) $(qobject-obj-y) $(version-obj-y) qemu-timer-common.o
@@ -146,7 +146,7 @@ check-qfloat: check-qfloat.o qfloat.o $(CHECK_PROG_DEPS)
check-qjson: check-qjson.o qfloat.o qint.o qdict.o qstring.o qlist.o qbool.o qjson.o json-streamer.o json-lexer.o json-parser.o error.o qerror.o qemu-error.o $(CHECK_PROG_DEPS)
qapi-dir := qapi-generated
-$(qapi-obj-y) test-visiter.o test-qmp-commands.o: QEMU_CFLAGS += -I $(qapi-dir)
+$(qapi-obj-y) test-visiter.o test-qmp-commands.o qemu-ga$(EXESUF): QEMU_CFLAGS += -I $(qapi-dir)
$(qapi-dir)/test-qapi-types.c: $(qapi-dir)/test-qapi-types.h
$(qapi-dir)/test-qapi-types.h: $(SRC_PATH)/qapi-schema-test.json $(SRC_PATH)/scripts/qapi-types.py
@@ -158,20 +158,32 @@ $(qapi-dir)/test-qmp-commands.h: $(qapi-dir)/test-qmp-marshal.c
$(qapi-dir)/test-qmp-marshal.c: $(SRC_PATH)/qapi-schema-test.json $(SRC_PATH)/scripts/qapi-commands.py
$(call quiet-command,python $(SRC_PATH)/scripts/qapi-commands.py -o "$(qapi-dir)" -p "test-" < $<, " GEN $@")
+$(qapi-dir)/qga-qapi-types.c: $(qapi-dir)/qga-qapi-types.h
+$(qapi-dir)/qga-qapi-types.h: $(SRC_PATH)/qapi-schema-guest.json $(SRC_PATH)/scripts/qapi-types.py
+ $(call quiet-command,python $(SRC_PATH)/scripts/qapi-types.py -o "$(qapi-dir)" -p "qga-" < $<, " GEN $@")
+$(qapi-dir)/qga-qapi-visit.c: $(qapi-dir)/qga-qapi-visit.h
+$(qapi-dir)/qga-qapi-visit.h: $(SRC_PATH)/qapi-schema-guest.json $(SRC_PATH)/scripts/qapi-visit.py
+ $(call quiet-command,python $(SRC_PATH)/scripts/qapi-visit.py -o "$(qapi-dir)" -p "qga-" < $<, " GEN $@")
+$(qapi-dir)/qga-qmp-marshal.c: $(SRC_PATH)/qapi-schema-guest.json $(SRC_PATH)/scripts/qapi-commands.py
+ $(call quiet-command,python $(SRC_PATH)/scripts/qapi-commands.py -o "$(qapi-dir)" -p "qga-" < $<, " GEN $@")
+
test-visiter.o: $(addprefix $(qapi-dir)/, test-qapi-types.c test-qapi-types.h test-qapi-visit.c test-qapi-visit.h)
test-visiter: test-visiter.o qfloat.o qint.o qdict.o qstring.o qlist.o qbool.o $(qapi-obj-y) error.o osdep.o qemu-malloc.o $(oslib-obj-y) qjson.o json-streamer.o json-lexer.o json-parser.o qerror.o qemu-error.o qemu-tool.o $(qapi-dir)/test-qapi-visit.o $(qapi-dir)/test-qapi-types.o
test-qmp-commands.o: $(addprefix $(qapi-dir)/, test-qapi-types.c test-qapi-types.h test-qapi-visit.c test-qapi-visit.h test-qmp-marshal.c test-qmp-commands.h)
test-qmp-commands: test-qmp-commands.o qfloat.o qint.o qdict.o qstring.o qlist.o qbool.o $(qapi-obj-y) error.o osdep.o qemu-malloc.o $(oslib-obj-y) qjson.o json-streamer.o json-lexer.o json-parser.o qerror.o qemu-error.o qemu-tool.o $(qapi-dir)/test-qapi-visit.o $(qapi-dir)/test-qapi-types.o $(qapi-dir)/test-qmp-marshal.o module.o
+qemu-ga.o: $(qapi-dir)/qga-qapi-types.c $(qapi-dir)/qga-qapi-types.h $(qapi-dir)/qga-qapi-visit.c $(qapi-dir)/qga-qmp-marshal.c
+qemu-ga$(EXESUF): qemu-ga.o qemu-tool.o qemu-error.o error.o $(oslib-obj-y) $(trace-obj-y) $(block-obj-y) $(qobject-obj-y) $(version-obj-y) $(qapi-obj-y) qemu-timer-common.o qemu-sockets.o qga/guest-agent-commands.o qga/guest-agent-command-state.o qga/guest-agent-worker.o $(qapi-dir)/qga-qapi-visit.o $(qapi-dir)/qga-qmp-marshal.o module.o qapi/qmp-dispatch.o qapi/qmp-registry.o
+
QEMULIBS=libhw32 libhw64 libuser libdis libdis-user
clean:
# avoid old build problems by removing potentially incorrect old files
rm -f config.mak op-i386.h opc-i386.h gen-op-i386.h op-arm.h opc-arm.h gen-op-arm.h
rm -f qemu-options.def
- rm -f *.o *.d *.a $(TOOLS) TAGS cscope.* *.pod *~ */*~
- rm -f slirp/*.o slirp/*.d audio/*.o audio/*.d block/*.o block/*.d net/*.o net/*.d fsdev/*.o fsdev/*.d ui/*.o ui/*.d
+ rm -f *.o *.d *.a $(TOOLS) qemu-ga TAGS cscope.* *.pod *~ */*~
+ rm -f slirp/*.o slirp/*.d audio/*.o audio/*.d block/*.o block/*.d net/*.o net/*.d fsdev/*.o fsdev/*.d ui/*.o ui/*.d $(qapi-dir)/* qga/*.o qga/*.d
rm -f qemu-img-cmds.h
rm -f trace.c trace.h trace.c-timestamp trace.h-timestamp
rm -f trace-dtrace.dtrace trace-dtrace.dtrace-timestamp
@@ -363,4 +375,4 @@ tarbin:
$(mandir)/man8/qemu-nbd.8
# Include automatically generated dependency files
--include $(wildcard *.d audio/*.d slirp/*.d block/*.d net/*.d ui/*.d)
+-include $(wildcard *.d audio/*.d slirp/*.d block/*.d net/*.d ui/*.d qapi/*.d qga/*.d)
diff --git a/qemu-ga.c b/qemu-ga.c
index 0438e76..efb3f22 100644
--- a/qemu-ga.c
+++ b/qemu-ga.c
@@ -100,6 +100,16 @@ bool ga_logging_enabled(GAState *s)
return s->logging_enabled;
}
+void ga_disable_logging(GAState *s)
+{
+ s->logging_enabled = false;
+}
+
+void ga_enable_logging(GAState *s)
+{
+ s->logging_enabled = true;
+}
+
static void ga_log(const gchar *domain, GLogLevelFlags level,
const gchar *msg, gpointer opaque)
{
--
1.7.0.4
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [Qemu-devel] [RESEND][PATCH v3][ 5/7] guest agent: add guest agent RPCs/commands
2011-06-01 17:54 [Qemu-devel] [QAPI+QGA 3/3] QEMU Guest Agent (virtagent) Michael Roth
` (6 preceding siblings ...)
2011-06-01 17:54 ` [Qemu-devel] [PATCH v3][ 7/7] guest agent: Makefile, build qemu-ga Michael Roth
@ 2011-06-02 15:25 ` Michael Roth
2011-06-02 15:25 ` [Qemu-devel] [RESEND][PATCH v3][ 6/7] guest agent: add guest agent commands schema file Michael Roth
8 siblings, 0 replies; 10+ messages in thread
From: Michael Roth @ 2011-06-02 15:25 UTC (permalink / raw)
To: qemu-devel; +Cc: aliguori, Jes.Sorensen, agl, mdroth, lcapitulino
This adds the initial set of QMP/QAPI commands provided by the guest
agent:
guest-sync
guest-ping
guest-info
guest-file-open
guest-file-read
guest-file-write
guest-file-seek
guest-file-close
guest-fsfreeze-freeze
guest-fsfreeze-thaw
guest-fsfreeze-status
The input/output specification for these commands are documented in the
schema.
Signed-off-by: Michael Roth <mdroth@linux.vnet.ibm.com>
---
qga/guest-agent-commands.c | 497 ++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 497 insertions(+), 0 deletions(-)
create mode 100644 qga/guest-agent-commands.c
diff --git a/qga/guest-agent-commands.c b/qga/guest-agent-commands.c
new file mode 100644
index 0000000..88865ee
--- /dev/null
+++ b/qga/guest-agent-commands.c
@@ -0,0 +1,497 @@
+/*
+ * QEMU Guest Agent commands
+ *
+ * Copyright IBM Corp. 2011
+ *
+ * Authors:
+ * Michael Roth <mdroth@linux.vnet.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include <glib.h>
+#include <mntent.h>
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <linux/fs.h>
+//#include "guest-agent.h"
+#include "qga/guest-agent-core.h"
+#include "qga-qmp-commands.h"
+
+static GAState *ga_state;
+
+static bool logging_enabled(void)
+{
+ return ga_logging_enabled(ga_state);
+}
+
+static void disable_logging(void)
+{
+ ga_disable_logging(ga_state);
+}
+
+static void enable_logging(void)
+{
+ ga_enable_logging(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 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));
+
+ info->version = g_strdup(QGA_VERSION);
+ info->timeout_ms = ga_get_timeout(ga_state);
+
+ return info;
+}
+
+void qmp_guest_shutdown(const char *shutdown_mode, Error **err)
+{
+ int ret;
+ const char *shutdown_flag;
+
+ if (!logging_enabled()) {
+ error_set(err, QERR_QGA_LOGGING_FAILED);
+ return;
+ }
+
+ slog("guest-shutdown called, shutdown_mode: %s", shutdown_mode);
+ if (strcmp(shutdown_mode, "halt") == 0) {
+ shutdown_flag = "-H";
+ } else if (strcmp(shutdown_mode, "powerdown") == 0) {
+ shutdown_flag = "-P";
+ } else if (strcmp(shutdown_mode, "reboot") == 0) {
+ shutdown_flag = "-r";
+ } else {
+ error_set(err, QERR_INVALID_PARAMETER_VALUE, "shutdown_mode",
+ "halt|powerdown|reboot");
+ return;
+ }
+
+ ret = fork();
+ if (ret == 0) {
+ /* child, start the shutdown */
+ setsid();
+ fclose(stdin);
+ fclose(stdout);
+ fclose(stderr);
+
+ sleep(5);
+ ret = execl("/sbin/shutdown", "shutdown", shutdown_flag, "+0",
+ "hypervisor initiated shutdown", (char*)NULL);
+ exit(!!ret);
+ } else if (ret < 0) {
+ error_set(err, QERR_UNDEFINED_ERROR);
+ }
+}
+
+typedef struct GuestFileHandle {
+ uint64_t id;
+ FILE *fh;
+} GuestFileHandle;
+
+static struct {
+ GSList *filehandles;
+ uint64_t last_id;
+} guest_file_state;
+
+static int64_t guest_file_handle_add(FILE *fh)
+{
+ GuestFileHandle *gfh;
+
+ gfh = g_malloc(sizeof(GuestFileHandle));
+ gfh->id = guest_file_state.last_id++;
+ gfh->fh = fh;
+ guest_file_state.filehandles = g_slist_append(guest_file_state.filehandles,
+ gfh);
+ return gfh->id;
+}
+
+static gint guest_file_handle_match(gconstpointer elem, gconstpointer id_p)
+{
+ const uint64_t *id = id_p;
+ const GuestFileHandle *gfh = elem;
+
+ g_assert(gfh);
+ return (gfh->id != *id);
+}
+
+static FILE *guest_file_handle_find(int64_t id)
+{
+ GSList *elem = g_slist_find_custom(guest_file_state.filehandles, &id,
+ guest_file_handle_match);
+ GuestFileHandle *gfh;
+
+ if (elem) {
+ g_assert(elem->data);
+ gfh = elem->data;
+ return gfh->fh;
+ }
+
+ return NULL;
+}
+
+static void guest_file_handle_remove(int64_t id)
+{
+ GSList *elem = g_slist_find_custom(guest_file_state.filehandles, &id,
+ guest_file_handle_match);
+ gpointer data = elem->data;
+
+ if (!data) {
+ return;
+ }
+ guest_file_state.filehandles = g_slist_remove(guest_file_state.filehandles,
+ data);
+ g_free(data);
+}
+
+int64_t qmp_guest_file_open(const char *filename, const char *mode, Error **err)
+{
+ FILE *fh;
+ int fd, ret;
+ int64_t id = -1;
+
+ if (!logging_enabled()) {
+ error_set(err, QERR_QGA_LOGGING_FAILED);
+ goto out;
+ }
+ slog("guest-file-open called, filename: %s, mode: %s", filename, mode);
+ fh = fopen(filename, mode);
+ if (!fh) {
+ error_set(err, QERR_OPEN_FILE_FAILED, filename);
+ goto out;
+ }
+
+ /* 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_OPEN_FILE_FAILED, filename);
+ fclose(fh);
+ goto out;
+ }
+
+ id = guest_file_handle_add(fh);
+ slog("guest-file-open, filehandle: %ld", id);
+out:
+ return id;
+}
+
+struct GuestFileRead *qmp_guest_file_read(int64_t filehandle, int64_t count,
+ Error **err)
+{
+ GuestFileRead *read_data;
+ guchar *buf;
+ FILE *fh = guest_file_handle_find(filehandle);
+ size_t read_count;
+
+ if (!fh) {
+ error_set(err, QERR_FD_NOT_FOUND, "filehandle");
+ return NULL;
+ }
+
+ read_data = g_malloc0(sizeof(GuestFileRead));
+ buf = g_malloc(count);
+
+ read_count = fread(buf, 1, count, fh);
+ buf[read_count] = 0;
+ read_data->count = read_count;
+ read_data->eof = feof(fh);
+ if (read_count) {
+ read_data->buf = g_base64_encode(buf, read_count);
+ }
+ g_free(buf);
+ /* clear error and eof. error is generally due to EAGAIN from non-blocking
+ * mode, and no real way to differenitate from a real error since we only
+ * get boolean error flag from ferror()
+ */
+ clearerr(fh);
+
+ return read_data;
+}
+
+GuestFileWrite *qmp_guest_file_write(int64_t filehandle, const char *data_b64,
+ int64_t count, Error **err)
+{
+ GuestFileWrite *write_data;
+ guchar *data;
+ gsize data_len;
+ int write_count;
+ FILE *fh = guest_file_handle_find(filehandle);
+
+ if (!fh) {
+ error_set(err, QERR_FD_NOT_FOUND, "filehandle");
+ return NULL;
+ }
+
+ write_data = g_malloc0(sizeof(GuestFileWrite));
+ data = g_base64_decode(data_b64, &data_len);
+ write_count = fwrite(data, 1, MIN(count, data_len), fh);
+ write_data->count = write_count;
+ write_data->eof = feof(fh);
+ g_free(data);
+ clearerr(fh);
+
+ return write_data;
+}
+
+struct GuestFileSeek *qmp_guest_file_seek(int64_t filehandle, int64_t offset,
+ int64_t whence, Error **err)
+{
+ GuestFileSeek *seek_data;
+ FILE *fh = guest_file_handle_find(filehandle);
+ int ret;
+
+ if (!fh) {
+ error_set(err, QERR_FD_NOT_FOUND, "filehandle");
+ return NULL;
+ }
+
+ seek_data = g_malloc0(sizeof(GuestFileRead));
+ ret = fseek(fh, offset, whence);
+ if (ret == -1) {
+ error_set(err, QERR_UNDEFINED_ERROR);
+ g_free(seek_data);
+ return NULL;
+ }
+ seek_data->position = ftell(fh);
+ seek_data->eof = feof(fh);
+ clearerr(fh);
+
+ return seek_data;
+}
+
+void qmp_guest_file_close(int64_t filehandle, Error **err)
+{
+ FILE *fh = guest_file_handle_find(filehandle);
+
+ if (!logging_enabled()) {
+ error_set(err, QERR_QGA_LOGGING_FAILED);
+ return;
+ }
+ slog("guest-file-close called, filehandle: %ld", filehandle);
+ if (!fh) {
+ error_set(err, QERR_FD_NOT_FOUND, "filehandle");
+ return;
+ }
+
+ fclose(fh);
+ guest_file_handle_remove(filehandle);
+}
+
+/*
+ * Walk the mount table and build a list of local file systems
+ */
+
+struct direntry {
+ char *dirname;
+ char *devtype;
+ struct direntry *next;
+};
+
+struct {
+ struct direntry *mount_list;
+ enum {
+ FREEZE_ERROR = -1,
+ FREEZE_THAWED = 0,
+ FREEZE_INPROGRESS = 1,
+ FREEZE_FROZEN = 2,
+ FREEZE_THAWINPROGRESS = 3,
+ } status;
+} guest_fsfreeze_state;
+
+static int guest_fsfreeze_build_mount_list(void)
+{
+ struct mntent *mnt;
+ struct direntry *entry;
+ struct direntry *next;
+ char const *mtab = MOUNTED;
+ FILE *fp;
+
+ fp = setmntent(mtab, "r");
+ if (!fp) {
+ g_warning("fsfreeze: unable to read mtab");
+ goto fail;
+ }
+
+ while ((mnt = 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 ((mnt->mnt_fsname[0] != '/') ||
+ (strcmp(mnt->mnt_type, "smbfs") == 0) ||
+ (strcmp(mnt->mnt_type, "cifs") == 0)) {
+ continue;
+ }
+
+ entry = g_malloc(sizeof(struct direntry));
+ entry->dirname = qemu_strdup(mnt->mnt_dir);
+ entry->devtype = qemu_strdup(mnt->mnt_type);
+ entry->next = guest_fsfreeze_state.mount_list;
+
+ guest_fsfreeze_state.mount_list = entry;
+ }
+
+ endmntent(fp);
+
+ return 0;
+
+fail:
+ while(guest_fsfreeze_state.mount_list) {
+ next = guest_fsfreeze_state.mount_list->next;
+ g_free(guest_fsfreeze_state.mount_list->dirname);
+ g_free(guest_fsfreeze_state.mount_list->devtype);
+ g_free(guest_fsfreeze_state.mount_list);
+ guest_fsfreeze_state.mount_list = next;
+ }
+
+ return -1;
+}
+
+/*
+ * Return status of freeze/thaw
+ */
+int64_t 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 direntry *entry;
+ int fd;
+
+ if (!logging_enabled()) {
+ error_set(err, QERR_QGA_LOGGING_FAILED);
+ goto out;
+ }
+
+ slog("guest-fsfreeze called");
+
+ if (guest_fsfreeze_state.status != FREEZE_THAWED) {
+ ret = 0;
+ goto out;
+ }
+
+ ret = guest_fsfreeze_build_mount_list();
+ if (ret < 0) {
+ goto out;
+ }
+
+ guest_fsfreeze_state.status = FREEZE_INPROGRESS;
+
+ /* cannot risk guest agent blocking itself on a write in this state */
+ disable_logging();
+
+ entry = guest_fsfreeze_state.mount_list;
+ while(entry) {
+ fd = qemu_open(entry->dirname, O_RDONLY);
+ if (fd == -1) {
+ ret = errno;
+ goto error;
+ }
+ ret = ioctl(fd, FIFREEZE);
+ close(fd);
+ if (ret < 0 && ret != EOPNOTSUPP) {
+ goto error;
+ }
+
+ entry = entry->next;
+ i++;
+ }
+
+ guest_fsfreeze_state.status = FREEZE_FROZEN;
+ ret = i;
+out:
+ return ret;
+error:
+ if (i > 0) {
+ guest_fsfreeze_state.status = FREEZE_ERROR;
+ }
+ goto out;
+}
+
+/*
+ * Walk list of frozen file systems in the guest, and thaw them.
+ */
+int64_t qmp_guest_fsfreeze_thaw(Error **err)
+{
+ int ret;
+ struct direntry *entry;
+ int fd, i = 0;
+
+ if (guest_fsfreeze_state.status != FREEZE_FROZEN &&
+ guest_fsfreeze_state.status != FREEZE_INPROGRESS) {
+ ret = 0;
+ goto out_enable_logging;
+ }
+
+ while((entry = guest_fsfreeze_state.mount_list)) {
+ fd = qemu_open(entry->dirname, O_RDONLY);
+ if (fd == -1) {
+ ret = -1;
+ goto out;
+ }
+ ret = ioctl(fd, FITHAW);
+ close(fd);
+ if (ret < 0 && ret != EOPNOTSUPP) {
+ ret = -1;
+ goto out;
+ }
+
+ guest_fsfreeze_state.mount_list = entry->next;
+ g_free(entry->dirname);
+ g_free(entry->devtype);
+ g_free(entry);
+ i++;
+ }
+
+ guest_fsfreeze_state.status = FREEZE_THAWED;
+ ret = i;
+out_enable_logging:
+ enable_logging();
+out:
+ return ret;
+}
+
+/* register init/cleanup routines for stateful command groups */
+void ga_command_state_init(GAState *s, GACommandState *cs)
+{
+ ga_state = s;
+}
--
1.7.0.4
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [Qemu-devel] [RESEND][PATCH v3][ 6/7] guest agent: add guest agent commands schema file
2011-06-01 17:54 [Qemu-devel] [QAPI+QGA 3/3] QEMU Guest Agent (virtagent) Michael Roth
` (7 preceding siblings ...)
2011-06-02 15:25 ` [Qemu-devel] [RESEND][PATCH v3][ 5/7] guest agent: add guest agent RPCs/commands Michael Roth
@ 2011-06-02 15:25 ` Michael Roth
8 siblings, 0 replies; 10+ messages in thread
From: Michael Roth @ 2011-06-02 15:25 UTC (permalink / raw)
To: qemu-devel; +Cc: aliguori, Jes.Sorensen, agl, mdroth, lcapitulino
Signed-off-by: Michael Roth <mdroth@linux.vnet.ibm.com>
---
qapi-schema-guest.json | 198 ++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 198 insertions(+), 0 deletions(-)
create mode 100644 qapi-schema-guest.json
diff --git a/qapi-schema-guest.json b/qapi-schema-guest.json
new file mode 100644
index 0000000..be79c49
--- /dev/null
+++ b/qapi-schema-guest.json
@@ -0,0 +1,198 @@
+# *-*- Mode: Python -*-*
+
+##
+# @guest-sync:
+#
+# Echo back a unique integer value
+#
+# This is used by clients talking to the guest agent over the
+# wire to ensure the stream is in sync and doesn't contain stale
+# data from previous client. All guest agent responses should be
+# ignored until the provided unique integer value is returned,
+# and it is up to the client to handle stale whole or
+# partially-delivered JSON text in such a way that this response
+# can be obtained.
+#
+# Such clients should also preceed this command
+# with a 0xFF byte to make such the guest agent flushes any
+# partially read JSON data from a previous session.
+#
+# @id: randomly generated 64-bit integer
+#
+# Returns: The unique integer id passed in by the client
+#
+# Since: 0.15.0
+##
+{ 'command': 'guest-sync'
+ 'data': { 'id': 'int' },
+ 'returns': 'int' }
+
+##
+# @guest-ping:
+#
+# Ping the guest agent, a non-error return implies success
+#
+# Since: 0.15.0
+##
+{ 'command': 'guest-ping' }
+
+##
+# @guest-info:
+#
+# Get some information about the guest agent.
+#
+# Since: 0.15.0
+##
+{ 'type': 'GuestAgentInfo', 'data': {'version': 'str', 'timeout_ms': 'int'} }
+{ 'command': 'guest-info',
+ 'returns': 'GuestAgentInfo' }
+
+##
+# @guest-shutdown:
+#
+# Initiate guest-activated shutdown
+#
+# @shutdown_mode: "halt", "powerdown", or "reboot"
+#
+# Returns: Nothing on success
+#
+# Since: 0.15.0
+##
+{ 'command': 'guest-shutdown', 'data': { 'shutdown_mode': 'str' } }
+
+##
+# @guest-file-open:
+#
+# Open a file in the guest and retrieve a file handle for it
+#
+# @filename: Full path to the file in the guest to open.
+#
+# @mode: #optional open mode, as per fopen(), "r" is the default.
+#
+# Returns: Guest file handle on success.
+# If @filename cannot be opened, OpenFileFailed
+#
+# Since: 0.15.0
+##
+{ 'command': 'guest-file-open',
+ 'data': { 'filename': 'str', 'mode': 'str' },
+ 'returns': 'int' }
+
+##
+# @guest-file-read:
+#
+# Read from an open file in the guest
+#
+# @filehandle: filehandle returned by guest-file-open
+#
+# @count: maximum number of bytes to read
+#
+# Returns: GuestFileRead on success.
+# If @filehandle cannot be found, OpenFileFailed
+#
+# Since: 0.15.0
+##
+{ 'type': 'GuestFileRead',
+ 'data': { 'count': 'int', 'buf': 'str', 'eof': 'bool' } }
+
+{ 'command': 'guest-file-read',
+ 'data': { 'filehandle': 'int', 'count': 'int' },
+ 'returns': 'GuestFileRead' }
+
+##
+# @guest-file-write:
+#
+# Write to an open file in the guest
+#
+# @filehandle: filehandle returned by guest-file-open
+#
+# @data_b64: base64-encoded string representing data to be written
+#
+# @count: bytes to write (actual bytes, after b64-decode)
+#
+# Returns: GuestFileWrite on success.
+# If @filehandle cannot be found, OpenFileFailed
+#
+# Since: 0.15.0
+##
+{ 'type': 'GuestFileWrite',
+ 'data': { 'count': 'int', 'eof': 'bool' } }
+{ 'command': 'guest-file-write',
+ 'data': { 'filehandle': 'int', 'data_b64': 'str', 'count': 'int' },
+ 'returns': 'GuestFileWrite' }
+
+##
+# @guest-file-seek:
+#
+# Seek to a position in the file, as with fseek(), and return the
+# current file position afterward. Also encapsulates ftell()'s
+# functionality, just Set offset=0, whence=SEEK_CUR.
+#
+# @filehandle: filehandle returned by guest-file-open
+#
+# @offset: bytes to skip over in the file stream
+#
+# @whence: SEEK_SET, SEEK_CUR, or SEEK_END, as with fseek()
+#
+# Returns: GuestFileSeek on success.
+# If @filename cannot be opened, OpenFileFailed
+#
+# Since: 0.15.0
+##
+{ 'type': 'GuestFileSeek',
+ 'data': { 'position': 'int', 'eof': 'bool' } }
+
+{ 'command': 'guest-file-seek',
+ 'data': { 'filehandle': 'int', 'offset': 'int', 'whence': 'int' },
+ 'returns': 'GuestFileSeek' }
+
+##
+# @guest-file-close:
+#
+# Close an open file in the guest
+#
+# @filehandle: filehandle returned by guest-file-open
+#
+# Returns: Nothing on success.
+# If @filename cannot be opened, OpenFileFailed
+#
+# Since: 0.15.0
+##
+{ 'command': 'guest-file-close',
+ 'data': { 'filehandle': 'int' } }
+
+##
+# @guest-fsfreeze-status:
+#
+# get guest fsfreeze state
+#
+# Returns: Status of fsfreeze state
+#
+# Since: 0.15.0
+##
+{ 'command': 'guest-fsfreeze-status',
+ 'returns': 'int' }
+
+##
+# @guest-fsfreeze-freeze:
+#
+# Sync and freeze all non-network guest filesystems
+#
+# Returns: Number of file systems frozen
+#
+# Since: 0.15.0
+##
+{ 'command': 'guest-fsfreeze-freeze',
+ 'returns': 'int' }
+
+##
+# @guest-fsfreeze-thaw:
+#
+# Unfreeze frozen guest fileystems
+#
+# Returns: Number of file systems thawed
+#
+# Since: 0.15.0
+##
+{ 'command': 'guest-fsfreeze-thaw',
+ 'returns': 'int' }
--
1.7.0.4
^ permalink raw reply related [flat|nested] 10+ messages in thread