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