configfs work

A couple of fixes (UAF in configfs_lookup() and really old races
 introduced when lseek() on configfs directories stopped locking those
 directories; impact up to and including UAF).
 
 Fixes aside, the main result is that configfs is finally switched
 to tree-in-dcache machinery.  It's *not* making use of recursive removal
 helpers yet, and it still does the bloody awful "build subtree in full
 sight of userland, with possibility of failure halfway through and need
 to unroll" that forces the locking model from hell; dealing with that
 is a separate patch series, once this one is out of the way.  However,
 it is using DCACHE_PERSISTENT properly now.  And apparmorfs is the sole
 remaining user of __simple_{unlink,rmdir}() at that point.
 
 Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
 -----BEGIN PGP SIGNATURE-----
 
 iHUEABYKAB0WIQQqUNBr3gm4hGXdBJlZ7Krx/gZQ6wUCai8uagAKCRBZ7Krx/gZQ
 68DcAQCrG0zPl8BwHlKVVlylAzgjK2lY120bHX36a4+qLmFE4AD9HTeSgqoZ8vAv
 XkZl3FY9nwgDhkTJGU1kubt974BnRAA=
 =NCvS
 -----END PGP SIGNATURE-----

Merge tag 'pull-configfs-fixed' of git://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs

Pull configfs updates from Al Viro:
 "A couple of fixes (UAF in configfs_lookup() and really old races
  introduced when lseek() on configfs directories stopped locking those
  directories; impact up to and including UAF).

  Fixes aside, the main result is that configfs is finally switched to
  tree-in-dcache machinery. It's *not* making use of recursive removal
  helpers yet, and it still does the bloody awful "build subtree in full
  sight of userland, with possibility of failure halfway through and
  need to unroll" that forces the locking model from hell; dealing with
  that is a separate patch series, once this one is out of the way.
  However, it is using DCACHE_PERSISTENT properly now. And apparmorfs is
  the sole remaining user of __simple_{unlink,rmdir}() at that point"

* tag 'pull-configfs-fixed' of git://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs:
  create_default_group(): pass parent's dentry instead of config_group
  configfs_attach_group(): drop the unused parent_item argument
  configs_attach_item(): drop unused parent_item argument
  configfs_create(): lift parent timestamp updates into callers
  kill configfs_drop_dentry()
  configfs: mark pinned dentries persistent
  configfs: dentry refcount needs to be pinned only once
  switch configfs_detach_{group,item}() to passing dentry
  configfs_remove_dir(), detach_attrs(): switch to passing dentry
  populate_attrs(): move cleanup to the sole caller
  populate_group(): move cleanup on failure to the sole caller
  configfs_detach_rollback(): pass configfs_dirent instead of dentry
  configfs_do_depend_item(): pass configfs_dirent instead of dentry
  configfs_depend_prep(): pass configfs_dirent instead of dentry
  configfs_detach_prep(): pass configfs_dirent instead of dentry
  configfs_mkdir(): use take_dentry_name_snapshot()
  configfs: fix lockless traversals of ->s_children
  configfs_lookup(): don't leave ->s_dentry dangling on failure
This commit is contained in:
Linus Torvalds 2026-06-15 04:28:20 +05:30
commit de02909ae8
4 changed files with 161 additions and 191 deletions

View File

