]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
fuse: add more control over cache invalidation behaviour
authorLuis Henriques <luis@igalia.com>
Wed, 26 Feb 2025 09:14:51 +0000 (09:14 +0000)
committerMiklos Szeredi <mszeredi@redhat.com>
Tue, 15 Apr 2025 10:56:40 +0000 (12:56 +0200)
Currently userspace is able to notify the kernel to invalidate the cache
for an inode.  This means that, if all the inodes in a filesystem need to
be invalidated, then userspace needs to iterate through all of them and do
this kernel notification separately.

This patch adds the concept of 'epoch': each fuse connection will have the
current epoch initialized and every new dentry will have it's d_time set to
the current epoch value.  A new operation will then allow userspace to
increment the epoch value.  Every time a dentry is d_revalidate()'ed, it's
epoch is compared with the current connection epoch and invalidated if it's
value is different.

Signed-off-by: Luis Henriques <luis@igalia.com>
Tested-by: Laura Promberger <laura.promberger@cern.ch>
Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
fs/fuse/dev.c
fs/fuse/dir.c
fs/fuse/fuse_i.h
fs/fuse/inode.c
fs/fuse/readdir.c
include/uapi/linux/fuse.h

index 6dcbaa218b7a166adc922f533b87f7e4f17dcb6e..00888befb5c8e1731c8efb81b5d42832bdeea2fe 100644 (file)
@@ -2021,6 +2021,19 @@ static int fuse_notify_resend(struct fuse_conn *fc)
        return 0;
 }
 
