qemu-devel.nongnu.org archive mirror
 help / color / mirror / Atom feed
* [RFC 0/3] tests/vhost-user-fs-test: add vhost-user-fs test case
@ 2019-10-25 10:01 Stefan Hajnoczi
  2019-10-25 10:01 ` [RFC 1/3] WIP virtiofsd: import Linux <fuse.h> header file Stefan Hajnoczi
                   ` (2 more replies)
  0 siblings, 3 replies; 11+ messages in thread
From: Stefan Hajnoczi @ 2019-10-25 10:01 UTC (permalink / raw)
  To: qemu-devel
  Cc: Laurent Vivier, Thomas Huth, Michael S. Tsirkin, Cornelia Huck,
	Dr. David Alan Gilbert, virtio-fs, Stefan Hajnoczi,
	Paolo Bonzini, vgoyal

This patch series adds an automated test for the vhost-user-fs device using
virtiofsd.  This makes low-level testing of FUSE messages and even VIRTIO
possible.  These things can be hard to do inside a normal guest environment
where such tests require building kernel modules and can result in kernel
panics.

To get a feel for how vhost-user-fs-test.c test cases work, here is an example:

  /* Create file on host and check its contents and metadata in guest */
  static void test_file_from_host(void *parent, void *arg, QGuestAllocator *alloc)
  {
      g_autofree gchar *filename = g_strdup_printf("%s/%s", shared_dir, "foo");
      const char *str = "This is a test\n";
      char buf[strlen(str)];
      QVirtioFS *vfs = parent;
      struct fuse_entry_out entry;
      int32_t error;
      uint64_t nodeid;
      uint64_t fh;
      ssize_t nread;
      gboolean ok;

      SKIP_TEST_IF_CROSS_ENDIAN();

      /* Create the test file in the shared directory */
      ok = g_file_set_contents(filename, str, strlen(str), NULL);
      g_assert(ok);

      fuse_init(vfs);

      error = fuse_lookup(vfs, FUSE_ROOT_ID, "foo", &entry);
      g_assert_cmpint(error, ==, 0);
      g_assert_cmpint(guest64(entry.attr.size), ==, strlen(str));
      nodeid = guest64(entry.nodeid);

      error = fuse_open(vfs, nodeid, O_RDONLY, &fh);
      g_assert_cmpint(error, ==, 0);

      nread = fuse_read(vfs, fh, 0, buf, sizeof(buf));
      g_assert_cmpint(nread, ==, sizeof(buf));
      g_assert_cmpint(memcmp(buf, str, sizeof(buf)), ==, 0);

      fuse_release(vfs, fh);
      fuse_forget(vfs, nodeid);
  }

This patch series is based on "[PATCH v4 00/16] libqos: add VIRTIO PCI 1.0
support" and the https://gitlab.com/virtio-fs/qemu virtio-fs-dev branch.  I
expect conflicts and will resend again once these dependencies have landed in
qemu.git/master.

Stefan Hajnoczi (3):
  WIP virtiofsd: import Linux <fuse.h> header file
  qgraph: add an "after" test callback function
  tests/vhost-user-fs-test: add vhost-user-fs test case

 tests/Makefile.include                        |   8 +-
 contrib/virtiofsd/fuse_lowlevel.h             |   2 +-
 .../standard-headers/linux/fuse.h             |   0
 tests/libqos/qgraph.h                         |   2 +
 tests/libqos/qgraph_internal.h                |   1 +
 tests/libqos/virtio-fs.h                      |  46 ++
 contrib/virtiofsd/fuse_loop_mt.c              |   2 +-
 contrib/virtiofsd/fuse_lowlevel.c             |   2 +-
 contrib/virtiofsd/fuse_virtio.c               |   2 +-
 tests/libqos/qgraph.c                         |   1 +
 tests/libqos/virtio-fs.c                      | 104 +++
 tests/qos-test.c                              |   6 +
 tests/vhost-user-fs-test.c                    | 660 ++++++++++++++++++
 scripts/update-linux-headers.sh               |   3 +-
 14 files changed, 832 insertions(+), 7 deletions(-)
 rename contrib/virtiofsd/fuse_kernel.h => include/standard-headers/linux/fuse.h (100%)
 create mode 100644 tests/libqos/virtio-fs.h
 create mode 100644 tests/libqos/virtio-fs.c
 create mode 100644 tests/vhost-user-fs-test.c

-- 
2.21.0



^ permalink raw reply	[flat|nested] 11+ messages in thread

* [RFC 1/3] WIP virtiofsd: import Linux <fuse.h> header file
  2019-10-25 10:01 [RFC 0/3] tests/vhost-user-fs-test: add vhost-user-fs test case Stefan Hajnoczi
@ 2019-10-25 10:01 ` Stefan Hajnoczi
  2019-10-26 21:49   ` Michael S. Tsirkin
  2019-10-25 10:01 ` [RFC 2/3] qgraph: add an "after" test callback function Stefan Hajnoczi
  2019-10-25 10:01 ` [RFC 3/3] tests/vhost-user-fs-test: add vhost-user-fs test case Stefan Hajnoczi
  2 siblings, 1 reply; 11+ messages in thread
From: Stefan Hajnoczi @ 2019-10-25 10:01 UTC (permalink / raw)
  To: qemu-devel
  Cc: Laurent Vivier, Thomas Huth, Michael S. Tsirkin, Cornelia Huck,
	Dr. David Alan Gilbert, virtio-fs, Stefan Hajnoczi,
	Paolo Bonzini, vgoyal

tests/vhost-user-fs-test.c needs fuse.h.  The private copy that
virtiofsd has can be replaced with a properly imported file using
update-linux-headers.sh.

TODO rerun update-linux-headers.sh with upstream kernel tree!

Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
---
 contrib/virtiofsd/fuse_lowlevel.h                              | 2 +-
 .../fuse_kernel.h => include/standard-headers/linux/fuse.h     | 0
 contrib/virtiofsd/fuse_loop_mt.c                               | 2 +-
 contrib/virtiofsd/fuse_lowlevel.c                              | 2 +-
 contrib/virtiofsd/fuse_virtio.c                                | 2 +-
 scripts/update-linux-headers.sh                                | 3 ++-
 6 files changed, 6 insertions(+), 5 deletions(-)
 rename contrib/virtiofsd/fuse_kernel.h => include/standard-headers/linux/fuse.h (100%)

diff --git a/contrib/virtiofsd/fuse_lowlevel.h b/contrib/virtiofsd/fuse_lowlevel.h
index 79fb30a1c2..a8c92ff7e0 100644
--- a/contrib/virtiofsd/fuse_lowlevel.h
+++ b/contrib/virtiofsd/fuse_lowlevel.h
@@ -23,7 +23,7 @@
 #endif
 
 #include "fuse_common.h"
-#include "fuse_kernel.h"
+#include "standard-headers/linux/fuse.h"
 
 #include <utime.h>
 #include <fcntl.h>
diff --git a/contrib/virtiofsd/fuse_kernel.h b/include/standard-headers/linux/fuse.h
similarity index 100%
rename from contrib/virtiofsd/fuse_kernel.h
rename to include/standard-headers/linux/fuse.h
diff --git a/contrib/virtiofsd/fuse_loop_mt.c b/contrib/virtiofsd/fuse_loop_mt.c
index 2000a8902a..af7b501fac 100644
--- a/contrib/virtiofsd/fuse_loop_mt.c
+++ b/contrib/virtiofsd/fuse_loop_mt.c
@@ -11,7 +11,7 @@
 #include "fuse_i.h"
 #include "fuse_lowlevel.h"
 #include "fuse_misc.h"
-#include "fuse_kernel.h"
+#include "standard-headers/linux/fuse.h"
 #include "fuse_virtio.h"
 
 #include <stdio.h>
diff --git a/contrib/virtiofsd/fuse_lowlevel.c b/contrib/virtiofsd/fuse_lowlevel.c
index 78ccfe3a27..c1a901cb4d 100644
--- a/contrib/virtiofsd/fuse_lowlevel.c
+++ b/contrib/virtiofsd/fuse_lowlevel.c
@@ -10,7 +10,7 @@
 */
 
 #include "fuse_i.h"
-#include "fuse_kernel.h"
+#include "standard-headers/linux/fuse.h"
 #include "fuse_opt.h"
 #include "fuse_misc.h"
 #include "fuse_virtio.h"
diff --git a/contrib/virtiofsd/fuse_virtio.c b/contrib/virtiofsd/fuse_virtio.c
index 533ef24bb7..7a0d0b2603 100644
--- a/contrib/virtiofsd/fuse_virtio.c
+++ b/contrib/virtiofsd/fuse_virtio.c
@@ -15,7 +15,7 @@
 #include "qapi/error.h"
 
 #include "fuse_i.h"
-#include "fuse_kernel.h"
+#include "standard-headers/linux/fuse.h"
 #include "fuse_misc.h"
 #include "fuse_opt.h"
 #include "fuse_virtio.h"
diff --git a/scripts/update-linux-headers.sh b/scripts/update-linux-headers.sh
index f76d77363b..1a627ccd73 100755
--- a/scripts/update-linux-headers.sh
+++ b/scripts/update-linux-headers.sh
@@ -184,7 +184,8 @@ EOF
 
 rm -rf "$output/include/standard-headers/linux"
 mkdir -p "$output/include/standard-headers/linux"
-for i in "$tmpdir"/include/linux/*virtio*.h \
+for i in "$tmpdir/include/linux/fuse.h" \
+         "$tmpdir"/include/linux/*virtio*.h \
          "$tmpdir/include/linux/qemu_fw_cfg.h" \
          "$tmpdir/include/linux/input.h" \
          "$tmpdir/include/linux/input-event-codes.h" \
-- 
2.21.0



^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [RFC 2/3] qgraph: add an "after" test callback function
  2019-10-25 10:01 [RFC 0/3] tests/vhost-user-fs-test: add vhost-user-fs test case Stefan Hajnoczi
  2019-10-25 10:01 ` [RFC 1/3] WIP virtiofsd: import Linux <fuse.h> header file Stefan Hajnoczi
@ 2019-10-25 10:01 ` Stefan Hajnoczi
  2019-10-25 10:01 ` [RFC 3/3] tests/vhost-user-fs-test: add vhost-user-fs test case Stefan Hajnoczi
  2 siblings, 0 replies; 11+ messages in thread
From: Stefan Hajnoczi @ 2019-10-25 10:01 UTC (permalink / raw)
  To: qemu-devel
  Cc: Laurent Vivier, Thomas Huth, Michael S. Tsirkin, Cornelia Huck,
	Dr. David Alan Gilbert, virtio-fs, Stefan Hajnoczi,
	Paolo Bonzini, vgoyal

Add a callback that runs after a test completes.  This will be used for
cleanup.

Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
---
 tests/libqos/qgraph.h          | 2 ++
 tests/libqos/qgraph_internal.h | 1 +
 tests/libqos/qgraph.c          | 1 +
 tests/qos-test.c               | 6 ++++++
 4 files changed, 10 insertions(+)

diff --git a/tests/libqos/qgraph.h b/tests/libqos/qgraph.h
index 3a25dda4b2..5e73a00e4a 100644
--- a/tests/libqos/qgraph.h
+++ b/tests/libqos/qgraph.h
@@ -47,6 +47,7 @@ typedef void (*QOSStartFunct) (QOSGraphObject *object);
 
 /* Test options functions */
 typedef void *(*QOSBeforeTest) (GString *cmd_line, void *arg);
+typedef void (*QOSAfterTest) (void *arg);
 
 /**
  * SECTION: qgraph.h
@@ -341,6 +342,7 @@ struct QOSGraphTestOptions {
                                  * additional parameters to the command line
                                  * and modify the argument to the test function.
                                  */
+    QOSAfterTest after;         /* executed after the test */
     bool subprocess;            /* run the test in a subprocess */
 };
 
diff --git a/tests/libqos/qgraph_internal.h b/tests/libqos/qgraph_internal.h
index f4734c8681..5e1131f06c 100644
--- a/tests/libqos/qgraph_internal.h
+++ b/tests/libqos/qgraph_internal.h
@@ -68,6 +68,7 @@ struct QOSGraphNode {
             QOSTestFunc function;
             void *arg;
             QOSBeforeTest before;
+            QOSAfterTest after;
             bool subprocess;
         } test;
     } u;
diff --git a/tests/libqos/qgraph.c b/tests/libqos/qgraph.c
index 7a7ae2a19e..f3e792a509 100644
--- a/tests/libqos/qgraph.c
+++ b/tests/libqos/qgraph.c
@@ -603,6 +603,7 @@ void qos_add_test(const char *name, const char *interface,
     assert(!opts->edge.size_arg);
 
     node->u.test.before = opts->before;
+    node->u.test.after = opts->after;
     node->u.test.subprocess = opts->subprocess;
     node->available = true;
     add_edge(interface, test_name, QEDGE_CONSUMED_BY, &opts->edge);
diff --git a/tests/qos-test.c b/tests/qos-test.c
index fd70d73ea5..fa77f661c6 100644
--- a/tests/qos-test.c
+++ b/tests/qos-test.c
@@ -273,6 +273,7 @@ void *qos_allocate_objects(QTestState *qts, QGuestAllocator **p_alloc)
  * 3) call all nodes constructor and get_driver/get_device depending on edge,
  *    start the hardware (*_device_enable functions)
  * 4) start test
