summaryrefslogtreecommitdiff
path: root/fs/kernfs/dir.c
diff options
context:
space:
mode:
authorTejun Heo <tj@kernel.org>2014-01-17 18:58:25 +0400
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2014-01-17 23:50:07 +0400
commitdb4aad209bc9aefd91f0a9aeb9e37364088b39ad (patch)
tree28cc941640469a11111808a6e77be61e892e5041 /fs/kernfs/dir.c
parent917f56caaabc215f9658006dad28a9665ec0ce19 (diff)
downloadlinux-db4aad209bc9aefd91f0a9aeb9e37364088b39ad.tar.xz
kernfs: associate a new kernfs_node with its parent on creation
Once created, a kernfs_node is always destroyed by kernfs_put(). Since ba7443bc656e ("sysfs, kernfs: implement kernfs_create/destroy_root()"), kernfs_put() depends on kernfs_root() to locate the ino_ida. kernfs_root() in turn depends on kernfs_node->parent being set for !dir nodes. This means that kernfs_put() of a !dir node requires its ->parent to be initialized. This leads to oops when a newly created !dir node is destroyed without going through kernfs_add_one() or after failing kernfs_add_one() before ->parent is set. kernfs_root() invoked from kernfs_put() will try to dereference NULL parent. Fix it by moving parent association to kernfs_new_node() from kernfs_add_one(). kernfs_new_node() now takes @parent instead of @root and determines the root from the parent and also sets the new node's parent properly. @parent parameter is removed from kernfs_add_one(). As there's no parent when creating the root node, __kernfs_new_node() which takes @root as before and doesn't set the parent is used in that case. This ensures that a kernfs_node in any stage in its life has its parent associated and thus can be put. Signed-off-by: Tejun Heo <tj@kernel.org> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'fs/kernfs/dir.c')
-rw-r--r--fs/kernfs/dir.c40
1 files changed, 26 insertions, 14 deletions
diff --git a/fs/kernfs/dir.c b/fs/kernfs/dir.c
index 510b5062ef30..5104cf5d25c5 100644
--- a/fs/kernfs/dir.c
+++ b/fs/kernfs/dir.c
@@ -324,8 +324,9 @@ const struct dentry_operations kernfs_dops = {
.d_release = kernfs_dop_release,
};
-struct kernfs_node *kernfs_new_node(struct kernfs_root *root, const char *name,
- umode_t mode, unsigned flags)
+static struct kernfs_node *__kernfs_new_node(struct kernfs_root *root,
+ const char *name, umode_t mode,
+ unsigned flags)
{
char *dup_name = NULL;
struct kernfs_node *kn;
@@ -362,6 +363,20 @@ struct kernfs_node *kernfs_new_node(struct kernfs_root *root, const char *name,
return NULL;
}
+struct kernfs_node *kernfs_new_node(struct kernfs_node *parent,
+ const char *name, umode_t mode,
+ unsigned flags)
+{
+ struct kernfs_node *kn;
+
+ kn = __kernfs_new_node(kernfs_root(parent), name, mode, flags);
+ if (kn) {
+ kernfs_get(parent);
+ kn->parent = parent;
+ }
+ return kn;
+}
+
/**
* kernfs_addrm_start - prepare for kernfs_node add/remove
* @acxt: pointer to kernfs_addrm_cxt to be used
@@ -386,11 +401,10 @@ void kernfs_addrm_start(struct kernfs_addrm_cxt *acxt)
* kernfs_add_one - add kernfs_node to parent without warning
* @acxt: addrm context to use
* @kn: kernfs_node to be added
- * @parent: the parent kernfs_node to add @kn to
*
- * Get @parent and set @kn->parent to it and increment nlink of the
- * parent inode if @kn is a directory and link into the children list
- * of the parent.
+ * The caller must already have initialized @kn->parent. This
+ * function increments nlink of the parent's inode if @kn is a
+ * directory and link into the children list of the parent.
*
* This function should be called between calls to
* kernfs_addrm_start() and kernfs_addrm_finish() and should be passed
@@ -403,9 +417,9 @@ void kernfs_addrm_start(struct kernfs_addrm_cxt *acxt)
* 0 on success, -EEXIST if entry with the given name already
* exists.
*/
-int kernfs_add_one(struct kernfs_addrm_cxt *acxt, struct kernfs_node *kn,
- struct kernfs_node *parent)
+int kernfs_add_one(struct kernfs_addrm_cxt *acxt, struct kernfs_node *kn)
{
+ struct kernfs_node *parent = kn->parent;
bool has_ns = kernfs_ns_enabled(parent);
struct kernfs_iattrs *ps_iattr;
int ret;
@@ -423,8 +437,6 @@ int kernfs_add_one(struct kernfs_addrm_cxt *acxt, struct kernfs_node *kn,
return -ENOENT;
kn->hash = kernfs_name_hash(kn->name, kn->ns);
- kn->parent = parent;
- kernfs_get(parent);
ret = kernfs_link_sibling(kn);
if (ret)
@@ -600,7 +612,8 @@ struct kernfs_root *kernfs_create_root(struct kernfs_dir_ops *kdops, void *priv)
ida_init(&root->ino_ida);
- kn = kernfs_new_node(root, "", S_IFDIR | S_IRUGO | S_IXUGO, KERNFS_DIR);
+ kn = __kernfs_new_node(root, "", S_IFDIR | S_IRUGO | S_IXUGO,
+ KERNFS_DIR);
if (!kn) {
ida_destroy(&root->ino_ida);
kfree(root);
@@ -648,8 +661,7 @@ struct kernfs_node *kernfs_create_dir_ns(struct kernfs_node *parent,
int rc;
/* allocate */
- kn = kernfs_new_node(kernfs_root(parent), name, mode | S_IFDIR,
- KERNFS_DIR);
+ kn = kernfs_new_node(parent, name, mode | S_IFDIR, KERNFS_DIR);
if (!kn)
return ERR_PTR(-ENOMEM);
@@ -659,7 +671,7 @@ struct kernfs_node *kernfs_create_dir_ns(struct kernfs_node *parent,
/* link in */
kernfs_addrm_start(&acxt);
- rc = kernfs_add_one(&acxt, kn, parent);
+ rc = kernfs_add_one(&acxt, kn);
kernfs_addrm_finish(&acxt);
if (!rc)