Skip to content

Commit ab84ad5

Browse files
luis-henrixMiklos Szeredi
authored andcommitted
fuse: new work queue to periodically invalidate expired dentries
This patch adds the necessary infrastructure to keep track of all dentries created for FUSE file systems. A set of rbtrees, protected by hashed locks, will be used to keep all these dentries sorted by expiry time. A new module parameter 'inval_wq' is also added. When set, it will start a work queue which will periodically invalidate expired dentries. The value of this new parameter is the period, in seconds, for this work queue. Once this parameter is set, every new dentry will be added to one of the rbtrees. When the work queue is executed, it will check all the rbtrees and will invalidate those dentries that have timed-out. The work queue period can not be smaller than 5 seconds, but can be disabled by setting 'inval_wq' to zero (which is the default). Signed-off-by: Luis Henriques <luis@igalia.com> Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
1 parent 395b955 commit ab84ad5

File tree

3 files changed

+208
-21
lines changed

3 files changed

+208
-21
lines changed

fs/fuse/dir.c

Lines changed: 195 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -27,40 +27,199 @@ module_param(allow_sys_admin_access, bool, 0644);
2727
MODULE_PARM_DESC(allow_sys_admin_access,
2828
"Allow users with CAP_SYS_ADMIN in initial userns to bypass allow_other access check");
2929

30+
struct dentry_bucket {
31+
struct rb_root tree;
32+
spinlock_t lock;
33+
};
34+
35+
#define HASH_BITS 5
36+
#define HASH_SIZE (1 << HASH_BITS)
37+
static struct dentry_bucket dentry_hash[HASH_SIZE];
38+
struct delayed_work dentry_tree_work;
39+
40+
/* Minimum invalidation work queue frequency */
41+
#define FUSE_DENTRY_INVAL_FREQ_MIN 5
42+
43+
unsigned __read_mostly inval_wq;
44+
static int inval_wq_set(const char *val, const struct kernel_param *kp)
45+
{
46+
unsigned int num;
47+
unsigned int old = inval_wq;
48+
int ret;
49+
50+
if (!val)
51+
return -EINVAL;
52+
53+
ret = kstrtouint(val, 0, &num);
54+
if (ret)
55+
return ret;
56+
57+
if ((num < FUSE_DENTRY_INVAL_FREQ_MIN) && (num != 0))
58+
return -EINVAL;
59+
60+
/* This should prevent overflow in secs_to_jiffies() */
61+
if (num > USHRT_MAX)
62+
return -EINVAL;
63+
64+
*((unsigned int *)kp->arg) = num;
65+
66+
if (num && !old)
67+
schedule_delayed_work(&dentry_tree_work,
68+
secs_to_jiffies(num));
69+
else if (!num && old)
70+
cancel_delayed_work_sync(&dentry_tree_work);
71+
72+
return 0;
73+
}
74+
static const struct kernel_param_ops inval_wq_ops = {
75+
.set = inval_wq_set,
76+
.get = param_get_uint,
77+
};
78+
module_param_cb(inval_wq, &inval_wq_ops, &inval_wq, 0644);
79+
__MODULE_PARM_TYPE(inval_wq, "uint");
80+
MODULE_PARM_DESC(inval_wq,
81+
"Dentries invalidation work queue period in secs (>= "
82+
__stringify(FUSE_DENTRY_INVAL_FREQ_MIN) ").");
83+
84+
static inline struct dentry_bucket *get_dentry_bucket(struct dentry *dentry)
85+
{
86+
int i = hash_ptr(dentry, HASH_BITS);
87+
88+
return &dentry_hash[i];
89+
}
90+
3091
static void fuse_advise_use_readdirplus(struct inode *dir)
3192
{
3293
struct fuse_inode *fi = get_fuse_inode(dir);
3394

3495
set_bit(FUSE_I_ADVISE_RDPLUS, &fi->state);
3596
}
3697

37-
#if BITS_PER_LONG >= 64
38-
static inline void __fuse_dentry_settime(struct dentry *entry, u64 time)
98+
struct fuse_dentry {
99+
u64 time;
100+
union {
101+
struct rcu_head rcu;
102+
struct rb_node node;
103+
};
104+
struct dentry *dentry;
105+
};
106+
107+
static void __fuse_dentry_tree_del_node(struct fuse_dentry *fd,
108+
struct dentry_bucket *bucket)
39109
{
40-
entry->d_fsdata = (void *) time;
110+
if (!RB_EMPTY_NODE(&fd->node)) {
111+
rb_erase(&fd->node, &bucket->tree);
112+
RB_CLEAR_NODE(&fd->node);
113+
}
41114
}
42115

