1 /*
2  * Copyright (c) 2025 Antmicro <www.antmicro.com>
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include <zephyr/init.h>
8 #include <zephyr/fs/fs.h>
9 #include <zephyr/fs/fs_sys.h>
10 #include <zephyr/fs/virtiofs.h>
11 #include <zephyr/posix/fcntl.h>
12 #include "../fs_impl.h"
13 #include <string.h>
14 #include "virtiofs.h"
15 
16 #define MODE_FTYPE_MASK 0170000
17 #define MODE_FTYPE_DIR 040000
18 
19 #define DT_DIR 4
20 #define DT_REG 8
21 
22 struct virtiofs_file {
23 	uint64_t fh;
24 	uint64_t nodeid;
25 	uint64_t offset;
26 	uint32_t open_flags;
27 };
28 
29 struct virtiofs_dir {
30 	uint64_t fh;
31 	uint64_t nodeid;
32 	uint64_t offset;
33 };
34 
35 K_MEM_SLAB_DEFINE_STATIC(
36 	file_struct_slab, sizeof(struct virtiofs_file), CONFIG_VIRTIOFS_MAX_FILES, sizeof(void *)
37 );
38 K_MEM_SLAB_DEFINE_STATIC(
39 	dir_struct_slab, sizeof(struct virtiofs_dir), CONFIG_VIRTIOFS_MAX_FILES, sizeof(void *)
40 );
41 
zephyr_mode_to_posix(int m)42 static int zephyr_mode_to_posix(int m)
43 {
44 	int mode = (m & FS_O_CREATE) ? O_CREAT : 0;
45 
46 	mode |= (m & FS_O_APPEND) ? O_APPEND : 0;
47 	mode |= (m & FS_O_TRUNC) ? O_TRUNC : 0;
48 
49 	switch (m & FS_O_MODE_MASK) {
50 	case FS_O_READ:
51 		mode |= O_RDONLY;
52 		break;
53 	case FS_O_WRITE:
54 		mode |= O_WRONLY;
55 		break;
56 	case FS_O_RDWR:
57 		mode |= O_RDWR;
58 		break;
59 	default:
60 		break;
61 	}
62 
63 	return mode;
64 }
65 
virtiofs_strip_prefix(const char * path,const struct fs_mount_t * mp)66 static const char *virtiofs_strip_prefix(const char *path, const struct fs_mount_t *mp)
67 {
68 	const char *virtiofs_path = fs_impl_strip_prefix(path, mp);
69 
70 	if (virtiofs_path[0] == '/') {
71 		virtiofs_path++;
72 	}
73 	return virtiofs_path;
74 }
75 
strip_path(const char * fpath)76 static const char *strip_path(const char *fpath)
77 {
78 	const char *c = fpath + strlen(fpath);
79 
80 	for (; c > fpath; c--) {
81 		if (*c == '/') {
82 			c++;
83 			break;
84 		}
85 	}
86 
87 	return c;
88 }
89 
90 /*
91  * despite the similarity of fuse/virtiofs to posix fs functions there are some notable differences:
92  * - open() is split into lookup+open in case of existing files and lookup+create in case of
93  *   O_CREATE
94  * - opendir() is split into lookup+opendir
95  * - lookups are non-recursive, we have to traverse through each directory in the path
96  * - close()/closedir() is split into release+forget/releasedir+forget
97  * - read()/write()/readdir() takes offset as a parameter
98  * - there is sort of reverse stat() - settatr, that can be used to i.e. truncate the file
99  */
100 
virtiofs_zfs_open_existing(struct fs_file_t * filp,struct fuse_entry_out lookup_ret,int flags)101 static int virtiofs_zfs_open_existing(
102 	struct fs_file_t *filp, struct fuse_entry_out lookup_ret, int flags)
103 {
104 	struct fuse_open_out open_ret;
105 	struct virtiofs_file *file;
106 
107 	int ret = virtiofs_open(
108 		filp->mp->storage_dev, lookup_ret.nodeid, zephyr_mode_to_posix(flags), &open_ret,
109 		FUSE_FILE
110 	);
111 	if (ret == 0) {
112 		ret = k_mem_slab_alloc(&file_struct_slab, (void **)&file, K_NO_WAIT);
113 		if (ret != 0) {
114 			virtiofs_release(
115 				filp->mp->storage_dev, lookup_ret.nodeid, open_ret.fh, FUSE_FILE
116 			);
117 			return ret;
118 		}
119 
120 		file->fh = open_ret.fh;
121 		file->nodeid = lookup_ret.nodeid;
122 		file->offset = 0;
123 		file->open_flags = flags;
124 
125 		filp->filep = file;
126 	}
127 
128 	return ret;
129 }
130 
virtiofs_zfs_open_create(struct fs_file_t * filp,int flags,const char * path,uint64_t parent_inode)131 static int virtiofs_zfs_open_create(
132 	struct fs_file_t *filp, int flags, const char *path, uint64_t parent_inode)
133 {
134 	struct fuse_create_out create_ret;
135 	const char *fname = strip_path(path);
136 	struct virtiofs_file *file;
137 
138 	int ret = virtiofs_create(
139 		filp->mp->storage_dev, parent_inode, fname,
140 		zephyr_mode_to_posix(flags), CONFIG_VIRTIOFS_CREATE_MODE_VALUE, &create_ret
141 	);
142 	if (ret == 0) {
143 		ret = k_mem_slab_alloc(&file_struct_slab, (void **)&file, K_NO_WAIT);
144 		if (ret != 0) {
145 			virtiofs_release(
146 				filp->mp->storage_dev, create_ret.entry_out.nodeid,
147 				create_ret.open_out.fh, FUSE_FILE
148 			);
149 			return ret;
150 		}
151 
152 		file->fh = create_ret.open_out.fh;
153 		file->nodeid = create_ret.entry_out.nodeid;
154 		file->offset = 0;
155 		file->open_flags = flags;
156 
157 		filp->filep = file;
158 	}
159 
160 	return ret;
161 }
162 
virtiofs_zfs_open(struct fs_file_t * filp,const char * fs_path,fs_mode_t flags)163 static int virtiofs_zfs_open(struct fs_file_t *filp, const char *fs_path, fs_mode_t flags)
164 {
165 	int ret = 0;
166 	struct fuse_entry_out lookup_ret;
167 	const char *path = virtiofs_strip_prefix(fs_path, filp->mp);
168 	uint64_t parent_inode = FUSE_ROOT_INODE;
169 
170 	ret = virtiofs_lookup(
171 		filp->mp->storage_dev, FUSE_ROOT_INODE, path, &lookup_ret, &parent_inode
172 	);
173 	if (ret == 0) {
174 		ret = virtiofs_zfs_open_existing(filp, lookup_ret, flags & ~FS_O_CREATE);
175 	} else if ((flags & FS_O_CREATE) && parent_inode != 0) {
176 		ret = virtiofs_zfs_open_create(filp, flags, path, parent_inode);
177 	} else {
178 		if (parent_inode != 0) {
179 			virtiofs_forget(filp->mp->storage_dev, parent_inode, 1);
180 		}
181 		return ret;
182 	}
183 
184 	if (parent_inode != 0) {
185 		virtiofs_forget(filp->mp->storage_dev, parent_inode, 1);
186 	}
187 
188 	if (ret != 0) {
189 		virtiofs_forget(filp->mp->storage_dev, lookup_ret.nodeid, 1);
190 	}
191 
192 	return ret;
193 }
194 
virtiofs_zfs_close(struct fs_file_t * filp)195 static int virtiofs_zfs_close(struct fs_file_t *filp)
196 {
197 	struct virtiofs_file *file = filp->filep;
198 	uint64_t nodeid = file->nodeid;
199 	int ret = virtiofs_release(filp->mp->storage_dev, file->nodeid, file->fh, FUSE_FILE);
200 
201 	if (ret == 0) {
202 		k_mem_slab_free(&file_struct_slab, file);
203 	} else {
204 		return ret;
205 	}
206 
207 	virtiofs_forget(filp->mp->storage_dev, nodeid, 1);
208 
209 	return 0;
210 }
211 
virtiofs_zfs_read(struct fs_file_t * filp,void * dest,size_t nbytes)212 static ssize_t virtiofs_zfs_read(struct fs_file_t *filp, void *dest, size_t nbytes)
213 {
214 	struct virtiofs_file *file = filp->filep;
215 	int read_c = virtiofs_read(
216 		filp->mp->storage_dev, file->nodeid, file->fh, file->offset, nbytes, dest
217 	);
218 
219 	if (read_c >= 0) {
220 		file->offset += read_c;
221 	}
222 
223 	return read_c;
224 }
225 
226 #define FUSE_SEEK_SET 0
227 #define FUSE_SEEK_CUR 1
228 #define FUSE_SEEK_END 2
229 
zephyr_whence_to_posix(int whence)230 static int zephyr_whence_to_posix(int whence)
231 {
232 	switch (whence) {
233 	case SEEK_SET:
234 		return FUSE_SEEK_SET;
235 	case SEEK_CUR:
236 		return FUSE_SEEK_CUR;
237 	case SEEK_END:
238 		return FUSE_SEEK_END;
239 	default:
240 		return whence;
241 	}
242 }
243 
virtio_zfs_lseek(struct fs_file_t * filp,off_t off,int whence)244 static int virtio_zfs_lseek(struct fs_file_t *filp, off_t off, int whence)
245 {
246 	struct virtiofs_file *file = filp->filep;
247 	struct fuse_lseek_out lseek_ret;
248 	uint64_t off_arg = off;
249 
250 	whence = zephyr_whence_to_posix(whence);
251 
252 	/*
253 	 * SEEK_CUR is kind of broken with FUSE_LSEEK as reads/writes don't update the file
254 	 * offset on the host side so if we never used FUSE_LSEEK since opening the file, but
255 	 * did some reads/writes in the meantime and then used FUSE_LSEEK with SEEK_CUR+x,
256 	 * the returned offset would've been x instead of sum of read/written bytes + x. One
257 	 * solution is to pair each read/write with lseek(SEEK_CUR, read_c/write_c) to keep
258 	 * the offset updated on the host side, but we just don't use SEEK_CUR+x and instead
259 	 * use SEEK_SET with file->offset+x. Essentially the only thing FUSE_LSEEK provides
260 	 * for us is bounds checking and easier handling of SEEK_END (otherwise we would have
261 	 * to use other fuse call to determine file size)
262 	 */
263 	if (whence == FUSE_SEEK_CUR) {
264 		whence = FUSE_SEEK_SET;
265 		off_arg = file->offset + off;
266 	}
267 
268 	int ret = virtiofs_lseek(
269 		filp->mp->storage_dev, file->nodeid, file->fh, off_arg, whence, &lseek_ret
270 	);
271 
272 	if (ret != 0) {
273 		return ret;
274 	}
275 
276 	file->offset = lseek_ret.offset;
277 
278 	return file->offset;
279 }
280 
virtio_zfs_write(struct fs_file_t * filp,const void * src,size_t nbytes)281 static ssize_t virtio_zfs_write(struct fs_file_t *filp, const void *src, size_t nbytes)
282 {
283 	struct virtiofs_file *file = filp->filep;
284 	struct virtiofs_fs_data *fs_data = filp->mp->fs_data;
285 	const uint8_t *curr_addr = src;
286 	int write_c = 0;
287 
288 	while (nbytes > 0) {
289 		/*
290 		 * max write size is limited to max_write from fuse_init_out received during fs
291 		 * initalization, so we have to split bigger writes into multiple smaller ones.
292 		 * If we try to write more the recent virtiofsd it will return 12 (Not enough
293 		 * space), but the older one will assert, rendering fs unusable until restart.
294 		 */
295 		size_t curr_size = MIN(nbytes, fs_data->max_write);
296 
297 		/*
298 		 * while FUSE_WRITE will always write to the end if O_APPEND was passed when opening
299 		 * file (ignoring offset param) the file offset itself will remain unmodified on
300 		 * zephyr side, so we have to update it here
301 		 */
302 		if (file->open_flags & FS_O_APPEND) {
303 			virtio_zfs_lseek(filp, 0, SEEK_END);
304 		}
305 
306 		int ret = virtiofs_write(
307 			filp->mp->storage_dev, file->nodeid, file->fh, file->offset + write_c,
308 			curr_size, curr_addr
309 		);
310 
311 		if (ret >= 0) {
312 			write_c += ret;
313 		} else {
314 			/*
315 			 * according to fs_write comment in fs.h zephyr handles partial
316 			 * failures like that
317 			 */
318 			if (write_c > 0) {
319 				errno = ret;
320 				file->offset += write_c;
321 				return write_c;
322 			} else {
323 				return ret;
324 			}
325 		}
326 
327 		nbytes -= curr_size;
328 		curr_addr += curr_size;
329 	}
330 
331 	file->offset += write_c;
332 	return write_c;
333 }
334 
virtiofs_zfs_tell(struct fs_file_t * filp)335 static off_t virtiofs_zfs_tell(struct fs_file_t *filp)
336 {
337 	struct virtiofs_file *file = filp->filep;
338 
339 	return file->offset;
340 }
341 
virtiofs_zfs_truncate(struct fs_file_t * filp,off_t length)342 static int virtiofs_zfs_truncate(struct fs_file_t *filp, off_t length)
343 {
344 	struct virtiofs_file *file = filp->filep;
345 	struct fuse_setattr_in attrs;
346 	struct fuse_attr_out setattr_ret;
347 
348 	attrs.fh = file->fh;
349 	attrs.size = length;
350 	attrs.valid = FATTR_SIZE;
351 
352 	return virtiofs_setattr(filp->mp->storage_dev, file->nodeid, &attrs, &setattr_ret);
353 }
354 
virtiofs_zfs_sync(struct fs_file_t * filp)355 static int virtiofs_zfs_sync(struct fs_file_t *filp)
356 {
357 	struct virtiofs_file *file = filp->filep;
358 
359 	return virtiofs_fsync(filp->mp->storage_dev, file->nodeid, file->fh);
360 }
361 
virtiofs_zfs_mkdir(struct fs_mount_t * mountp,const char * name)362 static int virtiofs_zfs_mkdir(struct fs_mount_t *mountp, const char *name)
363 {
364 	const char *path = virtiofs_strip_prefix(name, mountp);
365 	struct fuse_entry_out lookup_ret;
366 	uint64_t parent_inode = FUSE_ROOT_INODE;
367 	int ret = virtiofs_lookup(
368 		mountp->storage_dev, FUSE_ROOT_INODE, path, &lookup_ret, &parent_inode
369 	);
370 
371 	if (parent_inode != 0) {
372 		ret = virtiofs_mkdir(
373 			mountp->storage_dev, parent_inode, strip_path(name),
374 			CONFIG_VIRTIOFS_CREATE_MODE_VALUE
375 		);
376 		virtiofs_forget(mountp->storage_dev, parent_inode, 1);
377 	}
378 
379 	return ret;
380 }
381 
virtiofs_zfs_opendir(struct fs_dir_t * dirp,const char * fs_path)382 static int virtiofs_zfs_opendir(struct fs_dir_t *dirp, const char *fs_path)
383 {
384 	int ret = 0;
385 	struct virtiofs_dir *dir;
386 	struct fuse_entry_out lookup_ret;
387 	const char *path = virtiofs_strip_prefix(fs_path, dirp->mp);
388 
389 	if (path[0] == '\0') {
390 		/* looking up for "" or "/" yields nothing, so we have to use "." for root dir */
391 		path = ".";
392 	}
393 
394 	ret = virtiofs_lookup(dirp->mp->storage_dev, FUSE_ROOT_INODE, path, &lookup_ret, NULL);
395 	if (ret == 0) {
396 		struct fuse_open_out open_ret;
397 
398 		ret = virtiofs_open(
399 			dirp->mp->storage_dev, lookup_ret.nodeid, O_RDONLY, &open_ret, FUSE_DIR
400 		);
401 		if (ret == 0) {
402 			ret = k_mem_slab_alloc(&dir_struct_slab, (void **)&dir, K_NO_WAIT);
403 			if (ret != 0) {
404 				virtiofs_forget(dirp->mp->storage_dev, lookup_ret.nodeid, 1);
405 				return ret;
406 			}
407 
408 			dir->fh = open_ret.fh;
409 			dir->nodeid = lookup_ret.nodeid;
410 			dir->offset = 0;
411 			dirp->dirp = dir;
412 		}
413 	} else {
414 		virtiofs_forget(dirp->mp->storage_dev, lookup_ret.nodeid, 1);
415 	}
416 
417 	return ret;
418 }
419 
virtiofs_zfs_readdir(struct fs_dir_t * dirp,struct fs_dirent * entry)420 static int virtiofs_zfs_readdir(struct fs_dir_t *dirp, struct fs_dirent *entry)
421 {
422 	struct virtiofs_dir *dir = dirp->dirp;
423 	struct fuse_dirent de;
424 
425 	int read_c = virtiofs_readdir(
426 		dirp->mp->storage_dev, dir->nodeid, dir->fh, dir->offset,
427 		(uint8_t *)&de, sizeof(de), (uint8_t *)&entry->name, sizeof(entry->name)
428 	);
429 
430 	/* end of dir */
431 	if (read_c == 0) {
432 		entry->name[0] = '\0';
433 		return 0;
434 	}
435 
436 	if (read_c < sizeof(de) || de.namelen >= sizeof(entry->name) - 1) {
437 		return -EIO;
438 	}
439 
440 	/*
441 	 * usually name is already null terminated, but sometimes name of the last entry
442 	 * in directory is not (maybe also in other cases), so we null terminate it here
443 	 */
444 	entry->name[de.namelen] = '\0';
445 
446 	dir->offset = de.off;
447 
448 	if (de.type == DT_REG) {
449 		struct fuse_entry_out lookup_ret;
450 		int ret = virtiofs_lookup(
451 			dirp->mp->storage_dev, dir->nodeid, entry->name, &lookup_ret, NULL
452 		);
453 
454 		if (ret != 0) {
455 			return ret;
456 		}
457 
458 		virtiofs_forget(dirp->mp->storage_dev, lookup_ret.nodeid, 1);
459 
460 		entry->type = FS_DIR_ENTRY_FILE;
461 		entry->size = lookup_ret.attr.size;
462 	} else if (de.type == DT_DIR) {
463 		entry->type = FS_DIR_ENTRY_DIR;
464 		entry->size = 0;
465 	} else {
466 		return -ENOTSUP;
467 	}
468 
469 	return 0;
470 }
471 
virtiofs_zfs_closedir(struct fs_dir_t * dirp)472 static int virtiofs_zfs_closedir(struct fs_dir_t *dirp)
473 {
474 	struct virtiofs_dir *dir = dirp->dirp;
475 	uint64_t nodeid = dir->nodeid;
476 	int ret = virtiofs_release(dirp->mp->storage_dev, dir->nodeid, dir->fh, FUSE_DIR);
477 
478 	if (ret == 0) {
479 		k_mem_slab_free(&dir_struct_slab, dir);
480 	} else {
481 		return ret;
482 	}
483 
484 	virtiofs_forget(dirp->mp->storage_dev, nodeid, 1);
485 
486 	return 0;
487 }
488 
virtiofs_zfs_mount(struct fs_mount_t * mountp)489 static int virtiofs_zfs_mount(struct fs_mount_t *mountp)
490 {
491 	struct fuse_init_out out;
492 	int ret = virtiofs_init(mountp->storage_dev, &out);
493 
494 	if (ret == 0) {
495 		struct virtiofs_fs_data *fs_data = mountp->fs_data;
496 
497 		fs_data->max_write = out.max_write;
498 	}
499 
500 	return ret;
501 }
502 
virtiofs_zfs_unmount(struct fs_mount_t * mountp)503 static int virtiofs_zfs_unmount(struct fs_mount_t *mountp)
504 {
505 	return virtiofs_destroy(mountp->storage_dev);
506 }
507 
virtiofs_zfs_stat(struct fs_mount_t * mountp,const char * fs_path,struct fs_dirent * entry)508 static int virtiofs_zfs_stat(
509 	struct fs_mount_t *mountp, const char *fs_path, struct fs_dirent *entry)
510 {
511 	const char *path = virtiofs_strip_prefix(fs_path, mountp);
512 	const char *name = strip_path(fs_path);
513 
514 	if (strlen(name) + 1 > sizeof(entry->name)) {
515 		return -ENOBUFS;
516 	}
517 
518 	struct fuse_entry_out lookup_ret;
519 	int ret = virtiofs_lookup(mountp->storage_dev, FUSE_ROOT_INODE, path, &lookup_ret, NULL);
520 
521 	if (ret != 0) {
522 		return ret;
523 	}
524 
525 	strcpy((char *)&entry->name, name);
526 
527 	if ((lookup_ret.attr.mode & MODE_FTYPE_MASK) == MODE_FTYPE_DIR) {
528 		entry->type = FS_DIR_ENTRY_DIR;
529 		entry->size = 0;
530 	} else {
531 		entry->type = FS_DIR_ENTRY_FILE;
532 		entry->size = lookup_ret.attr.size;
533 	}
534 
535 	virtiofs_forget(mountp->storage_dev, lookup_ret.nodeid, 1);
536 
537 	return 0;
538 }
539 
virtiofs_zfs_unlink(struct fs_mount_t * mountp,const char * name)540 static int virtiofs_zfs_unlink(struct fs_mount_t *mountp, const char *name)
541 {
542 	const char *path = virtiofs_strip_prefix(name, mountp);
543 	struct fs_dirent d;
544 	int ret = virtiofs_zfs_stat(mountp, name, &d);
545 
546 	if (ret != 0) {
547 		return ret;
548 	}
549 
550 	if (d.type == FS_DIR_ENTRY_FILE) {
551 #ifdef CONFIG_VIRTIOFS_VIRTIOFSD_UNLINK_QUIRK
552 		struct fuse_entry_out lookup_ret;
553 		/*
554 		 * Even if unlink doesn't take nodeid as a param it still fails with -EIO if the
555 		 * file wasn't looked up using some virtiofsd versions. It happens at least with
556 		 * the one from Debian's package (Debian 1:7.2+dfsg-7+deb12u7). Virtiofsd 1.12.0
557 		 * built from sources doesn't need it
558 		 */
559 		ret = virtiofs_lookup(
560 			mountp->storage_dev, FUSE_ROOT_INODE, path, &lookup_ret, NULL
561 		);
562 
563 		if (ret != 0) {
564 			return ret;
565 		}
566 #endif
567 
568 		ret = virtiofs_unlink(mountp->storage_dev, path, FUSE_FILE);
569 
570 #ifdef CONFIG_VIRTIOFS_VIRTIOFSD_UNLINK_QUIRK
571 		virtiofs_forget(mountp->storage_dev, lookup_ret.nodeid, 1);
572 #endif
573 	} else {
574 		ret = virtiofs_unlink(mountp->storage_dev, path, FUSE_DIR);
575 	}
576 
577 	return ret;
578 }
579 
virtiofs_zfs_rename(struct fs_mount_t * mountp,const char * from,const char * to)580 static int virtiofs_zfs_rename(struct fs_mount_t *mountp, const char *from, const char *to)
581 {
582 	const char *old_path = virtiofs_strip_prefix(from, mountp);
583 	const char *new_path = virtiofs_strip_prefix(to, mountp);
584 	uint64_t old_dir = FUSE_ROOT_INODE;
585 	uint64_t new_dir = FUSE_ROOT_INODE;
586 	struct fuse_entry_out old_lookup_ret;
587 	struct fuse_entry_out new_lookup_ret;
588 	int ret;
589 
590 	ret = virtiofs_lookup(
591 		mountp->storage_dev, FUSE_ROOT_INODE, old_path, &old_lookup_ret, &old_dir
592 	);
593 
594 	if (ret != 0) {
595 		if (old_dir != 0 && old_dir != FUSE_ROOT_INODE) {
596 			virtiofs_forget(mountp->storage_dev, old_dir, 1);
597 		}
598 		return ret;
599 	}
600 
601 	ret = virtiofs_lookup(
602 		mountp->storage_dev, FUSE_ROOT_INODE, new_path, &new_lookup_ret, &new_dir
603 	);
604 
605 	/* there is no immediate parent of object's new path */
606 	if (ret != 0 && new_dir == 0) {
607 		virtiofs_forget(mountp->storage_dev, old_lookup_ret.nodeid, 1);
608 		return ret;
609 	}
610 
611 	ret = virtiofs_rename(
612 		mountp->storage_dev,
613 		old_dir, strip_path(old_path),
614 		new_dir, strip_path(new_path)
615 	);
616 
617 	virtiofs_forget(mountp->storage_dev, old_lookup_ret.nodeid, 1);
618 	virtiofs_forget(mountp->storage_dev, new_lookup_ret.nodeid, 1);
619 	virtiofs_forget(mountp->storage_dev, old_dir, 1);
620 	virtiofs_forget(mountp->storage_dev, new_dir, 1);
621 
622 	return ret;
623 }
624 
virtiofs_zfs_statvfs(struct fs_mount_t * mountp,const char * fs_path,struct fs_statvfs * stat)625 static int virtiofs_zfs_statvfs(
626 	struct fs_mount_t *mountp, const char *fs_path, struct fs_statvfs *stat)
627 {
628 	struct fuse_kstatfs statfs_out;
629 	int ret = virtiofs_statfs(mountp->storage_dev, &statfs_out);
630 
631 	if (ret != 0) {
632 		return ret;
633 	}
634 
635 	stat->f_bsize = statfs_out.bsize;
636 	stat->f_frsize = statfs_out.frsize;
637 	stat->f_blocks = statfs_out.blocks;
638 	stat->f_bfree = statfs_out.bfree;
639 
640 	return 0;
641 }
642 
643 static const struct fs_file_system_t virtiofs_ops = {
644 	.open = virtiofs_zfs_open,
645 	.close = virtiofs_zfs_close,
646 	.read = virtiofs_zfs_read,
647 	.write = virtio_zfs_write,
648 	.lseek = virtio_zfs_lseek,
649 	.tell = virtiofs_zfs_tell,
650 	.truncate = virtiofs_zfs_truncate,
651 	.sync = virtiofs_zfs_sync,
652 	.mkdir = virtiofs_zfs_mkdir,
653 	.opendir = virtiofs_zfs_opendir,
654 	.readdir = virtiofs_zfs_readdir,
655 	.closedir = virtiofs_zfs_closedir,
656 	.mount = virtiofs_zfs_mount,
657 	.unmount = virtiofs_zfs_unmount,
658 	.unlink = virtiofs_zfs_unlink,
659 	.rename = virtiofs_zfs_rename,
660 	.stat = virtiofs_zfs_stat,
661 	.statvfs = virtiofs_zfs_statvfs
662 };
663 
virtiofs_register(void)664 static int virtiofs_register(void)
665 {
666 	return fs_register(FS_VIRTIOFS, &virtiofs_ops);
667 }
668 
669 SYS_INIT(virtiofs_register, POST_KERNEL, 99);
670