Skip to contentSkip to conversation

QEMU Guest Agent (virtagent) v5

PATCH New Michael Roth 2 participants 23 messages Jun 14, 2011 – Jun 21, 2011 Kbuild

lore
Share & tools
lore
#0 lore raw

PATCH 3/3 QEMU Guest Agent (virtagent) v5

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

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

(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 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

communicate with the guest agent:

  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(-)
#1 lore raw

PATCH v5 1/5 guest agent: command state class

---
 qga/guest-agent-command-state.c |   73 +++++++++++++++++++++++++++++++++++++++
 qga/guest-agent-core.h          |   25 +++++++++++++
 2 files changed, 98 insertions(+), 0 deletions(-)
 create mode 100644 qga/guest-agent-command-state.c
 create mode 100644 qga/guest-agent-core.h
qga/guest-agent-command-state.c new file +73 -0
new file mode 100644
index 0000000..969da23
@@ -0,0 +1,73 @@
+/*
+ * QEMU Guest Agent command state interfaces
+ *
+ * Copyright IBM Corp. 2011
+ *
+ * Authors:
+ *  Michael Roth      <[email protected]>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+#include <glib.h>
+#include "qga/guest-agent-core.h"
+
+struct GACommandState {
+    GSList *groups;
+};
+
+typedef struct GACommandGroup {
+    void (*init)(void);
+    void (*cleanup)(void);
+} GACommandGroup;
+
+/* handle init/cleanup for stateful guest commands */
+
+void ga_command_state_add(GACommandState *cs,
+                          void (*init)(void),
+                          void (*cleanup)(void))
+{
+    GACommandGroup *cg = g_malloc0(sizeof(GACommandGroup));
+    cg->init = init;
+    cg->cleanup = cleanup;
+    cs->groups = g_slist_append(cs->groups, cg);
+}
+
+static void ga_command_group_init(gpointer opaque, gpointer unused)
+{
+    GACommandGroup *cg = opaque;
+
+    g_assert(cg);
+    if (cg->init) {
+        cg->init();
+    }
+}
+
+void ga_command_state_init_all(GACommandState *cs)
+{
+    g_assert(cs);
+    g_slist_foreach(cs->groups, ga_command_group_init, NULL);
+}
+
+static void ga_command_group_cleanup(gpointer opaque, gpointer unused)
+{
+    GACommandGroup *cg = opaque;
+
+    g_assert(cg);
+    if (cg->cleanup) {
+        cg->cleanup();
+    }
+}
+
+void ga_command_state_cleanup_all(GACommandState *cs)
+{
+    g_assert(cs);
+    g_slist_foreach(cs->groups, ga_command_group_cleanup, NULL);
+}
+
+GACommandState *ga_command_state_new(void)
+{
+    GACommandState *cs = g_malloc0(sizeof(GACommandState));
+    cs->groups = NULL;
+    return cs;
+}
qga/guest-agent-core.h new file +25 -0
new file mode 100644
index 0000000..688f120
@@ -0,0 +1,25 @@
+/*
+ * QEMU Guest Agent core declarations
+ *
+ * Copyright IBM Corp. 2011
+ *
+ * Authors:
+ *  Adam Litke        <[email protected]>
+ *  Michael Roth      <[email protected]>
+ *
+ * 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);
Signature · 1 line
1.7.0.4
#2 lore raw

Re: [Qemu-devel] [PATCH v5 1/5] guest agent: command state class

Quoted 5 lines · On Tue, 14 Jun 2011 15:06:21 -0500 Michael Roth <[email protected]>…
---
 qga/guest-agent-command-state.c |   73 +++++++++++++++++++++++++++++++++++++++
 qga/guest-agent-core.h          |   25 +++++++++++++

It's not possible to build this. I see that the last patch has the Makefile changes, but what we want is that a patch introducing a .c file also updates the Makefile, so that the .c file can be built.

Quoted 30 lines
 2 files changed, 98 insertions(+), 0 deletions(-)
 create mode 100644 qga/guest-agent-command-state.c
 create mode 100644 qga/guest-agent-core.h
qga/guest-agent-command-state.c new file +20 -0
new file mode 100644
index 0000000..969da23
@@ -0,0 +1,73 @@
+/*
+ * QEMU Guest Agent command state interfaces
+ *
+ * Copyright IBM Corp. 2011
+ *
+ * Authors:
+ *  Michael Roth      <[email protected]>
+ *
+ * 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);

qga/guest-agent-command-state.c line 30

Quoted 10 lines
qga/guest-agent-command-state.c new file +10 -0
+    void (*cleanup)(void);
+} GACommandGroup;
+
+/* handle init/cleanup for stateful guest commands */
+
+void ga_command_state_add(GACommandState *cs,
+                          void (*init)(void),
+                          void (*cleanup)(void))
+{
+    GACommandGroup *cg = g_malloc0(sizeof(GACommandGroup));

This is linked against qemu-ga only, right? Otherwise I think we should use qemu_mallocz(). And you probably want to check against NULL.

qga/guest-agent-command-state.c line 33

Quoted 3 lines
qga/guest-agent-command-state.c new file +3 -0
+    cg->init = init;
+    cg->cleanup = cleanup;
+    cs->groups = g_slist_append(cs->groups, cg);

Not that I'm asking to you change anything here, but we're going to get a funny mix with QObjects :)

Quoted 71 lines
qga/guest-agent-command-state.c new file +40 -0
+}
+
+static void ga_command_group_init(gpointer opaque, gpointer unused)
+{
+    GACommandGroup *cg = opaque;
+
+    g_assert(cg);
+    if (cg->init) {
+        cg->init();
+    }
+}
+
+void ga_command_state_init_all(GACommandState *cs)
+{
+    g_assert(cs);
+    g_slist_foreach(cs->groups, ga_command_group_init, NULL);
+}
+
+static void ga_command_group_cleanup(gpointer opaque, gpointer unused)
+{
+    GACommandGroup *cg = opaque;
+
+    g_assert(cg);
+    if (cg->cleanup) {
+        cg->cleanup();
+    }
+}
+
+void ga_command_state_cleanup_all(GACommandState *cs)
+{
+    g_assert(cs);
+    g_slist_foreach(cs->groups, ga_command_group_cleanup, NULL);
+}
+
+GACommandState *ga_command_state_new(void)
+{
+    GACommandState *cs = g_malloc0(sizeof(GACommandState));
+    cs->groups = NULL;
+    return cs;
+}
qga/guest-agent-core.h new file +25 -0
new file mode 100644
index 0000000..688f120
@@ -0,0 +1,25 @@
+/*
+ * QEMU Guest Agent core declarations
+ *
+ * Copyright IBM Corp. 2011
+ *
+ * Authors:
+ *  Adam Litke        <[email protected]>
+ *  Michael Roth      <[email protected]>
+ *
+ * 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);
#3 lore raw

Re: [Qemu-devel] [PATCH v5 1/5] guest agent: command state class

Quoted 4 lines · Luiz Capitulino
Quoted 1 line · On Tue, 14 Jun 2011 15:06:21 -0500 Michael Roth<[email protected]>…
Quoted 9 lines
Quoted 4 lines
---
  qga/guest-agent-command-state.c |   73 +++++++++++++++++++++++++++++++++++++++
  qga/guest-agent-core.h          |   25 +++++++++++++

It's not possible to build this. I see that the last patch has the Makefile changes, but what we want is that a patch introducing a .c file also updates the Makefile, so that the .c file can be built.

I made it a point to test build each C file as introduced, but I'm not sure there's a way of doing incremental builds without modifying the Makefile manually anyway, since we'll be missing a main() function until qemu-ga.c. Or maybe I'm just missing something...any ideas?

Quoted 44 lines
Quoted 30 lines
  2 files changed, 98 insertions(+), 0 deletions(-)

create mode 100644 qga/guest-agent-command-state.c create mode 100644 qga/guest-agent-core.h

qga/guest-agent-command-state.c new file +20 -0
new file mode 100644
index 0000000..969da23
@@ -0,0 +1,73 @@
+/*
+ * QEMU Guest Agent command state interfaces
+ *
+ * Copyright IBM Corp. 2011
+ *
+ * Authors:
+ *  Michael Roth<[email protected]>
+ *
+ * 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);

qga/guest-agent-command-state.c line 30

Quoted 10 lines
qga/guest-agent-command-state.c new file +10 -0
+    void (*cleanup)(void);
+} GACommandGroup;
+
+/* handle init/cleanup for stateful guest commands */
+
+void ga_command_state_add(GACommandState *cs,
+                          void (*init)(void),
+                          void (*cleanup)(void))
+{
+    GACommandGroup *cg = g_malloc0(sizeof(GACommandGroup));

This is linked against qemu-ga only, right? Otherwise I think we should use qemu_mallocz(). And you probably want to check against NULL.

Quoted 7 lines

qga/guest-agent-command-state.c line 33

Quoted 3 lines
qga/guest-agent-command-state.c new file +3 -0
+    cg->init = init;
+    cg->cleanup = cleanup;
+    cs->groups = g_slist_append(cs->groups, cg);

Not that I'm asking to you change anything here, but we're going to get a funny mix with QObjects :)

glib is a hard dependency here so i tried to stick with it where possible, since there's a lot of code where i'm kinda forced to use GObject stuff, like all the GIO functions for instance (GObject + GError + g_free etc). I think fsfreeze still uses qlist/qemu_free/etc, best I can do is fix that up to relegate all the non-glib stuff to qemu-ga.c and the qapi/qmp stuff it pulls in.

Quoted 72 lines
Quoted 71 lines
qga/guest-agent-command-state.c new file +40 -0
+}
+
+static void ga_command_group_init(gpointer opaque, gpointer unused)
+{
+    GACommandGroup *cg = opaque;
+
+    g_assert(cg);
+    if (cg->init) {
+        cg->init();
+    }
+}
+
+void ga_command_state_init_all(GACommandState *cs)
+{
+    g_assert(cs);
+    g_slist_foreach(cs->groups, ga_command_group_init, NULL);
+}
+
+static void ga_command_group_cleanup(gpointer opaque, gpointer unused)
+{
+    GACommandGroup *cg = opaque;
+
+    g_assert(cg);
+    if (cg->cleanup) {
+        cg->cleanup();
+    }
+}
+
+void ga_command_state_cleanup_all(GACommandState *cs)
+{
+    g_assert(cs);
+    g_slist_foreach(cs->groups, ga_command_group_cleanup, NULL);
+}
+
+GACommandState *ga_command_state_new(void)
+{
+    GACommandState *cs = g_malloc0(sizeof(GACommandState));
+    cs->groups = NULL;
+    return cs;
+}
qga/guest-agent-core.h new file +25 -0
new file mode 100644
index 0000000..688f120
@@ -0,0 +1,25 @@
+/*
+ * QEMU Guest Agent core declarations
+ *
+ * Copyright IBM Corp. 2011
+ *
+ * Authors:
+ *  Adam Litke<[email protected]>
+ *  Michael Roth<[email protected]>
+ *
+ * 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);
#4 lore raw

Re: [Qemu-devel] [PATCH v5 1/5] guest agent: command state class

Quoted 15 lines · On Thu, 16 Jun 2011 13:46:31 -0500 Michael Roth <[email protected]>…
Quoted 4 lines · Luiz Capitulino
Quoted 1 line · On Tue, 14 Jun 2011 15:06:21 -0500 Michael Roth<[email protected]>…
Quoted 9 lines
Quoted 4 lines
---
  qga/guest-agent-command-state.c |   73 +++++++++++++++++++++++++++++++++++++++
  qga/guest-agent-core.h          |   25 +++++++++++++

It's not possible to build this. I see that the last patch has the Makefile changes, but what we want is that a patch introducing a .c file also updates the Makefile, so that the .c file can be built.

Quoted 4 lines

I made it a point to test build each C file as introduced, but I'm not sure there's a way of doing incremental builds without modifying the Makefile manually anyway, since we'll be missing a main() function until qemu-ga.c. Or maybe I'm just missing something...any ideas?

In mind it doesn't make much sense to add a .c file that can't be built, so you have two options: you merge this in the next patch or you build the object file only. I like the former more.

Quoted 53 lines
Quoted 45 lines
Quoted 31 lines
  2 files changed, 98 insertions(+), 0 deletions(-)

create mode 100644 qga/guest-agent-command-state.c create mode 100644 qga/guest-agent-core.h

qga/guest-agent-command-state.c new file +20 -0
new file mode 100644
index 0000000..969da23
@@ -0,0 +1,73 @@
+/*
+ * QEMU Guest Agent command state interfaces
+ *
+ * Copyright IBM Corp. 2011
+ *
+ * Authors:
+ *  Michael Roth<[email protected]>
+ *
+ * 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);

qga/guest-agent-command-state.c line 30

Quoted 10 lines
qga/guest-agent-command-state.c new file +10 -0
+    void (*cleanup)(void);
+} GACommandGroup;
+
+/* handle init/cleanup for stateful guest commands */
+
+void ga_command_state_add(GACommandState *cs,
+                          void (*init)(void),
+                          void (*cleanup)(void))
+{
+    GACommandGroup *cg = g_malloc0(sizeof(GACommandGroup));

This is linked against qemu-ga only, right? Otherwise I think we should use qemu_mallocz(). And you probably want to check against NULL.

Quoted 7 lines

qga/guest-agent-command-state.c line 33

Quoted 3 lines
qga/guest-agent-command-state.c new file +3 -0
+    cg->init = init;
+    cg->cleanup = cleanup;
+    cs->groups = g_slist_append(cs->groups, cg);

Not that I'm asking to you change anything here, but we're going to get a funny mix with QObjects :)

Quoted 6 lines

glib is a hard dependency here so i tried to stick with it where possible, since there's a lot of code where i'm kinda forced to use GObject stuff, like all the GIO functions for instance (GObject + GError
+ g_free etc). I think fsfreeze still uses qlist/qemu_free/etc, best I can do is fix that up to relegate all the non-glib stuff to qemu-ga.c and the qapi/qmp stuff it pulls in.

Honestly, I don't know where to draw the line. I mean, something we should really avoid is mixing them in a way that it gets confusing and error prone, like the same function using Error and GError or allocating data with g_malloc() and freeing it with qemu_free() (I'm not saying I saw that in this series, it's just an example).

Now, I don't know why we should use g_malloc() for example. Does it have any additional features? Like what talloc has? If it doesn't have, then I think we should use qemu's variants.

Another example is glib's list implementation. I think it shouldn't be used when qemu's QLIST suffices.

