[libvirt] [PATCH RFC] Allow a client to connect to QEMU's VNC by passing an FD via libvirt

Daniel P. Berrange berrange at redhat.com
Thu Jun 23 12:48:15 UTC 2011


This patch implements a new API which allows a libvirt client
application to connect to QEMU's VNC server, by passing a FD
from an anonymous socketpair, to libvirt, which then passes it
onto QEMU via the monitor. This obviously only works if the
client app is talking to a local libvirt over UNIX sockets,
not for any remote apps using TCP, or SSH tunnel.

 typedef enum {
     VIR_DOMAIN_CONNECT_GRAPHICS_SKIPAUTH = (1 << 0),
 } virDomainConnectGraphicsFlags;

 int virDomainConnectGraphics(virDomainPtr dom,
                              const char *devname,
                              int fd,
                              unsigned int flags);

The QEMU monitor command I proposed actually allo2s
conecting to VNC or SPICE or any character device,
so I might rename this API to replace 'Graphics'
with 'Client' or something. Or we could add a
separate API for connecting to character devices.

This does a semi-nasty hack to the remote protocol, defining
a new client -> server message type: REMOTE_CALL_WITH_FD
which is identical to REMOTE_CALL, but a UNIX FD is passed
between the header & payload. The reason I call this nasty
is that it only allows for a single FD to be passed per
RPC call. This seems like a reasonable restriction and it
makes the code utterly trivial now, but I'm afraid it might
bite us later.

* src/qemu/qemu_monitor.c, src/qemu/qemu_monitor.h,
  src/qemu/qemu_monitor_text.c, src/qemu/qemu_monitor_text.h,
  src/qemu/qemu_monitor_json.c, src/qemu/qemu_monitor_json.h:
  Add support for 'client_add' monitor command with FD passing
* src/qemu/qemu_driver.c: Implement the new virDomainConnectGraphics
  API call
* include/libvirt/libvirt.h.in, src/driver.h,
  src/libvirt_public.syms, src/libvirt.c: Define a new
  virDomainConnectGraphics API
* daemon/dispatch.c, daemon/libvirtd.c, daemon/libvirtd.h,
  daemon/remote.c, src/remote/remote_driver.c,
  src/remote/remote_protocol.x: Support for FD passing
  and the virDomainConnectGraphics API
---
 daemon/dispatch.c            |   14 +++++-
 daemon/libvirtd.c            |   13 +++++
 daemon/libvirtd.h            |    4 ++
 daemon/remote.c              |   44 ++++++++++++++++++
 include/libvirt/libvirt.h.in |   14 +++++-
 src/driver.h                 |    6 +++
 src/libvirt.c                |   50 ++++++++++++++++++++
 src/libvirt_private.syms     |    3 +
 src/libvirt_public.syms      |    1 +
 src/qemu/qemu_driver.c       |   59 ++++++++++++++++++++++++
 src/qemu/qemu_monitor.c      |   32 +++++++++++++
 src/qemu/qemu_monitor.h      |    6 +++
 src/qemu/qemu_monitor_json.c |   31 ++++++++++++-
 src/qemu/qemu_monitor_json.h |    5 ++
 src/qemu/qemu_monitor_text.c |   31 +++++++++++++
 src/qemu/qemu_monitor_text.h |    5 ++
 src/remote/remote_driver.c   |  102 +++++++++++++++++++++++++++++++++++++++++-
 src/remote/remote_protocol.x |   23 +++++++++-
 18 files changed, 434 insertions(+), 9 deletions(-)

diff --git a/daemon/dispatch.c b/daemon/dispatch.c
index 010be1e..723b1c7 100644
--- a/daemon/dispatch.c
+++ b/daemon/dispatch.c
@@ -29,9 +29,11 @@
 
 #include "dispatch.h"
 #include "remote.h"
-
+#include "files.h"
 #include "memory.h"
 
