Removing subtrees of kernel filesystems is done in quite a few

places; unfortunately, it's easy to get wrong.  A number of open-coded
 attempts are out there, with varying amount of bogosities.
 
 	simple_recursive_removal() had been introduced for doing that with
 all precautions needed; it does an equivalent of rm -rf, with sufficient
 locking, eviction of anything mounted on top of the subtree, etc.
 
 	This series converts a bunch of open-coded instances to using that.
 -----BEGIN PGP SIGNATURE-----
 
 iHUEABYIAB0WIQQqUNBr3gm4hGXdBJlZ7Krx/gZQ6wUCaIRCUwAKCRBZ7Krx/gZQ
 66XWAP9BNyHcvl9uV/ku/mswYiRBxYoVogciIKeugwYTVLuTJgEA7jdh1eyLkvbS
 rwbL7XD+Q35/vXZHEet+RLCGH3ae6wc=
 =yaKF
 -----END PGP SIGNATURE-----

Merge tag 'pull-simple_recursive_removal' of git://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs

Pull simple_recursive_removal() update from Al Viro:
 "Removing subtrees of kernel filesystems is done in quite a few places;
  unfortunately, it's easy to get wrong. A number of open-coded attempts
  are out there, with varying amount of bogosities.

  simple_recursive_removal() had been introduced for doing that with all
  precautions needed; it does an equivalent of rm -rf, with sufficient
  locking, eviction of anything mounted on top of the subtree, etc.

  This series converts a bunch of open-coded instances to using that"

* tag 'pull-simple_recursive_removal' of git://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs:
  functionfs, gadgetfs: use simple_recursive_removal()
  kill binderfs_remove_file()
  fuse_ctl: use simple_recursive_removal()
  pstore: switch to locked_recursive_removal()
  binfmt_misc: switch to locked_recursive_removal()
  spufs: switch to locked_recursive_removal()
  add locked_recursive_removal()
  better lockdep annotations for simple_recursive_removal()
  simple_recursive_removal(): saner interaction with fsnotify
This commit is contained in:
Linus Torvalds 2025-07-28 09:43:51 -07:00
commit 1959e18cc0
12 changed files with 55 additions and 142 deletions

View File

