master
c 1,153 lines 35.4 KB
Raw
1 /*
2 * QTest testcase for VirtIO 9P
3 *
4 * Copyright (c) 2014 SUSE LINUX Products GmbH
5 *
6 * This work is licensed under the terms of the GNU GPL, version 2 or later.
7 * See the COPYING file in the top-level directory.
8 */
9
10 /*
11 * Not so fast! You might want to read the 9p developer docs first:
12 * https://wiki.qemu.org/Documentation/9p
13 */
14
15 #include "qemu/osdep.h"
16 #include "qemu/module.h"
17 #include "libqos/virtio.h"
18 #include "libqos/virtio-9p-client.h"
19
20 #define twalk(...) v9fs_twalk((TWalkOpt) __VA_ARGS__)
21 #define tversion(...) v9fs_tversion((TVersionOpt) __VA_ARGS__)
22 #define tattach(...) v9fs_tattach((TAttachOpt) __VA_ARGS__)
23 #define tgetattr(...) v9fs_tgetattr((TGetAttrOpt) __VA_ARGS__)
24 #define tsetattr(...) v9fs_tsetattr((TSetAttrOpt) __VA_ARGS__)
25 #define treaddir(...) v9fs_treaddir((TReadDirOpt) __VA_ARGS__)
26 #define tlopen(...) v9fs_tlopen((TLOpenOpt) __VA_ARGS__)
27 #define twrite(...) v9fs_twrite((TWriteOpt) __VA_ARGS__)
28 #define tflush(...) v9fs_tflush((TFlushOpt) __VA_ARGS__)
29 #define tmkdir(...) v9fs_tmkdir((TMkdirOpt) __VA_ARGS__)
30 #define tlcreate(...) v9fs_tlcreate((TlcreateOpt) __VA_ARGS__)
31 #define tsymlink(...) v9fs_tsymlink((TsymlinkOpt) __VA_ARGS__)
32 #define tlink(...) v9fs_tlink((TlinkOpt) __VA_ARGS__)
33 #define tunlinkat(...) v9fs_tunlinkat((TunlinkatOpt) __VA_ARGS__)
34 #define tread(...) v9fs_tread((TReadOpt) __VA_ARGS__)
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 on high-level VFS
42 * layer. However filesystems impose their own limits:
43 *
44 * - btrfs: 16219 bytes
45 *
46 * - ext4: blocksize - 56 bytes (~4KB) if no ea_inode capability enabled,
47 * 64k if ea_inode enabled
48 *
49 * So let's be conservative and just use 1k for the xattr tests.
50 */
51 #define TEST_XATTR_SIZE (1 * 1024)
52
53 static void pci_config(void *obj, void *data, QGuestAllocator *t_alloc)
54 {
55 QVirtio9P *v9p = obj;
56 v9fs_set_allocator(t_alloc);
57 size_t tag_len = qvirtio_config_readw(v9p->vdev, 0);
58 g_autofree char *tag = NULL;
59 int i;
60
61 g_assert_cmpint(tag_len, ==, strlen(MOUNT_TAG));
62
63 tag = g_malloc(tag_len);
64 for (i = 0; i < tag_len; i++) {
65 tag[i] = qvirtio_config_readb(v9p->vdev, i + 2);
66 }
67 g_assert_cmpmem(tag, tag_len, MOUNT_TAG, tag_len);
68 }
69
70 static inline bool is_same_qid(v9fs_qid a, v9fs_qid b)
71 {
72 /* don't compare QID version for checking for file ID equalness */
73 return a[0] == b[0] && memcmp(&a[5], &b[5], 8) == 0;
74 }
75
76 static void fs_version(void *obj, void *data, QGuestAllocator *t_alloc)
77 {
78 v9fs_set_allocator(t_alloc);
79 tversion({ .client = obj });
80 }
81
82 static void fs_attach(void *obj, void *data, QGuestAllocator *t_alloc)
83 {
84 v9fs_set_allocator(t_alloc);
85 tattach({ .client = obj });
86 }
87
88 static void fs_walk(void *obj, void *data, QGuestAllocator *t_alloc)
89 {
90 QVirtio9P *v9p = obj;
91 v9fs_set_allocator(t_alloc);
92 char *wnames[P9_MAXWELEM];
93 uint16_t nwqid;
94 g_autofree v9fs_qid *wqid = NULL;
95 int i;
96
97 for (i = 0; i < P9_MAXWELEM; i++) {
98 wnames[i] = g_strdup_printf(QTEST_V9FS_SYNTH_WALK_FILE, i);
99 }
100
101 tattach({ .client = v9p });
102 twalk({
103 .client = v9p, .fid = 0, .newfid = 1,
104 .nwname = P9_MAXWELEM, .wnames = wnames,
105 .rwalk = { .nwqid = &nwqid, .wqid = &wqid }
106 });
107
108 g_assert_cmpint(nwqid, ==, P9_MAXWELEM);
109
110 for (i = 0; i < P9_MAXWELEM; i++) {
111 g_free(wnames[i]);
112 }
113 }
114
115 static bool fs_dirents_contain_name(struct V9fsDirent *e, const char* name)
116 {
117 for (; e; e = e->next) {
118 if (!strcmp(e->name, name)) {
119 return true;
120 }
121 }
122 return false;
123 }
124
125 /*
126 * Returns the current internal xattr FID count (works with synth driver only).
127 */
128 static size_t get_xattr_count(QVirtio9P *v9p)
129 {
130 uint16_t nwqid;
131 v9fs_qid *wqid;
132 const char *xattr_count_path[] = { "stat", "xattr_count" };
133 size_t xattr_count;
134 uint32_t bytes_read;
135
136 /* walk to /stat/xattr_count file */
137 uint32_t fid = twalk({
138 .client = v9p, .fid = 0,
139 .nwname = 2, .wnames = (char **)xattr_count_path,
140 .rwalk = { .nwqid = &nwqid, .wqid = &wqid }
141 }).newfid;
142
143 /* open for read */
144 tlopen({
145 .client = v9p, .fid = fid, .flags = O_RDONLY,
146 .rlopen = { .qid = NULL, .iounit = NULL }
147 });
148
149 /* read the internal xattr FID count */
150 tread({
151 .client = v9p, .fid = fid, .offset = 0, .count = sizeof(xattr_count),
152 .rread = { .count = &bytes_read, .data = &xattr_count }
153 });
154
155 /* cleanup */
156 tclunk({ .client = v9p, .fid = fid });
157
158 return xattr_count;
159 }
160
161 /* basic readdir test where reply fits into a single response message */
162 static void fs_readdir(void *obj, void *data, QGuestAllocator *t_alloc)
163 {
164 QVirtio9P *v9p = obj;
165 v9fs_set_allocator(t_alloc);
166 char *wnames[] = { g_strdup(QTEST_V9FS_SYNTH_READDIR_DIR) };
167 uint16_t nqid;
168 v9fs_qid qid;
169 uint32_t count, nentries;
170 struct V9fsDirent *entries = NULL;
171
172 tattach({ .client = v9p });
173 twalk({
174 .client = v9p, .fid = 0, .newfid = 1,
175 .nwname = 1, .wnames = wnames, .rwalk.nwqid = &nqid
176 });
177 g_assert_cmpint(nqid, ==, 1);
178
179 tlopen({
180 .client = v9p, .fid = 1, .flags = O_DIRECTORY, .rlopen.qid = &qid
181 });
182
183 /*
184 * submit count = msize - 11, because 11 is the header size of Rreaddir
185 */
186 treaddir({
187 .client = v9p, .fid = 1, .offset = 0, .count = P9_MAX_SIZE - 11,
188 .rreaddir = {
189 .count = &count, .nentries = &nentries, .entries = &entries
190 }
191 });
192
193 /*
194 * Assuming msize (P9_MAX_SIZE) is large enough so we can retrieve all
195 * dir entries with only one readdir request.
196 */
197 g_assert_cmpint(
198 nentries, ==,
199 QTEST_V9FS_SYNTH_READDIR_NFILES + 2 /* "." and ".." */
200 );
201
202 /*
203 * Check all file names exist in returned entries, ignore their order
204 * though.
205 */
206 g_assert_cmpint(fs_dirents_contain_name(entries, "."), ==, true);
207 g_assert_cmpint(fs_dirents_contain_name(entries, ".."), ==, true);
208 for (int i = 0; i < QTEST_V9FS_SYNTH_READDIR_NFILES; ++i) {
209 g_autofree char *name =
210 g_strdup_printf(QTEST_V9FS_SYNTH_READDIR_FILE, i);
211 g_assert_cmpint(fs_dirents_contain_name(entries, name), ==, true);
212 }
213
214 v9fs_free_dirents(entries);
215 g_free(wnames[0]);
216 }
217
218 /* readdir test where overall request is split over several messages */
219 static void do_readdir_split(QVirtio9P *v9p, uint32_t count)
220 {
221 char *wnames[] = { g_strdup(QTEST_V9FS_SYNTH_READDIR_DIR) };
222 uint16_t nqid;
223 v9fs_qid qid;
224 uint32_t nentries, npartialentries;
225 struct V9fsDirent *entries, *tail, *partialentries;
226 int fid;
227 uint64_t offset;
228
229 tattach({ .client = v9p });
230
231 fid = 1;
232 offset = 0;
233 entries = NULL;
234 nentries = 0;
235 tail = NULL;
236
237 twalk({
238 .client = v9p, .fid = 0, .newfid = fid,
239 .nwname = 1, .wnames = wnames, .rwalk.nwqid = &nqid
240 });
241 g_assert_cmpint(nqid, ==, 1);
242
243 tlopen({
244 .client = v9p, .fid = fid, .flags = O_DIRECTORY, .rlopen.qid = &qid
245 });
246
247 /*
248 * send as many Treaddir requests as required to get all directory
249 * entries
250 */
251 while (true) {
252 npartialentries = 0;
253 partialentries = NULL;
254
255 treaddir({
256 .client = v9p, .fid = fid, .offset = offset, .count = count,
257 .rreaddir = {
258 .count = &count, .nentries = &npartialentries,
259 .entries = &partialentries
260 }
261 });
262 if (npartialentries > 0 && partialentries) {
263 if (!entries) {
264 entries = partialentries;
265 nentries = npartialentries;
266 tail = partialentries;
267 } else {
268 tail->next = partialentries;
269 nentries += npartialentries;
270 }
271 while (tail->next) {
272 tail = tail->next;
273 }
274 offset = tail->offset;
275 } else {
276 break;
277 }
278 }
279
280 g_assert_cmpint(
281 nentries, ==,
282 QTEST_V9FS_SYNTH_READDIR_NFILES + 2 /* "." and ".." */
283 );
284
285 /*
286 * Check all file names exist in returned entries, ignore their order
287 * though.
288 */
289 g_assert_cmpint(fs_dirents_contain_name(entries, "."), ==, true);
290 g_assert_cmpint(fs_dirents_contain_name(entries, ".."), ==, true);
291 for (int i = 0; i < QTEST_V9FS_SYNTH_READDIR_NFILES; ++i) {
292 char *name = g_strdup_printf(QTEST_V9FS_SYNTH_READDIR_FILE, i);
293 g_assert_cmpint(fs_dirents_contain_name(entries, name), ==, true);
294 g_free(name);
295 }
296
297 v9fs_free_dirents(entries);
298
299 g_free(wnames[0]);
300 }
301
302 /*
303 * Test 9p server's xattr FID count limit enforcement.
304 *
305 * Shared test code for both 'synth' and 'local' driver to verify correct
306 * behaviour of 9p server enforcing preconfigured xattr FID count limit
307 * correctly.
308 *
309 * @v9p: 9pfs client
310 *
311 * @max_xattr: max. allowed xattr FIDs, or -1 for infinite
312 *
313 * @check_counter: whether to verify 9p server internal xattr FID counter
314 * (only works with 'synth' fs driver)
315 */
316 static void do_xattr_limit(QVirtio9P *v9p, int max_xattr, bool check_counter)
317 {
318 size_t count;
319 int i;
320 int limit = (max_xattr != -1) ? max_xattr : V9FS_MAX_XATTR_DEFAULT + 100;
321 g_autofree uint32_t *fids = g_new0(uint32_t, limit);
322 uint32_t err_fid = 0;
323 const char *file_path[] = { QTEST_V9FS_SYNTH_WRITE_FILE };
324 g_autofree uint8_t *xattr_data = g_malloc(TEST_XATTR_SIZE);
325
326 if (!g_test_slow()) {
327 g_test_skip("This is a slow test, run with -m slow");
328 return;
329 }
330
331 /* prepare xattr data with 'X' characters */
332 memset(xattr_data, 'X', TEST_XATTR_SIZE);
333
334 tattach({ .client = v9p });
335
336 /* create max. amount of permitted xattrs */
337 for (i = 0; i < limit; i++) {
338 /* walk to create a new fid */
339 fids[i] = twalk({
340 .client = v9p, .fid = 0,
341 .nwname = 1, .wnames = (char **) file_path
342 }).newfid;
343
344 /* create new xattr fid */
345 txattrcreate({
346 .client = v9p, .fid = fids[i], .name = "user.test",
347 .size = TEST_XATTR_SIZE, .flags = 0
348 });
349
350 /* transfer the xattr data */
351 twrite({
352 .client = v9p, .fid = fids[i], .offset = 0,
353 .count = TEST_XATTR_SIZE, .data = xattr_data
354 });
355
356 /* verify server internal xattr counter */
357 if (check_counter) {
358 count = get_xattr_count(v9p);
359 g_assert_cmpuint(count, ==, (i + 1));
360 }
361
362 /* avoid virtio descriptor exhaustion */
363 qvirtqueue_reset_pool(v9p->vq);
364 }
365
366 /* if xattrs are limited, the next xattr should fail */
367 if (max_xattr != -1) {
368 /* walk to create another fid */
369 err_fid = twalk({
370 .client = v9p, .fid = 0,
371 .nwname = 1, .wnames = (char **) file_path
372 }).newfid;
373
374 /* try to create one more xattr fid - should fail */
375 txattrcreate({
376 .client = v9p, .fid = err_fid, .name = "user.test_exceed",
377 .size = TEST_XATTR_SIZE, .flags = 0,
378 .expectErr = ENOSPC
379 });
380
381 /* verify internal xattr counter hasn't changed */
382 if (check_counter) {
383 count = get_xattr_count(v9p);
384 g_assert_cmpuint(count, ==, limit);
385 }
386 }
387
388 /* clunk all fids (should decrement xattr counter) */
389 for (i = 0; i < limit; i++) {
390 tclunk({ .client = v9p, .fid = fids[i] });
391 qvirtqueue_reset_pool(v9p->vq);
392 }
393 if (err_fid) {
394 tclunk({ .client = v9p, .fid = err_fid });
395 }
396
397 /* verify internal xattr counter is zero */
398 if (check_counter) {
399 count = get_xattr_count(v9p);
400 g_assert_cmpuint(count, ==, 0);
401 }
402 }
403
404 static void do_local_xattr_limit(QVirtio9P *v9p, int max_xattr)
405 {
406 g_autofree char *test_file = virtio_9p_test_path("WRITE");
407
408 /*
409 * this file must be created for the test to work with the 'local' fs driver
410 */
411 g_assert(g_file_set_contents(test_file, "", 0, NULL));
412
413 /* the actual test code shared with the 'synth' fs driver tests */
414 do_xattr_limit(v9p, max_xattr, false);
415 }
416
417 static void fs_walk_no_slash(void *obj, void *data, QGuestAllocator *t_alloc)
418 {
419 QVirtio9P *v9p = obj;
420 v9fs_set_allocator(t_alloc);
421 char *wnames[] = { g_strdup(" /") };
422
423 tattach({ .client = v9p });
424 twalk({
425 .client = v9p, .fid = 0, .newfid = 1, .nwname = 1, .wnames = wnames,
426 .expectErr = ENOENT
427 });
428
429 g_free(wnames[0]);
430 }
431
432 static void fs_walk_nonexistent(void *obj, void *data, QGuestAllocator *t_alloc)
433 {
434 QVirtio9P *v9p = obj;
435 v9fs_set_allocator(t_alloc);
436
437 tattach({ .client = v9p });
438 /*
439 * The 9p2000 protocol spec says: "If the first element cannot be walked
440 * for any reason, Rerror is returned."
441 */
442 twalk({ .client = v9p, .path = "non-existent", .expectErr = ENOENT });
443 }
444
445 static void fs_walk_2nd_nonexistent(void *obj, void *data,
446 QGuestAllocator *t_alloc)
447 {
448 QVirtio9P *v9p = obj;
449 v9fs_set_allocator(t_alloc);
450 v9fs_qid root_qid;
451 uint16_t nwqid;
452 uint32_t fid;
453 g_autofree v9fs_qid *wqid = NULL;
454 g_autofree char *path = g_strdup_printf(
455 QTEST_V9FS_SYNTH_WALK_FILE "/non-existent", 0
456 );
457
458 tattach({ .client = v9p, .rattach.qid = &root_qid });
459 fid = twalk({
460 .client = v9p, .path = path,
461 .rwalk = { .nwqid = &nwqid, .wqid = &wqid }
462 }).newfid;
463 /*
464 * The 9p2000 protocol spec says: "nwqid is therefore either nwname or the
465 * index of the first elementwise walk that failed."
466 */
467 assert(nwqid == 1);
468
469 /* returned QID wqid[0] is file ID of 1st subdir */
470 g_assert(wqid && wqid[0] && !is_same_qid(root_qid, wqid[0]));
471
472 /* expect fid being unaffected by walk above */
473 tgetattr({
474 .client = v9p, .fid = fid, .request_mask = P9_GETATTR_BASIC,
475 .expectErr = ENOENT
476 });
477 }
478
479 static void fs_walk_none(void *obj, void *data, QGuestAllocator *t_alloc)
480 {
481 QVirtio9P *v9p = obj;
482 v9fs_set_allocator(t_alloc);
483 v9fs_qid root_qid;
484 g_autofree v9fs_qid *wqid = NULL;
485 struct v9fs_attr attr;
486
487 tversion({ .client = v9p });
488 tattach({
489 .client = v9p, .fid = 0, .n_uname = getuid(),
490 .rattach.qid = &root_qid
491 });
492
493 twalk({
494 .client = v9p, .fid = 0, .newfid = 1, .nwname = 0, .wnames = NULL,
495 .rwalk.wqid = &wqid
496 });
497
498 /* special case: no QID is returned if nwname=0 was sent */
499 g_assert(wqid == NULL);
500
501 tgetattr({
502 .client = v9p, .fid = 1, .request_mask = P9_GETATTR_BASIC,
503 .rgetattr.attr = &attr
504 });
505
506 g_assert(is_same_qid(root_qid, attr.qid));
507 }
508
509 static void fs_walk_dotdot(void *obj, void *data, QGuestAllocator *t_alloc)
510 {
511 QVirtio9P *v9p = obj;
512 v9fs_set_allocator(t_alloc);
513 char *wnames[] = { g_strdup("..") };
514 v9fs_qid root_qid;
515 g_autofree v9fs_qid *wqid = NULL;
516
517 tversion({ .client = v9p });
518 tattach({
519 .client = v9p, .fid = 0, .n_uname = getuid(),
520 .rattach.qid = &root_qid
521 });
522
523 twalk({
524 .client = v9p, .fid = 0, .newfid = 1, .nwname = 1, .wnames = wnames,
525 .rwalk.wqid = &wqid /* We now we'll get one qid */
526 });
527
528 g_assert_cmpmem(&root_qid, 13, wqid[0], 13);
529
530 g_free(wnames[0]);
531 }
532
533 static void fs_lopen(void *obj, void *data, QGuestAllocator *t_alloc)
534 {
535 QVirtio9P *v9p = obj;
536 v9fs_set_allocator(t_alloc);
537 char *wnames[] = { g_strdup(QTEST_V9FS_SYNTH_LOPEN_FILE) };
538
539 tattach({ .client = v9p });
540 twalk({
541 .client = v9p, .fid = 0, .newfid = 1, .nwname = 1, .wnames = wnames
542 });
543
544 tlopen({ .client = v9p, .fid = 1, .flags = O_WRONLY });
545
546 g_free(wnames[0]);
547 }
548
549 static void fs_write(void *obj, void *data, QGuestAllocator *t_alloc)
550 {
551 QVirtio9P *v9p = obj;
552 v9fs_set_allocator(t_alloc);
553 static const uint32_t write_count = P9_MAX_SIZE / 2;
554 char *wnames[] = { g_strdup(QTEST_V9FS_SYNTH_WRITE_FILE) };
555 g_autofree char *buf = g_malloc0(write_count);
556 uint32_t count;
557
558 tattach({ .client = v9p });
559 twalk({
560 .client = v9p, .fid = 0, .newfid = 1, .nwname = 1, .wnames = wnames
561 });
562
563 tlopen({ .client = v9p, .fid = 1, .flags = O_WRONLY });
564
565 count = twrite({
566 .client = v9p, .fid = 1, .offset = 0, .count = write_count,
567 .data = buf
568 }).count;
569 g_assert_cmpint(count, ==, write_count);
570
571 g_free(wnames[0]);
572 }
573
574 static void fs_flush_success(void *obj, void *data, QGuestAllocator *t_alloc)
575 {
576 QVirtio9P *v9p = obj;
577 v9fs_set_allocator(t_alloc);
578 char *wnames[] = { g_strdup(QTEST_V9FS_SYNTH_FLUSH_FILE) };
579 P9Req *req, *flush_req;
580 uint32_t reply_len;
581 uint8_t should_block;
582
583 tattach({ .client = v9p });
584 twalk({
585 .client = v9p, .fid = 0, .newfid = 1, .nwname = 1, .wnames = wnames
586 });
587
588 tlopen({ .client = v9p, .fid = 1, .flags = O_WRONLY });
589
590 /* This will cause the 9p server to try to write data to the backend,
591 * until the write request gets cancelled.
592 */
593 should_block = 1;
594 req = twrite({
595 .client = v9p, .fid = 1, .offset = 0,
596 .count = sizeof(should_block), .data = &should_block,
597 .requestOnly = true
598 }).req;
599
600 flush_req = tflush({
601 .client = v9p, .oldtag = req->tag, .tag = 1, .requestOnly = true
602 }).req;
603
604 /* The write request is supposed to be flushed: the server should just
605 * mark the write request as used and reply to the flush request.
606 */
607 v9fs_req_wait_for_reply(req, &reply_len);
608 g_assert_cmpint(reply_len, ==, 0);
609 v9fs_req_free(req);
610 v9fs_rflush(flush_req);
611
612 g_free(wnames[0]);
613 }
614
615 static void fs_flush_ignored(void *obj, void *data, QGuestAllocator *t_alloc)
616 {
617 QVirtio9P *v9p = obj;
618 v9fs_set_allocator(t_alloc);
619 char *wnames[] = { g_strdup(QTEST_V9FS_SYNTH_FLUSH_FILE) };
620 P9Req *req, *flush_req;
621 uint32_t count;
622 uint8_t should_block;
623
624 tattach({ .client = v9p });
625 twalk({
626 .client = v9p, .fid = 0, .newfid = 1, .nwname = 1, .wnames = wnames
627 });
628
629 tlopen({ .client = v9p, .fid = 1, .flags = O_WRONLY });
630
631 /* This will cause the write request to complete right away, before it
632 * could be actually cancelled.
633 */
634 should_block = 0;
635 req = twrite({
636 .client = v9p, .fid = 1, .offset = 0,
637 .count = sizeof(should_block), .data = &should_block,
638 .requestOnly = true
639 }).req;
640
641 flush_req = tflush({
642 .client = v9p, .oldtag = req->tag, .tag = 1, .requestOnly = true
643 }).req;
644
645 /* The write request is supposed to complete. The server should
646 * reply to the write request and the flush request.
647 */
648 v9fs_req_wait_for_reply(req, NULL);
649 v9fs_rwrite(req, &count);
650 g_assert_cmpint(count, ==, sizeof(should_block));
651 v9fs_rflush(flush_req);
652
653 g_free(wnames[0]);
654 }
655
656 static void fs_readdir_split_128(void *obj, void *data,
657 QGuestAllocator *t_alloc)
658 {
659 v9fs_set_allocator(t_alloc);
660 do_readdir_split(obj, 128);
661 }
662
663 static void fs_readdir_split_256(void *obj, void *data,
664 QGuestAllocator *t_alloc)
665 {
666 v9fs_set_allocator(t_alloc);
667 do_readdir_split(obj, 256);
668 }
669
670 static void fs_readdir_split_512(void *obj, void *data,
671 QGuestAllocator *t_alloc)
672 {
673 v9fs_set_allocator(t_alloc);
674 do_readdir_split(obj, 512);
675 }
676
677 static void fs_synth_xattr_limit_default(void *obj, void *data,
678 QGuestAllocator *t_alloc)
679 {
680 v9fs_set_allocator(t_alloc);
681 do_xattr_limit(obj, V9FS_MAX_XATTR_DEFAULT, true);
682 }
683
684 static void fs_synth_xattr_limit_custom(void *obj, void *data,
685 QGuestAllocator *t_alloc)
686 {
687 v9fs_set_allocator(t_alloc);
688 do_xattr_limit(obj, 100, true);
689 }
690
691 static void fs_synth_xattr_limit_unlimited(void *obj, void *data,
692 QGuestAllocator *t_alloc)
693 {
694 v9fs_set_allocator(t_alloc);
695 do_xattr_limit(obj, -1, true);
696 }
697
698
699 /* tests using the 9pfs 'local' fs driver */
700
701 static void fs_create_dir(void *obj, void *data, QGuestAllocator *t_alloc)
702 {
703 QVirtio9P *v9p = obj;
704 v9fs_set_allocator(t_alloc);
705 struct stat st;
706 g_autofree char *root_path = virtio_9p_test_path("");
707 g_autofree char *new_dir = virtio_9p_test_path("01");
708
709 g_assert(root_path != NULL);
710
711 tattach({ .client = v9p });
712 tmkdir({ .client = v9p, .atPath = "/", .name = "01" });
713
714 /* check if created directory really exists now ... */
715 g_assert(stat(new_dir, &st) == 0);
716 /* ... and is actually a directory */
717 g_assert((st.st_mode & S_IFMT) == S_IFDIR);
718 }
719
720 static void fs_unlinkat_dir(void *obj, void *data, QGuestAllocator *t_alloc)
721 {
722 QVirtio9P *v9p = obj;
723 v9fs_set_allocator(t_alloc);
724 struct stat st;
725 g_autofree char *root_path = virtio_9p_test_path("");
726 g_autofree char *new_dir = virtio_9p_test_path("02");
727
728 g_assert(root_path != NULL);
729
730 tattach({ .client = v9p });
731 tmkdir({ .client = v9p, .atPath = "/", .name = "02" });
732
733 /* check if created directory really exists now ... */
734 g_assert(stat(new_dir, &st) == 0);
735 /* ... and is actually a directory */
736 g_assert((st.st_mode & S_IFMT) == S_IFDIR);
737
738 tunlinkat({
739 .client = v9p, .atPath = "/", .name = "02",
740 .flags = P9_DOTL_AT_REMOVEDIR
741 });
742 /* directory should be gone now */
743 g_assert(stat(new_dir, &st) != 0);
744 }
745
746 static void fs_create_file(void *obj, void *data, QGuestAllocator *t_alloc)
747 {
748 QVirtio9P *v9p = obj;
749 v9fs_set_allocator(t_alloc);
750 struct stat st;
751 g_autofree char *new_file = virtio_9p_test_path("03/1st_file");
752
753 tattach({ .client = v9p });
754 tmkdir({ .client = v9p, .atPath = "/", .name = "03" });
755 tlcreate({ .client = v9p, .atPath = "03", .name = "1st_file" });
756
757 /* check if created file exists now ... */
758 g_assert(stat(new_file, &st) == 0);
759 /* ... and is a regular file */
760 g_assert((st.st_mode & S_IFMT) == S_IFREG);
761 }
762
763 static void fs_unlinkat_file(void *obj, void *data, QGuestAllocator *t_alloc)
764 {
765 QVirtio9P *v9p = obj;
766 v9fs_set_allocator(t_alloc);
767 struct stat st;
768 g_autofree char *new_file = virtio_9p_test_path("04/doa_file");
769
770 tattach({ .client = v9p });
771 tmkdir({ .client = v9p, .atPath = "/", .name = "04" });
772 tlcreate({ .client = v9p, .atPath = "04", .name = "doa_file" });
773
774 /* check if created file exists now ... */
775 g_assert(stat(new_file, &st) == 0);
776 /* ... and is a regular file */
777 g_assert((st.st_mode & S_IFMT) == S_IFREG);
778
779 tunlinkat({ .client = v9p, .atPath = "04", .name = "doa_file" });
780 /* file should be gone now */
781 g_assert(stat(new_file, &st) != 0);
782 }
783
784 static void fs_symlink_file(void *obj, void *data, QGuestAllocator *t_alloc)
785 {
786 QVirtio9P *v9p = obj;
787 v9fs_set_allocator(t_alloc);
788 struct stat st;
789 g_autofree char *real_file = virtio_9p_test_path("05/real_file");
790 g_autofree char *symlink_file = virtio_9p_test_path("05/symlink_file");
791
792 tattach({ .client = v9p });
793 tmkdir({ .client = v9p, .atPath = "/", .name = "05" });
794 tlcreate({ .client = v9p, .atPath = "05", .name = "real_file" });
795 g_assert(stat(real_file, &st) == 0);
796 g_assert((st.st_mode & S_IFMT) == S_IFREG);
797
798 tsymlink({
799 .client = v9p, .atPath = "05", .name = "symlink_file",
800 .symtgt = "real_file"
801 });
802
803 /* check if created link exists now */
804 g_assert(stat(symlink_file, &st) == 0);
805 }
806
807 static void fs_unlinkat_symlink(void *obj, void *data,
808 QGuestAllocator *t_alloc)
809 {
810 QVirtio9P *v9p = obj;
811 v9fs_set_allocator(t_alloc);
812 struct stat st;
813 g_autofree char *real_file = virtio_9p_test_path("06/real_file");
814 g_autofree char *symlink_file = virtio_9p_test_path("06/symlink_file");
815
816 tattach({ .client = v9p });
817 tmkdir({ .client = v9p, .atPath = "/", .name = "06" });
818 tlcreate({ .client = v9p, .atPath = "06", .name = "real_file" });
819 g_assert(stat(real_file, &st) == 0);
820 g_assert((st.st_mode & S_IFMT) == S_IFREG);
821
822 tsymlink({
823 .client = v9p, .atPath = "06", .name = "symlink_file",
824 .symtgt = "real_file"
825 });
826 g_assert(stat(symlink_file, &st) == 0);
827
828 tunlinkat({ .client = v9p, .atPath = "06", .name = "symlink_file" });
829 /* symlink should be gone now */
830 g_assert(stat(symlink_file, &st) != 0);
831 }
832
833 static void fs_hardlink_file(void *obj, void *data, QGuestAllocator *t_alloc)
834 {
835 QVirtio9P *v9p = obj;
836 v9fs_set_allocator(t_alloc);
837 struct stat st_real, st_link;
838 g_autofree char *real_file = virtio_9p_test_path("07/real_file");
839 g_autofree char *hardlink_file = virtio_9p_test_path("07/hardlink_file");
840
841 tattach({ .client = v9p });
842 tmkdir({ .client = v9p, .atPath = "/", .name = "07" });
843 tlcreate({ .client = v9p, .atPath = "07", .name = "real_file" });
844 g_assert(stat(real_file, &st_real) == 0);
845 g_assert((st_real.st_mode & S_IFMT) == S_IFREG);
846
847 tlink({
848 .client = v9p, .atPath = "07", .name = "hardlink_file",
849 .toPath = "07/real_file"
850 });
851
852 /* check if link exists now ... */
853 g_assert(stat(hardlink_file, &st_link) == 0);
854 /* ... and it's a hard link, right? */
855 g_assert((st_link.st_mode & S_IFMT) == S_IFREG);
856 g_assert(st_link.st_dev == st_real.st_dev);
857 g_assert(st_link.st_ino == st_real.st_ino);
858 }
859
860 static void fs_unlinkat_hardlink(void *obj, void *data,
861 QGuestAllocator *t_alloc)
862 {
863 QVirtio9P *v9p = obj;
864 v9fs_set_allocator(t_alloc);
865 struct stat st_real, st_link;
866 g_autofree char *real_file = virtio_9p_test_path("08/real_file");
867 g_autofree char *hardlink_file = virtio_9p_test_path("08/hardlink_file");
868
869 tattach({ .client = v9p });
870 tmkdir({ .client = v9p, .atPath = "/", .name = "08" });
871 tlcreate({ .client = v9p, .atPath = "08", .name = "real_file" });
872 g_assert(stat(real_file, &st_real) == 0);
873 g_assert((st_real.st_mode & S_IFMT) == S_IFREG);
874
875 tlink({
876 .client = v9p, .atPath = "08", .name = "hardlink_file",
877 .toPath = "08/real_file"
878 });
879 g_assert(stat(hardlink_file, &st_link) == 0);
880
881 tunlinkat({ .client = v9p, .atPath = "08", .name = "hardlink_file" });
882 /* symlink should be gone now */
883 g_assert(stat(hardlink_file, &st_link) != 0);
884 /* and old file should still exist */
885 g_assert(stat(real_file, &st_real) == 0);
886 }
887
888 static void fs_use_after_unlink(void *obj, void *data,
889 QGuestAllocator *t_alloc)
890 {
891 QVirtio9P *v9p = obj;
892 v9fs_set_allocator(t_alloc);
893 static const uint32_t write_count = P9_MAX_SIZE / 2;
894 g_autofree char *real_file = virtio_9p_test_path("09/doa_file");
895 g_autofree char *buf = g_malloc0(write_count);
896 struct stat st_file;
897 struct v9fs_attr attr;
898 uint32_t fid_file;
899 uint32_t count;
900
901 tattach({ .client = v9p });
902
903 /* create a file "09/doa_file" and make sure it exists and is regular */
904 tmkdir({ .client = v9p, .atPath = "/", .name = "09" });
905 tlcreate({ .client = v9p, .atPath = "09", .name = "doa_file" });
906 g_assert(stat(real_file, &st_file) == 0);
907 g_assert((st_file.st_mode & S_IFMT) == S_IFREG);
908
909 /* request a FID for that regular file that we can work with next */
910 fid_file = twalk({
911 .client = v9p, .fid = 0, .path = "09/doa_file"
912 }).newfid;
913 g_assert(fid_file != 0);
914
915 /* now first open the file in write mode before ... */
916 tlopen({ .client = v9p, .fid = fid_file, .flags = O_WRONLY });
917 /* ... removing the file from file system */
918 tunlinkat({ .client = v9p, .atPath = "09", .name = "doa_file" });
919
920 /* file is removed, but we still have it open, so this should succeed */
921 tgetattr({
922 .client = v9p, .fid = fid_file, .request_mask = P9_GETATTR_BASIC,
923 .rgetattr.attr = &attr
924 });
925 count = twrite({
926 .client = v9p, .fid = fid_file, .offset = 0, .count = write_count,
927 .data = buf
928 }).count;
929 g_assert_cmpint(count, ==, write_count);
930
931 /* truncate file to (arbitrarily chosen) size 2001 */
932 tsetattr({
933 .client = v9p, .fid = fid_file, .attr = (v9fs_attr) {
934 .valid = P9_SETATTR_SIZE,
935 .size = 2001
936 }
937 });
938 /* truncate apparently succeeded, let's double-check the size */
939 tgetattr({
940 .client = v9p, .fid = fid_file, .request_mask = P9_GETATTR_BASIC,
941 .rgetattr.attr = &attr
942 });
943 g_assert_cmpint(attr.size, ==, 2001);
944 }
945
946 /* https://gitlab.com/qemu-project/qemu/-/issues/3358 */
947 static void fs_deep_absolute_path(void *obj, void *data,
948 QGuestAllocator *t_alloc)
949 {
950 QVirtio9P *v9p = obj;
951 v9fs_set_allocator(t_alloc);
952
953 if (!g_test_slow()) {
954 g_test_skip("This is a slow test, run with -m slow");
955 return;
956 }
957
958 GString *path = g_string_new("/");
959 char name[256];
960 uint32_t current_fid = 0;
961
962 tattach({ .client = v9p });
963
964 /* Create deep directory structure until absolute path length
965 * exceeds 16-bit range.
966 */
967 while (path->len <= 65536) {
968 /* use 255-byte name (NAME_MAX) to reduce iterations to ~257 */
969 memset(name, 'A', 255);
970 name[255] = '\0';
971
972 /* create the directory relative to current FID */
973 tmkdir({
974 .client = v9p,
975 .dfid = current_fid,
976 .name = name
977 });
978
979 /* just for locally tracking the current path length */
980 g_string_append(path, name);
981 g_string_append(path, "/");
982
983 /* acquire new FID for the newly created directory */
984 char *wnames[] = { name };
985 current_fid = twalk({
986 .client = v9p,
987 .fid = current_fid,
988 .nwname = 1,
989 .wnames = wnames
990 }).newfid;
991
992 /* Reset descriptor pool to avoid exhaustion. The simplified
993 * virtio test driver does never free descriptors back to the pool
994 * after use, so we must manually reset it for the required high
995 * amount of 9p requests here.
996 */
997 qvirtqueue_reset_pool(v9p->vq);
998 }
999
1000 /* check if the deepest directory is accessible */
1001 v9fs_attr attr = {};
1002 tgetattr({
1003 .client = v9p,
1004 .fid = current_fid,
1005 .request_mask = P9_GETATTR_BASIC,
1006 .rgetattr.attr = &attr
1007 });
1008
1009 g_string_free(path, TRUE);
1010 }
1011
1012 static void fs_local_xattr_limit_default(void *obj, void *data,
1013 QGuestAllocator *t_alloc)
1014 {
1015 v9fs_set_allocator(t_alloc);
1016 do_local_xattr_limit(obj, V9FS_MAX_XATTR_DEFAULT);
1017 }
1018
1019 static void fs_local_xattr_limit_custom(void *obj, void *data,
1020 QGuestAllocator *t_alloc)
1021 {
1022 v9fs_set_allocator(t_alloc);
1023 do_local_xattr_limit(obj, 100);
1024 }
1025
1026 static void fs_local_xattr_limit_unlimited(void *obj, void *data,
1027 QGuestAllocator *t_alloc)
1028 {
1029 v9fs_set_allocator(t_alloc);
1030 do_local_xattr_limit(obj, -1);
1031 }
1032
1033 static void *synth_max_xattr_custom_opt(GString *cmd_line, void *arg)
1034 {
1035 virtio_9p_add_synth_driver_args(cmd_line, "max_xattr=100");
1036 return arg;
1037 }
1038
1039 static void *synth_max_xattr_unlimited_opt(GString *cmd_line, void *arg)
1040 {
1041 virtio_9p_add_synth_driver_args(cmd_line, "max_xattr=0");
1042 return arg;
1043 }
1044
1045 static void cleanup_9p_local_driver(void *data)
1046 {
1047 /* remove previously created test dir when test is completed */
1048 virtio_9p_remove_local_test_dir();
1049 }
1050
1051 static void assign_9p_local_driver_with_args(GString *cmd_line,
1052 const char *extra_opts)
1053 {
1054 /* make sure test dir for the 'local' tests exists */
1055 virtio_9p_create_local_test_dir();
1056
1057 g_autofree char *opts =
1058 (extra_opts) ?
1059 g_strdup_printf("security_model=mapped-xattr,%s", extra_opts) :
1060 g_strdup("security_model=mapped-xattr");
1061
1062 virtio_9p_assign_local_driver(cmd_line, opts);
1063
1064 g_test_queue_destroy(cleanup_9p_local_driver, NULL);
1065 }
1066
1067 static void *assign_9p_local_driver(GString *cmd_line, void *arg)
1068 {
1069 assign_9p_local_driver_with_args(cmd_line, NULL);
1070 return arg;
1071 }
1072
1073 static void *local_max_xattr_custom_opt(GString *cmd_line, void *arg)
1074 {
1075 assign_9p_local_driver_with_args(cmd_line, "max_xattr=100");
1076 return arg;
1077 }
1078
1079 static void *local_max_xattr_unlimited_opt(GString *cmd_line, void *arg)
1080 {
1081 assign_9p_local_driver_with_args(cmd_line, "max_xattr=0");
1082 return arg;
1083 }
1084
1085 static void register_virtio_9p_test(void)
1086 {
1087 QOSGraphTestOptions opts = {
1088 };
1089
1090 /* 9pfs test cases using the 'synth' filesystem driver */
1091 qos_add_test("synth/config", "virtio-9p", pci_config, &opts);
1092 qos_add_test("synth/version/basic", "virtio-9p", fs_version, &opts);
1093 qos_add_test("synth/attach/basic", "virtio-9p", fs_attach, &opts);
1094 qos_add_test("synth/walk/basic", "virtio-9p", fs_walk, &opts);
1095 qos_add_test("synth/walk/no_slash", "virtio-9p", fs_walk_no_slash,
1096 &opts);
1097 qos_add_test("synth/walk/none", "virtio-9p", fs_walk_none, &opts);
1098 qos_add_test("synth/walk/dotdot_from_root", "virtio-9p",
1099 fs_walk_dotdot, &opts);
1100 qos_add_test("synth/walk/non_existent", "virtio-9p", fs_walk_nonexistent,
1101 &opts);
1102 qos_add_test("synth/walk/2nd_non_existent", "virtio-9p",
1103 fs_walk_2nd_nonexistent, &opts);
1104 qos_add_test("synth/lopen/basic", "virtio-9p", fs_lopen, &opts);
1105 qos_add_test("synth/write/basic", "virtio-9p", fs_write, &opts);
1106 qos_add_test("synth/flush/success", "virtio-9p", fs_flush_success,
1107 &opts);
1108 qos_add_test("synth/flush/ignored", "virtio-9p", fs_flush_ignored,
1109 &opts);
1110 qos_add_test("synth/readdir/basic", "virtio-9p", fs_readdir, &opts);
1111 qos_add_test("synth/readdir/split_512", "virtio-9p",
1112 fs_readdir_split_512, &opts);
1113 qos_add_test("synth/readdir/split_256", "virtio-9p",
1114 fs_readdir_split_256, &opts);
1115 qos_add_test("synth/readdir/split_128", "virtio-9p",
1116 fs_readdir_split_128, &opts);
1117 qos_add_test("synth/xattr_limit/default", "virtio-9p",
1118 fs_synth_xattr_limit_default, &opts);
1119 opts.before = synth_max_xattr_custom_opt;
1120 qos_add_test("synth/xattr_limit/custom", "virtio-9p",
1121 fs_synth_xattr_limit_custom, &opts);
1122 opts.before = synth_max_xattr_unlimited_opt;
1123 qos_add_test("synth/xattr_limit/unlimited", "virtio-9p",
1124 fs_synth_xattr_limit_unlimited, &opts);
1125
1126 /* 9pfs test cases using the 'local' filesystem driver */
1127 opts.before = assign_9p_local_driver;
1128 qos_add_test("local/config", "virtio-9p", pci_config, &opts);
1129 qos_add_test("local/create_dir", "virtio-9p", fs_create_dir, &opts);
1130 qos_add_test("local/unlinkat_dir", "virtio-9p", fs_unlinkat_dir, &opts);
1131 qos_add_test("local/create_file", "virtio-9p", fs_create_file, &opts);
1132 qos_add_test("local/unlinkat_file", "virtio-9p", fs_unlinkat_file, &opts);
1133 qos_add_test("local/symlink_file", "virtio-9p", fs_symlink_file, &opts);
1134 qos_add_test("local/unlinkat_symlink", "virtio-9p", fs_unlinkat_symlink,
1135 &opts);
1136 qos_add_test("local/hardlink_file", "virtio-9p", fs_hardlink_file, &opts);
1137 qos_add_test("local/unlinkat_hardlink", "virtio-9p", fs_unlinkat_hardlink,
1138 &opts);
1139 qos_add_test("local/use_after_unlink", "virtio-9p", fs_use_after_unlink,
1140 &opts);
1141 qos_add_test("local/deep_absolute_path", "virtio-9p",
1142 fs_deep_absolute_path, &opts);
1143 qos_add_test("local/xattr_limit/default", "virtio-9p",
1144 fs_local_xattr_limit_default, &opts);
1145 opts.before = local_max_xattr_custom_opt;
1146 qos_add_test("local/xattr_limit/custom", "virtio-9p",
1147 fs_local_xattr_limit_custom, &opts);
1148 opts.before = local_max_xattr_unlimited_opt;
1149 qos_add_test("local/xattr_limit/unlimited", "virtio-9p",
1150 fs_local_xattr_limit_unlimited, &opts);
1151 }
1152
1153 libqos_init(register_virtio_9p_test);