diff options
| author | Linus Torvalds <torvalds@linux-foundation.org> | 2025-12-05 14:36:21 -0800 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2025-12-05 14:36:21 -0800 |
| commit | 7cd122b55283d3ceef71a5b723ccaa03a72284b4 (patch) | |
| tree | 56cb173d2c4ea306add677d6cc8f70e8cb4c14f2 /drivers/usb/gadget/function | |
| parent | 7203ca412fc8e8a0588e9adc0f777d3163f8dff3 (diff) | |
| parent | eb028c33451af08bb34f45c6be6967ef1c98cbd1 (diff) | |
Merge tag 'pull-persistency' of git://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs
Pull persistent dentry infrastructure and conversion from Al Viro:
"Some filesystems use a kinda-sorta controlled dentry refcount leak to
pin dentries of created objects in dcache (and undo it when removing
those). A reference is grabbed and not released, but it's not actually
_stored_ anywhere.
That works, but it's hard to follow and verify; among other things, we
have no way to tell _which_ of the increments is intended to be an
unpaired one. Worse, on removal we need to decide whether the
reference had already been dropped, which can be non-trivial if that
removal is on umount and we need to figure out if this dentry is
pinned due to e.g. unlink() not done. Usually that is handled by using
kill_litter_super() as ->kill_sb(), but there are open-coded special
cases of the same (consider e.g. /proc/self).
Things get simpler if we introduce a new dentry flag
(DCACHE_PERSISTENT) marking those "leaked" dentries. Having it set
claims responsibility for +1 in refcount.
The end result this series is aiming for:
- get these unbalanced dget() and dput() replaced with new primitives
that would, in addition to adjusting refcount, set and clear
persistency flag.
- instead of having kill_litter_super() mess with removing the
remaining "leaked" references (e.g. for all tmpfs files that hadn't
been removed prior to umount), have the regular
shrink_dcache_for_umount() strip DCACHE_PERSISTENT of all dentries,
dropping the corresponding reference if it had been set. After that
kill_litter_super() becomes an equivalent of kill_anon_super().
Doing that in a single step is not feasible - it would affect too many
places in too many filesystems. It has to be split into a series.
This work has really started early in 2024; quite a few preliminary
pieces have already gone into mainline. This chunk is finally getting
to the meat of that stuff - infrastructure and most of the conversions
to it.
Some pieces are still sitting in the local branches, but the bulk of
that stuff is here"
* tag 'pull-persistency' of git://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs: (54 commits)
d_make_discardable(): warn if given a non-persistent dentry
kill securityfs_recursive_remove()
convert securityfs
get rid of kill_litter_super()
convert rust_binderfs
convert nfsctl
convert rpc_pipefs
convert hypfs
hypfs: swich hypfs_create_u64() to returning int
hypfs: switch hypfs_create_str() to returning int
hypfs: don't pin dentries twice
convert gadgetfs
gadgetfs: switch to simple_remove_by_name()
convert functionfs
functionfs: switch to simple_remove_by_name()
functionfs: fix the open/removal races
functionfs: need to cancel ->reset_work in ->kill_sb()
functionfs: don't bother with ffs->ref in ffs_data_{opened,closed}()
functionfs: don't abuse ffs_data_closed() on fs shutdown
convert selinuxfs
...
Diffstat (limited to 'drivers/usb/gadget/function')
| -rw-r--r-- | drivers/usb/gadget/function/f_fs.c | 144 |
1 files changed, 86 insertions, 58 deletions
diff --git a/drivers/usb/gadget/function/f_fs.c b/drivers/usb/gadget/function/f_fs.c index 47cfbe41fdff..4bf61017b42d 100644 --- a/drivers/usb/gadget/function/f_fs.c +++ b/drivers/usb/gadget/function/f_fs.c @@ -160,8 +160,6 @@ struct ffs_epfile { struct ffs_data *ffs; struct ffs_ep *ep; /* P: ffs->eps_lock */ - struct dentry *dentry; - /* * Buffer for holding data from partial reads which may happen since * we’re rounding user read requests to a multiple of a max packet size. @@ -271,11 +269,11 @@ struct ffs_desc_helper { }; static int __must_check ffs_epfiles_create(struct ffs_data *ffs); -static void ffs_epfiles_destroy(struct ffs_epfile *epfiles, unsigned count); +static void ffs_epfiles_destroy(struct super_block *sb, + struct ffs_epfile *epfiles, unsigned count); -static struct dentry * -ffs_sb_create_file(struct super_block *sb, const char *name, void *data, - const struct file_operations *fops); +static int ffs_sb_create_file(struct super_block *sb, const char *name, + void *data, const struct file_operations *fops); /* Devices management *******************************************************/ @@ -640,13 +638,22 @@ done_mutex: static int ffs_ep0_open(struct inode *inode, struct file *file) { - struct ffs_data *ffs = inode->i_private; + struct ffs_data *ffs = inode->i_sb->s_fs_info; + int ret; - if (ffs->state == FFS_CLOSING) - return -EBUSY; + /* Acquire mutex */ + ret = ffs_mutex_lock(&ffs->mutex, file->f_flags & O_NONBLOCK); + if (ret < 0) + return ret; - file->private_data = ffs; ffs_data_opened(ffs); + if (ffs->state == FFS_CLOSING) { + ffs_data_closed(ffs); + mutex_unlock(&ffs->mutex); + return -EBUSY; + } + mutex_unlock(&ffs->mutex); + file->private_data = ffs; return stream_open(inode, file); } @@ -1193,14 +1200,33 @@ error: static int ffs_epfile_open(struct inode *inode, struct file *file) { - struct ffs_epfile *epfile = inode->i_private; + struct ffs_data *ffs = inode->i_sb->s_fs_info; + struct ffs_epfile *epfile; + int ret; - if (WARN_ON(epfile->ffs->state != FFS_ACTIVE)) + /* Acquire mutex */ + ret = ffs_mutex_lock(&ffs->mutex, file->f_flags & O_NONBLOCK); + if (ret < 0) + return ret; + + if (!atomic_inc_not_zero(&ffs->opened)) { + mutex_unlock(&ffs->mutex); return -ENODEV; + } + /* + * we want the state to be FFS_ACTIVE; FFS_ACTIVE alone is + * not enough, though - we might have been through FFS_CLOSING + * and back to FFS_ACTIVE, with our file already removed. + */ + epfile = smp_load_acquire(&inode->i_private); + if (unlikely(ffs->state != FFS_ACTIVE || !epfile)) { + mutex_unlock(&ffs->mutex); + ffs_data_closed(ffs); + return -ENODEV; + } + mutex_unlock(&ffs->mutex); file->private_data = epfile; - ffs_data_opened(epfile->ffs); - return stream_open(inode, file); } @@ -1332,7 +1358,7 @@ static void ffs_dmabuf_put(struct dma_buf_attachment *attach) static int ffs_epfile_release(struct inode *inode, struct file *file) { - struct ffs_epfile *epfile = inode->i_private; + struct ffs_epfile *epfile = file->private_data; struct ffs_dmabuf_priv *priv, *tmp; struct ffs_data *ffs = epfile->ffs; @@ -1866,26 +1892,26 @@ ffs_sb_make_inode(struct super_block *sb, void *data, } /* Create "regular" file */ -static struct dentry *ffs_sb_create_file(struct super_block *sb, - const char *name, void *data, - const struct file_operations *fops) +static int ffs_sb_create_file(struct super_block *sb, const char *name, + void *data, const struct file_operations *fops) { struct ffs_data *ffs = sb->s_fs_info; struct dentry *dentry; struct inode *inode; - dentry = d_alloc_name(sb->s_root, name); - if (!dentry) - return NULL; - inode = ffs_sb_make_inode(sb, data, fops, NULL, &ffs->file_perms); - if (!inode) { - dput(dentry); - return NULL; + if (!inode) + return -ENOMEM; + dentry = simple_start_creating(sb->s_root, name); + if (IS_ERR(dentry)) { + iput(inode); + return PTR_ERR(dentry); } - d_add(dentry, inode); - return dentry; + d_make_persistent(dentry, inode); + + simple_done_creating(dentry); + return 0; } /* Super block */ @@ -1928,10 +1954,7 @@ static int ffs_sb_fill(struct super_block *sb, struct fs_context *fc) return -ENOMEM; /* EP0 file */ - if (!ffs_sb_create_file(sb, "ep0", ffs, &ffs_ep0_operations)) - return -ENOMEM; - - return 0; + return ffs_sb_create_file(sb, "ep0", ffs, &ffs_ep0_operations); } enum { @@ -2071,12 +2094,21 @@ static int ffs_fs_init_fs_context(struct fs_context *fc) return 0; } +static void ffs_data_reset(struct ffs_data *ffs); + static void ffs_fs_kill_sb(struct super_block *sb) { - kill_litter_super(sb); - if (sb->s_fs_info) - ffs_data_closed(sb->s_fs_info); + kill_anon_super(sb); + if (sb->s_fs_info) { + struct ffs_data *ffs = sb->s_fs_info; + ffs->state = FFS_CLOSING; + ffs_data_reset(ffs); + // no configfs accesses from that point on, + // so no further schedule_work() is possible + cancel_work_sync(&ffs->reset_work); + ffs_data_put(ffs); + } } static struct file_system_type ffs_fs_type = { @@ -2114,7 +2146,6 @@ static void functionfs_cleanup(void) /* ffs_data and ffs_function construction and destruction code **************/ static void ffs_data_clear(struct ffs_data *ffs); -static void ffs_data_reset(struct ffs_data *ffs); static void ffs_data_get(struct ffs_data *ffs) { @@ -2123,7 +2154,6 @@ static void ffs_data_get(struct ffs_data *ffs) static void ffs_data_opened(struct ffs_data *ffs) { - refcount_inc(&ffs->ref); if (atomic_add_return(1, &ffs->opened) == 1 && ffs->state == FFS_DEACTIVATED) { ffs->state = FFS_CLOSING; @@ -2148,11 +2178,11 @@ static void ffs_data_put(struct ffs_data *ffs) static void ffs_data_closed(struct ffs_data *ffs) { - struct ffs_epfile *epfiles; - unsigned long flags; - if (atomic_dec_and_test(&ffs->opened)) { if (ffs->no_disconnect) { + struct ffs_epfile *epfiles; + unsigned long flags; + ffs->state = FFS_DEACTIVATED; spin_lock_irqsave(&ffs->eps_lock, flags); epfiles = ffs->epfiles; @@ -2161,7 +2191,7 @@ static void ffs_data_closed(struct ffs_data *ffs) flags); if (epfiles) - ffs_epfiles_destroy(epfiles, + ffs_epfiles_destroy(ffs->sb, epfiles, ffs->eps_count); if (ffs->setup_state == FFS_SETUP_PENDING) @@ -2171,12 +2201,6 @@ static void ffs_data_closed(struct ffs_data *ffs) ffs_data_reset(ffs); } } - if (atomic_read(&ffs->opened) < 0) { - ffs->state = FFS_CLOSING; - ffs_data_reset(ffs); - } - - ffs_data_put(ffs); } static struct ffs_data *ffs_data_new(const char *dev_name) @@ -2226,7 +2250,7 @@ static void ffs_data_clear(struct ffs_data *ffs) * copy of epfile will save us from use-after-free. */ if (epfiles) { - ffs_epfiles_destroy(epfiles, ffs->eps_count); + ffs_epfiles_destroy(ffs->sb, epfiles, ffs->eps_count); ffs->epfiles = NULL; } @@ -2323,6 +2347,7 @@ static int ffs_epfiles_create(struct ffs_data *ffs) { struct ffs_epfile *epfile, *epfiles; unsigned i, count; + int err; count = ffs->eps_count; epfiles = kcalloc(count, sizeof(*epfiles), GFP_KERNEL); @@ -2339,12 +2364,11 @@ static int ffs_epfiles_create(struct ffs_data *ffs) sprintf(epfile->name, "ep%02x", ffs->eps_addrmap[i]); else sprintf(epfile->name, "ep%u", i); - epfile->dentry = ffs_sb_create_file(ffs->sb, epfile->name, - epfile, - &ffs_epfile_operations); - if (!epfile->dentry) { - ffs_epfiles_destroy(epfiles, i - 1); - return -ENOMEM; + err = ffs_sb_create_file(ffs->sb, epfile->name, + epfile, &ffs_epfile_operations); + if (err) { + ffs_epfiles_destroy(ffs->sb, epfiles, i - 1); + return err; } } @@ -2352,16 +2376,20 @@ static int ffs_epfiles_create(struct ffs_data *ffs) return 0; } -static void ffs_epfiles_destroy(struct ffs_epfile *epfiles, unsigned count) +static void clear_one(struct dentry *dentry) +{ + smp_store_release(&dentry->d_inode->i_private, NULL); +} + +static void ffs_epfiles_destroy(struct super_block *sb, + struct ffs_epfile *epfiles, unsigned count) { struct ffs_epfile *epfile = epfiles; + struct dentry *root = sb->s_root; for (; count; --count, ++epfile) { BUG_ON(mutex_is_locked(&epfile->mutex)); - if (epfile->dentry) { - simple_recursive_removal(epfile->dentry, NULL); - epfile->dentry = NULL; - } + simple_remove_by_name(root, epfile->name, clear_one); } kfree(epfiles); |
