2024-04-22 19:48:20 +03:00
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright ( c ) 2023 - 2024 Oracle . All Rights Reserved .
* Author : Darrick J . Wong < djwong @ kernel . org >
*/
# 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_trans.h"
# include "xfs_inode.h"
# include "xfs_icache.h"
# include "xfs_dir2.h"
# include "xfs_dir2_priv.h"
# include "xfs_attr.h"
# include "xfs_parent.h"
# include "scrub/scrub.h"
# include "scrub/common.h"
# include "scrub/bitmap.h"
# include "scrub/ino_bitmap.h"
# include "scrub/xfile.h"
# include "scrub/xfarray.h"
# include "scrub/xfblob.h"
# include "scrub/listxattr.h"
# include "scrub/trace.h"
2024-04-22 19:48:22 +03:00
# include "scrub/repair.h"
# include "scrub/orphanage.h"
2024-04-22 19:48:20 +03:00
# include "scrub/dirtree.h"
/*
* Directory Tree Structure Validation
* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
*
* Validating the tree qualities of the directory tree structure can be
* difficult . If the tree is frozen , running a depth ( or breadth ) first search
* and marking a bitmap suffices to determine if there is a cycle . XORing the
* mark bitmap with the inode bitmap afterwards tells us if there are
* disconnected cycles . If the tree is not frozen , directory updates can move
* subtrees across the scanner wavefront , which complicates the design greatly .
*
* Directory parent pointers change that by enabling an incremental approach to
* validation of the tree structure . Instead of using one thread to scan the
* entire filesystem , we instead can have multiple threads walking individual
* subdirectories upwards to the root . In a perfect world , the IOLOCK would
* suffice to stabilize two directories in a parent - > child relationship .
* Unfortunately , the VFS does not take the IOLOCK when moving a child
* subdirectory , so we instead synchronize on ILOCK and use dirent update hooks
* to detect a race . If a race occurs in a path , we restart the scan .
*
* If the walk terminates without reaching the root , we know the path is
* disconnected and ought to be attached to the lost and found . If on the walk
* we find the same subdir that we ' re scanning , we know this is a cycle and
* should delete an incoming edge . If we find multiple paths to the root , we
* know to delete an incoming edge .
*
* There are two big hitches with this approach : first , all file link counts
* must be correct to prevent other writers from doing the wrong thing with the
* directory tree structure . Second , because we ' re walking upwards in a tree
* of arbitrary depth , we cannot hold all the ILOCKs . Instead , we will use a
* directory update hook to invalidate the scan results if one of the paths
* we ' ve scanned has changed .
*/
/* Clean up the dirtree checking resources. */
STATIC void
xchk_dirtree_buf_cleanup (
void * buf )
{
struct xchk_dirtree * dl = buf ;
struct xchk_dirpath * path , * n ;
2024-04-22 19:48:21 +03:00
if ( dl - > scan_ino ! = NULLFSINO )
xfs_dir_hook_del ( dl - > sc - > mp , & dl - > dhook ) ;
2024-04-22 19:48:20 +03:00
xchk_dirtree_for_each_path_safe ( dl , path , n ) {
list_del_init ( & path - > list ) ;
xino_bitmap_destroy ( & path - > seen_inodes ) ;
kfree ( path ) ;
}
xfblob_destroy ( dl - > path_names ) ;
xfarray_destroy ( dl - > path_steps ) ;
mutex_destroy ( & dl - > lock ) ;
}
/* Set us up to look for directory loops. */
int
xchk_setup_dirtree (
struct xfs_scrub * sc )
{
struct xchk_dirtree * dl ;
char * descr ;
int error ;
2024-04-22 19:48:21 +03:00
xchk_fsgates_enable ( sc , XCHK_FSGATES_DIRENTS ) ;
2024-04-22 19:48:22 +03:00
if ( xchk_could_repair ( sc ) ) {
error = xrep_setup_dirtree ( sc ) ;
if ( error )
return error ;
}
2024-04-22 19:48:20 +03:00
dl = kvzalloc ( sizeof ( struct xchk_dirtree ) , XCHK_GFP_FLAGS ) ;
if ( ! dl )
return - ENOMEM ;
dl - > sc = sc ;
dl - > xname . name = dl - > namebuf ;
2024-04-22 19:48:21 +03:00
dl - > hook_xname . name = dl - > hook_namebuf ;
2024-04-22 19:48:20 +03:00
INIT_LIST_HEAD ( & dl - > path_list ) ;
dl - > root_ino = NULLFSINO ;
2024-04-22 19:48:21 +03:00
dl - > scan_ino = NULLFSINO ;
2024-04-22 19:48:22 +03:00
dl - > parent_ino = NULLFSINO ;
2024-04-22 19:48:20 +03:00
mutex_init ( & dl - > lock ) ;
descr = xchk_xfile_ino_descr ( sc , " dirtree path steps " ) ;
error = xfarray_create ( descr , 0 , sizeof ( struct xchk_dirpath_step ) ,
& dl - > path_steps ) ;
kfree ( descr ) ;
if ( error )
goto out_dl ;
descr = xchk_xfile_ino_descr ( sc , " dirtree path names " ) ;
error = xfblob_create ( descr , & dl - > path_names ) ;
kfree ( descr ) ;
if ( error )
goto out_steps ;
error = xchk_setup_inode_contents ( sc , 0 ) ;
if ( error )
goto out_names ;
sc - > buf = dl ;
sc - > buf_cleanup = xchk_dirtree_buf_cleanup ;
return 0 ;
out_names :
xfblob_destroy ( dl - > path_names ) ;
out_steps :
xfarray_destroy ( dl - > path_steps ) ;
out_dl :
mutex_destroy ( & dl - > lock ) ;
kvfree ( dl ) ;
return error ;
}
/*
* Add the parent pointer described by @ dl - > pptr to the given path as a new
* step . Returns - ELNRNG if the path is too deep .
*/
2024-04-22 19:48:22 +03:00
int
2024-04-22 19:48:20 +03:00
xchk_dirpath_append (
struct xchk_dirtree * dl ,
struct xfs_inode * ip ,
struct xchk_dirpath * path ,
const struct xfs_name * name ,
const struct xfs_parent_rec * pptr )
{
struct xchk_dirpath_step step = {
. pptr_rec = * pptr , /* struct copy */
. name_len = name - > len ,
} ;
int error ;
/*
* If this path is more than 2 billion steps long , this directory tree
* is too far gone to fix .
*/
if ( path - > nr_steps > = XFS_MAXLINK )
return - ELNRNG ;
error = xfblob_storename ( dl - > path_names , & step . name_cookie , name ) ;
if ( error )
return error ;
error = xino_bitmap_set ( & path - > seen_inodes , ip - > i_ino ) ;
if ( error )
return error ;
error = xfarray_append ( dl - > path_steps , & step ) ;
if ( error )
return error ;
path - > nr_steps + + ;
return 0 ;
}
/*
* Create an xchk_path for each parent pointer of the directory that we ' re
* scanning . For each path created , we will eventually try to walk towards the
* root with the goal of deleting all parents except for one that leads to the
* root .
*
* Returns - EFSCORRUPTED to signal that the inode being scanned has a corrupt
* parent pointer and hence there ' s no point in continuing ; or - ENOSR if there
* are too many parent pointers for this directory .
*/
STATIC int
xchk_dirtree_create_path (
struct xfs_scrub * sc ,
struct xfs_inode * ip ,
unsigned int attr_flags ,
const unsigned char * name ,
unsigned int namelen ,
const void * value ,
unsigned int valuelen ,
void * priv )
{
struct xfs_name xname = {
. name = name ,
. len = namelen ,
} ;
struct xchk_dirtree * dl = priv ;
struct xchk_dirpath * path ;
const struct xfs_parent_rec * rec = value ;
int error ;
if ( ! ( attr_flags & XFS_ATTR_PARENT ) )
return 0 ;
error = xfs_parent_from_attr ( sc - > mp , attr_flags , name , namelen , value ,
valuelen , NULL , NULL ) ;
if ( error )
return error ;
/*
* If there are more than 2 billion actual parent pointers for this
* subdirectory , this fs is too far gone to fix .
*/
if ( dl - > nr_paths > = XFS_MAXLINK )
return - ENOSR ;
trace_xchk_dirtree_create_path ( sc , ip , dl - > nr_paths , & xname , rec ) ;
/*
* Create a new xchk_path structure to remember this parent pointer
* and record the first name step .
*/
path = kmalloc ( sizeof ( struct xchk_dirpath ) , XCHK_GFP_FLAGS ) ;
if ( ! path )
return - ENOMEM ;
INIT_LIST_HEAD ( & path - > list ) ;
xino_bitmap_init ( & path - > seen_inodes ) ;
path - > nr_steps = 0 ;
path - > outcome = XCHK_DIRPATH_SCANNING ;
error = xchk_dirpath_append ( dl , sc - > ip , path , & xname , rec ) ;
if ( error )
goto out_path ;
path - > first_step = xfarray_length ( dl - > path_steps ) - 1 ;
path - > second_step = XFARRAY_NULLIDX ;
path - > path_nr = dl - > nr_paths ;
list_add_tail ( & path - > list , & dl - > path_list ) ;
dl - > nr_paths + + ;
return 0 ;
out_path :
kfree ( path ) ;
return error ;
}
/*
* Validate that the first step of this path still has a corresponding
* parent pointer in @ sc - > ip . We probably dropped @ sc - > ip ' s ILOCK while
* walking towards the roots , which is why this is necessary .
*
* This function has a side effect of loading the first parent pointer of this
* path into the parent pointer scratch pad . This prepares us to walk up the
* directory tree towards the root . Returns - ESTALE if the scan data is now
* out of date .
*/
STATIC int
xchk_dirpath_revalidate (
struct xchk_dirtree * dl ,
struct xchk_dirpath * path )
{
struct xfs_scrub * sc = dl - > sc ;
int error ;
/*
* Look up the parent pointer that corresponds to the start of this
* path . If the parent pointer has disappeared on us , dump all the
* scan results and try again .
*/
error = xfs_parent_lookup ( sc - > tp , sc - > ip , & dl - > xname , & dl - > pptr_rec ,
& dl - > pptr_args ) ;
if ( error = = - ENOATTR ) {
trace_xchk_dirpath_disappeared ( dl - > sc , sc - > ip , path - > path_nr ,
path - > first_step , & dl - > xname , & dl - > pptr_rec ) ;
dl - > stale = true ;
return - ESTALE ;
}
return error ;
}
/*
* Walk the parent pointers of a directory at the end of a path and record
* the parent that we find in @ dl - > xname / pptr_rec .
*/
STATIC int
xchk_dirpath_find_next_step (
struct xfs_scrub * sc ,
struct xfs_inode * ip ,
unsigned int attr_flags ,
const unsigned char * name ,
unsigned int namelen ,
const void * value ,
unsigned int valuelen ,
void * priv )
{
struct xchk_dirtree * dl = priv ;
const struct xfs_parent_rec * rec = value ;
int error ;
if ( ! ( attr_flags & XFS_ATTR_PARENT ) )
return 0 ;
error = xfs_parent_from_attr ( sc - > mp , attr_flags , name , namelen , value ,
valuelen , NULL , NULL ) ;
if ( error )
return error ;
/*
* If we ' ve already set @ dl - > pptr_rec , then this directory has multiple
* parents . Signal this back to the caller via - EMLINK .
*/
if ( dl - > parents_found > 0 )
return - EMLINK ;
dl - > parents_found + + ;
memcpy ( dl - > namebuf , name , namelen ) ;
dl - > xname . len = namelen ;
dl - > pptr_rec = * rec ; /* struct copy */
return 0 ;
}
/* Set and log the outcome of a path walk. */
static inline void
xchk_dirpath_set_outcome (
struct xchk_dirtree * dl ,
struct xchk_dirpath * path ,
enum xchk_dirpath_outcome outcome )
{
trace_xchk_dirpath_set_outcome ( dl - > sc , path - > path_nr , path - > nr_steps ,
outcome ) ;
path - > outcome = outcome ;
}
/*
* Scan the directory at the end of this path for its parent directory link .
* If we find one , extend the path . Returns - ESTALE if the scan data out of
* date . Returns - EFSCORRUPTED if the parent pointer is bad ; or - ELNRNG if
* the path got too deep .
*/
STATIC int
xchk_dirpath_step_up (
struct xchk_dirtree * dl ,
struct xchk_dirpath * path )
{
struct xfs_scrub * sc = dl - > sc ;
struct xfs_inode * dp ;
xfs_ino_t parent_ino = be64_to_cpu ( dl - > pptr_rec . p_ino ) ;
unsigned int lock_mode ;
int error ;
/* Grab and lock the parent directory. */
error = xchk_iget ( sc , parent_ino , & dp ) ;
if ( error )
return error ;
lock_mode = xfs_ilock_attr_map_shared ( dp ) ;
mutex_lock ( & dl - > lock ) ;
if ( dl - > stale ) {
error = - ESTALE ;
goto out_scanlock ;
}
/* We've reached the root directory; the path is ok. */
if ( parent_ino = = dl - > root_ino ) {
xchk_dirpath_set_outcome ( dl , path , XCHK_DIRPATH_OK ) ;
error = 0 ;
goto out_scanlock ;
}
/*
* The inode being scanned is its own distant ancestor ! Get rid of
* this path .
*/
if ( parent_ino = = sc - > ip - > i_ino ) {
xchk_dirpath_set_outcome ( dl , path , XCHK_DIRPATH_DELETE ) ;
error = 0 ;
goto out_scanlock ;
}
/*
* We ' ve seen this inode before during the path walk . There ' s a loop
* above us in the directory tree . This probably means that we cannot
* continue , but let ' s keep walking paths to get a full picture .
*/
if ( xino_bitmap_test ( & path - > seen_inodes , parent_ino ) ) {
xchk_dirpath_set_outcome ( dl , path , XCHK_DIRPATH_LOOP ) ;
error = 0 ;
goto out_scanlock ;
}
/* The handle encoded in the parent pointer must match. */
if ( VFS_I ( dp ) - > i_generation ! = be32_to_cpu ( dl - > pptr_rec . p_gen ) ) {
trace_xchk_dirpath_badgen ( dl - > sc , dp , path - > path_nr ,
path - > nr_steps , & dl - > xname , & dl - > pptr_rec ) ;
error = - EFSCORRUPTED ;
goto out_scanlock ;
}
/* Parent pointer must point up to a directory. */
if ( ! S_ISDIR ( VFS_I ( dp ) - > i_mode ) ) {
trace_xchk_dirpath_nondir_parent ( dl - > sc , dp , path - > path_nr ,
path - > nr_steps , & dl - > xname , & dl - > pptr_rec ) ;
error = - EFSCORRUPTED ;
goto out_scanlock ;
}
/* Parent cannot be an unlinked directory. */
if ( VFS_I ( dp ) - > i_nlink = = 0 ) {
trace_xchk_dirpath_unlinked_parent ( dl - > sc , dp , path - > path_nr ,
path - > nr_steps , & dl - > xname , & dl - > pptr_rec ) ;
error = - EFSCORRUPTED ;
goto out_scanlock ;
}
/*
* If the extended attributes look as though they has been zapped by
* the inode record repair code , we cannot scan for parent pointers .
*/
if ( xchk_pptr_looks_zapped ( dp ) ) {
error = - EBUSY ;
xchk_set_incomplete ( sc ) ;
goto out_scanlock ;
}
/*
* Walk the parent pointers of @ dp to find the parent of this directory
* to find the next step in our walk . If we find that @ dp has exactly
* one parent , the parent pointer information will be stored in
* @ dl - > pptr_rec . This prepares us for the next step of the walk .
*/
mutex_unlock ( & dl - > lock ) ;
dl - > parents_found = 0 ;
error = xchk_xattr_walk ( sc , dp , xchk_dirpath_find_next_step , NULL , dl ) ;
mutex_lock ( & dl - > lock ) ;
if ( error = = - EFSCORRUPTED | | error = = - EMLINK | |
( ! error & & dl - > parents_found = = 0 ) ) {
/*
* Further up the directory tree from @ sc - > ip , we found a
* corrupt parent pointer , multiple parent pointers while
* finding this directory ' s parent , or zero parents despite
* having a nonzero link count . Keep looking for other paths .
*/
xchk_dirpath_set_outcome ( dl , path , XCHK_DIRPATH_CORRUPT ) ;
error = 0 ;
goto out_scanlock ;
}
if ( error )
goto out_scanlock ;
if ( dl - > stale ) {
error = - ESTALE ;
goto out_scanlock ;
}
trace_xchk_dirpath_found_next_step ( sc , dp , path - > path_nr ,
path - > nr_steps , & dl - > xname , & dl - > pptr_rec ) ;
/* Append to the path steps */
error = xchk_dirpath_append ( dl , dp , path , & dl - > xname , & dl - > pptr_rec ) ;
if ( error )
goto out_scanlock ;
if ( path - > second_step = = XFARRAY_NULLIDX )
path - > second_step = xfarray_length ( dl - > path_steps ) - 1 ;
out_scanlock :
mutex_unlock ( & dl - > lock ) ;
xfs_iunlock ( dp , lock_mode ) ;
xchk_irele ( sc , dp ) ;
return error ;
}
/*
* Walk the directory tree upwards towards what is hopefully the root
* directory , recording path steps as we go . The current path components are
* stored in dl - > pptr_rec and dl - > xname .
*
* Returns - ESTALE if the scan data are out of date . Returns - EFSCORRUPTED
* only if the direct parent pointer of @ sc - > ip associated with this path is
* corrupt .
*/
STATIC int
xchk_dirpath_walk_upwards (
struct xchk_dirtree * dl ,
struct xchk_dirpath * path )
{
struct xfs_scrub * sc = dl - > sc ;
int error ;
ASSERT ( sc - > ilock_flags & XFS_ILOCK_EXCL ) ;
/* Reload the start of this path and make sure it's still there. */
error = xchk_dirpath_revalidate ( dl , path ) ;
if ( error )
return error ;
trace_xchk_dirpath_walk_upwards ( sc , sc - > ip , path - > path_nr , & dl - > xname ,
& dl - > pptr_rec ) ;
/*
* The inode being scanned is its own direct ancestor !
* Get rid of this path .
*/
if ( be64_to_cpu ( dl - > pptr_rec . p_ino ) = = sc - > ip - > i_ino ) {
xchk_dirpath_set_outcome ( dl , path , XCHK_DIRPATH_DELETE ) ;
return 0 ;
}
/*
* Drop ILOCK_EXCL on the inode being scanned . We still hold
* IOLOCK_EXCL on it , so it cannot move around or be renamed .
*
* Beyond this point we ' re walking up the directory tree , which means
* that we can acquire and drop the ILOCK on an alias of sc - > ip . The
* ILOCK state is no longer tracked in the scrub context . Hence we
* must drop @ sc - > ip ' s ILOCK during the walk .
*/
mutex_unlock ( & dl - > lock ) ;
xchk_iunlock ( sc , XFS_ILOCK_EXCL ) ;
/*
* Take the first step in the walk towards the root by checking the
* start of this path , which is a direct parent pointer of @ sc - > ip .
* If we see any kind of error here ( including corruptions ) , the parent
* pointer of @ sc - > ip is corrupt . Stop the whole scan .
*/
error = xchk_dirpath_step_up ( dl , path ) ;
if ( error ) {
xchk_ilock ( sc , XFS_ILOCK_EXCL ) ;
mutex_lock ( & dl - > lock ) ;
return error ;
}
/*
* Take steps upward from the second step in this path towards the
* root . If we hit corruption errors here , there ' s a problem
* * somewhere * in the path , but we don ' t need to stop scanning .
*/
while ( ! error & & path - > outcome = = XCHK_DIRPATH_SCANNING )
error = xchk_dirpath_step_up ( dl , path ) ;
/* Retake the locks we had, mark paths, etc. */
xchk_ilock ( sc , XFS_ILOCK_EXCL ) ;
mutex_lock ( & dl - > lock ) ;
if ( error = = - EFSCORRUPTED ) {
xchk_dirpath_set_outcome ( dl , path , XCHK_DIRPATH_CORRUPT ) ;
error = 0 ;
}
if ( ! error & & dl - > stale )
return - ESTALE ;
return error ;
}
2024-04-22 19:48:21 +03:00
/*
* Decide if this path step has been touched by this live update . Returns
* 1 for yes , 0 for no , or a negative errno .
*/
STATIC int
xchk_dirpath_step_is_stale (
struct xchk_dirtree * dl ,
struct xchk_dirpath * path ,
unsigned int step_nr ,
xfarray_idx_t step_idx ,
struct xfs_dir_update_params * p ,
xfs_ino_t * cursor )
{
struct xchk_dirpath_step step ;
xfs_ino_t child_ino = * cursor ;
int error ;
error = xfarray_load ( dl - > path_steps , step_idx , & step ) ;
if ( error )
return error ;
* cursor = be64_to_cpu ( step . pptr_rec . p_ino ) ;
/*
* If the parent and child being updated are not the ones mentioned in
* this path step , the scan data is still ok .
*/
if ( p - > ip - > i_ino ! = child_ino | | p - > dp - > i_ino ! = * cursor )
return 0 ;
/*
* If the dirent name lengths or byte sequences are different , the scan
* data is still ok .
*/
if ( p - > name - > len ! = step . name_len )
return 0 ;
error = xfblob_loadname ( dl - > path_names , step . name_cookie ,
& dl - > hook_xname , step . name_len ) ;
if ( error )
return error ;
if ( memcmp ( dl - > hook_xname . name , p - > name - > name , p - > name - > len ) ! = 0 )
return 0 ;
2024-04-22 19:48:22 +03:00
/*
* If the update comes from the repair code itself , walk the state
* machine forward .
*/
if ( p - > ip - > i_ino = = dl - > scan_ino & &
path - > outcome = = XREP_DIRPATH_ADOPTING ) {
xchk_dirpath_set_outcome ( dl , path , XREP_DIRPATH_ADOPTED ) ;
return 0 ;
}
if ( p - > ip - > i_ino = = dl - > scan_ino & &
path - > outcome = = XREP_DIRPATH_DELETING ) {
xchk_dirpath_set_outcome ( dl , path , XREP_DIRPATH_DELETED ) ;
return 0 ;
}
2024-04-22 19:48:21 +03:00
/* Exact match, scan data is out of date. */
trace_xchk_dirpath_changed ( dl - > sc , path - > path_nr , step_nr , p - > dp ,
p - > ip , p - > name ) ;
return 1 ;
}
/*
* Decide if this path has been touched by this live update . Returns 1 for
* yes , 0 for no , or a negative errno .
*/
STATIC int
xchk_dirpath_is_stale (
struct xchk_dirtree * dl ,
struct xchk_dirpath * path ,
struct xfs_dir_update_params * p )
{
xfs_ino_t cursor = dl - > scan_ino ;
xfarray_idx_t idx = path - > first_step ;
unsigned int i ;
int ret ;
/*
* The child being updated has not been seen by this path at all ; this
* path cannot be stale .
*/
if ( ! xino_bitmap_test ( & path - > seen_inodes , p - > ip - > i_ino ) )
return 0 ;
ret = xchk_dirpath_step_is_stale ( dl , path , 0 , idx , p , & cursor ) ;
if ( ret ! = 0 )
return ret ;
for ( i = 1 , idx = path - > second_step ; i < path - > nr_steps ; i + + , idx + + ) {
ret = xchk_dirpath_step_is_stale ( dl , path , i , idx , p , & cursor ) ;
if ( ret ! = 0 )
return ret ;
}
return 0 ;
}
/*
* Decide if a directory update from the regular filesystem touches any of the
* paths we ' ve scanned , and invalidate the scan data if true .
*/
STATIC int
xchk_dirtree_live_update (
struct notifier_block * nb ,
unsigned long action ,
void * data )
{
struct xfs_dir_update_params * p = data ;
struct xchk_dirtree * dl ;
struct xchk_dirpath * path ;
int ret ;
dl = container_of ( nb , struct xchk_dirtree , dhook . dirent_hook . nb ) ;
trace_xchk_dirtree_live_update ( dl - > sc , p - > dp , action , p - > ip , p - > delta ,
p - > name ) ;
mutex_lock ( & dl - > lock ) ;
if ( dl - > stale | | dl - > aborted )
goto out_unlock ;
xchk_dirtree_for_each_path ( dl , path ) {
ret = xchk_dirpath_is_stale ( dl , path , p ) ;
if ( ret < 0 ) {
dl - > aborted = true ;
break ;
}
if ( ret = = 1 ) {
dl - > stale = true ;
break ;
}
}
out_unlock :
mutex_unlock ( & dl - > lock ) ;
return NOTIFY_DONE ;
}
2024-04-22 19:48:20 +03:00
/* Delete all the collected path information. */
STATIC void
xchk_dirtree_reset (
void * buf )
{
struct xchk_dirtree * dl = buf ;
struct xchk_dirpath * path , * n ;
ASSERT ( dl - > sc - > ilock_flags & XFS_ILOCK_EXCL ) ;
xchk_dirtree_for_each_path_safe ( dl , path , n ) {
list_del_init ( & path - > list ) ;
xino_bitmap_destroy ( & path - > seen_inodes ) ;
kfree ( path ) ;
}
dl - > nr_paths = 0 ;
xfarray_truncate ( dl - > path_steps ) ;
xfblob_truncate ( dl - > path_names ) ;
dl - > stale = false ;
}
/*
* Load the name / pptr from the first step in this path into @ dl - > pptr_rec and
* @ dl - > xname .
*/
STATIC int
xchk_dirtree_load_path (
struct xchk_dirtree * dl ,
struct xchk_dirpath * path )
{
struct xchk_dirpath_step step ;
int error ;
error = xfarray_load ( dl - > path_steps , path - > first_step , & step ) ;
if ( error )
return error ;
error = xfblob_loadname ( dl - > path_names , step . name_cookie , & dl - > xname ,
step . name_len ) ;
if ( error )
return error ;
dl - > pptr_rec = step . pptr_rec ; /* struct copy */
return 0 ;
}
/*
* For each parent pointer of this subdir , trace a path upwards towards the
* root directory and record what we find . Returns 0 for success ;
* - EFSCORRUPTED if walking the parent pointers of @ sc - > ip failed , - ELNRNG if a
* path was too deep ; - ENOSR if there were too many parent pointers ; or
* a negative errno .
*/
2024-04-22 19:48:22 +03:00
int
2024-04-22 19:48:20 +03:00
xchk_dirtree_find_paths_to_root (
struct xchk_dirtree * dl )
{
struct xfs_scrub * sc = dl - > sc ;
struct xchk_dirpath * path ;
int error = 0 ;
do {
if ( xchk_should_terminate ( sc , & error ) )
return error ;
xchk_dirtree_reset ( dl ) ;
/*
* If the extended attributes look as though they has been
* zapped by the inode record repair code , we cannot scan for
* parent pointers .
*/
if ( xchk_pptr_looks_zapped ( sc - > ip ) ) {
xchk_set_incomplete ( sc ) ;
return - EBUSY ;
}
/*
* Create path walk contexts for each parent of the directory
* that is being scanned . Directories are supposed to have
* only one parent , but this is how we detect multiple parents .
*/
error = xchk_xattr_walk ( sc , sc - > ip , xchk_dirtree_create_path ,
NULL , dl ) ;
if ( error )
return error ;
xchk_dirtree_for_each_path ( dl , path ) {
/* Load path components into dl->pptr/xname */
error = xchk_dirtree_load_path ( dl , path ) ;
if ( error )
return error ;
/*
* Try to walk up each path to the root . This enables
* us to find directory loops in ancestors , and the
* like .
*/
error = xchk_dirpath_walk_upwards ( dl , path ) ;
if ( error = = - EFSCORRUPTED ) {
/*
* A parent pointer of @ sc - > ip is bad , don ' t
* bother continuing .
*/
break ;
}
if ( error = = - ESTALE ) {
/* This had better be an invalidation. */
ASSERT ( dl - > stale ) ;
break ;
}
if ( error )
return error ;
2024-04-22 19:48:21 +03:00
if ( dl - > aborted )
return 0 ;
2024-04-22 19:48:20 +03:00
}
} while ( dl - > stale ) ;
return error ;
}
/*
* Figure out what to do with the paths we tried to find . Do not call this
* if the scan results are stale .
*/
2024-04-22 19:48:22 +03:00
void
2024-04-22 19:48:20 +03:00
xchk_dirtree_evaluate (
struct xchk_dirtree * dl ,
struct xchk_dirtree_outcomes * oc )
{
struct xchk_dirpath * path ;
ASSERT ( ! dl - > stale ) ;
/* Scan the paths we have to decide what to do. */
memset ( oc , 0 , sizeof ( struct xchk_dirtree_outcomes ) ) ;
xchk_dirtree_for_each_path ( dl , path ) {
trace_xchk_dirpath_evaluate_path ( dl - > sc , path - > path_nr ,
path - > nr_steps , path - > outcome ) ;
switch ( path - > outcome ) {
case XCHK_DIRPATH_SCANNING :
/* shouldn't get here */
ASSERT ( 0 ) ;
break ;
case XCHK_DIRPATH_DELETE :
/* This one is already going away. */
oc - > bad + + ;
break ;
case XCHK_DIRPATH_CORRUPT :
case XCHK_DIRPATH_LOOP :
/* Couldn't find the end of this path. */
oc - > suspect + + ;
break ;
case XCHK_DIRPATH_STALE :
/* shouldn't get here either */
ASSERT ( 0 ) ;
break ;
case XCHK_DIRPATH_OK :
/* This path got all the way to the root. */
oc - > good + + ;
break ;
2024-04-22 19:48:22 +03:00
case XREP_DIRPATH_DELETING :
case XREP_DIRPATH_DELETED :
case XREP_DIRPATH_ADOPTING :
case XREP_DIRPATH_ADOPTED :
/* These should not be in progress! */
ASSERT ( 0 ) ;
break ;
2024-04-22 19:48:20 +03:00
}
}
trace_xchk_dirtree_evaluate ( dl , oc ) ;
}
/* Look for directory loops. */
int
xchk_dirtree (
struct xfs_scrub * sc )
{
struct xchk_dirtree_outcomes oc ;
struct xchk_dirtree * dl = sc - > buf ;
int error ;
/*
* Nondirectories do not point downwards to other files , so they cannot
* cause a cycle in the directory tree .
*/
if ( ! S_ISDIR ( VFS_I ( sc - > ip ) - > i_mode ) )
return - ENOENT ;
ASSERT ( xfs_has_parent ( sc - > mp ) ) ;
2024-04-22 19:48:21 +03:00
/*
* Find the root of the directory tree . Remember which directory to
* scan , because the hook doesn ' t detach until after sc - > ip gets
* released during teardown .
*/
2024-04-22 19:48:20 +03:00
dl - > root_ino = sc - > mp - > m_rootip - > i_ino ;
2024-04-22 19:48:21 +03:00
dl - > scan_ino = sc - > ip - > i_ino ;
2024-04-22 19:48:20 +03:00
trace_xchk_dirtree_start ( sc - > ip , sc - > sm , 0 ) ;
2024-04-22 19:48:21 +03:00
/*
* Hook into the directory entry code so that we can capture updates to
* paths that we have already scanned . The scanner thread takes each
* directory ' s ILOCK , which means that any in - progress directory update
* will finish before we can scan the directory .
*/
ASSERT ( sc - > flags & XCHK_FSGATES_DIRENTS ) ;
xfs_dir_hook_setup ( & dl - > dhook , xchk_dirtree_live_update ) ;
error = xfs_dir_hook_add ( sc - > mp , & dl - > dhook ) ;
if ( error )
goto out ;
2024-04-22 19:48:20 +03:00
mutex_lock ( & dl - > lock ) ;
/* Trace each parent pointer's path to the root. */
error = xchk_dirtree_find_paths_to_root ( dl ) ;
if ( error = = - EFSCORRUPTED | | error = = - ELNRNG | | error = = - ENOSR ) {
/*
* Don ' t bother walking the paths if the xattr structure or the
* parent pointers are corrupt ; this scan cannot be completed
* without full information .
*/
xchk_ino_xref_set_corrupt ( sc , sc - > ip - > i_ino ) ;
error = 0 ;
goto out_scanlock ;
}
if ( error = = - EBUSY ) {
/*
* We couldn ' t scan some directory ' s parent pointers because
* the attr fork looked like it had been zapped . The
* scan was marked incomplete , so no further error code
* is necessary .
*/
error = 0 ;
goto out_scanlock ;
}
if ( error )
goto out_scanlock ;
2024-04-22 19:48:21 +03:00
if ( dl - > aborted ) {
xchk_set_incomplete ( sc ) ;
goto out_scanlock ;
}
2024-04-22 19:48:20 +03:00
/* Assess what we found in our path evaluation. */
xchk_dirtree_evaluate ( dl , & oc ) ;
if ( xchk_dirtree_parentless ( dl ) ) {
if ( oc . good | | oc . bad | | oc . suspect )
xchk_ino_set_corrupt ( sc , sc - > ip - > i_ino ) ;
} else {
if ( oc . bad | | oc . good + oc . suspect ! = 1 )
xchk_ino_set_corrupt ( sc , sc - > ip - > i_ino ) ;
if ( oc . suspect )
xchk_ino_xref_set_corrupt ( sc , sc - > ip - > i_ino ) ;
}
out_scanlock :
mutex_unlock ( & dl - > lock ) ;
2024-04-22 19:48:21 +03:00
out :
2024-04-22 19:48:20 +03:00
trace_xchk_dirtree_done ( sc - > ip , sc - > sm , error ) ;
return error ;
}