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