linux: implement filesystem-side clone ioctls
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
This commit is contained in:
parent
5a35c68b67
commit
9927f219f1
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue