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