mirror of
https://github.com/xen-project/xen.git
synced 2026-10-28 18:21:39 +01:00
...to denote what it actually does. The name libxl__xs_write() suggests something taking a buffer and length, akin to write(2), whereas the semantics of the function are actually more akin to printf(3). This patch is a textual substitution of libxl__xs_write with libxl__xs_printf with some associated formatting fixes. Signed-off-by: Paul Durrant <paul.durrant@citrix.com> Acked-by: Ian Jackson <ian.jackson@eu.citrix.com> Cc: Stefano Stabellini <stefano.stabellini@eu.citrix.com> Cc: Ian Campbell <ian.campbell@citrix.com> Cc: Wei Liu <wei.liu2@citrix.com>
1000 lines
27 KiB
C
1000 lines
27 KiB
C
/*
|
|
* Copyright (C) 2011 Citrix Ltd.
|
|
* Author Anthony PERARD <anthony.perard@citrix.com>
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
#include "libxl_osdeps.h" /* must come before any other headers */
|
|
|
|
#include <sys/un.h>
|
|
|
|
#include <yajl/yajl_gen.h>
|
|
|
|
#include "_libxl_list.h"
|
|
#include "libxl_internal.h"
|
|
|
|
/* #define DEBUG_RECEIVED */
|
|
|
|
#ifdef DEBUG_RECEIVED
|
|
# define DEBUG_REPORT_RECEIVED(buf, len) \
|
|
LOG(DEBUG, "received: '%.*s'", len, buf)
|
|
#else
|
|
# define DEBUG_REPORT_RECEIVED(buf, len) ((void)0)
|
|
#endif
|
|
|
|
/*
|
|
* QMP types & constant
|
|
*/
|
|
|
|
#define QMP_RECEIVE_BUFFER_SIZE 4096
|
|
#define PCI_PT_QDEV_ID "pci-pt-%02x_%02x.%01x"
|
|
|
|
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;
|
|
LIBXL_STAILQ_ENTRY(struct callback_id_pair) next;
|
|
} callback_id_pair;
|
|
|
|
struct libxl__qmp_handler {
|
|
struct sockaddr_un addr;
|
|
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];
|
|
libxl__yajl_ctx *yajl_ctx;
|
|
|
|
libxl_ctx *ctx;
|
|
uint32_t domid;
|
|
|
|
int last_id_used;
|
|
LIBXL_STAILQ_HEAD(callback_list, callback_id_pair) callback_list;
|
|
};
|
|
|
|
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 store_serial_port_info(libxl__qmp_handler *qmp,
|
|
const char *chardev,
|
|
int port)
|
|
{
|
|
GC_INIT(qmp->ctx);
|
|
char *path = NULL;
|
|
int ret = 0;
|
|
|
|
if (!(chardev && strncmp("pty:", chardev, 4) == 0)) {
|
|
return 0;
|
|
}
|
|
|
|
path = libxl__xs_get_dompath(gc, qmp->domid);
|
|
path = GCSPRINTF("%s/serial/%d/tty", path, port);
|
|
|
|
ret = libxl__xs_printf(gc, XBT_NULL, path, "%s", chardev + 4);
|
|
|
|
GC_FREE;
|
|
return ret;
|
|
}
|
|
|
|
static int register_serials_chardev_callback(libxl__qmp_handler *qmp,
|
|
const libxl__json_object *o,
|
|
void *unused)
|
|
{
|
|
const libxl__json_object *obj = NULL;
|
|
const libxl__json_object *label = NULL;
|
|
const char *s = NULL;
|
|
int i = 0;
|
|
const char *chardev = NULL;
|
|
int ret = 0;
|
|
|
|
for (i = 0; (obj = libxl__json_array_get(o, i)); i++) {
|
|
if (!libxl__json_object_is_map(obj))
|
|
continue;
|
|
label = libxl__json_map_get("label", obj, JSON_STRING);
|
|
s = libxl__json_object_get_string(label);
|
|
|
|
if (s && strncmp("serial", s, strlen("serial")) == 0) {
|
|
const libxl__json_object *filename = NULL;
|
|
char *endptr = NULL;
|
|
int port_number;
|
|
|
|
filename = libxl__json_map_get("filename", obj, JSON_STRING);
|
|
chardev = libxl__json_object_get_string(filename);
|
|
|
|
s += strlen("serial");
|
|
port_number = strtol(s, &endptr, 10);
|
|
if (*s == 0 || *endptr != 0) {
|
|
LIBXL__LOG(qmp->ctx, LIBXL__LOG_ERROR,
|
|
"Invalid serial port number: %s", s);
|
|
return -1;
|
|
}
|
|
ret = store_serial_port_info(qmp, chardev, port_number);
|
|
if (ret) {
|
|
LIBXL__LOG_ERRNO(qmp->ctx, LIBXL__LOG_ERROR,
|
|
"Failed to store serial port information"
|
|
" in xenstore");
|
|
return ret;
|
|
}
|
|
}
|
|
};
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int qmp_write_domain_console_item(libxl__gc *gc, int domid,
|
|
const char *item, const char *value)
|
|
{
|
|
char *path;
|
|
|
|
path = libxl__xs_get_dompath(gc, domid);
|
|
path = GCSPRINTF("%s/console/%s", path, item);
|
|
|
|
return libxl__xs_printf(gc, XBT_NULL, path, "%s", value);
|
|
}
|
|
|
|
static int qmp_register_vnc_callback(libxl__qmp_handler *qmp,
|
|
const libxl__json_object *o,
|
|
void *unused)
|
|
{
|
|
GC_INIT(qmp->ctx);
|
|
const libxl__json_object *obj;
|
|
const char *addr, *port;
|
|
int rc = -1;
|
|
|
|
if (!libxl__json_object_is_map(o)) {
|
|
goto out;
|
|
}
|
|
|
|
obj = libxl__json_map_get("enabled", o, JSON_BOOL);
|
|
if (!obj || !libxl__json_object_get_bool(obj)) {
|
|
rc = 0;
|
|
goto out;
|
|
}
|
|
|
|
obj = libxl__json_map_get("host", o, JSON_STRING);
|
|
addr = libxl__json_object_get_string(obj);
|
|
obj = libxl__json_map_get("service", o, JSON_STRING);
|
|
port = libxl__json_object_get_string(obj);
|
|
|
|
if (!addr || !port) {
|
|
LOG(ERROR, "Failed to retreive VNC connect information.");
|
|
goto out;
|
|
}
|
|
|
|
rc = qmp_write_domain_console_item(gc, qmp->domid, "vnc-listen", addr);
|
|
if (!rc)
|
|
rc = qmp_write_domain_console_item(gc, qmp->domid, "vnc-port", port);
|
|
|
|
out:
|
|
GC_FREE;
|
|
return rc;
|
|
}
|
|
|
|
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(libxl__qmp_handler *qmp,
|
|
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);
|
|
|
|
LIBXL_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;
|
|
}
|
|
LIBXL_STAILQ_REMOVE(&qmp->callback_list, pp, callback_id_pair, next);
|
|
free(pp);
|
|
}
|
|
|
|
LOG(ERROR, "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(qmp, resp);
|
|
LOG(DEBUG, "message type: %s", libxl__qmp_message_type_to_string(type));
|
|
|
|
switch (type) {
|
|
case LIBXL__QMP_MESSAGE_TYPE_QMP:
|
|
/* 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;
|
|
}
|
|
LIBXL_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;
|
|
}
|
|
|
|
/*
|
|
* 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) {
|
|
LOGE(ERROR, "Failed to allocate qmp_handler");
|
|
return NULL;
|
|
}
|
|
qmp->ctx = CTX;
|
|
qmp->domid = domid;
|
|
qmp->timeout = 5;
|
|
|
|
LIBXL_STAILQ_INIT(&qmp->callback_list);
|
|
|
|
return qmp;
|
|
}
|
|
|
|
static int qmp_open(libxl__qmp_handler *qmp, const char *qmp_socket_path,
|
|
int timeout)
|
|
{
|
|
int ret = -1;
|
|
int i = 0;
|
|
|
|
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;
|
|
}
|
|
|
|
if (sizeof (qmp->addr.sun_path) <= strlen(qmp_socket_path)) {
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
memset(&qmp->addr, 0, sizeof (qmp->addr));
|
|
qmp->addr.sun_family = AF_UNIX;
|
|
strncpy(qmp->addr.sun_path, qmp_socket_path,
|
|
sizeof (qmp->addr.sun_path)-1);
|
|
|
|
do {
|
|
ret = connect(qmp->qmp_fd, (struct sockaddr *) &qmp->addr,
|
|
sizeof (qmp->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);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void qmp_close(libxl__qmp_handler *qmp)
|
|
{
|
|
callback_id_pair *pp = NULL;
|
|
callback_id_pair *tmp = NULL;
|
|
|
|
close(qmp->qmp_fd);
|
|
LIBXL_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) {
|
|
LOG(ERROR, "timeout");
|
|
return -1;
|
|
} else if (ret < 0) {
|
|
if (errno == EINTR)
|
|
continue;
|
|
LOGE(ERROR, "Select error");
|
|
return -1;
|
|
}
|
|
|
|
rd = read(qmp->qmp_fd, qmp->buffer, QMP_RECEIVE_BUFFER_SIZE);
|
|
if (rd == 0) {
|
|
LOG(ERROR, "Unexpected end of socket");
|
|
return -1;
|
|
} else if (rd < 0) {
|
|
LOGE(ERROR, "Socket read error");
|
|
return rd;
|
|
}
|
|
|
|
DEBUG_REPORT_RECEIVED(qmp->buffer, rd);
|
|
|
|
do {
|
|
char *end = NULL;
|
|
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;
|
|
}
|
|
|
|
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 {
|
|
LOG(ERROR, "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_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)
|
|
{
|
|
const unsigned char *buf = NULL;
|
|
char *ret = NULL;
|
|
libxl_yajl_length len = 0;
|
|
yajl_gen_status s;
|
|
yajl_gen hand;
|
|
callback_id_pair *elm = NULL;
|
|
|
|
hand = libxl_yajl_gen_alloc(NULL);
|
|
|
|
if (!hand) {
|
|
return NULL;
|
|
}
|
|
|
|
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, ++qmp->last_id_used);
|
|
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) {
|
|
LOG(ERROR, "Failed to generate a qmp command");
|
|
goto out;
|
|
}
|
|
|
|
elm = malloc(sizeof (callback_id_pair));
|
|
if (elm == NULL) {
|
|
LOGE(ERROR, "Failed to allocate a QMP callback");
|
|
goto out;
|
|
}
|
|
elm->id = qmp->last_id_used;
|
|
elm->callback = callback;
|
|
elm->opaque = opaque;
|
|
elm->context = context;
|
|
LIBXL_STAILQ_INSERT_TAIL(&qmp->callback_list, elm, next);
|
|
|
|
ret = libxl__strndup(gc, (const char*)buf, len);
|
|
|
|
LOG(DEBUG, "next qmp command: '%s'", buf);
|
|
|
|
out:
|
|
yajl_gen_free(hand);
|
|
return ret;
|
|
}
|
|
|
|
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;
|
|
if (libxl_write_exactly(qmp->ctx, qmp->qmp_fd, "\r\n", 2,
|
|
"CRLF", "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,
|
|
int 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 -1;
|
|
}
|
|
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);
|
|
}
|
|
|
|
static void qmp_parameters_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);
|
|
}
|
|
|
|
static void qmp_parameters_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);
|
|
}
|
|
|
|
static void qmp_parameters_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);
|
|
}
|
|
|
|
#define QMP_PARAMETERS_SPRINTF(args, name, format, ...) \
|
|
qmp_parameters_add_string(gc, args, name, GCSPRINTF(format, __VA_ARGS__))
|
|
|
|
/*
|
|
* 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) {
|
|
LOGE(ERROR, "Connection error");
|
|
qmp_free_handler(qmp);
|
|
return NULL;
|
|
}
|
|
|
|
LOG(DEBUG, "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) {
|
|
LOG(ERROR, "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) {
|
|
LOGE(ERROR, "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) {
|
|
LOGE(ERROR, "Failed to remove QMP socket file %s", qmp_socket);
|
|
}
|
|
}
|
|
}
|
|
|
|
int libxl__qmp_query_serial(libxl__qmp_handler *qmp)
|
|
{
|
|
return qmp_synchronous_send(qmp, "query-chardev", NULL,
|
|
register_serials_chardev_callback,
|
|
NULL, qmp->timeout);
|
|
}
|
|
|
|
static int qmp_query_vnc(libxl__qmp_handler *qmp)
|
|
{
|
|
return qmp_synchronous_send(qmp, "query-vnc", NULL,
|
|
qmp_register_vnc_callback,
|
|
NULL, qmp->timeout);
|
|
}
|
|
|
|
static int pci_add_callback(libxl__qmp_handler *qmp,
|
|
const libxl__json_object *response, void *opaque)
|
|
{
|
|
libxl_device_pci *pcidev = opaque;
|
|
const libxl__json_object *bus = NULL;
|
|
GC_INIT(qmp->ctx);
|
|
int i, j, rc = -1;
|
|
char *asked_id = GCSPRINTF(PCI_PT_QDEV_ID,
|
|
pcidev->bus, pcidev->dev, pcidev->func);
|
|
|
|
for (i = 0; (bus = libxl__json_array_get(response, i)); i++) {
|
|
const libxl__json_object *devices = NULL;
|
|
const libxl__json_object *device = NULL;
|
|
const libxl__json_object *o = NULL;
|
|
const char *id = NULL;
|
|
|
|
devices = libxl__json_map_get("devices", bus, JSON_ARRAY);
|
|
|
|
for (j = 0; (device = libxl__json_array_get(devices, j)); j++) {
|
|
o = libxl__json_map_get("qdev_id", device, JSON_STRING);
|
|
id = libxl__json_object_get_string(o);
|
|
|
|
if (id && strcmp(asked_id, id) == 0) {
|
|
int dev_slot, dev_func;
|
|
|
|
o = libxl__json_map_get("slot", device, JSON_INTEGER);
|
|
if (!o)
|
|
goto out;
|
|
dev_slot = libxl__json_object_get_integer(o);
|
|
o = libxl__json_map_get("function", device, JSON_INTEGER);
|
|
if (!o)
|
|
goto out;
|
|
dev_func = libxl__json_object_get_integer(o);
|
|
|
|
pcidev->vdevfn = PCI_DEVFN(dev_slot, dev_func);
|
|
|
|
rc = 0;
|
|
goto out;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
out:
|
|
GC_FREE;
|
|
return rc;
|
|
}
|
|
|
|
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_pci_add(libxl__gc *gc, int domid, libxl_device_pci *pcidev)
|
|
{
|
|
libxl__qmp_handler *qmp = NULL;
|
|
libxl__json_object *args = NULL;
|
|
char *hostaddr = NULL;
|
|
int rc = 0;
|
|
|
|
qmp = libxl__qmp_initialize(gc, domid);
|
|
if (!qmp)
|
|
return -1;
|
|
|
|
hostaddr = GCSPRINTF("%04x:%02x:%02x.%01x", pcidev->domain,
|
|
pcidev->bus, pcidev->dev, pcidev->func);
|
|
if (!hostaddr)
|
|
return -1;
|
|
|
|
qmp_parameters_add_string(gc, &args, "driver", "xen-pci-passthrough");
|
|
QMP_PARAMETERS_SPRINTF(&args, "id", PCI_PT_QDEV_ID,
|
|
pcidev->bus, pcidev->dev, pcidev->func);
|
|
qmp_parameters_add_string(gc, &args, "hostaddr", hostaddr);
|
|
if (pcidev->vdevfn) {
|
|
QMP_PARAMETERS_SPRINTF(&args, "addr", "%x.%x",
|
|
PCI_SLOT(pcidev->vdevfn), PCI_FUNC(pcidev->vdevfn));
|
|
}
|
|
/*
|
|
* Version of QEMU prior to the XSA-131 fix did not support this
|
|
* property and were effectively always in permissive mode. The
|
|
* fix for XSA-131 switched the default to be restricted by
|
|
* default and added the permissive property.
|
|
*
|
|
* Therefore in order to support both old and new QEMU we only set
|
|
* the permissive flag if it is true. Users of older QEMU have no
|
|
* reason to set the flag so this is ok.
|
|
*/
|
|
if (pcidev->permissive)
|
|
qmp_parameters_add_bool(gc, &args, "permissive", true);
|
|
|
|
rc = qmp_synchronous_send(qmp, "device_add", args,
|
|
NULL, NULL, qmp->timeout);
|
|
if (rc == 0) {
|
|
rc = qmp_synchronous_send(qmp, "query-pci", NULL,
|
|
pci_add_callback, pcidev, qmp->timeout);
|
|
}
|
|
|
|
libxl__qmp_close(qmp);
|
|
return rc;
|
|
}
|
|
|
|
static int qmp_device_del(libxl__gc *gc, int domid, char *id)
|
|
{
|
|
libxl__json_object *args = NULL;
|
|
|
|
qmp_parameters_add_string(gc, &args, "id", id);
|
|
return qmp_run_command(gc, domid, "device_del", args, NULL, NULL);
|
|
}
|
|
|
|
int libxl__qmp_pci_del(libxl__gc *gc, int domid, libxl_device_pci *pcidev)
|
|
{
|
|
char *id = NULL;
|
|
|
|
id = GCSPRINTF(PCI_PT_QDEV_ID, pcidev->bus, pcidev->dev, pcidev->func);
|
|
|
|
return qmp_device_del(gc, domid, id);
|
|
}
|
|
|
|
int libxl__qmp_system_wakeup(libxl__gc *gc, int domid)
|
|
{
|
|
return qmp_run_command(gc, domid, "system_wakeup", NULL, NULL, NULL);
|
|
}
|
|
|
|
int libxl__qmp_save(libxl__gc *gc, int domid, const char *filename)
|
|
{
|
|
libxl__json_object *args = NULL;
|
|
|
|
qmp_parameters_add_string(gc, &args, "filename", (char *)filename);
|
|
return qmp_run_command(gc, domid, "xen-save-devices-state", args,
|
|
NULL, NULL);
|
|
}
|
|
|
|
static int qmp_change(libxl__gc *gc, libxl__qmp_handler *qmp,
|
|
char *device, char *target, char *arg)
|
|
{
|
|
libxl__json_object *args = NULL;
|
|
int rc = 0;
|
|
|
|
qmp_parameters_add_string(gc, &args, "device", device);
|
|
qmp_parameters_add_string(gc, &args, "target", target);
|
|
if (arg) {
|
|
qmp_parameters_add_string(gc, &args, "arg", arg);
|
|
}
|
|
|
|
rc = qmp_synchronous_send(qmp, "change", args,
|
|
NULL, NULL, qmp->timeout);
|
|
|
|
return rc;
|
|
}
|
|
|
|
int libxl__qmp_stop(libxl__gc *gc, int domid)
|
|
{
|
|
return qmp_run_command(gc, domid, "stop", NULL, NULL, NULL);
|
|
}
|
|
|
|
int libxl__qmp_resume(libxl__gc *gc, int domid)
|
|
{
|
|
return qmp_run_command(gc, domid, "cont", NULL, NULL, NULL);
|
|
}
|
|
|
|
int libxl__qmp_set_global_dirty_log(libxl__gc *gc, int domid, bool enable)
|
|
{
|
|
libxl__json_object *args = NULL;
|
|
|
|
qmp_parameters_add_bool(gc, &args, "enable", enable);
|
|
|
|
return qmp_run_command(gc, domid, "xen-set-global-dirty-log", args,
|
|
NULL, NULL);
|
|
}
|
|
|
|
int libxl__qmp_insert_cdrom(libxl__gc *gc, int domid,
|
|
const libxl_device_disk *disk)
|
|
{
|
|
libxl__json_object *args = NULL;
|
|
int dev_number = libxl__device_disk_dev_number(disk->vdev, NULL, NULL);
|
|
|
|
QMP_PARAMETERS_SPRINTF(&args, "device", "ide-%i", dev_number);
|
|
|
|
if (disk->format == LIBXL_DISK_FORMAT_EMPTY) {
|
|
return qmp_run_command(gc, domid, "eject", args, NULL, NULL);
|
|
} else {
|
|
qmp_parameters_add_string(gc, &args, "target", disk->pdev_path);
|
|
return qmp_run_command(gc, domid, "change", args, NULL, NULL);
|
|
}
|
|
}
|
|
|
|
int libxl__qmp_cpu_add(libxl__gc *gc, int domid, int idx)
|
|
{
|
|
libxl__json_object *args = NULL;
|
|
|
|
qmp_parameters_add_integer(gc, &args, "id", idx);
|
|
|
|
return qmp_run_command(gc, domid, "cpu-add", args, NULL, NULL);
|
|
}
|
|
|
|
int libxl__qmp_initializations(libxl__gc *gc, uint32_t domid,
|
|
const libxl_domain_config *guest_config)
|
|
{
|
|
const libxl_vnc_info *vnc = libxl__dm_vnc(guest_config);
|
|
libxl__qmp_handler *qmp = NULL;
|
|
int ret = 0;
|
|
|
|
qmp = libxl__qmp_initialize(gc, domid);
|
|
if (!qmp)
|
|
return -1;
|
|
ret = libxl__qmp_query_serial(qmp);
|
|
if (!ret && vnc && vnc->passwd) {
|
|
ret = qmp_change(gc, qmp, "vnc", "password", vnc->passwd);
|
|
qmp_write_domain_console_item(gc, domid, "vnc-pass", vnc->passwd);
|
|
}
|
|
if (!ret) {
|
|
ret = qmp_query_vnc(qmp);
|
|
}
|
|
libxl__qmp_close(qmp);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Local variables:
|
|
* mode: C
|
|
* c-basic-offset: 4
|
|
* indent-tabs-mode: nil
|
|
* End:
|
|
*/
|