+ * 5) @after test function as defined in the given QOSGraphTestOptions
  */
 static void run_one_test(const void *arg)
 {
@@ -296,6 +297,11 @@ static void run_one_test(const void *arg)
 
     obj = qos_allocate_objects(global_qtest, &alloc);
     test_node->u.test.function(obj, test_arg, alloc);
+
+    /* After test */
+    if (test_node->u.test.after) {
+        test_node->u.test.after(test_arg);
+    }
 }
 
 static void subprocess_run_one_test(const void *arg)
-- 
2.21.0



^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [RFC 3/3] tests/vhost-user-fs-test: add vhost-user-fs test case
  2019-10-25 10:01 [RFC 0/3] tests/vhost-user-fs-test: add vhost-user-fs test case Stefan Hajnoczi
  2019-10-25 10:01 ` [RFC 1/3] WIP virtiofsd: import Linux <fuse.h> header file Stefan Hajnoczi
  2019-10-25 10:01 ` [RFC 2/3] qgraph: add an "after" test callback function Stefan Hajnoczi
@ 2019-10-25 10:01 ` Stefan Hajnoczi
  2019-10-29  0:36   ` Dr. David Alan Gilbert
  2 siblings, 1 reply; 11+ messages in thread
From: Stefan Hajnoczi @ 2019-10-25 10:01 UTC (permalink / raw)
  To: qemu-devel
  Cc: Laurent Vivier, Thomas Huth, Michael S. Tsirkin, Cornelia Huck,
	Dr. David Alan Gilbert, virtio-fs, Stefan Hajnoczi,
	Paolo Bonzini, vgoyal

Add a test case for the vhost-user-fs device.  There are two
limitations:

1. This test only runs when invoked as root.  The virtiofsd vhost-user
   device backend currently requires root in order to maintain accurate
   file system ownership information (uid/gid).

2. Cross-endian is not supported because virtiofsd currently only
   supports same-endian configurations.

This test uses FUSE_INIT, FUSE_LOOKUP, FUSE_OPEN, FUSE_CREATE,
FUSE_READ, FUSE_WRITE, FUSE_RELEASE, and FUSE_FORGET messages to perform
basic sanity testing.

This test can be expanded on in the future to perform low-level
virtio-fs testing, including invalid FUSE messages that are hard to
generate from a real guest.

Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
---
 tests/Makefile.include     |   8 +-
 tests/libqos/virtio-fs.h   |  46 +++
 tests/libqos/virtio-fs.c   | 104 ++++++
 tests/vhost-user-fs-test.c | 660 +++++++++++++++++++++++++++++++++++++
 4 files changed, 816 insertions(+), 2 deletions(-)
 create mode 100644 tests/libqos/virtio-fs.h
 create mode 100644 tests/libqos/virtio-fs.c
 create mode 100644 tests/vhost-user-fs-test.c

diff --git a/tests/Makefile.include b/tests/Makefile.include
index fde8a0c5ef..0472565d96 100644
--- a/tests/Makefile.include
+++ b/tests/Makefile.include
@@ -718,6 +718,7 @@ qos-test-obj-y += tests/libqos/sdhci.o
 qos-test-obj-y += tests/libqos/tpci200.o
 qos-test-obj-y += tests/libqos/virtio.o
 qos-test-obj-$(CONFIG_VIRTFS) += tests/libqos/virtio-9p.o
+qos-test-obj-$(CONFIG_VHOST_USER_FS) += tests/libqos/virtio-fs.o
 qos-test-obj-y += tests/libqos/virtio-balloon.o
 qos-test-obj-y += tests/libqos/virtio-blk.o
 qos-test-obj-y += tests/libqos/virtio-mmio.o
@@ -759,6 +760,7 @@ qos-test-obj-y += tests/spapr-phb-test.o
 qos-test-obj-y += tests/tmp105-test.o
 qos-test-obj-y += tests/usb-hcd-ohci-test.o $(libqos-usb-obj-y)
 qos-test-obj-$(CONFIG_VHOST_NET_USER) += tests/vhost-user-test.o $(chardev-obj-y) $(test-io-obj-y)
+qos-test-obj-$(CONFIG_VHOST_USER_FS) += tests/vhost-user-fs-test.o
 qos-test-obj-y += tests/virtio-test.o
 qos-test-obj-$(CONFIG_VIRTFS) += tests/virtio-9p-test.o
 qos-test-obj-y += tests/virtio-blk-test.o
@@ -907,7 +909,8 @@ endef
 $(patsubst %, check-qtest-%, $(QTEST_TARGETS)): check-qtest-%: %-softmmu/all $(check-qtest-y)
 	$(call do_test_human,$(check-qtest-$*-y) $(check-qtest-generic-y), \
 	  QTEST_QEMU_BINARY=$*-softmmu/qemu-system-$* \
-	  QTEST_QEMU_IMG=qemu-img$(EXESUF))
+	  QTEST_QEMU_IMG=qemu-img$(EXESUF) \
+	  QTEST_VIRTIOFSD=virtiofsd$(EXESUF))
 
 check-unit: $(check-unit-y)
 	$(call do_test_human, $^)
@@ -920,7 +923,8 @@ check-speed: $(check-speed-y)
 $(patsubst %, check-report-qtest-%.tap, $(QTEST_TARGETS)): check-report-qtest-%.tap: %-softmmu/all $(check-qtest-y)
 	$(call do_test_tap, $(check-qtest-$*-y) $(check-qtest-generic-y), \
 	  QTEST_QEMU_BINARY=$*-softmmu/qemu-system-$* \
-	  QTEST_QEMU_IMG=qemu-img$(EXESUF))
+	  QTEST_QEMU_IMG=qemu-img$(EXESUF) \
+	  QTEST_VIRTIOFSD=virtiofsd$(EXESUF))
 
 check-report-unit.tap: $(check-unit-y)
 	$(call do_test_tap,$^)
