]> asedeno.scripts.mit.edu Git - linux.git/blobdiff - fs/xfs/xfs_dir2_readdir.c
Merge branch 'irq-core-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git...
[linux.git] / fs / xfs / xfs_dir2_readdir.c
index 283df898dd9f6f5c02701dc99bda8f7d9f3b0599..0d3b640cf1ccece7eb682d90c6a0f846aa3b8105 100644 (file)
@@ -17,6 +17,7 @@
 #include "xfs_trace.h"
 #include "xfs_bmap.h"
 #include "xfs_trans.h"
+#include "xfs_error.h"
 
 /*
  * Directory file type support functions
@@ -47,6 +48,7 @@ xfs_dir2_sf_getdents(
 {
        int                     i;              /* shortform entry number */
        struct xfs_inode        *dp = args->dp; /* incore directory inode */
+       struct xfs_mount        *mp = dp->i_mount;
        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 */
@@ -68,15 +70,15 @@ xfs_dir2_sf_getdents(
                return 0;
 
        /*
-        * Precalculate offsets for . and .. as we will always need them.
-        *
-        * XXX(hch): the second argument is sometimes 0 and sometimes
-        * geo->datablk
+        * Precalculate offsets for "." and ".." as we will always need them.
+        * This relies on the fact that directories always start with the
+        * entries for "." and "..".
         */
        dot_offset = xfs_dir2_db_off_to_dataptr(geo, geo->datablk,
-                                               dp->d_ops->data_dot_offset);
+                       geo->data_entry_offset);
        dotdot_offset = xfs_dir2_db_off_to_dataptr(geo, geo->datablk,
-                                               dp->d_ops->data_dotdot_offset);
+                       geo->data_entry_offset +
+                       xfs_dir2_data_entsize(mp, sizeof(".") - 1));
 
        /*
         * Put . entry unless we're starting past it.
@@ -91,7 +93,7 @@ xfs_dir2_sf_getdents(
         * Put .. entry unless we're starting past it.
         */
        if (ctx->pos <= dotdot_offset) {
-               ino = dp->d_ops->sf_get_parent_ino(sfp);
+               ino = xfs_dir2_sf_get_parent_ino(sfp);
                ctx->pos = dotdot_offset & 0x7fffffff;
                if (!dir_emit(ctx, "..", 2, ino, DT_DIR))
                        return 0;
@@ -108,17 +110,21 @@ xfs_dir2_sf_getdents(
                                xfs_dir2_sf_get_offset(sfep));
 
                if (ctx->pos > off) {
-                       sfep = dp->d_ops->sf_nextentry(sfp, sfep);
+                       sfep = xfs_dir2_sf_nextentry(mp, sfp, sfep);
                        continue;
                }
 
-               ino = dp->d_ops->sf_get_ino(sfp, sfep);
-               filetype = dp->d_ops->sf_get_ftype(sfep);
+               ino = xfs_dir2_sf_get_ino(mp, sfp, sfep);
+               filetype = xfs_dir2_sf_get_ftype(mp, sfep);
                ctx->pos = off & 0x7fffffff;
+               if (XFS_IS_CORRUPT(dp->i_mount,
+                                  !xfs_dir2_namecheck(sfep->name,
+                                                      sfep->namelen)))
+                       return -EFSCORRUPTED;
                if (!dir_emit(ctx, (char *)sfep->name, sfep->namelen, ino,
-                           xfs_dir3_get_dtype(dp->i_mount, filetype)))
+                           xfs_dir3_get_dtype(mp, filetype)))
                        return 0;
-               sfep = dp->d_ops->sf_nextentry(sfp, sfep);
+               sfep = xfs_dir2_sf_nextentry(mp, sfp, sfep);
        }
 
        ctx->pos = xfs_dir2_db_off_to_dataptr(geo, geo->datablk + 1, 0) &