Quoted 74 lines
Quoted 73 lines
Quoted 72 lines
qga/guest-agent-command-state.c new file +40 -0
+}
+
+static void ga_command_group_init(gpointer opaque, gpointer unused)
+{
+    GACommandGroup *cg = opaque;
+
+    g_assert(cg);
+    if (cg->init) {
+        cg->init();
+    }
+}
+
+void ga_command_state_init_all(GACommandState *cs)
+{
+    g_assert(cs);
+    g_slist_foreach(cs->groups, ga_command_group_init, NULL);
+}
+
+static void ga_command_group_cleanup(gpointer opaque, gpointer unused)
+{
+    GACommandGroup *cg = opaque;
+
+    g_assert(cg);
+    if (cg->cleanup) {
+        cg->cleanup();
+    }
+}
+
+void ga_command_state_cleanup_all(GACommandState *cs)
+{
+    g_assert(cs);
+    g_slist_foreach(cs->groups, ga_command_group_cleanup, NULL);
+}
+
+GACommandState *ga_command_state_new(void)
+{
+    GACommandState *cs = g_malloc0(sizeof(GACommandState));
+    cs->groups = NULL;
+    return cs;
+}
qga/guest-agent-core.h new file +25 -0
new file mode 100644
index 0000000..688f120
@@ -0,0 +1,25 @@
+/*
+ * QEMU Guest Agent core declarations
+ *
+ * Copyright IBM Corp. 2011
+ *
+ * Authors:
+ *  Adam Litke<[email protected]>
+ *  Michael Roth<[email protected]>
+ *
+ * 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);
#5 lore raw

PATCH v5 2/5 guest agent: qemu-ga daemon

In reply to Michael Roth

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
---
 qemu-ga.c              |  631 ++++++++++++++++++++++++++++++++++++++++++++++++
 qga/guest-agent-core.h |    4 +
 2 files changed, 635 insertions(+), 0 deletions(-)
 create mode 100644 qemu-ga.c
qemu-ga.c new file +631 -0
new file mode 100644
index 0000000..df08d8c
@@ -0,0 +1,631 @@
+/*
+ * QEMU Guest Agent
+ *
+ * Copyright IBM Corp. 2011
+ *
+ * Authors:
+ *  Adam Litke        <[email protected]>
+ *  Michael Roth      <[email protected]>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdbool.h>
+#include <glib.h>
+#include <gio/gio.h>
+#include <getopt.h>
+#include <termios.h>
+#include <syslog.h>
+#include "qemu_socket.h"
+#include "json-streamer.h"
+#include "json-parser.h"
+#include "qint.h"
+#include "qjson.h"
+#include "qga/guest-agent-core.h"
+#include "qga-qmp-commands.h"
+#include "module.h"
+
+#define QGA_VIRTIO_PATH_DEFAULT "/dev/virtio-ports/org.qemu.guest_agent"
+#define QGA_PIDFILE_DEFAULT "/var/run/qemu-va.pid"
+#define QGA_BAUDRATE_DEFAULT B38400 /* for isa-serial channels */
+#define QGA_TIMEOUT_DEFAULT 30*1000 /* ms */
+
+struct GAState {
+    const char *proxy_path;
+    JSONMessageParser parser;
+    GMainLoop *main_loop;
+    guint conn_id;
+    GSocket *conn_sock;
+    GIOChannel *conn_channel;
+    guint listen_id;
+    GSocket *listen_sock;
+    GIOChannel *listen_channel;
+    const char *path;
+    const char *method;
+    bool virtio; /* fastpath to check for virtio to deal with poll() quirks */
+    GACommandState *command_state;
+    GLogLevelFlags log_level;
+    FILE *log_file;
+    bool logging_enabled;
+};
+
+static void usage(const char *cmd)
+{
+    printf(
+"Usage: %s -c <channel_opts>\n"
+"QEMU Guest Agent %s\n"
+"\n"
+"  -c, --channel     channel method: one of unix-connect, virtio-serial, or\n"
+"                    isa-serial (virtio-serial is the default)\n"
+"  -p, --path        channel path (%s is the default for virtio-serial)\n"
+"  -l, --logfile     set logfile path, logs to stderr by default\n"
+"  -f, --pidfile     specify pidfile (default is %s)\n"
+"  -v, --verbose     log extra debugging information\n"
+"  -V, --version     print version information and exit\n"
+"  -d, --daemonize   become a daemon\n"
+"  -h, --help        display this help and exit\n"
+"\n"
+"Report bugs to <[email protected]>\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_RDWR, S_IRUSR|S_IWUSR);
+    if (!pidfd || lockf(pidfd, F_TLOCK, 0)) {
+        g_error("Cannot lock pid file");
+    }
+
+    if (ftruncate(pidfd, 0) || lseek(pidfd, 0, SEEK_SET)) {
+        g_critical("Cannot truncate pid file");
+        goto fail;
+    }
+    if (asprintf(&pidstr, "%d", getpid()) == -1) {
+        g_critical("Cannot allocate memory");
+        goto fail;
+    }
+    if (write(pidfd, pidstr, strlen(pidstr)) != strlen(pidstr)) {
+        g_critical("Failed to write pid file");
+        goto fail;
+    }
+
+    umask(0);
+    sid = setsid();
+    if (sid < 0) {
+        goto fail;
+    }
+    if ((chdir("/")) < 0) {
+        goto fail;
+    }
+
+    close(STDIN_FILENO);
+    close(STDOUT_FILENO);
+    close(STDERR_FILENO);
+    return;
+
+fail:
+    if (pidstr) {
+        free(pidstr);
+    }
+    unlink(pidfile);
+    g_error("failed to daemonize");
+}
+
+static int conn_channel_send_payload(GIOChannel *channel, QObject *payload)
+{
+    gsize count, written = 0;
+    int ret;
+    const char *buf;
+    QString *payload_qstr;
+    GIOStatus status;
+    GError *err = NULL;
+
+    if (!payload || !channel) {
+        ret = -EINVAL;
+        goto out;
+    }
+
+    payload_qstr = qobject_to_json(payload);
+    if (!payload_qstr) {
+        ret = -EINVAL;
+        goto out;
+    }
+
+    buf = qstring_get_str(payload_qstr);
+    count = strlen(buf);
+
+    while (count) {
+        g_debug("sending data, count: %d", (int)count);
+        status = g_io_channel_write_chars(channel, buf, count, &written, &err);
+        if (err != NULL) {
+            g_warning("error writing payload to channel: %s", err->message);
+            ret = err->code;
+            goto out_free;
+        }
+        if (status == G_IO_STATUS_NORMAL) {
+            count -= written;
+        } else if (status == G_IO_STATUS_ERROR || status == G_IO_STATUS_EOF) {
+            ret = -EPIPE;
+            goto out_free;
+        }
+    }
+
+    status = g_io_channel_write_chars(channel, (char *)"\n", 1, &written, &err);
+    if (err != NULL) {
+        g_warning("error sending newline: %s", err->message);
+        ret = err->code;
+        goto out_free;
+    } else if (status == G_IO_STATUS_ERROR || status == G_IO_STATUS_EOF) {
+        ret = -EPIPE;
+        goto out_free;
+    }
+
+    g_io_channel_flush(channel, &err);
+    if (err != NULL) {
+        g_warning("error flushing payload: %s", err->message);
+        ret = err->code;
+        goto out_free;
+    }
+
+    ret = 0;
+
+out_free:
+    QDECREF(payload_qstr);
+    if (err != NULL) {
+        g_error_free(err);
+    }
+out:
+    return ret;
+}
+
+static void process_command(GAState *s, QDict *req)
+{
+    QObject *rsp = NULL;
+    Error *err = NULL;
+    int ret;
+
+    g_assert(req);
+    g_debug("processing command");
+    rsp = qmp_dispatch(QOBJECT(req));
+    if (rsp) {
+        if (err) {
+            g_warning("command failed: %s", error_get_pretty(err));
+        }
+        ret = conn_channel_send_payload(s->conn_channel, rsp);
+        if (ret) {
+            g_warning("error sending payload: %s", strerror(ret));
+        }
+        qobject_decref(rsp);
+    } else {
+        g_warning("error getting response");
+        if (err) {
+            g_warning("dispatch failed: %s", error_get_pretty(err));
+        }
+    }
+}
+
+/* handle requests/control events coming in over the channel */
+static void process_event(JSONMessageParser *parser, QList *tokens)
+{
+    GAState *s = container_of(parser, GAState, parser);
+    QObject *obj;
+    QDict *qdict;
+    Error *err = NULL;
+
+    g_assert(s && parser);
+
+    g_debug("process_event: called");
+    obj = json_parser_parse_err(tokens, NULL, &err);
+    if (!obj || qobject_type(obj) != QTYPE_QDICT) {
+        g_warning("failed to parse event");
+        return;
+    } else {
+        g_debug("parse successful");
+        qdict = qobject_to_qdict(obj);
+        g_assert(qdict);
+    }
+
+    /* handle host->guest commands */
+    if (qdict_haskey(qdict, "execute")) {
+        process_command(s, qdict);
+    } else {
+        g_warning("unrecognized payload format, ignoring");
+    }
+
+    QDECREF(qdict);
+}
+
+static gboolean conn_channel_read(GIOChannel *channel, GIOCondition condition,
+                                  gpointer data)
+{
+    GAState *s = data;
+    gchar buf[1024];
+    gsize count;
+    GError *err = NULL;
+    memset(buf, 0, 1024);
+    GIOStatus status = g_io_channel_read_chars(channel, buf, 1024,
+                                               &count, &err);
+    if (err != NULL) {
+        g_warning("error reading channel: %s", err->message);
+        conn_channel_close(s);
+        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, listen_fd=0
+ * indicates we should use the existing s->listen_channel
+ */
+static int listen_channel_add(GAState *s, int listen_fd)
+{
+    GError *err = NULL;
+    guint listen_id;
+
+    if (listen_fd) {
+        s->listen_channel = g_io_channel_unix_new(listen_fd);
+        if (s->listen_sock) {
+            g_object_unref(s->listen_sock);
+        }
+        s->listen_sock = g_socket_new_from_fd(listen_fd, &err);
+        if (err != NULL) {
+            g_warning("error converting fd to gsocket: %s", err->message);
+            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);
+    } else if (strcmp(s->method, "virtio-serial") == 0) {
+        /* we spin on EOF for virtio-serial, so back off a bit. also,
+         * dont close the connection in this case, it'll resume normal
+         * operation when another process connects to host chardev
+         */
+        usleep(100*1000);
+        goto out_noclose;
+    }
+    g_io_channel_unref(s->conn_channel);
+    s->conn_channel = NULL;
+    s->conn_id = 0;
+out_noclose:
+    return;
+}
+
+static void init_guest_agent(GAState *s)
+{
+    struct termios tio;
+    int ret, fd;
+
+    if (s->method == NULL) {
+        /* try virtio-serial as our default */
+        s->method = "virtio-serial";
+    }
+
+    if (s->path == NULL) {
+        if (strcmp(s->method, "virtio-serial") != 0) {
+            g_error("must specify a path for this channel");
+        }
+        /* try the default path for the virtio-serial port */
+        s->path = QGA_VIRTIO_PATH_DEFAULT;
+    }
+
+    if (strcmp(s->method, "virtio-serial") == 0) {
+        s->virtio = true;
+        fd = qemu_open(s->path, O_RDWR);
+        if (fd == -1) {
+            g_error("error opening channel: %s", strerror(errno));
+        }
+        ret = fcntl(fd, F_GETFL);
+        if (ret < 0) {
+            g_error("error getting channel flags: %s", strerror(errno));
+        }
+        ret = fcntl(fd, F_SETFL, ret | O_NONBLOCK | O_ASYNC);
+        if (ret < 0) {
+            g_error("error setting channel flags: %s", strerror(errno));
+        }
+        ret = conn_channel_add(s, fd);
+        if (ret) {
+            g_error("error adding channel to main loop");
+        }
+    } else if (strcmp(s->method, "isa-serial") == 0) {
+        fd = qemu_open(s->path, O_RDWR | O_NOCTTY);
+        if (fd == -1) {
+            g_error("error opening channel: %s", strerror(errno));
+        }
+        tcgetattr(fd, &tio);
+        /* set up serial port for non-canonical, dumb byte streaming */
+        tio.c_iflag &= ~(IGNBRK | BRKINT | IGNPAR | PARMRK | INPCK | ISTRIP |
+                         INLCR | IGNCR | ICRNL | IXON | IXOFF | IXANY |
+                         IMAXBEL);
+        tio.c_oflag = 0;
+        tio.c_lflag = 0;
+        tio.c_cflag |= QGA_BAUDRATE_DEFAULT;
+        /* 1 available byte min or reads will block (we'll set non-blocking
+         * elsewhere, else we have to deal with read()=0 instead)
+         */
+        tio.c_cc[VMIN] = 1;
+        tio.c_cc[VTIME] = 0;
+        /* flush everything waiting for read/xmit, it's garbage at this point */
+        tcflush(fd, TCIFLUSH);
+        tcsetattr(fd, TCSANOW, &tio);
+        ret = conn_channel_add(s, fd);
+        if (ret) {
+            g_error("error adding channel to main loop");
+        }
+    } else if (strcmp(s->method, "unix-listen") == 0) {
+        fd = unix_listen(s->path, NULL, strlen(s->path));
+        if (fd == -1) {
+            g_error("error opening path: %s", strerror(errno));
+        }
+        ret = listen_channel_add(s, fd);
+        if (ret) {
+            g_error("error binding/listening to specified socket");
+        }
+    } else {
+        g_error("unsupported channel method/type: %s", s->method);
+    }
+
+    json_message_parser_init(&s->parser, process_event);
+    s->main_loop = g_main_loop_new(NULL, false);
+}
+
+int main(int argc, char **argv)
+{
+    const char *sopt = "hVvdc:p:l:f:";
+    const char *method = NULL, *path = NULL, *pidfile = QGA_PIDFILE_DEFAULT;
+    struct option lopt[] = {
+        { "help", 0, NULL, 'h' },
+        { "version", 0, NULL, 'V' },
+        { "logfile", 0, NULL, 'l' },
+        { "pidfile", 0, NULL, 'f' },
+        { "verbose", 0, NULL, 'v' },
+        { "channel", 0, NULL, 'c' },
+        { "path", 0, NULL, 'p' },
+        { "daemonize", 0, NULL, 'd' },
+        { NULL, 0, NULL, 0 }
+    };
+    int opt_ind = 0, ch, daemonize = 0;
+    GLogLevelFlags log_level = G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL;
+    FILE *log_file = stderr;
+    GAState *s;
+
+    g_type_init();
+    g_thread_init(NULL);
+
+    while ((ch = getopt_long(argc, argv, sopt, lopt, &opt_ind)) != -1) {
+        switch (ch) {
+        case 'c':
+            method = optarg;
+            break;
+        case 'p':
+            path = optarg;
+            break;
+        case 'l':
+            log_file = fopen(optarg, "a");
+            if (!log_file) {
+                g_error("unable to open specified log file: %s",
+                        strerror(errno));
+            }
+            break;
+        case 'f':
+            pidfile = optarg;
+            break;
+        case 'v':
+            /* enable all log levels */
+            log_level = G_LOG_LEVEL_MASK;
+            break;
+        case 'V':
+            printf("QEMU Guest Agent %s\n", QGA_VERSION);
+            return 0;
+        case 'd':
+            daemonize = 1;
+            break;
+        case 'h':
+            usage(argv[0]);
+            return 0;
+        case '?':
+            g_error("Unknown option, try '%s --help' for more information.",
+                    argv[0]);
+        }
+    }
+
+    if (daemonize) {
+        g_debug("starting daemon");
+        become_daemon(pidfile);
+    }
+
+    s = g_malloc0(sizeof(GAState));
+    s->conn_id = 0;
+    s->conn_channel = NULL;
+    s->path = path;
+    s->method = method;
+    s->command_state = ga_command_state_new();
+    ga_command_state_init(s, s->command_state);
+    ga_command_state_init_all(s->command_state);
+    s->log_file = log_file;
+    s->log_level = log_level;
+    g_log_set_default_handler(ga_log, s);
+    g_log_set_fatal_mask(NULL, G_LOG_LEVEL_ERROR);
+    s->logging_enabled = true;
+
+    module_call_init(MODULE_INIT_QAPI);
+    init_guest_agent(s);
+
+    g_main_loop_run(s->main_loop);
+
+    ga_command_state_cleanup_all(s->command_state);
+
+    return 0;
+}
qga/guest-agent-core.h +4 -0
index 688f120..66d1729 100644
@@ -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);
Signature · 1 line
1.7.0.4
#6 lore raw

Re: [Qemu-devel] [PATCH v5 2/5] guest agent: qemu-ga daemon

Quoted 7 lines · On Tue, 14 Jun 2011 15:06:22 -0500 Michael Roth <[email protected]>…

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:

Quoted 8 lines

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

Would be nice to have a more complete description, like explaining how to do a simple test.

And this can't be built...

Quoted 38 lines
---
 qemu-ga.c              |  631 ++++++++++++++++++++++++++++++++++++++++++++++++
 qga/guest-agent-core.h |    4 +
 2 files changed, 635 insertions(+), 0 deletions(-)
 create mode 100644 qemu-ga.c
qemu-ga.c new file +26 -0
new file mode 100644
index 0000000..df08d8c
@@ -0,0 +1,631 @@
+/*
+ * QEMU Guest Agent
+ *
+ * Copyright IBM Corp. 2011
+ *
+ * Authors:
+ *  Adam Litke        <[email protected]>
+ *  Michael Roth      <[email protected]>
+ *
+ * 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"

qemu-ga.c line 36

Quoted 10 lines
qemu-ga.c new file +10 -0
+#include "qga-qmp-commands.h"
+#include "module.h"
+
+#define QGA_VIRTIO_PATH_DEFAULT "/dev/virtio-ports/org.qemu.guest_agent"
+#define QGA_PIDFILE_DEFAULT "/var/run/qemu-va.pid"
+#define QGA_BAUDRATE_DEFAULT B38400 /* for isa-serial channels */
+#define QGA_TIMEOUT_DEFAULT 30*1000 /* ms */
+
+struct GAState {
+    const char *proxy_path;

Where is this used?

Quoted 63 lines
qemu-ga.c new file +63 -0
+    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 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 <[email protected]>\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;
+}

qemu-ga.c line 109

Quoted 10 lines
qemu-ga.c new file +10 -0
+
+void ga_disable_logging(GAState *s)
+{
+    s->logging_enabled = false;
+}
+
+void ga_enable_logging(GAState *s)
+{
+    s->logging_enabled = true;
+}

Just to check I got this right, this is needed because of the fsfreeze command, correct? Isn't it better to have a more descriptive name, like fsfrozen?

First I thought this was about a log file. Then I realized this was probably about letting the user log in, but we don't seem to have this functionality so I got confused.

Quoted 28 lines
qemu-ga.c new file +28 -0
+
+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;

qemu-ga.c line 147

Quoted 10 lines
qemu-ga.c new file +10 -0
+
+    pid = fork();
+    if (pid < 0) {
+        exit(EXIT_FAILURE);
+    }
+    if (pid > 0) {
+        exit(EXIT_SUCCESS);
+    }
+
+    pidfd = open(pidfile, O_CREAT|O_RDWR, S_IRUSR|S_IWUSR);

O_WRONLY is enough.

qemu-ga.c line 150

Quoted 3 lines
qemu-ga.c new file +3 -0
+    if (!pidfd || lockf(pidfd, F_TLOCK, 0)) {
+        g_error("Cannot lock pid file");
+    }

Are you sure we need lockf() here? I think using O_EXCL is enough for pid files.

qemu-ga.c line 154

Quoted 4 lines
qemu-ga.c new file +4 -0
+
+    if (ftruncate(pidfd, 0) || lseek(pidfd, 0, SEEK_SET)) {
+        g_critical("Cannot truncate pid file");
+        goto fail;

You can use O_TRUNC and the file pointer is already positioned at the beginning of the file, no need to call lseek().

qemu-ga.c line 163

Quoted 9 lines
qemu-ga.c new file +9 -0
+    }
+    if (asprintf(&pidstr, "%d", getpid()) == -1) {
+        g_critical("Cannot allocate memory");
+        goto fail;
+    }
+    if (write(pidfd, pidstr, strlen(pidstr)) != strlen(pidstr)) {
+        g_critical("Failed to write pid file");
+        goto fail;
+    }

You're not freeing pidstr, nor closing the file (although I think you do this because of the lockf() call()).

Quoted 9 lines
qemu-ga.c new file +9 -0
+
+    umask(0);
+    sid = setsid();
+    if (sid < 0) {
+        goto fail;
+    }
+    if ((chdir("/")) < 0) {
+        goto fail;
+    }

qemu-ga.c line 182

Quoted 10 lines
qemu-ga.c new file +10 -0
+
+    close(STDIN_FILENO);
+    close(STDOUT_FILENO);
+    close(STDERR_FILENO);
+    return;
+
+fail:
+    if (pidstr) {
+        free(pidstr);
+    }

This check is not needed.

Quoted 7 lines
qemu-ga.c new file +7 -0
+    unlink(pidfile);
+    g_error("failed to daemonize");
+}
+
+static int conn_channel_send_payload(GIOChannel *channel, QObject *payload)
+{
+    gsize count, written = 0;

qemu-ga.c line 199

Quoted 10 lines
qemu-ga.c new file +10 -0
+    int ret;
+    const char *buf;
+    QString *payload_qstr;
+    GIOStatus status;
+    GError *err = NULL;
+
+    if (!payload || !channel) {
+        ret = -EINVAL;
+        goto out;
+    }

Just do "return -EINVAL" instead of using goto, but I wonder if this should be an assert() instead.

qemu-ga.c line 205

Quoted 6 lines
qemu-ga.c new file +6 -0
+
+    payload_qstr = qobject_to_json(payload);
+    if (!payload_qstr) {
+        ret = -EINVAL;
+        goto out;
+    }

return -EINVAL.

Quoted 19 lines
qemu-ga.c new file +19 -0
+
+    buf = qstring_get_str(payload_qstr);
+    count = strlen(buf);
+
+    while (count) {
+        g_debug("sending data, count: %d", (int)count);
+        status = g_io_channel_write_chars(channel, buf, count, &written, &err);
+        if (err != NULL) {
+            g_warning("error writing payload to channel: %s", err->message);
+            ret = err->code;
+            goto out_free;
+        }
+        if (status == G_IO_STATUS_NORMAL) {
+            count -= written;
+        } else if (status == G_IO_STATUS_ERROR || status == G_IO_STATUS_EOF) {
+            ret = -EPIPE;
+            goto out_free;
+        }
+    }

qemu-ga.c line 234

Quoted 10 lines
qemu-ga.c new file +10 -0
+
+    status = g_io_channel_write_chars(channel, (char *)"\n", 1, &written, &err);
+    if (err != NULL) {
+        g_warning("error sending newline: %s", err->message);
+        ret = err->code;
+        goto out_free;
+    } else if (status == G_IO_STATUS_ERROR || status == G_IO_STATUS_EOF) {
+        ret = -EPIPE;
+        goto out_free;
+    }

This wants to be a function.

Quoted 5 lines
qemu-ga.c new file +5 -0
+
+    g_io_channel_flush(channel, &err);
+    if (err != NULL) {
+        g_warning("error flushing payload: %s", err->message);
+        ret = err->code;

qemu-ga.c line 249

Quoted 10 lines
qemu-ga.c new file +10 -0
+        goto out_free;
+    }
+
+    ret = 0;
+
+out_free:
+    QDECREF(payload_qstr);
+    if (err != NULL) {
+        g_error_free(err);
+    }

No need to check against NULL.

Quoted 5 lines
qemu-ga.c new file +5 -0
+out:
+    return ret;
+}
+
+static void process_command(GAState *s, QDict *req)

qemu-ga.c line 264

Quoted 10 lines
qemu-ga.c new file +10 -0
+{
+    QObject *rsp = NULL;
+    Error *err = NULL;
+    int ret;
+
+    g_assert(req);
+    g_debug("processing command");
+    rsp = qmp_dispatch(QOBJECT(req));
+    if (rsp) {
+        if (err) {

Apart from its initialization, 'err' is read only. Either, you want to use qmp_dispatch_err() here or you have to modify qmp_dispatch() to also accept an Error argument.

If you want to send a "return" or "error" QMP kind of response, then you have to do the latter.

Quoted 19 lines
qemu-ga.c new file +19 -0
+            g_warning("command failed: %s", error_get_pretty(err));
+        }
+        ret = conn_channel_send_payload(s->conn_channel, rsp);
+        if (ret) {
+            g_warning("error sending payload: %s", strerror(ret));
+        }
+        qobject_decref(rsp);
+    } else {
+        g_warning("error getting response");
+        if (err) {
+            g_warning("dispatch failed: %s", error_get_pretty(err));
+        }
+    }
+}
+
+/* handle requests/control events coming in over the channel */
+static void process_event(JSONMessageParser *parser, QList *tokens)
+{
+    GAState *s = container_of(parser, GAState, parser);

qemu-ga.c line 293

Quoted 10 lines
qemu-ga.c new file +10 -0
+    QObject *obj;
+    QDict *qdict;
+    Error *err = NULL;
+
+    g_assert(s && parser);
+
+    g_debug("process_event: called");
+    obj = json_parser_parse_err(tokens, NULL, &err);
+    if (!obj || qobject_type(obj) != QTYPE_QDICT) {
+        g_warning("failed to parse event");

Two possible leaks here. You have to call qobject_decref() if obj != NULL and there's a missing call to error_free(). But you don't use 'err' anyway, so you could pass NULL there.

Oh, btw, the guest agent doesn't seem to report errors back to its client... Not even json error messages, is this intended?

qemu-ga.c line 299

Quoted 6 lines
qemu-ga.c new file +6 -0
+        return;
+    } else {
+        g_debug("parse successful");
+        qdict = qobject_to_qdict(obj);
+        g_assert(qdict);
+    }

Superfluous else clause.

qga/guest-agent-core.h

Quoted 336 lines
qemu-ga.c new file +332 -0
+
+    /* 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, listen_fd=0
+ * indicates we should use the existing s->listen_channel
+ */
+static int listen_channel_add(GAState *s, int listen_fd)
+{
+    GError *err = NULL;
+    guint listen_id;
+
+    if (listen_fd) {
+        s->listen_channel = g_io_channel_unix_new(listen_fd);
+        if (s->listen_sock) {
+            g_object_unref(s->listen_sock);
+        }
+        s->listen_sock = g_socket_new_from_fd(listen_fd, &err);
+        if (err != NULL) {
+            g_warning("error converting fd to gsocket: %s", err->message);
+            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);
+    } else if (strcmp(s->method, "virtio-serial") == 0) {
+        /* we spin on EOF for virtio-serial, so back off a bit. also,
+         * dont close the connection in this case, it'll resume normal
+         * operation when another process connects to host chardev
+         */
+        usleep(100*1000);
+        goto out_noclose;
+    }
+    g_io_channel_unref(s->conn_channel);
+    s->conn_channel = NULL;
+    s->conn_id = 0;
+out_noclose:
+    return;
+}
+
+static void init_guest_agent(GAState *s)
+{
+    struct termios tio;
+    int ret, fd;
+
+    if (s->method == NULL) {
+        /* try virtio-serial as our default */
+        s->method = "virtio-serial";
+    }
+
+    if (s->path == NULL) {
+        if (strcmp(s->method, "virtio-serial") != 0) {
+            g_error("must specify a path for this channel");
+        }
+        /* try the default path for the virtio-serial port */
+        s->path = QGA_VIRTIO_PATH_DEFAULT;
+    }
+
+    if (strcmp(s->method, "virtio-serial") == 0) {
+        s->virtio = true;
+        fd = qemu_open(s->path, O_RDWR);
+        if (fd == -1) {
+            g_error("error opening channel: %s", strerror(errno));
+        }
+        ret = fcntl(fd, F_GETFL);
+        if (ret < 0) {
+            g_error("error getting channel flags: %s", strerror(errno));
+        }
+        ret = fcntl(fd, F_SETFL, ret | O_NONBLOCK | O_ASYNC);
+        if (ret < 0) {
+            g_error("error setting channel flags: %s", strerror(errno));
+        }
+        ret = conn_channel_add(s, fd);
+        if (ret) {
+            g_error("error adding channel to main loop");
+        }
+    } else if (strcmp(s->method, "isa-serial") == 0) {
+        fd = qemu_open(s->path, O_RDWR | O_NOCTTY);
+        if (fd == -1) {
+            g_error("error opening channel: %s", strerror(errno));
+        }
+        tcgetattr(fd, &tio);
+        /* set up serial port for non-canonical, dumb byte streaming */
+        tio.c_iflag &= ~(IGNBRK | BRKINT | IGNPAR | PARMRK | INPCK | ISTRIP |
+                         INLCR | IGNCR | ICRNL | IXON | IXOFF | IXANY |
+                         IMAXBEL);
+        tio.c_oflag = 0;
+        tio.c_lflag = 0;
+        tio.c_cflag |= QGA_BAUDRATE_DEFAULT;
+        /* 1 available byte min or reads will block (we'll set non-blocking
+         * elsewhere, else we have to deal with read()=0 instead)
+         */
+        tio.c_cc[VMIN] = 1;
+        tio.c_cc[VTIME] = 0;
+        /* flush everything waiting for read/xmit, it's garbage at this point */
+        tcflush(fd, TCIFLUSH);
+        tcsetattr(fd, TCSANOW, &tio);
+        ret = conn_channel_add(s, fd);
+        if (ret) {
+            g_error("error adding channel to main loop");
+        }
+    } else if (strcmp(s->method, "unix-listen") == 0) {
+        fd = unix_listen(s->path, NULL, strlen(s->path));
+        if (fd == -1) {
+            g_error("error opening path: %s", strerror(errno));
+        }
+        ret = listen_channel_add(s, fd);
+        if (ret) {
+            g_error("error binding/listening to specified socket");
+        }
+    } else {
+        g_error("unsupported channel method/type: %s", s->method);
+    }
+
+    json_message_parser_init(&s->parser, process_event);
+    s->main_loop = g_main_loop_new(NULL, false);
+}
+
+int main(int argc, char **argv)
+{
+    const char *sopt = "hVvdc:p:l:f:";
+    const char *method = NULL, *path = NULL, *pidfile = QGA_PIDFILE_DEFAULT;
+    struct option lopt[] = {
+        { "help", 0, NULL, 'h' },
+        { "version", 0, NULL, 'V' },
+        { "logfile", 0, NULL, 'l' },
+        { "pidfile", 0, NULL, 'f' },
+        { "verbose", 0, NULL, 'v' },
+        { "channel", 0, NULL, 'c' },
+        { "path", 0, NULL, 'p' },
+        { "daemonize", 0, NULL, 'd' },
+        { NULL, 0, NULL, 0 }
+    };
+    int opt_ind = 0, ch, daemonize = 0;
+    GLogLevelFlags log_level = G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL;
+    FILE *log_file = stderr;
+    GAState *s;
+
+    g_type_init();
+    g_thread_init(NULL);
+
+    while ((ch = getopt_long(argc, argv, sopt, lopt, &opt_ind)) != -1) {
+        switch (ch) {
+        case 'c':
+            method = optarg;
+            break;
+        case 'p':
+            path = optarg;
+            break;
+        case 'l':
+            log_file = fopen(optarg, "a");
+            if (!log_file) {
+                g_error("unable to open specified log file: %s",
+                        strerror(errno));
+            }
+            break;
+        case 'f':
+            pidfile = optarg;
+            break;
+        case 'v':
+            /* enable all log levels */
+            log_level = G_LOG_LEVEL_MASK;
+            break;
+        case 'V':
+            printf("QEMU Guest Agent %s\n", QGA_VERSION);
+            return 0;
+        case 'd':
+            daemonize = 1;
+            break;
+        case 'h':
+            usage(argv[0]);
+            return 0;
+        case '?':
+            g_error("Unknown option, try '%s --help' for more information.",
+                    argv[0]);
+        }
+    }
+
+    if (daemonize) {
+        g_debug("starting daemon");
+        become_daemon(pidfile);
+    }
+
+    s = g_malloc0(sizeof(GAState));
+    s->conn_id = 0;
+    s->conn_channel = NULL;
+    s->path = path;
+    s->method = method;
+    s->command_state = ga_command_state_new();
+    ga_command_state_init(s, s->command_state);
+    ga_command_state_init_all(s->command_state);
+    s->log_file = log_file;
+    s->log_level = log_level;
+    g_log_set_default_handler(ga_log, s);
+    g_log_set_fatal_mask(NULL, G_LOG_LEVEL_ERROR);
+    s->logging_enabled = true;
+
+    module_call_init(MODULE_INIT_QAPI);
+    init_guest_agent(s);
+
+    g_main_loop_run(s->main_loop);
+
+    ga_command_state_cleanup_all(s->command_state);
+
+    return 0;
+}
qga/guest-agent-core.h +0 -0
index 688f120..66d1729 100644

No license text.

Quoted 15 lines
qga/guest-agent-core.h +4 -0
@@ -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);
#7 lore raw

Re: [Qemu-devel] [PATCH v5 2/5] guest agent: qemu-ga daemon

Quoted 66 lines · Luiz Capitulino
Quoted 7 lines · On Tue, 14 Jun 2011 15:06:22 -0500 Michael Roth<[email protected]>…

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:

Quoted 8 lines

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

Would be nice to have a more complete description, like explaining how to do a simple test.

And this can't be built...

Quoted 42 lines
---
  qemu-ga.c              |  631 ++++++++++++++++++++++++++++++++++++++++++++++++
  qga/guest-agent-core.h |    4 +
  2 files changed, 635 insertions(+), 0 deletions(-)

create mode 100644 qemu-ga.c

qemu-ga.c new file +30 -0
new file mode 100644
index 0000000..df08d8c
@@ -0,0 +1,631 @@
+/*
+ * QEMU Guest Agent
+ *
+ * Copyright IBM Corp. 2011
+ *
+ * Authors:
+ *  Adam Litke<[email protected]>
+ *  Michael Roth<[email protected]>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+#include<stdlib.h>
+#include<stdio.h>
+#include<stdbool.h>
+#include<glib.h>
+#include<gio/gio.h>
+#include<getopt.h>
+#include<termios.h>
+#include<syslog.h>
+#include "qemu_socket.h"
+#include "json-streamer.h"
+#include "json-parser.h"
+#include "qint.h"
+#include "qjson.h"
+#include "qga/guest-agent-core.h"
+#include "qga-qmp-commands.h"
+#include "module.h"
+
+#define QGA_VIRTIO_PATH_DEFAULT "/dev/virtio-ports/org.qemu.guest_agent"
Quoted 9 lines

qemu-ga.c line 36

Quoted 6 lines
qemu-ga.c new file +6 -0
+#define QGA_PIDFILE_DEFAULT "/var/run/qemu-va.pid"
+#define QGA_BAUDRATE_DEFAULT B38400 /* for isa-serial channels */
+#define QGA_TIMEOUT_DEFAULT 30*1000 /* ms */
+
+struct GAState {
+    const char *proxy_path;

Where is this used?

Nowhere actually. Will remove.

Quoted 78 lines
Quoted 63 lines
qemu-ga.c new file +63 -0
+    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 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<[email protected]>\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;
+}

qemu-ga.c line 109

Quoted 10 lines
qemu-ga.c new file +10 -0
+
+void ga_disable_logging(GAState *s)
+{
+    s->logging_enabled = false;
+}
+
+void ga_enable_logging(GAState *s)
+{
+    s->logging_enabled = true;
+}

Just to check I got this right, this is needed because of the fsfreeze command, correct? Isn't it better to have a more descriptive name, like fsfrozen?

Quoted 4 lines

First I thought this was about a log file. Then I realized this was probably about letting the user log in, but we don't seem to have this functionality so I got confused.

Yup, this is currently due to fsfreeze support. From the perspective of the fsfreeze command the explicit "is_frozen" check makes more sense, but the reason it affects other RPCs is because because we can't log stuff in that state. If an RPC shoots itself in the foot by writing to a frozen filesystem we don't really care so much, and up until recently that case was handled with a pthread_cancel timeout mechanism (was removed for the time being, will re-implement using a child process most likely).

What we don't want to do is give a host a way to bypass the expectation we set for guest owners by allowing RPCs to be logged. So that's what the check is based on, rather than lower level details like *why* logging is disabled. Also, I'd really like to avoid a precedence where a single RPC can place restrictions on all the others, so the logging aspect seemed general enough that it doesn't necessarily provide that precedence.

This has come up a few times without any real consensus. I can probably go either way, but I think the check for logging is easier to set expectations with: "if logging is important from an auditing perspective, don't execute this if logging is disabled". beyond that, same behavior/symantics you'd get with anything that causes a write() on a filesystem in a frozen state: block.

Quoted 41 lines
Quoted 28 lines
qemu-ga.c new file +28 -0
+
+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;

qemu-ga.c line 147

Quoted 10 lines
qemu-ga.c new file +10 -0
+
+    pid = fork();
+    if (pid<  0) {
+        exit(EXIT_FAILURE);
+    }
+    if (pid>  0) {
+        exit(EXIT_SUCCESS);
+    }
+
+    pidfd = open(pidfile, O_CREAT|O_RDWR, S_IRUSR|S_IWUSR);

O_WRONLY is enough.

Quoted 7 lines

qemu-ga.c line 150

Quoted 3 lines
qemu-ga.c new file +3 -0
+    if (!pidfd || lockf(pidfd, F_TLOCK, 0)) {
+        g_error("Cannot lock pid file");
+    }

Are you sure we need lockf() here? I think using O_EXCL is enough for pid files.

O_EXCL + O_CREAT? Wouldn't we have issues if the pid file wasn't cleaned up from a previous run?

Quoted 18 lines

qemu-ga.c line 154

Quoted 4 lines
qemu-ga.c new file +4 -0
+
+    if (ftruncate(pidfd, 0) || lseek(pidfd, 0, SEEK_SET)) {
+        g_critical("Cannot truncate pid file");
+        goto fail;

You can use O_TRUNC and the file pointer is already positioned at the beginning of the file, no need to call lseek().

qemu-ga.c line 163

Quoted 9 lines
qemu-ga.c new file +9 -0
+    }
+    if (asprintf(&pidstr, "%d", getpid()) == -1) {
+        g_critical("Cannot allocate memory");
+        goto fail;
+    }
+    if (write(pidfd, pidstr, strlen(pidstr)) != strlen(pidstr)) {
+        g_critical("Failed to write pid file");
+        goto fail;
+    }
Quoted 3 lines

You're not freeing pidstr, nor closing the file (although I think you do this because of the lockf() call()).

pidstr gets free'd in the fail: block. And yeah, closing would give up the lock prematurely.

Quoted 34 lines
Quoted 9 lines
qemu-ga.c new file +9 -0
+
+    umask(0);
+    sid = setsid();
+    if (sid<  0) {
+        goto fail;
+    }
+    if ((chdir("/"))<  0) {
+        goto fail;
+    }

qemu-ga.c line 182

Quoted 10 lines
qemu-ga.c new file +10 -0
+
+    close(STDIN_FILENO);
+    close(STDOUT_FILENO);
+    close(STDERR_FILENO);
+    return;
+
+fail:
+    if (pidstr) {
+        free(pidstr);
+    }

This check is not needed.

Quoted 12 lines
qemu-ga.c new file +12 -0
+    unlink(pidfile);
+    g_error("failed to daemonize");
+}
+
+static int conn_channel_send_payload(GIOChannel *channel, QObject *payload)
+{
+    gsize count, written = 0;
+    int ret;
+    const char *buf;
+    QString *payload_qstr;
+    GIOStatus status;
+    GError *err = NULL;
Quoted 9 lines

qemu-ga.c line 199

Quoted 5 lines
qemu-ga.c new file +5 -0
+
+    if (!payload || !channel) {
+        ret = -EINVAL;
+        goto out;
+    }

Just do "return -EINVAL" instead of using goto, but I wonder if this should be an assert() instead.

Yah, looks like it.

Quoted 79 lines

qemu-ga.c line 205

Quoted 6 lines
qemu-ga.c new file +6 -0
+
+    payload_qstr = qobject_to_json(payload);
+    if (!payload_qstr) {
+        ret = -EINVAL;
+        goto out;
+    }

return -EINVAL.

Quoted 19 lines
qemu-ga.c new file +19 -0
+
+    buf = qstring_get_str(payload_qstr);
+    count = strlen(buf);
+
+    while (count) {
+        g_debug("sending data, count: %d", (int)count);
+        status = g_io_channel_write_chars(channel, buf, count,&written,&err);
+        if (err != NULL) {
+            g_warning("error writing payload to channel: %s", err->message);
+            ret = err->code;
+            goto out_free;
+        }
+        if (status == G_IO_STATUS_NORMAL) {
+            count -= written;
+        } else if (status == G_IO_STATUS_ERROR || status == G_IO_STATUS_EOF) {
+            ret = -EPIPE;
+            goto out_free;
+        }
+    }

qemu-ga.c line 234

Quoted 10 lines
qemu-ga.c new file +10 -0
+
+    status = g_io_channel_write_chars(channel, (char *)"\n", 1,&written,&err);
+    if (err != NULL) {
+        g_warning("error sending newline: %s", err->message);
+        ret = err->code;
+        goto out_free;
+    } else if (status == G_IO_STATUS_ERROR || status == G_IO_STATUS_EOF) {
+        ret = -EPIPE;
+        goto out_free;
+    }

This wants to be a function.

Quoted 5 lines
qemu-ga.c new file +5 -0
+
+    g_io_channel_flush(channel,&err);
+    if (err != NULL) {
+        g_warning("error flushing payload: %s", err->message);
+        ret = err->code;

qemu-ga.c line 249

Quoted 10 lines
qemu-ga.c new file +10 -0
+        goto out_free;
+    }
+
+    ret = 0;
+
+out_free:
+    QDECREF(payload_qstr);
+    if (err != NULL) {
+        g_error_free(err);
+    }

No need to check against NULL.

Quoted 5 lines
qemu-ga.c new file +5 -0
+out:
+    return ret;
+}
+
+static void process_command(GAState *s, QDict *req)

qemu-ga.c line 264

Quoted 10 lines
qemu-ga.c new file +10 -0
+{
+    QObject *rsp = NULL;
+    Error *err = NULL;
+    int ret;
+
+    g_assert(req);
+    g_debug("processing command");
+    rsp = qmp_dispatch(QOBJECT(req));
+    if (rsp) {
+        if (err) {

Apart from its initialization, 'err' is read only. Either, you want to use qmp_dispatch_err() here or you have to modify qmp_dispatch() to also accept an Error argument.

Quoted 3 lines

If you want to send a "return" or "error" QMP kind of response, then you have to do the latter.

qmp_dispatch() does the Error -> QMP error conversion. The unused err is cruft from when a seperate worker thread did the dispatch, will remove it.

Quoted 34 lines
Quoted 19 lines
qemu-ga.c new file +19 -0
+            g_warning("command failed: %s", error_get_pretty(err));
+        }
+        ret = conn_channel_send_payload(s->conn_channel, rsp);
+        if (ret) {
+            g_warning("error sending payload: %s", strerror(ret));
+        }
+        qobject_decref(rsp);
+    } else {
+        g_warning("error getting response");
+        if (err) {
+            g_warning("dispatch failed: %s", error_get_pretty(err));
+        }
+    }
+}
+
+/* handle requests/control events coming in over the channel */
+static void process_event(JSONMessageParser *parser, QList *tokens)
+{
+    GAState *s = container_of(parser, GAState, parser);

qemu-ga.c line 293

Quoted 10 lines
qemu-ga.c new file +10 -0
+    QObject *obj;
+    QDict *qdict;
+    Error *err = NULL;
+
+    g_assert(s&&  parser);
+
+    g_debug("process_event: called");
+    obj = json_parser_parse_err(tokens, NULL,&err);
+    if (!obj || qobject_type(obj) != QTYPE_QDICT) {
+        g_warning("failed to parse event");

Two possible leaks here. You have to call qobject_decref() if obj != NULL and there's a missing call to error_free(). But you don't use 'err' anyway, so you could pass NULL there.

Quoted 3 lines

Oh, btw, the guest agent doesn't seem to report errors back to its client... Not even json error messages, is this intended?

Hmm, yes it's intended, but I probably should send those to the client...

You do get non-parse errors though: missing arguments, etc.

I'll fix this to do what QMP does here.

Quoted 364 lines

qemu-ga.c line 299

Quoted 6 lines
qemu-ga.c new file +6 -0
+        return;
+    } else {
+        g_debug("parse successful");
+        qdict = qobject_to_qdict(obj);
+        g_assert(qdict);
+    }

Superfluous else clause.

qga/guest-agent-core.h

Quoted 336 lines
qemu-ga.c new file +332 -0
+
+    /* 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, listen_fd=0
+ * indicates we should use the existing s->listen_channel
+ */
+static int listen_channel_add(GAState *s, int listen_fd)
+{
+    GError *err = NULL;
+    guint listen_id;
+
+    if (listen_fd) {
+        s->listen_channel = g_io_channel_unix_new(listen_fd);
+        if (s->listen_sock) {
+            g_object_unref(s->listen_sock);
+        }
+        s->listen_sock = g_socket_new_from_fd(listen_fd,&err);
+        if (err != NULL) {
+            g_warning("error converting fd to gsocket: %s", err->message);
+            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);
+    } else if (strcmp(s->method, "virtio-serial") == 0) {
+        /* we spin on EOF for virtio-serial, so back off a bit. also,
+         * dont close the connection in this case, it'll resume normal
+         * operation when another process connects to host chardev
+         */
+        usleep(100*1000);
+        goto out_noclose;
+    }
+    g_io_channel_unref(s->conn_channel);
+    s->conn_channel = NULL;
+    s->conn_id = 0;
+out_noclose:
+    return;
+}
+
+static void init_guest_agent(GAState *s)
+{
+    struct termios tio;
+    int ret, fd;
+
+    if (s->method == NULL) {
+        /* try virtio-serial as our default */
+        s->method = "virtio-serial";
+    }
+
+    if (s->path == NULL) {
+        if (strcmp(s->method, "virtio-serial") != 0) {
+            g_error("must specify a path for this channel");
+        }
+        /* try the default path for the virtio-serial port */
+        s->path = QGA_VIRTIO_PATH_DEFAULT;
+    }
+
+    if (strcmp(s->method, "virtio-serial") == 0) {
+        s->virtio = true;
+        fd = qemu_open(s->path, O_RDWR);
+        if (fd == -1) {
+            g_error("error opening channel: %s", strerror(errno));
+        }
+        ret = fcntl(fd, F_GETFL);
+        if (ret<  0) {
+            g_error("error getting channel flags: %s", strerror(errno));
+        }
+        ret = fcntl(fd, F_SETFL, ret | O_NONBLOCK | O_ASYNC);
+        if (ret<  0) {
+            g_error("error setting channel flags: %s", strerror(errno));
+        }
+        ret = conn_channel_add(s, fd);
+        if (ret) {
+            g_error("error adding channel to main loop");
+        }
+    } else if (strcmp(s->method, "isa-serial") == 0) {
+        fd = qemu_open(s->path, O_RDWR | O_NOCTTY);
+        if (fd == -1) {
+            g_error("error opening channel: %s", strerror(errno));
+        }
+        tcgetattr(fd,&tio);
+        /* set up serial port for non-canonical, dumb byte streaming */
+        tio.c_iflag&= ~(IGNBRK | BRKINT | IGNPAR | PARMRK | INPCK | ISTRIP |
+                         INLCR | IGNCR | ICRNL | IXON | IXOFF | IXANY |
+                         IMAXBEL);
+        tio.c_oflag = 0;
+        tio.c_lflag = 0;
+        tio.c_cflag |= QGA_BAUDRATE_DEFAULT;
+        /* 1 available byte min or reads will block (we'll set non-blocking
+         * elsewhere, else we have to deal with read()=0 instead)
+         */
+        tio.c_cc[VMIN] = 1;
+        tio.c_cc[VTIME] = 0;
+        /* flush everything waiting for read/xmit, it's garbage at this point */
+        tcflush(fd, TCIFLUSH);
+        tcsetattr(fd, TCSANOW,&tio);
+        ret = conn_channel_add(s, fd);
+        if (ret) {
+            g_error("error adding channel to main loop");
+        }
+    } else if (strcmp(s->method, "unix-listen") == 0) {
+        fd = unix_listen(s->path, NULL, strlen(s->path));
+        if (fd == -1) {
+            g_error("error opening path: %s", strerror(errno));
+        }
+        ret = listen_channel_add(s, fd);
+        if (ret) {
+            g_error("error binding/listening to specified socket");
+        }
+    } else {
+        g_error("unsupported channel method/type: %s", s->method);
+    }
+
+    json_message_parser_init(&s->parser, process_event);
+    s->main_loop = g_main_loop_new(NULL, false);
+}
+
+int main(int argc, char **argv)
+{
+    const char *sopt = "hVvdc:p:l:f:";
+    const char *method = NULL, *path = NULL, *pidfile = QGA_PIDFILE_DEFAULT;
+    struct option lopt[] = {
+        { "help", 0, NULL, 'h' },
+        { "version", 0, NULL, 'V' },
+        { "logfile", 0, NULL, 'l' },
+        { "pidfile", 0, NULL, 'f' },
+        { "verbose", 0, NULL, 'v' },
+        { "channel", 0, NULL, 'c' },
+        { "path", 0, NULL, 'p' },
+        { "daemonize", 0, NULL, 'd' },
+        { NULL, 0, NULL, 0 }
+    };
+    int opt_ind = 0, ch, daemonize = 0;
+    GLogLevelFlags log_level = G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL;
+    FILE *log_file = stderr;
+    GAState *s;
+
+    g_type_init();
+    g_thread_init(NULL);
+
+    while ((ch = getopt_long(argc, argv, sopt, lopt,&opt_ind)) != -1) {
+        switch (ch) {
+        case 'c':
+            method = optarg;
+            break;
+        case 'p':
+            path = optarg;
+            break;
+        case 'l':
+            log_file = fopen(optarg, "a");
+            if (!log_file) {
+                g_error("unable to open specified log file: %s",
+                        strerror(errno));
+            }
+            break;
+        case 'f':
+            pidfile = optarg;
+            break;
+        case 'v':
+            /* enable all log levels */
+            log_level = G_LOG_LEVEL_MASK;
+            break;
+        case 'V':
+            printf("QEMU Guest Agent %s\n", QGA_VERSION);
+            return 0;
+        case 'd':
+            daemonize = 1;
+            break;
+        case 'h':
+            usage(argv[0]);
+            return 0;
+        case '?':
+            g_error("Unknown option, try '%s --help' for more information.",
+                    argv[0]);
+        }
+    }
+
+    if (daemonize) {
+        g_debug("starting daemon");
+        become_daemon(pidfile);
+    }
+
+    s = g_malloc0(sizeof(GAState));
+    s->conn_id = 0;
+    s->conn_channel = NULL;
+    s->path = path;
+    s->method = method;
+    s->command_state = ga_command_state_new();
+    ga_command_state_init(s, s->command_state);
+    ga_command_state_init_all(s->command_state);
+    s->log_file = log_file;
+    s->log_level = log_level;
+    g_log_set_default_handler(ga_log, s);
+    g_log_set_fatal_mask(NULL, G_LOG_LEVEL_ERROR);
+    s->logging_enabled = true;
+
+    module_call_init(MODULE_INIT_QAPI);
+    init_guest_agent(s);
+
+    g_main_loop_run(s->main_loop);
+
+    ga_command_state_cleanup_all(s->command_state);
+
+    return 0;
+}
qga/guest-agent-core.h +0 -0
index 688f120..66d1729 100644

No license text.

Quoted 15 lines
qga/guest-agent-core.h +4 -0
@@ -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);
#8 lore raw

