/* Look for a single entry in a directory pointing to an inode. */ STATICint
xchk_parent_actor( struct xfs_scrub *sc, struct xfs_inode *dp,
xfs_dir2_dataptr_t dapos, conststruct xfs_name *name,
xfs_ino_t ino, void *priv)
{ struct xchk_parent_ctx *spc = priv; int error = 0;
/* Does this name make sense? */ if (!xfs_dir2_namecheck(name->name, name->len))
error = -EFSCORRUPTED; if (!xchk_fblock_xref_process_error(sc, XFS_DATA_FORK, 0, &error)) return error;
if (sc->ip->i_ino == ino)
spc->nlink++;
if (xchk_should_terminate(spc->sc, &error)) return error;
return 0;
}
/* * Try to lock a parent directory for checking dirents. Returns the inode * flags for the locks we now hold, or zero if we failed.
*/ STATICunsignedint
xchk_parent_ilock_dir( struct xfs_inode *dp)
{ if (!xfs_ilock_nowait(dp, XFS_ILOCK_SHARED)) return 0;
if (!xfs_need_iread_extents(&dp->i_df)) return XFS_ILOCK_SHARED;
xfs_iunlock(dp, XFS_ILOCK_SHARED);
if (!xfs_ilock_nowait(dp, XFS_ILOCK_EXCL)) return 0;
return XFS_ILOCK_EXCL;
}
/* * Given the inode number of the alleged parent of the inode being scrubbed, * try to validate that the parent has exactly one directory entry pointing * back to the inode being scrubbed. Returns -EAGAIN if we need to revalidate * the dotdot entry.
*/ STATICint
xchk_parent_validate( struct xfs_scrub *sc,
xfs_ino_t parent_ino)
{ struct xchk_parent_ctx spc = {
.sc = sc,
.nlink = 0,
}; struct xfs_mount *mp = sc->mp; struct xfs_inode *dp = NULL;
xfs_nlink_t expected_nlink; unsignedint lock_mode; int error = 0;
/* Is this the root dir? Then '..' must point to itself. */ if (sc->ip == mp->m_rootip) { if (sc->ip->i_ino != mp->m_sb.sb_rootino ||
sc->ip->i_ino != parent_ino)
xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, 0); return 0;
}
/* Is this the metadata root dir? Then '..' must point to itself. */ if (sc->ip == mp->m_metadirip) { if (sc->ip->i_ino != mp->m_sb.sb_metadirino ||
sc->ip->i_ino != parent_ino)
xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, 0); return 0;
}
/* '..' must not point to ourselves. */ if (sc->ip->i_ino == parent_ino) {
xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, 0); return 0;
}
/* * If we're an unlinked directory, the parent /won't/ have a link * to us. Otherwise, it should have one link.
*/
expected_nlink = VFS_I(sc->ip)->i_nlink == 0 ? 0 : 1;
/* * Grab the parent directory inode. This must be released before we * cancel the scrub transaction. * * If _iget returns -EINVAL or -ENOENT then the parent inode number is * garbage and the directory is corrupt. If the _iget returns * -EFSCORRUPTED or -EFSBADCRC then the parent is corrupt which is a * cross referencing error. Any other error is an operational error.
*/
error = xchk_iget(sc, parent_ino, &dp); if (error == -EINVAL || error == -ENOENT) {
error = -EFSCORRUPTED;
xchk_fblock_process_error(sc, XFS_DATA_FORK, 0, &error); return error;
} if (!xchk_fblock_xref_process_error(sc, XFS_DATA_FORK, 0, &error)) return error; if (dp == sc->ip || xrep_is_tempfile(dp) ||
!S_ISDIR(VFS_I(dp)->i_mode)) {
xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, 0); goto out_rele;
}
/* * We cannot yet validate this parent pointer if the directory looks as * though it has been zapped by the inode record repair code.
*/ if (xchk_dir_looks_zapped(dp)) {
error = -EBUSY;
xchk_set_incomplete(sc); goto out_unlock;
}
/* Metadata and regular inodes cannot cross trees. */ if (xfs_is_metadir_inode(dp) != xfs_is_metadir_inode(sc->ip)) {
xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, 0); goto out_unlock;
}
/* Look for a directory entry in the parent pointing to the child. */
error = xchk_dir_walk(sc, dp, xchk_parent_actor, &spc); if (!xchk_fblock_xref_process_error(sc, XFS_DATA_FORK, 0, &error)) goto out_unlock;
/* * Ensure that the parent has as many links to the child as the child * thinks it has to the parent.
*/ if (spc.nlink != expected_nlink)
xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, 0);
/* * Checking of Parent Pointers * =========================== * * On filesystems with directory parent pointers, we check the referential * integrity by visiting each parent pointer of a child file and checking that * the directory referenced by the pointer actually has a dirent pointing * forward to the child file.
*/
/* Deferred parent pointer entry that we saved for later. */ struct xchk_pptr { /* Cookie for retrieval of the pptr name. */
xfblob_cookie name_cookie;
if (pp->parent_ino == parent_ino) return -ECANCELED;
return 0;
}
/* Look up the dotdot entry so that we can check it as we walk the pptrs. */ STATICint
xchk_parent_pptr_and_dotdot( struct xchk_pptrs *pp)
{ struct xfs_scrub *sc = pp->sc; int error;
/* Look up '..' */
error = xchk_dir_lookup(sc, sc->ip, &xfs_name_dotdot, &pp->parent_ino); if (!xchk_fblock_process_error(sc, XFS_DATA_FORK, 0, &error)) return error; if (!xfs_verify_dir_ino(sc->mp, pp->parent_ino)) {
xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, 0); return 0;
}
/* Is this the root dir? Then '..' must point to itself. */ if (xchk_inode_is_dirtree_root(sc->ip)) { if (sc->ip->i_ino != pp->parent_ino)
xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, 0); return 0;
}
/* * If this is now an unlinked directory, the dotdot value is * meaningless as long as it points to a valid inode.
*/ if (VFS_I(sc->ip)->i_nlink == 0) return 0;
if (pp->sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT) return 0;
/* Otherwise, walk the pptrs again, and check. */
error = xchk_xattr_walk(sc, sc->ip, xchk_parent_scan_dotdot, NULL, pp); if (error == -ECANCELED) { /* Found a parent pointer that matches dotdot. */ return 0;
} if (!error || error == -EFSCORRUPTED) { /* Found a broken parent pointer or no match. */
xchk_fblock_set_corrupt(sc, XFS_ATTR_FORK, 0); return 0;
} return error;
}
/* * Try to lock a parent directory for checking dirents. Returns the inode * flags for the locks we now hold, or zero if we failed.
*/ STATICunsignedint
xchk_parent_lock_dir( struct xfs_scrub *sc, struct xfs_inode *dp)
{ if (!xfs_ilock_nowait(dp, XFS_IOLOCK_SHARED)) return 0;
if (!xfs_ilock_nowait(dp, XFS_ILOCK_SHARED)) {
xfs_iunlock(dp, XFS_IOLOCK_SHARED); return 0;
}
if (!xfs_need_iread_extents(&dp->i_df)) return XFS_IOLOCK_SHARED | XFS_ILOCK_SHARED;
xfs_iunlock(dp, XFS_ILOCK_SHARED);
if (!xfs_ilock_nowait(dp, XFS_ILOCK_EXCL)) {
xfs_iunlock(dp, XFS_IOLOCK_SHARED); return 0;
}
return XFS_IOLOCK_SHARED | XFS_ILOCK_EXCL;
}
/* Check the forward link (dirent) associated with this parent pointer. */ STATICint
xchk_parent_dirent( struct xchk_pptrs *pp, conststruct xfs_name *xname, struct xfs_inode *dp)
{ struct xfs_scrub *sc = pp->sc;
xfs_ino_t child_ino; int error;
/* * Use the name attached to this parent pointer to look up the * directory entry in the alleged parent.
*/
error = xchk_dir_lookup(sc, dp, xname, &child_ino); if (error == -ENOENT) {
xchk_fblock_xref_set_corrupt(sc, XFS_ATTR_FORK, 0); return 0;
} if (!xchk_fblock_xref_process_error(sc, XFS_ATTR_FORK, 0, &error)) return error;
/* Does the inode number match? */ if (child_ino != sc->ip->i_ino) {
xchk_fblock_xref_set_corrupt(sc, XFS_ATTR_FORK, 0); return 0;
}
/* * Walk an xattr of a file. If this xattr is a parent pointer, follow it up * to a parent directory and check that the parent has a dirent pointing back * to us.
*/ STATICint
xchk_parent_scan_attr( struct xfs_scrub *sc, struct xfs_inode *ip, unsignedint attr_flags, constunsignedchar *name, unsignedint namelen, constvoid *value, unsignedint valuelen, void *priv)
{ struct xfs_name xname = {
.name = name,
.len = namelen,
}; struct xchk_pptrs *pp = priv; struct xfs_inode *dp = NULL; conststruct xfs_parent_rec *pptr_rec = value;
xfs_ino_t parent_ino; unsignedint lockmode; int error;
/* * Revalidate a parent pointer that we collected in the past but couldn't check * because of lock contention. Returns 0 if the parent pointer is still valid, * -ENOENT if it has gone away on us, or a negative errno.
*/ STATICint
xchk_parent_revalidate_pptr( struct xchk_pptrs *pp, conststruct xfs_name *xname, struct xfs_parent_rec *pptr)
{ struct xfs_scrub *sc = pp->sc; int error;
error = xfs_parent_lookup(sc->tp, sc->ip, xname, pptr, &pp->pptr_args); if (error == -ENOATTR) { /* Parent pointer went away, nothing to revalidate. */ return -ENOENT;
}
return error;
}
/* * Check a parent pointer the slow way, which means we cycle locks a bunch * and put up with revalidation until we get it done.
*/ STATICint
xchk_parent_slow_pptr( struct xchk_pptrs *pp, conststruct xfs_name *xname, struct xfs_parent_rec *pptr)
{ struct xfs_scrub *sc = pp->sc; struct xfs_inode *dp = NULL; unsignedint lockmode; int error;
/* Check that the deferred parent pointer still exists. */ if (pp->need_revalidate) {
error = xchk_parent_revalidate_pptr(pp, xname, pptr); if (error == -ENOENT) return 0; if (!xchk_fblock_xref_process_error(sc, XFS_ATTR_FORK, 0,
&error)) return error;
}
error = xchk_parent_iget(pp, pptr, &dp); if (error) return error; if (!dp) return 0;
/* * If we can grab both IOLOCK and ILOCK of the alleged parent, we * can proceed with the validation.
*/
lockmode = xchk_parent_lock_dir(sc, dp); if (lockmode) {
trace_xchk_parent_slowpath(sc->ip, xname, dp->i_ino); goto check_dirent;
}
/* * We couldn't lock the parent dir. Drop all the locks and try to * get them again, one at a time.
*/
xchk_iunlock(sc, sc->ilock_flags);
pp->need_revalidate = true;
/* Check all the parent pointers that we deferred the first time around. */ STATICint
xchk_parent_finish_slow_pptrs( struct xchk_pptrs *pp)
{
xfarray_idx_t array_cur; int error;
/* * Compare the number of parent pointers to the link count. For * non-directories these should be the same. For unlinked directories the * count should be zero; for linked directories, it should be nonzero.
*/ STATICint
xchk_parent_count_pptrs( struct xchk_pptrs *pp)
{ struct xfs_scrub *sc = pp->sc; int error;
/* * If we cycled the ILOCK while cross-checking parent pointers with * dirents, then we need to recalculate the number of parent pointers.
*/ if (pp->need_revalidate) {
pp->pptrs_found = 0;
error = xchk_xattr_walk(sc, sc->ip, xchk_parent_count_pptr,
NULL, pp); if (error == -EFSCORRUPTED) { /* Found a bad parent pointer */
xchk_fblock_set_corrupt(sc, XFS_ATTR_FORK, 0); return 0;
} if (error) return error;
}
if (S_ISDIR(VFS_I(sc->ip)->i_mode)) { if (xchk_inode_is_dirtree_root(sc->ip))
pp->pptrs_found++;
if (VFS_I(sc->ip)->i_nlink == 0 && pp->pptrs_found > 0)
xchk_ino_set_corrupt(sc, sc->ip->i_ino); elseif (VFS_I(sc->ip)->i_nlink > 0 &&
pp->pptrs_found == 0)
xchk_ino_set_corrupt(sc, sc->ip->i_ino);
} else { /* * Starting with metadir, we allow checking of parent pointers * of non-directory files that are children of the superblock. * Pretend that we found a parent pointer attr.
*/ if (xfs_has_metadir(sc->mp) && xchk_inode_is_sb_rooted(sc->ip))
pp->pptrs_found++;
if (VFS_I(sc->ip)->i_nlink != pp->pptrs_found)
xchk_ino_set_corrupt(sc, sc->ip->i_ino);
}
return 0;
}
/* Check parent pointers of a file. */ STATICint
xchk_parent_pptr( struct xfs_scrub *sc)
{ struct xchk_pptrs *pp; char *descr; int error;
/* * Set up some staging memory for parent pointers that we can't check * due to locking contention.
*/
descr = xchk_xfile_ino_descr(sc, "slow parent pointer entries");
error = xfarray_create(descr, 0, sizeof(struct xchk_pptr),
&pp->pptr_entries);
kfree(descr); if (error) goto out_pp;
error = xchk_parent_finish_slow_pptrs(pp); if (error == -ETIMEDOUT) { /* Couldn't grab a lock, scrub was marked incomplete */
error = 0; goto out_names;
} if (error) goto out_names;
if (pp->sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT) goto out_names;
/* * For subdirectories, make sure the dotdot entry references the same * inode as the parent pointers. * * If we're scanning a /consistent/ directory, there should only be * one parent pointer, and it should point to the same directory as * the dotdot entry. * * However, a corrupt directory tree might feature a subdirectory with * multiple parents. The directory loop scanner is responsible for * correcting that kind of problem, so for now we only validate that * the dotdot entry matches /one/ of the parents.
*/ if (S_ISDIR(VFS_I(sc->ip)->i_mode)) {
error = xchk_parent_pptr_and_dotdot(pp); if (error) goto out_names;
}
if (pp->sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT) goto out_names;
/* * Complain if the number of parent pointers doesn't match the link * count. This could be a sign of missing parent pointers (or an * incorrect link count).
*/
error = xchk_parent_count_pptrs(pp); if (error) goto out_names;
/* Scrub a parent pointer. */ int
xchk_parent( struct xfs_scrub *sc)
{ struct xfs_mount *mp = sc->mp;
xfs_ino_t parent_ino; int error = 0;
if (xfs_has_parent(mp)) return xchk_parent_pptr(sc);
/* * If we're a directory, check that the '..' link points up to * a directory that has one entry pointing to us.
*/ if (!S_ISDIR(VFS_I(sc->ip)->i_mode)) return -ENOENT;
/* We're not a special inode, are we? */ if (!xfs_verify_dir_ino(mp, sc->ip->i_ino)) {
xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, 0); return 0;
}
do { if (xchk_should_terminate(sc, &error)) break;
/* Look up '..' */
error = xchk_dir_lookup(sc, sc->ip, &xfs_name_dotdot,
&parent_ino); if (!xchk_fblock_process_error(sc, XFS_DATA_FORK, 0, &error)) return error; if (!xfs_verify_dir_ino(mp, parent_ino)) {
xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, 0); return 0;
}
/* * Check that the dotdot entry points to a parent directory * containing a dirent pointing to this subdirectory.
*/
error = xchk_parent_validate(sc, parent_ino);
} while (error == -EAGAIN); if (error == -EBUSY) { /* * We could not scan a directory, so we marked the check * incomplete. No further error return is necessary.
*/ return 0;
}
return error;
}
/* * Decide if this file's extended attributes (and therefore its parent * pointers) have been zapped to satisfy the inode and ifork verifiers. * Checking and repairing should be postponed until the extended attribute * structure is fixed.
*/ bool
xchk_pptr_looks_zapped( struct xfs_inode *ip)
{ struct inode *inode = VFS_I(ip);
ASSERT(xfs_has_parent(ip->i_mount));
/* * Temporary files that cannot be linked into the directory tree do not * have attr forks because they cannot ever have parents.
*/ if (inode->i_nlink == 0 && !(inode->i_state & I_LINKABLE)) returnfalse;
/* * Directory tree roots do not have parents, so the expected outcome * of a parent pointer scan is always the empty set. It's safe to scan * them even if the attr fork was zapped.
*/ if (xchk_inode_is_dirtree_root(ip)) returnfalse;
/* * Metadata inodes that are rooted in the superblock do not have any * parents. Hence the attr fork will not be initialized, but there are * no parent pointers that might have been zapped.
*/ if (xchk_inode_is_sb_rooted(ip)) returnfalse;
/* * Linked and linkable non-rootdir files should always have an * attribute fork because that is where parent pointers are * stored. If the fork is absent, something is amiss.
*/ if (!xfs_inode_has_attr_fork(ip)) returntrue;
/* Repair zapped this file's attr fork a short time ago */ if (xfs_ifork_zapped(ip, XFS_ATTR_FORK)) returntrue;
/* * If the dinode repair found a bad attr fork, it will reset the fork * to extents format with zero records and wait for the bmapbta * scrubber to reconstruct the block mappings. The extended attribute * structure always contain some content when parent pointers are * enabled, so this is a clear sign of a zapped attr fork.
*/ return ip->i_af.if_format == XFS_DINODE_FMT_EXTENTS &&
ip->i_af.if_nextents == 0;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.