qemu-devel.nongnu.org archive mirror
 help / color / mirror / Atom feed
* [Qemu-devel] [QAPI+QGA 3/3] QEMU Guest Agent (virtagent) v4
@ 2011-06-03 22:47 Michael Roth
  2011-06-03 22:48 ` [Qemu-devel] [PATCH v4][ 1/7] guest agent: worker thread class Michael Roth
                   ` (6 more replies)
  0 siblings, 7 replies; 11+ messages in thread
From: Michael Roth @ 2011-06-03 22:47 UTC (permalink / raw)
  To: qemu-devel; +Cc: aliguori, Jes.Sorensen, agl, mdroth, lcapitulino

This is Set 3/3 of the QAPI+QGA patchsets.

These patches apply on top of qapi-backport-set2-v2, and can also be obtained from:
git://repo.or.cz/qemu/mdroth.git qapi-backport-set3-v4

(Set1+2 are a backport of some of the QAPI-related work from Anthony's
glib tree. The main goal is to get the basic code generation infrastructure in
place so that it can be used by the guest agent to implement a QMP-like guest
interface, and so that future work regarding the QMP conversion to QAPI can be
decoupled from the infrastructure bits. Set3 is the Qemu Guest Agent
(virtagent), rebased on the new code QAPI code generation infrastructure. This
is the first user of QAPI, QMP will follow.)
___

CHANGES SINCE V3:

 - Fixed error-handling issues in fsfreeze commands leading to certain mounted directories causing freeze/thaw operations to fail
 - Added cleanup hook to thaw filesystems on graceful guest agent exit
 - Removed unused enum values and added additional details to schema documentation
 - Fixed build issue that was missed due to deprecated files in source tree, removed unused includes

CHANGES SINCE V2:

 - Rebased on new QAPI code generation framework
 - Dropped ability for QMP to act as a proxy for the guest agent, will be added when new QMP server is backported from Anthony's glib tree
 - Replaced negotiation/control events with a simple 2-way handshake implemented by a standard RPC (guest-sync)
 - Removed enforcement of "pristine" sessions, state is now global/persistant across multiple clients/connections
 - Fixed segfault in logging code
 - Added Jes' filesystem freeze patches 
 - General cleanups

CHANGES SINCE V1:

 - Added guest agent worker thread to execute RPCs in the guest. With this in place we have a reliable timeout mechanism for hung commands, currently set at 30 seconds.
 - Add framework for registering init/cleanup routines for stateful RPCs to clean up after themselves after a timeout.
 - Added the following RPCs: guest-file-{open,close,read,write,seek}, guest-shutdown, guest-info, and removed stubs for guest-view-file (now deprecated)
 - Added GUEST_AGENT_UP/GUEST_AGENT_DOWN QMP events
 - Switched to a TCP-style host-initiated 3-way handshake for channel negotiation, this simplifies client negotiation/interaction over the wire
 - Added configurable log level/log file/pid file options for guest agent
 - Various fixes for bugs/memory leaks and checkpatch.pl fixups

ISSUES/TODOS:

 - Add unit tests for guest agent wire protocol

OVERVIEW

For a better overview of what these patches are meant to accomplish, please reference the RFC for virtagent:

http://comments.gmane.org/gmane.comp.emulators.qemu/96096

These patches integrate the previous virtagent guest agent work directly in QAPI/QMP to leverage it's auto-generated marshalling code. This has numerous benefits:

 - addresses previous concerns over relying on external libraries to handle data encapsulation
 - reduces the need for manual unmarshalling of requests/responses, which makes adding new RPCs much safer/less error-prone, as well as cutting down on redundant code
 - QAPI documentation aligns completely with guest-side RPC implementation
 - is Just Better (TM)

BUILD/USAGE

build:
  ./configure --target-list=x86_64-softmmu
  make
  make qemu-ga #should be built on|for target guest

start guest:
  qemu \
  -drive file=/home/mdroth/vm/rhel6_64_base.raw,snapshot=off,if=virtio \
  -net nic,model=virtio,macaddr=52:54:00:12:34:00 \
  -net tap,script=/etc/qemu-ifup \
  -vnc :1 -m 1024 --enable-kvm \
  -chardev socket,path=/tmp/qga.sock,server,nowait,id=qga \
  -device virtio-serial \
  -device virtserialport,chardev=qga,name=qga"

