|
@@ -30,11 +30,25 @@
|
|
|
#include <linux/mount.h>
|
|
|
#include <linux/module.h>
|
|
|
#include <linux/slab.h>
|
|
|
+#include <linux/err.h>
|
|
|
|
|
|
#include <linux/configfs.h>
|
|
|
#include "configfs_internal.h"
|
|
|
|
|
|
DECLARE_RWSEM(configfs_rename_sem);
|
|
|
+/*
|
|
|
+ * Protects mutations of configfs_dirent linkage together with proper i_mutex
|
|
|
+ * Also protects mutations of symlinks linkage to target configfs_dirent
|
|
|
+ * Mutators of configfs_dirent linkage must *both* have the proper inode locked
|
|
|
+ * and configfs_dirent_lock locked, in that order.
|
|
|
+ * This allows one to safely traverse configfs_dirent trees and symlinks without
|
|
|
+ * 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);
|
|
|
|
|
|
static void configfs_d_iput(struct dentry * dentry,
|
|
|
struct inode * inode)
|
|
@@ -74,13 +88,20 @@ static struct configfs_dirent *configfs_new_dirent(struct configfs_dirent * pare
|
|
|
|
|
|
sd = kmem_cache_zalloc(configfs_dir_cachep, GFP_KERNEL);
|
|
|
if (!sd)
|
|
|
- return NULL;
|
|
|
+ return ERR_PTR(-ENOMEM);
|
|
|
|
|
|
atomic_set(&sd->s_count, 1);
|
|
|
INIT_LIST_HEAD(&sd->s_links);
|
|
|
INIT_LIST_HEAD(&sd->s_children);
|
|
|
- list_add(&sd->s_sibling, &parent_sd->s_children);
|
|
|
sd->s_element = element;
|
|
|
+ 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);
|
|
|
+ spin_unlock(&configfs_dirent_lock);
|
|
|
|
|
|
return sd;
|
|
|
}
|
|
@@ -118,8 +139,8 @@ int configfs_make_dirent(struct configfs_dirent * parent_sd,
|
|
|
struct configfs_dirent * sd;
|
|
|
|
|
|
sd = configfs_new_dirent(parent_sd, element);
|
|
|
- if (!sd)
|
|
|
- return -ENOMEM;
|
|
|
+ if (IS_ERR(sd))
|
|
|
+ return PTR_ERR(sd);
|
|
|
|
|
|
sd->s_mode = mode;
|
|
|
sd->s_type = type;
|
|
@@ -173,7 +194,9 @@ static int create_dir(struct config_item * k, struct dentry * p,
|
|
|
} else {
|
|
|
struct configfs_dirent *sd = d->d_fsdata;
|
|
|
if (sd) {
|
|
|
+ spin_lock(&configfs_dirent_lock);
|
|
|
list_del_init(&sd->s_sibling);
|
|
|
+ spin_unlock(&configfs_dirent_lock);
|
|
|
configfs_put(sd);
|
|
|
}
|
|
|
}
|
|
@@ -224,7 +247,9 @@ int configfs_create_link(struct configfs_symlink *sl,
|
|
|
else {
|
|
|
struct configfs_dirent *sd = dentry->d_fsdata;
|
|
|
if (sd) {
|
|
|
+ spin_lock(&configfs_dirent_lock);
|
|
|
list_del_init(&sd->s_sibling);
|
|
|
+ spin_unlock(&configfs_dirent_lock);
|
|
|
configfs_put(sd);
|
|
|
}
|
|
|
}
|
|
@@ -238,7 +263,9 @@ static void remove_dir(struct dentry * d)
|
|
|
struct configfs_dirent * sd;
|
|
|
|
|
|
sd = d->d_fsdata;
|
|
|
+ spin_lock(&configfs_dirent_lock);
|
|
|
list_del_init(&sd->s_sibling);
|
|
|
+ spin_unlock(&configfs_dirent_lock);
|
|
|
configfs_put(sd);
|
|
|
if (d->d_inode)
|
|
|
simple_rmdir(parent->d_inode,d);
|
|
@@ -331,13 +358,13 @@ static struct dentry * configfs_lookup(struct inode *dir,
|
|
|
|
|
|
/*
|
|
|
* Only subdirectories count here. Files (CONFIGFS_NOT_PINNED) are
|
|
|
- * attributes and are removed by rmdir(). We recurse, taking i_mutex
|
|
|
- * on all children that are candidates for default detach. If the
|
|
|
- * result is clean, then configfs_detach_group() will handle dropping
|
|
|
- * i_mutex. If there is an error, the caller will clean up the i_mutex
|
|
|
- * holders via configfs_detach_rollback().
|
|
|
+ * attributes and are removed by rmdir(). We recurse, setting
|
|
|
+ * CONFIGFS_USET_DROPPING on all children that are candidates for
|
|
|
+ * default detach.
|
|
|
+ * If there is an error, the caller will reset the flags via
|
|
|
+ * configfs_detach_rollback().
|
|
|
*/
|
|
|
-static int configfs_detach_prep(struct dentry *dentry)
|
|
|
+static int configfs_detach_prep(struct dentry *dentry, struct mutex **wait_mutex)
|
|
|
{
|
|
|
struct configfs_dirent *parent_sd = dentry->d_fsdata;
|
|
|
struct configfs_dirent *sd;
|
|
@@ -352,15 +379,20 @@ static int configfs_detach_prep(struct dentry *dentry)
|
|
|
if (sd->s_type & CONFIGFS_NOT_PINNED)
|
|
|
continue;
|
|
|
if (sd->s_type & CONFIGFS_USET_DEFAULT) {
|
|
|
- mutex_lock(&sd->s_dentry->d_inode->i_mutex);
|
|
|
- /* Mark that we've taken i_mutex */
|
|
|
+ /* Abort if racing with mkdir() */
|
|
|
+ if (sd->s_type & CONFIGFS_USET_IN_MKDIR) {
|
|
|
+ if (wait_mutex)
|
|
|
+ *wait_mutex = &sd->s_dentry->d_inode->i_mutex;
|
|
|
+ return -EAGAIN;
|
|
|
+ }
|
|
|
+ /* Mark that we're trying to drop the group */
|
|
|
sd->s_type |= CONFIGFS_USET_DROPPING;
|
|
|
|
|
|
/*
|
|
|
* Yup, recursive. If there's a problem, blame
|
|
|
* deep nesting of default_groups
|
|
|
*/
|
|
|
- ret = configfs_detach_prep(sd->s_dentry);
|
|
|
+ ret = configfs_detach_prep(sd->s_dentry, wait_mutex);
|
|
|
if (!ret)
|
|
|
continue;
|
|
|
} else
|
|
@@ -374,7 +406,7 @@ out:
|
|
|
}
|
|
|
|
|
|
/*
|
|
|
- * Walk the tree, dropping i_mutex wherever CONFIGFS_USET_DROPPING is
|
|
|
+ * Walk the tree, resetting CONFIGFS_USET_DROPPING wherever it was
|
|
|
* set.
|
|
|
*/
|
|
|
static void configfs_detach_rollback(struct dentry *dentry)
|
|
@@ -385,11 +417,7 @@ static void configfs_detach_rollback(struct dentry *dentry)
|
|
|
list_for_each_entry(sd, &parent_sd->s_children, s_sibling) {
|
|
|
if (sd->s_type & CONFIGFS_USET_DEFAULT) {
|
|
|
configfs_detach_rollback(sd->s_dentry);
|
|
|
-
|
|
|
- if (sd->s_type & CONFIGFS_USET_DROPPING) {
|
|
|
- sd->s_type &= ~CONFIGFS_USET_DROPPING;
|
|
|
- mutex_unlock(&sd->s_dentry->d_inode->i_mutex);
|
|
|
- }
|
|
|
+ sd->s_type &= ~CONFIGFS_USET_DROPPING;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
@@ -410,7 +438,9 @@ static void detach_attrs(struct config_item * item)
|
|
|
list_for_each_entry_safe(sd, tmp, &parent_sd->s_children, s_sibling) {
|
|
|
if (!sd->s_element || !(sd->s_type & CONFIGFS_NOT_PINNED))
|
|
|
continue;
|
|
|
+ spin_lock(&configfs_dirent_lock);
|
|
|
list_del_init(&sd->s_sibling);
|
|
|
+ spin_unlock(&configfs_dirent_lock);
|
|
|
configfs_drop_dentry(sd, dentry);
|
|
|
configfs_put(sd);
|
|
|
}
|
|
@@ -466,16 +496,12 @@ static void detach_groups(struct config_group *group)
|
|
|
|
|
|
child = sd->s_dentry;
|
|
|
|
|
|
+ mutex_lock(&child->d_inode->i_mutex);
|
|
|
+
|
|
|
configfs_detach_group(sd->s_element);
|
|
|
child->d_inode->i_flags |= S_DEAD;
|
|
|
|
|
|
- /*
|
|
|
- * 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);
|
|
|
+ mutex_unlock(&child->d_inode->i_mutex);
|
|
|
|
|
|
d_delete(child);
|
|
|
dput(child);
|
|
@@ -1047,25 +1073,24 @@ static int configfs_mkdir(struct inode *dir, struct dentry *dentry, int mode)
|
|
|
group = NULL;
|
|
|
item = NULL;
|
|
|
if (type->ct_group_ops->make_group) {
|
|
|
- group = type->ct_group_ops->make_group(to_config_group(parent_item), name);
|
|
|
- if (group) {
|
|
|
+ ret = type->ct_group_ops->make_group(to_config_group(parent_item), name, &group);
|
|
|
+ if (!ret) {
|
|
|
link_group(to_config_group(parent_item), group);
|
|
|
item = &group->cg_item;
|
|
|
}
|
|
|
} else {
|
|
|
- item = type->ct_group_ops->make_item(to_config_group(parent_item), name);
|
|
|
- if (item)
|
|
|
+ ret = type->ct_group_ops->make_item(to_config_group(parent_item), name, &item);
|
|
|
+ if (!ret)
|
|
|
link_obj(parent_item, item);
|
|
|
}
|
|
|
mutex_unlock(&subsys->su_mutex);
|
|
|
|
|
|
kfree(name);
|
|
|
- if (!item) {
|
|
|
+ if (ret) {
|
|
|
/*
|
|
|
- * If item == NULL, then link_obj() was never called.
|
|
|
+ * If ret != 0, then link_obj() was never called.
|
|
|
* There are no extra references to clean up.
|
|
|
*/
|
|
|
- ret = -ENOMEM;
|
|
|
goto out_put;
|
|
|
}
|
|
|
|
|
@@ -1093,11 +1118,26 @@ static int configfs_mkdir(struct inode *dir, struct dentry *dentry, int mode)
|
|
|
*/
|
|
|
module_got = 1;
|
|
|
|
|
|
+ /*
|
|
|
+ * Make racing rmdir() fail if it did not tag parent with
|
|
|
+ * CONFIGFS_USET_DROPPING
|
|
|
+ * Note: if CONFIGFS_USET_DROPPING is already set, attach_group() will
|
|
|
+ * fail and let rmdir() terminate correctly
|
|
|
+ */
|
|
|
+ spin_lock(&configfs_dirent_lock);
|
|
|
+ /* This will make configfs_detach_prep() fail */
|
|
|
+ sd->s_type |= CONFIGFS_USET_IN_MKDIR;
|
|
|
+ spin_unlock(&configfs_dirent_lock);
|
|
|
+
|
|
|
if (group)
|
|
|
ret = configfs_attach_group(parent_item, item, dentry);
|
|
|
else
|
|
|
ret = configfs_attach_item(parent_item, item, dentry);
|
|
|
|
|
|
+ spin_lock(&configfs_dirent_lock);
|
|
|
+ sd->s_type &= ~CONFIGFS_USET_IN_MKDIR;
|
|
|
+ spin_unlock(&configfs_dirent_lock);
|
|
|
+
|
|
|
out_unlink:
|
|
|
if (ret) {
|
|
|
/* Tear down everything we built up */
|
|
@@ -1161,12 +1201,27 @@ static int configfs_rmdir(struct inode *dir, struct dentry *dentry)
|
|
|
return -EINVAL;
|
|
|
}
|
|
|
|
|
|
- ret = configfs_detach_prep(dentry);
|
|
|
- if (ret) {
|
|
|
- configfs_detach_rollback(dentry);
|
|
|
- config_item_put(parent_item);
|
|
|
- return ret;
|
|
|
- }
|
|
|
+ spin_lock(&configfs_dirent_lock);
|
|
|
+ do {
|
|
|
+ struct mutex *wait_mutex;
|
|
|
+
|
|
|
+ ret = configfs_detach_prep(dentry, &wait_mutex);
|
|
|
+ if (ret) {
|
|
|
+ configfs_detach_rollback(dentry);
|
|
|
+ spin_unlock(&configfs_dirent_lock);
|
|
|
+ if (ret != -EAGAIN) {
|
|
|
+ config_item_put(parent_item);
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Wait until the racing operation terminates */
|
|
|
+ mutex_lock(wait_mutex);
|
|
|
+ mutex_unlock(wait_mutex);
|
|
|
+
|
|
|
+ spin_lock(&configfs_dirent_lock);
|
|
|
+ }
|
|
|
+ } while (ret == -EAGAIN);
|
|
|
+ spin_unlock(&configfs_dirent_lock);
|
|
|
|
|
|
/* Get a working ref for the duration of this function */
|
|
|
item = configfs_get_config_item(dentry);
|
|
@@ -1258,7 +1313,7 @@ static int configfs_dir_open(struct inode *inode, struct file *file)
|
|
|
file->private_data = configfs_new_dirent(parent_sd, NULL);
|
|
|
mutex_unlock(&dentry->d_inode->i_mutex);
|
|
|
|
|
|
- return file->private_data ? 0 : -ENOMEM;
|
|
|
+ return IS_ERR(file->private_data) ? PTR_ERR(file->private_data) : 0;
|
|
|
|
|
|
}
|
|
|
|
|
@@ -1268,7 +1323,9 @@ static int configfs_dir_close(struct inode *inode, struct file *file)
|
|
|
struct configfs_dirent * cursor = file->private_data;
|
|
|
|
|
|
mutex_lock(&dentry->d_inode->i_mutex);
|
|
|
+ spin_lock(&configfs_dirent_lock);
|
|
|
list_del_init(&cursor->s_sibling);
|
|
|
+ spin_unlock(&configfs_dirent_lock);
|
|
|
mutex_unlock(&dentry->d_inode->i_mutex);
|
|
|
|
|
|
release_configfs_dirent(cursor);
|
|
@@ -1308,7 +1365,9 @@ static int configfs_readdir(struct file * filp, void * dirent, filldir_t filldir
|
|
|
/* fallthrough */
|
|
|
default:
|
|
|
if (filp->f_pos == 2) {
|
|
|
+ spin_lock(&configfs_dirent_lock);
|
|
|
list_move(q, &parent_sd->s_children);
|
|
|
+ spin_unlock(&configfs_dirent_lock);
|
|
|
}
|
|
|
for (p=q->next; p!= &parent_sd->s_children; p=p->next) {
|
|
|
struct configfs_dirent *next;
|
|
@@ -1331,7 +1390,9 @@ static int configfs_readdir(struct file * filp, void * dirent, filldir_t filldir
|
|
|
dt_type(next)) < 0)
|
|
|
return 0;
|
|
|
|
|
|
+ spin_lock(&configfs_dirent_lock);
|
|
|
list_move(q, p);
|
|
|
+ spin_unlock(&configfs_dirent_lock);
|
|
|
p = q;
|
|
|
filp->f_pos++;
|
|
|
}
|
|
@@ -1362,6 +1423,7 @@ static loff_t configfs_dir_lseek(struct file * file, loff_t offset, int origin)
|
|
|
struct list_head *p;
|
|
|
loff_t n = file->f_pos - 2;
|
|
|
|
|
|
+ spin_lock(&configfs_dirent_lock);
|
|
|
list_del(&cursor->s_sibling);
|
|
|
p = sd->s_children.next;
|
|
|
while (n && p != &sd->s_children) {
|
|
@@ -1373,6 +1435,7 @@ static loff_t configfs_dir_lseek(struct file * file, loff_t offset, int origin)
|
|
|
p = p->next;
|
|
|
}
|
|
|
list_add_tail(&cursor->s_sibling, p);
|
|
|
+ spin_unlock(&configfs_dirent_lock);
|
|
|
}
|
|
|
}
|
|
|
mutex_unlock(&dentry->d_inode->i_mutex);
|
|
@@ -1448,9 +1511,11 @@ void configfs_unregister_subsystem(struct configfs_subsystem *subsys)
|
|
|
mutex_lock_nested(&configfs_sb->s_root->d_inode->i_mutex,
|
|
|
I_MUTEX_PARENT);
|
|
|
mutex_lock_nested(&dentry->d_inode->i_mutex, I_MUTEX_CHILD);
|
|
|
- if (configfs_detach_prep(dentry)) {
|
|
|
+ spin_lock(&configfs_dirent_lock);
|
|
|
+ if (configfs_detach_prep(dentry, NULL)) {
|
|
|
printk(KERN_ERR "configfs: Tried to unregister non-empty subsystem!\n");
|
|
|
}
|
|
|
+ spin_unlock(&configfs_dirent_lock);
|
|
|
configfs_detach_group(&group->cg_item);
|
|
|
dentry->d_inode->i_flags |= S_DEAD;
|
|
|
mutex_unlock(&dentry->d_inode->i_mutex);
|