diff options
-rw-r--r-- | fs/debugfs/file.c | 55 | ||||
-rw-r--r-- | fs/debugfs/inode.c | 36 | ||||
-rw-r--r-- | fs/debugfs/internal.h | 8 |
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 | } |
58 | EXPORT_SYMBOL_GPL(debugfs_real_fops); | 67 | EXPORT_SYMBOL_GPL(debugfs_real_fops); |
@@ -74,9 +83,35 @@ EXPORT_SYMBOL_GPL(debugfs_real_fops); | |||
74 | */ | 83 | */ |
75 | int debugfs_file_get(struct dentry *dentry) | 84 | int 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 | */ |
99 | void debugfs_file_put(struct dentry *dentry) | 134 | void 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 | ||
185 | static void debugfs_release_dentry(struct dentry *dentry) | 185 | static 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 | ||
190 | static struct vfsmount *debugfs_automount(struct path *path) | 193 | static 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_ */ |