All of lore.kernel.org
 help / color / mirror / Atom feed
* [Qemu-devel] [QAPI+QGA 3/3] QEMU Guest Agent (virtagent) v6
@ 2011-07-05 13:21 Michael Roth
  2011-07-05 13:21 ` [Qemu-devel] [PATCH v6 1/4] guest agent: command state class Michael Roth
                   ` (5 more replies)
  0 siblings, 6 replies; 25+ messages in thread
From: Michael Roth @ 2011-07-05 13:21 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-v5, and can also be obtained from:
git://repo.or.cz/qemu/mdroth.git qapi-backport-set3-v6

(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 V5:
 - switched to using qemu malloc/list functions where possible
 - removed unused proxy_path field in struct GAState
 - pid file now opened write-only, removed lockf() in favor of O_EXCL, added SIGINT/SIGTERM signal handlers to handle cleanup
 - cleaned up error-handling, switched to asserts where appropriate, removed unecessary gotos and NULL checks for qemu_free()/qobject_decref()
 - refactored send_payload() using helper functions
 - fixed improper handling of pidfile fd==0
 - changed guest-shutdown's "shutdown_mode" param to "mode"
 - switched to using kernel-generated FDs for guest-file-open rather than an autoincrement value
 - add maximum chunk size of guest-file-read/guest-file-write
 - added checks to avoid guest-file-write from writing data beyond the provided data buffer
 - made logging best-effort, removed handling of failures to log as errors
 - guest-shutdown exec errors now logged to guest syslog, clarified shutdown's asynchronous, no gauruntee nature in schema.

CHANGES SINCE V4:
 - Removed timeout mechanism via worker thread/pthread_cancel due to potential memory leak. Will re-introduce guest-side timeout support in future version.
 - Fixed up fsfreeze code to use enums specified within the guest agent's qapi schema.
 - Fixed memory leak due to a log statement, and added missing cleanup functions for heap-allocated g_error objects.
 - Made "mode" param to guest-file-open optional, defaults to "r" (read-only)

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"}}

  {"execute":"guest-file-open", "arguments":{"filepath":"/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":{"filepath":"/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 +-
 configure                       |    1 +
 qapi-schema-guest.json          |  202 +++++++++++++
 qemu-ga.c                       |  631 +++++++++++++++++++++++++++++++++++++++
 qerror.c                        |    4 +
 qerror.h                        |    3 +
 qga/guest-agent-command-state.c |   73 +++++
 qga/guest-agent-commands.c      |  522 ++++++++++++++++++++++++++++++++
 qga/guest-agent-core.h          |   30 ++
 9 files changed, 1483 insertions(+), 5 deletions(-)

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

* [Qemu-devel] [PATCH v6 1/4] guest agent: command state class
  2011-07-05 13:21 [Qemu-devel] [QAPI+QGA 3/3] QEMU Guest Agent (virtagent) v6 Michael Roth
@ 2011-07-05 13:21 ` Michael Roth
  2011-07-08 14:25   ` Luiz Capitulino
  2011-07-05 13:21 ` [Qemu-devel] [PATCH v6 2/4] guest agent: qemu-ga daemon Michael Roth
                   ` (4 subsequent siblings)
  5 siblings, 1 reply; 25+ messages in thread
From: Michael Roth @ 2011-07-05 13:21 UTC (permalink / raw)
  To: qemu-devel; +Cc: aliguori, Jes.Sorensen, agl, mdroth, lcapitulino


Signed-off-by: Michael Roth <mdroth@linux.vnet.ibm.com>
---
 Makefile                        |    4 ++-
 configure                       |    1 +
 qga/guest-agent-command-state.c |   73 +++++++++++++++++++++++++++++++++++++++
 qga/guest-agent-core.h          |   25 +++++++++++++
 4 files changed, 102 insertions(+), 1 deletions(-)
 create mode 100644 qga/guest-agent-command-state.c
 create mode 100644 qga/guest-agent-core.h

diff --git a/Makefile b/Makefile
index cbd2d77..6c3ba71 100644
--- a/Makefile
+++ b/Makefile
@@ -181,6 +181,8 @@ test-visitor: test-visitor.o qfloat.o qint.o qdict.o qstring.o qlist.o qbool.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
 
+QGALIB=qga/guest-agent-command-state.o
+
 QEMULIBS=libhw32 libhw64 libuser libdis libdis-user
 
 clean:
@@ -189,7 +191,7 @@ clean:
 	rm -f qemu-options.def
 	rm -f *.o *.d *.a *.lo $(TOOLS) TAGS cscope.* *.pod *~ */*~
 	rm -Rf .libs
-	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/*.o qapi/*.d
+	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/*.o qapi/*.d 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
diff --git a/configure b/configure
index 02c552e..6a03002 100755
--- a/configure
+++ b/configure
@@ -3487,6 +3487,7 @@ DIRS="$DIRS pc-bios/spapr-rtas"
 DIRS="$DIRS roms/seabios roms/vgabios"
 DIRS="$DIRS fsdev ui"
 DIRS="$DIRS qapi"
+DIRS="$DIRS qga"
 FILES="Makefile tests/Makefile"
 FILES="$FILES tests/cris/Makefile tests/cris/.gdbinit"
 FILES="$FILES pc-bios/optionrom/Makefile pc-bios/keymaps"
diff --git a/qga/guest-agent-command-state.c b/qga/guest-agent-command-state.c
new file mode 100644
index 0000000..bc6e0bd
--- /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 = qemu_mallocz(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 = qemu_mallocz(sizeof(GACommandState));
+    cs->groups = NULL;
+    return cs;
+}
diff --git a/qga/guest-agent-core.h b/qga/guest-agent-core.h
new file mode 100644
index 0000000..688f120
--- /dev/null
+++ b/qga/guest-agent-core.h
@@ -0,0 +1,25 @@
+/*
+ * 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"
+
+#define QGA_VERSION "1.0"
+
+typedef struct GACommandState GACommandState;
+
+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);
-- 
1.7.0.4

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

* [Qemu-devel] [PATCH v6 2/4] guest agent: qemu-ga daemon
  2011-07-05 13:21 [Qemu-devel] [QAPI+QGA 3/3] QEMU Guest Agent (virtagent) v6 Michael Roth
  2011-07-05 13:21 ` [Qemu-devel] [PATCH v6 1/4] guest agent: command state class Michael Roth
@ 2011-07-05 13:21 ` Michael Roth
  2011-07-06  0:34   ` Michael Roth
  2011-07-08 14:36   ` Luiz Capitulino
  2011-07-05 13:21 ` [Qemu-devel] [PATCH v6 3/4] guest agent: add guest agent commands schema file Michael Roth
                   ` (3 subsequent siblings)
  5 siblings, 2 replies; 25+ messages in thread
From: Michael Roth @ 2011-07-05 13:21 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>
---
 Makefile               |   10 +-
 qemu-ga.c              |  651 ++++++++++++++++++++++++++++++++++++++++++++++++
 qga/guest-agent-core.h |    4 +
 3 files changed, 661 insertions(+), 4 deletions(-)
 create mode 100644 qemu-ga.c

diff --git a/Makefile b/Makefile
index 6c3ba71..b2e8593 100644
--- a/Makefile
+++ b/Makefile
@@ -140,7 +140,7 @@ endif
 ######################################################################
 
 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
 
@@ -163,7 +163,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-visitor.o test-qmp-commands.o: QEMU_CFLAGS += -I $(qapi-dir)
+$(qapi-obj-y) test-visitor.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
@@ -183,13 +183,15 @@ test-qmp-commands: test-qmp-commands.o qfloat.o qint.o qdict.o qstring.o qlist.o
 
 QGALIB=qga/guest-agent-command-state.o
 
+qemu-ga$(EXESUF): qemu-ga.o $(QGALIB) 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 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 *.lo $(TOOLS) TAGS cscope.* *.pod *~ */*~
+	rm -f *.o *.d *.a *.lo $(TOOLS) qemu-ga TAGS cscope.* *.pod *~ */*~
 	rm -Rf .libs
 	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/*.o qapi/*.d qga/*.o qga/*.d
 	rm -f qemu-img-cmds.h
@@ -385,4 +387,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 qapi/*.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
new file mode 100644
index 0000000..649c16a
--- /dev/null
+++ b/qemu-ga.c
@@ -0,0 +1,651 @@
+/*
+ * 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 "module.h"
+#include "signal.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 {
+    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;
+    GLogLevelFlags log_level;
+    FILE *log_file;
+    bool logging_enabled;
+};
+
+static struct GAState *ga_state;
+
+static void quit_handler(int sig)
+{
+    g_debug("recieved signal num %d, quitting");
+
+    if (g_main_loop_is_running(ga_state->main_loop)) {
+        g_main_loop_quit(ga_state->main_loop);
+    }
+}
+
+static void register_signal_handlers(void)
+{
+    struct sigaction sigact;
+    int ret;
+
+    sigact.sa_handler = quit_handler;
+
+    ret = sigaction(SIGINT, &sigact, NULL);
+    if (ret == -1) {
+        g_error("error configuring signal handler: %s", strerror(errno));
+    }
+    ret = sigaction(SIGTERM, &sigact, NULL);
+    if (ret == -1) {
+        g_error("error configuring signal handler: %s", strerror(errno));
+    }
+}
+
+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;
+}
+
+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)
+{
+    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);
+    }
+}
+
+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_WRONLY|O_EXCL, S_IRUSR|S_IWUSR);
+    if (pidfd == -1) {
+        g_error("Cannot create pid file, %s", strerror(errno));
+    }
+
+    if (asprintf(&pidstr, "%d", getpid()) == -1) {
+        g_critical("Cannot allocate memory");
+        goto fail;
+    }
+    if (write(pidfd, pidstr, strlen(pidstr)) != strlen(pidstr)) {
+        free(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);
+    free(pidstr);
+    return;
+
+fail:
+    unlink(pidfile);
+    g_error("failed to daemonize");
+}
+
+static int conn_channel_send_buf(GIOChannel *channel, const char *buf,
+                                 gsize count)
+{
+    GError *err = NULL;
+    gsize written = 0;
+    GIOStatus status;
+
+    while (count) {
+        status = g_io_channel_write_chars(channel, buf, count, &written, &err);
+        g_debug("sending data, count: %d", (int)count);
+        if (err != NULL) {
+            g_warning("error sending newline: %s", err->message);
+            return err->code;
+        }
+        if (status == G_IO_STATUS_ERROR || status == G_IO_STATUS_EOF) {
+            return -EPIPE;
+        }
+
+        if (status == G_IO_STATUS_NORMAL) {
+            count -= written;
+        }
+    }
+
+    return 0;
+}
+
+static int conn_channel_send_payload(GIOChannel *channel, QObject *payload)
+{
+    int ret = 0;
+    const char *buf;
+    QString *payload_qstr;
+    GError *err = NULL;
+
+    g_assert(payload && channel);
+
+    payload_qstr = qobject_to_json(payload);
+    if (!payload_qstr) {
+        return -EINVAL;
+    }
+
+    buf = qstring_get_str(payload_qstr);
+    ret = conn_channel_send_buf(channel, buf, strlen(buf));
+    if (ret) {
+        goto out_free;
+    }
+
+    ret = conn_channel_send_buf(channel, "\n", 1);
+    if (ret) {
+        goto out_free;
+    }
+
+    g_io_channel_flush(channel, &err);
+    if (err != NULL) {
+        g_warning("error flushing payload: %s", err->message);
+        ret = err->code;
+        goto out_free;
+    }
+
+out_free:
+    QDECREF(payload_qstr);
+    if (err) {
+        g_error_free(err);
+    }
+    return ret;
+}
+
+static void process_command(GAState *s, QDict *req)
+{
+    QObject *rsp = NULL;
+    int ret;
+
+    g_assert(req);
+    g_debug("processing command");
+    rsp = qmp_dispatch(QOBJECT(req));
+    if (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) {
+        qobject_decref(obj);
+        g_warning("failed to parse event");
+        return;
+    }
+
+    if (err) {
+        error_free(err);
+        g_warning("failed to parse event: %s", error_get_pretty(err));
+        return;
+    }
+
+    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);
+        g_error_free(err);
+        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");
+        g_error_free(err);
+        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);
+        g_error_free(err);
+        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);
+        g_error_free(err);
+        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, new==true
+ * indicates we should use the existing s->listen_channel
+ */
+static int listen_channel_add(GAState *s, int listen_fd, bool new)
+{
+    GError *err = NULL;
+    guint listen_id;
+
+    if (new) {
+        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);
+            g_error_free(err);
+            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);
+        g_error_free(err);
+        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, false);
+    } else if (strcmp(s->method, "virtio-serial") == 0) {
+        /* we spin on EOF for virtio-serial, so back off a bit. also,
+         * dont close the connection in this case, it'll resume normal
+         * operation when another process connects to host chardev
+         */
+        usleep(100*1000);
+        goto out_noclose;
+    }
+    g_io_channel_unref(s->conn_channel);
+    s->conn_channel = NULL;
+    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, true);
+        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;
+
+    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);
+    }
+
+    g_type_init();
+    g_thread_init(NULL);
+
+    s = qemu_mallocz(sizeof(GAState));
+    s->conn_id = 0;
+    s->conn_channel = NULL;
+    s->path = path;
+    s->method = method;
+    s->log_file = log_file;
+    s->log_level = log_level;
+    g_log_set_default_handler(ga_log, s);
+    g_log_set_fatal_mask(NULL, G_LOG_LEVEL_ERROR);
+    s->logging_enabled = true;
+    ga_state = s;
+
+    module_call_init(MODULE_INIT_QAPI);
+    init_guest_agent(ga_state);
+    register_signal_handlers();
+
+    g_main_loop_run(ga_state->main_loop);
+
+    unlink(pidfile);
+
+    return 0;
+}
diff --git a/qga/guest-agent-core.h b/qga/guest-agent-core.h
index 688f120..66d1729 100644
--- a/qga/guest-agent-core.h
+++ b/qga/guest-agent-core.h
@@ -15,6 +15,7 @@
 
 #define QGA_VERSION "1.0"
 
+typedef struct GAState GAState;
 typedef struct GACommandState GACommandState;
 
 void ga_command_state_add(GACommandState *cs,
@@ -23,3 +24,6 @@ void ga_command_state_add(GACommandState *cs,
 void ga_command_state_init_all(GACommandState *cs);
 void ga_command_state_cleanup_all(GACommandState *cs);
 GACommandState *ga_command_state_new(void);
+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] 25+ messages in thread

* [Qemu-devel] [PATCH v6 3/4] guest agent: add guest agent commands schema file
  2011-07-05 13:21 [Qemu-devel] [QAPI+QGA 3/3] QEMU Guest Agent (virtagent) v6 Michael Roth
  2011-07-05 13:21 ` [Qemu-devel] [PATCH v6 1/4] guest agent: command state class Michael Roth
  2011-07-05 13:21 ` [Qemu-devel] [PATCH v6 2/4] guest agent: qemu-ga daemon Michael Roth
@ 2011-07-05 13:21 ` Michael Roth
  2011-07-08 15:08   ` Luiz Capitulino
  2011-07-05 13:21 ` [Qemu-devel] [PATCH v6 4/4] guest agent: add guest agent RPCs/commands Michael Roth
                   ` (2 subsequent siblings)
  5 siblings, 1 reply; 25+ messages in thread
From: Michael Roth @ 2011-07-05 13:21 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 |  204 ++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 204 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..367b42d
--- /dev/null
+++ b/qapi-schema-guest.json
@@ -0,0 +1,204 @@
+# *-*- 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'} }
+{ 'command': 'guest-info',
+  'returns': 'GuestAgentInfo' }
+
+##
+# @guest-shutdown:
+#
+# Initiate guest-activated shutdown. Note: this is an asynchronous
+# shutdown request, with no guaruntee of successful shutdown. Errors
+# will be logged to guest's syslog.
+#
+# @mode: "halt", "powerdown", or "reboot"
+#
+# Returns: Nothing on success
+#
+# Since: 0.15.0
+##
+{ 'command': 'guest-shutdown', 'data': { 'mode': 'str' } }
+
+##
+# @guest-file-open:
+#
+# Open a file in the guest and retrieve a file handle for it
+#
+# @filepath: 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 @filepath cannot be opened, OpenFileFailed
+#
+# Since: 0.15.0
+##
+{ 'command': 'guest-file-open',
+  'data':    { 'filepath': '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 is not open, 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 is not opened, 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 @filehandle is not 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 @filehandle is not opened, OpenFileFailed
+#
+# Since: 0.15.0
+##
+{ 'command': 'guest-file-close',
+  'data': { 'filehandle': 'int' } }
+
+##
+# @guest-fsfreeze-status:
+#
+# get guest fsfreeze state
+#
+# Returns: GuestFsfreezeStatus (enumeration starts at 1)
+#
+# Since: 0.15.0
+##
+{ 'enum': 'GuestFsfreezeStatus',
+  'data': [ 'thawed', 'inprogress', 'frozen', 'error' ] }
+{ 'command': 'guest-fsfreeze-status',
+  'returns': 'GuestFsfreezeStatus' }
+
+##
+# @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] 25+ messages in thread

* [Qemu-devel] [PATCH v6 4/4] guest agent: add guest agent RPCs/commands
  2011-07-05 13:21 [Qemu-devel] [QAPI+QGA 3/3] QEMU Guest Agent (virtagent) v6 Michael Roth
                   ` (2 preceding siblings ...)
  2011-07-05 13:21 ` [Qemu-devel] [PATCH v6 3/4] guest agent: add guest agent commands schema file Michael Roth
@ 2011-07-05 13:21 ` Michael Roth
  2011-07-08 15:14   ` Luiz Capitulino
  2011-07-13 13:14 ` [Qemu-devel] [QAPI+QGA 3/3] QEMU Guest Agent (virtagent) v6 Daniel P. Berrange
  2011-07-14  2:53 ` Zhi Yong Wu
  5 siblings, 1 reply; 25+ messages in thread
From: Michael Roth @ 2011-07-05 13:21 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-shutdown
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.

Example usage:

  host:
    qemu -device virtio-serial \
         -chardev socket,path=/tmp/vs0.sock,server,nowait,id=qga0 \
         -device virtserialport,chardev=qga0,name=qga0
         ...

    echo "{'execute':'guest-info'}" | socat stdio \
         unix-connect:/tmp/qga0.sock

  guest:
    qemu-ga -c virtio-serial -p /dev/virtio-ports/qga0 \
            -p /var/run/qemu-guest-agent.pid -d

Signed-off-by: Michael Roth <mdroth@linux.vnet.ibm.com>
---
 Makefile                   |   15 ++-
 qemu-ga.c                  |    4 +
 qerror.h                   |    3 +
 qga/guest-agent-commands.c |  501 ++++++++++++++++++++++++++++++++++++++++++++
 qga/guest-agent-core.h     |    2 +
 5 files changed, 523 insertions(+), 2 deletions(-)
 create mode 100644 qga/guest-agent-commands.c

diff --git a/Makefile b/Makefile
index b2e8593..7e4f722 100644
--- a/Makefile
+++ b/Makefile
@@ -175,15 +175,26 @@ $(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-visitor.o: $(addprefix $(qapi-dir)/, test-qapi-types.c test-qapi-types.h test-qapi-visit.c test-qapi-visit.h)
 test-visitor: test-visitor.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
 
-QGALIB=qga/guest-agent-command-state.o
+QGALIB=qga/guest-agent-command-state.o qga/guest-agent-commands.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 $(QGALIB) 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 module.o qapi/qmp-dispatch.o qapi/qmp-registry.o
+qemu-ga$(EXESUF): qemu-ga.o $(QGALIB) 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 module.o qapi/qmp-dispatch.o qapi/qmp-registry.o $(qapi-dir)/qga-qapi-visit.o $(qapi-dir)/qga-qmp-marshal.o
 
 QEMULIBS=libhw32 libhw64 libuser libdis libdis-user
 
diff --git a/qemu-ga.c b/qemu-ga.c
index 649c16a..04ead22 100644
--- a/qemu-ga.c
+++ b/qemu-ga.c
@@ -637,6 +637,9 @@ int main(int argc, char **argv)
     g_log_set_default_handler(ga_log, s);
     g_log_set_fatal_mask(NULL, G_LOG_LEVEL_ERROR);
     s->logging_enabled = true;
+    s->command_state = ga_command_state_new();
+    ga_command_state_init(s, s->command_state);
+    ga_command_state_init_all(s->command_state);
     ga_state = s;
 
     module_call_init(MODULE_INIT_QAPI);
@@ -645,6 +648,7 @@ int main(int argc, char **argv)
 
     g_main_loop_run(ga_state->main_loop);
 
+    ga_command_state_cleanup_all(ga_state->command_state);
     unlink(pidfile);
 
     return 0;
diff --git a/qerror.h b/qerror.h
index 9a9fa5b..0f618ac 100644
--- a/qerror.h
+++ b/qerror.h
@@ -184,4 +184,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 */
diff --git a/qga/guest-agent-commands.c b/qga/guest-agent-commands.c
new file mode 100644
index 0000000..42390fb
--- /dev/null
+++ b/qga/guest-agent-commands.c
@@ -0,0 +1,501 @@
+/*
+ * 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"
+#include "qerror.h"
+#include "qemu-queue.h"
+
+static GAState *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 ga_logging_enabled() beforehand,
+ * and use the QERR_QGA_LOGGING_DISABLED to generate an error
+ */
+static void slog(const char *fmt, ...)
+{
+    va_list ap;
+
+    va_start(ap, fmt);
+    g_logv("syslog", G_LOG_LEVEL_INFO, fmt, ap);
+    va_end(ap);
+}
+
+int64_t qmp_guest_sync(int64_t id, Error **errp)
+{
+    return id;
+}
+
+void qmp_guest_ping(Error **err)
+{
+    slog("guest-ping called");
+}
+
+struct GuestAgentInfo *qmp_guest_info(Error **err)
+{
+    GuestAgentInfo *info = qemu_mallocz(sizeof(GuestAgentInfo));
+
+    info->version = g_strdup(QGA_VERSION);
+
+    return info;
+}
+
+void qmp_guest_shutdown(const char *mode, Error **err)
+{
+    int ret;
+    const char *shutdown_flag;
+
+    slog("guest-shutdown called, mode: %s", mode);
+    if (strcmp(mode, "halt") == 0) {
+        shutdown_flag = "-H";
+    } else if (strcmp(mode, "powerdown") == 0) {
+        shutdown_flag = "-P";
+    } else if (strcmp(mode, "reboot") == 0) {
+        shutdown_flag = "-r";
+    } else {
+        error_set(err, QERR_INVALID_PARAMETER_VALUE, "mode",
+                  "halt|powerdown|reboot");
+        return;
+    }
+
+    ret = fork();
+    if (ret == 0) {
+        /* child, start the shutdown */
+        setsid();
+        fclose(stdin);
+        fclose(stdout);
+        fclose(stderr);
+
+        sleep(5);
+        ret = execl("/sbin/shutdown", "shutdown", shutdown_flag, "+0",
+                    "hypervisor initiated shutdown", (char*)NULL);
+        if (ret) {
+            slog("guest-shutdown failed: %s", strerror(errno));
+        }
+        exit(!!ret);
+    } else if (ret < 0) {
+        error_set(err, QERR_UNDEFINED_ERROR);
+    }
+}
+
+typedef struct GuestFileHandle {
+    uint64_t id;
+    FILE *fh;
+    QTAILQ_ENTRY(GuestFileHandle) next;
+} GuestFileHandle;
+
+static struct {
+    QTAILQ_HEAD(, GuestFileHandle) filehandles;
+} guest_file_state;
+
+static void guest_file_handle_add(FILE *fh)
+{
+    GuestFileHandle *gfh;
+
+    gfh = qemu_mallocz(sizeof(GuestFileHandle));
+    gfh->id = fileno(fh);
+    gfh->fh = fh;
+    QTAILQ_INSERT_TAIL(&guest_file_state.filehandles, gfh, next);
+}
+
+static GuestFileHandle *guest_file_handle_find(int64_t id)
+{
+    GuestFileHandle *gfh;
+
+    QTAILQ_FOREACH(gfh, &guest_file_state.filehandles, next)
+    {
+        if (gfh->id == id) {
+            return gfh;
+        }
+    }
+
+    return NULL;
+}
+
+int64_t qmp_guest_file_open(const char *filepath, bool has_mode, const char *mode, Error **err)
+{
+    FILE *fh;
+    int fd;
+    int64_t ret = -1;
+
+    if (!has_mode) {
+        mode = "r";
+    }
+    slog("guest-file-open called, filepath: %s, mode: %s", filepath, mode);
+    fh = fopen(filepath, mode);
+    if (!fh) {
+        error_set(err, QERR_OPEN_FILE_FAILED, filepath);
+        return -1;
+    }
+
+    /* set fd non-blocking to avoid common use cases (like reading from a
+     * named pipe) from hanging the agent
+     */
+    fd = fileno(fh);
+    ret = fcntl(fd, F_GETFL);
+    ret = fcntl(fd, F_SETFL, ret | O_NONBLOCK);
+    if (ret == -1) {
+        error_set(err, QERR_OPEN_FILE_FAILED, filepath);
+        fclose(fh);
+        return -1;
+    }
+
+    guest_file_handle_add(fh);
+    slog("guest-file-open, filehandle: %d", fd);
+    return fd;
+}
+
+struct GuestFileRead *qmp_guest_file_read(int64_t filehandle, int64_t count,
+                                          Error **err)
+{
+    GuestFileHandle *gfh = guest_file_handle_find(filehandle);
+    GuestFileRead *read_data;
+    guchar *buf;
+    FILE *fh;
+    size_t read_count;
+
+    if (!gfh) {
+        error_set(err, QERR_FD_NOT_FOUND, "filehandle");
+        return NULL;
+    }
+
+    if (count < 0 || count > QGA_READ_LIMIT) {
+        error_set(err, QERR_INVALID_PARAMETER, "count");
+        return NULL;
+    }
+
+    fh = gfh->fh;
+    read_data = qemu_mallocz(sizeof(GuestFileRead) + 1);
+    buf = qemu_mallocz(count+1);
+    if (!buf) {
+        error_set(err, QERR_UNDEFINED_ERROR);
+        return NULL;
+    }
+
+    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);
+    }
+    qemu_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;
+    GuestFileHandle *gfh = guest_file_handle_find(filehandle);
+    FILE *fh;
+
+    if (!gfh) {
+        error_set(err, QERR_FD_NOT_FOUND, "filehandle");
+        return NULL;
+    }
+
+    fh = gfh->fh;
+    data = g_base64_decode(data_b64, &data_len);
+    if (count > data_len) {
+        qemu_free(data);
+        error_set(err, QERR_INVALID_PARAMETER, "count");
+        return NULL;
+    }
+    write_data = qemu_mallocz(sizeof(GuestFileWrite));
+    write_count = fwrite(data, 1, count, fh);
+    write_data->count = write_count;
+    write_data->eof = feof(fh);
+    qemu_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;
+    GuestFileHandle *gfh = guest_file_handle_find(filehandle);
+    FILE *fh;
+    int ret;
+
+    if (!gfh) {
+        error_set(err, QERR_FD_NOT_FOUND, "filehandle");
+        return NULL;
+    }
+
+    fh = gfh->fh;
+    seek_data = qemu_mallocz(sizeof(GuestFileRead));
+    ret = fseek(fh, offset, whence);
+    if (ret == -1) {
+        error_set(err, QERR_UNDEFINED_ERROR);
+        qemu_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)
+{
+    GuestFileHandle *gfh = guest_file_handle_find(filehandle);
+
+    slog("guest-file-close called, filehandle: %ld", filehandle);
+    if (!gfh) {
+        error_set(err, QERR_FD_NOT_FOUND, "filehandle");
+        return;
+    }
+
+    fclose(gfh->fh);
+    QTAILQ_REMOVE(&guest_file_state.filehandles, gfh, next);
+    qemu_free(gfh);
+}
+
+static void guest_file_init(void)
+{
+    QTAILQ_INIT(&guest_file_state.filehandles);
+}
+
+typedef struct GuestFsfreezeMount {
+    char *dirname;
+    char *devtype;
+    QTAILQ_ENTRY(GuestFsfreezeMount) next;
+} GuestFsfreezeMount;
+
+struct {
+    GuestFsfreezeStatus status;
+    QTAILQ_HEAD(, GuestFsfreezeMount) mount_list;
+} guest_fsfreeze_state;
+
+/*
+ * Walk the mount table and build a list of local file systems
+ */
+static int guest_fsfreeze_build_mount_list(void)
+{
+    struct mntent *ment;
+    GuestFsfreezeMount *mount, *temp;
+    char const *mtab = MOUNTED;
+    FILE *fp;
+
+    fp = setmntent(mtab, "r");
+    if (!fp) {
+        g_warning("fsfreeze: unable to read mtab");
+        goto fail;
+    }
+
+    while ((ment = getmntent(fp))) {
+        /*
+         * An entry which device name doesn't start with a '/' is
+         * either a dummy file system or a network file system.
+         * Add special handling for smbfs and cifs as is done by
+         * coreutils as well.
+         */
+        if ((ment->mnt_fsname[0] != '/') ||
+            (strcmp(ment->mnt_type, "smbfs") == 0) ||
+            (strcmp(ment->mnt_type, "cifs") == 0)) {
+            continue;
+        }
+
+        mount = qemu_mallocz(sizeof(GuestFsfreezeMount));
+        mount->dirname = qemu_strdup(ment->mnt_dir);
+        mount->devtype = qemu_strdup(ment->mnt_type);
+
+        QTAILQ_INSERT_TAIL(&guest_fsfreeze_state.mount_list, mount, next);
+    }
+
+    endmntent(fp);
+
+    return 0;
+
+fail:
+    QTAILQ_FOREACH_SAFE(mount, &guest_fsfreeze_state.mount_list, next, temp) {
+        QTAILQ_REMOVE(&guest_fsfreeze_state.mount_list, mount, next);
+        qemu_free(mount->dirname);
+        qemu_free(mount->devtype);
+        qemu_free(mount);
+    }
+
+    return -1;
+}
+
+/*
+ * Return status of freeze/thaw
+ */
+GuestFsfreezeStatus qmp_guest_fsfreeze_status(Error **err)
+{
+    return guest_fsfreeze_state.status;
+}
+
+/*
+ * Walk list of mounted file systems in the guest, and freeze the ones which
+ * are real local file systems.
+ */
+int64_t qmp_guest_fsfreeze_freeze(Error **err)
+{
+    int ret = 0, i = 0;
+    struct GuestFsfreezeMount *mount, *temp;
+    int fd;
+
+    slog("guest-fsfreeze called");
+
+    if (guest_fsfreeze_state.status != GUEST_FSFREEZE_STATUS_THAWED) {
+        ret = 0;
+        goto out;
+    }
+
+    ret = guest_fsfreeze_build_mount_list();
+    if (ret < 0) {
+        goto out;
+    }
+
+    guest_fsfreeze_state.status = GUEST_FSFREEZE_STATUS_INPROGRESS;
+
+    /* cannot risk guest agent blocking itself on a write in this state */
+    disable_logging();
+
+    QTAILQ_FOREACH_SAFE(mount, &guest_fsfreeze_state.mount_list, next, temp) {
+        fd = qemu_open(mount->dirname, O_RDONLY);
+        if (fd == -1) {
+            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);
+
+        i++;
+    }
+
+    guest_fsfreeze_state.status = GUEST_FSFREEZE_STATUS_FROZEN;
+    ret = i;
+out:
+    return ret;
+error:
+    if (i > 0) {
+        guest_fsfreeze_state.status = GUEST_FSFREEZE_STATUS_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;
+    GuestFsfreezeMount *mount, *temp;
+    int fd, i = 0;
+
+    if (guest_fsfreeze_state.status != GUEST_FSFREEZE_STATUS_FROZEN &&
+        guest_fsfreeze_state.status != GUEST_FSFREEZE_STATUS_INPROGRESS) {
+        ret = 0;
+        goto out_enable_logging;
+    }
+
+    QTAILQ_FOREACH_SAFE(mount, &guest_fsfreeze_state.mount_list, next, temp) {
+        fd = qemu_open(mount->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);
+
+        QTAILQ_REMOVE(&guest_fsfreeze_state.mount_list, mount, next);
+        qemu_free(mount->dirname);
+        qemu_free(mount->devtype);
+        qemu_free(mount);
+        i++;
+    }
+
+    guest_fsfreeze_state.status = GUEST_FSFREEZE_STATUS_THAWED;
+    ret = i;
+out_enable_logging:
+    enable_logging();
+out:
+    return ret;
+}
+
+static void guest_fsfreeze_init(void)
+{
+    guest_fsfreeze_state.status = GUEST_FSFREEZE_STATUS_THAWED;
+    QTAILQ_INIT(&guest_fsfreeze_state.mount_list);
+}
+
+static void guest_fsfreeze_cleanup(void)
+{
+    int64_t ret;
+    Error *err = NULL;
+
+    if (guest_fsfreeze_state.status == GUEST_FSFREEZE_STATUS_FROZEN) {
+        ret = qmp_guest_fsfreeze_thaw(&err);
+        if (ret < 0 || err) {
+            slog("failed to clean up frozen filesystems");
+        }
+    }
+}
+
+/* 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, guest_fsfreeze_init, guest_fsfreeze_cleanup);
+    ga_command_state_add(cs, guest_file_init, NULL);
+}
diff --git a/qga/guest-agent-core.h b/qga/guest-agent-core.h
index 66d1729..3501ff4 100644
--- a/qga/guest-agent-core.h
+++ b/qga/guest-agent-core.h
@@ -14,10 +14,12 @@
 #include "qemu-common.h"
 
 #define QGA_VERSION "1.0"
+#define QGA_READ_LIMIT 4 << 20 /* 4MB block size max for chunked reads */
 
 typedef struct GAState GAState;
 typedef struct GACommandState GACommandState;
 
+void ga_command_state_init(GAState *s, GACommandState *cs);
 void ga_command_state_add(GACommandState *cs,
                           void (*init)(void),
                           void (*cleanup)(void));
-- 
1.7.0.4

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

* Re: [Qemu-devel] [PATCH v6 2/4] guest agent: qemu-ga daemon
  2011-07-05 13:21 ` [Qemu-devel] [PATCH v6 2/4] guest agent: qemu-ga daemon Michael Roth
@ 2011-07-06  0:34   ` Michael Roth
  2011-07-08 14:36   ` Luiz Capitulino
  1 sibling, 0 replies; 25+ messages in thread
From: Michael Roth @ 2011-07-06  0:34 UTC (permalink / raw)
  To: Michael Roth
  Cc: agl, Jes.Sorensen, qemu-devel, lcapitulino, aliguori, MATSUDA, Daiki

Doh! Missed a final build test and this little bugger slipped in. Please 
apply the following patch to fix it, qapi-backport-set3-v6 has been 
updated accordingly. Thanks to Matsuda Daiki for the catch/patch!

--- qemu-ga.c.orig	2011-07-06 09:03:00.656139317 +0900
+++ qemu-ga.c	2011-07-06 09:03:10.783264699 +0900
@@ -54,7 +54,7 @@ static struct GAState *ga_state;

  static void quit_handler(int sig)
  {
-    g_debug("recieved signal num %d, quitting");
+    g_debug("recieved signal num %d, quitting", sig);

      if (g_main_loop_is_running(ga_state->main_loop)) {
          g_main_loop_quit(ga_state->main_loop);

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

* Re: [Qemu-devel] [PATCH v6 1/4] guest agent: command state class
  2011-07-05 13:21 ` [Qemu-devel] [PATCH v6 1/4] guest agent: command state class Michael Roth
@ 2011-07-08 14:25   ` Luiz Capitulino
  2011-07-08 20:22     ` Michael Roth
  0 siblings, 1 reply; 25+ messages in thread
From: Luiz Capitulino @ 2011-07-08 14:25 UTC (permalink / raw)
  To: Michael Roth; +Cc: aliguori, agl, qemu-devel, Jes.Sorensen

On Tue,  5 Jul 2011 08:21:37 -0500
Michael Roth <mdroth@linux.vnet.ibm.com> wrote:

> 
> Signed-off-by: Michael Roth <mdroth@linux.vnet.ibm.com>
> ---
>  Makefile                        |    4 ++-
>  configure                       |    1 +
>  qga/guest-agent-command-state.c |   73 +++++++++++++++++++++++++++++++++++++++
>  qga/guest-agent-core.h          |   25 +++++++++++++
>  4 files changed, 102 insertions(+), 1 deletions(-)
>  create mode 100644 qga/guest-agent-command-state.c
>  create mode 100644 qga/guest-agent-core.h

I'm not sure there's much value in having this as a separate patch, maybe
it should be folded in the next one.

> 
> diff --git a/Makefile b/Makefile
> index cbd2d77..6c3ba71 100644
> --- a/Makefile
> +++ b/Makefile
> @@ -181,6 +181,8 @@ test-visitor: test-visitor.o qfloat.o qint.o qdict.o qstring.o qlist.o qbool.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
>  
> +QGALIB=qga/guest-agent-command-state.o
> +
>  QEMULIBS=libhw32 libhw64 libuser libdis libdis-user
>  
>  clean:
> @@ -189,7 +191,7 @@ clean:
>  	rm -f qemu-options.def
>  	rm -f *.o *.d *.a *.lo $(TOOLS) TAGS cscope.* *.pod *~ */*~
>  	rm -Rf .libs
> -	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/*.o qapi/*.d
> +	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/*.o qapi/*.d 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
> diff --git a/configure b/configure
> index 02c552e..6a03002 100755
> --- a/configure
> +++ b/configure
> @@ -3487,6 +3487,7 @@ DIRS="$DIRS pc-bios/spapr-rtas"
>  DIRS="$DIRS roms/seabios roms/vgabios"
>  DIRS="$DIRS fsdev ui"
>  DIRS="$DIRS qapi"
> +DIRS="$DIRS qga"
>  FILES="Makefile tests/Makefile"
>  FILES="$FILES tests/cris/Makefile tests/cris/.gdbinit"
>  FILES="$FILES pc-bios/optionrom/Makefile pc-bios/keymaps"
> diff --git a/qga/guest-agent-command-state.c b/qga/guest-agent-command-state.c
> new file mode 100644
> index 0000000..bc6e0bd
> --- /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 = qemu_mallocz(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 = qemu_mallocz(sizeof(GACommandState));
> +    cs->groups = NULL;
> +    return cs;
> +}
> diff --git a/qga/guest-agent-core.h b/qga/guest-agent-core.h
> new file mode 100644
> index 0000000..688f120
> --- /dev/null
> +++ b/qga/guest-agent-core.h
> @@ -0,0 +1,25 @@
> +/*
> + * 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"
> +
> +#define QGA_VERSION "1.0"
> +
> +typedef struct GACommandState GACommandState;
> +
> +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);

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

* Re: [Qemu-devel] [PATCH v6 2/4] guest agent: qemu-ga daemon
  2011-07-05 13:21 ` [Qemu-devel] [PATCH v6 2/4] guest agent: qemu-ga daemon Michael Roth
  2011-07-06  0:34   ` Michael Roth
@ 2011-07-08 14:36   ` Luiz Capitulino
  2011-07-08 21:12     ` Michael Roth
  1 sibling, 1 reply; 25+ messages in thread
From: Luiz Capitulino @ 2011-07-08 14:36 UTC (permalink / raw)
  To: Michael Roth; +Cc: aliguori, agl, qemu-devel, Jes.Sorensen

On Tue,  5 Jul 2011 08:21:38 -0500
Michael Roth <mdroth@linux.vnet.ibm.com> wrote:

> 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

I think you meant -f /var/run/qemu-guest-agent.pid

> 
> Signed-off-by: Michael Roth <mdroth@linux.vnet.ibm.com>
> ---
>  Makefile               |   10 +-
>  qemu-ga.c              |  651 ++++++++++++++++++++++++++++++++++++++++++++++++
>  qga/guest-agent-core.h |    4 +
>  3 files changed, 661 insertions(+), 4 deletions(-)
>  create mode 100644 qemu-ga.c
> 
> diff --git a/Makefile b/Makefile
> index 6c3ba71..b2e8593 100644
> --- a/Makefile
> +++ b/Makefile
> @@ -140,7 +140,7 @@ endif
>  ######################################################################
>  
>  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
>  
> @@ -163,7 +163,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-visitor.o test-qmp-commands.o: QEMU_CFLAGS += -I $(qapi-dir)
> +$(qapi-obj-y) test-visitor.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
> @@ -183,13 +183,15 @@ test-qmp-commands: test-qmp-commands.o qfloat.o qint.o qdict.o qstring.o qlist.o
>  
>  QGALIB=qga/guest-agent-command-state.o
>  
> +qemu-ga$(EXESUF): qemu-ga.o $(QGALIB) 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 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 *.lo $(TOOLS) TAGS cscope.* *.pod *~ */*~
> +	rm -f *.o *.d *.a *.lo $(TOOLS) qemu-ga TAGS cscope.* *.pod *~ */*~
>  	rm -Rf .libs
>  	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/*.o qapi/*.d qga/*.o qga/*.d
>  	rm -f qemu-img-cmds.h
> @@ -385,4 +387,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 qapi/*.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
> new file mode 100644
> index 0000000..649c16a
> --- /dev/null
> +++ b/qemu-ga.c
> @@ -0,0 +1,651 @@
> +/*
> + * 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 "module.h"
> +#include "signal.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 {
> +    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;
> +    GLogLevelFlags log_level;
> +    FILE *log_file;
> +    bool logging_enabled;
> +};
> +
> +static struct GAState *ga_state;
> +
> +static void quit_handler(int sig)
> +{
> +    g_debug("recieved signal num %d, quitting");
> +
> +    if (g_main_loop_is_running(ga_state->main_loop)) {
> +        g_main_loop_quit(ga_state->main_loop);
> +    }
> +}
> +
> +static void register_signal_handlers(void)
> +{
> +    struct sigaction sigact;
> +    int ret;
> +
> +    sigact.sa_handler = quit_handler;
> +
> +    ret = sigaction(SIGINT, &sigact, NULL);
> +    if (ret == -1) {
> +        g_error("error configuring signal handler: %s", strerror(errno));
> +    }
> +    ret = sigaction(SIGTERM, &sigact, NULL);
> +    if (ret == -1) {
> +        g_error("error configuring signal handler: %s", strerror(errno));
> +    }
> +}
> +
> +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;
> +}
> +
> +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)
> +{
> +    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);
> +    }
> +}
> +
> +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_WRONLY|O_EXCL, S_IRUSR|S_IWUSR);
> +    if (pidfd == -1) {
> +        g_error("Cannot create pid file, %s", strerror(errno));
> +    }
> +
> +    if (asprintf(&pidstr, "%d", getpid()) == -1) {
> +        g_critical("Cannot allocate memory");
> +        goto fail;
> +    }
> +    if (write(pidfd, pidstr, strlen(pidstr)) != strlen(pidstr)) {
> +        free(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);
> +    free(pidstr);
> +    return;
> +
> +fail:
> +    unlink(pidfile);
> +    g_error("failed to daemonize");
> +}
> +
> +static int conn_channel_send_buf(GIOChannel *channel, const char *buf,
> +                                 gsize count)
> +{
> +    GError *err = NULL;
> +    gsize written = 0;
> +    GIOStatus status;
> +
> +    while (count) {
> +        status = g_io_channel_write_chars(channel, buf, count, &written, &err);
> +        g_debug("sending data, count: %d", (int)count);
> +        if (err != NULL) {
> +            g_warning("error sending newline: %s", err->message);
> +            return err->code;
> +        }
> +        if (status == G_IO_STATUS_ERROR || status == G_IO_STATUS_EOF) {
> +            return -EPIPE;
> +        }
> +
> +        if (status == G_IO_STATUS_NORMAL) {
> +            count -= written;
> +        }
> +    }
> +
> +    return 0;
> +}
> +
> +static int conn_channel_send_payload(GIOChannel *channel, QObject *payload)
> +{
> +    int ret = 0;
> +    const char *buf;
> +    QString *payload_qstr;
> +    GError *err = NULL;
> +
> +    g_assert(payload && channel);
> +
> +    payload_qstr = qobject_to_json(payload);
> +    if (!payload_qstr) {
> +        return -EINVAL;
> +    }

You can do:

 qstring_append_chr(payload_qstr, '\n');

so that you avoid the additional conn_channel_send_buf() call below.

> +
> +    buf = qstring_get_str(payload_qstr);
> +    ret = conn_channel_send_buf(channel, buf, strlen(buf));
> +    if (ret) {
> +        goto out_free;
> +    }
> +
> +    ret = conn_channel_send_buf(channel, "\n", 1);
> +    if (ret) {
> +        goto out_free;
> +    }
> +
> +    g_io_channel_flush(channel, &err);
> +    if (err != NULL) {
> +        g_warning("error flushing payload: %s", err->message);
> +        ret = err->code;
> +        goto out_free;
> +    }
> +
> +out_free:
> +    QDECREF(payload_qstr);
> +    if (err) {
> +        g_error_free(err);
> +    }
> +    return ret;
> +}
> +
> +static void process_command(GAState *s, QDict *req)
> +{
> +    QObject *rsp = NULL;
> +    int ret;
> +
> +    g_assert(req);
> +    g_debug("processing command");
> +    rsp = qmp_dispatch(QOBJECT(req));
> +    if (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) {
> +        qobject_decref(obj);
> +        g_warning("failed to parse event");

No error is returned to the client, is this intended?

> +        return;
> +    }
> +
> +    if (err) {
> +        error_free(err);
> +        g_warning("failed to parse event: %s", error_get_pretty(err));

Same here.

> +        return;
> +    }
> +
> +    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 {

And here.

> +        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);
> +        g_error_free(err);
> +        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");
> +        g_error_free(err);
> +        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);
> +        g_error_free(err);
> +        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);
> +        g_error_free(err);
> +        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, new==true
> + * indicates we should use the existing s->listen_channel
> + */
> +static int listen_channel_add(GAState *s, int listen_fd, bool new)
> +{
> +    GError *err = NULL;
> +    guint listen_id;
> +
> +    if (new) {
> +        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);
> +            g_error_free(err);
> +            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);
> +        g_error_free(err);
> +        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, false);
> +    } else if (strcmp(s->method, "virtio-serial") == 0) {
> +        /* we spin on EOF for virtio-serial, so back off a bit. also,
> +         * dont close the connection in this case, it'll resume normal
> +         * operation when another process connects to host chardev
> +         */
> +        usleep(100*1000);
> +        goto out_noclose;
> +    }
> +    g_io_channel_unref(s->conn_channel);
> +    s->conn_channel = NULL;
> +    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));
> +        }

