]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
fuse: add prune notification
authorMiklos Szeredi <mszeredi@redhat.com>
Tue, 2 Sep 2025 08:22:06 +0000 (10:22 +0200)
committerMiklos Szeredi <mszeredi@redhat.com>
Fri, 5 Sep 2025 07:11:28 +0000 (09:11 +0200)
Some fuse servers need to prune their caches, which can only be done if the
kernel's own dentry/inode caches are pruned first to avoid dangling
references.

Add FUSE_NOTIFY_PRUNE, which takes an array of node ID's to try and get rid
of.  Inodes with active references are skipped.

A similar functionality is already provided by FUSE_NOTIFY_INVAL_ENTRY with
the FUSE_EXPIRE_ONLY flag.  Differences in the interface are

FUSE_NOTIFY_INVAL_ENTRY:

  - can only prune one dentry

  - dentry is determined by parent ID and name

  - if inode has multiple aliases (cached hard links), then they would have
    to be invalidated individually to be able to get rid of the inode

FUSE_NOTIFY_PRUNE:

  - can prune multiple inodes

  - inodes determined by their node ID

  - aliases are taken care of automatically

Reviewed-by: Joanne Koong <joannelkoong@gmail.com>
Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
fs/fuse/dev.c
fs/fuse/fuse_i.h
fs/fuse/inode.c
include/uapi/linux/fuse.h

index 1258acee970422f2a3b419feeacc75fcc03faf9b..4229b38546bb8637bccdc6d31aa8e1d178c7d452 100644 (file)
@@ -2034,6 +2034,42 @@ static int fuse_notify_inc_epoch(struct fuse_conn *fc)
        return 0;
 }
 
+static int fuse_notify_prune(struct fuse_conn *fc, unsigned int size,
+                            struct fuse_copy_state *cs)
+{
+       struct fuse_notify_prune_out outarg;
+       const unsigned int batch = 512;
+       u64 *nodeids __free(kfree) = kmalloc(sizeof(u64) * batch, GFP_KERNEL);
+       unsigned int num, i;
+       int err;
+
+       if (!nodeids)
+               return -ENOMEM;
+
+       if (size < sizeof(outarg))
+               return -EINVAL;
+
+       err = fuse_copy_one(cs, &outarg, sizeof(outarg));
+       if (err)
+               return err;
+
+       if (size - sizeof(outarg) != outarg.count * sizeof(u64))
+               return -EINVAL;
+
+       for (; outarg.count; outarg.count -= num) {
+               num = min(batch, outarg.count);
+               err = fuse_copy_one(cs, nodeids, num * sizeof(u64));
+               if (err)
+                       return err;
+
+               scoped_guard(rwsem_read, &fc->killsb) {
+                       for (i = 0; i < num; i++)
+                               fuse_try_prune_one_inode(fc, nodeids[i]);
+               }
+       }
+       return 0;
+}
+
 static int fuse_notify(struct fuse_conn *fc, enum fuse_notify_code code,
                       unsigned int size, struct fuse_copy_state *cs)
 {
@@ -2065,6 +2101,9 @@ static int fuse_notify(struct fuse_conn *fc, enum fuse_notify_code code,
        case FUSE_NOTIFY_INC_EPOCH:
                return fuse_notify_inc_epoch(fc);
 
+       case FUSE_NOTIFY_PRUNE:
+               return fuse_notify_prune(fc, size, cs);
+
        default:
                return -EINVAL;
        }
index 233c6111f768f222c01b1759e29aa584c97e24e0..fb6604120b53d9259327e1cf47a93c3b4a78ecf5 100644 (file)
@@ -1413,6 +1413,12 @@ int fuse_reverse_inval_inode(struct fuse_conn *fc, u64 nodeid,
 int fuse_reverse_inval_entry(struct fuse_conn *fc, u64 parent_nodeid,
                             u64 child_nodeid, struct qstr *name, u32 flags);
 
+/*
+ * Try to prune this inode.  If neither the inode itself nor dentries associated
+ * with this inode have any external reference, then the inode can be freed.
+ */
+void fuse_try_prune_one_inode(struct fuse_conn *fc, u64 nodeid);
+
 int fuse_do_open(struct fuse_mount *fm, u64 nodeid, struct file *file,
                 bool isdir);
 
index 5b7897bf7e456fe52e99d9cc37d5afff10db3219..a4d361b34d0632d43663440bbfe04ea285f69a4f 100644 (file)
@@ -585,6 +585,17 @@ int fuse_reverse_inval_inode(struct fuse_conn *fc, u64 nodeid,
        return 0;
 }
 
+void fuse_try_prune_one_inode(struct fuse_conn *fc, u64 nodeid)
+{
+       struct inode *inode;
+
+       inode = fuse_ilookup(fc, nodeid,  NULL);
+       if (!inode)
+               return;
+       d_prune_aliases(inode);
+       iput(inode);
+}
+
 bool fuse_lock_inode(struct inode *inode)
 {
        bool locked = false;
index 30bf0846547f171059f64a34a27ba3c02b2b56ea..c13e1f9a2f12bd39f535188cb5466688eba42263 100644 (file)
  *  7.45
  *  - add FUSE_COPY_FILE_RANGE_64
  *  - add struct fuse_copy_file_range_out
+ *  - add FUSE_NOTIFY_PRUNE
  */
 
 #ifndef _LINUX_FUSE_H
@@ -680,6 +681,7 @@ enum fuse_notify_code {
        FUSE_NOTIFY_DELETE = 6,
        FUSE_NOTIFY_RESEND = 7,
        FUSE_NOTIFY_INC_EPOCH = 8,
+       FUSE_NOTIFY_PRUNE = 9,
 };
 
 /* The read buffer is required to be at least 8k, but may be much larger */
@@ -1118,6 +1120,12 @@ struct fuse_notify_retrieve_in {
        uint64_t        dummy4;
 };
 
+struct fuse_notify_prune_out {
+       uint32_t        count;
+       uint32_t        padding;
+       uint64_t        spare;
+};
+
 struct fuse_backing_map {
        int32_t         fd;
        uint32_t        flags;