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