@ -143,42 +143,13 @@ spufs_evict_inode(struct inode *inode)
put_spu_gang(ei->i_gang);
}
static void spufs_prune_dir(struct dentry *dir)
{
struct dentry *dentry;
struct hlist_node *n;
inode_lock(d_inode(dir));
hlist_for_each_entry_safe(dentry, n, &dir->d_children, d_sib) {
spin_lock(&dentry->d_lock);
if (simple_positive(dentry)) {
dget_dlock(dentry);
__d_drop(dentry);
spin_unlock(&dentry->d_lock);
simple_unlink(d_inode(dir), dentry);
/* XXX: what was dcache_lock protecting here? Other
* filesystems (IB, configfs) release dcache_lock
* before unlink */
dput(dentry);
} else {
spin_unlock(&dentry->d_lock);
}
}
shrink_dcache_parent(dir);
inode_unlock(d_inode(dir));
}
/* Caller must hold parent->i_mutex */
static int spufs_rmdir(struct inode *parent, struct dentry *dir)
static void spufs_rmdir(struct inode *parent, struct dentry *dir)
{
/* remove all entries */
int res;
spufs_prune_dir(dir);
d_drop(dir);
res = simple_rmdir(parent, dir);
/* We have to give up the mm_struct */
spu_forget(SPUFS_I(d_inode(dir))->i_ctx);
return res;
struct spu_context *ctx = SPUFS_I(d_inode(dir))->i_ctx;
locked_recursive_removal(dir, NULL);
spu_forget(ctx);
}
static int spufs_fill_dir(struct dentry *dir,
@ -222,15 +193,13 @@ static int spufs_dir_close(struct inode *inode, struct file *file)
{
struct inode *parent;
struct dentry *dir;
int ret;
dir = file->f_path.dentry;
parent = d_inode(dir->d_parent);
inode_lock_nested(parent, I_MUTEX_PARENT);
ret = spufs_rmdir(parent, dir);
spufs_rmdir(parent, dir);
inode_unlock(parent);
WARN_ON(ret);
unuse_gang(dir->d_parent);
return dcache_dir_close(inode, file);
@ -288,11 +257,11 @@ spufs_mkdir(struct inode *dir, struct dentry *dentry, unsigned int flags,
ret = spufs_fill_dir(dentry, spufs_dir_debug_contents,
mode, ctx);
inode_unlock(inode);
if (ret)
spufs_rmdir(dir, dentry);
inode_unlock(inode);
return ret;
}
@ -475,7 +444,7 @@ spufs_create_context(struct inode *inode, struct dentry *dentry,
ret = spufs_context_open(&path);
if (ret < 0)
WARN_ON(spufs_rmdir(inode, dentry));
spufs_rmdir(inode, dentry);
out_aff_unlock:
if (affinity)

View File

@ -6128,7 +6128,7 @@ static int binder_release(struct inode *nodp, struct file *filp)
debugfs_remove(proc->debugfs_entry);
if (proc->binderfs_entry) {
binderfs_remove_file(proc->binderfs_entry);
simple_recursive_removal(proc->binderfs_entry, NULL);
proc->binderfs_entry = NULL;
}

View File

@ -81,7 +81,6 @@ extern bool is_binderfs_device(const struct inode *inode);
extern struct dentry *binderfs_create_file(struct dentry *dir, const char *name,
const struct file_operations *fops,
void *data);
extern void binderfs_remove_file(struct dentry *dentry);
#else
static inline bool is_binderfs_device(const struct inode *inode)
{
@ -94,7 +93,6 @@ static inline struct dentry *binderfs_create_file(struct dentry *dir,
{
return NULL;
}
static inline void binderfs_remove_file(struct dentry *dentry) {}
#endif
#ifdef CONFIG_ANDROID_BINDERFS

View File

@ -500,21 +500,6 @@ static struct dentry *binderfs_create_dentry(struct dentry *parent,
return dentry;
}
void binderfs_remove_file(struct dentry *dentry)
{
struct inode *parent_inode;
parent_inode = d_inode(dentry->d_parent);
inode_lock(parent_inode);
if (simple_positive(dentry)) {
dget(dentry);
simple_unlink(parent_inode, dentry);
d_delete(dentry);
dput(dentry);
}
inode_unlock(parent_inode);
}
struct dentry *binderfs_create_file(struct dentry *parent, const char *name,
const struct file_operations *fops,
void *data)

View File

@ -2369,8 +2369,7 @@ static void ffs_epfiles_destroy(struct ffs_epfile *epfiles, unsigned count)
for (; count; --count, ++epfile) {
BUG_ON(mutex_is_locked(&epfile->mutex));
if (epfile->dentry) {
d_delete(epfile->dentry);
dput(epfile->dentry);
simple_recursive_removal(epfile->dentry, NULL);
epfile->dentry = NULL;
}
}

View File

@ -1561,7 +1561,6 @@ static void destroy_ep_files (struct dev_data *dev)
spin_lock_irq (&dev->lock);
while (!list_empty(&dev->epfiles)) {
struct ep_data *ep;
struct inode *parent;
struct dentry *dentry;
/* break link to FS */
@ -1571,7 +1570,6 @@ static void destroy_ep_files (struct dev_data *dev)
dentry = ep->dentry;
ep->dentry = NULL;
parent = d_inode(dentry->d_parent);
/* break link to controller */
mutex_lock(&ep->lock);
@ -1586,10 +1584,7 @@ static void destroy_ep_files (struct dev_data *dev)
put_ep (ep);
/* break link to dcache */
inode_lock(parent);
d_delete (dentry);
dput (dentry);
inode_unlock(parent);
simple_recursive_removal(dentry, NULL);
spin_lock_irq (&dev->lock);
}

View File

@ -674,44 +674,6 @@ static void bm_evict_inode(struct inode *inode)
}
}
/**
* unlink_binfmt_dentry - remove the dentry for the binary type handler
* @dentry: dentry associated with the binary type handler
*
* Do the actual filesystem work to remove a dentry for a registered binary
* type handler. Since binfmt_misc only allows simple files to be created
* directly under the root dentry of the filesystem we ensure that we are
* indeed passed a dentry directly beneath the root dentry, that the inode
* associated with the root dentry is locked, and that it is a regular file we
* are asked to remove.
*/
static void unlink_binfmt_dentry(struct dentry *dentry)
{
struct dentry *parent = dentry->d_parent;
struct inode *inode, *parent_inode;
/* All entries are immediate descendants of the root dentry. */
if (WARN_ON_ONCE(dentry->d_sb->s_root != parent))
return;
/* We only expect to be called on regular files. */
inode = d_inode(dentry);
if (WARN_ON_ONCE(!S_ISREG(inode->i_mode)))
return;
/* The parent inode must be locked. */
parent_inode = d_inode(parent);
if (WARN_ON_ONCE(!inode_is_locked(parent_inode)))
return;
if (simple_positive(dentry)) {
dget(dentry);
simple_unlink(parent_inode, dentry);
d_delete(dentry);
dput(dentry);
}
}
/**
* remove_binfmt_handler - remove a binary type handler
* @misc: handle to binfmt_misc instance
@ -729,7 +691,7 @@ static void remove_binfmt_handler(struct binfmt_misc *misc, Node *e)
write_lock(&misc->entries_lock);
list_del_init(&e->list);
write_unlock(&misc->entries_lock);
unlink_binfmt_dentry(e->dentry);
locked_recursive_removal(e->dentry, NULL);
}
/* /<entry> */
@ -772,7 +734,7 @@ static ssize_t bm_entry_write(struct file *file, const char __user *buffer,
case 3:
/* Delete this handler. */
inode = d_inode(inode->i_sb->s_root);
inode_lock(inode);
inode_lock_nested(inode, I_MUTEX_PARENT);
/*
* In order to add new element or remove elements from the list
@ -922,7 +884,7 @@ static ssize_t bm_status_write(struct file *file, const char __user *buffer,
case 3:
/* Delete all handlers. */
inode = d_inode(file_inode(file)->i_sb->s_root);
inode_lock(inode);
inode_lock_nested(inode, I_MUTEX_PARENT);
/*
* In order to add new element or remove elements from the list

View File

@ -11,6 +11,7 @@
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs_context.h>
#include <linux/namei.h>
#define FUSE_CTL_SUPER_MAGIC 0x65735543
@ -212,7 +213,6 @@ static struct dentry *fuse_ctl_add_dentry(struct dentry *parent,
struct dentry *dentry;
struct inode *inode;
BUG_ON(fc->ctl_ndents >= FUSE_CTL_NUM_DENTRIES);
dentry = d_alloc_name(parent, name);
if (!dentry)
return NULL;
@ -236,8 +236,6 @@ static struct dentry *fuse_ctl_add_dentry(struct dentry *parent,
inode->i_private = fc;
d_add(dentry, inode);
fc->ctl_dentry[fc->ctl_ndents++] = dentry;
return dentry;
}
@ -280,27 +278,29 @@ int fuse_ctl_add_conn(struct fuse_conn *fc)
return -ENOMEM;
}
static void remove_one(struct dentry *dentry)
{
d_inode(dentry)->i_private = NULL;
}
/*
* Remove a connection from the control filesystem (if it exists).
* Caller must hold fuse_mutex
*/
void fuse_ctl_remove_conn(struct fuse_conn *fc)
{
int i;
struct dentry *dentry;
char name[32];
if (!fuse_control_sb || fc->no_control)
return;
for (i = fc->ctl_ndents - 1; i >= 0; i--) {
struct dentry *dentry = fc->ctl_dentry[i];
d_inode(dentry)->i_private = NULL;
if (!i) {
/* Get rid of submounts: */
d_invalidate(dentry);
}
dput(dentry);
sprintf(name, "%u", fc->dev);
dentry = lookup_noperm_positive_unlocked(&QSTR(name), fuse_control_sb->s_root);
if (!IS_ERR(dentry)) {
simple_recursive_removal(dentry, remove_one);
dput(dentry); // paired with lookup_noperm_positive_unlocked()
}
drop_nlink(d_inode(fuse_control_sb->s_root));
}
static int fuse_ctl_fill_super(struct super_block *sb, struct fs_context *fsc)
@ -346,12 +346,8 @@ static int fuse_ctl_init_fs_context(struct fs_context *fsc)
static void fuse_ctl_kill_sb(struct super_block *sb)
{
struct fuse_conn *fc;
mutex_lock(&fuse_mutex);
fuse_control_sb = NULL;
list_for_each_entry(fc, &fuse_conn_list, entry)
fc->ctl_ndents = 0;
mutex_unlock(&fuse_mutex);
kill_litter_super(sb);

View File

@ -913,12 +913,6 @@ struct fuse_conn {
/** Device ID from the root super block */
dev_t dev;
/** Dentries in the control filesystem */
struct dentry *ctl_dentry[FUSE_CTL_NUM_DENTRIES];
/** number of dentries used in the above array */
int ctl_ndents;
/** Key for lock owner ID scrambling */
u32 scramble_key[4];

View File

@ -602,15 +602,16 @@ struct dentry *find_next_child(struct dentry *parent, struct dentry *prev)
}
EXPORT_SYMBOL(find_next_child);
void simple_recursive_removal(struct dentry *dentry,
void (*callback)(struct dentry *))
static void __simple_recursive_removal(struct dentry *dentry,
void (*callback)(struct dentry *),
bool locked)
{
struct dentry *this = dget(dentry);
while (true) {
struct dentry *victim = NULL, *child;
struct inode *inode = this->d_inode;
inode_lock(inode);
inode_lock_nested(inode, I_MUTEX_CHILD);
if (d_is_dir(this))
inode->i_flags |= S_DEAD;
while ((child = find_next_child(this, victim)) == NULL) {
@ -622,15 +623,13 @@ void simple_recursive_removal(struct dentry *dentry,
victim = this;
this = this->d_parent;
inode = this->d_inode;
inode_lock(inode);
if (!locked || victim != dentry)
inode_lock_nested(inode, I_MUTEX_CHILD);
if (simple_positive(victim)) {
d_invalidate(victim); // avoid lost mounts
if (d_is_dir(victim))
fsnotify_rmdir(inode, victim);
else
fsnotify_unlink(inode, victim);
if (callback)
callback(victim);
fsnotify_delete(inode, d_inode(victim), victim);
dput(victim); // unpin it
}
if (victim == dentry) {
@ -638,7 +637,8 @@ void simple_recursive_removal(struct dentry *dentry,
inode_set_ctime_current(inode));
if (d_is_dir(dentry))
drop_nlink(inode);
inode_unlock(inode);
if (!locked)
inode_unlock(inode);
dput(dentry);
return;
}
@ -647,8 +647,22 @@ void simple_recursive_removal(struct dentry *dentry,
this = child;
}
}
void simple_recursive_removal(struct dentry *dentry,
void (*callback)(struct dentry *))
{
return __simple_recursive_removal(dentry, callback, false);
}
EXPORT_SYMBOL(simple_recursive_removal);
/* caller holds parent directory with I_MUTEX_PARENT */
void locked_recursive_removal(struct dentry *dentry,
void (*callback)(struct dentry *))
{
return __simple_recursive_removal(dentry, callback, true);
}
EXPORT_SYMBOL(locked_recursive_removal);
static const struct super_operations simple_super_operations = {
.statfs = simple_statfs,
};

View File

@ -300,7 +300,7 @@ static struct dentry *psinfo_lock_root(void)
return NULL;
root = pstore_sb->s_root;
inode_lock(d_inode(root));
inode_lock_nested(d_inode(root), I_MUTEX_PARENT);
return root;
}
@ -318,8 +318,7 @@ int pstore_put_backend_records(struct pstore_info *psi)
list_for_each_entry_safe(pos, tmp, &records_list, list) {
if (pos->record->psi == psi) {
list_del_init(&pos->list);
d_invalidate(pos->dentry);
simple_unlink(d_inode(root), pos->dentry);
locked_recursive_removal(pos->dentry, NULL);
pos->dentry = NULL;
}
}

View File

@ -3600,6 +3600,8 @@ extern int simple_rename(struct mnt_idmap *, struct inode *,
unsigned int);
extern void simple_recursive_removal(struct dentry *,
void (*callback)(struct dentry *));
extern void locked_recursive_removal(struct dentry *,
void (*callback)(struct dentry *));
extern int noop_fsync(struct file *, loff_t, loff_t, int);
extern ssize_t noop_direct_IO(struct kiocb *iocb, struct iov_iter *iter);
extern int simple_empty(struct dentry *);