Re: [Qemu-devel] [PATCH v5 2/5] guest agent: qemu-ga daemon

Quoted 179 lines · On Fri, 17 Jun 2011 14:21:31 -0500 Michael Roth <[email protected]>…
Quoted 66 lines · Luiz Capitulino
Quoted 7 lines · On Tue, 14 Jun 2011 15:06:22 -0500 Michael Roth<[email protected]>…

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:

Quoted 8 lines

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

Would be nice to have a more complete description, like explaining how to do a simple test.

And this can't be built...

Quoted 42 lines
---
  qemu-ga.c              |  631 ++++++++++++++++++++++++++++++++++++++++++++++++
  qga/guest-agent-core.h |    4 +
  2 files changed, 635 insertions(+), 0 deletions(-)

create mode 100644 qemu-ga.c

qemu-ga.c new file +30 -0
new file mode 100644
index 0000000..df08d8c
@@ -0,0 +1,631 @@
+/*
+ * QEMU Guest Agent
+ *
+ * Copyright IBM Corp. 2011
+ *
+ * Authors:
+ *  Adam Litke<[email protected]>
+ *  Michael Roth<[email protected]>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+#include<stdlib.h>
+#include<stdio.h>
+#include<stdbool.h>
+#include<glib.h>
+#include<gio/gio.h>
+#include<getopt.h>
+#include<termios.h>
+#include<syslog.h>
+#include "qemu_socket.h"
+#include "json-streamer.h"
+#include "json-parser.h"
+#include "qint.h"
+#include "qjson.h"
+#include "qga/guest-agent-core.h"
+#include "qga-qmp-commands.h"
+#include "module.h"
+
+#define QGA_VIRTIO_PATH_DEFAULT "/dev/virtio-ports/org.qemu.guest_agent"
Quoted 9 lines

qemu-ga.c line 36

Quoted 6 lines
qemu-ga.c new file +6 -0
+#define QGA_PIDFILE_DEFAULT "/var/run/qemu-va.pid"
+#define QGA_BAUDRATE_DEFAULT B38400 /* for isa-serial channels */
+#define QGA_TIMEOUT_DEFAULT 30*1000 /* ms */
+
+struct GAState {
+    const char *proxy_path;

Where is this used?

Nowhere actually. Will remove.

Quoted 78 lines
Quoted 63 lines
qemu-ga.c new file +63 -0
+    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 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<[email protected]>\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;
+}

qemu-ga.c line 109

Quoted 10 lines
qemu-ga.c new file +10 -0
+
+void ga_disable_logging(GAState *s)
+{
+    s->logging_enabled = false;
+}
+
+void ga_enable_logging(GAState *s)
+{
+    s->logging_enabled = true;
+}

Just to check I got this right, this is needed because of the fsfreeze command, correct? Isn't it better to have a more descriptive name, like fsfrozen?

Quoted 4 lines

First I thought this was about a log file. Then I realized this was probably about letting the user log in, but we don't seem to have this functionality so I got confused.

Yup, this is currently due to fsfreeze support. From the perspective of the fsfreeze command the explicit "is_frozen" check makes more sense, but the reason it affects other RPCs is because because we can't log stuff in that state. If an RPC shoots itself in the foot by writing to a frozen filesystem we don't really care so much, and up until recently that case was handled with a pthread_cancel timeout mechanism (was removed for the time being, will re-implement using a child process most likely).

What we don't want to do is give a host a way to bypass the expectation we set for guest owners by allowing RPCs to be logged. So that's what the check is based on, rather than lower level details like *why* logging is disabled. Also, I'd really like to avoid a precedence where a single RPC can place restrictions on all the others, so the logging aspect seemed general enough that it doesn't necessarily provide that precedence.

Quoted 6 lines

This has come up a few times without any real consensus. I can probably go either way, but I think the check for logging is easier to set expectations with: "if logging is important from an auditing perspective, don't execute this if logging is disabled". beyond that, same behavior/symantics you'd get with anything that causes a write() on a filesystem in a frozen state: block.

While I understand your arguments, I still find the current behavior
confusing: you issue a guest-open-file command and get a QgaLoggingFailed error. The first question is: what does a log write fail have to do with me opening a file? The second question is: what should I do? Try again? Give up?

IMHO, making fsfrozen a global state makes more sense. It's the true guest state after all. This way a client will clearly know what's happening and what it has to do in order to make its command successful.

Another possible solution is to have the same semantics of a non-blocking I/O, that's, return EWOULDBLOCK. I find this less confusing than the current error.

Quoted 46 lines
Quoted 46 lines
Quoted 29 lines
qemu-ga.c new file +28 -0
+
+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;

qemu-ga.c line 147

Quoted 10 lines
qemu-ga.c new file +10 -0
+
+    pid = fork();
+    if (pid<  0) {
+        exit(EXIT_FAILURE);
+    }
+    if (pid>  0) {
+        exit(EXIT_SUCCESS);
+    }
+
+    pidfd = open(pidfile, O_CREAT|O_RDWR, S_IRUSR|S_IWUSR);

O_WRONLY is enough.

Quoted 3 lines
qemu-ga.c new file +3 -0
+    if (!pidfd || lockf(pidfd, F_TLOCK, 0)) {
+        g_error("Cannot lock pid file");
+    }
Quoted 6 lines
Quoted 3 lines

Are you sure we need lockf() here? I think using O_EXCL is enough for pid files.

O_EXCL + O_CREAT? Wouldn't we have issues if the pid file wasn't cleaned up from a previous run?

Yes, but that's the intention (and that's how most daemons work afaik). The only reason for a daemon not to cleanup its pid file is when it exists abnormally. If this happens, then only the sysadmin can ensure that it's safe to run another daemon instance.

We could add a -r (--override-existing-pid-file) option, but we should not do it silently.

Btw, we need to implement a signal handler for SIGTERM.

Quoted 18 lines
Quoted 18 lines

qemu-ga.c line 154

Quoted 4 lines
qemu-ga.c new file +4 -0
+
+    if (ftruncate(pidfd, 0) || lseek(pidfd, 0, SEEK_SET)) {
+        g_critical("Cannot truncate pid file");
+        goto fail;

You can use O_TRUNC and the file pointer is already positioned at the beginning of the file, no need to call lseek().

Quoted 9 lines
qemu-ga.c new file +9 -0
+    }
+    if (asprintf(&pidstr, "%d", getpid()) == -1) {
+        g_critical("Cannot allocate memory");
+        goto fail;
+    }
+    if (write(pidfd, pidstr, strlen(pidstr)) != strlen(pidstr)) {
+        g_critical("Failed to write pid file");
+        goto fail;
+    }
Quoted 6 lines
Quoted 3 lines

You're not freeing pidstr, nor closing the file (although I think you do this because of the lockf() call()).

pidstr gets free'd in the fail: block. And yeah, closing would give up the lock prematurely.

pidstr leaks on a successful execution.

Quoted 542 lines
Quoted 35 lines
Quoted 10 lines
qemu-ga.c new file +9 -0
+
+    umask(0);
+    sid = setsid();
+    if (sid<  0) {
+        goto fail;
+    }
+    if ((chdir("/"))<  0) {
+        goto fail;
+    }

qemu-ga.c line 182

Quoted 10 lines
qemu-ga.c new file +10 -0
+
+    close(STDIN_FILENO);
+    close(STDOUT_FILENO);
+    close(STDERR_FILENO);
+    return;
+
+fail:
+    if (pidstr) {
+        free(pidstr);
+    }

This check is not needed.

Quoted 12 lines
qemu-ga.c new file +12 -0
+    unlink(pidfile);
+    g_error("failed to daemonize");
+}
+
+static int conn_channel_send_payload(GIOChannel *channel, QObject *payload)
+{
+    gsize count, written = 0;
+    int ret;
+    const char *buf;
+    QString *payload_qstr;
+    GIOStatus status;
+    GError *err = NULL;
Quoted 9 lines

qemu-ga.c line 199

Quoted 5 lines
qemu-ga.c new file +5 -0
+
+    if (!payload || !channel) {
+        ret = -EINVAL;
+        goto out;
+    }

Just do "return -EINVAL" instead of using goto, but I wonder if this should be an assert() instead.

Yah, looks like it.

Quoted 79 lines

qemu-ga.c line 205

Quoted 6 lines
qemu-ga.c new file +6 -0
+
+    payload_qstr = qobject_to_json(payload);
+    if (!payload_qstr) {
+        ret = -EINVAL;
+        goto out;
+    }

return -EINVAL.

Quoted 19 lines
qemu-ga.c new file +19 -0
+
+    buf = qstring_get_str(payload_qstr);
+    count = strlen(buf);
+
+    while (count) {
+        g_debug("sending data, count: %d", (int)count);
+        status = g_io_channel_write_chars(channel, buf, count,&written,&err);
+        if (err != NULL) {
+            g_warning("error writing payload to channel: %s", err->message);
+            ret = err->code;
+            goto out_free;
+        }
+        if (status == G_IO_STATUS_NORMAL) {
+            count -= written;
+        } else if (status == G_IO_STATUS_ERROR || status == G_IO_STATUS_EOF) {
+            ret = -EPIPE;
+            goto out_free;
+        }
+    }

qemu-ga.c line 234

Quoted 10 lines
qemu-ga.c new file +10 -0
+
+    status = g_io_channel_write_chars(channel, (char *)"\n", 1,&written,&err);
+    if (err != NULL) {
+        g_warning("error sending newline: %s", err->message);
+        ret = err->code;
+        goto out_free;
+    } else if (status == G_IO_STATUS_ERROR || status == G_IO_STATUS_EOF) {
+        ret = -EPIPE;
+        goto out_free;
+    }

This wants to be a function.

Quoted 5 lines
qemu-ga.c new file +5 -0
+
+    g_io_channel_flush(channel,&err);
+    if (err != NULL) {
+        g_warning("error flushing payload: %s", err->message);
+        ret = err->code;

qemu-ga.c line 249

Quoted 10 lines
qemu-ga.c new file +10 -0
+        goto out_free;
+    }
+
+    ret = 0;
+
+out_free:
+    QDECREF(payload_qstr);
+    if (err != NULL) {
+        g_error_free(err);
+    }

No need to check against NULL.

Quoted 5 lines
qemu-ga.c new file +5 -0
+out:
+    return ret;
+}
+
+static void process_command(GAState *s, QDict *req)

qemu-ga.c line 264

Quoted 10 lines
qemu-ga.c new file +10 -0
+{
+    QObject *rsp = NULL;
+    Error *err = NULL;
+    int ret;
+
+    g_assert(req);
+    g_debug("processing command");
+    rsp = qmp_dispatch(QOBJECT(req));
+    if (rsp) {
+        if (err) {

Apart from its initialization, 'err' is read only. Either, you want to use qmp_dispatch_err() here or you have to modify qmp_dispatch() to also accept an Error argument.

Quoted 3 lines

If you want to send a "return" or "error" QMP kind of response, then you have to do the latter.

qmp_dispatch() does the Error -> QMP error conversion. The unused err is cruft from when a seperate worker thread did the dispatch, will remove it.

Quoted 34 lines
Quoted 19 lines
qemu-ga.c new file +19 -0
+            g_warning("command failed: %s", error_get_pretty(err));
+        }
+        ret = conn_channel_send_payload(s->conn_channel, rsp);
+        if (ret) {
+            g_warning("error sending payload: %s", strerror(ret));
+        }
+        qobject_decref(rsp);
+    } else {
+        g_warning("error getting response");
+        if (err) {
+            g_warning("dispatch failed: %s", error_get_pretty(err));
+        }
+    }
+}
+
+/* handle requests/control events coming in over the channel */
+static void process_event(JSONMessageParser *parser, QList *tokens)
+{
+    GAState *s = container_of(parser, GAState, parser);

qemu-ga.c line 293

Quoted 10 lines
qemu-ga.c new file +10 -0
+    QObject *obj;
+    QDict *qdict;
+    Error *err = NULL;
+
+    g_assert(s&&  parser);
+
+    g_debug("process_event: called");
+    obj = json_parser_parse_err(tokens, NULL,&err);
+    if (!obj || qobject_type(obj) != QTYPE_QDICT) {
+        g_warning("failed to parse event");

Two possible leaks here. You have to call qobject_decref() if obj != NULL and there's a missing call to error_free(). But you don't use 'err' anyway, so you could pass NULL there.

Quoted 3 lines

Oh, btw, the guest agent doesn't seem to report errors back to its client... Not even json error messages, is this intended?

Hmm, yes it's intended, but I probably should send those to the client...

You do get non-parse errors though: missing arguments, etc.

I'll fix this to do what QMP does here.

Quoted 364 lines

qemu-ga.c line 299

Quoted 6 lines
qemu-ga.c new file +6 -0
+        return;
+    } else {
+        g_debug("parse successful");
+        qdict = qobject_to_qdict(obj);
+        g_assert(qdict);
+    }

Superfluous else clause.

qga/guest-agent-core.h

Quoted 336 lines
qemu-ga.c new file +332 -0
+
+    /* 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, listen_fd=0
+ * indicates we should use the existing s->listen_channel
+ */
+static int listen_channel_add(GAState *s, int listen_fd)
+{
+    GError *err = NULL;
+    guint listen_id;
+
+    if (listen_fd) {
+        s->listen_channel = g_io_channel_unix_new(listen_fd);
+        if (s->listen_sock) {
+            g_object_unref(s->listen_sock);
+        }
+        s->listen_sock = g_socket_new_from_fd(listen_fd,&err);
+        if (err != NULL) {
+            g_warning("error converting fd to gsocket: %s", err->message);
+            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);
+    } else if (strcmp(s->method, "virtio-serial") == 0) {
+        /* we spin on EOF for virtio-serial, so back off a bit. also,
+         * dont close the connection in this case, it'll resume normal
+         * operation when another process connects to host chardev
+         */
+        usleep(100*1000);
+        goto out_noclose;
+    }
+    g_io_channel_unref(s->conn_channel);
+    s->conn_channel = NULL;
+    s->conn_id = 0;
+out_noclose:
+    return;
+}
+
+static void init_guest_agent(GAState *s)
+{
+    struct termios tio;
+    int ret, fd;
+
+    if (s->method == NULL) {
+        /* try virtio-serial as our default */
+        s->method = "virtio-serial";
+    }
+
+    if (s->path == NULL) {
+        if (strcmp(s->method, "virtio-serial") != 0) {
+            g_error("must specify a path for this channel");
+        }
+        /* try the default path for the virtio-serial port */
+        s->path = QGA_VIRTIO_PATH_DEFAULT;
+    }
+
+    if (strcmp(s->method, "virtio-serial") == 0) {
+        s->virtio = true;
+        fd = qemu_open(s->path, O_RDWR);
+        if (fd == -1) {
+            g_error("error opening channel: %s", strerror(errno));
+        }
+        ret = fcntl(fd, F_GETFL);
+        if (ret<  0) {
+            g_error("error getting channel flags: %s", strerror(errno));
+        }
+        ret = fcntl(fd, F_SETFL, ret | O_NONBLOCK | O_ASYNC);
+        if (ret<  0) {
+            g_error("error setting channel flags: %s", strerror(errno));
+        }
+        ret = conn_channel_add(s, fd);
+        if (ret) {
+            g_error("error adding channel to main loop");
+        }
+    } else if (strcmp(s->method, "isa-serial") == 0) {
+        fd = qemu_open(s->path, O_RDWR | O_NOCTTY);
+        if (fd == -1) {
+            g_error("error opening channel: %s", strerror(errno));
+        }
+        tcgetattr(fd,&tio);
+        /* set up serial port for non-canonical, dumb byte streaming */
+        tio.c_iflag&= ~(IGNBRK | BRKINT | IGNPAR | PARMRK | INPCK | ISTRIP |
+                         INLCR | IGNCR | ICRNL | IXON | IXOFF | IXANY |
+                         IMAXBEL);
+        tio.c_oflag = 0;
+        tio.c_lflag = 0;
+        tio.c_cflag |= QGA_BAUDRATE_DEFAULT;
+        /* 1 available byte min or reads will block (we'll set non-blocking
+         * elsewhere, else we have to deal with read()=0 instead)
+         */
+        tio.c_cc[VMIN] = 1;
+        tio.c_cc[VTIME] = 0;
+        /* flush everything waiting for read/xmit, it's garbage at this point */
+        tcflush(fd, TCIFLUSH);
+        tcsetattr(fd, TCSANOW,&tio);
+        ret = conn_channel_add(s, fd);
+        if (ret) {
+            g_error("error adding channel to main loop");
+        }
+    } else if (strcmp(s->method, "unix-listen") == 0) {
+        fd = unix_listen(s->path, NULL, strlen(s->path));
+        if (fd == -1) {
+            g_error("error opening path: %s", strerror(errno));
+        }
+        ret = listen_channel_add(s, fd);
+        if (ret) {
+            g_error("error binding/listening to specified socket");
+        }
+    } else {
+        g_error("unsupported channel method/type: %s", s->method);
+    }
+
+    json_message_parser_init(&s->parser, process_event);
+    s->main_loop = g_main_loop_new(NULL, false);
+}
+
+int main(int argc, char **argv)
+{
+    const char *sopt = "hVvdc:p:l:f:";
+    const char *method = NULL, *path = NULL, *pidfile = QGA_PIDFILE_DEFAULT;
+    struct option lopt[] = {
+        { "help", 0, NULL, 'h' },
+        { "version", 0, NULL, 'V' },
+        { "logfile", 0, NULL, 'l' },
+        { "pidfile", 0, NULL, 'f' },
+        { "verbose", 0, NULL, 'v' },
+        { "channel", 0, NULL, 'c' },
+        { "path", 0, NULL, 'p' },
+        { "daemonize", 0, NULL, 'd' },
+        { NULL, 0, NULL, 0 }
+    };
+    int opt_ind = 0, ch, daemonize = 0;
+    GLogLevelFlags log_level = G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL;
+    FILE *log_file = stderr;
+    GAState *s;
+
+    g_type_init();
+    g_thread_init(NULL);
+
+    while ((ch = getopt_long(argc, argv, sopt, lopt,&opt_ind)) != -1) {
+        switch (ch) {
+        case 'c':
+            method = optarg;
+            break;
+        case 'p':
+            path = optarg;
+            break;
+        case 'l':
+            log_file = fopen(optarg, "a");
+            if (!log_file) {
+                g_error("unable to open specified log file: %s",
+                        strerror(errno));
+            }
+            break;
+        case 'f':
+            pidfile = optarg;
+            break;
+        case 'v':
+            /* enable all log levels */
+            log_level = G_LOG_LEVEL_MASK;
+            break;
+        case 'V':
+            printf("QEMU Guest Agent %s\n", QGA_VERSION);
+            return 0;
+        case 'd':
+            daemonize = 1;
+            break;
+        case 'h':
+            usage(argv[0]);
+            return 0;
+        case '?':
+            g_error("Unknown option, try '%s --help' for more information.",
+                    argv[0]);
+        }
+    }
+
+    if (daemonize) {
+        g_debug("starting daemon");
+        become_daemon(pidfile);
+    }
+
+    s = g_malloc0(sizeof(GAState));
+    s->conn_id = 0;
+    s->conn_channel = NULL;
+    s->path = path;
+    s->method = method;
+    s->command_state = ga_command_state_new();
+    ga_command_state_init(s, s->command_state);
+    ga_command_state_init_all(s->command_state);
+    s->log_file = log_file;
+    s->log_level = log_level;
+    g_log_set_default_handler(ga_log, s);
+    g_log_set_fatal_mask(NULL, G_LOG_LEVEL_ERROR);
+    s->logging_enabled = true;
+
+    module_call_init(MODULE_INIT_QAPI);
+    init_guest_agent(s);
+
+    g_main_loop_run(s->main_loop);
+
+    ga_command_state_cleanup_all(s->command_state);
+
+    return 0;
+}
qga/guest-agent-core.h +0 -0
index 688f120..66d1729 100644

No license text.

Quoted 15 lines
qga/guest-agent-core.h +4 -0
@@ -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);
#9 lore raw

Re: [Qemu-devel] [PATCH v5 2/5] guest agent: qemu-ga daemon

Quoted 189 lines · Luiz Capitulino
Quoted 179 lines · On Fri, 17 Jun 2011 14:21:31 -0500 Michael Roth<[email protected]>…
Quoted 66 lines · Luiz Capitulino
Quoted 7 lines · On Tue, 14 Jun 2011 15:06:22 -0500 Michael Roth<[email protected]>…

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:

Quoted 8 lines

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

Would be nice to have a more complete description, like explaining how to do a simple test.

And this can't be built...

Quoted 42 lines
---
   qemu-ga.c              |  631 ++++++++++++++++++++++++++++++++++++++++++++++++
   qga/guest-agent-core.h |    4 +
   2 files changed, 635 insertions(+), 0 deletions(-)

create mode 100644 qemu-ga.c

qemu-ga.c new file +30 -0
new file mode 100644
index 0000000..df08d8c
@@ -0,0 +1,631 @@
+/*
+ * QEMU Guest Agent
+ *
+ * Copyright IBM Corp. 2011
+ *
+ * Authors:
+ *  Adam Litke<[email protected]>
+ *  Michael Roth<[email protected]>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+#include<stdlib.h>
+#include<stdio.h>
+#include<stdbool.h>
+#include<glib.h>
+#include<gio/gio.h>
+#include<getopt.h>
+#include<termios.h>
+#include<syslog.h>
+#include "qemu_socket.h"
+#include "json-streamer.h"
+#include "json-parser.h"
+#include "qint.h"
+#include "qjson.h"
+#include "qga/guest-agent-core.h"
+#include "qga-qmp-commands.h"
+#include "module.h"
+
+#define QGA_VIRTIO_PATH_DEFAULT "/dev/virtio-ports/org.qemu.guest_agent"
Quoted 9 lines

qemu-ga.c line 36

Quoted 6 lines
qemu-ga.c new file +6 -0
+#define QGA_PIDFILE_DEFAULT "/var/run/qemu-va.pid"
+#define QGA_BAUDRATE_DEFAULT B38400 /* for isa-serial channels */
+#define QGA_TIMEOUT_DEFAULT 30*1000 /* ms */
+
+struct GAState {
+    const char *proxy_path;

Where is this used?

Nowhere actually. Will remove.

Quoted 78 lines
Quoted 63 lines
qemu-ga.c new file +63 -0
+    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 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<[email protected]>\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;
+}

qemu-ga.c line 109

Quoted 10 lines
qemu-ga.c new file +10 -0
+
+void ga_disable_logging(GAState *s)
+{
+    s->logging_enabled = false;
+}
+
+void ga_enable_logging(GAState *s)
+{
+    s->logging_enabled = true;
+}

Just to check I got this right, this is needed because of the fsfreeze command, correct? Isn't it better to have a more descriptive name, like fsfrozen?

Quoted 4 lines

First I thought this was about a log file. Then I realized this was probably about letting the user log in, but we don't seem to have this functionality so I got confused.

Yup, this is currently due to fsfreeze support. From the perspective of the fsfreeze command the explicit "is_frozen" check makes more sense, but the reason it affects other RPCs is because because we can't log stuff in that state. If an RPC shoots itself in the foot by writing to a frozen filesystem we don't really care so much, and up until recently that case was handled with a pthread_cancel timeout mechanism (was removed for the time being, will re-implement using a child process most likely).

What we don't want to do is give a host a way to bypass the expectation we set for guest owners by allowing RPCs to be logged. So that's what the check is based on, rather than lower level details like *why* logging is disabled. Also, I'd really like to avoid a precedence where a single RPC can place restrictions on all the others, so the logging aspect seemed general enough that it doesn't necessarily provide that precedence.

Quoted 6 lines

This has come up a few times without any real consensus. I can probably go either way, but I think the check for logging is easier to set expectations with: "if logging is important from an auditing perspective, don't execute this if logging is disabled". beyond that, same behavior/symantics you'd get with anything that causes a write() on a filesystem in a frozen state: block.

Quoted 5 lines

While I understand your arguments, I still find the current behavior
confusing: you issue a guest-open-file command and get a QgaLoggingFailed error. The first question is: what does a log write fail have to do with me opening a file? The second question is: what should I do? Try again? Give up?

I agree it's a better solution for the client there, but at the same time:

guest_privileged_ping():
if fsfreeze.status == FROZEN:
syslog("privileged ping, thought you should know")
else:
return "error, filesystem frozen"

Seems silly to the client as well. "Why does a ping command care about the filesystem state?"

Inability to log is the true error. That's also the case for the guest-file-open command. There's nothing wrong with opening a read-only file on a frozen filesystem, it's the fact that we can't log the open that causes us to bail out..

So, what if we just munge the 2 to give the user the proper clues to fix things, and instead return an error like:

"Guest agent failed to log RPC (is the filesystem frozen?)"?

Quoted 641 lines

IMHO, making fsfrozen a global state makes more sense. It's the true guest state after all. This way a client will clearly know what's happening and what it has to do in order to make its command successful.

Another possible solution is to have the same semantics of a non-blocking I/O, that's, return EWOULDBLOCK. I find this less confusing than the current error.

Quoted 46 lines
Quoted 46 lines
Quoted 29 lines
qemu-ga.c new file +28 -0
+
+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;

qemu-ga.c line 147

Quoted 10 lines
qemu-ga.c new file +10 -0
+
+    pid = fork();
+    if (pid<   0) {
+        exit(EXIT_FAILURE);
+    }
+    if (pid>   0) {
+        exit(EXIT_SUCCESS);
+    }
+
+    pidfd = open(pidfile, O_CREAT|O_RDWR, S_IRUSR|S_IWUSR);

O_WRONLY is enough.

Quoted 3 lines
qemu-ga.c new file +3 -0
+    if (!pidfd || lockf(pidfd, F_TLOCK, 0)) {
+        g_error("Cannot lock pid file");
+    }
Quoted 6 lines
Quoted 3 lines

Are you sure we need lockf() here? I think using O_EXCL is enough for pid files.

O_EXCL + O_CREAT? Wouldn't we have issues if the pid file wasn't cleaned up from a previous run?

Yes, but that's the intention (and that's how most daemons work afaik). The only reason for a daemon not to cleanup its pid file is when it exists abnormally. If this happens, then only the sysadmin can ensure that it's safe to run another daemon instance.

We could add a -r (--override-existing-pid-file) option, but we should not do it silently.

Btw, we need to implement a signal handler for SIGTERM.

Quoted 18 lines
Quoted 18 lines

qemu-ga.c line 154

Quoted 4 lines
qemu-ga.c new file +4 -0
+
+    if (ftruncate(pidfd, 0) || lseek(pidfd, 0, SEEK_SET)) {
+        g_critical("Cannot truncate pid file");
+        goto fail;

You can use O_TRUNC and the file pointer is already positioned at the beginning of the file, no need to call lseek().

Quoted 9 lines
qemu-ga.c new file +9 -0
+    }
+    if (asprintf(&pidstr, "%d", getpid()) == -1) {
+        g_critical("Cannot allocate memory");
+        goto fail;
+    }
+    if (write(pidfd, pidstr, strlen(pidstr)) != strlen(pidstr)) {
+        g_critical("Failed to write pid file");
+        goto fail;
+    }
Quoted 6 lines
Quoted 3 lines

You're not freeing pidstr, nor closing the file (although I think you do this because of the lockf() call()).

pidstr gets free'd in the fail: block. And yeah, closing would give up the lock prematurely.

pidstr leaks on a successful execution.

Quoted 542 lines
Quoted 35 lines
Quoted 10 lines
qemu-ga.c new file +9 -0
+
+    umask(0);
+    sid = setsid();
+    if (sid<   0) {
+        goto fail;
+    }
+    if ((chdir("/"))<   0) {
+        goto fail;
+    }

qemu-ga.c line 182

Quoted 10 lines
qemu-ga.c new file +10 -0
+
+    close(STDIN_FILENO);
+    close(STDOUT_FILENO);
+    close(STDERR_FILENO);
+    return;
+
+fail:
+    if (pidstr) {
+        free(pidstr);
+    }

This check is not needed.

Quoted 12 lines
qemu-ga.c new file +12 -0
+    unlink(pidfile);
+    g_error("failed to daemonize");
+}
+
+static int conn_channel_send_payload(GIOChannel *channel, QObject *payload)
+{
+    gsize count, written = 0;
+    int ret;
+    const char *buf;
+    QString *payload_qstr;
+    GIOStatus status;
+    GError *err = NULL;
Quoted 9 lines

qemu-ga.c line 199

Quoted 5 lines
qemu-ga.c new file +5 -0
+
+    if (!payload || !channel) {
+        ret = -EINVAL;
+        goto out;
+    }

Just do "return -EINVAL" instead of using goto, but I wonder if this should be an assert() instead.

Yah, looks like it.

Quoted 79 lines

qemu-ga.c line 205

Quoted 6 lines
qemu-ga.c new file +6 -0
+
+    payload_qstr = qobject_to_json(payload);
+    if (!payload_qstr) {
+        ret = -EINVAL;
+        goto out;
+    }

return -EINVAL.

Quoted 19 lines
qemu-ga.c new file +19 -0
+
+    buf = qstring_get_str(payload_qstr);
+    count = strlen(buf);
+
+    while (count) {
+        g_debug("sending data, count: %d", (int)count);
+        status = g_io_channel_write_chars(channel, buf, count,&written,&err);
+        if (err != NULL) {
+            g_warning("error writing payload to channel: %s", err->message);
+            ret = err->code;
+            goto out_free;
+        }
+        if (status == G_IO_STATUS_NORMAL) {
+            count -= written;
+        } else if (status == G_IO_STATUS_ERROR || status == G_IO_STATUS_EOF) {
+            ret = -EPIPE;
+            goto out_free;
+        }
+    }

qemu-ga.c line 234

Quoted 10 lines
qemu-ga.c new file +10 -0
+
+    status = g_io_channel_write_chars(channel, (char *)"\n", 1,&written,&err);
+    if (err != NULL) {
+        g_warning("error sending newline: %s", err->message);
+        ret = err->code;
+        goto out_free;
+    } else if (status == G_IO_STATUS_ERROR || status == G_IO_STATUS_EOF) {
+        ret = -EPIPE;
+        goto out_free;
+    }

This wants to be a function.

Quoted 5 lines
qemu-ga.c new file +5 -0
+
+    g_io_channel_flush(channel,&err);
+    if (err != NULL) {
+        g_warning("error flushing payload: %s", err->message);
+        ret = err->code;

qemu-ga.c line 249

Quoted 10 lines
qemu-ga.c new file +10 -0
+        goto out_free;
+    }
+
+    ret = 0;
+
+out_free:
+    QDECREF(payload_qstr);
+    if (err != NULL) {
+        g_error_free(err);
+    }

No need to check against NULL.

Quoted 5 lines
qemu-ga.c new file +5 -0
+out:
+    return ret;
+}
+
+static void process_command(GAState *s, QDict *req)

qemu-ga.c line 264

Quoted 10 lines
qemu-ga.c new file +10 -0
+{
+    QObject *rsp = NULL;
+    Error *err = NULL;
+    int ret;
+
+    g_assert(req);
+    g_debug("processing command");
+    rsp = qmp_dispatch(QOBJECT(req));
+    if (rsp) {
+        if (err) {

Apart from its initialization, 'err' is read only. Either, you want to use qmp_dispatch_err() here or you have to modify qmp_dispatch() to also accept an Error argument.

Quoted 3 lines

If you want to send a "return" or "error" QMP kind of response, then you have to do the latter.

qmp_dispatch() does the Error -> QMP error conversion. The unused err is cruft from when a seperate worker thread did the dispatch, will remove it.

Quoted 34 lines
Quoted 19 lines
qemu-ga.c new file +19 -0
+            g_warning("command failed: %s", error_get_pretty(err));
+        }
+        ret = conn_channel_send_payload(s->conn_channel, rsp);
+        if (ret) {
+            g_warning("error sending payload: %s", strerror(ret));
+        }
+        qobject_decref(rsp);
+    } else {
+        g_warning("error getting response");
+        if (err) {
+            g_warning("dispatch failed: %s", error_get_pretty(err));
+        }
+    }
+}
+
+/* handle requests/control events coming in over the channel */
+static void process_event(JSONMessageParser *parser, QList *tokens)
+{
+    GAState *s = container_of(parser, GAState, parser);

qemu-ga.c line 293

Quoted 10 lines
qemu-ga.c new file +10 -0
+    QObject *obj;
+    QDict *qdict;
+    Error *err = NULL;
+
+    g_assert(s&&   parser);
+
+    g_debug("process_event: called");
+    obj = json_parser_parse_err(tokens, NULL,&err);
+    if (!obj || qobject_type(obj) != QTYPE_QDICT) {
+        g_warning("failed to parse event");

Two possible leaks here. You have to call qobject_decref() if obj != NULL and there's a missing call to error_free(). But you don't use 'err' anyway, so you could pass NULL there.

Quoted 3 lines

Oh, btw, the guest agent doesn't seem to report errors back to its client... Not even json error messages, is this intended?

Hmm, yes it's intended, but I probably should send those to the client...

You do get non-parse errors though: missing arguments, etc.

I'll fix this to do what QMP does here.

Quoted 364 lines

qemu-ga.c line 299

Quoted 6 lines
qemu-ga.c new file +6 -0
+        return;
+    } else {
+        g_debug("parse successful");
+        qdict = qobject_to_qdict(obj);
+        g_assert(qdict);
+    }

Superfluous else clause.

qga/guest-agent-core.h

Quoted 336 lines
qemu-ga.c new file +332 -0
+
+    /* 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, listen_fd=0
+ * indicates we should use the existing s->listen_channel
+ */
+static int listen_channel_add(GAState *s, int listen_fd)
+{
+    GError *err = NULL;
+    guint listen_id;
+
+    if (listen_fd) {
+        s->listen_channel = g_io_channel_unix_new(listen_fd);
+        if (s->listen_sock) {
+            g_object_unref(s->listen_sock);
+        }
+        s->listen_sock = g_socket_new_from_fd(listen_fd,&err);
+        if (err != NULL) {
+            g_warning("error converting fd to gsocket: %s", err->message);
+            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);
+    } else if (strcmp(s->method, "virtio-serial") == 0) {
+        /* we spin on EOF for virtio-serial, so back off a bit. also,
+         * dont close the connection in this case, it'll resume normal
+         * operation when another process connects to host chardev
+         */
+        usleep(100*1000);
+        goto out_noclose;
+    }
+    g_io_channel_unref(s->conn_channel);
+    s->conn_channel = NULL;
+    s->conn_id = 0;
+out_noclose:
+    return;
+}
+
+static void init_guest_agent(GAState *s)
+{
+    struct termios tio;
+    int ret, fd;
+
+    if (s->method == NULL) {
+        /* try virtio-serial as our default */
+        s->method = "virtio-serial";
+    }
+
+    if (s->path == NULL) {
+        if (strcmp(s->method, "virtio-serial") != 0) {
+            g_error("must specify a path for this channel");
+        }
+        /* try the default path for the virtio-serial port */
+        s->path = QGA_VIRTIO_PATH_DEFAULT;
+    }
+
+    if (strcmp(s->method, "virtio-serial") == 0) {
+        s->virtio = true;
+        fd = qemu_open(s->path, O_RDWR);
+        if (fd == -1) {
+            g_error("error opening channel: %s", strerror(errno));
+        }
+        ret = fcntl(fd, F_GETFL);
+        if (ret<   0) {
+            g_error("error getting channel flags: %s", strerror(errno));
+        }
+        ret = fcntl(fd, F_SETFL, ret | O_NONBLOCK | O_ASYNC);
+        if (ret<   0) {
+            g_error("error setting channel flags: %s", strerror(errno));
+        }
+        ret = conn_channel_add(s, fd);
+        if (ret) {
+            g_error("error adding channel to main loop");
+        }
+    } else if (strcmp(s->method, "isa-serial") == 0) {
+        fd = qemu_open(s->path, O_RDWR | O_NOCTTY);
+        if (fd == -1) {
+            g_error("error opening channel: %s", strerror(errno));
+        }
+        tcgetattr(fd,&tio);
+        /* set up serial port for non-canonical, dumb byte streaming */
+        tio.c_iflag&= ~(IGNBRK | BRKINT | IGNPAR | PARMRK | INPCK | ISTRIP |
+                         INLCR | IGNCR | ICRNL | IXON | IXOFF | IXANY |
+                         IMAXBEL);
+        tio.c_oflag = 0;
+        tio.c_lflag = 0;
+        tio.c_cflag |= QGA_BAUDRATE_DEFAULT;
+        /* 1 available byte min or reads will block (we'll set non-blocking
+         * elsewhere, else we have to deal with read()=0 instead)
+         */
+        tio.c_cc[VMIN] = 1;
+        tio.c_cc[VTIME] = 0;
+        /* flush everything waiting for read/xmit, it's garbage at this point */
+        tcflush(fd, TCIFLUSH);
+        tcsetattr(fd, TCSANOW,&tio);
+        ret = conn_channel_add(s, fd);
+        if (ret) {
+            g_error("error adding channel to main loop");
+        }
+    } else if (strcmp(s->method, "unix-listen") == 0) {
+        fd = unix_listen(s->path, NULL, strlen(s->path));
+        if (fd == -1) {
+            g_error("error opening path: %s", strerror(errno));
+        }
+        ret = listen_channel_add(s, fd);
+        if (ret) {
+            g_error("error binding/listening to specified socket");
+        }
+    } else {
+        g_error("unsupported channel method/type: %s", s->method);
+    }
+
+    json_message_parser_init(&s->parser, process_event);
+    s->main_loop = g_main_loop_new(NULL, false);
+}
+
+int main(int argc, char **argv)
+{
+    const char *sopt = "hVvdc:p:l:f:";
+    const char *method = NULL, *path = NULL, *pidfile = QGA_PIDFILE_DEFAULT;
+    struct option lopt[] = {
+        { "help", 0, NULL, 'h' },
+        { "version", 0, NULL, 'V' },
+        { "logfile", 0, NULL, 'l' },
+        { "pidfile", 0, NULL, 'f' },
+        { "verbose", 0, NULL, 'v' },
+        { "channel", 0, NULL, 'c' },
+        { "path", 0, NULL, 'p' },
+        { "daemonize", 0, NULL, 'd' },
+        { NULL, 0, NULL, 0 }
+    };
+    int opt_ind = 0, ch, daemonize = 0;
+    GLogLevelFlags log_level = G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL;
+    FILE *log_file = stderr;
+    GAState *s;
+
+    g_type_init();
+    g_thread_init(NULL);
+
+    while ((ch = getopt_long(argc, argv, sopt, lopt,&opt_ind)) != -1) {
+        switch (ch) {
+        case 'c':
+            method = optarg;
+            break;
+        case 'p':
+            path = optarg;
+            break;
+        case 'l':
+            log_file = fopen(optarg, "a");
+            if (!log_file) {
+                g_error("unable to open specified log file: %s",
+                        strerror(errno));
+            }
+            break;
+        case 'f':
+            pidfile = optarg;
+            break;
+        case 'v':
+            /* enable all log levels */
+            log_level = G_LOG_LEVEL_MASK;
+            break;
+        case 'V':
+            printf("QEMU Guest Agent %s\n", QGA_VERSION);
+            return 0;
+        case 'd':
+            daemonize = 1;
+            break;
+        case 'h':
+            usage(argv[0]);
+            return 0;
+        case '?':
+            g_error("Unknown option, try '%s --help' for more information.",
+                    argv[0]);
+        }
+    }
+
+    if (daemonize) {
+        g_debug("starting daemon");
+        become_daemon(pidfile);
+    }
+
+    s = g_malloc0(sizeof(GAState));
+    s->conn_id = 0;
+    s->conn_channel = NULL;
+    s->path = path;
+    s->method = method;
+    s->command_state = ga_command_state_new();
+    ga_command_state_init(s, s->command_state);
+    ga_command_state_init_all(s->command_state);
+    s->log_file = log_file;
+    s->log_level = log_level;
+    g_log_set_default_handler(ga_log, s);
+    g_log_set_fatal_mask(NULL, G_LOG_LEVEL_ERROR);
+    s->logging_enabled = true;
+
+    module_call_init(MODULE_INIT_QAPI);
+    init_guest_agent(s);
+
+    g_main_loop_run(s->main_loop);
+
+    ga_command_state_cleanup_all(s->command_state);
+
+    return 0;
+}
qga/guest-agent-core.h +0 -0
index 688f120..66d1729 100644

No license text.

Quoted 15 lines
qga/guest-agent-core.h +4 -0
@@ -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);
#10 lore raw

Re: [Qemu-devel] [PATCH v5 2/5] guest agent: qemu-ga daemon

Quoted 204 lines · On Fri, 17 Jun 2011 16:25:32 -0500 Michael Roth <[email protected]>…
Quoted 189 lines · Luiz Capitulino
Quoted 179 lines · On Fri, 17 Jun 2011 14:21:31 -0500 Michael Roth<[email protected]>…
Quoted 66 lines · Luiz Capitulino
Quoted 7 lines · On Tue, 14 Jun 2011 15:06:22 -0500 Michael Roth<[email protected]>…

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:

Quoted 8 lines

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

Would be nice to have a more complete description, like explaining how to do a simple test.

And this can't be built...

Quoted 42 lines
---
   qemu-ga.c              |  631 ++++++++++++++++++++++++++++++++++++++++++++++++
   qga/guest-agent-core.h |    4 +
   2 files changed, 635 insertions(+), 0 deletions(-)

create mode 100644 qemu-ga.c

qemu-ga.c new file +30 -0
new file mode 100644
index 0000000..df08d8c
@@ -0,0 +1,631 @@
+/*
+ * QEMU Guest Agent
+ *
+ * Copyright IBM Corp. 2011
+ *
+ * Authors:
+ *  Adam Litke<[email protected]>
+ *  Michael Roth<[email protected]>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+#include<stdlib.h>
+#include<stdio.h>
+#include<stdbool.h>
+#include<glib.h>
+#include<gio/gio.h>
+#include<getopt.h>
+#include<termios.h>
+#include<syslog.h>
+#include "qemu_socket.h"
+#include "json-streamer.h"
+#include "json-parser.h"
+#include "qint.h"
+#include "qjson.h"
+#include "qga/guest-agent-core.h"
+#include "qga-qmp-commands.h"
+#include "module.h"
+
+#define QGA_VIRTIO_PATH_DEFAULT "/dev/virtio-ports/org.qemu.guest_agent"
Quoted 9 lines

qemu-ga.c line 36

Quoted 6 lines
qemu-ga.c new file +6 -0
+#define QGA_PIDFILE_DEFAULT "/var/run/qemu-va.pid"
+#define QGA_BAUDRATE_DEFAULT B38400 /* for isa-serial channels */
+#define QGA_TIMEOUT_DEFAULT 30*1000 /* ms */
+
+struct GAState {
+    const char *proxy_path;

Where is this used?

Nowhere actually. Will remove.

Quoted 78 lines
Quoted 63 lines
qemu-ga.c new file +63 -0
+    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 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<[email protected]>\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;
+}

qemu-ga.c line 109

Quoted 10 lines
qemu-ga.c new file +10 -0
+
+void ga_disable_logging(GAState *s)
+{
+    s->logging_enabled = false;
+}
+
+void ga_enable_logging(GAState *s)
+{
+    s->logging_enabled = true;
+}

Just to check I got this right, this is needed because of the fsfreeze command, correct? Isn't it better to have a more descriptive name, like fsfrozen?

Quoted 4 lines

First I thought this was about a log file. Then I realized this was probably about letting the user log in, but we don't seem to have this functionality so I got confused.

Yup, this is currently due to fsfreeze support. From the perspective of the fsfreeze command the explicit "is_frozen" check makes more sense, but the reason it affects other RPCs is because because we can't log stuff in that state. If an RPC shoots itself in the foot by writing to a frozen filesystem we don't really care so much, and up until recently that case was handled with a pthread_cancel timeout mechanism (was removed for the time being, will re-implement using a child process most likely).

What we don't want to do is give a host a way to bypass the expectation we set for guest owners by allowing RPCs to be logged. So that's what the check is based on, rather than lower level details like *why* logging is disabled. Also, I'd really like to avoid a precedence where a single RPC can place restrictions on all the others, so the logging aspect seemed general enough that it doesn't necessarily provide that precedence.

Quoted 6 lines

This has come up a few times without any real consensus. I can probably go either way, but I think the check for logging is easier to set expectations with: "if logging is important from an auditing perspective, don't execute this if logging is disabled". beyond that, same behavior/symantics you'd get with anything that causes a write() on a filesystem in a frozen state: block.

Quoted 5 lines

While I understand your arguments, I still find the current behavior
confusing: you issue a guest-open-file command and get a QgaLoggingFailed error. The first question is: what does a log write fail have to do with me opening a file? The second question is: what should I do? Try again? Give up?

I agree it's a better solution for the client there, but at the same time:

guest_privileged_ping():
if fsfreeze.status == FROZEN:
syslog("privileged ping, thought you should know")
else:
return "error, filesystem frozen"

Quoted 5 lines

Seems silly to the client as well. "Why does a ping command care about the filesystem state?"

Inability to log is the true error. That's also the case for the guest-file-open command.

There's a difference. In the guest ping the inability to log is an internal agent error, it's not interesting to the client. We could just ignore the error and reply back (unless we define that guest-privileged-ping requires writing to disk).

The guest-file-write command, on the other hand, clearly requires to write to disk, so a client would expect a EWOULDBLOCK error.

EWOULDBLOCK looks good to me. We could define as general rule that commands don't block, so clients should always expect a EWOULDBLOCK.

Quoted 3 lines

There's nothing wrong with opening a read-only
file on a frozen filesystem, it's the fact that we can't log the open that causes us to bail out..

But why do we bail out? Why can't we just ignore the fact we can't log?

Quoted 4 lines

So, what if we just munge the 2 to give the user the proper clues to fix things, and instead return an error like:

"Guest agent failed to log RPC (is the filesystem frozen?)"?

The 'desc' part of an error is a human error descriptor, clients shouldn't parse it. The real error is the error class.

Quoted 642 lines
Quoted 641 lines

IMHO, making fsfrozen a global state makes more sense. It's the true guest state after all. This way a client will clearly know what's happening and what it has to do in order to make its command successful.

Another possible solution is to have the same semantics of a non-blocking I/O, that's, return EWOULDBLOCK. I find this less confusing than the current error.

Quoted 46 lines
Quoted 46 lines
Quoted 29 lines
qemu-ga.c new file +28 -0
+
+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;

qemu-ga.c line 147

Quoted 10 lines
qemu-ga.c new file +10 -0
+
+    pid = fork();
+    if (pid<   0) {
+        exit(EXIT_FAILURE);
+    }
+    if (pid>   0) {
+        exit(EXIT_SUCCESS);
+    }
+
+    pidfd = open(pidfile, O_CREAT|O_RDWR, S_IRUSR|S_IWUSR);

O_WRONLY is enough.

Quoted 3 lines
qemu-ga.c new file +3 -0
+    if (!pidfd || lockf(pidfd, F_TLOCK, 0)) {
+        g_error("Cannot lock pid file");
+    }
Quoted 6 lines
Quoted 3 lines

Are you sure we need lockf() here? I think using O_EXCL is enough for pid files.

O_EXCL + O_CREAT? Wouldn't we have issues if the pid file wasn't cleaned up from a previous run?

Yes, but that's the intention (and that's how most daemons work afaik). The only reason for a daemon not to cleanup its pid file is when it exists abnormally. If this happens, then only the sysadmin can ensure that it's safe to run another daemon instance.

We could add a -r (--override-existing-pid-file) option, but we should not do it silently.

Btw, we need to implement a signal handler for SIGTERM.

Quoted 18 lines
Quoted 18 lines

qemu-ga.c line 154

Quoted 4 lines
qemu-ga.c new file +4 -0
+
+    if (ftruncate(pidfd, 0) || lseek(pidfd, 0, SEEK_SET)) {
+        g_critical("Cannot truncate pid file");
+        goto fail;

You can use O_TRUNC and the file pointer is already positioned at the beginning of the file, no need to call lseek().

Quoted 9 lines
qemu-ga.c new file +9 -0
+    }
+    if (asprintf(&pidstr, "%d", getpid()) == -1) {
+        g_critical("Cannot allocate memory");
+        goto fail;
+    }
+    if (write(pidfd, pidstr, strlen(pidstr)) != strlen(pidstr)) {
+        g_critical("Failed to write pid file");
+        goto fail;
+    }
Quoted 6 lines
Quoted 3 lines

You're not freeing pidstr, nor closing the file (although I think you do this because of the lockf() call()).

pidstr gets free'd in the fail: block. And yeah, closing would give up the lock prematurely.

pidstr leaks on a successful execution.

Quoted 542 lines
Quoted 35 lines
Quoted 10 lines
qemu-ga.c new file +9 -0
+
+    umask(0);
+    sid = setsid();
+    if (sid<   0) {
+        goto fail;
+    }
+    if ((chdir("/"))<   0) {
+        goto fail;
+    }

qemu-ga.c line 182

Quoted 10 lines
qemu-ga.c new file +10 -0
+
+    close(STDIN_FILENO);
+    close(STDOUT_FILENO);
+    close(STDERR_FILENO);
+    return;
+
+fail:
+    if (pidstr) {
+        free(pidstr);
+    }

This check is not needed.

Quoted 12 lines
qemu-ga.c new file +12 -0
+    unlink(pidfile);
+    g_error("failed to daemonize");
+}
+
+static int conn_channel_send_payload(GIOChannel *channel, QObject *payload)
+{
+    gsize count, written = 0;
+    int ret;
+    const char *buf;
+    QString *payload_qstr;
+    GIOStatus status;
+    GError *err = NULL;
Quoted 9 lines

qemu-ga.c line 199

Quoted 5 lines
qemu-ga.c new file +5 -0
+
+    if (!payload || !channel) {
+        ret = -EINVAL;
+        goto out;
+    }

Just do "return -EINVAL" instead of using goto, but I wonder if this should be an assert() instead.

Yah, looks like it.

Quoted 79 lines

qemu-ga.c line 205

Quoted 6 lines
qemu-ga.c new file +6 -0
+
+    payload_qstr = qobject_to_json(payload);
+    if (!payload_qstr) {
+        ret = -EINVAL;
+        goto out;
+    }

return -EINVAL.

Quoted 19 lines
qemu-ga.c new file +19 -0
+
+    buf = qstring_get_str(payload_qstr);
+    count = strlen(buf);
+
+    while (count) {
+        g_debug("sending data, count: %d", (int)count);
+        status = g_io_channel_write_chars(channel, buf, count,&written,&err);
+        if (err != NULL) {
+            g_warning("error writing payload to channel: %s", err->message);
+            ret = err->code;
+            goto out_free;
+        }
+        if (status == G_IO_STATUS_NORMAL) {
+            count -= written;
+        } else if (status == G_IO_STATUS_ERROR || status == G_IO_STATUS_EOF) {
+            ret = -EPIPE;
+            goto out_free;
+        }
+    }

qemu-ga.c line 234

Quoted 10 lines
qemu-ga.c new file +10 -0
+
+    status = g_io_channel_write_chars(channel, (char *)"\n", 1,&written,&err);
+    if (err != NULL) {
+        g_warning("error sending newline: %s", err->message);
+        ret = err->code;
+        goto out_free;
+    } else if (status == G_IO_STATUS_ERROR || status == G_IO_STATUS_EOF) {
+        ret = -EPIPE;
+        goto out_free;
+    }

This wants to be a function.

Quoted 5 lines
qemu-ga.c new file +5 -0
+
+    g_io_channel_flush(channel,&err);
+    if (err != NULL) {
+        g_warning("error flushing payload: %s", err->message);
+        ret = err->code;

qemu-ga.c line 249

Quoted 10 lines
qemu-ga.c new file +10 -0
+        goto out_free;
+    }
+
+    ret = 0;
+
+out_free:
+    QDECREF(payload_qstr);
+    if (err != NULL) {
+        g_error_free(err);
+    }

No need to check against NULL.

Quoted 5 lines
qemu-ga.c new file +5 -0
+out:
+    return ret;
+}
+
+static void process_command(GAState *s, QDict *req)

qemu-ga.c line 264

Quoted 10 lines
qemu-ga.c new file +10 -0
+{
+    QObject *rsp = NULL;
+    Error *err = NULL;
+    int ret;
+
+    g_assert(req);
+    g_debug("processing command");
+    rsp = qmp_dispatch(QOBJECT(req));
+    if (rsp) {
+        if (err) {

Apart from its initialization, 'err' is read only. Either, you want to use qmp_dispatch_err() here or you have to modify qmp_dispatch() to also accept an Error argument.

Quoted 3 lines

If you want to send a "return" or "error" QMP kind of response, then you have to do the latter.

qmp_dispatch() does the Error -> QMP error conversion. The unused err is cruft from when a seperate worker thread did the dispatch, will remove it.

Quoted 34 lines
Quoted 19 lines
qemu-ga.c new file +19 -0
+            g_warning("command failed: %s", error_get_pretty(err));
+        }
+        ret = conn_channel_send_payload(s->conn_channel, rsp);
+        if (ret) {
+            g_warning("error sending payload: %s", strerror(ret));
+        }
+        qobject_decref(rsp);
+    } else {
+        g_warning("error getting response");
+        if (err) {
+            g_warning("dispatch failed: %s", error_get_pretty(err));
+        }
+    }
+}
+
+/* handle requests/control events coming in over the channel */
+static void process_event(JSONMessageParser *parser, QList *tokens)
+{
+    GAState *s = container_of(parser, GAState, parser);

qemu-ga.c line 293

Quoted 10 lines
qemu-ga.c new file +10 -0
+    QObject *obj;
+    QDict *qdict;
+    Error *err = NULL;
+
+    g_assert(s&&   parser);
+
+    g_debug("process_event: called");
+    obj = json_parser_parse_err(tokens, NULL,&err);
+    if (!obj || qobject_type(obj) != QTYPE_QDICT) {
+        g_warning("failed to parse event");

Two possible leaks here. You have to call qobject_decref() if obj != NULL and there's a missing call to error_free(). But you don't use 'err' anyway, so you could pass NULL there.

Quoted 3 lines

Oh, btw, the guest agent doesn't seem to report errors back to its client... Not even json error messages, is this intended?

Hmm, yes it's intended, but I probably should send those to the client...

You do get non-parse errors though: missing arguments, etc.

I'll fix this to do what QMP does here.

Quoted 364 lines

qemu-ga.c line 299

Quoted 6 lines
qemu-ga.c new file +6 -0
+        return;
+    } else {
+        g_debug("parse successful");
+        qdict = qobject_to_qdict(obj);
+        g_assert(qdict);
+    }

Superfluous else clause.

qga/guest-agent-core.h

Quoted 336 lines
qemu-ga.c new file +332 -0
+
+    /* 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, listen_fd=0
+ * indicates we should use the existing s->listen_channel
+ */
+static int listen_channel_add(GAState *s, int listen_fd)
+{
+    GError *err = NULL;
+    guint listen_id;
+
+    if (listen_fd) {
+        s->listen_channel = g_io_channel_unix_new(listen_fd);
+        if (s->listen_sock) {
+            g_object_unref(s->listen_sock);
+        }
+        s->listen_sock = g_socket_new_from_fd(listen_fd,&err);
+        if (err != NULL) {
+            g_warning("error converting fd to gsocket: %s", err->message);
+            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);
+    } else if (strcmp(s->method, "virtio-serial") == 0) {
+        /* we spin on EOF for virtio-serial, so back off a bit. also,
+         * dont close the connection in this case, it'll resume normal
+         * operation when another process connects to host chardev
+         */
+        usleep(100*1000);
+        goto out_noclose;
+    }
+    g_io_channel_unref(s->conn_channel);
+    s->conn_channel = NULL;
+    s->conn_id = 0;
+out_noclose:
+    return;
+}
+
+static void init_guest_agent(GAState *s)
+{
+    struct termios tio;
+    int ret, fd;
+
+    if (s->method == NULL) {
+        /* try virtio-serial as our default */
+        s->method = "virtio-serial";
+    }
+
+    if (s->path == NULL) {
+        if (strcmp(s->method, "virtio-serial") != 0) {
+            g_error("must specify a path for this channel");
+        }
+        /* try the default path for the virtio-serial port */
+        s->path = QGA_VIRTIO_PATH_DEFAULT;
+    }
+
+    if (strcmp(s->method, "virtio-serial") == 0) {
+        s->virtio = true;
+        fd = qemu_open(s->path, O_RDWR);
+        if (fd == -1) {
+            g_error("error opening channel: %s", strerror(errno));
+        }
+        ret = fcntl(fd, F_GETFL);
+        if (ret<   0) {
+            g_error("error getting channel flags: %s", strerror(errno));
+        }
+        ret = fcntl(fd, F_SETFL, ret | O_NONBLOCK | O_ASYNC);
+        if (ret<   0) {
+            g_error("error setting channel flags: %s", strerror(errno));
+        }
+        ret = conn_channel_add(s, fd);
+        if (ret) {
+            g_error("error adding channel to main loop");
+        }
+    } else if (strcmp(s->method, "isa-serial") == 0) {
+        fd = qemu_open(s->path, O_RDWR | O_NOCTTY);
+        if (fd == -1) {
+            g_error("error opening channel: %s", strerror(errno));
+        }
+        tcgetattr(fd,&tio);
+        /* set up serial port for non-canonical, dumb byte streaming */
+        tio.c_iflag&= ~(IGNBRK | BRKINT | IGNPAR | PARMRK | INPCK | ISTRIP |
+                         INLCR | IGNCR | ICRNL | IXON | IXOFF | IXANY |
+                         IMAXBEL);
+        tio.c_oflag = 0;
+        tio.c_lflag = 0;
+        tio.c_cflag |= QGA_BAUDRATE_DEFAULT;
+        /* 1 available byte min or reads will block (we'll set non-blocking
+         * elsewhere, else we have to deal with read()=0 instead)
+         */
+        tio.c_cc[VMIN] = 1;
+        tio.c_cc[VTIME] = 0;
+        /* flush everything waiting for read/xmit, it's garbage at this point */
+        tcflush(fd, TCIFLUSH);
+        tcsetattr(fd, TCSANOW,&tio);
+        ret = conn_channel_add(s, fd);
+        if (ret) {
+            g_error("error adding channel to main loop");
+        }
+    } else if (strcmp(s->method, "unix-listen") == 0) {
+        fd = unix_listen(s->path, NULL, strlen(s->path));
+        if (fd == -1) {
+            g_error("error opening path: %s", strerror(errno));
+        }
+        ret = listen_channel_add(s, fd);
+        if (ret) {
+            g_error("error binding/listening to specified socket");
+        }
+    } else {
+        g_error("unsupported channel method/type: %s", s->method);
+    }
+
+    json_message_parser_init(&s->parser, process_event);
+    s->main_loop = g_main_loop_new(NULL, false);
+}
+
+int main(int argc, char **argv)
+{
+    const char *sopt = "hVvdc:p:l:f:";
+    const char *method = NULL, *path = NULL, *pidfile = QGA_PIDFILE_DEFAULT;
+    struct option lopt[] = {
+        { "help", 0, NULL, 'h' },
+        { "version", 0, NULL, 'V' },
+        { "logfile", 0, NULL, 'l' },
+        { "pidfile", 0, NULL, 'f' },
+        { "verbose", 0, NULL, 'v' },
+        { "channel", 0, NULL, 'c' },
+        { "path", 0, NULL, 'p' },
+        { "daemonize", 0, NULL, 'd' },
+        { NULL, 0, NULL, 0 }
+    };
+    int opt_ind = 0, ch, daemonize = 0;
+    GLogLevelFlags log_level = G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL;
+    FILE *log_file = stderr;
+    GAState *s;
+
+    g_type_init();
+    g_thread_init(NULL);
+
+    while ((ch = getopt_long(argc, argv, sopt, lopt,&opt_ind)) != -1) {
+        switch (ch) {
+        case 'c':
+            method = optarg;
+            break;
+        case 'p':
+            path = optarg;
+            break;
+        case 'l':
+            log_file = fopen(optarg, "a");
+            if (!log_file) {
+                g_error("unable to open specified log file: %s",
+                        strerror(errno));
+            }
+            break;
+        case 'f':
+            pidfile = optarg;
+            break;
+        case 'v':
+            /* enable all log levels */
+            log_level = G_LOG_LEVEL_MASK;
+            break;
+        case 'V':
+            printf("QEMU Guest Agent %s\n", QGA_VERSION);
+            return 0;
+        case 'd':
+            daemonize = 1;
+            break;
+        case 'h':
+            usage(argv[0]);
+            return 0;
+        case '?':
+            g_error("Unknown option, try '%s --help' for more information.",
+                    argv[0]);
+        }
+    }
+
+    if (daemonize) {
+        g_debug("starting daemon");
+        become_daemon(pidfile);
+    }
+
+    s = g_malloc0(sizeof(GAState));
+    s->conn_id = 0;
+    s->conn_channel = NULL;
+    s->path = path;
+    s->method = method;
+    s->command_state = ga_command_state_new();
+    ga_command_state_init(s, s->command_state);
+    ga_command_state_init_all(s->command_state);
+    s->log_file = log_file;
+    s->log_level = log_level;
+    g_log_set_default_handler(ga_log, s);
+    g_log_set_fatal_mask(NULL, G_LOG_LEVEL_ERROR);
+    s->logging_enabled = true;
+
+    module_call_init(MODULE_INIT_QAPI);
+    init_guest_agent(s);
+
+    g_main_loop_run(s->main_loop);
+
+    ga_command_state_cleanup_all(s->command_state);
+
+    return 0;
+}
qga/guest-agent-core.h +0 -0
index 688f120..66d1729 100644

No license text.

Quoted 15 lines
qga/guest-agent-core.h +4 -0
@@ -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);
#11 lore raw

