2024-04-15 14:54:53 -07:00
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright ( c ) 2020 - 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_defer.h"
# include "xfs_bit.h"
# include "xfs_log_format.h"
# include "xfs_trans.h"
# include "xfs_sb.h"
# include "xfs_inode.h"
# include "xfs_icache.h"
# include "xfs_da_format.h"
# include "xfs_da_btree.h"
# include "xfs_dir2.h"
# include "xfs_bmap_btree.h"
# include "xfs_dir2_priv.h"
# include "xfs_trans_space.h"
# include "xfs_health.h"
# include "xfs_exchmaps.h"
2024-04-22 09:48:12 -07:00
# include "xfs_parent.h"
2024-04-22 09:48:16 -07:00
# include "xfs_attr.h"
# include "xfs_bmap.h"
2024-04-22 09:48:18 -07:00
# include "xfs_ag.h"
2024-04-15 14:54:53 -07:00
# include "scrub/xfs_scrub.h"
# include "scrub/scrub.h"
# include "scrub/common.h"
# include "scrub/trace.h"
# include "scrub/repair.h"
# include "scrub/iscan.h"
# include "scrub/findparent.h"
# include "scrub/readdir.h"
2024-04-15 14:54:55 -07:00
# include "scrub/tempfile.h"
2024-04-22 09:48:16 -07:00
# include "scrub/tempexch.h"
2024-04-15 14:54:55 -07:00
# include "scrub/orphanage.h"
2024-04-22 09:48:12 -07:00
# include "scrub/xfile.h"
# include "scrub/xfarray.h"
# include "scrub/xfblob.h"
2024-04-22 09:48:16 -07:00
# include "scrub/attr_repair.h"
# include "scrub/listxattr.h"
2024-04-15 14:54:53 -07:00
/*
* Repairing The Directory Parent Pointer
* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
*
* Currently , only directories support parent pointers ( in the form of ' . . '
* entries ) , so we simply scan the filesystem and update the ' . . ' entry .
*
* Note that because the only parent pointer is the dotdot entry , we won ' t
* touch an unhealthy directory , since the directory repair code is perfectly
* capable of rebuilding a directory with the proper parent inode .
*
* See the section on locking issues in dir_repair . c for more information about
* conflicts with the VFS . The findparent code wll keep our incore parent
* inode up to date .
2024-04-22 09:48:12 -07:00
*
* If parent pointers are enabled , we instead reconstruct the parent pointer
* information by visiting every directory entry of every directory in the
* system and translating the relevant dirents into parent pointers . In this
* case , it is advantageous to stash all parent pointers created from dirents
* from a single parent file before replaying them into the temporary file . To
* save memory , the live filesystem scan reuses the findparent object . Parent
* pointer repair chooses either directory scanning or findparent , but not
* both .
*
* When salvaging completes , the remaining stashed entries are replayed to the
* temporary file . All non - parent pointer extended attributes are copied to
2024-04-22 09:48:16 -07:00
* the temporary file ' s extended attributes . An atomic file mapping exchange
* is used to commit the new xattr blocks to the file being repaired . This
* will disrupt attrmulti cursors .
2024-04-22 09:48:12 -07:00
*/
2024-04-22 09:48:13 -07:00
/* Create a parent pointer in the tempfile. */
# define XREP_PPTR_ADD (1)
/* Remove a parent pointer from the tempfile. */
# define XREP_PPTR_REMOVE (2)
2024-04-22 09:48:12 -07:00
/* A stashed parent pointer update. */
struct xrep_pptr {
/* Cookie for retrieval of the pptr name. */
xfblob_cookie name_cookie ;
/* Parent pointer record. */
struct xfs_parent_rec pptr_rec ;
/* Length of the pptr name. */
uint8_t namelen ;
2024-04-22 09:48:13 -07:00
/* XREP_PPTR_{ADD,REMOVE} */
uint8_t action ;
2024-04-22 09:48:12 -07:00
} ;
/*
* Stash up to 8 pages of recovered parent pointers in pptr_recs and
* pptr_names before we write them to the temp file .
2024-04-15 14:54:53 -07:00
*/
2024-04-22 09:48:12 -07:00
# define XREP_PARENT_MAX_STASH_BYTES (PAGE_SIZE * 8)
2024-04-15 14:54:53 -07:00
struct xrep_parent {
struct xfs_scrub * sc ;
2024-04-22 09:48:12 -07:00
/* Fixed-size array of xrep_pptr structures. */
struct xfarray * pptr_recs ;
/* Blobs containing parent pointer names. */
struct xfblob * pptr_names ;
2024-04-22 09:48:16 -07:00
/* xattr keys */
struct xfarray * xattr_records ;
/* xattr values */
struct xfblob * xattr_blobs ;
/* Scratch buffers for saving extended attributes */
unsigned char * xattr_name ;
void * xattr_value ;
unsigned int xattr_value_sz ;
/*
* Information used to exchange the attr fork mappings , if the fs
* supports parent pointers .
*/
struct xrep_tempexch tx ;
2024-04-15 14:54:53 -07:00
/*
* Information used to scan the filesystem to find the inumber of the
2024-04-22 09:48:12 -07:00
* dotdot entry for this directory . On filesystems without parent
* pointers , we use the findparent_ * functions on this object and
* access only the parent_ino field directly .
*
* When parent pointers are enabled , the directory entry scanner uses
* the iscan , hooks , and lock fields of this object directly .
* @ pscan . lock coordinates access to pptr_recs , pptr_names , pptr , and
* pptr_scratch . This reduces the memory requirements of this
* structure .
2024-04-22 09:48:16 -07:00
*
* The lock also controls access to xattr_records and xattr_blobs ( ? )
2024-04-15 14:54:53 -07:00
*/
struct xrep_parent_scan_info pscan ;
2024-04-15 14:54:55 -07:00
/* Orphanage reparenting request. */
struct xrep_adoption adoption ;
/* Directory entry name, plus the trailing null. */
struct xfs_name xname ;
unsigned char namebuf [ MAXNAMELEN ] ;
2024-04-22 09:48:12 -07:00
/* Scratch buffer for scanning pptr xattrs */
struct xfs_da_args pptr_args ;
2024-04-22 09:48:16 -07:00
/* Have we seen any live updates of parent pointers recently? */
bool saw_pptr_updates ;
2024-04-22 09:48:18 -07:00
/* Number of parents we found after all other repairs */
unsigned long long parents ;
2024-04-15 14:54:53 -07:00
} ;
2024-04-22 09:48:16 -07:00
struct xrep_parent_xattr {
/* Cookie for retrieval of the xattr name. */
xfblob_cookie name_cookie ;
/* Cookie for retrieval of the xattr value. */
xfblob_cookie value_cookie ;
/* XFS_ATTR_* flags */
int flags ;
/* Length of the value and name. */
uint32_t valuelen ;
uint16_t namelen ;
} ;
/*
* Stash up to 8 pages of attrs in xattr_records / xattr_blobs before we write
* them to the temp file .
*/
# define XREP_PARENT_XATTR_MAX_STASH_BYTES (PAGE_SIZE * 8)
2024-04-15 14:54:53 -07:00
/* Tear down all the incore stuff we created. */
static void
xrep_parent_teardown (
struct xrep_parent * rp )
{
xrep_findparent_scan_teardown ( & rp - > pscan ) ;
2024-04-22 09:48:16 -07:00
kvfree ( rp - > xattr_name ) ;
rp - > xattr_name = NULL ;
kvfree ( rp - > xattr_value ) ;
rp - > xattr_value = NULL ;
if ( rp - > xattr_blobs )
xfblob_destroy ( rp - > xattr_blobs ) ;
rp - > xattr_blobs = NULL ;
if ( rp - > xattr_records )
xfarray_destroy ( rp - > xattr_records ) ;
rp - > xattr_records = NULL ;
2024-04-22 09:48:12 -07:00
if ( rp - > pptr_names )
xfblob_destroy ( rp - > pptr_names ) ;
rp - > pptr_names = NULL ;
if ( rp - > pptr_recs )
xfarray_destroy ( rp - > pptr_recs ) ;
rp - > pptr_recs = NULL ;
2024-04-15 14:54:53 -07:00
}
/* Set up for a parent repair. */
int
xrep_setup_parent (
struct xfs_scrub * sc )
{
struct xrep_parent * rp ;
2024-04-22 09:48:12 -07:00
int error ;
2024-04-15 14:54:53 -07:00
xchk_fsgates_enable ( sc , XCHK_FSGATES_DIRENTS ) ;
rp = kvzalloc ( sizeof ( struct xrep_parent ) , XCHK_GFP_FLAGS ) ;
if ( ! rp )
return - ENOMEM ;
rp - > sc = sc ;
2024-04-15 14:54:55 -07:00
rp - > xname . name = rp - > namebuf ;
2024-04-15 14:54:53 -07:00
sc - > buf = rp ;
2024-04-22 09:48:12 -07:00
error = xrep_tempfile_create ( sc , S_IFREG ) ;
if ( error )
return error ;
2024-04-15 14:54:55 -07:00
return xrep_orphanage_try_create ( sc ) ;
2024-04-15 14:54:53 -07:00
}
/*
* Scan all files in the filesystem for a child dirent that we can turn into
* the dotdot entry for this directory .
*/
STATIC int
xrep_parent_find_dotdot (
struct xrep_parent * rp )
{
struct xfs_scrub * sc = rp - > sc ;
xfs_ino_t ino ;
unsigned int sick , checked ;
int error ;
/*
* Avoid sick directories . There shouldn ' t be anyone else clearing the
* directory ' s sick status .
*/
xfs_inode_measure_sickness ( sc - > ip , & sick , & checked ) ;
if ( sick & XFS_SICK_INO_DIR )
return - EFSCORRUPTED ;
ino = xrep_findparent_self_reference ( sc ) ;
if ( ino ! = NULLFSINO ) {
xrep_findparent_scan_finish_early ( & rp - > pscan , ino ) ;
return 0 ;
}
/*
* Drop the ILOCK on this directory so that we can scan for the dotdot
* entry . Figure out who is going to be the parent of this directory ,
* then retake the ILOCK so that we can salvage directory entries .
*/
xchk_iunlock ( sc , XFS_ILOCK_EXCL ) ;
2024-04-15 14:54:54 -07:00
/* Does the VFS dcache have an answer for us? */
ino = xrep_findparent_from_dcache ( sc ) ;
if ( ino ! = NULLFSINO ) {
error = xrep_findparent_confirm ( sc , & ino ) ;
if ( ! error & & ino ! = NULLFSINO ) {
xrep_findparent_scan_finish_early ( & rp - > pscan , ino ) ;
goto out_relock ;
}
}
/* Scan the entire filesystem for a parent. */
2024-04-15 14:54:53 -07:00
error = xrep_findparent_scan ( & rp - > pscan ) ;
2024-04-15 14:54:54 -07:00
out_relock :
2024-04-15 14:54:53 -07:00
xchk_ilock ( sc , XFS_ILOCK_EXCL ) ;
return error ;
}
2024-04-22 09:48:12 -07:00
/*
* Add this stashed incore parent pointer to the temporary file .
* The caller must hold the tempdir ' s IOLOCK , must not hold any ILOCKs , and
* must not be in transaction context .
*/
STATIC int
xrep_parent_replay_update (
struct xrep_parent * rp ,
const struct xfs_name * xname ,
struct xrep_pptr * pptr )
{
struct xfs_scrub * sc = rp - > sc ;
2024-04-22 09:48:13 -07:00
switch ( pptr - > action ) {
case XREP_PPTR_ADD :
/* Create parent pointer. */
trace_xrep_parent_replay_parentadd ( sc - > tempip , xname ,
& pptr - > pptr_rec ) ;
return xfs_parent_set ( sc - > tempip , sc - > ip - > i_ino , xname ,
& pptr - > pptr_rec , & rp - > pptr_args ) ;
case XREP_PPTR_REMOVE :
/* Remove parent pointer. */
trace_xrep_parent_replay_parentremove ( sc - > tempip , xname ,
& pptr - > pptr_rec ) ;
return xfs_parent_unset ( sc - > tempip , sc - > ip - > i_ino , xname ,
& pptr - > pptr_rec , & rp - > pptr_args ) ;
}
2024-04-22 09:48:12 -07:00
2024-04-22 09:48:13 -07:00
ASSERT ( 0 ) ;
return - EIO ;
2024-04-22 09:48:12 -07:00
}
/*
* Flush stashed parent pointer updates that have been recorded by the scanner .
* This is done to reduce the memory requirements of the parent pointer
* rebuild , since files can have a lot of hardlinks and the fs can be busy .
*
* Caller must not hold transactions or ILOCKs . Caller must hold the tempfile
* IOLOCK .
*/
STATIC int
xrep_parent_replay_updates (
struct xrep_parent * rp )
{
xfarray_idx_t array_cur ;
int error ;
mutex_lock ( & rp - > pscan . lock ) ;
foreach_xfarray_idx ( rp - > pptr_recs , array_cur ) {
struct xrep_pptr pptr ;
error = xfarray_load ( rp - > pptr_recs , array_cur , & pptr ) ;
if ( error )
goto out_unlock ;
error = xfblob_loadname ( rp - > pptr_names , pptr . name_cookie ,
& rp - > xname , pptr . namelen ) ;
if ( error )
goto out_unlock ;
rp - > xname . len = pptr . namelen ;
mutex_unlock ( & rp - > pscan . lock ) ;
error = xrep_parent_replay_update ( rp , & rp - > xname , & pptr ) ;
if ( error )
return error ;
mutex_lock ( & rp - > pscan . lock ) ;
}
/* Empty out both arrays now that we've added the entries. */
xfarray_truncate ( rp - > pptr_recs ) ;
xfblob_truncate ( rp - > pptr_names ) ;
mutex_unlock ( & rp - > pscan . lock ) ;
return 0 ;
out_unlock :
mutex_unlock ( & rp - > pscan . lock ) ;
return error ;
}
/*
* Remember that we want to create a parent pointer in the tempfile . These
* stashed actions will be replayed later .
*/
STATIC int
xrep_parent_stash_parentadd (
struct xrep_parent * rp ,
const struct xfs_name * name ,
const struct xfs_inode * dp )
{
struct xrep_pptr pptr = {
2024-04-22 09:48:13 -07:00
. action = XREP_PPTR_ADD ,
2024-04-22 09:48:12 -07:00
. namelen = name - > len ,
} ;
int error ;
trace_xrep_parent_stash_parentadd ( rp - > sc - > tempip , dp , name ) ;
xfs_inode_to_parent_rec ( & pptr . pptr_rec , dp ) ;
error = xfblob_storename ( rp - > pptr_names , & pptr . name_cookie , name ) ;
if ( error )
return error ;
return xfarray_append ( rp - > pptr_recs , & pptr ) ;
}
2024-04-22 09:48:13 -07:00
/*
* Remember that we want to remove a parent pointer from the tempfile . These
* stashed actions will be replayed later .
*/
STATIC int
xrep_parent_stash_parentremove (
struct xrep_parent * rp ,
const struct xfs_name * name ,
const struct xfs_inode * dp )
{
struct xrep_pptr pptr = {
. action = XREP_PPTR_REMOVE ,
. namelen = name - > len ,
} ;
int error ;
trace_xrep_parent_stash_parentremove ( rp - > sc - > tempip , dp , name ) ;
xfs_inode_to_parent_rec ( & pptr . pptr_rec , dp ) ;
error = xfblob_storename ( rp - > pptr_names , & pptr . name_cookie , name ) ;
if ( error )
return error ;
return xfarray_append ( rp - > pptr_recs , & pptr ) ;
}
2024-04-22 09:48:12 -07:00
/*
* Examine an entry of a directory . If this dirent leads us back to the file
* whose parent pointers we ' re rebuilding , add a pptr to the temporary
* directory .
*/
STATIC int
xrep_parent_scan_dirent (
struct xfs_scrub * sc ,
struct xfs_inode * dp ,
xfs_dir2_dataptr_t dapos ,
const struct xfs_name * name ,
xfs_ino_t ino ,
void * priv )
{
struct xrep_parent * rp = priv ;
int error ;
/* Dirent doesn't point to this directory. */
if ( ino ! = rp - > sc - > ip - > i_ino )
return 0 ;
/* No weird looking names. */
if ( name - > len = = 0 | | ! xfs_dir2_namecheck ( name - > name , name - > len ) )
return - EFSCORRUPTED ;
/* No mismatching ftypes. */
if ( name - > type ! = xfs_mode_to_ftype ( VFS_I ( sc - > ip ) - > i_mode ) )
return - EFSCORRUPTED ;
/* Don't pick up dot or dotdot entries; we only want child dirents. */
if ( xfs_dir2_samename ( name , & xfs_name_dotdot ) | |
xfs_dir2_samename ( name , & xfs_name_dot ) )
return 0 ;
/*
* Transform this dirent into a parent pointer and queue it for later
* addition to the temporary file .
*/
mutex_lock ( & rp - > pscan . lock ) ;
error = xrep_parent_stash_parentadd ( rp , name , dp ) ;
mutex_unlock ( & rp - > pscan . lock ) ;
return error ;
}
/*
* Decide if we want to look for dirents in this directory . Skip the file
* being repaired and any files being used to stage repairs .
*/
static inline bool
xrep_parent_want_scan (
struct xrep_parent * rp ,
const struct xfs_inode * ip )
{
return ip ! = rp - > sc - > ip & & ! xrep_is_tempfile ( ip ) ;
}
/*
* Take ILOCK on a file that we want to scan .
*
* Select ILOCK_EXCL if the file is a directory with an unloaded data bmbt .
* Otherwise , take ILOCK_SHARED .
*/
static inline unsigned int
xrep_parent_scan_ilock (
struct xrep_parent * rp ,
struct xfs_inode * ip )
{
uint lock_mode = XFS_ILOCK_SHARED ;
/* Still need to take the shared ILOCK to advance the iscan cursor. */
if ( ! xrep_parent_want_scan ( rp , ip ) )
goto lock ;
if ( S_ISDIR ( VFS_I ( ip ) - > i_mode ) & & xfs_need_iread_extents ( & ip - > i_df ) ) {
lock_mode = XFS_ILOCK_EXCL ;
goto lock ;
}
lock :
xfs_ilock ( ip , lock_mode ) ;
return lock_mode ;
}
/*
* Scan this file for relevant child dirents that point to the file whose
* parent pointers we ' re rebuilding .
*/
STATIC int
xrep_parent_scan_file (
struct xrep_parent * rp ,
struct xfs_inode * ip )
{
unsigned int lock_mode ;
int error = 0 ;
lock_mode = xrep_parent_scan_ilock ( rp , ip ) ;
if ( ! xrep_parent_want_scan ( rp , ip ) )
goto scan_done ;
if ( S_ISDIR ( VFS_I ( ip ) - > i_mode ) ) {
/*
* If the directory looks as though it has been zapped by the
* inode record repair code , we cannot scan for child dirents .
*/
if ( xchk_dir_looks_zapped ( ip ) ) {
error = - EBUSY ;
goto scan_done ;
}
error = xchk_dir_walk ( rp - > sc , ip , xrep_parent_scan_dirent , rp ) ;
if ( error )
goto scan_done ;
}
scan_done :
xchk_iscan_mark_visited ( & rp - > pscan . iscan , ip ) ;
xfs_iunlock ( ip , lock_mode ) ;
return error ;
}
/* Decide if we've stashed too much pptr data in memory. */
static inline bool
xrep_parent_want_flush_stashed (
struct xrep_parent * rp )
{
unsigned long long bytes ;
bytes = xfarray_bytes ( rp - > pptr_recs ) + xfblob_bytes ( rp - > pptr_names ) ;
return bytes > XREP_PARENT_MAX_STASH_BYTES ;
}
/*
* Scan all directories in the filesystem to look for dirents that we can turn
* into parent pointers .
*/
STATIC int
xrep_parent_scan_dirtree (
struct xrep_parent * rp )
{
struct xfs_scrub * sc = rp - > sc ;
struct xfs_inode * ip ;
int error ;
/*
* Filesystem scans are time consuming . Drop the file ILOCK and all
* other resources for the duration of the scan and hope for the best .
* The live update hooks will keep our scan information up to date .
*/
xchk_trans_cancel ( sc ) ;
if ( sc - > ilock_flags & ( XFS_ILOCK_SHARED | XFS_ILOCK_EXCL ) )
xchk_iunlock ( sc , sc - > ilock_flags & ( XFS_ILOCK_SHARED |
XFS_ILOCK_EXCL ) ) ;
error = xchk_trans_alloc_empty ( sc ) ;
if ( error )
return error ;
while ( ( error = xchk_iscan_iter ( & rp - > pscan . iscan , & ip ) ) = = 1 ) {
bool flush ;
error = xrep_parent_scan_file ( rp , ip ) ;
xchk_irele ( sc , ip ) ;
if ( error )
break ;
/* Flush stashed pptr updates to constrain memory usage. */
mutex_lock ( & rp - > pscan . lock ) ;
flush = xrep_parent_want_flush_stashed ( rp ) ;
mutex_unlock ( & rp - > pscan . lock ) ;
if ( flush ) {
xchk_trans_cancel ( sc ) ;
error = xrep_tempfile_iolock_polled ( sc ) ;
if ( error )
break ;
error = xrep_parent_replay_updates ( rp ) ;
xrep_tempfile_iounlock ( sc ) ;
if ( error )
break ;
error = xchk_trans_alloc_empty ( sc ) ;
if ( error )
break ;
}
if ( xchk_should_terminate ( sc , & error ) )
break ;
}
xchk_iscan_iter_finish ( & rp - > pscan . iscan ) ;
if ( error ) {
/*
* If we couldn ' t grab an inode that was busy with a state
* change , change the error code so that we exit to userspace
* as quickly as possible .
*/
if ( error = = - EBUSY )
return - ECANCELED ;
return error ;
}
/*
2024-04-22 09:48:16 -07:00
* Retake sc - > ip ' s ILOCK now that we ' re done flushing stashed parent
* pointers . We end this function with an empty transaction and the
* ILOCK .
2024-04-22 09:48:12 -07:00
*/
2024-04-22 09:48:16 -07:00
xchk_ilock ( rp - > sc , XFS_ILOCK_EXCL ) ;
2024-04-22 09:48:12 -07:00
return 0 ;
}
2024-04-22 09:48:13 -07:00
/*
* Capture dirent updates being made by other threads which are relevant to the
* file being repaired .
*/
STATIC int
xrep_parent_live_update (
struct notifier_block * nb ,
unsigned long action ,
void * data )
{
struct xfs_dir_update_params * p = data ;
struct xrep_parent * rp ;
struct xfs_scrub * sc ;
int error ;
rp = container_of ( nb , struct xrep_parent , pscan . dhook . dirent_hook . nb ) ;
sc = rp - > sc ;
/*
* This thread updated a dirent that points to the file that we ' re
* repairing , so stash the update for replay against the temporary
* file .
*/
if ( p - > ip - > i_ino = = sc - > ip - > i_ino & &
xchk_iscan_want_live_update ( & rp - > pscan . iscan , p - > dp - > i_ino ) ) {
mutex_lock ( & rp - > pscan . lock ) ;
if ( p - > delta > 0 )
error = xrep_parent_stash_parentadd ( rp , p - > name , p - > dp ) ;
else
error = xrep_parent_stash_parentremove ( rp , p - > name ,
p - > dp ) ;
2024-04-22 09:48:16 -07:00
if ( ! error )
rp - > saw_pptr_updates = true ;
2024-04-22 09:48:13 -07:00
mutex_unlock ( & rp - > pscan . lock ) ;
if ( error )
goto out_abort ;
}
return NOTIFY_DONE ;
out_abort :
xchk_iscan_abort ( & rp - > pscan . iscan ) ;
return NOTIFY_DONE ;
}
2024-04-15 14:54:53 -07:00
/* Reset a directory's dotdot entry, if needed. */
STATIC int
xrep_parent_reset_dotdot (
struct xrep_parent * rp )
{
struct xfs_scrub * sc = rp - > sc ;
xfs_ino_t ino ;
unsigned int spaceres ;
int error = 0 ;
ASSERT ( sc - > ilock_flags & XFS_ILOCK_EXCL ) ;
error = xchk_dir_lookup ( sc , sc - > ip , & xfs_name_dotdot , & ino ) ;
if ( error | | ino = = rp - > pscan . parent_ino )
return error ;
xfs_trans_ijoin ( sc - > tp , sc - > ip , 0 ) ;
trace_xrep_parent_reset_dotdot ( sc - > ip , rp - > pscan . parent_ino ) ;
/*
* Reserve more space just in case we have to expand the dir . We ' re
* allowed to exceed quota to repair inconsistent metadata .
*/
2024-04-22 09:47:50 -07:00
spaceres = xfs_rename_space_res ( sc - > mp , 0 , false , xfs_name_dotdot . len ,
false ) ;
2024-04-15 14:54:53 -07:00
error = xfs_trans_reserve_more_inode ( sc - > tp , sc - > ip , spaceres , 0 ,
true ) ;
if ( error )
return error ;
error = xfs_dir_replace ( sc - > tp , sc - > ip , & xfs_name_dotdot ,
rp - > pscan . parent_ino , spaceres ) ;
if ( error )
return error ;
/*
* Roll transaction to detach the inode from the transaction but retain
* ILOCK_EXCL .
*/
return xfs_trans_roll ( & sc - > tp ) ;
}
2024-04-22 09:48:16 -07:00
/* Pass back the parent inumber if this a parent pointer */
STATIC int
xrep_parent_lookup_pptr (
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 )
{
xfs_ino_t * inop = priv ;
xfs_ino_t parent_ino ;
int error ;
if ( ! ( attr_flags & XFS_ATTR_PARENT ) )
return 0 ;
error = xfs_parent_from_attr ( sc - > mp , attr_flags , name , namelen , value ,
valuelen , & parent_ino , NULL ) ;
if ( error )
return error ;
* inop = parent_ino ;
return - ECANCELED ;
}
/*
* Find the first parent of the scrub target by walking parent pointers for
* the purpose of deciding if we ' re going to move it to the orphanage .
* We don ' t care if the attr fork is zapped .
*/
STATIC int
xrep_parent_lookup_pptrs (
struct xfs_scrub * sc ,
xfs_ino_t * inop )
{
int error ;
* inop = NULLFSINO ;
error = xchk_xattr_walk ( sc , sc - > ip , xrep_parent_lookup_pptr , NULL ,
inop ) ;
if ( error & & error ! = - ECANCELED )
return error ;
return 0 ;
}
2024-04-15 14:54:55 -07:00
/*
* Move the current file to the orphanage .
*
* Caller must hold IOLOCK_EXCL on @ sc - > ip , and no other inode locks . Upon
* successful return , the scrub transaction will have enough extra reservation
* to make the move ; it will hold IOLOCK_EXCL and ILOCK_EXCL of @ sc - > ip and the
* orphanage ; and both inodes will be ijoined .
*/
STATIC int
xrep_parent_move_to_orphanage (
struct xrep_parent * rp )
{
struct xfs_scrub * sc = rp - > sc ;
xfs_ino_t orig_parent , new_parent ;
int error ;
2024-04-22 09:48:16 -07:00
if ( S_ISDIR ( VFS_I ( sc - > ip ) - > i_mode ) ) {
/*
* We are about to drop the ILOCK on sc - > ip to lock the
* orphanage and prepare for the adoption . Therefore , look up
* the old dotdot entry for sc - > ip so that we can compare it
* after we re - lock sc - > ip .
*/
error = xchk_dir_lookup ( sc , sc - > ip , & xfs_name_dotdot ,
& orig_parent ) ;
if ( error )
return error ;
} else {
/*
* We haven ' t dropped the ILOCK since we committed the new
* xattr structure ( and hence the new parent pointer records ) ,
* which means that the file cannot have been moved in the
* directory tree , and there are no parents .
*/
orig_parent = NULLFSINO ;
}
2024-04-15 14:54:55 -07:00
/*
* Drop the ILOCK on the scrub target and commit the transaction .
* Adoption computes its own resource requirements and gathers the
* necessary components .
*/
error = xrep_trans_commit ( sc ) ;
if ( error )
return error ;
xchk_iunlock ( sc , XFS_ILOCK_EXCL ) ;
/* If we can take the orphanage's iolock then we're ready to move. */
if ( ! xrep_orphanage_ilock_nowait ( sc , XFS_IOLOCK_EXCL ) ) {
xchk_iunlock ( sc , sc - > ilock_flags ) ;
error = xrep_orphanage_iolock_two ( sc ) ;
if ( error )
return error ;
}
/* Grab transaction and ILOCK the two files. */
error = xrep_adoption_trans_alloc ( sc , & rp - > adoption ) ;
if ( error )
return error ;
error = xrep_adoption_compute_name ( & rp - > adoption , & rp - > xname ) ;
if ( error )
return error ;
/*
* Now that we ' ve reacquired the ILOCK on sc - > ip , look up the dotdot
* entry again . If the parent changed or the child was unlinked while
* the child directory was unlocked , we don ' t need to move the child to
2024-04-22 09:48:16 -07:00
* the orphanage after all . For a non - directory , we have to scan for
* the first parent pointer to see if one has been added .
2024-04-15 14:54:55 -07:00
*/
2024-04-22 09:48:16 -07:00
if ( S_ISDIR ( VFS_I ( sc - > ip ) - > i_mode ) )
error = xchk_dir_lookup ( sc , sc - > ip , & xfs_name_dotdot ,
& new_parent ) ;
else
error = xrep_parent_lookup_pptrs ( sc , & new_parent ) ;
2024-04-15 14:54:55 -07:00
if ( error )
return error ;
/*
* Attach to the orphanage if we still have a linked directory and it
* hasn ' t been moved .
*/
if ( orig_parent = = new_parent & & VFS_I ( sc - > ip ) - > i_nlink > 0 ) {
error = xrep_adoption_move ( & rp - > adoption ) ;
if ( error )
return error ;
}
/*
* Launder the scrub transaction so we can drop the orphanage ILOCK
* and IOLOCK . Return holding the scrub target ' s ILOCK and IOLOCK .
*/
error = xrep_adoption_trans_roll ( & rp - > adoption ) ;
if ( error )
return error ;
xrep_orphanage_iunlock ( sc , XFS_ILOCK_EXCL ) ;
xrep_orphanage_iunlock ( sc , XFS_IOLOCK_EXCL ) ;
return 0 ;
}
2024-04-22 09:48:16 -07:00
/* Ensure that the xattr value buffer is large enough. */
STATIC int
xrep_parent_alloc_xattr_value (
struct xrep_parent * rp ,
size_t bufsize )
{
void * new_val ;
if ( rp - > xattr_value_sz > = bufsize )
return 0 ;
if ( rp - > xattr_value ) {
kvfree ( rp - > xattr_value ) ;
rp - > xattr_value = NULL ;
rp - > xattr_value_sz = 0 ;
}
new_val = kvmalloc ( bufsize , XCHK_GFP_FLAGS ) ;
if ( ! new_val )
return - ENOMEM ;
rp - > xattr_value = new_val ;
rp - > xattr_value_sz = bufsize ;
return 0 ;
}
/* Retrieve the (remote) value of a non-pptr xattr. */
STATIC int
xrep_parent_fetch_xattr_remote (
struct xrep_parent * rp ,
struct xfs_inode * ip ,
unsigned int attr_flags ,
const unsigned char * name ,
unsigned int namelen ,
unsigned int valuelen )
{
struct xfs_scrub * sc = rp - > sc ;
struct xfs_da_args args = {
. attr_filter = attr_flags & XFS_ATTR_NSP_ONDISK_MASK ,
. geo = sc - > mp - > m_attr_geo ,
. whichfork = XFS_ATTR_FORK ,
. dp = ip ,
. name = name ,
. namelen = namelen ,
. trans = sc - > tp ,
. valuelen = valuelen ,
. owner = ip - > i_ino ,
} ;
int error ;
/*
* If we need a larger value buffer , try to allocate one . If that
* fails , return with - EDEADLOCK to try harder .
*/
error = xrep_parent_alloc_xattr_value ( rp , valuelen ) ;
if ( error = = - ENOMEM )
return - EDEADLOCK ;
if ( error )
return error ;
args . value = rp - > xattr_value ;
xfs_attr_sethash ( & args ) ;
return xfs_attr_get_ilocked ( & args ) ;
}
/* Stash non-pptr attributes for later replay into the temporary file. */
STATIC int
xrep_parent_stash_xattr (
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 xrep_parent_xattr key = {
. valuelen = valuelen ,
. namelen = namelen ,
. flags = attr_flags & XFS_ATTR_NSP_ONDISK_MASK ,
} ;
struct xrep_parent * rp = priv ;
int error ;
if ( attr_flags & ( XFS_ATTR_INCOMPLETE | XFS_ATTR_PARENT ) )
return 0 ;
if ( ! value ) {
error = xrep_parent_fetch_xattr_remote ( rp , ip , attr_flags ,
name , namelen , valuelen ) ;
if ( error )
return error ;
value = rp - > xattr_value ;
}
trace_xrep_parent_stash_xattr ( rp - > sc - > tempip , key . flags , ( void * ) name ,
key . namelen , key . valuelen ) ;
error = xfblob_store ( rp - > xattr_blobs , & key . name_cookie , name ,
key . namelen ) ;
if ( error )
return error ;
error = xfblob_store ( rp - > xattr_blobs , & key . value_cookie , value ,
key . valuelen ) ;
if ( error )
return error ;
return xfarray_append ( rp - > xattr_records , & key ) ;
}
/* Insert one xattr key/value. */
STATIC int
xrep_parent_insert_xattr (
struct xrep_parent * rp ,
const struct xrep_parent_xattr * key )
{
struct xfs_da_args args = {
. dp = rp - > sc - > tempip ,
. attr_filter = key - > flags ,
. namelen = key - > namelen ,
. valuelen = key - > valuelen ,
. owner = rp - > sc - > ip - > i_ino ,
. geo = rp - > sc - > mp - > m_attr_geo ,
. whichfork = XFS_ATTR_FORK ,
. op_flags = XFS_DA_OP_OKNOENT ,
} ;
int error ;
ASSERT ( ! ( key - > flags & XFS_ATTR_PARENT ) ) ;
/*
* Grab pointers to the scrub buffer so that we can use them to insert
* attrs into the temp file .
*/
args . name = rp - > xattr_name ;
args . value = rp - > xattr_value ;
/*
* The attribute name is stored near the end of the in - core buffer ,
* though we reserve one more byte to ensure null termination .
*/
rp - > xattr_name [ XATTR_NAME_MAX ] = 0 ;
error = xfblob_load ( rp - > xattr_blobs , key - > name_cookie , rp - > xattr_name ,
key - > namelen ) ;
if ( error )
return error ;
error = xfblob_free ( rp - > xattr_blobs , key - > name_cookie ) ;
if ( error )
return error ;
error = xfblob_load ( rp - > xattr_blobs , key - > value_cookie , args . value ,
key - > valuelen ) ;
if ( error )
return error ;
error = xfblob_free ( rp - > xattr_blobs , key - > value_cookie ) ;
if ( error )
return error ;
rp - > xattr_name [ key - > namelen ] = 0 ;
trace_xrep_parent_insert_xattr ( rp - > sc - > tempip , key - > flags ,
rp - > xattr_name , key - > namelen , key - > valuelen ) ;
xfs_attr_sethash ( & args ) ;
return xfs_attr_set ( & args , XFS_ATTRUPDATE_UPSERT , false ) ;
}
/*
* Periodically flush salvaged attributes to the temporary file . This is done
* to reduce the memory requirements of the xattr rebuild because files can
* contain millions of attributes .
*/
STATIC int
xrep_parent_flush_xattrs (
struct xrep_parent * rp )
{
xfarray_idx_t array_cur ;
int error ;
/*
* Entering this function , the scrub context has a reference to the
* inode being repaired , the temporary file , and the empty scrub
* transaction that we created for the xattr scan . We hold ILOCK_EXCL
* on the inode being repaired .
*
* To constrain kernel memory use , we occasionally flush salvaged
* xattrs from the xfarray and xfblob structures into the temporary
* file in preparation for exchanging the xattr structures at the end .
* Updating the temporary file requires a transaction , so we commit the
* scrub transaction and drop the ILOCK so that xfs_attr_set can
* allocate whatever transaction it wants .
*
* We still hold IOLOCK_EXCL on the inode being repaired , which
* prevents anyone from adding xattrs ( or parent pointers ) while we ' re
* flushing .
*/
xchk_trans_cancel ( rp - > sc ) ;
xchk_iunlock ( rp - > sc , XFS_ILOCK_EXCL ) ;
/*
* Take the IOLOCK of the temporary file while we modify xattrs . This
* isn ' t strictly required because the temporary file is never revealed
* to userspace , but we follow the same locking rules . We still hold
* sc - > ip ' s IOLOCK .
*/
error = xrep_tempfile_iolock_polled ( rp - > sc ) ;
if ( error )
return error ;
/* Add all the salvaged attrs to the temporary file. */
foreach_xfarray_idx ( rp - > xattr_records , array_cur ) {
struct xrep_parent_xattr key ;
error = xfarray_load ( rp - > xattr_records , array_cur , & key ) ;
if ( error )
return error ;
error = xrep_parent_insert_xattr ( rp , & key ) ;
if ( error )
return error ;
}
/* Empty out both arrays now that we've added the entries. */
xfarray_truncate ( rp - > xattr_records ) ;
xfblob_truncate ( rp - > xattr_blobs ) ;
xrep_tempfile_iounlock ( rp - > sc ) ;
/* Recreate the empty transaction and relock the inode. */
error = xchk_trans_alloc_empty ( rp - > sc ) ;
if ( error )
return error ;
xchk_ilock ( rp - > sc , XFS_ILOCK_EXCL ) ;
return 0 ;
}
/* Decide if we've stashed too much xattr data in memory. */
static inline bool
xrep_parent_want_flush_xattrs (
struct xrep_parent * rp )
{
unsigned long long bytes ;
bytes = xfarray_bytes ( rp - > xattr_records ) +
xfblob_bytes ( rp - > xattr_blobs ) ;
return bytes > XREP_PARENT_XATTR_MAX_STASH_BYTES ;
}
/* Flush staged attributes to the temporary file if we're over the limit. */
STATIC int
xrep_parent_try_flush_xattrs (
struct xfs_scrub * sc ,
void * priv )
{
struct xrep_parent * rp = priv ;
int error ;
if ( ! xrep_parent_want_flush_xattrs ( rp ) )
return 0 ;
error = xrep_parent_flush_xattrs ( rp ) ;
if ( error )
return error ;
/*
* If there were any parent pointer updates to the xattr structure
* while we dropped the ILOCK , the xattr structure is now stale .
* Signal to the attr copy process that we need to start over , but
* this time without opportunistic attr flushing .
*
* This is unlikely to happen , so we ' re ok with restarting the copy .
*/
mutex_lock ( & rp - > pscan . lock ) ;
if ( rp - > saw_pptr_updates )
error = - ESTALE ;
mutex_unlock ( & rp - > pscan . lock ) ;
return error ;
}
/* Copy all the non-pptr extended attributes into the temporary file. */
STATIC int
xrep_parent_copy_xattrs (
struct xrep_parent * rp )
{
struct xfs_scrub * sc = rp - > sc ;
int error ;
/*
* Clear the pptr updates flag . We hold sc - > ip ILOCKed , so there
* can ' t be any parent pointer updates in progress .
*/
mutex_lock ( & rp - > pscan . lock ) ;
rp - > saw_pptr_updates = false ;
mutex_unlock ( & rp - > pscan . lock ) ;
/* Copy xattrs, stopping periodically to flush the incore buffers. */
error = xchk_xattr_walk ( sc , sc - > ip , xrep_parent_stash_xattr ,
xrep_parent_try_flush_xattrs , rp ) ;
if ( error & & error ! = - ESTALE )
return error ;
if ( error = = - ESTALE ) {
/*
* The xattr copy collided with a parent pointer update .
* Restart the copy , but this time hold the ILOCK all the way
* to the end to lock out any directory parent pointer updates .
*/
error = xchk_xattr_walk ( sc , sc - > ip , xrep_parent_stash_xattr ,
NULL , rp ) ;
if ( error )
return error ;
}
/* Flush any remaining stashed xattrs to the temporary file. */
if ( xfarray_bytes ( rp - > xattr_records ) = = 0 )
return 0 ;
return xrep_parent_flush_xattrs ( rp ) ;
}
/*
* Ensure that @ sc - > ip and @ sc - > tempip both have attribute forks before we head
* into the attr fork exchange transaction . All files on a filesystem with
* parent pointers must have an attr fork because the parent pointer code does
* not itself add attribute forks .
*
* Note : Unlinkable unlinked files don ' t need one , but the overhead of having
* an unnecessary attr fork is not justified by the additional code complexity
* that would be needed to track that state correctly .
*/
STATIC int
xrep_parent_ensure_attr_fork (
struct xrep_parent * rp )
{
struct xfs_scrub * sc = rp - > sc ;
int error ;
error = xfs_attr_add_fork ( sc - > tempip ,
sizeof ( struct xfs_attr_sf_hdr ) , 1 ) ;
if ( error )
return error ;
return xfs_attr_add_fork ( sc - > ip , sizeof ( struct xfs_attr_sf_hdr ) , 1 ) ;
}
/*
* Finish replaying stashed parent pointer updates , allocate a transaction for
* exchanging extent mappings , and take the ILOCKs of both files before we
* commit the new attribute structure .
*/
STATIC int
xrep_parent_finalize_tempfile (
struct xrep_parent * rp )
{
struct xfs_scrub * sc = rp - > sc ;
int error ;
/*
* Repair relies on the ILOCK to quiesce all possible xattr updates .
* Replay all queued parent pointer updates into the tempfile before
* exchanging the contents , even if that means dropping the ILOCKs and
* the transaction .
*/
do {
error = xrep_parent_replay_updates ( rp ) ;
if ( error )
return error ;
error = xrep_parent_ensure_attr_fork ( rp ) ;
if ( error )
return error ;
error = xrep_tempexch_trans_alloc ( sc , XFS_ATTR_FORK , & rp - > tx ) ;
if ( error )
return error ;
if ( xfarray_length ( rp - > pptr_recs ) = = 0 )
break ;
xchk_trans_cancel ( sc ) ;
xrep_tempfile_iunlock_both ( sc ) ;
} while ( ! xchk_should_terminate ( sc , & error ) ) ;
return error ;
}
/*
* Replay all the stashed parent pointers into the temporary file , copy all
* the non - pptr xattrs from the file being repaired into the temporary file ,
* and exchange the attr fork contents atomically .
*/
STATIC int
xrep_parent_rebuild_pptrs (
struct xrep_parent * rp )
{
struct xfs_scrub * sc = rp - > sc ;
xfs_ino_t parent_ino = NULLFSINO ;
int error ;
/*
* Copy non - ppttr xattrs from the file being repaired into the
* temporary file ' s xattr structure . We hold sc - > ip ' s IOLOCK , which
* prevents setxattr / removexattr calls from occurring , but renames
* update the parent pointers without holding IOLOCK . If we detect
* stale attr structures , we restart the scan but only flush at the
* end .
*/
error = xrep_parent_copy_xattrs ( rp ) ;
if ( error )
return error ;
/*
* Cancel the empty transaction that we used to walk and copy attrs ,
* and drop the ILOCK so that we can take the IOLOCK on the temporary
* file . We still hold sc - > ip ' s IOLOCK .
*/
xchk_trans_cancel ( sc ) ;
xchk_iunlock ( sc , XFS_ILOCK_EXCL ) ;
error = xrep_tempfile_iolock_polled ( sc ) ;
if ( error )
return error ;
/*
* Allocate transaction , lock inodes , and make sure that we ' ve replayed
* all the stashed pptr updates to the tempdir . After this point ,
* we ' re ready to exchange the attr fork mappings .
*/
error = xrep_parent_finalize_tempfile ( rp ) ;
if ( error )
return error ;
/* Last chance to abort before we start committing pptr fixes. */
if ( xchk_should_terminate ( sc , & error ) )
return error ;
if ( xchk_iscan_aborted ( & rp - > pscan . iscan ) )
return - ECANCELED ;
/*
* Exchange the attr fork contents and junk the old attr fork contents ,
* which are now in the tempfile .
*/
error = xrep_xattr_swap ( sc , & rp - > tx ) ;
if ( error )
return error ;
error = xrep_xattr_reset_tempfile_fork ( sc ) ;
if ( error )
return error ;
/*
* Roll to get a transaction without any inodes joined to it . Then we
* can drop the tempfile ' s ILOCK and IOLOCK before doing more work on
* the scrub target file .
*/
error = xfs_trans_roll ( & sc - > tp ) ;
if ( error )
return error ;
xrep_tempfile_iunlock ( sc ) ;
xrep_tempfile_iounlock ( sc ) ;
/*
* We ' ve committed the new parent pointers . Find at least one parent
* so that we can decide if we ' re moving this file to the orphanage .
* For this purpose , root directories are their own parents .
*/
if ( sc - > ip = = sc - > mp - > m_rootip ) {
xrep_findparent_scan_found ( & rp - > pscan , sc - > ip - > i_ino ) ;
} else {
error = xrep_parent_lookup_pptrs ( sc , & parent_ino ) ;
if ( error )
return error ;
if ( parent_ino ! = NULLFSINO )
xrep_findparent_scan_found ( & rp - > pscan , parent_ino ) ;
}
return 0 ;
}
2024-04-15 14:54:53 -07:00
/*
* Commit the new parent pointer structure ( currently only the dotdot entry ) to
* the file that we ' re repairing .
*/
STATIC int
xrep_parent_rebuild_tree (
struct xrep_parent * rp )
{
2024-04-22 09:48:16 -07:00
int error ;
if ( xfs_has_parent ( rp - > sc - > mp ) ) {
error = xrep_parent_rebuild_pptrs ( rp ) ;
if ( error )
return error ;
}
2024-04-15 14:54:53 -07:00
if ( rp - > pscan . parent_ino = = NULLFSINO ) {
2024-04-15 14:54:55 -07:00
if ( xrep_orphanage_can_adopt ( rp - > sc ) )
return xrep_parent_move_to_orphanage ( rp ) ;
2024-04-15 14:54:53 -07:00
return - EFSCORRUPTED ;
}
2024-04-22 09:48:16 -07:00
if ( S_ISDIR ( VFS_I ( rp - > sc - > ip ) - > i_mode ) )
return xrep_parent_reset_dotdot ( rp ) ;
return 0 ;
2024-04-15 14:54:53 -07:00
}
2024-04-22 09:48:18 -07:00
/* Count the number of parent pointers. */
STATIC int
xrep_parent_count_pptr (
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 xrep_parent * rp = priv ;
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 ;
rp - > parents + + ;
return 0 ;
}
/*
* After all parent pointer rebuilding and adoption activity completes , reset
* the link count of this nondirectory , having scanned the fs to rebuild all
* parent pointers .
*/
STATIC int
xrep_parent_set_nondir_nlink (
struct xrep_parent * rp )
{
struct xfs_scrub * sc = rp - > sc ;
struct xfs_inode * ip = sc - > ip ;
struct xfs_perag * pag ;
bool joined = false ;
int error ;
/* Count parent pointers so we can reset the file link count. */
rp - > parents = 0 ;
error = xchk_xattr_walk ( sc , ip , xrep_parent_count_pptr , NULL , rp ) ;
if ( error )
return error ;
if ( rp - > parents > 0 & & xfs_inode_on_unlinked_list ( ip ) ) {
xfs_trans_ijoin ( sc - > tp , sc - > ip , 0 ) ;
joined = true ;
/*
* The file is on the unlinked list but we found parents .
* Remove the file from the unlinked list .
*/
pag = xfs_perag_get ( sc - > mp , XFS_INO_TO_AGNO ( sc - > mp , ip - > i_ino ) ) ;
if ( ! pag ) {
ASSERT ( 0 ) ;
return - EFSCORRUPTED ;
}
error = xfs_iunlink_remove ( sc - > tp , pag , ip ) ;
xfs_perag_put ( pag ) ;
if ( error )
return error ;
} else if ( rp - > parents = = 0 & & ! xfs_inode_on_unlinked_list ( ip ) ) {
xfs_trans_ijoin ( sc - > tp , sc - > ip , 0 ) ;
joined = true ;
/*
* The file is not on the unlinked list but we found no
* parents . Add the file to the unlinked list .
*/
error = xfs_iunlink ( sc - > tp , ip ) ;
if ( error )
return error ;
}
/* Set the correct link count. */
if ( VFS_I ( ip ) - > i_nlink ! = rp - > parents ) {
if ( ! joined ) {
xfs_trans_ijoin ( sc - > tp , sc - > ip , 0 ) ;
joined = true ;
}
set_nlink ( VFS_I ( ip ) , min_t ( unsigned long long , rp - > parents ,
XFS_NLINK_PINNED ) ) ;
}
/* Log the inode to keep it moving forward if we dirtied anything. */
if ( joined )
xfs_trans_log_inode ( sc - > tp , ip , XFS_ILOG_CORE ) ;
return 0 ;
}
2024-04-15 14:54:53 -07:00
/* Set up the filesystem scan so we can look for parents. */
STATIC int
xrep_parent_setup_scan (
struct xrep_parent * rp )
{
struct xfs_scrub * sc = rp - > sc ;
2024-04-22 09:48:12 -07:00
char * descr ;
2024-04-22 09:48:16 -07:00
struct xfs_da_geometry * geo = sc - > mp - > m_attr_geo ;
int max_len ;
2024-04-22 09:48:12 -07:00
int error ;
2024-04-15 14:54:53 -07:00
2024-04-22 09:48:12 -07:00
if ( ! xfs_has_parent ( sc - > mp ) )
return xrep_findparent_scan_start ( sc , & rp - > pscan ) ;
2024-04-22 09:48:16 -07:00
/* Buffers for copying non-pptr attrs to the tempfile */
rp - > xattr_name = kvmalloc ( XATTR_NAME_MAX + 1 , XCHK_GFP_FLAGS ) ;
if ( ! rp - > xattr_name )
return - ENOMEM ;
/*
* Allocate enough memory to handle loading local attr values from the
* xfblob data while flushing stashed attrs to the temporary file .
* We only realloc the buffer when salvaging remote attr values , so
* TRY_HARDER means we allocate the maximal attr value size .
*/
if ( sc - > flags & XCHK_TRY_HARDER )
max_len = XATTR_SIZE_MAX ;
else
max_len = xfs_attr_leaf_entsize_local_max ( geo - > blksize ) ;
error = xrep_parent_alloc_xattr_value ( rp , max_len ) ;
if ( error )
goto out_xattr_name ;
2024-04-22 09:48:12 -07:00
/* Set up some staging memory for logging parent pointer updates. */
descr = xchk_xfile_ino_descr ( sc , " parent pointer entries " ) ;
error = xfarray_create ( descr , 0 , sizeof ( struct xrep_pptr ) ,
& rp - > pptr_recs ) ;
kfree ( descr ) ;
if ( error )
2024-04-22 09:48:16 -07:00
goto out_xattr_value ;
2024-04-22 09:48:12 -07:00
descr = xchk_xfile_ino_descr ( sc , " parent pointer names " ) ;
error = xfblob_create ( descr , & rp - > pptr_names ) ;
kfree ( descr ) ;
if ( error )
goto out_recs ;
2024-04-22 09:48:16 -07:00
/* Set up some storage for copying attrs before the mapping exchange */
descr = xchk_xfile_ino_descr ( sc ,
" parent pointer retained xattr entries " ) ;
error = xfarray_create ( descr , 0 , sizeof ( struct xrep_parent_xattr ) ,
& rp - > xattr_records ) ;
kfree ( descr ) ;
if ( error )
goto out_names ;
descr = xchk_xfile_ino_descr ( sc ,
" parent pointer retained xattr values " ) ;
error = xfblob_create ( descr , & rp - > xattr_blobs ) ;
kfree ( descr ) ;
if ( error )
goto out_attr_keys ;
2024-04-22 09:48:13 -07:00
error = __xrep_findparent_scan_start ( sc , & rp - > pscan ,
xrep_parent_live_update ) ;
2024-04-22 09:48:12 -07:00
if ( error )
2024-04-22 09:48:16 -07:00
goto out_attr_values ;
2024-04-22 09:48:12 -07:00
return 0 ;
2024-04-22 09:48:16 -07:00
out_attr_values :
xfblob_destroy ( rp - > xattr_blobs ) ;
rp - > xattr_blobs = NULL ;
out_attr_keys :
xfarray_destroy ( rp - > xattr_records ) ;
rp - > xattr_records = NULL ;
2024-04-22 09:48:12 -07:00
out_names :
xfblob_destroy ( rp - > pptr_names ) ;
rp - > pptr_names = NULL ;
out_recs :
xfarray_destroy ( rp - > pptr_recs ) ;
rp - > pptr_recs = NULL ;
2024-04-22 09:48:16 -07:00
out_xattr_value :
kvfree ( rp - > xattr_value ) ;
rp - > xattr_value = NULL ;
out_xattr_name :
kvfree ( rp - > xattr_name ) ;
rp - > xattr_name = NULL ;
2024-04-22 09:48:12 -07:00
return error ;
2024-04-15 14:54:53 -07:00
}
int
xrep_parent (
struct xfs_scrub * sc )
{
struct xrep_parent * rp = sc - > buf ;
int error ;
2024-04-22 09:48:12 -07:00
/*
* When the parent pointers feature is enabled , repairs are committed
* by atomically committing a new xattr structure and reaping the old
2024-04-22 09:48:29 -07:00
* attr fork . Reaping requires rmap and exchange - range to be enabled .
2024-04-22 09:48:12 -07:00
*/
2024-04-22 09:48:29 -07:00
if ( xfs_has_parent ( sc - > mp ) ) {
if ( ! xfs_has_rmapbt ( sc - > mp ) )
return - EOPNOTSUPP ;
if ( ! xfs_has_exchange_range ( sc - > mp ) )
return - EOPNOTSUPP ;
}
2024-04-22 09:48:12 -07:00
2024-04-15 14:54:53 -07:00
error = xrep_parent_setup_scan ( rp ) ;
if ( error )
return error ;
2024-04-22 09:48:12 -07:00
if ( xfs_has_parent ( sc - > mp ) )
error = xrep_parent_scan_dirtree ( rp ) ;
else
error = xrep_parent_find_dotdot ( rp ) ;
2024-04-15 14:54:53 -07:00
if ( error )
goto out_teardown ;
2024-04-22 09:48:16 -07:00
/* Last chance to abort before we start committing dotdot fixes. */
2024-04-15 14:54:53 -07:00
if ( xchk_should_terminate ( sc , & error ) )
goto out_teardown ;
error = xrep_parent_rebuild_tree ( rp ) ;
if ( error )
goto out_teardown ;
2024-04-22 09:48:18 -07:00
if ( xfs_has_parent ( sc - > mp ) & & ! S_ISDIR ( VFS_I ( sc - > ip ) - > i_mode ) ) {
error = xrep_parent_set_nondir_nlink ( rp ) ;
if ( error )
goto out_teardown ;
}
error = xrep_defer_finish ( sc ) ;
2024-04-15 14:54:53 -07:00
out_teardown :
xrep_parent_teardown ( rp ) ;
return error ;
}