Patchwork [13/15] qapi: add code generator for libqmp (v2)

login
register
mail settings
Submitter Anthony Liguori
Date March 11, 2011, 11:05 p.m.
Message ID <1299884745-521-14-git-send-email-aliguori@us.ibm.com>
Download mbox | patch
Permalink /patch/86472/
State New
Headers show

Comments

Anthony Liguori - March 11, 2011, 11:05 p.m.
Signed-off-by: Anthony Liguori <aliguori@us.ibm.com>
---
v1 -> v2
 - update code generator to use multiline
 - proxy command support
 - async command support
Blue Swirl - March 12, 2011, 11:10 a.m.
On Sat, Mar 12, 2011 at 1:05 AM, Anthony Liguori <aliguori@us.ibm.com> wrote:
> Signed-off-by: Anthony Liguori <aliguori@us.ibm.com>
> ---
> v1 -> v2
>  - update code generator to use multiline
>  - proxy command support
>  - async command support
>
> diff --git a/Makefile b/Makefile
> index 47a755d..5170675 100644
> --- a/Makefile
> +++ b/Makefile
> @@ -4,7 +4,7 @@ GENERATED_HEADERS = config-host.h trace.h qemu-options.def
>  ifeq ($(TRACE_BACKEND),dtrace)
>  GENERATED_HEADERS += trace-dtrace.h
>  endif
> -GENERATED_HEADERS += qmp-types.h qmp-marshal-types.h qmp.h
> +GENERATED_HEADERS += qmp-types.h qmp-marshal-types.h qmp.h libqmp.h
>
>  ifneq ($(wildcard config-host.mak),)
>  # Put the all: rule here so that config-host.mak can contain dependencies.
> @@ -165,9 +165,16 @@ qmp.h: $(SRC_PATH)/qmp-schema.json $(SRC_PATH)/qmp-gen.py
>  qmp-marshal.c: $(SRC_PATH)/qmp-schema.json $(SRC_PATH)/qmp-gen.py
>        $(call quiet-command,python $(SRC_PATH)/qmp-gen.py --body < $< > $@, "  GEN   $@")
>
> +libqmp.h: $(SRC_PATH)/qmp-schema.json $(SRC_PATH)/qmp-gen.py
> +       $(call quiet-command,python $(SRC_PATH)/qmp-gen.py --lib-header < $< > $@, "  GEN   $@")
> +
> +libqmp.c: $(SRC_PATH)/qmp-schema.json $(SRC_PATH)/qmp-gen.py
> +       $(call quiet-command,python $(SRC_PATH)/qmp-gen.py --lib-body < $< > $@, "  GEN   $@")
> +
>  qmp-types.o: qmp-types.c qmp-types.h
>  qmp-marshal-types.o: qmp-marshal-types.c qmp-marshal-types.h qmp-types.h
>  qmp-marshal.o: qmp-marshal.c qmp.h qmp-types.h qmp-marshal-types.h
> +libqmp.o: libqmp.c libqmp.h qmp-types.h
>
>  version.o: $(SRC_PATH)/version.rc config-host.mak
>        $(call quiet-command,$(WINDRES) -I. -o $@ $<,"  RC    $(TARGET_DIR)$@")
> diff --git a/libqmp-core.c b/libqmp-core.c
> new file mode 100644
> index 0000000..4613d4f
> --- /dev/null
> +++ b/libqmp-core.c
> @@ -0,0 +1,361 @@
> +/*
> + * QAPI
> + *
> + * Copyright IBM, Corp. 2011
> + *
> + * Authors:
> + *  Anthony Liguori   <aliguori@us.ibm.com>
> + *
> + * This work is licensed under the terms of the GNU LGPL, version 2.  See
> + * the COPYING.LIB file in the top-level directory.
> + */
> +#include "libqmp.h"
> +#include "libqmp-internal.h"
> +#include "libqmp-core.h"
> +#include "json-streamer.h"
> +#include "json-parser.h"
> +#include "dirent.h"
> +#include <sys/socket.h>
> +#include <sys/un.h>
> +#include <assert.h>
> +
> +#ifndef container_of
> +#define offset_of(type, field) \
> +    ((unsigned long)(&((type *)0)->field))
> +#define container_of(obj, type, field) \
> +    ((type *)(((char *)obj) - offsetof(type, field)))
> +#endif

