[PATCH 5/8] fuse: implement file attributes mask for statx

Darrick J. Wong
2025-09-16T00:25:22Z

Actually copy the attributes/attributes_mask from userspace. Ignore
file attributes bits that the VFS sets (or doesn't set) on its own.

Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
Reviewed-by: Joanne Koong <joannelkoong@gmail.com>
---
fs/fuse/fuse_i.h | 37 +++++++++++++++++++++++++++++++++++++
fs/fuse/dir.c | 4 ++++
fs/fuse/inode.c | 3 +++
3 files changed, 44 insertions(+)

diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
index e13e8270f4f58d..52776b77efc0e4 100644
--- a/fs/fuse/fuse_i.h
+++ b/fs/fuse/fuse_i.h
@@ -140,6 +140,10 @@ struct fuse_inode {
/** Version of last attribute change */
u64 attr_version;

+ /** statx file attributes */
+ u64 statx_attributes;
+ u64 statx_attributes_mask;
+
union {
/* read/write io cache (regular file only) */
struct {
@@ -1221,6 +1225,39 @@ void fuse_change_attributes_common(struct inode *inode, struct fuse_attr *attr,
u64 attr_valid, u32 cache_mask,
u64 evict_ctr);

+/*
+ * These statx attribute flags are set by the VFS so mask them out of replies
+ * from the fuse server for local filesystems. Nonlocal filesystems are
+ * responsible for enforcing and advertising these flags themselves.
+ */
+#define FUSE_STATX_LOCAL_VFS_ATTRIBUTES (STATX_ATTR_IMMUTABLE | \
+ STATX_ATTR_APPEND)
+
+/*
+ * These statx attribute flags are set by the VFS so mask them out of replies
+ * from the fuse server.
+ */
+#define FUSE_STATX_VFS_ATTRIBUTES (STATX_ATTR_AUTOMOUNT | STATX_ATTR_DAX | \
+ STATX_ATTR_MOUNT_ROOT)
+
+static inline u64 fuse_statx_attributes_mask(const struct fuse_conn *fc,
+ const struct fuse_statx *sx)
+{
+ if (fc->local_fs)
+ return sx->attributes_mask & ~(FUSE_STATX_VFS_ATTRIBUTES |
+ FUSE_STATX_LOCAL_VFS_ATTRIBUTES);
+ return sx->attributes_mask & ~FUSE_STATX_VFS_ATTRIBUTES;
+}
+
+static inline u64 fuse_statx_attributes(const struct fuse_conn *fc,
+ const struct fuse_statx *sx)
+{
+ if (fc->local_fs)
+ return sx->attributes & ~(FUSE_STATX_VFS_ATTRIBUTES |
+ FUSE_STATX_LOCAL_VFS_ATTRIBUTES);
+ return sx->attributes & ~FUSE_STATX_VFS_ATTRIBUTES;
+}
+
u32 fuse_get_cache_mask(struct inode *inode);

/**
diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c
index 5c569c3cb53f3d..a7f47e43692f1c 100644
--- a/fs/fuse/dir.c
+++ b/fs/fuse/dir.c
@@ -1278,6 +1278,8 @@ static int fuse_do_statx(struct mnt_idmap *idmap, struct inode *inode,
stat->result_mask = sx->mask & (STATX_BASIC_STATS | STATX_BTIME);
stat->btime.tv_sec = sx->btime.tv_sec;
stat->btime.tv_nsec = min_t(u32, sx->btime.tv_nsec, NSEC_PER_SEC - 1);
+ stat->attributes |= fuse_statx_attributes(fm->fc, sx);
+ stat->attributes_mask |= fuse_statx_attributes_mask(fm->fc, sx);
fuse_fillattr(idmap, inode, &attr, stat);
stat->result_mask |= STATX_TYPE;
}
@@ -1382,6 +1384,8 @@ static int fuse_update_get_attr(struct mnt_idmap *idmap, struct inode *inode,
stat->btime = fi->i_btime;
stat->result_mask |= STATX_BTIME;
}
+ stat->attributes = fi->statx_attributes;
+ stat->attributes_mask = fi->statx_attributes_mask;
}

return err;
diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c
index c8dd0bcb7e6f9f..55db991bb6b8c1 100644
--- a/fs/fuse/inode.c
+++ b/fs/fuse/inode.c
@@ -287,6 +287,9 @@ void fuse_change_attributes_common(struct inode *inode, struct fuse_attr *attr,
fi->i_btime.tv_sec = sx->btime.tv_sec;
fi->i_btime.tv_nsec = sx->btime.tv_nsec;
}
+
+ fi->statx_attributes = fuse_statx_attributes(fc, sx);
+ fi->statx_attributes_mask = fuse_statx_attributes_mask(fc, sx);
}

if (attr->blksize)