diff options
Diffstat (limited to 'fs/debugfs/inode.c')
-rw-r--r-- | fs/debugfs/inode.c | 39 |
1 files changed, 29 insertions, 10 deletions
diff --git a/fs/debugfs/inode.c b/fs/debugfs/inode.c index 8c41b52da358..1e3b99d3db0d 100644 --- a/fs/debugfs/inode.c +++ b/fs/debugfs/inode.c | |||
@@ -66,7 +66,7 @@ static struct inode *debugfs_get_inode(struct super_block *sb, umode_t mode, dev | |||
66 | break; | 66 | break; |
67 | } | 67 | } |
68 | } | 68 | } |
69 | return inode; | 69 | return inode; |
70 | } | 70 | } |
71 | 71 | ||
72 | /* SMP-safe */ | 72 | /* SMP-safe */ |
@@ -317,7 +317,7 @@ static struct dentry *__create_file(const char *name, umode_t mode, | |||
317 | goto exit; | 317 | goto exit; |
318 | 318 | ||
319 | /* If the parent is not specified, we create it in the root. | 319 | /* If the parent is not specified, we create it in the root. |
320 | * We need the root dentry to do this, which is in the super | 320 | * We need the root dentry to do this, which is in the super |
321 | * block. A pointer to that is in the struct vfsmount that we | 321 | * block. A pointer to that is in the struct vfsmount that we |
322 | * have around. | 322 | * have around. |
323 | */ | 323 | */ |
@@ -330,7 +330,7 @@ static struct dentry *__create_file(const char *name, umode_t mode, | |||
330 | switch (mode & S_IFMT) { | 330 | switch (mode & S_IFMT) { |
331 | case S_IFDIR: | 331 | case S_IFDIR: |
332 | error = debugfs_mkdir(parent->d_inode, dentry, mode); | 332 | error = debugfs_mkdir(parent->d_inode, dentry, mode); |
333 | 333 | ||
334 | break; | 334 | break; |
335 | case S_IFLNK: | 335 | case S_IFLNK: |
336 | error = debugfs_link(parent->d_inode, dentry, mode, | 336 | error = debugfs_link(parent->d_inode, dentry, mode, |
@@ -534,7 +534,7 @@ EXPORT_SYMBOL_GPL(debugfs_remove); | |||
534 | */ | 534 | */ |
535 | void debugfs_remove_recursive(struct dentry *dentry) | 535 | void debugfs_remove_recursive(struct dentry *dentry) |
536 | { | 536 | { |
537 | struct dentry *child, *next, *parent; | 537 | struct dentry *child, *parent; |
538 | 538 | ||
539 | if (IS_ERR_OR_NULL(dentry)) | 539 | if (IS_ERR_OR_NULL(dentry)) |
540 | return; | 540 | return; |
@@ -546,30 +546,49 @@ void debugfs_remove_recursive(struct dentry *dentry) | |||
546 | parent = dentry; | 546 | parent = dentry; |
547 | down: | 547 | down: |
548 | mutex_lock(&parent->d_inode->i_mutex); | 548 | mutex_lock(&parent->d_inode->i_mutex); |
549 | list_for_each_entry_safe(child, next, &parent->d_subdirs, d_u.d_child) { | 549 | loop: |
550 | /* | ||
551 | * The parent->d_subdirs is protected by the d_lock. Outside that | ||
552 | * lock, the child can be unlinked and set to be freed which can | ||
553 | * use the d_u.d_child as the rcu head and corrupt this list. | ||
554 | */ | ||
555 | spin_lock(&parent->d_lock); | ||
556 | list_for_each_entry(child, &parent->d_subdirs, d_u.d_child) { | ||
550 | if (!debugfs_positive(child)) | 557 | if (!debugfs_positive(child)) |
551 | continue; | 558 | continue; |
552 | 559 | ||
553 | /* perhaps simple_empty(child) makes more sense */ | 560 | /* perhaps simple_empty(child) makes more sense */ |
554 | if (!list_empty(&child->d_subdirs)) { | 561 | if (!list_empty(&child->d_subdirs)) { |
562 | spin_unlock(&parent->d_lock); | ||
555 | mutex_unlock(&parent->d_inode->i_mutex); | 563 | mutex_unlock(&parent->d_inode->i_mutex); |
556 | parent = child; | 564 | parent = child; |
557 | goto down; | 565 | goto down; |
558 | } | 566 | } |
559 | up: | 567 | |
568 | spin_unlock(&parent->d_lock); | ||
569 | |||
560 | if (!__debugfs_remove(child, parent)) | 570 | if (!__debugfs_remove(child, parent)) |
561 | simple_release_fs(&debugfs_mount, &debugfs_mount_count); | 571 | simple_release_fs(&debugfs_mount, &debugfs_mount_count); |
572 | |||
573 | /* | ||
574 | * The parent->d_lock protects agaist child from unlinking | ||
575 | * from d_subdirs. When releasing the parent->d_lock we can | ||
576 | * no longer trust that the next pointer is valid. | ||
577 | * Restart the loop. We'll skip this one with the | ||
578 | * debugfs_positive() check. | ||
579 | */ | ||
580 | goto loop; | ||
562 | } | 581 | } |
582 | spin_unlock(&parent->d_lock); | ||
563 | 583 | ||
564 | mutex_unlock(&parent->d_inode->i_mutex); | 584 | mutex_unlock(&parent->d_inode->i_mutex); |
565 | child = parent; | 585 | child = parent; |
566 | parent = parent->d_parent; | 586 | parent = parent->d_parent; |
567 | mutex_lock(&parent->d_inode->i_mutex); | 587 | mutex_lock(&parent->d_inode->i_mutex); |
568 | 588 | ||
569 | if (child != dentry) { | 589 | if (child != dentry) |
570 | next = list_next_entry(child, d_u.d_child); | 590 | /* go up */ |
571 | goto up; | 591 | goto loop; |
572 | } | ||
573 | 592 | ||
574 | if (!__debugfs_remove(child, parent)) | 593 | if (!__debugfs_remove(child, parent)) |
575 | simple_release_fs(&debugfs_mount, &debugfs_mount_count); | 594 | simple_release_fs(&debugfs_mount, &debugfs_mount_count); |