@@ -135,17 +141,14 @@ xfs_dir2_block_getdents(
        struct dir_context      *ctx)
 {
        struct xfs_inode        *dp = args->dp; /* incore directory inode */
-       xfs_dir2_data_hdr_t     *hdr;           /* block header */
        struct xfs_buf          *bp;            /* buffer for block */
-       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 */
-       char                    *ptr;           /* current data entry */
        int                     wantoff;        /* starting block offset */
        xfs_off_t               cook;
        struct xfs_da_geometry  *geo = args->geo;
        int                     lock_mode;
+       unsigned int            offset;
+       unsigned int            end;
 
        /*
         * If the block number in the offset is out of range, we're done.
@@ -164,56 +167,55 @@ xfs_dir2_block_getdents(
         * We'll skip entries before this.
         */
        wantoff = xfs_dir2_dataptr_to_off(geo, ctx->pos);
-       hdr = bp->b_addr;
        xfs_dir3_data_check(dp, bp);
-       /*
-        * Set up values for the loop.
-        */
-       ptr = (char *)dp->d_ops->data_entry_p(hdr);
-       endptr = xfs_dir3_data_endp(geo, hdr);
 
        /*
         * Loop over the data portion of the block.
         * Each object is a real entry (dep) or an unused one (dup).
         */
-       while (ptr < endptr) {
+       offset = geo->data_entry_offset;
+       end = xfs_dir3_data_end_offset(geo, bp->b_addr);
+       while (offset < end) {
+               struct xfs_dir2_data_unused     *dup = bp->b_addr + offset;
+               struct xfs_dir2_data_entry      *dep = bp->b_addr + offset;
                uint8_t filetype;
 
-               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);
+                       offset += be16_to_cpu(dup->length);
                        continue;
                }
 
-               dep = (xfs_dir2_data_entry_t *)ptr;
-
                /*
                 * Bump pointer for the next iteration.
                 */
-               ptr += dp->d_ops->data_entsize(dep->namelen);
+               offset += xfs_dir2_data_entsize(dp->i_mount, dep->namelen);
+
                /*
                 * The entry is before the desired starting point, skip it.
                 */
-               if ((char *)dep - (char *)hdr < wantoff)
+               if (offset < wantoff)
                        continue;
 
-               cook = xfs_dir2_db_off_to_dataptr(geo, geo->datablk,
-                                           (char *)dep - (char *)hdr);
+               cook = xfs_dir2_db_off_to_dataptr(geo, geo->datablk, offset);
 
                ctx->pos = cook & 0x7fffffff;
-               filetype = dp->d_ops->data_get_ftype(dep);
+               filetype = xfs_dir2_data_get_ftype(dp->i_mount, dep);
                /*
                 * If it didn't fit, set the final offset to here & return.
                 */
+               if (XFS_IS_CORRUPT(dp->i_mount,
+                                  !xfs_dir2_namecheck(dep->name,
+                                                      dep->namelen))) {
+                       error = -EFSCORRUPTED;
+                       goto out_rele;
+               }
                if (!dir_emit(ctx, (char *)dep->name, dep->namelen,
                            be64_to_cpu(dep->inumber),
-                           xfs_dir3_get_dtype(dp->i_mount, filetype))) {
-                       xfs_trans_brelse(args->trans, bp);
-                       return 0;
-               }
+                           xfs_dir3_get_dtype(dp->i_mount, filetype)))
+                       goto out_rele;
        }
 
        /*
@@ -222,8 +224,9 @@ xfs_dir2_block_getdents(
         */
        ctx->pos = xfs_dir2_db_off_to_dataptr(geo, geo->datablk + 1, 0) &
                                                                0x7fffffff;
+out_rele:
        xfs_trans_brelse(args->trans, bp);
-       return 0;
+       return error;
 }
 
 /*
@@ -276,7 +279,7 @@ xfs_dir2_leaf_readbuf(
        new_off = xfs_dir2_da_to_byte(geo, map.br_startoff);
        if (new_off > *cur_off)
                *cur_off = new_off;
-       error = xfs_dir3_data_read(args->trans, dp, map.br_startoff, -1, &bp);
+       error = xfs_dir3_data_read(args->trans, dp, map.br_startoff, 0, &bp);
        if (error)
                goto out;
 
@@ -311,7 +314,8 @@ xfs_dir2_leaf_readbuf(
                                break;
                        }
                        if (next_ra > *ra_blk) {
-                               xfs_dir3_data_readahead(dp, next_ra, -2);
+                               xfs_dir3_data_readahead(dp, next_ra,
+                                                       XFS_DABUF_MAP_HOLE_OK);
                                *ra_blk = next_ra;
                        }
                        ra_want -= geo->fsbcount;
@@ -343,17 +347,17 @@ xfs_dir2_leaf_getdents(
        size_t                  bufsize)
 {
        struct xfs_inode        *dp = args->dp;
+       struct xfs_mount        *mp = dp->i_mount;
        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 */
