diff --git a/sbin/mount_fusefs/mount_fusefs.8 b/sbin/mount_fusefs/mount_fusefs.8 --- a/sbin/mount_fusefs/mount_fusefs.8 +++ b/sbin/mount_fusefs/mount_fusefs.8 @@ -164,6 +164,12 @@ .Fl o Cm noprivate . .It Cm push_symlinks_in Prefix absolute symlinks with the mountpoint. +.\" .It Cm simulate_nfs +.\" Simulate the behavior of nfsd by ignoring +.\" .Xr VOP_OPEN +.\" and +.\" .Xr VOP_CLOSE . +.\" For internal testing purposes only; do not expose to users. .It Cm subtype Ns = Ns Ar fsname Suffix .Ar fsname diff --git a/sys/fs/fuse/fuse_ipc.h b/sys/fs/fuse/fuse_ipc.h --- a/sys/fs/fuse/fuse_ipc.h +++ b/sys/fs/fuse/fuse_ipc.h @@ -229,6 +229,7 @@ #define FSESS_DEFAULT_PERMISSIONS 0x0040 /* kernel does permission checking */ #define FSESS_ASYNC_READ 0x1000 /* allow multiple reads of some file */ #define FSESS_POSIX_LOCKS 0x2000 /* daemon supports POSIX locks */ +#define FSESS_SIMULATE_NFS 0x4000 /* Force all VNOPs to mimic nfsd */ #define FSESS_EXPORT_SUPPORT 0x10000 /* daemon supports NFS-style lookups */ #define FSESS_INTR 0x20000 /* interruptible mounts */ #define FSESS_WARN_SHORT_WRITE 0x40000 /* Short write without direct_io */ @@ -240,7 +241,7 @@ #define FSESS_WARN_READLINK_EMBEDDED_NUL 0x1000000 /* corrupt READLINK output */ #define FSESS_MNTOPTS_MASK ( \ FSESS_DAEMON_CAN_SPY | FSESS_PUSH_SYMLINKS_IN | \ - FSESS_DEFAULT_PERMISSIONS | FSESS_INTR) + FSESS_SIMULATE_NFS | FSESS_DEFAULT_PERMISSIONS | FSESS_INTR) extern int fuse_data_cache_mode; diff --git a/sys/fs/fuse/fuse_vfsops.c b/sys/fs/fuse/fuse_vfsops.c --- a/sys/fs/fuse/fuse_vfsops.c +++ b/sys/fs/fuse/fuse_vfsops.c @@ -335,6 +335,7 @@ FUSE_FLAGOPT(push_symlinks_in, FSESS_PUSH_SYMLINKS_IN); FUSE_FLAGOPT(default_permissions, FSESS_DEFAULT_PERMISSIONS); FUSE_FLAGOPT(intr, FSESS_INTR); + FUSE_FLAGOPT(simulate_nfs, FSESS_SIMULATE_NFS); (void)vfs_scanopt(opts, "max_read=", "%u", &max_read); (void)vfs_scanopt(opts, "linux_errnos", "%d", &linux_errnos); diff --git a/sys/fs/fuse/fuse_vnops.c b/sys/fs/fuse/fuse_vnops.c --- a/sys/fs/fuse/fuse_vnops.c +++ b/sys/fs/fuse/fuse_vnops.c @@ -368,6 +368,17 @@ return (0); } +/* Should we treat the mountpoint as though it's NFS-exported? */ +static inline bool +fuse_mp_exported(struct vnode *vp) +{ + struct mount *mp = vnode_mount(vp); + struct fuse_data *data = fuse_get_mpdata(mp); + + return ((mp->mnt_flag & MNT_EXPORTED) || + (data->dataflags & FSESS_SIMULATE_NFS)); +} + /* Send FUSE_LSEEK for this node */ static int @@ -783,6 +794,7 @@ struct vnode *vp = ap->a_vp; struct mount *mp = vnode_mount(vp); struct ucred *cred = ap->a_cred; + struct fuse_data *data = fuse_get_mpdata(mp); int fflag = ap->a_fflag; struct thread *td = ap->a_td; pid_t pid = td->td_proc->p_pid; @@ -795,17 +807,15 @@ return 0; if (fflag & IO_NDELAY) return 0; + if (data->dataflags & FSESS_SIMULATE_NFS) + return 0; err = fuse_flush(vp, cred, pid, fflag); if (err == 0 && (fvdat->flag & FN_ATIMECHANGE) && !vfs_isrdonly(mp)) { struct vattr vap; - struct fuse_data *data; - int dataflags; int access_e = 0; - data = fuse_get_mpdata(mp); - dataflags = data->dataflags; - if (dataflags & FSESS_DEFAULT_PERMISSIONS) { + if (data->dataflags & FSESS_DEFAULT_PERMISSIONS) { struct vattr va; fuse_internal_getattr(vp, &va, cred, td); @@ -1735,6 +1745,8 @@ int a_mode = ap->a_mode; struct thread *td = ap->a_td; struct ucred *cred = ap->a_cred; + struct mount *mp = vnode_mount(vp); + struct fuse_data *data = fuse_get_mpdata(mp); pid_t pid = td->td_proc->p_pid; if (fuse_isdeadfs(vp)) @@ -1743,6 +1755,8 @@ return (EOPNOTSUPP); if ((a_mode & (FREAD | FWRITE | FEXEC)) == 0) return EINVAL; + if (data->dataflags & FSESS_SIMULATE_NFS) + return 0; if (fuse_filehandle_validrw(vp, a_mode, cred, pid)) { fuse_vnode_open(vp, 0, td); @@ -1866,7 +1880,7 @@ } err = fuse_filehandle_getrw(vp, FREAD, &fufh, cred, pid); - if (err == EBADF && vnode_mount(vp)->mnt_flag & MNT_EXPORTED) { + if (err == EBADF && fuse_mp_exported(vp)) { /* * nfsd will do I/O without first doing VOP_OPEN. We * must implicitly open the file here @@ -1944,7 +1958,7 @@ tresid = uio->uio_resid; err = fuse_filehandle_get_dir(vp, &fufh, cred, pid); - if (err == EBADF && mp->mnt_flag & MNT_EXPORTED) { + if (err == EBADF && fuse_mp_exported(vp)) { KASSERT(!fsess_is_impl(mp, FUSE_OPENDIR), ("FUSE file systems that implement " "FUSE_OPENDIR should not be exported")); @@ -2479,7 +2493,7 @@ } err = fuse_filehandle_getrw(vp, FWRITE, &fufh, cred, pid); - if (err == EBADF && vnode_mount(vp)->mnt_flag & MNT_EXPORTED) { + if (err == EBADF && fuse_mp_exported(vp)) { /* * nfsd will do I/O without first doing VOP_OPEN. We * must implicitly open the file here @@ -3015,7 +3029,7 @@ goto fallback; err = fuse_filehandle_getrw(vp, FWRITE, &fufh, cred, pid); - if (err == EBADF && vnode_mount(vp)->mnt_flag & MNT_EXPORTED) { + if (err == EBADF && fuse_mp_exported(vp)) { /* * nfsd will do I/O without first doing VOP_OPEN. We * must implicitly open the file here @@ -3200,8 +3214,7 @@ "VOP_VPTOFH without FUSE_EXPORT_SUPPORT"); return EOPNOTSUPP; } - if ((mp->mnt_flag & MNT_EXPORTED) && - fsess_is_impl(mp, FUSE_OPENDIR)) + if (fuse_mp_exported(vp) && fsess_is_impl(mp, FUSE_OPENDIR)) { /* * NFS is stateless, so nfsd must reopen a directory on every diff --git a/tests/sys/fs/fusefs/mockfs.hh b/tests/sys/fs/fusefs/mockfs.hh --- a/tests/sys/fs/fusefs/mockfs.hh +++ b/tests/sys/fs/fusefs/mockfs.hh @@ -360,7 +360,8 @@ enum poll_method pm, uint32_t flags, uint32_t kernel_minor_version, uint32_t max_write, bool async, bool no_clusterr, unsigned time_gran, bool nointr, - bool noatime, const char *fsname, const char *subtype); + bool noatime, bool simulate_nfs, const char *fsname, + const char *subtype); virtual ~MockFS(); diff --git a/tests/sys/fs/fusefs/mockfs.cc b/tests/sys/fs/fusefs/mockfs.cc --- a/tests/sys/fs/fusefs/mockfs.cc +++ b/tests/sys/fs/fusefs/mockfs.cc @@ -295,7 +295,8 @@ in.body.read.offset, in.body.read.size); if (verbosity > 1) - printf(" flags=%#x", in.body.read.flags); + printf(" flags=%#x fh=%#" PRIx64, + in.body.read.flags, in.body.read.fh); break; case FUSE_READDIR: printf(" fh=%#" PRIx64 " offset=%" PRIu64 " size=%u", @@ -420,7 +421,7 @@ bool push_symlinks_in, bool ro, enum poll_method pm, uint32_t flags, uint32_t kernel_minor_version, uint32_t max_write, bool async, bool noclusterr, unsigned time_gran, bool nointr, bool noatime, - const char *fsname, const char *subtype) + bool simulate_nfs, const char *fsname, const char *subtype) : m_daemon_id(NULL), m_kernel_minor_version(kernel_minor_version), m_kq(pm == KQ ? kqueue() : -1), @@ -509,6 +510,10 @@ build_iovec(&iov, &iovlen, "fsname=", __DECONST(void*, fsname), -1); } + if (simulate_nfs) { + build_iovec(&iov, &iovlen, "simulate_nfs", + __DECONST(void*, &trueval), sizeof(bool)); + } if (*subtype) { build_iovec(&iov, &iovlen, "subtype=", __DECONST(void*, subtype), -1); diff --git a/tests/sys/fs/fusefs/read.cc b/tests/sys/fs/fusefs/read.cc --- a/tests/sys/fs/fusefs/read.cc +++ b/tests/sys/fs/fusefs/read.cc @@ -111,6 +111,20 @@ } }; +class ReadNfs: public Read { + virtual void SetUp() { + m_simulate_nfs = true; + Read::SetUp(); + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in.header.opcode == FUSE_OPEN); + }, Eq(true)), + _) + ).Times(AtMost(1)) + .WillOnce(Invoke(ReturnErrno(ENOSYS))); + } +}; + class ReadNoatime: public Read { virtual void SetUp() { m_noatime = true; @@ -840,6 +854,29 @@ leak(fd); } +/* Read a file like nfsd does, without VOP_OPEN */ +TEST_F(ReadNfs, read) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + const char *CONTENTS = "abcdefgh"; + uint64_t ino = 42; + int fd; + ssize_t bufsize = strlen(CONTENTS); + uint8_t buf[bufsize]; + + expect_lookup(RELPATH, ino, bufsize); + expect_read(ino, 0, bufsize, bufsize, CONTENTS, 0, 0); + + fd = open(FULLPATH, O_RDONLY); + ASSERT_LE(0, fd) << strerror(errno); + + ASSERT_EQ(bufsize, read(fd, buf, bufsize)) << strerror(errno); + ASSERT_EQ(0, memcmp(buf, CONTENTS, bufsize)); + + leak(fd); +} + /* * The kernel should not update the cached atime attribute during a read, if * MNT_NOATIME is used. diff --git a/tests/sys/fs/fusefs/utils.hh b/tests/sys/fs/fusefs/utils.hh --- a/tests/sys/fs/fusefs/utils.hh +++ b/tests/sys/fs/fusefs/utils.hh @@ -63,6 +63,7 @@ uint32_t m_kernel_minor_version; enum poll_method m_pm; bool m_noatime; + bool m_simulate_nfs; bool m_push_symlinks_in; bool m_ro; bool m_async; @@ -88,6 +89,7 @@ m_kernel_minor_version(FUSE_KERNEL_MINOR_VERSION), m_pm(BLOCKING), m_noatime(false), + m_simulate_nfs(false), m_push_symlinks_in(false), m_ro(false), m_async(false), diff --git a/tests/sys/fs/fusefs/utils.cc b/tests/sys/fs/fusefs/utils.cc --- a/tests/sys/fs/fusefs/utils.cc +++ b/tests/sys/fs/fusefs/utils.cc @@ -158,7 +158,8 @@ m_default_permissions, m_push_symlinks_in, m_ro, m_pm, m_init_flags, m_kernel_minor_version, m_maxwrite, m_async, m_noclusterr, m_time_gran, - m_nointr, m_noatime, m_fsname, m_subtype); + m_nointr, m_noatime, m_simulate_nfs, m_fsname, + m_subtype); /* * FUSE_ACCESS is called almost universally. Expecting it in * each test case would be super-annoying. Instead, set a