fuse: add support for copy_file_range()
There are several FUSE filesystems that can implement server-side copy or other efficient copy/duplication/clone methods. The copy_file_range() syscall is the standard interface that users have access to while not depending on external libraries that bypass FUSE. Signed-off-by: Niels de Vos <ndevos@redhat.com> Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
This commit is contained in:
		
							parent
							
								
									908a572b80
								
							
						
					
					
						commit
						88bc7d5097
					
				| @ -3011,6 +3011,82 @@ out: | ||||
| 	return err; | ||||
| } | ||||
| 
 | ||||
| static ssize_t fuse_copy_file_range(struct file *file_in, loff_t pos_in, | ||||
| 				    struct file *file_out, loff_t pos_out, | ||||
| 				    size_t len, unsigned int flags) | ||||
| { | ||||
| 	struct fuse_file *ff_in = file_in->private_data; | ||||
| 	struct fuse_file *ff_out = file_out->private_data; | ||||
| 	struct inode *inode_out = file_inode(file_out); | ||||
| 	struct fuse_inode *fi_out = get_fuse_inode(inode_out); | ||||
| 	struct fuse_conn *fc = ff_in->fc; | ||||
| 	FUSE_ARGS(args); | ||||
| 	struct fuse_copy_file_range_in inarg = { | ||||
| 		.fh_in = ff_in->fh, | ||||
| 		.off_in = pos_in, | ||||
| 		.nodeid_out = ff_out->nodeid, | ||||
| 		.fh_out = ff_out->fh, | ||||
| 		.off_out = pos_out, | ||||
| 		.len = len, | ||||
| 		.flags = flags | ||||
| 	}; | ||||
| 	struct fuse_write_out outarg; | ||||
| 	ssize_t err; | ||||
| 	/* mark unstable when write-back is not used, and file_out gets
 | ||||
| 	 * extended */ | ||||
| 	bool is_unstable = (!fc->writeback_cache) && | ||||
| 			   ((pos_out + len) > inode_out->i_size); | ||||
| 
 | ||||
| 	if (fc->no_copy_file_range) | ||||
| 		return -EOPNOTSUPP; | ||||
| 
 | ||||
| 	inode_lock(inode_out); | ||||
| 
 | ||||
| 	if (fc->writeback_cache) { | ||||
| 		err = filemap_write_and_wait_range(inode_out->i_mapping, | ||||
| 						   pos_out, pos_out + len); | ||||
| 		if (err) | ||||
| 			goto out; | ||||
| 
 | ||||
| 		fuse_sync_writes(inode_out); | ||||
| 	} | ||||
| 
 | ||||
| 	if (is_unstable) | ||||
| 		set_bit(FUSE_I_SIZE_UNSTABLE, &fi_out->state); | ||||
| 
 | ||||
| 	args.in.h.opcode = FUSE_COPY_FILE_RANGE; | ||||
| 	args.in.h.nodeid = ff_in->nodeid; | ||||
| 	args.in.numargs = 1; | ||||
| 	args.in.args[0].size = sizeof(inarg); | ||||
| 	args.in.args[0].value = &inarg; | ||||
| 	args.out.numargs = 1; | ||||
| 	args.out.args[0].size = sizeof(outarg); | ||||
| 	args.out.args[0].value = &outarg; | ||||
| 	err = fuse_simple_request(fc, &args); | ||||
| 	if (err == -ENOSYS) { | ||||
| 		fc->no_copy_file_range = 1; | ||||
| 		err = -EOPNOTSUPP; | ||||
| 	} | ||||
| 	if (err) | ||||
| 		goto out; | ||||
| 
 | ||||
| 	if (fc->writeback_cache) { | ||||
| 		fuse_write_update_size(inode_out, pos_out + outarg.size); | ||||
| 		file_update_time(file_out); | ||||
| 	} | ||||
| 
 | ||||
| 	fuse_invalidate_attr(inode_out); | ||||
| 
 | ||||
| 	err = outarg.size; | ||||
| out: | ||||
| 	if (is_unstable) | ||||
| 		clear_bit(FUSE_I_SIZE_UNSTABLE, &fi_out->state); | ||||
| 
 | ||||
| 	inode_unlock(inode_out); | ||||
| 
 | ||||
| 	return err; | ||||
| } | ||||
| 
 | ||||
| static const struct file_operations fuse_file_operations = { | ||||
| 	.llseek		= fuse_file_llseek, | ||||
| 	.read_iter	= fuse_file_read_iter, | ||||
| @ -3027,6 +3103,7 @@ static const struct file_operations fuse_file_operations = { | ||||
| 	.compat_ioctl	= fuse_file_compat_ioctl, | ||||
| 	.poll		= fuse_file_poll, | ||||
| 	.fallocate	= fuse_file_fallocate, | ||||
| 	.copy_file_range = fuse_copy_file_range, | ||||
| }; | ||||
| 
 | ||||
| static const struct file_operations fuse_direct_io_file_operations = { | ||||
|  | ||||
| @ -637,6 +637,9 @@ struct fuse_conn { | ||||
| 	/** Allow other than the mounter user to access the filesystem ? */ | ||||
| 	unsigned allow_other:1; | ||||
| 
 | ||||
| 	/** Does the filesystem support copy_file_range? */ | ||||
| 	unsigned no_copy_file_range:1; | ||||
| 
 | ||||
| 	/** The number of requests waiting for completion */ | ||||
| 	atomic_t num_waiting; | ||||
| 
 | ||||
|  | ||||
| @ -116,6 +116,9 @@ | ||||
|  * | ||||
|  *  7.27 | ||||
|  *  - add FUSE_ABORT_ERROR | ||||
|  * | ||||
|  *  7.28 | ||||
|  *  - add FUSE_COPY_FILE_RANGE | ||||
|  */ | ||||
| 
 | ||||
| #ifndef _LINUX_FUSE_H | ||||
| @ -151,7 +154,7 @@ | ||||
| #define FUSE_KERNEL_VERSION 7 | ||||
| 
 | ||||
| /** Minor version number of this interface */ | ||||
| #define FUSE_KERNEL_MINOR_VERSION 27 | ||||
| #define FUSE_KERNEL_MINOR_VERSION 28 | ||||
| 
 | ||||
| /** The node ID of the root inode */ | ||||
| #define FUSE_ROOT_ID 1 | ||||
| @ -381,6 +384,7 @@ enum fuse_opcode { | ||||
| 	FUSE_READDIRPLUS	= 44, | ||||
| 	FUSE_RENAME2		= 45, | ||||
| 	FUSE_LSEEK		= 46, | ||||
| 	FUSE_COPY_FILE_RANGE	= 47, | ||||
| 
 | ||||
| 	/* CUSE specific operations */ | ||||
| 	CUSE_INIT		= 4096, | ||||
| @ -792,4 +796,14 @@ struct fuse_lseek_out { | ||||
| 	uint64_t	offset; | ||||
| }; | ||||
| 
 | ||||
| struct fuse_copy_file_range_in { | ||||
| 	uint64_t	fh_in; | ||||
| 	uint64_t	off_in; | ||||
| 	uint64_t	nodeid_out; | ||||
| 	uint64_t	fh_out; | ||||
| 	uint64_t	off_out; | ||||
| 	uint64_t	len; | ||||
| 	uint64_t	flags; | ||||
| }; | ||||
| 
 | ||||
| #endif /* _LINUX_FUSE_H */ | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user