From a4856c96d0e0c6a41d48c3fd43c139c8ab0857c1 Mon Sep 17 00:00:00 2001 From: Alan Somers Date: Wed, 29 May 2019 16:39:52 +0000 Subject: [PATCH] fusefs: raise protocol level to 7.12 This commit raises the protocol level and adds backwards-compatibility code to handle structure size changes. It doesn't implement any new features. The new features added in protocol 7.12 are: * server-side umask processing (which FreeBSD won't do) * asynchronous inode and directory entry invalidation (which I'll do next) Sponsored by: The FreeBSD Foundation --- sys/fs/fuse/fuse_internal.c | 14 ++++- sys/fs/fuse/fuse_kernel.h | 37 +++++++++++- sys/fs/fuse/fuse_vnops.c | 29 ++++++--- tests/sys/fs/fusefs/create.cc | 68 +++++++++++++++++++++- tests/sys/fs/fusefs/default_permissions.cc | 2 +- tests/sys/fs/fusefs/mkdir.cc | 5 ++ tests/sys/fs/fusefs/mknod.cc | 66 ++++++++++++++++++++- tests/sys/fs/fusefs/mockfs.cc | 30 ++++++---- tests/sys/fs/fusefs/mockfs.hh | 3 + 9 files changed, 228 insertions(+), 26 deletions(-) diff --git a/sys/fs/fuse/fuse_internal.c b/sys/fs/fuse/fuse_internal.c index 575530d4387c..ff001029c9b4 100644 --- a/sys/fs/fuse/fuse_internal.c +++ b/sys/fs/fuse/fuse_internal.c @@ -331,12 +331,22 @@ int fuse_internal_mknod(struct vnode *dvp, struct vnode **vpp, struct componentname *cnp, struct vattr *vap) { + struct fuse_data *data; struct fuse_mknod_in fmni; + size_t insize; + + data = fuse_get_mpdata(dvp->v_mount); fmni.mode = MAKEIMODE(vap->va_type, vap->va_mode); fmni.rdev = vap->va_rdev; + if (fuse_libabi_geq(data, 7, 12)) { + insize = sizeof(fmni); + fmni.umask = curthread->td_proc->p_fd->fd_cmask; + } else { + insize = FUSE_COMPAT_MKNOD_IN_SIZE; + } return (fuse_internal_newentry(dvp, vpp, cnp, FUSE_MKNOD, &fmni, - sizeof(fmni), vap->va_type)); + insize, vap->va_type)); } /* readdir */ @@ -824,6 +834,8 @@ fuse_internal_send_init(struct fuse_data *data, struct thread *td) * Unsupported features: * FUSE_FILE_OPS: No known FUSE server or client supports it * FUSE_ATOMIC_O_TRUNC: our VFS cannot support it + * FUSE_DONT_MASK: unlike Linux, FreeBSD always applies the umask, even + * when default ACLs are in use. */ fiii->flags = FUSE_ASYNC_READ | FUSE_POSIX_LOCKS | FUSE_EXPORT_SUPPORT | FUSE_BIG_WRITES; diff --git a/sys/fs/fuse/fuse_kernel.h b/sys/fs/fuse/fuse_kernel.h index ff8ec855743b..6c7f91aa3cf2 100644 --- a/sys/fs/fuse/fuse_kernel.h +++ b/sys/fs/fuse/fuse_kernel.h @@ -52,6 +52,11 @@ * 7.11 * - add IOCTL message * - add unsolicited notification support + * + * 7.12 + * - add umask flag to input argument of open, mknod and mkdir + * - add notification messages for invalidation of inodes and + * directory entries */ #ifndef _FUSE_FUSE_KERNEL_H @@ -60,6 +65,7 @@ #ifndef linux #include #define __u64 uint64_t +#define __s64 int64_t #define __u32 uint32_t #define __s32 int32_t #else @@ -70,7 +76,7 @@ #define FUSE_KERNEL_VERSION 7 /** Minor version number of this interface */ -#define FUSE_KERNEL_MINOR_VERSION 11 +#define FUSE_KERNEL_MINOR_VERSION 12 /** The node ID of the root inode */ #define FUSE_ROOT_ID 1 @@ -146,6 +152,7 @@ struct fuse_file_lock { * INIT request/reply flags * * FUSE_EXPORT_SUPPORT: filesystem handles lookups of "." and ".." + * FUSE_DONT_MASK: don't apply umask to file mode on create operations */ #define FUSE_ASYNC_READ (1 << 0) #define FUSE_POSIX_LOCKS (1 << 1) @@ -153,6 +160,7 @@ struct fuse_file_lock { #define FUSE_ATOMIC_O_TRUNC (1 << 3) #define FUSE_EXPORT_SUPPORT (1 << 4) #define FUSE_BIG_WRITES (1 << 5) +#define FUSE_DONT_MASK (1 << 6) #ifdef linux /** @@ -262,6 +270,8 @@ enum fuse_opcode { enum fuse_notify_code { FUSE_NOTIFY_POLL = 1, + FUSE_NOTIFY_INVAL_INODE = 2, + FUSE_NOTIFY_INVAL_ENTRY = 3, FUSE_NOTIFY_CODE_MAX, }; @@ -300,14 +310,18 @@ struct fuse_attr_out { struct fuse_attr attr; }; +#define FUSE_COMPAT_MKNOD_IN_SIZE 8 + struct fuse_mknod_in { __u32 mode; __u32 rdev; + __u32 umask; + __u32 padding; }; struct fuse_mkdir_in { __u32 mode; - __u32 padding; + __u32 umask; }; struct fuse_rename_in { @@ -338,8 +352,15 @@ struct fuse_setattr_in { }; struct fuse_open_in { + __u32 flags; + __u32 unused; +}; + +struct fuse_create_in { __u32 flags; __u32 mode; + __u32 umask; + __u32 padding; }; struct fuse_open_out { @@ -558,4 +579,16 @@ struct fuse_dirent { #define FUSE_DIRENT_SIZE(d) \ FUSE_DIRENT_ALIGN(FUSE_NAME_OFFSET + (d)->namelen) +struct fuse_notify_inval_inode_out { + __u64 ino; + __s64 off; + __s64 len; +}; + +struct fuse_notify_inval_entry_out { + __u64 parent; + __u32 namelen; + __u32 padding; +}; + #endif /* _FUSE_FUSE_KERNEL_H */ diff --git a/sys/fs/fuse/fuse_vnops.c b/sys/fs/fuse/fuse_vnops.c index 29db6232f558..ecfc02422964 100644 --- a/sys/fs/fuse/fuse_vnops.c +++ b/sys/fs/fuse/fuse_vnops.c @@ -540,7 +540,8 @@ fuse_vnop_create(struct vop_create_args *ap) struct thread *td = cnp->cn_thread; struct ucred *cred = cnp->cn_cred; - struct fuse_open_in *foi; + struct fuse_data *data; + struct fuse_create_in *fci; struct fuse_entry_out *feo; struct fuse_open_out *foo; struct fuse_dispatcher fdi, fdi2; @@ -550,6 +551,7 @@ fuse_vnop_create(struct vop_create_args *ap) int err; struct mount *mp = vnode_mount(dvp); + data = fuse_get_mpdata(mp); uint64_t parentnid = VTOFUD(dvp)->nid; mode_t mode = MAKEIMODE(vap->va_type, vap->va_mode); enum fuse_opcode op; @@ -580,15 +582,24 @@ fuse_vnop_create(struct vop_create_args *ap) cred, mode, &op); } else { /* Use FUSE_CREATE */ + size_t insize; + op = FUSE_CREATE; - fdisp_init(fdip, sizeof(*foi) + cnp->cn_namelen + 1); + fdisp_init(fdip, sizeof(*fci) + cnp->cn_namelen + 1); fdisp_make(fdip, op, vnode_mount(dvp), parentnid, td, cred); - foi = fdip->indata; - foi->mode = mode; - foi->flags = O_CREAT | flags; - memcpy((char *)fdip->indata + sizeof(*foi), cnp->cn_nameptr, + fci = fdip->indata; + fci->mode = mode; + fci->flags = O_CREAT | flags; + if (fuse_libabi_geq(data, 7, 12)) { + insize = sizeof(*fci); + fci->umask = td->td_proc->p_fd->fd_cmask; + } else { + insize = sizeof(struct fuse_open_in); + } + + memcpy((char *)fdip->indata + insize, cnp->cn_nameptr, cnp->cn_namelen); - ((char *)fdip->indata)[sizeof(*foi) + cnp->cn_namelen] = '\0'; + ((char *)fdip->indata)[insize + cnp->cn_namelen] = '\0'; } err = fdisp_wait_answ(fdip); @@ -614,12 +625,13 @@ fuse_vnop_create(struct vop_create_args *ap) foo = (struct fuse_open_out*)(feo + 1); } else { /* Issue a separate FUSE_OPEN */ + struct fuse_open_in *foi; + fdip2 = &fdi2; fdisp_init(fdip2, sizeof(*foi)); fdisp_make(fdip2, FUSE_OPEN, vnode_mount(dvp), feo->nodeid, td, cred); foi = fdip2->indata; - foi->mode = mode; foi->flags = flags; err = fdisp_wait_answ(fdip2); if (err) @@ -1162,6 +1174,7 @@ fuse_vnop_mkdir(struct vop_mkdir_args *ap) return ENXIO; } fmdi.mode = MAKEIMODE(vap->va_type, vap->va_mode); + fmdi.umask = curthread->td_proc->p_fd->fd_cmask; return (fuse_internal_newentry(dvp, vpp, cnp, FUSE_MKDIR, &fmdi, sizeof(fmdi), VDIR)); diff --git a/tests/sys/fs/fusefs/create.cc b/tests/sys/fs/fusefs/create.cc index 2dd13371b12f..6025a27ecaad 100644 --- a/tests/sys/fs/fusefs/create.cc +++ b/tests/sys/fs/fusefs/create.cc @@ -42,12 +42,16 @@ public: void expect_create(const char *relpath, mode_t mode, ProcessMockerT r) { + mode_t mask = umask(0); + (void)umask(mask); + EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { const char *name = (const char*)in.body.bytes + - sizeof(fuse_open_in); + sizeof(fuse_create_in); return (in.header.opcode == FUSE_CREATE && - in.body.open.mode == mode && + in.body.create.mode == mode && + in.body.create.umask == mask && (0 == strcmp(relpath, name))); }, Eq(true)), _) @@ -63,6 +67,45 @@ virtual void SetUp() { m_kernel_minor_version = 8; Create::SetUp(); } + +void expect_create(const char *relpath, mode_t mode, ProcessMockerT r) +{ + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + const char *name = (const char*)in.body.bytes + + sizeof(fuse_open_in); + return (in.header.opcode == FUSE_CREATE && + in.body.create.mode == mode && + (0 == strcmp(relpath, name))); + }, Eq(true)), + _) + ).WillOnce(Invoke(r)); +} + +}; + +/* FUSE_CREATE operations for a server built at protocol <= 7.11 */ +class Create_7_11: public FuseTest { +public: +virtual void SetUp() { + m_kernel_minor_version = 11; + FuseTest::SetUp(); +} + +void expect_create(const char *relpath, mode_t mode, ProcessMockerT r) +{ + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + const char *name = (const char*)in.body.bytes + + sizeof(fuse_open_in); + return (in.header.opcode == FUSE_CREATE && + in.body.create.mode == mode && + (0 == strcmp(relpath, name))); + }, Eq(true)), + _) + ).WillOnce(Invoke(r)); +} + }; @@ -372,4 +415,25 @@ TEST_F(Create_7_8, ok) /* Deliberately leak fd. close(2) will be tested in release.cc */ } +TEST_F(Create_7_11, ok) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + mode_t mode = S_IFREG | 0755; + uint64_t ino = 42; + int fd; + EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); + expect_create(RELPATH, mode, + ReturnImmediate([=](auto in __unused, auto& out) { + SET_OUT_HEADER_LEN(out, create); + out.body.create.entry.attr.mode = mode; + out.body.create.entry.nodeid = ino; + out.body.create.entry.entry_valid = UINT64_MAX; + out.body.create.entry.attr_valid = UINT64_MAX; + })); + + fd = open(FULLPATH, O_CREAT | O_EXCL, mode); + EXPECT_LE(0, fd) << strerror(errno); + /* Deliberately leak fd. close(2) will be tested in release.cc */ +} diff --git a/tests/sys/fs/fusefs/default_permissions.cc b/tests/sys/fs/fusefs/default_permissions.cc index 232bd84b8fee..bba2942ce0f9 100644 --- a/tests/sys/fs/fusefs/default_permissions.cc +++ b/tests/sys/fs/fusefs/default_permissions.cc @@ -92,7 +92,7 @@ void expect_create(const char *relpath, uint64_t ino) EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { const char *name = (const char*)in.body.bytes + - sizeof(fuse_open_in); + sizeof(fuse_create_in); return (in.header.opcode == FUSE_CREATE && (0 == strcmp(relpath, name))); }, Eq(true)), diff --git a/tests/sys/fs/fusefs/mkdir.cc b/tests/sys/fs/fusefs/mkdir.cc index 85b098cf1fbc..a763a0dd50a6 100644 --- a/tests/sys/fs/fusefs/mkdir.cc +++ b/tests/sys/fs/fusefs/mkdir.cc @@ -159,6 +159,10 @@ TEST_F(Mkdir, ok) const char RELPATH[] = "some_dir"; mode_t mode = 0755; uint64_t ino = 42; + mode_t mask; + + mask = umask(0); + (void)umask(mask); EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); @@ -168,6 +172,7 @@ TEST_F(Mkdir, ok) sizeof(fuse_mkdir_in); return (in.header.opcode == FUSE_MKDIR && in.body.mkdir.mode == (S_IFDIR | mode) && + in.body.mkdir.umask == mask && (0 == strcmp(RELPATH, name))); }, Eq(true)), _) diff --git a/tests/sys/fs/fusefs/mknod.cc b/tests/sys/fs/fusefs/mknod.cc index fddfe0c74f7d..21ec97257f3a 100644 --- a/tests/sys/fs/fusefs/mknod.cc +++ b/tests/sys/fs/fusefs/mknod.cc @@ -48,14 +48,23 @@ const char RELPATH[] = "some_file.txt"; class Mknod: public FuseTest { +mode_t m_oldmask; +const static mode_t c_umask = 022; + public: virtual void SetUp() { - FuseTest::SetUp(); - + m_oldmask = umask(c_umask); + printf("m_oldmask=%#o\n", m_oldmask); if (geteuid() != 0) { GTEST_SKIP() << "Only root may use most mknod(2) variations"; } + FuseTest::SetUp(); +} + +virtual void TearDown() { + FuseTest::TearDown(); + (void)umask(m_oldmask); } /* Test an OK creation of a file with the given mode and device number */ @@ -68,6 +77,50 @@ void expect_mknod(mode_t mode, dev_t dev) { ResultOf([=](auto in) { const char *name = (const char*)in.body.bytes + sizeof(fuse_mknod_in); + return (in.header.opcode == FUSE_MKNOD && + in.body.mknod.mode == mode && + in.body.mknod.rdev == (uint32_t)dev && + in.body.mknod.umask == c_umask && + (0 == strcmp(RELPATH, name))); + }, Eq(true)), + _) + ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { + SET_OUT_HEADER_LEN(out, entry); + out.body.entry.attr.mode = mode; + out.body.entry.nodeid = ino; + out.body.entry.entry_valid = UINT64_MAX; + out.body.entry.attr_valid = UINT64_MAX; + out.body.entry.attr.rdev = dev; + }))); +} + +}; + +class Mknod_7_11: public FuseTest { +public: +virtual void SetUp() { + m_kernel_minor_version = 11; + if (geteuid() != 0) { + GTEST_SKIP() << "Only root may use most mknod(2) variations"; + } + FuseTest::SetUp(); +} + +void expect_lookup(const char *relpath, uint64_t ino, uint64_t size) +{ + FuseTest::expect_lookup_7_8(relpath, ino, S_IFREG | 0644, size, 1); +} + +/* Test an OK creation of a file with the given mode and device number */ +void expect_mknod(mode_t mode, dev_t dev) { + uint64_t ino = 42; + + EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + const char *name = (const char*)in.body.bytes + + FUSE_COMPAT_MKNOD_IN_SIZE; return (in.header.opcode == FUSE_MKNOD && in.body.mknod.mode == mode && in.body.mknod.rdev == (uint32_t)dev && @@ -172,3 +225,12 @@ TEST_F(Mknod, DISABLED_whiteout) expect_mknod(mode, rdev); EXPECT_EQ(0, mknod(FULLPATH, mode, 0)) << strerror(errno); } + +/* A server built at protocol version 7.11 or earlier can still use mknod */ +TEST_F(Mknod_7_11, fifo) +{ + mode_t mode = S_IFIFO | 0755; + dev_t rdev = VNOVAL; + expect_mknod(mode, rdev); + EXPECT_EQ(0, mkfifo(FULLPATH, mode)) << strerror(errno); +} diff --git a/tests/sys/fs/fusefs/mockfs.cc b/tests/sys/fs/fusefs/mockfs.cc index b178e81f5dac..a6551e7160d2 100644 --- a/tests/sys/fs/fusefs/mockfs.cc +++ b/tests/sys/fs/fusefs/mockfs.cc @@ -153,7 +153,7 @@ void sigint_handler(int __unused sig) { // Don't do anything except interrupt the daemon's read(2) call } -void debug_fuseop(const mockfs_buf_in &in) +void MockFS::debug_fuseop(const mockfs_buf_in &in) { printf("%-11s ino=%2" PRIu64, opcode2opname(in.header.opcode), in.header.nodeid); @@ -169,8 +169,12 @@ void debug_fuseop(const mockfs_buf_in &in) printf(" mask=%#x", in.body.access.mask); break; case FUSE_CREATE: - name = (const char*)in.body.bytes + - sizeof(fuse_open_in); + if (m_kernel_minor_version >= 12) + name = (const char*)in.body.bytes + + sizeof(fuse_create_in); + else + name = (const char*)in.body.bytes + + sizeof(fuse_open_in); printf(" flags=%#x name=%s", in.body.open.flags, name); break; @@ -200,19 +204,25 @@ void debug_fuseop(const mockfs_buf_in &in) case FUSE_MKDIR: name = (const char*)in.body.bytes + sizeof(fuse_mkdir_in); - printf(" name=%s mode=%#o", name, in.body.mkdir.mode); + printf(" name=%s mode=%#o umask=%#o", name, + in.body.mkdir.mode, in.body.mkdir.umask); break; case FUSE_MKNOD: - printf(" mode=%#o rdev=%x", in.body.mknod.mode, - in.body.mknod.rdev); + if (m_kernel_minor_version >= 12) + name = (const char*)in.body.bytes + + sizeof(fuse_mknod_in); + else + name = (const char*)in.body.bytes + + FUSE_COMPAT_MKNOD_IN_SIZE; + printf(" mode=%#o rdev=%x umask=%#o name=%s", + in.body.mknod.mode, in.body.mknod.rdev, + in.body.mknod.umask, name); break; case FUSE_OPEN: - printf(" flags=%#x mode=%#o", - in.body.open.flags, in.body.open.mode); + printf(" flags=%#x", in.body.open.flags); break; case FUSE_OPENDIR: - printf(" flags=%#x mode=%#o", - in.body.opendir.flags, in.body.opendir.mode); + printf(" flags=%#x", in.body.opendir.flags); break; case FUSE_READ: printf(" offset=%" PRIu64 " size=%u", diff --git a/tests/sys/fs/fusefs/mockfs.hh b/tests/sys/fs/fusefs/mockfs.hh index 186d54064cb9..6943a0ecafbf 100644 --- a/tests/sys/fs/fusefs/mockfs.hh +++ b/tests/sys/fs/fusefs/mockfs.hh @@ -126,6 +126,7 @@ union fuse_payloads_in { fuse_access_in access; /* value is from fuse_kern_chan.c in fusefs-libs */ uint8_t bytes[0x21000 - sizeof(struct fuse_in_header)]; + fuse_create_in create; fuse_flush_in flush; fuse_fsync_in fsync; fuse_fsync_in fsyncdir; @@ -257,6 +258,8 @@ class MockFS { /* Method the daemon should use for I/O to and from /dev/fuse */ enum poll_method m_pm; + void debug_fuseop(const mockfs_buf_in&); + /* Initialize a session after mounting */ void init(uint32_t flags);