[xen staging] libxl_qmp: Implementation of libxl__ev_qmp_*

classic Classic list List threaded Threaded
1 message Options
Reply | Threaded
Open this post in threaded view
|

[xen staging] libxl_qmp: Implementation of libxl__ev_qmp_*

patchbot
commit a40e753fa1fe2d63dda63badd3807af40e3f7656
Author:     Anthony PERARD <[hidden email]>
AuthorDate: Thu Nov 8 17:38:19 2018 +0000
Commit:     Wei Liu <[hidden email]>
CommitDate: Fri Jan 11 14:57:44 2019 +0000

    libxl_qmp: Implementation of libxl__ev_qmp_*
   
    This patch implement the API libxl__ev_qmp documented in the previous
    patch, "libxl: Design of an async API to issue QMP commands to QEMU".
   
    Since this API is to interact with QEMU via the QMP protocol, it also
    implement a QMP client. The specification for the QEMU Machine Protocol
    (QMP) can be found in the QEMU repository at:
    https://git.qemu.org/?p=qemu.git;a=blob_plain;f=docs/interop/qmp-spec.txt
   
    Signed-off-by: Anthony PERARD <[hidden email]>
    Acked-by: Ian Jackson <[hidden email]>
    [ wei: fix build ]
    Signed-off-by: Wei Liu <[hidden email]>
---
 tools/libxl/libxl_internal.h |  34 ++
 tools/libxl/libxl_qmp.c      | 739 +++++++++++++++++++++++++++++++++++++++++++
 tools/libxl/libxl_types.idl  |   6 +
 3 files changed, 779 insertions(+)

diff --git a/tools/libxl/libxl_internal.h b/tools/libxl/libxl_internal.h
index 5ad44c38dc..c3934e2d8d 100644
--- a/tools/libxl/libxl_internal.h
+++ b/tools/libxl/libxl_internal.h
@@ -418,6 +418,19 @@ _hidden int libxl__ev_qmp_send(libxl__gc *gc, libxl__ev_qmp *ev,
                                const char *cmd, libxl__json_object *args);
 _hidden void libxl__ev_qmp_dispose(libxl__gc *gc, libxl__ev_qmp *ev);
 
