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.

Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
This commit is contained in:
Al Viro
2019-11-18 09:43:10 -05:00
parent e42617b825
commit a3d1e7eb5a
9 changed files with 104 additions and 218 deletions

View File

@@ -330,7 +330,10 @@ static struct dentry *start_creating(const char *name, struct dentry *parent)
parent = tracefs_mount->mnt_root;
inode_lock(parent->d_inode);
dentry = lookup_one_len(name, parent, strlen(name));
if (unlikely(IS_DEADDIR(parent->d_inode)))
dentry = ERR_PTR(-ENOENT);
else
dentry = lookup_one_len(name, parent, strlen(name));
if (!IS_ERR(dentry) && dentry->d_inode) {
dput(dentry);
dentry = ERR_PTR(-EEXIST);
@@ -499,122 +502,27 @@ __init struct dentry *tracefs_create_instance_dir(const char *name,
return dentry;
}
static int __tracefs_remove(struct dentry *dentry, struct dentry *parent)
static void remove_one(struct dentry *victim)
{
int ret = 0;
if (simple_positive(dentry)) {
if (dentry->d_inode) {
dget(dentry);
switch (dentry->d_inode->i_mode & S_IFMT) {
case S_IFDIR:
ret = simple_rmdir(parent->d_inode, dentry);
if (!ret)
fsnotify_rmdir(parent->d_inode, dentry);
break;
default:
simple_unlink(parent->d_inode, dentry);
fsnotify_unlink(parent->d_inode, dentry);
break;
}
if (!ret)
d_delete(dentry);
dput(dentry);
}
}
return ret;
simple_release_fs(&tracefs_mount, &tracefs_mount_count);
}
/**
* tracefs_remove - removes a file or directory from the tracefs filesystem
* @dentry: a pointer to a the dentry of the file or directory to be
* removed.
*
* This function removes a file or directory in tracefs that was previously
* created with a call to another tracefs function (like
* tracefs_create_file() or variants thereof.)
*/
void tracefs_remove(struct dentry *dentry)
{
struct dentry *parent;
int ret;
if (IS_ERR_OR_NULL(dentry))
return;
parent = dentry->d_parent;
inode_lock(parent->d_inode);
ret = __tracefs_remove(dentry, parent);
inode_unlock(parent->d_inode);
if (!ret)
simple_release_fs(&tracefs_mount, &tracefs_mount_count);
}
/**
* tracefs_remove_recursive - recursively removes a directory
* tracefs_remove - recursively removes a directory
* @dentry: a pointer to a the dentry of the directory to be removed.
*
* This function recursively removes a directory tree in tracefs that
* was previously created with a call to another tracefs function
* (like tracefs_create_file() or variants thereof.)
*/
void tracefs_remove_recursive(struct dentry *dentry)
void tracefs_remove(struct dentry *dentry)
{
struct dentry *child, *parent;
if (IS_ERR_OR_NULL(dentry))
return;
parent = dentry;
down:
inode_lock(parent->d_inode);
loop:
/*
* The parent->d_subdirs is protected by the d_lock. Outside that
* lock, the child can be unlinked and set to be freed which can
* use the d_u.d_child as the rcu head and corrupt this list.
*/
spin_lock(&parent->d_lock);
list_for_each_entry(child, &parent->d_subdirs, d_child) {
if (!simple_positive(child))
continue;
/* perhaps simple_empty(child) makes more sense */
if (!list_empty(&child->d_subdirs)) {
spin_unlock(&parent->d_lock);
inode_unlock(parent->d_inode);
parent = child;
goto down;
}
spin_unlock(&parent->d_lock);
if (!__tracefs_remove(child, parent))
simple_release_fs(&tracefs_mount, &tracefs_mount_count);
/*
* The parent->d_lock protects agaist child from unlinking
* from d_subdirs. When releasing the parent->d_lock we can
* no longer trust that the next pointer is valid.
* Restart the loop. We'll skip this one with the
* simple_positive() check.
*/
goto loop;
}
spin_unlock(&parent->d_lock);
inode_unlock(parent->d_inode);
child = parent;
parent = parent->d_parent;
inode_lock(parent->d_inode);
if (child != dentry)
/* go up */
goto loop;
if (!__tracefs_remove(child, parent))
simple_release_fs(&tracefs_mount, &tracefs_mount_count);
inode_unlock(parent->d_inode);
simple_pin_fs(&trace_fs_type, &tracefs_mount, &tracefs_mount_count);
simple_recursive_removal(dentry, remove_one);
simple_release_fs(&tracefs_mount, &tracefs_mount_count);
}
/**