diff options
author | Tejun Heo <tj@kernel.org> | 2014-01-17 09:58:25 -0500 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2014-01-17 14:50:07 -0500 |
commit | db4aad209bc9aefd91f0a9aeb9e37364088b39ad (patch) | |
tree | 28cc941640469a11111808a6e77be61e892e5041 /fs/kernfs | |
parent | 917f56caaabc215f9658006dad28a9665ec0ce19 (diff) |
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')
-rw-r--r-- | fs/kernfs/dir.c | 40 | ||||
-rw-r--r-- | fs/kernfs/file.c | 5 | ||||
-rw-r--r-- | fs/kernfs/kernfs-internal.h | 8 | ||||
-rw-r--r-- | fs/kernfs/symlink.c | 5 |
4 files changed, 34 insertions, 24 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 = { | |||
324 | .d_release = kernfs_dop_release, | 324 | .d_release = kernfs_dop_release, |
325 | }; | 325 | }; |
326 | 326 | ||
327 | struct kernfs_node *kernfs_new_node(struct kernfs_root *root, const char *name, | 327 | static struct kernfs_node *__kernfs_new_node(struct kernfs_root *root, |
328 | umode_t mode, unsigned flags) | 328 | const char *name, umode_t mode, |
329 | unsigned flags) | ||
329 | { | 330 | { |
330 | char *dup_name = NULL; | 331 | char *dup_name = NULL; |
331 | struct kernfs_node *kn; | 332 | struct kernfs_node *kn; |
@@ -362,6 +363,20 @@ struct kernfs_node *kernfs_new_node(struct kernfs_root *root, const char *name, | |||
362 | return NULL; | 363 | return NULL; |
363 | } | 364 | } |
364 | 365 | ||
366 | struct kernfs_node *kernfs_new_node(struct kernfs_node *parent, | ||
367 | const char *name, umode_t mode, | ||
368 | unsigned flags) | ||
369 | { | ||
370 | struct kernfs_node *kn; | ||
371 | |||
372 | kn = __kernfs_new_node(kernfs_root(parent), name, mode, flags); | ||
373 | if (kn) { | ||
374 | kernfs_get(parent); | ||
375 | kn->parent = parent; | ||
376 | } | ||
377 | return kn; | ||
378 | } | ||
379 | |||
365 | /** | 380 | /** |
366 | * kernfs_addrm_start - prepare for kernfs_node add/remove | 381 | * kernfs_addrm_start - prepare for kernfs_node add/remove |
367 | * @acxt: pointer to kernfs_addrm_cxt to be used | 382 | * @acxt: pointer to kernfs_addrm_cxt to be used |
@@ -386,11 +401,10 @@ void kernfs_addrm_start(struct kernfs_addrm_cxt *acxt) | |||
386 | * kernfs_add_one - add kernfs_node to parent without warning | 401 | * kernfs_add_one - add kernfs_node to parent without warning |
387 | * @acxt: addrm context to use | 402 | * @acxt: addrm context to use |
388 | * @kn: kernfs_node to be added | 403 | * @kn: kernfs_node to be added |
389 | * @parent: the parent kernfs_node to add @kn to | ||
390 | * | 404 | * |
391 | * Get @parent and set @kn->parent to it and increment nlink of the | 405 | * The caller must already have initialized @kn->parent. This |
392 | * parent inode if @kn is a directory and link into the children list | 406 | * function increments nlink of the parent's inode if @kn is a |
393 | * of the parent. | 407 | * directory and link into the children list of the parent. |
394 | * | 408 | * |
395 | * This function should be called between calls to | 409 | * This function should be called between calls to |
396 | * kernfs_addrm_start() and kernfs_addrm_finish() and should be passed | 410 | * kernfs_addrm_start() and kernfs_addrm_finish() and should be passed |
@@ -403,9 +417,9 @@ void kernfs_addrm_start(struct kernfs_addrm_cxt *acxt) | |||
403 | * 0 on success, -EEXIST if entry with the given name already | 417 | * 0 on success, -EEXIST if entry with the given name already |
404 | * exists. | 418 | * exists. |
405 | */ | 419 | */ |
406 | int kernfs_add_one(struct kernfs_addrm_cxt *acxt, struct kernfs_node *kn, | 420 | int kernfs_add_one(struct kernfs_addrm_cxt *acxt, struct kernfs_node *kn) |
407 | struct kernfs_node *parent) | ||
408 | { | 421 | { |
422 | struct kernfs_node *parent = kn->parent; | ||
409 | bool has_ns = kernfs_ns_enabled(parent); | 423 | bool has_ns = kernfs_ns_enabled(parent); |
410 | struct kernfs_iattrs *ps_iattr; | 424 | struct kernfs_iattrs *ps_iattr; |
411 | int ret; | 425 | int ret; |
@@ -423,8 +437,6 @@ int kernfs_add_one(struct kernfs_addrm_cxt *acxt, struct kernfs_node *kn, | |||
423 | return -ENOENT; | 437 | return -ENOENT; |
424 | 438 | ||
425 | kn->hash = kernfs_name_hash(kn->name, kn->ns); | 439 | kn->hash = kernfs_name_hash(kn->name, kn->ns); |
426 | kn->parent = parent; | ||
427 | kernfs_get(parent); | ||
428 | 440 | ||
429 | ret = kernfs_link_sibling(kn); | 441 | ret = kernfs_link_sibling(kn); |
430 | if (ret) | 442 | if (ret) |
@@ -600,7 +612,8 @@ struct kernfs_root *kernfs_create_root(struct kernfs_dir_ops *kdops, void *priv) | |||
600 | 612 | ||
601 | ida_init(&root->ino_ida); | 613 | ida_init(&root->ino_ida); |
602 | 614 | ||
603 | kn = kernfs_new_node(root, "", S_IFDIR | S_IRUGO | S_IXUGO, KERNFS_DIR); | 615 | kn = __kernfs_new_node(root, "", S_IFDIR | S_IRUGO | S_IXUGO, |
616 | KERNFS_DIR); | ||
604 | if (!kn) { | 617 | if (!kn) { |
605 | ida_destroy(&root->ino_ida); | 618 | ida_destroy(&root->ino_ida); |
606 | kfree(root); | 619 | kfree(root); |
@@ -648,8 +661,7 @@ struct kernfs_node *kernfs_create_dir_ns(struct kernfs_node *parent, | |||
648 | int rc; | 661 | int rc; |
649 | 662 | ||
650 | /* allocate */ | 663 | /* allocate */ |
651 | kn = kernfs_new_node(kernfs_root(parent), name, mode | S_IFDIR, | 664 | kn = kernfs_new_node(parent, name, mode | S_IFDIR, KERNFS_DIR); |
652 | KERNFS_DIR); | ||
653 | if (!kn) | 665 | if (!kn) |
654 | return ERR_PTR(-ENOMEM); | 666 | return ERR_PTR(-ENOMEM); |
655 | 667 | ||
@@ -659,7 +671,7 @@ struct kernfs_node *kernfs_create_dir_ns(struct kernfs_node *parent, | |||
659 | 671 | ||
660 | /* link in */ | 672 | /* link in */ |
661 | kernfs_addrm_start(&acxt); | 673 | kernfs_addrm_start(&acxt); |
662 | rc = kernfs_add_one(&acxt, kn, parent); | 674 | rc = kernfs_add_one(&acxt, kn); |
663 | kernfs_addrm_finish(&acxt); | 675 | kernfs_addrm_finish(&acxt); |
664 | 676 | ||
665 | if (!rc) | 677 | if (!rc) |
diff --git a/fs/kernfs/file.c b/fs/kernfs/file.c index bdd38854ef65..dbf397bfdff2 100644 --- a/fs/kernfs/file.c +++ b/fs/kernfs/file.c | |||
@@ -829,8 +829,7 @@ struct kernfs_node *__kernfs_create_file(struct kernfs_node *parent, | |||
829 | if (name_is_static) | 829 | if (name_is_static) |
830 | flags |= KERNFS_STATIC_NAME; | 830 | flags |= KERNFS_STATIC_NAME; |
831 | 831 | ||
832 | kn = kernfs_new_node(kernfs_root(parent), name, | 832 | kn = kernfs_new_node(parent, name, (mode & S_IALLUGO) | S_IFREG, flags); |
833 | (mode & S_IALLUGO) | S_IFREG, flags); | ||
834 | if (!kn) | 833 | if (!kn) |
835 | return ERR_PTR(-ENOMEM); | 834 | return ERR_PTR(-ENOMEM); |
836 | 835 | ||
@@ -857,7 +856,7 @@ struct kernfs_node *__kernfs_create_file(struct kernfs_node *parent, | |||
857 | kn->flags |= KERNFS_HAS_MMAP; | 856 | kn->flags |= KERNFS_HAS_MMAP; |
858 | 857 | ||
859 | kernfs_addrm_start(&acxt); | 858 | kernfs_addrm_start(&acxt); |
860 | rc = kernfs_add_one(&acxt, kn, parent); | 859 | rc = kernfs_add_one(&acxt, kn); |
861 | kernfs_addrm_finish(&acxt); | 860 | kernfs_addrm_finish(&acxt); |
862 | 861 | ||
863 | if (rc) { | 862 | if (rc) { |
diff --git a/fs/kernfs/kernfs-internal.h b/fs/kernfs/kernfs-internal.h index c6ba5bc37a98..eb536b76374a 100644 --- a/fs/kernfs/kernfs-internal.h +++ b/fs/kernfs/kernfs-internal.h | |||
@@ -101,11 +101,11 @@ extern const struct inode_operations kernfs_dir_iops; | |||
101 | struct kernfs_node *kernfs_get_active(struct kernfs_node *kn); | 101 | struct kernfs_node *kernfs_get_active(struct kernfs_node *kn); |
102 | void kernfs_put_active(struct kernfs_node *kn); | 102 | void kernfs_put_active(struct kernfs_node *kn); |
103 | void kernfs_addrm_start(struct kernfs_addrm_cxt *acxt); | 103 | void kernfs_addrm_start(struct kernfs_addrm_cxt *acxt); |
104 | int kernfs_add_one(struct kernfs_addrm_cxt *acxt, struct kernfs_node *kn, | 104 | int kernfs_add_one(struct kernfs_addrm_cxt *acxt, struct kernfs_node *kn); |
105 | struct kernfs_node *parent); | ||
106 | void kernfs_addrm_finish(struct kernfs_addrm_cxt *acxt); | 105 | void kernfs_addrm_finish(struct kernfs_addrm_cxt *acxt); |
107 | struct kernfs_node *kernfs_new_node(struct kernfs_root *root, const char *name, | 106 | struct kernfs_node *kernfs_new_node(struct kernfs_node *parent, |
108 | umode_t mode, unsigned flags); | 107 | const char *name, umode_t mode, |
108 | unsigned flags); | ||
109 | 109 | ||
110 | /* | 110 | /* |
111 | * file.c | 111 | * file.c |
diff --git a/fs/kernfs/symlink.c b/fs/kernfs/symlink.c index a03e26036ef9..4d457055acb9 100644 --- a/fs/kernfs/symlink.c +++ b/fs/kernfs/symlink.c | |||
@@ -30,8 +30,7 @@ struct kernfs_node *kernfs_create_link(struct kernfs_node *parent, | |||
30 | struct kernfs_addrm_cxt acxt; | 30 | struct kernfs_addrm_cxt acxt; |
31 | int error; | 31 | int error; |
32 | 32 | ||
33 | kn = kernfs_new_node(kernfs_root(parent), name, S_IFLNK|S_IRWXUGO, | 33 | kn = kernfs_new_node(parent, name, S_IFLNK|S_IRWXUGO, KERNFS_LINK); |
34 | KERNFS_LINK); | ||
35 | if (!kn) | 34 | if (!kn) |
36 | return ERR_PTR(-ENOMEM); | 35 | return ERR_PTR(-ENOMEM); |
37 | 36 | ||
@@ -41,7 +40,7 @@ struct kernfs_node *kernfs_create_link(struct kernfs_node *parent, | |||
41 | kernfs_get(target); /* ref owned by symlink */ | 40 | kernfs_get(target); /* ref owned by symlink */ |
42 | 41 | ||
43 | kernfs_addrm_start(&acxt); | 42 | kernfs_addrm_start(&acxt); |
44 | error = kernfs_add_one(&acxt, kn, parent); | 43 | error = kernfs_add_one(&acxt, kn); |
45 | kernfs_addrm_finish(&acxt); | 44 | kernfs_addrm_finish(&acxt); |
46 | 45 | ||
47 | if (!error) | 46 | if (!error) |