Re: [Qemu-devel] [PATCH v5 2/5] guest agent: qemu-ga daemon

Quoted 210 lines · Luiz Capitulino
Quoted 207 lines · On Fri, 17 Jun 2011 16:25:32 -0500 Michael Roth<[email protected]>…
Quoted 189 lines · Luiz Capitulino
Quoted 179 lines · On Fri, 17 Jun 2011 14:21:31 -0500 Michael Roth<[email protected]>…
Quoted 66 lines · Luiz Capitulino
Quoted 7 lines · On Tue, 14 Jun 2011 15:06:22 -0500 Michael Roth<[email protected]>…

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:

Quoted 8 lines

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

Would be nice to have a more complete description, like explaining how to do a simple test.

And this can't be built...

Quoted 42 lines
---
    qemu-ga.c              |  631 ++++++++++++++++++++++++++++++++++++++++++++++++
    qga/guest-agent-core.h |    4 +
    2 files changed, 635 insertions(+), 0 deletions(-)
    create mode 100644 qemu-ga.c
qemu-ga.c new file +30 -0
new file mode 100644
index 0000000..df08d8c
@@ -0,0 +1,631 @@
+/*
+ * QEMU Guest Agent
+ *
+ * Copyright IBM Corp. 2011
+ *
+ * Authors:
+ *  Adam Litke<[email protected]>
+ *  Michael Roth<[email protected]>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+#include<stdlib.h>
+#include<stdio.h>
+#include<stdbool.h>
+#include<glib.h>
+#include<gio/gio.h>
+#include<getopt.h>
+#include<termios.h>
+#include<syslog.h>
+#include "qemu_socket.h"
+#include "json-streamer.h"
+#include "json-parser.h"
+#include "qint.h"
+#include "qjson.h"
+#include "qga/guest-agent-core.h"
+#include "qga-qmp-commands.h"
+#include "module.h"
+
+#define QGA_VIRTIO_PATH_DEFAULT "/dev/virtio-ports/org.qemu.guest_agent"
Quoted 9 lines

qemu-ga.c line 36

Quoted 6 lines
qemu-ga.c new file +6 -0
+#define QGA_PIDFILE_DEFAULT "/var/run/qemu-va.pid"
+#define QGA_BAUDRATE_DEFAULT B38400 /* for isa-serial channels */
+#define QGA_TIMEOUT_DEFAULT 30*1000 /* ms */
+
+struct GAState {
+    const char *proxy_path;

Where is this used?

Nowhere actually. Will remove.

Quoted 78 lines
Quoted 63 lines
qemu-ga.c new file +63 -0
+    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 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<[email protected]>\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;
+}

qemu-ga.c line 109

Quoted 10 lines
qemu-ga.c new file +10 -0
+
+void ga_disable_logging(GAState *s)
+{
+    s->logging_enabled = false;
+}
+
+void ga_enable_logging(GAState *s)
+{
+    s->logging_enabled = true;
+}

Just to check I got this right, this is needed because of the fsfreeze command, correct? Isn't it better to have a more descriptive name, like fsfrozen?

Quoted 4 lines

First I thought this was about a log file. Then I realized this was probably about letting the user log in, but we don't seem to have this functionality so I got confused.

Yup, this is currently due to fsfreeze support. From the perspective of the fsfreeze command the explicit "is_frozen" check makes more sense, but the reason it affects other RPCs is because because we can't log stuff in that state. If an RPC shoots itself in the foot by writing to a frozen filesystem we don't really care so much, and up until recently that case was handled with a pthread_cancel timeout mechanism (was removed for the time being, will re-implement using a child process most likely).

What we don't want to do is give a host a way to bypass the expectation we set for guest owners by allowing RPCs to be logged. So that's what the check is based on, rather than lower level details like *why* logging is disabled. Also, I'd really like to avoid a precedence where a single RPC can place restrictions on all the others, so the logging aspect seemed general enough that it doesn't necessarily provide that precedence.

Quoted 6 lines

This has come up a few times without any real consensus. I can probably go either way, but I think the check for logging is easier to set expectations with: "if logging is important from an auditing perspective, don't execute this if logging is disabled". beyond that, same behavior/symantics you'd get with anything that causes a write() on a filesystem in a frozen state: block.

Quoted 5 lines

While I understand your arguments, I still find the current behavior
confusing: you issue a guest-open-file command and get a QgaLoggingFailed error. The first question is: what does a log write fail have to do with me opening a file? The second question is: what should I do? Try again? Give up?

I agree it's a better solution for the client there, but at the same time:

guest_privileged_ping():
if fsfreeze.status == FROZEN:
syslog("privileged ping, thought you should know")
else:
return "error, filesystem frozen"

Seems silly to the client as well. "Why does a ping command care about the filesystem state?"

Quoted 8 lines
Quoted 2 lines

Inability to log is the true error. That's also the case for the guest-file-open command.

There's a difference. In the guest ping the inability to log is an internal agent error, it's not interesting to the client. We could just ignore the error and reply back (unless we define that guest-privileged-ping requires writing to disk).

To clarify, these's 2 different types of errors here and their relation to fsfreeze is causing the separation to get munged a bit:

1) Logging/accounting errors: even though we try to make it clear wherever possible this solution is based on a "trusted" hypervisor that already has substantial privileges over guests (direct access to images, etc), one of our internal use cases is the ability for customers to properly account for actions initiated by the guest agent. Shutdowns, whats files were read/written, etc.

For some commands, this accounting is not important. guest-ping, or guest-info, for instance, is uninteresting from a security accounting perspective. So logging is in fact optional and logging failures are silently ignored.

guest-shutdown, guest-file-open, guest-fsfreeze, however, are important. So the QgaLoggingError we currently report really means "this command requires logging, but logging has been disabled". You're right that this is because of fsfreeze, but it's not because of the filesystem state. guest-file-open on a network mount that wasn't frozen would *still*, by design, report an error due to inability to log.

For this case I do see your point about the error not being very helpful to users, which is why I think something like:

"Guest agent failed to log RPC due to frozen filesystems"

Is the best way to report it. If we need to fix up the error id to something like QgaLoggingToFrozenFilesystem I'd be fine with that.

2) EWOULDBLOCK or I/O errors due to writing to frozen filesystems: this is currently treated as a PEBKAC and not enforced/reported at all. I wouldn't be against disabling guest-file-write as a side-effect of freezing, but that's not totally correct. Writes to non-frozen filesystems like networked or sysfs mounts is technically still okay, for instance. We store path information in guest-file-open and use it to scan against fsfreeze's state info about frozen mounts to handle this a little better.

Even then you still have RPCs like guest-shutdown that may do a syslog() that would cause the command to freeze, or in the future we may add the ability for the guest agent to execute user/distro-installed scripts that may/may not need to write to the filesystem. Some these might even be intended to run when the filesystem is frozen...cleanup scripts for databases and whatnot for snapshotting is something I've seen come up.

We can

a) be very restrictive, which I'm not totally against...my initial inclination was to disable everything except guest-fsfreeze-thaw/guest-fsfreeze-status while frozen, but this has some limitations that aren't as obscure/unlikely as one would hope.

b) we can be completely unrestrictive about it and give users the same experiences they'd get at a command-line if they, or another user on the system, was messing with fsfreeze.

c) try to capture some common cases, but make it clear that you can still shoot yourself in the foot using the guest agent on a frozen filesystem, evaluating when to enforce this in code on a case-by-case basis.

Personally I like b), mostly because it's the least work, but also because it's actually the easiest way to set expectations about fsfreeze.

Quoted 6 lines

The guest-file-write command, on the other hand, clearly requires to write to disk, so a client would expect a EWOULDBLOCK error.

EWOULDBLOCK looks good to me. We could define as general rule that commands don't block, so clients should always expect a EWOULDBLOCK.

This falls under 2)

Quoted 6 lines
Quoted 3 lines

There's nothing wrong with opening a read-only
file on a frozen filesystem, it's the fact that we can't log the open that causes us to bail out..

But why do we bail out? Why can't we just ignore the fact we can't log?

This falls under 1)

Quoted 8 lines
Quoted 4 lines

So, what if we just munge the 2 to give the user the proper clues to fix things, and instead return an error like:

"Guest agent failed to log RPC (is the filesystem frozen?)"?

The 'desc' part of an error is a human error descriptor, clients shouldn't parse it. The real error is the error class.

As mentioned above, we could change the error id itself to capture both the immediate error and the underlying error.

#12 lore raw

Re: [Qemu-devel] [PATCH v5 2/5] guest agent: qemu-ga daemon

Quoted 223 lines · On Sun, 19 Jun 2011 14:00:30 -0500 Michael Roth <[email protected]>…
Quoted 210 lines · Luiz Capitulino
Quoted 207 lines · On Fri, 17 Jun 2011 16:25:32 -0500 Michael Roth<[email protected]>…
Quoted 189 lines · Luiz Capitulino
Quoted 179 lines · On Fri, 17 Jun 2011 14:21:31 -0500 Michael Roth<[email protected]>…
Quoted 66 lines · Luiz Capitulino
Quoted 7 lines · On Tue, 14 Jun 2011 15:06:22 -0500 Michael Roth<[email protected]>…

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:

Quoted 8 lines

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

Would be nice to have a more complete description, like explaining how to do a simple test.

And this can't be built...

Quoted 42 lines
---
    qemu-ga.c              |  631 ++++++++++++++++++++++++++++++++++++++++++++++++
    qga/guest-agent-core.h |    4 +
    2 files changed, 635 insertions(+), 0 deletions(-)
    create mode 100644 qemu-ga.c
qemu-ga.c new file +30 -0
new file mode 100644
index 0000000..df08d8c
@@ -0,0 +1,631 @@
+/*
+ * QEMU Guest Agent
+ *
+ * Copyright IBM Corp. 2011
+ *
+ * Authors:
+ *  Adam Litke<[email protected]>
+ *  Michael Roth<[email protected]>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+#include<stdlib.h>
+#include<stdio.h>
+#include<stdbool.h>
+#include<glib.h>
+#include<gio/gio.h>
+#include<getopt.h>
+#include<termios.h>
+#include<syslog.h>
+#include "qemu_socket.h"
+#include "json-streamer.h"
+#include "json-parser.h"
+#include "qint.h"
+#include "qjson.h"
+#include "qga/guest-agent-core.h"
+#include "qga-qmp-commands.h"
+#include "module.h"
+
+#define QGA_VIRTIO_PATH_DEFAULT "/dev/virtio-ports/org.qemu.guest_agent"
Quoted 9 lines

qemu-ga.c line 36

Quoted 6 lines
qemu-ga.c new file +6 -0
+#define QGA_PIDFILE_DEFAULT "/var/run/qemu-va.pid"
+#define QGA_BAUDRATE_DEFAULT B38400 /* for isa-serial channels */
+#define QGA_TIMEOUT_DEFAULT 30*1000 /* ms */
+
+struct GAState {
+    const char *proxy_path;

Where is this used?

Nowhere actually. Will remove.

Quoted 78 lines
Quoted 63 lines
qemu-ga.c new file +63 -0
+    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 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<[email protected]>\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;
+}

qemu-ga.c line 109

Quoted 10 lines
qemu-ga.c new file +10 -0
+
+void ga_disable_logging(GAState *s)
+{
+    s->logging_enabled = false;
+}
+
+void ga_enable_logging(GAState *s)
+{
+    s->logging_enabled = true;
+}

Just to check I got this right, this is needed because of the fsfreeze command, correct? Isn't it better to have a more descriptive name, like fsfrozen?

Quoted 4 lines

First I thought this was about a log file. Then I realized this was probably about letting the user log in, but we don't seem to have this functionality so I got confused.

Yup, this is currently due to fsfreeze support. From the perspective of the fsfreeze command the explicit "is_frozen" check makes more sense, but the reason it affects other RPCs is because because we can't log stuff in that state. If an RPC shoots itself in the foot by writing to a frozen filesystem we don't really care so much, and up until recently that case was handled with a pthread_cancel timeout mechanism (was removed for the time being, will re-implement using a child process most likely).

What we don't want to do is give a host a way to bypass the expectation we set for guest owners by allowing RPCs to be logged. So that's what the check is based on, rather than lower level details like *why* logging is disabled. Also, I'd really like to avoid a precedence where a single RPC can place restrictions on all the others, so the logging aspect seemed general enough that it doesn't necessarily provide that precedence.

Quoted 6 lines

This has come up a few times without any real consensus. I can probably go either way, but I think the check for logging is easier to set expectations with: "if logging is important from an auditing perspective, don't execute this if logging is disabled". beyond that, same behavior/symantics you'd get with anything that causes a write() on a filesystem in a frozen state: block.

Quoted 5 lines

While I understand your arguments, I still find the current behavior
confusing: you issue a guest-open-file command and get a QgaLoggingFailed error. The first question is: what does a log write fail have to do with me opening a file? The second question is: what should I do? Try again? Give up?

I agree it's a better solution for the client there, but at the same time:

guest_privileged_ping():
if fsfreeze.status == FROZEN:
syslog("privileged ping, thought you should know")
else:
return "error, filesystem frozen"

Seems silly to the client as well. "Why does a ping command care about the filesystem state?"

Quoted 8 lines
Quoted 2 lines

Inability to log is the true error. That's also the case for the guest-file-open command.

There's a difference. In the guest ping the inability to log is an internal agent error, it's not interesting to the client. We could just ignore the error and reply back (unless we define that guest-privileged-ping requires writing to disk).

To clarify, these's 2 different types of errors here and their relation to fsfreeze is causing the separation to get munged a bit:

Quoted 6 lines

1) Logging/accounting errors: even though we try to make it clear wherever possible this solution is based on a "trusted" hypervisor that already has substantial privileges over guests (direct access to images, etc), one of our internal use cases is the ability for customers to properly account for actions initiated by the guest agent. Shutdowns, whats files were read/written, etc.

This is fine.

Quoted 11 lines

For some commands, this accounting is not important. guest-ping, or guest-info, for instance, is uninteresting from a security accounting perspective. So logging is in fact optional and logging failures are silently ignored.

guest-shutdown, guest-file-open, guest-fsfreeze, however, are important. So the QgaLoggingError we currently report really means "this command requires logging, but logging has been disabled". You're right that this is because of fsfreeze, but it's not because of the filesystem state. guest-file-open on a network mount that wasn't frozen would *still*, by design, report an error due to inability to log.

Completely ignoring the file-system state and considering only what you say about security, this doesn't make sense to me. Actually, I think it's a flaw, because a client could open the daemon's log file and write garbage on it.

But, how does the ability to log protects you from anything? I would understand it's importance if the user had to provide credentials to use the agent, but s/he doesn't. It's like you were unable to use a linux box because syslogd is not running.

This looks like a misfeature to me. If you really want to provide it, then you could provide a --enable-log-error which, when enabled, would make the daemon not to execute *any* command if the it's able to log its actions. Otherwise log errors are just ignored.

Quoted 8 lines

For this case I do see your point about the error not being very helpful to users, which is why I think something like:

"Guest agent failed to log RPC due to frozen filesystems"

Is the best way to report it. If we need to fix up the error id to something like QgaLoggingToFrozenFilesystem I'd be fine with that.

Quoted 8 lines

2) EWOULDBLOCK or I/O errors due to writing to frozen filesystems: this is currently treated as a PEBKAC and not enforced/reported at all. I wouldn't be against disabling guest-file-write as a side-effect of freezing, but that's not totally correct. Writes to non-frozen filesystems like networked or sysfs mounts is technically still okay, for instance. We store path information in guest-file-open and use it to scan against fsfreeze's state info about frozen mounts to handle this a little better.

Yes, I agree with you here.

Quoted 56 lines

Even then you still have RPCs like guest-shutdown that may do a syslog() that would cause the command to freeze, or in the future we may add the ability for the guest agent to execute user/distro-installed scripts that may/may not need to write to the filesystem. Some these might even be intended to run when the filesystem is frozen...cleanup scripts for databases and whatnot for snapshotting is something I've seen come up.

We can

a) be very restrictive, which I'm not totally against...my initial inclination was to disable everything except guest-fsfreeze-thaw/guest-fsfreeze-status while frozen, but this has some limitations that aren't as obscure/unlikely as one would hope.

b) we can be completely unrestrictive about it and give users the same experiences they'd get at a command-line if they, or another user on the system, was messing with fsfreeze.

c) try to capture some common cases, but make it clear that you can still shoot yourself in the foot using the guest agent on a frozen filesystem, evaluating when to enforce this in code on a case-by-case basis.

Personally I like b), mostly because it's the least work, but also because it's actually the easiest way to set expectations about fsfreeze.

Quoted 6 lines

The guest-file-write command, on the other hand, clearly requires to write to disk, so a client would expect a EWOULDBLOCK error.

EWOULDBLOCK looks good to me. We could define as general rule that commands don't block, so clients should always expect a EWOULDBLOCK.

This falls under 2)

Quoted 6 lines
Quoted 3 lines

There's nothing wrong with opening a read-only
file on a frozen filesystem, it's the fact that we can't log the open that causes us to bail out..

But why do we bail out? Why can't we just ignore the fact we can't log?

This falls under 1)

Quoted 8 lines
Quoted 4 lines

So, what if we just munge the 2 to give the user the proper clues to fix things, and instead return an error like:

"Guest agent failed to log RPC (is the filesystem frozen?)"?

The 'desc' part of an error is a human error descriptor, clients shouldn't parse it. The real error is the error class.

As mentioned above, we could change the error id itself to capture both the immediate error and the underlying error.

#13 lore raw

Re: [Qemu-devel] [PATCH v5 2/5] guest agent: qemu-ga daemon

Quoted 257 lines · Luiz Capitulino
Quoted 223 lines · On Sun, 19 Jun 2011 14:00:30 -0500 Michael Roth<[email protected]>…
Quoted 210 lines · Luiz Capitulino
Quoted 207 lines · On Fri, 17 Jun 2011 16:25:32 -0500 Michael Roth<[email protected]>…
Quoted 189 lines · Luiz Capitulino
Quoted 179 lines · On Fri, 17 Jun 2011 14:21:31 -0500 Michael Roth<[email protected]>…
Quoted 66 lines · Luiz Capitulino
Quoted 7 lines · On Tue, 14 Jun 2011 15:06:22 -0500 Michael Roth<[email protected]>…

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:

Quoted 8 lines

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

Would be nice to have a more complete description, like explaining how to do a simple test.

And this can't be built...

Quoted 42 lines
---
     qemu-ga.c              |  631 ++++++++++++++++++++++++++++++++++++++++++++++++
     qga/guest-agent-core.h |    4 +
     2 files changed, 635 insertions(+), 0 deletions(-)
     create mode 100644 qemu-ga.c
qemu-ga.c new file +30 -0
new file mode 100644
index 0000000..df08d8c
@@ -0,0 +1,631 @@
+/*
+ * QEMU Guest Agent
+ *
+ * Copyright IBM Corp. 2011
+ *
+ * Authors:
+ *  Adam Litke<[email protected]>
+ *  Michael Roth<[email protected]>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+#include<stdlib.h>
+#include<stdio.h>
+#include<stdbool.h>
+#include<glib.h>
+#include<gio/gio.h>
+#include<getopt.h>
+#include<termios.h>
+#include<syslog.h>
+#include "qemu_socket.h"
+#include "json-streamer.h"
+#include "json-parser.h"
+#include "qint.h"
+#include "qjson.h"
+#include "qga/guest-agent-core.h"
+#include "qga-qmp-commands.h"
+#include "module.h"
+
+#define QGA_VIRTIO_PATH_DEFAULT "/dev/virtio-ports/org.qemu.guest_agent"
Quoted 9 lines

qemu-ga.c line 36

Quoted 6 lines
qemu-ga.c new file +6 -0
+#define QGA_PIDFILE_DEFAULT "/var/run/qemu-va.pid"
+#define QGA_BAUDRATE_DEFAULT B38400 /* for isa-serial channels */
+#define QGA_TIMEOUT_DEFAULT 30*1000 /* ms */
+
+struct GAState {
+    const char *proxy_path;

Where is this used?

Nowhere actually. Will remove.

Quoted 78 lines
Quoted 63 lines
qemu-ga.c new file +63 -0
+    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 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<[email protected]>\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;
+}

qemu-ga.c line 109

Quoted 10 lines
qemu-ga.c new file +10 -0
+
+void ga_disable_logging(GAState *s)
+{
+    s->logging_enabled = false;
+}
+
+void ga_enable_logging(GAState *s)
+{
+    s->logging_enabled = true;
+}

Just to check I got this right, this is needed because of the fsfreeze command, correct? Isn't it better to have a more descriptive name, like fsfrozen?

Quoted 4 lines

First I thought this was about a log file. Then I realized this was probably about letting the user log in, but we don't seem to have this functionality so I got confused.

Yup, this is currently due to fsfreeze support. From the perspective of the fsfreeze command the explicit "is_frozen" check makes more sense, but the reason it affects other RPCs is because because we can't log stuff in that state. If an RPC shoots itself in the foot by writing to a frozen filesystem we don't really care so much, and up until recently that case was handled with a pthread_cancel timeout mechanism (was removed for the time being, will re-implement using a child process most likely).

What we don't want to do is give a host a way to bypass the expectation we set for guest owners by allowing RPCs to be logged. So that's what the check is based on, rather than lower level details like *why* logging is disabled. Also, I'd really like to avoid a precedence where a single RPC can place restrictions on all the others, so the logging aspect seemed general enough that it doesn't necessarily provide that precedence.

Quoted 6 lines

This has come up a few times without any real consensus. I can probably go either way, but I think the check for logging is easier to set expectations with: "if logging is important from an auditing perspective, don't execute this if logging is disabled". beyond that, same behavior/symantics you'd get with anything that causes a write() on a filesystem in a frozen state: block.

Quoted 5 lines

While I understand your arguments, I still find the current behavior
confusing: you issue a guest-open-file command and get a QgaLoggingFailed error. The first question is: what does a log write fail have to do with me opening a file? The second question is: what should I do? Try again? Give up?

I agree it's a better solution for the client there, but at the same time:

guest_privileged_ping():
if fsfreeze.status == FROZEN:
syslog("privileged ping, thought you should know")
else:
return "error, filesystem frozen"

Seems silly to the client as well. "Why does a ping command care about the filesystem state?"

Quoted 8 lines
Quoted 2 lines

Inability to log is the true error. That's also the case for the guest-file-open command.

There's a difference. In the guest ping the inability to log is an internal agent error, it's not interesting to the client. We could just ignore the error and reply back (unless we define that guest-privileged-ping requires writing to disk).

To clarify, these's 2 different types of errors here and their relation to fsfreeze is causing the separation to get munged a bit:

Quoted 6 lines

1) Logging/accounting errors: even though we try to make it clear wherever possible this solution is based on a "trusted" hypervisor that already has substantial privileges over guests (direct access to images, etc), one of our internal use cases is the ability for customers to properly account for actions initiated by the guest agent. Shutdowns, whats files were read/written, etc.

This is fine.

Quoted 11 lines

For some commands, this accounting is not important. guest-ping, or guest-info, for instance, is uninteresting from a security accounting perspective. So logging is in fact optional and logging failures are silently ignored.

guest-shutdown, guest-file-open, guest-fsfreeze, however, are important. So the QgaLoggingError we currently report really means "this command requires logging, but logging has been disabled". You're right that this is because of fsfreeze, but it's not because of the filesystem state. guest-file-open on a network mount that wasn't frozen would *still*, by design, report an error due to inability to log.

Completely ignoring the file-system state and considering only what you say about security, this doesn't make sense to me. Actually, I think it's a flaw, because a client could open the daemon's log file and write garbage on it.

But, how does the ability to log protects you from anything? I would understand it's importance if the user had to provide credentials to use the agent, but s/he doesn't. It's like you were unable to use a linux box because syslogd is not running.

Quoted 5 lines

This looks like a misfeature to me. If you really want to provide it, then you could provide a --enable-log-error which, when enabled, would make the daemon not to execute *any* command if the it's able to log its actions. Otherwise log errors are just ignored.

Hmm, you're right. I'd thought that if you clobber the log file at least the RPC that clobbered the log file would get logged, but since we only log the guest-file-open, and we do that before the guest-file-write, they could overwrite and fabricate the log file history and nothing would be obviously suspicious.

Since we can't build any accounting around something like that I'm fine with having the logging silently fail in a freeze state for now.

A better thought out accounting mechanism can be added as an optional feature later if it comes to that.

Quoted 17 lines
Quoted 8 lines

For this case I do see your point about the error not being very helpful to users, which is why I think something like:

"Guest agent failed to log RPC due to frozen filesystems"

Is the best way to report it. If we need to fix up the error id to something like QgaLoggingToFrozenFilesystem I'd be fine with that.

Quoted 8 lines

2) EWOULDBLOCK or I/O errors due to writing to frozen filesystems: this is currently treated as a PEBKAC and not enforced/reported at all. I wouldn't be against disabling guest-file-write as a side-effect of freezing, but that's not totally correct. Writes to non-frozen filesystems like networked or sysfs mounts is technically still okay, for instance. We store path information in guest-file-open and use it to scan against fsfreeze's state info about frozen mounts to handle this a little better.

Quoted 2 lines

Yes, I agree with you here.

Treating I/O errors/deadlocks due to fsfreeze as a user issue (as things are currently), or working around it? If the latter, a), b), or c)?

Quoted 57 lines
Quoted 56 lines

Even then you still have RPCs like guest-shutdown that may do a syslog() that would cause the command to freeze, or in the future we may add the ability for the guest agent to execute user/distro-installed scripts that may/may not need to write to the filesystem. Some these might even be intended to run when the filesystem is frozen...cleanup scripts for databases and whatnot for snapshotting is something I've seen come up.

We can

a) be very restrictive, which I'm not totally against...my initial inclination was to disable everything except guest-fsfreeze-thaw/guest-fsfreeze-status while frozen, but this has some limitations that aren't as obscure/unlikely as one would hope.

b) we can be completely unrestrictive about it and give users the same experiences they'd get at a command-line if they, or another user on the system, was messing with fsfreeze.

c) try to capture some common cases, but make it clear that you can still shoot yourself in the foot using the guest agent on a frozen filesystem, evaluating when to enforce this in code on a case-by-case basis.

Personally I like b), mostly because it's the least work, but also because it's actually the easiest way to set expectations about fsfreeze.

Quoted 6 lines

The guest-file-write command, on the other hand, clearly requires to write to disk, so a client would expect a EWOULDBLOCK error.

EWOULDBLOCK looks good to me. We could define as general rule that commands don't block, so clients should always expect a EWOULDBLOCK.

This falls under 2)

Quoted 6 lines
Quoted 3 lines

There's nothing wrong with opening a read-only
file on a frozen filesystem, it's the fact that we can't log the open that causes us to bail out..

But why do we bail out? Why can't we just ignore the fact we can't log?

This falls under 1)

Quoted 8 lines
Quoted 4 lines

So, what if we just munge the 2 to give the user the proper clues to fix things, and instead return an error like:

"Guest agent failed to log RPC (is the filesystem frozen?)"?

The 'desc' part of an error is a human error descriptor, clients shouldn't parse it. The real error is the error class.

As mentioned above, we could change the error id itself to capture both the immediate error and the underlying error.

#14 lore raw

Re: [Qemu-devel] [PATCH v5 2/5] guest agent: qemu-ga daemon

Quoted 270 lines · On Mon, 20 Jun 2011 18:40:38 -0500 Michael Roth <[email protected]>…
Quoted 257 lines · Luiz Capitulino
Quoted 223 lines · On Sun, 19 Jun 2011 14:00:30 -0500 Michael Roth<[email protected]>…
Quoted 210 lines · Luiz Capitulino
Quoted 207 lines · On Fri, 17 Jun 2011 16:25:32 -0500 Michael Roth<[email protected]>…
Quoted 189 lines · Luiz Capitulino
Quoted 179 lines · On Fri, 17 Jun 2011 14:21:31 -0500 Michael Roth<[email protected]>…
Quoted 66 lines · Luiz Capitulino
Quoted 7 lines · On Tue, 14 Jun 2011 15:06:22 -0500 Michael Roth<[email protected]>…

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:

Quoted 8 lines

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

Would be nice to have a more complete description, like explaining how to do a simple test.

And this can't be built...

Quoted 42 lines
---
     qemu-ga.c              |  631 ++++++++++++++++++++++++++++++++++++++++++++++++
     qga/guest-agent-core.h |    4 +
     2 files changed, 635 insertions(+), 0 deletions(-)
     create mode 100644 qemu-ga.c
qemu-ga.c new file +30 -0
new file mode 100644
index 0000000..df08d8c
@@ -0,0 +1,631 @@
+/*
+ * QEMU Guest Agent
+ *
+ * Copyright IBM Corp. 2011
+ *
+ * Authors:
+ *  Adam Litke<[email protected]>
+ *  Michael Roth<[email protected]>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+#include<stdlib.h>
+#include<stdio.h>
+#include<stdbool.h>
+#include<glib.h>
+#include<gio/gio.h>
+#include<getopt.h>
+#include<termios.h>
+#include<syslog.h>
+#include "qemu_socket.h"
+#include "json-streamer.h"
+#include "json-parser.h"
+#include "qint.h"
+#include "qjson.h"
+#include "qga/guest-agent-core.h"
+#include "qga-qmp-commands.h"
+#include "module.h"
+
+#define QGA_VIRTIO_PATH_DEFAULT "/dev/virtio-ports/org.qemu.guest_agent"
Quoted 9 lines

qemu-ga.c line 36

Quoted 6 lines
qemu-ga.c new file +6 -0
+#define QGA_PIDFILE_DEFAULT "/var/run/qemu-va.pid"
+#define QGA_BAUDRATE_DEFAULT B38400 /* for isa-serial channels */
+#define QGA_TIMEOUT_DEFAULT 30*1000 /* ms */
+
+struct GAState {
+    const char *proxy_path;

Where is this used?

Nowhere actually. Will remove.

Quoted 78 lines
Quoted 63 lines
qemu-ga.c new file +63 -0
+    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 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<[email protected]>\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;
+}

qemu-ga.c line 109

Quoted 10 lines
qemu-ga.c new file +10 -0
+
+void ga_disable_logging(GAState *s)
+{
+    s->logging_enabled = false;
+}
+
+void ga_enable_logging(GAState *s)
+{
+    s->logging_enabled = true;
+}