Can't O_NONBLOCK and O_ASYNC be set as open() flags?

> +        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, true);
> +        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[] = {

Can be const.

> +        { "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;
> +
> +    while ((ch = getopt_long(argc, argv, sopt, lopt, &opt_ind)) != -1) {
> +        switch (ch) {
> +        case 'c':
> +            method = optarg;

Why don't you call this option 'm' ('method') then?

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

g_error() documentation from:

 http://developer.gnome.org/glib/2.29/glib-Message-Logging.html#g-error

Says:

 "This function will result in a core dump; don't use it for errors you expect.
  Using this function indicates a bug in your program, i.e. an assertion failure."

But I see it's being used beyond assertions.

> +        }
> +    }
> +
> +    if (daemonize) {
> +        g_debug("starting daemon");
> +        become_daemon(pidfile);
> +    }
> +
> +    g_type_init();
> +    g_thread_init(NULL);
> +
> +    s = qemu_mallocz(sizeof(GAState));
> +    s->conn_id = 0;
> +    s->conn_channel = NULL;
> +    s->path = path;
> +    s->method = method;
> +    s->log_file = log_file;
> +    s->log_level = log_level;
> +    g_log_set_default_handler(ga_log, s);
> +    g_log_set_fatal_mask(NULL, G_LOG_LEVEL_ERROR);
> +    s->logging_enabled = true;
> +    ga_state = s;
> +
> +    module_call_init(MODULE_INIT_QAPI);
> +    init_guest_agent(ga_state);
> +    register_signal_handlers();
> +
> +    g_main_loop_run(ga_state->main_loop);
> +
> +    unlink(pidfile);
> +
> +    return 0;
> +}
> diff --git a/qga/guest-agent-core.h b/qga/guest-agent-core.h
> index 688f120..66d1729 100644
> --- a/qga/guest-agent-core.h
> +++ b/qga/guest-agent-core.h
> @@ -15,6 +15,7 @@
>  
>  #define QGA_VERSION "1.0"
>  
> +typedef struct GAState GAState;
>  typedef struct GACommandState GACommandState;
>  
>  void ga_command_state_add(GACommandState *cs,
> @@ -23,3 +24,6 @@ void ga_command_state_add(GACommandState *cs,
>  void ga_command_state_init_all(GACommandState *cs);
>  void ga_command_state_cleanup_all(GACommandState *cs);
>  GACommandState *ga_command_state_new(void);
> +bool ga_logging_enabled(GAState *s);
> +void ga_disable_logging(GAState *s);
> +void ga_enable_logging(GAState *s);

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

* Re: [Qemu-devel] [PATCH v6 3/4] guest agent: add guest agent commands schema file
  2011-07-05 13:21 ` [Qemu-devel] [PATCH v6 3/4] guest agent: add guest agent commands schema file Michael Roth
@ 2011-07-08 15:08   ` Luiz Capitulino
  2011-07-08 21:42     ` Michael Roth
  0 siblings, 1 reply; 25+ messages in thread
From: Luiz Capitulino @ 2011-07-08 15:08 UTC (permalink / raw)
  To: Michael Roth; +Cc: aliguori, agl, qemu-devel, Jes.Sorensen

On Tue,  5 Jul 2011 08:21:39 -0500
Michael Roth <mdroth@linux.vnet.ibm.com> wrote:

> 
> Signed-off-by: Michael Roth <mdroth@linux.vnet.ibm.com>
> ---
>  qapi-schema-guest.json |  204 ++++++++++++++++++++++++++++++++++++++++++++++++
>  1 files changed, 204 insertions(+), 0 deletions(-)
>  create mode 100644 qapi-schema-guest.json

I think this should be folded in the next patch.

More comments below.

> 
> diff --git a/qapi-schema-guest.json b/qapi-schema-guest.json
> new file mode 100644
> index 0000000..367b42d
> --- /dev/null
> +++ b/qapi-schema-guest.json
> @@ -0,0 +1,204 @@
> +# *-*- 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'} }
> +{ 'command': 'guest-info',
> +  'returns': 'GuestAgentInfo' }
> +
> +##
> +# @guest-shutdown:
> +#
> +# Initiate guest-activated shutdown. Note: this is an asynchronous
> +# shutdown request, with no guaruntee of successful shutdown. Errors
> +# will be logged to guest's syslog.
> +#
> +# @mode: "halt", "powerdown", or "reboot"
> +#
> +# Returns: Nothing on success
> +#
> +# Since: 0.15.0
> +##
> +{ 'command': 'guest-shutdown', 'data': { 'mode': 'str' } }

Shouldn't 'mode' be optional?

> +
> +##
> +# @guest-file-open:
> +#
> +# Open a file in the guest and retrieve a file handle for it
> +#
> +# @filepath: 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 @filepath cannot be opened, OpenFileFailed
> +#
> +# Since: 0.15.0
> +##
> +{ 'command': 'guest-file-open',
> +  'data':    { 'filepath': 'str', '*mode': 'str' },
> +  'returns': 'int' }

You can use 'file-path'. Actually, I'd use just 'path'.

> +
> +##
> +# @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 is not open, 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' }

file-handle. Also, we have to say that the returned data is base64-encoded.

> +
> +##
> +# @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 is not opened, 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' }

data-b64

> +
> +##
> +# @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 @filehandle is not 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 @filehandle is not opened, OpenFileFailed
> +#
> +# Since: 0.15.0
> +##
> +{ 'command': 'guest-file-close',
> +  'data': { 'filehandle': 'int' } }
> +
> +##
> +# @guest-fsfreeze-status:
> +#
> +# get guest fsfreeze state
> +#
> +# Returns: GuestFsfreezeStatus (enumeration starts at 1)
> +#
> +# Since: 0.15.0
> +##
> +{ 'enum': 'GuestFsfreezeStatus',
> +  'data': [ 'thawed', 'inprogress', 'frozen', 'error' ] }
> +{ 'command': 'guest-fsfreeze-status',
> +  'returns': 'GuestFsfreezeStatus' }

hmm, I thought a qapi command implementation would return an enum (ie. int),
but the qapi would translate it to a string and return the string to the
client. However, this returns an int (as explained above).

Did I misunderstand how the qapi handles enums then?

> +
> +##
> +# @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' }

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

* Re: [Qemu-devel] [PATCH v6 4/4] guest agent: add guest agent RPCs/commands
  2011-07-05 13:21 ` [Qemu-devel] [PATCH v6 4/4] guest agent: add guest agent RPCs/commands Michael Roth
@ 2011-07-08 15:14   ` Luiz Capitulino
  2011-07-11 20:11     ` Michael Roth
  0 siblings, 1 reply; 25+ messages in thread
From: Luiz Capitulino @ 2011-07-08 15:14 UTC (permalink / raw)
  To: Michael Roth; +Cc: aliguori, agl, qemu-devel, Jes.Sorensen

On Tue,  5 Jul 2011 08:21:40 -0500
Michael Roth <mdroth@linux.vnet.ibm.com> wrote:

> This adds the initial set of QMP/QAPI commands provided by the guest
> agent:
> 
> guest-sync
> guest-ping
> guest-info
> guest-shutdown
> 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.
> 
> Example usage:
> 
>   host:
>     qemu -device virtio-serial \
>          -chardev socket,path=/tmp/vs0.sock,server,nowait,id=qga0 \
>          -device virtserialport,chardev=qga0,name=qga0
>          ...
> 
>     echo "{'execute':'guest-info'}" | socat stdio \
>          unix-connect:/tmp/qga0.sock
> 
>   guest:
>     qemu-ga -c virtio-serial -p /dev/virtio-ports/qga0 \
>             -p /var/run/qemu-guest-agent.pid -d
> 
> Signed-off-by: Michael Roth <mdroth@linux.vnet.ibm.com>
> ---
>  Makefile                   |   15 ++-
>  qemu-ga.c                  |    4 +
>  qerror.h                   |    3 +
>  qga/guest-agent-commands.c |  501 ++++++++++++++++++++++++++++++++++++++++++++
>  qga/guest-agent-core.h     |    2 +
>  5 files changed, 523 insertions(+), 2 deletions(-)
>  create mode 100644 qga/guest-agent-commands.c
> 
> diff --git a/Makefile b/Makefile
> index b2e8593..7e4f722 100644
> --- a/Makefile
> +++ b/Makefile
> @@ -175,15 +175,26 @@ $(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-visitor.o: $(addprefix $(qapi-dir)/, test-qapi-types.c test-qapi-types.h test-qapi-visit.c test-qapi-visit.h)
>  test-visitor: test-visitor.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
>  
> -QGALIB=qga/guest-agent-command-state.o
> +QGALIB=qga/guest-agent-command-state.o qga/guest-agent-commands.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 $(QGALIB) 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 module.o qapi/qmp-dispatch.o qapi/qmp-registry.o
> +qemu-ga$(EXESUF): qemu-ga.o $(QGALIB) 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 module.o qapi/qmp-dispatch.o qapi/qmp-registry.o $(qapi-dir)/qga-qapi-visit.o $(qapi-dir)/qga-qmp-marshal.o
>  
>  QEMULIBS=libhw32 libhw64 libuser libdis libdis-user
>  
> diff --git a/qemu-ga.c b/qemu-ga.c
> index 649c16a..04ead22 100644
> --- a/qemu-ga.c
> +++ b/qemu-ga.c
> @@ -637,6 +637,9 @@ int main(int argc, char **argv)
>      g_log_set_default_handler(ga_log, s);
>      g_log_set_fatal_mask(NULL, G_LOG_LEVEL_ERROR);
>      s->logging_enabled = true;
> +    s->command_state = ga_command_state_new();
> +    ga_command_state_init(s, s->command_state);
> +    ga_command_state_init_all(s->command_state);
>      ga_state = s;
>  
>      module_call_init(MODULE_INIT_QAPI);
> @@ -645,6 +648,7 @@ int main(int argc, char **argv)
>  
>      g_main_loop_run(ga_state->main_loop);
>  
> +    ga_command_state_cleanup_all(ga_state->command_state);
>      unlink(pidfile);
>  
>      return 0;
> diff --git a/qerror.h b/qerror.h
> index 9a9fa5b..0f618ac 100644
> --- a/qerror.h
> +++ b/qerror.h
> @@ -184,4 +184,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 */
> diff --git a/qga/guest-agent-commands.c b/qga/guest-agent-commands.c
> new file mode 100644
> index 0000000..42390fb
> --- /dev/null
> +++ b/qga/guest-agent-commands.c
> @@ -0,0 +1,501 @@
> +/*
> + * 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"
> +#include "qerror.h"
> +#include "qemu-queue.h"
> +
> +static GAState *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 ga_logging_enabled() beforehand,
> + * and use the QERR_QGA_LOGGING_DISABLED to generate an error
> + */
> +static void slog(const char *fmt, ...)
> +{
> +    va_list ap;
> +
> +    va_start(ap, fmt);
> +    g_logv("syslog", G_LOG_LEVEL_INFO, fmt, ap);
> +    va_end(ap);
> +}
> +
> +int64_t qmp_guest_sync(int64_t id, Error **errp)
> +{
> +    return id;
> +}
> +
> +void qmp_guest_ping(Error **err)
> +{
> +    slog("guest-ping called");
> +}
> +
> +struct GuestAgentInfo *qmp_guest_info(Error **err)
> +{
> +    GuestAgentInfo *info = qemu_mallocz(sizeof(GuestAgentInfo));
> +
> +    info->version = g_strdup(QGA_VERSION);
> +
> +    return info;
> +}
> +
> +void qmp_guest_shutdown(const char *mode, Error **err)
> +{
> +    int ret;
> +    const char *shutdown_flag;
> +
> +    slog("guest-shutdown called, mode: %s", mode);
> +    if (strcmp(mode, "halt") == 0) {
> +        shutdown_flag = "-H";
> +    } else if (strcmp(mode, "powerdown") == 0) {
> +        shutdown_flag = "-P";
> +    } else if (strcmp(mode, "reboot") == 0) {
> +        shutdown_flag = "-r";
> +    } else {
> +        error_set(err, QERR_INVALID_PARAMETER_VALUE, "mode",
> +                  "halt|powerdown|reboot");
> +        return;
> +    }
> +
> +    ret = fork();
> +    if (ret == 0) {
> +        /* child, start the shutdown */
> +        setsid();
> +        fclose(stdin);
> +        fclose(stdout);
> +        fclose(stderr);
> +
> +        sleep(5);

If we're required to return a response before the shutdown happens, this
is a bug and I'm afraid that the right way to this is a bit complex.

Otherwise we can just leave it out.

> +        ret = execl("/sbin/shutdown", "shutdown", shutdown_flag, "+0",
> +                    "hypervisor initiated shutdown", (char*)NULL);
> +        if (ret) {
> +            slog("guest-shutdown failed: %s", strerror(errno));
> +        }
> +        exit(!!ret);
> +    } else if (ret < 0) {
> +        error_set(err, QERR_UNDEFINED_ERROR);
> +    }
> +}
> +
> +typedef struct GuestFileHandle {
> +    uint64_t id;
> +    FILE *fh;
> +    QTAILQ_ENTRY(GuestFileHandle) next;
> +} GuestFileHandle;
> +
> +static struct {
> +    QTAILQ_HEAD(, GuestFileHandle) filehandles;
> +} guest_file_state;
> +
> +static void guest_file_handle_add(FILE *fh)
> +{
> +    GuestFileHandle *gfh;
> +
> +    gfh = qemu_mallocz(sizeof(GuestFileHandle));
> +    gfh->id = fileno(fh);
> +    gfh->fh = fh;
> +    QTAILQ_INSERT_TAIL(&guest_file_state.filehandles, gfh, next);
> +}
> +
> +static GuestFileHandle *guest_file_handle_find(int64_t id)
> +{
> +    GuestFileHandle *gfh;
> +
> +    QTAILQ_FOREACH(gfh, &guest_file_state.filehandles, next)
> +    {
> +        if (gfh->id == id) {
> +            return gfh;
> +        }
> +    }
> +
> +    return NULL;
> +}
> +
> +int64_t qmp_guest_file_open(const char *filepath, bool has_mode, const char *mode, Error **err)
> +{
> +    FILE *fh;
> +    int fd;
> +    int64_t ret = -1;
> +
> +    if (!has_mode) {
> +        mode = "r";
> +    }
> +    slog("guest-file-open called, filepath: %s, mode: %s", filepath, mode);
> +    fh = fopen(filepath, mode);
> +    if (!fh) {
> +        error_set(err, QERR_OPEN_FILE_FAILED, filepath);
> +        return -1;
> +    }
> +
> +    /* set fd non-blocking to avoid common use cases (like reading from a
> +     * named pipe) from hanging the agent
> +     */
> +    fd = fileno(fh);
> +    ret = fcntl(fd, F_GETFL);
> +    ret = fcntl(fd, F_SETFL, ret | O_NONBLOCK);
> +    if (ret == -1) {
> +        error_set(err, QERR_OPEN_FILE_FAILED, filepath);
> +        fclose(fh);
> +        return -1;
> +    }
> +
> +    guest_file_handle_add(fh);
> +    slog("guest-file-open, filehandle: %d", fd);
> +    return fd;
> +}
> +
> +struct GuestFileRead *qmp_guest_file_read(int64_t filehandle, int64_t count,
> +                                          Error **err)
> +{
> +    GuestFileHandle *gfh = guest_file_handle_find(filehandle);
> +    GuestFileRead *read_data;
> +    guchar *buf;
> +    FILE *fh;
> +    size_t read_count;
> +
> +    if (!gfh) {
> +        error_set(err, QERR_FD_NOT_FOUND, "filehandle");
> +        return NULL;
> +    }
> +
> +    if (count < 0 || count > QGA_READ_LIMIT) {
> +        error_set(err, QERR_INVALID_PARAMETER, "count");
> +        return NULL;
> +    }

Are we imposing that limit because of the malloc() call below? If that's
the case I think it's wrong, because we don't know the VM (neither the guest)
better than the client.

The best thing we can do here is to limit it to the file size. Additionally
to this we could have a command-line option to allow the sysadmin set his/her
own limit.

> +
> +    fh = gfh->fh;
> +    read_data = qemu_mallocz(sizeof(GuestFileRead) + 1);
> +    buf = qemu_mallocz(count+1);
> +    if (!buf) {
> +        error_set(err, QERR_UNDEFINED_ERROR);
> +        return NULL;
> +    }

qemu_malloc() functions never fail...

> +
> +    read_count = fread(buf, 1, count, fh);

Isn't 'nmemb' and 'size' swapped?

> +    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);
> +    }
> +    qemu_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;
> +    GuestFileHandle *gfh = guest_file_handle_find(filehandle);
> +    FILE *fh;
> +
> +    if (!gfh) {
> +        error_set(err, QERR_FD_NOT_FOUND, "filehandle");
> +        return NULL;
> +    }
> +
> +    fh = gfh->fh;
> +    data = g_base64_decode(data_b64, &data_len);
> +    if (count > data_len) {
> +        qemu_free(data);
> +        error_set(err, QERR_INVALID_PARAMETER, "count");
> +        return NULL;
> +    }
> +    write_data = qemu_mallocz(sizeof(GuestFileWrite));
> +    write_count = fwrite(data, 1, count, fh);
> +    write_data->count = write_count;
> +    write_data->eof = feof(fh);
> +    qemu_free(data);
> +    clearerr(fh);

Shouldn't we check for errors instead of doing this?

Btw, I think it's a good idea to offer guest-file-flush() too (or do a flush()
here, but that's probably bad).

> +
> +    return write_data;
> +}
> +
> +struct GuestFileSeek *qmp_guest_file_seek(int64_t filehandle, int64_t offset,
> +                                          int64_t whence, Error **err)
> +{
> +    GuestFileSeek *seek_data;
> +    GuestFileHandle *gfh = guest_file_handle_find(filehandle);
> +    FILE *fh;
> +    int ret;
> +
> +    if (!gfh) {
> +        error_set(err, QERR_FD_NOT_FOUND, "filehandle");
> +        return NULL;
> +    }
> +
> +    fh = gfh->fh;
> +    seek_data = qemu_mallocz(sizeof(GuestFileRead));
> +    ret = fseek(fh, offset, whence);
> +    if (ret == -1) {
> +        error_set(err, QERR_UNDEFINED_ERROR);
> +        qemu_free(seek_data);
> +        return NULL;
> +    }
> +    seek_data->position = ftell(fh);
> +    seek_data->eof = feof(fh);
> +    clearerr(fh);

Again, I don't see why we should do this.

> +
> +    return seek_data;
> +}
> +
> +void qmp_guest_file_close(int64_t filehandle, Error **err)
> +{
> +    GuestFileHandle *gfh = guest_file_handle_find(filehandle);
> +
> +    slog("guest-file-close called, filehandle: %ld", filehandle);
> +    if (!gfh) {
> +        error_set(err, QERR_FD_NOT_FOUND, "filehandle");
> +        return;
> +    }
> +
> +    fclose(gfh->fh);
> +    QTAILQ_REMOVE(&guest_file_state.filehandles, gfh, next);
> +    qemu_free(gfh);
> +}
> +
> +static void guest_file_init(void)
> +{
> +    QTAILQ_INIT(&guest_file_state.filehandles);
> +}
> +
> +typedef struct GuestFsfreezeMount {
> +    char *dirname;
> +    char *devtype;
> +    QTAILQ_ENTRY(GuestFsfreezeMount) next;
> +} GuestFsfreezeMount;
> +
> +struct {
> +    GuestFsfreezeStatus status;
> +    QTAILQ_HEAD(, GuestFsfreezeMount) mount_list;
> +} guest_fsfreeze_state;
> +
> +/*
> + * Walk the mount table and build a list of local file systems
> + */
> +static int guest_fsfreeze_build_mount_list(void)
> +{
> +    struct mntent *ment;
> +    GuestFsfreezeMount *mount, *temp;
> +    char const *mtab = MOUNTED;
> +    FILE *fp;
> +
> +    fp = setmntent(mtab, "r");
> +    if (!fp) {
> +        g_warning("fsfreeze: unable to read mtab");
> +        goto fail;
> +    }
> +
> +    while ((ment = getmntent(fp))) {
> +        /*
> +         * An entry which device name doesn't start with a '/' is
> +         * either a dummy file system or a network file system.
> +         * Add special handling for smbfs and cifs as is done by
> +         * coreutils as well.
> +         */
> +        if ((ment->mnt_fsname[0] != '/') ||
> +            (strcmp(ment->mnt_type, "smbfs") == 0) ||
> +            (strcmp(ment->mnt_type, "cifs") == 0)) {
> +            continue;
> +        }
> +
> +        mount = qemu_mallocz(sizeof(GuestFsfreezeMount));
> +        mount->dirname = qemu_strdup(ment->mnt_dir);
> +        mount->devtype = qemu_strdup(ment->mnt_type);
> +
> +        QTAILQ_INSERT_TAIL(&guest_fsfreeze_state.mount_list, mount, next);
> +    }
> +
> +    endmntent(fp);
> +
> +    return 0;
> +
> +fail:
> +    QTAILQ_FOREACH_SAFE(mount, &guest_fsfreeze_state.mount_list, next, temp) {
> +        QTAILQ_REMOVE(&guest_fsfreeze_state.mount_list, mount, next);
> +        qemu_free(mount->dirname);
> +        qemu_free(mount->devtype);
> +        qemu_free(mount);
> +    }

This doesn't seem to be used.

> +
> +    return -1;
> +}
> +
> +/*
> + * Return status of freeze/thaw
> + */
> +GuestFsfreezeStatus qmp_guest_fsfreeze_status(Error **err)
> +{
> +    return guest_fsfreeze_state.status;
> +}
> +
> +/*
> + * Walk list of mounted file systems in the guest, and freeze the ones which
> + * are real local file systems.
> + */
> +int64_t qmp_guest_fsfreeze_freeze(Error **err)
> +{
> +    int ret = 0, i = 0;
> +    struct GuestFsfreezeMount *mount, *temp;
> +    int fd;
> +
> +    slog("guest-fsfreeze called");
> +
> +    if (guest_fsfreeze_state.status != GUEST_FSFREEZE_STATUS_THAWED) {

return 0;

> +        ret = 0;
> +        goto out;
> +    }
> +
> +    ret = guest_fsfreeze_build_mount_list();
> +    if (ret < 0) {

return ret;

> +        goto out;
> +    }
> +
> +    guest_fsfreeze_state.status = GUEST_FSFREEZE_STATUS_INPROGRESS;
> +
> +    /* cannot risk guest agent blocking itself on a write in this state */
> +    disable_logging();
> +
> +    QTAILQ_FOREACH_SAFE(mount, &guest_fsfreeze_state.mount_list, next, temp) {
> +        fd = qemu_open(mount->dirname, O_RDONLY);
> +        if (fd == -1) {
> +            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);
> +
> +        i++;
> +    }
> +
> +    guest_fsfreeze_state.status = GUEST_FSFREEZE_STATUS_FROZEN;
> +    ret = i;
> +out:
> +    return ret;
> +error:
> +    if (i > 0) {
> +        guest_fsfreeze_state.status = GUEST_FSFREEZE_STATUS_ERROR;
> +    }

Shouldn't you undo everything that has been done so far? Which is
freeing the build list and thawing the file-systems that were frozen
before the 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;
> +    GuestFsfreezeMount *mount, *temp;
> +    int fd, i = 0;
> +
> +    if (guest_fsfreeze_state.status != GUEST_FSFREEZE_STATUS_FROZEN &&
> +        guest_fsfreeze_state.status != GUEST_FSFREEZE_STATUS_INPROGRESS) {

I don't follow why we're checking against INPROGRESS here.

> +        ret = 0;
> +        goto out_enable_logging;
> +    }
> +
> +    QTAILQ_FOREACH_SAFE(mount, &guest_fsfreeze_state.mount_list, next, temp) {
> +        fd = qemu_open(mount->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;

Shouldn't you continue and try to thaw the other file-systems in the list?

> +        }
> +        close(fd);
> +
> +        QTAILQ_REMOVE(&guest_fsfreeze_state.mount_list, mount, next);
> +        qemu_free(mount->dirname);
> +        qemu_free(mount->devtype);
> +        qemu_free(mount);
> +        i++;
> +    }
> +
> +    guest_fsfreeze_state.status = GUEST_FSFREEZE_STATUS_THAWED;
> +    ret = i;
> +out_enable_logging:
> +    enable_logging();
> +out:
> +    return ret;
> +}
> +
> +static void guest_fsfreeze_init(void)
> +{
> +    guest_fsfreeze_state.status = GUEST_FSFREEZE_STATUS_THAWED;
> +    QTAILQ_INIT(&guest_fsfreeze_state.mount_list);
> +}
> +
> +static void guest_fsfreeze_cleanup(void)
> +{
> +    int64_t ret;
> +    Error *err = NULL;
> +
> +    if (guest_fsfreeze_state.status == GUEST_FSFREEZE_STATUS_FROZEN) {
> +        ret = qmp_guest_fsfreeze_thaw(&err);
> +        if (ret < 0 || err) {
> +            slog("failed to clean up frozen filesystems");
> +        }
> +    }
> +}
> +
> +/* 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, guest_fsfreeze_init, guest_fsfreeze_cleanup);
> +    ga_command_state_add(cs, guest_file_init, NULL);
> +}
> diff --git a/qga/guest-agent-core.h b/qga/guest-agent-core.h
> index 66d1729..3501ff4 100644
> --- a/qga/guest-agent-core.h
> +++ b/qga/guest-agent-core.h
> @@ -14,10 +14,12 @@
>  #include "qemu-common.h"
>  
>  #define QGA_VERSION "1.0"
> +#define QGA_READ_LIMIT 4 << 20 /* 4MB block size max for chunked reads */
>  
>  typedef struct GAState GAState;
>  typedef struct GACommandState GACommandState;
>  
> +void ga_command_state_init(GAState *s, GACommandState *cs);
>  void ga_command_state_add(GACommandState *cs,
>                            void (*init)(void),
>                            void (*cleanup)(void));

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

* Re: [Qemu-devel] [PATCH v6 1/4] guest agent: command state class
  2011-07-08 14:25   ` Luiz Capitulino
@ 2011-07-08 20:22     ` Michael Roth
  0 siblings, 0 replies; 25+ messages in thread
From: Michael Roth @ 2011-07-08 20:22 UTC (permalink / raw)
  To: Luiz Capitulino; +Cc: aliguori, agl, qemu-devel, Jes.Sorensen

On 07/08/2011 09:25 AM, Luiz Capitulino wrote:
> On Tue,  5 Jul 2011 08:21:37 -0500
> Michael Roth<mdroth@linux.vnet.ibm.com>  wrote:
>
>>
>> Signed-off-by: Michael Roth<mdroth@linux.vnet.ibm.com>
>> ---
>>   Makefile                        |    4 ++-
>>   configure                       |    1 +
>>   qga/guest-agent-command-state.c |   73 +++++++++++++++++++++++++++++++++++++++
>>   qga/guest-agent-core.h          |   25 +++++++++++++
>>   4 files changed, 102 insertions(+), 1 deletions(-)
>>   create mode 100644 qga/guest-agent-command-state.c
>>   create mode 100644 qga/guest-agent-core.h
>
> I'm not sure there's much value in having this as a separate patch, maybe
> it should be folded in the next one.
>

I can, just trying to make it more review-friendly really. Same with 
breaking out the schema from the command implementations. If needed I 
can respin a more squashed version when things look ready, but this 
stuff is pretty simple and modularized so it doesn't seem like a bad 
idea to pull it out of the meatier patch.

>>
>> diff --git a/Makefile b/Makefile
>> index cbd2d77..6c3ba71 100644
>> --- a/Makefile
>> +++ b/Makefile
>> @@ -181,6 +181,8 @@ test-visitor: test-visitor.o qfloat.o qint.o qdict.o qstring.o qlist.o qbool.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
>>
>> +QGALIB=qga/guest-agent-command-state.o
>> +
>>   QEMULIBS=libhw32 libhw64 libuser libdis libdis-user
>>
>>   clean:
>> @@ -189,7 +191,7 @@ clean:
>>   	rm -f qemu-options.def
>>   	rm -f *.o *.d *.a *.lo $(TOOLS) TAGS cscope.* *.pod *~ */*~
>>   	rm -Rf .libs
>> -	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/*.o qapi/*.d
>> +	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/*.o qapi/*.d 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
>> diff --git a/configure b/configure
>> index 02c552e..6a03002 100755
>> --- a/configure
>> +++ b/configure
>> @@ -3487,6 +3487,7 @@ DIRS="$DIRS pc-bios/spapr-rtas"
>>   DIRS="$DIRS roms/seabios roms/vgabios"
>>   DIRS="$DIRS fsdev ui"
>>   DIRS="$DIRS qapi"
>> +DIRS="$DIRS qga"
>>   FILES="Makefile tests/Makefile"
>>   FILES="$FILES tests/cris/Makefile tests/cris/.gdbinit"
>>   FILES="$FILES pc-bios/optionrom/Makefile pc-bios/keymaps"
>> diff --git a/qga/guest-agent-command-state.c b/qga/guest-agent-command-state.c
>> new file mode 100644
>> index 0000000..bc6e0bd
>> --- /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 = qemu_mallocz(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 = qemu_mallocz(sizeof(GACommandState));
>> +    cs->groups = NULL;
>> +    return cs;
>> +}
>> diff --git a/qga/guest-agent-core.h b/qga/guest-agent-core.h
>> new file mode 100644
>> index 0000000..688f120
>> --- /dev/null
>> +++ b/qga/guest-agent-core.h
>> @@ -0,0 +1,25 @@
>> +/*
>> + * 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"
>> +
>> +#define QGA_VERSION "1.0"
>> +
>> +typedef struct GACommandState GACommandState;
>> +
>> +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);
>

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

* Re: [Qemu-devel] [PATCH v6 2/4] guest agent: qemu-ga daemon
  2011-07-08 14:36   ` Luiz Capitulino
@ 2011-07-08 21:12     ` Michael Roth
  0 siblings, 0 replies; 25+ messages in thread
From: Michael Roth @ 2011-07-08 21:12 UTC (permalink / raw)
  To: Luiz Capitulino; +Cc: aliguori, agl, qemu-devel, Jes.Sorensen

On 07/08/2011 09:36 AM, Luiz Capitulino wrote:
> On Tue,  5 Jul 2011 08:21:38 -0500
> Michael Roth<mdroth@linux.vnet.ibm.com>  wrote:
>
>> 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
>
> I think you meant -f /var/run/qemu-guest-agent.pid
>

Yup, sorry.

>>
>> Signed-off-by: Michael Roth<mdroth@linux.vnet.ibm.com>
>> ---
>>   Makefile               |   10 +-
>>   qemu-ga.c              |  651 ++++++++++++++++++++++++++++++++++++++++++++++++
>>   qga/guest-agent-core.h |    4 +
>>   3 files changed, 661 insertions(+), 4 deletions(-)
>>   create mode 100644 qemu-ga.c
>>
>> diff --git a/Makefile b/Makefile
>> index 6c3ba71..b2e8593 100644
>> --- a/Makefile
>> +++ b/Makefile
>> @@ -140,7 +140,7 @@ endif
>>   ######################################################################
>>
>>   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
>>
>> @@ -163,7 +163,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-visitor.o test-qmp-commands.o: QEMU_CFLAGS += -I $(qapi-dir)
>> +$(qapi-obj-y) test-visitor.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
>> @@ -183,13 +183,15 @@ test-qmp-commands: test-qmp-commands.o qfloat.o qint.o qdict.o qstring.o qlist.o
>>
>>   QGALIB=qga/guest-agent-command-state.o
>>
>> +qemu-ga$(EXESUF): qemu-ga.o $(QGALIB) 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 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 *.lo $(TOOLS) TAGS cscope.* *.pod *~ */*~
>> +	rm -f *.o *.d *.a *.lo $(TOOLS) qemu-ga TAGS cscope.* *.pod *~ */*~
>>   	rm -Rf .libs
>>   	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/*.o qapi/*.d qga/*.o qga/*.d
>>   	rm -f qemu-img-cmds.h
>> @@ -385,4 +387,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 qapi/*.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
>> new file mode 100644
>> index 0000000..649c16a
>> --- /dev/null
>> +++ b/qemu-ga.c
>> @@ -0,0 +1,651 @@
>> +/*
>> + * 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 "module.h"
>> +#include "signal.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 {
>> +    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;
>> +    GLogLevelFlags log_level;
>> +    FILE *log_file;
>> +    bool logging_enabled;
>> +};
>> +
>> +static struct GAState *ga_state;
>> +
>> +static void quit_handler(int sig)
>> +{
>> +    g_debug("recieved signal num %d, quitting");
>> +
>> +    if (g_main_loop_is_running(ga_state->main_loop)) {
>> +        g_main_loop_quit(ga_state->main_loop);
>> +    }
>> +}
>> +
>> +static void register_signal_handlers(void)
>> +{
>> +    struct sigaction sigact;
>> +    int ret;
>> +
>> +    sigact.sa_handler = quit_handler;
>> +
>> +    ret = sigaction(SIGINT,&sigact, NULL);
>> +    if (ret == -1) {
>> +        g_error("error configuring signal handler: %s", strerror(errno));
>> +    }
>> +    ret = sigaction(SIGTERM,&sigact, NULL);
>> +    if (ret == -1) {
>> +        g_error("error configuring signal handler: %s", strerror(errno));
>> +    }
>> +}
>> +
>> +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;
>> +}
>> +
>> +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)
>> +{
>> +    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);
>> +    }
>> +}
>> +
>> +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_WRONLY|O_EXCL, S_IRUSR|S_IWUSR);
>> +    if (pidfd == -1) {
>> +        g_error("Cannot create pid file, %s", strerror(errno));
>> +    }
>> +
>> +    if (asprintf(&pidstr, "%d", getpid()) == -1) {
>> +        g_critical("Cannot allocate memory");
>> +        goto fail;
>> +    }
>> +    if (write(pidfd, pidstr, strlen(pidstr)) != strlen(pidstr)) {
>> +        free(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);
>> +    free(pidstr);
>> +    return;
>> +
>> +fail:
>> +    unlink(pidfile);
>> +    g_error("failed to daemonize");
>> +}
>> +
>> +static int conn_channel_send_buf(GIOChannel *channel, const char *buf,
>> +                                 gsize count)
>> +{
>> +    GError *err = NULL;
>> +    gsize written = 0;
>> +    GIOStatus status;
>> +
>> +    while (count) {
>> +        status = g_io_channel_write_chars(channel, buf, count,&written,&err);
>> +        g_debug("sending data, count: %d", (int)count);
>> +        if (err != NULL) {
>> +            g_warning("error sending newline: %s", err->message);
>> +            return err->code;
>> +        }
>> +        if (status == G_IO_STATUS_ERROR || status == G_IO_STATUS_EOF) {
>> +            return -EPIPE;
>> +        }
>> +
>> +        if (status == G_IO_STATUS_NORMAL) {
>> +            count -= written;
>> +        }
>> +    }
>> +
>> +    return 0;
>> +}
>> +
>> +static int conn_channel_send_payload(GIOChannel *channel, QObject *payload)
>> +{
>> +    int ret = 0;
>> +    const char *buf;
>> +    QString *payload_qstr;
>> +    GError *err = NULL;
>> +
>> +    g_assert(payload&&  channel);
>> +
>> +    payload_qstr = qobject_to_json(payload);
>> +    if (!payload_qstr) {
>> +        return -EINVAL;
>> +    }
>
> You can do:
>
>   qstring_append_chr(payload_qstr, '\n');
>
> so that you avoid the additional conn_channel_send_buf() call below.
>

Nice!

>> +
>> +    buf = qstring_get_str(payload_qstr);
>> +    ret = conn_channel_send_buf(channel, buf, strlen(buf));
>> +    if (ret) {
>> +        goto out_free;
>> +    }
>> +
>> +    ret = conn_channel_send_buf(channel, "\n", 1);
>> +    if (ret) {
>> +        goto out_free;
>> +    }
>> +
>> +    g_io_channel_flush(channel,&err);
>> +    if (err != NULL) {
>> +        g_warning("error flushing payload: %s", err->message);
>> +        ret = err->code;
>> +        goto out_free;
>> +    }
>> +
>> +out_free:
>> +    QDECREF(payload_qstr);
>> +    if (err) {
>> +        g_error_free(err);
>> +    }
>> +    return ret;
>> +}
>> +
>> +static void process_command(GAState *s, QDict *req)
>> +{
>> +    QObject *rsp = NULL;
>> +    int ret;
>> +
>> +    g_assert(req);
>> +    g_debug("processing command");
>> +    rsp = qmp_dispatch(QOBJECT(req));
>> +    if (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) {
>> +        qobject_decref(obj);
>> +        g_warning("failed to parse event");
>
> No error is returned to the client, is this intended?
>

Hmm, nope... originally the errors got rolled into the response in 
qmp_dispatch(). But when I reworked qmp_dispatch() to take a pre-parsed 
QDict instead of a token list this functionality was lost..

Which makes me wonder if I should move the parsing back into 
qmp_dispatch()... I'll take a look. But yah, these errors should get 
returned.

>> +        return;
>> +    }
>> +
>> +    if (err) {
>> +        error_free(err);
>> +        g_warning("failed to parse event: %s", error_get_pretty(err));
>
> Same here.
>
>> +        return;
>> +    }
>> +
>> +    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 {
>
> And here.
>
>> +        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);
>> +        g_error_free(err);
>> +        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");
>> +        g_error_free(err);
>> +        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);
>> +        g_error_free(err);
>> +        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);
>> +        g_error_free(err);
>> +        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, new==true
>> + * indicates we should use the existing s->listen_channel
>> + */
>> +static int listen_channel_add(GAState *s, int listen_fd, bool new)
>> +{
>> +    GError *err = NULL;
>> +    guint listen_id;
>> +
>> +    if (new) {
>> +        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);
>> +            g_error_free(err);
>> +            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);
>> +        g_error_free(err);
>> +        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, false);
>> +    } else if (strcmp(s->method, "virtio-serial") == 0) {
>> +        /* we spin on EOF for virtio-serial, so back off a bit. also,
>> +         * dont close the connection in this case, it'll resume normal
>> +         * operation when another process connects to host chardev
>> +         */
>> +        usleep(100*1000);
>> +        goto out_noclose;
>> +    }
>> +    g_io_channel_unref(s->conn_channel);
>> +    s->conn_channel = NULL;
>> +    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));
>> +        }
>
> Can't O_NONBLOCK and O_ASYNC be set as open() flags?
>
>> +        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, true);
>> +        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[] = {
>
> Can be const.
>
>> +        { "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;
>> +
>> +    while ((ch = getopt_long(argc, argv, sopt, lopt,&opt_ind)) != -1) {
>> +        switch (ch) {
>> +        case 'c':
>> +            method = optarg;
>
> Why don't you call this option 'm' ('method') then?
>

That's probably better.

>> +            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]);
>
> g_error() documentation from:
>
>   http://developer.gnome.org/glib/2.29/glib-Message-Logging.html#g-error
>
> Says:
>
>   "This function will result in a core dump; don't use it for errors you expect.
>    Using this function indicates a bug in your program, i.e. an assertion failure."
>
> But I see it's being used beyond assertions.
>

I've been using it to bail on unrecoverable error states. Might be a bit 
extreme for unknown options though.

>> +        }
>> +    }
>> +
>> +    if (daemonize) {
>> +        g_debug("starting daemon");
>> +        become_daemon(pidfile);
>> +    }
>> +
>> +    g_type_init();
>> +    g_thread_init(NULL);
>> +
>> +    s = qemu_mallocz(sizeof(GAState));
>> +    s->conn_id = 0;
>> +    s->conn_channel = NULL;
>> +    s->path = path;
>> +    s->method = method;
>> +    s->log_file = log_file;
>> +    s->log_level = log_level;
>> +    g_log_set_default_handler(ga_log, s);
>> +    g_log_set_fatal_mask(NULL, G_LOG_LEVEL_ERROR);
>> +    s->logging_enabled = true;
>> +    ga_state = s;
>> +
>> +    module_call_init(MODULE_INIT_QAPI);
>> +    init_guest_agent(ga_state);
>> +    register_signal_handlers();
>> +
>> +    g_main_loop_run(ga_state->main_loop);
>> +
>> +    unlink(pidfile);
>> +
>> +    return 0;
>> +}
>> diff --git a/qga/guest-agent-core.h b/qga/guest-agent-core.h
>> index 688f120..66d1729 100644
>> --- a/qga/guest-agent-core.h
>> +++ b/qga/guest-agent-core.h
>> @@ -15,6 +15,7 @@
>>
>>   #define QGA_VERSION "1.0"
>>
>> +typedef struct GAState GAState;
>>   typedef struct GACommandState GACommandState;
>>
>>   void ga_command_state_add(GACommandState *cs,
>> @@ -23,3 +24,6 @@ void ga_command_state_add(GACommandState *cs,
>>   void ga_command_state_init_all(GACommandState *cs);
>>   void ga_command_state_cleanup_all(GACommandState *cs);
>>   GACommandState *ga_command_state_new(void);
>> +bool ga_logging_enabled(GAState *s);
>> +void ga_disable_logging(GAState *s);
>> +void ga_enable_logging(GAState *s);
>

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

* Re: [Qemu-devel] [PATCH v6 3/4] guest agent: add guest agent commands schema file
  2011-07-08 15:08   ` Luiz Capitulino
@ 2011-07-08 21:42     ` Michael Roth
  0 siblings, 0 replies; 25+ messages in thread
From: Michael Roth @ 2011-07-08 21:42 UTC (permalink / raw)
  To: Luiz Capitulino; +Cc: aliguori, agl, qemu-devel, Jes.Sorensen

On 07/08/2011 10:08 AM, Luiz Capitulino wrote:
> On Tue,  5 Jul 2011 08:21:39 -0500
> Michael Roth<mdroth@linux.vnet.ibm.com>  wrote:
>
>>
>> Signed-off-by: Michael Roth<mdroth@linux.vnet.ibm.com>
>> ---
>>   qapi-schema-guest.json |  204 ++++++++++++++++++++++++++++++++++++++++++++++++
>>   1 files changed, 204 insertions(+), 0 deletions(-)
>>   create mode 100644 qapi-schema-guest.json
>
> I think this should be folded in the next patch.
>
> More comments below.
>
>>
>> diff --git a/qapi-schema-guest.json b/qapi-schema-guest.json
>> new file mode 100644
>> index 0000000..367b42d
>> --- /dev/null
>> +++ b/qapi-schema-guest.json
>> @@ -0,0 +1,204 @@
>> +# *-*- 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'} }
>> +{ 'command': 'guest-info',
>> +  'returns': 'GuestAgentInfo' }
>> +
>> +##
>> +# @guest-shutdown:
>> +#
>> +# Initiate guest-activated shutdown. Note: this is an asynchronous
>> +# shutdown request, with no guaruntee of successful shutdown. Errors
>> +# will be logged to guest's syslog.
>> +#
>> +# @mode: "halt", "powerdown", or "reboot"
>> +#
>> +# Returns: Nothing on success
>> +#
>> +# Since: 0.15.0
>> +##
>> +{ 'command': 'guest-shutdown', 'data': { 'mode': 'str' } }
>
> Shouldn't 'mode' be optional?
>
>> +
>> +##
>> +# @guest-file-open:
>> +#
>> +# Open a file in the guest and retrieve a file handle for it
>> +#
>> +# @filepath: 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 @filepath cannot be opened, OpenFileFailed
>> +#
>> +# Since: 0.15.0
>> +##
>> +{ 'command': 'guest-file-open',
>> +  'data':    { 'filepath': 'str', '*mode': 'str' },
>> +  'returns': 'int' }
>
> You can use 'file-path'. Actually, I'd use just 'path'.
>
>> +
>> +##
>> +# @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 is not open, 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' }
>
> file-handle. Also, we have to say that the returned data is base64-encoded.
>
>> +
>> +##
>> +# @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 is not opened, 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' }
>
> data-b64
>
>> +
>> +##
>> +# @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 @filehandle is not 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 @filehandle is not opened, OpenFileFailed
>> +#
>> +# Since: 0.15.0
>> +##
>> +{ 'command': 'guest-file-close',
>> +  'data': { 'filehandle': 'int' } }
>> +
>> +##
>> +# @guest-fsfreeze-status:
>> +#
>> +# get guest fsfreeze state
>> +#
>> +# Returns: GuestFsfreezeStatus (enumeration starts at 1)
>> +#
>> +# Since: 0.15.0
>> +##
>> +{ 'enum': 'GuestFsfreezeStatus',
>> +  'data': [ 'thawed', 'inprogress', 'frozen', 'error' ] }
>> +{ 'command': 'guest-fsfreeze-status',
>> +  'returns': 'GuestFsfreezeStatus' }
>
> hmm, I thought a qapi command implementation would return an enum (ie. int),
> but the qapi would translate it to a string and return the string to the
> client. However, this returns an int (as explained above).
>
> Did I misunderstand how the qapi handles enums then?
>

No, I think you're right... the original code generation seemed to 
support this at least. With the switchover to visitor functions Anthony 
treated enum types as integers when handling input/output, but now that 
I look at it again there was a commented-out block that suggested a 
possible TODO here.

It could be done by generating a lookup table for each qapi-defined enum 
and then passing that to qmp_input_type_enum()/qapi_output_type_enum()

Sure would make the enum functionality more useful :) I'll see if I can 
work it into set2 without too much churn.

>> +
>> +##
>> +# @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' }
>

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

* Re: [Qemu-devel] [PATCH v6 4/4] guest agent: add guest agent RPCs/commands
  2011-07-08 15:14   ` Luiz Capitulino
@ 2011-07-11 20:11     ` Michael Roth
  2011-07-11 21:12       ` Luiz Capitulino
  0 siblings, 1 reply; 25+ messages in thread
From: Michael Roth @ 2011-07-11 20:11 UTC (permalink / raw)
  To: Luiz Capitulino; +Cc: aliguori, agl, qemu-devel, Jes.Sorensen

On 07/08/2011 10:14 AM, Luiz Capitulino wrote:
> On Tue,  5 Jul 2011 08:21:40 -0500
> Michael Roth<mdroth@linux.vnet.ibm.com>  wrote:
>
>> This adds the initial set of QMP/QAPI commands provided by the guest
>> agent:
>>
>> guest-sync
>> guest-ping
>> guest-info
>> guest-shutdown
>> 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.
>>
>> Example usage:
>>
>>    host:
>>      qemu -device virtio-serial \
>>           -chardev socket,path=/tmp/vs0.sock,server,nowait,id=qga0 \
>>           -device virtserialport,chardev=qga0,name=qga0
>>           ...
>>
>>      echo "{'execute':'guest-info'}" | socat stdio \
>>           unix-connect:/tmp/qga0.sock
>>
>>    guest:
>>      qemu-ga -c virtio-serial -p /dev/virtio-ports/qga0 \
>>              -p /var/run/qemu-guest-agent.pid -d
>>
>> Signed-off-by: Michael Roth<mdroth@linux.vnet.ibm.com>
>> ---
>>   Makefile                   |   15 ++-
>>   qemu-ga.c                  |    4 +
>>   qerror.h                   |    3 +
>>   qga/guest-agent-commands.c |  501 ++++++++++++++++++++++++++++++++++++++++++++
>>   qga/guest-agent-core.h     |    2 +
>>   5 files changed, 523 insertions(+), 2 deletions(-)
>>   create mode 100644 qga/guest-agent-commands.c
>>
>> diff --git a/Makefile b/Makefile
>> index b2e8593..7e4f722 100644
>> --- a/Makefile
>> +++ b/Makefile
>> @@ -175,15 +175,26 @@ $(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-visitor.o: $(addprefix $(qapi-dir)/, test-qapi-types.c test-qapi-types.h test-qapi-visit.c test-qapi-visit.h)
>>   test-visitor: test-visitor.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
>>
>> -QGALIB=qga/guest-agent-command-state.o
>> +QGALIB=qga/guest-agent-command-state.o qga/guest-agent-commands.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 $(QGALIB) 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 module.o qapi/qmp-dispatch.o qapi/qmp-registry.o
>> +qemu-ga$(EXESUF): qemu-ga.o $(QGALIB) 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 module.o qapi/qmp-dispatch.o qapi/qmp-registry.o $(qapi-dir)/qga-qapi-visit.o $(qapi-dir)/qga-qmp-marshal.o
>>
>>   QEMULIBS=libhw32 libhw64 libuser libdis libdis-user
>>
>> diff --git a/qemu-ga.c b/qemu-ga.c
>> index 649c16a..04ead22 100644
>> --- a/qemu-ga.c
>> +++ b/qemu-ga.c
>> @@ -637,6 +637,9 @@ int main(int argc, char **argv)
>>       g_log_set_default_handler(ga_log, s);
>>       g_log_set_fatal_mask(NULL, G_LOG_LEVEL_ERROR);
>>       s->logging_enabled = true;
>> +    s->command_state = ga_command_state_new();
>> +    ga_command_state_init(s, s->command_state);
>> +    ga_command_state_init_all(s->command_state);
>>       ga_state = s;
>>
>>       module_call_init(MODULE_INIT_QAPI);
>> @@ -645,6 +648,7 @@ int main(int argc, char **argv)
>>
>>       g_main_loop_run(ga_state->main_loop);
>>
>> +    ga_command_state_cleanup_all(ga_state->command_state);
>>       unlink(pidfile);
>>
>>       return 0;
>> diff --git a/qerror.h b/qerror.h
>> index 9a9fa5b..0f618ac 100644
>> --- a/qerror.h
>> +++ b/qerror.h
>> @@ -184,4 +184,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 */
>> diff --git a/qga/guest-agent-commands.c b/qga/guest-agent-commands.c
>> new file mode 100644
>> index 0000000..42390fb
>> --- /dev/null
>> +++ b/qga/guest-agent-commands.c
>> @@ -0,0 +1,501 @@
>> +/*
>> + * 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"
>> +#include "qerror.h"
>> +#include "qemu-queue.h"
>> +
>> +static GAState *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 ga_logging_enabled() beforehand,
>> + * and use the QERR_QGA_LOGGING_DISABLED to generate an error
>> + */
>> +static void slog(const char *fmt, ...)
>> +{
>> +    va_list ap;
>> +
>> +    va_start(ap, fmt);
>> +    g_logv("syslog", G_LOG_LEVEL_INFO, fmt, ap);
>> +    va_end(ap);
>> +}
>> +
>> +int64_t qmp_guest_sync(int64_t id, Error **errp)
>> +{
>> +    return id;
>> +}
>> +
>> +void qmp_guest_ping(Error **err)
>> +{
>> +    slog("guest-ping called");
>> +}
>> +
>> +struct GuestAgentInfo *qmp_guest_info(Error **err)
>> +{
>> +    GuestAgentInfo *info = qemu_mallocz(sizeof(GuestAgentInfo));
>> +
>> +    info->version = g_strdup(QGA_VERSION);
>> +
>> +    return info;
>> +}
>> +
>> +void qmp_guest_shutdown(const char *mode, Error **err)
>> +{
>> +    int ret;
>> +    const char *shutdown_flag;
>> +
>> +    slog("guest-shutdown called, mode: %s", mode);
>> +    if (strcmp(mode, "halt") == 0) {
>> +        shutdown_flag = "-H";
>> +    } else if (strcmp(mode, "powerdown") == 0) {
>> +        shutdown_flag = "-P";
>> +    } else if (strcmp(mode, "reboot") == 0) {
>> +        shutdown_flag = "-r";
>> +    } else {
>> +        error_set(err, QERR_INVALID_PARAMETER_VALUE, "mode",
>> +                  "halt|powerdown|reboot");
>> +        return;
>> +    }
>> +
>> +    ret = fork();
>> +    if (ret == 0) {
>> +        /* child, start the shutdown */
>> +        setsid();
>> +        fclose(stdin);
>> +        fclose(stdout);
>> +        fclose(stderr);
>> +
>> +        sleep(5);
>
> If we're required to return a response before the shutdown happens, this
> is a bug and I'm afraid that the right way to this is a bit complex.
>
> Otherwise we can just leave it out.
>

Yah, I ran this by Anthony and Adam and just leaving it out seems to be 
the preferred approach, for now at least.

>> +        ret = execl("/sbin/shutdown", "shutdown", shutdown_flag, "+0",
>> +                    "hypervisor initiated shutdown", (char*)NULL);
>> +        if (ret) {
>> +            slog("guest-shutdown failed: %s", strerror(errno));
>> +        }
>> +        exit(!!ret);
>> +    } else if (ret<  0) {
>> +        error_set(err, QERR_UNDEFINED_ERROR);
>> +    }
>> +}
>> +
>> +typedef struct GuestFileHandle {
>> +    uint64_t id;
>> +    FILE *fh;
>> +    QTAILQ_ENTRY(GuestFileHandle) next;
>> +} GuestFileHandle;
>> +
>> +static struct {
>> +    QTAILQ_HEAD(, GuestFileHandle) filehandles;
>> +} guest_file_state;
>> +
>> +static void guest_file_handle_add(FILE *fh)
>> +{
>> +    GuestFileHandle *gfh;
>> +
>> +    gfh = qemu_mallocz(sizeof(GuestFileHandle));
>> +    gfh->id = fileno(fh);
>> +    gfh->fh = fh;
>> +    QTAILQ_INSERT_TAIL(&guest_file_state.filehandles, gfh, next);
>> +}
>> +
>> +static GuestFileHandle *guest_file_handle_find(int64_t id)
>> +{
>> +    GuestFileHandle *gfh;
>> +
>> +    QTAILQ_FOREACH(gfh,&guest_file_state.filehandles, next)
>> +    {
>> +        if (gfh->id == id) {
>> +            return gfh;
>> +        }
>> +    }
>> +
>> +    return NULL;
>> +}
>> +
>> +int64_t qmp_guest_file_open(const char *filepath, bool has_mode, const char *mode, Error **err)
>> +{
>> +    FILE *fh;
>> +    int fd;
>> +    int64_t ret = -1;
>> +
>> +    if (!has_mode) {
>> +        mode = "r";
>> +    }
>> +    slog("guest-file-open called, filepath: %s, mode: %s", filepath, mode);
>> +    fh = fopen(filepath, mode);
>> +    if (!fh) {
>> +        error_set(err, QERR_OPEN_FILE_FAILED, filepath);
>> +        return -1;
>> +    }
>> +
>> +    /* set fd non-blocking to avoid common use cases (like reading from a
>> +     * named pipe) from hanging the agent
>> +     */
>> +    fd = fileno(fh);
>> +    ret = fcntl(fd, F_GETFL);
>> +    ret = fcntl(fd, F_SETFL, ret | O_NONBLOCK);
>> +    if (ret == -1) {
>> +        error_set(err, QERR_OPEN_FILE_FAILED, filepath);
>> +        fclose(fh);
>> +        return -1;
>> +    }
>> +
>> +    guest_file_handle_add(fh);
>> +    slog("guest-file-open, filehandle: %d", fd);
>> +    return fd;
>> +}
>> +
>> +struct GuestFileRead *qmp_guest_file_read(int64_t filehandle, int64_t count,
>> +                                          Error **err)
>> +{
>> +    GuestFileHandle *gfh = guest_file_handle_find(filehandle);
>> +    GuestFileRead *read_data;
>> +    guchar *buf;
>> +    FILE *fh;
>> +    size_t read_count;
>> +
>> +    if (!gfh) {
>> +        error_set(err, QERR_FD_NOT_FOUND, "filehandle");
>> +        return NULL;
>> +    }
>> +
>> +    if (count<  0 || count>  QGA_READ_LIMIT) {
>> +        error_set(err, QERR_INVALID_PARAMETER, "count");
>> +        return NULL;
>> +    }
>
> Are we imposing that limit because of the malloc() call below? If that's
> the case I think it's wrong, because we don't know the VM (neither the guest)
> better than the client.
>
> The best thing we can do here is to limit it to the file size. Additionally
> to this we could have a command-line option to allow the sysadmin set his/her
> own limit.
>

That's technically the better approach, but we're also bound by the 
maximum token size limit in the JSON parser, which is 64MB. Best we can 
do is set QGA_READ_LIMIT accordingly.

>> +
>> +    fh = gfh->fh;
>> +    read_data = qemu_mallocz(sizeof(GuestFileRead) + 1);
>> +    buf = qemu_mallocz(count+1);
>> +    if (!buf) {
>> +        error_set(err, QERR_UNDEFINED_ERROR);
>> +        return NULL;
>> +    }
>
> qemu_malloc() functions never fail...
>
>> +
>> +    read_count = fread(buf, 1, count, fh);
>
> Isn't 'nmemb' and 'size' swapped?
>

I'm not sure...

size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);

I wrote it as $count number of 1-bytes elements. This seems logical to 
me, since it's a non-blocking FD which way result in a partial of some 
lesser number of bytes than count.

>> +    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);
>> +    }
>> +    qemu_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;
>> +    GuestFileHandle *gfh = guest_file_handle_find(filehandle);
>> +    FILE *fh;
>> +
>> +    if (!gfh) {
>> +        error_set(err, QERR_FD_NOT_FOUND, "filehandle");
>> +        return NULL;
>> +    }
>> +
>> +    fh = gfh->fh;
>> +    data = g_base64_decode(data_b64,&data_len);
>> +    if (count>  data_len) {
>> +        qemu_free(data);
>> +        error_set(err, QERR_INVALID_PARAMETER, "count");
>> +        return NULL;
>> +    }
>> +    write_data = qemu_mallocz(sizeof(GuestFileWrite));
>> +    write_count = fwrite(data, 1, count, fh);
>> +    write_data->count = write_count;
>> +    write_data->eof = feof(fh);
>> +    qemu_free(data);
>> +    clearerr(fh);
>
> Shouldn't we check for errors instead of doing this?
>

Yah...unfortunately we only get a boolean flag with ferror() so it's not 
all that useful, but I can add an error flag to the calls to capture it 
at least. clearerr() is only being used here to clear the eof flag.

> Btw, I think it's a good idea to offer guest-file-flush() too (or do a flush()
> here, but that's probably bad).
>

Yah, I'd been planning on adding a guest-file-flush() for a while now. 
I'll add that for the respin.

>> +
>> +    return write_data;
>> +}
>> +
>> +struct GuestFileSeek *qmp_guest_file_seek(int64_t filehandle, int64_t offset,
>> +                                          int64_t whence, Error **err)
>> +{
>> +    GuestFileSeek *seek_data;
>> +    GuestFileHandle *gfh = guest_file_handle_find(filehandle);
>> +    FILE *fh;
>> +    int ret;
>> +
>> +    if (!gfh) {
>> +        error_set(err, QERR_FD_NOT_FOUND, "filehandle");
>> +        return NULL;
>> +    }
>> +
>> +    fh = gfh->fh;
>> +    seek_data = qemu_mallocz(sizeof(GuestFileRead));
>> +    ret = fseek(fh, offset, whence);
>> +    if (ret == -1) {
>> +        error_set(err, QERR_UNDEFINED_ERROR);
>> +        qemu_free(seek_data);
>> +        return NULL;
>> +    }
>> +    seek_data->position = ftell(fh);
>> +    seek_data->eof = feof(fh);
>> +    clearerr(fh);
>
> Again, I don't see why we should do this.
>
>> +
>> +    return seek_data;
>> +}
>> +
>> +void qmp_guest_file_close(int64_t filehandle, Error **err)
>> +{
>> +    GuestFileHandle *gfh = guest_file_handle_find(filehandle);
>> +
>> +    slog("guest-file-close called, filehandle: %ld", filehandle);
>> +    if (!gfh) {
>> +        error_set(err, QERR_FD_NOT_FOUND, "filehandle");
>> +        return;
>> +    }
>> +
>> +    fclose(gfh->fh);
>> +    QTAILQ_REMOVE(&guest_file_state.filehandles, gfh, next);
>> +    qemu_free(gfh);
>> +}
>> +
>> +static void guest_file_init(void)
>> +{
>> +    QTAILQ_INIT(&guest_file_state.filehandles);
>> +}
>> +
>> +typedef struct GuestFsfreezeMount {
>> +    char *dirname;
>> +    char *devtype;
>> +    QTAILQ_ENTRY(GuestFsfreezeMount) next;
>> +} GuestFsfreezeMount;
>> +
>> +struct {
>> +    GuestFsfreezeStatus status;
>> +    QTAILQ_HEAD(, GuestFsfreezeMount) mount_list;
>> +} guest_fsfreeze_state;
>> +
>> +/*
>> + * Walk the mount table and build a list of local file systems
>> + */
>> +static int guest_fsfreeze_build_mount_list(void)
>> +{
>> +    struct mntent *ment;
>> +    GuestFsfreezeMount *mount, *temp;
>> +    char const *mtab = MOUNTED;
>> +    FILE *fp;
>> +
>> +    fp = setmntent(mtab, "r");
>> +    if (!fp) {
>> +        g_warning("fsfreeze: unable to read mtab");
>> +        goto fail;
>> +    }
>> +
>> +    while ((ment = getmntent(fp))) {
>> +        /*
>> +         * An entry which device name doesn't start with a '/' is
>> +         * either a dummy file system or a network file system.
>> +         * Add special handling for smbfs and cifs as is done by
>> +         * coreutils as well.
>> +         */
>> +        if ((ment->mnt_fsname[0] != '/') ||
>> +            (strcmp(ment->mnt_type, "smbfs") == 0) ||
>> +            (strcmp(ment->mnt_type, "cifs") == 0)) {
>> +            continue;
>> +        }
>> +
>> +        mount = qemu_mallocz(sizeof(GuestFsfreezeMount));
>> +        mount->dirname = qemu_strdup(ment->mnt_dir);
>> +        mount->devtype = qemu_strdup(ment->mnt_type);
>> +
>> +        QTAILQ_INSERT_TAIL(&guest_fsfreeze_state.mount_list, mount, next);
>> +    }
>> +
>> +    endmntent(fp);
>> +
>> +    return 0;
>> +
>> +fail:
>> +    QTAILQ_FOREACH_SAFE(mount,&guest_fsfreeze_state.mount_list, next, temp) {
>> +        QTAILQ_REMOVE(&guest_fsfreeze_state.mount_list, mount, next);
>> +        qemu_free(mount->dirname);
>> +        qemu_free(mount->devtype);
>> +        qemu_free(mount);
>> +    }
>
> This doesn't seem to be used.
>

It can get used even a 2nd invocation of this function gets called that 
results in a goto fail. But looking again this should be done 
unconditionally at the start of the function, since the mount list is 
part of global state now.

>> +
>> +    return -1;
>> +}
>> +
>> +/*
>> + * Return status of freeze/thaw
>> + */
>> +GuestFsfreezeStatus qmp_guest_fsfreeze_status(Error **err)
>> +{
>> +    return guest_fsfreeze_state.status;
>> +}
>> +
>> +/*
>> + * Walk list of mounted file systems in the guest, and freeze the ones which
>> + * are real local file systems.
>> + */
>> +int64_t qmp_guest_fsfreeze_freeze(Error **err)
>> +{
>> +    int ret = 0, i = 0;
>> +    struct GuestFsfreezeMount *mount, *temp;
>> +    int fd;
>> +
>> +    slog("guest-fsfreeze called");
>> +
>> +    if (guest_fsfreeze_state.status != GUEST_FSFREEZE_STATUS_THAWED) {
>
> return 0;
>
>> +        ret = 0;
>> +        goto out;
>> +    }
>> +
>> +    ret = guest_fsfreeze_build_mount_list();
>> +    if (ret<  0) {
>
> return ret;
>
>> +        goto out;
>> +    }
>> +
>> +    guest_fsfreeze_state.status = GUEST_FSFREEZE_STATUS_INPROGRESS;
>> +
>> +    /* cannot risk guest agent blocking itself on a write in this state */
>> +    disable_logging();
>> +
>> +    QTAILQ_FOREACH_SAFE(mount,&guest_fsfreeze_state.mount_list, next, temp) {
>> +        fd = qemu_open(mount->dirname, O_RDONLY);
>> +        if (fd == -1) {
>> +            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);
>> +
>> +        i++;
>> +    }
>> +
>> +    guest_fsfreeze_state.status = GUEST_FSFREEZE_STATUS_FROZEN;
>> +    ret = i;
>> +out:
>> +    return ret;
>> +error:
>> +    if (i>  0) {
>> +        guest_fsfreeze_state.status = GUEST_FSFREEZE_STATUS_ERROR;
>> +    }
>
> Shouldn't you undo everything that has been done so far? Which is
> freeing the build list and thawing the file-systems that were frozen
> before the error?
>

We can...the way it's done right now is you get a count of how many 
filesystems were frozen, along an error status. Depending on the 
error/count the user can either ignore the error, do what it is they 
want to do, then call thaw(), or just immediately call thaw().

So we can do an automatic thaw() on their behalf, but i figured the 
status was good enough.

>> +    goto out;
>> +}
>> +
>> +/*
>> + * Walk list of frozen file systems in the guest, and thaw them.
>> + */
>> +int64_t qmp_guest_fsfreeze_thaw(Error **err)
>> +{
>> +    int ret;
>> +    GuestFsfreezeMount *mount, *temp;
>> +    int fd, i = 0;
>> +
>> +    if (guest_fsfreeze_state.status != GUEST_FSFREEZE_STATUS_FROZEN&&
>> +        guest_fsfreeze_state.status != GUEST_FSFREEZE_STATUS_INPROGRESS) {
>
> I don't follow why we're checking against INPROGRESS here.
>

To prevent a race I believe...but we're synchronous now so that's 
probably no longer needed. I'll look it over and remove it if that's the 
case.

>> +        ret = 0;
>> +        goto out_enable_logging;
>> +    }
>> +
>> +    QTAILQ_FOREACH_SAFE(mount,&guest_fsfreeze_state.mount_list, next, temp) {
>> +        fd = qemu_open(mount->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;
>
> Shouldn't you continue and try to thaw the other file-systems in the list?
>

That's probably better

>> +        }
>> +        close(fd);
>> +
>> +        QTAILQ_REMOVE(&guest_fsfreeze_state.mount_list, mount, next);
>> +        qemu_free(mount->dirname);
>> +        qemu_free(mount->devtype);
>> +        qemu_free(mount);
>> +        i++;
>> +    }
>> +
>> +    guest_fsfreeze_state.status = GUEST_FSFREEZE_STATUS_THAWED;
>> +    ret = i;
>> +out_enable_logging:
>> +    enable_logging();
>> +out:
>> +    return ret;
>> +}
>> +
>> +static void guest_fsfreeze_init(void)
>> +{
>> +    guest_fsfreeze_state.status = GUEST_FSFREEZE_STATUS_THAWED;
>> +    QTAILQ_INIT(&guest_fsfreeze_state.mount_list);
>> +}
>> +
>> +static void guest_fsfreeze_cleanup(void)
>> +{
>> +    int64_t ret;
>> +    Error *err = NULL;
>> +
>> +    if (guest_fsfreeze_state.status == GUEST_FSFREEZE_STATUS_FROZEN) {
>> +        ret = qmp_guest_fsfreeze_thaw(&err);
>> +        if (ret<  0 || err) {
>> +            slog("failed to clean up frozen filesystems");
>> +        }
>> +    }
>> +}
>> +
>> +/* 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, guest_fsfreeze_init, guest_fsfreeze_cleanup);
>> +    ga_command_state_add(cs, guest_file_init, NULL);
>> +}
>> diff --git a/qga/guest-agent-core.h b/qga/guest-agent-core.h
>> index 66d1729..3501ff4 100644
>> --- a/qga/guest-agent-core.h
>> +++ b/qga/guest-agent-core.h
>> @@ -14,10 +14,12 @@
>>   #include "qemu-common.h"
>>
>>   #define QGA_VERSION "1.0"
>> +#define QGA_READ_LIMIT 4<<  20 /* 4MB block size max for chunked reads */
>>
>>   typedef struct GAState GAState;
>>   typedef struct GACommandState GACommandState;
>>
>> +void ga_command_state_init(GAState *s, GACommandState *cs);
>>   void ga_command_state_add(GACommandState *cs,
>>                             void (*init)(void),
>>                             void (*cleanup)(void));
>

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

* Re: [Qemu-devel] [PATCH v6 4/4] guest agent: add guest agent RPCs/commands
  2011-07-11 20:11     ` Michael Roth
@ 2011-07-11 21:12       ` Luiz Capitulino
  2011-07-11 23:11         ` Michael Roth
  0 siblings, 1 reply; 25+ messages in thread
From: Luiz Capitulino @ 2011-07-11 21:12 UTC (permalink / raw)
  To: Michael Roth; +Cc: aliguori, agl, qemu-devel, Jes.Sorensen

On Mon, 11 Jul 2011 15:11:26 -0500
Michael Roth <mdroth@linux.vnet.ibm.com> wrote:

> On 07/08/2011 10:14 AM, Luiz Capitulino wrote:
> > On Tue,  5 Jul 2011 08:21:40 -0500
> > Michael Roth<mdroth@linux.vnet.ibm.com>  wrote:
> >
> >> This adds the initial set of QMP/QAPI commands provided by the guest
> >> agent:
> >>
> >> guest-sync
> >> guest-ping
> >> guest-info
> >> guest-shutdown
> >> 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.
> >>
> >> Example usage:
> >>
> >>    host:
> >>      qemu -device virtio-serial \
> >>           -chardev socket,path=/tmp/vs0.sock,server,nowait,id=qga0 \
> >>           -device virtserialport,chardev=qga0,name=qga0
> >>           ...
> >>
> >>      echo "{'execute':'guest-info'}" | socat stdio \
> >>           unix-connect:/tmp/qga0.sock
> >>
> >>    guest:
> >>      qemu-ga -c virtio-serial -p /dev/virtio-ports/qga0 \
> >>              -p /var/run/qemu-guest-agent.pid -d
> >>
> >> Signed-off-by: Michael Roth<mdroth@linux.vnet.ibm.com>
> >> ---
> >>   Makefile                   |   15 ++-
> >>   qemu-ga.c                  |    4 +
> >>   qerror.h                   |    3 +
> >>   qga/guest-agent-commands.c |  501 ++++++++++++++++++++++++++++++++++++++++++++
> >>   qga/guest-agent-core.h     |    2 +
> >>   5 files changed, 523 insertions(+), 2 deletions(-)
> >>   create mode 100644 qga/guest-agent-commands.c
> >>
> >> diff --git a/Makefile b/Makefile
> >> index b2e8593..7e4f722 100644
> >> --- a/Makefile
> >> +++ b/Makefile
> >> @@ -175,15 +175,26 @@ $(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-visitor.o: $(addprefix $(qapi-dir)/, test-qapi-types.c test-qapi-types.h test-qapi-visit.c test-qapi-visit.h)
> >>   test-visitor: test-visitor.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
> >>
> >> -QGALIB=qga/guest-agent-command-state.o
> >> +QGALIB=qga/guest-agent-command-state.o qga/guest-agent-commands.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 $(QGALIB) 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 module.o qapi/qmp-dispatch.o qapi/qmp-registry.o
> >> +qemu-ga$(EXESUF): qemu-ga.o $(QGALIB) 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 module.o qapi/qmp-dispatch.o qapi/qmp-registry.o $(qapi-dir)/qga-qapi-visit.o $(qapi-dir)/qga-qmp-marshal.o
> >>
> >>   QEMULIBS=libhw32 libhw64 libuser libdis libdis-user
> >>
> >> diff --git a/qemu-ga.c b/qemu-ga.c
> >> index 649c16a..04ead22 100644
> >> --- a/qemu-ga.c
> >> +++ b/qemu-ga.c
> >> @@ -637,6 +637,9 @@ int main(int argc, char **argv)
> >>       g_log_set_default_handler(ga_log, s);
> >>       g_log_set_fatal_mask(NULL, G_LOG_LEVEL_ERROR);
> >>       s->logging_enabled = true;
> >> +    s->command_state = ga_command_state_new();
> >> +    ga_command_state_init(s, s->command_state);
> >> +    ga_command_state_init_all(s->command_state);
> >>       ga_state = s;
> >>
> >>       module_call_init(MODULE_INIT_QAPI);
> >> @@ -645,6 +648,7 @@ int main(int argc, char **argv)
> >>
> >>       g_main_loop_run(ga_state->main_loop);
> >>
> >> +    ga_command_state_cleanup_all(ga_state->command_state);
> >>       unlink(pidfile);
> >>
> >>       return 0;
> >> diff --git a/qerror.h b/qerror.h
> >> index 9a9fa5b..0f618ac 100644
> >> --- a/qerror.h
> >> +++ b/qerror.h
> >> @@ -184,4 +184,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 */
> >> diff --git a/qga/guest-agent-commands.c b/qga/guest-agent-commands.c
> >> new file mode 100644
> >> index 0000000..42390fb
> >> --- /dev/null
> >> +++ b/qga/guest-agent-commands.c
> >> @@ -0,0 +1,501 @@
> >> +/*
> >> + * 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"
> >> +#include "qerror.h"
> >> +#include "qemu-queue.h"
> >> +
> >> +static GAState *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 ga_logging_enabled() beforehand,
> >> + * and use the QERR_QGA_LOGGING_DISABLED to generate an error
> >> + */
> >> +static void slog(const char *fmt, ...)
> >> +{
> >> +    va_list ap;
> >> +
> >> +    va_start(ap, fmt);
> >> +    g_logv("syslog", G_LOG_LEVEL_INFO, fmt, ap);
> >> +    va_end(ap);
> >> +}
> >> +
> >> +int64_t qmp_guest_sync(int64_t id, Error **errp)
> >> +{
> >> +    return id;
> >> +}
> >> +
> >> +void qmp_guest_ping(Error **err)
> >> +{
> >> +    slog("guest-ping called");
> >> +}
> >> +
> >> +struct GuestAgentInfo *qmp_guest_info(Error **err)
> >> +{
> >> +    GuestAgentInfo *info = qemu_mallocz(sizeof(GuestAgentInfo));
> >> +
> >> +    info->version = g_strdup(QGA_VERSION);
> >> +
> >> +    return info;
> >> +}
> >> +
> >> +void qmp_guest_shutdown(const char *mode, Error **err)
> >> +{
> >> +    int ret;
> >> +    const char *shutdown_flag;
> >> +
> >> +    slog("guest-shutdown called, mode: %s", mode);
> >> +    if (strcmp(mode, "halt") == 0) {
> >> +        shutdown_flag = "-H";
> >> +    } else if (strcmp(mode, "powerdown") == 0) {
> >> +        shutdown_flag = "-P";
> >> +    } else if (strcmp(mode, "reboot") == 0) {
> >> +        shutdown_flag = "-r";
> >> +    } else {
> >> +        error_set(err, QERR_INVALID_PARAMETER_VALUE, "mode",
> >> +                  "halt|powerdown|reboot");
> >> +        return;
> >> +    }
> >> +
> >> +    ret = fork();
> >> +    if (ret == 0) {
> >> +        /* child, start the shutdown */
> >> +        setsid();
> >> +        fclose(stdin);
> >> +        fclose(stdout);
> >> +        fclose(stderr);
> >> +
> >> +        sleep(5);
> >
> > If we're required to return a response before the shutdown happens, this
> > is a bug and I'm afraid that the right way to this is a bit complex.
> >
> > Otherwise we can just leave it out.
> >
> 
> Yah, I ran this by Anthony and Adam and just leaving it out seems to be 
> the preferred approach, for now at least.
> 
> >> +        ret = execl("/sbin/shutdown", "shutdown", shutdown_flag, "+0",
> >> +                    "hypervisor initiated shutdown", (char*)NULL);
> >> +        if (ret) {
> >> +            slog("guest-shutdown failed: %s", strerror(errno));
> >> +        }
> >> +        exit(!!ret);
> >> +    } else if (ret<  0) {
> >> +        error_set(err, QERR_UNDEFINED_ERROR);
> >> +    }
> >> +}
> >> +
> >> +typedef struct GuestFileHandle {
> >> +    uint64_t id;
> >> +    FILE *fh;
> >> +    QTAILQ_ENTRY(GuestFileHandle) next;
> >> +} GuestFileHandle;
> >> +
> >> +static struct {
> >> +    QTAILQ_HEAD(, GuestFileHandle) filehandles;
> >> +} guest_file_state;
> >> +
> >> +static void guest_file_handle_add(FILE *fh)
> >> +{
> >> +    GuestFileHandle *gfh;
> >> +
> >> +    gfh = qemu_mallocz(sizeof(GuestFileHandle));
> >> +    gfh->id = fileno(fh);
> >> +    gfh->fh = fh;
> >> +    QTAILQ_INSERT_TAIL(&guest_file_state.filehandles, gfh, next);
> >> +}
> >> +
> >> +static GuestFileHandle *guest_file_handle_find(int64_t id)
> >> +{
> >> +    GuestFileHandle *gfh;
> >> +
> >> +    QTAILQ_FOREACH(gfh,&guest_file_state.filehandles, next)
> >> +    {
> >> +        if (gfh->id == id) {
> >> +            return gfh;
> >> +        }
> >> +    }
> >> +
> >> +    return NULL;
> >> +}
> >> +
> >> +int64_t qmp_guest_file_open(const char *filepath, bool has_mode, const char *mode, Error **err)
> >> +{
> >> +    FILE *fh;
> >> +    int fd;
> >> +    int64_t ret = -1;
> >> +
> >> +    if (!has_mode) {
> >> +        mode = "r";
> >> +    }
> >> +    slog("guest-file-open called, filepath: %s, mode: %s", filepath, mode);
> >> +    fh = fopen(filepath, mode);
> >> +    if (!fh) {
> >> +        error_set(err, QERR_OPEN_FILE_FAILED, filepath);
> >> +        return -1;
> >> +    }
> >> +
> >> +    /* set fd non-blocking to avoid common use cases (like reading from a
> >> +     * named pipe) from hanging the agent
> >> +     */
> >> +    fd = fileno(fh);
> >> +    ret = fcntl(fd, F_GETFL);
> >> +    ret = fcntl(fd, F_SETFL, ret | O_NONBLOCK);
> >> +    if (ret == -1) {
> >> +        error_set(err, QERR_OPEN_FILE_FAILED, filepath);
> >> +        fclose(fh);
> >> +        return -1;
> >> +    }
> >> +
> >> +    guest_file_handle_add(fh);
> >> +    slog("guest-file-open, filehandle: %d", fd);
> >> +    return fd;
> >> +}
> >> +
> >> +struct GuestFileRead *qmp_guest_file_read(int64_t filehandle, int64_t count,
> >> +                                          Error **err)
> >> +{
> >> +    GuestFileHandle *gfh = guest_file_handle_find(filehandle);
> >> +    GuestFileRead *read_data;
> >> +    guchar *buf;
> >> +    FILE *fh;
> >> +    size_t read_count;
> >> +
> >> +    if (!gfh) {
> >> +        error_set(err, QERR_FD_NOT_FOUND, "filehandle");
> >> +        return NULL;
> >> +    }
> >> +
> >> +    if (count<  0 || count>  QGA_READ_LIMIT) {
> >> +        error_set(err, QERR_INVALID_PARAMETER, "count");
> >> +        return NULL;
> >> +    }
> >
> > Are we imposing that limit because of the malloc() call below? If that's
> > the case I think it's wrong, because we don't know the VM (neither the guest)
> > better than the client.
> >
> > The best thing we can do here is to limit it to the file size. Additionally
> > to this we could have a command-line option to allow the sysadmin set his/her
> > own limit.
> >
> 
> That's technically the better approach, but we're also bound by the 
> maximum token size limit in the JSON parser, which is 64MB. Best we can 
> do is set QGA_READ_LIMIT accordingly.

Good point, but I think it's ok to let the parser do this check itself, as
control won't get here anyway. Maybe we should only document that the server
imposes a limit on the token size.

> 
> >> +
> >> +    fh = gfh->fh;
> >> +    read_data = qemu_mallocz(sizeof(GuestFileRead) + 1);
> >> +    buf = qemu_mallocz(count+1);
> >> +    if (!buf) {
> >> +        error_set(err, QERR_UNDEFINED_ERROR);
> >> +        return NULL;
> >> +    }
> >
> > qemu_malloc() functions never fail...
> >
> >> +
> >> +    read_count = fread(buf, 1, count, fh);
> >
> > Isn't 'nmemb' and 'size' swapped?
> >
> 
> I'm not sure...
> 
> size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
> 
> I wrote it as $count number of 1-bytes elements. This seems logical to 
> me, since it's a non-blocking FD which way result in a partial of some 
> lesser number of bytes than count.

Ok. I think either way will work.

> 
> >> +    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);
> >> +    }
> >> +    qemu_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;
> >> +    GuestFileHandle *gfh = guest_file_handle_find(filehandle);
> >> +    FILE *fh;
> >> +
> >> +    if (!gfh) {
> >> +        error_set(err, QERR_FD_NOT_FOUND, "filehandle");
> >> +        return NULL;
> >> +    }
> >> +
> >> +    fh = gfh->fh;
> >> +    data = g_base64_decode(data_b64,&data_len);
> >> +    if (count>  data_len) {
> >> +        qemu_free(data);
> >> +        error_set(err, QERR_INVALID_PARAMETER, "count");
> >> +        return NULL;
> >> +    }
> >> +    write_data = qemu_mallocz(sizeof(GuestFileWrite));
> >> +    write_count = fwrite(data, 1, count, fh);
> >> +    write_data->count = write_count;
> >> +    write_data->eof = feof(fh);
> >> +    qemu_free(data);
> >> +    clearerr(fh);
> >
> > Shouldn't we check for errors instead of doing this?
> >
> 
> Yah...unfortunately we only get a boolean flag with ferror() so it's not 
> all that useful, but I can add an error flag to the calls to capture it 
> at least. clearerr() is only being used here to clear the eof flag.

I meant to check fwrite()'s return.

> 
> > Btw, I think it's a good idea to offer guest-file-flush() too (or do a flush()
> > here, but that's probably bad).
> >
> 
> Yah, I'd been planning on adding a guest-file-flush() for a while now. 
> I'll add that for the respin.
> 
> >> +
> >> +    return write_data;
> >> +}
> >> +
> >> +struct GuestFileSeek *qmp_guest_file_seek(int64_t filehandle, int64_t offset,
> >> +                                          int64_t whence, Error **err)
> >> +{
> >> +    GuestFileSeek *seek_data;
> >> +    GuestFileHandle *gfh = guest_file_handle_find(filehandle);
> >> +    FILE *fh;
> >> +    int ret;
> >> +
> >> +    if (!gfh) {
> >> +        error_set(err, QERR_FD_NOT_FOUND, "filehandle");
> >> +        return NULL;
> >> +    }
> >> +
> >> +    fh = gfh->fh;
> >> +    seek_data = qemu_mallocz(sizeof(GuestFileRead));
> >> +    ret = fseek(fh, offset, whence);
> >> +    if (ret == -1) {
> >> +        error_set(err, QERR_UNDEFINED_ERROR);
> >> +        qemu_free(seek_data);
> >> +        return NULL;
> >> +    }
> >> +    seek_data->position = ftell(fh);
> >> +    seek_data->eof = feof(fh);
> >> +    clearerr(fh);
> >
> > Again, I don't see why we should do this.

This is probably ok, as we're checking fseek() above.

> >
> >> +
> >> +    return seek_data;
> >> +}
> >> +
> >> +void qmp_guest_file_close(int64_t filehandle, Error **err)
> >> +{
> >> +    GuestFileHandle *gfh = guest_file_handle_find(filehandle);
> >> +
> >> +    slog("guest-file-close called, filehandle: %ld", filehandle);
> >> +    if (!gfh) {
> >> +        error_set(err, QERR_FD_NOT_FOUND, "filehandle");
> >> +        return;
> >> +    }
> >> +
> >> +    fclose(gfh->fh);
> >> +    QTAILQ_REMOVE(&guest_file_state.filehandles, gfh, next);
> >> +    qemu_free(gfh);
> >> +}
> >> +
> >> +static void guest_file_init(void)
> >> +{
> >> +    QTAILQ_INIT(&guest_file_state.filehandles);
> >> +}
> >> +
> >> +typedef struct GuestFsfreezeMount {
> >> +    char *dirname;
> >> +    char *devtype;
> >> +    QTAILQ_ENTRY(GuestFsfreezeMount) next;
> >> +} GuestFsfreezeMount;
> >> +
> >> +struct {
> >> +    GuestFsfreezeStatus status;
> >> +    QTAILQ_HEAD(, GuestFsfreezeMount) mount_list;
> >> +} guest_fsfreeze_state;
> >> +
> >> +/*
> >> + * Walk the mount table and build a list of local file systems
> >> + */
> >> +static int guest_fsfreeze_build_mount_list(void)
> >> +{
> >> +    struct mntent *ment;
> >> +    GuestFsfreezeMount *mount, *temp;
> >> +    char const *mtab = MOUNTED;
> >> +    FILE *fp;
> >> +
> >> +    fp = setmntent(mtab, "r");
> >> +    if (!fp) {
> >> +        g_warning("fsfreeze: unable to read mtab");
> >> +        goto fail;
> >> +    }
> >> +
> >> +    while ((ment = getmntent(fp))) {
> >> +        /*
> >> +         * An entry which device name doesn't start with a '/' is
> >> +         * either a dummy file system or a network file system.
> >> +         * Add special handling for smbfs and cifs as is done by
> >> +         * coreutils as well.
> >> +         */
> >> +        if ((ment->mnt_fsname[0] != '/') ||
> >> +            (strcmp(ment->mnt_type, "smbfs") == 0) ||
> >> +            (strcmp(ment->mnt_type, "cifs") == 0)) {
> >> +            continue;
> >> +        }
> >> +
> >> +        mount = qemu_mallocz(sizeof(GuestFsfreezeMount));
> >> +        mount->dirname = qemu_strdup(ment->mnt_dir);
> >> +        mount->devtype = qemu_strdup(ment->mnt_type);
> >> +
> >> +        QTAILQ_INSERT_TAIL(&guest_fsfreeze_state.mount_list, mount, next);
> >> +    }
> >> +
> >> +    endmntent(fp);
> >> +
> >> +    return 0;
> >> +
> >> +fail:
> >> +    QTAILQ_FOREACH_SAFE(mount,&guest_fsfreeze_state.mount_list, next, temp) {
> >> +        QTAILQ_REMOVE(&guest_fsfreeze_state.mount_list, mount, next);
> >> +        qemu_free(mount->dirname);
> >> +        qemu_free(mount->devtype);
> >> +        qemu_free(mount);
> >> +    }
> >
> > This doesn't seem to be used.
> >
> 
> It can get used even a 2nd invocation of this function gets called that 
> results in a goto fail. But looking again this should be done 
> unconditionally at the start of the function, since the mount list is 
> part of global state now.
> 
> >> +
> >> +    return -1;
> >> +}
> >> +
> >> +/*
> >> + * Return status of freeze/thaw
> >> + */
> >> +GuestFsfreezeStatus qmp_guest_fsfreeze_status(Error **err)
> >> +{
> >> +    return guest_fsfreeze_state.status;
> >> +}
> >> +
> >> +/*
> >> + * Walk list of mounted file systems in the guest, and freeze the ones which
> >> + * are real local file systems.
> >> + */
> >> +int64_t qmp_guest_fsfreeze_freeze(Error **err)
> >> +{
> >> +    int ret = 0, i = 0;
> >> +    struct GuestFsfreezeMount *mount, *temp;
> >> +    int fd;
> >> +
> >> +    slog("guest-fsfreeze called");
> >> +
> >> +    if (guest_fsfreeze_state.status != GUEST_FSFREEZE_STATUS_THAWED) {
> >
> > return 0;
> >
> >> +        ret = 0;
> >> +        goto out;
> >> +    }
> >> +
> >> +    ret = guest_fsfreeze_build_mount_list();
> >> +    if (ret<  0) {
> >
> > return ret;
> >
> >> +        goto out;
> >> +    }
> >> +
> >> +    guest_fsfreeze_state.status = GUEST_FSFREEZE_STATUS_INPROGRESS;
> >> +
> >> +    /* cannot risk guest agent blocking itself on a write in this state */
> >> +    disable_logging();
> >> +
> >> +    QTAILQ_FOREACH_SAFE(mount,&guest_fsfreeze_state.mount_list, next, temp) {
> >> +        fd = qemu_open(mount->dirname, O_RDONLY);
> >> +        if (fd == -1) {
> >> +            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);
> >> +
> >> +        i++;
> >> +    }
> >> +
> >> +    guest_fsfreeze_state.status = GUEST_FSFREEZE_STATUS_FROZEN;
> >> +    ret = i;
> >> +out:
> >> +    return ret;
> >> +error:
> >> +    if (i>  0) {
> >> +        guest_fsfreeze_state.status = GUEST_FSFREEZE_STATUS_ERROR;
> >> +    }
> >
> > Shouldn't you undo everything that has been done so far? Which is
> > freeing the build list and thawing the file-systems that were frozen
> > before the error?
> >
> 
> We can...the way it's done right now is you get a count of how many 
> filesystems were frozen, along an error status. Depending on the 
> error/count the user can either ignore the error, do what it is they 
> want to do, then call thaw(), or just immediately call thaw().

But you don't get the count on error, so it's difficult (if possible) to
learn how many file-systems were frozen.

> 
> So we can do an automatic thaw() on their behalf, but i figured the 
> status was good enough.
> 
> >> +    goto out;
> >> +}
> >> +
> >> +/*
> >> + * Walk list of frozen file systems in the guest, and thaw them.
> >> + */
> >> +int64_t qmp_guest_fsfreeze_thaw(Error **err)
> >> +{
> >> +    int ret;
> >> +    GuestFsfreezeMount *mount, *temp;
> >> +    int fd, i = 0;
> >> +
> >> +    if (guest_fsfreeze_state.status != GUEST_FSFREEZE_STATUS_FROZEN&&
> >> +        guest_fsfreeze_state.status != GUEST_FSFREEZE_STATUS_INPROGRESS) {
> >
> > I don't follow why we're checking against INPROGRESS here.
> >
> 
> To prevent a race I believe...but we're synchronous now so that's 
> probably no longer needed. I'll look it over and remove it if that's the 
> case.
> 
> >> +        ret = 0;
> >> +        goto out_enable_logging;
> >> +    }
> >> +
> >> +    QTAILQ_FOREACH_SAFE(mount,&guest_fsfreeze_state.mount_list, next, temp) {
> >> +        fd = qemu_open(mount->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;
> >
> > Shouldn't you continue and try to thaw the other file-systems in the list?
> >
> 
> That's probably better
> 
> >> +        }
> >> +        close(fd);
> >> +
> >> +        QTAILQ_REMOVE(&guest_fsfreeze_state.mount_list, mount, next);
> >> +        qemu_free(mount->dirname);
> >> +        qemu_free(mount->devtype);
> >> +        qemu_free(mount);
> >> +        i++;
> >> +    }
> >> +
> >> +    guest_fsfreeze_state.status = GUEST_FSFREEZE_STATUS_THAWED;
> >> +    ret = i;
> >> +out_enable_logging:
> >> +    enable_logging();
> >> +out:
> >> +    return ret;
> >> +}
> >> +
> >> +static void guest_fsfreeze_init(void)
> >> +{
> >> +    guest_fsfreeze_state.status = GUEST_FSFREEZE_STATUS_THAWED;
> >> +    QTAILQ_INIT(&guest_fsfreeze_state.mount_list);
> >> +}
> >> +
> >> +static void guest_fsfreeze_cleanup(void)
> >> +{
> >> +    int64_t ret;
> >> +    Error *err = NULL;
> >> +
> >> +    if (guest_fsfreeze_state.status == GUEST_FSFREEZE_STATUS_FROZEN) {
> >> +        ret = qmp_guest_fsfreeze_thaw(&err);
> >> +        if (ret<  0 || err) {
> >> +            slog("failed to clean up frozen filesystems");
> >> +        }
> >> +    }
> >> +}
> >> +
> >> +/* 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, guest_fsfreeze_init, guest_fsfreeze_cleanup);
> >> +    ga_command_state_add(cs, guest_file_init, NULL);
> >> +}
> >> diff --git a/qga/guest-agent-core.h b/qga/guest-agent-core.h
> >> index 66d1729..3501ff4 100644
> >> --- a/qga/guest-agent-core.h
> >> +++ b/qga/guest-agent-core.h
> >> @@ -14,10 +14,12 @@
> >>   #include "qemu-common.h"
> >>
> >>   #define QGA_VERSION "1.0"
> >> +#define QGA_READ_LIMIT 4<<  20 /* 4MB block size max for chunked reads */
> >>
> >>   typedef struct GAState GAState;
> >>   typedef struct GACommandState GACommandState;
> >>
> >> +void ga_command_state_init(GAState *s, GACommandState *cs);
> >>   void ga_command_state_add(GACommandState *cs,
> >>                             void (*init)(void),
> >>                             void (*cleanup)(void));
> >
> 

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

