Patchwork [RFC,v2,15/17] guest agent: qemu-ga daemon

login
register
mail settings
Submitter Michael Roth
Date April 18, 2011, 3:02 p.m.
Message ID <1303138953-1334-16-git-send-email-mdroth@linux.vnet.ibm.com>
Download mbox | patch
Permalink /patch/91784/
State New
Headers show

Comments

Michael Roth - April 18, 2011, 3:02 p.m.
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 |  711 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 711 insertions(+), 0 deletions(-)
 create mode 100644 qemu-ga.c
Jes Sorensen - April 21, 2011, 8:50 a.m.
On 04/18/11 17:02, Michael Roth wrote:
> +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";
> +    }

Urgh!

No two statements on the same line please!

Jes
Michael Roth - April 21, 2011, 1:21 p.m.
On 04/21/2011 03:50 AM, Jes Sorensen wrote:
> On 04/18/11 17:02, Michael Roth wrote:
>> +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";
>> +    }
>
> Urgh!
>
> No two statements on the same line please!

Darn, I was hoping my surplus on coding style points for actually 
indenting my case statements would make up for that :)

>
> Jes
Ian Molton - April 22, 2011, 9:23 a.m.
On Thu, 2011-04-21 at 08:21 -0500, Michael Roth wrote:
> >> +    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";
> >> +    }
> >
> > Urgh!
> >
> > No two statements on the same line please!

Always wondered what the logic for this one is. IMHO the above is FAR
neater than splitting it to near double its height.

What kind of coding error does splitting this out aim to prevent?
missing break; / return; statements? Because I dont see how it achieves
that...
Jes Sorensen - April 22, 2011, 11:51 a.m.
On 04/22/11 11:23, Ian Molton wrote:
> On Thu, 2011-04-21 at 08:21 -0500, Michael Roth wrote:
>>>> +    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";
>>>> +    }
>>>
>>> Urgh!
>>>
>>> No two statements on the same line please!
> 
> Always wondered what the logic for this one is. IMHO the above is FAR
> neater than splitting it to near double its height.
> 
> What kind of coding error does splitting this out aim to prevent?
> missing break; / return; statements? Because I dont see how it achieves
> that...

Hiding things you miss when reading the code, it's a classic for people
to do if(foo) bleh(); on the same line, and whoever reads the code will
expect the action on the next line, especially if foo is a long complex
statement.

It's one of these 'just don't do it, it bites you in the end' things.

Jes
Ian Molton - April 25, 2011, 12:27 p.m.
On Fri, 2011-04-22 at 13:51 +0200, Jes Sorensen wrote:
> > What kind of coding error does splitting this out aim to prevent?
> > missing break; / return; statements? Because I dont see how it
> achieves
> > that...
> 
> Hiding things you miss when reading the code, it's a classic for
> people
> to do if(foo) bleh(); on the same line, and whoever reads the code
> will
> expect the action on the next line, especially if foo is a long
> complex
> statement.
> 
> It's one of these 'just don't do it, it bites you in the end' things. 

Meh. I dont see it that way...

Sure, if it was one line out of 20 written that way, it would be weird,
but as is, its just part of a block of identical lines.

I dont really see a parallel with the if() statement either since the
condition in the switch() case isnt on the same line as such. I must
admit that I only write one-liner if statements if the condition is
short though.

-Ian
Jes Sorensen - April 26, 2011, 1:39 p.m.
On 04/25/11 14:27, Ian Molton wrote:
> On Fri, 2011-04-22 at 13:51 +0200, Jes Sorensen wrote:
>> Hiding things you miss when reading the code, it's a classic for 
>> people to do if(foo) bleh(); on the same line, and whoever reads
>> the code will expect the action on the next line, especially if foo
>> is a long complex statement.
>>
>> It's one of these 'just don't do it, it bites you in the end' things. 
> 
> Meh. I dont see it that way...
> 
> Sure, if it was one line out of 20 written that way, it would be weird,
> but as is, its just part of a block of identical lines.

It is a matter of consistency, we allow it in one place, we suddenly
have it all over. The moment someone wants to add a slightly more
complex case to such a switch statement it is all down the drain. It is
way better to stay consistent across the board.

> I dont really see a parallel with the if() statement either since the
> condition in the switch() case isnt on the same line as such. I must
> admit that I only write one-liner if statements if the condition is
> short though.

Writing one-liner if() statements is inherently broken, or you could
call it the Perl syndrome. Write-once, read-never.....

Jes

Patch

