/* * Pointer to an entry's tag word. */ static __be16 * xfs_dir2_data_entry_tag_p( struct xfs_dir2_data_entry *dep) { return (__be16 *)((char *)dep + xfs_dir2_data_entsize(dep->namelen) - sizeof(__be16)); }
/* * Check consistency of shortform directory, assert if bad. */ static void xfs_dir2_sf_check( xfs_da_args_t *args) /* operation arguments */ { xfs_inode_t *dp; /* incore directory inode */ int i; /* entry number */ int i8count; /* number of big inode#s */ xfs_ino_t ino; /* entry inode number */ int offset; /* data offset */ xfs_dir2_sf_entry_t *sfep; /* shortform dir entry */ xfs_dir2_sf_hdr_t *sfp; /* shortform structure */ dp = args->dp; sfp = (xfs_dir2_sf_hdr_t *)dp->i_df.if_u1.if_data; offset = XFS_DIR2_DATA_FIRST_OFFSET; ino = xfs_dir2_sf_get_parent_ino(sfp); i8count = ino > XFS_DIR2_MAX_SHORT_INUM; for (i = 0, sfep = xfs_dir2_sf_firstentry(sfp); i < sfp->count; i++, sfep = xfs_dir2_sf_nextentry(sfp, sfep)) { ASSERT(xfs_dir2_sf_get_offset(sfep) >= offset); ino = xfs_dir2_sfe_get_ino(sfp, sfep); i8count += ino > XFS_DIR2_MAX_SHORT_INUM; offset = xfs_dir2_sf_get_offset(sfep) + xfs_dir2_data_entsize(sfep->namelen); } ASSERT(i8count == sfp->i8count); ASSERT(XFS_BIG_INUMS || i8count == 0); ASSERT((char *)sfep - (char *)sfp == dp->i_d.di_size); ASSERT(offset + (sfp->count + 2) * (uint)sizeof(xfs_dir2_leaf_entry_t) + (uint)sizeof(xfs_dir2_block_tail_t) <= dp->i_mount->m_dirblksize); }
/*ARGSUSED*/ static int /* pick result */ xfs_dir2_sf_addname_pick( xfs_da_args_t *args, /* operation arguments */ int objchange, /* inode # size changes */ xfs_dir2_sf_entry_t **sfepp, /* out(1): new entry ptr */ xfs_dir2_data_aoff_t *offsetp) /* out(1): new offset */ { xfs_inode_t *dp; /* incore directory inode */ int holefit; /* found hole it will fit in */ int i; /* entry number */ xfs_mount_t *mp; /* filesystem mount point */ xfs_dir2_data_aoff_t offset; /* data block offset */ xfs_dir2_sf_entry_t *sfep; /* shortform entry */ xfs_dir2_sf_hdr_t *sfp; /* shortform structure */ int size; /* entry's data size */ int used; /* data bytes used */ dp = args->dp; mp = dp->i_mount; sfp = (xfs_dir2_sf_hdr_t *)dp->i_df.if_u1.if_data; size = xfs_dir2_data_entsize(args->namelen); offset = XFS_DIR3_DATA_FIRST_OFFSET(mp); sfep = xfs_dir2_sf_firstentry(sfp); holefit = 0; /* * Loop over sf entries. * Keep track of data offset and whether we've seen a place * to insert the new entry. */ for (i = 0; i < sfp->count; i++) { if (!holefit) holefit = offset + size <= xfs_dir2_sf_get_offset(sfep); offset = xfs_dir2_sf_get_offset(sfep) + xfs_dir2_data_entsize(sfep->namelen); sfep = xfs_dir2_sf_nextentry(sfp, sfep); } /* * Calculate data bytes used excluding the new entry, if this * was a data block (block form directory). */ used = offset + (sfp->count + 3) * (uint)sizeof(xfs_dir2_leaf_entry_t) + (uint)sizeof(xfs_dir2_block_tail_t); /* * If it won't fit in a block form then we can't insert it, * we'll go back, convert to block, then try the insert and convert * to leaf. */ if (used + (holefit ? 0 : size) > mp->m_dirblksize) return 0; /* * If changing the inode number size, do it the hard way. */ #if XFS_BIG_INUMS if (objchange) { return 2; } #else ASSERT(objchange == 0); #endif /* * If it won't fit at the end then do it the hard way (use the hole). */ if (used + size > mp->m_dirblksize) return 2; /* * Do it the easy way. */ *sfepp = sfep; *offsetp = offset; return 1; }
/* ARGSUSED */ static void xfs_dir2_sf_addname_hard( xfs_da_args_t *args, /* operation arguments */ int objchange, /* changing inode number size */ int new_isize) /* new directory size */ { int add_datasize; /* data size need for new ent */ char *buf; /* buffer for old */ xfs_inode_t *dp; /* incore directory inode */ int eof; /* reached end of old dir */ int nbytes; /* temp for byte copies */ xfs_dir2_data_aoff_t new_offset; /* next offset value */ xfs_dir2_data_aoff_t offset; /* current offset value */ int old_isize; /* previous di_size */ xfs_dir2_sf_entry_t *oldsfep; /* entry in original dir */ xfs_dir2_sf_hdr_t *oldsfp; /* original shortform dir */ xfs_dir2_sf_entry_t *sfep; /* entry in new dir */ xfs_dir2_sf_hdr_t *sfp; /* new shortform dir */ /* * Copy the old directory to the stack buffer. */ dp = args->dp; sfp = (xfs_dir2_sf_hdr_t *)dp->i_df.if_u1.if_data; old_isize = (int)dp->i_d.di_size; buf = kmem_alloc(old_isize, KM_SLEEP); oldsfp = (xfs_dir2_sf_hdr_t *)buf; memcpy(oldsfp, sfp, old_isize); /* * Loop over the old directory finding the place we're going * to insert the new entry. * If it's going to end up at the end then oldsfep will point there. */ for (offset = XFS_DIR3_DATA_FIRST_OFFSET(dp->i_mount), oldsfep = xfs_dir2_sf_firstentry(oldsfp), add_datasize = xfs_dir2_data_entsize(args->namelen), eof = (char *)oldsfep == &buf[old_isize]; !eof; offset = new_offset + xfs_dir2_data_entsize(oldsfep->namelen), oldsfep = xfs_dir2_sf_nextentry(oldsfp, oldsfep), eof = (char *)oldsfep == &buf[old_isize]) { new_offset = xfs_dir2_sf_get_offset(oldsfep); if (offset + add_datasize <= new_offset) break; } /* * Get rid of the old directory, then allocate space for * the new one. We do this so xfs_idata_realloc won't copy * the data. */ xfs_idata_realloc(dp, -old_isize, XFS_DATA_FORK); xfs_idata_realloc(dp, new_isize, XFS_DATA_FORK); /* * Reset the pointer since the buffer was reallocated. */ sfp = (xfs_dir2_sf_hdr_t *)dp->i_df.if_u1.if_data; /* * Copy the first part of the directory, including the header. */ nbytes = (int)((char *)oldsfep - (char *)oldsfp); memcpy(sfp, oldsfp, nbytes); sfep = (xfs_dir2_sf_entry_t *)((char *)sfp + nbytes); /* * Fill in the new entry, and update the header counts. */ sfep->namelen = args->namelen; xfs_dir2_sf_put_offset(sfep, offset); memcpy(sfep->name, args->name, sfep->namelen); xfs_dir2_sfe_put_ino(sfp, sfep, args->inumber); sfp->count++; #if XFS_BIG_INUMS if (args->inumber > XFS_DIR2_MAX_SHORT_INUM && !objchange) sfp->i8count++; #endif /* * If there's more left to copy, do that. */ if (!eof) { sfep = xfs_dir2_sf_nextentry(sfp, sfep); memcpy(sfep, oldsfep, old_isize - nbytes); } kmem_free(buf); dp->i_d.di_size = new_isize; xfs_dir2_sf_check(args); }
/* * Convert a block format directory to shortform. * Caller has already checked that it will fit, and built us a header. */ int /* error */ xfs_dir2_block_to_sf( xfs_da_args_t *args, /* operation arguments */ struct xfs_buf *bp, int size, /* shortform directory size */ xfs_dir2_sf_hdr_t *sfhp) /* shortform directory hdr */ { xfs_dir2_data_hdr_t *hdr; /* block header */ xfs_dir2_block_tail_t *btp; /* block tail pointer */ xfs_dir2_data_entry_t *dep; /* data entry pointer */ xfs_inode_t *dp; /* incore directory inode */ xfs_dir2_data_unused_t *dup; /* unused data pointer */ char *endptr; /* end of data entries */ int error; /* error return value */ int logflags; /* inode logging flags */ xfs_mount_t *mp; /* filesystem mount point */ char *ptr; /* current data pointer */ xfs_dir2_sf_entry_t *sfep; /* shortform entry */ xfs_dir2_sf_hdr_t *sfp; /* shortform directory header */ trace_xfs_dir2_block_to_sf(args); dp = args->dp; mp = dp->i_mount; /* * Make a copy of the block data, so we can shrink the inode * and add local data. */ hdr = kmem_alloc(mp->m_dirblksize, KM_SLEEP); memcpy(hdr, bp->b_addr, mp->m_dirblksize); logflags = XFS_ILOG_CORE; if ((error = xfs_dir2_shrink_inode(args, mp->m_dirdatablk, bp))) { ASSERT(error != ENOSPC); goto out; } /* * The buffer is now unconditionally gone, whether * xfs_dir2_shrink_inode worked or not. * * Convert the inode to local format. */ dp->i_df.if_flags &= ~XFS_IFEXTENTS; dp->i_df.if_flags |= XFS_IFINLINE; dp->i_d.di_format = XFS_DINODE_FMT_LOCAL; ASSERT(dp->i_df.if_bytes == 0); xfs_idata_realloc(dp, size, XFS_DATA_FORK); logflags |= XFS_ILOG_DDATA; /* * Copy the header into the newly allocate local space. */ sfp = (xfs_dir2_sf_hdr_t *)dp->i_df.if_u1.if_data; memcpy(sfp, sfhp, xfs_dir2_sf_hdr_size(sfhp->i8count)); dp->i_d.di_size = size; /* * Set up to loop over the block's entries. */ btp = xfs_dir2_block_tail_p(mp, hdr); ptr = (char *)xfs_dir3_data_entry_p(hdr); endptr = (char *)xfs_dir2_block_leaf_p(btp); sfep = xfs_dir2_sf_firstentry(sfp); /* * Loop over the active and unused entries. * Stop when we reach the leaf/tail portion of the block. */ while (ptr < endptr) { /* * If it's unused, just skip over it. */ dup = (xfs_dir2_data_unused_t *)ptr; 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; /* * Skip . */ if (dep->namelen == 1 && dep->name[0] == '.') ASSERT(be64_to_cpu(dep->inumber) == dp->i_ino); /* * Skip .., but make sure the inode number is right. */ else if (dep->namelen == 2 && dep->name[0] == '.' && dep->name[1] == '.') ASSERT(be64_to_cpu(dep->inumber) == xfs_dir2_sf_get_parent_ino(sfp)); /* * Normal entry, copy it into shortform. */ else { sfep->namelen = dep->namelen; xfs_dir2_sf_put_offset(sfep, (xfs_dir2_data_aoff_t) ((char *)dep - (char *)hdr)); memcpy(sfep->name, dep->name, dep->namelen); xfs_dir2_sfe_put_ino(sfp, sfep, be64_to_cpu(dep->inumber)); sfep = xfs_dir2_sf_nextentry(sfp, sfep); } ptr += xfs_dir2_data_entsize(dep->namelen); } ASSERT((char *)sfep - (char *)sfp == size); xfs_dir2_sf_check(args); out: xfs_trans_log_inode(args->trans, dp, logflags); kmem_free(hdr); return error; }
/* * Readdir for block directories. */ int /* error */ xfs_dir2_block_getdents( xfs_inode_t *dp, /* incore inode */ void *dirent, xfs_off_t *offset, filldir_t filldir) { 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, *offset) > mp->m_dirdatablk) return 0; error = xfs_dir2_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, *offset); hdr = bp->b_addr; xfs_dir2_data_check(dp, bp); /* * Set up values for the loop. */ btp = xfs_dir2_block_tail_p(mp, hdr); ptr = (char *)(hdr + 1); 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); /* * If it didn't fit, set the final offset to here & return. */ if (filldir(dirent, (char *)dep->name, dep->namelen, cook & 0x7fffffff, be64_to_cpu(dep->inumber), DT_UNKNOWN)) { *offset = cook & 0x7fffffff; xfs_trans_brelse(NULL, bp); return 0; } } /* * Reached the end of the block. * Set the offset to a non-existent block 1 and return. */ *offset = xfs_dir2_db_off_to_dataptr(mp, mp->m_dirdatablk + 1, 0) & 0x7fffffff; xfs_trans_brelse(NULL, bp); return 0; }
static void xfs_dir2_sf_addname_hard( xfs_da_args_t *args, /* */ int objchange, /* */ int new_isize) /* */ { int add_datasize; /* */ char *buf; /* */ xfs_inode_t *dp; /* */ int eof; /* */ int nbytes; /* */ xfs_dir2_data_aoff_t new_offset; /* */ xfs_dir2_data_aoff_t offset; /* */ int old_isize; /* */ xfs_dir2_sf_entry_t *oldsfep; /* */ xfs_dir2_sf_hdr_t *oldsfp; /* */ xfs_dir2_sf_entry_t *sfep; /* */ xfs_dir2_sf_hdr_t *sfp; /* */ /* */ dp = args->dp; sfp = (xfs_dir2_sf_hdr_t *)dp->i_df.if_u1.if_data; old_isize = (int)dp->i_d.di_size; buf = kmem_alloc(old_isize, KM_SLEEP); oldsfp = (xfs_dir2_sf_hdr_t *)buf; memcpy(oldsfp, sfp, old_isize); /* */ for (offset = XFS_DIR2_DATA_FIRST_OFFSET, oldsfep = xfs_dir2_sf_firstentry(oldsfp), add_datasize = xfs_dir2_data_entsize(args->namelen), eof = (char *)oldsfep == &buf[old_isize]; !eof; offset = new_offset + xfs_dir2_data_entsize(oldsfep->namelen), oldsfep = xfs_dir2_sf_nextentry(oldsfp, oldsfep), eof = (char *)oldsfep == &buf[old_isize]) { new_offset = xfs_dir2_sf_get_offset(oldsfep); if (offset + add_datasize <= new_offset) break; } /* */ xfs_idata_realloc(dp, -old_isize, XFS_DATA_FORK); xfs_idata_realloc(dp, new_isize, XFS_DATA_FORK); /* */ sfp = (xfs_dir2_sf_hdr_t *)dp->i_df.if_u1.if_data; /* */ nbytes = (int)((char *)oldsfep - (char *)oldsfp); memcpy(sfp, oldsfp, nbytes); sfep = (xfs_dir2_sf_entry_t *)((char *)sfp + nbytes); /* */ sfep->namelen = args->namelen; xfs_dir2_sf_put_offset(sfep, offset); memcpy(sfep->name, args->name, sfep->namelen); xfs_dir2_sfe_put_ino(sfp, sfep, args->inumber); sfp->count++; #if XFS_BIG_INUMS if (args->inumber > XFS_DIR2_MAX_SHORT_INUM && !objchange) sfp->i8count++; #endif /* */ if (!eof) { sfep = xfs_dir2_sf_nextentry(sfp, sfep); memcpy(sfep, oldsfep, old_isize - nbytes); } kmem_free(buf); dp->i_d.di_size = new_isize; xfs_dir2_sf_check(args); }
static int /* */ xfs_dir2_sf_addname_pick( xfs_da_args_t *args, /* */ int objchange, /* */ xfs_dir2_sf_entry_t **sfepp, /* */ xfs_dir2_data_aoff_t *offsetp) /* */ { xfs_inode_t *dp; /* */ int holefit; /* */ int i; /* */ xfs_mount_t *mp; /* */ xfs_dir2_data_aoff_t offset; /* */ xfs_dir2_sf_entry_t *sfep; /* */ xfs_dir2_sf_hdr_t *sfp; /* */ int size; /* */ int used; /* */ dp = args->dp; mp = dp->i_mount; sfp = (xfs_dir2_sf_hdr_t *)dp->i_df.if_u1.if_data; size = xfs_dir2_data_entsize(args->namelen); offset = XFS_DIR2_DATA_FIRST_OFFSET; sfep = xfs_dir2_sf_firstentry(sfp); holefit = 0; /* */ for (i = 0; i < sfp->count; i++) { if (!holefit) holefit = offset + size <= xfs_dir2_sf_get_offset(sfep); offset = xfs_dir2_sf_get_offset(sfep) + xfs_dir2_data_entsize(sfep->namelen); sfep = xfs_dir2_sf_nextentry(sfp, sfep); } /* */ used = offset + (sfp->count + 3) * (uint)sizeof(xfs_dir2_leaf_entry_t) + (uint)sizeof(xfs_dir2_block_tail_t); /* */ if (used + (holefit ? 0 : size) > mp->m_dirblksize) return 0; /* */ #if XFS_BIG_INUMS if (objchange) { return 2; } #else ASSERT(objchange == 0); #endif /* */ if (used + size > mp->m_dirblksize) return 2; /* */ *sfepp = sfep; *offsetp = offset; return 1; }
int /* */ xfs_dir2_block_to_sf( xfs_da_args_t *args, /* */ xfs_dabuf_t *bp, /* */ int size, /* */ xfs_dir2_sf_hdr_t *sfhp) /* */ { xfs_dir2_data_hdr_t *hdr; /* */ xfs_dir2_block_tail_t *btp; /* */ xfs_dir2_data_entry_t *dep; /* */ xfs_inode_t *dp; /* */ xfs_dir2_data_unused_t *dup; /* */ char *endptr; /* */ int error; /* */ int logflags; /* */ xfs_mount_t *mp; /* */ char *ptr; /* */ xfs_dir2_sf_entry_t *sfep; /* */ xfs_dir2_sf_hdr_t *sfp; /* */ trace_xfs_dir2_block_to_sf(args); dp = args->dp; mp = dp->i_mount; /* */ hdr = kmem_alloc(mp->m_dirblksize, KM_SLEEP); memcpy(hdr, bp->data, mp->m_dirblksize); logflags = XFS_ILOG_CORE; if ((error = xfs_dir2_shrink_inode(args, mp->m_dirdatablk, bp))) { ASSERT(error != ENOSPC); goto out; } /* */ dp->i_df.if_flags &= ~XFS_IFEXTENTS; dp->i_df.if_flags |= XFS_IFINLINE; dp->i_d.di_format = XFS_DINODE_FMT_LOCAL; ASSERT(dp->i_df.if_bytes == 0); xfs_idata_realloc(dp, size, XFS_DATA_FORK); logflags |= XFS_ILOG_DDATA; /* */ sfp = (xfs_dir2_sf_hdr_t *)dp->i_df.if_u1.if_data; memcpy(sfp, sfhp, xfs_dir2_sf_hdr_size(sfhp->i8count)); dp->i_d.di_size = size; /* */ btp = xfs_dir2_block_tail_p(mp, hdr); ptr = (char *)(hdr + 1); endptr = (char *)xfs_dir2_block_leaf_p(btp); sfep = xfs_dir2_sf_firstentry(sfp); /* */ while (ptr < endptr) { /* */ dup = (xfs_dir2_data_unused_t *)ptr; 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; /* */ if (dep->namelen == 1 && dep->name[0] == '.') ASSERT(be64_to_cpu(dep->inumber) == dp->i_ino); /* */ else if (dep->namelen == 2 && dep->name[0] == '.' && dep->name[1] == '.') ASSERT(be64_to_cpu(dep->inumber) == xfs_dir2_sf_get_parent_ino(sfp)); /* */ else { sfep->namelen = dep->namelen; xfs_dir2_sf_put_offset(sfep, (xfs_dir2_data_aoff_t) ((char *)dep - (char *)hdr)); memcpy(sfep->name, dep->name, dep->namelen); xfs_dir2_sfe_put_ino(sfp, sfep, be64_to_cpu(dep->inumber)); sfep = xfs_dir2_sf_nextentry(sfp, sfep); } ptr += xfs_dir2_data_entsize(dep->namelen); } ASSERT((char *)sfep - (char *)sfp == size); xfs_dir2_sf_check(args); out: xfs_trans_log_inode(args->trans, dp, logflags); kmem_free(hdr); 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 */ void *dirent, size_t bufsize, xfs_off_t *offset, filldir_t filldir) { xfs_dabuf_t *bp; /* data block buffer */ int byteoff; /* offset in current block */ xfs_dir2_db_t curdb; /* db for current block */ xfs_dir2_off_t curoff; /* current overall offset */ xfs_dir2_data_t *data; /* data block structure */ xfs_dir2_data_entry_t *dep; /* data entry */ xfs_dir2_data_unused_t *dup; /* unused entry */ int error = 0; /* error return value */ int i; /* temporary loop index */ int j; /* temporary loop index */ int length; /* temporary length value */ xfs_bmbt_irec_t *map; /* map vector for blocks */ 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 */ xfs_mount_t *mp; /* filesystem mount point */ xfs_dir2_off_t newoff; /* new curoff after new blk */ int nmap; /* mappings to ask xfs_bmapi */ char *ptr = NULL; /* pointer to current data */ 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 */ /* * If the offset is at or past the largest allowed value, * give up right away. */ if (*offset >= 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. */ map_size = howmany(bufsize + mp->m_dirblksize, mp->m_sb.sb_blocksize); map = kmem_alloc(map_size * sizeof(*map), KM_SLEEP); map_valid = ra_index = ra_offset = ra_current = map_blocks = 0; bp = NULL; /* * Inside the loop we keep the main offset value as a byte offset * in the directory file. */ curoff = xfs_dir2_dataptr_to_byte(mp, *offset); /* * Force this conversion through db so we truncate the offset * down to get the start of the data block. */ 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->data + mp->m_dirblksize) { /* * If we have a buffer, we need to release it and * take it out of the mapping. */ if (bp) { xfs_da_brelse(NULL, bp); bp = NULL; 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((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 && --map_valid) memmove(&map[0], &map[1], sizeof(map[0]) * map_valid); i -= j; } } /* * Recalculate the readahead blocks wanted. */ ra_want = howmany(bufsize + mp->m_dirblksize, mp->m_sb.sb_blocksize) - 1; ASSERT(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 + ra_want > map_blocks && 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. */ nmap = map_size - map_valid; error = xfs_bmapi(NULL, dp, map_off, xfs_dir2_byte_to_da(mp, XFS_DIR2_LEAF_OFFSET) - map_off, XFS_BMAPI_METADATA, NULL, 0, &map[map_valid], &nmap, NULL, NULL); /* * 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) break; /* 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 (nmap == map_size - map_valid) map_off = map[map_valid + nmap - 1].br_startoff + map[map_valid + nmap - 1].br_blockcount; else 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 = map_valid; i < map_valid + nmap; ) { if (map[i].br_startblock == HOLESTARTBLOCK) { nmap--; length = map_valid + nmap - i; if (length) memmove(&map[i], &map[i + 1], sizeof(map[i]) * length); } else { map_blocks += map[i].br_blockcount; i++; } } map_valid += nmap; } /* * No valid mappings, so no more data blocks. */ if (!map_valid) { curoff = xfs_dir2_da_to_byte(mp, map_off); break; } /* * Read the directory block starting at the first * mapping. */ curdb = xfs_dir2_da_to_db(mp, map->br_startoff); error = xfs_da_read_buf(NULL, dp, map->br_startoff, map->br_blockcount >= mp->m_dirblkfsbs ? XFS_FSB_TO_DADDR(mp, map->br_startblock) : -1, &bp, XFS_DATA_FORK); /* * Should just skip over the data block instead * of giving up. */ if (error) break; /* XXX */ /* * Adjust the current amount of read-ahead: we just * read a block that was previously ra. */ if (ra_current) ra_current -= mp->m_dirblkfsbs; /* * Do we need more readahead? */ for (ra_index = ra_offset = i = 0; ra_want > ra_current && i < map_blocks; i += mp->m_dirblkfsbs) { ASSERT(ra_index < map_valid); /* * Read-ahead a contiguous directory block. */ if (i > ra_current && map[ra_index].br_blockcount >= mp->m_dirblkfsbs) { //TODO: make sure this is right, should readahead be //replaced by readbuf? //xfs_buf_readahead(mp->m_ddev_targp, // XFS_FSB_TO_DADDR(mp, // map[ra_index].br_startblock + // ra_offset), // (int)BTOBB(mp->m_dirblksize)); //TODO: figure out flags, flags =0 //libxfs_readbuf(mp->m_dev, // XFS_FSB_TO_DADDR(mp, // map[ra_index].br_startblock + // ra_offset), // (int)BTOBB(mp->m_dirblksize), 0); 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 > ra_current) { (void)xfs_da_reada_buf(NULL, dp, map[ra_index].br_startoff + ra_offset, XFS_DATA_FORK); 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(mp->m_dirblkfsbs, (int)(map[ra_index].br_blockcount - ra_offset)); j += length; ra_offset += length; /* * Advance to the next mapping if * this one is used up. */ if (ra_offset == map[ra_index].br_blockcount) { ra_offset = 0; ra_index++; } } } /* * Having done a read, we need to set a new offset. */ newoff = xfs_dir2_db_off_to_byte(mp, 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) == curdb); data = bp->data; xfs_dir2_data_check(dp, bp); /* * Find our position in the block. */ ptr = (char *)&data->u; byteoff = xfs_dir2_byte_to_off(mp, curoff); /* * Skip past the header. */ if (byteoff == 0) curoff += (uint)sizeof(data->hdr); /* * Skip past entries until we reach our offset. */ else { while ((char *)ptr - (char *)data < 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 *)data); if (ptr >= (char *)data + 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); if (filldir(dirent, (char *)dep->name, dep->namelen, xfs_dir2_byte_to_dataptr(mp, curoff) & 0x7fffffff, 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)) *offset = XFS_DIR2_MAX_DATAPTR & 0x7fffffff; else *offset = xfs_dir2_byte_to_dataptr(mp, curoff) & 0x7fffffff; kmem_free(map); if (bp) xfs_da_brelse(NULL, bp); return error; }
/* * Remove an entry from a block format directory. * If that makes the block small enough to fit in shortform, transform it. */ int /* error */ xfs_dir2_block_removename( xfs_da_args_t *args) /* directory operation args */ { xfs_dir2_block_t *block; /* block structure */ xfs_dir2_leaf_entry_t *blp; /* block leaf pointer */ xfs_dabuf_t *bp; /* block buffer */ xfs_dir2_block_tail_t *btp; /* block tail */ xfs_dir2_data_entry_t *dep; /* block data entry */ xfs_inode_t *dp; /* incore inode */ int ent; /* block leaf entry index */ int error; /* error return value */ xfs_mount_t *mp; /* filesystem mount point */ int needlog; /* need to log block header */ int needscan; /* need to fixup bestfree */ xfs_dir2_sf_hdr_t sfh; /* shortform header */ int size; /* shortform size */ xfs_trans_t *tp; /* transaction pointer */ trace_xfs_dir2_block_removename(args); /* * Look up the entry in the block. Gets the buffer and entry index. * It will always be there, the vnodeops level does a lookup first. */ if ((error = xfs_dir2_block_lookup_int(args, &bp, &ent))) { return error; } dp = args->dp; tp = args->trans; mp = dp->i_mount; block = bp->data; btp = xfs_dir2_block_tail_p(mp, block); blp = xfs_dir2_block_leaf_p(btp); /* * Point to the data entry using the leaf entry. */ dep = (xfs_dir2_data_entry_t *) ((char *)block + xfs_dir2_dataptr_to_off(mp, be32_to_cpu(blp[ent].address))); /* * Mark the data entry's space free. */ needlog = needscan = 0; xfs_dir2_data_make_free(tp, bp, (xfs_dir2_data_aoff_t)((char *)dep - (char *)block), xfs_dir2_data_entsize(dep->namelen), &needlog, &needscan); /* * Fix up the block tail. */ be32_add_cpu(&btp->stale, 1); xfs_dir2_block_log_tail(tp, bp); /* * Remove the leaf entry by marking it stale. */ blp[ent].address = cpu_to_be32(XFS_DIR2_NULL_DATAPTR); xfs_dir2_block_log_leaf(tp, bp, ent, ent); /* * Fix up bestfree, log the header if necessary. */ if (needscan) xfs_dir2_data_freescan(mp, (xfs_dir2_data_t *)block, &needlog); if (needlog) xfs_dir2_data_log_header(tp, bp); xfs_dir2_data_check(dp, bp); /* * See if the size as a shortform is good enough. */ if ((size = xfs_dir2_block_sfsize(dp, block, &sfh)) > XFS_IFORK_DSIZE(dp)) { xfs_da_buf_done(bp); return 0; } /* * If it works, do the conversion. */ return xfs_dir2_block_to_sf(args, bp, size, &sfh); }
/* * Add an entry to a block directory. */ int /* error */ xfs_dir2_block_addname( xfs_da_args_t *args) /* directory op arguments */ { xfs_dir2_data_free_t *bf; /* bestfree table in block */ xfs_dir2_block_t *block; /* directory block structure */ xfs_dir2_leaf_entry_t *blp; /* block leaf entries */ xfs_dabuf_t *bp; /* buffer for block */ xfs_dir2_block_tail_t *btp; /* block tail */ int compact; /* need to compact leaf ents */ xfs_dir2_data_entry_t *dep; /* block data entry */ xfs_inode_t *dp; /* directory inode */ xfs_dir2_data_unused_t *dup; /* block unused entry */ int error; /* error return value */ xfs_dir2_data_unused_t *enddup=NULL; /* unused at end of data */ xfs_dahash_t hash; /* hash value of found entry */ int high; /* high index for binary srch */ int highstale; /* high stale index */ int lfloghigh=0; /* last final leaf to log */ int lfloglow=0; /* first final leaf to log */ int len; /* length of the new entry */ int low; /* low index for binary srch */ int lowstale; /* low stale index */ int mid=0; /* midpoint for binary srch */ xfs_mount_t *mp; /* filesystem mount point */ int needlog; /* need to log header */ int needscan; /* need to rescan freespace */ __be16 *tagp; /* pointer to tag value */ xfs_trans_t *tp; /* transaction structure */ trace_xfs_dir2_block_addname(args); dp = args->dp; tp = args->trans; mp = dp->i_mount; /* * Read the (one and only) directory block into dabuf bp. */ if ((error = xfs_da_read_buf(tp, dp, mp->m_dirdatablk, -1, &bp, XFS_DATA_FORK))) { return error; } ASSERT(bp != NULL); block = bp->data; /* * Check the magic number, corrupted if wrong. */ if (unlikely(be32_to_cpu(block->hdr.magic) != XFS_DIR2_BLOCK_MAGIC)) { XFS_CORRUPTION_ERROR("xfs_dir2_block_addname", XFS_ERRLEVEL_LOW, mp, block); xfs_da_brelse(tp, bp); return XFS_ERROR(EFSCORRUPTED); } len = xfs_dir2_data_entsize(args->namelen); /* * Set up pointers to parts of the block. */ bf = block->hdr.bestfree; btp = xfs_dir2_block_tail_p(mp, block); blp = xfs_dir2_block_leaf_p(btp); /* * No stale entries? Need space for entry and new leaf. */ if (!btp->stale) { /* * Tag just before the first leaf entry. */ tagp = (__be16 *)blp - 1; /* * Data object just before the first leaf entry. */ enddup = (xfs_dir2_data_unused_t *)((char *)block + be16_to_cpu(*tagp)); /* * If it's not free then can't do this add without cleaning up: * the space before the first leaf entry needs to be free so it * can be expanded to hold the pointer to the new entry. */ if (be16_to_cpu(enddup->freetag) != XFS_DIR2_DATA_FREE_TAG) dup = enddup = NULL; /* * Check out the biggest freespace and see if it's the same one. */ else { dup = (xfs_dir2_data_unused_t *) ((char *)block + be16_to_cpu(bf[0].offset)); if (dup == enddup) { /* * It is the biggest freespace, is it too small * to hold the new leaf too? */ if (be16_to_cpu(dup->length) < len + (uint)sizeof(*blp)) { /* * Yes, we use the second-largest * entry instead if it works. */ if (be16_to_cpu(bf[1].length) >= len) dup = (xfs_dir2_data_unused_t *) ((char *)block + be16_to_cpu(bf[1].offset)); else dup = NULL; } } else { /* * Not the same free entry, * just check its length. */ if (be16_to_cpu(dup->length) < len) { dup = NULL; } } } compact = 0; } /* * If there are stale entries we'll use one for the leaf. * Is the biggest entry enough to avoid compaction? */ else if (be16_to_cpu(bf[0].length) >= len) { dup = (xfs_dir2_data_unused_t *) ((char *)block + be16_to_cpu(bf[0].offset)); compact = 0; } /* * Will need to compact to make this work. */ else { /* * Tag just before the first leaf entry. */ tagp = (__be16 *)blp - 1; /* * Data object just before the first leaf entry. */ dup = (xfs_dir2_data_unused_t *)((char *)block + be16_to_cpu(*tagp)); /* * If it's not free then the data will go where the * leaf data starts now, if it works at all. */ if (be16_to_cpu(dup->freetag) == XFS_DIR2_DATA_FREE_TAG) { if (be16_to_cpu(dup->length) + (be32_to_cpu(btp->stale) - 1) * (uint)sizeof(*blp) < len) dup = NULL; } else if ((be32_to_cpu(btp->stale) - 1) * (uint)sizeof(*blp) < len) dup = NULL; else dup = (xfs_dir2_data_unused_t *)blp; compact = 1; } /* * If this isn't a real add, we're done with the buffer. */ if (args->op_flags & XFS_DA_OP_JUSTCHECK) xfs_da_brelse(tp, bp); /* * If we don't have space for the new entry & leaf ... */ if (!dup) { /* * Not trying to actually do anything, or don't have * a space reservation: return no-space. */ if ((args->op_flags & XFS_DA_OP_JUSTCHECK) || args->total == 0) return XFS_ERROR(ENOSPC); /* * Convert to the next larger format. * Then add the new entry in that format. */ error = xfs_dir2_block_to_leaf(args, bp); xfs_da_buf_done(bp); if (error) return error; return xfs_dir2_leaf_addname(args); } /* * Just checking, and it would work, so say so. */ if (args->op_flags & XFS_DA_OP_JUSTCHECK) return 0; needlog = needscan = 0; /* * If need to compact the leaf entries, do it now. * Leave the highest-numbered stale entry stale. * XXX should be the one closest to mid but mid is not yet computed. */ if (compact) { int fromidx; /* source leaf index */ int toidx; /* target leaf index */ for (fromidx = toidx = be32_to_cpu(btp->count) - 1, highstale = lfloghigh = -1; fromidx >= 0; fromidx--) { if (be32_to_cpu(blp[fromidx].address) == XFS_DIR2_NULL_DATAPTR) { if (highstale == -1) highstale = toidx; else { if (lfloghigh == -1) lfloghigh = toidx; continue; } } if (fromidx < toidx) blp[toidx] = blp[fromidx]; toidx--; } lfloglow = toidx + 1 - (be32_to_cpu(btp->stale) - 1); lfloghigh -= be32_to_cpu(btp->stale) - 1; be32_add_cpu(&btp->count, -(be32_to_cpu(btp->stale) - 1)); xfs_dir2_data_make_free(tp, bp, (xfs_dir2_data_aoff_t)((char *)blp - (char *)block), (xfs_dir2_data_aoff_t)((be32_to_cpu(btp->stale) - 1) * sizeof(*blp)), &needlog, &needscan); blp += be32_to_cpu(btp->stale) - 1; btp->stale = cpu_to_be32(1); /* * If we now need to rebuild the bestfree map, do so. * This needs to happen before the next call to use_free. */ if (needscan) { xfs_dir2_data_freescan(mp, (xfs_dir2_data_t *)block, &needlog); needscan = 0; } } /* * Set leaf logging boundaries to impossible state. * For the no-stale case they're set explicitly. */ else if (btp->stale) { lfloglow = be32_to_cpu(btp->count); lfloghigh = -1; } /* * Find the slot that's first lower than our hash value, -1 if none. */ for (low = 0, high = be32_to_cpu(btp->count) - 1; low <= high; ) { mid = (low + high) >> 1; if ((hash = be32_to_cpu(blp[mid].hashval)) == args->hashval) break; if (hash < args->hashval) low = mid + 1; else high = mid - 1; } while (mid >= 0 && be32_to_cpu(blp[mid].hashval) >= args->hashval) { mid--; } /* * No stale entries, will use enddup space to hold new leaf. */ if (!btp->stale) { /* * Mark the space needed for the new leaf entry, now in use. */ xfs_dir2_data_use_free(tp, bp, enddup, (xfs_dir2_data_aoff_t) ((char *)enddup - (char *)block + be16_to_cpu(enddup->length) - sizeof(*blp)), (xfs_dir2_data_aoff_t)sizeof(*blp), &needlog, &needscan); /* * Update the tail (entry count). */ be32_add_cpu(&btp->count, 1); /* * If we now need to rebuild the bestfree map, do so. * This needs to happen before the next call to use_free. */ if (needscan) { xfs_dir2_data_freescan(mp, (xfs_dir2_data_t *)block, &needlog); needscan = 0; } /* * Adjust pointer to the first leaf entry, we're about to move * the table up one to open up space for the new leaf entry. * Then adjust our index to match. */ blp--; mid++; if (mid) memmove(blp, &blp[1], mid * sizeof(*blp)); lfloglow = 0; lfloghigh = mid; } /* * Use a stale leaf for our new entry. */ else { for (lowstale = mid; lowstale >= 0 && be32_to_cpu(blp[lowstale].address) != XFS_DIR2_NULL_DATAPTR; lowstale--) continue; for (highstale = mid + 1; highstale < be32_to_cpu(btp->count) && be32_to_cpu(blp[highstale].address) != XFS_DIR2_NULL_DATAPTR && (lowstale < 0 || mid - lowstale > highstale - mid); highstale++) continue; /* * Move entries toward the low-numbered stale entry. */ if (lowstale >= 0 && (highstale == be32_to_cpu(btp->count) || mid - lowstale <= highstale - mid)) { if (mid - lowstale) memmove(&blp[lowstale], &blp[lowstale + 1], (mid - lowstale) * sizeof(*blp)); lfloglow = MIN(lowstale, lfloglow); lfloghigh = MAX(mid, lfloghigh); } /* * Move entries toward the high-numbered stale entry. */ else { ASSERT(highstale < be32_to_cpu(btp->count)); mid++; if (highstale - mid) memmove(&blp[mid + 1], &blp[mid], (highstale - mid) * sizeof(*blp)); lfloglow = MIN(mid, lfloglow); lfloghigh = MAX(highstale, lfloghigh); } be32_add_cpu(&btp->stale, -1); } /* * Point to the new data entry. */ dep = (xfs_dir2_data_entry_t *)dup; /* * Fill in the leaf entry. */ blp[mid].hashval = cpu_to_be32(args->hashval); blp[mid].address = cpu_to_be32(xfs_dir2_byte_to_dataptr(mp, (char *)dep - (char *)block)); xfs_dir2_block_log_leaf(tp, bp, lfloglow, lfloghigh); /* * Mark space for the data entry used. */ xfs_dir2_data_use_free(tp, bp, dup, (xfs_dir2_data_aoff_t)((char *)dup - (char *)block), (xfs_dir2_data_aoff_t)len, &needlog, &needscan); /* * Create the new data entry. */ dep->inumber = cpu_to_be64(args->inumber); dep->namelen = args->namelen; memcpy(dep->name, args->name, args->namelen); tagp = xfs_dir2_data_entry_tag_p(dep); *tagp = cpu_to_be16((char *)dep - (char *)block); /* * Clean up the bestfree array and log the header, tail, and entry. */ if (needscan) xfs_dir2_data_freescan(mp, (xfs_dir2_data_t *)block, &needlog); if (needlog) xfs_dir2_data_log_header(tp, bp); xfs_dir2_block_log_tail(tp, bp); xfs_dir2_data_log_entry(tp, bp, dep); xfs_dir2_data_check(dp, bp); xfs_da_buf_done(bp); return 0; }
/* * Readdir for block directories. */ int /* error */ xfs_dir2_block_getdents( xfs_inode_t *dp, /* incore inode */ void *dirent, xfs_off_t *offset, filldir_t filldir) { xfs_dir2_block_t *block; /* directory block structure */ xfs_dabuf_t *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_ino_t ino; 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, *offset) > mp->m_dirdatablk) { return 0; } /* * Can't read the block, give up, else get dabuf in bp. */ error = xfs_da_read_buf(NULL, dp, mp->m_dirdatablk, -1, &bp, XFS_DATA_FORK); if (error) return error; ASSERT(bp != NULL); /* * Extract the byte offset we start at from the seek pointer. * We'll skip entries before this. */ wantoff = xfs_dir2_dataptr_to_off(mp, *offset); block = bp->data; xfs_dir2_data_check(dp, bp); /* * Set up values for the loop. */ btp = xfs_dir2_block_tail_p(mp, block); ptr = (char *)block->u; 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 *)block < wantoff) continue; cook = xfs_dir2_db_off_to_dataptr(mp, mp->m_dirdatablk, (char *)dep - (char *)block); ino = be64_to_cpu(dep->inumber); #if XFS_BIG_INUMS ino += mp->m_inoadd; #endif /* * If it didn't fit, set the final offset to here & return. */ if (filldir(dirent, dep->name, dep->namelen, cook, ino, DT_UNKNOWN)) { *offset = cook; xfs_da_brelse(NULL, bp); return 0; } } /* * Reached the end of the block. * Set the offset to a non-existent block 1 and return. */ *offset = xfs_dir2_db_off_to_dataptr(mp, mp->m_dirdatablk + 1, 0); xfs_da_brelse(NULL, bp); return 0; }