aboutsummaryrefslogtreecommitdiffstats
path: root/fs/debugfs/file.c
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 /fs/debugfs/file.c
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>
Diffstat (limited to 'fs/debugfs/file.c')
-rw-r--r--fs/debugfs/file.c55
1 files changed, 46 insertions, 9 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);