lkml.org 
[lkml]   [2014]   [Jul]   [25]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
Patch in this message
/
From
Subject[RFC PATCH 5/5] gfs2: Add xreaddir file operation and supporting functions
Date
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 <adas@redhat.com>
---
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


\
 
 \ /
  Last update: 2014-07-25 20:21    [W:0.133 / U:0.024 seconds]
©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site