diff --git a/qemu-ga.c b/qemu-ga.c
new file mode 100644
index 0000000..800d16b
--- /dev/null
+++ b/qemu-ga.c
@@ -0,0 +1,711 @@ 
+/*
+ * 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 "qga/guest-agent.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 {
+    bool active;
+    int session_id;
+    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;
+};
+
+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";
+    }
+}
+
+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);
+
+    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;
+}
+
+/* when inactive, we keep reading channel, but ignore all non-control messages
+ * until channel is active again (i.e. negotiation is complete)
+ */
+static void set_channel_active(GAState *s, bool active)
+{
+    g_debug("channel active state: %d", active);
+    s->active = active;
+}
+
+static void send_guest_up(GAState *s)
+{
+    QDict *payload = qdict_new();
+    int ret;
+
+    qdict_put_obj(payload, "_control_event",
+                  QOBJECT(qstring_from_str("guest_up")));
+
+    ret = conn_channel_send_payload(s->conn_channel, QOBJECT(payload));
+    if (ret) {
+        g_warning("failed to send guest init, resetting connection");
+        conn_channel_close(s);
+    }
+}
+
+/* host started channel negotiation, process/acknowledge it */
+static void send_guest_ack(GAState *s, int host_sid)
+{
+    QDict *payload = qdict_new();
+    int ret;
+
+    g_debug("[re]negotiating connection");
+
+    g_assert(s && s->conn_channel);
+    set_channel_active(s, false);
+    s->session_id = g_random_int_range(1, G_MAXINT32);
+
+    qdict_put_obj(payload, "_control_event",
+                  QOBJECT(qstring_from_str("guest_ack")));
+    qdict_put_obj(payload, "_control_arg_guest_sid",
+                  QOBJECT(qint_from_int(s->session_id)));
+    qdict_put_obj(payload, "_control_arg_host_sid",
+                  QOBJECT(qint_from_int(host_sid)));
+
+    ret = conn_channel_send_payload(s->conn_channel, QOBJECT(payload));
+    if (ret) {
+        g_warning("failed to send guest init, resetting connection");
+        conn_channel_close(s);
+    }
+}
+
+/* process transport-level events */
+static void process_control_event(GAState *s, const QDict *qdict)
+{
+    const char *cmd;
+    int sid;
+
+    g_assert(s && qdict);
+    cmd = qdict_get_try_str(qdict, "_control_event");
+    if (!cmd) {
+        g_warning("received NULL transport event");
+    } else if (strcmp(cmd, "host_init") == 0) {
+        sid = qdict_get_try_int(qdict, "_control_arg_host_sid", 0);
+        /* host started negotiation, acknowledge it */
+        send_guest_ack(s, sid);
+        set_channel_active(s, false);
+    } else if (strcmp(cmd, "host_ack") == 0) {
+        /* host attempting to complete negotiation, process it */
+        sid = qdict_get_try_int(qdict, "_control_arg_guest_sid", 0);
+        if (sid != s->session_id) {
+            g_warning("received host ack for incorrect session id");
+        } else {
+            set_channel_active(s, true);
+        }
+    } else {
+        g_debug("recieved unknown control event: %s", cmd);
+    }
+}
+
+static void process_command_worker(void *input, void *output, Error **errp)
+{
+    QDict *req = input;
+    QObject **rsp = output;
+
+    g_assert(req && rsp);
+    *rsp = qga_dispatch(QOBJECT(req), errp);
+}
+
+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");
+    }
+}
+
+/* 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);
+    }
+
+    /* check for transport-only commands/events */
+    if (qdict_haskey(qdict, "_control_event")) {
+        process_control_event(s, qdict);
+        goto out_free;
+    }
+
+    /* ignore any non-control-related events/objects */
+    if (!s->active) {
+        g_debug("ignoring non-control event, still awaiting host ack");
+        goto out_free;
+    }
+
+    /* handle host->guest commands */
+    if (qdict_haskey(qdict, "execute")) {
+        process_command(s, qdict);
+    } else {
+        g_warning("unrecognized payload format, ignoring");
+    }
+
+out_free:
+    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:
+    /* reset negotiated state. we'll either send another init upon reconnect
+     * to our socket, or the host will send an init to tell us it has
+     * re-opened the chardev, at which point we'll send another init
+     */
+    set_channel_active(s, false);
+}
+
+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");
+        }
+        send_guest_up(s);
+    } 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);
+    }
+
+    qga_init_marshal();
+
+    s = g_malloc0(sizeof(GAState));
+    s->session_id = 0;
+    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);
+
+    set_channel_active(s, false);
+    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;
+}