From: Darrick J. Wong <djwong@xxxxxxxxxx> There are three inode flags (immutable, append, sync) that are enforced by the VFS. Whenever we go around setting iflags, let's update the VFS state so that they actually work. Signed-off-by: "Darrick J. Wong" <djwong@xxxxxxxxxx> --- fs/fuse/fuse_i.h | 1 + fs/fuse/fuse_trace.h | 23 +++++++++++++ fs/fuse/dir.c | 1 + fs/fuse/inode.c | 1 + fs/fuse/ioctl.c | 90 ++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 116 insertions(+) diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index a710c56b205e30..f7a7d8ad641d5b 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -1588,6 +1588,7 @@ long fuse_file_compat_ioctl(struct file *file, unsigned int cmd, int fuse_fileattr_get(struct dentry *dentry, struct file_kattr *fa); int fuse_fileattr_set(struct mnt_idmap *idmap, struct dentry *dentry, struct file_kattr *fa); +void fuse_fileattr_init(struct inode *inode, const struct fuse_attr *attr); /* iomode.c */ int fuse_file_cached_io_open(struct inode *inode, struct fuse_file *ff); diff --git a/fs/fuse/fuse_trace.h b/fs/fuse/fuse_trace.h index 80af541a54c5bd..aea9ea0835d497 100644 --- a/fs/fuse/fuse_trace.h +++ b/fs/fuse/fuse_trace.h @@ -176,6 +176,29 @@ TRACE_EVENT(fuse_request_end, __entry->unique, __entry->len, __entry->error) ); +TRACE_EVENT(fuse_fileattr_update_inode, + TP_PROTO(const struct inode *inode, unsigned int old_iflags), + + TP_ARGS(inode, old_iflags), + + TP_STRUCT__entry( + FUSE_INODE_FIELDS + __field(unsigned int, old_iflags) + __field(unsigned int, new_iflags) + ), + + TP_fast_assign( + FUSE_INODE_ASSIGN(inode, fi, fm); + __entry->old_iflags = old_iflags; + __entry->new_iflags = inode->i_flags; + ), + + TP_printk(FUSE_INODE_FMT " old_iflags 0x%x iflags 0x%x", + FUSE_INODE_PRINTK_ARGS, + __entry->old_iflags, + __entry->new_iflags) +); + #ifdef CONFIG_FUSE_BACKING #define FUSE_BACKING_PASSTHROUGH (1U << 0) #define FUSE_BACKING_IOMAP (1U << 1) diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index 05cb79beb8e426..d2f9bcccd776f0 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -1254,6 +1254,7 @@ static void fuse_fillattr(struct mnt_idmap *idmap, struct inode *inode, blkbits = inode->i_sb->s_blocksize_bits; stat->blksize = 1 << blkbits; + generic_fill_statx_attr(inode, stat); } static void fuse_statx_to_attr(struct fuse_statx *sx, struct fuse_attr *attr) diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c index 18dc9492d19174..b1793df3cbbd1a 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c @@ -524,6 +524,7 @@ struct inode *fuse_iget(struct super_block *sb, u64 nodeid, inode->i_flags |= S_NOCMTIME; inode->i_generation = generation; fuse_init_inode(inode, attr, fc); + fuse_fileattr_init(inode, attr); unlock_new_inode(inode); } else if (fuse_stale_inode(inode, generation, attr)) { /* nodeid was reused, any I/O on the old inode should fail */ diff --git a/fs/fuse/ioctl.c b/fs/fuse/ioctl.c index f5f7d806262cdf..c320ea80cb3db8 100644 --- a/fs/fuse/ioctl.c +++ b/fs/fuse/ioctl.c @@ -4,6 +4,7 @@ */ #include "fuse_i.h" +#include "fuse_trace.h" #include <linux/uio.h> #include <linux/compat.h> @@ -502,6 +503,92 @@ static void fuse_priv_ioctl_cleanup(struct inode *inode, struct fuse_file *ff) fuse_file_release(inode, ff, O_RDONLY, NULL, S_ISDIR(inode->i_mode)); } +static inline void update_iflag(struct inode *inode, unsigned int iflag, + bool set) +{ + if (set) + inode->i_flags |= iflag; + else + inode->i_flags &= ~iflag; +} + +static void fuse_fileattr_update_inode(struct inode *inode, + const struct file_kattr *fa) +{ + unsigned int old_iflags = inode->i_flags; + + /* + * Prior to iomap, the fuse driver sent all file IO operations to the + * fuse server, which was wholly responsible for enforcing the + * immutable and append bits. With iomap, we let more of the kernel IO + * path stay within the kernel, so we actually have to set the VFS + * flags now so that the enforcement can take place inside the kernel. + */ + if (!fuse_has_iomap(inode)) + return; + + /* + * Configure VFS enforcement of the three inode flags that we support. + * XXX: still need to figure out what's going on wrt NOATIME in fuse. + */ + if (fa->flags_valid) { + update_iflag(inode, S_SYNC, fa->flags & FS_SYNC_FL); + update_iflag(inode, S_IMMUTABLE, fa->flags & FS_IMMUTABLE_FL); + update_iflag(inode, S_APPEND, fa->flags & FS_APPEND_FL); + } else if (fa->fsx_xflags) { + update_iflag(inode, S_SYNC, fa->fsx_xflags & FS_XFLAG_SYNC); + update_iflag(inode, S_IMMUTABLE, + fa->fsx_xflags & FS_XFLAG_IMMUTABLE); + update_iflag(inode, S_APPEND, fa->fsx_xflags & FS_XFLAG_APPEND); + } + + trace_fuse_fileattr_update_inode(inode, old_iflags); + + if (old_iflags != inode->i_flags) + fuse_invalidate_attr(inode); +} + +void fuse_fileattr_init(struct inode *inode, const struct fuse_attr *attr) +{ + struct file_kattr fa; + struct fsxattr xfa = { }; + struct fuse_file *ff; + struct fuse_conn *fc = get_fuse_conn(inode); + unsigned int flags = 0; + int err; + + if (!fuse_has_iomap(inode)) + return; + + /* + * Don't do this when we're setting up the root inode because the + * connection workers haven't been set up yet. + */ + if (attr->ino == fc->root_nodeid && attr->blksize == 0) + return; + + ff = fuse_priv_ioctl_prepare(inode); + if (IS_ERR(ff)) + return; + + err = fuse_priv_ioctl(inode, ff, FS_IOC_FSGETXATTR, &xfa, sizeof(xfa)); + if (!err) { + fileattr_fill_xflags(&fa, xfa.fsx_xflags); + fuse_fileattr_update_inode(inode, &fa); + goto cleanup; + } + + err = fuse_priv_ioctl(inode, ff, FS_IOC_GETFLAGS, &flags, sizeof(flags)); + if (!err) { + fileattr_fill_flags(&fa, flags); + fuse_fileattr_update_inode(inode, &fa); + goto cleanup; + } + +cleanup: + fuse_priv_ioctl_cleanup(inode, ff); +} + int fuse_fileattr_get(struct dentry *dentry, struct file_kattr *fa) { struct inode *inode = d_inode(dentry); @@ -574,7 +661,10 @@ int fuse_fileattr_set(struct mnt_idmap *idmap, err = fuse_priv_ioctl(inode, ff, FS_IOC_FSSETXATTR, &xfa, sizeof(xfa)); + if (err) + goto cleanup; } + fuse_fileattr_update_inode(inode, fa); cleanup: fuse_priv_ioctl_cleanup(inode, ff);