Just to check I got this right, this is needed because of the fsfreeze command, correct? Isn't it better to have a more descriptive name, like fsfrozen?

Quoted 4 lines

First I thought this was about a log file. Then I realized this was probably about letting the user log in, but we don't seem to have this functionality so I got confused.

Yup, this is currently due to fsfreeze support. From the perspective of the fsfreeze command the explicit "is_frozen" check makes more sense, but the reason it affects other RPCs is because because we can't log stuff in that state. If an RPC shoots itself in the foot by writing to a frozen filesystem we don't really care so much, and up until recently that case was handled with a pthread_cancel timeout mechanism (was removed for the time being, will re-implement using a child process most likely).

What we don't want to do is give a host a way to bypass the expectation we set for guest owners by allowing RPCs to be logged. So that's what the check is based on, rather than lower level details like *why* logging is disabled. Also, I'd really like to avoid a precedence where a single RPC can place restrictions on all the others, so the logging aspect seemed general enough that it doesn't necessarily provide that precedence.

Quoted 6 lines

This has come up a few times without any real consensus. I can probably go either way, but I think the check for logging is easier to set expectations with: "if logging is important from an auditing perspective, don't execute this if logging is disabled". beyond that, same behavior/symantics you'd get with anything that causes a write() on a filesystem in a frozen state: block.

Quoted 5 lines

While I understand your arguments, I still find the current behavior
confusing: you issue a guest-open-file command and get a QgaLoggingFailed error. The first question is: what does a log write fail have to do with me opening a file? The second question is: what should I do? Try again? Give up?

I agree it's a better solution for the client there, but at the same time:

guest_privileged_ping():
if fsfreeze.status == FROZEN:
syslog("privileged ping, thought you should know")
else:
return "error, filesystem frozen"

Seems silly to the client as well. "Why does a ping command care about the filesystem state?"

Quoted 8 lines
Quoted 2 lines

Inability to log is the true error. That's also the case for the guest-file-open command.

There's a difference. In the guest ping the inability to log is an internal agent error, it's not interesting to the client. We could just ignore the error and reply back (unless we define that guest-privileged-ping requires writing to disk).

To clarify, these's 2 different types of errors here and their relation to fsfreeze is causing the separation to get munged a bit:

Quoted 6 lines

1) Logging/accounting errors: even though we try to make it clear wherever possible this solution is based on a "trusted" hypervisor that already has substantial privileges over guests (direct access to images, etc), one of our internal use cases is the ability for customers to properly account for actions initiated by the guest agent. Shutdowns, whats files were read/written, etc.

This is fine.

Quoted 11 lines

For some commands, this accounting is not important. guest-ping, or guest-info, for instance, is uninteresting from a security accounting perspective. So logging is in fact optional and logging failures are silently ignored.

guest-shutdown, guest-file-open, guest-fsfreeze, however, are important. So the QgaLoggingError we currently report really means "this command requires logging, but logging has been disabled". You're right that this is because of fsfreeze, but it's not because of the filesystem state. guest-file-open on a network mount that wasn't frozen would *still*, by design, report an error due to inability to log.

Completely ignoring the file-system state and considering only what you say about security, this doesn't make sense to me. Actually, I think it's a flaw, because a client could open the daemon's log file and write garbage on it.

But, how does the ability to log protects you from anything? I would understand it's importance if the user had to provide credentials to use the agent, but s/he doesn't. It's like you were unable to use a linux box because syslogd is not running.

Quoted 5 lines

This looks like a misfeature to me. If you really want to provide it, then you could provide a --enable-log-error which, when enabled, would make the daemon not to execute *any* command if the it's able to log its actions. Otherwise log errors are just ignored.

Hmm, you're right. I'd thought that if you clobber the log file at least the RPC that clobbered the log file would get logged, but since we only log the guest-file-open, and we do that before the guest-file-write, they could overwrite and fabricate the log file history and nothing would be obviously suspicious.

Quoted 5 lines

Since we can't build any accounting around something like that I'm fine with having the logging silently fail in a freeze state for now.

A better thought out accounting mechanism can be added as an optional feature later if it comes to that.

Yes.

Quoted 17 lines
Quoted 17 lines
Quoted 16 lines

For this case I do see your point about the error not being very helpful to users, which is why I think something like:

"Guest agent failed to log RPC due to frozen filesystems"

Is the best way to report it. If we need to fix up the error id to something like QgaLoggingToFrozenFilesystem I'd be fine with that.

2) EWOULDBLOCK or I/O errors due to writing to frozen filesystems: this is currently treated as a PEBKAC and not enforced/reported at all. I wouldn't be against disabling guest-file-write as a side-effect of freezing, but that's not totally correct. Writes to non-frozen filesystems like networked or sysfs mounts is technically still okay, for instance. We store path information in guest-file-open and use it to scan against fsfreeze's state info about frozen mounts to handle this a little better.

Quoted 5 lines
Quoted 2 lines

Yes, I agree with you here.

Treating I/O errors/deadlocks due to fsfreeze as a user issue (as things are currently), or working around it? If the latter, a), b), or c)?

Option b) makes sense to me too.

Quoted 59 lines
Quoted 58 lines
Quoted 57 lines

Even then you still have RPCs like guest-shutdown that may do a syslog() that would cause the command to freeze, or in the future we may add the ability for the guest agent to execute user/distro-installed scripts that may/may not need to write to the filesystem. Some these might even be intended to run when the filesystem is frozen...cleanup scripts for databases and whatnot for snapshotting is something I've seen come up.

We can

a) be very restrictive, which I'm not totally against...my initial inclination was to disable everything except guest-fsfreeze-thaw/guest-fsfreeze-status while frozen, but this has some limitations that aren't as obscure/unlikely as one would hope.

b) we can be completely unrestrictive about it and give users the same experiences they'd get at a command-line if they, or another user on the system, was messing with fsfreeze.

c) try to capture some common cases, but make it clear that you can still shoot yourself in the foot using the guest agent on a frozen filesystem, evaluating when to enforce this in code on a case-by-case basis.

Personally I like b), mostly because it's the least work, but also because it's actually the easiest way to set expectations about fsfreeze.

Quoted 6 lines

The guest-file-write command, on the other hand, clearly requires to write to disk, so a client would expect a EWOULDBLOCK error.

EWOULDBLOCK looks good to me. We could define as general rule that commands don't block, so clients should always expect a EWOULDBLOCK.

This falls under 2)

Quoted 6 lines
Quoted 3 lines

There's nothing wrong with opening a read-only
file on a frozen filesystem, it's the fact that we can't log the open that causes us to bail out..

But why do we bail out? Why can't we just ignore the fact we can't log?

This falls under 1)

Quoted 8 lines
Quoted 4 lines

So, what if we just munge the 2 to give the user the proper clues to fix things, and instead return an error like:

"Guest agent failed to log RPC (is the filesystem frozen?)"?

The 'desc' part of an error is a human error descriptor, clients shouldn't parse it. The real error is the error class.

As mentioned above, we could change the error id itself to capture both the immediate error and the underlying error.

#15 lore raw

PATCH v5 3/5 guest agent: add guest agent RPCs/commands

In reply to Michael Roth

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.

---
 qerror.c                   |    4 +
 qerror.h                   |    3 +
 qga/guest-agent-commands.c |  522 ++++++++++++++++++++++++++++++++++++++++++++
 qga/guest-agent-core.h     |    1 +
 4 files changed, 530 insertions(+), 0 deletions(-)
 create mode 100644 qga/guest-agent-commands.c
qerror.c +4 -0
index d7fcd93..24f0c48 100644
@@ -213,6 +213,10 @@ static const QErrorStringTable qerror_table[] = {
         .error_fmt = QERR_VNC_SERVER_FAILED,
         .desc      = "Could not start VNC server on %(target)",
     },
+    {
+        .error_fmt = QERR_QGA_LOGGING_FAILED,
+        .desc      = "failed to write log statement due to logging being disabled",
+    },
     {}
 };
 
qerror.h +3 -0
index 7a89a50..bf3d5a9 100644
@@ -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 */
qga/guest-agent-commands.c new file +522 -0

522 lines not on this page · raw message

qga/guest-agent-core.h +1 -0
index 66d1729..a85a5e4 100644
@@ -18,6 +18,7 @@
 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));
Signature · 1 line
1.7.0.4
#16 lore raw

PATCH 1/1 guest agent: fix for guest agent RPCs/commands

One of my commits must've gotten lost while I was squashing patches. Please apply the attached patch to correct a build issue due to qmp_guest_file_open()'s implementation not matching the qapi-generated prototype. Patch 3 has been updated in the corresponding repo branch if you're pulling from there.

Thanks to Matsuda Daiki for catching this.

---

qga/guest-agent-commands.c +4 -1
index 6f9886a..dbdc3b7 100644
@@ -169,7 +169,7 @@ static void guest_file_handle_remove(int64_t id)
     g_free(data);
 }
 
-int64_t qmp_guest_file_open(const char *filepath, const char *mode, Error **err)
+int64_t qmp_guest_file_open(const char *filepath, bool has_mode, const char *mode, Error **err)
 {
     FILE *fh;
     int fd, ret;
@@ -179,6 +179,9 @@ int64_t qmp_guest_file_open(const char *filepath, const char *mode, Error **err)
         error_set(err, QERR_QGA_LOGGING_FAILED);
         goto out;
     }
+    if (!has_mode) {
+        mode = "r";
+    }
     slog("guest-file-open called, filepath: %s, mode: %s", filepath, 
mode);
     fh = fopen(filepath, mode);
     if (!fh) {
#17 lore raw

Re: [Qemu-devel] [PATCH v5 3/5] guest agent: add guest agent RPCs/commands

In reply to Michael Roth
Quoted 123 lines · On Tue, 14 Jun 2011 15:06:23 -0500 Michael Roth <[email protected]>…

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.

---
 qerror.c                   |    4 +
 qerror.h                   |    3 +
 qga/guest-agent-commands.c |  522 ++++++++++++++++++++++++++++++++++++++++++++
 qga/guest-agent-core.h     |    1 +
 4 files changed, 530 insertions(+), 0 deletions(-)
 create mode 100644 qga/guest-agent-commands.c
qerror.c +4 -0
index d7fcd93..24f0c48 100644
@@ -213,6 +213,10 @@ static const QErrorStringTable qerror_table[] = {
         .error_fmt = QERR_VNC_SERVER_FAILED,
         .desc      = "Could not start VNC server on %(target)",
     },
+    {
+        .error_fmt = QERR_QGA_LOGGING_FAILED,
+        .desc      = "failed to write log statement due to logging being disabled",
+    },
     {}
 };
 
qerror.h +3 -0
index 7a89a50..bf3d5a9 100644
@@ -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 */
qga/guest-agent-commands.c new file +62 -0
new file mode 100644
index 0000000..6f9886a
@@ -0,0 +1,522 @@
+/*
+ * QEMU Guest Agent commands
+ *
+ * Copyright IBM Corp. 2011
+ *
+ * Authors:
+ *  Michael Roth      <[email protected]>
+ *
+ * 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"
+
+static GAState *ga_state;
+
+static bool logging_enabled(void)
+{
+    return ga_logging_enabled(ga_state);
+}
+
+static void disable_logging(void)
+{
+    ga_disable_logging(ga_state);
+}
+
+static void enable_logging(void)
+{
+    ga_enable_logging(ga_state);
+}
+
+/* Note: in some situations, like with the fsfreeze, logging may be
+ * temporarilly disabled. if it is necessary that a command be able
+ * to log for accounting purposes, check logging_enabled() beforehand,
+ * and use the QERR_QGA_LOGGING_DISABLED to generate an error
+ */
+static void slog(const char *fmt, ...)
+{
+    va_list ap;
+
+    va_start(ap, fmt);
+    g_logv("syslog", G_LOG_LEVEL_INFO, fmt, ap);
+    va_end(ap);
+}
+
+int64_t qmp_guest_sync(int64_t id, Error **errp)
+{
+    return id;
+}
+
+void qmp_guest_ping(Error **err)
+{
+    slog("guest-ping called");
+}
+

qga/guest-agent-commands.c line 72

Quoted 10 lines
qga/guest-agent-commands.c new file +10 -0
+struct GuestAgentInfo *qmp_guest_info(Error **err)
+{
+    GuestAgentInfo *info = g_malloc0(sizeof(GuestAgentInfo));
+
+    info->version = g_strdup(QGA_VERSION);
+
+    return info;
+}
+
+void qmp_guest_shutdown(const char *shutdown_mode, Error **err)

I'd call the argument just 'mode'.

Quoted 19 lines
qga/guest-agent-commands.c new file +19 -0
+{
+    int ret;
+    const char *shutdown_flag;
+
+    if (!logging_enabled()) {
+        error_set(err, QERR_QGA_LOGGING_FAILED);
+        return;
+    }
+
+    slog("guest-shutdown called, shutdown_mode: %s", shutdown_mode);
+    if (strcmp(shutdown_mode, "halt") == 0) {
+        shutdown_flag = "-H";
+    } else if (strcmp(shutdown_mode, "powerdown") == 0) {
+        shutdown_flag = "-P";
+    } else if (strcmp(shutdown_mode, "reboot") == 0) {
+        shutdown_flag = "-r";
+    } else {
+        error_set(err, QERR_INVALID_PARAMETER_VALUE, "shutdown_mode",
+                  "halt|powerdown|reboot");

qga/guest-agent-commands.c line 101

Quoted 10 lines
qga/guest-agent-commands.c new file +10 -0
+        return;
+    }
+
+    ret = fork();
+    if (ret == 0) {
+        /* child, start the shutdown */
+        setsid();
+        fclose(stdin);
+        fclose(stdout);
+        fclose(stderr);

Would be nice to have a log file, whose descriptor is passed to the child.

qga/guest-agent-commands.c line 103

Quoted 2 lines
qga/guest-agent-commands.c new file +2 -0
+
+        sleep(5);

Why sleep()?

qga/guest-agent-commands.c line 109

Quoted 6 lines
qga/guest-agent-commands.c new file +6 -0
+        ret = execl("/sbin/shutdown", "shutdown", shutdown_flag, "+0",
+                    "hypervisor initiated shutdown", (char*)NULL);
+        exit(!!ret);
+    } else if (ret < 0) {
+        error_set(err, QERR_UNDEFINED_ERROR);
+    }

Doesn't have the parent process wait for the child, so that exec() errors can be reported?

qga/guest-agent-commands.c line 118

Quoted 9 lines
qga/guest-agent-commands.c new file +9 -0
+}
+
+typedef struct GuestFileHandle {
+    uint64_t id;
+    FILE *fh;
+} GuestFileHandle;
+
+static struct {
+    GSList *filehandles;

Wouldn't this be simpler if we use qemu list implementation instead?

qga/guest-agent-commands.c line 127

Quoted 9 lines
qga/guest-agent-commands.c new file +9 -0
+    uint64_t last_id;
+} guest_file_state;
+
+static int64_t guest_file_handle_add(FILE *fh)
+{
+    GuestFileHandle *gfh;
+
+    gfh = g_malloc(sizeof(GuestFileHandle));
+    gfh->id = guest_file_state.last_id++;

I don't know if the uint64_t limit can be reached in practice, but I'd expect a bitmap, so that you can return ids in guest_file_handle_remove().

Another simpler option would be to use the real fd instead. I mean, the one returned by the guest kernel.

Quoted 43 lines
qga/guest-agent-commands.c new file +43 -0
+    gfh->fh = fh;
+    guest_file_state.filehandles = g_slist_append(guest_file_state.filehandles,
+                                                  gfh);
+    return gfh->id;
+}
+
+static gint guest_file_handle_match(gconstpointer elem, gconstpointer id_p)
+{
+    const uint64_t *id = id_p;
+    const GuestFileHandle *gfh = elem;
+
+    g_assert(gfh);
+    return (gfh->id != *id);
+}
+
+static FILE *guest_file_handle_find(int64_t id)
+{
+    GSList *elem = g_slist_find_custom(guest_file_state.filehandles, &id,
+                                       guest_file_handle_match);
+    GuestFileHandle *gfh;
+
+    if (elem) {
+        g_assert(elem->data);
+        gfh = elem->data;
+        return gfh->fh;
+    }
+
+    return NULL;
+}
+
+static void guest_file_handle_remove(int64_t id)
+{
+    GSList *elem = g_slist_find_custom(guest_file_state.filehandles, &id,
+                                       guest_file_handle_match);
+    gpointer data = elem->data;
+
+    if (!data) {
+        return;
+    }
+    guest_file_state.filehandles = g_slist_remove(guest_file_state.filehandles,
+                                                  data);
+    g_free(data);
+}

qga/guest-agent-commands.c line 180

Quoted 10 lines
qga/guest-agent-commands.c new file +10 -0
+
+int64_t qmp_guest_file_open(const char *filepath, const char *mode, Error **err)
+{
+    FILE *fh;
+    int fd, ret;
+    int64_t id = -1;
+
+    if (!logging_enabled()) {
+        error_set(err, QERR_QGA_LOGGING_FAILED);
+        goto out;

No need to have a goto here, just do return -1. This true for other functions too.

Quoted 31 lines
qga/guest-agent-commands.c new file +31 -0
+    }
+    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);
+        goto out;
+    }
+
+    /* set fd non-blocking to avoid common use cases (like reading from a
+     * named pipe) from hanging the agent
+     */
+    fd = fileno(fh);
+    ret = fcntl(fd, F_GETFL);
+    ret = fcntl(fd, F_SETFL, ret | O_NONBLOCK);
+    if (ret == -1) {
+        error_set(err, QERR_OPEN_FILE_FAILED, filepath);
+        fclose(fh);
+        goto out;
+    }
+
+    id = guest_file_handle_add(fh);
+    slog("guest-file-open, filehandle: %ld", id);
+out:
+    return id;
+}
+
+struct GuestFileRead *qmp_guest_file_read(int64_t filehandle, int64_t count,
+                                          Error **err)
+{
+    GuestFileRead *read_data;
+    guchar *buf;

qga/guest-agent-commands.c line 221

Quoted 10 lines
qga/guest-agent-commands.c new file +10 -0
+    FILE *fh = guest_file_handle_find(filehandle);
+    size_t read_count;
+
+    if (!fh) {
+        error_set(err, QERR_FD_NOT_FOUND, "filehandle");
+        return NULL;
+    }
+
+    read_data = g_malloc0(sizeof(GuestFileRead));
+    buf = g_malloc(count);

What happens if the client passes a bogus value? Like -1 or a very big number?

I think count has to be checked against the file size. You could call stat() and store the value. Also, you're not checking g_malloc()'s return, so a bad allocation can crash the agent.

qga/guest-agent-commands.c line 224

Quoted 3 lines
qga/guest-agent-commands.c new file +3 -0
+
+    read_count = fread(buf, 1, count, fh);
+    buf[read_count] = 0;

We need to allocate an additional byte to do that.

Quoted 22 lines
qga/guest-agent-commands.c new file +22 -0
+    read_data->count = read_count;
+    read_data->eof = feof(fh);
+    if (read_count) {
+        read_data->buf = g_base64_encode(buf, read_count);
+    }
+    g_free(buf);
+    /* clear error and eof. error is generally due to EAGAIN from non-blocking
+     * mode, and no real way to differenitate from a real error since we only
+     * get boolean error flag from ferror()
+     */
+    clearerr(fh);
+
+    return read_data;
+}
+
+GuestFileWrite *qmp_guest_file_write(int64_t filehandle, const char *data_b64,
+                                     int64_t count, Error **err)
+{
+    GuestFileWrite *write_data;
+    guchar *data;
+    gsize data_len;
+    int write_count;

qga/guest-agent-commands.c line 256

Quoted 10 lines
qga/guest-agent-commands.c new file +10 -0
+    FILE *fh = guest_file_handle_find(filehandle);
+
+    if (!fh) {
+        error_set(err, QERR_FD_NOT_FOUND, "filehandle");
+        return NULL;
+    }
+
+    write_data = g_malloc0(sizeof(GuestFileWrite));
+    data = g_base64_decode(data_b64, &data_len);
+    write_count = fwrite(data, 1, MIN(count, data_len), fh);

IMO, we should do what the user is asking us to do, if it's impossible we should return an error. IOW, we should use count. It's okay if the buffer is bigger then count, but if count is bigger then we should return an error.

What does write() do in this case? segfaults?

Quoted 50 lines
qga/guest-agent-commands.c new file +50 -0
+    write_data->count = write_count;
+    write_data->eof = feof(fh);
+    g_free(data);
+    clearerr(fh);
+
+    return write_data;
+}
+
+struct GuestFileSeek *qmp_guest_file_seek(int64_t filehandle, int64_t offset,
+                                          int64_t whence, Error **err)
+{
+    GuestFileSeek *seek_data;
+    FILE *fh = guest_file_handle_find(filehandle);
+    int ret;
+
+    if (!fh) {
+        error_set(err, QERR_FD_NOT_FOUND, "filehandle");
+        return NULL;
+    }
+
+    seek_data = g_malloc0(sizeof(GuestFileRead));
+    ret = fseek(fh, offset, whence);
+    if (ret == -1) {
+        error_set(err, QERR_UNDEFINED_ERROR);
+        g_free(seek_data);
+        return NULL;
+    }
+    seek_data->position = ftell(fh);
+    seek_data->eof = feof(fh);
+    clearerr(fh);
+
+    return seek_data;
+}
+
+void qmp_guest_file_close(int64_t filehandle, Error **err)
+{
+    FILE *fh = guest_file_handle_find(filehandle);
+
+    if (!logging_enabled()) {
+        error_set(err, QERR_QGA_LOGGING_FAILED);
+        return;
+    }
+    slog("guest-file-close called, filehandle: %ld", filehandle);
+    if (!fh) {
+        error_set(err, QERR_FD_NOT_FOUND, "filehandle");
+        return;
+    }
+
+    fclose(fh);
+    guest_file_handle_remove(filehandle);

qga/guest-agent-commands.c line 316

Quoted 10 lines
qga/guest-agent-commands.c new file +10 -0
+}
+
+/*
+ * Walk the mount table and build a list of local file systems
+ */
+
+struct direntry {
+    char *dirname;
+    char *devtype;
+    struct direntry *next;

Wouldn't it be better to use qemu's list implementation?

Quoted 90 lines
qga/guest-agent-commands.c new file +90 -0
+};
+
+struct {
+    struct direntry *mount_list;
+    GuestFsfreezeStatus status;
+} guest_fsfreeze_state;
+
+static int guest_fsfreeze_build_mount_list(void)
+{
+    struct mntent *mnt;
+    struct direntry *entry;
+    struct direntry *next;
+    char const *mtab = MOUNTED;
+    FILE *fp;
+
+    fp = setmntent(mtab, "r");
+    if (!fp) {
+        g_warning("fsfreeze: unable to read mtab");
+        goto fail;
+    }
+
+    while ((mnt = getmntent(fp))) {
+        /*
+         * An entry which device name doesn't start with a '/' is
+         * either a dummy file system or a network file system.
+         * Add special handling for smbfs and cifs as is done by
+         * coreutils as well.
+         */
+        if ((mnt->mnt_fsname[0] != '/') ||
+            (strcmp(mnt->mnt_type, "smbfs") == 0) ||
+            (strcmp(mnt->mnt_type, "cifs") == 0)) {
+            continue;
+        }
+
+        entry = g_malloc(sizeof(struct direntry));
+        entry->dirname = qemu_strdup(mnt->mnt_dir);
+        entry->devtype = qemu_strdup(mnt->mnt_type);
+        entry->next = guest_fsfreeze_state.mount_list;
+
+        guest_fsfreeze_state.mount_list = entry;
+    }
+
+    endmntent(fp);
+
+    return 0;
+
+fail:
+    while(guest_fsfreeze_state.mount_list) {
+        next = guest_fsfreeze_state.mount_list->next;
+        g_free(guest_fsfreeze_state.mount_list->dirname);
+        g_free(guest_fsfreeze_state.mount_list->devtype);
+        g_free(guest_fsfreeze_state.mount_list);
+        guest_fsfreeze_state.mount_list = next;
+    }
+
+    return -1;
+}
+
+/*
+ * Return status of freeze/thaw
+ */
+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 direntry *entry;
+    int fd;
+
+    if (!logging_enabled()) {
+        error_set(err, QERR_QGA_LOGGING_FAILED);
+        goto out;
+    }
+
+    slog("guest-fsfreeze called");
+
+    if (guest_fsfreeze_state.status != GUEST_FSFREEZE_STATUS_THAWED) {
+        ret = 0;
+        goto out;
+    }
+
+    ret = guest_fsfreeze_build_mount_list();
+    if (ret < 0) {

qga/guest-agent-commands.c line 416

Quoted 10 lines
qga/guest-agent-commands.c new file +10 -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();
+
+    entry = guest_fsfreeze_state.mount_list;
+    while(entry) {

A for() loop would be clearer, imho.

Quoted 10 lines
qga/guest-agent-commands.c new file +10 -0
+        fd = qemu_open(entry->dirname, O_RDONLY);
+        if (fd == -1) {
+            ret = errno;
+            goto error;
+        }
+
+        /* we try to cull filesytems we know won't work in advance, but other
+         * filesytems may not implement fsfreeze for less obvious reasons.
+         * these will reason EOPNOTSUPP, so we simply ignore them. when
+         * thawing, these filesystems will return an EINVAL instead, due to

qga/guest-agent-commands.c line 436

Quoted 10 lines
qga/guest-agent-commands.c new file +10 -0
+         * 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;

Does the FIFREEZE ioctl() call returns the errno code? If it doesn't, then we have to set it as the qemu_open() does above.

Quoted 5 lines
qga/guest-agent-commands.c new file +5 -0
+        }
+        close(fd);
+
+        entry = entry->next;
+        i++;

qga/guest-agent-commands.c line 451

Quoted 10 lines
qga/guest-agent-commands.c new file +10 -0
+    }
+
+    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;
+    }

I'm not sure this correct. What happens if an error happens in the first iteration of the while loop? A better way would to check 'ret' instead.

Quoted 83 lines
qga/guest-agent-commands.c new file +71 -0
+    goto out;
+}
+
+/*
+ * Walk list of frozen file systems in the guest, and thaw them.
+ */
+int64_t qmp_guest_fsfreeze_thaw(Error **err)
+{
+    int ret;
+    struct direntry *entry;
+    int fd, i = 0;
+
+    if (guest_fsfreeze_state.status != GUEST_FSFREEZE_STATUS_FROZEN &&
+        guest_fsfreeze_state.status != GUEST_FSFREEZE_STATUS_INPROGRESS) {
+        ret = 0;
+        goto out_enable_logging;
+    }
+
+    while((entry = guest_fsfreeze_state.mount_list)) {
+        fd = qemu_open(entry->dirname, O_RDONLY);
+        if (fd == -1) {
+            ret = -errno;
+            goto out;
+        }
+        ret = ioctl(fd, FITHAW);
+        if (ret < 0 && errno != EOPNOTSUPP && errno != EINVAL) {
+            ret = -errno;
+            close(fd);
+            goto out;
+        }
+        close(fd);
+
+        guest_fsfreeze_state.mount_list = entry->next;
+        g_free(entry->dirname);
+        g_free(entry->devtype);
+        g_free(entry);
+        i++;
+    }
+
+    guest_fsfreeze_state.status = 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;
+}
+
+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);
+}
qga/guest-agent-core.h +1 -0
index 66d1729..a85a5e4 100644
@@ -18,6 +18,7 @@
 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));
#18 lore raw

Re: [Qemu-devel] [PATCH v5 3/5] guest agent: add guest agent RPCs/commands

Quoted 172 lines · Luiz Capitulino
Quoted 123 lines · On Tue, 14 Jun 2011 15:06:23 -0500 Michael Roth<[email protected]>…

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.

---
  qerror.c                   |    4 +
  qerror.h                   |    3 +
  qga/guest-agent-commands.c |  522 ++++++++++++++++++++++++++++++++++++++++++++
  qga/guest-agent-core.h     |    1 +
  4 files changed, 530 insertions(+), 0 deletions(-)

create mode 100644 qga/guest-agent-commands.c

qerror.c +4 -0
index d7fcd93..24f0c48 100644
@@ -213,6 +213,10 @@ static const QErrorStringTable qerror_table[] = {
          .error_fmt = QERR_VNC_SERVER_FAILED,
          .desc      = "Could not start VNC server on %(target)",
      },
+    {
+        .error_fmt = QERR_QGA_LOGGING_FAILED,
+        .desc      = "failed to write log statement due to logging being disabled",
+    },
      {}
  };
 
qerror.h +3 -0
index 7a89a50..bf3d5a9 100644
@@ -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 */
qga/guest-agent-commands.c new file +62 -0
new file mode 100644
index 0000000..6f9886a
@@ -0,0 +1,522 @@
+/*
+ * QEMU Guest Agent commands
+ *
+ * Copyright IBM Corp. 2011
+ *
+ * Authors:
+ *  Michael Roth<[email protected]>
+ *
+ * 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"
+
+static GAState *ga_state;
+
+static bool logging_enabled(void)
+{
+    return ga_logging_enabled(ga_state);
+}
+
+static void disable_logging(void)
+{
+    ga_disable_logging(ga_state);
+}
+
+static void enable_logging(void)
+{
+    ga_enable_logging(ga_state);
+}
+
+/* Note: in some situations, like with the fsfreeze, logging may be
+ * temporarilly disabled. if it is necessary that a command be able
+ * to log for accounting purposes, check logging_enabled() beforehand,
+ * and use the QERR_QGA_LOGGING_DISABLED to generate an error
+ */
+static void slog(const char *fmt, ...)
+{
+    va_list ap;
+
+    va_start(ap, fmt);
+    g_logv("syslog", G_LOG_LEVEL_INFO, fmt, ap);
+    va_end(ap);
+}
+
+int64_t qmp_guest_sync(int64_t id, Error **errp)
+{
+    return id;
+}
+
+void qmp_guest_ping(Error **err)
+{
+    slog("guest-ping called");
+}
+

qga/guest-agent-commands.c line 72

Quoted 10 lines
qga/guest-agent-commands.c new file +10 -0
+struct GuestAgentInfo *qmp_guest_info(Error **err)
+{
+    GuestAgentInfo *info = g_malloc0(sizeof(GuestAgentInfo));
+
+    info->version = g_strdup(QGA_VERSION);
+
+    return info;
+}
+
+void qmp_guest_shutdown(const char *shutdown_mode, Error **err)

I'd call the argument just 'mode'.

