git: e8449c0e0fcb - main - fusefs: respect the server's FUSE_SETXATTR_EXT flag

From: Alan Somers <asomers_at_FreeBSD.org>
Date: Mon, 03 Nov 2025 17:34:03 UTC
The branch main has been updated by asomers:

URL: https://cgit.FreeBSD.org/src/commit/?id=e8449c0e0fcb8a3eb5872cbee5c3dde4b05a5f50

commit e8449c0e0fcb8a3eb5872cbee5c3dde4b05a5f50
Author:     Alan Somers <asomers@FreeBSD.org>
AuthorDate: 2025-10-26 18:06:51 +0000
Commit:     Alan Somers <asomers@FreeBSD.org>
CommitDate: 2025-11-03 17:14:35 +0000

    fusefs: respect the server's FUSE_SETXATTR_EXT flag
    
    FUSE protocol 7.33 extended the FUSE_SETXATTR request format.  But the
    extension is optional.  The server must opt-in by setting the
    FUSE_SETXATTR_IN flag during FUSE_INIT.  We were wrongly using the
    extended format for any server using protocol 7.33 or later.
    
    PR:             290547
    Co-authored-by: CismonX <admin@cismon.net>
    Fixes:          d5e3cf41e89 ("fusefs: Upgrade FUSE protocol to version 7.33")
    MFC after:      3 days
---
 sys/fs/fuse/fuse_internal.c  |  5 +++-
 sys/fs/fuse/fuse_ipc.h       |  1 +
 sys/fs/fuse/fuse_vnops.c     |  4 +--
 tests/sys/fs/fusefs/xattr.cc | 67 ++++++++++++++++++++++++++++++++++----------
 4 files changed, 59 insertions(+), 18 deletions(-)

diff --git a/sys/fs/fuse/fuse_internal.c b/sys/fs/fuse/fuse_internal.c
index 61fe2ed032f6..eba0a8a79ff3 100644
--- a/sys/fs/fuse/fuse_internal.c
+++ b/sys/fs/fuse/fuse_internal.c
@@ -1063,6 +1063,8 @@ fuse_internal_init_callback(struct fuse_ticket *tick, struct uio *uio)
 	if (!fuse_libabi_geq(data, 7, 28))
 		fsess_set_notimpl(data->mp, FUSE_COPY_FILE_RANGE);
 
+	if (fuse_libabi_geq(data, 7, 33) && (fiio->flags & FUSE_SETXATTR_EXT))
+		data->dataflags |= FSESS_SETXATTR_EXT;
 out:
 	if (err) {
 		fdata_set_dead(data);
@@ -1115,7 +1117,8 @@ fuse_internal_send_init(struct fuse_data *data, struct thread *td)
 	 */
 	fiii->flags = FUSE_ASYNC_READ | FUSE_POSIX_LOCKS | FUSE_EXPORT_SUPPORT
 		| FUSE_BIG_WRITES | FUSE_WRITEBACK_CACHE
-		| FUSE_NO_OPEN_SUPPORT | FUSE_NO_OPENDIR_SUPPORT;
+		| FUSE_NO_OPEN_SUPPORT | FUSE_NO_OPENDIR_SUPPORT
+		| FUSE_SETXATTR_EXT;
 
 	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 3bfc859dbac9..d9d79f38c269 100644
--- a/sys/fs/fuse/fuse_ipc.h
+++ b/sys/fs/fuse/fuse_ipc.h
@@ -243,6 +243,7 @@ struct fuse_data {
 #define FSESS_MNTOPTS_MASK	( \
 	FSESS_DAEMON_CAN_SPY | FSESS_PUSH_SYMLINKS_IN | \
 	FSESS_DEFAULT_PERMISSIONS | FSESS_INTR)
+#define	FSESS_SETXATTR_EXT	  0x8000000 /* extended fuse_setxattr_in */
 
 extern int fuse_data_cache_mode;
 
diff --git a/sys/fs/fuse/fuse_vnops.c b/sys/fs/fuse/fuse_vnops.c
index 97aa23bfb0b0..6c79e646d2f3 100644
--- a/sys/fs/fuse/fuse_vnops.c
+++ b/sys/fs/fuse/fuse_vnops.c
@@ -2777,7 +2777,7 @@ fuse_vnop_setextattr(struct vop_setextattr_args *ap)
 	    strlen(ap->a_name) + 1;
 
 	/* older FUSE servers  use a smaller fuse_setxattr_in struct*/
-	if (fuse_libabi_geq(fuse_get_mpdata(mp), 7, 33))
+	if (fuse_get_mpdata(mp)->dataflags & FSESS_SETXATTR_EXT)
 		struct_size = sizeof(*set_xattr_in);
 
 	fdisp_init(&fdi, len + struct_size + uio->uio_resid);
@@ -2786,7 +2786,7 @@ fuse_vnop_setextattr(struct vop_setextattr_args *ap)
 	set_xattr_in = fdi.indata;
 	set_xattr_in->size = uio->uio_resid;
 
-	if (fuse_libabi_geq(fuse_get_mpdata(mp), 7, 33)) {
+	if (fuse_get_mpdata(mp)->dataflags & FSESS_SETXATTR_EXT) {
 		set_xattr_in->setxattr_flags = 0;
 		set_xattr_in->padding = 0;
 	}
diff --git a/tests/sys/fs/fusefs/xattr.cc b/tests/sys/fs/fusefs/xattr.cc
index 0ab203c96254..afeacd4a249e 100644
--- a/tests/sys/fs/fusefs/xattr.cc
+++ b/tests/sys/fs/fusefs/xattr.cc
@@ -100,7 +100,11 @@ void expect_removexattr(uint64_t ino, const char *attr, int error)
 	).WillOnce(Invoke(ReturnErrno(error)));
 }
 
