1739a2fe0SDarrick J. Wong // SPDX-License-Identifier: GPL-2.0-or-later
20f28b257SDarrick J. Wong /*
3ecc73f8aSDarrick J. Wong * Copyright (C) 2017-2023 Oracle. All Rights Reserved.
4739a2fe0SDarrick J. Wong * Author: Darrick J. Wong <djwong@kernel.org>
50f28b257SDarrick J. Wong */
60f28b257SDarrick J. Wong #include "xfs.h"
70f28b257SDarrick J. Wong #include "xfs_fs.h"
80f28b257SDarrick J. Wong #include "xfs_shared.h"
90f28b257SDarrick J. Wong #include "xfs_format.h"
100f28b257SDarrick J. Wong #include "xfs_trans_resv.h"
110f28b257SDarrick J. Wong #include "xfs_mount.h"
120f28b257SDarrick J. Wong #include "xfs_log_format.h"
130f28b257SDarrick J. Wong #include "xfs_inode.h"
140f28b257SDarrick J. Wong #include "xfs_icache.h"
150f28b257SDarrick J. Wong #include "xfs_dir2.h"
160f28b257SDarrick J. Wong #include "xfs_dir2_priv.h"
170f28b257SDarrick J. Wong #include "scrub/scrub.h"
180f28b257SDarrick J. Wong #include "scrub/common.h"
194c233b5cSDarrick J. Wong #include "scrub/readdir.h"
200f28b257SDarrick J. Wong
210f28b257SDarrick J. Wong /* Set us up to scrub parents. */
220f28b257SDarrick J. Wong int
xchk_setup_parent(struct xfs_scrub * sc)23c517b3aaSDarrick J. Wong xchk_setup_parent(
24026f57ebSDarrick J. Wong struct xfs_scrub *sc)
250f28b257SDarrick J. Wong {
26026f57ebSDarrick J. Wong return xchk_setup_inode_contents(sc, 0);
270f28b257SDarrick J. Wong }
280f28b257SDarrick J. Wong
290f28b257SDarrick J. Wong /* Parent pointers */
300f28b257SDarrick J. Wong
310f28b257SDarrick J. Wong /* Look for an entry in a parent pointing to this inode. */
320f28b257SDarrick J. Wong
33c517b3aaSDarrick J. Wong struct xchk_parent_ctx {
348feb4732SDarrick J. Wong struct xfs_scrub *sc;
350f28b257SDarrick J. Wong xfs_nlink_t nlink;
360f28b257SDarrick J. Wong };
370f28b257SDarrick J. Wong
380f28b257SDarrick J. Wong /* Look for a single entry in a directory pointing to an inode. */
394c233b5cSDarrick J. Wong STATIC int
xchk_parent_actor(struct xfs_scrub * sc,struct xfs_inode * dp,xfs_dir2_dataptr_t dapos,const struct xfs_name * name,xfs_ino_t ino,void * priv)40c517b3aaSDarrick J. Wong xchk_parent_actor(
414c233b5cSDarrick J. Wong struct xfs_scrub *sc,
424c233b5cSDarrick J. Wong struct xfs_inode *dp,
434c233b5cSDarrick J. Wong xfs_dir2_dataptr_t dapos,
444c233b5cSDarrick J. Wong const struct xfs_name *name,
454c233b5cSDarrick J. Wong xfs_ino_t ino,
464c233b5cSDarrick J. Wong void *priv)
470f28b257SDarrick J. Wong {
484c233b5cSDarrick J. Wong struct xchk_parent_ctx *spc = priv;
498feb4732SDarrick J. Wong int error = 0;
500f28b257SDarrick J. Wong
514c233b5cSDarrick J. Wong /* Does this name make sense? */
524c233b5cSDarrick J. Wong if (!xfs_dir2_namecheck(name->name, name->len))
534c233b5cSDarrick J. Wong error = -EFSCORRUPTED;
544c233b5cSDarrick J. Wong if (!xchk_fblock_xref_process_error(sc, XFS_DATA_FORK, 0, &error))
554c233b5cSDarrick J. Wong return error;
564c233b5cSDarrick J. Wong
574c233b5cSDarrick J. Wong if (sc->ip->i_ino == ino)
580f28b257SDarrick J. Wong spc->nlink++;
598feb4732SDarrick J. Wong
608feb4732SDarrick J. Wong if (xchk_should_terminate(spc->sc, &error))
614c233b5cSDarrick J. Wong return error;
628feb4732SDarrick J. Wong
634c233b5cSDarrick J. Wong return 0;
640f28b257SDarrick J. Wong }
650f28b257SDarrick J. Wong
660f28b257SDarrick J. Wong /*
670916056eSDarrick J. Wong * Try to lock a parent directory for checking dirents. Returns the inode
680916056eSDarrick J. Wong * flags for the locks we now hold, or zero if we failed.
690916056eSDarrick J. Wong */
700916056eSDarrick J. Wong STATIC unsigned int
xchk_parent_ilock_dir(struct xfs_inode * dp)710916056eSDarrick J. Wong xchk_parent_ilock_dir(
720916056eSDarrick J. Wong struct xfs_inode *dp)
730916056eSDarrick J. Wong {
740916056eSDarrick J. Wong if (!xfs_ilock_nowait(dp, XFS_ILOCK_SHARED))
750916056eSDarrick J. Wong return 0;
760916056eSDarrick J. Wong
770916056eSDarrick J. Wong if (!xfs_need_iread_extents(&dp->i_df))
780916056eSDarrick J. Wong return XFS_ILOCK_SHARED;
790916056eSDarrick J. Wong
800916056eSDarrick J. Wong xfs_iunlock(dp, XFS_ILOCK_SHARED);
810916056eSDarrick J. Wong
820916056eSDarrick J. Wong if (!xfs_ilock_nowait(dp, XFS_ILOCK_EXCL))
830916056eSDarrick J. Wong return 0;
840916056eSDarrick J. Wong
850916056eSDarrick J. Wong return XFS_ILOCK_EXCL;
860916056eSDarrick J. Wong }
870916056eSDarrick J. Wong
880916056eSDarrick J. Wong /*
890916056eSDarrick J. Wong * Given the inode number of the alleged parent of the inode being scrubbed,
900916056eSDarrick J. Wong * try to validate that the parent has exactly one directory entry pointing
910916056eSDarrick J. Wong * back to the inode being scrubbed. Returns -EAGAIN if we need to revalidate
920916056eSDarrick J. Wong * the dotdot entry.
930f28b257SDarrick J. Wong */
940f28b257SDarrick J. Wong STATIC int
xchk_parent_validate(struct xfs_scrub * sc,xfs_ino_t parent_ino)95c517b3aaSDarrick J. Wong xchk_parent_validate(
961d8a748aSDarrick J. Wong struct xfs_scrub *sc,
970916056eSDarrick J. Wong xfs_ino_t parent_ino)
980f28b257SDarrick J. Wong {
99cbab28f4SDarrick J. Wong struct xchk_parent_ctx spc = {
100cbab28f4SDarrick J. Wong .sc = sc,
101cbab28f4SDarrick J. Wong .nlink = 0,
102cbab28f4SDarrick J. Wong };
1030f28b257SDarrick J. Wong struct xfs_mount *mp = sc->mp;
1040f28b257SDarrick J. Wong struct xfs_inode *dp = NULL;
1050f28b257SDarrick J. Wong xfs_nlink_t expected_nlink;
1060916056eSDarrick J. Wong unsigned int lock_mode;
10772f76f73SDarrick J. Wong int error = 0;
1080f28b257SDarrick J. Wong
109b049962cSDarrick J. Wong /* Is this the root dir? Then '..' must point to itself. */
110b049962cSDarrick J. Wong if (sc->ip == mp->m_rootip) {
111b049962cSDarrick J. Wong if (sc->ip->i_ino != mp->m_sb.sb_rootino ||
112b049962cSDarrick J. Wong sc->ip->i_ino != parent_ino)
113b049962cSDarrick J. Wong xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, 0);
1140916056eSDarrick J. Wong return 0;
115b049962cSDarrick J. Wong }
1168bc763c2SDarrick J. Wong
1170f28b257SDarrick J. Wong /* '..' must not point to ourselves. */
118b049962cSDarrick J. Wong if (sc->ip->i_ino == parent_ino) {
119c517b3aaSDarrick J. Wong xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, 0);
1200916056eSDarrick J. Wong return 0;
1210f28b257SDarrick J. Wong }
1220f28b257SDarrick J. Wong
1230f28b257SDarrick J. Wong /*
1240f28b257SDarrick J. Wong * If we're an unlinked directory, the parent /won't/ have a link
1250f28b257SDarrick J. Wong * to us. Otherwise, it should have one link.
1260f28b257SDarrick J. Wong */
1270f28b257SDarrick J. Wong expected_nlink = VFS_I(sc->ip)->i_nlink == 0 ? 0 : 1;
1280f28b257SDarrick J. Wong
1290f28b257SDarrick J. Wong /*
130a03297a0SDarrick J. Wong * Grab the parent directory inode. This must be released before we
131a03297a0SDarrick J. Wong * cancel the scrub transaction.
1325927268fSDarrick J. Wong *
133da531cc4SDarrick J. Wong * If _iget returns -EINVAL or -ENOENT then the parent inode number is
134da531cc4SDarrick J. Wong * garbage and the directory is corrupt. If the _iget returns
135da531cc4SDarrick J. Wong * -EFSCORRUPTED or -EFSBADCRC then the parent is corrupt which is a
136da531cc4SDarrick J. Wong * cross referencing error. Any other error is an operational error.
1370f28b257SDarrick J. Wong */
138a03297a0SDarrick J. Wong error = xchk_iget(sc, parent_ino, &dp);
139da531cc4SDarrick J. Wong if (error == -EINVAL || error == -ENOENT) {
1405927268fSDarrick J. Wong error = -EFSCORRUPTED;
141c517b3aaSDarrick J. Wong xchk_fblock_process_error(sc, XFS_DATA_FORK, 0, &error);
1420916056eSDarrick J. Wong return error;
1435927268fSDarrick J. Wong }
144c517b3aaSDarrick J. Wong if (!xchk_fblock_xref_process_error(sc, XFS_DATA_FORK, 0, &error))
1450916056eSDarrick J. Wong return error;
14646c59736SDarrick J. Wong if (dp == sc->ip || !S_ISDIR(VFS_I(dp)->i_mode)) {
147c517b3aaSDarrick J. Wong xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, 0);
1480f28b257SDarrick J. Wong goto out_rele;
1490f28b257SDarrick J. Wong }
1500f28b257SDarrick J. Wong
1510916056eSDarrick J. Wong lock_mode = xchk_parent_ilock_dir(dp);
1520916056eSDarrick J. Wong if (!lock_mode) {
153*294012fbSDarrick J. Wong xchk_iunlock(sc, XFS_ILOCK_EXCL);
154*294012fbSDarrick J. Wong xchk_ilock(sc, XFS_ILOCK_EXCL);
1550916056eSDarrick J. Wong error = -EAGAIN;
156b049962cSDarrick J. Wong goto out_rele;
1570f28b257SDarrick J. Wong }
1580f28b257SDarrick J. Wong
1590916056eSDarrick J. Wong /* Look for a directory entry in the parent pointing to the child. */
160cbab28f4SDarrick J. Wong error = xchk_dir_walk(sc, dp, xchk_parent_actor, &spc);
161c517b3aaSDarrick J. Wong if (!xchk_fblock_xref_process_error(sc, XFS_DATA_FORK, 0, &error))
1620f28b257SDarrick J. Wong goto out_unlock;
1630f28b257SDarrick J. Wong
1640916056eSDarrick J. Wong /*
1650916056eSDarrick J. Wong * Ensure that the parent has as many links to the child as the child
1660916056eSDarrick J. Wong * thinks it has to the parent.
1670916056eSDarrick J. Wong */
168cbab28f4SDarrick J. Wong if (spc.nlink != expected_nlink)
169c517b3aaSDarrick J. Wong xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, 0);
1700f28b257SDarrick J. Wong
1710f28b257SDarrick J. Wong out_unlock:
1720916056eSDarrick J. Wong xfs_iunlock(dp, lock_mode);
1730f28b257SDarrick J. Wong out_rele:
174a03297a0SDarrick J. Wong xchk_irele(sc, dp);
1750f28b257SDarrick J. Wong return error;
1760f28b257SDarrick J. Wong }
1770f28b257SDarrick J. Wong
1780f28b257SDarrick J. Wong /* Scrub a parent pointer. */
1790f28b257SDarrick J. Wong int
xchk_parent(struct xfs_scrub * sc)180c517b3aaSDarrick J. Wong xchk_parent(
1811d8a748aSDarrick J. Wong struct xfs_scrub *sc)
1820f28b257SDarrick J. Wong {
1830f28b257SDarrick J. Wong struct xfs_mount *mp = sc->mp;
184b049962cSDarrick J. Wong xfs_ino_t parent_ino;
18572f76f73SDarrick J. Wong int error = 0;
1860f28b257SDarrick J. Wong
1870f28b257SDarrick J. Wong /*
1880f28b257SDarrick J. Wong * If we're a directory, check that the '..' link points up to
1890f28b257SDarrick J. Wong * a directory that has one entry pointing to us.
1900f28b257SDarrick J. Wong */
1910f28b257SDarrick J. Wong if (!S_ISDIR(VFS_I(sc->ip)->i_mode))
1920f28b257SDarrick J. Wong return -ENOENT;
1930f28b257SDarrick J. Wong
1940f28b257SDarrick J. Wong /* We're not a special inode, are we? */
1950f28b257SDarrick J. Wong if (!xfs_verify_dir_ino(mp, sc->ip->i_ino)) {
196c517b3aaSDarrick J. Wong xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, 0);
1970916056eSDarrick J. Wong return 0;
1980f28b257SDarrick J. Wong }
1990f28b257SDarrick J. Wong
200b049962cSDarrick J. Wong do {
2010916056eSDarrick J. Wong if (xchk_should_terminate(sc, &error))
2020916056eSDarrick J. Wong break;
2030916056eSDarrick J. Wong
2040f28b257SDarrick J. Wong /* Look up '..' */
2050916056eSDarrick J. Wong error = xchk_dir_lookup(sc, sc->ip, &xfs_name_dotdot,
2060916056eSDarrick J. Wong &parent_ino);
207c517b3aaSDarrick J. Wong if (!xchk_fblock_process_error(sc, XFS_DATA_FORK, 0, &error))
2080916056eSDarrick J. Wong return error;
209b049962cSDarrick J. Wong if (!xfs_verify_dir_ino(mp, parent_ino)) {
210c517b3aaSDarrick J. Wong xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, 0);
2110916056eSDarrick J. Wong return 0;
2120f28b257SDarrick J. Wong }
2130f28b257SDarrick J. Wong
2140916056eSDarrick J. Wong /*
2150916056eSDarrick J. Wong * Check that the dotdot entry points to a parent directory
2160916056eSDarrick J. Wong * containing a dirent pointing to this subdirectory.
2170916056eSDarrick J. Wong */
2180916056eSDarrick J. Wong error = xchk_parent_validate(sc, parent_ino);
2190916056eSDarrick J. Wong } while (error == -EAGAIN);
2200f28b257SDarrick J. Wong
2210f28b257SDarrick J. Wong return error;
2220f28b257SDarrick J. Wong }
223