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