|  | // SPDX-License-Identifier: GPL-2.0 | 
|  | /* | 
|  | * Copyright (c) 2000-2002,2005 Silicon Graphics, Inc. | 
|  | * All Rights Reserved. | 
|  | */ | 
|  |  | 
|  | #include "libxfs.h" | 
|  | #include "avl.h" | 
|  | #include "globals.h" | 
|  | #include "incore.h" | 
|  | #include "err_protos.h" | 
|  | #include "dinode.h" | 
|  | #include "dir2.h" | 
|  | #include "bmap.h" | 
|  | #include "da_util.h" | 
|  | #include "prefetch.h" | 
|  | #include "progress.h" | 
|  | #include "slab.h" | 
|  | #include "rmap.h" | 
|  | #include "rt.h" | 
|  |  | 
|  | /* | 
|  | * Known bad inode list.  These are seen when the leaf and node | 
|  | * block linkages are incorrect. | 
|  | */ | 
|  | struct dir2_bad { | 
|  | xfs_ino_t	ino; | 
|  | struct dir2_bad	*next; | 
|  | }; | 
|  |  | 
|  | static struct dir2_bad	*dir2_bad_list; | 
|  | pthread_mutex_t		dir2_bad_list_lock = PTHREAD_MUTEX_INITIALIZER; | 
|  |  | 
|  | static void | 
|  | dir2_add_badlist( | 
|  | xfs_ino_t	ino) | 
|  | { | 
|  | struct dir2_bad	*l; | 
|  |  | 
|  | l = malloc(sizeof(*l)); | 
|  | if (!l) { | 
|  | do_error( | 
|  | _("malloc failed (%zu bytes) dir2_add_badlist:ino %" PRIu64 "\n"), | 
|  | sizeof(*l), ino); | 
|  | exit(1); | 
|  | } | 
|  | pthread_mutex_lock(&dir2_bad_list_lock); | 
|  | l->next = dir2_bad_list; | 
|  | dir2_bad_list = l; | 
|  | l->ino = ino; | 
|  | pthread_mutex_unlock(&dir2_bad_list_lock); | 
|  | } | 
|  |  | 
|  | bool | 
|  | dir2_is_badino( | 
|  | xfs_ino_t	ino) | 
|  | { | 
|  | struct dir2_bad	*l; | 
|  | bool		ret = false; | 
|  |  | 
|  | pthread_mutex_lock(&dir2_bad_list_lock); | 
|  | for (l = dir2_bad_list; l; l = l->next) { | 
|  | if (l->ino == ino) { | 
|  | ret = true; | 
|  | break; | 
|  | } | 
|  | } | 
|  | pthread_mutex_unlock(&dir2_bad_list_lock); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Fix up a shortform directory which was in long form (i8count set) | 
|  | * and is now in short form (i8count clear). | 
|  | * Return pointer to the end of the data when done. | 
|  | */ | 
|  | void | 
|  | process_sf_dir2_fixi8( | 
|  | struct xfs_mount	*mp, | 
|  | struct xfs_dir2_sf_hdr	*sfp, | 
|  | xfs_dir2_sf_entry_t	**next_sfep) | 
|  | { | 
|  | xfs_ino_t		ino; | 
|  | struct xfs_dir2_sf_hdr	*newsfp; | 
|  | xfs_dir2_sf_entry_t	*newsfep; | 
|  | struct xfs_dir2_sf_hdr	*oldsfp; | 
|  | xfs_dir2_sf_entry_t	*oldsfep; | 
|  | int			oldsize; | 
|  |  | 
|  | newsfp = sfp; | 
|  | oldsize = (intptr_t)*next_sfep - (intptr_t)sfp; | 
|  | oldsfp = malloc(oldsize); | 
|  | if (oldsfp == NULL) { | 
|  | do_error(_("couldn't malloc dir2 shortform copy\n")); | 
|  | exit(1); | 
|  | } | 
|  | memmove(oldsfp, newsfp, oldsize); | 
|  | newsfp->count = oldsfp->count; | 
|  | newsfp->i8count = 0; | 
|  | ino = libxfs_dir2_sf_get_parent_ino(oldsfp); | 
|  | libxfs_dir2_sf_put_parent_ino(newsfp, ino); | 
|  | oldsfep = xfs_dir2_sf_firstentry(oldsfp); | 
|  | newsfep = xfs_dir2_sf_firstentry(newsfp); | 
|  | while ((int)((char *)oldsfep - (char *)oldsfp) < oldsize) { | 
|  | newsfep->namelen = oldsfep->namelen; | 
|  | xfs_dir2_sf_put_offset(newsfep, | 
|  | xfs_dir2_sf_get_offset(oldsfep)); | 
|  | memmove(newsfep->name, oldsfep->name, newsfep->namelen); | 
|  | ino = libxfs_dir2_sf_get_ino(mp, oldsfp, oldsfep); | 
|  | libxfs_dir2_sf_put_ino(mp, newsfp, newsfep, ino); | 
|  | oldsfep = libxfs_dir2_sf_nextentry(mp, oldsfp, oldsfep); | 
|  | newsfep = libxfs_dir2_sf_nextentry(mp, newsfp, newsfep); | 
|  | } | 
|  | *next_sfep = newsfep; | 
|  | free(oldsfp); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Regenerate legal (minimal) offsets for the shortform directory. | 
|  | */ | 
|  | static void | 
|  | process_sf_dir2_fixoff( | 
|  | xfs_mount_t		*mp, | 
|  | struct xfs_dinode	*dip) | 
|  | { | 
|  | int			i; | 
|  | int			offset; | 
|  | xfs_dir2_sf_entry_t	*sfep; | 
|  | struct xfs_dir2_sf_hdr	*sfp; | 
|  |  | 
|  | sfp = (struct xfs_dir2_sf_hdr *)XFS_DFORK_DPTR(dip); | 
|  | sfep = xfs_dir2_sf_firstentry(sfp); | 
|  | offset = mp->m_dir_geo->data_first_offset; | 
|  |  | 
|  | for (i = 0; i < sfp->count; i++) { | 
|  | xfs_dir2_sf_put_offset(sfep, offset); | 
|  | offset += libxfs_dir2_data_entsize(mp, sfep->namelen); | 
|  | sfep = libxfs_dir2_sf_nextentry(mp, sfp, sfep); | 
|  | } | 
|  | } | 
|  |  | 
|  | static inline bool | 
|  | is_metadata_directory( | 
|  | struct xfs_mount	*mp, | 
|  | struct xfs_dinode	*dip) | 
|  | { | 
|  | xfs_failaddr_t		fa; | 
|  | uint16_t		mode; | 
|  | uint16_t		flags; | 
|  | uint64_t		flags2; | 
|  |  | 
|  | if (!xfs_has_metadir(mp)) | 
|  | return false; | 
|  | if (dip->di_version < 3 || | 
|  | !(dip->di_flags2 & cpu_to_be64(XFS_DIFLAG2_METADIR))) | 
|  | return false; | 
|  |  | 
|  | mode = be16_to_cpu(dip->di_mode); | 
|  | flags = be16_to_cpu(dip->di_flags); | 
|  | flags2 = be64_to_cpu(dip->di_flags2); | 
|  | fa = libxfs_dinode_verify_metadir(mp, dip, mode, flags, flags2); | 
|  | return fa == NULL; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * this routine performs inode discovery and tries to fix things | 
|  | * in place.  available redundancy -- inode data size should match | 
|  | * used directory space in inode. | 
|  | * a non-zero return value means the directory is bogus and should be blasted. | 
|  | */ | 
|  | /* ARGSUSED */ | 
|  | static int | 
|  | process_sf_dir2( | 
|  | xfs_mount_t		*mp, | 
|  | xfs_ino_t		ino, | 
|  | struct xfs_dinode	*dip, | 
|  | int			ino_discovery, | 
|  | int			*dino_dirty, /* out: 1 if dinode buffer dirty */ | 
|  | char			*dirname,    /* directory pathname */ | 
|  | xfs_ino_t		*parent,     /* out: NULLFSINO if no entry */ | 
|  | int			*repair)     /* out: 1 if dir was fixed up */ | 
|  | { | 
|  | int			bad_offset; | 
|  | int			bad_sfnamelen; | 
|  | int			i; | 
|  | int			i8; | 
|  | int64_t			ino_dir_size; | 
|  | int			ino_off; | 
|  | ino_tree_node_t		*irec_p; | 
|  | int			junkit; | 
|  | char			*junkreason = NULL; | 
|  | xfs_ino_t		lino; | 
|  | int			max_size; | 
|  | char			name[MAXNAMELEN + 1]; | 
|  | int			namelen; | 
|  | xfs_dir2_sf_entry_t	*next_sfep; | 
|  | int			num_entries; | 
|  | int			offset; | 
|  | struct xfs_dir2_sf_hdr	*sfp; | 
|  | xfs_dir2_sf_entry_t	*sfep; | 
|  | int			tmp_elen; | 
|  | int			tmp_len; | 
|  | xfs_dir2_sf_entry_t	*tmp_sfep; | 
|  |  | 
|  | sfp = (struct xfs_dir2_sf_hdr *)XFS_DFORK_DPTR(dip); | 
|  | max_size = XFS_DFORK_DSIZE(dip, mp); | 
|  | num_entries = sfp->count; | 
|  | ino_dir_size = be64_to_cpu(dip->di_size); | 
|  | offset = mp->m_dir_geo->data_first_offset; | 
|  | bad_offset = *repair = 0; | 
|  |  | 
|  | ASSERT(ino_dir_size <= max_size); | 
|  |  | 
|  | /* | 
|  | * Initialize i8 based on size of parent inode number. | 
|  | */ | 
|  | i8 = (libxfs_dir2_sf_get_parent_ino(sfp) > XFS_DIR2_MAX_SHORT_INUM); | 
|  |  | 
|  | /* | 
|  | * check for bad entry count | 
|  | */ | 
|  | if (num_entries * libxfs_dir2_sf_entsize(mp, sfp, 1) + | 
|  | xfs_dir2_sf_hdr_size(0) > 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 | 
|  | */ | 
|  | next_sfep = xfs_dir2_sf_firstentry(sfp); | 
|  | for (i = 0; | 
|  | i < num_entries && ino_dir_size > (char *)next_sfep - (char *)sfp; | 
|  | i++) { | 
|  | tmp_sfep = NULL; | 
|  | sfep = next_sfep; | 
|  | junkit = 0; | 
|  | bad_sfnamelen = 0; | 
|  | lino = libxfs_dir2_sf_get_ino(mp, sfp, sfep); | 
|  | /* | 
|  | * 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; | 
|  | junkreason = _("current"); | 
|  | } else if (!libxfs_verify_dir_ino(mp, lino)) { | 
|  | junkit = 1; | 
|  | junkreason = _("invalid"); | 
|  | } else if (is_metadata_directory(mp, dip)) { | 
|  | /* | 
|  | * Metadata directories are always rebuilt, so don't | 
|  | * bother checking if the child inode is free or not. | 
|  | */ | 
|  | junkit = 0; | 
|  | } else if (lino == mp->m_sb.sb_rbmino)  { | 
|  | junkit = 1; | 
|  | junkreason = _("realtime bitmap"); | 
|  | } else if (lino == mp->m_sb.sb_rsumino)  { | 
|  | junkit = 1; | 
|  | junkreason = _("realtime summary"); | 
|  | } else if (lino == mp->m_sb.sb_uquotino)  { | 
|  | junkit = 1; | 
|  | junkreason = _("user quota"); | 
|  | } else if (lino == mp->m_sb.sb_gquotino)  { | 
|  | junkit = 1; | 
|  | junkreason = _("group quota"); | 
|  | } else if (lino == mp->m_sb.sb_pquotino)  { | 
|  | junkit = 1; | 
|  | junkreason = _("project quota"); | 
|  | } else if (lino == mp->m_sb.sb_metadirino)  { | 
|  | junkit = 1; | 
|  | junkreason = _("metadata directory root"); | 
|  | } else if (is_rtrmap_inode(lino)) { | 
|  | junkit = 1; | 
|  | junkreason = _("realtime rmap"); | 
|  | } else if (is_rtrefcount_inode(lino)) { | 
|  | junkit = 1; | 
|  | junkreason = _("realtime refcount"); | 
|  | } else if ((irec_p = find_inode_rec(mp, | 
|  | 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 (is_inode_free(irec_p, ino_off) && !ino_discovery) { | 
|  | junkit = 1; | 
|  | junkreason = _("free"); | 
|  | } | 
|  | } 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. | 
|  | */ | 
|  | junkit = 1; | 
|  | junkreason = _("non-existent"); | 
|  | } | 
|  | namelen = sfep->namelen; | 
|  | if (junkit) | 
|  | do_warn( | 
|  | _("entry \"%*.*s\" in shortform directory %" PRIu64 " references %s inode %" PRIu64 "\n"), | 
|  | namelen, namelen, sfep->name, ino, junkreason, | 
|  | lino); | 
|  |  | 
|  | /* is dir namelen 0 or does this entry extend past dir size? */ | 
|  | if (namelen == 0) { | 
|  | junkreason = _("is zero length"); | 
|  | bad_sfnamelen = 1; | 
|  | } else if ((intptr_t) sfep - (intptr_t) sfp + | 
|  | libxfs_dir2_sf_entsize(mp, sfp, sfep->namelen) | 
|  | > ino_dir_size)  { | 
|  | junkreason = _("extends past end of dir"); | 
|  | bad_sfnamelen = 1; | 
|  | } | 
|  |  | 
|  | if (bad_sfnamelen) { | 
|  | do_warn( | 
|  | _("entry #%d %s in shortform dir %" PRIu64), | 
|  | i, junkreason, 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 loop | 
|  | */ | 
|  | 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 (!libxfs_dir2_namecheck(sfep->name, namelen)) { | 
|  | /* | 
|  | * junk entry | 
|  | */ | 
|  | do_warn( | 
|  | _("entry contains illegal character in shortform dir %" PRIu64 "\n"), | 
|  | ino); | 
|  | junkit = 1; | 
|  | } | 
|  |  | 
|  | if (xfs_dir2_sf_get_offset(sfep) < offset) { | 
|  | do_warn( | 
|  | _("entry contains offset out of order in shortform dir %" PRIu64 "\n"), | 
|  | ino); | 
|  | bad_offset = 1; | 
|  | } | 
|  | offset = xfs_dir2_sf_get_offset(sfep) + | 
|  | libxfs_dir2_data_entsize(mp, namelen); | 
|  |  | 
|  | /* | 
|  | * 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_sfep 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, sfep->name, namelen); | 
|  | name[namelen] = '\0'; | 
|  |  | 
|  | if (!no_modify)  { | 
|  | tmp_elen = libxfs_dir2_sf_entsize(mp, sfp, | 
|  | sfep->namelen); | 
|  | be64_add_cpu(&dip->di_size, -tmp_elen); | 
|  | ino_dir_size -= tmp_elen; | 
|  |  | 
|  | tmp_sfep = (xfs_dir2_sf_entry_t *) | 
|  | ((intptr_t) sfep + tmp_elen); | 
|  | tmp_len = max_size - ((intptr_t) tmp_sfep | 
|  | - (intptr_t) sfp); | 
|  |  | 
|  | memmove(sfep, tmp_sfep, tmp_len); | 
|  |  | 
|  | sfp->count -= 1; | 
|  | num_entries--; | 
|  | memset((void *) ((intptr_t) sfep + tmp_len), 0, | 
|  | tmp_elen); | 
|  |  | 
|  | /* | 
|  | * reset the tmp value to the current | 
|  | * pointer so we'll process the entry | 
|  | * we just moved up | 
|  | */ | 
|  | tmp_sfep = sfep; | 
|  |  | 
|  | /* | 
|  | * 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 %" PRIu64 "\n"), | 
|  | name, ino); | 
|  | } else  { | 
|  | do_warn( | 
|  | _("would have junked entry \"%s\" in directory inode %" PRIu64 "\n"), | 
|  | name, ino); | 
|  | } | 
|  | } else if (lino > XFS_DIR2_MAX_SHORT_INUM) | 
|  | i8++; | 
|  | /* | 
|  | * 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_sfep. | 
|  | */ | 
|  | next_sfep = (tmp_sfep == NULL) | 
|  | ? (xfs_dir2_sf_entry_t *) ((intptr_t) sfep | 
|  | + ((!bad_sfnamelen) | 
|  | ? libxfs_dir2_sf_entsize(mp, sfp, sfep->namelen) | 
|  | : libxfs_dir2_sf_entsize(mp, sfp, namelen))) | 
|  | : tmp_sfep; | 
|  | } | 
|  |  | 
|  | /* sync up sizes and entry counts */ | 
|  |  | 
|  | if (sfp->count != i) { | 
|  | if (no_modify) { | 
|  | do_warn( | 
|  | _("would have corrected entry count in directory %" PRIu64 " from %d to %d\n"), | 
|  | ino, sfp->count, i); | 
|  | } else { | 
|  | do_warn( | 
|  | _("corrected entry count in directory %" PRIu64 ", was %d, now %d\n"), | 
|  | ino, sfp->count, i); | 
|  | sfp->count = i; | 
|  | *dino_dirty = 1; | 
|  | *repair = 1; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (sfp->i8count != i8)  { | 
|  | if (no_modify)  { | 
|  | do_warn( | 
|  | _("would have corrected i8 count in directory %" PRIu64 " from %d to %d\n"), | 
|  | ino, sfp->i8count, i8); | 
|  | } else { | 
|  | do_warn( | 
|  | _("corrected i8 count in directory %" PRIu64 ", was %d, now %d\n"), | 
|  | ino, sfp->i8count, i8); | 
|  | if (i8 == 0) | 
|  | process_sf_dir2_fixi8(mp, sfp, &next_sfep); | 
|  | else | 
|  | sfp->i8count = i8; | 
|  | *dino_dirty = 1; | 
|  | *repair = 1; | 
|  | } | 
|  | } | 
|  |  | 
|  | if ((intptr_t)next_sfep - (intptr_t)sfp != ino_dir_size)  { | 
|  | if (no_modify)  { | 
|  | do_warn( | 
|  | _("would have corrected directory %" PRIu64 " size from %" PRId64 " to %" PRIdPTR "\n"), | 
|  | ino, ino_dir_size, | 
|  | (intptr_t)next_sfep - (intptr_t)sfp); | 
|  | } else  { | 
|  | do_warn( | 
|  | _("corrected directory %" PRIu64 " size, was %" PRId64 ", now %" PRIdPTR "\n"), | 
|  | ino, ino_dir_size, | 
|  | (intptr_t)next_sfep - (intptr_t)sfp); | 
|  |  | 
|  | dip->di_size = cpu_to_be64( | 
|  | (intptr_t)next_sfep - (intptr_t)sfp); | 
|  | *dino_dirty = 1; | 
|  | *repair = 1; | 
|  | } | 
|  | } | 
|  | if (offset + (sfp->count + 2) * sizeof(xfs_dir2_leaf_entry_t) + | 
|  | sizeof(xfs_dir2_block_tail_t) > mp->m_dir_geo->blksize) { | 
|  | do_warn(_("directory %" PRIu64 " offsets too high\n"), ino); | 
|  | bad_offset = 1; | 
|  | } | 
|  | if (bad_offset) { | 
|  | if (no_modify) { | 
|  | do_warn( | 
|  | _("would have corrected entry offsets in directory %" PRIu64 "\n"), | 
|  | ino); | 
|  | } else { | 
|  | do_warn( | 
|  | _("corrected entry offsets in directory %" PRIu64 "\n"), | 
|  | ino); | 
|  | process_sf_dir2_fixoff(mp, dip); | 
|  | *dino_dirty = 1; | 
|  | *repair = 1; | 
|  | } | 
|  | } | 
|  |  | 
|  | /* | 
|  | * check parent (..) entry | 
|  | */ | 
|  | *parent = libxfs_dir2_sf_get_parent_ino(sfp); | 
|  | /* | 
|  | * If this function is called during inode discovery (phase 3), it will | 
|  | * set a bad sf dir parent pointer to the root directory. This fixes | 
|  | * the directory enough to pass the inode fork verifier in phase 6 when | 
|  | * we try to reset the parent pointer to the correct value. There is no | 
|  | * need to re-check the parent pointer during phase 4. | 
|  | */ | 
|  | if (!ino_discovery) | 
|  | return 0; | 
|  |  | 
|  | /* | 
|  | * if parent entry is bogus, null it out.  we'll fix it later . | 
|  | * If the validation fails for the root inode we fix it in | 
|  | * the next else case. | 
|  | */ | 
|  | if (!libxfs_verify_dir_ino(mp, *parent) && ino != mp->m_sb.sb_rootino) { | 
|  | do_warn( | 
|  | _("bogus .. inode number (%" PRIu64 ") in directory inode %" PRIu64 ", "), | 
|  | *parent, ino); | 
|  | *parent = NULLFSINO; | 
|  | if (!no_modify)  { | 
|  | do_warn(_("clearing inode number\n")); | 
|  |  | 
|  | libxfs_dir2_sf_put_parent_ino(sfp, mp->m_sb.sb_rootino); | 
|  | *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 %" PRIu64 " .. entry, was %" PRIu64 ", now %" PRIu64 "\n"), | 
|  | ino, *parent, ino); | 
|  | *parent = ino; | 
|  | libxfs_dir2_sf_put_parent_ino(sfp, ino); | 
|  | *dino_dirty = 1; | 
|  | *repair = 1; | 
|  | } else  { | 
|  | do_warn( | 
|  | _("would have corrected root directory %" PRIu64 " .. entry from %" PRIu64" to %" PRIu64 "\n"), | 
|  | ino, *parent, ino); | 
|  | } | 
|  | } else if (ino == *parent && ino != mp->m_sb.sb_rootino && | 
|  | ino != mp->m_sb.sb_metadirino)  { | 
|  | /* | 
|  | * likewise, non-root directories can't have .. pointing | 
|  | * to . | 
|  | */ | 
|  | *parent = NULLFSINO; | 
|  | do_warn( | 
|  | _("bad .. entry in directory inode %" PRIu64 ", points to self, "), | 
|  | ino); | 
|  | if (!no_modify)  { | 
|  | do_warn(_("clearing inode number\n")); | 
|  |  | 
|  | libxfs_dir2_sf_put_parent_ino(sfp, mp->m_sb.sb_rootino); | 
|  | *dino_dirty = 1; | 
|  | *repair = 1; | 
|  | } else  { | 
|  | do_warn(_("would clear inode number\n")); | 
|  | } | 
|  | } | 
|  |  | 
|  | return(0); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Process one directory data block. | 
|  | */ | 
|  | /* ARGSUSED */ | 
|  | static int | 
|  | process_dir2_data( | 
|  | xfs_mount_t		*mp, | 
|  | xfs_ino_t		ino, | 
|  | struct xfs_dinode	*dip, | 
|  | int			ino_discovery, | 
|  | char			*dirname, /* directory pathname */ | 
|  | xfs_ino_t		*parent,  /* out: NULLFSINO if entry not exist*/ | 
|  | struct xfs_buf		*bp, | 
|  | int			*dot,	  /* out: 1 if there's a dot else 0 */ | 
|  | int			*dotdot,  /* out: 1 if there's a dotdot else 0*/ | 
|  | xfs_dablk_t		da_bno, | 
|  | char			*endptr, | 
|  | int			*dirty) | 
|  | { | 
|  | int			badbest; | 
|  | xfs_dir2_data_free_t	*bf; | 
|  | int			clearino; | 
|  | char			*clearreason = NULL; | 
|  | struct xfs_dir2_data_hdr *d; | 
|  | xfs_dir2_data_entry_t	*dep; | 
|  | xfs_dir2_data_free_t	*dfp; | 
|  | xfs_dir2_data_unused_t	*dup; | 
|  | int			freeseen; | 
|  | int			i; | 
|  | int			ino_off; | 
|  | ino_tree_node_t		*irec_p; | 
|  | int			junkit; | 
|  | int			lastfree; | 
|  | int			nm_illegal; | 
|  | char			*ptr; | 
|  | xfs_ino_t		ent_ino; | 
|  |  | 
|  | d = bp->b_addr; | 
|  | bf = libxfs_dir2_data_bestfree_p(mp, d); | 
|  | ptr = (char *)d + mp->m_dir_geo->data_entry_offset; | 
|  | badbest = lastfree = freeseen = 0; | 
|  | if (be16_to_cpu(bf[0].length) == 0) { | 
|  | badbest |= be16_to_cpu(bf[0].offset) != 0; | 
|  | freeseen |= 1 << 0; | 
|  | } | 
|  | if (be16_to_cpu(bf[1].length) == 0) { | 
|  | badbest |= be16_to_cpu(bf[1].offset) != 0; | 
|  | freeseen |= 1 << 1; | 
|  | } | 
|  | if (be16_to_cpu(bf[2].length) == 0) { | 
|  | badbest |= be16_to_cpu(bf[2].offset) != 0; | 
|  | freeseen |= 1 << 2; | 
|  | } | 
|  | badbest |= be16_to_cpu(bf[0].length) < be16_to_cpu(bf[1].length); | 
|  | badbest |= be16_to_cpu(bf[1].length) < be16_to_cpu(bf[2].length); | 
|  | while (ptr < endptr) { | 
|  | dup = (xfs_dir2_data_unused_t *)ptr; | 
|  | /* | 
|  | * If it's unused, look for the space in the bestfree table. | 
|  | * If we find it, account for that, else make sure it doesn't | 
|  | * need to be there. | 
|  | */ | 
|  | if (be16_to_cpu(dup->freetag) == XFS_DIR2_DATA_FREE_TAG) { | 
|  | if (ptr + be16_to_cpu(dup->length) > endptr || | 
|  | be16_to_cpu(dup->length) == 0 || | 
|  | (be16_to_cpu(dup->length) & (XFS_DIR2_DATA_ALIGN - 1))) | 
|  | break; | 
|  | if (be16_to_cpu(*xfs_dir2_data_unused_tag_p(dup)) != | 
|  | (char *)dup - (char *)d) | 
|  | break; | 
|  | badbest |= lastfree != 0; | 
|  | dfp = xfs_dir2_data_freefind(d, bf, dup); | 
|  | if (dfp) { | 
|  | i = dfp - bf; | 
|  | badbest |= (freeseen & (1 << i)) != 0; | 
|  | freeseen |= 1 << i; | 
|  | } else | 
|  | badbest |= be16_to_cpu(dup->length) > | 
|  | be16_to_cpu(bf[2].length); | 
|  | ptr += be16_to_cpu(dup->length); | 
|  | lastfree = 1; | 
|  | continue; | 
|  | } | 
|  | dep = (xfs_dir2_data_entry_t *)ptr; | 
|  | if (ptr + libxfs_dir2_data_entsize(mp, dep->namelen) > endptr) | 
|  | break; | 
|  | if (be16_to_cpu(*libxfs_dir2_data_entry_tag_p(mp, dep)) != | 
|  | (char *)dep - (char *)d) | 
|  | break; | 
|  | ptr += libxfs_dir2_data_entsize(mp, dep->namelen); | 
|  | lastfree = 0; | 
|  | } | 
|  | /* | 
|  | * Dropped out before we processed everything, give up. | 
|  | * Phase 6 will kill this block if we don't kill the inode. | 
|  | */ | 
|  | if (ptr != endptr) { | 
|  | do_warn(_("corrupt block %u in directory inode %" PRIu64 "\n"), | 
|  | da_bno, ino); | 
|  | if (!no_modify) | 
|  | do_warn(_("\twill junk block\n")); | 
|  | else | 
|  | do_warn(_("\twould junk block\n")); | 
|  | return 1; | 
|  | } | 
|  | ptr = (char *)d + mp->m_dir_geo->data_entry_offset; | 
|  | /* | 
|  | * Process the entries now. | 
|  | */ | 
|  | 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; | 
|  | ent_ino = be64_to_cpu(dep->inumber); | 
|  | clearino = 1; | 
|  | clearreason = NULL; | 
|  | /* | 
|  | * 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. | 
|  | * | 
|  | * Conditions must either set clearino to zero or set | 
|  | * clearreason why it's being cleared. | 
|  | */ | 
|  | if (!ino_discovery && dep->name[0] == '/') { | 
|  | /* | 
|  | * Don't do a damned thing.  We already found this | 
|  | * (or did it ourselves) during phase 3. | 
|  | */ | 
|  | clearino = 0; | 
|  | } else if (!libxfs_verify_dir_ino(mp, ent_ino)) { | 
|  | /* | 
|  | * 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. | 
|  | */ | 
|  | clearreason = _("invalid"); | 
|  | } else if (is_metadata_directory(mp, dip)) { | 
|  | /* | 
|  | * Metadata directories are always rebuilt, so don't | 
|  | * bother checking if the child inode is free or not. | 
|  | */ | 
|  | clearino = 0; | 
|  | } else if (ent_ino == mp->m_sb.sb_rbmino) { | 
|  | clearreason = _("realtime bitmap"); | 
|  | } else if (ent_ino == mp->m_sb.sb_rsumino) { | 
|  | clearreason = _("realtime summary"); | 
|  | } else if (ent_ino == mp->m_sb.sb_uquotino) { | 
|  | clearreason = _("user quota"); | 
|  | } else if (ent_ino == mp->m_sb.sb_gquotino) { | 
|  | clearreason = _("group quota"); | 
|  | } else if (ent_ino == mp->m_sb.sb_pquotino) { | 
|  | clearreason = _("project quota"); | 
|  | } else if (ent_ino == mp->m_sb.sb_metadirino)  { | 
|  | clearreason = _("metadata directory root"); | 
|  | } else if (is_rtrmap_inode(ent_ino)) { | 
|  | clearreason = _("realtime rmap"); | 
|  | } else if (is_rtrefcount_inode(ent_ino)) { | 
|  | clearreason = _("realtime refcount"); | 
|  | } else { | 
|  | irec_p = find_inode_rec(mp, | 
|  | XFS_INO_TO_AGNO(mp, ent_ino), | 
|  | XFS_INO_TO_AGINO(mp, ent_ino)); | 
|  | if (irec_p == NULL) { | 
|  | if (ino_discovery) { | 
|  | add_inode_uncertain(mp, ent_ino, 0); | 
|  | clearino = 0; | 
|  | } else | 
|  | clearreason = _("non-existent"); | 
|  | } else { | 
|  | /* | 
|  | * Inode recs should have only confirmed | 
|  | * inodes in them. | 
|  | */ | 
|  | ino_off = XFS_INO_TO_AGINO(mp, ent_ino) | 
|  | - 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)) | 
|  | clearreason = _("free"); | 
|  | else | 
|  | clearino = 0; | 
|  | } | 
|  | } | 
|  | ASSERT((clearino == 0 && clearreason == NULL) || | 
|  | (clearino != 0 && clearreason != NULL)); | 
|  | if (clearino) | 
|  | do_warn( | 
|  | _("entry \"%*.*s\" at block %d offset %" PRIdPTR " in directory inode %" PRIu64 | 
|  | " references %s inode %" PRIu64 "\n"), | 
|  | dep->namelen, dep->namelen, dep->name, | 
|  | da_bno, (intptr_t)ptr - (intptr_t)d, ino, | 
|  | clearreason, ent_ino); | 
|  |  | 
|  | /* | 
|  | * We have a special dot & dotdot fixer-upper below which can | 
|  | * sort out the proper inode number, so don't clear it. | 
|  | */ | 
|  | if ((dep->namelen == 1 && dep->name[0] == '.') || | 
|  | (dep->namelen == 2 && | 
|  | dep->name[0] == '.' && dep->name[1] == '.')) { | 
|  | clearino = 0; | 
|  | clearreason = NULL; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * If the name length is 0 (illegal) make it 1 and blast | 
|  | * the entry. | 
|  | */ | 
|  | if (dep->namelen == 0) { | 
|  | do_warn( | 
|  | _("entry at block %u offset %" PRIdPTR " in directory inode %" PRIu64 | 
|  | "has 0 namelength\n"), | 
|  | da_bno, (intptr_t)ptr - (intptr_t)d, ino); | 
|  | if (!no_modify) | 
|  | dep->namelen = 1; | 
|  | clearino = 1; | 
|  | } | 
|  | /* | 
|  | * If needed to clear the inode number, do it now. | 
|  | */ | 
|  | if (clearino) { | 
|  | if (!no_modify) { | 
|  | do_warn( | 
|  | _("\tclearing inode number in entry at offset %" PRIdPTR "...\n"), | 
|  | (intptr_t)ptr - (intptr_t)d); | 
|  | dep->name[0] = '/'; | 
|  | *dirty = 1; | 
|  | } else { | 
|  | do_warn( | 
|  | _("\twould clear inode number in entry at offset %" PRIdPTR "...\n"), | 
|  | (intptr_t)ptr - (intptr_t)d); | 
|  | } | 
|  | } | 
|  | /* | 
|  | * Only complain about illegal names in phase 3 (when inode | 
|  | * discovery is turned on).  Otherwise, we'd complain a lot | 
|  | * during phase 4. | 
|  | */ | 
|  | junkit = dep->name[0] == '/'; | 
|  | nm_illegal = !libxfs_dir2_namecheck(dep->name, dep->namelen); | 
|  | if (ino_discovery && nm_illegal) { | 
|  | do_warn( | 
|  | _("entry at block %u offset %" PRIdPTR " in directory inode %" PRIu64 " has illegal name \"%*.*s\": "), | 
|  | da_bno, (intptr_t)ptr - (intptr_t)d, ino, | 
|  | dep->namelen, dep->namelen, dep->name); | 
|  | junkit = 1; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Ensure we write back bad entries for later processing | 
|  | */ | 
|  | if (!no_modify && dep->name[0] == '/') { | 
|  | *dirty = 1; | 
|  | junkit = 0; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Special .. entry processing. | 
|  | */ | 
|  | if (dep->namelen == 2 && | 
|  | dep->name[0] == '.' && dep->name[1] == '.') { | 
|  | if (!*dotdot) { | 
|  | (*dotdot)++; | 
|  | *parent = ent_ino; | 
|  | /* | 
|  | * What if .. == .?  Legal only in the root | 
|  | * inode.  Blow out entry and set parent to | 
|  | * NULLFSINO otherwise. | 
|  | */ | 
|  | if (ino == ent_ino && | 
|  | ino != mp->m_sb.sb_rootino && | 
|  | ino != mp->m_sb.sb_metadirino) { | 
|  | *parent = NULLFSINO; | 
|  | do_warn( | 
|  | _("bad .. entry in directory inode %" PRIu64 ", points to self: "), | 
|  | ino); | 
|  | junkit = 1; | 
|  | } | 
|  | /* | 
|  | * We have to make sure that . == .. in the | 
|  | * root inode. | 
|  | */ | 
|  | else if (ino != ent_ino && | 
|  | ino == mp->m_sb.sb_rootino) { | 
|  | do_warn( | 
|  | _("bad .. entry in root directory inode %" PRIu64 ", was %" PRIu64 ": "), | 
|  | ino, ent_ino); | 
|  | if (!no_modify) { | 
|  | do_warn(_("correcting\n")); | 
|  | dep->inumber = cpu_to_be64(ino); | 
|  | *dirty = 1; | 
|  | } else { | 
|  | do_warn(_("would correct\n")); | 
|  | } | 
|  | *parent = ino; | 
|  | } | 
|  | /* | 
|  | * Make sure our parent directory doesn't point | 
|  | * off into space. | 
|  | */ | 
|  | if (!junkit && | 
|  | *parent != NULLFSINO && | 
|  | !libxfs_verify_ino(mp, *parent)) { | 
|  | do_warn( | 
|  | _("bad .. entry in directory inode %" PRIu64 ", was %" PRIu64 ": "), | 
|  | ino, *parent); | 
|  | if (!no_modify) { | 
|  | do_warn(_("correcting\n")); | 
|  | } else { | 
|  | do_warn(_("would correct\n")); | 
|  | } | 
|  | *parent = NULLFSINO; | 
|  | } | 
|  | } | 
|  | /* | 
|  | * Can't fix the directory unless we know which .. | 
|  | * entry is the right one.  Both have valid inode | 
|  | * numbers or we wouldn't be here.  So since both | 
|  | * seem equally valid, trash this one. | 
|  | */ | 
|  | else { | 
|  | do_warn( | 
|  | _("multiple .. entries in directory inode %" PRIu64 ": "), | 
|  | ino); | 
|  | junkit = 1; | 
|  | } | 
|  | } | 
|  | /* | 
|  | * Special . entry processing. | 
|  | */ | 
|  | else if (dep->namelen == 1 && dep->name[0] == '.') { | 
|  | if (!*dot) { | 
|  | (*dot)++; | 
|  | if (ent_ino != ino) { | 
|  | do_warn( | 
|  | _("bad . entry in directory inode %" PRIu64 ", was %" PRIu64 ": "), | 
|  | ino, ent_ino); | 
|  | if (!no_modify) { | 
|  | do_warn(_("correcting\n")); | 
|  | dep->inumber = cpu_to_be64(ino); | 
|  | *dirty = 1; | 
|  | } else { | 
|  | do_warn(_("would correct\n")); | 
|  | } | 
|  | } | 
|  | } else { | 
|  | do_warn( | 
|  | _("multiple . entries in directory inode %" PRIu64 ": "), | 
|  | ino); | 
|  | junkit = 1; | 
|  | } | 
|  | } | 
|  | /* | 
|  | * All other entries -- make sure only . references self. | 
|  | */ | 
|  | else if (ent_ino == ino) { | 
|  | do_warn( | 
|  | _("entry \"%*.*s\" in directory inode %" PRIu64 " points to self: "), | 
|  | dep->namelen, dep->namelen, dep->name, ino); | 
|  | junkit = 1; | 
|  | } | 
|  | /* | 
|  | * Clear junked entries. | 
|  | */ | 
|  | if (junkit) { | 
|  | if (!no_modify) { | 
|  | dep->name[0] = '/'; | 
|  | *dirty = 1; | 
|  | do_warn(_("clearing entry\n")); | 
|  | } else { | 
|  | do_warn(_("would clear entry\n")); | 
|  | } | 
|  | } | 
|  | /* | 
|  | * Advance to the next entry. | 
|  | */ | 
|  | ptr += libxfs_dir2_data_entsize(mp, dep->namelen); | 
|  | } | 
|  | /* | 
|  | * Check the bestfree table. | 
|  | */ | 
|  | if (freeseen != 7 || badbest) { | 
|  | do_warn( | 
|  | _("bad bestfree table in block %u in directory inode %" PRIu64 ": "), | 
|  | da_bno, ino); | 
|  | if (!no_modify) { | 
|  | do_warn(_("repairing table\n")); | 
|  | libxfs_dir2_data_freescan(mp, d, &i); | 
|  | *dirty = 1; | 
|  | } else { | 
|  | do_warn(_("would repair table\n")); | 
|  | } | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Process a block-format directory. | 
|  | */ | 
|  | /* ARGSUSED */ | 
|  | static int | 
|  | process_block_dir2( | 
|  | xfs_mount_t		*mp, | 
|  | xfs_ino_t		ino, | 
|  | struct xfs_dinode	*dip, | 
|  | int			ino_discovery, | 
|  | int			*dino_dirty, /* out: 1 if dinode buffer dirty */ | 
|  | char			*dirname,    /* directory pathname */ | 
|  | xfs_ino_t		*parent,     /* out: NULLFSINO if no entry */ | 
|  | blkmap_t		*blkmap, | 
|  | int			*dot,	     /* out: 1 if there's a dot else 0 */ | 
|  | int			*dotdot,     /* out: 1 if there's a dotdot else 0 */ | 
|  | int			*repair)     /* out: 1 if something was fixed */ | 
|  | { | 
|  | struct xfs_dir2_data_hdr *block; | 
|  | xfs_dir2_leaf_entry_t	*blp; | 
|  | bmap_ext_t		*bmp; | 
|  | struct xfs_buf		*bp; | 
|  | xfs_dir2_block_tail_t	*btp; | 
|  | int			nex; | 
|  | int			rval; | 
|  | bmap_ext_t		lbmp; | 
|  | int			dirty = 0; | 
|  |  | 
|  | *repair = *dot = *dotdot = 0; | 
|  | *parent = NULLFSINO; | 
|  | nex = blkmap_getn(blkmap, mp->m_dir_geo->datablk, | 
|  | mp->m_dir_geo->fsbcount, &bmp, &lbmp); | 
|  | if (nex == 0) { | 
|  | do_warn( | 
|  | _("block %u for directory inode %" PRIu64 " is missing\n"), | 
|  | mp->m_dir_geo->datablk, ino); | 
|  | return 1; | 
|  | } | 
|  | bp = da_read_buf(mp, nex, bmp, &xfs_dir3_block_buf_ops); | 
|  | if (bmp != &lbmp) | 
|  | free(bmp); | 
|  | if (bp == NULL) { | 
|  | do_warn( | 
|  | _("can't read block %u for directory inode %" PRIu64 "\n"), | 
|  | mp->m_dir_geo->datablk, ino); | 
|  | return 1; | 
|  | } | 
|  | if (bp->b_error == -EFSCORRUPTED) { | 
|  | do_warn( | 
|  | _("corrupt directory block %u for inode %" PRIu64 "\n"), | 
|  | mp->m_dir_geo->datablk, ino); | 
|  | libxfs_buf_relse(bp); | 
|  | return 1; | 
|  | } | 
|  | /* | 
|  | * Verify the block | 
|  | */ | 
|  | block = bp->b_addr; | 
|  | if (!(be32_to_cpu(block->magic) == XFS_DIR2_BLOCK_MAGIC || | 
|  | be32_to_cpu(block->magic) == XFS_DIR3_BLOCK_MAGIC)) | 
|  | do_warn( | 
|  | _("bad directory block magic # %#x in block %u for directory inode %" PRIu64 "\n"), | 
|  | be32_to_cpu(block->magic), mp->m_dir_geo->datablk, ino); | 
|  | /* | 
|  | * process the data area | 
|  | * this also checks & fixes the bestfree | 
|  | */ | 
|  | btp = xfs_dir2_block_tail_p(mp->m_dir_geo, block); | 
|  | blp = xfs_dir2_block_leaf_p(btp); | 
|  | /* | 
|  | * Don't let this go past the end of the block. | 
|  | */ | 
|  | if ((char *)blp > (char *)btp) | 
|  | blp = (xfs_dir2_leaf_entry_t *)btp; | 
|  | rval = process_dir2_data(mp, ino, dip, ino_discovery, dirname, parent, | 
|  | bp, dot, dotdot, mp->m_dir_geo->datablk, (char *)blp, &dirty); | 
|  | /* If block looks ok but CRC didn't match, make sure to recompute it. */ | 
|  | if (bp->b_error == -EFSBADCRC) { | 
|  | do_warn( | 
|  | _("corrupt directory block %u for inode %" PRIu64 "\n"), | 
|  | mp->m_dir_geo->datablk, ino); | 
|  | if (!rval) | 
|  | dirty = 1; | 
|  | } | 
|  | if (dirty && !no_modify) { | 
|  | *repair = 1; | 
|  | libxfs_buf_mark_dirty(bp); | 
|  | libxfs_buf_relse(bp); | 
|  | } else | 
|  | libxfs_buf_relse(bp); | 
|  | return rval; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Validates leaf contents, node format directories only. | 
|  | * magic number and sibling pointers checked by caller. | 
|  | * Returns 0 if block is ok, 1 if the block is bad. | 
|  | * Looking for: out of order hash values, bad stale counts. | 
|  | */ | 
|  | static int | 
|  | process_leaf_block_dir2( | 
|  | xfs_mount_t		*mp, | 
|  | xfs_dir2_leaf_t		*leaf, | 
|  | xfs_dablk_t		da_bno, | 
|  | xfs_ino_t		ino, | 
|  | xfs_dahash_t		last_hashval, | 
|  | xfs_dahash_t		*next_hashval) | 
|  | { | 
|  | int			i; | 
|  | int			stale; | 
|  | struct xfs_dir2_leaf_entry *ents; | 
|  | struct xfs_dir3_icleaf_hdr leafhdr; | 
|  |  | 
|  | libxfs_dir2_leaf_hdr_from_disk(mp, &leafhdr, leaf); | 
|  | ents = leafhdr.ents; | 
|  |  | 
|  | for (i = stale = 0; i < leafhdr.count; i++) { | 
|  | if ((char *)&ents[i] >= (char *)leaf + mp->m_dir_geo->blksize) { | 
|  | do_warn( | 
|  | _("bad entry count in block %u of directory inode %" PRIu64 "\n"), | 
|  | da_bno, ino); | 
|  | return 1; | 
|  | } | 
|  | if (be32_to_cpu(ents[i].address) == XFS_DIR2_NULL_DATAPTR) | 
|  | stale++; | 
|  | else if (be32_to_cpu(ents[i].hashval) < last_hashval) { | 
|  | do_warn( | 
|  | _("bad hash ordering in block %u of directory inode %" PRIu64 "\n"), | 
|  | da_bno, ino); | 
|  | return 1; | 
|  | } | 
|  | *next_hashval = last_hashval = be32_to_cpu(ents[i].hashval); | 
|  | } | 
|  | if (stale != leafhdr.stale) { | 
|  | do_warn( | 
|  | _("bad stale count in block %u of directory inode %" PRIu64 "\n"), | 
|  | da_bno, ino); | 
|  | return 1; | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Returns 0 if the directory is ok, 1 if it has to be rebuilt. | 
|  | */ | 
|  | static int | 
|  | process_leaf_level_dir2( | 
|  | xfs_mount_t		*mp, | 
|  | da_bt_cursor_t		*da_cursor, | 
|  | int			*repair) | 
|  | { | 
|  | bmap_ext_t		*bmp; | 
|  | struct xfs_buf		*bp; | 
|  | int			buf_dirty; | 
|  | xfs_dahash_t		current_hashval; | 
|  | xfs_dablk_t		da_bno; | 
|  | xfs_dahash_t		greatest_hashval; | 
|  | xfs_ino_t		ino; | 
|  | xfs_dir2_leaf_t		*leaf; | 
|  | int			nex; | 
|  | xfs_dablk_t		prev_bno; | 
|  | bmap_ext_t		lbmp; | 
|  | struct xfs_dir3_icleaf_hdr leafhdr; | 
|  |  | 
|  | da_bno = da_cursor->level[0].bno; | 
|  | ino = da_cursor->ino; | 
|  | prev_bno = 0; | 
|  | bmp = NULL; | 
|  | current_hashval = 0; | 
|  | greatest_hashval = 0; | 
|  | buf_dirty = 0; | 
|  |  | 
|  | do { | 
|  | nex = blkmap_getn(da_cursor->blkmap, da_bno, | 
|  | mp->m_dir_geo->fsbcount, &bmp, &lbmp); | 
|  | /* | 
|  | * 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 (nex == 0) { | 
|  | do_warn( | 
|  | _("can't map block %u for directory inode %" PRIu64 "\n"), | 
|  | da_bno, ino); | 
|  | goto error_out; | 
|  | } | 
|  | bp = da_read_buf(mp, nex, bmp, &xfs_dir3_leafn_buf_ops); | 
|  | if (bmp != &lbmp) | 
|  | free(bmp); | 
|  | bmp = NULL; | 
|  | if (bp == NULL) { | 
|  | do_warn( | 
|  | _("can't read file block %u for directory inode %" PRIu64 "\n"), | 
|  | da_bno, ino); | 
|  | goto error_out; | 
|  | } | 
|  | if (bp->b_error == -EFSCORRUPTED) { | 
|  | do_warn( | 
|  | _("corrupt directory leafn block %u for inode %" PRIu64 "\n"), | 
|  | da_bno, ino); | 
|  | libxfs_buf_relse(bp); | 
|  | goto error_out; | 
|  | } | 
|  | leaf = bp->b_addr; | 
|  | libxfs_dir2_leaf_hdr_from_disk(mp, &leafhdr, leaf); | 
|  | /* | 
|  | * Check magic number for leaf directory btree block. | 
|  | */ | 
|  | if (!(leafhdr.magic == XFS_DIR2_LEAFN_MAGIC || | 
|  | leafhdr.magic == XFS_DIR3_LEAFN_MAGIC)) { | 
|  | do_warn( | 
|  | _("bad directory leaf magic # %#x for directory inode %" PRIu64 " block %u\n"), | 
|  | leafhdr.magic, ino, da_bno); | 
|  | libxfs_buf_relse(bp); | 
|  | goto error_out; | 
|  | } | 
|  | 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_block_dir2(mp, leaf, da_bno, ino, | 
|  | current_hashval, &greatest_hashval)) { | 
|  | libxfs_buf_relse(bp); | 
|  | goto error_out; | 
|  | } | 
|  | /* | 
|  | * Index can be set to hdr.count so match the indices 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 = leafhdr.count; | 
|  | da_cursor->level[0].dirty = buf_dirty; | 
|  |  | 
|  | if (leafhdr.back != prev_bno) { | 
|  | do_warn( | 
|  | _("bad sibling back pointer for block %u in directory inode %" PRIu64 "\n"), | 
|  | da_bno, ino); | 
|  | libxfs_buf_relse(bp); | 
|  | goto error_out; | 
|  | } | 
|  | prev_bno = da_bno; | 
|  | da_bno = leafhdr.forw; | 
|  | if (da_bno != 0) { | 
|  | if (verify_da_path(mp, da_cursor, 0, XFS_DATA_FORK)) { | 
|  | libxfs_buf_relse(bp); | 
|  | goto error_out; | 
|  | } | 
|  | } | 
|  | current_hashval = greatest_hashval; | 
|  | /* | 
|  | * If block looks ok but CRC didn't match, make sure to | 
|  | * recompute it. | 
|  | */ | 
|  | if (bp->b_error == -EFSBADCRC) { | 
|  | do_warn( | 
|  | _("bad checksum for directory leafn block %u for inode %" PRIu64 "\n"), | 
|  | da_bno, ino); | 
|  | if (!no_modify) | 
|  | buf_dirty = 1; | 
|  | } | 
|  |  | 
|  | ASSERT(buf_dirty == 0 || (buf_dirty && !no_modify)); | 
|  | if (buf_dirty && !no_modify) { | 
|  | *repair = 1; | 
|  | libxfs_buf_mark_dirty(bp); | 
|  | libxfs_buf_relse(bp); | 
|  | } else | 
|  | libxfs_buf_relse(bp); | 
|  | } while (da_bno != 0); | 
|  | if (verify_final_da_path(mp, da_cursor, 0, XFS_DATA_FORK)) { | 
|  | /* | 
|  | * Verify the final path up (right-hand-side) if still ok. | 
|  | */ | 
|  | do_warn(_("bad hash path in directory %" PRIu64 "\n"), ino); | 
|  | goto error_out; | 
|  | } | 
|  | /* | 
|  | * Redundant but just for testing. | 
|  | */ | 
|  | release_da_cursor(mp, da_cursor, 0); | 
|  | return 0; | 
|  |  | 
|  | error_out: | 
|  | /* | 
|  | * Release all buffers holding interior btree blocks. | 
|  | */ | 
|  | err_release_da_cursor(mp, da_cursor, 0); | 
|  | if (bmp && (bmp != &lbmp)) | 
|  | free(bmp); | 
|  | return 1; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Return 1 if the directory's leaf/node space is corrupted and | 
|  | * needs to be rebuilt, 0 if it's ok. | 
|  | */ | 
|  | static int | 
|  | process_node_dir2( | 
|  | xfs_mount_t		*mp, | 
|  | xfs_ino_t		ino, | 
|  | struct xfs_dinode	*dip, | 
|  | blkmap_t		*blkmap, | 
|  | int			*repair) | 
|  | { | 
|  | xfs_dablk_t		bno; | 
|  | da_bt_cursor_t		da_cursor; | 
|  |  | 
|  | /* | 
|  | * 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_cursor)); | 
|  | da_cursor.ino = ino; | 
|  | da_cursor.dip = dip; | 
|  | da_cursor.blkmap = blkmap; | 
|  |  | 
|  | /* | 
|  | * Now process interior node. | 
|  | */ | 
|  | if (traverse_int_dablock(mp, &da_cursor, &bno, XFS_DATA_FORK) == 0) | 
|  | return 1; | 
|  |  | 
|  | /* | 
|  | * Skip directories with a root marked XFS_DIR2_LEAFN_MAGIC | 
|  | * | 
|  | * Be careful here: If any level of the da cursor was filled out then | 
|  | * the directory has a da btree containing an invalid before pointer to | 
|  | * dblock 0, and we should move on to rebuilding the directory.  If no | 
|  | * levels in the da cursor got filled out, then we just have a single | 
|  | * leafn block and we're done. | 
|  | */ | 
|  | if (bno == 0) { | 
|  | if (da_cursor.active > 0) { | 
|  | err_release_da_cursor(mp, &da_cursor, 0); | 
|  | return 1; | 
|  | } else { | 
|  | release_da_cursor(mp, &da_cursor, 0); | 
|  | return 0; | 
|  | } | 
|  | } else { | 
|  | /* | 
|  | * 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. | 
|  | */ | 
|  | return process_leaf_level_dir2(mp, &da_cursor, repair); | 
|  | } | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Process leaf and node directories. | 
|  | * Process the data blocks then, if it's a node directory, check | 
|  | * the consistency of those blocks. | 
|  | */ | 
|  | static int | 
|  | process_leaf_node_dir2( | 
|  | xfs_mount_t		*mp, | 
|  | xfs_ino_t		ino, | 
|  | struct xfs_dinode	*dip, | 
|  | int			ino_discovery, | 
|  | char			*dirname, /* directory pathname */ | 
|  | xfs_ino_t		*parent,  /* out: NULLFSINO if no entry */ | 
|  | blkmap_t		*blkmap, | 
|  | int			*dot,	  /* out: 1 if there's a dot else 0 */ | 
|  | int			*dotdot,  /* out: 1 if there's a dotdot else 0*/ | 
|  | int			*repair,  /* out: 1 if something was fixed */ | 
|  | int			isnode)	  /* node directory not leaf */ | 
|  | { | 
|  | bmap_ext_t		*bmp; | 
|  | struct xfs_buf		*bp; | 
|  | struct xfs_dir2_data_hdr *data; | 
|  | xfs_fileoff_t		dbno; | 
|  | int			good; | 
|  | int			i; | 
|  | xfs_fileoff_t		ndbno; | 
|  | int			nex; | 
|  | xfs_extnum_t		t; | 
|  | bmap_ext_t		lbmp; | 
|  | int			dirty = 0; | 
|  |  | 
|  | *repair = *dot = *dotdot = good = 0; | 
|  | *parent = NULLFSINO; | 
|  | ndbno = NULLFILEOFF; | 
|  | while ((dbno = blkmap_next_off(blkmap, ndbno, &t)) < mp->m_dir_geo->leafblk) { | 
|  | nex = blkmap_getn(blkmap, dbno, mp->m_dir_geo->fsbcount, &bmp, &lbmp); | 
|  | /* Advance through map to last dfs block in this dir block */ | 
|  | ndbno = dbno; | 
|  | while (ndbno < dbno + mp->m_dir_geo->fsbcount - 1) { | 
|  | ndbno = blkmap_next_off(blkmap, ndbno, &t); | 
|  | } | 
|  | if (nex == 0) { | 
|  | do_warn( | 
|  | _("block %" PRIu64 " for directory inode %" PRIu64 " is missing\n"), | 
|  | dbno, ino); | 
|  | continue; | 
|  | } | 
|  | bp = da_read_buf(mp, nex, bmp, &xfs_dir3_data_buf_ops); | 
|  | if (bmp != &lbmp) | 
|  | free(bmp); | 
|  | if (bp == NULL) { | 
|  | do_warn( | 
|  | _("can't read block %" PRIu64 " for directory inode %" PRIu64 "\n"), | 
|  | dbno, ino); | 
|  | continue; | 
|  | } | 
|  | if (bp->b_error == -EFSCORRUPTED) { | 
|  | do_warn( | 
|  | _("corrupt directory data block %" PRIu64 " for inode %" PRIu64 "\n"), | 
|  | dbno, ino); | 
|  | libxfs_buf_relse(bp); | 
|  | continue; | 
|  | } | 
|  | data = bp->b_addr; | 
|  | if (!(be32_to_cpu(data->magic) == XFS_DIR2_DATA_MAGIC || | 
|  | be32_to_cpu(data->magic) == XFS_DIR3_DATA_MAGIC)) | 
|  | do_warn( | 
|  | _("bad directory block magic # %#x in block %" PRIu64 " for directory inode %" PRIu64 "\n"), | 
|  | be32_to_cpu(data->magic), dbno, ino); | 
|  | i = process_dir2_data(mp, ino, dip, ino_discovery, dirname, | 
|  | parent, bp, dot, dotdot, (xfs_dablk_t)dbno, | 
|  | (char *)data + mp->m_dir_geo->blksize, &dirty); | 
|  | if (i == 0) | 
|  | good++; | 
|  | if (bp->b_error == -EFSBADCRC) { | 
|  | do_warn( | 
|  | _("bad checksum in directory data block %" PRIu64 " for inode %" PRIu64 "\n"), | 
|  | dbno, ino); | 
|  | if (i == 0) | 
|  | dirty = 1; | 
|  | } | 
|  | if (dirty && !no_modify) { | 
|  | *repair = 1; | 
|  | libxfs_buf_mark_dirty(bp); | 
|  | libxfs_buf_relse(bp); | 
|  | } else | 
|  | libxfs_buf_relse(bp); | 
|  | } | 
|  | if (good == 0) | 
|  | return 1; | 
|  | if (!isnode) | 
|  | return 0; | 
|  | if (dir2_is_badino(ino)) | 
|  | return 0; | 
|  |  | 
|  | if (process_node_dir2(mp, ino, dip, blkmap, repair)) | 
|  | dir2_add_badlist(ino); | 
|  | 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_dir2( | 
|  | xfs_mount_t		*mp, | 
|  | xfs_ino_t		ino, | 
|  | struct xfs_dinode	*dip, | 
|  | int			ino_discovery, | 
|  | int			*dino_dirty, | 
|  | char			*dirname, | 
|  | xfs_ino_t		*parent, | 
|  | blkmap_t		*blkmap) | 
|  | { | 
|  | int			dot; | 
|  | int			dotdot; | 
|  | xfs_fileoff_t		last; | 
|  | int			repair; | 
|  | int			res; | 
|  |  | 
|  | *parent = NULLFSINO; | 
|  | dot = dotdot = 0; | 
|  | last = 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 (blkmap) | 
|  | last = blkmap_last_off(blkmap); | 
|  | if (be64_to_cpu(dip->di_size) <= XFS_DFORK_DSIZE(dip, mp) && | 
|  | dip->di_format == XFS_DINODE_FMT_LOCAL) { | 
|  | dot = dotdot = 1; | 
|  | res = process_sf_dir2(mp, ino, dip, ino_discovery, dino_dirty, | 
|  | dirname, parent, &repair); | 
|  | } else if (last == mp->m_dir_geo->fsbcount && | 
|  | (dip->di_format == XFS_DINODE_FMT_EXTENTS || | 
|  | dip->di_format == XFS_DINODE_FMT_BTREE)) { | 
|  | res = process_block_dir2(mp, ino, dip, ino_discovery, | 
|  | dino_dirty, dirname, parent, blkmap, &dot, &dotdot, | 
|  | &repair); | 
|  | } else if (last >= mp->m_dir_geo->leafblk + mp->m_dir_geo->fsbcount && | 
|  | (dip->di_format == XFS_DINODE_FMT_EXTENTS || | 
|  | dip->di_format == XFS_DINODE_FMT_BTREE)) { | 
|  | res = process_leaf_node_dir2(mp, ino, dip, ino_discovery, | 
|  | dirname, parent, blkmap, &dot, &dotdot, &repair, | 
|  | last > mp->m_dir_geo->leafblk + mp->m_dir_geo->fsbcount); | 
|  | } else { | 
|  | do_warn(_("bad size/format for directory %" PRIu64 "\n"), ino); | 
|  | return 1; | 
|  | } | 
|  | /* | 
|  | * bad . entries in all directories will be fixed up in phase 6 | 
|  | */ | 
|  | if (dot == 0) { | 
|  | do_warn(_("no . entry for directory %" PRIu64 "\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 %" PRIu64 "\n"), ino); | 
|  | } else if (dotdot == 0 && ino == mp->m_sb.sb_rootino) { | 
|  | do_warn(_("no .. entry for root directory %" PRIu64 "\n"), ino); | 
|  | need_root_dotdot = 1; | 
|  | } else if (dotdot == 0 && ino == mp->m_sb.sb_metadirino) { | 
|  | do_warn(_("no .. entry for metaino directory %" PRIu64 "\n"), ino); | 
|  | need_metadir_dotdot = 1; | 
|  | } | 
|  |  | 
|  | ASSERT((ino != mp->m_sb.sb_rootino && ino != *parent) || | 
|  | (ino == mp->m_sb.sb_metadirino && | 
|  | (ino == *parent || need_metadir_dotdot == 1)) || | 
|  | (ino == mp->m_sb.sb_rootino && | 
|  | (ino == *parent || need_root_dotdot == 1))); | 
|  |  | 
|  | return res; | 
|  | } |