diff --git a/fs/xfs/Makefile b/fs/xfs/Makefile index 7f2319d3554d9ae848ebec6664fe64d1fb32b455..b6158afa94e752daf4fdac5cfc61dc0f07e4e357 100644 --- a/fs/xfs/Makefile +++ b/fs/xfs/Makefile @@ -30,6 +30,7 @@ xfs-y += xfs_aops.o \ xfs_bit.o \ xfs_buf.o \ xfs_dfrag.o \ + xfs_dir2_readdir.o \ xfs_discard.o \ xfs_error.o \ xfs_export.o \ diff --git a/fs/xfs/xfs_dir2.c b/fs/xfs/xfs_dir2.c index 8f023dee404da0da9c2092ba15d5ad904588d5e4..431be441347f26a41314afe7a303d3c644fc2e04 100644 --- a/fs/xfs/xfs_dir2.c +++ b/fs/xfs/xfs_dir2.c @@ -362,37 +362,6 @@ xfs_dir_removename( return rval; } -/* - * Read a directory. - */ -int -xfs_readdir( - xfs_inode_t *dp, - struct dir_context *ctx, - size_t bufsize) -{ - int rval; /* return value */ - int v; /* type-checking value */ - - trace_xfs_readdir(dp); - - if (XFS_FORCED_SHUTDOWN(dp->i_mount)) - return XFS_ERROR(EIO); - - ASSERT(S_ISDIR(dp->i_d.di_mode)); - XFS_STATS_INC(xs_dir_getdents); - - if (dp->i_d.di_format == XFS_DINODE_FMT_LOCAL) - rval = xfs_dir2_sf_getdents(dp, ctx); - else if ((rval = xfs_dir2_isblock(NULL, dp, &v))) - ; - else if (v) - rval = xfs_dir2_block_getdents(dp, ctx); - else - rval = xfs_dir2_leaf_getdents(dp, ctx, bufsize); - return rval; -} - /* * Replace the inode number of a directory entry. */ diff --git a/fs/xfs/xfs_dir2_block.c b/fs/xfs/xfs_dir2_block.c index 5e7fbd72cf5255c53a8494a991986c339ea5293a..5e8400064fa986722992fd2ae8d0fb054a6e71fb 100644 --- a/fs/xfs/xfs_dir2_block.c +++ b/fs/xfs/xfs_dir2_block.c @@ -126,7 +126,7 @@ const struct xfs_buf_ops xfs_dir3_block_buf_ops = { .verify_write = xfs_dir3_block_write_verify, }; -static int +int xfs_dir3_block_read( struct xfs_trans *tp, struct xfs_inode *dp, @@ -564,101 +564,6 @@ xfs_dir2_block_addname( return 0; } -/* - * Readdir for block directories. - */ -int /* error */ -xfs_dir2_block_getdents( - xfs_inode_t *dp, /* incore inode */ - struct dir_context *ctx) -{ - xfs_dir2_data_hdr_t *hdr; /* block header */ - struct xfs_buf *bp; /* buffer for block */ - xfs_dir2_block_tail_t *btp; /* block tail */ - xfs_dir2_data_entry_t *dep; /* block data entry */ - xfs_dir2_data_unused_t *dup; /* block unused entry */ - char *endptr; /* end of the data entries */ - int error; /* error return value */ - xfs_mount_t *mp; /* filesystem mount point */ - char *ptr; /* current data entry */ - int wantoff; /* starting block offset */ - xfs_off_t cook; - - mp = dp->i_mount; - /* - * If the block number in the offset is out of range, we're done. - */ - if (xfs_dir2_dataptr_to_db(mp, ctx->pos) > mp->m_dirdatablk) - return 0; - - error = xfs_dir3_block_read(NULL, dp, &bp); - if (error) - return error; - - /* - * Extract the byte offset we start at from the seek pointer. - * We'll skip entries before this. - */ - wantoff = xfs_dir2_dataptr_to_off(mp, ctx->pos); - hdr = bp->b_addr; - xfs_dir3_data_check(dp, bp); - /* - * Set up values for the loop. - */ - btp = xfs_dir2_block_tail_p(mp, hdr); - ptr = (char *)xfs_dir3_data_entry_p(hdr); - endptr = (char *)xfs_dir2_block_leaf_p(btp); - - /* - * Loop over the data portion of the block. - * Each object is a real entry (dep) or an unused one (dup). - */ - while (ptr < endptr) { - dup = (xfs_dir2_data_unused_t *)ptr; - /* - * Unused, skip it. - */ - if (be16_to_cpu(dup->freetag) == XFS_DIR2_DATA_FREE_TAG) { - ptr += be16_to_cpu(dup->length); - continue; - } - - dep = (xfs_dir2_data_entry_t *)ptr; - - /* - * Bump pointer for the next iteration. - */ - ptr += xfs_dir2_data_entsize(dep->namelen); - /* - * The entry is before the desired starting point, skip it. - */ - if ((char *)dep - (char *)hdr < wantoff) - continue; - - cook = xfs_dir2_db_off_to_dataptr(mp, mp->m_dirdatablk, - (char *)dep - (char *)hdr); - - ctx->pos = cook & 0x7fffffff; - /* - * If it didn't fit, set the final offset to here & return. - */ - if (!dir_emit(ctx, (char *)dep->name, dep->namelen, - be64_to_cpu(dep->inumber), DT_UNKNOWN)) { - xfs_trans_brelse(NULL, bp); - return 0; - } - } - - /* - * Reached the end of the block. - * Set the offset to a non-existent block 1 and return. - */ - ctx->pos = xfs_dir2_db_off_to_dataptr(mp, mp->m_dirdatablk + 1, 0) & - 0x7fffffff; - xfs_trans_brelse(NULL, bp); - return 0; -} - /* * Log leaf entries from the block. */ diff --git a/fs/xfs/xfs_dir2_leaf.c b/fs/xfs/xfs_dir2_leaf.c index 2aed25cae04d9f265df00aba8e5d63a624350a0f..e1386aabb6909e1840e1144200cecc62a36de90c 100644 --- a/fs/xfs/xfs_dir2_leaf.c +++ b/fs/xfs/xfs_dir2_leaf.c @@ -1083,396 +1083,6 @@ xfs_dir3_leaf_compact_x1( *highstalep = highstale; } -struct xfs_dir2_leaf_map_info { - xfs_extlen_t map_blocks; /* number of fsbs in map */ - xfs_dablk_t map_off; /* last mapped file offset */ - int map_size; /* total entries in *map */ - int map_valid; /* valid entries in *map */ - int nmap; /* mappings to ask xfs_bmapi */ - xfs_dir2_db_t curdb; /* db for current block */ - int ra_current; /* number of read-ahead blks */ - int ra_index; /* *map index for read-ahead */ - int ra_offset; /* map entry offset for ra */ - int ra_want; /* readahead count wanted */ - struct xfs_bmbt_irec map[]; /* map vector for blocks */ -}; - -STATIC int -xfs_dir2_leaf_readbuf( - struct xfs_inode *dp, - size_t bufsize, - struct xfs_dir2_leaf_map_info *mip, - xfs_dir2_off_t *curoff, - struct xfs_buf **bpp) -{ - struct xfs_mount *mp = dp->i_mount; - struct xfs_buf *bp = *bpp; - struct xfs_bmbt_irec *map = mip->map; - struct blk_plug plug; - int error = 0; - int length; - int i; - int j; - - /* - * If we have a buffer, we need to release it and - * take it out of the mapping. - */ - - if (bp) { - xfs_trans_brelse(NULL, bp); - bp = NULL; - mip->map_blocks -= mp->m_dirblkfsbs; - /* - * Loop to get rid of the extents for the - * directory block. - */ - for (i = mp->m_dirblkfsbs; i > 0; ) { - j = min_t(int, map->br_blockcount, i); - map->br_blockcount -= j; - map->br_startblock += j; - map->br_startoff += j; - /* - * If mapping is done, pitch it from - * the table. - */ - if (!map->br_blockcount && --mip->map_valid) - memmove(&map[0], &map[1], - sizeof(map[0]) * mip->map_valid); - i -= j; - } - } - - /* - * Recalculate the readahead blocks wanted. - */ - mip->ra_want = howmany(bufsize + mp->m_dirblksize, - mp->m_sb.sb_blocksize) - 1; - ASSERT(mip->ra_want >= 0); - - /* - * If we don't have as many as we want, and we haven't - * run out of data blocks, get some more mappings. - */ - if (1 + mip->ra_want > mip->map_blocks && - mip->map_off < xfs_dir2_byte_to_da(mp, XFS_DIR2_LEAF_OFFSET)) { - /* - * Get more bmaps, fill in after the ones - * we already have in the table. - */ - mip->nmap = mip->map_size - mip->map_valid; - error = xfs_bmapi_read(dp, mip->map_off, - xfs_dir2_byte_to_da(mp, XFS_DIR2_LEAF_OFFSET) - - mip->map_off, - &map[mip->map_valid], &mip->nmap, 0); - - /* - * Don't know if we should ignore this or try to return an - * error. The trouble with returning errors is that readdir - * will just stop without actually passing the error through. - */ - if (error) - goto out; /* XXX */ - - /* - * If we got all the mappings we asked for, set the final map - * offset based on the last bmap value received. Otherwise, - * we've reached the end. - */ - if (mip->nmap == mip->map_size - mip->map_valid) { - i = mip->map_valid + mip->nmap - 1; - mip->map_off = map[i].br_startoff + map[i].br_blockcount; - } else - mip->map_off = xfs_dir2_byte_to_da(mp, - XFS_DIR2_LEAF_OFFSET); - - /* - * Look for holes in the mapping, and eliminate them. Count up - * the valid blocks. - */ - for (i = mip->map_valid; i < mip->map_valid + mip->nmap; ) { - if (map[i].br_startblock == HOLESTARTBLOCK) { - mip->nmap--; - length = mip->map_valid + mip->nmap - i; - if (length) - memmove(&map[i], &map[i + 1], - sizeof(map[i]) * length); - } else { - mip->map_blocks += map[i].br_blockcount; - i++; - } - } - mip->map_valid += mip->nmap; - } - - /* - * No valid mappings, so no more data blocks. - */ - if (!mip->map_valid) { - *curoff = xfs_dir2_da_to_byte(mp, mip->map_off); - goto out; - } - - /* - * Read the directory block starting at the first mapping. - */ - mip->curdb = xfs_dir2_da_to_db(mp, map->br_startoff); - error = xfs_dir3_data_read(NULL, dp, map->br_startoff, - map->br_blockcount >= mp->m_dirblkfsbs ? - XFS_FSB_TO_DADDR(mp, map->br_startblock) : -1, &bp); - - /* - * Should just skip over the data block instead of giving up. - */ - if (error) - goto out; /* XXX */ - - /* - * Adjust the current amount of read-ahead: we just read a block that - * was previously ra. - */ - if (mip->ra_current) - mip->ra_current -= mp->m_dirblkfsbs; - - /* - * Do we need more readahead? - */ - blk_start_plug(&plug); - for (mip->ra_index = mip->ra_offset = i = 0; - mip->ra_want > mip->ra_current && i < mip->map_blocks; - i += mp->m_dirblkfsbs) { - ASSERT(mip->ra_index < mip->map_valid); - /* - * Read-ahead a contiguous directory block. - */ - if (i > mip->ra_current && - map[mip->ra_index].br_blockcount >= mp->m_dirblkfsbs) { - xfs_dir3_data_readahead(NULL, dp, - map[mip->ra_index].br_startoff + mip->ra_offset, - XFS_FSB_TO_DADDR(mp, - map[mip->ra_index].br_startblock + - mip->ra_offset)); - mip->ra_current = i; - } - - /* - * Read-ahead a non-contiguous directory block. This doesn't - * use our mapping, but this is a very rare case. - */ - else if (i > mip->ra_current) { - xfs_dir3_data_readahead(NULL, dp, - map[mip->ra_index].br_startoff + - mip->ra_offset, -1); - mip->ra_current = i; - } - - /* - * Advance offset through the mapping table. - */ - for (j = 0; j < mp->m_dirblkfsbs; j++) { - /* - * The rest of this extent but not more than a dir - * block. - */ - length = min_t(int, mp->m_dirblkfsbs, - map[mip->ra_index].br_blockcount - - mip->ra_offset); - j += length; - mip->ra_offset += length; - - /* - * Advance to the next mapping if this one is used up. - */ - if (mip->ra_offset == map[mip->ra_index].br_blockcount) { - mip->ra_offset = 0; - mip->ra_index++; - } - } - } - blk_finish_plug(&plug); - -out: - *bpp = bp; - return error; -} - -/* - * Getdents (readdir) for leaf and node directories. - * This reads the data blocks only, so is the same for both forms. - */ -int /* error */ -xfs_dir2_leaf_getdents( - xfs_inode_t *dp, /* incore directory inode */ - struct dir_context *ctx, - size_t bufsize) -{ - struct xfs_buf *bp = NULL; /* data block buffer */ - xfs_dir2_data_hdr_t *hdr; /* data block header */ - xfs_dir2_data_entry_t *dep; /* data entry */ - xfs_dir2_data_unused_t *dup; /* unused entry */ - int error = 0; /* error return value */ - int length; /* temporary length value */ - xfs_mount_t *mp; /* filesystem mount point */ - int byteoff; /* offset in current block */ - xfs_dir2_off_t curoff; /* current overall offset */ - xfs_dir2_off_t newoff; /* new curoff after new blk */ - char *ptr = NULL; /* pointer to current data */ - struct xfs_dir2_leaf_map_info *map_info; - - /* - * If the offset is at or past the largest allowed value, - * give up right away. - */ - if (ctx->pos >= XFS_DIR2_MAX_DATAPTR) - return 0; - - mp = dp->i_mount; - - /* - * Set up to bmap a number of blocks based on the caller's - * buffer size, the directory block size, and the filesystem - * block size. - */ - length = howmany(bufsize + mp->m_dirblksize, - mp->m_sb.sb_blocksize); - map_info = kmem_zalloc(offsetof(struct xfs_dir2_leaf_map_info, map) + - (length * sizeof(struct xfs_bmbt_irec)), - KM_SLEEP | KM_NOFS); - map_info->map_size = length; - - /* - * Inside the loop we keep the main offset value as a byte offset - * in the directory file. - */ - curoff = xfs_dir2_dataptr_to_byte(mp, ctx->pos); - - /* - * Force this conversion through db so we truncate the offset - * down to get the start of the data block. - */ - map_info->map_off = xfs_dir2_db_to_da(mp, - xfs_dir2_byte_to_db(mp, curoff)); - - /* - * Loop over directory entries until we reach the end offset. - * Get more blocks and readahead as necessary. - */ - while (curoff < XFS_DIR2_LEAF_OFFSET) { - /* - * If we have no buffer, or we're off the end of the - * current buffer, need to get another one. - */ - if (!bp || ptr >= (char *)bp->b_addr + mp->m_dirblksize) { - - error = xfs_dir2_leaf_readbuf(dp, bufsize, map_info, - &curoff, &bp); - if (error || !map_info->map_valid) - break; - - /* - * Having done a read, we need to set a new offset. - */ - newoff = xfs_dir2_db_off_to_byte(mp, map_info->curdb, 0); - /* - * Start of the current block. - */ - if (curoff < newoff) - curoff = newoff; - /* - * Make sure we're in the right block. - */ - else if (curoff > newoff) - ASSERT(xfs_dir2_byte_to_db(mp, curoff) == - map_info->curdb); - hdr = bp->b_addr; - xfs_dir3_data_check(dp, bp); - /* - * Find our position in the block. - */ - ptr = (char *)xfs_dir3_data_entry_p(hdr); - byteoff = xfs_dir2_byte_to_off(mp, curoff); - /* - * Skip past the header. - */ - if (byteoff == 0) - curoff += xfs_dir3_data_entry_offset(hdr); - /* - * Skip past entries until we reach our offset. - */ - else { - while ((char *)ptr - (char *)hdr < byteoff) { - dup = (xfs_dir2_data_unused_t *)ptr; - - if (be16_to_cpu(dup->freetag) - == XFS_DIR2_DATA_FREE_TAG) { - - length = be16_to_cpu(dup->length); - ptr += length; - continue; - } - dep = (xfs_dir2_data_entry_t *)ptr; - length = - xfs_dir2_data_entsize(dep->namelen); - ptr += length; - } - /* - * Now set our real offset. - */ - curoff = - xfs_dir2_db_off_to_byte(mp, - xfs_dir2_byte_to_db(mp, curoff), - (char *)ptr - (char *)hdr); - if (ptr >= (char *)hdr + mp->m_dirblksize) { - continue; - } - } - } - /* - * We have a pointer to an entry. - * Is it a live one? - */ - dup = (xfs_dir2_data_unused_t *)ptr; - /* - * No, it's unused, skip over it. - */ - if (be16_to_cpu(dup->freetag) == XFS_DIR2_DATA_FREE_TAG) { - length = be16_to_cpu(dup->length); - ptr += length; - curoff += length; - continue; - } - - dep = (xfs_dir2_data_entry_t *)ptr; - length = xfs_dir2_data_entsize(dep->namelen); - - ctx->pos = xfs_dir2_byte_to_dataptr(mp, curoff) & 0x7fffffff; - if (!dir_emit(ctx, (char *)dep->name, dep->namelen, - be64_to_cpu(dep->inumber), DT_UNKNOWN)) - break; - - /* - * Advance to next entry in the block. - */ - ptr += length; - curoff += length; - /* bufsize may have just been a guess; don't go negative */ - bufsize = bufsize > length ? bufsize - length : 0; - } - - /* - * All done. Set output offset value to current offset. - */ - if (curoff > xfs_dir2_dataptr_to_byte(mp, XFS_DIR2_MAX_DATAPTR)) - ctx->pos = XFS_DIR2_MAX_DATAPTR & 0x7fffffff; - else - ctx->pos = xfs_dir2_byte_to_dataptr(mp, curoff) & 0x7fffffff; - kmem_free(map_info); - if (bp) - xfs_trans_brelse(NULL, bp); - return error; -} - - /* * Log the bests entries indicated from a leaf1 block. */ diff --git a/fs/xfs/xfs_dir2_priv.h b/fs/xfs/xfs_dir2_priv.h index 0511cda4a712a480682b946829c5587cf37c4070..72ff8d74e8ebff8bfcc8c9309e5091cd638e273a 100644 --- a/fs/xfs/xfs_dir2_priv.h +++ b/fs/xfs/xfs_dir2_priv.h @@ -32,9 +32,9 @@ extern int xfs_dir_cilookup_result(struct xfs_da_args *args, /* xfs_dir2_block.c */ extern const struct xfs_buf_ops xfs_dir3_block_buf_ops; +extern int xfs_dir3_block_read(struct xfs_trans *tp, struct xfs_inode *dp, + struct xfs_buf **bpp); extern int xfs_dir2_block_addname(struct xfs_da_args *args); -extern int xfs_dir2_block_getdents(struct xfs_inode *dp, - struct dir_context *ctx); extern int xfs_dir2_block_lookup(struct xfs_da_args *args); extern int xfs_dir2_block_removename(struct xfs_da_args *args); extern int xfs_dir2_block_replace(struct xfs_da_args *args); @@ -91,8 +91,6 @@ extern void xfs_dir3_leaf_compact(struct xfs_da_args *args, extern void xfs_dir3_leaf_compact_x1(struct xfs_dir3_icleaf_hdr *leafhdr, struct xfs_dir2_leaf_entry *ents, int *indexp, int *lowstalep, int *highstalep, int *lowlogp, int *highlogp); -extern int xfs_dir2_leaf_getdents(struct xfs_inode *dp, struct dir_context *ctx, - size_t bufsize); extern int xfs_dir3_leaf_get_buf(struct xfs_da_args *args, xfs_dir2_db_t bno, struct xfs_buf **bpp, __uint16_t magic); extern void xfs_dir3_leaf_log_ents(struct xfs_trans *tp, struct xfs_buf *bp, @@ -153,7 +151,6 @@ extern int xfs_dir2_block_to_sf(struct xfs_da_args *args, struct xfs_buf *bp, int size, xfs_dir2_sf_hdr_t *sfhp); extern int xfs_dir2_sf_addname(struct xfs_da_args *args); extern int xfs_dir2_sf_create(struct xfs_da_args *args, xfs_ino_t pino); -extern int xfs_dir2_sf_getdents(struct xfs_inode *dp, struct dir_context *ctx); extern int xfs_dir2_sf_lookup(struct xfs_da_args *args); extern int xfs_dir2_sf_removename(struct xfs_da_args *args); extern int xfs_dir2_sf_replace(struct xfs_da_args *args); diff --git a/fs/xfs/xfs_dir2_readdir.c b/fs/xfs/xfs_dir2_readdir.c new file mode 100644 index 0000000000000000000000000000000000000000..2bc4b324ff76b746955739561eb80abae97b006a --- /dev/null +++ b/fs/xfs/xfs_dir2_readdir.c @@ -0,0 +1,645 @@ +/* + * Copyright (c) 2000-2005 Silicon Graphics, Inc. + * Copyright (c) 2013 Red Hat, Inc. + * All Rights Reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it would be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +#include "xfs.h" +#include "xfs_fs.h" +#include "xfs_types.h" +#include "xfs_bit.h" +#include "xfs_log.h" +#include "xfs_trans.h" +#include "xfs_sb.h" +#include "xfs_ag.h" +#include "xfs_mount.h" +#include "xfs_da_btree.h" +#include "xfs_bmap_btree.h" +#include "xfs_dinode.h" +#include "xfs_inode.h" +#include "xfs_dir2_format.h" +#include "xfs_dir2_priv.h" +#include "xfs_error.h" +#include "xfs_trace.h" +#include "xfs_bmap.h" + +STATIC int +xfs_dir2_sf_getdents( + xfs_inode_t *dp, /* incore directory inode */ + struct dir_context *ctx) +{ + int i; /* shortform entry number */ + xfs_mount_t *mp; /* filesystem mount point */ + xfs_dir2_dataptr_t off; /* current entry's offset */ + xfs_dir2_sf_entry_t *sfep; /* shortform directory entry */ + xfs_dir2_sf_hdr_t *sfp; /* shortform structure */ + xfs_dir2_dataptr_t dot_offset; + xfs_dir2_dataptr_t dotdot_offset; + xfs_ino_t ino; + + mp = dp->i_mount; + + ASSERT(dp->i_df.if_flags & XFS_IFINLINE); + /* + * Give up if the directory is way too short. + */ + if (dp->i_d.di_size < offsetof(xfs_dir2_sf_hdr_t, parent)) { + ASSERT(XFS_FORCED_SHUTDOWN(mp)); + return XFS_ERROR(EIO); + } + + ASSERT(dp->i_df.if_bytes == dp->i_d.di_size); + ASSERT(dp->i_df.if_u1.if_data != NULL); + + sfp = (xfs_dir2_sf_hdr_t *)dp->i_df.if_u1.if_data; + + ASSERT(dp->i_d.di_size >= xfs_dir2_sf_hdr_size(sfp->i8count)); + + /* + * If the block number in the offset is out of range, we're done. + */ + if (xfs_dir2_dataptr_to_db(mp, ctx->pos) > mp->m_dirdatablk) + return 0; + + /* + * Precalculate offsets for . and .. as we will always need them. + * + * XXX(hch): the second argument is sometimes 0 and sometimes + * mp->m_dirdatablk. + */ + dot_offset = xfs_dir2_db_off_to_dataptr(mp, mp->m_dirdatablk, + XFS_DIR3_DATA_DOT_OFFSET(mp)); + dotdot_offset = xfs_dir2_db_off_to_dataptr(mp, mp->m_dirdatablk, + XFS_DIR3_DATA_DOTDOT_OFFSET(mp)); + + /* + * Put . entry unless we're starting past it. + */ + if (ctx->pos <= dot_offset) { + ctx->pos = dot_offset & 0x7fffffff; + if (!dir_emit(ctx, ".", 1, dp->i_ino, DT_DIR)) + return 0; + } + + /* + * Put .. entry unless we're starting past it. + */ + if (ctx->pos <= dotdot_offset) { + ino = xfs_dir2_sf_get_parent_ino(sfp); + ctx->pos = dotdot_offset & 0x7fffffff; + if (!dir_emit(ctx, "..", 2, ino, DT_DIR)) + return 0; + } + + /* + * Loop while there are more entries and put'ing works. + */ + sfep = xfs_dir2_sf_firstentry(sfp); + for (i = 0; i < sfp->count; i++) { + off = xfs_dir2_db_off_to_dataptr(mp, mp->m_dirdatablk, + xfs_dir2_sf_get_offset(sfep)); + + if (ctx->pos > off) { + sfep = xfs_dir2_sf_nextentry(sfp, sfep); + continue; + } + + ino = xfs_dir2_sfe_get_ino(sfp, sfep); + ctx->pos = off & 0x7fffffff; + if (!dir_emit(ctx, (char *)sfep->name, sfep->namelen, + ino, DT_UNKNOWN)) + return 0; + sfep = xfs_dir2_sf_nextentry(sfp, sfep); + } + + ctx->pos = xfs_dir2_db_off_to_dataptr(mp, mp->m_dirdatablk + 1, 0) & + 0x7fffffff; + return 0; +} + +/* + * Readdir for block directories. + */ +STATIC int +xfs_dir2_block_getdents( + xfs_inode_t *dp, /* incore inode */ + struct dir_context *ctx) +{ + xfs_dir2_data_hdr_t *hdr; /* block header */ + struct xfs_buf *bp; /* buffer for block */ + xfs_dir2_block_tail_t *btp; /* block tail */ + xfs_dir2_data_entry_t *dep; /* block data entry */ + xfs_dir2_data_unused_t *dup; /* block unused entry */ + char *endptr; /* end of the data entries */ + int error; /* error return value */ + xfs_mount_t *mp; /* filesystem mount point */ + char *ptr; /* current data entry */ + int wantoff; /* starting block offset */ + xfs_off_t cook; + + mp = dp->i_mount; + /* + * If the block number in the offset is out of range, we're done. + */ + if (xfs_dir2_dataptr_to_db(mp, ctx->pos) > mp->m_dirdatablk) + return 0; + + error = xfs_dir3_block_read(NULL, dp, &bp); + if (error) + return error; + + /* + * Extract the byte offset we start at from the seek pointer. + * We'll skip entries before this. + */ + wantoff = xfs_dir2_dataptr_to_off(mp, ctx->pos); + hdr = bp->b_addr; + xfs_dir3_data_check(dp, bp); + /* + * Set up values for the loop. + */ + btp = xfs_dir2_block_tail_p(mp, hdr); + ptr = (char *)xfs_dir3_data_entry_p(hdr); + endptr = (char *)xfs_dir2_block_leaf_p(btp); + + /* + * Loop over the data portion of the block. + * Each object is a real entry (dep) or an unused one (dup). + */ + while (ptr < endptr) { + dup = (xfs_dir2_data_unused_t *)ptr; + /* + * Unused, skip it. + */ + if (be16_to_cpu(dup->freetag) == XFS_DIR2_DATA_FREE_TAG) { + ptr += be16_to_cpu(dup->length); + continue; + } + + dep = (xfs_dir2_data_entry_t *)ptr; + + /* + * Bump pointer for the next iteration. + */ + ptr += xfs_dir2_data_entsize(dep->namelen); + /* + * The entry is before the desired starting point, skip it. + */ + if ((char *)dep - (char *)hdr < wantoff) + continue; + + cook = xfs_dir2_db_off_to_dataptr(mp, mp->m_dirdatablk, + (char *)dep - (char *)hdr); + + ctx->pos = cook & 0x7fffffff; + /* + * If it didn't fit, set the final offset to here & return. + */ + if (!dir_emit(ctx, (char *)dep->name, dep->namelen, + be64_to_cpu(dep->inumber), DT_UNKNOWN)) { + xfs_trans_brelse(NULL, bp); + return 0; + } + } + + /* + * Reached the end of the block. + * Set the offset to a non-existent block 1 and return. + */ + ctx->pos = xfs_dir2_db_off_to_dataptr(mp, mp->m_dirdatablk + 1, 0) & + 0x7fffffff; + xfs_trans_brelse(NULL, bp); + return 0; +} + +struct xfs_dir2_leaf_map_info { + xfs_extlen_t map_blocks; /* number of fsbs in map */ + xfs_dablk_t map_off; /* last mapped file offset */ + int map_size; /* total entries in *map */ + int map_valid; /* valid entries in *map */ + int nmap; /* mappings to ask xfs_bmapi */ + xfs_dir2_db_t curdb; /* db for current block */ + int ra_current; /* number of read-ahead blks */ + int ra_index; /* *map index for read-ahead */ + int ra_offset; /* map entry offset for ra */ + int ra_want; /* readahead count wanted */ + struct xfs_bmbt_irec map[]; /* map vector for blocks */ +}; + +STATIC int +xfs_dir2_leaf_readbuf( + struct xfs_inode *dp, + size_t bufsize, + struct xfs_dir2_leaf_map_info *mip, + xfs_dir2_off_t *curoff, + struct xfs_buf **bpp) +{ + struct xfs_mount *mp = dp->i_mount; + struct xfs_buf *bp = *bpp; + struct xfs_bmbt_irec *map = mip->map; + struct blk_plug plug; + int error = 0; + int length; + int i; + int j; + + /* + * If we have a buffer, we need to release it and + * take it out of the mapping. + */ + + if (bp) { + xfs_trans_brelse(NULL, bp); + bp = NULL; + mip->map_blocks -= mp->m_dirblkfsbs; + /* + * Loop to get rid of the extents for the + * directory block. + */ + for (i = mp->m_dirblkfsbs; i > 0; ) { + j = min_t(int, map->br_blockcount, i); + map->br_blockcount -= j; + map->br_startblock += j; + map->br_startoff += j; + /* + * If mapping is done, pitch it from + * the table. + */ + if (!map->br_blockcount && --mip->map_valid) + memmove(&map[0], &map[1], + sizeof(map[0]) * mip->map_valid); + i -= j; + } + } + + /* + * Recalculate the readahead blocks wanted. + */ + mip->ra_want = howmany(bufsize + mp->m_dirblksize, + mp->m_sb.sb_blocksize) - 1; + ASSERT(mip->ra_want >= 0); + + /* + * If we don't have as many as we want, and we haven't + * run out of data blocks, get some more mappings. + */ + if (1 + mip->ra_want > mip->map_blocks && + mip->map_off < xfs_dir2_byte_to_da(mp, XFS_DIR2_LEAF_OFFSET)) { + /* + * Get more bmaps, fill in after the ones + * we already have in the table. + */ + mip->nmap = mip->map_size - mip->map_valid; + error = xfs_bmapi_read(dp, mip->map_off, + xfs_dir2_byte_to_da(mp, XFS_DIR2_LEAF_OFFSET) - + mip->map_off, + &map[mip->map_valid], &mip->nmap, 0); + + /* + * Don't know if we should ignore this or try to return an + * error. The trouble with returning errors is that readdir + * will just stop without actually passing the error through. + */ + if (error) + goto out; /* XXX */ + + /* + * If we got all the mappings we asked for, set the final map + * offset based on the last bmap value received. Otherwise, + * we've reached the end. + */ + if (mip->nmap == mip->map_size - mip->map_valid) { + i = mip->map_valid + mip->nmap - 1; + mip->map_off = map[i].br_startoff + map[i].br_blockcount; + } else + mip->map_off = xfs_dir2_byte_to_da(mp, + XFS_DIR2_LEAF_OFFSET); + + /* + * Look for holes in the mapping, and eliminate them. Count up + * the valid blocks. + */ + for (i = mip->map_valid; i < mip->map_valid + mip->nmap; ) { + if (map[i].br_startblock == HOLESTARTBLOCK) { + mip->nmap--; + length = mip->map_valid + mip->nmap - i; + if (length) + memmove(&map[i], &map[i + 1], + sizeof(map[i]) * length); + } else { + mip->map_blocks += map[i].br_blockcount; + i++; + } + } + mip->map_valid += mip->nmap; + } + + /* + * No valid mappings, so no more data blocks. + */ + if (!mip->map_valid) { + *curoff = xfs_dir2_da_to_byte(mp, mip->map_off); + goto out; + } + + /* + * Read the directory block starting at the first mapping. + */ + mip->curdb = xfs_dir2_da_to_db(mp, map->br_startoff); + error = xfs_dir3_data_read(NULL, dp, map->br_startoff, + map->br_blockcount >= mp->m_dirblkfsbs ? + XFS_FSB_TO_DADDR(mp, map->br_startblock) : -1, &bp); + + /* + * Should just skip over the data block instead of giving up. + */ + if (error) + goto out; /* XXX */ + + /* + * Adjust the current amount of read-ahead: we just read a block that + * was previously ra. + */ + if (mip->ra_current) + mip->ra_current -= mp->m_dirblkfsbs; + + /* + * Do we need more readahead? + */ + blk_start_plug(&plug); + for (mip->ra_index = mip->ra_offset = i = 0; + mip->ra_want > mip->ra_current && i < mip->map_blocks; + i += mp->m_dirblkfsbs) { + ASSERT(mip->ra_index < mip->map_valid); + /* + * Read-ahead a contiguous directory block. + */ + if (i > mip->ra_current && + map[mip->ra_index].br_blockcount >= mp->m_dirblkfsbs) { + xfs_dir3_data_readahead(NULL, dp, + map[mip->ra_index].br_startoff + mip->ra_offset, + XFS_FSB_TO_DADDR(mp, + map[mip->ra_index].br_startblock + + mip->ra_offset)); + mip->ra_current = i; + } + + /* + * Read-ahead a non-contiguous directory block. This doesn't + * use our mapping, but this is a very rare case. + */ + else if (i > mip->ra_current) { + xfs_dir3_data_readahead(NULL, dp, + map[mip->ra_index].br_startoff + + mip->ra_offset, -1); + mip->ra_current = i; + } + + /* + * Advance offset through the mapping table. + */ + for (j = 0; j < mp->m_dirblkfsbs; j++) { + /* + * The rest of this extent but not more than a dir + * block. + */ + length = min_t(int, mp->m_dirblkfsbs, + map[mip->ra_index].br_blockcount - + mip->ra_offset); + j += length; + mip->ra_offset += length; + + /* + * Advance to the next mapping if this one is used up. + */ + if (mip->ra_offset == map[mip->ra_index].br_blockcount) { + mip->ra_offset = 0; + mip->ra_index++; + } + } + } + blk_finish_plug(&plug); + +out: + *bpp = bp; + return error; +} + +/* + * Getdents (readdir) for leaf and node directories. + * This reads the data blocks only, so is the same for both forms. + */ +STATIC int +xfs_dir2_leaf_getdents( + xfs_inode_t *dp, /* incore directory inode */ + struct dir_context *ctx, + size_t bufsize) +{ + struct xfs_buf *bp = NULL; /* data block buffer */ + xfs_dir2_data_hdr_t *hdr; /* data block header */ + xfs_dir2_data_entry_t *dep; /* data entry */ + xfs_dir2_data_unused_t *dup; /* unused entry */ + int error = 0; /* error return value */ + int length; /* temporary length value */ + xfs_mount_t *mp; /* filesystem mount point */ + int byteoff; /* offset in current block */ + xfs_dir2_off_t curoff; /* current overall offset */ + xfs_dir2_off_t newoff; /* new curoff after new blk */ + char *ptr = NULL; /* pointer to current data */ + struct xfs_dir2_leaf_map_info *map_info; + + /* + * If the offset is at or past the largest allowed value, + * give up right away. + */ + if (ctx->pos >= XFS_DIR2_MAX_DATAPTR) + return 0; + + mp = dp->i_mount; + + /* + * Set up to bmap a number of blocks based on the caller's + * buffer size, the directory block size, and the filesystem + * block size. + */ + length = howmany(bufsize + mp->m_dirblksize, + mp->m_sb.sb_blocksize); + map_info = kmem_zalloc(offsetof(struct xfs_dir2_leaf_map_info, map) + + (length * sizeof(struct xfs_bmbt_irec)), + KM_SLEEP | KM_NOFS); + map_info->map_size = length; + + /* + * Inside the loop we keep the main offset value as a byte offset + * in the directory file. + */ + curoff = xfs_dir2_dataptr_to_byte(mp, ctx->pos); + + /* + * Force this conversion through db so we truncate the offset + * down to get the start of the data block. + */ + map_info->map_off = xfs_dir2_db_to_da(mp, + xfs_dir2_byte_to_db(mp, curoff)); + + /* + * Loop over directory entries until we reach the end offset. + * Get more blocks and readahead as necessary. + */ + while (curoff < XFS_DIR2_LEAF_OFFSET) { + /* + * If we have no buffer, or we're off the end of the + * current buffer, need to get another one. + */ + if (!bp || ptr >= (char *)bp->b_addr + mp->m_dirblksize) { + + error = xfs_dir2_leaf_readbuf(dp, bufsize, map_info, + &curoff, &bp); + if (error || !map_info->map_valid) + break; + + /* + * Having done a read, we need to set a new offset. + */ + newoff = xfs_dir2_db_off_to_byte(mp, map_info->curdb, 0); + /* + * Start of the current block. + */ + if (curoff < newoff) + curoff = newoff; + /* + * Make sure we're in the right block. + */ + else if (curoff > newoff) + ASSERT(xfs_dir2_byte_to_db(mp, curoff) == + map_info->curdb); + hdr = bp->b_addr; + xfs_dir3_data_check(dp, bp); + /* + * Find our position in the block. + */ + ptr = (char *)xfs_dir3_data_entry_p(hdr); + byteoff = xfs_dir2_byte_to_off(mp, curoff); + /* + * Skip past the header. + */ + if (byteoff == 0) + curoff += xfs_dir3_data_entry_offset(hdr); + /* + * Skip past entries until we reach our offset. + */ + else { + while ((char *)ptr - (char *)hdr < byteoff) { + dup = (xfs_dir2_data_unused_t *)ptr; + + if (be16_to_cpu(dup->freetag) + == XFS_DIR2_DATA_FREE_TAG) { + + length = be16_to_cpu(dup->length); + ptr += length; + continue; + } + dep = (xfs_dir2_data_entry_t *)ptr; + length = + xfs_dir2_data_entsize(dep->namelen); + ptr += length; + } + /* + * Now set our real offset. + */ + curoff = + xfs_dir2_db_off_to_byte(mp, + xfs_dir2_byte_to_db(mp, curoff), + (char *)ptr - (char *)hdr); + if (ptr >= (char *)hdr + mp->m_dirblksize) { + continue; + } + } + } + /* + * We have a pointer to an entry. + * Is it a live one? + */ + dup = (xfs_dir2_data_unused_t *)ptr; + /* + * No, it's unused, skip over it. + */ + if (be16_to_cpu(dup->freetag) == XFS_DIR2_DATA_FREE_TAG) { + length = be16_to_cpu(dup->length); + ptr += length; + curoff += length; + continue; + } + + dep = (xfs_dir2_data_entry_t *)ptr; + length = xfs_dir2_data_entsize(dep->namelen); + + ctx->pos = xfs_dir2_byte_to_dataptr(mp, curoff) & 0x7fffffff; + if (!dir_emit(ctx, (char *)dep->name, dep->namelen, + be64_to_cpu(dep->inumber), DT_UNKNOWN)) + break; + + /* + * Advance to next entry in the block. + */ + ptr += length; + curoff += length; + /* bufsize may have just been a guess; don't go negative */ + bufsize = bufsize > length ? bufsize - length : 0; + } + + /* + * All done. Set output offset value to current offset. + */ + if (curoff > xfs_dir2_dataptr_to_byte(mp, XFS_DIR2_MAX_DATAPTR)) + ctx->pos = XFS_DIR2_MAX_DATAPTR & 0x7fffffff; + else + ctx->pos = xfs_dir2_byte_to_dataptr(mp, curoff) & 0x7fffffff; + kmem_free(map_info); + if (bp) + xfs_trans_brelse(NULL, bp); + return error; +} + +/* + * Read a directory. + */ +int +xfs_readdir( + xfs_inode_t *dp, + struct dir_context *ctx, + size_t bufsize) +{ + int rval; /* return value */ + int v; /* type-checking value */ + + trace_xfs_readdir(dp); + + if (XFS_FORCED_SHUTDOWN(dp->i_mount)) + return XFS_ERROR(EIO); + + ASSERT(S_ISDIR(dp->i_d.di_mode)); + XFS_STATS_INC(xs_dir_getdents); + + if (dp->i_d.di_format == XFS_DINODE_FMT_LOCAL) + rval = xfs_dir2_sf_getdents(dp, ctx); + else if ((rval = xfs_dir2_isblock(NULL, dp, &v))) + ; + else if (v) + rval = xfs_dir2_block_getdents(dp, ctx); + else + rval = xfs_dir2_leaf_getdents(dp, ctx, bufsize); + return rval; +} diff --git a/fs/xfs/xfs_dir2_sf.c b/fs/xfs/xfs_dir2_sf.c index 97676a347da166e5d843db8ab2052666390e018d..f24ce9044e72c8b70c7b360e45bb685e699bad50 100644 --- a/fs/xfs/xfs_dir2_sf.c +++ b/fs/xfs/xfs_dir2_sf.c @@ -765,100 +765,6 @@ xfs_dir2_sf_create( return 0; } -int /* error */ -xfs_dir2_sf_getdents( - xfs_inode_t *dp, /* incore directory inode */ - struct dir_context *ctx) -{ - int i; /* shortform entry number */ - xfs_mount_t *mp; /* filesystem mount point */ - xfs_dir2_dataptr_t off; /* current entry's offset */ - xfs_dir2_sf_entry_t *sfep; /* shortform directory entry */ - xfs_dir2_sf_hdr_t *sfp; /* shortform structure */ - xfs_dir2_dataptr_t dot_offset; - xfs_dir2_dataptr_t dotdot_offset; - xfs_ino_t ino; - - mp = dp->i_mount; - - ASSERT(dp->i_df.if_flags & XFS_IFINLINE); - /* - * Give up if the directory is way too short. - */ - if (dp->i_d.di_size < offsetof(xfs_dir2_sf_hdr_t, parent)) { - ASSERT(XFS_FORCED_SHUTDOWN(mp)); - return XFS_ERROR(EIO); - } - - ASSERT(dp->i_df.if_bytes == dp->i_d.di_size); - ASSERT(dp->i_df.if_u1.if_data != NULL); - - sfp = (xfs_dir2_sf_hdr_t *)dp->i_df.if_u1.if_data; - - ASSERT(dp->i_d.di_size >= xfs_dir2_sf_hdr_size(sfp->i8count)); - - /* - * If the block number in the offset is out of range, we're done. - */ - if (xfs_dir2_dataptr_to_db(mp, ctx->pos) > mp->m_dirdatablk) - return 0; - - /* - * Precalculate offsets for . and .. as we will always need them. - * - * XXX(hch): the second argument is sometimes 0 and sometimes - * mp->m_dirdatablk. - */ - dot_offset = xfs_dir2_db_off_to_dataptr(mp, mp->m_dirdatablk, - XFS_DIR3_DATA_DOT_OFFSET(mp)); - dotdot_offset = xfs_dir2_db_off_to_dataptr(mp, mp->m_dirdatablk, - XFS_DIR3_DATA_DOTDOT_OFFSET(mp)); - - /* - * Put . entry unless we're starting past it. - */ - if (ctx->pos <= dot_offset) { - ctx->pos = dot_offset & 0x7fffffff; - if (!dir_emit(ctx, ".", 1, dp->i_ino, DT_DIR)) - return 0; - } - - /* - * Put .. entry unless we're starting past it. - */ - if (ctx->pos <= dotdot_offset) { - ino = xfs_dir2_sf_get_parent_ino(sfp); - ctx->pos = dotdot_offset & 0x7fffffff; - if (!dir_emit(ctx, "..", 2, ino, DT_DIR)) - return 0; - } - - /* - * Loop while there are more entries and put'ing works. - */ - sfep = xfs_dir2_sf_firstentry(sfp); - for (i = 0; i < sfp->count; i++) { - off = xfs_dir2_db_off_to_dataptr(mp, mp->m_dirdatablk, - xfs_dir2_sf_get_offset(sfep)); - - if (ctx->pos > off) { - sfep = xfs_dir2_sf_nextentry(sfp, sfep); - continue; - } - - ino = xfs_dir2_sfe_get_ino(sfp, sfep); - ctx->pos = off & 0x7fffffff; - if (!dir_emit(ctx, (char *)sfep->name, sfep->namelen, - ino, DT_UNKNOWN)) - return 0; - sfep = xfs_dir2_sf_nextentry(sfp, sfep); - } - - ctx->pos = xfs_dir2_db_off_to_dataptr(mp, mp->m_dirdatablk + 1, 0) & - 0x7fffffff; - return 0; -} - /* * Lookup an entry in a shortform directory. * Returns EEXIST if found, ENOENT if not found.