diff --git a/tests/libqos/virtio-fs.h b/tests/libqos/virtio-fs.h
new file mode 100644
index 0000000000..40289ba283
--- /dev/null
+++ b/tests/libqos/virtio-fs.h
@@ -0,0 +1,46 @@
+/* SPDX-License-Identifer: GPL-2.0-or-later */
+/*
+ * libqos virtio-fs device driver
+ *
+ * Copyright (C) 2019 Red Hat, Inc.
+ */
+
+#ifndef TESTS_LIBQOS_VIRTIO_FS_H
+#define TESTS_LIBQOS_VIRTIO_FS_H
+
+#include "libqos/virtio-pci.h"
+
+#define VIRTIO_FS_TAG "myfs"
+
+typedef struct {
+    QVirtioDevice *vdev;
+    QGuestAllocator *alloc;
+    QVirtQueue *hiprio_vq;
+    QVirtQueue *request_vq;
+    uint64_t unique_counter;
+} QVirtioFS;
+
+typedef struct {
+    QVirtioPCIDevice pci_vdev;
+    QVirtioFS vfs;
+} QVirtioFSPCI;
+
+typedef struct {
+    QOSGraphObject obj;
+    QVirtioFS vfs;
+} QVirtioFSDevice;
+
+static inline uint64_t virtio_fs_get_unique(QVirtioFS *vfs)
+{
+    /*
+     * Interrupt requests share the unique ID of the request, except the
+     * least-significant bit.
+     *
+     * Note that unique ID 0 is invalid so we increment right away.
+     */
+    vfs->unique_counter += 2;
+
+    return vfs->unique_counter;
+}
+
+#endif /* TESTS_LIBQOS_VIRTIO_FS_H */
diff --git a/tests/libqos/virtio-fs.c b/tests/libqos/virtio-fs.c
new file mode 100644
index 0000000000..47f22d50b9
--- /dev/null
+++ b/tests/libqos/virtio-fs.c
@@ -0,0 +1,104 @@
+/* SPDX-License-Identifer: GPL-2.0-or-later */
+/*
+ * libqos virtio-fs device driver
+ *
+ * Copyright (C) 2019 Red Hat, Inc.
+ */
+
+#include "qemu/osdep.h"
+#include "standard-headers/linux/virtio_fs.h"
+#include "libqos/virtio-fs.h"
+
+static void virtio_fs_cleanup(QVirtioFS *vfs)
+{
+    QVirtioDevice *vdev = vfs->vdev;
+
+    qvirtqueue_cleanup(vdev->bus, vfs->hiprio_vq, vfs->alloc);
+    qvirtqueue_cleanup(vdev->bus, vfs->request_vq, vfs->alloc);
+    vfs->hiprio_vq = NULL;
+    vfs->request_vq = NULL;
+}
+
+static void virtio_fs_setup(QVirtioFS *vfs)
+{
+    QVirtioDevice *vdev = vfs->vdev;
+    uint64_t features;
+
+    features = qvirtio_get_features(vdev);
+    features &= ~(QVIRTIO_F_BAD_FEATURE |
+                  (1ull << VIRTIO_RING_F_EVENT_IDX));
+    qvirtio_set_features(vdev, features);
+
+    vfs->hiprio_vq = qvirtqueue_setup(vdev, vfs->alloc, 0);
+    vfs->request_vq = qvirtqueue_setup(vdev, vfs->alloc, 1);
+
+    qvirtio_set_driver_ok(vdev);
+}
+
+static void vhost_user_fs_pci_destructor(QOSGraphObject *obj)
+{
+    QVirtioFSPCI *vfs_pci = (QVirtioFSPCI *)obj;
+    QVirtioFS *vfs = &vfs_pci->vfs;
+
+    virtio_fs_cleanup(vfs);
+    qvirtio_pci_destructor(&vfs_pci->pci_vdev.obj);
+}
+
+static void vhost_user_fs_pci_start_hw(QOSGraphObject *obj)
+{
+    QVirtioFSPCI *vfs_pci = (QVirtioFSPCI *)obj;
+    QVirtioFS *vfs = &vfs_pci->vfs;
+
+    qvirtio_pci_start_hw(&vfs_pci->pci_vdev.obj);
+    virtio_fs_setup(vfs);
+}
+
+static void *vhost_user_fs_pci_get_driver(void *object, const char *interface)
+{
+    QVirtioFSPCI *vfs_pci = object;
+
+    if (g_strcmp0(interface, "virtio-fs") == 0) {
+        return &vfs_pci->vfs;
+    }
+
+    fprintf(stderr, "%s not present in virtio-fs\n", interface);
+    g_assert_not_reached();
+}
+
+static void *vhost_user_fs_pci_create(void *pci_bus, QGuestAllocator *alloc, void *addr)
+{
+    QVirtioFSPCI *vfs_pci = g_new0(QVirtioFSPCI, 1);
+    QVirtioFS *vfs = &vfs_pci->vfs;
+    QOSGraphObject *obj = &vfs_pci->pci_vdev.obj;
+
+    virtio_pci_init(&vfs_pci->pci_vdev, pci_bus, addr);
+    vfs->vdev = &vfs_pci->pci_vdev.vdev;
+    vfs->alloc = alloc;
+
+    g_assert_cmphex(vfs->vdev->device_type, ==, VIRTIO_ID_FS);
+
+    obj->destructor = vhost_user_fs_pci_destructor;
+    obj->start_hw = vhost_user_fs_pci_start_hw;
+    obj->get_driver = vhost_user_fs_pci_get_driver;
+
+    return obj;
+}
+
+static void virtio_fs_register_nodes(void)
+{
+    QOSGraphEdgeOptions opts = {
+        .extra_device_opts = "chardev=char-virtio-fs,addr=04.0,tag=" VIRTIO_FS_TAG,
+        .before_cmd_line = "-m 512M -object memory-backend-file,id=mem,"
+            "size=512M,mem-path=/dev/shm,share=on -numa node,memdev=mem",
+    };
+    QPCIAddress addr = {
+        .devfn = QPCI_DEVFN(4, 0),
+    };
+
+    add_qpci_address(&opts, &addr);
+    qos_node_create_driver("vhost-user-fs-pci", vhost_user_fs_pci_create);
+    qos_node_consumes("vhost-user-fs-pci", "pci-bus", &opts);
+    qos_node_produces("vhost-user-fs-pci", "virtio-fs");
+}
+
+libqos_init(virtio_fs_register_nodes);
diff --git a/tests/vhost-user-fs-test.c b/tests/vhost-user-fs-test.c
new file mode 100644
index 0000000000..76394adee6
--- /dev/null
+++ b/tests/vhost-user-fs-test.c
@@ -0,0 +1,660 @@
+/* SPDX-License-Identifer: GPL-2.0-or-later */
+/*
+ * vhost-user-fs device test
+ *
+ * Copyright (C) 2019 Red Hat, Inc.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/bswap.h"
+#include "qemu/iov.h"
+#include "standard-headers/linux/virtio_fs.h"
+#include "standard-headers/linux/fuse.h"
+#include "libqos/virtio-fs.h"
+#include "libqtest-single.h"
+
+#define TIMEOUT_US (30 * 1000 * 1000)
+
+#ifdef HOST_WORDS_BIGENDIAN
+static const bool host_is_big_endian = true;
+#else
+static const bool host_is_big_endian; /* false */
+#endif
+
+/*
+ * This macro skips tests when run in a cross-endian configuration.
+ * virtiofsd does not byte-swap FUSE messages and therefore does not support
+ * cross-endian.
+ */
+#define SKIP_TEST_IF_CROSS_ENDIAN() { \
+    if (host_is_big_endian != qtest_big_endian(global_qtest)) { \
+        g_test_skip("cross-endian is not supported by virtiofsd yet"); \
+        return; \
+    } \
+}
+
+static char *socket_path;
+static char *shared_dir;
+
+static bool remove_dir_and_children(const char *path)
+{
+    GDir *dir;
+    const gchar *name;
+
+    dir = g_dir_open(path, 0, NULL);
+    if (!dir) {
+        return false;
+    }
+
+    while ((name = g_dir_read_name(dir)) != NULL) {
+        g_autofree gchar *child = g_strdup_printf("%s/%s", path, name);
+
+        g_test_message("unlinking %s", child);
+
+        if (unlink(child) == -1 && errno == EISDIR) {
+            remove_dir_and_children(child);
+        }
+    }
+
+    g_dir_close(dir);
+
+    g_test_message("rmdir %s", path);
+    return rmdir(path) == 0;
+}
+
+static void after_test(void *arg G_GNUC_UNUSED)
+{
+    unlink(socket_path);
+
+    remove_dir_and_children(shared_dir);
+
+    /*
+     * Both QEMU and virtiofsd need to be restarted after each test and the
+     * shared directory will be recreated.  This ensures isolation between test
+     * runs.
+     */
+    qos_invalidate_command_line();
+}
+
+/* Called on SIGABRT */
+static void abrt_handler(void *arg G_GNUC_UNUSED)
+{
+    after_test(NULL);
+}
+
+static int create_socket(const char *path)
+{
+    union {
+        struct sockaddr sa;
+        struct sockaddr_un un;
+    } sa;
+    int fd;
+
+    fd = socket(AF_UNIX, SOCK_STREAM, 0);
+    if (fd < 0) {
+        g_test_message("socket failed (errno=%d)", errno);
+        abort();
+    }
+
+    unlink(path); /* in case it already exists */
+
+    sa.un.sun_family = AF_UNIX;
+    snprintf(sa.un.sun_path, sizeof(sa.un.sun_path), "%s", path);
+
+    if (bind(fd, &sa.sa, sizeof(sa.un)) < 0) {
+        g_test_message("bind failed (errno=%d)", errno);
+        abort();
+    }
+
+    if (listen(fd, 1) < 0) {
+        g_test_message("listen failed (errno=%d)", errno);
+        abort();
+    }
+
+    return fd;
+}
+
+static const char *qtest_virtiofsd(void)
+{
+    const char *virtiofsd_binary;
+
+    virtiofsd_binary = getenv("QTEST_VIRTIOFSD");
+    if (!virtiofsd_binary) {
+        fprintf(stderr, "Environment variable QTEST_VIRTIOFSD required\n");
+        exit(1);
+    }
+
+    return virtiofsd_binary;
+}
+
+/* Launch virtiofsd before each test with an empty shared directory */
+static void *before_test(GString *cmd_line G_GNUC_UNUSED, void *arg)
+{
+    g_autofree char *command = NULL;
+    char *virtiofsd_path;
+    int fd;
+    pid_t pid;
+
+    fd = create_socket(socket_path);
+
+    if (mkdir(shared_dir, 0777) < 0) {
+        g_message("mkdir failed (errno=%d)", errno);
+        abort();
+    }
+
+    virtiofsd_path = realpath(qtest_virtiofsd(), NULL);
+    g_assert_nonnull(virtiofsd_path);
+
+    command = g_strdup_printf("exec %s --fd=%d -o source=%s",
+                              virtiofsd_path,
+                              fd,
+                              shared_dir);
+    free(virtiofsd_path);
+    g_test_message("starting virtiofsd: %s", command);
+
+    /* virtiofsd terminates when QEMU closes the vhost-user socket connection,
+     * so there is no need to kill it explicitly later on.
+     */
+    pid = fork();
+    g_assert_cmpint(pid, >=, 0);
+    if (pid == 0) {
+        execlp("/bin/sh", "sh", "-c", command, NULL);
+        exit(1);
+    }
+
+    close(fd);
+
+    return arg;
+}
+
+/*
+ * Send scatter-gather lists on the request virtqueue and return the number of
+ * bytes filled by the device.
+ *
+ * Note that in/out have opposite meanings in FUSE and VIRTIO.  This function
+ * uses VIRTIO terminology (out - to device, in - from device).
+ */
+static uint32_t do_request(QVirtioFS *vfs, QTestState *qts,
+                           struct iovec *sg_out, unsigned out_num,
+                           struct iovec *sg_in, unsigned in_num)
+{
+    QVirtioDevice *dev = vfs->vdev;
+    QVirtQueue *vq = vfs->request_vq;
+    size_t out_bytes = iov_size(sg_out, out_num);
+    size_t in_bytes = iov_size(sg_in, in_num);
+    uint64_t out_addr;
+    uint64_t in_addr;
+    uint64_t addr;
+    uint32_t head = 0;
+    uint32_t nfilled;
+    unsigned i;
+
+    g_assert_cmpint(out_num, >, 0);
+    g_assert_cmpint(in_num, >, 0);
+
+    /* Add out buffers */
+    addr = out_addr = guest_alloc(vfs->alloc, out_bytes);
+    for (i = 0; i < out_num; i++) {
+        size_t len = sg_out[i].iov_len;
+        uint32_t desc_idx;
+        bool first = i == 0;
+
+        qtest_memwrite(qts, addr, sg_out[i].iov_base, len);
+        desc_idx = qvirtqueue_add(qts, vq, addr, len, false, true);
+
+        if (first) {
+            head = desc_idx;
+        }
+
+        addr += len;
+    }
+
+    /* Add in buffers */
+    addr = in_addr = guest_alloc(vfs->alloc, in_bytes);
+    for (i = 0; i < in_num; i++) {
+        size_t len = sg_in[i].iov_len;
+        bool next = i != in_num - 1;
+
+        qvirtqueue_add(qts, vq, addr, len, true, next);
+
+        addr += len;
+    }
+
+    /* Process the request */
+    qvirtqueue_kick(qts, dev, vq, head);
+    qvirtio_wait_used_elem(qts, dev, vq, head, &nfilled, TIMEOUT_US);
+
+    /* Copy in buffers back */
+    addr = in_addr;
+    for (i = 0; i < in_num; i++) {
+        size_t len = sg_in[i].iov_len;
+
+        qtest_memread(qts, addr, sg_in[i].iov_base, len);
+        addr += len;
+    }
+
+    guest_free(vfs->alloc, in_addr);
+    guest_free(vfs->alloc, out_addr);
+
+    return nfilled;
+}
+
+/* Byte-swap values if host endianness differs from guest */
+static uint32_t guest32(uint32_t val)
+{
+    if (qtest_big_endian(global_qtest) != host_is_big_endian) {
+        return bswap32(val);
+    }
+    return val;
+}
+
+static uint64_t guest64(uint64_t val)
+{
+    if (qtest_big_endian(global_qtest) != host_is_big_endian) {
+        return bswap64(val);
+    }
+    return val;
+}
+
+/* Make a FUSE_INIT request */
+static void fuse_init(QVirtioFS *vfs)
+{
+    struct fuse_in_header in_hdr = {
+        .opcode = guest32(FUSE_INIT),
+        .unique = guest64(virtio_fs_get_unique(vfs)),
+    };
+    struct fuse_init_in in = {
+        .major = guest32(FUSE_KERNEL_VERSION),
+        .minor = guest32(FUSE_KERNEL_MINOR_VERSION),
+    };
+    struct iovec sg_in[] = {
+        { .iov_base = &in_hdr, .iov_len = sizeof(in_hdr) },
+        { .iov_base = &in, .iov_len = sizeof(in) },
+    };
+    struct fuse_out_header out_hdr;
+    struct fuse_init_out out;
+    struct iovec sg_out[] = {
+        { .iov_base = &out_hdr, .iov_len = sizeof(out_hdr) },
+        { .iov_base = &out, .iov_len = sizeof(out) },
+    };
+
+    in_hdr.len = guest32(iov_size(sg_in, G_N_ELEMENTS(sg_in)));
+
+    do_request(vfs, global_qtest, sg_in, G_N_ELEMENTS(sg_in),
+               sg_out, G_N_ELEMENTS(sg_out));
+
+    g_assert_cmpint(guest32(out_hdr.error), ==, 0);
+    g_assert_cmpint(guest32(out.major), ==, FUSE_KERNEL_VERSION);
+}
+
+/* Look up a directory entry by name using FUSE_LOOKUP */
+static int32_t fuse_lookup(QVirtioFS *vfs, uint64_t parent, const char *name,
+                           struct fuse_entry_out *entry)
+{
+    struct fuse_in_header in_hdr = {
+        .opcode = guest32(FUSE_LOOKUP),
+        .unique = guest64(virtio_fs_get_unique(vfs)),
+        .nodeid = guest64(parent),
+    };
+    struct iovec sg_in[] = {
+        { .iov_base = &in_hdr, .iov_len = sizeof(in_hdr) },
+        { .iov_base = (void *)name, .iov_len = strlen(name) + 1 },
+    };
+    struct fuse_out_header out_hdr;
+    struct iovec sg_out[] = {
+        { .iov_base = &out_hdr, .iov_len = sizeof(out_hdr) },
+        { .iov_base = entry, .iov_len = sizeof(*entry) },
+    };
+
+    in_hdr.len = guest32(iov_size(sg_in, G_N_ELEMENTS(sg_in)));
+
+    do_request(vfs, global_qtest, sg_in, G_N_ELEMENTS(sg_in),
+               sg_out, G_N_ELEMENTS(sg_out));
+
+    return guest32(out_hdr.error);
+}
+
+/* Open a file by nodeid using FUSE_OPEN */
+static int32_t fuse_open(QVirtioFS *vfs, uint64_t nodeid, uint32_t flags,
+                         uint64_t *fh)
+{
+    struct fuse_in_header in_hdr = {
+        .opcode = guest32(FUSE_OPEN),
+        .unique = guest64(virtio_fs_get_unique(vfs)),
+        .nodeid = guest64(nodeid),
+    };
+    struct fuse_open_in in = {
+        .flags = guest32(flags),
+    };
+    struct iovec sg_in[] = {
+        { .iov_base = &in_hdr, .iov_len = sizeof(in_hdr) },
+        { .iov_base = &in, .iov_len = sizeof(in) },
+    };
+    struct fuse_out_header out_hdr;
+    struct fuse_open_out out;
+    struct iovec sg_out[] = {
+        { .iov_base = &out_hdr, .iov_len = sizeof(out_hdr) },
+        { .iov_base = &out, .iov_len = sizeof(out) },
+    };
+    int32_t error;
+
+    in_hdr.len = guest32(iov_size(sg_in, G_N_ELEMENTS(sg_in)));
+
+    do_request(vfs, global_qtest, sg_in, G_N_ELEMENTS(sg_in),
+               sg_out, G_N_ELEMENTS(sg_out));
+
+    error = guest32(out_hdr.error);
+    if (!error) {
+        *fh = guest64(out.fh);
+    } else {
+        *fh = 0;
+    }
+    return error;
+}
+
+/* Create a file using FUSE_CREATE */
+static int32_t fuse_create(QVirtioFS *vfs, uint64_t parent, const char *name,
+                           uint32_t mode, uint32_t flags,
+                           uint64_t *nodeid, uint64_t *fh)
+{
+    struct fuse_in_header in_hdr = {
+        .opcode = guest32(FUSE_CREATE),
+        .unique = guest64(virtio_fs_get_unique(vfs)),
+        .nodeid = guest64(parent),
+    };
+    struct fuse_create_in in = {
+        .flags = guest32(flags),
+        .mode = guest32(mode),
+        .umask = guest32(0002),
+    };
+    struct iovec sg_in[] = {
+        { .iov_base = &in_hdr, .iov_len = sizeof(in_hdr) },
+        { .iov_base = &in, .iov_len = sizeof(in) },
+        { .iov_base = (void *)name, .iov_len = strlen(name) + 1 },
+    };
+    struct fuse_out_header out_hdr;
+    struct fuse_entry_out entry;
+    struct fuse_open_out out;
+    struct iovec sg_out[] = {
+        { .iov_base = &out_hdr, .iov_len = sizeof(out_hdr) },
+        { .iov_base = &entry, .iov_len = sizeof(entry) },
+        { .iov_base = &out, .iov_len = sizeof(out) },
+    };
+    int32_t error;
+
+    in_hdr.len = guest32(iov_size(sg_in, G_N_ELEMENTS(sg_in)));
+
+    do_request(vfs, global_qtest, sg_in, G_N_ELEMENTS(sg_in),
+               sg_out, G_N_ELEMENTS(sg_out));
+
+    error = guest32(out_hdr.error);
+    if (!error) {
+        *nodeid = guest64(entry.nodeid);
+        *fh = guest64(out.fh);
+    } else {
+        *nodeid = 0;
+        *fh = 0;
+    }
+    return error;
+}
+
+/* Read bytes from a file using FILE_READ */
+static ssize_t fuse_read(QVirtioFS *vfs, uint64_t fh, uint64_t offset,
+                         void *buf, size_t len)
+{
+    struct fuse_in_header in_hdr = {
+        .opcode = guest32(FUSE_READ),
+        .unique = guest64(virtio_fs_get_unique(vfs)),
+    };
+    struct fuse_read_in in = {
+        .fh = guest64(fh),
+        .offset = guest64(offset),
+        .size = guest32(len),
+    };
+    struct iovec sg_in[] = {
+        { .iov_base = &in_hdr, .iov_len = sizeof(in_hdr) },
+        { .iov_base = &in, .iov_len = sizeof(in) },
+    };
+    struct fuse_out_header out_hdr;
+    struct iovec sg_out[] = {
+        { .iov_base = &out_hdr, .iov_len = sizeof(out_hdr) },
+        { .iov_base = buf, .iov_len = len },
+    };
+    uint32_t nread;
+
+    in_hdr.len = guest32(iov_size(sg_in, G_N_ELEMENTS(sg_in)));
+
+    nread = do_request(vfs, global_qtest, sg_in, G_N_ELEMENTS(sg_in),
+                       sg_out, G_N_ELEMENTS(sg_out));
+    g_assert_cmpint(guest32(out_hdr.error), ==, 0);
+
+    return nread - sizeof(out_hdr);
+}
+
+/* Write bytes to a file using FILE_WRITE */
+static ssize_t fuse_write(QVirtioFS *vfs, uint64_t fh, uint64_t offset,
+                          const void *buf, size_t len)
+{
+    struct fuse_in_header in_hdr = {
+        .opcode = guest32(FUSE_WRITE),
+        .unique = guest64(virtio_fs_get_unique(vfs)),
+    };
+    struct fuse_write_in in = {
+        .fh = guest64(fh),
+        .offset = guest64(offset),
+        .size = guest32(len),
+    };
+    struct iovec sg_in[] = {
+        { .iov_base = &in_hdr, .iov_len = sizeof(in_hdr) },
+        { .iov_base = &in, .iov_len = sizeof(in) },
+        { .iov_base = (void *)buf, .iov_len = len },
+    };
+    struct fuse_out_header out_hdr;
+    struct fuse_write_out out;
+    struct iovec sg_out[] = {
+        { .iov_base = &out_hdr, .iov_len = sizeof(out_hdr) },
+        { .iov_base = &out, .iov_len = sizeof(out) },
+    };
+
+    in_hdr.len = guest32(iov_size(sg_in, G_N_ELEMENTS(sg_in)));
+
+    do_request(vfs, global_qtest, sg_in, G_N_ELEMENTS(sg_in),
+               sg_out, G_N_ELEMENTS(sg_out));
+    g_assert_cmpint(guest32(out_hdr.error), ==, 0);
+
+    return guest32(out.size);
+}
+
+/* Close a file handle using FUSE_RELEASE */
+static void fuse_release(QVirtioFS *vfs, uint64_t fh)
+{
+    struct fuse_in_header in_hdr = {
+        .opcode = guest32(FUSE_RELEASE),
+        .unique = guest64(virtio_fs_get_unique(vfs)),
+    };
+    struct fuse_release_in in = {
+        .fh = guest64(fh),
+    };
+    struct iovec sg_in[] = {
+        { .iov_base = &in_hdr, .iov_len = sizeof(in_hdr) },
+        { .iov_base = &in, .iov_len = sizeof(in) },
+    };
+    struct fuse_out_header out_hdr;
+    struct iovec sg_out[] = {
+        { .iov_base = &out_hdr, .iov_len = sizeof(out_hdr) },
+    };
+
+    in_hdr.len = guest32(iov_size(sg_in, G_N_ELEMENTS(sg_in)));
+
+    do_request(vfs, global_qtest, sg_in, G_N_ELEMENTS(sg_in),
+               sg_out, G_N_ELEMENTS(sg_out));
+
+    g_assert_cmpint(guest32(out_hdr.error), ==, 0);
+}
+
+/* Drop an inode reference using FUSE_FORGET */
+static void fuse_forget(QVirtioFS *vfs, uint64_t nodeid)
+{
+    struct fuse_in_header in_hdr = {
+        .opcode = guest32(FUSE_FORGET),
+        .unique = guest64(virtio_fs_get_unique(vfs)),
+        .nodeid = guest64(nodeid),
+    };
+    struct fuse_forget_in in = {
+        .nlookup = guest64(1),
+    };
+    struct iovec sg_in[] = {
+        { .iov_base = &in_hdr, .iov_len = sizeof(in_hdr) },
+        { .iov_base = &in, .iov_len = sizeof(in) },
+    };
+    struct fuse_out_header out_hdr;
+    struct iovec sg_out[] = {
+        { .iov_base = &out_hdr, .iov_len = sizeof(out_hdr) },
+    };
+
+    in_hdr.len = guest32(iov_size(sg_in, G_N_ELEMENTS(sg_in)));
+
+    do_request(vfs, global_qtest, sg_in, G_N_ELEMENTS(sg_in),
+               sg_out, G_N_ELEMENTS(sg_out));
+
+    g_assert_cmpint(guest32(out_hdr.error), ==, 0);
+}
+
+/* Check contents of VIRTIO Configuration Space */
+static void test_config(void *parent, void *arg, QGuestAllocator *alloc)
+{
+    QVirtioFS *vfs = parent;
+    size_t i;
+    uint32_t num_request_queues;
+    char tag[37];
+
+    SKIP_TEST_IF_CROSS_ENDIAN();
+
+    for (i = 0; i < sizeof(tag) - 1; i++) {
+        tag[i] = qvirtio_config_readw(vfs->vdev, i);
+    }
+    tag[36] = '\0';
+
+    g_assert_cmpstr(tag, ==, VIRTIO_FS_TAG);
+
+    num_request_queues = qvirtio_config_readl(vfs->vdev,
+            offsetof(struct virtio_fs_config, num_request_queues));
+
+    g_assert_cmpint(num_request_queues, ==, 1);
+}
+
+/* Create file on host and check its contents and metadata in guest */
+static void test_file_from_host(void *parent, void *arg, QGuestAllocator *alloc)
+{
+    g_autofree gchar *filename = g_strdup_printf("%s/%s", shared_dir, "foo");
+    const char *str = "This is a test\n";
+    char buf[strlen(str)];
+    QVirtioFS *vfs = parent;
+    struct fuse_entry_out entry;
+    int32_t error;
+    uint64_t nodeid;
+    uint64_t fh;
+    ssize_t nread;
+    gboolean ok;
+
+    SKIP_TEST_IF_CROSS_ENDIAN();
+
+    /* Create the test file in the shared directory */
+    ok = g_file_set_contents(filename, str, strlen(str), NULL);
+    g_assert(ok);
+
+    fuse_init(vfs);
+
+    error = fuse_lookup(vfs, FUSE_ROOT_ID, "foo", &entry);
+    g_assert_cmpint(error, ==, 0);
+    g_assert_cmpint(guest64(entry.attr.size), ==, strlen(str));
+    nodeid = guest64(entry.nodeid);
+
+    error = fuse_open(vfs, nodeid, O_RDONLY, &fh);
+    g_assert_cmpint(error, ==, 0);
+
+    nread = fuse_read(vfs, fh, 0, buf, sizeof(buf));
+    g_assert_cmpint(nread, ==, sizeof(buf));
+    g_assert_cmpint(memcmp(buf, str, sizeof(buf)), ==, 0);
+
+    fuse_release(vfs, fh);
+    fuse_forget(vfs, nodeid);
+}
+
+/* Create file from host and check its contents and metadata on host */
+static void test_file_from_guest(void *parent, void *arg,
+                                 QGuestAllocator *alloc)
+{
+    g_autofree gchar *filename = g_strdup_printf("%s/%s", shared_dir, "foo");
+    const char *str = "This is a test\n";
+    gchar *contents = NULL;
+    gsize length = 0;
+    QVirtioFS *vfs = parent;
+    int32_t error;
+    uint64_t nodeid;
+    uint64_t fh;
+    ssize_t nwritten;
+    gboolean ok;
+
+    SKIP_TEST_IF_CROSS_ENDIAN();
+
+    fuse_init(vfs);
+
+    error = fuse_create(vfs, FUSE_ROOT_ID, "foo", 0644, O_CREAT | O_WRONLY,
+                        &nodeid, &fh);
+    g_assert_cmpint(error, ==, 0);
+
+    nwritten = fuse_write(vfs, fh, 0, str, strlen(str));
+    g_assert_cmpint(nwritten, ==, strlen(str));
+
+    fuse_release(vfs, fh);
+    fuse_forget(vfs, nodeid);
+
+    /* Check the file on the host */
+    ok = g_file_get_contents(filename, &contents, &length, NULL);
+    g_assert(ok);
+    g_assert_cmpint(length, ==, strlen(str));
+    g_assert_cmpint(memcmp(contents, str, strlen(str)), ==, 0);
+    g_free(contents);
+}
+
+static void register_vhost_user_fs_test(void)
+{
+    g_autofree gchar *cmd_line =
+        g_strdup_printf("-chardev socket,id=char-virtio-fs,path=%s",
+                        socket_path);
+    QOSGraphTestOptions opts = {
+        .edge.before_cmd_line = cmd_line,
+        .before = before_test,
+        .after = after_test,
+    };
+
+    if (geteuid() != 0) {
+        g_test_message("Skipping vhost-user-fs tests because root is "
+                       "required for virtiofsd");
+        return;
+    }
+
+    qtest_add_abrt_handler(abrt_handler, NULL);
+
+    qos_add_test("config", "virtio-fs", test_config, &opts);
+    qos_add_test("file-from-host", "virtio-fs", test_file_from_host, &opts);
+    qos_add_test("file-from-guest", "virtio-fs", test_file_from_guest, &opts);
+}
+
+libqos_init(register_vhost_user_fs_test);
+
+static void __attribute__((constructor)) init_paths(void)
+{
+    socket_path = g_strdup_printf("/tmp/qtest-%d-vhost-fs.sock", getpid());
+    shared_dir = g_strdup_printf("/tmp/qtest-%d-virtio-fs-dir", getpid());
+}
+
+static void __attribute__((destructor)) destroy_paths(void)
+{
+    g_free(shared_dir);
+    shared_dir = NULL;
+
+    g_free(socket_path);
+    socket_path = NULL;
+}
-- 
2.21.0