43-
static inline u64 fuse_dentry_time(const struct dentry *entry)
116+
static void fuse_dentry_tree_del_node(struct dentry *dentry)
44117
{
45-
return (u64)entry->d_fsdata;
118+
struct fuse_dentry *fd = dentry->d_fsdata;
119+
struct dentry_bucket *bucket = get_dentry_bucket(dentry);
120+
121+
spin_lock(&bucket->lock);
122+
__fuse_dentry_tree_del_node(fd, bucket);
123+
spin_unlock(&bucket->lock);
46124
}
47125

48-
#else
49-
union fuse_dentry {
50-
u64 time;
51-
struct rcu_head rcu;
52-
};
126+
static void fuse_dentry_tree_add_node(struct dentry *dentry)
127+
{
128+
struct fuse_dentry *fd = dentry->d_fsdata;
129+
struct dentry_bucket *bucket;
130+
struct fuse_dentry *cur;
131+
struct rb_node **p, *parent = NULL;
132+
133+
if (!inval_wq)
134+
return;
135+
136+
bucket = get_dentry_bucket(dentry);
137+
138+
spin_lock(&bucket->lock);
139+
140+
__fuse_dentry_tree_del_node(fd, bucket);
141+
142+
p = &bucket->tree.rb_node;
143+
while (*p) {
144+
parent = *p;
145+
cur = rb_entry(*p, struct fuse_dentry, node);
146+
if (fd->time < cur->time)
147+
p = &(*p)->rb_left;
148+
else
149+
p = &(*p)->rb_right;
150+
}
151+
rb_link_node(&fd->node, parent, p);
152+
rb_insert_color(&fd->node, &bucket->tree);
153+
spin_unlock(&bucket->lock);
154+
}
155+
156+
/*
157+
* work queue which, when enabled, will periodically check for expired dentries
158+
* in the dentries tree.
159+
*/
160+
static void fuse_dentry_tree_work(struct work_struct *work)
161+
{
162+
LIST_HEAD(dispose);
163+
struct fuse_dentry *fd;
164+
struct rb_node *node;
165+
int i;
166+
167+
for (i = 0; i < HASH_SIZE; i++) {
168+
spin_lock(&dentry_hash[i].lock);
169+
node = rb_first(&dentry_hash[i].tree);
170+
while (node) {
171+
fd = rb_entry(node, struct fuse_dentry, node);
172+
if (time_after64(get_jiffies_64(), fd->time)) {
173+
rb_erase(&fd->node, &dentry_hash[i].tree);
174+
RB_CLEAR_NODE(&fd->node);
175+
spin_unlock(&dentry_hash[i].lock);
176+
d_dispose_if_unused(fd->dentry, &dispose);
177+
cond_resched();
178+
spin_lock(&dentry_hash[i].lock);
179+
} else
180+
break;
181+
node = rb_first(&dentry_hash[i].tree);
182+
}
183+
spin_unlock(&dentry_hash[i].lock);
184+
shrink_dentry_list(&dispose);
185+
}
186+
187+
if (inval_wq)
188+
schedule_delayed_work(&dentry_tree_work,
189+
secs_to_jiffies(inval_wq));
190+
}
191+
192+
void fuse_dentry_tree_init(void)
193+
{
194+
int i;
195+
196+
for (i = 0; i < HASH_SIZE; i++) {
197+
spin_lock_init(&dentry_hash[i].lock);
198+
dentry_hash[i].tree = RB_ROOT;
199+
}
200+
INIT_DELAYED_WORK(&dentry_tree_work, fuse_dentry_tree_work);
201+
}
202+
203+
void fuse_dentry_tree_cleanup(void)
204+
{
205+
int i;
206+
207+
inval_wq = 0;
208+
cancel_delayed_work_sync(&dentry_tree_work);
209+
210+
for (i = 0; i < HASH_SIZE; i++)
211+
WARN_ON_ONCE(!RB_EMPTY_ROOT(&dentry_hash[i].tree));
212+
}
53213

54214
static inline void __fuse_dentry_settime(struct dentry *dentry, u64 time)
55215
{
56-
((union fuse_dentry *) dentry->d_fsdata)->time = time;
216+
((struct fuse_dentry *) dentry->d_fsdata)->time = time;
57217
}
58218

59219
static inline u64 fuse_dentry_time(const struct dentry *entry)
60220
{
61-
return ((union fuse_dentry *) entry->d_fsdata)->time;
221+
return ((struct fuse_dentry *) entry->d_fsdata)->time;
62222
}
63-
#endif
64223

