Skip to content
This repository was archived by the owner on Jan 25, 2023. It is now read-only.

Commit 06cef01

Browse files
Al Virovramyanaidu
authored andcommitted
simple_recursive_removal(): kernel-side rm -rf for ramfs-style filesystems
two requirements: no file creations in IS_DEADDIR and no cross-directory renames whatsoever. Change-Id: Idde09a008d06063c76d20310dce27a726685f3db Tracked-On: PKT-3186 Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
1 parent 7e82879 commit 06cef01

9 files changed

Lines changed: 100 additions & 212 deletions

File tree

fs/debugfs/inode.c

Lines changed: 14 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,10 @@ static struct dentry *start_creating(const char *name, struct dentry *parent)
310310
parent = debugfs_mount->mnt_root;
311311

312312
inode_lock(d_inode(parent));
313-
dentry = lookup_one_len(name, parent, strlen(name));
313+
if (unlikely(IS_DEADDIR(d_inode(parent))))
314+
dentry = ERR_PTR(-ENOENT);
315+
else
316+
dentry = lookup_one_len(name, parent, strlen(name));
314317
if (!IS_ERR(dentry) && d_really_is_positive(dentry)) {
315318
dput(dentry);
316319
dentry = ERR_PTR(-EEXIST);
@@ -638,59 +641,15 @@ static void __debugfs_file_removed(struct dentry *dentry)
638641
wait_for_completion(&fsd->active_users_drained);
639642
}
640643

641-
static int __debugfs_remove(struct dentry *dentry, struct dentry *parent)
642-
{
643-
int ret = 0;
644-
645-
if (simple_positive(dentry)) {
646-
dget(dentry);
647-
if (d_is_dir(dentry)) {
648-
ret = simple_rmdir(d_inode(parent), dentry);
649-
} else {
650-
simple_unlink(d_inode(parent), dentry);
651-
}
652-
if (!ret)
653-
d_delete(dentry);
654-
if (d_is_reg(dentry))
655-
__debugfs_file_removed(dentry);
656-
dput(dentry);
657-
}
658-
return ret;
659-
}
660-
661-
/**
662-
* debugfs_remove - removes a file or directory from the debugfs filesystem
663-
* @dentry: a pointer to a the dentry of the file or directory to be
664-
* removed. If this parameter is NULL or an error value, nothing
665-
* will be done.
666-
*
667-
* This function removes a file or directory in debugfs that was previously
668-
* created with a call to another debugfs function (like
669-
* debugfs_create_file() or variants thereof.)
670-
*
671-
* This function is required to be called in order for the file to be
672-
* removed, no automatic cleanup of files will happen when a module is
673-
* removed, you are responsible here.
674-
*/
675-
void debugfs_remove(struct dentry *dentry)
644+
static void remove_one(struct dentry *victim)
676645
{
677-
struct dentry *parent;
678-
int ret;
679-
680-
if (IS_ERR_OR_NULL(dentry))
681-
return;
682-
683-
parent = dentry->d_parent;
684-
inode_lock(d_inode(parent));
685-
ret = __debugfs_remove(dentry, parent);
686-
inode_unlock(d_inode(parent));
687-
if (!ret)
688-
simple_release_fs(&debugfs_mount, &debugfs_mount_count);
646+
if (d_is_reg(victim))
647+
__debugfs_file_removed(victim);
648+
simple_release_fs(&debugfs_mount, &debugfs_mount_count);
689649
}
690-
EXPORT_SYMBOL_GPL(debugfs_remove);
691650

