|
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] [Xen-devel] [PATCH v3 12/17] libxl: use vchan for QMP access with Linux stubdomain, libxl__ev_qmp_* version
Access to QMP of QEMU in Linux stubdomain is possible over vchan
connection. Add appropriate handling in libxl__ev_qmp_* API, keeping all the
asynchronous properties.
Since only one client can be connected to vchan server at the same time
and it is not enforced by the libxenvchan itself, additional client-side
locking is needed. Note that qemu supports only one simultaneous client
on a control socket anyway (but in UNIX socket case, it enforce it
server-side), so it doesn't add any extra limitation.
Regarding pipe to self:
Vchan issue notification about incoming data (or space for it)
only once - even if there is more data to read, FD returned by
libxenvchan_fd_for_select() will not be readable. Similar to buffer
space - there is one notification if more data can be written, but FD
isn't considered "writable" after libxenvchan_wait() call, even if in
fact there is a buffer space.
There are two situations where it is problematic:
- some QMP message results in a user callback and processing further
messages must stop (even if more data is already available in vchan
buffer)
- data is scheduled to write after a buffer space notification; this
may result from either delayed libxl__ev_qmp_send() call, or internal
state change
To avoid the above problems, use pipe to self to schedule vchan data
processing, even if vchan would not issue notification itself.
Signed-off-by: Marek Marczykowski-Górecki <marmarek@xxxxxxxxxxxxxxxxxxxxxx>
---
TODO:
- handle locking for vchan access - a lockfile + flock comes to mind,
but there is no async provisioning for it in libxl
Changes in v3:
- new patch, in place of "libxl: access QMP socket via console for
qemu-in-stubdomain"
---
tools/Rules.mk | 4 +-
tools/libxl/Makefile | 2 +-
tools/libxl/libxl_dm.c | 2 +-
tools/libxl/libxl_internal.h | 7 +-
tools/libxl/libxl_qmp.c | 293 +++++++++++++++++++++++++++++++++++-
5 files changed, 301 insertions(+), 7 deletions(-)
diff --git a/tools/Rules.mk b/tools/Rules.mk
index 68f2ed7..12b3129 100644
--- a/tools/Rules.mk
+++ b/tools/Rules.mk
@@ -187,8 +187,8 @@ SHLIB_libblktapctl =
PKG_CONFIG_REMOVE += xenblktapctl
endif
-CFLAGS_libxenlight = -I$(XEN_XENLIGHT) $(CFLAGS_libxenctrl)
$(CFLAGS_xeninclude)
-SHDEPS_libxenlight = $(SHLIB_libxenctrl) $(SHLIB_libxenstore)
$(SHLIB_libblktapctl)
+CFLAGS_libxenlight = -I$(XEN_XENLIGHT) $(CFLAGS_libxenctrl)
$(CFLAGS_xeninclude) $(CFLAGS_libxenvchan)
+SHDEPS_libxenlight = $(SHLIB_libxenctrl) $(SHLIB_libxenstore)
$(SHLIB_libblktapctl) $(SHLIB_libxenvchan)
LDLIBS_libxenlight = $(SHDEPS_libxenlight)
$(XEN_XENLIGHT)/libxenlight$(libextension)
SHLIB_libxenlight = $(SHDEPS_libxenlight) -Wl,-rpath-link=$(XEN_XENLIGHT)
diff --git a/tools/libxl/Makefile b/tools/libxl/Makefile
index 6da342e..55b0b63 100644
--- a/tools/libxl/Makefile
+++ b/tools/libxl/Makefile
@@ -24,6 +24,7 @@ LIBXL_LIBS = $(LDLIBS_libxentoollog) $(LDLIBS_libxenevtchn)
$(LDLIBS_libxenctrl)
ifeq ($(CONFIG_LIBNL),y)
LIBXL_LIBS += $(LIBNL3_LIBS)
endif
+LIBXL_LIBS += $(LDLIBS_libxenvchan)
CFLAGS_LIBXL += $(CFLAGS_libxentoollog)
CFLAGS_LIBXL += $(CFLAGS_libxentoolcore)
@@ -35,6 +36,7 @@ CFLAGS_LIBXL += $(CFLAGS_libblktapctl)
ifeq ($(CONFIG_LIBNL),y)
CFLAGS_LIBXL += $(LIBNL3_CFLAGS)
endif
+CFLAGS_LIBXL += $(CFLAGS_libxenvchan)
CFLAGS_LIBXL += -Wshadow
LIBXL_LIBS-$(CONFIG_ARM) += -lfdt
diff --git a/tools/libxl/libxl_dm.c b/tools/libxl/libxl_dm.c
index 6cfc256..b9a53f3 100644
--- a/tools/libxl/libxl_dm.c
+++ b/tools/libxl/libxl_dm.c
@@ -1180,7 +1180,7 @@ static int libxl__build_device_model_args_new(libxl__gc
*gc,
"-xen-domid",
GCSPRINTF("%d", guest_domid), NULL);
- /* There is currently no way to access the QMP socket in the stubdom */
+ /* QMP access to qemu running in stubdomain is done over vchan, not local
socket */
if (!is_stubdom) {
flexarray_append(dm_args, "-chardev");
if (state->dm_monitor_fd >= 0) {
diff --git a/tools/libxl/libxl_internal.h b/tools/libxl/libxl_internal.h
index 0095835..9a903a5 100644
--- a/tools/libxl/libxl_internal.h
+++ b/tools/libxl/libxl_internal.h
@@ -57,6 +57,7 @@
#include <xenctrl.h>
#include <xenguest.h>
#include <xc_dom.h>
+#include <libxenvchan.h>
#include <xen-tools/libs.h>
@@ -509,6 +510,12 @@ struct libxl__ev_qmp {
libxl__carefd *cfd;
libxl__ev_fd efd;
libxl__qmp_state state;
+ /* pipe to wake itself if there is more data in vchan */
+ libxl__carefd *pipe_cfd_read;
+ libxl__carefd *pipe_cfd_write;
+ libxl__ev_fd pipe_efd;
+ struct libxenvchan *vchan;
+ libxl__xswait_state xswait;
int id;
int next_id; /* next id to use */
/* receive buffer */
diff --git a/tools/libxl/libxl_qmp.c b/tools/libxl/libxl_qmp.c
index a235095..45b9f74 100644
--- a/tools/libxl/libxl_qmp.c
+++ b/tools/libxl/libxl_qmp.c
@@ -1466,6 +1466,14 @@ static void dm_state_saved(libxl__egc *egc,
libxl__ev_qmp *ev,
/* prototypes */
+static int qmp_ev_connect_vchan(libxl__gc *gc, libxl__ev_qmp *ev);
+static void qmp_vchan_watch_callback(libxl__egc *egc,
+ libxl__xswait_state *xswa, int rc, const char *data);
+static void qmp_ev_vchan_fd_callback(libxl__egc *egc, libxl__ev_fd *ev_fd,
+ int fd, short events, short revents);
+static void qmp_ev_vchan_pipe_fd_callback(libxl__egc *egc, libxl__ev_fd *ev_fd,
+ int fd, short events, short revents);
+static void qmp_ev_vchan_schedule_wakeup(libxl__ev_qmp *ev);
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,
@@ -1491,6 +1499,17 @@ static void qmp_ev_ensure_reading_writing(libxl__gc *gc,
libxl__ev_qmp *ev)
else if ((ev->state == qmp_state_waiting_reply) && ev->msg)
events |= POLLOUT;
+ if (ev->vchan || ev->xswait.what) {
+ if (ev->vchan &&
+ libxenvchan_buffer_space(ev->vchan) &&
+ (events & POLLOUT)) {
+ /* If vchan notification about available buffer space was received
+ * already, it won't be dispatched again, trigger it manually. */
+ qmp_ev_vchan_schedule_wakeup(ev);
+ }
+ return;
+ }
+
libxl__ev_fd_modify(gc, &ev->efd, events);
}
@@ -1564,6 +1583,8 @@ static int qmp_error_class_to_libxl_error_code(libxl__gc
*gc,
/* Setup connection */
+/* - QMP over unix socket */
+
static int qmp_ev_connect(libxl__gc *gc, libxl__ev_qmp *ev)
/* disconnected -> connecting but with `msg` free
* on error: broken */
@@ -1575,6 +1596,10 @@ static int qmp_ev_connect(libxl__gc *gc, libxl__ev_qmp
*ev)
assert(ev->state == qmp_state_disconnected);
+ /* use vchan connection for stubdomain */
+ if (libxl_get_stubdom_id(CTX, ev->domid))
+ return qmp_ev_connect_vchan(gc, ev);
+
qmp_socket_path = libxl__qemu_qmp_path(gc, ev->domid);
LOGD(DEBUG, ev->domid, "Connecting to %s", qmp_socket_path);
@@ -1618,6 +1643,106 @@ out:
return rc;
}
+/* - QMP over vchan */
+
+static int qmp_ev_connect_vchan(libxl__gc *gc, libxl__ev_qmp *ev)
+ /* disconnected -> connecting but with `msg` free
+ * on error: broken */
+{
+ int dm_domid;
+ int rc;
+
+ assert(ev->state == qmp_state_disconnected);
+
+ dm_domid = libxl_get_stubdom_id(CTX, ev->domid);
+ assert(dm_domid != 0);
+
+ ev->xswait.ao = ev->ao;
+ ev->xswait.what = GCSPRINTF("qmp vchan for %u", ev->domid);
+ ev->xswait.path = DEVICE_MODEL_XS_PATH(gc, dm_domid, ev->domid,
"/qmp-vchan");
+ ev->xswait.timeout_ms = LIBXL_STUBDOM_START_TIMEOUT * 1000;
+ ev->xswait.callback = qmp_vchan_watch_callback;
+
+ LOGD(DEBUG, ev->domid, "Connecting to vchan at %s", ev->xswait.path);
+
+ rc = libxl__xswait_start(gc, &ev->xswait);
+ if (rc) goto out;
+
+ qmp_ev_set_state(gc, ev, qmp_state_connecting);
+
+ return 0;
+
+out:
+ return rc;
+}
+
+static void qmp_vchan_watch_callback(libxl__egc *egc,
+ libxl__xswait_state *xswa, int rc, const char *data)
+{
+ libxl__ev_qmp *ev = CONTAINER_OF(xswa, *ev, xswait);
+ STATE_AO_GC(ev->ao);
+ int dm_domid = libxl_get_stubdom_id(CTX, ev->domid);
+ int pipe_fd[2];
+
+ if (!rc) {
+ ev->vchan = libxenvchan_client_init(CTX->lg, dm_domid,
ev->xswait.path);
+ if (ev->vchan) {
+ /* ok */
+ libxl__xswait_stop(gc, &ev->xswait);
+
+ ev->vchan->blocking = 0;
+
+ rc = libxl__ev_fd_register(gc, &ev->efd, qmp_ev_vchan_fd_callback,
+ libxenvchan_fd_for_select(ev->vchan), POLLIN);
+ if (rc)
+ goto error;
+
+ libxl__carefd_begin();
+ rc = pipe(pipe_fd);
+ if (!rc) {
+ ev->pipe_cfd_read = libxl__carefd_record(CTX, pipe_fd[0]);
+ ev->pipe_cfd_write = libxl__carefd_record(CTX, pipe_fd[1]);
+ }
+ libxl__carefd_unlock();
+ if (rc) {
+ LOGED(ERROR, ev->domid, "pipe() failed");
+ rc = ERROR_FAIL;
+ goto error;
+ }
+ if (!ev->pipe_cfd_read || !ev->pipe_cfd_write) {
+ LOGED(ERROR, ev->domid, "pipe FD registration failed");
+ rc = ERROR_FAIL;
+ goto error;
+ }
+ rc = libxl__ev_fd_register(gc, &ev->pipe_efd,
qmp_ev_vchan_pipe_fd_callback,
+ libxl__carefd_fd(ev->pipe_cfd_read), POLLIN);
+ if (rc)
+ goto error;
+ } else if (errno == ENOENT) {
+ /* not ready yet, wait */
+ return;
+ } else {
+ LOGED(ERROR, ev->domid, "Connection to qmp vchan for %u failed",
ev->domid);
+ rc = ERROR_FAIL;
+ }
+ }
+
+ if (!rc)
+ return;
+
+error:
+
+ if (rc==ERROR_TIMEDOUT || rc==ERROR_ABORTED)
+ LOGD(ERROR, ev->domid, "Connection to qmp vchan for %u timed out",
ev->domid);
+
+ /* 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 */
+}
+
+
/* QMP FD callbacks */
static void qmp_ev_fd_callback(libxl__egc *egc, libxl__ev_fd *ev_fd,
@@ -1690,6 +1815,105 @@ error:
ev->callback(egc, ev, NULL, rc); /* must be last */
}
+/* Make sure vchan events will be handled even if no new notification is sent.
+ */
+static void qmp_ev_vchan_schedule_wakeup(libxl__ev_qmp *ev)
+{
+ assert(ev->pipe_cfd_write);
+ write(libxl__carefd_fd(ev->pipe_cfd_write), ".", 1);
+}
+
+static int qmp_ev_vchan_handle_read_write(libxl__egc *egc, libxl__ev_qmp *ev)
+{
+ int rc;
+ STATE_AO_GC(ev->ao);
+
+ rc = qmp_ev_callback_readable(egc, ev, -1);
+ if (rc)
+ /* returns both rc values -ERROR_* and 1 */
+ return rc;
+
+ if (ev->tx_buf || ((ev->state == qmp_state_waiting_reply) && ev->msg)) {
+ rc = qmp_ev_callback_writable(gc, ev, -1);
+ if (rc)
+ return rc;
+ }
+ return 0;
+}
+
+static void qmp_ev_vchan_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_vchan_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;
+
+ assert(ev->vchan);
+
+ if (revents & ~(POLLIN)) {
+ LOGD(ERROR, ev->domid,
+ "unexpected poll event 0x%x on QMP vchan FD (expected POLLIN)",
+ revents);
+ rc = ERROR_FAIL;
+ goto error;
+ }
+
+ if (revents & POLLIN) {
+ libxenvchan_wait(ev->vchan);
+ rc = qmp_ev_vchan_handle_read_write(egc, ev);
+ if (rc < 0)
+ goto error;
+ }
+
+ 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 void qmp_ev_vchan_pipe_fd_callback(libxl__egc *egc, libxl__ev_fd *ev_fd,
+ int fd, short events, short revents)
+{
+ char buf[1];
+ int rc = 0;
+ libxl__ev_qmp *ev = CONTAINER_OF(ev_fd, *ev, pipe_efd);
+ STATE_AO_GC(ev->ao);
+
+ if (revents & POLLIN) {
+ read(fd, buf, 1);
+ rc = qmp_ev_vchan_handle_read_write(egc, ev);
+ if (rc < 0)
+ goto error;
+ }
+
+ 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
@@ -1725,6 +1949,13 @@ static int qmp_ev_callback_writable(libxl__gc *gc,
ev->payload_fd >= 0 &&
ev->tx_buf_off == 0) {
+ /* sending FDs not supported over vchan */
+ if (ev->vchan) {
+ /* XXX should it be assert? */
+ LOGED(ERROR, ev->domid, "Sending FD over vchan connection not
supported");
+ return ERROR_NI;
+ }
+
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 */
@@ -1737,7 +1968,16 @@ static int qmp_ev_callback_writable(libxl__gc *gc,
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 (ev->vchan) {
+ r = libxenvchan_write(ev->vchan, ev->tx_buf + ev->tx_buf_off,
max_write);
+ /* libxenvchan_write returns 0 if no space left, translate to
EWOULDBLOCK */
+ if (r == 0) {
+ r = -1;
+ errno = EWOULDBLOCK;
+ }
+ } else {
+ r = write(fd, ev->tx_buf + ev->tx_buf_off, max_write);
+ }
if (r < 0) {
if (errno == EINTR)
continue;
@@ -1790,6 +2030,24 @@ static int qmp_ev_callback_readable(libxl__egc *egc,
else if (rc)
return rc;
+ /*
+ * When adding support for multiple simultaneousl requests (or
events),
+ * possibly multiple responses will be already waiting in vchan
+ * buffer. 'ev' can't be touched here after calling user callback,
+ * so remaining messages may be processed in the next callback
+ * (qmp_ev_fd_callback/qmp_ev_vchan_fd_callback). But if at this
+ * point all the data is already in vchan buffer and qemu will not
+ * write anything more, no vchan notification will be issued
+ * (ev->efd will not report POLLIN). Because of this, the callback
+ * will need to be triggered the other way:
+ *
+ * if (ev->vchan && libxenvchan_data_ready(ev->vchan))
+ * qmp_ev_vchan_schedule_wakeup(ev);
+ *
+ * With only one qmp request pending, this isn't needed, as if user
+ * callback is called, there are no more (interesting) messages in
+ * the vchan buffer (there was only one).
+ */
/* Must be last and return when the user callback is called */
rc = qmp_ev_handle_message(egc, ev, o);
if (rc)
@@ -1811,8 +2069,18 @@ static int qmp_ev_callback_readable(libxl__egc *egc,
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 (ev->vchan) {
+ r = libxenvchan_read(ev->vchan, ev->rx_buf + ev->rx_buf_used,
+ ev->rx_buf_size - ev->rx_buf_used);
+ /* translate r == 0 to EWOULDBLOCK */
+ if (r == 0) {
+ r = -1;
+ errno = EWOULDBLOCK;
+ }
+ } else {
+ 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;
@@ -2098,7 +2366,14 @@ void libxl__ev_qmp_init(libxl__ev_qmp *ev)
ev->next_id = 0x786c7100;
ev->cfd = NULL;
+ ev->pipe_cfd_read = NULL;
+ ev->pipe_cfd_write = NULL;
+ ev->vchan = NULL;
+ libxl__xswait_init(&ev->xswait);
+ ev->xswait.what = NULL;
+ ev->xswait.path = NULL;
libxl__ev_fd_init(&ev->efd);
+ libxl__ev_fd_init(&ev->pipe_efd);
ev->state = qmp_state_disconnected;
ev->id = 0;
@@ -2162,7 +2437,17 @@ void libxl__ev_qmp_dispose(libxl__gc *gc, libxl__ev_qmp
*ev)
LOGD(DEBUG, ev->domid, " ev %p", ev);
libxl__ev_fd_deregister(gc, &ev->efd);
- libxl__carefd_close(ev->cfd);
+ if (ev->vchan)
+ libxenvchan_close(ev->vchan);
+ else
+ libxl__carefd_close(ev->cfd);
+ if (ev->pipe_cfd_read) {
+ libxl__ev_fd_deregister(gc, &ev->pipe_efd);
+ libxl__carefd_close(ev->pipe_cfd_read);
+ }
+ if (ev->pipe_cfd_write) {
+ libxl__carefd_close(ev->pipe_cfd_write);
+ }
libxl__ev_qmp_init(ev);
}
--
git-series 0.9.1
_______________________________________________
Xen-devel mailing list
Xen-devel@xxxxxxxxxxxxxxxxxxxx
https://lists.xenproject.org/mailman/listinfo/xen-devel
|
![]() |
Lists.xenproject.org is hosted with RackSpace, monitoring our |