use guest agent:
  ./qemu-ga -h
  ./qemu-ga -c virtio-serial -p /dev/virtio-ports/qga

start/use qmp:
  mdroth@illuin:~$ sudo socat unix-connect:/tmp/qga.sock readline
  {"execute":"guest-sync", "arguments":{"id":1234}}
  {"return": 1234}

  {"execute":"guest-info"}
  {"return": {}}

  {"execute": "guest-info"}
  {"return": {"version": "1.0", "timeout_ms": 30000}}

  {"execute":"guest-file-open", "arguments":{"filename":"/tmp/testqga","mode":"w+"}}
  {"return": 0}
  {"execute":"guest-file-write", "arguments":{"filehandle":0,"data_b64":"aGVsbG8gd29ybGQhCg==","count":13}} // writes "hello world!\n"
  {"return": {"count": 13, "eof": false}}

  {"execute":"guest-file-open", "arguments":{"filename":"/tmp/testqga","mode":"r"}}
  {"return": 1}
  {"execute":"guest-file-read", "arguments":{"filehandle":1,"count":1024}}
  {"return": {"buf": "aGVsbG8gd29ybGQhCg==", "count": 13, "eof": true}}
  {"execute":"guest-file-close","arguments":{"filehandle":1}}
  {"return": {}}

 Makefile                        |   22 +-
 qapi-schema-guest.json          |  201 ++++++++++++
 qemu-ga.c                       |  645 +++++++++++++++++++++++++++++++++++++++
 qerror.c                        |    4 +
 qerror.h                        |    3 +
 qga/guest-agent-command-state.c |   73 +++++
 qga/guest-agent-commands.c      |  522 +++++++++++++++++++++++++++++++
 qga/guest-agent-core.h          |   39 +++
 qga/guest-agent-worker.c        |  179 +++++++++++
 9 files changed, 1683 insertions(+), 5 deletions(-)

^ permalink raw reply	[flat|nested] 11+ messages in thread

* [Qemu-devel] [PATCH v4][ 1/7] guest agent: worker thread class
  2011-06-03 22:47 [Qemu-devel] [QAPI+QGA 3/3] QEMU Guest Agent (virtagent) v4 Michael Roth
@ 2011-06-03 22:48 ` Michael Roth
  2011-06-07 19:11   ` Anthony Liguori
  2011-06-03 22:48 ` [Qemu-devel] [PATCH v4][ 2/7] guest agent: command state class Michael Roth
                   ` (5 subsequent siblings)
  6 siblings, 1 reply; 11+ messages in thread
From: Michael Roth @ 2011-06-03 22:48 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] 11+ messages in thread

* [Qemu-devel] [PATCH v4][ 2/7] guest agent: command state class
  2011-06-03 22:47 [Qemu-devel] [QAPI+QGA 3/3] QEMU Guest Agent (virtagent) v4 Michael Roth
  2011-06-03 22:48 ` [Qemu-devel] [PATCH v4][ 1/7] guest agent: worker thread class Michael Roth
@ 2011-06-03 22:48 ` Michael Roth
  2011-06-03 22:48 ` [Qemu-devel] [PATCH v4][ 3/7] guest agent: qemu-ga daemon Michael Roth
                   ` (4 subsequent siblings)
  6 siblings, 0 replies; 11+ messages in thread