+/*
+ * Increments the fuse connection epoch.  This will result of dentries from
+ * previous epochs to be invalidated.
+ *
+ * XXX optimization: add call to shrink_dcache_sb()?
+ */
+static int fuse_notify_inc_epoch(struct fuse_conn *fc)
+{
+       atomic_inc(&fc->epoch);
+
+       return 0;
+}
+
 static int fuse_notify(struct fuse_conn *fc, enum fuse_notify_code code,
                       unsigned int size, struct fuse_copy_state *cs)
 {
@@ -2049,6 +2062,9 @@ static int fuse_notify(struct fuse_conn *fc, enum fuse_notify_code code,
        case FUSE_NOTIFY_RESEND:
                return fuse_notify_resend(fc);
 
+       case FUSE_NOTIFY_INC_EPOCH:
+               return fuse_notify_inc_epoch(fc);
+
        default:
                fuse_copy_finish(cs);
                return -EINVAL;
index 83ac192e7fdd1929fa42b583c14ce75c5672e3ca..1fb0b15a608821588eb0fa76355da0c94ff04e74 100644 (file)
@@ -200,9 +200,14 @@ static int fuse_dentry_revalidate(struct inode *dir, const struct qstr *name,
 {
        struct inode *inode;
        struct fuse_mount *fm;
+       struct fuse_conn *fc;
        struct fuse_inode *fi;
        int ret;
 
+       fc = get_fuse_conn_super(dir->i_sb);
+       if (entry->d_time < atomic_read(&fc->epoch))
+               goto invalid;
+
        inode = d_inode_rcu(entry);
        if (inode && fuse_is_bad(inode))
                goto invalid;
@@ -415,16 +420,20 @@ int fuse_lookup_name(struct super_block *sb, u64 nodeid, const struct qstr *name
 static struct dentry *fuse_lookup(struct inode *dir, struct dentry *entry,
                                  unsigned int flags)
 {
-       int err;
        struct fuse_entry_out outarg;
+       struct fuse_conn *fc;
        struct inode *inode;
        struct dentry *newent;
+       int err, epoch;
        bool outarg_valid = true;
        bool locked;
 
        if (fuse_is_bad(dir))
                return ERR_PTR(-EIO);
 
+       fc = get_fuse_conn_super(dir->i_sb);
+       epoch = atomic_read(&fc->epoch);
+
        locked = fuse_lock_inode(dir);
        err = fuse_lookup_name(dir->i_sb, get_node_id(dir), &entry->d_name,
                               &outarg, &inode);
@@ -446,6 +455,7 @@ static struct dentry *fuse_lookup(struct inode *dir, struct dentry *entry,
                goto out_err;
 
        entry = newent ? newent : entry;
+       entry->d_time = epoch;
        if (outarg_valid)
                fuse_change_entry_timeout(entry, &outarg);
        else
@@ -619,7 +629,6 @@ static int fuse_create_open(struct mnt_idmap *idmap, struct inode *dir,
                            struct dentry *entry, struct file *file,
                            unsigned int flags, umode_t mode, u32 opcode)
 {
-       int err;
        struct inode *inode;
        struct fuse_mount *fm = get_fuse_mount(dir);
        FUSE_ARGS(args);
@@ -629,11 +638,13 @@ static int fuse_create_open(struct mnt_idmap *idmap, struct inode *dir,
        struct fuse_entry_out outentry;
        struct fuse_inode *fi;
        struct fuse_file *ff;
+       int epoch, err;
        bool trunc = flags & O_TRUNC;
 
        /* Userspace expects S_IFREG in create mode */
        BUG_ON((mode & S_IFMT) != S_IFREG);
 
+       epoch = atomic_read(&fm->fc->epoch);
        forget = fuse_alloc_forget();
        err = -ENOMEM;
        if (!forget)
@@ -702,6 +713,7 @@ static int fuse_create_open(struct mnt_idmap *idmap, struct inode *dir,
        }
        kfree(forget);
        d_instantiate(entry, inode);
+       entry->d_time = epoch;
        fuse_change_entry_timeout(entry, &outentry);
        fuse_dir_changed(dir);
        err = generic_file_open(inode, file);
@@ -788,12 +800,14 @@ static struct dentry *create_new_entry(struct mnt_idmap *idmap, struct fuse_moun
        struct fuse_entry_out outarg;
        struct inode *inode;
        struct dentry *d;
-       int err;
        struct fuse_forget_link *forget;
+       int epoch, err;
 
        if (fuse_is_bad(dir))
                return ERR_PTR(-EIO);
 
+       epoch = atomic_read(&fm->fc->epoch);
+
        forget = fuse_alloc_forget();
        if (!forget)
                return ERR_PTR(-ENOMEM);
@@ -835,10 +849,13 @@ static struct dentry *create_new_entry(struct mnt_idmap *idmap, struct fuse_moun
        if (IS_ERR(d))
                return d;
 
-       if (d)
+       if (d) {
+               d->d_time = epoch;
                fuse_change_entry_timeout(d, &outarg);
-       else
+       } else {
+               entry->d_time = epoch;
                fuse_change_entry_timeout(entry, &outarg);
+       }
        fuse_dir_changed(dir);
        return d;
 
index f359efc3c2cffbd3845b77c8038fe1dacde6f2af..74340e4eebe52aa49c148cac7ae913df81c06868 100644 (file)
@@ -636,6 +636,9 @@ struct fuse_conn {
        /** Number of fuse_dev's */
        atomic_t dev_count;
 
+       /** Current epoch for up-to-date dentries */
+       atomic_t epoch;
+
        struct rcu_head rcu;
 
        /** The user id for this mount */
index 9471cc57637f6b1b06bf8bfef475829d626b2dd7..bfe8d8af46f3c30cef9c5f189bf8865aa435b705 100644 (file)
@@ -962,6 +962,7 @@ void fuse_conn_init(struct fuse_conn *fc, struct fuse_mount *fm,
        init_rwsem(&fc->killsb);
        refcount_set(&fc->count, 1);
        atomic_set(&fc->dev_count, 1);
+       atomic_set(&fc->epoch, 1);
        init_waitqueue_head(&fc->blocked_waitq);
        fuse_iqueue_init(&fc->iq, fiq_ops, fiq_priv);
        INIT_LIST_HEAD(&fc->bg_queue);
index 17ce9636a2b1a4674e29e4050f08deca7ed10d72..46b7146f2c0de3a8bbb07ab4478851e45e89eb4f 100644 (file)
@@ -161,6 +161,7 @@ static int fuse_direntplus_link(struct file *file,
        struct fuse_conn *fc;
        struct inode *inode;
        DECLARE_WAIT_QUEUE_HEAD_ONSTACK(wq);
+       int epoch;
 
        if (!o->nodeid) {
                /*
@@ -190,6 +191,7 @@ static int fuse_direntplus_link(struct file *file,
                return -EIO;
 
        fc = get_fuse_conn(dir);
+       epoch = atomic_read(&fc->epoch);
 
        name.hash = full_name_hash(parent, name.name, name.len);
        dentry = d_lookup(parent, &name);
@@ -256,6 +258,7 @@ retry:
        }
        if (fc->readdirplus_auto)
                set_bit(FUSE_I_INIT_RDPLUS, &get_fuse_inode(inode)->state);
+       dentry->d_time = epoch;
        fuse_change_entry_timeout(dentry, o);
 
        dput(dentry);
index 5ec43ecbceb7831d51d537a04b6c574b33ad9f9d..122d6586e8d4da0cb2a35a4ee80d231ac8762345 100644 (file)
  *
  *  7.43
  *  - add FUSE_REQUEST_TIMEOUT
+ *
+ *  7.44
+ *  - add FUSE_NOTIFY_INC_EPOCH
  */
 
 #ifndef _LINUX_FUSE_H
 #define FUSE_KERNEL_VERSION 7
 
 /** Minor version number of this interface */
-#define FUSE_KERNEL_MINOR_VERSION 43
+#define FUSE_KERNEL_MINOR_VERSION 44
 
 /** The node ID of the root inode */
 #define FUSE_ROOT_ID 1
@@ -671,6 +674,7 @@ enum fuse_notify_code {
        FUSE_NOTIFY_RETRIEVE = 5,
        FUSE_NOTIFY_DELETE = 6,
        FUSE_NOTIFY_RESEND = 7,
+       FUSE_NOTIFY_INC_EPOCH = 8,
        FUSE_NOTIFY_CODE_MAX,
 };