Skip to content

Commit a2847a3

Browse files
committed
fuse: Add LRU dentry pruning
Implement an on-demand dentry pruning mechanism triggered by inode count limits. When the number of inodes exceeds the configured max_inodes limit, a delayed worker walks the dentry LRU and invalidates dentries to reduce both dentry and inode counts. The prune context is embedded in struct fuse_mount The worker is initialized during FUSE_INIT if the server provides a max_inodes value, and is properly cancelled during mount teardown to prevent use-after-free. This provides a unified approach to managing both inode and dentry memory usage through a single limit Signed-off-by: Horst Birthelmer <hbirthelmer@ddn.com>
1 parent a8c45a3 commit a2847a3

4 files changed

Lines changed: 169 additions & 1 deletion

File tree

fs/fuse/dir.c

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@
2222
#include <linux/security.h>
2323
#include <linux/types.h>
2424
#include <linux/kernel.h>
25+
#include <linux/dcache.h>
26+
#include <linux/list_lru.h>
27+
#include <linux/workqueue.h>
2528

2629
static bool __read_mostly allow_sys_admin_access;
2730
module_param(allow_sys_admin_access, bool, 0644);
@@ -2233,3 +2236,140 @@ void fuse_init_symlink(struct inode *inode)
22332236
inode->i_data.a_ops = &fuse_symlink_aops;
22342237
inode_nohighmem(inode);
22352238
}
2239+
2240+
struct fuse_prune_ctx {
2241+
struct dentry **dentries;
2242+
unsigned long count;
2243+
unsigned long max;
2244+
};
2245+
2246+
static enum lru_status fuse_lru_isolate(struct list_head *item,
2247+
struct list_lru_one *lru,
2248+
spinlock_t *lru_lock,
2249+
void *arg)
2250+
{
2251+
struct dentry *dentry = container_of(item, struct dentry, d_lru);
2252+
struct fuse_prune_ctx *ctx = arg;
2253+
2254+
if (!spin_trylock(&dentry->d_lock))
2255+
return LRU_SKIP;
2256+
2257+
/* Skip dentries that are in use or already unhashed */
2258+
if (dentry->d_lockref.count > 0 || d_unhashed(dentry)) {
2259+
spin_unlock(&dentry->d_lock);
2260+
return LRU_SKIP;
2261+
}
2262+
2263+
/* Skip dentries that were recently referenced - give them another chance */
2264+
if (dentry->d_flags & DCACHE_REFERENCED) {
2265+
dentry->d_flags &= ~DCACHE_REFERENCED;
2266+
spin_unlock(&dentry->d_lock);
2267+
return LRU_ROTATE;
2268+
}
2269+
2270+
/* Skip negative dentries - they don't have inodes to forget */
2271+
if (!d_inode(dentry)) {
2272+
spin_unlock(&dentry->d_lock);
2273+
return LRU_ROTATE;
2274+
}
2275+
2276+
/* Skip directories - d_invalidate() would recursively shrink children */
2277+
if (d_is_dir(dentry)) {
2278+
spin_unlock(&dentry->d_lock);
2279+
return LRU_ROTATE;
2280+
}
2281+
2282+
/* Check if we've collected enough dentries */
2283+
if (ctx->count >= ctx->max) {
2284+
spin_unlock(&dentry->d_lock);
2285+
return LRU_SKIP;
2286+
}
2287+
2288+
/* Grab a reference and collect this dentry for pruning */
2289+
dget_dlock(dentry);
2290+
spin_unlock(&dentry->d_lock);
2291+
2292+
ctx->dentries[ctx->count++] = dentry;
2293+
return LRU_ROTATE;
2294+
}
2295+
2296+
static void fuse_lru_prune_worker(struct work_struct *work)
2297+
{
2298+
struct fuse_mount *fm = container_of(work, struct fuse_mount,
2299+
lru_prune_work.work);
2300+
struct super_block *sb;
2301+
unsigned long i;
2302+
2303+
down_read(&fm->fc->killsb);
2304+
sb = fm->sb;
2305+
if (!sb) {
2306+
up_read(&fm->fc->killsb);
2307+
return;
2308+
}
2309+
2310+
fm->prune_ctx.count = 0;
2311+
fm->prune_ctx.max = ARRAY_SIZE(fm->prune_ctx.dentries);
2312+
2313+
if (fm->max_inodes && atomic64_read(&fm->fc->num_inodes) > fm->max_inodes) {
2314+
unsigned long to_scan = atomic64_read(&fm->fc->num_inodes) - fm->max_inodes;
2315+
list_lru_walk(&sb->s_dentry_lru, fuse_lru_isolate, &fm->prune_ctx,
2316+
min(to_scan, (unsigned long)ARRAY_SIZE(fm->prune_ctx.dentries)));
2317+
}
2318+
2319+
up_read(&fm->fc->killsb);
2320+
2321+
/*
2322+
* Invalidate dentries to free them and send FORGET to server.
2323+
* We only collected non-directory dentries, so d_invalidate()
2324+
* won't recursively shrink children.
2325+
*
2326+
* Since we're only collecting the exact excess (not being aggressive),
2327+
* this should be safe and won't flood the server with FORGET requests.
2328+
*/
2329+
for (i = 0; i < fm->prune_ctx.count; i++) {
2330+
d_invalidate(fm->prune_ctx.dentries[i]);
2331+
dput(fm->prune_ctx.dentries[i]);
2332+
}
2333+
2334+
}
2335+
2336+
void fuse_lru_prune_init(struct fuse_mount *fm, unsigned long max_inodes)
2337+
{
2338+
INIT_DELAYED_WORK(&fm->lru_prune_work, fuse_lru_prune_worker);
2339+
fm->max_inodes = max_inodes;
2340+
}
2341+
2342+
void fuse_lru_prune_stop(struct fuse_mount *fm)
2343+
{
2344+
if (fm->max_inodes == 0)
2345+
return;
2346+
2347+
fm->max_inodes = 0;
2348+
cancel_delayed_work_sync(&fm->lru_prune_work);
2349+
}
2350+
2351+
void fuse_lru_prune_set_inode_limit(struct fuse_mount *fm,
2352+
unsigned long max_inodes)
2353+
{
2354+
WRITE_ONCE(fm->max_inodes, max_inodes);
2355+
}
2356+
2357+
bool fuse_lru_prune_trigger(struct fuse_mount *fm)
2358+
{
2359+
struct super_block *sb;
2360+
2361+
if (fm->max_inodes == 0)
2362+
return false;
2363+
2364+
sb = READ_ONCE(fm->sb);
2365+
if (!sb)
2366+
return false;
2367+
2368+
if (fm->max_inodes > 0 &&
2369+
atomic64_read(&fm->fc->num_inodes) > fm->max_inodes) {
2370+
mod_delayed_work(system_wq, &fm->lru_prune_work, 0);
2371+
return true;
2372+
}
2373+
2374+
return false;
2375+
}