From: Michael Roth @ 2011-06-03 22:48 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..969da23
--- /dev/null
+++ b/qga/guest-agent-command-state.c
@@ -0,0 +1,73 @@
+/*
+ * QEMU Guest Agent command state interfaces
+ *
+ * Copyright IBM Corp. 2011
+ *
+ * Authors:
+ *  Michael Roth      <mdroth@linux.vnet.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+#include <glib.h>
+#include "qga/guest-agent-core.h"
+
+struct GACommandState {
+    GSList *groups;
+};
+
+typedef struct GACommandGroup {
+    void (*init)(void);
+    void (*cleanup)(void);
+} GACommandGroup;
+
+/* handle init/cleanup for stateful guest commands */
+
+void ga_command_state_add(GACommandState *cs,
+                          void (*init)(void),
+                          void (*cleanup)(void))
+{
+    GACommandGroup *cg = g_malloc0(sizeof(GACommandGroup));
+    cg->init = init;
+    cg->cleanup = cleanup;
+    cs->groups = g_slist_append(cs->groups, cg);
+}
+
+static void ga_command_group_init(gpointer opaque, gpointer unused)
+{
+    GACommandGroup *cg = opaque;
+
+    g_assert(cg);
+    if (cg->init) {
+        cg->init();
+    }
+}
+
+void ga_command_state_init_all(GACommandState *cs)
+{
+    g_assert(cs);
+    g_slist_foreach(cs->groups, ga_command_group_init, NULL);
+}
+
+static void ga_command_group_cleanup(gpointer opaque, gpointer unused)
+{
+    GACommandGroup *cg = opaque;
+
+    g_assert(cg);
+    if (cg->cleanup) {
+        cg->cleanup();
+    }
+}
+
+void ga_command_state_cleanup_all(GACommandState *cs)
+{
+    g_assert(cs);
+    g_slist_foreach(cs->groups, ga_command_group_cleanup, NULL);
+}
+
+GACommandState *ga_command_state_new(void)
+{
+    GACommandState *cs = g_malloc0(sizeof(GACommandState));
+    cs->groups = NULL;
+    return cs;
+}
-- 
1.7.0.4

^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [Qemu-devel] [PATCH v4][ 3/7] guest agent: qemu-ga daemon
  2011-06-03 22:47 [Qemu-devel] [QAPI+QGA 3/3] QEMU Guest Agent (virtagent) v4 Michael Roth
  2011-06-03 22:48 ` [Qemu-devel] [PATCH v4][ 1/7] guest agent: worker thread class Michael Roth
  2011-06-03 22:48 ` [Qemu-devel] [PATCH v4][ 2/7] guest agent: command state class Michael Roth
@ 2011-06-03 22:48 ` Michael Roth
  2011-06-03 22:48 ` [Qemu-devel] [PATCH v4][ 4/7] guest agent: add error class for QERR_QGA_LOGGING_FAILED Michael Roth
                   ` (3 subsequent siblings)
  6 siblings, 0 replies; 11+ messages in thread
From: Michael Roth @ 2011-06-03 22:48 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] 11+ messages in thread

* [Qemu-devel] [PATCH v4][ 4/7] guest agent: add error class for QERR_QGA_LOGGING_FAILED
  2011-06-03 22:47 [Qemu-devel] [QAPI+QGA 3/3] QEMU Guest Agent (virtagent) v4 Michael Roth
                   ` (2 preceding siblings ...)
  2011-06-03 22:48 ` [Qemu-devel] [PATCH v4][ 3/7] guest agent: qemu-ga daemon Michael Roth
@ 2011-06-03 22:48 ` Michael Roth
  2011-06-03 22:48 ` [Qemu-devel] [PATCH v4][ 5/7] guest agent: add guest agent RPCs/commands Michael Roth
                   ` (2 subsequent siblings)
  6 siblings, 0 replies; 11+ messages in thread
From: Michael Roth @ 2011-06-03 22:48 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] 11+ messages in thread

* [Qemu-devel] [PATCH v4][ 5/7] guest agent: add guest agent RPCs/commands
  2011-06-03 22:47 [Qemu-devel] [QAPI+QGA 3/3] QEMU Guest Agent (virtagent) v4 Michael Roth
                   ` (3 preceding siblings ...)
  2011-06-03 22:48 ` [Qemu-devel] [PATCH v4][ 4/7] guest agent: add error class for QERR_QGA_LOGGING_FAILED Michael Roth
