two requirements: no file creations in IS_DEADDIR and no cross-directory renames whatsoever.
Unified · Split
| @@ -332,7 +332,10 @@ static struct dentry *start_creating(const char *name, struct dentry *parent) · 331 unmodified lines | ||
| 332 | 332 | parent = debugfs_mount->mnt_root; |
| 333 | 333 | |
| 334 | 334 | inode_lock(d_inode(parent)); |
| 335 | − dentry = lookup_one_len(name, parent, strlen(name)); | |
| 335 | + if (unlikely(IS_DEADDIR(d_inode(parent)))) | |
| 336 | + dentry = ERR_PTR(-ENOENT); | |
| 337 | + else | |
| 338 | + dentry = lookup_one_len(name, parent, strlen(name)); | |
| 336 | 339 | if (!IS_ERR(dentry) && d_really_is_positive(dentry)) { |
| 337 | 340 | if (d_is_dir(dentry)) |
| 338 | 341 | pr_err("Directory '%s' with parent '%s' already present!\n", |
| @@ -681,62 +684,15 @@ static void __debugfs_file_removed(struct dentry *dentry) · 342 unmodified lines | ||
| 681 | 684 | wait_for_completion(&fsd->active_users_drained); |
| 682 | 685 | } |
| 683 | 686 | |
| 684 | −static int __debugfs_remove(struct dentry *dentry, struct dentry *parent) | |
| 685 | −{ | |
| 686 | − int ret = 0; | |
| 687 | − | |
| 688 | − if (simple_positive(dentry)) { | |
| 689 | − dget(dentry); | |
| 690 | − if (d_is_dir(dentry)) { | |
| 691 | − ret = simple_rmdir(d_inode(parent), dentry); | |
| 692 | − if (!ret) | |
| 693 | − fsnotify_rmdir(d_inode(parent), dentry); | |
| 694 | − } else { | |
| 695 | − simple_unlink(d_inode(parent), dentry); | |
| 696 | − fsnotify_unlink(d_inode(parent), dentry); | |
| 697 | − } | |
| 698 | − if (!ret) | |
| 699 | − d_delete(dentry); | |
| 700 | − if (d_is_reg(dentry)) | |
| 701 | − __debugfs_file_removed(dentry); | |
| 702 | − dput(dentry); | |
| 703 | − } | |
| 704 | − return ret; | |
| 705 | −} | |
| 706 | − | |
| 707 | −/** | |
| 708 | − * debugfs_remove - removes a file or directory from the debugfs filesystem | |
| 709 | − * @dentry: a pointer to a the dentry of the file or directory to be | |
| 710 | − * removed. If this parameter is NULL or an error value, nothing | |
| 711 | − * will be done. | |
| 712 | − * | |
| 713 | − * This function removes a file or directory in debugfs that was previously | |
| 714 | − * created with a call to another debugfs function (like | |
| 715 | − * debugfs_create_file() or variants thereof.) | |
| 716 | − * | |
| 717 | − * This function is required to be called in order for the file to be | |
| 718 | − * removed, no automatic cleanup of files will happen when a module is | |
| 719 | − * removed, you are responsible here. | |
| 720 | − */ | |
| 721 | −void debugfs_remove(struct dentry *dentry) | |
| 687 | +static void remove_one(struct dentry *victim) | |
| 722 | 688 | { |
| 723 | − struct dentry *parent; | |
| 724 | − int ret; | |
| 725 | − | |
| 726 | − if (IS_ERR_OR_NULL(dentry)) | |
| 727 | − return; | |
| 728 | − | |
| 729 | − parent = dentry->d_parent; | |
| 730 | − inode_lock(d_inode(parent)); | |
| 731 | − ret = __debugfs_remove(dentry, parent); | |
| 732 | − inode_unlock(d_inode(parent)); | |
| 733 | − if (!ret) | |
| 734 | − simple_release_fs(&debugfs_mount, &debugfs_mount_count); | |
| 689 | + if (d_is_reg(victim)) | |
| 690 | + __debugfs_file_removed(victim); | |
| 691 | + simple_release_fs(&debugfs_mount, &debugfs_mount_count); | |
| 735 | 692 | } |
| 736 | −EXPORT_SYMBOL_GPL(debugfs_remove); | |
| 737 | 693 | |
| 738 | 694 | /** |
| 739 | − * debugfs_remove_recursive - recursively removes a directory | |
| 695 | + * debugfs_remove - recursively removes a directory | |
| 740 | 696 | * @dentry: a pointer to a the dentry of the directory to be removed. If this |
| 741 | 697 | * parameter is NULL or an error value, nothing will be done. |
| 742 | 698 | * |
| @@ -748,65 +704,16 @@ EXPORT_SYMBOL_GPL(debugfs_remove); · 5 unmodified lines | ||
| 748 | 704 | * removed, no automatic cleanup of files will happen when a module is |
| 749 | 705 | * removed, you are responsible here. |
| 750 | 706 | */ |
| 751 | −void debugfs_remove_recursive(struct dentry *dentry) | |
| 707 | +void debugfs_remove(struct dentry *dentry) | |
| 752 | 708 | { |
| 753 | − struct dentry *child, *parent; | |
| 754 | − | |
| 755 | 709 | if (IS_ERR_OR_NULL(dentry)) |
| 756 | 710 | return; |
| 757 | 711 | |
| 758 | − parent = dentry; | |
| 759 | − down: | |
| 760 | − inode_lock(d_inode(parent)); | |
| 761 | − loop: | |
| 762 | − /* | |
| 763 | − * The parent->d_subdirs is protected by the d_lock. Outside that | |
| 764 | − * lock, the child can be unlinked and set to be freed which can | |
| 765 | − * use the d_u.d_child as the rcu head and corrupt this list. | |
| 766 | − */ | |
| 767 | − spin_lock(&parent->d_lock); | |
| 768 | − list_for_each_entry(child, &parent->d_subdirs, d_child) { | |
| 769 | − if (!simple_positive(child)) | |
| 770 | − continue; | |
| 771 | − | |
| 772 | − /* perhaps simple_empty(child) makes more sense */ | |
| 773 | − if (!list_empty(&child->d_subdirs)) { | |
| 774 | − spin_unlock(&parent->d_lock); | |
| 775 | − inode_unlock(d_inode(parent)); | |
| 776 | − parent = child; | |
| 777 | − goto down; | |
| 778 | − } | |
| 779 | − | |
| 780 | − spin_unlock(&parent->d_lock); | |
| 781 | − | |
| 782 | − if (!__debugfs_remove(child, parent)) | |
| 783 | − simple_release_fs(&debugfs_mount, &debugfs_mount_count); | |
| 784 | − | |
| 785 | − /* | |
| 786 | − * The parent->d_lock protects agaist child from unlinking | |
| 787 | − * from d_subdirs. When releasing the parent->d_lock we can | |
| 788 | − * no longer trust that the next pointer is valid. | |
| 789 | − * Restart the loop. We'll skip this one with the | |
| 790 | − * simple_positive() check. | |
| 791 | − */ | |
| 792 | − goto loop; | |
| 793 | − } | |
| 794 | − spin_unlock(&parent->d_lock); | |
| 795 | − | |
| 796 | − inode_unlock(d_inode(parent)); | |
| 797 | − child = parent; | |
| 798 | − parent = parent->d_parent; | |
| 799 | − inode_lock(d_inode(parent)); | |
| 800 | − | |
| 801 | − if (child != dentry) | |
| 802 | − /* go up */ | |
| 803 | − goto loop; | |
| 804 | − | |
| 805 | − if (!__debugfs_remove(child, parent)) | |
| 806 | − simple_release_fs(&debugfs_mount, &debugfs_mount_count); | |
| 807 | − inode_unlock(d_inode(parent)); | |
| 712 | + simple_pin_fs(&debug_fs_type, &debugfs_mount, &debugfs_mount_count); | |
| 713 | + simple_recursive_removal(dentry, remove_one); | |
| 714 | + simple_release_fs(&debugfs_mount, &debugfs_mount_count); | |
| 808 | 715 | } |
| 809 | −EXPORT_SYMBOL_GPL(debugfs_remove_recursive); | |
| 716 | +EXPORT_SYMBOL_GPL(debugfs_remove); | |
| 810 | 717 | |
| 811 | 718 | /** |
| 812 | 719 | * debugfs_rename - rename a file/directory in the debugfs filesystem |
| @@ -19,6 +19,7 @@ · 18 unmodified lines | ||
| 19 | 19 | #include <linux/buffer_head.h> /* sync_mapping_buffers */ |
| 20 | 20 | #include <linux/fs_context.h> |
| 21 | 21 | #include <linux/pseudo_fs.h> |
| 22 | +#include <linux/fsnotify.h> | |
| 22 | 23 | |
| 23 | 24 | #include <linux/uaccess.h> |
| 24 | 25 | |
| @@ -239,6 +240,75 @@ const struct inode_operations simple_dir_inode_operations = { · 214 unmodified lines | ||
| 239 | 240 | }; |
| 240 | 241 | EXPORT_SYMBOL(simple_dir_inode_operations); |
| 241 | 242 | |
| 243 | +static struct dentry *find_next_child(struct dentry *parent, struct dentry *prev) | |
| 244 | +{ | |
| 245 | + struct dentry *child = NULL; | |
| 246 | + struct list_head *p = prev ? &prev->d_child : &parent->d_subdirs; | |
| 247 | + | |
| 248 | + spin_lock(&parent->d_lock); | |
| 249 | + while ((p = p->next) != &parent->d_subdirs) { | |
| 250 | + struct dentry *d = container_of(p, struct dentry, d_child); | |
| 251 | + if (simple_positive(d)) { | |
| 252 | + spin_lock_nested(&d->d_lock, DENTRY_D_LOCK_NESTED); | |
| 253 | + if (simple_positive(d)) | |
| 254 | + child = dget_dlock(d); | |
| 255 | + spin_unlock(&d->d_lock); | |
| 256 | + if (likely(child)) | |
| 257 | + break; | |
| 258 | + } | |
| 259 | + } | |
| 260 | + spin_unlock(&parent->d_lock); | |
| 261 | + dput(prev); | |
| 262 | + return child; | |
| 263 | +} | |
| 264 | + | |
| 265 | +void simple_recursive_removal(struct dentry *dentry, | |
| 266 | + void (*callback)(struct dentry *)) | |
| 267 | +{ | |
| 268 | + struct dentry *this = dget(dentry); | |
| 269 | + while (true) { | |
| 270 | + struct dentry *victim = NULL, *child; | |
| 271 | + struct inode *inode = this->d_inode; | |
| 272 | + | |
| 273 | + inode_lock(inode); | |
| 274 | + if (d_is_dir(this)) | |
| 275 | + inode->i_flags |= S_DEAD; | |
| 276 | + while ((child = find_next_child(this, victim)) == NULL) { | |
| 277 | + // kill and ascend | |
| 278 | + // update metadata while it's still locked | |
| 279 | + inode->i_ctime = current_time(inode); | |
| 280 | + clear_nlink(inode); | |
| 281 | + inode_unlock(inode); | |
| 282 | + victim = this; | |
| 283 | + this = this->d_parent; | |
| 284 | + inode = this->d_inode; | |
| 285 | + inode_lock(inode); | |
| 286 | + if (simple_positive(victim)) { | |
| 287 | + d_invalidate(victim); // avoid lost mounts | |
| 288 | + if (d_is_dir(victim)) | |
| 289 | + fsnotify_rmdir(inode, victim); | |
| 290 | + else | |
| 291 | + fsnotify_unlink(inode, victim); | |
| 292 | + if (callback) | |
| 293 | + callback(victim); | |
| 294 | + dput(victim); // unpin it | |
| 295 | + } | |
| 296 | + if (victim == dentry) { | |
| 297 | + inode->i_ctime = inode->i_mtime = | |
| 298 | + current_time(inode); | |
| 299 | + if (d_is_dir(dentry)) | |
| 300 | + drop_nlink(inode); | |
| 301 | + inode_unlock(inode); | |
| 302 | + dput(dentry); | |
| 303 | + return; | |
| 304 | + } | |
| 305 | + } | |
| 306 | + inode_unlock(inode); | |
| 307 | + this = child; | |
| 308 | + } | |
| 309 | +} | |
| 310 | +EXPORT_SYMBOL(simple_recursive_removal); | |
| 311 | + | |
| 242 | 312 | static const struct super_operations simple_super_operations = { |
| 243 | 313 | .statfs = simple_statfs, |
| 244 | 314 | }; |
| @@ -330,7 +330,10 @@ static struct dentry *start_creating(const char *name, struct dentry *parent) · 329 unmodified lines | ||
| 330 | 330 | parent = tracefs_mount->mnt_root; |
| 331 | 331 | |
| 332 | 332 | inode_lock(parent->d_inode); |
| 333 | − dentry = lookup_one_len(name, parent, strlen(name)); | |
| 333 | + if (unlikely(IS_DEADDIR(parent->d_inode))) | |
| 334 | + dentry = ERR_PTR(-ENOENT); | |
| 335 | + else | |
| 336 | + dentry = lookup_one_len(name, parent, strlen(name)); | |
| 334 | 337 | if (!IS_ERR(dentry) && dentry->d_inode) { |
| 335 | 338 | dput(dentry); |
| 336 | 339 | dentry = ERR_PTR(-EEXIST); |
| @@ -499,122 +502,27 @@ __init struct dentry *tracefs_create_instance_dir(const char *name, · 162 unmodified lines | ||
| 499 | 502 | return dentry; |
| 500 | 503 | } |
| 501 | 504 | |
| 502 | −static int __tracefs_remove(struct dentry *dentry, struct dentry *parent) | |
| 505 | +static void remove_one(struct dentry *victim) | |
| 503 | 506 | { |
| 504 | − int ret = 0; | |
| 505 | − | |
| 506 | − if (simple_positive(dentry)) { | |
| 507 | − if (dentry->d_inode) { | |
| 508 | − dget(dentry); | |
| 509 | − switch (dentry->d_inode->i_mode & S_IFMT) { | |
| 510 | − case S_IFDIR: | |
| 511 | − ret = simple_rmdir(parent->d_inode, dentry); | |
| 512 | − if (!ret) | |
| 513 | − fsnotify_rmdir(parent->d_inode, dentry); | |
| 514 | − break; | |
| 515 | − default: | |
| 516 | − simple_unlink(parent->d_inode, dentry); | |
| 517 | − fsnotify_unlink(parent->d_inode, dentry); | |
| 518 | − break; | |
| 519 | − } | |
| 520 | − if (!ret) | |
| 521 | − d_delete(dentry); | |
| 522 | − dput(dentry); | |
| 523 | − } | |
| 524 | − } | |
| 525 | − return ret; | |
| 526 | −} | |
| 527 | − | |
| 528 | −/** | |
| 529 | − * tracefs_remove - removes a file or directory from the tracefs filesystem | |
| 530 | − * @dentry: a pointer to a the dentry of the file or directory to be | |
| 531 | − * removed. | |
| 532 | − * | |
| 533 | − * This function removes a file or directory in tracefs that was previously | |
| 534 | − * created with a call to another tracefs function (like | |
| 535 | − * tracefs_create_file() or variants thereof.) | |
| 536 | − */ | |
| 537 | −void tracefs_remove(struct dentry *dentry) | |
| 538 | −{ | |
| 539 | − struct dentry *parent; | |
| 540 | − int ret; | |
| 541 | − | |
| 542 | − if (IS_ERR_OR_NULL(dentry)) | |
| 543 | − return; | |
| 544 | − | |
| 545 | − parent = dentry->d_parent; | |
| 546 | − inode_lock(parent->d_inode); | |
| 547 | − ret = __tracefs_remove(dentry, parent); | |
| 548 | − inode_unlock(parent->d_inode); | |
| 549 | − if (!ret) | |
| 550 | − simple_release_fs(&tracefs_mount, &tracefs_mount_count); | |
| 507 | + simple_release_fs(&tracefs_mount, &tracefs_mount_count); | |
| 551 | 508 | } |
| 552 | 509 | |
| 553 | 510 | /** |
| 554 | − * tracefs_remove_recursive - recursively removes a directory | |
| 511 | + * tracefs_remove - recursively removes a directory | |
| 555 | 512 | * @dentry: a pointer to a the dentry of the directory to be removed. |
| 556 | 513 | * |
| 557 | 514 | * This function recursively removes a directory tree in tracefs that |
| 558 | 515 | * was previously created with a call to another tracefs function |
| 559 | 516 | * (like tracefs_create_file() or variants thereof.) |
| 560 | 517 | */ |
| 561 | −void tracefs_remove_recursive(struct dentry *dentry) | |
| 518 | +void tracefs_remove(struct dentry *dentry) | |
| 562 | 519 | { |
| 563 | − struct dentry *child, *parent; | |
| 564 | − | |
| 565 | 520 | if (IS_ERR_OR_NULL(dentry)) |
| 566 | 521 | return; |
| 567 | 522 | |
| 568 | − parent = dentry; | |
| 569 | − down: | |
| 570 | − inode_lock(parent->d_inode); | |
| 571 | − loop: | |
| 572 | − /* | |
| 573 | − * The parent->d_subdirs is protected by the d_lock. Outside that | |
| 574 | − * lock, the child can be unlinked and set to be freed which can | |
| 575 | − * use the d_u.d_child as the rcu head and corrupt this list. | |
| 576 | − */ | |
| 577 | − spin_lock(&parent->d_lock); | |
| 578 | − list_for_each_entry(child, &parent->d_subdirs, d_child) { | |
| 579 | − if (!simple_positive(child)) | |
| 580 | − continue; | |
| 581 | − | |
| 582 | − /* perhaps simple_empty(child) makes more sense */ | |
| 583 | − if (!list_empty(&child->d_subdirs)) { | |
| 584 | − spin_unlock(&parent->d_lock); | |
| 585 | − inode_unlock(parent->d_inode); | |
| 586 | − parent = child; | |
| 587 | − goto down; | |
| 588 | − } | |
| 589 | − | |
| 590 | − spin_unlock(&parent->d_lock); | |
| 591 | − | |
| 592 | − if (!__tracefs_remove(child, parent)) | |
| 593 | − simple_release_fs(&tracefs_mount, &tracefs_mount_count); | |
| 594 | − | |
| 595 | − /* | |
| 596 | − * The parent->d_lock protects agaist child from unlinking | |
| 597 | − * from d_subdirs. When releasing the parent->d_lock we can | |
| 598 | − * no longer trust that the next pointer is valid. | |
| 599 | − * Restart the loop. We'll skip this one with the | |
| 600 | − * simple_positive() check. | |
| 601 | − */ | |
| 602 | − goto loop; | |
| 603 | − } | |
| 604 | − spin_unlock(&parent->d_lock); | |
| 605 | − | |
| 606 | − inode_unlock(parent->d_inode); | |
| 607 | − child = parent; | |
| 608 | − parent = parent->d_parent; | |
| 609 | − inode_lock(parent->d_inode); | |
| 610 | − | |
| 611 | − if (child != dentry) | |
| 612 | − /* go up */ | |
| 613 | − goto loop; | |
| 614 | − | |
| 615 | − if (!__tracefs_remove(child, parent)) | |
| 616 | − simple_release_fs(&tracefs_mount, &tracefs_mount_count); | |
| 617 | − inode_unlock(parent->d_inode); | |
| 523 | + simple_pin_fs(&trace_fs_type, &tracefs_mount, &tracefs_mount_count); | |
| 524 | + simple_recursive_removal(dentry, remove_one); | |
| 525 | + simple_release_fs(&tracefs_mount, &tracefs_mount_count); | |
| 618 | 526 | } |
| 619 | 527 | |
| 620 | 528 | /** |
| @@ -83,7 +83,7 @@ struct dentry *debugfs_create_automount(const char *name, · 82 unmodified lines | ||
| 83 | 83 | void *data); |
| 84 | 84 | |
| 85 | 85 | void debugfs_remove(struct dentry *dentry); |
| 86 | −void debugfs_remove_recursive(struct dentry *dentry); | |
| 86 | +#define debugfs_remove_recursive debugfs_remove | |
| 87 | 87 | |
| 88 | 88 | const struct file_operations *debugfs_real_fops(const struct file *filp); |
| 89 | 89 | |
| @@ -3303,6 +3303,8 @@ extern int simple_unlink(struct inode *, struct dentry *); · 3302 unmodified lines | ||
| 3303 | 3303 | extern int simple_rmdir(struct inode *, struct dentry *); |
| 3304 | 3304 | extern int simple_rename(struct inode *, struct dentry *, |
| 3305 | 3305 | struct inode *, struct dentry *, unsigned int); |
| 3306 | +extern void simple_recursive_removal(struct dentry *, | |
| 3307 | + void (*callback)(struct dentry *)); | |
| 3306 | 3308 | extern int noop_fsync(struct file *, loff_t, loff_t, int); |
| 3307 | 3309 | extern int noop_set_page_dirty(struct page *page); |
| 3308 | 3310 | extern void noop_invalidatepage(struct page *page, unsigned int offset, |
| @@ -28,7 +28,6 @@ struct dentry *tracefs_create_file(const char *name, umode_t mode, · 27 unmodified lines | ||
| 28 | 28 | struct dentry *tracefs_create_dir(const char *name, struct dentry *parent); |
| 29 | 29 | |
| 30 | 30 | void tracefs_remove(struct dentry *dentry); |
| 31 | −void tracefs_remove_recursive(struct dentry *dentry); | |
| 32 | 31 | |
| 33 | 32 | struct dentry *tracefs_create_instance_dir(const char *name, struct dentry *parent, |
| 34 | 33 | int (*mkdir)(const char *name), |
| @@ -8496,7 +8496,7 @@ static struct trace_array *trace_array_create(const char *name) · 8495 unmodified lines | ||
| 8496 | 8496 | |
| 8497 | 8497 | ret = event_trace_add_tracer(tr->dir, tr); |
| 8498 | 8498 | if (ret) { |
| 8499 | − tracefs_remove_recursive(tr->dir); | |
| 8499 | + tracefs_remove(tr->dir); | |
| 8500 | 8500 | goto out_free_tr; |
| 8501 | 8501 | } |
| 8502 | 8502 | |
| @@ -8605,7 +8605,7 @@ static int __remove_instance(struct trace_array *tr) · 102 unmodified lines | ||
| 8605 | 8605 | event_trace_del_tracer(tr); |
| 8606 | 8606 | ftrace_clear_pids(tr); |
| 8607 | 8607 | ftrace_destroy_function_files(tr); |
| 8608 | − tracefs_remove_recursive(tr->dir); | |
| 8608 | + tracefs_remove(tr->dir); | |
| 8609 | 8609 | free_trace_buffers(tr); |
| 8610 | 8610 | |
| 8611 | 8611 | for (i = 0; i < tr->nr_topts; i++) { |
| @@ -697,7 +697,7 @@ static void remove_subsystem(struct trace_subsystem_dir *dir) · 696 unmodified lines | ||
| 697 | 697 | return; |
| 698 | 698 | |
| 699 | 699 | if (!--dir->nr_events) { |
| 700 | − tracefs_remove_recursive(dir->entry); | |
| 700 | + tracefs_remove(dir->entry); | |
| 701 | 701 | list_del(&dir->list); |
| 702 | 702 | __put_system_dir(dir); |
| 703 | 703 | } |
| @@ -716,7 +716,7 @@ static void remove_event_file_dir(struct trace_event_file *file) · 12 unmodified lines | ||
| 716 | 716 | } |
| 717 | 717 | spin_unlock(&dir->d_lock); |
| 718 | 718 | |
| 719 | − tracefs_remove_recursive(dir); | |
| 719 | + tracefs_remove(dir); | |
| 720 | 720 | } |
| 721 | 721 | |
| 722 | 722 | list_del(&file->list); |
| @@ -3064,7 +3064,7 @@ int event_trace_del_tracer(struct trace_array *tr) · 2341 unmodified lines | ||
| 3064 | 3064 | |
| 3065 | 3065 | down_write(&trace_event_sem); |
| 3066 | 3066 | __trace_remove_event_dirs(tr); |
| 3067 | − tracefs_remove_recursive(tr->event_dir); | |
| 3067 | + tracefs_remove(tr->event_dir); | |
| 3068 | 3068 | up_write(&trace_event_sem); |
| 3069 | 3069 | |
| 3070 | 3070 | tr->event_dir = NULL; |
| @@ -556,7 +556,7 @@ static int init_tracefs(void) · 555 unmodified lines | ||
| 556 | 556 | return 0; |
| 557 | 557 | |
| 558 | 558 | err: |
| 559 | − tracefs_remove_recursive(top_dir); | |
| 559 | + tracefs_remove(top_dir); | |
| 560 | 560 | return -ENOMEM; |
| 561 | 561 | } |
| 562 | 562 | |