Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

Merge branch 'work.recursive_removal' of git://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs

Pull vfs recursive removal updates from Al Viro:
"We have quite a few places where synthetic filesystems do an
equivalent of 'rm -rf', with varying amounts of code duplication,
wrong locking, etc. That really ought to be a library helper.

Only debugfs (and very similar tracefs) are converted here - I have
more conversions, but they'd never been in -next, so they'll have to
wait"

* 'work.recursive_removal' of git://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs:
simple_recursive_removal(): kernel-side rm -rf for ramfs-style filesystems

+104 -218
+14 -107
fs/debugfs/inode.c
··· 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", ··· 684 681 wait_for_completion(&fsd->active_users_drained); 685 682 } 686 683 687 - static int __debugfs_remove(struct dentry *dentry, struct dentry *parent) 684 + static void remove_one(struct dentry *victim) 688 685 { 689 - int ret = 0; 690 - 691 - if (simple_positive(dentry)) { 692 - dget(dentry); 693 - if (d_is_dir(dentry)) { 694 - ret = simple_rmdir(d_inode(parent), dentry); 695 - if (!ret) 696 - fsnotify_rmdir(d_inode(parent), dentry); 697 - } else { 698 - simple_unlink(d_inode(parent), dentry); 699 - fsnotify_unlink(d_inode(parent), dentry); 700 - } 701 - if (!ret) 702 - d_delete(dentry); 703 - if (d_is_reg(dentry)) 704 - __debugfs_file_removed(dentry); 705 - dput(dentry); 706 - } 707 - return ret; 686 + if (d_is_reg(victim)) 687 + __debugfs_file_removed(victim); 688 + simple_release_fs(&debugfs_mount, &debugfs_mount_count); 708 689 } 709 690 710 691 /** 711 - * debugfs_remove - removes a file or directory from the debugfs filesystem 712 - * @dentry: a pointer to a the dentry of the file or directory to be 713 - * removed. If this parameter is NULL or an error value, nothing 714 - * will be done. 715 - * 716 - * This function removes a file or directory in debugfs that was previously 717 - * created with a call to another debugfs function (like 718 - * debugfs_create_file() or variants thereof.) 719 - * 720 - * This function is required to be called in order for the file to be 721 - * removed, no automatic cleanup of files will happen when a module is 722 - * removed, you are responsible here. 723 - */ 724 - void debugfs_remove(struct dentry *dentry) 725 - { 726 - struct dentry *parent; 727 - int ret; 728 - 729 - if (IS_ERR_OR_NULL(dentry)) 730 - return; 731 - 732 - parent = dentry->d_parent; 733 - inode_lock(d_inode(parent)); 734 - ret = __debugfs_remove(dentry, parent); 735 - inode_unlock(d_inode(parent)); 736 - if (!ret) 737 - simple_release_fs(&debugfs_mount, &debugfs_mount_count); 738 - } 739 - EXPORT_SYMBOL_GPL(debugfs_remove); 740 - 741 - /** 742 - * debugfs_remove_recursive - recursively removes a directory 692 + * debugfs_remove - recursively removes a directory 743 693 * @dentry: a pointer to a the dentry of the directory to be removed. If this 744 694 * parameter is NULL or an error value, nothing will be done. 745 695 * ··· 704 748 * removed, no automatic cleanup of files will happen when a module is 705 749 * removed, you are responsible here. 706 750 */ 707 - void debugfs_remove_recursive(struct dentry *dentry) 751 + void debugfs_remove(struct dentry *dentry) 708 752 { 709 - struct dentry *child, *parent; 710 - 711 753 if (IS_ERR_OR_NULL(dentry)) 712 754 return; 713 755 714 - parent = dentry; 715 - down: 716 - inode_lock(d_inode(parent)); 717 - loop: 718 - /* 719 - * The parent->d_subdirs is protected by the d_lock. Outside that 720 - * lock, the child can be unlinked and set to be freed which can 721 - * use the d_u.d_child as the rcu head and corrupt this list. 722 - */ 723 - spin_lock(&parent->d_lock); 724 - list_for_each_entry(child, &parent->d_subdirs, d_child) { 725 - if (!simple_positive(child)) 726 - continue; 727 - 728 - /* perhaps simple_empty(child) makes more sense */ 729 - if (!list_empty(&child->d_subdirs)) { 730 - spin_unlock(&parent->d_lock); 731 - inode_unlock(d_inode(parent)); 732 - parent = child; 733 - goto down; 734 - } 735 - 736 - spin_unlock(&parent->d_lock); 737 - 738 - if (!__debugfs_remove(child, parent)) 739 - simple_release_fs(&debugfs_mount, &debugfs_mount_count); 740 - 741 - /* 742 - * The parent->d_lock protects agaist child from unlinking 743 - * from d_subdirs. When releasing the parent->d_lock we can 744 - * no longer trust that the next pointer is valid. 745 - * Restart the loop. We'll skip this one with the 746 - * simple_positive() check. 747 - */ 748 - goto loop; 749 - } 750 - spin_unlock(&parent->d_lock); 751 - 752 - inode_unlock(d_inode(parent)); 753 - child = parent; 754 - parent = parent->d_parent; 755 - inode_lock(d_inode(parent)); 756 - 757 - if (child != dentry) 758 - /* go up */ 759 - goto loop; 760 - 761 - if (!__debugfs_remove(child, parent)) 762 - simple_release_fs(&debugfs_mount, &debugfs_mount_count); 763 - inode_unlock(d_inode(parent)); 756 + simple_pin_fs(&debug_fs_type, &debugfs_mount, &debugfs_mount_count); 757 + simple_recursive_removal(dentry, remove_one); 758 + simple_release_fs(&debugfs_mount, &debugfs_mount_count); 764 759 } 765 - EXPORT_SYMBOL_GPL(debugfs_remove_recursive); 760 + EXPORT_SYMBOL_GPL(debugfs_remove); 766 761 767 762 /** 768 763 * debugfs_rename - rename a file/directory in the debugfs filesystem
+70
fs/libfs.c
··· 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 238 .lookup = simple_lookup, 240 239 }; 241 240 EXPORT_SYMBOL(simple_dir_inode_operations); 241 + 242 + static struct dentry *find_next_child(struct dentry *parent, struct dentry *prev) 243 + { 244 + struct dentry *child = NULL; 245 + struct list_head *p = prev ? &prev->d_child : &parent->d_subdirs; 246 + 247 + spin_lock(&parent->d_lock); 248 + while ((p = p->next) != &parent->d_subdirs) { 249 + struct dentry *d = container_of(p, struct dentry, d_child); 250 + if (simple_positive(d)) { 251 + spin_lock_nested(&d->d_lock, DENTRY_D_LOCK_NESTED); 252 + if (simple_positive(d)) 253 + child = dget_dlock(d); 254 + spin_unlock(&d->d_lock); 255 + if (likely(child)) 256 + break; 257 + } 258 + } 259 + spin_unlock(&parent->d_lock); 260 + dput(prev); 261 + return child; 262 + } 263 + 264 + void simple_recursive_removal(struct dentry *dentry, 265 + void (*callback)(struct dentry *)) 266 + { 267 + struct dentry *this = dget(dentry); 268 + while (true) { 269 + struct dentry *victim = NULL, *child; 270 + struct inode *inode = this->d_inode; 271 + 272 + inode_lock(inode); 273 + if (d_is_dir(this)) 274 + inode->i_flags |= S_DEAD; 275 + while ((child = find_next_child(this, victim)) == NULL) { 276 + // kill and ascend 277 + // update metadata while it's still locked 278 + inode->i_ctime = current_time(inode); 279 + clear_nlink(inode); 280 + inode_unlock(inode); 281 + victim = this; 282 + this = this->d_parent; 283 + inode = this->d_inode; 284 + inode_lock(inode); 285 + if (simple_positive(victim)) { 286 + d_invalidate(victim); // avoid lost mounts 287 + if (d_is_dir(victim)) 288 + fsnotify_rmdir(inode, victim); 289 + else 290 + fsnotify_unlink(inode, victim); 291 + if (callback) 292 + callback(victim); 293 + dput(victim); // unpin it 294 + } 295 + if (victim == dentry) { 296 + inode->i_ctime = inode->i_mtime = 297 + current_time(inode); 298 + if (d_is_dir(dentry)) 299 + drop_nlink(inode); 300 + inode_unlock(inode); 301 + dput(dentry); 302 + return; 303 + } 304 + } 305 + inode_unlock(inode); 306 + this = child; 307 + } 308 + } 309 + EXPORT_SYMBOL(simple_recursive_removal); 242 310 243 311 static const struct super_operations simple_super_operations = { 244 312 .statfs = simple_statfs,
+11 -103
fs/tracefs/inode.c
··· 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); ··· 502 499 return dentry; 503 500 } 504 501 505 - static int __tracefs_remove(struct dentry *dentry, struct dentry *parent) 502 + static void remove_one(struct dentry *victim) 506 503 { 507 - int ret = 0; 508 - 509 - if (simple_positive(dentry)) { 510 - if (dentry->d_inode) { 511 - dget(dentry); 512 - switch (dentry->d_inode->i_mode & S_IFMT) { 513 - case S_IFDIR: 514 - ret = simple_rmdir(parent->d_inode, dentry); 515 - if (!ret) 516 - fsnotify_rmdir(parent->d_inode, dentry); 517 - break; 518 - default: 519 - simple_unlink(parent->d_inode, dentry); 520 - fsnotify_unlink(parent->d_inode, dentry); 521 - break; 522 - } 523 - if (!ret) 524 - d_delete(dentry); 525 - dput(dentry); 526 - } 527 - } 528 - return ret; 504 + simple_release_fs(&tracefs_mount, &tracefs_mount_count); 529 505 } 530 506 531 507 /** 532 - * tracefs_remove - removes a file or directory from the tracefs filesystem 533 - * @dentry: a pointer to a the dentry of the file or directory to be 534 - * removed. 535 - * 536 - * This function removes a file or directory in tracefs that was previously 537 - * created with a call to another tracefs function (like 538 - * tracefs_create_file() or variants thereof.) 539 - */ 540 - void tracefs_remove(struct dentry *dentry) 541 - { 542 - struct dentry *parent; 543 - int ret; 544 - 545 - if (IS_ERR_OR_NULL(dentry)) 546 - return; 547 - 548 - parent = dentry->d_parent; 549 - inode_lock(parent->d_inode); 550 - ret = __tracefs_remove(dentry, parent); 551 - inode_unlock(parent->d_inode); 552 - if (!ret) 553 - simple_release_fs(&tracefs_mount, &tracefs_mount_count); 554 - } 555 - 556 - /** 557 - * tracefs_remove_recursive - recursively removes a directory 508 + * tracefs_remove - recursively removes a directory 558 509 * @dentry: a pointer to a the dentry of the directory to be removed. 559 510 * 560 511 * This function recursively removes a directory tree in tracefs that 561 512 * was previously created with a call to another tracefs function 562 513 * (like tracefs_create_file() or variants thereof.) 563 514 */ 564 - void tracefs_remove_recursive(struct dentry *dentry) 515 + void tracefs_remove(struct dentry *dentry) 565 516 { 566 - struct dentry *child, *parent; 567 - 568 517 if (IS_ERR_OR_NULL(dentry)) 569 518 return; 570 519 571 - parent = dentry; 572 - down: 573 - inode_lock(parent->d_inode); 574 - loop: 575 - /* 576 - * The parent->d_subdirs is protected by the d_lock. Outside that 577 - * lock, the child can be unlinked and set to be freed which can 578 - * use the d_u.d_child as the rcu head and corrupt this list. 579 - */ 580 - spin_lock(&parent->d_lock); 581 - list_for_each_entry(child, &parent->d_subdirs, d_child) { 582 - if (!simple_positive(child)) 583 - continue; 584 - 585 - /* perhaps simple_empty(child) makes more sense */ 586 - if (!list_empty(&child->d_subdirs)) { 587 - spin_unlock(&parent->d_lock); 588 - inode_unlock(parent->d_inode); 589 - parent = child; 590 - goto down; 591 - } 592 - 593 - spin_unlock(&parent->d_lock); 594 - 595 - if (!__tracefs_remove(child, parent)) 596 - simple_release_fs(&tracefs_mount, &tracefs_mount_count); 597 - 598 - /* 599 - * The parent->d_lock protects agaist child from unlinking 600 - * from d_subdirs. When releasing the parent->d_lock we can 601 - * no longer trust that the next pointer is valid. 602 - * Restart the loop. We'll skip this one with the 603 - * simple_positive() check. 604 - */ 605 - goto loop; 606 - } 607 - spin_unlock(&parent->d_lock); 608 - 609 - inode_unlock(parent->d_inode); 610 - child = parent; 611 - parent = parent->d_parent; 612 - inode_lock(parent->d_inode); 613 - 614 - if (child != dentry) 615 - /* go up */ 616 - goto loop; 617 - 618 - if (!__tracefs_remove(child, parent)) 619 - simple_release_fs(&tracefs_mount, &tracefs_mount_count); 620 - inode_unlock(parent->d_inode); 520 + simple_pin_fs(&trace_fs_type, &tracefs_mount, &tracefs_mount_count); 521 + simple_recursive_removal(dentry, remove_one); 522 + simple_release_fs(&tracefs_mount, &tracefs_mount_count); 621 523 } 622 524 623 525 /**
+1 -1
include/linux/debugfs.h
··· 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
+2
include/linux/fs.h
··· 3318 3318 extern int simple_rmdir(struct inode *, struct dentry *); 3319 3319 extern int simple_rename(struct inode *, struct dentry *, 3320 3320 struct inode *, struct dentry *, unsigned int); 3321 + extern void simple_recursive_removal(struct dentry *, 3322 + void (*callback)(struct dentry *)); 3321 3323 extern int noop_fsync(struct file *, loff_t, loff_t, int); 3322 3324 extern int noop_set_page_dirty(struct page *page); 3323 3325 extern void noop_invalidatepage(struct page *page, unsigned int offset,
-1
include/linux/tracefs.h
··· 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),
+2 -2
kernel/trace/trace.c
··· 8504 8504 8505 8505 ret = event_trace_add_tracer(tr->dir, tr); 8506 8506 if (ret) { 8507 - tracefs_remove_recursive(tr->dir); 8507 + tracefs_remove(tr->dir); 8508 8508 goto out_free_tr; 8509 8509 } 8510 8510 ··· 8613 8613 event_trace_del_tracer(tr); 8614 8614 ftrace_clear_pids(tr); 8615 8615 ftrace_destroy_function_files(tr); 8616 - tracefs_remove_recursive(tr->dir); 8616 + tracefs_remove(tr->dir); 8617 8617 free_trace_buffers(tr); 8618 8618 8619 8619 for (i = 0; i < tr->nr_topts; i++) {
+3 -3
kernel/trace/trace_events.c
··· 698 698 return; 699 699 700 700 if (!--dir->nr_events) { 701 - tracefs_remove_recursive(dir->entry); 701 + tracefs_remove(dir->entry); 702 702 list_del(&dir->list); 703 703 __put_system_dir(dir); 704 704 } ··· 717 717 } 718 718 spin_unlock(&dir->d_lock); 719 719 720 - tracefs_remove_recursive(dir); 720 + tracefs_remove(dir); 721 721 } 722 722 723 723 list_del(&file->list); ··· 3082 3082 3083 3083 down_write(&trace_event_sem); 3084 3084 __trace_remove_event_dirs(tr); 3085 - tracefs_remove_recursive(tr->event_dir); 3085 + tracefs_remove(tr->event_dir); 3086 3086 up_write(&trace_event_sem); 3087 3087 3088 3088 tr->event_dir = NULL;
+1 -1
kernel/trace/trace_hwlat.c
··· 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