mirror of
git://git.yoctoproject.org/linux-yocto.git
synced 2026-01-27 12:47:24 +01:00
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:
commit
1959e18cc0
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
|
||||
|
|
|
|||
32
fs/libfs.c
32
fs/libfs.c
|
|
@ -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,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 *);
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user