[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Xen-devel] [PATCH v8 06/17] 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 <anthony.perard@xxxxxxxxxx>
---

Notes:
    v8:
    - using STATE_AO_GC everywhere
    - call qmp_ev_ensure_reading_writing from qmp_ev_set_state instead of
      other places. But add a call to qmp_ev_ensure_reading_writing when the
      tx_buf is updated (data been transmitted to QEMU)
    - assert return value of write().
    
    - fix implementation doc of rx_buf (can be free when used)
    - fix doc of qmp_ev_ensure_reading_writing
    
    - reorder qmp_ev_parse_error_messages and qmp_ev_handle_message
    
    v7:
    - Make use of the new `ao` field filled by the caller
      (no more free(tx_buf))
    - Have reworked the state table to be more accurate, easier to read and
      with better desc of the waiting_reply state with sub-states as well as
      better description of the state of the ev_fd `efd`.
    - There is state transition changes, now we have cap.neg ->
      waiting_reply (before it was cap.neg -> connected -> waiting_reply),
      that makes the internal connected state the same as the external
      Connected state.
    - there are now 3 different id variable instead of a single one, the
      currently sent one `id`, the next one `next_id`, and the one
      associated with the caller's command `msg_id`, `id` will always be the
      for the next expected message from QEMU.
    - qmp_ev_fd_callback now check for POLLERR, and read async error with
      getsockopt(SO_ERROR).
    - qmp_ev_callback_readable takes care once again to also parse messages.
      But it now first attempt to read messages that would be in the rx_buf
      before reading from the socket.
    - Some functions state changes have been updated,
      qmp_ev_ensure_reading_writing, qmp_ev_set_state,
      qmp_ev_callback_writable, qmp_ev_callback_readable
    - Have cleaned up qmp_ev_ensure_reading_writing
    - Have cleaned up qmp_error_class_to_libxl_error_code, and log unknown
      error classes.
    - In qmp_ev_callback_readable, the size increase of the buffer have been
      reworked to update both rx_buf_size and rx_buf at the same time, and
      remove the use of max()
    - Return value of qmp_prepare_cmd calles is checked and logged on error.
    - comments in RHS of struct
    - New qmp_ev_tx_buf_clear func
    - Add a link to the QMP spec.
    - update callers of qmp_prepare_cmd, which doesn't provide the string
      lenght anymore also remove ev->msg_len
    - rename internal fields qmp_cfd, qmp_efd, qmp_state to cfd, efd and
      state.
    - change qmp_ev_connect to only allow disconnect on entry
    - squash qmp_ev_prepare_cmd into libxl__ev_qmp_send
    - reduce max rx buffer size to 1M
      query-vcpus with 71 cpus active yield 14484 bytes.
    - use JSON to print libxl__json_object
    
    v6.2:
        Add definition of the internal broken state
        updated comments about states
    v6.1:
        Adding some comment about possible internal state changes
    
    v6:
        This is a squash of 7 commits on the previous version:
        - libxl_qmp: Connect to QMP socket
        - libxl_qmp: Implement fd callback and read data
        - libxl_qmp: Parse JSON input from QMP
        - libxl_qmp: Prepare the command to be sent
        - libxl_qmp: Handle write to QMP socket
        - libxl_qmp: Handle messages from QEMU
        - libxl_qmp: Respond to QMP greeting
    
        General rework of the implementation.
        Added more comment, with a description of allowed internal states.
        Check for EINPROGRESS after connect().
        Read until EWOULDBLOCK.
        Handle EWOULDBLOCK on write and sendmsg.
        Using memmem instead of strstr.
        Using memmove instead of having an offset in rx_buf.
        Rework buffer allocation
        Don't feed \r into json parser anymore
        Add a check for a maximum RX buffer size
        Added more error messages
        New error code ERROR_PROTOCOL_ERROR_QMP
        Rewrite conversion of QMP ErrorClass to libxl_error code
        Added helpers: qmp_ev_ensure_reading_writing, qmp_ev_set_state
        Split some functions, squash others
        Added ev->msg* to store generated user command as tx_buf is used during
            connection (for qmp_capabilities)
        Remove qmp_state_greeting
        Added qmp_state_waiting_reply
    
    v5:
        nits
        use a define instead of a static int for 
QMP_CAPABILITY_NEGOCIATION_MSGID
        use a switch in qmp_ev_callback_writable to check qmp_state
        Add a description of the different value of libxl__qmp_state enum.
        some cleanup
        remove read loop that only handled EINTR, simply return
        initialize len and s at declaration time
        remove old comment
        rename buf_fd to send_fd
        Adding default:abort() in qmp_ev_handle_message.
    
    v4:
        remove use of a linked list of receive buffer, and use realloc instead.
        simplification of the patch due to use of a single allocated space for 
the
        receive buffer.

 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 fadafa5f11..be18d4f341 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..f2ab08620a 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 (%ld > %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", [
-- 
Anthony PERARD


_______________________________________________
Xen-devel mailing list
Xen-devel@xxxxxxxxxxxxxxxxxxxx
https://lists.xenproject.org/mailman/listinfo/xen-devel

 


Rackspace

Lists.xenproject.org is hosted with RackSpace, monitoring our
servers 24x7x365 and backed by RackSpace's Fanatical Support®.