* [PATCH v9 00/17] libxl: Enable save/restore/migration of a restricted QEMU + libxl__ev_qmp_*
@ 2019-01-11 12:32 Anthony PERARD
2019-01-11 12:32 ` [PATCH v9 06/17] libxl_qmp: Implementation of libxl__ev_qmp_* Anthony PERARD
0 siblings, 1 reply; 2+ messages in thread
From: Anthony PERARD @ 2019-01-11 12:32 UTC (permalink / raw)
To: xen-devel; +Cc: Anthony PERARD, Wei Liu, Ian Jackson
Patch series available in this git branch:
https://xenbits.xen.org/git-http/people/aperard/xen-unstable.git br.libxl-ev-qmp-v9
Changes in v9:
fix on assert in "libxl_qmp: Implementation of libxl__ev_qmp_*".
all patch acked.
Cheers,
Anthony PERARD (17):
libxl: Enhance libxl__sendmsg_fds to deal with EINTR and EWOULDBLOCK
libxl_qmp: Separate QMP message generation from qmp_send_prepare
libxl_qmp: Change qmp_qemu_check_version to compare version
libxl: Add wrapper around libxl__json_object_to_json JSON
libxl: Design of an async API to issue QMP commands to QEMU
libxl_qmp: Implementation of libxl__ev_qmp_*
libxl_exec: Add libxl__spawn_initiate_failure
libxl: Add init/dispose of for libxl__domain_build_state
libxl_dm: Pre-open QMP socket for QEMU
libxl: Add dmss_init/dispose for libxl__dm_spawn_state
libxl: QEMU startup sync based on QMP
libxl_qmp: Store advertised QEMU version in libxl__ev_qmp
libxl: Change libxl__domain_suspend_device_model() to be async
libxl: Re-implement domain_suspend_device_model using libxl__ev_qmp
libxl: Remove unused arg from libxl__sendmsg_fds
libxl_json: Remove libxl__json_object_append_to from header
libxl: Add comments to libxl__json_*get* functions
tools/libxl/libxl_aoutils.c | 2 +-
tools/libxl/libxl_create.c | 54 +-
tools/libxl/libxl_dm.c | 143 ++++-
tools/libxl/libxl_dom_suspend.c | 37 +-
tools/libxl/libxl_exec.c | 11 +-
tools/libxl/libxl_internal.h | 197 ++++++-
tools/libxl/libxl_json.c | 9 +-
tools/libxl/libxl_qmp.c | 991 ++++++++++++++++++++++++++++++--
tools/libxl/libxl_types.idl | 7 +
tools/libxl/libxl_utils.c | 28 +-
10 files changed, 1356 insertions(+), 123 deletions(-)
--
Anthony PERARD
_______________________________________________
Xen-devel mailing list
Xen-devel@lists.xenproject.org
https://lists.xenproject.org/mailman/listinfo/xen-devel
^ permalink raw reply [flat|nested] 2+ messages in thread
* [PATCH v9 06/17] libxl_qmp: Implementation of libxl__ev_qmp_*
2019-01-11 12:32 [PATCH v9 00/17] libxl: Enable save/restore/migration of a restricted QEMU + libxl__ev_qmp_* Anthony PERARD
@ 2019-01-11 12:32 ` Anthony PERARD
0 siblings, 0 replies; 2+ messages in thread
From: Anthony PERARD @ 2019-01-11 12:32 UTC (permalink / raw)
To: xen-devel; +Cc: Anthony PERARD, Wei Liu, Ian Jackson
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@citrix.com>
Acked-by: Ian Jackson <ian.jackson@eu.citrix.com>
---
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@lists.xenproject.org
https://lists.xenproject.org/mailman/listinfo/xen-devel
^ permalink raw reply related [flat|nested] 2+ messages in thread
end of thread, other threads:[~2019-01-11 12:32 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2019-01-11 12:32 [PATCH v9 00/17] libxl: Enable save/restore/migration of a restricted QEMU + libxl__ev_qmp_* Anthony PERARD
2019-01-11 12:32 ` [PATCH v9 06/17] libxl_qmp: Implementation of libxl__ev_qmp_* Anthony PERARD
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.