From 0f37d0c8bed442dd0d2c1b1dddd68653fa6eec66 Mon Sep 17 00:00:00 2001 From: Richard Yao Date: Wed, 7 Aug 2013 08:53:45 -0400 Subject: [PATCH] Linux 3.11 compat: fops->iterate() 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 Signed-off-by: Brian Behlendorf Closes #1653 Issue #1591 --- config/kernel-vfs-iterate.m4 | 43 ++++++++ config/kernel.m4 | 1 + include/sys/zfs_vnops.h | 4 +- include/sys/zpl.h | 59 +++++++++++ module/zfs/zfs_vnops.c | 12 +-- module/zfs/zpl_ctldir.c | 197 ++++++++++++++++------------------- module/zfs/zpl_file.c | 23 +++- module/zfs/zpl_xattr.c | 49 +++++++-- 8 files changed, 262 insertions(+), 126 deletions(-) create mode 100644 config/kernel-vfs-iterate.m4 diff --git a/config/kernel-vfs-iterate.m4 b/config/kernel-vfs-iterate.m4 new file mode 100644 index 000000000..c2c65622c --- /dev/null +++ b/config/kernel-vfs-iterate.m4 @@ -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 + 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 + 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) + ]) + + ]) +]) diff --git a/config/kernel.m4 b/config/kernel.m4 index 8742bc51f..74ce22ce6 100644 --- a/config/kernel.m4 +++ b/config/kernel.m4 @@ -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" diff --git a/include/sys/zfs_vnops.h b/include/sys/zfs_vnops.h index 75f7c128b..c9fecf8ba 100644 --- a/include/sys/zfs_vnops.h +++ b/include/sys/zfs_vnops.h @@ -31,6 +31,7 @@ #include #include #include +#include #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); diff --git a/include/sys/zpl.h b/include/sys/zpl.h index 61a57ef29..89cf8240c 100644 --- a/include/sys/zpl.h +++ b/include/sys/zpl.h @@ -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 */ diff --git a/module/zfs/zfs_vnops.c b/module/zfs/zfs_vnops.c index db5d3856a..876d44b35 100644 --- a/module/zfs/zfs_vnops.c +++ b/module/zfs/zfs_vnops.c @@ -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) { diff --git a/module/zfs/zpl_ctldir.c b/module/zfs/zpl_ctldir.c index 089701707..1bb646fdf 100644 --- a/module/zfs/zpl_ctldir.c +++ b/module/zfs/zpl_ctldir.c @@ -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) + if (!dir_emit_dots(filp, ctx)) + goto out; + + 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 1: - error = filldir(dirent, "..", 2, 1, parent_ino(dentry), DT_DIR); - if (error) + ctx->pos++; + } + + 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 */ - case 2: - error = filldir(dirent, ZFS_SNAPDIR_NAME, - strlen(ZFS_SNAPDIR_NAME), 2, ZFSCTL_INO_SNAPDIR, DT_DIR); - if (error) - goto out; - - 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) - 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 (!dir_emit_dots(filp, ctx)) + goto out; + + 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 */ - case 1: - error = filldir(dirent, "..", 2, 1, parent_ino(dentry), DT_DIR); - if (error) + if (!dir_emit(ctx, snapname, strlen(snapname), + ZFSCTL_INO_SHARES - id, DT_DIR)) 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; - } } 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 + }; /* diff --git a/module/zfs/zpl_file.c b/module/zfs/zpl_file.c index 299589193..6598c1779 100644 --- a/module/zfs/zpl_file.c +++ b/module/zfs/zpl_file.c @@ -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 diff --git a/module/zfs/zpl_xattr.c b/module/zfs/zpl_xattr.c index eb2c00dfb..dca1ad668 100644 --- a/module/zfs/zpl_xattr.c +++ b/module/zfs/zpl_xattr.c @@ -80,6 +80,7 @@ #include #include #include +#include #include #include @@ -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); }