Quoted 19 lines
qga/guest-agent-commands.c new file +19 -0
+{
+    int ret;
+    const char *shutdown_flag;
+
+    if (!logging_enabled()) {
+        error_set(err, QERR_QGA_LOGGING_FAILED);
+        return;
+    }
+
+    slog("guest-shutdown called, shutdown_mode: %s", shutdown_mode);
+    if (strcmp(shutdown_mode, "halt") == 0) {
+        shutdown_flag = "-H";
+    } else if (strcmp(shutdown_mode, "powerdown") == 0) {
+        shutdown_flag = "-P";
+    } else if (strcmp(shutdown_mode, "reboot") == 0) {
+        shutdown_flag = "-r";
+    } else {
+        error_set(err, QERR_INVALID_PARAMETER_VALUE, "shutdown_mode",
+                  "halt|powerdown|reboot");

qga/guest-agent-commands.c line 101

Quoted 10 lines
qga/guest-agent-commands.c new file +10 -0
+        return;
+    }
+
+    ret = fork();
+    if (ret == 0) {
+        /* child, start the shutdown */
+        setsid();
+        fclose(stdin);
+        fclose(stdout);
+        fclose(stderr);

Would be nice to have a log file, whose descriptor is passed to the child.

Quoted 5 lines

qga/guest-agent-commands.c line 103

Quoted 2 lines
qga/guest-agent-commands.c new file +2 -0
+
+        sleep(5);

Why sleep()?

Want to give the agent time to send a response. It's still racy, but less so that immediately issuing the shutdown. Ideal we'd push the sleep() into shutdown's time param, but that only has minute resolution.

Quoted 9 lines

qga/guest-agent-commands.c line 109

Quoted 6 lines
qga/guest-agent-commands.c new file +6 -0
+        ret = execl("/sbin/shutdown", "shutdown", shutdown_flag, "+0",
+                    "hypervisor initiated shutdown", (char*)NULL);
+        exit(!!ret);
+    } else if (ret<  0) {
+        error_set(err, QERR_UNDEFINED_ERROR);
+    }

Doesn't have the parent process wait for the child, so that exec() errors can be reported?

The exec() won't return until the shutdown is executed, so the RPC's behavior would be racy. At some point I documented that the shutdown is an async "request" that may or may not complete but that was lost in the reworking. I'll clarify in the schema.

Quoted 13 lines

qga/guest-agent-commands.c line 118

Quoted 10 lines
qga/guest-agent-commands.c new file +9 -0
+}
+
+typedef struct GuestFileHandle {
+    uint64_t id;
+    FILE *fh;
+} GuestFileHandle;
+
+static struct {
+    GSList *filehandles;

Wouldn't this be simpler if we use qemu list implementation instead?

YES! This stuff is terribly verbose. I was trying to stick with glib outside of QMP/QAPI stuff though...and I think more glib stuff will find it's way into here over time that'll make appearances of GSList/gmalloc/etc inevitable.

But if intermixing is not a big deal I'm more than happy to "allow" qemu malloc/list stuff where it makes sense, and refactor these accordingly.

Quoted 13 lines

qga/guest-agent-commands.c line 127

Quoted 9 lines
qga/guest-agent-commands.c new file +9 -0
+    uint64_t last_id;
+} guest_file_state;
+
+static int64_t guest_file_handle_add(FILE *fh)
+{
+    GuestFileHandle *gfh;
+
+    gfh = g_malloc(sizeof(GuestFileHandle));
+    gfh->id = guest_file_state.last_id++;

I don't know if the uint64_t limit can be reached in practice, but I'd expect a bitmap, so that you can return ids in guest_file_handle_remove().

Quoted 3 lines

Another simpler option would be to use the real fd instead. I mean, the one returned by the guest kernel.

I think I'll go with this. I was hesitant to do this at first since I didn't want users specifying FDs opened outside of guest-file-open, but so long as we only rely on our internal list of open FDs I guess that's not applicable.

Quoted 102 lines
Quoted 43 lines
qga/guest-agent-commands.c new file +43 -0
+    gfh->fh = fh;
+    guest_file_state.filehandles = g_slist_append(guest_file_state.filehandles,
+                                                  gfh);
+    return gfh->id;
+}
+
+static gint guest_file_handle_match(gconstpointer elem, gconstpointer id_p)
+{
+    const uint64_t *id = id_p;
+    const GuestFileHandle *gfh = elem;
+
+    g_assert(gfh);
+    return (gfh->id != *id);
+}
+
+static FILE *guest_file_handle_find(int64_t id)
+{
+    GSList *elem = g_slist_find_custom(guest_file_state.filehandles,&id,
+                                       guest_file_handle_match);
+    GuestFileHandle *gfh;
+
+    if (elem) {
+        g_assert(elem->data);
+        gfh = elem->data;
+        return gfh->fh;
+    }
+
+    return NULL;
+}
+
+static void guest_file_handle_remove(int64_t id)
+{
+    GSList *elem = g_slist_find_custom(guest_file_state.filehandles,&id,
+                                       guest_file_handle_match);
+    gpointer data = elem->data;
+
+    if (!data) {
+        return;
+    }
+    guest_file_state.filehandles = g_slist_remove(guest_file_state.filehandles,
+                                                  data);
+    g_free(data);
+}

qga/guest-agent-commands.c line 180

Quoted 10 lines
qga/guest-agent-commands.c new file +10 -0
+
+int64_t qmp_guest_file_open(const char *filepath, const char *mode, Error **err)
+{
+    FILE *fh;
+    int fd, ret;
+    int64_t id = -1;
+
+    if (!logging_enabled()) {
+        error_set(err, QERR_QGA_LOGGING_FAILED);
+        goto out;

No need to have a goto here, just do return -1. This true for other functions too.

Quoted 31 lines
qga/guest-agent-commands.c new file +31 -0
+    }
+    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);
+        goto out;
+    }
+
+    /* set fd non-blocking to avoid common use cases (like reading from a
+     * named pipe) from hanging the agent
+     */
+    fd = fileno(fh);
+    ret = fcntl(fd, F_GETFL);
+    ret = fcntl(fd, F_SETFL, ret | O_NONBLOCK);
+    if (ret == -1) {
+        error_set(err, QERR_OPEN_FILE_FAILED, filepath);
+        fclose(fh);
+        goto out;
+    }
+
+    id = guest_file_handle_add(fh);
+    slog("guest-file-open, filehandle: %ld", id);
+out:
+    return id;
+}
+
+struct GuestFileRead *qmp_guest_file_read(int64_t filehandle, int64_t count,
+                                          Error **err)
+{
+    GuestFileRead *read_data;
+    guchar *buf;

qga/guest-agent-commands.c line 221

Quoted 10 lines
qga/guest-agent-commands.c new file +10 -0
+    FILE *fh = guest_file_handle_find(filehandle);
+    size_t read_count;
+
+    if (!fh) {
+        error_set(err, QERR_FD_NOT_FOUND, "filehandle");
+        return NULL;
+    }
+
+    read_data = g_malloc0(sizeof(GuestFileRead));
+    buf = g_malloc(count);

What happens if the client passes a bogus value? Like -1 or a very big number?

Quoted 4 lines

I think count has to be checked against the file size. You could call stat() and store the value. Also, you're not checking g_malloc()'s return, so a bad allocation can crash the agent.

All good points

Quoted 99 lines

qga/guest-agent-commands.c line 224

Quoted 3 lines
qga/guest-agent-commands.c new file +3 -0
+
+    read_count = fread(buf, 1, count, fh);
+    buf[read_count] = 0;

We need to allocate an additional byte to do that.

Quoted 22 lines
qga/guest-agent-commands.c new file +22 -0
+    read_data->count = read_count;
+    read_data->eof = feof(fh);
+    if (read_count) {
+        read_data->buf = g_base64_encode(buf, read_count);
+    }
+    g_free(buf);
+    /* clear error and eof. error is generally due to EAGAIN from non-blocking
+     * mode, and no real way to differenitate from a real error since we only
+     * get boolean error flag from ferror()
+     */
+    clearerr(fh);
+
+    return read_data;
+}
+
+GuestFileWrite *qmp_guest_file_write(int64_t filehandle, const char *data_b64,
+                                     int64_t count, Error **err)
+{
+    GuestFileWrite *write_data;
+    guchar *data;
+    gsize data_len;
+    int write_count;

qga/guest-agent-commands.c line 256

Quoted 10 lines
qga/guest-agent-commands.c new file +10 -0
+    FILE *fh = guest_file_handle_find(filehandle);
+
+    if (!fh) {
+        error_set(err, QERR_FD_NOT_FOUND, "filehandle");
+        return NULL;
+    }
+
+    write_data = g_malloc0(sizeof(GuestFileWrite));
+    data = g_base64_decode(data_b64,&data_len);
+    write_count = fwrite(data, 1, MIN(count, data_len), fh);

IMO, we should do what the user is asking us to do, if it's impossible we should return an error. IOW, we should use count. It's okay if the buffer is bigger then count, but if count is bigger then we should return an error.

What does write() do in this case? segfaults?

Quoted 54 lines
qga/guest-agent-commands.c new file +54 -0
+    write_data->count = write_count;
+    write_data->eof = feof(fh);
+    g_free(data);
+    clearerr(fh);
+
+    return write_data;
+}
+
+struct GuestFileSeek *qmp_guest_file_seek(int64_t filehandle, int64_t offset,
+                                          int64_t whence, Error **err)
+{
+    GuestFileSeek *seek_data;
+    FILE *fh = guest_file_handle_find(filehandle);
+    int ret;
+
+    if (!fh) {
+        error_set(err, QERR_FD_NOT_FOUND, "filehandle");
+        return NULL;
+    }
+
+    seek_data = g_malloc0(sizeof(GuestFileRead));
+    ret = fseek(fh, offset, whence);
+    if (ret == -1) {
+        error_set(err, QERR_UNDEFINED_ERROR);
+        g_free(seek_data);
+        return NULL;
+    }
+    seek_data->position = ftell(fh);
+    seek_data->eof = feof(fh);
+    clearerr(fh);
+
+    return seek_data;
+}
+
+void qmp_guest_file_close(int64_t filehandle, Error **err)
+{
+    FILE *fh = guest_file_handle_find(filehandle);
+
+    if (!logging_enabled()) {
+        error_set(err, QERR_QGA_LOGGING_FAILED);
+        return;
+    }
+    slog("guest-file-close called, filehandle: %ld", filehandle);
+    if (!fh) {
+        error_set(err, QERR_FD_NOT_FOUND, "filehandle");
+        return;
+    }
+
+    fclose(fh);
+    guest_file_handle_remove(filehandle);
+}
+
+/*
+ * Walk the mount table and build a list of local file systems
Quoted 9 lines

qga/guest-agent-commands.c line 316

Quoted 6 lines
qga/guest-agent-commands.c new file +6 -0
+ */
+
+struct direntry {
+    char *dirname;
+    char *devtype;
+    struct direntry *next;

Wouldn't it be better to use qemu's list implementation?

yes, ill fix all the list stuff up to use qemu's

Quoted 124 lines
Quoted 90 lines
qga/guest-agent-commands.c new file +90 -0
+};
+
+struct {
+    struct direntry *mount_list;
+    GuestFsfreezeStatus status;
+} guest_fsfreeze_state;
+
+static int guest_fsfreeze_build_mount_list(void)
+{
+    struct mntent *mnt;
+    struct direntry *entry;
+    struct direntry *next;
+    char const *mtab = MOUNTED;
+    FILE *fp;
+
+    fp = setmntent(mtab, "r");
+    if (!fp) {
+        g_warning("fsfreeze: unable to read mtab");
+        goto fail;
+    }
+
+    while ((mnt = getmntent(fp))) {
+        /*
+         * An entry which device name doesn't start with a '/' is
+         * either a dummy file system or a network file system.
+         * Add special handling for smbfs and cifs as is done by
+         * coreutils as well.
+         */
+        if ((mnt->mnt_fsname[0] != '/') ||
+            (strcmp(mnt->mnt_type, "smbfs") == 0) ||
+            (strcmp(mnt->mnt_type, "cifs") == 0)) {
+            continue;
+        }
+
+        entry = g_malloc(sizeof(struct direntry));
+        entry->dirname = qemu_strdup(mnt->mnt_dir);
+        entry->devtype = qemu_strdup(mnt->mnt_type);
+        entry->next = guest_fsfreeze_state.mount_list;
+
+        guest_fsfreeze_state.mount_list = entry;
+    }
+
+    endmntent(fp);
+
+    return 0;
+
+fail:
+    while(guest_fsfreeze_state.mount_list) {
+        next = guest_fsfreeze_state.mount_list->next;
+        g_free(guest_fsfreeze_state.mount_list->dirname);
+        g_free(guest_fsfreeze_state.mount_list->devtype);
+        g_free(guest_fsfreeze_state.mount_list);
+        guest_fsfreeze_state.mount_list = next;
+    }
+
+    return -1;
+}
+
+/*
+ * Return status of freeze/thaw
+ */
+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 direntry *entry;
+    int fd;
+
+    if (!logging_enabled()) {
+        error_set(err, QERR_QGA_LOGGING_FAILED);
+        goto out;
+    }
+
+    slog("guest-fsfreeze called");
+
+    if (guest_fsfreeze_state.status != GUEST_FSFREEZE_STATUS_THAWED) {
+        ret = 0;
+        goto out;
+    }
+
+    ret = guest_fsfreeze_build_mount_list();
+    if (ret<  0) {

qga/guest-agent-commands.c line 416

Quoted 10 lines
qga/guest-agent-commands.c new file +10 -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();
+
+    entry = guest_fsfreeze_state.mount_list;
+    while(entry) {

A for() loop would be clearer, imho.

Quoted 10 lines
qga/guest-agent-commands.c new file +10 -0
+        fd = qemu_open(entry->dirname, O_RDONLY);
+        if (fd == -1) {
+            ret = errno;
+            goto error;
+        }
+
+        /* we try to cull filesytems we know won't work in advance, but other
+         * filesytems may not implement fsfreeze for less obvious reasons.
+         * these will reason EOPNOTSUPP, so we simply ignore them. when
+         * thawing, these filesystems will return an EINVAL instead, due to

qga/guest-agent-commands.c line 436

Quoted 10 lines
qga/guest-agent-commands.c new file +10 -0
+         * 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;
Quoted 3 lines

Does the FIFREEZE ioctl() call returns the errno code? If it doesn't, then we have to set it as the qemu_open() does above.

Nope, -1 + errno, good catch.

Quoted 11 lines
Quoted 11 lines
qga/guest-agent-commands.c new file +11 -0
+        }
+        close(fd);
+
+        entry = entry->next;
+        i++;
+    }
+
+    guest_fsfreeze_state.status = GUEST_FSFREEZE_STATUS_FROZEN;
+    ret = i;
+out:
+    return ret;
Quoted 9 lines

qga/guest-agent-commands.c line 451

Quoted 4 lines
qga/guest-agent-commands.c new file +4 -0
+error:
+    if (i>  0) {
+        guest_fsfreeze_state.status = GUEST_FSFREEZE_STATUS_ERROR;
+    }

I'm not sure this correct. What happens if an error happens in the first iteration of the while loop? A better way would to check 'ret' instead.

I believe this is meant to track states where some filesystems have been frozen and others not. We want to return the count, but not the error via fsfreeze_status. If we bail in the first iteration it means FIFREEZE was never completed successfully. We should reset state the THAWED then though.

Quoted 84 lines
Quoted 83 lines
qga/guest-agent-commands.c new file +71 -0
+    goto out;
+}
+
+/*
+ * Walk list of frozen file systems in the guest, and thaw them.
+ */
+int64_t qmp_guest_fsfreeze_thaw(Error **err)
+{
+    int ret;
+    struct direntry *entry;
+    int fd, i = 0;
+
+    if (guest_fsfreeze_state.status != GUEST_FSFREEZE_STATUS_FROZEN&&
+        guest_fsfreeze_state.status != GUEST_FSFREEZE_STATUS_INPROGRESS) {
+        ret = 0;
+        goto out_enable_logging;
+    }
+
+    while((entry = guest_fsfreeze_state.mount_list)) {
+        fd = qemu_open(entry->dirname, O_RDONLY);
+        if (fd == -1) {
+            ret = -errno;
+            goto out;
+        }
+        ret = ioctl(fd, FITHAW);
+        if (ret<  0&&  errno != EOPNOTSUPP&&  errno != EINVAL) {
+            ret = -errno;
+            close(fd);
+            goto out;
+        }
+        close(fd);
+
+        guest_fsfreeze_state.mount_list = entry->next;
+        g_free(entry->dirname);
+        g_free(entry->devtype);
+        g_free(entry);
+        i++;
+    }
+
+    guest_fsfreeze_state.status = 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;
+}
+
+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);
+}
qga/guest-agent-core.h +1 -0
index 66d1729..a85a5e4 100644
@@ -18,6 +18,7 @@
  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));
#19 lore raw

Re: [Qemu-devel] [PATCH v5 3/5] guest agent: add guest agent RPCs/commands

Quoted 173 lines · On Fri, 17 Jun 2011 15:19:56 -0500 Michael Roth <[email protected]>…
Quoted 172 lines · Luiz Capitulino
Quoted 123 lines · On Tue, 14 Jun 2011 15:06:23 -0500 Michael Roth<[email protected]>…

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.

---
  qerror.c                   |    4 +
  qerror.h                   |    3 +
  qga/guest-agent-commands.c |  522 ++++++++++++++++++++++++++++++++++++++++++++
  qga/guest-agent-core.h     |    1 +
  4 files changed, 530 insertions(+), 0 deletions(-)

create mode 100644 qga/guest-agent-commands.c

qerror.c +4 -0
index d7fcd93..24f0c48 100644
@@ -213,6 +213,10 @@ static const QErrorStringTable qerror_table[] = {
          .error_fmt = QERR_VNC_SERVER_FAILED,
          .desc      = "Could not start VNC server on %(target)",
      },
+    {
+        .error_fmt = QERR_QGA_LOGGING_FAILED,
+        .desc      = "failed to write log statement due to logging being disabled",
+    },
      {}
  };
 
qerror.h +3 -0
index 7a89a50..bf3d5a9 100644
@@ -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 */
qga/guest-agent-commands.c new file +62 -0
new file mode 100644
index 0000000..6f9886a
@@ -0,0 +1,522 @@
+/*
+ * QEMU Guest Agent commands
+ *
+ * Copyright IBM Corp. 2011
+ *
+ * Authors:
+ *  Michael Roth<[email protected]>
+ *
+ * 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"
+
+static GAState *ga_state;
+
+static bool logging_enabled(void)
+{
+    return ga_logging_enabled(ga_state);
+}
+
+static void disable_logging(void)
+{
+    ga_disable_logging(ga_state);
+}
+
+static void enable_logging(void)
+{
+    ga_enable_logging(ga_state);
+}
+
+/* Note: in some situations, like with the fsfreeze, logging may be
+ * temporarilly disabled. if it is necessary that a command be able
+ * to log for accounting purposes, check logging_enabled() beforehand,
+ * and use the QERR_QGA_LOGGING_DISABLED to generate an error
+ */
+static void slog(const char *fmt, ...)
+{
+    va_list ap;
+
+    va_start(ap, fmt);
+    g_logv("syslog", G_LOG_LEVEL_INFO, fmt, ap);
+    va_end(ap);
+}
+
+int64_t qmp_guest_sync(int64_t id, Error **errp)
+{
+    return id;
+}
+
+void qmp_guest_ping(Error **err)
+{
+    slog("guest-ping called");
+}
+

qga/guest-agent-commands.c line 72

Quoted 10 lines
qga/guest-agent-commands.c new file +10 -0
+struct GuestAgentInfo *qmp_guest_info(Error **err)
+{
+    GuestAgentInfo *info = g_malloc0(sizeof(GuestAgentInfo));
+
+    info->version = g_strdup(QGA_VERSION);
+
+    return info;
+}
+
+void qmp_guest_shutdown(const char *shutdown_mode, Error **err)

I'd call the argument just 'mode'.

Quoted 19 lines
qga/guest-agent-commands.c new file +19 -0
+{
+    int ret;
+    const char *shutdown_flag;
+
+    if (!logging_enabled()) {
+        error_set(err, QERR_QGA_LOGGING_FAILED);
+        return;
+    }
+
+    slog("guest-shutdown called, shutdown_mode: %s", shutdown_mode);
+    if (strcmp(shutdown_mode, "halt") == 0) {
+        shutdown_flag = "-H";
+    } else if (strcmp(shutdown_mode, "powerdown") == 0) {
+        shutdown_flag = "-P";
+    } else if (strcmp(shutdown_mode, "reboot") == 0) {
+        shutdown_flag = "-r";
+    } else {
+        error_set(err, QERR_INVALID_PARAMETER_VALUE, "shutdown_mode",
+                  "halt|powerdown|reboot");

qga/guest-agent-commands.c line 101

Quoted 10 lines
qga/guest-agent-commands.c new file +10 -0
+        return;
+    }
+
+    ret = fork();
+    if (ret == 0) {
+        /* child, start the shutdown */
+        setsid();
+        fclose(stdin);
+        fclose(stdout);
+        fclose(stderr);

Would be nice to have a log file, whose descriptor is passed to the child.

Quoted 8 lines
Quoted 5 lines

qga/guest-agent-commands.c line 103

Quoted 2 lines
qga/guest-agent-commands.c new file +2 -0
+
+        sleep(5);

Why sleep()?

Want to give the agent time to send a response. It's still racy, but less so that immediately issuing the shutdown.

It's only less race for the particular test-case you're testing :)

Quoted 2 lines

Ideal we'd push the
sleep() into shutdown's time param, but that only has minute resolution.

I don't think so, because this is an implementation detail. The right thing would be to make the child wait for an "go ahead" signal from the parent.

This could be done by calling pause() in the child and making the parent send a SIGUSR1 when the response has been sent.

One way to do it w/o tying sever actions (like to send a response) to agent commands, would be to introduce a "response sent" callback, so that commands could register and have their specific actions executed at the right time.

Quoted 8 lines
Quoted 8 lines
Quoted 7 lines
qga/guest-agent-commands.c new file +6 -0
+        ret = execl("/sbin/shutdown", "shutdown", shutdown_flag, "+0",
+                    "hypervisor initiated shutdown", (char*)NULL);
+        exit(!!ret);
+    } else if (ret<  0) {
+        error_set(err, QERR_UNDEFINED_ERROR);
+    }
Quoted 7 lines
Quoted 2 lines

Doesn't have the parent process wait for the child, so that exec() errors can be reported?

The exec() won't return until the shutdown is executed, so the RPC's behavior would be racy. At some point I documented that the shutdown is an async "request" that may or may not complete but that was lost in the reworking. I'll clarify in the schema.

Seems fine.

Quoted 15 lines
Quoted 14 lines

qga/guest-agent-commands.c line 118

Quoted 11 lines
qga/guest-agent-commands.c new file +9 -0
+}
+
+typedef struct GuestFileHandle {
+    uint64_t id;
+    FILE *fh;
+} GuestFileHandle;
+
+static struct {
+    GSList *filehandles;

Wouldn't this be simpler if we use qemu list implementation instead?

Quoted 7 lines

YES! This stuff is terribly verbose. I was trying to stick with glib outside of QMP/QAPI stuff though...and I think more glib stuff will find it's way into here over time that'll make appearances of GSList/gmalloc/etc inevitable.

But if intermixing is not a big deal I'm more than happy to "allow" qemu malloc/list stuff where it makes sense, and refactor these accordingly.

It makes sense to me.

Quoted 13 lines
Quoted 13 lines

qga/guest-agent-commands.c line 127

Quoted 9 lines
qga/guest-agent-commands.c new file +9 -0
+    uint64_t last_id;
+} guest_file_state;
+
+static int64_t guest_file_handle_add(FILE *fh)
+{
+    GuestFileHandle *gfh;
+
+    gfh = g_malloc(sizeof(GuestFileHandle));
+    gfh->id = guest_file_state.last_id++;

I don't know if the uint64_t limit can be reached in practice, but I'd expect a bitmap, so that you can return ids in guest_file_handle_remove().

Quoted 8 lines
Quoted 3 lines

Another simpler option would be to use the real fd instead. I mean, the one returned by the guest kernel.

I think I'll go with this. I was hesitant to do this at first since I didn't want users specifying FDs opened outside of guest-file-open, but so long as we only rely on our internal list of open FDs I guess that's not applicable.

Yes.

Quoted 462 lines
Quoted 102 lines
Quoted 43 lines
qga/guest-agent-commands.c new file +43 -0
+    gfh->fh = fh;
+    guest_file_state.filehandles = g_slist_append(guest_file_state.filehandles,
+                                                  gfh);
+    return gfh->id;
+}
+
+static gint guest_file_handle_match(gconstpointer elem, gconstpointer id_p)
+{
+    const uint64_t *id = id_p;
+    const GuestFileHandle *gfh = elem;
+
+    g_assert(gfh);
+    return (gfh->id != *id);
+}
+
+static FILE *guest_file_handle_find(int64_t id)
+{
+    GSList *elem = g_slist_find_custom(guest_file_state.filehandles,&id,
+                                       guest_file_handle_match);
+    GuestFileHandle *gfh;
+
+    if (elem) {
+        g_assert(elem->data);
+        gfh = elem->data;
+        return gfh->fh;
+    }
+
+    return NULL;
+}
+
+static void guest_file_handle_remove(int64_t id)
+{
+    GSList *elem = g_slist_find_custom(guest_file_state.filehandles,&id,
+                                       guest_file_handle_match);
+    gpointer data = elem->data;
+
+    if (!data) {
+        return;
+    }
+    guest_file_state.filehandles = g_slist_remove(guest_file_state.filehandles,
+                                                  data);
+    g_free(data);
+}

qga/guest-agent-commands.c line 180

Quoted 10 lines
qga/guest-agent-commands.c new file +10 -0
+
+int64_t qmp_guest_file_open(const char *filepath, const char *mode, Error **err)
+{
+    FILE *fh;
+    int fd, ret;
+    int64_t id = -1;
+
+    if (!logging_enabled()) {
+        error_set(err, QERR_QGA_LOGGING_FAILED);
+        goto out;

No need to have a goto here, just do return -1. This true for other functions too.

Quoted 31 lines
qga/guest-agent-commands.c new file +31 -0
+    }
+    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);
+        goto out;
+    }
+
+    /* set fd non-blocking to avoid common use cases (like reading from a
+     * named pipe) from hanging the agent
+     */
+    fd = fileno(fh);
+    ret = fcntl(fd, F_GETFL);
+    ret = fcntl(fd, F_SETFL, ret | O_NONBLOCK);
+    if (ret == -1) {
+        error_set(err, QERR_OPEN_FILE_FAILED, filepath);
+        fclose(fh);
+        goto out;
+    }
+
+    id = guest_file_handle_add(fh);
+    slog("guest-file-open, filehandle: %ld", id);
+out:
+    return id;
+}
+
+struct GuestFileRead *qmp_guest_file_read(int64_t filehandle, int64_t count,
+                                          Error **err)
+{
+    GuestFileRead *read_data;
+    guchar *buf;

qga/guest-agent-commands.c line 221

Quoted 10 lines
qga/guest-agent-commands.c new file +10 -0
+    FILE *fh = guest_file_handle_find(filehandle);
+    size_t read_count;
+
+    if (!fh) {
+        error_set(err, QERR_FD_NOT_FOUND, "filehandle");
+        return NULL;
+    }
+
+    read_data = g_malloc0(sizeof(GuestFileRead));
+    buf = g_malloc(count);

What happens if the client passes a bogus value? Like -1 or a very big number?

Quoted 4 lines

I think count has to be checked against the file size. You could call stat() and store the value. Also, you're not checking g_malloc()'s return, so a bad allocation can crash the agent.

All good points

Quoted 99 lines

qga/guest-agent-commands.c line 224

Quoted 3 lines
qga/guest-agent-commands.c new file +3 -0
+
+    read_count = fread(buf, 1, count, fh);
+    buf[read_count] = 0;

We need to allocate an additional byte to do that.

Quoted 22 lines
qga/guest-agent-commands.c new file +22 -0
+    read_data->count = read_count;
+    read_data->eof = feof(fh);
+    if (read_count) {
+        read_data->buf = g_base64_encode(buf, read_count);
+    }
+    g_free(buf);
+    /* clear error and eof. error is generally due to EAGAIN from non-blocking
+     * mode, and no real way to differenitate from a real error since we only
+     * get boolean error flag from ferror()
+     */
+    clearerr(fh);
+
+    return read_data;
+}
+
+GuestFileWrite *qmp_guest_file_write(int64_t filehandle, const char *data_b64,
+                                     int64_t count, Error **err)
+{
+    GuestFileWrite *write_data;
+    guchar *data;
+    gsize data_len;
+    int write_count;

qga/guest-agent-commands.c line 256

Quoted 10 lines
qga/guest-agent-commands.c new file +10 -0
+    FILE *fh = guest_file_handle_find(filehandle);
+
+    if (!fh) {
+        error_set(err, QERR_FD_NOT_FOUND, "filehandle");
+        return NULL;
+    }
+
+    write_data = g_malloc0(sizeof(GuestFileWrite));
+    data = g_base64_decode(data_b64,&data_len);
+    write_count = fwrite(data, 1, MIN(count, data_len), fh);

IMO, we should do what the user is asking us to do, if it's impossible we should return an error. IOW, we should use count. It's okay if the buffer is bigger then count, but if count is bigger then we should return an error.

What does write() do in this case? segfaults?

Quoted 54 lines
qga/guest-agent-commands.c new file +54 -0
+    write_data->count = write_count;
+    write_data->eof = feof(fh);
+    g_free(data);
+    clearerr(fh);
+
+    return write_data;
+}
+
+struct GuestFileSeek *qmp_guest_file_seek(int64_t filehandle, int64_t offset,
+                                          int64_t whence, Error **err)
+{
+    GuestFileSeek *seek_data;
+    FILE *fh = guest_file_handle_find(filehandle);
+    int ret;
+
+    if (!fh) {
+        error_set(err, QERR_FD_NOT_FOUND, "filehandle");
+        return NULL;
+    }
+
+    seek_data = g_malloc0(sizeof(GuestFileRead));
+    ret = fseek(fh, offset, whence);
+    if (ret == -1) {
+        error_set(err, QERR_UNDEFINED_ERROR);
+        g_free(seek_data);
+        return NULL;
+    }
+    seek_data->position = ftell(fh);
+    seek_data->eof = feof(fh);
+    clearerr(fh);
+
+    return seek_data;
+}
+
+void qmp_guest_file_close(int64_t filehandle, Error **err)
+{
+    FILE *fh = guest_file_handle_find(filehandle);
+
+    if (!logging_enabled()) {
+        error_set(err, QERR_QGA_LOGGING_FAILED);
+        return;
+    }
+    slog("guest-file-close called, filehandle: %ld", filehandle);
+    if (!fh) {
+        error_set(err, QERR_FD_NOT_FOUND, "filehandle");
+        return;
+    }
+
+    fclose(fh);
+    guest_file_handle_remove(filehandle);
+}
+
+/*
+ * Walk the mount table and build a list of local file systems
Quoted 9 lines

qga/guest-agent-commands.c line 316

Quoted 6 lines
qga/guest-agent-commands.c new file +6 -0
+ */
+
+struct direntry {
+    char *dirname;
+    char *devtype;
+    struct direntry *next;

Wouldn't it be better to use qemu's list implementation?

yes, ill fix all the list stuff up to use qemu's

Quoted 124 lines
Quoted 90 lines
qga/guest-agent-commands.c new file +90 -0
+};
+
+struct {
+    struct direntry *mount_list;
+    GuestFsfreezeStatus status;
+} guest_fsfreeze_state;
+
+static int guest_fsfreeze_build_mount_list(void)
+{
+    struct mntent *mnt;
+    struct direntry *entry;
+    struct direntry *next;
+    char const *mtab = MOUNTED;
+    FILE *fp;
+
+    fp = setmntent(mtab, "r");
+    if (!fp) {
+        g_warning("fsfreeze: unable to read mtab");
+        goto fail;
+    }
+
+    while ((mnt = getmntent(fp))) {
+        /*
+         * An entry which device name doesn't start with a '/' is
+         * either a dummy file system or a network file system.
+         * Add special handling for smbfs and cifs as is done by
+         * coreutils as well.
+         */
+        if ((mnt->mnt_fsname[0] != '/') ||
+            (strcmp(mnt->mnt_type, "smbfs") == 0) ||
+            (strcmp(mnt->mnt_type, "cifs") == 0)) {
+            continue;
+        }
+
+        entry = g_malloc(sizeof(struct direntry));
+        entry->dirname = qemu_strdup(mnt->mnt_dir);
+        entry->devtype = qemu_strdup(mnt->mnt_type);
+        entry->next = guest_fsfreeze_state.mount_list;
+
+        guest_fsfreeze_state.mount_list = entry;
+    }
+
+    endmntent(fp);
+
+    return 0;
+
+fail:
+    while(guest_fsfreeze_state.mount_list) {
+        next = guest_fsfreeze_state.mount_list->next;
+        g_free(guest_fsfreeze_state.mount_list->dirname);
+        g_free(guest_fsfreeze_state.mount_list->devtype);
+        g_free(guest_fsfreeze_state.mount_list);
+        guest_fsfreeze_state.mount_list = next;
+    }
+
+    return -1;
+}
+
+/*
+ * Return status of freeze/thaw
+ */
+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 direntry *entry;
+    int fd;
+
+    if (!logging_enabled()) {
+        error_set(err, QERR_QGA_LOGGING_FAILED);
+        goto out;
+    }
+
+    slog("guest-fsfreeze called");
+
+    if (guest_fsfreeze_state.status != GUEST_FSFREEZE_STATUS_THAWED) {
+        ret = 0;
+        goto out;
+    }
+
+    ret = guest_fsfreeze_build_mount_list();
+    if (ret<  0) {

qga/guest-agent-commands.c line 416

Quoted 10 lines
qga/guest-agent-commands.c new file +10 -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();
+
+    entry = guest_fsfreeze_state.mount_list;
+    while(entry) {

A for() loop would be clearer, imho.

Quoted 10 lines
qga/guest-agent-commands.c new file +10 -0
+        fd = qemu_open(entry->dirname, O_RDONLY);
+        if (fd == -1) {
+            ret = errno;
+            goto error;
+        }
+
+        /* we try to cull filesytems we know won't work in advance, but other
+         * filesytems may not implement fsfreeze for less obvious reasons.
+         * these will reason EOPNOTSUPP, so we simply ignore them. when
+         * thawing, these filesystems will return an EINVAL instead, due to

qga/guest-agent-commands.c line 436

Quoted 10 lines
qga/guest-agent-commands.c new file +10 -0
+         * 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;
Quoted 3 lines

Does the FIFREEZE ioctl() call returns the errno code? If it doesn't, then we have to set it as the qemu_open() does above.

Nope, -1 + errno, good catch.

Quoted 11 lines
Quoted 11 lines
qga/guest-agent-commands.c new file +11 -0
+        }
+        close(fd);
+
+        entry = entry->next;
+        i++;
+    }
+
+    guest_fsfreeze_state.status = GUEST_FSFREEZE_STATUS_FROZEN;
+    ret = i;
+out:
+    return ret;
Quoted 9 lines

qga/guest-agent-commands.c line 451

Quoted 4 lines
qga/guest-agent-commands.c new file +4 -0
+error:
+    if (i>  0) {
+        guest_fsfreeze_state.status = GUEST_FSFREEZE_STATUS_ERROR;
+    }

I'm not sure this correct. What happens if an error happens in the first iteration of the while loop? A better way would to check 'ret' instead.

I believe this is meant to track states where some filesystems have been frozen and others not. We want to return the count, but not the error via fsfreeze_status. If we bail in the first iteration it means FIFREEZE was never completed successfully. We should reset state the THAWED then though.

Quoted 84 lines
Quoted 83 lines
qga/guest-agent-commands.c new file +71 -0
+    goto out;
+}
+
+/*
+ * Walk list of frozen file systems in the guest, and thaw them.
+ */
+int64_t qmp_guest_fsfreeze_thaw(Error **err)
+{
+    int ret;
+    struct direntry *entry;
+    int fd, i = 0;
+
+    if (guest_fsfreeze_state.status != GUEST_FSFREEZE_STATUS_FROZEN&&
+        guest_fsfreeze_state.status != GUEST_FSFREEZE_STATUS_INPROGRESS) {
+        ret = 0;
+        goto out_enable_logging;
+    }
+
+    while((entry = guest_fsfreeze_state.mount_list)) {
+        fd = qemu_open(entry->dirname, O_RDONLY);
+        if (fd == -1) {
+            ret = -errno;
+            goto out;
+        }
+        ret = ioctl(fd, FITHAW);
+        if (ret<  0&&  errno != EOPNOTSUPP&&  errno != EINVAL) {
+            ret = -errno;
+            close(fd);
+            goto out;
+        }
+        close(fd);
+
+        guest_fsfreeze_state.mount_list = entry->next;
+        g_free(entry->dirname);
+        g_free(entry->devtype);
+        g_free(entry);
+        i++;
+    }
+
+    guest_fsfreeze_state.status = 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;
+}
+
+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);
+}
qga/guest-agent-core.h +1 -0
index 66d1729..a85a5e4 100644
@@ -18,6 +18,7 @@
  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));
