diff options
Diffstat (limited to 'fs/debugfs/inode.c')
| -rw-r--r-- | fs/debugfs/inode.c | 39 | 
1 files changed, 29 insertions, 10 deletions
diff --git a/fs/debugfs/inode.c b/fs/debugfs/inode.c index 8c41b52da358..1e3b99d3db0d 100644 --- a/fs/debugfs/inode.c +++ b/fs/debugfs/inode.c @@ -66,7 +66,7 @@ static struct inode *debugfs_get_inode(struct super_block *sb, umode_t mode, dev  			break;  		}  	} -	return inode;  +	return inode;  }  /* SMP-safe */ @@ -317,7 +317,7 @@ static struct dentry *__create_file(const char *name, umode_t mode,  		goto exit;  	/* If the parent is not specified, we create it in the root. -	 * We need the root dentry to do this, which is in the super  +	 * We need the root dentry to do this, which is in the super  	 * block. A pointer to that is in the struct vfsmount that we  	 * have around.  	 */ @@ -330,7 +330,7 @@ static struct dentry *__create_file(const char *name, umode_t mode,  		switch (mode & S_IFMT) {  		case S_IFDIR:  			error = debugfs_mkdir(parent->d_inode, dentry, mode); -					       +  			break;  		case S_IFLNK:  			error = debugfs_link(parent->d_inode, dentry, mode, @@ -534,7 +534,7 @@ EXPORT_SYMBOL_GPL(debugfs_remove);   */  void debugfs_remove_recursive(struct dentry *dentry)  { -	struct dentry *child, *next, *parent; +	struct dentry *child, *parent;  	if (IS_ERR_OR_NULL(dentry))  		return; @@ -546,30 +546,49 @@ void debugfs_remove_recursive(struct dentry *dentry)  	parent = dentry;   down:  	mutex_lock(&parent->d_inode->i_mutex); -	list_for_each_entry_safe(child, next, &parent->d_subdirs, d_u.d_child) { + loop: +	/* +	 * The parent->d_subdirs is protected by the d_lock. Outside that +	 * lock, the child can be unlinked and set to be freed which can +	 * use the d_u.d_child as the rcu head and corrupt this list. +	 */ +	spin_lock(&parent->d_lock); +	list_for_each_entry(child, &parent->d_subdirs, d_u.d_child) {  		if (!debugfs_positive(child))  			continue;  		/* perhaps simple_empty(child) makes more sense */  		if (!list_empty(&child->d_subdirs)) { +			spin_unlock(&parent->d_lock);  			mutex_unlock(&parent->d_inode->i_mutex);  			parent = child;  			goto down;  		} - up: + +		spin_unlock(&parent->d_lock); +  		if (!__debugfs_remove(child, parent))  			simple_release_fs(&debugfs_mount, &debugfs_mount_count); + +		/* +		 * The parent->d_lock protects agaist child from unlinking +		 * from d_subdirs. When releasing the parent->d_lock we can +		 * no longer trust that the next pointer is valid. +		 * Restart the loop. We'll skip this one with the +		 * debugfs_positive() check. +		 */ +		goto loop;  	} +	spin_unlock(&parent->d_lock);  	mutex_unlock(&parent->d_inode->i_mutex);  	child = parent;  	parent = parent->d_parent;  	mutex_lock(&parent->d_inode->i_mutex); -	if (child != dentry) { -		next = list_next_entry(child, d_u.d_child); -		goto up; -	} +	if (child != dentry) +		/* go up */ +		goto loop;  	if (!__debugfs_remove(child, parent))  		simple_release_fs(&debugfs_mount, &debugfs_mount_count);  |