* Re: [Qemu-devel] [PATCH v6 4/4] guest agent: add guest agent RPCs/commands
  2011-07-11 21:12       ` Luiz Capitulino
@ 2011-07-11 23:11         ` Michael Roth
  2011-07-12 14:15           ` Luiz Capitulino
  0 siblings, 1 reply; 25+ messages in thread
From: Michael Roth @ 2011-07-11 23:11 UTC (permalink / raw)
  To: Luiz Capitulino; +Cc: aliguori, agl, qemu-devel, Jes.Sorensen

On 07/11/2011 04:12 PM, Luiz Capitulino wrote:
> On Mon, 11 Jul 2011 15:11:26 -0500
> Michael Roth<mdroth@linux.vnet.ibm.com>  wrote:
>
>> On 07/08/2011 10:14 AM, Luiz Capitulino wrote:
>>> On Tue,  5 Jul 2011 08:21:40 -0500
>>> Michael Roth<mdroth@linux.vnet.ibm.com>   wrote:
>>>
>>>> This adds the initial set of QMP/QAPI commands provided by the guest
>>>> agent:
>>>>
>>>> guest-sync
>>>> guest-ping
>>>> guest-info
>>>> guest-shutdown
>>>> 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.
>>>>
>>>> Example usage:
>>>>
>>>>     host:
>>>>       qemu -device virtio-serial \
>>>>            -chardev socket,path=/tmp/vs0.sock,server,nowait,id=qga0 \
>>>>            -device virtserialport,chardev=qga0,name=qga0
>>>>            ...
>>>>
>>>>       echo "{'execute':'guest-info'}" | socat stdio \
>>>>            unix-connect:/tmp/qga0.sock
>>>>
>>>>     guest:
>>>>       qemu-ga -c virtio-serial -p /dev/virtio-ports/qga0 \
>>>>               -p /var/run/qemu-guest-agent.pid -d
>>>>
>>>> Signed-off-by: Michael Roth<mdroth@linux.vnet.ibm.com>
>>>> ---
>>>>    Makefile                   |   15 ++-
>>>>    qemu-ga.c                  |    4 +
>>>>    qerror.h                   |    3 +
>>>>    qga/guest-agent-commands.c |  501 ++++++++++++++++++++++++++++++++++++++++++++
>>>>    qga/guest-agent-core.h     |    2 +
>>>>    5 files changed, 523 insertions(+), 2 deletions(-)
>>>>    create mode 100644 qga/guest-agent-commands.c
>>>>
>>>> diff --git a/Makefile b/Makefile
>>>> index b2e8593..7e4f722 100644
>>>> --- a/Makefile
>>>> +++ b/Makefile
>>>> @@ -175,15 +175,26 @@ $(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-visitor.o: $(addprefix $(qapi-dir)/, test-qapi-types.c test-qapi-types.h test-qapi-visit.c test-qapi-visit.h)
>>>>    test-visitor: test-visitor.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
>>>>
>>>> -QGALIB=qga/guest-agent-command-state.o
>>>> +QGALIB=qga/guest-agent-command-state.o qga/guest-agent-commands.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 $(QGALIB) 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 module.o qapi/qmp-dispatch.o qapi/qmp-registry.o
>>>> +qemu-ga$(EXESUF): qemu-ga.o $(QGALIB) 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 module.o qapi/qmp-dispatch.o qapi/qmp-registry.o $(qapi-dir)/qga-qapi-visit.o $(qapi-dir)/qga-qmp-marshal.o
>>>>
>>>>    QEMULIBS=libhw32 libhw64 libuser libdis libdis-user
>>>>
>>>> diff --git a/qemu-ga.c b/qemu-ga.c
>>>> index 649c16a..04ead22 100644
>>>> --- a/qemu-ga.c
>>>> +++ b/qemu-ga.c
>>>> @@ -637,6 +637,9 @@ int main(int argc, char **argv)
>>>>        g_log_set_default_handler(ga_log, s);
>>>>        g_log_set_fatal_mask(NULL, G_LOG_LEVEL_ERROR);
>>>>        s->logging_enabled = true;
>>>> +    s->command_state = ga_command_state_new();
>>>> +    ga_command_state_init(s, s->command_state);
>>>> +    ga_command_state_init_all(s->command_state);
>>>>        ga_state = s;
>>>>
>>>>        module_call_init(MODULE_INIT_QAPI);
>>>> @@ -645,6 +648,7 @@ int main(int argc, char **argv)
>>>>
>>>>        g_main_loop_run(ga_state->main_loop);
>>>>
>>>> +    ga_command_state_cleanup_all(ga_state->command_state);
>>>>        unlink(pidfile);
>>>>
>>>>        return 0;
>>>> diff --git a/qerror.h b/qerror.h
>>>> index 9a9fa5b..0f618ac 100644
>>>> --- a/qerror.h
>>>> +++ b/qerror.h
>>>> @@ -184,4 +184,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 */
>>>> diff --git a/qga/guest-agent-commands.c b/qga/guest-agent-commands.c
>>>> new file mode 100644
>>>> index 0000000..42390fb
>>>> --- /dev/null
>>>> +++ b/qga/guest-agent-commands.c
>>>> @@ -0,0 +1,501 @@
>>>> +/*
>>>> + * 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"
>>>> +#include "qerror.h"
>>>> +#include "qemu-queue.h"
>>>> +
>>>> +static GAState *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 ga_logging_enabled() beforehand,
>>>> + * and use the QERR_QGA_LOGGING_DISABLED to generate an error
>>>> + */
>>>> +static void slog(const char *fmt, ...)
>>>> +{
>>>> +    va_list ap;
>>>> +
>>>> +    va_start(ap, fmt);
>>>> +    g_logv("syslog", G_LOG_LEVEL_INFO, fmt, ap);
>>>> +    va_end(ap);
>>>> +}
>>>> +
>>>> +int64_t qmp_guest_sync(int64_t id, Error **errp)
>>>> +{
>>>> +    return id;
>>>> +}
>>>> +
>>>> +void qmp_guest_ping(Error **err)
>>>> +{
>>>> +    slog("guest-ping called");
>>>> +}
>>>> +
>>>> +struct GuestAgentInfo *qmp_guest_info(Error **err)
>>>> +{
>>>> +    GuestAgentInfo *info = qemu_mallocz(sizeof(GuestAgentInfo));
>>>> +
>>>> +    info->version = g_strdup(QGA_VERSION);
>>>> +
>>>> +    return info;
>>>> +}
>>>> +
>>>> +void qmp_guest_shutdown(const char *mode, Error **err)
>>>> +{
>>>> +    int ret;
>>>> +    const char *shutdown_flag;
>>>> +
>>>> +    slog("guest-shutdown called, mode: %s", mode);
>>>> +    if (strcmp(mode, "halt") == 0) {
>>>> +        shutdown_flag = "-H";
>>>> +    } else if (strcmp(mode, "powerdown") == 0) {
>>>> +        shutdown_flag = "-P";
>>>> +    } else if (strcmp(mode, "reboot") == 0) {
>>>> +        shutdown_flag = "-r";
>>>> +    } else {
>>>> +        error_set(err, QERR_INVALID_PARAMETER_VALUE, "mode",
>>>> +                  "halt|powerdown|reboot");
>>>> +        return;
>>>> +    }
>>>> +
>>>> +    ret = fork();
>>>> +    if (ret == 0) {
>>>> +        /* child, start the shutdown */
>>>> +        setsid();
>>>> +        fclose(stdin);
>>>> +        fclose(stdout);
>>>> +        fclose(stderr);
>>>> +
>>>> +        sleep(5);
>>>
>>> If we're required to return a response before the shutdown happens, this
>>> is a bug and I'm afraid that the right way to this is a bit complex.
>>>
>>> Otherwise we can just leave it out.
>>>
>>
>> Yah, I ran this by Anthony and Adam and just leaving it out seems to be
>> the preferred approach, for now at least.
>>
>>>> +        ret = execl("/sbin/shutdown", "shutdown", shutdown_flag, "+0",
>>>> +                    "hypervisor initiated shutdown", (char*)NULL);
>>>> +        if (ret) {
>>>> +            slog("guest-shutdown failed: %s", strerror(errno));
>>>> +        }
>>>> +        exit(!!ret);
>>>> +    } else if (ret<   0) {
>>>> +        error_set(err, QERR_UNDEFINED_ERROR);
>>>> +    }
>>>> +}
>>>> +
>>>> +typedef struct GuestFileHandle {
>>>> +    uint64_t id;
>>>> +    FILE *fh;
>>>> +    QTAILQ_ENTRY(GuestFileHandle) next;
>>>> +} GuestFileHandle;
>>>> +
>>>> +static struct {
>>>> +    QTAILQ_HEAD(, GuestFileHandle) filehandles;
>>>> +} guest_file_state;
>>>> +
>>>> +static void guest_file_handle_add(FILE *fh)
>>>> +{
>>>> +    GuestFileHandle *gfh;
>>>> +
>>>> +    gfh = qemu_mallocz(sizeof(GuestFileHandle));
>>>> +    gfh->id = fileno(fh);
>>>> +    gfh->fh = fh;
>>>> +    QTAILQ_INSERT_TAIL(&guest_file_state.filehandles, gfh, next);
>>>> +}
>>>> +
>>>> +static GuestFileHandle *guest_file_handle_find(int64_t id)
>>>> +{
>>>> +    GuestFileHandle *gfh;
>>>> +
>>>> +    QTAILQ_FOREACH(gfh,&guest_file_state.filehandles, next)
>>>> +    {
>>>> +        if (gfh->id == id) {
>>>> +            return gfh;
>>>> +        }
>>>> +    }
>>>> +
>>>> +    return NULL;
>>>> +}
>>>> +
>>>> +int64_t qmp_guest_file_open(const char *filepath, bool has_mode, const char *mode, Error **err)
>>>> +{
>>>> +    FILE *fh;
>>>> +    int fd;
>>>> +    int64_t ret = -1;
>>>> +
>>>> +    if (!has_mode) {
>>>> +        mode = "r";
>>>> +    }
>>>> +    slog("guest-file-open called, filepath: %s, mode: %s", filepath, mode);
>>>> +    fh = fopen(filepath, mode);
>>>> +    if (!fh) {
>>>> +        error_set(err, QERR_OPEN_FILE_FAILED, filepath);
>>>> +        return -1;
>>>> +    }
>>>> +
>>>> +    /* set fd non-blocking to avoid common use cases (like reading from a
>>>> +     * named pipe) from hanging the agent
>>>> +     */
>>>> +    fd = fileno(fh);
>>>> +    ret = fcntl(fd, F_GETFL);
>>>> +    ret = fcntl(fd, F_SETFL, ret | O_NONBLOCK);
>>>> +    if (ret == -1) {
>>>> +        error_set(err, QERR_OPEN_FILE_FAILED, filepath);
>>>> +        fclose(fh);
>>>> +        return -1;
>>>> +    }
>>>> +
>>>> +    guest_file_handle_add(fh);
>>>> +    slog("guest-file-open, filehandle: %d", fd);
>>>> +    return fd;
>>>> +}
>>>> +
>>>> +struct GuestFileRead *qmp_guest_file_read(int64_t filehandle, int64_t count,
>>>> +                                          Error **err)
>>>> +{
>>>> +    GuestFileHandle *gfh = guest_file_handle_find(filehandle);
>>>> +    GuestFileRead *read_data;
>>>> +    guchar *buf;
>>>> +    FILE *fh;
>>>> +    size_t read_count;
>>>> +
>>>> +    if (!gfh) {
>>>> +        error_set(err, QERR_FD_NOT_FOUND, "filehandle");
>>>> +        return NULL;
>>>> +    }
>>>> +
>>>> +    if (count<   0 || count>   QGA_READ_LIMIT) {
>>>> +        error_set(err, QERR_INVALID_PARAMETER, "count");
>>>> +        return NULL;
>>>> +    }
>>>
>>> Are we imposing that limit because of the malloc() call below? If that's
>>> the case I think it's wrong, because we don't know the VM (neither the guest)
>>> better than the client.
>>>
>>> The best thing we can do here is to limit it to the file size. Additionally
>>> to this we could have a command-line option to allow the sysadmin set his/her
>>> own limit.
>>>
>>
>> That's technically the better approach, but we're also bound by the
>> maximum token size limit in the JSON parser, which is 64MB. Best we can
>> do is set QGA_READ_LIMIT accordingly.
>
> Good point, but I think it's ok to let the parser do this check itself, as
> control won't get here anyway. Maybe we should only document that the server
> imposes a limit on the token size.
>

 From a usability perspective I think the QERR_INVALID_PARAMETER, or 
perhaps something more descriptive, is a little nicer. Plus, they won't 
have to wait for 64MB to get streamed back before they get the error :)

>>
>>>> +
>>>> +    fh = gfh->fh;
>>>> +    read_data = qemu_mallocz(sizeof(GuestFileRead) + 1);
>>>> +    buf = qemu_mallocz(count+1);
>>>> +    if (!buf) {
>>>> +        error_set(err, QERR_UNDEFINED_ERROR);
>>>> +        return NULL;
>>>> +    }
>>>
>>> qemu_malloc() functions never fail...
>>>
>>>> +
>>>> +    read_count = fread(buf, 1, count, fh);
>>>
>>> Isn't 'nmemb' and 'size' swapped?
>>>
>>
>> I'm not sure...
>>
>> size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
>>
>> I wrote it as $count number of 1-bytes elements. This seems logical to
>> me, since it's a non-blocking FD which way result in a partial of some
>> lesser number of bytes than count.
>
> Ok. I think either way will work.
>
>>
>>>> +    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);
>>>> +    }
>>>> +    qemu_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;
>>>> +    GuestFileHandle *gfh = guest_file_handle_find(filehandle);
>>>> +    FILE *fh;
>>>> +
>>>> +    if (!gfh) {
>>>> +        error_set(err, QERR_FD_NOT_FOUND, "filehandle");
>>>> +        return NULL;
>>>> +    }
>>>> +
>>>> +    fh = gfh->fh;
>>>> +    data = g_base64_decode(data_b64,&data_len);
>>>> +    if (count>   data_len) {
>>>> +        qemu_free(data);
>>>> +        error_set(err, QERR_INVALID_PARAMETER, "count");
>>>> +        return NULL;
>>>> +    }
>>>> +    write_data = qemu_mallocz(sizeof(GuestFileWrite));
>>>> +    write_count = fwrite(data, 1, count, fh);
>>>> +    write_data->count = write_count;
>>>> +    write_data->eof = feof(fh);
>>>> +    qemu_free(data);
>>>> +    clearerr(fh);
>>>
>>> Shouldn't we check for errors instead of doing this?
>>>
>>
>> Yah...unfortunately we only get a boolean flag with ferror() so it's not
>> all that useful, but I can add an error flag to the calls to capture it
>> at least. clearerr() is only being used here to clear the eof flag.
>
> I meant to check fwrite()'s return.
>

Ahh, right. Definitely.

>>
>>> Btw, I think it's a good idea to offer guest-file-flush() too (or do a flush()
>>> here, but that's probably bad).
>>>
>>
>> Yah, I'd been planning on adding a guest-file-flush() for a while now.
>> I'll add that for the respin.
>>
>>>> +
>>>> +    return write_data;
>>>> +}
>>>> +
>>>> +struct GuestFileSeek *qmp_guest_file_seek(int64_t filehandle, int64_t offset,
>>>> +                                          int64_t whence, Error **err)
>>>> +{
>>>> +    GuestFileSeek *seek_data;
>>>> +    GuestFileHandle *gfh = guest_file_handle_find(filehandle);
>>>> +    FILE *fh;
>>>> +    int ret;
>>>> +
>>>> +    if (!gfh) {
>>>> +        error_set(err, QERR_FD_NOT_FOUND, "filehandle");
>>>> +        return NULL;
>>>> +    }
>>>> +
>>>> +    fh = gfh->fh;
>>>> +    seek_data = qemu_mallocz(sizeof(GuestFileRead));
>>>> +    ret = fseek(fh, offset, whence);
>>>> +    if (ret == -1) {
>>>> +        error_set(err, QERR_UNDEFINED_ERROR);
>>>> +        qemu_free(seek_data);
>>>> +        return NULL;
>>>> +    }
>>>> +    seek_data->position = ftell(fh);
>>>> +    seek_data->eof = feof(fh);
>>>> +    clearerr(fh);
>>>
>>> Again, I don't see why we should do this.
>
> This is probably ok, as we're checking fseek() above.
>
>>>
>>>> +
>>>> +    return seek_data;
>>>> +}
>>>> +
>>>> +void qmp_guest_file_close(int64_t filehandle, Error **err)
>>>> +{
>>>> +    GuestFileHandle *gfh = guest_file_handle_find(filehandle);
>>>> +
>>>> +    slog("guest-file-close called, filehandle: %ld", filehandle);
>>>> +    if (!gfh) {
>>>> +        error_set(err, QERR_FD_NOT_FOUND, "filehandle");
>>>> +        return;
>>>> +    }
>>>> +
>>>> +    fclose(gfh->fh);
>>>> +    QTAILQ_REMOVE(&guest_file_state.filehandles, gfh, next);
>>>> +    qemu_free(gfh);
>>>> +}
>>>> +
>>>> +static void guest_file_init(void)
>>>> +{
>>>> +    QTAILQ_INIT(&guest_file_state.filehandles);
>>>> +}
>>>> +
>>>> +typedef struct GuestFsfreezeMount {
>>>> +    char *dirname;
>>>> +    char *devtype;
>>>> +    QTAILQ_ENTRY(GuestFsfreezeMount) next;
>>>> +} GuestFsfreezeMount;
>>>> +
>>>> +struct {
>>>> +    GuestFsfreezeStatus status;
>>>> +    QTAILQ_HEAD(, GuestFsfreezeMount) mount_list;
>>>> +} guest_fsfreeze_state;
>>>> +
>>>> +/*
>>>> + * Walk the mount table and build a list of local file systems
>>>> + */
>>>> +static int guest_fsfreeze_build_mount_list(void)
>>>> +{
>>>> +    struct mntent *ment;
>>>> +    GuestFsfreezeMount *mount, *temp;
>>>> +    char const *mtab = MOUNTED;
>>>> +    FILE *fp;
>>>> +
>>>> +    fp = setmntent(mtab, "r");
>>>> +    if (!fp) {
>>>> +        g_warning("fsfreeze: unable to read mtab");
>>>> +        goto fail;
>>>> +    }
>>>> +
>>>> +    while ((ment = getmntent(fp))) {
>>>> +        /*
>>>> +         * An entry which device name doesn't start with a '/' is
>>>> +         * either a dummy file system or a network file system.
>>>> +         * Add special handling for smbfs and cifs as is done by
>>>> +         * coreutils as well.
>>>> +         */
>>>> +        if ((ment->mnt_fsname[0] != '/') ||
>>>> +            (strcmp(ment->mnt_type, "smbfs") == 0) ||
>>>> +            (strcmp(ment->mnt_type, "cifs") == 0)) {
>>>> +            continue;
>>>> +        }
>>>> +
>>>> +        mount = qemu_mallocz(sizeof(GuestFsfreezeMount));
>>>> +        mount->dirname = qemu_strdup(ment->mnt_dir);
>>>> +        mount->devtype = qemu_strdup(ment->mnt_type);
>>>> +
>>>> +        QTAILQ_INSERT_TAIL(&guest_fsfreeze_state.mount_list, mount, next);
>>>> +    }
>>>> +
>>>> +    endmntent(fp);
>>>> +
>>>> +    return 0;
>>>> +
>>>> +fail:
>>>> +    QTAILQ_FOREACH_SAFE(mount,&guest_fsfreeze_state.mount_list, next, temp) {
>>>> +        QTAILQ_REMOVE(&guest_fsfreeze_state.mount_list, mount, next);
>>>> +        qemu_free(mount->dirname);
>>>> +        qemu_free(mount->devtype);
>>>> +        qemu_free(mount);
>>>> +    }
>>>
>>> This doesn't seem to be used.
>>>
>>
>> It can get used even a 2nd invocation of this function gets called that
>> results in a goto fail. But looking again this should be done
>> unconditionally at the start of the function, since the mount list is
>> part of global state now.
>>
>>>> +
>>>> +    return -1;
>>>> +}
>>>> +
>>>> +/*
>>>> + * Return status of freeze/thaw
>>>> + */
>>>> +GuestFsfreezeStatus qmp_guest_fsfreeze_status(Error **err)
>>>> +{
>>>> +    return guest_fsfreeze_state.status;
>>>> +}
>>>> +
>>>> +/*
>>>> + * Walk list of mounted file systems in the guest, and freeze the ones which
>>>> + * are real local file systems.
>>>> + */
>>>> +int64_t qmp_guest_fsfreeze_freeze(Error **err)
>>>> +{
>>>> +    int ret = 0, i = 0;
>>>> +    struct GuestFsfreezeMount *mount, *temp;
>>>> +    int fd;
>>>> +
>>>> +    slog("guest-fsfreeze called");
>>>> +
>>>> +    if (guest_fsfreeze_state.status != GUEST_FSFREEZE_STATUS_THAWED) {
>>>
>>> return 0;
>>>
>>>> +        ret = 0;
>>>> +        goto out;
>>>> +    }
>>>> +
>>>> +    ret = guest_fsfreeze_build_mount_list();
>>>> +    if (ret<   0) {
>>>
>>> return ret;
>>>
>>>> +        goto out;
>>>> +    }
>>>> +
>>>> +    guest_fsfreeze_state.status = GUEST_FSFREEZE_STATUS_INPROGRESS;
>>>> +
>>>> +    /* cannot risk guest agent blocking itself on a write in this state */
>>>> +    disable_logging();
>>>> +
>>>> +    QTAILQ_FOREACH_SAFE(mount,&guest_fsfreeze_state.mount_list, next, temp) {
>>>> +        fd = qemu_open(mount->dirname, O_RDONLY);
>>>> +        if (fd == -1) {
>>>> +            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);
>>>> +
>>>> +        i++;
>>>> +    }
>>>> +
>>>> +    guest_fsfreeze_state.status = GUEST_FSFREEZE_STATUS_FROZEN;
>>>> +    ret = i;
>>>> +out:
>>>> +    return ret;
>>>> +error:
>>>> +    if (i>   0) {
>>>> +        guest_fsfreeze_state.status = GUEST_FSFREEZE_STATUS_ERROR;
>>>> +    }
>>>
>>> Shouldn't you undo everything that has been done so far? Which is
>>> freeing the build list and thawing the file-systems that were frozen
>>> before the error?
>>>
>>
>> We can...the way it's done right now is you get a count of how many
>> filesystems were frozen, along an error status. Depending on the
>> error/count the user can either ignore the error, do what it is they
>> want to do, then call thaw(), or just immediately call thaw().
>
> But you don't get the count on error, so it's difficult (if possible) to
> learn how many file-systems were frozen.
>

Yah, my mistake. I think the out: label was one line higher, but if we 
did that we lose error information. Automatically unfreezing should be okay.

>>
>> So we can do an automatic thaw() on their behalf, but i figured the
>> status was good enough.
>>
>>>> +    goto out;
>>>> +}
>>>> +
>>>> +/*
>>>> + * Walk list of frozen file systems in the guest, and thaw them.
>>>> + */
>>>> +int64_t qmp_guest_fsfreeze_thaw(Error **err)
>>>> +{
>>>> +    int ret;
>>>> +    GuestFsfreezeMount *mount, *temp;
>>>> +    int fd, i = 0;
>>>> +
>>>> +    if (guest_fsfreeze_state.status != GUEST_FSFREEZE_STATUS_FROZEN&&
>>>> +        guest_fsfreeze_state.status != GUEST_FSFREEZE_STATUS_INPROGRESS) {
>>>
>>> I don't follow why we're checking against INPROGRESS here.
>>>
>>
>> To prevent a race I believe...but we're synchronous now so that's
>> probably no longer needed. I'll look it over and remove it if that's the
>> case.
>>
>>>> +        ret = 0;
>>>> +        goto out_enable_logging;
>>>> +    }
>>>> +
>>>> +    QTAILQ_FOREACH_SAFE(mount,&guest_fsfreeze_state.mount_list, next, temp) {
>>>> +        fd = qemu_open(mount->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;
>>>
>>> Shouldn't you continue and try to thaw the other file-systems in the list?
>>>
>>
>> That's probably better
>>
>>>> +        }
>>>> +        close(fd);
>>>> +
>>>> +        QTAILQ_REMOVE(&guest_fsfreeze_state.mount_list, mount, next);
>>>> +        qemu_free(mount->dirname);
>>>> +        qemu_free(mount->devtype);
>>>> +        qemu_free(mount);
>>>> +        i++;
>>>> +    }
>>>> +
>>>> +    guest_fsfreeze_state.status = GUEST_FSFREEZE_STATUS_THAWED;
>>>> +    ret = i;
>>>> +out_enable_logging:
>>>> +    enable_logging();
>>>> +out:
>>>> +    return ret;
>>>> +}
>>>> +
>>>> +static void guest_fsfreeze_init(void)
>>>> +{
>>>> +    guest_fsfreeze_state.status = GUEST_FSFREEZE_STATUS_THAWED;
>>>> +    QTAILQ_INIT(&guest_fsfreeze_state.mount_list);
>>>> +}
>>>> +
>>>> +static void guest_fsfreeze_cleanup(void)
>>>> +{
>>>> +    int64_t ret;
>>>> +    Error *err = NULL;
>>>> +
>>>> +    if (guest_fsfreeze_state.status == GUEST_FSFREEZE_STATUS_FROZEN) {
>>>> +        ret = qmp_guest_fsfreeze_thaw(&err);
>>>> +        if (ret<   0 || err) {
>>>> +            slog("failed to clean up frozen filesystems");
>>>> +        }
>>>> +    }
>>>> +}
>>>> +
>>>> +/* 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, guest_fsfreeze_init, guest_fsfreeze_cleanup);
>>>> +    ga_command_state_add(cs, guest_file_init, NULL);
>>>> +}
>>>> diff --git a/qga/guest-agent-core.h b/qga/guest-agent-core.h
>>>> index 66d1729..3501ff4 100644
>>>> --- a/qga/guest-agent-core.h
>>>> +++ b/qga/guest-agent-core.h
>>>> @@ -14,10 +14,12 @@
>>>>    #include "qemu-common.h"
>>>>
>>>>    #define QGA_VERSION "1.0"
>>>> +#define QGA_READ_LIMIT 4<<   20 /* 4MB block size max for chunked reads */
>>>>
>>>>    typedef struct GAState GAState;
>>>>    typedef struct GACommandState GACommandState;
>>>>
>>>> +void ga_command_state_init(GAState *s, GACommandState *cs);
>>>>    void ga_command_state_add(GACommandState *cs,
>>>>                              void (*init)(void),
>>>>                              void (*cleanup)(void));
>>>
>>
>

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

* Re: [Qemu-devel] [PATCH v6 4/4] guest agent: add guest agent RPCs/commands
  2011-07-11 23:11         ` Michael Roth
