422 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * Copyright (C) 2022-2023 Oracle. All Rights Reserved.4 * Author: Darrick J. Wong <djwong@kernel.org>5 */6#include "xfs.h"7#include "xfs_fs.h"8#include "xfs_shared.h"9#include "xfs_format.h"10#include "xfs_log_format.h"11#include "xfs_trans_resv.h"12#include "xfs_mount.h"13#include "xfs_inode.h"14#include "xfs_dir2.h"15#include "xfs_dir2_priv.h"16#include "xfs_trace.h"17#include "xfs_bmap.h"18#include "xfs_trans.h"19#include "xfs_error.h"20#include "scrub/scrub.h"21#include "scrub/common.h"22#include "scrub/readdir.h"23 24/* Call a function for every entry in a shortform directory. */25STATIC int26xchk_dir_walk_sf(27 struct xfs_scrub *sc,28 struct xfs_inode *dp,29 xchk_dirent_fn dirent_fn,30 void *priv)31{32 struct xfs_name name = {33 .name = ".",34 .len = 1,35 .type = XFS_DIR3_FT_DIR,36 };37 struct xfs_mount *mp = dp->i_mount;38 struct xfs_da_geometry *geo = mp->m_dir_geo;39 struct xfs_dir2_sf_entry *sfep;40 struct xfs_dir2_sf_hdr *sfp = dp->i_df.if_data;41 xfs_ino_t ino;42 xfs_dir2_dataptr_t dapos;43 unsigned int i;44 int error;45 46 ASSERT(dp->i_df.if_bytes == dp->i_disk_size);47 ASSERT(sfp != NULL);48 49 /* dot entry */50 dapos = xfs_dir2_db_off_to_dataptr(geo, geo->datablk,51 geo->data_entry_offset);52 53 error = dirent_fn(sc, dp, dapos, &name, dp->i_ino, priv);54 if (error)55 return error;56 57 /* dotdot entry */58 dapos = xfs_dir2_db_off_to_dataptr(geo, geo->datablk,59 geo->data_entry_offset +60 xfs_dir2_data_entsize(mp, sizeof(".") - 1));61 ino = xfs_dir2_sf_get_parent_ino(sfp);62 name.name = "..";63 name.len = 2;64 65 error = dirent_fn(sc, dp, dapos, &name, ino, priv);66 if (error)67 return error;68 69 /* iterate everything else */70 sfep = xfs_dir2_sf_firstentry(sfp);71 for (i = 0; i < sfp->count; i++) {72 dapos = xfs_dir2_db_off_to_dataptr(geo, geo->datablk,73 xfs_dir2_sf_get_offset(sfep));74 ino = xfs_dir2_sf_get_ino(mp, sfp, sfep);75 name.name = sfep->name;76 name.len = sfep->namelen;77 name.type = xfs_dir2_sf_get_ftype(mp, sfep);78 79 error = dirent_fn(sc, dp, dapos, &name, ino, priv);80 if (error)81 return error;82 83 sfep = xfs_dir2_sf_nextentry(mp, sfp, sfep);84 }85 86 return 0;87}88 89/* Call a function for every entry in a block directory. */90STATIC int91xchk_dir_walk_block(92 struct xfs_scrub *sc,93 struct xfs_inode *dp,94 xchk_dirent_fn dirent_fn,95 void *priv)96{97 struct xfs_mount *mp = dp->i_mount;98 struct xfs_da_geometry *geo = mp->m_dir_geo;99 struct xfs_buf *bp;100 unsigned int off, next_off, end;101 int error;102 103 error = xfs_dir3_block_read(sc->tp, dp, dp->i_ino, &bp);104 if (error)105 return error;106 107 /* Walk each directory entry. */108 end = xfs_dir3_data_end_offset(geo, bp->b_addr);109 for (off = geo->data_entry_offset; off < end; off = next_off) {110 struct xfs_name name = { };111 struct xfs_dir2_data_unused *dup = bp->b_addr + off;112 struct xfs_dir2_data_entry *dep = bp->b_addr + off;113 xfs_ino_t ino;114 xfs_dir2_dataptr_t dapos;115 116 /* Skip an empty entry. */117 if (be16_to_cpu(dup->freetag) == XFS_DIR2_DATA_FREE_TAG) {118 next_off = off + be16_to_cpu(dup->length);119 continue;120 }121 122 /* Otherwise, find the next entry and report it. */123 next_off = off + xfs_dir2_data_entsize(mp, dep->namelen);124 if (next_off > end)125 break;126 127 dapos = xfs_dir2_db_off_to_dataptr(geo, geo->datablk, off);128 ino = be64_to_cpu(dep->inumber);129 name.name = dep->name;130 name.len = dep->namelen;131 name.type = xfs_dir2_data_get_ftype(mp, dep);132 133 error = dirent_fn(sc, dp, dapos, &name, ino, priv);134 if (error)135 break;136 }137 138 xfs_trans_brelse(sc->tp, bp);139 return error;140}141 142/* Read a leaf-format directory buffer. */143STATIC int144xchk_read_leaf_dir_buf(145 struct xfs_trans *tp,146 struct xfs_inode *dp,147 struct xfs_da_geometry *geo,148 xfs_dir2_off_t *curoff,149 struct xfs_buf **bpp)150{151 struct xfs_iext_cursor icur;152 struct xfs_bmbt_irec map;153 struct xfs_ifork *ifp = xfs_ifork_ptr(dp, XFS_DATA_FORK);154 xfs_dablk_t last_da;155 xfs_dablk_t map_off;156 xfs_dir2_off_t new_off;157 158 *bpp = NULL;159 160 /*161 * Look for mapped directory blocks at or above the current offset.162 * Truncate down to the nearest directory block to start the scanning163 * operation.164 */165 last_da = xfs_dir2_byte_to_da(geo, XFS_DIR2_LEAF_OFFSET);166 map_off = xfs_dir2_db_to_da(geo, xfs_dir2_byte_to_db(geo, *curoff));167 168 if (!xfs_iext_lookup_extent(dp, ifp, map_off, &icur, &map))169 return 0;170 if (map.br_startoff >= last_da)171 return 0;172 xfs_trim_extent(&map, map_off, last_da - map_off);173 174 /* Read the directory block of that first mapping. */175 new_off = xfs_dir2_da_to_byte(geo, map.br_startoff);176 if (new_off > *curoff)177 *curoff = new_off;178 179 return xfs_dir3_data_read(tp, dp, dp->i_ino, map.br_startoff, 0, bpp);180}181 182/* Call a function for every entry in a leaf directory. */183STATIC int184xchk_dir_walk_leaf(185 struct xfs_scrub *sc,186 struct xfs_inode *dp,187 xchk_dirent_fn dirent_fn,188 void *priv)189{190 struct xfs_mount *mp = dp->i_mount;191 struct xfs_da_geometry *geo = mp->m_dir_geo;192 struct xfs_buf *bp = NULL;193 xfs_dir2_off_t curoff = 0;194 unsigned int offset = 0;195 int error;196 197 /* Iterate every directory offset in this directory. */198 while (curoff < XFS_DIR2_LEAF_OFFSET) {199 struct xfs_name name = { };200 struct xfs_dir2_data_unused *dup;201 struct xfs_dir2_data_entry *dep;202 xfs_ino_t ino;203 unsigned int length;204 xfs_dir2_dataptr_t dapos;205 206 /*207 * If we have no buffer, or we're off the end of the208 * current buffer, need to get another one.209 */210 if (!bp || offset >= geo->blksize) {211 if (bp) {212 xfs_trans_brelse(sc->tp, bp);213 bp = NULL;214 }215 216 error = xchk_read_leaf_dir_buf(sc->tp, dp, geo, &curoff,217 &bp);218 if (error || !bp)219 break;220 221 /*222 * Find our position in the block.223 */224 offset = geo->data_entry_offset;225 curoff += geo->data_entry_offset;226 }227 228 /* Skip an empty entry. */229 dup = bp->b_addr + offset;230 if (be16_to_cpu(dup->freetag) == XFS_DIR2_DATA_FREE_TAG) {231 length = be16_to_cpu(dup->length);232 offset += length;233 curoff += length;234 continue;235 }236 237 /* Otherwise, find the next entry and report it. */238 dep = bp->b_addr + offset;239 length = xfs_dir2_data_entsize(mp, dep->namelen);240 241 dapos = xfs_dir2_byte_to_dataptr(curoff) & 0x7fffffff;242 ino = be64_to_cpu(dep->inumber);243 name.name = dep->name;244 name.len = dep->namelen;245 name.type = xfs_dir2_data_get_ftype(mp, dep);246 247 error = dirent_fn(sc, dp, dapos, &name, ino, priv);248 if (error)249 break;250 251 /* Advance to the next entry. */252 offset += length;253 curoff += length;254 }255 256 if (bp)257 xfs_trans_brelse(sc->tp, bp);258 return error;259}260 261/*262 * Call a function for every entry in a directory.263 *264 * Callers must hold the ILOCK. File types are XFS_DIR3_FT_*.265 */266int267xchk_dir_walk(268 struct xfs_scrub *sc,269 struct xfs_inode *dp,270 xchk_dirent_fn dirent_fn,271 void *priv)272{273 struct xfs_da_args args = {274 .dp = dp,275 .geo = dp->i_mount->m_dir_geo,276 .trans = sc->tp,277 .owner = dp->i_ino,278 };279 int error;280 281 if (xfs_is_shutdown(dp->i_mount))282 return -EIO;283 284 ASSERT(S_ISDIR(VFS_I(dp)->i_mode));285 xfs_assert_ilocked(dp, XFS_ILOCK_SHARED | XFS_ILOCK_EXCL);286 287 switch (xfs_dir2_format(&args, &error)) {288 case XFS_DIR2_FMT_SF:289 return xchk_dir_walk_sf(sc, dp, dirent_fn, priv);290 case XFS_DIR2_FMT_BLOCK:291 return xchk_dir_walk_block(sc, dp, dirent_fn, priv);292 case XFS_DIR2_FMT_LEAF:293 case XFS_DIR2_FMT_NODE:294 return xchk_dir_walk_leaf(sc, dp, dirent_fn, priv);295 default:296 return error;297 }298}299 300/*301 * Look up the inode number for an exact name in a directory.302 *303 * Callers must hold the ILOCK. File types are XFS_DIR3_FT_*. Names are not304 * checked for correctness.305 */306int307xchk_dir_lookup(308 struct xfs_scrub *sc,309 struct xfs_inode *dp,310 const struct xfs_name *name,311 xfs_ino_t *ino)312{313 struct xfs_da_args args = {314 .dp = dp,315 .geo = dp->i_mount->m_dir_geo,316 .trans = sc->tp,317 .name = name->name,318 .namelen = name->len,319 .filetype = name->type,320 .hashval = xfs_dir2_hashname(dp->i_mount, name),321 .whichfork = XFS_DATA_FORK,322 .op_flags = XFS_DA_OP_OKNOENT,323 .owner = dp->i_ino,324 };325 int error;326 327 if (xfs_is_shutdown(dp->i_mount))328 return -EIO;329 330 /*331 * A temporary directory's block headers are written with the owner332 * set to sc->ip, so we must switch the owner here for the lookup.333 */334 if (dp == sc->tempip)335 args.owner = sc->ip->i_ino;336 337 ASSERT(S_ISDIR(VFS_I(dp)->i_mode));338 xfs_assert_ilocked(dp, XFS_ILOCK_SHARED | XFS_ILOCK_EXCL);339 340 error = xfs_dir_lookup_args(&args);341 if (!error)342 *ino = args.inumber;343 return error;344}345 346/*347 * Try to grab the IOLOCK and ILOCK of sc->ip and ip, returning @ip's lock348 * state. The caller may have a transaction, so we must use trylock for both349 * IOLOCKs.350 */351static inline unsigned int352xchk_dir_trylock_both(353 struct xfs_scrub *sc,354 struct xfs_inode *ip)355{356 if (!xchk_ilock_nowait(sc, XFS_IOLOCK_EXCL))357 return 0;358 359 if (!xfs_ilock_nowait(ip, XFS_IOLOCK_SHARED))360 goto parent_iolock;361 362 xchk_ilock(sc, XFS_ILOCK_EXCL);363 if (!xfs_ilock_nowait(ip, XFS_ILOCK_EXCL))364 goto parent_ilock;365 366 return XFS_IOLOCK_SHARED | XFS_ILOCK_EXCL;367 368parent_ilock:369 xchk_iunlock(sc, XFS_ILOCK_EXCL);370 xfs_iunlock(ip, XFS_IOLOCK_SHARED);371parent_iolock:372 xchk_iunlock(sc, XFS_IOLOCK_EXCL);373 return 0;374}375 376/*377 * Try for a limited time to grab the IOLOCK and ILOCK of both the scrub target378 * (@sc->ip) and the inode at the other end (@ip) of a directory or parent379 * pointer link so that we can check that link.380 *381 * We do not know ahead of time that the directory tree is /not/ corrupt, so we382 * cannot use the "lock two inode" functions because we do not know that there383 * is not a racing thread trying to take the locks in opposite order. First384 * take IOLOCK_EXCL of the scrub target, and then try to take IOLOCK_SHARED385 * of @ip to synchronize with the VFS. Next, take ILOCK_EXCL of the scrub386 * target and @ip to synchronize with XFS.387 *388 * If the trylocks succeed, *lockmode will be set to the locks held for @ip;389 * @sc->ilock_flags will be set for the locks held for @sc->ip; and zero will390 * be returned. If not, returns -EDEADLOCK to try again; or -ETIMEDOUT if391 * XCHK_TRY_HARDER was set. Returns -EINTR if the process has been killed.392 */393int394xchk_dir_trylock_for_pptrs(395 struct xfs_scrub *sc,396 struct xfs_inode *ip,397 unsigned int *lockmode)398{399 unsigned int nr;400 int error = 0;401 402 ASSERT(sc->ilock_flags == 0);403 404 for (nr = 0; nr < HZ; nr++) {405 *lockmode = xchk_dir_trylock_both(sc, ip);406 if (*lockmode)407 return 0;408 409 if (xchk_should_terminate(sc, &error))410 return error;411 412 delay(1);413 }414 415 if (sc->flags & XCHK_TRY_HARDER) {416 xchk_set_incomplete(sc);417 return -ETIMEDOUT;418 }419 420 return -EDEADLOCK;421}422