From 802e7b5feb0135483de119eac1da192404eb5bb7 Mon Sep 17 00:00:00 2001 From: Li Dongyang Date: Thu, 13 Jun 2013 13:51:09 -0400 Subject: [PATCH] Add SEEK_DATA/SEEK_HOLE to lseek()/llseek() The approach taken was the rework zfs_holey() as little as possible and then just wrap the code as needed to ensure correct locking and error handling. Tested with xfstests 285 and 286. All tests pass except for 7-9 of 285 which try to reserve blocks first via fallocate(2) and fail because fallocate(2) is not yet supported. Note that the filp->f_lock spinlock did not exist prior to Linux 2.6.30, but we avoid the need for autotools check by virtue of the fact that SEEK_DATA/SEEK_HOLE support was not added until Linux 3.1. An autoconf check was added for lseek_execute() which is currently a private function but the expectation is that it will be exported perhaps as early as Linux 3.11. Reviewed-by: Richard Laager Signed-off-by: Richard Yao Signed-off-by: Brian Behlendorf Closes #1384 --- config/kernel-lseek-execute.m4 | 23 +++++++++++++++++++++ config/kernel.m4 | 1 + include/linux/vfs_compat.h | 22 ++++++++++++++++++++ include/sys/zfs_vnops.h | 1 + module/zfs/zfs_vnops.c | 37 +++++++++++++++++++++++++--------- module/zfs/zpl_file.c | 24 +++++++++++++++++++++- 6 files changed, 97 insertions(+), 11 deletions(-) create mode 100644 config/kernel-lseek-execute.m4 diff --git a/config/kernel-lseek-execute.m4 b/config/kernel-lseek-execute.m4 new file mode 100644 index 000000000..8c4032b92 --- /dev/null +++ b/config/kernel-lseek-execute.m4 @@ -0,0 +1,23 @@ +dnl # +dnl # 3.11 API change +dnl # lseek_execute helper exported +dnl # +AC_DEFUN([ZFS_AC_KERNEL_LSEEK_EXECUTE], + [AC_MSG_CHECKING([whether lseek_execute() is available]) + ZFS_LINUX_TRY_COMPILE_SYMBOL([ + #include + ], [ + struct file *fp __attribute__ ((unused)) = NULL; + struct inode *ip __attribute__ ((unused)) = NULL; + loff_t offset __attribute__ ((unused)) = 0; + loff_t maxsize __attribute__ ((unused)) = 0; + + lseek_execute(fp, ip, offset, maxsize); + ], [lseek_exclusive], [fs/read_write.c], [ + AC_MSG_RESULT(yes) + AC_DEFINE(HAVE_LSEEK_EXECUTE, 1, + [lseek_execute() is available]) + ], [ + AC_MSG_RESULT(no) + ]) +]) diff --git a/config/kernel.m4 b/config/kernel.m4 index d06898097..7b8e3b04b 100644 --- a/config/kernel.m4 +++ b/config/kernel.m4 @@ -77,6 +77,7 @@ AC_DEFUN([ZFS_AC_CONFIG_KERNEL], [ ZFS_AC_KERNEL_SET_NLINK ZFS_AC_KERNEL_ELEVATOR_CHANGE ZFS_AC_KERNEL_5ARG_SGET + ZFS_AC_KERNEL_LSEEK_EXECUTE AS_IF([test "$LINUX_OBJ" != "$LINUX"], [ KERNELMAKE_PARAMS="$KERNELMAKE_PARAMS O=$LINUX_OBJ" diff --git a/include/linux/vfs_compat.h b/include/linux/vfs_compat.h index bccf0d01f..17fa3ff7b 100644 --- a/include/linux/vfs_compat.h +++ b/include/linux/vfs_compat.h @@ -152,4 +152,26 @@ typedef int zpl_umode_t; #define ZFS_IOC_GETFLAGS FS_IOC_GETFLAGS #define ZFS_IOC_SETFLAGS FS_IOC_SETFLAGS +#if defined(SEEK_HOLE) && defined(SEEK_DATA) && !defined(HAVE_LSEEK_EXECUTE) +static inline loff_t +lseek_execute(struct file *filp, struct inode *inode, + loff_t offset, loff_t maxsize) +{ + if (offset < 0 && !(filp->f_mode & FMODE_UNSIGNED_OFFSET)) + return (-EINVAL); + + if (offset > maxsize) + return (-EINVAL); + + if (offset != filp->f_pos) { + spin_lock(&filp->f_lock); + filp->f_pos = offset; + filp->f_version = 0; + spin_unlock(&filp->f_lock); + } + + return (offset); +} +#endif /* SEEK_HOLE && SEEK_DATA && !HAVE_LSEEK_EXECUTE */ + #endif /* _ZFS_VFS_H */ diff --git a/include/sys/zfs_vnops.h b/include/sys/zfs_vnops.h index 5da5eaf1d..75f7c128b 100644 --- a/include/sys/zfs_vnops.h +++ b/include/sys/zfs_vnops.h @@ -38,6 +38,7 @@ extern "C" { extern int zfs_open(struct inode *ip, int mode, int flag, cred_t *cr); extern int zfs_close(struct inode *ip, int flag, cred_t *cr); +extern int zfs_holey(struct inode *ip, int cmd, loff_t *off); extern int zfs_read(struct inode *ip, uio_t *uio, int ioflag, cred_t *cr); extern int zfs_write(struct inode *ip, uio_t *uio, int ioflag, cred_t *cr); extern int zfs_access(struct inode *ip, int mode, int flag, cred_t *cr); diff --git a/module/zfs/zfs_vnops.c b/module/zfs/zfs_vnops.c index 5a9c96334..3c3e8db4b 100644 --- a/module/zfs/zfs_vnops.c +++ b/module/zfs/zfs_vnops.c @@ -240,15 +240,15 @@ zfs_close(struct inode *ip, int flag, cred_t *cr) } EXPORT_SYMBOL(zfs_close); -#if 0 +#if defined(SEEK_HOLE) && defined(SEEK_DATA) /* - * Lseek support for finding holes (cmd == _FIO_SEEK_HOLE) and - * data (cmd == _FIO_SEEK_DATA). "off" is an in/out parameter. + * Lseek support for finding holes (cmd == SEEK_HOLE) and + * data (cmd == SEEK_DATA). "off" is an in/out parameter. */ static int -zfs_holey(vnode_t *vp, int cmd, offset_t *off) +zfs_holey_common(struct inode *ip, int cmd, loff_t *off) { - znode_t *zp = VTOZ(vp); + znode_t *zp = ITOZ(ip); uint64_t noff = (uint64_t)*off; /* new offset */ uint64_t file_sz; int error; @@ -256,15 +256,15 @@ zfs_holey(vnode_t *vp, int cmd, offset_t *off) file_sz = zp->z_size; if (noff >= file_sz) { - return (SET_ERROR(ENXIO)); + return (ENXIO); } - if (cmd == _FIO_SEEK_HOLE) + if (cmd == SEEK_HOLE) hole = B_TRUE; else hole = B_FALSE; - error = dmu_offset_next(zp->z_zfsvfs->z_os, zp->z_id, hole, &noff); + error = dmu_offset_next(ZTOZSB(zp)->z_os, zp->z_id, hole, &noff); /* end of file? */ if ((error == ESRCH) || (noff > file_sz)) { @@ -275,7 +275,7 @@ zfs_holey(vnode_t *vp, int cmd, offset_t *off) *off = file_sz; return (0); } - return (SET_ERROR(ENXIO)); + return (ENXIO); } if (noff < *off) @@ -283,7 +283,24 @@ zfs_holey(vnode_t *vp, int cmd, offset_t *off) *off = noff; return (error); } -#endif + +int +zfs_holey(struct inode *ip, int cmd, loff_t *off) +{ + znode_t *zp = ITOZ(ip); + zfs_sb_t *zsb = ITOZSB(ip); + int error; + + ZFS_ENTER(zsb); + ZFS_VERIFY_ZP(zp); + + error = zfs_holey_common(ip, cmd, off); + + ZFS_EXIT(zsb); + return (error); +} +EXPORT_SYMBOL(zfs_holey); +#endif /* SEEK_HOLE && SEEK_DATA */ #if defined(_KERNEL) /* diff --git a/module/zfs/zpl_file.c b/module/zfs/zpl_file.c index c6d97db50..ebae6bfe2 100644 --- a/module/zfs/zpl_file.c +++ b/module/zfs/zpl_file.c @@ -235,6 +235,28 @@ zpl_write(struct file *filp, const char __user *buf, size_t len, loff_t *ppos) return (wrote); } +static loff_t +zpl_llseek(struct file *filp, loff_t offset, int whence) +{ +#if defined(SEEK_HOLE) && defined(SEEK_DATA) + if (whence == SEEK_DATA || whence == SEEK_HOLE) { + struct inode *ip = filp->f_mapping->host; + loff_t maxbytes = ip->i_sb->s_maxbytes; + loff_t error; + + spl_inode_lock(ip); + error = -zfs_holey(ip, whence, &offset); + if (error == 0) + error = lseek_execute(filp, ip, offset, maxbytes); + spl_inode_unlock(ip); + + return (error); + } +#endif /* SEEK_HOLE && SEEK_DATA */ + + return generic_file_llseek(filp, offset, whence); +} + /* * It's worth taking a moment to describe how mmap is implemented * for zfs because it differs considerably from other Linux filesystems. @@ -464,7 +486,7 @@ const struct address_space_operations zpl_address_space_operations = { const struct file_operations zpl_file_operations = { .open = zpl_open, .release = zpl_release, - .llseek = generic_file_llseek, + .llseek = zpl_llseek, .read = zpl_read, .write = zpl_write, .mmap = zpl_mmap, -- 2.40.0