summaryrefslogtreecommitdiff
path: root/fs/debugfs/inode.c
diff options
context:
space:
mode:
authorNicolai Stange <nicstange@gmail.com>2017-10-31 00:15:54 +0100
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2017-11-07 20:25:03 +0100
commit7d39bc50c47b3f8ed0e1a9d671ecb9ec02f10a2d (patch)
treeba7e45925bc61a6dbb4749ee503a481449966112 /fs/debugfs/inode.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/inode.c')
-rw-r--r--fs/debugfs/inode.c36
1 files changed, 19 insertions, 17 deletions
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 = {
static void debugfs_release_dentry(struct dentry *dentry)
{
- kfree(dentry->d_fsdata);
+ void *fsd = dentry->d_fsdata;
+
+ if (!((unsigned long)fsd & DEBUGFS_FSDATA_IS_REAL_FOPS_BIT))
+ kfree(dentry->d_fsdata);
}
static struct vfsmount *debugfs_automount(struct path *path)
@@ -344,35 +347,25 @@ static struct dentry *__debugfs_create_file(const char *name, umode_t mode,
{
struct dentry *dentry;
struct inode *inode;
- struct debugfs_fsdata *fsd;
-
- fsd = kmalloc(sizeof(*fsd), GFP_KERNEL);
- if (!fsd)
- return NULL;
if (!(mode & S_IFMT))
mode |= S_IFREG;
BUG_ON(!S_ISREG(mode));
dentry = start_creating(name, parent);
- if (IS_ERR(dentry)) {
- kfree(fsd);
+ if (IS_ERR(dentry))
return NULL;
- }
inode = debugfs_get_inode(dentry->d_sb);
- if (unlikely(!inode)) {
- kfree(fsd);
+ if (unlikely(!inode))
return failed_creating(dentry);
- }
inode->i_mode = mode;
inode->i_private = data;
inode->i_fop = proxy_fops;
- fsd->real_fops = real_fops;
- refcount_set(&fsd->active_users, 1);
- dentry->d_fsdata = fsd;
+ dentry->d_fsdata = (void *)((unsigned long)real_fops |
+ DEBUGFS_FSDATA_IS_REAL_FOPS_BIT);
d_instantiate(dentry, inode);
fsnotify_create(d_inode(dentry->d_parent), dentry);
@@ -635,8 +628,17 @@ static void __debugfs_remove_file(struct dentry *dentry, struct dentry *parent)
simple_unlink(d_inode(parent), dentry);
d_delete(dentry);
- fsd = dentry->d_fsdata;
- init_completion(&fsd->active_users_drained);
+
+ /*
+ * Paired with the closing smp_mb() implied by a successful
+ * cmpxchg() in debugfs_file_get(): either
+ * debugfs_file_get() must see a dead dentry or we must see a
+ * debugfs_fsdata instance at ->d_fsdata here (or both).
+ */
+ smp_mb();
+ fsd = READ_ONCE(dentry->d_fsdata);
+ if ((unsigned long)fsd & DEBUGFS_FSDATA_IS_REAL_FOPS_BIT)
+ return;
if (!refcount_dec_and_test(&fsd->active_users))
wait_for_completion(&fsd->active_users_drained);
}