xref: /qemu/tests/qtest/virtio-9p-test.c (revision 28c736709b82c8f47edf3cb18b9fb601fdab9151)
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-9p-client.h"
18 
19 #define twalk(...) v9fs_twalk((TWalkOpt) __VA_ARGS__)
20 #define tversion(...) v9fs_tversion((TVersionOpt) __VA_ARGS__)
21 #define tattach(...) v9fs_tattach((TAttachOpt) __VA_ARGS__)
22 #define tgetattr(...) v9fs_tgetattr((TGetAttrOpt) __VA_ARGS__)
23 
24 static void pci_config(void *obj, void *data, QGuestAllocator *t_alloc)
25 {
26     QVirtio9P *v9p = obj;
27     v9fs_set_allocator(t_alloc);
28     size_t tag_len = qvirtio_config_readw(v9p->vdev, 0);
29     g_autofree char *tag = NULL;
30     int i;
31 
32     g_assert_cmpint(tag_len, ==, strlen(MOUNT_TAG));
33 
34     tag = g_malloc(tag_len);
35     for (i = 0; i < tag_len; i++) {
36         tag[i] = qvirtio_config_readb(v9p->vdev, i + 2);
37     }
38     g_assert_cmpmem(tag, tag_len, MOUNT_TAG, tag_len);
39 }
40 
41 static inline bool is_same_qid(v9fs_qid a, v9fs_qid b)
42 {
43     /* don't compare QID version for checking for file ID equalness */
44     return a[0] == b[0] && memcmp(&a[5], &b[5], 8) == 0;
45 }
46 
47 static void fs_version(void *obj, void *data, QGuestAllocator *t_alloc)
48 {
49     v9fs_set_allocator(t_alloc);
50     tversion({ .client = obj });
51 }
52 
53 static void fs_attach(void *obj, void *data, QGuestAllocator *t_alloc)
54 {
55     v9fs_set_allocator(t_alloc);
56     tattach({ .client = obj });
57 }
58 
59 static void fs_walk(void *obj, void *data, QGuestAllocator *t_alloc)
60 {
61     QVirtio9P *v9p = obj;
62     v9fs_set_allocator(t_alloc);
63     char *wnames[P9_MAXWELEM];
64     uint16_t nwqid;
65     g_autofree v9fs_qid *wqid = NULL;
66     int i;
67 
68     for (i = 0; i < P9_MAXWELEM; i++) {
69         wnames[i] = g_strdup_printf(QTEST_V9FS_SYNTH_WALK_FILE, i);
70     }
71 
72     tattach({ .client = v9p });
73     twalk({
74         .client = v9p, .fid = 0, .newfid = 1,
75         .nwname = P9_MAXWELEM, .wnames = wnames,
76         .rwalk = { .nwqid = &nwqid, .wqid = &wqid }
77     });
78 
79     g_assert_cmpint(nwqid, ==, P9_MAXWELEM);
80 
81     for (i = 0; i < P9_MAXWELEM; i++) {
82         g_free(wnames[i]);
83     }
84 }
85 
86 static bool fs_dirents_contain_name(struct V9fsDirent *e, const char* name)
87 {
88     for (; e; e = e->next) {
89         if (!strcmp(e->name, name)) {
90             return true;
91         }
92     }
93     return false;
94 }
95 
96 /* basic readdir test where reply fits into a single response message */
97 static void fs_readdir(void *obj, void *data, QGuestAllocator *t_alloc)
98 {
99     QVirtio9P *v9p = obj;
100     v9fs_set_allocator(t_alloc);
101     char *wnames[] = { g_strdup(QTEST_V9FS_SYNTH_READDIR_DIR) };
102     uint16_t nqid;
103     v9fs_qid qid;
104     uint32_t count, nentries;
105     struct V9fsDirent *entries = NULL;
106     P9Req *req;
107 
108     tattach({ .client = v9p });
109     twalk({
110         .client = v9p, .fid = 0, .newfid = 1,
111         .nwname = 1, .wnames = wnames, .rwalk.nwqid = &nqid
112     });
113     g_assert_cmpint(nqid, ==, 1);
114 
115     req = v9fs_tlopen(v9p, 1, O_DIRECTORY, 0);
116     v9fs_req_wait_for_reply(req, NULL);
117     v9fs_rlopen(req, &qid, NULL);
118 
119     /*
120      * submit count = msize - 11, because 11 is the header size of Rreaddir
121      */
122     req = v9fs_treaddir(v9p, 1, 0, P9_MAX_SIZE - 11, 0);
123     v9fs_req_wait_for_reply(req, NULL);
124     v9fs_rreaddir(req, &count, &nentries, &entries);
125 
126     /*
127      * Assuming msize (P9_MAX_SIZE) is large enough so we can retrieve all
128      * dir entries with only one readdir request.
129      */
130     g_assert_cmpint(
131         nentries, ==,
132         QTEST_V9FS_SYNTH_READDIR_NFILES + 2 /* "." and ".." */
133     );
134 
135     /*
136      * Check all file names exist in returned entries, ignore their order
137      * though.
138      */
139     g_assert_cmpint(fs_dirents_contain_name(entries, "."), ==, true);
140     g_assert_cmpint(fs_dirents_contain_name(entries, ".."), ==, true);
141     for (int i = 0; i < QTEST_V9FS_SYNTH_READDIR_NFILES; ++i) {
142         g_autofree char *name =
143             g_strdup_printf(QTEST_V9FS_SYNTH_READDIR_FILE, i);
144         g_assert_cmpint(fs_dirents_contain_name(entries, name), ==, true);
145     }
146 
147     v9fs_free_dirents(entries);
148     g_free(wnames[0]);
149 }
150 
151 /* readdir test where overall request is split over several messages */
152 static void do_readdir_split(QVirtio9P *v9p, uint32_t count)
153 {
154     char *wnames[] = { g_strdup(QTEST_V9FS_SYNTH_READDIR_DIR) };
155     uint16_t nqid;
156     v9fs_qid qid;
157     uint32_t nentries, npartialentries;
158     struct V9fsDirent *entries, *tail, *partialentries;
159     P9Req *req;
160     int fid;
161     uint64_t offset;
162 
163     tattach({ .client = v9p });
164 
165     fid = 1;
166     offset = 0;
167     entries = NULL;
168     nentries = 0;
169     tail = NULL;
170 
171     twalk({
172         .client = v9p, .fid = 0, .newfid = fid,
173         .nwname = 1, .wnames = wnames, .rwalk.nwqid = &nqid
174     });
175     g_assert_cmpint(nqid, ==, 1);
176 
177     req = v9fs_tlopen(v9p, fid, O_DIRECTORY, 0);
178     v9fs_req_wait_for_reply(req, NULL);
179     v9fs_rlopen(req, &qid, NULL);
180 
181     /*
182      * send as many Treaddir requests as required to get all directory
183      * entries
184      */
185     while (true) {
186         npartialentries = 0;
187         partialentries = NULL;
188 
189         req = v9fs_treaddir(v9p, fid, offset, count, 0);
190         v9fs_req_wait_for_reply(req, NULL);
191         v9fs_rreaddir(req, &count, &npartialentries, &partialentries);
192         if (npartialentries > 0 && partialentries) {
193             if (!entries) {
194                 entries = partialentries;
195                 nentries = npartialentries;
196                 tail = partialentries;
197             } else {
198                 tail->next = partialentries;
199                 nentries += npartialentries;
200             }
201             while (tail->next) {
202                 tail = tail->next;
203             }
204             offset = tail->offset;
205         } else {
206             break;
207         }
208     }
209 
210     g_assert_cmpint(
211         nentries, ==,
212         QTEST_V9FS_SYNTH_READDIR_NFILES + 2 /* "." and ".." */
213     );
214 
215     /*
216      * Check all file names exist in returned entries, ignore their order
217      * though.
218      */
219     g_assert_cmpint(fs_dirents_contain_name(entries, "."), ==, true);
220     g_assert_cmpint(fs_dirents_contain_name(entries, ".."), ==, true);
221     for (int i = 0; i < QTEST_V9FS_SYNTH_READDIR_NFILES; ++i) {
222         char *name = g_strdup_printf(QTEST_V9FS_SYNTH_READDIR_FILE, i);
223         g_assert_cmpint(fs_dirents_contain_name(entries, name), ==, true);
224         g_free(name);
225     }
226 
227     v9fs_free_dirents(entries);
228 
229     g_free(wnames[0]);
230 }
231 
232 static void fs_walk_no_slash(void *obj, void *data, QGuestAllocator *t_alloc)
233 {
234     QVirtio9P *v9p = obj;
235     v9fs_set_allocator(t_alloc);
236     char *wnames[] = { g_strdup(" /") };
237 
238     tattach({ .client = v9p });
239     twalk({
240         .client = v9p, .fid = 0, .newfid = 1, .nwname = 1, .wnames = wnames,
241         .expectErr = ENOENT
242     });
243 
244     g_free(wnames[0]);
245 }
246 
247 static void fs_walk_nonexistent(void *obj, void *data, QGuestAllocator *t_alloc)
248 {
249     QVirtio9P *v9p = obj;
250     v9fs_set_allocator(t_alloc);
251 
252     tattach({ .client = v9p });
253     /*
254      * The 9p2000 protocol spec says: "If the first element cannot be walked
255      * for any reason, Rerror is returned."
256      */
257     twalk({ .client = v9p, .path = "non-existent", .expectErr = ENOENT });
258 }
259 
260 static void fs_walk_2nd_nonexistent(void *obj, void *data,
261                                     QGuestAllocator *t_alloc)
262 {
263     QVirtio9P *v9p = obj;
264     v9fs_set_allocator(t_alloc);
265     v9fs_qid root_qid;
266     uint16_t nwqid;
267     uint32_t fid;
268     g_autofree v9fs_qid *wqid = NULL;
269     g_autofree char *path = g_strdup_printf(
270         QTEST_V9FS_SYNTH_WALK_FILE "/non-existent", 0
271     );
272 
273     tattach({ .client = v9p, .rattach.qid = &root_qid });
274     fid = twalk({
275         .client = v9p, .path = path,
276         .rwalk = { .nwqid = &nwqid, .wqid = &wqid }
277     }).newfid;
278     /*
279      * The 9p2000 protocol spec says: "nwqid is therefore either nwname or the
280      * index of the first elementwise walk that failed."
281      */
282     assert(nwqid == 1);
283 
284     /* returned QID wqid[0] is file ID of 1st subdir */
285     g_assert(wqid && wqid[0] && !is_same_qid(root_qid, wqid[0]));
286 
287     /* expect fid being unaffected by walk above */
288     tgetattr({
289         .client = v9p, .fid = fid, .request_mask = P9_GETATTR_BASIC,
290         .expectErr = ENOENT
291     });
292 }
293 
294 static void fs_walk_none(void *obj, void *data, QGuestAllocator *t_alloc)
295 {
296     QVirtio9P *v9p = obj;
297     v9fs_set_allocator(t_alloc);
298     v9fs_qid root_qid;
299     g_autofree v9fs_qid *wqid = NULL;
300     struct v9fs_attr attr;
301 
302     tversion({ .client = v9p });
303     tattach({
304         .client = v9p, .fid = 0, .n_uname = getuid(),
305         .rattach.qid = &root_qid
306     });
307 
308     twalk({
309         .client = v9p, .fid = 0, .newfid = 1, .nwname = 0, .wnames = NULL,
310         .rwalk.wqid = &wqid
311     });
312 
313     /* special case: no QID is returned if nwname=0 was sent */
314     g_assert(wqid == NULL);
315 
316     tgetattr({
317         .client = v9p, .fid = 1, .request_mask = P9_GETATTR_BASIC,
318         .rgetattr.attr = &attr
319     });
320 
321     g_assert(is_same_qid(root_qid, attr.qid));
322 }
323 
324 static void fs_walk_dotdot(void *obj, void *data, QGuestAllocator *t_alloc)
325 {
326     QVirtio9P *v9p = obj;
327     v9fs_set_allocator(t_alloc);
328     char *wnames[] = { g_strdup("..") };
329     v9fs_qid root_qid;
330     g_autofree v9fs_qid *wqid = NULL;
331 
332     tversion({ .client = v9p });
333     tattach({
334         .client = v9p, .fid = 0, .n_uname = getuid(),
335         .rattach.qid = &root_qid
336     });
337 
338     twalk({
339         .client = v9p, .fid = 0, .newfid = 1, .nwname = 1, .wnames = wnames,
340         .rwalk.wqid = &wqid /* We now we'll get one qid */
341     });
342 
343     g_assert_cmpmem(&root_qid, 13, wqid[0], 13);
344 
345     g_free(wnames[0]);
346 }
347 
348 static void fs_lopen(void *obj, void *data, QGuestAllocator *t_alloc)
349 {
350     QVirtio9P *v9p = obj;
351     v9fs_set_allocator(t_alloc);
352     char *wnames[] = { g_strdup(QTEST_V9FS_SYNTH_LOPEN_FILE) };
353     P9Req *req;
354 
355     tattach({ .client = v9p });
356     twalk({
357         .client = v9p, .fid = 0, .newfid = 1, .nwname = 1, .wnames = wnames
358     });
359 
360     req = v9fs_tlopen(v9p, 1, O_WRONLY, 0);
361     v9fs_req_wait_for_reply(req, NULL);
362     v9fs_rlopen(req, NULL, NULL);
363 
364     g_free(wnames[0]);
365 }
366 
367 static void fs_write(void *obj, void *data, QGuestAllocator *t_alloc)
368 {
369     QVirtio9P *v9p = obj;
370     v9fs_set_allocator(t_alloc);
371     static const uint32_t write_count = P9_MAX_SIZE / 2;
372     char *wnames[] = { g_strdup(QTEST_V9FS_SYNTH_WRITE_FILE) };
373     g_autofree char *buf = g_malloc0(write_count);
374     uint32_t count;
375     P9Req *req;
376 
377     tattach({ .client = v9p });
378     twalk({
379         .client = v9p, .fid = 0, .newfid = 1, .nwname = 1, .wnames = wnames
380     });
381 
382     req = v9fs_tlopen(v9p, 1, O_WRONLY, 0);
383     v9fs_req_wait_for_reply(req, NULL);
384     v9fs_rlopen(req, NULL, NULL);
385 
386     req = v9fs_twrite(v9p, 1, 0, write_count, buf, 0);
387     v9fs_req_wait_for_reply(req, NULL);
388     v9fs_rwrite(req, &count);
389     g_assert_cmpint(count, ==, write_count);
390 
391     g_free(wnames[0]);
392 }
393 
394 static void fs_flush_success(void *obj, void *data, QGuestAllocator *t_alloc)
395 {
396     QVirtio9P *v9p = obj;
397     v9fs_set_allocator(t_alloc);
398     char *wnames[] = { g_strdup(QTEST_V9FS_SYNTH_FLUSH_FILE) };
399     P9Req *req, *flush_req;
400     uint32_t reply_len;
401     uint8_t should_block;
402 
403     tattach({ .client = v9p });
404     twalk({
405         .client = v9p, .fid = 0, .newfid = 1, .nwname = 1, .wnames = wnames
406     });
407 
408     req = v9fs_tlopen(v9p, 1, O_WRONLY, 0);
409     v9fs_req_wait_for_reply(req, NULL);
410     v9fs_rlopen(req, NULL, NULL);
411 
412     /* This will cause the 9p server to try to write data to the backend,
413      * until the write request gets cancelled.
414      */
415     should_block = 1;
416     req = v9fs_twrite(v9p, 1, 0, sizeof(should_block), &should_block, 0);
417 
418     flush_req = v9fs_tflush(v9p, req->tag, 1);
419 
420     /* The write request is supposed to be flushed: the server should just
421      * mark the write request as used and reply to the flush request.
422      */
423     v9fs_req_wait_for_reply(req, &reply_len);
424     g_assert_cmpint(reply_len, ==, 0);
425     v9fs_req_free(req);
426     v9fs_rflush(flush_req);
427 
428     g_free(wnames[0]);
429 }
430 
431 static void fs_flush_ignored(void *obj, void *data, QGuestAllocator *t_alloc)
432 {
433     QVirtio9P *v9p = obj;
434     v9fs_set_allocator(t_alloc);
435     char *wnames[] = { g_strdup(QTEST_V9FS_SYNTH_FLUSH_FILE) };
436     P9Req *req, *flush_req;
437     uint32_t count;
438     uint8_t should_block;
439 
440     tattach({ .client = v9p });
441     twalk({
442         .client = v9p, .fid = 0, .newfid = 1, .nwname = 1, .wnames = wnames
443     });
444 
445     req = v9fs_tlopen(v9p, 1, O_WRONLY, 0);
446     v9fs_req_wait_for_reply(req, NULL);
447     v9fs_rlopen(req, NULL, NULL);
448 
449     /* This will cause the write request to complete right away, before it
450      * could be actually cancelled.
451      */
452     should_block = 0;
453     req = v9fs_twrite(v9p, 1, 0, sizeof(should_block), &should_block, 0);
454 
455     flush_req = v9fs_tflush(v9p, req->tag, 1);
456 
457     /* The write request is supposed to complete. The server should
458      * reply to the write request and the flush request.
459      */
460     v9fs_req_wait_for_reply(req, NULL);
461     v9fs_rwrite(req, &count);
462     g_assert_cmpint(count, ==, sizeof(should_block));
463     v9fs_rflush(flush_req);
464 
465     g_free(wnames[0]);
466 }
467 
468 static void do_mkdir(QVirtio9P *v9p, const char *path, const char *cname)
469 {
470     g_autofree char *name = g_strdup(cname);
471     uint32_t fid;
472     P9Req *req;
473 
474     fid = twalk({ .client = v9p, .path = path }).newfid;
475 
476     req = v9fs_tmkdir(v9p, fid, name, 0750, 0, 0);
477     v9fs_req_wait_for_reply(req, NULL);
478     v9fs_rmkdir(req, NULL);
479 }
480 
481 /* create a regular file with Tlcreate and return file's fid */
482 static uint32_t do_lcreate(QVirtio9P *v9p, const char *path,
483                            const char *cname)
484 {
485     g_autofree char *name = g_strdup(cname);
486     uint32_t fid;
487     P9Req *req;
488 
489     fid = twalk({ .client = v9p, .path = path }).newfid;
490 
491     req = v9fs_tlcreate(v9p, fid, name, 0, 0750, 0, 0);
492     v9fs_req_wait_for_reply(req, NULL);
493     v9fs_rlcreate(req, NULL, NULL);
494 
495     return fid;
496 }
497 
498 /* create symlink named @a clink in directory @a path pointing to @a to */
499 static void do_symlink(QVirtio9P *v9p, const char *path, const char *clink,
500                        const char *to)
501 {
502     g_autofree char *name = g_strdup(clink);
503     g_autofree char *dst = g_strdup(to);
504     uint32_t fid;
505     P9Req *req;
506 
507     fid = twalk({ .client = v9p, .path = path }).newfid;
508 
509     req = v9fs_tsymlink(v9p, fid, name, dst, 0, 0);
510     v9fs_req_wait_for_reply(req, NULL);
511     v9fs_rsymlink(req, NULL);
512 }
513 
514 /* create a hard link named @a clink in directory @a path pointing to @a to */
515 static void do_hardlink(QVirtio9P *v9p, const char *path, const char *clink,
516                         const char *to)
517 {
518     uint32_t dfid, fid;
519     P9Req *req;
520 
521     dfid = twalk({ .client = v9p, .path = path }).newfid;
522     fid = twalk({ .client = v9p, .path = to }).newfid;
523 
524     req = v9fs_tlink(v9p, dfid, fid, clink, 0);
525     v9fs_req_wait_for_reply(req, NULL);
526     v9fs_rlink(req);
527 }
528 
529 static void do_unlinkat(QVirtio9P *v9p, const char *atpath, const char *rpath,
530                         uint32_t flags)
531 {
532     g_autofree char *name = g_strdup(rpath);
533     uint32_t fid;
534     P9Req *req;
535 
536     fid = twalk({ .client = v9p, .path = atpath }).newfid;
537 
538     req = v9fs_tunlinkat(v9p, fid, name, flags, 0);
539     v9fs_req_wait_for_reply(req, NULL);
540     v9fs_runlinkat(req);
541 }
542 
543 static void fs_readdir_split_128(void *obj, void *data,
544                                  QGuestAllocator *t_alloc)
545 {
546     v9fs_set_allocator(t_alloc);
547     do_readdir_split(obj, 128);
548 }
549 
550 static void fs_readdir_split_256(void *obj, void *data,
551                                  QGuestAllocator *t_alloc)
552 {
553     v9fs_set_allocator(t_alloc);
554     do_readdir_split(obj, 256);
555 }
556 
557 static void fs_readdir_split_512(void *obj, void *data,
558                                  QGuestAllocator *t_alloc)
559 {
560     v9fs_set_allocator(t_alloc);
561     do_readdir_split(obj, 512);
562 }
563 
564 
565 /* tests using the 9pfs 'local' fs driver */
566 
567 static void fs_create_dir(void *obj, void *data, QGuestAllocator *t_alloc)
568 {
569     QVirtio9P *v9p = obj;
570     v9fs_set_allocator(t_alloc);
571     struct stat st;
572     g_autofree char *root_path = virtio_9p_test_path("");
573     g_autofree char *new_dir = virtio_9p_test_path("01");
574 
575     g_assert(root_path != NULL);
576 
577     tattach({ .client = v9p });
578     do_mkdir(v9p, "/", "01");
579 
580     /* check if created directory really exists now ... */
581     g_assert(stat(new_dir, &st) == 0);
582     /* ... and is actually a directory */
583     g_assert((st.st_mode & S_IFMT) == S_IFDIR);
584 }
585 
586 static void fs_unlinkat_dir(void *obj, void *data, QGuestAllocator *t_alloc)
587 {
588     QVirtio9P *v9p = obj;
589     v9fs_set_allocator(t_alloc);
590     struct stat st;
591     g_autofree char *root_path = virtio_9p_test_path("");
592     g_autofree char *new_dir = virtio_9p_test_path("02");
593 
594     g_assert(root_path != NULL);
595 
596     tattach({ .client = v9p });
597     do_mkdir(v9p, "/", "02");
598 
599     /* check if created directory really exists now ... */
600     g_assert(stat(new_dir, &st) == 0);
601     /* ... and is actually a directory */
602     g_assert((st.st_mode & S_IFMT) == S_IFDIR);
603 
604     do_unlinkat(v9p, "/", "02", P9_DOTL_AT_REMOVEDIR);
605     /* directory should be gone now */
606     g_assert(stat(new_dir, &st) != 0);
607 }
608 
609 static void fs_create_file(void *obj, void *data, QGuestAllocator *t_alloc)
610 {
611     QVirtio9P *v9p = obj;
612     v9fs_set_allocator(t_alloc);
613     struct stat st;
614     g_autofree char *new_file = virtio_9p_test_path("03/1st_file");
615 
616     tattach({ .client = v9p });
617     do_mkdir(v9p, "/", "03");
618     do_lcreate(v9p, "03", "1st_file");
619 
620     /* check if created file exists now ... */
621     g_assert(stat(new_file, &st) == 0);
622     /* ... and is a regular file */
623     g_assert((st.st_mode & S_IFMT) == S_IFREG);
624 }
625 
626 static void fs_unlinkat_file(void *obj, void *data, QGuestAllocator *t_alloc)
627 {
628     QVirtio9P *v9p = obj;
629     v9fs_set_allocator(t_alloc);
630     struct stat st;
631     g_autofree char *new_file = virtio_9p_test_path("04/doa_file");
632 
633     tattach({ .client = v9p });
634     do_mkdir(v9p, "/", "04");
635     do_lcreate(v9p, "04", "doa_file");
636 
637     /* check if created file exists now ... */
638     g_assert(stat(new_file, &st) == 0);
639     /* ... and is a regular file */
640     g_assert((st.st_mode & S_IFMT) == S_IFREG);
641 
642     do_unlinkat(v9p, "04", "doa_file", 0);
643     /* file should be gone now */
644     g_assert(stat(new_file, &st) != 0);
645 }
646 
647 static void fs_symlink_file(void *obj, void *data, QGuestAllocator *t_alloc)
648 {
649     QVirtio9P *v9p = obj;
650     v9fs_set_allocator(t_alloc);
651     struct stat st;
652     g_autofree char *real_file = virtio_9p_test_path("05/real_file");
653     g_autofree char *symlink_file = virtio_9p_test_path("05/symlink_file");
654 
655     tattach({ .client = v9p });
656     do_mkdir(v9p, "/", "05");
657     do_lcreate(v9p, "05", "real_file");
658     g_assert(stat(real_file, &st) == 0);
659     g_assert((st.st_mode & S_IFMT) == S_IFREG);
660 
661     do_symlink(v9p, "05", "symlink_file", "real_file");
662 
663     /* check if created link exists now */
664     g_assert(stat(symlink_file, &st) == 0);
665 }
666 
667 static void fs_unlinkat_symlink(void *obj, void *data,
668                                 QGuestAllocator *t_alloc)
669 {
670     QVirtio9P *v9p = obj;
671     v9fs_set_allocator(t_alloc);
672     struct stat st;
673     g_autofree char *real_file = virtio_9p_test_path("06/real_file");
674     g_autofree char *symlink_file = virtio_9p_test_path("06/symlink_file");
675 
676     tattach({ .client = v9p });
677     do_mkdir(v9p, "/", "06");
678     do_lcreate(v9p, "06", "real_file");
679     g_assert(stat(real_file, &st) == 0);
680     g_assert((st.st_mode & S_IFMT) == S_IFREG);
681 
682     do_symlink(v9p, "06", "symlink_file", "real_file");
683     g_assert(stat(symlink_file, &st) == 0);
684 
685     do_unlinkat(v9p, "06", "symlink_file", 0);
686     /* symlink should be gone now */
687     g_assert(stat(symlink_file, &st) != 0);
688 }
689 
690 static void fs_hardlink_file(void *obj, void *data, QGuestAllocator *t_alloc)
691 {
692     QVirtio9P *v9p = obj;
693     v9fs_set_allocator(t_alloc);
694     struct stat st_real, st_link;
695     g_autofree char *real_file = virtio_9p_test_path("07/real_file");
696     g_autofree char *hardlink_file = virtio_9p_test_path("07/hardlink_file");
697 
698     tattach({ .client = v9p });
699     do_mkdir(v9p, "/", "07");
700     do_lcreate(v9p, "07", "real_file");
701     g_assert(stat(real_file, &st_real) == 0);
702     g_assert((st_real.st_mode & S_IFMT) == S_IFREG);
703 
704     do_hardlink(v9p, "07", "hardlink_file", "07/real_file");
705 
706     /* check if link exists now ... */
707     g_assert(stat(hardlink_file, &st_link) == 0);
708     /* ... and it's a hard link, right? */
709     g_assert((st_link.st_mode & S_IFMT) == S_IFREG);
710     g_assert(st_link.st_dev == st_real.st_dev);
711     g_assert(st_link.st_ino == st_real.st_ino);
712 }
713 
714 static void fs_unlinkat_hardlink(void *obj, void *data,
715                                  QGuestAllocator *t_alloc)
716 {
717     QVirtio9P *v9p = obj;
718     v9fs_set_allocator(t_alloc);
719     struct stat st_real, st_link;
720     g_autofree char *real_file = virtio_9p_test_path("08/real_file");
721     g_autofree char *hardlink_file = virtio_9p_test_path("08/hardlink_file");
722 
723     tattach({ .client = v9p });
724     do_mkdir(v9p, "/", "08");
725     do_lcreate(v9p, "08", "real_file");
726     g_assert(stat(real_file, &st_real) == 0);
727     g_assert((st_real.st_mode & S_IFMT) == S_IFREG);
728 
729     do_hardlink(v9p, "08", "hardlink_file", "08/real_file");
730     g_assert(stat(hardlink_file, &st_link) == 0);
731 
732     do_unlinkat(v9p, "08", "hardlink_file", 0);
733     /* symlink should be gone now */
734     g_assert(stat(hardlink_file, &st_link) != 0);
735     /* and old file should still exist */
736     g_assert(stat(real_file, &st_real) == 0);
737 }
738 
739 static void *assign_9p_local_driver(GString *cmd_line, void *arg)
740 {
741     virtio_9p_assign_local_driver(cmd_line, "security_model=mapped-xattr");
742     return arg;
743 }
744 
745 static void register_virtio_9p_test(void)
746 {
747 
748     QOSGraphTestOptions opts = {
749     };
750 
751     /* 9pfs test cases using the 'synth' filesystem driver */
752     qos_add_test("synth/config", "virtio-9p", pci_config, &opts);
753     qos_add_test("synth/version/basic", "virtio-9p", fs_version,  &opts);
754     qos_add_test("synth/attach/basic", "virtio-9p", fs_attach,  &opts);
755     qos_add_test("synth/walk/basic", "virtio-9p", fs_walk,  &opts);
756     qos_add_test("synth/walk/no_slash", "virtio-9p", fs_walk_no_slash,
757                   &opts);
758     qos_add_test("synth/walk/none", "virtio-9p", fs_walk_none, &opts);
759     qos_add_test("synth/walk/dotdot_from_root", "virtio-9p",
760                  fs_walk_dotdot,  &opts);
761     qos_add_test("synth/walk/non_existent", "virtio-9p", fs_walk_nonexistent,
762                   &opts);
763     qos_add_test("synth/walk/2nd_non_existent", "virtio-9p",
764                  fs_walk_2nd_nonexistent, &opts);
765     qos_add_test("synth/lopen/basic", "virtio-9p", fs_lopen,  &opts);
766     qos_add_test("synth/write/basic", "virtio-9p", fs_write,  &opts);
767     qos_add_test("synth/flush/success", "virtio-9p", fs_flush_success,
768                   &opts);
769     qos_add_test("synth/flush/ignored", "virtio-9p", fs_flush_ignored,
770                   &opts);
771     qos_add_test("synth/readdir/basic", "virtio-9p", fs_readdir,  &opts);
772     qos_add_test("synth/readdir/split_512", "virtio-9p",
773                  fs_readdir_split_512,  &opts);
774     qos_add_test("synth/readdir/split_256", "virtio-9p",
775                  fs_readdir_split_256,  &opts);
776     qos_add_test("synth/readdir/split_128", "virtio-9p",
777                  fs_readdir_split_128,  &opts);
778 
779 
780     /* 9pfs test cases using the 'local' filesystem driver */
781 
782     /*
783      * XXX: Until we are sure that these tests can run everywhere,
784      * keep them as "slow" so that they aren't run with "make check".
785      */
786     if (!g_test_slow()) {
787         return;
788     }
789 
790     opts.before = assign_9p_local_driver;
791     qos_add_test("local/config", "virtio-9p", pci_config,  &opts);
792     qos_add_test("local/create_dir", "virtio-9p", fs_create_dir, &opts);
793     qos_add_test("local/unlinkat_dir", "virtio-9p", fs_unlinkat_dir, &opts);
794     qos_add_test("local/create_file", "virtio-9p", fs_create_file, &opts);
795     qos_add_test("local/unlinkat_file", "virtio-9p", fs_unlinkat_file, &opts);
796     qos_add_test("local/symlink_file", "virtio-9p", fs_symlink_file, &opts);
797     qos_add_test("local/unlinkat_symlink", "virtio-9p", fs_unlinkat_symlink,
798                  &opts);
799     qos_add_test("local/hardlink_file", "virtio-9p", fs_hardlink_file, &opts);
800     qos_add_test("local/unlinkat_hardlink", "virtio-9p", fs_unlinkat_hardlink,
801                  &opts);
802 }
803 
804 libqos_init(register_virtio_9p_test);
805 
806 static void __attribute__((constructor)) construct_9p_test(void)
807 {
808     /* make sure test dir for the 'local' tests exists */
809     virtio_9p_create_local_test_dir();
810 }
811 
812 static void __attribute__((destructor)) destruct_9p_test(void)
813 {
814     /* remove previously created test dir when test suite completed */
815     virtio_9p_remove_local_test_dir();
816 }
817