^ permalink raw reply related	[flat|nested] 11+ messages in thread

* Re: [RFC 1/3] WIP virtiofsd: import Linux <fuse.h> header file
  2019-10-25 10:01 ` [RFC 1/3] WIP virtiofsd: import Linux <fuse.h> header file Stefan Hajnoczi
@ 2019-10-26 21:49   ` Michael S. Tsirkin
  2019-10-27 12:36     ` Stefan Hajnoczi
  0 siblings, 1 reply; 11+ messages in thread
From: Michael S. Tsirkin @ 2019-10-26 21:49 UTC (permalink / raw)
  To: Stefan Hajnoczi
  Cc: Laurent Vivier, Thomas Huth, Cornelia Huck, qemu-devel,
	Dr. David Alan Gilbert, virtio-fs, Paolo Bonzini, vgoyal

On Fri, Oct 25, 2019 at 12:01:50PM +0200, Stefan Hajnoczi wrote:
> tests/vhost-user-fs-test.c needs fuse.h.  The private copy that
> virtiofsd has can be replaced with a properly imported file using
> update-linux-headers.sh.
> 
> TODO rerun update-linux-headers.sh with upstream kernel tree!
> 
> Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>

OK I would just add this with the virtiofsd patchset.

> ---
>  contrib/virtiofsd/fuse_lowlevel.h                              | 2 +-
>  .../fuse_kernel.h => include/standard-headers/linux/fuse.h     | 0
>  contrib/virtiofsd/fuse_loop_mt.c                               | 2 +-
>  contrib/virtiofsd/fuse_lowlevel.c                              | 2 +-
>  contrib/virtiofsd/fuse_virtio.c                                | 2 +-
>  scripts/update-linux-headers.sh                                | 3 ++-
>  6 files changed, 6 insertions(+), 5 deletions(-)
>  rename contrib/virtiofsd/fuse_kernel.h => include/standard-headers/linux/fuse.h (100%)
> 
> diff --git a/contrib/virtiofsd/fuse_lowlevel.h b/contrib/virtiofsd/fuse_lowlevel.h
> index 79fb30a1c2..a8c92ff7e0 100644
> --- a/contrib/virtiofsd/fuse_lowlevel.h
> +++ b/contrib/virtiofsd/fuse_lowlevel.h
> @@ -23,7 +23,7 @@
>  #endif
>  
>  #include "fuse_common.h"
> -#include "fuse_kernel.h"
> +#include "standard-headers/linux/fuse.h"
>  
>  #include <utime.h>
>  #include <fcntl.h>
> diff --git a/contrib/virtiofsd/fuse_kernel.h b/include/standard-headers/linux/fuse.h
> similarity index 100%
> rename from contrib/virtiofsd/fuse_kernel.h
> rename to include/standard-headers/linux/fuse.h
> diff --git a/contrib/virtiofsd/fuse_loop_mt.c b/contrib/virtiofsd/fuse_loop_mt.c
> index 2000a8902a..af7b501fac 100644
> --- a/contrib/virtiofsd/fuse_loop_mt.c
> +++ b/contrib/virtiofsd/fuse_loop_mt.c
> @@ -11,7 +11,7 @@
>  #include "fuse_i.h"
>  #include "fuse_lowlevel.h"
>  #include "fuse_misc.h"
> -#include "fuse_kernel.h"
> +#include "standard-headers/linux/fuse.h"
>  #include "fuse_virtio.h"
>  
>  #include <stdio.h>
> diff --git a/contrib/virtiofsd/fuse_lowlevel.c b/contrib/virtiofsd/fuse_lowlevel.c
> index 78ccfe3a27..c1a901cb4d 100644
> --- a/contrib/virtiofsd/fuse_lowlevel.c
> +++ b/contrib/virtiofsd/fuse_lowlevel.c
> @@ -10,7 +10,7 @@
>  */
>  
>  #include "fuse_i.h"
> -#include "fuse_kernel.h"
> +#include "standard-headers/linux/fuse.h"
>  #include "fuse_opt.h"
>  #include "fuse_misc.h"
>  #include "fuse_virtio.h"
> diff --git a/contrib/virtiofsd/fuse_virtio.c b/contrib/virtiofsd/fuse_virtio.c
> index 533ef24bb7..7a0d0b2603 100644
> --- a/contrib/virtiofsd/fuse_virtio.c
> +++ b/contrib/virtiofsd/fuse_virtio.c
> @@ -15,7 +15,7 @@
>  #include "qapi/error.h"
>  
>  #include "fuse_i.h"
> -#include "fuse_kernel.h"
> +#include "standard-headers/linux/fuse.h"
>  #include "fuse_misc.h"
>  #include "fuse_opt.h"
>  #include "fuse_virtio.h"
> diff --git a/scripts/update-linux-headers.sh b/scripts/update-linux-headers.sh
> index f76d77363b..1a627ccd73 100755
> --- a/scripts/update-linux-headers.sh
> +++ b/scripts/update-linux-headers.sh
> @@ -184,7 +184,8 @@ EOF
>  
>  rm -rf "$output/include/standard-headers/linux"
>  mkdir -p "$output/include/standard-headers/linux"
> -for i in "$tmpdir"/include/linux/*virtio*.h \
> +for i in "$tmpdir/include/linux/fuse.h" \
> +         "$tmpdir"/include/linux/*virtio*.h \
>           "$tmpdir/include/linux/qemu_fw_cfg.h" \
>           "$tmpdir/include/linux/input.h" \
>           "$tmpdir/include/linux/input-event-codes.h" \
> -- 
> 2.21.0


^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [RFC 1/3] WIP virtiofsd: import Linux <fuse.h> header file
  2019-10-26 21:49   ` Michael S. Tsirkin
