Skip to content

Commit

Permalink
linux: implement filesystem-side clone ioctls
Browse files Browse the repository at this point in the history
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.

Signed-off-by: Rob Norris <[email protected]>
Sponsored-By: OpenDrives Inc.
Sponsored-By: Klara Inc.
  • Loading branch information
robn authored and usaleem-ix committed Jul 24, 2023
1 parent 5067565 commit fde89eb
Show file tree
Hide file tree
Showing 3 changed files with 120 additions and 0 deletions.
35 changes: 35 additions & 0 deletions include/os/linux/zfs/sys/zpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,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)
Expand Down
6 changes: 6 additions & 0 deletions module/os/linux/zfs/zpl_file.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
79 changes: 79 additions & 0 deletions module/os/linux/zfs/zpl_file_range.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

0 comments on commit fde89eb

Please sign in to comment.