@ 2011-06-03 22:48 ` Michael Roth
  2011-06-04 20:08   ` Andi Kleen
  2011-06-03 22:48 ` [Qemu-devel] [PATCH v4][ 6/7] guest agent: add guest agent commands schema file Michael Roth
  2011-06-03 22:48 ` [Qemu-devel] [PATCH v4][ 7/7] guest agent: Makefile, build qemu-ga Michael Roth
  6 siblings, 1 reply; 11+ messages in thread
From: Michael Roth @ 2011-06-03 22:48 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 |  522 ++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 522 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..c836f33
--- /dev/null
+++ b/qga/guest-agent-commands.c
@@ -0,0 +1,522 @@
+/*
+ * 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 "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,
+    } 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;
+        }
+
+        /* 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 reason 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) {
+            close(fd);
+            goto error;
+        }
+        close(fd);
+
+        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 = -errno;
+            goto out;
+        }
+        ret = ioctl(fd, FITHAW);
+        if (ret < 0 && errno != EOPNOTSUPP && errno != EINVAL) {
+            ret = -errno;
+            close(fd);
+            goto out;
+        }
+        close(fd);
+
+        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;
+}
+
+static void guest_fsfreeze_cleanup(void)
+{
+    int64_t ret;
+    Error *err = NULL;
+
+    if (guest_fsfreeze_state.status == FREEZE_FROZEN) {
+        ret = qmp_guest_fsfreeze_thaw(&err);
+        if (ret < 0 || err) {
+            slog("failed to clean up frozen filesystems");
+        }
+    }
+}
+
+/* register init/cleanup routines for stateful command groups */
+void ga_command_state_init(GAState *s, GACommandState *cs)
+{
+    ga_state = s;
+    ga_command_state_add(cs, NULL, guest_fsfreeze_cleanup);
+}
-- 
1.7.0.4

^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [Qemu-devel] [PATCH v4][ 6/7] guest agent: add guest agent commands schema file
  2011-06-03 22:47 [Qemu-devel] [QAPI+QGA 3/3] QEMU Guest Agent (virtagent) v4 Michael Roth
                   ` (4 preceding siblings ...)
  2011-06-03 22:48 ` [Qemu-devel] [PATCH v4][ 5/7] guest agent: add guest agent RPCs/commands Michael Roth
@ 2011-06-03 22:48 ` Michael Roth
  2011-06-03 22:48 ` [Qemu-devel] [PATCH v4][ 7/7] guest agent: Makefile, build qemu-ga Michael Roth
  6 siblings, 0 replies; 11+ messages in thread
From: Michael Roth @ 2011-06-03 22:48 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 |  201 ++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 201 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..9b509f3
--- /dev/null
+++ b/qapi-schema-guest.json
@@ -0,0 +1,201 @@
+# *-*- 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 [-1,4] for error, thawed, inprogress,
+#            frozen, respectively
+#
+# 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
+#          If error, -1 (unknown error) or -errno
+#
+# Since: 0.15.0
+##
+{ 'command': 'guest-fsfreeze-freeze',
+  'returns': 'int' }
+
+##
+# @guest-fsfreeze-thaw:
+#
+# Unfreeze frozen guest fileystems
+#
+# Returns: Number of file systems thawed
+#          If error, -1 (unknown error) or -errno
+#
+# Since: 0.15.0
+##
+{ 'command': 'guest-fsfreeze-thaw',
+  'returns': 'int' }
-- 
1.7.0.4

^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [Qemu-devel] [PATCH v4][ 7/7] guest agent: Makefile, build qemu-ga
  2011-06-03 22:47 [Qemu-devel] [QAPI+QGA 3/3] QEMU Guest Agent (virtagent) v4 Michael Roth
                   ` (5 preceding siblings ...)
  2011-06-03 22:48 ` [Qemu-devel] [PATCH v4][ 6/7] guest agent: add guest agent commands schema file Michael Roth
@ 2011-06-03 22:48 ` Michael Roth
  6 siblings, 0 replies; 11+ messages in thread
From: Michael Roth @ 2011-06-03 22:48 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 944e85e..bc86156 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
@@ -364,4 +376,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] 11+ messages in thread

* Re: [Qemu-devel] [PATCH v4][ 5/7] guest agent: add guest agent RPCs/commands
  2011-06-03 22:48 ` [Qemu-devel] [PATCH v4][ 5/7] guest agent: add guest agent RPCs/commands Michael Roth
@ 2011-06-04 20:08   ` Andi Kleen
  2011-06-04 22:29     ` Anthony Liguori
  0 siblings, 1 reply; 11+ messages in thread
From: Andi Kleen @ 2011-06-04 20:08 UTC (permalink / raw)
  To: Michael Roth; +Cc: aliguori, lcapitulino, agl, qemu-devel, Jes.Sorensen

Michael Roth <mdroth@linux.vnet.ibm.com> writes:
> +
> +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;
> +    }

Does this really allow a guest to open any host file ?!?
Have you considered all the security implications of that?

-Andi

-- 
ak@linux.intel.com -- Speaking for myself only

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [Qemu-devel] [PATCH v4][ 5/7] guest agent: add guest agent RPCs/commands
  2011-06-04 20:08   ` Andi Kleen
@ 2011-06-04 22:29     ` Anthony Liguori
  0 siblings, 0 replies; 11+ messages in thread
From: Anthony Liguori @ 2011-06-04 22:29 UTC (permalink / raw)
  To: Andi Kleen; +Cc: Jes.Sorensen, lcapitulino, agl, Michael Roth, qemu-devel

On 06/04/2011 03:08 PM, Andi Kleen wrote:
> Michael Roth<mdroth@linux.vnet.ibm.com>  writes:
>> +
>> +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;
>> +    }
>
> Does this really allow a guest to open any host file ?!?

It does the opposite.  The host can open files in the guest.  Since the 
host can see the disk image of the guest anyway, it already could do 
this albeit it in a more convoluted way.

Regards,

Anthony Liguroi

> Have you considered all the security implications of that?
>
> -Andi
>

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [Qemu-devel] [PATCH v4][ 1/7] guest agent: worker thread class
  2011-06-03 22:48 ` [Qemu-devel] [PATCH v4][ 1/7] guest agent: worker thread class Michael Roth
@ 2011-06-07 19:11   ` Anthony Liguori
  0 siblings, 0 replies; 11+ messages in thread
From: Anthony Liguori @ 2011-06-07 19:11 UTC (permalink / raw)
  To: Michael Roth; +Cc: aliguori, lcapitulino, agl, qemu-devel, Jes.Sorensen

On 06/03/2011 05:48 PM, Michael Roth wrote:
> 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);

It's more typical to push the lock() and unlock to the outside of the 
loop.  That makes it safer in the event that someone adds a continue or 
break.

Regards,

Anthony Liguori

> +        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);
> +}

^ permalink raw reply	[flat|nested] 11+ messages in thread

end of thread, other threads:[~2011-06-07 19:12 UTC | newest]

Thread overview: 11+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2011-06-03 22:47 [Qemu-devel] [QAPI+QGA 3/3] QEMU Guest Agent (virtagent) v4 Michael Roth
2011-06-03 22:48 ` [Qemu-devel] [PATCH v4][ 1/7] guest agent: worker thread class Michael Roth
2011-06-07 19:11   ` Anthony Liguori
2011-06-03 22:48 ` [Qemu-devel] [PATCH v4][ 2/7] guest agent: command state class Michael Roth
2011-06-03 22:48 ` [Qemu-devel] [PATCH v4][ 3/7] guest agent: qemu-ga daemon Michael Roth
2011-06-03 22:48 ` [Qemu-devel] [PATCH v4][ 4/7] guest agent: add error class for QERR_QGA_LOGGING_FAILED Michael Roth
2011-06-03 22:48 ` [Qemu-devel] [PATCH v4][ 5/7] guest agent: add guest agent RPCs/commands Michael Roth
2011-06-04 20:08   ` Andi Kleen
2011-06-04 22:29     ` Anthony Liguori
2011-06-03 22:48 ` [Qemu-devel] [PATCH v4][ 6/7] guest agent: add guest agent commands schema file Michael Roth
2011-06-03 22:48 ` [Qemu-devel] [PATCH v4][ 7/7] guest agent: Makefile, build qemu-ga Michael Roth

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).