692651
/**
693-
* debugfs_remove_recursive - recursively removes a directory
652+
* debugfs_remove - recursively removes a directory
694653
* @dentry: a pointer to a the dentry of the directory to be removed. If this
695654
* parameter is NULL or an error value, nothing will be done.
696655
*
@@ -702,65 +661,16 @@ EXPORT_SYMBOL_GPL(debugfs_remove);
702661
* removed, no automatic cleanup of files will happen when a module is
703662
* removed, you are responsible here.
704663
*/
705-
void debugfs_remove_recursive(struct dentry *dentry)
664+
void debugfs_remove(struct dentry *dentry)
706665
{
707-
struct dentry *child, *parent;
708-
709666
if (IS_ERR_OR_NULL(dentry))
710667
return;
711668

712-
parent = dentry;
713-
down:
714-
inode_lock(d_inode(parent));
715-
loop:
716-
/*
717-
* The parent->d_subdirs is protected by the d_lock. Outside that
718-
* lock, the child can be unlinked and set to be freed which can
719-
* use the d_u.d_child as the rcu head and corrupt this list.
720-
*/
721-
spin_lock(&parent->d_lock);
722-
list_for_each_entry(child, &parent->d_subdirs, d_child) {
723-
if (!simple_positive(child))
724-
continue;
725-
726-
/* perhaps simple_empty(child) makes more sense */
727-
if (!list_empty(&child->d_subdirs)) {
728-
spin_unlock(&parent->d_lock);
729-
inode_unlock(d_inode(parent));
730-
parent = child;
731-
goto down;
732-
}
733-
734-
spin_unlock(&parent->d_lock);
735-
736-
if (!__debugfs_remove(child, parent))
737-
simple_release_fs(&debugfs_mount, &debugfs_mount_count);
738-
739-
/*
740-
* The parent->d_lock protects agaist child from unlinking
741-
* from d_subdirs. When releasing the parent->d_lock we can
742-
* no longer trust that the next pointer is valid.
743-
* Restart the loop. We'll skip this one with the
744-
* simple_positive() check.
745-
*/
746-
goto loop;
747-
}
748-
spin_unlock(&parent->d_lock);
749-
750-
inode_unlock(d_inode(parent));
751-
child = parent;
752-
parent = parent->d_parent;
753-
inode_lock(d_inode(parent));
754-
755-
if (child != dentry)
756-
/* go up */
757-
goto loop;
758-
759-
if (!__debugfs_remove(child, parent))
760-
simple_release_fs(&debugfs_mount, &debugfs_mount_count);
761-
inode_unlock(d_inode(parent));
669+
simple_pin_fs(&debug_fs_type, &debugfs_mount, &debugfs_mount_count);
670+
simple_recursive_removal(dentry, remove_one);
671+
simple_release_fs(&debugfs_mount, &debugfs_mount_count);
762672
}
763-
EXPORT_SYMBOL_GPL(debugfs_remove_recursive);
673+
EXPORT_SYMBOL_GPL(debugfs_remove);
764674

765675
/**
766676
* debugfs_rename - rename a file/directory in the debugfs filesystem

fs/libfs.c

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include <linux/exportfs.h>
1717
#include <linux/writeback.h>
1818
#include <linux/buffer_head.h> /* sync_mapping_buffers */
19+
#include <linux/fsnotify.h>
1920

2021
#include <linux/uaccess.h>
2122

@@ -233,6 +234,71 @@ const struct inode_operations simple_dir_inode_operations = {
233234
};
234235
EXPORT_SYMBOL(simple_dir_inode_operations);
235236