@ 2011-07-12 14:15           ` Luiz Capitulino
  2011-07-12 15:44             ` Michael Roth
  0 siblings, 1 reply; 25+ messages in thread
From: Luiz Capitulino @ 2011-07-12 14:15 UTC (permalink / raw)
  To: Michael Roth; +Cc: aliguori, agl, qemu-devel, Jes.Sorensen

On Mon, 11 Jul 2011 18:11:21 -0500
Michael Roth <mdroth@linux.vnet.ibm.com> wrote:

> On 07/11/2011 04:12 PM, Luiz Capitulino wrote:
> > On Mon, 11 Jul 2011 15:11:26 -0500
> > Michael Roth<mdroth@linux.vnet.ibm.com>  wrote:
> >
> >> On 07/08/2011 10:14 AM, Luiz Capitulino wrote:
> >>> On Tue,  5 Jul 2011 08:21:40 -0500
> >>> Michael Roth<mdroth@linux.vnet.ibm.com>   wrote:
> >>>
> >>>> This adds the initial set of QMP/QAPI commands provided by the guest
> >>>> agent:
> >>>>
> >>>> guest-sync
> >>>> guest-ping
> >>>> guest-info
> >>>> guest-shutdown
> >>>> 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.
> >>>>
> >>>> Example usage:
> >>>>
> >>>>     host:
> >>>>       qemu -device virtio-serial \
> >>>>            -chardev socket,path=/tmp/vs0.sock,server,nowait,id=qga0 \
> >>>>            -device virtserialport,chardev=qga0,name=qga0
> >>>>            ...
> >>>>
> >>>>       echo "{'execute':'guest-info'}" | socat stdio \
> >>>>            unix-connect:/tmp/qga0.sock
> >>>>
> >>>>     guest:
> >>>>       qemu-ga -c virtio-serial -p /dev/virtio-ports/qga0 \
> >>>>               -p /var/run/qemu-guest-agent.pid -d
> >>>>
> >>>> Signed-off-by: Michael Roth<mdroth@linux.vnet.ibm.com>
> >>>> ---
> >>>>    Makefile                   |   15 ++-
> >>>>    qemu-ga.c                  |    4 +
> >>>>    qerror.h                   |    3 +
> >>>>    qga/guest-agent-commands.c |  501 ++++++++++++++++++++++++++++++++++++++++++++
> >>>>    qga/guest-agent-core.h     |    2 +
> >>>>    5 files changed, 523 insertions(+), 2 deletions(-)
> >>>>    create mode 100644 qga/guest-agent-commands.c
> >>>>
> >>>> diff --git a/Makefile b/Makefile
> >>>> index b2e8593..7e4f722 100644
> >>>> --- a/Makefile
> >>>> +++ b/Makefile
> >>>> @@ -175,15 +175,26 @@ $(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-visitor.o: $(addprefix $(qapi-dir)/, test-qapi-types.c test-qapi-types.h test-qapi-visit.c test-qapi-visit.h)
> >>>>    test-visitor: test-visitor.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
> >>>>
> >>>> -QGALIB=qga/guest-agent-command-state.o
> >>>> +QGALIB=qga/guest-agent-command-state.o qga/guest-agent-commands.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 $(QGALIB) 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 module.o qapi/qmp-dispatch.o qapi/qmp-registry.o
> >>>> +qemu-ga$(EXESUF): qemu-ga.o $(QGALIB) 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 module.o qapi/qmp-dispatch.o qapi/qmp-registry.o $(qapi-dir)/qga-qapi-visit.o $(qapi-dir)/qga-qmp-marshal.o
> >>>>
> >>>>    QEMULIBS=libhw32 libhw64 libuser libdis libdis-user
> >>>>
> >>>> diff --git a/qemu-ga.c b/qemu-ga.c
> >>>> index 649c16a..04ead22 100644
> >>>> --- a/qemu-ga.c
> >>>> +++ b/qemu-ga.c
> >>>> @@ -637,6 +637,9 @@ int main(int argc, char **argv)
> >>>>        g_log_set_default_handler(ga_log, s);
> >>>>        g_log_set_fatal_mask(NULL, G_LOG_LEVEL_ERROR);
> >>>>        s->logging_enabled = true;
> >>>> +    s->command_state = ga_command_state_new();
> >>>> +    ga_command_state_init(s, s->command_state);
> >>>> +    ga_command_state_init_all(s->command_state);
> >>>>        ga_state = s;
> >>>>
> >>>>        module_call_init(MODULE_INIT_QAPI);
> >>>> @@ -645,6 +648,7 @@ int main(int argc, char **argv)
> >>>>
> >>>>        g_main_loop_run(ga_state->main_loop);
> >>>>
> >>>> +    ga_command_state_cleanup_all(ga_state->command_state);
> >>>>        unlink(pidfile);
> >>>>
> >>>>        return 0;
> >>>> diff --git a/qerror.h b/qerror.h
> >>>> index 9a9fa5b..0f618ac 100644
> >>>> --- a/qerror.h
> >>>> +++ b/qerror.h
> >>>> @@ -184,4 +184,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 */
> >>>> diff --git a/qga/guest-agent-commands.c b/qga/guest-agent-commands.c
> >>>> new file mode 100644
> >>>> index 0000000..42390fb
> >>>> --- /dev/null
> >>>> +++ b/qga/guest-agent-commands.c
> >>>> @@ -0,0 +1,501 @@
> >>>> +/*
> >>>> + * 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"
> >>>> +#include "qerror.h"
> >>>> +#include "qemu-queue.h"
> >>>> +
> >>>> +static GAState *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 ga_logging_enabled() beforehand,
> >>>> + * and use the QERR_QGA_LOGGING_DISABLED to generate an error
> >>>> + */
> >>>> +static void slog(const char *fmt, ...)
> >>>> +{
> >>>> +    va_list ap;
> >>>> +
> >>>> +    va_start(ap, fmt);
> >>>> +    g_logv("syslog", G_LOG_LEVEL_INFO, fmt, ap);
> >>>> +    va_end(ap);
> >>>> +}
> >>>> +
> >>>> +int64_t qmp_guest_sync(int64_t id, Error **errp)
> >>>> +{
> >>>> +    return id;
> >>>> +}
> >>>> +
> >>>> +void qmp_guest_ping(Error **err)
> >>>> +{
> >>>> +    slog("guest-ping called");
> >>>> +}
> >>>> +
> >>>> +struct GuestAgentInfo *qmp_guest_info(Error **err)
> >>>> +{
> >>>> +    GuestAgentInfo *info = qemu_mallocz(sizeof(GuestAgentInfo));
> >>>> +
> >>>> +    info->version = g_strdup(QGA_VERSION);
> >>>> +
> >>>> +    return info;
> >>>> +}
> >>>> +
> >>>> +void qmp_guest_shutdown(const char *mode, Error **err)
> >>>> +{
> >>>> +    int ret;
> >>>> +    const char *shutdown_flag;
> >>>> +
> >>>> +    slog("guest-shutdown called, mode: %s", mode);
> >>>> +    if (strcmp(mode, "halt") == 0) {
> >>>> +        shutdown_flag = "-H";
> >>>> +    } else if (strcmp(mode, "powerdown") == 0) {
> >>>> +        shutdown_flag = "-P";
> >>>> +    } else if (strcmp(mode, "reboot") == 0) {
> >>>> +        shutdown_flag = "-r";
> >>>> +    } else {
> >>>> +        error_set(err, QERR_INVALID_PARAMETER_VALUE, "mode",
> >>>> +                  "halt|powerdown|reboot");
> >>>> +        return;
> >>>> +    }
> >>>> +
> >>>> +    ret = fork();
> >>>> +    if (ret == 0) {
> >>>> +        /* child, start the shutdown */
> >>>> +        setsid();
> >>>> +        fclose(stdin);
> >>>> +        fclose(stdout);
> >>>> +        fclose(stderr);
> >>>> +
> >>>> +        sleep(5);
> >>>
> >>> If we're required to return a response before the shutdown happens, this
> >>> is a bug and I'm afraid that the right way to this is a bit complex.
> >>>
> >>> Otherwise we can just leave it out.
> >>>
> >>
> >> Yah, I ran this by Anthony and Adam and just leaving it out seems to be
> >> the preferred approach, for now at least.
> >>
> >>>> +        ret = execl("/sbin/shutdown", "shutdown", shutdown_flag, "+0",
> >>>> +                    "hypervisor initiated shutdown", (char*)NULL);
> >>>> +        if (ret) {
> >>>> +            slog("guest-shutdown failed: %s", strerror(errno));
> >>>> +        }
> >>>> +        exit(!!ret);
> >>>> +    } else if (ret<   0) {
> >>>> +        error_set(err, QERR_UNDEFINED_ERROR);
> >>>> +    }
> >>>> +}
> >>>> +
> >>>> +typedef struct GuestFileHandle {
> >>>> +    uint64_t id;
> >>>> +    FILE *fh;
> >>>> +    QTAILQ_ENTRY(GuestFileHandle) next;
> >>>> +} GuestFileHandle;
> >>>> +
> >>>> +static struct {
> >>>> +    QTAILQ_HEAD(, GuestFileHandle) filehandles;
> >>>> +} guest_file_state;
> >>>> +
> >>>> +static void guest_file_handle_add(FILE *fh)
> >>>> +{
> >>>> +    GuestFileHandle *gfh;
> >>>> +
> >>>> +    gfh = qemu_mallocz(sizeof(GuestFileHandle));
> >>>> +    gfh->id = fileno(fh);
> >>>> +    gfh->fh = fh;
> >>>> +    QTAILQ_INSERT_TAIL(&guest_file_state.filehandles, gfh, next);
> >>>> +}
> >>>> +
> >>>> +static GuestFileHandle *guest_file_handle_find(int64_t id)
> >>>> +{
> >>>> +    GuestFileHandle *gfh;
> >>>> +
> >>>> +    QTAILQ_FOREACH(gfh,&guest_file_state.filehandles, next)
> >>>> +    {
> >>>> +        if (gfh->id == id) {
> >>>> +            return gfh;
> >>>> +        }
> >>>> +    }
> >>>> +
> >>>> +    return NULL;
> >>>> +}
> >>>> +
> >>>> +int64_t qmp_guest_file_open(const char *filepath, bool has_mode, const char *mode, Error **err)
> >>>> +{
> >>>> +    FILE *fh;
> >>>> +    int fd;
> >>>> +    int64_t ret = -1;
> >>>> +
> >>>> +    if (!has_mode) {
> >>>> +        mode = "r";
> >>>> +    }
> >>>> +    slog("guest-file-open called, filepath: %s, mode: %s", filepath, mode);
> >>>> +    fh = fopen(filepath, mode);
> >>>> +    if (!fh) {
> >>>> +        error_set(err, QERR_OPEN_FILE_FAILED, filepath);
> >>>> +        return -1;
> >>>> +    }
> >>>> +
> >>>> +    /* set fd non-blocking to avoid common use cases (like reading from a
> >>>> +     * named pipe) from hanging the agent
> >>>> +     */
> >>>> +    fd = fileno(fh);
> >>>> +    ret = fcntl(fd, F_GETFL);
> >>>> +    ret = fcntl(fd, F_SETFL, ret | O_NONBLOCK);
> >>>> +    if (ret == -1) {
> >>>> +        error_set(err, QERR_OPEN_FILE_FAILED, filepath);
> >>>> +        fclose(fh);
> >>>> +        return -1;
> >>>> +    }
> >>>> +
> >>>> +    guest_file_handle_add(fh);
> >>>> +    slog("guest-file-open, filehandle: %d", fd);
> >>>> +    return fd;
> >>>> +}
> >>>> +
> >>>> +struct GuestFileRead *qmp_guest_file_read(int64_t filehandle, int64_t count,
> >>>> +                                          Error **err)
> >>>> +{
> >>>> +    GuestFileHandle *gfh = guest_file_handle_find(filehandle);
> >>>> +    GuestFileRead *read_data;
> >>>> +    guchar *buf;
> >>>> +    FILE *fh;
> >>>> +    size_t read_count;
> >>>> +
> >>>> +    if (!gfh) {
> >>>> +        error_set(err, QERR_FD_NOT_FOUND, "filehandle");
> >>>> +        return NULL;
> >>>> +    }
> >>>> +
> >>>> +    if (count<   0 || count>   QGA_READ_LIMIT) {
> >>>> +        error_set(err, QERR_INVALID_PARAMETER, "count");
> >>>> +        return NULL;
> >>>> +    }
> >>>
> >>> Are we imposing that limit because of the malloc() call below? If that's
> >>> the case I think it's wrong, because we don't know the VM (neither the guest)
> >>> better than the client.
> >>>
> >>> The best thing we can do here is to limit it to the file size. Additionally
> >>> to this we could have a command-line option to allow the sysadmin set his/her
> >>> own limit.
> >>>
> >>
> >> That's technically the better approach, but we're also bound by the
> >> maximum token size limit in the JSON parser, which is 64MB. Best we can
> >> do is set QGA_READ_LIMIT accordingly.
> >
> > Good point, but I think it's ok to let the parser do this check itself, as
> > control won't get here anyway. Maybe we should only document that the server
> > imposes a limit on the token size.
> >
> 
>  From a usability perspective I think the QERR_INVALID_PARAMETER, or 
> perhaps something more descriptive, is a little nicer. Plus, they won't 
> have to wait for 64MB to get streamed back before they get the error :)

That's why I suggested documenting it. Are we going to add this check
to all commands that make it more likely to reach the parser's limit?
(also note that theoretically all commands can do it).

> 
> >>
> >>>> +
> >>>> +    fh = gfh->fh;
> >>>> +    read_data = qemu_mallocz(sizeof(GuestFileRead) + 1);
> >>>> +    buf = qemu_mallocz(count+1);
> >>>> +    if (!buf) {
> >>>> +        error_set(err, QERR_UNDEFINED_ERROR);
> >>>> +        return NULL;
> >>>> +    }
> >>>
> >>> qemu_malloc() functions never fail...
> >>>
> >>>> +
> >>>> +    read_count = fread(buf, 1, count, fh);
> >>>
> >>> Isn't 'nmemb' and 'size' swapped?
> >>>
> >>
> >> I'm not sure...
> >>
> >> size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
> >>
> >> I wrote it as $count number of 1-bytes elements. This seems logical to
> >> me, since it's a non-blocking FD which way result in a partial of some
> >> lesser number of bytes than count.
> >
> > Ok. I think either way will work.
> >
> >>
> >>>> +    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);
> >>>> +    }
> >>>> +    qemu_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;
> >>>> +    GuestFileHandle *gfh = guest_file_handle_find(filehandle);
> >>>> +    FILE *fh;
> >>>> +
> >>>> +    if (!gfh) {
> >>>> +        error_set(err, QERR_FD_NOT_FOUND, "filehandle");
> >>>> +        return NULL;
> >>>> +    }
> >>>> +
> >>>> +    fh = gfh->fh;
> >>>> +    data = g_base64_decode(data_b64,&data_len);
> >>>> +    if (count>   data_len) {
> >>>> +        qemu_free(data);
> >>>> +        error_set(err, QERR_INVALID_PARAMETER, "count");
> >>>> +        return NULL;
> >>>> +    }
> >>>> +    write_data = qemu_mallocz(sizeof(GuestFileWrite));
> >>>> +    write_count = fwrite(data, 1, count, fh);
> >>>> +    write_data->count = write_count;
> >>>> +    write_data->eof = feof(fh);
> >>>> +    qemu_free(data);
> >>>> +    clearerr(fh);
> >>>
> >>> Shouldn't we check for errors instead of doing this?
> >>>
> >>
> >> Yah...unfortunately we only get a boolean flag with ferror() so it's not
> >> all that useful, but I can add an error flag to the calls to capture it
> >> at least. clearerr() is only being used here to clear the eof flag.
> >
> > I meant to check fwrite()'s return.
> >
> 
> Ahh, right. Definitely.
> 
> >>
> >>> Btw, I think it's a good idea to offer guest-file-flush() too (or do a flush()
> >>> here, but that's probably bad).
> >>>
> >>
> >> Yah, I'd been planning on adding a guest-file-flush() for a while now.
> >> I'll add that for the respin.
> >>
> >>>> +
> >>>> +    return write_data;
> >>>> +}
> >>>> +
> >>>> +struct GuestFileSeek *qmp_guest_file_seek(int64_t filehandle, int64_t offset,
> >>>> +                                          int64_t whence, Error **err)
> >>>> +{
> >>>> +    GuestFileSeek *seek_data;
> >>>> +    GuestFileHandle *gfh = guest_file_handle_find(filehandle);
> >>>> +    FILE *fh;
> >>>> +    int ret;
> >>>> +
> >>>> +    if (!gfh) {
> >>>> +        error_set(err, QERR_FD_NOT_FOUND, "filehandle");
> >>>> +        return NULL;
> >>>> +    }
> >>>> +
> >>>> +    fh = gfh->fh;
> >>>> +    seek_data = qemu_mallocz(sizeof(GuestFileRead));
> >>>> +    ret = fseek(fh, offset, whence);
> >>>> +    if (ret == -1) {
> >>>> +        error_set(err, QERR_UNDEFINED_ERROR);
> >>>> +        qemu_free(seek_data);
> >>>> +        return NULL;
> >>>> +    }
> >>>> +    seek_data->position = ftell(fh);
> >>>> +    seek_data->eof = feof(fh);
> >>>> +    clearerr(fh);
> >>>
> >>> Again, I don't see why we should do this.
> >
> > This is probably ok, as we're checking fseek() above.
> >
> >>>
> >>>> +
> >>>> +    return seek_data;
> >>>> +}
> >>>> +
> >>>> +void qmp_guest_file_close(int64_t filehandle, Error **err)
> >>>> +{
> >>>> +    GuestFileHandle *gfh = guest_file_handle_find(filehandle);
> >>>> +
> >>>> +    slog("guest-file-close called, filehandle: %ld", filehandle);
> >>>> +    if (!gfh) {
> >>>> +        error_set(err, QERR_FD_NOT_FOUND, "filehandle");
> >>>> +        return;
> >>>> +    }
> >>>> +
> >>>> +    fclose(gfh->fh);
> >>>> +    QTAILQ_REMOVE(&guest_file_state.filehandles, gfh, next);
> >>>> +    qemu_free(gfh);
> >>>> +}
> >>>> +
> >>>> +static void guest_file_init(void)
> >>>> +{
> >>>> +    QTAILQ_INIT(&guest_file_state.filehandles);
> >>>> +}
> >>>> +
> >>>> +typedef struct GuestFsfreezeMount {
> >>>> +    char *dirname;
> >>>> +    char *devtype;
> >>>> +    QTAILQ_ENTRY(GuestFsfreezeMount) next;
> >>>> +} GuestFsfreezeMount;
> >>>> +
> >>>> +struct {
> >>>> +    GuestFsfreezeStatus status;
> >>>> +    QTAILQ_HEAD(, GuestFsfreezeMount) mount_list;
> >>>> +} guest_fsfreeze_state;
> >>>> +
> >>>> +/*
> >>>> + * Walk the mount table and build a list of local file systems
> >>>> + */
> >>>> +static int guest_fsfreeze_build_mount_list(void)
> >>>> +{
> >>>> +    struct mntent *ment;
> >>>> +    GuestFsfreezeMount *mount, *temp;
> >>>> +    char const *mtab = MOUNTED;
> >>>> +    FILE *fp;
> >>>> +
> >>>> +    fp = setmntent(mtab, "r");
> >>>> +    if (!fp) {
> >>>> +        g_warning("fsfreeze: unable to read mtab");
> >>>> +        goto fail;
> >>>> +    }
> >>>> +
> >>>> +    while ((ment = getmntent(fp))) {
> >>>> +        /*
> >>>> +         * An entry which device name doesn't start with a '/' is
> >>>> +         * either a dummy file system or a network file system.
> >>>> +         * Add special handling for smbfs and cifs as is done by
> >>>> +         * coreutils as well.
> >>>> +         */
> >>>> +        if ((ment->mnt_fsname[0] != '/') ||
> >>>> +            (strcmp(ment->mnt_type, "smbfs") == 0) ||
> >>>> +            (strcmp(ment->mnt_type, "cifs") == 0)) {
> >>>> +            continue;
> >>>> +        }
> >>>> +
> >>>> +        mount = qemu_mallocz(sizeof(GuestFsfreezeMount));
> >>>> +        mount->dirname = qemu_strdup(ment->mnt_dir);
> >>>> +        mount->devtype = qemu_strdup(ment->mnt_type);
> >>>> +
> >>>> +        QTAILQ_INSERT_TAIL(&guest_fsfreeze_state.mount_list, mount, next);
> >>>> +    }
> >>>> +
> >>>> +    endmntent(fp);
> >>>> +
> >>>> +    return 0;
> >>>> +
> >>>> +fail:
> >>>> +    QTAILQ_FOREACH_SAFE(mount,&guest_fsfreeze_state.mount_list, next, temp) {
> >>>> +        QTAILQ_REMOVE(&guest_fsfreeze_state.mount_list, mount, next);
> >>>> +        qemu_free(mount->dirname);
> >>>> +        qemu_free(mount->devtype);
> >>>> +        qemu_free(mount);
> >>>> +    }
> >>>
> >>> This doesn't seem to be used.
> >>>
> >>
> >> It can get used even a 2nd invocation of this function gets called that
> >> results in a goto fail. But looking again this should be done
> >> unconditionally at the start of the function, since the mount list is
> >> part of global state now.
> >>
> >>>> +
> >>>> +    return -1;
> >>>> +}
> >>>> +
> >>>> +/*
> >>>> + * Return status of freeze/thaw
> >>>> + */
> >>>> +GuestFsfreezeStatus qmp_guest_fsfreeze_status(Error **err)
> >>>> +{
> >>>> +    return guest_fsfreeze_state.status;
> >>>> +}
> >>>> +
> >>>> +/*
> >>>> + * Walk list of mounted file systems in the guest, and freeze the ones which
> >>>> + * are real local file systems.
> >>>> + */
> >>>> +int64_t qmp_guest_fsfreeze_freeze(Error **err)
> >>>> +{
> >>>> +    int ret = 0, i = 0;
> >>>> +    struct GuestFsfreezeMount *mount, *temp;
> >>>> +    int fd;
> >>>> +
> >>>> +    slog("guest-fsfreeze called");
> >>>> +
> >>>> +    if (guest_fsfreeze_state.status != GUEST_FSFREEZE_STATUS_THAWED) {
> >>>
> >>> return 0;
> >>>
> >>>> +        ret = 0;
> >>>> +        goto out;
> >>>> +    }
> >>>> +
> >>>> +    ret = guest_fsfreeze_build_mount_list();
> >>>> +    if (ret<   0) {
> >>>
> >>> return ret;
> >>>
> >>>> +        goto out;
> >>>> +    }
> >>>> +
> >>>> +    guest_fsfreeze_state.status = GUEST_FSFREEZE_STATUS_INPROGRESS;
> >>>> +
> >>>> +    /* cannot risk guest agent blocking itself on a write in this state */
> >>>> +    disable_logging();
> >>>> +
> >>>> +    QTAILQ_FOREACH_SAFE(mount,&guest_fsfreeze_state.mount_list, next, temp) {
> >>>> +        fd = qemu_open(mount->dirname, O_RDONLY);
> >>>> +        if (fd == -1) {
> >>>> +            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);
> >>>> +
> >>>> +        i++;
> >>>> +    }
> >>>> +
> >>>> +    guest_fsfreeze_state.status = GUEST_FSFREEZE_STATUS_FROZEN;
> >>>> +    ret = i;
> >>>> +out:
> >>>> +    return ret;
> >>>> +error:
> >>>> +    if (i>   0) {
> >>>> +        guest_fsfreeze_state.status = GUEST_FSFREEZE_STATUS_ERROR;
> >>>> +    }
> >>>
> >>> Shouldn't you undo everything that has been done so far? Which is
> >>> freeing the build list and thawing the file-systems that were frozen
> >>> before the error?
> >>>
> >>
> >> We can...the way it's done right now is you get a count of how many
> >> filesystems were frozen, along an error status. Depending on the
> >> error/count the user can either ignore the error, do what it is they
> >> want to do, then call thaw(), or just immediately call thaw().
> >
> > But you don't get the count on error, so it's difficult (if possible) to
> > learn how many file-systems were frozen.
> >
> 
> Yah, my mistake. I think the out: label was one line higher, but if we 
> did that we lose error information. Automatically unfreezing should be okay.
> 
> >>
> >> So we can do an automatic thaw() on their behalf, but i figured the
> >> status was good enough.
> >>
> >>>> +    goto out;
> >>>> +}
> >>>> +
> >>>> +/*
> >>>> + * Walk list of frozen file systems in the guest, and thaw them.
> >>>> + */
> >>>> +int64_t qmp_guest_fsfreeze_thaw(Error **err)
> >>>> +{
> >>>> +    int ret;
> >>>> +    GuestFsfreezeMount *mount, *temp;
> >>>> +    int fd, i = 0;
> >>>> +
> >>>> +    if (guest_fsfreeze_state.status != GUEST_FSFREEZE_STATUS_FROZEN&&
> >>>> +        guest_fsfreeze_state.status != GUEST_FSFREEZE_STATUS_INPROGRESS) {
> >>>
> >>> I don't follow why we're checking against INPROGRESS here.
> >>>
> >>
> >> To prevent a race I believe...but we're synchronous now so that's
> >> probably no longer needed. I'll look it over and remove it if that's the
> >> case.
> >>
> >>>> +        ret = 0;
> >>>> +        goto out_enable_logging;
> >>>> +    }
> >>>> +
> >>>> +    QTAILQ_FOREACH_SAFE(mount,&guest_fsfreeze_state.mount_list, next, temp) {
> >>>> +        fd = qemu_open(mount->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;
> >>>
> >>> Shouldn't you continue and try to thaw the other file-systems in the list?
> >>>
> >>
> >> That's probably better
> >>
> >>>> +        }
> >>>> +        close(fd);
> >>>> +
> >>>> +        QTAILQ_REMOVE(&guest_fsfreeze_state.mount_list, mount, next);
> >>>> +        qemu_free(mount->dirname);
> >>>> +        qemu_free(mount->devtype);
> >>>> +        qemu_free(mount);
> >>>> +        i++;
> >>>> +    }
> >>>> +
> >>>> +    guest_fsfreeze_state.status = GUEST_FSFREEZE_STATUS_THAWED;
> >>>> +    ret = i;
> >>>> +out_enable_logging:
> >>>> +    enable_logging();
> >>>> +out:
> >>>> +    return ret;
> >>>> +}
> >>>> +
> >>>> +static void guest_fsfreeze_init(void)
> >>>> +{
> >>>> +    guest_fsfreeze_state.status = GUEST_FSFREEZE_STATUS_THAWED;
> >>>> +    QTAILQ_INIT(&guest_fsfreeze_state.mount_list);
> >>>> +}
> >>>> +
> >>>> +static void guest_fsfreeze_cleanup(void)
> >>>> +{
> >>>> +    int64_t ret;
> >>>> +    Error *err = NULL;
> >>>> +
> >>>> +    if (guest_fsfreeze_state.status == GUEST_FSFREEZE_STATUS_FROZEN) {
> >>>> +        ret = qmp_guest_fsfreeze_thaw(&err);
> >>>> +        if (ret<   0 || err) {
> >>>> +            slog("failed to clean up frozen filesystems");
> >>>> +        }
> >>>> +    }
> >>>> +}
> >>>> +
> >>>> +/* 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, guest_fsfreeze_init, guest_fsfreeze_cleanup);
> >>>> +    ga_command_state_add(cs, guest_file_init, NULL);
> >>>> +}
> >>>> diff --git a/qga/guest-agent-core.h b/qga/guest-agent-core.h
> >>>> index 66d1729..3501ff4 100644
> >>>> --- a/qga/guest-agent-core.h
> >>>> +++ b/qga/guest-agent-core.h
> >>>> @@ -14,10 +14,12 @@
> >>>>    #include "qemu-common.h"
> >>>>
> >>>>    #define QGA_VERSION "1.0"
> >>>> +#define QGA_READ_LIMIT 4<<   20 /* 4MB block size max for chunked reads */
> >>>>
> >>>>    typedef struct GAState GAState;
> >>>>    typedef struct GACommandState GACommandState;
> >>>>
> >>>> +void ga_command_state_init(GAState *s, GACommandState *cs);
> >>>>    void ga_command_state_add(GACommandState *cs,
> >>>>                              void (*init)(void),
> >>>>                              void (*cleanup)(void));
> >>>
> >>
> >
> 

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

