| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421 |
- // SPDX-License-Identifier: GPL-2.0-or-later
- /*
- * Copyright (C) 2022-2023 Oracle. All Rights Reserved.
- * Author: Darrick J. Wong <djwong@kernel.org>
- */
- #include "xfs_platform.h"
- #include "xfs_fs.h"
- #include "xfs_shared.h"
- #include "xfs_format.h"
- #include "xfs_log_format.h"
- #include "xfs_trans_resv.h"
- #include "xfs_mount.h"
- #include "xfs_inode.h"
- #include "xfs_dir2.h"
- #include "xfs_dir2_priv.h"
- #include "xfs_trace.h"
- #include "xfs_bmap.h"
- #include "xfs_trans.h"
- #include "xfs_error.h"
- #include "scrub/scrub.h"
- #include "scrub/common.h"
- #include "scrub/readdir.h"
- /* Call a function for every entry in a shortform directory. */
- STATIC int
- xchk_dir_walk_sf(
- struct xfs_scrub *sc,
- struct xfs_inode *dp,
- xchk_dirent_fn dirent_fn,
- void *priv)
- {
- struct xfs_name name = {
- .name = ".",
- .len = 1,
- .type = XFS_DIR3_FT_DIR,
- };
- struct xfs_mount *mp = dp->i_mount;
- struct xfs_da_geometry *geo = mp->m_dir_geo;
- struct xfs_dir2_sf_entry *sfep;
- struct xfs_dir2_sf_hdr *sfp = dp->i_df.if_data;
- xfs_ino_t ino;
- xfs_dir2_dataptr_t dapos;
- unsigned int i;
- int error;
- ASSERT(dp->i_df.if_bytes == dp->i_disk_size);
- ASSERT(sfp != NULL);
- /* dot entry */
- dapos = xfs_dir2_db_off_to_dataptr(geo, geo->datablk,
- geo->data_entry_offset);
- error = dirent_fn(sc, dp, dapos, &name, dp->i_ino, priv);
- if (error)
- return error;
- /* dotdot entry */
- dapos = xfs_dir2_db_off_to_dataptr(geo, geo->datablk,
- geo->data_entry_offset +
- xfs_dir2_data_entsize(mp, sizeof(".") - 1));
- ino = xfs_dir2_sf_get_parent_ino(sfp);
- name.name = "..";
- name.len = 2;
- error = dirent_fn(sc, dp, dapos, &name, ino, priv);
- if (error)
- return error;
- /* iterate everything else */
- sfep = xfs_dir2_sf_firstentry(sfp);
- for (i = 0; i < sfp->count; i++) {
- dapos = xfs_dir2_db_off_to_dataptr(geo, geo->datablk,
- xfs_dir2_sf_get_offset(sfep));
- ino = xfs_dir2_sf_get_ino(mp, sfp, sfep);
- name.name = sfep->name;
- name.len = sfep->namelen;
- name.type = xfs_dir2_sf_get_ftype(mp, sfep);
- error = dirent_fn(sc, dp, dapos, &name, ino, priv);
- if (error)
- return error;
- sfep = xfs_dir2_sf_nextentry(mp, sfp, sfep);
- }
- return 0;
- }
- /* Call a function for every entry in a block directory. */
- STATIC int
- xchk_dir_walk_block(
- struct xfs_scrub *sc,
- struct xfs_inode *dp,
- xchk_dirent_fn dirent_fn,
- void *priv)
- {
- struct xfs_mount *mp = dp->i_mount;
- struct xfs_da_geometry *geo = mp->m_dir_geo;
- struct xfs_buf *bp;
- unsigned int off, next_off, end;
- int error;
- error = xfs_dir3_block_read(sc->tp, dp, dp->i_ino, &bp);
- if (error)
- return error;
- /* Walk each directory entry. */
- end = xfs_dir3_data_end_offset(geo, bp->b_addr);
- for (off = geo->data_entry_offset; off < end; off = next_off) {
- struct xfs_name name = { };
- struct xfs_dir2_data_unused *dup = bp->b_addr + off;
- struct xfs_dir2_data_entry *dep = bp->b_addr + off;
- xfs_ino_t ino;
- xfs_dir2_dataptr_t dapos;
- /* Skip an empty entry. */
- if (be16_to_cpu(dup->freetag) == XFS_DIR2_DATA_FREE_TAG) {
- next_off = off + be16_to_cpu(dup->length);
- continue;
- }
- /* Otherwise, find the next entry and report it. */
- next_off = off + xfs_dir2_data_entsize(mp, dep->namelen);
- if (next_off > end)
- break;
- dapos = xfs_dir2_db_off_to_dataptr(geo, geo->datablk, off);
- ino = be64_to_cpu(dep->inumber);
- name.name = dep->name;
- name.len = dep->namelen;
- name.type = xfs_dir2_data_get_ftype(mp, dep);
- error = dirent_fn(sc, dp, dapos, &name, ino, priv);
- if (error)
- break;
- }
- xfs_trans_brelse(sc->tp, bp);
- return error;
- }
- /* Read a leaf-format directory buffer. */
- STATIC int
- xchk_read_leaf_dir_buf(
- struct xfs_trans *tp,
- struct xfs_inode *dp,
- struct xfs_da_geometry *geo,
- xfs_dir2_off_t *curoff,
- struct xfs_buf **bpp)
- {
- struct xfs_iext_cursor icur;
- struct xfs_bmbt_irec map;
- struct xfs_ifork *ifp = xfs_ifork_ptr(dp, XFS_DATA_FORK);
- xfs_dablk_t last_da;
- xfs_dablk_t map_off;
- xfs_dir2_off_t new_off;
- *bpp = NULL;
- /*
- * Look for mapped directory blocks at or above the current offset.
- * Truncate down to the nearest directory block to start the scanning
- * operation.
- */
- last_da = xfs_dir2_byte_to_da(geo, XFS_DIR2_LEAF_OFFSET);
- map_off = xfs_dir2_db_to_da(geo, xfs_dir2_byte_to_db(geo, *curoff));
- if (!xfs_iext_lookup_extent(dp, ifp, map_off, &icur, &map))
- return 0;
- if (map.br_startoff >= last_da)
- return 0;
- xfs_trim_extent(&map, map_off, last_da - map_off);
- /* Read the directory block of that first mapping. */
- new_off = xfs_dir2_da_to_byte(geo, map.br_startoff);
- if (new_off > *curoff)
- *curoff = new_off;
- return xfs_dir3_data_read(tp, dp, dp->i_ino, map.br_startoff, 0, bpp);
- }
- /* Call a function for every entry in a leaf directory. */
- STATIC int
- xchk_dir_walk_leaf(
- struct xfs_scrub *sc,
- struct xfs_inode *dp,
- xchk_dirent_fn dirent_fn,
- void *priv)
- {
- struct xfs_mount *mp = dp->i_mount;
- struct xfs_da_geometry *geo = mp->m_dir_geo;
- struct xfs_buf *bp = NULL;
- xfs_dir2_off_t curoff = 0;
- unsigned int offset = 0;
- int error;
- /* Iterate every directory offset in this directory. */
- while (curoff < XFS_DIR2_LEAF_OFFSET) {
- struct xfs_name name = { };
- struct xfs_dir2_data_unused *dup;
- struct xfs_dir2_data_entry *dep;
- xfs_ino_t ino;
- unsigned int length;
- xfs_dir2_dataptr_t dapos;
- /*
- * If we have no buffer, or we're off the end of the
- * current buffer, need to get another one.
- */
- if (!bp || offset >= geo->blksize) {
- if (bp) {
- xfs_trans_brelse(sc->tp, bp);
- bp = NULL;
- }
- error = xchk_read_leaf_dir_buf(sc->tp, dp, geo, &curoff,
- &bp);
- if (error || !bp)
- break;
- /*
- * Find our position in the block.
- */
- offset = geo->data_entry_offset;
- curoff += geo->data_entry_offset;
- }
- /* Skip an empty entry. */
- dup = bp->b_addr + offset;
- if (be16_to_cpu(dup->freetag) == XFS_DIR2_DATA_FREE_TAG) {
- length = be16_to_cpu(dup->length);
- offset += length;
- curoff += length;
- continue;
- }
- /* Otherwise, find the next entry and report it. */
- dep = bp->b_addr + offset;
- length = xfs_dir2_data_entsize(mp, dep->namelen);
- dapos = xfs_dir2_byte_to_dataptr(curoff) & 0x7fffffff;
- ino = be64_to_cpu(dep->inumber);
- name.name = dep->name;
- name.len = dep->namelen;
- name.type = xfs_dir2_data_get_ftype(mp, dep);
- error = dirent_fn(sc, dp, dapos, &name, ino, priv);
- if (error)
- break;
- /* Advance to the next entry. */
- offset += length;
- curoff += length;
- }
- if (bp)
- xfs_trans_brelse(sc->tp, bp);
- return error;
- }
- /*
- * Call a function for every entry in a directory.
- *
- * Callers must hold the ILOCK. File types are XFS_DIR3_FT_*.
- */
- int
- xchk_dir_walk(
- struct xfs_scrub *sc,
- struct xfs_inode *dp,
- xchk_dirent_fn dirent_fn,
- void *priv)
- {
- struct xfs_da_args args = {
- .dp = dp,
- .geo = dp->i_mount->m_dir_geo,
- .trans = sc->tp,
- .owner = dp->i_ino,
- };
- int error;
- if (xfs_is_shutdown(dp->i_mount))
- return -EIO;
- ASSERT(S_ISDIR(VFS_I(dp)->i_mode));
- xfs_assert_ilocked(dp, XFS_ILOCK_SHARED | XFS_ILOCK_EXCL);
- switch (xfs_dir2_format(&args, &error)) {
- case XFS_DIR2_FMT_SF:
- return xchk_dir_walk_sf(sc, dp, dirent_fn, priv);
- case XFS_DIR2_FMT_BLOCK:
- return xchk_dir_walk_block(sc, dp, dirent_fn, priv);
- case XFS_DIR2_FMT_LEAF:
- case XFS_DIR2_FMT_NODE:
- return xchk_dir_walk_leaf(sc, dp, dirent_fn, priv);
- default:
- return error;
- }
- }
- /*
- * Look up the inode number for an exact name in a directory.
- *
- * Callers must hold the ILOCK. File types are XFS_DIR3_FT_*. Names are not
- * checked for correctness.
- */
- int
- xchk_dir_lookup(
- struct xfs_scrub *sc,
- struct xfs_inode *dp,
- const struct xfs_name *name,
- xfs_ino_t *ino)
- {
- struct xfs_da_args args = {
- .dp = dp,
- .geo = dp->i_mount->m_dir_geo,
- .trans = sc->tp,
- .name = name->name,
- .namelen = name->len,
- .filetype = name->type,
- .hashval = xfs_dir2_hashname(dp->i_mount, name),
- .whichfork = XFS_DATA_FORK,
- .op_flags = XFS_DA_OP_OKNOENT,
- .owner = dp->i_ino,
- };
- int error;
- if (xfs_is_shutdown(dp->i_mount))
- return -EIO;
- /*
- * A temporary directory's block headers are written with the owner
- * set to sc->ip, so we must switch the owner here for the lookup.
- */
- if (dp == sc->tempip)
- args.owner = sc->ip->i_ino;
- ASSERT(S_ISDIR(VFS_I(dp)->i_mode));
- xfs_assert_ilocked(dp, XFS_ILOCK_SHARED | XFS_ILOCK_EXCL);
- error = xfs_dir_lookup_args(&args);
- if (!error)
- *ino = args.inumber;
- return error;
- }
- /*
- * Try to grab the IOLOCK and ILOCK of sc->ip and ip, returning @ip's lock
- * state. The caller may have a transaction, so we must use trylock for both
- * IOLOCKs.
- */
- static inline unsigned int
- xchk_dir_trylock_both(
- struct xfs_scrub *sc,
- struct xfs_inode *ip)
- {
- if (!xchk_ilock_nowait(sc, XFS_IOLOCK_EXCL))
- return 0;
- if (!xfs_ilock_nowait(ip, XFS_IOLOCK_SHARED))
- goto parent_iolock;
- xchk_ilock(sc, XFS_ILOCK_EXCL);
- if (!xfs_ilock_nowait(ip, XFS_ILOCK_EXCL))
- goto parent_ilock;
- return XFS_IOLOCK_SHARED | XFS_ILOCK_EXCL;
- parent_ilock:
- xchk_iunlock(sc, XFS_ILOCK_EXCL);
- xfs_iunlock(ip, XFS_IOLOCK_SHARED);
- parent_iolock:
- xchk_iunlock(sc, XFS_IOLOCK_EXCL);
- return 0;
- }
- /*
- * Try for a limited time to grab the IOLOCK and ILOCK of both the scrub target
- * (@sc->ip) and the inode at the other end (@ip) of a directory or parent
- * pointer link so that we can check that link.
- *
- * We do not know ahead of time that the directory tree is /not/ corrupt, so we
- * cannot use the "lock two inode" functions because we do not know that there
- * is not a racing thread trying to take the locks in opposite order. First
- * take IOLOCK_EXCL of the scrub target, and then try to take IOLOCK_SHARED
- * of @ip to synchronize with the VFS. Next, take ILOCK_EXCL of the scrub
- * target and @ip to synchronize with XFS.
- *
- * If the trylocks succeed, *lockmode will be set to the locks held for @ip;
- * @sc->ilock_flags will be set for the locks held for @sc->ip; and zero will
- * be returned. If not, returns -EDEADLOCK to try again; or -ETIMEDOUT if
- * XCHK_TRY_HARDER was set. Returns -EINTR if the process has been killed.
- */
- int
- xchk_dir_trylock_for_pptrs(
- struct xfs_scrub *sc,
- struct xfs_inode *ip,
- unsigned int *lockmode)
- {
- unsigned int nr;
- int error = 0;
- ASSERT(sc->ilock_flags == 0);
- for (nr = 0; nr < HZ; nr++) {
- *lockmode = xchk_dir_trylock_both(sc, ip);
- if (*lockmode)
- return 0;
- if (xchk_should_terminate(sc, &error))
- return error;
- delay(1);
- }
- if (sc->flags & XCHK_TRY_HARDER) {
- xchk_set_incomplete(sc);
- return -ETIMEDOUT;
- }
- return -EDEADLOCK;
- }
|