237+
static struct dentry *find_next_child(struct dentry *parent, struct dentry *prev)
238+
{
239+
struct dentry *child = NULL;
240+
struct list_head *p = prev ? &prev->d_child : &parent->d_subdirs;
241+
242+
spin_lock(&parent->d_lock);
243+
while ((p = p->next) != &parent->d_subdirs) {
244+
struct dentry *d = container_of(p, struct dentry, d_child);
245+
if (simple_positive(d)) {
246+
spin_lock_nested(&d->d_lock, DENTRY_D_LOCK_NESTED);
247+
if (simple_positive(d))
248+
child = dget_dlock(d);
249+
spin_unlock(&d->d_lock);
250+
if (likely(child))
251+
break;
252+
}
253+
}
254+
spin_unlock(&parent->d_lock);
255+
dput(prev);
256+
return child;
257+
}
258+
259+
void simple_recursive_removal(struct dentry *dentry,
260+
void (*callback)(struct dentry *))
261+
{
262+
struct dentry *this = dget(dentry);
263+
while (true) {
264+
struct dentry *victim = NULL, *child;
265+
struct inode *inode = this->d_inode;
266+
267+
inode_lock(inode);
268+
if (d_is_dir(this))
269+
inode->i_flags |= S_DEAD;
270+
while ((child = find_next_child(this, victim)) == NULL) {
271+
// kill and ascend
272+
// update metadata while it's still locked
273+
inode->i_ctime = current_time(inode);
274+
clear_nlink(inode);
275+
inode_unlock(inode);
276+
victim = this;
277+
this = this->d_parent;
278+
inode = this->d_inode;
279+
inode_lock(inode);
280+
if (simple_positive(victim)) {
281+
d_invalidate(victim); // avoid lost mounts
282+
if (callback)
283+
callback(victim);
284+
dput(victim); // unpin it
285+
}
286+
if (victim == dentry) {
287+
inode->i_ctime = inode->i_mtime =
288+
current_time(inode);
289+
if (d_is_dir(dentry))
290+
drop_nlink(inode);
291+
inode_unlock(inode);
292+
dput(dentry);
293+
return;
294+
}
295+
}
296+
inode_unlock(inode);
297+
this = child;
298+
}
299+
}
300+
EXPORT_SYMBOL(simple_recursive_removal);
301+
236302
static const struct super_operations simple_super_operations = {
237303
.statfs = simple_statfs,
238304
};

fs/tracefs/inode.c

Lines changed: 11 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -333,7 +333,10 @@ static struct dentry *start_creating(const char *name, struct dentry *parent)
333333
parent = tracefs_mount->mnt_root;
334334

335335
inode_lock(parent->d_inode);
336-
dentry = lookup_one_len(name, parent, strlen(name));
336+
if (unlikely(IS_DEADDIR(parent->d_inode)))
337+
dentry = ERR_PTR(-ENOENT);
338+
else
339+
dentry = lookup_one_len(name, parent, strlen(name));
337340
if (!IS_ERR(dentry) && dentry->d_inode) {
338341
dput(dentry);
339342
dentry = ERR_PTR(-EEXIST);
@@ -499,119 +502,27 @@ __init struct dentry *tracefs_create_instance_dir(const char *name,
499502
return dentry;
500503
}
501504

502-
static int __tracefs_remove(struct dentry *dentry, struct dentry *parent)
505+
static void remove_one(struct dentry *victim)
503506
{
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-
break;
513-
default:
514-
simple_unlink(parent->d_inode, dentry);
515-
break;
516-
}
517-
if (!ret)
518-
d_delete(dentry);
519-
dput(dentry);
520-
}
521-
}
522-
return ret;
523-
}
524-
525-
/**
526-
* tracefs_remove - removes a file or directory from the tracefs filesystem
527-
* @dentry: a pointer to a the dentry of the file or directory to be
528-
* removed.
529-
*
530-
* This function removes a file or directory in tracefs that was previously
531-
* created with a call to another tracefs function (like
532-
* tracefs_create_file() or variants thereof.)
533-
*/
534-
void tracefs_remove(struct dentry *dentry)
535-
{
536-
struct dentry *parent;
537-
int ret;
538-
539-
if (IS_ERR_OR_NULL(dentry))
540-
return;
541-
542-
parent = dentry->d_parent;
543-
inode_lock(parent->d_inode);
544-
ret = __tracefs_remove(dentry, parent);
545-
inode_unlock(parent->d_inode);
546-
if (!ret)
547-
simple_release_fs(&tracefs_mount, &tracefs_mount_count);
507+
simple_release_fs(&tracefs_mount, &tracefs_mount_count);
548508
}
549509

