# 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"
/*
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