configfs: configfs_mkdir() failed to cleanup linkage.
If configfs_mkdir() errored in certain ways after the parent<->child linkage was already created, it would not undo the linkage. Also, comment the reference counting for clarity. Signed-off-by: Joel Becker <joel.becker@oracle.com> Signed-off-by: Mark Fasheh <mark.fasheh@oracle.com>
This commit is contained in:
Родитель
84efad1a53
Коммит
eed7a0db46
|
@ -505,13 +505,15 @@ static int populate_groups(struct config_group *group)
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
if (group->default_groups) {
|
if (group->default_groups) {
|
||||||
/* FYI, we're faking mkdir here
|
/*
|
||||||
|
* FYI, we're faking mkdir here
|
||||||
* I'm not sure we need this semaphore, as we're called
|
* I'm not sure we need this semaphore, as we're called
|
||||||
* from our parent's mkdir. That holds our parent's
|
* from our parent's mkdir. That holds our parent's
|
||||||
* i_mutex, so afaik lookup cannot continue through our
|
* i_mutex, so afaik lookup cannot continue through our
|
||||||
* parent to find us, let alone mess with our tree.
|
* parent to find us, let alone mess with our tree.
|
||||||
* That said, taking our i_mutex is closer to mkdir
|
* That said, taking our i_mutex is closer to mkdir
|
||||||
* emulation, and shouldn't hurt. */
|
* emulation, and shouldn't hurt.
|
||||||
|
*/
|
||||||
mutex_lock(&dentry->d_inode->i_mutex);
|
mutex_lock(&dentry->d_inode->i_mutex);
|
||||||
|
|
||||||
for (i = 0; group->default_groups[i]; i++) {
|
for (i = 0; group->default_groups[i]; i++) {
|
||||||
|
@ -546,20 +548,34 @@ static void unlink_obj(struct config_item *item)
|
||||||
|
|
||||||
item->ci_group = NULL;
|
item->ci_group = NULL;
|
||||||
item->ci_parent = NULL;
|
item->ci_parent = NULL;
|
||||||
|
|
||||||
|
/* Drop the reference for ci_entry */
|
||||||
config_item_put(item);
|
config_item_put(item);
|
||||||
|
|
||||||
|
/* Drop the reference for ci_parent */
|
||||||
config_group_put(group);
|
config_group_put(group);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void link_obj(struct config_item *parent_item, struct config_item *item)
|
static void link_obj(struct config_item *parent_item, struct config_item *item)
|
||||||
{
|
{
|
||||||
/* Parent seems redundant with group, but it makes certain
|
/*
|
||||||
* traversals much nicer. */
|
* Parent seems redundant with group, but it makes certain
|
||||||
|
* traversals much nicer.
|
||||||
|
*/
|
||||||
item->ci_parent = parent_item;
|
item->ci_parent = parent_item;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We hold a reference on the parent for the child's ci_parent
|
||||||
|
* link.
|
||||||
|
*/
|
||||||
item->ci_group = config_group_get(to_config_group(parent_item));
|
item->ci_group = config_group_get(to_config_group(parent_item));
|
||||||
list_add_tail(&item->ci_entry, &item->ci_group->cg_children);
|
list_add_tail(&item->ci_entry, &item->ci_group->cg_children);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We hold a reference on the child for ci_entry on the parent's
|
||||||
|
* cg_children
|
||||||
|
*/
|
||||||
config_item_get(item);
|
config_item_get(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -684,6 +700,10 @@ static void client_drop_item(struct config_item *parent_item,
|
||||||
type = parent_item->ci_type;
|
type = parent_item->ci_type;
|
||||||
BUG_ON(!type);
|
BUG_ON(!type);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If ->drop_item() exists, it is responsible for the
|
||||||
|
* config_item_put().
|
||||||
|
*/
|
||||||
if (type->ct_group_ops && type->ct_group_ops->drop_item)
|
if (type->ct_group_ops && type->ct_group_ops->drop_item)
|
||||||
type->ct_group_ops->drop_item(to_config_group(parent_item),
|
type->ct_group_ops->drop_item(to_config_group(parent_item),
|
||||||
item);
|
item);
|
||||||
|
@ -694,14 +714,14 @@ static void client_drop_item(struct config_item *parent_item,
|
||||||
|
|
||||||
static int configfs_mkdir(struct inode *dir, struct dentry *dentry, int mode)
|
static int configfs_mkdir(struct inode *dir, struct dentry *dentry, int mode)
|
||||||
{
|
{
|
||||||
int ret;
|
int ret, module_got = 0;
|
||||||
struct config_group *group;
|
struct config_group *group;
|
||||||
struct config_item *item;
|
struct config_item *item;
|
||||||
struct config_item *parent_item;
|
struct config_item *parent_item;
|
||||||
struct configfs_subsystem *subsys;
|
struct configfs_subsystem *subsys;
|
||||||
struct configfs_dirent *sd;
|
struct configfs_dirent *sd;
|
||||||
struct config_item_type *type;
|
struct config_item_type *type;
|
||||||
struct module *owner;
|
struct module *owner = NULL;
|
||||||
char *name;
|
char *name;
|
||||||
|
|
||||||
if (dentry->d_parent == configfs_sb->s_root) {
|
if (dentry->d_parent == configfs_sb->s_root) {
|
||||||
|
@ -754,43 +774,63 @@ static int configfs_mkdir(struct inode *dir, struct dentry *dentry, int mode)
|
||||||
|
|
||||||
kfree(name);
|
kfree(name);
|
||||||
if (!item) {
|
if (!item) {
|
||||||
|
/*
|
||||||
|
* If item == NULL, then link_obj() was never called.
|
||||||
|
* There are no extra references to clean up.
|
||||||
|
*/
|
||||||
ret = -ENOMEM;
|
ret = -ENOMEM;
|
||||||
goto out_put;
|
goto out_put;
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = -EINVAL;
|
/*
|
||||||
|
* link_obj() has been called (via link_group() for groups).
|
||||||
|
* From here on out, errors must clean that up.
|
||||||
|
*/
|
||||||
|
|
||||||
type = item->ci_type;
|
type = item->ci_type;
|
||||||
if (type) {
|
if (!type) {
|
||||||
owner = type->ct_owner;
|
ret = -EINVAL;
|
||||||
if (try_module_get(owner)) {
|
goto out_unlink;
|
||||||
if (group) {
|
}
|
||||||
ret = configfs_attach_group(parent_item,
|
|
||||||
item,
|
|
||||||
dentry);
|
|
||||||
} else {
|
|
||||||
ret = configfs_attach_item(parent_item,
|
|
||||||
item,
|
|
||||||
dentry);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ret) {
|
owner = type->ct_owner;
|
||||||
down(&subsys->su_sem);
|
if (!try_module_get(owner)) {
|
||||||
if (group)
|
ret = -EINVAL;
|
||||||
unlink_group(group);
|
goto out_unlink;
|
||||||
else
|
}
|
||||||
unlink_obj(item);
|
|
||||||
client_drop_item(parent_item, item);
|
|
||||||
up(&subsys->su_sem);
|
|
||||||
|
|
||||||
module_put(owner);
|
/*
|
||||||
}
|
* I hate doing it this way, but if there is
|
||||||
}
|
* an error, module_put() probably should
|
||||||
|
* happen after any cleanup.
|
||||||
|
*/
|
||||||
|
module_got = 1;
|
||||||
|
|
||||||
|
if (group)
|
||||||
|
ret = configfs_attach_group(parent_item, item, dentry);
|
||||||
|
else
|
||||||
|
ret = configfs_attach_item(parent_item, item, dentry);
|
||||||
|
|
||||||
|
out_unlink:
|
||||||
|
if (ret) {
|
||||||
|
/* Tear down everything we built up */
|
||||||
|
down(&subsys->su_sem);
|
||||||
|
if (group)
|
||||||
|
unlink_group(group);
|
||||||
|
else
|
||||||
|
unlink_obj(item);
|
||||||
|
client_drop_item(parent_item, item);
|
||||||
|
up(&subsys->su_sem);
|
||||||
|
|
||||||
|
if (module_got)
|
||||||
|
module_put(owner);
|
||||||
}
|
}
|
||||||
|
|
||||||
out_put:
|
out_put:
|
||||||
/*
|
/*
|
||||||
* link_obj()/link_group() took a reference from child->parent.
|
* link_obj()/link_group() took a reference from child->parent,
|
||||||
* Drop our working ref
|
* so the parent is safely pinned. We can drop our working
|
||||||
|
* reference.
|
||||||
*/
|
*/
|
||||||
config_item_put(parent_item);
|
config_item_put(parent_item);
|
||||||
|
|
||||||
|
|
Загрузка…
Ссылка в новой задаче