A splice() into /dev/fuse with SPLICE_F_MOVE lets the reply to a read request steal full pages from the pipe. Each stolen page replaces, in the fuse file's page cache, the folio that the request allocated. The page can be a page cache folio of another file, a page written into the pipe, or a gifted user page.
Always copy. The server sees the same data, and the move was a hint that splice() never promised to honour. Keep advertising FUSE_SPLICE_MOVE. libfuse fails the mount of a filesystem that asks for a capability the kernel does not offer. Preparation for removing ->try_steal() from pipe buffers. Suggested-by: David Hildenbrand (Arm) <[email protected]> Suggested-by: Linus Torvalds <[email protected]> Signed-off-by: Kiryl Shutsemau (Meta) <[email protected]> --- fs/fuse/args.h | 1 - fs/fuse/dev.c | 144 +------------------------------------------ fs/fuse/file.c | 1 - fs/fuse/fuse_dev_i.h | 1 - 4 files changed, 3 insertions(+), 144 deletions(-) diff --git a/fs/fuse/args.h b/fs/fuse/args.h index 5173264a1261..36ef0c6d5999 100644 --- a/fs/fuse/args.h +++ b/fs/fuse/args.h @@ -36,7 +36,6 @@ struct fuse_args { 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; diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index 3641abb90d6b..e7238eed21fb 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -1099,128 +1099,6 @@ static int fuse_copy_do(struct fuse_copy_state *cs, void **val, unsigned *size) return ncpy; } -static int fuse_check_folio(struct folio *folio) -{ - if (folio_mapped(folio) || - folio->mapping != NULL || - (folio->flags.f & PAGE_FLAGS_CHECK_AT_PREP & - ~(1 << PG_locked | - 1 << PG_referenced | - 1 << PG_lru | - 1 << PG_active | - 1 << PG_workingset | - 1 << PG_reclaim | - 1 << PG_waiters | - LRU_GEN_MASK | LRU_REFS_MASK))) { - dump_page(&folio->page, "fuse: trying to steal weird page"); - return 1; - } - return 0; -} - -/* - * Attempt to steal a page from the splice() pipe and move it into the - * pagecache. If successful, the pointer in @pagep will be updated. The - * folio that was originally in @pagep will lose a reference and the new - * folio returned in @pagep will carry a reference. - */ -static int fuse_try_move_folio(struct fuse_copy_state *cs, struct folio **foliop) -{ - int err; - struct folio *oldfolio = *foliop; - struct folio *newfolio; - struct pipe_buffer *buf = cs->pipebufs; - - folio_get(oldfolio); - err = unlock_request(cs->req); - if (err) - goto out_put_old; - - fuse_copy_finish(cs); - - err = pipe_buf_confirm(cs->pipe, buf); - if (err) - goto out_put_old; - - BUG_ON(!cs->nr_segs); - cs->currbuf = buf; - cs->len = buf->len; - cs->pipebufs++; - cs->nr_segs--; - - if (cs->len != folio_size(oldfolio)) - goto out_fallback; - - newfolio = page_folio(buf->page); - - if (folio_test_large(newfolio)) - goto out_fallback; - - /* - * This is a new and locked page, it shouldn't be mapped or - * have any special flags on it - */ - if (WARN_ON(folio_mapped(oldfolio))) - goto out_fallback; - if (WARN_ON(folio_has_private(oldfolio))) - goto out_fallback; - if (WARN_ON(folio_test_dirty(oldfolio) || - folio_test_writeback(oldfolio))) - goto out_fallback; - if (WARN_ON(folio_test_mlocked(oldfolio))) - goto out_fallback; - - if (!pipe_buf_try_steal(cs->pipe, buf)) - goto out_fallback; - - folio_clear_uptodate(newfolio); - folio_clear_mappedtodisk(newfolio); - - if (fuse_check_folio(newfolio) != 0) - goto out_fallback_unlock; - - err = lock_request(cs->req); - if (err) - goto out_fallback_unlock; - - replace_page_cache_folio(oldfolio, newfolio); - - folio_get(newfolio); - - if (!(buf->flags & PIPE_BUF_FLAG_LRU)) - folio_add_lru(newfolio); - - /* - * Release while we have extra ref on stolen page. Otherwise - * anon_pipe_buf_release() might think the page can be reused. - */ - pipe_buf_release(cs->pipe, buf); - - *foliop = newfolio; - folio_unlock(oldfolio); - /* Drop ref for ap->pages[] array */ - folio_put(oldfolio); - cs->len = 0; - - err = 0; -out_put_old: - /* Drop ref obtained in this function */ - folio_put(oldfolio); - return err; - -out_fallback_unlock: - folio_unlock(newfolio); -out_fallback: - cs->pg = buf->page; - cs->offset = buf->offset; - - err = lock_request(cs->req); - if (!err) - err = 1; - - goto out_put_old; -} - static int fuse_ref_folio(struct fuse_copy_state *cs, struct folio *folio, unsigned offset, unsigned count) { @@ -1275,16 +1153,9 @@ int fuse_copy_folio(struct fuse_copy_state *cs, struct folio **foliop, return fuse_ref_folio(cs, folio, offset, count); } } else if (!cs->len) { - if (cs->move_folios && folio && - offset == 0 && count == folio_size(folio)) { - err = fuse_try_move_folio(cs, foliop); - if (err <= 0) - return err; - } else { - err = fuse_copy_fill(cs); - if (err) - return err; - } + err = fuse_copy_fill(cs); + if (err) + return err; } if (folio) { void *mapaddr = kmap_local_folio(folio, offset); @@ -1920,9 +1791,6 @@ static ssize_t fuse_dev_do_write(struct fuse_dev *fud, if (!smp_load_acquire(&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; } @@ -1966,8 +1834,6 @@ static ssize_t fuse_dev_do_write(struct fuse_dev *fud, set_bit(FR_LOCKED, &req->flags); spin_unlock(&fpq->lock); cs->req = req; - if (!req->args->page_replace) - cs->move_folios = false; if (oh.error) err = nbytes != sizeof(oh) ? -EINVAL : 0; @@ -2068,7 +1934,6 @@ static ssize_t fuse_dev_splice_write(struct pipe_inode_info *pipe, goto out_free; *obuf = *ibuf; - obuf->flags &= ~PIPE_BUF_FLAG_GIFT; obuf->len = rem; ibuf->offset += obuf->len; ibuf->len -= obuf->len; @@ -2083,9 +1948,6 @@ static ssize_t fuse_dev_splice_write(struct pipe_inode_info *pipe, cs.nr_segs = nbuf; cs.pipe = pipe; - if (flags & SPLICE_F_MOVE) - cs.move_folios = true; - ret = fuse_dev_do_write(fud, &cs, len); pipe_lock(pipe); diff --git a/fs/fuse/file.c b/fs/fuse/file.c index d5e1cb53d86a..ab955e4c3e5d 100644 --- a/fs/fuse/file.c +++ b/fs/fuse/file.c @@ -1096,7 +1096,6 @@ static void fuse_send_readpages(struct fuse_io_args *ia, struct file *file, ap->args.out_pages = true; ap->args.page_zeroing = true; - ap->args.page_replace = true; /* * Don't overflow end offset. diff --git a/fs/fuse/fuse_dev_i.h b/fs/fuse/fuse_dev_i.h index 4b412a76225f..a90ce26ded31 100644 --- a/fs/fuse/fuse_dev_i.h +++ b/fs/fuse/fuse_dev_i.h @@ -326,7 +326,6 @@ struct fuse_copy_state { unsigned int len; unsigned int offset; bool write:1; - bool move_folios:1; bool is_uring:1; /* set when the payload is zero-copied. folios are filled in place */ bool skip_folio_copy:1; -- 2.54.0