* Re: [Qemu-devel] [PATCH v6 4/4] guest agent: add guest agent RPCs/commands
  2011-07-12 14:15           ` Luiz Capitulino
@ 2011-07-12 15:44             ` Michael Roth
  2011-07-12 16:30               ` Luiz Capitulino
  0 siblings, 1 reply; 25+ messages in thread
From: Michael Roth @ 2011-07-12 15:44 UTC (permalink / raw)
  To: Luiz Capitulino; +Cc: aliguori, agl, qemu-devel, Jes.Sorensen

On 07/12/2011 09:15 AM, Luiz Capitulino wrote:
> On Mon, 11 Jul 2011 18:11:21 -0500
> Michael Roth<mdroth@linux.vnet.ibm.com>  wrote:
>
>> On 07/11/2011 04:12 PM, Luiz Capitulino wrote:
>>> On Mon, 11 Jul 2011 15:11:26 -0500
>>> Michael Roth<mdroth@linux.vnet.ibm.com>   wrote:
>>>
>>>> On 07/08/2011 10:14 AM, Luiz Capitulino wrote:
>>>>> On Tue,  5 Jul 2011 08:21:40 -0500
>>>>> Michael Roth<mdroth@linux.vnet.ibm.com>    wrote:
>>>>>
>>>>>> This adds the initial set of QMP/QAPI commands provided by the guest
>>>>>> agent:
>>>>>>
>>>>>> guest-sync
>>>>>> guest-ping
>>>>>> guest-info
>>>>>> guest-shutdown
>>>>>> 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.
>>>>>>
>>>>>> Example usage:
>>>>>>
>>>>>>      host:
>>>>>>        qemu -device virtio-serial \
>>>>>>             -chardev socket,path=/tmp/vs0.sock,server,nowait,id=qga0 \
>>>>>>             -device virtserialport,chardev=qga0,name=qga0
>>>>>>             ...
>>>>>>
>>>>>>        echo "{'execute':'guest-info'}" | socat stdio \
>>>>>>             unix-connect:/tmp/qga0.sock
>>>>>>
>>>>>>      guest:
>>>>>>        qemu-ga -c virtio-serial -p /dev/virtio-ports/qga0 \
>>>>>>                -p /var/run/qemu-guest-agent.pid -d
>>>>>>
>>>>>> Signed-off-by: Michael Roth<mdroth@linux.vnet.ibm.com>
>>>>>> ---
>>>>>>     Makefile                   |   15 ++-
>>>>>>     qemu-ga.c                  |    4 +
>>>>>>     qerror.h                   |    3 +
>>>>>>     qga/guest-agent-commands.c |  501 ++++++++++++++++++++++++++++++++++++++++++++
>>>>>>     qga/guest-agent-core.h     |    2 +
>>>>>>     5 files changed, 523 insertions(+), 2 deletions(-)
>>>>>>     create mode 100644 qga/guest-agent-commands.c
>>>>>>
>>>>>> diff --git a/Makefile b/Makefile
>>>>>> index b2e8593..7e4f722 100644
>>>>>> --- a/Makefile
>>>>>> +++ b/Makefile
>>>>>> @@ -175,15 +175,26 @@ $(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-visitor.o: $(addprefix $(qapi-dir)/, test-qapi-types.c test-qapi-types.h test-qapi-visit.c test-qapi-visit.h)
>>>>>>     test-visitor: test-visitor.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
>>>>>>
>>>>>> -QGALIB=qga/guest-agent-command-state.o
>>>>>> +QGALIB=qga/guest-agent-command-state.o qga/guest-agent-commands.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 $(QGALIB) 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 module.o qapi/qmp-dispatch.o qapi/qmp-registry.o
>>>>>> +qemu-ga$(EXESUF): qemu-ga.o $(QGALIB) 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 module.o qapi/qmp-dispatch.o qapi/qmp-registry.o $(qapi-dir)/qga-qapi-visit.o $(qapi-dir)/qga-qmp-marshal.o
>>>>>>
>>>>>>     QEMULIBS=libhw32 libhw64 libuser libdis libdis-user
>>>>>>
>>>>>> diff --git a/qemu-ga.c b/qemu-ga.c
>>>>>> index 649c16a..04ead22 100644
>>>>>> --- a/qemu-ga.c
>>>>>> +++ b/qemu-ga.c
>>>>>> @@ -637,6 +637,9 @@ int main(int argc, char **argv)
>>>>>>         g_log_set_default_handler(ga_log, s);
>>>>>>         g_log_set_fatal_mask(NULL, G_LOG_LEVEL_ERROR);
>>>>>>         s->logging_enabled = true;
>>>>>> +    s->command_state = ga_command_state_new();
>>>>>> +    ga_command_state_init(s, s->command_state);
>>>>>> +    ga_command_state_init_all(s->command_state);
>>>>>>         ga_state = s;
>>>>>>
>>>>>>         module_call_init(MODULE_INIT_QAPI);
>>>>>> @@ -645,6 +648,7 @@ int main(int argc, char **argv)
>>>>>>
>>>>>>         g_main_loop_run(ga_state->main_loop);
>>>>>>
>>>>>> +    ga_command_state_cleanup_all(ga_state->command_state);
>>>>>>         unlink(pidfile);
>>>>>>
>>>>>>         return 0;
>>>>>> diff --git a/qerror.h b/qerror.h
>>>>>> index 9a9fa5b..0f618ac 100644
>>>>>> --- a/qerror.h
>>>>>> +++ b/qerror.h
>>>>>> @@ -184,4 +184,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 */
>>>>>> diff --git a/qga/guest-agent-commands.c b/qga/guest-agent-commands.c
>>>>>> new file mode 100644
>>>>>> index 0000000..42390fb
>>>>>> --- /dev/null
>>>>>> +++ b/qga/guest-agent-commands.c
>>>>>> @@ -0,0 +1,501 @@
>>>>>> +/*
>>>>>> + * 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"
>>>>>> +#include "qerror.h"
>>>>>> +#include "qemu-queue.h"
>>>>>> +
>>>>>> +static GAState *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 ga_logging_enabled() beforehand,
>>>>>> + * and use the QERR_QGA_LOGGING_DISABLED to generate an error
>>>>>> + */
>>>>>> +static void slog(const char *fmt, ...)
>>>>>> +{
>>>>>> +    va_list ap;
>>>>>> +
>>>>>> +    va_start(ap, fmt);
>>>>>> +    g_logv("syslog", G_LOG_LEVEL_INFO, fmt, ap);
>>>>>> +    va_end(ap);
>>>>>> +}
>>>>>> +
>>>>>> +int64_t qmp_guest_sync(int64_t id, Error **errp)
>>>>>> +{
>>>>>> +    return id;
>>>>>> +}
>>>>>> +
>>>>>> +void qmp_guest_ping(Error **err)
>>>>>> +{
>>>>>> +    slog("guest-ping called");
>>>>>> +}
>>>>>> +
>>>>>> +struct GuestAgentInfo *qmp_guest_info(Error **err)
>>>>>> +{
>>>>>> +    GuestAgentInfo *info = qemu_mallocz(sizeof(GuestAgentInfo));
>>>>>> +
>>>>>> +    info->version = g_strdup(QGA_VERSION);
>>>>>> +
>>>>>> +    return info;
>>>>>> +}
>>>>>> +
>>>>>> +void qmp_guest_shutdown(const char *mode, Error **err)
>>>>>> +{
>>>>>> +    int ret;
>>>>>> +    const char *shutdown_flag;
>>>>>> +
>>>>>> +    slog("guest-shutdown called, mode: %s", mode);
>>>>>> +    if (strcmp(mode, "halt") == 0) {
>>>>>> +        shutdown_flag = "-H";
>>>>>> +    } else if (strcmp(mode, "powerdown") == 0) {
>>>>>> +        shutdown_flag = "-P";
>>>>>> +    } else if (strcmp(mode, "reboot") == 0) {
>>>>>> +        shutdown_flag = "-r";
>>>>>> +    } else {
>>>>>> +        error_set(err, QERR_INVALID_PARAMETER_VALUE, "mode",
>>>>>> +                  "halt|powerdown|reboot");
>>>>>> +        return;
>>>>>> +    }
>>>>>> +
>>>>>> +    ret = fork();
>>>>>> +    if (ret == 0) {
>>>>>> +        /* child, start the shutdown */
>>>>>> +        setsid();
>>>>>> +        fclose(stdin);
>>>>>> +        fclose(stdout);
>>>>>> +        fclose(stderr);
>>>>>> +
>>>>>> +        sleep(5);
>>>>>
>>>>> If we're required to return a response before the shutdown happens, this
>>>>> is a bug and I'm afraid that the right way to this is a bit complex.
>>>>>
>>>>> Otherwise we can just leave it out.
>>>>>
>>>>
>>>> Yah, I ran this by Anthony and Adam and just leaving it out seems to be
>>>> the preferred approach, for now at least.
>>>>
>>>>>> +        ret = execl("/sbin/shutdown", "shutdown", shutdown_flag, "+0",
>>>>>> +                    "hypervisor initiated shutdown", (char*)NULL);
>>>>>> +        if (ret) {
>>>>>> +            slog("guest-shutdown failed: %s", strerror(errno));
>>>>>> +        }
>>>>>> +        exit(!!ret);
>>>>>> +    } else if (ret<    0) {
>>>>>> +        error_set(err, QERR_UNDEFINED_ERROR);
>>>>>> +    }
>>>>>> +}
>>>>>> +
>>>>>> +typedef struct GuestFileHandle {
>>>>>> +    uint64_t id;
>>>>>> +    FILE *fh;
>>>>>> +    QTAILQ_ENTRY(GuestFileHandle) next;
>>>>>> +} GuestFileHandle;
>>>>>> +
>>>>>> +static struct {
>>>>>> +    QTAILQ_HEAD(, GuestFileHandle) filehandles;
>>>>>> +} guest_file_state;
>>>>>> +
>>>>>> +static void guest_file_handle_add(FILE *fh)
>>>>>> +{
>>>>>> +    GuestFileHandle *gfh;
>>>>>> +
>>>>>> +    gfh = qemu_mallocz(sizeof(GuestFileHandle));
>>>>>> +    gfh->id = fileno(fh);
>>>>>> +    gfh->fh = fh;
>>>>>> +    QTAILQ_INSERT_TAIL(&guest_file_state.filehandles, gfh, next);
>>>>>> +}
>>>>>> +
>>>>>> +static GuestFileHandle *guest_file_handle_find(int64_t id)
>>>>>> +{
>>>>>> +    GuestFileHandle *gfh;
>>>>>> +
>>>>>> +    QTAILQ_FOREACH(gfh,&guest_file_state.filehandles, next)
>>>>>> +    {
>>>>>> +        if (gfh->id == id) {
>>>>>> +            return gfh;
>>>>>> +        }
>>>>>> +    }
>>>>>> +
>>>>>> +    return NULL;
>>>>>> +}
>>>>>> +
>>>>>> +int64_t qmp_guest_file_open(const char *filepath, bool has_mode, const char *mode, Error **err)
>>>>>> +{
>>>>>> +    FILE *fh;
>>>>>> +    int fd;
>>>>>> +    int64_t ret = -1;
>>>>>> +
>>>>>> +    if (!has_mode) {
>>>>>> +        mode = "r";
>>>>>> +    }
>>>>>> +    slog("guest-file-open called, filepath: %s, mode: %s", filepath, mode);
>>>>>> +    fh = fopen(filepath, mode);
>>>>>> +    if (!fh) {
>>>>>> +        error_set(err, QERR_OPEN_FILE_FAILED, filepath);
>>>>>> +        return -1;
>>>>>> +    }
>>>>>> +
>>>>>> +    /* set fd non-blocking to avoid common use cases (like reading from a
>>>>>> +     * named pipe) from hanging the agent
>>>>>> +     */
>>>>>> +    fd = fileno(fh);
>>>>>> +    ret = fcntl(fd, F_GETFL);
>>>>>> +    ret = fcntl(fd, F_SETFL, ret | O_NONBLOCK);
>>>>>> +    if (ret == -1) {
>>>>>> +        error_set(err, QERR_OPEN_FILE_FAILED, filepath);
>>>>>> +        fclose(fh);
>>>>>> +        return -1;
>>>>>> +    }
>>>>>> +
>>>>>> +    guest_file_handle_add(fh);
>>>>>> +    slog("guest-file-open, filehandle: %d", fd);
>>>>>> +    return fd;
>>>>>> +}
>>>>>> +
>>>>>> +struct GuestFileRead *qmp_guest_file_read(int64_t filehandle, int64_t count,
>>>>>> +                                          Error **err)
>>>>>> +{
>>>>>> +    GuestFileHandle *gfh = guest_file_handle_find(filehandle);
>>>>>> +    GuestFileRead *read_data;
>>>>>> +    guchar *buf;
>>>>>> +    FILE *fh;
>>>>>> +    size_t read_count;
>>>>>> +
>>>>>> +    if (!gfh) {
>>>>>> +        error_set(err, QERR_FD_NOT_FOUND, "filehandle");
>>>>>> +        return NULL;
>>>>>> +    }
>>>>>> +
>>>>>> +    if (count<    0 || count>    QGA_READ_LIMIT) {
>>>>>> +        error_set(err, QERR_INVALID_PARAMETER, "count");
>>>>>> +        return NULL;
>>>>>> +    }
>>>>>
>>>>> Are we imposing that limit because of the malloc() call below? If that's
>>>>> the case I think it's wrong, because we don't know the VM (neither the guest)
>>>>> better than the client.
>>>>>
>>>>> The best thing we can do here is to limit it to the file size. Additionally
>>>>> to this we could have a command-line option to allow the sysadmin set his/her
>>>>> own limit.
>>>>>
>>>>
>>>> That's technically the better approach, but we're also bound by the
>>>> maximum token size limit in the JSON parser, which is 64MB. Best we can
>>>> do is set QGA_READ_LIMIT accordingly.
>>>
>>> Good point, but I think it's ok to let the parser do this check itself, as
>>> control won't get here anyway. Maybe we should only document that the server
>>> imposes a limit on the token size.
>>>
>>
>>    From a usability perspective I think the QERR_INVALID_PARAMETER, or
>> perhaps something more descriptive, is a little nicer. Plus, they won't
>> have to wait for 64MB to get streamed back before they get the error :)
>
> That's why I suggested documenting it. Are we going to add this check
> to all commands that make it more likely to reach the parser's limit?
> (also note that theoretically all commands can do it).
>

Good point, and I agree that it needs to be documented either way. Might 
not hurt to further clarify the limitations this imposes on a command 
like guest-file-read in the schema documentation where appropriate though.

I'm not sure about the alternative suggestion of bounding this by 
filesize though, since they may also be writing to the same fd via 
guest-file-write, and the current file size wouldn't be meaningful 
except after an fflush() or fclose(). So we'd be forced to fflush() in 
guest-file-write to implement this somewhat reliably, but I think we 
agree that a separate guest-file-flush would be better.

So let's just let the client blow up their guest if they're so inclined.

>>
>>>>
>>>>>> +
>>>>>> +    fh = gfh->fh;
>>>>>> +    read_data = qemu_mallocz(sizeof(GuestFileRead) + 1);
>>>>>> +    buf = qemu_mallocz(count+1);
>>>>>> +    if (!buf) {
>>>>>> +        error_set(err, QERR_UNDEFINED_ERROR);
>>>>>> +        return NULL;
>>>>>> +    }
>>>>>
>>>>> qemu_malloc() functions never fail...
>>>>>
>>>>>> +
>>>>>> +    read_count = fread(buf, 1, count, fh);
>>>>>
>>>>> Isn't 'nmemb' and 'size' swapped?
>>>>>
>>>>
>>>> I'm not sure...
>>>>
>>>> size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
>>>>
>>>> I wrote it as $count number of 1-bytes elements. This seems logical to
>>>> me, since it's a non-blocking FD which way result in a partial of some
>>>> lesser number of bytes than count.
>>>
>>> Ok. I think either way will work.
>>>
>>>>
>>>>>> +    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);
>>>>>> +    }
>>>>>> +    qemu_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;
>>>>>> +    GuestFileHandle *gfh = guest_file_handle_find(filehandle);
>>>>>> +    FILE *fh;
>>>>>> +
>>>>>> +    if (!gfh) {
>>>>>> +        error_set(err, QERR_FD_NOT_FOUND, "filehandle");
>>>>>> +        return NULL;
>>>>>> +    }
>>>>>> +
>>>>>> +    fh = gfh->fh;
>>>>>> +    data = g_base64_decode(data_b64,&data_len);
>>>>>> +    if (count>    data_len) {
>>>>>> +        qemu_free(data);
>>>>>> +        error_set(err, QERR_INVALID_PARAMETER, "count");
>>>>>> +        return NULL;
>>>>>> +    }
>>>>>> +    write_data = qemu_mallocz(sizeof(GuestFileWrite));
>>>>>> +    write_count = fwrite(data, 1, count, fh);
>>>>>> +    write_data->count = write_count;
>>>>>> +    write_data->eof = feof(fh);
>>>>>> +    qemu_free(data);
>>>>>> +    clearerr(fh);
>>>>>
>>>>> Shouldn't we check for errors instead of doing this?
>>>>>
>>>>
>>>> Yah...unfortunately we only get a boolean flag with ferror() so it's not
>>>> all that useful, but I can add an error flag to the calls to capture it
>>>> at least. clearerr() is only being used here to clear the eof flag.
>>>
>>> I meant to check fwrite()'s return.
>>>
>>
>> Ahh, right. Definitely.
>>
>>>>
>>>>> Btw, I think it's a good idea to offer guest-file-flush() too (or do a flush()
>>>>> here, but that's probably bad).
>>>>>
>>>>
>>>> Yah, I'd been planning on adding a guest-file-flush() for a while now.
>>>> I'll add that for the respin.
>>>>
>>>>>> +
>>>>>> +    return write_data;
>>>>>> +}
>>>>>> +
>>>>>> +struct GuestFileSeek *qmp_guest_file_seek(int64_t filehandle, int64_t offset,
>>>>>> +                                          int64_t whence, Error **err)
>>>>>> +{
>>>>>> +    GuestFileSeek *seek_data;
>>>>>> +    GuestFileHandle *gfh = guest_file_handle_find(filehandle);
>>>>>> +    FILE *fh;
>>>>>> +    int ret;
>>>>>> +
>>>>>> +    if (!gfh) {
>>>>>> +        error_set(err, QERR_FD_NOT_FOUND, "filehandle");
>>>>>> +        return NULL;
>>>>>> +    }
>>>>>> +
>>>>>> +    fh = gfh->fh;
>>>>>> +    seek_data = qemu_mallocz(sizeof(GuestFileRead));
>>>>>> +    ret = fseek(fh, offset, whence);
>>>>>> +    if (ret == -1) {
>>>>>> +        error_set(err, QERR_UNDEFINED_ERROR);
>>>>>> +        qemu_free(seek_data);
>>>>>> +        return NULL;
>>>>>> +    }
>>>>>> +    seek_data->position = ftell(fh);
>>>>>> +    seek_data->eof = feof(fh);
>>>>>> +    clearerr(fh);
>>>>>
>>>>> Again, I don't see why we should do this.
>>>
>>> This is probably ok, as we're checking fseek() above.
>>>
>>>>>
>>>>>> +
>>>>>> +    return seek_data;
>>>>>> +}
>>>>>> +
>>>>>> +void qmp_guest_file_close(int64_t filehandle, Error **err)
>>>>>> +{
>>>>>> +    GuestFileHandle *gfh = guest_file_handle_find(filehandle);
>>>>>> +
>>>>>> +    slog("guest-file-close called, filehandle: %ld", filehandle);
>>>>>> +    if (!gfh) {
>>>>>> +        error_set(err, QERR_FD_NOT_FOUND, "filehandle");
>>>>>> +        return;
>>>>>> +    }
>>>>>> +
>>>>>> +    fclose(gfh->fh);
>>>>>> +    QTAILQ_REMOVE(&guest_file_state.filehandles, gfh, next);
>>>>>> +    qemu_free(gfh);
>>>>>> +}
>>>>>> +
>>>>>> +static void guest_file_init(void)
>>>>>> +{
>>>>>> +    QTAILQ_INIT(&guest_file_state.filehandles);
>>>>>> +}
>>>>>> +
>>>>>> +typedef struct GuestFsfreezeMount {
>>>>>> +    char *dirname;
>>>>>> +    char *devtype;
>>>>>> +    QTAILQ_ENTRY(GuestFsfreezeMount) next;
>>>>>> +} GuestFsfreezeMount;
>>>>>> +
>>>>>> +struct {
>>>>>> +    GuestFsfreezeStatus status;
>>>>>> +    QTAILQ_HEAD(, GuestFsfreezeMount) mount_list;
>>>>>> +} guest_fsfreeze_state;
>>>>>> +
>>>>>> +/*
>>>>>> + * Walk the mount table and build a list of local file systems
>>>>>> + */
>>>>>> +static int guest_fsfreeze_build_mount_list(void)
>>>>>> +{
>>>>>> +    struct mntent *ment;
>>>>>> +    GuestFsfreezeMount *mount, *temp;
>>>>>> +    char const *mtab = MOUNTED;
>>>>>> +    FILE *fp;
>>>>>> +
>>>>>> +    fp = setmntent(mtab, "r");
>>>>>> +    if (!fp) {
>>>>>> +        g_warning("fsfreeze: unable to read mtab");
>>>>>> +        goto fail;
>>>>>> +    }
>>>>>> +
>>>>>> +    while ((ment = getmntent(fp))) {
>>>>>> +        /*
>>>>>> +         * An entry which device name doesn't start with a '/' is
>>>>>> +         * either a dummy file system or a network file system.
>>>>>> +         * Add special handling for smbfs and cifs as is done by
>>>>>> +         * coreutils as well.
>>>>>> +         */
>>>>>> +        if ((ment->mnt_fsname[0] != '/') ||
>>>>>> +            (strcmp(ment->mnt_type, "smbfs") == 0) ||
>>>>>> +            (strcmp(ment->mnt_type, "cifs") == 0)) {
>>>>>> +            continue;
>>>>>> +        }
>>>>>> +
>>>>>> +        mount = qemu_mallocz(sizeof(GuestFsfreezeMount));
>>>>>> +        mount->dirname = qemu_strdup(ment->mnt_dir);
>>>>>> +        mount->devtype = qemu_strdup(ment->mnt_type);
>>>>>> +
>>>>>> +        QTAILQ_INSERT_TAIL(&guest_fsfreeze_state.mount_list, mount, next);
>>>>>> +    }
>>>>>> +
>>>>>> +    endmntent(fp);
>>>>>> +
>>>>>> +    return 0;
>>>>>> +
>>>>>> +fail:
>>>>>> +    QTAILQ_FOREACH_SAFE(mount,&guest_fsfreeze_state.mount_list, next, temp) {
>>>>>> +        QTAILQ_REMOVE(&guest_fsfreeze_state.mount_list, mount, next);
>>>>>> +        qemu_free(mount->dirname);
>>>>>> +        qemu_free(mount->devtype);
>>>>>> +        qemu_free(mount);
>>>>>> +    }
>>>>>
>>>>> This doesn't seem to be used.
>>>>>
>>>>
>>>> It can get used even a 2nd invocation of this function gets called that
>>>> results in a goto fail. But looking again this should be done
>>>> unconditionally at the start of the function, since the mount list is
>>>> part of global state now.
>>>>
>>>>>> +
>>>>>> +    return -1;
>>>>>> +}
>>>>>> +
>>>>>> +/*
>>>>>> + * Return status of freeze/thaw
>>>>>> + */
>>>>>> +GuestFsfreezeStatus qmp_guest_fsfreeze_status(Error **err)
>>>>>> +{
>>>>>> +    return guest_fsfreeze_state.status;
>>>>>> +}
>>>>>> +
>>>>>> +/*
>>>>>> + * Walk list of mounted file systems in the guest, and freeze the ones which
>>>>>> + * are real local file systems.
>>>>>> + */
>>>>>> +int64_t qmp_guest_fsfreeze_freeze(Error **err)
>>>>>> +{
>>>>>> +    int ret = 0, i = 0;
>>>>>> +    struct GuestFsfreezeMount *mount, *temp;
>>>>>> +    int fd;
>>>>>> +
>>>>>> +    slog("guest-fsfreeze called");
>>>>>> +
>>>>>> +    if (guest_fsfreeze_state.status != GUEST_FSFREEZE_STATUS_THAWED) {
>>>>>
>>>>> return 0;
>>>>>
>>>>>> +        ret = 0;
>>>>>> +        goto out;
>>>>>> +    }
>>>>>> +
>>>>>> +    ret = guest_fsfreeze_build_mount_list();
>>>>>> +    if (ret<    0) {
>>>>>
>>>>> return ret;
>>>>>
>>>>>> +        goto out;
>>>>>> +    }
>>>>>> +
>>>>>> +    guest_fsfreeze_state.status = GUEST_FSFREEZE_STATUS_INPROGRESS;
>>>>>> +
>>>>>> +    /* cannot risk guest agent blocking itself on a write in this state */
>>>>>> +    disable_logging();
>>>>>> +
>>>>>> +    QTAILQ_FOREACH_SAFE(mount,&guest_fsfreeze_state.mount_list, next, temp) {
>>>>>> +        fd = qemu_open(mount->dirname, O_RDONLY);
>>>>>> +        if (fd == -1) {
>>>>>> +            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);
>>>>>> +
>>>>>> +        i++;
>>>>>> +    }
>>>>>> +
>>>>>> +    guest_fsfreeze_state.status = GUEST_FSFREEZE_STATUS_FROZEN;
>>>>>> +    ret = i;
>>>>>> +out:
>>>>>> +    return ret;
>>>>>> +error:
>>>>>> +    if (i>    0) {
>>>>>> +        guest_fsfreeze_state.status = GUEST_FSFREEZE_STATUS_ERROR;
>>>>>> +    }
>>>>>
>>>>> Shouldn't you undo everything that has been done so far? Which is
>>>>> freeing the build list and thawing the file-systems that were frozen
>>>>> before the error?
>>>>>
>>>>
>>>> We can...the way it's done right now is you get a count of how many
>>>> filesystems were frozen, along an error status. Depending on the
>>>> error/count the user can either ignore the error, do what it is they
>>>> want to do, then call thaw(), or just immediately call thaw().
>>>
>>> But you don't get the count on error, so it's difficult (if possible) to
>>> learn how many file-systems were frozen.
>>>
>>
>> Yah, my mistake. I think the out: label was one line higher, but if we
>> did that we lose error information. Automatically unfreezing should be okay.
>>
>>>>
>>>> So we can do an automatic thaw() on their behalf, but i figured the
>>>> status was good enough.
>>>>
>>>>>> +    goto out;
>>>>>> +}
>>>>>> +
>>>>>> +/*
>>>>>> + * Walk list of frozen file systems in the guest, and thaw them.
>>>>>> + */
>>>>>> +int64_t qmp_guest_fsfreeze_thaw(Error **err)
>>>>>> +{
>>>>>> +    int ret;
>>>>>> +    GuestFsfreezeMount *mount, *temp;
>>>>>> +    int fd, i = 0;
>>>>>> +
>>>>>> +    if (guest_fsfreeze_state.status != GUEST_FSFREEZE_STATUS_FROZEN&&
>>>>>> +        guest_fsfreeze_state.status != GUEST_FSFREEZE_STATUS_INPROGRESS) {
>>>>>
>>>>> I don't follow why we're checking against INPROGRESS here.
>>>>>
>>>>
>>>> To prevent a race I believe...but we're synchronous now so that's
>>>> probably no longer needed. I'll look it over and remove it if that's the
>>>> case.
>>>>
>>>>>> +        ret = 0;
>>>>>> +        goto out_enable_logging;
>>>>>> +    }
>>>>>> +
>>>>>> +    QTAILQ_FOREACH_SAFE(mount,&guest_fsfreeze_state.mount_list, next, temp) {
>>>>>> +        fd = qemu_open(mount->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;
>>>>>
>>>>> Shouldn't you continue and try to thaw the other file-systems in the list?
>>>>>
>>>>
>>>> That's probably better
>>>>
>>>>>> +        }
>>>>>> +        close(fd);
>>>>>> +
>>>>>> +        QTAILQ_REMOVE(&guest_fsfreeze_state.mount_list, mount, next);
>>>>>> +        qemu_free(mount->dirname);
>>>>>> +        qemu_free(mount->devtype);
>>>>>> +        qemu_free(mount);
>>>>>> +        i++;
>>>>>> +    }
>>>>>> +
>>>>>> +    guest_fsfreeze_state.status = GUEST_FSFREEZE_STATUS_THAWED;
>>>>>> +    ret = i;
>>>>>> +out_enable_logging:
>>>>>> +    enable_logging();
>>>>>> +out:
>>>>>> +    return ret;
>>>>>> +}
>>>>>> +
>>>>>> +static void guest_fsfreeze_init(void)
>>>>>> +{
>>>>>> +    guest_fsfreeze_state.status = GUEST_FSFREEZE_STATUS_THAWED;
>>>>>> +    QTAILQ_INIT(&guest_fsfreeze_state.mount_list);
>>>>>> +}
>>>>>> +
>>>>>> +static void guest_fsfreeze_cleanup(void)
>>>>>> +{
>>>>>> +    int64_t ret;
>>>>>> +    Error *err = NULL;
>>>>>> +
>>>>>> +    if (guest_fsfreeze_state.status == GUEST_FSFREEZE_STATUS_FROZEN) {
>>>>>> +        ret = qmp_guest_fsfreeze_thaw(&err);
>>>>>> +        if (ret<    0 || err) {
>>>>>> +            slog("failed to clean up frozen filesystems");
>>>>>> +        }
>>>>>> +    }
>>>>>> +}
>>>>>> +
>>>>>> +/* 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, guest_fsfreeze_init, guest_fsfreeze_cleanup);
>>>>>> +    ga_command_state_add(cs, guest_file_init, NULL);
>>>>>> +}
>>>>>> diff --git a/qga/guest-agent-core.h b/qga/guest-agent-core.h
>>>>>> index 66d1729..3501ff4 100644
>>>>>> --- a/qga/guest-agent-core.h
>>>>>> +++ b/qga/guest-agent-core.h
>>>>>> @@ -14,10 +14,12 @@
>>>>>>     #include "qemu-common.h"
>>>>>>
>>>>>>     #define QGA_VERSION "1.0"
>>>>>> +#define QGA_READ_LIMIT 4<<    20 /* 4MB block size max for chunked reads */
>>>>>>
>>>>>>     typedef struct GAState GAState;
>>>>>>     typedef struct GACommandState GACommandState;
>>>>>>
>>>>>> +void ga_command_state_init(GAState *s, GACommandState *cs);
>>>>>>     void ga_command_state_add(GACommandState *cs,
>>>>>>                               void (*init)(void),
>>>>>>                               void (*cleanup)(void));
>>>>>
>>>>
>>>
>>
>

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

* Re: [Qemu-devel] [PATCH v6 4/4] guest agent: add guest agent RPCs/commands
  2011-07-12 15:44             ` Michael Roth
