Add SEEK_DATA/SEEK_HOLE to lseek()/llseek()
authorLi Dongyang <dongyang.li@anu.edu.au>
Thu, 13 Jun 2013 17:51:09 +0000 (13:51 -0400)
committerBrian Behlendorf <behlendorf1@llnl.gov>
Tue, 2 Jul 2013 16:24:43 +0000 (09:24 -0700)
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 <rlaager@wiktel.com>
Signed-off-by: Richard Yao <ryao@gentoo.org>
Signed-off-by: Brian Behlendorf <behlendorf1@llnl.gov>
Closes #1384

config/kernel-lseek-execute.m4 [new file with mode: 0644]
config/kernel.m4
include/linux/vfs_compat.h
include/sys/zfs_vnops.h
module/zfs/zfs_vnops.c
module/zfs/zpl_file.c

diff --git a/config/kernel-lseek-execute.m4 b/config/kernel-lseek-execute.m4
new file mode 100644 (file)
index 0000000..8c4032b
--- /dev/null
@@ -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 <linux/fs.h>
+       ], [
+               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)
+       ])
+])
index d068980..7b8e3b0 100644 (file)
@@ -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"
index bccf0d0..17fa3ff 100644 (file)
@@ -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 */
index 5da5eaf..75f7c12 100644 (file)
@@ -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);
index 5a9c963..3c3e8db 100644 (file)
@@ -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)
 /*
index c6d97db..ebae6bf 100644 (file)
@@ -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,