This patch adds support in GFS2 for the xgetdents syscall by
implementing the xreaddir file operation.

GFS2 uses vbufs (buffer backed by a vector of pages) to store
intermediate data like dirents, stat info and extended attribute
keys/values to eventually bundle them into a container structure
to return to the user.

Signed-off-by: Abhi Das <[email protected]>
---
 fs/gfs2/Makefile     |    3 +-
 fs/gfs2/dir.c        |   80 ++--
 fs/gfs2/dir.h        |   13 +-
 fs/gfs2/export.c     |    2 +-
 fs/gfs2/file.c       |   17 +-
 fs/gfs2/incore.h     |    6 +
 fs/gfs2/inode.c      |    3 +-
 fs/gfs2/inode.h      |    5 +
 fs/gfs2/ops_fstype.c |    4 +
 fs/gfs2/sys.c        |   26 +-
 fs/gfs2/util.c       |    9 +
 fs/gfs2/xattr.c      |   27 +-
 fs/gfs2/xattr.h      |   23 ++
 fs/gfs2/xreaddir.c   | 1024 ++++++++++++++++++++++++++++++++++++++++++++++++++
 fs/gfs2/xreaddir.h   |   84 +++++
 15 files changed, 1260 insertions(+), 66 deletions(-)
 create mode 100644 fs/gfs2/xreaddir.c
 create mode 100644 fs/gfs2/xreaddir.h

diff --git a/fs/gfs2/Makefile b/fs/gfs2/Makefile
index 8612820..da8253b 100644
--- a/fs/gfs2/Makefile
+++ b/fs/gfs2/Makefile
@@ -4,7 +4,8 @@ gfs2-y := acl.o bmap.o dir.o xattr.o glock.o \
        glops.o log.o lops.o main.o meta_io.o \
        aops.o dentry.o export.o file.o \
        ops_fstype.o inode.o quota.o \
-       recovery.o rgrp.o super.o sys.o trans.o util.o
+       recovery.o rgrp.o super.o sys.o \
+       trans.o util.o xreaddir.o
 
 gfs2-$(CONFIG_GFS2_FS_LOCKING_DLM) += lock_dlm.o
 
diff --git a/fs/gfs2/dir.c b/fs/gfs2/dir.c
index 1a349f9..21f5926 100644
--- a/fs/gfs2/dir.c
+++ b/fs/gfs2/dir.c
@@ -74,15 +74,13 @@
 #include "trans.h"
 #include "bmap.h"
 #include "util.h"
+#include "xreaddir.h"
 
 #define IS_LEAF     1 /* Hashed (leaf) directory */
 #define IS_DINODE   2 /* Linear (stuffed dinode block) directory */
 
 #define MAX_RA_BLOCKS 32 /* max read-ahead blocks */
 
-#define gfs2_disk_hash2offset(h) (((u64)(h)) >> 1)
-#define gfs2_dir_offset2hash(p) ((u32)(((u64)(p)) << 1))
-
 struct qstr gfs2_qdot __read_mostly;
 struct qstr gfs2_qdotdot __read_mostly;
 
@@ -1185,17 +1183,13 @@ out_kfree:
  *   lt: returns -1
  *   eq: returns 0
  */
-
-static int compare_dents(const void *a, const void *b)
+int compare_dents_i(const struct gfs2_dirent *dent_a,
+                 const struct gfs2_dirent *dent_b)
 {
-       const struct gfs2_dirent *dent_a, *dent_b;
        u32 hash_a, hash_b;
        int ret = 0;
 
-       dent_a = *(const struct gfs2_dirent **)a;
        hash_a = be32_to_cpu(dent_a->de_hash);
-
-       dent_b = *(const struct gfs2_dirent **)b;
        hash_b = be32_to_cpu(dent_b->de_hash);
 
        if (hash_a > hash_b)
@@ -1217,6 +1211,12 @@ static int compare_dents(const void *a, const void *b)
        return ret;
 }
 
+int compare_dents(const void *a, const void *b)
+{
+       return compare_dents_i(*(const struct gfs2_dirent **)a,
+                              *(const struct gfs2_dirent **)b);
+}
+
 /**
  * do_filldir_main - read out directory entries
  * @dip: The GFS2 inode
@@ -1234,13 +1234,14 @@ static int compare_dents(const void *a, const void *b)
  */
 
 static int do_filldir_main(struct gfs2_inode *dip, struct dir_context *ctx,
-                          const struct gfs2_dirent **darr, u32 entries,
-                          int *copied)
+                          struct gfs2_xrdir_ctx *xc, const struct gfs2_dirent 
**darr,
+                          u32 entries, int *copied)
 {
        const struct gfs2_dirent *dent, *dent_next;
        u64 off, off_next;
+       u64 *dst_pos = xc ? &xc->xc_offset : &ctx->pos;
        unsigned int x, y;
-       int run = 0;
+       int run = 0, error = 0;
 
        sort(darr, entries, sizeof(struct gfs2_dirent *), compare_dents, NULL);
 
@@ -1256,29 +1257,39 @@ static int do_filldir_main(struct gfs2_inode *dip, 
struct dir_context *ctx,
                        dent_next = darr[y];
                        off_next = be32_to_cpu(dent_next->de_hash);
                        off_next = gfs2_disk_hash2offset(off_next);
-
-                       if (off < ctx->pos)
+                       if (off < *dst_pos)
                                continue;
-                       ctx->pos = off;
+
+                       *dst_pos = off;
 
                        if (off_next == off) {
-                               if (*copied && !run)
+                               if (*copied && !run) {
+                                       if (xc)
+                                               gfs2_xrdir_partial_collect(xc);
                                        return 1;
+                               }
                                run = 1;
                        } else
                                run = 0;
                } else {
-                       if (off < ctx->pos)
+                       if (off < *dst_pos)
                                continue;
-                       ctx->pos = off;
+                       *dst_pos = off;
                }
 
-               if (!dir_emit(ctx, (const char *)(dent + 1),
-                               be16_to_cpu(dent->de_name_len),
-                               be64_to_cpu(dent->de_inum.no_addr),
-                               be16_to_cpu(dent->de_type)))
-                       return 1;
-
+               if (xc) {
+                       error = gfs2_xrdir_collect_dents(dent, off, xc);
+                       if (error) {
+                               gfs2_xrdir_partial_collect(xc);
+                               return 1;
+                       }
+               } else {
+                       if (!dir_emit(ctx, (const char *)(dent + 1),
+                                     be16_to_cpu(dent->de_name_len),
+                                     be64_to_cpu(dent->de_inum.no_addr),
+                                     be16_to_cpu(dent->de_type)))
+                               return 1;
+               }
                *copied = 1;
        }
 
@@ -1286,8 +1297,7 @@ static int do_filldir_main(struct gfs2_inode *dip, struct 
dir_context *ctx,
           do_filldir fxn, we get the next entry instead of the last one in the
           current leaf */
 
-       ctx->pos++;
-
+       (*dst_pos)++;
        return 0;
 }
 
@@ -1311,8 +1321,8 @@ static void gfs2_free_sort_buffer(void *ptr)
 }
 
 static int gfs2_dir_read_leaf(struct inode *inode, struct dir_context *ctx,
-                             int *copied, unsigned *depth,
-                             u64 leaf_no)
+                             struct gfs2_xrdir_ctx *xc, int *copied,
+                             unsigned *depth, u64 leaf_no)
 {
        struct gfs2_inode *ip = GFS2_I(inode);
        struct gfs2_sbd *sdp = GFS2_SB(inode);
@@ -1389,7 +1399,7 @@ static int gfs2_dir_read_leaf(struct inode *inode, struct 
dir_context *ctx,
        } while(lfn);
 
        BUG_ON(entries2 != entries);
-       error = do_filldir_main(ip, ctx, darr, entries, copied);
+       error = do_filldir_main(ip, ctx, xc, darr, entries, copied);
 out_free:
        for(i = 0; i < leaf; i++)
                brelse(larr[i]);
@@ -1454,7 +1464,7 @@ static void gfs2_dir_readahead(struct inode *inode, 
unsigned hsize, u32 index,
  */
 
 static int dir_e_read(struct inode *inode, struct dir_context *ctx,
-                     struct file_ra_state *f_ra)
+                     struct gfs2_xrdir_ctx *xc, struct file_ra_state *f_ra)
 {
        struct gfs2_inode *dip = GFS2_I(inode);
        u32 hsize, len = 0;
@@ -1465,7 +1475,7 @@ static int dir_e_read(struct inode *inode, struct 
dir_context *ctx,
        unsigned depth = 0;
 
        hsize = 1 << dip->i_depth;
-       hash = gfs2_dir_offset2hash(ctx->pos);
+       hash = gfs2_dir_offset2hash(xc ? xc->xc_offset : ctx->pos);
        index = hash >> (32 - dip->i_depth);
 
        if (dip->i_hash_cache == NULL)
@@ -1477,7 +1487,7 @@ static int dir_e_read(struct inode *inode, struct 
dir_context *ctx,
        gfs2_dir_readahead(inode, hsize, index, f_ra);
 
        while (index < hsize) {
-               error = gfs2_dir_read_leaf(inode, ctx,
+               error = gfs2_dir_read_leaf(inode, ctx, xc,
                                           &copied, &depth,
                                           be64_to_cpu(lp[index]));
                if (error)
@@ -1493,7 +1503,7 @@ static int dir_e_read(struct inode *inode, struct 
dir_context *ctx,
 }
 
 int gfs2_dir_read(struct inode *inode, struct dir_context *ctx,
-                 struct file_ra_state *f_ra)
+                 struct gfs2_xrdir_ctx *xc, struct file_ra_state *f_ra)
 {
        struct gfs2_inode *dip = GFS2_I(inode);
        struct gfs2_sbd *sdp = GFS2_SB(inode);
@@ -1507,7 +1517,7 @@ int gfs2_dir_read(struct inode *inode, struct dir_context 
*ctx,
                return 0;
 
        if (dip->i_diskflags & GFS2_DIF_EXHASH)
-               return dir_e_read(inode, ctx, f_ra);
+               return dir_e_read(inode, ctx, xc, f_ra);
 
        if (!gfs2_is_stuffed(dip)) {
                gfs2_consist_inode(dip);
@@ -1539,7 +1549,7 @@ int gfs2_dir_read(struct inode *inode, struct dir_context 
*ctx,
                        error = -EIO;
                        goto out;
                }
-               error = do_filldir_main(dip, ctx, darr,
+               error = do_filldir_main(dip, ctx, xc, darr,
                                        dip->i_entries, &copied);
 out:
                kfree(darr);
diff --git a/fs/gfs2/dir.h b/fs/gfs2/dir.h
index 126c65d..8d40590 100644
--- a/fs/gfs2/dir.h
+++ b/fs/gfs2/dir.h
@@ -12,6 +12,10 @@
 
 #include <linux/dcache.h>
 #include <linux/crc32.h>
+#include "util.h"
+
+#define gfs2_disk_hash2offset(h) (((u64)(h)) >> 1)
+#define gfs2_dir_offset2hash(p) ((u32)(((u64)(p)) << 1))
 
 struct inode;
 struct gfs2_inode;
@@ -25,6 +29,13 @@ struct gfs2_diradd {
        struct buffer_head *bh;
 };
 
+typedef int (*process_dent_t)(const struct gfs2_dirent *, loff_t, void *, 
filldir_t);
+extern int compare_dents_i(const struct gfs2_dirent *dent_a,
+                          const struct gfs2_dirent *dent_b);
+extern int foreach_dent(u64 *offset, void *opaque, filldir_t filldir,
+                       const struct gfs2_dirent **darr, u32 entries,
+                       int *copied, process_dent_t pd_fn);
+ 
 extern struct inode *gfs2_dir_search(struct inode *dir,
                                     const struct qstr *filename,
                                     bool fail_on_exist);
@@ -40,7 +51,7 @@ static inline void gfs2_dir_no_add(struct gfs2_diradd *da)
 }
 extern int gfs2_dir_del(struct gfs2_inode *dip, const struct dentry *dentry);
 extern int gfs2_dir_read(struct inode *inode, struct dir_context *ctx,
-                        struct file_ra_state *f_ra);
+                        struct gfs2_xrdir_ctx *xc, struct file_ra_state *f_ra);
 extern int gfs2_dir_mvino(struct gfs2_inode *dip, const struct qstr *filename,
                          const struct gfs2_inode *nip, unsigned int new_type);
 
diff --git a/fs/gfs2/export.c b/fs/gfs2/export.c
index 8b9b377..1f5085d 100644
--- a/fs/gfs2/export.c
+++ b/fs/gfs2/export.c
@@ -114,7 +114,7 @@ static int gfs2_get_name(struct dentry *parent, char *name,
        if (error)
                return error;
 
-       error = gfs2_dir_read(dir, &gnfd.ctx, &f_ra);
+       error = gfs2_dir_read(dir, &gnfd.ctx, NULL, &f_ra);
 
        gfs2_glock_dq_uninit(&gh);
 
diff --git a/fs/gfs2/file.c b/fs/gfs2/file.c
index 26b3f95..d2d7561f 100644
--- a/fs/gfs2/file.c
+++ b/fs/gfs2/file.c
@@ -16,6 +16,8 @@
 #include <linux/blkdev.h>
 #include <linux/mm.h>
 #include <linux/mount.h>
+#include <linux/stat.h>
+#include <linux/sort.h>
 #include <linux/fs.h>
 #include <linux/gfs2_ondisk.h>
 #include <linux/falloc.h>
@@ -40,6 +42,7 @@
 #include "rgrp.h"
 #include "trans.h"
 #include "util.h"
+#include "xreaddir.h"
 
 /**
  * gfs2_llseek - seek to a location in a file
@@ -100,7 +103,7 @@ static int gfs2_readdir(struct file *file, struct 
dir_context *ctx)
        if (error)
                return error;
 
-       error = gfs2_dir_read(dir, ctx, &file->f_ra);
+       error = gfs2_dir_read(dir, ctx, NULL, &file->f_ra);
 
        gfs2_glock_dq_uninit(&d_gh);
 
@@ -562,8 +565,13 @@ int gfs2_open_common(struct inode *inode, struct file 
*file)
                return -ENOMEM;
 
        mutex_init(&fp->f_fl_mutex);
-
        gfs2_assert_warn(GFS2_SB(inode), !file->private_data);
+
+       if (S_ISDIR(inode->i_mode)) {
+               ret = gfs2_xrdir_ctx_init(fp, GFS2_SB(inode));
+               if (ret)
+                       return ret;
+       }
        file->private_data = fp;
        return 0;
 }
@@ -617,6 +625,9 @@ static int gfs2_release(struct inode *inode, struct file 
*file)
 {
        struct gfs2_inode *ip = GFS2_I(inode);
 
+       if (S_ISDIR(ip->i_inode.i_mode))
+               gfs2_xrdir_ctx_uninit((struct gfs2_file *)file->private_data);
+
        kfree(file->private_data);
        file->private_data = NULL;
 
@@ -1075,6 +1086,7 @@ const struct file_operations gfs2_file_fops = {
 
 const struct file_operations gfs2_dir_fops = {
        .iterate        = gfs2_readdir,
+       .xreaddir       = gfs2_xreaddir,
        .unlocked_ioctl = gfs2_ioctl,
        .open           = gfs2_open,
        .release        = gfs2_release,
@@ -1105,6 +1117,7 @@ const struct file_operations gfs2_file_fops_nolock = {
 
 const struct file_operations gfs2_dir_fops_nolock = {
        .iterate        = gfs2_readdir,
+       .xreaddir       = gfs2_xreaddir,
        .unlocked_ioctl = gfs2_ioctl,
        .open           = gfs2_open,
        .release        = gfs2_release,
diff --git a/fs/gfs2/incore.h b/fs/gfs2/incore.h
index 67d310c..f86b6d3 100644
--- a/fs/gfs2/incore.h
+++ b/fs/gfs2/incore.h
@@ -414,6 +414,7 @@ static inline struct gfs2_sbd *GFS2_SB(const struct inode 
*inode)
 struct gfs2_file {
        struct mutex f_fl_mutex;
        struct gfs2_holder f_fl_gh;
+       struct gfs2_xrdir_ctx *f_xrctx;
 };
 
 struct gfs2_revoke_replay {
@@ -570,6 +571,8 @@ struct gfs2_tune {
        unsigned int gt_complain_secs;
        unsigned int gt_statfs_quantum;
        unsigned int gt_statfs_slow;
+       unsigned int gt_max_vb_pages; /* Max pages to utilize for vector-page 
buffers */
+       unsigned int gt_max_xrdir_dents; /* Maximum dents to process per 
collect cycle (conserves memory) */
 };
 
 enum {
@@ -812,6 +815,9 @@ struct gfs2_sbd {
        struct dentry *debugfs_dentry_glocks;
        struct dentry *debugfs_dentry_glstats;
        struct dentry *debugfs_dentry_sbstats;
+
+       /* Vector Pages accounting */
+       atomic_t sd_vb_page_count;
 };
 
 static inline void gfs2_glstats_inc(struct gfs2_glock *gl, int which)
diff --git a/fs/gfs2/inode.c b/fs/gfs2/inode.c
index e62e594..46c3602 100644
--- a/fs/gfs2/inode.c
+++ b/fs/gfs2/inode.c
@@ -1833,7 +1833,8 @@ static int gfs2_getattr(struct vfsmount *mnt, struct 
dentry *dentry,
                }
        }
 
-       generic_fillattr(inode, stat);
+       gfs2_getattr_i(ip, stat);
+
        if (unlock)
                gfs2_glock_dq_uninit(&gh);
        else if (frozen_root && atomic_dec_and_test(&sdp->sd_frozen_root))
diff --git a/fs/gfs2/inode.h b/fs/gfs2/inode.h
index ba4d949..665f508 100644
--- a/fs/gfs2/inode.h
+++ b/fs/gfs2/inode.h
@@ -93,6 +93,11 @@ err:
        return -EIO;
 }
 
+static inline void gfs2_getattr_i(struct gfs2_inode *ip, struct kstat *stat)
+{
+       generic_fillattr(&ip->i_inode, stat);
+}
+
 extern struct inode *gfs2_inode_lookup(struct super_block *sb, unsigned type, 
                                       u64 no_addr, u64 no_formal_ino,
                                       int non_block);
diff --git a/fs/gfs2/ops_fstype.c b/fs/gfs2/ops_fstype.c
index bc564c0..2d541ba 100644
--- a/fs/gfs2/ops_fstype.c
+++ b/fs/gfs2/ops_fstype.c
@@ -60,6 +60,8 @@ static void gfs2_tune_init(struct gfs2_tune *gt)
        gt->gt_new_files_jdata = 0;
        gt->gt_max_readahead = 1 << 18;
        gt->gt_complain_secs = 10;
+       gt->gt_max_vb_pages = 65536;
+       gt->gt_max_xrdir_dents = 25000;
 }
 
 static struct gfs2_sbd *init_sbd(struct super_block *sb)
@@ -135,6 +137,8 @@ static struct gfs2_sbd *init_sbd(struct super_block *sb)
        atomic_set(&sdp->sd_frozen_root, 0);
        init_waitqueue_head(&sdp->sd_frozen_root_wait);
 
+       atomic_set(&sdp->sd_vb_page_count, 0);
+
        return sdp;
 }
 
diff --git a/fs/gfs2/sys.c b/fs/gfs2/sys.c
index 3ab566b..279aa86 100644
--- a/fs/gfs2/sys.c
+++ b/fs/gfs2/sys.c
@@ -548,8 +548,8 @@ static ssize_t quota_scale_store(struct gfs2_sbd *sdp, 
const char *buf,
        return len;
 }
 
-static ssize_t tune_set(struct gfs2_sbd *sdp, unsigned int *field,
-                       int check_zero, const char *buf, size_t len)
+static ssize_t tune_set(struct gfs2_sbd *sdp, unsigned int *field, int 
check_zero,
+                       unsigned int min, unsigned int max, const char *buf, 
size_t len)
 {
        struct gfs2_tune *gt = &sdp->sd_tune;
        unsigned int x;
@@ -562,6 +562,12 @@ static ssize_t tune_set(struct gfs2_sbd *sdp, unsigned int 
*field,
        if (check_zero && !x)
                return -EINVAL;
 
+       if (min && x < min)
+               return -EINVAL;
+
+       if (max && x > max)
+               return -EINVAL;
+
        spin_lock(&gt->gt_spin);
        *field = x;
        spin_unlock(&gt->gt_spin);
@@ -578,13 +584,21 @@ static ssize_t name##_show(struct gfs2_sbd *sdp, char 
*buf)                   \
 }                                                                             \
 TUNE_ATTR_3(name, name##_show, store)
 
-#define TUNE_ATTR(name, check_zero)                                           \
+#define TUNE_ATTR(name, check_zero)                                            
    \
+static ssize_t name##_store(struct gfs2_sbd *sdp, const char *buf, size_t len) 
    \
+{                                                                              
    \
+       return tune_set(sdp, &sdp->sd_tune.gt_##name, check_zero, 0, 0, buf, 
len); \
+}                                                                              
    \
+TUNE_ATTR_2(name, name##_store)
+
+#define TUNE_ATTR_B(name, min, max)                                           \
 static ssize_t name##_store(struct gfs2_sbd *sdp, const char *buf, size_t len)\
 {                                                                             \
-       return tune_set(sdp, &sdp->sd_tune.gt_##name, check_zero, buf, len);  \
+       return tune_set(sdp, &sdp->sd_tune.gt_##name, 0, min, max, buf, len); \
 }                                                                             \
 TUNE_ATTR_2(name, name##_store)
 
+
 TUNE_ATTR(quota_warn_period, 0);
 TUNE_ATTR(quota_quantum, 0);
 TUNE_ATTR(max_readahead, 0);
@@ -593,6 +607,8 @@ TUNE_ATTR(statfs_slow, 0);
 TUNE_ATTR(new_files_jdata, 0);
 TUNE_ATTR(statfs_quantum, 1);
 TUNE_ATTR_3(quota_scale, quota_scale_show, quota_scale_store);
+TUNE_ATTR_B(max_vb_pages, 32, 8388608); /* total capacity can be 128K to 32G 
bytes */
+TUNE_ATTR(max_xrdir_dents, 0);
 
 static struct attribute *tune_attrs[] = {
        &tune_attr_quota_warn_period.attr,
@@ -603,6 +619,8 @@ static struct attribute *tune_attrs[] = {
        &tune_attr_statfs_quantum.attr,
        &tune_attr_quota_scale.attr,
        &tune_attr_new_files_jdata.attr,
+       &tune_attr_max_vb_pages.attr,
+       &tune_attr_max_xrdir_dents.attr,
        NULL,
 };
 
diff --git a/fs/gfs2/util.c b/fs/gfs2/util.c
index 2c1aee3..793f69e 100644
--- a/fs/gfs2/util.c
+++ b/fs/gfs2/util.c
@@ -301,6 +301,9 @@ static int vp_extend(struct vp_ctx *vpx, int size)
 {
        struct gfs2_sbd *sdp = vpx->vp_sdp;
 
+       if ((gfs2_tune_get(sdp, gt_max_vb_pages)
+            - atomic_read(&sdp->sd_vb_page_count)) < size)
+               goto out;
        /* first make room for more pointers */
        if (size <= 0)
                return -EINVAL;
@@ -317,6 +320,7 @@ static int vp_extend(struct vp_ctx *vpx, int size)
                goto out;
 
        vpx->vp_size += size;
+       atomic_add(size, &sdp->sd_vb_page_count);
        return 0;
 out:
        return -ENOMEM;
@@ -328,6 +332,9 @@ int vp_init(struct gfs2_sbd *sdp, struct vbuf *vb, int 
init_cap)
        struct vp_ctx *vpx;
 
        cap = DIV_ROUND_UP(init_cap, PAGE_SIZE);
+       if ((gfs2_tune_get(sdp, gt_max_vb_pages)
+            - atomic_read(&sdp->sd_vb_page_count)) < cap)
+               goto out;
 
        vpx = kmalloc(sizeof(struct vp_ctx), GFP_KERNEL);
        if (vpx == NULL)
@@ -344,6 +351,7 @@ int vp_init(struct gfs2_sbd *sdp, struct vbuf *vb, int 
init_cap)
 
        vpx->vp_baseptr = vpx->vp_top = page_address(vpx->vp_pages[0]);
        vpx->vp_sdp = sdp;
+       atomic_add(cap, &sdp->sd_vb_page_count);
        vb->v_ptr = vpx->vp_baseptr;
        vb->v_opaque = vpx;
 
@@ -373,6 +381,7 @@ void vp_uninit(struct vbuf *vb)
 
        vp_free_pages(vpx);
        kfree(vpx->vp_pages);
+       atomic_sub(vpx->vp_size, &vpx->vp_sdp->sd_vb_page_count);
        kfree(vpx);
        vb->v_ptr = vb->v_opaque = NULL;
 }
diff --git a/fs/gfs2/xattr.c b/fs/gfs2/xattr.c
index 0b81f78..f156b21 100644
--- a/fs/gfs2/xattr.c
+++ b/fs/gfs2/xattr.c
@@ -11,6 +11,7 @@
 #include <linux/spinlock.h>
 #include <linux/completion.h>
 #include <linux/buffer_head.h>
+#include <linux/sort.h>
 #include <linux/xattr.h>
 #include <linux/gfs2_ondisk.h>
 #include <linux/posix_acl_xattr.h>
@@ -19,6 +20,7 @@
 #include "gfs2.h"
 #include "incore.h"
 #include "acl.h"
+#include "dir.h"
 #include "xattr.h"
 #include "glock.h"
 #include "inode.h"
@@ -27,6 +29,7 @@
 #include "rgrp.h"
 #include "trans.h"
 #include "util.h"
+#include "xreaddir.h"
 
 /**
  * ea_calc_size - returns the acutal number of bytes the request will take up
@@ -72,10 +75,6 @@ static int ea_check_size(struct gfs2_sbd *sdp, unsigned int 
nsize, size_t dsize)
        return 0;
 }
 
-typedef int (*ea_call_t) (struct gfs2_inode *ip, struct buffer_head *bh,
-                         struct gfs2_ea_header *ea,
-                         struct gfs2_ea_header *prev, void *private);
-
 static int ea_foreach_i(struct gfs2_inode *ip, struct buffer_head *bh,
                        ea_call_t ea_call, void *data)
 {
@@ -113,7 +112,7 @@ fail:
        return -EIO;
 }
 
-static int ea_foreach(struct gfs2_inode *ip, ea_call_t ea_call, void *data)
+int ea_foreach(struct gfs2_inode *ip, ea_call_t ea_call, void *data)
 {
        struct buffer_head *bh, *eabh;
        __be64 *eablk, *end;
@@ -374,28 +373,14 @@ static int ea_list_i(struct gfs2_inode *ip, struct 
buffer_head *bh,
                return 0;
 
        if (er->er_data_len) {
-               char *prefix = NULL;
+               char prefix[9];
                unsigned int l = 0;
                char c = 0;
 
                if (ei->ei_size + ea_size > er->er_data_len)
                        return -ERANGE;
 
-               switch (ea->ea_type) {
-               case GFS2_EATYPE_USR:
-                       prefix = "user.";
-                       l = 5;
-                       break;
-               case GFS2_EATYPE_SYS:
-                       prefix = "system.";
-                       l = 7;
-                       break;
-               case GFS2_EATYPE_SECURITY:
-                       prefix = "security.";
-                       l = 9;
-                       break;
-               }
-
+               l = ea_prefix(ea, prefix, 9);
                BUG_ON(l == 0);
 
                memcpy(er->er_data + ei->ei_size, prefix, l);
diff --git a/fs/gfs2/xattr.h b/fs/gfs2/xattr.h
index d392f83..c09f090 100644
--- a/fs/gfs2/xattr.h
+++ b/fs/gfs2/xattr.h
@@ -10,6 +10,8 @@
 #ifndef __EATTR_DOT_H__
 #define __EATTR_DOT_H__
 
+#include "dir.h"
+
 struct gfs2_inode;
 struct iattr;
 
@@ -53,9 +55,30 @@ struct gfs2_ea_location {
        struct gfs2_ea_header *el_prev;
 };
 
+static __inline__ int ea_prefix(struct gfs2_ea_header *ea, char *buf, int size)
+{
+       BUG_ON(size < 9);
+       switch (ea->ea_type) {
+       case GFS2_EATYPE_USR:
+               strncpy(buf, "user.", 5);
+               return 5;
+       case GFS2_EATYPE_SYS:
+               strncpy(buf, "system.", 7);
+               return 7;
+       case GFS2_EATYPE_SECURITY:
+               strncpy(buf, "security.", 9);
+               return 9;
+       }
+       return 0;
+}
+
 extern int __gfs2_xattr_set(struct inode *inode, const char *name,
                            const void *value, size_t size,
                            int flags, int type);
+typedef int (*ea_call_t) (struct gfs2_inode *ip, struct buffer_head *bh,
+                         struct gfs2_ea_header *ea,
+                         struct gfs2_ea_header *prev, void *private);
+extern int ea_foreach(struct gfs2_inode *ip, ea_call_t ea_call, void *data);
 extern ssize_t gfs2_listxattr(struct dentry *dentry, char *buffer, size_t 
size);
 extern int gfs2_ea_dealloc(struct gfs2_inode *ip);
 
diff --git a/fs/gfs2/xreaddir.c b/fs/gfs2/xreaddir.c
new file mode 100644
index 0000000..44e0232
--- /dev/null
+++ b/fs/gfs2/xreaddir.c
@@ -0,0 +1,1024 @@
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/completion.h>
+#include <linux/buffer_head.h>
+#include <linux/pagemap.h>
+#include <linux/uio.h>
+#include <linux/blkdev.h>
+#include <linux/mm.h>
+#include <linux/mount.h>
+#include <linux/stat.h>
+#include <linux/sort.h>
+#include <linux/fs.h>
+#include <linux/gfs2_ondisk.h>
+#include <linux/falloc.h>
+#include <linux/swap.h>
+#include <linux/crc32.h>
+#include <linux/writeback.h>
+#include <asm/uaccess.h>
+#include <linux/dlm.h>
+#include <linux/dlm_plock.h>
+
+#include "gfs2.h"
+#include "incore.h"
+#include "bmap.h"
+#include "dir.h"
+#include "glock.h"
+#include "glops.h"
+#include "inode.h"
+#include "log.h"
+#include "meta_io.h"
+#include "quota.h"
+#include "rgrp.h"
+#include "trans.h"
+#include "util.h"
+#include "xattr.h"
+#include "xreaddir.h"
+
+static int gfs2_dirent_dot_or_dotdot(const struct gfs2_dirent *dent)
+{
+       const char *name = (char *)(dent + 1);
+
+       if (be16_to_cpu(dent->de_type) == DT_DIR) {
+               if (be16_to_cpu(dent->de_name_len) == 1 && name[0] == '.')
+                       return 1;
+               if (be16_to_cpu(dent->de_name_len) == 2 &&
+                   strncmp(name, "..", 2) == 0)
+                       return 1;
+       }
+       return 0;   
+}
+
+/*
+ * Compare the inode blocks of two entries
+ */
+int ctx_compare_dent_iblks(void *opaque, const void *a, const void *b)
+{
+       struct gfs2_xrdir_ctx *xc = opaque;
+       const struct gfs2_xdirent *a_vb_p = *(struct gfs2_xdirent **)a;
+       const struct gfs2_xdirent *b_vb_p = *(struct gfs2_xdirent **)b;
+       u64 a_blkno, b_blkno;
+
+       vp_read(&xc->xc_dirents, &a_blkno, &a_vb_p->x_ino, sizeof(u64));
+       vp_read(&xc->xc_dirents, &b_blkno, &b_vb_p->x_ino, sizeof(u64));
+
+       if (a_blkno > b_blkno)
+               return 1;
+       else
+               return -1;
+}
+
+/*
+ * Compare the xattr blocks of two entries
+ */
+int ctx_compare_dent_eablks(void *opaque, const void *a, const void *b)
+{
+       struct gfs2_xrdir_ctx *xc = opaque;
+       const struct gfs2_xdirent *a_vb_p = *(struct gfs2_xdirent **)a;
+       const struct gfs2_xdirent *b_vb_p = *(struct gfs2_xdirent **)b;
+       u64 a_blkno, b_blkno;
+
+       vp_read(&xc->xc_dirents, &a_blkno, &a_vb_p->x_eablk, sizeof(u64));
+       vp_read(&xc->xc_dirents, &b_blkno, &b_vb_p->x_eablk, sizeof(u64));
+
+       if (a_blkno > b_blkno)
+               return 1;
+       else
+               return -1;
+}
+
+/*
+ * Compare two entries based on their hash value
+ */
+int ctx_compare_dents(void *opaque, const void *a, const void *b)
+{
+       struct gfs2_xrdir_ctx *xc = opaque;
+       const struct gfs2_xdirent *a_vb_p = *(struct gfs2_xdirent **)a;
+       const struct gfs2_xdirent *b_vb_p = *(struct gfs2_xdirent **)b;
+       u32 a_hash, b_hash;
+       int ret = 0;
+
+       vp_read(&xc->xc_dirents, &a_hash, &a_vb_p->x_hash, sizeof(u32));
+       vp_read(&xc->xc_dirents, &b_hash, &b_vb_p->x_hash, sizeof(u32));
+
+       if (a_hash > b_hash)
+               ret = 1;
+       else if (a_hash < b_hash)
+               ret = -1;
+       else {
+               unsigned int len_a, len_b;
+               vp_read(&xc->xc_dirents, &len_a, &a_vb_p->x_namelen, 
sizeof(unsigned int));
+               vp_read(&xc->xc_dirents, &len_b, &b_vb_p->x_namelen, 
sizeof(unsigned int));
+
+               if (len_a > len_b)
+                       ret = 1;
+               else if (len_a < len_b)
+                       ret = -1;
+               else {
+                       char *a, *b, *buf;
+                       buf = kmalloc(len_a * 2, GFP_KERNEL);
+                       if (buf == NULL) {
+                               ret = 0;
+                               goto out;
+                       }
+                       a = buf;
+                       b = buf + len_a;
+
+                       vp_read(&xc->xc_dirents, a, a_vb_p->x_name, len_a);
+                       vp_read(&xc->xc_dirents, b, b_vb_p->x_name, len_b);
+
+                       ret = memcmp(a, b, len_a);
+
+                       kfree(buf);
+               }
+       }
+out:
+       return ret;
+}
+
+void gfs2_xrdir_ctx_uninit(struct gfs2_file *fp)
+{
+       struct gfs2_xrdir_ctx *xc;
+
+       if (!fp || !fp->f_xrctx)
+               return;
+
+       xc = fp->f_xrctx;
+       if (xc->xc_vb_dptrs)
+               kfree(xc->xc_vb_dptrs);
+       vp_uninit(&xc->xc_xattr_values);
+       vp_uninit(&xc->xc_xattr_keys);
+       vp_uninit(&xc->xc_dirents);
+       kfree(xc);
+       fp->f_xrctx = NULL;
+}
+
+int gfs2_xrdir_ctx_init(struct gfs2_file *fp, struct gfs2_sbd *sdp)
+{
+       struct gfs2_xrdir_ctx *xc;
+       if (!fp)
+               return -EINVAL;
+
+       BUG_ON(fp->f_xrctx != NULL);
+
+       xc = kzalloc(sizeof(struct gfs2_xrdir_ctx), GFP_KERNEL);
+       if (xc == NULL)
+               return -ENOMEM;
+
+       if (vp_init(sdp, &xc->xc_dirents, 1) ||
+           vp_init(sdp, &xc->xc_xattr_keys, 1) ||
+           vp_init(sdp, &xc->xc_xattr_values, 1)) {
+               gfs2_xrdir_ctx_uninit(fp);
+               kfree(xc);
+               return -ENOMEM;
+       }
+       xc->xc_flags |= XC_FL_ALLOCATED;
+       fp->f_xrctx = xc;
+
+       return 0;
+}
+
+/*
+ * There was an error while collecting entries.
+ * Figure out what happened and twiddle flags
+ * appropriately.
+ */
+void gfs2_xrdir_partial_collect(struct gfs2_xrdir_ctx *xc)
+{
+       if (xc->xc_flags & XC_FL_GATHER_PART_INT ||
+           xc->xc_flags & XC_FL_ERROR)
+         return;
+
+       /*
+        * We encountered a hash collision situation. We've read
+        * entries in hash order up to the point (not including)
+        * the colliding hashes. Setting XC_FL_HASH_COLL denotes
+        * that. Also setting XC_FL_HASH_COLL_NXT so we know
+        * that the next time we collect entries, the hash
+        * colliding entries will be part of the collection
+        */
+       xc->xc_flags |= (XC_FL_HASH_COLL | XC_FL_HASH_COLL_NXT);
+       xc->xc_flags |= (XC_FL_GATHER_PARTS | XC_FL_GATHER_PART_INT);
+       xc->xc_hash_coll_off = xc->xc_offset;
+
+       return;
+}
+
+/*
+ * We have run out of memory while collecting entries and
+ * don't have a single entry to return to the user. We deal
+ * with such a situation by halving the number of dents we
+ * tried to read last time and returning -EAGAIN to the user
+ * so we can have a go at it again
+ */
+static int gfs2_xrdir_handle_oom(struct gfs2_xrdir_ctx *xc)
+{
+       /* next time, only try half the number of dents */
+       xc->xc_dent_cap = DIV_ROUND_UP(xc->xc_count, 2);
+       /* clear out some flags */
+       xc->xc_flags &= ~(XC_FL_ERROR_OOM | XC_FL_ERROR);
+       xc->xc_flags &= ~XC_FL_GATHER_PART_INT;
+       /* In an oom situation, we're going to re-read fewer
+        * entries from the same collection. This may or may
+        * not hit the hash collision we recorded (if any).
+        * So, we reset the relevant flags */
+       xc->xc_flags &= ~(XC_FL_HASH_COLL | XC_FL_HASH_COLL_NXT);
+       xc->xc_hash_coll_off = 0;
+
+       return -EAGAIN;
+}
+
+static int gfs2_xrdir_collect_errcheck(struct gfs2_xrdir_ctx *xc, int error)
+{
+       if (error < 0) { /* If we're out of memory */
+               if (error == -ENOMEM)
+                       xc->xc_flags |= XC_FL_ERROR_OOM;
+               xc->xc_flags |= XC_FL_ERROR;
+               return error;
+       } else {
+               if ((xc->xc_dent_cap && xc->xc_count >= xc->xc_dent_cap) ||
+                   (xc->xc_dent_memcap && vp_get_size(&xc->xc_dirents) 
+                    >= xc->xc_dent_memcap)) {
+                       /* We hit one of our limits, flag and return */
+                       xc->xc_flags |= XC_FL_GATHER_PARTS;
+                       xc->xc_flags |= XC_FL_GATHER_PART_INT;
+                       return -EOVERFLOW;
+               }
+               return 0;
+       }
+}
+
+/*
+ * To reduce disk-seeking, we collect all the info in stages.
+ * In each stage, we access relevant disk blocks in order
+ * by pre-sorting the entries correspondingly.
+ *
+ * 1. Collect entry info (name, ino, type, offset) etc for all the
+ *    entries. Obtained by reading the directory inode
+ * 2. Collect stat info for all the entries. Obtained by reading
+ *    the file inode blocks.
+ * 3. Collect xattr info for all the entries. Obtained by reading
+ *    the eattr block of each inode.
+ *
+ * With this scheme of collecting data, we don't know what the final
+ * size of a dirent would be ahead of time. gfs2_xrdir_estimate_dent_memcap()
+ * attempts to guess the size. Right now it statically computes and
+ * reserves a fixed percentage of available space for entry+stat info
+ * and xattr info based on what data is requested by the user.
+ *
+ * TODO: Make this dynamic. Analyse the directory being processed
+ * and use observed ratios to improve throughput.
+ */
+static u64 gfs2_xrdir_estimate_dent_memcap(struct gfs2_sbd *sdp,
+                                          struct gfs2_xrdir_ctx *xc)
+{
+       u64 avail;
+       int perc = 80;
+       unsigned int mask = xc->xc_xattr_mask;
+
+       avail = (gfs2_tune_get(sdp, gt_max_vb_pages) +
+                vp_get_page_count(&xc->xc_dirents) +
+                vp_get_page_count(&xc->xc_xattr_keys) +
+                vp_get_page_count(&xc->xc_xattr_values) -
+                atomic_read(&sdp->sd_vb_page_count)) * PAGE_SIZE;
+       if ((mask & XSTAT_XATTR_ALL) && (mask & XSTAT_XATTR_VALUES))
+               perc = 50;
+
+       return (avail * perc) / 100;
+}
+
+/*
+ * We setup the xreaddir context before every collect run
+ */
+static int gfs2_xrdir_ctx_setup(struct file *file, struct gfs2_xrdir_ctx *xc,
+                               unsigned int flags, unsigned int mask)
+{
+       struct gfs2_sbd *sdp = GFS2_SB(file->f_mapping->host);
+
+       if (!(xc->xc_flags & XC_FL_GATHER_PARTS)) {
+               /*
+                * We only update flags and mask once per readdirplus
+                * initiation. If there are multiple parts, use the
+                * same values as initialized at the start
+                */
+               xc->xc_xst_flags = flags;
+               xc->xc_xattr_mask = mask;
+               xc->xc_offset = file->f_pos;
+       }
+
+       /*
+        * Set limits for this part based on how much memory is available
+        * or how many entries per cycle as defined by sysfs file.
+        * If dent_cap established in a previous run, leave it alone
+        */
+       xc->xc_dent_cap = xc->xc_dent_cap ? xc->xc_dent_cap : 
+               gfs2_tune_get(sdp, gt_max_xrdir_dents);
+       xc->xc_dent_memcap = gfs2_xrdir_estimate_dent_memcap(sdp, xc);
+
+       xc->xc_dent_valid = 0;
+       xc->xc_count = 0;
+       xc->xc_next_dent = NULL;
+       kfree(xc->xc_vb_dptrs);
+       xc->xc_vb_dptrs = NULL;
+       vp_reset(&xc->xc_dirents);
+       vp_reset(&xc->xc_xattr_keys);
+       vp_reset(&xc->xc_xattr_values);
+
+       return 0;
+}
+
+/*
+ * Add a gfs2_dirent to the xreaddir context
+ */
+int gfs2_xrdir_collect_dents(const struct gfs2_dirent *dent, loff_t off,
+                            struct gfs2_xrdir_ctx *xc)
+{
+       struct gfs2_xdirent *x;
+       u64 x_ino;
+       u32 x_hash;
+       u8 x_valid = 0;
+       char x_type;
+       unsigned int x_xattr_count, x_namelen;
+       const void *nullptr = NULL;
+       int error = 0;
+
+       if (gfs2_dirent_dot_or_dotdot(dent))
+               return 0;
+
+       if (xc->xc_next_dent == NULL)
+               xc->xc_next_dent = xc->xc_dirents.v_ptr;
+       x = xc->xc_next_dent;
+       vp_memset(&xc->xc_dirents, x, 0, sizeof(struct gfs2_xdirent));
+
+       /*
+        * If we know that we're encountering hash-colliding
+        * entries this time around, we read only these in
+        * and nothing else
+        */
+       if (xc->xc_flags & XC_FL_HASH_COLL_NXT &&
+           off != xc->xc_hash_coll_off) {
+               /*
+                * setting dent_cap to how many we've read in
+                * so we don't read anymore
+                */
+               xc->xc_dent_cap = xc->xc_count;
+               xc->xc_flags &= ~XC_FL_HASH_COLL_NXT;
+               /*
+                * xc_offset will get incremented to read
+                * at the next offset when everything
+                * is written out properly this cycle
+                */
+               xc->xc_offset = xc->xc_hash_coll_off;
+               xc->xc_hash_coll_off = 0;
+               goto err_check;
+       }
+
+       /* Copy the dirent contents */
+       x_ino = be64_to_cpu(dent->de_inum.no_addr);
+       x_hash = be32_to_cpu(dent->de_hash);
+       x_type = be16_to_cpu(dent->de_type);
+       x_xattr_count = 0;
+       x_namelen = be16_to_cpu(dent->de_name_len);
+
+       error = vp_write(&xc->xc_dirents, &x->x_ino, &x_ino, sizeof(x->x_ino));
+       if (error != sizeof(x->x_ino)) goto err_check;
+
+       error = vp_write(&xc->xc_dirents, &x->x_hash, &x_hash, 
sizeof(x->x_hash));
+       if (error != sizeof(x->x_hash)) goto err_check;
+
+       error = vp_write(&xc->xc_dirents, &x->x_valid, &x_valid, 
sizeof(x->x_valid));
+       if (error != sizeof(x->x_valid)) goto err_check;
+
+       error = vp_write(&xc->xc_dirents, &x->x_type, &x_type, 
sizeof(x->x_type));
+       if (error != sizeof(x->x_type)) goto err_check;
+
+       error = vp_write(&xc->xc_dirents, &x->x_xattr_count, &x_xattr_count,
+                        sizeof(x->x_xattr_count));
+       if (error != sizeof(x->x_xattr_count)) goto err_check;
+
+       error = vp_write(&xc->xc_dirents, &x->x_vb_xattr_arr_ptr, &nullptr,
+                        sizeof(x->x_vb_xattr_arr_ptr));
+       if (error != sizeof(x->x_vb_xattr_arr_ptr)) goto err_check;
+
+       error = vp_write(&xc->xc_dirents, &x->x_namelen, &x_namelen,
+                        sizeof(x->x_namelen));
+       if (error != sizeof(x->x_namelen)) goto err_check;
+
+       error = vp_write(&xc->xc_dirents, &x->x_name, (char*)(dent + 1), 
x_namelen);
+       if (error != x_namelen) goto err_check;
+
+       xc->xc_next_dent = x->x_name + x_namelen;
+       xc->xc_count++;
+       error = 0;
+err_check:
+       return gfs2_xrdir_collect_errcheck(xc, error);
+}
+
+/*
+ * Create the array of pointers that point to all the
+ * collected entries within the xc_dirents vbuf.
+ */
+static int gfs2_xrdir_create_dptrs(struct gfs2_xrdir_ctx *xc)
+{
+       int i;
+       unsigned int namelen;
+       struct gfs2_xdirent *x = NULL;
+
+       BUG_ON(xc->xc_vb_dptrs || xc->xc_count == 0);
+
+       /* allocate the dirent pointers */
+       xc->xc_vb_dptrs = kmalloc(sizeof(struct gfs2_xdirent *) * xc->xc_count,
+                                 GFP_KERNEL);
+       if (xc->xc_vb_dptrs == NULL)
+               return -ENOMEM;
+
+       for (i = 0; i < xc->xc_count; i++) {
+               if (!x)
+                       x = xc->xc_dirents.v_ptr;
+               xc->xc_vb_dptrs[i] = x;
+               vp_read(&xc->xc_dirents, &namelen, &x->x_namelen,
+                       sizeof(x->x_namelen));
+               /* 
+                * reclen is sizeof(struct gfs2_xdirent) + x_namelen.
+                * see struct gfs2_xdirent for more info
+                */
+               x = (void *)x->x_name + namelen;
+       }
+       return 0;
+}
+
+static int gfs2_xrdir_collect_xstat(struct gfs2_xrdir_ctx *xc)
+{
+       int i;
+       struct kstat st;
+
+       for (i = 0; i < xc->xc_count; i++) {
+               struct gfs2_xdirent *x_vb_p = xc->xc_vb_dptrs[i];
+               struct gfs2_inode *ip;
+
+               vp_read(&xc->xc_dirents, &ip, &x_vb_p->x_ip, sizeof(struct 
gfs2_inode *));
+               gfs2_getattr_i(ip, &st);
+
+               vp_write(&xc->xc_dirents, &x_vb_p->x_kstat, &st, sizeof(struct 
kstat));
+               vp_write(&xc->xc_dirents, &x_vb_p->x_eablk, &ip->i_eattr, 
+                        sizeof(x_vb_p->x_eablk));
+       }
+       return 0;
+}
+
+static inline int xattr_requested(char type, unsigned int mask)
+{
+       if ((type == GFS2_EATYPE_USR) && (mask & XSTAT_XATTR_USER))
+               return 1;
+       if ((type == GFS2_EATYPE_SYS) && (mask & XSTAT_XATTR_SYSTEM))
+               return 1;
+       if ((type == GFS2_EATYPE_SECURITY) && (mask & XSTAT_XATTR_SECURITY))
+               return 1;
+       return 0;
+}
+
+static int gfs2_xrdir_xattr_list_i(struct gfs2_inode *ip, 
+                                  struct buffer_head *bh,
+                                  struct gfs2_ea_header *ea,
+                                  struct gfs2_ea_header *prev, void *private)
+{
+       struct gfs2_xdir_ctx_bndle *bundle = private;
+       struct gfs2_xrdir_ctx *xc = bundle->xcb_xc;
+       struct gfs2_xdirent *x = bundle->xcb_xd;
+       struct gfs2_xd_xattr *xtr;
+       char prefix[9];
+       unsigned int l = 0, xtr_count, namlen, reclen;
+       void *p;
+
+       if (!xattr_requested(ea->ea_type, xc->xc_xattr_mask))
+               return 0;
+
+       if (ea->ea_type == GFS2_EATYPE_UNUSED)
+               return 0;
+
+       l = ea_prefix(ea, prefix, 9);
+       BUG_ON(l == 0);
+
+       xtr = vp_get_top(&xc->xc_xattr_keys);
+       /*
+        * Only certain vp_XXX ops can trip -ENOMEM where we might be extending
+        * the vbuf. We ignore the error code of other ops.
+        */
+       if (vp_memset(&xc->xc_xattr_keys, xtr, 0, 
+                     sizeof(struct gfs2_xd_xattr)) == -ENOMEM)
+               goto set_oom;
+
+       /* if mask says don't do values, skip the following lines */
+       if (GFS2_EA_DATA_LEN(ea) > 0 && (xc->xc_xattr_mask & 
XSTAT_XATTR_VALUES)) {
+               void *valptr = vp_get_top(&xc->xc_xattr_values);
+               unsigned long len = GFS2_EA_DATA_LEN(ea);
+
+               vp_write(&xc->xc_xattr_keys, &xtr->xa_value_len,
+                        &len, sizeof(xtr->xa_value_len));
+               vp_write(&xc->xc_xattr_keys, &xtr->xa_vb_value_ptr, &valptr,
+                        sizeof(void*));
+               vp_read(&xc->xc_xattr_keys, &p, &xtr->xa_vb_value_ptr,
+                       sizeof(void*));
+               if (vp_append(&xc->xc_xattr_values, GFS2_EA2DATA(ea), len)
+                   == -ENOMEM)
+                       goto set_oom;
+       }
+
+       namlen = l + ea->ea_name_len;
+       vp_write(&xc->xc_xattr_keys, &xtr->xa_keylen, &namlen,
+                sizeof(xtr->xa_keylen));
+       if (vp_write(&xc->xc_xattr_keys, xtr->xa_keyname, &prefix, l) == 
-ENOMEM)
+               goto set_oom;
+       if (vp_write(&xc->xc_xattr_keys, xtr->xa_keyname + l, 
+                    GFS2_EA2NAME(ea), namlen) == -ENOMEM)
+               goto set_oom;
+
+       /* gfs2_xd_xattr.xa_keyname[1] has an extra byte */
+       reclen = (xtr->xa_keyname + l + namlen) - (char *)xtr;
+       vp_write(&xc->xc_xattr_keys, &xtr->xa_reclen, &reclen,
+                sizeof(xtr->xa_reclen));
+
+       vp_read(&xc->xc_dirents, &xtr_count, &x->x_xattr_count,
+               sizeof(x->x_xattr_count));
+       xtr_count++;
+       vp_write(&xc->xc_dirents, &x->x_xattr_count, &xtr_count,
+                sizeof(x->x_xattr_count));
+
+       return 0;
+set_oom:
+       xc->xc_flags |= XC_FL_ERROR_OOM;
+       return -ENOMEM;
+}
+
+int gfs2_xrdir_collect_xattrs(struct gfs2_xrdir_ctx *xc)
+{
+       int error = 0, i;
+
+       for (i = 0; i < xc->xc_count; i++) {
+               struct gfs2_xdirent *xtop, *x_vb_p = xc->xc_vb_dptrs[i];
+               struct gfs2_inode *ip;
+               struct gfs2_xdir_ctx_bndle bundle;
+               u8 valid = 1;
+
+               vp_read(&xc->xc_dirents, &ip, &x_vb_p->x_ip,
+                       sizeof(struct gfs2_inode *));
+
+               if (!ip->i_eattr || !(xc->xc_xattr_mask & XSTAT_XATTR_ALL))
+                       goto mark_valid;
+
+               bundle.xcb_xc = xc;
+               bundle.xcb_xd = x_vb_p;
+
+               xtop = vp_get_top(&xc->xc_xattr_keys);
+               vp_write(&xc->xc_dirents, &x_vb_p->x_vb_xattr_arr_ptr, &xtop,
+                        sizeof(struct gfs2_xd_xattr*));
+
+               error = ea_foreach(ip, gfs2_xrdir_xattr_list_i, &bundle);
+               if (error)
+                       break;
+       mark_valid:
+               /* Read the xattrs for this dent, so mark it as valid */
+               vp_write(&xc->xc_dirents, &x_vb_p->x_valid, &valid,
+                        sizeof(x_vb_p->x_valid));
+               xc->xc_dent_valid++;
+       }
+       return error;
+}
+
+static int gfs2_xrdir_collect_extra_info(struct gfs2_xrdir_ctx *xc,
+                                        struct gfs2_inode *dip)
+{
+       int error = -ENOMEM, i;
+       struct gfs2_holder *ghs;
+
+       /* First sort the dents according to inode blk order for stat */
+       ctx_sort(xc, xc->xc_vb_dptrs, xc->xc_count, sizeof(void *),
+                ctx_compare_dent_iblks, NULL);
+       
+       /* Lookup all the inodes for stat info */
+       for (i = 0; i < xc->xc_count; i++) {
+               struct gfs2_xdirent *x_vb_p = xc->xc_vb_dptrs[i];
+               u64 ino;
+               struct inode *inode;
+               struct gfs2_inode *ip, *nullptr = NULL;
+       
+               vp_read(&xc->xc_dirents, &ino, &x_vb_p->x_ino,
+                       sizeof(x_vb_p->x_ino));
+
+               inode = gfs2_lookup_by_inum(GFS2_SB(&dip->i_inode), ino, NULL,
+                                           GFS2_BLKST_DINODE);
+               if (IS_ERR(inode)) {
+                       vp_write(&xc->xc_dirents, &ip, &nullptr,
+                                sizeof(struct gfs2_inode *));
+                       error = -1;
+                       goto iput_iarr;
+               }
+               ip = GFS2_I(inode);
+               vp_write(&xc->xc_dirents, &x_vb_p->x_ip, &ip,
+                        sizeof(struct gfs2_inode *));
+       }
+
+       /* lock all inodes */
+       ghs = kcalloc(xc->xc_count, sizeof(struct gfs2_holder), GFP_NOFS);
+       if (!ghs)
+               goto iput_iarr;
+       for (i = 0; i < xc->xc_count; i++) {
+               struct gfs2_xdirent *x_vb_p = xc->xc_vb_dptrs[i];
+               struct gfs2_inode *ip;
+
+               vp_read(&xc->xc_dirents, &ip, &x_vb_p->x_ip,
+                       sizeof(struct gfs2_inode *));
+               gfs2_holder_init(ip->i_gl, LM_ST_SHARED, LM_FLAG_ANY, ghs + i);
+       }
+
+       error = gfs2_glock_nq_m(xc->xc_count, ghs);
+       if (error)
+               goto free_ghs;
+
+       if (gfs2_xrdir_collect_xstat(xc))
+               goto free_ghs;
+
+       /* Sort the dents according to eattr blk order */
+       ctx_sort(xc, xc->xc_vb_dptrs, xc->xc_count, sizeof(void *),
+                ctx_compare_dent_eablks, NULL);
+
+       error = gfs2_xrdir_collect_xattrs(xc);
+
+       for (i = 0; i < xc->xc_count; i++)
+               gfs2_glock_dq_uninit(&ghs[i]);
+free_ghs:
+       kfree(ghs);
+iput_iarr:
+       for (i = 0; i < xc->xc_count; i++) {
+               struct gfs2_xdirent *x_vb_p = xc->xc_vb_dptrs[i];
+               struct gfs2_inode *ip;
+
+               vp_read(&xc->xc_dirents, &ip, &x_vb_p->x_ip,
+                       sizeof(struct gfs2_inode *));
+               if (ip)
+                       iput(&ip->i_inode);
+       }
+       /* Sort the pointers back to dent order */
+       ctx_sort(xc, xc->xc_vb_dptrs, xc->xc_count, sizeof(void *),
+                ctx_compare_dents, NULL);
+
+       if (error == -ENOMEM) {
+               /*
+                * If at least one dent has been collected in full,
+                * void -ENOMEM
+                * We shuffled the order of dents multiple times while
+                * retrieving stat and xattrs, so we have to ensure that
+                * at least the first dent in the final ordering is valid
+                * in order to be able to return at least 1 entry. This
+                * is because we need to preserve the order (hash order)
+                * when we return the dents to the user. XXX: OR DO WE??
+                */
+               struct gfs2_xdirent *x_vb_p = xc->xc_vb_dptrs[0];
+               u8 valid;
+               vp_read(&xc->xc_dirents, &valid, &x_vb_p->x_valid,
+                       sizeof(x_vb_p->x_valid));
+
+               if (valid)
+                       error = 0;
+               else {
+                       u32 hash;
+                       vp_read(&xc->xc_dirents, &hash, &x_vb_p->x_hash,
+                               sizeof(hash));
+                       xc->xc_offset = gfs2_disk_hash2offset(hash);
+               }
+       }
+       if (!error)
+               xc->xc_flags |= XC_FL_DATA_AVAIL;
+       
+       return error;
+}
+
+static int gfs2_xrdir_to_user_xattrs(struct gfs2_xrdir_ctx *xc,
+                                    struct gfs2_xdirent *x, 
+                                    struct gfs2_xd_xattr *xdx_vb_p,
+                                    struct xdirent_xattr __user *xx,
+                                    size_t count, size_t *bytes, char *tempbuf)
+{
+       struct gfs2_xd_xattr xdx;
+       int attrcount = 0, error = -EINVAL;
+       
+       while (attrcount < x->x_xattr_count) {
+               vp_read(&xc->xc_xattr_keys, &xdx, xdx_vb_p,
+                       sizeof(struct gfs2_xd_xattr));
+
+               if ((count - *bytes) < 
+                   (sizeof(struct xdirent_xattr) + 
+                    xdx.xa_keylen + xdx.xa_value_len)) {
+                       error = -EOVERFLOW;
+                       goto out;
+               }
+
+               if (__put_user(xdx.xa_value_len, &xx->xa_value_len))
+                       goto out;
+
+               vp_read(&xc->xc_xattr_keys, tempbuf, xdx_vb_p->xa_keyname,
+                       xdx.xa_keylen);
+
+               if (copy_to_user(xx->xa_name_val, tempbuf, xdx.xa_keylen))
+                       goto out;
+               if (__put_user(0, xx->xa_name_val + xdx.xa_keylen))
+                       goto out;
+
+               if ((xc->xc_xattr_mask & XSTAT_XATTR_VALUES) &&
+                   xdx.xa_vb_value_ptr) {
+                       vp_read(&xc->xc_xattr_values, tempbuf, 
xdx.xa_vb_value_ptr,
+                               xdx.xa_value_len);
+
+                       if (copy_to_user(xx->xa_name_val + xdx.xa_keylen + 1, 
tempbuf,
+                                        xdx.xa_value_len))
+                               goto out;
+               }
+
+               xx = (struct xdirent_xattr __user *)
+                       ((char *)xx + sizeof(xx->xa_value_len)
+                        + xdx.xa_keylen + 1 + xdx.xa_value_len);
+               xdx_vb_p = (void*) xdx_vb_p + xdx.xa_reclen;
+
+               *bytes += sizeof(struct xdirent_xattr) + xdx.xa_keylen +
+                       xdx.xa_value_len;
+               attrcount++;
+       }
+       error = 0;
+out:
+       return error;
+}
+
+static int gfs2_xrdir_to_user_vars(struct gfs2_xrdir_ctx *xc,
+                                  struct gfs2_xdirent *x,
+                                  struct gfs2_xdirent *x_vb_p,
+                                  struct linux_xdirent __user *lxd,
+                                  size_t count, size_t *bytes)
+{
+       int error = -EINVAL;
+       char *tempbuf = NULL;
+       struct xdirent_blob __user *xblob;
+       struct xdirent_xattr __user *xx;
+       struct gfs2_xd_xattr *xdx_vb_p;
+
+       tempbuf = kmalloc(PAGE_SIZE, GFP_KERNEL);
+       if (!tempbuf) {
+               error = -ENOMEM;
+               goto out;
+       }
+
+       xblob = &lxd->xd_blob;
+
+       /* copy all the variable length fields */
+       if ((count - *bytes) < x->x_namelen) {
+               error = -EOVERFLOW;
+               goto free;
+       }
+
+       vp_read(&xc->xc_dirents, tempbuf, x_vb_p->x_name, x->x_namelen);
+
+       if (copy_to_user(xblob->xb_blob, tempbuf, x->x_namelen))
+               goto free;
+       if (__put_user(0, xblob->xb_blob + x->x_namelen))
+               goto free;
+
+       *bytes += x->x_namelen;
+       error = 0;
+
+       if ((xc->xc_xattr_mask & XSTAT_XATTR_ALL) &&
+               lxd->xd_blob.xb_xattr_count) {
+               xx = (struct xdirent_xattr __user *)
+                       (xblob->xb_blob + x->x_namelen + 1);
+               xdx_vb_p = x->x_vb_xattr_arr_ptr;
+
+               error = gfs2_xrdir_to_user_xattrs(xc, x, xdx_vb_p, xx,
+                                                 count, bytes, tempbuf);
+       }
+free:
+       kfree(tempbuf);
+out:
+       return error;
+}
+
+static int gfs2_xrdir_to_user_fixed(struct gfs2_xrdir_ctx *xc,
+                                   struct gfs2_xdirent *x,
+                                   struct gfs2_xdirent *x_vb_p,
+                                   struct linux_xdirent __user *lxd, 
+                                   size_t count, size_t *bytes)
+{
+       struct xdirent_blob __user *xblob;
+       int error = -EINVAL;
+
+       vp_read(&xc->xc_dirents, x, x_vb_p, sizeof(struct gfs2_xdirent));
+
+       if ((count - *bytes) < sizeof(struct linux_xdirent)) {
+               error = -EOVERFLOW;
+               goto out;
+       }
+
+       if (__put_user(x->x_ino, &lxd->xd_ino))
+               goto out;
+       if (__put_user(x->x_type, &lxd->xd_type))
+               goto out;
+       if (__put_user(0, &lxd->xd_off))
+               goto out;
+
+       error = xstat_set_result(&x->x_kstat, &lxd->xd_stat);
+       if (error)
+               goto out;
+
+       xblob = &lxd->xd_blob;
+
+       error = -EINVAL;
+       if (__put_user(x->x_xattr_count, &xblob->xb_xattr_count))
+               goto out;
+
+       /* copied all the fixed size fields */
+       *bytes += sizeof(struct linux_xdirent);
+       error = 0;
+out:
+       return error;
+}
+
+static size_t gfs2_xrdir_to_user(struct gfs2_xrdir_ctx *xc, void __user *buf,
+                                size_t count)
+{
+       size_t error = -EINVAL, bytes = 0, bytes_bef = 0;
+       int i, skip = 1, written = 0;
+       struct gfs2_xdirent x, *x_vb_p;
+       struct linux_xdirent __user *lxd = buf;
+       u8 valid;
+
+       if (!(xc->xc_flags & XC_FL_DATA_AVAIL))
+               goto out;
+
+       for (i = 0; i < xc->xc_count; i++) {
+               u32 hash;
+               x_vb_p = xc->xc_vb_dptrs[i];
+               vp_read(&xc->xc_dirents, &hash, &x_vb_p->x_hash, sizeof(hash));
+
+               if (skip && xc->xc_vb_dptrs[i] != xc->xc_next_dent)
+                       continue;
+               skip = 0;
+               vp_read(&xc->xc_dirents, &valid, &x_vb_p->x_valid,
+                       sizeof(x_vb_p->x_valid));
+               if (!valid)
+                       break;
+
+               /* This will fill up x from x_vb_p and subsequently lxd from x 
*/
+               error = gfs2_xrdir_to_user_fixed(xc, &x, x_vb_p, lxd, count,
+                                                &bytes);
+               if (error) {
+                       if (error == -EOVERFLOW)
+                               goto overflow;
+                       goto out;
+               }
+
+               error = gfs2_xrdir_to_user_vars(xc, &x, x_vb_p, lxd, count,
+                                               &bytes);
+               if (error) {
+                       u64 ino;
+                       vp_read(&xc->xc_dirents, &ino, &x_vb_p->x_ino, 
sizeof(ino));
+                       if (error == -EOVERFLOW)
+                               goto overflow;
+                       goto out;
+               }
+
+               if (__put_user(bytes - bytes_bef, &lxd->xd_reclen))
+                       goto out;
+
+               lxd = (void *)lxd + (bytes - bytes_bef);
+               xc->xc_next_dent = xc->xc_vb_dptrs[i+1];
+               written++;
+               bytes_bef = bytes;
+       }
+overflow:
+       if (written) {
+               if (!valid) {
+                       u32 hash;
+                       x_vb_p = xc->xc_vb_dptrs[i];
+                       vp_read(&xc->xc_dirents, &hash, &x_vb_p->x_hash,
+                               sizeof(hash));
+                       /*
+                        * Some of the entries we collected were incomplete,
+                        * so we only wrote the ones that were complete. For
+                        * next time, we'll only try to collect half the 
+                        * number of entries. This will also invalidate the
+                        * assumption that we'll encounter hash-colliding
+                        * entries in the next pass
+                        */
+                       xc->xc_offset = gfs2_disk_hash2offset(hash);
+                       xc->xc_flags &= ~(XC_FL_GATHER_PART_INT |
+                                         XC_FL_DATA_AVAIL |
+                                         XC_FL_HASH_COLL |
+                                         XC_FL_HASH_COLL_NXT);
+                       xc->xc_hash_coll_off = 0;
+                       xc->xc_dent_cap = DIV_ROUND_UP(xc->xc_count, 2);
+               } else {
+                       /*
+                        * If we didn't overflow the user buffer, we
+                        * have written out all the collected dents to
+                        * the user buffer
+                        */
+                       if (error != -EOVERFLOW) {
+                               xc->xc_flags &= ~(XC_FL_GATHER_PART_INT |
+                                                 XC_FL_DATA_AVAIL);
+                               xc->xc_dent_cap = 0;
+                               if (!(xc->xc_flags & XC_FL_HASH_COLL))
+                                       xc->xc_offset++;
+                       }
+               }
+       }
+       if (!written && !skip) {
+               error = -EOVERFLOW;
+               goto out;
+       }
+       error = bytes_bef;
+out:
+       return error;
+}
+
+/**
+ * gfs2_xreaddir - GFS2's implementation of xreaddir functionality
+ * @file  : The directory to xreaddir
+ * @flags : flags used by xstat
+ * @mask  : field mask for xstat and xattrs
+ * @buf   : User buffer to fill data into
+ * @count : Size of the user buffer in bytes
+ *
+ * Collect extended information (xstat, xattrs) about the dents in the
+ * given directory and fill them into the user buf passed in.
+ *
+ * Returns: 0       if successful.
+ *          -EAGAIN if the user should retry.
+ *          -ve values for other errors
+ */
+
+size_t gfs2_xreaddir(struct file *file, unsigned int flags, unsigned int mask,
+                           void __user *buf, size_t count)
+{
+       struct gfs2_xrdir_ctx *xc = ((struct gfs2_file *)
+                                    file->private_data)->f_xrctx;
+       size_t error = 0;
+       struct inode *dir = file->f_mapping->host;
+       struct gfs2_inode *dip = GFS2_I(dir);
+       struct gfs2_holder d_gh;
+
+       if (xc->xc_flags & XC_FL_DATA_AVAIL) {
+               error = gfs2_xrdir_to_user(xc, buf, count);
+               file->f_pos = xc->xc_offset;
+               return error;
+       }
+
+       error = gfs2_xrdir_ctx_setup(file, xc, flags, mask);
+       if (error)
+               goto out;
+
+       gfs2_holder_init(dip->i_gl, LM_ST_SHARED, 0, &d_gh);
+       error = gfs2_glock_nq(&d_gh);
+       if (error) {
+               gfs2_holder_uninit(&d_gh);
+               goto out;
+       }
+
+       xc->xc_flags &= ~XC_FL_HASH_COLL;
+       error = gfs2_dir_read(dir, NULL, xc, &file->f_ra);
+       if (error) {
+               if (xc->xc_flags & XC_FL_ERROR_OOM)
+                       error = gfs2_xrdir_handle_oom(xc);
+               goto uninit;
+       }
+
+       if (xc->xc_count == 0)
+               goto uninit;
+       
+       if (!(xc->xc_flags & XC_FL_GATHER_PARTS))
+               xc->xc_flags |= XC_FL_GATHER_FULL;
+       else if (!(xc->xc_flags & XC_FL_GATHER_PART_INT))
+               xc->xc_flags |= XC_FL_GATHER_PART_END;
+
+       error = gfs2_xrdir_create_dptrs(xc);
+       if (error) {
+               if (error == -ENOMEM)
+                       error = gfs2_xrdir_handle_oom(xc);
+               goto uninit;
+       }
+
+       error = gfs2_xrdir_collect_extra_info(xc, dip);
+       if (error) {
+               if (error == -ENOMEM)
+                       error = gfs2_xrdir_handle_oom(xc);
+               goto uninit;
+       }
+
+       xc->xc_next_dent = xc->xc_vb_dptrs[0];
+       error = gfs2_xrdir_to_user(xc, buf, count);
+
+       file->f_pos = xc->xc_offset;
+uninit:
+       if (xc->xc_flags & XC_FL_HASH_COLL && !(xc->xc_flags & 
XC_FL_DATA_AVAIL))
+               xc->xc_flags &= ~XC_FL_HASH_COLL;
+
+       gfs2_glock_dq_uninit(&d_gh);
+out:
+       return error;
+}
diff --git a/fs/gfs2/xreaddir.h b/fs/gfs2/xreaddir.h
new file mode 100644
index 0000000..ea6c82c
--- /dev/null
+++ b/fs/gfs2/xreaddir.h
@@ -0,0 +1,84 @@
+#ifndef __XREADDIR_H__
+#define __XREADDIR_H__
+
+struct gfs2_xd_xattr {
+       unsigned int   xa_reclen;
+       void          *xa_vb_value_ptr;
+       unsigned long  xa_value_len;
+       unsigned int   xa_keylen;
+       char           __pad[7];
+       char           xa_keyname[1];
+};
+
+struct gfs2_xdirent {
+       u32                      x_hash;
+       u8                       x_valid;
+       struct gfs2_inode       *x_ip;
+       u64                      x_ino;
+       u64                      x_eablk;
+       char                     x_type;
+       struct kstat             x_kstat;
+       unsigned int             x_xattr_count;
+       void                    *x_vb_xattr_arr_ptr;
+       unsigned int             x_namelen;
+       char                     x_name[1];
+};
+
+#define XC_FL_ALLOCATED                 0x00000001
+#define XC_FL_GATHER_FULL               0x00000002
+#define XC_FL_GATHER_PARTS              0x00000004
+#define XC_FL_GATHER_PART_INT           0x00000008
+#define XC_FL_GATHER_PART_END           0x00000010
+#define XC_FL_HASH_COLL                 0x00000020
+#define XC_FL_HASH_COLL_NXT             0x00000040
+#define XC_FL_ERROR_OOM                 0x00000080
+#define XC_FL_ERROR                     0x00000100
+#define XC_FL_DATA_AVAIL                0x00000200
+#define XC_FL_PRINTOK                   0x10000000
+
+/*
+ * readdir ctx
+ */
+struct gfs2_xrdir_ctx {
+       u32                   xc_flags;           /* XC_FL_XXXX */
+       u64                   xc_dent_memcap;     /* mem limit per collect */
+       u32                   xc_dent_cap;        /* # dent limit per collect */
+       u32                   xc_dent_valid;      /* # valid dents collected */
+       u32                   xc_xattr_mask;      /* XSTAT_XATTR_XXX see 
stat.h*/
+       u32                   xc_xst_flags;       /* XSTAT_XXX see stat.h */
+       loff_t                xc_offset;          /* offset of next dent */
+       unsigned long         xc_count;           /* # dents collected */
+       loff_t                xc_hash_coll_off;   /* last hash collision offset 
*/
+       void                 *xc_next_dent;       /* next dent to write out */
+       void                **xc_vb_dptrs;        /* ptrs to dents in 
xc_dirents */
+       struct vbuf           xc_dirents;         /* temp storage for dents */
+       struct vbuf           xc_xattr_keys;      /* xattr keys for dents */
+       struct vbuf           xc_xattr_values;    /* corresponding values */
+};
+
+/*
+ * Ugly struct to blob together these two
+ * structs. Only used in one place to 
+ * retrieve extended attributes.
+ * This is so that we don't have to change
+ * the prototypes of all the existing
+ * xattr handling functions to accept an
+ * extra arg.
+ */
+struct gfs2_xdir_ctx_bndle {
+       struct gfs2_xrdir_ctx *xcb_xc;
+       struct gfs2_xdirent   *xcb_xd;
+};
+
+extern size_t gfs2_xreaddir(struct file *file, unsigned int flags,
+                           unsigned int mask, void __user *buf,
+                           size_t count);
+extern int gfs2_xrdir_collect_dents(const struct gfs2_dirent *dent, loff_t off,
+                                   struct gfs2_xrdir_ctx *xc);
+extern void gfs2_xrdir_partial_collect(struct gfs2_xrdir_ctx *xc);
+extern int gfs2_xrdir_collect_xattrs(struct gfs2_xrdir_ctx *xc);
+
+extern int gfs2_xrdir_ctx_init(struct gfs2_file *fp, struct gfs2_sbd *sdp);
+extern void gfs2_xrdir_ctx_uninit(struct gfs2_file *fp);
+
+#endif /* __XREADDIR_H_ */
-- 
1.8.1.4

Reply via email to