@ 2019-10-27 12:36     ` Stefan Hajnoczi
  2020-06-01 10:28       ` Alex Bennée
  0 siblings, 1 reply; 11+ messages in thread
From: Stefan Hajnoczi @ 2019-10-27 12:36 UTC (permalink / raw)
  To: Michael S. Tsirkin
  Cc: Laurent Vivier, Thomas Huth, Cornelia Huck, qemu-devel,
	Dr. David Alan Gilbert, virtio-fs, Paolo Bonzini, vgoyal

[-- Attachment #1: Type: text/plain, Size: 543 bytes --]

On Sat, Oct 26, 2019 at 05:49:11PM -0400, Michael S. Tsirkin wrote:
> On Fri, Oct 25, 2019 at 12:01:50PM +0200, Stefan Hajnoczi wrote:
> > tests/vhost-user-fs-test.c needs fuse.h.  The private copy that
> > virtiofsd has can be replaced with a properly imported file using
> > update-linux-headers.sh.
> > 
> > TODO rerun update-linux-headers.sh with upstream kernel tree!
> > 
> > Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
> 
> OK I would just add this with the virtiofsd patchset.

Yes, I'll talk to David.

Stefan

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [RFC 3/3] tests/vhost-user-fs-test: add vhost-user-fs test case
  2019-10-25 10:01 ` [RFC 3/3] tests/vhost-user-fs-test: add vhost-user-fs test case Stefan Hajnoczi
@ 2019-10-29  0:36   ` Dr. David Alan Gilbert
  2019-11-05 16:02     ` Stefan Hajnoczi
  0 siblings, 1 reply; 11+ messages in thread
From: Dr. David Alan Gilbert @ 2019-10-29  0:36 UTC (permalink / raw)
  To: Stefan Hajnoczi
  Cc: Laurent Vivier, Thomas Huth, Michael S. Tsirkin, Cornelia Huck,
	qemu-devel, virtio-fs, Paolo Bonzini, vgoyal

* Stefan Hajnoczi (stefanha@redhat.com) wrote:
> Add a test case for the vhost-user-fs device.  There are two
> limitations:
> 
> 1. This test only runs when invoked as root.  The virtiofsd vhost-user
>    device backend currently requires root in order to maintain accurate
>    file system ownership information (uid/gid).
> 
> 2. Cross-endian is not supported because virtiofsd currently only
>    supports same-endian configurations.
> 
> This test uses FUSE_INIT, FUSE_LOOKUP, FUSE_OPEN, FUSE_CREATE,
> FUSE_READ, FUSE_WRITE, FUSE_RELEASE, and FUSE_FORGET messages to perform
> basic sanity testing.
> 
> This test can be expanded on in the future to perform low-level
> virtio-fs testing, including invalid FUSE messages that are hard to
> generate from a real guest.
> 
> Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
> ---
>  tests/Makefile.include     |   8 +-
>  tests/libqos/virtio-fs.h   |  46 +++
>  tests/libqos/virtio-fs.c   | 104 ++++++
>  tests/vhost-user-fs-test.c | 660 +++++++++++++++++++++++++++++++++++++
>  4 files changed, 816 insertions(+), 2 deletions(-)
>  create mode 100644 tests/libqos/virtio-fs.h
>  create mode 100644 tests/libqos/virtio-fs.c
>  create mode 100644 tests/vhost-user-fs-test.c
> 
> diff --git a/tests/Makefile.include b/tests/Makefile.include
> index fde8a0c5ef..0472565d96 100644
> --- a/tests/Makefile.include
> +++ b/tests/Makefile.include
> @@ -718,6 +718,7 @@ qos-test-obj-y += tests/libqos/sdhci.o
>  qos-test-obj-y += tests/libqos/tpci200.o
>  qos-test-obj-y += tests/libqos/virtio.o
>  qos-test-obj-$(CONFIG_VIRTFS) += tests/libqos/virtio-9p.o
> +qos-test-obj-$(CONFIG_VHOST_USER_FS) += tests/libqos/virtio-fs.o
>  qos-test-obj-y += tests/libqos/virtio-balloon.o
>  qos-test-obj-y += tests/libqos/virtio-blk.o
>  qos-test-obj-y += tests/libqos/virtio-mmio.o
> @@ -759,6 +760,7 @@ qos-test-obj-y += tests/spapr-phb-test.o
>  qos-test-obj-y += tests/tmp105-test.o
>  qos-test-obj-y += tests/usb-hcd-ohci-test.o $(libqos-usb-obj-y)
>  qos-test-obj-$(CONFIG_VHOST_NET_USER) += tests/vhost-user-test.o $(chardev-obj-y) $(test-io-obj-y)
> +qos-test-obj-$(CONFIG_VHOST_USER_FS) += tests/vhost-user-fs-test.o
>  qos-test-obj-y += tests/virtio-test.o
>  qos-test-obj-$(CONFIG_VIRTFS) += tests/virtio-9p-test.o
>  qos-test-obj-y += tests/virtio-blk-test.o
> @@ -907,7 +909,8 @@ endef
>  $(patsubst %, check-qtest-%, $(QTEST_TARGETS)): check-qtest-%: %-softmmu/all $(check-qtest-y)
>  	$(call do_test_human,$(check-qtest-$*-y) $(check-qtest-generic-y), \
>  	  QTEST_QEMU_BINARY=$*-softmmu/qemu-system-$* \
> -	  QTEST_QEMU_IMG=qemu-img$(EXESUF))
> +	  QTEST_QEMU_IMG=qemu-img$(EXESUF) \
> +	  QTEST_VIRTIOFSD=virtiofsd$(EXESUF))
>  
>  check-unit: $(check-unit-y)
>  	$(call do_test_human, $^)
> @@ -920,7 +923,8 @@ check-speed: $(check-speed-y)
>  $(patsubst %, check-report-qtest-%.tap, $(QTEST_TARGETS)): check-report-qtest-%.tap: %-softmmu/all $(check-qtest-y)
>  	$(call do_test_tap, $(check-qtest-$*-y) $(check-qtest-generic-y), \
>  	  QTEST_QEMU_BINARY=$*-softmmu/qemu-system-$* \
> -	  QTEST_QEMU_IMG=qemu-img$(EXESUF))
> +	  QTEST_QEMU_IMG=qemu-img$(EXESUF) \
> +	  QTEST_VIRTIOFSD=virtiofsd$(EXESUF))
>  
>  check-report-unit.tap: $(check-unit-y)
>  	$(call do_test_tap,$^)
> diff --git a/tests/libqos/virtio-fs.h b/tests/libqos/virtio-fs.h
> new file mode 100644
> index 0000000000..40289ba283
> --- /dev/null
> +++ b/tests/libqos/virtio-fs.h
> @@ -0,0 +1,46 @@
> +/* SPDX-License-Identifer: GPL-2.0-or-later */
> +/*
> + * libqos virtio-fs device driver
> + *
> + * Copyright (C) 2019 Red Hat, Inc.
> + */
> +
> +#ifndef TESTS_LIBQOS_VIRTIO_FS_H
> +#define TESTS_LIBQOS_VIRTIO_FS_H
> +
> +#include "libqos/virtio-pci.h"
> +
> +#define VIRTIO_FS_TAG "myfs"
> +
> +typedef struct {
> +    QVirtioDevice *vdev;
> +    QGuestAllocator *alloc;
> +    QVirtQueue *hiprio_vq;
> +    QVirtQueue *request_vq;
> +    uint64_t unique_counter;
> +} QVirtioFS;
> +
> +typedef struct {
> +    QVirtioPCIDevice pci_vdev;
> +    QVirtioFS vfs;
> +} QVirtioFSPCI;
> +
> +typedef struct {
> +    QOSGraphObject obj;
> +    QVirtioFS vfs;
> +} QVirtioFSDevice;
> +
> +static inline uint64_t virtio_fs_get_unique(QVirtioFS *vfs)
> +{
> +    /*
> +     * Interrupt requests share the unique ID of the request, except the
> +     * least-significant bit.
> +     *
> +     * Note that unique ID 0 is invalid so we increment right away.
> +     */
> +    vfs->unique_counter += 2;
> +
> +    return vfs->unique_counter;
> +}
> +
> +#endif /* TESTS_LIBQOS_VIRTIO_FS_H */
> diff --git a/tests/libqos/virtio-fs.c b/tests/libqos/virtio-fs.c
> new file mode 100644
> index 0000000000..47f22d50b9
> --- /dev/null
> +++ b/tests/libqos/virtio-fs.c
> @@ -0,0 +1,104 @@
> +/* SPDX-License-Identifer: GPL-2.0-or-later */
> +/*
> + * libqos virtio-fs device driver
> + *
> + * Copyright (C) 2019 Red Hat, Inc.
> + */
> +
> +#include "qemu/osdep.h"
> +#include "standard-headers/linux/virtio_fs.h"
> +#include "libqos/virtio-fs.h"
> +
> +static void virtio_fs_cleanup(QVirtioFS *vfs)
> +{
> +    QVirtioDevice *vdev = vfs->vdev;
> +
> +    qvirtqueue_cleanup(vdev->bus, vfs->hiprio_vq, vfs->alloc);
> +    qvirtqueue_cleanup(vdev->bus, vfs->request_vq, vfs->alloc);
> +    vfs->hiprio_vq = NULL;
> +    vfs->request_vq = NULL;
> +}
> +
> +static void virtio_fs_setup(QVirtioFS *vfs)
> +{
> +    QVirtioDevice *vdev = vfs->vdev;
> +    uint64_t features;
> +
> +    features = qvirtio_get_features(vdev);
> +    features &= ~(QVIRTIO_F_BAD_FEATURE |
> +                  (1ull << VIRTIO_RING_F_EVENT_IDX));
> +    qvirtio_set_features(vdev, features);
> +
> +    vfs->hiprio_vq = qvirtqueue_setup(vdev, vfs->alloc, 0);
> +    vfs->request_vq = qvirtqueue_setup(vdev, vfs->alloc, 1);
> +
> +    qvirtio_set_driver_ok(vdev);
> +}
> +
> +static void vhost_user_fs_pci_destructor(QOSGraphObject *obj)
> +{
> +    QVirtioFSPCI *vfs_pci = (QVirtioFSPCI *)obj;
> +    QVirtioFS *vfs = &vfs_pci->vfs;
> +
> +    virtio_fs_cleanup(vfs);
> +    qvirtio_pci_destructor(&vfs_pci->pci_vdev.obj);
> +}
> +
> +static void vhost_user_fs_pci_start_hw(QOSGraphObject *obj)
> +{
> +    QVirtioFSPCI *vfs_pci = (QVirtioFSPCI *)obj;
> +    QVirtioFS *vfs = &vfs_pci->vfs;
> +
> +    qvirtio_pci_start_hw(&vfs_pci->pci_vdev.obj);
> +    virtio_fs_setup(vfs);
> +}
> +
> +static void *vhost_user_fs_pci_get_driver(void *object, const char *interface)
> +{
> +    QVirtioFSPCI *vfs_pci = object;
> +
> +    if (g_strcmp0(interface, "virtio-fs") == 0) {
> +        return &vfs_pci->vfs;
> +    }
> +
> +    fprintf(stderr, "%s not present in virtio-fs\n", interface);
> +    g_assert_not_reached();
> +}
> +
> +static void *vhost_user_fs_pci_create(void *pci_bus, QGuestAllocator *alloc, void *addr)
> +{
> +    QVirtioFSPCI *vfs_pci = g_new0(QVirtioFSPCI, 1);
> +    QVirtioFS *vfs = &vfs_pci->vfs;
> +    QOSGraphObject *obj = &vfs_pci->pci_vdev.obj;
> +
> +    virtio_pci_init(&vfs_pci->pci_vdev, pci_bus, addr);
> +    vfs->vdev = &vfs_pci->pci_vdev.vdev;
> +    vfs->alloc = alloc;
> +
> +    g_assert_cmphex(vfs->vdev->device_type, ==, VIRTIO_ID_FS);
> +
> +    obj->destructor = vhost_user_fs_pci_destructor;
> +    obj->start_hw = vhost_user_fs_pci_start_hw;
> +    obj->get_driver = vhost_user_fs_pci_get_driver;
> +
> +    return obj;
> +}
> +
> +static void virtio_fs_register_nodes(void)
> +{
> +    QOSGraphEdgeOptions opts = {
> +        .extra_device_opts = "chardev=char-virtio-fs,addr=04.0,tag=" VIRTIO_FS_TAG,
> +        .before_cmd_line = "-m 512M -object memory-backend-file,id=mem,"
> +            "size=512M,mem-path=/dev/shm,share=on -numa node,memdev=mem",
> +    };
> +    QPCIAddress addr = {
> +        .devfn = QPCI_DEVFN(4, 0),
> +    };
> +
> +    add_qpci_address(&opts, &addr);
> +    qos_node_create_driver("vhost-user-fs-pci", vhost_user_fs_pci_create);
> +    qos_node_consumes("vhost-user-fs-pci", "pci-bus", &opts);
> +    qos_node_produces("vhost-user-fs-pci", "virtio-fs");
> +}
> +
> +libqos_init(virtio_fs_register_nodes);
> diff --git a/tests/vhost-user-fs-test.c b/tests/vhost-user-fs-test.c
> new file mode 100644
> index 0000000000..76394adee6
> --- /dev/null
> +++ b/tests/vhost-user-fs-test.c
> @@ -0,0 +1,660 @@
> +/* SPDX-License-Identifer: GPL-2.0-or-later */
> +/*
> + * vhost-user-fs device test
> + *
> + * Copyright (C) 2019 Red Hat, Inc.
> + */
> +
> +#include "qemu/osdep.h"
> +#include "qemu/bswap.h"
> +#include "qemu/iov.h"
> +#include "standard-headers/linux/virtio_fs.h"
> +#include "standard-headers/linux/fuse.h"
> +#include "libqos/virtio-fs.h"
> +#include "libqtest-single.h"
> +
> +#define TIMEOUT_US (30 * 1000 * 1000)
> +
> +#ifdef HOST_WORDS_BIGENDIAN
> +static const bool host_is_big_endian = true;
> +#else
> +static const bool host_is_big_endian; /* false */
> +#endif
> +
> +/*
> + * This macro skips tests when run in a cross-endian configuration.
> + * virtiofsd does not byte-swap FUSE messages and therefore does not support
> + * cross-endian.
> + */
> +#define SKIP_TEST_IF_CROSS_ENDIAN() { \
> +    if (host_is_big_endian != qtest_big_endian(global_qtest)) { \
> +        g_test_skip("cross-endian is not supported by virtiofsd yet"); \
> +        return; \
> +    } \
> +}
> +
> +static char *socket_path;
> +static char *shared_dir;
> +
> +static bool remove_dir_and_children(const char *path)
> +{
> +    GDir *dir;
> +    const gchar *name;
> +
> +    dir = g_dir_open(path, 0, NULL);
> +    if (!dir) {
> +        return false;
> +    }
> +
> +    while ((name = g_dir_read_name(dir)) != NULL) {
> +        g_autofree gchar *child = g_strdup_printf("%s/%s", path, name);
> +
> +        g_test_message("unlinking %s", child);
> +
> +        if (unlink(child) == -1 && errno == EISDIR) {
> +            remove_dir_and_children(child);
> +        }
> +    }
> +
> +    g_dir_close(dir);
> +
> +    g_test_message("rmdir %s", path);
> +    return rmdir(path) == 0;
> +}
> +
> +static void after_test(void *arg G_GNUC_UNUSED)
> +{
> +    unlink(socket_path);
> +
> +    remove_dir_and_children(shared_dir);

This scares me. Especially since it's running as root.
Can we add a bunch of paranoid checks to make sure it doesn't
end up rm -rf / ?

> +    /*
> +     * Both QEMU and virtiofsd need to be restarted after each test and the
> +     * shared directory will be recreated.  This ensures isolation between test
> +     * runs.
> +     */
> +    qos_invalidate_command_line();
> +}
> +
> +/* Called on SIGABRT */
> +static void abrt_handler(void *arg G_GNUC_UNUSED)
> +{
> +    after_test(NULL);
> +}
> +
> +static int create_socket(const char *path)
> +{
> +    union {
> +        struct sockaddr sa;
> +        struct sockaddr_un un;
> +    } sa;
> +    int fd;
> +
> +    fd = socket(AF_UNIX, SOCK_STREAM, 0);
> +    if (fd < 0) {
> +        g_test_message("socket failed (errno=%d)", errno);
> +        abort();
> +    }
> +
> +    unlink(path); /* in case it already exists */
> +
> +    sa.un.sun_family = AF_UNIX;
> +    snprintf(sa.un.sun_path, sizeof(sa.un.sun_path), "%s", path);
> +
> +    if (bind(fd, &sa.sa, sizeof(sa.un)) < 0) {
> +        g_test_message("bind failed (errno=%d)", errno);
> +        abort();
> +    }
> +
> +    if (listen(fd, 1) < 0) {
> +        g_test_message("listen failed (errno=%d)", errno);
> +        abort();
> +    }
> +
> +    return fd;
> +}
> +
> +static const char *qtest_virtiofsd(void)
> +{
> +    const char *virtiofsd_binary;
> +
> +    virtiofsd_binary = getenv("QTEST_VIRTIOFSD");
> +    if (!virtiofsd_binary) {
> +        fprintf(stderr, "Environment variable QTEST_VIRTIOFSD required\n");
> +        exit(1);
> +    }
> +
> +    return virtiofsd_binary;
> +}
> +
> +/* Launch virtiofsd before each test with an empty shared directory */
> +static void *before_test(GString *cmd_line G_GNUC_UNUSED, void *arg)
> +{
> +    g_autofree char *command = NULL;
> +    char *virtiofsd_path;
> +    int fd;
> +    pid_t pid;
> +
> +    fd = create_socket(socket_path);
> +
> +    if (mkdir(shared_dir, 0777) < 0) {
> +        g_message("mkdir failed (errno=%d)", errno);
> +        abort();
> +    }
> +
> +    virtiofsd_path = realpath(qtest_virtiofsd(), NULL);
> +    g_assert_nonnull(virtiofsd_path);
> +
> +    command = g_strdup_printf("exec %s --fd=%d -o source=%s",
> +                              virtiofsd_path,
> +                              fd,
> +                              shared_dir);
> +    free(virtiofsd_path);
> +    g_test_message("starting virtiofsd: %s", command);
> +
> +    /* virtiofsd terminates when QEMU closes the vhost-user socket connection,
> +     * so there is no need to kill it explicitly later on.
> +     */
> +    pid = fork();
> +    g_assert_cmpint(pid, >=, 0);
> +    if (pid == 0) {
> +        execlp("/bin/sh", "sh", "-c", command, NULL);
> +        exit(1);
> +    }
> +
> +    close(fd);
> +
> +    return arg;
> +}
> +
> +/*
> + * Send scatter-gather lists on the request virtqueue and return the number of
> + * bytes filled by the device.
> + *
> + * Note that in/out have opposite meanings in FUSE and VIRTIO.  This function
> + * uses VIRTIO terminology (out - to device, in - from device).
> + */
> +static uint32_t do_request(QVirtioFS *vfs, QTestState *qts,
> +                           struct iovec *sg_out, unsigned out_num,
> +                           struct iovec *sg_in, unsigned in_num)
> +{
> +    QVirtioDevice *dev = vfs->vdev;
> +    QVirtQueue *vq = vfs->request_vq;
> +    size_t out_bytes = iov_size(sg_out, out_num);
> +    size_t in_bytes = iov_size(sg_in, in_num);
> +    uint64_t out_addr;
> +    uint64_t in_addr;
> +    uint64_t addr;
> +    uint32_t head = 0;
> +    uint32_t nfilled;
> +    unsigned i;
> +
> +    g_assert_cmpint(out_num, >, 0);
> +    g_assert_cmpint(in_num, >, 0);
> +
> +    /* Add out buffers */
> +    addr = out_addr = guest_alloc(vfs->alloc, out_bytes);
> +    for (i = 0; i < out_num; i++) {
> +        size_t len = sg_out[i].iov_len;
> +        uint32_t desc_idx;
> +        bool first = i == 0;
> +
> +        qtest_memwrite(qts, addr, sg_out[i].iov_base, len);
> +        desc_idx = qvirtqueue_add(qts, vq, addr, len, false, true);
> +
> +        if (first) {
> +            head = desc_idx;
> +        }
> +
> +        addr += len;
> +    }
> +
> +    /* Add in buffers */
> +    addr = in_addr = guest_alloc(vfs->alloc, in_bytes);
> +    for (i = 0; i < in_num; i++) {
> +        size_t len = sg_in[i].iov_len;
> +        bool next = i != in_num - 1;
> +
> +        qvirtqueue_add(qts, vq, addr, len, true, next);
> +
> +        addr += len;
> +    }
> +
> +    /* Process the request */
> +    qvirtqueue_kick(qts, dev, vq, head);
> +    qvirtio_wait_used_elem(qts, dev, vq, head, &nfilled, TIMEOUT_US);
> +
> +    /* Copy in buffers back */
> +    addr = in_addr;
> +    for (i = 0; i < in_num; i++) {
> +        size_t len = sg_in[i].iov_len;
> +
> +        qtest_memread(qts, addr, sg_in[i].iov_base, len);
> +        addr += len;
> +    }
> +
> +    guest_free(vfs->alloc, in_addr);
> +    guest_free(vfs->alloc, out_addr);
> +
> +    return nfilled;
> +}
> +
> +/* Byte-swap values if host endianness differs from guest */
> +static uint32_t guest32(uint32_t val)
> +{
> +    if (qtest_big_endian(global_qtest) != host_is_big_endian) {
> +        return bswap32(val);
> +    }
> +    return val;
> +}
> +
> +static uint64_t guest64(uint64_t val)
> +{
> +    if (qtest_big_endian(global_qtest) != host_is_big_endian) {
> +        return bswap64(val);
> +    }
> +    return val;
> +}
> +
> +/* Make a FUSE_INIT request */
> +static void fuse_init(QVirtioFS *vfs)
> +{
> +    struct fuse_in_header in_hdr = {
> +        .opcode = guest32(FUSE_INIT),
> +        .unique = guest64(virtio_fs_get_unique(vfs)),
> +    };
> +    struct fuse_init_in in = {
> +        .major = guest32(FUSE_KERNEL_VERSION),
> +        .minor = guest32(FUSE_KERNEL_MINOR_VERSION),
> +    };
> +    struct iovec sg_in[] = {
> +        { .iov_base = &in_hdr, .iov_len = sizeof(in_hdr) },
> +        { .iov_base = &in, .iov_len = sizeof(in) },
> +    };
> +    struct fuse_out_header out_hdr;
> +    struct fuse_init_out out;
> +    struct iovec sg_out[] = {
> +        { .iov_base = &out_hdr, .iov_len = sizeof(out_hdr) },
> +        { .iov_base = &out, .iov_len = sizeof(out) },
> +    };
> +
> +    in_hdr.len = guest32(iov_size(sg_in, G_N_ELEMENTS(sg_in)));
> +
> +    do_request(vfs, global_qtest, sg_in, G_N_ELEMENTS(sg_in),
> +               sg_out, G_N_ELEMENTS(sg_out));
> +
> +    g_assert_cmpint(guest32(out_hdr.error), ==, 0);
> +    g_assert_cmpint(guest32(out.major), ==, FUSE_KERNEL_VERSION);
> +}
> +
> +/* Look up a directory entry by name using FUSE_LOOKUP */
> +static int32_t fuse_lookup(QVirtioFS *vfs, uint64_t parent, const char *name,
> +                           struct fuse_entry_out *entry)
> +{
> +    struct fuse_in_header in_hdr = {
> +        .opcode = guest32(FUSE_LOOKUP),
> +        .unique = guest64(virtio_fs_get_unique(vfs)),
> +        .nodeid = guest64(parent),
> +    };
> +    struct iovec sg_in[] = {
> +        { .iov_base = &in_hdr, .iov_len = sizeof(in_hdr) },
> +        { .iov_base = (void *)name, .iov_len = strlen(name) + 1 },
> +    };
> +    struct fuse_out_header out_hdr;
> +    struct iovec sg_out[] = {
> +        { .iov_base = &out_hdr, .iov_len = sizeof(out_hdr) },
> +        { .iov_base = entry, .iov_len = sizeof(*entry) },
> +    };
> +
> +    in_hdr.len = guest32(iov_size(sg_in, G_N_ELEMENTS(sg_in)));
> +
> +    do_request(vfs, global_qtest, sg_in, G_N_ELEMENTS(sg_in),
> +               sg_out, G_N_ELEMENTS(sg_out));
> +
> +    return guest32(out_hdr.error);
> +}
> +
> +/* Open a file by nodeid using FUSE_OPEN */
> +static int32_t fuse_open(QVirtioFS *vfs, uint64_t nodeid, uint32_t flags,
> +                         uint64_t *fh)
> +{
> +    struct fuse_in_header in_hdr = {
> +        .opcode = guest32(FUSE_OPEN),
> +        .unique = guest64(virtio_fs_get_unique(vfs)),
> +        .nodeid = guest64(nodeid),
> +    };
> +    struct fuse_open_in in = {
> +        .flags = guest32(flags),
> +    };
> +    struct iovec sg_in[] = {
> +        { .iov_base = &in_hdr, .iov_len = sizeof(in_hdr) },
> +        { .iov_base = &in, .iov_len = sizeof(in) },
> +    };
> +    struct fuse_out_header out_hdr;
> +    struct fuse_open_out out;
> +    struct iovec sg_out[] = {
> +        { .iov_base = &out_hdr, .iov_len = sizeof(out_hdr) },
> +        { .iov_base = &out, .iov_len = sizeof(out) },
> +    };

I wonder if anything can be done to reduce the size of the iovec boiler
plate?

> +    int32_t error;
> +
> +    in_hdr.len = guest32(iov_size(sg_in, G_N_ELEMENTS(sg_in)));
> +
> +    do_request(vfs, global_qtest, sg_in, G_N_ELEMENTS(sg_in),
> +               sg_out, G_N_ELEMENTS(sg_out));
> +
> +    error = guest32(out_hdr.error);
> +    if (!error) {
> +        *fh = guest64(out.fh);
> +    } else {
> +        *fh = 0;
> +    }
> +    return error;
> +}
> +
> +/* Create a file using FUSE_CREATE */
> +static int32_t fuse_create(QVirtioFS *vfs, uint64_t parent, const char *name,
> +                           uint32_t mode, uint32_t flags,
> +                           uint64_t *nodeid, uint64_t *fh)
> +{
> +    struct fuse_in_header in_hdr = {
> +        .opcode = guest32(FUSE_CREATE),
> +        .unique = guest64(virtio_fs_get_unique(vfs)),
> +        .nodeid = guest64(parent),
> +    };
> +    struct fuse_create_in in = {
> +        .flags = guest32(flags),
> +        .mode = guest32(mode),
> +        .umask = guest32(0002),
> +    };
> +    struct iovec sg_in[] = {
> +        { .iov_base = &in_hdr, .iov_len = sizeof(in_hdr) },
> +        { .iov_base = &in, .iov_len = sizeof(in) },
> +        { .iov_base = (void *)name, .iov_len = strlen(name) + 1 },
> +    };
> +    struct fuse_out_header out_hdr;
> +    struct fuse_entry_out entry;
> +    struct fuse_open_out out;
> +    struct iovec sg_out[] = {
> +        { .iov_base = &out_hdr, .iov_len = sizeof(out_hdr) },
> +        { .iov_base = &entry, .iov_len = sizeof(entry) },
> +        { .iov_base = &out, .iov_len = sizeof(out) },
> +    };
> +    int32_t error;
> +
> +    in_hdr.len = guest32(iov_size(sg_in, G_N_ELEMENTS(sg_in)));
> +
> +    do_request(vfs, global_qtest, sg_in, G_N_ELEMENTS(sg_in),
> +               sg_out, G_N_ELEMENTS(sg_out));
> +
> +    error = guest32(out_hdr.error);
> +    if (!error) {
> +        *nodeid = guest64(entry.nodeid);
> +        *fh = guest64(out.fh);
> +    } else {
> +        *nodeid = 0;
> +        *fh = 0;
> +    }
> +    return error;
> +}
> +
> +/* Read bytes from a file using FILE_READ */
> +static ssize_t fuse_read(QVirtioFS *vfs, uint64_t fh, uint64_t offset,
> +                         void *buf, size_t len)
> +{
> +    struct fuse_in_header in_hdr = {
> +        .opcode = guest32(FUSE_READ),
> +        .unique = guest64(virtio_fs_get_unique(vfs)),
> +    };
> +    struct fuse_read_in in = {
> +        .fh = guest64(fh),
> +        .offset = guest64(offset),
> +        .size = guest32(len),
> +    };
> +    struct iovec sg_in[] = {
> +        { .iov_base = &in_hdr, .iov_len = sizeof(in_hdr) },
> +        { .iov_base = &in, .iov_len = sizeof(in) },
> +    };
> +    struct fuse_out_header out_hdr;
> +    struct iovec sg_out[] = {
> +        { .iov_base = &out_hdr, .iov_len = sizeof(out_hdr) },
> +        { .iov_base = buf, .iov_len = len },
> +    };
> +    uint32_t nread;
> +
> +    in_hdr.len = guest32(iov_size(sg_in, G_N_ELEMENTS(sg_in)));
> +
> +    nread = do_request(vfs, global_qtest, sg_in, G_N_ELEMENTS(sg_in),
> +                       sg_out, G_N_ELEMENTS(sg_out));
> +    g_assert_cmpint(guest32(out_hdr.error), ==, 0);
> +
> +    return nread - sizeof(out_hdr);
> +}
> +
> +/* Write bytes to a file using FILE_WRITE */
> +static ssize_t fuse_write(QVirtioFS *vfs, uint64_t fh, uint64_t offset,
> +                          const void *buf, size_t len)
> +{
> +    struct fuse_in_header in_hdr = {
> +        .opcode = guest32(FUSE_WRITE),
> +        .unique = guest64(virtio_fs_get_unique(vfs)),
> +    };
> +    struct fuse_write_in in = {
> +        .fh = guest64(fh),
> +        .offset = guest64(offset),
> +        .size = guest32(len),
> +    };
> +    struct iovec sg_in[] = {
> +        { .iov_base = &in_hdr, .iov_len = sizeof(in_hdr) },
> +        { .iov_base = &in, .iov_len = sizeof(in) },
> +        { .iov_base = (void *)buf, .iov_len = len },
> +    };
> +    struct fuse_out_header out_hdr;
> +    struct fuse_write_out out;
> +    struct iovec sg_out[] = {
> +        { .iov_base = &out_hdr, .iov_len = sizeof(out_hdr) },
> +        { .iov_base = &out, .iov_len = sizeof(out) },
> +    };
> +
> +    in_hdr.len = guest32(iov_size(sg_in, G_N_ELEMENTS(sg_in)));
> +
> +    do_request(vfs, global_qtest, sg_in, G_N_ELEMENTS(sg_in),
> +               sg_out, G_N_ELEMENTS(sg_out));
> +    g_assert_cmpint(guest32(out_hdr.error), ==, 0);
> +
> +    return guest32(out.size);
> +}
> +
> +/* Close a file handle using FUSE_RELEASE */
> +static void fuse_release(QVirtioFS *vfs, uint64_t fh)
> +{
> +    struct fuse_in_header in_hdr = {
> +        .opcode = guest32(FUSE_RELEASE),
> +        .unique = guest64(virtio_fs_get_unique(vfs)),
> +    };
> +    struct fuse_release_in in = {
> +        .fh = guest64(fh),
> +    };
> +    struct iovec sg_in[] = {
> +        { .iov_base = &in_hdr, .iov_len = sizeof(in_hdr) },
> +        { .iov_base = &in, .iov_len = sizeof(in) },
> +    };
> +    struct fuse_out_header out_hdr;
> +    struct iovec sg_out[] = {
> +        { .iov_base = &out_hdr, .iov_len = sizeof(out_hdr) },
> +    };
> +
> +    in_hdr.len = guest32(iov_size(sg_in, G_N_ELEMENTS(sg_in)));
> +
> +    do_request(vfs, global_qtest, sg_in, G_N_ELEMENTS(sg_in),
> +               sg_out, G_N_ELEMENTS(sg_out));
> +
> +    g_assert_cmpint(guest32(out_hdr.error), ==, 0);
> +}
> +
> +/* Drop an inode reference using FUSE_FORGET */
> +static void fuse_forget(QVirtioFS *vfs, uint64_t nodeid)
> +{
> +    struct fuse_in_header in_hdr = {
> +        .opcode = guest32(FUSE_FORGET),
> +        .unique = guest64(virtio_fs_get_unique(vfs)),
> +        .nodeid = guest64(nodeid),
> +    };
> +    struct fuse_forget_in in = {
> +        .nlookup = guest64(1),
> +    };
> +    struct iovec sg_in[] = {
> +        { .iov_base = &in_hdr, .iov_len = sizeof(in_hdr) },
> +        { .iov_base = &in, .iov_len = sizeof(in) },
> +    };
> +    struct fuse_out_header out_hdr;
> +    struct iovec sg_out[] = {
> +        { .iov_base = &out_hdr, .iov_len = sizeof(out_hdr) },
> +    };
> +
> +    in_hdr.len = guest32(iov_size(sg_in, G_N_ELEMENTS(sg_in)));
> +
> +    do_request(vfs, global_qtest, sg_in, G_N_ELEMENTS(sg_in),
> +               sg_out, G_N_ELEMENTS(sg_out));
> +
> +    g_assert_cmpint(guest32(out_hdr.error), ==, 0);
> +}
> +
> +/* Check contents of VIRTIO Configuration Space */
> +static void test_config(void *parent, void *arg, QGuestAllocator *alloc)
> +{
> +    QVirtioFS *vfs = parent;
> +    size_t i;
> +    uint32_t num_request_queues;
> +    char tag[37];
> +
> +    SKIP_TEST_IF_CROSS_ENDIAN();
> +
> +    for (i = 0; i < sizeof(tag) - 1; i++) {
> +        tag[i] = qvirtio_config_readw(vfs->vdev, i);
> +    }
> +    tag[36] = '\0';
> +
> +    g_assert_cmpstr(tag, ==, VIRTIO_FS_TAG);
> +
> +    num_request_queues = qvirtio_config_readl(vfs->vdev,
> +            offsetof(struct virtio_fs_config, num_request_queues));
> +
> +    g_assert_cmpint(num_request_queues, ==, 1);
> +}
> +
> +/* Create file on host and check its contents and metadata in guest */
> +static void test_file_from_host(void *parent, void *arg, QGuestAllocator *alloc)
> +{
> +    g_autofree gchar *filename = g_strdup_printf("%s/%s", shared_dir, "foo");
> +    const char *str = "This is a test\n";
> +    char buf[strlen(str)];
> +    QVirtioFS *vfs = parent;
> +    struct fuse_entry_out entry;
> +    int32_t error;
> +    uint64_t nodeid;
> +    uint64_t fh;
> +    ssize_t nread;
> +    gboolean ok;
> +
> +    SKIP_TEST_IF_CROSS_ENDIAN();
> +
> +    /* Create the test file in the shared directory */
> +    ok = g_file_set_contents(filename, str, strlen(str), NULL);
> +    g_assert(ok);
> +
> +    fuse_init(vfs);
> +
> +    error = fuse_lookup(vfs, FUSE_ROOT_ID, "foo", &entry);
> +    g_assert_cmpint(error, ==, 0);
> +    g_assert_cmpint(guest64(entry.attr.size), ==, strlen(str));
> +    nodeid = guest64(entry.nodeid);
> +
> +    error = fuse_open(vfs, nodeid, O_RDONLY, &fh);
> +    g_assert_cmpint(error, ==, 0);
> +
> +    nread = fuse_read(vfs, fh, 0, buf, sizeof(buf));
> +    g_assert_cmpint(nread, ==, sizeof(buf));
> +    g_assert_cmpint(memcmp(buf, str, sizeof(buf)), ==, 0);
> +
> +    fuse_release(vfs, fh);
> +    fuse_forget(vfs, nodeid);
> +}
> +
> +/* Create file from host and check its contents and metadata on host */
> +static void test_file_from_guest(void *parent, void *arg,
> +                                 QGuestAllocator *alloc)
> +{
> +    g_autofree gchar *filename = g_strdup_printf("%s/%s", shared_dir, "foo");
> +    const char *str = "This is a test\n";
> +    gchar *contents = NULL;
> +    gsize length = 0;
> +    QVirtioFS *vfs = parent;
> +    int32_t error;
> +    uint64_t nodeid;
> +    uint64_t fh;
> +    ssize_t nwritten;
> +    gboolean ok;
> +
> +    SKIP_TEST_IF_CROSS_ENDIAN();
> +
> +    fuse_init(vfs);
> +
> +    error = fuse_create(vfs, FUSE_ROOT_ID, "foo", 0644, O_CREAT | O_WRONLY,
> +                        &nodeid, &fh);
> +    g_assert_cmpint(error, ==, 0);
> +
> +    nwritten = fuse_write(vfs, fh, 0, str, strlen(str));
> +    g_assert_cmpint(nwritten, ==, strlen(str));
> +
> +    fuse_release(vfs, fh);
> +    fuse_forget(vfs, nodeid);
> +
> +    /* Check the file on the host */
> +    ok = g_file_get_contents(filename, &contents, &length, NULL);
> +    g_assert(ok);
> +    g_assert_cmpint(length, ==, strlen(str));
> +    g_assert_cmpint(memcmp(contents, str, strlen(str)), ==, 0);
> +    g_free(contents);
> +}
> +
> +static void register_vhost_user_fs_test(void)
> +{
> +    g_autofree gchar *cmd_line =
> +        g_strdup_printf("-chardev socket,id=char-virtio-fs,path=%s",
> +                        socket_path);
> +    QOSGraphTestOptions opts = {
> +        .edge.before_cmd_line = cmd_line,
> +        .before = before_test,
> +        .after = after_test,
> +    };
> +
> +    if (geteuid() != 0) {
> +        g_test_message("Skipping vhost-user-fs tests because root is "
> +                       "required for virtiofsd");
> +        return;
> +    }
> +
> +    qtest_add_abrt_handler(abrt_handler, NULL);
> +
> +    qos_add_test("config", "virtio-fs", test_config, &opts);
> +    qos_add_test("file-from-host", "virtio-fs", test_file_from_host, &opts);
> +    qos_add_test("file-from-guest", "virtio-fs", test_file_from_guest, &opts);
> +}
> +
> +libqos_init(register_vhost_user_fs_test);
> +
> +static void __attribute__((constructor)) init_paths(void)
> +{
> +    socket_path = g_strdup_printf("/tmp/qtest-%d-vhost-fs.sock", getpid());
> +    shared_dir = g_strdup_printf("/tmp/qtest-%d-virtio-fs-dir", getpid());
> +}
> +
> +static void __attribute__((destructor)) destroy_paths(void)
> +{
> +    g_free(shared_dir);
> +    shared_dir = NULL;
> +
> +    g_free(socket_path);
> +    socket_path = NULL;
> +}
> -- 
> 2.21.0
> 
--
Dr. David Alan Gilbert / dgilbert@redhat.com / Manchester, UK



^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [RFC 3/3] tests/vhost-user-fs-test: add vhost-user-fs test case
  2019-10-29  0:36   ` Dr. David Alan Gilbert
@ 2019-11-05 16:02     ` Stefan Hajnoczi
  2019-11-07 12:26       ` Dr. David Alan Gilbert
  0 siblings, 1 reply; 11+ messages in thread
From: Stefan Hajnoczi @ 2019-11-05 16:02 UTC (permalink / raw)
  To: Dr. David Alan Gilbert
  Cc: Laurent Vivier, Thomas Huth, Michael S. Tsirkin, Cornelia Huck,
	qemu-devel, virtio-fs, Stefan Hajnoczi, Paolo Bonzini, vgoyal

[-- Attachment #1: Type: text/plain, Size: 1758 bytes --]

On Tue, Oct 29, 2019 at 12:36:05AM +0000, Dr. David Alan Gilbert wrote:
> * Stefan Hajnoczi (stefanha@redhat.com) wrote:
> > +static void after_test(void *arg G_GNUC_UNUSED)
> > +{
> > +    unlink(socket_path);
> > +
> > +    remove_dir_and_children(shared_dir);
> 
> This scares me. Especially since it's running as root.
> Can we add a bunch of paranoid checks to make sure it doesn't
> end up rm -rf / ?

Yes, we can resolve the path and check it is not "/".

> > +/* Open a file by nodeid using FUSE_OPEN */
> > +static int32_t fuse_open(QVirtioFS *vfs, uint64_t nodeid, uint32_t flags,
> > +                         uint64_t *fh)
> > +{
> > +    struct fuse_in_header in_hdr = {
> > +        .opcode = guest32(FUSE_OPEN),
> > +        .unique = guest64(virtio_fs_get_unique(vfs)),
> > +        .nodeid = guest64(nodeid),
> > +    };
> > +    struct fuse_open_in in = {
> > +        .flags = guest32(flags),
> > +    };
> > +    struct iovec sg_in[] = {
> > +        { .iov_base = &in_hdr, .iov_len = sizeof(in_hdr) },
> > +        { .iov_base = &in, .iov_len = sizeof(in) },
> > +    };
> > +    struct fuse_out_header out_hdr;
> > +    struct fuse_open_out out;
> > +    struct iovec sg_out[] = {
> > +        { .iov_base = &out_hdr, .iov_len = sizeof(out_hdr) },
> > +        { .iov_base = &out, .iov_len = sizeof(out) },
> > +    };
> 
> I wonder if anything can be done to reduce the size of the iovec boiler
> plate?

I'm not aware of a clean way to build the iovec array automatically but
we could do this if you prefer it:

  #define IOVEC(elem) { .iov_base = &elem, .iov_len = sizeof(elem) }

  struct iovec sg_in[] = {
    IOVEC(in_hdr),
    IOVEC(in),
  };

Do you find this nicer?

Stefan

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [RFC 3/3] tests/vhost-user-fs-test: add vhost-user-fs test case
  2019-11-05 16:02     ` Stefan Hajnoczi
@ 2019-11-07 12:26       ` Dr. David Alan Gilbert
  0 siblings, 0 replies; 11+ messages in thread
From: Dr. David Alan Gilbert @ 2019-11-07 12:26 UTC (permalink / raw)
  To: Stefan Hajnoczi
  Cc: Laurent Vivier, Thomas Huth, Michael S. Tsirkin, Cornelia Huck,
	qemu-devel, virtio-fs, Stefan Hajnoczi, Paolo Bonzini, vgoyal

* Stefan Hajnoczi (stefanha@gmail.com) wrote:
> On Tue, Oct 29, 2019 at 12:36:05AM +0000, Dr. David Alan Gilbert wrote:
> > * Stefan Hajnoczi (stefanha@redhat.com) wrote:
> > > +static void after_test(void *arg G_GNUC_UNUSED)
> > > +{
> > > +    unlink(socket_path);
> > > +
> > > +    remove_dir_and_children(shared_dir);
> > 
> > This scares me. Especially since it's running as root.
> > Can we add a bunch of paranoid checks to make sure it doesn't
> > end up rm -rf / ?
> 
> Yes, we can resolve the path and check it is not "/".

I suggest checking for "/", ".", ".." and ""
if any of those get in it's probably bad.

> > > +/* Open a file by nodeid using FUSE_OPEN */
> > > +static int32_t fuse_open(QVirtioFS *vfs, uint64_t nodeid, uint32_t flags,
> > > +                         uint64_t *fh)
> > > +{
> > > +    struct fuse_in_header in_hdr = {
> > > +        .opcode = guest32(FUSE_OPEN),
> > > +        .unique = guest64(virtio_fs_get_unique(vfs)),
> > > +        .nodeid = guest64(nodeid),
> > > +    };
> > > +    struct fuse_open_in in = {
> > > +        .flags = guest32(flags),
> > > +    };
> > > +    struct iovec sg_in[] = {
> > > +        { .iov_base = &in_hdr, .iov_len = sizeof(in_hdr) },
> > > +        { .iov_base = &in, .iov_len = sizeof(in) },
> > > +    };
> > > +    struct fuse_out_header out_hdr;
> > > +    struct fuse_open_out out;
> > > +    struct iovec sg_out[] = {
> > > +        { .iov_base = &out_hdr, .iov_len = sizeof(out_hdr) },
> > > +        { .iov_base = &out, .iov_len = sizeof(out) },
> > > +    };
> > 
> > I wonder if anything can be done to reduce the size of the iovec boiler
> > plate?
> 
> I'm not aware of a clean way to build the iovec array automatically but
> we could do this if you prefer it:
> 
>   #define IOVEC(elem) { .iov_base = &elem, .iov_len = sizeof(elem) }
> 
>   struct iovec sg_in[] = {
>     IOVEC(in_hdr),
>     IOVEC(in),
>   };
> 
> Do you find this nicer?

Only a little; probably not worth it.

Dave

> Stefan


--
Dr. David Alan Gilbert / dgilbert@redhat.com / Manchester, UK



^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [RFC 1/3] WIP virtiofsd: import Linux <fuse.h> header file
  2019-10-27 12:36     ` Stefan Hajnoczi
@ 2020-06-01 10:28       ` Alex Bennée
  2020-06-01 15:55         ` Stefan Hajnoczi
  0 siblings, 1 reply; 11+ messages in thread
From: Alex Bennée @ 2020-06-01 10:28 UTC (permalink / raw)
  To: Stefan Hajnoczi
  Cc: Laurent Vivier, Thomas Huth, Michael S. Tsirkin, Cornelia Huck,
	Dr. David Alan Gilbert, qemu-devel, virtio-fs, Paolo Bonzini,
	vgoyal


Stefan Hajnoczi <stefanha@redhat.com> writes:

> On Sat, Oct 26, 2019 at 05:49:11PM -0400, Michael S. Tsirkin wrote:
>> On Fri, Oct 25, 2019 at 12:01:50PM +0200, Stefan Hajnoczi wrote:
>> > tests/vhost-user-fs-test.c needs fuse.h.  The private copy that
>> > virtiofsd has can be replaced with a properly imported file using
>> > update-linux-headers.sh.
>> > 
>> > TODO rerun update-linux-headers.sh with upstream kernel tree!
>> > 
>> > Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
>> 
>> OK I would just add this with the virtiofsd patchset.
>
> Yes, I'll talk to David.

Catching up after the fact I see this didn't get merged. Was there a
reason?

-- 
Alex Bennée


^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [RFC 1/3] WIP virtiofsd: import Linux <fuse.h> header file
  2020-06-01 10:28       ` Alex Bennée
@ 2020-06-01 15:55         ` Stefan Hajnoczi
  0 siblings, 0 replies; 11+ messages in thread
From: Stefan Hajnoczi @ 2020-06-01 15:55 UTC (permalink / raw)
  To: Alex Bennée
  Cc: Laurent Vivier, Thomas Huth, Michael S. Tsirkin, Cornelia Huck,
	Dr. David Alan Gilbert, qemu-devel, virtio-fs, Paolo Bonzini,
	vgoyal

[-- Attachment #1: Type: text/plain, Size: 1062 bytes --]

On Mon, Jun 01, 2020 at 11:28:44AM +0100, Alex Bennée wrote:
> 
> Stefan Hajnoczi <stefanha@redhat.com> writes:
> 
> > On Sat, Oct 26, 2019 at 05:49:11PM -0400, Michael S. Tsirkin wrote:
> >> On Fri, Oct 25, 2019 at 12:01:50PM +0200, Stefan Hajnoczi wrote:
> >> > tests/vhost-user-fs-test.c needs fuse.h.  The private copy that
> >> > virtiofsd has can be replaced with a properly imported file using
> >> > update-linux-headers.sh.
> >> > 
> >> > TODO rerun update-linux-headers.sh with upstream kernel tree!
> >> > 
> >> > Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
> >> 
> >> OK I would just add this with the virtiofsd patchset.
> >
> > Yes, I'll talk to David.
> 
> Catching up after the fact I see this didn't get merged. Was there a
> reason?

This patch series is stalled because I have been busy with other tasks.

The standard-headers fuse.h header will be needed when something outside
virtiofsd uses FUSE definitions for virtio-fs. At the moment nothing
does so there is no need to merge this patch yet.

Stefan

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]

^ permalink raw reply	[flat|nested] 11+ messages in thread

end of thread, other threads:[~2020-06-01 15:56 UTC | newest]

Thread overview: 11+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2019-10-25 10:01 [RFC 0/3] tests/vhost-user-fs-test: add vhost-user-fs test case Stefan Hajnoczi
2019-10-25 10:01 ` [RFC 1/3] WIP virtiofsd: import Linux <fuse.h> header file Stefan Hajnoczi
2019-10-26 21:49   ` Michael S. Tsirkin
2019-10-27 12:36     ` Stefan Hajnoczi
2020-06-01 10:28       ` Alex Bennée
2020-06-01 15:55         ` Stefan Hajnoczi
2019-10-25 10:01 ` [RFC 2/3] qgraph: add an "after" test callback function Stefan Hajnoczi
2019-10-25 10:01 ` [RFC 3/3] tests/vhost-user-fs-test: add vhost-user-fs test case Stefan Hajnoczi
2019-10-29  0:36   ` Dr. David Alan Gilbert
2019-11-05 16:02     ` Stefan Hajnoczi
2019-11-07 12:26       ` Dr. David Alan Gilbert

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).