diff mbox

[RFC,02/14] Add vhost-user-backend

Message ID 1465076003-26291-3-git-send-email-marcandre.lureau@redhat.com (mailing list archive)
State New, archived
Headers show

Commit Message

Marc-André Lureau June 4, 2016, 9:33 p.m. UTC
From: Marc-André Lureau <marcandre.lureau@redhat.com>

Create a vhost-user-backend object that holds a connection to a
vhost-user backend and can be referenced from virtio devices that
support it.

Currently, you may specify the executable to spawn directly from command
line, ex: -object vhost-user-backend,id=vui,cmd="./vhost-user-input
/dev/input.."

It may be considered a security breach to allow creating processes that
may execute arbitrary executables, so this may be restricted to some
known executables or directoy. If not acceptable, the object may just
take use a socket chardev instead (like vhost-user-net today).

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 backends/Makefile.objs              |   2 +
 backends/vhost-user.c               | 262 ++++++++++++++++++++++++++++++++++++
 include/sysemu/vhost-user-backend.h |  65 +++++++++
 3 files changed, 329 insertions(+)
 create mode 100644 backends/vhost-user.c
 create mode 100644 include/sysemu/vhost-user-backend.h
diff mbox

Patch

diff --git a/backends/Makefile.objs b/backends/Makefile.objs
index 31a3a89..902addd 100644
--- a/backends/Makefile.objs
+++ b/backends/Makefile.objs
@@ -9,3 +9,5 @@  common-obj-$(CONFIG_TPM) += tpm.o
 
 common-obj-y += hostmem.o hostmem-ram.o
 common-obj-$(CONFIG_LINUX) += hostmem-file.o
