xref: /qemu/hw/9pfs/9p-local.c (revision 3dbcf27334b6c41e74a476b55d76f60df1c4007b)
1 /*
2  * 9p Posix callback
3  *
4  * Copyright IBM, Corp. 2010
5  *
6  * Authors:
7  *  Anthony Liguori   <aliguori@us.ibm.com>
8  *
9  * This work is licensed under the terms of the GNU GPL, version 2.  See
10  * the COPYING file in the top-level directory.
11  *
12  */
13 
14 #include "qemu/osdep.h"
15 #include "9p.h"
16 #include "9p-local.h"
17 #include "9p-xattr.h"
18 #include "9p-util.h"
19 #include "fsdev/qemu-fsdev.h"   /* local_ops */
20 #include <arpa/inet.h>
21 #include <pwd.h>
22 #include <grp.h>
23 #include <sys/socket.h>
24 #include <sys/un.h>
25 #include "qemu/xattr.h"
26 #include "qemu/cutils.h"
27 #include "qemu/error-report.h"
28 #include <libgen.h>
29 #include <linux/fs.h>
30 #ifdef CONFIG_LINUX_MAGIC_H
31 #include <linux/magic.h>
32 #endif
33 #include <sys/ioctl.h>
34 
35 #ifndef XFS_SUPER_MAGIC
36 #define XFS_SUPER_MAGIC  0x58465342
37 #endif
38 #ifndef EXT2_SUPER_MAGIC
39 #define EXT2_SUPER_MAGIC 0xEF53
40 #endif
41 #ifndef REISERFS_SUPER_MAGIC
42 #define REISERFS_SUPER_MAGIC 0x52654973
43 #endif
44 #ifndef BTRFS_SUPER_MAGIC
45 #define BTRFS_SUPER_MAGIC 0x9123683E
46 #endif
47 
48 typedef struct {
49     int mountfd;
50 } LocalData;
51 
52 int local_open_nofollow(FsContext *fs_ctx, const char *path, int flags,
53                         mode_t mode)
54 {
55     LocalData *data = fs_ctx->private;
56     int fd = data->mountfd;
57 
58     while (*path && fd != -1) {
59         const char *c;
60         int next_fd;
61         char *head;
62 
63         /* Only relative paths without consecutive slashes */
64         assert(*path != '/');
65 
66         head = g_strdup(path);
67         c = strchrnul(path, '/');
68         if (*c) {
69             /* Intermediate path element */
70             head[c - path] = 0;
71             path = c + 1;
72             next_fd = openat_dir(fd, head);
73         } else {
74             /* Rightmost path element */
75             next_fd = openat_file(fd, head, flags, mode);
76             path = c;
77         }
78         g_free(head);
79         if (fd != data->mountfd) {
80             close_preserve_errno(fd);
81         }
82         fd = next_fd;
83     }
84 
85     assert(fd != data->mountfd);
86     return fd;
87 }
88 
89 int local_opendir_nofollow(FsContext *fs_ctx, const char *path)
90 {
91     return local_open_nofollow(fs_ctx, path, O_DIRECTORY | O_RDONLY, 0);
92 }
93 
94 static void renameat_preserve_errno(int odirfd, const char *opath, int ndirfd,
95                                     const char *npath)
96 {
97     int serrno = errno;
98     renameat(odirfd, opath, ndirfd, npath);
99     errno = serrno;
100 }
101 
102 static void unlinkat_preserve_errno(int dirfd, const char *path, int flags)
103 {
104     int serrno = errno;
105     unlinkat(dirfd, path, flags);
106     errno = serrno;
107 }
108 
109 #define VIRTFS_META_DIR ".virtfs_metadata"
110 
111 static FILE *local_fopenat(int dirfd, const char *name, const char *mode)
112 {
113     int fd, o_mode = 0;
114     FILE *fp;
115     int flags;
116     /*
117      * only supports two modes
118      */
119     if (mode[0] == 'r') {
120         flags = O_RDONLY;
121     } else if (mode[0] == 'w') {
122         flags = O_WRONLY | O_TRUNC | O_CREAT;
123         o_mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
124     } else {
125         return NULL;
126     }
127     fd = openat_file(dirfd, name, flags, o_mode);
128     if (fd == -1) {
129         return NULL;
130     }
131     fp = fdopen(fd, mode);
132     if (!fp) {
133         close(fd);
134     }
135     return fp;
136 }
137 
138 #define ATTR_MAX 100
139 static void local_mapped_file_attr(int dirfd, const char *name,
140                                    struct stat *stbuf)
141 {
142     FILE *fp;
143     char buf[ATTR_MAX];
144     int map_dirfd;
145 
146     map_dirfd = openat_dir(dirfd, VIRTFS_META_DIR);
147     if (map_dirfd == -1) {
148         return;
149     }
150 
151     fp = local_fopenat(map_dirfd, name, "r");
152     close_preserve_errno(map_dirfd);
153     if (!fp) {
154         return;
155     }
156     memset(buf, 0, ATTR_MAX);
157     while (fgets(buf, ATTR_MAX, fp)) {
158         if (!strncmp(buf, "virtfs.uid", 10)) {
159             stbuf->st_uid = atoi(buf+11);
160         } else if (!strncmp(buf, "virtfs.gid", 10)) {
161             stbuf->st_gid = atoi(buf+11);
162         } else if (!strncmp(buf, "virtfs.mode", 11)) {
163             stbuf->st_mode = atoi(buf+12);
164         } else if (!strncmp(buf, "virtfs.rdev", 11)) {
165             stbuf->st_rdev = atoi(buf+12);
166         }
167         memset(buf, 0, ATTR_MAX);
168     }
169     fclose(fp);
170 }
171 
172 static int local_lstat(FsContext *fs_ctx, V9fsPath *fs_path, struct stat *stbuf)
173 {
174     int err = -1;
175     char *dirpath = g_path_get_dirname(fs_path->data);
176     char *name = g_path_get_basename(fs_path->data);
177     int dirfd;
178 
179     dirfd = local_opendir_nofollow(fs_ctx, dirpath);
180     if (dirfd == -1) {
181         goto out;
182     }
183 
184     err = fstatat(dirfd, name, stbuf, AT_SYMLINK_NOFOLLOW);
185     if (err) {
186         goto err_out;
187     }
188     if (fs_ctx->export_flags & V9FS_SM_MAPPED) {
189         /* Actual credentials are part of extended attrs */
190         uid_t tmp_uid;
191         gid_t tmp_gid;
192         mode_t tmp_mode;
193         dev_t tmp_dev;
194 
195         if (fgetxattrat_nofollow(dirfd, name, "user.virtfs.uid", &tmp_uid,
196                                  sizeof(uid_t)) > 0) {
197             stbuf->st_uid = le32_to_cpu(tmp_uid);
198         }
199         if (fgetxattrat_nofollow(dirfd, name, "user.virtfs.gid", &tmp_gid,
200                                  sizeof(gid_t)) > 0) {
201             stbuf->st_gid = le32_to_cpu(tmp_gid);
202         }
203         if (fgetxattrat_nofollow(dirfd, name, "user.virtfs.mode", &tmp_mode,
204                                  sizeof(mode_t)) > 0) {
205             stbuf->st_mode = le32_to_cpu(tmp_mode);
206         }
207         if (fgetxattrat_nofollow(dirfd, name, "user.virtfs.rdev", &tmp_dev,
208                                  sizeof(dev_t)) > 0) {
209             stbuf->st_rdev = le64_to_cpu(tmp_dev);
210         }
211     } else if (fs_ctx->export_flags & V9FS_SM_MAPPED_FILE) {
212         local_mapped_file_attr(dirfd, name, stbuf);
213     }
214 
215 err_out:
216     close_preserve_errno(dirfd);
217 out:
218     g_free(name);
219     g_free(dirpath);
220     return err;
221 }
222 
223 static int local_set_mapped_file_attrat(int dirfd, const char *name,
224                                         FsCred *credp)
225 {
226     FILE *fp;
227     int ret;
228     char buf[ATTR_MAX];
229     int uid = -1, gid = -1, mode = -1, rdev = -1;
230     int map_dirfd;
231 
232     ret = mkdirat(dirfd, VIRTFS_META_DIR, 0700);
233     if (ret < 0 && errno != EEXIST) {
234         return -1;
235     }
236 
237     map_dirfd = openat_dir(dirfd, VIRTFS_META_DIR);
238     if (map_dirfd == -1) {
239         return -1;
240     }
241 
242     fp = local_fopenat(map_dirfd, name, "r");
243     if (!fp) {
244         if (errno == ENOENT) {
245             goto update_map_file;
246         } else {
247             close_preserve_errno(map_dirfd);
248             return -1;
249         }
250     }
251     memset(buf, 0, ATTR_MAX);
252     while (fgets(buf, ATTR_MAX, fp)) {
253         if (!strncmp(buf, "virtfs.uid", 10)) {
254             uid = atoi(buf + 11);
255         } else if (!strncmp(buf, "virtfs.gid", 10)) {
256             gid = atoi(buf + 11);
257         } else if (!strncmp(buf, "virtfs.mode", 11)) {
258             mode = atoi(buf + 12);
259         } else if (!strncmp(buf, "virtfs.rdev", 11)) {
260             rdev = atoi(buf + 12);
261         }
262         memset(buf, 0, ATTR_MAX);
263     }
264     fclose(fp);
265 
266 update_map_file:
267     fp = local_fopenat(map_dirfd, name, "w");
268     close_preserve_errno(map_dirfd);
269     if (!fp) {
270         return -1;
271     }
272 
273     if (credp->fc_uid != -1) {
274         uid = credp->fc_uid;
275     }
276     if (credp->fc_gid != -1) {
277         gid = credp->fc_gid;
278     }
279     if (credp->fc_mode != -1) {
280         mode = credp->fc_mode;
281     }
282     if (credp->fc_rdev != -1) {
283         rdev = credp->fc_rdev;
284     }
285 
286     if (uid != -1) {
287         fprintf(fp, "virtfs.uid=%d\n", uid);
288     }
289     if (gid != -1) {
290         fprintf(fp, "virtfs.gid=%d\n", gid);
291     }
292     if (mode != -1) {
293         fprintf(fp, "virtfs.mode=%d\n", mode);
294     }
295     if (rdev != -1) {
296         fprintf(fp, "virtfs.rdev=%d\n", rdev);
297     }
298     fclose(fp);
299 
300     return 0;
301 }
302 
303 static int fchmodat_nofollow(int dirfd, const char *name, mode_t mode)
304 {
305     int fd, ret;
306 
307     /* FIXME: this should be handled with fchmodat(AT_SYMLINK_NOFOLLOW).
308      * Unfortunately, the linux kernel doesn't implement it yet. As an
309      * alternative, let's open the file and use fchmod() instead. This
310      * may fail depending on the permissions of the file, but it is the
311      * best we can do to avoid TOCTTOU. We first try to open read-only
312      * in case name points to a directory. If that fails, we try write-only
313      * in case name doesn't point to a directory.
314      */
315     fd = openat_file(dirfd, name, O_RDONLY, 0);
316     if (fd == -1) {
317         /* In case the file is writable-only and isn't a directory. */
318         if (errno == EACCES) {
319             fd = openat_file(dirfd, name, O_WRONLY, 0);
320         }
321         if (fd == -1 && errno == EISDIR) {
322             errno = EACCES;
323         }
324     }
325     if (fd == -1) {
326         return -1;
327     }
328     ret = fchmod(fd, mode);
329     close_preserve_errno(fd);
330     return ret;
331 }
332 
333 static int local_set_xattrat(int dirfd, const char *path, FsCred *credp)
334 {
335     int err;
336 
337     if (credp->fc_uid != -1) {
338         uint32_t tmp_uid = cpu_to_le32(credp->fc_uid);
339         err = fsetxattrat_nofollow(dirfd, path, "user.virtfs.uid", &tmp_uid,
340                                    sizeof(uid_t), 0);
341         if (err) {
342             return err;
343         }
344     }
345     if (credp->fc_gid != -1) {
346         uint32_t tmp_gid = cpu_to_le32(credp->fc_gid);
347         err = fsetxattrat_nofollow(dirfd, path, "user.virtfs.gid", &tmp_gid,
348                                    sizeof(gid_t), 0);
349         if (err) {
350             return err;
351         }
352     }
353     if (credp->fc_mode != -1) {
354         uint32_t tmp_mode = cpu_to_le32(credp->fc_mode);
355         err = fsetxattrat_nofollow(dirfd, path, "user.virtfs.mode", &tmp_mode,
356                                    sizeof(mode_t), 0);
357         if (err) {
358             return err;
359         }
360     }
361     if (credp->fc_rdev != -1) {
362         uint64_t tmp_rdev = cpu_to_le64(credp->fc_rdev);
363         err = fsetxattrat_nofollow(dirfd, path, "user.virtfs.rdev", &tmp_rdev,
364                                    sizeof(dev_t), 0);
365         if (err) {
366             return err;
367         }
368     }
369     return 0;
370 }
371 
372 static int local_set_cred_passthrough(FsContext *fs_ctx, int dirfd,
373                                       const char *name, FsCred *credp)
374 {
375     if (fchownat(dirfd, name, credp->fc_uid, credp->fc_gid,
376                  AT_SYMLINK_NOFOLLOW) < 0) {
377         /*
378          * If we fail to change ownership and if we are
379          * using security model none. Ignore the error
380          */
381         if ((fs_ctx->export_flags & V9FS_SEC_MASK) != V9FS_SM_NONE) {
382             return -1;
383         }
384     }
385 
386     return fchmodat_nofollow(dirfd, name, credp->fc_mode & 07777);
387 }
388 
389 static ssize_t local_readlink(FsContext *fs_ctx, V9fsPath *fs_path,
390                               char *buf, size_t bufsz)
391 {
392     ssize_t tsize = -1;
393 
394     if ((fs_ctx->export_flags & V9FS_SM_MAPPED) ||
395         (fs_ctx->export_flags & V9FS_SM_MAPPED_FILE)) {
396         int fd;
397 
398         fd = local_open_nofollow(fs_ctx, fs_path->data, O_RDONLY, 0);
399         if (fd == -1) {
400             return -1;
401         }
402         do {
403             tsize = read(fd, (void *)buf, bufsz);
404         } while (tsize == -1 && errno == EINTR);
405         close_preserve_errno(fd);
406     } else if ((fs_ctx->export_flags & V9FS_SM_PASSTHROUGH) ||
407                (fs_ctx->export_flags & V9FS_SM_NONE)) {
408         char *dirpath = g_path_get_dirname(fs_path->data);
409         char *name = g_path_get_basename(fs_path->data);
410         int dirfd;
411 
412         dirfd = local_opendir_nofollow(fs_ctx, dirpath);
413         if (dirfd == -1) {
414             goto out;
415         }
416 
417         tsize = readlinkat(dirfd, name, buf, bufsz);
418         close_preserve_errno(dirfd);
419     out:
420         g_free(name);
421         g_free(dirpath);
422     }
423     return tsize;
424 }
425 
426 static int local_close(FsContext *ctx, V9fsFidOpenState *fs)
427 {
428     return close(fs->fd);
429 }
430 
431 static int local_closedir(FsContext *ctx, V9fsFidOpenState *fs)
432 {
433     return closedir(fs->dir.stream);
434 }
435 
436 static int local_open(FsContext *ctx, V9fsPath *fs_path,
437                       int flags, V9fsFidOpenState *fs)
438 {
439     int fd;
440 
441     fd = local_open_nofollow(ctx, fs_path->data, flags, 0);
442     if (fd == -1) {
443         return -1;
444     }
445     fs->fd = fd;
446     return fs->fd;
447 }
448 
449 static int local_opendir(FsContext *ctx,
450                          V9fsPath *fs_path, V9fsFidOpenState *fs)
451 {
452     int dirfd;
453     DIR *stream;
454 
455     dirfd = local_opendir_nofollow(ctx, fs_path->data);
456     if (dirfd == -1) {
457         return -1;
458     }
459 
460     stream = fdopendir(dirfd);
461     if (!stream) {
462         close(dirfd);
463         return -1;
464     }
465     fs->dir.stream = stream;
466     return 0;
467 }
468 
469 static void local_rewinddir(FsContext *ctx, V9fsFidOpenState *fs)
470 {
471     rewinddir(fs->dir.stream);
472 }
473 
474 static off_t local_telldir(FsContext *ctx, V9fsFidOpenState *fs)
475 {
476     return telldir(fs->dir.stream);
477 }
478 
479 static bool local_is_mapped_file_metadata(FsContext *fs_ctx, const char *name)
480 {
481     return !strcmp(name, VIRTFS_META_DIR);
482 }
483 
484 static struct dirent *local_readdir(FsContext *ctx, V9fsFidOpenState *fs)
485 {
486     struct dirent *entry;
487 
488 again:
489     entry = readdir(fs->dir.stream);
490     if (!entry) {
491         return NULL;
492     }
493 
494     if (ctx->export_flags & V9FS_SM_MAPPED) {
495         entry->d_type = DT_UNKNOWN;
496     } else if (ctx->export_flags & V9FS_SM_MAPPED_FILE) {
497         if (local_is_mapped_file_metadata(ctx, entry->d_name)) {
498             /* skip the meta data directory */
499             goto again;
500         }
501         entry->d_type = DT_UNKNOWN;
502     }
503 
504     return entry;
505 }
506 
507 static void local_seekdir(FsContext *ctx, V9fsFidOpenState *fs, off_t off)
508 {
509     seekdir(fs->dir.stream, off);
510 }
511 
512 static ssize_t local_preadv(FsContext *ctx, V9fsFidOpenState *fs,
513                             const struct iovec *iov,
514                             int iovcnt, off_t offset)
515 {
516 #ifdef CONFIG_PREADV
517     return preadv(fs->fd, iov, iovcnt, offset);
518 #else
519     int err = lseek(fs->fd, offset, SEEK_SET);
520     if (err == -1) {
521         return err;
522     } else {
523         return readv(fs->fd, iov, iovcnt);
524     }
525 #endif
526 }
527 
528 static ssize_t local_pwritev(FsContext *ctx, V9fsFidOpenState *fs,
529                              const struct iovec *iov,
530                              int iovcnt, off_t offset)
531 {
532     ssize_t ret;
533 #ifdef CONFIG_PREADV
534     ret = pwritev(fs->fd, iov, iovcnt, offset);
535 #else
536     int err = lseek(fs->fd, offset, SEEK_SET);
537     if (err == -1) {
538         return err;
539     } else {
540         ret = writev(fs->fd, iov, iovcnt);
541     }
542 #endif
543 #ifdef CONFIG_SYNC_FILE_RANGE
544     if (ret > 0 && ctx->export_flags & V9FS_IMMEDIATE_WRITEOUT) {
545         /*
546          * Initiate a writeback. This is not a data integrity sync.
547          * We want to ensure that we don't leave dirty pages in the cache
548          * after write when writeout=immediate is sepcified.
549          */
550         sync_file_range(fs->fd, offset, ret,
551                         SYNC_FILE_RANGE_WAIT_BEFORE | SYNC_FILE_RANGE_WRITE);
552     }
553 #endif
554     return ret;
555 }
556 
557 static int local_chmod(FsContext *fs_ctx, V9fsPath *fs_path, FsCred *credp)
558 {
559     char *dirpath = g_path_get_dirname(fs_path->data);
560     char *name = g_path_get_basename(fs_path->data);
561     int ret = -1;
562     int dirfd;
563 
564     dirfd = local_opendir_nofollow(fs_ctx, dirpath);
565     if (dirfd == -1) {
566         goto out;
567     }
568 
569     if (fs_ctx->export_flags & V9FS_SM_MAPPED) {
570         ret = local_set_xattrat(dirfd, name, credp);
571     } else if (fs_ctx->export_flags & V9FS_SM_MAPPED_FILE) {
572         ret = local_set_mapped_file_attrat(dirfd, name, credp);
573     } else if (fs_ctx->export_flags & V9FS_SM_PASSTHROUGH ||
574                fs_ctx->export_flags & V9FS_SM_NONE) {
575         ret = fchmodat_nofollow(dirfd, name, credp->fc_mode);
576     }
577     close_preserve_errno(dirfd);
578 
579 out:
580     g_free(dirpath);
581     g_free(name);
582     return ret;
583 }
584 
585 static int local_mknod(FsContext *fs_ctx, V9fsPath *dir_path,
586                        const char *name, FsCred *credp)
587 {
588     int err = -1;
589     int dirfd;
590 
591     if (fs_ctx->export_flags & V9FS_SM_MAPPED_FILE &&
592         local_is_mapped_file_metadata(fs_ctx, name)) {
593         errno = EINVAL;
594         return -1;
595     }
596 
597     dirfd = local_opendir_nofollow(fs_ctx, dir_path->data);
598     if (dirfd == -1) {
599         return -1;
600     }
601 
602     if (fs_ctx->export_flags & V9FS_SM_MAPPED ||
603         fs_ctx->export_flags & V9FS_SM_MAPPED_FILE) {
604         err = mknodat(dirfd, name, SM_LOCAL_MODE_BITS | S_IFREG, 0);
605         if (err == -1) {
606             goto out;
607         }
608 
609         if (fs_ctx->export_flags & V9FS_SM_MAPPED) {
610             err = local_set_xattrat(dirfd, name, credp);
611         } else {
612             err = local_set_mapped_file_attrat(dirfd, name, credp);
613         }
614         if (err == -1) {
615             goto err_end;
616         }
617     } else if (fs_ctx->export_flags & V9FS_SM_PASSTHROUGH ||
618                fs_ctx->export_flags & V9FS_SM_NONE) {
619         err = mknodat(dirfd, name, credp->fc_mode, credp->fc_rdev);
620         if (err == -1) {
621             goto out;
622         }
623         err = local_set_cred_passthrough(fs_ctx, dirfd, name, credp);
624         if (err == -1) {
625             goto err_end;
626         }
627     }
628     goto out;
629 
630 err_end:
631     unlinkat_preserve_errno(dirfd, name, 0);
632 out:
633     close_preserve_errno(dirfd);
634     return err;
635 }
636 
637 static int local_mkdir(FsContext *fs_ctx, V9fsPath *dir_path,
638                        const char *name, FsCred *credp)
639 {
640     int err = -1;
641     int dirfd;
642 
643     if (fs_ctx->export_flags & V9FS_SM_MAPPED_FILE &&
644         local_is_mapped_file_metadata(fs_ctx, name)) {
645         errno = EINVAL;
646         return -1;
647     }
648 
649     dirfd = local_opendir_nofollow(fs_ctx, dir_path->data);
650     if (dirfd == -1) {
651         return -1;
652     }
653 
654     if (fs_ctx->export_flags & V9FS_SM_MAPPED ||
655         fs_ctx->export_flags & V9FS_SM_MAPPED_FILE) {
656         err = mkdirat(dirfd, name, SM_LOCAL_DIR_MODE_BITS);
657         if (err == -1) {
658             goto out;
659         }
660         credp->fc_mode = credp->fc_mode | S_IFDIR;
661 
662         if (fs_ctx->export_flags & V9FS_SM_MAPPED) {
663             err = local_set_xattrat(dirfd, name, credp);
664         } else {
665             err = local_set_mapped_file_attrat(dirfd, name, credp);
666         }
667         if (err == -1) {
668             goto err_end;
669         }
670     } else if (fs_ctx->export_flags & V9FS_SM_PASSTHROUGH ||
671                fs_ctx->export_flags & V9FS_SM_NONE) {
672         err = mkdirat(dirfd, name, credp->fc_mode);
673         if (err == -1) {
674             goto out;
675         }
676         err = local_set_cred_passthrough(fs_ctx, dirfd, name, credp);
677         if (err == -1) {
678             goto err_end;
679         }
680     }
681     goto out;
682 
683 err_end:
684     unlinkat_preserve_errno(dirfd, name, AT_REMOVEDIR);
685 out:
686     close_preserve_errno(dirfd);
687     return err;
688 }
689 
690 static int local_fstat(FsContext *fs_ctx, int fid_type,
691                        V9fsFidOpenState *fs, struct stat *stbuf)
692 {
693     int err, fd;
694 
695     if (fid_type == P9_FID_DIR) {
696         fd = dirfd(fs->dir.stream);
697     } else {
698         fd = fs->fd;
699     }
700 
701     err = fstat(fd, stbuf);
702     if (err) {
703         return err;
704     }
705     if (fs_ctx->export_flags & V9FS_SM_MAPPED) {
706         /* Actual credentials are part of extended attrs */
707         uid_t tmp_uid;
708         gid_t tmp_gid;
709         mode_t tmp_mode;
710         dev_t tmp_dev;
711 
712         if (fgetxattr(fd, "user.virtfs.uid", &tmp_uid, sizeof(uid_t)) > 0) {
713             stbuf->st_uid = le32_to_cpu(tmp_uid);
714         }
715         if (fgetxattr(fd, "user.virtfs.gid", &tmp_gid, sizeof(gid_t)) > 0) {
716             stbuf->st_gid = le32_to_cpu(tmp_gid);
717         }
718         if (fgetxattr(fd, "user.virtfs.mode", &tmp_mode, sizeof(mode_t)) > 0) {
719             stbuf->st_mode = le32_to_cpu(tmp_mode);
720         }
721         if (fgetxattr(fd, "user.virtfs.rdev", &tmp_dev, sizeof(dev_t)) > 0) {
722             stbuf->st_rdev = le64_to_cpu(tmp_dev);
723         }
724     } else if (fs_ctx->export_flags & V9FS_SM_MAPPED_FILE) {
725         errno = EOPNOTSUPP;
726         return -1;
727     }
728     return err;
729 }
730 
731 static int local_open2(FsContext *fs_ctx, V9fsPath *dir_path, const char *name,
732                        int flags, FsCred *credp, V9fsFidOpenState *fs)
733 {
734     int fd = -1;
735     int err = -1;
736     int dirfd;
737 
738     if (fs_ctx->export_flags & V9FS_SM_MAPPED_FILE &&
739         local_is_mapped_file_metadata(fs_ctx, name)) {
740         errno = EINVAL;
741         return -1;
742     }
743 
744     /*
745      * Mark all the open to not follow symlinks
746      */
747     flags |= O_NOFOLLOW;
748 
749     dirfd = local_opendir_nofollow(fs_ctx, dir_path->data);
750     if (dirfd == -1) {
751         return -1;
752     }
753 
754     /* Determine the security model */
755     if (fs_ctx->export_flags & V9FS_SM_MAPPED ||
756         fs_ctx->export_flags & V9FS_SM_MAPPED_FILE) {
757         fd = openat_file(dirfd, name, flags, SM_LOCAL_MODE_BITS);
758         if (fd == -1) {
759             goto out;
760         }
761         credp->fc_mode = credp->fc_mode|S_IFREG;
762         if (fs_ctx->export_flags & V9FS_SM_MAPPED) {
763             /* Set cleint credentials in xattr */
764             err = local_set_xattrat(dirfd, name, credp);
765         } else {
766             err = local_set_mapped_file_attrat(dirfd, name, credp);
767         }
768         if (err == -1) {
769             goto err_end;
770         }
771     } else if ((fs_ctx->export_flags & V9FS_SM_PASSTHROUGH) ||
772                (fs_ctx->export_flags & V9FS_SM_NONE)) {
773         fd = openat_file(dirfd, name, flags, credp->fc_mode);
774         if (fd == -1) {
775             goto out;
776         }
777         err = local_set_cred_passthrough(fs_ctx, dirfd, name, credp);
778         if (err == -1) {
779             goto err_end;
780         }
781     }
782     err = fd;
783     fs->fd = fd;
784     goto out;
785 
786 err_end:
787     unlinkat_preserve_errno(dirfd, name,
788                             flags & O_DIRECTORY ? AT_REMOVEDIR : 0);
789     close_preserve_errno(fd);
790 out:
791     close_preserve_errno(dirfd);
792     return err;
793 }
794 
795 
796 static int local_symlink(FsContext *fs_ctx, const char *oldpath,
797                          V9fsPath *dir_path, const char *name, FsCred *credp)
798 {
799     int err = -1;
800     int dirfd;
801 
802     if (fs_ctx->export_flags & V9FS_SM_MAPPED_FILE &&
803         local_is_mapped_file_metadata(fs_ctx, name)) {
804         errno = EINVAL;
805         return -1;
806     }
807 
808     dirfd = local_opendir_nofollow(fs_ctx, dir_path->data);
809     if (dirfd == -1) {
810         return -1;
811     }
812 
813     /* Determine the security model */
814     if (fs_ctx->export_flags & V9FS_SM_MAPPED ||
815         fs_ctx->export_flags & V9FS_SM_MAPPED_FILE) {
816         int fd;
817         ssize_t oldpath_size, write_size;
818 
819         fd = openat_file(dirfd, name, O_CREAT | O_EXCL | O_RDWR,
820                          SM_LOCAL_MODE_BITS);
821         if (fd == -1) {
822             goto out;
823         }
824         /* Write the oldpath (target) to the file. */
825         oldpath_size = strlen(oldpath);
826         do {
827             write_size = write(fd, (void *)oldpath, oldpath_size);
828         } while (write_size == -1 && errno == EINTR);
829         close_preserve_errno(fd);
830 
831         if (write_size != oldpath_size) {
832             goto err_end;
833         }
834         /* Set cleint credentials in symlink's xattr */
835         credp->fc_mode = credp->fc_mode | S_IFLNK;
836 
837         if (fs_ctx->export_flags & V9FS_SM_MAPPED) {
838             err = local_set_xattrat(dirfd, name, credp);
839         } else {
840             err = local_set_mapped_file_attrat(dirfd, name, credp);
841         }
842         if (err == -1) {
843             goto err_end;
844         }
845     } else if (fs_ctx->export_flags & V9FS_SM_PASSTHROUGH ||
846                fs_ctx->export_flags & V9FS_SM_NONE) {
847         err = symlinkat(oldpath, dirfd, name);
848         if (err) {
849             goto out;
850         }
851         err = fchownat(dirfd, name, credp->fc_uid, credp->fc_gid,
852                        AT_SYMLINK_NOFOLLOW);
853         if (err == -1) {
854             /*
855              * If we fail to change ownership and if we are
856              * using security model none. Ignore the error
857              */
858             if ((fs_ctx->export_flags & V9FS_SEC_MASK) != V9FS_SM_NONE) {
859                 goto err_end;
860             } else {
861                 err = 0;
862             }
863         }
864     }
865     goto out;
866 
867 err_end:
868     unlinkat_preserve_errno(dirfd, name, 0);
869 out:
870     close_preserve_errno(dirfd);
871     return err;
872 }
873 
874 static int local_link(FsContext *ctx, V9fsPath *oldpath,
875                       V9fsPath *dirpath, const char *name)
876 {
877     char *odirpath = g_path_get_dirname(oldpath->data);
878     char *oname = g_path_get_basename(oldpath->data);
879     int ret = -1;
880     int odirfd, ndirfd;
881 
882     if (ctx->export_flags & V9FS_SM_MAPPED_FILE &&
883         local_is_mapped_file_metadata(ctx, name)) {
884         errno = EINVAL;
885         return -1;
886     }
887 
888     odirfd = local_opendir_nofollow(ctx, odirpath);
889     if (odirfd == -1) {
890         goto out;
891     }
892 
893     ndirfd = local_opendir_nofollow(ctx, dirpath->data);
894     if (ndirfd == -1) {
895         close_preserve_errno(odirfd);
896         goto out;
897     }
898 
899     ret = linkat(odirfd, oname, ndirfd, name, 0);
900     if (ret < 0) {
901         goto out_close;
902     }
903 
904     /* now link the virtfs_metadata files */
905     if (ctx->export_flags & V9FS_SM_MAPPED_FILE) {
906         int omap_dirfd, nmap_dirfd;
907 
908         ret = mkdirat(ndirfd, VIRTFS_META_DIR, 0700);
909         if (ret < 0 && errno != EEXIST) {
910             goto err_undo_link;
911         }
912 
913         omap_dirfd = openat_dir(odirfd, VIRTFS_META_DIR);
914         if (omap_dirfd == -1) {
915             goto err;
916         }
917 
918         nmap_dirfd = openat_dir(ndirfd, VIRTFS_META_DIR);
919         if (nmap_dirfd == -1) {
920             close_preserve_errno(omap_dirfd);
921             goto err;
922         }
923 
924         ret = linkat(omap_dirfd, oname, nmap_dirfd, name, 0);
925         close_preserve_errno(nmap_dirfd);
926         close_preserve_errno(omap_dirfd);
927         if (ret < 0 && errno != ENOENT) {
928             goto err_undo_link;
929         }
930 
931         ret = 0;
932     }
933     goto out_close;
934 
935 err:
936     ret = -1;
937 err_undo_link:
938     unlinkat_preserve_errno(ndirfd, name, 0);
939 out_close:
940     close_preserve_errno(ndirfd);
941     close_preserve_errno(odirfd);
942 out:
943     g_free(oname);
944     g_free(odirpath);
945     return ret;
946 }
947 
948 static int local_truncate(FsContext *ctx, V9fsPath *fs_path, off_t size)
949 {
950     int fd, ret;
951 
952     fd = local_open_nofollow(ctx, fs_path->data, O_WRONLY, 0);
953     if (fd == -1) {
954         return -1;
955     }
956     ret = ftruncate(fd, size);
957     close_preserve_errno(fd);
958     return ret;
959 }
960 
961 static int local_chown(FsContext *fs_ctx, V9fsPath *fs_path, FsCred *credp)
962 {
963     char *dirpath = g_path_get_dirname(fs_path->data);
964     char *name = g_path_get_basename(fs_path->data);
965     int ret = -1;
966     int dirfd;
967 
968     dirfd = local_opendir_nofollow(fs_ctx, dirpath);
969     if (dirfd == -1) {
970         goto out;
971     }
972 
973     if ((credp->fc_uid == -1 && credp->fc_gid == -1) ||
974         (fs_ctx->export_flags & V9FS_SM_PASSTHROUGH) ||
975         (fs_ctx->export_flags & V9FS_SM_NONE)) {
976         ret = fchownat(dirfd, name, credp->fc_uid, credp->fc_gid,
977                        AT_SYMLINK_NOFOLLOW);
978     } else if (fs_ctx->export_flags & V9FS_SM_MAPPED) {
979         ret = local_set_xattrat(dirfd, name, credp);
980     } else if (fs_ctx->export_flags & V9FS_SM_MAPPED_FILE) {
981         ret = local_set_mapped_file_attrat(dirfd, name, credp);
982     }
983 
984     close_preserve_errno(dirfd);
985 out:
986     g_free(name);
987     g_free(dirpath);
988     return ret;
989 }
990 
991 static int local_utimensat(FsContext *s, V9fsPath *fs_path,
992                            const struct timespec *buf)
993 {
994     char *dirpath = g_path_get_dirname(fs_path->data);
995     char *name = g_path_get_basename(fs_path->data);
996     int dirfd, ret = -1;
997 
998     dirfd = local_opendir_nofollow(s, dirpath);
999     if (dirfd == -1) {
1000         goto out;
1001     }
1002 
1003     ret = utimensat(dirfd, name, buf, AT_SYMLINK_NOFOLLOW);
1004     close_preserve_errno(dirfd);
1005 out:
1006     g_free(dirpath);
1007     g_free(name);
1008     return ret;
1009 }
1010 
1011 static int local_unlinkat_common(FsContext *ctx, int dirfd, const char *name,
1012                                  int flags)
1013 {
1014     int ret = -1;
1015 
1016     if (ctx->export_flags & V9FS_SM_MAPPED_FILE) {
1017         int map_dirfd;
1018 
1019         /* We need to remove the metadata as well:
1020          * - the metadata directory if we're removing a directory
1021          * - the metadata file in the parent's metadata directory
1022          *
1023          * If any of these are missing (ie, ENOENT) then we're probably
1024          * trying to remove something that wasn't created in mapped-file
1025          * mode. We just ignore the error.
1026          */
1027         if (flags == AT_REMOVEDIR) {
1028             int fd;
1029 
1030             fd = openat_dir(dirfd, name);
1031             if (fd == -1) {
1032                 goto err_out;
1033             }
1034             ret = unlinkat(fd, VIRTFS_META_DIR, AT_REMOVEDIR);
1035             close_preserve_errno(fd);
1036             if (ret < 0 && errno != ENOENT) {
1037                 goto err_out;
1038             }
1039         }
1040         map_dirfd = openat_dir(dirfd, VIRTFS_META_DIR);
1041         if (map_dirfd != -1) {
1042             ret = unlinkat(map_dirfd, name, 0);
1043             close_preserve_errno(map_dirfd);
1044             if (ret < 0 && errno != ENOENT) {
1045                 goto err_out;
1046             }
1047         } else if (errno != ENOENT) {
1048             goto err_out;
1049         }
1050     }
1051 
1052     ret = unlinkat(dirfd, name, flags);
1053 err_out:
1054     return ret;
1055 }
1056 
1057 static int local_remove(FsContext *ctx, const char *path)
1058 {
1059     struct stat stbuf;
1060     char *dirpath = g_path_get_dirname(path);
1061     char *name = g_path_get_basename(path);
1062     int flags = 0;
1063     int dirfd;
1064     int err = -1;
1065 
1066     dirfd = local_opendir_nofollow(ctx, dirpath);
1067     if (dirfd == -1) {
1068         goto out;
1069     }
1070 
1071     if (fstatat(dirfd, path, &stbuf, AT_SYMLINK_NOFOLLOW) < 0) {
1072         goto err_out;
1073     }
1074 
1075     if (S_ISDIR(stbuf.st_mode)) {
1076         flags |= AT_REMOVEDIR;
1077     }
1078 
1079     err = local_unlinkat_common(ctx, dirfd, name, flags);
1080 err_out:
1081     close_preserve_errno(dirfd);
1082 out:
1083     g_free(name);
1084     g_free(dirpath);
1085     return err;
1086 }
1087 
1088 static int local_fsync(FsContext *ctx, int fid_type,
1089                        V9fsFidOpenState *fs, int datasync)
1090 {
1091     int fd;
1092 
1093     if (fid_type == P9_FID_DIR) {
1094         fd = dirfd(fs->dir.stream);
1095     } else {
1096         fd = fs->fd;
1097     }
1098 
1099     if (datasync) {
1100         return qemu_fdatasync(fd);
1101     } else {
1102         return fsync(fd);
1103     }
1104 }
1105 
1106 static int local_statfs(FsContext *s, V9fsPath *fs_path, struct statfs *stbuf)
1107 {
1108     int fd, ret;
1109 
1110     fd = local_open_nofollow(s, fs_path->data, O_RDONLY, 0);
1111     if (fd == -1) {
1112         return -1;
1113     }
1114     ret = fstatfs(fd, stbuf);
1115     close_preserve_errno(fd);
1116     return ret;
1117 }
1118 
1119 static ssize_t local_lgetxattr(FsContext *ctx, V9fsPath *fs_path,
1120                                const char *name, void *value, size_t size)
1121 {
1122     char *path = fs_path->data;
1123 
1124     return v9fs_get_xattr(ctx, path, name, value, size);
1125 }
1126 
1127 static ssize_t local_llistxattr(FsContext *ctx, V9fsPath *fs_path,
1128                                 void *value, size_t size)
1129 {
1130     char *path = fs_path->data;
1131 
1132     return v9fs_list_xattr(ctx, path, value, size);
1133 }
1134 
1135 static int local_lsetxattr(FsContext *ctx, V9fsPath *fs_path, const char *name,
1136                            void *value, size_t size, int flags)
1137 {
1138     char *path = fs_path->data;
1139 
1140     return v9fs_set_xattr(ctx, path, name, value, size, flags);
1141 }
1142 
1143 static int local_lremovexattr(FsContext *ctx, V9fsPath *fs_path,
1144                               const char *name)
1145 {
1146     char *path = fs_path->data;
1147 
1148     return v9fs_remove_xattr(ctx, path, name);
1149 }
1150 
1151 static int local_name_to_path(FsContext *ctx, V9fsPath *dir_path,
1152                               const char *name, V9fsPath *target)
1153 {
1154     if (ctx->export_flags & V9FS_SM_MAPPED_FILE &&
1155         local_is_mapped_file_metadata(ctx, name)) {
1156         errno = EINVAL;
1157         return -1;
1158     }
1159 
1160     if (dir_path) {
1161         if (!strcmp(name, ".")) {
1162             /* "." relative to "foo/bar" is "foo/bar" */
1163             v9fs_path_copy(target, dir_path);
1164         } else if (!strcmp(name, "..")) {
1165             if (!strcmp(dir_path->data, ".")) {
1166                 /* ".." relative to the root is "." */
1167                 v9fs_path_sprintf(target, ".");
1168             } else {
1169                 char *tmp = g_path_get_dirname(dir_path->data);
1170                 /* Symbolic links are resolved by the client. We can assume
1171                  * that ".." relative to "foo/bar" is equivalent to "foo"
1172                  */
1173                 v9fs_path_sprintf(target, "%s", tmp);
1174                 g_free(tmp);
1175             }
1176         } else {
1177             assert(!strchr(name, '/'));
1178             v9fs_path_sprintf(target, "%s/%s", dir_path->data, name);
1179         }
1180     } else if (!strcmp(name, "/") || !strcmp(name, ".") ||
1181                !strcmp(name, "..")) {
1182             /* This is the root fid */
1183         v9fs_path_sprintf(target, ".");
1184     } else {
1185         assert(!strchr(name, '/'));
1186         v9fs_path_sprintf(target, "./%s", name);
1187     }
1188     return 0;
1189 }
1190 
1191 static int local_renameat(FsContext *ctx, V9fsPath *olddir,
1192                           const char *old_name, V9fsPath *newdir,
1193                           const char *new_name)
1194 {
1195     int ret;
1196     int odirfd, ndirfd;
1197 
1198     if (ctx->export_flags & V9FS_SM_MAPPED_FILE &&
1199         (local_is_mapped_file_metadata(ctx, old_name) ||
1200          local_is_mapped_file_metadata(ctx, new_name))) {
1201         errno = EINVAL;
1202         return -1;
1203     }
1204 
1205     odirfd = local_opendir_nofollow(ctx, olddir->data);
1206     if (odirfd == -1) {
1207         return -1;
1208     }
1209 
1210     ndirfd = local_opendir_nofollow(ctx, newdir->data);
1211     if (ndirfd == -1) {
1212         close_preserve_errno(odirfd);
1213         return -1;
1214     }
1215 
1216     ret = renameat(odirfd, old_name, ndirfd, new_name);
1217     if (ret < 0) {
1218         goto out;
1219     }
1220 
1221     if (ctx->export_flags & V9FS_SM_MAPPED_FILE) {
1222         int omap_dirfd, nmap_dirfd;
1223 
1224         ret = mkdirat(ndirfd, VIRTFS_META_DIR, 0700);
1225         if (ret < 0 && errno != EEXIST) {
1226             goto err_undo_rename;
1227         }
1228 
1229         omap_dirfd = openat_dir(odirfd, VIRTFS_META_DIR);
1230         if (omap_dirfd == -1) {
1231             goto err;
1232         }
1233 
1234         nmap_dirfd = openat_dir(ndirfd, VIRTFS_META_DIR);
1235         if (nmap_dirfd == -1) {
1236             close_preserve_errno(omap_dirfd);
1237             goto err;
1238         }
1239 
1240         /* rename the .virtfs_metadata files */
1241         ret = renameat(omap_dirfd, old_name, nmap_dirfd, new_name);
1242         close_preserve_errno(nmap_dirfd);
1243         close_preserve_errno(omap_dirfd);
1244         if (ret < 0 && errno != ENOENT) {
1245             goto err_undo_rename;
1246         }
1247 
1248         ret = 0;
1249     }
1250     goto out;
1251 
1252 err:
1253     ret = -1;
1254 err_undo_rename:
1255     renameat_preserve_errno(ndirfd, new_name, odirfd, old_name);
1256 out:
1257     close_preserve_errno(ndirfd);
1258     close_preserve_errno(odirfd);
1259     return ret;
1260 }
1261 
1262 static void v9fs_path_init_dirname(V9fsPath *path, const char *str)
1263 {
1264     path->data = g_path_get_dirname(str);
1265     path->size = strlen(path->data) + 1;
1266 }
1267 
1268 static int local_rename(FsContext *ctx, const char *oldpath,
1269                         const char *newpath)
1270 {
1271     int err;
1272     char *oname = g_path_get_basename(oldpath);
1273     char *nname = g_path_get_basename(newpath);
1274     V9fsPath olddir, newdir;
1275 
1276     v9fs_path_init_dirname(&olddir, oldpath);
1277     v9fs_path_init_dirname(&newdir, newpath);
1278 
1279     err = local_renameat(ctx, &olddir, oname, &newdir, nname);
1280 
1281     v9fs_path_free(&newdir);
1282     v9fs_path_free(&olddir);
1283     g_free(nname);
1284     g_free(oname);
1285 
1286     return err;
1287 }
1288 
1289 static int local_unlinkat(FsContext *ctx, V9fsPath *dir,
1290                           const char *name, int flags)
1291 {
1292     int ret;
1293     int dirfd;
1294 
1295     if (ctx->export_flags & V9FS_SM_MAPPED_FILE &&
1296         local_is_mapped_file_metadata(ctx, name)) {
1297         errno = EINVAL;
1298         return -1;
1299     }
1300 
1301     dirfd = local_opendir_nofollow(ctx, dir->data);
1302     if (dirfd == -1) {
1303         return -1;
1304     }
1305 
1306     ret = local_unlinkat_common(ctx, dirfd, name, flags);
1307     close_preserve_errno(dirfd);
1308     return ret;
1309 }
1310 
1311 static int local_ioc_getversion(FsContext *ctx, V9fsPath *path,
1312                                 mode_t st_mode, uint64_t *st_gen)
1313 {
1314 #ifdef FS_IOC_GETVERSION
1315     int err;
1316     V9fsFidOpenState fid_open;
1317 
1318     /*
1319      * Do not try to open special files like device nodes, fifos etc
1320      * We can get fd for regular files and directories only
1321      */
1322     if (!S_ISREG(st_mode) && !S_ISDIR(st_mode)) {
1323         errno = ENOTTY;
1324         return -1;
1325     }
1326     err = local_open(ctx, path, O_RDONLY, &fid_open);
1327     if (err < 0) {
1328         return err;
1329     }
1330     err = ioctl(fid_open.fd, FS_IOC_GETVERSION, st_gen);
1331     local_close(ctx, &fid_open);
1332     return err;
1333 #else
1334     errno = ENOTTY;
1335     return -1;
1336 #endif
1337 }
1338 
1339 static int local_init(FsContext *ctx)
1340 {
1341     struct statfs stbuf;
1342     LocalData *data = g_malloc(sizeof(*data));
1343 
1344     data->mountfd = open(ctx->fs_root, O_DIRECTORY | O_RDONLY);
1345     if (data->mountfd == -1) {
1346         goto err;
1347     }
1348 
1349 #ifdef FS_IOC_GETVERSION
1350     /*
1351      * use ioc_getversion only if the ioctl is definied
1352      */
1353     if (fstatfs(data->mountfd, &stbuf) < 0) {
1354         close_preserve_errno(data->mountfd);
1355         goto err;
1356     }
1357     switch (stbuf.f_type) {
1358     case EXT2_SUPER_MAGIC:
1359     case BTRFS_SUPER_MAGIC:
1360     case REISERFS_SUPER_MAGIC:
1361     case XFS_SUPER_MAGIC:
1362         ctx->exops.get_st_gen = local_ioc_getversion;
1363         break;
1364     }
1365 #endif
1366 
1367     if (ctx->export_flags & V9FS_SM_PASSTHROUGH) {
1368         ctx->xops = passthrough_xattr_ops;
1369     } else if (ctx->export_flags & V9FS_SM_MAPPED) {
1370         ctx->xops = mapped_xattr_ops;
1371     } else if (ctx->export_flags & V9FS_SM_NONE) {
1372         ctx->xops = none_xattr_ops;
1373     } else if (ctx->export_flags & V9FS_SM_MAPPED_FILE) {
1374         /*
1375          * xattr operation for mapped-file and passthrough
1376          * remain same.
1377          */
1378         ctx->xops = passthrough_xattr_ops;
1379     }
1380     ctx->export_flags |= V9FS_PATHNAME_FSCONTEXT;
1381 
1382     ctx->private = data;
1383     return 0;
1384 
1385 err:
1386     g_free(data);
1387     return -1;
1388 }
1389 
1390 static void local_cleanup(FsContext *ctx)
1391 {
1392     LocalData *data = ctx->private;
1393 
1394     close(data->mountfd);
1395     g_free(data);
1396 }
1397 
1398 static int local_parse_opts(QemuOpts *opts, struct FsDriverEntry *fse)
1399 {
1400     const char *sec_model = qemu_opt_get(opts, "security_model");
1401     const char *path = qemu_opt_get(opts, "path");
1402     Error *err = NULL;
1403 
1404     if (!sec_model) {
1405         error_report("Security model not specified, local fs needs security model");
1406         error_printf("valid options are:"
1407                      "\tsecurity_model=[passthrough|mapped-xattr|mapped-file|none]\n");
1408         return -1;
1409     }
1410 
1411     if (!strcmp(sec_model, "passthrough")) {
1412         fse->export_flags |= V9FS_SM_PASSTHROUGH;
1413     } else if (!strcmp(sec_model, "mapped") ||
1414                !strcmp(sec_model, "mapped-xattr")) {
1415         fse->export_flags |= V9FS_SM_MAPPED;
1416     } else if (!strcmp(sec_model, "none")) {
1417         fse->export_flags |= V9FS_SM_NONE;
1418     } else if (!strcmp(sec_model, "mapped-file")) {
1419         fse->export_flags |= V9FS_SM_MAPPED_FILE;
1420     } else {
1421         error_report("Invalid security model %s specified", sec_model);
1422         error_printf("valid options are:"
1423                      "\t[passthrough|mapped-xattr|mapped-file|none]\n");
1424         return -1;
1425     }
1426 
1427     if (!path) {
1428         error_report("fsdev: No path specified");
1429         return -1;
1430     }
1431 
1432     fsdev_throttle_parse_opts(opts, &fse->fst, &err);
1433     if (err) {
1434         error_reportf_err(err, "Throttle configuration is not valid: ");
1435         return -1;
1436     }
1437 
1438     fse->path = g_strdup(path);
1439 
1440     return 0;
1441 }
1442 
1443 FileOperations local_ops = {
1444     .parse_opts = local_parse_opts,
1445     .init  = local_init,
1446     .cleanup = local_cleanup,
1447     .lstat = local_lstat,
1448     .readlink = local_readlink,
1449     .close = local_close,
1450     .closedir = local_closedir,
1451     .open = local_open,
1452     .opendir = local_opendir,
1453     .rewinddir = local_rewinddir,
1454     .telldir = local_telldir,
1455     .readdir = local_readdir,
1456     .seekdir = local_seekdir,
1457     .preadv = local_preadv,
1458     .pwritev = local_pwritev,
1459     .chmod = local_chmod,
1460     .mknod = local_mknod,
1461     .mkdir = local_mkdir,
1462     .fstat = local_fstat,
1463     .open2 = local_open2,
1464     .symlink = local_symlink,
1465     .link = local_link,
1466     .truncate = local_truncate,
1467     .rename = local_rename,
1468     .chown = local_chown,
1469     .utimensat = local_utimensat,
1470     .remove = local_remove,
1471     .fsync = local_fsync,
1472     .statfs = local_statfs,
1473     .lgetxattr = local_lgetxattr,
1474     .llistxattr = local_llistxattr,
1475     .lsetxattr = local_lsetxattr,
1476     .lremovexattr = local_lremovexattr,
1477     .name_to_path = local_name_to_path,
1478     .renameat  = local_renameat,
1479     .unlinkat = local_unlinkat,
1480 };
1481