@ 2011-07-12 16:30               ` Luiz Capitulino
  0 siblings, 0 replies; 25+ messages in thread
From: Luiz Capitulino @ 2011-07-12 16:30 UTC (permalink / raw)
  To: Michael Roth; +Cc: aliguori, agl, qemu-devel, Jes.Sorensen

On Tue, 12 Jul 2011 10:44:14 -0500
Michael Roth <mdroth@linux.vnet.ibm.com> wrote:

> On 07/12/2011 09:15 AM, Luiz Capitulino wrote:
> > On Mon, 11 Jul 2011 18:11:21 -0500
> > Michael Roth<mdroth@linux.vnet.ibm.com>  wrote:
> >
> >> On 07/11/2011 04:12 PM, Luiz Capitulino wrote:
> >>> On Mon, 11 Jul 2011 15:11:26 -0500
> >>> Michael Roth<mdroth@linux.vnet.ibm.com>   wrote:
> >>>
> >>>> On 07/08/2011 10:14 AM, Luiz Capitulino wrote:
> >>>>> On Tue,  5 Jul 2011 08:21:40 -0500
> >>>>> Michael Roth<mdroth@linux.vnet.ibm.com>    wrote:
> >>>>>
> >>>>>> This adds the initial set of QMP/QAPI commands provided by the guest
> >>>>>> agent:
> >>>>>>
> >>>>>> guest-sync
> >>>>>> guest-ping
> >>>>>> guest-info
> >>>>>> guest-shutdown
> >>>>>> 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.
> >>>>>>
> >>>>>> Example usage:
> >>>>>>
> >>>>>>      host:
> >>>>>>        qemu -device virtio-serial \
> >>>>>>             -chardev socket,path=/tmp/vs0.sock,server,nowait,id=qga0 \
> >>>>>>             -device virtserialport,chardev=qga0,name=qga0
> >>>>>>             ...
> >>>>>>
> >>>>>>        echo "{'execute':'guest-info'}" | socat stdio \
> >>>>>>             unix-connect:/tmp/qga0.sock
> >>>>>>
> >>>>>>      guest:
> >>>>>>        qemu-ga -c virtio-serial -p /dev/virtio-ports/qga0 \
> >>>>>>                -p /var/run/qemu-guest-agent.pid -d
> >>>>>>
> >>>>>> Signed-off-by: Michael Roth<mdroth@linux.vnet.ibm.com>
> >>>>>> ---
> >>>>>>     Makefile                   |   15 ++-
> >>>>>>     qemu-ga.c                  |    4 +
> >>>>>>     qerror.h                   |    3 +
> >>>>>>     qga/guest-agent-commands.c |  501 ++++++++++++++++++++++++++++++++++++++++++++
> >>>>>>     qga/guest-agent-core.h     |    2 +
> >>>>>>     5 files changed, 523 insertions(+), 2 deletions(-)
> >>>>>>     create mode 100644 qga/guest-agent-commands.c
> >>>>>>
> >>>>>> diff --git a/Makefile b/Makefile
> >>>>>> index b2e8593..7e4f722 100644
> >>>>>> --- a/Makefile
> >>>>>> +++ b/Makefile
> >>>>>> @@ -175,15 +175,26 @@ $(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-visitor.o: $(addprefix $(qapi-dir)/, test-qapi-types.c test-qapi-types.h test-qapi-visit.c test-qapi-visit.h)
> >>>>>>     test-visitor: test-visitor.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
> >>>>>>
> >>>>>> -QGALIB=qga/guest-agent-command-state.o
> >>>>>> +QGALIB=qga/guest-agent-command-state.o qga/guest-agent-commands.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 $(QGALIB) 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 module.o qapi/qmp-dispatch.o qapi/qmp-registry.o
> >>>>>> +qemu-ga$(EXESUF): qemu-ga.o $(QGALIB) 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 module.o qapi/qmp-dispatch.o qapi/qmp-registry.o $(qapi-dir)/qga-qapi-visit.o $(qapi-dir)/qga-qmp-marshal.o
> >>>>>>
> >>>>>>     QEMULIBS=libhw32 libhw64 libuser libdis libdis-user
> >>>>>>
> >>>>>> diff --git a/qemu-ga.c b/qemu-ga.c
> >>>>>> index 649c16a..04ead22 100644
> >>>>>> --- a/qemu-ga.c
> >>>>>> +++ b/qemu-ga.c
> >>>>>> @@ -637,6 +637,9 @@ int main(int argc, char **argv)
> >>>>>>         g_log_set_default_handler(ga_log, s);
> >>>>>>         g_log_set_fatal_mask(NULL, G_LOG_LEVEL_ERROR);
> >>>>>>         s->logging_enabled = true;
> >>>>>> +    s->command_state = ga_command_state_new();
> >>>>>> +    ga_command_state_init(s, s->command_state);
> >>>>>> +    ga_command_state_init_all(s->command_state);
> >>>>>>         ga_state = s;
> >>>>>>
> >>>>>>         module_call_init(MODULE_INIT_QAPI);
> >>>>>> @@ -645,6 +648,7 @@ int main(int argc, char **argv)
> >>>>>>
> >>>>>>         g_main_loop_run(ga_state->main_loop);
> >>>>>>
> >>>>>> +    ga_command_state_cleanup_all(ga_state->command_state);
> >>>>>>         unlink(pidfile);
> >>>>>>
> >>>>>>         return 0;
> >>>>>> diff --git a/qerror.h b/qerror.h
> >>>>>> index 9a9fa5b..0f618ac 100644
> >>>>>> --- a/qerror.h
> >>>>>> +++ b/qerror.h
> >>>>>> @@ -184,4 +184,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 */
> >>>>>> diff --git a/qga/guest-agent-commands.c b/qga/guest-agent-commands.c
> >>>>>> new file mode 100644
> >>>>>> index 0000000..42390fb
> >>>>>> --- /dev/null
> >>>>>> +++ b/qga/guest-agent-commands.c
> >>>>>> @@ -0,0 +1,501 @@
> >>>>>> +/*
> >>>>>> + * 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"
> >>>>>> +#include "qerror.h"
> >>>>>> +#include "qemu-queue.h"
> >>>>>> +
> >>>>>> +static GAState *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 ga_logging_enabled() beforehand,
> >>>>>> + * and use the QERR_QGA_LOGGING_DISABLED to generate an error
> >>>>>> + */
> >>>>>> +static void slog(const char *fmt, ...)
> >>>>>> +{
> >>>>>> +    va_list ap;
> >>>>>> +
> >>>>>> +    va_start(ap, fmt);
> >>>>>> +    g_logv("syslog", G_LOG_LEVEL_INFO, fmt, ap);
> >>>>>> +    va_end(ap);
> >>>>>> +}
> >>>>>> +
> >>>>>> +int64_t qmp_guest_sync(int64_t id, Error **errp)
> >>>>>> +{
> >>>>>> +    return id;
> >>>>>> +}
> >>>>>> +
> >>>>>> +void qmp_guest_ping(Error **err)
> >>>>>> +{
> >>>>>> +    slog("guest-ping called");
> >>>>>> +}
> >>>>>> +
> >>>>>> +struct GuestAgentInfo *qmp_guest_info(Error **err)
> >>>>>> +{
> >>>>>> +    GuestAgentInfo *info = qemu_mallocz(sizeof(GuestAgentInfo));
> >>>>>> +
> >>>>>> +    info->version = g_strdup(QGA_VERSION);
> >>>>>> +
> >>>>>> +    return info;
> >>>>>> +}
> >>>>>> +
> >>>>>> +void qmp_guest_shutdown(const char *mode, Error **err)
> >>>>>> +{
> >>>>>> +    int ret;
> >>>>>> +    const char *shutdown_flag;
> >>>>>> +
> >>>>>> +    slog("guest-shutdown called, mode: %s", mode);
> >>>>>> +    if (strcmp(mode, "halt") == 0) {
> >>>>>> +        shutdown_flag = "-H";
> >>>>>> +    } else if (strcmp(mode, "powerdown") == 0) {
> >>>>>> +        shutdown_flag = "-P";
> >>>>>> +    } else if (strcmp(mode, "reboot") == 0) {
> >>>>>> +        shutdown_flag = "-r";
> >>>>>> +    } else {
> >>>>>> +        error_set(err, QERR_INVALID_PARAMETER_VALUE, "mode",
> >>>>>> +                  "halt|powerdown|reboot");
> >>>>>> +        return;
> >>>>>> +    }
> >>>>>> +
> >>>>>> +    ret = fork();
> >>>>>> +    if (ret == 0) {
> >>>>>> +        /* child, start the shutdown */
> >>>>>> +        setsid();
> >>>>>> +        fclose(stdin);
> >>>>>> +        fclose(stdout);
> >>>>>> +        fclose(stderr);
> >>>>>> +
> >>>>>> +        sleep(5);
> >>>>>
> >>>>> If we're required to return a response before the shutdown happens, this
> >>>>> is a bug and I'm afraid that the right way to this is a bit complex.
> >>>>>
> >>>>> Otherwise we can just leave it out.
> >>>>>
> >>>>
> >>>> Yah, I ran this by Anthony and Adam and just leaving it out seems to be
> >>>> the preferred approach, for now at least.
> >>>>
> >>>>>> +        ret = execl("/sbin/shutdown", "shutdown", shutdown_flag, "+0",
> >>>>>> +                    "hypervisor initiated shutdown", (char*)NULL);
> >>>>>> +        if (ret) {
> >>>>>> +            slog("guest-shutdown failed: %s", strerror(errno));
> >>>>>> +        }
> >>>>>> +        exit(!!ret);
> >>>>>> +    } else if (ret<    0) {
> >>>>>> +        error_set(err, QERR_UNDEFINED_ERROR);
> >>>>>> +    }
> >>>>>> +}
> >>>>>> +
> >>>>>> +typedef struct GuestFileHandle {
> >>>>>> +    uint64_t id;
> >>>>>> +    FILE *fh;
> >>>>>> +    QTAILQ_ENTRY(GuestFileHandle) next;
> >>>>>> +} GuestFileHandle;
> >>>>>> +
> >>>>>> +static struct {
> >>>>>> +    QTAILQ_HEAD(, GuestFileHandle) filehandles;
> >>>>>> +} guest_file_state;
> >>>>>> +
> >>>>>> +static void guest_file_handle_add(FILE *fh)
> >>>>>> +{
> >>>>>> +    GuestFileHandle *gfh;
> >>>>>> +
> >>>>>> +    gfh = qemu_mallocz(sizeof(GuestFileHandle));
> >>>>>> +    gfh->id = fileno(fh);
> >>>>>> +    gfh->fh = fh;
> >>>>>> +    QTAILQ_INSERT_TAIL(&guest_file_state.filehandles, gfh, next);
> >>>>>> +}
> >>>>>> +
> >>>>>> +static GuestFileHandle *guest_file_handle_find(int64_t id)
> >>>>>> +{
> >>>>>> +    GuestFileHandle *gfh;
> >>>>>> +
> >>>>>> +    QTAILQ_FOREACH(gfh,&guest_file_state.filehandles, next)
> >>>>>> +    {
> >>>>>> +        if (gfh->id == id) {
> >>>>>> +            return gfh;
> >>>>>> +        }
> >>>>>> +    }
> >>>>>> +
> >>>>>> +    return NULL;
> >>>>>> +}
> >>>>>> +
> >>>>>> +int64_t qmp_guest_file_open(const char *filepath, bool has_mode, const char *mode, Error **err)
> >>>>>> +{
> >>>>>> +    FILE *fh;
> >>>>>> +    int fd;
> >>>>>> +    int64_t ret = -1;
> >>>>>> +
> >>>>>> +    if (!has_mode) {
> >>>>>> +        mode = "r";
> >>>>>> +    }
> >>>>>> +    slog("guest-file-open called, filepath: %s, mode: %s", filepath, mode);
> >>>>>> +    fh = fopen(filepath, mode);
> >>>>>> +    if (!fh) {
> >>>>>> +        error_set(err, QERR_OPEN_FILE_FAILED, filepath);
> >>>>>> +        return -1;
> >>>>>> +    }
> >>>>>> +
> >>>>>> +    /* set fd non-blocking to avoid common use cases (like reading from a
> >>>>>> +     * named pipe) from hanging the agent
> >>>>>> +     */
> >>>>>> +    fd = fileno(fh);
> >>>>>> +    ret = fcntl(fd, F_GETFL);
> >>>>>> +    ret = fcntl(fd, F_SETFL, ret | O_NONBLOCK);
> >>>>>> +    if (ret == -1) {
> >>>>>> +        error_set(err, QERR_OPEN_FILE_FAILED, filepath);
> >>>>>> +        fclose(fh);
> >>>>>> +        return -1;
> >>>>>> +    }
> >>>>>> +
> >>>>>> +    guest_file_handle_add(fh);
> >>>>>> +    slog("guest-file-open, filehandle: %d", fd);
> >>>>>> +    return fd;
> >>>>>> +}
> >>>>>> +
> >>>>>> +struct GuestFileRead *qmp_guest_file_read(int64_t filehandle, int64_t count,
> >>>>>> +                                          Error **err)
> >>>>>> +{
> >>>>>> +    GuestFileHandle *gfh = guest_file_handle_find(filehandle);
> >>>>>> +    GuestFileRead *read_data;
> >>>>>> +    guchar *buf;
> >>>>>> +    FILE *fh;
> >>>>>> +    size_t read_count;
> >>>>>> +
> >>>>>> +    if (!gfh) {
> >>>>>> +        error_set(err, QERR_FD_NOT_FOUND, "filehandle");
> >>>>>> +        return NULL;
> >>>>>> +    }
> >>>>>> +
> >>>>>> +    if (count<    0 || count>    QGA_READ_LIMIT) {
> >>>>>> +        error_set(err, QERR_INVALID_PARAMETER, "count");
> >>>>>> +        return NULL;
> >>>>>> +    }
> >>>>>
> >>>>> Are we imposing that limit because of the malloc() call below? If that's
> >>>>> the case I think it's wrong, because we don't know the VM (neither the guest)
> >>>>> better than the client.
> >>>>>
> >>>>> The best thing we can do here is to limit it to the file size. Additionally
> >>>>> to this we could have a command-line option to allow the sysadmin set his/her
> >>>>> own limit.
> >>>>>
> >>>>
> >>>> That's technically the better approach, but we're also bound by the
> >>>> maximum token size limit in the JSON parser, which is 64MB. Best we can
> >>>> do is set QGA_READ_LIMIT accordingly.
> >>>
> >>> Good point, but I think it's ok to let the parser do this check itself, as
> >>> control won't get here anyway. Maybe we should only document that the server
> >>> imposes a limit on the token size.
> >>>
> >>
> >>    From a usability perspective I think the QERR_INVALID_PARAMETER, or
> >> perhaps something more descriptive, is a little nicer. Plus, they won't
> >> have to wait for 64MB to get streamed back before they get the error :)
> >
> > That's why I suggested documenting it. Are we going to add this check
> > to all commands that make it more likely to reach the parser's limit?
> > (also note that theoretically all commands can do it).
> >
> 
> Good point, and I agree that it needs to be documented either way. Might 
> not hurt to further clarify the limitations this imposes on a command 
> like guest-file-read in the schema documentation where appropriate though.
> 
> I'm not sure about the alternative suggestion of bounding this by 
> filesize though, since they may also be writing to the same fd via 
> guest-file-write, and the current file size wouldn't be meaningful 
> except after an fflush() or fclose().

This is just like the OS, the application has to what's doing.

> So we'd be forced to fflush() in 
> guest-file-write to implement this somewhat reliably, but I think we 
> agree that a separate guest-file-flush would be better.

Yes.

> So let's just let the client blow up their guest if they're so inclined.

Exactly :) I mean, if something like this turns out to be a problem, we'd
have to fix it differently IMO.

> 
> >>
> >>>>
> >>>>>> +
> >>>>>> +    fh = gfh->fh;
> >>>>>> +    read_data = qemu_mallocz(sizeof(GuestFileRead) + 1);
> >>>>>> +    buf = qemu_mallocz(count+1);
> >>>>>> +    if (!buf) {
> >>>>>> +        error_set(err, QERR_UNDEFINED_ERROR);
> >>>>>> +        return NULL;
> >>>>>> +    }
> >>>>>
> >>>>> qemu_malloc() functions never fail...
> >>>>>
> >>>>>> +
> >>>>>> +    read_count = fread(buf, 1, count, fh);
> >>>>>
> >>>>> Isn't 'nmemb' and 'size' swapped?
> >>>>>
> >>>>
> >>>> I'm not sure...
> >>>>
> >>>> size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
> >>>>
> >>>> I wrote it as $count number of 1-bytes elements. This seems logical to
> >>>> me, since it's a non-blocking FD which way result in a partial of some
> >>>> lesser number of bytes than count.
> >>>
> >>> Ok. I think either way will work.
> >>>
> >>>>
> >>>>>> +    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);
> >>>>>> +    }
> >>>>>> +    qemu_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;
> >>>>>> +    GuestFileHandle *gfh = guest_file_handle_find(filehandle);
> >>>>>> +    FILE *fh;
> >>>>>> +
> >>>>>> +    if (!gfh) {
> >>>>>> +        error_set(err, QERR_FD_NOT_FOUND, "filehandle");
> >>>>>> +        return NULL;
> >>>>>> +    }
> >>>>>> +
> >>>>>> +    fh = gfh->fh;
> >>>>>> +    data = g_base64_decode(data_b64,&data_len);
> >>>>>> +    if (count>    data_len) {
> >>>>>> +        qemu_free(data);
> >>>>>> +        error_set(err, QERR_INVALID_PARAMETER, "count");
> >>>>>> +        return NULL;
> >>>>>> +    }
> >>>>>> +    write_data = qemu_mallocz(sizeof(GuestFileWrite));
> >>>>>> +    write_count = fwrite(data, 1, count, fh);
> >>>>>> +    write_data->count = write_count;
> >>>>>> +    write_data->eof = feof(fh);
> >>>>>> +    qemu_free(data);
> >>>>>> +    clearerr(fh);
> >>>>>
> >>>>> Shouldn't we check for errors instead of doing this?
> >>>>>
> >>>>
> >>>> Yah...unfortunately we only get a boolean flag with ferror() so it's not
> >>>> all that useful, but I can add an error flag to the calls to capture it
> >>>> at least. clearerr() is only being used here to clear the eof flag.
> >>>
> >>> I meant to check fwrite()'s return.
> >>>
> >>
> >> Ahh, right. Definitely.
> >>
> >>>>
> >>>>> Btw, I think it's a good idea to offer guest-file-flush() too (or do a flush()
> >>>>> here, but that's probably bad).
> >>>>>
> >>>>
> >>>> Yah, I'd been planning on adding a guest-file-flush() for a while now.
> >>>> I'll add that for the respin.
> >>>>
> >>>>>> +
> >>>>>> +    return write_data;
> >>>>>> +}
> >>>>>> +
> >>>>>> +struct GuestFileSeek *qmp_guest_file_seek(int64_t filehandle, int64_t offset,
> >>>>>> +                                          int64_t whence, Error **err)
> >>>>>> +{
> >>>>>> +    GuestFileSeek *seek_data;
> >>>>>> +    GuestFileHandle *gfh = guest_file_handle_find(filehandle);
> >>>>>> +    FILE *fh;
> >>>>>> +    int ret;
> >>>>>> +
> >>>>>> +    if (!gfh) {
> >>>>>> +        error_set(err, QERR_FD_NOT_FOUND, "filehandle");
> >>>>>> +        return NULL;
> >>>>>> +    }
> >>>>>> +
> >>>>>> +    fh = gfh->fh;
> >>>>>> +    seek_data = qemu_mallocz(sizeof(GuestFileRead));
> >>>>>> +    ret = fseek(fh, offset, whence);
> >>>>>> +    if (ret == -1) {
> >>>>>> +        error_set(err, QERR_UNDEFINED_ERROR);
> >>>>>> +        qemu_free(seek_data);
> >>>>>> +        return NULL;
> >>>>>> +    }
> >>>>>> +    seek_data->position = ftell(fh);
> >>>>>> +    seek_data->eof = feof(fh);
> >>>>>> +    clearerr(fh);
> >>>>>
> >>>>> Again, I don't see why we should do this.
> >>>
> >>> This is probably ok, as we're checking fseek() above.
> >>>
> >>>>>
> >>>>>> +
> >>>>>> +    return seek_data;
> >>>>>> +}
> >>>>>> +
> >>>>>> +void qmp_guest_file_close(int64_t filehandle, Error **err)
> >>>>>> +{
> >>>>>> +    GuestFileHandle *gfh = guest_file_handle_find(filehandle);
> >>>>>> +
> >>>>>> +    slog("guest-file-close called, filehandle: %ld", filehandle);
> >>>>>> +    if (!gfh) {
> >>>>>> +        error_set(err, QERR_FD_NOT_FOUND, "filehandle");
> >>>>>> +        return;
> >>>>>> +    }
> >>>>>> +
> >>>>>> +    fclose(gfh->fh);
> >>>>>> +    QTAILQ_REMOVE(&guest_file_state.filehandles, gfh, next);
> >>>>>> +    qemu_free(gfh);
> >>>>>> +}
> >>>>>> +
> >>>>>> +static void guest_file_init(void)
> >>>>>> +{
> >>>>>> +    QTAILQ_INIT(&guest_file_state.filehandles);
> >>>>>> +}
> >>>>>> +
> >>>>>> +typedef struct GuestFsfreezeMount {
> >>>>>> +    char *dirname;
> >>>>>> +    char *devtype;
> >>>>>> +    QTAILQ_ENTRY(GuestFsfreezeMount) next;
> >>>>>> +} GuestFsfreezeMount;
> >>>>>> +
> >>>>>> +struct {
> >>>>>> +    GuestFsfreezeStatus status;
> >>>>>> +    QTAILQ_HEAD(, GuestFsfreezeMount) mount_list;
> >>>>>> +} guest_fsfreeze_state;
> >>>>>> +
> >>>>>> +/*
> >>>>>> + * Walk the mount table and build a list of local file systems
> >>>>>> + */
> >>>>>> +static int guest_fsfreeze_build_mount_list(void)
> >>>>>> +{
> >>>>>> +    struct mntent *ment;
> >>>>>> +    GuestFsfreezeMount *mount, *temp;
> >>>>>> +    char const *mtab = MOUNTED;
> >>>>>> +    FILE *fp;
> >>>>>> +
> >>>>>> +    fp = setmntent(mtab, "r");
> >>>>>> +    if (!fp) {
> >>>>>> +        g_warning("fsfreeze: unable to read mtab");
> >>>>>> +        goto fail;
> >>>>>> +    }
> >>>>>> +
> >>>>>> +    while ((ment = getmntent(fp))) {
> >>>>>> +        /*
> >>>>>> +         * An entry which device name doesn't start with a '/' is
> >>>>>> +         * either a dummy file system or a network file system.
> >>>>>> +         * Add special handling for smbfs and cifs as is done by
> >>>>>> +         * coreutils as well.
> >>>>>> +         */
> >>>>>> +        if ((ment->mnt_fsname[0] != '/') ||
> >>>>>> +            (strcmp(ment->mnt_type, "smbfs") == 0) ||
> >>>>>> +            (strcmp(ment->mnt_type, "cifs") == 0)) {
> >>>>>> +            continue;
> >>>>>> +        }
> >>>>>> +
> >>>>>> +        mount = qemu_mallocz(sizeof(GuestFsfreezeMount));
> >>>>>> +        mount->dirname = qemu_strdup(ment->mnt_dir);
> >>>>>> +        mount->devtype = qemu_strdup(ment->mnt_type);
> >>>>>> +
> >>>>>> +        QTAILQ_INSERT_TAIL(&guest_fsfreeze_state.mount_list, mount, next);
> >>>>>> +    }
> >>>>>> +
> >>>>>> +    endmntent(fp);
> >>>>>> +
> >>>>>> +    return 0;
> >>>>>> +
> >>>>>> +fail:
> >>>>>> +    QTAILQ_FOREACH_SAFE(mount,&guest_fsfreeze_state.mount_list, next, temp) {
> >>>>>> +        QTAILQ_REMOVE(&guest_fsfreeze_state.mount_list, mount, next);
> >>>>>> +        qemu_free(mount->dirname);
> >>>>>> +        qemu_free(mount->devtype);
> >>>>>> +        qemu_free(mount);
> >>>>>> +    }
> >>>>>
> >>>>> This doesn't seem to be used.
> >>>>>
> >>>>
> >>>> It can get used even a 2nd invocation of this function gets called that
> >>>> results in a goto fail. But looking again this should be done
> >>>> unconditionally at the start of the function, since the mount list is
> >>>> part of global state now.
> >>>>
> >>>>>> +
> >>>>>> +    return -1;
> >>>>>> +}
> >>>>>> +
> >>>>>> +/*
> >>>>>> + * Return status of freeze/thaw
> >>>>>> + */
> >>>>>> +GuestFsfreezeStatus qmp_guest_fsfreeze_status(Error **err)
> >>>>>> +{
> >>>>>> +    return guest_fsfreeze_state.status;
> >>>>>> +}
> >>>>>> +
> >>>>>> +/*
> >>>>>> + * Walk list of mounted file systems in the guest, and freeze the ones which
> >>>>>> + * are real local file systems.
> >>>>>> + */
> >>>>>> +int64_t qmp_guest_fsfreeze_freeze(Error **err)
> >>>>>> +{
> >>>>>> +    int ret = 0, i = 0;
> >>>>>> +    struct GuestFsfreezeMount *mount, *temp;
> >>>>>> +    int fd;
> >>>>>> +
> >>>>>> +    slog("guest-fsfreeze called");
> >>>>>> +
> >>>>>> +    if (guest_fsfreeze_state.status != GUEST_FSFREEZE_STATUS_THAWED) {
> >>>>>
> >>>>> return 0;
> >>>>>
> >>>>>> +        ret = 0;
> >>>>>> +        goto out;
> >>>>>> +    }
> >>>>>> +
> >>>>>> +    ret = guest_fsfreeze_build_mount_list();
> >>>>>> +    if (ret<    0) {
> >>>>>
> >>>>> return ret;
> >>>>>
> >>>>>> +        goto out;
> >>>>>> +    }
> >>>>>> +
> >>>>>> +    guest_fsfreeze_state.status = GUEST_FSFREEZE_STATUS_INPROGRESS;
> >>>>>> +
> >>>>>> +    /* cannot risk guest agent blocking itself on a write in this state */
> >>>>>> +    disable_logging();
> >>>>>> +
> >>>>>> +    QTAILQ_FOREACH_SAFE(mount,&guest_fsfreeze_state.mount_list, next, temp) {
> >>>>>> +        fd = qemu_open(mount->dirname, O_RDONLY);
> >>>>>> +        if (fd == -1) {
> >>>>>> +            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);
> >>>>>> +
> >>>>>> +        i++;
> >>>>>> +    }
> >>>>>> +
> >>>>>> +    guest_fsfreeze_state.status = GUEST_FSFREEZE_STATUS_FROZEN;
> >>>>>> +    ret = i;
> >>>>>> +out:
> >>>>>> +    return ret;
> >>>>>> +error:
> >>>>>> +    if (i>    0) {
> >>>>>> +        guest_fsfreeze_state.status = GUEST_FSFREEZE_STATUS_ERROR;
> >>>>>> +    }
> >>>>>
> >>>>> Shouldn't you undo everything that has been done so far? Which is
> >>>>> freeing the build list and thawing the file-systems that were frozen
> >>>>> before the error?
> >>>>>
> >>>>
> >>>> We can...the way it's done right now is you get a count of how many
> >>>> filesystems were frozen, along an error status. Depending on the
> >>>> error/count the user can either ignore the error, do what it is they
> >>>> want to do, then call thaw(), or just immediately call thaw().
> >>>
> >>> But you don't get the count on error, so it's difficult (if possible) to
> >>> learn how many file-systems were frozen.
> >>>
> >>
> >> Yah, my mistake. I think the out: label was one line higher, but if we
> >> did that we lose error information. Automatically unfreezing should be okay.
> >>
> >>>>
> >>>> So we can do an automatic thaw() on their behalf, but i figured the
> >>>> status was good enough.
> >>>>
> >>>>>> +    goto out;
> >>>>>> +}
> >>>>>> +
> >>>>>> +/*
> >>>>>> + * Walk list of frozen file systems in the guest, and thaw them.
> >>>>>> + */
> >>>>>> +int64_t qmp_guest_fsfreeze_thaw(Error **err)
> >>>>>> +{
> >>>>>> +    int ret;
> >>>>>> +    GuestFsfreezeMount *mount, *temp;
> >>>>>> +    int fd, i = 0;
> >>>>>> +
> >>>>>> +    if (guest_fsfreeze_state.status != GUEST_FSFREEZE_STATUS_FROZEN&&
> >>>>>> +        guest_fsfreeze_state.status != GUEST_FSFREEZE_STATUS_INPROGRESS) {
> >>>>>
> >>>>> I don't follow why we're checking against INPROGRESS here.
> >>>>>
> >>>>
> >>>> To prevent a race I believe...but we're synchronous now so that's
> >>>> probably no longer needed. I'll look it over and remove it if that's the
> >>>> case.
> >>>>
> >>>>>> +        ret = 0;
> >>>>>> +        goto out_enable_logging;
> >>>>>> +    }
> >>>>>> +
> >>>>>> +    QTAILQ_FOREACH_SAFE(mount,&guest_fsfreeze_state.mount_list, next, temp) {
> >>>>>> +        fd = qemu_open(mount->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;
> >>>>>
> >>>>> Shouldn't you continue and try to thaw the other file-systems in the list?
> >>>>>
> >>>>
> >>>> That's probably better
> >>>>
> >>>>>> +        }
> >>>>>> +        close(fd);
> >>>>>> +
> >>>>>> +        QTAILQ_REMOVE(&guest_fsfreeze_state.mount_list, mount, next);
> >>>>>> +        qemu_free(mount->dirname);
> >>>>>> +        qemu_free(mount->devtype);
> >>>>>> +        qemu_free(mount);
> >>>>>> +        i++;
> >>>>>> +    }
> >>>>>> +
> >>>>>> +    guest_fsfreeze_state.status = GUEST_FSFREEZE_STATUS_THAWED;
> >>>>>> +    ret = i;
> >>>>>> +out_enable_logging:
> >>>>>> +    enable_logging();
> >>>>>> +out:
> >>>>>> +    return ret;
> >>>>>> +}
> >>>>>> +
> >>>>>> +static void guest_fsfreeze_init(void)
> >>>>>> +{
> >>>>>> +    guest_fsfreeze_state.status = GUEST_FSFREEZE_STATUS_THAWED;
> >>>>>> +    QTAILQ_INIT(&guest_fsfreeze_state.mount_list);
> >>>>>> +}
> >>>>>> +
> >>>>>> +static void guest_fsfreeze_cleanup(void)
> >>>>>> +{
> >>>>>> +    int64_t ret;
> >>>>>> +    Error *err = NULL;
> >>>>>> +
> >>>>>> +    if (guest_fsfreeze_state.status == GUEST_FSFREEZE_STATUS_FROZEN) {
> >>>>>> +        ret = qmp_guest_fsfreeze_thaw(&err);
> >>>>>> +        if (ret<    0 || err) {
> >>>>>> +            slog("failed to clean up frozen filesystems");
> >>>>>> +        }
> >>>>>> +    }
> >>>>>> +}
> >>>>>> +
> >>>>>> +/* 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, guest_fsfreeze_init, guest_fsfreeze_cleanup);
> >>>>>> +    ga_command_state_add(cs, guest_file_init, NULL);
> >>>>>> +}
> >>>>>> diff --git a/qga/guest-agent-core.h b/qga/guest-agent-core.h
> >>>>>> index 66d1729..3501ff4 100644
> >>>>>> --- a/qga/guest-agent-core.h
> >>>>>> +++ b/qga/guest-agent-core.h
> >>>>>> @@ -14,10 +14,12 @@
> >>>>>>     #include "qemu-common.h"
> >>>>>>
> >>>>>>     #define QGA_VERSION "1.0"
> >>>>>> +#define QGA_READ_LIMIT 4<<    20 /* 4MB block size max for chunked reads */
> >>>>>>
> >>>>>>     typedef struct GAState GAState;
> >>>>>>     typedef struct GACommandState GACommandState;
> >>>>>>
> >>>>>> +void ga_command_state_init(GAState *s, GACommandState *cs);
> >>>>>>     void ga_command_state_add(GACommandState *cs,
> >>>>>>                               void (*init)(void),
> >>>>>>                               void (*cleanup)(void));
> >>>>>
> >>>>
> >>>
> >>
> >
> 

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

* Re: [Qemu-devel] [QAPI+QGA 3/3] QEMU Guest Agent (virtagent) v6
  2011-07-05 13:21 [Qemu-devel] [QAPI+QGA 3/3] QEMU Guest Agent (virtagent) v6 Michael Roth
                   ` (3 preceding siblings ...)
  2011-07-05 13:21 ` [Qemu-devel] [PATCH v6 4/4] guest agent: add guest agent RPCs/commands Michael Roth
@ 2011-07-13 13:14 ` Daniel P. Berrange
  2011-07-13 17:51   ` Michael Roth
  2011-07-14  2:53 ` Zhi Yong Wu
  5 siblings, 1 reply; 25+ messages in thread