fs/fuse/fuse_i.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -940,6 +940,8 @@ struct fuse_conn {
940940

941941
};
942942

943+
#define FUSE_PRUNE_DENTRY_BATCH 512
944+
943945
/*
944946
* Represents a mounted filesystem, potentially a submount.
945947
*
@@ -960,6 +962,17 @@ struct fuse_mount {
960962
/* Entry on fc->mounts */
961963
struct list_head fc_entry;
962964
struct rcu_head rcu;
965+
966+
/* LRU pruning worker */
967+
struct delayed_work lru_prune_work;
968+
unsigned long max_inodes;
969+
970+
/* Prune context - used by the worker to collect dentries */
971+
struct {
972+
struct dentry *dentries[FUSE_PRUNE_DENTRY_BATCH];
973+
unsigned long count;
974+
unsigned long max;
975+
} prune_ctx;
963976
};
964977

965978
/*
@@ -1496,4 +1509,10 @@ extern void fuse_sysctl_unregister(void);
14961509
#define fuse_sysctl_unregister() do { } while (0)
14971510
#endif /* CONFIG_SYSCTL */
14981511

1512+
/* Unified LRU pruning for dentries and inodes (on-demand only) */
1513+
void fuse_lru_prune_init(struct fuse_mount *fm, unsigned long max_inodes);
1514+
void fuse_lru_prune_stop(struct fuse_mount *fm);
1515+
void fuse_lru_prune_set_inode_limit(struct fuse_mount *fm, unsigned long max_inodes);
1516+
bool fuse_lru_prune_trigger(struct fuse_mount *fm);
1517+
14991518
#endif /* _FS_FUSE_I_H */

fs/fuse/inode.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -522,6 +522,9 @@ struct inode *fuse_iget(struct super_block *sb, u64 nodeid,
522522
done:
523523
fuse_change_attributes_i(inode, attr, NULL, attr_valid, attr_version,
524524
evict_ctr);
525+
526+
fuse_lru_prune_trigger(get_fuse_mount_super(sb));
527+
525528
return inode;
526529
}
527530

@@ -1458,6 +1461,10 @@ static void process_init_reply(struct fuse_mount *fm, struct fuse_args *args,
14581461
fc->max_write = arg->minor < 5 ? 4096 : arg->max_write;
14591462
fc->max_write = max_t(unsigned, 4096, fc->max_write);
14601463
fc->conn_init = 1;
1464+
1465+
/* Initialize unified LRU pruning if inode limit is set */
1466+
if (arg->max_inodes > 0)
1467+
fuse_lru_prune_init(fm, arg->max_inodes);
14611468
}
14621469
kfree(ia);
14631470

@@ -2103,6 +2110,7 @@ static void fuse_sb_destroy(struct super_block *sb)
21032110

21042111
void fuse_mount_destroy(struct fuse_mount *fm)
21052112
{
2113+
fuse_lru_prune_stop(fm);
21062114
fuse_conn_put(fm->fc);
21072115
kfree_rcu(fm, rcu);
21082116
}

include/uapi/linux/fuse.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -923,7 +923,8 @@ struct fuse_init_out {
923923
uint32_t max_stack_depth;
924924
uint8_t align_page_order;
925925
uint8_t padding[3];
926-
uint32_t unused[5];
926+
uint32_t max_inodes;
927+
uint32_t unused[4];
927928
};
928929

929930
#define CUSE_INIT_INFO_MAX 4096

0 commit comments

Comments
 (0)