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