Page MenuHomeFreeBSD

D57536.diff
No OneTemporary

D57536.diff

diff --git a/sys/fs/fuse/fuse_io.c b/sys/fs/fuse/fuse_io.c
--- a/sys/fs/fuse/fuse_io.c
+++ b/sys/fs/fuse/fuse_io.c
@@ -928,58 +928,13 @@
return (vn_fsync_buf(vp, waitfor));
}
-/*
- * Flush and invalidate all dirty buffers. If another process is already
- * doing the flush, just wait for completion.
- */
+/* Flush and invalidate all dirty buffers. */
int
fuse_io_invalbuf(struct vnode *vp, struct thread *td)
{
- struct fuse_vnode_data *fvdat = VTOFUD(vp);
- int error = 0;
-
if (VN_IS_DOOMED(vp))
return 0;
- ASSERT_VOP_ELOCKED(vp, "fuse_io_invalbuf");
-
- while (fvdat->flag & FN_FLUSHINPROG) {
- struct proc *p = td->td_proc;
-
- if (vp->v_mount->mnt_kern_flag & MNTK_UNMOUNTF)
- return EIO;
- fvdat->flag |= FN_FLUSHWANT;
- tsleep(&fvdat->flag, PRIBIO, "fusevinv", 2 * hz);
- error = 0;
- if (p != NULL) {
- PROC_LOCK(p);
- if (SIGNOTEMPTY(p->p_siglist) ||
- SIGNOTEMPTY(td->td_siglist))
- error = EINTR;
- PROC_UNLOCK(p);
- }
- if (error == EINTR)
- return EINTR;
- }
- fvdat->flag |= FN_FLUSHINPROG;
-
vnode_pager_clean_sync(vp);
- error = vinvalbuf(vp, V_SAVE, PCATCH, 0);
- while (error) {
- if (error == ERESTART || error == EINTR) {
- fvdat->flag &= ~FN_FLUSHINPROG;
- if (fvdat->flag & FN_FLUSHWANT) {
- fvdat->flag &= ~FN_FLUSHWANT;
- wakeup(&fvdat->flag);
- }
- return EINTR;
- }
- error = vinvalbuf(vp, V_SAVE, PCATCH, 0);
- }
- fvdat->flag &= ~FN_FLUSHINPROG;
- if (fvdat->flag & FN_FLUSHWANT) {
- fvdat->flag &= ~FN_FLUSHWANT;
- wakeup(&fvdat->flag);
- }
- return (error);
+ return (vinvalbuf(vp, V_SAVE, PCATCH, 0));
}
diff --git a/sys/fs/fuse/fuse_node.h b/sys/fs/fuse/fuse_node.h
--- a/sys/fs/fuse/fuse_node.h
+++ b/sys/fs/fuse/fuse_node.h
@@ -71,8 +71,6 @@
#include "fuse_file.h"
#define FN_REVOKED 0x00000020
-#define FN_FLUSHINPROG 0x00000040
-#define FN_FLUSHWANT 0x00000080
/*
* Indicates that the file's size is dirty; the kernel has changed it but not
* yet send the change to the daemon. When this bit is set, the
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
@@ -879,8 +879,10 @@
int fflag = ap->a_fflag;
struct thread *td;
struct fuse_vnode_data *fvdat = VTOFUD(vp);
+ struct timespec va_atime;
pid_t pid;
int err = 0;
+ bool atime_change, size_change;
/* NB: a_td will be NULL from some async kernel contexts */
td = ap->a_td ? ap->a_td : curthread;
@@ -897,8 +899,14 @@
cred = td->td_ucred;
err = fuse_flush(vp, cred, pid, fflag);
- ASSERT_CACHED_ATTRS_LOCKED(vp); /* For fvdat->flag */
- if (err == 0 && (fvdat->flag & FN_ATIMECHANGE) && !vfs_isrdonly(mp)) {
+
+ CACHED_ATTR_LOCK(vp);
+ atime_change = fvdat->flag & FN_ATIMECHANGE;
+ size_change = fvdat->flag & FN_SIZECHANGE;
+ va_atime = fvdat->cached_attrs.va_atime;
+ CACHED_ATTR_UNLOCK(vp);
+
+ if (err == 0 && atime_change && !vfs_isrdonly(mp)) {
struct vattr vap;
struct fuse_data *data;
int dataflags;
@@ -915,19 +923,28 @@
}
if (access_e == 0) {
VATTR_NULL(&vap);
- ASSERT_CACHED_ATTRS_LOCKED(vp);
- vap.va_atime = fvdat->cached_attrs.va_atime;
+ vap.va_atime = va_atime;
/*
* Ignore errors setting when setting atime. That
* should not cause close(2) to fail.
*/
+ CACHED_ATTR_LOCK(vp);
fuse_internal_setattr(vp, &vap, td, NULL);
+ CACHED_ATTR_UNLOCK(vp);
}
}
/* TODO: close the file handle, if we're sure it's no longer used */
- if ((fvdat->flag & FN_SIZECHANGE) != 0) {
+ if (size_change != 0) {
+ /*
+ * NB: this may panic if MNTK_SHARED_WRITES is ever enabled.
+ * For now it cannot, because it is illegal to use fexecve to
+ * execute a file descriptor open for writing, there's no way
+ * to dirty a file's size without writing to it, and we don't
+ * set MNTK_SHARED_WRITES.
+ */
fuse_vnode_savesize(vp, cred, pid);
}
+
return err;
}
diff --git a/tests/sys/fs/fusefs/Makefile b/tests/sys/fs/fusefs/Makefile
--- a/tests/sys/fs/fusefs/Makefile
+++ b/tests/sys/fs/fusefs/Makefile
@@ -5,6 +5,7 @@
TESTSDIR= ${TESTSBASE}/sys/fs/fusefs
ATF_TESTS_SH+= ctl
+ATF_TESTS_SH+= ext2-misc
# We could simply link all of these files into a single executable. But since
# Kyua treats googletest programs as plain tests, it's better to separate them
@@ -35,6 +36,7 @@
GTESTS+= locks
GTESTS+= lookup
GTESTS+= lseek
+GTESTS+= misc
GTESTS+= mkdir
GTESTS+= mknod
GTESTS+= mount
diff --git a/tests/sys/fs/fusefs/ext2-misc.sh b/tests/sys/fs/fusefs/ext2-misc.sh
new file mode 100644
--- /dev/null
+++ b/tests/sys/fs/fusefs/ext2-misc.sh
@@ -0,0 +1,55 @@
+# SPDX-License-Identifier: BSD-2-Clause
+#
+# Copyright (c) 2026 ConnectWise
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS DOCUMENTATION IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+# Regression test for https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=295957
+#
+# Almost any fuse file system would work, but this tests uses fusefs-ext2
+# because it's simple and its download is very small.
+atf_test_case execute cleanup
+execute_head()
+{
+ atf_set "descr" "Execute a file mounted on a fusefs file system"
+ atf_set "require.user" "root"
+ atf_set "require.progs" "fuse-ext2 mkfs.ext2"
+ atf_set "require.kmods" "fusefs"
+}
+execute_body()
+{
+ atf_check mkdir mnt
+ atf_check truncate -s 64m ext2.img
+ atf_check -o ignore -e ignore mkfs.ext2 ext2.img
+ atf_check fuse-ext2 -o rw+ ext2.img mnt
+ atf_check cp /usr/bin/true mnt
+ atf_check su -m nobody -c mnt/true
+}
+execute_cleanup()
+{
+ umount $PWD/mnt || true
+}
+
+atf_init_test_cases()
+{
+ atf_add_test_case execute
+}
diff --git a/tests/sys/fs/fusefs/misc.cc b/tests/sys/fs/fusefs/misc.cc
new file mode 100644
--- /dev/null
+++ b/tests/sys/fs/fusefs/misc.cc
@@ -0,0 +1,304 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2026 Alan Somers
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/* Miscellaneous tests that don't relate to any particular fuse operation */
+
+extern "C" {
+#include <sys/wait.h>
+
+#include <fcntl.h>
+#include <unistd.h>
+}
+
+#include "mockfs.hh"
+#include "utils.hh"
+
+using namespace testing;
+
+
+class Execv: public FuseTest {
+public:
+virtual void SetUp() {
+ /* Enable FUSE_ASYNC_READ to allow shared vnode locks */
+ m_init_flags = FUSE_ASYNC_READ;
+ FuseTest::SetUp();
+}
+};
+class Fexecv: public Execv {};
+
+class FexecvDefaultPermissions: public Fexecv {
+virtual void SetUp() {
+ m_default_permissions = true;
+ Fexecv::SetUp();
+}
+};
+
+/*
+ * Execute a file mounted on a fusefs file system. The server should get the
+ * FUSE_RELEASE request when sys_fexecve closes the file.
+ *
+ * Crucially, execve ignores the file system's MNTK_EXTENDED_SHARED flag.
+ *
+ * Regression test for https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=295957
+ */
+TEST_F(Execv, close)
+{
+ const static char FULLPATH[] = "mountpoint/true";
+ const static char RELPATH[] = "true";
+ const static size_t BUFSIZE = 16384;
+ FILE *true_file;
+ uint64_t ino = 42;
+ size_t true_len;
+ int status;
+ char *buf;
+
+ buf = new char[BUFSIZE];
+ true_file = fopen("/usr/bin/true", "r");
+ ASSERT_TRUE(true_file) << strerror(errno);
+ true_len = fread(buf, 1, BUFSIZE, true_file);
+ ASSERT_LT(true_len, BUFSIZE) << "Must increase BUFSIZE";
+ fclose(true_file);
+
+ fork(false, &status, [&] {
+ expect_lookup(RELPATH, ino, S_IFREG | 0755, true_len, 1,
+ UINT64_MAX);
+ expect_open(ino, 0, 1);
+ expect_read(ino, 0, true_len, true_len, buf);
+ expect_flush(ino, 1, ReturnErrno(ENOSYS));
+ EXPECT_CALL(*m_mock, process(
+ ResultOf([=](auto in) {
+ return (in.header.opcode == FUSE_RELEASE &&
+ in.header.nodeid == ino);
+ }, Eq(true)),
+ _)
+ ).Times(1)
+ .WillRepeatedly(Invoke(ReturnErrno(0)));
+
+ }, [&] {
+ char *const argv[] = {__DECONST(char *, "true"), NULL};
+ char *const env[] = {NULL};
+
+ execve(FULLPATH, argv, env);
+ fprintf(stderr, "execv: %s\n", strerror(errno));
+ return 1;
+ });
+ ASSERT_EQ(0, WEXITSTATUS(status));
+
+ delete[] buf;
+}
+
+TEST_F(Fexecv, close)
+{
+ const static char FULLPATH[] = "mountpoint/true";
+ const static char RELPATH[] = "true";
+ const static size_t BUFSIZE = 16384;
+ FILE *true_file;
+ uint64_t ino = 42;
+ size_t true_len;
+ int status;
+ char *buf;
+
+ buf = new char[BUFSIZE];
+ true_file = fopen("/usr/bin/true", "r");
+ ASSERT_TRUE(true_file) << strerror(errno);
+ true_len = fread(buf, 1, BUFSIZE, true_file);
+ ASSERT_LT(true_len, BUFSIZE) << "Must increase BUFSIZE";
+ fclose(true_file);
+
+ fork(false, &status, [&] {
+ expect_lookup(RELPATH, ino, S_IFREG | 0755, true_len, 1,
+ UINT64_MAX);
+ expect_open(ino, 0, 2);
+ expect_read(ino, 0, true_len, true_len, buf);
+ expect_flush(ino, 1, ReturnErrno(ENOSYS));
+ EXPECT_CALL(*m_mock, process(
+ ResultOf([=](auto in) {
+ return (in.header.opcode == FUSE_RELEASE &&
+ in.header.nodeid == ino);
+ }, Eq(true)),
+ _)
+ ).Times(2)
+ .WillRepeatedly(Invoke(ReturnErrno(0)));
+
+ }, [&] {
+ char *const argv[] = {__DECONST(char *, "true"), NULL};
+ char *const env[] = {NULL};
+ int fd;
+
+ fd = open(FULLPATH, O_EXEC);
+ if (fd < 0) {
+ fprintf(stderr, "open: %s\n", strerror(errno));
+ return 1;
+ }
+ fexecve(fd, argv, env);
+ fprintf(stderr, "execv: %s\n", strerror(errno));
+ return 1;
+ });
+ ASSERT_EQ(0, WEXITSTATUS(status));
+
+ delete[] buf;
+}
+
+/*
+ * Execute a file stored on a fusefs file system that does not implement
+ * FUSE_OPEN
+ */
+TEST_F(Fexecv, close_noopen)
+{
+ const static char FULLPATH[] = "mountpoint/true";
+ const static char RELPATH[] = "true";
+ const static size_t BUFSIZE = 16384;
+ FILE *true_file;
+ uint64_t ino = 42;
+ size_t true_len;
+ int status;
+ char *buf;
+
+ buf = new char[BUFSIZE];
+ true_file = fopen("/usr/bin/true", "r");
+ ASSERT_TRUE(true_file) << strerror(errno);
+ true_len = fread(buf, 1, BUFSIZE, true_file);
+ ASSERT_LT(true_len, BUFSIZE) << "Must increase BUFSIZE";
+ fclose(true_file);
+
+ fork(false, &status, [&] {
+ expect_lookup(RELPATH, ino, S_IFREG | 0755, true_len, 1,
+ UINT64_MAX);
+ EXPECT_CALL(*m_mock, process(
+ ResultOf([=](auto in) {
+ return (in.header.opcode == FUSE_OPEN &&
+ in.header.nodeid == ino);
+ }, Eq(true)),
+ _)
+ ).Times(1)
+ .WillOnce(Invoke(ReturnErrno(ENOSYS)));
+ expect_read(ino, 0, true_len, true_len, buf, -1, 0);
+ expect_flush(ino, 1, ReturnErrno(ENOSYS));
+ }, [&] {
+ char *const argv[] = {__DECONST(char *, "true"), NULL};
+ char *const env[] = {NULL};
+ int fd;
+
+ fd = open(FULLPATH, O_EXEC);
+ if (fd < 0) {
+ fprintf(stderr, "open: %s\n", strerror(errno));
+ return 1;
+ }
+ fexecve(fd, argv, env);
+ fprintf(stderr, "execv: %s\n", strerror(errno));
+ return 1;
+ });
+ ASSERT_EQ(0, WEXITSTATUS(status));
+
+ delete[] buf;
+}
+
+/*
+ * When execute a file with a dirty atime, fusefs must send FUSE_SETATTR to the
+ * daemon during close.
+ */
+TEST_F(FexecvDefaultPermissions, atime)
+{
+ const static char FULLPATH[] = "mountpoint/true";
+ const static char RELPATH[] = "true";
+ const static size_t BUFSIZE = 16384;
+ FILE *true_file;
+ uint64_t ino = 42;
+ size_t true_len;
+ int status;
+ char *buf;
+
+ buf = new char[BUFSIZE];
+ true_file = fopen("/usr/bin/true", "r");
+ ASSERT_TRUE(true_file) << strerror(errno);
+ true_len = fread(buf, 1, BUFSIZE, true_file);
+ ASSERT_LT(true_len, BUFSIZE) << "Must increase BUFSIZE";
+ fclose(true_file);
+
+ fork(false, &status, [&] {
+ expect_lookup(RELPATH, ino, S_IFREG | 0777, true_len, 1,
+ UINT64_MAX);
+ EXPECT_CALL(*m_mock, process(
+ ResultOf([=](auto in) {
+ return (in.header.opcode == FUSE_GETATTR &&
+ in.header.nodeid == FUSE_ROOT_ID);
+ }, Eq(true)),
+ _)
+ ).WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out) {
+ SET_OUT_HEADER_LEN(out, attr);
+ out.body.attr.attr.ino = FUSE_ROOT_ID;
+ out.body.attr.attr.mode = S_IFDIR | 0777;
+ out.body.attr.attr.size = 0;
+ out.body.attr.attr_valid = UINT64_MAX;
+ })));
+ EXPECT_CALL(*m_mock, process(
+ ResultOf([=](auto in) {
+ return (in.header.opcode == FUSE_OPEN &&
+ in.header.nodeid == ino);
+ }, Eq(true)),
+ _)
+ ).Times(1)
+ .WillOnce(Invoke(ReturnErrno(ENOSYS)));
+ expect_read(ino, 0, true_len, true_len, buf, -1, 0);
+ expect_flush(ino, 1, ReturnErrno(ENOSYS));
+ EXPECT_CALL(*m_mock, process(
+ ResultOf([&](auto in) {
+ return (in.header.opcode == FUSE_SETATTR &&
+ in.header.nodeid == ino &&
+ in.body.setattr.valid == FATTR_ATIME);
+ }, Eq(true)),
+ _)
+ ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
+ SET_OUT_HEADER_LEN(out, attr);
+ out.body.attr.attr.ino = ino;
+ out.body.attr.attr.mode = S_IFREG | 0777;
+ })));
+ }, [&] {
+ char *const argv[] = {__DECONST(char *, "true"), NULL};
+ char *const env[] = {NULL};
+ char buf[8];
+ int fd;
+
+ /* Note that fexecve doesn't actually require O_EXEC */
+ fd = open(FULLPATH, O_RDONLY);
+ if (fd < 0) {
+ fprintf(stderr, "open: %s\n", strerror(errno));
+ return 1;
+ }
+ /* Read a few bytes, just to dirty the file's atime */
+ if (read(fd, buf, sizeof(buf)) < 0) {
+ fprintf(stderr, "read: %s\n", strerror(errno));
+ return 1;
+ }
+ fexecve(fd, argv, env);
+ fprintf(stderr, "execv: %s\n", strerror(errno));
+ return 1;
+ });
+ ASSERT_EQ(0, WEXITSTATUS(status));
+
+ delete[] buf;
+}
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
@@ -300,7 +300,7 @@
in.body.read.offset,
in.body.read.size);
if (verbosity > 1)
- printf(" flags=%#x", in.body.read.flags);
+ printf(" fh=%#" PRIx64 " flags=%#x", in.body.read.fh, in.body.read.flags);
break;
case FUSE_READDIR:
printf(" fh=%#" PRIx64 " offset=%" PRIu64 " size=%u",
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
@@ -386,7 +386,8 @@
in.body.read.size == isize &&
(flags == -1 ?
(in.body.read.flags == O_RDONLY ||
- in.body.read.flags == O_RDWR)
+ in.body.read.flags == O_RDWR ||
+ in.body.read.flags == O_EXEC)
: in.body.read.flags == (uint32_t)flags));
}, Eq(true)),
_)

File Metadata

Mime Type
text/plain
Expires
Tue, Jul 14, 2:32 AM (15 h, 17 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
35052851
Default Alt Text
D57536.diff (16 KB)

Event Timeline