xref: /qemu/tests/qtest/virtio-9p-test.c (revision e11680102af6a9f42ab198a77015696820a1993e)
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 #define tlopen(...) v9fs_tlopen((TLOpenOpt) __VA_ARGS__)
25 #define twrite(...) v9fs_twrite((TWriteOpt) __VA_ARGS__)
26 #define tflush(...) v9fs_tflush((TFlushOpt) __VA_ARGS__)
27 #define tmkdir(...) v9fs_tmkdir((TMkdirOpt) __VA_ARGS__)
28 
29 static void pci_config(void *obj, void *data, QGuestAllocator *t_alloc)
30 {
31     QVirtio9P *v9p = obj;
32     v9fs_set_allocator(t_alloc);
33     size_t tag_len = qvirtio_config_readw(v9p->vdev, 0);
34     g_autofree char *tag = NULL;
35     int i;
36 
37     g_assert_cmpint(tag_len, ==, strlen(MOUNT_TAG));
38 
39     tag = g_malloc(tag_len);
40     for (i = 0; i < tag_len; i++) {
41         tag[i] = qvirtio_config_readb(v9p->vdev, i + 2);
42     }
43     g_assert_cmpmem(tag, tag_len, MOUNT_TAG, tag_len);
44 }
45 
46 static inline bool is_same_qid(v9fs_qid a, v9fs_qid b)
47 {
48     /* don't compare QID version for checking for file ID equalness */
49     return a[0] == b[0] && memcmp(&a[5], &b[5], 8) == 0;
50 }
51 
52 static void fs_version(void *obj, void *data, QGuestAllocator *t_alloc)
53 {
54     v9fs_set_allocator(t_alloc);
55     tversion({ .client = obj });
56 }
57 
58 static void fs_attach(void *obj, void *data, QGuestAllocator *t_alloc)
59 {
60     v9fs_set_allocator(t_alloc);
61     tattach({ .client = obj });
62 }
63 
64 static void fs_walk(void *obj, void *data, QGuestAllocator *t_alloc)
65 {
66     QVirtio9P *v9p = obj;
67     v9fs_set_allocator(t_alloc);
68     char *wnames[P9_MAXWELEM];
69     uint16_t nwqid;
70     g_autofree v9fs_qid *wqid = NULL;
71     int i;
72 
73     for (i = 0; i < P9_MAXWELEM; i++) {
74         wnames[i] = g_strdup_printf(QTEST_V9FS_SYNTH_WALK_FILE, i);
75     }
76 
77     tattach({ .client = v9p });
78     twalk({
79         .client = v9p, .fid = 0, .newfid = 1,
80         .nwname = P9_MAXWELEM, .wnames = wnames,
81         .rwalk = { .nwqid = &nwqid, .wqid = &wqid }
82     });
83 
84     g_assert_cmpint(nwqid, ==, P9_MAXWELEM);
85 
86     for (i = 0; i < P9_MAXWELEM; i++) {
87         g_free(wnames[i]);
88     }
89 }
90 
91 static bool fs_dirents_contain_name(struct V9fsDirent *e, const char* name)
92 {
93     for (; e; e = e->next) {
94         if (!strcmp(e->name, name)) {
95             return true;
96         }
97     }
98     return false;
99 }
100 
101 /* basic readdir test where reply fits into a single response message */
102 static void fs_readdir(void *obj, void *data, QGuestAllocator *t_alloc)
103 {
104     QVirtio9P *v9p = obj;
105     v9fs_set_allocator(t_alloc);
106     char *wnames[] = { g_strdup(QTEST_V9FS_SYNTH_READDIR_DIR) };
107     uint16_t nqid;
108     v9fs_qid qid;
109     uint32_t count, nentries;
110     struct V9fsDirent *entries = NULL;
111 
112     tattach({ .client = v9p });
113     twalk({
114         .client = v9p, .fid = 0, .newfid = 1,
115         .nwname = 1, .wnames = wnames, .rwalk.nwqid = &nqid
116     });
117     g_assert_cmpint(nqid, ==, 1);
118 
119     tlopen({
120         .client = v9p, .fid = 1, .flags = O_DIRECTORY, .rlopen.qid = &qid
121     });
122 
123     /*
124      * submit count = msize - 11, because 11 is the header size of Rreaddir
125      */
126     treaddir({
127         .client = v9p, .fid = 1, .offset = 0, .count = P9_MAX_SIZE - 11,
128         .rreaddir = {
129             .count = &count, .nentries = &nentries, .entries = &entries
130         }
131     });
132 
133     /*
134      * Assuming msize (P9_MAX_SIZE) is large enough so we can retrieve all
135      * dir entries with only one readdir request.
136      */
137     g_assert_cmpint(
138         nentries, ==,
139         QTEST_V9FS_SYNTH_READDIR_NFILES + 2 /* "." and ".." */
140     );
141 
142     /*
143      * Check all file names exist in returned entries, ignore their order
144      * though.
145      */
146     g_assert_cmpint(fs_dirents_contain_name(entries, "."), ==, true);
147     g_assert_cmpint(fs_dirents_contain_name(entries, ".."), ==, true);
148     for (int i = 0; i < QTEST_V9FS_SYNTH_READDIR_NFILES; ++i) {
149         g_autofree char *name =
150             g_strdup_printf(QTEST_V9FS_SYNTH_READDIR_FILE, i);
151         g_assert_cmpint(fs_dirents_contain_name(entries, name), ==, true);
152     }
153 
154     v9fs_free_dirents(entries);
155     g_free(wnames[0]);
156 }
157 
158 /* readdir test where overall request is split over several messages */
159 static void do_readdir_split(QVirtio9P *v9p, uint32_t count)
160 {
161     char *wnames[] = { g_strdup(QTEST_V9FS_SYNTH_READDIR_DIR) };
162     uint16_t nqid;
163     v9fs_qid qid;
164     uint32_t nentries, npartialentries;
165     struct V9fsDirent *entries, *tail, *partialentries;
166     int fid;
167     uint64_t offset;
168 
169     tattach({ .client = v9p });
170 
171     fid = 1;
172     offset = 0;
173     entries = NULL;
174     nentries = 0;
175     tail = NULL;
176 
177     twalk({
178         .client = v9p, .fid = 0, .newfid = fid,
179         .nwname = 1, .wnames = wnames, .rwalk.nwqid = &nqid
180     });
181     g_assert_cmpint(nqid, ==, 1);
182 
183     tlopen({
184         .client = v9p, .fid = fid, .flags = O_DIRECTORY, .rlopen.qid = &qid
185     });
186 
187     /*
188      * send as many Treaddir requests as required to get all directory
189      * entries
190      */
191     while (true) {
192         npartialentries = 0;
193         partialentries = NULL;
194 
195         treaddir({
196             .client = v9p, .fid = fid, .offset = offset, .count = count,
197             .rreaddir = {
198                 .count = &count, .nentries = &npartialentries,
199                 .entries = &partialentries
200             }
201         });
202         if (npartialentries > 0 && partialentries) {
203             if (!entries) {
204                 entries = partialentries;
205                 nentries = npartialentries;
206                 tail = partialentries;
207             } else {
208                 tail->next = partialentries;
209                 nentries += npartialentries;
210             }
211             while (tail->next) {
212                 tail = tail->next;
213             }
214             offset = tail->offset;
215         } else {
216             break;
217         }
218     }
219 
220     g_assert_cmpint(
221         nentries, ==,
222         QTEST_V9FS_SYNTH_READDIR_NFILES + 2 /* "." and ".." */
223     );
224 
225     /*
226      * Check all file names exist in returned entries, ignore their order
227      * though.
228      */
229     g_assert_cmpint(fs_dirents_contain_name(entries, "."), ==, true);
230     g_assert_cmpint(fs_dirents_contain_name(entries, ".."), ==, true);
231     for (int i = 0; i < QTEST_V9FS_SYNTH_READDIR_NFILES; ++i) {
232         char *name = g_strdup_printf(QTEST_V9FS_SYNTH_READDIR_FILE, i);
233         g_assert_cmpint(fs_dirents_contain_name(entries, name), ==, true);
234         g_free(name);
235     }
236 
237     v9fs_free_dirents(entries);
238 
239     g_free(wnames[0]);
240 }
241 
242 static void fs_walk_no_slash(void *obj, void *data, QGuestAllocator *t_alloc)
243 {
244     QVirtio9P *v9p = obj;
245     v9fs_set_allocator(t_alloc);
246     char *wnames[] = { g_strdup(" /") };
247 
248     tattach({ .client = v9p });
249     twalk({
250         .client = v9p, .fid = 0, .newfid = 1, .nwname = 1, .wnames = wnames,
251         .expectErr = ENOENT
252     });
253 
254     g_free(wnames[0]);
255 }
256 
257 static void fs_walk_nonexistent(void *obj, void *data, QGuestAllocator *t_alloc)
258 {
259     QVirtio9P *v9p = obj;
260     v9fs_set_allocator(t_alloc);
261 
262     tattach({ .client = v9p });
263     /*
264      * The 9p2000 protocol spec says: "If the first element cannot be walked
265      * for any reason, Rerror is returned."
266      */
267     twalk({ .client = v9p, .path = "non-existent", .expectErr = ENOENT });
268 }
269 
270 static void fs_walk_2nd_nonexistent(void *obj, void *data,
271                                     QGuestAllocator *t_alloc)
272 {
273     QVirtio9P *v9p = obj;
274     v9fs_set_allocator(t_alloc);
275     v9fs_qid root_qid;
276     uint16_t nwqid;
277     uint32_t fid;
278     g_autofree v9fs_qid *wqid = NULL;
279     g_autofree char *path = g_strdup_printf(
280         QTEST_V9FS_SYNTH_WALK_FILE "/non-existent", 0
281     );
282 
283     tattach({ .client = v9p, .rattach.qid = &root_qid });
284     fid = twalk({
285         .client = v9p, .path = path,
286         .rwalk = { .nwqid = &nwqid, .wqid = &wqid }
287     }).newfid;
288     /*
289      * The 9p2000 protocol spec says: "nwqid is therefore either nwname or the
290      * index of the first elementwise walk that failed."
291      */
292     assert(nwqid == 1);
293 
294     /* returned QID wqid[0] is file ID of 1st subdir */
295     g_assert(wqid && wqid[0] && !is_same_qid(root_qid, wqid[0]));
296 
297     /* expect fid being unaffected by walk above */
298     tgetattr({
299         .client = v9p, .fid = fid, .request_mask = P9_GETATTR_BASIC,
300         .expectErr = ENOENT
301     });
302 }
303 
304 static void fs_walk_none(void *obj, void *data, QGuestAllocator *t_alloc)
305 {
306     QVirtio9P *v9p = obj;
307     v9fs_set_allocator(t_alloc);
308     v9fs_qid root_qid;
309     g_autofree v9fs_qid *wqid = NULL;
310     struct v9fs_attr attr;
311 
312     tversion({ .client = v9p });
313     tattach({
314         .client = v9p, .fid = 0, .n_uname = getuid(),
315         .rattach.qid = &root_qid
316     });
317 
318     twalk({
319         .client = v9p, .fid = 0, .newfid = 1, .nwname = 0, .wnames = NULL,
320         .rwalk.wqid = &wqid
321     });
322 
323     /* special case: no QID is returned if nwname=0 was sent */
324     g_assert(wqid == NULL);
325 
326     tgetattr({
327         .client = v9p, .fid = 1, .request_mask = P9_GETATTR_BASIC,
328         .rgetattr.attr = &attr
329     });
330 
331     g_assert(is_same_qid(root_qid, attr.qid));
332 }
333 
334 static void fs_walk_dotdot(void *obj, void *data, QGuestAllocator *t_alloc)
335 {
336     QVirtio9P *v9p = obj;
337     v9fs_set_allocator(t_alloc);
338     char *wnames[] = { g_strdup("..") };
339     v9fs_qid root_qid;
340     g_autofree v9fs_qid *wqid = NULL;
341 
342     tversion({ .client = v9p });
343     tattach({
344         .client = v9p, .fid = 0, .n_uname = getuid(),
345         .rattach.qid = &root_qid
346     });
347 
348     twalk({
349         .client = v9p, .fid = 0, .newfid = 1, .nwname = 1, .wnames = wnames,
350         .rwalk.wqid = &wqid /* We now we'll get one qid */
351     });
352 
353     g_assert_cmpmem(&root_qid, 13, wqid[0], 13);
354 
355     g_free(wnames[0]);
356 }
357 
358 static void fs_lopen(void *obj, void *data, QGuestAllocator *t_alloc)
359 {
360     QVirtio9P *v9p = obj;
361     v9fs_set_allocator(t_alloc);
362     char *wnames[] = { g_strdup(QTEST_V9FS_SYNTH_LOPEN_FILE) };
363 
364     tattach({ .client = v9p });
365     twalk({
366         .client = v9p, .fid = 0, .newfid = 1, .nwname = 1, .wnames = wnames
367     });
368 
369     tlopen({ .client = v9p, .fid = 1, .flags = O_WRONLY });
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 
383     tattach({ .client = v9p });
384     twalk({
385         .client = v9p, .fid = 0, .newfid = 1, .nwname = 1, .wnames = wnames
386     });
387 
388     tlopen({ .client = v9p, .fid = 1, .flags = O_WRONLY });
389 
390     count = twrite({
391         .client = v9p, .fid = 1, .offset = 0, .count = write_count,
392         .data = buf
393     }).count;
394     g_assert_cmpint(count, ==, write_count);
395 
396     g_free(wnames[0]);
397 }
398 
399 static void fs_flush_success(void *obj, void *data, QGuestAllocator *t_alloc)
400 {
401     QVirtio9P *v9p = obj;
402     v9fs_set_allocator(t_alloc);
403     char *wnames[] = { g_strdup(QTEST_V9FS_SYNTH_FLUSH_FILE) };
404     P9Req *req, *flush_req;
405     uint32_t reply_len;
406     uint8_t should_block;
407 
408     tattach({ .client = v9p });
409     twalk({
410         .client = v9p, .fid = 0, .newfid = 1, .nwname = 1, .wnames = wnames
411     });
412 
413     tlopen({ .client = v9p, .fid = 1, .flags = O_WRONLY });
414 
415     /* This will cause the 9p server to try to write data to the backend,
416      * until the write request gets cancelled.
417      */
418     should_block = 1;
419     req = twrite({
420         .client = v9p, .fid = 1, .offset = 0,
421         .count = sizeof(should_block), .data = &should_block,
422         .requestOnly = true
423     }).req;
424 
425     flush_req = tflush({
426         .client = v9p, .oldtag = req->tag, .tag = 1, .requestOnly = true
427     }).req;
428 
429     /* The write request is supposed to be flushed: the server should just
430      * mark the write request as used and reply to the flush request.
431      */
432     v9fs_req_wait_for_reply(req, &reply_len);
433     g_assert_cmpint(reply_len, ==, 0);
434     v9fs_req_free(req);
435     v9fs_rflush(flush_req);
436 
437     g_free(wnames[0]);
438 }
439 
440 static void fs_flush_ignored(void *obj, void *data, QGuestAllocator *t_alloc)
441 {
442     QVirtio9P *v9p = obj;
443     v9fs_set_allocator(t_alloc);
444     char *wnames[] = { g_strdup(QTEST_V9FS_SYNTH_FLUSH_FILE) };
445     P9Req *req, *flush_req;
446     uint32_t count;
447     uint8_t should_block;
448 
449     tattach({ .client = v9p });
450     twalk({
451         .client = v9p, .fid = 0, .newfid = 1, .nwname = 1, .wnames = wnames
452     });
453 
454     tlopen({ .client = v9p, .fid = 1, .flags = O_WRONLY });
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 = twrite({
461         .client = v9p, .fid = 1, .offset = 0,
462         .count = sizeof(should_block), .data = &should_block,
463         .requestOnly = true
464     }).req;
465 
466     flush_req = tflush({
467         .client = v9p, .oldtag = req->tag, .tag = 1, .requestOnly = true
468     }).req;
469 
470     /* The write request is supposed to complete. The server should
471      * reply to the write request and the flush request.
472      */
473     v9fs_req_wait_for_reply(req, NULL);
474     v9fs_rwrite(req, &count);
475     g_assert_cmpint(count, ==, sizeof(should_block));
476     v9fs_rflush(flush_req);
477 
478     g_free(wnames[0]);
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     tmkdir({ .client = v9p, .atPath = "/", .name = "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     tmkdir({ .client = v9p, .atPath = "/", .name = "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     tmkdir({ .client = v9p, .atPath = "/", .name = "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     tmkdir({ .client = v9p, .atPath = "/", .name = "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     tmkdir({ .client = v9p, .atPath = "/", .name = "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     tmkdir({ .client = v9p, .atPath = "/", .name = "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     tmkdir({ .client = v9p, .atPath = "/", .name = "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     tmkdir({ .client = v9p, .atPath = "/", .name = "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