-       char                    *ptr = NULL;    /* pointer to current data */
        struct xfs_da_geometry  *geo = args->geo;
        xfs_dablk_t             rablk = 0;      /* current readahead block */
        xfs_dir2_off_t          curoff;         /* current overall offset */
        int                     length;         /* temporary length value */
        int                     byteoff;        /* offset in current block */
        int                     lock_mode;
+       unsigned int            offset = 0;
        int                     error = 0;      /* error return value */
 
        /*
@@ -380,7 +384,7 @@ xfs_dir2_leaf_getdents(
                 * 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 + geo->blksize) {
+               if (!bp || offset >= geo->blksize) {
                        if (bp) {
                                xfs_trans_brelse(args->trans, bp);
                                bp = NULL;
@@ -393,36 +397,35 @@ xfs_dir2_leaf_getdents(
                        if (error || !bp)
                                break;
 
-                       hdr = bp->b_addr;
                        xfs_dir3_data_check(dp, bp);
                        /*
                         * Find our position in the block.
                         */
-                       ptr = (char *)dp->d_ops->data_entry_p(hdr);
+                       offset = geo->data_entry_offset;
                        byteoff = xfs_dir2_byte_to_off(geo, curoff);
                        /*
                         * Skip past the header.
                         */
                        if (byteoff == 0)
-                               curoff += dp->d_ops->data_entry_offset;
+                               curoff += geo->data_entry_offset;
                        /*
                         * Skip past entries until we reach our offset.
                         */
                        else {
-                               while ((char *)ptr - (char *)hdr < byteoff) {
-                                       dup = (xfs_dir2_data_unused_t *)ptr;
+                               while (offset < byteoff) {
+                                       dup = bp->b_addr + offset;
 
                                        if (be16_to_cpu(dup->freetag)
                                                  == XFS_DIR2_DATA_FREE_TAG) {
 
                                                length = be16_to_cpu(dup->length);
-                                               ptr += length;
+                                               offset += length;
                                                continue;
                                        }
-                                       dep = (xfs_dir2_data_entry_t *)ptr;
-                                       length =
-                                          dp->d_ops->data_entsize(dep->namelen);
-                                       ptr += length;
+                                       dep = bp->b_addr + offset;
+                                       length = xfs_dir2_data_entsize(mp,
+                                                       dep->namelen);
+                                       offset += length;
                                }
                                /*
                                 * Now set our real offset.
@@ -430,32 +433,38 @@ xfs_dir2_leaf_getdents(
                                curoff =
                                        xfs_dir2_db_off_to_byte(geo,
                                            xfs_dir2_byte_to_db(geo, curoff),
-                                           (char *)ptr - (char *)hdr);
-                               if (ptr >= (char *)hdr + geo->blksize) {
+                                           offset);
+                               if (offset >= geo->blksize)
                                        continue;
-                               }
                        }
                }
+
                /*
-                * We have a pointer to an entry.
-                * Is it a live one?
+                * We have a pointer to an entry.  Is it a live one?
                 */
-               dup = (xfs_dir2_data_unused_t *)ptr;
+               dup = bp->b_addr + offset;
+
                /*
                 * 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;
+                       offset += length;
                        curoff += length;
                        continue;
                }
 
-               dep = (xfs_dir2_data_entry_t *)ptr;
-               length = dp->d_ops->data_entsize(dep->namelen);
-               filetype = dp->d_ops->data_get_ftype(dep);
+               dep = bp->b_addr + offset;
+               length = xfs_dir2_data_entsize(mp, dep->namelen);
+               filetype = xfs_dir2_data_get_ftype(mp, dep);
 
                ctx->pos = xfs_dir2_byte_to_dataptr(curoff) & 0x7fffffff;
+               if (XFS_IS_CORRUPT(dp->i_mount,
+                                  !xfs_dir2_namecheck(dep->name,
+                                                      dep->namelen))) {
+                       error = -EFSCORRUPTED;
+                       break;
+               }
                if (!dir_emit(ctx, (char *)dep->name, dep->namelen,
                            be64_to_cpu(dep->inumber),
                            xfs_dir3_get_dtype(dp->i_mount, filetype)))
@@ -464,7 +473,7 @@ xfs_dir2_leaf_getdents(
                /*
                 * Advance to next entry in the block.
                 */
-               ptr += length;
+               offset += length;
                curoff += length;
                /* bufsize may have just been a guess; don't go negative */
                bufsize = bufsize > length ? bufsize - length : 0;