/* * Copyright (C) 2011 Citrix Ltd. * Author Anthony PERARD * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation; version 2.1 only. with the special * exception on linking described in file LICENSE. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. */ /* * This file implement a client for QMP (QEMU Monitor Protocol). For the * Specification, see in the QEMU repository. * * WARNING - Do not trust QEMU when writing codes for new commands or when * improving the client code. */ /* * Logic used to send command to QEMU * * qmp_open(): * Will open a socket and connect to QEMU. * * qmp_next(): * Will read data sent by QEMU and then call qmp_handle_response() once a * complete QMP message is received. * The function return on timeout/error or once every data received as been * processed. * * qmp_handle_response() * This process json messages received from QEMU and update different list and * may call callback function. * `libxl__qmp_handler.wait_for_id` is reset once a message with this ID is * processed. * `libxl__qmp_handler.callback_list`: list with ID of command sent and * optional assotiated callback function. The return value of a callback is * set in context. * * qmp_send(): * Simply prepare a QMP command and send it to QEMU. * It also add a `struct callback_id_pair` on the * `libxl__qmp_handler.callback_list` via qmp_send_prepare(). * * qmp_synchronous_send(): * This function calls qmp_send(), then wait for QEMU to reply to the command. * The wait is done by calling qmp_next() over and over again until either * there is a response for the command or there is an error. * * An ID can be set for each QMP command, this is set into * `libxl__qmp_handler.wait_for_id`. qmp_next will check every response's ID * again this field and change the value of the field once the ID is found. */ #include "libxl_osdeps.h" /* must come before any other headers */ #include #include #include "xen_list.h" #include "libxl_internal.h" /* #define DEBUG_RECEIVED */ #ifdef DEBUG_RECEIVED # define DEBUG_REPORT_RECEIVED(dom, buf, len) \ LOGD(DEBUG, dom, "received: '%.*s'", len, buf) #else # 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) /* * qmp_callback_t is call whenever a message from QMP contain the "id" * associated with the callback. * "tree" contain the JSON tree that is in "return" of a QMP message. If QMP * sent an error message, "tree" will be NULL. */ typedef int (*qmp_callback_t)(libxl__qmp_handler *qmp, const libxl__json_object *tree, void *opaque); typedef struct qmp_request_context { int rc; } qmp_request_context; typedef struct callback_id_pair { int id; qmp_callback_t callback; void *opaque; qmp_request_context *context; XEN_STAILQ_ENTRY(struct callback_id_pair) next; } callback_id_pair; struct libxl__qmp_handler { int qmp_fd; bool connected; time_t timeout; /* wait_for_id will be used by the synchronous send function */ int wait_for_id; char buffer[QMP_RECEIVE_BUFFER_SIZE + 1]; libxl_ctx *ctx; uint32_t domid; int last_id_used; XEN_STAILQ_HEAD(callback_list, callback_id_pair) callback_list; struct { int major; int minor; int micro; } version; }; static int qmp_send(libxl__qmp_handler *qmp, const char *cmd, libxl__json_object *args, qmp_callback_t callback, void *opaque, qmp_request_context *context); static const int QMP_SOCKET_CONNECT_TIMEOUT = 5; /* * QMP callbacks functions */ static int qmp_capabilities_callback(libxl__qmp_handler *qmp, const libxl__json_object *o, void *unused) { qmp->connected = true; return 0; } /* * QMP commands */ static int enable_qmp_capabilities(libxl__qmp_handler *qmp) { return qmp_send(qmp, "qmp_capabilities", NULL, qmp_capabilities_callback, NULL, NULL); } /* * Helpers */ static libxl__qmp_message_type qmp_response_type(const libxl__json_object *o) { libxl__qmp_message_type type; libxl__json_map_node *node = NULL; int i = 0; for (i = 0; (node = libxl__json_map_node_get(o, i)); i++) { if (libxl__qmp_message_type_from_string(node->map_key, &type) == 0) return type; } return LIBXL__QMP_MESSAGE_TYPE_INVALID; } static callback_id_pair *qmp_get_callback_from_id(libxl__qmp_handler *qmp, const libxl__json_object *o) { const libxl__json_object *id_object = libxl__json_map_get("id", o, JSON_INTEGER); int id = -1; callback_id_pair *pp = NULL; if (id_object) { id = libxl__json_object_get_integer(id_object); XEN_STAILQ_FOREACH(pp, &qmp->callback_list, next) { if (pp->id == id) { return pp; } } } return NULL; } static void qmp_handle_error_response(libxl__gc *gc, libxl__qmp_handler *qmp, const libxl__json_object *resp) { callback_id_pair *pp = qmp_get_callback_from_id(qmp, resp); resp = libxl__json_map_get("error", resp, JSON_MAP); resp = libxl__json_map_get("desc", resp, JSON_STRING); if (pp) { if (pp->callback) { int rc = pp->callback(qmp, NULL, pp->opaque); if (pp->context) { pp->context->rc = rc; } } if (pp->id == qmp->wait_for_id) { /* tell that the id have been processed */ qmp->wait_for_id = 0; } XEN_STAILQ_REMOVE(&qmp->callback_list, pp, callback_id_pair, next); free(pp); } LOGD(ERROR, qmp->domid, "received an error message from QMP server: %s", libxl__json_object_get_string(resp)); } static int qmp_handle_response(libxl__gc *gc, libxl__qmp_handler *qmp, const libxl__json_object *resp) { libxl__qmp_message_type type = LIBXL__QMP_MESSAGE_TYPE_INVALID; type = qmp_response_type(resp); LOGD(DEBUG, qmp->domid, "message type: %s", libxl__qmp_message_type_to_string(type)); switch (type) { case LIBXL__QMP_MESSAGE_TYPE_QMP: { const libxl__json_object *o; o = libxl__json_map_get("QMP", resp, JSON_MAP); o = libxl__json_map_get("version", o, JSON_MAP); o = libxl__json_map_get("qemu", o, JSON_MAP); qmp->version.major = libxl__json_object_get_integer( libxl__json_map_get("major", o, JSON_INTEGER)); qmp->version.minor = libxl__json_object_get_integer( libxl__json_map_get("minor", o, JSON_INTEGER)); qmp->version.micro = libxl__json_object_get_integer( libxl__json_map_get("micro", o, JSON_INTEGER)); LOGD(DEBUG, qmp->domid, "QEMU version: %d.%d.%d", qmp->version.major, qmp->version.minor, qmp->version.micro); /* On the greeting message from the server, enable QMP capabilities */ return enable_qmp_capabilities(qmp); } case LIBXL__QMP_MESSAGE_TYPE_RETURN: { callback_id_pair *pp = qmp_get_callback_from_id(qmp, resp); if (pp) { if (pp->callback) { int rc = pp->callback(qmp, libxl__json_map_get("return", resp, JSON_ANY), pp->opaque); if (pp->context) { pp->context->rc = rc; } } if (pp->id == qmp->wait_for_id) { /* tell that the id have been processed */ qmp->wait_for_id = 0; } XEN_STAILQ_REMOVE(&qmp->callback_list, pp, callback_id_pair, next); free(pp); } return 0; } case LIBXL__QMP_MESSAGE_TYPE_ERROR: qmp_handle_error_response(gc, qmp, resp); return -1; case LIBXL__QMP_MESSAGE_TYPE_EVENT: return 0; case LIBXL__QMP_MESSAGE_TYPE_INVALID: return -1; } return 0; } /* * return values: * < 0 if qemu's version < asked version * = 0 if qemu's version == asked version * > 0 if qemu's version > asked version */ int libxl__qmp_ev_qemu_compare_version(libxl__ev_qmp *ev, int major, int minor, int micro) { assert(ev->state == qmp_state_connected); #define CHECK_VERSION(level) do { \ if (ev->qemu_version.level > (level)) return +1; \ if (ev->qemu_version.level < (level)) return -1; \ } while (0) CHECK_VERSION(major); CHECK_VERSION(minor); CHECK_VERSION(micro); #undef CHECK_VERSION return 0; } /* * Handler functions */ static libxl__qmp_handler *qmp_init_handler(libxl__gc *gc, uint32_t domid) { libxl__qmp_handler *qmp = NULL; qmp = calloc(1, sizeof (libxl__qmp_handler)); if (qmp == NULL) { LOGED(ERROR, domid, "Failed to allocate qmp_handler"); return NULL; } qmp->ctx = CTX; qmp->domid = domid; qmp->timeout = 5; XEN_STAILQ_INIT(&qmp->callback_list); return qmp; } static int qmp_open(libxl__qmp_handler *qmp, const char *qmp_socket_path, int timeout) { GC_INIT(qmp->ctx); int ret = -1; int i = 0; struct sockaddr_un addr; qmp->qmp_fd = socket(AF_UNIX, SOCK_STREAM, 0); if (qmp->qmp_fd < 0) { goto out; } ret = libxl_fd_set_nonblock(qmp->ctx, qmp->qmp_fd, 1); if (ret) { ret = -1; goto out; } ret = libxl_fd_set_cloexec(qmp->ctx, qmp->qmp_fd, 1); if (ret) { ret = -1; goto out; } ret = libxl__prepare_sockaddr_un(gc, &addr, qmp_socket_path, "QMP socket"); if (ret) goto out; do { ret = connect(qmp->qmp_fd, (struct sockaddr *) &addr, sizeof(addr)); if (ret == 0) break; if (errno == ENOENT || errno == ECONNREFUSED) { /* ENOENT : Socket may not have shown up yet * ECONNREFUSED : Leftover socket hasn't been removed yet */ continue; } ret = -1; goto out; } while ((++i / 5 <= timeout) && (usleep(200 * 1000) <= 0)); out: if (ret == -1 && qmp->qmp_fd > -1) close(qmp->qmp_fd); GC_FREE; return ret; } static void qmp_close(libxl__qmp_handler *qmp) { callback_id_pair *pp = NULL; callback_id_pair *tmp = NULL; close(qmp->qmp_fd); XEN_STAILQ_FOREACH(pp, &qmp->callback_list, next) { free(tmp); tmp = pp; } free(tmp); } static int qmp_next(libxl__gc *gc, libxl__qmp_handler *qmp) { ssize_t rd; char *s = NULL; char *s_end = NULL; char *incomplete = NULL; size_t incomplete_size = 0; int rc = 0; do { fd_set rfds; int ret = 0; struct timeval timeout = { .tv_sec = qmp->timeout, .tv_usec = 0, }; FD_ZERO(&rfds); FD_SET(qmp->qmp_fd, &rfds); ret = select(qmp->qmp_fd + 1, &rfds, NULL, NULL, &timeout); if (ret == 0) { LOGD(ERROR, qmp->domid, "timeout"); return -1; } else if (ret < 0) { if (errno == EINTR) continue; LOGED(ERROR, qmp->domid, "Select error"); return -1; } rd = read(qmp->qmp_fd, qmp->buffer, QMP_RECEIVE_BUFFER_SIZE); if (rd == 0) { LOGD(ERROR, qmp->domid, "Unexpected end of socket"); return -1; } else if (rd < 0) { LOGED(ERROR, qmp->domid, "Socket read error"); return rd; } qmp->buffer[rd] = '\0'; DEBUG_REPORT_RECEIVED(qmp->domid, qmp->buffer, (int)rd); if (incomplete) { size_t current_pos = s - incomplete; incomplete = libxl__realloc(gc, incomplete, incomplete_size + rd + 1); strncat(incomplete + incomplete_size, qmp->buffer, rd); s = incomplete + current_pos; incomplete_size += rd; s_end = incomplete + incomplete_size; } else { incomplete = libxl__strndup(gc, qmp->buffer, rd); incomplete_size = rd; s = incomplete; s_end = s + rd; rd = 0; } do { char *end = NULL; end = strstr(s, "\r\n"); if (end) { libxl__json_object *o = NULL; *end = '\0'; o = libxl__json_parse(gc, s); if (o) { rc = qmp_handle_response(gc, qmp, o); } else { LOGD(ERROR, qmp->domid, "Parse error of : %s", s); return -1; } s = end + 2; } else { break; } } while (s < s_end); } while (s < s_end); return rc; } static char *qmp_prepare_cmd(libxl__gc *gc, const char *cmd, const libxl__json_object *args, int id) { yajl_gen hand = NULL; /* memory for 'buf' is owned by 'hand' */ const unsigned char *buf; libxl_yajl_length len; yajl_gen_status s; char *ret = NULL; hand = libxl_yajl_gen_alloc(NULL); if (!hand) { return NULL; } #if HAVE_YAJL_V2 /* Disable beautify for data sent to QEMU */ yajl_gen_config(hand, yajl_gen_beautify, 0); #endif yajl_gen_map_open(hand); libxl__yajl_gen_asciiz(hand, "execute"); libxl__yajl_gen_asciiz(hand, cmd); libxl__yajl_gen_asciiz(hand, "id"); yajl_gen_integer(hand, id); if (args) { libxl__yajl_gen_asciiz(hand, "arguments"); libxl__json_object_to_yajl_gen(gc, hand, args); } yajl_gen_map_close(hand); s = yajl_gen_get_buf(hand, &buf, &len); if (s != yajl_gen_status_ok) goto out; ret = libxl__sprintf(gc, "%*.*s\r\n", (int)len, (int)len, buf); out: yajl_gen_free(hand); return ret; } static char *qmp_send_prepare(libxl__gc *gc, libxl__qmp_handler *qmp, const char *cmd, libxl__json_object *args, qmp_callback_t callback, void *opaque, qmp_request_context *context) { char *buf; callback_id_pair *elm; buf = qmp_prepare_cmd(gc, cmd, args, ++qmp->last_id_used); if (!buf) { LOGD(ERROR, qmp->domid, "Failed to generate a qmp command"); goto out; } elm = malloc(sizeof (callback_id_pair)); if (elm == NULL) { LOGED(ERROR, qmp->domid, "Failed to allocate a QMP callback"); goto out; } elm->id = qmp->last_id_used; elm->callback = callback; elm->opaque = opaque; elm->context = context; XEN_STAILQ_INSERT_TAIL(&qmp->callback_list, elm, next); LOGD(DEBUG, qmp->domid, "next qmp command: '%s'", buf); out: return buf; } static int qmp_send(libxl__qmp_handler *qmp, const char *cmd, libxl__json_object *args, qmp_callback_t callback, void *opaque, qmp_request_context *context) { char *buf = NULL; int rc = -1; GC_INIT(qmp->ctx); buf = qmp_send_prepare(gc, qmp, cmd, args, callback, opaque, context); if (buf == NULL) { goto out; } if (libxl_write_exactly(qmp->ctx, qmp->qmp_fd, buf, strlen(buf), "QMP command", "QMP socket")) goto out; rc = qmp->last_id_used; out: GC_FREE; return rc; } static int qmp_synchronous_send(libxl__qmp_handler *qmp, const char *cmd, libxl__json_object *args, qmp_callback_t callback, void *opaque, time_t ask_timeout) { int id = 0; int ret = 0; GC_INIT(qmp->ctx); qmp_request_context context = { .rc = 0 }; id = qmp_send(qmp, cmd, args, callback, opaque, &context); if (id <= 0) { return ERROR_FAIL; } qmp->wait_for_id = id; while (qmp->wait_for_id == id) { if ((ret = qmp_next(gc, qmp)) < 0) { break; } } if (qmp->wait_for_id != id && ret == 0) { ret = context.rc; } GC_FREE; return ret; } static void qmp_free_handler(libxl__qmp_handler *qmp) { free(qmp); } /* * QMP Parameters Helpers */ static void qmp_parameters_common_add(libxl__gc *gc, libxl__json_object **param, const char *name, libxl__json_object *obj) { libxl__json_map_node *arg = NULL; if (!*param) { *param = libxl__json_object_alloc(gc, JSON_MAP); } GCNEW(arg); arg->map_key = libxl__strdup(gc, name); arg->obj = obj; flexarray_append((*param)->u.map, arg); } void libxl__qmp_param_add_string(libxl__gc *gc, libxl__json_object **param, const char *name, const char *argument) { libxl__json_object *obj; obj = libxl__json_object_alloc(gc, JSON_STRING); obj->u.string = libxl__strdup(gc, argument); qmp_parameters_common_add(gc, param, name, obj); } void libxl__qmp_param_add_bool(libxl__gc *gc, libxl__json_object **param, const char *name, bool b) { libxl__json_object *obj; obj = libxl__json_object_alloc(gc, JSON_BOOL); obj->u.b = b; qmp_parameters_common_add(gc, param, name, obj); } void libxl__qmp_param_add_integer(libxl__gc *gc, libxl__json_object **param, const char *name, const int i) { libxl__json_object *obj; obj = libxl__json_object_alloc(gc, JSON_INTEGER); obj->u.i = i; qmp_parameters_common_add(gc, param, name, obj); } /* * API */ libxl__qmp_handler *libxl__qmp_initialize(libxl__gc *gc, uint32_t domid) { int ret = 0; libxl__qmp_handler *qmp = NULL; char *qmp_socket; qmp = qmp_init_handler(gc, domid); if (!qmp) return NULL; qmp_socket = GCSPRINTF("%s/qmp-libxl-%d", libxl__run_dir_path(), domid); if ((ret = qmp_open(qmp, qmp_socket, QMP_SOCKET_CONNECT_TIMEOUT)) < 0) { LOGED(ERROR, domid, "Connection error"); qmp_free_handler(qmp); return NULL; } LOGD(DEBUG, domid, "connected to %s", qmp_socket); /* Wait for the response to qmp_capabilities */ while (!qmp->connected) { if ((ret = qmp_next(gc, qmp)) < 0) { break; } } if (!qmp->connected) { LOGD(ERROR, domid, "Failed to connect to QMP"); libxl__qmp_close(qmp); return NULL; } return qmp; } void libxl__qmp_close(libxl__qmp_handler *qmp) { if (!qmp) return; qmp_close(qmp); qmp_free_handler(qmp); } void libxl__qmp_cleanup(libxl__gc *gc, uint32_t domid) { char *qmp_socket; qmp_socket = GCSPRINTF("%s/qmp-libxl-%d", libxl__run_dir_path(), domid); if (unlink(qmp_socket) == -1) { if (errno != ENOENT) { LOGED(ERROR, domid, "Failed to remove QMP socket file %s", qmp_socket); } } qmp_socket = GCSPRINTF("%s/qmp-libxenstat-%d", libxl__run_dir_path(), domid); if (unlink(qmp_socket) == -1) { if (errno != ENOENT) { LOGED(ERROR, domid, "Failed to remove QMP socket file %s", qmp_socket); } } } static int qmp_run_command(libxl__gc *gc, int domid, const char *cmd, libxl__json_object *args, qmp_callback_t callback, void *opaque) { libxl__qmp_handler *qmp = NULL; int rc = 0; qmp = libxl__qmp_initialize(gc, domid); if (!qmp) return ERROR_FAIL; rc = qmp_synchronous_send(qmp, cmd, args, callback, opaque, qmp->timeout); libxl__qmp_close(qmp); return rc; } int libxl__qmp_restore(libxl__gc *gc, int domid, const char *state_file) { libxl__json_object *args = NULL; libxl__qmp_param_add_string(gc, &args, "filename", state_file); return qmp_run_command(gc, domid, "xen-load-devices-state", args, NULL, NULL); } int libxl__qmp_resume(libxl__gc *gc, int domid) { return qmp_run_command(gc, domid, "cont", NULL, NULL, NULL); } int libxl__qmp_nbd_server_start(libxl__gc *gc, int domid, const char *host, const char *port) { libxl__json_object *args = NULL; libxl__json_object *addr = NULL; libxl__json_object *data = NULL; /* 'addr': { * 'type': 'inet', * 'data': { * 'host': '$nbd_host', * 'port': '$nbd_port' * } * } */ libxl__qmp_param_add_string(gc, &data, "host", host); libxl__qmp_param_add_string(gc, &data, "port", port); libxl__qmp_param_add_string(gc, &addr, "type", "inet"); qmp_parameters_common_add(gc, &addr, "data", data); qmp_parameters_common_add(gc, &args, "addr", addr); return qmp_run_command(gc, domid, "nbd-server-start", args, NULL, NULL); } int libxl__qmp_nbd_server_add(libxl__gc *gc, int domid, const char *disk) { libxl__json_object *args = NULL; libxl__qmp_param_add_string(gc, &args, "device", disk); libxl__qmp_param_add_bool(gc, &args, "writable", true); return qmp_run_command(gc, domid, "nbd-server-add", args, NULL, NULL); } int libxl__qmp_start_replication(libxl__gc *gc, int domid, bool primary) { libxl__json_object *args = NULL; libxl__qmp_param_add_bool(gc, &args, "enable", true); libxl__qmp_param_add_bool(gc, &args, "primary", primary); return qmp_run_command(gc, domid, "xen-set-replication", args, NULL, NULL); } int libxl__qmp_query_xen_replication_status(libxl__gc *gc, int domid) { return qmp_run_command(gc, domid, "query-xen-replication-status", NULL, NULL, NULL); } int libxl__qmp_colo_do_checkpoint(libxl__gc *gc, int domid) { return qmp_run_command(gc, domid, "xen-colo-do-checkpoint", NULL, NULL, NULL); } int libxl__qmp_stop_replication(libxl__gc *gc, int domid, bool primary) { libxl__json_object *args = NULL; libxl__qmp_param_add_bool(gc, &args, "enable", false); libxl__qmp_param_add_bool(gc, &args, "primary", primary); return qmp_run_command(gc, domid, "xen-set-replication", args, NULL, NULL); } int libxl__qmp_nbd_server_stop(libxl__gc *gc, int domid) { return qmp_run_command(gc, domid, "nbd-server-stop", NULL, NULL, NULL); } int libxl__qmp_x_blockdev_change(libxl__gc *gc, int domid, const char *parent, const char *child, const char *node) { libxl__json_object *args = NULL; libxl__qmp_param_add_string(gc, &args, "parent", parent); if (child) libxl__qmp_param_add_string(gc, &args, "child", child); if (node) libxl__qmp_param_add_string(gc, &args, "node", node); return qmp_run_command(gc, domid, "x-blockdev-change", args, NULL, NULL); } static int hmp_callback(libxl__qmp_handler *qmp, const libxl__json_object *response, void *opaque) { char **output = opaque; GC_INIT(qmp->ctx); int rc; rc = 0; if (!output) goto out; *output = NULL; if (libxl__json_object_is_string(response)) { *output = libxl__strdup(NOGC, libxl__json_object_get_string(response)); goto out; } LOG(ERROR, "Response has unexpected format"); rc = ERROR_FAIL; out: GC_FREE; return rc; } int libxl__qmp_hmp(libxl__gc *gc, int domid, const char *command_line, char **output) { libxl__json_object *args = NULL; libxl__qmp_param_add_string(gc, &args, "command-line", command_line); return qmp_run_command(gc, domid, "human-monitor-command", args, hmp_callback, output); } typedef struct { libxl__ev_qmp qmp; char **output; /* user pointer */ } qemu_monitor_command_state; static void qemu_monitor_command_done(libxl__egc *, libxl__ev_qmp *, const libxl__json_object *response, int rc); int libxl_qemu_monitor_command(libxl_ctx *ctx, uint32_t domid, const char *command_line, char **output, const libxl_asyncop_how *ao_how) { AO_CREATE(ctx, domid, ao_how); qemu_monitor_command_state *qmcs; libxl__json_object *args = NULL; int rc; if (!output) { rc = ERROR_INVAL; goto out; } GCNEW(qmcs); libxl__ev_qmp_init(&qmcs->qmp); qmcs->qmp.ao = ao; qmcs->qmp.domid = domid; qmcs->qmp.payload_fd = -1; qmcs->qmp.callback = qemu_monitor_command_done; qmcs->output = output; libxl__qmp_param_add_string(gc, &args, "command-line", command_line); rc = libxl__ev_qmp_send(egc, &qmcs->qmp, "human-monitor-command", args); out: if (rc) return AO_CREATE_FAIL(rc); return AO_INPROGRESS; } static void qemu_monitor_command_done(libxl__egc *egc, libxl__ev_qmp *qmp, const libxl__json_object *response, int rc) { STATE_AO_GC(qmp->ao); qemu_monitor_command_state *qmcs = CONTAINER_OF(qmp, *qmcs, qmp); if (rc) goto out; if (!libxl__json_object_is_string(response)) { rc = ERROR_QEMU_API; LOGD(ERROR, qmp->domid, "Response has unexpected format"); goto out; } *(qmcs->output) = libxl__strdup(NOGC, libxl__json_object_get_string(response)); rc = 0; out: libxl__ev_qmp_dispose(gc, qmp); libxl__ao_complete(egc, ao, rc); } /* * Functions using libxl__ev_qmp */ static void dm_stopped(libxl__egc *egc, libxl__ev_qmp *ev, const libxl__json_object *response, int rc); static void dm_state_fd_ready(libxl__egc *egc, libxl__ev_qmp *ev, const libxl__json_object *response, int rc); static void dm_state_save_to_fdset(libxl__egc *egc, libxl__ev_qmp *ev, int fdset); static void dm_state_saved(libxl__egc *egc, libxl__ev_qmp *ev, const libxl__json_object *response, int rc); /* calls dsps->callback_device_model_done when done */ void libxl__qmp_suspend_save(libxl__egc *egc, libxl__domain_suspend_state *dsps) { EGC_GC; int rc; libxl__ev_qmp *ev = &dsps->qmp; ev->ao = dsps->ao; ev->domid = dsps->domid; ev->callback = dm_stopped; ev->payload_fd = -1; rc = libxl__ev_qmp_send(egc, ev, "stop", NULL); if (rc) goto error; return; error: dsps->callback_device_model_done(egc, dsps, rc); } static void dm_stopped(libxl__egc *egc, libxl__ev_qmp *ev, const libxl__json_object *response, int rc) { EGC_GC; libxl__domain_suspend_state *dsps = CONTAINER_OF(ev, *dsps, qmp); const char *const filename = dsps->dm_savefile; uint32_t dm_domid = libxl_get_stubdom_id(CTX, dsps->domid); if (rc) goto error; if (dm_domid) { /* see Linux stubdom interface in docs/stubdom.txt */ dm_state_save_to_fdset(egc, ev, 1); return; } ev->payload_fd = open(filename, O_WRONLY | O_CREAT, 0600); if (ev->payload_fd < 0) { LOGED(ERROR, ev->domid, "Failed to open file %s for QEMU", filename); rc = ERROR_FAIL; goto error; } ev->callback = dm_state_fd_ready; rc = libxl__ev_qmp_send(egc, ev, "add-fd", NULL); if (rc) goto error; return; error: if (ev->payload_fd >= 0) { close(ev->payload_fd); libxl__remove_file(gc, filename); ev->payload_fd = -1; } dsps->callback_device_model_done(egc, dsps, rc); } static void dm_state_fd_ready(libxl__egc *egc, libxl__ev_qmp *ev, const libxl__json_object *response, int rc) { EGC_GC; int fdset; const libxl__json_object *o; libxl__domain_suspend_state *dsps = CONTAINER_OF(ev, *dsps, qmp); close(ev->payload_fd); ev->payload_fd = -1; if (rc) goto error; o = libxl__json_map_get("fdset-id", response, JSON_INTEGER); if (!o) { rc = ERROR_QEMU_API; goto error; } fdset = libxl__json_object_get_integer(o); dm_state_save_to_fdset(egc, ev, fdset); return; error: assert(rc); libxl__remove_file(gc, dsps->dm_savefile); dsps->callback_device_model_done(egc, dsps, rc); } static void dm_state_save_to_fdset(libxl__egc *egc, libxl__ev_qmp *ev, int fdset) { EGC_GC; int rc; libxl__json_object *args = NULL; libxl__domain_suspend_state *dsps = CONTAINER_OF(ev, *dsps, qmp); ev->callback = dm_state_saved; /* The `live` parameter was added to QEMU 2.11. It signals QEMU that * the save operation is for a live migration rather than for taking a * snapshot. */ if (libxl__qmp_ev_qemu_compare_version(ev, 2, 11, 0) >= 0) libxl__qmp_param_add_bool(gc, &args, "live", dsps->live); QMP_PARAMETERS_SPRINTF(&args, "filename", "/dev/fdset/%d", fdset); rc = libxl__ev_qmp_send(egc, ev, "xen-save-devices-state", args); if (rc) goto error; return; error: assert(rc); if (!libxl_get_stubdom_id(CTX, dsps->domid)) libxl__remove_file(gc, dsps->dm_savefile); dsps->callback_device_model_done(egc, dsps, rc); } static void dm_state_saved(libxl__egc *egc, libxl__ev_qmp *ev, const libxl__json_object *response, int rc) { EGC_GC; libxl__domain_suspend_state *dsps = CONTAINER_OF(ev, *dsps, qmp); if (rc) libxl__remove_file(gc, dsps->dm_savefile); dsps->callback_device_model_done(egc, dsps, rc); } /* ------------ Implementation of libxl__ev_qmp ---------------- */ /* * Possible internal state compared to qmp_state: * * qmp_state External cfd efd id rx_buf* tx_buf* msg* lock * disconnected Idle NULL Idle reset free free free Idle * waiting_lock Active open Idle reset used free set Active * connecting Active open IN reset used free set Acquired * cap.neg Active open IN|OUT sent used cap_neg set Acquired * cap.neg Active open IN sent used free set Acquired * connected Connected open IN any used free free Acquired * waiting_reply Active open IN|OUT sent used free set Acquired * waiting_reply Active open IN|OUT sent used user's free Acquired * waiting_reply Active open IN sent used free free Acquired * broken[1] none[2] any Active any 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 -> waiting_lock * waiting_lock -> 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->state == qmp_state_waiting_lock) /* We can't modify the efd yet, as it isn't registered. */ return; 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_waiting_lock: assert(ev->state == qmp_state_disconnected); break; case qmp_state_connecting: assert(ev->state == qmp_state_waiting_lock); 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((unsigned char)*s) != tolower((unsigned char)*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 void qmp_ev_lock_aquired(libxl__egc *, libxl__ev_slowlock *, int rc); static void lock_error_callback(libxl__egc *, libxl__ev_immediate *); static int qmp_ev_connect(libxl__egc *egc, libxl__ev_qmp *ev) /* disconnected -> waiting_lock/connecting but with `msg` free * on error: broken */ { EGC_GC; int fd; int rc; /* Convenience aliases */ libxl__ev_slowlock *lock = &ev->lock; assert(ev->state == qmp_state_disconnected); 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; qmp_ev_set_state(gc, ev, qmp_state_waiting_lock); lock->ao = ev->ao; lock->domid = ev->domid; lock->callback = qmp_ev_lock_aquired; libxl__ev_slowlock_lock(egc, &ev->lock); return 0; out: return rc; } static void qmp_ev_lock_aquired(libxl__egc *egc, libxl__ev_slowlock *lock, int rc) /* waiting_lock (with `lock' Acquired) -> connecting * on error: broken */ { libxl__ev_qmp *ev = CONTAINER_OF(lock, *ev, lock); EGC_GC; const char *qmp_socket_path; struct sockaddr_un un; int r; if (rc) goto out; qmp_socket_path = libxl__qemu_qmp_path(gc, ev->domid); LOGD(DEBUG, ev->domid, "Connecting to %s", qmp_socket_path); 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; out: /* An error occurred and we need to let the caller know. At this * point, we can only do so via the callback. Unfortunately, the * callback of libxl__ev_slowlock_lock() might be called synchronously, * but libxl__ev_qmp_send() promise that it will not call the callback * synchronously. So we have to arrange to call the callback * asynchronously. */ ev->rc = rc; ev->ei.callback = lock_error_callback; libxl__ev_immediate_register(egc, &ev->ei); } static void lock_error_callback(libxl__egc *egc, libxl__ev_immediate *ei) /* broken -> disconnected */ { EGC_GC; libxl__ev_qmp *ev = CONTAINER_OF(ei, *ev, ei); int rc = ev->rc; /* On error, deallocate all private resources */ libxl__ev_qmp_dispose(gc, ev); /* And tell libxl__ev_qmp user about the error */ ev->callback(egc, ev, NULL, rc); /* must be last */ } /* 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[ev->tx_buf_off], 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; } /* * Store advertised QEMU version * { "QMP": { "version": { * "qemu": { "major": int, "minor": int, "micro": int } } } } */ o = libxl__json_map_get("QMP", resp, JSON_MAP); o = libxl__json_map_get("version", o, JSON_MAP); o = libxl__json_map_get("qemu", o, JSON_MAP); #define GRAB_VERSION(level) do { \ ev->qemu_version.level = libxl__json_object_get_integer( \ libxl__json_map_get(#level, o, JSON_INTEGER)); \ } while (0) GRAB_VERSION(major); GRAB_VERSION(minor); GRAB_VERSION(micro); #undef GRAB_VERSION LOGD(DEBUG, ev->domid, "QEMU version: %d.%d.%d", ev->qemu_version.major, ev->qemu_version.minor, ev->qemu_version.micro); /* 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; ev->qemu_version.major = -1; ev->qemu_version.minor = -1; ev->qemu_version.micro = -1; libxl__ev_qmplock_init(&ev->lock); ev->rc = 0; } int libxl__ev_qmp_send(libxl__egc *egc, libxl__ev_qmp *ev, const char *cmd, libxl__json_object *args) /* disconnected -> waiting_lock/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(egc, 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_slowlock_dispose(gc, &ev->lock); libxl__ev_qmp_init(ev); } /* * Local variables: * mode: C * c-basic-offset: 4 * indent-tabs-mode: nil * End: */