mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-08-31 09:20:13 -04:00
When fuse enables large folios, a large folio will be backed by iomap_folio_state that keeps track of uptodate and dirty state in an internal bitmap. Fuse writethrough and notify store paths currently set folio uptodate state with folio_mark_uptodate(), which touches only the folio-level flag, but on an iomap-backed folio, that leaves the uptodate bitmap out of sync. Use the iomap_folio_mark_uptodate() helper to update both the folio uptodate state and the iomap uptodate bitmap. Reviewed-by: Darrick J. Wong <djwong@kernel.org> Signed-off-by: Joanne Koong <joannelkoong@gmail.com> Link: https://patch.msgid.link/20260707220450.1200943-4-joannelkoong@gmail.com Acked-by: Miklos Szeredi <mszeredi@redhat.com> Reviewed-by: Christoph Hellwig <hch@lst.de> Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
447 lines
9.6 KiB
C
447 lines
9.6 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
|
|
#include "dev.h"
|
|
#include "fuse_i.h"
|
|
|
|
#include <linux/iomap.h>
|
|
#include <linux/pagemap.h>
|
|
|
|
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;
|
|
if (!S_ISREG(inode->i_mode)) {
|
|
err = -EINVAL;
|
|
goto out_iput;
|
|
}
|
|
|
|
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));
|
|
iomap_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;
|
|
if (!folio_test_uptodate(folio)) {
|
|
folio_put(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 = -EINVAL;
|
|
if (S_ISREG(inode->i_mode))
|
|
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 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)
|
|
{
|
|
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;
|
|
}
|
|
}
|