+typedef enum {
+    /* initial state */
+    qmp_state_disconnected = 1,
+    /* connected to QMP socket, waiting for greeting message */
+    qmp_state_connecting,
+    /* qmp_capabilities command sent, waiting for reply */
+    qmp_state_capability_negotiation,
+    /* sending user's cmd and waiting for reply */
+    qmp_state_waiting_reply,
+    /* ready to send commands */
+    qmp_state_connected,
+} libxl__qmp_state;
+
 struct libxl__ev_qmp {
     /* caller should include this in their own struct */
     /* caller must fill these in, and they must all remain valid */
@@ -425,6 +438,27 @@ struct libxl__ev_qmp {
     libxl_domid domid;
     libxl__ev_qmp_callback *callback;
     int payload_fd; /* set to send a fd with the command, -1 otherwise */
+
+    /*
+     * remaining fields are private to libxl_ev_qmp_*
+     */
+
+    libxl__carefd *cfd;
+    libxl__ev_fd efd;
+    libxl__qmp_state state;
+    int id;
+    int next_id;        /* next id to use */
+    /* receive buffer */
+    char *rx_buf;
+    size_t rx_buf_size; /* current allocated size */
+    size_t rx_buf_used; /* actual data in the buffer */
+    /* sending buffer */
+    char *tx_buf;
+    size_t tx_buf_len;  /* tx_buf size */
+    size_t tx_buf_off;  /* already sent */
+    /* The message to send when ready */
+    char *msg;
+    int msg_id;
 };
 
 
diff --git a/tools/libxl/libxl_qmp.c b/tools/libxl/libxl_qmp.c
index 73f2202b4f..4e21a504db 100644
--- a/tools/libxl/libxl_qmp.c
+++ b/tools/libxl/libxl_qmp.c
@@ -75,11 +75,18 @@
 #  define DEBUG_REPORT_RECEIVED(dom, buf, len) ((void)0)
 #endif
 
+#ifdef DEBUG_QMP_CLIENT
+#  define LOG_QMP(f, ...) LOGD(DEBUG, ev->domid, f, ##__VA_ARGS__)
+#else
+#  define LOG_QMP(f, ...)
+#endif
+
 /*
  * QMP types & constant
  */
 
 #define QMP_RECEIVE_BUFFER_SIZE 4096
+#define QMP_MAX_SIZE_RX_BUF MB(1)
 #define PCI_PT_QDEV_ID "pci-pt-%02x_%02x.%01x"
 
 /*
@@ -1307,6 +1314,738 @@ int libxl__qmp_initializations(libxl__gc *gc, uint32_t domid,
     return ret;
 }
 
+/* ------------ Implementation of libxl__ev_qmp ---------------- */
+
+/*
+ * Possible internal state compared to qmp_state:
+ *
+ * qmp_state     External   cfd    efd     id     rx_buf* tx_buf* msg*
+ * disconnected   Idle       NULL   Idle    reset  free    free    free
+ * connecting     Active     open   IN      reset  used    free    set
+ * cap.neg        Active     open   IN|OUT  sent   used    cap_neg set
+ * cap.neg        Active     open   IN      sent   used    free    set
+ * connected      Connected  open   IN      any    used    free    free
+ * waiting_reply  Active     open   IN|OUT  sent   used    free    set
+ * waiting_reply  Active     open   IN|OUT  sent   used    user's  free
+ * waiting_reply  Active     open   IN      sent   used    free    free
+ * broken[1]      none[2]    any    Active  any    any     any     any
+ *
+ * [1] When an internal function return an error, it can leave ev_qmp in a
+ * `broken` state but only if the caller is another internal function.
+ * That `broken` needs to be cleaned up, e.i. transitionned to the
+ * `disconnected` state, before the control of ev_qmp is released outsides
+ * of ev_qmp implementation.
+ *
+ * [2] This internal state should not be visible externally, see [1].
+ *
+ * Possible buffers states:
+ * - receiving buffer:
+ *                     free   used
+ *     rx_buf           NULL   NULL or allocated
+ *     rx_buf_size      0      allocation size of `rx_buf`
+ *     rx_buf_used      0      <= rx_buf_size, actual data in the buffer
+ * - transmitting buffer:
+ *                     free   used
+ *     tx_buf           NULL   contains data
+ *     tx_buf_len       0      size of data
+ *     tx_buf_off       0      <= tx_buf_len, data already sent
+ * - queued user command:
+ *                     free  set
+ *     msg              NULL  contains data
+ *     msg_id           0     id assoctiated with the command in `msg`
+ *
+ * - Allowed internal state transition:
+ * disconnected                     -> connecting
+ * connection                       -> capability_negotiation
+ * capability_negotiation/connected -> waiting_reply
+ * waiting_reply                    -> connected
+ * any                              -> broken
+ * broken                           -> disconnected
+ * any                              -> disconnected
+ *
+ * The QEMU Machine Protocol (QMP) specification can be found in the QEMU
+ * repository:
+ * https://git.qemu.org/?p=qemu.git;a=blob_plain;f=docs/interop/qmp-spec.txt
+ */
+
+/* prototypes */
+
+static void qmp_ev_fd_callback(libxl__egc *egc, libxl__ev_fd *ev_fd,
+                               int fd, short events, short revents);
+static int qmp_ev_callback_writable(libxl__gc *gc,
+                                    libxl__ev_qmp *ev, int fd);
+static int qmp_ev_callback_readable(libxl__egc *egc,
+                                    libxl__ev_qmp *ev, int fd);
+static int qmp_ev_get_next_msg(libxl__egc *egc, libxl__ev_qmp *ev,
+                               libxl__json_object **o_r);
+static int qmp_ev_handle_message(libxl__egc *egc,
+                                 libxl__ev_qmp *ev,
+                                 const libxl__json_object *resp);
+
+/* helpers */
+
+static void qmp_ev_ensure_reading_writing(libxl__gc *gc, libxl__ev_qmp *ev)
+    /* Update the state of `efd` to match the permited state
+     * on entry: !disconnected */
+{
+    short events = POLLIN;
+
+    if (ev->tx_buf)
+        events |= POLLOUT;
+    else if ((ev->state == qmp_state_waiting_reply) && ev->msg)
+        events |= POLLOUT;
+
+    libxl__ev_fd_modify(gc, &ev->efd, events);
+}
+
+static void qmp_ev_set_state(libxl__gc *gc, libxl__ev_qmp *ev,
+                             libxl__qmp_state new_state)
+    /* on entry: !broken and !disconnected */
+{
+    switch (new_state) {
+    case qmp_state_disconnected:
+        break;
+    case qmp_state_connecting:
+        assert(ev->state == qmp_state_disconnected);
+        break;
+    case qmp_state_capability_negotiation:
+        assert(ev->state == qmp_state_connecting);
+        break;
+    case qmp_state_waiting_reply:
+        assert(ev->state == qmp_state_capability_negotiation ||
+               ev->state == qmp_state_connected);
+        break;
+    case qmp_state_connected:
+        assert(ev->state == qmp_state_waiting_reply);
+        break;
+    }
+
+    ev->state = new_state;
+
+    qmp_ev_ensure_reading_writing(gc, ev);
+}
+
+static void qmp_ev_tx_buf_clear(libxl__ev_qmp *ev)
+{
+    ev->tx_buf = NULL;
+    ev->tx_buf_len = 0;
+    ev->tx_buf_off = 0;
+}
+
+static int qmp_error_class_to_libxl_error_code(libxl__gc *gc,
+                                               const char *eclass)
+{
+    const libxl_enum_string_table *t = libxl_error_string_table;
+    const char skip[] = "QMP_";
+    const size_t skipl = sizeof(skip) - 1;
+
+    /* compare "QMP_GENERIC_ERROR" from libxl_error to "GenericError"
+     * generated by the QMP server */
+
+    for (; t->s; t++) {
+            const char *s = eclass;
+            const char *se = t->s;
+        if (strncasecmp(t->s, skip, skipl))
+            continue;
+        se += skipl;
+        while (*s && *se) {
+            /* skip underscores */
+            if (*se == '_') {
+                se++;
+                continue;
+            }
+            if (tolower(*s) != tolower(*se))
+                break;
+            s++, se++;
+        }
+        if (!*s && !*se)
+            return t->v;
+    }
+
+    LOG(ERROR, "Unknown QMP error class '%s'", eclass);
+    return ERROR_UNKNOWN_QMP_ERROR;
+}
+
+/* Setup connection */
+
+static int qmp_ev_connect(libxl__gc *gc, libxl__ev_qmp *ev)
+    /* disconnected -> connecting but with `msg` free
+     * on error: broken */
+{
+    int fd;
+    int rc, r;
+    struct sockaddr_un un;
+    const char *qmp_socket_path;
+
+    assert(ev->state == qmp_state_disconnected);
+
+    qmp_socket_path = libxl__qemu_qmp_path(gc, ev->domid);
+
+    LOGD(DEBUG, ev->domid, "Connecting to %s", qmp_socket_path);
+
+    libxl__carefd_begin();
+    fd = socket(AF_UNIX, SOCK_STREAM, 0);
+    ev->cfd = libxl__carefd_opened(CTX, fd);
+    if (!ev->cfd) {
+        LOGED(ERROR, ev->domid, "socket() failed");
+        rc = ERROR_FAIL;
+        goto out;
+    }
+    rc = libxl_fd_set_nonblock(CTX, libxl__carefd_fd(ev->cfd), 1);
+    if (rc)
+        goto out;
+
+    rc = libxl__prepare_sockaddr_un(gc, &un, qmp_socket_path,
+                                    "QMP socket");
+    if (rc)
+        goto out;
+
+    r = connect(libxl__carefd_fd(ev->cfd),
+                (struct sockaddr *) &un, sizeof(un));
+    if (r && errno != EINPROGRESS) {
+        LOGED(ERROR, ev->domid, "Failed to connect to QMP socket %s",
+              qmp_socket_path);
+        rc = ERROR_FAIL;
+        goto out;
+    }
+
+    rc = libxl__ev_fd_register(gc, &ev->efd, qmp_ev_fd_callback,
+                               libxl__carefd_fd(ev->cfd), POLLIN);
+    if (rc)
+        goto out;
+
+    qmp_ev_set_state(gc, ev, qmp_state_connecting);
+
+    return 0;
+
+out:
+    return rc;
+}
+
+/* QMP FD callbacks */
+
+static void qmp_ev_fd_callback(libxl__egc *egc, libxl__ev_fd *ev_fd,
+                               int fd, short events, short revents)
+    /* On entry, ev_fd is (of course) Active.  The ev_qmp may be in any
+     * state where this is permitted.  qmp_ev_fd_callback will do the work
+     * necessary to make progress, depending on the current state, and make
+     * the appropriate state transitions and callbacks.  */
+{
+    libxl__ev_qmp *ev = CONTAINER_OF(ev_fd, *ev, efd);
+    STATE_AO_GC(ev->ao);
+    int rc;
+
+    if (revents & (POLLHUP|POLLERR)) {
+        int r;
+        int error_val = 0;
+        socklen_t opt_len = sizeof(error_val);
+
+        r = getsockopt(fd, SOL_SOCKET, SO_ERROR, &error_val, &opt_len);
+        if (r)
+            LOGED(ERROR, ev->domid, "getsockopt failed");
+        if (!r && error_val) {
+            errno = error_val;
+            LOGED(ERROR, ev->domid, "error on QMP socket");
+        } else {
+            LOGD(ERROR, ev->domid,
+                 "received POLLHUP|POLLERR from QMP socket");
+        }
+        rc = ERROR_PROTOCOL_ERROR_QMP;
+        goto error;
+    }
+
+    if (revents & ~(POLLIN|POLLOUT)) {
+        LOGD(ERROR, ev->domid,
+             "unexpected poll event 0x%x on QMP socket (expected POLLIN "
+             "and/or POLLOUT)",
+            revents);
+        rc = ERROR_FAIL;
+        goto error;
+    }
+
+    if (revents & POLLOUT) {
+        rc = qmp_ev_callback_writable(gc, ev, fd);
+        if (rc)
+            goto error;
+    }
+
+    if (revents & POLLIN) {
+        rc = qmp_ev_callback_readable(egc, ev, fd);
+        if (rc < 0)
+            goto error;
+        if (rc == 1) {
+            /* user callback has been called */
+            return;
+        }
+    }
+
+    return;
+
+error:
+    assert(rc);
+
+    LOGD(ERROR, ev->domid,
+         "Error happened with the QMP connection to QEMU");
+
+    /* On error, deallocate all private ressources */
+    libxl__ev_qmp_dispose(gc, ev);
+
+    /* And tell libxl__ev_qmp user about the error */
+    ev->callback(egc, ev, NULL, rc); /* must be last */
+}
+
+static int qmp_ev_callback_writable(libxl__gc *gc,
+                                    libxl__ev_qmp *ev, int fd)
+    /* on entry: !disconnected
+     * on return, one of these state transition:
+     *   waiting_reply (with msg set) -> waiting_reply (with msg free)
+     *   tx_buf set -> same state or tx_buf free
+     * on error: broken */
+{
+    int rc;
+    ssize_t r;
+
+    if (ev->state == qmp_state_waiting_reply) {
+        if (ev->msg) {
+            assert(!ev->tx_buf);
+            ev->tx_buf = ev->msg;
+            ev->tx_buf_len = strlen(ev->msg);
+            ev->tx_buf_off = 0;
+            ev->id = ev->msg_id;
+            ev->msg = NULL;
+            ev->msg_id = 0;
+        }
+    }
+
+    assert(ev->tx_buf);
+
+    LOG_QMP("sending: '%.*s'", (int)ev->tx_buf_len, ev->tx_buf);
+
+    /*
+     * We will send a file descriptor associated with a command on the
+     * first byte of this command.
+     */
+    if (ev->state == qmp_state_waiting_reply &&
+        ev->payload_fd >= 0 &&
+        ev->tx_buf_off == 0) {
+
+        rc = libxl__sendmsg_fds(gc, fd, ev->tx_buf, 1,
+                                1, &ev->payload_fd, "QMP socket");
+        /* Check for EWOULDBLOCK, and return to try again later */
+        if (rc == ERROR_NOT_READY)
+            return 0;
+        if (rc)
+            return rc;
+        ev->tx_buf_off++;
+    }
+
+    while (ev->tx_buf_off < ev->tx_buf_len) {
+        ssize_t max_write = ev->tx_buf_len - ev->tx_buf_off;
+        r = write(fd, ev->tx_buf + ev->tx_buf_off, max_write);
+        if (r < 0) {
+            if (errno == EINTR)
+                continue;
+            if (errno == EWOULDBLOCK)
+                break;
+            LOGED(ERROR, ev->domid, "failed to write to QMP socket");
+            return ERROR_FAIL;
+        }
+        assert(r > 0 && r <= max_write);
+        ev->tx_buf_off += r;
+    }
+
+    if (ev->tx_buf_off == ev->tx_buf_len)
+        qmp_ev_tx_buf_clear(ev);
+
+    qmp_ev_ensure_reading_writing(gc, ev);
+
+    return 0;
+}
+
+static int qmp_ev_callback_readable(libxl__egc *egc,
+                                    libxl__ev_qmp *ev, int fd)
+    /*
+     * Return values:
+     *   < 0    libxl error code
+     *   0      success
+     *   1      success, but a user callback has been called,
+     *          `ev` should not be used anymore.
+     *
+     * This function will update the rx buffer and possibly update
+     * ev->state:
+     *  connecting             -> capability_negotiation
+     *  capability_negotiation -> waiting_reply
+     *  waiting_reply          -> connected
+     * on error: broken
+     */
+{
+    STATE_AO_GC(ev->ao);
+    int rc;
+    ssize_t r;
+
+    while (1) {
+        while (1) {
+            libxl__json_object *o = NULL;
+
+            /* parse rx buffer to find one json object */
+            rc = qmp_ev_get_next_msg(egc, ev, &o);
+            if (rc == ERROR_NOTFOUND)
+                break;
+            else if (rc)
+                return rc;
+
+            /* Must be last and return when the user callback is called */
+            rc = qmp_ev_handle_message(egc, ev, o);
+            if (rc)
+                /* returns both rc values -ERROR_* and 1 */
+                return rc;
+        }
+
+        /* Check if the buffer still have space, or increase size */
+        if (ev->rx_buf_size - ev->rx_buf_used < QMP_RECEIVE_BUFFER_SIZE) {
+            size_t newsize = ev->rx_buf_size * 2 + QMP_RECEIVE_BUFFER_SIZE;
+
+            if (newsize > QMP_MAX_SIZE_RX_BUF) {
+                LOGD(ERROR, ev->domid,
+                     "QMP receive buffer is too big (%zu > %lld)",
+                     newsize, QMP_MAX_SIZE_RX_BUF);
+                return ERROR_BUFFERFULL;
+            }
+            ev->rx_buf_size = newsize;
+            ev->rx_buf = libxl__realloc(gc, ev->rx_buf, ev->rx_buf_size);
+        }
+
+        r = read(fd, ev->rx_buf + ev->rx_buf_used,
+                 ev->rx_buf_size - ev->rx_buf_used);
+        if (r < 0) {
+            if (errno == EINTR)
+                continue;
+            if (errno == EWOULDBLOCK)
+                break;
+            LOGED(ERROR, ev->domid, "error reading QMP socket");
+            return ERROR_FAIL;
+        }
+
+        if (r == 0) {
+            LOGD(ERROR, ev->domid, "Unexpected EOF on QMP socket");
+            return ERROR_PROTOCOL_ERROR_QMP;
+        }
+
+        LOG_QMP("received %ldB: '%.*s'", r,
+                (int)r, ev->rx_buf + ev->rx_buf_used);
+
+        ev->rx_buf_used += r;
+        assert(ev->rx_buf_used <= ev->rx_buf_size);
+    }
+
+    return 0;
+}
+
+/* Handle messages received from QMP server */
+
+static int qmp_ev_get_next_msg(libxl__egc *egc, libxl__ev_qmp *ev,
+                               libxl__json_object **o_r)
+    /* Find a JSON object and store it in o_r.
+     * return ERROR_NOTFOUND if no object is found.
+     *
+     * !disconnected -> same state (with rx buffer updated)
+     */
+{
+    STATE_AO_GC(ev->ao);
+    size_t len;
+    char *end = NULL;
+    const char eom[] = "\r\n";
+    const size_t eoml = sizeof(eom) - 1;
+    libxl__json_object *o = NULL;
+
+    if (!ev->rx_buf_used)
+        return ERROR_NOTFOUND;
+
+    /* Search for the end of a QMP message: "\r\n" */
+    end = memmem(ev->rx_buf, ev->rx_buf_used, eom, eoml);
+    if (!end)
+        return ERROR_NOTFOUND;
+    len = (end - ev->rx_buf) + eoml;
+
+    LOG_QMP("parsing %luB: '%.*s'", len, (int)len, ev->rx_buf);
+
+    /* Replace \r by \0 so that libxl__json_parse can use strlen */
+    ev->rx_buf[len - eoml] = '\0';
+    o = libxl__json_parse(gc, ev->rx_buf);
+
+    if (!o) {
+        LOGD(ERROR, ev->domid, "Parse error");
+        return ERROR_PROTOCOL_ERROR_QMP;
+    }
+
+    ev->rx_buf_used -= len;
+    memmove(ev->rx_buf, ev->rx_buf + len, ev->rx_buf_used);
+
+    LOG_QMP("JSON object received: %s", JSON(o));
+
+    *o_r = o;
+
+    return 0;
+}
+
+static int qmp_ev_parse_error_messages(libxl__egc *egc,
+                                       libxl__ev_qmp *ev,
+                                       const libxl__json_object *resp);
+
+static int qmp_ev_handle_message(libxl__egc *egc,
+                                 libxl__ev_qmp *ev,
+                                 const libxl__json_object *resp)
+    /*
+     * This function will handle every messages sent by the QMP server.
+     * Return values:
+     *   < 0    libxl error code
+     *   0      success
+     *   1      success, but a user callback has been called,
+     *          `ev` should not be used anymore.
+     *
+     * Possible state changes:
+     * connecting -> capability_negotiation
+     * capability_negotiation -> waiting_reply
+     * waiting_reply -> waiting_reply/connected
+     *
+     * on error: broken
+     */
+{
+    STATE_AO_GC(ev->ao);
+    int id;
+    char *buf;
+    int rc = 0;
+    const libxl__json_object *o;
+    const libxl__json_object *response;
+    libxl__qmp_message_type type = qmp_response_type(resp);
+
+    switch (type) {
+    case LIBXL__QMP_MESSAGE_TYPE_QMP:
+        /* greeting message */
+
+        if (ev->state != qmp_state_connecting) {
+            LOGD(ERROR, ev->domid,
+                 "Unexpected greeting message received");
+            return ERROR_PROTOCOL_ERROR_QMP;
+        }
+
+        /* Prepare next message to send */
+        assert(!ev->tx_buf);
+        ev->id = ev->next_id++;
+        buf = qmp_prepare_cmd(gc, "qmp_capabilities", NULL, ev->id);
+        if (!buf) {
+            LOGD(ERROR, ev->domid,
+                 "Failed to generate qmp_capabilities command");
+            return ERROR_FAIL;
+        }
+        ev->tx_buf = buf;
+        ev->tx_buf_len = strlen(buf);
+        ev->tx_buf_off = 0;
+        qmp_ev_set_state(gc, ev, qmp_state_capability_negotiation);
+
+        return 0;
+
+    case LIBXL__QMP_MESSAGE_TYPE_RETURN:
+    case LIBXL__QMP_MESSAGE_TYPE_ERROR:
+        /*
+         * Reply to a command (success/error) or server error
+         *
+         * In this cases, we are parsing two possibles responses:
+         * - success:
+         * { "return": json-value, "id": int }
+         * - error:
+         * { "error": { "class": string, "desc": string }, "id": int }
+         */
+
+        o = libxl__json_map_get("id", resp, JSON_INTEGER);
+        if (!o) {
+            /*
+             * If "id" isn't present, an error occur on the server before
+             * it has read the "id" provided by libxl.
+             *
+             * We deliberately squash all errors into
+             * ERROR_PROTOCOL_ERROR_QMP as qmp_ev_parse_error_messages may
+             * also return ERROR_QMP_* but those are reserved for errors
+             * return by the caller's command.
+             */
+            qmp_ev_parse_error_messages(egc, ev, resp);
+            return ERROR_PROTOCOL_ERROR_QMP;
+        }
+
+        id = libxl__json_object_get_integer(o);
+
+        if (id != ev->id) {
+            LOGD(ERROR, ev->domid,
+                 "Message from QEMU with unexpected id %d: %s",
+                 id, JSON(resp));
+            return ERROR_PROTOCOL_ERROR_QMP;
+        }
+
+        switch (ev->state) {
+        case qmp_state_capability_negotiation:
+            if (type != LIBXL__QMP_MESSAGE_TYPE_RETURN) {
+                LOGD(ERROR, ev->domid,
+                     "Error during capability negotiation: %s",
+                     JSON(resp));
+                return ERROR_PROTOCOL_ERROR_QMP;
+            }
+            qmp_ev_set_state(gc, ev, qmp_state_waiting_reply);
+            return 0;
+        case qmp_state_waiting_reply:
+            if (type == LIBXL__QMP_MESSAGE_TYPE_RETURN) {
+                response = libxl__json_map_get("return", resp, JSON_ANY);
+                rc = 0;
+            } else {
+                /* error message */
+                response = NULL;
+                rc = qmp_ev_parse_error_messages(egc, ev, resp);
+            }
+            qmp_ev_set_state(gc, ev, qmp_state_connected);
+            ev->callback(egc, ev, response, rc); /* must be last */
+            return 1;
+        default:
+            LOGD(ERROR, ev->domid, "Unexpected message: %s", JSON(resp));
+            return ERROR_PROTOCOL_ERROR_QMP;
+        }
+        return 0;
+
+    case LIBXL__QMP_MESSAGE_TYPE_EVENT:
+        /* Events are ignored */
+        return 0;
+
+    case LIBXL__QMP_MESSAGE_TYPE_INVALID:
+        LOGD(ERROR, ev->domid, "Unexpected message received: %s",
+             JSON(resp));
+        return ERROR_PROTOCOL_ERROR_QMP;
+
+    default:
+        abort();
+    }
+
+    return 0;
+}
+
+static int qmp_ev_parse_error_messages(libxl__egc *egc,
+                                       libxl__ev_qmp *ev,
+                                       const libxl__json_object *resp)
+    /* no state change */
+{
+    STATE_AO_GC(ev->ao);
+    int rc;
+    const char *s;
+    const libxl__json_object *o;
+    const libxl__json_object *err;
+
+    /*
+     * { "error": { "class": string, "desc": string } }
+     */
+
+    err = libxl__json_map_get("error", resp, JSON_MAP);
+
+    o = libxl__json_map_get("class", err, JSON_STRING);
+    if (!o) {
+        LOGD(ERROR, ev->domid,
+             "Protocol error: missing 'class' member in error message");
+        return ERROR_PROTOCOL_ERROR_QMP;
+    }
+    s = libxl__json_object_get_string(o);
+    if (s)
+        rc = qmp_error_class_to_libxl_error_code(gc, s);
+    else
+        rc = ERROR_PROTOCOL_ERROR_QMP;
+
+    o = libxl__json_map_get("desc", err, JSON_STRING);
+    if (!o) {
+        LOGD(ERROR, ev->domid,
+             "Protocol error: missing 'desc' member in error message");
+        return ERROR_PROTOCOL_ERROR_QMP;
+    }
+    s = libxl__json_object_get_string(o);
+    if (s)
+        LOGD(ERROR, ev->domid, "%s", s);
+    else
+        LOGD(ERROR, ev->domid, "Received unexpected error: %s",
+             JSON(resp));
+    return rc;
+}
+
+/*
+ * libxl__ev_qmp_*
+ */
+
+void libxl__ev_qmp_init(libxl__ev_qmp *ev)
+    /* disconnected -> disconnected */
+{
+    /* Start with an message ID that is obviously generated by libxl
+     * "xlq\0" */
+    ev->next_id = 0x786c7100;
+
+    ev->cfd = NULL;
+    libxl__ev_fd_init(&ev->efd);
+    ev->state = qmp_state_disconnected;
+    ev->id = 0;
+
+    ev->rx_buf = NULL;
+    ev->rx_buf_size = ev->rx_buf_used = 0;
+    qmp_ev_tx_buf_clear(ev);
+
+    ev->msg = NULL;
+    ev->msg_id = 0;
+}
+
+int libxl__ev_qmp_send(libxl__gc *unused_gc, libxl__ev_qmp *ev,
+                       const char *cmd, libxl__json_object *args)
+    /* disconnected -> connecting
+     * connected -> waiting_reply (with msg set)
+     * on error: disconnected */
+{
+    STATE_AO_GC(ev->ao);
+    int rc;
+
+    LOGD(DEBUG, ev->domid, " ev %p, cmd '%s'", ev, cmd);
+
+    assert(ev->state == qmp_state_disconnected ||
+           ev->state == qmp_state_connected);
+    assert(cmd);
+
+    /* Connect to QEMU if not already connected */
+    if (ev->state == qmp_state_disconnected) {
+        rc = qmp_ev_connect(gc, ev);
+        if (rc)
+            goto error;
+    }
+
+    /* Prepare user command */
+    ev->msg_id = ev->next_id++;
+    ev->msg = qmp_prepare_cmd(gc, cmd, args, ev->msg_id);
+    if (!ev->msg) {
+        LOGD(ERROR, ev->domid, "Failed to generate caller's command %s",
+             cmd);
+        rc = ERROR_FAIL;
+        goto error;
+    }
+    if (ev->state == qmp_state_connected) {
+        qmp_ev_set_state(gc, ev, qmp_state_waiting_reply);
+    }
+
+    return 0;
+
+error:
+    libxl__ev_qmp_dispose(gc, ev);
+    return rc;
+}
+
+void libxl__ev_qmp_dispose(libxl__gc *gc, libxl__ev_qmp *ev)
+    /* * -> disconnected */
+{
+    LOGD(DEBUG, ev->domid, " ev %p", ev);
+
+    libxl__ev_fd_deregister(gc, &ev->efd);
+    libxl__carefd_close(ev->cfd);
+
+    libxl__ev_qmp_init(ev);
+}
+
 /*
  * Local variables:
  * mode: C
diff --git a/tools/libxl/libxl_types.idl b/tools/libxl/libxl_types.idl
index 141c46e42a..212b00a677 100644
--- a/tools/libxl/libxl_types.idl
+++ b/tools/libxl/libxl_types.idl
@@ -69,6 +69,12 @@ libxl_error = Enumeration("error", [
     (-23, "NOTFOUND"),
     (-24, "DOMAIN_DESTROYED"), # Target domain ceased to exist during op
     (-25, "FEATURE_REMOVED"), # For functionality that has been removed
+    (-26, "PROTOCOL_ERROR_QMP"),
+    (-27, "UNKNOWN_QMP_ERROR"),
+    (-28, "QMP_GENERIC_ERROR"), # unspecified qmp error
+    (-29, "QMP_COMMAND_NOT_FOUND"), # the requested command has not been found
+    (-30, "QMP_DEVICE_NOT_ACTIVE"), # a device has failed to be become active
+    (-31, "QMP_DEVICE_NOT_FOUND"), # the requested device has not been found
     ], value_namespace = "")
 
 libxl_domain_type = Enumeration("domain_type", [
--
generated by git-patchbot for /home/xen/git/xen.git#staging

_______________________________________________
Xen-changelog mailing list
[hidden email]
https://lists.xenproject.org/xen-changelog