From: Daniel P. Berrange @ 2011-07-13 13:14 UTC (permalink / raw)
  To: Michael Roth; +Cc: aliguori, lcapitulino, agl, qemu-devel, Jes.Sorensen

On Tue, Jul 05, 2011 at 08:21:36AM -0500, Michael Roth wrote:
> 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

Have we documented any naming convention for virtio serial ports yet ?
For both Matahari, and libguestfs we've followed the reverse domain
name style naming adopted by things like DBus and AMQP, or language
package namespaces. eg

  org.libguestfs.channel.0
  org.apache.qpid.matahari.0

The '.0' is just in case we find we need to add further channels
for each agent later with different usage.

I think this would be a good general naming convention to recommend
to app developers in order to avoid naming clashes, and thus think
that the QEMU guest agent should use a channel name prefixed with
'org.qemu.' eg perhaps

   ...
   -device virtserialport,chardev=qga,name=org.qemu.guestagent.0"
   ...

   ./qemu-ga -c virtio-serial -p /dev/virtio-ports/org.qemu.guestagent.0

Regards,
Daniel
-- 
|: http://berrange.com      -o-    http://www.flickr.com/photos/dberrange/ :|
|: http://libvirt.org              -o-             http://virt-manager.org :|
|: http://autobuild.org       -o-         http://search.cpan.org/~danberr/ :|
|: http://entangle-photo.org       -o-       http://live.gnome.org/gtk-vnc :|

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