@ -76,7 +76,6 @@ extern int configfs_make_dirent(struct configfs_dirent *, struct dentry *,
extern int configfs_dirent_is_ready(struct configfs_dirent *);
extern const unsigned char * configfs_get_name(struct configfs_dirent *sd);
extern void configfs_drop_dentry(struct configfs_dirent *sd, struct dentry *parent);
extern int configfs_setattr(struct mnt_idmap *idmap,
struct dentry *dentry, struct iattr *iattr);

View File

@ -235,15 +235,16 @@ static int configfs_dirent_exists(struct dentry *dentry)
const unsigned char *new = dentry->d_name.name;
struct configfs_dirent *sd;
spin_lock(&configfs_dirent_lock);
list_for_each_entry(sd, &parent_sd->s_children, s_sibling) {
if (sd->s_element) {
const unsigned char *existing = configfs_get_name(sd);
if (strcmp(existing, new))
continue;
else
if (strcmp(configfs_get_name(sd), new) == 0) {
spin_unlock(&configfs_dirent_lock);
return -EEXIST;
}
}
}
spin_unlock(&configfs_dirent_lock);
return 0;
}
@ -295,6 +296,7 @@ static int configfs_create_dir(struct config_item *item, struct dentry *dentry,
int error;
umode_t mode = S_IFDIR| S_IRWXU | S_IRUGO | S_IXUGO;
struct dentry *p = dentry->d_parent;
struct inode *p_inode = d_inode(p);
struct inode *inode;
BUG_ON(!item);
@ -314,10 +316,9 @@ static int configfs_create_dir(struct config_item *item, struct dentry *dentry,
inode->i_fop = &configfs_dir_operations;
/* directory inodes start off with i_nlink == 2 (for "." entry) */
inc_nlink(inode);
d_instantiate(dentry, inode);
/* already hashed */
dget(dentry); /* pin directory dentries in core */
inc_nlink(d_inode(p));
d_make_persistent(dentry, inode);
inc_nlink(p_inode);
inode_set_mtime_to_ts(p_inode, inode_set_ctime_current(p_inode));
item->ci_dentry = dentry;
return 0;
@ -371,6 +372,7 @@ int configfs_create_link(struct configfs_dirent *target, struct dentry *parent,
int err = 0;
umode_t mode = S_IFLNK | S_IRWXUGO;
struct configfs_dirent *p = parent->d_fsdata;
struct inode *p_inode = d_inode(parent);
struct inode *inode;
err = configfs_make_dirent(p, dentry, target, mode, CONFIGFS_ITEM_LINK,
@ -384,8 +386,8 @@ int configfs_create_link(struct configfs_dirent *target, struct dentry *parent,
inode->i_link = body;
inode->i_op = &configfs_symlink_inode_operations;
d_instantiate(dentry, inode);
dget(dentry); /* pin link dentries in core */
d_make_persistent(dentry, inode);
inode_set_mtime_to_ts(p_inode, inode_set_ctime_current(p_inode));
return 0;
out_remove:
@ -394,29 +396,9 @@ int configfs_create_link(struct configfs_dirent *target, struct dentry *parent,
return PTR_ERR(inode);
}
static void remove_dir(struct dentry * d)
{
struct dentry * parent = dget(d->d_parent);
configfs_remove_dirent(d);
if (d_really_is_positive(d)) {
if (likely(simple_empty(d))) {
__simple_rmdir(d_inode(parent),d);
dput(d);
} else {
pr_warn("remove_dir (%pd): attributes remain", d);
}
}
pr_debug(" o %pd removing done (%d)\n", d, d_count(d));
dput(parent);
}
/**
* configfs_remove_dir - remove an config_item's directory.
* @item: config_item we're removing.
* @d: dentry we're removing.
*
* The only thing special about this is that we remove any files in
* the directory before we remove the directory, and we've inlined
@ -425,18 +407,20 @@ static void remove_dir(struct dentry * d)
* Caller holds the mutex of the item's inode
*/
static void configfs_remove_dir(struct config_item * item)
static void configfs_remove_dir(struct dentry *d)
{
struct dentry * dentry = dget(item->ci_dentry);
struct dentry * parent = dget(d->d_parent);
if (!dentry)
return;
configfs_remove_dirent(d);
remove_dir(dentry);
/**
* Drop reference from dget() on entrance.
*/
dput(dentry);
if (d_really_is_positive(d)) {
if (unlikely(simple_rmdir(d_inode(parent), d)))
pr_warn("remove_dir (%pd): attributes remain", d);
}
pr_debug(" o %pd removing done (%d)\n", d, d_count(d));
dput(parent);
}
static struct dentry * configfs_lookup(struct inode *dir,
@ -486,6 +470,9 @@ static struct dentry * configfs_lookup(struct inode *dir,
inode = configfs_create(dentry, mode);
if (IS_ERR(inode)) {
spin_lock(&configfs_dirent_lock);
sd->s_dentry = NULL;
spin_unlock(&configfs_dirent_lock);
configfs_put(sd);
return ERR_CAST(inode);
}
@ -512,9 +499,8 @@ static struct dentry * configfs_lookup(struct inode *dir,
* If there is an error, the caller will reset the flags via
* configfs_detach_rollback().
*/
static int configfs_detach_prep(struct dentry *dentry, struct dentry **wait)
static int configfs_detach_prep(struct configfs_dirent *parent_sd, struct dentry **wait)
{
struct configfs_dirent *parent_sd = dentry->d_fsdata;
struct configfs_dirent *sd;
int ret;
@ -542,7 +528,7 @@ static int configfs_detach_prep(struct dentry *dentry, struct dentry **wait)
* Yup, recursive. If there's a problem, blame
* deep nesting of default_groups
*/
ret = configfs_detach_prep(sd->s_dentry, wait);
ret = configfs_detach_prep(sd, wait);
if (!ret)
continue;
} else
@ -559,45 +545,77 @@ static int configfs_detach_prep(struct dentry *dentry, struct dentry **wait)
* Walk the tree, resetting CONFIGFS_USET_DROPPING wherever it was
* set.
*/
static void configfs_detach_rollback(struct dentry *dentry)
static void configfs_detach_rollback(struct configfs_dirent *parent_sd)
{
struct configfs_dirent *parent_sd = dentry->d_fsdata;
struct configfs_dirent *sd;
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);
configfs_detach_rollback(sd);
}
static void detach_attrs(struct config_item * item)
/*
* Find the next non-cursor. configfs_dirent_lock held by caller.
*/
static struct configfs_dirent *next_dirent(struct configfs_dirent *parent,
struct configfs_dirent *last)
{
struct dentry * dentry = dget(item->ci_dentry);
struct configfs_dirent * parent_sd;
struct configfs_dirent * sd, * tmp;
struct configfs_dirent *s;
if (!dentry)
return;
s = list_prepare_entry(last, &parent->s_children, s_sibling);
pr_debug("configfs %s: dropping attrs for dir\n",
dentry->d_name.name);
list_for_each_entry_continue(s, &parent->s_children, s_sibling) {
if (s->s_element)
return s;
}
return NULL;
}
static void detach_attrs(struct dentry *dentry)
{
struct configfs_dirent *parent_sd;
struct configfs_dirent *sd, *next;
pr_debug("configfs %pd: dropping attrs for dir\n", dentry);
parent_sd = dentry->d_fsdata;
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);
}
/**
* Drop reference from dget() on entrance.
*/
dput(dentry);
spin_lock(&configfs_dirent_lock);
for (sd = next_dirent(parent_sd, NULL); sd; sd = next) {
struct dentry *child;
next = next_dirent(parent_sd, sd);
if (!(sd->s_type & CONFIGFS_NOT_PINNED))
continue;
list_del_init(&sd->s_sibling);
child = sd->s_dentry;
if (child) {
spin_lock(&child->d_lock);
if (simple_positive(child)) {
dget_dlock(child);
__d_drop(child);
spin_unlock(&child->d_lock);
} else {
spin_unlock(&child->d_lock);
child = NULL;
}
}
spin_unlock(&configfs_dirent_lock);
if (child) {
struct inode *inode = child->d_inode;
inode_lock_nested(inode, I_MUTEX_NONDIR2);
inode_set_ctime_current(inode);
drop_nlink(inode);
inode_unlock(inode);
dput(child);
}
configfs_put(sd);
spin_lock(&configfs_dirent_lock);
}
spin_unlock(&configfs_dirent_lock);
}
static int populate_attrs(struct config_item *item)
@ -619,54 +637,49 @@ static int populate_attrs(struct config_item *item)
if (ops && ops->is_visible && !ops->is_visible(item, attr, i))
continue;
if ((error = configfs_create_file(item, attr)))
break;
error = configfs_create_file(item, attr);
if (error)
return error;
}
}
if (!error && t->ct_bin_attrs) {
if (t->ct_bin_attrs) {
for (i = 0; (bin_attr = t->ct_bin_attrs[i]) != NULL; i++) {
if (ops && ops->is_bin_visible && !ops->is_bin_visible(item, bin_attr, i))
continue;
error = configfs_create_bin_file(item, bin_attr);
if (error)
break;
return error;
}
}
if (error)
detach_attrs(item);
return error;
return 0;
}
static int configfs_attach_group(struct config_item *parent_item,
struct config_item *item,
static int configfs_attach_group(struct config_item *item,
struct dentry *dentry,
struct configfs_fragment *frag);
static void configfs_detach_group(struct config_item *item);
static void configfs_detach_group(struct dentry *dentry);
static void detach_groups(struct config_group *group)
static void detach_groups(struct dentry *dentry)
{
struct dentry * dentry = dget(group->cg_item.ci_dentry);
struct dentry *child;
struct configfs_dirent *parent_sd;
struct configfs_dirent *sd, *tmp;
if (!dentry)
return;
struct configfs_dirent *sd, *next;
parent_sd = dentry->d_fsdata;
list_for_each_entry_safe(sd, tmp, &parent_sd->s_children, s_sibling) {
if (!sd->s_element ||
!(sd->s_type & CONFIGFS_USET_DEFAULT))
spin_lock(&configfs_dirent_lock);
for (sd = next_dirent(parent_sd, NULL); sd; sd = next) {
next = next_dirent(parent_sd, sd);
if (!(sd->s_type & CONFIGFS_USET_DEFAULT))
continue;
child = sd->s_dentry;
child = dget(sd->s_dentry);
spin_unlock(&configfs_dirent_lock);
inode_lock(d_inode(child));
configfs_detach_group(sd->s_element);
configfs_detach_group(child);
d_inode(child)->i_flags |= S_DEAD;
dont_mount(child);
@ -674,12 +687,9 @@ static void detach_groups(struct config_group *group)
d_delete(child);
dput(child);
spin_lock(&configfs_dirent_lock);
}
/**
* Drop reference from dget() on entrance.
*/
dput(dentry);
spin_unlock(&configfs_dirent_lock);
}
/*
@ -690,14 +700,14 @@ static void detach_groups(struct config_group *group)
* We could, perhaps, tweak our parent's ->mkdir for a minute and
* try using vfs_mkdir. Just a thought.
*/
static int create_default_group(struct config_group *parent_group,
static int create_default_group(struct dentry *parent,
struct config_group *group,
struct configfs_fragment *frag)
{
int ret;
struct configfs_dirent *sd;
/* We trust the caller holds a reference to parent */
struct dentry *child, *parent = parent_group->cg_item.ci_dentry;
struct dentry *child;
if (!group->cg_item.ci_name)
group->cg_item.ci_name = group->cg_item.ci_namebuf;
@ -707,16 +717,15 @@ static int create_default_group(struct config_group *parent_group,
if (child) {
d_add(child, NULL);
ret = configfs_attach_group(&parent_group->cg_item,
&group->cg_item, child, frag);
ret = configfs_attach_group(&group->cg_item, child, frag);
if (!ret) {
sd = child->d_fsdata;
sd->s_type |= CONFIGFS_USET_DEFAULT;
} else {
BUG_ON(d_inode(child));
d_drop(child);
dput(child);
}
dput(child);
}
return ret;
@ -725,18 +734,15 @@ static int create_default_group(struct config_group *parent_group,
static int populate_groups(struct config_group *group,
struct configfs_fragment *frag)
{
struct dentry *parent = group->cg_item.ci_dentry;
struct config_group *new_group;
int ret = 0;
list_for_each_entry(new_group, &group->default_groups, group_entry) {
ret = create_default_group(group, new_group, frag);
if (ret) {
detach_groups(group);
break;
}
int ret = create_default_group(parent, new_group, frag);
if (ret)
return ret;
}
return ret;
return 0;
}
void configfs_remove_default_groups(struct config_group *group)
@ -826,6 +832,13 @@ static void link_group(struct config_group *parent_group, struct config_group *g
link_group(group, new_group);
}
/* Caller holds the mutex of the item's inode */
static void configfs_detach_item(struct dentry *dentry)
{
detach_attrs(dentry);
configfs_remove_dir(dentry);
}
/*
* The goal is that configfs_attach_item() (and
* configfs_attach_group()) can be called from either the VFS or this
@ -841,8 +854,7 @@ static void link_group(struct config_group *parent_group, struct config_group *g
* clean up the configfs items, and they expect their callers will
* handle the dcache bits.
*/
static int configfs_attach_item(struct config_item *parent_item,
struct config_item *item,
static int configfs_attach_item(struct config_item *item,
struct dentry *dentry,
struct configfs_fragment *frag)
{
@ -858,7 +870,7 @@ static int configfs_attach_item(struct config_item *parent_item,
* we must lock them as rmdir() would.
*/
inode_lock(d_inode(dentry));
configfs_remove_dir(item);
configfs_detach_item(dentry);
d_inode(dentry)->i_flags |= S_DEAD;
dont_mount(dentry);
inode_unlock(d_inode(dentry));
@ -869,22 +881,21 @@ 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)
/* Caller holds the mutex of the group's inode */
static void configfs_detach_group(struct dentry *dentry)
{
detach_attrs(item);
configfs_remove_dir(item);
detach_groups(dentry);
configfs_detach_item(dentry);
}
static int configfs_attach_group(struct config_item *parent_item,
struct config_item *item,
static int configfs_attach_group(struct config_item *item,
struct dentry *dentry,
struct configfs_fragment *frag)
{
int ret;
struct configfs_dirent *sd;
ret = configfs_attach_item(parent_item, item, dentry, frag);
ret = configfs_attach_item(item, dentry, frag);
if (!ret) {
sd = dentry->d_fsdata;
sd->s_type |= CONFIGFS_USET_DIR;
@ -902,7 +913,7 @@ static int configfs_attach_group(struct config_item *parent_item,
configfs_adjust_dir_dirent_depth_before_populate(sd);
ret = populate_groups(to_config_group(item), frag);
if (ret) {
configfs_detach_item(item);
configfs_detach_group(dentry);
d_inode(dentry)->i_flags |= S_DEAD;
dont_mount(dentry);
}
@ -915,13 +926,6 @@ static int configfs_attach_group(struct config_item *parent_item,
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));
configfs_detach_item(item);
}
/*
* After the item has been detached from the filesystem view, we are
* ready to tear it out of the hierarchy. Notify the client before
@ -1064,15 +1068,12 @@ static int configfs_dump(struct configfs_dirent *sd, int level)
* much on the stack, though, so folks that need this function - be careful
* about your stack! Patches will be accepted to make it iterative.
*/
static int configfs_depend_prep(struct dentry *origin,
static int configfs_depend_prep(struct configfs_dirent *sd,
struct config_item *target)
{
struct configfs_dirent *child_sd, *sd;
struct configfs_dirent *child_sd;
int ret = 0;
BUG_ON(!origin || !origin->d_fsdata);
sd = origin->d_fsdata;
if (sd->s_element == target) /* Boo-yah */
goto out;
@ -1080,8 +1081,7 @@ static int configfs_depend_prep(struct dentry *origin,
if ((child_sd->s_type & CONFIGFS_DIR) &&
!(child_sd->s_type & CONFIGFS_USET_DROPPING) &&
!(child_sd->s_type & CONFIGFS_USET_CREATING)) {
ret = configfs_depend_prep(child_sd->s_dentry,
target);
ret = configfs_depend_prep(child_sd, target);
if (!ret)
goto out; /* Child path boo-yah */
}
@ -1094,7 +1094,7 @@ static int configfs_depend_prep(struct dentry *origin,
return ret;
}
static int configfs_do_depend_item(struct dentry *subsys_dentry,
static int configfs_do_depend_item(struct configfs_dirent *subsys_sd,
struct config_item *target)
{
struct configfs_dirent *p;
@ -1102,7 +1102,7 @@ static int configfs_do_depend_item(struct dentry *subsys_dentry,
spin_lock(&configfs_dirent_lock);
/* Scan the tree, return 0 if found */
ret = configfs_depend_prep(subsys_dentry, target);
ret = configfs_depend_prep(subsys_sd, target);
if (ret)
goto out_unlock_dirent_lock;
@ -1126,6 +1126,7 @@ configfs_find_subsys_dentry(struct configfs_dirent *root_sd,
struct configfs_dirent *p;
struct configfs_dirent *ret = NULL;
spin_lock(&configfs_dirent_lock);
list_for_each_entry(p, &root_sd->s_children, s_sibling) {
if (p->s_type & CONFIGFS_DIR &&
p->s_element == subsys_item) {
@ -1133,6 +1134,7 @@ configfs_find_subsys_dentry(struct configfs_dirent *root_sd,
break;
}
}
spin_unlock(&configfs_dirent_lock);
return ret;
}
@ -1168,7 +1170,7 @@ int configfs_depend_item(struct configfs_subsystem *subsys,
}
/* Ok, now we can trust subsys/s_item */
ret = configfs_do_depend_item(subsys_sd->s_dentry, target);
ret = configfs_do_depend_item(subsys_sd, target);
out_unlock_fs:
inode_unlock(d_inode(root));
@ -1270,7 +1272,7 @@ int configfs_depend_item_unlocked(struct configfs_subsystem *caller_subsys,
}
/* Now we can execute core of depend item */
ret = configfs_do_depend_item(subsys_sd->s_dentry, target);
ret = configfs_do_depend_item(subsys_sd, target);
if (target_subsys != caller_subsys)
out_root_unlock:
@ -1297,7 +1299,11 @@ static struct dentry *configfs_mkdir(struct mnt_idmap *idmap, struct inode *dir,
const struct config_item_type *type;
struct module *subsys_owner = NULL, *new_item_owner = NULL;
struct configfs_fragment *frag;
char *name;
struct name_snapshot n;
const char *name;
take_dentry_name_snapshot(&n, dentry);
name = n.name.name;
sd = dentry->d_parent->d_fsdata;
@ -1349,14 +1355,6 @@ static struct dentry *configfs_mkdir(struct mnt_idmap *idmap, struct inode *dir,
goto out_put;
}
name = kmalloc(dentry->d_name.len + 1, GFP_KERNEL);
if (!name) {
ret = -ENOMEM;
goto out_subsys_put;
}
snprintf(name, dentry->d_name.len + 1, "%s", dentry->d_name.name);
mutex_lock(&subsys->su_mutex);
if (type->ct_group_ops->make_group) {
group = type->ct_group_ops->make_group(to_config_group(parent_item), name);
@ -1378,7 +1376,6 @@ static struct dentry *configfs_mkdir(struct mnt_idmap *idmap, struct inode *dir,
}
mutex_unlock(&subsys->su_mutex);
kfree(name);
if (ret) {
/*
* If ret != 0, then link_obj() was never called.
@ -1423,9 +1420,9 @@ static struct dentry *configfs_mkdir(struct mnt_idmap *idmap, struct inode *dir,
spin_unlock(&configfs_dirent_lock);
if (group)
ret = configfs_attach_group(parent_item, item, dentry, frag);
ret = configfs_attach_group(item, dentry, frag);
else
ret = configfs_attach_item(parent_item, item, dentry, frag);
ret = configfs_attach_item(item, dentry, frag);
spin_lock(&configfs_dirent_lock);
sd->s_type &= ~CONFIGFS_USET_IN_MKDIR;
@ -1465,6 +1462,7 @@ static struct dentry *configfs_mkdir(struct mnt_idmap *idmap, struct inode *dir,
put_fragment(frag);
out:
release_dentry_name_snapshot(&n);
return ERR_PTR(ret);
}
@ -1512,9 +1510,9 @@ static int configfs_rmdir(struct inode *dir, struct dentry *dentry)
*/
ret = sd->s_dependent_count ? -EBUSY : 0;
if (!ret) {
ret = configfs_detach_prep(dentry, &wait);
ret = configfs_detach_prep(sd, &wait);
if (ret)
configfs_detach_rollback(dentry);
configfs_detach_rollback(sd);
}
spin_unlock(&configfs_dirent_lock);
mutex_unlock(&configfs_symlink_mutex);
@ -1535,7 +1533,7 @@ static int configfs_rmdir(struct inode *dir, struct dentry *dentry)
frag = sd->s_frag;
if (down_write_killable(&frag->frag_sem)) {
spin_lock(&configfs_dirent_lock);
configfs_detach_rollback(dentry);
configfs_detach_rollback(sd);
spin_unlock(&configfs_dirent_lock);
config_item_put(parent_item);
return -EINTR;
@ -1553,13 +1551,13 @@ static int configfs_rmdir(struct inode *dir, struct dentry *dentry)
dead_item_owner = item->ci_type->ct_owner;
if (sd->s_type & CONFIGFS_USET_DIR) {
configfs_detach_group(item);
configfs_detach_group(dentry);
mutex_lock(&subsys->su_mutex);
client_disconnect_notify(parent_item, item);
unlink_group(to_config_group(item));
} else {
configfs_detach_item(item);
configfs_detach_item(dentry);
mutex_lock(&subsys->su_mutex);
client_disconnect_notify(parent_item, item);
@ -1769,7 +1767,7 @@ int configfs_register_group(struct config_group *parent_group,
parent = parent_group->cg_item.ci_dentry;
inode_lock_nested(d_inode(parent), I_MUTEX_PARENT);
ret = create_default_group(parent_group, group, frag);
ret = create_default_group(parent, group, frag);
if (ret)
goto err_out;
@ -1798,7 +1796,7 @@ EXPORT_SYMBOL(configfs_register_group);
void configfs_unregister_group(struct config_group *group)
{
struct configfs_subsystem *subsys = group->cg_subsys;
struct dentry *dentry = group->cg_item.ci_dentry;
struct dentry *dentry = dget(group->cg_item.ci_dentry);
struct dentry *parent = group->cg_item.ci_parent->ci_dentry;
struct configfs_dirent *sd = dentry->d_fsdata;
struct configfs_fragment *frag = sd->s_frag;
@ -1809,10 +1807,10 @@ void configfs_unregister_group(struct config_group *group)
inode_lock_nested(d_inode(parent), I_MUTEX_PARENT);
spin_lock(&configfs_dirent_lock);
configfs_detach_prep(dentry, NULL);
configfs_detach_prep(sd, NULL);
spin_unlock(&configfs_dirent_lock);
configfs_detach_group(&group->cg_item);
configfs_detach_group(dentry);
d_inode(dentry)->i_flags |= S_DEAD;
dont_mount(dentry);
d_drop(dentry);
@ -1907,18 +1905,17 @@ int configfs_register_subsystem(struct configfs_subsystem *subsys)
err = configfs_dirent_exists(dentry);
if (!err)
err = configfs_attach_group(sd->s_element,
&group->cg_item,
err = configfs_attach_group(&group->cg_item,
dentry, frag);
if (err) {
BUG_ON(d_inode(dentry));
d_drop(dentry);
dput(dentry);
} else {
spin_lock(&configfs_dirent_lock);
configfs_dir_set_ready(dentry->d_fsdata);
spin_unlock(&configfs_dirent_lock);
}
dput(dentry);
}
inode_unlock(d_inode(root));
@ -1937,7 +1934,7 @@ int configfs_register_subsystem(struct configfs_subsystem *subsys)
void configfs_unregister_subsystem(struct configfs_subsystem *subsys)
{
struct config_group *group = &subsys->su_group;
struct dentry *dentry = group->cg_item.ci_dentry;
struct dentry *dentry = dget(group->cg_item.ci_dentry);
struct dentry *root = dentry->d_sb->s_root;
struct configfs_dirent *sd = dentry->d_fsdata;
struct configfs_fragment *frag = sd->s_frag;
@ -1956,12 +1953,12 @@ void configfs_unregister_subsystem(struct configfs_subsystem *subsys)
inode_lock_nested(d_inode(dentry), I_MUTEX_CHILD);
mutex_lock(&configfs_symlink_mutex);
spin_lock(&configfs_dirent_lock);
if (configfs_detach_prep(dentry, NULL)) {
if (configfs_detach_prep(sd, NULL)) {
pr_err("Tried to unregister non-empty subsystem!\n");
}
spin_unlock(&configfs_dirent_lock);
mutex_unlock(&configfs_symlink_mutex);
configfs_detach_group(&group->cg_item);
configfs_detach_group(dentry);
d_inode(dentry)->i_flags |= S_DEAD;
dont_mount(dentry);
inode_unlock(d_inode(dentry));

View File

@ -157,7 +157,6 @@ struct inode *configfs_create(struct dentry *dentry, umode_t mode)
{
struct inode *inode = NULL;
struct configfs_dirent *sd;
struct inode *p_inode;
if (!dentry)
return ERR_PTR(-ENOENT);
@ -170,8 +169,6 @@ struct inode *configfs_create(struct dentry *dentry, umode_t mode)
if (!inode)
return ERR_PTR(-ENOMEM);
p_inode = d_inode(dentry->d_parent);
inode_set_mtime_to_ts(p_inode, inode_set_ctime_current(p_inode));
configfs_set_inode_lock_class(sd, inode);
return inode;
}
@ -195,25 +192,3 @@ const unsigned char * configfs_get_name(struct configfs_dirent *sd)
}
return NULL;
}
/*
* Unhashes the dentry corresponding to given configfs_dirent
* Called with parent inode's i_mutex held.
*/
void configfs_drop_dentry(struct configfs_dirent * sd, struct dentry * parent)
{
struct dentry * dentry = sd->s_dentry;
if (dentry) {
spin_lock(&dentry->d_lock);
if (simple_positive(dentry)) {
dget_dlock(dentry);
__d_drop(dentry);
spin_unlock(&dentry->d_lock);
__simple_unlink(d_inode(parent), dentry);
dput(dentry);
} else
spin_unlock(&dentry->d_lock);
}
}

View File

@ -229,9 +229,8 @@ int configfs_unlink(struct inode *dir, struct dentry *dentry)
spin_lock(&configfs_dirent_lock);
list_del_init(&sd->s_sibling);
spin_unlock(&configfs_dirent_lock);
configfs_drop_dentry(sd, dentry->d_parent);
dput(dentry);
configfs_put(sd);
simple_unlink(dir, dentry);
/*
* drop_link() must be called before