From 670c07cf1972efc4962adc9033fa9f5c0e9f0494 Mon Sep 17 00:00:00 2001 From: Horst Birthelmer Date: Fri, 16 Oct 2026 13:11:36 +0200 Subject: [PATCH] fuse: bound the attribute timeout of an inode a DLM lock was taken on Under DLM the server's attr_valid can keep the attributes of a locked inode valid indefinitely. Once a DLM lock was taken on an inode, cap its i_time to dlm_attr_timeout seconds so a GETATTR goes out at most that long after the lock. The first attribute reply from the server after the lock ends the bound; a reply to a request sent before it is discarded by bumping attr_version. Default 30s, module parameter, 0 keeps the server's timeout. Signed-off-by: Horst Birthelmer --- fs/fuse/file.c | 2 ++ fs/fuse/fuse_dlm_cache.c | 2 ++ fs/fuse/fuse_i.h | 9 +++++++++ fs/fuse/inode.c | 36 ++++++++++++++++++++++++++++++++++++ 4 files changed, 49 insertions(+) diff --git a/fs/fuse/file.c b/fs/fuse/file.c index f8f47b448f66e8..80751f7a64215d 100644 --- a/fs/fuse/file.c +++ b/fs/fuse/file.c @@ -3497,6 +3497,8 @@ static int fuse_get_page_mkwrite_lock(struct file *file, loff_t offset, size_t l fuse_abort_conn(fc); err = -EINVAL; } + if (!err && fc->dlm) + fuse_dlm_attr_timeout(get_fuse_inode(inode)); return err; } diff --git a/fs/fuse/fuse_dlm_cache.c b/fs/fuse/fuse_dlm_cache.c index e6080f312cc047..c641f1cb8ddbcf 100644 --- a/fs/fuse/fuse_dlm_cache.c +++ b/fs/fuse/fuse_dlm_cache.c @@ -827,6 +827,8 @@ int fuse_get_dlm_lock(struct file *file, loff_t offset, if (err) return err; + fuse_dlm_attr_timeout(fi); + if (inarg.start < outarg.start || inarg.end > outarg.end) { /* fuse server is seriously broken */ pr_warn("fuse: dlm lock request for %llu:%llu returned %llu:%llu bytes\n", diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index 72ecb2bfa5e4ab..5fa86547ed89cd 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -338,6 +338,12 @@ enum { * See fuse_reverse_inval_inode()/fuse_file_io_open(). */ FUSE_I_FORCE_DIO, + /* + * A DLM lock was taken on this inode and no attribute reply has + * arrived since: i_time is bounded to dlm_attr_timeout seconds from + * the lock, see fuse_dlm_attr_timeout(). + */ + FUSE_I_DLM_LOCKED, }; struct fuse_conn; @@ -1633,6 +1639,9 @@ struct inode *fuse_ilookup(struct fuse_conn *fc, u64 nodeid, int fuse_reverse_inval_inode(struct fuse_conn *fc, u64 nodeid, loff_t offset, loff_t len); +/* A DLM lock was taken on @fi: bound its attribute timeout until the next reply */ +void fuse_dlm_attr_timeout(struct fuse_inode *fi); + /** * File-system tells the kernel to invalidate parent attributes and * the dentry matching parent/name. diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c index 2d01d2bf3c549d..5a84bf85c045c3 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c @@ -55,6 +55,39 @@ module_param(enable_notify_dio, bool, 0644); MODULE_PARM_DESC(enable_notify_dio, "Latch a contended inode to direct IO on an invalidation notify storm"); +/* + * Attribute timeout of an inode once a DLM lock was taken on it, in + * seconds: i_time is bounded to it until the next attribute reply from + * the server, so a GETATTR goes out at most this long after the lock. + * 0 leaves the server's timeout alone. + */ +static unsigned int __read_mostly dlm_attr_timeout = 30; +module_param(dlm_attr_timeout, uint, 0644); +MODULE_PARM_DESC(dlm_attr_timeout, + "Seconds until the attributes of an inode a DLM lock was taken on are fetched again (0 = server's timeout)"); + +void fuse_dlm_attr_timeout(struct fuse_inode *fi) +{ + struct fuse_conn *fc = get_fuse_conn(&fi->inode); + unsigned int timeout = READ_ONCE(dlm_attr_timeout); + u64 valid; + + if (!timeout) + return; + valid = get_jiffies_64() + secs_to_jiffies(timeout); + + spin_lock(&fi->lock); + /* + * Only a reply to a request sent after the lock may end the bound: + * one to an earlier request is discarded by fuse_change_attributes_i(). + */ + if (!test_and_set_bit(FUSE_I_DLM_LOCKED, &fi->state)) + fi->attr_version = atomic64_inc_return(&fc->attr_version); + if (time_after64(fi->i_time, valid)) + fi->i_time = valid; + spin_unlock(&fi->lock); +} + static struct kmem_cache *fuse_inode_cachep; struct list_head fuse_conn_list; DEFINE_MUTEX(fuse_mutex); @@ -402,6 +435,9 @@ void fuse_change_attributes_common(struct inode *inode, struct fuse_attr *attr, lockdep_assert_held(&fi->lock); + /* The first attribute reply after a DLM lock ends the bounded timeout */ + clear_bit(FUSE_I_DLM_LOCKED, &fi->state); + if (sx) { return fuse_change_attributes_common_sx(inode, attr, sx, attr_valid, cache_mask,