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