-void expect_setxattr(uint64_t ino, const char *attr, const char *value,
+/*
+ * Expect a FUSE_SETXATTR request in the format used by protocol 7.33 and
+ * later, with the FUSE_SETXATTR_EXT bit set.
+ */
+void expect_setxattr_ext(uint64_t ino, const char *attr, const char *value,
 	ProcessMockerT r)
 {
 	EXPECT_CALL(*m_mock, process(
@@ -119,16 +123,10 @@ void expect_setxattr(uint64_t ino, const char *attr, const char *value,
 	).WillOnce(Invoke(r));
 }
 
-};
-
-class Xattr_7_32:public FuseTest {
-public:
-virtual void SetUp()
-{
-	m_kernel_minor_version = 32;
-	FuseTest::SetUp();
-}
-
+/*
+ * Expect a FUSE_SETXATTR request in the format used by protocol 7.32 and
+ * earlier.
+ */
 void expect_setxattr_7_32(uint64_t ino, const char *attr, const char *value,
 	ProcessMockerT r)
 {
@@ -148,6 +146,15 @@ void expect_setxattr_7_32(uint64_t ino, const char *attr, const char *value,
 }
 };
 
+class Xattr_7_32: public Xattr {
+public:
+virtual void SetUp()
+{
+	m_kernel_minor_version = 32;
+	Xattr::SetUp();
+}
+};
+
 class Getxattr: public Xattr {};
 
 class Listxattr: public Xattr {};
@@ -182,6 +189,13 @@ void TearDown() {
 
 class Removexattr: public Xattr {};
 class Setxattr: public Xattr {};
+class SetxattrExt: public Setxattr {
+public:
+virtual void SetUp() {
+	m_init_flags |= FUSE_SETXATTR_EXT;
+	Setxattr::SetUp();
+}
+};
 class Setxattr_7_32:public Xattr_7_32 {};
 class RofsXattr: public Xattr {
 public:
@@ -773,7 +787,7 @@ TEST_F(Setxattr, enosys)
 	ssize_t r;
 
 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 2);
-	expect_setxattr(ino, "user.foo", value, ReturnErrno(ENOSYS));
+	expect_setxattr_7_32(ino, "user.foo", value, ReturnErrno(ENOSYS));
 
 	r = extattr_set_file(FULLPATH, ns, "foo", (const void*)value,
 		value_len);
@@ -800,7 +814,7 @@ TEST_F(Setxattr, enotsup)
 	ssize_t r;
 
 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
-	expect_setxattr(ino, "user.foo", value, ReturnErrno(ENOTSUP));
+	expect_setxattr_7_32(ino, "user.foo", value, ReturnErrno(ENOTSUP));
 
 	r = extattr_set_file(FULLPATH, ns, "foo", (const void*)value,
 		value_len);
@@ -820,7 +834,7 @@ TEST_F(Setxattr, user)
 	ssize_t r;
 
 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
-	expect_setxattr(ino, "user.foo", value, ReturnErrno(0));
+	expect_setxattr_7_32(ino, "user.foo", value, ReturnErrno(0));
 
 	r = extattr_set_file(FULLPATH, ns, "foo", (const void*)value,
 		value_len);
@@ -839,7 +853,7 @@ TEST_F(Setxattr, system)
 	ssize_t r;
 
 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
-	expect_setxattr(ino, "system.foo", value, ReturnErrno(0));
+	expect_setxattr_7_32(ino, "system.foo", value, ReturnErrno(0));
 
 	r = extattr_set_file(FULLPATH, ns, "foo", (const void*)value,
 		value_len);
@@ -847,6 +861,10 @@ TEST_F(Setxattr, system)
 }
 
 
+/*
+ * For servers using protocol 7.32 and older, the kernel should use the older
+ * FUSE_SETXATTR format.
+ */
 TEST_F(Setxattr_7_32, ok)
 {
 	uint64_t ino = 42;
@@ -863,6 +881,25 @@ TEST_F(Setxattr_7_32, ok)
 	ASSERT_EQ(value_len, r) << strerror(errno);
 }
 
+/*
+ * Successfully set a user attribute using the extended format
+ */
+TEST_F(SetxattrExt, user)
+{
+	uint64_t ino = 42;
+	const char value[] = "whatever";
+	ssize_t value_len = strlen(value) + 1;
+	int ns = EXTATTR_NAMESPACE_USER;
+	ssize_t r;
+
+	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
+	expect_setxattr_ext(ino, "user.foo", value, ReturnErrno(0));
+
+	r = extattr_set_file(FULLPATH, ns, "foo", (const void*)value,
+		value_len);
+	ASSERT_EQ(value_len, r) << strerror(errno);
+}
+
 TEST_F(RofsXattr, deleteextattr_erofs)
 {
 	uint64_t ino = 42;