* Re: [Qemu-devel] [QAPI+QGA 3/3] QEMU Guest Agent (virtagent) v6
  2011-07-13 13:14 ` [Qemu-devel] [QAPI+QGA 3/3] QEMU Guest Agent (virtagent) v6 Daniel P. Berrange
@ 2011-07-13 17:51   ` Michael Roth
  0 siblings, 0 replies; 25+ messages in thread
From: Michael Roth @ 2011-07-13 17:51 UTC (permalink / raw)
  To: Daniel P. Berrange
  Cc: agl, Jes.Sorensen, qemu-devel, lcapitulino, aliguori, pmyers

On 07/13/2011 08:14 AM, Daniel P. Berrange wrote:
> On Tue, Jul 05, 2011 at 08:21:36AM -0500, Michael Roth wrote:
>> 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
>
> Have we documented any naming convention for virtio serial ports yet ?
> For both Matahari, and libguestfs we've followed the reverse domain
> name style naming adopted by things like DBus and AMQP, or language
> package namespaces. eg
>
>    org.libguestfs.channel.0
>    org.apache.qpid.matahari.0
>
> The '.0' is just in case we find we need to add further channels
> for each agent later with different usage.
>
> I think this would be a good general naming convention to recommend
> to app developers in order to avoid naming clashes, and thus think
> that the QEMU guest agent should use a channel name prefixed with
> 'org.qemu.' eg perhaps
>
>     ...
>     -device virtserialport,chardev=qga,name=org.qemu.guestagent.0"
>     ...
>
>     ./qemu-ga -c virtio-serial -p /dev/virtio-ports/org.qemu.guestagent.0
>
> Regards,
> Daniel

Hi Daniel,

If you don't specify the -p option explicitly, qemu-ga will look for 
"/dev/virtio-ports/org.qemu.guest_agent"

Adding ".0" would be useful though, so I'll make 
"/dev/virtio-ports/org.qemu.guest_agent.0" the default, and update any 
surrounding documentation to encourage that convention.

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

* Re: [Qemu-devel] [QAPI+QGA 3/3] QEMU Guest Agent (virtagent) v6
  2011-07-05 13:21 [Qemu-devel] [QAPI+QGA 3/3] QEMU Guest Agent (virtagent) v6 Michael Roth
                   ` (4 preceding siblings ...)
  2011-07-13 13:14 ` [Qemu-devel] [QAPI+QGA 3/3] QEMU Guest Agent (virtagent) v6 Daniel P. Berrange
@ 2011-07-14  2:53 ` Zhi Yong Wu
  2011-07-14 12:55   ` Luiz Capitulino
  5 siblings, 1 reply; 25+ messages in thread
From: Zhi Yong Wu @ 2011-07-14  2:53 UTC (permalink / raw)
  To: Michael Roth; +Cc: aliguori, lcapitulino, agl, qemu-devel, Jes.Sorensen

HI, Michael,

On Tue, Jul 5, 2011 at 9:21 PM, Michael Roth <mdroth@linux.vnet.ibm.com> wrote:
> This is Set 3/3 of the QAPI+QGA patchsets.
>
> These patches apply on top of qapi-backport-set2-v5, and can also be obtained from:
> git://repo.or.cz/qemu/mdroth.git qapi-backport-set3-v6
>
> (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 V5:
>  - switched to using qemu malloc/list functions where possible
>  - removed unused proxy_path field in struct GAState
>  - pid file now opened write-only, removed lockf() in favor of O_EXCL, added SIGINT/SIGTERM signal handlers to handle cleanup
>  - cleaned up error-handling, switched to asserts where appropriate, removed unecessary gotos and NULL checks for qemu_free()/qobject_decref()
>  - refactored send_payload() using helper functions
>  - fixed improper handling of pidfile fd==0
>  - changed guest-shutdown's "shutdown_mode" param to "mode"
>  - switched to using kernel-generated FDs for guest-file-open rather than an autoincrement value
>  - add maximum chunk size of guest-file-read/guest-file-write
>  - added checks to avoid guest-file-write from writing data beyond the provided data buffer
>  - made logging best-effort, removed handling of failures to log as errors
>  - guest-shutdown exec errors now logged to guest syslog, clarified shutdown's asynchronous, no gauruntee nature in schema.
>
> CHANGES SINCE V4:
>  - Removed timeout mechanism via worker thread/pthread_cancel due to potential memory leak. Will re-introduce guest-side timeout support in future version.
>  - Fixed up fsfreeze code to use enums specified within the guest agent's qapi schema.
>  - Fixed memory leak due to a log statement, and added missing cleanup functions for heap-allocated g_error objects.
>  - Made "mode" param to guest-file-open optional, defaults to "r" (read-only)
>
> 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
Is the above command "./qemu-ga" issued on guest teminal or hypervisor teminal?

>
> 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"}}
>
>  {"execute":"guest-file-open", "arguments":{"filepath":"/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":{"filepath":"/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 +-
>  configure                       |    1 +
>  qapi-schema-guest.json          |  202 +++++++++++++
>  qemu-ga.c                       |  631 +++++++++++++++++++++++++++++++++++++++
>  qerror.c                        |    4 +
>  qerror.h                        |    3 +
>  qga/guest-agent-command-state.c |   73 +++++
>  qga/guest-agent-commands.c      |  522 ++++++++++++++++++++++++++++++++
>  qga/guest-agent-core.h          |   30 ++
>  9 files changed, 1483 insertions(+), 5 deletions(-)
>
>
>
-- 
Regards,

Zhi Yong Wu

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

* Re: [Qemu-devel] [QAPI+QGA 3/3] QEMU Guest Agent (virtagent) v6
  2011-07-14  2:53 ` Zhi Yong Wu
@ 2011-07-14 12:55   ` Luiz Capitulino
  2011-07-14 13:53     ` Zhi Yong Wu
  0 siblings, 1 reply; 25+ messages in thread
From: Luiz Capitulino @ 2011-07-14 12:55 UTC (permalink / raw)
  To: Zhi Yong Wu; +Cc: aliguori, Jes.Sorensen, agl, Michael Roth, qemu-devel

On Thu, 14 Jul 2011 10:53:51 +0800
Zhi Yong Wu <zwu.kernel@gmail.com> wrote:

> HI, Michael,
> 
> On Tue, Jul 5, 2011 at 9:21 PM, Michael Roth <mdroth@linux.vnet.ibm.com> wrote:
> > This is Set 3/3 of the QAPI+QGA patchsets.
> >
> > These patches apply on top of qapi-backport-set2-v5, and can also be obtained from:
> > git://repo.or.cz/qemu/mdroth.git qapi-backport-set3-v6
> >
> > (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 V5:
> >  - switched to using qemu malloc/list functions where possible
> >  - removed unused proxy_path field in struct GAState
> >  - pid file now opened write-only, removed lockf() in favor of O_EXCL, added SIGINT/SIGTERM signal handlers to handle cleanup
> >  - cleaned up error-handling, switched to asserts where appropriate, removed unecessary gotos and NULL checks for qemu_free()/qobject_decref()
> >  - refactored send_payload() using helper functions
> >  - fixed improper handling of pidfile fd==0
> >  - changed guest-shutdown's "shutdown_mode" param to "mode"
> >  - switched to using kernel-generated FDs for guest-file-open rather than an autoincrement value
> >  - add maximum chunk size of guest-file-read/guest-file-write
> >  - added checks to avoid guest-file-write from writing data beyond the provided data buffer
> >  - made logging best-effort, removed handling of failures to log as errors
> >  - guest-shutdown exec errors now logged to guest syslog, clarified shutdown's asynchronous, no gauruntee nature in schema.
> >
> > CHANGES SINCE V4:
> >  - Removed timeout mechanism via worker thread/pthread_cancel due to potential memory leak. Will re-introduce guest-side timeout support in future version.
> >  - Fixed up fsfreeze code to use enums specified within the guest agent's qapi schema.
> >  - Fixed memory leak due to a log statement, and added missing cleanup functions for heap-allocated g_error objects.
> >  - Made "mode" param to guest-file-open optional, defaults to "r" (read-only)
> >
> > 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
> Is the above command "./qemu-ga" issued on guest teminal or hypervisor teminal?

In the guest.

> 
> >
> > 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"}}
> >
> >  {"execute":"guest-file-open", "arguments":{"filepath":"/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":{"filepath":"/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 +-
> >  configure                       |    1 +
> >  qapi-schema-guest.json          |  202 +++++++++++++
> >  qemu-ga.c                       |  631 +++++++++++++++++++++++++++++++++++++++
> >  qerror.c                        |    4 +
> >  qerror.h                        |    3 +
> >  qga/guest-agent-command-state.c |   73 +++++
> >  qga/guest-agent-commands.c      |  522 ++++++++++++++++++++++++++++++++
> >  qga/guest-agent-core.h          |   30 ++
> >  9 files changed, 1483 insertions(+), 5 deletions(-)
> >
> >
> >

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

* Re: [Qemu-devel] [QAPI+QGA 3/3] QEMU Guest Agent (virtagent) v6
  2011-07-14 12:55   ` Luiz Capitulino
@ 2011-07-14 13:53     ` Zhi Yong Wu
  2011-07-14 14:04       ` Michael Roth
  0 siblings, 1 reply; 25+ messages in thread
From: Zhi Yong Wu @ 2011-07-14 13:53 UTC (permalink / raw)
  To: Luiz Capitulino; +Cc: aliguori, Jes.Sorensen, agl, Michael Roth, qemu-devel

On Thu, Jul 14, 2011 at 8:55 PM, Luiz Capitulino <lcapitulino@redhat.com> wrote:
> On Thu, 14 Jul 2011 10:53:51 +0800
> Zhi Yong Wu <zwu.kernel@gmail.com> wrote:
>
>> HI, Michael,
>>
>> On Tue, Jul 5, 2011 at 9:21 PM, Michael Roth <mdroth@linux.vnet.ibm.com> wrote:
>> > This is Set 3/3 of the QAPI+QGA patchsets.
>> >
>> > These patches apply on top of qapi-backport-set2-v5, and can also be obtained from:
>> > git://repo.or.cz/qemu/mdroth.git qapi-backport-set3-v6
>> >
>> > (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 V5:
>> >  - switched to using qemu malloc/list functions where possible
>> >  - removed unused proxy_path field in struct GAState
>> >  - pid file now opened write-only, removed lockf() in favor of O_EXCL, added SIGINT/SIGTERM signal handlers to handle cleanup
>> >  - cleaned up error-handling, switched to asserts where appropriate, removed unecessary gotos and NULL checks for qemu_free()/qobject_decref()
>> >  - refactored send_payload() using helper functions
>> >  - fixed improper handling of pidfile fd==0
>> >  - changed guest-shutdown's "shutdown_mode" param to "mode"
>> >  - switched to using kernel-generated FDs for guest-file-open rather than an autoincrement value
>> >  - add maximum chunk size of guest-file-read/guest-file-write
>> >  - added checks to avoid guest-file-write from writing data beyond the provided data buffer
>> >  - made logging best-effort, removed handling of failures to log as errors
>> >  - guest-shutdown exec errors now logged to guest syslog, clarified shutdown's asynchronous, no gauruntee nature in schema.
>> >
>> > CHANGES SINCE V4:
>> >  - Removed timeout mechanism via worker thread/pthread_cancel due to potential memory leak. Will re-introduce guest-side timeout support in future version.
>> >  - Fixed up fsfreeze code to use enums specified within the guest agent's qapi schema.
>> >  - Fixed memory leak due to a log statement, and added missing cleanup functions for heap-allocated g_error objects.
>> >  - Made "mode" param to guest-file-open optional, defaults to "r" (read-only)
>> >
>> > 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
>> Is the above command "./qemu-ga" issued on guest teminal or hypervisor teminal?
>
> In the guest.

Can we make it work similiar as a linux service? It may is more
convenient to use.

[root@f12 ~]# service qemu-ga start/stop/status/restart

[root@f12 ~]# chkconfig qemu-ga on/off

>
>>
>> >
>> > 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"}}
>> >
>> >  {"execute":"guest-file-open", "arguments":{"filepath":"/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":{"filepath":"/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 +-
>> >  configure                       |    1 +
>> >  qapi-schema-guest.json          |  202 +++++++++++++
>> >  qemu-ga.c                       |  631 +++++++++++++++++++++++++++++++++++++++
>> >  qerror.c                        |    4 +
>> >  qerror.h                        |    3 +
>> >  qga/guest-agent-command-state.c |   73 +++++
>> >  qga/guest-agent-commands.c      |  522 ++++++++++++++++++++++++++++++++
>> >  qga/guest-agent-core.h          |   30 ++
>> >  9 files changed, 1483 insertions(+), 5 deletions(-)
>> >
>> >
>> >
>
>



-- 
Regards,

Zhi Yong Wu

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

* Re: [Qemu-devel] [QAPI+QGA 3/3] QEMU Guest Agent (virtagent) v6
  2011-07-14 13:53     ` Zhi Yong Wu
@ 2011-07-14 14:04       ` Michael Roth
  0 siblings, 0 replies; 25+ messages in thread
From: Michael Roth @ 2011-07-14 14:04 UTC (permalink / raw)
  To: Zhi Yong Wu; +Cc: aliguori, Jes.Sorensen, agl, qemu-devel, Luiz Capitulino

On 07/14/2011 08:53 AM, Zhi Yong Wu wrote:
> On Thu, Jul 14, 2011 at 8:55 PM, Luiz Capitulino<lcapitulino@redhat.com>  wrote:
>> On Thu, 14 Jul 2011 10:53:51 +0800
>> Zhi Yong Wu<zwu.kernel@gmail.com>  wrote:
>>
>>> HI, Michael,
>>>
>>> On Tue, Jul 5, 2011 at 9:21 PM, Michael Roth<mdroth@linux.vnet.ibm.com>  wrote:
>>>> This is Set 3/3 of the QAPI+QGA patchsets.
>>>>
>>>> These patches apply on top of qapi-backport-set2-v5, and can also be obtained from:
>>>> git://repo.or.cz/qemu/mdroth.git qapi-backport-set3-v6
>>>>
>>>> (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 V5:
>>>>   - switched to using qemu malloc/list functions where possible
>>>>   - removed unused proxy_path field in struct GAState
>>>>   - pid file now opened write-only, removed lockf() in favor of O_EXCL, added SIGINT/SIGTERM signal handlers to handle cleanup
>>>>   - cleaned up error-handling, switched to asserts where appropriate, removed unecessary gotos and NULL checks for qemu_free()/qobject_decref()
>>>>   - refactored send_payload() using helper functions
>>>>   - fixed improper handling of pidfile fd==0
>>>>   - changed guest-shutdown's "shutdown_mode" param to "mode"
>>>>   - switched to using kernel-generated FDs for guest-file-open rather than an autoincrement value
>>>>   - add maximum chunk size of guest-file-read/guest-file-write
>>>>   - added checks to avoid guest-file-write from writing data beyond the provided data buffer
>>>>   - made logging best-effort, removed handling of failures to log as errors
>>>>   - guest-shutdown exec errors now logged to guest syslog, clarified shutdown's asynchronous, no gauruntee nature in schema.
>>>>
>>>> CHANGES SINCE V4:
>>>>   - Removed timeout mechanism via worker thread/pthread_cancel due to potential memory leak. Will re-introduce guest-side timeout support in future version.
>>>>   - Fixed up fsfreeze code to use enums specified within the guest agent's qapi schema.
>>>>   - Fixed memory leak due to a log statement, and added missing cleanup functions for heap-allocated g_error objects.
>>>>   - Made "mode" param to guest-file-open optional, defaults to "r" (read-only)
>>>>
>>>> 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
>>> Is the above command "./qemu-ga" issued on guest teminal or hypervisor teminal?
>>
>> In the guest.
>
> Can we make it work similiar as a linux service? It may is more
> convenient to use.
>
> [root@f12 ~]# service qemu-ga start/stop/status/restart
>
> [root@f12 ~]# chkconfig qemu-ga on/off
>

Definitely, we need the agent to act as a service to be able to rely on 
it remaining after restarts and whatnot. We have some init scripts and 
whatnot for in-house testing/deployment, but this is somewhat 
distro-dependent and is more of a packaging concern than something for 
qemu-devel.

Although, if we do deployment via the guest tools ISO that I sent an RFC 
out for a while back, it may be worthwhile to cover this as part of that 
discussion since ideally we'd come up with something that Just Works on 
most distros. I hope to have a prototype of ISO generation scripts out 
within the next week or so.

>>
>>>
>>>>
>>>> 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"}}
>>>>
>>>>   {"execute":"guest-file-open", "arguments":{"filepath":"/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":{"filepath":"/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 +-
>>>>   configure                       |    1 +
>>>>   qapi-schema-guest.json          |  202 +++++++++++++
>>>>   qemu-ga.c                       |  631 +++++++++++++++++++++++++++++++++++++++
>>>>   qerror.c                        |    4 +
>>>>   qerror.h                        |    3 +
>>>>   qga/guest-agent-command-state.c |   73 +++++
>>>>   qga/guest-agent-commands.c      |  522 ++++++++++++++++++++++++++++++++
>>>>   qga/guest-agent-core.h          |   30 ++
>>>>   9 files changed, 1483 insertions(+), 5 deletions(-)
>>>>
>>>>
>>>>
>>
>>
>
>
>

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

end of thread, other threads:[~2011-07-14 14:05 UTC | newest]

Thread overview: 25+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2011-07-05 13:21 [Qemu-devel] [QAPI+QGA 3/3] QEMU Guest Agent (virtagent) v6 Michael Roth
2011-07-05 13:21 ` [Qemu-devel] [PATCH v6 1/4] guest agent: command state class Michael Roth
2011-07-08 14:25   ` Luiz Capitulino
2011-07-08 20:22     ` Michael Roth
2011-07-05 13:21 ` [Qemu-devel] [PATCH v6 2/4] guest agent: qemu-ga daemon Michael Roth
2011-07-06  0:34   ` Michael Roth
2011-07-08 14:36   ` Luiz Capitulino
2011-07-08 21:12     ` Michael Roth
2011-07-05 13:21 ` [Qemu-devel] [PATCH v6 3/4] guest agent: add guest agent commands schema file Michael Roth
2011-07-08 15:08   ` Luiz Capitulino
2011-07-08 21:42     ` Michael Roth
2011-07-05 13:21 ` [Qemu-devel] [PATCH v6 4/4] guest agent: add guest agent RPCs/commands Michael Roth
2011-07-08 15:14   ` Luiz Capitulino
2011-07-11 20:11     ` Michael Roth
2011-07-11 21:12       ` Luiz Capitulino
2011-07-11 23:11         ` Michael Roth
2011-07-12 14:15           ` Luiz Capitulino
2011-07-12 15:44             ` Michael Roth
2011-07-12 16:30               ` Luiz Capitulino
2011-07-13 13:14 ` [Qemu-devel] [QAPI+QGA 3/3] QEMU Guest Agent (virtagent) v6 Daniel P. Berrange
2011-07-13 17:51   ` Michael Roth
2011-07-14  2:53 ` Zhi Yong Wu
2011-07-14 12:55   ` Luiz Capitulino
2011-07-14 13:53     ` Zhi Yong Wu
2011-07-14 14:04       ` Michael Roth

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.