+
+common-obj-y += vhost-user.o
diff --git a/backends/vhost-user.c b/backends/vhost-user.c
new file mode 100644
index 0000000..89121ed
--- /dev/null
+++ b/backends/vhost-user.c
@@ -0,0 +1,262 @@ 
+/*
+ * QEMU vhost-user backend
+ *
+ * Copyright (C) 2016 Red Hat Inc
+ *
+ * Authors:
+ *  Marc-André Lureau <marcandre.lureau@redhat.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+
+#include "qemu/osdep.h"
+#include "hw/qdev.h"
+#include "qapi/error.h"
+#include "qemu/error-report.h"
+#include "qom/object_interfaces.h"
+#include "sysemu/vhost-user-backend.h"
+#include "sysemu/char.h"
+#include "sysemu/kvm.h"
+#include "io/channel-command.h"
+#include "hw/virtio/virtio-bus.h"
+
+static bool
+ioeventfd_enabled(void)
+{
+    return kvm_enabled() && kvm_eventfds_enabled();
+}
+
+int
+vhost_user_backend_dev_init(VhostUserBackend *b, VirtIODevice *vdev,
+                            unsigned nvqs, Error **errp)
+{
+    int ret;
+
+    assert(!b->vdev);
+
+    if (!ioeventfd_enabled()) {
+        error_setg(errp, "vhost initialization failed: requires kvm");
+        return -1;
+    }
+
+    b->vdev = vdev;
+    b->dev.nvqs = nvqs;
+    b->dev.vqs = g_new(struct vhost_virtqueue, nvqs);
+
+    ret = vhost_dev_init(&b->dev, b->chr, VHOST_BACKEND_TYPE_USER);
+    if (ret < 0) {
+        error_setg(errp, "vhost initialization failed: %s", strerror(-ret));
+        return -1;
+    }
+
+    return 0;
+}
+
+void
+vhost_user_backend_start(VhostUserBackend *b)
+{
+    BusState *qbus = BUS(qdev_get_parent_bus(DEVICE(b->vdev)));
+    VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus);
+    int ret;
+
+    if (b->started)
+        return;
+
+    if (!k->set_guest_notifiers) {
+        error_report("binding does not support guest notifiers");
+        return;
+    }
+
+    ret = vhost_dev_enable_notifiers(&b->dev, b->vdev);
+    if (ret < 0) {
+        return;
+    }
+
+    b->dev.acked_features = b->vdev->guest_features;
+    ret = vhost_dev_start(&b->dev, b->vdev);
+    if (ret < 0) {
+        error_report("Error start vhost dev");
+        goto err_notifiers;
+    }
+
+    ret = k->set_guest_notifiers(qbus->parent, b->dev.nvqs, true);
+    if (ret < 0) {
+        error_report("Error binding guest notifier");
+        goto err_vhost_stop;
+    }
+
+    b->started = true;
+    return;
+
+err_vhost_stop:
+    vhost_dev_stop(&b->dev, b->vdev);
+err_notifiers:
+    vhost_dev_disable_notifiers(&b->dev, b->vdev);
+}
+
+void
+vhost_user_backend_stop(VhostUserBackend *b)
+{
+    BusState *qbus = BUS(qdev_get_parent_bus(DEVICE(b->vdev)));
+    VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus);
+    int ret = 0;
+
+    if (!b->started)
+        return;
+
+    if (k->set_guest_notifiers) {
+        ret = k->set_guest_notifiers(qbus->parent,
+                                     b->dev.nvqs, false);
+        if (ret < 0) {
+            error_report("vhost guest notifier cleanup failed: %d", ret);
+        }
+    }
+
+    vhost_dev_stop(&b->dev, b->vdev);
+    vhost_dev_disable_notifiers(&b->dev, b->vdev);
+
+    b->started = false;
+}
+
+static int
+vhost_user_backend_spawn_cmd(VhostUserBackend *b, int vhostfd, Error **errp)
+{
+    int devnull = open("/dev/null", O_RDWR);
+    pid_t pid;
+
+    assert(b->cmd);
+    assert(!b->child);
+
+    if (devnull < 0) {
+        error_setg_errno(errp, errno, "Unable to open /dev/null");
+        return -1;
+    }
+
+    pid = qemu_fork(errp);
+    if (pid < 0) {
+        close(devnull);
+        return -1;
+    }
+
+    if (pid == 0) { /* child */
+        int fd, maxfd = sysconf(_SC_OPEN_MAX);
+
+        dup2(devnull, STDIN_FILENO);
+        dup2(devnull, STDOUT_FILENO);
+        dup2(vhostfd, 3);
+
+        for (fd = 4; fd < maxfd; fd++) {
+            close(fd);
+        }
+
+        execlp("/bin/sh", "sh", "-c", b->cmd, NULL);
+        _exit(1);
+    }
+
+    b->child = QIO_CHANNEL(qio_channel_command_new_pid(devnull, devnull, pid));
+
+    return 0;
+}
+
+static void
+vhost_user_backend_complete(UserCreatable *uc, Error **errp)
+{
+    VhostUserBackend *b = VHOST_USER_BACKEND(uc);
+    int sv[2];
+
+    if (socketpair(PF_UNIX, SOCK_STREAM, 0, sv) == -1) {
+        error_setg_errno(errp, errno, "socketpair() failed");
+        return;
+    }
+
+    b->chr = qemu_chr_open_socket(sv[0], errp);
+    if (!b->chr) {
+        return;
+    }
+
+    vhost_user_backend_spawn_cmd(b, sv[1], errp);
+
+    close(sv[1]);
+
+    /* could vhost_dev_init() happen here, so early vhost-user message
+     * can be exchanged */
+    b->dev.opaque = b->chr;
+}
+
+static char *get_cmd(Object *obj, Error **errp)
+{
+    VhostUserBackend *b = VHOST_USER_BACKEND(obj);
+
+    return g_strdup(b->cmd);
+}
+
+static void set_cmd(Object *obj, const char *str, Error **errp)
+{
+    VhostUserBackend *b = VHOST_USER_BACKEND(obj);
+
+    if (b->child) {
+        error_setg(errp, "cannot change property value");
+        return;
+    }
+
+    g_free(b->cmd);
+    b->cmd = g_strdup(str);
+}
+
+static void vhost_user_backend_init(Object *obj)
+{
+    object_property_add_str(obj, "cmd", get_cmd, set_cmd, NULL);
+}
+
+static void vhost_user_backend_finalize(Object *obj)
+{
+    VhostUserBackend *b = VHOST_USER_BACKEND(obj);
+
+    g_free(b->cmd);
+
+    if (b->chr) {
+        qemu_chr_delete(b->chr);
+    }
+
+    if (b->child) {
+        object_unref(OBJECT(b->child));
+    }
+}
+
+static bool
+vhost_user_backend_can_be_deleted(UserCreatable *uc, Error **errp)
+{
+    return true;
+}
+
+static void
+vhost_user_backend_class_init(ObjectClass *oc, void *data)
+{
+    UserCreatableClass *ucc = USER_CREATABLE_CLASS(oc);
+
+    ucc->complete = vhost_user_backend_complete;
+    ucc->can_be_deleted = vhost_user_backend_can_be_deleted;
+}
+
+static const TypeInfo vhost_user_backend_info = {
+    .name = TYPE_VHOST_USER_BACKEND,
+    .parent = TYPE_OBJECT,
+    .instance_size = sizeof(VhostUserBackend),
+    .instance_init = vhost_user_backend_init,
+    .instance_finalize = vhost_user_backend_finalize,
+    .class_size = sizeof(VhostUserBackendClass),
+    .class_init = vhost_user_backend_class_init,
+    .interfaces = (InterfaceInfo[]) {
+        { TYPE_USER_CREATABLE },
+        { }
+    }
+};
+
+static void register_types(void)
+{
+    type_register_static(&vhost_user_backend_info);
+}
+
+type_init(register_types);
diff --git a/include/sysemu/vhost-user-backend.h b/include/sysemu/vhost-user-backend.h
new file mode 100644
index 0000000..ab7cdbc
--- /dev/null
+++ b/include/sysemu/vhost-user-backend.h
@@ -0,0 +1,65 @@ 
+/*
+ * QEMU vhost-user backend
+ *
+ * Copyright (C) 2016 Red Hat Inc
+ *
+ * Authors:
+ *  Marc-André Lureau <marcandre.lureau@redhat.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+#ifndef QEMU_VHOST_USER_BACKEND_H
+#define QEMU_VHOST_USER_BACKEND_H
+
+#include "qom/object.h"
+#include "exec/memory.h"
+#include "qemu/option.h"
+#include "qemu/bitmap.h"
+#include "hw/virtio/vhost.h"
+#include "io/channel.h"
+
+#define TYPE_VHOST_USER_BACKEND "vhost-user-backend"
+#define VHOST_USER_BACKEND(obj) \
+    OBJECT_CHECK(VhostUserBackend, (obj), TYPE_VHOST_USER_BACKEND)
+#define VHOST_USER_BACKEND_GET_CLASS(obj) \
+    OBJECT_GET_CLASS(VhostUserBackendClass, (obj), TYPE_VHOST_USER_BACKEND)
+#define VHOST_USER_BACKEND_CLASS(klass) \
+    OBJECT_CLASS_CHECK(VhostUserBackendClass, (klass), TYPE_VHOST_USER_BACKEND)
+
+typedef struct VhostUserBackend VhostUserBackend;
+typedef struct VhostUserBackendClass VhostUserBackendClass;
+
+/**
+ * VhostUserBackendClass:
+ * @parent_class: opaque parent class container
+ */
+struct VhostUserBackendClass {
+    ObjectClass parent_class;
+};
+
+/**
+ * @VhostUserBackend
+ *
+ * @parent: opaque parent object container
+ */
+struct VhostUserBackend {
+    /* private */
+    Object parent;
+
+    char *cmd;
+
+    CharDriverState *chr;
+    struct vhost_dev dev;
+    QIOChannel *child;
+    VirtIODevice *vdev;
+
+    bool started;
+};
+
+int vhost_user_backend_dev_init(VhostUserBackend *b, VirtIODevice *vdev,
+                                unsigned nvqs, Error **errp);
+void vhost_user_backend_start(VhostUserBackend *b);
+void vhost_user_backend_stop(VhostUserBackend *b);
+
+#endif