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