Why not use the existing definitions?
Anthony Liguori - March 12, 2011, 2:53 p.m.
On 03/12/2011 05:10 AM, Blue Swirl wrote:
> On Sat, Mar 12, 2011 at 1:05 AM, Anthony Liguori<aliguori@us.ibm.com>  wrote:
>> Signed-off-by: Anthony Liguori<aliguori@us.ibm.com>
>> ---
>> v1 ->  v2
>>   - update code generator to use multiline
>>   - proxy command support
>>   - async command support
>>
>> diff --git a/Makefile b/Makefile
>> index 47a755d..5170675 100644
>> --- a/Makefile
>> +++ b/Makefile
>> @@ -4,7 +4,7 @@ GENERATED_HEADERS = config-host.h trace.h qemu-options.def
>>   ifeq ($(TRACE_BACKEND),dtrace)
>>   GENERATED_HEADERS += trace-dtrace.h
>>   endif
>> -GENERATED_HEADERS += qmp-types.h qmp-marshal-types.h qmp.h
>> +GENERATED_HEADERS += qmp-types.h qmp-marshal-types.h qmp.h libqmp.h
>>
>>   ifneq ($(wildcard config-host.mak),)
>>   # Put the all: rule here so that config-host.mak can contain dependencies.
>> @@ -165,9 +165,16 @@ qmp.h: $(SRC_PATH)/qmp-schema.json $(SRC_PATH)/qmp-gen.py
>>   qmp-marshal.c: $(SRC_PATH)/qmp-schema.json $(SRC_PATH)/qmp-gen.py
>>         $(call quiet-command,python $(SRC_PATH)/qmp-gen.py --body<  $<  >  $@, "  GEN   $@")
>>
>> +libqmp.h: $(SRC_PATH)/qmp-schema.json $(SRC_PATH)/qmp-gen.py
>> +       $(call quiet-command,python $(SRC_PATH)/qmp-gen.py --lib-header<  $<  >  $@, "  GEN   $@")
>> +
>> +libqmp.c: $(SRC_PATH)/qmp-schema.json $(SRC_PATH)/qmp-gen.py
>> +       $(call quiet-command,python $(SRC_PATH)/qmp-gen.py --lib-body<  $<  >  $@, "  GEN   $@")
>> +
>>   qmp-types.o: qmp-types.c qmp-types.h
>>   qmp-marshal-types.o: qmp-marshal-types.c qmp-marshal-types.h qmp-types.h
>>   qmp-marshal.o: qmp-marshal.c qmp.h qmp-types.h qmp-marshal-types.h
>> +libqmp.o: libqmp.c libqmp.h qmp-types.h
>>
>>   version.o: $(SRC_PATH)/version.rc config-host.mak
>>         $(call quiet-command,$(WINDRES) -I. -o $@ $<,"  RC    $(TARGET_DIR)$@")
>> diff --git a/libqmp-core.c b/libqmp-core.c
>> new file mode 100644
>> index 0000000..4613d4f
>> --- /dev/null
>> +++ b/libqmp-core.c
>> @@ -0,0 +1,361 @@
>> +/*
>> + * QAPI
>> + *
>> + * Copyright IBM, Corp. 2011
>> + *
>> + * Authors:
>> + *  Anthony Liguori<aliguori@us.ibm.com>
>> + *
>> + * This work is licensed under the terms of the GNU LGPL, version 2.  See
>> + * the COPYING.LIB file in the top-level directory.
>> + */
>> +#include "libqmp.h"
>> +#include "libqmp-internal.h"
>> +#include "libqmp-core.h"
>> +#include "json-streamer.h"
>> +#include "json-parser.h"
>> +#include "dirent.h"
>> +#include<sys/socket.h>
>> +#include<sys/un.h>
>> +#include<assert.h>
>> +
>> +#ifndef container_of
>> +#define offset_of(type, field) \
>> +    ((unsigned long)(&((type *)0)->field))
>> +#define container_of(obj, type, field) \
>> +    ((type *)(((char *)obj) - offsetof(type, field)))
>> +#endif
> Why not use the existing definitions?

