From 9f6f44aa5a58aaae0838126dc09460c3e1f56ccb Mon Sep 17 00:00:00 2001 From: Joanne Koong Date: Wed, 22 Apr 2026 09:31:23 -0700 Subject: fuse: don't block in fuse_get_dev() for non-sync_init case Commit a8dd5f1b73bc ("fuse: create fuse_dev on /dev/fuse open instead of mount") changed behavior so that fuse_get_dev() now unconditionally blocks waiting for a connection, even in the case where sync_init was not set. Previously, non-sync_init opens returned -EPERM immediately. Restore the previous behavior of returning -EPERM. Fixes: a8dd5f1b73bc ("fuse: create fuse_dev on /dev/fuse open instead of mount") Reported-by: Mark Brown Closes: https://lore.kernel.org/all/3c9f8396-41f4-4c88-b883-34bede72b427@sirena.org.uk/ Cc: Signed-off-by: Joanne Koong Tested-by: Mark Brown Signed-off-by: Miklos Szeredi --- fs/fuse/dev.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index 5dda7080f4a9..05b6d15afe61 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -1562,9 +1562,15 @@ struct fuse_dev *fuse_get_dev(struct file *file) struct fuse_dev *fud = fuse_file_to_fud(file); int err; - err = wait_event_interruptible(fuse_dev_waitq, fuse_dev_fc_get(fud) != NULL); - if (err) - return ERR_PTR(err); + if (unlikely(!fuse_dev_fc_get(fud))) { + /* only block waiting for mount if sync init was requested */ + if (!fud->sync_init) + return ERR_PTR(-EPERM); + + err = wait_event_interruptible(fuse_dev_waitq, fuse_dev_fc_get(fud) != NULL); + if (err) + return ERR_PTR(err); + } return fud; } -- cgit v1.2.3 From a078484921052d0badd827fcc2770b5cfc1d4120 Mon Sep 17 00:00:00 2001 From: Joanne Koong Date: Mon, 18 May 2026 22:28:06 -0700 Subject: fuse: re-lock request before replacing page cache folio fuse_try_move_folio() unlocks the request on entry but does not re-lock it on the success path. This means fuse_chan_abort() can end the request and free the fuse_io_args (eg fuse_readpages_end()) while the subsequent copy chain logic after fuse_try_move_folio() accesses the fuse_io_args, leading to use-after-free issues. Fix this by calling lock_request() before replace_page_cache_folio(). This ensures the request is locked on the success path which will prevent the fuse_io_args from being freed while the later copying logic runs, and also ensures that the ap->folios[i]->mapping is never null since ap->folios[i] will always point to the newfolio after replace_page_cache_folio(). Fixes: ce534fb05292 ("fuse: allow splice to move pages") Cc: stable@vger.kernel.org Reported-by: Lei Lu Signed-off-by: Joanne Koong Signed-off-by: Miklos Szeredi --- fs/fuse/dev.c | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index 05b6d15afe61..86b62a1d3746 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -1037,6 +1037,10 @@ static int fuse_try_move_folio(struct fuse_copy_state *cs, struct folio **foliop if (WARN_ON(folio_test_mlocked(oldfolio))) goto out_fallback_unlock; + err = lock_request(cs->req); + if (err) + goto out_fallback_unlock; + replace_page_cache_folio(oldfolio, newfolio); folio_get(newfolio); @@ -1050,20 +1054,7 @@ static int fuse_try_move_folio(struct fuse_copy_state *cs, struct folio **foliop */ pipe_buf_release(cs->pipe, buf); - err = 0; - spin_lock(&cs->req->waitq.lock); - if (test_bit(FR_ABORTED, &cs->req->flags)) - err = -ENOENT; - else - *foliop = newfolio; - spin_unlock(&cs->req->waitq.lock); - - if (err) { - folio_unlock(newfolio); - folio_put(newfolio); - goto out_put_old; - } - + *foliop = newfolio; folio_unlock(oldfolio); /* Drop ref for ap->pages[] array */ folio_put(oldfolio); -- cgit v1.2.3 From b5befa80fdbe287a98480effed9564712924add5 Mon Sep 17 00:00:00 2001 From: Joanne Koong Date: Mon, 18 May 2026 22:28:07 -0700 Subject: fuse: re-lock request before returning from fuse_ref_folio() fuse_ref_folio() unlocks the request but does not re-lock it before returning. fuse_chan_abort() can end the request and the async end callback (eg fuse_writepage_free()) can free the args while the subsequent copy chain logic after fuse_ref_folio() accesses them, leading to use-after-free issues. Fix this by locking the request in fuse_ref_folio() before returning. Fixes: c3021629a0d8 ("fuse: support splice() reading from fuse device") Cc: stable@vger.kernel.org Signed-off-by: Joanne Koong Signed-off-by: Miklos Szeredi --- fs/fuse/dev.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index 86b62a1d3746..b527d90ef74b 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -1106,7 +1106,7 @@ static int fuse_ref_folio(struct fuse_copy_state *cs, struct folio *folio, cs->nr_segs++; cs->len = 0; - return 0; + return lock_request(cs->req); } /* -- cgit v1.2.3 From 06b41351779e9289e8785694ade9042ae85e41ea Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Thu, 28 May 2026 10:58:24 +0200 Subject: virtiofs: fix UAF on submount umount MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit iput() called from fuse_release_end() can Oops if the super block has already been destroyed. Normally this is prevented by waiting for num_waiting to go down to zero before commencing with super block shutdown. This only works, however, for the last submount instance, as the wait counter is per connection, not per superblock. Revert to using synchronous release requests for the auto_submounts case, which is virtiofs only at this time. Reported-by: Aurélien Bombo Reported-by: Zhihao Cheng Cc: Greg Kurz Closes: https://github.com/kata-containers/kata-containers/issues/12589 Fixes: 26e5c67deb2e ("fuse: fix livelock in synchronous file put from fuseblk workers") Cc: stable@vger.kernel.org Reviewed-by: Greg Kurz Signed-off-by: Miklos Szeredi --- fs/fuse/file.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/fs/fuse/file.c b/fs/fuse/file.c index c59452d60b8d..9a0f5a8661da 100644 --- a/fs/fuse/file.c +++ b/fs/fuse/file.c @@ -380,8 +380,14 @@ void fuse_file_release(struct inode *inode, struct fuse_file *ff, * aio and closes the fd before the aio completes. Since aio takes its * own ref to the file, the IO completion has to drop the ref, which is * how the fuse server can end up closing its clients' files. + * + * Exception is virtio-fs, which is not affected by the above (server is + * on host, cannot close open files in guest). Virtio-fs needs sync + * release, because the num_waiting mechanism to wait for all requests + * before commencing with fs shutdown doesn't work if submounts are + * used. */ - fuse_file_put(ff, false); + fuse_file_put(ff, ff->fm->fc->auto_submounts); } void fuse_release_common(struct file *file, bool isdir) -- cgit v1.2.3 From 4dd6f6d3085a84e74b0a1efec3a05ed0b5125dce Mon Sep 17 00:00:00 2001 From: "Matthew R. Ochs" Date: Tue, 26 May 2026 08:20:21 -0700 Subject: fuse: back uncached readdir buffers with pages Commit dabb90391028 ("fuse: increase readdir buffer size") changed fuse_readdir_uncached() to size its temporary buffer from ctx->count. This is useful for overlayfs and other in-kernel callers that use INT_MAX to indicate an unlimited directory read. The larger buffer is currently supplied as a kvec output argument. For virtiofs, kvec arguments are copied through req->argbuf, which is allocated with kmalloc(..., GFP_ATOMIC). A large uncached readdir buffer can therefore require a multi-megabyte contiguous atomic allocation before the request is queued. Avoid the large bounce-buffer allocation by backing uncached readdir output with pages and setting out_pages. Transports such as virtiofs can then pass the pages as scatter-gather entries instead of copying the output through argbuf. Map the pages with vm_map_ram() only while parsing the returned dirents. The existing parser can then continue to use a linear kernel mapping. [SzM: separate allocation of pages into a helper function] Fixes: dabb90391028 ("fuse: increase readdir buffer size") Cc: stable@vger.kernel.org Signed-off-by: Matthew R. Ochs Signed-off-by: Miklos Szeredi --- fs/fuse/readdir.c | 85 +++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 67 insertions(+), 18 deletions(-) diff --git a/fs/fuse/readdir.c b/fs/fuse/readdir.c index db5ae8ec1030..c38139225a2e 100644 --- a/fs/fuse/readdir.c +++ b/fs/fuse/readdir.c @@ -12,6 +12,7 @@ #include #include #include +#include static bool fuse_use_readdirplus(struct inode *dir, struct dir_context *ctx) { @@ -335,6 +336,43 @@ static int parse_dirplusfile(char *buf, size_t nbytes, struct file *file, return 0; } +static struct page **fuse_readdir_alloc_buf(struct fuse_args_pages *ap, size_t *bufsize) +{ + unsigned int i, nr_alloc, nr_pages = DIV_ROUND_UP(*bufsize, PAGE_SIZE); + struct page **pages = kcalloc(nr_pages, sizeof(*pages), GFP_KERNEL); + + if (!pages) + return NULL; + + nr_alloc = alloc_pages_bulk(GFP_KERNEL, nr_pages, pages); + if (!nr_alloc) + goto free_array; + + if (nr_alloc < nr_pages) { + nr_pages = nr_alloc; + *bufsize = (size_t) nr_pages << PAGE_SHIFT; + } + + ap->folios = fuse_folios_alloc(nr_pages, GFP_KERNEL, &ap->descs); + if (!ap->folios) + goto release_pages; + + for (i = 0; i < nr_pages; i++) { + ap->folios[i] = page_folio(pages[i]); + ap->descs[i].length = min_t(size_t, *bufsize - (size_t)i * PAGE_SIZE, PAGE_SIZE); + } + ap->num_folios = nr_pages; + ap->args.out_pages = true; + + return pages; + +release_pages: + release_pages(pages, nr_pages); +free_array: + kfree(pages); + return NULL; +} + static int fuse_readdir_uncached(struct file *file, struct dir_context *ctx) { int plus; @@ -343,18 +381,16 @@ static int fuse_readdir_uncached(struct file *file, struct dir_context *ctx) struct fuse_mount *fm = get_fuse_mount(inode); struct fuse_conn *fc = fm->fc; struct fuse_io_args ia = {}; - struct fuse_args *args = &ia.ap.args; + struct fuse_args_pages *ap = &ia.ap; void *buf; size_t bufsize = clamp((unsigned int) ctx->count, PAGE_SIZE, fc->max_pages << PAGE_SHIFT); u64 attr_version = 0, evict_ctr = 0; bool locked; + struct page **pages = fuse_readdir_alloc_buf(ap, &bufsize); - buf = kvmalloc(bufsize, GFP_KERNEL); - if (!buf) + if (!pages) return -ENOMEM; - args->out_args[0].value = buf; - plus = fuse_use_readdirplus(inode, ctx); if (plus) { attr_version = fuse_get_attr_version(fm->fc); @@ -364,24 +400,37 @@ static int fuse_readdir_uncached(struct file *file, struct dir_context *ctx) fuse_read_args_fill(&ia, file, ctx->pos, bufsize, FUSE_READDIR); } locked = fuse_lock_inode(inode); - res = fuse_simple_request(fm, args); + res = fuse_simple_request(fm, &ap->args); fuse_unlock_inode(inode, locked); - if (res >= 0) { - if (!res) { - struct fuse_file *ff = file->private_data; - - if (ff->open_flags & FOPEN_CACHE_DIR) - fuse_readdir_cache_end(file, ctx->pos); - } else if (plus) { - res = parse_dirplusfile(buf, res, file, ctx, attr_version, - evict_ctr); - } else { + if (res < 0) + goto out; + + if (!res) { + struct fuse_file *ff = file->private_data; + + if (ff->open_flags & FOPEN_CACHE_DIR) + fuse_readdir_cache_end(file, ctx->pos); + goto out; + } + + buf = vm_map_ram(pages, ap->num_folios, -1); + if (!buf) { + res = -ENOMEM; + } else { + if (plus) + res = parse_dirplusfile(buf, res, file, ctx, attr_version, evict_ctr); + else res = parse_dirfile(buf, res, file, ctx); - } + + vm_unmap_ram(buf, ap->num_folios); } +out: + kfree(ap->folios); + release_pages(pages, ap->num_folios); + kfree(pages); - kvfree(buf); fuse_invalidate_atime(inode); + return res; } -- cgit v1.2.3 From 3a0a8bc51a13951c5141262bf770eeea3e0b6228 Mon Sep 17 00:00:00 2001 From: Chris Mason Date: Fri, 5 Jun 2026 12:27:06 -0700 Subject: fuse-uring: fix EFAULT clobber in fuse_uring_commit copy_from_user() returns the number of bytes not copied as an unsigned residual on failure (1..sizeof(struct fuse_out_header)). fuse_uring_commit stores that residual in ssize_t err, sets req->out.h.error to -EFAULT, then jumps to out: with err still holding the positive residual. err = copy_from_user(&req->out.h, &ent->headers->in_out, sizeof(req->out.h)); if (err) { req->out.h.error = -EFAULT; goto out; /* err is the positive residual */ } ... out: fuse_uring_req_end(ent, req, err); fuse_uring_req_end() then runs if (error) req->out.h.error = error; which overwrites the just-assigned -EFAULT with the positive residual. FUSE callers such as fuse_simple_request() test err < 0 to detect failure, so the positive value is interpreted as success and the caller proceeds with an uninitialised or partial req->out.args. Fix by assigning err = -EFAULT in the failure branch before jumping to out, so fuse_uring_req_end() receives a negative errno and sets req->out.h.error to -EFAULT. Fixes: c090c8abae4b ("fuse: Add io-uring sqe commit and fetch support") Cc: stable@vger.kernel.org Reviewed-by: Joanne Koong Assisted-by: kres (claude-opus-4-7) Signed-off-by: Chris Mason Reviewed-by: Bernd Schubert Signed-off-by: Miklos Szeredi --- fs/fuse/dev_uring.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c index 7b9822e8837b..c79652a0e337 100644 --- a/fs/fuse/dev_uring.c +++ b/fs/fuse/dev_uring.c @@ -813,14 +813,11 @@ static void fuse_uring_commit(struct fuse_ring_ent *ent, struct fuse_req *req, { struct fuse_ring *ring = ent->queue->ring; struct fuse_conn *fc = ring->fc; - ssize_t err = 0; + ssize_t err = -EFAULT; - err = copy_from_user(&req->out.h, &ent->headers->in_out, - sizeof(req->out.h)); - if (err) { - req->out.h.error = -EFAULT; + if (copy_from_user(&req->out.h, &ent->headers->in_out, + sizeof(req->out.h))) goto out; - } err = fuse_uring_out_header_has_err(&req->out.h, req, fc); if (err) { -- cgit v1.2.3 From 46725a0056c884cf58a6897f222892807327d82d Mon Sep 17 00:00:00 2001 From: Chris Mason Date: Fri, 5 Jun 2026 12:27:07 -0700 Subject: fuse-uring: fix data races on ring->ready On weakly-ordered architectures, the store to fiq->ops can be reordered past the store to ring->ready, allowing a CPU that sees ring->ready == true via fuse_uring_ready() to dispatch requests through a stale fiq->ops pointer. Upgrade the store to smp_store_release() and the load in fuse_uring_ready() to smp_load_acquire() so that the preceding WRITE_ONCE(fiq->ops, ...) is visible to any CPU that observes ring->ready == true. Additionally, fuse_uring_do_register() publishes ring->ready with WRITE_ONCE() but the fast-path check reads it with a plain load. This is a marked-vs-unmarked access that KCSAN will flag. Wrap it in READ_ONCE() to mark it without adding unnecessary ordering. Also wrap the fc->ring load in fuse_uring_ready() in READ_ONCE() to prevent the compiler from reloading it between the NULL check and the dereference. Fixes: c2c9af9a0b13 ("fuse: Allow to queue fg requests through io-uring") Cc: stable@vger.kernel.org Reviewed-by: Joanne Koong Assisted-by: kres (claude-opus-4-7) Signed-off-by: Chris Mason Reviewed-by: Bernd Schubert Signed-off-by: Miklos Szeredi --- fs/fuse/dev_uring.c | 4 ++-- fs/fuse/dev_uring_i.h | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c index c79652a0e337..33b9fcee4fc3 100644 --- a/fs/fuse/dev_uring.c +++ b/fs/fuse/dev_uring.c @@ -988,12 +988,12 @@ static void fuse_uring_do_register(struct fuse_ring_ent *ent, fuse_uring_ent_avail(ent, queue); spin_unlock(&queue->lock); - if (!ring->ready) { + if (!READ_ONCE(ring->ready)) { bool ready = is_ring_ready(ring, queue->qid); if (ready) { WRITE_ONCE(fiq->ops, &fuse_io_uring_ops); - WRITE_ONCE(ring->ready, true); + smp_store_release(&ring->ready, true); wake_up_all(&fc->blocked_waitq); } } diff --git a/fs/fuse/dev_uring_i.h b/fs/fuse/dev_uring_i.h index 51a563922ce1..531233e03e54 100644 --- a/fs/fuse/dev_uring_i.h +++ b/fs/fuse/dev_uring_i.h @@ -169,7 +169,9 @@ static inline void fuse_uring_wait_stopped_queues(struct fuse_conn *fc) static inline bool fuse_uring_ready(struct fuse_conn *fc) { - return fc->ring && fc->ring->ready; + struct fuse_ring *ring = READ_ONCE(fc->ring); + + return ring && smp_load_acquire(&ring->ready); } #else /* CONFIG_FUSE_IO_URING */ -- cgit v1.2.3 From 952b5d36f6a298f57c52a59e72076c69386a8aaf Mon Sep 17 00:00:00 2001 From: Joanne Koong Date: Mon, 8 Jun 2026 12:21:47 -0700 Subject: fuse-uring: fix race between registration and connection abortion This fixes this race: - thread a: io_uring_enter -> register sqe -> fuse_uring_create_ring_ent -> allocate ent but doesn't grab queue_ref yet - thread b: fuse_conn_destroy() -> fuse_chan_abort() -> fuse_uring_abort() is a no-op due to queue ref being 0 - thread a: grabs the queue_ref, queue_ref is now 1, rest of fuse_uring_do_register() logic executes - thread b: fuse_chan_abort() returns, fuse_chan_wait_aborted() now runs and calls "wait_event(ring->stop_waitq, atomic_read(&ring->queue_refs) == 0);" The abort/unmount thread will hang indefinitely in unkillable state as nothing will decrement queue_refs or wake stop_waitq, and the ring, queue, and ent are leaked. Fix this by checking fch->connected under fch->lock after the created ent has grabbed a ref count on the queue. This ensures that in the scenario above, it is guaranteed that we either release the queue ref and wake up stop_waitq (in case fuse_chan_wait_aborted() is already waiting) in fuse_uring_do_register() when we detect !fch->connected, or if the connection is aborted after the check, it is guaranteed that the async teardown worker will be running in the background cleaning up ents and decrementing the ent's ref on the queue, which will unblock the eventual queue and ring teardown. Fixes: 24fe962c86f5 ("fuse: {io-uring} Handle SQEs - register commands") Cc: stable@vger.kernel.org Reviewed-by: Bernd Schubert Signed-off-by: Joanne Koong Signed-off-by: Miklos Szeredi --- fs/fuse/dev_uring.c | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c index 33b9fcee4fc3..16b9229c2dbd 100644 --- a/fs/fuse/dev_uring.c +++ b/fs/fuse/dev_uring.c @@ -972,15 +972,26 @@ static bool is_ring_ready(struct fuse_ring *ring, int current_qid) /* * fuse_uring_req_fetch command handling */ -static void fuse_uring_do_register(struct fuse_ring_ent *ent, - struct io_uring_cmd *cmd, - unsigned int issue_flags) +static int fuse_uring_do_register(struct fuse_ring_ent *ent, + struct io_uring_cmd *cmd, + unsigned int issue_flags) { struct fuse_ring_queue *queue = ent->queue; struct fuse_ring *ring = queue->ring; struct fuse_conn *fc = ring->fc; struct fuse_iqueue *fiq = &fc->iq; + spin_lock(&fch->lock); + /* abort teardown path is running or has run */ + if (!fch->connected) { + spin_unlock(&fch->lock); + if (atomic_dec_and_test(&ring->queue_refs)) + wake_up_all(&ring->stop_waitq); + kfree(ent); + return -ECONNABORTED; + } + spin_unlock(&fch->lock); + fuse_uring_prepare_cancel(cmd, issue_flags, ent); spin_lock(&queue->lock); @@ -997,6 +1008,7 @@ static void fuse_uring_do_register(struct fuse_ring_ent *ent, wake_up_all(&fc->blocked_waitq); } } + return 0; } /* @@ -1113,9 +1125,7 @@ static int fuse_uring_register(struct io_uring_cmd *cmd, if (IS_ERR(ent)) return PTR_ERR(ent); - fuse_uring_do_register(ent, cmd, issue_flags); - - return 0; + return fuse_uring_do_register(ent, cmd, issue_flags); } /* -- cgit v1.2.3 From c146284c4355e96550e50e8f6e694223d107b621 Mon Sep 17 00:00:00 2001 From: Joanne Koong Date: Mon, 8 Jun 2026 12:21:48 -0700 Subject: fuse-uring: check connection abort during ring creation Check fch->connected under fch->lock in fuse_uring_create() before attaching a new ring. Without this, a race between fuse_uring_create() and fuse_chan_abort() can result in the ring, queue, and fpq.processing table being created after fuse_uring_abort() has already run, leading to unnecessary allocation and teardown. These are eventually cleaned up by fuse_uring_destruct() but will linger until the process exits, even with the connection aborted. Reviewed-by: Bernd Schubert Signed-off-by: Joanne Koong Signed-off-by: Miklos Szeredi --- fs/fuse/dev_uring.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c index 16b9229c2dbd..fb24a2cb53dc 100644 --- a/fs/fuse/dev_uring.c +++ b/fs/fuse/dev_uring.c @@ -244,6 +244,10 @@ static struct fuse_ring *fuse_uring_create(struct fuse_conn *fc) max_payload_size = max(max_payload_size, fc->max_pages * PAGE_SIZE); spin_lock(&fc->lock); + if (!fc->connected) { + spin_unlock(&fc->lock); + goto out_err; + } if (fc->ring) { /* race, another thread created the ring in the meantime */ spin_unlock(&fc->lock); @@ -981,16 +985,16 @@ static int fuse_uring_do_register(struct fuse_ring_ent *ent, struct fuse_conn *fc = ring->fc; struct fuse_iqueue *fiq = &fc->iq; - spin_lock(&fch->lock); + spin_lock(&fc->lock); /* abort teardown path is running or has run */ - if (!fch->connected) { - spin_unlock(&fch->lock); + if (!fc->connected) { + spin_unlock(&fc->lock); if (atomic_dec_and_test(&ring->queue_refs)) wake_up_all(&ring->stop_waitq); kfree(ent); return -ECONNABORTED; } - spin_unlock(&fch->lock); + spin_unlock(&fc->lock); fuse_uring_prepare_cancel(cmd, issue_flags, ent); -- cgit v1.2.3 From 198f45eeb9f78b2a2d6d8be95e4e43468eb2c6bc Mon Sep 17 00:00:00 2001 From: Joanne Koong Date: Mon, 8 Jun 2026 12:21:49 -0700 Subject: fuse-uring: fix moving cancelled entry to ent_in_userspace list fuse_uring_cancel() moves entries that are available (these have no reqs attached) to the ent_in_userspace list. ent_list_request_expired() checks the first entry on ent_in_userspace and dereferences ent->fuse_req unconditionally, which will crash on a cancelled entry that was moved to this list. Fix this by freeing the entry and dropping queue_refs directly in fuse_uring_cancel(). This is safe because cancel is the cancel handler itself - after io_uring_cmd_done(), no more cancels will be dispatched for this command, and teardown serializes with cancel via queue->lock. Since cancel now decrements queue_refs, fuse_uring_abort() must no longer gate fuse_uring_abort_end_requests() on queue_refs > 0, as cancelled entries may have already dropped queue_refs while requests are still queued. Remove the gate so abort always flushes requests and stops queues. Reported-by: Heechan Kang Tested-by: Heechan Kang Reviewed-by: Bernd Schubert Fixes: 4fea593e625c ("fuse: optimize over-io-uring request expiration check") Cc: stable@vger.kernel.org Suggested-by: Jian Huang Li Suggested-by: Horst Birthelmer Signed-off-by: Joanne Koong Signed-off-by: Miklos Szeredi --- fs/fuse/dev_uring.c | 6 ++++-- fs/fuse/dev_uring_i.h | 6 +++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c index fb24a2cb53dc..0bda31f76c7f 100644 --- a/fs/fuse/dev_uring.c +++ b/fs/fuse/dev_uring.c @@ -511,8 +511,7 @@ static void fuse_uring_cancel(struct io_uring_cmd *cmd, queue = ent->queue; spin_lock(&queue->lock); if (ent->state == FRRS_AVAILABLE) { - ent->state = FRRS_USERSPACE; - list_move_tail(&ent->list, &queue->ent_in_userspace); + list_del_init(&ent->list); need_cmd_done = true; ent->cmd = NULL; } @@ -521,6 +520,9 @@ static void fuse_uring_cancel(struct io_uring_cmd *cmd, if (need_cmd_done) { /* no queue lock to avoid lock order issues */ io_uring_cmd_done(cmd, -ENOTCONN, issue_flags); + kfree(ent); + if (atomic_dec_and_test(&queue->ring->queue_refs)) + wake_up_all(&queue->ring->stop_waitq); } } diff --git a/fs/fuse/dev_uring_i.h b/fs/fuse/dev_uring_i.h index 531233e03e54..054ad4fdd749 100644 --- a/fs/fuse/dev_uring_i.h +++ b/fs/fuse/dev_uring_i.h @@ -152,10 +152,10 @@ static inline void fuse_uring_abort(struct fuse_conn *fc) if (ring == NULL) return; - if (atomic_read(&ring->queue_refs) > 0) { - fuse_uring_abort_end_requests(ring); + fuse_uring_abort_end_requests(ring); + + if (atomic_read(&ring->queue_refs) > 0) fuse_uring_stop_queues(ring); - } } static inline void fuse_uring_wait_stopped_queues(struct fuse_conn *fc) -- cgit v1.2.3 From bea4fe98204b6ce7eb8e29f7bf867dd7619b3ddd Mon Sep 17 00:00:00 2001 From: Chris Mason Date: Mon, 8 Jun 2026 17:28:55 -0700 Subject: fuse-uring: end fuse_req on io-uring cancel task work When io_uring delivers task work with tw.cancel set (PF_EXITING, PF_KTHREAD fallback, or percpu_ref_is_dying on the ring context), fuse_uring_send_in_task() takes the cancel branch, assigns -ECANCELED, and falls through to fuse_uring_send(). That path only flips the entry to FRRS_USERSPACE and completes the io_uring cmd; it never discharges the ring entry's owning reference to the fuse_req that fuse_uring_add_req_to_ring_ent() handed it at dispatch time. fuse_uring_send_in_task() tw.cancel == true err = -ECANCELED fuse_uring_send(ent, cmd, err, issue_flags) ent->state = FRRS_USERSPACE list_move(&ent->list, &queue->ent_in_userspace) ent->cmd = NULL io_uring_cmd_done(-ECANCELED) /* ent->fuse_req still set, req still hashed */ The fuse_req stays linked on fpq->processing[hash] and fuse_request_end() is never invoked. The originating syscall thread blocks in D-state in request_wait_answer() until fuse_abort_conn() runs, which can be the entire connection lifetime. For FR_BACKGROUND requests fc->num_background is never decremented either, so repeated cancels inflate the counter until max_background is hit and all later background ops stall. tw.cancel does not imply a connection abort (e.g. a single io_uring worker thread exits while the fuse connection stays up), so this cannot be left for fuse_abort_conn() to clean up. Ending the req but still routing the entry through fuse_uring_send() is not enough: that leaves a req-less entry on ent_in_userspace, and ent_list_request_expired() dereferences ent->fuse_req unconditionally on the head of that list, which would then NULL-deref. Fix the cancel branch to release the entry directly. Remove it from the queue, complete the io_uring cmd, end the fuse_req, free the entry, and drop its queue_refs (waking the teardown waiter if it was the last). Fixes: c2c9af9a0b13 ("fuse: Allow to queue fg requests through io-uring") Cc: stable@vger.kernel.org Reviewed-by: Joanne Koong Assisted-by: kres (claude-opus-4-7) Signed-off-by: Chris Mason Signed-off-by: Miklos Szeredi --- fs/fuse/dev_uring.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c index 0bda31f76c7f..7b20a0f7643d 100644 --- a/fs/fuse/dev_uring.c +++ b/fs/fuse/dev_uring.c @@ -1238,11 +1238,21 @@ static void fuse_uring_send_in_task(struct io_tw_req tw_req, io_tw_token_t tw) fuse_uring_next_fuse_req(ent, queue, issue_flags); return; } + fuse_uring_send(ent, cmd, err, issue_flags); } else { err = -ECANCELED; - } - fuse_uring_send(ent, cmd, err, issue_flags); + spin_lock(&queue->lock); + list_del_init(&ent->list); + spin_unlock(&queue->lock); + + io_uring_cmd_done(cmd, err, issue_flags); + + fuse_uring_req_end(ent, ent->fuse_req, err); + kfree(ent); + if (atomic_dec_and_test(&queue->ring->queue_refs)) + wake_up_all(&queue->ring->stop_waitq); + } } static struct fuse_ring_queue *fuse_uring_task_to_queue(struct fuse_ring *ring) -- cgit v1.2.3 From d351da75066955144515cb2f9aa959f24a04287a Mon Sep 17 00:00:00 2001 From: Bernd Schubert Date: Mon, 8 Jun 2026 23:03:43 +0200 Subject: fuse-uring: Avoid use-after-free in fuse_uring_async_stop_queues fuse_uring_async_stop_queues() might run when the last reference on ring->queue_refs was already dropped. In order to avoid an early destruction a reference on struct fuse_conn is now taken before starting fuse_uring_async_stop_queues() and that reference is only released when that delayed work queue terminates. Fixes: 4a9bfb9b6850 ("fuse: {io-uring} Handle teardown of ring entries") Cc: stable@kernel.org # 6.14 Reported-by: Berkant Koc Signed-off-by: Bernd Schubert Reviewed-by: Joanne Koong Signed-off-by: Miklos Szeredi --- fs/fuse/dev_uring.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c index 7b20a0f7643d..38b5a05a423f 100644 --- a/fs/fuse/dev_uring.c +++ b/fs/fuse/dev_uring.c @@ -470,6 +470,7 @@ static void fuse_uring_async_stop_queues(struct work_struct *work) FUSE_URING_TEARDOWN_INTERVAL); } else { wake_up_all(&ring->stop_waitq); + fuse_conn_put(ring->chan->conn); } } @@ -481,6 +482,7 @@ void fuse_uring_stop_queues(struct fuse_ring *ring) fuse_uring_teardown_all_queues(ring); if (atomic_read(&ring->queue_refs) > 0) { + fuse_conn_get(ring->chan->conn); ring->teardown_time = jiffies; INIT_DELAYED_WORK(&ring->async_teardown_work, fuse_uring_async_stop_queues); -- cgit v1.2.3 From b70a3aca16934c196f92abb17b01c1647b9bb63c Mon Sep 17 00:00:00 2001 From: Bernd Schubert Date: Mon, 8 Jun 2026 23:03:44 +0200 Subject: fuse-uring: Avoid queue->stopped races and set/read that value under lock There are several readers of queue->stopped that check the value under lock, but fuse_uring_commit_fetch() did not and actually the value was not set under the lock in fuse_uring_abort_end_requests() either. Especially in fuse_uring_commit_fetch it is important to check under a lock, because due to races 'struct fuse_req' might be freed with fuse_request_end, but another thread/cpu might already do teardown work. Cc: stable@kernel.org # 6.14 Fixes: 4a9bfb9b6850fec ("fuse: {io-uring} Handle teardown of ring entries") Reported-by: Berkant Koc Reported-by: xlabai Signed-off-by: Bernd Schubert Reviewed-by: Joanne Koong Signed-off-by: Miklos Szeredi --- fs/fuse/dev_uring.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c index 38b5a05a423f..ed652034927e 100644 --- a/fs/fuse/dev_uring.c +++ b/fs/fuse/dev_uring.c @@ -130,10 +130,9 @@ void fuse_uring_abort_end_requests(struct fuse_ring *ring) if (!queue) continue; - queue->stopped = true; - WARN_ON_ONCE(ring->fc->max_background != UINT_MAX); spin_lock(&queue->lock); + queue->stopped = true; spin_lock(&fc->bg_lock); fuse_uring_flush_bg(queue); spin_unlock(&fc->bg_lock); @@ -470,7 +469,7 @@ static void fuse_uring_async_stop_queues(struct work_struct *work) FUSE_URING_TEARDOWN_INTERVAL); } else { wake_up_all(&ring->stop_waitq); - fuse_conn_put(ring->chan->conn); + fuse_conn_put(ring->fc); } } @@ -482,7 +481,7 @@ void fuse_uring_stop_queues(struct fuse_ring *ring) fuse_uring_teardown_all_queues(ring); if (atomic_read(&ring->queue_refs) > 0) { - fuse_conn_get(ring->chan->conn); + fuse_conn_get(ring->fc); ring->teardown_time = jiffies; INIT_DELAYED_WORK(&ring->async_teardown_work, fuse_uring_async_stop_queues); @@ -903,10 +902,15 @@ static int fuse_uring_commit_fetch(struct io_uring_cmd *cmd, int issue_flags, return err; fpq = &queue->fpq; - if (!READ_ONCE(fc->connected) || READ_ONCE(queue->stopped)) + if (!READ_ONCE(fc->connected)) return err; spin_lock(&queue->lock); + if (unlikely(queue->stopped)) { + spin_unlock(&queue->lock); + return err; + } + /* Find a request based on the unique ID of the fuse request * This should get revised, as it needs a hash calculation and list * search. And full struct fuse_pqueue is needed (memory overhead). -- cgit v1.2.3 From 1efd3d474fc0ba74dfd984249bca78807d739812 Mon Sep 17 00:00:00 2001 From: Bernd Schubert Date: Mon, 8 Jun 2026 23:03:45 +0200 Subject: fuse-uring: make a fuse_req on SQE commit only findable after memcpy Bad userspace might try to trick us and send commit SQEs request unique / commit-id of requests that are not even send to fuse-server (io_uring_cmd_done() not called) yet. fuse_uring_commit_fetch() ends the fuse request when the ring entry has a wrong state, but that could have caused a use-after-free with the memcpy operations in fuse_uring_send_in_task(). In order to avoid such races the call of fuse_uring_add_to_pq() is moved after the copy operations and just before completing the io-uring request - malicious userspace cannot find the request anymore until all prepration work in fuse-client/kernel is completed. This also moves fuse_uring_add_to_pq() a bit up in the code to avoid a forward declaration. Also not with a preparation commit, to make it easier to back port to older kernels. Reported-by: xlabai Reported-by: Berkant Koc Fixes: c090c8abae4b6b ("fuse: Add io-uring sqe commit and fetch support") Cc: stable@kernel.org # 6.14 Signed-off-by: Bernd Schubert Reviewed-by: Joanne Koong Signed-off-by: Miklos Szeredi --- fs/fuse/dev_uring.c | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c index ed652034927e..aae79d5a6588 100644 --- a/fs/fuse/dev_uring.c +++ b/fs/fuse/dev_uring.c @@ -717,6 +717,19 @@ static int fuse_uring_prepare_send(struct fuse_ring_ent *ent, return err; } +/* Used to find the request on SQE commit */ +static void fuse_uring_add_to_pq(struct fuse_ring_ent *ent) +{ + struct fuse_ring_queue *queue = ent->queue; + struct fuse_pqueue *fpq = &queue->fpq; + unsigned int hash; + struct fuse_req *req = ent->fuse_req; + + req->ring_entry = ent; + hash = fuse_req_hash(req->in.h.unique); + list_move_tail(&req->list, &fpq->processing[hash]); +} + /* * Write data to the ring buffer and send the request to userspace, * userspace will read it @@ -739,6 +752,7 @@ static int fuse_uring_send_next_to_ring(struct fuse_ring_ent *ent, ent->cmd = NULL; ent->state = FRRS_USERSPACE; list_move_tail(&ent->list, &queue->ent_in_userspace); + fuse_uring_add_to_pq(ent); spin_unlock(&queue->lock); io_uring_cmd_done(cmd, 0, issue_flags); @@ -756,19 +770,6 @@ static void fuse_uring_ent_avail(struct fuse_ring_ent *ent, ent->state = FRRS_AVAILABLE; } -/* Used to find the request on SQE commit */ -static void fuse_uring_add_to_pq(struct fuse_ring_ent *ent, - struct fuse_req *req) -{ - struct fuse_ring_queue *queue = ent->queue; - struct fuse_pqueue *fpq = &queue->fpq; - unsigned int hash; - - req->ring_entry = ent; - hash = fuse_req_hash(req->in.h.unique); - list_move_tail(&req->list, &fpq->processing[hash]); -} - /* * Assign a fuse queue entry to the given entry */ @@ -786,10 +787,13 @@ static void fuse_uring_add_req_to_ring_ent(struct fuse_ring_ent *ent, } clear_bit(FR_PENDING, &req->flags); + + /* Until fuse_uring_add_to_pq() the req is not attached to any list */ + list_del_init(&req->list); + ent->fuse_req = req; ent->state = FRRS_FUSE_REQ; list_move_tail(&ent->list, &queue->ent_w_req_queue); - fuse_uring_add_to_pq(ent, req); } /* Fetch the next fuse request if available */ @@ -1220,6 +1224,7 @@ static void fuse_uring_send(struct fuse_ring_ent *ent, struct io_uring_cmd *cmd, ent->state = FRRS_USERSPACE; list_move_tail(&ent->list, &queue->ent_in_userspace); ent->cmd = NULL; + fuse_uring_add_to_pq(ent); spin_unlock(&queue->lock); io_uring_cmd_done(cmd, ret, issue_flags); -- cgit v1.2.3 From f8fce75fedf73ac72aa09163deb8f4291fdcaad2 Mon Sep 17 00:00:00 2001 From: Ji'an Zhou Date: Tue, 9 Jun 2026 09:58:51 +0000 Subject: fuse: clear intr_entry in fuse_resend and fuse_remove_pending_req When fuse_resend() moves a request from fpq->processing back to fiq->pending, it sets FR_PENDING and clears FR_SENT but does not remove the requests intr_entry from fiq->interrupts. If the request had FR_INTERRUPTED set from a prior signal, intr_entry remains dangling on fiq->interrupts. When the requesting task then receives a fatal signal, fuse_remove_pending_req() sees FR_PENDING=1, removes the request from fiq->pending and frees it via the refcount path, also without cleaning intr_entry. The stale intr_entry causes use-after-free when fuse_read_interrupt() iterates fiq->interrupts: - list_del_init(&req->intr_entry) -> UAF write on freed slab - req->in.h.unique -> UAF read, data leaked to userspace Remove intr_entry from fiq->interrupts in fuse_resend() for interrupted requests before they are placed back on fiq->pending. Add a WARN_ON if the intr_entry is not empty on request destruction. Fixes: 760eac73f9f6 ("fuse: Introduce a new notification type for resend pending requests") Cc: stable@vger.kernel.org # 6.9 Signed-off-by: Ji'an Zhou Signed-off-by: Miklos Szeredi --- fs/fuse/dev.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index b527d90ef74b..296cdb696f58 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -148,6 +148,7 @@ static struct fuse_req *fuse_request_alloc(struct fuse_mount *fm, gfp_t flags) static void fuse_request_free(struct fuse_req *req) { + WARN_ON(!list_empty(&req->intr_entry)); kmem_cache_free(fuse_req_cachep, req); } @@ -2023,6 +2024,14 @@ static void fuse_resend(struct fuse_conn *fc) fuse_dev_end_requests(&to_queue); return; } + /* + * Remove interrupt entries for resent requests to prevent stale + * intr_entry on fiq->interrupts after the request is re-queued. + */ + list_for_each_entry(req, &to_queue, list) { + if (test_bit(FR_INTERRUPTED, &req->flags)) + list_del_init(&req->intr_entry); + } /* iq and pq requests are both oldest to newest */ list_splice(&to_queue, &fiq->pending); fuse_dev_wake_and_unlock(fiq); -- cgit v1.2.3 From 1c57a69be962d459c5e705f5cb4355b841b3461c Mon Sep 17 00:00:00 2001 From: Joanne Koong Date: Tue, 9 Jun 2026 14:36:58 -0700 Subject: fuse-uring: remove request-less entries from ent_w_req_queue to fix NULL deref If a copy into the userspace ring buffer fails, a request will be terminated and fuse_uring_req_end() will set ent->fuse_req to NULL but it will leave the entry on ent_w_req_queue in FRRS_FUSE_REQ state. This can lead to a NULL deref if the request expiration logic scans ent_w_req_queue in the window before the entry is moved off it. Fix this by taking the entry off ent_w_req_queue and changing its state from FRRS_FUSE_REQ to FRRS_INVALID before terminating the request. Fixes: 4fea593e625c ("fuse: optimize over-io-uring request expiration check") Cc: stable@kernel.org Signed-off-by: Joanne Koong Signed-off-by: Miklos Szeredi --- fs/fuse/dev_uring.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c index aae79d5a6588..7e5fe462b935 100644 --- a/fs/fuse/dev_uring.c +++ b/fs/fuse/dev_uring.c @@ -709,10 +709,20 @@ static int fuse_uring_prepare_send(struct fuse_ring_ent *ent, int err; err = fuse_uring_copy_to_ring(ent, req); - if (!err) + if (!err) { set_bit(FR_SENT, &req->flags); - else + } else { + /* + * Copying the request failed. Remove the entry from the + * ent_w_req_queue list and terminate the request + */ + spin_lock(&ent->queue->lock); + list_del_init(&ent->list); + ent->state = FRRS_INVALID; + spin_unlock(&ent->queue->lock); + fuse_uring_req_end(ent, req, err); + } return err; } -- cgit v1.2.3 From 54243797cedf55447b4c5d560e8cd709900061ae Mon Sep 17 00:00:00 2001 From: Samuel Moelius Date: Wed, 10 Jun 2026 00:37:18 +0000 Subject: fuse: avoid 32-bit prune notification count wrap FUSE_NOTIFY_PRUNE validates the nodeid payload length with: size - sizeof(outarg) != outarg.count * sizeof(u64) On 32-bit kernels, size_t is also 32 bits, so the daemon-controlled count multiplication can wrap. A prune notification with count 0x20000000 and no nodeid payload passes the check, enters the copy loop, and asks the device copy path to read nodeids that are not present in the userspace write buffer. In QEMU this reaches the fuse_copy_fill() BUG_ON(!err) path. Validate the payload length with array_size() instead. That accepts exactly the same valid messages, but avoids wrapping arithmetic before the copy loop consumes the count. Assisted-by: Codex:gpt-5.5-cyber-preview Fixes: 3f29d59e92a9 ("fuse: add prune notification") Cc: stable@vger.kernel.org Signed-off-by: Samuel Moelius Reviewed-by: Joanne Koong Signed-off-by: Miklos Szeredi --- fs/fuse/dev.c | 2 +- fs/fuse/notify.c | 434 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 435 insertions(+), 1 deletion(-) create mode 100644 fs/fuse/notify.c diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index 296cdb696f58..12b57a8c6024 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -2076,7 +2076,7 @@ static int fuse_notify_prune(struct fuse_conn *fc, unsigned int size, if (err) return err; - if (size - sizeof(outarg) != outarg.count * sizeof(u64)) + if (size - sizeof(outarg) != array_size(outarg.count, sizeof(u64))) return -EINVAL; for (; outarg.count; outarg.count -= num) { diff --git a/fs/fuse/notify.c b/fs/fuse/notify.c new file mode 100644 index 000000000000..7cd63502e124 --- /dev/null +++ b/fs/fuse/notify.c @@ -0,0 +1,434 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include "dev.h" +#include "fuse_i.h" +#include + +static int fuse_notify_poll(struct fuse_conn *fc, unsigned int size, + struct fuse_copy_state *cs) +{ + struct fuse_notify_poll_wakeup_out outarg; + int err; + + if (size != sizeof(outarg)) + return -EINVAL; + + err = fuse_copy_one(cs, &outarg, sizeof(outarg)); + if (err) + return err; + + fuse_copy_finish(cs); + return fuse_notify_poll_wakeup(fc, &outarg); +} + +static int fuse_notify_inval_inode(struct fuse_conn *fc, unsigned int size, + struct fuse_copy_state *cs) +{ + struct fuse_notify_inval_inode_out outarg; + int err; + + if (size != sizeof(outarg)) + return -EINVAL; + + err = fuse_copy_one(cs, &outarg, sizeof(outarg)); + if (err) + return err; + fuse_copy_finish(cs); + + down_read(&fc->killsb); + err = fuse_reverse_inval_inode(fc, outarg.ino, + outarg.off, outarg.len); + up_read(&fc->killsb); + return err; +} + +static int fuse_notify_inval_entry(struct fuse_conn *fc, unsigned int size, + struct fuse_copy_state *cs) +{ + struct fuse_notify_inval_entry_out outarg; + int err; + char *buf; + struct qstr name; + + if (size < sizeof(outarg)) + return -EINVAL; + + err = fuse_copy_one(cs, &outarg, sizeof(outarg)); + if (err) + return err; + + if (outarg.namelen > fc->name_max) + return -ENAMETOOLONG; + + err = -EINVAL; + if (size != sizeof(outarg) + outarg.namelen + 1) + return -EINVAL; + + buf = kzalloc(outarg.namelen + 1, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + name.name = buf; + name.len = outarg.namelen; + err = fuse_copy_one(cs, buf, outarg.namelen + 1); + if (err) + goto err; + fuse_copy_finish(cs); + buf[outarg.namelen] = 0; + + down_read(&fc->killsb); + err = fuse_reverse_inval_entry(fc, outarg.parent, 0, &name, outarg.flags); + up_read(&fc->killsb); +err: + kfree(buf); + return err; +} + +static int fuse_notify_delete(struct fuse_conn *fc, unsigned int size, + struct fuse_copy_state *cs) +{ + struct fuse_notify_delete_out outarg; + int err; + char *buf; + struct qstr name; + + if (size < sizeof(outarg)) + return -EINVAL; + + err = fuse_copy_one(cs, &outarg, sizeof(outarg)); + if (err) + return err; + + if (outarg.namelen > fc->name_max) + return -ENAMETOOLONG; + + if (size != sizeof(outarg) + outarg.namelen + 1) + return -EINVAL; + + buf = kzalloc(outarg.namelen + 1, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + name.name = buf; + name.len = outarg.namelen; + err = fuse_copy_one(cs, buf, outarg.namelen + 1); + if (err) + goto err; + fuse_copy_finish(cs); + buf[outarg.namelen] = 0; + + down_read(&fc->killsb); + err = fuse_reverse_inval_entry(fc, outarg.parent, outarg.child, &name, 0); + up_read(&fc->killsb); +err: + kfree(buf); + return err; +} + +static int fuse_notify_store(struct fuse_conn *fc, unsigned int size, + struct fuse_copy_state *cs) +{ + struct fuse_notify_store_out outarg; + struct inode *inode; + struct address_space *mapping; + u64 nodeid; + int err; + unsigned int num; + loff_t file_size; + loff_t pos; + loff_t end; + + if (size < sizeof(outarg)) + return -EINVAL; + + err = fuse_copy_one(cs, &outarg, sizeof(outarg)); + if (err) + return err; + + if (size - sizeof(outarg) != outarg.size) + return -EINVAL; + + if (outarg.offset >= MAX_LFS_FILESIZE) + return -EINVAL; + + nodeid = outarg.nodeid; + pos = outarg.offset; + num = min(outarg.size, MAX_LFS_FILESIZE - pos); + + down_read(&fc->killsb); + + err = -ENOENT; + inode = fuse_ilookup(fc, nodeid, NULL); + if (!inode) + goto out_up_killsb; + + mapping = inode->i_mapping; + file_size = i_size_read(inode); + end = pos + num; + if (end > file_size) { + file_size = end; + fuse_write_update_attr(inode, file_size, num); + } + + while (num) { + struct folio *folio; + unsigned int folio_offset; + unsigned int nr_bytes; + pgoff_t index = pos >> PAGE_SHIFT; + + folio = filemap_grab_folio(mapping, index); + err = PTR_ERR(folio); + if (IS_ERR(folio)) + goto out_iput; + + folio_offset = offset_in_folio(folio, pos); + nr_bytes = min(num, folio_size(folio) - folio_offset); + + err = fuse_copy_folio(cs, &folio, folio_offset, nr_bytes, 0); + if (!folio_test_uptodate(folio) && !err && folio_offset == 0 && + (nr_bytes == folio_size(folio) || file_size == end)) { + folio_zero_segment(folio, nr_bytes, folio_size(folio)); + folio_mark_uptodate(folio); + } + folio_unlock(folio); + folio_put(folio); + + if (err) + goto out_iput; + + pos += nr_bytes; + num -= nr_bytes; + } + + err = 0; + +out_iput: + iput(inode); +out_up_killsb: + up_read(&fc->killsb); + return err; +} + +struct fuse_retrieve_args { + struct fuse_args_pages ap; + struct fuse_notify_retrieve_in inarg; +}; + +static void fuse_retrieve_end(struct fuse_args *args, int error) +{ + struct fuse_retrieve_args *ra = + container_of(args, typeof(*ra), ap.args); + + release_pages(ra->ap.folios, ra->ap.num_folios); + kfree(ra); +} + +static int fuse_retrieve(struct fuse_mount *fm, struct inode *inode, + struct fuse_notify_retrieve_out *outarg) +{ + int err; + struct address_space *mapping = inode->i_mapping; + loff_t file_size; + unsigned int num; + unsigned int offset; + size_t total_len = 0; + unsigned int num_pages; + struct fuse_conn *fc = fm->fc; + struct fuse_retrieve_args *ra; + size_t args_size = sizeof(*ra); + struct fuse_args_pages *ap; + struct fuse_args *args; + loff_t pos = outarg->offset; + + offset = offset_in_page(pos); + file_size = i_size_read(inode); + + num = min(outarg->size, fc->max_write); + if (pos > file_size) + num = 0; + else if (num > file_size - pos) + num = file_size - pos; + + num_pages = DIV_ROUND_UP(num + offset, PAGE_SIZE); + num_pages = min(num_pages, fc->max_pages); + num = min(num, num_pages << PAGE_SHIFT); + + args_size += num_pages * (sizeof(ap->folios[0]) + sizeof(ap->descs[0])); + + ra = kzalloc(args_size, GFP_KERNEL); + if (!ra) + return -ENOMEM; + + ap = &ra->ap; + ap->folios = (void *) (ra + 1); + ap->descs = (void *) (ap->folios + num_pages); + + args = &ap->args; + args->nodeid = outarg->nodeid; + args->opcode = FUSE_NOTIFY_REPLY; + args->in_numargs = 3; + args->in_pages = true; + args->end = fuse_retrieve_end; + + while (num && ap->num_folios < num_pages) { + struct folio *folio; + unsigned int folio_offset; + unsigned int nr_bytes; + pgoff_t index = pos >> PAGE_SHIFT; + + folio = filemap_get_folio(mapping, index); + if (IS_ERR(folio)) + break; + + folio_offset = offset_in_folio(folio, pos); + nr_bytes = min(folio_size(folio) - folio_offset, num); + + ap->folios[ap->num_folios] = folio; + ap->descs[ap->num_folios].offset = folio_offset; + ap->descs[ap->num_folios].length = nr_bytes; + ap->num_folios++; + + pos += nr_bytes; + num -= nr_bytes; + total_len += nr_bytes; + } + ra->inarg.offset = outarg->offset; + ra->inarg.size = total_len; + fuse_set_zero_arg0(args); + args->in_args[1].size = sizeof(ra->inarg); + args->in_args[1].value = &ra->inarg; + args->in_args[2].size = total_len; + + err = fuse_simple_notify_reply(fm, args, outarg->notify_unique); + if (err) + fuse_retrieve_end(args, err); + + return err; +} + +static int fuse_notify_retrieve(struct fuse_conn *fc, unsigned int size, + struct fuse_copy_state *cs) +{ + struct fuse_notify_retrieve_out outarg; + struct fuse_mount *fm; + struct inode *inode; + u64 nodeid; + int err; + + if (size != sizeof(outarg)) + return -EINVAL; + + err = fuse_copy_one(cs, &outarg, sizeof(outarg)); + if (err) + return err; + + fuse_copy_finish(cs); + + if (outarg.offset >= MAX_LFS_FILESIZE) + return -EINVAL; + + down_read(&fc->killsb); + err = -ENOENT; + nodeid = outarg.nodeid; + + inode = fuse_ilookup(fc, nodeid, &fm); + if (inode) { + err = fuse_retrieve(fm, inode, &outarg); + iput(inode); + } + up_read(&fc->killsb); + + return err; +} + +static int fuse_notify_resend(struct fuse_conn *fc) +{ + fuse_chan_resend(fc->chan); + return 0; +} + +/* + * Increments the fuse connection epoch. This will result of dentries from + * previous epochs to be invalidated. Additionally, if inval_wq is set, a work + * queue is scheduled to trigger the invalidation. + */ +static int fuse_notify_inc_epoch(struct fuse_conn *fc) +{ + atomic_inc(&fc->epoch); + if (inval_wq) + schedule_work(&fc->epoch_work); + + return 0; +} + +static int fuse_notify_prune(struct fuse_conn *fc, unsigned int size, + struct fuse_copy_state *cs) +{ + struct fuse_notify_prune_out outarg; + const unsigned int batch = 512; + u64 *nodeids __free(kfree) = kmalloc(sizeof(u64) * batch, GFP_KERNEL); + unsigned int num, i; + int err; + + if (!nodeids) + return -ENOMEM; + + if (size < sizeof(outarg)) + return -EINVAL; + + err = fuse_copy_one(cs, &outarg, sizeof(outarg)); + if (err) + return err; + + if (size - sizeof(outarg) != array_size(outarg.count, sizeof(u64))) + return -EINVAL; + + for (; outarg.count; outarg.count -= num) { + num = min(batch, outarg.count); + err = fuse_copy_one(cs, nodeids, num * sizeof(u64)); + if (err) + return err; + + scoped_guard(rwsem_read, &fc->killsb) { + for (i = 0; i < num; i++) + fuse_try_prune_one_inode(fc, nodeids[i]); + } + } + return 0; +} + +int fuse_notify(struct fuse_conn *fc, enum fuse_notify_code code, + unsigned int size, struct fuse_copy_state *cs) +{ + switch (code) { + case FUSE_NOTIFY_POLL: + return fuse_notify_poll(fc, size, cs); + + case FUSE_NOTIFY_INVAL_INODE: + return fuse_notify_inval_inode(fc, size, cs); + + case FUSE_NOTIFY_INVAL_ENTRY: + return fuse_notify_inval_entry(fc, size, cs); + + case FUSE_NOTIFY_STORE: + return fuse_notify_store(fc, size, cs); + + case FUSE_NOTIFY_RETRIEVE: + return fuse_notify_retrieve(fc, size, cs); + + case FUSE_NOTIFY_DELETE: + return fuse_notify_delete(fc, size, cs); + + case FUSE_NOTIFY_RESEND: + return fuse_notify_resend(fc); + + case FUSE_NOTIFY_INC_EPOCH: + return fuse_notify_inc_epoch(fc); + + case FUSE_NOTIFY_PRUNE: + return fuse_notify_prune(fc, size, cs); + + default: + return -EINVAL; + } +} -- cgit v1.2.3 From c12bbaf7c2d19325913ffd7002e580e63952d9e2 Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Wed, 10 Jun 2026 12:57:21 +0200 Subject: Revert "fuse: fix conversion of fuse_reverse_inval_entry() to start_removing()" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit cab012375122304a6343c1ed09404e5143b9dc01. Commit c9ba789dad15 ("VFS: introduce start_creating_noperm() and start_removing_noperm()") caused a regression in FUSE_NOTIFY_INVAL_ENTRY, which failed to invalidate negative dentries. This manifests in the filesystem returning -ENOENT for operations on an existing file. Fixing it properly while still keeping the start_removing* infrastructure would add much additional complexity. Instead revert to the original simple implementation. The start_removing* infrastructure is needed in VFS to abstract the filesystem locking. However filesystem code can still safely use the raw locking primitives without affacting other filesystems. This is part one of the revert. Reported-by: Артем Лабазов <123321artyom@gmail.com> Closes: https://lore.kernel.org/all/CAFbF8N7++zopZuEcsKRxBV_sgOGCbzCY0hOyMw1SiGAtuzGhyQ@mail.gmail.com/ Fixes: c9ba789dad15 ("VFS: introduce start_creating_noperm() and start_removing_noperm()") Cc: stable@vger.kernel.org # 6.19 Cc: NeilBrown Signed-off-by: Miklos Szeredi --- fs/fuse/dir.c | 23 +++++++---------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index b658b6baf72f..6b05846c9bed 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -1587,8 +1587,8 @@ int fuse_reverse_inval_entry(struct fuse_conn *fc, u64 parent_nodeid, { int err = -ENOTDIR; struct inode *parent; - struct dentry *dir = NULL; - struct dentry *entry = NULL; + struct dentry *dir; + struct dentry *entry; parent = fuse_ilookup(fc, parent_nodeid, NULL); if (!parent) @@ -1601,19 +1601,11 @@ int fuse_reverse_inval_entry(struct fuse_conn *fc, u64 parent_nodeid, dir = d_find_alias(parent); if (!dir) goto put_parent; - while (!entry) { - struct dentry *child = try_lookup_noperm(name, dir); - if (!child || IS_ERR(child)) - goto put_parent; - entry = start_removing_dentry(dir, child); - dput(child); - if (IS_ERR(entry)) - goto put_parent; - if (!d_same_name(entry, dir, name)) { - end_removing(entry); - entry = NULL; - } - } + + entry = start_removing_noperm(dir, name); + dput(dir); + if (IS_ERR(entry)) + goto put_parent; fuse_dir_changed(parent); if (!(flags & FUSE_EXPIRE_ONLY)) @@ -1651,7 +1643,6 @@ int fuse_reverse_inval_entry(struct fuse_conn *fc, u64 parent_nodeid, end_removing(entry); put_parent: - dput(dir); iput(parent); return err; } -- cgit v1.2.3 From 6e1b235627bb1172d27c1b2ea7bf53e67dbced8d Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Wed, 10 Jun 2026 13:02:53 +0200 Subject: fuse: do not use start_removing_noperm() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Revert the fuse part of commit c9ba789dad15 ("VFS: introduce start_creating_noperm() and start_removing_noperm()"). Commit c9ba789dad15 ("VFS: introduce start_creating_noperm() and start_removing_noperm()") caused a regression in FUSE_NOTIFY_INVAL_ENTRY, which failed to invalidate negative dentries. This manifests in the filesystem returning -ENOENT for operations on an existing file. Fixing it properly while still keeping the start_removing* infrastructure would add much additional complexity. Instead revert to the original simple implementation. The start_removing* infrastructure is needed in VFS to abstract the filesystem locking. However filesystem code can still safely use the raw locking primitives without affacting other filesystems. This is part two of the revert. Reported-by: Артем Лабазов <123321artyom@gmail.com> Closes: https://lore.kernel.org/all/CAFbF8N7++zopZuEcsKRxBV_sgOGCbzCY0hOyMw1SiGAtuzGhyQ@mail.gmail.com/ Fixes: c9ba789dad15 ("VFS: introduce start_creating_noperm() and start_removing_noperm()") Cc: stable@vger.kernel.org # 6.19 Cc: NeilBrown Signed-off-by: Miklos Szeredi --- fs/fuse/dir.c | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index 6b05846c9bed..97751bacb79c 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -1594,25 +1594,27 @@ int fuse_reverse_inval_entry(struct fuse_conn *fc, u64 parent_nodeid, if (!parent) return -ENOENT; + inode_lock_nested(parent, I_MUTEX_PARENT); if (!S_ISDIR(parent->i_mode)) - goto put_parent; + goto unlock; err = -ENOENT; dir = d_find_alias(parent); if (!dir) - goto put_parent; + goto unlock; - entry = start_removing_noperm(dir, name); + name->hash = full_name_hash(dir, name->name, name->len); + entry = d_lookup(dir, name); dput(dir); - if (IS_ERR(entry)) - goto put_parent; + if (!entry) + goto unlock; fuse_dir_changed(parent); if (!(flags & FUSE_EXPIRE_ONLY)) d_invalidate(entry); fuse_invalidate_entry_cache(entry); - if (child_nodeid != 0) { + if (child_nodeid != 0 && d_really_is_positive(entry)) { inode_lock(d_inode(entry)); if (get_node_id(d_inode(entry)) != child_nodeid) { err = -ENOENT; @@ -1640,9 +1642,10 @@ int fuse_reverse_inval_entry(struct fuse_conn *fc, u64 parent_nodeid, } else { err = 0; } + dput(entry); - end_removing(entry); - put_parent: + unlock: + inode_unlock(parent); iput(parent); return err; } -- cgit v1.2.3 From 9fa4f7a53406430ee9982f2f636a15b338185122 Mon Sep 17 00:00:00 2001 From: Alberto Ruiz Date: Wed, 8 Apr 2026 17:23:40 +0200 Subject: fuse: fix device node leak in cuse_process_init_reply() If device_add() succeeds during CUSE initialization but a subsequent step (cdev_alloc() or cdev_add()) fails, the error path calls put_device() without first calling device_del(). This leaks the devtmpfs entry created by device_add(), leaving a stale /dev/ node that persists until reboot. Since the cuse_conn is never linked into cuse_conntbl on the failure path, cuse_channel_release() sees cc->dev == NULL and skips device_unregister(), so no other code path cleans up the node. This has several consequences: - The device name is permanently poisoned: any subsequent attempt to create a CUSE device with the same name hits the stale sysfs entry, device_add() fails, and the new device is aborted. - The collision manifests as ENODEV returned to userspace with no dmesg diagnostic, making it very difficult to debug. - The failure is self-perpetuating: once a name is leaked, all future attempts with that name fail identically. Fix this by introducing an err_dev label that calls device_del() to undo device_add() before falling through to err_unlock. The existing err_unlock path from a device_add() failure correctly skips device_del() since the device was never added. Testing instructions can be found at the lore link below. Link: https://lore.kernel.org/all/20260408-wip-cuse-leak-fix-v1-0-1c028d575e97@redhat.com/ Signed-off-by: Alberto Ruiz Fixes: 151060ac1314 ("CUSE: implement CUSE - Character device in Userspace") Cc: stable@vger.kernel.org Signed-off-by: Miklos Szeredi --- fs/fuse/cuse.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fs/fuse/cuse.c b/fs/fuse/cuse.c index 174333633471..3d38828af41f 100644 --- a/fs/fuse/cuse.c +++ b/fs/fuse/cuse.c @@ -391,7 +391,7 @@ static void cuse_process_init_reply(struct fuse_mount *fm, rc = -ENOMEM; cdev = cdev_alloc(); if (!cdev) - goto err_unlock; + goto err_dev; cdev->owner = THIS_MODULE; cdev->ops = &cuse_frontend_fops; @@ -417,6 +417,8 @@ out: err_cdev: cdev_del(cdev); +err_dev: + device_del(dev); err_unlock: mutex_unlock(&cuse_lock); put_device(dev); -- cgit v1.2.3 From 31da059891bd3be9c6e59280b8e1777ead90db34 Mon Sep 17 00:00:00 2001 From: Joanne Koong Date: Wed, 8 Apr 2026 10:25:10 -0700 Subject: fuse: fix io-uring background queue dispatch on request completion When a background request completes via the io_uring path, the background queue gets flushed to dispatch pending background requests, but this is done before the connection-level background counters (fc->num_background, fc->active_background) are properly accounted, which may reduce effective queue depth to one. The connection-level counters are decremented in fuse_request_end(), but flush_bg_queue() flushes the /dev/fuse path queue (fc->bg_queue), not the io_uring per-queue bg one, which means pending uring background requests on the queue are never dispatched in this path. Fix this by accounting the connection-level background counters first before flushing the queue's background queue. Since fuse_request_bg_finish() clears FR_BACKGROUND, fuse_request_end() will skip the background cleanup branch entirely, which avoids any double-decrements; it will call the wake_up(&req->waitq) branch but this is effectively a no-op as background requests have no waiters on req->waitq. Reviewed-by: Bernd Schubert Fixes: 857b0263f30e ("fuse: Allow to queue bg requests through io-uring") Cc: stable@vger.kernel.org Signed-off-by: Joanne Koong Signed-off-by: Miklos Szeredi --- fs/fuse/dev.c | 41 ++++++++++++++++++++++++----------------- fs/fuse/dev_uring.c | 1 + fs/fuse/fuse_dev_i.h | 1 + 3 files changed, 26 insertions(+), 17 deletions(-) diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index 12b57a8c6024..916c214798ca 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -448,6 +448,29 @@ static void flush_bg_queue(struct fuse_conn *fc) } } +void fuse_request_bg_finish(struct fuse_conn *fc, struct fuse_req *req) +{ + lockdep_assert_held(&fc->bg_lock); + + clear_bit(FR_BACKGROUND, &req->flags); + if (fc->num_background == fc->max_background) { + fc->blocked = 0; + wake_up(&fc->blocked_waitq); + } else if (!fc->blocked) { + /* + * Wake up next waiter, if any. It's okay to use + * waitqueue_active(), as we've already synced up + * fc->blocked with waiters with the wake_up() call + * above. + */ + if (waitqueue_active(&fc->blocked_waitq)) + wake_up(&fc->blocked_waitq); + } + + fc->num_background--; + fc->active_background--; +} + /* * This function is called when a request is finished. Either a reply * has arrived or it was aborted (and not yet sent) or some error @@ -480,23 +503,7 @@ void fuse_request_end(struct fuse_req *req) WARN_ON(test_bit(FR_SENT, &req->flags)); if (test_bit(FR_BACKGROUND, &req->flags)) { spin_lock(&fc->bg_lock); - clear_bit(FR_BACKGROUND, &req->flags); - if (fc->num_background == fc->max_background) { - fc->blocked = 0; - wake_up(&fc->blocked_waitq); - } else if (!fc->blocked) { - /* - * Wake up next waiter, if any. It's okay to use - * waitqueue_active(), as we've already synced up - * fc->blocked with waiters with the wake_up() call - * above. - */ - if (waitqueue_active(&fc->blocked_waitq)) - wake_up(&fc->blocked_waitq); - } - - fc->num_background--; - fc->active_background--; + fuse_request_bg_finish(fc, req); flush_bg_queue(fc); spin_unlock(&fc->bg_lock); } else { diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c index 7e5fe462b935..8fe788b8ecba 100644 --- a/fs/fuse/dev_uring.c +++ b/fs/fuse/dev_uring.c @@ -90,6 +90,7 @@ static void fuse_uring_req_end(struct fuse_ring_ent *ent, struct fuse_req *req, if (test_bit(FR_BACKGROUND, &req->flags)) { queue->active_background--; spin_lock(&fc->bg_lock); + fuse_request_bg_finish(fc, req); fuse_uring_flush_bg(queue); spin_unlock(&fc->bg_lock); } diff --git a/fs/fuse/fuse_dev_i.h b/fs/fuse/fuse_dev_i.h index 910f883cd090..1853940fe80a 100644 --- a/fs/fuse/fuse_dev_i.h +++ b/fs/fuse/fuse_dev_i.h @@ -77,6 +77,7 @@ unsigned int fuse_req_hash(u64 unique); struct fuse_req *fuse_request_find(struct fuse_pqueue *fpq, u64 unique); void fuse_dev_end_requests(struct list_head *head); +void fuse_request_bg_finish(struct fuse_conn *fc, struct fuse_req *req); void fuse_copy_init(struct fuse_copy_state *cs, bool write, struct iov_iter *iter); -- cgit v1.2.3 From f14348575dec6430c37f1bc9b6c7652bbaa7169f Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Tue, 10 Mar 2026 16:45:20 +0100 Subject: fuse: move request timeout code to a new source file This marks the first step in cleanly separating the transport layer from the filesystem layer. Add "dev.h", which will contain the interface definition for the transport layer. Signed-off-by: Miklos Szeredi --- fs/fuse/Makefile | 2 +- fs/fuse/dev.c | 94 ------------------------------- fs/fuse/dev.h | 10 ++++ fs/fuse/fuse_i.h | 19 ------- fs/fuse/inode.c | 33 +---------- fs/fuse/req_timeout.c | 150 ++++++++++++++++++++++++++++++++++++++++++++++++++ fs/fuse/sysctl.c | 1 + fs/fuse/sysctl.h | 9 +++ 8 files changed, 173 insertions(+), 145 deletions(-) create mode 100644 fs/fuse/dev.h create mode 100644 fs/fuse/req_timeout.c create mode 100644 fs/fuse/sysctl.h diff --git a/fs/fuse/Makefile b/fs/fuse/Makefile index 22ad9538dfc4..30dd1bee931d 100644 --- a/fs/fuse/Makefile +++ b/fs/fuse/Makefile @@ -11,7 +11,7 @@ obj-$(CONFIG_CUSE) += cuse.o obj-$(CONFIG_VIRTIO_FS) += virtiofs.o fuse-y := trace.o # put trace.o first so we see ftrace errors sooner -fuse-y += dev.o dir.o file.o inode.o control.o xattr.o acl.o readdir.o ioctl.o +fuse-y += dev.o dir.o file.o inode.o control.o xattr.o acl.o readdir.o ioctl.o req_timeout.o fuse-y += iomode.o fuse-$(CONFIG_FUSE_DAX) += dax.o fuse-$(CONFIG_FUSE_PASSTHROUGH) += passthrough.o backing.o diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index 916c214798ca..0359a7b10393 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -32,100 +32,6 @@ MODULE_ALIAS("devname:fuse"); static struct kmem_cache *fuse_req_cachep; -const unsigned long fuse_timeout_timer_freq = - secs_to_jiffies(FUSE_TIMEOUT_TIMER_FREQ); - -bool fuse_request_expired(struct fuse_conn *fc, struct list_head *list) -{ - struct fuse_req *req; - - req = list_first_entry_or_null(list, struct fuse_req, list); - if (!req) - return false; - return time_is_before_jiffies(req->create_time + fc->timeout.req_timeout); -} - -static bool fuse_fpq_processing_expired(struct fuse_conn *fc, struct list_head *processing) -{ - int i; - - for (i = 0; i < FUSE_PQ_HASH_SIZE; i++) - if (fuse_request_expired(fc, &processing[i])) - return true; - - return false; -} - -/* - * Check if any requests aren't being completed by the time the request timeout - * elapses. To do so, we: - * - check the fiq pending list - * - check the bg queue - * - check the fpq io and processing lists - * - * To make this fast, we only check against the head request on each list since - * these are generally queued in order of creation time (eg newer requests get - * queued to the tail). We might miss a few edge cases (eg requests transitioning - * between lists, re-sent requests at the head of the pending list having a - * later creation time than other requests on that list, etc.) but that is fine - * since if the request never gets fulfilled, it will eventually be caught. - */ -void fuse_check_timeout(struct work_struct *work) -{ - struct delayed_work *dwork = to_delayed_work(work); - struct fuse_conn *fc = container_of(dwork, struct fuse_conn, - timeout.work); - struct fuse_iqueue *fiq = &fc->iq; - struct fuse_dev *fud; - struct fuse_pqueue *fpq; - bool expired = false; - - if (!atomic_read(&fc->num_waiting)) - goto out; - - spin_lock(&fiq->lock); - expired = fuse_request_expired(fc, &fiq->pending); - spin_unlock(&fiq->lock); - if (expired) - goto abort_conn; - - spin_lock(&fc->bg_lock); - expired = fuse_request_expired(fc, &fc->bg_queue); - spin_unlock(&fc->bg_lock); - if (expired) - goto abort_conn; - - spin_lock(&fc->lock); - if (!fc->connected) { - spin_unlock(&fc->lock); - return; - } - list_for_each_entry(fud, &fc->devices, entry) { - fpq = &fud->pq; - spin_lock(&fpq->lock); - if (fuse_request_expired(fc, &fpq->io) || - fuse_fpq_processing_expired(fc, fpq->processing)) { - spin_unlock(&fpq->lock); - spin_unlock(&fc->lock); - goto abort_conn; - } - - spin_unlock(&fpq->lock); - } - spin_unlock(&fc->lock); - - if (fuse_uring_request_expired(fc)) - goto abort_conn; - -out: - queue_delayed_work(system_percpu_wq, &fc->timeout.work, - fuse_timeout_timer_freq); - return; - -abort_conn: - fuse_abort_conn(fc); -} - static void fuse_request_init(struct fuse_mount *fm, struct fuse_req *req) { INIT_LIST_HEAD(&req->list); diff --git a/fs/fuse/dev.h b/fs/fuse/dev.h new file mode 100644 index 000000000000..f7db15c33cf9 --- /dev/null +++ b/fs/fuse/dev.h @@ -0,0 +1,10 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef _FS_FUSE_DEV_H +#define _FS_FUSE_DEV_H + +struct fuse_conn; + +void fuse_init_server_timeout(struct fuse_conn *fc, unsigned int timeout); + +#endif /* _FS_FUSE_DEV_H */ diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index 17423d4e3cfa..111fb6f3ebc4 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -48,12 +48,6 @@ /** Number of dentries for each connection in the control filesystem */ #define FUSE_CTL_NUM_DENTRIES 5 -/* Frequency (in seconds) of request timeout checks, if opted into */ -#define FUSE_TIMEOUT_TIMER_FREQ 15 - -/** Frequency (in jiffies) of request timeout checks, if opted into */ -extern const unsigned long fuse_timeout_timer_freq; - /* * Dentries invalidation workqueue period, in seconds. The value of this * parameter shall be >= FUSE_DENTRY_INVAL_FREQ_MIN seconds, or 0 (zero), in @@ -63,16 +57,6 @@ extern unsigned inval_wq __read_mostly; /** Maximum of max_pages received in init_out */ extern unsigned int fuse_max_pages_limit; -/* - * Default timeout (in seconds) for the server to reply to a request - * before the connection is aborted, if no timeout was specified on mount. - */ -extern unsigned int fuse_default_req_timeout; -/* - * Max timeout (in seconds) for the server to reply to a request before - * the connection is aborted. - */ -extern unsigned int fuse_max_req_timeout; /** List of active connections */ extern struct list_head fuse_conn_list; @@ -1286,9 +1270,6 @@ void fuse_request_end(struct fuse_req *req); void fuse_abort_conn(struct fuse_conn *fc); void fuse_wait_aborted(struct fuse_conn *fc); -/* Check if any requests timed out */ -void fuse_check_timeout(struct work_struct *work); - void fuse_dentry_tree_init(void); void fuse_dentry_tree_cleanup(void); diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c index deddfffb037f..8c0ad401a0b5 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c @@ -6,6 +6,7 @@ See the file COPYING. */ +#include "dev.h" #include "fuse_i.h" #include "fuse_dev_i.h" #include "dev_uring_i.h" @@ -41,8 +42,6 @@ static int set_global_limit(const char *val, const struct kernel_param *kp); unsigned int fuse_max_pages_limit = 256; /* default is no timeout */ -unsigned int fuse_default_req_timeout; -unsigned int fuse_max_req_timeout; unsigned int max_user_bgreq; module_param_call(max_user_bgreq, set_global_limit, param_get_uint, @@ -1311,34 +1310,6 @@ static void process_init_limits(struct fuse_conn *fc, struct fuse_init_out *arg) spin_unlock(&fc->bg_lock); } -static void set_request_timeout(struct fuse_conn *fc, unsigned int timeout) -{ - fc->timeout.req_timeout = secs_to_jiffies(timeout); - INIT_DELAYED_WORK(&fc->timeout.work, fuse_check_timeout); - queue_delayed_work(system_percpu_wq, &fc->timeout.work, - fuse_timeout_timer_freq); -} - -static void init_server_timeout(struct fuse_conn *fc, unsigned int timeout) -{ - if (!timeout && !fuse_max_req_timeout && !fuse_default_req_timeout) - return; - - if (!timeout) - timeout = fuse_default_req_timeout; - - if (fuse_max_req_timeout) { - if (timeout) - timeout = min(fuse_max_req_timeout, timeout); - else - timeout = fuse_max_req_timeout; - } - - timeout = max(FUSE_TIMEOUT_TIMER_FREQ, timeout); - - set_request_timeout(fc, timeout); -} - struct fuse_init_args { struct fuse_args args; struct fuse_init_in in; @@ -1491,7 +1462,7 @@ static void process_init_reply(struct fuse_mount *fm, struct fuse_args *args, fc->no_flock = 1; } - init_server_timeout(fc, timeout); + fuse_init_server_timeout(fc, timeout); fm->sb->s_bdi->ra_pages = min(fm->sb->s_bdi->ra_pages, ra_pages); diff --git a/fs/fuse/req_timeout.c b/fs/fuse/req_timeout.c new file mode 100644 index 000000000000..64d9b503e6c5 --- /dev/null +++ b/fs/fuse/req_timeout.c @@ -0,0 +1,150 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include "dev.h" +#include "sysctl.h" +#include "fuse_i.h" +#include "fuse_dev_i.h" +#include "dev_uring_i.h" + +/* Frequency (in seconds) of request timeout checks, if opted into */ +#define FUSE_TIMEOUT_TIMER_FREQ 15 + +/* Frequency (in jiffies) of request timeout checks, if opted into */ +static const unsigned long fuse_timeout_timer_freq = + secs_to_jiffies(FUSE_TIMEOUT_TIMER_FREQ); + +/* + * Default timeout (in seconds) for the server to reply to a request + * before the connection is aborted, if no timeout was specified on mount. + * + * Exported via sysctl + */ +unsigned int fuse_default_req_timeout; + +/* + * Max timeout (in seconds) for the server to reply to a request before + * the connection is aborted. + * + * Exported via sysctl + */ +unsigned int fuse_max_req_timeout; + +bool fuse_request_expired(struct fuse_conn *fc, struct list_head *list) +{ + struct fuse_req *req; + + req = list_first_entry_or_null(list, struct fuse_req, list); + if (!req) + return false; + return time_is_before_jiffies(req->create_time + fc->timeout.req_timeout); +} + +static bool fuse_fpq_processing_expired(struct fuse_conn *fc, struct list_head *processing) +{ + int i; + + for (i = 0; i < FUSE_PQ_HASH_SIZE; i++) + if (fuse_request_expired(fc, &processing[i])) + return true; + + return false; +} + +/* + * Check if any requests aren't being completed by the time the request timeout + * elapses. To do so, we: + * - check the fiq pending list + * - check the bg queue + * - check the fpq io and processing lists + * + * To make this fast, we only check against the head request on each list since + * these are generally queued in order of creation time (eg newer requests get + * queued to the tail). We might miss a few edge cases (eg requests transitioning + * between lists, re-sent requests at the head of the pending list having a + * later creation time than other requests on that list, etc.) but that is fine + * since if the request never gets fulfilled, it will eventually be caught. + */ +static void fuse_check_timeout(struct work_struct *work) +{ + struct delayed_work *dwork = to_delayed_work(work); + struct fuse_conn *fc = container_of(dwork, struct fuse_conn, + timeout.work); + struct fuse_iqueue *fiq = &fc->iq; + struct fuse_dev *fud; + struct fuse_pqueue *fpq; + bool expired = false; + + if (!atomic_read(&fc->num_waiting)) + goto out; + + spin_lock(&fiq->lock); + expired = fuse_request_expired(fc, &fiq->pending); + spin_unlock(&fiq->lock); + if (expired) + goto abort_conn; + + spin_lock(&fc->bg_lock); + expired = fuse_request_expired(fc, &fc->bg_queue); + spin_unlock(&fc->bg_lock); + if (expired) + goto abort_conn; + + spin_lock(&fc->lock); + if (!fc->connected) { + spin_unlock(&fc->lock); + return; + } + list_for_each_entry(fud, &fc->devices, entry) { + fpq = &fud->pq; + spin_lock(&fpq->lock); + if (fuse_request_expired(fc, &fpq->io) || + fuse_fpq_processing_expired(fc, fpq->processing)) { + spin_unlock(&fpq->lock); + spin_unlock(&fc->lock); + goto abort_conn; + } + + spin_unlock(&fpq->lock); + } + spin_unlock(&fc->lock); + + if (fuse_uring_request_expired(fc)) + goto abort_conn; + +out: + queue_delayed_work(system_percpu_wq, &fc->timeout.work, + fuse_timeout_timer_freq); + return; + +abort_conn: + fuse_abort_conn(fc); +} + +static void set_request_timeout(struct fuse_conn *fc, unsigned int timeout) +{ + fc->timeout.req_timeout = secs_to_jiffies(timeout); + INIT_DELAYED_WORK(&fc->timeout.work, fuse_check_timeout); + queue_delayed_work(system_percpu_wq, &fc->timeout.work, + fuse_timeout_timer_freq); +} + +void fuse_init_server_timeout(struct fuse_conn *fc, unsigned int timeout) +{ + if (!timeout && !fuse_max_req_timeout && !fuse_default_req_timeout) + return; + + if (!timeout) + timeout = fuse_default_req_timeout; + + if (fuse_max_req_timeout) { + if (timeout) + timeout = min(fuse_max_req_timeout, timeout); + else + timeout = fuse_max_req_timeout; + } + + timeout = max(FUSE_TIMEOUT_TIMER_FREQ, timeout); + + set_request_timeout(fc, timeout); +} + diff --git a/fs/fuse/sysctl.c b/fs/fuse/sysctl.c index e2d921abcb88..74eca5ce9a2c 100644 --- a/fs/fuse/sysctl.c +++ b/fs/fuse/sysctl.c @@ -6,6 +6,7 @@ */ #include +#include "sysctl.h" #include "fuse_i.h" static struct ctl_table_header *fuse_table_header; diff --git a/fs/fuse/sysctl.h b/fs/fuse/sysctl.h new file mode 100644 index 000000000000..948d88417133 --- /dev/null +++ b/fs/fuse/sysctl.h @@ -0,0 +1,9 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef _FS_FUSE_SYSCTL_H +#define _FS_FUSE_SYSCTL_H + +extern unsigned int fuse_default_req_timeout; +extern unsigned int fuse_max_req_timeout; + +#endif /* _FS_FUSE_SYSCTL_H */ -- cgit v1.2.3 From a73a7883b40ea0a123409ef39a072218401ac5d8 Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Tue, 17 Mar 2026 12:00:29 +0100 Subject: fuse: add struct fuse_chan The goal is to separate transport layer stuff out from struct fuse_conn, leaving just the filesystem related members. Add a new object referenced from fuse_conn. This patch just implements the allocation and freeing of this object. Following patches will move transport related members from fuse_conn to fuse_chan. Signed-off-by: Miklos Szeredi --- fs/fuse/cuse.c | 7 ++++++- fs/fuse/dev.c | 13 +++++++++++++ fs/fuse/dev.h | 7 +++++++ fs/fuse/fuse_dev_i.h | 4 ++++ fs/fuse/fuse_i.h | 6 +++++- fs/fuse/inode.c | 10 ++++++++-- fs/fuse/virtio_fs.c | 8 +++++++- 7 files changed, 50 insertions(+), 5 deletions(-) diff --git a/fs/fuse/cuse.c b/fs/fuse/cuse.c index 3d38828af41f..e0616604d81a 100644 --- a/fs/fuse/cuse.c +++ b/fs/fuse/cuse.c @@ -51,6 +51,7 @@ #include #include +#include "dev.h" #include "fuse_i.h" #include "fuse_dev_i.h" @@ -504,8 +505,12 @@ static int cuse_channel_open(struct inode *inode, struct file *file) { struct fuse_dev *fud; struct cuse_conn *cc; + struct fuse_chan *fch __free(fuse_chan_free) = fuse_chan_new(); int rc; + if (!fch) + return -ENOMEM; + /* set up cuse_conn */ cc = kzalloc_obj(*cc); if (!cc) @@ -516,7 +521,7 @@ static int cuse_channel_open(struct inode *inode, struct file *file) * be represented in file->f_cred->user_ns. */ fuse_conn_init(&cc->fc, &cc->fm, file->f_cred->user_ns, - &fuse_dev_fiq_ops, NULL); + &fuse_dev_fiq_ops, NULL, no_free_ptr(fch)); cc->fc.release = cuse_fc_release; fud = fuse_dev_alloc_install(&cc->fc); diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index 0359a7b10393..c3d7eb481974 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -6,6 +6,7 @@ See the file COPYING. */ +#include "dev.h" #include "dev_uring_i.h" #include "fuse_i.h" #include "fuse_dev_i.h" @@ -320,6 +321,18 @@ const struct fuse_iqueue_ops fuse_dev_fiq_ops = { }; EXPORT_SYMBOL_GPL(fuse_dev_fiq_ops); +void fuse_chan_free(struct fuse_chan *fch) +{ + kfree(fch); +} +EXPORT_SYMBOL_GPL(fuse_chan_free); + +struct fuse_chan *fuse_chan_new(void) +{ + return kzalloc_obj(struct fuse_chan); +} +EXPORT_SYMBOL_GPL(fuse_chan_new); + static void fuse_send_one(struct fuse_iqueue *fiq, struct fuse_req *req) { req->in.h.len = sizeof(struct fuse_in_header) + diff --git a/fs/fuse/dev.h b/fs/fuse/dev.h index f7db15c33cf9..70e4a75e6942 100644 --- a/fs/fuse/dev.h +++ b/fs/fuse/dev.h @@ -3,7 +3,14 @@ #ifndef _FS_FUSE_DEV_H #define _FS_FUSE_DEV_H +#include + struct fuse_conn; +struct fuse_chan; + +struct fuse_chan *fuse_chan_new(void); +void fuse_chan_free(struct fuse_chan *fch); +DEFINE_FREE(fuse_chan_free, struct fuse_chan *, if (_T) fuse_chan_free(_T)) void fuse_init_server_timeout(struct fuse_conn *fc, unsigned int timeout); diff --git a/fs/fuse/fuse_dev_i.h b/fs/fuse/fuse_dev_i.h index 1853940fe80a..256d973aa6c0 100644 --- a/fs/fuse/fuse_dev_i.h +++ b/fs/fuse/fuse_dev_i.h @@ -21,6 +21,10 @@ struct fuse_req; struct fuse_iqueue; struct fuse_forget_link; +struct fuse_chan { + /* will move stuff from struct fuse_conn */ +}; + struct fuse_copy_state { struct fuse_req *req; struct iov_iter *iter; diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index 111fb6f3ebc4..deaffe245284 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -668,6 +668,9 @@ struct fuse_conn { /** Input queue */ struct fuse_iqueue iq; + /* transport layer object */ + struct fuse_chan *chan; + /** The next unique kernel file handle */ atomic64_t khctr; @@ -1313,7 +1316,8 @@ void fuse_pqueue_init(struct fuse_pqueue *fpq); */ void fuse_conn_init(struct fuse_conn *fc, struct fuse_mount *fm, struct user_namespace *user_ns, - const struct fuse_iqueue_ops *fiq_ops, void *fiq_priv); + const struct fuse_iqueue_ops *fiq_ops, void *fiq_priv, + struct fuse_chan *fch); /** * Release reference to fuse_conn diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c index 8c0ad401a0b5..65f11108c81a 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c @@ -997,7 +997,7 @@ void fuse_pqueue_init(struct fuse_pqueue *fpq) void fuse_conn_init(struct fuse_conn *fc, struct fuse_mount *fm, struct user_namespace *user_ns, - const struct fuse_iqueue_ops *fiq_ops, void *fiq_priv) + const struct fuse_iqueue_ops *fiq_ops, void *fiq_priv, struct fuse_chan *fch) { memset(fc, 0, sizeof(*fc)); spin_lock_init(&fc->lock); @@ -1035,6 +1035,7 @@ void fuse_conn_init(struct fuse_conn *fc, struct fuse_mount *fm, INIT_LIST_HEAD(&fc->mounts); list_add(&fm->fc_entry, &fc->mounts); fm->fc = fc; + fc->chan = fch; } EXPORT_SYMBOL_GPL(fuse_conn_init); @@ -1043,6 +1044,7 @@ static void delayed_release(struct rcu_head *p) struct fuse_conn *fc = container_of(p, struct fuse_conn, rcu); fuse_uring_destruct(fc); + fuse_chan_free(fc->chan); put_user_ns(fc->user_ns); fc->release(fc); @@ -1983,8 +1985,12 @@ static int fuse_get_tree(struct fs_context *fsc) struct fuse_conn *fc; struct fuse_mount *fm; struct super_block *sb; + struct fuse_chan *fch __free(fuse_chan_free) = fuse_chan_new(); int err; + if (!fch) + return -ENOMEM; + fc = kmalloc_obj(*fc); if (!fc) return -ENOMEM; @@ -1995,7 +2001,7 @@ static int fuse_get_tree(struct fs_context *fsc) return -ENOMEM; } - fuse_conn_init(fc, fm, fsc->user_ns, &fuse_dev_fiq_ops, NULL); + fuse_conn_init(fc, fm, fsc->user_ns, &fuse_dev_fiq_ops, NULL, no_free_ptr(fch)); fc->release = fuse_free_conn; fsc->s_fs_info = fm; diff --git a/fs/fuse/virtio_fs.c b/fs/fuse/virtio_fs.c index 12300651a0f1..f6d41b760210 100644 --- a/fs/fuse/virtio_fs.c +++ b/fs/fuse/virtio_fs.c @@ -19,6 +19,7 @@ #include #include #include +#include "dev.h" #include "fuse_i.h" #include "fuse_dev_i.h" @@ -1683,8 +1684,12 @@ static int virtio_fs_get_tree(struct fs_context *fsc) struct fuse_conn *fc = NULL; struct fuse_mount *fm; unsigned int virtqueue_size; + struct fuse_chan *fch __free(fuse_chan_free) = fuse_chan_new(); int err = -EIO; + if (!fch) + return -ENOMEM; + if (!fsc->source) return invalf(fsc, "No source specified"); @@ -1711,7 +1716,8 @@ static int virtio_fs_get_tree(struct fs_context *fsc) if (!fm) goto out_err; - fuse_conn_init(fc, fm, fsc->user_ns, &virtio_fs_fiq_ops, fs); + fuse_conn_init(fc, fm, fsc->user_ns, &virtio_fs_fiq_ops, fs, no_free_ptr(fch)); + fc->release = fuse_free_conn; fc->delete_stale = true; fc->auto_submounts = true; -- cgit v1.2.3 From b88fb2b92b24b3eee0ea87001c8d9fbf5ba54bb7 Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Tue, 17 Mar 2026 12:08:00 +0100 Subject: fuse: move fuse_iqueue to fuse_chan Move the 'fiq' member from fuse_conn to fuse_chan. Move iqueue related structure definitions and function declarations from "fuse_i.h" to "fuse_dev_i.h". Add a fuse_dev_chan_new() helper, that returns a fuse_chan initialized with the fuse_dev_fiq_ops. Add a fuse_chan_release() function, that calls fiq->ops->release(). Signed-off-by: Miklos Szeredi --- fs/fuse/cuse.c | 6 ++-- fs/fuse/dev.c | 64 +++++++++++++++++++++++++++---------- fs/fuse/dev.h | 2 ++ fs/fuse/dev_uring.c | 2 +- fs/fuse/dev_uring_i.h | 1 + fs/fuse/fuse_dev_i.h | 81 ++++++++++++++++++++++++++++++++++++++++++++++- fs/fuse/fuse_i.h | 87 +-------------------------------------------------- fs/fuse/inode.c | 27 +++------------- fs/fuse/req_timeout.c | 2 +- fs/fuse/virtio_fs.c | 11 ++++--- 10 files changed, 146 insertions(+), 137 deletions(-) diff --git a/fs/fuse/cuse.c b/fs/fuse/cuse.c index e0616604d81a..36215a6b3e3a 100644 --- a/fs/fuse/cuse.c +++ b/fs/fuse/cuse.c @@ -505,7 +505,7 @@ static int cuse_channel_open(struct inode *inode, struct file *file) { struct fuse_dev *fud; struct cuse_conn *cc; - struct fuse_chan *fch __free(fuse_chan_free) = fuse_chan_new(); + struct fuse_chan *fch __free(fuse_chan_free) = fuse_dev_chan_new(); int rc; if (!fch) @@ -520,9 +520,7 @@ static int cuse_channel_open(struct inode *inode, struct file *file) * Limit the cuse channel to requests that can * be represented in file->f_cred->user_ns. */ - fuse_conn_init(&cc->fc, &cc->fm, file->f_cred->user_ns, - &fuse_dev_fiq_ops, NULL, no_free_ptr(fch)); - + fuse_conn_init(&cc->fc, &cc->fm, file->f_cred->user_ns, no_free_ptr(fch)); cc->fc.release = cuse_fc_release; fud = fuse_dev_alloc_install(&cc->fc); fuse_conn_put(&cc->fc); diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index c3d7eb481974..0c0906a2372a 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -314,12 +314,32 @@ static void fuse_dev_queue_req(struct fuse_iqueue *fiq, struct fuse_req *req) } } -const struct fuse_iqueue_ops fuse_dev_fiq_ops = { +static const struct fuse_iqueue_ops fuse_dev_fiq_ops = { .send_forget = fuse_dev_queue_forget, .send_interrupt = fuse_dev_queue_interrupt, .send_req = fuse_dev_queue_req, }; -EXPORT_SYMBOL_GPL(fuse_dev_fiq_ops); + +void fuse_iqueue_init(struct fuse_iqueue *fiq, const struct fuse_iqueue_ops *ops, void *priv) +{ + spin_lock_init(&fiq->lock); + init_waitqueue_head(&fiq->waitq); + INIT_LIST_HEAD(&fiq->pending); + INIT_LIST_HEAD(&fiq->interrupts); + fiq->forget_list_tail = &fiq->forget_list_head; + fiq->connected = 1; + fiq->ops = ops; + fiq->priv = priv; +} +EXPORT_SYMBOL_GPL(fuse_iqueue_init); + +void fuse_chan_release(struct fuse_chan *fch) +{ + struct fuse_iqueue *fiq = &fch->iq; + + if (fiq->ops->release) + fiq->ops->release(fiq); +} void fuse_chan_free(struct fuse_chan *fch) { @@ -333,6 +353,18 @@ struct fuse_chan *fuse_chan_new(void) } EXPORT_SYMBOL_GPL(fuse_chan_new); +struct fuse_chan *fuse_dev_chan_new(void) +{ + struct fuse_chan *fch = fuse_chan_new(); + if (!fch) + return NULL; + + fuse_iqueue_init(&fch->iq, &fuse_dev_fiq_ops, NULL); + + return fch; +} +EXPORT_SYMBOL_GPL(fuse_dev_chan_new); + static void fuse_send_one(struct fuse_iqueue *fiq, struct fuse_req *req) { req->in.h.len = sizeof(struct fuse_in_header) + @@ -344,7 +376,7 @@ static void fuse_send_one(struct fuse_iqueue *fiq, struct fuse_req *req) void fuse_queue_forget(struct fuse_conn *fc, struct fuse_forget_link *forget, u64 nodeid, u64 nlookup) { - struct fuse_iqueue *fiq = &fc->iq; + struct fuse_iqueue *fiq = &fc->chan->iq; forget->forget_one.nodeid = nodeid; forget->forget_one.nlookup = nlookup; @@ -354,7 +386,7 @@ void fuse_queue_forget(struct fuse_conn *fc, struct fuse_forget_link *forget, static void flush_bg_queue(struct fuse_conn *fc) { - struct fuse_iqueue *fiq = &fc->iq; + struct fuse_iqueue *fiq = &fc->chan->iq; while (fc->active_background < fc->max_background && !list_empty(&fc->bg_queue)) { @@ -402,7 +434,7 @@ void fuse_request_end(struct fuse_req *req) { struct fuse_mount *fm = req->fm; struct fuse_conn *fc = fm->fc; - struct fuse_iqueue *fiq = &fc->iq; + struct fuse_iqueue *fiq = &fc->chan->iq; if (test_and_set_bit(FR_FINISHED, &req->flags)) goto put_request; @@ -439,7 +471,7 @@ EXPORT_SYMBOL_GPL(fuse_request_end); static int queue_interrupt(struct fuse_req *req) { - struct fuse_iqueue *fiq = &req->fm->fc->iq; + struct fuse_iqueue *fiq = &req->fm->fc->chan->iq; /* Check for we've sent request to interrupt this req */ if (unlikely(!test_bit(FR_INTERRUPTED, &req->flags))) @@ -471,7 +503,7 @@ bool fuse_remove_pending_req(struct fuse_req *req, spinlock_t *lock) static void request_wait_answer(struct fuse_req *req) { struct fuse_conn *fc = req->fm->fc; - struct fuse_iqueue *fiq = &fc->iq; + struct fuse_iqueue *fiq = &fc->chan->iq; int err; if (!fc->no_interrupt) { @@ -519,7 +551,7 @@ static void request_wait_answer(struct fuse_req *req) static void __fuse_request_send(struct fuse_req *req) { - struct fuse_iqueue *fiq = &req->fm->fc->iq; + struct fuse_iqueue *fiq = &req->fm->fc->chan->iq; BUG_ON(test_bit(FR_BACKGROUND, &req->flags)); @@ -638,7 +670,7 @@ ssize_t __fuse_simple_request(struct mnt_idmap *idmap, static bool fuse_request_queue_background_uring(struct fuse_conn *fc, struct fuse_req *req) { - struct fuse_iqueue *fiq = &fc->iq; + struct fuse_iqueue *fiq = &fc->chan->iq; req->in.h.len = sizeof(struct fuse_in_header) + fuse_len_args(req->args->in_numargs, @@ -717,7 +749,7 @@ static int fuse_simple_notify_reply(struct fuse_mount *fm, struct fuse_args *args, u64 unique) { struct fuse_req *req; - struct fuse_iqueue *fiq = &fm->fc->iq; + struct fuse_iqueue *fiq = &fm->fc->chan->iq; req = fuse_get_req(&invalid_mnt_idmap, fm, false); if (IS_ERR(req)) @@ -1331,7 +1363,7 @@ static ssize_t fuse_dev_do_read(struct fuse_dev *fud, struct file *file, { ssize_t err; struct fuse_conn *fc = fud->fc; - struct fuse_iqueue *fiq = &fc->iq; + struct fuse_iqueue *fiq = &fc->chan->iq; struct fuse_pqueue *fpq = &fud->pq; struct fuse_req *req; struct fuse_args *args; @@ -1915,7 +1947,7 @@ static void fuse_resend(struct fuse_conn *fc) { struct fuse_dev *fud; struct fuse_req *req, *next; - struct fuse_iqueue *fiq = &fc->iq; + struct fuse_iqueue *fiq = &fc->chan->iq; LIST_HEAD(to_queue); unsigned int i; @@ -2327,7 +2359,7 @@ static __poll_t fuse_dev_poll(struct file *file, poll_table *wait) if (IS_ERR(fud)) return EPOLLERR; - fiq = &fud->fc->iq; + fiq = &fud->fc->chan->iq; poll_wait(file, &fiq->waitq, wait); spin_lock(&fiq->lock); @@ -2388,7 +2420,7 @@ static void end_polls(struct fuse_conn *fc) */ void fuse_abort_conn(struct fuse_conn *fc) { - struct fuse_iqueue *fiq = &fc->iq; + struct fuse_iqueue *fiq = &fc->chan->iq; spin_lock(&fc->lock); if (fc->connected) { @@ -2496,7 +2528,7 @@ int fuse_dev_release(struct inode *inode, struct file *file) spin_unlock(&fc->lock); if (last) { - WARN_ON(fc->iq.fasync != NULL); + WARN_ON(fc->chan->iq.fasync != NULL); fuse_abort_conn(fc); } fuse_conn_put(fc); @@ -2514,7 +2546,7 @@ static int fuse_dev_fasync(int fd, struct file *file, int on) return PTR_ERR(fud); /* No locking - fasync_helper does its own locking */ - return fasync_helper(fd, file, on, &fud->fc->iq.fasync); + return fasync_helper(fd, file, on, &fud->fc->chan->iq.fasync); } static long fuse_dev_ioctl_clone(struct file *file, __u32 __user *argp) diff --git a/fs/fuse/dev.h b/fs/fuse/dev.h index 70e4a75e6942..d5ccfae80115 100644 --- a/fs/fuse/dev.h +++ b/fs/fuse/dev.h @@ -9,6 +9,8 @@ struct fuse_conn; struct fuse_chan; struct fuse_chan *fuse_chan_new(void); +struct fuse_chan *fuse_dev_chan_new(void); +void fuse_chan_release(struct fuse_chan *fch); void fuse_chan_free(struct fuse_chan *fch); DEFINE_FREE(fuse_chan_free, struct fuse_chan *, if (_T) fuse_chan_free(_T)) diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c index 8fe788b8ecba..02b138727c9b 100644 --- a/fs/fuse/dev_uring.c +++ b/fs/fuse/dev_uring.c @@ -1006,7 +1006,7 @@ static int fuse_uring_do_register(struct fuse_ring_ent *ent, struct fuse_ring_queue *queue = ent->queue; struct fuse_ring *ring = queue->ring; struct fuse_conn *fc = ring->fc; - struct fuse_iqueue *fiq = &fc->iq; + struct fuse_iqueue *fiq = &fc->chan->iq; spin_lock(&fc->lock); /* abort teardown path is running or has run */ diff --git a/fs/fuse/dev_uring_i.h b/fs/fuse/dev_uring_i.h index 054ad4fdd749..e60057b5dbf8 100644 --- a/fs/fuse/dev_uring_i.h +++ b/fs/fuse/dev_uring_i.h @@ -8,6 +8,7 @@ #define _FS_FUSE_DEV_URING_I_H #include "fuse_i.h" +#include "fuse_dev_i.h" #ifdef CONFIG_FUSE_IO_URING diff --git a/fs/fuse/fuse_dev_i.h b/fs/fuse/fuse_dev_i.h index 256d973aa6c0..7c607a49b1be 100644 --- a/fs/fuse/fuse_dev_i.h +++ b/fs/fuse/fuse_dev_i.h @@ -21,8 +21,75 @@ struct fuse_req; struct fuse_iqueue; struct fuse_forget_link; +/** + * Input queue callbacks + * + * Input queue signalling is device-specific. For example, the /dev/fuse file + * uses fiq->waitq and fasync to wake processes that are waiting on queue + * readiness. These callbacks allow other device types to respond to input + * queue activity. + */ +struct fuse_iqueue_ops { + /** + * Send one forget + */ + void (*send_forget)(struct fuse_iqueue *fiq, struct fuse_forget_link *link); + + /** + * Send interrupt for request + */ + void (*send_interrupt)(struct fuse_iqueue *fiq, struct fuse_req *req); + + /** + * Send one request + */ + void (*send_req)(struct fuse_iqueue *fiq, struct fuse_req *req); + + /** + * Clean up when fuse_iqueue is destroyed + */ + void (*release)(struct fuse_iqueue *fiq); +}; + +struct fuse_iqueue { + /** Connection established */ + unsigned connected; + + /** Lock protecting accesses to members of this structure */ + spinlock_t lock; + + /** Readers of the connection are waiting on this */ + wait_queue_head_t waitq; + + /** The next unique request id */ + u64 reqctr; + + /** The list of pending requests */ + struct list_head pending; + + /** Pending interrupts */ + struct list_head interrupts; + + /** Queue of pending forgets */ + struct fuse_forget_link forget_list_head; + struct fuse_forget_link *forget_list_tail; + + /** Batching of FORGET requests (positive indicates FORGET batch) */ + int forget_batch; + + /** O_ASYNC requests */ + struct fasync_struct *fasync; + + /** Device-specific callbacks */ + const struct fuse_iqueue_ops *ops; + + /** Device-specific state */ + void *priv; +}; + struct fuse_chan { - /* will move stuff from struct fuse_conn */ + /** Input queue */ + struct fuse_iqueue iq; }; struct fuse_copy_state { @@ -75,6 +142,8 @@ static inline struct fuse_dev *__fuse_get_dev(struct file *file) return fud; } +void fuse_iqueue_init(struct fuse_iqueue *fiq, const struct fuse_iqueue_ops *ops, void *priv); + struct fuse_dev *fuse_get_dev(struct file *file); unsigned int fuse_req_hash(u64 unique); @@ -98,5 +167,15 @@ bool fuse_remove_pending_req(struct fuse_req *req, spinlock_t *lock); bool fuse_request_expired(struct fuse_conn *fc, struct list_head *list); +/** + * Assign a unique id to a fuse request + */ +void fuse_request_assign_unique(struct fuse_iqueue *fiq, struct fuse_req *req); + +/** + * Get the next unique ID for a request + */ +u64 fuse_get_unique(struct fuse_iqueue *fiq); + #endif diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index deaffe245284..29126f5cb66c 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -469,77 +469,6 @@ struct fuse_req { unsigned long create_time; }; -struct fuse_iqueue; - -/** - * Input queue callbacks - * - * Input queue signalling is device-specific. For example, the /dev/fuse file - * uses fiq->waitq and fasync to wake processes that are waiting on queue - * readiness. These callbacks allow other device types to respond to input - * queue activity. - */ -struct fuse_iqueue_ops { - /** - * Send one forget - */ - void (*send_forget)(struct fuse_iqueue *fiq, struct fuse_forget_link *link); - - /** - * Send interrupt for request - */ - void (*send_interrupt)(struct fuse_iqueue *fiq, struct fuse_req *req); - - /** - * Send one request - */ - void (*send_req)(struct fuse_iqueue *fiq, struct fuse_req *req); - - /** - * Clean up when fuse_iqueue is destroyed - */ - void (*release)(struct fuse_iqueue *fiq); -}; - -/** /dev/fuse input queue operations */ -extern const struct fuse_iqueue_ops fuse_dev_fiq_ops; - -struct fuse_iqueue { - /** Connection established */ - unsigned connected; - - /** Lock protecting accesses to members of this structure */ - spinlock_t lock; - - /** Readers of the connection are waiting on this */ - wait_queue_head_t waitq; - - /** The next unique request id */ - u64 reqctr; - - /** The list of pending requests */ - struct list_head pending; - - /** Pending interrupts */ - struct list_head interrupts; - - /** Queue of pending forgets */ - struct fuse_forget_link forget_list_head; - struct fuse_forget_link *forget_list_tail; - - /** Batching of FORGET requests (positive indicates FORGET batch) */ - int forget_batch; - - /** O_ASYNC requests */ - struct fasync_struct *fasync; - - /** Device-specific callbacks */ - const struct fuse_iqueue_ops *ops; - - /** Device-specific state */ - void *priv; -}; - #define FUSE_PQ_HASH_BITS 8 #define FUSE_PQ_HASH_SIZE (1 << FUSE_PQ_HASH_BITS) @@ -665,9 +594,6 @@ struct fuse_conn { /** Constrain ->max_pages to this value during feature negotiation */ unsigned int max_pages_limit; - /** Input queue */ - struct fuse_iqueue iq; - /* transport layer object */ struct fuse_chan *chan; @@ -1259,11 +1185,6 @@ static inline ssize_t fuse_simple_idmap_request(struct mnt_idmap *idmap, int fuse_simple_background(struct fuse_mount *fm, struct fuse_args *args, gfp_t gfp_flags); -/** - * Assign a unique id to a fuse request - */ -void fuse_request_assign_unique(struct fuse_iqueue *fiq, struct fuse_req *req); - /** * End a finished request */ @@ -1315,9 +1236,7 @@ void fuse_pqueue_init(struct fuse_pqueue *fpq); * Initialize fuse_conn */ void fuse_conn_init(struct fuse_conn *fc, struct fuse_mount *fm, - struct user_namespace *user_ns, - const struct fuse_iqueue_ops *fiq_ops, void *fiq_priv, - struct fuse_chan *fch); + struct user_namespace *user_ns, struct fuse_chan *fch); /** * Release reference to fuse_conn @@ -1484,10 +1403,6 @@ int fuse_readdir(struct file *file, struct dir_context *ctx); */ unsigned int fuse_len_args(unsigned int numargs, struct fuse_arg *args); -/** - * Get the next unique ID for a request - */ -u64 fuse_get_unique(struct fuse_iqueue *fiq); void fuse_free_conn(struct fuse_conn *fc); /* dax.c */ diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c index 65f11108c81a..2ee9320964fa 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c @@ -969,21 +969,6 @@ static int fuse_show_options(struct seq_file *m, struct dentry *root) return 0; } -static void fuse_iqueue_init(struct fuse_iqueue *fiq, - const struct fuse_iqueue_ops *ops, - void *priv) -{ - memset(fiq, 0, sizeof(struct fuse_iqueue)); - spin_lock_init(&fiq->lock); - init_waitqueue_head(&fiq->waitq); - INIT_LIST_HEAD(&fiq->pending); - INIT_LIST_HEAD(&fiq->interrupts); - fiq->forget_list_tail = &fiq->forget_list_head; - fiq->connected = 1; - fiq->ops = ops; - fiq->priv = priv; -} - void fuse_pqueue_init(struct fuse_pqueue *fpq) { unsigned int i; @@ -996,8 +981,7 @@ void fuse_pqueue_init(struct fuse_pqueue *fpq) } void fuse_conn_init(struct fuse_conn *fc, struct fuse_mount *fm, - struct user_namespace *user_ns, - const struct fuse_iqueue_ops *fiq_ops, void *fiq_priv, struct fuse_chan *fch) + struct user_namespace *user_ns, struct fuse_chan *fch) { memset(fc, 0, sizeof(*fc)); spin_lock_init(&fc->lock); @@ -1007,7 +991,6 @@ void fuse_conn_init(struct fuse_conn *fc, struct fuse_mount *fm, atomic_set(&fc->epoch, 1); INIT_WORK(&fc->epoch_work, fuse_epoch_work); init_waitqueue_head(&fc->blocked_waitq); - fuse_iqueue_init(&fc->iq, fiq_ops, fiq_priv); INIT_LIST_HEAD(&fc->bg_queue); INIT_LIST_HEAD(&fc->entry); INIT_LIST_HEAD(&fc->devices); @@ -1052,7 +1035,6 @@ static void delayed_release(struct rcu_head *p) void fuse_conn_put(struct fuse_conn *fc) { - struct fuse_iqueue *fiq = &fc->iq; struct fuse_sync_bucket *bucket; if (!refcount_dec_and_test(&fc->count)) @@ -1063,8 +1045,7 @@ void fuse_conn_put(struct fuse_conn *fc) if (fc->timeout.req_timeout) cancel_delayed_work_sync(&fc->timeout.work); cancel_work_sync(&fc->epoch_work); - if (fiq->ops->release) - fiq->ops->release(fiq); + fuse_chan_release(fc->chan); put_pid_ns(fc->pid_ns); bucket = rcu_dereference_protected(fc->curr_bucket, 1); if (bucket) { @@ -1985,7 +1966,7 @@ static int fuse_get_tree(struct fs_context *fsc) struct fuse_conn *fc; struct fuse_mount *fm; struct super_block *sb; - struct fuse_chan *fch __free(fuse_chan_free) = fuse_chan_new(); + struct fuse_chan *fch __free(fuse_chan_free) = fuse_dev_chan_new(); int err; if (!fch) @@ -2001,7 +1982,7 @@ static int fuse_get_tree(struct fs_context *fsc) return -ENOMEM; } - fuse_conn_init(fc, fm, fsc->user_ns, &fuse_dev_fiq_ops, NULL, no_free_ptr(fch)); + fuse_conn_init(fc, fm, fsc->user_ns, no_free_ptr(fch)); fc->release = fuse_free_conn; fsc->s_fs_info = fm; diff --git a/fs/fuse/req_timeout.c b/fs/fuse/req_timeout.c index 64d9b503e6c5..5357a2d63b3f 100644 --- a/fs/fuse/req_timeout.c +++ b/fs/fuse/req_timeout.c @@ -69,7 +69,7 @@ static void fuse_check_timeout(struct work_struct *work) struct delayed_work *dwork = to_delayed_work(work); struct fuse_conn *fc = container_of(dwork, struct fuse_conn, timeout.work); - struct fuse_iqueue *fiq = &fc->iq; + struct fuse_iqueue *fiq = &fc->chan->iq; struct fuse_dev *fud; struct fuse_pqueue *fpq; bool expired = false; diff --git a/fs/fuse/virtio_fs.c b/fs/fuse/virtio_fs.c index f6d41b760210..d688a2a95753 100644 --- a/fs/fuse/virtio_fs.c +++ b/fs/fuse/virtio_fs.c @@ -1563,7 +1563,7 @@ static int virtio_fs_fill_super(struct super_block *sb, struct fs_context *fsc) { struct fuse_mount *fm = get_fuse_mount_super(sb); struct fuse_conn *fc = fm->fc; - struct virtio_fs *fs = fc->iq.priv; + struct virtio_fs *fs = fc->chan->iq.priv; struct fuse_fs_context *ctx = fsc->fs_private; unsigned int i; int err; @@ -1626,7 +1626,7 @@ err: static void virtio_fs_conn_destroy(struct fuse_mount *fm) { struct fuse_conn *fc = fm->fc; - struct virtio_fs *vfs = fc->iq.priv; + struct virtio_fs *vfs = fc->chan->iq.priv; struct virtio_fs_vq *fsvq = &vfs->vqs[VQ_HIPRIO]; /* Stop dax worker. Soon evict_inodes() will be called which @@ -1674,7 +1674,7 @@ static int virtio_fs_test_super(struct super_block *sb, struct fuse_mount *fsc_fm = fsc->s_fs_info; struct fuse_mount *sb_fm = get_fuse_mount_super(sb); - return fsc_fm->fc->iq.priv == sb_fm->fc->iq.priv; + return fsc_fm->fc->chan->iq.priv == sb_fm->fc->chan->iq.priv; } static int virtio_fs_get_tree(struct fs_context *fsc) @@ -1694,7 +1694,7 @@ static int virtio_fs_get_tree(struct fs_context *fsc) return invalf(fsc, "No source specified"); /* This gets a reference on virtio_fs object. This ptr gets installed - * in fc->iq->priv. Once fuse_conn is going away, it calls ->put() + * in chan->iq->priv. Once fuse_conn is going away, it calls ->put() * to drop the reference to this object. */ fs = virtio_fs_find_instance(fsc->source); @@ -1716,7 +1716,8 @@ static int virtio_fs_get_tree(struct fs_context *fsc) if (!fm) goto out_err; - fuse_conn_init(fc, fm, fsc->user_ns, &virtio_fs_fiq_ops, fs, no_free_ptr(fch)); + fuse_iqueue_init(&fch->iq, &virtio_fs_fiq_ops, fs); + fuse_conn_init(fc, fm, fsc->user_ns, no_free_ptr(fch)); fc->release = fuse_free_conn; fc->delete_stale = true; -- cgit v1.2.3 From d8189630a1a1468a8702e252dd93e36d6ec8121e Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Tue, 17 Mar 2026 12:44:31 +0100 Subject: fuse: move fuse_dev and fuse_pqueue to dev.c Move function definitions to dev.c, struct definitions to fuse_dev_i.h. Signed-off-by: Miklos Szeredi --- fs/fuse/dev.c | 94 ++++++++++++++++++++++++++++++++++++++++++++++++++++ fs/fuse/dev.h | 4 +++ fs/fuse/fuse_dev_i.h | 46 +++++++++++++++++++++++++ fs/fuse/fuse_i.h | 46 ------------------------- fs/fuse/inode.c | 94 ---------------------------------------------------- 5 files changed, 144 insertions(+), 140 deletions(-) diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index 0c0906a2372a..7339959fa8d3 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -365,6 +365,100 @@ struct fuse_chan *fuse_dev_chan_new(void) } EXPORT_SYMBOL_GPL(fuse_dev_chan_new); +void fuse_pqueue_init(struct fuse_pqueue *fpq) +{ + unsigned int i; + + spin_lock_init(&fpq->lock); + for (i = 0; i < FUSE_PQ_HASH_SIZE; i++) + INIT_LIST_HEAD(&fpq->processing[i]); + INIT_LIST_HEAD(&fpq->io); + fpq->connected = 1; +} + +struct fuse_dev *fuse_dev_alloc(void) +{ + struct fuse_dev *fud; + struct list_head *pq; + + fud = kzalloc_obj(struct fuse_dev); + if (!fud) + return NULL; + + refcount_set(&fud->ref, 1); + pq = kzalloc_objs(struct list_head, FUSE_PQ_HASH_SIZE); + if (!pq) { + kfree(fud); + return NULL; + } + + fud->pq.processing = pq; + fuse_pqueue_init(&fud->pq); + + return fud; +} +EXPORT_SYMBOL_GPL(fuse_dev_alloc); + +void fuse_dev_install(struct fuse_dev *fud, struct fuse_conn *fc) +{ + struct fuse_conn *old_fc; + + spin_lock(&fc->lock); + /* + * Pairs with: + * - xchg() in fuse_dev_release() + * - smp_load_acquire() in fuse_dev_fc_get() + */ + old_fc = cmpxchg(&fud->fc, NULL, fc); + if (old_fc) { + /* + * failed to set fud->fc because + * - it was already set to a different fc + * - it was set to disconneted + */ + fc->connected = 0; + } else { + list_add_tail(&fud->entry, &fc->devices); + fuse_conn_get(fc); + } + spin_unlock(&fc->lock); +} +EXPORT_SYMBOL_GPL(fuse_dev_install); + +struct fuse_dev *fuse_dev_alloc_install(struct fuse_conn *fc) +{ + struct fuse_dev *fud; + + fud = fuse_dev_alloc(); + if (!fud) + return NULL; + + fuse_dev_install(fud, fc); + return fud; +} +EXPORT_SYMBOL_GPL(fuse_dev_alloc_install); + +void fuse_dev_put(struct fuse_dev *fud) +{ + struct fuse_conn *fc; + + if (!refcount_dec_and_test(&fud->ref)) + return; + + fc = fuse_dev_fc_get(fud); + if (fc && fc != FUSE_DEV_FC_DISCONNECTED) { + /* This is the virtiofs case (fuse_dev_release() not called) */ + spin_lock(&fc->lock); + list_del(&fud->entry); + spin_unlock(&fc->lock); + + fuse_conn_put(fc); + } + kfree(fud->pq.processing); + kfree(fud); +} +EXPORT_SYMBOL_GPL(fuse_dev_put); + static void fuse_send_one(struct fuse_iqueue *fiq, struct fuse_req *req) { req->in.h.len = sizeof(struct fuse_in_header) + diff --git a/fs/fuse/dev.h b/fs/fuse/dev.h index d5ccfae80115..1f8f2cf1ecfd 100644 --- a/fs/fuse/dev.h +++ b/fs/fuse/dev.h @@ -7,6 +7,7 @@ struct fuse_conn; struct fuse_chan; +struct fuse_dev; struct fuse_chan *fuse_chan_new(void); struct fuse_chan *fuse_dev_chan_new(void); @@ -14,6 +15,9 @@ void fuse_chan_release(struct fuse_chan *fch); void fuse_chan_free(struct fuse_chan *fch); DEFINE_FREE(fuse_chan_free, struct fuse_chan *, if (_T) fuse_chan_free(_T)) +void fuse_dev_install(struct fuse_dev *fud, struct fuse_conn *fc); +void fuse_dev_put(struct fuse_dev *fud); + void fuse_init_server_timeout(struct fuse_conn *fc, unsigned int timeout); #endif /* _FS_FUSE_DEV_H */ diff --git a/fs/fuse/fuse_dev_i.h b/fs/fuse/fuse_dev_i.h index 7c607a49b1be..6afc9bb608a9 100644 --- a/fs/fuse/fuse_dev_i.h +++ b/fs/fuse/fuse_dev_i.h @@ -92,6 +92,43 @@ struct fuse_chan { struct fuse_iqueue iq; }; +#define FUSE_PQ_HASH_BITS 8 +#define FUSE_PQ_HASH_SIZE (1 << FUSE_PQ_HASH_BITS) + +struct fuse_pqueue { + /** Connection established */ + unsigned connected; + + /** Lock protecting accessess to members of this structure */ + spinlock_t lock; + + /** Hash table of requests being processed */ + struct list_head *processing; + + /** The list of requests under I/O */ + struct list_head io; +}; + +/** + * Fuse device instance + */ +struct fuse_dev { + /** Reference count of this object */ + refcount_t ref; + + /** Issue FUSE_INIT synchronously */ + bool sync_init; + + /** Fuse connection for this device */ + struct fuse_conn *fc; + + /** Processing queue */ + struct fuse_pqueue pq; + + /** list entry on fc->devices */ + struct list_head entry; +}; + struct fuse_copy_state { struct fuse_req *req; struct iov_iter *iter; @@ -177,5 +214,14 @@ void fuse_request_assign_unique(struct fuse_iqueue *fiq, struct fuse_req *req); */ u64 fuse_get_unique(struct fuse_iqueue *fiq); +struct fuse_dev *fuse_dev_alloc_install(struct fuse_conn *fc); +struct fuse_dev *fuse_dev_alloc(void); + +/** + * Initialize the fuse processing queue + */ +void fuse_pqueue_init(struct fuse_pqueue *fpq); + + #endif diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index 29126f5cb66c..fd5f741693ca 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -469,43 +469,6 @@ struct fuse_req { unsigned long create_time; }; -#define FUSE_PQ_HASH_BITS 8 -#define FUSE_PQ_HASH_SIZE (1 << FUSE_PQ_HASH_BITS) - -struct fuse_pqueue { - /** Connection established */ - unsigned connected; - - /** Lock protecting accessess to members of this structure */ - spinlock_t lock; - - /** Hash table of requests being processed */ - struct list_head *processing; - - /** The list of requests under I/O */ - struct list_head io; -}; - -/** - * Fuse device instance - */ -struct fuse_dev { - /** Reference count of this object */ - refcount_t ref; - - /** Issue FUSE_INIT synchronously */ - bool sync_init; - - /** Fuse connection for this device */ - struct fuse_conn *fc; - - /** Processing queue */ - struct fuse_pqueue pq; - - /** list entry on fc->devices */ - struct list_head entry; -}; - enum fuse_dax_mode { FUSE_DAX_INODE_DEFAULT, /* default */ FUSE_DAX_ALWAYS, /* "-o dax=always" */ @@ -1227,11 +1190,6 @@ void fuse_change_entry_timeout(struct dentry *entry, struct fuse_entry_out *o); */ struct fuse_conn *fuse_conn_get(struct fuse_conn *fc); -/** - * Initialize the fuse processing queue - */ -void fuse_pqueue_init(struct fuse_pqueue *fpq); - /** * Initialize fuse_conn */ @@ -1243,10 +1201,6 @@ void fuse_conn_init(struct fuse_conn *fc, struct fuse_mount *fm, */ void fuse_conn_put(struct fuse_conn *fc); -struct fuse_dev *fuse_dev_alloc_install(struct fuse_conn *fc); -struct fuse_dev *fuse_dev_alloc(void); -void fuse_dev_install(struct fuse_dev *fud, struct fuse_conn *fc); -void fuse_dev_put(struct fuse_dev *fud); int fuse_send_init(struct fuse_mount *fm); /** diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c index 2ee9320964fa..48a836b1ccd3 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c @@ -969,17 +969,6 @@ static int fuse_show_options(struct seq_file *m, struct dentry *root) return 0; } -void fuse_pqueue_init(struct fuse_pqueue *fpq) -{ - unsigned int i; - - spin_lock_init(&fpq->lock); - for (i = 0; i < FUSE_PQ_HASH_SIZE; i++) - INIT_LIST_HEAD(&fpq->processing[i]); - INIT_LIST_HEAD(&fpq->io); - fpq->connected = 1; -} - void fuse_conn_init(struct fuse_conn *fc, struct fuse_mount *fm, struct user_namespace *user_ns, struct fuse_chan *fch) { @@ -1597,89 +1586,6 @@ static int fuse_bdi_init(struct fuse_conn *fc, struct super_block *sb) return 0; } -struct fuse_dev *fuse_dev_alloc(void) -{ - struct fuse_dev *fud; - struct list_head *pq; - - fud = kzalloc_obj(struct fuse_dev); - if (!fud) - return NULL; - - refcount_set(&fud->ref, 1); - pq = kzalloc_objs(struct list_head, FUSE_PQ_HASH_SIZE); - if (!pq) { - kfree(fud); - return NULL; - } - - fud->pq.processing = pq; - fuse_pqueue_init(&fud->pq); - - return fud; -} -EXPORT_SYMBOL_GPL(fuse_dev_alloc); - -void fuse_dev_install(struct fuse_dev *fud, struct fuse_conn *fc) -{ - struct fuse_conn *old_fc; - - spin_lock(&fc->lock); - /* - * Pairs with: - * - xchg() in fuse_dev_release() - * - smp_load_acquire() in fuse_dev_fc_get() - */ - old_fc = cmpxchg(&fud->fc, NULL, fc); - if (old_fc) { - /* - * failed to set fud->fc because - * - it was already set to a different fc - * - it was set to disconneted - */ - fc->connected = 0; - } else { - list_add_tail(&fud->entry, &fc->devices); - fuse_conn_get(fc); - } - spin_unlock(&fc->lock); -} -EXPORT_SYMBOL_GPL(fuse_dev_install); - -struct fuse_dev *fuse_dev_alloc_install(struct fuse_conn *fc) -{ - struct fuse_dev *fud; - - fud = fuse_dev_alloc(); - if (!fud) - return NULL; - - fuse_dev_install(fud, fc); - return fud; -} -EXPORT_SYMBOL_GPL(fuse_dev_alloc_install); - -void fuse_dev_put(struct fuse_dev *fud) -{ - struct fuse_conn *fc; - - if (!refcount_dec_and_test(&fud->ref)) - return; - - fc = fuse_dev_fc_get(fud); - if (fc && fc != FUSE_DEV_FC_DISCONNECTED) { - /* This is the virtiofs case (fuse_dev_release() not called) */ - spin_lock(&fc->lock); - list_del(&fud->entry); - spin_unlock(&fc->lock); - - fuse_conn_put(fc); - } - kfree(fud->pq.processing); - kfree(fud); -} -EXPORT_SYMBOL_GPL(fuse_dev_put); - static void fuse_fill_attr_from_inode(struct fuse_attr *attr, const struct fuse_inode *fi) { -- cgit v1.2.3 From 4e0de84063159aa1804120c6c5493bd05be35adf Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Tue, 17 Mar 2026 13:09:20 +0100 Subject: fuse: move 'devices' member from fuse_conn to fuse_chan This belongs in the transport layer. Signed-off-by: Miklos Szeredi --- fs/fuse/dev.c | 17 ++++++++++++----- fs/fuse/fuse_dev_i.h | 5 ++++- fs/fuse/fuse_i.h | 3 --- fs/fuse/inode.c | 2 -- fs/fuse/req_timeout.c | 2 +- 5 files changed, 17 insertions(+), 12 deletions(-) diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index 7339959fa8d3..41834108bcc1 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -343,13 +343,20 @@ void fuse_chan_release(struct fuse_chan *fch) void fuse_chan_free(struct fuse_chan *fch) { + WARN_ON(!list_empty(&fch->devices)); kfree(fch); } EXPORT_SYMBOL_GPL(fuse_chan_free); struct fuse_chan *fuse_chan_new(void) { - return kzalloc_obj(struct fuse_chan); + struct fuse_chan *fch = kzalloc_obj(struct fuse_chan); + if (!fch) + return NULL; + + INIT_LIST_HEAD(&fch->devices); + + return fch; } EXPORT_SYMBOL_GPL(fuse_chan_new); @@ -418,7 +425,7 @@ void fuse_dev_install(struct fuse_dev *fud, struct fuse_conn *fc) */ fc->connected = 0; } else { - list_add_tail(&fud->entry, &fc->devices); + list_add_tail(&fud->entry, &fc->chan->devices); fuse_conn_get(fc); } spin_unlock(&fc->lock); @@ -2051,7 +2058,7 @@ static void fuse_resend(struct fuse_conn *fc) return; } - list_for_each_entry(fud, &fc->devices, entry) { + list_for_each_entry(fud, &fc->chan->devices, entry) { struct fuse_pqueue *fpq = &fud->pq; spin_lock(&fpq->lock); @@ -2532,7 +2539,7 @@ void fuse_abort_conn(struct fuse_conn *fc) spin_unlock(&fc->bg_lock); fuse_set_initialized(fc); - list_for_each_entry(fud, &fc->devices, entry) { + list_for_each_entry(fud, &fc->chan->devices, entry) { struct fuse_pqueue *fpq = &fud->pq; spin_lock(&fpq->lock); @@ -2618,7 +2625,7 @@ int fuse_dev_release(struct inode *inode, struct file *file) spin_lock(&fc->lock); list_del(&fud->entry); /* Are we the last open device? */ - last = list_empty(&fc->devices); + last = list_empty(&fc->chan->devices); spin_unlock(&fc->lock); if (last) { diff --git a/fs/fuse/fuse_dev_i.h b/fs/fuse/fuse_dev_i.h index 6afc9bb608a9..1949a07d7833 100644 --- a/fs/fuse/fuse_dev_i.h +++ b/fs/fuse/fuse_dev_i.h @@ -90,6 +90,9 @@ struct fuse_iqueue { struct fuse_chan { /** Input queue */ struct fuse_iqueue iq; + + /** List of device instances belonging to this connection */ + struct list_head devices; }; #define FUSE_PQ_HASH_BITS 8 @@ -125,7 +128,7 @@ struct fuse_dev { /** Processing queue */ struct fuse_pqueue pq; - /** list entry on fc->devices */ + /** list entry on fch->devices */ struct list_head entry; }; diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index fd5f741693ca..6185b97df106 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -835,9 +835,6 @@ struct fuse_conn { */ struct rw_semaphore killsb; - /** List of device instances belonging to this connection */ - struct list_head devices; - #ifdef CONFIG_FUSE_DAX /* Dax mode */ enum fuse_dax_mode dax_mode; diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c index 48a836b1ccd3..e7271879e7e2 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c @@ -982,7 +982,6 @@ void fuse_conn_init(struct fuse_conn *fc, struct fuse_mount *fm, init_waitqueue_head(&fc->blocked_waitq); INIT_LIST_HEAD(&fc->bg_queue); INIT_LIST_HEAD(&fc->entry); - INIT_LIST_HEAD(&fc->devices); atomic_set(&fc->num_waiting, 0); fc->max_background = FUSE_DEFAULT_MAX_BACKGROUND; fc->congestion_threshold = FUSE_DEFAULT_CONGESTION_THRESHOLD; @@ -1543,7 +1542,6 @@ EXPORT_SYMBOL_GPL(fuse_send_init); void fuse_free_conn(struct fuse_conn *fc) { - WARN_ON(!list_empty(&fc->devices)); kfree(fc); } EXPORT_SYMBOL_GPL(fuse_free_conn); diff --git a/fs/fuse/req_timeout.c b/fs/fuse/req_timeout.c index 5357a2d63b3f..ca44a7940174 100644 --- a/fs/fuse/req_timeout.c +++ b/fs/fuse/req_timeout.c @@ -94,7 +94,7 @@ static void fuse_check_timeout(struct work_struct *work) spin_unlock(&fc->lock); return; } - list_for_each_entry(fud, &fc->devices, entry) { + list_for_each_entry(fud, &fc->chan->devices, entry) { fpq = &fud->pq; spin_lock(&fpq->lock); if (fuse_request_expired(fc, &fpq->io) || -- cgit v1.2.3 From 2b07cbc4e4865c246b5ba096def7fb9725d8cc1d Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Tue, 17 Mar 2026 14:34:20 +0100 Subject: fuse: move background queuing related members to fuse_chan Move: - max_background - num_background - active_background - bg_queue - bg_lock Signed-off-by: Miklos Szeredi --- fs/fuse/control.c | 11 ++++++----- fs/fuse/dev.c | 47 +++++++++++++++++++++++++---------------------- fs/fuse/dev.h | 3 +++ fs/fuse/dev_uring.c | 24 ++++++++++++------------ fs/fuse/file.c | 5 +++-- fs/fuse/fuse_dev_i.h | 15 +++++++++++++++ fs/fuse/fuse_i.h | 16 ---------------- fs/fuse/inode.c | 16 +++++----------- fs/fuse/req_timeout.c | 6 +++--- fs/fuse/virtio_fs.c | 2 +- 10 files changed, 73 insertions(+), 72 deletions(-) diff --git a/fs/fuse/control.c b/fs/fuse/control.c index f902a7fb4630..6279a37716c5 100644 --- a/fs/fuse/control.c +++ b/fs/fuse/control.c @@ -7,6 +7,7 @@ */ #include "fuse_i.h" +#include "fuse_dev_i.h" #include #include @@ -111,7 +112,7 @@ static ssize_t fuse_conn_max_background_read(struct file *file, if (!fc) return 0; - val = READ_ONCE(fc->max_background); + val = READ_ONCE(fc->chan->max_background); fuse_conn_put(fc); return fuse_conn_limit_read(file, buf, len, ppos, val); @@ -129,12 +130,12 @@ static ssize_t fuse_conn_max_background_write(struct file *file, if (ret > 0) { struct fuse_conn *fc = fuse_ctl_file_conn_get(file); if (fc) { - spin_lock(&fc->bg_lock); - fc->max_background = val; - fc->blocked = fc->num_background >= fc->max_background; + spin_lock(&fc->chan->bg_lock); + fc->chan->max_background = val; + fc->blocked = fc->chan->num_background >= fc->chan->max_background; if (!fc->blocked) wake_up(&fc->blocked_waitq); - spin_unlock(&fc->bg_lock); + spin_unlock(&fc->chan->bg_lock); fuse_conn_put(fc); } } diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index 41834108bcc1..1a22d9004c85 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -181,10 +181,10 @@ static void fuse_put_request(struct fuse_req *req) * We get here in the unlikely case that a background * request was allocated but not sent */ - spin_lock(&fc->bg_lock); + spin_lock(&fc->chan->bg_lock); if (!fc->blocked) wake_up(&fc->blocked_waitq); - spin_unlock(&fc->bg_lock); + spin_unlock(&fc->chan->bg_lock); } if (test_bit(FR_WAITING, &req->flags)) { @@ -355,6 +355,9 @@ struct fuse_chan *fuse_chan_new(void) return NULL; INIT_LIST_HEAD(&fch->devices); + spin_lock_init(&fch->bg_lock); + INIT_LIST_HEAD(&fch->bg_queue); + fch->max_background = FUSE_DEFAULT_MAX_BACKGROUND; return fch; } @@ -489,23 +492,23 @@ static void flush_bg_queue(struct fuse_conn *fc) { struct fuse_iqueue *fiq = &fc->chan->iq; - while (fc->active_background < fc->max_background && - !list_empty(&fc->bg_queue)) { + while (fc->chan->active_background < fc->chan->max_background && + !list_empty(&fc->chan->bg_queue)) { struct fuse_req *req; - req = list_first_entry(&fc->bg_queue, struct fuse_req, list); + req = list_first_entry(&fc->chan->bg_queue, struct fuse_req, list); list_del(&req->list); - fc->active_background++; + fc->chan->active_background++; fuse_send_one(fiq, req); } } void fuse_request_bg_finish(struct fuse_conn *fc, struct fuse_req *req) { - lockdep_assert_held(&fc->bg_lock); + lockdep_assert_held(&fc->chan->bg_lock); clear_bit(FR_BACKGROUND, &req->flags); - if (fc->num_background == fc->max_background) { + if (fc->chan->num_background == fc->chan->max_background) { fc->blocked = 0; wake_up(&fc->blocked_waitq); } else if (!fc->blocked) { @@ -519,8 +522,8 @@ void fuse_request_bg_finish(struct fuse_conn *fc, struct fuse_req *req) wake_up(&fc->blocked_waitq); } - fc->num_background--; - fc->active_background--; + fc->chan->num_background--; + fc->chan->active_background--; } /* @@ -554,10 +557,10 @@ void fuse_request_end(struct fuse_req *req) WARN_ON(test_bit(FR_PENDING, &req->flags)); WARN_ON(test_bit(FR_SENT, &req->flags)); if (test_bit(FR_BACKGROUND, &req->flags)) { - spin_lock(&fc->bg_lock); + spin_lock(&fc->chan->bg_lock); fuse_request_bg_finish(fc, req); flush_bg_queue(fc); - spin_unlock(&fc->bg_lock); + spin_unlock(&fc->chan->bg_lock); } else { /* Wake up waiter sleeping in request_wait_answer() */ wake_up(&req->waitq); @@ -803,16 +806,16 @@ static int fuse_request_queue_background(struct fuse_req *req) return fuse_request_queue_background_uring(fc, req); #endif - spin_lock(&fc->bg_lock); + spin_lock(&fc->chan->bg_lock); if (likely(fc->connected)) { - fc->num_background++; - if (fc->num_background == fc->max_background) + fc->chan->num_background++; + if (fc->chan->num_background == fc->chan->max_background) fc->blocked = 1; - list_add_tail(&req->list, &fc->bg_queue); + list_add_tail(&req->list, &fc->chan->bg_queue); flush_bg_queue(fc); queued = true; } - spin_unlock(&fc->bg_lock); + spin_unlock(&fc->chan->bg_lock); return queued; } @@ -2534,9 +2537,9 @@ void fuse_abort_conn(struct fuse_conn *fc) cancel_delayed_work(&fc->timeout.work); /* Background queuing checks fc->connected under bg_lock */ - spin_lock(&fc->bg_lock); + spin_lock(&fc->chan->bg_lock); fc->connected = 0; - spin_unlock(&fc->bg_lock); + spin_unlock(&fc->chan->bg_lock); fuse_set_initialized(fc); list_for_each_entry(fud, &fc->chan->devices, entry) { @@ -2560,11 +2563,11 @@ void fuse_abort_conn(struct fuse_conn *fc) &to_end); spin_unlock(&fpq->lock); } - spin_lock(&fc->bg_lock); + spin_lock(&fc->chan->bg_lock); fc->blocked = 0; - fc->max_background = UINT_MAX; + fc->chan->max_background = UINT_MAX; flush_bg_queue(fc); - spin_unlock(&fc->bg_lock); + spin_unlock(&fc->chan->bg_lock); spin_lock(&fiq->lock); fiq->connected = 0; diff --git a/fs/fuse/dev.h b/fs/fuse/dev.h index 1f8f2cf1ecfd..2630dab3ef56 100644 --- a/fs/fuse/dev.h +++ b/fs/fuse/dev.h @@ -5,6 +5,9 @@ #include +/** Maximum number of outstanding background requests */ +#define FUSE_DEFAULT_MAX_BACKGROUND 12 + struct fuse_conn; struct fuse_chan; struct fuse_dev; diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c index 02b138727c9b..9c553726701e 100644 --- a/fs/fuse/dev_uring.c +++ b/fs/fuse/dev_uring.c @@ -54,7 +54,7 @@ static void fuse_uring_flush_bg(struct fuse_ring_queue *queue) struct fuse_conn *fc = ring->fc; lockdep_assert_held(&queue->lock); - lockdep_assert_held(&fc->bg_lock); + lockdep_assert_held(&fc->chan->bg_lock); /* * Allow one bg request per queue, ignoring global fc limits. @@ -62,14 +62,14 @@ static void fuse_uring_flush_bg(struct fuse_ring_queue *queue) * eliminates the need for remote queue wake-ups when global * limits are met but this queue has no more waiting requests. */ - while ((fc->active_background < fc->max_background || + while ((fc->chan->active_background < fc->chan->max_background || !queue->active_background) && (!list_empty(&queue->fuse_req_bg_queue))) { struct fuse_req *req; req = list_first_entry(&queue->fuse_req_bg_queue, struct fuse_req, list); - fc->active_background++; + fc->chan->active_background++; queue->active_background++; list_move_tail(&req->list, &queue->fuse_req_queue); @@ -89,10 +89,10 @@ static void fuse_uring_req_end(struct fuse_ring_ent *ent, struct fuse_req *req, list_del_init(&req->list); if (test_bit(FR_BACKGROUND, &req->flags)) { queue->active_background--; - spin_lock(&fc->bg_lock); + spin_lock(&fc->chan->bg_lock); fuse_request_bg_finish(fc, req); fuse_uring_flush_bg(queue); - spin_unlock(&fc->bg_lock); + spin_unlock(&fc->chan->bg_lock); } spin_unlock(&queue->lock); @@ -131,12 +131,12 @@ void fuse_uring_abort_end_requests(struct fuse_ring *ring) if (!queue) continue; - WARN_ON_ONCE(ring->fc->max_background != UINT_MAX); + WARN_ON_ONCE(ring->fc->chan->max_background != UINT_MAX); spin_lock(&queue->lock); queue->stopped = true; - spin_lock(&fc->bg_lock); + spin_lock(&fc->chan->bg_lock); fuse_uring_flush_bg(queue); - spin_unlock(&fc->bg_lock); + spin_unlock(&fc->chan->bg_lock); spin_unlock(&queue->lock); fuse_uring_abort_end_queue_requests(queue); } @@ -1370,12 +1370,12 @@ bool fuse_uring_queue_bq_req(struct fuse_req *req) ent = list_first_entry_or_null(&queue->ent_avail_queue, struct fuse_ring_ent, list); - spin_lock(&fc->bg_lock); - fc->num_background++; - if (fc->num_background == fc->max_background) + spin_lock(&fc->chan->bg_lock); + fc->chan->num_background++; + if (fc->chan->num_background == fc->chan->max_background) fc->blocked = 1; fuse_uring_flush_bg(queue); - spin_unlock(&fc->bg_lock); + spin_unlock(&fc->chan->bg_lock); /* * Due to bg_queue flush limits there might be other bg requests diff --git a/fs/fuse/file.c b/fs/fuse/file.c index 9a0f5a8661da..564bab1ea982 100644 --- a/fs/fuse/file.c +++ b/fs/fuse/file.c @@ -7,6 +7,7 @@ */ #include "fuse_i.h" +#include "fuse_dev_i.h" #include #include @@ -908,7 +909,7 @@ static int fuse_handle_readahead(struct folio *folio, ia = NULL; } if (!ia) { - if (fc->num_background >= fc->congestion_threshold && + if (fc->chan->num_background >= fc->congestion_threshold && rac->ra->async_size >= readahead_count(rac)) /* * Congested and only async pages left, so skip the @@ -2300,7 +2301,7 @@ static int fuse_writepages(struct address_space *mapping, return -EIO; if (wbc->sync_mode == WB_SYNC_NONE && - fc->num_background >= fc->congestion_threshold) + fc->chan->num_background >= fc->congestion_threshold) return 0; return iomap_writepages(&wpc); diff --git a/fs/fuse/fuse_dev_i.h b/fs/fuse/fuse_dev_i.h index 1949a07d7833..db539dbb11f2 100644 --- a/fs/fuse/fuse_dev_i.h +++ b/fs/fuse/fuse_dev_i.h @@ -93,6 +93,21 @@ struct fuse_chan { /** List of device instances belonging to this connection */ struct list_head devices; + + /** Maximum number of outstanding background requests */ + unsigned max_background; + + /** Number of requests currently in the background */ + unsigned num_background; + + /** Number of background requests currently queued for userspace */ + unsigned active_background; + + /** The list of background requests set aside for later queuing */ + struct list_head bg_queue; + + /** Protects: max_background, num_background, active_background, bg_queue, blocked */ + spinlock_t bg_lock; }; #define FUSE_PQ_HASH_BITS 8 diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index 6185b97df106..ccf0db3a4eeb 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -566,25 +566,9 @@ struct fuse_conn { /** rbtree of fuse_files waiting for poll events indexed by ph */ struct rb_root polled_files; - /** Maximum number of outstanding background requests */ - unsigned max_background; - /** Number of background requests at which congestion starts */ unsigned congestion_threshold; - /** Number of requests currently in the background */ - unsigned num_background; - - /** Number of background requests currently queued for userspace */ - unsigned active_background; - - /** The list of background requests set aside for later queuing */ - struct list_head bg_queue; - - /** Protects: max_background, congestion_threshold, num_background, - * active_background, bg_queue, blocked */ - spinlock_t bg_lock; - /** Flag indicating that INIT reply has been received. Allocating * any fuse request will be suspended until the flag is set */ int initialized; diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c index e7271879e7e2..6e0ab13b093c 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c @@ -61,9 +61,6 @@ MODULE_PARM_DESC(max_user_congthresh, #define FUSE_DEFAULT_BLKSIZE 512 -/** Maximum number of outstanding background requests */ -#define FUSE_DEFAULT_MAX_BACKGROUND 12 - /** Congestion starts at 75% of maximum */ #define FUSE_DEFAULT_CONGESTION_THRESHOLD (FUSE_DEFAULT_MAX_BACKGROUND * 3 / 4) @@ -974,16 +971,13 @@ void fuse_conn_init(struct fuse_conn *fc, struct fuse_mount *fm, { memset(fc, 0, sizeof(*fc)); spin_lock_init(&fc->lock); - spin_lock_init(&fc->bg_lock); init_rwsem(&fc->killsb); refcount_set(&fc->count, 1); atomic_set(&fc->epoch, 1); INIT_WORK(&fc->epoch_work, fuse_epoch_work); init_waitqueue_head(&fc->blocked_waitq); - INIT_LIST_HEAD(&fc->bg_queue); INIT_LIST_HEAD(&fc->entry); atomic_set(&fc->num_waiting, 0); - fc->max_background = FUSE_DEFAULT_MAX_BACKGROUND; fc->congestion_threshold = FUSE_DEFAULT_CONGESTION_THRESHOLD; atomic64_set(&fc->khctr, 0); fc->polled_files = RB_ROOT; @@ -1264,12 +1258,12 @@ static void process_init_limits(struct fuse_conn *fc, struct fuse_init_out *arg) sanitize_global_limit(&max_user_bgreq); sanitize_global_limit(&max_user_congthresh); - spin_lock(&fc->bg_lock); + spin_lock(&fc->chan->bg_lock); if (arg->max_background) { - fc->max_background = arg->max_background; + fc->chan->max_background = arg->max_background; - if (!cap_sys_admin && fc->max_background > max_user_bgreq) - fc->max_background = max_user_bgreq; + if (!cap_sys_admin && fc->chan->max_background > max_user_bgreq) + fc->chan->max_background = max_user_bgreq; } if (arg->congestion_threshold) { fc->congestion_threshold = arg->congestion_threshold; @@ -1278,7 +1272,7 @@ static void process_init_limits(struct fuse_conn *fc, struct fuse_init_out *arg) fc->congestion_threshold > max_user_congthresh) fc->congestion_threshold = max_user_congthresh; } - spin_unlock(&fc->bg_lock); + spin_unlock(&fc->chan->bg_lock); } struct fuse_init_args { diff --git a/fs/fuse/req_timeout.c b/fs/fuse/req_timeout.c index ca44a7940174..ee2b4dc394e0 100644 --- a/fs/fuse/req_timeout.c +++ b/fs/fuse/req_timeout.c @@ -83,9 +83,9 @@ static void fuse_check_timeout(struct work_struct *work) if (expired) goto abort_conn; - spin_lock(&fc->bg_lock); - expired = fuse_request_expired(fc, &fc->bg_queue); - spin_unlock(&fc->bg_lock); + spin_lock(&fc->chan->bg_lock); + expired = fuse_request_expired(fc, &fc->chan->bg_queue); + spin_unlock(&fc->chan->bg_lock); if (expired) goto abort_conn; diff --git a/fs/fuse/virtio_fs.c b/fs/fuse/virtio_fs.c index d688a2a95753..53ce21836ba0 100644 --- a/fs/fuse/virtio_fs.c +++ b/fs/fuse/virtio_fs.c @@ -1520,7 +1520,7 @@ static void virtio_fs_send_req(struct fuse_iqueue *fiq, struct fuse_req *req) if (ret == -ENOSPC) { /* * Virtqueue full. Retry submission from worker - * context as we might be holding fc->bg_lock. + * context as we might be holding fc->chan->bg_lock. */ spin_lock(&fsvq->lock); list_add_tail(&req->list, &fsvq->queued_reqs); -- cgit v1.2.3 From 599ad4427bbc901eadd4f2872071eef87830959d Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Tue, 17 Mar 2026 14:48:33 +0100 Subject: fuse: move request blocking related members to fuse_chan Move: - initialized - blocked - blocked_waitq - connected - num_waiting Signed-off-by: Miklos Szeredi --- fs/fuse/control.c | 8 ++--- fs/fuse/cuse.c | 4 +-- fs/fuse/dev.c | 81 +++++++++++++++++++++++++++------------------------ fs/fuse/dev_uring.c | 18 ++++++------ fs/fuse/fuse_dev_i.h | 21 ++++++++++++- fs/fuse/fuse_i.h | 19 ------------ fs/fuse/inode.c | 7 +---- fs/fuse/req_timeout.c | 4 +-- 8 files changed, 81 insertions(+), 81 deletions(-) diff --git a/fs/fuse/control.c b/fs/fuse/control.c index 6279a37716c5..228e1e7114b7 100644 --- a/fs/fuse/control.c +++ b/fs/fuse/control.c @@ -58,7 +58,7 @@ static ssize_t fuse_conn_waiting_read(struct file *file, char __user *buf, if (!fc) return 0; - value = atomic_read(&fc->num_waiting); + value = atomic_read(&fc->chan->num_waiting); file->private_data = (void *)value; fuse_conn_put(fc); } @@ -132,9 +132,9 @@ static ssize_t fuse_conn_max_background_write(struct file *file, if (fc) { spin_lock(&fc->chan->bg_lock); fc->chan->max_background = val; - fc->blocked = fc->chan->num_background >= fc->chan->max_background; - if (!fc->blocked) - wake_up(&fc->blocked_waitq); + fc->chan->blocked = fc->chan->num_background >= fc->chan->max_background; + if (!fc->chan->blocked) + wake_up(&fc->chan->blocked_waitq); spin_unlock(&fc->chan->bg_lock); fuse_conn_put(fc); } diff --git a/fs/fuse/cuse.c b/fs/fuse/cuse.c index 36215a6b3e3a..950c070ab3e9 100644 --- a/fs/fuse/cuse.c +++ b/fs/fuse/cuse.c @@ -529,7 +529,7 @@ static int cuse_channel_open(struct inode *inode, struct file *file) INIT_LIST_HEAD(&cc->list); - cc->fc.initialized = 1; + cc->fc.chan->initialized = 1; rc = cuse_send_init(cc); if (rc) { fuse_dev_put(fud); @@ -586,7 +586,7 @@ static ssize_t cuse_class_waiting_show(struct device *dev, { struct cuse_conn *cc = dev_get_drvdata(dev); - return sprintf(buf, "%d\n", atomic_read(&cc->fc.num_waiting)); + return sprintf(buf, "%d\n", atomic_read(&cc->fc.chan->num_waiting)); } static DEVICE_ATTR(waiting, 0400, cuse_class_waiting_show, NULL); diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index 1a22d9004c85..3c05a84b5d70 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -74,26 +74,26 @@ void fuse_set_initialized(struct fuse_conn *fc) { /* Make sure stores before this are seen on another CPU */ smp_wmb(); - fc->initialized = 1; + fc->chan->initialized = 1; } static bool fuse_block_alloc(struct fuse_conn *fc, bool for_background) { - return !fc->initialized || (for_background && fc->blocked) || - (fc->io_uring && fc->connected && !fuse_uring_ready(fc)); + return !fc->chan->initialized || (for_background && fc->chan->blocked) || + (fc->io_uring && fc->chan->connected && !fuse_uring_ready(fc)); } static void fuse_drop_waiting(struct fuse_conn *fc) { /* - * lockess check of fc->connected is okay, because atomic_dec_and_test() + * lockess check of fc->chan->connected is okay, because atomic_dec_and_test() * provides a memory barrier matched with the one in fuse_wait_aborted() * to ensure no wake-up is missed. */ - if (atomic_dec_and_test(&fc->num_waiting) && - !READ_ONCE(fc->connected)) { + if (atomic_dec_and_test(&fc->chan->num_waiting) && + !READ_ONCE(fc->chan->connected)) { /* wake up aborters */ - wake_up_all(&fc->blocked_waitq); + wake_up_all(&fc->chan->blocked_waitq); } } @@ -110,11 +110,11 @@ static struct fuse_req *fuse_get_req(struct mnt_idmap *idmap, kgid_t fsgid; int err; - atomic_inc(&fc->num_waiting); + atomic_inc(&fc->chan->num_waiting); if (fuse_block_alloc(fc, for_background)) { err = -EINTR; - if (wait_event_state_exclusive(fc->blocked_waitq, + if (wait_event_state_exclusive(fc->chan->blocked_waitq, !fuse_block_alloc(fc, for_background), (TASK_KILLABLE | TASK_FREEZABLE))) goto out; @@ -123,7 +123,7 @@ static struct fuse_req *fuse_get_req(struct mnt_idmap *idmap, smp_rmb(); err = -ENOTCONN; - if (!fc->connected) + if (!fc->chan->connected) goto out; err = -ECONNREFUSED; @@ -134,7 +134,7 @@ static struct fuse_req *fuse_get_req(struct mnt_idmap *idmap, err = -ENOMEM; if (!req) { if (for_background) - wake_up(&fc->blocked_waitq); + wake_up(&fc->chan->blocked_waitq); goto out; } @@ -182,8 +182,8 @@ static void fuse_put_request(struct fuse_req *req) * request was allocated but not sent */ spin_lock(&fc->chan->bg_lock); - if (!fc->blocked) - wake_up(&fc->blocked_waitq); + if (!fc->chan->blocked) + wake_up(&fc->chan->blocked_waitq); spin_unlock(&fc->chan->bg_lock); } @@ -357,7 +357,12 @@ struct fuse_chan *fuse_chan_new(void) INIT_LIST_HEAD(&fch->devices); spin_lock_init(&fch->bg_lock); INIT_LIST_HEAD(&fch->bg_queue); + init_waitqueue_head(&fch->blocked_waitq); + atomic_set(&fch->num_waiting, 0); fch->max_background = FUSE_DEFAULT_MAX_BACKGROUND; + fch->initialized = 0; + fch->blocked = 0; + fch->connected = 1; return fch; } @@ -426,7 +431,7 @@ void fuse_dev_install(struct fuse_dev *fud, struct fuse_conn *fc) * - it was already set to a different fc * - it was set to disconneted */ - fc->connected = 0; + fc->chan->connected = 0; } else { list_add_tail(&fud->entry, &fc->chan->devices); fuse_conn_get(fc); @@ -503,27 +508,27 @@ static void flush_bg_queue(struct fuse_conn *fc) } } -void fuse_request_bg_finish(struct fuse_conn *fc, struct fuse_req *req) +void fuse_request_bg_finish(struct fuse_chan *fch, struct fuse_req *req) { - lockdep_assert_held(&fc->chan->bg_lock); + lockdep_assert_held(&fch->bg_lock); clear_bit(FR_BACKGROUND, &req->flags); - if (fc->chan->num_background == fc->chan->max_background) { - fc->blocked = 0; - wake_up(&fc->blocked_waitq); - } else if (!fc->blocked) { + if (fch->num_background == fch->max_background) { + fch->blocked = 0; + wake_up(&fch->blocked_waitq); + } else if (!fch->blocked) { /* * Wake up next waiter, if any. It's okay to use * waitqueue_active(), as we've already synced up - * fc->blocked with waiters with the wake_up() call + * fch->blocked with waiters with the wake_up() call * above. */ - if (waitqueue_active(&fc->blocked_waitq)) - wake_up(&fc->blocked_waitq); + if (waitqueue_active(&fch->blocked_waitq)) + wake_up(&fch->blocked_waitq); } - fc->chan->num_background--; - fc->chan->active_background--; + fch->num_background--; + fch->active_background--; } /* @@ -558,7 +563,7 @@ void fuse_request_end(struct fuse_req *req) WARN_ON(test_bit(FR_SENT, &req->flags)); if (test_bit(FR_BACKGROUND, &req->flags)) { spin_lock(&fc->chan->bg_lock); - fuse_request_bg_finish(fc, req); + fuse_request_bg_finish(fc->chan, req); flush_bg_queue(fc); spin_unlock(&fc->chan->bg_lock); } else { @@ -737,7 +742,7 @@ ssize_t __fuse_simple_request(struct mnt_idmap *idmap, ssize_t ret; if (args->force) { - atomic_inc(&fc->num_waiting); + atomic_inc(&fc->chan->num_waiting); req = fuse_request_alloc(fm, GFP_KERNEL | __GFP_NOFAIL); if (!args->nocreds) @@ -797,7 +802,7 @@ static int fuse_request_queue_background(struct fuse_req *req) WARN_ON(!test_bit(FR_BACKGROUND, &req->flags)); if (!test_bit(FR_WAITING, &req->flags)) { __set_bit(FR_WAITING, &req->flags); - atomic_inc(&fc->num_waiting); + atomic_inc(&fc->chan->num_waiting); } __set_bit(FR_ISREPLY, &req->flags); @@ -807,10 +812,10 @@ static int fuse_request_queue_background(struct fuse_req *req) #endif spin_lock(&fc->chan->bg_lock); - if (likely(fc->connected)) { + if (likely(fc->chan->connected)) { fc->chan->num_background++; if (fc->chan->num_background == fc->chan->max_background) - fc->blocked = 1; + fc->chan->blocked = 1; list_add_tail(&req->list, &fc->chan->bg_queue); flush_bg_queue(fc); queued = true; @@ -2056,7 +2061,7 @@ static void fuse_resend(struct fuse_conn *fc) unsigned int i; spin_lock(&fc->lock); - if (!fc->connected) { + if (!fc->chan->connected) { spin_unlock(&fc->lock); return; } @@ -2162,7 +2167,7 @@ static int fuse_notify(struct fuse_conn *fc, enum fuse_notify_code code, * Only allow notifications during while the connection is in an * initialized and connected state */ - if (!fc->initialized || !fc->connected) + if (!fc->chan->initialized || !fc->chan->connected) return -EINVAL; /* Don't try to move folios (yet) */ @@ -2527,7 +2532,7 @@ void fuse_abort_conn(struct fuse_conn *fc) struct fuse_iqueue *fiq = &fc->chan->iq; spin_lock(&fc->lock); - if (fc->connected) { + if (fc->chan->connected) { struct fuse_dev *fud; struct fuse_req *req, *next; LIST_HEAD(to_end); @@ -2536,9 +2541,9 @@ void fuse_abort_conn(struct fuse_conn *fc) if (fc->timeout.req_timeout) cancel_delayed_work(&fc->timeout.work); - /* Background queuing checks fc->connected under bg_lock */ + /* Background queuing checks fc->chan->connected under bg_lock */ spin_lock(&fc->chan->bg_lock); - fc->connected = 0; + fc->chan->connected = 0; spin_unlock(&fc->chan->bg_lock); fuse_set_initialized(fc); @@ -2564,7 +2569,7 @@ void fuse_abort_conn(struct fuse_conn *fc) spin_unlock(&fpq->lock); } spin_lock(&fc->chan->bg_lock); - fc->blocked = 0; + fc->chan->blocked = 0; fc->chan->max_background = UINT_MAX; flush_bg_queue(fc); spin_unlock(&fc->chan->bg_lock); @@ -2580,7 +2585,7 @@ void fuse_abort_conn(struct fuse_conn *fc) spin_unlock(&fiq->lock); kill_fasync(&fiq->fasync, SIGIO, POLL_IN); end_polls(fc); - wake_up_all(&fc->blocked_waitq); + wake_up_all(&fc->chan->blocked_waitq); spin_unlock(&fc->lock); fuse_dev_end_requests(&to_end); @@ -2600,7 +2605,7 @@ void fuse_wait_aborted(struct fuse_conn *fc) { /* matches implicit memory barrier in fuse_drop_waiting() */ smp_mb(); - wait_event(fc->blocked_waitq, atomic_read(&fc->num_waiting) == 0); + wait_event(fc->chan->blocked_waitq, atomic_read(&fc->chan->num_waiting) == 0); fuse_uring_wait_stopped_queues(fc); } diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c index 9c553726701e..723f7f2e2b1e 100644 --- a/fs/fuse/dev_uring.c +++ b/fs/fuse/dev_uring.c @@ -90,7 +90,7 @@ static void fuse_uring_req_end(struct fuse_ring_ent *ent, struct fuse_req *req, if (test_bit(FR_BACKGROUND, &req->flags)) { queue->active_background--; spin_lock(&fc->chan->bg_lock); - fuse_request_bg_finish(fc, req); + fuse_request_bg_finish(fc->chan, req); fuse_uring_flush_bg(queue); spin_unlock(&fc->chan->bg_lock); } @@ -244,7 +244,7 @@ static struct fuse_ring *fuse_uring_create(struct fuse_conn *fc) max_payload_size = max(max_payload_size, fc->max_pages * PAGE_SIZE); spin_lock(&fc->lock); - if (!fc->connected) { + if (!fc->chan->connected) { spin_unlock(&fc->lock); goto out_err; } @@ -917,7 +917,7 @@ static int fuse_uring_commit_fetch(struct io_uring_cmd *cmd, int issue_flags, return err; fpq = &queue->fpq; - if (!READ_ONCE(fc->connected)) + if (!READ_ONCE(fc->chan->connected)) return err; spin_lock(&queue->lock); @@ -1010,7 +1010,7 @@ static int fuse_uring_do_register(struct fuse_ring_ent *ent, spin_lock(&fc->lock); /* abort teardown path is running or has run */ - if (!fc->connected) { + if (!fc->chan->connected) { spin_unlock(&fc->lock); if (atomic_dec_and_test(&ring->queue_refs)) wake_up_all(&ring->stop_waitq); @@ -1032,7 +1032,7 @@ static int fuse_uring_do_register(struct fuse_ring_ent *ent, if (ready) { WRITE_ONCE(fiq->ops, &fuse_io_uring_ops); smp_store_release(&ring->ready, true); - wake_up_all(&fc->blocked_waitq); + wake_up_all(&fc->chan->blocked_waitq); } } return 0; @@ -1190,14 +1190,14 @@ int fuse_uring_cmd(struct io_uring_cmd *cmd, unsigned int issue_flags) if (fc->aborted) return -ECONNABORTED; - if (!fc->connected) + if (!fc->chan->connected) return -ENOTCONN; /* * fuse_uring_register() needs the ring to be initialized, * we need to know the max payload size */ - if (!fc->initialized) + if (!fc->chan->initialized) return -EAGAIN; switch (cmd_op) { @@ -1207,7 +1207,7 @@ int fuse_uring_cmd(struct io_uring_cmd *cmd, unsigned int issue_flags) pr_info_once("FUSE_IO_URING_CMD_REGISTER failed err=%d\n", err); fc->io_uring = 0; - wake_up_all(&fc->blocked_waitq); + wake_up_all(&fc->chan->blocked_waitq); return err; } break; @@ -1373,7 +1373,7 @@ bool fuse_uring_queue_bq_req(struct fuse_req *req) spin_lock(&fc->chan->bg_lock); fc->chan->num_background++; if (fc->chan->num_background == fc->chan->max_background) - fc->blocked = 1; + fc->chan->blocked = 1; fuse_uring_flush_bg(queue); spin_unlock(&fc->chan->bg_lock); diff --git a/fs/fuse/fuse_dev_i.h b/fs/fuse/fuse_dev_i.h index db539dbb11f2..3c350dfd31b4 100644 --- a/fs/fuse/fuse_dev_i.h +++ b/fs/fuse/fuse_dev_i.h @@ -108,6 +108,25 @@ struct fuse_chan { /** Protects: max_background, num_background, active_background, bg_queue, blocked */ spinlock_t bg_lock; + + /** Flag indicating that INIT reply has been received. Allocating + * any fuse request will be suspended until the flag is set */ + int initialized; + + /** Flag indicating if connection is blocked. This will be + the case before the INIT reply is received, and if there + are too many outstading backgrounds requests */ + int blocked; + + /** waitq for blocked connection */ + wait_queue_head_t blocked_waitq; + + /** Connection established, cleared on umount, connection + abort and device release */ + unsigned connected; + + /** The number of requests waiting for completion */ + atomic_t num_waiting; }; #define FUSE_PQ_HASH_BITS 8 @@ -205,7 +224,7 @@ unsigned int fuse_req_hash(u64 unique); struct fuse_req *fuse_request_find(struct fuse_pqueue *fpq, u64 unique); void fuse_dev_end_requests(struct list_head *head); -void fuse_request_bg_finish(struct fuse_conn *fc, struct fuse_req *req); +void fuse_request_bg_finish(struct fuse_chan *fch, struct fuse_req *req); void fuse_copy_init(struct fuse_copy_state *cs, bool write, struct iov_iter *iter); diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index ccf0db3a4eeb..a56e49c7a324 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -569,22 +569,6 @@ struct fuse_conn { /** Number of background requests at which congestion starts */ unsigned congestion_threshold; - /** Flag indicating that INIT reply has been received. Allocating - * any fuse request will be suspended until the flag is set */ - int initialized; - - /** Flag indicating if connection is blocked. This will be - the case before the INIT reply is received, and if there - are too many outstading backgrounds requests */ - int blocked; - - /** waitq for blocked connection */ - wait_queue_head_t blocked_waitq; - - /** Connection established, cleared on umount, connection - abort and device release */ - unsigned connected; - /** Connection aborted via sysfs */ bool aborted; @@ -786,9 +770,6 @@ struct fuse_conn { /** Maximum stack depth for passthrough backing files */ int max_stack_depth; - /** The number of requests waiting for completion */ - atomic_t num_waiting; - /** Negotiated minor version */ unsigned minor; diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c index 6e0ab13b093c..13d788abe6de 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c @@ -975,15 +975,10 @@ void fuse_conn_init(struct fuse_conn *fc, struct fuse_mount *fm, refcount_set(&fc->count, 1); atomic_set(&fc->epoch, 1); INIT_WORK(&fc->epoch_work, fuse_epoch_work); - init_waitqueue_head(&fc->blocked_waitq); INIT_LIST_HEAD(&fc->entry); - atomic_set(&fc->num_waiting, 0); fc->congestion_threshold = FUSE_DEFAULT_CONGESTION_THRESHOLD; atomic64_set(&fc->khctr, 0); fc->polled_files = RB_ROOT; - fc->blocked = 0; - fc->initialized = 0; - fc->connected = 1; atomic64_set(&fc->attr_version, 1); atomic64_set(&fc->evict_ctr, 1); get_random_bytes(&fc->scramble_key, sizeof(fc->scramble_key)); @@ -1444,7 +1439,7 @@ static void process_init_reply(struct fuse_mount *fm, struct fuse_args *args, } fuse_set_initialized(fc); - wake_up_all(&fc->blocked_waitq); + wake_up_all(&fc->chan->blocked_waitq); } static struct fuse_init_args *fuse_new_init(struct fuse_mount *fm) diff --git a/fs/fuse/req_timeout.c b/fs/fuse/req_timeout.c index ee2b4dc394e0..ef2e39e4dcf4 100644 --- a/fs/fuse/req_timeout.c +++ b/fs/fuse/req_timeout.c @@ -74,7 +74,7 @@ static void fuse_check_timeout(struct work_struct *work) struct fuse_pqueue *fpq; bool expired = false; - if (!atomic_read(&fc->num_waiting)) + if (!atomic_read(&fc->chan->num_waiting)) goto out; spin_lock(&fiq->lock); @@ -90,7 +90,7 @@ static void fuse_check_timeout(struct work_struct *work) goto abort_conn; spin_lock(&fc->lock); - if (!fc->connected) { + if (!fc->chan->connected) { spin_unlock(&fc->lock); return; } -- cgit v1.2.3 From 0ea79b7d077f57db79b804cefb791c527d0ca9ba Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Tue, 17 Mar 2026 15:29:04 +0100 Subject: fuse: move io_uring related members to fuse_chan Move: - io_uring - ring Signed-off-by: Miklos Szeredi --- fs/fuse/dev.c | 2 +- fs/fuse/dev_uring.c | 26 +++++++++++++------------- fs/fuse/dev_uring_i.h | 6 +++--- fs/fuse/fuse_dev_i.h | 8 ++++++++ fs/fuse/fuse_i.h | 8 -------- fs/fuse/inode.c | 2 +- 6 files changed, 26 insertions(+), 26 deletions(-) diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index 3c05a84b5d70..81bb40778a5b 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -80,7 +80,7 @@ void fuse_set_initialized(struct fuse_conn *fc) static bool fuse_block_alloc(struct fuse_conn *fc, bool for_background) { return !fc->chan->initialized || (for_background && fc->chan->blocked) || - (fc->io_uring && fc->chan->connected && !fuse_uring_ready(fc)); + (fc->chan->io_uring && fc->chan->connected && !fuse_uring_ready(fc)); } static void fuse_drop_waiting(struct fuse_conn *fc) diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c index 723f7f2e2b1e..eef7f7eac795 100644 --- a/fs/fuse/dev_uring.c +++ b/fs/fuse/dev_uring.c @@ -159,7 +159,7 @@ static bool ent_list_request_expired(struct fuse_conn *fc, struct list_head *lis bool fuse_uring_request_expired(struct fuse_conn *fc) { - struct fuse_ring *ring = fc->ring; + struct fuse_ring *ring = fc->chan->ring; struct fuse_ring_queue *queue; int qid; @@ -187,7 +187,7 @@ bool fuse_uring_request_expired(struct fuse_conn *fc) void fuse_uring_destruct(struct fuse_conn *fc) { - struct fuse_ring *ring = fc->ring; + struct fuse_ring *ring = fc->chan->ring; int qid; if (!ring) @@ -218,7 +218,7 @@ void fuse_uring_destruct(struct fuse_conn *fc) kfree(ring->queues); kfree(ring); - fc->ring = NULL; + fc->chan->ring = NULL; } /* @@ -231,7 +231,7 @@ static struct fuse_ring *fuse_uring_create(struct fuse_conn *fc) struct fuse_ring *res = NULL; size_t max_payload_size; - ring = kzalloc_obj(*fc->ring, GFP_KERNEL_ACCOUNT); + ring = kzalloc_obj(*fc->chan->ring, GFP_KERNEL_ACCOUNT); if (!ring) return NULL; @@ -248,10 +248,10 @@ static struct fuse_ring *fuse_uring_create(struct fuse_conn *fc) spin_unlock(&fc->lock); goto out_err; } - if (fc->ring) { + if (fc->chan->ring) { /* race, another thread created the ring in the meantime */ spin_unlock(&fc->lock); - res = fc->ring; + res = fc->chan->ring; goto out_err; } @@ -260,7 +260,7 @@ static struct fuse_ring *fuse_uring_create(struct fuse_conn *fc) ring->nr_queues = nr_queues; ring->fc = fc; ring->max_payload_sz = max_payload_size; - smp_store_release(&fc->ring, ring); + smp_store_release(&fc->chan->ring, ring); spin_unlock(&fc->lock); return ring; @@ -898,7 +898,7 @@ static int fuse_uring_commit_fetch(struct io_uring_cmd *cmd, int issue_flags, struct fuse_uring_cmd_req); struct fuse_ring_ent *ent; int err; - struct fuse_ring *ring = fc->ring; + struct fuse_ring *ring = fc->chan->ring; struct fuse_ring_queue *queue; uint64_t commit_id = READ_ONCE(cmd_req->commit_id); unsigned int qid = READ_ONCE(cmd_req->qid); @@ -1118,7 +1118,7 @@ static int fuse_uring_register(struct io_uring_cmd *cmd, { const struct fuse_uring_cmd_req *cmd_req = io_uring_sqe128_cmd(cmd->sqe, struct fuse_uring_cmd_req); - struct fuse_ring *ring = smp_load_acquire(&fc->ring); + struct fuse_ring *ring = smp_load_acquire(&fc->chan->ring); struct fuse_ring_queue *queue; struct fuse_ring_ent *ent; int err; @@ -1183,7 +1183,7 @@ int fuse_uring_cmd(struct io_uring_cmd *cmd, unsigned int issue_flags) fc = fud->fc; /* Once a connection has io-uring enabled on it, it can't be disabled */ - if (!enable_uring && !fc->io_uring) { + if (!enable_uring && !fc->chan->io_uring) { pr_info_ratelimited("fuse-io-uring is disabled\n"); return -EOPNOTSUPP; } @@ -1206,7 +1206,7 @@ int fuse_uring_cmd(struct io_uring_cmd *cmd, unsigned int issue_flags) if (err) { pr_info_once("FUSE_IO_URING_CMD_REGISTER failed err=%d\n", err); - fc->io_uring = 0; + fc->chan->io_uring = 0; wake_up_all(&fc->chan->blocked_waitq); return err; } @@ -1307,7 +1307,7 @@ static void fuse_uring_dispatch_ent(struct fuse_ring_ent *ent) void fuse_uring_queue_fuse_req(struct fuse_iqueue *fiq, struct fuse_req *req) { struct fuse_conn *fc = req->fm->fc; - struct fuse_ring *ring = fc->ring; + struct fuse_ring *ring = fc->chan->ring; struct fuse_ring_queue *queue; struct fuse_ring_ent *ent = NULL; int err; @@ -1350,7 +1350,7 @@ err: bool fuse_uring_queue_bq_req(struct fuse_req *req) { struct fuse_conn *fc = req->fm->fc; - struct fuse_ring *ring = fc->ring; + struct fuse_ring *ring = fc->chan->ring; struct fuse_ring_queue *queue; struct fuse_ring_ent *ent = NULL; diff --git a/fs/fuse/dev_uring_i.h b/fs/fuse/dev_uring_i.h index e60057b5dbf8..35604f1c7058 100644 --- a/fs/fuse/dev_uring_i.h +++ b/fs/fuse/dev_uring_i.h @@ -148,7 +148,7 @@ bool fuse_uring_request_expired(struct fuse_conn *fc); static inline void fuse_uring_abort(struct fuse_conn *fc) { - struct fuse_ring *ring = fc->ring; + struct fuse_ring *ring = fc->chan->ring; if (ring == NULL) return; @@ -161,7 +161,7 @@ static inline void fuse_uring_abort(struct fuse_conn *fc) static inline void fuse_uring_wait_stopped_queues(struct fuse_conn *fc) { - struct fuse_ring *ring = fc->ring; + struct fuse_ring *ring = fc->chan->ring; if (ring) wait_event(ring->stop_waitq, @@ -170,7 +170,7 @@ static inline void fuse_uring_wait_stopped_queues(struct fuse_conn *fc) static inline bool fuse_uring_ready(struct fuse_conn *fc) { - struct fuse_ring *ring = READ_ONCE(fc->ring); + struct fuse_ring *ring = READ_ONCE(fc->chan->ring); return ring && smp_load_acquire(&ring->ready); } diff --git a/fs/fuse/fuse_dev_i.h b/fs/fuse/fuse_dev_i.h index 3c350dfd31b4..916aa9867147 100644 --- a/fs/fuse/fuse_dev_i.h +++ b/fs/fuse/fuse_dev_i.h @@ -127,6 +127,14 @@ struct fuse_chan { /** The number of requests waiting for completion */ atomic_t num_waiting; + + /* Use io_uring for communication */ + unsigned int io_uring; + +#ifdef CONFIG_FUSE_IO_URING + /** uring connection information*/ + struct fuse_ring *ring; +#endif }; #define FUSE_PQ_HASH_BITS 8 diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index a56e49c7a324..701bb18dcb00 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -764,9 +764,6 @@ struct fuse_conn { /* Is synchronous FUSE_INIT allowed? */ unsigned int sync_init:1; - /* Use io_uring for communication */ - unsigned int io_uring; - /** Maximum stack depth for passthrough backing files */ int max_stack_depth; @@ -819,11 +816,6 @@ struct fuse_conn { struct idr backing_files_map; #endif -#ifdef CONFIG_FUSE_IO_URING - /** uring connection information*/ - struct fuse_ring *ring; -#endif - /** Only used if the connection opts into request timeouts */ struct { /* Worker for checking if any requests have timed out */ diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c index 13d788abe6de..e34881ae8b9f 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c @@ -1412,7 +1412,7 @@ static void process_init_reply(struct fuse_mount *fm, struct fuse_args *args, ok = false; } if (flags & FUSE_OVER_IO_URING && fuse_uring_enabled()) - fc->io_uring = 1; + fc->chan->io_uring = 1; if (flags & FUSE_REQUEST_TIMEOUT) timeout = arg->request_timeout; -- cgit v1.2.3 From 36b6a1e5edd2ac23b64645c25a6b62c2d37a41f4 Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Tue, 31 Mar 2026 15:05:51 +0200 Subject: fuse: move interrupt related members to fuse_chan Move: - no_interrupt Signed-off-by: Miklos Szeredi --- fs/fuse/dev.c | 4 ++-- fs/fuse/fuse_dev_i.h | 3 +++ fs/fuse/fuse_i.h | 3 --- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index 81bb40778a5b..27357ee01fe5 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -615,7 +615,7 @@ static void request_wait_answer(struct fuse_req *req) struct fuse_iqueue *fiq = &fc->chan->iq; int err; - if (!fc->no_interrupt) { + if (!fc->chan->no_interrupt) { /* Any signal may interrupt this */ err = wait_event_interruptible(req->waitq, test_bit(FR_FINISHED, &req->flags)); @@ -2308,7 +2308,7 @@ static ssize_t fuse_dev_do_write(struct fuse_dev *fud, if (nbytes != sizeof(struct fuse_out_header)) err = -EINVAL; else if (oh.error == -ENOSYS) - fc->no_interrupt = 1; + fc->chan->no_interrupt = true; else if (oh.error == -EAGAIN) err = queue_interrupt(req); diff --git a/fs/fuse/fuse_dev_i.h b/fs/fuse/fuse_dev_i.h index 916aa9867147..5c31272a4902 100644 --- a/fs/fuse/fuse_dev_i.h +++ b/fs/fuse/fuse_dev_i.h @@ -128,6 +128,9 @@ struct fuse_chan { /** The number of requests waiting for completion */ atomic_t num_waiting; + /** Is interrupt not implemented by fs? */ + bool no_interrupt; + /* Use io_uring for communication */ unsigned int io_uring; diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index 701bb18dcb00..59bdabaf994b 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -659,9 +659,6 @@ struct fuse_conn { /** Is create not implemented by fs? */ unsigned no_create:1; - /** Is interrupt not implemented by fs? */ - unsigned no_interrupt:1; - /** Is bmap not implemented by fs? */ unsigned no_bmap:1; -- cgit v1.2.3 From bf9932623d20e8b7b695077f531d1fa43ddaaaf3 Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Tue, 17 Mar 2026 16:32:37 +0100 Subject: fuse: split off fch->lock from fc->lock And document which members they protect. end_polls() is called with both, outer fch->lock is probably unnecessary, but doesn't hurt for now. Signed-off-by: Miklos Szeredi --- fs/fuse/dev.c | 29 ++++++++++++++++------------- fs/fuse/dev_uring.c | 20 ++++++++++---------- fs/fuse/fuse_dev_i.h | 8 ++++++++ fs/fuse/fuse_i.h | 8 ++++++-- fs/fuse/req_timeout.c | 8 ++++---- 5 files changed, 44 insertions(+), 29 deletions(-) diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index 27357ee01fe5..52f46efd85ba 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -354,6 +354,7 @@ struct fuse_chan *fuse_chan_new(void) if (!fch) return NULL; + spin_lock_init(&fch->lock); INIT_LIST_HEAD(&fch->devices); spin_lock_init(&fch->bg_lock); INIT_LIST_HEAD(&fch->bg_queue); @@ -418,7 +419,7 @@ void fuse_dev_install(struct fuse_dev *fud, struct fuse_conn *fc) { struct fuse_conn *old_fc; - spin_lock(&fc->lock); + spin_lock(&fc->chan->lock); /* * Pairs with: * - xchg() in fuse_dev_release() @@ -436,7 +437,7 @@ void fuse_dev_install(struct fuse_dev *fud, struct fuse_conn *fc) list_add_tail(&fud->entry, &fc->chan->devices); fuse_conn_get(fc); } - spin_unlock(&fc->lock); + spin_unlock(&fc->chan->lock); } EXPORT_SYMBOL_GPL(fuse_dev_install); @@ -463,9 +464,9 @@ void fuse_dev_put(struct fuse_dev *fud) fc = fuse_dev_fc_get(fud); if (fc && fc != FUSE_DEV_FC_DISCONNECTED) { /* This is the virtiofs case (fuse_dev_release() not called) */ - spin_lock(&fc->lock); + spin_lock(&fc->chan->lock); list_del(&fud->entry); - spin_unlock(&fc->lock); + spin_unlock(&fc->chan->lock); fuse_conn_put(fc); } @@ -2060,9 +2061,9 @@ static void fuse_resend(struct fuse_conn *fc) LIST_HEAD(to_queue); unsigned int i; - spin_lock(&fc->lock); + spin_lock(&fc->chan->lock); if (!fc->chan->connected) { - spin_unlock(&fc->lock); + spin_unlock(&fc->chan->lock); return; } @@ -2074,7 +2075,7 @@ static void fuse_resend(struct fuse_conn *fc) list_splice_tail_init(&fpq->processing[i], &to_queue); spin_unlock(&fpq->lock); } - spin_unlock(&fc->lock); + spin_unlock(&fc->chan->lock); list_for_each_entry_safe(req, next, &to_queue, list) { set_bit(FR_PENDING, &req->flags); @@ -2498,6 +2499,7 @@ static void end_polls(struct fuse_conn *fc) { struct rb_node *p; + spin_lock(&fc->lock); p = rb_first(&fc->polled_files); while (p) { @@ -2507,6 +2509,7 @@ static void end_polls(struct fuse_conn *fc) p = rb_next(p); } + spin_unlock(&fc->lock); } /* @@ -2531,7 +2534,7 @@ void fuse_abort_conn(struct fuse_conn *fc) { struct fuse_iqueue *fiq = &fc->chan->iq; - spin_lock(&fc->lock); + spin_lock(&fc->chan->lock); if (fc->chan->connected) { struct fuse_dev *fud; struct fuse_req *req, *next; @@ -2586,17 +2589,17 @@ void fuse_abort_conn(struct fuse_conn *fc) kill_fasync(&fiq->fasync, SIGIO, POLL_IN); end_polls(fc); wake_up_all(&fc->chan->blocked_waitq); - spin_unlock(&fc->lock); + spin_unlock(&fc->chan->lock); fuse_dev_end_requests(&to_end); /* - * fc->lock must not be taken to avoid conflicts with io-uring + * fc->chan->lock must not be taken to avoid conflicts with io-uring * locks */ fuse_uring_abort(fc); } else { - spin_unlock(&fc->lock); + spin_unlock(&fc->chan->lock); } } EXPORT_SYMBOL_GPL(fuse_abort_conn); @@ -2630,11 +2633,11 @@ int fuse_dev_release(struct inode *inode, struct file *file) fuse_dev_end_requests(&to_end); - spin_lock(&fc->lock); + spin_lock(&fc->chan->lock); list_del(&fud->entry); /* Are we the last open device? */ last = list_empty(&fc->chan->devices); - spin_unlock(&fc->lock); + spin_unlock(&fc->chan->lock); if (last) { WARN_ON(fc->chan->iq.fasync != NULL); diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c index eef7f7eac795..53bd5ae4b686 100644 --- a/fs/fuse/dev_uring.c +++ b/fs/fuse/dev_uring.c @@ -243,14 +243,14 @@ static struct fuse_ring *fuse_uring_create(struct fuse_conn *fc) max_payload_size = max(FUSE_MIN_READ_BUFFER, fc->max_write); max_payload_size = max(max_payload_size, fc->max_pages * PAGE_SIZE); - spin_lock(&fc->lock); + spin_lock(&fc->chan->lock); if (!fc->chan->connected) { - spin_unlock(&fc->lock); + spin_unlock(&fc->chan->lock); goto out_err; } if (fc->chan->ring) { /* race, another thread created the ring in the meantime */ - spin_unlock(&fc->lock); + spin_unlock(&fc->chan->lock); res = fc->chan->ring; goto out_err; } @@ -262,7 +262,7 @@ static struct fuse_ring *fuse_uring_create(struct fuse_conn *fc) ring->max_payload_sz = max_payload_size; smp_store_release(&fc->chan->ring, ring); - spin_unlock(&fc->lock); + spin_unlock(&fc->chan->lock); return ring; out_err: @@ -302,9 +302,9 @@ static struct fuse_ring_queue *fuse_uring_create_queue(struct fuse_ring *ring, queue->fpq.processing = pq; fuse_pqueue_init(&queue->fpq); - spin_lock(&fc->lock); + spin_lock(&fc->chan->lock); if (ring->queues[qid]) { - spin_unlock(&fc->lock); + spin_unlock(&fc->chan->lock); kfree(queue->fpq.processing); kfree(queue); return ring->queues[qid]; @@ -314,7 +314,7 @@ static struct fuse_ring_queue *fuse_uring_create_queue(struct fuse_ring *ring, * write_once and lock as the caller mostly doesn't take the lock at all */ WRITE_ONCE(ring->queues[qid], queue); - spin_unlock(&fc->lock); + spin_unlock(&fc->chan->lock); return queue; } @@ -1008,16 +1008,16 @@ static int fuse_uring_do_register(struct fuse_ring_ent *ent, struct fuse_conn *fc = ring->fc; struct fuse_iqueue *fiq = &fc->chan->iq; - spin_lock(&fc->lock); + spin_lock(&fc->chan->lock); /* abort teardown path is running or has run */ if (!fc->chan->connected) { - spin_unlock(&fc->lock); + spin_unlock(&fc->chan->lock); if (atomic_dec_and_test(&ring->queue_refs)) wake_up_all(&ring->stop_waitq); kfree(ent); return -ECONNABORTED; } - spin_unlock(&fc->lock); + spin_unlock(&fc->chan->lock); fuse_uring_prepare_cancel(cmd, issue_flags, ent); diff --git a/fs/fuse/fuse_dev_i.h b/fs/fuse/fuse_dev_i.h index 5c31272a4902..22d9c9e795d9 100644 --- a/fs/fuse/fuse_dev_i.h +++ b/fs/fuse/fuse_dev_i.h @@ -88,6 +88,14 @@ struct fuse_iqueue { }; struct fuse_chan { + /** Lock protecting: + - devices + - connected + - ring + - ring->queues[qid] + */ + spinlock_t lock; + /** Input queue */ struct fuse_iqueue iq; diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index 59bdabaf994b..6343218563b1 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -421,7 +421,7 @@ enum fuse_req_flag { * * .waitq.lock protects the following fields: * - FR_ABORTED - * - FR_LOCKED (may also be modified under fc->lock, tested under both) + * - FR_LOCKED (may also be modified under fpq->lock, tested under both) */ struct fuse_req { /** This can be on either pending processing or io lists in @@ -520,7 +520,11 @@ struct fuse_sync_bucket { * fuse_mount is destroyed. */ struct fuse_conn { - /** Lock protecting accessess to members of this structure */ + /** Lock protecting: + - polled_files + - backing_files_map + - curr_bucket + */ spinlock_t lock; /** Refcount */ diff --git a/fs/fuse/req_timeout.c b/fs/fuse/req_timeout.c index ef2e39e4dcf4..401d3545b0a8 100644 --- a/fs/fuse/req_timeout.c +++ b/fs/fuse/req_timeout.c @@ -89,9 +89,9 @@ static void fuse_check_timeout(struct work_struct *work) if (expired) goto abort_conn; - spin_lock(&fc->lock); + spin_lock(&fc->chan->lock); if (!fc->chan->connected) { - spin_unlock(&fc->lock); + spin_unlock(&fc->chan->lock); return; } list_for_each_entry(fud, &fc->chan->devices, entry) { @@ -100,13 +100,13 @@ static void fuse_check_timeout(struct work_struct *work) if (fuse_request_expired(fc, &fpq->io) || fuse_fpq_processing_expired(fc, fpq->processing)) { spin_unlock(&fpq->lock); - spin_unlock(&fc->lock); + spin_unlock(&fc->chan->lock); goto abort_conn; } spin_unlock(&fpq->lock); } - spin_unlock(&fc->lock); + spin_unlock(&fc->chan->lock); if (fuse_uring_request_expired(fc)) goto abort_conn; -- cgit v1.2.3 From 7809dc217ab441fdcfbb76fe47bb5bca6d550c8c Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Wed, 18 Mar 2026 17:49:01 +0100 Subject: fuse: add back pointer from fuse_chan to fuse_conn Will be needed by callbacks from the transport layer to the fs layer. Signed-off-by: Miklos Szeredi --- fs/fuse/fuse_dev_i.h | 3 +++ fs/fuse/inode.c | 1 + 2 files changed, 4 insertions(+) diff --git a/fs/fuse/fuse_dev_i.h b/fs/fuse/fuse_dev_i.h index 22d9c9e795d9..188a21f1e141 100644 --- a/fs/fuse/fuse_dev_i.h +++ b/fs/fuse/fuse_dev_i.h @@ -96,6 +96,9 @@ struct fuse_chan { */ spinlock_t lock; + /* back pointer: fc->chan->conn == fc */ + struct fuse_conn *conn; + /** Input queue */ struct fuse_iqueue iq; diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c index e34881ae8b9f..3d9ac14d636c 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c @@ -995,6 +995,7 @@ void fuse_conn_init(struct fuse_conn *fc, struct fuse_mount *fm, INIT_LIST_HEAD(&fc->mounts); list_add(&fm->fc_entry, &fc->mounts); fm->fc = fc; + fch->conn = fc; fc->chan = fch; } EXPORT_SYMBOL_GPL(fuse_conn_init); -- cgit v1.2.3 From 56b4332e12a567eed708bb76eb69d43238a06183 Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Wed, 18 Mar 2026 18:01:27 +0100 Subject: fuse: move request timeout to fuse_chan Move: - timeout Signed-off-by: Miklos Szeredi --- fs/fuse/dev.c | 5 +++-- fs/fuse/dev.h | 2 +- fs/fuse/dev_uring.c | 16 +++++++-------- fs/fuse/dev_uring_i.h | 4 ++-- fs/fuse/fuse_dev_i.h | 11 +++++++++- fs/fuse/fuse_i.h | 9 -------- fs/fuse/inode.c | 7 +++---- fs/fuse/req_timeout.c | 57 +++++++++++++++++++++++++-------------------------- 8 files changed, 55 insertions(+), 56 deletions(-) diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index 52f46efd85ba..19b8cee3cff3 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -364,6 +364,7 @@ struct fuse_chan *fuse_chan_new(void) fch->initialized = 0; fch->blocked = 0; fch->connected = 1; + fch->timeout.req_timeout = 0; return fch; } @@ -2541,8 +2542,8 @@ void fuse_abort_conn(struct fuse_conn *fc) LIST_HEAD(to_end); unsigned int i; - if (fc->timeout.req_timeout) - cancel_delayed_work(&fc->timeout.work); + if (fc->chan->timeout.req_timeout) + cancel_delayed_work(&fc->chan->timeout.work); /* Background queuing checks fc->chan->connected under bg_lock */ spin_lock(&fc->chan->bg_lock); diff --git a/fs/fuse/dev.h b/fs/fuse/dev.h index 2630dab3ef56..df8a199b42d2 100644 --- a/fs/fuse/dev.h +++ b/fs/fuse/dev.h @@ -21,6 +21,6 @@ DEFINE_FREE(fuse_chan_free, struct fuse_chan *, if (_T) fuse_chan_free(_T)) void fuse_dev_install(struct fuse_dev *fud, struct fuse_conn *fc); void fuse_dev_put(struct fuse_dev *fud); -void fuse_init_server_timeout(struct fuse_conn *fc, unsigned int timeout); +void fuse_init_server_timeout(struct fuse_chan *fch, unsigned int timeout); #endif /* _FS_FUSE_DEV_H */ diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c index 53bd5ae4b686..da63fc8d0c59 100644 --- a/fs/fuse/dev_uring.c +++ b/fs/fuse/dev_uring.c @@ -142,7 +142,7 @@ void fuse_uring_abort_end_requests(struct fuse_ring *ring) } } -static bool ent_list_request_expired(struct fuse_conn *fc, struct list_head *list) +static bool ent_list_request_expired(struct fuse_chan *fch, struct list_head *list) { struct fuse_ring_ent *ent; struct fuse_req *req; @@ -154,12 +154,12 @@ static bool ent_list_request_expired(struct fuse_conn *fc, struct list_head *lis req = ent->fuse_req; return time_is_before_jiffies(req->create_time + - fc->timeout.req_timeout); + fch->timeout.req_timeout); } -bool fuse_uring_request_expired(struct fuse_conn *fc) +bool fuse_uring_request_expired(struct fuse_chan *fch) { - struct fuse_ring *ring = fc->chan->ring; + struct fuse_ring *ring = fch->ring; struct fuse_ring_queue *queue; int qid; @@ -172,10 +172,10 @@ bool fuse_uring_request_expired(struct fuse_conn *fc) continue; spin_lock(&queue->lock); - if (fuse_request_expired(fc, &queue->fuse_req_queue) || - fuse_request_expired(fc, &queue->fuse_req_bg_queue) || - ent_list_request_expired(fc, &queue->ent_w_req_queue) || - ent_list_request_expired(fc, &queue->ent_in_userspace)) { + if (fuse_request_expired(fch, &queue->fuse_req_queue) || + fuse_request_expired(fch, &queue->fuse_req_bg_queue) || + ent_list_request_expired(fch, &queue->ent_w_req_queue) || + ent_list_request_expired(fch, &queue->ent_in_userspace)) { spin_unlock(&queue->lock); return true; } diff --git a/fs/fuse/dev_uring_i.h b/fs/fuse/dev_uring_i.h index 35604f1c7058..c9fd92cb0f0d 100644 --- a/fs/fuse/dev_uring_i.h +++ b/fs/fuse/dev_uring_i.h @@ -144,7 +144,7 @@ int fuse_uring_cmd(struct io_uring_cmd *cmd, unsigned int issue_flags); void fuse_uring_queue_fuse_req(struct fuse_iqueue *fiq, struct fuse_req *req); bool fuse_uring_queue_bq_req(struct fuse_req *req); bool fuse_uring_remove_pending_req(struct fuse_req *req); -bool fuse_uring_request_expired(struct fuse_conn *fc); +bool fuse_uring_request_expired(struct fuse_chan *fch); static inline void fuse_uring_abort(struct fuse_conn *fc) { @@ -204,7 +204,7 @@ static inline bool fuse_uring_remove_pending_req(struct fuse_req *req) return false; } -static inline bool fuse_uring_request_expired(struct fuse_conn *fc) +static inline bool fuse_uring_request_expired(struct fuse_chan *fch) { return false; } diff --git a/fs/fuse/fuse_dev_i.h b/fs/fuse/fuse_dev_i.h index 188a21f1e141..b512e484d307 100644 --- a/fs/fuse/fuse_dev_i.h +++ b/fs/fuse/fuse_dev_i.h @@ -149,6 +149,15 @@ struct fuse_chan { /** uring connection information*/ struct fuse_ring *ring; #endif + + /** Only used if the connection opts into request timeouts */ + struct { + /* Worker for checking if any requests have timed out */ + struct delayed_work work; + + /* Request timeout (in jiffies). 0 = no timeout */ + unsigned int req_timeout; + } timeout; }; #define FUSE_PQ_HASH_BITS 8 @@ -261,7 +270,7 @@ void fuse_dev_queue_forget(struct fuse_iqueue *fiq, void fuse_dev_queue_interrupt(struct fuse_iqueue *fiq, struct fuse_req *req); bool fuse_remove_pending_req(struct fuse_req *req, spinlock_t *lock); -bool fuse_request_expired(struct fuse_conn *fc, struct list_head *list); +bool fuse_request_expired(struct fuse_chan *fch, struct list_head *list); /** * Assign a unique id to a fuse request diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index 6343218563b1..7bc6c2054d1a 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -816,15 +816,6 @@ struct fuse_conn { /** IDR for backing files ids */ struct idr backing_files_map; #endif - - /** Only used if the connection opts into request timeouts */ - struct { - /* Worker for checking if any requests have timed out */ - struct delayed_work work; - - /* Request timeout (in jiffies). 0 = no timeout */ - unsigned int req_timeout; - } timeout; }; /* diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c index 3d9ac14d636c..772d9bec1362 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c @@ -987,7 +987,6 @@ void fuse_conn_init(struct fuse_conn *fc, struct fuse_mount *fm, fc->max_pages = FUSE_DEFAULT_MAX_PAGES_PER_REQ; fc->max_pages_limit = fuse_max_pages_limit; fc->name_max = FUSE_NAME_LOW_MAX; - fc->timeout.req_timeout = 0; if (IS_ENABLED(CONFIG_FUSE_PASSTHROUGH)) fuse_backing_files_init(fc); @@ -1020,8 +1019,8 @@ void fuse_conn_put(struct fuse_conn *fc) if (IS_ENABLED(CONFIG_FUSE_DAX)) fuse_dax_conn_free(fc); - if (fc->timeout.req_timeout) - cancel_delayed_work_sync(&fc->timeout.work); + if (fc->chan->timeout.req_timeout) + cancel_delayed_work_sync(&fc->chan->timeout.work); cancel_work_sync(&fc->epoch_work); fuse_chan_release(fc->chan); put_pid_ns(fc->pid_ns); @@ -1423,7 +1422,7 @@ static void process_init_reply(struct fuse_mount *fm, struct fuse_args *args, fc->no_flock = 1; } - fuse_init_server_timeout(fc, timeout); + fuse_init_server_timeout(fc->chan, timeout); fm->sb->s_bdi->ra_pages = min(fm->sb->s_bdi->ra_pages, ra_pages); diff --git a/fs/fuse/req_timeout.c b/fs/fuse/req_timeout.c index 401d3545b0a8..ef464bebc0b6 100644 --- a/fs/fuse/req_timeout.c +++ b/fs/fuse/req_timeout.c @@ -29,22 +29,22 @@ unsigned int fuse_default_req_timeout; */ unsigned int fuse_max_req_timeout; -bool fuse_request_expired(struct fuse_conn *fc, struct list_head *list) +bool fuse_request_expired(struct fuse_chan *fch, struct list_head *list) { struct fuse_req *req; req = list_first_entry_or_null(list, struct fuse_req, list); if (!req) return false; - return time_is_before_jiffies(req->create_time + fc->timeout.req_timeout); + return time_is_before_jiffies(req->create_time + fch->timeout.req_timeout); } -static bool fuse_fpq_processing_expired(struct fuse_conn *fc, struct list_head *processing) +static bool fuse_fpq_processing_expired(struct fuse_chan *fch, struct list_head *processing) { int i; for (i = 0; i < FUSE_PQ_HASH_SIZE; i++) - if (fuse_request_expired(fc, &processing[i])) + if (fuse_request_expired(fch, &processing[i])) return true; return false; @@ -67,68 +67,67 @@ static bool fuse_fpq_processing_expired(struct fuse_conn *fc, struct list_head * static void fuse_check_timeout(struct work_struct *work) { struct delayed_work *dwork = to_delayed_work(work); - struct fuse_conn *fc = container_of(dwork, struct fuse_conn, - timeout.work); - struct fuse_iqueue *fiq = &fc->chan->iq; + struct fuse_chan *fch = container_of(dwork, struct fuse_chan, timeout.work); + struct fuse_iqueue *fiq = &fch->iq; struct fuse_dev *fud; struct fuse_pqueue *fpq; bool expired = false; - if (!atomic_read(&fc->chan->num_waiting)) + if (!atomic_read(&fch->num_waiting)) goto out; spin_lock(&fiq->lock); - expired = fuse_request_expired(fc, &fiq->pending); + expired = fuse_request_expired(fch, &fiq->pending); spin_unlock(&fiq->lock); if (expired) goto abort_conn; - spin_lock(&fc->chan->bg_lock); - expired = fuse_request_expired(fc, &fc->chan->bg_queue); - spin_unlock(&fc->chan->bg_lock); + spin_lock(&fch->bg_lock); + expired = fuse_request_expired(fch, &fch->bg_queue); + spin_unlock(&fch->bg_lock); if (expired) goto abort_conn; - spin_lock(&fc->chan->lock); - if (!fc->chan->connected) { - spin_unlock(&fc->chan->lock); + spin_lock(&fch->lock); + if (!fch->connected) { + spin_unlock(&fch->lock); return; } - list_for_each_entry(fud, &fc->chan->devices, entry) { + list_for_each_entry(fud, &fch->devices, entry) { fpq = &fud->pq; spin_lock(&fpq->lock); - if (fuse_request_expired(fc, &fpq->io) || - fuse_fpq_processing_expired(fc, fpq->processing)) { + if (fuse_request_expired(fch, &fpq->io) || + fuse_fpq_processing_expired(fch, fpq->processing)) { spin_unlock(&fpq->lock); - spin_unlock(&fc->chan->lock); + spin_unlock(&fch->lock); goto abort_conn; } spin_unlock(&fpq->lock); } - spin_unlock(&fc->chan->lock); + spin_unlock(&fch->lock); - if (fuse_uring_request_expired(fc)) + if (fuse_uring_request_expired(fch)) goto abort_conn; out: - queue_delayed_work(system_percpu_wq, &fc->timeout.work, + queue_delayed_work(system_percpu_wq, &fch->timeout.work, fuse_timeout_timer_freq); return; abort_conn: - fuse_abort_conn(fc); + fuse_abort_conn(fch->conn); } -static void set_request_timeout(struct fuse_conn *fc, unsigned int timeout) +static void set_request_timeout(struct fuse_chan *fch, unsigned int timeout) { - fc->timeout.req_timeout = secs_to_jiffies(timeout); - INIT_DELAYED_WORK(&fc->timeout.work, fuse_check_timeout); - queue_delayed_work(system_percpu_wq, &fc->timeout.work, + fch->timeout.req_timeout = secs_to_jiffies(timeout); + INIT_DELAYED_WORK(&fch->timeout.work, fuse_check_timeout); + queue_delayed_work(system_percpu_wq, &fch->timeout.work, fuse_timeout_timer_freq); } -void fuse_init_server_timeout(struct fuse_conn *fc, unsigned int timeout) +void fuse_init_server_timeout(struct fuse_chan *fch, unsigned int timeout) { if (!timeout && !fuse_max_req_timeout && !fuse_default_req_timeout) return; @@ -145,6 +144,6 @@ void fuse_init_server_timeout(struct fuse_conn *fc, unsigned int timeout) timeout = max(FUSE_TIMEOUT_TIMER_FREQ, timeout); - set_request_timeout(fc, timeout); + set_request_timeout(fch, timeout); } -- cgit v1.2.3 From c1265fe4994b75fa378f6379705fb6c354a1cc6c Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Thu, 19 Mar 2026 11:57:10 +0100 Subject: fuse: move struct fuse_req and related to fuse_dev_i.h Signed-off-by: Miklos Szeredi --- fs/fuse/fuse_dev_i.h | 91 +++++++++++++++++++++++++++++++++++++++++++++++++++- fs/fuse/fuse_i.h | 91 ---------------------------------------------------- 2 files changed, 90 insertions(+), 92 deletions(-) diff --git a/fs/fuse/fuse_dev_i.h b/fs/fuse/fuse_dev_i.h index b512e484d307..9a9e14f9c8a4 100644 --- a/fs/fuse/fuse_dev_i.h +++ b/fs/fuse/fuse_dev_i.h @@ -17,10 +17,95 @@ extern struct wait_queue_head fuse_dev_waitq; struct fuse_arg; struct fuse_args; struct fuse_pqueue; -struct fuse_req; struct fuse_iqueue; struct fuse_forget_link; +/** + * Request flags + * + * FR_ISREPLY: set if the request has reply + * FR_FORCE: force sending of the request even if interrupted + * FR_BACKGROUND: request is sent in the background + * FR_WAITING: request is counted as "waiting" + * FR_ABORTED: the request was aborted + * FR_INTERRUPTED: the request has been interrupted + * FR_LOCKED: data is being copied to/from the request + * FR_PENDING: request is not yet in userspace + * FR_SENT: request is in userspace, waiting for an answer + * FR_FINISHED: request is finished + * FR_PRIVATE: request is on private list + * FR_ASYNC: request is asynchronous + * FR_URING: request is handled through fuse-io-uring + */ +enum fuse_req_flag { + FR_ISREPLY, + FR_FORCE, + FR_BACKGROUND, + FR_WAITING, + FR_ABORTED, + FR_INTERRUPTED, + FR_LOCKED, + FR_PENDING, + FR_SENT, + FR_FINISHED, + FR_PRIVATE, + FR_ASYNC, + FR_URING, +}; + +/** + * A request to the client + * + * .waitq.lock protects the following fields: + * - FR_ABORTED + * - FR_LOCKED (may also be modified under fpq->lock, tested under both) + */ +struct fuse_req { + /** This can be on either pending processing or io lists in + fuse_conn */ + struct list_head list; + + /** Entry on the interrupts list */ + struct list_head intr_entry; + + /* Input/output arguments */ + struct fuse_args *args; + + /** refcount */ + refcount_t count; + + /* Request flags, updated with test/set/clear_bit() */ + unsigned long flags; + + /* The request input header */ + struct { + struct fuse_in_header h; + } in; + + /* The request output header */ + struct { + struct fuse_out_header h; + } out; + + /** Used to wake up the task waiting for completion of request*/ + wait_queue_head_t waitq; + +#if IS_ENABLED(CONFIG_VIRTIO_FS) + /** virtio-fs's physically contiguous buffer for in and out args */ + void *argbuf; +#endif + + /** fuse_mount this request belongs to */ + struct fuse_mount *fm; + +#ifdef CONFIG_FUSE_IO_URING + void *ring_entry; + void *ring_queue; +#endif + /** When (in jiffies) the request was created */ + unsigned long create_time; +}; + /** * Input queue callbacks * @@ -290,6 +375,10 @@ struct fuse_dev *fuse_dev_alloc(void); */ void fuse_pqueue_init(struct fuse_pqueue *fpq); +/** + * End a finished request + */ +void fuse_request_end(struct fuse_req *req); #endif diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index 7bc6c2054d1a..e6c1c161214c 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -383,92 +383,6 @@ struct fuse_io_priv { .iocb = i, \ } -/** - * Request flags - * - * FR_ISREPLY: set if the request has reply - * FR_FORCE: force sending of the request even if interrupted - * FR_BACKGROUND: request is sent in the background - * FR_WAITING: request is counted as "waiting" - * FR_ABORTED: the request was aborted - * FR_INTERRUPTED: the request has been interrupted - * FR_LOCKED: data is being copied to/from the request - * FR_PENDING: request is not yet in userspace - * FR_SENT: request is in userspace, waiting for an answer - * FR_FINISHED: request is finished - * FR_PRIVATE: request is on private list - * FR_ASYNC: request is asynchronous - * FR_URING: request is handled through fuse-io-uring - */ -enum fuse_req_flag { - FR_ISREPLY, - FR_FORCE, - FR_BACKGROUND, - FR_WAITING, - FR_ABORTED, - FR_INTERRUPTED, - FR_LOCKED, - FR_PENDING, - FR_SENT, - FR_FINISHED, - FR_PRIVATE, - FR_ASYNC, - FR_URING, -}; - -/** - * A request to the client - * - * .waitq.lock protects the following fields: - * - FR_ABORTED - * - FR_LOCKED (may also be modified under fpq->lock, tested under both) - */ -struct fuse_req { - /** This can be on either pending processing or io lists in - fuse_conn */ - struct list_head list; - - /** Entry on the interrupts list */ - struct list_head intr_entry; - - /* Input/output arguments */ - struct fuse_args *args; - - /** refcount */ - refcount_t count; - - /* Request flags, updated with test/set/clear_bit() */ - unsigned long flags; - - /* The request input header */ - struct { - struct fuse_in_header h; - } in; - - /* The request output header */ - struct { - struct fuse_out_header h; - } out; - - /** Used to wake up the task waiting for completion of request*/ - wait_queue_head_t waitq; - -#if IS_ENABLED(CONFIG_VIRTIO_FS) - /** virtio-fs's physically contiguous buffer for in and out args */ - void *argbuf; -#endif - - /** fuse_mount this request belongs to */ - struct fuse_mount *fm; - -#ifdef CONFIG_FUSE_IO_URING - void *ring_entry; - void *ring_queue; -#endif - /** When (in jiffies) the request was created */ - unsigned long create_time; -}; - enum fuse_dax_mode { FUSE_DAX_INODE_DEFAULT, /* default */ FUSE_DAX_ALWAYS, /* "-o dax=always" */ @@ -1094,11 +1008,6 @@ static inline ssize_t fuse_simple_idmap_request(struct mnt_idmap *idmap, int fuse_simple_background(struct fuse_mount *fm, struct fuse_args *args, gfp_t gfp_flags); -/** - * End a finished request - */ -void fuse_request_end(struct fuse_req *req); - /* Abort all requests */ void fuse_abort_conn(struct fuse_conn *fc); void fuse_wait_aborted(struct fuse_conn *fc); -- cgit v1.2.3 From b6d83d4e3eff4532c4c93d958dec78099ea37f5a Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Thu, 19 Mar 2026 15:40:10 +0100 Subject: fuse: don't access transport layer structs directly from the fs layer Add helpers (get and set functions mainly) that cleanly separate the layers. Remove #include "fuse_dev_i.h" from: - inode.c - file.c - control.c Remove #include "dev_uring_i.h" from inode.c. [Li Wang: drop redundant initializer in process_init_limits()] Signed-off-by: Miklos Szeredi --- fs/fuse/control.c | 13 +++------ fs/fuse/dev.c | 77 +++++++++++++++++++++++++++++++++++++++++++++++---- fs/fuse/dev.h | 27 ++++++++++++++++++ fs/fuse/dev_uring.c | 7 +++-- fs/fuse/dev_uring_i.h | 11 -------- fs/fuse/file.c | 6 ++-- fs/fuse/fuse_i.h | 2 -- fs/fuse/inode.c | 36 ++++++++++-------------- 8 files changed, 124 insertions(+), 55 deletions(-) diff --git a/fs/fuse/control.c b/fs/fuse/control.c index 228e1e7114b7..e6f513eb7d4a 100644 --- a/fs/fuse/control.c +++ b/fs/fuse/control.c @@ -7,7 +7,7 @@ */ #include "fuse_i.h" -#include "fuse_dev_i.h" +#include "dev.h" #include #include @@ -58,7 +58,7 @@ static ssize_t fuse_conn_waiting_read(struct file *file, char __user *buf, if (!fc) return 0; - value = atomic_read(&fc->chan->num_waiting); + value = fuse_chan_num_waiting(fc->chan); file->private_data = (void *)value; fuse_conn_put(fc); } @@ -112,7 +112,7 @@ static ssize_t fuse_conn_max_background_read(struct file *file, if (!fc) return 0; - val = READ_ONCE(fc->chan->max_background); + val = fuse_chan_max_background(fc->chan); fuse_conn_put(fc); return fuse_conn_limit_read(file, buf, len, ppos, val); @@ -130,12 +130,7 @@ static ssize_t fuse_conn_max_background_write(struct file *file, if (ret > 0) { struct fuse_conn *fc = fuse_ctl_file_conn_get(file); if (fc) { - spin_lock(&fc->chan->bg_lock); - fc->chan->max_background = val; - fc->chan->blocked = fc->chan->num_background >= fc->chan->max_background; - if (!fc->chan->blocked) - wake_up(&fc->chan->blocked_waitq); - spin_unlock(&fc->chan->bg_lock); + fuse_chan_max_background_set(fc->chan, val); fuse_conn_put(fc); } } diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index 19b8cee3cff3..df851c2383e9 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -70,11 +70,12 @@ static void __fuse_put_request(struct fuse_req *req) refcount_dec(&req->count); } -void fuse_set_initialized(struct fuse_conn *fc) +void fuse_chan_set_initialized(struct fuse_chan *fch) { /* Make sure stores before this are seen on another CPU */ smp_wmb(); - fc->chan->initialized = 1; + fch->initialized = 1; + wake_up_all(&fch->blocked_waitq); } static bool fuse_block_alloc(struct fuse_conn *fc, bool for_background) @@ -119,7 +120,7 @@ static struct fuse_req *fuse_get_req(struct mnt_idmap *idmap, (TASK_KILLABLE | TASK_FREEZABLE))) goto out; } - /* Matches smp_wmb() in fuse_set_initialized() */ + /* Matches smp_wmb() in fuse_chan_set_initialized() */ smp_rmb(); err = -ENOTCONN; @@ -339,6 +340,9 @@ void fuse_chan_release(struct fuse_chan *fch) if (fiq->ops->release) fiq->ops->release(fiq); + + if (fch->timeout.req_timeout) + cancel_delayed_work_sync(&fch->timeout.work); } void fuse_chan_free(struct fuse_chan *fch) @@ -382,6 +386,41 @@ struct fuse_chan *fuse_dev_chan_new(void) } EXPORT_SYMBOL_GPL(fuse_dev_chan_new); +unsigned int fuse_chan_num_background(struct fuse_chan *fch) +{ + return fch->num_background; +} + +unsigned int fuse_chan_max_background(struct fuse_chan *fch) +{ + return READ_ONCE(fch->max_background); +} + +void fuse_chan_max_background_set(struct fuse_chan *fch, unsigned int val) +{ + spin_lock(&fch->bg_lock); + fch->max_background = val; + fch->blocked = fch->num_background >= fch->max_background; + if (!fch->blocked) + wake_up(&fch->blocked_waitq); + spin_unlock(&fch->bg_lock); +} + +unsigned int fuse_chan_num_waiting(struct fuse_chan *fch) +{ + return atomic_read(&fch->num_waiting); +} + +void fuse_chan_set_fc(struct fuse_chan *fch, struct fuse_conn *fc) +{ + fch->conn = fc; +} + +void fuse_chan_io_uring_enable(struct fuse_chan *fch) +{ + fch->io_uring = 1; +} + void fuse_pqueue_init(struct fuse_pqueue *fpq) { unsigned int i; @@ -476,6 +515,34 @@ void fuse_dev_put(struct fuse_dev *fud) } EXPORT_SYMBOL_GPL(fuse_dev_put); +bool fuse_dev_is_installed(struct fuse_dev *fud) +{ + struct fuse_conn *fc = fuse_dev_fc_get(fud); + + return fc != NULL && fc != FUSE_DEV_FC_DISCONNECTED; +} + +/* + * Checks if @fc matches the one installed in @fud + */ +bool fuse_dev_verify(struct fuse_dev *fud, struct fuse_conn *fc) +{ + return fuse_dev_fc_get(fud) == fc; +} + +bool fuse_dev_is_sync_init(struct fuse_dev *fud) +{ + return fud->sync_init; +} + +struct fuse_dev *fuse_dev_grab(struct file *file) +{ + struct fuse_dev *fud = fuse_file_to_fud(file); + + refcount_inc(&fud->ref); + return fud; +} + static void fuse_send_one(struct fuse_iqueue *fiq, struct fuse_req *req) { req->in.h.len = sizeof(struct fuse_in_header) + @@ -2550,7 +2617,7 @@ void fuse_abort_conn(struct fuse_conn *fc) fc->chan->connected = 0; spin_unlock(&fc->chan->bg_lock); - fuse_set_initialized(fc); + fuse_chan_set_initialized(fc->chan); list_for_each_entry(fud, &fc->chan->devices, entry) { struct fuse_pqueue *fpq = &fud->pq; @@ -2609,7 +2676,7 @@ void fuse_wait_aborted(struct fuse_conn *fc) { /* matches implicit memory barrier in fuse_drop_waiting() */ smp_mb(); - wait_event(fc->chan->blocked_waitq, atomic_read(&fc->chan->num_waiting) == 0); + wait_event(fc->chan->blocked_waitq, fuse_chan_num_waiting(fc->chan) == 0); fuse_uring_wait_stopped_queues(fc); } diff --git a/fs/fuse/dev.h b/fs/fuse/dev.h index df8a199b42d2..7994f5290252 100644 --- a/fs/fuse/dev.h +++ b/fs/fuse/dev.h @@ -11,16 +11,43 @@ struct fuse_conn; struct fuse_chan; struct fuse_dev; +struct file; struct fuse_chan *fuse_chan_new(void); struct fuse_chan *fuse_dev_chan_new(void); void fuse_chan_release(struct fuse_chan *fch); void fuse_chan_free(struct fuse_chan *fch); +unsigned int fuse_chan_num_background(struct fuse_chan *fch); +unsigned int fuse_chan_max_background(struct fuse_chan *fch); +void fuse_chan_max_background_set(struct fuse_chan *fch, unsigned int val); +unsigned int fuse_chan_num_waiting(struct fuse_chan *fch); +void fuse_chan_set_fc(struct fuse_chan *fch, struct fuse_conn *fc); +void fuse_chan_set_initialized(struct fuse_chan *fch); +void fuse_chan_io_uring_enable(struct fuse_chan *fch); + DEFINE_FREE(fuse_chan_free, struct fuse_chan *, if (_T) fuse_chan_free(_T)) void fuse_dev_install(struct fuse_dev *fud, struct fuse_conn *fc); +bool fuse_dev_verify(struct fuse_dev *fud, struct fuse_conn *fc); void fuse_dev_put(struct fuse_dev *fud); +bool fuse_dev_is_installed(struct fuse_dev *fud); +bool fuse_dev_is_sync_init(struct fuse_dev *fud); +struct fuse_dev *fuse_dev_grab(struct file *file); void fuse_init_server_timeout(struct fuse_chan *fch, unsigned int timeout); +#ifdef CONFIG_FUSE_IO_URING +bool fuse_uring_enabled(void); +void fuse_uring_destruct(struct fuse_chan *fch); +#else /* CONFIG_FUSE_IO_URING */ +static inline bool fuse_uring_enabled(void) +{ + return false; +} + +static inline void fuse_uring_destruct(struct fuse_chan *fch) +{ +} +#endif /* CONFIG_FUSE_IO_URING */ + #endif /* _FS_FUSE_DEV_H */ diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c index da63fc8d0c59..296da4ff233c 100644 --- a/fs/fuse/dev_uring.c +++ b/fs/fuse/dev_uring.c @@ -4,6 +4,7 @@ * Copyright (c) 2023-2024 DataDirect Networks. */ +#include "dev.h" #include "fuse_i.h" #include "dev_uring_i.h" #include "fuse_dev_i.h" @@ -185,9 +186,9 @@ bool fuse_uring_request_expired(struct fuse_chan *fch) return false; } -void fuse_uring_destruct(struct fuse_conn *fc) +void fuse_uring_destruct(struct fuse_chan *fch) { - struct fuse_ring *ring = fc->chan->ring; + struct fuse_ring *ring = fch->ring; int qid; if (!ring) @@ -218,7 +219,7 @@ void fuse_uring_destruct(struct fuse_conn *fc) kfree(ring->queues); kfree(ring); - fc->chan->ring = NULL; + fch->ring = NULL; } /* diff --git a/fs/fuse/dev_uring_i.h b/fs/fuse/dev_uring_i.h index c9fd92cb0f0d..8ba149e35423 100644 --- a/fs/fuse/dev_uring_i.h +++ b/fs/fuse/dev_uring_i.h @@ -136,8 +136,6 @@ struct fuse_ring { bool ready; }; -bool fuse_uring_enabled(void); -void fuse_uring_destruct(struct fuse_conn *fc); void fuse_uring_stop_queues(struct fuse_ring *ring); void fuse_uring_abort_end_requests(struct fuse_ring *ring); int fuse_uring_cmd(struct io_uring_cmd *cmd, unsigned int issue_flags); @@ -177,15 +175,6 @@ static inline bool fuse_uring_ready(struct fuse_conn *fc) #else /* CONFIG_FUSE_IO_URING */ -static inline void fuse_uring_destruct(struct fuse_conn *fc) -{ -} - -static inline bool fuse_uring_enabled(void) -{ - return false; -} - static inline void fuse_uring_abort(struct fuse_conn *fc) { } diff --git a/fs/fuse/file.c b/fs/fuse/file.c index 564bab1ea982..c603cb0070f2 100644 --- a/fs/fuse/file.c +++ b/fs/fuse/file.c @@ -7,7 +7,7 @@ */ #include "fuse_i.h" -#include "fuse_dev_i.h" +#include "dev.h" #include #include @@ -909,7 +909,7 @@ static int fuse_handle_readahead(struct folio *folio, ia = NULL; } if (!ia) { - if (fc->chan->num_background >= fc->congestion_threshold && + if (fuse_chan_num_background(fc->chan) >= fc->congestion_threshold && rac->ra->async_size >= readahead_count(rac)) /* * Congested and only async pages left, so skip the @@ -2301,7 +2301,7 @@ static int fuse_writepages(struct address_space *mapping, return -EIO; if (wbc->sync_mode == WB_SYNC_NONE && - fc->chan->num_background >= fc->congestion_threshold) + fuse_chan_num_background(fc->chan) >= fc->congestion_threshold) return 0; return iomap_writepages(&wpc); diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index e6c1c161214c..18fa729bfef2 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -1184,8 +1184,6 @@ int fuse_write_inode(struct inode *inode, struct writeback_control *wbc); int fuse_do_setattr(struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr, struct file *file); -void fuse_set_initialized(struct fuse_conn *fc); - void fuse_unlock_inode(struct inode *inode, bool locked); bool fuse_lock_inode(struct inode *inode); diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c index 772d9bec1362..4de144cd4992 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c @@ -8,8 +8,6 @@ #include "dev.h" #include "fuse_i.h" -#include "fuse_dev_i.h" -#include "dev_uring_i.h" #include #include @@ -812,8 +810,7 @@ static int fuse_opt_fd(struct fs_context *fsc, struct file *file) if (file->f_cred->user_ns != fsc->user_ns) return invalfc(fsc, "wrong user namespace for fuse device"); - ctx->fud = file->private_data; - refcount_inc(&ctx->fud->ref); + ctx->fud = fuse_dev_grab(file); return 0; } @@ -994,7 +991,7 @@ void fuse_conn_init(struct fuse_conn *fc, struct fuse_mount *fm, INIT_LIST_HEAD(&fc->mounts); list_add(&fm->fc_entry, &fc->mounts); fm->fc = fc; - fch->conn = fc; + fuse_chan_set_fc(fch, fc); fc->chan = fch; } EXPORT_SYMBOL_GPL(fuse_conn_init); @@ -1003,7 +1000,7 @@ static void delayed_release(struct rcu_head *p) { struct fuse_conn *fc = container_of(p, struct fuse_conn, rcu); - fuse_uring_destruct(fc); + fuse_uring_destruct(fc->chan); fuse_chan_free(fc->chan); put_user_ns(fc->user_ns); @@ -1019,8 +1016,6 @@ void fuse_conn_put(struct fuse_conn *fc) if (IS_ENABLED(CONFIG_FUSE_DAX)) fuse_dax_conn_free(fc); - if (fc->chan->timeout.req_timeout) - cancel_delayed_work_sync(&fc->chan->timeout.work); cancel_work_sync(&fc->epoch_work); fuse_chan_release(fc->chan); put_pid_ns(fc->pid_ns); @@ -1253,12 +1248,13 @@ static void process_init_limits(struct fuse_conn *fc, struct fuse_init_out *arg) sanitize_global_limit(&max_user_bgreq); sanitize_global_limit(&max_user_congthresh); - spin_lock(&fc->chan->bg_lock); if (arg->max_background) { - fc->chan->max_background = arg->max_background; + unsigned int max_background = arg->max_background; - if (!cap_sys_admin && fc->chan->max_background > max_user_bgreq) - fc->chan->max_background = max_user_bgreq; + if (!cap_sys_admin && max_background > max_user_bgreq) + max_background = max_user_bgreq; + + fuse_chan_max_background_set(fc->chan, max_background); } if (arg->congestion_threshold) { fc->congestion_threshold = arg->congestion_threshold; @@ -1267,7 +1263,6 @@ static void process_init_limits(struct fuse_conn *fc, struct fuse_init_out *arg) fc->congestion_threshold > max_user_congthresh) fc->congestion_threshold = max_user_congthresh; } - spin_unlock(&fc->chan->bg_lock); } struct fuse_init_args { @@ -1412,7 +1407,7 @@ static void process_init_reply(struct fuse_mount *fm, struct fuse_args *args, ok = false; } if (flags & FUSE_OVER_IO_URING && fuse_uring_enabled()) - fc->chan->io_uring = 1; + fuse_chan_io_uring_enable(fc->chan); if (flags & FUSE_REQUEST_TIMEOUT) timeout = arg->request_timeout; @@ -1438,8 +1433,7 @@ static void process_init_reply(struct fuse_mount *fm, struct fuse_args *args, fc->conn_error = 1; } - fuse_set_initialized(fc); - wake_up_all(&fc->chan->blocked_waitq); + fuse_chan_set_initialized(fc->chan); } static struct fuse_init_args *fuse_new_init(struct fuse_mount *fm) @@ -1788,9 +1782,9 @@ int fuse_fill_super_common(struct super_block *sb, struct fuse_fs_context *ctx) mutex_lock(&fuse_mutex); err = -EINVAL; if (fud) { - if (fuse_dev_fc_get(fud)) + if (fuse_dev_is_installed(fud)) goto err_unlock; - if (fud->sync_init) + if (fuse_dev_is_sync_init(fud)) fc->sync_init = 1; } @@ -1848,9 +1842,7 @@ static int fuse_set_no_super(struct super_block *sb, struct fs_context *fsc) static int fuse_test_super(struct super_block *sb, struct fs_context *fsc) { - struct fuse_dev *fud = fsc->sget_key; - - return fuse_dev_fc_get(fud) == get_fuse_conn_super(sb); + return fuse_dev_verify(fsc->sget_key, get_fuse_conn_super(sb)); } static int fuse_get_tree(struct fs_context *fsc) @@ -1896,7 +1888,7 @@ static int fuse_get_tree(struct fs_context *fsc) * Allow creating a fuse mount with an already initialized fuse * connection */ - if (fuse_dev_fc_get(ctx->fud)) { + if (fuse_dev_is_installed(ctx->fud)) { fsc->sget_key = ctx->fud; sb = sget_fc(fsc, fuse_test_super, fuse_set_no_super); err = PTR_ERR_OR_ZERO(sb); -- cgit v1.2.3 From 4f5f0038d69102bd112240a36bf807b4408a11ab Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Tue, 31 Mar 2026 13:18:27 +0200 Subject: fuse: move forget related struct and helpers Move: - struct fuse_forget_link to fuse_dev_i.h - fuse_alloc_forget() to dev.c/dev.h Rename: - fuse_queue_forget -> fuse_chan_queue_forget Signed-off-by: Miklos Szeredi --- fs/fuse/dev.c | 11 ++++++++--- fs/fuse/dev.h | 5 +++++ fs/fuse/dir.c | 9 +++++---- fs/fuse/fuse_dev_i.h | 8 +++++++- fs/fuse/fuse_i.h | 14 +------------- fs/fuse/inode.c | 11 +++-------- 6 files changed, 29 insertions(+), 29 deletions(-) diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index df851c2383e9..cc89346f29fb 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -244,6 +244,11 @@ __releases(fiq->lock) spin_unlock(&fiq->lock); } +struct fuse_forget_link *fuse_alloc_forget(void) +{ + return kzalloc_obj(struct fuse_forget_link, GFP_KERNEL_ACCOUNT); +} + void fuse_dev_queue_forget(struct fuse_iqueue *fiq, struct fuse_forget_link *forget) { @@ -551,10 +556,10 @@ static void fuse_send_one(struct fuse_iqueue *fiq, struct fuse_req *req) fiq->ops->send_req(fiq, req); } -void fuse_queue_forget(struct fuse_conn *fc, struct fuse_forget_link *forget, - u64 nodeid, u64 nlookup) +void fuse_chan_queue_forget(struct fuse_chan *fch, struct fuse_forget_link *forget, + u64 nodeid, u64 nlookup) { - struct fuse_iqueue *fiq = &fc->chan->iq; + struct fuse_iqueue *fiq = &fch->iq; forget->forget_one.nodeid = nodeid; forget->forget_one.nlookup = nlookup; diff --git a/fs/fuse/dev.h b/fs/fuse/dev.h index 7994f5290252..1d3d2e0c9b91 100644 --- a/fs/fuse/dev.h +++ b/fs/fuse/dev.h @@ -25,6 +25,11 @@ void fuse_chan_set_fc(struct fuse_chan *fch, struct fuse_conn *fc); void fuse_chan_set_initialized(struct fuse_chan *fch); void fuse_chan_io_uring_enable(struct fuse_chan *fch); +struct fuse_forget_link *fuse_alloc_forget(void); +void fuse_chan_queue_forget(struct fuse_chan *fch, struct fuse_forget_link *forget, + u64 nodeid, u64 nlookup); + + DEFINE_FREE(fuse_chan_free, struct fuse_chan *, if (_T) fuse_chan_free(_T)) void fuse_dev_install(struct fuse_dev *fud, struct fuse_conn *fc); diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index 97751bacb79c..0ceb49337a44 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -6,6 +6,7 @@ See the file COPYING. */ +#include "dev.h" #include "fuse_i.h" #include @@ -430,7 +431,7 @@ static int fuse_dentry_revalidate(struct inode *dir, const struct qstr *name, fi = get_fuse_inode(inode); if (outarg.nodeid != get_node_id(inode) || (bool) IS_AUTOMOUNT(inode) != (bool) (outarg.attr.flags & FUSE_ATTR_SUBMOUNT)) { - fuse_queue_forget(fm->fc, forget, + fuse_chan_queue_forget(fm->fc->chan, forget, outarg.nodeid, 1); goto invalid; } @@ -593,7 +594,7 @@ int fuse_lookup_name(struct super_block *sb, u64 nodeid, const struct qstr *name attr_version, evict_ctr); err = -ENOMEM; if (!*inode) { - fuse_queue_forget(fm->fc, forget, outarg->nodeid, 1); + fuse_chan_queue_forget(fm->fc->chan, forget, outarg->nodeid, 1); goto out; } err = 0; @@ -894,7 +895,7 @@ static int fuse_create_open(struct mnt_idmap *idmap, struct inode *dir, if (!inode) { flags &= ~(O_CREAT | O_EXCL | O_TRUNC); fuse_sync_release(NULL, ff, flags); - fuse_queue_forget(fm->fc, forget, outentry.nodeid, 1); + fuse_chan_queue_forget(fm->fc->chan, forget, outentry.nodeid, 1); err = -ENOMEM; goto out_err; } @@ -1019,7 +1020,7 @@ static struct dentry *create_new_entry(struct mnt_idmap *idmap, struct fuse_moun inode = fuse_iget(dir->i_sb, outarg.nodeid, outarg.generation, &outarg.attr, ATTR_TIMEOUT(&outarg), 0, 0); if (!inode) { - fuse_queue_forget(fm->fc, forget, outarg.nodeid, 1); + fuse_chan_queue_forget(fm->fc->chan, forget, outarg.nodeid, 1); return ERR_PTR(-ENOMEM); } kfree(forget); diff --git a/fs/fuse/fuse_dev_i.h b/fs/fuse/fuse_dev_i.h index 9a9e14f9c8a4..d1eb816f0c4a 100644 --- a/fs/fuse/fuse_dev_i.h +++ b/fs/fuse/fuse_dev_i.h @@ -6,6 +6,7 @@ #ifndef _FS_FUSE_DEV_I_H #define _FS_FUSE_DEV_I_H +#include #include /* Ordinary requests have even IDs, while interrupts IDs are odd */ @@ -18,7 +19,6 @@ struct fuse_arg; struct fuse_args; struct fuse_pqueue; struct fuse_iqueue; -struct fuse_forget_link; /** * Request flags @@ -106,6 +106,12 @@ struct fuse_req { unsigned long create_time; }; +/* One forget request */ +struct fuse_forget_link { + struct fuse_forget_one forget_one; + struct fuse_forget_link *next; +}; + /** * Input queue callbacks * diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index 18fa729bfef2..a0d120057ddb 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -68,11 +68,7 @@ extern struct mutex fuse_mutex; extern unsigned int max_user_bgreq; extern unsigned int max_user_congthresh; -/* One forget request */ -struct fuse_forget_link { - struct fuse_forget_one forget_one; - struct fuse_forget_link *next; -}; +struct fuse_forget_link; /* Submount lookup tracking */ struct fuse_submount_lookup { @@ -883,14 +879,6 @@ struct inode *fuse_iget(struct super_block *sb, u64 nodeid, int fuse_lookup_name(struct super_block *sb, u64 nodeid, const struct qstr *name, struct fuse_entry_out *outarg, struct inode **inode); -/** - * Send FORGET command - */ -void fuse_queue_forget(struct fuse_conn *fc, struct fuse_forget_link *forget, - u64 nodeid, u64 nlookup); - -struct fuse_forget_link *fuse_alloc_forget(void); - /* * Initialize READ or READDIR request */ diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c index 4de144cd4992..54059404e7c4 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c @@ -66,11 +66,6 @@ MODULE_PARM_DESC(max_user_congthresh, static struct file_system_type fuseblk_fs_type; #endif -struct fuse_forget_link *fuse_alloc_forget(void) -{ - return kzalloc_obj(struct fuse_forget_link, GFP_KERNEL_ACCOUNT); -} - static struct fuse_submount_lookup *fuse_alloc_submount_lookup(void) { struct fuse_submount_lookup *sl; @@ -144,7 +139,7 @@ static void fuse_cleanup_submount_lookup(struct fuse_conn *fc, if (!refcount_dec_and_test(&sl->count)) return; - fuse_queue_forget(fc, sl->forget, sl->nodeid, 1); + fuse_chan_queue_forget(fc->chan, sl->forget, sl->nodeid, 1); sl->forget = NULL; kfree(sl); } @@ -167,8 +162,8 @@ static void fuse_evict_inode(struct inode *inode) if (FUSE_IS_DAX(inode)) fuse_dax_inode_cleanup(inode); if (fi->nlookup) { - fuse_queue_forget(fc, fi->forget, fi->nodeid, - fi->nlookup); + fuse_chan_queue_forget(fc->chan, fi->forget, fi->nodeid, + fi->nlookup); fi->forget = NULL; } -- cgit v1.2.3 From 3344b7367acd302c550aa4136a27447d3b39a169 Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Thu, 19 Mar 2026 15:49:56 +0100 Subject: fuse: move fuse_dev_waitq to dev.c Move wake_up_all(&fuse_dev_waitq) into fuse_dev_install() where it logically belongs. Signed-off-by: Miklos Szeredi --- fs/fuse/dev.c | 3 +++ fs/fuse/fuse_dev_i.h | 2 -- fs/fuse/inode.c | 6 ++---- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index cc89346f29fb..96f0815b4df4 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -31,6 +31,8 @@ MODULE_ALIAS_MISCDEV(FUSE_MINOR); MODULE_ALIAS("devname:fuse"); +static DECLARE_WAIT_QUEUE_HEAD(fuse_dev_waitq); + static struct kmem_cache *fuse_req_cachep; static void fuse_request_init(struct fuse_mount *fm, struct fuse_req *req) @@ -481,6 +483,7 @@ void fuse_dev_install(struct fuse_dev *fud, struct fuse_conn *fc) } else { list_add_tail(&fud->entry, &fc->chan->devices); fuse_conn_get(fc); + wake_up_all(&fuse_dev_waitq); } spin_unlock(&fc->chan->lock); } diff --git a/fs/fuse/fuse_dev_i.h b/fs/fuse/fuse_dev_i.h index d1eb816f0c4a..846abb063c69 100644 --- a/fs/fuse/fuse_dev_i.h +++ b/fs/fuse/fuse_dev_i.h @@ -13,8 +13,6 @@ #define FUSE_INT_REQ_BIT (1ULL << 0) #define FUSE_REQ_ID_STEP (1ULL << 1) -extern struct wait_queue_head fuse_dev_waitq; - struct fuse_arg; struct fuse_args; struct fuse_pqueue; diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c index 54059404e7c4..5695a7dca051 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c @@ -34,7 +34,6 @@ MODULE_LICENSE("GPL"); static struct kmem_cache *fuse_inode_cachep; struct list_head fuse_conn_list; DEFINE_MUTEX(fuse_mutex); -DECLARE_WAIT_QUEUE_HEAD(fuse_dev_waitq); static int set_global_limit(const char *val, const struct kernel_param *kp); @@ -1789,10 +1788,9 @@ int fuse_fill_super_common(struct super_block *sb, struct fuse_fs_context *ctx) list_add_tail(&fc->entry, &fuse_conn_list); sb->s_root = root_dentry; - if (fud) { + if (fud) fuse_dev_install(fud, fc); - wake_up_all(&fuse_dev_waitq); - } + mutex_unlock(&fuse_mutex); return 0; -- cgit v1.2.3 From d1f2721f8c9fc83ed8fb489402eb81ee96175770 Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Tue, 24 Mar 2026 13:07:07 +0100 Subject: fuse: remove #include "fuse_i.h" from "dev_uring_i.h" Start getting rid of fs layer stuff from transport layer files. Signed-off-by: Miklos Szeredi --- fs/fuse/dev.c | 8 ++++---- fs/fuse/dev_uring_i.h | 19 +++++++++---------- fs/fuse/fuse_dev_i.h | 4 ++++ 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index 96f0815b4df4..586d6fe46e31 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -83,7 +83,7 @@ void fuse_chan_set_initialized(struct fuse_chan *fch) static bool fuse_block_alloc(struct fuse_conn *fc, bool for_background) { return !fc->chan->initialized || (for_background && fc->chan->blocked) || - (fc->chan->io_uring && fc->chan->connected && !fuse_uring_ready(fc)); + (fc->chan->io_uring && fc->chan->connected && !fuse_uring_ready(fc->chan)); } static void fuse_drop_waiting(struct fuse_conn *fc) @@ -884,7 +884,7 @@ static int fuse_request_queue_background(struct fuse_req *req) __set_bit(FR_ISREPLY, &req->flags); #ifdef CONFIG_FUSE_IO_URING - if (fuse_uring_ready(fc)) + if (fuse_uring_ready(fc->chan)) return fuse_request_queue_background_uring(fc, req); #endif @@ -2673,7 +2673,7 @@ void fuse_abort_conn(struct fuse_conn *fc) * fc->chan->lock must not be taken to avoid conflicts with io-uring * locks */ - fuse_uring_abort(fc); + fuse_uring_abort(fc->chan); } else { spin_unlock(&fc->chan->lock); } @@ -2686,7 +2686,7 @@ void fuse_wait_aborted(struct fuse_conn *fc) smp_mb(); wait_event(fc->chan->blocked_waitq, fuse_chan_num_waiting(fc->chan) == 0); - fuse_uring_wait_stopped_queues(fc); + fuse_uring_wait_stopped_queues(fc->chan); } int fuse_dev_release(struct inode *inode, struct file *file) diff --git a/fs/fuse/dev_uring_i.h b/fs/fuse/dev_uring_i.h index 8ba149e35423..c8649c4f30e6 100644 --- a/fs/fuse/dev_uring_i.h +++ b/fs/fuse/dev_uring_i.h @@ -7,7 +7,6 @@ #ifndef _FS_FUSE_DEV_URING_I_H #define _FS_FUSE_DEV_URING_I_H -#include "fuse_i.h" #include "fuse_dev_i.h" #ifdef CONFIG_FUSE_IO_URING @@ -144,9 +143,9 @@ bool fuse_uring_queue_bq_req(struct fuse_req *req); bool fuse_uring_remove_pending_req(struct fuse_req *req); bool fuse_uring_request_expired(struct fuse_chan *fch); -static inline void fuse_uring_abort(struct fuse_conn *fc) +static inline void fuse_uring_abort(struct fuse_chan *fch) { - struct fuse_ring *ring = fc->chan->ring; + struct fuse_ring *ring = fch->ring; if (ring == NULL) return; @@ -157,33 +156,33 @@ static inline void fuse_uring_abort(struct fuse_conn *fc) fuse_uring_stop_queues(ring); } -static inline void fuse_uring_wait_stopped_queues(struct fuse_conn *fc) +static inline void fuse_uring_wait_stopped_queues(struct fuse_chan *fch) { - struct fuse_ring *ring = fc->chan->ring; + struct fuse_ring *ring = fch->ring; if (ring) wait_event(ring->stop_waitq, atomic_read(&ring->queue_refs) == 0); } -static inline bool fuse_uring_ready(struct fuse_conn *fc) +static inline bool fuse_uring_ready(struct fuse_chan *fch) { - struct fuse_ring *ring = READ_ONCE(fc->chan->ring); + struct fuse_ring *ring = READ_ONCE(fch->ring); return ring && smp_load_acquire(&ring->ready); } #else /* CONFIG_FUSE_IO_URING */ -static inline void fuse_uring_abort(struct fuse_conn *fc) +static inline void fuse_uring_abort(struct fuse_chan *fch) { } -static inline void fuse_uring_wait_stopped_queues(struct fuse_conn *fc) +static inline void fuse_uring_wait_stopped_queues(struct fuse_chan *fch) { } -static inline bool fuse_uring_ready(struct fuse_conn *fc) +static inline bool fuse_uring_ready(struct fuse_chan *fch) { return false; } diff --git a/fs/fuse/fuse_dev_i.h b/fs/fuse/fuse_dev_i.h index 846abb063c69..a0eb9cac9b2f 100644 --- a/fs/fuse/fuse_dev_i.h +++ b/fs/fuse/fuse_dev_i.h @@ -8,6 +8,10 @@ #include #include +#include +#include +#include +#include /* Ordinary requests have even IDs, while interrupts IDs are odd */ #define FUSE_INT_REQ_BIT (1ULL << 0) -- cgit v1.2.3 From a3a3e06bfbdd44317bb61993f4d981c1cd2f00c9 Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Tue, 24 Mar 2026 13:16:34 +0100 Subject: fuse: remove #include "fuse_i.h" from "req_timeout.c" Just need to move fuse_abort_conn(). Signed-off-by: Miklos Szeredi --- fs/fuse/dev.h | 3 +++ fs/fuse/fuse_i.h | 2 -- fs/fuse/req_timeout.c | 1 - 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/fs/fuse/dev.h b/fs/fuse/dev.h index 1d3d2e0c9b91..efdaf0bf6f0f 100644 --- a/fs/fuse/dev.h +++ b/fs/fuse/dev.h @@ -41,6 +41,9 @@ struct fuse_dev *fuse_dev_grab(struct file *file); void fuse_init_server_timeout(struct fuse_chan *fch, unsigned int timeout); +/* Abort all requests */ +void fuse_abort_conn(struct fuse_conn *fc); + #ifdef CONFIG_FUSE_IO_URING bool fuse_uring_enabled(void); void fuse_uring_destruct(struct fuse_chan *fch); diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index a0d120057ddb..af8e05a403e5 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -996,8 +996,6 @@ static inline ssize_t fuse_simple_idmap_request(struct mnt_idmap *idmap, int fuse_simple_background(struct fuse_mount *fm, struct fuse_args *args, gfp_t gfp_flags); -/* Abort all requests */ -void fuse_abort_conn(struct fuse_conn *fc); void fuse_wait_aborted(struct fuse_conn *fc); void fuse_dentry_tree_init(void); diff --git a/fs/fuse/req_timeout.c b/fs/fuse/req_timeout.c index ef464bebc0b6..9c48789b941a 100644 --- a/fs/fuse/req_timeout.c +++ b/fs/fuse/req_timeout.c @@ -2,7 +2,6 @@ #include "dev.h" #include "sysctl.h" -#include "fuse_i.h" #include "fuse_dev_i.h" #include "dev_uring_i.h" -- cgit v1.2.3 From 4eeb5e6cb0fd89b343fcf755ae3aad76fdb5b2c2 Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Tue, 24 Mar 2026 16:12:28 +0100 Subject: fuse: abort related layering cleanup - rename fuse_abort_conn() to fuse_chan_abort(), pass fuse_chan pointer instead of fuse_conn - pass an abort_with_err argument that tells fuse_dev_(read|write) to return with ECONNABORTED instead of ENODEV - move fc->aborted to fch->abort_with_err - rename fuse_wait_aborted() to fuse_chan_wait_aborted() Signed-off-by: Miklos Szeredi --- fs/fuse/control.c | 4 +-- fs/fuse/cuse.c | 4 +-- fs/fuse/dev.c | 84 ++++++++++++++++++++++++++------------------------- fs/fuse/dev.h | 3 +- fs/fuse/dev_uring.c | 2 +- fs/fuse/fuse_dev_i.h | 3 ++ fs/fuse/fuse_i.h | 5 --- fs/fuse/inode.c | 6 ++-- fs/fuse/req_timeout.c | 12 ++++---- 9 files changed, 61 insertions(+), 62 deletions(-) diff --git a/fs/fuse/control.c b/fs/fuse/control.c index e6f513eb7d4a..925a15488499 100644 --- a/fs/fuse/control.c +++ b/fs/fuse/control.c @@ -38,9 +38,7 @@ static ssize_t fuse_conn_abort_write(struct file *file, const char __user *buf, { struct fuse_conn *fc = fuse_ctl_file_conn_get(file); if (fc) { - if (fc->abort_err) - fc->aborted = true; - fuse_abort_conn(fc); + fuse_chan_abort(fc->chan, fc->abort_err); fuse_conn_put(fc); } return count; diff --git a/fs/fuse/cuse.c b/fs/fuse/cuse.c index 950c070ab3e9..321ba7af0ec5 100644 --- a/fs/fuse/cuse.c +++ b/fs/fuse/cuse.c @@ -426,7 +426,7 @@ err_unlock: err_region: unregister_chrdev_region(devt, 1); err: - fuse_abort_conn(fc); + fuse_chan_abort(fc->chan, false); goto out; } @@ -596,7 +596,7 @@ static ssize_t cuse_class_abort_store(struct device *dev, { struct cuse_conn *cc = dev_get_drvdata(dev); - fuse_abort_conn(&cc->fc); + fuse_chan_abort(cc->fc.chan, false); return count; } static DEVICE_ATTR(abort, 0200, NULL, cuse_class_abort_store); diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index 586d6fe46e31..c4a6ee948282 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -90,7 +90,7 @@ static void fuse_drop_waiting(struct fuse_conn *fc) { /* * lockess check of fc->chan->connected is okay, because atomic_dec_and_test() - * provides a memory barrier matched with the one in fuse_wait_aborted() + * provides a memory barrier matched with the one in fuse_chan_wait_aborted() * to ensure no wake-up is missed. */ if (atomic_dec_and_test(&fc->chan->num_waiting) && @@ -570,17 +570,17 @@ void fuse_chan_queue_forget(struct fuse_chan *fch, struct fuse_forget_link *forg fiq->ops->send_forget(fiq, forget); } -static void flush_bg_queue(struct fuse_conn *fc) +static void flush_bg_queue(struct fuse_chan *fch) { - struct fuse_iqueue *fiq = &fc->chan->iq; + struct fuse_iqueue *fiq = &fch->iq; - while (fc->chan->active_background < fc->chan->max_background && - !list_empty(&fc->chan->bg_queue)) { + while (fch->active_background < fch->max_background && + !list_empty(&fch->bg_queue)) { struct fuse_req *req; - req = list_first_entry(&fc->chan->bg_queue, struct fuse_req, list); + req = list_first_entry(&fch->bg_queue, struct fuse_req, list); list_del(&req->list); - fc->chan->active_background++; + fch->active_background++; fuse_send_one(fiq, req); } } @@ -641,7 +641,7 @@ void fuse_request_end(struct fuse_req *req) if (test_bit(FR_BACKGROUND, &req->flags)) { spin_lock(&fc->chan->bg_lock); fuse_request_bg_finish(fc->chan, req); - flush_bg_queue(fc); + flush_bg_queue(fc->chan); spin_unlock(&fc->chan->bg_lock); } else { /* Wake up waiter sleeping in request_wait_answer() */ @@ -716,7 +716,7 @@ static void request_wait_answer(struct fuse_req *req) return; if (req->args->abort_on_kill) { - fuse_abort_conn(fc); + fuse_chan_abort(fc->chan, false); return; } @@ -894,7 +894,7 @@ static int fuse_request_queue_background(struct fuse_req *req) if (fc->chan->num_background == fc->chan->max_background) fc->chan->blocked = 1; list_add_tail(&req->list, &fc->chan->bg_queue); - flush_bg_queue(fc); + flush_bg_queue(fc->chan); queued = true; } spin_unlock(&fc->chan->bg_lock); @@ -1590,7 +1590,7 @@ static ssize_t fuse_dev_do_read(struct fuse_dev *fud, struct file *file, } if (!fiq->connected) { - err = fc->aborted ? -ECONNABORTED : -ENODEV; + err = fc->chan->abort_with_err ? -ECONNABORTED : -ENODEV; goto err_unlock; } @@ -1628,7 +1628,7 @@ static ssize_t fuse_dev_do_read(struct fuse_dev *fud, struct file *file, spin_lock(&fpq->lock); /* * Must not put request on fpq->io queue after having been shut down by - * fuse_abort_conn() + * fuse_chan_abort() */ if (!fpq->connected) { req->out.h.error = err = -ECONNABORTED; @@ -1646,7 +1646,7 @@ static ssize_t fuse_dev_do_read(struct fuse_dev *fud, struct file *file, spin_lock(&fpq->lock); clear_bit(FR_LOCKED, &req->flags); if (!fpq->connected) { - err = fc->aborted ? -ECONNABORTED : -ENODEV; + err = fc->chan->abort_with_err ? -ECONNABORTED : -ENODEV; goto out_end; } if (err) { @@ -2606,27 +2606,29 @@ static void end_polls(struct fuse_conn *fc) * is OK, the request will in that case be removed from the list before we touch * it. */ -void fuse_abort_conn(struct fuse_conn *fc) +void fuse_chan_abort(struct fuse_chan *fch, bool abort_with_err) { - struct fuse_iqueue *fiq = &fc->chan->iq; + struct fuse_iqueue *fiq = &fch->iq; - spin_lock(&fc->chan->lock); - if (fc->chan->connected) { + fch->abort_with_err = abort_with_err; + + spin_lock(&fch->lock); + if (fch->connected) { struct fuse_dev *fud; struct fuse_req *req, *next; LIST_HEAD(to_end); unsigned int i; - if (fc->chan->timeout.req_timeout) - cancel_delayed_work(&fc->chan->timeout.work); + if (fch->timeout.req_timeout) + cancel_delayed_work(&fch->timeout.work); - /* Background queuing checks fc->chan->connected under bg_lock */ - spin_lock(&fc->chan->bg_lock); - fc->chan->connected = 0; - spin_unlock(&fc->chan->bg_lock); + /* Background queuing checks fch->connected under bg_lock */ + spin_lock(&fch->bg_lock); + fch->connected = 0; + spin_unlock(&fch->bg_lock); - fuse_chan_set_initialized(fc->chan); - list_for_each_entry(fud, &fc->chan->devices, entry) { + fuse_chan_set_initialized(fch); + list_for_each_entry(fud, &fch->devices, entry) { struct fuse_pqueue *fpq = &fud->pq; spin_lock(&fpq->lock); @@ -2647,11 +2649,11 @@ void fuse_abort_conn(struct fuse_conn *fc) &to_end); spin_unlock(&fpq->lock); } - spin_lock(&fc->chan->bg_lock); - fc->chan->blocked = 0; - fc->chan->max_background = UINT_MAX; - flush_bg_queue(fc); - spin_unlock(&fc->chan->bg_lock); + spin_lock(&fch->bg_lock); + fch->blocked = 0; + fch->max_background = UINT_MAX; + flush_bg_queue(fch); + spin_unlock(&fch->bg_lock); spin_lock(&fiq->lock); fiq->connected = 0; @@ -2663,30 +2665,30 @@ void fuse_abort_conn(struct fuse_conn *fc) wake_up_all(&fiq->waitq); spin_unlock(&fiq->lock); kill_fasync(&fiq->fasync, SIGIO, POLL_IN); - end_polls(fc); - wake_up_all(&fc->chan->blocked_waitq); - spin_unlock(&fc->chan->lock); + end_polls(fch->conn); + wake_up_all(&fch->blocked_waitq); + spin_unlock(&fch->lock); fuse_dev_end_requests(&to_end); /* - * fc->chan->lock must not be taken to avoid conflicts with io-uring + * fch->lock must not be taken to avoid conflicts with io-uring * locks */ - fuse_uring_abort(fc->chan); + fuse_uring_abort(fch); } else { - spin_unlock(&fc->chan->lock); + spin_unlock(&fch->lock); } } -EXPORT_SYMBOL_GPL(fuse_abort_conn); +EXPORT_SYMBOL_GPL(fuse_chan_abort); -void fuse_wait_aborted(struct fuse_conn *fc) +void fuse_chan_wait_aborted(struct fuse_chan *fch) { /* matches implicit memory barrier in fuse_drop_waiting() */ smp_mb(); - wait_event(fc->chan->blocked_waitq, fuse_chan_num_waiting(fc->chan) == 0); + wait_event(fch->blocked_waitq, fuse_chan_num_waiting(fch) == 0); - fuse_uring_wait_stopped_queues(fc->chan); + fuse_uring_wait_stopped_queues(fch); } int fuse_dev_release(struct inode *inode, struct file *file) @@ -2717,7 +2719,7 @@ int fuse_dev_release(struct inode *inode, struct file *file) if (last) { WARN_ON(fc->chan->iq.fasync != NULL); - fuse_abort_conn(fc); + fuse_chan_abort(fc->chan, false); } fuse_conn_put(fc); } diff --git a/fs/fuse/dev.h b/fs/fuse/dev.h index efdaf0bf6f0f..a0c1212573d6 100644 --- a/fs/fuse/dev.h +++ b/fs/fuse/dev.h @@ -42,7 +42,8 @@ struct fuse_dev *fuse_dev_grab(struct file *file); void fuse_init_server_timeout(struct fuse_chan *fch, unsigned int timeout); /* Abort all requests */ -void fuse_abort_conn(struct fuse_conn *fc); +void fuse_chan_abort(struct fuse_chan *fch, bool abort_with_err); +void fuse_chan_wait_aborted(struct fuse_chan *fch); #ifdef CONFIG_FUSE_IO_URING bool fuse_uring_enabled(void); diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c index 296da4ff233c..71608a5beead 100644 --- a/fs/fuse/dev_uring.c +++ b/fs/fuse/dev_uring.c @@ -1189,7 +1189,7 @@ int fuse_uring_cmd(struct io_uring_cmd *cmd, unsigned int issue_flags) return -EOPNOTSUPP; } - if (fc->aborted) + if (fc->chan->abort_with_err) return -ECONNABORTED; if (!fc->chan->connected) return -ENOTCONN; diff --git a/fs/fuse/fuse_dev_i.h b/fs/fuse/fuse_dev_i.h index a0eb9cac9b2f..34cf0ac411b7 100644 --- a/fs/fuse/fuse_dev_i.h +++ b/fs/fuse/fuse_dev_i.h @@ -238,6 +238,9 @@ struct fuse_chan { /* Use io_uring for communication */ unsigned int io_uring; + /** Connection aborted via sysfs, respond with ECONNABORTED on device I/O */ + bool abort_with_err; + #ifdef CONFIG_FUSE_IO_URING /** uring connection information*/ struct fuse_ring *ring; diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index af8e05a403e5..b8655c3b48e2 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -483,9 +483,6 @@ struct fuse_conn { /** Number of background requests at which congestion starts */ unsigned congestion_threshold; - /** Connection aborted via sysfs */ - bool aborted; - /** Connection failed (version mismatch). Cannot race with setting other bitfields since it is only set once in INIT reply, before any other request, and never cleared */ @@ -996,8 +993,6 @@ static inline ssize_t fuse_simple_idmap_request(struct mnt_idmap *idmap, int fuse_simple_background(struct fuse_mount *fm, struct fuse_args *args, gfp_t gfp_flags); -void fuse_wait_aborted(struct fuse_conn *fc); - void fuse_dentry_tree_init(void); void fuse_dentry_tree_cleanup(void); diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c index 5695a7dca051..66e37af3a6dd 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c @@ -612,7 +612,7 @@ static void fuse_umount_begin(struct super_block *sb) if (fc->no_force_umount) return; - fuse_abort_conn(fc); + fuse_chan_abort(fc->chan, false); // Only retire block-device-based superblocks. if (sb->s_bdev != NULL) @@ -1952,8 +1952,8 @@ void fuse_conn_destroy(struct fuse_mount *fm) if (fc->destroy) fuse_send_destroy(fm); - fuse_abort_conn(fc); - fuse_wait_aborted(fc); + fuse_chan_abort(fc->chan, false); + fuse_chan_wait_aborted(fc->chan); if (!list_empty(&fc->entry)) { mutex_lock(&fuse_mutex); diff --git a/fs/fuse/req_timeout.c b/fs/fuse/req_timeout.c index 9c48789b941a..6cc6fc491343 100644 --- a/fs/fuse/req_timeout.c +++ b/fs/fuse/req_timeout.c @@ -79,13 +79,13 @@ static void fuse_check_timeout(struct work_struct *work) expired = fuse_request_expired(fch, &fiq->pending); spin_unlock(&fiq->lock); if (expired) - goto abort_conn; + goto chan_abort; spin_lock(&fch->bg_lock); expired = fuse_request_expired(fch, &fch->bg_queue); spin_unlock(&fch->bg_lock); if (expired) - goto abort_conn; + goto chan_abort; spin_lock(&fch->lock); if (!fch->connected) { @@ -99,7 +99,7 @@ static void fuse_check_timeout(struct work_struct *work) fuse_fpq_processing_expired(fch, fpq->processing)) { spin_unlock(&fpq->lock); spin_unlock(&fch->lock); - goto abort_conn; + goto chan_abort; } spin_unlock(&fpq->lock); @@ -107,15 +107,15 @@ static void fuse_check_timeout(struct work_struct *work) spin_unlock(&fch->lock); if (fuse_uring_request_expired(fch)) - goto abort_conn; + goto chan_abort; out: queue_delayed_work(system_percpu_wq, &fch->timeout.work, fuse_timeout_timer_freq); return; -abort_conn: - fuse_abort_conn(fch->conn); +chan_abort: + fuse_chan_abort(fch, false); } static void set_request_timeout(struct fuse_chan *fch, unsigned int timeout) -- cgit v1.2.3 From 229f9b9b66ab5be9e015422cf30b97740cfbdc8d Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Thu, 26 Mar 2026 14:47:20 +0100 Subject: fuse: split off fuse_args and related definitions into a separate header This is going to be used by both layers (transport and filesystem) Signed-off-by: Miklos Szeredi --- fs/fuse/args.h | 65 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ fs/fuse/fuse_i.h | 53 +-------------------------------------------- 2 files changed, 66 insertions(+), 52 deletions(-) create mode 100644 fs/fuse/args.h diff --git a/fs/fuse/args.h b/fs/fuse/args.h new file mode 100644 index 000000000000..12c94ece569f --- /dev/null +++ b/fs/fuse/args.h @@ -0,0 +1,65 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef _FS_FUSE_ARGS_H +#define _FS_FUSE_ARGS_H + +#include + +struct fuse_mount; + +/** One input argument of a request */ +struct fuse_in_arg { + unsigned size; + const void *value; +}; + +/** One output argument of a request */ +struct fuse_arg { + unsigned size; + void *value; +}; + +struct fuse_args { + u64 nodeid; + u32 opcode; + u32 uid; + u32 gid; + u32 pid; + u8 in_numargs; + u8 out_numargs; + u8 ext_idx; + bool force:1; + bool noreply:1; + bool nocreds:1; + bool in_pages:1; + bool out_pages:1; + bool user_pages:1; + bool out_argvar:1; + bool page_zeroing:1; + bool page_replace:1; + bool may_block:1; + bool is_ext:1; + bool is_pinned:1; + bool invalidate_vmap:1; + bool abort_on_kill:1; + struct fuse_in_arg in_args[4]; + struct fuse_arg out_args[2]; + void (*end)(struct fuse_mount *fm, struct fuse_args *args, int error); + /* Used for kvec iter backed by vmalloc address */ + void *vmap_base; +}; + +/** FUSE folio descriptor */ +struct fuse_folio_desc { + unsigned int length; + unsigned int offset; +}; + +struct fuse_args_pages { + struct fuse_args args; + struct folio **folios; + struct fuse_folio_desc *descs; + unsigned int num_folios; +}; + +#endif /* _FS_FUSE_ARGS_H */ diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index b8655c3b48e2..22a6d58678ad 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -13,6 +13,7 @@ # define pr_fmt(fmt) "fuse: " fmt #endif +#include "args.h" #include #include #include @@ -288,58 +289,6 @@ struct fuse_file { bool flock:1; }; -/** One input argument of a request */ -struct fuse_in_arg { - unsigned size; - const void *value; -}; - -/** One output argument of a request */ -struct fuse_arg { - unsigned size; - void *value; -}; - -/** FUSE folio descriptor */ -struct fuse_folio_desc { - unsigned int length; - unsigned int offset; -}; - -struct fuse_args { - uint64_t nodeid; - uint32_t opcode; - uint8_t in_numargs; - uint8_t out_numargs; - uint8_t ext_idx; - bool force:1; - bool noreply:1; - bool nocreds:1; - bool in_pages:1; - bool out_pages:1; - bool user_pages:1; - bool out_argvar:1; - bool page_zeroing:1; - bool page_replace:1; - bool may_block:1; - bool is_ext:1; - bool is_pinned:1; - bool invalidate_vmap:1; - bool abort_on_kill:1; - struct fuse_in_arg in_args[4]; - struct fuse_arg out_args[2]; - void (*end)(struct fuse_mount *fm, struct fuse_args *args, int error); - /* Used for kvec iter backed by vmalloc address */ - void *vmap_base; -}; - -struct fuse_args_pages { - struct fuse_args args; - struct folio **folios; - struct fuse_folio_desc *descs; - unsigned int num_folios; -}; - struct fuse_release_args { struct fuse_args args; struct fuse_release_in inarg; -- cgit v1.2.3 From a697d95fcdbb3bbe25cdc29db5542ddcebb831c1 Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Thu, 26 Mar 2026 16:11:52 +0100 Subject: fuse: remove fm arg of args->end callback Only used by FUSE_INIT and CUSE_INIT, these can store the relevant pointer in their structs derived from fuse_args. Signed-off-by: Miklos Szeredi --- fs/fuse/args.h | 2 +- fs/fuse/cuse.c | 7 ++++--- fs/fuse/dev.c | 7 +++---- fs/fuse/file.c | 22 +++++++++------------- fs/fuse/inode.c | 10 ++++++---- 5 files changed, 23 insertions(+), 25 deletions(-) diff --git a/fs/fuse/args.h b/fs/fuse/args.h index 12c94ece569f..ecfe51a192af 100644 --- a/fs/fuse/args.h +++ b/fs/fuse/args.h @@ -44,7 +44,7 @@ struct fuse_args { bool abort_on_kill:1; struct fuse_in_arg in_args[4]; struct fuse_arg out_args[2]; - void (*end)(struct fuse_mount *fm, struct fuse_args *args, int error); + void (*end)(struct fuse_args *args, int error); /* Used for kvec iter backed by vmalloc address */ void *vmap_base; }; diff --git a/fs/fuse/cuse.c b/fs/fuse/cuse.c index 321ba7af0ec5..f66ee8c2b310 100644 --- a/fs/fuse/cuse.c +++ b/fs/fuse/cuse.c @@ -307,6 +307,7 @@ struct cuse_init_args { struct cuse_init_out out; struct folio *folio; struct fuse_folio_desc desc; + struct fuse_conn *fc; }; /** @@ -320,11 +321,10 @@ struct cuse_init_args { * required data structures for it. Please read the comment at the * top of this file for high level overview. */ -static void cuse_process_init_reply(struct fuse_mount *fm, - struct fuse_args *args, int error) +static void cuse_process_init_reply(struct fuse_args *args, int error) { - struct fuse_conn *fc = fm->fc; struct cuse_init_args *ia = container_of(args, typeof(*ia), ap.args); + struct fuse_conn *fc = ia->fc; struct fuse_args_pages *ap = &ia->ap; struct cuse_conn *cc = fc_to_cc(fc), *pos; struct cuse_init_out *arg = &ia->out; @@ -469,6 +469,7 @@ static int cuse_send_init(struct cuse_conn *cc) ap->descs = &ia->desc; ia->folio = folio; ia->desc.length = ap->args.out_args[1].size; + ia->fc = &cc->fc; ap->args.end = cuse_process_init_reply; rc = fuse_simple_background(fm, &ap->args, GFP_KERNEL); diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index c4a6ee948282..d933d030f78c 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -649,7 +649,7 @@ void fuse_request_end(struct fuse_req *req) } if (test_bit(FR_ASYNC, &req->flags)) - req->args->end(fm, req->args, req->out.h.error); + req->args->end(req->args, req->out.h.error); put_request: fuse_put_request(req); } @@ -1988,8 +1988,7 @@ struct fuse_retrieve_args { struct fuse_notify_retrieve_in inarg; }; -static void fuse_retrieve_end(struct fuse_mount *fm, struct fuse_args *args, - int error) +static void fuse_retrieve_end(struct fuse_args *args, int error) { struct fuse_retrieve_args *ra = container_of(args, typeof(*ra), ap.args); @@ -2076,7 +2075,7 @@ static int fuse_retrieve(struct fuse_mount *fm, struct inode *inode, err = fuse_simple_notify_reply(fm, args, outarg->notify_unique); if (err) - fuse_retrieve_end(fm, args, err); + fuse_retrieve_end(args, err); return err; } diff --git a/fs/fuse/file.c b/fs/fuse/file.c index c603cb0070f2..057ff884500d 100644 --- a/fs/fuse/file.c +++ b/fs/fuse/file.c @@ -92,8 +92,7 @@ static struct fuse_file *fuse_file_get(struct fuse_file *ff) return ff; } -static void fuse_release_end(struct fuse_mount *fm, struct fuse_args *args, - int error) +static void fuse_release_end(struct fuse_args *args, int error) { struct fuse_release_args *ra = container_of(args, typeof(*ra), args); @@ -113,10 +112,10 @@ static void fuse_file_put(struct fuse_file *ff, bool sync) if (!args) { /* Do nothing when server does not implement 'opendir' */ } else if (args->opcode == FUSE_RELEASE && ff->fm->fc->no_open) { - fuse_release_end(ff->fm, args, 0); + fuse_release_end(args, 0); } else if (sync) { fuse_simple_request(ff->fm, args); - fuse_release_end(ff->fm, args, 0); + fuse_release_end(args, 0); } else { /* * DAX inodes may need to issue a number of synchronous @@ -127,7 +126,7 @@ static void fuse_file_put(struct fuse_file *ff, bool sync) args->end = fuse_release_end; if (fuse_simple_background(ff->fm, args, GFP_KERNEL | __GFP_NOFAIL)) - fuse_release_end(ff->fm, args, -ENOTCONN); + fuse_release_end(args, -ENOTCONN); } kfree(ff); } @@ -716,8 +715,7 @@ static void fuse_io_free(struct fuse_io_args *ia) kfree(ia); } -static void fuse_aio_complete_req(struct fuse_mount *fm, struct fuse_args *args, - int err) +static void fuse_aio_complete_req(struct fuse_args *args, int err) { struct fuse_io_args *ia = container_of(args, typeof(*ia), ap.args); struct fuse_io_priv *io = ia->io; @@ -765,7 +763,7 @@ static ssize_t fuse_async_req_send(struct fuse_mount *fm, ia->ap.args.may_block = io->should_dirty; err = fuse_simple_background(fm, &ia->ap.args, GFP_KERNEL); if (err) - fuse_aio_complete_req(fm, &ia->ap.args, err); + fuse_aio_complete_req(&ia->ap.args, err); return num_bytes; } @@ -1008,8 +1006,7 @@ static int fuse_iomap_read_folio_range(const struct iomap_iter *iter, return fuse_do_readfolio(file, folio, off, len); } -static void fuse_readpages_end(struct fuse_mount *fm, struct fuse_args *args, - int err) +static void fuse_readpages_end(struct fuse_args *args, int err) { int i; struct fuse_io_args *ia = container_of(args, typeof(*ia), ap.args); @@ -1075,7 +1072,7 @@ static void fuse_send_readpages(struct fuse_io_args *ia, struct file *file, res = fuse_simple_request(fm, &ap->args); err = res < 0 ? res : 0; } - fuse_readpages_end(fm, &ap->args, err); + fuse_readpages_end(&ap->args, err); } static void fuse_readahead(struct readahead_control *rac) @@ -1992,8 +1989,7 @@ __acquires(fi->lock) } } -static void fuse_writepage_end(struct fuse_mount *fm, struct fuse_args *args, - int error) +static void fuse_writepage_end(struct fuse_args *args, int error) { struct fuse_writepage_args *wpa = container_of(args, typeof(*wpa), ia.ap.args); diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c index 66e37af3a6dd..8d689f84e533 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c @@ -1263,13 +1263,14 @@ struct fuse_init_args { struct fuse_args args; struct fuse_init_in in; struct fuse_init_out out; + struct fuse_mount *fm; }; -static void process_init_reply(struct fuse_mount *fm, struct fuse_args *args, - int error) +static void process_init_reply(struct fuse_args *args, int error) { - struct fuse_conn *fc = fm->fc; struct fuse_init_args *ia = container_of(args, typeof(*ia), args); + struct fuse_mount *fm = ia->fm; + struct fuse_conn *fc = fm->fc; struct fuse_init_out *arg = &ia->out; bool ok = true; @@ -1437,6 +1438,7 @@ static struct fuse_init_args *fuse_new_init(struct fuse_mount *fm) ia = kzalloc_obj(*ia, GFP_KERNEL | __GFP_NOFAIL); + ia->fm = fm; ia->in.major = FUSE_KERNEL_VERSION; ia->in.minor = FUSE_KERNEL_MINOR_VERSION; ia->in.max_readahead = fm->sb->s_bdi->ra_pages * PAGE_SIZE; @@ -1510,7 +1512,7 @@ int fuse_send_init(struct fuse_mount *fm) if (!err) return 0; } - process_init_reply(fm, &ia->args, err); + process_init_reply(&ia->args, err); if (fm->fc->conn_error) return -ENOTCONN; return 0; -- cgit v1.2.3 From 28d733cc3ecad3a48e67c0d2bf8cbd1268bf30c6 Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Mon, 30 Mar 2026 12:36:59 +0200 Subject: fuse: change req->fm to req->chan Store a struct fuse_chan pointer in fuse_req instead of a struct fuse_mount pointer. Signed-off-by: Miklos Szeredi --- fs/fuse/dev.c | 101 +++++++++++++++++++++++++-------------------------- fs/fuse/dev_uring.c | 17 ++++----- fs/fuse/fuse_dev_i.h | 4 +- fs/fuse/fuse_trace.h | 4 +- 4 files changed, 61 insertions(+), 65 deletions(-) diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index d933d030f78c..21aa39923a1a 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -35,22 +35,22 @@ static DECLARE_WAIT_QUEUE_HEAD(fuse_dev_waitq); static struct kmem_cache *fuse_req_cachep; -static void fuse_request_init(struct fuse_mount *fm, struct fuse_req *req) +static void fuse_request_init(struct fuse_chan *fch, struct fuse_req *req) { INIT_LIST_HEAD(&req->list); INIT_LIST_HEAD(&req->intr_entry); init_waitqueue_head(&req->waitq); refcount_set(&req->count, 1); __set_bit(FR_PENDING, &req->flags); - req->fm = fm; + req->chan = fch; req->create_time = jiffies; } -static struct fuse_req *fuse_request_alloc(struct fuse_mount *fm, gfp_t flags) +static struct fuse_req *fuse_request_alloc(struct fuse_chan *fch, gfp_t flags) { struct fuse_req *req = kmem_cache_zalloc(fuse_req_cachep, flags); if (req) - fuse_request_init(fm, req); + fuse_request_init(fch, req); return req; } @@ -86,17 +86,17 @@ static bool fuse_block_alloc(struct fuse_conn *fc, bool for_background) (fc->chan->io_uring && fc->chan->connected && !fuse_uring_ready(fc->chan)); } -static void fuse_drop_waiting(struct fuse_conn *fc) +static void fuse_drop_waiting(struct fuse_chan *fch) { /* - * lockess check of fc->chan->connected is okay, because atomic_dec_and_test() + * lockess check of fch->connected is okay, because atomic_dec_and_test() * provides a memory barrier matched with the one in fuse_chan_wait_aborted() * to ensure no wake-up is missed. */ - if (atomic_dec_and_test(&fc->chan->num_waiting) && - !READ_ONCE(fc->chan->connected)) { + if (atomic_dec_and_test(&fch->num_waiting) && + !READ_ONCE(fch->connected)) { /* wake up aborters */ - wake_up_all(&fc->chan->blocked_waitq); + wake_up_all(&fch->blocked_waitq); } } @@ -133,7 +133,7 @@ static struct fuse_req *fuse_get_req(struct mnt_idmap *idmap, if (fc->conn_error) goto out; - req = fuse_request_alloc(fm, GFP_KERNEL); + req = fuse_request_alloc(fc->chan, GFP_KERNEL); err = -ENOMEM; if (!req) { if (for_background) @@ -170,13 +170,13 @@ static struct fuse_req *fuse_get_req(struct mnt_idmap *idmap, return req; out: - fuse_drop_waiting(fc); + fuse_drop_waiting(fc->chan); return ERR_PTR(err); } static void fuse_put_request(struct fuse_req *req) { - struct fuse_conn *fc = req->fm->fc; + struct fuse_chan *fch = req->chan; if (refcount_dec_and_test(&req->count)) { if (test_bit(FR_BACKGROUND, &req->flags)) { @@ -184,15 +184,15 @@ static void fuse_put_request(struct fuse_req *req) * We get here in the unlikely case that a background * request was allocated but not sent */ - spin_lock(&fc->chan->bg_lock); - if (!fc->chan->blocked) - wake_up(&fc->chan->blocked_waitq); - spin_unlock(&fc->chan->bg_lock); + spin_lock(&fch->bg_lock); + if (!fch->blocked) + wake_up(&fch->blocked_waitq); + spin_unlock(&fch->bg_lock); } if (test_bit(FR_WAITING, &req->flags)) { __clear_bit(FR_WAITING, &req->flags); - fuse_drop_waiting(fc); + fuse_drop_waiting(fch); } fuse_request_free(req); @@ -618,9 +618,8 @@ void fuse_request_bg_finish(struct fuse_chan *fch, struct fuse_req *req) */ void fuse_request_end(struct fuse_req *req) { - struct fuse_mount *fm = req->fm; - struct fuse_conn *fc = fm->fc; - struct fuse_iqueue *fiq = &fc->chan->iq; + struct fuse_chan *fch = req->chan; + struct fuse_iqueue *fiq = &fch->iq; if (test_and_set_bit(FR_FINISHED, &req->flags)) goto put_request; @@ -639,10 +638,10 @@ void fuse_request_end(struct fuse_req *req) WARN_ON(test_bit(FR_PENDING, &req->flags)); WARN_ON(test_bit(FR_SENT, &req->flags)); if (test_bit(FR_BACKGROUND, &req->flags)) { - spin_lock(&fc->chan->bg_lock); - fuse_request_bg_finish(fc->chan, req); - flush_bg_queue(fc->chan); - spin_unlock(&fc->chan->bg_lock); + spin_lock(&fch->bg_lock); + fuse_request_bg_finish(fch, req); + flush_bg_queue(fch); + spin_unlock(&fch->bg_lock); } else { /* Wake up waiter sleeping in request_wait_answer() */ wake_up(&req->waitq); @@ -657,7 +656,7 @@ EXPORT_SYMBOL_GPL(fuse_request_end); static int queue_interrupt(struct fuse_req *req) { - struct fuse_iqueue *fiq = &req->fm->fc->chan->iq; + struct fuse_iqueue *fiq = &req->chan->iq; /* Check for we've sent request to interrupt this req */ if (unlikely(!test_bit(FR_INTERRUPTED, &req->flags))) @@ -688,11 +687,11 @@ bool fuse_remove_pending_req(struct fuse_req *req, spinlock_t *lock) static void request_wait_answer(struct fuse_req *req) { - struct fuse_conn *fc = req->fm->fc; - struct fuse_iqueue *fiq = &fc->chan->iq; + struct fuse_chan *fch = req->chan; + struct fuse_iqueue *fiq = &fch->iq; int err; - if (!fc->chan->no_interrupt) { + if (!fch->no_interrupt) { /* Any signal may interrupt this */ err = wait_event_interruptible(req->waitq, test_bit(FR_FINISHED, &req->flags)); @@ -716,7 +715,7 @@ static void request_wait_answer(struct fuse_req *req) return; if (req->args->abort_on_kill) { - fuse_chan_abort(fc->chan, false); + fuse_chan_abort(fch, false); return; } @@ -737,7 +736,7 @@ static void request_wait_answer(struct fuse_req *req) static void __fuse_request_send(struct fuse_req *req) { - struct fuse_iqueue *fiq = &req->fm->fc->chan->iq; + struct fuse_iqueue *fiq = &req->chan->iq; BUG_ON(test_bit(FR_BACKGROUND, &req->flags)); @@ -784,11 +783,11 @@ static void fuse_adjust_compat(struct fuse_conn *fc, struct fuse_args *args) } } -static void fuse_force_creds(struct fuse_req *req) +static void fuse_force_creds(struct fuse_mount *fm, struct fuse_req *req) { - struct fuse_conn *fc = req->fm->fc; + struct fuse_conn *fc = fm->fc; - if (!req->fm->sb || req->fm->sb->s_iflags & SB_I_NOIDMAP) { + if (!fm->sb || fm->sb->s_iflags & SB_I_NOIDMAP) { req->in.h.uid = from_kuid_munged(fc->user_ns, current_fsuid()); req->in.h.gid = from_kgid_munged(fc->user_ns, current_fsgid()); } else { @@ -820,10 +819,10 @@ ssize_t __fuse_simple_request(struct mnt_idmap *idmap, if (args->force) { atomic_inc(&fc->chan->num_waiting); - req = fuse_request_alloc(fm, GFP_KERNEL | __GFP_NOFAIL); + req = fuse_request_alloc(fc->chan, GFP_KERNEL | __GFP_NOFAIL); if (!args->nocreds) - fuse_force_creds(req); + fuse_force_creds(fm, req); __set_bit(FR_WAITING, &req->flags); if (!args->abort_on_kill) @@ -853,10 +852,9 @@ ssize_t __fuse_simple_request(struct mnt_idmap *idmap, } #ifdef CONFIG_FUSE_IO_URING -static bool fuse_request_queue_background_uring(struct fuse_conn *fc, - struct fuse_req *req) +static bool fuse_request_queue_background_uring(struct fuse_req *req) { - struct fuse_iqueue *fiq = &fc->chan->iq; + struct fuse_iqueue *fiq = &req->chan->iq; req->in.h.len = sizeof(struct fuse_in_header) + fuse_len_args(req->args->in_numargs, @@ -872,32 +870,31 @@ static bool fuse_request_queue_background_uring(struct fuse_conn *fc, */ static int fuse_request_queue_background(struct fuse_req *req) { - struct fuse_mount *fm = req->fm; - struct fuse_conn *fc = fm->fc; + struct fuse_chan *fch = req->chan; bool queued = false; WARN_ON(!test_bit(FR_BACKGROUND, &req->flags)); if (!test_bit(FR_WAITING, &req->flags)) { __set_bit(FR_WAITING, &req->flags); - atomic_inc(&fc->chan->num_waiting); + atomic_inc(&fch->num_waiting); } __set_bit(FR_ISREPLY, &req->flags); #ifdef CONFIG_FUSE_IO_URING - if (fuse_uring_ready(fc->chan)) - return fuse_request_queue_background_uring(fc, req); + if (fuse_uring_ready(fch)) + return fuse_request_queue_background_uring(req); #endif - spin_lock(&fc->chan->bg_lock); - if (likely(fc->chan->connected)) { - fc->chan->num_background++; - if (fc->chan->num_background == fc->chan->max_background) - fc->chan->blocked = 1; - list_add_tail(&req->list, &fc->chan->bg_queue); - flush_bg_queue(fc->chan); + spin_lock(&fch->bg_lock); + if (likely(fch->connected)) { + fch->num_background++; + if (fch->num_background == fch->max_background) + fch->blocked = 1; + list_add_tail(&req->list, &fch->bg_queue); + flush_bg_queue(fch); queued = true; } - spin_unlock(&fc->chan->bg_lock); + spin_unlock(&fch->bg_lock); return queued; } @@ -909,7 +906,7 @@ int fuse_simple_background(struct fuse_mount *fm, struct fuse_args *args, if (args->force) { WARN_ON(!args->nocreds); - req = fuse_request_alloc(fm, gfp_flags); + req = fuse_request_alloc(fm->fc->chan, gfp_flags); if (!req) return -ENOMEM; __set_bit(FR_BACKGROUND, &req->flags); diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c index 71608a5beead..857c1df728e1 100644 --- a/fs/fuse/dev_uring.c +++ b/fs/fuse/dev_uring.c @@ -1307,8 +1307,7 @@ static void fuse_uring_dispatch_ent(struct fuse_ring_ent *ent) /* queue a fuse request and send it if a ring entry is available */ void fuse_uring_queue_fuse_req(struct fuse_iqueue *fiq, struct fuse_req *req) { - struct fuse_conn *fc = req->fm->fc; - struct fuse_ring *ring = fc->chan->ring; + struct fuse_ring *ring = req->chan->ring; struct fuse_ring_queue *queue; struct fuse_ring_ent *ent = NULL; int err; @@ -1350,8 +1349,8 @@ err: bool fuse_uring_queue_bq_req(struct fuse_req *req) { - struct fuse_conn *fc = req->fm->fc; - struct fuse_ring *ring = fc->chan->ring; + struct fuse_chan *fch = req->chan; + struct fuse_ring *ring = fch->ring; struct fuse_ring_queue *queue; struct fuse_ring_ent *ent = NULL; @@ -1371,12 +1370,12 @@ bool fuse_uring_queue_bq_req(struct fuse_req *req) ent = list_first_entry_or_null(&queue->ent_avail_queue, struct fuse_ring_ent, list); - spin_lock(&fc->chan->bg_lock); - fc->chan->num_background++; - if (fc->chan->num_background == fc->chan->max_background) - fc->chan->blocked = 1; + spin_lock(&fch->bg_lock); + fch->num_background++; + if (fch->num_background == fch->max_background) + fch->blocked = 1; fuse_uring_flush_bg(queue); - spin_unlock(&fc->chan->bg_lock); + spin_unlock(&fch->bg_lock); /* * Due to bg_queue flush limits there might be other bg requests diff --git a/fs/fuse/fuse_dev_i.h b/fs/fuse/fuse_dev_i.h index 34cf0ac411b7..e8659a3f2672 100644 --- a/fs/fuse/fuse_dev_i.h +++ b/fs/fuse/fuse_dev_i.h @@ -97,8 +97,8 @@ struct fuse_req { void *argbuf; #endif - /** fuse_mount this request belongs to */ - struct fuse_mount *fm; + /** fuse_chan this request belongs to */ + struct fuse_chan *chan; #ifdef CONFIG_FUSE_IO_URING void *ring_entry; diff --git a/fs/fuse/fuse_trace.h b/fs/fuse/fuse_trace.h index bbe9ddd8c716..4e34ddb172ed 100644 --- a/fs/fuse/fuse_trace.h +++ b/fs/fuse/fuse_trace.h @@ -90,7 +90,7 @@ TRACE_EVENT(fuse_request_send, ), TP_fast_assign( - __entry->connection = req->fm->fc->dev; + __entry->connection = req->chan->conn->dev; __entry->unique = req->in.h.unique; __entry->opcode = req->in.h.opcode; __entry->len = req->in.h.len; @@ -114,7 +114,7 @@ TRACE_EVENT(fuse_request_end, ), TP_fast_assign( - __entry->connection = req->fm->fc->dev; + __entry->connection = req->chan->conn->dev; __entry->unique = req->in.h.unique; __entry->len = req->out.h.len; __entry->error = req->out.h.error; -- cgit v1.2.3 From 794e811d144390d61eab407fb2e9ae37aefe50e7 Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Mon, 30 Mar 2026 12:58:19 +0200 Subject: fuse: split out filesystem part of request sending Create a new source file: req.c and add the request sending entry functions: __fuse_simple_request() fuse_simple_background() fuse_simple_notify_reply() Introduce transport layer sending functions that are called by the respective fs layer function: fuse_chan_send() fuse_chan_send_bg() fuse_chan_send_notify_reply() Move calculation of request header fields uid, gid and pid from fuse_get_req() and fuse_force_creads() to a new helper: fuse_fill_creds(). These fileds are now passed to the transport layer via struct fuse_args. Signed-off-by: Miklos Szeredi --- fs/fuse/Makefile | 2 +- fs/fuse/dev.c | 103 +++++++++++++------------------------------------------ fs/fuse/dev.h | 4 +++ fs/fuse/fuse_i.h | 1 + fs/fuse/req.c | 99 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 129 insertions(+), 80 deletions(-) create mode 100644 fs/fuse/req.c diff --git a/fs/fuse/Makefile b/fs/fuse/Makefile index 30dd1bee931d..08be6817a1cc 100644 --- a/fs/fuse/Makefile +++ b/fs/fuse/Makefile @@ -11,7 +11,7 @@ obj-$(CONFIG_CUSE) += cuse.o obj-$(CONFIG_VIRTIO_FS) += virtiofs.o fuse-y := trace.o # put trace.o first so we see ftrace errors sooner -fuse-y += dev.o dir.o file.o inode.o control.o xattr.o acl.o readdir.o ioctl.o req_timeout.o +fuse-y += dev.o dir.o file.o inode.o control.o xattr.o acl.o readdir.o ioctl.o req_timeout.o req.o fuse-y += iomode.o fuse-$(CONFIG_FUSE_DAX) += dax.o fuse-$(CONFIG_FUSE_PASSTHROUGH) += passthrough.o backing.o diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index 21aa39923a1a..423b5fcc4b1f 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -102,75 +102,44 @@ static void fuse_drop_waiting(struct fuse_chan *fch) static void fuse_put_request(struct fuse_req *req); -static struct fuse_req *fuse_get_req(struct mnt_idmap *idmap, - struct fuse_mount *fm, - bool for_background) +static struct fuse_req *fuse_get_req(struct fuse_chan *fch, bool for_background) { - struct fuse_conn *fc = fm->fc; struct fuse_req *req; - bool no_idmap = !fm->sb || (fm->sb->s_iflags & SB_I_NOIDMAP); - kuid_t fsuid; - kgid_t fsgid; int err; - atomic_inc(&fc->chan->num_waiting); + atomic_inc(&fch->num_waiting); - if (fuse_block_alloc(fc, for_background)) { + if (fuse_block_alloc(fch->conn, for_background)) { err = -EINTR; - if (wait_event_state_exclusive(fc->chan->blocked_waitq, - !fuse_block_alloc(fc, for_background), + if (wait_event_state_exclusive(fch->blocked_waitq, + !fuse_block_alloc(fch->conn, for_background), (TASK_KILLABLE | TASK_FREEZABLE))) goto out; } + /* Matches smp_wmb() in fuse_chan_set_initialized() */ smp_rmb(); err = -ENOTCONN; - if (!fc->chan->connected) - goto out; - - err = -ECONNREFUSED; - if (fc->conn_error) + if (!fch->connected) goto out; - req = fuse_request_alloc(fc->chan, GFP_KERNEL); + req = fuse_request_alloc(fch, GFP_KERNEL); err = -ENOMEM; if (!req) { if (for_background) - wake_up(&fc->chan->blocked_waitq); + wake_up(&fch->blocked_waitq); goto out; } - req->in.h.pid = pid_nr_ns(task_pid(current), fc->pid_ns); - __set_bit(FR_WAITING, &req->flags); if (for_background) __set_bit(FR_BACKGROUND, &req->flags); - /* - * Keep the old behavior when idmappings support was not - * declared by a FUSE server. - * - * For those FUSE servers who support idmapped mounts, - * we send UID/GID only along with "inode creation" - * fuse requests, otherwise idmap == &invalid_mnt_idmap and - * req->in.h.{u,g}id will be equal to FUSE_INVALID_UIDGID. - */ - fsuid = no_idmap ? current_fsuid() : mapped_fsuid(idmap, fc->user_ns); - fsgid = no_idmap ? current_fsgid() : mapped_fsgid(idmap, fc->user_ns); - req->in.h.uid = from_kuid(fc->user_ns, fsuid); - req->in.h.gid = from_kgid(fc->user_ns, fsgid); - - if (no_idmap && unlikely(req->in.h.uid == ((uid_t)-1) || - req->in.h.gid == ((gid_t)-1))) { - fuse_put_request(req); - return ERR_PTR(-EOVERFLOW); - } - return req; out: - fuse_drop_waiting(fc->chan); + fuse_drop_waiting(fch); return ERR_PTR(err); } @@ -783,25 +752,13 @@ static void fuse_adjust_compat(struct fuse_conn *fc, struct fuse_args *args) } } -static void fuse_force_creds(struct fuse_mount *fm, struct fuse_req *req) -{ - struct fuse_conn *fc = fm->fc; - - if (!fm->sb || fm->sb->s_iflags & SB_I_NOIDMAP) { - req->in.h.uid = from_kuid_munged(fc->user_ns, current_fsuid()); - req->in.h.gid = from_kgid_munged(fc->user_ns, current_fsgid()); - } else { - req->in.h.uid = FUSE_INVALID_UIDGID; - req->in.h.gid = FUSE_INVALID_UIDGID; - } - - req->in.h.pid = pid_nr_ns(task_pid(current), fc->pid_ns); -} - static void fuse_args_to_req(struct fuse_req *req, struct fuse_args *args) { req->in.h.opcode = args->opcode; req->in.h.nodeid = args->nodeid; + req->in.h.uid = args->uid; + req->in.h.gid = args->gid; + req->in.h.pid = args->pid; req->args = args; if (args->is_ext) req->in.h.total_extlen = args->in_args[args->ext_idx].size / 8; @@ -809,33 +766,26 @@ static void fuse_args_to_req(struct fuse_req *req, struct fuse_args *args) __set_bit(FR_ASYNC, &req->flags); } -ssize_t __fuse_simple_request(struct mnt_idmap *idmap, - struct fuse_mount *fm, - struct fuse_args *args) +ssize_t fuse_chan_send(struct fuse_chan *fch, struct fuse_args *args) { - struct fuse_conn *fc = fm->fc; struct fuse_req *req; ssize_t ret; if (args->force) { - atomic_inc(&fc->chan->num_waiting); - req = fuse_request_alloc(fc->chan, GFP_KERNEL | __GFP_NOFAIL); - - if (!args->nocreds) - fuse_force_creds(fm, req); + atomic_inc(&fch->num_waiting); + req = fuse_request_alloc(fch, GFP_KERNEL | __GFP_NOFAIL); __set_bit(FR_WAITING, &req->flags); if (!args->abort_on_kill) __set_bit(FR_FORCE, &req->flags); } else { - WARN_ON(args->nocreds); - req = fuse_get_req(idmap, fm, false); + req = fuse_get_req(fch, false); if (IS_ERR(req)) return PTR_ERR(req); } /* Needs to be done after fuse_get_req() so that fc->minor is valid */ - fuse_adjust_compat(fc, args); + fuse_adjust_compat(fch->conn, args); fuse_args_to_req(req, args); if (!args->noreply) @@ -899,20 +849,17 @@ static int fuse_request_queue_background(struct fuse_req *req) return queued; } -int fuse_simple_background(struct fuse_mount *fm, struct fuse_args *args, - gfp_t gfp_flags) +int fuse_chan_send_bg(struct fuse_chan *fch, struct fuse_args *args, gfp_t gfp_flags) { struct fuse_req *req; if (args->force) { - WARN_ON(!args->nocreds); - req = fuse_request_alloc(fm->fc->chan, gfp_flags); + req = fuse_request_alloc(fch, gfp_flags); if (!req) return -ENOMEM; __set_bit(FR_BACKGROUND, &req->flags); } else { - WARN_ON(args->nocreds); - req = fuse_get_req(&invalid_mnt_idmap, fm, true); + req = fuse_get_req(fch, true); if (IS_ERR(req)) return PTR_ERR(req); } @@ -926,15 +873,13 @@ int fuse_simple_background(struct fuse_mount *fm, struct fuse_args *args, return 0; } -EXPORT_SYMBOL_GPL(fuse_simple_background); -static int fuse_simple_notify_reply(struct fuse_mount *fm, - struct fuse_args *args, u64 unique) +int fuse_chan_send_notify_reply(struct fuse_chan *fch, struct fuse_args *args, u64 unique) { struct fuse_req *req; - struct fuse_iqueue *fiq = &fm->fc->chan->iq; + struct fuse_iqueue *fiq = &fch->iq; - req = fuse_get_req(&invalid_mnt_idmap, fm, false); + req = fuse_get_req(fch, false); if (IS_ERR(req)) return PTR_ERR(req); diff --git a/fs/fuse/dev.h b/fs/fuse/dev.h index a0c1212573d6..fb814a0b6782 100644 --- a/fs/fuse/dev.h +++ b/fs/fuse/dev.h @@ -11,6 +11,7 @@ struct fuse_conn; struct fuse_chan; struct fuse_dev; +struct fuse_args; struct file; struct fuse_chan *fuse_chan_new(void); @@ -24,6 +25,9 @@ unsigned int fuse_chan_num_waiting(struct fuse_chan *fch); void fuse_chan_set_fc(struct fuse_chan *fch, struct fuse_conn *fc); void fuse_chan_set_initialized(struct fuse_chan *fch); void fuse_chan_io_uring_enable(struct fuse_chan *fch); +ssize_t fuse_chan_send(struct fuse_chan *fch, struct fuse_args *args); +int fuse_chan_send_bg(struct fuse_chan *fch, struct fuse_args *args, gfp_t gfp_flags); +int fuse_chan_send_notify_reply(struct fuse_chan *fch, struct fuse_args *args, u64 unique); struct fuse_forget_link *fuse_alloc_forget(void); void fuse_chan_queue_forget(struct fuse_chan *fch, struct fuse_forget_link *forget, diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index 22a6d58678ad..f7a9c7cedd39 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -941,6 +941,7 @@ static inline ssize_t fuse_simple_idmap_request(struct mnt_idmap *idmap, int fuse_simple_background(struct fuse_mount *fm, struct fuse_args *args, gfp_t gfp_flags); +int fuse_simple_notify_reply(struct fuse_mount *fm, struct fuse_args *args, u64 unique); void fuse_dentry_tree_init(void); void fuse_dentry_tree_cleanup(void); diff --git a/fs/fuse/req.c b/fs/fuse/req.c new file mode 100644 index 000000000000..a01ee743d31e --- /dev/null +++ b/fs/fuse/req.c @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include "dev.h" +#include "fuse_i.h" + +static int fuse_fill_creds(struct fuse_mount *fm, struct fuse_args *args, struct mnt_idmap *idmap) +{ + struct fuse_conn *fc = fm->fc; + bool no_idmap = !fm->sb || (fm->sb->s_iflags & SB_I_NOIDMAP); + kuid_t fsuid = mapped_fsuid(idmap, fc->user_ns); + kgid_t fsgid = mapped_fsgid(idmap, fc->user_ns); + + args->pid = pid_nr_ns(task_pid(current), fc->pid_ns); + + if (args->force) { + if (args->nocreds) + return 0; + + if (no_idmap) { + args->uid = from_kuid_munged(fc->user_ns, current_fsuid()); + args->gid = from_kgid_munged(fc->user_ns, current_fsgid()); + } else { + args->uid = FUSE_INVALID_UIDGID; + args->gid = FUSE_INVALID_UIDGID; + } + return 0; + } + + WARN_ON(args->nocreds); + /* + * Keep the old behavior when idmappings support was not + * declared by a FUSE server. + * + * For those FUSE servers who support idmapped mounts, we send UID/GID + * only along with "inode creation" fuse requests, otherwise idmap == + * &invalid_mnt_idmap and req->in.h.{u,g}id will be equal to + * FUSE_INVALID_UIDGID. + */ + if (no_idmap) { + fsuid = current_fsuid(); + fsgid = current_fsgid(); + } + args->uid = from_kuid(fc->user_ns, fsuid); + args->gid = from_kgid(fc->user_ns, fsgid); + + if (no_idmap && unlikely(args->uid == ((uid_t)-1) || args->gid == ((gid_t)-1))) + return -EOVERFLOW; + + return 0; +} + +static int fuse_req_prep(struct fuse_mount *fm, struct fuse_args *args, struct mnt_idmap *idmap) +{ + if (!args->force && fm->fc->conn_error) + return -ECONNREFUSED; + + return fuse_fill_creds(fm, args, idmap); +} + +ssize_t __fuse_simple_request(struct mnt_idmap *idmap, struct fuse_mount *fm, + struct fuse_args *args) +{ + struct fuse_conn *fc = fm->fc; + int err = fuse_req_prep(fm, args, idmap); + + if (err) + return err; + + return fuse_chan_send(fc->chan, args); +} + +int fuse_simple_background(struct fuse_mount *fm, struct fuse_args *args, gfp_t gfp_flags) +{ + struct fuse_conn *fc = fm->fc; + int err; + + WARN_ON(args->force && !args->nocreds); + + err = fuse_req_prep(fm, args, &invalid_mnt_idmap); + if (err) + return err; + + return fuse_chan_send_bg(fc->chan, args, gfp_flags); +} +EXPORT_SYMBOL_GPL(fuse_simple_background); + +int fuse_simple_notify_reply(struct fuse_mount *fm, struct fuse_args *args, u64 unique) +{ + struct fuse_conn *fc = fm->fc; + int err; + + WARN_ON(args->force && !args->nocreds); + + err = fuse_req_prep(fm, args, &invalid_mnt_idmap); + if (err) + return err; + + return fuse_chan_send_notify_reply(fc->chan, args, unique); +} -- cgit v1.2.3 From 994d807943551022fd8d610db5a0e8c457b14d4a Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Mon, 30 Mar 2026 14:27:23 +0200 Subject: fuse: change fud->fc to fud->chan Store pointer to struct fuse_chan instead of struct fuse_conn in fuse_dev. Signed-off-by: Miklos Szeredi --- fs/fuse/cuse.c | 4 +-- fs/fuse/dev.c | 98 ++++++++++++++++++++++++++-------------------------- fs/fuse/dev.h | 4 +-- fs/fuse/dev_uring.c | 58 +++++++++++++++---------------- fs/fuse/fuse_dev_i.h | 20 +++++------ fs/fuse/inode.c | 4 +-- fs/fuse/virtio_fs.c | 2 +- 7 files changed, 95 insertions(+), 95 deletions(-) diff --git a/fs/fuse/cuse.c b/fs/fuse/cuse.c index f66ee8c2b310..3c15b5ba16d7 100644 --- a/fs/fuse/cuse.c +++ b/fs/fuse/cuse.c @@ -523,7 +523,7 @@ static int cuse_channel_open(struct inode *inode, struct file *file) */ fuse_conn_init(&cc->fc, &cc->fm, file->f_cred->user_ns, no_free_ptr(fch)); cc->fc.release = cuse_fc_release; - fud = fuse_dev_alloc_install(&cc->fc); + fud = fuse_dev_alloc_install(cc->fc.chan); fuse_conn_put(&cc->fc); if (!fud) return -ENOMEM; @@ -555,7 +555,7 @@ static int cuse_channel_open(struct inode *inode, struct file *file) static int cuse_channel_release(struct inode *inode, struct file *file) { struct fuse_dev *fud = __fuse_get_dev(file); - struct cuse_conn *cc = fc_to_cc(fud->fc); + struct cuse_conn *cc = fc_to_cc(fud->chan->conn); /* remove from the conntbl, no more access from this point on */ mutex_lock(&cuse_lock); diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index 423b5fcc4b1f..dbbd20265dfc 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -431,34 +431,34 @@ struct fuse_dev *fuse_dev_alloc(void) } EXPORT_SYMBOL_GPL(fuse_dev_alloc); -void fuse_dev_install(struct fuse_dev *fud, struct fuse_conn *fc) +void fuse_dev_install(struct fuse_dev *fud, struct fuse_chan *fch) { - struct fuse_conn *old_fc; + struct fuse_chan *old_fch; - spin_lock(&fc->chan->lock); + spin_lock(&fch->lock); /* * Pairs with: * - xchg() in fuse_dev_release() * - smp_load_acquire() in fuse_dev_fc_get() */ - old_fc = cmpxchg(&fud->fc, NULL, fc); - if (old_fc) { + old_fch = cmpxchg(&fud->chan, NULL, fch); + if (old_fch) { /* - * failed to set fud->fc because + * failed to set fud->chan because * - it was already set to a different fc * - it was set to disconneted */ - fc->chan->connected = 0; + fch->connected = 0; } else { - list_add_tail(&fud->entry, &fc->chan->devices); - fuse_conn_get(fc); + list_add_tail(&fud->entry, &fch->devices); + fuse_conn_get(fch->conn); wake_up_all(&fuse_dev_waitq); } - spin_unlock(&fc->chan->lock); + spin_unlock(&fch->lock); } EXPORT_SYMBOL_GPL(fuse_dev_install); -struct fuse_dev *fuse_dev_alloc_install(struct fuse_conn *fc) +struct fuse_dev *fuse_dev_alloc_install(struct fuse_chan *fch) { struct fuse_dev *fud; @@ -466,26 +466,26 @@ struct fuse_dev *fuse_dev_alloc_install(struct fuse_conn *fc) if (!fud) return NULL; - fuse_dev_install(fud, fc); + fuse_dev_install(fud, fch); return fud; } EXPORT_SYMBOL_GPL(fuse_dev_alloc_install); void fuse_dev_put(struct fuse_dev *fud) { - struct fuse_conn *fc; + struct fuse_chan *fch; if (!refcount_dec_and_test(&fud->ref)) return; - fc = fuse_dev_fc_get(fud); - if (fc && fc != FUSE_DEV_FC_DISCONNECTED) { + fch = fuse_dev_chan_get(fud); + if (fch && fch != FUSE_DEV_CHAN_DISCONNECTED) { /* This is the virtiofs case (fuse_dev_release() not called) */ - spin_lock(&fc->chan->lock); + spin_lock(&fch->lock); list_del(&fud->entry); - spin_unlock(&fc->chan->lock); + spin_unlock(&fch->lock); - fuse_conn_put(fc); + fuse_conn_put(fch->conn); } kfree(fud->pq.processing); kfree(fud); @@ -494,17 +494,17 @@ EXPORT_SYMBOL_GPL(fuse_dev_put); bool fuse_dev_is_installed(struct fuse_dev *fud) { - struct fuse_conn *fc = fuse_dev_fc_get(fud); + struct fuse_chan *fch = fuse_dev_chan_get(fud); - return fc != NULL && fc != FUSE_DEV_FC_DISCONNECTED; + return fch != NULL && fch != FUSE_DEV_CHAN_DISCONNECTED; } /* * Checks if @fc matches the one installed in @fud */ -bool fuse_dev_verify(struct fuse_dev *fud, struct fuse_conn *fc) +bool fuse_dev_verify(struct fuse_dev *fud, struct fuse_chan *fch) { - return fuse_dev_fc_get(fud) == fc; + return fuse_dev_chan_get(fud) == fch; } bool fuse_dev_is_sync_init(struct fuse_dev *fud) @@ -1490,8 +1490,8 @@ static ssize_t fuse_dev_do_read(struct fuse_dev *fud, struct file *file, struct fuse_copy_state *cs, size_t nbytes) { ssize_t err; - struct fuse_conn *fc = fud->fc; - struct fuse_iqueue *fiq = &fc->chan->iq; + struct fuse_chan *fch = fud->chan; + struct fuse_iqueue *fiq = &fch->iq; struct fuse_pqueue *fpq = &fud->pq; struct fuse_req *req; struct fuse_args *args; @@ -1513,7 +1513,7 @@ static ssize_t fuse_dev_do_read(struct fuse_dev *fud, struct file *file, if (nbytes < max_t(size_t, FUSE_MIN_READ_BUFFER, sizeof(struct fuse_in_header) + sizeof(struct fuse_write_in) + - fc->max_write)) + fch->conn->max_write)) return -EINVAL; restart: @@ -1532,7 +1532,7 @@ static ssize_t fuse_dev_do_read(struct fuse_dev *fud, struct file *file, } if (!fiq->connected) { - err = fc->chan->abort_with_err ? -ECONNABORTED : -ENODEV; + err = fch->abort_with_err ? -ECONNABORTED : -ENODEV; goto err_unlock; } @@ -1544,7 +1544,7 @@ static ssize_t fuse_dev_do_read(struct fuse_dev *fud, struct file *file, if (forget_pending(fiq)) { if (list_empty(&fiq->pending) || fiq->forget_batch-- > 0) - return fuse_read_forget(fc, fiq, cs, nbytes); + return fuse_read_forget(fch->conn, fiq, cs, nbytes); if (fiq->forget_batch <= -8) fiq->forget_batch = 16; @@ -1588,7 +1588,7 @@ static ssize_t fuse_dev_do_read(struct fuse_dev *fud, struct file *file, spin_lock(&fpq->lock); clear_bit(FR_LOCKED, &req->flags); if (!fpq->connected) { - err = fc->chan->abort_with_err ? -ECONNABORTED : -ENODEV; + err = fch->abort_with_err ? -ECONNABORTED : -ENODEV; goto out_end; } if (err) { @@ -1640,12 +1640,12 @@ struct fuse_dev *fuse_get_dev(struct file *file) struct fuse_dev *fud = fuse_file_to_fud(file); int err; - if (unlikely(!fuse_dev_fc_get(fud))) { + if (unlikely(!fuse_dev_chan_get(fud))) { /* only block waiting for mount if sync init was requested */ if (!fud->sync_init) return ERR_PTR(-EPERM); - err = wait_event_interruptible(fuse_dev_waitq, fuse_dev_fc_get(fud) != NULL); + err = wait_event_interruptible(fuse_dev_waitq, fuse_dev_chan_get(fud) != NULL); if (err) return ERR_PTR(err); } @@ -2276,7 +2276,7 @@ static ssize_t fuse_dev_do_write(struct fuse_dev *fud, struct fuse_copy_state *cs, size_t nbytes) { int err; - struct fuse_conn *fc = fud->fc; + struct fuse_chan *fch = fud->chan; struct fuse_pqueue *fpq = &fud->pq; struct fuse_req *req; struct fuse_out_header oh; @@ -2298,7 +2298,7 @@ static ssize_t fuse_dev_do_write(struct fuse_dev *fud, * and error contains notification code. */ if (!oh.unique) { - err = fuse_notify(fc, oh.error, nbytes - sizeof(oh), cs); + err = fuse_notify(fch->conn, oh.error, nbytes - sizeof(oh), cs); goto copy_finish; } @@ -2326,7 +2326,7 @@ static ssize_t fuse_dev_do_write(struct fuse_dev *fud, if (nbytes != sizeof(struct fuse_out_header)) err = -EINVAL; else if (oh.error == -ENOSYS) - fc->chan->no_interrupt = true; + fch->no_interrupt = 1; else if (oh.error == -EAGAIN) err = queue_interrupt(req); @@ -2486,7 +2486,7 @@ static __poll_t fuse_dev_poll(struct file *file, poll_table *wait) if (IS_ERR(fud)) return EPOLLERR; - fiq = &fud->fc->chan->iq; + fiq = &fud->chan->iq; poll_wait(file, &fiq->waitq, wait); spin_lock(&fiq->lock); @@ -2636,9 +2636,9 @@ int fuse_dev_release(struct inode *inode, struct file *file) { struct fuse_dev *fud = fuse_file_to_fud(file); /* Pairs with cmpxchg() in fuse_dev_install() */ - struct fuse_conn *fc = xchg(&fud->fc, FUSE_DEV_FC_DISCONNECTED); + struct fuse_chan *fch = xchg(&fud->chan, FUSE_DEV_CHAN_DISCONNECTED); - if (fc) { + if (fch) { struct fuse_pqueue *fpq = &fud->pq; LIST_HEAD(to_end); unsigned int i; @@ -2652,17 +2652,17 @@ int fuse_dev_release(struct inode *inode, struct file *file) fuse_dev_end_requests(&to_end); - spin_lock(&fc->chan->lock); + spin_lock(&fch->lock); list_del(&fud->entry); /* Are we the last open device? */ - last = list_empty(&fc->chan->devices); - spin_unlock(&fc->chan->lock); + last = list_empty(&fch->devices); + spin_unlock(&fch->lock); if (last) { - WARN_ON(fc->chan->iq.fasync != NULL); - fuse_chan_abort(fc->chan, false); + WARN_ON(fch->iq.fasync != NULL); + fuse_chan_abort(fch, false); } - fuse_conn_put(fc); + fuse_conn_put(fch->conn); } fuse_dev_put(fud); return 0; @@ -2677,7 +2677,7 @@ static int fuse_dev_fasync(int fd, struct file *file, int on) return PTR_ERR(fud); /* No locking - fasync_helper does its own locking */ - return fasync_helper(fd, file, on, &fud->fc->chan->iq.fasync); + return fasync_helper(fd, file, on, &fud->chan->iq.fasync); } static long fuse_dev_ioctl_clone(struct file *file, __u32 __user *argp) @@ -2704,10 +2704,10 @@ static long fuse_dev_ioctl_clone(struct file *file, __u32 __user *argp) return PTR_ERR(fud); new_fud = fuse_file_to_fud(file); - if (fuse_dev_fc_get(new_fud)) + if (fuse_dev_chan_get(new_fud)) return -EINVAL; - fuse_dev_install(new_fud, fud->fc); + fuse_dev_install(new_fud, fud->chan); return 0; } @@ -2727,7 +2727,7 @@ static long fuse_dev_ioctl_backing_open(struct file *file, if (copy_from_user(&map, argp, sizeof(map))) return -EFAULT; - return fuse_backing_open(fud->fc, &map); + return fuse_backing_open(fud->chan->conn, &map); } static long fuse_dev_ioctl_backing_close(struct file *file, __u32 __user *argp) @@ -2744,7 +2744,7 @@ static long fuse_dev_ioctl_backing_close(struct file *file, __u32 __user *argp) if (get_user(backing_id, argp)) return -EFAULT; - return fuse_backing_close(fud->fc, backing_id); + return fuse_backing_close(fud->chan->conn, backing_id); } static long fuse_dev_ioctl_sync_init(struct file *file) @@ -2753,7 +2753,7 @@ static long fuse_dev_ioctl_sync_init(struct file *file) struct fuse_dev *fud = fuse_file_to_fud(file); mutex_lock(&fuse_mutex); - if (!fuse_dev_fc_get(fud)) { + if (!fuse_dev_chan_get(fud)) { fud->sync_init = true; err = 0; } @@ -2791,7 +2791,7 @@ static void fuse_dev_show_fdinfo(struct seq_file *seq, struct file *file) if (!fud) return; - seq_printf(seq, "fuse_connection:\t%u\n", fud->fc->dev); + seq_printf(seq, "fuse_connection:\t%u\n", fud->chan->conn->dev); } #endif diff --git a/fs/fuse/dev.h b/fs/fuse/dev.h index fb814a0b6782..9a1f79b130bd 100644 --- a/fs/fuse/dev.h +++ b/fs/fuse/dev.h @@ -36,8 +36,8 @@ void fuse_chan_queue_forget(struct fuse_chan *fch, struct fuse_forget_link *forg DEFINE_FREE(fuse_chan_free, struct fuse_chan *, if (_T) fuse_chan_free(_T)) -void fuse_dev_install(struct fuse_dev *fud, struct fuse_conn *fc); -bool fuse_dev_verify(struct fuse_dev *fud, struct fuse_conn *fc); +void fuse_dev_install(struct fuse_dev *fud, struct fuse_chan *fch); +bool fuse_dev_verify(struct fuse_dev *fud, struct fuse_chan *fch); void fuse_dev_put(struct fuse_dev *fud); bool fuse_dev_is_installed(struct fuse_dev *fud); bool fuse_dev_is_sync_init(struct fuse_dev *fud); diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c index 857c1df728e1..109e3d07c3f1 100644 --- a/fs/fuse/dev_uring.c +++ b/fs/fuse/dev_uring.c @@ -225,14 +225,14 @@ void fuse_uring_destruct(struct fuse_chan *fch) /* * Basic ring setup for this connection based on the provided configuration */ -static struct fuse_ring *fuse_uring_create(struct fuse_conn *fc) +static struct fuse_ring *fuse_uring_create(struct fuse_chan *fch) { struct fuse_ring *ring; size_t nr_queues = num_possible_cpus(); struct fuse_ring *res = NULL; size_t max_payload_size; - ring = kzalloc_obj(*fc->chan->ring, GFP_KERNEL_ACCOUNT); + ring = kzalloc_obj(*ring, GFP_KERNEL_ACCOUNT); if (!ring) return NULL; @@ -241,29 +241,29 @@ static struct fuse_ring *fuse_uring_create(struct fuse_conn *fc) if (!ring->queues) goto out_err; - max_payload_size = max(FUSE_MIN_READ_BUFFER, fc->max_write); - max_payload_size = max(max_payload_size, fc->max_pages * PAGE_SIZE); + max_payload_size = max(FUSE_MIN_READ_BUFFER, fch->conn->max_write); + max_payload_size = max(max_payload_size, fch->conn->max_pages * PAGE_SIZE); - spin_lock(&fc->chan->lock); - if (!fc->chan->connected) { - spin_unlock(&fc->chan->lock); + spin_lock(&fch->lock); + if (!fch->connected) { + spin_unlock(&fch->lock); goto out_err; } - if (fc->chan->ring) { + if (fch->ring) { /* race, another thread created the ring in the meantime */ - spin_unlock(&fc->chan->lock); - res = fc->chan->ring; + spin_unlock(&fch->lock); + res = fch->ring; goto out_err; } init_waitqueue_head(&ring->stop_waitq); ring->nr_queues = nr_queues; - ring->fc = fc; + ring->fc = fch->conn; ring->max_payload_sz = max_payload_size; - smp_store_release(&fc->chan->ring, ring); + smp_store_release(&fch->ring, ring); - spin_unlock(&fc->chan->lock); + spin_unlock(&fch->lock); return ring; out_err: @@ -893,13 +893,13 @@ static int fuse_ring_ent_set_commit(struct fuse_ring_ent *ent) /* FUSE_URING_CMD_COMMIT_AND_FETCH handler */ static int fuse_uring_commit_fetch(struct io_uring_cmd *cmd, int issue_flags, - struct fuse_conn *fc) + struct fuse_chan *fch) { const struct fuse_uring_cmd_req *cmd_req = io_uring_sqe128_cmd(cmd->sqe, struct fuse_uring_cmd_req); struct fuse_ring_ent *ent; int err; - struct fuse_ring *ring = fc->chan->ring; + struct fuse_ring *ring = fch->ring; struct fuse_ring_queue *queue; uint64_t commit_id = READ_ONCE(cmd_req->commit_id); unsigned int qid = READ_ONCE(cmd_req->qid); @@ -918,7 +918,7 @@ static int fuse_uring_commit_fetch(struct io_uring_cmd *cmd, int issue_flags, return err; fpq = &queue->fpq; - if (!READ_ONCE(fc->chan->connected)) + if (!READ_ONCE(fch->connected)) return err; spin_lock(&queue->lock); @@ -1115,11 +1115,11 @@ fuse_uring_create_ring_ent(struct io_uring_cmd *cmd, * entry as "ready to get fuse requests" on the queue */ static int fuse_uring_register(struct io_uring_cmd *cmd, - unsigned int issue_flags, struct fuse_conn *fc) + unsigned int issue_flags, struct fuse_chan *fch) { const struct fuse_uring_cmd_req *cmd_req = io_uring_sqe128_cmd(cmd->sqe, struct fuse_uring_cmd_req); - struct fuse_ring *ring = smp_load_acquire(&fc->chan->ring); + struct fuse_ring *ring = smp_load_acquire(&fch->ring); struct fuse_ring_queue *queue; struct fuse_ring_ent *ent; int err; @@ -1127,7 +1127,7 @@ static int fuse_uring_register(struct io_uring_cmd *cmd, err = -ENOMEM; if (!ring) { - ring = fuse_uring_create(fc); + ring = fuse_uring_create(fch); if (!ring) return err; } @@ -1163,7 +1163,7 @@ static int fuse_uring_register(struct io_uring_cmd *cmd, int fuse_uring_cmd(struct io_uring_cmd *cmd, unsigned int issue_flags) { struct fuse_dev *fud; - struct fuse_conn *fc; + struct fuse_chan *fch; u32 cmd_op = cmd->cmd_op; int err; @@ -1181,39 +1181,39 @@ int fuse_uring_cmd(struct io_uring_cmd *cmd, unsigned int issue_flags) pr_info_ratelimited("No fuse device found\n"); return PTR_ERR(fud); } - fc = fud->fc; + fch = fud->chan; /* Once a connection has io-uring enabled on it, it can't be disabled */ - if (!enable_uring && !fc->chan->io_uring) { + if (!enable_uring && !fch->io_uring) { pr_info_ratelimited("fuse-io-uring is disabled\n"); return -EOPNOTSUPP; } - if (fc->chan->abort_with_err) + if (fch->abort_with_err) return -ECONNABORTED; - if (!fc->chan->connected) + if (!fch->connected) return -ENOTCONN; /* * fuse_uring_register() needs the ring to be initialized, * we need to know the max payload size */ - if (!fc->chan->initialized) + if (!fch->initialized) return -EAGAIN; switch (cmd_op) { case FUSE_IO_URING_CMD_REGISTER: - err = fuse_uring_register(cmd, issue_flags, fc); + err = fuse_uring_register(cmd, issue_flags, fch); if (err) { pr_info_once("FUSE_IO_URING_CMD_REGISTER failed err=%d\n", err); - fc->chan->io_uring = 0; - wake_up_all(&fc->chan->blocked_waitq); + fch->io_uring = 0; + wake_up_all(&fch->blocked_waitq); return err; } break; case FUSE_IO_URING_CMD_COMMIT_AND_FETCH: - err = fuse_uring_commit_fetch(cmd, issue_flags, fc); + err = fuse_uring_commit_fetch(cmd, issue_flags, fch); if (err) { pr_info_once("FUSE_IO_URING_COMMIT_AND_FETCH failed err=%d\n", err); diff --git a/fs/fuse/fuse_dev_i.h b/fs/fuse/fuse_dev_i.h index e8659a3f2672..9b5ebc6b7762 100644 --- a/fs/fuse/fuse_dev_i.h +++ b/fs/fuse/fuse_dev_i.h @@ -283,8 +283,8 @@ struct fuse_dev { /** Issue FUSE_INIT synchronously */ bool sync_init; - /** Fuse connection for this device */ - struct fuse_conn *fc; + /** Fuse channel for this device */ + struct fuse_chan *chan; /** Processing queue */ struct fuse_pqueue pq; @@ -311,21 +311,21 @@ struct fuse_copy_state { } ring; }; -/* fud->fc gets assigned to this value when /dev/fuse is closed */ -#define FUSE_DEV_FC_DISCONNECTED ((struct fuse_conn *) 1) +/* fud->chan gets assigned to this value when /dev/fuse is closed */ +#define FUSE_DEV_CHAN_DISCONNECTED ((struct fuse_chan *) 1) /* - * Lockless access is OK, because fud->fc is set once during mount and is valid + * Lockless access is OK, because fud->chan is set once during mount and is valid * until the file is released. * - * fud->fc is set to FUSE_DEV_FC_DISCONNECTED only after the containing file is + * fud->chan is set to FUSE_DEV_CHAN_DISCONNECTED only after the containing file is * released, so result is safe to dereference in most cases. Exceptions are: * fuse_dev_put() and fuse_fill_super_common(). */ -static inline struct fuse_conn *fuse_dev_fc_get(struct fuse_dev *fud) +static inline struct fuse_chan *fuse_dev_chan_get(struct fuse_dev *fud) { /* Pairs with xchg() in fuse_dev_install() */ - return smp_load_acquire(&fud->fc); + return smp_load_acquire(&fud->chan); } static inline struct fuse_dev *fuse_file_to_fud(struct file *file) @@ -337,7 +337,7 @@ static inline struct fuse_dev *__fuse_get_dev(struct file *file) { struct fuse_dev *fud = fuse_file_to_fud(file); - if (!fuse_dev_fc_get(fud)) + if (!fuse_dev_chan_get(fud)) return NULL; return fud; @@ -378,7 +378,7 @@ void fuse_request_assign_unique(struct fuse_iqueue *fiq, struct fuse_req *req); */ u64 fuse_get_unique(struct fuse_iqueue *fiq); -struct fuse_dev *fuse_dev_alloc_install(struct fuse_conn *fc); +struct fuse_dev *fuse_dev_alloc_install(struct fuse_chan *fch); struct fuse_dev *fuse_dev_alloc(void); /** diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c index 8d689f84e533..9e96bacdfc3e 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c @@ -1791,7 +1791,7 @@ int fuse_fill_super_common(struct super_block *sb, struct fuse_fs_context *ctx) list_add_tail(&fc->entry, &fuse_conn_list); sb->s_root = root_dentry; if (fud) - fuse_dev_install(fud, fc); + fuse_dev_install(fud, fc->chan); mutex_unlock(&fuse_mutex); return 0; @@ -1837,7 +1837,7 @@ static int fuse_set_no_super(struct super_block *sb, struct fs_context *fsc) static int fuse_test_super(struct super_block *sb, struct fs_context *fsc) { - return fuse_dev_verify(fsc->sget_key, get_fuse_conn_super(sb)); + return fuse_dev_verify(fsc->sget_key, get_fuse_conn_super(sb)->chan); } static int fuse_get_tree(struct fs_context *fsc) diff --git a/fs/fuse/virtio_fs.c b/fs/fuse/virtio_fs.c index 53ce21836ba0..a4cf813cebfc 100644 --- a/fs/fuse/virtio_fs.c +++ b/fs/fuse/virtio_fs.c @@ -1607,7 +1607,7 @@ static int virtio_fs_fill_super(struct super_block *sb, struct fs_context *fsc) for (i = 0; i < fs->nvqs; i++) { struct virtio_fs_vq *fsvq = &fs->vqs[i]; - fuse_dev_install(fsvq->fud, fc); + fuse_dev_install(fsvq->fud, fc->chan); } /* Previous unmount will stop all queues. Start these again */ -- cgit v1.2.3 From b8b072cbafb817d5cb95da50a6295a4169af1aa2 Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Tue, 31 Mar 2026 13:49:22 +0200 Subject: fuse: create poll.c Move f_op->poll related functions to the new source file. Signed-off-by: Miklos Szeredi --- fs/fuse/Makefile | 1 + fs/fuse/dev.c | 45 ++++++------------ fs/fuse/dev.h | 2 + fs/fuse/file.c | 119 ---------------------------------------------- fs/fuse/poll.c | 141 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 158 insertions(+), 150 deletions(-) create mode 100644 fs/fuse/poll.c diff --git a/fs/fuse/Makefile b/fs/fuse/Makefile index 08be6817a1cc..26086bf5b494 100644 --- a/fs/fuse/Makefile +++ b/fs/fuse/Makefile @@ -12,6 +12,7 @@ obj-$(CONFIG_VIRTIO_FS) += virtiofs.o fuse-y := trace.o # put trace.o first so we see ftrace errors sooner fuse-y += dev.o dir.o file.o inode.o control.o xattr.o acl.o readdir.o ioctl.o req_timeout.o req.o +fuse-y += poll.o fuse-y += iomode.o fuse-$(CONFIG_FUSE_DAX) += dax.o fuse-$(CONFIG_FUSE_PASSTHROUGH) += passthrough.o backing.o diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index dbbd20265dfc..18a9c4f3d63c 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -80,10 +80,10 @@ void fuse_chan_set_initialized(struct fuse_chan *fch) wake_up_all(&fch->blocked_waitq); } -static bool fuse_block_alloc(struct fuse_conn *fc, bool for_background) +static bool fuse_block_alloc(struct fuse_chan *fch, bool for_background) { - return !fc->chan->initialized || (for_background && fc->chan->blocked) || - (fc->chan->io_uring && fc->chan->connected && !fuse_uring_ready(fc->chan)); + return !fch->initialized || (for_background && fch->blocked) || + (fch->io_uring && fch->connected && !fuse_uring_ready(fch)); } static void fuse_drop_waiting(struct fuse_chan *fch) @@ -109,10 +109,10 @@ static struct fuse_req *fuse_get_req(struct fuse_chan *fch, bool for_background) atomic_inc(&fch->num_waiting); - if (fuse_block_alloc(fch->conn, for_background)) { + if (fuse_block_alloc(fch, for_background)) { err = -EINTR; if (wait_event_state_exclusive(fch->blocked_waitq, - !fuse_block_alloc(fch->conn, for_background), + !fuse_block_alloc(fch, for_background), (TASK_KILLABLE | TASK_FREEZABLE))) goto out; } @@ -2070,21 +2070,21 @@ static int fuse_notify_retrieve(struct fuse_conn *fc, unsigned int size, * if the FUSE daemon takes careful measures to avoid processing duplicated * non-idempotent requests. */ -static void fuse_resend(struct fuse_conn *fc) +static void fuse_resend(struct fuse_chan *fch) { struct fuse_dev *fud; struct fuse_req *req, *next; - struct fuse_iqueue *fiq = &fc->chan->iq; + struct fuse_iqueue *fiq = &fch->iq; LIST_HEAD(to_queue); unsigned int i; - spin_lock(&fc->chan->lock); - if (!fc->chan->connected) { - spin_unlock(&fc->chan->lock); + spin_lock(&fch->lock); + if (!fch->connected) { + spin_unlock(&fch->lock); return; } - list_for_each_entry(fud, &fc->chan->devices, entry) { + list_for_each_entry(fud, &fch->devices, entry) { struct fuse_pqueue *fpq = &fud->pq; spin_lock(&fpq->lock); @@ -2092,7 +2092,7 @@ static void fuse_resend(struct fuse_conn *fc) list_splice_tail_init(&fpq->processing[i], &to_queue); spin_unlock(&fpq->lock); } - spin_unlock(&fc->chan->lock); + spin_unlock(&fch->lock); list_for_each_entry_safe(req, next, &to_queue, list) { set_bit(FR_PENDING, &req->flags); @@ -2124,7 +2124,7 @@ static void fuse_resend(struct fuse_conn *fc) static int fuse_notify_resend(struct fuse_conn *fc) { - fuse_resend(fc); + fuse_resend(fc->chan); return 0; } @@ -2512,23 +2512,6 @@ void fuse_dev_end_requests(struct list_head *head) } } -static void end_polls(struct fuse_conn *fc) -{ - struct rb_node *p; - - spin_lock(&fc->lock); - p = rb_first(&fc->polled_files); - - while (p) { - struct fuse_file *ff; - ff = rb_entry(p, struct fuse_file, polled_node); - wake_up_interruptible_all(&ff->poll_wait); - - p = rb_next(p); - } - spin_unlock(&fc->lock); -} - /* * Abort all requests. * @@ -2606,7 +2589,7 @@ void fuse_chan_abort(struct fuse_chan *fch, bool abort_with_err) wake_up_all(&fiq->waitq); spin_unlock(&fiq->lock); kill_fasync(&fiq->fasync, SIGIO, POLL_IN); - end_polls(fch->conn); + fuse_end_polls(fch->conn); wake_up_all(&fch->blocked_waitq); spin_unlock(&fch->lock); diff --git a/fs/fuse/dev.h b/fs/fuse/dev.h index 9a1f79b130bd..2c4d0c53ffbb 100644 --- a/fs/fuse/dev.h +++ b/fs/fuse/dev.h @@ -49,6 +49,8 @@ void fuse_init_server_timeout(struct fuse_chan *fch, unsigned int timeout); void fuse_chan_abort(struct fuse_chan *fch, bool abort_with_err); void fuse_chan_wait_aborted(struct fuse_chan *fch); +void fuse_end_polls(struct fuse_conn *fc); + #ifdef CONFIG_FUSE_IO_URING bool fuse_uring_enabled(void); void fuse_uring_destruct(struct fuse_chan *fch); diff --git a/fs/fuse/file.c b/fs/fuse/file.c index 057ff884500d..e8833e2a6610 100644 --- a/fs/fuse/file.c +++ b/fs/fuse/file.c @@ -2683,125 +2683,6 @@ static loff_t fuse_file_llseek(struct file *file, loff_t offset, int whence) return retval; } -/* - * All files which have been polled are linked to RB tree - * fuse_conn->polled_files which is indexed by kh. Walk the tree and - * find the matching one. - */ -static struct rb_node **fuse_find_polled_node(struct fuse_conn *fc, u64 kh, - struct rb_node **parent_out) -{ - struct rb_node **link = &fc->polled_files.rb_node; - struct rb_node *last = NULL; - - while (*link) { - struct fuse_file *ff; - - last = *link; - ff = rb_entry(last, struct fuse_file, polled_node); - - if (kh < ff->kh) - link = &last->rb_left; - else if (kh > ff->kh) - link = &last->rb_right; - else - return link; - } - - if (parent_out) - *parent_out = last; - return link; -} - -/* - * The file is about to be polled. Make sure it's on the polled_files - * RB tree. Note that files once added to the polled_files tree are - * not removed before the file is released. This is because a file - * polled once is likely to be polled again. - */ -static void fuse_register_polled_file(struct fuse_conn *fc, - struct fuse_file *ff) -{ - spin_lock(&fc->lock); - if (RB_EMPTY_NODE(&ff->polled_node)) { - struct rb_node **link, *parent; - - link = fuse_find_polled_node(fc, ff->kh, &parent); - BUG_ON(*link); - rb_link_node(&ff->polled_node, parent, link); - rb_insert_color(&ff->polled_node, &fc->polled_files); - } - spin_unlock(&fc->lock); -} - -__poll_t fuse_file_poll(struct file *file, poll_table *wait) -{ - struct fuse_file *ff = file->private_data; - struct fuse_mount *fm = ff->fm; - struct fuse_poll_in inarg = { .fh = ff->fh, .kh = ff->kh }; - struct fuse_poll_out outarg; - FUSE_ARGS(args); - int err; - - if (fm->fc->no_poll) - return DEFAULT_POLLMASK; - - poll_wait(file, &ff->poll_wait, wait); - inarg.events = mangle_poll(poll_requested_events(wait)); - - /* - * Ask for notification iff there's someone waiting for it. - * The client may ignore the flag and always notify. - */ - if (waitqueue_active(&ff->poll_wait)) { - inarg.flags |= FUSE_POLL_SCHEDULE_NOTIFY; - fuse_register_polled_file(fm->fc, ff); - } - - args.opcode = FUSE_POLL; - args.nodeid = ff->nodeid; - args.in_numargs = 1; - args.in_args[0].size = sizeof(inarg); - args.in_args[0].value = &inarg; - args.out_numargs = 1; - args.out_args[0].size = sizeof(outarg); - args.out_args[0].value = &outarg; - err = fuse_simple_request(fm, &args); - - if (!err) - return demangle_poll(outarg.revents); - if (err == -ENOSYS) { - fm->fc->no_poll = 1; - return DEFAULT_POLLMASK; - } - return EPOLLERR; -} -EXPORT_SYMBOL_GPL(fuse_file_poll); - -/* - * This is called from fuse_handle_notify() on FUSE_NOTIFY_POLL and - * wakes up the poll waiters. - */ -int fuse_notify_poll_wakeup(struct fuse_conn *fc, - struct fuse_notify_poll_wakeup_out *outarg) -{ - u64 kh = outarg->kh; - struct rb_node **link; - - spin_lock(&fc->lock); - - link = fuse_find_polled_node(fc, kh, NULL); - if (*link) { - struct fuse_file *ff; - - ff = rb_entry(*link, struct fuse_file, polled_node); - wake_up_interruptible_sync(&ff->poll_wait); - } - - spin_unlock(&fc->lock); - return 0; -} - static void fuse_do_truncate(struct file *file) { struct inode *inode = file->f_mapping->host; diff --git a/fs/fuse/poll.c b/fs/fuse/poll.c new file mode 100644 index 000000000000..bce3ee2e861e --- /dev/null +++ b/fs/fuse/poll.c @@ -0,0 +1,141 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include "dev.h" +#include "fuse_i.h" + +void fuse_end_polls(struct fuse_conn *fc) +{ + struct rb_node *p; + + spin_lock(&fc->lock); + p = rb_first(&fc->polled_files); + + while (p) { + struct fuse_file *ff; + ff = rb_entry(p, struct fuse_file, polled_node); + wake_up_interruptible_all(&ff->poll_wait); + + p = rb_next(p); + } + spin_unlock(&fc->lock); +} + +/* + * All files which have been polled are linked to RB tree + * fuse_conn->polled_files which is indexed by kh. Walk the tree and + * find the matching one. + */ +static struct rb_node **fuse_find_polled_node(struct fuse_conn *fc, u64 kh, + struct rb_node **parent_out) +{ + struct rb_node **link = &fc->polled_files.rb_node; + struct rb_node *last = NULL; + + while (*link) { + struct fuse_file *ff; + + last = *link; + ff = rb_entry(last, struct fuse_file, polled_node); + + if (kh < ff->kh) + link = &last->rb_left; + else if (kh > ff->kh) + link = &last->rb_right; + else + return link; + } + + if (parent_out) + *parent_out = last; + return link; +} + +/* + * The file is about to be polled. Make sure it's on the polled_files + * RB tree. Note that files once added to the polled_files tree are + * not removed before the file is released. This is because a file + * polled once is likely to be polled again. + */ +static void fuse_register_polled_file(struct fuse_conn *fc, + struct fuse_file *ff) +{ + spin_lock(&fc->lock); + if (RB_EMPTY_NODE(&ff->polled_node)) { + struct rb_node **link, *parent; + + link = fuse_find_polled_node(fc, ff->kh, &parent); + BUG_ON(*link); + rb_link_node(&ff->polled_node, parent, link); + rb_insert_color(&ff->polled_node, &fc->polled_files); + } + spin_unlock(&fc->lock); +} + +__poll_t fuse_file_poll(struct file *file, poll_table *wait) +{ + struct fuse_file *ff = file->private_data; + struct fuse_mount *fm = ff->fm; + struct fuse_poll_in inarg = { .fh = ff->fh, .kh = ff->kh }; + struct fuse_poll_out outarg; + FUSE_ARGS(args); + int err; + + if (fm->fc->no_poll) + return DEFAULT_POLLMASK; + + poll_wait(file, &ff->poll_wait, wait); + inarg.events = mangle_poll(poll_requested_events(wait)); + + /* + * Ask for notification iff there's someone waiting for it. + * The client may ignore the flag and always notify. + */ + if (waitqueue_active(&ff->poll_wait)) { + inarg.flags |= FUSE_POLL_SCHEDULE_NOTIFY; + fuse_register_polled_file(fm->fc, ff); + } + + args.opcode = FUSE_POLL; + args.nodeid = ff->nodeid; + args.in_numargs = 1; + args.in_args[0].size = sizeof(inarg); + args.in_args[0].value = &inarg; + args.out_numargs = 1; + args.out_args[0].size = sizeof(outarg); + args.out_args[0].value = &outarg; + err = fuse_simple_request(fm, &args); + + if (!err) + return demangle_poll(outarg.revents); + if (err == -ENOSYS) { + fm->fc->no_poll = 1; + return DEFAULT_POLLMASK; + } + return EPOLLERR; +} +EXPORT_SYMBOL_GPL(fuse_file_poll); + +/* + * This is called from fuse_handle_notify() on FUSE_NOTIFY_POLL and + * wakes up the poll waiters. + */ +int fuse_notify_poll_wakeup(struct fuse_conn *fc, + struct fuse_notify_poll_wakeup_out *outarg) +{ + u64 kh = outarg->kh; + struct rb_node **link; + + spin_lock(&fc->lock); + + link = fuse_find_polled_node(fc, kh, NULL); + if (*link) { + struct fuse_file *ff; + + ff = rb_entry(*link, struct fuse_file, polled_node); + wake_up_interruptible_sync(&ff->poll_wait); + } + + spin_unlock(&fc->lock); + return 0; +} + -- cgit v1.2.3 From ca46a4aca6f703c53e59b6fd1a88d49c3eb5c186 Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Tue, 31 Mar 2026 14:50:24 +0200 Subject: fuse: create notify.c Move FUSE_NOTIFY_* handling into a separate source file. Signed-off-by: Miklos Szeredi --- fs/fuse/Makefile | 2 +- fs/fuse/dev.c | 458 ++------------------------------------------------- fs/fuse/dev.h | 12 +- fs/fuse/fuse_dev_i.h | 1 - 4 files changed, 27 insertions(+), 446 deletions(-) diff --git a/fs/fuse/Makefile b/fs/fuse/Makefile index 26086bf5b494..245e67852b03 100644 --- a/fs/fuse/Makefile +++ b/fs/fuse/Makefile @@ -12,7 +12,7 @@ obj-$(CONFIG_VIRTIO_FS) += virtiofs.o fuse-y := trace.o # put trace.o first so we see ftrace errors sooner fuse-y += dev.o dir.o file.o inode.o control.o xattr.o acl.o readdir.o ioctl.o req_timeout.o req.o -fuse-y += poll.o +fuse-y += poll.o notify.o fuse-y += iomode.o fuse-$(CONFIG_FUSE_DAX) += dax.o fuse-$(CONFIG_FUSE_PASSTHROUGH) += passthrough.o backing.o diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index 18a9c4f3d63c..41f6a1b9397f 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -1200,8 +1200,8 @@ static int fuse_ref_folio(struct fuse_copy_state *cs, struct folio *folio, * Copy a folio in the request to/from the userspace buffer. Must be * done atomically */ -static int fuse_copy_folio(struct fuse_copy_state *cs, struct folio **foliop, - unsigned offset, unsigned count, int zeroing) +int fuse_copy_folio(struct fuse_copy_state *cs, struct folio **foliop, + unsigned offset, unsigned count, int zeroing) { int err; struct folio *folio = *foliop; @@ -1282,7 +1282,7 @@ static int fuse_copy_folios(struct fuse_copy_state *cs, unsigned nbytes, } /* Copy a single argument in the request to/from userspace buffer */ -static int fuse_copy_one(struct fuse_copy_state *cs, void *val, unsigned size) +int fuse_copy_one(struct fuse_copy_state *cs, void *val, unsigned size) { while (size) { if (!cs->len) { @@ -1720,343 +1720,6 @@ out: return ret; } -static int fuse_notify_poll(struct fuse_conn *fc, unsigned int size, - struct fuse_copy_state *cs) -{ - struct fuse_notify_poll_wakeup_out outarg; - int err; - - if (size != sizeof(outarg)) - return -EINVAL; - - err = fuse_copy_one(cs, &outarg, sizeof(outarg)); - if (err) - return err; - - fuse_copy_finish(cs); - return fuse_notify_poll_wakeup(fc, &outarg); -} - -static int fuse_notify_inval_inode(struct fuse_conn *fc, unsigned int size, - struct fuse_copy_state *cs) -{ - struct fuse_notify_inval_inode_out outarg; - int err; - - if (size != sizeof(outarg)) - return -EINVAL; - - err = fuse_copy_one(cs, &outarg, sizeof(outarg)); - if (err) - return err; - fuse_copy_finish(cs); - - down_read(&fc->killsb); - err = fuse_reverse_inval_inode(fc, outarg.ino, - outarg.off, outarg.len); - up_read(&fc->killsb); - return err; -} - -static int fuse_notify_inval_entry(struct fuse_conn *fc, unsigned int size, - struct fuse_copy_state *cs) -{ - struct fuse_notify_inval_entry_out outarg; - int err; - char *buf; - struct qstr name; - - if (size < sizeof(outarg)) - return -EINVAL; - - err = fuse_copy_one(cs, &outarg, sizeof(outarg)); - if (err) - return err; - - if (outarg.namelen > fc->name_max) - return -ENAMETOOLONG; - - err = -EINVAL; - if (size != sizeof(outarg) + outarg.namelen + 1) - return -EINVAL; - - buf = kzalloc(outarg.namelen + 1, GFP_KERNEL); - if (!buf) - return -ENOMEM; - - name.name = buf; - name.len = outarg.namelen; - err = fuse_copy_one(cs, buf, outarg.namelen + 1); - if (err) - goto err; - fuse_copy_finish(cs); - buf[outarg.namelen] = 0; - - down_read(&fc->killsb); - err = fuse_reverse_inval_entry(fc, outarg.parent, 0, &name, outarg.flags); - up_read(&fc->killsb); -err: - kfree(buf); - return err; -} - -static int fuse_notify_delete(struct fuse_conn *fc, unsigned int size, - struct fuse_copy_state *cs) -{ - struct fuse_notify_delete_out outarg; - int err; - char *buf; - struct qstr name; - - if (size < sizeof(outarg)) - return -EINVAL; - - err = fuse_copy_one(cs, &outarg, sizeof(outarg)); - if (err) - return err; - - if (outarg.namelen > fc->name_max) - return -ENAMETOOLONG; - - if (size != sizeof(outarg) + outarg.namelen + 1) - return -EINVAL; - - buf = kzalloc(outarg.namelen + 1, GFP_KERNEL); - if (!buf) - return -ENOMEM; - - name.name = buf; - name.len = outarg.namelen; - err = fuse_copy_one(cs, buf, outarg.namelen + 1); - if (err) - goto err; - fuse_copy_finish(cs); - buf[outarg.namelen] = 0; - - down_read(&fc->killsb); - err = fuse_reverse_inval_entry(fc, outarg.parent, outarg.child, &name, 0); - up_read(&fc->killsb); -err: - kfree(buf); - return err; -} - -static int fuse_notify_store(struct fuse_conn *fc, unsigned int size, - struct fuse_copy_state *cs) -{ - struct fuse_notify_store_out outarg; - struct inode *inode; - struct address_space *mapping; - u64 nodeid; - int err; - unsigned int num; - loff_t file_size; - loff_t pos; - loff_t end; - - if (size < sizeof(outarg)) - return -EINVAL; - - err = fuse_copy_one(cs, &outarg, sizeof(outarg)); - if (err) - return err; - - if (size - sizeof(outarg) != outarg.size) - return -EINVAL; - - if (outarg.offset >= MAX_LFS_FILESIZE) - return -EINVAL; - - nodeid = outarg.nodeid; - pos = outarg.offset; - num = min(outarg.size, MAX_LFS_FILESIZE - pos); - - down_read(&fc->killsb); - - err = -ENOENT; - inode = fuse_ilookup(fc, nodeid, NULL); - if (!inode) - goto out_up_killsb; - - mapping = inode->i_mapping; - file_size = i_size_read(inode); - end = pos + num; - if (end > file_size) { - file_size = end; - fuse_write_update_attr(inode, file_size, num); - } - - while (num) { - struct folio *folio; - unsigned int folio_offset; - unsigned int nr_bytes; - pgoff_t index = pos >> PAGE_SHIFT; - - folio = filemap_grab_folio(mapping, index); - err = PTR_ERR(folio); - if (IS_ERR(folio)) - goto out_iput; - - folio_offset = offset_in_folio(folio, pos); - nr_bytes = min(num, folio_size(folio) - folio_offset); - - err = fuse_copy_folio(cs, &folio, folio_offset, nr_bytes, 0); - if (!folio_test_uptodate(folio) && !err && folio_offset == 0 && - (nr_bytes == folio_size(folio) || file_size == end)) { - folio_zero_segment(folio, nr_bytes, folio_size(folio)); - folio_mark_uptodate(folio); - } - folio_unlock(folio); - folio_put(folio); - - if (err) - goto out_iput; - - pos += nr_bytes; - num -= nr_bytes; - } - - err = 0; - -out_iput: - iput(inode); -out_up_killsb: - up_read(&fc->killsb); - return err; -} - -struct fuse_retrieve_args { - struct fuse_args_pages ap; - struct fuse_notify_retrieve_in inarg; -}; - -static void fuse_retrieve_end(struct fuse_args *args, int error) -{ - struct fuse_retrieve_args *ra = - container_of(args, typeof(*ra), ap.args); - - release_pages(ra->ap.folios, ra->ap.num_folios); - kfree(ra); -} - -static int fuse_retrieve(struct fuse_mount *fm, struct inode *inode, - struct fuse_notify_retrieve_out *outarg) -{ - int err; - struct address_space *mapping = inode->i_mapping; - loff_t file_size; - unsigned int num; - unsigned int offset; - size_t total_len = 0; - unsigned int num_pages; - struct fuse_conn *fc = fm->fc; - struct fuse_retrieve_args *ra; - size_t args_size = sizeof(*ra); - struct fuse_args_pages *ap; - struct fuse_args *args; - loff_t pos = outarg->offset; - - offset = offset_in_page(pos); - file_size = i_size_read(inode); - - num = min(outarg->size, fc->max_write); - if (pos > file_size) - num = 0; - else if (num > file_size - pos) - num = file_size - pos; - - num_pages = DIV_ROUND_UP(num + offset, PAGE_SIZE); - num_pages = min(num_pages, fc->max_pages); - num = min(num, num_pages << PAGE_SHIFT); - - args_size += num_pages * (sizeof(ap->folios[0]) + sizeof(ap->descs[0])); - - ra = kzalloc(args_size, GFP_KERNEL); - if (!ra) - return -ENOMEM; - - ap = &ra->ap; - ap->folios = (void *) (ra + 1); - ap->descs = (void *) (ap->folios + num_pages); - - args = &ap->args; - args->nodeid = outarg->nodeid; - args->opcode = FUSE_NOTIFY_REPLY; - args->in_numargs = 3; - args->in_pages = true; - args->end = fuse_retrieve_end; - - while (num && ap->num_folios < num_pages) { - struct folio *folio; - unsigned int folio_offset; - unsigned int nr_bytes; - pgoff_t index = pos >> PAGE_SHIFT; - - folio = filemap_get_folio(mapping, index); - if (IS_ERR(folio)) - break; - - folio_offset = offset_in_folio(folio, pos); - nr_bytes = min(folio_size(folio) - folio_offset, num); - - ap->folios[ap->num_folios] = folio; - ap->descs[ap->num_folios].offset = folio_offset; - ap->descs[ap->num_folios].length = nr_bytes; - ap->num_folios++; - - pos += nr_bytes; - num -= nr_bytes; - total_len += nr_bytes; - } - ra->inarg.offset = outarg->offset; - ra->inarg.size = total_len; - fuse_set_zero_arg0(args); - args->in_args[1].size = sizeof(ra->inarg); - args->in_args[1].value = &ra->inarg; - args->in_args[2].size = total_len; - - err = fuse_simple_notify_reply(fm, args, outarg->notify_unique); - if (err) - fuse_retrieve_end(args, err); - - return err; -} - -static int fuse_notify_retrieve(struct fuse_conn *fc, unsigned int size, - struct fuse_copy_state *cs) -{ - struct fuse_notify_retrieve_out outarg; - struct fuse_mount *fm; - struct inode *inode; - u64 nodeid; - int err; - - if (size != sizeof(outarg)) - return -EINVAL; - - err = fuse_copy_one(cs, &outarg, sizeof(outarg)); - if (err) - return err; - - fuse_copy_finish(cs); - - if (outarg.offset >= MAX_LFS_FILESIZE) - return -EINVAL; - - down_read(&fc->killsb); - err = -ENOENT; - nodeid = outarg.nodeid; - - inode = fuse_ilookup(fc, nodeid, &fm); - if (inode) { - err = fuse_retrieve(fm, inode, &outarg); - iput(inode); - } - up_read(&fc->killsb); - - return err; -} - /* * Resending all processing queue requests. * @@ -2070,7 +1733,7 @@ static int fuse_notify_retrieve(struct fuse_conn *fc, unsigned int size, * if the FUSE daemon takes careful measures to avoid processing duplicated * non-idempotent requests. */ -static void fuse_resend(struct fuse_chan *fch) +void fuse_chan_resend(struct fuse_chan *fch) { struct fuse_dev *fud; struct fuse_req *req, *next; @@ -2122,108 +1785,6 @@ static void fuse_resend(struct fuse_chan *fch) fuse_dev_wake_and_unlock(fiq); } -static int fuse_notify_resend(struct fuse_conn *fc) -{ - fuse_resend(fc->chan); - return 0; -} - -/* - * Increments the fuse connection epoch. This will result of dentries from - * previous epochs to be invalidated. Additionally, if inval_wq is set, a work - * queue is scheduled to trigger the invalidation. - */ -static int fuse_notify_inc_epoch(struct fuse_conn *fc) -{ - atomic_inc(&fc->epoch); - if (inval_wq) - schedule_work(&fc->epoch_work); - - return 0; -} - -static int fuse_notify_prune(struct fuse_conn *fc, unsigned int size, - struct fuse_copy_state *cs) -{ - struct fuse_notify_prune_out outarg; - const unsigned int batch = 512; - u64 *nodeids __free(kfree) = kmalloc(sizeof(u64) * batch, GFP_KERNEL); - unsigned int num, i; - int err; - - if (!nodeids) - return -ENOMEM; - - if (size < sizeof(outarg)) - return -EINVAL; - - err = fuse_copy_one(cs, &outarg, sizeof(outarg)); - if (err) - return err; - - if (size - sizeof(outarg) != array_size(outarg.count, sizeof(u64))) - return -EINVAL; - - for (; outarg.count; outarg.count -= num) { - num = min(batch, outarg.count); - err = fuse_copy_one(cs, nodeids, num * sizeof(u64)); - if (err) - return err; - - scoped_guard(rwsem_read, &fc->killsb) { - for (i = 0; i < num; i++) - fuse_try_prune_one_inode(fc, nodeids[i]); - } - } - return 0; -} - -static int fuse_notify(struct fuse_conn *fc, enum fuse_notify_code code, - unsigned int size, struct fuse_copy_state *cs) -{ - /* - * Only allow notifications during while the connection is in an - * initialized and connected state - */ - if (!fc->chan->initialized || !fc->chan->connected) - return -EINVAL; - - /* Don't try to move folios (yet) */ - cs->move_folios = false; - - switch (code) { - case FUSE_NOTIFY_POLL: - return fuse_notify_poll(fc, size, cs); - - case FUSE_NOTIFY_INVAL_INODE: - return fuse_notify_inval_inode(fc, size, cs); - - case FUSE_NOTIFY_INVAL_ENTRY: - return fuse_notify_inval_entry(fc, size, cs); - - case FUSE_NOTIFY_STORE: - return fuse_notify_store(fc, size, cs); - - case FUSE_NOTIFY_RETRIEVE: - return fuse_notify_retrieve(fc, size, cs); - - case FUSE_NOTIFY_DELETE: - return fuse_notify_delete(fc, size, cs); - - case FUSE_NOTIFY_RESEND: - return fuse_notify_resend(fc); - - case FUSE_NOTIFY_INC_EPOCH: - return fuse_notify_inc_epoch(fc); - - case FUSE_NOTIFY_PRUNE: - return fuse_notify_prune(fc, size, cs); - - default: - return -EINVAL; - } -} - /* Look up request on processing list by unique ID */ struct fuse_req *fuse_request_find(struct fuse_pqueue *fpq, u64 unique) { @@ -2298,6 +1859,17 @@ static ssize_t fuse_dev_do_write(struct fuse_dev *fud, * and error contains notification code. */ if (!oh.unique) { + /* + * Only allow notifications during while the connection is in an + * initialized and connected state + */ + err = -EINVAL; + if (!fch->initialized || !fch->connected) + goto copy_finish; + + /* Don't try to move folios (yet) */ + cs->move_folios = false; + err = fuse_notify(fch->conn, oh.error, nbytes - sizeof(oh), cs); goto copy_finish; } diff --git a/fs/fuse/dev.h b/fs/fuse/dev.h index 2c4d0c53ffbb..2bf4aba256d1 100644 --- a/fs/fuse/dev.h +++ b/fs/fuse/dev.h @@ -12,7 +12,10 @@ struct fuse_conn; struct fuse_chan; struct fuse_dev; struct fuse_args; +struct fuse_copy_state; struct file; +struct folio; +enum fuse_notify_code; struct fuse_chan *fuse_chan_new(void); struct fuse_chan *fuse_dev_chan_new(void); @@ -28,12 +31,12 @@ void fuse_chan_io_uring_enable(struct fuse_chan *fch); ssize_t fuse_chan_send(struct fuse_chan *fch, struct fuse_args *args); int fuse_chan_send_bg(struct fuse_chan *fch, struct fuse_args *args, gfp_t gfp_flags); int fuse_chan_send_notify_reply(struct fuse_chan *fch, struct fuse_args *args, u64 unique); +void fuse_chan_resend(struct fuse_chan *fch); struct fuse_forget_link *fuse_alloc_forget(void); void fuse_chan_queue_forget(struct fuse_chan *fch, struct fuse_forget_link *forget, u64 nodeid, u64 nlookup); - DEFINE_FREE(fuse_chan_free, struct fuse_chan *, if (_T) fuse_chan_free(_T)) void fuse_dev_install(struct fuse_dev *fud, struct fuse_chan *fch); @@ -50,6 +53,13 @@ void fuse_chan_abort(struct fuse_chan *fch, bool abort_with_err); void fuse_chan_wait_aborted(struct fuse_chan *fch); void fuse_end_polls(struct fuse_conn *fc); +int fuse_notify(struct fuse_conn *fc, enum fuse_notify_code code, + unsigned int size, struct fuse_copy_state *cs); + +int fuse_copy_one(struct fuse_copy_state *cs, void *val, unsigned size); +int fuse_copy_folio(struct fuse_copy_state *cs, struct folio **foliop, + unsigned offset, unsigned count, int zeroing); +void fuse_copy_finish(struct fuse_copy_state *cs); #ifdef CONFIG_FUSE_IO_URING bool fuse_uring_enabled(void); diff --git a/fs/fuse/fuse_dev_i.h b/fs/fuse/fuse_dev_i.h index 9b5ebc6b7762..35475a2f6f0a 100644 --- a/fs/fuse/fuse_dev_i.h +++ b/fs/fuse/fuse_dev_i.h @@ -355,7 +355,6 @@ void fuse_request_bg_finish(struct fuse_chan *fch, struct fuse_req *req); void fuse_copy_init(struct fuse_copy_state *cs, bool write, struct iov_iter *iter); -void fuse_copy_finish(struct fuse_copy_state *cs); int fuse_copy_args(struct fuse_copy_state *cs, unsigned int numargs, unsigned int argpages, struct fuse_arg *args, int zeroing); -- cgit v1.2.3 From ff572265f29e4ad6d2fe5f625d9bbd741a187266 Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Tue, 31 Mar 2026 15:50:29 +0200 Subject: fuse: set params in fuse_chan_set_initialized() Set minor, max_write and max_pages in the fuse_chan. These match the same fields in fuse_conn but are needed in both layers. [Dongyang Jin: Pointers should use NULL instead of explicit '0'] Signed-off-by: Miklos Szeredi --- fs/fuse/dev.c | 30 ++++++++++++++++++------------ fs/fuse/dev.h | 8 +++++++- fs/fuse/dev_uring.c | 4 ++-- fs/fuse/fuse_dev_i.h | 9 +++++++++ fs/fuse/inode.c | 10 ++++++++-- 5 files changed, 44 insertions(+), 17 deletions(-) diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index 41f6a1b9397f..ec2f53b3d2c8 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -72,8 +72,14 @@ static void __fuse_put_request(struct fuse_req *req) refcount_dec(&req->count); } -void fuse_chan_set_initialized(struct fuse_chan *fch) +void fuse_chan_set_initialized(struct fuse_chan *fch, struct fuse_chan_param *param) { + if (param) { + fch->minor = param->minor; + fch->max_write = param->max_write; + fch->max_pages = param->max_pages; + } + /* Make sure stores before this are seen on another CPU */ smp_wmb(); fch->initialized = 1; @@ -719,12 +725,12 @@ static void __fuse_request_send(struct fuse_req *req) smp_rmb(); } -static void fuse_adjust_compat(struct fuse_conn *fc, struct fuse_args *args) +static void fuse_adjust_compat(struct fuse_chan *fch, struct fuse_args *args) { - if (fc->minor < 4 && args->opcode == FUSE_STATFS) + if (fch->minor < 4 && args->opcode == FUSE_STATFS) args->out_args[0].size = FUSE_COMPAT_STATFS_SIZE; - if (fc->minor < 9) { + if (fch->minor < 9) { switch (args->opcode) { case FUSE_LOOKUP: case FUSE_CREATE: @@ -740,7 +746,7 @@ static void fuse_adjust_compat(struct fuse_conn *fc, struct fuse_args *args) break; } } - if (fc->minor < 12) { + if (fch->minor < 12) { switch (args->opcode) { case FUSE_CREATE: args->in_args[0].size = sizeof(struct fuse_open_in); @@ -784,8 +790,8 @@ ssize_t fuse_chan_send(struct fuse_chan *fch, struct fuse_args *args) return PTR_ERR(req); } - /* Needs to be done after fuse_get_req() so that fc->minor is valid */ - fuse_adjust_compat(fch->conn, args); + /* Needs to be done after fuse_get_req() so that fch->minor is valid */ + fuse_adjust_compat(fch, args); fuse_args_to_req(req, args); if (!args->noreply) @@ -1466,12 +1472,12 @@ __releases(fiq->lock) return ih.len; } -static int fuse_read_forget(struct fuse_conn *fc, struct fuse_iqueue *fiq, +static int fuse_read_forget(struct fuse_chan *fch, struct fuse_iqueue *fiq, struct fuse_copy_state *cs, size_t nbytes) __releases(fiq->lock) { - if (fc->minor < 16 || fiq->forget_list_head.next->next == NULL) + if (fch->minor < 16 || fiq->forget_list_head.next->next == NULL) return fuse_read_single_forget(fiq, cs, nbytes); else return fuse_read_batch_forget(fiq, cs, nbytes); @@ -1513,7 +1519,7 @@ static ssize_t fuse_dev_do_read(struct fuse_dev *fud, struct file *file, if (nbytes < max_t(size_t, FUSE_MIN_READ_BUFFER, sizeof(struct fuse_in_header) + sizeof(struct fuse_write_in) + - fch->conn->max_write)) + fch->max_write)) return -EINVAL; restart: @@ -1544,7 +1550,7 @@ static ssize_t fuse_dev_do_read(struct fuse_dev *fud, struct file *file, if (forget_pending(fiq)) { if (list_empty(&fiq->pending) || fiq->forget_batch-- > 0) - return fuse_read_forget(fch->conn, fiq, cs, nbytes); + return fuse_read_forget(fch, fiq, cs, nbytes); if (fiq->forget_batch <= -8) fiq->forget_batch = 16; @@ -2123,7 +2129,7 @@ void fuse_chan_abort(struct fuse_chan *fch, bool abort_with_err) fch->connected = 0; spin_unlock(&fch->bg_lock); - fuse_chan_set_initialized(fch); + fuse_chan_set_initialized(fch, NULL); list_for_each_entry(fud, &fch->devices, entry) { struct fuse_pqueue *fpq = &fud->pq; diff --git a/fs/fuse/dev.h b/fs/fuse/dev.h index 2bf4aba256d1..6437331058d9 100644 --- a/fs/fuse/dev.h +++ b/fs/fuse/dev.h @@ -17,6 +17,12 @@ struct file; struct folio; enum fuse_notify_code; +struct fuse_chan_param { + unsigned int minor; + unsigned int max_write; + unsigned int max_pages; +}; + struct fuse_chan *fuse_chan_new(void); struct fuse_chan *fuse_dev_chan_new(void); void fuse_chan_release(struct fuse_chan *fch); @@ -26,7 +32,7 @@ unsigned int fuse_chan_max_background(struct fuse_chan *fch); void fuse_chan_max_background_set(struct fuse_chan *fch, unsigned int val); unsigned int fuse_chan_num_waiting(struct fuse_chan *fch); void fuse_chan_set_fc(struct fuse_chan *fch, struct fuse_conn *fc); -void fuse_chan_set_initialized(struct fuse_chan *fch); +void fuse_chan_set_initialized(struct fuse_chan *fch, struct fuse_chan_param *param); void fuse_chan_io_uring_enable(struct fuse_chan *fch); ssize_t fuse_chan_send(struct fuse_chan *fch, struct fuse_args *args); int fuse_chan_send_bg(struct fuse_chan *fch, struct fuse_args *args, gfp_t gfp_flags); diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c index 109e3d07c3f1..73dc886d967b 100644 --- a/fs/fuse/dev_uring.c +++ b/fs/fuse/dev_uring.c @@ -241,8 +241,8 @@ static struct fuse_ring *fuse_uring_create(struct fuse_chan *fch) if (!ring->queues) goto out_err; - max_payload_size = max(FUSE_MIN_READ_BUFFER, fch->conn->max_write); - max_payload_size = max(max_payload_size, fch->conn->max_pages * PAGE_SIZE); + max_payload_size = max(FUSE_MIN_READ_BUFFER, fch->max_write); + max_payload_size = max(max_payload_size, fch->max_pages * PAGE_SIZE); spin_lock(&fch->lock); if (!fch->connected) { diff --git a/fs/fuse/fuse_dev_i.h b/fs/fuse/fuse_dev_i.h index 35475a2f6f0a..6550952f815b 100644 --- a/fs/fuse/fuse_dev_i.h +++ b/fs/fuse/fuse_dev_i.h @@ -238,6 +238,15 @@ struct fuse_chan { /* Use io_uring for communication */ unsigned int io_uring; + /* Negotiated minor version */ + unsigned int minor; + + /* Maximum write size */ + unsigned int max_write; + + /* Maximum number of pages that can be used in a single request */ + unsigned int max_pages; + /** Connection aborted via sysfs, respond with ECONNABORTED on device I/O */ bool abort_with_err; diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c index 9e96bacdfc3e..79047734ab40 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c @@ -1426,9 +1426,15 @@ static void process_init_reply(struct fuse_args *args, int error) if (!ok) { fc->conn_init = 0; fc->conn_error = 1; + fuse_chan_set_initialized(fc->chan, NULL); + } else { + struct fuse_chan_param cp = { + .minor = fc->minor, + .max_write = fc->max_write, + .max_pages = fc->max_pages, + }; + fuse_chan_set_initialized(fc->chan, &cp); } - - fuse_chan_set_initialized(fc->chan); } static struct fuse_init_args *fuse_new_init(struct fuse_mount *fm) -- cgit v1.2.3 From e582371be4a75c461fecb61b98ee391025f91946 Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Tue, 31 Mar 2026 16:32:21 +0200 Subject: fuse: remove fuse_mutex protection from fuse_dev_ioctl_sync_init() In normal use ioctl(FUSE_DEV_IOC_SYNC_INIT) comes before the mount() or fsconfig() syscalls, they are executed strictly serially. If ioctl and mount are performed in parallel, the behavior is nondeterministic. Removing the mutex does not change this. Signed-off-by: Miklos Szeredi --- fs/fuse/dev.c | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index ec2f53b3d2c8..aab98f75b9dd 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -2310,16 +2310,13 @@ static long fuse_dev_ioctl_backing_close(struct file *file, __u32 __user *argp) static long fuse_dev_ioctl_sync_init(struct file *file) { - int err = -EINVAL; struct fuse_dev *fud = fuse_file_to_fud(file); - mutex_lock(&fuse_mutex); - if (!fuse_dev_chan_get(fud)) { - fud->sync_init = true; - err = 0; - } - mutex_unlock(&fuse_mutex); - return err; + if (fuse_dev_chan_get(fud)) + return -EINVAL; + + fud->sync_init = true; + return 0; } static long fuse_dev_ioctl(struct file *file, unsigned int cmd, -- cgit v1.2.3 From b03404ea3a05668d374c87741c397a4eeaf4dd81 Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Tue, 31 Mar 2026 16:57:50 +0200 Subject: fuse: change ring->fc to ring->chan Store pointer to struct fuse_chan instead of struct fuse_conn in fuse_ring. Signed-off-by: Miklos Szeredi --- fs/fuse/dev_uring.c | 58 +++++++++++++++++++++++++-------------------------- fs/fuse/dev_uring_i.h | 2 +- 2 files changed, 29 insertions(+), 31 deletions(-) diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c index 73dc886d967b..f3603f1f7040 100644 --- a/fs/fuse/dev_uring.c +++ b/fs/fuse/dev_uring.c @@ -52,10 +52,10 @@ static struct fuse_ring_ent *uring_cmd_to_ring_ent(struct io_uring_cmd *cmd) static void fuse_uring_flush_bg(struct fuse_ring_queue *queue) { struct fuse_ring *ring = queue->ring; - struct fuse_conn *fc = ring->fc; + struct fuse_chan *fch = ring->chan; lockdep_assert_held(&queue->lock); - lockdep_assert_held(&fc->chan->bg_lock); + lockdep_assert_held(&fch->bg_lock); /* * Allow one bg request per queue, ignoring global fc limits. @@ -63,14 +63,14 @@ static void fuse_uring_flush_bg(struct fuse_ring_queue *queue) * eliminates the need for remote queue wake-ups when global * limits are met but this queue has no more waiting requests. */ - while ((fc->chan->active_background < fc->chan->max_background || + while ((fch->active_background < fch->max_background || !queue->active_background) && (!list_empty(&queue->fuse_req_bg_queue))) { struct fuse_req *req; req = list_first_entry(&queue->fuse_req_bg_queue, struct fuse_req, list); - fc->chan->active_background++; + fch->active_background++; queue->active_background++; list_move_tail(&req->list, &queue->fuse_req_queue); @@ -82,7 +82,7 @@ static void fuse_uring_req_end(struct fuse_ring_ent *ent, struct fuse_req *req, { struct fuse_ring_queue *queue = ent->queue; struct fuse_ring *ring = queue->ring; - struct fuse_conn *fc = ring->fc; + struct fuse_chan *fch = ring->chan; lockdep_assert_not_held(&queue->lock); spin_lock(&queue->lock); @@ -90,10 +90,10 @@ static void fuse_uring_req_end(struct fuse_ring_ent *ent, struct fuse_req *req, list_del_init(&req->list); if (test_bit(FR_BACKGROUND, &req->flags)) { queue->active_background--; - spin_lock(&fc->chan->bg_lock); - fuse_request_bg_finish(fc->chan, req); + spin_lock(&fch->bg_lock); + fuse_request_bg_finish(fch, req); fuse_uring_flush_bg(queue); - spin_unlock(&fc->chan->bg_lock); + spin_unlock(&fch->bg_lock); } spin_unlock(&queue->lock); @@ -125,19 +125,19 @@ void fuse_uring_abort_end_requests(struct fuse_ring *ring) { int qid; struct fuse_ring_queue *queue; - struct fuse_conn *fc = ring->fc; + struct fuse_chan *fch = ring->chan; for (qid = 0; qid < ring->nr_queues; qid++) { queue = READ_ONCE(ring->queues[qid]); if (!queue) continue; - WARN_ON_ONCE(ring->fc->chan->max_background != UINT_MAX); + WARN_ON_ONCE(fch->max_background != UINT_MAX); spin_lock(&queue->lock); queue->stopped = true; - spin_lock(&fc->chan->bg_lock); + spin_lock(&fch->bg_lock); fuse_uring_flush_bg(queue); - spin_unlock(&fc->chan->bg_lock); + spin_unlock(&fch->bg_lock); spin_unlock(&queue->lock); fuse_uring_abort_end_queue_requests(queue); } @@ -259,7 +259,7 @@ static struct fuse_ring *fuse_uring_create(struct fuse_chan *fch) init_waitqueue_head(&ring->stop_waitq); ring->nr_queues = nr_queues; - ring->fc = fch->conn; + ring->chan = fch; ring->max_payload_sz = max_payload_size; smp_store_release(&fch->ring, ring); @@ -275,7 +275,7 @@ out_err: static struct fuse_ring_queue *fuse_uring_create_queue(struct fuse_ring *ring, int qid) { - struct fuse_conn *fc = ring->fc; + struct fuse_chan *fch = ring->chan; struct fuse_ring_queue *queue; struct list_head *pq; @@ -303,9 +303,9 @@ static struct fuse_ring_queue *fuse_uring_create_queue(struct fuse_ring *ring, queue->fpq.processing = pq; fuse_pqueue_init(&queue->fpq); - spin_lock(&fc->chan->lock); + spin_lock(&fch->lock); if (ring->queues[qid]) { - spin_unlock(&fc->chan->lock); + spin_unlock(&fch->lock); kfree(queue->fpq.processing); kfree(queue); return ring->queues[qid]; @@ -315,7 +315,7 @@ static struct fuse_ring_queue *fuse_uring_create_queue(struct fuse_ring *ring, * write_once and lock as the caller mostly doesn't take the lock at all */ WRITE_ONCE(ring->queues[qid], queue); - spin_unlock(&fc->chan->lock); + spin_unlock(&fch->lock); return queue; } @@ -471,7 +471,7 @@ static void fuse_uring_async_stop_queues(struct work_struct *work) FUSE_URING_TEARDOWN_INTERVAL); } else { wake_up_all(&ring->stop_waitq); - fuse_conn_put(ring->fc); + fuse_conn_put(ring->chan->conn); } } @@ -483,7 +483,7 @@ void fuse_uring_stop_queues(struct fuse_ring *ring) fuse_uring_teardown_all_queues(ring); if (atomic_read(&ring->queue_refs) > 0) { - fuse_conn_get(ring->fc); + fuse_conn_get(ring->chan->conn); ring->teardown_time = jiffies; INIT_DELAYED_WORK(&ring->async_teardown_work, fuse_uring_async_stop_queues); @@ -540,8 +540,7 @@ static void fuse_uring_prepare_cancel(struct io_uring_cmd *cmd, int issue_flags, * Checks for errors and stores it into the request */ static int fuse_uring_out_header_has_err(struct fuse_out_header *oh, - struct fuse_req *req, - struct fuse_conn *fc) + struct fuse_req *req) { int err; @@ -835,14 +834,13 @@ static void fuse_uring_commit(struct fuse_ring_ent *ent, struct fuse_req *req, unsigned int issue_flags) { struct fuse_ring *ring = ent->queue->ring; - struct fuse_conn *fc = ring->fc; ssize_t err = -EFAULT; if (copy_from_user(&req->out.h, &ent->headers->in_out, sizeof(req->out.h))) goto out; - err = fuse_uring_out_header_has_err(&req->out.h, req, fc); + err = fuse_uring_out_header_has_err(&req->out.h, req); if (err) { /* req->out.h.error already set */ goto out; @@ -1006,19 +1004,19 @@ static int fuse_uring_do_register(struct fuse_ring_ent *ent, { struct fuse_ring_queue *queue = ent->queue; struct fuse_ring *ring = queue->ring; - struct fuse_conn *fc = ring->fc; - struct fuse_iqueue *fiq = &fc->chan->iq; + struct fuse_chan *fch = ring->chan; + struct fuse_iqueue *fiq = &fch->iq; - spin_lock(&fc->chan->lock); + spin_lock(&fch->lock); /* abort teardown path is running or has run */ - if (!fc->chan->connected) { - spin_unlock(&fc->chan->lock); + if (!fch->connected) { + spin_unlock(&fch->lock); if (atomic_dec_and_test(&ring->queue_refs)) wake_up_all(&ring->stop_waitq); kfree(ent); return -ECONNABORTED; } - spin_unlock(&fc->chan->lock); + spin_unlock(&fch->lock); fuse_uring_prepare_cancel(cmd, issue_flags, ent); @@ -1033,7 +1031,7 @@ static int fuse_uring_do_register(struct fuse_ring_ent *ent, if (ready) { WRITE_ONCE(fiq->ops, &fuse_io_uring_ops); smp_store_release(&ring->ready, true); - wake_up_all(&fc->chan->blocked_waitq); + wake_up_all(&fch->blocked_waitq); } } return 0; diff --git a/fs/fuse/dev_uring_i.h b/fs/fuse/dev_uring_i.h index c8649c4f30e6..1875f73168ef 100644 --- a/fs/fuse/dev_uring_i.h +++ b/fs/fuse/dev_uring_i.h @@ -107,7 +107,7 @@ struct fuse_ring_queue { */ struct fuse_ring { /* back pointer */ - struct fuse_conn *fc; + struct fuse_chan *chan; /* number of ring queues */ size_t nr_queues; -- cgit v1.2.3 From c0f817320d6afc8c609400e235f6f16636ed871b Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Wed, 1 Apr 2026 09:02:14 +0200 Subject: fuse: remove #include "fuse_i.h" from dev.c and dev_uring.c Move a couple of function declarations from fuse_i.h to dev.h and fuse_dev_i.h. Add fuse_conn_get_id() helper that retrieves the connection ID (s_dev) from fuse_conn. With the exception of cuse.c, virtio_fs.c and trace.c source files now either include fuse_i.h or fuse_dev_i/dev_uring_i.h but not both. Signed-off-by: Miklos Szeredi --- fs/fuse/backing.c | 1 + fs/fuse/dev.c | 5 ++--- fs/fuse/dev.h | 26 ++++++++++++++++++++++++++ fs/fuse/dev_uring.c | 3 +-- fs/fuse/fuse_dev_i.h | 7 +++++++ fs/fuse/fuse_i.h | 28 ---------------------------- fs/fuse/inode.c | 5 +++++ 7 files changed, 42 insertions(+), 33 deletions(-) diff --git a/fs/fuse/backing.c b/fs/fuse/backing.c index d95dfa48483f..3d3f49c2dd42 100644 --- a/fs/fuse/backing.c +++ b/fs/fuse/backing.c @@ -5,6 +5,7 @@ * Copyright (c) 2023 CTERA Networks. */ +#include "dev.h" #include "fuse_i.h" #include diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index aab98f75b9dd..2403cd445e72 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -7,9 +7,8 @@ */ #include "dev.h" +#include "args.h" #include "dev_uring_i.h" -#include "fuse_i.h" -#include "fuse_dev_i.h" #include #include @@ -2349,7 +2348,7 @@ static void fuse_dev_show_fdinfo(struct seq_file *seq, struct file *file) if (!fud) return; - seq_printf(seq, "fuse_connection:\t%u\n", fud->chan->conn->dev); + seq_printf(seq, "fuse_connection:\t%u\n", fuse_conn_get_id(fud->chan->conn)); } #endif diff --git a/fs/fuse/dev.h b/fs/fuse/dev.h index 6437331058d9..aed69fd14c41 100644 --- a/fs/fuse/dev.h +++ b/fs/fuse/dev.h @@ -13,6 +13,7 @@ struct fuse_chan; struct fuse_dev; struct fuse_args; struct fuse_copy_state; +struct fuse_backing_map; struct file; struct folio; enum fuse_notify_code; @@ -45,6 +46,16 @@ void fuse_chan_queue_forget(struct fuse_chan *fch, struct fuse_forget_link *forg DEFINE_FREE(fuse_chan_free, struct fuse_chan *, if (_T) fuse_chan_free(_T)) +/** + * Initialize the client device + */ +int fuse_dev_init(void); + +/** + * Cleanup the client device + */ +void fuse_dev_cleanup(void); + void fuse_dev_install(struct fuse_dev *fud, struct fuse_chan *fch); bool fuse_dev_verify(struct fuse_dev *fud, struct fuse_chan *fch); void fuse_dev_put(struct fuse_dev *fud); @@ -58,10 +69,25 @@ void fuse_init_server_timeout(struct fuse_chan *fch, unsigned int timeout); void fuse_chan_abort(struct fuse_chan *fch, bool abort_with_err); void fuse_chan_wait_aborted(struct fuse_chan *fch); +/** + * Acquire reference to fuse_conn + */ +struct fuse_conn *fuse_conn_get(struct fuse_conn *fc); + +/** + * Release reference to fuse_conn + */ +void fuse_conn_put(struct fuse_conn *fc); + +dev_t fuse_conn_get_id(struct fuse_conn *fc); + void fuse_end_polls(struct fuse_conn *fc); int fuse_notify(struct fuse_conn *fc, enum fuse_notify_code code, unsigned int size, struct fuse_copy_state *cs); +int fuse_backing_open(struct fuse_conn *fc, struct fuse_backing_map *map); +int fuse_backing_close(struct fuse_conn *fc, int backing_id); + int fuse_copy_one(struct fuse_copy_state *cs, void *val, unsigned size); int fuse_copy_folio(struct fuse_copy_state *cs, struct folio **foliop, unsigned offset, unsigned count, int zeroing); diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c index f3603f1f7040..99138e3ec83f 100644 --- a/fs/fuse/dev_uring.c +++ b/fs/fuse/dev_uring.c @@ -5,9 +5,8 @@ */ #include "dev.h" -#include "fuse_i.h" +#include "args.h" #include "dev_uring_i.h" -#include "fuse_dev_i.h" #include "fuse_trace.h" #include diff --git a/fs/fuse/fuse_dev_i.h b/fs/fuse/fuse_dev_i.h index 6550952f815b..dcfafac786fc 100644 --- a/fs/fuse/fuse_dev_i.h +++ b/fs/fuse/fuse_dev_i.h @@ -364,6 +364,11 @@ void fuse_request_bg_finish(struct fuse_chan *fch, struct fuse_req *req); void fuse_copy_init(struct fuse_copy_state *cs, bool write, struct iov_iter *iter); +/** + * Return the number of bytes in an arguments list + */ +unsigned int fuse_len_args(unsigned int numargs, struct fuse_arg *args); + int fuse_copy_args(struct fuse_copy_state *cs, unsigned int numargs, unsigned int argpages, struct fuse_arg *args, int zeroing); @@ -389,6 +394,8 @@ u64 fuse_get_unique(struct fuse_iqueue *fiq); struct fuse_dev *fuse_dev_alloc_install(struct fuse_chan *fch); struct fuse_dev *fuse_dev_alloc(void); +int fuse_dev_release(struct inode *inode, struct file *file); + /** * Initialize the fuse processing queue */ diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index f7a9c7cedd39..8c58a030d296 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -907,16 +907,6 @@ void fuse_change_attributes_common(struct inode *inode, struct fuse_attr *attr, u32 fuse_get_cache_mask(struct inode *inode); -/** - * Initialize the client device - */ -int fuse_dev_init(void); - -/** - * Cleanup the client device - */ -void fuse_dev_cleanup(void); - int fuse_ctl_init(void); void __exit fuse_ctl_cleanup(void); @@ -971,22 +961,12 @@ u64 fuse_time_to_jiffies(u64 sec, u32 nsec); void fuse_change_entry_timeout(struct dentry *entry, struct fuse_entry_out *o); -/** - * Acquire reference to fuse_conn - */ -struct fuse_conn *fuse_conn_get(struct fuse_conn *fc); - /** * Initialize fuse_conn */ void fuse_conn_init(struct fuse_conn *fc, struct fuse_mount *fm, struct user_namespace *user_ns, struct fuse_chan *fch); -/** - * Release reference to fuse_conn - */ -void fuse_conn_put(struct fuse_conn *fc); - int fuse_send_init(struct fuse_mount *fm); /** @@ -1105,7 +1085,6 @@ long fuse_do_ioctl(struct file *file, unsigned int cmd, unsigned long arg, long fuse_ioctl_common(struct file *file, unsigned int cmd, unsigned long arg, unsigned int flags); __poll_t fuse_file_poll(struct file *file, poll_table *wait); -int fuse_dev_release(struct inode *inode, struct file *file); bool fuse_write_update_attr(struct inode *inode, loff_t pos, ssize_t written); @@ -1136,11 +1115,6 @@ int fuse_set_acl(struct mnt_idmap *, struct dentry *dentry, /* readdir.c */ int fuse_readdir(struct file *file, struct dir_context *ctx); -/** - * Return the number of bytes in an arguments list - */ -unsigned int fuse_len_args(unsigned int numargs, struct fuse_arg *args); - void fuse_free_conn(struct fuse_conn *fc); /* dax.c */ @@ -1208,8 +1182,6 @@ static inline struct fuse_backing *fuse_backing_lookup(struct fuse_conn *fc, void fuse_backing_files_init(struct fuse_conn *fc); void fuse_backing_files_free(struct fuse_conn *fc); -int fuse_backing_open(struct fuse_conn *fc, struct fuse_backing_map *map); -int fuse_backing_close(struct fuse_conn *fc, int backing_id); /* passthrough.c */ static inline struct fuse_backing *fuse_inode_backing(struct fuse_inode *fi) diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c index 79047734ab40..438d5211222c 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c @@ -1031,6 +1031,11 @@ struct fuse_conn *fuse_conn_get(struct fuse_conn *fc) } EXPORT_SYMBOL_GPL(fuse_conn_get); +dev_t fuse_conn_get_id(struct fuse_conn *fc) +{ + return fc->dev; +} + static struct inode *fuse_get_root_inode(struct super_block *sb, unsigned int mode) { struct fuse_attr attr; -- cgit v1.2.3 From 48649c0603bd355fb1d2c26ed4b6f635146278ea Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Thu, 2 Apr 2026 14:49:09 +0200 Subject: fuse: alloc pqueue before installing fch in fuse_dev Prior to this patchset, fuse_dev (containing fuse_pqueue) was allocated on mount. But now fuse_dev is allocated when opening /dev/fuse, even though the queues are not needed at that time. Delay allocation of the pqueue (4k worth of list_head) just before mounting or cloning a device. Various distributions (e.g. Debian/Fedora) configure /dev/fuse as world writable, so the pqueue allocation should be deferred to a privileged operation (mount) to prevent unprivileged userspace from consuming pinned kernel memory. [Li Wang: fix kernel NULL pointer dereference in fuse_uring_add_to_pq()] [Fix race in fuse_dev_release()] Signed-off-by: Miklos Szeredi --- fs/fuse/dev.c | 82 ++++++++++++++++++++++++++++++++++++++-------------- fs/fuse/dev_uring.c | 4 +-- fs/fuse/fuse_dev_i.h | 5 ++++ 3 files changed, 67 insertions(+), 24 deletions(-) diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index 2403cd445e72..fd0b86ea5691 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -329,6 +329,7 @@ void fuse_chan_release(struct fuse_chan *fch) void fuse_chan_free(struct fuse_chan *fch) { WARN_ON(!list_empty(&fch->devices)); + kfree(fch->pq_prealloc); kfree(fch); } EXPORT_SYMBOL_GPL(fuse_chan_free); @@ -355,15 +356,30 @@ struct fuse_chan *fuse_chan_new(void) } EXPORT_SYMBOL_GPL(fuse_chan_new); +struct list_head *fuse_pqueue_alloc(void) +{ + struct list_head *pq = kzalloc_objs(struct list_head, FUSE_PQ_HASH_SIZE); + + if (pq) { + for (int i = 0; i < FUSE_PQ_HASH_SIZE; i++) + INIT_LIST_HEAD(&pq[i]); + } + return pq; +} + struct fuse_chan *fuse_dev_chan_new(void) { - struct fuse_chan *fch = fuse_chan_new(); + struct fuse_chan *fch __free(kfree) = fuse_chan_new(); if (!fch) return NULL; + fch->pq_prealloc = fuse_pqueue_alloc(); + if (!fch->pq_prealloc) + return NULL; + fuse_iqueue_init(&fch->iq, &fuse_dev_fiq_ops, NULL); - return fch; + return no_free_ptr(fch); } EXPORT_SYMBOL_GPL(fuse_dev_chan_new); @@ -404,39 +420,42 @@ void fuse_chan_io_uring_enable(struct fuse_chan *fch) void fuse_pqueue_init(struct fuse_pqueue *fpq) { - unsigned int i; - spin_lock_init(&fpq->lock); - for (i = 0; i < FUSE_PQ_HASH_SIZE; i++) - INIT_LIST_HEAD(&fpq->processing[i]); INIT_LIST_HEAD(&fpq->io); fpq->connected = 1; + fpq->processing = NULL; } -struct fuse_dev *fuse_dev_alloc(void) +static struct fuse_dev *fuse_dev_alloc_no_pq(void) { struct fuse_dev *fud; - struct list_head *pq; fud = kzalloc_obj(struct fuse_dev); if (!fud) return NULL; refcount_set(&fud->ref, 1); - pq = kzalloc_objs(struct list_head, FUSE_PQ_HASH_SIZE); - if (!pq) { - kfree(fud); - return NULL; - } - - fud->pq.processing = pq; fuse_pqueue_init(&fud->pq); return fud; } + +struct fuse_dev *fuse_dev_alloc(void) +{ + struct fuse_dev *fud __free(kfree) = fuse_dev_alloc_no_pq(); + if (!fud) + return NULL; + + fud->pq.processing = fuse_pqueue_alloc(); + if (!fud->pq.processing) + return NULL; + + return no_free_ptr(fud); +} EXPORT_SYMBOL_GPL(fuse_dev_alloc); -void fuse_dev_install(struct fuse_dev *fud, struct fuse_chan *fch) +static void fuse_dev_install_with_pq(struct fuse_dev *fud, struct fuse_chan *fch, + struct list_head *pq) { struct fuse_chan *old_fch; @@ -454,20 +473,33 @@ void fuse_dev_install(struct fuse_dev *fud, struct fuse_chan *fch) * - it was set to disconneted */ fch->connected = 0; + kfree(pq); } else { + if (pq) { + WARN_ON(fud->pq.processing); + fud->pq.processing = pq; + } list_add_tail(&fud->entry, &fch->devices); fuse_conn_get(fch->conn); wake_up_all(&fuse_dev_waitq); } spin_unlock(&fch->lock); } + +void fuse_dev_install(struct fuse_dev *fud, struct fuse_chan *fch) +{ + struct list_head *pq = fch->pq_prealloc; + + fch->pq_prealloc = NULL; + fuse_dev_install_with_pq(fud, fch, pq); +} EXPORT_SYMBOL_GPL(fuse_dev_install); struct fuse_dev *fuse_dev_alloc_install(struct fuse_chan *fch) { struct fuse_dev *fud; - fud = fuse_dev_alloc(); + fud = fuse_dev_alloc_no_pq(); if (!fud) return NULL; @@ -1631,7 +1663,7 @@ out_end: static int fuse_dev_open(struct inode *inode, struct file *file) { - struct fuse_dev *fud = fuse_dev_alloc(); + struct fuse_dev *fud = fuse_dev_alloc_no_pq(); if (!fud) return -ENOMEM; @@ -2204,20 +2236,21 @@ int fuse_dev_release(struct inode *inode, struct file *file) unsigned int i; bool last; + /* Make sure fuse_dev_install_with_pq() has finished */ + spin_lock(&fch->lock); spin_lock(&fpq->lock); WARN_ON(!list_empty(&fpq->io)); for (i = 0; i < FUSE_PQ_HASH_SIZE; i++) list_splice_init(&fpq->processing[i], &to_end); spin_unlock(&fpq->lock); - fuse_dev_end_requests(&to_end); - - spin_lock(&fch->lock); list_del(&fud->entry); /* Are we the last open device? */ last = list_empty(&fch->devices); spin_unlock(&fch->lock); + fuse_dev_end_requests(&to_end); + if (last) { WARN_ON(fch->iq.fasync != NULL); fuse_chan_abort(fch, false); @@ -2244,6 +2277,7 @@ static long fuse_dev_ioctl_clone(struct file *file, __u32 __user *argp) { int oldfd; struct fuse_dev *fud, *new_fud; + struct list_head *pq; if (get_user(oldfd, argp)) return -EFAULT; @@ -2267,7 +2301,11 @@ static long fuse_dev_ioctl_clone(struct file *file, __u32 __user *argp) if (fuse_dev_chan_get(new_fud)) return -EINVAL; - fuse_dev_install(new_fud, fud->chan); + pq = fuse_pqueue_alloc(); + if (!pq) + return -ENOMEM; + + fuse_dev_install_with_pq(new_fud, fud->chan, pq); return 0; } diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c index 99138e3ec83f..2901c7a5ff05 100644 --- a/fs/fuse/dev_uring.c +++ b/fs/fuse/dev_uring.c @@ -281,7 +281,7 @@ static struct fuse_ring_queue *fuse_uring_create_queue(struct fuse_ring *ring, queue = kzalloc_obj(*queue, GFP_KERNEL_ACCOUNT); if (!queue) return NULL; - pq = kzalloc_objs(struct list_head, FUSE_PQ_HASH_SIZE); + pq = fuse_pqueue_alloc(); if (!pq) { kfree(queue); return NULL; @@ -299,8 +299,8 @@ static struct fuse_ring_queue *fuse_uring_create_queue(struct fuse_ring *ring, INIT_LIST_HEAD(&queue->fuse_req_bg_queue); INIT_LIST_HEAD(&queue->ent_released); - queue->fpq.processing = pq; fuse_pqueue_init(&queue->fpq); + queue->fpq.processing = pq; spin_lock(&fch->lock); if (ring->queues[qid]) { diff --git a/fs/fuse/fuse_dev_i.h b/fs/fuse/fuse_dev_i.h index dcfafac786fc..430d292d31fb 100644 --- a/fs/fuse/fuse_dev_i.h +++ b/fs/fuse/fuse_dev_i.h @@ -247,6 +247,9 @@ struct fuse_chan { /* Maximum number of pages that can be used in a single request */ unsigned int max_pages; + /* Before being installed into fud, contains the preallocated pq array*/ + struct list_head *pq_prealloc; + /** Connection aborted via sysfs, respond with ECONNABORTED on device I/O */ bool abort_with_err; @@ -396,6 +399,8 @@ struct fuse_dev *fuse_dev_alloc(void); int fuse_dev_release(struct inode *inode, struct file *file); +struct list_head *fuse_pqueue_alloc(void); + /** * Initialize the fuse processing queue */ -- cgit v1.2.3 From a63607723108f8c4003c040ca7fd704c6340814b Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Thu, 2 Apr 2026 22:57:49 +0200 Subject: fuse: simplify fuse_dev_ioctl_clone() Don't need to check if the new device file is already initialized, since fuse_dev_install_with_pq() will do that anyway. Make fuse_dev_install_with_pq() return a boolean value indicating success so that fuse_dev_ioctl_clone() can return an error in case of failure. Move aborting the connection (setting fc->connected to zero) to fuse_dev_install(), because it is not needed when the clone ioctl fails. Signed-off-by: Miklos Szeredi --- fs/fuse/dev.c | 39 +++++++++++++++++++++------------------ 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index fd0b86ea5691..9feecbd2532a 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -454,12 +454,15 @@ struct fuse_dev *fuse_dev_alloc(void) } EXPORT_SYMBOL_GPL(fuse_dev_alloc); -static void fuse_dev_install_with_pq(struct fuse_dev *fud, struct fuse_chan *fch, +/* + * Installs @fch into @fud, return true on success. "Consumes" @pq in either case. + */ +static bool fuse_dev_install_with_pq(struct fuse_dev *fud, struct fuse_chan *fch, struct list_head *pq) { struct fuse_chan *old_fch; - spin_lock(&fch->lock); + guard(spinlock)(&fch->lock); /* * Pairs with: * - xchg() in fuse_dev_release() @@ -472,18 +475,17 @@ static void fuse_dev_install_with_pq(struct fuse_dev *fud, struct fuse_chan *fch * - it was already set to a different fc * - it was set to disconneted */ - fch->connected = 0; kfree(pq); - } else { - if (pq) { - WARN_ON(fud->pq.processing); - fud->pq.processing = pq; - } - list_add_tail(&fud->entry, &fch->devices); - fuse_conn_get(fch->conn); - wake_up_all(&fuse_dev_waitq); + return false; } - spin_unlock(&fch->lock); + if (pq) { + WARN_ON(fud->pq.processing); + fud->pq.processing = pq; + } + list_add_tail(&fud->entry, &fch->devices); + fuse_conn_get(fch->conn); + wake_up_all(&fuse_dev_waitq); + return true; } void fuse_dev_install(struct fuse_dev *fud, struct fuse_chan *fch) @@ -491,7 +493,10 @@ void fuse_dev_install(struct fuse_dev *fud, struct fuse_chan *fch) struct list_head *pq = fch->pq_prealloc; fch->pq_prealloc = NULL; - fuse_dev_install_with_pq(fud, fch, pq); + if (!fuse_dev_install_with_pq(fud, fch, pq)) { + /* Channel is not usable without a dev */ + fuse_chan_abort(fch, false); + } } EXPORT_SYMBOL_GPL(fuse_dev_install); @@ -2297,15 +2302,13 @@ static long fuse_dev_ioctl_clone(struct file *file, __u32 __user *argp) if (IS_ERR(fud)) return PTR_ERR(fud); - new_fud = fuse_file_to_fud(file); - if (fuse_dev_chan_get(new_fud)) - return -EINVAL; - pq = fuse_pqueue_alloc(); if (!pq) return -ENOMEM; - fuse_dev_install_with_pq(new_fud, fud->chan, pq); + new_fud = fuse_file_to_fud(file); + if (!fuse_dev_install_with_pq(new_fud, fud->chan, pq)) + return -EINVAL; return 0; } -- cgit v1.2.3 From 288241c802990584afb7dd3d7bbf3c28d58e9514 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Mon, 6 Apr 2026 17:50:38 -0700 Subject: fuse-uring: drop kernel-doc notation for a comment Use regular C comment syntax for a non-kernel-doc comment to avoid a kernel-doc warning: Warning: fs/fuse/dev_uring_i.h:104 This comment starts with '/**', but isn't a kernel-doc comment. * Describes if uring is for communication and holds alls the data needed Binary build output is the same before and after this change. Signed-off-by: Randy Dunlap Signed-off-by: Miklos Szeredi --- fs/fuse/dev_uring_i.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/fuse/dev_uring_i.h b/fs/fuse/dev_uring_i.h index 1875f73168ef..55f8d04e4b0b 100644 --- a/fs/fuse/dev_uring_i.h +++ b/fs/fuse/dev_uring_i.h @@ -101,7 +101,7 @@ struct fuse_ring_queue { bool stopped; }; -/** +/* * Describes if uring is for communication and holds alls the data needed * for uring communication */ -- cgit v1.2.3 From eafb65976d0f00e2861a8bfe3929d72670ccd053 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Mon, 6 Apr 2026 17:50:39 -0700 Subject: fuse: fuse_dev_i.h: clean up kernel-doc warnings Change some "/**" to "/*" since they are not kernel-doc comments: Warning: fs/fuse/fuse_dev_i.h:25 This comment starts with '/**', but isn't a kernel-doc comment. Refer to Documentation/doc-guide/kernel-doc.rst * Request flags Warning: fs/fuse/fuse_dev_i.h:58 This comment starts with '/**', but isn't a kernel-doc comment. Refer to Documentation/doc-guide/kernel-doc.rst * A request to the client Warning: fs/fuse/fuse_dev_i.h:117 This comment starts with '/**', but isn't a kernel-doc comment. Refer to Documentation/doc-guide/kernel-doc.rst * Input queue callbacks Warning: fs/fuse/fuse_dev_i.h:289 This comment starts with '/**', but isn't a kernel-doc comment. Refer to Documentation/doc-guide/kernel-doc.rst * Fuse device instance and more like this. Convert enum fuse_req_flag to kernel-doc format. Convert struct fuse_req, struct fuse_iqueue_ops, and struct fuse_dev to kernel-doc format. These warnings remain: Warning: fs/fuse/fuse_dev_i.h:115 struct member 'ring_entry' not described in 'fuse_req' Warning: fs/fuse/fuse_dev_i.h:115 struct member 'ring_queue' not described in 'fuse_req' Binary build output is the same before and after these changes. Signed-off-by: Randy Dunlap Signed-off-by: Miklos Szeredi --- fs/fuse/fuse_dev_i.h | 93 ++++++++++++++++++++++++++++------------------------ 1 file changed, 50 insertions(+), 43 deletions(-) diff --git a/fs/fuse/fuse_dev_i.h b/fs/fuse/fuse_dev_i.h index 430d292d31fb..668c8391d61c 100644 --- a/fs/fuse/fuse_dev_i.h +++ b/fs/fuse/fuse_dev_i.h @@ -23,21 +23,21 @@ struct fuse_pqueue; struct fuse_iqueue; /** - * Request flags + * enum fuse_req_flag - Request flags * - * FR_ISREPLY: set if the request has reply - * FR_FORCE: force sending of the request even if interrupted - * FR_BACKGROUND: request is sent in the background - * FR_WAITING: request is counted as "waiting" - * FR_ABORTED: the request was aborted - * FR_INTERRUPTED: the request has been interrupted - * FR_LOCKED: data is being copied to/from the request - * FR_PENDING: request is not yet in userspace - * FR_SENT: request is in userspace, waiting for an answer - * FR_FINISHED: request is finished - * FR_PRIVATE: request is on private list - * FR_ASYNC: request is asynchronous - * FR_URING: request is handled through fuse-io-uring + * @FR_ISREPLY: set if the request has reply + * @FR_FORCE: force sending of the request even if interrupted + * @FR_BACKGROUND: request is sent in the background + * @FR_WAITING: request is counted as "waiting" + * @FR_ABORTED: the request was aborted + * @FR_INTERRUPTED: the request has been interrupted + * @FR_LOCKED: data is being copied to/from the request + * @FR_PENDING: request is not yet in userspace + * @FR_SENT: request is in userspace, waiting for an answer + * @FR_FINISHED: request is finished + * @FR_PRIVATE: request is on private list + * @FR_ASYNC: request is asynchronous + * @FR_URING: request is handled through fuse-io-uring */ enum fuse_req_flag { FR_ISREPLY, @@ -56,55 +56,62 @@ enum fuse_req_flag { }; /** - * A request to the client + * struct fuse_req - A request to the client * * .waitq.lock protects the following fields: * - FR_ABORTED * - FR_LOCKED (may also be modified under fpq->lock, tested under both) */ struct fuse_req { - /** This can be on either pending processing or io lists in - fuse_conn */ + /** + * @list: This can be on either pending processing or io lists in + * fuse_conn + */ struct list_head list; - /** Entry on the interrupts list */ + /** @intr_entry: Entry on the interrupts list */ struct list_head intr_entry; - /* Input/output arguments */ + /** @args: Input/output arguments */ struct fuse_args *args; - /** refcount */ + /** @count: refcount */ refcount_t count; - /* Request flags, updated with test/set/clear_bit() */ + /** @flags: Request flags, updated with test/set/clear_bit() */ unsigned long flags; - /* The request input header */ + /** @in: The request input header */ struct { + /** @in.h: The request input header */ struct fuse_in_header h; } in; - /* The request output header */ + /** @out: The request output header */ struct { + /** @out.h: The request output header */ struct fuse_out_header h; } out; - /** Used to wake up the task waiting for completion of request*/ + /** @waitq: Used to wake up the task waiting for completion of request */ wait_queue_head_t waitq; #if IS_ENABLED(CONFIG_VIRTIO_FS) - /** virtio-fs's physically contiguous buffer for in and out args */ + /** + * @argbuf: virtio-fs's physically contiguous buffer for in and out + * args + */ void *argbuf; #endif - /** fuse_chan this request belongs to */ + /** @chan: fuse_chan this request belongs to */ struct fuse_chan *chan; #ifdef CONFIG_FUSE_IO_URING void *ring_entry; void *ring_queue; #endif - /** When (in jiffies) the request was created */ + /** @create_time: When (in jiffies) the request was created */ unsigned long create_time; }; @@ -115,7 +122,7 @@ struct fuse_forget_link { }; /** - * Input queue callbacks + * struct fuse_iqueue_ops - Input queue callbacks * * Input queue signalling is device-specific. For example, the /dev/fuse file * uses fiq->waitq and fasync to wake processes that are waiting on queue @@ -124,22 +131,22 @@ struct fuse_forget_link { */ struct fuse_iqueue_ops { /** - * Send one forget + * @send_forget: Send one forget */ void (*send_forget)(struct fuse_iqueue *fiq, struct fuse_forget_link *link); /** - * Send interrupt for request + * @send_interrupt: Send interrupt for request */ void (*send_interrupt)(struct fuse_iqueue *fiq, struct fuse_req *req); /** - * Send one request + * @send_req: Send one request */ void (*send_req)(struct fuse_iqueue *fiq, struct fuse_req *req); /** - * Clean up when fuse_iqueue is destroyed + * @release: Clean up when fuse_iqueue is destroyed */ void (*release)(struct fuse_iqueue *fiq); }; @@ -286,22 +293,22 @@ struct fuse_pqueue { }; /** - * Fuse device instance + * struct fuse_dev - Fuse device instance */ struct fuse_dev { - /** Reference count of this object */ + /** @ref: Reference count of this object */ refcount_t ref; - /** Issue FUSE_INIT synchronously */ + /** @sync_init: Issue FUSE_INIT synchronously */ bool sync_init; - /** Fuse channel for this device */ + /** @chan: Fuse channel for this device */ struct fuse_chan *chan; - /** Processing queue */ + /** @pq: Processing queue */ struct fuse_pqueue pq; - /** list entry on fch->devices */ + /** @entry: list entry on fch->devices */ struct list_head entry; }; @@ -367,7 +374,7 @@ void fuse_request_bg_finish(struct fuse_chan *fch, struct fuse_req *req); void fuse_copy_init(struct fuse_copy_state *cs, bool write, struct iov_iter *iter); -/** +/* * Return the number of bytes in an arguments list */ unsigned int fuse_len_args(unsigned int numargs, struct fuse_arg *args); @@ -384,12 +391,12 @@ bool fuse_remove_pending_req(struct fuse_req *req, spinlock_t *lock); bool fuse_request_expired(struct fuse_chan *fch, struct list_head *list); -/** +/* * Assign a unique id to a fuse request */ void fuse_request_assign_unique(struct fuse_iqueue *fiq, struct fuse_req *req); -/** +/* * Get the next unique ID for a request */ u64 fuse_get_unique(struct fuse_iqueue *fiq); @@ -401,12 +408,12 @@ int fuse_dev_release(struct inode *inode, struct file *file); struct list_head *fuse_pqueue_alloc(void); -/** +/* * Initialize the fuse processing queue */ void fuse_pqueue_init(struct fuse_pqueue *fpq); -/** +/* * End a finished request */ void fuse_request_end(struct fuse_req *req); -- cgit v1.2.3 From 567820f02a9b34df65130c10ffa617c36e63a04d Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Mon, 6 Apr 2026 17:50:40 -0700 Subject: fuse: fuse_i.h: clean up kernel-doc comments Convert many comments to kernel-doc format to eliminate around 20 kernel-doc warnings like these: Warning: fs/fuse/fuse_i.h:374 This comment starts with '/**', but isn't a kernel-doc comment. Refer to Documentation/doc-guide/kernel-doc.rst * A Fuse connection. Warning: fs/fuse/fuse_i.h:817 This comment starts with '/**', but isn't a kernel-doc comment. Refer to Documentation/doc-guide/kernel-doc.rst * Get a filled in inode Warning: fs/fuse/fuse_i.h:859 This comment starts with '/**', but isn't a kernel-doc comment. Refer to Documentation/doc-guide/kernel-doc.rst * Send RELEASE or RELEASEDIR request and more like this. Also add struct member and function parameter descriptions to avoid these warnings: Warning: fs/fuse/fuse_i.h:1071 struct member 'epoch_work' not described in 'fuse_conn' Warning: fs/fuse/fuse_i.h:1071 struct member 'rcu' not described in 'fuse_conn' Warning: fs/fuse/fuse_i.h:1423 function parameter 'fc' not described in 'fuse_reverse_inval_inode' Warning: fs/fuse/fuse_i.h:1423 function parameter 'nodeid' not described in 'fuse_reverse_inval_inode' Warning: fs/fuse/fuse_i.h:1423 function parameter 'offset' not described in 'fuse_reverse_inval_inode' Warning: fs/fuse/fuse_i.h:1423 function parameter 'len' not described in 'fuse_reverse_inval_inode' Warning: fs/fuse/fuse_i.h:1436 function parameter 'fc' not described in 'fuse_reverse_inval_entry' Warning: fs/fuse/fuse_i.h:1436 function parameter 'parent_nodeid' not described in 'fuse_reverse_inval_entry' Warning: fs/fuse/fuse_i.h:1436 function parameter 'child_nodeid' not described in 'fuse_reverse_inval_entry' Warning: fs/fuse/fuse_i.h:1436 function parameter 'name' not described in 'fuse_reverse_inval_entry' Warning: fs/fuse/fuse_i.h:1436 function parameter 'flags' not described in 'fuse_reverse_inval_entry' Convert struct fuse_file, struct fuse_submount_lookup, struct fuse_inode, and struct fuse_conn to kernel-doc. Convert these to plain comments: Warning: fs/fuse/fuse_i.h:1423 expecting prototype for File(). Prototype was for fuse_reverse_inval_inode() instead Warning: fs/fuse/fuse_i.h:1436 expecting prototype for File(). Prototype was for fuse_reverse_inval_entry() instead Change some "/**" to "/*" since they are not kernel-doc comments. The changes above fix most kernel-doc warnings in this file but these warnings are not fixed and still remain: Warning: fs/fuse/fuse_i.h:1428 No description found for return value of 'fuse_fill_super_common' Warning: fs/fuse/fuse_i.h:1455 No description found for return value of 'fuse_ctl_add_conn' Binary build output is the same before and after these changes. Signed-off-by: Randy Dunlap Signed-off-by: Miklos Szeredi --- fs/fuse/fuse_i.h | 455 +++++++++++++++++++++++++++++++++---------------------- 1 file changed, 272 insertions(+), 183 deletions(-) diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index 8c58a030d296..93087fc04975 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -71,137 +71,166 @@ extern unsigned int max_user_congthresh; struct fuse_forget_link; -/* Submount lookup tracking */ +/** + * struct fuse_submount_lookup - Submount lookup tracking + */ struct fuse_submount_lookup { - /** Refcount */ + /** @count: Refcount */ refcount_t count; - /** Unique ID, which identifies the inode between userspace - * and kernel */ + /** + * @nodeid: Unique ID, which identifies the inode between userspace + * and kernel + */ u64 nodeid; - /** The request used for sending the FORGET message */ + /** @forget: The request used for sending the FORGET message */ struct fuse_forget_link *forget; }; -/** Container for data related to mapping to backing file */ +/* Container for data related to mapping to backing file */ struct fuse_backing { struct file *file; struct cred *cred; - /** refcount */ + /* refcount */ refcount_t count; struct rcu_head rcu; }; -/** FUSE inode */ +/** + * struct fuse_inode - FUSE inode + */ struct fuse_inode { - /** Inode data */ + /** @inode: Inode data */ struct inode inode; - /** Unique ID, which identifies the inode between userspace - * and kernel */ + /** + * @nodeid: Unique ID, which identifies the inode between userspace + * and kernel + */ u64 nodeid; - /** Number of lookups on this inode */ + /** @nlookup: Number of lookups on this inode */ u64 nlookup; - /** The request used for sending the FORGET message */ + /** @forget: The request used for sending the FORGET message */ struct fuse_forget_link *forget; - /** Time in jiffies until the file attributes are valid */ + /** @i_time: Time in jiffies until the file attributes are valid */ u64 i_time; - /* Which attributes are invalid */ + /** @inval_mask: Which attributes are invalid */ u32 inval_mask; - /** The sticky bit in inode->i_mode may have been removed, so - preserve the original mode */ + /** + * @orig_i_mode: The sticky bit in inode->i_mode may have been removed, + * so preserve the original mode + */ umode_t orig_i_mode; - /* Cache birthtime */ + /** @i_btime: Cache birthtime */ struct timespec64 i_btime; - /** 64 bit inode number */ + /** @orig_ino: 64-bit inode number */ u64 orig_ino; - /** Version of last attribute change */ + /** @attr_version: Version of last attribute change */ u64 attr_version; union { /* read/write io cache (regular file only) */ struct { - /* Files usable in writepage. Protected by fi->lock */ + /** + * @write_files: Files usable in writepage. + * Protected by fi->lock + */ struct list_head write_files; - /* Writepages pending on truncate or fsync */ + /** + * @queued_writes: Writepages pending on truncate or + * fsync + */ struct list_head queued_writes; - /* Number of sent writes, a negative bias - * (FUSE_NOWRITE) means more writes are blocked */ + /** + * @writectr: Number of sent writes, a negative bias + * (FUSE_NOWRITE) means more writes are blocked + */ int writectr; - /** Number of files/maps using page cache */ + /** @iocachectr: Number of files/maps using page cache */ int iocachectr; - /* Waitq for writepage completion */ + /** @page_waitq: Waitq for writepage completion */ wait_queue_head_t page_waitq; - /* waitq for direct-io completion */ + /** @direct_io_waitq: waitq for direct-io completion */ wait_queue_head_t direct_io_waitq; }; - /* readdir cache (directory only) */ + /** @rdc: readdir cache (directory only) */ struct { - /* true if fully cached */ + /** @cached: true if fully cached */ bool cached; - /* size of cache */ + /** @size: size of cache */ loff_t size; - /* position at end of cache (position of next entry) */ + /** + * @pos: position at end of cache (position of next + * entry) + */ loff_t pos; - /* version of the cache */ + /** @version: version of the cache */ u64 version; - /* modification time of directory when cache was - * started */ + /** + * @mtime: modification time of directory when cache was + * started + */ struct timespec64 mtime; - /* iversion of directory when cache was started */ + /** + * @iversion: iversion of directory when cache was + * started + */ u64 iversion; - /* protects above fields */ + /** @lock: protects above fields */ spinlock_t lock; } rdc; }; - /** Miscellaneous bits describing inode state */ + /** @state: Miscellaneous bits describing inode state */ unsigned long state; - /** Lock for serializing lookup and readdir for back compatibility*/ + /** + * @mutex: Lock for serializing lookup and readdir for back + * compatibility + */ struct mutex mutex; - /** Lock to protect write related fields */ + /** @lock: Lock to protect write-related fields */ spinlock_t lock; #ifdef CONFIG_FUSE_DAX - /* - * Dax specific inode data + /** + * @dax: Dax specific inode data */ struct fuse_inode_dax *dax; #endif - /** Submount specific lookup tracking */ + /** @submount_lookup: Submount specific lookup tracking */ struct fuse_submount_lookup *submount_lookup; #ifdef CONFIG_FUSE_PASSTHROUGH - /** Reference to backing file in passthrough mode */ + /** @fb: Reference to backing file in passthrough mode */ struct fuse_backing *fb; #endif - /* - * The underlying inode->i_blkbits value will not be modified, - * so preserve the blocksize specified by the server. + /** + * @cached_i_blkbits: The underlying inode->i_blkbits value will not + * be modified, so preserve the blocksize specified by the server. */ u8 cached_i_blkbits; }; @@ -231,61 +260,64 @@ struct fuse_conn; struct fuse_mount; union fuse_file_args; -/** FUSE specific file data */ +/** + * struct fuse_file - FUSE-specific file data + */ struct fuse_file { - /** Fuse connection for this file */ + /** @fm: Fuse connection for this file */ struct fuse_mount *fm; - /* Argument space reserved for open/release */ + /** @args: Argument space reserved for open/release */ union fuse_file_args *args; - /** Kernel file handle guaranteed to be unique */ + /** @kh: Kernel file handle guaranteed to be unique */ u64 kh; - /** File handle used by userspace */ + /** @fh: File handle used by userspace */ u64 fh; - /** Node id of this file */ + /** @nodeid: Node id of this file */ u64 nodeid; - /** Refcount */ + /** @count: Refcount */ refcount_t count; - /** FOPEN_* flags returned by open */ + /** @open_flags: FOPEN_* flags returned by open */ u32 open_flags; - /** Entry on inode's write_files list */ + /** @write_entry: Entry on inode's write_files list */ struct list_head write_entry; - /* Readdir related */ + /** @readdir: Readdir-related */ struct { - /* Dir stream position */ + /** @pos: Dir stream position */ loff_t pos; - /* Offset in cache */ + /** @cache_off: Offset in cache */ loff_t cache_off; - /* Version of cache we are reading */ + /** @version: Version of cache we are reading */ u64 version; } readdir; - /** RB node to be linked on fuse_conn->polled_files */ + /** @polled_node: RB node to be linked on fuse_conn->polled_files */ struct rb_node polled_node; - /** Wait queue head for poll */ + /** @poll_wait: Wait queue head for poll */ wait_queue_head_t poll_wait; - /** Does file hold a fi->iocachectr refcount? */ + /** @iomode: Does file hold a fi->iocachectr refcount? */ enum { IOM_NONE, IOM_CACHED, IOM_UNCACHED } iomode; #ifdef CONFIG_FUSE_PASSTHROUGH - /** Reference to backing file in passthrough mode */ + /** @passthrough: Reference to backing file in passthrough mode */ struct file *passthrough; + /** @cred: passthrough file credentials */ const struct cred *cred; #endif - /** Has flock been performed on this file? */ + /** @flock: Has flock been performed on this file? */ bool flock:1; }; @@ -372,102 +404,135 @@ struct fuse_sync_bucket { }; /** - * A Fuse connection. + * struct fuse_conn - A Fuse connection. * * This structure is created, when the root filesystem is mounted, and * is destroyed, when the client device is closed and the last * fuse_mount is destroyed. */ struct fuse_conn { - /** Lock protecting: - - polled_files - - backing_files_map - - curr_bucket + /** + * @lock: Lock protecting: + * - polled_files + * - backing_files_map + * - curr_bucket */ spinlock_t lock; - /** Refcount */ + /** @count: Refcount */ refcount_t count; - /** Current epoch for up-to-date dentries */ + /** @epoch: Current epoch for up-to-date dentries */ atomic_t epoch; + /** @epoch_work: Used to invalidate dentries from old epochs */ struct work_struct epoch_work; + /** @rcu: Used to delay freeing fuse_conn, making it safe */ struct rcu_head rcu; - /** The user id for this mount */ + /** @user_id: The user id for this mount */ kuid_t user_id; - /** The group id for this mount */ + /** @group_id: The group id for this mount */ kgid_t group_id; - /** The pid namespace for this mount */ + /** @pid_ns: The pid namespace for this mount */ struct pid_namespace *pid_ns; - /** The user namespace for this mount */ + /** @user_ns: The user namespace for this mount */ struct user_namespace *user_ns; - /** Maximum read size */ + /** @max_read: Maximum read size */ unsigned max_read; - /** Maximum write size */ + /** @max_write: Maximum write size */ unsigned max_write; - /** Maximum number of pages that can be used in a single request */ + /** + * @max_pages: Maximum number of pages that can be used in a + * single request + */ unsigned int max_pages; - /** Constrain ->max_pages to this value during feature negotiation */ + /** + * @max_pages_limit: Constrain ->max_pages to this value during + * feature negotiation + */ unsigned int max_pages_limit; - /* transport layer object */ + /** @chan: transport layer object */ struct fuse_chan *chan; - /** The next unique kernel file handle */ + /** @khctr: The next unique kernel file handle */ atomic64_t khctr; - /** rbtree of fuse_files waiting for poll events indexed by ph */ + /** + * @polled_files: rbtree of fuse_files waiting for poll events + * indexed by ph + */ struct rb_root polled_files; - /** Number of background requests at which congestion starts */ + /** + * @congestion_threshold: Number of background requests at which + * congestion starts + */ unsigned congestion_threshold; - /** Connection failed (version mismatch). Cannot race with - setting other bitfields since it is only set once in INIT - reply, before any other request, and never cleared */ + /** + * @conn_error: Connection failed (version mismatch). Cannot race with + * setting other bitfields since it is only set once in INIT + * reply, before any other request, and never cleared + */ unsigned conn_error:1; - /** Connection successful. Only set in INIT */ + /** @conn_init: Connection successful. Only set in INIT */ unsigned conn_init:1; - /** Do readahead asynchronously? Only set in INIT */ + /** @async_read: Do readahead asynchronously? Only set in INIT */ unsigned async_read:1; - /** Return an unique read error after abort. Only set in INIT */ + /** + * @abort_err: Return an unique read error after abort. + * Only set in INIT + */ unsigned abort_err:1; - /** Do not send separate SETATTR request before open(O_TRUNC) */ + /** + * @atomic_o_trunc: Do not send separate SETATTR request before + * open(O_TRUNC) + */ unsigned atomic_o_trunc:1; - /** Filesystem supports NFS exporting. Only set in INIT */ + /** + * @export_support: Filesystem supports NFS exporting. + * Only set in INIT + */ unsigned export_support:1; - /** write-back cache policy (default is write-through) */ + /** @writeback_cache: write-back cache policy (default is write-through) */ unsigned writeback_cache:1; - /** allow parallel lookups and readdir (default is serialized) */ + /** + * @parallel_dirops: allow parallel lookups and readdir (default is + * serialized) + */ unsigned parallel_dirops:1; - /** handle fs handles killing suid/sgid/cap on write/chown/trunc */ + /** + * @handle_killpriv: handle fs handles killing suid/sgid/cap on + * write/chown/trunc + */ unsigned handle_killpriv:1; - /** cache READLINK responses in page cache */ + /** @cache_symlinks: cache READLINK responses in page cache */ unsigned cache_symlinks:1; - /* show legacy mount options */ + /** @legacy_opts_show: show legacy mount options */ unsigned int legacy_opts_show:1; - /* + /** + * @handle_killpriv_v2: * fs kills suid/sgid/cap on write/chown/trunc. suid is killed on * write/trunc only if caller did not have CAP_FSETID. sgid is killed * on write/truncate only if caller did not have CAP_FSETID as well as @@ -480,196 +545,217 @@ struct fuse_conn { * and hence races in setting them will not cause malfunction */ - /** Is open/release not implemented by fs? */ + /** @no_open: Is open/release not implemented by fs? */ unsigned no_open:1; - /** Is opendir/releasedir not implemented by fs? */ + /** @no_opendir: Is opendir/releasedir not implemented by fs? */ unsigned no_opendir:1; - /** Is fsync not implemented by fs? */ + /** @no_fsync: Is fsync not implemented by fs? */ unsigned no_fsync:1; - /** Is fsyncdir not implemented by fs? */ + /** @no_fsyncdir: Is fsyncdir not implemented by fs? */ unsigned no_fsyncdir:1; - /** Is flush not implemented by fs? */ + /** @no_flush: Is flush not implemented by fs? */ unsigned no_flush:1; - /** Is setxattr not implemented by fs? */ + /** @no_setxattr: Is setxattr not implemented by fs? */ unsigned no_setxattr:1; - /** Does file server support extended setxattr */ + /** @setxattr_ext: Does file server support extended setxattr */ unsigned setxattr_ext:1; - /** Is getxattr not implemented by fs? */ + /** @no_getxattr: Is getxattr not implemented by fs? */ unsigned no_getxattr:1; - /** Is listxattr not implemented by fs? */ + /** @no_listxattr: Is listxattr not implemented by fs? */ unsigned no_listxattr:1; - /** Is removexattr not implemented by fs? */ + /** @no_removexattr: Is removexattr not implemented by fs? */ unsigned no_removexattr:1; - /** Are posix file locking primitives not implemented by fs? */ + /** @no_lock: Are posix file locking primitives not implemented by fs? */ unsigned no_lock:1; - /** Is access not implemented by fs? */ + /** @no_access: Is access not implemented by fs? */ unsigned no_access:1; - /** Is create not implemented by fs? */ + /** @no_create: Is create not implemented by fs? */ unsigned no_create:1; - /** Is bmap not implemented by fs? */ + /** @no_bmap: Is bmap not implemented by fs? */ unsigned no_bmap:1; - /** Is poll not implemented by fs? */ + /** @no_poll: Is poll not implemented by fs? */ unsigned no_poll:1; - /** Do multi-page cached writes */ + /** @big_writes: Do multi-page cached writes */ unsigned big_writes:1; - /** Don't apply umask to creation modes */ + /** @dont_mask: Don't apply umask to creation modes */ unsigned dont_mask:1; - /** Are BSD file locking primitives not implemented by fs? */ + /** @no_flock: Are BSD file locking primitives not implemented by fs? */ unsigned no_flock:1; - /** Is fallocate not implemented by fs? */ + /** @no_fallocate: Is fallocate not implemented by fs? */ unsigned no_fallocate:1; - /** Is rename with flags implemented by fs? */ + /** @no_rename2: Is rename with flags implemented by fs? */ unsigned no_rename2:1; - /** Use enhanced/automatic page cache invalidation. */ + /** @auto_inval_data: Use enhanced/automatic page cache invalidation. */ unsigned auto_inval_data:1; - /** Filesystem is fully responsible for page cache invalidation. */ + /** + * @explicit_inval_data: Filesystem is fully responsible for page cache + * invalidation. + */ unsigned explicit_inval_data:1; - /** Does the filesystem support readdirplus? */ + /** @do_readdirplus: Does the filesystem support readdirplus? */ unsigned do_readdirplus:1; - /** Does the filesystem want adaptive readdirplus? */ + /** @readdirplus_auto: Does the filesystem want adaptive readdirplus? */ unsigned readdirplus_auto:1; - /** Does the filesystem support asynchronous direct-IO submission? */ + /** + * @async_dio: Does the filesystem support asynchronous direct-IO + * submission? + */ unsigned async_dio:1; - /** Is lseek not implemented by fs? */ + /** @no_lseek: Is lseek not implemented by fs? */ unsigned no_lseek:1; - /** Does the filesystem support posix acls? */ + /** @posix_acl: Does the filesystem support posix acls? */ unsigned posix_acl:1; - /** Check permissions based on the file mode or not? */ + /** + * @default_permissions: Check permissions based on the file mode + * or not? + */ unsigned default_permissions:1; - /** Allow other than the mounter user to access the filesystem ? */ + /** + * @allow_other: Allow other than the mounter user to access the + * filesystem ? + */ unsigned allow_other:1; - /** Does the filesystem support copy_file_range? */ + /** @no_copy_file_range: Does the filesystem support copy_file_range? */ unsigned no_copy_file_range:1; - /** Does the filesystem support copy_file_range_64? */ + /** + * @no_copy_file_range_64: Does the filesystem support + * copy_file_range_64? + */ unsigned no_copy_file_range_64:1; - /* Send DESTROY request */ + /** @destroy: Send DESTROY request */ unsigned int destroy:1; - /* Delete dentries that have gone stale */ + /** @delete_stale: Delete dentries that have gone stale */ unsigned int delete_stale:1; - /** Do not create entry in fusectl fs */ + /** @no_control: Do not create entry in fusectl fs */ unsigned int no_control:1; - /** Do not allow MNT_FORCE umount */ + /** @no_force_umount: Do not allow MNT_FORCE umount */ unsigned int no_force_umount:1; - /* Auto-mount submounts announced by the server */ + /** @auto_submounts: Auto-mount submounts announced by the server */ unsigned int auto_submounts:1; - /* Propagate syncfs() to server */ + /** @sync_fs: Propagate syncfs() to server */ unsigned int sync_fs:1; - /* Initialize security xattrs when creating a new inode */ + /** @init_security: Initialize security xattrs when creating a new inode */ unsigned int init_security:1; - /* Add supplementary group info when creating a new inode */ + /** + * @create_supp_group: Add supplementary group info when creating + * a new inode + */ unsigned int create_supp_group:1; - /* Does the filesystem support per inode DAX? */ + /** @inode_dax: Does the filesystem support per inode DAX? */ unsigned int inode_dax:1; - /* Is tmpfile not implemented by fs? */ + /** @no_tmpfile: Is tmpfile not implemented by fs? */ unsigned int no_tmpfile:1; - /* Relax restrictions to allow shared mmap in FOPEN_DIRECT_IO mode */ + /** + * @direct_io_allow_mmap: Relax restrictions to allow shared mmap + * in FOPEN_DIRECT_IO mode + */ unsigned int direct_io_allow_mmap:1; - /* Is statx not implemented by fs? */ + /** @no_statx: Is statx not implemented by fs? */ unsigned int no_statx:1; - /** Passthrough support for read/write IO */ + /** @passthrough: Passthrough support for read/write IO */ unsigned int passthrough:1; - /* Use pages instead of pointer for kernel I/O */ + /** @use_pages_for_kvec_io: Use pages instead of pointer for kernel I/O */ unsigned int use_pages_for_kvec_io:1; - /* Is link not implemented by fs? */ + /** @no_link: Is link not implemented by fs? */ unsigned int no_link:1; - /* Is synchronous FUSE_INIT allowed? */ + /** @sync_init: Is synchronous FUSE_INIT allowed? */ unsigned int sync_init:1; - /** Maximum stack depth for passthrough backing files */ + /** @max_stack_depth: Maximum stack depth for passthrough backing files */ int max_stack_depth; - /** Negotiated minor version */ + /** @minor: Negotiated minor version */ unsigned minor; - /** Entry on the fuse_conn_list */ + /** @entry: Entry on the fuse_conn_list */ struct list_head entry; - /** Device ID from the root super block */ + /** @dev: Device ID from the root super block */ dev_t dev; - /** Key for lock owner ID scrambling */ + /** @scramble_key: Key for lock owner ID scrambling */ u32 scramble_key[4]; - /** Version counter for attribute changes */ + /** @attr_version: Version counter for attribute changes */ atomic64_t attr_version; - /** Version counter for evict inode */ + /** @evict_ctr: Version counter for evict inode */ atomic64_t evict_ctr; - /* maximum file name length */ + /** @name_max: maximum file name length */ u32 name_max; - /** Called on final put */ + /** @release: Called on final put */ void (*release)(struct fuse_conn *); /** - * Read/write semaphore to hold when accessing the sb of any + * @killsb: Read/write semaphore to hold when accessing the sb of any * fuse_mount belonging to this connection */ struct rw_semaphore killsb; #ifdef CONFIG_FUSE_DAX - /* Dax mode */ + /** @dax_mode: Dax mode */ enum fuse_dax_mode dax_mode; - /* Dax specific conn data, non-NULL if DAX is enabled */ + /** @dax: Dax specific conn data, non-NULL if DAX is enabled */ struct fuse_conn_dax *dax; #endif - /** List of filesystems using this connection */ + /** @mounts: List of filesystems using this connection */ struct list_head mounts; - /* New writepages go into this bucket */ + /** @curr_bucket: New writepages go into this bucket */ struct fuse_sync_bucket __rcu *curr_bucket; #ifdef CONFIG_FUSE_PASSTHROUGH - /** IDR for backing files ids */ + /** @backing_files_map: IDR for backing files ids */ struct idr backing_files_map; #endif }; @@ -814,7 +900,7 @@ extern const struct file_operations fuse_dev_operations; extern const struct dentry_operations fuse_dentry_operations; -/** +/* * Get a filled in inode */ struct inode *fuse_iget(struct super_block *sb, u64 nodeid, @@ -856,44 +942,44 @@ int fuse_finish_open(struct inode *inode, struct file *file); void fuse_sync_release(struct fuse_inode *fi, struct fuse_file *ff, unsigned int flags); -/** +/* * Send RELEASE or RELEASEDIR request */ void fuse_release_common(struct file *file, bool isdir); -/** +/* * Send FSYNC or FSYNCDIR request */ int fuse_fsync_common(struct file *file, loff_t start, loff_t end, int datasync, int opcode); -/** +/* * Notify poll wakeup */ int fuse_notify_poll_wakeup(struct fuse_conn *fc, struct fuse_notify_poll_wakeup_out *outarg); -/** +/* * Initialize file operations on a regular file */ void fuse_init_file_inode(struct inode *inode, unsigned int flags); -/** +/* * Initialize inode operations on regular files and special files */ void fuse_init_common(struct inode *inode); -/** +/* * Initialize inode and file operations on a directory */ void fuse_init_dir(struct inode *inode); -/** +/* * Initialize inode operations on a symlink */ void fuse_init_symlink(struct inode *inode); -/** +/* * Change attributes of an inode */ void fuse_change_attributes(struct inode *inode, struct fuse_attr *attr, @@ -910,7 +996,7 @@ u32 fuse_get_cache_mask(struct inode *inode); int fuse_ctl_init(void); void __exit fuse_ctl_cleanup(void); -/** +/* * Simple request sending that does request allocation and freeing */ ssize_t __fuse_simple_request(struct mnt_idmap *idmap, @@ -938,7 +1024,7 @@ void fuse_dentry_tree_cleanup(void); void fuse_epoch_work(struct work_struct *work); -/** +/* * Invalidate inode attributes */ @@ -961,7 +1047,7 @@ u64 fuse_time_to_jiffies(u64 sec, u32 nsec); void fuse_change_entry_timeout(struct dentry *entry, struct fuse_entry_out *o); -/** +/* * Initialize fuse_conn */ void fuse_conn_init(struct fuse_conn *fc, struct fuse_mount *fm, @@ -970,16 +1056,17 @@ void fuse_conn_init(struct fuse_conn *fc, struct fuse_mount *fm, int fuse_send_init(struct fuse_mount *fm); /** - * Fill in superblock and initialize fuse connection + * fuse_fill_super_common - Fill in superblock and initialize fuse connection * @sb: partially-initialized superblock to fill in * @ctx: mount context */ int fuse_fill_super_common(struct super_block *sb, struct fuse_fs_context *ctx); -/* - * Remove the mount from the connection +/** + * fuse_mount_remove - Remove the mount from the connection + * @fm: fuse_mount to remove * - * Returns whether this was the last mount + * Returns: whether this was the last mount */ bool fuse_mount_remove(struct fuse_mount *fm); @@ -997,23 +1084,25 @@ void fuse_conn_destroy(struct fuse_mount *fm); void fuse_mount_destroy(struct fuse_mount *fm); /** - * Add connection to control filesystem + * fuse_ctl_add_conn - Add connection to control filesystem + * @fc: Fuse connection to add */ int fuse_ctl_add_conn(struct fuse_conn *fc); /** - * Remove connection from control filesystem + * fuse_ctl_remove_conn - Remove connection from control filesystem + * @fc: Fuse connection to remove */ void fuse_ctl_remove_conn(struct fuse_conn *fc); -/** +/* * Is file type valid? */ int fuse_valid_type(int m); bool fuse_invalid_attr(struct fuse_attr *attr); -/** +/* * Is current process allowed to perform filesystem operation? */ bool fuse_allow_current_process(struct fuse_conn *fc); @@ -1030,7 +1119,7 @@ void fuse_flush_writepages(struct inode *inode); void fuse_set_nowrite(struct inode *inode); void fuse_release_nowrite(struct inode *inode); -/** +/* * Scan all fuse_mounts belonging to fc to find the first where * ilookup5() returns a result. Return that result and the * respective fuse_mount in *fm (unless fm is NULL). @@ -1040,13 +1129,13 @@ void fuse_release_nowrite(struct inode *inode); struct inode *fuse_ilookup(struct fuse_conn *fc, u64 nodeid, struct fuse_mount **fm); -/** +/* * File-system tells the kernel to invalidate cache for the given node id. */ int fuse_reverse_inval_inode(struct fuse_conn *fc, u64 nodeid, loff_t offset, loff_t len); -/** +/* * File-system tells the kernel to invalidate parent attributes and * the dentry matching parent/name. * @@ -1068,7 +1157,7 @@ void fuse_try_prune_one_inode(struct fuse_conn *fc, u64 nodeid); int fuse_do_open(struct fuse_mount *fm, u64 nodeid, struct file *file, bool isdir); -/** +/* * fuse_direct_io() flags */ -- cgit v1.2.3 From 8c72b1f0e2a284f6c775789a7a52f772a1052d3e Mon Sep 17 00:00:00 2001 From: Li Wang Date: Fri, 10 Apr 2026 10:34:33 +0800 Subject: fuse: drop redundant err assignment in fuse_create_open() In fuse_create_open(), err is initialized to -ENOMEM immediately before the fuse_alloc_forget() NULL check. If forget allocation fails, it branches to out_err with that value. If it succeeds, it falls through without modifying err, so err is still -ENOMEM at the point where fuse_file_alloc() is called. The second err = -ENOMEM before fuse_file_alloc() therefore is redundant. Signed-off-by: Li Wang Reviewed-by: Joanne Koong Signed-off-by: Miklos Szeredi --- fs/fuse/dir.c | 1 - 1 file changed, 1 deletion(-) diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index 0ceb49337a44..2872e068c317 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -838,7 +838,6 @@ static int fuse_create_open(struct mnt_idmap *idmap, struct inode *dir, if (!forget) goto out_err; - err = -ENOMEM; ff = fuse_file_alloc(fm, true); if (!ff) goto out_put_forget_req; -- cgit v1.2.3 From fde04c3d6f2bbc5c8fe25c99fd640f57e1e80d10 Mon Sep 17 00:00:00 2001 From: Konrad Sztyber Date: Tue, 14 Apr 2026 10:27:23 +0200 Subject: fuse: reduce attributes invalidated on directory change When the contents of a directory is modified, some of its attributes may also change, so they need to be invalidated. But this isn't the case for every attribute. For instance, unlinking or creating a file doesn't change the uid/gid of its parent directory. This can cause unnecessary FUSE_GETATTRs to be sent to user-space. For example, fuse_permission() checks if mode, uid, and gid are valid and will issue a FUSE_GETATTR if they're not, which results in an extra FUSE_GETATTR request for every FUSE_UNLINK when removing files in the same directory. Signed-off-by: Konrad Sztyber Signed-off-by: Miklos Szeredi --- fs/fuse/dir.c | 2 +- fs/fuse/fuse_i.h | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index 2872e068c317..1b71fb5adc01 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -318,7 +318,7 @@ void fuse_invalidate_attr(struct inode *inode) static void fuse_dir_changed(struct inode *dir) { - fuse_invalidate_attr(dir); + fuse_invalidate_attr_mask(dir, FUSE_STATX_MODDIR); inode_maybe_inc_iversion(dir, false); } diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index 93087fc04975..3a7ac74a23ed 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -1034,6 +1034,9 @@ void fuse_epoch_work(struct work_struct *work); /* Attributes possibly changed on data and/or size modification */ #define FUSE_STATX_MODSIZE (FUSE_STATX_MODIFY | STATX_SIZE) +/* Attributes possibly changed on directory modification */ +#define FUSE_STATX_MODDIR (FUSE_STATX_MODSIZE | STATX_NLINK) + void fuse_invalidate_attr(struct inode *inode); void fuse_invalidate_attr_mask(struct inode *inode, u32 mask); -- cgit v1.2.3 From d3aa89c9bfb7cd72a7025ebbb92275128d28afbd Mon Sep 17 00:00:00 2001 From: Li Wang Date: Tue, 21 Apr 2026 11:38:16 +0800 Subject: fuse: drop redundant check in fuse_sync_bucket_alloc() kzalloc_obj with __GFP_NOFAIL is documented to never return failure, and checking for NULL is redundant (__GFP_NOFAIL in gfp_types.h). Signed-off-by: Li Wang Reviewed-by: Joanne Koong Signed-off-by: Miklos Szeredi --- fs/fuse/inode.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c index 438d5211222c..c3689f4f7beb 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c @@ -676,11 +676,9 @@ static struct fuse_sync_bucket *fuse_sync_bucket_alloc(void) struct fuse_sync_bucket *bucket; bucket = kzalloc_obj(*bucket, GFP_KERNEL | __GFP_NOFAIL); - if (bucket) { - init_waitqueue_head(&bucket->waitq); - /* Initial active count */ - atomic_set(&bucket->count, 1); - } + init_waitqueue_head(&bucket->waitq); + /* Initial active count */ + atomic_set(&bucket->count, 1); return bucket; } -- cgit v1.2.3 From 6bd421b499ef16006bdc2ee85bf66d3956b52def Mon Sep 17 00:00:00 2001 From: Joanne Koong Date: Thu, 30 Apr 2026 04:47:46 -0700 Subject: fuse: remove redundant buffer size checks for interrupt and forget requests In fuse_dev_do_read(), there is already logic that ensures the buffer is a minimum of at least FUSE_MIN_READ_BUFFER (8k) bytes. This makes the buffer size checks for interrupt and forget requests redundant as sizeof(struct fuse_in_header) + sizeof(struct fuse_interrupt_in) and sizeof(struct fuse_in_header) + sizeof(struct fuse_forget_in) are both less than FUSE_MIN_READ_BUFFER. We can get rid of these checks. Signed-off-by: Joanne Koong Signed-off-by: Miklos Szeredi --- fs/fuse/dev.c | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index 9feecbd2532a..32b0bb1f79d7 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -1376,7 +1376,7 @@ static int request_pending(struct fuse_iqueue *fiq) */ static int fuse_read_interrupt(struct fuse_iqueue *fiq, struct fuse_copy_state *cs, - size_t nbytes, struct fuse_req *req) + struct fuse_req *req) __releases(fiq->lock) { struct fuse_in_header ih; @@ -1393,8 +1393,6 @@ __releases(fiq->lock) arg.unique = req->in.h.unique; spin_unlock(&fiq->lock); - if (nbytes < reqsize) - return -EINVAL; err = fuse_copy_one(cs, &ih, sizeof(ih)); if (!err) @@ -1427,8 +1425,7 @@ static struct fuse_forget_link *fuse_dequeue_forget(struct fuse_iqueue *fiq, } static int fuse_read_single_forget(struct fuse_iqueue *fiq, - struct fuse_copy_state *cs, - size_t nbytes) + struct fuse_copy_state *cs) __releases(fiq->lock) { int err; @@ -1445,8 +1442,6 @@ __releases(fiq->lock) spin_unlock(&fiq->lock); kfree(forget); - if (nbytes < ih.len) - return -EINVAL; err = fuse_copy_one(cs, &ih, sizeof(ih)); if (!err) @@ -1474,11 +1469,6 @@ __releases(fiq->lock) .len = sizeof(ih) + sizeof(arg), }; - if (nbytes < ih.len) { - spin_unlock(&fiq->lock); - return -EINVAL; - } - max_forgets = (nbytes - ih.len) / sizeof(struct fuse_forget_one); head = fuse_dequeue_forget(fiq, max_forgets, &count); spin_unlock(&fiq->lock); @@ -1514,7 +1504,7 @@ static int fuse_read_forget(struct fuse_chan *fch, struct fuse_iqueue *fiq, __releases(fiq->lock) { if (fch->minor < 16 || fiq->forget_list_head.next->next == NULL) - return fuse_read_single_forget(fiq, cs, nbytes); + return fuse_read_single_forget(fiq, cs); else return fuse_read_batch_forget(fiq, cs, nbytes); } @@ -1581,7 +1571,7 @@ static ssize_t fuse_dev_do_read(struct fuse_dev *fud, struct file *file, if (!list_empty(&fiq->interrupts)) { req = list_entry(fiq->interrupts.next, struct fuse_req, intr_entry); - return fuse_read_interrupt(fiq, cs, nbytes, req); + return fuse_read_interrupt(fiq, cs, req); } if (forget_pending(fiq)) { -- cgit v1.2.3 From 9107a3d8bbde1b4a38db01422601906f81f04ad5 Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Mon, 11 May 2026 14:05:09 +0200 Subject: fuse: expand MAINTAINERS with subsystem info, update mailing list - Bernd and Joanne are maintainers for fuse-uring - Amir is maintainer for passthrough - mailing list is now officially - change status of fuse-core to be "Supported" Reviewed-by: Bernd Schubert Reviewed-by: Joanne Koong Reviewed-by: Amir Goldstein Signed-off-by: Miklos Szeredi --- MAINTAINERS | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/MAINTAINERS b/MAINTAINERS index 2fb1c75afd16..6e3da76e4cb1 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -10574,10 +10574,10 @@ L: netdev@vger.kernel.org S: Maintained F: drivers/net/ethernet/fungible/ -FUSE: FILESYSTEM IN USERSPACE +FUSE FILESYSTEM [CORE] M: Miklos Szeredi -L: linux-fsdevel@vger.kernel.org -S: Maintained +L: fuse-devel@lists.linux.dev +S: Supported W: https://github.com/libfuse/ T: git git://git.kernel.org/pub/scm/linux/kernel/git/mszeredi/fuse.git F: Documentation/filesystems/fuse/* @@ -10585,6 +10585,26 @@ F: fs/fuse/ F: include/uapi/linux/fuse.h F: tools/testing/selftests/filesystems/fuse/ +FUSE FILESYSTEM [IO-URING] +M: Bernd Schubert +M: Joanne Koong +L: fuse-devel@lists.linux.dev +S: Maintained +T: git git://git.kernel.org/pub/scm/linux/kernel/git/mszeredi/fuse.git +F: Documentation/filesystems/fuse/fuse-io-uring.rst +F: fs/fuse/dev_uring.c +F: fs/fuse/dev_uring_i.h + +FUSE FILESYSTEM [PASSTHROUGH] +M: Amir Goldstein +L: fuse-devel@lists.linux.dev +S: Maintained +T: git git://git.kernel.org/pub/scm/linux/kernel/git/mszeredi/fuse.git +F: Documentation/filesystems/fuse/fuse-passthrough.rst +F: fs/fuse/backing.c +F: fs/fuse/iomode.c +F: fs/fuse/passthrough.c + FUTEX SUBSYSTEM M: Thomas Gleixner M: Ingo Molnar -- cgit v1.2.3 From c51248524a0f546b9a9b44710038f5663688ed10 Mon Sep 17 00:00:00 2001 From: GuoHan Zhao Date: Sun, 10 May 2026 22:54:37 +0800 Subject: fuse: use current creds for backing files FUSE backing files only need a stable snapshot of the current credentials for later backing-file I/O. prepare_creds() allocates a mutable copy and can fail, but this code never modifies or commits the result. Use get_current_cred() instead and store it as a const pointer. This matches the rest of the backing-file helpers and avoids an unnecessary allocation and failure path. Signed-off-by: GuoHan Zhao Reviewed-by: Amir Goldstein Acked-by: Christian Brauner Signed-off-by: Miklos Szeredi --- fs/fuse/backing.c | 2 +- fs/fuse/fuse_i.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/fuse/backing.c b/fs/fuse/backing.c index 3d3f49c2dd42..472b6afa7dff 100644 --- a/fs/fuse/backing.c +++ b/fs/fuse/backing.c @@ -119,7 +119,7 @@ int fuse_backing_open(struct fuse_conn *fc, struct fuse_backing_map *map) goto out_fput; fb->file = file; - fb->cred = prepare_creds(); + fb->cred = get_current_cred(); refcount_set(&fb->count, 1); res = fuse_backing_id_alloc(fc, fb); diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index 3a7ac74a23ed..55a3841b2889 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -91,7 +91,7 @@ struct fuse_submount_lookup { /* Container for data related to mapping to backing file */ struct fuse_backing { struct file *file; - struct cred *cred; + const struct cred *cred; /* refcount */ refcount_t count; -- cgit v1.2.3 From 03728af4aeef6ee9914f93d60936db351e106863 Mon Sep 17 00:00:00 2001 From: William Theesfeld Date: Mon, 1 Jun 2026 15:29:34 -0400 Subject: fuse: convert page array allocation to kcalloc() fuse_get_user_pages() allocates the temporary pages[] array used by iov_iter_extract_pages() with the open-coded kzalloc(n * sizeof(*p), ...) form. max_pages is derived from the inbound iov_iter and is not bounded at compile time, so the multiplication can overflow on sufficiently large iter counts; the resulting too-small allocation would then be written past by iov_iter_extract_pages(). Switch to kcalloc(), which carries the same zero-on-allocation semantics and adds the standard size_mul overflow check. No functional change for non-overflow inputs. Signed-off-by: William Theesfeld Signed-off-by: Miklos Szeredi --- fs/fuse/file.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/fuse/file.c b/fs/fuse/file.c index e8833e2a6610..cbd02fa3cb74 100644 --- a/fs/fuse/file.c +++ b/fs/fuse/file.c @@ -1590,7 +1590,7 @@ static int fuse_get_user_pages(struct fuse_args_pages *ap, struct iov_iter *ii, * manually extract pages using iov_iter_extract_pages() and then * copy that to a folios array. */ - struct page **pages = kzalloc(max_pages * sizeof(struct page *), + struct page **pages = kcalloc(max_pages, sizeof(struct page *), GFP_KERNEL); if (!pages) { ret = -ENOMEM; -- cgit v1.2.3 From 5ac67ab433d95bef5dcb8d18db0e3e4616f576cb Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Wed, 3 Jun 2026 00:40:56 +0200 Subject: fuse: use QSTR() instead of QSTR_INIT() in fuse_get_dentry Drop the hard-coded length argument and use the simpler QSTR(). Inline the code and drop the local variable. Signed-off-by: Thorsten Blum Signed-off-by: Miklos Szeredi --- fs/fuse/inode.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c index c3689f4f7beb..e62fc738ddbc 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c @@ -1064,12 +1064,11 @@ static struct dentry *fuse_get_dentry(struct super_block *sb, inode = ilookup5(sb, handle->nodeid, fuse_inode_eq, &handle->nodeid); if (!inode) { struct fuse_entry_out outarg; - const struct qstr name = QSTR_INIT(".", 1); if (!fc->export_support) goto out_err; - err = fuse_lookup_name(sb, handle->nodeid, &name, &outarg, + err = fuse_lookup_name(sb, handle->nodeid, &QSTR("."), &outarg, &inode); if (err && err != -ENOENT) goto out_err; -- cgit v1.2.3 From 9f3e7166aaa69a8f7c535e267ac23ecc931c3697 Mon Sep 17 00:00:00 2001 From: Tim Bird Date: Thu, 4 Jun 2026 13:55:08 -0600 Subject: fuse: Add SPDX ID lines to some files Some fuse source files are missing SPDX-License-Identifier lines. Add appropriate IDs to these files, and remove old license references from the headers. Signed-off-by: Tim Bird Signed-off-by: Miklos Szeredi --- fs/fuse/acl.c | 4 +--- fs/fuse/control.c | 4 +--- fs/fuse/dev.c | 4 +--- fs/fuse/dir.c | 4 +--- fs/fuse/file.c | 4 +--- fs/fuse/fuse_i.h | 4 +--- fs/fuse/inode.c | 4 +--- fs/fuse/readdir.c | 4 +--- fs/fuse/xattr.c | 4 +--- 9 files changed, 9 insertions(+), 27 deletions(-) diff --git a/fs/fuse/acl.c b/fs/fuse/acl.c index cbde6ac1add3..31fb50e16aed 100644 --- a/fs/fuse/acl.c +++ b/fs/fuse/acl.c @@ -1,9 +1,7 @@ +// SPDX-License-Identifier: GPL-2.0 /* * FUSE: Filesystem in Userspace * Copyright (C) 2016 Canonical Ltd. - * - * This program can be distributed under the terms of the GNU GPL. - * See the file COPYING. */ #include "fuse_i.h" diff --git a/fs/fuse/control.c b/fs/fuse/control.c index 925a15488499..21ffde596d61 100644 --- a/fs/fuse/control.c +++ b/fs/fuse/control.c @@ -1,9 +1,7 @@ +// SPDX-License-Identifier: GPL-2.0 /* FUSE: Filesystem in Userspace Copyright (C) 2001-2008 Miklos Szeredi - - This program can be distributed under the terms of the GNU GPL. - See the file COPYING. */ #include "fuse_i.h" diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index 32b0bb1f79d7..7e9bf358f015 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -1,9 +1,7 @@ +// SPDX-License-Identifier: GPL-2.0 /* FUSE: Filesystem in Userspace Copyright (C) 2001-2008 Miklos Szeredi - - This program can be distributed under the terms of the GNU GPL. - See the file COPYING. */ #include "dev.h" diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index 1b71fb5adc01..795e92037ce7 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -1,9 +1,7 @@ +// SPDX-License-Identifier: GPL-2.0 /* FUSE: Filesystem in Userspace Copyright (C) 2001-2008 Miklos Szeredi - - This program can be distributed under the terms of the GNU GPL. - See the file COPYING. */ #include "dev.h" diff --git a/fs/fuse/file.c b/fs/fuse/file.c index cbd02fa3cb74..8c8d9f6d8f72 100644 --- a/fs/fuse/file.c +++ b/fs/fuse/file.c @@ -1,9 +1,7 @@ +// SPDX-License-Identifier: GPL-2.0 /* FUSE: Filesystem in Userspace Copyright (C) 2001-2008 Miklos Szeredi - - This program can be distributed under the terms of the GNU GPL. - See the file COPYING. */ #include "fuse_i.h" diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index 55a3841b2889..30129d798088 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -1,9 +1,7 @@ +/* SPDX-License-Identifier: GPL-2.0 */ /* FUSE: Filesystem in Userspace Copyright (C) 2001-2008 Miklos Szeredi - - This program can be distributed under the terms of the GNU GPL. - See the file COPYING. */ #ifndef _FS_FUSE_I_H diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c index e62fc738ddbc..d975073c6029 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c @@ -1,9 +1,7 @@ +// SPDX-License-Identifier: GPL-2.0 /* FUSE: Filesystem in Userspace Copyright (C) 2001-2008 Miklos Szeredi - - This program can be distributed under the terms of the GNU GPL. - See the file COPYING. */ #include "dev.h" diff --git a/fs/fuse/readdir.c b/fs/fuse/readdir.c index c38139225a2e..e993255584df 100644 --- a/fs/fuse/readdir.c +++ b/fs/fuse/readdir.c @@ -1,9 +1,7 @@ +// SPDX-License-Identifier: GPL-2.0 /* FUSE: Filesystem in Userspace Copyright (C) 2001-2018 Miklos Szeredi - - This program can be distributed under the terms of the GNU GPL. - See the file COPYING. */ diff --git a/fs/fuse/xattr.c b/fs/fuse/xattr.c index 93dfb06b6cea..cab2685acc65 100644 --- a/fs/fuse/xattr.c +++ b/fs/fuse/xattr.c @@ -1,9 +1,7 @@ +// SPDX-License-Identifier: GPL-2.0 /* * FUSE: Filesystem in Userspace * Copyright (C) 2001-2016 Miklos Szeredi - * - * This program can be distributed under the terms of the GNU GPL. - * See the file COPYING. */ #include "fuse_i.h" -- cgit v1.2.3 From e0dcd3f02c5b406b506b085ea41b14218c98b4a2 Mon Sep 17 00:00:00 2001 From: Amir Goldstein Date: Fri, 5 Jun 2026 11:26:47 +0200 Subject: fuse: add fuse_request_sent tracepoint This new tracepoint complements fuse_request_send (enqueue) and fuse_request_end (completion). It fires after the request has been successfully copied to the daemon's buffer, just before the daemon can start to process it. fuse_request_sent does not fire if the copy of the request fails. It also does not fire for NOTIFY_REPLY, which fires the _end tracepoint at the end of copy. This is needed for tools tracking the in-flight state of user initiated fuse requests. Signed-off-by: Amir Goldstein Reviewed-by: Joanne Koong Signed-off-by: Miklos Szeredi --- fs/fuse/dev.c | 1 + fs/fuse/dev_uring.c | 1 + fs/fuse/fuse_trace.h | 22 ++++++++++++++++++++++ 3 files changed, 24 insertions(+) diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index 7e9bf358f015..63a289853023 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -1633,6 +1633,7 @@ static ssize_t fuse_dev_do_read(struct fuse_dev *fud, struct file *file, list_move_tail(&req->list, &fpq->processing[hash]); __fuse_get_request(req); set_bit(FR_SENT, &req->flags); + trace_fuse_request_sent(req); spin_unlock(&fpq->lock); /* matches barrier in request_wait_answer() */ smp_mb__after_atomic(); diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c index 2901c7a5ff05..87f5f9eba534 100644 --- a/fs/fuse/dev_uring.c +++ b/fs/fuse/dev_uring.c @@ -711,6 +711,7 @@ static int fuse_uring_prepare_send(struct fuse_ring_ent *ent, err = fuse_uring_copy_to_ring(ent, req); if (!err) { set_bit(FR_SENT, &req->flags); + trace_fuse_request_sent(req); } else { /* * Copying the request failed. Remove the entry from the diff --git a/fs/fuse/fuse_trace.h b/fs/fuse/fuse_trace.h index 4e34ddb172ed..60baa10bbcb9 100644 --- a/fs/fuse/fuse_trace.h +++ b/fs/fuse/fuse_trace.h @@ -101,6 +101,28 @@ TRACE_EVENT(fuse_request_send, __print_symbolic(__entry->opcode, OPCODES), __entry->len) ); +TRACE_EVENT(fuse_request_sent, + TP_PROTO(const struct fuse_req *req), + + TP_ARGS(req), + + TP_STRUCT__entry( + __field(dev_t, connection) + __field(uint64_t, unique) + __field(enum fuse_opcode, opcode) + ), + + TP_fast_assign( + __entry->connection = req->chan->conn->dev; + __entry->unique = req->in.h.unique; + __entry->opcode = req->in.h.opcode; + ), + + TP_printk("connection %u req %llu opcode %u (%s)", + __entry->connection, __entry->unique, __entry->opcode, + __print_symbolic(__entry->opcode, OPCODES)) +); + TRACE_EVENT(fuse_request_end, TP_PROTO(const struct fuse_req *req), -- cgit v1.2.3 From a9489fbda747ca8eb183161f887f5295df3564fe Mon Sep 17 00:00:00 2001 From: Marco Crivellari Date: Fri, 8 May 2026 15:45:33 +0200 Subject: fuse: dax: Move long delayed work on system_dfl_long_wq Currently the code enqueue work items using {queue|mod}_delayed_work(), using system_long_wq. This workqueue should be used when long works are expected and it is a per-cpu workqueue. The function(s) end up calling __queue_delayed_work(), which set a global timer that could fire anywhere, enqueuing the work where the timer fired. Unbound works could benefit from scheduler task placement, to optimize performance and power consumption. Long work shouldn't stick to a single CPU. Recently, a new unbound workqueue specific for long running work has been added: c116737e972e ("workqueue: Add system_dfl_long_wq for long unbound works") Since the workqueue work doesn't rely on per-cpu variables, there is no obvious reason that justify the use of a per-cpu workqueue. So change system_long_wq with system_dfl_long_wq so that the work may benefit from scheduler task placement. Signed-off-by: Marco Crivellari Signed-off-by: Miklos Szeredi --- fs/fuse/dax.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/fuse/dax.c b/fs/fuse/dax.c index f6cf00a8938c..8b53625ac7ab 100644 --- a/fs/fuse/dax.c +++ b/fs/fuse/dax.c @@ -113,7 +113,7 @@ __kick_dmap_free_worker(struct fuse_conn_dax *fcd, unsigned long delay_ms) free_threshold = max_t(unsigned long, fcd->nr_ranges * FUSE_DAX_RECLAIM_THRESHOLD / 100, 1); if (fcd->nr_free_ranges < free_threshold) - queue_delayed_work(system_long_wq, &fcd->free_work, + queue_delayed_work(system_dfl_long_wq, &fcd->free_work, msecs_to_jiffies(delay_ms)); } -- cgit v1.2.3 From 521fed4886ccb3b78846595e843ec9419d343623 Mon Sep 17 00:00:00 2001 From: Li Wang Date: Fri, 22 May 2026 16:38:05 +0800 Subject: fuse: use READ_ONCE in fuse_chan_num_background() fuse_chan_num_background() is called without holding fch->bg_lock (for example from fuse_writepages() to compare against fc->congestion_threshold), while fch->num_background is updated under bg_lock in dev.c and dev_uring.c. This is the same locked-write/lockless-read pattern already used for max_background in fuse_chan_max_background(). Use READ_ONCE() on the read side so that: - The compiler does not cache or coalesce loads of a value that may change concurrently on another CPU. - Prevent KCSAN from reporting an unexpected race. Signed-off-by: Li Wang Fixes: 670d21c6e17f ("fuse: remove reliance on bdi congestion") Signed-off-by: Miklos Szeredi --- fs/fuse/dev.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index 63a289853023..d09cddd68e03 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -383,7 +383,7 @@ EXPORT_SYMBOL_GPL(fuse_dev_chan_new); unsigned int fuse_chan_num_background(struct fuse_chan *fch) { - return fch->num_background; + return READ_ONCE(fch->num_background); } unsigned int fuse_chan_max_background(struct fuse_chan *fch) -- cgit v1.2.3 From 1ca4b8b32e511654d5bb89da6c3d850f541e789e Mon Sep 17 00:00:00 2001 From: Joanne Koong Date: Thu, 5 Mar 2026 17:05:24 -0800 Subject: fuse: remove stray newline in fuse_dev_do_read() Remove stray newline that shouldn't be there. Signed-off-by: Joanne Koong Signed-off-by: Miklos Szeredi --- fs/fuse/dev.c | 1 - 1 file changed, 1 deletion(-) diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index d09cddd68e03..4405b956c115 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -1605,7 +1605,6 @@ static ssize_t fuse_dev_do_read(struct fuse_dev *fud, struct file *file, if (!fpq->connected) { req->out.h.error = err = -ECONNABORTED; goto out_end; - } list_add(&req->list, &fpq->io); spin_unlock(&fpq->lock); -- cgit v1.2.3 From ac7304fa1de49e4663307dc92f34e455daa15a74 Mon Sep 17 00:00:00 2001 From: Joanne Koong Date: Thu, 5 Mar 2026 17:05:25 -0800 Subject: fuse: clean up interrupt reading Clean up interrupt reading logic. Remove passing the pointer to the fuse request as an arg and make the header initializations more readable. Signed-off-by: Joanne Koong Signed-off-by: Miklos Szeredi --- fs/fuse/dev.c | 32 +++++++++++++------------------- 1 file changed, 13 insertions(+), 19 deletions(-) diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index 4405b956c115..5763a7cd3b37 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -1372,24 +1372,21 @@ static int request_pending(struct fuse_iqueue *fiq) * * Called with fiq->lock held, releases it */ -static int fuse_read_interrupt(struct fuse_iqueue *fiq, - struct fuse_copy_state *cs, - struct fuse_req *req) +static int fuse_read_interrupt(struct fuse_iqueue *fiq, struct fuse_copy_state *cs) __releases(fiq->lock) { - struct fuse_in_header ih; - struct fuse_interrupt_in arg; - unsigned reqsize = sizeof(ih) + sizeof(arg); + struct fuse_req *req = list_first_entry(&fiq->interrupts, struct fuse_req, intr_entry); + struct fuse_interrupt_in arg = { + .unique = req->in.h.unique, + }; + struct fuse_in_header ih = { + .opcode = FUSE_INTERRUPT, + .unique = (req->in.h.unique | FUSE_INT_REQ_BIT), + .len = sizeof(ih) + sizeof(arg), + }; int err; list_del_init(&req->intr_entry); - memset(&ih, 0, sizeof(ih)); - memset(&arg, 0, sizeof(arg)); - ih.len = reqsize; - ih.opcode = FUSE_INTERRUPT; - ih.unique = (req->in.h.unique | FUSE_INT_REQ_BIT); - arg.unique = req->in.h.unique; - spin_unlock(&fiq->lock); err = fuse_copy_one(cs, &ih, sizeof(ih)); @@ -1397,7 +1394,7 @@ __releases(fiq->lock) err = fuse_copy_one(cs, &arg, sizeof(arg)); fuse_copy_finish(cs); - return err ? err : reqsize; + return err ? err : ih.len; } static struct fuse_forget_link *fuse_dequeue_forget(struct fuse_iqueue *fiq, @@ -1566,11 +1563,8 @@ static ssize_t fuse_dev_do_read(struct fuse_dev *fud, struct file *file, goto err_unlock; } - if (!list_empty(&fiq->interrupts)) { - req = list_entry(fiq->interrupts.next, struct fuse_req, - intr_entry); - return fuse_read_interrupt(fiq, cs, req); - } + if (!list_empty(&fiq->interrupts)) + return fuse_read_interrupt(fiq, cs); if (forget_pending(fiq)) { if (list_empty(&fiq->pending) || fiq->forget_batch-- > 0) -- cgit v1.2.3 From 71947173cef279be5eed209ec28f8c11f9d73159 Mon Sep 17 00:00:00 2001 From: Zhang Tianci Date: Thu, 25 Dec 2025 19:11:56 +0800 Subject: fuse: set ff->flock only on success If FUSE_SETLK fails (e.g., due to EWOULDBLOCK), we shall not set FUSE_RELEASE_FLOCK_UNLOCK in fuse_file_release(). Reported-by: Li Yichao Signed-off-by: Zhang Tianci Signed-off-by: Miklos Szeredi --- fs/fuse/file.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fs/fuse/file.c b/fs/fuse/file.c index 8c8d9f6d8f72..9160836664b0 100644 --- a/fs/fuse/file.c +++ b/fs/fuse/file.c @@ -2569,8 +2569,9 @@ static int fuse_file_flock(struct file *file, int cmd, struct file_lock *fl) struct fuse_file *ff = file->private_data; /* emulate flock with POSIX locks */ - ff->flock = true; err = fuse_setlk(file, fl, 1); + if (!err) + ff->flock = true; } return err; -- cgit v1.2.3 From 2b0408d0284f4ff376cf5610fa8c9905e93c2541 Mon Sep 17 00:00:00 2001 From: Cheng Ding Date: Mon, 20 Apr 2026 16:39:34 +0800 Subject: fuse: invalidate page cache after DIO and async DIO writes This fixe does page cache invalidation after DIO and async DIO writes for both O_DIRECT and FOPEN_DIRECT_IO cases. Commit b359af8275a9 ("fuse: Invalidate the page cache after FOPEN_DIRECT_IO write") fixed xfstests generic/209 for DIO writes in the FOPEN_DIRECT_IO path. DIO writes without FOPEN_DIRECT_IO are already handled by generic_file_direct_write(). However, async DIO writes (xfstests generic/451) remain unhandled. After this fix: - Async write with FUSE_ASYNC_DIO: invalidate in fuse_aio_invalidate_worker() - Otherwise (Sync or async write without FUSE_ASYNC_DIO): - With FOPEN_DIRECT_IO: invalidate in fuse_direct_write_iter() - Without FOPEN_DIRECT_IO: invalidate in generic_file_direct_write() Workqueue is required for async write invalidation to prevent deadlock: calling it directly in the I/O end routine (which is in fuse worker thread context) can block on a folio lock held by a buffered I/O thread waiting for the same fuse worker thread. Co-developed-by: Jingbo Xu Signed-off-by: Jingbo Xu Signed-off-by: Cheng Ding Reviewed-by: Jingbo Xu Signed-off-by: Miklos Szeredi --- fs/fuse/file.c | 57 ++++++++++++++++++++++++++++++++++++++---------- fs/fuse/fuse_i.h | 1 + fs/internal.h | 1 - include/linux/fs/super.h | 2 ++ 4 files changed, 49 insertions(+), 12 deletions(-) diff --git a/fs/fuse/file.c b/fs/fuse/file.c index 9160836664b0..cb8da4c06d17 100644 --- a/fs/fuse/file.c +++ b/fs/fuse/file.c @@ -639,6 +639,19 @@ static ssize_t fuse_get_res_by_io(struct fuse_io_priv *io) return io->bytes < 0 ? io->size : io->bytes; } +static void fuse_aio_invalidate_worker(struct work_struct *work) +{ + struct fuse_io_priv *io = container_of(work, struct fuse_io_priv, work); + struct address_space *mapping = io->iocb->ki_filp->f_mapping; + ssize_t res = fuse_get_res_by_io(io); + pgoff_t start = io->offset >> PAGE_SHIFT; + pgoff_t end = (io->offset + res - 1) >> PAGE_SHIFT; + + invalidate_inode_pages2_range(mapping, start, end); + io->iocb->ki_complete(io->iocb, res); + kref_put(&io->refcnt, fuse_io_release); +} + /* * In case of short read, the caller sets 'pos' to the position of * actual end of fuse request in IO request. Otherwise, if bytes_requested @@ -671,10 +684,11 @@ static void fuse_aio_complete(struct fuse_io_priv *io, int err, ssize_t pos) spin_unlock(&io->lock); if (!left && !io->blocking) { + struct inode *inode = file_inode(io->iocb->ki_filp); + struct address_space *mapping = io->iocb->ki_filp->f_mapping; ssize_t res = fuse_get_res_by_io(io); if (res >= 0) { - struct inode *inode = file_inode(io->iocb->ki_filp); struct fuse_conn *fc = get_fuse_conn(inode); struct fuse_inode *fi = get_fuse_inode(inode); @@ -683,6 +697,17 @@ static void fuse_aio_complete(struct fuse_io_priv *io, int err, ssize_t pos) spin_unlock(&fi->lock); } + if (io->write && res > 0 && mapping->nrpages) { + /* + * As in generic_file_direct_write(), invalidate after the + * write, to invalidate read-ahead cache that may have competed + * with the write. + */ + INIT_WORK(&io->work, fuse_aio_invalidate_worker); + queue_work(inode->i_sb->s_dio_done_wq, &io->work); + return; + } + io->iocb->ki_complete(io->iocb, res); } @@ -1745,15 +1770,6 @@ ssize_t fuse_direct_io(struct fuse_io_priv *io, struct iov_iter *iter, if (res > 0) *ppos = pos; - if (res > 0 && write && fopen_direct_io) { - /* - * As in generic_file_direct_write(), invalidate after the - * write, to invalidate read-ahead cache that may have competed - * with the write. - */ - invalidate_inode_pages2_range(mapping, idx_from, idx_to); - } - return res > 0 ? res : err; } EXPORT_SYMBOL_GPL(fuse_direct_io); @@ -1792,6 +1808,8 @@ static ssize_t fuse_direct_read_iter(struct kiocb *iocb, struct iov_iter *to) static ssize_t fuse_direct_write_iter(struct kiocb *iocb, struct iov_iter *from) { struct inode *inode = file_inode(iocb->ki_filp); + struct address_space *mapping = inode->i_mapping; + loff_t pos = iocb->ki_pos; ssize_t res; bool exclusive; @@ -1808,6 +1826,16 @@ static ssize_t fuse_direct_write_iter(struct kiocb *iocb, struct iov_iter *from) FUSE_DIO_WRITE); fuse_write_update_attr(inode, iocb->ki_pos, res); } + if (res > 0 && mapping->nrpages) { + /* + * As in generic_file_direct_write(), invalidate after + * write, to invalidate read-ahead cache that may have + * with the write. + */ + invalidate_inode_pages2_range(mapping, + pos >> PAGE_SHIFT, + (pos + res - 1) >> PAGE_SHIFT); + } } fuse_dio_unlock(iocb, exclusive); @@ -2714,6 +2742,7 @@ fuse_direct_IO(struct kiocb *iocb, struct iov_iter *iter) size_t count = iov_iter_count(iter), shortened = 0; loff_t offset = iocb->ki_pos; struct fuse_io_priv *io; + bool async = ff->fm->fc->async_dio; pos = offset; inode = file->f_mapping->host; @@ -2722,6 +2751,12 @@ fuse_direct_IO(struct kiocb *iocb, struct iov_iter *iter) if ((iov_iter_rw(iter) == READ) && (offset >= i_size)) return 0; + if ((iov_iter_rw(iter) == WRITE) && async && !inode->i_sb->s_dio_done_wq) { + ret = sb_init_dio_done_wq(inode->i_sb); + if (ret < 0) + return ret; + } + io = kmalloc_obj(struct fuse_io_priv); if (!io) return -ENOMEM; @@ -2737,7 +2772,7 @@ fuse_direct_IO(struct kiocb *iocb, struct iov_iter *iter) * By default, we want to optimize all I/Os with async request * submission to the client filesystem if supported. */ - io->async = ff->fm->fc->async_dio; + io->async = async; io->iocb = iocb; io->blocking = is_sync_kiocb(iocb); diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index 30129d798088..6455617b74a0 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -337,6 +337,7 @@ union fuse_file_args { /** The request IO state (for asynchronous processing) */ struct fuse_io_priv { struct kref refcnt; + struct work_struct work; int async; spinlock_t lock; unsigned reqs; diff --git a/fs/internal.h b/fs/internal.h index d77578d66d42..355d93f92208 100644 --- a/fs/internal.h +++ b/fs/internal.h @@ -138,7 +138,6 @@ extern bool super_trylock_shared(struct super_block *sb); struct super_block *user_get_super(dev_t, bool excl); void put_super(struct super_block *sb); extern bool mount_capable(struct fs_context *); -int sb_init_dio_done_wq(struct super_block *sb); /* * Prepare superblock for changing its read-only state (i.e., either remount diff --git a/include/linux/fs/super.h b/include/linux/fs/super.h index f21ffbb6dea5..405612678115 100644 --- a/include/linux/fs/super.h +++ b/include/linux/fs/super.h @@ -235,4 +235,6 @@ int freeze_super(struct super_block *super, enum freeze_holder who, int thaw_super(struct super_block *super, enum freeze_holder who, const void *freeze_owner); +int sb_init_dio_done_wq(struct super_block *sb); + #endif /* _LINUX_FS_SUPER_H */ -- cgit v1.2.3 From 6af3330ec5d5fb8c06c04eb520a71cf73ea5a765 Mon Sep 17 00:00:00 2001 From: Yung-Tse Cheng Date: Mon, 6 Apr 2026 03:30:39 +0800 Subject: virtio-fs: avoid double-free on failed queue setup virtio_fs_setup_vqs() allocates fs->vqs and fs->mq_map before calling virtio_find_vqs(). If virtio_find_vqs() fails, the error path frees both pointers and returns an error to virtio_fs_probe(). virtio_fs_probe() then drops the last kobject reference, and virtio_fs_ktype_release() frees fs->vqs and fs->mq_map again. This leaves dangling pointers in struct virtio_fs and can trigger a double-free during probe failure cleanup. Set fs->vqs and fs->mq_map to NULL immediately after kfree() in the virtio_fs_setup_vqs() error path so that the later kobject release sees an uninitialized state and kfree(NULL) becomes harmless. This can be reproduced when a broken virtio-fs device advertises more request queues than the transport actually provides. In that case virtio_find_vqs() fails while setting up the extra queue, and the probe path reaches the double-free cleanup sequence. Signed-off-by: Yung-Tse Cheng Signed-off-by: Miklos Szeredi --- fs/fuse/virtio_fs.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fs/fuse/virtio_fs.c b/fs/fuse/virtio_fs.c index a4cf813cebfc..df25d4faca41 100644 --- a/fs/fuse/virtio_fs.c +++ b/fs/fuse/virtio_fs.c @@ -1010,7 +1010,9 @@ out: kfree(vqs); if (ret) { kfree(fs->vqs); + fs->vqs = NULL; kfree(fs->mq_map); + fs->mq_map = NULL; } return ret; } -- cgit v1.2.3 From 0fa8346099b5794b9909989e3f1cb617e9d8d3fa Mon Sep 17 00:00:00 2001 From: Jun Wu Date: Thu, 14 May 2026 17:14:12 -0700 Subject: fuse: invalidate readdir cache on epoch bump FUSE_NOTIFY_INC_EPOCH invalidates dentries, but does not invalidate cached readdir results. A process with cwd inside a FUSE mount can therefore observe stale readdir(".") output after an epoch bump. Fix this by recording epoch in the readdir cache and checking it on reuse. Minimal reproducer: - mount a tiny FUSE fs with an empty root directory - on opendir, enable fi->cache_readdir and fi->keep_cache - chdir into the mount and call readdir(".") to populate readdir cache - make the FUSE server report one file in the root directory - send only FUSE_NOTIFY_INC_EPOCH - call readdir(".") again; before this change it stays stale, after this change it sees the new file Fixes: 2396356a945b ("fuse: add more control over cache invalidation behaviour") Signed-off-by: Jun Wu Reviewed-by: Joanne Koong Reviewed-by: Luis Henriques Signed-off-by: Miklos Szeredi --- fs/fuse/fuse_i.h | 5 +++++ fs/fuse/notify.c | 6 +++--- fs/fuse/readdir.c | 5 ++++- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index 6455617b74a0..85f738c53122 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -190,6 +190,11 @@ struct fuse_inode { */ struct timespec64 mtime; + /** + * @epoch: epoch of fc when cache was started + */ + int epoch; + /** * @iversion: iversion of directory when cache was * started diff --git a/fs/fuse/notify.c b/fs/fuse/notify.c index 7cd63502e124..f93d6fa05bf7 100644 --- a/fs/fuse/notify.c +++ b/fs/fuse/notify.c @@ -348,9 +348,9 @@ static int fuse_notify_resend(struct fuse_conn *fc) } /* - * Increments the fuse connection epoch. This will result of dentries from - * previous epochs to be invalidated. Additionally, if inval_wq is set, a work - * queue is scheduled to trigger the invalidation. + * Increments the fuse connection epoch. This will cause dentries and + * readdir caches from previous epochs to be invalidated. Additionally, + * if inval_wq is set, a work queue is scheduled to trigger the invalidation. */ static int fuse_notify_inc_epoch(struct fuse_conn *fc) { diff --git a/fs/fuse/readdir.c b/fs/fuse/readdir.c index e993255584df..a94b00e13f6e 100644 --- a/fs/fuse/readdir.c +++ b/fs/fuse/readdir.c @@ -489,6 +489,7 @@ static void fuse_rdc_reset(struct inode *inode) fi->rdc.version++; fi->rdc.size = 0; fi->rdc.pos = 0; + fi->rdc.epoch = 0; } #define UNCACHED 1 @@ -530,6 +531,7 @@ retry_locked: if (!ctx->pos && !fi->rdc.size) { fi->rdc.mtime = inode_get_mtime(inode); fi->rdc.iversion = inode_query_iversion(inode); + fi->rdc.epoch = atomic_read(&fc->epoch); } spin_unlock(&fi->rdc.lock); return UNCACHED; @@ -543,7 +545,8 @@ retry_locked: struct timespec64 mtime = inode_get_mtime(inode); if (inode_peek_iversion(inode) != fi->rdc.iversion || - !timespec64_equal(&fi->rdc.mtime, &mtime)) { + !timespec64_equal(&fi->rdc.mtime, &mtime) || + fi->rdc.epoch != atomic_read(&fc->epoch)) { fuse_rdc_reset(inode); goto retry_locked; } -- cgit v1.2.3 From 6813da09506842612cce91f88ef567ca569d9663 Mon Sep 17 00:00:00 2001 From: Joanne Koong Date: Fri, 12 Jun 2026 14:05:04 -0700 Subject: fuse-uring: separate next request fetching from sending logic Simplify the logic for fetching + sending off the next request. This gets rid of fuse_uring_send_next_to_ring() which contained duplicated logic from fuse_uring_send(). This decouples request fetching from the send operation, which makes the control flow clearer and reduces unnecessary parameter passing. Reviewed-by: Bernd Schubert Reviewed-by: Jeff Layton Reviewed-by: Baokun Li Signed-off-by: Joanne Koong Signed-off-by: Miklos Szeredi --- fs/fuse/dev_uring.c | 81 +++++++++++++++++++---------------------------------- 1 file changed, 29 insertions(+), 52 deletions(-) diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c index 87f5f9eba534..0b66fe3a5da0 100644 --- a/fs/fuse/dev_uring.c +++ b/fs/fuse/dev_uring.c @@ -741,35 +741,6 @@ static void fuse_uring_add_to_pq(struct fuse_ring_ent *ent) list_move_tail(&req->list, &fpq->processing[hash]); } -/* - * Write data to the ring buffer and send the request to userspace, - * userspace will read it - * This is comparable with classical read(/dev/fuse) - */ -static int fuse_uring_send_next_to_ring(struct fuse_ring_ent *ent, - struct fuse_req *req, - unsigned int issue_flags) -{ - struct fuse_ring_queue *queue = ent->queue; - int err; - struct io_uring_cmd *cmd; - - err = fuse_uring_prepare_send(ent, req); - if (err) - return err; - - spin_lock(&queue->lock); - cmd = ent->cmd; - ent->cmd = NULL; - ent->state = FRRS_USERSPACE; - list_move_tail(&ent->list, &queue->ent_in_userspace); - fuse_uring_add_to_pq(ent); - spin_unlock(&queue->lock); - - io_uring_cmd_done(cmd, 0, issue_flags); - return 0; -} - /* * Make a ring entry available for fuse_req assignment */ @@ -852,11 +823,13 @@ out: } /* - * Get the next fuse req and send it + * Get the next fuse req. + * + * Returns true if the next fuse request has been assigned to the ent. + * Else, there is no next fuse request and this returns false. */ -static void fuse_uring_next_fuse_req(struct fuse_ring_ent *ent, - struct fuse_ring_queue *queue, - unsigned int issue_flags) +static bool fuse_uring_get_next_fuse_req(struct fuse_ring_ent *ent, + struct fuse_ring_queue *queue) { int err; struct fuse_req *req; @@ -868,10 +841,12 @@ retry: spin_unlock(&queue->lock); if (req) { - err = fuse_uring_send_next_to_ring(ent, req, issue_flags); + err = fuse_uring_prepare_send(ent, req); if (err) goto retry; } + + return req != NULL; } static int fuse_ring_ent_set_commit(struct fuse_ring_ent *ent) @@ -889,6 +864,21 @@ static int fuse_ring_ent_set_commit(struct fuse_ring_ent *ent) return 0; } +static void fuse_uring_send(struct fuse_ring_ent *ent, struct io_uring_cmd *cmd, + ssize_t ret, unsigned int issue_flags) +{ + struct fuse_ring_queue *queue = ent->queue; + + spin_lock(&queue->lock); + ent->state = FRRS_USERSPACE; + list_move_tail(&ent->list, &queue->ent_in_userspace); + ent->cmd = NULL; + fuse_uring_add_to_pq(ent); + spin_unlock(&queue->lock); + + io_uring_cmd_done(cmd, ret, issue_flags); +} + /* FUSE_URING_CMD_COMMIT_AND_FETCH handler */ static int fuse_uring_commit_fetch(struct io_uring_cmd *cmd, int issue_flags, struct fuse_chan *fch) @@ -966,7 +956,8 @@ static int fuse_uring_commit_fetch(struct io_uring_cmd *cmd, int issue_flags, * and fetching is done in one step vs legacy fuse, which has separated * read (fetch request) and write (commit result). */ - fuse_uring_next_fuse_req(ent, queue, issue_flags); + if (fuse_uring_get_next_fuse_req(ent, queue)) + fuse_uring_send(ent, cmd, 0, issue_flags); return 0; } @@ -1225,21 +1216,6 @@ int fuse_uring_cmd(struct io_uring_cmd *cmd, unsigned int issue_flags) return -EIOCBQUEUED; } -static void fuse_uring_send(struct fuse_ring_ent *ent, struct io_uring_cmd *cmd, - ssize_t ret, unsigned int issue_flags) -{ - struct fuse_ring_queue *queue = ent->queue; - - spin_lock(&queue->lock); - ent->state = FRRS_USERSPACE; - list_move_tail(&ent->list, &queue->ent_in_userspace); - ent->cmd = NULL; - fuse_uring_add_to_pq(ent); - spin_unlock(&queue->lock); - - io_uring_cmd_done(cmd, ret, issue_flags); -} - /* * This prepares and sends the ring request in fuse-uring task context. * User buffers are not mapped yet - the application does not have permission @@ -1256,8 +1232,9 @@ static void fuse_uring_send_in_task(struct io_tw_req tw_req, io_tw_token_t tw) if (!tw.cancel) { err = fuse_uring_prepare_send(ent, ent->fuse_req); if (err) { - fuse_uring_next_fuse_req(ent, queue, issue_flags); - return; + if (!fuse_uring_get_next_fuse_req(ent, queue)) + return; + err = 0; } fuse_uring_send(ent, cmd, err, issue_flags); } else { -- cgit v1.2.3 From 6582f8a06698403dccf8a01b7eef176b2c6dd7ff Mon Sep 17 00:00:00 2001 From: Joanne Koong Date: Fri, 12 Jun 2026 14:05:05 -0700 Subject: fuse-uring: refactor io-uring header copying to ring Move header copying to ring logic into a new copy_header_to_ring() function. This makes the copy_to_user() logic more clear and centralizes error handling / rate-limited logging. Reviewed-by: Bernd Schubert Reviewed-by: Jeff Layton Reviewed-by: Baokun Li Signed-off-by: Joanne Koong Signed-off-by: Miklos Szeredi --- fs/fuse/dev_uring.c | 39 +++++++++++++++++++++------------------ 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c index 0b66fe3a5da0..1d37a2ee5532 100644 --- a/fs/fuse/dev_uring.c +++ b/fs/fuse/dev_uring.c @@ -578,6 +578,18 @@ err: return err; } +static __always_inline int copy_header_to_ring(void __user *ring, + const void *header, + size_t header_size) +{ + if (copy_to_user(ring, header, header_size)) { + pr_info_ratelimited("Copying header to ring failed.\n"); + return -EFAULT; + } + + return 0; +} + static int fuse_uring_copy_from_ring(struct fuse_ring *ring, struct fuse_req *req, struct fuse_ring_ent *ent) @@ -640,13 +652,11 @@ static int fuse_uring_args_to_ring(struct fuse_ring *ring, struct fuse_req *req, * Some op code have that as zero size. */ if (args->in_args[0].size > 0) { - err = copy_to_user(&ent->headers->op_in, in_args->value, - in_args->size); - if (err) { - pr_info_ratelimited( - "Copying the header failed.\n"); - return -EFAULT; - } + err = copy_header_to_ring(&ent->headers->op_in, + in_args->value, + in_args->size); + if (err) + return err; } in_args++; num_args--; @@ -662,9 +672,8 @@ static int fuse_uring_args_to_ring(struct fuse_ring *ring, struct fuse_req *req, } ent_in_out.payload_sz = cs.ring.copied_sz; - err = copy_to_user(&ent->headers->ring_ent_in_out, &ent_in_out, - sizeof(ent_in_out)); - return err ? -EFAULT : 0; + return copy_header_to_ring(&ent->headers->ring_ent_in_out, &ent_in_out, + sizeof(ent_in_out)); } static int fuse_uring_copy_to_ring(struct fuse_ring_ent *ent, @@ -693,14 +702,8 @@ static int fuse_uring_copy_to_ring(struct fuse_ring_ent *ent, } /* copy fuse_in_header */ - err = copy_to_user(&ent->headers->in_out, &req->in.h, - sizeof(req->in.h)); - if (err) { - err = -EFAULT; - return err; - } - - return 0; + return copy_header_to_ring(&ent->headers->in_out, &req->in.h, + sizeof(req->in.h)); } static int fuse_uring_prepare_send(struct fuse_ring_ent *ent, -- cgit v1.2.3 From ba7d47897fd895533c19af436ca7fc4f6b171238 Mon Sep 17 00:00:00 2001 From: Joanne Koong Date: Fri, 12 Jun 2026 14:05:06 -0700 Subject: fuse-uring: refactor io-uring header copying from ring Move header copying from ring logic into a new copy_header_from_ring() function. This makes the copy_from_user() logic more clear and centralizes error handling / rate-limited logging. Reviewed-by: Bernd Schubert Reviewed-by: Jeff Layton Reviewed-by: Baokun Li Signed-off-by: Joanne Koong Signed-off-by: Miklos Szeredi --- fs/fuse/dev_uring.c | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c index 1d37a2ee5532..42627838cfa6 100644 --- a/fs/fuse/dev_uring.c +++ b/fs/fuse/dev_uring.c @@ -590,6 +590,18 @@ static __always_inline int copy_header_to_ring(void __user *ring, return 0; } +static __always_inline int copy_header_from_ring(void *header, + const void __user *ring, + size_t header_size) +{ + if (copy_from_user(header, ring, header_size)) { + pr_info_ratelimited("Copying header from ring failed.\n"); + return -EFAULT; + } + + return 0; +} + static int fuse_uring_copy_from_ring(struct fuse_ring *ring, struct fuse_req *req, struct fuse_ring_ent *ent) @@ -600,10 +612,10 @@ static int fuse_uring_copy_from_ring(struct fuse_ring *ring, int err; struct fuse_uring_ent_in_out ring_in_out; - err = copy_from_user(&ring_in_out, &ent->headers->ring_ent_in_out, - sizeof(ring_in_out)); + err = copy_header_from_ring(&ring_in_out, &ent->headers->ring_ent_in_out, + sizeof(ring_in_out)); if (err) - return -EFAULT; + return err; err = import_ubuf(ITER_SOURCE, ent->payload, ring->max_payload_sz, &iter); @@ -810,8 +822,8 @@ static void fuse_uring_commit(struct fuse_ring_ent *ent, struct fuse_req *req, struct fuse_ring *ring = ent->queue->ring; ssize_t err = -EFAULT; - if (copy_from_user(&req->out.h, &ent->headers->in_out, - sizeof(req->out.h))) + if (copy_header_from_ring(&req->out.h, &ent->headers->in_out, + sizeof(req->out.h))) goto out; err = fuse_uring_out_header_has_err(&req->out.h, req); -- cgit v1.2.3 From b2bbd7dcd2433e29b7e9a726aaa9571a78fa8d5f Mon Sep 17 00:00:00 2001 From: Joanne Koong Date: Fri, 12 Jun 2026 14:05:07 -0700 Subject: fuse-uring: use enum types for header copying Use enum types to identify which part of the header needs to be copied. This improves the interface and will simplify both kernel-space and user-space header addresses copying when buffer rings are added. Reviewed-by: Bernd Schubert Reviewed-by: Jeff Layton Reviewed-by: Baokun Li Signed-off-by: Joanne Koong Signed-off-by: Miklos Szeredi --- fs/fuse/dev_uring.c | 66 ++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 53 insertions(+), 13 deletions(-) diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c index 42627838cfa6..58e19e8f537c 100644 --- a/fs/fuse/dev_uring.c +++ b/fs/fuse/dev_uring.c @@ -31,6 +31,15 @@ struct fuse_uring_pdu { static const struct fuse_iqueue_ops fuse_io_uring_ops; +enum fuse_uring_header_type { + /* struct fuse_in_header / struct fuse_out_header */ + FUSE_URING_HEADER_IN_OUT, + /* per op code header */ + FUSE_URING_HEADER_OP, + /* struct fuse_uring_ent_in_out header */ + FUSE_URING_HEADER_RING_ENT, +}; + static void uring_cmd_set_ring_ent(struct io_uring_cmd *cmd, struct fuse_ring_ent *ring_ent) { @@ -578,10 +587,33 @@ err: return err; } -static __always_inline int copy_header_to_ring(void __user *ring, - const void *header, - size_t header_size) +static int ring_header_type_offset(enum fuse_uring_header_type type) { + switch (type) { + case FUSE_URING_HEADER_IN_OUT: + return 0; + case FUSE_URING_HEADER_OP: + return offsetof(struct fuse_uring_req_header, op_in); + case FUSE_URING_HEADER_RING_ENT: + return offsetof(struct fuse_uring_req_header, ring_ent_in_out); + default: + WARN_ONCE(1, "Invalid header type: %d\n", type); + return -EINVAL; + } +} + +static int copy_header_to_ring(struct fuse_ring_ent *ent, + enum fuse_uring_header_type type, + const void *header, size_t header_size) +{ + int offset = ring_header_type_offset(type); + void __user *ring; + + if (offset < 0) + return offset; + + ring = (void __user *)ent->headers + offset; + if (copy_to_user(ring, header, header_size)) { pr_info_ratelimited("Copying header to ring failed.\n"); return -EFAULT; @@ -590,10 +622,18 @@ static __always_inline int copy_header_to_ring(void __user *ring, return 0; } -static __always_inline int copy_header_from_ring(void *header, - const void __user *ring, - size_t header_size) +static int copy_header_from_ring(struct fuse_ring_ent *ent, + enum fuse_uring_header_type type, void *header, + size_t header_size) { + int offset = ring_header_type_offset(type); + const void __user *ring; + + if (offset < 0) + return offset; + + ring = (void __user *)ent->headers + offset; + if (copy_from_user(header, ring, header_size)) { pr_info_ratelimited("Copying header from ring failed.\n"); return -EFAULT; @@ -612,8 +652,8 @@ static int fuse_uring_copy_from_ring(struct fuse_ring *ring, int err; struct fuse_uring_ent_in_out ring_in_out; - err = copy_header_from_ring(&ring_in_out, &ent->headers->ring_ent_in_out, - sizeof(ring_in_out)); + err = copy_header_from_ring(ent, FUSE_URING_HEADER_RING_ENT, + &ring_in_out, sizeof(ring_in_out)); if (err) return err; @@ -664,7 +704,7 @@ static int fuse_uring_args_to_ring(struct fuse_ring *ring, struct fuse_req *req, * Some op code have that as zero size. */ if (args->in_args[0].size > 0) { - err = copy_header_to_ring(&ent->headers->op_in, + err = copy_header_to_ring(ent, FUSE_URING_HEADER_OP, in_args->value, in_args->size); if (err) @@ -684,8 +724,8 @@ static int fuse_uring_args_to_ring(struct fuse_ring *ring, struct fuse_req *req, } ent_in_out.payload_sz = cs.ring.copied_sz; - return copy_header_to_ring(&ent->headers->ring_ent_in_out, &ent_in_out, - sizeof(ent_in_out)); + return copy_header_to_ring(ent, FUSE_URING_HEADER_RING_ENT, + &ent_in_out, sizeof(ent_in_out)); } static int fuse_uring_copy_to_ring(struct fuse_ring_ent *ent, @@ -714,7 +754,7 @@ static int fuse_uring_copy_to_ring(struct fuse_ring_ent *ent, } /* copy fuse_in_header */ - return copy_header_to_ring(&ent->headers->in_out, &req->in.h, + return copy_header_to_ring(ent, FUSE_URING_HEADER_IN_OUT, &req->in.h, sizeof(req->in.h)); } @@ -822,7 +862,7 @@ static void fuse_uring_commit(struct fuse_ring_ent *ent, struct fuse_req *req, struct fuse_ring *ring = ent->queue->ring; ssize_t err = -EFAULT; - if (copy_header_from_ring(&req->out.h, &ent->headers->in_out, + if (copy_header_from_ring(ent, FUSE_URING_HEADER_IN_OUT, &req->out.h, sizeof(req->out.h))) goto out; -- cgit v1.2.3 From c0f9203732fc70de8d20697270bfe405481eac14 Mon Sep 17 00:00:00 2001 From: Joanne Koong Date: Fri, 12 Jun 2026 14:05:08 -0700 Subject: fuse-uring: refactor setting up copy state for payload copying Add a new helper function setup_fuse_copy_state() to contain the logic for setting up the copy state for payload copying. Reviewed-by: Bernd Schubert Reviewed-by: Jeff Layton Reviewed-by: Baokun Li Signed-off-by: Joanne Koong Signed-off-by: Miklos Szeredi --- fs/fuse/dev_uring.c | 38 ++++++++++++++++++++++++-------------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c index 58e19e8f537c..0867799e1a3f 100644 --- a/fs/fuse/dev_uring.c +++ b/fs/fuse/dev_uring.c @@ -642,6 +642,27 @@ static int copy_header_from_ring(struct fuse_ring_ent *ent, return 0; } +static int setup_fuse_copy_state(struct fuse_copy_state *cs, + struct fuse_ring *ring, struct fuse_req *req, + struct fuse_ring_ent *ent, int dir, + struct iov_iter *iter) +{ + int err; + + err = import_ubuf(dir, ent->payload, ring->max_payload_sz, iter); + if (err) { + pr_info_ratelimited("fuse: Import of user buffer failed\n"); + return err; + } + + fuse_copy_init(cs, dir == ITER_DEST, iter); + + cs->is_uring = true; + cs->req = req; + + return 0; +} + static int fuse_uring_copy_from_ring(struct fuse_ring *ring, struct fuse_req *req, struct fuse_ring_ent *ent) @@ -657,15 +678,10 @@ static int fuse_uring_copy_from_ring(struct fuse_ring *ring, if (err) return err; - err = import_ubuf(ITER_SOURCE, ent->payload, ring->max_payload_sz, - &iter); + err = setup_fuse_copy_state(&cs, ring, req, ent, ITER_SOURCE, &iter); if (err) return err; - fuse_copy_init(&cs, false, &iter); - cs.is_uring = true; - cs.req = req; - err = fuse_copy_out_args(&cs, args, ring_in_out.payload_sz); fuse_copy_finish(&cs); return err; @@ -688,15 +704,9 @@ static int fuse_uring_args_to_ring(struct fuse_ring *ring, struct fuse_req *req, .commit_id = req->in.h.unique, }; - err = import_ubuf(ITER_DEST, ent->payload, ring->max_payload_sz, &iter); - if (err) { - pr_info_ratelimited("fuse: Import of user buffer failed\n"); + err = setup_fuse_copy_state(&cs, ring, req, ent, ITER_DEST, &iter); + if (err) return err; - } - - fuse_copy_init(&cs, true, &iter); - cs.is_uring = true; - cs.req = req; if (num_args > 0) { /* -- cgit v1.2.3 From 8bbb2ad1f687633a991839bd3efae04ccfb29e19 Mon Sep 17 00:00:00 2001 From: Joanne Koong Date: Fri, 12 Jun 2026 14:05:09 -0700 Subject: fuse-uring: use named constants for io-uring iovec indices Replace magic indices 0 and 1 for the iovec array with named constants FUSE_URING_IOV_HEADERS and FUSE_URING_IOV_PAYLOAD. This makes the usages self-documenting and prepares for buffer ring support which will also reference these iovec slots by index. Reviewed-by: Bernd Schubert Reviewed-by: Jeff Layton Reviewed-by: Baokun Li Signed-off-by: Joanne Koong Signed-off-by: Miklos Szeredi --- fs/fuse/dev_uring.c | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c index 0867799e1a3f..ba5edf5d01b3 100644 --- a/fs/fuse/dev_uring.c +++ b/fs/fuse/dev_uring.c @@ -18,7 +18,8 @@ MODULE_PARM_DESC(enable_uring, "Enable userspace communication through io-uring"); #define FUSE_URING_IOV_SEGS 2 /* header and payload */ - +#define FUSE_URING_IOV_HEADERS 0 +#define FUSE_URING_IOV_PAYLOAD 1 bool fuse_uring_enabled(void) { @@ -1094,8 +1095,8 @@ static int fuse_uring_do_register(struct fuse_ring_ent *ent, } /* - * sqe->addr is a ptr to an iovec array, iov[0] has the headers, iov[1] - * the payload + * sqe->addr is a ptr to an iovec array, iov[FUSE_URING_IOV_HEADERS] has the + * headers, iov[FUSE_URING_IOV_PAYLOAD] the payload */ static int fuse_uring_get_iovec_from_sqe(const struct io_uring_sqe *sqe, struct iovec iov[FUSE_URING_IOV_SEGS]) @@ -1125,8 +1126,8 @@ fuse_uring_create_ring_ent(struct io_uring_cmd *cmd, { struct fuse_ring *ring = queue->ring; struct fuse_ring_ent *ent; - size_t payload_size; struct iovec iov[FUSE_URING_IOV_SEGS]; + struct iovec *headers, *payload; int err; err = fuse_uring_get_iovec_from_sqe(cmd->sqe, iov); @@ -1137,15 +1138,16 @@ fuse_uring_create_ring_ent(struct io_uring_cmd *cmd, } err = -EINVAL; - if (iov[0].iov_len < sizeof(struct fuse_uring_req_header)) { - pr_info_ratelimited("Invalid header len %zu\n", iov[0].iov_len); + headers = &iov[FUSE_URING_IOV_HEADERS]; + if (headers->iov_len < sizeof(struct fuse_uring_req_header)) { + pr_info_ratelimited("Invalid header len %zu\n", headers->iov_len); return ERR_PTR(err); } - payload_size = iov[1].iov_len; - if (payload_size < ring->max_payload_sz) { + payload = &iov[FUSE_URING_IOV_PAYLOAD]; + if (payload->iov_len < ring->max_payload_sz) { pr_info_ratelimited("Invalid req payload len %zu\n", - payload_size); + payload->iov_len); return ERR_PTR(err); } @@ -1157,8 +1159,8 @@ fuse_uring_create_ring_ent(struct io_uring_cmd *cmd, INIT_LIST_HEAD(&ent->list); ent->queue = queue; - ent->headers = iov[0].iov_base; - ent->payload = iov[1].iov_base; + ent->headers = headers->iov_base; + ent->payload = payload->iov_base; atomic_inc(&ring->queue_refs); return ent; -- cgit v1.2.3 From 7d87a5a284bb34edb3f4e7e312ef403b3385a7b7 Mon Sep 17 00:00:00 2001 From: Zhenghang Xiao Date: Mon, 15 Jun 2026 12:25:56 +0200 Subject: fuse-uring: clear ent->fuse_req in commit_fetch error path fuse_uring_commit_fetch() error path called fuse_request_end(req) without clearing ent->fuse_req when fuse_ring_ent_set_commit() fails. The still-pending fuse_uring_send_in_task() task-work later dereferences the dangling pointer through fuse_uring_prepare_send(), causing a use-after-free. End the request with fuse_uring_req_end(), which handles all conditions already. Annotation/edition by Bernd: The UAF should be fixed by other means already and actually has to be avoided that way. Just checking for ent->fuse_req == NULL in fuse_uring_send_in_task() would be prone to race conditions, because if malicious userspace would commit requests that have passed the NULL check, but are in doing args copy, it would still trigger a use-after-free. Setting ent->fuse_req = NULL in fuse_uring_commit_fetch() still makes sense, though. Reported-by: Shuvam Pandey Reported-by: Berkant Koc Signed-off-by: Zhenghang Xiao Signed-off-by: Bernd Schubert Reviewed-by: Joanne Koong Signed-off-by: Miklos Szeredi --- fs/fuse/dev_uring.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c index ba5edf5d01b3..77c8cec43d9c 100644 --- a/fs/fuse/dev_uring.c +++ b/fs/fuse/dev_uring.c @@ -1003,9 +1003,7 @@ static int fuse_uring_commit_fetch(struct io_uring_cmd *cmd, int issue_flags, pr_info_ratelimited("qid=%d commit_id %llu state %d", queue->qid, commit_id, ent->state); spin_unlock(&queue->lock); - req->out.h.error = err; - clear_bit(FR_SENT, &req->flags); - fuse_request_end(req); + fuse_uring_req_end(ent, req, err); return err; } -- cgit v1.2.3