Linux 3.11 compat: fops->iterate()
authorRichard Yao <ryao@gentoo.org>
Wed, 7 Aug 2013 12:53:45 +0000 (08:53 -0400)
committerBrian Behlendorf <behlendorf1@llnl.gov>
Thu, 15 Aug 2013 23:19:07 +0000 (16:19 -0700)
Commit torvalds/linux@2233f31aade393641f0eaed43a71110e629bb900
replaced ->readdir() with ->iterate() in struct file_operations.
All filesystems must now use the new ->iterate method.

To handle this the code was reworked to use the new ->iterate
interface.  Care was taken to keep the majority of changes
confined to the ZPL layer which is already Linux specific.
However, minor changes were required to the common zfs_readdir()
function.

Compatibility with older kernels was accomplished by adding
versions of the trivial dir_emit* helper functions.  Also the
various *_readdir() functions were reworked in to wrappers
which create a dir_context structure to pass to the new
*_iterate() functions.

Unfortunately, the new dir_emit* functions prevent us from
passing a private pointer to the filldir function.  The xattr
directory code leveraged this ability through zfs_readdir()
to generate the list of xattr names.  Since we can no longer
use zfs_readdir() a simplified zpl_xattr_readdir() function
was added to perform the same task.

Signed-off-by: Richard Yao <ryao@cs.stonybrook.edu>
Signed-off-by: Brian Behlendorf <behlendorf1@llnl.gov>
Closes #1653
Issue #1591

config/kernel-vfs-iterate.m4 [new file with mode: 0644]
config/kernel.m4
include/sys/zfs_vnops.h
include/sys/zpl.h
module/zfs/zfs_vnops.c
module/zfs/zpl_ctldir.c
module/zfs/zpl_file.c
module/zfs/zpl_xattr.c

