aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNicolai Stange <nicstange@gmail.com>2017-10-30 19:15:54 -0400
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2017-11-07 14:25:03 -0500
commit7d39bc50c47b3f8ed0e1a9d671ecb9ec02f10a2d (patch)
treeba7e45925bc61a6dbb4749ee503a481449966112
parent154b9d7512ae012aca7b4e90af67a72419ad1941 (diff)
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 <nicstange@gmail.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
-rw-r--r--fs/debugfs/file.c55
-rw-r--r--fs/debugfs/inode.c36
-rw-r--r--fs/debugfs/internal.h8
3 files changed, 73 insertions, 26 deletions
diff --git a/fs/debugfs/file.c b/fs/debugfs/file.c
index bc3549c95574..65872340e301 100644
--- a/fs/debugfs/file.c
+++ b/fs/debugfs/file.c
@@ -53,6 +53,15 @@ const struct file_operations *debugfs_real_fops(const struct file *filp)
53{ 53{
54 struct debugfs_fsdata *fsd = F_DENTRY(filp)->d_fsdata; 54 struct debugfs_fsdata *fsd = F_DENTRY(filp)->d_fsdata;
55 55
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
56 return fsd->real_fops; 65 return fsd->real_fops;
57} 66}
58EXPORT_SYMBOL_GPL(debugfs_real_fops); 67EXPORT_SYMBOL_GPL(debugfs_real_fops);
@@ -74,9 +83,35 @@ EXPORT_SYMBOL_GPL(debugfs_real_fops);
74 */ 83 */
75int debugfs_file_get(struct dentry *dentry) 84int debugfs_file_get(struct dentry *dentry)
76{ 85{
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 }
78 106
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 */
80 if (d_unlinked(dentry)) 115 if (d_unlinked(dentry))
81 return -EIO; 116 return -EIO;
82 117
@@ -98,7 +133,7 @@ EXPORT_SYMBOL_GPL(debugfs_file_get);
98 */ 133 */
99void debugfs_file_put(struct dentry *dentry) 134void debugfs_file_put(struct dentry *dentry)
100{ 135{
101 struct debugfs_fsdata *fsd = dentry->d_fsdata; 136 struct debugfs_fsdata *fsd = READ_ONCE(dentry->d_fsdata);
102 137
103 if (refcount_dec_and_test(&fsd->active_users)) 138 if (refcount_dec_and_test(&fsd->active_users))
104 complete(&fsd->active_users_drained); 139 complete(&fsd->active_users_drained);
@@ -109,10 +144,11 @@ static int open_proxy_open(struct inode *inode, struct file *filp)
109{ 144{
110 struct dentry *dentry = F_DENTRY(filp); 145 struct dentry *dentry = F_DENTRY(filp);
111 const struct file_operations *real_fops = NULL; 146 const struct file_operations *real_fops = NULL;
112 int r = 0; 147 int r;
113 148
114 if (debugfs_file_get(dentry)) 149 r = debugfs_file_get(dentry);
115 return -ENOENT; 150 if (r)
151 return r == -EIO ? -ENOENT : r;
116 152
117 real_fops = debugfs_real_fops(filp); 153 real_fops = debugfs_real_fops(filp);
118 real_fops = fops_get(real_fops); 154 real_fops = fops_get(real_fops);
@@ -233,10 +269,11 @@ static int full_proxy_open(struct inode *inode, struct file *filp)
233 struct dentry *dentry = F_DENTRY(filp); 269 struct dentry *dentry = F_DENTRY(filp);
234 const struct file_operations *real_fops = NULL; 270 const struct file_operations *real_fops = NULL;
235 struct file_operations *proxy_fops = NULL; 271 struct file_operations *proxy_fops = NULL;
236 int r = 0; 272 int r;
237 273
238 if (debugfs_file_get(dentry)) 274 r = debugfs_file_get(dentry);
239 return -ENOENT; 275 if (r)
276 return r == -EIO ? -ENOENT : r;
240 277
241 real_fops = debugfs_real_fops(filp); 278 real_fops = debugfs_real_fops(filp);
242 real_fops = fops_get(real_fops); 279 real_fops = fops_get(real_fops);
diff --git a/fs/debugfs/inode.c b/fs/debugfs/inode.c
index f587aded46b5..9dca4da059b3 100644
--- a/fs/debugfs/inode.c
+++ b/fs/debugfs/inode.c
@@ -184,7 +184,10 @@ static const struct super_operations debugfs_super_operations = {
184 184
185static void debugfs_release_dentry(struct dentry *dentry) 185static void debugfs_release_dentry(struct dentry *dentry)
186{ 186{
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);
188} 191}
189 192
190static struct vfsmount *debugfs_automount(struct path *path) 193static struct vfsmount *debugfs_automount(struct path *path)
@@ -344,35 +347,25 @@ static struct dentry *__debugfs_create_file(const char *name, umode_t mode,
344{ 347{
345 struct dentry *dentry; 348 struct dentry *dentry;
346 struct inode *inode; 349 struct inode *inode;
347 struct debugfs_fsdata *fsd;
348
349 fsd = kmalloc(sizeof(*fsd), GFP_KERNEL);
350 if (!fsd)
351 return NULL;
352 350
353 if (!(mode & S_IFMT)) 351 if (!(mode & S_IFMT))
354 mode |= S_IFREG; 352 mode |= S_IFREG;
355 BUG_ON(!S_ISREG(mode)); 353 BUG_ON(!S_ISREG(mode));
356 dentry = start_creating(name, parent); 354 dentry = start_creating(name, parent);
357 355
358 if (IS_ERR(dentry)) { 356 if (IS_ERR(dentry))
359 kfree(fsd);
360 return NULL; 357 return NULL;
361 }
362 358
363 inode = debugfs_get_inode(dentry->d_sb); 359 inode = debugfs_get_inode(dentry->d_sb);
364 if (unlikely(!inode)) { 360 if (unlikely(!inode))
365 kfree(fsd);
366 return failed_creating(dentry); 361 return failed_creating(dentry);
367 }
368 362
369 inode->i_mode = mode; 363 inode->i_mode = mode;
370 inode->i_private = data; 364 inode->i_private = data;
371 365
372 inode->i_fop = proxy_fops; 366 inode->i_fop = proxy_fops;
373 fsd->real_fops = real_fops; 367 dentry->d_fsdata = (void *)((unsigned long)real_fops |
374 refcount_set(&fsd->active_users, 1); 368 DEBUGFS_FSDATA_IS_REAL_FOPS_BIT);
375 dentry->d_fsdata = fsd;
376 369
377 d_instantiate(dentry, inode); 370 d_instantiate(dentry, inode);
378 fsnotify_create(d_inode(dentry->d_parent), dentry); 371 fsnotify_create(d_inode(dentry->d_parent), dentry);
@@ -635,8 +628,17 @@ static void __debugfs_remove_file(struct dentry *dentry, struct dentry *parent)
635 628
636 simple_unlink(d_inode(parent), dentry); 629 simple_unlink(d_inode(parent), dentry);
637 d_delete(dentry); 630 d_delete(dentry);
638 fsd = dentry->d_fsdata; 631
639 init_completion(&fsd->active_users_drained); 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;
640 if (!refcount_dec_and_test(&fsd->active_users)) 642 if (!refcount_dec_and_test(&fsd->active_users))
641 wait_for_completion(&fsd->active_users_drained); 643 wait_for_completion(&fsd->active_users_drained);
642} 644}
diff --git a/fs/debugfs/internal.h b/fs/debugfs/internal.h
index 0eea99432840..cb1e8139c398 100644
--- a/fs/debugfs/internal.h
+++ b/fs/debugfs/internal.h
@@ -25,4 +25,12 @@ struct debugfs_fsdata {
25 struct completion active_users_drained; 25 struct completion active_users_drained;
26}; 26};
27 27
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
28#endif /* _DEBUGFS_INTERNAL_H_ */ 36#endif /* _DEBUGFS_INTERNAL_H_ */