diff options
Diffstat (limited to 'fs/gfs2/dir.c')
-rw-r--r-- | fs/gfs2/dir.c | 412 |
1 files changed, 161 insertions, 251 deletions
diff --git a/fs/gfs2/dir.c b/fs/gfs2/dir.c index f31f163da1a1..ba3438553f33 100644 --- a/fs/gfs2/dir.c +++ b/fs/gfs2/dir.c @@ -60,6 +60,7 @@ #include <linux/buffer_head.h> #include <linux/sort.h> #include <linux/gfs2_ondisk.h> +#include <linux/crc32.h> #include <asm/semaphore.h> #include "gfs2.h" @@ -344,7 +345,8 @@ fail: } typedef int (*gfs2_dscan_t)(const struct gfs2_dirent *dent, - const struct qstr *name); + const struct qstr *name, + void *opaque); static inline int __gfs2_dirent_find(const struct gfs2_dirent *dent, const struct qstr *name, int ret) @@ -358,13 +360,15 @@ static inline int __gfs2_dirent_find(const struct gfs2_dirent *dent, } static int gfs2_dirent_find(const struct gfs2_dirent *dent, - const struct qstr *name) + const struct qstr *name, + void *opaque) { return __gfs2_dirent_find(dent, name, 1); } static int gfs2_dirent_prev(const struct gfs2_dirent *dent, - const struct qstr *name) + const struct qstr *name, + void *opaque) { return __gfs2_dirent_find(dent, name, 2); } @@ -374,7 +378,8 @@ static int gfs2_dirent_prev(const struct gfs2_dirent *dent, * name->len holds size of block. */ static int gfs2_dirent_last(const struct gfs2_dirent *dent, - const struct qstr *name) + const struct qstr *name, + void *opaque) { const char *start = name->name; const char *end = (const char *)dent + be16_to_cpu(dent->de_rec_len); @@ -384,17 +389,36 @@ static int gfs2_dirent_last(const struct gfs2_dirent *dent, } static int gfs2_dirent_find_space(const struct gfs2_dirent *dent, - const struct qstr *name) + const struct qstr *name, + void *opaque) { unsigned required = GFS2_DIRENT_SIZE(name->len); unsigned actual = GFS2_DIRENT_SIZE(be16_to_cpu(dent->de_name_len)); unsigned totlen = be16_to_cpu(dent->de_rec_len); + if (!dent->de_inum.no_addr) + actual = GFS2_DIRENT_SIZE(0); if ((totlen - actual) >= required) return 1; return 0; } +struct dirent_gather { + const struct gfs2_dirent **pdent; + unsigned offset; +}; + +static int gfs2_dirent_gather(const struct gfs2_dirent *dent, + const struct qstr *name, + void *opaque) +{ + struct dirent_gather *g = opaque; + if (dent->de_inum.no_addr) { + g->pdent[g->offset++] = dent; + } + return 0; +} + /* * Other possible things to check: * - Inode located within filesystem size (and on valid block) @@ -431,19 +455,12 @@ error: return -EIO; } -static struct gfs2_dirent *gfs2_dirent_scan(struct inode *inode, - void *buf, - unsigned int len, gfs2_dscan_t scan, - const struct qstr *name) +static int gfs2_dirent_offset(const void *buf) { - struct gfs2_meta_header *h = buf; - struct gfs2_dirent *dent, *prev; - unsigned offset; - unsigned size; - int ret = 0; + const struct gfs2_meta_header *h = buf; + int offset; BUG_ON(buf == NULL); - BUG_ON(name == NULL); switch(be16_to_cpu(h->mh_type)) { case GFS2_METATYPE_LF: @@ -455,14 +472,36 @@ static struct gfs2_dirent *gfs2_dirent_scan(struct inode *inode, default: goto wrong_type; } + return offset; +wrong_type: + printk(KERN_WARNING "gfs2_scan_dirent: wrong block type %u\n", + be16_to_cpu(h->mh_type)); + return -1; +} +static struct gfs2_dirent *gfs2_dirent_scan(struct inode *inode, + void *buf, + unsigned int len, gfs2_dscan_t scan, + const struct qstr *name, + void *opaque) +{ + struct gfs2_dirent *dent, *prev; + unsigned offset; + unsigned size; + int ret = 0; + + ret = gfs2_dirent_offset(buf); + if (ret < 0) + goto consist_inode; + + offset = ret; prev = NULL; dent = (struct gfs2_dirent *)(buf + offset); size = be16_to_cpu(dent->de_rec_len); if (gfs2_check_dirent(dent, offset, size, len, 1)) goto consist_inode; do { - ret = scan(dent, name); + ret = scan(dent, name, opaque); if (ret) break; offset += size; @@ -487,9 +526,6 @@ static struct gfs2_dirent *gfs2_dirent_scan(struct inode *inode, return ERR_PTR(ret); } -wrong_type: - printk(KERN_WARNING "gfs2_scan_dirent: %p wrong block type %u\n", scan, - be16_to_cpu(h->mh_type)); consist_inode: gfs2_consist_inode(inode->u.generic_ip); return ERR_PTR(-EIO); @@ -651,7 +687,8 @@ static struct gfs2_dirent *gfs2_dirent_alloc(struct inode *inode, const struct qstr *name) { struct gfs2_dirent *dent; - dent = gfs2_dirent_scan(inode, bh->b_data, bh->b_size, gfs2_dirent_find_space, name); + dent = gfs2_dirent_scan(inode, bh->b_data, bh->b_size, + gfs2_dirent_find_space, name, NULL); if (!dent || IS_ERR(dent)) return dent; return gfs2_init_dirent(inode, dent, name, bh); @@ -734,7 +771,7 @@ static struct gfs2_dirent *gfs2_dirent_search(struct inode *inode, return ERR_PTR(error); do { dent = gfs2_dirent_scan(inode, bh->b_data, bh->b_size, - scan, name); + scan, name, NULL); if (dent) goto got_dent; leaf = (struct gfs2_leaf *)bh->b_data; @@ -751,7 +788,7 @@ static struct gfs2_dirent *gfs2_dirent_search(struct inode *inode, error = gfs2_meta_inode_buffer(ip, &bh); if (error) return ERR_PTR(error); - dent = gfs2_dirent_scan(inode, bh->b_data, bh->b_size, scan, name); + dent = gfs2_dirent_scan(inode, bh->b_data, bh->b_size, scan, name, NULL); got_dent: *pbh = bh; return dent; @@ -764,6 +801,7 @@ static struct gfs2_leaf *new_leaf(struct inode *inode, struct buffer_head **pbh, struct buffer_head *bh = gfs2_meta_new(ip->i_gl, bn); struct gfs2_leaf *leaf; struct gfs2_dirent *dent; + struct qstr name = { .name = "", .len = 0, .hash = 0 }; if (!bh) return NULL; gfs2_trans_add_bh(ip->i_gl, bh, 1); @@ -775,12 +813,7 @@ static struct gfs2_leaf *new_leaf(struct inode *inode, struct buffer_head **pbh, leaf->lf_next = cpu_to_be64(0); memset(leaf->lf_reserved, 0, sizeof(leaf->lf_reserved)); dent = (struct gfs2_dirent *)(leaf+1); - dent->de_inum.no_formal_ino = cpu_to_be64(0); - dent->de_inum.no_addr = cpu_to_be64(0); - dent->de_hash = cpu_to_be32(0); - dent->de_rec_len = cpu_to_be16(bh->b_size - sizeof(struct gfs2_leaf)); - dent->de_name_len = cpu_to_be16(0); - dent->de_type = cpu_to_be16(0); + gfs2_qstr2dirent(&name, bh->b_size - sizeof(struct gfs2_leaf), dent); *pbh = bh; return leaf; } @@ -831,7 +864,7 @@ static int dir_make_exhash(struct inode *inode) sizeof(struct gfs2_leaf); args.name = bh->b_data; dent = gfs2_dirent_scan(dip->i_vnode, bh->b_data, bh->b_size, - gfs2_dirent_last, &args); + gfs2_dirent_last, &args, NULL); if (!dent) { brelse(bh); brelse(dibh); @@ -939,7 +972,7 @@ static int dir_split_leaf(struct inode *inode, const struct qstr *name) lp[x] = cpu_to_be64(bn); error = gfs2_dir_write_data(dip, (char *)lp, start * sizeof(uint64_t), - half_len * sizeof(uint64_t)); + half_len * sizeof(uint64_t)); if (error != half_len * sizeof(uint64_t)) { if (error >= 0) error = -EIO; @@ -965,7 +998,7 @@ static int dir_split_leaf(struct inode *inode, const struct qstr *name) str.name = (char*)(dent+1); str.len = be16_to_cpu(dent->de_name_len); str.hash = be32_to_cpu(dent->de_hash); - new = gfs2_dirent_alloc(dip->i_vnode, nbh, &str); + new = gfs2_dirent_alloc(inode, nbh, &str); if (IS_ERR(new)) { error = PTR_ERR(new); break; @@ -1154,10 +1187,10 @@ static int compare_dents(const void *a, const void *b) static int do_filldir_main(struct gfs2_inode *dip, uint64_t *offset, void *opaque, gfs2_filldir_t filldir, - struct gfs2_dirent **darr, uint32_t entries, + const struct gfs2_dirent **darr, uint32_t entries, int *copied) { - struct gfs2_dirent *dent, *dent_next; + const struct gfs2_dirent *dent, *dent_next; struct gfs2_inum inum; uint64_t off, off_next; unsigned int x, y; @@ -1216,189 +1249,74 @@ static int do_filldir_main(struct gfs2_inode *dip, uint64_t *offset, return 0; } -/** - * do_filldir_single - Read directory entries out of a single block - * @dip: The GFS2 inode - * @offset: The offset in the file to read from - * @opaque: opaque data to pass to filldir - * @filldir: The function to pass entries to - * @bh: the block - * @entries: the number of entries in the block - * @copied: pointer to int that's non-zero if a entry has been copied out - * - * Returns: errno, >0 on exception from filldir - */ - -static int do_filldir_single(struct gfs2_inode *dip, uint64_t *offset, - void *opaque, gfs2_filldir_t filldir, - struct buffer_head *bh, uint32_t entries, - int *copied) +static int gfs2_dir_read_leaf(struct inode *inode, u64 *offset, void *opaque, + gfs2_filldir_t filldir, int *copied, + unsigned *depth, u64 leaf_no) { - struct gfs2_dirent **darr; - struct gfs2_dirent *de; - unsigned int e = 0; - int error; - - if (!entries) - return 0; - - darr = kcalloc(entries, sizeof(struct gfs2_dirent *), GFP_KERNEL); - if (!darr) - return -ENOMEM; + struct gfs2_inode *ip = inode->u.generic_ip; + struct buffer_head *bh; + struct gfs2_leaf *lf; + unsigned entries = 0; + unsigned leaves = 0; + const struct gfs2_dirent **darr, *dent; + struct dirent_gather g; + struct buffer_head **larr; + int leaf = 0; + int error, i; + u64 lfn = leaf_no; - dirent_first(dip, bh, &de); do { - if (!de->de_inum.no_addr) - continue; - if (e >= entries) { - gfs2_consist_inode(dip); - error = -EIO; - goto out; - } - darr[e++] = de; - } while (dirent_next(dip, bh, &de) == 0); - - if (e != entries) { - gfs2_consist_inode(dip); - error = -EIO; - goto out; - } - - error = do_filldir_main(dip, offset, opaque, filldir, darr, - entries, copied); - - out: - kfree(darr); - - return error; -} - -/** - * do_filldir_multi - Read directory entries out of a linked leaf list - * @dip: The GFS2 inode - * @offset: The offset in the file to read from - * @opaque: opaque data to pass to filldir - * @filldir: The function to pass entries to - * @bh: the first leaf in the list - * @copied: pointer to int that's non-zero if a entry has been copied out - * - * Returns: errno, >0 on exception from filldir - */ - -static int do_filldir_multi(struct gfs2_inode *dip, uint64_t *offset, - void *opaque, gfs2_filldir_t filldir, - struct buffer_head *bh, int *copied) -{ - struct buffer_head **larr = NULL; - struct gfs2_dirent **darr; - struct gfs2_leaf *leaf; - struct buffer_head *tmp_bh; - struct gfs2_dirent *de; - unsigned int entries, e = 0; - unsigned int leaves = 0, l = 0; - unsigned int x; - uint64_t ln; - int error = 0; - - /* Count leaves and entries */ - - leaf = (struct gfs2_leaf *)bh->b_data; - entries = be16_to_cpu(leaf->lf_entries); - ln = be64_to_cpu(leaf->lf_next); - - while (ln) { - error = get_leaf(dip, ln, &tmp_bh); + error = get_leaf(ip, lfn, &bh); if (error) - return error; - - leaf = (struct gfs2_leaf *)tmp_bh->b_data; - if (leaf->lf_entries) { - entries += be16_to_cpu(leaf->lf_entries); - leaves++; - } - ln = be64_to_cpu(leaf->lf_next); - - brelse(tmp_bh); - } + goto out; + lf = (struct gfs2_leaf *)bh->b_data; + if (leaves == 0) + *depth = be16_to_cpu(lf->lf_depth); + entries += be16_to_cpu(lf->lf_entries); + leaves++; + lfn = be64_to_cpu(lf->lf_next); + brelse(bh); + } while(lfn); if (!entries) return 0; - if (leaves) { - larr = kcalloc(leaves, sizeof(struct buffer_head *),GFP_KERNEL); - if (!larr) - return -ENOMEM; - } - - darr = kcalloc(entries, sizeof(struct gfs2_dirent *), GFP_KERNEL); - if (!darr) { - kfree(larr); - return -ENOMEM; - } - - leaf = (struct gfs2_leaf *)bh->b_data; - if (leaf->lf_entries) { - dirent_first(dip, bh, &de); - do { - if (!de->de_inum.no_addr) - continue; - if (e >= entries) { - gfs2_consist_inode(dip); - error = -EIO; - goto out; - } - darr[e++] = de; - } while (dirent_next(dip, bh, &de) == 0); - } - ln = be64_to_cpu(leaf->lf_next); + error = -ENOMEM; + larr = kmalloc((leaves + entries) * sizeof(void*), GFP_KERNEL); + if (!larr) + goto out; + darr = (const struct gfs2_dirent **)(larr + leaves); + g.pdent = darr; + g.offset = 0; + lfn = leaf_no; - while (ln) { - error = get_leaf(dip, ln, &tmp_bh); + do { + error = get_leaf(ip, lfn, &bh); if (error) - goto out; - - leaf = (struct gfs2_leaf *)tmp_bh->b_data; - if (leaf->lf_entries) { - dirent_first(dip, tmp_bh, &de); - do { - if (!de->de_inum.no_addr) - continue; - if (e >= entries) { - gfs2_consist_inode(dip); - error = -EIO; - goto out; - } - darr[e++] = de; - } while (dirent_next(dip, tmp_bh, &de) == 0); - - larr[l++] = tmp_bh; - - ln = be64_to_cpu(leaf->lf_next); + goto out_kfree; + lf = (struct gfs2_leaf *)bh->b_data; + lfn = be64_to_cpu(lf->lf_next); + if (lf->lf_entries) { + dent = gfs2_dirent_scan(inode, bh->b_data, bh->b_size, + gfs2_dirent_gather, NULL, &g); + error = PTR_ERR(dent); + if (IS_ERR(dent)) { + goto out_kfree; + } + error = 0; + larr[leaf++] = bh; } else { - ln = be64_to_cpu(leaf->lf_next); - brelse(tmp_bh); + brelse(bh); } - } + } while(lfn); - if (gfs2_assert_withdraw(dip->i_sbd, l == leaves)) { - error = -EIO; - goto out; - } - if (e != entries) { - gfs2_consist_inode(dip); - error = -EIO; - goto out; - } - - error = do_filldir_main(dip, offset, opaque, filldir, darr, + error = do_filldir_main(ip, offset, opaque, filldir, darr, entries, copied); - - out: - kfree(darr); - for (x = 0; x < l; x++) - brelse(larr[x]); +out_kfree: + for(i = 0; i < leaf; i++) + brelse(larr[i]); kfree(larr); - +out: return error; } @@ -1412,18 +1330,18 @@ static int do_filldir_multi(struct gfs2_inode *dip, uint64_t *offset, * Returns: errno */ -static int dir_e_read(struct gfs2_inode *dip, uint64_t *offset, void *opaque, +static int dir_e_read(struct inode *inode, uint64_t *offset, void *opaque, gfs2_filldir_t filldir) { + struct gfs2_inode *dip = inode->u.generic_ip; struct gfs2_sbd *sdp = dip->i_sbd; - struct buffer_head *bh; - struct gfs2_leaf *leaf; - uint32_t hsize, len; + uint32_t hsize, len = 0; uint32_t ht_offset, lp_offset, ht_offset_cur = -1; uint32_t hash, index; uint64_t *lp; int copied = 0; int error = 0; + unsigned depth; hsize = 1 << dip->i_di.di_depth; if (hsize * sizeof(uint64_t) != dip->i_di.di_size) { @@ -1454,61 +1372,66 @@ static int dir_e_read(struct gfs2_inode *dip, uint64_t *offset, void *opaque, ht_offset_cur = ht_offset; } - error = get_leaf(dip, be64_to_cpu(lp[lp_offset]), &bh); + error = gfs2_dir_read_leaf(inode, offset, opaque, filldir, + &copied, &depth, + be64_to_cpu(lp[lp_offset])); if (error) - goto out; - - leaf = (struct gfs2_leaf *)bh->b_data; - if (leaf->lf_next) - error = do_filldir_multi(dip, offset, opaque, filldir, - bh, &copied); - else - error = do_filldir_single(dip, offset, opaque, filldir, - bh, - be16_to_cpu(leaf->lf_entries), - &copied); - - brelse(bh); - - if (error) { - if (error > 0) - error = 0; - goto out; - } + break; - len = 1 << (dip->i_di.di_depth - be16_to_cpu(leaf->lf_depth)); + len = 1 << (dip->i_di.di_depth - depth); index = (index & ~(len - 1)) + len; } - out: +out: kfree(lp); - + if (error > 0) + error = 0; return error; } -static int dir_l_read(struct gfs2_inode *dip, uint64_t *offset, void *opaque, - gfs2_filldir_t filldir) +int gfs2_dir_read(struct inode *inode, uint64_t *offset, void *opaque, + gfs2_filldir_t filldir) { + struct gfs2_inode *dip = inode->u.generic_ip; + struct dirent_gather g; + const struct gfs2_dirent **darr, *dent; struct buffer_head *dibh; int copied = 0; int error; + if (!dip->i_di.di_entries) + return 0; + + if (dip->i_di.di_flags & GFS2_DIF_EXHASH) + return dir_e_read(inode, offset, opaque, filldir); + if (!gfs2_is_stuffed(dip)) { gfs2_consist_inode(dip); return -EIO; } - if (!dip->i_di.di_entries) - return 0; - error = gfs2_meta_inode_buffer(dip, &dibh); if (error) return error; - error = do_filldir_single(dip, offset, - opaque, filldir, - dibh, dip->i_di.di_entries, - &copied); + error = -ENOMEM; + darr = kmalloc(dip->i_di.di_entries * sizeof(struct gfs2_dirent *), + GFP_KERNEL); + if (darr) { + g.pdent = darr; + g.offset = 0; + dent = gfs2_dirent_scan(inode, dibh->b_data, dibh->b_size, + gfs2_dirent_gather, NULL, &g); + if (IS_ERR(dent)) { + error = PTR_ERR(dent); + goto out; + } + error = do_filldir_main(dip, offset, opaque, filldir, darr, + dip->i_di.di_entries, &copied); +out: + kfree(darr); + } + if (error > 0) error = 0; @@ -1694,7 +1617,7 @@ int gfs2_dir_del(struct gfs2_inode *dip, const struct qstr *name) return PTR_ERR(dent); } /* If not first in block, adjust pointers accordingly */ - if (gfs2_dirent_find(dent, name) == 0) { + if (gfs2_dirent_find(dent, name, NULL) == 0) { prev = dent; dent = (struct gfs2_dirent *)((char *)dent + be16_to_cpu(prev->de_rec_len)); } @@ -1724,19 +1647,6 @@ int gfs2_dir_del(struct gfs2_inode *dip, const struct qstr *name) return error; } -int gfs2_dir_read(struct gfs2_inode *dip, uint64_t *offset, void *opaque, - gfs2_filldir_t filldir) -{ - int error; - - if (dip->i_di.di_flags & GFS2_DIF_EXHASH) - error = dir_e_read(dip, offset, opaque, filldir); - else - error = dir_l_read(dip, offset, opaque, filldir); - - return error; -} - /** * gfs2_dir_mvino - Change inode number of directory entry * @dip: The GFS2 inode |