From 6b0a4be5fec60eb774c5393a0093150c608b7496 Mon Sep 17 00:00:00 2001 From: Rob Norris Date: Sun, 25 Jun 2023 20:50:19 +1000 Subject: [PATCH] linux: implement filesystem-side copy/clone functions for EL7 Redhat have backported copy_file_range and clone_file_range to the EL7 kernel using an "extended file operations" wrapper structure. This connects all that up to let cloning work there too. Reviewed-by: Brian Behlendorf Reviewed-by: Kay Pedersen Signed-off-by: Rob Norris Sponsored-By: OpenDrives Inc. Sponsored-By: Klara Inc. Closes #15050 --- config/kernel-vfs-extended-file_range.m4 | 50 ++++++++++++++++++++++++ config/kernel.m4 | 2 + include/os/linux/zfs/sys/zpl.h | 4 ++ module/os/linux/zfs/zfs_vfsops.c | 6 +++ module/os/linux/zfs/zfs_znode.c | 8 ++++ module/os/linux/zfs/zpl_file.c | 16 ++++++-- module/os/linux/zfs/zpl_file_range.c | 12 +++--- 7 files changed, 90 insertions(+), 8 deletions(-) create mode 100644 config/kernel-vfs-extended-file_range.m4 diff --git a/config/kernel-vfs-extended-file_range.m4 b/config/kernel-vfs-extended-file_range.m4 new file mode 100644 index 000000000..a26223131 --- /dev/null +++ b/config/kernel-vfs-extended-file_range.m4 @@ -0,0 +1,50 @@ +dnl # +dnl # EL7 have backported copy_file_range and clone_file_range and +dnl # added them to an "extended" file_operations struct. +dnl # +dnl # We're testing for both functions in one here, because they will only +dnl # ever appear together and we don't want to match a similar method in +dnl # some future vendor kernel. +dnl # +AC_DEFUN([ZFS_AC_KERNEL_SRC_VFS_FILE_OPERATIONS_EXTEND], [ + ZFS_LINUX_TEST_SRC([vfs_file_operations_extend], [ + #include + + static ssize_t test_copy_file_range(struct file *src_file, + loff_t src_off, struct file *dst_file, loff_t dst_off, + size_t len, unsigned int flags) { + (void) src_file; (void) src_off; + (void) dst_file; (void) dst_off; + (void) len; (void) flags; + return (0); + } + + static int test_clone_file_range(struct file *src_file, + loff_t src_off, struct file *dst_file, loff_t dst_off, + u64 len) { + (void) src_file; (void) src_off; + (void) dst_file; (void) dst_off; + (void) len; + return (0); + } + + static const struct file_operations_extend + fops __attribute__ ((unused)) = { + .kabi_fops = {}, + .copy_file_range = test_copy_file_range, + .clone_file_range = test_clone_file_range, + }; + ],[]) +]) +AC_DEFUN([ZFS_AC_KERNEL_VFS_FILE_OPERATIONS_EXTEND], [ + AC_MSG_CHECKING([whether file_operations_extend takes \ +.copy_file_range() and .clone_file_range()]) + ZFS_LINUX_TEST_RESULT([vfs_file_operations_extend], [ + AC_MSG_RESULT([yes]) + AC_DEFINE(HAVE_VFS_FILE_OPERATIONS_EXTEND, 1, + [file_operations_extend takes .copy_file_range() + and .clone_file_range()]) + ],[ + AC_MSG_RESULT([no]) + ]) +]) diff --git a/config/kernel.m4 b/config/kernel.m4 index b17ccfdee..1487fa2e7 100644 --- a/config/kernel.m4 +++ b/config/kernel.m4 @@ -121,6 +121,7 @@ AC_DEFUN([ZFS_AC_KERNEL_TEST_SRC], [ ZFS_AC_KERNEL_SRC_VFS_REMAP_FILE_RANGE ZFS_AC_KERNEL_SRC_VFS_CLONE_FILE_RANGE ZFS_AC_KERNEL_SRC_VFS_DEDUPE_FILE_RANGE + ZFS_AC_KERNEL_SRC_VFS_FILE_OPERATIONS_EXTEND ZFS_AC_KERNEL_SRC_KMAP_ATOMIC_ARGS ZFS_AC_KERNEL_SRC_FOLLOW_DOWN_ONE ZFS_AC_KERNEL_SRC_MAKE_REQUEST_FN @@ -259,6 +260,7 @@ AC_DEFUN([ZFS_AC_KERNEL_TEST_RESULT], [ ZFS_AC_KERNEL_VFS_REMAP_FILE_RANGE ZFS_AC_KERNEL_VFS_CLONE_FILE_RANGE ZFS_AC_KERNEL_VFS_DEDUPE_FILE_RANGE + ZFS_AC_KERNEL_VFS_FILE_OPERATIONS_EXTEND ZFS_AC_KERNEL_KMAP_ATOMIC_ARGS ZFS_AC_KERNEL_FOLLOW_DOWN_ONE ZFS_AC_KERNEL_MAKE_REQUEST_FN diff --git a/include/os/linux/zfs/sys/zpl.h b/include/os/linux/zfs/sys/zpl.h index b62ab5eec..0bd20f648 100644 --- a/include/os/linux/zfs/sys/zpl.h +++ b/include/os/linux/zfs/sys/zpl.h @@ -52,7 +52,11 @@ extern const struct inode_operations zpl_special_inode_operations; /* zpl_file.c */ extern const struct address_space_operations zpl_address_space_operations; +#ifdef HAVE_VFS_FILE_OPERATIONS_EXTEND +extern const struct file_operations_extend zpl_file_operations; +#else extern const struct file_operations zpl_file_operations; +#endif extern const struct file_operations zpl_dir_file_operations; /* zpl_super.c */ diff --git a/module/os/linux/zfs/zfs_vfsops.c b/module/os/linux/zfs/zfs_vfsops.c index 87c4e6dca..464c12e11 100644 --- a/module/os/linux/zfs/zfs_vfsops.c +++ b/module/os/linux/zfs/zfs_vfsops.c @@ -2092,6 +2092,9 @@ zfs_init(void) zfs_znode_init(); dmu_objset_register_type(DMU_OST_ZFS, zpl_get_file_info); register_filesystem(&zpl_fs_type); +#ifdef HAVE_VFS_FILE_OPERATIONS_EXTEND + register_fo_extend(&zpl_file_operations); +#endif } void @@ -2102,6 +2105,9 @@ zfs_fini(void) */ taskq_wait(system_delay_taskq); taskq_wait(system_taskq); +#ifdef HAVE_VFS_FILE_OPERATIONS_EXTEND + unregister_fo_extend(&zpl_file_operations); +#endif unregister_filesystem(&zpl_fs_type); zfs_znode_fini(); zfsctl_fini(); diff --git a/module/os/linux/zfs/zfs_znode.c b/module/os/linux/zfs/zfs_znode.c index 02b1af3ed..335ae3460 100644 --- a/module/os/linux/zfs/zfs_znode.c +++ b/module/os/linux/zfs/zfs_znode.c @@ -415,7 +415,11 @@ zfs_inode_set_ops(zfsvfs_t *zfsvfs, struct inode *ip) switch (ip->i_mode & S_IFMT) { case S_IFREG: ip->i_op = &zpl_inode_operations; +#ifdef HAVE_VFS_FILE_OPERATIONS_EXTEND + ip->i_fop = &zpl_file_operations.kabi_fops; +#else ip->i_fop = &zpl_file_operations; +#endif ip->i_mapping->a_ops = &zpl_address_space_operations; break; @@ -455,7 +459,11 @@ zfs_inode_set_ops(zfsvfs_t *zfsvfs, struct inode *ip) /* Assume the inode is a file and attempt to continue */ ip->i_mode = S_IFREG | 0644; ip->i_op = &zpl_inode_operations; +#ifdef HAVE_VFS_FILE_OPERATIONS_EXTEND + ip->i_fop = &zpl_file_operations.kabi_fops; +#else ip->i_fop = &zpl_file_operations; +#endif ip->i_mapping->a_ops = &zpl_address_space_operations; break; } diff --git a/module/os/linux/zfs/zpl_file.c b/module/os/linux/zfs/zpl_file.c index 87a248af8..73526db73 100644 --- a/module/os/linux/zfs/zpl_file.c +++ b/module/os/linux/zfs/zpl_file.c @@ -1311,7 +1311,12 @@ const struct address_space_operations zpl_address_space_operations = { #endif }; +#ifdef HAVE_VFS_FILE_OPERATIONS_EXTEND +const struct file_operations_extend zpl_file_operations = { + .kabi_fops = { +#else const struct file_operations zpl_file_operations = { +#endif .open = zpl_open, .release = zpl_release, .llseek = zpl_llseek, @@ -1341,12 +1346,12 @@ const struct file_operations zpl_file_operations = { #ifdef HAVE_VFS_COPY_FILE_RANGE .copy_file_range = zpl_copy_file_range, #endif -#ifdef HAVE_VFS_REMAP_FILE_RANGE - .remap_file_range = zpl_remap_file_range, -#endif #ifdef HAVE_VFS_CLONE_FILE_RANGE .clone_file_range = zpl_clone_file_range, #endif +#ifdef HAVE_VFS_REMAP_FILE_RANGE + .remap_file_range = zpl_remap_file_range, +#endif #ifdef HAVE_VFS_DEDUPE_FILE_RANGE .dedupe_file_range = zpl_dedupe_file_range, #endif @@ -1357,6 +1362,11 @@ const struct file_operations zpl_file_operations = { #ifdef CONFIG_COMPAT .compat_ioctl = zpl_compat_ioctl, #endif +#ifdef HAVE_VFS_FILE_OPERATIONS_EXTEND + }, /* kabi_fops */ + .copy_file_range = zpl_copy_file_range, + .clone_file_range = zpl_clone_file_range, +#endif }; const struct file_operations zpl_dir_file_operations = { diff --git a/module/os/linux/zfs/zpl_file_range.c b/module/os/linux/zfs/zpl_file_range.c index aad502a80..18efebfc1 100644 --- a/module/os/linux/zfs/zpl_file_range.c +++ b/module/os/linux/zfs/zpl_file_range.c @@ -77,7 +77,8 @@ __zpl_clone_file_range(struct file *src_file, loff_t src_off, return ((ssize_t)len_o); } -#ifdef HAVE_VFS_COPY_FILE_RANGE +#if defined(HAVE_VFS_COPY_FILE_RANGE) || \ + defined(HAVE_VFS_FILE_OPERATIONS_EXTEND) /* * Entry point for copy_file_range(). Copy len bytes from src_off in src_file * to dst_off in dst_file. We are permitted to do this however we like, so we @@ -94,7 +95,7 @@ zpl_copy_file_range(struct file *src_file, loff_t src_off, return (-EINVAL); /* Try to do it via zfs_clone_range() */ - ret =__zpl_clone_file_range(src_file, src_off, + ret = __zpl_clone_file_range(src_file, src_off, dst_file, dst_off, len); #ifdef HAVE_VFS_GENERIC_COPY_FILE_RANGE @@ -109,7 +110,7 @@ zpl_copy_file_range(struct file *src_file, loff_t src_off, return (ret); } -#endif /* HAVE_VFS_COPY_FILE_RANGE */ +#endif /* HAVE_VFS_COPY_FILE_RANGE || HAVE_VFS_FILE_OPERATIONS_EXTEND */ #ifdef HAVE_VFS_REMAP_FILE_RANGE /* @@ -152,7 +153,8 @@ zpl_remap_file_range(struct file *src_file, loff_t src_off, } #endif /* HAVE_VFS_REMAP_FILE_RANGE */ -#ifdef HAVE_VFS_CLONE_FILE_RANGE +#if defined(HAVE_VFS_CLONE_FILE_RANGE) || \ + defined(HAVE_VFS_FILE_OPERATIONS_EXTEND) /* * Entry point for FICLONE and FICLONERANGE, before Linux 4.20. */ @@ -167,7 +169,7 @@ zpl_clone_file_range(struct file *src_file, loff_t src_off, return (__zpl_clone_file_range(src_file, src_off, dst_file, dst_off, len)); } -#endif /* HAVE_VFS_CLONE_FILE_RANGE */ +#endif /* HAVE_VFS_CLONE_FILE_RANGE || HAVE_VFS_FILE_OPERATIONS_EXTEND */ #ifdef HAVE_VFS_DEDUPE_FILE_RANGE /*