65224
static void fuse_dentry_settime(struct dentry *dentry, u64 time)
66225
{
@@ -81,6 +240,7 @@ static void fuse_dentry_settime(struct dentry *dentry, u64 time)
81240
}
82241

83242
__fuse_dentry_settime(dentry, time);
243+
fuse_dentry_tree_add_node(dentry);
84244
}
85245

86246
/*
@@ -283,21 +443,36 @@ static int fuse_dentry_revalidate(struct inode *dir, const struct qstr *name,
283443
goto out;
284444
}
285445

286-
#if BITS_PER_LONG < 64
287446
static int fuse_dentry_init(struct dentry *dentry)
288447
{
289-
dentry->d_fsdata = kzalloc(sizeof(union fuse_dentry),
290-
GFP_KERNEL_ACCOUNT | __GFP_RECLAIMABLE);
448+
struct fuse_dentry *fd;
291449

292-
return dentry->d_fsdata ? 0 : -ENOMEM;
450+
fd = kzalloc(sizeof(struct fuse_dentry),
451+
GFP_KERNEL_ACCOUNT | __GFP_RECLAIMABLE);
452+
if (!fd)
453+
return -ENOMEM;
454+
455+
fd->dentry = dentry;
456+
RB_CLEAR_NODE(&fd->node);
457+
dentry->d_fsdata = fd;
458+
459+
return 0;
460+
}
461+
462+
static void fuse_dentry_prune(struct dentry *dentry)
463+
{
464+
struct fuse_dentry *fd = dentry->d_fsdata;
465+
466+
if (!RB_EMPTY_NODE(&fd->node))
467+
fuse_dentry_tree_del_node(dentry);
293468
}
469+
294470
static void fuse_dentry_release(struct dentry *dentry)
295471
{
296-
union fuse_dentry *fd = dentry->d_fsdata;
472+
struct fuse_dentry *fd = dentry->d_fsdata;
297473

298474
kfree_rcu(fd, rcu);
299475
}
300-
#endif
301476

302477
static int fuse_dentry_delete(const struct dentry *dentry)
303478
{
@@ -331,10 +506,9 @@ static struct vfsmount *fuse_dentry_automount(struct path *path)
331506
const struct dentry_operations fuse_dentry_operations = {
332507
.d_revalidate = fuse_dentry_revalidate,
333508
.d_delete = fuse_dentry_delete,
334-
#if BITS_PER_LONG < 64
335509
.d_init = fuse_dentry_init,
510+
.d_prune = fuse_dentry_prune,
336511
.d_release = fuse_dentry_release,
337-
#endif
338512
.d_automount = fuse_dentry_automount,
339513
};
340514

fs/fuse/fuse_i.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,13 @@
5454
/** Frequency (in jiffies) of request timeout checks, if opted into */
5555
extern const unsigned long fuse_timeout_timer_freq;
5656

57+
/*
58+
* Dentries invalidation workqueue period, in seconds. The value of this
59+
* parameter shall be >= FUSE_DENTRY_INVAL_FREQ_MIN seconds, or 0 (zero), in
60+
* which case no workqueue will be created.
61+
*/
62+
extern unsigned inval_wq __read_mostly;
63+
5764
/** Maximum of max_pages received in init_out */
5865
extern unsigned int fuse_max_pages_limit;
5966
/*
@@ -1277,6 +1284,9 @@ void fuse_wait_aborted(struct fuse_conn *fc);
12771284
/* Check if any requests timed out */
12781285
void fuse_check_timeout(struct work_struct *work);
12791286

1287+
void fuse_dentry_tree_init(void);
1288+
void fuse_dentry_tree_cleanup(void);
1289+
12801290
/**
12811291
* Invalidate inode attributes
12821292
*/

fs/fuse/inode.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2294,6 +2294,8 @@ static int __init fuse_init(void)
22942294
if (res)
22952295
goto err_sysfs_cleanup;
22962296

2297+
fuse_dentry_tree_init();
2298+
22972299
sanitize_global_limit(&max_user_bgreq);
22982300
sanitize_global_limit(&max_user_congthresh);
22992301

@@ -2313,6 +2315,7 @@ static void __exit fuse_exit(void)
23132315
{
23142316
pr_debug("exit\n");
23152317

2318+
fuse_dentry_tree_cleanup();
23162319
fuse_ctl_cleanup();
23172320
fuse_sysfs_cleanup();
23182321
fuse_fs_cleanup();

0 commit comments

Comments
 (0)