[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] [Xen-devel] [PATCH v9 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> Acked-by: Ian Jackson <ian.jackson@xxxxxxxxxxxxx> --- Notes: v9: - assert(r>0) of the write return value - acked 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..b5ce349dad 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
|
Lists.xenproject.org is hosted with RackSpace, monitoring our |