configfs: Fix deadlock with racing rmdir() and rename()

This patch fixes the deadlock between racing sys_rename() and configfs_rmdir().

The idea is to avoid locking i_mutexes of default groups in
configfs_detach_prep(), and rely instead on the new configfs_dirent_lock to
protect against configfs_dirent's linkage mutations. To ensure that an mkdir()
racing with rmdir() will not create new items in a to-be-removed default group,
we make configfs_new_dirent() check for the CONFIGFS_USET_DROPPING flag right
before linking the new dirent, and return error if the flag is set. This makes
racing mkdir()/symlink()/dir_open() fail in places where errors could already
happen, resp. in (attach_item()|attach_group())/create_link()/new_dirent().

configfs_depend() remains safe since it locks all the path from configfs root,
and is thus mutually exclusive with rmdir().

An advantage of this is that now detach_groups() unconditionnaly takes the
default groups i_mutex, which makes it more consistent with populate_groups().

Signed-off-by: Louis Rilling <Louis.Rilling@kerlabs.com>
Signed-off-by: Joel Becker <joel.becker@oracle.com>
This commit is contained in:
Louis Rilling 2008-06-16 19:01:01 +02:00 committed by Mark Fasheh
parent 107ed40bd0
commit b3e76af874
1 changed files with 25 additions and 20 deletions

View File

