git: dfb3365aa112 - stable/13 - fusefs: implement FUSE_NO_OPEN_SUPPORT and FUSE_NO_OPENDIR_SUPPORT
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Tue, 07 Dec 2021 05:04:27 UTC
The branch stable/13 has been updated by asomers: URL: https://cgit.FreeBSD.org/src/commit/?id=dfb3365aa112d49d794b47e5b985416762152ba9 commit dfb3365aa112d49d794b47e5b985416762152ba9 Author: Alan Somers <asomers@FreeBSD.org> AuthorDate: 2021-09-25 03:53:28 +0000 Commit: Alan Somers <asomers@FreeBSD.org> CommitDate: 2021-12-07 04:51:55 +0000 fusefs: implement FUSE_NO_OPEN_SUPPORT and FUSE_NO_OPENDIR_SUPPORT For file systems that allow it, fusefs will skip FUSE_OPEN, FUSE_RELEASE, FUSE_OPENDIR, and FUSE_RELEASEDIR operations, a minor optimization. Reviewed by: pfg Differential Revision: https://reviews.freebsd.org/D32141 (cherry picked from commit 7124d2bc3a3bd58f6d3803a0579f2e0fa853b56d) --- sys/fs/fuse/fuse_file.c | 65 +++++++++++++++++++++++++++++------------ sys/fs/fuse/fuse_file.h | 3 +- sys/fs/fuse/fuse_internal.c | 8 +++-- sys/fs/fuse/fuse_ipc.h | 2 ++ sys/fs/fuse/fuse_kernel.h | 4 ++- tests/sys/fs/fusefs/open.cc | 66 ++++++++++++++++++++++++++++++++++++++++++ tests/sys/fs/fusefs/opendir.cc | 48 ++++++++++++++++++++++++++++++ 7 files changed, 174 insertions(+), 22 deletions(-) diff --git a/sys/fs/fuse/fuse_file.c b/sys/fs/fuse/fuse_file.c index f9b0c781f49a..a7357d85a8b3 100644 --- a/sys/fs/fuse/fuse_file.c +++ b/sys/fs/fuse/fuse_file.c @@ -124,44 +124,68 @@ int fuse_filehandle_open(struct vnode *vp, int a_mode, struct fuse_filehandle **fufhp, struct thread *td, struct ucred *cred) { + struct mount *mp = vnode_mount(vp); + struct fuse_data *data = fuse_get_mpdata(mp); struct fuse_dispatcher fdi; - struct fuse_open_in *foi; - struct fuse_open_out *foo; + const struct fuse_open_out default_foo = { + .fh = 0, + .open_flags = FOPEN_KEEP_CACHE, + .padding = 0 + }; + struct fuse_open_in *foi = NULL; + const struct fuse_open_out *foo; fufh_type_t fufh_type; - + int dataflags = data->dataflags; int err = 0; int oflags = 0; int op = FUSE_OPEN; + int relop = FUSE_RELEASE; + int fsess_no_op_support = FSESS_NO_OPEN_SUPPORT; fufh_type = fflags_2_fufh_type(a_mode); oflags = fufh_type_2_fflags(fufh_type); if (vnode_isdir(vp)) { op = FUSE_OPENDIR; + relop = FUSE_RELEASEDIR; + fsess_no_op_support = FSESS_NO_OPENDIR_SUPPORT; /* vn_open_vnode already rejects FWRITE on directories */ MPASS(fufh_type == FUFH_RDONLY || fufh_type == FUFH_EXEC); } fdisp_init(&fdi, sizeof(*foi)); - fdisp_make_vp(&fdi, op, vp, td, cred); - - foi = fdi.indata; - foi->flags = oflags; - - if ((err = fdisp_wait_answ(&fdi))) { - SDT_PROBE2(fusefs, , file, trace, 1, - "OUCH ... daemon didn't give fh"); - if (err == ENOENT) { - fuse_internal_vnode_disappear(vp); + if (fsess_not_impl(mp, op) && dataflags & fsess_no_op_support) { + /* The operation implicitly succeeds */ + foo = &default_foo; + } else { + fdisp_make_vp(&fdi, op, vp, td, cred); + + foi = fdi.indata; + foi->flags = oflags; + + err = fdisp_wait_answ(&fdi); + if (err == ENOSYS && dataflags & fsess_no_op_support) { + /* The operation implicitly succeeds */ + foo = &default_foo; + fsess_set_notimpl(mp, op); + fsess_set_notimpl(mp, relop); + err = 0; + } else if (err) { + SDT_PROBE2(fusefs, , file, trace, 1, + "OUCH ... daemon didn't give fh"); + if (err == ENOENT) + fuse_internal_vnode_disappear(vp); + goto out; + } else { + foo = fdi.answ; } - goto out; } - foo = fdi.answ; fuse_filehandle_init(vp, fufh_type, fufhp, td, cred, foo); fuse_vnode_open(vp, foo->open_flags, td); out: - fdisp_destroy(&fdi); + if (foi) + fdisp_destroy(&fdi); return err; } @@ -169,6 +193,7 @@ int fuse_filehandle_close(struct vnode *vp, struct fuse_filehandle *fufh, struct thread *td, struct ucred *cred) { + struct mount *mp = vnode_mount(vp); struct fuse_dispatcher fdi; struct fuse_release_in *fri; @@ -180,6 +205,10 @@ fuse_filehandle_close(struct vnode *vp, struct fuse_filehandle *fufh, } if (vnode_isdir(vp)) op = FUSE_RELEASEDIR; + + if (fsess_not_impl(mp, op)) + goto out; + fdisp_init(&fdi, sizeof(*fri)); fdisp_make_vp(&fdi, op, vp, td, cred); fri = fdi.indata; @@ -327,8 +356,8 @@ fuse_filehandle_getrw(struct vnode *vp, int fflag, void fuse_filehandle_init(struct vnode *vp, fufh_type_t fufh_type, - struct fuse_filehandle **fufhp, struct thread *td, struct ucred *cred, - struct fuse_open_out *foo) + struct fuse_filehandle **fufhp, struct thread *td, const struct ucred *cred, + const struct fuse_open_out *foo) { struct fuse_vnode_data *fvdat = VTOFUD(vp); struct fuse_filehandle *fufh; diff --git a/sys/fs/fuse/fuse_file.h b/sys/fs/fuse/fuse_file.h index 10c799d5d40d..25004beead51 100644 --- a/sys/fs/fuse/fuse_file.h +++ b/sys/fs/fuse/fuse_file.h @@ -211,7 +211,8 @@ int fuse_filehandle_getrw(struct vnode *vp, int fflag, void fuse_filehandle_init(struct vnode *vp, fufh_type_t fufh_type, struct fuse_filehandle **fufhp, struct thread *td, - struct ucred *cred, struct fuse_open_out *foo); + const struct ucred *cred, + const struct fuse_open_out *foo); int fuse_filehandle_open(struct vnode *vp, int mode, struct fuse_filehandle **fufhp, struct thread *td, struct ucred *cred); diff --git a/sys/fs/fuse/fuse_internal.c b/sys/fs/fuse/fuse_internal.c index 731ac9c2ba24..c52f12d3a71a 100644 --- a/sys/fs/fuse/fuse_internal.c +++ b/sys/fs/fuse/fuse_internal.c @@ -1009,6 +1009,10 @@ fuse_internal_init_callback(struct fuse_ticket *tick, struct uio *uio) data->dataflags |= FSESS_POSIX_LOCKS; if (fiio->flags & FUSE_EXPORT_SUPPORT) data->dataflags |= FSESS_EXPORT_SUPPORT; + if (fiio->flags & FUSE_NO_OPEN_SUPPORT) + data->dataflags |= FSESS_NO_OPEN_SUPPORT; + if (fiio->flags & FUSE_NO_OPENDIR_SUPPORT) + data->dataflags |= FSESS_NO_OPENDIR_SUPPORT; /* * Don't bother to check FUSE_BIG_WRITES, because it's * redundant with max_write @@ -1098,7 +1102,6 @@ fuse_internal_send_init(struct fuse_data *data, struct thread *td) * FUSE_DO_READDIRPLUS: not yet implemented * FUSE_READDIRPLUS_AUTO: not yet implemented * FUSE_ASYNC_DIO: not yet implemented - * FUSE_NO_OPEN_SUPPORT: not yet implemented * FUSE_PARALLEL_DIROPS: not yet implemented * FUSE_HANDLE_KILLPRIV: not yet implemented * FUSE_POSIX_ACL: not yet implemented @@ -1107,7 +1110,8 @@ fuse_internal_send_init(struct fuse_data *data, struct thread *td) * FUSE_MAX_PAGES: not yet implemented */ fiii->flags = FUSE_ASYNC_READ | FUSE_POSIX_LOCKS | FUSE_EXPORT_SUPPORT - | FUSE_BIG_WRITES | FUSE_WRITEBACK_CACHE; + | FUSE_BIG_WRITES | FUSE_WRITEBACK_CACHE + | FUSE_NO_OPEN_SUPPORT | FUSE_NO_OPENDIR_SUPPORT; fuse_insert_callback(fdi.tick, fuse_internal_init_callback); fuse_insert_message(fdi.tick, false); diff --git a/sys/fs/fuse/fuse_ipc.h b/sys/fs/fuse/fuse_ipc.h index 7b27fd97e212..fe616b3639a7 100644 --- a/sys/fs/fuse/fuse_ipc.h +++ b/sys/fs/fuse/fuse_ipc.h @@ -229,6 +229,8 @@ struct fuse_data { /* (and being observed by the daemon) */ #define FSESS_PUSH_SYMLINKS_IN 0x0020 /* prefix absolute symlinks with mp */ #define FSESS_DEFAULT_PERMISSIONS 0x0040 /* kernel does permission checking */ +#define FSESS_NO_OPEN_SUPPORT 0x0080 /* can elide FUSE_OPEN ops */ +#define FSESS_NO_OPENDIR_SUPPORT 0x0100 /* can elide FUSE_OPENDIR ops */ #define FSESS_ASYNC_READ 0x1000 /* allow multiple reads of some file */ #define FSESS_POSIX_LOCKS 0x2000 /* daemon supports POSIX locks */ #define FSESS_EXPORT_SUPPORT 0x10000 /* daemon supports NFS-style lookups */ diff --git a/sys/fs/fuse/fuse_kernel.h b/sys/fs/fuse/fuse_kernel.h index bd7323e9def2..51445637b9a8 100644 --- a/sys/fs/fuse/fuse_kernel.h +++ b/sys/fs/fuse/fuse_kernel.h @@ -198,7 +198,7 @@ #define FUSE_KERNEL_VERSION 7 /** Minor version number of this interface */ -#define FUSE_KERNEL_MINOR_VERSION 28 +#define FUSE_KERNEL_MINOR_VERSION 29 /** The node ID of the root inode */ #define FUSE_ROOT_ID 1 @@ -300,6 +300,7 @@ struct fuse_file_lock { * FUSE_ABORT_ERROR: reading the device after abort returns ECONNABORTED * FUSE_MAX_PAGES: init_out.max_pages contains the max number of req pages * FUSE_CACHE_SYMLINKS: cache READLINK responses + * FUSE_NO_OPENDIR_SUPPORT: kernel supports zero-message opendir */ #define FUSE_ASYNC_READ (1 << 0) #define FUSE_POSIX_LOCKS (1 << 1) @@ -325,6 +326,7 @@ struct fuse_file_lock { #define FUSE_ABORT_ERROR (1 << 21) #define FUSE_MAX_PAGES (1 << 22) #define FUSE_CACHE_SYMLINKS (1 << 23) +#define FUSE_NO_OPENDIR_SUPPORT (1 << 24) #ifdef linux /** diff --git a/tests/sys/fs/fusefs/open.cc b/tests/sys/fs/fusefs/open.cc index ede7ea9f04f7..7ac177a65d14 100644 --- a/tests/sys/fs/fusefs/open.cc +++ b/tests/sys/fs/fusefs/open.cc @@ -73,6 +73,13 @@ void test_ok(int os_flags, int fuse_flags) { }; +class OpenNoOpenSupport: public FuseTest { + virtual void SetUp() { + m_init_flags = FUSE_NO_OPEN_SUPPORT; + FuseTest::SetUp(); + } +}; + /* * fusefs(5) does not support I/O on device nodes (neither does UFS). But it * shouldn't crash @@ -274,3 +281,62 @@ TEST_F(Open, o_rdwr) test_ok(O_RDWR, O_RDWR); } +/* + * Without FUSE_NO_OPEN_SUPPORT, returning ENOSYS is an error + */ +TEST_F(Open, enosys) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + uint64_t ino = 42; + int fd; + + FuseTest::expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1); + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in.header.opcode == FUSE_OPEN && + in.body.open.flags == (uint32_t)O_RDONLY && + in.header.nodeid == ino); + }, Eq(true)), + _) + ).Times(1) + .WillOnce(Invoke(ReturnErrno(ENOSYS))); + + fd = open(FULLPATH, O_RDONLY); + ASSERT_EQ(-1, fd) << strerror(errno); + EXPECT_EQ(ENOSYS, errno); +} + +/* + * If a fuse server sets FUSE_NO_OPEN_SUPPORT and returns ENOSYS to a + * FUSE_OPEN, then it and subsequent FUSE_OPEN and FUSE_RELEASE operations will + * also succeed automatically without being sent to the server. + */ +TEST_F(OpenNoOpenSupport, enosys) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + uint64_t ino = 42; + int fd; + + FuseTest::expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 2); + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in.header.opcode == FUSE_OPEN && + in.body.open.flags == (uint32_t)O_RDONLY && + in.header.nodeid == ino); + }, Eq(true)), + _) + ).Times(1) + .WillOnce(Invoke(ReturnErrno(ENOSYS))); + expect_flush(ino, 1, ReturnErrno(ENOSYS)); + + fd = open(FULLPATH, O_RDONLY); + ASSERT_LE(0, fd) << strerror(errno); + close(fd); + + fd = open(FULLPATH, O_RDONLY); + ASSERT_LE(0, fd) << strerror(errno); + + leak(fd); +} diff --git a/tests/sys/fs/fusefs/opendir.cc b/tests/sys/fs/fusefs/opendir.cc index 7943be3124ca..365aa8dff9e2 100644 --- a/tests/sys/fs/fusefs/opendir.cc +++ b/tests/sys/fs/fusefs/opendir.cc @@ -73,6 +73,13 @@ void expect_opendir(uint64_t ino, uint32_t flags, ProcessMockerT r) }; +class OpendirNoOpendirSupport: public Opendir { + virtual void SetUp() { + m_init_flags = FUSE_NO_OPENDIR_SUPPORT; + FuseTest::SetUp(); + } +}; + /* * The fuse daemon fails the request with enoent. This usually indicates a @@ -172,3 +179,44 @@ TEST_F(Opendir, opendir) errno = 0; EXPECT_NE(nullptr, opendir(FULLPATH)) << strerror(errno); } + +/* + * Without FUSE_NO_OPENDIR_SUPPORT, returning ENOSYS is an error + */ +TEST_F(Opendir, enosys) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + uint64_t ino = 42; + + expect_lookup(RELPATH, ino); + expect_opendir(ino, O_RDONLY, ReturnErrno(ENOSYS)); + + EXPECT_EQ(-1, open(FULLPATH, O_DIRECTORY)); + EXPECT_EQ(ENOSYS, errno); +} + +/* + * If a fuse server sets FUSE_NO_OPENDIR_SUPPORT and returns ENOSYS to a + * FUSE_OPENDIR, then it and subsequent FUSE_OPENDIR and FUSE_RELEASEDIR + * operations will also succeed automatically without being sent to the server. + */ +TEST_F(OpendirNoOpendirSupport, enosys) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + uint64_t ino = 42; + int fd; + + FuseTest::expect_lookup(RELPATH, ino, S_IFDIR | 0755, 0, 2); + expect_opendir(ino, O_RDONLY, ReturnErrno(ENOSYS)); + + fd = open(FULLPATH, O_DIRECTORY); + ASSERT_LE(0, fd) << strerror(errno); + close(fd); + + fd = open(FULLPATH, O_DIRECTORY); + ASSERT_LE(0, fd) << strerror(errno); + + leak(fd); +}