]> git.proxmox.com Git - mirror_zfs.git/commitdiff
linux: implement filesystem-side clone ioctls
authorRob Norris <rob.norris@klarasystems.com>
Tue, 27 Jun 2023 13:45:00 +0000 (23:45 +1000)
committerBrian Behlendorf <behlendorf1@llnl.gov>
Mon, 24 Jul 2023 23:36:54 +0000 (16:36 -0700)
Prior to Linux 4.5, the FICLONE etc ioctls were specific to BTRFS, and
were implemented as regular filesystem-specific ioctls. This implements
those ioctls directly in OpenZFS, allowing cloning to work on older
kernels.

There's no need to gate these behind version checks; on later kernels
Linux will simply never deliver these ioctls, instead calling the
approprate VFS op.

Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov>
Reviewed-by: Kay Pedersen <mail@mkwg.de>
Signed-off-by: Rob Norris <rob.norris@klarasystems.com>
Sponsored-By: OpenDrives Inc.
Sponsored-By: Klara Inc.
Closes #15050

include/os/linux/zfs/sys/zpl.h
module/os/linux/zfs/zpl_file.c
module/os/linux/zfs/zpl_file_range.c

index 8b0e79afb0f13bfd8492d58b9d31f7e3e17d7b74..b62ab5eec81fc5024705c686c7e3918c4601a2c3 100644 (file)
@@ -193,6 +193,41 @@ extern int zpl_clone_file_range(struct file *src_file, loff_t src_off,
 extern int zpl_dedupe_file_range(struct file *src_file, loff_t src_off,
     struct file *dst_file, loff_t dst_off, uint64_t len);
 
+/* compat for FICLONE/FICLONERANGE/FIDEDUPERANGE ioctls */
+typedef struct {
+       int64_t         fcr_src_fd;
+       uint64_t        fcr_src_offset;
+       uint64_t        fcr_src_length;
+       uint64_t        fcr_dest_offset;
+} zfs_ioc_compat_file_clone_range_t;
+
+typedef struct {
+       int64_t         fdri_dest_fd;
+       uint64_t        fdri_dest_offset;
+       uint64_t        fdri_bytes_deduped;
+       int32_t         fdri_status;
+       uint32_t        fdri_reserved;
+} zfs_ioc_compat_dedupe_range_info_t;
+
+typedef struct {
+       uint64_t        fdr_src_offset;
+       uint64_t        fdr_src_length;
+       uint16_t        fdr_dest_count;
+       uint16_t        fdr_reserved1;
+       uint32_t        fdr_reserved2;
+       zfs_ioc_compat_dedupe_range_info_t      fdr_info[];
+} zfs_ioc_compat_dedupe_range_t;
+
+#define        ZFS_IOC_COMPAT_FICLONE          _IOW(0x94, 9, int)
+#define        ZFS_IOC_COMPAT_FICLONERANGE \
+    _IOW(0x94, 13, zfs_ioc_compat_file_clone_range_t)
+#define        ZFS_IOC_COMPAT_FIDEDUPERANGE \
+    _IOWR(0x94, 54, zfs_ioc_compat_dedupe_range_t)
+
+extern long zpl_ioctl_ficlone(struct file *filp, void *arg);
+extern long zpl_ioctl_ficlonerange(struct file *filp, void *arg);
+extern long zpl_ioctl_fideduperange(struct file *filp, void *arg);
+
 
 #if defined(HAVE_INODE_TIMESTAMP_TRUNCATE)
 #define        zpl_inode_timestamp_truncate(ts, ip)    timestamp_truncate(ts, ip)
index 92b603e98a23540780bdf0439a513a9fc6570f7d..87a248af8303f9b2f38787c81ed546f51355a550 100644 (file)
@@ -1257,6 +1257,12 @@ zpl_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
                return (zpl_ioctl_getdosflags(filp, (void *)arg));
        case ZFS_IOC_SETDOSFLAGS:
                return (zpl_ioctl_setdosflags(filp, (void *)arg));
+       case ZFS_IOC_COMPAT_FICLONE:
+               return (zpl_ioctl_ficlone(filp, (void *)arg));
+       case ZFS_IOC_COMPAT_FICLONERANGE:
+               return (zpl_ioctl_ficlonerange(filp, (void *)arg));
+       case ZFS_IOC_COMPAT_FIDEDUPERANGE:
+               return (zpl_ioctl_fideduperange(filp, (void *)arg));
        default:
                return (-ENOTTY);
        }
index db387a748130cd868daa517de725992f96754677..aad502a8092e734a69c2b3f53026cc56844bde6d 100644 (file)
@@ -181,3 +181,82 @@ zpl_dedupe_file_range(struct file *src_file, loff_t src_off,
        return (-EOPNOTSUPP);
 }
 #endif /* HAVE_VFS_DEDUPE_FILE_RANGE */
+
+/* Entry point for FICLONE, before Linux 4.5. */
+long
+zpl_ioctl_ficlone(struct file *dst_file, void *arg)
+{
+       unsigned long sfd = (unsigned long)arg;
+
+       struct file *src_file = fget(sfd);
+       if (src_file == NULL)
+               return (-EBADF);
+
+       if (dst_file->f_op != src_file->f_op)
+               return (-EXDEV);
+
+       size_t len = i_size_read(file_inode(src_file));
+
+       ssize_t ret =
+           __zpl_clone_file_range(src_file, 0, dst_file, 0, len);
+
+       fput(src_file);
+
+       if (ret < 0) {
+               if (ret == -EOPNOTSUPP)
+                       return (-ENOTTY);
+               return (ret);
+       }
+
+       if (ret != len)
+               return (-EINVAL);
+
+       return (0);
+}
+
+/* Entry point for FICLONERANGE, before Linux 4.5. */
+long
+zpl_ioctl_ficlonerange(struct file *dst_file, void __user *arg)
+{
+       zfs_ioc_compat_file_clone_range_t fcr;
+
+       if (copy_from_user(&fcr, arg, sizeof (fcr)))
+               return (-EFAULT);
+
+       struct file *src_file = fget(fcr.fcr_src_fd);
+       if (src_file == NULL)
+               return (-EBADF);
+
+       if (dst_file->f_op != src_file->f_op)
+               return (-EXDEV);
+
+       size_t len = fcr.fcr_src_length;
+       if (len == 0)
+               len = i_size_read(file_inode(src_file)) - fcr.fcr_src_offset;
+
+       ssize_t ret = __zpl_clone_file_range(src_file, fcr.fcr_src_offset,
+           dst_file, fcr.fcr_dest_offset, len);
+
+       fput(src_file);
+
+       if (ret < 0) {
+               if (ret == -EOPNOTSUPP)
+                       return (-ENOTTY);
+               return (ret);
+       }
+
+       if (ret != len)
+               return (-EINVAL);
+
+       return (0);
+}
+
+/* Entry point for FIDEDUPERANGE, before Linux 4.5. */
+long
+zpl_ioctl_fideduperange(struct file *filp, void *arg)
+{
+       (void) arg;
+
+       /* No support for dedup yet */
+       return (-ENOTTY);
+}