@samitouri / QOSamiQemu / commits / 0e1085819e

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>

Christian Schoenebeck committed Jun 13, 2026 at 16:55 UTC 0e1085819e444365bc3160f7b1adae3843b2da79
1 file changed +188 -1
tests/qtest/virtio-9p-test.c
+188 -1
@@ -35,6 +35,15 @@
35 #define tclunk(...) v9fs_tclunk((TClunkOpt) __VA_ARGS__)
36 #define txattrcreate(...) v9fs_txattrcreate((TXattrCreateOpt) __VA_ARGS__)
37
38 +/*
39 + * xattr size to be used for xattr tests
40 + *
41 + * 64k is the max. xattr size supported by the Linux kernel, However btrfs
42 + * for instance supports only 16219 bytes. So let's be conservative and
43 + * just use 8k for the xattr tests.
44 + */
45 +#define TEST_XATTR_SIZE (8 * 1024)
46 +
47 static void pci_config(void *obj, void *data, QGuestAllocator *t_alloc)
48 {
49 QVirtio9P *v9p = obj;
@@ -107,6 +116,42 @@ static bool fs_dirents_contain_name(struct V9fsDirent *e, const char* name)
116 return false;
117 }
118
119 +/*
120 + * Returns the current internal xattr FID count (works with synth driver only).
121 + */
122 +static size_t get_xattr_count(QVirtio9P *v9p)
123 +{
124 + uint16_t nwqid;
125 + v9fs_qid *wqid;
126 + const char *xattr_count_path[] = { "stat", "xattr_count" };
127 + size_t xattr_count;
128 + uint32_t bytes_read;
129 +
130 + /* walk to /stat/xattr_count file */
131 + uint32_t fid = twalk({
132 + .client = v9p, .fid = 0,
133 + .nwname = 2, .wnames = (char **)xattr_count_path,
134 + .rwalk = { .nwqid = &nwqid, .wqid = &wqid }
135 + }).newfid;
136 +
137 + /* open for read */
138 + tlopen({
139 + .client = v9p, .fid = fid, .flags = O_RDONLY,
140 + .rlopen = { .qid = NULL, .iounit = NULL }
141 + });
142 +
143 + /* read the internal xattr FID count */
144 + tread({
145 + .client = v9p, .fid = fid, .offset = 0, .count = sizeof(xattr_count),
146 + .rread = { .count = &bytes_read, .data = &xattr_count }
147 + });
148 +
149 + /* cleanup */
150 + tclunk({ .client = v9p, .fid = fid });
151 +
152 + return xattr_count;
153 +}
154 +
155 /* basic readdir test where reply fits into a single response message */
156 static void fs_readdir(void *obj, void *data, QGuestAllocator *t_alloc)
157 {
@@ -248,6 +293,108 @@ static void do_readdir_split(QVirtio9P *v9p, uint32_t count)
293 g_free(wnames[0]);
294 }
295
296 +/*
297 + * Test 9p server's xattr FID count limit enforcement.
298 + *
299 + * Shared test code for both 'synth' and 'local' driver to verify correct
300 + * behaviour of 9p server enforcing preconfigured xattr FID count limit
301 + * correctly.
302 + *
303 + * @v9p: 9pfs client
304 + *
305 + * @max_xattr: max. allowed xattr FIDs, or -1 for infinite
306 + *
307 + * @check_counter: whether to verify 9p server internal xattr FID counter
308 + * (only works with 'synth' fs driver)
309 + */
310 +static void do_xattr_limit(QVirtio9P *v9p, int max_xattr, bool check_counter)
311 +{
312 + size_t count;
313 + int i;
314 + int limit = (max_xattr != -1) ? max_xattr : V9FS_MAX_XATTR_DEFAULT + 100;
315 + g_autofree uint32_t *fids = g_new0(uint32_t, limit);
316 + uint32_t err_fid = 0;
317 + const char *file_path[] = { QTEST_V9FS_SYNTH_WRITE_FILE };
318 + g_autofree uint8_t *xattr_data = g_malloc(TEST_XATTR_SIZE);
319 +
320 + if (!g_test_slow()) {
321 + g_test_skip("This is a slow test, run with -m slow");
322 + return;
323 + }
324 +
325 + /* prepare xattr data with 'X' characters */
326 + memset(xattr_data, 'X', TEST_XATTR_SIZE);
327 +
328 + tattach({ .client = v9p });
329 +
330 + /* create max. amount of permitted xattrs */
331 + for (i = 0; i < limit; i++) {
332 + /* walk to create a new fid */
333 + fids[i] = twalk({
334 + .client = v9p, .fid = 0,
335 + .nwname = 1, .wnames = (char **) file_path
336 + }).newfid;
337 +
338 + /* create new xattr fid */
339 + txattrcreate({
340 + .client = v9p, .fid = fids[i], .name = "user.test",
341 + .size = TEST_XATTR_SIZE, .flags = 0
342 + });
343 +
344 + /* transfer the xattr data */
345 + twrite({
346 + .client = v9p, .fid = fids[i], .offset = 0,
347 + .count = TEST_XATTR_SIZE, .data = xattr_data
348 + });
349 +
350 + /* verify server internal xattr counter */
351 + if (check_counter) {
352 + count = get_xattr_count(v9p);
353 + g_assert_cmpuint(count, ==, (i + 1));
354 + }
355 +
356 + /* avoid virtio descriptor exhaustion */
357 + qvirtqueue_reset_pool(v9p->vq);
358 + }
359 +
360 + /* if xattrs are limited, the next xattr should fail */
361 + if (max_xattr != -1) {
362 + /* walk to create another fid */
363 + err_fid = twalk({
364 + .client = v9p, .fid = 0,
365 + .nwname = 1, .wnames = (char **) file_path
366 + }).newfid;
367 +
368 + /* try to create one more xattr fid - should fail */
369 + txattrcreate({
370 + .client = v9p, .fid = err_fid, .name = "user.test_exceed",
371 + .size = TEST_XATTR_SIZE, .flags = 0,
372 + .expectErr = ENOSPC
373 + });
374 +
375 + /* verify internal xattr counter hasn't changed */
376 + if (check_counter) {
377 + count = get_xattr_count(v9p);
378 + g_assert_cmpuint(count, ==, limit);
379 + }
380 + }
381 +
382 + /* clunk all fids (should decrement xattr counter) */
383 + for (i = 0; i < limit; i++) {
384 + tclunk({ .client = v9p, .fid = fids[i] });
385 + qvirtqueue_reset_pool(v9p->vq);
386 + }
387 + if (err_fid) {
388 + tclunk({ .client = v9p, .fid = err_fid });
389 + }
390 +
391 + /* verify internal xattr counter is zero */
392 + if (check_counter) {
393 + count = get_xattr_count(v9p);
394 + g_assert_cmpuint(count, ==, 0);
395 + }
396 +}
397 +
398 static void fs_walk_no_slash(void *obj, void *data, QGuestAllocator *t_alloc)
399 {
400 QVirtio9P *v9p = obj;
@@ -508,6 +655,27 @@ static void fs_readdir_split_512(void *obj, void *data,
655 do_readdir_split(obj, 512);
656 }
657
658 +static void fs_synth_xattr_limit_default(void *obj, void *data,
659 + QGuestAllocator *t_alloc)
660 +{
661 + v9fs_set_allocator(t_alloc);
662 + do_xattr_limit(obj, V9FS_MAX_XATTR_DEFAULT, true);
663 +}
664 +
665 +static void fs_synth_xattr_limit_custom(void *obj, void *data,
666 + QGuestAllocator *t_alloc)
667 +{
668 + v9fs_set_allocator(t_alloc);
669 + do_xattr_limit(obj, 100, true);
670 +}
671 +
672 +static void fs_synth_xattr_limit_unlimited(void *obj, void *data,
673 + QGuestAllocator *t_alloc)
674 +{
675 + v9fs_set_allocator(t_alloc);
676 + do_xattr_limit(obj, -1, true);
677 +}
678 +
679
680 /* tests using the 9pfs 'local' fs driver */
681
@@ -822,6 +990,18 @@ static void fs_deep_absolute_path(void *obj, void *data,
990 g_string_free(path, TRUE);
991 }
992
993 +static void *synth_max_xattr_custom_opt(GString *cmd_line, void *arg)
994 +{
995 + virtio_9p_add_synth_driver_args(cmd_line, "max_xattr=100");
996 + return arg;
997 +}
998 +
999 +static void *synth_max_xattr_unlimited_opt(GString *cmd_line, void *arg)
1000 +{
1001 + virtio_9p_add_synth_driver_args(cmd_line, "max_xattr=0");
1002 + return arg;
1003 +}
1004 +
1005 static void cleanup_9p_local_driver(void *data)
1006 {
1007 /* remove previously created test dir when test is completed */
@@ -872,7 +1052,14 @@ static void register_virtio_9p_test(void)
1052 fs_readdir_split_256, &opts);
1053 qos_add_test("synth/readdir/split_128", "virtio-9p",
1054 fs_readdir_split_128, &opts);
875 -
1055 + qos_add_test("synth/xattr_limit/default", "virtio-9p",
1056 + fs_synth_xattr_limit_default, &opts);
1057 + opts.before = synth_max_xattr_custom_opt;
1058 + qos_add_test("synth/xattr_limit/custom", "virtio-9p",
1059 + fs_synth_xattr_limit_custom, &opts);
1060 + opts.before = synth_max_xattr_unlimited_opt;
1061 + qos_add_test("synth/xattr_limit/unlimited", "virtio-9p",
1062 + fs_synth_xattr_limit_unlimited, &opts);
1063
1064 /* 9pfs test cases using the 'local' filesystem driver */
1065 opts.before = assign_9p_local_driver;