// 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; }}
// 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;
}
}