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 <nde...@redhat.com>

---
libfuse: https://github.com/libfuse/libfuse/pull/259

v2: return ssize_t instead of long
v3: add nodeid_out to fuse_copy_file_range_in for libfuse expectations
v4: use fuse_write_out as response, correct write-back handling
---
 fs/fuse/file.c            |  80 +++++++++++++++++++++++++++++
 fs/fuse/fuse_i.h          |   3 ++
 include/uapi/linux/fuse.h | 104 +++++++++++++++++++++-----------------
 3 files changed, 142 insertions(+), 45 deletions(-)

diff --git a/fs/fuse/file.c b/fs/fuse/file.c
index a201fb0ac64f..6aea3675fd88 100644
--- a/fs/fuse/file.c
+++ b/fs/fuse/file.c
@@ -3009,6 +3009,85 @@ static long fuse_file_fallocate(struct file *file, int 
mode, loff_t offset,
        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) {
+               bool changed = fuse_write_update_size(inode_out,
+                                                     pos_out + outarg.size);
+
+               if (changed)
+                       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,
@@ -3025,6 +3104,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 = {
diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
index 5256ad333b05..ea848bb7d9e2 100644
--- a/fs/fuse/fuse_i.h
+++ b/fs/fuse/fuse_i.h
@@ -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;
 
diff --git a/include/uapi/linux/fuse.h b/include/uapi/linux/fuse.h
index 92fa24c24c92..c806a17beaef 100644
--- a/include/uapi/linux/fuse.h
+++ b/include/uapi/linux/fuse.h
@@ -116,6 +116,9 @@
  *
  *  7.27
  *  - add FUSE_ABORT_ERROR
+ *
+ *  7.28
+ *  - add FUSE_COPY_FILE_RANGE
  */
 
 #ifndef _LINUX_FUSE_H
@@ -337,53 +340,54 @@ struct fuse_file_lock {
 #define FUSE_POLL_SCHEDULE_NOTIFY (1 << 0)
 
 enum fuse_opcode {
-       FUSE_LOOKUP        = 1,
-       FUSE_FORGET        = 2,  /* no reply */
-       FUSE_GETATTR       = 3,
-       FUSE_SETATTR       = 4,
-       FUSE_READLINK      = 5,
-       FUSE_SYMLINK       = 6,
-       FUSE_MKNOD         = 8,
-       FUSE_MKDIR         = 9,
-       FUSE_UNLINK        = 10,
-       FUSE_RMDIR         = 11,
-       FUSE_RENAME        = 12,
-       FUSE_LINK          = 13,
-       FUSE_OPEN          = 14,
-       FUSE_READ          = 15,
-       FUSE_WRITE         = 16,
-       FUSE_STATFS        = 17,
-       FUSE_RELEASE       = 18,
-       FUSE_FSYNC         = 20,
-       FUSE_SETXATTR      = 21,
-       FUSE_GETXATTR      = 22,
-       FUSE_LISTXATTR     = 23,
-       FUSE_REMOVEXATTR   = 24,
-       FUSE_FLUSH         = 25,
-       FUSE_INIT          = 26,
-       FUSE_OPENDIR       = 27,
-       FUSE_READDIR       = 28,
-       FUSE_RELEASEDIR    = 29,
-       FUSE_FSYNCDIR      = 30,
-       FUSE_GETLK         = 31,
-       FUSE_SETLK         = 32,
-       FUSE_SETLKW        = 33,
-       FUSE_ACCESS        = 34,
-       FUSE_CREATE        = 35,
-       FUSE_INTERRUPT     = 36,
-       FUSE_BMAP          = 37,
-       FUSE_DESTROY       = 38,
-       FUSE_IOCTL         = 39,
-       FUSE_POLL          = 40,
-       FUSE_NOTIFY_REPLY  = 41,
-       FUSE_BATCH_FORGET  = 42,
-       FUSE_FALLOCATE     = 43,
-       FUSE_READDIRPLUS   = 44,
-       FUSE_RENAME2       = 45,
-       FUSE_LSEEK         = 46,
+       FUSE_LOOKUP             = 1,
+       FUSE_FORGET             = 2,  /* no reply */
+       FUSE_GETATTR            = 3,
+       FUSE_SETATTR            = 4,
+       FUSE_READLINK           = 5,
+       FUSE_SYMLINK            = 6,
+       FUSE_MKNOD              = 8,
+       FUSE_MKDIR              = 9,
+       FUSE_UNLINK             = 10,
+       FUSE_RMDIR              = 11,
+       FUSE_RENAME             = 12,
+       FUSE_LINK               = 13,
+       FUSE_OPEN               = 14,
+       FUSE_READ               = 15,
+       FUSE_WRITE              = 16,
+       FUSE_STATFS             = 17,
+       FUSE_RELEASE            = 18,
+       FUSE_FSYNC              = 20,
+       FUSE_SETXATTR           = 21,
+       FUSE_GETXATTR           = 22,
+       FUSE_LISTXATTR          = 23,
+       FUSE_REMOVEXATTR        = 24,
+       FUSE_FLUSH              = 25,
+       FUSE_INIT               = 26,
+       FUSE_OPENDIR            = 27,
+       FUSE_READDIR            = 28,
+       FUSE_RELEASEDIR         = 29,
+       FUSE_FSYNCDIR           = 30,
+       FUSE_GETLK              = 31,
+       FUSE_SETLK              = 32,
+       FUSE_SETLKW             = 33,
+       FUSE_ACCESS             = 34,
+       FUSE_CREATE             = 35,
+       FUSE_INTERRUPT          = 36,
+       FUSE_BMAP               = 37,
+       FUSE_DESTROY            = 38,
+       FUSE_IOCTL              = 39,
+       FUSE_POLL               = 40,
+       FUSE_NOTIFY_REPLY       = 41,
+       FUSE_BATCH_FORGET       = 42,
+       FUSE_FALLOCATE          = 43,
+       FUSE_READDIRPLUS        = 44,
+       FUSE_RENAME2            = 45,
+       FUSE_LSEEK              = 46,
+       FUSE_COPY_FILE_RANGE    = 47,
 
        /* CUSE specific operations */
-       CUSE_INIT          = 4096,
+       CUSE_INIT               = 4096,
 };
 
 enum fuse_notify_code {
@@ -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 */
-- 
2.17.1

Reply via email to