| /* |
| * Copyright (c) 2000-2002,2005 Silicon Graphics, 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 <libxfs.h> |
| #include "avl.h" |
| #include "globals.h" |
| #include "agheader.h" |
| #include "incore.h" |
| #include "protos.h" |
| #include "err_protos.h" |
| #include "dinode.h" |
| #include "dir.h" |
| #include "bmap.h" |
| |
| #if XFS_DIR_LEAF_MAPSIZE >= XFS_ATTR_LEAF_MAPSIZE |
| #define XR_DA_LEAF_MAPSIZE XFS_DIR_LEAF_MAPSIZE |
| #else |
| #define XR_DA_LEAF_MAPSIZE XFS_ATTR_LEAF_MAPSIZE |
| #endif |
| |
| |
| |
| typedef struct da_hole_map { |
| int lost_holes; |
| int num_holes; |
| struct { |
| int base; |
| int size; |
| } hentries[XR_DA_LEAF_MAPSIZE]; |
| } da_hole_map_t; |
| |
| /* |
| * takes a name and length (name need not be null-terminated) |
| * and returns 1 if the name contains a '/' or a \0, returns 0 |
| * otherwise |
| */ |
| int |
| namecheck(char *name, int length) |
| { |
| char *c; |
| int i; |
| |
| ASSERT(length < MAXNAMELEN); |
| |
| for (c = name, i = 0; i < length; i++, c++) { |
| if (*c == '/' || *c == '\0') |
| return(1); |
| } |
| |
| return(0); |
| } |
| |
| /* |
| * this routine performs inode discovery and tries to fix things |
| * in place. available redundancy -- inode data size should match |
| * used directory space in inode. returns number of valid directory |
| * entries. a non-zero return value means the directory is bogus |
| * and should be blasted. |
| */ |
| /* ARGSUSED */ |
| int |
| process_shortform_dir( |
| xfs_mount_t *mp, |
| xfs_ino_t ino, |
| xfs_dinode_t *dip, |
| int ino_discovery, |
| int *dino_dirty, /* out - 1 if dinode buffer dirty? */ |
| xfs_ino_t *parent, /* out - NULLFSINO if entry doesn't exist */ |
| char *dirname, /* directory pathname */ |
| int *repair) /* out - 1 if dir was fixed up */ |
| { |
| xfs_dir_shortform_t *sf; |
| xfs_dir_sf_entry_t *sf_entry, *next_sfe, *tmp_sfe; |
| xfs_ino_t lino; |
| int max_size; |
| __int64_t ino_dir_size; |
| int num_entries; |
| int ino_off; |
| int namelen; |
| int i; |
| int junkit; |
| int tmp_len; |
| int tmp_elen; |
| int bad_sfnamelen; |
| ino_tree_node_t *irec_p; |
| char name[MAXNAMELEN + 1]; |
| |
| #ifdef XR_DIR_TRACE |
| fprintf(stderr, "process_shortform_dir - inode %llu\n", ino); |
| #endif |
| |
| sf = (xfs_dir_shortform_t *)XFS_DFORK_DPTR(dip); |
| max_size = XFS_DFORK_DSIZE(dip, mp); |
| num_entries = sf->hdr.count; |
| ino_dir_size = be64_to_cpu(dip->di_core.di_size); |
| *repair = 0; |
| |
| ASSERT(ino_dir_size <= max_size); |
| |
| /* |
| * check for bad entry count |
| */ |
| if (num_entries * sizeof(xfs_dir_sf_entry_t) + sizeof(xfs_dir_sf_hdr_t) |
| > max_size || num_entries == 0) |
| num_entries = 0xFF; |
| |
| /* |
| * run through entries, stop at first bad entry, don't need |
| * to check for .. since that's encoded in its own field |
| */ |
| sf_entry = next_sfe = &sf->list[0]; |
| for (i = 0; i < num_entries && ino_dir_size > |
| (__psint_t)next_sfe - (__psint_t)sf; i++) { |
| tmp_sfe = NULL; |
| sf_entry = next_sfe; |
| junkit = 0; |
| bad_sfnamelen = 0; |
| xfs_dir_sf_get_dirino(&sf_entry->inumber, &lino); |
| |
| /* |
| * if entry points to self, junk it since only '.' or '..' |
| * should do that and shortform dirs don't contain either |
| * entry. if inode number is invalid, trash entry. |
| * if entry points to special inodes, trash it. |
| * if inode is unknown but number is valid, |
| * add it to the list of uncertain inodes. don't |
| * have to worry about an entry pointing to a |
| * deleted lost+found inode because the entry was |
| * deleted at the same time that the inode was cleared. |
| */ |
| if (lino == ino) { |
| junkit = 1; |
| } else if (verify_inum(mp, lino)) { |
| /* |
| * junk the entry, mark lino as NULL since it's bad |
| */ |
| do_warn( |
| _("invalid inode number %llu in directory %llu\n"), lino, ino); |
| lino = NULLFSINO; |
| junkit = 1; |
| } else if (lino == mp->m_sb.sb_rbmino) { |
| do_warn( |
| _("entry in shortform dir %llu references rt bitmap inode %llu\n"), |
| ino, lino); |
| junkit = 1; |
| } else if (lino == mp->m_sb.sb_rsumino) { |
| do_warn( |
| _("entry in shortform dir %llu references rt summary inode %llu\n"), |
| ino, lino); |
| junkit = 1; |
| } else if (lino == mp->m_sb.sb_uquotino) { |
| do_warn( |
| _("entry in shortform dir %llu references user quota inode %llu\n"), |
| ino, lino); |
| junkit = 1; |
| } else if (lino == mp->m_sb.sb_gquotino) { |
| do_warn( |
| _("entry in shortform dir %llu references group quota inode %llu\n"), |
| ino, lino); |
| junkit = 1; |
| } else if ((irec_p = find_inode_rec(XFS_INO_TO_AGNO(mp, lino), |
| XFS_INO_TO_AGINO(mp, lino))) != NULL) { |
| /* |
| * if inode is marked free and we're in inode |
| * discovery mode, leave the entry alone for now. |
| * if the inode turns out to be used, we'll figure |
| * that out when we scan it. If the inode really |
| * is free, we'll hit this code again in phase 4 |
| * after we've finished inode discovery and blow |
| * out the entry then. |
| */ |
| ino_off = XFS_INO_TO_AGINO(mp, lino) - |
| irec_p->ino_startnum; |
| ASSERT(is_inode_confirmed(irec_p, ino_off)); |
| |
| if (!ino_discovery && is_inode_free(irec_p, ino_off)) { |
| do_warn( |
| _("entry references free inode %llu in shortform directory %llu\n"), |
| lino, ino); |
| junkit = 1; |
| } |
| } else if (ino_discovery) { |
| /* |
| * put the inode on the uncertain list. we'll |
| * pull the inode off the list and check it later. |
| * if the inode turns out be bogus, we'll delete |
| * this entry in phase 6. |
| */ |
| add_inode_uncertain(mp, lino, 0); |
| } else { |
| /* |
| * blow the entry out. we know about all |
| * undiscovered entries now (past inode discovery |
| * phase) so this is clearly a bogus entry. |
| */ |
| do_warn( |
| _("entry references non-existent inode %llu in shortform dir %llu\n"), |
| lino, ino); |
| junkit = 1; |
| } |
| |
| namelen = sf_entry->namelen; |
| |
| if (namelen == 0) { |
| /* |
| * if we're really lucky, this is |
| * the last entry in which case we |
| * can use the dir size to set the |
| * namelen value. otherwise, forget |
| * it because we're not going to be |
| * able to find the next entry. |
| */ |
| bad_sfnamelen = 1; |
| |
| if (i == num_entries - 1) { |
| namelen = ino_dir_size - |
| ((__psint_t) &sf_entry->name[0] - |
| (__psint_t) sf); |
| if (!no_modify) { |
| do_warn( |
| _("zero length entry in shortform dir %llu, resetting to %d\n"), |
| ino, namelen); |
| sf_entry->namelen = namelen; |
| } else { |
| do_warn( |
| _("zero length entry in shortform dir %llu, would set to %d\n"), |
| ino, namelen); |
| } |
| } else { |
| do_warn( |
| _("zero length entry in shortform dir %llu, "), |
| ino); |
| if (!no_modify) |
| do_warn(_("junking %d entries\n"), |
| num_entries - i); |
| else |
| do_warn(_("would junk %d entries\n"), |
| num_entries - i); |
| /* |
| * don't process the rest of the directory, |
| * break out of processing looop |
| */ |
| break; |
| } |
| } else if ((__psint_t) sf_entry - (__psint_t) sf + |
| + xfs_dir_sf_entsize_byentry(sf_entry) |
| > ino_dir_size) { |
| bad_sfnamelen = 1; |
| |
| if (i == num_entries - 1) { |
| namelen = ino_dir_size - |
| ((__psint_t) &sf_entry->name[0] - |
| (__psint_t) sf); |
| do_warn( |
| _("size of last entry overflows space left in in shortform dir %llu, "), |
| ino); |
| if (!no_modify) { |
| do_warn(_("resetting to %d\n"), |
| namelen); |
| sf_entry->namelen = namelen; |
| *dino_dirty = 1; |
| } else { |
| do_warn(_("would reset to %d\n"), |
| namelen); |
| } |
| } else { |
| do_warn( |
| _("size of entry #%d overflows space left in in shortform dir %llu\n"), |
| i, ino); |
| if (!no_modify) { |
| if (i == num_entries - 1) |
| do_warn( |
| _("junking entry #%d\n"), |
| i); |
| else |
| do_warn( |
| _("junking %d entries\n"), |
| num_entries - i); |
| } else { |
| if (i == num_entries - 1) |
| do_warn( |
| _("would junk entry #%d\n"), |
| i); |
| else |
| do_warn( |
| _("would junk %d entries\n"), |
| num_entries - i); |
| } |
| |
| break; |
| } |
| } |
| |
| /* |
| * check for illegal chars in name. |
| * no need to check for bad length because |
| * the length value is stored in a byte |
| * so it can't be too big, it can only wrap |
| */ |
| if (namecheck((char *)&sf_entry->name[0], namelen)) { |
| /* |
| * junk entry |
| */ |
| do_warn( |
| _("entry contains illegal character in shortform dir %llu\n"), |
| ino); |
| junkit = 1; |
| } |
| |
| /* |
| * junk the entry by copying up the rest of the |
| * fork over the current entry and decrementing |
| * the entry count. if we're in no_modify mode, |
| * just issue the warning instead. then continue |
| * the loop with the next_sfe pointer set to the |
| * correct place in the fork and other counters |
| * properly set to reflect the deletion if it |
| * happened. |
| */ |
| if (junkit) { |
| memmove(name, sf_entry->name, namelen); |
| name[namelen] = '\0'; |
| |
| if (!no_modify) { |
| tmp_elen = xfs_dir_sf_entsize_byentry(sf_entry); |
| be64_add_cpu(&dip->di_core.di_size, -tmp_elen); |
| ino_dir_size -= tmp_elen; |
| |
| tmp_sfe = (xfs_dir_sf_entry_t *) |
| ((__psint_t) sf_entry + tmp_elen); |
| tmp_len = max_size - ((__psint_t) tmp_sfe |
| - (__psint_t) sf); |
| |
| memmove(sf_entry, tmp_sfe, tmp_len); |
| |
| sf->hdr.count -= 1; |
| num_entries--; |
| memset((void *)((__psint_t)sf_entry + tmp_len), |
| 0, tmp_elen); |
| |
| /* |
| * reset the tmp value to the current |
| * pointer so we'll process the entry |
| * we just moved up |
| */ |
| tmp_sfe = sf_entry; |
| |
| /* |
| * WARNING: drop the index i by one |
| * so it matches the decremented count |
| * for accurate comparisons later |
| */ |
| i--; |
| |
| *dino_dirty = 1; |
| *repair = 1; |
| |
| do_warn( |
| _("junking entry \"%s\" in directory inode %llu\n"), |
| name, ino); |
| } else { |
| do_warn( |
| _("would have junked entry \"%s\" in directory inode %llu\n"), |
| name, ino); |
| } |
| } |
| |
| /* |
| * go onto next entry unless we've just junked an |
| * entry in which the current entry pointer points |
| * to an unprocessed entry. have to take into zero-len |
| * entries into account in no modify mode since we |
| * calculate size based on next_sfe. |
| */ |
| next_sfe = (tmp_sfe == NULL) |
| ? (xfs_dir_sf_entry_t *) ((__psint_t) sf_entry |
| + ((!bad_sfnamelen) |
| ? xfs_dir_sf_entsize_byentry(sf_entry) |
| : sizeof(xfs_dir_sf_entry_t) - 1 |
| + namelen)) |
| : tmp_sfe; |
| } |
| |
| /* sync up sizes and entry counts */ |
| |
| if (sf->hdr.count != i) { |
| if (no_modify) { |
| do_warn( |
| _("would have corrected entry count in directory %llu from %d to %d\n"), |
| ino, sf->hdr.count, i); |
| } else { |
| do_warn( |
| _("corrected entry count in directory %llu, was %d, now %d\n"), |
| ino, sf->hdr.count, i); |
| sf->hdr.count = i; |
| *dino_dirty = 1; |
| *repair = 1; |
| } |
| } |
| |
| if ((__psint_t) next_sfe - (__psint_t) sf != ino_dir_size) { |
| if (no_modify) { |
| do_warn( |
| _("would have corrected directory %llu size from %lld to %lld\n"), |
| ino, (__int64_t) ino_dir_size, |
| (__int64_t)((__psint_t) next_sfe - (__psint_t) sf)); |
| } else { |
| do_warn( |
| _("corrected directory %llu size, was %lld, now %lld\n"), |
| ino, (__int64_t) ino_dir_size, |
| (__int64_t)((__psint_t) next_sfe - (__psint_t) sf)); |
| |
| dip->di_core.di_size = cpu_to_be64((__psint_t)next_sfe |
| - (__psint_t)sf); |
| *dino_dirty = 1; |
| *repair = 1; |
| } |
| } |
| /* |
| * check parent (..) entry |
| */ |
| xfs_dir_sf_get_dirino(&sf->hdr.parent, parent); |
| |
| /* |
| * if parent entry is bogus, null it out. we'll fix it later . |
| */ |
| if (verify_inum(mp, *parent)) { |
| *parent = NULLFSINO; |
| |
| do_warn( |
| _("bogus .. inode number (%llu) in directory inode %llu, "), |
| *parent, ino); |
| if (!no_modify) { |
| do_warn(_("clearing inode number\n")); |
| |
| xfs_dir_sf_put_dirino(parent, &sf->hdr.parent); |
| *dino_dirty = 1; |
| *repair = 1; |
| } else { |
| do_warn(_("would clear inode number\n")); |
| } |
| } else if (ino == mp->m_sb.sb_rootino && ino != *parent) { |
| /* |
| * root directories must have .. == . |
| */ |
| if (!no_modify) { |
| do_warn( |
| _("corrected root directory %llu .. entry, was %llu, now %llu\n"), |
| ino, *parent, ino); |
| *parent = ino; |
| xfs_dir_sf_put_dirino(parent, &sf->hdr.parent); |
| *dino_dirty = 1; |
| *repair = 1; |
| } else { |
| do_warn( |
| _("would have corrected root directory %llu .. entry from %llu to %llu\n"), |
| ino, *parent, ino); |
| } |
| } else if (ino == *parent && ino != mp->m_sb.sb_rootino) { |
| /* |
| * likewise, non-root directories can't have .. pointing |
| * to . |
| */ |
| *parent = NULLFSINO; |
| do_warn(_("bad .. entry in dir ino %llu, points to self, "), |
| ino); |
| if (!no_modify) { |
| do_warn(_("clearing inode number\n")); |
| |
| xfs_dir_sf_put_dirino(parent, &sf->hdr.parent); |
| *dino_dirty = 1; |
| *repair = 1; |
| } else { |
| do_warn(_("would clear inode number\n")); |
| } |
| } |
| |
| return(0); |
| } |
| |
| /* |
| * freespace map for directory leaf blocks (1 bit per byte) |
| * 1 == used, 0 == free |
| */ |
| size_t ts_dir_freemap_size = sizeof(da_freemap_t) * DA_BMAP_SIZE; |
| |
| void |
| init_da_freemap(da_freemap_t *dir_freemap) |
| { |
| memset(dir_freemap, 0, sizeof(da_freemap_t) * DA_BMAP_SIZE); |
| } |
| |
| /* |
| * sets directory freemap, returns 1 if there is a conflict |
| * returns 0 if everything's good. the range [start, stop) is set. |
| * right now, we just use the static array since only one directory |
| * block will be processed at once even though the interface allows |
| * you to pass in arbitrary da_freemap_t array's. |
| * |
| * Within a char, the lowest bit of the char represents the byte with |
| * the smallest address |
| */ |
| int |
| set_da_freemap(xfs_mount_t *mp, da_freemap_t *map, int start, int stop) |
| { |
| const da_freemap_t mask = 0x1; |
| int i; |
| |
| if (start > stop) { |
| /* |
| * allow == relation since [x, x) claims 1 byte |
| */ |
| do_warn(_("bad range claimed [%d, %d) in da block\n"), |
| start, stop); |
| return(1); |
| } |
| |
| if (stop > mp->m_sb.sb_blocksize) { |
| do_warn( |
| _("byte range end [%d %d) in da block larger than blocksize %d\n"), |
| start, stop, mp->m_sb.sb_blocksize); |
| return(1); |
| } |
| |
| for (i = start; i < stop; i ++) { |
| if (map[i / NBBY] & (mask << i % NBBY)) { |
| do_warn(_("multiply claimed byte %d in da block\n"), i); |
| return(1); |
| } |
| map[i / NBBY] |= (mask << i % NBBY); |
| } |
| |
| return(0); |
| } |
| |
| /* |
| * returns 0 if holemap is consistent with reality (as expressed by |
| * the da_freemap_t). returns 1 if there's a conflict. |
| */ |
| int |
| verify_da_freemap(xfs_mount_t *mp, da_freemap_t *map, da_hole_map_t *holes, |
| xfs_ino_t ino, xfs_dablk_t da_bno) |
| { |
| int i, j, start, len; |
| const da_freemap_t mask = 0x1; |
| |
| for (i = 0; i < XFS_DIR_LEAF_MAPSIZE; i++) { |
| if (holes->hentries[i].size == 0) |
| continue; |
| |
| start = holes->hentries[i].base; |
| len = holes->hentries[i].size; |
| |
| if (start >= mp->m_sb.sb_blocksize || |
| start + len > mp->m_sb.sb_blocksize) { |
| do_warn( |
| _("hole (start %d, len %d) out of range, block %d, dir ino %llu\n"), |
| start, len, da_bno, ino); |
| return(1); |
| } |
| |
| for (j = start; j < start + len; j++) { |
| if ((map[j / NBBY] & (mask << (j % NBBY))) != 0) { |
| /* |
| * bad news -- hole claims a used byte is free |
| */ |
| do_warn( |
| _("hole claims used byte %d, block %d, dir ino %llu\n"), |
| j, da_bno, ino); |
| return(1); |
| } |
| } |
| } |
| |
| return(0); |
| } |
| |
| void |
| process_da_freemap(xfs_mount_t *mp, da_freemap_t *map, da_hole_map_t *holes) |
| { |
| int i, j, in_hole, start, length, smallest, num_holes; |
| const da_freemap_t mask = 0x1; |
| |
| num_holes = in_hole = start = length = 0; |
| |
| for (i = 0; i < mp->m_sb.sb_blocksize; i++) { |
| if ((map[i / NBBY] & (mask << (i % NBBY))) == 0) { |
| /* |
| * byte is free (unused) |
| */ |
| if (in_hole == 1) |
| continue; |
| /* |
| * start of a new hole |
| */ |
| in_hole = 1; |
| start = i; |
| } else { |
| /* |
| * byte is used |
| */ |
| if (in_hole == 0) |
| continue; |
| /* |
| * end of a hole |
| */ |
| in_hole = 0; |
| /* |
| * if the hole disappears, throw it away |
| */ |
| length = i - start; |
| |
| if (length <= 0) |
| continue; |
| |
| num_holes++; |
| |
| for (smallest = j = 0; j < XR_DA_LEAF_MAPSIZE; j++) { |
| if (holes->hentries[j].size < |
| holes->hentries[smallest].size) |
| smallest = j; |
| |
| } |
| if (length > holes->hentries[smallest].size) { |
| holes->hentries[smallest].base = start; |
| holes->hentries[smallest].size = length; |
| } |
| } |
| } |
| |
| /* |
| * see if we have a big hole at the end |
| */ |
| if (in_hole == 1) { |
| /* |
| * duplicate of hole placement code above |
| */ |
| length = i - start; |
| |
| if (length > 0) { |
| num_holes++; |
| |
| for (smallest = j = 0; j < XR_DA_LEAF_MAPSIZE; j++) { |
| if (holes->hentries[j].size < |
| holes->hentries[smallest].size) |
| smallest = j; |
| |
| } |
| if (length > holes->hentries[smallest].size) { |
| holes->hentries[smallest].base = start; |
| holes->hentries[smallest].size = length; |
| } |
| } |
| } |
| |
| holes->lost_holes = MAX(num_holes - XR_DA_LEAF_MAPSIZE, 0); |
| holes->num_holes = num_holes; |
| |
| return; |
| } |
| |
| /* |
| * returns 1 if the hole info doesn't match, 0 if it does |
| */ |
| /* ARGSUSED */ |
| int |
| compare_da_freemaps(xfs_mount_t *mp, da_hole_map_t *holemap, |
| da_hole_map_t *block_hmap, int entries, |
| xfs_ino_t ino, xfs_dablk_t da_bno) |
| { |
| int i, k, res, found; |
| |
| res = 0; |
| |
| /* |
| * we chop holemap->lost_holes down to being two-valued |
| * value (1 or 0) for the test because the filesystem |
| * value is two-valued |
| */ |
| if ((holemap->lost_holes > 0 ? 1 : 0) != block_hmap->lost_holes) { |
| if (verbose) { |
| do_warn( |
| _("- derived hole value %d, saw %d, block %d, dir ino %llu\n"), |
| holemap->lost_holes, block_hmap->lost_holes, |
| da_bno, ino); |
| res = 1; |
| } else |
| return(1); |
| } |
| |
| for (i = 0; i < entries; i++) { |
| for (found = k = 0; k < entries; k++) { |
| if (holemap->hentries[i].base == |
| block_hmap->hentries[k].base |
| && holemap->hentries[i].size == |
| block_hmap->hentries[k].size) |
| found = 1; |
| } |
| if (!found) { |
| if (verbose) { |
| do_warn( |
| _("- derived hole (base %d, size %d) in block %d, dir inode %llu not found\n"), |
| holemap->hentries[i].base, |
| holemap->hentries[i].size, |
| da_bno, ino); |
| res = 1; |
| } else |
| return(1); |
| } |
| } |
| |
| return(res); |
| } |
| |
| #if 0 |
| void |
| test(xfs_mount_t *mp) |
| { |
| int i = 0; |
| da_hole_map_t holemap; |
| |
| init_da_freemap(dir_freemap); |
| memset(&holemap, 0, sizeof(da_hole_map_t)); |
| |
| set_da_freemap(mp, dir_freemap, 0, 50); |
| set_da_freemap(mp, dir_freemap, 100, 126); |
| set_da_freemap(mp, dir_freemap, 126, 129); |
| set_da_freemap(mp, dir_freemap, 130, 131); |
| set_da_freemap(mp, dir_freemap, 150, 160); |
| process_da_freemap(mp, dir_freemap, &holemap); |
| |
| return; |
| } |
| #endif |
| |
| |
| /* |
| * walk tree from root to the left-most leaf block reading in |
| * blocks and setting up cursor. passes back file block number of the |
| * left-most leaf block if successful (bno). returns 1 if successful, |
| * 0 if unsuccessful. |
| */ |
| int |
| traverse_int_dablock(xfs_mount_t *mp, |
| da_bt_cursor_t *da_cursor, |
| xfs_dablk_t *rbno, |
| int whichfork) |
| { |
| xfs_dablk_t bno; |
| int i; |
| xfs_da_intnode_t *node; |
| xfs_dfsbno_t fsbno; |
| xfs_buf_t *bp; |
| |
| /* |
| * traverse down left-side of tree until we hit the |
| * left-most leaf block setting up the btree cursor along |
| * the way. |
| */ |
| bno = 0; |
| i = -1; |
| node = NULL; |
| da_cursor->active = 0; |
| |
| do { |
| /* |
| * read in each block along the way and set up cursor |
| */ |
| fsbno = blkmap_get(da_cursor->blkmap, bno); |
| |
| if (fsbno == NULLDFSBNO) |
| goto error_out; |
| |
| bp = libxfs_readbuf(mp->m_dev, XFS_FSB_TO_DADDR(mp, fsbno), |
| XFS_FSB_TO_BB(mp, 1), 0); |
| if (!bp) { |
| if (whichfork == XFS_DATA_FORK) |
| do_warn(_("can't read block %u (fsbno %llu) " |
| "for directory inode %llu\n"), |
| bno, fsbno, da_cursor->ino); |
| else |
| do_warn(_("can't read block %u (fsbno %llu) " |
| "for attrbute fork of inode %llu\n"), |
| bno, fsbno, da_cursor->ino); |
| goto error_out; |
| } |
| |
| node = (xfs_da_intnode_t *)XFS_BUF_PTR(bp); |
| |
| if (be16_to_cpu(node->hdr.info.magic) != XFS_DA_NODE_MAGIC) { |
| do_warn(_("bad dir/attr magic number in inode %llu, " |
| "file bno = %u, fsbno = %llu\n"), |
| da_cursor->ino, bno, fsbno); |
| libxfs_putbuf(bp); |
| goto error_out; |
| } |
| if (be16_to_cpu(node->hdr.count) > |
| mp->m_dir_node_ents) { |
| do_warn(_("bad record count in inode %llu, " |
| "count = %d, max = %d\n"), |
| da_cursor->ino, |
| be16_to_cpu(node->hdr.count), |
| mp->m_dir_node_ents); |
| libxfs_putbuf(bp); |
| goto error_out; |
| } |
| |
| /* |
| * maintain level counter |
| */ |
| if (i == -1) |
| i = da_cursor->active = be16_to_cpu(node->hdr.level); |
| else { |
| if (be16_to_cpu(node->hdr.level) == i - 1) { |
| i--; |
| } else { |
| if (whichfork == XFS_DATA_FORK) |
| do_warn(_("bad directory btree for " |
| "directory inode %llu\n"), |
| da_cursor->ino); |
| else |
| do_warn(_("bad attribute fork btree " |
| "for inode %llu\n"), |
| da_cursor->ino); |
| libxfs_putbuf(bp); |
| goto error_out; |
| } |
| } |
| |
| da_cursor->level[i].hashval = be32_to_cpu( |
| node->btree[0].hashval); |
| da_cursor->level[i].bp = bp; |
| da_cursor->level[i].bno = bno; |
| da_cursor->level[i].index = 0; |
| #ifdef XR_DIR_TRACE |
| da_cursor->level[i].n = XFS_BUF_TO_DA_INTNODE(bp); |
| #endif |
| |
| /* |
| * set up new bno for next level down |
| */ |
| bno = be32_to_cpu(node->btree[0].before); |
| } while (node != NULL && i > 1); |
| |
| /* |
| * now return block number and get out |
| */ |
| *rbno = da_cursor->level[0].bno = bno; |
| return(1); |
| |
| error_out: |
| while (i > 1 && i <= da_cursor->active) { |
| libxfs_putbuf(da_cursor->level[i].bp); |
| i++; |
| } |
| |
| return(0); |
| } |
| |
| /* |
| * blow out buffer for this level and all the rest above as well |
| * if error == 0, we are not expecting to encounter any unreleased |
| * buffers (e.g. if we do, it's a mistake). if error == 1, we're |
| * in an error-handling case so unreleased buffers may exist. |
| */ |
| void |
| release_da_cursor_int(xfs_mount_t *mp, |
| da_bt_cursor_t *cursor, |
| int prev_level, |
| int error) |
| { |
| int level = prev_level + 1; |
| |
| if (cursor->level[level].bp != NULL) { |
| if (!error) { |
| do_warn(_("release_da_cursor_int got unexpected " |
| "non-null bp, dabno = %u\n"), |
| cursor->level[level].bno); |
| } |
| ASSERT(error != 0); |
| |
| libxfs_putbuf(cursor->level[level].bp); |
| cursor->level[level].bp = NULL; |
| } |
| |
| if (level < cursor->active) |
| release_da_cursor_int(mp, cursor, level, error); |
| |
| return; |
| } |
| |
| void |
| release_da_cursor(xfs_mount_t *mp, |
| da_bt_cursor_t *cursor, |
| int prev_level) |
| { |
| release_da_cursor_int(mp, cursor, prev_level, 0); |
| } |
| |
| void |
| err_release_da_cursor(xfs_mount_t *mp, |
| da_bt_cursor_t *cursor, |
| int prev_level) |
| { |
| release_da_cursor_int(mp, cursor, prev_level, 1); |
| } |
| |
| /* |
| * like traverse_int_dablock only it does far less checking |
| * and doesn't maintain the cursor. Just gets you to the |
| * leftmost block in the directory. returns the fsbno |
| * of that block if successful, NULLDFSBNO if not. |
| */ |
| xfs_dfsbno_t |
| get_first_dblock_fsbno(xfs_mount_t *mp, |
| xfs_ino_t ino, |
| xfs_dinode_t *dino) |
| { |
| xfs_dablk_t bno; |
| int i; |
| xfs_da_intnode_t *node; |
| xfs_dfsbno_t fsbno; |
| xfs_buf_t *bp; |
| |
| /* |
| * traverse down left-side of tree until we hit the |
| * left-most leaf block setting up the btree cursor along |
| * the way. |
| */ |
| bno = 0; |
| i = -1; |
| node = NULL; |
| |
| fsbno = get_bmapi(mp, dino, ino, bno, XFS_DATA_FORK); |
| |
| if (fsbno == NULLDFSBNO) { |
| do_warn(_("bmap of block #%u of inode %llu failed\n"), |
| bno, ino); |
| return(fsbno); |
| } |
| |
| if (be64_to_cpu(dino->di_core.di_size) <= XFS_LBSIZE(mp)) |
| return(fsbno); |
| |
| do { |
| /* |
| * walk down left side of btree, release buffers as you |
| * go. if the root block is a leaf (single-level btree), |
| * just return it. |
| * |
| */ |
| |
| bp = libxfs_readbuf(mp->m_dev, XFS_FSB_TO_DADDR(mp, fsbno), |
| XFS_FSB_TO_BB(mp, 1), 0); |
| if (!bp) { |
| do_warn(_("can't read block %u (fsbno %llu) " |
| "for directory inode %llu\n"), |
| bno, fsbno, ino); |
| return(NULLDFSBNO); |
| } |
| |
| node = (xfs_da_intnode_t *)XFS_BUF_PTR(bp); |
| |
| if (XFS_DA_NODE_MAGIC != |
| be16_to_cpu(node->hdr.info.magic)) { |
| do_warn(_("bad dir/attr magic number in inode %llu, " |
| "file bno = %u, fsbno = %llu\n"), |
| ino, bno, fsbno); |
| libxfs_putbuf(bp); |
| return(NULLDFSBNO); |
| } |
| |
| if (i == -1) |
| i = be16_to_cpu(node->hdr.level); |
| bno = be32_to_cpu(node->btree[0].before); |
| |
| libxfs_putbuf(bp); |
| |
| fsbno = get_bmapi(mp, dino, ino, bno, XFS_DATA_FORK); |
| |
| if (fsbno == NULLDFSBNO) { |
| do_warn(_("bmap of block #%u of inode %llu failed\n"), |
| bno, ino); |
| return(NULLDFSBNO); |
| } |
| |
| i--; |
| } while(i > 0); |
| |
| return(fsbno); |
| } |
| |
| /* |
| * make sure that all entries in all blocks along the right side of |
| * of the tree are used and hashval's are consistent. level is the |
| * level of the descendent block. returns 0 if good (even if it had |
| * to be fixed up), and 1 if bad. The right edge of the tree is |
| * technically a block boundary. this routine should be used then |
| * instead of verify_da_path(). |
| */ |
| int |
| verify_final_da_path(xfs_mount_t *mp, |
| da_bt_cursor_t *cursor, |
| const int p_level) |
| { |
| xfs_da_intnode_t *node; |
| xfs_dahash_t hashval; |
| int bad = 0; |
| int entry; |
| int this_level = p_level + 1; |
| |
| #ifdef XR_DIR_TRACE |
| fprintf(stderr, "in verify_final_da_path, this_level = %d\n", |
| this_level); |
| #endif |
| /* |
| * the index should point to the next "unprocessed" entry |
| * in the block which should be the final (rightmost) entry |
| */ |
| entry = cursor->level[this_level].index; |
| node = (xfs_da_intnode_t *)XFS_BUF_PTR(cursor->level[this_level].bp); |
| /* |
| * check internal block consistency on this level -- ensure |
| * that all entries are used, encountered and expected hashvals |
| * match, etc. |
| */ |
| if (entry != be16_to_cpu(node->hdr.count) - 1) { |
| do_warn(_("directory/attribute block used/count " |
| "inconsistency - %d/%hu\n"), |
| entry, be16_to_cpu(node->hdr.count)); |
| bad++; |
| } |
| /* |
| * hash values monotonically increasing ??? |
| */ |
| if (cursor->level[this_level].hashval >= |
| be32_to_cpu(node->btree[entry].hashval)) { |
| do_warn(_("directory/attribute block hashvalue inconsistency, " |
| "expected > %u / saw %u\n"), |
| cursor->level[this_level].hashval, |
| be32_to_cpu(node->btree[entry].hashval)); |
| bad++; |
| } |
| if (be32_to_cpu(node->hdr.info.forw) != 0) { |
| do_warn(_("bad directory/attribute forward block pointer, " |
| "expected 0, saw %u\n"), |
| be32_to_cpu(node->hdr.info.forw)); |
| bad++; |
| } |
| if (bad) { |
| do_warn(_("bad directory block in dir ino %llu\n"), |
| cursor->ino); |
| return(1); |
| } |
| /* |
| * keep track of greatest block # -- that gets |
| * us the length of the directory |
| */ |
| if (cursor->level[this_level].bno > cursor->greatest_bno) |
| cursor->greatest_bno = cursor->level[this_level].bno; |
| |
| /* |
| * ok, now check descendant block number against this level |
| */ |
| if (cursor->level[p_level].bno != be32_to_cpu( |
| node->btree[entry].before)) { |
| #ifdef XR_DIR_TRACE |
| fprintf(stderr, "bad directory btree pointer, child bno should " |
| "be %d, block bno is %d, hashval is %u\n", |
| be16_to_cpu(node->btree[entry].before), |
| cursor->level[p_level].bno, |
| cursor->level[p_level].hashval); |
| fprintf(stderr, "verify_final_da_path returns 1 (bad) #1a\n"); |
| #endif |
| return(1); |
| } |
| |
| if (cursor->level[p_level].hashval != be32_to_cpu( |
| node->btree[entry].hashval)) { |
| if (!no_modify) { |
| do_warn(_("correcting bad hashval in non-leaf " |
| "dir/attr block\n\tin (level %d) in " |
| "inode %llu.\n"), |
| this_level, cursor->ino); |
| node->btree[entry].hashval = cpu_to_be32( |
| cursor->level[p_level].hashval); |
| cursor->level[this_level].dirty++; |
| } else { |
| do_warn(_("would correct bad hashval in non-leaf " |
| "dir/attr block\n\tin (level %d) in " |
| "inode %llu.\n"), |
| this_level, cursor->ino); |
| } |
| } |
| |
| /* |
| * Note: squirrel hashval away _before_ releasing the |
| * buffer, preventing a use-after-free problem. |
| */ |
| hashval = be32_to_cpu(node->btree[entry].hashval); |
| |
| /* |
| * release/write buffer |
| */ |
| ASSERT(cursor->level[this_level].dirty == 0 || |
| (cursor->level[this_level].dirty && !no_modify)); |
| |
| if (cursor->level[this_level].dirty && !no_modify) |
| libxfs_writebuf(cursor->level[this_level].bp, 0); |
| else |
| libxfs_putbuf(cursor->level[this_level].bp); |
| |
| cursor->level[this_level].bp = NULL; |
| |
| /* |
| * bail out if this is the root block (top of tree) |
| */ |
| if (this_level >= cursor->active) { |
| #ifdef XR_DIR_TRACE |
| fprintf(stderr, "verify_final_da_path returns 0 (ok)\n"); |
| #endif |
| return(0); |
| } |
| /* |
| * set hashvalue to correctly reflect the now-validated |
| * last entry in this block and continue upwards validation |
| */ |
| cursor->level[this_level].hashval = hashval; |
| return(verify_final_da_path(mp, cursor, this_level)); |
| } |
| |
| /* |
| * Verifies the path from a descendant block up to the root. |
| * Should be called when the descendant level traversal hits |
| * a block boundary before crossing the boundary (reading in a new |
| * block). |
| * |
| * the directory/attr btrees work differently to the other fs btrees. |
| * each interior block contains records that are <hashval, bno> |
| * pairs. The bno is a file bno, not a filesystem bno. The last |
| * hashvalue in the block <bno> will be <hashval>. BUT unlike |
| * the freespace btrees, the *last* value in each block gets |
| * propagated up the tree instead of the first value in each block. |
| * that is, the interior records point to child blocks and the *greatest* |
| * hash value contained by the child block is the one the block above |
| * uses as the key for the child block. |
| * |
| * level is the level of the descendent block. returns 0 if good, |
| * and 1 if bad. The descendant block may be a leaf block. |
| * |
| * the invariant here is that the values in the cursor for the |
| * levels beneath this level (this_level) and the cursor index |
| * for this level *must* be valid. |
| * |
| * that is, the hashval/bno info is accurate for all |
| * DESCENDANTS and match what the node[index] information |
| * for the current index in the cursor for this level. |
| * |
| * the index values in the cursor for the descendant level |
| * are allowed to be off by one as they will reflect the |
| * next entry at those levels to be processed. |
| * |
| * the hashvalue for the current level can't be set until |
| * we hit the last entry in the block so, it's garbage |
| * until set by this routine. |
| * |
| * bno and bp for the current block/level are always valid |
| * since they have to be set so we can get a buffer for the |
| * block. |
| */ |
| int |
| verify_da_path(xfs_mount_t *mp, |
| da_bt_cursor_t *cursor, |
| const int p_level) |
| { |
| xfs_da_intnode_t *node; |
| xfs_da_intnode_t *newnode; |
| xfs_dfsbno_t fsbno; |
| xfs_dablk_t dabno; |
| xfs_buf_t *bp; |
| int bad; |
| int entry; |
| int this_level = p_level + 1; |
| |
| /* |
| * index is currently set to point to the entry that |
| * should be processed now in this level. |
| */ |
| entry = cursor->level[this_level].index; |
| node = (xfs_da_intnode_t *)XFS_BUF_PTR(cursor->level[this_level].bp); |
| |
| /* |
| * if this block is out of entries, validate this |
| * block and move on to the next block. |
| * and update cursor value for said level |
| */ |
| if (entry >= be16_to_cpu(node->hdr.count)) { |
| /* |
| * update the hash value for this level before |
| * validating it. bno value should be ok since |
| * it was set when the block was first read in. |
| */ |
| cursor->level[this_level].hashval = |
| be32_to_cpu(node->btree[entry - 1].hashval); |
| |
| /* |
| * keep track of greatest block # -- that gets |
| * us the length of the directory |
| */ |
| if (cursor->level[this_level].bno > cursor->greatest_bno) |
| cursor->greatest_bno = cursor->level[this_level].bno; |
| |
| /* |
| * validate the path for the current used-up block |
| * before we trash it |
| */ |
| if (verify_da_path(mp, cursor, this_level)) |
| return(1); |
| /* |
| * ok, now get the next buffer and check sibling pointers |
| */ |
| dabno = be32_to_cpu(node->hdr.info.forw); |
| ASSERT(dabno != 0); |
| fsbno = blkmap_get(cursor->blkmap, dabno); |
| |
| if (fsbno == NULLDFSBNO) { |
| do_warn(_("can't get map info for block %u " |
| "of directory inode %llu\n"), |
| dabno, cursor->ino); |
| return(1); |
| } |
| |
| bp = libxfs_readbuf(mp->m_dev, XFS_FSB_TO_DADDR(mp, fsbno), |
| XFS_FSB_TO_BB(mp, 1), 0); |
| if (!bp) { |
| do_warn(_("can't read block %u (%llu) " |
| "for directory inode %llu\n"), |
| dabno, fsbno, cursor->ino); |
| return(1); |
| } |
| |
| newnode = (xfs_da_intnode_t *)XFS_BUF_PTR(bp); |
| /* |
| * verify magic number and back pointer, sanity-check |
| * entry count, verify level |
| */ |
| bad = 0; |
| if (XFS_DA_NODE_MAGIC != be16_to_cpu(newnode->hdr.info.magic)) { |
| do_warn(_("bad magic number %x in block %u (%llu) " |
| "for directory inode %llu\n"), |
| be16_to_cpu(newnode->hdr.info.magic), |
| dabno, fsbno, cursor->ino); |
| bad++; |
| } |
| if (be32_to_cpu(newnode->hdr.info.back) != |
| cursor->level[this_level].bno) { |
| do_warn(_("bad back pointer in block %u (%llu) " |
| "for directory inode %llu\n"), |
| dabno, fsbno, cursor->ino); |
| bad++; |
| } |
| if (be16_to_cpu(newnode->hdr.count) > mp->m_dir_node_ents) { |
| do_warn(_("entry count %d too large in block %u (%llu) " |
| "for directory inode %llu\n"), |
| be16_to_cpu(newnode->hdr.count), |
| dabno, fsbno, cursor->ino); |
| bad++; |
| } |
| if (be16_to_cpu(newnode->hdr.level) != this_level) { |
| do_warn(_("bad level %d in block %u (%llu) " |
| "for directory inode %llu\n"), |
| be16_to_cpu(newnode->hdr.level), |
| dabno, fsbno, cursor->ino); |
| bad++; |
| } |
| if (bad) { |
| #ifdef XR_DIR_TRACE |
| fprintf(stderr, "verify_da_path returns 1 (bad) #4\n"); |
| #endif |
| libxfs_putbuf(bp); |
| return(1); |
| } |
| /* |
| * update cursor, write out the *current* level if |
| * required. don't write out the descendant level |
| */ |
| ASSERT(cursor->level[this_level].dirty == 0 || |
| (cursor->level[this_level].dirty && !no_modify)); |
| |
| if (cursor->level[this_level].dirty && !no_modify) |
| libxfs_writebuf(cursor->level[this_level].bp, 0); |
| else |
| libxfs_putbuf(cursor->level[this_level].bp); |
| cursor->level[this_level].bp = bp; |
| cursor->level[this_level].dirty = 0; |
| cursor->level[this_level].bno = dabno; |
| cursor->level[this_level].hashval = |
| be32_to_cpu(newnode->btree[0].hashval); |
| #ifdef XR_DIR_TRACE |
| cursor->level[this_level].n = newnode; |
| #endif |
| node = newnode; |
| |
| entry = cursor->level[this_level].index = 0; |
| } |
| /* |
| * ditto for block numbers |
| */ |
| if (cursor->level[p_level].bno != |
| be32_to_cpu(node->btree[entry].before)) { |
| #ifdef XR_DIR_TRACE |
| fprintf(stderr, "bad directory btree pointer, child bno " |
| "should be %d, block bno is %d, hashval is %u\n", |
| be32_to_cpu(node->btree[entry].before), |
| cursor->level[p_level].bno, |
| cursor->level[p_level].hashval); |
| fprintf(stderr, "verify_da_path returns 1 (bad) #1a\n"); |
| #endif |
| return(1); |
| } |
| /* |
| * ok, now validate last hashvalue in the descendant |
| * block against the hashval in the current entry |
| */ |
| if (cursor->level[p_level].hashval != |
| be32_to_cpu(node->btree[entry].hashval)) { |
| if (!no_modify) { |
| do_warn(_("correcting bad hashval in interior " |
| "dir/attr block\n\tin (level %d) in " |
| "inode %llu.\n"), |
| this_level, cursor->ino); |
| node->btree[entry].hashval = cpu_to_be32( |
| cursor->level[p_level].hashval); |
| cursor->level[this_level].dirty++; |
| } else { |
| do_warn(_("would correct bad hashval in interior " |
| "dir/attr block\n\tin (level %d) in " |
| "inode %llu.\n"), |
| this_level, cursor->ino); |
| } |
| } |
| /* |
| * increment index for this level to point to next entry |
| * (which should point to the next descendant block) |
| */ |
| cursor->level[this_level].index++; |
| #ifdef XR_DIR_TRACE |
| fprintf(stderr, "verify_da_path returns 0 (ok)\n"); |
| #endif |
| return(0); |
| } |
| |
| size_t ts_dirbuf_size = 64*1024; |
| |
| /* |
| * called by both node dir and leaf dir processing routines |
| * validates all contents *but* the sibling pointers (forw/back) |
| * and the magic number. |
| * |
| * returns 0 if the directory is ok or has been brought to the |
| * stage that it can be fixed up later (in phase 6), |
| * 1 if it has to be junked. |
| * |
| * Right now we fix a lot of things (TBD == to be deleted). |
| * |
| * incorrect . entries - inode # is corrected |
| * entries with mismatched hashvalue/name strings - hashvalue reset |
| * entries whose hashvalues are out-of-order - entry marked TBD |
| * .. entries with invalid inode numbers - entry marked TBD |
| * entries with invalid inode numbers - entry marked TBD |
| * multiple . entries - all but the first entry are marked TBD |
| * zero-length entries - entry is deleted |
| * entries with an out-of-bounds name index ptr - entry is deleted |
| * |
| * entries marked TBD have the first character of the name (which |
| * lives in the heap) have the first character in the name set |
| * to '/' -- an illegal value. |
| * |
| * entries deleted right here are deleted by blowing away the entry |
| * (but leaving the heap untouched). any space that was used |
| * by the deleted entry will be reclaimed by the block freespace |
| * (da_freemap) processing code. |
| * |
| * if two entries claim the same space in the heap (say, due to |
| * bad entry name index pointers), we lose the directory. We could |
| * try harder to fix this but it'll do for now. |
| */ |
| /* ARGSUSED */ |
| int |
| process_leaf_dir_block( |
| xfs_mount_t *mp, |
| xfs_dir_leafblock_t *leaf, |
| xfs_dablk_t da_bno, |
| xfs_ino_t ino, |
| xfs_dahash_t last_hashval, /* last hashval encountered */ |
| int ino_discovery, |
| blkmap_t *blkmap, |
| int *dot, |
| int *dotdot, |
| xfs_ino_t *parent, |
| int *buf_dirty, /* is buffer dirty? */ |
| xfs_dahash_t *next_hashval) /* greatest hashval in block */ |
| { |
| xfs_ino_t lino; |
| xfs_dir_leaf_entry_t *entry; |
| xfs_dir_leaf_entry_t *s_entry; |
| xfs_dir_leaf_entry_t *d_entry; |
| xfs_dir_leafblock_t *new_leaf; |
| char *first_byte; |
| xfs_dir_leaf_name_t *namest; |
| ino_tree_node_t *irec_p; |
| int num_entries; |
| xfs_dahash_t hashval; |
| int i; |
| int nm_illegal; |
| int bytes; |
| int start; |
| int stop; |
| int res = 0; |
| int ino_off; |
| int first_used; |
| int bytes_used; |
| int reset_holes; |
| int zero_len_entries; |
| char fname[MAXNAMELEN + 1]; |
| da_hole_map_t holemap; |
| da_hole_map_t bholemap; |
| unsigned char *dir_freemap = ts_dir_freemap(); |
| |
| #ifdef XR_DIR_TRACE |
| fprintf(stderr, "\tprocess_leaf_dir_block - ino %llu\n", ino); |
| #endif |
| |
| /* |
| * clear static dir block freespace bitmap |
| */ |
| init_da_freemap(dir_freemap); |
| |
| *buf_dirty = 0; |
| first_used = mp->m_sb.sb_blocksize; |
| zero_len_entries = 0; |
| bytes_used = 0; |
| |
| i = stop = sizeof(xfs_dir_leaf_hdr_t); |
| if (set_da_freemap(mp, dir_freemap, 0, stop)) { |
| do_warn( |
| _("directory block header conflicts with used space in directory inode %llu\n"), |
| ino); |
| return(1); |
| } |
| |
| /* |
| * verify structure: monotonically increasing hash value for |
| * all leaf entries, indexes for all entries must be within |
| * this fs block (trivially true for 64K blocks). also track |
| * used space so we can check the freespace map. check for |
| * zero-length entries. for now, if anything's wrong, we |
| * junk the directory and we'll pick up no-longer referenced |
| * inodes on a later pass. |
| */ |
| for (i = 0, entry = &leaf->entries[0]; |
| i < be16_to_cpu(leaf->hdr.count); |
| i++, entry++) { |
| /* |
| * check that the name index isn't out of bounds |
| * if it is, delete the entry since we can't |
| * grab the inode #. |
| */ |
| if (be16_to_cpu(entry->nameidx) >= |
| mp->m_sb.sb_blocksize) { |
| if (!no_modify) { |
| *buf_dirty = 1; |
| |
| if (be16_to_cpu(leaf->hdr.count) > 1) { |
| do_warn( |
| _("nameidx %d for entry #%d, bno %d, ino %llu > fs blocksize, deleting entry\n"), |
| be16_to_cpu(entry->nameidx), |
| i, da_bno, ino); |
| ASSERT(be16_to_cpu(leaf->hdr.count) > i); |
| |
| bytes = (be16_to_cpu(leaf->hdr.count) - i) * |
| sizeof(xfs_dir_leaf_entry_t); |
| |
| /* |
| * compress table unless we're |
| * only dealing with 1 entry |
| * (the last one) in which case |
| * just zero it. |
| */ |
| if (bytes > |
| sizeof(xfs_dir_leaf_entry_t)) { |
| memmove(entry, entry + 1, |
| bytes); |
| memset((void *) |
| ((__psint_t) entry + bytes), 0, |
| sizeof(xfs_dir_leaf_entry_t)); |
| } else { |
| memset(entry, 0, |
| sizeof(xfs_dir_leaf_entry_t)); |
| } |
| |
| /* |
| * sync vars to match smaller table. |
| * don't have to worry about freespace |
| * map since we haven't set it for |
| * this entry yet. |
| */ |
| be16_add_cpu(&leaf->hdr.count, -1); |
| i--; |
| entry--; |
| } else { |
| do_warn( |
| _("nameidx %d, entry #%d, bno %d, ino %llu > fs blocksize, marking entry bad\n"), |
| be16_to_cpu(entry->nameidx), |
| i, da_bno, ino); |
| entry->nameidx = cpu_to_be16( |
| mp->m_sb.sb_blocksize - |
| sizeof(xfs_dir_leaf_name_t)); |
| namest = xfs_dir_leaf_namestruct(leaf, |
| be16_to_cpu(entry->nameidx)); |
| lino = NULLFSINO; |
| xfs_dir_sf_put_dirino(&lino, |
| &namest->inumber); |
| namest->name[0] = '/'; |
| } |
| } else { |
| do_warn( |
| _("nameidx %d, entry #%d, bno %d, ino %llu > fs blocksize, would delete entry\n"), |
| be16_to_cpu(entry->nameidx), |
| i, da_bno, ino); |
| } |
| continue; |
| } |
| /* |
| * inode processing -- make sure the inode |
| * is in our tree or we add it to the uncertain |
| * list if the inode # is valid. if namelen is 0, |
| * we can still try for the inode as long as nameidx |
| * is ok. |
| */ |
| namest = xfs_dir_leaf_namestruct(leaf, |
| be16_to_cpu(entry->nameidx)); |
| xfs_dir_sf_get_dirino(&namest->inumber, &lino); |
| |
| /* |
| * we may have to blow out an entry because of bad |
| * inode numbers. do NOT touch the name until after |
| * we've computed the hashvalue and done a namecheck() |
| * on the name. |
| */ |
| if (!ino_discovery && lino == NULLFSINO) { |
| /* |
| * don't do a damned thing. We already |
| * found this (or did it ourselves) during |
| * phase 3. |
| */ |
| } else if (verify_inum(mp, lino)) { |
| /* |
| * bad inode number. clear the inode |
| * number and the entry will get removed |
| * later. We don't trash the directory |
| * since it's still structurally intact. |
| */ |
| do_warn( |
| _("invalid ino number %llu in dir ino %llu, entry #%d, bno %d\n"), |
| lino, ino, i, da_bno); |
| if (!no_modify) { |
| do_warn( |
| _("\tclearing ino number in entry %d...\n"), |
| i); |
| lino = NULLFSINO; |
| xfs_dir_sf_put_dirino(&lino, &namest->inumber); |
| *buf_dirty = 1; |
| } else { |
| do_warn( |
| _("\twould clear ino number in entry %d...\n"), |
| i); |
| } |
| } else if (lino == mp->m_sb.sb_rbmino) { |
| do_warn( |
| _("entry #%d, bno %d in directory %llu references realtime bitmap inode %llu\n"), |
| i, da_bno, ino, lino); |
| if (!no_modify) { |
| do_warn( |
| _("\tclearing ino number in entry %d...\n"), |
| i); |
| |
| lino = NULLFSINO; |
| xfs_dir_sf_put_dirino(&lino, &namest->inumber); |
| *buf_dirty = 1; |
| } else { |
| do_warn( |
| _("\twould clear ino number in entry %d...\n"), |
| i); |
| } |
| } else if (lino == mp->m_sb.sb_rsumino) { |
| do_warn( |
| _("entry #%d, bno %d in directory %llu references realtime summary inode %llu\n"), |
| i, da_bno, ino, lino); |
| if (!no_modify) { |
| do_warn( |
| _("\tclearing ino number in entry %d...\n"), i); |
| |
| lino = NULLFSINO; |
| xfs_dir_sf_put_dirino(&lino, &namest->inumber); |
| *buf_dirty = 1; |
| } else { |
| do_warn( |
| _("\twould clear ino number in entry %d...\n"), |
| i); |
| } |
| } else if (lino == mp->m_sb.sb_uquotino) { |
| do_warn( |
| _("entry #%d, bno %d in directory %llu references user quota inode %llu\n"), |
| i, da_bno, ino, lino); |
| if (!no_modify) { |
| do_warn( |
| _("\tclearing ino number in entry %d...\n"), |
| i); |
| |
| lino = NULLFSINO; |
| xfs_dir_sf_put_dirino(&lino, &namest->inumber); |
| *buf_dirty = 1; |
| } else { |
| do_warn( |
| _("\twould clear ino number in entry %d...\n"), |
| i); |
| } |
| } else if (lino == mp->m_sb.sb_gquotino) { |
| do_warn( |
| _("entry #%d, bno %d in directory %llu references group quota inode %llu\n"), |
| i, da_bno, ino, lino); |
| if (!no_modify) { |
| do_warn( |
| _("\tclearing ino number in entry %d...\n"), |
| i); |
| |
| lino = NULLFSINO; |
| xfs_dir_sf_put_dirino(&lino, &namest->inumber); |
| *buf_dirty = 1; |
| } else { |
| do_warn( |
| _("\twould clear ino number in entry %d...\n"), |
| i); |
| } |
| } else if ((irec_p = find_inode_rec( |
| XFS_INO_TO_AGNO(mp, lino), |
| XFS_INO_TO_AGINO(mp, lino))) != NULL) { |
| /* |
| * inode recs should have only confirmed |
| * inodes in them |
| */ |
| ino_off = XFS_INO_TO_AGINO(mp, lino) - |
| irec_p->ino_startnum; |
| ASSERT(is_inode_confirmed(irec_p, ino_off)); |
| /* |
| * if inode is marked free and we're in inode |
| * discovery mode, leave the entry alone for now. |
| * if the inode turns out to be used, we'll figure |
| * that out when we scan it. If the inode really |
| * is free, we'll hit this code again in phase 4 |
| * after we've finished inode discovery and blow |
| * out the entry then. |
| */ |
| if (!ino_discovery && is_inode_free(irec_p, ino_off)) { |
| if (!no_modify) { |
| do_warn( |
| _("entry references free inode %llu in directory %llu, will clear entry\n"), |
| lino, ino); |
| lino = NULLFSINO; |
| xfs_dir_sf_put_dirino(&lino, |
| &namest->inumber); |
| *buf_dirty = 1; |
| } else { |
| do_warn( |
| _("entry references free inode %llu in directory %llu, would clear entry\n"), |
| lino, ino); |
| } |
| } |
| } else if (ino_discovery) { |
| add_inode_uncertain(mp, lino, 0); |
| } else { |
| do_warn( |
| _("bad ino number %llu in dir ino %llu, entry #%d, bno %d\n"), |
| lino, ino, i, da_bno); |
| if (!no_modify) { |
| do_warn(_("clearing inode number...\n")); |
| lino = NULLFSINO; |
| xfs_dir_sf_put_dirino(&lino, &namest->inumber); |
| *buf_dirty = 1; |
| } else { |
| do_warn(_("would clear inode number...\n")); |
| } |
| } |
| /* |
| * if we have a zero-length entry, trash it. |
| * we may lose the inode (chunk) if we don't |
| * finish the repair successfully and the inode |
| * isn't mentioned anywhere else (like in the inode |
| * tree) but the alternative is to risk losing the |
| * entire directory by trying to use the next byte |
| * to turn the entry into a 1-char entry. That's |
| * probably a safe bet but if it didn't work, we'd |
| * lose the entire directory the way we currently do |
| * things. (Maybe we should change that later :-). |
| */ |
| if (entry->namelen == 0) { |
| *buf_dirty = 1; |
| |
| if (be16_to_cpu(leaf->hdr.count) > 1) { |
| do_warn( |
| _("entry #%d, dir inode %llu, has zero-len name, deleting entry\n"), |
| i, ino); |
| ASSERT(be16_to_cpu(leaf->hdr.count) > i); |
| |
| bytes = (be16_to_cpu(leaf->hdr.count) - i) * |
| sizeof(xfs_dir_leaf_entry_t); |
| |
| /* |
| * compress table unless we're |
| * only dealing with 1 entry |
| * (the last one) in which case |
| * just zero it. |
| */ |
| if (bytes > sizeof(xfs_dir_leaf_entry_t)) { |
| memmove(entry, entry + 1, bytes); |
| memset((void *)((__psint_t) entry + |
| bytes), 0, |
| sizeof(xfs_dir_leaf_entry_t)); |
| } else { |
| memset(entry, 0, |
| sizeof(xfs_dir_leaf_entry_t)); |
| } |
| |
| /* |
| * sync vars to match smaller table. |
| * don't have to worry about freespace |
| * map since we haven't set it for |
| * this entry yet. |
| */ |
| be16_add_cpu(&leaf->hdr.count, -1); |
| i--; |
| entry--; |
| } else { |
| /* |
| * if it's the only entry, preserve the |
| * inode number for now |
| */ |
| do_warn( |
| _("entry #%d, dir inode %llu, has zero-len name, marking entry bad\n"), |
| i, ino); |
| entry->nameidx = cpu_to_be16( |
| mp->m_sb.sb_blocksize - |
| sizeof(xfs_dir_leaf_name_t)); |
| namest = xfs_dir_leaf_namestruct(leaf, |
| be16_to_cpu(entry->nameidx)); |
| xfs_dir_sf_put_dirino(&lino, &namest->inumber); |
| namest->name[0] = '/'; |
| } |
| } else if (be16_to_cpu(entry->nameidx) + entry->namelen > |
| XFS_LBSIZE(mp)) { |
| do_warn( |
| _("bad size, entry #%d in dir inode %llu, block %u -- entry overflows block\n"), |
| i, ino, da_bno); |
| |
| return(1); |
| } |
| |
| start = (__psint_t)&leaf->entries[i] - (__psint_t)leaf;; |
| stop = start + sizeof(xfs_dir_leaf_entry_t); |
| |
| if (set_da_freemap(mp, dir_freemap, start, stop)) { |
| do_warn( |
| _("dir entry slot %d in block %u conflicts with used space in dir inode %llu\n"), |
| i, da_bno, ino); |
| return(1); |
| } |
| |
| /* |
| * check if the name is legal. if so, then |
| * check that the name and hashvalues match. |
| * |
| * if the name is illegal, we don't check the |
| * hashvalue computed from it. we just make |
| * sure that the hashvalue in the entry is |
| * monotonically increasing wrt to the previous |
| * entry. |
| * |
| * Note that we do NOT have to check the length |
| * because the length is stored in a one-byte |
| * unsigned int which max's out at MAXNAMELEN |
| * making it impossible for the stored length |
| * value to be out of range. |
| */ |
| memmove(fname, namest->name, entry->namelen); |
| fname[entry->namelen] = '\0'; |
| hashval = libxfs_da_hashname((uchar_t *) fname, entry->namelen); |
| |
| /* |
| * only complain about illegal names in phase 3 (when |
| * inode discovery is turned on). Otherwise, we'd complain |
| * a lot during phase 4. If the name is illegal, leave |
| * the hash value in that entry alone. |
| */ |
| nm_illegal = namecheck(fname, entry->namelen); |
| |
| if (ino_discovery && nm_illegal) { |
| /* |
| * junk the entry, illegal name |
| */ |
| if (!no_modify) { |
| do_warn( |
| _("illegal name \"%s\" in directory inode %llu, entry will be cleared\n"), |
| fname, ino); |
| namest->name[0] = '/'; |
| *buf_dirty = 1; |
| } else { |
| do_warn( |
| _("illegal name \"%s\" in directory inode %llu, entry would be cleared\n"), |
| fname, ino); |
| } |
| } else if (!nm_illegal && |
| be32_to_cpu(entry->hashval) != hashval) { |
| /* |
| * try resetting the hashvalue to the correct |
| * value for the string, if the string has been |
| * corrupted, too, that will get picked up next |
| */ |
| do_warn(_("\tmismatched hash value for entry \"%s\"\n"), |
| fname); |
| if (!no_modify) { |
| do_warn( |
| _("\t\tin directory inode %llu. resetting hash value.\n"), |
| ino); |
| entry->hashval = cpu_to_be32(hashval); |
| *buf_dirty = 1; |
| } else { |
| do_warn( |
| _("\t\tin directory inode %llu. would reset hash value.\n"), |
| ino); |
| } |
| } |
| |
| /* |
| * now we can mark entries with NULLFSINO's bad |
| */ |
| if (!no_modify && lino == NULLFSINO) { |
| namest->name[0] = '/'; |
| *buf_dirty = 1; |
| } |
| |
| /* |
| * regardless of whether the entry has or hasn't been |
| * marked for deletion, the hash value ordering must |
| * be maintained. |
| */ |
| if (be32_to_cpu(entry->hashval) < last_hashval) { |
| /* |
| * blow out the entry -- set hashval to sane value |
| * and set the first character in the string to |
| * the illegal value '/'. Reset the hash value |
| * to the last hashvalue so that verify_da_path |
| * will fix up the interior pointers correctly. |
| * the entry will be deleted later (by routines |
| * that need only the entry #). We keep the |
| * inode number in the entry so we can attach |
| * the inode to the orphanage later. |
| */ |
| do_warn(_("\tbad hash ordering for entry \"%s\"\n"), |
| fname); |
| if (!no_modify) { |
| do_warn( |
| _("\t\tin directory inode %llu. will clear entry\n"), |
| ino); |
| entry->hashval = cpu_to_be32(last_hashval); |
| namest->name[0] = '/'; |
| *buf_dirty = 1; |
| } else { |
| do_warn( |
| _("\t\tin directory inode %llu. would clear entry\n"), |
| ino); |
| } |
| } |
| |
| *next_hashval = last_hashval = be32_to_cpu(entry->hashval); |
| |
| /* |
| * if heap data conflicts with something, |
| * blow it out and skip the rest of the loop |
| */ |
| if (set_da_freemap(mp, dir_freemap, be16_to_cpu(entry->nameidx), |
| be16_to_cpu(entry->nameidx) |
| + sizeof(xfs_dir_leaf_name_t) |
| + entry->namelen - 1)) { |
| do_warn( |
| _("name \"%s\" (block %u, slot %d) conflicts with used space in dir inode %llu\n"), |
| fname, da_bno, i, ino); |
| if (!no_modify) { |
| entry->namelen = 0; |
| *buf_dirty = 1; |
| |
| do_warn( |
| _("will clear entry \"%s\" (#%d) in directory inode %llu\n"), |
| fname, i, ino); |
| } else { |
| do_warn( |
| _("would clear entry \"%s\" (#%d)in directory inode %llu\n"), |
| fname, i, ino); |
| } |
| continue; |
| } |
| |
| /* |
| * keep track of heap stats (first byte used, total bytes used) |
| */ |
| if (be16_to_cpu(entry->nameidx) < first_used) |
| first_used = be16_to_cpu(entry->nameidx); |
| bytes_used += entry->namelen; |
| |
| /* |
| * special . or .. entry processing |
| */ |
| if (entry->namelen == 2 && namest->name[0] == '.' && |
| namest->name[1] == '.') { |
| /* |
| * the '..' case |
| */ |
| if (!*dotdot) { |
| (*dotdot)++; |
| *parent = lino; |
| #ifdef XR_DIR_TRACE |
| fprintf(stderr, "process_leaf_dir_block found .. entry (parent) = %llu\n", lino); |
| #endif |
| /* |
| * what if .. == .? legal only in |
| * the root inode. blow out entry |
| * and set parent to NULLFSINO otherwise. |
| */ |
| if (ino == lino && |
| ino != mp->m_sb.sb_rootino) { |
| *parent = NULLFSINO; |
| do_warn( |
| _("bad .. entry in dir ino %llu, points to self"), |
| ino); |
| if (!no_modify) { |
| do_warn( |
| _("will clear entry\n")); |
| namest->name[0] = '/'; |
| *buf_dirty = 1; |
| } else { |
| do_warn( |
| _("would clear entry\n")); |
| } |
| } else if (ino != lino && |
| ino == mp->m_sb.sb_rootino) { |
| /* |
| * we have to make sure that . == .. |
| * in the root inode |
| */ |
| if (!no_modify) { |
| do_warn( |
| _("correcting .. entry in root inode %llu, was %llu\n"), |
| ino, *parent); |
| xfs_dir_sf_put_dirino( |
| &ino, &namest->inumber); |
| *buf_dirty = 1; |
| } else { |
| do_warn( |
| _("bad .. entry (%llu) in root inode %llu should be %llu\n"), |
| *parent, |
| ino, ino); |
| } |
| } |
| } else { |
| /* |
| * can't fix the directory unless we know |
| * which .. entry is the right one. Both |
| * have valid inode numbers, match the hash |
| * value and the hash values are ordered |
| * properly or we wouldn't be here. So |
| * since both seem equally valid, trash |
| * this one. |
| */ |
| if (!no_modify) { |
| do_warn( |
| _("multiple .. entries in directory inode %llu, will clear second entry\n"), |
| ino); |
| namest->name[0] = '/'; |
| *buf_dirty = 1; |
| } else { |
| do_warn( |
| _("multiple .. entries in directory inode %llu, would clear second entry\n"), |
| ino); |
| } |
| } |
| } else if (entry->namelen == 1 && namest->name[0] == '.') { |
| /* |
| * the '.' case |
| */ |
| if (!*dot) { |
| (*dot)++; |
| if (lino != ino) { |
| if (!no_modify) { |
| do_warn( |
| _(". in directory inode %llu has wrong value (%llu), fixing entry...\n"), |
| ino, lino); |
| xfs_dir_sf_put_dirino(&ino, |
| &namest->inumber); |
| *buf_dirty = 1; |
| } else { |
| do_warn( |
| _(". in directory inode %llu has wrong value (%llu)\n"), |
| ino, lino); |
| } |
| } |
| } else { |
| do_warn( |
| _("multiple . entries in directory inode %llu\n"), |
| ino); |
| /* |
| * mark entry as to be junked. |
| */ |
| if (!no_modify) { |
| do_warn( |
| _("will clear one . entry in directory inode %llu\n"), |
| ino); |
| namest->name[0] = '/'; |
| *buf_dirty = 1; |
| } else { |
| do_warn( |
| _("would clear one . entry in directory inode %llu\n"), |
| ino); |
| } |
| } |
| } else { |
| /* |
| * all the rest -- make sure only . references self |
| */ |
| if (lino == ino) { |
| do_warn( |
| _("entry \"%s\" in directory inode %llu points to self, "), |
| fname, ino); |
| if (!no_modify) { |
| do_warn(_("will clear entry\n")); |
| namest->name[0] = '/'; |
| *buf_dirty = 1; |
| } else { |
| do_warn(_("would clear entry\n")); |
| } |
| } |
| } |
| } |
| |
| /* |
| * compare top of heap values and reset as required. if the |
| * holes flag is set, don't reset first_used unless it's |
| * pointing to used bytes. we're being conservative here |
| * since the block will get compacted anyhow by the kernel. |
| */ |
| if ((leaf->hdr.holes == 0 && |
| first_used != be16_to_cpu(leaf->hdr.firstused)) || |
| be16_to_cpu(leaf->hdr.firstused) > first_used) { |
| if (!no_modify) { |
| if (verbose) |
| do_warn( |
| _("- resetting first used heap value from %d to %d in block %u of dir ino %llu\n"), |
| be16_to_cpu(leaf->hdr.firstused), |
| first_used, da_bno, ino); |
| leaf->hdr.firstused = cpu_to_be16(first_used); |
| *buf_dirty = 1; |
| } else { |
| if (verbose) |
| do_warn( |
| _("- would reset first used value from %d to %d in block %u of dir ino %llu\n"), |
| be16_to_cpu(leaf->hdr.firstused), |
| first_used, da_bno, ino); |
| } |
| } |
| |
| if (bytes_used != be16_to_cpu(leaf->hdr.namebytes)) { |
| if (!no_modify) { |
| if (verbose) |
| do_warn( |
| _("- resetting namebytes cnt from %d to %d in block %u of dir inode %llu\n"), |
| be16_to_cpu(leaf->hdr.namebytes), |
| bytes_used, da_bno, ino); |
| leaf->hdr.namebytes = cpu_to_be16(bytes_used); |
| *buf_dirty = 1; |
| } else { |
| if (verbose) |
| do_warn( |
| _("- would reset namebytes cnt from %d to %d in block %u of dir inode %llu\n"), |
| be16_to_cpu(leaf->hdr.namebytes), |
| bytes_used, da_bno, ino); |
| } |
| } |
| |
| /* |
| * If the hole flag is not set, then we know that there can |
| * be no lost holes. If the hole flag is set, then it's ok |
| * if the on-disk holemap doesn't describe everything as long |
| * as what it does describe doesn't conflict with reality. |
| */ |
| |
| reset_holes = 0; |
| |
| bholemap.lost_holes = leaf->hdr.holes; |
| for (i = 0; i < XFS_DIR_LEAF_MAPSIZE; i++) { |
| bholemap.hentries[i].base = be16_to_cpu(leaf->hdr.freemap[i].base); |
| bholemap.hentries[i].size = be16_to_cpu(leaf->hdr.freemap[i].size); |
| } |
| |
| /* |
| * Ok, now set up our own freespace list |
| * (XFS_DIR_LEAF_MAPSIZE (3) * biggest regions) |
| * and see if they match what's in the block |
| */ |
| memset(&holemap, 0, sizeof(da_hole_map_t)); |
| process_da_freemap(mp, dir_freemap, &holemap); |
| |
| if (zero_len_entries) { |
| reset_holes = 1; |
| } else if (leaf->hdr.holes == 0) { |
| if (holemap.lost_holes > 0) { |
| if (verbose) |
| do_warn( |
| _("- found unexpected lost holes in block %u, dir inode %llu\n"), |
| da_bno, ino); |
| |
| reset_holes = 1; |
| } else if (compare_da_freemaps(mp, &holemap, &bholemap, |
| XFS_DIR_LEAF_MAPSIZE, ino, da_bno)) { |
| if (verbose) |
| do_warn( |
| _("- hole info non-optimal in block %u, dir inode %llu\n"), |
| da_bno, ino); |
| reset_holes = 1; |
| } |
| } else if (verify_da_freemap(mp, dir_freemap, &holemap, ino, da_bno)) { |
| if (verbose) |
| do_warn( |
| _("- hole info incorrect in block %u, dir inode %llu\n"), |
| da_bno, ino); |
| reset_holes = 1; |
| } |
| |
| if (reset_holes) { |
| /* |
| * have to reset block hole info |
| */ |
| if (verbose) { |
| do_warn( |
| _("- existing hole info for block %d, dir inode %llu (base, size) - \n"), |
| da_bno, ino); |
| do_warn("- \t"); |
| for (i = 0; i < XFS_DIR_LEAF_MAPSIZE; i++) { |
| do_warn( |
| "- (%d, %d) ", bholemap.hentries[i].base, |
| bholemap.hentries[i].size); |
| } |
| do_warn(_("- holes flag = %d\n"), bholemap.lost_holes); |
| } |
| |
| if (!no_modify) { |
| if (verbose) |
| do_warn( |
| _("- compacting block %u in dir inode %llu\n"), |
| da_bno, ino); |
| |
| new_leaf = (xfs_dir_leafblock_t *) ts_dirbuf(); |
| |
| /* |
| * copy leaf block header |
| */ |
| memmove(&new_leaf->hdr, &leaf->hdr, |
| sizeof(xfs_dir_leaf_hdr_t)); |
| |
| /* |
| * reset count in case we have some zero length entries |
| * that are being junked |
| */ |
| num_entries = 0; |
| first_used = XFS_LBSIZE(mp); |
| first_byte = (char *) new_leaf |
| + (__psint_t) XFS_LBSIZE(mp); |
| |
| /* |
| * copy entry table and pack names starting from the end |
| * of the block |
| */ |
| for (i = 0, s_entry = &leaf->entries[0], |
| d_entry = &new_leaf->entries[0]; |
| i < be16_to_cpu(leaf->hdr.count); |
| i++, s_entry++) { |
| /* |
| * skip zero-length entries |
| */ |
| if (s_entry->namelen == 0) |
| continue; |
| |
| bytes = sizeof(xfs_dir_leaf_name_t) |
| + s_entry->namelen - 1; |
| |
| if ((__psint_t) first_byte - bytes < |
| sizeof(xfs_dir_leaf_entry_t) |
| + (__psint_t) d_entry) { |
| do_warn( |
| _("not enough space in block %u of dir inode %llu for all entries\n"), |
| da_bno, ino); |
| break; |
| } |
| |
| first_used -= bytes; |
| first_byte -= bytes; |
| |
| d_entry->nameidx = cpu_to_be16(first_used); |
| d_entry->hashval = s_entry->hashval; |
| d_entry->namelen = s_entry->namelen; |
| d_entry->pad2 = 0; |
| |
| memmove(first_byte, (char *)leaf + |
| be16_to_cpu(s_entry->nameidx), bytes); |
| |
| num_entries++; |
| d_entry++; |
| } |
| |
| ASSERT((char *) first_byte >= (char *) d_entry); |
| ASSERT(first_used <= XFS_LBSIZE(mp)); |
| |
| /* |
| * zero space between end of table and top of heap |
| */ |
| memset(d_entry, 0, (__psint_t) first_byte |
| - (__psint_t) d_entry); |
| |
| /* |
| * reset header info |
| */ |
| if (num_entries != be16_to_cpu(new_leaf->hdr.count)) |
| new_leaf->hdr.count = cpu_to_be16(num_entries); |
| |
| new_leaf->hdr.firstused = cpu_to_be16(first_used); |
| new_leaf->hdr.holes = 0; |
| new_leaf->hdr.pad1 = 0; |
| |
| new_leaf->hdr.freemap[0].base = cpu_to_be16( |
| (__psint_t) d_entry - (__psint_t) new_leaf); |
| new_leaf->hdr.freemap[0].size = cpu_to_be16( |
| (__psint_t) first_byte - (__psint_t) d_entry); |
| |
| ASSERT(be16_to_cpu(new_leaf->hdr.freemap[0].base) < first_used); |
| ASSERT(be16_to_cpu(new_leaf->hdr.freemap[0].base) == |
| (__psint_t) (&new_leaf->entries[0]) |
| - (__psint_t) new_leaf |
| + i * sizeof(xfs_dir_leaf_entry_t)); |
| ASSERT(be16_to_cpu(new_leaf->hdr.freemap[0].base) < XFS_LBSIZE(mp)); |
| ASSERT(be16_to_cpu(new_leaf->hdr.freemap[0].size) < XFS_LBSIZE(mp)); |
| ASSERT(be16_to_cpu(new_leaf->hdr.freemap[0].base) + |
| be16_to_cpu(new_leaf->hdr.freemap[0].size) == first_used); |
| |
| new_leaf->hdr.freemap[1].base = 0; |
| new_leaf->hdr.freemap[1].size = 0; |
| new_leaf->hdr.freemap[2].base = 0; |
| new_leaf->hdr.freemap[2].size = 0; |
| |
| /* |
| * final step, copy block back |
| */ |
| memmove(leaf, new_leaf, mp->m_sb.sb_blocksize); |
| |
| *buf_dirty = 1; |
| } else { |
| if (verbose) |
| do_warn( |
| _("- would compact block %u in dir inode %llu\n"), |
| da_bno, ino); |
| } |
| } |
| #if 0 |
| if (!no_modify) { |
| /* |
| * now take care of deleting or marking the entries with |
| * zero-length namelen's |
| */ |
| junk_zerolen_dir_leaf_entries(mp, leaf, ino, buf_dirty); |
| } |
| #endif |
| #ifdef XR_DIR_TRACE |
| fprintf(stderr, "process_leaf_dir_block returns %d\n", res); |
| #endif |
| return((res > 0) ? 1 : 0); |
| } |
| |
| /* |
| * returns 0 if the directory is ok, 1 if it has to be junked. |
| */ |
| int |
| process_leaf_dir_level(xfs_mount_t *mp, |
| da_bt_cursor_t *da_cursor, |
| int ino_discovery, |
| int *repair, |
| int *dot, |
| int *dotdot, |
| xfs_ino_t *parent) |
| { |
| xfs_dir_leafblock_t *leaf; |
| xfs_buf_t *bp; |
| xfs_ino_t ino; |
| xfs_dfsbno_t dev_bno; |
| xfs_dablk_t da_bno; |
| xfs_dablk_t prev_bno; |
| int res = 0; |
| int buf_dirty = 0; |
| xfs_daddr_t bd_addr; |
| xfs_dahash_t current_hashval = 0; |
| xfs_dahash_t greatest_hashval; |
| |
| #ifdef XR_DIR_TRACE |
| fprintf(stderr, "process_leaf_dir_level - ino %llu\n", da_cursor->ino); |
| #endif |
| *repair = 0; |
| da_bno = da_cursor->level[0].bno; |
| ino = da_cursor->ino; |
| prev_bno = 0; |
| |
| do { |
| dev_bno = blkmap_get(da_cursor->blkmap, da_bno); |
| /* |
| * directory code uses 0 as the NULL block pointer |
| * since 0 is the root block and no directory block |
| * pointer can point to the root block of the btree |
| */ |
| ASSERT(da_bno != 0); |
| |
| if (dev_bno == NULLDFSBNO) { |
| do_warn( |
| _("can't map block %u for directory inode %llu\n"), |
| da_bno, ino); |
| goto error_out; |
| } |
| |
| bd_addr = (xfs_daddr_t)XFS_FSB_TO_DADDR(mp, dev_bno); |
| |
| bp = libxfs_readbuf(mp->m_dev, XFS_FSB_TO_DADDR(mp, dev_bno), |
| XFS_FSB_TO_BB(mp, 1), 0); |
| if (!bp) { |
| do_warn( |
| _("can't read file block %u (fsbno %llu, daddr %lld) " |
| "for directory inode %llu\n"), |
| da_bno, dev_bno, (__int64_t) bd_addr, ino); |
| goto error_out; |
| } |
| |
| leaf = (xfs_dir_leafblock_t *)XFS_BUF_PTR(bp); |
| |
| /* |
| * check magic number for leaf directory btree block |
| */ |
| if (XFS_DIR_LEAF_MAGIC != |
| be16_to_cpu(leaf->hdr.info.magic)) { |
| do_warn( |
| _("bad directory leaf magic # %#x for dir ino %llu\n"), |
| be16_to_cpu(leaf->hdr.info.magic), |
| ino); |
| libxfs_putbuf(bp); |
| goto error_out; |
| } |
| /* |
| * keep track of greatest block # -- that gets |
| * us the length of the directory |
| */ |
| if (da_bno > da_cursor->greatest_bno) |
| da_cursor->greatest_bno = da_bno; |
| |
| buf_dirty = 0; |
| /* |
| * for each block, process the block, verify its path, |
| * then get next block. update cursor values along the way |
| */ |
| if (process_leaf_dir_block(mp, leaf, da_bno, ino, |
| current_hashval, ino_discovery, |
| da_cursor->blkmap, dot, dotdot, parent, |
| &buf_dirty, &greatest_hashval)) { |
| libxfs_putbuf(bp); |
| goto error_out; |
| } |
| |
| /* |
| * index can be set to hdr.count so match the |
| * indexes of the interior blocks -- which at the |
| * end of the block will point to 1 after the final |
| * real entry in the block |
| */ |
| da_cursor->level[0].hashval = greatest_hashval; |
| da_cursor->level[0].bp = bp; |
| da_cursor->level[0].bno = da_bno; |
| da_cursor->level[0].index = be16_to_cpu(leaf->hdr.count); |
| da_cursor->level[0].dirty = buf_dirty; |
| |
| if (be32_to_cpu(leaf->hdr.info.back) != prev_bno) { |
| do_warn(_("bad sibling back pointer for directory " |
| "block %u in directory inode %llu\n"), |
| da_bno, ino); |
| libxfs_putbuf(bp); |
| goto error_out; |
| } |
| |
| prev_bno = da_bno; |
| da_bno = be32_to_cpu(leaf->hdr.info.forw); |
| |
| if (da_bno != 0) |
| if (verify_da_path(mp, da_cursor, 0)) { |
| libxfs_putbuf(bp); |
| goto error_out; |
| } |
| |
| current_hashval = greatest_hashval; |
| |
| ASSERT(buf_dirty == 0 || (buf_dirty && !no_modify)); |
| |
| if (buf_dirty && !no_modify) { |
| *repair = 1; |
| libxfs_writebuf(bp, 0); |
| } |
| else |
| libxfs_putbuf(bp); |
| } while (da_bno != 0 && res == 0); |
| |
| if (verify_final_da_path(mp, da_cursor, 0)) { |
| /* |
| * verify the final path up (right-hand-side) if still ok |
| */ |
| do_warn(_("bad hash path in directory %llu\n"), da_cursor->ino); |
| goto error_out; |
| } |
| |
| #ifdef XR_DIR_TRACE |
| fprintf(stderr, "process_leaf_dir_level returns %d (%s)\n", |
| res, ((res) ? "bad" : "ok")); |
| #endif |
| /* |
| * redundant but just for testing |
| */ |
| release_da_cursor(mp, da_cursor, 0); |
| |
| return(res); |
| |
| error_out: |
| /* |
| * release all buffers holding interior btree blocks |
| */ |
| err_release_da_cursor(mp, da_cursor, 0); |
| |
| return(1); |
| } |
| |
| /* |
| * a node directory is a true btree directory -- where the directory |
| * has gotten big enough that it is represented as a non-trivial (e.g. |
| * has more than just a root block) btree. |
| * |
| * Note that if we run into any problems, we trash the |
| * directory. Even if it's the root directory, |
| * we'll be able to traverse all the disconnected |
| * subtrees later (phase 6). |
| * |
| * one day, if we actually fix things, we'll set repair to 1 to |
| * indicate that we have or that we should. |
| * |
| * dirname can be set to NULL if the name is unknown (or to |
| * the string representation of the inode) |
| * |
| * returns 0 if things are ok, 1 if bad (directory needs to be junked) |
| */ |
| /* ARGSUSED */ |
| int |
| process_node_dir( |
| xfs_mount_t *mp, |
| xfs_ino_t ino, |
| xfs_dinode_t *dip, |
| int ino_discovery, |
| blkmap_t *blkmap, |
| int *dot, |
| int *dotdot, |
| xfs_ino_t *parent, /* out - parent ino # or NULLFSINO */ |
| char *dirname, |
| int *repair) |
| { |
| xfs_dablk_t bno; |
| int error = 0; |
| da_bt_cursor_t da_cursor; |
| |
| #ifdef XR_DIR_TRACE |
| fprintf(stderr, "process_node_dir - ino %llu\n", ino); |
| #endif |
| *repair = *dot = *dotdot = 0; |
| *parent = NULLFSINO; |
| |
| /* |
| * try again -- traverse down left-side of tree until we hit |
| * the left-most leaf block setting up the btree cursor along |
| * the way. Then walk the leaf blocks left-to-right, calling |
| * a parent-verification routine each time we traverse a block. |
| */ |
| memset(&da_cursor, 0, sizeof(da_bt_cursor_t)); |
| |
| da_cursor.active = 0; |
| da_cursor.type = 0; |
| da_cursor.ino = ino; |
| da_cursor.dip = dip; |
| da_cursor.greatest_bno = 0; |
| da_cursor.blkmap = blkmap; |
| |
| /* |
| * now process interior node |
| */ |
| |
| error = traverse_int_dablock(mp, &da_cursor, &bno, XFS_DATA_FORK); |
| |
| if (error == 0) |
| return(1); |
| |
| /* |
| * now pass cursor and bno into leaf-block processing routine |
| * the leaf dir level routine checks the interior paths |
| * up to the root including the final right-most path. |
| */ |
| |
| error = process_leaf_dir_level(mp, &da_cursor, ino_discovery, |
| repair, dot, dotdot, parent); |
| |
| if (error) |
| return(1); |
| |
| /* |
| * sanity check inode size |
| */ |
| if (be64_to_cpu(dip->di_core.di_size) < |
| (da_cursor.greatest_bno + 1) * mp->m_sb.sb_blocksize) { |
| if ((xfs_fsize_t) da_cursor.greatest_bno |
| * mp->m_sb.sb_blocksize > UINT_MAX) { |
| do_warn( |
| _("out of range internal directory block numbers (inode %llu)\n"), |
| ino); |
| return(1); |
| } |
| |
| do_warn( |
| _("setting directory inode (%llu) size to %llu bytes, was %lld bytes\n"), |
| ino, (xfs_dfiloff_t) (da_cursor.greatest_bno + 1) |
| * mp->m_sb.sb_blocksize, |
| be64_to_cpu(dip->di_core.di_size)); |
| |
| dip->di_core.di_size = cpu_to_be64((da_cursor.greatest_bno + 1) |
| * mp->m_sb.sb_blocksize); |
| } |
| return(0); |
| } |
| |
| /* |
| * a leaf directory is one where the directory is too big for |
| * the inode data fork but is small enough to fit into one |
| * directory btree block (filesystem block) outside the inode |
| * |
| * returns NULLFSINO if the directory is cannot be salvaged |
| * and the .. ino if things are ok (even if the directory had |
| * to be altered to make it ok). |
| * |
| * dirname can be set to NULL if the name is unknown (or to |
| * the string representation of the inode) |
| * |
| * returns 0 if things are ok, 1 if bad (directory needs to be junked) |
| */ |
| /* ARGSUSED */ |
| int |
| process_leaf_dir( |
| xfs_mount_t *mp, |
| xfs_ino_t ino, |
| xfs_dinode_t *dip, |
| int ino_discovery, |
| int *dino_dirty, |
| blkmap_t *blkmap, |
| int *dot, /* out - 1 if there is a dot, else 0 */ |
| int *dotdot, /* out - 1 if there's a dotdot, else 0 */ |
| xfs_ino_t *parent, /* out - parent ino # or NULLFSINO */ |
| char *dirname, /* in - directory pathname */ |
| int *repair) /* out - 1 if something was fixed */ |
| { |
| xfs_dir_leafblock_t *leaf; |
| xfs_dahash_t next_hashval; |
| xfs_dfsbno_t bno; |
| xfs_buf_t *bp; |
| int buf_dirty = 0; |
| |
| #ifdef XR_DIR_TRACE |
| fprintf(stderr, "process_leaf_dir - ino %llu\n", ino); |
| #endif |
| *repair = *dot = *dotdot = 0; |
| *parent = NULLFSINO; |
| |
| bno = blkmap_get(blkmap, 0); |
| if (bno == NULLDFSBNO) { |
| do_warn(_("block 0 for directory inode %llu is missing\n"), |
| ino); |
| return(1); |
| } |
| bp = libxfs_readbuf(mp->m_dev, XFS_FSB_TO_DADDR(mp, bno), |
| XFS_FSB_TO_BB(mp, 1), 0); |
| if (!bp) { |
| do_warn(_("can't read block 0 for directory inode %llu\n"), |
| ino); |
| return(1); |
| } |
| /* |
| * verify leaf block |
| */ |
| leaf = (xfs_dir_leafblock_t *)XFS_BUF_PTR(bp); |
| |
| /* |
| * check magic number for leaf directory btree block |
| */ |
| if (be16_to_cpu(leaf->hdr.info.magic) != XFS_DIR_LEAF_MAGIC) { |
| do_warn(_("bad directory leaf magic # %#x for dir ino %llu\n"), |
| be16_to_cpu(leaf->hdr.info.magic), ino); |
| libxfs_putbuf(bp); |
| return(1); |
| } |
| |
| if (process_leaf_dir_block(mp, leaf, 0, ino, 0, ino_discovery, blkmap, |
| dot, dotdot, parent, &buf_dirty, &next_hashval)) { |
| /* |
| * the block is bad. lose the directory. |
| * XXX - later, we should try and just lose |
| * the block without losing the entire directory |
| */ |
| ASSERT(*dotdot == 0 || (*dotdot == 1 && *parent != NULLFSINO)); |
| libxfs_putbuf(bp); |
| return(1); |
| } |
| |
| /* |
| * check sibling pointers in leaf block (above doesn't do it) |
| */ |
| if (leaf->hdr.info.forw || leaf->hdr.info.back) { |
| if (!no_modify) { |
| do_warn(_("clearing forw/back pointers for " |
| "directory inode %llu\n"), ino); |
| buf_dirty = 1; |
| leaf->hdr.info.forw = 0; |
| leaf->hdr.info.back = 0; |
| } else { |
| do_warn(_("would clear forw/back pointers for " |
| "directory inode %llu\n"), ino); |
| } |
| } |
| |
| ASSERT(buf_dirty == 0 || (buf_dirty && !no_modify)); |
| |
| if (buf_dirty && !no_modify) |
| libxfs_writebuf(bp, 0); |
| else |
| libxfs_putbuf(bp); |
| |
| return(0); |
| } |
| |
| /* |
| * returns 1 if things are bad (directory needs to be junked) |
| * and 0 if things are ok. If ino_discovery is 1, add unknown |
| * inodes to uncertain inode list. |
| */ |
| int |
| process_dir( |
| xfs_mount_t *mp, |
| xfs_ino_t ino, |
| xfs_dinode_t *dip, |
| int ino_discovery, |
| int *dino_dirty, |
| char *dirname, |
| xfs_ino_t *parent, |
| blkmap_t *blkmap) |
| { |
| int dot; |
| int dotdot; |
| int repair = 0; |
| int res = 0; |
| |
| *parent = NULLFSINO; |
| dot = dotdot = 0; |
| |
| /* |
| * branch off depending on the type of inode. This routine |
| * is only called ONCE so all the subordinate routines will |
| * fix '.' and junk '..' if they're bogus. |
| */ |
| if (be64_to_cpu(dip->di_core.di_size) <= XFS_DFORK_DSIZE(dip, mp)) { |
| dot = 1; |
| dotdot = 1; |
| if (process_shortform_dir(mp, ino, dip, ino_discovery, |
| dino_dirty, parent, dirname, &repair)) |
| res = 1; |
| } else if (be64_to_cpu(dip->di_core.di_size) <= XFS_LBSIZE(mp)) { |
| if (process_leaf_dir(mp, ino, dip, ino_discovery, |
| dino_dirty, blkmap, &dot, &dotdot, |
| parent, dirname, &repair)) |
| res = 1; |
| } else { |
| if (process_node_dir(mp, ino, dip, ino_discovery, |
| blkmap, &dot, &dotdot, |
| parent, dirname, &repair)) |
| res = 1; |
| } |
| /* |
| * bad . entries in all directories will be fixed up in phase 6 |
| */ |
| if (dot == 0) |
| do_warn(_("no . entry for directory %llu\n"), ino); |
| |
| /* |
| * shortform dirs always have a .. entry. .. for all longform |
| * directories will get fixed in phase 6. .. for other shortform |
| * dirs also get fixed there. .. for a shortform root was |
| * fixed in place since we know what it should be |
| */ |
| if (dotdot == 0 && ino != mp->m_sb.sb_rootino) { |
| do_warn(_("no .. entry for directory %llu\n"), ino); |
| } else if (dotdot == 0 && ino == mp->m_sb.sb_rootino) { |
| do_warn(_("no .. entry for root directory %llu\n"), ino); |
| need_root_dotdot = 1; |
| } |
| |
| #ifdef XR_DIR_TRACE |
| fprintf(stderr, "(process_dir), parent of %llu is %llu\n", ino, parent); |
| #endif |
| return(res); |
| } |