2018-06-06 05:42:14 +03:00
// SPDX-License-Identifier: GPL-2.0+
2017-10-18 07:37:46 +03:00
/*
* Copyright ( C ) 2017 Oracle . All Rights Reserved .
* Author : Darrick J . Wong < darrick . wong @ oracle . com >
*/
# include "xfs.h"
# include "xfs_fs.h"
# include "xfs_shared.h"
# include "xfs_format.h"
# include "xfs_trans_resv.h"
# include "xfs_mount.h"
# include "xfs_log_format.h"
# include "xfs_inode.h"
# include "xfs_icache.h"
# include "xfs_dir2.h"
# include "xfs_dir2_priv.h"
# include "scrub/scrub.h"
# include "scrub/common.h"
/* Set us up to scrub parents. */
int
2018-07-19 22:29:11 +03:00
xchk_setup_parent (
2021-04-08 03:59:39 +03:00
struct xfs_scrub * sc )
2017-10-18 07:37:46 +03:00
{
2021-04-08 03:59:39 +03:00
return xchk_setup_inode_contents ( sc , 0 ) ;
2017-10-18 07:37:46 +03:00
}
/* Parent pointers */
/* Look for an entry in a parent pointing to this inode. */
2018-07-19 22:29:11 +03:00
struct xchk_parent_ctx {
2018-07-19 22:29:12 +03:00
struct dir_context dc ;
2019-11-26 05:43:10 +03:00
struct xfs_scrub * sc ;
2018-07-19 22:29:12 +03:00
xfs_ino_t ino ;
xfs_nlink_t nlink ;
2019-11-26 05:43:10 +03:00
bool cancelled ;
2017-10-18 07:37:46 +03:00
} ;
/* Look for a single entry in a directory pointing to an inode. */
STATIC int
2018-07-19 22:29:11 +03:00
xchk_parent_actor (
2018-07-19 22:29:12 +03:00
struct dir_context * dc ,
const char * name ,
int namelen ,
loff_t pos ,
u64 ino ,
unsigned type )
2017-10-18 07:37:46 +03:00
{
2018-07-19 22:29:12 +03:00
struct xchk_parent_ctx * spc ;
2019-11-26 05:43:10 +03:00
int error = 0 ;
2017-10-18 07:37:46 +03:00
2018-07-19 22:29:11 +03:00
spc = container_of ( dc , struct xchk_parent_ctx , dc ) ;
2017-10-18 07:37:46 +03:00
if ( spc - > ino = = ino )
spc - > nlink + + ;
2019-11-26 05:43:10 +03:00
/*
* If we ' re facing a fatal signal , bail out . Store the cancellation
* status separately because the VFS readdir code squashes error codes
* into short directory reads .
*/
if ( xchk_should_terminate ( spc - > sc , & error ) )
spc - > cancelled = true ;
return error ;
2017-10-18 07:37:46 +03:00
}
/* Count the number of dentries in the parent dir that point to this inode. */
STATIC int
2018-07-19 22:29:11 +03:00
xchk_parent_count_parent_dentries (
2018-07-19 22:29:12 +03:00
struct xfs_scrub * sc ,
2018-07-19 22:29:12 +03:00
struct xfs_inode * parent ,
xfs_nlink_t * nlink )
2017-10-18 07:37:46 +03:00
{
2018-07-19 22:29:12 +03:00
struct xchk_parent_ctx spc = {
2019-11-26 05:43:10 +03:00
. dc . actor = xchk_parent_actor ,
. ino = sc - > ip - > i_ino ,
. sc = sc ,
2017-10-18 07:37:46 +03:00
} ;
2018-07-19 22:29:12 +03:00
size_t bufsize ;
loff_t oldpos ;
uint lock_mode ;
int error = 0 ;
2017-10-18 07:37:46 +03:00
/*
* If there are any blocks , read - ahead block 0 as we ' re almost
* certain to have the next operation be a read there . This is
* how we guarantee that the parent ' s extent map has been loaded ,
* if there is one .
*/
lock_mode = xfs_ilock_data_map_shared ( parent ) ;
2020-05-18 20:27:22 +03:00
if ( parent - > i_df . if_nextents > 0 )
2019-11-20 20:46:02 +03:00
error = xfs_dir3_data_readahead ( parent , 0 , 0 ) ;
2017-10-18 07:37:46 +03:00
xfs_iunlock ( parent , lock_mode ) ;
if ( error )
return error ;
/*
* Iterate the parent dir to confirm that there is
* exactly one entry pointing back to the inode being
* scanned .
*/
bufsize = ( size_t ) min_t ( loff_t , XFS_READDIR_BUFSIZE ,
2021-03-29 21:11:40 +03:00
parent - > i_disk_size ) ;
2017-10-18 07:37:46 +03:00
oldpos = 0 ;
while ( true ) {
error = xfs_readdir ( sc - > tp , parent , & spc . dc , bufsize ) ;
if ( error )
goto out ;
2019-11-26 05:43:10 +03:00
if ( spc . cancelled ) {
error = - EAGAIN ;
goto out ;
}
2017-10-18 07:37:46 +03:00
if ( oldpos = = spc . dc . pos )
break ;
oldpos = spc . dc . pos ;
}
* nlink = spc . nlink ;
out :
return error ;
}
/*
* 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 .
*/
STATIC int
2018-07-19 22:29:11 +03:00
xchk_parent_validate (
2018-07-19 22:29:12 +03:00
struct xfs_scrub * sc ,
2018-07-19 22:29:12 +03:00
xfs_ino_t dnum ,
bool * try_again )
2017-10-18 07:37:46 +03:00
{
2018-07-19 22:29:12 +03:00
struct xfs_mount * mp = sc - > mp ;
struct xfs_inode * dp = NULL ;
xfs_nlink_t expected_nlink ;
xfs_nlink_t nlink ;
int error = 0 ;
2017-10-18 07:37:46 +03:00
* try_again = false ;
2018-05-14 16:34:32 +03:00
if ( sc - > sm - > sm_flags & XFS_SCRUB_OFLAG_CORRUPT )
goto out ;
2017-10-18 07:37:46 +03:00
/* '..' must not point to ourselves. */
if ( sc - > ip - > i_ino = = dnum ) {
2018-07-19 22:29:11 +03:00
xchk_fblock_set_corrupt ( sc , XFS_DATA_FORK , 0 ) ;
2017-10-18 07:37:46 +03:00
goto out ;
}
/*
* 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 this parent inode . We release the inode before we
* cancel the scrub transaction . Since we ' re don ' t know a
* priori that releasing the inode won ' t trigger eofblocks
* cleanup ( which allocates what would be a nested transaction )
* if the parent pointer erroneously points to a file , we
* can ' t use DONTCACHE here because DONTCACHE inodes can trigger
* immediate inactive cleanup of the inode .
2018-03-23 20:06:56 +03:00
*
2020-12-02 23:25:44 +03:00
* 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 .
2017-10-18 07:37:46 +03:00
*/
2018-03-23 20:06:56 +03:00
error = xfs_iget ( mp , sc - > tp , dnum , XFS_IGET_UNTRUSTED , 0 , & dp ) ;
2020-12-02 23:25:44 +03:00
if ( error = = - EINVAL | | error = = - ENOENT ) {
2018-03-23 20:06:56 +03:00
error = - EFSCORRUPTED ;
2018-07-19 22:29:11 +03:00
xchk_fblock_process_error ( sc , XFS_DATA_FORK , 0 , & error ) ;
2018-03-23 20:06:56 +03:00
goto out ;
}
2018-07-19 22:29:11 +03:00
if ( ! xchk_fblock_xref_process_error ( sc , XFS_DATA_FORK , 0 , & error ) )
2017-10-18 07:37:46 +03:00
goto out ;
2018-01-09 22:11:42 +03:00
if ( dp = = sc - > ip | | ! S_ISDIR ( VFS_I ( dp ) - > i_mode ) ) {
2018-07-19 22:29:11 +03:00
xchk_fblock_set_corrupt ( sc , XFS_DATA_FORK , 0 ) ;
2017-10-18 07:37:46 +03:00
goto out_rele ;
}
/*
* We prefer to keep the inode locked while we lock and search
* its alleged parent for a forward reference . If we can grab
* the iolock , validate the pointers and we ' re done . We must
* use nowait here to avoid an ABBA deadlock on the parent and
* the child inodes .
*/
if ( xfs_ilock_nowait ( dp , XFS_IOLOCK_SHARED ) ) {
2018-07-19 22:29:11 +03:00
error = xchk_parent_count_parent_dentries ( sc , dp , & nlink ) ;
if ( ! xchk_fblock_xref_process_error ( sc , XFS_DATA_FORK , 0 ,
2017-10-18 07:37:46 +03:00
& error ) )
goto out_unlock ;
if ( nlink ! = expected_nlink )
2018-07-19 22:29:11 +03:00
xchk_fblock_set_corrupt ( sc , XFS_DATA_FORK , 0 ) ;
2017-10-18 07:37:46 +03:00
goto out_unlock ;
}
/*
* The game changes if we get here . We failed to lock the parent ,
* so we ' re going to try to verify both pointers while only holding
* one lock so as to avoid deadlocking with something that ' s actually
* trying to traverse down the directory tree .
*/
xfs_iunlock ( sc - > ip , sc - > ilock_flags ) ;
sc - > ilock_flags = 0 ;
2018-07-19 22:29:11 +03:00
error = xchk_ilock_inverted ( dp , XFS_IOLOCK_SHARED ) ;
2018-05-14 16:34:34 +03:00
if ( error )
goto out_rele ;
2017-10-18 07:37:46 +03:00
/* Go looking for our dentry. */
2018-07-19 22:29:11 +03:00
error = xchk_parent_count_parent_dentries ( sc , dp , & nlink ) ;
if ( ! xchk_fblock_xref_process_error ( sc , XFS_DATA_FORK , 0 , & error ) )
2017-10-18 07:37:46 +03:00
goto out_unlock ;
/* Drop the parent lock, relock this inode. */
xfs_iunlock ( dp , XFS_IOLOCK_SHARED ) ;
2018-07-19 22:29:11 +03:00
error = xchk_ilock_inverted ( sc - > ip , XFS_IOLOCK_EXCL ) ;
2018-05-14 16:34:34 +03:00
if ( error )
goto out_rele ;
2017-10-18 07:37:46 +03:00
sc - > ilock_flags = XFS_IOLOCK_EXCL ;
/*
* If we ' re an unlinked directory , the parent / won ' t / have a link
* to us . Otherwise , it should have one link . We have to re - set
* it here because we dropped the lock on sc - > ip .
*/
expected_nlink = VFS_I ( sc - > ip ) - > i_nlink = = 0 ? 0 : 1 ;
/* Look up '..' to see if the inode changed. */
error = xfs_dir_lookup ( sc - > tp , sc - > ip , & xfs_name_dotdot , & dnum , NULL ) ;
2018-07-19 22:29:11 +03:00
if ( ! xchk_fblock_process_error ( sc , XFS_DATA_FORK , 0 , & error ) )
2017-10-18 07:37:46 +03:00
goto out_rele ;
/* Drat, parent changed. Try again! */
if ( dnum ! = dp - > i_ino ) {
2018-07-25 22:52:32 +03:00
xfs_irele ( dp ) ;
2017-10-18 07:37:46 +03:00
* try_again = true ;
return 0 ;
}
2018-07-25 22:52:32 +03:00
xfs_irele ( dp ) ;
2017-10-18 07:37:46 +03:00
/*
* ' . . ' didn ' t change , so check that there was only one entry
* for us in the parent .
*/
if ( nlink ! = expected_nlink )
2018-07-19 22:29:11 +03:00
xchk_fblock_set_corrupt ( sc , XFS_DATA_FORK , 0 ) ;
2017-10-18 07:37:46 +03:00
return error ;
out_unlock :
xfs_iunlock ( dp , XFS_IOLOCK_SHARED ) ;
out_rele :
2018-07-25 22:52:32 +03:00
xfs_irele ( dp ) ;
2017-10-18 07:37:46 +03:00
out :
return error ;
}
/* Scrub a parent pointer. */
int
2018-07-19 22:29:11 +03:00
xchk_parent (
2018-07-19 22:29:12 +03:00
struct xfs_scrub * sc )
2017-10-18 07:37:46 +03:00
{
2018-07-19 22:29:12 +03:00
struct xfs_mount * mp = sc - > mp ;
xfs_ino_t dnum ;
bool try_again ;
int tries = 0 ;
int error = 0 ;
2017-10-18 07:37:46 +03:00
/*
* 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 ) ) {
2018-07-19 22:29:11 +03:00
xchk_fblock_set_corrupt ( sc , XFS_DATA_FORK , 0 ) ;
2017-10-18 07:37:46 +03:00
goto out ;
}
/*
* The VFS grabs a read or write lock via i_rwsem before it reads
* or writes to a directory . If we ' ve gotten this far we ' ve
* already obtained IOLOCK_EXCL , which ( since 4.10 ) is the same as
* getting a write lock on i_rwsem . Therefore , it is safe for us
* to drop the ILOCK here in order to do directory lookups .
*/
sc - > ilock_flags & = ~ ( XFS_ILOCK_EXCL | XFS_MMAPLOCK_EXCL ) ;
xfs_iunlock ( sc - > ip , XFS_ILOCK_EXCL | XFS_MMAPLOCK_EXCL ) ;
/* Look up '..' */
error = xfs_dir_lookup ( sc - > tp , sc - > ip , & xfs_name_dotdot , & dnum , NULL ) ;
2018-07-19 22:29:11 +03:00
if ( ! xchk_fblock_process_error ( sc , XFS_DATA_FORK , 0 , & error ) )
2017-10-18 07:37:46 +03:00
goto out ;
if ( ! xfs_verify_dir_ino ( mp , dnum ) ) {
2018-07-19 22:29:11 +03:00
xchk_fblock_set_corrupt ( sc , XFS_DATA_FORK , 0 ) ;
2017-10-18 07:37:46 +03:00
goto out ;
}
/* 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 ! = dnum )
2018-07-19 22:29:11 +03:00
xchk_fblock_set_corrupt ( sc , XFS_DATA_FORK , 0 ) ;
2017-10-18 07:37:46 +03:00
goto out ;
}
do {
2018-07-19 22:29:11 +03:00
error = xchk_parent_validate ( sc , dnum , & try_again ) ;
2017-10-18 07:37:46 +03:00
if ( error )
goto out ;
} while ( try_again & & + + tries < 20 ) ;
/*
* We gave it our best shot but failed , so mark this scrub
* incomplete . Userspace can decide if it wants to try again .
*/
if ( try_again & & tries = = 20 )
2018-07-19 22:29:11 +03:00
xchk_set_incomplete ( sc ) ;
2017-10-18 07:37:46 +03:00
out :
2018-05-14 16:34:34 +03:00
/*
* If we failed to lock the parent inode even after a retry , just mark
* this scrub incomplete and return .
*/
2019-04-16 18:21:59 +03:00
if ( ( sc - > flags & XCHK_TRY_HARDER ) & & error = = - EDEADLOCK ) {
2018-05-14 16:34:34 +03:00
error = 0 ;
2018-07-19 22:29:11 +03:00
xchk_set_incomplete ( sc ) ;
2018-05-14 16:34:34 +03:00
}
2017-10-18 07:37:46 +03:00
return error ;
}