libqmp builds outside of the normal QEMU code base (it's a client library).

libqmp wants to be LGPL.  It's not clear what the license of the stuff 
in osdep is.

Regards,

Anthony Liguori

Patch

diff --git a/Makefile b/Makefile
index 47a755d..5170675 100644
--- a/Makefile
+++ b/Makefile
@@ -4,7 +4,7 @@  GENERATED_HEADERS = config-host.h trace.h qemu-options.def
 ifeq ($(TRACE_BACKEND),dtrace)
 GENERATED_HEADERS += trace-dtrace.h
 endif
-GENERATED_HEADERS += qmp-types.h qmp-marshal-types.h qmp.h
+GENERATED_HEADERS += qmp-types.h qmp-marshal-types.h qmp.h libqmp.h
 
 ifneq ($(wildcard config-host.mak),)
 # Put the all: rule here so that config-host.mak can contain dependencies.
@@ -165,9 +165,16 @@  qmp.h: $(SRC_PATH)/qmp-schema.json $(SRC_PATH)/qmp-gen.py
 qmp-marshal.c: $(SRC_PATH)/qmp-schema.json $(SRC_PATH)/qmp-gen.py
 	$(call quiet-command,python $(SRC_PATH)/qmp-gen.py --body < $< > $@, "  GEN   $@")
 
+libqmp.h: $(SRC_PATH)/qmp-schema.json $(SRC_PATH)/qmp-gen.py
+	$(call quiet-command,python $(SRC_PATH)/qmp-gen.py --lib-header < $< > $@, "  GEN   $@")
+
+libqmp.c: $(SRC_PATH)/qmp-schema.json $(SRC_PATH)/qmp-gen.py
+	$(call quiet-command,python $(SRC_PATH)/qmp-gen.py --lib-body < $< > $@, "  GEN   $@")
+
 qmp-types.o: qmp-types.c qmp-types.h
 qmp-marshal-types.o: qmp-marshal-types.c qmp-marshal-types.h qmp-types.h
 qmp-marshal.o: qmp-marshal.c qmp.h qmp-types.h qmp-marshal-types.h
+libqmp.o: libqmp.c libqmp.h qmp-types.h
 
 version.o: $(SRC_PATH)/version.rc config-host.mak
 	$(call quiet-command,$(WINDRES) -I. -o $@ $<,"  RC    $(TARGET_DIR)$@")
diff --git a/libqmp-core.c b/libqmp-core.c
new file mode 100644
index 0000000..4613d4f
--- /dev/null
+++ b/libqmp-core.c
@@ -0,0 +1,361 @@ 
+/*
+ * QAPI
+ *
+ * Copyright IBM, Corp. 2011
+ *
+ * Authors:
+ *  Anthony Liguori   <aliguori@us.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU LGPL, version 2.  See
+ * the COPYING.LIB file in the top-level directory.
+ */
+#include "libqmp.h"
+#include "libqmp-internal.h"
+#include "libqmp-core.h"
+#include "json-streamer.h"
+#include "json-parser.h"
+#include "dirent.h"
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <assert.h>
+
+#ifndef container_of
+#define offset_of(type, field) \
+    ((unsigned long)(&((type *)0)->field))
+#define container_of(obj, type, field) \
+    ((type *)(((char *)obj) - offsetof(type, field)))
+#endif
+
+//#define DEBUG_LIBQMP 1
+
+typedef struct FdQmpSession
+{
+    QmpSession session;
+    JSONMessageParser parser;
+    QObject *result;
+    bool got_greeting;
+    int fd;
+    int event_count;
+} FdQmpSession;
+
+static EventTrampolineFunc *get_event_trampoline(QmpSession *sess, const char *name)
+{
+    QmpEventTrampoline *t;
+
+    QTAILQ_FOREACH(t, &sess->events, node) {
+        if (strcmp(t->name, name) == 0) {
+            return t->dispatch;
+        }
+    }
+
+    return NULL;
+}
+
+static void fd_qmp_session_process_event(FdQmpSession *fs, QDict *response)
+{
+    EventTrampolineFunc *tramp;
+    QmpSignal *signal;
+    const char *event;
+    int tag;
+
+    event = qdict_get_str(response, "event");
+    tramp = get_event_trampoline(&fs->session, event);
+
+    fs->event_count++;
+
+    if (tramp && qdict_haskey(response, "tag")) {
+        tag = qdict_get_int(response, "tag");
+
+        QTAILQ_FOREACH(signal, &fs->session.signals, node) {
+            if (signal->global_handle == tag) {
+                QmpConnection *conn;
+                QDict *args = NULL;
+                Error *err = NULL;
+
+                if (qdict_haskey(response, "data")) {
+                    args = qobject_to_qdict(qdict_get(response, "data"));
+                }
+
+                QTAILQ_FOREACH(conn, &signal->connections, node) {
+                    tramp(args, conn->fn, conn->opaque, &err);
+                    if (err) {
+                        error_free(err);
+                    }
+                }
+
+                break;
+            }
+        }
+    }
+}
+
+static void fd_qmp_session_parse(JSONMessageParser *parser, QList *tokens)
+{
+    FdQmpSession *fs = container_of(parser, FdQmpSession, parser);
+    QObject *result;
+
+    result = json_parser_parse(tokens, NULL);
+    if (!fs->got_greeting) {
+        fs->got_greeting = true;
+        qobject_decref(result);
+    } else {
+        QDict *response = qobject_to_qdict(result);
+        if (qdict_haskey(response, "event")) {
+            fd_qmp_session_process_event(fs, response);
+            qobject_decref(result);
+        } else {
+            qobject_decref(fs->result);
+            fs->result = result;
+        }
+    }
+}
+
+static QDict *fd_qmp_session_read(FdQmpSession *fs)
+{
+    QDict *response;
+
+    assert(fs->result == NULL);
+    fs->result = NULL;
+    while (!fs->result) {
+        char buffer[1024];
+        ssize_t len;
+
+        len = read(fs->fd, buffer, sizeof(buffer));
+        if (len == -1 && errno == EINTR) {
+            continue;
+        }
+        if (len < 1) {
+            abort();
+        }
+
+#if defined(DEBUG_LIBQMP)
+        fwrite(buffer, len, 1, stdout);
+        fflush(stdout);
+#endif
+        json_message_parser_feed(&fs->parser, buffer, len);
+    }
+
+    response = qobject_to_qdict(fs->result);
+    fs->result = NULL;
+
+    return response;
+}
+
+static bool qmp_session_fd_wait_event(QmpSession *s, struct timeval *tv)
+{
+    FdQmpSession *fs = (FdQmpSession *)s;
+    fd_set readfds;
+    int ret;
+
+    FD_ZERO(&readfds);
+    FD_SET(fs->fd, &readfds);
+
+    do {
+        ret = select(fs->fd + 1, &readfds, NULL, NULL, tv);
+    } while (ret == -1 && errno == EINTR);
+
+    if (ret) {
+        char buffer[1024];
+        ssize_t len;
+        int saved_event_count;
+
+        do {
+            len = read(fs->fd, buffer, sizeof(buffer));
+        } while (len == -1 && errno == EINTR);
+
+        if (len < 1) {
+            abort();
+        }
+
+#if defined(DEBUG_LIBQMP)
+        fwrite(buffer, len, 1, stdout);
+        fflush(stdout);
+#endif
+        saved_event_count = fs->event_count;
+        json_message_parser_feed(&fs->parser, buffer, len);
+        return (saved_event_count != fs->event_count);
+    }
+
+    return false;
+}
+
+static QObject *qmp_session_fd_dispatch(QmpSession *s, const char *name,
+                                        QDict *args, Error **err)
+{
+    FdQmpSession *fs = (FdQmpSession *)s;
+    QString *str;
+    const char *buffer;
+    size_t size;
+    size_t offset;
+    QDict *request = qdict_new();
+    QDict *response;
+
+    qdict_put(request, "execute", qstring_from_str(name));
+
+    if (qdict_size(args)) {
+        QINCREF(args);
+        qdict_put(request, "arguments", args);
+    }
+
+    str = qobject_to_json(QOBJECT(request));
+    buffer = qstring_get_str(str);
+    size = str->length;
+
+    offset = 0;
+    while (offset < size) {
+        ssize_t len;
+
+        len = write(fs->fd, buffer + offset, size - offset);
+        if (len == -1 && errno == EINTR) {
+            continue;
+        }
+        if (len < 1) {
+            abort();
+        }
+
+#if defined(DEBUG_LIBQMP)
+        fwrite(buffer + offset, size - offset, 1, stdout);
+        fflush(stdout);
+#endif
+        offset += len;
+    }
+
+    QDECREF(str);
+    QDECREF(request);
+
+    response = fd_qmp_session_read(fs);
+
+    if (qdict_haskey(response, "error")) {
+        error_set_qobject(err, qdict_get(response, "error"));
+        QDECREF(response);
+        return NULL;
+    } else {
+        QObject *result = qdict_get(response, "return");
+        qobject_incref(result);
+        QDECREF(response);
+        return result;
+    }
+}
+
+void libqmp_register_event(QmpSession *sess, const char *name, EventTrampolineFunc *func)
+{
+    QmpEventTrampoline *t = qemu_mallocz(sizeof(*t));
+    t->name = name;
+    t->dispatch = func;
+    QTAILQ_INSERT_TAIL(&sess->events, t, node);
+}
+
+QmpSession *qmp_session_new(int fd)
+{
+    FdQmpSession *s = qemu_mallocz(sizeof(*s));
+
+    s->fd = fd;
+    s->session.dispatch = qmp_session_fd_dispatch;
+    s->session.wait_event = qmp_session_fd_wait_event;
+    s->got_greeting = false;
+
+    QTAILQ_INIT(&s->session.events);
+    QTAILQ_INIT(&s->session.signals);
+    json_message_parser_init(&s->parser, fd_qmp_session_parse);
+
+    libqmp_init_events(&s->session);
+    libqmp_qmp_capabilities(&s->session, NULL);
+
+    return &s->session;
+}
+
+void qmp_session_destroy(QmpSession *s)
+{
+    FdQmpSession *fs = container_of(s, FdQmpSession, session);
+
+    while (!QTAILQ_EMPTY(&s->events)) {
+        QmpEventTrampoline *t = QTAILQ_FIRST(&s->events);
+        QTAILQ_REMOVE(&s->events, t, node);
+        qemu_free(t);
+    }
+    if (fs->result) {
+        qobject_decref(fs->result);
+        fs->result = NULL;
+    }
+    json_message_parser_destroy(&fs->parser);
+    close(fs->fd);
+    qemu_free(fs);
+}
+
+typedef struct GenericSignal
+{
+    QmpSignal *signal;
+} GenericSignal;
+
+void *libqmp_signal_new(QmpSession *s, size_t size, int global_handle)
+{
+    GenericSignal *obj;
+    obj = qemu_mallocz(size);
+    obj->signal = qemu_mallocz(sizeof(QmpSignal));
+    obj->signal->sess = s;
+    obj->signal->global_handle = global_handle;
+    // FIXME validate there isn't another global_handle
+    QTAILQ_INIT(&obj->signal->connections);
+    QTAILQ_INSERT_TAIL(&s->signals, obj->signal, node);
+    return obj;
+}
+
+int libqmp_signal_connect(QmpSignal *obj, void *func, void *opaque)
+{
+    QmpConnection *conn;
+
+    conn = qemu_mallocz(sizeof(*conn));
+    conn->fn = func;
+    conn->opaque = opaque;
+    conn->handle = ++obj->max_handle;
+    QTAILQ_INSERT_TAIL(&obj->connections, conn, node);
+    return conn->handle;
+}
+
+void libqmp_signal_disconnect(QmpSignal *obj, int handle)
+{
+    QmpConnection *conn;
+
+    QTAILQ_FOREACH(conn, &obj->connections, node) {
+        if (conn->handle == handle) {
+            break;
+        }
+    }
+    if (conn) {
+        QTAILQ_REMOVE(&obj->connections, conn, node);
+        qemu_free(conn);
+    }
+}
+
+void libqmp_signal_free(void *base, QmpSignal *obj)
+{
+    QTAILQ_REMOVE(&obj->sess->signals, obj, node);
+
+    libqmp_put_event(obj->sess, obj->global_handle, NULL);
+    while (!QTAILQ_EMPTY(&obj->connections)) {
+        QmpConnection *conn = QTAILQ_FIRST(&obj->connections);
+        QTAILQ_REMOVE(&obj->connections, conn, node);
+        qemu_free(conn);
+    }
+    qemu_free(obj);
+    qemu_free(base);
+}
+
+bool libqmp_wait_event(QmpSession *s, struct timeval *tv)
+{
+    return s->wait_event(s, tv);
+}
+
+bool libqmp_poll_event(QmpSession *s)
+{
+    struct timeval tv = { 0, 0 };
+    bool got_event = false;
+    bool ret;
+
+    do {
+        ret = libqmp_wait_event(s, &tv);
+        got_event |= ret;
+    } while (ret);
+
+    return got_event;
+}
diff --git a/libqmp-core.h b/libqmp-core.h
new file mode 100644
index 0000000..c1063e6
--- /dev/null
+++ b/libqmp-core.h
@@ -0,0 +1,44 @@ 
+/*
+ * QAPI
+ *
+ * Copyright IBM, Corp. 2011
+ *
+ * Authors:
+ *  Anthony Liguori   <aliguori@us.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU LGPL, version 2.  See
+ * the COPYING.LIB file in the top-level directory.
+ */
+#ifndef LIBQMP_CORE_H
+#define LIBQMP_CORE_H
+
+#include <sys/types.h>
+#include "qmp-types.h"
+#include "error.h"
+
+typedef struct QmpSession QmpSession;
+
+QmpSession *qmp_session_new(int fd);
+void qmp_session_destroy(QmpSession *s);
+
+bool libqmp_poll_event(QmpSession *s);
+bool libqmp_wait_event(QmpSession *s, struct timeval *tv);
+
+void *libqmp_signal_new(QmpSession *s, size_t size, int global_handle);
+int libqmp_signal_connect(QmpSignal *obj, void *func, void *opaque);
+void libqmp_signal_disconnect(QmpSignal *obj, int handle);
+void libqmp_signal_free(void *base, QmpSignal *obj);
+
+#define libqmp_signal_init(s, type, global_handle) \
+    ((type *)libqmp_signal_new(s, sizeof(type), global_handle))
+
+#define signal_connect(obj, fn, opaque) \
+    libqmp_signal_connect((obj)->signal, (obj)->func = fn, opaque)
+
+#define signal_disconnect(obj, handle) \
+    libqmp_signal_disconnect((obj)->signal, handle)
+
+#define signal_unref(obj) \
+    libqmp_signal_free((obj), (obj)->signal)
+
+#endif
diff --git a/libqmp-internal.h b/libqmp-internal.h
new file mode 100644
index 0000000..01a3dd8
--- /dev/null
+++ b/libqmp-internal.h
@@ -0,0 +1,56 @@ 
+/*
+ * QAPI
+ *
+ * Copyright IBM, Corp. 2011
+ *
+ * Authors:
+ *  Anthony Liguori   <aliguori@us.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU LGPL, version 2.  See
+ * the COPYING.LIB file in the top-level directory.
+ */
+#ifndef LIBQMP_INTERNAL_H
+#define LIBQMP_INTERNAL_H
+
+#include "qemu-objects.h"
+#include "qmp-marshal-types.h"
+#include "error_int.h"
+
+typedef void (EventTrampolineFunc)(QDict *qmp__args, void *qmp__fn, void *qmp__opaque, Error **qmp__errp);
+
+typedef struct QmpEventTrampoline
+{
+    const char *name;
+    EventTrampolineFunc *dispatch;
+    QTAILQ_ENTRY(QmpEventTrampoline) node;
+} QmpEventTrampoline;
+
+typedef struct QmpConnection
+{
+    void *fn;
+    void *opaque;
+    int handle;
+    QTAILQ_ENTRY(QmpConnection) node;
+} QmpConnection;
+
+struct QmpSignal
+{
+    QmpSession *sess;
+    int global_handle;
+    int max_handle;
+    QTAILQ_HEAD(, QmpConnection) connections;
+    QTAILQ_ENTRY(QmpSignal) node;
+};
+
+struct QmpSession
+{
+    QObject *(*dispatch)(QmpSession *session, const char *name, QDict *args, Error **err);
+    bool (*wait_event)(QmpSession *session, struct timeval *tv);
+    QTAILQ_HEAD(, QmpEventTrampoline) events;
+    QTAILQ_HEAD(, QmpSignal) signals;
+};
+
+void libqmp_init_events(QmpSession *sess);
+void libqmp_register_event(QmpSession *sess, const char *name, EventTrampolineFunc *func);
+
+#endif
diff --git a/qmp-gen.py b/qmp-gen.py
index f1bdb2f..35f64cf 100644
--- a/qmp-gen.py
+++ b/qmp-gen.py
@@ -925,6 +925,321 @@  qmp__out:
 
     return ret
 
+
+def gen_lib_decl(name, options, retval, suffix='', proxy=False, async=False):
+    if proxy:
+        async=True
+
+    if proxy:
+        args = []
+    else:
+        args = ['QmpSession *qmp__session']
+    for argname, argtype, optional in parse_args(options):
+        if argtype == '**':
+            args.append('KeyValues * %s' % c_var(argname))
+        else:
+            if optional:
+                args.append('bool has_%s' % c_var(argname))
+            args.append('%s %s' % (qmp_type_to_c(argtype), c_var(argname)))
+
+    args.append('Error **qmp__err')
+
+    if proxy:
+        prefix = 'qmp'
+    else:
+        prefix = 'libqmp'
+
+    if proxy:
+        qmp_retval = 'void'
+        args.append('%sCompletionFunc *qmp__cc' % camel_case(name))
+        args.append('void *qmp__opaque')
+    else:
+        qmp_retval = qmp_type_to_c(retval, True)
+
+    return mcgen('''
+%(ret)s %(prefix)s_%(name)s(%(args)s)%(suffix)s
+''', ret=qmp_retval, prefix=prefix, name=c_var(name), args=', '.join(args), suffix=suffix)
+
+def gen_lib_declaration(name, options, retval):
+    return gen_lib_decl(name, options, retval, ';')
+
+def gen_lib_event_definition(name, typeinfo):
+    args = ''
+    for argname, argtype, optional in parse_args(typeinfo):
+        if optional:
+            args += cgen('    bool has_%(name)s;', name=c_var(argname))
+        args += cgen('    %(type)s %(name)s = 0;', type=qmp_type_to_c(argtype, True), name=c_var(argname))
+
+    ret = mcgen('''
+
+static void libqmp_notify_%(fn_name)s(QDict *qmp__args, void *qmp__fn, void *qmp__opaque, Error **qmp__errp)
+{
+    %(fn_ret)s *qmp__native_fn = qmp__fn;
+    Error *qmp__err = NULL;
+%(args)s
+
+    (void)qmp__err;
+''',
+                fn_name=de_camel_case(qmp_event_to_c(name)),
+                fn_ret=qmp_event_func_to_c(name), args=args)
+
+    for argname, argtype, optional in parse_args(typeinfo):
+        if optional:
+            ret += cgen('    BUILD_BUG()')
+        ret += mcgen('''
+
+    if (!qdict_haskey(qmp__args, "%(name)s")) {
+        error_set(qmp__errp, QERR_MISSING_PARAMETER, "%(name)s");
+        goto qmp__out;
+    }
+
+    %(c_name)s = %(unmarshal)s(qdict_get(qmp__args, "%(name)s"), &qmp__err);
+    if (qmp__err) {
+        if (error_is_type(qmp__err, QERR_INVALID_PARAMETER_TYPE)) {
+            error_set(qmp__errp, QERR_INVALID_PARAMETER_TYPE, "%(name)s",
+                      error_get_field(qmp__err, "expected"));
+            error_free(qmp__err);
+            qmp__err = NULL;
+        } else {
+            error_propagate(qmp__errp, qmp__err);
+        }
+        goto qmp__out;
+    }
+''', name=argname, c_name=c_var(argname), unmarshal=qmp_type_from_qobj(argtype))
+
+    arglist = ['qmp__opaque']
+    for argname, argtype, optional in parse_args(typeinfo):
+        arglist.append(c_var(argname))
+    ret += mcgen('''
+
+    qmp__native_fn(%(args)s);
+''', args=', '.join(arglist))
+
+    has_label = False
+    for argname, argtype, optional in parse_args(typeinfo):
+        if not has_label:
+            ret += mcgen('''
+qmp__out:
+''')
+            has_label = True
+
+        if qmp_type_should_free(argtype):
+            ret += cgen('    %(free)s(%(name)s);', free=qmp_free_func(argtype), name=c_var(argname))
+    ret += mcgen('''
+    return;
+}
+''')
+    return ret
+
+def gen_async_lib_definition(name, options, retval):
+    ret = mcgen('''
+
+typedef struct %(cc_name)sCompletionCB
+{
+    %(cc_name)sCompletionFunc *cb;
+    void *opaque;
+} %(cc_name)sCompletionCB;
+
+static void qmp_%(c_name)s_cb(void *qmp__opaque, QObject *qmp__retval, Error *qmp__err)
+{
+    %(cc_name)sCompletionCB *qmp__cb = qmp__opaque;
+''',
+                cc_name=camel_case(name), c_name=c_var(name))
+
+    if retval != 'none':
+        ret += cgen('    %(ret_type)s qmp__native_retval = 0;',
+                           ret_type=qmp_type_to_c(retval, True))
+
+    if type(retval) == list:
+        ret += mcgen('''
+
+    if (!qmp__err) {
+        QList *qmp__list_retval = qobject_to_qlist(qmp__retval);
+        QListEntry *qmp__i;
+        QLIST_FOREACH_ENTRY(qmp__list_retval, qmp__i) {
+            %(ret_type)s qmp__native_i = %(unmarshal)s(qmp__i->value, &qmp__err);
+            if (qmp__err) {
+                %(free)s(qmp__native_retval);
+                break;
+            }
+            qmp__native_i->next = qmp__native_retval;
+            qmp__native_retval = qmp__native_i;
+        }
+    }
+''',
+                            ret_type=qmp_type_to_c(retval[0], True),
+                            unmarshal=qmp_type_from_qobj(retval[0]),
+                            free=qmp_free_func(retval[0]))
+    elif is_dict(retval):
+        ret += mcgen('''
+    // FIXME (using an anonymous dict as return value)')
+    BUILD_BUG();
+''')
+    elif retval != 'none':
+        ret += mcgen('''
+
+    if (!qmp__err) {
+        qmp__native_retval = %(unmarshal)s(qmp__retval, &qmp__err);
+    }
+''',
+                            unmarshal=qmp_type_from_qobj(retval))
+    ret += cgen('')
+    if retval == 'none':
+        ret += cgen('    qmp__cb->cb(qmp__cb->opaque, qmp__err);')
+    else:
+        ret += cgen('    qmp__cb->cb(qmp__cb->opaque, qmp__native_retval, qmp__err);')
+    ret += cgen('}')
+
+    return ret
+
+def gen_lib_definition(name, options, retval, proxy=False, async=False):
+    if proxy:
+        async = True
+
+    ret = ''
+    if proxy:
+        ret += gen_async_lib_definition(name, options, retval)
+
+    fn_decl = gen_lib_decl(name, options, retval, proxy=proxy, async=async)
+    ret += mcgen('''
+
+%(fn_decl)s
+{
+    QDict *qmp__args = qdict_new();
+''',
+                        fn_decl=fn_decl)
+    if async:
+        ret += mcgen('''
+    %(cc_name)sCompletionCB *qmp__cb = qemu_mallocz(sizeof(*qmp__cb));
+
+    qmp__cb->cb = qmp__cc;
+    qmp__cb->opaque = qmp__opaque;
+''',
+                            cc_name=camel_case(name))
+    else:
+        ret += mcgen('''
+    Error *qmp__local_err = NULL;
+    QObject *qmp__retval = NULL;
+''')
+        if retval != 'none':
+            ret += cgen('    %(ret_type)s qmp__native_retval = 0;',
+                               ret_type=qmp_type_to_c(retval, True))
+        if qmp_type_is_event(retval):
+            ret += cgen('    int qmp__global_handle = 0;')
+    ret += cgen('')
+
+    for argname, argtype, optional in parse_args(options):
+        if argtype == '**':
+            ret += mcgen('''
+    {
+        KeyValues *qmp__i;
+        for (qmp__i = %(name)s; qmp__i; qmp__i = qmp__i->next) {
+            qdict_put(qmp__args, qmp__i->key, qstring_from_str(qmp__i->value));
+        }
+    }
+''',
+                         name=c_var(argname))
+        else:
+            if optional:
+                ret += mcgen('''
+    if (has_%(c_name)s) {
+''',
+                                    c_name=c_var(argname))
+                push_indent()
+            ret += mcgen('''
+    qdict_put_obj(qmp__args, "%(name)s", %(marshal)s(%(c_name)s));
+''',
+                                name=argname, c_name=c_var(argname),
+                                marshal=qmp_type_to_qobj(argtype))
+            if optional:
+                pop_indent()
+                ret += mcgen('''
+    }
+''')
+
+    if proxy:
+        ret += mcgen('''
+    qmp_guest_dispatch("%(name)s", qmp__args, qmp__err, qmp_%(c_name)s_cb, qmp__cb);
+''',
+                            name=name, c_name=c_var(name))
+    else:
+        ret += mcgen('''
+    qmp__retval = qmp__session->dispatch(qmp__session, "%(name)s", qmp__args, &qmp__local_err);
+''',
+                            name=name)
+    ret += mcgen('''
+
+    QDECREF(qmp__args);
+''')
+
+    if async:
+        pass
+    elif type(retval) == list:
+        ret += mcgen('''
+
+    if (!qmp__local_err) {
+        QList *qmp__list_retval = qobject_to_qlist(qmp__retval);
+        QListEntry *qmp__i;
+        QLIST_FOREACH_ENTRY(qmp__list_retval, qmp__i) {
+            %(type)s qmp__native_i = %(unmarshal)s(qmp__i->value, &qmp__local_err);
+            if (qmp__local_err) {
+                %(free)s(qmp__native_retval);
+                break;
+            }
+            qmp__native_i->next = qmp__native_retval;
+            qmp__native_retval = qmp__native_i;
+        }
+        qobject_decref(qmp__retval);
+    }
+    error_propagate(qmp__err, qmp__local_err);
+    return qmp__native_retval;
+''',
+                     type=qmp_type_to_c(retval[0], True),
+                     unmarshal=qmp_type_from_qobj(retval[0]),
+                     free=qmp_free_func(retval[0]))
+    elif is_dict(retval):
+        ret += mcgen('''
+    // FIXME (using an anonymous dict as return value)
+    BUILD_BUG();
+''')
+    elif qmp_type_is_event(retval):
+        if proxy:
+            ret += cgen('    BUILD_BUG();')
+        ret += mcgen('''
+    if (!qmp__local_err) {
+        qmp__global_handle = %(unmarshal)s(qmp__retval, &qmp__local_err);
+        qobject_decref(qmp__retval);
+        qmp__retval = NULL;
+    }
+    if (!qmp__local_err) {
+        qmp__native_retval = libqmp_signal_init(qmp__session, %(type)s, qmp__global_handle);
+    }
+    error_propagate(qmp__err, qmp__local_err);
+    return qmp__native_retval;
+''',
+                            unmarshal=qmp_type_from_qobj('int'),
+                            type=qmp_event_to_c(retval))
+    elif retval != 'none':
+        ret += mcgen('''
+
+    if (!qmp__local_err) {
+        qmp__native_retval = %(unmarshal)s(qmp__retval, &qmp__local_err);
+        qobject_decref(qmp__retval);
+    }
+    error_propagate(qmp__err, qmp__local_err);
+    return qmp__native_retval;
+''',
+                            unmarshal=qmp_type_from_qobj(retval))
+    else:
+        ret += mcgen('''
+    qobject_decref(qmp__retval);
+    error_propagate(qmp__err, qmp__local_err);
+''')
+
+    ret += cgen('}')
+
+    return ret
+
 def tokenize(data):
     while len(data):
         if data[0] in ['{', '}', ':', ',', '[', ']']:
@@ -1028,6 +1343,18 @@  def generate(kind):
 #include "qmp.h"
 #include "qmp-core.h"
 ''')
+    elif kind == 'lib-header':
+        ret += mcgen('''
+#ifndef LIBQMP_H
+#define LIBQMP_H
+
+#include "libqmp-core.h"
+''')
+    elif kind == 'lib-body':
+        ret += mcgen('''
+#include "libqmp.h"
+#include "libqmp-internal.h"
+''')
     
     exprs = []
     expr = ''
@@ -1084,6 +1411,8 @@  def generate(kind):
            event_types[name] = data
            if kind == 'types-header':
                ret += gen_type_declaration(name, data)
+           elif kind == 'lib-body':
+               ret += gen_lib_event_definition(name, data)
        elif s.has_key('command'):
             name = s['command']
             options = {}
@@ -1094,10 +1423,17 @@  def generate(kind):
                 retval = s['returns']
             if kind == 'body':
                 async = qmp_is_async_cmd(name)
+                proxy = qmp_is_proxy_cmd(name)
+                if proxy:
+                    ret += gen_lib_definition(name, options, retval, proxy=True)
                 ret += gen_definition(name, options, retval, async=async)
             elif kind == 'header':
                 async = qmp_is_async_cmd(name)
                 ret += gen_declaration(name, options, retval, async=async)
+            elif kind == 'lib-body':
+                ret += gen_lib_definition(name, options, retval)
+            elif kind == 'lib-header':
+                ret += gen_lib_declaration(name, options, retval)
 
     if kind.endswith('header'):
         ret += cgen('#endif')
@@ -1135,6 +1471,21 @@  static void qmp_init_marshal(void)
 
 qapi_init(qmp_init_marshal);
 ''')
+    elif kind == 'lib-body':
+        ret += mcgen('''
+
+void libqmp_init_events(QmpSession *sess)
+{
+''')
+        for event in event_types:
+            ret += mcgen('''
+    libqmp_register_event(sess, "%(name)s", &libqmp_notify_%(c_event_name)s);
+''',
+                         name=event,
+                         c_event_name=de_camel_case(qmp_event_to_c(event)))
+        ret += mcgen('''
+}
+''')
 
     return ret