550510
/**
551-
* tracefs_remove_recursive - recursively removes a directory
511+
* tracefs_remove - recursively removes a directory
552512
* @dentry: a pointer to a the dentry of the directory to be removed.
553513
*
554514
* This function recursively removes a directory tree in tracefs that
555515
* was previously created with a call to another tracefs function
556516
* (like tracefs_create_file() or variants thereof.)
557517
*/
558-
void tracefs_remove_recursive(struct dentry *dentry)
518+
void tracefs_remove(struct dentry *dentry)
559519
{
560-
struct dentry *child, *parent;
561-
562520
if (IS_ERR_OR_NULL(dentry))
563521
return;
564522

565-
parent = dentry;
566-
down:
567-
inode_lock(parent->d_inode);
568-
loop:
569-
/*
570-
* The parent->d_subdirs is protected by the d_lock. Outside that
571-
* lock, the child can be unlinked and set to be freed which can
572-
* use the d_u.d_child as the rcu head and corrupt this list.
573-
*/
574-
spin_lock(&parent->d_lock);
575-
list_for_each_entry(child, &parent->d_subdirs, d_child) {
576-
if (!simple_positive(child))
577-
continue;
578-
579-
/* perhaps simple_empty(child) makes more sense */
580-
if (!list_empty(&child->d_subdirs)) {
581-
spin_unlock(&parent->d_lock);
582-
inode_unlock(parent->d_inode);
583-
parent = child;
584-
goto down;
585-
}
586-
587-
spin_unlock(&parent->d_lock);
588-
589-
if (!__tracefs_remove(child, parent))
590-
simple_release_fs(&tracefs_mount, &tracefs_mount_count);
591-
592-
/*
593-
* The parent->d_lock protects agaist child from unlinking
594-
* from d_subdirs. When releasing the parent->d_lock we can
595-
* no longer trust that the next pointer is valid.
596-
* Restart the loop. We'll skip this one with the
597-
* simple_positive() check.
598-
*/
599-
goto loop;
600-
}
601-
spin_unlock(&parent->d_lock);
602-
603-
inode_unlock(parent->d_inode);
604-
child = parent;
605-
parent = parent->d_parent;
606-
inode_lock(parent->d_inode);
607-
608-
if (child != dentry)
609-
/* go up */
610-
goto loop;
611-
612-
if (!__tracefs_remove(child, parent))
613-
simple_release_fs(&tracefs_mount, &tracefs_mount_count);
614-
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);
615526
}
616527

617528
/**

include/linux/debugfs.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ struct dentry *debugfs_create_automount(const char *name,
8282
void *data);
8383

8484
void debugfs_remove(struct dentry *dentry);
85-
void debugfs_remove_recursive(struct dentry *dentry);
85+
#define debugfs_remove_recursive debugfs_remove
8686

8787
const struct file_operations *debugfs_real_fops(const struct file *filp);
8888

include/linux/fs.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3206,6 +3206,8 @@ extern int simple_unlink(struct inode *, struct dentry *);
32063206
extern int simple_rmdir(struct inode *, struct dentry *);
32073207
extern int simple_rename(struct inode *, struct dentry *,
32083208
struct inode *, struct dentry *, unsigned int);
3209+
extern void simple_recursive_removal(struct dentry *,
3210+
void (*callback)(struct dentry *));
32093211
extern int noop_fsync(struct file *, loff_t, loff_t, int);
32103212
extern int noop_set_page_dirty(struct page *page);
32113213
extern void noop_invalidatepage(struct page *page, unsigned int offset,

include/linux/tracefs.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ struct dentry *tracefs_create_file(const char *name, umode_t mode,
3232
struct dentry *tracefs_create_dir(const char *name, struct dentry *parent);
3333

3434
void tracefs_remove(struct dentry *dentry);
35-
void tracefs_remove_recursive(struct dentry *dentry);
3635

3736
struct dentry *tracefs_create_instance_dir(const char *name, struct dentry *parent,
3837
int (*mkdir)(const char *name),

0 commit comments

Comments
 (0)