Commit 0e1085819e for qemu.org

commit 0e1085819e444365bc3160f7b1adae3843b2da79
Author: Christian Schoenebeck <qemu_oss@crudebyte.com>
Date:   Sat Jun 13 16:55:49 2026 +0200

    tests/9p: add 3 xattr FID limit test cases (synth fs driver)

    Add 3 test cases to verify correct xattr FID limit enforcement of
    9pfs server.

     - 1. test with default max_xattr=1024
     - 2. test with custom max_xattr=100
     - 3. test with unlimited max_xattr=0

    These are tests using the synth driver. Advantage: by using the
    synth driver the tests cannot only check when the xattr FID limit
    kicks in (server would return an Rlerror response with ENOSPC),
    but can also validate the current 9p server internal xattr FID
    counter at any moment.

    This is a slow test (may take several seconds) and therefore
    registered as "slow" test and not running by default.

    Use -m slow to run this test.

    Link: https://lore.kernel.org/qemu-devel/540c51faa074d9dd736bbf2170084a12288e23ef.1781361555.git.qemu_oss@crudebyte.com
    Signed-off-by: Christian Schoenebeck <qemu_oss@crudebyte.com>

diff --git a/tests/qtest/virtio-9p-test.c b/tests/qtest/virtio-9p-test.c
index 99a897b158..2e88429dfa 100644
--- a/tests/qtest/virtio-9p-test.c
+++ b/tests/qtest/virtio-9p-test.c
@@ -35,6 +35,15 @@
 #define tclunk(...) v9fs_tclunk((TClunkOpt) __VA_ARGS__)
 #define txattrcreate(...) v9fs_txattrcreate((TXattrCreateOpt) __VA_ARGS__)

+/*
+ * xattr size to be used for xattr tests
+ *
+ * 64k is the max. xattr size supported by the Linux kernel, However btrfs
+ * for instance supports only 16219 bytes. So let's be conservative and
+ * just use 8k for the xattr tests.
+ */
+#define TEST_XATTR_SIZE (8 * 1024)
+
 static void pci_config(void *obj, void *data, QGuestAllocator *t_alloc)
 {
     QVirtio9P *v9p = obj;
@@ -107,6 +116,42 @@ static bool fs_dirents_contain_name(struct V9fsDirent *e, const char* name)
     return false;
 }

+/*
+ * Returns the current internal xattr FID count (works with synth driver only).
+ */
+static size_t get_xattr_count(QVirtio9P *v9p)
+{
+    uint16_t nwqid;
+    v9fs_qid *wqid;
+    const char *xattr_count_path[] = { "stat", "xattr_count" };
+    size_t xattr_count;
+    uint32_t bytes_read;
+
+    /* walk to /stat/xattr_count file */
+    uint32_t fid = twalk({
+        .client = v9p, .fid = 0,
+        .nwname = 2, .wnames = (char **)xattr_count_path,
+        .rwalk = { .nwqid = &nwqid, .wqid = &wqid }
+    }).newfid;
+
+    /* open for read */
+    tlopen({
+        .client = v9p, .fid = fid, .flags = O_RDONLY,
+        .rlopen = { .qid = NULL, .iounit = NULL }
+    });
+
+    /* read the internal xattr FID count */
+    tread({
+        .client = v9p, .fid = fid, .offset = 0, .count = sizeof(xattr_count),
+        .rread = { .count = &bytes_read, .data = &xattr_count }
+    });
+
+    /* cleanup */
+    tclunk({ .client = v9p, .fid = fid });
+
+    return xattr_count;
+}
+
 /* basic readdir test where reply fits into a single response message */
 static void fs_readdir(void *obj, void *data, QGuestAllocator *t_alloc)
 {
@@ -248,6 +293,108 @@ static void do_readdir_split(QVirtio9P *v9p, uint32_t count)
     g_free(wnames[0]);
 }

