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