Skip to content

Commit 7d39bc5

Browse files
nicstangegregkh
authored andcommitted
debugfs: defer debugfs_fsdata allocation to first usage
Currently, __debugfs_create_file allocates one struct debugfs_fsdata instance for every file created. However, there are potentially many debugfs file around, most of which are never touched by userspace. Thus, defer the allocations to the first usage, i.e. to the first debugfs_file_get(). A dentry's ->d_fsdata starts out to point to the "real", user provided fops. After a debugfs_fsdata instance has been allocated (and the real fops pointer has been moved over into its ->real_fops member), ->d_fsdata is changed to point to it from then on. The two cases are distinguished by setting BIT(0) for the real fops case. struct debugfs_fsdata's foremost purpose is to track active users and to make debugfs_remove() block until they are done. Since no debugfs_fsdata instance means no active users, make debugfs_remove() return immediately in this case. Take care of possible races between debugfs_file_get() and debugfs_remove(): either debugfs_remove() must see a debugfs_fsdata instance and thus wait for possible active users or debugfs_file_get() must see a dead dentry and return immediately. Make a dentry's ->d_release(), i.e. debugfs_release_dentry(), check whether ->d_fsdata is actually a debugfs_fsdata instance before kfree()ing it. Similarly, make debugfs_real_fops() check whether ->d_fsdata is actually a debugfs_fsdata instance before returning it, otherwise emit a warning. The set of possible error codes returned from debugfs_file_get() has grown from -EIO to -EIO and -ENOMEM. Make open_proxy_open() and full_proxy_open() pass the -ENOMEM onwards to their callers. Signed-off-by: Nicolai Stange <[email protected]> Signed-off-by: Greg Kroah-Hartman <[email protected]>
1 parent 154b9d7 commit 7d39bc5

File tree

3 files changed

+73
-26
lines changed

3 files changed

+73
-26
lines changed

fs/debugfs/file.c

Lines changed: 46 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,15 @@ const struct file_operations *debugfs_real_fops(const struct file *filp)
5353
{
5454
struct debugfs_fsdata *fsd = F_DENTRY(filp)->d_fsdata;
5555

56+
if ((unsigned long)fsd & DEBUGFS_FSDATA_IS_REAL_FOPS_BIT) {
57+
/*
58+
* Urgh, we've been called w/o a protecting
59+
* debugfs_file_get().
60+
*/
61+
WARN_ON(1);
62+
return NULL;
63+
}
64+
5665
return fsd->real_fops;
5766
}
5867
EXPORT_SYMBOL_GPL(debugfs_real_fops);
@@ -74,9 +83,35 @@ EXPORT_SYMBOL_GPL(debugfs_real_fops);
7483
*/
7584
int debugfs_file_get(struct dentry *dentry)
7685
{
77-
struct debugfs_fsdata *fsd = dentry->d_fsdata;
86+
struct debugfs_fsdata *fsd;
87+
void *d_fsd;
88+
89+
d_fsd = READ_ONCE(dentry->d_fsdata);
90+
if (!((unsigned long)d_fsd & DEBUGFS_FSDATA_IS_REAL_FOPS_BIT)) {
91+
fsd = d_fsd;
92+
} else {
93+
fsd = kmalloc(sizeof(*fsd), GFP_KERNEL);
94+
if (!fsd)
95+
return -ENOMEM;
96+
97+
fsd->real_fops = (void *)((unsigned long)d_fsd &
98+
~DEBUGFS_FSDATA_IS_REAL_FOPS_BIT);
99+
refcount_set(&fsd->active_users, 1);
100+
init_completion(&fsd->active_users_drained);
101+
if (cmpxchg(&dentry->d_fsdata, d_fsd, fsd) != d_fsd) {
102+
kfree(fsd);
103+
fsd = READ_ONCE(dentry->d_fsdata);
104+
}
105+
}
78106

79-
/* Avoid starvation of removers. */
107+
/*
108+
* In case of a successful cmpxchg() above, this check is
109+
* strictly necessary and must follow it, see the comment in
110+
* __debugfs_remove_file().
111+
* OTOH, if the cmpxchg() hasn't been executed or wasn't
112+
* successful, this serves the purpose of not starving
113+
* removers.
114+
*/
80115
if (d_unlinked(dentry))
81116
return -EIO;
82117

@@ -98,7 +133,7 @@ EXPORT_SYMBOL_GPL(debugfs_file_get);
98133
*/
99134
void debugfs_file_put(struct dentry *dentry)
100135
{
101-
struct debugfs_fsdata *fsd = dentry->d_fsdata;
136+
struct debugfs_fsdata *fsd = READ_ONCE(dentry->d_fsdata);
102137

103138
if (refcount_dec_and_test(&fsd->active_users))
104139
complete(&fsd->active_users_drained);
@@ -109,10 +144,11 @@ static int open_proxy_open(struct inode *inode, struct file *filp)
109144
{
110145
struct dentry *dentry = F_DENTRY(filp);
111146
const struct file_operations *real_fops = NULL;
112-
int r = 0;
147+
int r;
113148

114-
if (debugfs_file_get(dentry))
115-
return -ENOENT;
149+
r = debugfs_file_get(dentry);
150+
if (r)
151+
return r == -EIO ? -ENOENT : r;
116152

117153
real_fops = debugfs_real_fops(filp);
118154
real_fops = fops_get(real_fops);
@@ -233,10 +269,11 @@ static int full_proxy_open(struct inode *inode, struct file *filp)
233269
struct dentry *dentry = F_DENTRY(filp);
234270
const struct file_operations *real_fops = NULL;
235271
struct file_operations *proxy_fops = NULL;
236-
int r = 0;
272+
int r;
237273

238-
if (debugfs_file_get(dentry))
239-
return -ENOENT;
274+
r = debugfs_file_get(dentry);
275+
if (r)
276+
return r == -EIO ? -ENOENT : r;
240277

241278
real_fops = debugfs_real_fops(filp);
242279
real_fops = fops_get(real_fops);

fs/debugfs/inode.c

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,10 @@ static const struct super_operations debugfs_super_operations = {
184184

185185
static void debugfs_release_dentry(struct dentry *dentry)
186186
{
187-
kfree(dentry->d_fsdata);
187+
void *fsd = dentry->d_fsdata;
188+
189+
if (!((unsigned long)fsd & DEBUGFS_FSDATA_IS_REAL_FOPS_BIT))
190+
kfree(dentry->d_fsdata);
188191
}
189192

190193
static struct vfsmount *debugfs_automount(struct path *path)
@@ -344,35 +347,25 @@ static struct dentry *__debugfs_create_file(const char *name, umode_t mode,
344347
{
345348
struct dentry *dentry;
346349
struct inode *inode;
347-
struct debugfs_fsdata *fsd;
348-
349-
fsd = kmalloc(sizeof(*fsd), GFP_KERNEL);
350-
if (!fsd)
351-
return NULL;
352350

353351
if (!(mode & S_IFMT))
354352
mode |= S_IFREG;
355353
BUG_ON(!S_ISREG(mode));
356354
dentry = start_creating(name, parent);
357355

358-
if (IS_ERR(dentry)) {
359-
kfree(fsd);
356+
if (IS_ERR(dentry))
360357
return NULL;
361-
}
362358

363359
inode = debugfs_get_inode(dentry->d_sb);
364-
if (unlikely(!inode)) {
365-
kfree(fsd);
360+
if (unlikely(!inode))
366361
return failed_creating(dentry);
367-
}
368362

369363
inode->i_mode = mode;
370364
inode->i_private = data;
371365

372366
inode->i_fop = proxy_fops;
373-
fsd->real_fops = real_fops;
374-
refcount_set(&fsd->active_users, 1);
375-
dentry->d_fsdata = fsd;
367+
dentry->d_fsdata = (void *)((unsigned long)real_fops |
368+
DEBUGFS_FSDATA_IS_REAL_FOPS_BIT);
376369

377370
d_instantiate(dentry, inode);
378371
fsnotify_create(d_inode(dentry->d_parent), dentry);
@@ -635,8 +628,17 @@ static void __debugfs_remove_file(struct dentry *dentry, struct dentry *parent)
635628

636629
simple_unlink(d_inode(parent), dentry);
637630
d_delete(dentry);
638-
fsd = dentry->d_fsdata;
639-
init_completion(&fsd->active_users_drained);
631+
632+
/*
633+
* Paired with the closing smp_mb() implied by a successful
634+
* cmpxchg() in debugfs_file_get(): either
635+
* debugfs_file_get() must see a dead dentry or we must see a
636+
* debugfs_fsdata instance at ->d_fsdata here (or both).
637+
*/
638+
smp_mb();
639+
fsd = READ_ONCE(dentry->d_fsdata);
640+
if ((unsigned long)fsd & DEBUGFS_FSDATA_IS_REAL_FOPS_BIT)
641+
return;
640642
if (!refcount_dec_and_test(&fsd->active_users))
641643
wait_for_completion(&fsd->active_users_drained);
642644
}

fs/debugfs/internal.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,12 @@ struct debugfs_fsdata {
2525
struct completion active_users_drained;
2626
};
2727

28+
/*
29+
* A dentry's ->d_fsdata either points to the real fops or to a
30+
* dynamically allocated debugfs_fsdata instance.
31+
* In order to distinguish between these two cases, a real fops
32+
* pointer gets its lowest bit set.
33+
*/
34+
#define DEBUGFS_FSDATA_IS_REAL_FOPS_BIT BIT(0)
35+
2836
#endif /* _DEBUGFS_INTERNAL_H_ */

0 commit comments

Comments
 (0)