diff --git a/config/kernel-vfs-iterate.m4 b/config/kernel-vfs-iterate.m4
new file mode 100644 (file)
index 0000000..c2c6562
--- /dev/null
@@ -0,0 +1,43 @@
+dnl #
+dnl # 3.11 API change
+dnl #
+AC_DEFUN([ZFS_AC_KERNEL_VFS_ITERATE], [
+       AC_MSG_CHECKING([whether fops->iterate() is available])
+       ZFS_LINUX_TRY_COMPILE([
+               #include <linux/fs.h>
+               int iterate(struct file *filp, struct dir_context * context)
+                   { return 0; }
+
+               static const struct file_operations fops
+                   __attribute__ ((unused)) = {
+                       .iterate         = iterate,
+               };
+       ],[
+       ],[
+               AC_MSG_RESULT(yes)
+               AC_DEFINE(HAVE_VFS_ITERATE, 1,
+                         [fops->iterate() is available])
+       ],[
+               AC_MSG_RESULT(no)
+
+               AC_MSG_CHECKING([whether fops->readdir() is available])
+               ZFS_LINUX_TRY_COMPILE([
+                       #include <linux/fs.h>
+                       int readdir(struct file *filp, void *entry, filldir_t func)
+                           { return 0; }
+
+                       static const struct file_operations fops
+                           __attribute__ ((unused)) = {
+                               .readdir = readdir,
+                       };
+               ],[
+               ],[
+                       AC_MSG_RESULT(yes)
+                       AC_DEFINE(HAVE_VFS_READDIR, 1,
+                                 [fops->readdir() is available])
+               ],[
+                       AC_MSG_ERROR(no; file a bug report with ZFSOnLinux)
+               ])
+
+       ])
+])
index 8742bc5..74ce22c 100644 (file)
@@ -80,6 +80,7 @@ AC_DEFUN([ZFS_AC_CONFIG_KERNEL], [
        ZFS_AC_KERNEL_ELEVATOR_CHANGE
        ZFS_AC_KERNEL_5ARG_SGET
        ZFS_AC_KERNEL_LSEEK_EXECUTE
+       ZFS_AC_KERNEL_VFS_ITERATE
 
        AS_IF([test "$LINUX_OBJ" != "$LINUX"], [
                KERNELMAKE_PARAMS="$KERNELMAKE_PARAMS O=$LINUX_OBJ"
index 75f7c12..c9fecf8 100644 (file)
@@ -31,6 +31,7 @@
 #include <sys/cred.h>
 #include <sys/fcntl.h>
 #include <sys/pathname.h>
+#include <sys/zpl.h>
 
 #ifdef __cplusplus
 extern "C" {
@@ -51,8 +52,7 @@ extern int zfs_mkdir(struct inode *dip, char *dirname, vattr_t *vap,
     struct inode **ipp, cred_t *cr, int flags, vsecattr_t *vsecp);
 extern int zfs_rmdir(struct inode *dip, char *name, struct inode *cwd,
     cred_t *cr, int flags);
-extern int zfs_readdir(struct inode *ip, void *dirent, filldir_t filldir,
-    loff_t *pos, cred_t *cr);
+extern int zfs_readdir(struct inode *ip, struct dir_context *ctx, cred_t *cr);
 extern int zfs_fsync(struct inode *ip, int syncflag, cred_t *cr);
 extern int zfs_getattr(struct inode *ip, vattr_t *vap, int flag, cred_t *cr);
 extern int zfs_getattr_fast(struct inode *ip, struct kstat *sp);
index 61a57ef..89cf824 100644 (file)
@@ -89,4 +89,63 @@ extern const struct inode_operations zpl_ops_snapdirs;
 extern const struct file_operations zpl_fops_shares;
 extern const struct inode_operations zpl_ops_shares;
 
+#ifdef HAVE_VFS_ITERATE
+
+#define DIR_CONTEXT_INIT(_dirent, _actor, _pos) {      \
+       .actor = _actor,                                \
+       .pos = _pos,                                    \
+}
+
+#else
+
+typedef struct dir_context {
+       void *dirent;
+       const filldir_t actor;
+       loff_t pos;
+} dir_context_t;
+
+#define DIR_CONTEXT_INIT(_dirent, _actor, _pos) {      \
+       .dirent = _dirent,                              \
+       .actor = _actor,                                \
+       .pos = _pos,                                    \
+}
+
+static inline bool
+dir_emit(struct dir_context *ctx, const char *name, int namelen,
+    uint64_t ino, unsigned type)
+{
+       return ctx->actor(ctx->dirent, name, namelen, ctx->pos, ino, type) == 0;
+}
+
+static inline bool
+dir_emit_dot(struct file *file, struct dir_context *ctx)
+{
+       return ctx->actor(ctx->dirent, ".", 1, ctx->pos,
+           file->f_path.dentry->d_inode->i_ino, DT_DIR) == 0;
+}
+
+static inline bool
+dir_emit_dotdot(struct file *file, struct dir_context *ctx)
+{
+       return ctx->actor(ctx->dirent, "..", 2, ctx->pos,
+           parent_ino(file->f_path.dentry), DT_DIR) == 0;
+}
+
+static inline bool
+dir_emit_dots(struct file *file, struct dir_context *ctx)
+{
+       if (ctx->pos == 0) {
+               if (!dir_emit_dot(file, ctx))
+                       return false;
+               ctx->pos = 1;
+       }
+       if (ctx->pos == 1) {
+               if (!dir_emit_dotdot(file, ctx))
+                       return false;
+               ctx->pos = 2;
+       }
+       return true;
+}
+#endif /* HAVE_VFS_ITERATE */
+
 #endif /* _SYS_ZPL_H */
index db5d385..876d44b 100644 (file)
@@ -1997,8 +1997,7 @@ EXPORT_SYMBOL(zfs_rmdir);
  */
 /* ARGSUSED */
 int
-zfs_readdir(struct inode *ip, void *dirent, filldir_t filldir,
-    loff_t *pos, cred_t *cr)
+zfs_readdir(struct inode *ip, struct dir_context *ctx, cred_t *cr)
 {
        znode_t         *zp = ITOZ(ip);
        zfs_sb_t        *zsb = ITOZSB(ip);
@@ -2010,6 +2009,7 @@ zfs_readdir(struct inode *ip, void *dirent, filldir_t filldir,
        uint8_t         prefetch;
        int             done = 0;
        uint64_t        parent;
+       loff_t          *pos = &(ctx->pos);
 
        ZFS_ENTER(zsb);
        ZFS_VERIFY_ZP(zp);
@@ -2098,11 +2098,11 @@ zfs_readdir(struct inode *ip, void *dirent, filldir_t filldir,
 
                        objnum = ZFS_DIRENT_OBJ(zap.za_first_integer);
                }
-               done = filldir(dirent, zap.za_name, strlen(zap.za_name),
-                              *pos, objnum, ZFS_DIRENT_TYPE(zap.za_first_integer));
-               if (done) {
+
+               done = !dir_emit(ctx, zap.za_name, strlen(zap.za_name),
+                   objnum, ZFS_DIRENT_TYPE(zap.za_first_integer));
+               if (done)
                        break;
-               }
 
                /* Prefetch znode */
                if (prefetch) {
index 0897017..1bb646f 100644 (file)
@@ -46,79 +46,34 @@ zpl_common_open(struct inode *ip, struct file *filp)
        return generic_file_open(ip, filp);
 }
 
-static int
-zpl_common_readdir(struct file *filp, void *dirent, filldir_t filldir)
-{
-       struct dentry *dentry = filp->f_path.dentry;
-       struct inode *ip = dentry->d_inode;
-       int error = 0;
-
-       switch (filp->f_pos) {
-       case 0:
-               error = filldir(dirent, ".", 1, 0, ip->i_ino, DT_DIR);
-               if (error)
-                       break;
-
-               filp->f_pos++;
-               /* fall-thru */
-       case 1:
-               error = filldir(dirent, "..", 2, 1, parent_ino(dentry), DT_DIR);
-               if (error)
-                       break;
-
-               filp->f_pos++;
-               /* fall-thru */
-       default:
-               break;
-       }
-
-       return (error);
-}
-
 /*
  * Get root directory contents.
  */
 static int
-zpl_root_readdir(struct file *filp, void *dirent, filldir_t filldir)
+zpl_root_iterate(struct file *filp, struct dir_context *ctx)
 {
-       struct dentry *dentry = filp->f_path.dentry;
-       struct inode *ip = dentry->d_inode;
-       zfs_sb_t *zsb = ITOZSB(ip);
+       zfs_sb_t *zsb = ITOZSB(filp->f_path.dentry->d_inode);
        int error = 0;
 
        ZFS_ENTER(zsb);
 
-       switch (filp->f_pos) {
-       case 0:
-               error = filldir(dirent, ".", 1, 0, ip->i_ino, DT_DIR);
-               if (error)
-                       goto out;
+       if (!dir_emit_dots(filp, ctx))
+               goto out;
 
-               filp->f_pos++;
-               /* fall-thru */
-       case 1:
-               error = filldir(dirent, "..", 2, 1, parent_ino(dentry), DT_DIR);
-               if (error)
+       if (ctx->pos == 2) {
+               if (!dir_emit(ctx, ZFS_SNAPDIR_NAME, strlen(ZFS_SNAPDIR_NAME),
+                   ZFSCTL_INO_SNAPDIR, DT_DIR))
                        goto out;
 
-               filp->f_pos++;
-               /* fall-thru */
-       case 2:
-               error = filldir(dirent, ZFS_SNAPDIR_NAME,
-                   strlen(ZFS_SNAPDIR_NAME), 2, ZFSCTL_INO_SNAPDIR, DT_DIR);
-               if (error)
-                       goto out;
+               ctx->pos++;
+       }
 
-               filp->f_pos++;
-               /* fall-thru */
-       case 3:
-               error = filldir(dirent, ZFS_SHAREDIR_NAME,
-                   strlen(ZFS_SHAREDIR_NAME), 3, ZFSCTL_INO_SHARES, DT_DIR);
-               if (error)
+       if (ctx->pos == 3) {
+               if (!dir_emit(ctx, ZFS_SHAREDIR_NAME, strlen(ZFS_SHAREDIR_NAME),
+                   ZFSCTL_INO_SHARES, DT_DIR))
                        goto out;
 
-               filp->f_pos++;
-               /* fall-thru */
+               ctx->pos++;
        }
 out:
        ZFS_EXIT(zsb);
@@ -126,6 +81,20 @@ out:
        return (error);
 }
 
+#if !defined(HAVE_VFS_ITERATE)
+static int
+zpl_root_readdir(struct file *filp, void *dirent, filldir_t filldir)
+{
+       struct dir_context ctx = DIR_CONTEXT_INIT(dirent, filldir, filp->f_pos);
+       int error;
+
+       error = zpl_root_iterate(filp, &ctx);
+       filp->f_pos = ctx.pos;
+
+       return (error);
+}
+#endif /* HAVE_VFS_ITERATE */
+
 /*
  * Get root directory attributes.
  */
@@ -175,7 +144,11 @@ const struct file_operations zpl_fops_root = {
        .open           = zpl_common_open,
        .llseek         = generic_file_llseek,
        .read           = generic_read_dir,
+#ifdef HAVE_VFS_ITERATE
+       .iterate        = zpl_root_iterate,
+#else
        .readdir        = zpl_root_readdir,
+#endif
 };
 
 const struct inode_operations zpl_ops_root = {
@@ -273,50 +246,29 @@ zpl_snapdir_lookup(struct inode *dip, struct dentry *dentry,
        return d_splice_alias(ip, dentry);
 }
 
-/* ARGSUSED */
 static int
-zpl_snapdir_readdir(struct file *filp, void *dirent, filldir_t filldir)
+zpl_snapdir_iterate(struct file *filp, struct dir_context *ctx)
 {
-       struct dentry *dentry = filp->f_path.dentry;
-       struct inode *dip = dentry->d_inode;
-       zfs_sb_t *zsb = ITOZSB(dip);
+       zfs_sb_t *zsb = ITOZSB(filp->f_path.dentry->d_inode);
        char snapname[MAXNAMELEN];
-       uint64_t id, cookie;
        boolean_t case_conflict;
+       uint64_t id;
        int error = 0;
 
        ZFS_ENTER(zsb);
 
-       cookie = filp->f_pos;
-       switch (filp->f_pos) {
-       case 0:
-               error = filldir(dirent, ".", 1, 0, dip->i_ino, DT_DIR);
-               if (error)
-                       goto out;
+       if (!dir_emit_dots(filp, ctx))
+               goto out;
 
-               filp->f_pos++;
-               /* fall-thru */
-       case 1:
-               error = filldir(dirent, "..", 2, 1, parent_ino(dentry), DT_DIR);
+       while (error == 0) {
+               error = -dmu_snapshot_list_next(zsb->z_os, MAXNAMELEN,
+                   snapname, &id, &(ctx->pos), &case_conflict);
                if (error)
                        goto out;
 
-               filp->f_pos++;
-               /* fall-thru */
-       default:
-               while (error == 0) {
-                       error = -dmu_snapshot_list_next(zsb->z_os, MAXNAMELEN,
-                           snapname, &id, &cookie, &case_conflict);
-                       if (error)
-                               goto out;
-
-                       error = filldir(dirent, snapname, strlen(snapname),
-                           filp->f_pos, ZFSCTL_INO_SHARES - id, DT_DIR);
-                       if (error)
-                               goto out;
-
-                       filp->f_pos = cookie;
-               }
+               if (!dir_emit(ctx, snapname, strlen(snapname),
+                   ZFSCTL_INO_SHARES - id, DT_DIR))
+                       goto out;
        }
 out:
        ZFS_EXIT(zsb);
@@ -327,6 +279,20 @@ out:
        return (error);
 }
 
+#if !defined(HAVE_VFS_ITERATE)
+static int
+zpl_snapdir_readdir(struct file *filp, void *dirent, filldir_t filldir)
+{
+       struct dir_context ctx = DIR_CONTEXT_INIT(dirent, filldir, filp->f_pos);
+       int error;
+
+       error = zpl_snapdir_iterate(filp, &ctx);
+       filp->f_pos = ctx.pos;
+
+       return (error);
+}
+#endif /* HAVE_VFS_ITERATE */
+
 int
 zpl_snapdir_rename(struct inode *sdip, struct dentry *sdentry,
     struct inode *tdip, struct dentry *tdentry)
@@ -413,7 +379,12 @@ const struct file_operations zpl_fops_snapdir = {
        .open           = zpl_common_open,
        .llseek         = generic_file_llseek,
        .read           = generic_read_dir,
+#ifdef HAVE_VFS_ITERATE
+       .iterate        = zpl_snapdir_iterate,
+#else
        .readdir        = zpl_snapdir_readdir,
+#endif
+
 };
 
 /*
@@ -458,42 +429,51 @@ zpl_shares_lookup(struct inode *dip, struct dentry *dentry,
        return d_splice_alias(ip, dentry);
 }
 
-/* ARGSUSED */
 static int
-zpl_shares_readdir(struct file *filp, void *dirent, filldir_t filldir)
+zpl_shares_iterate(struct file *filp, struct dir_context *ctx)
 {
        cred_t *cr = CRED();
-       struct dentry *dentry = filp->f_path.dentry;
-       struct inode *ip = dentry->d_inode;
-       zfs_sb_t *zsb = ITOZSB(ip);
+       zfs_sb_t *zsb = ITOZSB(filp->f_path.dentry->d_inode);
        znode_t *dzp;
-       int error;
+       int error = 0;
 
        ZFS_ENTER(zsb);
 
        if (zsb->z_shares_dir == 0) {
-               error = zpl_common_readdir(filp, dirent, filldir);
-               ZFS_EXIT(zsb);
-               return (error);
+               dir_emit_dots(filp, ctx);
+               goto out;
        }
 
        error = -zfs_zget(zsb, zsb->z_shares_dir, &dzp);
-       if (error) {
-               ZFS_EXIT(zsb);
-               return (error);
-       }
+       if (error)
+               goto out;
 
        crhold(cr);
-       error = -zfs_readdir(ZTOI(dzp), dirent, filldir, &filp->f_pos, cr);
+       error = -zfs_readdir(ZTOI(dzp), ctx, cr);
        crfree(cr);
 
        iput(ZTOI(dzp));
+out:
        ZFS_EXIT(zsb);
        ASSERT3S(error, <=, 0);
 
        return (error);
 }
 
+#if !defined(HAVE_VFS_ITERATE)
+static int
+zpl_shares_readdir(struct file *filp, void *dirent, filldir_t filldir)
+{
+       struct dir_context ctx = DIR_CONTEXT_INIT(dirent, filldir, filp->f_pos);
+       int error;
+
+       error = zpl_shares_iterate(filp, &ctx);
+       filp->f_pos = ctx.pos;
+
+       return (error);
+}
+#endif /* HAVE_VFS_ITERATE */
+
 /* ARGSUSED */
 static int
 zpl_shares_getattr(struct vfsmount *mnt, struct dentry *dentry,
@@ -532,7 +512,12 @@ const struct file_operations zpl_fops_shares = {
        .open           = zpl_common_open,
        .llseek         = generic_file_llseek,
        .read           = generic_read_dir,
+#ifdef HAVE_VFS_ITERATE
+       .iterate        = zpl_shares_iterate,
+#else
        .readdir        = zpl_shares_readdir,
+#endif
+
 };
 
 /*
index 2995891..6598c17 100644 (file)
@@ -64,21 +64,34 @@ zpl_release(struct inode *ip, struct file *filp)
 }
 
 static int
-zpl_readdir(struct file *filp, void *dirent, filldir_t filldir)
+zpl_iterate(struct file *filp, struct dir_context *ctx)
 {
        struct dentry *dentry = filp->f_path.dentry;
        cred_t *cr = CRED();
        int error;
 
        crhold(cr);
-       error = -zfs_readdir(dentry->d_inode, dirent, filldir,
-           &filp->f_pos, cr);
+       error = -zfs_readdir(dentry->d_inode, ctx, cr);
        crfree(cr);
        ASSERT3S(error, <=, 0);
 
        return (error);
 }
 
+#if !defined(HAVE_VFS_ITERATE)
+static int
+zpl_readdir(struct file *filp, void *dirent, filldir_t filldir)
+{
+       struct dir_context ctx = DIR_CONTEXT_INIT(dirent, filldir, filp->f_pos);
+       int error;
+
+       error = zpl_iterate(filp, &ctx);
+       filp->f_pos = ctx.pos;
+
+       return (error);
+}
+#endif /* HAVE_VFS_ITERATE */
+
 #if defined(HAVE_FSYNC_WITH_DENTRY)
 /*
  * Linux 2.6.x - 2.6.34 API,
@@ -506,7 +519,11 @@ const struct file_operations zpl_file_operations = {
 const struct file_operations zpl_dir_file_operations = {
        .llseek         = generic_file_llseek,
        .read           = generic_read_dir,
+#ifdef HAVE_VFS_ITERATE
+       .iterate        = zpl_iterate,
+#else
        .readdir        = zpl_readdir,
+#endif
        .fsync          = zpl_fsync,
        .unlocked_ioctl = zpl_ioctl,
 #ifdef CONFIG_COMPAT
index eb2c00d..dca1ad6 100644 (file)
@@ -80,6 +80,7 @@
 #include <sys/zfs_vfsops.h>
 #include <sys/zfs_vnops.h>
 #include <sys/zfs_znode.h>
+#include <sys/zap.h>
 #include <sys/vfs.h>
 #include <sys/zpl.h>
 
@@ -91,11 +92,8 @@ typedef struct xattr_filldir {
 } xattr_filldir_t;
 
 static int
-zpl_xattr_filldir(void *arg, const char *name, int name_len,
-    loff_t offset, uint64_t objnum, unsigned int d_type)
+zpl_xattr_filldir(xattr_filldir_t *xf, const char *name, int name_len)
 {
-       xattr_filldir_t *xf = arg;
-
        if (!strncmp(name, XATTR_USER_PREFIX, XATTR_USER_PREFIX_LEN))
                if (!(ITOZSB(xf->inode)->z_flags & ZSB_XATTR))
                        return (0);
@@ -118,12 +116,46 @@ zpl_xattr_filldir(void *arg, const char *name, int name_len,
        return (0);
 }
 
+/*
+ * Read as many directory entry names as will fit in to the provided buffer,
+ * or when no buffer is provided calculate the required buffer size.
+ */
+int
+zpl_xattr_readdir(struct inode *dxip, xattr_filldir_t *xf)
+{
+       zap_cursor_t zc;
+       zap_attribute_t zap;
+       int error;
+
+       zap_cursor_init(&zc, ITOZSB(dxip)->z_os, ITOZ(dxip)->z_id);
+
+       while ((error = -zap_cursor_retrieve(&zc, &zap)) == 0) {
+
+               if (zap.za_integer_length != 8 || zap.za_num_integers != 1) {
+                       error = -ENXIO;
+                       break;
+               }
+
+               error = zpl_xattr_filldir(xf, zap.za_name, strlen(zap.za_name));
+               if (error)
+                       break;
+
+               zap_cursor_advance(&zc);
+       }
+
+       zap_cursor_fini(&zc);
+
+       if (error == -ENOENT)
+               error = 0;
+
+       return (error);
+}
+
 static ssize_t
 zpl_xattr_list_dir(xattr_filldir_t *xf, cred_t *cr)
 {
        struct inode *ip = xf->inode;
        struct inode *dxip = NULL;
-       loff_t pos = 3;  /* skip '.', '..', and '.zfs' entries. */
        int error;
 
        /* Lookup the xattr directory */
@@ -135,8 +167,7 @@ zpl_xattr_list_dir(xattr_filldir_t *xf, cred_t *cr)
                return (error);
        }
 
-       /* Fill provided buffer via zpl_zattr_filldir helper */
-       error = -zfs_readdir(dxip, (void *)xf, zpl_xattr_filldir, &pos, cr);
+       error = zpl_xattr_readdir(dxip, xf);
        iput(dxip);
 
        return (error);
@@ -162,8 +193,8 @@ zpl_xattr_list_sa(xattr_filldir_t *xf)
        while ((nvp = nvlist_next_nvpair(zp->z_xattr_cached, nvp)) != NULL) {
                ASSERT3U(nvpair_type(nvp), ==, DATA_TYPE_BYTE_ARRAY);
 
-               error = zpl_xattr_filldir((void *)xf, nvpair_name(nvp),
-                    strlen(nvpair_name(nvp)), 0, 0, 0);
+               error = zpl_xattr_filldir(xf, nvpair_name(nvp),
+                    strlen(nvpair_name(nvp)));
                if (error)
                        return (error);
        }