4 * Copyright IBM, Corp. 2010
7 * Anthony Liguori <aliguori@us.ibm.com>
9 * This work is licensed under the terms of the GNU GPL, version 2. See
10 * the COPYING file in the top-level directory.
14 #include "qemu/osdep.h"
19 #include "fsdev/qemu-fsdev.h" /* local_ops */
20 #include <arpa/inet.h>
23 #include <sys/socket.h>
25 #include "qemu/xattr.h"
26 #include "qemu/cutils.h"
27 #include "qemu/error-report.h"
30 #ifdef CONFIG_LINUX_MAGIC_H
31 #include <linux/magic.h>
33 #include <sys/ioctl.h>
35 #ifndef XFS_SUPER_MAGIC
36 #define XFS_SUPER_MAGIC 0x58465342
38 #ifndef EXT2_SUPER_MAGIC
39 #define EXT2_SUPER_MAGIC 0xEF53
41 #ifndef REISERFS_SUPER_MAGIC
42 #define REISERFS_SUPER_MAGIC 0x52654973
44 #ifndef BTRFS_SUPER_MAGIC
45 #define BTRFS_SUPER_MAGIC 0x9123683E
52 int local_open_nofollow(FsContext
*fs_ctx
, const char *path
, int flags
,
55 LocalData
*data
= fs_ctx
->private;
56 int fd
= data
->mountfd
;
58 while (*path
&& fd
!= -1) {
63 /* Only relative paths without consecutive slashes */
66 head
= g_strdup(path
);
67 c
= strchrnul(path
, '/');
69 /* Intermediate path element */
72 next_fd
= openat_dir(fd
, head
);
74 /* Rightmost path element */
75 next_fd
= openat_file(fd
, head
, flags
, mode
);
79 if (fd
!= data
->mountfd
) {
80 close_preserve_errno(fd
);
85 assert(fd
!= data
->mountfd
);
89 int local_opendir_nofollow(FsContext
*fs_ctx
, const char *path
)
91 return local_open_nofollow(fs_ctx
, path
, O_DIRECTORY
| O_RDONLY
, 0);
94 static void renameat_preserve_errno(int odirfd
, const char *opath
, int ndirfd
,
98 renameat(odirfd
, opath
, ndirfd
, npath
);
102 static void unlinkat_preserve_errno(int dirfd
, const char *path
, int flags
)
105 unlinkat(dirfd
, path
, flags
);
109 #define VIRTFS_META_DIR ".virtfs_metadata"
110 #define VIRTFS_META_ROOT_FILE VIRTFS_META_DIR "_root"
112 static FILE *local_fopenat(int dirfd
, const char *name
, const char *mode
)
118 * only supports two modes
120 if (mode
[0] == 'r') {
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
;
128 fd
= openat_file(dirfd
, name
, flags
, o_mode
);
132 fp
= fdopen(fd
, mode
);
140 static void local_mapped_file_attr(int dirfd
, const char *name
,
147 if (strcmp(name
, ".")) {
148 map_dirfd
= openat_dir(dirfd
, VIRTFS_META_DIR
);
149 if (map_dirfd
== -1) {
153 fp
= local_fopenat(map_dirfd
, name
, "r");
154 close_preserve_errno(map_dirfd
);
156 fp
= local_fopenat(dirfd
, VIRTFS_META_ROOT_FILE
, "r");
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);
172 memset(buf
, 0, ATTR_MAX
);
177 static int local_lstat(FsContext
*fs_ctx
, V9fsPath
*fs_path
, struct stat
*stbuf
)
180 char *dirpath
= g_path_get_dirname(fs_path
->data
);
181 char *name
= g_path_get_basename(fs_path
->data
);
184 dirfd
= local_opendir_nofollow(fs_ctx
, dirpath
);
189 err
= fstatat(dirfd
, name
, stbuf
, AT_SYMLINK_NOFOLLOW
);
193 if (fs_ctx
->export_flags
& V9FS_SM_MAPPED
) {
194 /* Actual credentials are part of extended attrs */
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
);
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
);
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
);
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
);
216 } else if (fs_ctx
->export_flags
& V9FS_SM_MAPPED_FILE
) {
217 local_mapped_file_attr(dirfd
, name
, stbuf
);
221 close_preserve_errno(dirfd
);
228 static int local_set_mapped_file_attrat(int dirfd
, const char *name
,
234 int uid
= -1, gid
= -1, mode
= -1, rdev
= -1;
235 int map_dirfd
= -1, map_fd
;
236 bool is_root
= !strcmp(name
, ".");
239 fp
= local_fopenat(dirfd
, VIRTFS_META_ROOT_FILE
, "r");
241 if (errno
== ENOENT
) {
242 goto update_map_file
;
248 ret
= mkdirat(dirfd
, VIRTFS_META_DIR
, 0700);
249 if (ret
< 0 && errno
!= EEXIST
) {
253 map_dirfd
= openat_dir(dirfd
, VIRTFS_META_DIR
);
254 if (map_dirfd
== -1) {
258 fp
= local_fopenat(map_dirfd
, name
, "r");
260 if (errno
== ENOENT
) {
261 goto update_map_file
;
263 close_preserve_errno(map_dirfd
);
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);
279 memset(buf
, 0, ATTR_MAX
);
285 fp
= local_fopenat(dirfd
, VIRTFS_META_ROOT_FILE
, "w");
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.
291 if (map_dirfd
!= -1) {
292 close_preserve_errno(map_dirfd
);
300 assert(map_fd
!= -1);
301 ret
= fchmod(map_fd
, 0600);
304 if (credp
->fc_uid
!= -1) {
307 if (credp
->fc_gid
!= -1) {
310 if (credp
->fc_mode
!= -1) {
311 mode
= credp
->fc_mode
;
313 if (credp
->fc_rdev
!= -1) {
314 rdev
= credp
->fc_rdev
;
318 fprintf(fp
, "virtfs.uid=%d\n", uid
);
321 fprintf(fp
, "virtfs.gid=%d\n", gid
);
324 fprintf(fp
, "virtfs.mode=%d\n", mode
);
327 fprintf(fp
, "virtfs.rdev=%d\n", rdev
);
334 static int fchmodat_nofollow(int dirfd
, const char *name
, mode_t mode
)
338 /* FIXME: this should be handled with fchmodat(AT_SYMLINK_NOFOLLOW).
339 * Unfortunately, the linux kernel doesn't implement it yet. As an
340 * alternative, let's open the file and use fchmod() instead. This
341 * may fail depending on the permissions of the file, but it is the
342 * best we can do to avoid TOCTTOU. We first try to open read-only
343 * in case name points to a directory. If that fails, we try write-only
344 * in case name doesn't point to a directory.
346 fd
= openat_file(dirfd
, name
, O_RDONLY
, 0);
348 /* In case the file is writable-only and isn't a directory. */
349 if (errno
== EACCES
) {
350 fd
= openat_file(dirfd
, name
, O_WRONLY
, 0);
352 if (fd
== -1 && errno
== EISDIR
) {
359 ret
= fchmod(fd
, mode
);
360 close_preserve_errno(fd
);
364 static int local_set_xattrat(int dirfd
, const char *path
, FsCred
*credp
)
368 if (credp
->fc_uid
!= -1) {
369 uint32_t tmp_uid
= cpu_to_le32(credp
->fc_uid
);
370 err
= fsetxattrat_nofollow(dirfd
, path
, "user.virtfs.uid", &tmp_uid
,
376 if (credp
->fc_gid
!= -1) {
377 uint32_t tmp_gid
= cpu_to_le32(credp
->fc_gid
);
378 err
= fsetxattrat_nofollow(dirfd
, path
, "user.virtfs.gid", &tmp_gid
,
384 if (credp
->fc_mode
!= -1) {
385 uint32_t tmp_mode
= cpu_to_le32(credp
->fc_mode
);
386 err
= fsetxattrat_nofollow(dirfd
, path
, "user.virtfs.mode", &tmp_mode
,
392 if (credp
->fc_rdev
!= -1) {
393 uint64_t tmp_rdev
= cpu_to_le64(credp
->fc_rdev
);
394 err
= fsetxattrat_nofollow(dirfd
, path
, "user.virtfs.rdev", &tmp_rdev
,
403 static int local_set_cred_passthrough(FsContext
*fs_ctx
, int dirfd
,
404 const char *name
, FsCred
*credp
)
406 if (fchownat(dirfd
, name
, credp
->fc_uid
, credp
->fc_gid
,
407 AT_SYMLINK_NOFOLLOW
) < 0) {
409 * If we fail to change ownership and if we are
410 * using security model none. Ignore the error
412 if ((fs_ctx
->export_flags
& V9FS_SEC_MASK
) != V9FS_SM_NONE
) {
417 return fchmodat_nofollow(dirfd
, name
, credp
->fc_mode
& 07777);
420 static ssize_t
local_readlink(FsContext
*fs_ctx
, V9fsPath
*fs_path
,
421 char *buf
, size_t bufsz
)
425 if ((fs_ctx
->export_flags
& V9FS_SM_MAPPED
) ||
426 (fs_ctx
->export_flags
& V9FS_SM_MAPPED_FILE
)) {
429 fd
= local_open_nofollow(fs_ctx
, fs_path
->data
, O_RDONLY
, 0);
434 tsize
= read(fd
, (void *)buf
, bufsz
);
435 } while (tsize
== -1 && errno
== EINTR
);
436 close_preserve_errno(fd
);
437 } else if ((fs_ctx
->export_flags
& V9FS_SM_PASSTHROUGH
) ||
438 (fs_ctx
->export_flags
& V9FS_SM_NONE
)) {
439 char *dirpath
= g_path_get_dirname(fs_path
->data
);
440 char *name
= g_path_get_basename(fs_path
->data
);
443 dirfd
= local_opendir_nofollow(fs_ctx
, dirpath
);
448 tsize
= readlinkat(dirfd
, name
, buf
, bufsz
);
449 close_preserve_errno(dirfd
);
457 static int local_close(FsContext
*ctx
, V9fsFidOpenState
*fs
)
459 return close(fs
->fd
);
462 static int local_closedir(FsContext
*ctx
, V9fsFidOpenState
*fs
)
464 return closedir(fs
->dir
.stream
);
467 static int local_open(FsContext
*ctx
, V9fsPath
*fs_path
,
468 int flags
, V9fsFidOpenState
*fs
)
472 fd
= local_open_nofollow(ctx
, fs_path
->data
, flags
, 0);
480 static int local_opendir(FsContext
*ctx
,
481 V9fsPath
*fs_path
, V9fsFidOpenState
*fs
)
486 dirfd
= local_opendir_nofollow(ctx
, fs_path
->data
);
491 stream
= fdopendir(dirfd
);
496 fs
->dir
.stream
= stream
;
500 static void local_rewinddir(FsContext
*ctx
, V9fsFidOpenState
*fs
)
502 rewinddir(fs
->dir
.stream
);
505 static off_t
local_telldir(FsContext
*ctx
, V9fsFidOpenState
*fs
)
507 return telldir(fs
->dir
.stream
);
510 static bool local_is_mapped_file_metadata(FsContext
*fs_ctx
, const char *name
)
513 !strcmp(name
, VIRTFS_META_DIR
) || !strcmp(name
, VIRTFS_META_ROOT_FILE
);
516 static struct dirent
*local_readdir(FsContext
*ctx
, V9fsFidOpenState
*fs
)
518 struct dirent
*entry
;
521 entry
= readdir(fs
->dir
.stream
);
526 if (ctx
->export_flags
& V9FS_SM_MAPPED
) {
527 entry
->d_type
= DT_UNKNOWN
;
528 } else if (ctx
->export_flags
& V9FS_SM_MAPPED_FILE
) {
529 if (local_is_mapped_file_metadata(ctx
, entry
->d_name
)) {
530 /* skip the meta data */
533 entry
->d_type
= DT_UNKNOWN
;
539 static void local_seekdir(FsContext
*ctx
, V9fsFidOpenState
*fs
, off_t off
)
541 seekdir(fs
->dir
.stream
, off
);
544 static ssize_t
local_preadv(FsContext
*ctx
, V9fsFidOpenState
*fs
,
545 const struct iovec
*iov
,
546 int iovcnt
, off_t offset
)
549 return preadv(fs
->fd
, iov
, iovcnt
, offset
);
551 int err
= lseek(fs
->fd
, offset
, SEEK_SET
);
555 return readv(fs
->fd
, iov
, iovcnt
);
560 static ssize_t
local_pwritev(FsContext
*ctx
, V9fsFidOpenState
*fs
,
561 const struct iovec
*iov
,
562 int iovcnt
, off_t offset
)
566 ret
= pwritev(fs
->fd
, iov
, iovcnt
, offset
);
568 int err
= lseek(fs
->fd
, offset
, SEEK_SET
);
572 ret
= writev(fs
->fd
, iov
, iovcnt
);
575 #ifdef CONFIG_SYNC_FILE_RANGE
576 if (ret
> 0 && ctx
->export_flags
& V9FS_IMMEDIATE_WRITEOUT
) {
578 * Initiate a writeback. This is not a data integrity sync.
579 * We want to ensure that we don't leave dirty pages in the cache
580 * after write when writeout=immediate is sepcified.
582 sync_file_range(fs
->fd
, offset
, ret
,
583 SYNC_FILE_RANGE_WAIT_BEFORE
| SYNC_FILE_RANGE_WRITE
);
589 static int local_chmod(FsContext
*fs_ctx
, V9fsPath
*fs_path
, FsCred
*credp
)
591 char *dirpath
= g_path_get_dirname(fs_path
->data
);
592 char *name
= g_path_get_basename(fs_path
->data
);
596 dirfd
= local_opendir_nofollow(fs_ctx
, dirpath
);
601 if (fs_ctx
->export_flags
& V9FS_SM_MAPPED
) {
602 ret
= local_set_xattrat(dirfd
, name
, credp
);
603 } else if (fs_ctx
->export_flags
& V9FS_SM_MAPPED_FILE
) {
604 ret
= local_set_mapped_file_attrat(dirfd
, name
, credp
);
605 } else if (fs_ctx
->export_flags
& V9FS_SM_PASSTHROUGH
||
606 fs_ctx
->export_flags
& V9FS_SM_NONE
) {
607 ret
= fchmodat_nofollow(dirfd
, name
, credp
->fc_mode
);
609 close_preserve_errno(dirfd
);
617 static int local_mknod(FsContext
*fs_ctx
, V9fsPath
*dir_path
,
618 const char *name
, FsCred
*credp
)
623 if (fs_ctx
->export_flags
& V9FS_SM_MAPPED_FILE
&&
624 local_is_mapped_file_metadata(fs_ctx
, name
)) {
629 dirfd
= local_opendir_nofollow(fs_ctx
, dir_path
->data
);
634 if (fs_ctx
->export_flags
& V9FS_SM_MAPPED
||
635 fs_ctx
->export_flags
& V9FS_SM_MAPPED_FILE
) {
636 err
= mknodat(dirfd
, name
, fs_ctx
->fmode
| S_IFREG
, 0);
641 if (fs_ctx
->export_flags
& V9FS_SM_MAPPED
) {
642 err
= local_set_xattrat(dirfd
, name
, credp
);
644 err
= local_set_mapped_file_attrat(dirfd
, name
, credp
);
649 } else if (fs_ctx
->export_flags
& V9FS_SM_PASSTHROUGH
||
650 fs_ctx
->export_flags
& V9FS_SM_NONE
) {
651 err
= mknodat(dirfd
, name
, credp
->fc_mode
, credp
->fc_rdev
);
655 err
= local_set_cred_passthrough(fs_ctx
, dirfd
, name
, credp
);
663 unlinkat_preserve_errno(dirfd
, name
, 0);
665 close_preserve_errno(dirfd
);
669 static int local_mkdir(FsContext
*fs_ctx
, V9fsPath
*dir_path
,
670 const char *name
, FsCred
*credp
)
675 if (fs_ctx
->export_flags
& V9FS_SM_MAPPED_FILE
&&
676 local_is_mapped_file_metadata(fs_ctx
, name
)) {
681 dirfd
= local_opendir_nofollow(fs_ctx
, dir_path
->data
);
686 if (fs_ctx
->export_flags
& V9FS_SM_MAPPED
||
687 fs_ctx
->export_flags
& V9FS_SM_MAPPED_FILE
) {
688 err
= mkdirat(dirfd
, name
, fs_ctx
->dmode
);
692 credp
->fc_mode
= credp
->fc_mode
| S_IFDIR
;
694 if (fs_ctx
->export_flags
& V9FS_SM_MAPPED
) {
695 err
= local_set_xattrat(dirfd
, name
, credp
);
697 err
= local_set_mapped_file_attrat(dirfd
, name
, credp
);
702 } else if (fs_ctx
->export_flags
& V9FS_SM_PASSTHROUGH
||
703 fs_ctx
->export_flags
& V9FS_SM_NONE
) {
704 err
= mkdirat(dirfd
, name
, credp
->fc_mode
);
708 err
= local_set_cred_passthrough(fs_ctx
, dirfd
, name
, credp
);
716 unlinkat_preserve_errno(dirfd
, name
, AT_REMOVEDIR
);
718 close_preserve_errno(dirfd
);
722 static int local_fstat(FsContext
*fs_ctx
, int fid_type
,
723 V9fsFidOpenState
*fs
, struct stat
*stbuf
)
727 if (fid_type
== P9_FID_DIR
) {
728 fd
= dirfd(fs
->dir
.stream
);
733 err
= fstat(fd
, stbuf
);
737 if (fs_ctx
->export_flags
& V9FS_SM_MAPPED
) {
738 /* Actual credentials are part of extended attrs */
744 if (fgetxattr(fd
, "user.virtfs.uid", &tmp_uid
, sizeof(uid_t
)) > 0) {
745 stbuf
->st_uid
= le32_to_cpu(tmp_uid
);
747 if (fgetxattr(fd
, "user.virtfs.gid", &tmp_gid
, sizeof(gid_t
)) > 0) {
748 stbuf
->st_gid
= le32_to_cpu(tmp_gid
);
750 if (fgetxattr(fd
, "user.virtfs.mode", &tmp_mode
, sizeof(mode_t
)) > 0) {
751 stbuf
->st_mode
= le32_to_cpu(tmp_mode
);
753 if (fgetxattr(fd
, "user.virtfs.rdev", &tmp_dev
, sizeof(dev_t
)) > 0) {
754 stbuf
->st_rdev
= le64_to_cpu(tmp_dev
);
756 } else if (fs_ctx
->export_flags
& V9FS_SM_MAPPED_FILE
) {
763 static int local_open2(FsContext
*fs_ctx
, V9fsPath
*dir_path
, const char *name
,
764 int flags
, FsCred
*credp
, V9fsFidOpenState
*fs
)
770 if (fs_ctx
->export_flags
& V9FS_SM_MAPPED_FILE
&&
771 local_is_mapped_file_metadata(fs_ctx
, name
)) {
777 * Mark all the open to not follow symlinks
781 dirfd
= local_opendir_nofollow(fs_ctx
, dir_path
->data
);
786 /* Determine the security model */
787 if (fs_ctx
->export_flags
& V9FS_SM_MAPPED
||
788 fs_ctx
->export_flags
& V9FS_SM_MAPPED_FILE
) {
789 fd
= openat_file(dirfd
, name
, flags
, fs_ctx
->fmode
);
793 credp
->fc_mode
= credp
->fc_mode
|S_IFREG
;
794 if (fs_ctx
->export_flags
& V9FS_SM_MAPPED
) {
795 /* Set cleint credentials in xattr */
796 err
= local_set_xattrat(dirfd
, name
, credp
);
798 err
= local_set_mapped_file_attrat(dirfd
, name
, credp
);
803 } else if ((fs_ctx
->export_flags
& V9FS_SM_PASSTHROUGH
) ||
804 (fs_ctx
->export_flags
& V9FS_SM_NONE
)) {
805 fd
= openat_file(dirfd
, name
, flags
, credp
->fc_mode
);
809 err
= local_set_cred_passthrough(fs_ctx
, dirfd
, name
, credp
);
819 unlinkat_preserve_errno(dirfd
, name
,
820 flags
& O_DIRECTORY
? AT_REMOVEDIR
: 0);
821 close_preserve_errno(fd
);
823 close_preserve_errno(dirfd
);
828 static int local_symlink(FsContext
*fs_ctx
, const char *oldpath
,
829 V9fsPath
*dir_path
, const char *name
, FsCred
*credp
)
834 if (fs_ctx
->export_flags
& V9FS_SM_MAPPED_FILE
&&
835 local_is_mapped_file_metadata(fs_ctx
, name
)) {
840 dirfd
= local_opendir_nofollow(fs_ctx
, dir_path
->data
);
845 /* Determine the security model */
846 if (fs_ctx
->export_flags
& V9FS_SM_MAPPED
||
847 fs_ctx
->export_flags
& V9FS_SM_MAPPED_FILE
) {
849 ssize_t oldpath_size
, write_size
;
851 fd
= openat_file(dirfd
, name
, O_CREAT
| O_EXCL
| O_RDWR
,
856 /* Write the oldpath (target) to the file. */
857 oldpath_size
= strlen(oldpath
);
859 write_size
= write(fd
, (void *)oldpath
, oldpath_size
);
860 } while (write_size
== -1 && errno
== EINTR
);
861 close_preserve_errno(fd
);
863 if (write_size
!= oldpath_size
) {
866 /* Set cleint credentials in symlink's xattr */
867 credp
->fc_mode
= credp
->fc_mode
| S_IFLNK
;
869 if (fs_ctx
->export_flags
& V9FS_SM_MAPPED
) {
870 err
= local_set_xattrat(dirfd
, name
, credp
);
872 err
= local_set_mapped_file_attrat(dirfd
, name
, credp
);
877 } else if (fs_ctx
->export_flags
& V9FS_SM_PASSTHROUGH
||
878 fs_ctx
->export_flags
& V9FS_SM_NONE
) {
879 err
= symlinkat(oldpath
, dirfd
, name
);
883 err
= fchownat(dirfd
, name
, credp
->fc_uid
, credp
->fc_gid
,
884 AT_SYMLINK_NOFOLLOW
);
887 * If we fail to change ownership and if we are
888 * using security model none. Ignore the error
890 if ((fs_ctx
->export_flags
& V9FS_SEC_MASK
) != V9FS_SM_NONE
) {
900 unlinkat_preserve_errno(dirfd
, name
, 0);
902 close_preserve_errno(dirfd
);
906 static int local_link(FsContext
*ctx
, V9fsPath
*oldpath
,
907 V9fsPath
*dirpath
, const char *name
)
909 char *odirpath
= g_path_get_dirname(oldpath
->data
);
910 char *oname
= g_path_get_basename(oldpath
->data
);
914 if (ctx
->export_flags
& V9FS_SM_MAPPED_FILE
&&
915 local_is_mapped_file_metadata(ctx
, name
)) {
920 odirfd
= local_opendir_nofollow(ctx
, odirpath
);
925 ndirfd
= local_opendir_nofollow(ctx
, dirpath
->data
);
927 close_preserve_errno(odirfd
);
931 ret
= linkat(odirfd
, oname
, ndirfd
, name
, 0);
936 /* now link the virtfs_metadata files */
937 if (ctx
->export_flags
& V9FS_SM_MAPPED_FILE
) {
938 int omap_dirfd
, nmap_dirfd
;
940 ret
= mkdirat(ndirfd
, VIRTFS_META_DIR
, 0700);
941 if (ret
< 0 && errno
!= EEXIST
) {
945 omap_dirfd
= openat_dir(odirfd
, VIRTFS_META_DIR
);
946 if (omap_dirfd
== -1) {
950 nmap_dirfd
= openat_dir(ndirfd
, VIRTFS_META_DIR
);
951 if (nmap_dirfd
== -1) {
952 close_preserve_errno(omap_dirfd
);
956 ret
= linkat(omap_dirfd
, oname
, nmap_dirfd
, name
, 0);
957 close_preserve_errno(nmap_dirfd
);
958 close_preserve_errno(omap_dirfd
);
959 if (ret
< 0 && errno
!= ENOENT
) {
970 unlinkat_preserve_errno(ndirfd
, name
, 0);
972 close_preserve_errno(ndirfd
);
973 close_preserve_errno(odirfd
);
980 static int local_truncate(FsContext
*ctx
, V9fsPath
*fs_path
, off_t size
)
984 fd
= local_open_nofollow(ctx
, fs_path
->data
, O_WRONLY
, 0);
988 ret
= ftruncate(fd
, size
);
989 close_preserve_errno(fd
);
993 static int local_chown(FsContext
*fs_ctx
, V9fsPath
*fs_path
, FsCred
*credp
)
995 char *dirpath
= g_path_get_dirname(fs_path
->data
);
996 char *name
= g_path_get_basename(fs_path
->data
);
1000 dirfd
= local_opendir_nofollow(fs_ctx
, dirpath
);
1005 if ((credp
->fc_uid
== -1 && credp
->fc_gid
== -1) ||
1006 (fs_ctx
->export_flags
& V9FS_SM_PASSTHROUGH
) ||
1007 (fs_ctx
->export_flags
& V9FS_SM_NONE
)) {
1008 ret
= fchownat(dirfd
, name
, credp
->fc_uid
, credp
->fc_gid
,
1009 AT_SYMLINK_NOFOLLOW
);
1010 } else if (fs_ctx
->export_flags
& V9FS_SM_MAPPED
) {
1011 ret
= local_set_xattrat(dirfd
, name
, credp
);
1012 } else if (fs_ctx
->export_flags
& V9FS_SM_MAPPED_FILE
) {
1013 ret
= local_set_mapped_file_attrat(dirfd
, name
, credp
);
1016 close_preserve_errno(dirfd
);
1023 static int local_utimensat(FsContext
*s
, V9fsPath
*fs_path
,
1024 const struct timespec
*buf
)
1026 char *dirpath
= g_path_get_dirname(fs_path
->data
);
1027 char *name
= g_path_get_basename(fs_path
->data
);
1028 int dirfd
, ret
= -1;
1030 dirfd
= local_opendir_nofollow(s
, dirpath
);
1035 ret
= utimensat(dirfd
, name
, buf
, AT_SYMLINK_NOFOLLOW
);
1036 close_preserve_errno(dirfd
);
1043 static int local_unlinkat_common(FsContext
*ctx
, int dirfd
, const char *name
,
1048 if (ctx
->export_flags
& V9FS_SM_MAPPED_FILE
) {
1051 /* We need to remove the metadata as well:
1052 * - the metadata directory if we're removing a directory
1053 * - the metadata file in the parent's metadata directory
1055 * If any of these are missing (ie, ENOENT) then we're probably
1056 * trying to remove something that wasn't created in mapped-file
1057 * mode. We just ignore the error.
1059 if (flags
== AT_REMOVEDIR
) {
1062 fd
= openat_dir(dirfd
, name
);
1066 ret
= unlinkat(fd
, VIRTFS_META_DIR
, AT_REMOVEDIR
);
1067 close_preserve_errno(fd
);
1068 if (ret
< 0 && errno
!= ENOENT
) {
1072 map_dirfd
= openat_dir(dirfd
, VIRTFS_META_DIR
);
1073 if (map_dirfd
!= -1) {
1074 ret
= unlinkat(map_dirfd
, name
, 0);
1075 close_preserve_errno(map_dirfd
);
1076 if (ret
< 0 && errno
!= ENOENT
) {
1079 } else if (errno
!= ENOENT
) {
1084 ret
= unlinkat(dirfd
, name
, flags
);
1089 static int local_remove(FsContext
*ctx
, const char *path
)
1092 char *dirpath
= g_path_get_dirname(path
);
1093 char *name
= g_path_get_basename(path
);
1098 dirfd
= local_opendir_nofollow(ctx
, dirpath
);
1103 if (fstatat(dirfd
, name
, &stbuf
, AT_SYMLINK_NOFOLLOW
) < 0) {
1107 if (S_ISDIR(stbuf
.st_mode
)) {
1108 flags
|= AT_REMOVEDIR
;
1111 err
= local_unlinkat_common(ctx
, dirfd
, name
, flags
);
1113 close_preserve_errno(dirfd
);
1120 static int local_fsync(FsContext
*ctx
, int fid_type
,
1121 V9fsFidOpenState
*fs
, int datasync
)
1125 if (fid_type
== P9_FID_DIR
) {
1126 fd
= dirfd(fs
->dir
.stream
);
1132 return qemu_fdatasync(fd
);
1138 static int local_statfs(FsContext
*s
, V9fsPath
*fs_path
, struct statfs
*stbuf
)
1142 fd
= local_open_nofollow(s
, fs_path
->data
, O_RDONLY
, 0);
1146 ret
= fstatfs(fd
, stbuf
);
1147 close_preserve_errno(fd
);
1151 static ssize_t
local_lgetxattr(FsContext
*ctx
, V9fsPath
*fs_path
,
1152 const char *name
, void *value
, size_t size
)
1154 char *path
= fs_path
->data
;
1156 return v9fs_get_xattr(ctx
, path
, name
, value
, size
);
1159 static ssize_t
local_llistxattr(FsContext
*ctx
, V9fsPath
*fs_path
,
1160 void *value
, size_t size
)
1162 char *path
= fs_path
->data
;
1164 return v9fs_list_xattr(ctx
, path
, value
, size
);
1167 static int local_lsetxattr(FsContext
*ctx
, V9fsPath
*fs_path
, const char *name
,
1168 void *value
, size_t size
, int flags
)
1170 char *path
= fs_path
->data
;
1172 return v9fs_set_xattr(ctx
, path
, name
, value
, size
, flags
);
1175 static int local_lremovexattr(FsContext
*ctx
, V9fsPath
*fs_path
,
1178 char *path
= fs_path
->data
;
1180 return v9fs_remove_xattr(ctx
, path
, name
);
1183 static int local_name_to_path(FsContext
*ctx
, V9fsPath
*dir_path
,
1184 const char *name
, V9fsPath
*target
)
1186 if (ctx
->export_flags
& V9FS_SM_MAPPED_FILE
&&
1187 local_is_mapped_file_metadata(ctx
, name
)) {
1193 if (!strcmp(name
, ".")) {
1194 /* "." relative to "foo/bar" is "foo/bar" */
1195 v9fs_path_copy(target
, dir_path
);
1196 } else if (!strcmp(name
, "..")) {
1197 if (!strcmp(dir_path
->data
, ".")) {
1198 /* ".." relative to the root is "." */
1199 v9fs_path_sprintf(target
, ".");
1201 char *tmp
= g_path_get_dirname(dir_path
->data
);
1202 /* Symbolic links are resolved by the client. We can assume
1203 * that ".." relative to "foo/bar" is equivalent to "foo"
1205 v9fs_path_sprintf(target
, "%s", tmp
);
1209 assert(!strchr(name
, '/'));
1210 v9fs_path_sprintf(target
, "%s/%s", dir_path
->data
, name
);
1212 } else if (!strcmp(name
, "/") || !strcmp(name
, ".") ||
1213 !strcmp(name
, "..")) {
1214 /* This is the root fid */
1215 v9fs_path_sprintf(target
, ".");
1217 assert(!strchr(name
, '/'));
1218 v9fs_path_sprintf(target
, "./%s", name
);
1223 static int local_renameat(FsContext
*ctx
, V9fsPath
*olddir
,
1224 const char *old_name
, V9fsPath
*newdir
,
1225 const char *new_name
)
1230 if (ctx
->export_flags
& V9FS_SM_MAPPED_FILE
&&
1231 (local_is_mapped_file_metadata(ctx
, old_name
) ||
1232 local_is_mapped_file_metadata(ctx
, new_name
))) {
1237 odirfd
= local_opendir_nofollow(ctx
, olddir
->data
);
1242 ndirfd
= local_opendir_nofollow(ctx
, newdir
->data
);
1244 close_preserve_errno(odirfd
);
1248 ret
= renameat(odirfd
, old_name
, ndirfd
, new_name
);
1253 if (ctx
->export_flags
& V9FS_SM_MAPPED_FILE
) {
1254 int omap_dirfd
, nmap_dirfd
;
1256 ret
= mkdirat(ndirfd
, VIRTFS_META_DIR
, 0700);
1257 if (ret
< 0 && errno
!= EEXIST
) {
1258 goto err_undo_rename
;
1261 omap_dirfd
= openat_dir(odirfd
, VIRTFS_META_DIR
);
1262 if (omap_dirfd
== -1) {
1266 nmap_dirfd
= openat_dir(ndirfd
, VIRTFS_META_DIR
);
1267 if (nmap_dirfd
== -1) {
1268 close_preserve_errno(omap_dirfd
);
1272 /* rename the .virtfs_metadata files */
1273 ret
= renameat(omap_dirfd
, old_name
, nmap_dirfd
, new_name
);
1274 close_preserve_errno(nmap_dirfd
);
1275 close_preserve_errno(omap_dirfd
);
1276 if (ret
< 0 && errno
!= ENOENT
) {
1277 goto err_undo_rename
;
1287 renameat_preserve_errno(ndirfd
, new_name
, odirfd
, old_name
);
1289 close_preserve_errno(ndirfd
);
1290 close_preserve_errno(odirfd
);
1294 static void v9fs_path_init_dirname(V9fsPath
*path
, const char *str
)
1296 path
->data
= g_path_get_dirname(str
);
1297 path
->size
= strlen(path
->data
) + 1;
1300 static int local_rename(FsContext
*ctx
, const char *oldpath
,
1301 const char *newpath
)
1304 char *oname
= g_path_get_basename(oldpath
);
1305 char *nname
= g_path_get_basename(newpath
);
1306 V9fsPath olddir
, newdir
;
1308 v9fs_path_init_dirname(&olddir
, oldpath
);
1309 v9fs_path_init_dirname(&newdir
, newpath
);
1311 err
= local_renameat(ctx
, &olddir
, oname
, &newdir
, nname
);
1313 v9fs_path_free(&newdir
);
1314 v9fs_path_free(&olddir
);
1321 static int local_unlinkat(FsContext
*ctx
, V9fsPath
*dir
,
1322 const char *name
, int flags
)
1327 if (ctx
->export_flags
& V9FS_SM_MAPPED_FILE
&&
1328 local_is_mapped_file_metadata(ctx
, name
)) {
1333 dirfd
= local_opendir_nofollow(ctx
, dir
->data
);
1338 ret
= local_unlinkat_common(ctx
, dirfd
, name
, flags
);
1339 close_preserve_errno(dirfd
);
1343 static int local_ioc_getversion(FsContext
*ctx
, V9fsPath
*path
,
1344 mode_t st_mode
, uint64_t *st_gen
)
1346 #ifdef FS_IOC_GETVERSION
1348 V9fsFidOpenState fid_open
;
1351 * Do not try to open special files like device nodes, fifos etc
1352 * We can get fd for regular files and directories only
1354 if (!S_ISREG(st_mode
) && !S_ISDIR(st_mode
)) {
1358 err
= local_open(ctx
, path
, O_RDONLY
, &fid_open
);
1362 err
= ioctl(fid_open
.fd
, FS_IOC_GETVERSION
, st_gen
);
1363 local_close(ctx
, &fid_open
);
1371 static int local_init(FsContext
*ctx
)
1373 struct statfs stbuf
;
1374 LocalData
*data
= g_malloc(sizeof(*data
));
1376 data
->mountfd
= open(ctx
->fs_root
, O_DIRECTORY
| O_RDONLY
);
1377 if (data
->mountfd
== -1) {
1381 #ifdef FS_IOC_GETVERSION
1383 * use ioc_getversion only if the ioctl is definied
1385 if (fstatfs(data
->mountfd
, &stbuf
) < 0) {
1386 close_preserve_errno(data
->mountfd
);
1389 switch (stbuf
.f_type
) {
1390 case EXT2_SUPER_MAGIC
:
1391 case BTRFS_SUPER_MAGIC
:
1392 case REISERFS_SUPER_MAGIC
:
1393 case XFS_SUPER_MAGIC
:
1394 ctx
->exops
.get_st_gen
= local_ioc_getversion
;
1399 if (ctx
->export_flags
& V9FS_SM_PASSTHROUGH
) {
1400 ctx
->xops
= passthrough_xattr_ops
;
1401 } else if (ctx
->export_flags
& V9FS_SM_MAPPED
) {
1402 ctx
->xops
= mapped_xattr_ops
;
1403 } else if (ctx
->export_flags
& V9FS_SM_NONE
) {
1404 ctx
->xops
= none_xattr_ops
;
1405 } else if (ctx
->export_flags
& V9FS_SM_MAPPED_FILE
) {
1407 * xattr operation for mapped-file and passthrough
1410 ctx
->xops
= passthrough_xattr_ops
;
1412 ctx
->export_flags
|= V9FS_PATHNAME_FSCONTEXT
;
1414 ctx
->private = data
;
1422 static void local_cleanup(FsContext
*ctx
)
1424 LocalData
*data
= ctx
->private;
1426 close(data
->mountfd
);
1430 static int local_parse_opts(QemuOpts
*opts
, struct FsDriverEntry
*fse
)
1432 const char *sec_model
= qemu_opt_get(opts
, "security_model");
1433 const char *path
= qemu_opt_get(opts
, "path");
1437 error_report("Security model not specified, local fs needs security model");
1438 error_printf("valid options are:"
1439 "\tsecurity_model=[passthrough|mapped-xattr|mapped-file|none]\n");
1443 if (!strcmp(sec_model
, "passthrough")) {
1444 fse
->export_flags
|= V9FS_SM_PASSTHROUGH
;
1445 } else if (!strcmp(sec_model
, "mapped") ||
1446 !strcmp(sec_model
, "mapped-xattr")) {
1447 fse
->export_flags
|= V9FS_SM_MAPPED
;
1448 } else if (!strcmp(sec_model
, "none")) {
1449 fse
->export_flags
|= V9FS_SM_NONE
;
1450 } else if (!strcmp(sec_model
, "mapped-file")) {
1451 fse
->export_flags
|= V9FS_SM_MAPPED_FILE
;
1453 error_report("Invalid security model %s specified", sec_model
);
1454 error_printf("valid options are:"
1455 "\t[passthrough|mapped-xattr|mapped-file|none]\n");
1460 error_report("fsdev: No path specified");
1464 fsdev_throttle_parse_opts(opts
, &fse
->fst
, &err
);
1466 error_reportf_err(err
, "Throttle configuration is not valid: ");
1470 if (fse
->export_flags
& V9FS_SM_MAPPED
||
1471 fse
->export_flags
& V9FS_SM_MAPPED_FILE
) {
1473 qemu_opt_get_number(opts
, "fmode", SM_LOCAL_MODE_BITS
) & 0777;
1475 qemu_opt_get_number(opts
, "dmode", SM_LOCAL_DIR_MODE_BITS
) & 0777;
1477 if (qemu_opt_find(opts
, "fmode")) {
1478 error_report("fmode is only valid for mapped 9p modes");
1481 if (qemu_opt_find(opts
, "dmode")) {
1482 error_report("dmode is only valid for mapped 9p modes");
1487 fse
->path
= g_strdup(path
);
1492 FileOperations local_ops
= {
1493 .parse_opts
= local_parse_opts
,
1495 .cleanup
= local_cleanup
,
1496 .lstat
= local_lstat
,
1497 .readlink
= local_readlink
,
1498 .close
= local_close
,
1499 .closedir
= local_closedir
,
1501 .opendir
= local_opendir
,
1502 .rewinddir
= local_rewinddir
,
1503 .telldir
= local_telldir
,
1504 .readdir
= local_readdir
,
1505 .seekdir
= local_seekdir
,
1506 .preadv
= local_preadv
,
1507 .pwritev
= local_pwritev
,
1508 .chmod
= local_chmod
,
1509 .mknod
= local_mknod
,
1510 .mkdir
= local_mkdir
,
1511 .fstat
= local_fstat
,
1512 .open2
= local_open2
,
1513 .symlink
= local_symlink
,
1515 .truncate
= local_truncate
,
1516 .rename
= local_rename
,
1517 .chown
= local_chown
,
1518 .utimensat
= local_utimensat
,
1519 .remove
= local_remove
,
1520 .fsync
= local_fsync
,
1521 .statfs
= local_statfs
,
1522 .lgetxattr
= local_lgetxattr
,
1523 .llistxattr
= local_llistxattr
,
1524 .lsetxattr
= local_lsetxattr
,
1525 .lremovexattr
= local_lremovexattr
,
1526 .name_to_path
= local_name_to_path
,
1527 .renameat
= local_renameat
,
1528 .unlinkat
= local_unlinkat
,