18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+ 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Copyright (C) 2017 Oracle. All Rights Reserved. 48c2ecf20Sopenharmony_ci * Author: Darrick J. Wong <darrick.wong@oracle.com> 58c2ecf20Sopenharmony_ci */ 68c2ecf20Sopenharmony_ci#include "xfs.h" 78c2ecf20Sopenharmony_ci#include "xfs_fs.h" 88c2ecf20Sopenharmony_ci#include "xfs_shared.h" 98c2ecf20Sopenharmony_ci#include "xfs_format.h" 108c2ecf20Sopenharmony_ci#include "xfs_trans_resv.h" 118c2ecf20Sopenharmony_ci#include "xfs_mount.h" 128c2ecf20Sopenharmony_ci#include "xfs_log_format.h" 138c2ecf20Sopenharmony_ci#include "xfs_trans.h" 148c2ecf20Sopenharmony_ci#include "xfs_inode.h" 158c2ecf20Sopenharmony_ci#include "xfs_icache.h" 168c2ecf20Sopenharmony_ci#include "xfs_dir2.h" 178c2ecf20Sopenharmony_ci#include "xfs_dir2_priv.h" 188c2ecf20Sopenharmony_ci#include "scrub/scrub.h" 198c2ecf20Sopenharmony_ci#include "scrub/common.h" 208c2ecf20Sopenharmony_ci#include "scrub/dabtree.h" 218c2ecf20Sopenharmony_ci 228c2ecf20Sopenharmony_ci/* Set us up to scrub directories. */ 238c2ecf20Sopenharmony_ciint 248c2ecf20Sopenharmony_cixchk_setup_directory( 258c2ecf20Sopenharmony_ci struct xfs_scrub *sc, 268c2ecf20Sopenharmony_ci struct xfs_inode *ip) 278c2ecf20Sopenharmony_ci{ 288c2ecf20Sopenharmony_ci return xchk_setup_inode_contents(sc, ip, 0); 298c2ecf20Sopenharmony_ci} 308c2ecf20Sopenharmony_ci 318c2ecf20Sopenharmony_ci/* Directories */ 328c2ecf20Sopenharmony_ci 338c2ecf20Sopenharmony_ci/* Scrub a directory entry. */ 348c2ecf20Sopenharmony_ci 358c2ecf20Sopenharmony_cistruct xchk_dir_ctx { 368c2ecf20Sopenharmony_ci /* VFS fill-directory iterator */ 378c2ecf20Sopenharmony_ci struct dir_context dir_iter; 388c2ecf20Sopenharmony_ci 398c2ecf20Sopenharmony_ci struct xfs_scrub *sc; 408c2ecf20Sopenharmony_ci}; 418c2ecf20Sopenharmony_ci 428c2ecf20Sopenharmony_ci/* Check that an inode's mode matches a given DT_ type. */ 438c2ecf20Sopenharmony_ciSTATIC int 448c2ecf20Sopenharmony_cixchk_dir_check_ftype( 458c2ecf20Sopenharmony_ci struct xchk_dir_ctx *sdc, 468c2ecf20Sopenharmony_ci xfs_fileoff_t offset, 478c2ecf20Sopenharmony_ci xfs_ino_t inum, 488c2ecf20Sopenharmony_ci int dtype) 498c2ecf20Sopenharmony_ci{ 508c2ecf20Sopenharmony_ci struct xfs_mount *mp = sdc->sc->mp; 518c2ecf20Sopenharmony_ci struct xfs_inode *ip; 528c2ecf20Sopenharmony_ci int ino_dtype; 538c2ecf20Sopenharmony_ci int error = 0; 548c2ecf20Sopenharmony_ci 558c2ecf20Sopenharmony_ci if (!xfs_sb_version_hasftype(&mp->m_sb)) { 568c2ecf20Sopenharmony_ci if (dtype != DT_UNKNOWN && dtype != DT_DIR) 578c2ecf20Sopenharmony_ci xchk_fblock_set_corrupt(sdc->sc, XFS_DATA_FORK, 588c2ecf20Sopenharmony_ci offset); 598c2ecf20Sopenharmony_ci goto out; 608c2ecf20Sopenharmony_ci } 618c2ecf20Sopenharmony_ci 628c2ecf20Sopenharmony_ci /* 638c2ecf20Sopenharmony_ci * Grab the inode pointed to by the dirent. We release the 648c2ecf20Sopenharmony_ci * inode before we cancel the scrub transaction. Since we're 658c2ecf20Sopenharmony_ci * don't know a priori that releasing the inode won't trigger 668c2ecf20Sopenharmony_ci * eofblocks cleanup (which allocates what would be a nested 678c2ecf20Sopenharmony_ci * transaction), we can't use DONTCACHE here because DONTCACHE 688c2ecf20Sopenharmony_ci * inodes can trigger immediate inactive cleanup of the inode. 698c2ecf20Sopenharmony_ci */ 708c2ecf20Sopenharmony_ci error = xfs_iget(mp, sdc->sc->tp, inum, 0, 0, &ip); 718c2ecf20Sopenharmony_ci if (!xchk_fblock_xref_process_error(sdc->sc, XFS_DATA_FORK, offset, 728c2ecf20Sopenharmony_ci &error)) 738c2ecf20Sopenharmony_ci goto out; 748c2ecf20Sopenharmony_ci 758c2ecf20Sopenharmony_ci /* Convert mode to the DT_* values that dir_emit uses. */ 768c2ecf20Sopenharmony_ci ino_dtype = xfs_dir3_get_dtype(mp, 778c2ecf20Sopenharmony_ci xfs_mode_to_ftype(VFS_I(ip)->i_mode)); 788c2ecf20Sopenharmony_ci if (ino_dtype != dtype) 798c2ecf20Sopenharmony_ci xchk_fblock_set_corrupt(sdc->sc, XFS_DATA_FORK, offset); 808c2ecf20Sopenharmony_ci xfs_irele(ip); 818c2ecf20Sopenharmony_ciout: 828c2ecf20Sopenharmony_ci return error; 838c2ecf20Sopenharmony_ci} 848c2ecf20Sopenharmony_ci 858c2ecf20Sopenharmony_ci/* 868c2ecf20Sopenharmony_ci * Scrub a single directory entry. 878c2ecf20Sopenharmony_ci * 888c2ecf20Sopenharmony_ci * We use the VFS directory iterator (i.e. readdir) to call this 898c2ecf20Sopenharmony_ci * function for every directory entry in a directory. Once we're here, 908c2ecf20Sopenharmony_ci * we check the inode number to make sure it's sane, then we check that 918c2ecf20Sopenharmony_ci * we can look up this filename. Finally, we check the ftype. 928c2ecf20Sopenharmony_ci */ 938c2ecf20Sopenharmony_ciSTATIC int 948c2ecf20Sopenharmony_cixchk_dir_actor( 958c2ecf20Sopenharmony_ci struct dir_context *dir_iter, 968c2ecf20Sopenharmony_ci const char *name, 978c2ecf20Sopenharmony_ci int namelen, 988c2ecf20Sopenharmony_ci loff_t pos, 998c2ecf20Sopenharmony_ci u64 ino, 1008c2ecf20Sopenharmony_ci unsigned type) 1018c2ecf20Sopenharmony_ci{ 1028c2ecf20Sopenharmony_ci struct xfs_mount *mp; 1038c2ecf20Sopenharmony_ci struct xfs_inode *ip; 1048c2ecf20Sopenharmony_ci struct xchk_dir_ctx *sdc; 1058c2ecf20Sopenharmony_ci struct xfs_name xname; 1068c2ecf20Sopenharmony_ci xfs_ino_t lookup_ino; 1078c2ecf20Sopenharmony_ci xfs_dablk_t offset; 1088c2ecf20Sopenharmony_ci int error = 0; 1098c2ecf20Sopenharmony_ci 1108c2ecf20Sopenharmony_ci sdc = container_of(dir_iter, struct xchk_dir_ctx, dir_iter); 1118c2ecf20Sopenharmony_ci ip = sdc->sc->ip; 1128c2ecf20Sopenharmony_ci mp = ip->i_mount; 1138c2ecf20Sopenharmony_ci offset = xfs_dir2_db_to_da(mp->m_dir_geo, 1148c2ecf20Sopenharmony_ci xfs_dir2_dataptr_to_db(mp->m_dir_geo, pos)); 1158c2ecf20Sopenharmony_ci 1168c2ecf20Sopenharmony_ci if (xchk_should_terminate(sdc->sc, &error)) 1178c2ecf20Sopenharmony_ci return error; 1188c2ecf20Sopenharmony_ci 1198c2ecf20Sopenharmony_ci /* Does this inode number make sense? */ 1208c2ecf20Sopenharmony_ci if (!xfs_verify_dir_ino(mp, ino)) { 1218c2ecf20Sopenharmony_ci xchk_fblock_set_corrupt(sdc->sc, XFS_DATA_FORK, offset); 1228c2ecf20Sopenharmony_ci goto out; 1238c2ecf20Sopenharmony_ci } 1248c2ecf20Sopenharmony_ci 1258c2ecf20Sopenharmony_ci /* Does this name make sense? */ 1268c2ecf20Sopenharmony_ci if (!xfs_dir2_namecheck(name, namelen)) { 1278c2ecf20Sopenharmony_ci xchk_fblock_set_corrupt(sdc->sc, XFS_DATA_FORK, offset); 1288c2ecf20Sopenharmony_ci goto out; 1298c2ecf20Sopenharmony_ci } 1308c2ecf20Sopenharmony_ci 1318c2ecf20Sopenharmony_ci if (!strncmp(".", name, namelen)) { 1328c2ecf20Sopenharmony_ci /* If this is "." then check that the inum matches the dir. */ 1338c2ecf20Sopenharmony_ci if (xfs_sb_version_hasftype(&mp->m_sb) && type != DT_DIR) 1348c2ecf20Sopenharmony_ci xchk_fblock_set_corrupt(sdc->sc, XFS_DATA_FORK, 1358c2ecf20Sopenharmony_ci offset); 1368c2ecf20Sopenharmony_ci if (ino != ip->i_ino) 1378c2ecf20Sopenharmony_ci xchk_fblock_set_corrupt(sdc->sc, XFS_DATA_FORK, 1388c2ecf20Sopenharmony_ci offset); 1398c2ecf20Sopenharmony_ci } else if (!strncmp("..", name, namelen)) { 1408c2ecf20Sopenharmony_ci /* 1418c2ecf20Sopenharmony_ci * If this is ".." in the root inode, check that the inum 1428c2ecf20Sopenharmony_ci * matches this dir. 1438c2ecf20Sopenharmony_ci */ 1448c2ecf20Sopenharmony_ci if (xfs_sb_version_hasftype(&mp->m_sb) && type != DT_DIR) 1458c2ecf20Sopenharmony_ci xchk_fblock_set_corrupt(sdc->sc, XFS_DATA_FORK, 1468c2ecf20Sopenharmony_ci offset); 1478c2ecf20Sopenharmony_ci if (ip->i_ino == mp->m_sb.sb_rootino && ino != ip->i_ino) 1488c2ecf20Sopenharmony_ci xchk_fblock_set_corrupt(sdc->sc, XFS_DATA_FORK, 1498c2ecf20Sopenharmony_ci offset); 1508c2ecf20Sopenharmony_ci } 1518c2ecf20Sopenharmony_ci 1528c2ecf20Sopenharmony_ci /* Verify that we can look up this name by hash. */ 1538c2ecf20Sopenharmony_ci xname.name = name; 1548c2ecf20Sopenharmony_ci xname.len = namelen; 1558c2ecf20Sopenharmony_ci xname.type = XFS_DIR3_FT_UNKNOWN; 1568c2ecf20Sopenharmony_ci 1578c2ecf20Sopenharmony_ci error = xfs_dir_lookup(sdc->sc->tp, ip, &xname, &lookup_ino, NULL); 1588c2ecf20Sopenharmony_ci /* ENOENT means the hash lookup failed and the dir is corrupt */ 1598c2ecf20Sopenharmony_ci if (error == -ENOENT) 1608c2ecf20Sopenharmony_ci error = -EFSCORRUPTED; 1618c2ecf20Sopenharmony_ci if (!xchk_fblock_process_error(sdc->sc, XFS_DATA_FORK, offset, 1628c2ecf20Sopenharmony_ci &error)) 1638c2ecf20Sopenharmony_ci goto out; 1648c2ecf20Sopenharmony_ci if (lookup_ino != ino) { 1658c2ecf20Sopenharmony_ci xchk_fblock_set_corrupt(sdc->sc, XFS_DATA_FORK, offset); 1668c2ecf20Sopenharmony_ci goto out; 1678c2ecf20Sopenharmony_ci } 1688c2ecf20Sopenharmony_ci 1698c2ecf20Sopenharmony_ci /* Verify the file type. This function absorbs error codes. */ 1708c2ecf20Sopenharmony_ci error = xchk_dir_check_ftype(sdc, offset, lookup_ino, type); 1718c2ecf20Sopenharmony_ci if (error) 1728c2ecf20Sopenharmony_ci goto out; 1738c2ecf20Sopenharmony_ciout: 1748c2ecf20Sopenharmony_ci /* 1758c2ecf20Sopenharmony_ci * A negative error code returned here is supposed to cause the 1768c2ecf20Sopenharmony_ci * dir_emit caller (xfs_readdir) to abort the directory iteration 1778c2ecf20Sopenharmony_ci * and return zero to xchk_directory. 1788c2ecf20Sopenharmony_ci */ 1798c2ecf20Sopenharmony_ci if (error == 0 && sdc->sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT) 1808c2ecf20Sopenharmony_ci return -EFSCORRUPTED; 1818c2ecf20Sopenharmony_ci return error; 1828c2ecf20Sopenharmony_ci} 1838c2ecf20Sopenharmony_ci 1848c2ecf20Sopenharmony_ci/* Scrub a directory btree record. */ 1858c2ecf20Sopenharmony_ciSTATIC int 1868c2ecf20Sopenharmony_cixchk_dir_rec( 1878c2ecf20Sopenharmony_ci struct xchk_da_btree *ds, 1888c2ecf20Sopenharmony_ci int level) 1898c2ecf20Sopenharmony_ci{ 1908c2ecf20Sopenharmony_ci struct xfs_da_state_blk *blk = &ds->state->path.blk[level]; 1918c2ecf20Sopenharmony_ci struct xfs_mount *mp = ds->state->mp; 1928c2ecf20Sopenharmony_ci struct xfs_inode *dp = ds->dargs.dp; 1938c2ecf20Sopenharmony_ci struct xfs_da_geometry *geo = mp->m_dir_geo; 1948c2ecf20Sopenharmony_ci struct xfs_dir2_data_entry *dent; 1958c2ecf20Sopenharmony_ci struct xfs_buf *bp; 1968c2ecf20Sopenharmony_ci struct xfs_dir2_leaf_entry *ent; 1978c2ecf20Sopenharmony_ci unsigned int end; 1988c2ecf20Sopenharmony_ci unsigned int iter_off; 1998c2ecf20Sopenharmony_ci xfs_ino_t ino; 2008c2ecf20Sopenharmony_ci xfs_dablk_t rec_bno; 2018c2ecf20Sopenharmony_ci xfs_dir2_db_t db; 2028c2ecf20Sopenharmony_ci xfs_dir2_data_aoff_t off; 2038c2ecf20Sopenharmony_ci xfs_dir2_dataptr_t ptr; 2048c2ecf20Sopenharmony_ci xfs_dahash_t calc_hash; 2058c2ecf20Sopenharmony_ci xfs_dahash_t hash; 2068c2ecf20Sopenharmony_ci struct xfs_dir3_icleaf_hdr hdr; 2078c2ecf20Sopenharmony_ci unsigned int tag; 2088c2ecf20Sopenharmony_ci int error; 2098c2ecf20Sopenharmony_ci 2108c2ecf20Sopenharmony_ci ASSERT(blk->magic == XFS_DIR2_LEAF1_MAGIC || 2118c2ecf20Sopenharmony_ci blk->magic == XFS_DIR2_LEAFN_MAGIC); 2128c2ecf20Sopenharmony_ci 2138c2ecf20Sopenharmony_ci xfs_dir2_leaf_hdr_from_disk(mp, &hdr, blk->bp->b_addr); 2148c2ecf20Sopenharmony_ci ent = hdr.ents + blk->index; 2158c2ecf20Sopenharmony_ci 2168c2ecf20Sopenharmony_ci /* Check the hash of the entry. */ 2178c2ecf20Sopenharmony_ci error = xchk_da_btree_hash(ds, level, &ent->hashval); 2188c2ecf20Sopenharmony_ci if (error) 2198c2ecf20Sopenharmony_ci goto out; 2208c2ecf20Sopenharmony_ci 2218c2ecf20Sopenharmony_ci /* Valid hash pointer? */ 2228c2ecf20Sopenharmony_ci ptr = be32_to_cpu(ent->address); 2238c2ecf20Sopenharmony_ci if (ptr == 0) 2248c2ecf20Sopenharmony_ci return 0; 2258c2ecf20Sopenharmony_ci 2268c2ecf20Sopenharmony_ci /* Find the directory entry's location. */ 2278c2ecf20Sopenharmony_ci db = xfs_dir2_dataptr_to_db(geo, ptr); 2288c2ecf20Sopenharmony_ci off = xfs_dir2_dataptr_to_off(geo, ptr); 2298c2ecf20Sopenharmony_ci rec_bno = xfs_dir2_db_to_da(geo, db); 2308c2ecf20Sopenharmony_ci 2318c2ecf20Sopenharmony_ci if (rec_bno >= geo->leafblk) { 2328c2ecf20Sopenharmony_ci xchk_da_set_corrupt(ds, level); 2338c2ecf20Sopenharmony_ci goto out; 2348c2ecf20Sopenharmony_ci } 2358c2ecf20Sopenharmony_ci error = xfs_dir3_data_read(ds->dargs.trans, dp, rec_bno, 2368c2ecf20Sopenharmony_ci XFS_DABUF_MAP_HOLE_OK, &bp); 2378c2ecf20Sopenharmony_ci if (!xchk_fblock_process_error(ds->sc, XFS_DATA_FORK, rec_bno, 2388c2ecf20Sopenharmony_ci &error)) 2398c2ecf20Sopenharmony_ci goto out; 2408c2ecf20Sopenharmony_ci if (!bp) { 2418c2ecf20Sopenharmony_ci xchk_fblock_set_corrupt(ds->sc, XFS_DATA_FORK, rec_bno); 2428c2ecf20Sopenharmony_ci goto out; 2438c2ecf20Sopenharmony_ci } 2448c2ecf20Sopenharmony_ci xchk_buffer_recheck(ds->sc, bp); 2458c2ecf20Sopenharmony_ci 2468c2ecf20Sopenharmony_ci if (ds->sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT) 2478c2ecf20Sopenharmony_ci goto out_relse; 2488c2ecf20Sopenharmony_ci 2498c2ecf20Sopenharmony_ci dent = bp->b_addr + off; 2508c2ecf20Sopenharmony_ci 2518c2ecf20Sopenharmony_ci /* Make sure we got a real directory entry. */ 2528c2ecf20Sopenharmony_ci iter_off = geo->data_entry_offset; 2538c2ecf20Sopenharmony_ci end = xfs_dir3_data_end_offset(geo, bp->b_addr); 2548c2ecf20Sopenharmony_ci if (!end) { 2558c2ecf20Sopenharmony_ci xchk_fblock_set_corrupt(ds->sc, XFS_DATA_FORK, rec_bno); 2568c2ecf20Sopenharmony_ci goto out_relse; 2578c2ecf20Sopenharmony_ci } 2588c2ecf20Sopenharmony_ci for (;;) { 2598c2ecf20Sopenharmony_ci struct xfs_dir2_data_entry *dep = bp->b_addr + iter_off; 2608c2ecf20Sopenharmony_ci struct xfs_dir2_data_unused *dup = bp->b_addr + iter_off; 2618c2ecf20Sopenharmony_ci 2628c2ecf20Sopenharmony_ci if (iter_off >= end) { 2638c2ecf20Sopenharmony_ci xchk_fblock_set_corrupt(ds->sc, XFS_DATA_FORK, rec_bno); 2648c2ecf20Sopenharmony_ci goto out_relse; 2658c2ecf20Sopenharmony_ci } 2668c2ecf20Sopenharmony_ci 2678c2ecf20Sopenharmony_ci if (be16_to_cpu(dup->freetag) == XFS_DIR2_DATA_FREE_TAG) { 2688c2ecf20Sopenharmony_ci iter_off += be16_to_cpu(dup->length); 2698c2ecf20Sopenharmony_ci continue; 2708c2ecf20Sopenharmony_ci } 2718c2ecf20Sopenharmony_ci if (dep == dent) 2728c2ecf20Sopenharmony_ci break; 2738c2ecf20Sopenharmony_ci iter_off += xfs_dir2_data_entsize(mp, dep->namelen); 2748c2ecf20Sopenharmony_ci } 2758c2ecf20Sopenharmony_ci 2768c2ecf20Sopenharmony_ci /* Retrieve the entry, sanity check it, and compare hashes. */ 2778c2ecf20Sopenharmony_ci ino = be64_to_cpu(dent->inumber); 2788c2ecf20Sopenharmony_ci hash = be32_to_cpu(ent->hashval); 2798c2ecf20Sopenharmony_ci tag = be16_to_cpup(xfs_dir2_data_entry_tag_p(mp, dent)); 2808c2ecf20Sopenharmony_ci if (!xfs_verify_dir_ino(mp, ino) || tag != off) 2818c2ecf20Sopenharmony_ci xchk_fblock_set_corrupt(ds->sc, XFS_DATA_FORK, rec_bno); 2828c2ecf20Sopenharmony_ci if (dent->namelen == 0) { 2838c2ecf20Sopenharmony_ci xchk_fblock_set_corrupt(ds->sc, XFS_DATA_FORK, rec_bno); 2848c2ecf20Sopenharmony_ci goto out_relse; 2858c2ecf20Sopenharmony_ci } 2868c2ecf20Sopenharmony_ci calc_hash = xfs_da_hashname(dent->name, dent->namelen); 2878c2ecf20Sopenharmony_ci if (calc_hash != hash) 2888c2ecf20Sopenharmony_ci xchk_fblock_set_corrupt(ds->sc, XFS_DATA_FORK, rec_bno); 2898c2ecf20Sopenharmony_ci 2908c2ecf20Sopenharmony_ciout_relse: 2918c2ecf20Sopenharmony_ci xfs_trans_brelse(ds->dargs.trans, bp); 2928c2ecf20Sopenharmony_ciout: 2938c2ecf20Sopenharmony_ci return error; 2948c2ecf20Sopenharmony_ci} 2958c2ecf20Sopenharmony_ci 2968c2ecf20Sopenharmony_ci/* 2978c2ecf20Sopenharmony_ci * Is this unused entry either in the bestfree or smaller than all of 2988c2ecf20Sopenharmony_ci * them? We've already checked that the bestfrees are sorted longest to 2998c2ecf20Sopenharmony_ci * shortest, and that there aren't any bogus entries. 3008c2ecf20Sopenharmony_ci */ 3018c2ecf20Sopenharmony_ciSTATIC void 3028c2ecf20Sopenharmony_cixchk_directory_check_free_entry( 3038c2ecf20Sopenharmony_ci struct xfs_scrub *sc, 3048c2ecf20Sopenharmony_ci xfs_dablk_t lblk, 3058c2ecf20Sopenharmony_ci struct xfs_dir2_data_free *bf, 3068c2ecf20Sopenharmony_ci struct xfs_dir2_data_unused *dup) 3078c2ecf20Sopenharmony_ci{ 3088c2ecf20Sopenharmony_ci struct xfs_dir2_data_free *dfp; 3098c2ecf20Sopenharmony_ci unsigned int dup_length; 3108c2ecf20Sopenharmony_ci 3118c2ecf20Sopenharmony_ci dup_length = be16_to_cpu(dup->length); 3128c2ecf20Sopenharmony_ci 3138c2ecf20Sopenharmony_ci /* Unused entry is shorter than any of the bestfrees */ 3148c2ecf20Sopenharmony_ci if (dup_length < be16_to_cpu(bf[XFS_DIR2_DATA_FD_COUNT - 1].length)) 3158c2ecf20Sopenharmony_ci return; 3168c2ecf20Sopenharmony_ci 3178c2ecf20Sopenharmony_ci for (dfp = &bf[XFS_DIR2_DATA_FD_COUNT - 1]; dfp >= bf; dfp--) 3188c2ecf20Sopenharmony_ci if (dup_length == be16_to_cpu(dfp->length)) 3198c2ecf20Sopenharmony_ci return; 3208c2ecf20Sopenharmony_ci 3218c2ecf20Sopenharmony_ci /* Unused entry should be in the bestfrees but wasn't found. */ 3228c2ecf20Sopenharmony_ci xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, lblk); 3238c2ecf20Sopenharmony_ci} 3248c2ecf20Sopenharmony_ci 3258c2ecf20Sopenharmony_ci/* Check free space info in a directory data block. */ 3268c2ecf20Sopenharmony_ciSTATIC int 3278c2ecf20Sopenharmony_cixchk_directory_data_bestfree( 3288c2ecf20Sopenharmony_ci struct xfs_scrub *sc, 3298c2ecf20Sopenharmony_ci xfs_dablk_t lblk, 3308c2ecf20Sopenharmony_ci bool is_block) 3318c2ecf20Sopenharmony_ci{ 3328c2ecf20Sopenharmony_ci struct xfs_dir2_data_unused *dup; 3338c2ecf20Sopenharmony_ci struct xfs_dir2_data_free *dfp; 3348c2ecf20Sopenharmony_ci struct xfs_buf *bp; 3358c2ecf20Sopenharmony_ci struct xfs_dir2_data_free *bf; 3368c2ecf20Sopenharmony_ci struct xfs_mount *mp = sc->mp; 3378c2ecf20Sopenharmony_ci u16 tag; 3388c2ecf20Sopenharmony_ci unsigned int nr_bestfrees = 0; 3398c2ecf20Sopenharmony_ci unsigned int nr_frees = 0; 3408c2ecf20Sopenharmony_ci unsigned int smallest_bestfree; 3418c2ecf20Sopenharmony_ci int newlen; 3428c2ecf20Sopenharmony_ci unsigned int offset; 3438c2ecf20Sopenharmony_ci unsigned int end; 3448c2ecf20Sopenharmony_ci int error; 3458c2ecf20Sopenharmony_ci 3468c2ecf20Sopenharmony_ci if (is_block) { 3478c2ecf20Sopenharmony_ci /* dir block format */ 3488c2ecf20Sopenharmony_ci if (lblk != XFS_B_TO_FSBT(mp, XFS_DIR2_DATA_OFFSET)) 3498c2ecf20Sopenharmony_ci xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, lblk); 3508c2ecf20Sopenharmony_ci error = xfs_dir3_block_read(sc->tp, sc->ip, &bp); 3518c2ecf20Sopenharmony_ci } else { 3528c2ecf20Sopenharmony_ci /* dir data format */ 3538c2ecf20Sopenharmony_ci error = xfs_dir3_data_read(sc->tp, sc->ip, lblk, 0, &bp); 3548c2ecf20Sopenharmony_ci } 3558c2ecf20Sopenharmony_ci if (!xchk_fblock_process_error(sc, XFS_DATA_FORK, lblk, &error)) 3568c2ecf20Sopenharmony_ci goto out; 3578c2ecf20Sopenharmony_ci xchk_buffer_recheck(sc, bp); 3588c2ecf20Sopenharmony_ci 3598c2ecf20Sopenharmony_ci /* XXX: Check xfs_dir3_data_hdr.pad is zero once we start setting it. */ 3608c2ecf20Sopenharmony_ci 3618c2ecf20Sopenharmony_ci if (sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT) 3628c2ecf20Sopenharmony_ci goto out_buf; 3638c2ecf20Sopenharmony_ci 3648c2ecf20Sopenharmony_ci /* Do the bestfrees correspond to actual free space? */ 3658c2ecf20Sopenharmony_ci bf = xfs_dir2_data_bestfree_p(mp, bp->b_addr); 3668c2ecf20Sopenharmony_ci smallest_bestfree = UINT_MAX; 3678c2ecf20Sopenharmony_ci for (dfp = &bf[0]; dfp < &bf[XFS_DIR2_DATA_FD_COUNT]; dfp++) { 3688c2ecf20Sopenharmony_ci offset = be16_to_cpu(dfp->offset); 3698c2ecf20Sopenharmony_ci if (offset == 0) 3708c2ecf20Sopenharmony_ci continue; 3718c2ecf20Sopenharmony_ci if (offset >= mp->m_dir_geo->blksize) { 3728c2ecf20Sopenharmony_ci xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, lblk); 3738c2ecf20Sopenharmony_ci goto out_buf; 3748c2ecf20Sopenharmony_ci } 3758c2ecf20Sopenharmony_ci dup = bp->b_addr + offset; 3768c2ecf20Sopenharmony_ci tag = be16_to_cpu(*xfs_dir2_data_unused_tag_p(dup)); 3778c2ecf20Sopenharmony_ci 3788c2ecf20Sopenharmony_ci /* bestfree doesn't match the entry it points at? */ 3798c2ecf20Sopenharmony_ci if (dup->freetag != cpu_to_be16(XFS_DIR2_DATA_FREE_TAG) || 3808c2ecf20Sopenharmony_ci be16_to_cpu(dup->length) != be16_to_cpu(dfp->length) || 3818c2ecf20Sopenharmony_ci tag != offset) { 3828c2ecf20Sopenharmony_ci xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, lblk); 3838c2ecf20Sopenharmony_ci goto out_buf; 3848c2ecf20Sopenharmony_ci } 3858c2ecf20Sopenharmony_ci 3868c2ecf20Sopenharmony_ci /* bestfree records should be ordered largest to smallest */ 3878c2ecf20Sopenharmony_ci if (smallest_bestfree < be16_to_cpu(dfp->length)) { 3888c2ecf20Sopenharmony_ci xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, lblk); 3898c2ecf20Sopenharmony_ci goto out_buf; 3908c2ecf20Sopenharmony_ci } 3918c2ecf20Sopenharmony_ci 3928c2ecf20Sopenharmony_ci smallest_bestfree = be16_to_cpu(dfp->length); 3938c2ecf20Sopenharmony_ci nr_bestfrees++; 3948c2ecf20Sopenharmony_ci } 3958c2ecf20Sopenharmony_ci 3968c2ecf20Sopenharmony_ci /* Make sure the bestfrees are actually the best free spaces. */ 3978c2ecf20Sopenharmony_ci offset = mp->m_dir_geo->data_entry_offset; 3988c2ecf20Sopenharmony_ci end = xfs_dir3_data_end_offset(mp->m_dir_geo, bp->b_addr); 3998c2ecf20Sopenharmony_ci 4008c2ecf20Sopenharmony_ci /* Iterate the entries, stopping when we hit or go past the end. */ 4018c2ecf20Sopenharmony_ci while (offset < end) { 4028c2ecf20Sopenharmony_ci dup = bp->b_addr + offset; 4038c2ecf20Sopenharmony_ci 4048c2ecf20Sopenharmony_ci /* Skip real entries */ 4058c2ecf20Sopenharmony_ci if (dup->freetag != cpu_to_be16(XFS_DIR2_DATA_FREE_TAG)) { 4068c2ecf20Sopenharmony_ci struct xfs_dir2_data_entry *dep = bp->b_addr + offset; 4078c2ecf20Sopenharmony_ci 4088c2ecf20Sopenharmony_ci newlen = xfs_dir2_data_entsize(mp, dep->namelen); 4098c2ecf20Sopenharmony_ci if (newlen <= 0) { 4108c2ecf20Sopenharmony_ci xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, 4118c2ecf20Sopenharmony_ci lblk); 4128c2ecf20Sopenharmony_ci goto out_buf; 4138c2ecf20Sopenharmony_ci } 4148c2ecf20Sopenharmony_ci offset += newlen; 4158c2ecf20Sopenharmony_ci continue; 4168c2ecf20Sopenharmony_ci } 4178c2ecf20Sopenharmony_ci 4188c2ecf20Sopenharmony_ci /* Spot check this free entry */ 4198c2ecf20Sopenharmony_ci tag = be16_to_cpu(*xfs_dir2_data_unused_tag_p(dup)); 4208c2ecf20Sopenharmony_ci if (tag != offset) { 4218c2ecf20Sopenharmony_ci xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, lblk); 4228c2ecf20Sopenharmony_ci goto out_buf; 4238c2ecf20Sopenharmony_ci } 4248c2ecf20Sopenharmony_ci 4258c2ecf20Sopenharmony_ci /* 4268c2ecf20Sopenharmony_ci * Either this entry is a bestfree or it's smaller than 4278c2ecf20Sopenharmony_ci * any of the bestfrees. 4288c2ecf20Sopenharmony_ci */ 4298c2ecf20Sopenharmony_ci xchk_directory_check_free_entry(sc, lblk, bf, dup); 4308c2ecf20Sopenharmony_ci if (sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT) 4318c2ecf20Sopenharmony_ci goto out_buf; 4328c2ecf20Sopenharmony_ci 4338c2ecf20Sopenharmony_ci /* Move on. */ 4348c2ecf20Sopenharmony_ci newlen = be16_to_cpu(dup->length); 4358c2ecf20Sopenharmony_ci if (newlen <= 0) { 4368c2ecf20Sopenharmony_ci xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, lblk); 4378c2ecf20Sopenharmony_ci goto out_buf; 4388c2ecf20Sopenharmony_ci } 4398c2ecf20Sopenharmony_ci offset += newlen; 4408c2ecf20Sopenharmony_ci if (offset <= end) 4418c2ecf20Sopenharmony_ci nr_frees++; 4428c2ecf20Sopenharmony_ci } 4438c2ecf20Sopenharmony_ci 4448c2ecf20Sopenharmony_ci /* We're required to fill all the space. */ 4458c2ecf20Sopenharmony_ci if (offset != end) 4468c2ecf20Sopenharmony_ci xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, lblk); 4478c2ecf20Sopenharmony_ci 4488c2ecf20Sopenharmony_ci /* Did we see at least as many free slots as there are bestfrees? */ 4498c2ecf20Sopenharmony_ci if (nr_frees < nr_bestfrees) 4508c2ecf20Sopenharmony_ci xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, lblk); 4518c2ecf20Sopenharmony_ciout_buf: 4528c2ecf20Sopenharmony_ci xfs_trans_brelse(sc->tp, bp); 4538c2ecf20Sopenharmony_ciout: 4548c2ecf20Sopenharmony_ci return error; 4558c2ecf20Sopenharmony_ci} 4568c2ecf20Sopenharmony_ci 4578c2ecf20Sopenharmony_ci/* 4588c2ecf20Sopenharmony_ci * Does the free space length in the free space index block ($len) match 4598c2ecf20Sopenharmony_ci * the longest length in the directory data block's bestfree array? 4608c2ecf20Sopenharmony_ci * Assume that we've already checked that the data block's bestfree 4618c2ecf20Sopenharmony_ci * array is in order. 4628c2ecf20Sopenharmony_ci */ 4638c2ecf20Sopenharmony_ciSTATIC void 4648c2ecf20Sopenharmony_cixchk_directory_check_freesp( 4658c2ecf20Sopenharmony_ci struct xfs_scrub *sc, 4668c2ecf20Sopenharmony_ci xfs_dablk_t lblk, 4678c2ecf20Sopenharmony_ci struct xfs_buf *dbp, 4688c2ecf20Sopenharmony_ci unsigned int len) 4698c2ecf20Sopenharmony_ci{ 4708c2ecf20Sopenharmony_ci struct xfs_dir2_data_free *dfp; 4718c2ecf20Sopenharmony_ci 4728c2ecf20Sopenharmony_ci dfp = xfs_dir2_data_bestfree_p(sc->mp, dbp->b_addr); 4738c2ecf20Sopenharmony_ci 4748c2ecf20Sopenharmony_ci if (len != be16_to_cpu(dfp->length)) 4758c2ecf20Sopenharmony_ci xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, lblk); 4768c2ecf20Sopenharmony_ci 4778c2ecf20Sopenharmony_ci if (len > 0 && be16_to_cpu(dfp->offset) == 0) 4788c2ecf20Sopenharmony_ci xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, lblk); 4798c2ecf20Sopenharmony_ci} 4808c2ecf20Sopenharmony_ci 4818c2ecf20Sopenharmony_ci/* Check free space info in a directory leaf1 block. */ 4828c2ecf20Sopenharmony_ciSTATIC int 4838c2ecf20Sopenharmony_cixchk_directory_leaf1_bestfree( 4848c2ecf20Sopenharmony_ci struct xfs_scrub *sc, 4858c2ecf20Sopenharmony_ci struct xfs_da_args *args, 4868c2ecf20Sopenharmony_ci xfs_dablk_t lblk) 4878c2ecf20Sopenharmony_ci{ 4888c2ecf20Sopenharmony_ci struct xfs_dir3_icleaf_hdr leafhdr; 4898c2ecf20Sopenharmony_ci struct xfs_dir2_leaf_tail *ltp; 4908c2ecf20Sopenharmony_ci struct xfs_dir2_leaf *leaf; 4918c2ecf20Sopenharmony_ci struct xfs_buf *dbp; 4928c2ecf20Sopenharmony_ci struct xfs_buf *bp; 4938c2ecf20Sopenharmony_ci struct xfs_da_geometry *geo = sc->mp->m_dir_geo; 4948c2ecf20Sopenharmony_ci __be16 *bestp; 4958c2ecf20Sopenharmony_ci __u16 best; 4968c2ecf20Sopenharmony_ci __u32 hash; 4978c2ecf20Sopenharmony_ci __u32 lasthash = 0; 4988c2ecf20Sopenharmony_ci __u32 bestcount; 4998c2ecf20Sopenharmony_ci unsigned int stale = 0; 5008c2ecf20Sopenharmony_ci int i; 5018c2ecf20Sopenharmony_ci int error; 5028c2ecf20Sopenharmony_ci 5038c2ecf20Sopenharmony_ci /* Read the free space block. */ 5048c2ecf20Sopenharmony_ci error = xfs_dir3_leaf_read(sc->tp, sc->ip, lblk, &bp); 5058c2ecf20Sopenharmony_ci if (!xchk_fblock_process_error(sc, XFS_DATA_FORK, lblk, &error)) 5068c2ecf20Sopenharmony_ci return error; 5078c2ecf20Sopenharmony_ci xchk_buffer_recheck(sc, bp); 5088c2ecf20Sopenharmony_ci 5098c2ecf20Sopenharmony_ci leaf = bp->b_addr; 5108c2ecf20Sopenharmony_ci xfs_dir2_leaf_hdr_from_disk(sc->ip->i_mount, &leafhdr, leaf); 5118c2ecf20Sopenharmony_ci ltp = xfs_dir2_leaf_tail_p(geo, leaf); 5128c2ecf20Sopenharmony_ci bestcount = be32_to_cpu(ltp->bestcount); 5138c2ecf20Sopenharmony_ci bestp = xfs_dir2_leaf_bests_p(ltp); 5148c2ecf20Sopenharmony_ci 5158c2ecf20Sopenharmony_ci if (xfs_sb_version_hascrc(&sc->mp->m_sb)) { 5168c2ecf20Sopenharmony_ci struct xfs_dir3_leaf_hdr *hdr3 = bp->b_addr; 5178c2ecf20Sopenharmony_ci 5188c2ecf20Sopenharmony_ci if (hdr3->pad != cpu_to_be32(0)) 5198c2ecf20Sopenharmony_ci xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, lblk); 5208c2ecf20Sopenharmony_ci } 5218c2ecf20Sopenharmony_ci 5228c2ecf20Sopenharmony_ci /* 5238c2ecf20Sopenharmony_ci * There should be as many bestfree slots as there are dir data 5248c2ecf20Sopenharmony_ci * blocks that can fit under i_size. 5258c2ecf20Sopenharmony_ci */ 5268c2ecf20Sopenharmony_ci if (bestcount != xfs_dir2_byte_to_db(geo, sc->ip->i_d.di_size)) { 5278c2ecf20Sopenharmony_ci xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, lblk); 5288c2ecf20Sopenharmony_ci goto out; 5298c2ecf20Sopenharmony_ci } 5308c2ecf20Sopenharmony_ci 5318c2ecf20Sopenharmony_ci /* Is the leaf count even remotely sane? */ 5328c2ecf20Sopenharmony_ci if (leafhdr.count > geo->leaf_max_ents) { 5338c2ecf20Sopenharmony_ci xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, lblk); 5348c2ecf20Sopenharmony_ci goto out; 5358c2ecf20Sopenharmony_ci } 5368c2ecf20Sopenharmony_ci 5378c2ecf20Sopenharmony_ci /* Leaves and bests don't overlap in leaf format. */ 5388c2ecf20Sopenharmony_ci if ((char *)&leafhdr.ents[leafhdr.count] > (char *)bestp) { 5398c2ecf20Sopenharmony_ci xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, lblk); 5408c2ecf20Sopenharmony_ci goto out; 5418c2ecf20Sopenharmony_ci } 5428c2ecf20Sopenharmony_ci 5438c2ecf20Sopenharmony_ci /* Check hash value order, count stale entries. */ 5448c2ecf20Sopenharmony_ci for (i = 0; i < leafhdr.count; i++) { 5458c2ecf20Sopenharmony_ci hash = be32_to_cpu(leafhdr.ents[i].hashval); 5468c2ecf20Sopenharmony_ci if (i > 0 && lasthash > hash) 5478c2ecf20Sopenharmony_ci xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, lblk); 5488c2ecf20Sopenharmony_ci lasthash = hash; 5498c2ecf20Sopenharmony_ci if (leafhdr.ents[i].address == 5508c2ecf20Sopenharmony_ci cpu_to_be32(XFS_DIR2_NULL_DATAPTR)) 5518c2ecf20Sopenharmony_ci stale++; 5528c2ecf20Sopenharmony_ci } 5538c2ecf20Sopenharmony_ci if (leafhdr.stale != stale) 5548c2ecf20Sopenharmony_ci xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, lblk); 5558c2ecf20Sopenharmony_ci if (sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT) 5568c2ecf20Sopenharmony_ci goto out; 5578c2ecf20Sopenharmony_ci 5588c2ecf20Sopenharmony_ci /* Check all the bestfree entries. */ 5598c2ecf20Sopenharmony_ci for (i = 0; i < bestcount; i++, bestp++) { 5608c2ecf20Sopenharmony_ci best = be16_to_cpu(*bestp); 5618c2ecf20Sopenharmony_ci error = xfs_dir3_data_read(sc->tp, sc->ip, 5628c2ecf20Sopenharmony_ci xfs_dir2_db_to_da(args->geo, i), 5638c2ecf20Sopenharmony_ci XFS_DABUF_MAP_HOLE_OK, 5648c2ecf20Sopenharmony_ci &dbp); 5658c2ecf20Sopenharmony_ci if (!xchk_fblock_process_error(sc, XFS_DATA_FORK, lblk, 5668c2ecf20Sopenharmony_ci &error)) 5678c2ecf20Sopenharmony_ci break; 5688c2ecf20Sopenharmony_ci 5698c2ecf20Sopenharmony_ci if (!dbp) { 5708c2ecf20Sopenharmony_ci if (best != NULLDATAOFF) { 5718c2ecf20Sopenharmony_ci xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, 5728c2ecf20Sopenharmony_ci lblk); 5738c2ecf20Sopenharmony_ci break; 5748c2ecf20Sopenharmony_ci } 5758c2ecf20Sopenharmony_ci continue; 5768c2ecf20Sopenharmony_ci } 5778c2ecf20Sopenharmony_ci 5788c2ecf20Sopenharmony_ci if (best == NULLDATAOFF) 5798c2ecf20Sopenharmony_ci xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, lblk); 5808c2ecf20Sopenharmony_ci else 5818c2ecf20Sopenharmony_ci xchk_directory_check_freesp(sc, lblk, dbp, best); 5828c2ecf20Sopenharmony_ci xfs_trans_brelse(sc->tp, dbp); 5838c2ecf20Sopenharmony_ci if (sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT) 5848c2ecf20Sopenharmony_ci break; 5858c2ecf20Sopenharmony_ci } 5868c2ecf20Sopenharmony_ciout: 5878c2ecf20Sopenharmony_ci xfs_trans_brelse(sc->tp, bp); 5888c2ecf20Sopenharmony_ci return error; 5898c2ecf20Sopenharmony_ci} 5908c2ecf20Sopenharmony_ci 5918c2ecf20Sopenharmony_ci/* Check free space info in a directory freespace block. */ 5928c2ecf20Sopenharmony_ciSTATIC int 5938c2ecf20Sopenharmony_cixchk_directory_free_bestfree( 5948c2ecf20Sopenharmony_ci struct xfs_scrub *sc, 5958c2ecf20Sopenharmony_ci struct xfs_da_args *args, 5968c2ecf20Sopenharmony_ci xfs_dablk_t lblk) 5978c2ecf20Sopenharmony_ci{ 5988c2ecf20Sopenharmony_ci struct xfs_dir3_icfree_hdr freehdr; 5998c2ecf20Sopenharmony_ci struct xfs_buf *dbp; 6008c2ecf20Sopenharmony_ci struct xfs_buf *bp; 6018c2ecf20Sopenharmony_ci __u16 best; 6028c2ecf20Sopenharmony_ci unsigned int stale = 0; 6038c2ecf20Sopenharmony_ci int i; 6048c2ecf20Sopenharmony_ci int error; 6058c2ecf20Sopenharmony_ci 6068c2ecf20Sopenharmony_ci /* Read the free space block */ 6078c2ecf20Sopenharmony_ci error = xfs_dir2_free_read(sc->tp, sc->ip, lblk, &bp); 6088c2ecf20Sopenharmony_ci if (!xchk_fblock_process_error(sc, XFS_DATA_FORK, lblk, &error)) 6098c2ecf20Sopenharmony_ci return error; 6108c2ecf20Sopenharmony_ci xchk_buffer_recheck(sc, bp); 6118c2ecf20Sopenharmony_ci 6128c2ecf20Sopenharmony_ci if (xfs_sb_version_hascrc(&sc->mp->m_sb)) { 6138c2ecf20Sopenharmony_ci struct xfs_dir3_free_hdr *hdr3 = bp->b_addr; 6148c2ecf20Sopenharmony_ci 6158c2ecf20Sopenharmony_ci if (hdr3->pad != cpu_to_be32(0)) 6168c2ecf20Sopenharmony_ci xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, lblk); 6178c2ecf20Sopenharmony_ci } 6188c2ecf20Sopenharmony_ci 6198c2ecf20Sopenharmony_ci /* Check all the entries. */ 6208c2ecf20Sopenharmony_ci xfs_dir2_free_hdr_from_disk(sc->ip->i_mount, &freehdr, bp->b_addr); 6218c2ecf20Sopenharmony_ci for (i = 0; i < freehdr.nvalid; i++) { 6228c2ecf20Sopenharmony_ci best = be16_to_cpu(freehdr.bests[i]); 6238c2ecf20Sopenharmony_ci if (best == NULLDATAOFF) { 6248c2ecf20Sopenharmony_ci stale++; 6258c2ecf20Sopenharmony_ci continue; 6268c2ecf20Sopenharmony_ci } 6278c2ecf20Sopenharmony_ci error = xfs_dir3_data_read(sc->tp, sc->ip, 6288c2ecf20Sopenharmony_ci (freehdr.firstdb + i) * args->geo->fsbcount, 6298c2ecf20Sopenharmony_ci 0, &dbp); 6308c2ecf20Sopenharmony_ci if (!xchk_fblock_process_error(sc, XFS_DATA_FORK, lblk, 6318c2ecf20Sopenharmony_ci &error)) 6328c2ecf20Sopenharmony_ci goto out; 6338c2ecf20Sopenharmony_ci xchk_directory_check_freesp(sc, lblk, dbp, best); 6348c2ecf20Sopenharmony_ci xfs_trans_brelse(sc->tp, dbp); 6358c2ecf20Sopenharmony_ci } 6368c2ecf20Sopenharmony_ci 6378c2ecf20Sopenharmony_ci if (freehdr.nused + stale != freehdr.nvalid) 6388c2ecf20Sopenharmony_ci xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, lblk); 6398c2ecf20Sopenharmony_ciout: 6408c2ecf20Sopenharmony_ci xfs_trans_brelse(sc->tp, bp); 6418c2ecf20Sopenharmony_ci return error; 6428c2ecf20Sopenharmony_ci} 6438c2ecf20Sopenharmony_ci 6448c2ecf20Sopenharmony_ci/* Check free space information in directories. */ 6458c2ecf20Sopenharmony_ciSTATIC int 6468c2ecf20Sopenharmony_cixchk_directory_blocks( 6478c2ecf20Sopenharmony_ci struct xfs_scrub *sc) 6488c2ecf20Sopenharmony_ci{ 6498c2ecf20Sopenharmony_ci struct xfs_bmbt_irec got; 6508c2ecf20Sopenharmony_ci struct xfs_da_args args; 6518c2ecf20Sopenharmony_ci struct xfs_ifork *ifp = XFS_IFORK_PTR(sc->ip, XFS_DATA_FORK); 6528c2ecf20Sopenharmony_ci struct xfs_mount *mp = sc->mp; 6538c2ecf20Sopenharmony_ci xfs_fileoff_t leaf_lblk; 6548c2ecf20Sopenharmony_ci xfs_fileoff_t free_lblk; 6558c2ecf20Sopenharmony_ci xfs_fileoff_t lblk; 6568c2ecf20Sopenharmony_ci struct xfs_iext_cursor icur; 6578c2ecf20Sopenharmony_ci xfs_dablk_t dabno; 6588c2ecf20Sopenharmony_ci bool found; 6598c2ecf20Sopenharmony_ci int is_block = 0; 6608c2ecf20Sopenharmony_ci int error; 6618c2ecf20Sopenharmony_ci 6628c2ecf20Sopenharmony_ci /* Ignore local format directories. */ 6638c2ecf20Sopenharmony_ci if (ifp->if_format != XFS_DINODE_FMT_EXTENTS && 6648c2ecf20Sopenharmony_ci ifp->if_format != XFS_DINODE_FMT_BTREE) 6658c2ecf20Sopenharmony_ci return 0; 6668c2ecf20Sopenharmony_ci 6678c2ecf20Sopenharmony_ci lblk = XFS_B_TO_FSB(mp, XFS_DIR2_DATA_OFFSET); 6688c2ecf20Sopenharmony_ci leaf_lblk = XFS_B_TO_FSB(mp, XFS_DIR2_LEAF_OFFSET); 6698c2ecf20Sopenharmony_ci free_lblk = XFS_B_TO_FSB(mp, XFS_DIR2_FREE_OFFSET); 6708c2ecf20Sopenharmony_ci 6718c2ecf20Sopenharmony_ci /* Is this a block dir? */ 6728c2ecf20Sopenharmony_ci args.dp = sc->ip; 6738c2ecf20Sopenharmony_ci args.geo = mp->m_dir_geo; 6748c2ecf20Sopenharmony_ci args.trans = sc->tp; 6758c2ecf20Sopenharmony_ci error = xfs_dir2_isblock(&args, &is_block); 6768c2ecf20Sopenharmony_ci if (!xchk_fblock_process_error(sc, XFS_DATA_FORK, lblk, &error)) 6778c2ecf20Sopenharmony_ci goto out; 6788c2ecf20Sopenharmony_ci 6798c2ecf20Sopenharmony_ci /* Iterate all the data extents in the directory... */ 6808c2ecf20Sopenharmony_ci found = xfs_iext_lookup_extent(sc->ip, ifp, lblk, &icur, &got); 6818c2ecf20Sopenharmony_ci while (found && !(sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT)) { 6828c2ecf20Sopenharmony_ci /* Block directories only have a single block at offset 0. */ 6838c2ecf20Sopenharmony_ci if (is_block && 6848c2ecf20Sopenharmony_ci (got.br_startoff > 0 || 6858c2ecf20Sopenharmony_ci got.br_blockcount != args.geo->fsbcount)) { 6868c2ecf20Sopenharmony_ci xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, 6878c2ecf20Sopenharmony_ci got.br_startoff); 6888c2ecf20Sopenharmony_ci break; 6898c2ecf20Sopenharmony_ci } 6908c2ecf20Sopenharmony_ci 6918c2ecf20Sopenharmony_ci /* No more data blocks... */ 6928c2ecf20Sopenharmony_ci if (got.br_startoff >= leaf_lblk) 6938c2ecf20Sopenharmony_ci break; 6948c2ecf20Sopenharmony_ci 6958c2ecf20Sopenharmony_ci /* 6968c2ecf20Sopenharmony_ci * Check each data block's bestfree data. 6978c2ecf20Sopenharmony_ci * 6988c2ecf20Sopenharmony_ci * Iterate all the fsbcount-aligned block offsets in 6998c2ecf20Sopenharmony_ci * this directory. The directory block reading code is 7008c2ecf20Sopenharmony_ci * smart enough to do its own bmap lookups to handle 7018c2ecf20Sopenharmony_ci * discontiguous directory blocks. When we're done 7028c2ecf20Sopenharmony_ci * with the extent record, re-query the bmap at the 7038c2ecf20Sopenharmony_ci * next fsbcount-aligned offset to avoid redundant 7048c2ecf20Sopenharmony_ci * block checks. 7058c2ecf20Sopenharmony_ci */ 7068c2ecf20Sopenharmony_ci for (lblk = roundup((xfs_dablk_t)got.br_startoff, 7078c2ecf20Sopenharmony_ci args.geo->fsbcount); 7088c2ecf20Sopenharmony_ci lblk < got.br_startoff + got.br_blockcount; 7098c2ecf20Sopenharmony_ci lblk += args.geo->fsbcount) { 7108c2ecf20Sopenharmony_ci error = xchk_directory_data_bestfree(sc, lblk, 7118c2ecf20Sopenharmony_ci is_block); 7128c2ecf20Sopenharmony_ci if (error) 7138c2ecf20Sopenharmony_ci goto out; 7148c2ecf20Sopenharmony_ci } 7158c2ecf20Sopenharmony_ci dabno = got.br_startoff + got.br_blockcount; 7168c2ecf20Sopenharmony_ci lblk = roundup(dabno, args.geo->fsbcount); 7178c2ecf20Sopenharmony_ci found = xfs_iext_lookup_extent(sc->ip, ifp, lblk, &icur, &got); 7188c2ecf20Sopenharmony_ci } 7198c2ecf20Sopenharmony_ci 7208c2ecf20Sopenharmony_ci if (sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT) 7218c2ecf20Sopenharmony_ci goto out; 7228c2ecf20Sopenharmony_ci 7238c2ecf20Sopenharmony_ci /* Look for a leaf1 block, which has free info. */ 7248c2ecf20Sopenharmony_ci if (xfs_iext_lookup_extent(sc->ip, ifp, leaf_lblk, &icur, &got) && 7258c2ecf20Sopenharmony_ci got.br_startoff == leaf_lblk && 7268c2ecf20Sopenharmony_ci got.br_blockcount == args.geo->fsbcount && 7278c2ecf20Sopenharmony_ci !xfs_iext_next_extent(ifp, &icur, &got)) { 7288c2ecf20Sopenharmony_ci if (is_block) { 7298c2ecf20Sopenharmony_ci xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, lblk); 7308c2ecf20Sopenharmony_ci goto out; 7318c2ecf20Sopenharmony_ci } 7328c2ecf20Sopenharmony_ci error = xchk_directory_leaf1_bestfree(sc, &args, 7338c2ecf20Sopenharmony_ci leaf_lblk); 7348c2ecf20Sopenharmony_ci if (error) 7358c2ecf20Sopenharmony_ci goto out; 7368c2ecf20Sopenharmony_ci } 7378c2ecf20Sopenharmony_ci 7388c2ecf20Sopenharmony_ci if (sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT) 7398c2ecf20Sopenharmony_ci goto out; 7408c2ecf20Sopenharmony_ci 7418c2ecf20Sopenharmony_ci /* Scan for free blocks */ 7428c2ecf20Sopenharmony_ci lblk = free_lblk; 7438c2ecf20Sopenharmony_ci found = xfs_iext_lookup_extent(sc->ip, ifp, lblk, &icur, &got); 7448c2ecf20Sopenharmony_ci while (found && !(sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT)) { 7458c2ecf20Sopenharmony_ci /* 7468c2ecf20Sopenharmony_ci * Dirs can't have blocks mapped above 2^32. 7478c2ecf20Sopenharmony_ci * Single-block dirs shouldn't even be here. 7488c2ecf20Sopenharmony_ci */ 7498c2ecf20Sopenharmony_ci lblk = got.br_startoff; 7508c2ecf20Sopenharmony_ci if (lblk & ~0xFFFFFFFFULL) { 7518c2ecf20Sopenharmony_ci xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, lblk); 7528c2ecf20Sopenharmony_ci goto out; 7538c2ecf20Sopenharmony_ci } 7548c2ecf20Sopenharmony_ci if (is_block) { 7558c2ecf20Sopenharmony_ci xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, lblk); 7568c2ecf20Sopenharmony_ci goto out; 7578c2ecf20Sopenharmony_ci } 7588c2ecf20Sopenharmony_ci 7598c2ecf20Sopenharmony_ci /* 7608c2ecf20Sopenharmony_ci * Check each dir free block's bestfree data. 7618c2ecf20Sopenharmony_ci * 7628c2ecf20Sopenharmony_ci * Iterate all the fsbcount-aligned block offsets in 7638c2ecf20Sopenharmony_ci * this directory. The directory block reading code is 7648c2ecf20Sopenharmony_ci * smart enough to do its own bmap lookups to handle 7658c2ecf20Sopenharmony_ci * discontiguous directory blocks. When we're done 7668c2ecf20Sopenharmony_ci * with the extent record, re-query the bmap at the 7678c2ecf20Sopenharmony_ci * next fsbcount-aligned offset to avoid redundant 7688c2ecf20Sopenharmony_ci * block checks. 7698c2ecf20Sopenharmony_ci */ 7708c2ecf20Sopenharmony_ci for (lblk = roundup((xfs_dablk_t)got.br_startoff, 7718c2ecf20Sopenharmony_ci args.geo->fsbcount); 7728c2ecf20Sopenharmony_ci lblk < got.br_startoff + got.br_blockcount; 7738c2ecf20Sopenharmony_ci lblk += args.geo->fsbcount) { 7748c2ecf20Sopenharmony_ci error = xchk_directory_free_bestfree(sc, &args, 7758c2ecf20Sopenharmony_ci lblk); 7768c2ecf20Sopenharmony_ci if (error) 7778c2ecf20Sopenharmony_ci goto out; 7788c2ecf20Sopenharmony_ci } 7798c2ecf20Sopenharmony_ci dabno = got.br_startoff + got.br_blockcount; 7808c2ecf20Sopenharmony_ci lblk = roundup(dabno, args.geo->fsbcount); 7818c2ecf20Sopenharmony_ci found = xfs_iext_lookup_extent(sc->ip, ifp, lblk, &icur, &got); 7828c2ecf20Sopenharmony_ci } 7838c2ecf20Sopenharmony_ciout: 7848c2ecf20Sopenharmony_ci return error; 7858c2ecf20Sopenharmony_ci} 7868c2ecf20Sopenharmony_ci 7878c2ecf20Sopenharmony_ci/* Scrub a whole directory. */ 7888c2ecf20Sopenharmony_ciint 7898c2ecf20Sopenharmony_cixchk_directory( 7908c2ecf20Sopenharmony_ci struct xfs_scrub *sc) 7918c2ecf20Sopenharmony_ci{ 7928c2ecf20Sopenharmony_ci struct xchk_dir_ctx sdc = { 7938c2ecf20Sopenharmony_ci .dir_iter.actor = xchk_dir_actor, 7948c2ecf20Sopenharmony_ci .dir_iter.pos = 0, 7958c2ecf20Sopenharmony_ci .sc = sc, 7968c2ecf20Sopenharmony_ci }; 7978c2ecf20Sopenharmony_ci size_t bufsize; 7988c2ecf20Sopenharmony_ci loff_t oldpos; 7998c2ecf20Sopenharmony_ci int error = 0; 8008c2ecf20Sopenharmony_ci 8018c2ecf20Sopenharmony_ci if (!S_ISDIR(VFS_I(sc->ip)->i_mode)) 8028c2ecf20Sopenharmony_ci return -ENOENT; 8038c2ecf20Sopenharmony_ci 8048c2ecf20Sopenharmony_ci /* Plausible size? */ 8058c2ecf20Sopenharmony_ci if (sc->ip->i_d.di_size < xfs_dir2_sf_hdr_size(0)) { 8068c2ecf20Sopenharmony_ci xchk_ino_set_corrupt(sc, sc->ip->i_ino); 8078c2ecf20Sopenharmony_ci goto out; 8088c2ecf20Sopenharmony_ci } 8098c2ecf20Sopenharmony_ci 8108c2ecf20Sopenharmony_ci /* Check directory tree structure */ 8118c2ecf20Sopenharmony_ci error = xchk_da_btree(sc, XFS_DATA_FORK, xchk_dir_rec, NULL); 8128c2ecf20Sopenharmony_ci if (error) 8138c2ecf20Sopenharmony_ci return error; 8148c2ecf20Sopenharmony_ci 8158c2ecf20Sopenharmony_ci if (sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT) 8168c2ecf20Sopenharmony_ci return error; 8178c2ecf20Sopenharmony_ci 8188c2ecf20Sopenharmony_ci /* Check the freespace. */ 8198c2ecf20Sopenharmony_ci error = xchk_directory_blocks(sc); 8208c2ecf20Sopenharmony_ci if (error) 8218c2ecf20Sopenharmony_ci return error; 8228c2ecf20Sopenharmony_ci 8238c2ecf20Sopenharmony_ci if (sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT) 8248c2ecf20Sopenharmony_ci return error; 8258c2ecf20Sopenharmony_ci 8268c2ecf20Sopenharmony_ci /* 8278c2ecf20Sopenharmony_ci * Check that every dirent we see can also be looked up by hash. 8288c2ecf20Sopenharmony_ci * Userspace usually asks for a 32k buffer, so we will too. 8298c2ecf20Sopenharmony_ci */ 8308c2ecf20Sopenharmony_ci bufsize = (size_t)min_t(loff_t, XFS_READDIR_BUFSIZE, 8318c2ecf20Sopenharmony_ci sc->ip->i_d.di_size); 8328c2ecf20Sopenharmony_ci 8338c2ecf20Sopenharmony_ci /* 8348c2ecf20Sopenharmony_ci * Look up every name in this directory by hash. 8358c2ecf20Sopenharmony_ci * 8368c2ecf20Sopenharmony_ci * Use the xfs_readdir function to call xchk_dir_actor on 8378c2ecf20Sopenharmony_ci * every directory entry in this directory. In _actor, we check 8388c2ecf20Sopenharmony_ci * the name, inode number, and ftype (if applicable) of the 8398c2ecf20Sopenharmony_ci * entry. xfs_readdir uses the VFS filldir functions to provide 8408c2ecf20Sopenharmony_ci * iteration context. 8418c2ecf20Sopenharmony_ci * 8428c2ecf20Sopenharmony_ci * The VFS grabs a read or write lock via i_rwsem before it reads 8438c2ecf20Sopenharmony_ci * or writes to a directory. If we've gotten this far we've 8448c2ecf20Sopenharmony_ci * already obtained IOLOCK_EXCL, which (since 4.10) is the same as 8458c2ecf20Sopenharmony_ci * getting a write lock on i_rwsem. Therefore, it is safe for us 8468c2ecf20Sopenharmony_ci * to drop the ILOCK here in order to reuse the _readdir and 8478c2ecf20Sopenharmony_ci * _dir_lookup routines, which do their own ILOCK locking. 8488c2ecf20Sopenharmony_ci */ 8498c2ecf20Sopenharmony_ci oldpos = 0; 8508c2ecf20Sopenharmony_ci sc->ilock_flags &= ~XFS_ILOCK_EXCL; 8518c2ecf20Sopenharmony_ci xfs_iunlock(sc->ip, XFS_ILOCK_EXCL); 8528c2ecf20Sopenharmony_ci while (true) { 8538c2ecf20Sopenharmony_ci error = xfs_readdir(sc->tp, sc->ip, &sdc.dir_iter, bufsize); 8548c2ecf20Sopenharmony_ci if (!xchk_fblock_process_error(sc, XFS_DATA_FORK, 0, 8558c2ecf20Sopenharmony_ci &error)) 8568c2ecf20Sopenharmony_ci goto out; 8578c2ecf20Sopenharmony_ci if (oldpos == sdc.dir_iter.pos) 8588c2ecf20Sopenharmony_ci break; 8598c2ecf20Sopenharmony_ci oldpos = sdc.dir_iter.pos; 8608c2ecf20Sopenharmony_ci } 8618c2ecf20Sopenharmony_ci 8628c2ecf20Sopenharmony_ciout: 8638c2ecf20Sopenharmony_ci return error; 8648c2ecf20Sopenharmony_ci} 865