@ -43,6 +43,10 @@ DECLARE_RWSEM(configfs_rename_sem);
* and configfs_dirent_lock locked, in that order. * and configfs_dirent_lock locked, in that order.
* This allows one to safely traverse configfs_dirent trees and symlinks without * This allows one to safely traverse configfs_dirent trees and symlinks without
* having to lock inodes. * having to lock inodes.
*
* Protects setting of CONFIGFS_USET_DROPPING: checking the flag
* unlocked is not reliable unless in detach_groups() called from
* rmdir()/unregister() and from configfs_attach_group()
*/ */
DEFINE_SPINLOCK(configfs_dirent_lock); DEFINE_SPINLOCK(configfs_dirent_lock);
@ -91,6 +95,11 @@ static struct configfs_dirent *configfs_new_dirent(struct configfs_dirent * pare
INIT_LIST_HEAD(&sd->s_children); INIT_LIST_HEAD(&sd->s_children);
sd->s_element = element; sd->s_element = element;
spin_lock(&configfs_dirent_lock); spin_lock(&configfs_dirent_lock);
if (parent_sd->s_type & CONFIGFS_USET_DROPPING) {
spin_unlock(&configfs_dirent_lock);
kmem_cache_free(configfs_dir_cachep, sd);
return ERR_PTR(-ENOENT);
}
list_add(&sd->s_sibling, &parent_sd->s_children); list_add(&sd->s_sibling, &parent_sd->s_children);
spin_unlock(&configfs_dirent_lock); spin_unlock(&configfs_dirent_lock);
@ -349,11 +358,11 @@ static struct dentry * configfs_lookup(struct inode *dir,
/* /*
* Only subdirectories count here. Files (CONFIGFS_NOT_PINNED) are * Only subdirectories count here. Files (CONFIGFS_NOT_PINNED) are
* attributes and are removed by rmdir(). We recurse, taking i_mutex * attributes and are removed by rmdir(). We recurse, setting
* on all children that are candidates for default detach. If the * CONFIGFS_USET_DROPPING on all children that are candidates for
* result is clean, then configfs_detach_group() will handle dropping * default detach.
* i_mutex. If there is an error, the caller will clean up the i_mutex * If there is an error, the caller will reset the flags via
* holders via configfs_detach_rollback(). * configfs_detach_rollback().
*/ */
static int configfs_detach_prep(struct dentry *dentry) static int configfs_detach_prep(struct dentry *dentry)
{ {
@ -370,8 +379,7 @@ static int configfs_detach_prep(struct dentry *dentry)
if (sd->s_type & CONFIGFS_NOT_PINNED) if (sd->s_type & CONFIGFS_NOT_PINNED)
continue; continue;
if (sd->s_type & CONFIGFS_USET_DEFAULT) { if (sd->s_type & CONFIGFS_USET_DEFAULT) {
mutex_lock(&sd->s_dentry->d_inode->i_mutex); /* Mark that we're trying to drop the group */
/* Mark that we've taken i_mutex */
sd->s_type |= CONFIGFS_USET_DROPPING; sd->s_type |= CONFIGFS_USET_DROPPING;
/* /*
@ -392,7 +400,7 @@ out:
} }
/* /*
* Walk the tree, dropping i_mutex wherever CONFIGFS_USET_DROPPING is * Walk the tree, resetting CONFIGFS_USET_DROPPING wherever it was
* set. * set.
*/ */
static void configfs_detach_rollback(struct dentry *dentry) static void configfs_detach_rollback(struct dentry *dentry)
@ -403,11 +411,7 @@ static void configfs_detach_rollback(struct dentry *dentry)
list_for_each_entry(sd, &parent_sd->s_children, s_sibling) { list_for_each_entry(sd, &parent_sd->s_children, s_sibling) {
if (sd->s_type & CONFIGFS_USET_DEFAULT) { if (sd->s_type & CONFIGFS_USET_DEFAULT) {
configfs_detach_rollback(sd->s_dentry); configfs_detach_rollback(sd->s_dentry);
sd->s_type &= ~CONFIGFS_USET_DROPPING;
if (sd->s_type & CONFIGFS_USET_DROPPING) {
sd->s_type &= ~CONFIGFS_USET_DROPPING;
mutex_unlock(&sd->s_dentry->d_inode->i_mutex);
}
} }
} }
} }
@ -486,16 +490,12 @@ static void detach_groups(struct config_group *group)
child = sd->s_dentry; child = sd->s_dentry;
mutex_lock(&child->d_inode->i_mutex);
configfs_detach_group(sd->s_element); configfs_detach_group(sd->s_element);
child->d_inode->i_flags |= S_DEAD; child->d_inode->i_flags |= S_DEAD;
/* mutex_unlock(&child->d_inode->i_mutex);
* From rmdir/unregister, a configfs_detach_prep() pass
* has taken our i_mutex for us. Drop it.
* From mkdir/register cleanup, there is no sem held.
*/
if (sd->s_type & CONFIGFS_USET_DROPPING)
mutex_unlock(&child->d_inode->i_mutex);
d_delete(child); d_delete(child);
dput(child); dput(child);
@ -1181,12 +1181,15 @@ static int configfs_rmdir(struct inode *dir, struct dentry *dentry)
return -EINVAL; return -EINVAL;
} }
spin_lock(&configfs_dirent_lock);
ret = configfs_detach_prep(dentry); ret = configfs_detach_prep(dentry);
if (ret) { if (ret) {
configfs_detach_rollback(dentry); configfs_detach_rollback(dentry);
spin_unlock(&configfs_dirent_lock);
config_item_put(parent_item); config_item_put(parent_item);
return ret; return ret;
} }
spin_unlock(&configfs_dirent_lock);
/* Get a working ref for the duration of this function */ /* Get a working ref for the duration of this function */
item = configfs_get_config_item(dentry); item = configfs_get_config_item(dentry);
@ -1476,9 +1479,11 @@ void configfs_unregister_subsystem(struct configfs_subsystem *subsys)
mutex_lock_nested(&configfs_sb->s_root->d_inode->i_mutex, mutex_lock_nested(&configfs_sb->s_root->d_inode->i_mutex,
I_MUTEX_PARENT); I_MUTEX_PARENT);
mutex_lock_nested(&dentry->d_inode->i_mutex, I_MUTEX_CHILD); mutex_lock_nested(&dentry->d_inode->i_mutex, I_MUTEX_CHILD);
spin_lock(&configfs_dirent_lock);
if (configfs_detach_prep(dentry)) { if (configfs_detach_prep(dentry)) {
printk(KERN_ERR "configfs: Tried to unregister non-empty subsystem!\n"); printk(KERN_ERR "configfs: Tried to unregister non-empty subsystem!\n");
} }
spin_unlock(&configfs_dirent_lock);
configfs_detach_group(&group->cg_item); configfs_detach_group(&group->cg_item);
dentry->d_inode->i_flags |= S_DEAD; dentry->d_inode->i_flags |= S_DEAD;
mutex_unlock(&dentry->d_inode->i_mutex); mutex_unlock(&dentry->d_inode->i_mutex);