446 lines · 9.6 KB · C
RawDownload
// 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;	}}