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