+/*
+ * Test 9p server's xattr FID count limit enforcement.
+ *
+ * Shared test code for both 'synth' and 'local' driver to verify correct
+ * behaviour of 9p server enforcing preconfigured xattr FID count limit
+ * correctly.
+ *
+ * @v9p: 9pfs client
+ *
+ * @max_xattr: max. allowed xattr FIDs, or -1 for infinite
+ *
+ * @check_counter: whether to verify 9p server internal xattr FID counter
+ *                 (only works with 'synth' fs driver)
+ */
+static void do_xattr_limit(QVirtio9P *v9p, int max_xattr, bool check_counter)
+{
+    size_t count;
+    int i;
+    int limit = (max_xattr != -1) ? max_xattr : V9FS_MAX_XATTR_DEFAULT + 100;
+    g_autofree uint32_t *fids = g_new0(uint32_t, limit);
+    uint32_t err_fid = 0;
+    const char *file_path[] = { QTEST_V9FS_SYNTH_WRITE_FILE };
+    g_autofree uint8_t *xattr_data = g_malloc(TEST_XATTR_SIZE);
+
+    if (!g_test_slow()) {
+        g_test_skip("This is a slow test, run with -m slow");
+        return;
+    }
+
+    /* prepare xattr data with 'X' characters */
+    memset(xattr_data, 'X', TEST_XATTR_SIZE);
+
+    tattach({ .client = v9p });
+
+    /* create max. amount of permitted xattrs */
+    for (i = 0; i < limit; i++) {
+        /* walk to create a new fid */
+        fids[i] = twalk({
+            .client = v9p, .fid = 0,
+            .nwname = 1, .wnames = (char **) file_path
+        }).newfid;
+
+        /* create new xattr fid */
+        txattrcreate({
+            .client = v9p, .fid = fids[i], .name = "user.test",
+            .size = TEST_XATTR_SIZE, .flags = 0
+        });
+
+        /* transfer the xattr data */
+        twrite({
+            .client = v9p, .fid = fids[i], .offset = 0,
+            .count = TEST_XATTR_SIZE, .data = xattr_data
+        });
+
+        /* verify server internal xattr counter */
+        if (check_counter) {
+            count = get_xattr_count(v9p);
+            g_assert_cmpuint(count, ==, (i + 1));
+        }
+
+        /* avoid virtio descriptor exhaustion */
+        qvirtqueue_reset_pool(v9p->vq);
+    }
+
+    /* if xattrs are limited, the next xattr should fail */
+    if (max_xattr != -1) {
+        /* walk to create another fid */
+        err_fid = twalk({
+            .client = v9p, .fid = 0,
+            .nwname = 1, .wnames = (char **) file_path
+        }).newfid;
+
+        /* try to create one more xattr fid - should fail */
+        txattrcreate({
+            .client = v9p, .fid = err_fid, .name = "user.test_exceed",
+            .size = TEST_XATTR_SIZE, .flags = 0,
+            .expectErr = ENOSPC
+        });
+
+        /* verify internal xattr counter hasn't changed */
+        if (check_counter) {
+            count = get_xattr_count(v9p);
+            g_assert_cmpuint(count, ==, limit);
+        }
+    }
+
+    /* clunk all fids (should decrement xattr counter) */
+    for (i = 0; i < limit; i++) {
+        tclunk({ .client = v9p, .fid = fids[i] });
+        qvirtqueue_reset_pool(v9p->vq);
+    }
+    if (err_fid) {
+        tclunk({ .client = v9p, .fid = err_fid });
+    }
+
+    /* verify internal xattr counter is zero */
+    if (check_counter) {
+        count = get_xattr_count(v9p);
+        g_assert_cmpuint(count, ==, 0);
+    }
+}
+
 static void fs_walk_no_slash(void *obj, void *data, QGuestAllocator *t_alloc)
 {
     QVirtio9P *v9p = obj;
@@ -508,6 +655,27 @@ static void fs_readdir_split_512(void *obj, void *data,
     do_readdir_split(obj, 512);
 }

+static void fs_synth_xattr_limit_default(void *obj, void *data,
+                                         QGuestAllocator *t_alloc)
+{
+    v9fs_set_allocator(t_alloc);
+    do_xattr_limit(obj, V9FS_MAX_XATTR_DEFAULT, true);
+}
+
+static void fs_synth_xattr_limit_custom(void *obj, void *data,
+                                        QGuestAllocator *t_alloc)
+{
+    v9fs_set_allocator(t_alloc);
+    do_xattr_limit(obj, 100, true);
+}
+
+static void fs_synth_xattr_limit_unlimited(void *obj, void *data,
+                                           QGuestAllocator *t_alloc)
+{
+    v9fs_set_allocator(t_alloc);
+    do_xattr_limit(obj, -1, true);
+}
+

 /* tests using the 9pfs 'local' fs driver */

@@ -822,6 +990,18 @@ static void fs_deep_absolute_path(void *obj, void *data,
     g_string_free(path, TRUE);
 }

+static void *synth_max_xattr_custom_opt(GString *cmd_line, void *arg)
+{
+    virtio_9p_add_synth_driver_args(cmd_line, "max_xattr=100");
+    return arg;
+}
+
+static void *synth_max_xattr_unlimited_opt(GString *cmd_line, void *arg)
+{
+    virtio_9p_add_synth_driver_args(cmd_line, "max_xattr=0");
+    return arg;
+}
+
 static void cleanup_9p_local_driver(void *data)
 {
     /* remove previously created test dir when test is completed */
@@ -872,7 +1052,14 @@ static void register_virtio_9p_test(void)
                  fs_readdir_split_256,  &opts);
     qos_add_test("synth/readdir/split_128", "virtio-9p",
                  fs_readdir_split_128,  &opts);
-
+    qos_add_test("synth/xattr_limit/default", "virtio-9p",
+                 fs_synth_xattr_limit_default, &opts);
+    opts.before = synth_max_xattr_custom_opt;
+    qos_add_test("synth/xattr_limit/custom", "virtio-9p",
+                 fs_synth_xattr_limit_custom, &opts);
+    opts.before = synth_max_xattr_unlimited_opt;
+    qos_add_test("synth/xattr_limit/unlimited", "virtio-9p",
+                 fs_synth_xattr_limit_unlimited, &opts);

     /* 9pfs test cases using the 'local' filesystem driver */
     opts.before = assign_9p_local_driver;