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