#20 lore raw

Re: [Qemu-devel] [PATCH v5 3/5] guest agent: add guest agent RPCs/commands

Quoted 187 lines · On Fri, 17 Jun 2011 23:38:16 -0300 Luiz Capitulino <[email protected]>…
Quoted 173 lines · On Fri, 17 Jun 2011 15:19:56 -0500 Michael Roth <[email protected]>…
Quoted 172 lines · Luiz Capitulino
Quoted 123 lines · On Tue, 14 Jun 2011 15:06:23 -0500 Michael Roth<[email protected]>…

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.

---
  qerror.c                   |    4 +
  qerror.h                   |    3 +
  qga/guest-agent-commands.c |  522 ++++++++++++++++++++++++++++++++++++++++++++
  qga/guest-agent-core.h     |    1 +
  4 files changed, 530 insertions(+), 0 deletions(-)

create mode 100644 qga/guest-agent-commands.c

qerror.c +4 -0
index d7fcd93..24f0c48 100644
@@ -213,6 +213,10 @@ static const QErrorStringTable qerror_table[] = {
          .error_fmt = QERR_VNC_SERVER_FAILED,
          .desc      = "Could not start VNC server on %(target)",
      },
+    {
+        .error_fmt = QERR_QGA_LOGGING_FAILED,
+        .desc      = "failed to write log statement due to logging being disabled",
+    },
      {}
  };
 
qerror.h +3 -0
index 7a89a50..bf3d5a9 100644
@@ -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 */
qga/guest-agent-commands.c new file +62 -0
new file mode 100644
index 0000000..6f9886a
@@ -0,0 +1,522 @@
+/*
+ * QEMU Guest Agent commands
+ *
+ * Copyright IBM Corp. 2011
+ *
+ * Authors:
+ *  Michael Roth<[email protected]>
+ *
+ * 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"
+
+static GAState *ga_state;
+
+static bool logging_enabled(void)
+{
+    return ga_logging_enabled(ga_state);
+}
+
+static void disable_logging(void)
+{
+    ga_disable_logging(ga_state);
+}
+
+static void enable_logging(void)
+{
+    ga_enable_logging(ga_state);
+}
+
+/* Note: in some situations, like with the fsfreeze, logging may be
+ * temporarilly disabled. if it is necessary that a command be able
+ * to log for accounting purposes, check logging_enabled() beforehand,
+ * and use the QERR_QGA_LOGGING_DISABLED to generate an error
+ */
+static void slog(const char *fmt, ...)
+{
+    va_list ap;
+
+    va_start(ap, fmt);
+    g_logv("syslog", G_LOG_LEVEL_INFO, fmt, ap);
+    va_end(ap);
+}
+
+int64_t qmp_guest_sync(int64_t id, Error **errp)
+{
+    return id;
+}
+
+void qmp_guest_ping(Error **err)
+{
+    slog("guest-ping called");
+}
+

qga/guest-agent-commands.c line 72

Quoted 10 lines
qga/guest-agent-commands.c new file +10 -0
+struct GuestAgentInfo *qmp_guest_info(Error **err)
+{
+    GuestAgentInfo *info = g_malloc0(sizeof(GuestAgentInfo));
+
+    info->version = g_strdup(QGA_VERSION);
+
+    return info;
+}
+
+void qmp_guest_shutdown(const char *shutdown_mode, Error **err)

I'd call the argument just 'mode'.

Quoted 19 lines
qga/guest-agent-commands.c new file +19 -0
+{
+    int ret;
+    const char *shutdown_flag;
+
+    if (!logging_enabled()) {
+        error_set(err, QERR_QGA_LOGGING_FAILED);
+        return;
+    }
+
+    slog("guest-shutdown called, shutdown_mode: %s", shutdown_mode);
+    if (strcmp(shutdown_mode, "halt") == 0) {
+        shutdown_flag = "-H";
+    } else if (strcmp(shutdown_mode, "powerdown") == 0) {
+        shutdown_flag = "-P";
+    } else if (strcmp(shutdown_mode, "reboot") == 0) {
+        shutdown_flag = "-r";
+    } else {
+        error_set(err, QERR_INVALID_PARAMETER_VALUE, "shutdown_mode",
+                  "halt|powerdown|reboot");

qga/guest-agent-commands.c line 101

Quoted 10 lines
qga/guest-agent-commands.c new file +10 -0
+        return;
+    }
+
+    ret = fork();
+    if (ret == 0) {
+        /* child, start the shutdown */
+        setsid();
+        fclose(stdin);
+        fclose(stdout);
+        fclose(stderr);

Would be nice to have a log file, whose descriptor is passed to the child.

Quoted 8 lines
Quoted 5 lines

qga/guest-agent-commands.c line 103

Quoted 2 lines
qga/guest-agent-commands.c new file +2 -0
+
+        sleep(5);

Why sleep()?

Want to give the agent time to send a response. It's still racy, but less so that immediately issuing the shutdown.

It's only less race for the particular test-case you're testing :)

Quoted 8 lines
Quoted 2 lines

Ideal we'd push the
sleep() into shutdown's time param, but that only has minute resolution.

I don't think so, because this is an implementation detail. The right thing would be to make the child wait for an "go ahead" signal from the parent.

This could be done by calling pause() in the child and making the parent send a SIGUSR1 when the response has been sent.

Using pause() this way is obviously racy too, we have to block the signal in the parent and call sigsuspend() in the child.

Quoted 536 lines

One way to do it w/o tying sever actions (like to send a response) to agent commands, would be to introduce a "response sent" callback, so that commands could register and have their specific actions executed at the right time.

Quoted 8 lines
Quoted 8 lines
Quoted 7 lines
qga/guest-agent-commands.c new file +6 -0
+        ret = execl("/sbin/shutdown", "shutdown", shutdown_flag, "+0",
+                    "hypervisor initiated shutdown", (char*)NULL);
+        exit(!!ret);
+    } else if (ret<  0) {
+        error_set(err, QERR_UNDEFINED_ERROR);
+    }
Quoted 7 lines
Quoted 2 lines

Doesn't have the parent process wait for the child, so that exec() errors can be reported?

The exec() won't return until the shutdown is executed, so the RPC's behavior would be racy. At some point I documented that the shutdown is an async "request" that may or may not complete but that was lost in the reworking. I'll clarify in the schema.

Seems fine.

Quoted 15 lines
Quoted 14 lines

qga/guest-agent-commands.c line 118

Quoted 11 lines
qga/guest-agent-commands.c new file +9 -0
+}
+
+typedef struct GuestFileHandle {
+    uint64_t id;
+    FILE *fh;
+} GuestFileHandle;
+
+static struct {
+    GSList *filehandles;

Wouldn't this be simpler if we use qemu list implementation instead?

Quoted 7 lines

YES! This stuff is terribly verbose. I was trying to stick with glib outside of QMP/QAPI stuff though...and I think more glib stuff will find it's way into here over time that'll make appearances of GSList/gmalloc/etc inevitable.

But if intermixing is not a big deal I'm more than happy to "allow" qemu malloc/list stuff where it makes sense, and refactor these accordingly.

It makes sense to me.

Quoted 13 lines
Quoted 13 lines

qga/guest-agent-commands.c line 127

Quoted 9 lines
qga/guest-agent-commands.c new file +9 -0
+    uint64_t last_id;
+} guest_file_state;
+
+static int64_t guest_file_handle_add(FILE *fh)
+{
+    GuestFileHandle *gfh;
+
+    gfh = g_malloc(sizeof(GuestFileHandle));
+    gfh->id = guest_file_state.last_id++;

I don't know if the uint64_t limit can be reached in practice, but I'd expect a bitmap, so that you can return ids in guest_file_handle_remove().

Quoted 8 lines
Quoted 3 lines

Another simpler option would be to use the real fd instead. I mean, the one returned by the guest kernel.

I think I'll go with this. I was hesitant to do this at first since I didn't want users specifying FDs opened outside of guest-file-open, but so long as we only rely on our internal list of open FDs I guess that's not applicable.

Yes.

Quoted 462 lines
Quoted 102 lines
Quoted 43 lines
qga/guest-agent-commands.c new file +43 -0
+    gfh->fh = fh;
+    guest_file_state.filehandles = g_slist_append(guest_file_state.filehandles,
+                                                  gfh);
+    return gfh->id;
+}
+
+static gint guest_file_handle_match(gconstpointer elem, gconstpointer id_p)
+{
+    const uint64_t *id = id_p;
+    const GuestFileHandle *gfh = elem;
+
+    g_assert(gfh);
+    return (gfh->id != *id);
+}
+
+static FILE *guest_file_handle_find(int64_t id)
+{
+    GSList *elem = g_slist_find_custom(guest_file_state.filehandles,&id,
+                                       guest_file_handle_match);
+    GuestFileHandle *gfh;
+
+    if (elem) {
+        g_assert(elem->data);
+        gfh = elem->data;
+        return gfh->fh;
+    }
+
+    return NULL;
+}
+
+static void guest_file_handle_remove(int64_t id)
+{
+    GSList *elem = g_slist_find_custom(guest_file_state.filehandles,&id,
+                                       guest_file_handle_match);
+    gpointer data = elem->data;
+
+    if (!data) {
+        return;
+    }
+    guest_file_state.filehandles = g_slist_remove(guest_file_state.filehandles,
+                                                  data);
+    g_free(data);
+}

qga/guest-agent-commands.c line 180

Quoted 10 lines
qga/guest-agent-commands.c new file +10 -0
+
+int64_t qmp_guest_file_open(const char *filepath, const char *mode, Error **err)
+{
+    FILE *fh;
+    int fd, ret;
+    int64_t id = -1;
+
+    if (!logging_enabled()) {
+        error_set(err, QERR_QGA_LOGGING_FAILED);
+        goto out;

No need to have a goto here, just do return -1. This true for other functions too.

Quoted 31 lines
qga/guest-agent-commands.c new file +31 -0
+    }
+    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);
+        goto out;
+    }
+
+    /* set fd non-blocking to avoid common use cases (like reading from a
+     * named pipe) from hanging the agent
+     */
+    fd = fileno(fh);
+    ret = fcntl(fd, F_GETFL);
+    ret = fcntl(fd, F_SETFL, ret | O_NONBLOCK);
+    if (ret == -1) {
+        error_set(err, QERR_OPEN_FILE_FAILED, filepath);
+        fclose(fh);
+        goto out;
+    }
+
+    id = guest_file_handle_add(fh);
+    slog("guest-file-open, filehandle: %ld", id);
+out:
+    return id;
+}
+
+struct GuestFileRead *qmp_guest_file_read(int64_t filehandle, int64_t count,
+                                          Error **err)
+{
+    GuestFileRead *read_data;
+    guchar *buf;

qga/guest-agent-commands.c line 221

Quoted 10 lines
qga/guest-agent-commands.c new file +10 -0
+    FILE *fh = guest_file_handle_find(filehandle);
+    size_t read_count;
+
+    if (!fh) {
+        error_set(err, QERR_FD_NOT_FOUND, "filehandle");
+        return NULL;
+    }
+
+    read_data = g_malloc0(sizeof(GuestFileRead));
+    buf = g_malloc(count);

What happens if the client passes a bogus value? Like -1 or a very big number?

Quoted 4 lines

I think count has to be checked against the file size. You could call stat() and store the value. Also, you're not checking g_malloc()'s return, so a bad allocation can crash the agent.

All good points

Quoted 99 lines

qga/guest-agent-commands.c line 224

Quoted 3 lines
qga/guest-agent-commands.c new file +3 -0
+
+    read_count = fread(buf, 1, count, fh);
+    buf[read_count] = 0;

We need to allocate an additional byte to do that.

Quoted 22 lines
qga/guest-agent-commands.c new file +22 -0
+    read_data->count = read_count;
+    read_data->eof = feof(fh);
+    if (read_count) {
+        read_data->buf = g_base64_encode(buf, read_count);
+    }
+    g_free(buf);
+    /* clear error and eof. error is generally due to EAGAIN from non-blocking
+     * mode, and no real way to differenitate from a real error since we only
+     * get boolean error flag from ferror()
+     */
+    clearerr(fh);
+
+    return read_data;
+}
+
+GuestFileWrite *qmp_guest_file_write(int64_t filehandle, const char *data_b64,
+                                     int64_t count, Error **err)
+{
+    GuestFileWrite *write_data;
+    guchar *data;
+    gsize data_len;
+    int write_count;

qga/guest-agent-commands.c line 256

Quoted 10 lines
qga/guest-agent-commands.c new file +10 -0
+    FILE *fh = guest_file_handle_find(filehandle);
+
+    if (!fh) {
+        error_set(err, QERR_FD_NOT_FOUND, "filehandle");
+        return NULL;
+    }
+
+    write_data = g_malloc0(sizeof(GuestFileWrite));
+    data = g_base64_decode(data_b64,&data_len);
+    write_count = fwrite(data, 1, MIN(count, data_len), fh);

IMO, we should do what the user is asking us to do, if it's impossible we should return an error. IOW, we should use count. It's okay if the buffer is bigger then count, but if count is bigger then we should return an error.

What does write() do in this case? segfaults?

Quoted 54 lines
qga/guest-agent-commands.c new file +54 -0
+    write_data->count = write_count;
+    write_data->eof = feof(fh);
+    g_free(data);
+    clearerr(fh);
+
+    return write_data;
+}
+
+struct GuestFileSeek *qmp_guest_file_seek(int64_t filehandle, int64_t offset,
+                                          int64_t whence, Error **err)
+{
+    GuestFileSeek *seek_data;
+    FILE *fh = guest_file_handle_find(filehandle);
+    int ret;
+
+    if (!fh) {
+        error_set(err, QERR_FD_NOT_FOUND, "filehandle");
+        return NULL;
+    }
+
+    seek_data = g_malloc0(sizeof(GuestFileRead));
+    ret = fseek(fh, offset, whence);
+    if (ret == -1) {
+        error_set(err, QERR_UNDEFINED_ERROR);
+        g_free(seek_data);
+        return NULL;
+    }
+    seek_data->position = ftell(fh);
+    seek_data->eof = feof(fh);
+    clearerr(fh);
+
+    return seek_data;
+}
+
+void qmp_guest_file_close(int64_t filehandle, Error **err)
+{
+    FILE *fh = guest_file_handle_find(filehandle);
+
+    if (!logging_enabled()) {
+        error_set(err, QERR_QGA_LOGGING_FAILED);
+        return;
+    }
+    slog("guest-file-close called, filehandle: %ld", filehandle);
+    if (!fh) {
+        error_set(err, QERR_FD_NOT_FOUND, "filehandle");
+        return;
+    }
+
+    fclose(fh);
+    guest_file_handle_remove(filehandle);
+}
+
+/*
+ * Walk the mount table and build a list of local file systems
Quoted 9 lines

qga/guest-agent-commands.c line 316

Quoted 6 lines
qga/guest-agent-commands.c new file +6 -0
+ */
+
+struct direntry {
+    char *dirname;
+    char *devtype;
+    struct direntry *next;

Wouldn't it be better to use qemu's list implementation?

yes, ill fix all the list stuff up to use qemu's

Quoted 124 lines
Quoted 90 lines
qga/guest-agent-commands.c new file +90 -0
+};
+
+struct {
+    struct direntry *mount_list;
+    GuestFsfreezeStatus status;
+} guest_fsfreeze_state;
+
+static int guest_fsfreeze_build_mount_list(void)
+{
+    struct mntent *mnt;
+    struct direntry *entry;
+    struct direntry *next;
+    char const *mtab = MOUNTED;
+    FILE *fp;
+
+    fp = setmntent(mtab, "r");
+    if (!fp) {
+        g_warning("fsfreeze: unable to read mtab");
+        goto fail;
+    }
+
+    while ((mnt = getmntent(fp))) {
+        /*
+         * An entry which device name doesn't start with a '/' is
+         * either a dummy file system or a network file system.
+         * Add special handling for smbfs and cifs as is done by
+         * coreutils as well.
+         */
+        if ((mnt->mnt_fsname[0] != '/') ||
+            (strcmp(mnt->mnt_type, "smbfs") == 0) ||
+            (strcmp(mnt->mnt_type, "cifs") == 0)) {
+            continue;
+        }
+
+        entry = g_malloc(sizeof(struct direntry));
+        entry->dirname = qemu_strdup(mnt->mnt_dir);
+        entry->devtype = qemu_strdup(mnt->mnt_type);
+        entry->next = guest_fsfreeze_state.mount_list;
+
+        guest_fsfreeze_state.mount_list = entry;
+    }
+
+    endmntent(fp);
+
+    return 0;
+
+fail:
+    while(guest_fsfreeze_state.mount_list) {
+        next = guest_fsfreeze_state.mount_list->next;
+        g_free(guest_fsfreeze_state.mount_list->dirname);
+        g_free(guest_fsfreeze_state.mount_list->devtype);
+        g_free(guest_fsfreeze_state.mount_list);
+        guest_fsfreeze_state.mount_list = next;
+    }
+
+    return -1;
+}
+
+/*
+ * Return status of freeze/thaw
+ */
+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 direntry *entry;
+    int fd;
+
+    if (!logging_enabled()) {
+        error_set(err, QERR_QGA_LOGGING_FAILED);
+        goto out;
+    }
+
+    slog("guest-fsfreeze called");
+
+    if (guest_fsfreeze_state.status != GUEST_FSFREEZE_STATUS_THAWED) {
+        ret = 0;
+        goto out;
+    }
+
+    ret = guest_fsfreeze_build_mount_list();
+    if (ret<  0) {

qga/guest-agent-commands.c line 416

Quoted 10 lines
qga/guest-agent-commands.c new file +10 -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();
+
+    entry = guest_fsfreeze_state.mount_list;
+    while(entry) {

A for() loop would be clearer, imho.

Quoted 10 lines
qga/guest-agent-commands.c new file +10 -0
+        fd = qemu_open(entry->dirname, O_RDONLY);
+        if (fd == -1) {
+            ret = errno;
+            goto error;
+        }
+
+        /* we try to cull filesytems we know won't work in advance, but other
+         * filesytems may not implement fsfreeze for less obvious reasons.
+         * these will reason EOPNOTSUPP, so we simply ignore them. when
+         * thawing, these filesystems will return an EINVAL instead, due to

qga/guest-agent-commands.c line 436

Quoted 10 lines
qga/guest-agent-commands.c new file +10 -0
+         * 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;
Quoted 3 lines

Does the FIFREEZE ioctl() call returns the errno code? If it doesn't, then we have to set it as the qemu_open() does above.

Nope, -1 + errno, good catch.

Quoted 11 lines
Quoted 11 lines
qga/guest-agent-commands.c new file +11 -0
+        }
+        close(fd);
+
+        entry = entry->next;
+        i++;
+    }
+
+    guest_fsfreeze_state.status = GUEST_FSFREEZE_STATUS_FROZEN;
+    ret = i;
+out:
+    return ret;
Quoted 9 lines

qga/guest-agent-commands.c line 451

Quoted 4 lines
qga/guest-agent-commands.c new file +4 -0
+error:
+    if (i>  0) {
+        guest_fsfreeze_state.status = GUEST_FSFREEZE_STATUS_ERROR;
+    }

I'm not sure this correct. What happens if an error happens in the first iteration of the while loop? A better way would to check 'ret' instead.

I believe this is meant to track states where some filesystems have been frozen and others not. We want to return the count, but not the error via fsfreeze_status. If we bail in the first iteration it means FIFREEZE was never completed successfully. We should reset state the THAWED then though.

Quoted 84 lines
Quoted 83 lines
qga/guest-agent-commands.c new file +71 -0
+    goto out;
+}
+
+/*
+ * Walk list of frozen file systems in the guest, and thaw them.
+ */
+int64_t qmp_guest_fsfreeze_thaw(Error **err)
+{
+    int ret;
+    struct direntry *entry;
+    int fd, i = 0;
+
+    if (guest_fsfreeze_state.status != GUEST_FSFREEZE_STATUS_FROZEN&&
+        guest_fsfreeze_state.status != GUEST_FSFREEZE_STATUS_INPROGRESS) {
+        ret = 0;
+        goto out_enable_logging;
+    }
+
+    while((entry = guest_fsfreeze_state.mount_list)) {
+        fd = qemu_open(entry->dirname, O_RDONLY);
+        if (fd == -1) {
+            ret = -errno;
+            goto out;
+        }
+        ret = ioctl(fd, FITHAW);
+        if (ret<  0&&  errno != EOPNOTSUPP&&  errno != EINVAL) {
+            ret = -errno;
+            close(fd);
+            goto out;
+        }
+        close(fd);
+
+        guest_fsfreeze_state.mount_list = entry->next;
+        g_free(entry->dirname);
+        g_free(entry->devtype);
+        g_free(entry);
+        i++;
+    }
+
+    guest_fsfreeze_state.status = 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;
+}
+
+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);
+}
qga/guest-agent-core.h +1 -0
index 66d1729..a85a5e4 100644
@@ -18,6 +18,7 @@
  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));
#21 lore raw

PATCH v5 4/5 guest agent: add guest agent commands schema file

In reply to Michael Roth
---
 qapi-schema-guest.json |  202 ++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 202 insertions(+), 0 deletions(-)
 create mode 100644 qapi-schema-guest.json
qapi-schema-guest.json new file +202 -0
new file mode 100644
index 0000000..d8b178e
@@ -0,0 +1,202 @@
+# *-*- 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
+#
+# @shutdown_mode: "halt", "powerdown", or "reboot"
+#
+# Returns: Nothing on success
+#
+# Since: 0.15.0
+##
+{ 'command': 'guest-shutdown', 'data': { 'shutdown_mode': 'str' } }
+
+##
+# @guest-file-open:
+#
+# Open a file in the guest and retrieve a file handle for it
+#
+# @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' }
Signature · 1 line
1.7.0.4
#22 lore raw

PATCH v5 5/5 guest agent: Makefile, build qemu-ga

In reply to Michael Roth

This allows us to build qemu-ga with "make qemu-ga". It pulls in the qemu-tools deps, but does not currently build by default. This may change to avoid bitrot and help with host-side-only unit tests.

---
 Makefile  |   22 +++++++++++++++++-----
 configure |    1 +
 2 files changed, 18 insertions(+), 5 deletions(-)
Makefile +17 -5
index 6adf1a9..4be4e5d 100644
@@ -123,7 +123,7 @@ version-obj-$(CONFIG_WIN32) += version.o
 ######################################################################
 
 qemu-img.o: qemu-img-cmds.h
-qemu-img.o qemu-tool.o qemu-nbd.o qemu-io.o cmd.o: $(GENERATED_HEADERS)
+qemu-img.o qemu-tool.o qemu-nbd.o qemu-io.o cmd.o qemu-ga.o: $(GENERATED_HEADERS)
 
 qemu-img$(EXESUF): qemu-img.o qemu-tool.o qemu-error.o $(oslib-obj-y) $(trace-obj-y) $(block-obj-y) $(qobject-obj-y) $(version-obj-y) qemu-timer-common.o
 
@@ -146,7 +146,7 @@ check-qfloat: check-qfloat.o qfloat.o $(CHECK_PROG_DEPS)
 check-qjson: check-qjson.o qfloat.o qint.o qdict.o qstring.o qlist.o qbool.o qjson.o json-streamer.o json-lexer.o json-parser.o error.o qerror.o qemu-error.o $(CHECK_PROG_DEPS)
 
 qapi-dir := qapi-generated
-$(qapi-obj-y) test-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
@@ -158,20 +158,32 @@ $(qapi-dir)/test-qmp-commands.h: $(qapi-dir)/test-qmp-marshal.c
 $(qapi-dir)/test-qmp-marshal.c: $(SRC_PATH)/qapi-schema-test.json $(SRC_PATH)/scripts/qapi-commands.py
 	    $(call quiet-command,python $(SRC_PATH)/scripts/qapi-commands.py -o "$(qapi-dir)" -p "test-" < $<, "  GEN   $@")
 
+$(qapi-dir)/qga-qapi-types.c: $(qapi-dir)/qga-qapi-types.h
+$(qapi-dir)/qga-qapi-types.h: $(SRC_PATH)/qapi-schema-guest.json $(SRC_PATH)/scripts/qapi-types.py
+	$(call quiet-command,python $(SRC_PATH)/scripts/qapi-types.py -o "$(qapi-dir)" -p "qga-" < $<, "  GEN   $@")
+$(qapi-dir)/qga-qapi-visit.c: $(qapi-dir)/qga-qapi-visit.h
+$(qapi-dir)/qga-qapi-visit.h: $(SRC_PATH)/qapi-schema-guest.json $(SRC_PATH)/scripts/qapi-visit.py
+	$(call quiet-command,python $(SRC_PATH)/scripts/qapi-visit.py -o "$(qapi-dir)" -p "qga-" < $<, "  GEN   $@")
+$(qapi-dir)/qga-qmp-marshal.c: $(SRC_PATH)/qapi-schema-guest.json $(SRC_PATH)/scripts/qapi-commands.py
+	$(call quiet-command,python $(SRC_PATH)/scripts/qapi-commands.py -o "$(qapi-dir)" -p "qga-" < $<, "  GEN   $@")
+
 test-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
 
+qemu-ga.o: $(qapi-dir)/qga-qapi-types.c $(qapi-dir)/qga-qapi-types.h $(qapi-dir)/qga-qapi-visit.c $(qapi-dir)/qga-qmp-marshal.c
+qemu-ga$(EXESUF): qemu-ga.o qemu-tool.o qemu-error.o error.o $(oslib-obj-y) $(trace-obj-y) $(block-obj-y) $(qobject-obj-y) $(version-obj-y) $(qapi-obj-y) qemu-timer-common.o qemu-sockets.o qga/guest-agent-commands.o qga/guest-agent-command-state.o $(qapi-dir)/qga-qapi-visit.o $(qapi-dir)/qga-qmp-marshal.o module.o qapi/qmp-dispatch.o qapi/qmp-registry.o
+
 QEMULIBS=libhw32 libhw64 libuser libdis libdis-user
 
 clean:
 # avoid old build problems by removing potentially incorrect old files
 	rm -f config.mak op-i386.h opc-i386.h gen-op-i386.h op-arm.h opc-arm.h gen-op-arm.h
 	rm -f qemu-options.def
-	rm -f *.o *.d *.a $(TOOLS) TAGS cscope.* *.pod *~ */*~
-	rm -f slirp/*.o slirp/*.d audio/*.o audio/*.d block/*.o block/*.d net/*.o net/*.d fsdev/*.o fsdev/*.d ui/*.o ui/*.d qapi/*.o qapi/*.d
+	rm -f *.o *.d *.a $(TOOLS) qemu-ga TAGS cscope.* *.pod *~ */*~
+	rm -f slirp/*.o slirp/*.d audio/*.o audio/*.d block/*.o block/*.d net/*.o net/*.d fsdev/*.o fsdev/*.d ui/*.o ui/*.d qapi/*.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
@@ -365,4 +377,4 @@ tarbin:
 	$(mandir)/man8/qemu-nbd.8
 
 # Include automatically generated dependency files
--include $(wildcard *.d audio/*.d slirp/*.d block/*.d net/*.d ui/*.d)
+-include $(wildcard *.d audio/*.d slirp/*.d block/*.d net/*.d ui/*.d qapi/*.d qga/*.d)
configure +1 -0
index 3d73780..1a5893f 100755
@@ -3550,6 +3550,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"
Signature · 1 line
1.7.0.4