|
@@ -185,7 +185,7 @@ static int create_dir(struct config_item * k, struct dentry * p,
|
|
|
error = configfs_dirent_exists(p->d_fsdata, d->d_name.name);
|
|
|
if (!error)
|
|
|
error = configfs_make_dirent(p->d_fsdata, d, k, mode,
|
|
|
- CONFIGFS_DIR);
|
|
|
+ CONFIGFS_DIR | CONFIGFS_USET_CREATING);
|
|
|
if (!error) {
|
|
|
error = configfs_create(d, mode, init_dir);
|
|
|
if (!error) {
|
|
@@ -209,6 +209,9 @@ static int create_dir(struct config_item * k, struct dentry * p,
|
|
|
* configfs_create_dir - create a directory for an config_item.
|
|
|
* @item: config_itemwe're creating directory for.
|
|
|
* @dentry: config_item's dentry.
|
|
|
+ *
|
|
|
+ * Note: user-created entries won't be allowed under this new directory
|
|
|
+ * until it is validated by configfs_dir_set_ready()
|
|
|
*/
|
|
|
|
|
|
static int configfs_create_dir(struct config_item * item, struct dentry *dentry)
|
|
@@ -231,6 +234,44 @@ static int configfs_create_dir(struct config_item * item, struct dentry *dentry)
|
|
|
return error;
|
|
|
}
|
|
|
|
|
|
+/*
|
|
|
+ * Allow userspace to create new entries under a new directory created with
|
|
|
+ * configfs_create_dir(), and under all of its chidlren directories recursively.
|
|
|
+ * @sd configfs_dirent of the new directory to validate
|
|
|
+ *
|
|
|
+ * Caller must hold configfs_dirent_lock.
|
|
|
+ */
|
|
|
+static void configfs_dir_set_ready(struct configfs_dirent *sd)
|
|
|
+{
|
|
|
+ struct configfs_dirent *child_sd;
|
|
|
+
|
|
|
+ sd->s_type &= ~CONFIGFS_USET_CREATING;
|
|
|
+ list_for_each_entry(child_sd, &sd->s_children, s_sibling)
|
|
|
+ if (child_sd->s_type & CONFIGFS_USET_CREATING)
|
|
|
+ configfs_dir_set_ready(child_sd);
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ * Check that a directory does not belong to a directory hierarchy being
|
|
|
+ * attached and not validated yet.
|
|
|
+ * @sd configfs_dirent of the directory to check
|
|
|
+ *
|
|
|
+ * @return non-zero iff the directory was validated
|
|
|
+ *
|
|
|
+ * Note: takes configfs_dirent_lock, so the result may change from false to true
|
|
|
+ * in two consecutive calls, but never from true to false.
|
|
|
+ */
|
|
|
+int configfs_dirent_is_ready(struct configfs_dirent *sd)
|
|
|
+{
|
|
|
+ int ret;
|
|
|
+
|
|
|
+ spin_lock(&configfs_dirent_lock);
|
|
|
+ ret = !(sd->s_type & CONFIGFS_USET_CREATING);
|
|
|
+ spin_unlock(&configfs_dirent_lock);
|
|
|
+
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
int configfs_create_link(struct configfs_symlink *sl,
|
|
|
struct dentry *parent,
|
|
|
struct dentry *dentry)
|
|
@@ -283,6 +324,8 @@ static void remove_dir(struct dentry * d)
|
|
|
* The only thing special about this is that we remove any files in
|
|
|
* the directory before we remove the directory, and we've inlined
|
|
|
* what used to be configfs_rmdir() below, instead of calling separately.
|
|
|
+ *
|
|
|
+ * Caller holds the mutex of the item's inode
|
|
|
*/
|
|
|
|
|
|
static void configfs_remove_dir(struct config_item * item)
|
|
@@ -330,7 +373,19 @@ static struct dentry * configfs_lookup(struct inode *dir,
|
|
|
struct configfs_dirent * parent_sd = dentry->d_parent->d_fsdata;
|
|
|
struct configfs_dirent * sd;
|
|
|
int found = 0;
|
|
|
- int err = 0;
|
|
|
+ int err;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Fake invisibility if dir belongs to a group/default groups hierarchy
|
|
|
+ * being attached
|
|
|
+ *
|
|
|
+ * This forbids userspace to read/write attributes of items which may
|
|
|
+ * not complete their initialization, since the dentries of the
|
|
|
+ * attributes won't be instantiated.
|
|
|
+ */
|
|
|
+ err = -ENOENT;
|
|
|
+ if (!configfs_dirent_is_ready(parent_sd))
|
|
|
+ goto out;
|
|
|
|
|
|
list_for_each_entry(sd, &parent_sd->s_children, s_sibling) {
|
|
|
if (sd->s_type & CONFIGFS_NOT_PINNED) {
|
|
@@ -353,6 +408,7 @@ static struct dentry * configfs_lookup(struct inode *dir,
|
|
|
return simple_lookup(dir, dentry, nd);
|
|
|
}
|
|
|
|
|
|
+out:
|
|
|
return ERR_PTR(err);
|
|
|
}
|
|
|
|
|
@@ -370,13 +426,17 @@ static int configfs_detach_prep(struct dentry *dentry, struct mutex **wait_mutex
|
|
|
struct configfs_dirent *sd;
|
|
|
int ret;
|
|
|
|
|
|
+ /* Mark that we're trying to drop the group */
|
|
|
+ parent_sd->s_type |= CONFIGFS_USET_DROPPING;
|
|
|
+
|
|
|
ret = -EBUSY;
|
|
|
if (!list_empty(&parent_sd->s_links))
|
|
|
goto out;
|
|
|
|
|
|
ret = 0;
|
|
|
list_for_each_entry(sd, &parent_sd->s_children, s_sibling) {
|
|
|
- if (sd->s_type & CONFIGFS_NOT_PINNED)
|
|
|
+ if (!sd->s_element ||
|
|
|
+ (sd->s_type & CONFIGFS_NOT_PINNED))
|
|
|
continue;
|
|
|
if (sd->s_type & CONFIGFS_USET_DEFAULT) {
|
|
|
/* Abort if racing with mkdir() */
|
|
@@ -385,8 +445,6 @@ static int configfs_detach_prep(struct dentry *dentry, struct mutex **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
|
|
@@ -414,12 +472,11 @@ static void configfs_detach_rollback(struct dentry *dentry)
|
|
|
struct configfs_dirent *parent_sd = dentry->d_fsdata;
|
|
|
struct configfs_dirent *sd;
|
|
|
|
|
|
- list_for_each_entry(sd, &parent_sd->s_children, s_sibling) {
|
|
|
- if (sd->s_type & CONFIGFS_USET_DEFAULT) {
|
|
|
+ parent_sd->s_type &= ~CONFIGFS_USET_DROPPING;
|
|
|
+
|
|
|
+ list_for_each_entry(sd, &parent_sd->s_children, s_sibling)
|
|
|
+ if (sd->s_type & CONFIGFS_USET_DEFAULT)
|
|
|
configfs_detach_rollback(sd->s_dentry);
|
|
|
- sd->s_type &= ~CONFIGFS_USET_DROPPING;
|
|
|
- }
|
|
|
- }
|
|
|
}
|
|
|
|
|
|
static void detach_attrs(struct config_item * item)
|
|
@@ -558,36 +615,21 @@ static int create_default_group(struct config_group *parent_group,
|
|
|
static int populate_groups(struct config_group *group)
|
|
|
{
|
|
|
struct config_group *new_group;
|
|
|
- struct dentry *dentry = group->cg_item.ci_dentry;
|
|
|
int ret = 0;
|
|
|
int i;
|
|
|
|
|
|
if (group->default_groups) {
|
|
|
- /*
|
|
|
- * FYI, we're faking mkdir here
|
|
|
- * I'm not sure we need this semaphore, as we're called
|
|
|
- * from our parent's mkdir. That holds our parent's
|
|
|
- * i_mutex, so afaik lookup cannot continue through our
|
|
|
- * parent to find us, let alone mess with our tree.
|
|
|
- * That said, taking our i_mutex is closer to mkdir
|
|
|
- * emulation, and shouldn't hurt.
|
|
|
- */
|
|
|
- mutex_lock_nested(&dentry->d_inode->i_mutex, I_MUTEX_CHILD);
|
|
|
-
|
|
|
for (i = 0; group->default_groups[i]; i++) {
|
|
|
new_group = group->default_groups[i];
|
|
|
|
|
|
ret = create_default_group(group, new_group);
|
|
|
- if (ret)
|
|
|
+ if (ret) {
|
|
|
+ detach_groups(group);
|
|
|
break;
|
|
|
+ }
|
|
|
}
|
|
|
-
|
|
|
- mutex_unlock(&dentry->d_inode->i_mutex);
|
|
|
}
|
|
|
|
|
|
- if (ret)
|
|
|
- detach_groups(group);
|
|
|
-
|
|
|
return ret;
|
|
|
}
|
|
|
|
|
@@ -702,7 +744,15 @@ static int configfs_attach_item(struct config_item *parent_item,
|
|
|
if (!ret) {
|
|
|
ret = populate_attrs(item);
|
|
|
if (ret) {
|
|
|
+ /*
|
|
|
+ * We are going to remove an inode and its dentry but
|
|
|
+ * the VFS may already have hit and used them. Thus,
|
|
|
+ * we must lock them as rmdir() would.
|
|
|
+ */
|
|
|
+ mutex_lock(&dentry->d_inode->i_mutex);
|
|
|
configfs_remove_dir(item);
|
|
|
+ dentry->d_inode->i_flags |= S_DEAD;
|
|
|
+ mutex_unlock(&dentry->d_inode->i_mutex);
|
|
|
d_delete(dentry);
|
|
|
}
|
|
|
}
|
|
@@ -710,6 +760,7 @@ static int configfs_attach_item(struct config_item *parent_item,
|
|
|
return ret;
|
|
|
}
|
|
|
|
|
|
+/* Caller holds the mutex of the item's inode */
|
|
|
static void configfs_detach_item(struct config_item *item)
|
|
|
{
|
|
|
detach_attrs(item);
|
|
@@ -728,16 +779,30 @@ static int configfs_attach_group(struct config_item *parent_item,
|
|
|
sd = dentry->d_fsdata;
|
|
|
sd->s_type |= CONFIGFS_USET_DIR;
|
|
|
|
|
|
+ /*
|
|
|
+ * FYI, we're faking mkdir in populate_groups()
|
|
|
+ * We must lock the group's inode to avoid races with the VFS
|
|
|
+ * which can already hit the inode and try to add/remove entries
|
|
|
+ * under it.
|
|
|
+ *
|
|
|
+ * We must also lock the inode to remove it safely in case of
|
|
|
+ * error, as rmdir() would.
|
|
|
+ */
|
|
|
+ mutex_lock_nested(&dentry->d_inode->i_mutex, I_MUTEX_CHILD);
|
|
|
ret = populate_groups(to_config_group(item));
|
|
|
if (ret) {
|
|
|
configfs_detach_item(item);
|
|
|
- d_delete(dentry);
|
|
|
+ dentry->d_inode->i_flags |= S_DEAD;
|
|
|
}
|
|
|
+ mutex_unlock(&dentry->d_inode->i_mutex);
|
|
|
+ if (ret)
|
|
|
+ d_delete(dentry);
|
|
|
}
|
|
|
|
|
|
return ret;
|
|
|
}
|
|
|
|
|
|
+/* Caller holds the mutex of the group's inode */
|
|
|
static void configfs_detach_group(struct config_item *item)
|
|
|
{
|
|
|
detach_groups(to_config_group(item));
|
|
@@ -1035,7 +1100,7 @@ static int configfs_mkdir(struct inode *dir, struct dentry *dentry, int mode)
|
|
|
struct configfs_subsystem *subsys;
|
|
|
struct configfs_dirent *sd;
|
|
|
struct config_item_type *type;
|
|
|
- struct module *owner = NULL;
|
|
|
+ struct module *subsys_owner = NULL, *new_item_owner = NULL;
|
|
|
char *name;
|
|
|
|
|
|
if (dentry->d_parent == configfs_sb->s_root) {
|
|
@@ -1044,6 +1109,16 @@ static int configfs_mkdir(struct inode *dir, struct dentry *dentry, int mode)
|
|
|
}
|
|
|
|
|
|
sd = dentry->d_parent->d_fsdata;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Fake invisibility if dir belongs to a group/default groups hierarchy
|
|
|
+ * being attached
|
|
|
+ */
|
|
|
+ if (!configfs_dirent_is_ready(sd)) {
|
|
|
+ ret = -ENOENT;
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
+
|
|
|
if (!(sd->s_type & CONFIGFS_USET_DIR)) {
|
|
|
ret = -EPERM;
|
|
|
goto out;
|
|
@@ -1062,10 +1137,25 @@ static int configfs_mkdir(struct inode *dir, struct dentry *dentry, int mode)
|
|
|
goto out_put;
|
|
|
}
|
|
|
|
|
|
+ /*
|
|
|
+ * The subsystem may belong to a different module than the item
|
|
|
+ * being created. We don't want to safely pin the new item but
|
|
|
+ * fail to pin the subsystem it sits under.
|
|
|
+ */
|
|
|
+ if (!subsys->su_group.cg_item.ci_type) {
|
|
|
+ ret = -EINVAL;
|
|
|
+ goto out_put;
|
|
|
+ }
|
|
|
+ subsys_owner = subsys->su_group.cg_item.ci_type->ct_owner;
|
|
|
+ if (!try_module_get(subsys_owner)) {
|
|
|
+ ret = -EINVAL;
|
|
|
+ goto out_put;
|
|
|
+ }
|
|
|
+
|
|
|
name = kmalloc(dentry->d_name.len + 1, GFP_KERNEL);
|
|
|
if (!name) {
|
|
|
ret = -ENOMEM;
|
|
|
- goto out_put;
|
|
|
+ goto out_subsys_put;
|
|
|
}
|
|
|
|
|
|
snprintf(name, dentry->d_name.len + 1, "%s", dentry->d_name.name);
|
|
@@ -1094,10 +1184,10 @@ static int configfs_mkdir(struct inode *dir, struct dentry *dentry, int mode)
|
|
|
kfree(name);
|
|
|
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.
|
|
|
*/
|
|
|
- goto out_put;
|
|
|
+ goto out_subsys_put;
|
|
|
}
|
|
|
|
|
|
/*
|
|
@@ -1111,8 +1201,8 @@ static int configfs_mkdir(struct inode *dir, struct dentry *dentry, int mode)
|
|
|
goto out_unlink;
|
|
|
}
|
|
|
|
|
|
- owner = type->ct_owner;
|
|
|
- if (!try_module_get(owner)) {
|
|
|
+ new_item_owner = type->ct_owner;
|
|
|
+ if (!try_module_get(new_item_owner)) {
|
|
|
ret = -EINVAL;
|
|
|
goto out_unlink;
|
|
|
}
|
|
@@ -1142,6 +1232,8 @@ static int configfs_mkdir(struct inode *dir, struct dentry *dentry, int mode)
|
|
|
|
|
|
spin_lock(&configfs_dirent_lock);
|
|
|
sd->s_type &= ~CONFIGFS_USET_IN_MKDIR;
|
|
|
+ if (!ret)
|
|
|
+ configfs_dir_set_ready(dentry->d_fsdata);
|
|
|
spin_unlock(&configfs_dirent_lock);
|
|
|
|
|
|
out_unlink:
|
|
@@ -1159,9 +1251,13 @@ out_unlink:
|
|
|
mutex_unlock(&subsys->su_mutex);
|
|
|
|
|
|
if (module_got)
|
|
|
- module_put(owner);
|
|
|
+ module_put(new_item_owner);
|
|
|
}
|
|
|
|
|
|
+out_subsys_put:
|
|
|
+ if (ret)
|
|
|
+ module_put(subsys_owner);
|
|
|
+
|
|
|
out_put:
|
|
|
/*
|
|
|
* link_obj()/link_group() took a reference from child->parent,
|
|
@@ -1180,7 +1276,7 @@ static int configfs_rmdir(struct inode *dir, struct dentry *dentry)
|
|
|
struct config_item *item;
|
|
|
struct configfs_subsystem *subsys;
|
|
|
struct configfs_dirent *sd;
|
|
|
- struct module *owner = NULL;
|
|
|
+ struct module *subsys_owner = NULL, *dead_item_owner = NULL;
|
|
|
int ret;
|
|
|
|
|
|
if (dentry->d_parent == configfs_sb->s_root)
|
|
@@ -1207,6 +1303,15 @@ static int configfs_rmdir(struct inode *dir, struct dentry *dentry)
|
|
|
return -EINVAL;
|
|
|
}
|
|
|
|
|
|
+ /* configfs_mkdir() shouldn't have allowed this */
|
|
|
+ BUG_ON(!subsys->su_group.cg_item.ci_type);
|
|
|
+ subsys_owner = subsys->su_group.cg_item.ci_type->ct_owner;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Ensure that no racing symlink() will make detach_prep() fail while
|
|
|
+ * the new link is temporarily attached
|
|
|
+ */
|
|
|
+ mutex_lock(&configfs_symlink_mutex);
|
|
|
spin_lock(&configfs_dirent_lock);
|
|
|
do {
|
|
|
struct mutex *wait_mutex;
|
|
@@ -1215,6 +1320,7 @@ static int configfs_rmdir(struct inode *dir, struct dentry *dentry)
|
|
|
if (ret) {
|
|
|
configfs_detach_rollback(dentry);
|
|
|
spin_unlock(&configfs_dirent_lock);
|
|
|
+ mutex_unlock(&configfs_symlink_mutex);
|
|
|
if (ret != -EAGAIN) {
|
|
|
config_item_put(parent_item);
|
|
|
return ret;
|
|
@@ -1224,10 +1330,12 @@ static int configfs_rmdir(struct inode *dir, struct dentry *dentry)
|
|
|
mutex_lock(wait_mutex);
|
|
|
mutex_unlock(wait_mutex);
|
|
|
|
|
|
+ mutex_lock(&configfs_symlink_mutex);
|
|
|
spin_lock(&configfs_dirent_lock);
|
|
|
}
|
|
|
} while (ret == -EAGAIN);
|
|
|
spin_unlock(&configfs_dirent_lock);
|
|
|
+ mutex_unlock(&configfs_symlink_mutex);
|
|
|
|
|
|
/* Get a working ref for the duration of this function */
|
|
|
item = configfs_get_config_item(dentry);
|
|
@@ -1236,7 +1344,7 @@ static int configfs_rmdir(struct inode *dir, struct dentry *dentry)
|
|
|
config_item_put(parent_item);
|
|
|
|
|
|
if (item->ci_type)
|
|
|
- owner = item->ci_type->ct_owner;
|
|
|
+ dead_item_owner = item->ci_type->ct_owner;
|
|
|
|
|
|
if (sd->s_type & CONFIGFS_USET_DIR) {
|
|
|
configfs_detach_group(item);
|
|
@@ -1258,7 +1366,8 @@ static int configfs_rmdir(struct inode *dir, struct dentry *dentry)
|
|
|
/* Drop our reference from above */
|
|
|
config_item_put(item);
|
|
|
|
|
|
- module_put(owner);
|
|
|
+ module_put(dead_item_owner);
|
|
|
+ module_put(subsys_owner);
|
|
|
|
|
|
return 0;
|
|
|
}
|
|
@@ -1314,13 +1423,24 @@ static int configfs_dir_open(struct inode *inode, struct file *file)
|
|
|
{
|
|
|
struct dentry * dentry = file->f_path.dentry;
|
|
|
struct configfs_dirent * parent_sd = dentry->d_fsdata;
|
|
|
+ int err;
|
|
|
|
|
|
mutex_lock(&dentry->d_inode->i_mutex);
|
|
|
- file->private_data = configfs_new_dirent(parent_sd, NULL);
|
|
|
+ /*
|
|
|
+ * Fake invisibility if dir belongs to a group/default groups hierarchy
|
|
|
+ * being attached
|
|
|
+ */
|
|
|
+ err = -ENOENT;
|
|
|
+ if (configfs_dirent_is_ready(parent_sd)) {
|
|
|
+ file->private_data = configfs_new_dirent(parent_sd, NULL);
|
|
|
+ if (IS_ERR(file->private_data))
|
|
|
+ err = PTR_ERR(file->private_data);
|
|
|
+ else
|
|
|
+ err = 0;
|
|
|
+ }
|
|
|
mutex_unlock(&dentry->d_inode->i_mutex);
|
|
|
|
|
|
- return IS_ERR(file->private_data) ? PTR_ERR(file->private_data) : 0;
|
|
|
-
|
|
|
+ return err;
|
|
|
}
|
|
|
|
|
|
static int configfs_dir_close(struct inode *inode, struct file *file)
|
|
@@ -1491,6 +1611,10 @@ int configfs_register_subsystem(struct configfs_subsystem *subsys)
|
|
|
if (err) {
|
|
|
d_delete(dentry);
|
|
|
dput(dentry);
|
|
|
+ } else {
|
|
|
+ spin_lock(&configfs_dirent_lock);
|
|
|
+ configfs_dir_set_ready(dentry->d_fsdata);
|
|
|
+ spin_unlock(&configfs_dirent_lock);
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -1517,11 +1641,13 @@ 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);
|
|
|
+ mutex_lock(&configfs_symlink_mutex);
|
|
|
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);
|
|
|
+ mutex_unlock(&configfs_symlink_mutex);
|
|
|
configfs_detach_group(&group->cg_item);
|
|
|
dentry->d_inode->i_flags |= S_DEAD;
|
|
|
mutex_unlock(&dentry->d_inode->i_mutex);
|