+virThreadLocal clientFD;
+
 /* Convert a libvirt  virError object into wire format */
 static void
 remoteDispatchCopyError (remote_error *rerr,
@@ -391,6 +393,7 @@ remoteDispatchClientRequest(struct qemud_server *server,
 
     switch (msg->hdr.type) {
     case REMOTE_CALL:
+    case REMOTE_CALL_WITH_FD:
         return remoteDispatchClientCall(server, client, msg, qemu_call);
 
     case REMOTE_STREAM:
@@ -503,6 +506,10 @@ remoteDispatchClientCall (struct qemud_server *server,
     conn = client->conn;
     virMutexUnlock(&client->lock);
 
+    if (msg->hdr.type == REMOTE_CALL_WITH_FD) {
+        virThreadLocalSet(&clientFD, &msg->fd);
+    }
+
     /*
      * When the RPC handler is called:
      *
@@ -515,6 +522,11 @@ remoteDispatchClientCall (struct qemud_server *server,
      */
     rv = (data->fn)(server, client, conn, &msg->hdr, &rerr, &args, &ret);
 
+    if (msg->hdr.type == REMOTE_CALL_WITH_FD) {
+        virThreadLocalSet(&clientFD, NULL);
+        VIR_FORCE_CLOSE(msg->fd);
+    }
+
     virMutexLock(&server->lock);
     virMutexLock(&client->lock);
     virMutexUnlock(&server->lock);
diff --git a/daemon/libvirtd.c b/daemon/libvirtd.c
index 5f291ec..6d6a44b 100644
--- a/daemon/libvirtd.c
+++ b/daemon/libvirtd.c
@@ -67,6 +67,7 @@
 #include "stream.h"
 #include "hooks.h"
 #include "virtaudit.h"
+#include "passfd.h"
 #ifdef HAVE_AVAHI
 # include "mdns.h"
 #endif
@@ -875,6 +876,12 @@ static struct qemud_server *qemudInitialize(void) {
         return NULL;
     }
 
+    if (virThreadLocalInit(&clientFD, NULL) < 0) {
+        VIR_ERROR(_("Failed to initialize thread local"));
+        VIR_FREE(server);
+        return NULL;
+    }
+
     server->privileged = geteuid() == 0 ? 1 : 0;
     server->sigread = server->sigwrite = -1;
 
@@ -1882,6 +1889,12 @@ readmore:
             qemudDispatchClientFailure(client);
         }
 
+        if (msg->hdr.type == REMOTE_CALL_WITH_FD) {
+            VIR_ERROR("Trying to get FD");
+            msg->fd = recvfd(client->fd, O_CLOEXEC);
+            VIR_ERROR("Got %d", msg->fd);
+        }
+
         /* Check if any filters match this message */
         filter = client->filters;
         while (filter) {
diff --git a/daemon/libvirtd.h b/daemon/libvirtd.h
index ea00d5c..7866067 100644
--- a/daemon/libvirtd.h
+++ b/daemon/libvirtd.h
@@ -147,6 +147,8 @@ struct qemud_client_message {
 
     remote_message_header hdr;
 
+    int fd;
+
     struct qemud_client_message *next;
 };
 
@@ -304,6 +306,8 @@ struct qemud_server {
 # endif
 };
 
+extern virThreadLocal clientFD;
+
 void qemudLog(int priority, const char *fmt, ...)
     ATTRIBUTE_FMT_PRINTF(2,3);
 
diff --git a/daemon/remote.c b/daemon/remote.c
index 37fbed0..700e12c 100644
--- a/daemon/remote.c
+++ b/daemon/remote.c
@@ -2778,6 +2778,50 @@ cleanup:
 }
 
 
+static int
+remoteDispatchDomainConnectGraphics(struct qemud_server *server ATTRIBUTE_UNUSED,
+                                    struct qemud_client *client ATTRIBUTE_UNUSED,
+                                    virConnectPtr conn,
+                                    remote_message_header *hdr ATTRIBUTE_UNUSED,
+                                    remote_error *rerr,
+                                    remote_domain_connect_graphics_args *args,
+                                    void *ret ATTRIBUTE_UNUSED)
+{
+    virDomainPtr dom = NULL;
+    int *fd;
+    int rv = -1;
+
+    if (!conn) {
+        virNetError(VIR_ERR_INTERNAL_ERROR, "%s", _("connection not open"));
+        goto cleanup;
+    }
+
+    if (!(dom = get_nonnull_domain(conn, args->dom)))
+        goto cleanup;
+
+    fd = virThreadLocalGet(&clientFD);
+    if (!fd || *fd == -1) {
+        virNetError(VIR_ERR_INTERNAL_ERROR, "%s", _("Missing file descriptor"));
+        goto cleanup;
+    }
+
+    if (virDomainConnectGraphics(dom,
+                                 args->devicestr,
+                                 *fd,
+                                 args->flags) < 0)
+        goto cleanup;
+
+    rv = 0;
+
+cleanup:
+    if (rv < 0)
+        remoteDispatchError(rerr);
+    if (dom)
+        virDomainFree(dom);
+    return rv;
+}
+
+
 #include "remote_dispatch_bodies.h"
 #include "qemu_dispatch_bodies.h"
 
diff --git a/include/libvirt/libvirt.h.in b/include/libvirt/libvirt.h.in
index 3f634e6..49b8fc1 100644
--- a/include/libvirt/libvirt.h.in
+++ b/include/libvirt/libvirt.h.in
@@ -2654,6 +2654,7 @@ typedef enum {
 typedef enum {
     VIR_DOMAIN_EVENT_GRAPHICS_ADDRESS_IPV4,  /* IPv4 address */
     VIR_DOMAIN_EVENT_GRAPHICS_ADDRESS_IPV6,  /* IPv6 address */
+    VIR_DOMAIN_EVENT_GRAPHICS_ADDRESS_UNIX,  /* UNIX socket path */
 } virDomainEventGraphicsAddressType;
 
 
@@ -2665,8 +2666,8 @@ typedef enum {
  */
 struct _virDomainEventGraphicsAddress {
     int family;               /* Address family, virDomainEventGraphicsAddressType */
-    const char *node;         /* Address of node (eg IP address) */
-    const char *service;      /* Service name/number (eg TCP port) */
+    const char *node;         /* Address of node (eg IP address, or UNIX path) */
+    const char *service;      /* Service name/number (eg TCP port, or NULL) */
 };
 typedef struct _virDomainEventGraphicsAddress virDomainEventGraphicsAddress;
 typedef virDomainEventGraphicsAddress *virDomainEventGraphicsAddressPtr;
@@ -2862,6 +2863,15 @@ int virDomainOpenConsole(virDomainPtr dom,
                          virStreamPtr st,
                          unsigned int flags);
 
+typedef enum {
+    VIR_DOMAIN_CONNECT_GRAPHICS_SKIPAUTH = (1 << 0),
+} virDomainConnectGraphicsFlags;
+
+int virDomainConnectGraphics(virDomainPtr dom,
+                             const char *devname,
+                             int fd,
+                             unsigned int flags);
+
 int virDomainInjectNMI(virDomainPtr domain, unsigned int flags);
 
 
diff --git a/src/driver.h b/src/driver.h
index 62bbc1d..cfa4a00 100644
--- a/src/driver.h
+++ b/src/driver.h
@@ -564,6 +564,11 @@ typedef int
                                const char *devname,
                                virStreamPtr st,
                                unsigned int flags);
+typedef int
+    (*virDrvDomainConnectGraphics)(virDomainPtr dom,
+                                   const char *devname,
+                                   int fd,
+                                   unsigned int flags);
 
 typedef int
     (*virDrvDomainInjectNMI)(virDomainPtr dom, unsigned int flags);
@@ -797,6 +802,7 @@ struct _virDriver {
     virDrvDomainSnapshotDelete domainSnapshotDelete;
     virDrvDomainQemuMonitorCommand qemuDomainMonitorCommand;
     virDrvDomainOpenConsole domainOpenConsole;
+    virDrvDomainConnectGraphics domainConnectGraphics;
     virDrvDomainInjectNMI domainInjectNMI;
     virDrvDomainMigrateBegin3	domainMigrateBegin3;
     virDrvDomainMigratePrepare3	domainMigratePrepare3;
diff --git a/src/libvirt.c b/src/libvirt.c
index c57e0c3..bbd4157 100644
--- a/src/libvirt.c
+++ b/src/libvirt.c
@@ -15548,3 +15548,53 @@ error:
     virDispatchError(dom->conn);
     return -1;
 }
+
+int virDomainConnectGraphics(virDomainPtr dom,
+                             const char *devname,
+                             int fd,
+                             unsigned int flags)
+{
+    virConnectPtr conn;
+
+    VIR_DOMAIN_DEBUG(dom, "devname=%s, fd=%d, flags=%u",
+                     NULLSTR(devname), fd, flags);
+
+    virResetLastError();
+
+    if (!VIR_IS_DOMAIN(dom)) {
+        virLibDomainError(VIR_ERR_INVALID_DOMAIN, __FUNCTION__);
+        virDispatchError(NULL);
+        return -1;
+    }
+
+    conn = dom->conn;
+
+    if (devname == NULL) {
+        virLibDomainError(VIR_ERR_INVALID_ARG, __FUNCTION__);
+        goto error;
+    }
+
+    if (fd < 0) {
+        virLibDomainError(VIR_ERR_INVALID_ARG, __FUNCTION__);
+        goto error;
+    }
+
+    if (dom->conn->flags & VIR_CONNECT_RO) {
+        virLibDomainError(VIR_ERR_OPERATION_DENIED, __FUNCTION__);
+        goto error;
+    }
+
+    if (dom->conn->driver->domainConnectGraphics) {
+        int ret;
+        ret = dom->conn->driver->domainConnectGraphics(dom, devname, fd, flags);
+        if (ret < 0)
+            goto error;
+        return ret;
+    }
+
+    virLibConnError(VIR_ERR_NO_SUPPORT, __FUNCTION__);
+
+error:
+    virDispatchError(dom->conn);
+    return -1;
+}
diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms
index 6f253ab..24f811b 100644
--- a/src/libvirt_private.syms
+++ b/src/libvirt_private.syms
@@ -950,6 +950,9 @@ virThreadIsSelf;
 virThreadJoin;
 virThreadSelf;
 virThreadSelfID;
+virThreadLocalGet;
+virThreadLocalInit;
+virThreadLocalSet;
 
 
 # usb.h
diff --git a/src/libvirt_public.syms b/src/libvirt_public.syms
index 39d4cae..2f4aa0b 100644
--- a/src/libvirt_public.syms
+++ b/src/libvirt_public.syms
@@ -467,6 +467,7 @@ LIBVIRT_0.9.3 {
         virEventUpdateTimeout;
         virNodeGetCPUStats;
         virNodeGetMemoryStats;
+        virDomainConnectGraphics;
 } LIBVIRT_0.9.2;
 
 # .... define new API here using predicted next version number ....
diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c
index 01587e8..cdbd2e6 100644
--- a/src/qemu/qemu_driver.c
+++ b/src/qemu/qemu_driver.c
@@ -8314,6 +8314,64 @@ qemuDomainGetBlockPullInfo(virDomainPtr dom, const char *path,
     return qemuDomainBlockPullImpl(dom, path, info, BLOCK_PULL_MODE_INFO);
 }
 
+static int
+qemuDomainConnectGraphics(virDomainPtr dom,
+                          const char *devname,
+                          int fd,
+                          unsigned int flags)
+{
+    struct qemud_driver *driver = dom->conn->privateData;
+    virDomainObjPtr vm = NULL;
+    char uuidstr[VIR_UUID_STRING_BUFLEN];
+    int ret = -1;
+    qemuDomainObjPrivatePtr priv;
+
+    virCheckFlags(VIR_DOMAIN_CONNECT_GRAPHICS_SKIPAUTH, -1);
+
+    qemuDriverLock(driver);
+    virUUIDFormat(dom->uuid, uuidstr);
+    vm = virDomainFindByUUID(&driver->domains, dom->uuid);
+    if (!vm) {
+        qemuReportError(VIR_ERR_NO_DOMAIN,
+                        _("no domain with matching uuid '%s'"), uuidstr);
+        goto cleanup;
+    }
+
+    if (!virDomainObjIsActive(vm)) {
+        qemuReportError(VIR_ERR_OPERATION_INVALID,
+                        "%s", _("domain is not running"));
+        goto cleanup;
+    }
+
+    priv = vm->privateData;
+
+    if (STRNEQ(devname, "vnc") &&
+        STRNEQ(devname, "spice")) {
+        qemuReportError(VIR_ERR_INTERNAL_ERROR,
+                        _("cannot find graphics device %s"),
+                        devname);
+        goto cleanup;
+    }
+
+    if (qemuDomainObjBeginJobWithDriver(driver, vm) < 0)
+        goto cleanup;
+    qemuDomainObjEnterMonitorWithDriver(driver, vm);
+    ret = qemuMonitorConnectGraphics(priv->mon, devname, fd, "graphicsfd",
+                                     flags & VIR_DOMAIN_CONNECT_GRAPHICS_SKIPAUTH);
+    qemuDomainObjExitMonitorWithDriver(driver, vm);
+    if (qemuDomainObjEndJob(vm) == 0) {
+        vm = NULL;
+        goto cleanup;
+    }
+
+cleanup:
+    if (vm)
+        virDomainObjUnlock(vm);
+    qemuDriverUnlock(driver);
+    return ret;
+}
+
+
 static virDriver qemuDriver = {
     .no = VIR_DRV_QEMU,
     .name = "QEMU",
@@ -8429,6 +8487,7 @@ static virDriver qemuDriver = {
     .domainSnapshotDelete = qemuDomainSnapshotDelete, /* 0.8.0 */
     .qemuDomainMonitorCommand = qemuDomainMonitorCommand, /* 0.8.3 */
     .domainOpenConsole = qemuDomainOpenConsole, /* 0.8.6 */
+    .domainConnectGraphics = qemuDomainConnectGraphics, /* 0.9.2 */
     .domainInjectNMI = qemuDomainInjectNMI, /* 0.9.2 */
     .domainMigrateBegin3 = qemuDomainMigrateBegin3, /* 0.9.2 */
     .domainMigratePrepare3 = qemuDomainMigratePrepare3, /* 0.9.2 */
diff --git a/src/qemu/qemu_monitor.c b/src/qemu/qemu_monitor.c
index 89a3f64..8259b44 100644
--- a/src/qemu/qemu_monitor.c
+++ b/src/qemu/qemu_monitor.c
@@ -2404,3 +2404,35 @@ int qemuMonitorBlockPull(qemuMonitorPtr mon,
         ret = qemuMonitorTextBlockPull(mon, path, info, mode);
     return ret;
 }
+
+int qemuMonitorConnectGraphics(qemuMonitorPtr mon,
+                               const char *devicestr,
+                               int fd,
+                               const char *fdname,
+                               bool skipauth)
+{
+    VIR_DEBUG("mon=%p device=%s fd=%d fdname=%s skipauth=%d",
+              mon, devicestr, fd, NULLSTR(fdname), skipauth);
+    int ret;
+
+    if (!mon) {
+        qemuReportError(VIR_ERR_INVALID_ARG, "%s",
+                        _("monitor must not be NULL"));
+        return -1;
+    }
+
+    if (qemuMonitorSendFileHandle(mon, fdname, fd) < 0)
+        return -1;
+
+    if (mon->json)
+        ret = qemuMonitorJSONConnectGraphics(mon, devicestr, fdname, skipauth);
+    else
+        ret = qemuMonitorTextConnectGraphics(mon, devicestr, fdname, skipauth);
+
+    if (ret < 0) {
+        if (qemuMonitorCloseFileHandle(mon, fdname) < 0)
+            VIR_WARN("failed to close device handle '%s'", fdname);
+    }
+
+    return ret;
+}
diff --git a/src/qemu/qemu_monitor.h b/src/qemu/qemu_monitor.h
index 3bb0269..3fc6ec6 100644
--- a/src/qemu/qemu_monitor.h
+++ b/src/qemu/qemu_monitor.h
@@ -462,6 +462,12 @@ int qemuMonitorBlockPull(qemuMonitorPtr mon,
                          virDomainBlockPullInfoPtr info,
                          int mode);
 
+int qemuMonitorConnectGraphics(qemuMonitorPtr mon,
+                               const char *devicestr,
+                               int fd,
+                               const char *fdname,
+                               bool skipauth);
+
 /**
  * When running two dd process and using <> redirection, we need a
  * shell that will not truncate files.  These two strings serve that
diff --git a/src/qemu/qemu_monitor_json.c b/src/qemu/qemu_monitor_json.c
index 56ec65b..f8bac98 100644
--- a/src/qemu/qemu_monitor_json.c
+++ b/src/qemu/qemu_monitor_json.c
@@ -615,8 +615,8 @@ static void qemuMonitorJSONHandleIOError(qemuMonitorPtr mon, virJSONValuePtr dat
 
 
 VIR_ENUM_DECL(qemuMonitorGraphicsAddressFamily)
-VIR_ENUM_IMPL(qemuMonitorGraphicsAddressFamily, VIR_DOMAIN_EVENT_GRAPHICS_ADDRESS_IPV6 + 1,
-              "ipv4", "ipv6");
+VIR_ENUM_IMPL(qemuMonitorGraphicsAddressFamily, VIR_DOMAIN_EVENT_GRAPHICS_ADDRESS_UNIX + 1,
+              "ipv4", "ipv6", "unix");
 
 static void qemuMonitorJSONHandleVNC(qemuMonitorPtr mon, virJSONValuePtr data, int phase)
 {
@@ -2814,3 +2814,30 @@ int qemuMonitorJSONBlockPull(qemuMonitorPtr mon,
     virJSONValueFree(reply);
     return ret;
 }
+
+int qemuMonitorJSONConnectGraphics(qemuMonitorPtr mon,
+                                   const char *devicestr,
+                                   const char *fdname,
+                                   bool skipauth)
+{
+    int ret;
+    virJSONValuePtr cmd, reply = NULL;
+
+    cmd = qemuMonitorJSONMakeCommand("add_client",
+                                     "s:protocol", devicestr,
+                                     "s:fdname", fdname,
+                                     "b:skipauth", skipauth,
+                                     NULL);
+
+    if (!cmd)
+        return -1;
+
+    ret = qemuMonitorJSONCommand(mon, cmd, &reply);
+
+    if (ret == 0)
+        ret = qemuMonitorJSONCheckError(cmd, reply);
+
+    virJSONValueFree(cmd);
+    virJSONValueFree(reply);
+    return ret;
+}
diff --git a/src/qemu/qemu_monitor_json.h b/src/qemu/qemu_monitor_json.h
index 393d8fc..468acb8 100644
--- a/src/qemu/qemu_monitor_json.h
+++ b/src/qemu/qemu_monitor_json.h
@@ -222,4 +222,9 @@ int qemuMonitorJSONBlockPull(qemuMonitorPtr mon,
                              virDomainBlockPullInfoPtr info,
                              int mode);
 
+int qemuMonitorJSONConnectGraphics(qemuMonitorPtr mon,
+                                   const char *devicestr,
+                                   const char *fdname,
+                                   bool skipauth);
+
 #endif /* QEMU_MONITOR_JSON_H */
diff --git a/src/qemu/qemu_monitor_text.c b/src/qemu/qemu_monitor_text.c
index a16ea91..f11b58f 100644
--- a/src/qemu/qemu_monitor_text.c
+++ b/src/qemu/qemu_monitor_text.c
@@ -2906,3 +2906,34 @@ cleanup:
     VIR_FREE(reply);
     return ret;
 }
+
+int qemuMonitorTextConnectGraphics(qemuMonitorPtr mon,
+                                   const char *devicestr,
+                                   const char *fdname,
+                                   bool skipauth)
+{
+    char *cmd = NULL;
+    char *reply = NULL;
+    int ret = -1;
+
+    if (virAsprintf(&cmd, "add_client %s %s %d", devicestr, fdname, skipauth ? 0 : 1) < 0){
+        virReportOOMError();
+        goto cleanup;
+    }
+
+    if (qemuMonitorHMPCommand(mon, cmd, &reply) < 0) {
+        qemuReportError(VIR_ERR_OPERATION_FAILED,
+                        "%s", _("adding graphics client failed"));
+        goto cleanup;
+    }
+
+    if (STRNEQ(reply, ""))
+        goto cleanup;
+
+    ret = 0;
+
+cleanup:
+    VIR_FREE(reply);
+    VIR_FREE(cmd);
+    return ret;
+}
diff --git a/src/qemu/qemu_monitor_text.h b/src/qemu/qemu_monitor_text.h
index 4fa5064..12efe4f 100644
--- a/src/qemu/qemu_monitor_text.h
+++ b/src/qemu/qemu_monitor_text.h
@@ -215,4 +215,9 @@ int qemuMonitorTextBlockPull(qemuMonitorPtr mon,
                              virDomainBlockPullInfoPtr info,
                              int mode);
 
+int qemuMonitorTextConnectGraphics(qemuMonitorPtr mon,
+                                   const char *devicestr,
+                                   const char *fdname,
+                                   bool skipauth);
+
 #endif /* QEMU_MONITOR_TEXT_H */
diff --git a/src/remote/remote_driver.c b/src/remote/remote_driver.c
index d7ce76e..688e65d 100644
--- a/src/remote/remote_driver.c
+++ b/src/remote/remote_driver.c
@@ -83,6 +83,7 @@
 #include "ignore-value.h"
 #include "files.h"
 #include "command.h"
+#include "passfd.h"
 
 #define VIR_FROM_THIS VIR_FROM_REMOTE
 
@@ -128,6 +129,8 @@ struct remote_thread_call {
     unsigned int serial;
     unsigned int proc_nr;
 
+    int fd;
+
     virCond cond;
 
     int want_reply;
@@ -173,6 +176,7 @@ struct private_data {
     int errfd;                /* File handle connected to remote stderr */
     int watch;                  /* File handle watch */
     pid_t pid;                  /* PID of tunnel process */
+    int hasSendFD;              /* Whether sendfd is possible */
     int uses_tls;               /* TLS enabled on socket? */
     int is_secure;              /* Secure if TLS or SASL or UNIX sockets */
     gnutls_session_t session;   /* GnuTLS session (if uses_tls != 0). */
@@ -240,6 +244,10 @@ static int call (virConnectPtr conn, struct private_data *priv,
                  int flags, int proc_nr,
                  xdrproc_t args_filter, char *args,
                  xdrproc_t ret_filter, char *ret);
+static int callWithFD(virConnectPtr conn, struct private_data *priv,
+                      int flags, int fd, int proc_nr,
+                      xdrproc_t args_filter, char *args,
+                      xdrproc_t ret_filter, char *ret);
 static int remoteAuthenticate (virConnectPtr conn, struct private_data *priv, int in_open,
                                virConnectAuthPtr auth, const char *authtype);
 #if HAVE_SASL
@@ -722,6 +730,7 @@ doRemoteOpen (virConnectPtr conn,
             addr.sun_path[0] = '\0';
 
       autostart_retry:
+        priv->hasSendFD = 1;
         priv->is_secure = 1;
         priv->sock = socket (AF_UNIX, SOCK_STREAM, 0);
         if (priv->sock == -1) {
@@ -4603,6 +4612,38 @@ done:
     return rv;
 }
 
+
+static int
+remoteDomainConnectGraphics(virDomainPtr dom,
+                            const char *devicestr,
+                            int fd,
+                            unsigned int flags)
+{
+    struct private_data *priv = dom->conn->privateData;
+    int rv = -1;
+    remote_domain_connect_graphics_args args;
+
+    remoteDriverLock(priv);
+
+    make_nonnull_domain (&args.dom, dom);
+    args.devicestr = (char*)devicestr;
+    args.flags = flags;
+
+    if (callWithFD(dom->conn, priv, 0, fd, REMOTE_PROC_DOMAIN_CONNECT_GRAPHICS,
+                   (xdrproc_t) xdr_remote_domain_connect_graphics_args, (char *) &args,
+                   (xdrproc_t) xdr_void, NULL) == -1) {
+        goto done;
+    }
+
+    rv = 0;
+
+done:
+    remoteDriverUnlock(priv);
+
+    return rv;
+}
+
+
 /*----------------------------------------------------------------------*/
 
 static int
@@ -4995,6 +5036,7 @@ done:
 static struct remote_thread_call *
 prepareCall(struct private_data *priv,
             int flags,
+            int fd,
             int proc_nr,
             xdrproc_t args_filter, char *args,
             xdrproc_t ret_filter, char *ret)
@@ -5021,6 +5063,7 @@ prepareCall(struct private_data *priv,
     rv->ret_filter = ret_filter;
     rv->ret = ret;
     rv->want_reply = 1;
+    rv->fd = fd;
 
     if (flags & REMOTE_CALL_QEMU) {
         hdr.prog = QEMU_PROGRAM;
@@ -5031,7 +5074,7 @@ prepareCall(struct private_data *priv,
         hdr.vers = REMOTE_PROTOCOL_VERSION;
     }
     hdr.proc = proc_nr;
-    hdr.type = REMOTE_CALL;
+    hdr.type = fd == -1 ? REMOTE_CALL : REMOTE_CALL_WITH_FD;
     hdr.serial = rv->serial;
     hdr.status = REMOTE_OK;
 
@@ -5221,6 +5264,21 @@ remoteIOWriteMessage(struct private_data *priv,
         if (priv->saslEncodedOffset == priv->saslEncodedLength) {
             priv->saslEncoded = NULL;
             priv->saslEncodedOffset = priv->saslEncodedLength = 0;
+
+            if (thecall->fd != -1) {
+                if (!priv->hasSendFD) {
+                    remoteError(VIR_ERR_INTERNAL_ERROR, "%s",
+                                _("Unable to send file descriptors over this connection"));
+                    return -1;
+                }
+                if (sendfd(priv->sock, thecall->fd) < 0) {
+                    virReportSystemError(errno,
+                                         _("Failed to send file descriptor %d"),
+                                         thecall->fd);
+                    return -1;
+                }
+            }
+
             if (thecall->want_reply)
                 thecall->mode = REMOTE_MODE_WAIT_RX;
             else
@@ -5238,6 +5296,21 @@ remoteIOWriteMessage(struct private_data *priv,
 
         if (thecall->bufferOffset == thecall->bufferLength) {
             thecall->bufferOffset = thecall->bufferLength = 0;
+
+            if (thecall->fd != -1) {
+                if (!priv->hasSendFD) {
+                    remoteError(VIR_ERR_INTERNAL_ERROR, "%s",
+                                _("Unable to send file descriptors over this connection"));
+                    return -1;
+                }
+                if (sendfd(priv->sock, thecall->fd) < 0) {
+                    virReportSystemError(errno,
+                                         _("Failed to send file descriptor %d"),
+                                         thecall->fd);
+                    return -1;
+                }
+            }
+
             if (thecall->want_reply)
                 thecall->mode = REMOTE_MODE_WAIT_RX;
             else
@@ -6181,7 +6254,31 @@ call (virConnectPtr conn, struct private_data *priv,
     struct remote_thread_call *thiscall;
     int rv;
 
-    thiscall = prepareCall(priv, flags, proc_nr, args_filter, args,
+    thiscall = prepareCall(priv, flags, -1, proc_nr, args_filter, args,
+                           ret_filter, ret);
+
+    if (!thiscall) {
+        return -1;
+    }
+
+    rv = remoteIO(conn, priv, flags, thiscall);
+    ignore_value(virCondDestroy(&thiscall->cond));
+    VIR_FREE(thiscall);
+    return rv;
+}
+
+
+static int
+callWithFD(virConnectPtr conn, struct private_data *priv,
+           int flags, int fd,
+           int proc_nr,
+           xdrproc_t args_filter, char *args,
+           xdrproc_t ret_filter, char *ret)
+{
+    struct remote_thread_call *thiscall;
+    int rv;
+
+    thiscall = prepareCall(priv, flags, fd, proc_nr, args_filter, args,
                            ret_filter, ret);
 
     if (!thiscall) {
@@ -6525,6 +6622,7 @@ static virDriver remote_driver = {
     .domainSnapshotDelete = remoteDomainSnapshotDelete, /* 0.8.0 */
     .qemuDomainMonitorCommand = remoteQemuDomainMonitorCommand, /* 0.8.3 */
     .domainOpenConsole = remoteDomainOpenConsole, /* 0.8.6 */
+    .domainConnectGraphics = remoteDomainConnectGraphics, /* 0.9.2 */
     .domainInjectNMI = remoteDomainInjectNMI, /* 0.9.2 */
     .domainMigrateBegin3 = remoteDomainMigrateBegin3, /* 0.9.2 */
     .domainMigratePrepare3 = remoteDomainMigratePrepare3, /* 0.9.2 */
diff --git a/src/remote/remote_protocol.x b/src/remote/remote_protocol.x
index 3f8f006..3f40b1c 100644
--- a/src/remote/remote_protocol.x
+++ b/src/remote/remote_protocol.x
@@ -2038,6 +2038,12 @@ struct remote_domain_open_console_args {
     unsigned int flags;
 };
 
+struct remote_domain_connect_graphics_args {
+    remote_nonnull_domain dom;
+    remote_nonnull_string devicestr;
+    unsigned int flags;
+};
+
 struct remote_storage_vol_upload_args {
     remote_nonnull_storage_vol vol;
     unsigned hyper offset;
@@ -2425,7 +2431,8 @@ enum remote_procedure {
     REMOTE_PROC_DOMAIN_BLOCK_PULL_ABORT = 231, /* autogen autogen */
     REMOTE_PROC_DOMAIN_GET_BLOCK_PULL_INFO = 232, /* autogen autogen */
     REMOTE_PROC_DOMAIN_EVENT_BLOCK_PULL = 233, /* skipgen skipgen */
-    REMOTE_PROC_DOMAIN_GET_CONTROL_INFO = 234 /* autogen autogen */
+    REMOTE_PROC_DOMAIN_GET_CONTROL_INFO = 234, /* autogen autogen */
+    REMOTE_PROC_DOMAIN_CONNECT_GRAPHICS = 235 /* skipgen skipgen */
 
     /*
      * Notice how the entries are grouped in sets of 10 ?
@@ -2453,6 +2460,7 @@ enum remote_procedure {
  *    Length  | int                   | Total number of bytes in message _including_ length.
  *    Header  | remote_message_header | Control information about procedure call
  *    Payload | -                     | Variable payload data per procedure
+ *    FD      | SCM_CREDS             | A file descriptor OOM
  *
  * In header, the 'serial' field varies according to:
  *
@@ -2468,6 +2476,9 @@ enum remote_procedure {
  *  - type == REMOTE_STREAM
  *      * serial matches that from the corresponding REMOTE_CALL
  *
+ *  - type == REMOTE_CALL_WITH_FD
+ *      * As REMOTE_CALL, but a FD follows the payload
+ *
  * and the 'status' field varies according to:
  *
  *  - type == REMOTE_CALL
@@ -2485,6 +2496,9 @@ enum remote_procedure {
  *     * REMOTE_OK if stream is complete
  *     * REMOTE_ERROR if stream had an error
  *
+ *  - type == REMOTE_CALL_WITH_FD
+ *     * As with REMOTE_CALL
+ *
  * Payload varies according to type and status:
  *
  *  - type == REMOTE_CALL
@@ -2509,6 +2523,9 @@ enum remote_procedure {
  *          remote_error error information
  *     * status == REMOTE_OK
  *          <empty>
+ *
+ *  - type == REMOTE_CALL_WITH_FD
+ *     * As with REMOTE_CALL, but SCM_CREDS data has a following FD
  */
 enum remote_message_type {
     /* client -> server. args from a method call */
@@ -2518,7 +2535,9 @@ enum remote_message_type {
     /* either direction. async notification */
     REMOTE_MESSAGE = 2,
     /* either direction. stream data packet */
-    REMOTE_STREAM = 3
+    REMOTE_STREAM = 3,
+    /* passing a file descriptor */
+    REMOTE_CALL_WITH_FD = 4
 };
 
 enum remote_message_status {
-- 
1.7.4.4




More information about the libvir-list mailing list