2026-04-02 16:00:27 +02:00
// SPDX-License-Identifier: GPL-3.0-or-later
2026-04-02 16:25:49 +02:00
// Copyright (C) 2011 Andrea Mazzoleni
2011-03-04 20:59:21 +01:00
2026-06-24 00:15:15 +02:00
# include "os/portable.h"
2011-03-04 20:59:21 +01:00
2015-01-02 18:52:04 +01:00
# include "support.h"
2011-03-04 20:59:21 +01:00
# include "util.h"
# include "elem.h"
2013-02-27 21:07:16 +01:00
# include "import.h"
2014-10-19 21:07:17 +02:00
# include "search.h"
2011-03-04 20:59:21 +01:00
# include "state.h"
# include "parity.h"
# include "handle.h"
2013-12-28 09:10:00 +01:00
# include "raid/raid.h"
2013-12-29 22:13:08 +01:00
# include "raid/combo.h"
2011-03-04 20:59:21 +01:00
2026-08-28 09:36:12 +02:00
/*
* SnapRAID check / fix model
*
* Check verifies the data described by the content file against the files
* currently present on the data disks . For blocks with a current hash , the
* data is verified directly by hashing it . Unless running in audit - only mode ,
* parity is also read and can be used both to recover unreadable or corrupted
* data and to verify the stored parity itself .
*
* Fix uses the same verification and recovery logic as check , but writes
* validated recovered data and parity back to disk . Recovery is deliberately
* conservative : obtaining a solution from the RAID equations is not enough ;
* the result must also be validated independently before it can be accepted .
*
* Block state model
* - - - - - - - - - - - - - - - - -
*
* Check / fix must interpret block states differently because the content file
* may describe an incomplete sync . In that case , the logical block state and
* the physical parity on disk do not necessarily describe the same point in
* time .
*
* State Stored hash Current file Physical parity
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* EMPTY none none zero contribution
* BLK CURRENT present synchronized
* CHG OLD / ZERO / INVALID present may be OLD or CURRENT
* REP NEW present may be OLD or NEW
* DELETED OLD / INVALID none may be OLD or zero
2026-08-28 12:11:05 +02:00
* REBUILD CURRENT present parity untrusted / rebuild pending
2026-08-28 09:36:12 +02:00
*
* BLK has a hash for the data that should currently be present and normally
* represents a synchronized contribution .
*
* REP also has a hash for the current data , so its current contents can be
* verified . However , parity is still invalid because an interrupted sync may
* have left physical parity representing either the old or the new block .
*
* CHG is different : its stored hash describes the OLD data , not the current
* file contents . A readable CHG block therefore cannot be validated against
* block - > hash . Its current contents are kept as the best available copy , but
* the old hash is useful when considering parity that may still represent the
* state before sync .
*
* DELETED has no current file data . Its stored hash , when available , describes
* the previous contribution that may still be present in physical parity .
*
* Interrupted sync recovery model
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*
* When CHG , REP or DELETED blocks are present , check / fix cannot know whether
* an interrupted sync updated the physical parity before stopping . Recovery
* therefore tries two different interpretations of the same parity .
*
* The first strategy assumes that parity represents the CURRENT state :
*
* BLK - > current data is represented
* CHG - > current data is represented
* REP - > new / current data is represented
* DELETED - > contributes zero
2026-08-29 17:18:12 +02:00
* REBUILD - > data and hash are CURRENT , but physical parity remains untrusted
2026-08-28 09:36:12 +02:00
*
* Under this assumption , readable current data can be used directly and only
* blocks known to be bad or missing have to be reconstructed . BLK and REP
* recovery can be validated with their stored current hashes . CHG recovery is
* more subtle because only its OLD hash is available ; a recovered CHG may
* therefore remain ambiguous and be marked out - of - date .
*
* If that strategy cannot produce a validated recovery , a second strategy
* assumes that parity still represents the state BEFORE the interrupted sync :
*
* BLK - > synchronized data is represented
* CHG - > OLD data is represented
* REP - > OLD data is represented
* DELETED - > OLD data is represented
2026-08-29 17:18:12 +02:00
* REBUILD - > data and hash remain CURRENT ; only physical parity is untrusted
2026-09-02 16:31:21 +02:00
*
2026-08-29 17:18:12 +02:00
*
* REBUILD is orthogonal to the OLD / CURRENT sync - history distinction . Unlike
* CHG , REP and DELETED , it does not represent a different data generation .
* Its data and stored hash are CURRENT in both strategies . Only its physical
* parity is untrusted because an in - place - - force - full rebuild may have been
* interrupted .
2026-08-28 09:36:12 +02:00
*
* Under this assumption , the current contents of CHG and REP cannot be used
* as parity inputs even when they are perfectly readable . CHG , REP and
* DELETED must instead be treated as unknown old contributions , reconstructed
* only as needed to make recovery of a bad BLK possible .
*
* These reconstructed old contributions are auxiliary recovery state , not
* current user data . They are marked out - of - date so they cannot validate the
* recovery and so an old CHG / REP result is never promoted as known - good
* current data .
*
* A CHG whose OLD hash is ZERO is a useful special case : its old contribution
* is known exactly and can be supplied as an all - zero block without consuming
* a RAID recovery equation . Similarly , if an old CHG or DELETED block can be
* obtained from the import set using its stored OLD hash , that known old
* contribution reduces the number of unknowns . REP cannot use this shortcut
* because its stored hash describes the NEW data , not the old contribution .
*
* Recovery validation model
* - - - - - - - - - - - - - - - - - - - - - - - - -
*
* Solving the RAID equations does not by itself prove that the reconstructed
* data is correct . One of the parity blocks used for reconstruction may itself
* be damaged , and after an interrupted sync different parity levels may not
* necessarily provide the expected state .
*
* For F unknown data blocks , recovery therefore requires independent evidence :
*
* - If at least one recovered block has a trustworthy current hash , F parity
* equations are sufficient . The candidate recovery is accepted only if all
* available trustworthy hashes of recovered blocks match .
*
* - If no recovered block has a trustworthy hash , F + 1 parity blocks are
* required . F parity blocks are used to solve the data equations and the
* remaining parity block is reserved as an independent check of the result .
*
* When more parity levels are available , repair_step ( ) tries their different
* combinations . Missing parity levels are skipped , and a candidate obtained
* from a bad parity combination is rejected when its hash or independent
* parity check fails .
*
* This distinction is important : a successful RAID decode is only a candidate
* recovery . It becomes an accepted recovery only after independent validation .
*
* Parity validation model
* - - - - - - - - - - - - - - - - - - - - - - -
*
* Physical parity is checked or rewritten only when it is both meaningful and
* expected to be valid .
*
* used_parity means that at least one live data block uses the parity position .
* Parity for a position containing no live data is irrelevant and sync is not
* required to keep such unused parity updated .
*
2026-08-29 17:18:12 +02:00
* valid_parity means that no block at the position is CHG , REP , DELETED or
* REBUILD .
*
* For CHG , REP and DELETED , parity is invalid because its data - generation
* relationship is uncertain after an interrupted sync . For REBUILD the reason
* is different : the data and hash are CURRENT , but physical parity itself is
* deliberately untrusted until the in - place rebuild completes .
2026-08-28 09:36:12 +02:00
* When one of these states is present , parity may legitimately differ from
* parity recomputed from the current data because sync has not completed .
* Such a difference must not be reported or " fixed " by check / fix ; the next
* sync is responsible for bringing that parity position to the current state .
*
* Parity is therefore compared and repaired only when :
*
* used_parity & & valid_parity
*
* A parity level excluded from fix is not necessarily useless . When accessible
* it is still opened for reading because it can provide an additional recovery
* equation or independent validation , while remaining protected from writes .
*
* Fix and quarantine model
* - - - - - - - - - - - - - - - - - - - - - - - -
*
2026-09-08 08:15:27 +02:00
* failed_struct distinguishes two properties of a recovery entry .
2026-08-28 09:36:12 +02:00
*
* is_bad means that the current file block is missing , unreadable or does not
* match its expected current hash and therefore needs recovery .
*
2026-09-08 08:15:27 +02:00
* recovery tracks the data state of the block in buffer [ index ] :
2026-08-28 09:36:12 +02:00
*
2026-09-08 08:15:27 +02:00
* RECOVERY_NONE not recovered ( no valid replacement data available )
* RECOVERY_CURRENT certified CURRENT data ( via import / search or RAID )
* RECOVERY_OUTOFDATE recovered candidate that cannot be certified CURRENT
*
* Outside repair ( ) , file_recover ( ) writes RECOVERY_CURRENT data to the original
* file and reports it as fixed . An out - of - date candidate ( RECOVERY_OUTOFDATE )
2026-09-07 23:15:47 +02:00
* is written as best - effort data , but the containing file remains marked damaged
* and is quarantined under the . unrecoverable name .
2026-08-28 09:36:12 +02:00
*
2026-08-29 21:25:49 +02:00
* Missing files are created under their normal names . They are quarantined only
* if recovery produces data that cannot be certified as CURRENT .
*
* A later fix starts a new recovery attempt under the normal filename even if a
* previous . unrecoverable file exists . A previous quarantine cannot safely be
* reused as recovery input because it may contain an ambiguous recovered CHG .
* Such a CHG may have caused the file to be quarantined precisely because its
* CURRENT generation could not be proven . That provenance is runtime state and
* is not persisted in the . unrecoverable file . Reopening it in a later fix would
* make the CHG merely appear readable and could incorrectly accept it as CURRENT .
*
* The previous quarantine is therefore kept separate while the new recovery is
* in progress . If the new attempt succeeds completely , the obsolete quarantine
* is removed . If the new attempt also becomes unrecoverable , it replaces the
* previous quarantine .
*
* A quarantined file may be reopened later during the same fix run . In that case
* FILE_IS_QUARANTINED identifies it as belonging to the current failed recovery and
* handle_create ( ) continues operating on its . unrecoverable file .
*
* A partial - S / - B fix operates only at block level and therefore never performs
* file - level finalization .
2026-08-28 09:36:12 +02:00
*/
2011-03-04 20:59:21 +01:00
/****************************************************************************/
/* check */
2026-01-24 19:19:11 +01:00
static const char * es ( int err )
{
2026-02-22 14:58:06 +01:00
if ( is_hw ( err ) )
2026-01-24 19:19:11 +01:00
return " error_io " ;
else
return " error " ;
}
2026-09-08 08:15:27 +02:00
# define RECOVERY_NONE 0 /**< Not recovered (no valid candidate available) */
# define RECOVERY_CURRENT 1 /**< Recovered and certified CURRENT (via import/search or RAID) */
# define RECOVERY_OUTOFDATE 2 /**< Recovered candidate that cannot be certified CURRENT (best-effort / OLD / ambiguous) */
2012-02-09 23:17:36 +01:00
/**
2026-08-28 09:36:12 +02:00
* A block participating in recovery .
*
* Entries include blocks that are actually missing or corrupted , but also
* readable CHG , REP and DELETED blocks whose old / current contribution may be
* needed to interpret parity left by an interrupted sync .
2012-02-09 23:17:36 +01:00
*/
2011-04-23 21:38:26 +02:00
struct failed_struct {
2013-02-24 11:10:10 +01:00
/**
2026-08-28 09:36:12 +02:00
* If the current file block is known to require recovery because it is
* missing , unreadable , or doesn ' t match its expected current hash .
*
* DELETED blocks and readable CHG / REP auxiliary entries are not bad even
* though they may participate in the recovery equations .
2026-09-07 14:19:12 +02:00
*
* is_bad describes the state of the original file block . It may remain set
* even when buffer [ index ] already contains valid replacement data .
2013-02-24 11:10:10 +01:00
*/
int is_bad ;
/**
2026-09-08 08:15:27 +02:00
* Data state of the buffer associated with this entry :
2026-09-07 23:15:47 +02:00
*
2026-09-08 08:15:27 +02:00
* RECOVERY_NONE :
* The block is not recovered or does not contain valid replacement data .
2026-09-07 14:19:12 +02:00
*
2026-09-08 08:15:27 +02:00
* RECOVERY_CURRENT :
* buffer [ index ] contains certified CURRENT data .
* During repair ( ) strategy construction , this marks blocks obtained
* and validated independently through import / search , making them reusable
* as known inputs across strategies whose data generation is unchanged
* ( such as BLK and REBUILD ) .
* Upon successful recovery , repair ( ) marks all recovered non - outofdate blocks
* with RECOVERY_CURRENT .
* Outside repair ( ) , file_recover ( ) writes this data to the original file
* and marks it FIXED .
2026-09-07 14:19:12 +02:00
*
2026-09-08 08:15:27 +02:00
* RECOVERY_OUTOFDATE :
* buffer [ index ] contains a recovered candidate that cannot be proven to
* represent the required CURRENT contents ( e . g . reconstructed under the
* pre - sync parity strategy , or ambiguous CHG ) .
* If an out - of - date block is also is_bad , fix may still preserve the recovered
* bytes as the best available copy , but the containing file remains damaged
* and quarantined as . unrecoverable .
2026-09-07 14:19:12 +02:00
*/
2026-09-08 08:15:27 +02:00
int recovery ;
2026-09-07 14:19:12 +02:00
2012-02-09 23:17:36 +01:00
unsigned index ; /**< Index of the failed block. */
2015-10-03 10:43:02 +02:00
struct snapraid_block * block ; /**< The failed block */
struct snapraid_disk * disk ; /**< The failed disk. */
struct snapraid_file * file ; /**< The failed file. 0 for DELETED block. */
block_off_t file_pos ; /**< Offset inside the file */
struct snapraid_handle * handle ; /**< The handle containing the failed block, or 0 for a DELETED block */
2011-04-23 21:38:26 +02:00
} ;
2011-04-28 21:38:25 +02:00
/**
2015-10-07 22:07:20 +02:00
* Check if a block hash matches the specified buffer .
2026-09-07 11:45:16 +02:00
*
* This function validates hash identity only and deliberately omits the
* zero - padding check beyond the logical file size . It is used when testing
* candidate identity against a stored hash whose original logical size and
* padding provenance may differ from the CURRENT file metadata ( such as
* during CHG generation inference ) .
*
* Return = = 0 if equal , - 1 if mismatch .
2011-04-28 21:38:25 +02:00
*/
2026-09-07 11:45:16 +02:00
static int block_hash_cmp ( struct snapraid_state * state , int rehash , struct snapraid_block * block , size_t pos_size , unsigned char * buffer )
2011-04-28 21:38:25 +02:00
{
2016-04-07 21:39:02 +02:00
unsigned char hash [ HASH_MAX ] ;
2011-04-28 21:38:25 +02:00
2026-08-30 23:09:29 +02:00
/* compute the hash according to the block-hash semantics */
2013-07-01 23:08:25 +02:00
if ( rehash ) {
2026-08-30 23:09:29 +02:00
memhash_block ( state - > prevhash , state - > prevhashseed , hash , buffer , pos_size , state - > block_size ) ;
2013-07-01 23:08:25 +02:00
} else {
2026-08-30 23:09:29 +02:00
memhash_block ( state - > hash , state - > hashseed , hash , buffer , pos_size , state - > block_size ) ;
2013-07-01 23:08:25 +02:00
}
2011-04-28 21:38:25 +02:00
/* compare the hash */
2016-04-07 21:39:02 +02:00
if ( memcmp ( hash , block - > hash , BLOCK_HASH_SIZE ) ! = 0 ) {
2011-04-28 21:38:25 +02:00
return - 1 ;
}
2026-09-07 11:45:16 +02:00
return 0 ;
}
/**
* Check if block data matches the specified block and is canonical for the current logical size .
*
* Unlike block_hash_cmp ( ) , this function validates both hash identity and the
* canonical zero - padding requirement beyond the CURRENT logical file size .
* For legacy hashes ( Murmur3 / Spooky2 ) , the hash covers only logical bytes , so
* trailing garbage or corrupted parity could otherwise pass validation and later
* corrupt parity recalculations . For MuseAir , this enforces the canonical RAID
* block invariant .
*
* Use this for all normal CURRENT data validation ( e . g . is_hash_matching ( ) ) .
*
* Return = = 0 if equal and valid , - 1 if mismatch .
*/
static int block_data_cmp ( struct snapraid_state * state , int rehash , struct snapraid_block * block , size_t pos_size , unsigned char * buffer )
{
if ( block_hash_cmp ( state , rehash , block , pos_size , buffer ) ! = 0 ) {
return - 1 ;
}
2026-08-28 09:36:12 +02:00
/*
2026-08-30 23:09:29 +02:00
* Legacy hashes cover only the logical bytes belonging to the file , while RAID
2026-08-28 09:36:12 +02:00
* operates on the complete zero - padded block . Verify the padding separately :
2026-08-30 23:09:29 +02:00
* otherwise corruption confined to the reconstructed tail could pass a legacy
* hash check and later produce incorrect parity . This is redundant for MuseAir ,
* but also makes the canonical - block invariant explicit .
2026-08-28 09:36:12 +02:00
*/
2015-10-03 10:43:02 +02:00
if ( pos_size < state - > block_size ) {
2026-09-10 18:36:50 +02:00
if ( membcmp ( buffer + pos_size , 0 , state - > block_size - pos_size ) ! = 0 ) {
2011-04-28 21:38:25 +02:00
return - 1 ;
}
}
return 0 ;
}
2026-09-08 08:15:27 +02:00
/**
* Check if the specified block has a trustworthy CURRENT hash under the given strategy .
*
* In the OLD recovery strategy , physical parity reflects the pre - sync state ,
* so RAID reconstructs the OLD content of this block . However , for a REP
* block , block - > hash describes the NEW ( post - replace ) data . Comparing the
* reconstructed OLD bytes against the NEW hash would falsely fail validation .
*/
static int block_has_recovery_hash ( const struct snapraid_block * block , int is_old_strategy )
{
unsigned state = block_state_get ( block ) ;
if ( ! block_has_updated_hash ( block ) )
return 0 ;
if ( is_old_strategy & & state = = BLOCK_STATE_REP )
return 0 ;
return 1 ;
}
2013-09-18 21:56:06 +02:00
/**
2026-01-17 21:16:21 +01:00
* Check if the hash of all the failed blocks we are expecting to recover are now matching .
2013-09-18 21:56:06 +02:00
*/
2026-09-08 08:15:27 +02:00
static int is_hash_matching ( struct snapraid_state * state , int rehash , unsigned diskmax , struct failed_struct * failed , unsigned * failed_map , unsigned failed_count , void * * buffer , int is_old_strategy )
2013-09-18 21:56:06 +02:00
{
unsigned j ;
int hash_checked ;
hash_checked = 0 ; /* keep track if we check at least one block */
/* check if the recovered blocks are OK */
2014-09-27 00:32:39 +02:00
for ( j = 0 ; j < failed_count ; + + j ) {
2026-09-08 08:15:27 +02:00
/* if the block has a hash to check in this strategy */
if ( block_has_recovery_hash ( failed [ failed_map [ j ] ] . block , is_old_strategy ) ) {
2014-03-18 21:04:25 +01:00
/* if a hash doesn't match, fail the check */
2026-06-04 00:34:51 +02:00
size_t pos_size = file_block_size ( failed [ failed_map [ j ] ] . file , failed [ failed_map [ j ] ] . file_pos , state - > block_size ) ;
2026-09-07 11:45:16 +02:00
if ( block_data_cmp ( state , rehash , failed [ failed_map [ j ] ] . block , pos_size , buffer [ failed [ failed_map [ j ] ] . index ] ) ! = 0 ) {
2026-01-25 14:06:56 +01:00
log_tag ( " repair_hash_error:%u: Hash mismatch \n " , failed_map [ j ] ) ;
2014-03-18 21:04:25 +01:00
return 0 ;
}
2013-09-18 21:56:06 +02:00
hash_checked = 1 ;
}
}
2026-04-27 15:43:06 +02:00
/*
* If nothing checked , we reject it
* note that we are excluding this case at upper level
* but checking again doesn ' t hurt
*/
2014-03-18 22:35:01 +01:00
if ( ! hash_checked ) {
/* LCOV_EXCL_START */
2014-03-18 21:04:25 +01:00
return 0 ;
2014-03-18 22:35:01 +01:00
/* LCOV_EXCL_STOP */
}
2013-09-18 21:56:06 +02:00
2026-04-27 15:43:06 +02:00
/*
* If we checked something , and no block failed the check
* recompute all the redundancy information
*/
2026-09-02 16:31:21 +02:00
raid_gen ( diskmax , state - > level , state - > block_size , buffer , 0 ) ;
2014-03-18 21:04:25 +01:00
return 1 ;
2013-09-18 21:56:06 +02:00
}
/**
2015-10-07 22:07:20 +02:00
* Check if specified parity is now matching with a recomputed one .
2013-09-18 21:56:06 +02:00
*/
2013-12-27 13:56:56 +01:00
static int is_parity_matching ( struct snapraid_state * state , unsigned diskmax , unsigned i , void * * buffer , void * * buffer_recov )
2013-09-18 21:56:06 +02:00
{
/* recompute parity, note that we don't need parity over i */
2026-09-02 16:31:21 +02:00
raid_gen ( diskmax , i + 1 , state - > block_size , buffer , 0 ) ;
2013-09-18 21:56:06 +02:00
/* if the recovered parity block matches */
2014-09-27 00:32:39 +02:00
if ( memcmp ( buffer [ diskmax + i ] , buffer_recov [ i ] , state - > block_size ) = = 0 ) {
2013-09-18 21:56:06 +02:00
/* recompute all the redundancy information */
2026-09-02 16:31:21 +02:00
raid_gen ( diskmax , state - > level , state - > block_size , buffer , 0 ) ;
2013-09-18 21:56:06 +02:00
return 1 ;
}
return 0 ;
}
2011-04-23 21:38:26 +02:00
/**
2026-08-29 21:25:49 +02:00
* Try to recover the selected failed inputs and validate the reconstructed
* result using either a CURRENT hash or an additional RAID equation not used
* for reconstruction .
2026-08-28 09:36:12 +02:00
*
* Return :
* 0 if a recovery was successfully validated ;
* > 0 if one or more recovery candidates were tried but all failed validation ;
* < 0 if no usable recovery / validation strategy exists .
*
* On success all parity levels are recomputed in buffer .
2011-04-23 21:38:26 +02:00
*/
2026-09-08 08:15:27 +02:00
static int repair_step ( struct snapraid_state * state , int rehash , block_off_t pos , unsigned diskmax , struct failed_struct * failed , unsigned * failed_map , unsigned failed_count , void * * buffer , void * * buffer_recov , int is_old_strategy )
2011-04-23 21:38:26 +02:00
{
2013-10-29 22:22:40 +01:00
unsigned i , n ;
2013-09-18 21:56:06 +02:00
int error ;
2012-02-09 23:17:36 +01:00
int has_hash ;
2013-12-26 09:45:51 +01:00
int id [ LEV_MAX ] ;
int ip [ LEV_MAX ] ;
2011-04-24 10:24:05 +02:00
2014-03-20 22:10:19 +01:00
/* no fix required, already checked at higher level, but just to be sure */
2011-04-23 21:38:26 +02:00
if ( failed_count = = 0 ) {
2014-03-20 22:10:19 +01:00
/* LCOV_EXCL_START */
2013-10-25 18:03:05 +02:00
/* recompute only the parity */
2026-09-02 16:31:21 +02:00
raid_gen ( diskmax , state - > level , state - > block_size , buffer , 0 ) ;
2011-04-23 21:38:26 +02:00
return 0 ;
2014-03-20 22:10:19 +01:00
/* LCOV_EXCL_STOP */
2011-04-23 21:38:26 +02:00
}
2014-03-18 21:04:25 +01:00
2013-10-29 22:22:40 +01:00
n = state - > level ;
2013-09-18 21:56:06 +02:00
error = 0 ;
2026-05-21 10:02:16 +02:00
/* if failures exceed parity level, recovery is impossible */
if ( failed_count > n ) {
2026-06-04 00:34:51 +02:00
log_tag ( " recover_strategy_error:% " PRIu64 " : Impossible to recover from %u failures with %u parity \n " ,
2026-05-21 10:02:16 +02:00
pos , failed_count , n ) ;
return - 1 ;
}
2013-10-25 18:03:05 +02:00
/* setup vector of failed disk indexes */
2014-09-27 00:32:39 +02:00
for ( i = 0 ; i < failed_count ; + + i )
2013-12-26 09:45:51 +01:00
id [ i ] = failed [ failed_map [ i ] ] . index ;
2013-10-25 18:03:05 +02:00
2026-04-27 15:43:06 +02:00
/*
2026-08-28 09:36:12 +02:00
* RAID decoding alone cannot tell whether the parity equations used for the
* recovery were themselves correct .
*
* If at least one failed input has a trustworthy CURRENT hash , recovered data
2026-08-29 21:25:49 +02:00
* can provide independent validation . Otherwise one additional parity equation
* is reserved exclusively for checking the candidate result .
*
2026-09-08 08:15:27 +02:00
* Which block states provide a trustworthy hash depends on the strategy :
*
* - CURRENT strategy ( is_old_strategy = = 0 ) : BLK , REP , and REBUILD have
* trustworthy CURRENT hashes . CHG blocks do not ( their stored hash describes
* pre - sync OLD data ) .
*
* - OLD strategy ( is_old_strategy = = 1 ) : only BLK and REBUILD provide trustworthy
* hashes ( BLK represents synchronized data covered by pre - sync parity ; REBUILD
* data is always CURRENT ) . REP is excluded because its stored hash describes
* NEW data while RAID reconstructs its pre - sync OLD contribution . CHG and DELETED
* have no updated hash .
*
2026-08-29 21:25:49 +02:00
* The additional parity is an independent RAID equation , not an independent
* hash of the recovered data . The candidate is accepted only if the complete
* reconstructed block set satisfies this extra equation for the whole block .
* Therefore an accidental false match requires the data differences to satisfy
* the corresponding RAID relation at every byte position . Such correlated
* differences are possible in principle because RAID equations are linear , but
* are extremely unlikely for unrelated data corruption .
2026-04-27 15:43:06 +02:00
*/
2012-02-09 23:17:36 +01:00
has_hash = 0 ;
2014-09-27 00:32:39 +02:00
for ( i = 0 ; i < failed_count ; + + i ) {
2026-09-08 08:15:27 +02:00
if ( block_has_recovery_hash ( failed [ failed_map [ i ] ] . block , is_old_strategy ) )
2012-02-09 23:17:36 +01:00
has_hash = 1 ;
}
2026-04-27 15:43:06 +02:00
/*
* If we don ' t have a hash , but we have an extra parity
* ( strictly - less failures than number of parities )
*/
2013-10-29 22:22:40 +01:00
if ( ! has_hash & & failed_count < n ) {
/* number of parity to use, one more to check the recovering */
unsigned r = failed_count + 1 ;
/* all combinations (r of n) parities */
2013-12-26 09:45:51 +01:00
combination_first ( r , n , ip ) ;
2013-10-29 22:22:40 +01:00
do {
/* if a parity is missing, do nothing */
2014-09-27 00:32:39 +02:00
for ( i = 0 ; i < r ; + + i ) {
2013-12-26 09:45:51 +01:00
if ( buffer_recov [ ip [ i ] ] = = 0 )
2013-10-25 18:03:05 +02:00
break ;
}
2013-10-29 22:22:40 +01:00
if ( i ! = r )
2013-09-18 21:56:06 +02:00
continue ;
2013-09-11 23:30:32 +02:00
2013-10-29 22:22:40 +01:00
/* copy the parities to use, one less because the last is used for checking */
2014-09-27 00:32:39 +02:00
for ( i = 0 ; i < r - 1 ; + + i )
memcpy ( buffer [ diskmax + ip [ i ] ] , buffer_recov [ ip [ i ] ] , state - > block_size ) ;
2013-09-11 23:30:32 +02:00
2013-12-26 09:45:51 +01:00
/* recover using one less parity, the ip[r-1] one */
2015-03-21 17:16:16 +01:00
raid_data ( r - 1 , id , ip , diskmax , state - > block_size , buffer ) ;
2013-09-11 23:30:32 +02:00
2013-12-26 09:45:51 +01:00
/* use the remaining ip[r-1] parity to check the result */
2014-09-27 00:32:39 +02:00
if ( is_parity_matching ( state , diskmax , ip [ r - 1 ] , buffer , buffer_recov ) )
2013-09-18 21:56:06 +02:00
return 0 ;
2013-09-11 23:30:32 +02:00
2013-10-25 18:03:05 +02:00
/* log */
2026-06-04 00:34:51 +02:00
log_tag ( " recover_parity_error:% " PRIu64 " : " , pos ) ;
2014-09-27 00:32:39 +02:00
for ( i = 0 ; i < r ; + + i ) {
2013-10-29 22:22:40 +01:00
if ( i ! = 0 )
2026-01-25 14:06:56 +01:00
log_tag ( " , " ) ;
2015-04-20 22:07:42 +02:00
log_tag ( " %s " , lev_config_name ( ip [ i ] ) ) ;
2013-10-25 18:03:05 +02:00
}
2026-01-25 14:06:56 +01:00
log_tag ( " : Parity mismatch \n " ) ;
2013-09-18 21:56:06 +02:00
+ + error ;
2013-12-26 09:45:51 +01:00
} while ( combination_next ( r , n , ip ) ) ;
2013-09-11 23:30:32 +02:00
}
2026-04-27 15:43:06 +02:00
/*
* If we have a hash , and enough parities
* ( less - or - equal failures than number of parities )
*/
2013-10-29 22:22:40 +01:00
if ( has_hash & & failed_count < = n ) {
2026-01-17 21:16:21 +01:00
/* number of parities to use equal to the number of failures */
2013-10-29 22:22:40 +01:00
unsigned r = failed_count ;
/* all combinations (r of n) parities */
2013-12-26 09:45:51 +01:00
combination_first ( r , n , ip ) ;
2013-10-29 22:22:40 +01:00
do {
/* if a parity is missing, do nothing */
2014-09-27 00:32:39 +02:00
for ( i = 0 ; i < r ; + + i ) {
2013-12-26 09:45:51 +01:00
if ( buffer_recov [ ip [ i ] ] = = 0 )
2013-10-25 18:03:05 +02:00
break ;
}
2013-10-29 22:22:40 +01:00
if ( i ! = r )
2013-09-18 21:56:06 +02:00
continue ;
2013-09-11 23:30:32 +02:00
2013-10-29 22:22:40 +01:00
/* copy the parities to use */
2014-09-27 00:32:39 +02:00
for ( i = 0 ; i < r ; + + i )
memcpy ( buffer [ diskmax + ip [ i ] ] , buffer_recov [ ip [ i ] ] , state - > block_size ) ;
2013-09-11 23:30:32 +02:00
2013-10-25 18:03:05 +02:00
/* recover */
2015-03-21 17:16:16 +01:00
raid_data ( r , id , ip , diskmax , state - > block_size , buffer ) ;
2013-09-11 23:30:32 +02:00
2013-10-25 18:03:05 +02:00
/* use the hash to check the result */
2026-09-08 08:15:27 +02:00
if ( is_hash_matching ( state , rehash , diskmax , failed , failed_map , failed_count , buffer , is_old_strategy ) )
2013-09-18 21:56:06 +02:00
return 0 ;
2013-09-11 23:30:32 +02:00
2013-10-25 18:03:05 +02:00
/* log */
2026-06-04 00:34:51 +02:00
log_tag ( " recover_hash_error:% " PRIu64 " : " , pos ) ;
2014-09-27 00:32:39 +02:00
for ( i = 0 ; i < r ; + + i ) {
2013-10-29 22:22:40 +01:00
if ( i ! = 0 )
2015-04-20 22:07:42 +02:00
log_tag ( " / " ) ;
log_tag ( " %s " , lev_config_name ( ip [ i ] ) ) ;
2013-10-25 18:03:05 +02:00
}
2026-01-25 14:06:56 +01:00
log_tag ( " : Hash mismatch \n " ) ;
2013-09-18 21:56:06 +02:00
+ + error ;
2013-12-26 09:45:51 +01:00
} while ( combination_next ( r , n , ip ) ) ;
2013-09-11 23:30:32 +02:00
}
2012-02-09 23:17:36 +01:00
/* return the number of failed attempts, or -1 if no strategy */
if ( error )
return error ;
2014-03-02 17:05:21 +01:00
2026-06-04 00:34:51 +02:00
log_tag ( " recover_strategy_error:% " PRIu64 " : No strategy to recover from %u failures with %u parity %s hash \n " ,
2014-03-02 17:05:21 +01:00
pos , failed_count , n , has_hash ? " with " : " without " ) ;
return - 1 ;
2012-02-09 23:17:36 +01:00
}
2026-08-29 17:18:12 +02:00
/**
* Try to recover all the bad blocks of a parity stripe .
*
* Recovery after an interrupted sync is ambiguous because physical parity may
* represent either the CURRENT filesystem state or the OLD state that existed
* before the interrupted sync . For this reason two recovery strategies are
* attempted .
*
* This recovery logic relies on the following invariant :
*
* After an interrupted sync , the data - disk filesystem must not be changed
* before running recovery or before a subsequent sync completes successfully .
*
* Re - running sync without changing the filesystem is allowed , even if that sync
* is interrupted again . Under this invariant an unsynchronized CHG has only two
* relevant generations :
*
* OLD identified by the stored CHG hash ;
* CURRENT represented by the unchanged live filesystem contents .
*
* Physical parity may contain either generation because the interrupted sync
* may or may not have updated it , but there cannot be a third intermediate data
* generation created by a later filesystem change .
*
* This invariant is essential for CHG generation inference . If the filesystem
* is changed after an interrupted sync , for example by adding , deleting ,
* replacing or modifying files , a stored CHG / DELETED OLD hash may no longer
* identify the generation actually represented by physical parity . In that
* situation candidate ! = stored OLD would not be sufficient to prove CURRENT .
*
* 1. CURRENT strategy .
*
* Assume that parity was already updated by the interrupted sync .
* Readable blocks are therefore used as their CURRENT values and only bad
* blocks are reconstructed .
*
* BLK , REP and REBUILD have hashes identifying their required CURRENT
* contents and can therefore be independently validated .
*
* CHG is different : its stored hash identifies the OLD contents , not the
* CURRENT ones . After repair_step ( ) has independently validated the RAID
* solution , the recovered CHGs are examined as a group .
*
* Under the recovery invariant above , a recovered CHG candidate different
* from its stored OLD contents cannot belong to the OLD generation and
* therefore proves that the validated RAID solution is CURRENT .
*
* All CHGs reconstructed by the same repair_step ( ) belong to that same
* validated RAID solution . Consequently , if at least one recovered CHG
* proves that the solution is CURRENT , all CHGs reconstructed by that
* solution are CURRENT , including CHGs whose OLD hash is INVALID or whose
* OLD and CURRENT contents happen to be identical .
*
* If no recovered CHG distinguishes the solution from OLD , all recovered
2026-09-08 08:15:27 +02:00
* CHGs remain RECOVERY_OUTOFDATE . This does not mean that their contents are
2026-08-29 17:18:12 +02:00
* necessarily wrong or actually old , only that CURRENT cannot be proven .
*
* 2. OLD strategy .
*
* If the CURRENT strategy fails , assume that physical parity still
* represents the state before the interrupted sync .
*
* CHG , REP and DELETED entries are therefore converted to their OLD
* contribution when possible , or reconstructed as auxiliary RAID unknowns .
* This strategy is useful only when there is bad BLK / REBUILD data to
* recover . Any bad CHG / REP reconstructed under this strategy remains
2026-09-08 08:15:27 +02:00
* RECOVERY_OUTOFDATE because it cannot be certified as the required CURRENT data .
2026-08-29 17:18:12 +02:00
*
* REBUILD is orthogonal to the OLD / CURRENT sync - history distinction . Its data
* and stored hash are always CURRENT , while its physical parity is untrusted
* because an in - place - - force - full rebuild may have been interrupted . REBUILD
* therefore never requires conversion to an OLD data contribution and does not
* participate in the CHG generation inference . A matching REBUILD hash proves
* the recovered REBUILD data , but does not prove whether CHG / REP / DELETED parity
* belongs to the OLD or CURRENT sync generation .
*
* repair_step ( ) is responsible only for solving and independently validating
* the RAID equations . This function is responsible for interpreting the
* resulting solution according to the OLD / CURRENT history of unsynchronized
* blocks and the recovery invariant described above .
*
* Parameters :
*
* state SnapRAID state and configuration .
*
* rehash If non - zero , hashes must be computed using the previous hash
* algorithm / seed while processing a rehash operation .
*
* pos Parity block position being recovered . Used for logging .
*
* diskmax Number of data disks participating in the RAID stripe .
*
* failed Array describing all blocks relevant to recovery . Entries may
* represent actually bad blocks , but also readable CHG / REP or
* DELETED blocks needed by the OLD recovery strategy .
*
* failed_map Temporary array used to select which failed [ ] entries are RAID
* unknowns for each call to repair_step ( ) .
*
* failed_count Number of valid entries in failed [ ] .
*
* buffer Data and generated parity buffers . Data buffers are indexed by
* disk , followed by generated parity buffers .
*
* buffer_recov Physical parity blocks read from disk . A NULL entry means that
* the corresponding parity level is unavailable or cannot be
* trusted for recovery .
*
2026-09-07 23:15:47 +02:00
* buffer_scratch Scratch buffers used to hold OLD RAID unknowns without
* destroying certified CURRENT data .
*
2026-08-29 17:18:12 +02:00
* Return value :
*
* 0 A RAID solution was successfully validated under one of the two history
2026-09-08 08:15:27 +02:00
* hypotheses . Some originally bad CHG / REP entries may still be marked
* RECOVERY_OUTOFDATE , meaning that their required CURRENT contents could not
* be proven . The caller must inspect the recovery status before considering such
2026-08-29 17:18:12 +02:00
* blocks successfully recovered .
*
* > 0 One or more recovery candidates were attempted but failed independent
* validation . The value is the accumulated number of failed attempts .
*
* < 0 No usable recovery strategy could be attempted successfully , for example
* because there were too many failures or insufficient usable parity .
*
* On successful return buffer [ ] contains the recovered data and parity
* recomputed from that data .
*/
2026-09-07 23:15:47 +02:00
static int repair ( struct snapraid_state * state , int rehash , block_off_t pos , unsigned diskmax , struct failed_struct * failed , unsigned * failed_map , unsigned failed_count , void * * buffer , void * * buffer_recov , void * * buffer_scratch )
2012-02-09 23:17:36 +01:00
{
int ret ;
int error ;
2026-09-07 23:15:47 +02:00
unsigned j , k ;
unsigned n ;
2013-02-27 21:07:16 +01:00
int something_to_recover ;
2014-03-04 19:34:55 +01:00
int something_unsynced ;
2026-09-07 23:15:47 +02:00
int too_many_unknowns ;
struct {
unsigned j ;
void * buffer ;
} rep_saved [ LEV_MAX ] ;
unsigned scratch_count ;
2026-08-29 17:18:12 +02:00
2012-02-09 23:17:36 +01:00
error = 0 ;
2026-09-07 14:19:12 +02:00
for ( j = 0 ; j < failed_count ; + + j )
2026-09-08 08:15:27 +02:00
failed [ j ] . recovery = RECOVERY_NONE ;
2026-09-07 14:19:12 +02:00
2026-08-29 17:18:12 +02:00
/*
* Nothing is missing or damaged .
*
* repair ( ) is also responsible for leaving buffer [ ] with parity
* recomputed from the data buffers , so do that even if there is no
* actual recovery to perform .
*/
2014-02-01 17:57:26 +01:00
if ( failed_count = = 0 ) {
2026-09-02 16:31:21 +02:00
raid_gen ( diskmax , state - > level , state - > block_size , buffer , 0 ) ;
2014-02-01 17:57:26 +01:00
return 0 ;
}
2026-08-29 17:18:12 +02:00
/*
* Log all entries participating in recovery .
*
* failed [ ] contains more than just bad blocks . Readable CHG / REP and
* DELETED entries may also be present because their OLD contribution can
* be needed by the pre - sync recovery strategy below .
*/
2014-09-27 00:32:39 +02:00
for ( j = 0 ; j < failed_count ; + + j ) {
2015-08-05 09:42:11 +02:00
const char * desc ;
2014-03-18 21:04:25 +01:00
const char * hash ;
const char * data ;
struct snapraid_block * block = failed [ j ] . block ;
unsigned block_state = block_state_get ( block ) ;
switch ( block_state ) {
2015-08-05 09:42:11 +02:00
case BLOCK_STATE_DELETED : desc = " delete " ; break ;
case BLOCK_STATE_CHG : desc = " change " ; break ;
case BLOCK_STATE_REP : desc = " replace " ; break ;
case BLOCK_STATE_BLK : desc = " block " ; break ;
2026-08-28 12:11:05 +02:00
case BLOCK_STATE_REBUILD : desc = " rebuild " ; break ;
2014-03-18 22:35:01 +01:00
/* LCOV_EXCL_START */
2015-08-05 09:42:11 +02:00
default : desc = " unknown " ; break ;
2014-09-27 00:32:39 +02:00
/* LCOV_EXCL_STOP */
2014-03-18 21:04:25 +01:00
}
2026-07-30 12:40:08 +02:00
if ( block = = BLOCK_NULL ) {
hash = " none " ;
} else if ( hash_is_invalid ( block - > hash ) ) {
2014-03-18 21:04:25 +01:00
hash = " lost " ;
} else if ( hash_is_zero ( block - > hash ) ) {
hash = " zero " ;
} else {
hash = " known " ;
}
if ( failed [ j ] . is_bad )
data = " bad " ;
else
data = " good " ;
2015-10-03 10:43:02 +02:00
if ( failed [ j ] . file ) {
struct snapraid_disk * disk = failed [ j ] . disk ;
struct snapraid_file * file = failed [ j ] . file ;
block_off_t file_pos = failed [ j ] . file_pos ;
2014-03-18 21:04:25 +01:00
2026-06-03 23:06:27 +02:00
log_tag ( " repair_entry:%u:%s:%s:%s:%s:%s:% " PRIu64 " : \n " , j , desc , hash , data , disk - > name , esc_tag ( file - > sub ) , file_pos ) ;
2014-03-18 21:04:25 +01:00
} else {
2026-01-25 14:06:56 +01:00
log_tag ( " repair_entry:%u:%s:%s:%s: \n " , j , desc , hash , data ) ;
2014-03-18 21:04:25 +01:00
}
}
2026-04-27 15:43:06 +02:00
/*
2026-08-29 17:18:12 +02:00
* Under the recovery invariant documented above , an interrupted sync leaves
* exactly two possible data generations for unsynchronized blocks :
*
* OLD the state identified by the pre - sync metadata ;
* CURRENT the unchanged live filesystem state .
*
* The content file tells us which blocks were not fully synchronized , but it
* cannot tell us whether physical parity had already advanced from OLD to
* CURRENT before the interruption .
*
* We therefore try the only two possible history hypotheses :
*
* 1. parity represents CURRENT data ;
* 2. parity still represents OLD data .
*
* Repeated interrupted sync attempts do not add another generation as long as
* the filesystem remains unchanged between them .
*
* REBUILD does not introduce another data - generation hypothesis . Its data is
* CURRENT in both cases ; only the physical parity associated with it is
* independently untrusted .
*
* If sync completed normally there are no CHG / REP / DELETED states and the
* distinction disappears .
2026-04-27 15:43:06 +02:00
*/
2012-06-20 23:05:15 +02:00
2026-08-29 17:18:12 +02:00
/************************************************************************/
/* First strategy: parity represents CURRENT data. */
/************************************************************************/
2014-02-01 17:57:26 +01:00
2026-04-27 15:43:06 +02:00
/*
2026-08-29 17:18:12 +02:00
* Under the CURRENT hypothesis :
*
* BLK hash CURRENT , parity normally synchronized
* REP hash CURRENT , parity may represent OLD or CURRENT
* REBUILD hash CURRENT , data CURRENT , physical parity untrusted
* CHG hash OLD , parity may represent OLD or CURRENT
* DELETED no CURRENT data , parity may represent OLD or zero
*
* Readable blocks already contain their CURRENT filesystem contents , so
* only bad blocks have to be reconstructed .
*
* BLK / REP / REBUILD can first be fetched from import / search because their
* stored hash identifies exactly the contents required by this strategy .
*
* CHG cannot use this shortcut because fetching its stored hash would
* retrieve OLD contents .
2026-04-27 15:43:06 +02:00
*/
2012-02-09 23:17:36 +01:00
n = 0 ;
2026-08-29 17:18:12 +02:00
something_to_recover = 0 ;
2014-09-27 00:32:39 +02:00
for ( j = 0 ; j < failed_count ; + + j ) {
2012-02-09 23:17:36 +01:00
if ( failed [ j ] . is_bad ) {
2013-02-27 21:07:16 +01:00
unsigned block_state = block_state_get ( failed [ j ] . block ) ;
2026-08-29 17:18:12 +02:00
assert ( block_state ! = BLOCK_STATE_DELETED ) ;
2013-02-27 21:07:16 +01:00
2026-08-28 12:11:05 +02:00
if ( ( block_state = = BLOCK_STATE_BLK | | block_state = = BLOCK_STATE_REP | | block_state = = BLOCK_STATE_REBUILD )
2014-10-19 21:07:17 +02:00
& & ( state_import_fetch ( state , rehash , failed [ j ] . block , buffer [ failed [ j ] . index ] ) = = 0
2026-01-18 11:03:07 +01:00
| | state_search_fetch ( state , rehash , failed [ j ] . file , failed [ j ] . file_pos , failed [ j ] . block , buffer [ failed [ j ] . index ] ) = = 0 )
2014-02-01 17:57:26 +01:00
) {
2026-08-29 17:18:12 +02:00
/*
* The required CURRENT contents were obtained directly
* using their CURRENT hash , so this block no longer
* consumes a RAID recovery equation .
2026-09-07 14:19:12 +02:00
*
* Keep is_bad set because the file block still needs to be
2026-09-08 08:15:27 +02:00
* written back . RECOVERY_CURRENT records that buffer [ index ] already
2026-09-07 14:19:12 +02:00
* contains certified CURRENT data .
2026-08-29 17:18:12 +02:00
*/
2026-09-08 08:15:27 +02:00
failed [ j ] . recovery = RECOVERY_CURRENT ;
2026-01-25 14:06:56 +01:00
log_tag ( " repair_hash_import:%u: Fixed by import \n " , j ) ;
2013-02-27 21:07:16 +01:00
} else {
2026-08-29 17:18:12 +02:00
/*
* Otherwise reconstruct the block from parity .
*/
2013-02-27 21:07:16 +01:00
failed_map [ n ] = j ;
+ + n ;
something_to_recover = 1 ;
}
2012-02-09 23:17:36 +01:00
}
}
2026-08-29 17:18:12 +02:00
/*
* All originally bad blocks were recovered through import / search .
*/
2013-02-27 21:07:16 +01:00
if ( ! something_to_recover ) {
2026-06-04 00:34:51 +02:00
log_tag ( " recover_sync:% " PRIu64 " :%u: Skipped for already recovered \n " , pos , n ) ;
2014-03-18 21:04:25 +01:00
2026-09-02 16:31:21 +02:00
raid_gen ( diskmax , state - > level , state - > block_size , buffer , 0 ) ;
2012-02-09 23:17:36 +01:00
return 0 ;
}
2026-08-29 17:18:12 +02:00
/*
* Recover the selected bad blocks and independently validate the RAID
* solution .
*
* repair_step ( ) validates the reconstructed vector either using one or
* more trustworthy CURRENT hashes , or , when no such hash exists , using
* one additional independent parity equation .
*
* BLK , REP and REBUILD have CURRENT hashes and can therefore validate
* their own recovered data directly . In particular , a REBUILD hash is
* trustworthy even though the physical parity used to obtain the candidate
* is not .
*
* Recovered CHGs require an additional history interpretation because
* they have no stored CURRENT hash . Under the recovery invariant there are
* only OLD and CURRENT generations , allowing their generation to be inferred
* after the RAID solution itself has been validated .
*/
2026-09-08 08:15:27 +02:00
ret = repair_step ( state , rehash , pos , diskmax , failed , failed_map , n , buffer , buffer_recov , 0 ) ;
2026-08-29 17:18:12 +02:00
2012-02-09 23:17:36 +01:00
if ( ret = = 0 ) {
2026-08-29 17:18:12 +02:00
int has_recovered_chg ;
int current_generation_proven ;
has_recovered_chg = 0 ;
current_generation_proven = 0 ;
2026-04-27 15:43:06 +02:00
/*
2026-08-29 17:18:12 +02:00
* Determine the generation of all CHGs reconstructed by this RAID
* solution as a group .
*
* This inference relies on the recovery invariant documented by repair ( ) :
* the filesystem has not changed since the interrupted sync . Therefore the
* only possible CHG data generations are OLD and CURRENT , and the stored CHG
* hash identifies OLD .
*
* Under this invariant :
*
* candidate ! = OLD
*
* proves that this particular candidate cannot belong to the OLD generation .
* Since no third generation is possible , it therefore proves CURRENT .
*
* This would NOT be valid if the filesystem had been changed after the
* interrupted sync . For example , deleting and later reallocating a parity
* position may preserve a historical CHG / DELETED hash while physical parity
* represents another generation . In such a history candidate ! = stored hash
* would only prove that the candidate differs from that historical hash , not
* that it is CURRENT .
*
* All CHGs reconstructed by this repair_step ( ) belong to the same validated
* RAID solution . Consequently , once one recovered CHG proves that the
* solution is CURRENT , that generation classification applies to every CHG
* reconstructed by the same solution .
*
* This is intentionally a solution - level decision rather than a block - level
* one .
*
* For example :
*
* CHG A has an INVALID OLD hash ;
* CHG B has candidate ! = OLD .
*
* CHG B identifies the validated RAID solution as CURRENT , so CHG A is also
* CURRENT despite being unable to make that determination by itself .
*
* Similarly :
*
* CHG A candidate = = OLD ;
* CHG B candidate ! = OLD .
*
* CHG A may simply have identical OLD and CURRENT bytes . Matching OLD is
* therefore not evidence that CHG A is out - of - date once another CHG has
* identified the whole solution as CURRENT .
*
* REBUILD does not participate in this generation inference . Its recovered
* contents may be independently proven CURRENT by its hash , but this only
* validates REBUILD itself and says nothing about whether CHG parity belongs
* to the OLD or CURRENT sync generation .
*
* If no recovered CHG distinguishes the solution from OLD , the RAID solution
* remains valid but its generation is ambiguous . In that case all recovered
2026-09-08 08:15:27 +02:00
* CHGs are marked RECOVERY_OUTOFDATE together .
2026-04-27 15:43:06 +02:00
*/
2014-09-27 00:32:39 +02:00
for ( j = 0 ; j < failed_count ; + + j ) {
2026-08-29 17:18:12 +02:00
unsigned block_state ;
2012-06-20 23:05:15 +02:00
2026-08-29 17:18:12 +02:00
if ( ! failed [ j ] . is_bad )
continue ;
block_state = block_state_get ( failed [ j ] . block ) ;
if ( block_state ! = BLOCK_STATE_CHG ) {
2026-04-27 15:43:06 +02:00
/*
2026-08-29 17:18:12 +02:00
* BLK , REP and REBUILD have CURRENT hashes , so their own
* recovered contents can be validated directly and don ' t
* need this OLD / CURRENT generation inference .
*
* REBUILD is deliberately not a generation discriminator :
* its data is CURRENT independently from the state of its
* physical parity .
2026-04-27 15:43:06 +02:00
*/
2026-08-29 17:18:12 +02:00
assert ( block_state = = BLOCK_STATE_BLK | | block_state = = BLOCK_STATE_REP | | block_state = = BLOCK_STATE_REBUILD ) ;
continue ;
}
has_recovered_chg = 1 ;
2014-02-01 17:57:26 +01:00
2026-08-29 17:18:12 +02:00
/*
* With an INVALID OLD hash this CHG cannot tell us whether
* the candidate differs from OLD . It neither proves CURRENT
* nor disproves it .
*/
if ( hash_is_invalid ( failed [ j ] . block - > hash ) )
continue ;
2014-02-01 17:57:26 +01:00
2026-08-29 17:18:12 +02:00
if ( hash_is_zero ( failed [ j ] . block - > hash ) ) {
2026-04-27 15:43:06 +02:00
/*
2026-08-29 17:18:12 +02:00
* ZERO exactly identifies the OLD contribution under the
* recovery invariant .
*
* A non - zero candidate therefore proves candidate ! = OLD and ,
* since no third generation is possible , identifies the solution
* as CURRENT .
*
* A zero candidate is ambiguous because OLD and CURRENT
* may legitimately contain the same zero block .
2026-04-27 15:43:06 +02:00
*/
2026-09-10 18:36:50 +02:00
if ( membcmp ( buffer [ failed [ j ] . index ] , 0 , state - > block_size ) ! = 0 )
2026-08-29 17:18:12 +02:00
current_generation_proven = 1 ;
} else {
2026-09-07 11:58:31 +02:00
unsigned hash_kind ;
2014-03-18 21:04:25 +01:00
2026-09-07 11:58:31 +02:00
hash_kind = rehash ? state - > prevhash : state - > hash ;
2014-03-18 21:04:25 +01:00
2026-08-29 17:18:12 +02:00
/*
2026-09-07 11:45:16 +02:00
* The stored CHG hash identifies OLD contents under the recovery invariant .
* Generation detection must compare hash identity only and must not apply the
2026-09-07 11:58:31 +02:00
* CURRENT logical - size padding requirement .
2026-08-29 17:18:12 +02:00
*
2026-09-07 11:58:31 +02:00
* When the hash covers the complete canonical RAID block ( memhash_is_block ( ) ) ,
* candidate identity is independent of logical size : a mismatch proves
* candidate ! = OLD and therefore identifies this validated RAID solution as CURRENT .
*
* For hashes that depend on the logical size ( Murmur3 / Spooky2 ) , the OLD
* logical size is not available . Because a mismatch against CURRENT pos_size
* cannot distinguish candidate ! = OLD from a change in file size , the
* conservative approach keeps the generation ambiguous and does not treat
* the mismatch as proof of CURRENT .
2026-08-29 17:18:12 +02:00
*/
2026-09-07 11:58:31 +02:00
if ( memhash_is_block ( hash_kind ) ) {
size_t pos_size ;
pos_size = file_block_size ( failed [ j ] . file , failed [ j ] . file_pos , state - > block_size ) ;
if ( block_hash_cmp ( state , rehash , failed [ j ] . block , pos_size , buffer [ failed [ j ] . index ] ) ! = 0 )
current_generation_proven = 1 ;
}
2026-08-29 17:18:12 +02:00
}
}
if ( has_recovered_chg ) {
2026-09-08 08:15:27 +02:00
if ( current_generation_proven )
2026-08-29 17:18:12 +02:00
log_tag ( " repair_generation_current:% " PRIu64 " : Recovered CHG proves CURRENT generation \n " , pos ) ;
2026-09-08 08:15:27 +02:00
else
2026-08-29 17:18:12 +02:00
log_tag ( " repair_generation_unknown:% " PRIu64 " : Recovered CHG generation is ambiguous \n " , pos ) ;
2012-02-09 23:17:36 +01:00
}
2012-06-20 23:05:15 +02:00
2026-09-08 08:15:27 +02:00
/* mark all recovered blocks according to their proven generation */
2026-09-07 23:15:47 +02:00
for ( j = 0 ; j < failed_count ; + + j ) {
2026-09-08 08:15:27 +02:00
unsigned block_state ;
if ( ! failed [ j ] . is_bad )
continue ;
block_state = block_state_get ( failed [ j ] . block ) ;
if ( block_state = = BLOCK_STATE_CHG ) {
if ( current_generation_proven ) {
/*
* At least one recovered CHG distinguishes this validated
* RAID solution from OLD .
*
* Under the recovery invariant this proves that the solution
* is CURRENT . The CURRENT classification therefore applies to
* every CHG reconstructed by the same solution , including CHGs
* with an INVALID OLD hash or with OLD = = CURRENT contents .
*/
failed [ j ] . recovery = RECOVERY_CURRENT ;
} else {
/*
* The RAID solution itself was successfully validated ,
* but none of its recovered CHGs distinguishes CURRENT
* from OLD .
*
* This does not prove that the bytes are actually old or
* wrong . It only means that CURRENT cannot be proven .
*
* Preserve all recovered CHGs as best - effort data but
* mark them out - of - date so the caller does not report
* them as successfully recovered CURRENT contents .
*/
failed [ j ] . recovery = RECOVERY_OUTOFDATE ;
}
} else {
/* BLK, REP and REBUILD have trustworthy CURRENT hashes */
failed [ j ] . recovery = RECOVERY_CURRENT ;
}
2026-09-07 23:15:47 +02:00
}
2012-02-09 23:17:36 +01:00
return 0 ;
}
2026-08-29 17:18:12 +02:00
2012-02-09 23:17:36 +01:00
if ( ret > 0 )
error + = ret ;
2014-03-18 21:04:25 +01:00
if ( ret < 0 )
2026-06-04 00:34:51 +02:00
log_tag ( " recover_sync:% " PRIu64 " :%u: Failed with no attempts \n " , pos , n ) ;
2014-03-18 21:04:25 +01:00
else
2026-06-04 00:34:51 +02:00
log_tag ( " recover_sync:% " PRIu64 " :%u: Failed with %d attempts \n " , pos , n , ret ) ;
2014-03-18 21:04:25 +01:00
2026-08-29 17:18:12 +02:00
/************************************************************************/
/* Second strategy: parity still represents OLD data. */
/************************************************************************/
2026-04-27 15:43:06 +02:00
/*
2026-08-29 17:18:12 +02:00
* The CURRENT attempt failed , so retry under the opposite history
* hypothesis : physical parity still represents the state before the
* interrupted sync .
*
* This interpretation also relies on the recovery invariant . Because no
* filesystem changes occurred after the interruption , CHG / DELETED OLD
* hashes still refer to the only pre - CURRENT generation that physical
* parity may represent .
*
* Under this hypothesis :
*
* BLK OLD parity contains the synchronized data we want to
* recover ;
*
* REBUILD data and hash are CURRENT and unchanged by this history
* choice . Physical parity remains independently untrusted ;
*
* CHG parity contains OLD while the readable file contains
* CURRENT ;
*
* REP parity contains OLD while its stored hash describes
* CURRENT ;
*
* DELETED parity contains OLD contents that are no longer present
* in the current filesystem .
*
* Every CHG / REP / DELETED contribution must therefore be converted to OLD
* when possible , or reconstructed as an auxiliary RAID unknown .
*
* REBUILD does not require such conversion because it has no alternate OLD
* data contribution associated with this state . If REBUILD itself is bad ,
* it is recovered as required CURRENT data and independently validated
* through its CURRENT hash .
*
* The reconstructed OLD CHG / REP / DELETED values exist only to make recovery
* of bad BLK / REBUILD data possible . They are not themselves considered a
* successful recovery of CURRENT unsynchronized data .
2026-04-27 15:43:06 +02:00
*/
2012-02-09 23:17:36 +01:00
n = 0 ;
2026-08-29 17:18:12 +02:00
something_to_recover = 0 ;
something_unsynced = 0 ;
2026-09-07 23:15:47 +02:00
scratch_count = 0 ;
too_many_unknowns = 0 ;
2026-08-29 17:18:12 +02:00
2014-09-27 00:32:39 +02:00
for ( j = 0 ; j < failed_count ; + + j ) {
2013-02-27 21:07:16 +01:00
unsigned block_state = block_state_get ( failed [ j ] . block ) ;
2012-02-09 23:17:36 +01:00
2026-08-29 17:18:12 +02:00
/*
* Only CHG / REP / DELETED make the OLD strategy different from the
* CURRENT one because they have different OLD and CURRENT data
* contributions .
*
* REBUILD is deliberately excluded . Its data contribution is CURRENT
* in both history hypotheses ; only its physical parity is untrusted ,
* and repair_step ( ) deals with that through independent validation .
*/
if ( block_state = = BLOCK_STATE_DELETED | | block_state = = BLOCK_STATE_CHG | | block_state = = BLOCK_STATE_REP ) {
2026-04-27 15:43:06 +02:00
/*
2026-08-29 17:18:12 +02:00
* There is at least one entry whose OLD and CURRENT parity
* contributions may differ , making this history hypothesis
* distinct from the CURRENT attempt .
2026-04-27 15:43:06 +02:00
*/
2014-03-04 19:34:55 +01:00
something_unsynced = 1 ;
2014-02-01 17:57:26 +01:00
if ( block_state = = BLOCK_STATE_CHG
& & hash_is_zero ( failed [ j ] . block - > hash )
) {
2026-04-27 15:43:06 +02:00
/*
2026-08-29 17:18:12 +02:00
* ZERO exactly identifies the OLD CHG contribution under
* the recovery invariant .
*
* Supply it directly without consuming a RAID equation .
2026-08-28 09:36:12 +02:00
*
2026-08-29 17:18:12 +02:00
* For a readable CHG , is_bad is false , so this temporary
* OLD buffer will not later be written back to the file .
2026-08-28 09:36:12 +02:00
*/
2026-08-29 17:18:12 +02:00
memset ( buffer [ failed [ j ] . index ] , 0 , state - > block_size ) ;
2014-02-01 17:57:26 +01:00
} else if ( ( block_state = = BLOCK_STATE_CHG | | block_state = = BLOCK_STATE_DELETED )
2016-04-10 19:13:08 +02:00
& & hash_is_unique ( failed [ j ] . block - > hash )
2014-10-19 21:07:17 +02:00
& & state_import_fetch ( state , rehash , failed [ j ] . block , buffer [ failed [ j ] . index ] ) = = 0 ) {
2026-04-27 15:43:06 +02:00
/*
2026-08-29 17:18:12 +02:00
* CHG and DELETED retain a hash identifying OLD contents
* under the recovery invariant , so import can provide their
* exact pre - sync contribution .
*
* REP cannot use this shortcut because its stored hash
* identifies CURRENT / NEW contents instead .
*
* Again , readable auxiliary entries have is_bad = = 0 and
* this OLD value will not be written to the user file .
2026-04-27 15:43:06 +02:00
*/
2013-02-27 21:07:16 +01:00
} else {
2026-09-07 23:15:47 +02:00
/*
* Adding this OLD unknown would exceed the number of available parity
* equations . No OLD recovery strategy can possibly use it , so stop before
* consuming a scratch buffer or changing independently validated CURRENT data .
*/
if ( n > = state - > level ) {
too_many_unknowns = 1 ;
break ;
}
/*
* If buffer [ index ] already contains CURRENT data validated by import / search ,
* swap it with a preallocated scratch buffer so that the OLD reconstruction
* is solved into scratch without overwriting the certified CURRENT bytes .
*/
2026-09-08 08:15:27 +02:00
if ( block_state = = BLOCK_STATE_REP & & failed [ j ] . is_bad & & failed [ j ] . recovery = = RECOVERY_CURRENT ) {
2026-09-07 23:15:47 +02:00
assert ( scratch_count < state - > level ) ;
rep_saved [ scratch_count ] . j = j ;
rep_saved [ scratch_count ] . buffer = buffer [ failed [ j ] . index ] ;
buffer [ failed [ j ] . index ] = buffer_scratch [ scratch_count ] ;
+ + scratch_count ;
}
2026-08-29 17:18:12 +02:00
/*
* The required OLD contribution is unavailable directly ,
* so recover it as another RAID unknown .
*/
2013-02-27 21:07:16 +01:00
failed_map [ n ] = j ;
+ + n ;
2012-02-09 23:17:36 +01:00
2026-04-27 15:43:06 +02:00
/*
2026-08-29 17:18:12 +02:00
* Do not set something_to_recover .
*
* Reconstructing only OLD CHG / REP / DELETED values has no
* user - visible purpose . They are useful only as auxiliary
* state for recovering a BLK / REBUILD block .
2026-04-27 15:43:06 +02:00
*/
2013-02-27 21:07:16 +01:00
}
} else if ( failed [ j ] . is_bad ) {
2026-04-27 15:43:06 +02:00
/*
2026-08-29 17:18:12 +02:00
* All generation - dependent unsynchronized states were handled
* above .
*
* A bad BLK is synchronized data represented by the pre - sync
* parity .
*
* A bad REBUILD also belongs here because its required data and
* hash are CURRENT and do not have a separate OLD version . Its
* physical parity is untrusted , but repair_step ( ) accepts the
* candidate only after independent validation .
2026-09-07 14:19:12 +02:00
*
* If CURRENT recovery already obtained this BLK / REBUILD through
* import / search , buffer [ index ] contains certified CURRENT data .
* BLK and REBUILD have the same required data contribution under
* both history hypotheses , so that buffer remains a known RAID
* input and must not consume another recovery equation .
*
* Keep is_bad unchanged because the original file block still
* needs to be written back after the overall recovery succeeds .
2026-04-27 15:43:06 +02:00
*/
2026-08-28 12:11:05 +02:00
assert ( block_state = = BLOCK_STATE_BLK | | block_state = = BLOCK_STATE_REBUILD ) ;
2012-02-09 23:17:36 +01:00
2026-09-08 08:15:27 +02:00
if ( failed [ j ] . recovery ! = RECOVERY_CURRENT ) {
2026-09-07 23:15:47 +02:00
if ( n > = state - > level ) {
too_many_unknowns = 1 ;
break ;
}
2026-09-07 14:19:12 +02:00
something_to_recover = 1 ;
2014-02-01 17:57:26 +01:00
2026-09-07 14:19:12 +02:00
failed_map [ n ] = j ;
+ + n ;
}
2013-02-27 21:07:16 +01:00
}
}
2012-06-20 23:05:15 +02:00
2026-04-27 15:43:06 +02:00
/*
2026-08-29 17:18:12 +02:00
* Retry only when :
2026-08-28 09:36:12 +02:00
*
2026-08-29 17:18:12 +02:00
* - there is BLK / REBUILD data that we actually need to recover ; and
2026-08-28 09:36:12 +02:00
*
2026-08-29 17:18:12 +02:00
* - there is some generation - dependent CHG / REP / DELETED state making
* this strategy different from the CURRENT attempt .
*
* REBUILD alone does not justify this retry because changing from the
* CURRENT to the OLD history hypothesis does not change its data input .
*
* Otherwise this strategy either has no useful CURRENT data to recover
* or would just repeat the same recovery equations already attempted .
2026-04-27 15:43:06 +02:00
*/
2026-09-07 23:15:47 +02:00
if ( ! too_many_unknowns & & something_to_recover & & something_unsynced ) {
2026-09-08 08:15:27 +02:00
ret = repair_step ( state , rehash , pos , diskmax , failed , failed_map , n , buffer , buffer_recov , 1 ) ;
2026-08-29 17:18:12 +02:00
2013-02-27 21:07:16 +01:00
if ( ret = = 0 ) {
2026-04-27 15:43:06 +02:00
/*
2026-08-29 17:18:12 +02:00
* The RAID solution is valid under the OLD - history hypothesis .
*
* Recovered BLK data is useful CURRENT data because its
* synchronized contents are represented by OLD parity .
*
* Recovered REBUILD data is also required CURRENT data , but for
* a different reason : REBUILD has no alternate OLD data
* generation and its candidate is independently validated by
* its CURRENT hash despite its untrusted physical parity .
2026-09-07 23:15:47 +02:00
*/
/*
* Restore the independently proven CURRENT bytes for REP blocks that
* used a scratch buffer during the OLD strategy .
*/
for ( k = 0 ; k < scratch_count ; + + k ) {
unsigned idx = rep_saved [ k ] . j ;
buffer [ failed [ idx ] . index ] = rep_saved [ k ] . buffer ;
2026-09-08 08:15:27 +02:00
failed [ idx ] . recovery = RECOVERY_CURRENT ;
2026-09-07 23:15:47 +02:00
log_tag ( " repair_hash_import:%u: Restored CURRENT data after OLD retry \n " , idx ) ;
}
scratch_count = 0 ;
/*
2026-09-08 08:15:27 +02:00
* Mark all recovered blocks according to their proven generation .
2026-09-07 23:15:47 +02:00
* Any bad CHG / REP reconstructed here without validated CURRENT data is
* OLD - compatible auxiliary data and cannot be certified as CURRENT .
2026-09-08 08:15:27 +02:00
* Bad BLK and REBUILD blocks are certified CURRENT by their hash .
2026-04-27 15:43:06 +02:00
*/
2014-09-27 00:32:39 +02:00
for ( j = 0 ; j < failed_count ; + + j ) {
2026-09-08 08:15:27 +02:00
unsigned block_state ;
2014-03-18 21:04:25 +01:00
2026-09-08 08:15:27 +02:00
/*
* Skip blocks that do not need recovery , or blocks already validated as
* CURRENT via import / search ( including imported REPs restored above from
* scratch buffers ) to prevent demoting them to RECOVERY_OUTOFDATE .
*/
if ( ! failed [ j ] . is_bad | | failed [ j ] . recovery = = RECOVERY_CURRENT )
continue ;
block_state = block_state_get ( failed [ j ] . block ) ;
if ( block_state = = BLOCK_STATE_CHG | | block_state = = BLOCK_STATE_REP ) {
/* out-of-date entries cannot be certified CURRENT */
failed [ j ] . recovery = RECOVERY_OUTOFDATE ;
log_tag ( " repair_hash_unknown:%u: Surely old data \n " , j ) ;
} else {
/* BLK and REBUILD have trustworthy CURRENT hashes */
failed [ j ] . recovery = RECOVERY_CURRENT ;
}
2026-09-07 23:15:47 +02:00
}
2013-02-27 21:07:16 +01:00
return 0 ;
2012-02-09 23:17:36 +01:00
}
2026-08-29 17:18:12 +02:00
2013-02-27 21:07:16 +01:00
if ( ret > 0 )
error + = ret ;
2014-03-18 21:04:25 +01:00
if ( ret < 0 )
2026-06-04 00:34:51 +02:00
log_tag ( " recover_unsync:% " PRIu64 " :%u: Failed with no attempts \n " , pos , n ) ;
2014-03-18 21:04:25 +01:00
else
2026-06-04 00:34:51 +02:00
log_tag ( " recover_unsync:% " PRIu64 " :%u: Failed with %d attempts \n " , pos , n , ret ) ;
2014-03-18 21:04:25 +01:00
} else {
2026-09-07 23:15:47 +02:00
if ( too_many_unknowns )
log_tag ( " recover_unsync:% " PRIu64 " :%u: Skipped for too many failures \n " , pos , n + 1 ) ;
else
log_tag ( " recover_unsync:% " PRIu64 " :%u: Skipped for%s%s \n " , pos , n , ! something_to_recover ? " nothing to recover " : " " , ! something_unsynced ? " nothing unsynced " : " " ) ;
}
/* restore original buffer pointers and state if OLD strategy failed or was skipped */
for ( k = 0 ; k < scratch_count ; + + k ) {
unsigned idx = rep_saved [ k ] . j ;
buffer [ failed [ idx ] . index ] = rep_saved [ k ] . buffer ;
2026-09-08 08:15:27 +02:00
failed [ idx ] . recovery = RECOVERY_CURRENT ;
2012-02-09 23:17:36 +01:00
}
2026-08-29 17:18:12 +02:00
/*
* Neither history hypothesis produced a usable RAID solution .
*
* Return the accumulated number of failed validation attempts when at
* least one candidate was actually tried . Otherwise return - 1 to indicate
* that no usable recovery strategy was available .
*/
2011-04-24 10:24:05 +02:00
if ( error )
return error ;
else
return - 1 ;
2011-04-23 21:38:26 +02:00
}
2014-12-29 08:50:20 +01:00
/**
* Post process all the files at the specified block index : : i .
2026-01-17 21:16:21 +01:00
* For each file , if we are at the last block , close it ,
2015-10-07 22:07:20 +02:00
* adjust the timestamp , and print the result .
2015-08-31 22:01:34 +02:00
*
2021-05-22 21:02:51 +02:00
* This works only if the whole file is processed , including its last block .
2021-05-22 22:17:42 +02:00
* This doesn ' t always happen , like with an explicit end block .
2021-05-22 21:02:51 +02:00
*
* In such case , the check / fix command won ' t report any information of the
2021-05-22 22:17:42 +02:00
* files partially checked .
2014-12-29 08:50:20 +01:00
*/
2026-08-17 23:03:28 +02:00
static int file_post ( struct snapraid_state * state , int fix , int partial , block_off_t i , struct snapraid_handle * handle , unsigned diskmax )
2014-12-29 08:50:20 +01:00
{
unsigned j ;
int ret ;
2026-04-27 15:43:06 +02:00
/*
* For all the files print the final status , and do the final time fix
* we also ensure to close files after processing the last block
*/
2014-12-29 08:50:20 +01:00
for ( j = 0 ; j < diskmax ; + + j ) {
struct snapraid_block * block ;
struct snapraid_disk * disk ;
struct snapraid_file * collide_file ;
struct snapraid_file * file ;
2015-10-03 10:43:02 +02:00
block_off_t file_pos ;
2014-12-29 08:50:20 +01:00
disk = handle [ j ] . disk ;
if ( ! disk ) {
/* if no disk, nothing to do */
continue ;
}
2016-01-16 14:26:05 +01:00
block = fs_par2block_find ( disk , i ) ;
2014-12-29 08:50:20 +01:00
if ( ! block_has_file ( block ) ) {
/* if no file, nothing to do */
continue ;
}
2015-10-03 10:43:02 +02:00
file = fs_par2file_get ( disk , i , & file_pos ) ;
2014-12-29 08:50:20 +01:00
/* if it isn't the last block in the file */
2015-10-03 10:43:02 +02:00
if ( ! file_block_is_last ( file , file_pos ) ) {
2014-12-29 08:50:20 +01:00
/* nothing to do */
continue ;
}
/* if the file is excluded, we have nothing to adjust as the file is never written */
if ( file_flag_has ( file , FILE_IS_EXCLUDED )
| | ( state - > opt . syncedonly & & file_flag_has ( file , FILE_IS_UNSYNCED ) ) ) {
/* nothing to do, but close the file */
goto close_and_continue ;
}
/* finish the fix process if it's the last block of the files */
if ( fix ) {
2026-08-17 22:20:30 +02:00
if ( handle [ j ] . file ! = file ) {
/* LCOV_EXCL_START */
log_fatal ( EINTERNAL , " Internal inconsistency in file handle '%s' instead of '%s' \n " ,
handle [ j ] . file ! = 0 ? handle [ j ] . file - > sub : " <none> " , file - > sub ) ;
os_abort ( ) ;
/* LCOV_EXCL_STOP */
}
2026-08-17 23:03:28 +02:00
/*
* A partial fix operates only at block level . Reaching the last block of a
* file doesn ' t imply that the whole file was processed , so don ' t perform any
2026-08-29 21:25:49 +02:00
* file - level finalization , including marking it finished , reporting its final
* status , or restoring its timestamp .
2026-08-17 23:03:28 +02:00
* This is intentional also when an explicit block range happens to cover all
* the parity blocks , to keep - S / - B behavior independent of the parity size .
*/
if ( partial )
goto close_and_continue ;
2026-04-27 15:43:06 +02:00
/*
* Mark that we finished with this file
* to identify later any NOT finished ones
*/
2014-12-29 08:50:20 +01:00
file_flag_set ( file , FILE_IS_FINISHED ) ;
/* if the file is damaged, meaning that a fix failed */
if ( file_flag_has ( file , FILE_IS_DAMAGED ) ) {
2026-08-17 22:20:30 +02:00
log_tag ( " status:unrecoverable:%s:%s \n " , disk - > name , esc_tag ( file - > sub ) ) ;
msg_info ( " unrecoverable %s \n " , fmt_term ( disk , file - > sub ) ) ;
/* and do not set the time if damaged */
goto close_and_continue ;
}
2014-12-29 08:50:20 +01:00
/* if the file is not fixed, meaning that it is untouched */
2026-08-29 21:25:49 +02:00
if ( ! file_flag_has ( file , FILE_IS_FIXED ) ) {
2014-12-29 08:50:20 +01:00
/* nothing to do, but close the file */
goto close_and_continue ;
}
2026-04-27 15:43:06 +02:00
/*
* If the file is closed or different than the one expected , reopen it
* a different open file could happen when filtering for bad blocks
*/
2015-09-02 19:37:24 +02:00
if ( handle [ j ] . file ! = file ) {
2026-01-24 19:19:11 +01:00
/* keep a pointer at the file we are going to close for error reporting */
struct snapraid_file * report = handle [ j ] . file ;
2015-09-02 19:37:24 +02:00
ret = handle_close ( & handle [ j ] ) ;
if ( ret ! = 0 ) {
/* LCOV_EXCL_START */
2026-06-04 00:34:51 +02:00
log_tag ( " %s:% " PRIu64 " :%s:%s: Close error. %s. \n " , es ( errno ) , i , disk - > name , esc_tag ( report - > sub ) , strerror ( errno ) ) ;
2026-01-24 19:19:11 +01:00
log_fatal_errno ( errno , disk - > name ) ;
2015-09-02 19:37:24 +02:00
return - 1 ;
/* LCOV_EXCL_STOP */
}
2026-08-27 16:59:23 +02:00
/* reopen the file for writing, as required to set the mtime on Windows */
2026-08-17 22:20:30 +02:00
ret = handle_create ( & handle [ j ] , file , state - > file_mode ) ;
2015-09-02 19:37:24 +02:00
if ( ret ! = 0 ) {
/* LCOV_EXCL_START */
2026-06-04 00:34:51 +02:00
log_tag ( " %s:% " PRIu64 " :%s:%s: Open error. %s. \n " , es ( errno ) , i , disk - > name , esc_tag ( file - > sub ) , strerror ( errno ) ) ;
2026-01-24 19:19:11 +01:00
log_fatal_errno ( errno , disk - > name ) ;
2015-09-02 19:37:24 +02:00
return - 1 ;
/* LCOV_EXCL_STOP */
}
}
2014-12-29 08:50:20 +01:00
/* search for the corresponding inode */
2026-07-30 16:20:26 +02:00
uint64_t inode = handle [ j ] . st . st_ino ; /* don't know the exact type of st_ino and we cannot pass it by pointer in the search */
if ( inode ! = INODE_INVALID )
collide_file = tommy_hashdyn_search ( & disk - > inodeset , file_inode_compare_to_arg , & inode , file_inode_hash ( inode ) ) ;
else
collide_file = 0 ;
2014-12-29 08:50:20 +01:00
2026-04-27 15:43:06 +02:00
/*
* If the inode is already in the database and it refers to a different file name ,
* we can fix the file time ONLY if the time and size allow to differentiate
* between the two files
*
* For example , suppose we delete a bunch of files with all the same size and time ,
* when recreating them the inodes may be reused in a different order ,
* and at the next sync some files may have matching inode / size / time even if different name
* not allowing sync to detect that the file is changed and not renamed
*/
2014-12-29 08:50:20 +01:00
if ( ! collide_file /* if not in the database, there is no collision */
| | strcmp ( collide_file - > sub , file - > sub ) = = 0 /* if the name is the same, it's the right collision */
| | collide_file - > size ! = file - > size /* if the size is different, the collision is identified */
| | collide_file - > mtime_sec ! = file - > mtime_sec /* if the mtime is different, the collision is identified */
| | collide_file - > mtime_nsec ! = file - > mtime_nsec /* same for mtime_nsec */
) {
2026-08-29 21:25:49 +02:00
/* set the original modification time */
2014-12-29 08:50:20 +01:00
ret = handle_utime ( & handle [ j ] ) ;
if ( ret = = - 1 ) {
/* LCOV_EXCL_START */
2026-06-04 00:34:51 +02:00
log_tag ( " %s:% " PRIu64 " :%s:%s: Time error. %s. \n " , es ( errno ) , i , disk - > name , esc_tag ( file - > sub ) , strerror ( errno ) ) ;
2026-01-24 19:19:11 +01:00
log_fatal_errno ( errno , disk - > name ) ;
2014-12-29 08:50:20 +01:00
/* mark the file as damaged */
file_flag_set ( file , FILE_IS_DAMAGED ) ;
return - 1 ;
/* LCOV_EXCL_STOP */
}
} else {
2026-05-29 11:01:44 +02:00
log_tag ( " collision:%s:%s:%s: Not setting modification time to avoid inode collision \n " , disk - > name , esc_tag ( file - > sub ) , esc_tag ( collide_file - > sub ) ) ;
2014-12-29 08:50:20 +01:00
}
2026-08-27 16:59:23 +02:00
/*
2026-08-29 21:25:49 +02:00
* Close the normal file before removing the previous quarantine .
* A delayed close error means that recovery is not yet successful ,
* and the previous . unrecoverable file must remain available .
2026-08-27 16:59:23 +02:00
*/
2026-08-29 21:25:49 +02:00
ret = handle_close ( & handle [ j ] ) ;
if ( ret ! = 0 ) {
/* LCOV_EXCL_START */
log_tag ( " %s:% " PRIu64 " :%s:%s: Close error. %s. \n " , es ( errno ) , i , disk - > name , esc_tag ( file - > sub ) , strerror ( errno ) ) ;
log_fatal_errno ( errno , disk - > name ) ;
return - 1 ;
/* LCOV_EXCL_STOP */
}
2026-08-27 16:59:23 +02:00
2026-08-29 21:25:49 +02:00
/*
* The normal file has now been completely recovered , finalized , and closed .
* Remove any quarantine left by an earlier failed recovery attempt .
*/
ret = handle_unrecoverable_remove ( & handle [ j ] , file ) ;
if ( ret ! = 0 ) {
/* LCOV_EXCL_START */
log_tag ( " %s:% " PRIu64 " :%s:%s: Remove unrecoverable error. %s. \n " , es ( errno ) , i , disk - > name , esc_tag ( file - > sub ) , strerror ( errno ) ) ;
log_fatal_errno ( errno , disk - > name ) ;
return - 1 ;
/* LCOV_EXCL_STOP */
2026-08-27 16:59:23 +02:00
}
log_tag ( " status:recovered:%s:%s \n " , disk - > name , esc_tag ( file - > sub ) ) ;
msg_info ( " recovered %s \n " , fmt_term ( disk , file - > sub ) ) ;
2014-12-29 08:50:20 +01:00
} else {
2026-04-27 15:43:06 +02:00
/*
* We are not fixing , but only checking
* print just the final status
*/
2014-12-29 08:50:20 +01:00
if ( file_flag_has ( file , FILE_IS_DAMAGED ) ) {
2026-05-29 11:01:44 +02:00
log_tag ( " status:unrecoverable:%s:%s \n " , disk - > name , esc_tag ( file - > sub ) ) ;
msg_info ( " unrecoverable %s \n " , fmt_term ( disk , file - > sub ) ) ;
2014-12-29 08:50:20 +01:00
} else if ( file_flag_has ( file , FILE_IS_FIXED ) ) {
2026-05-29 11:01:44 +02:00
log_tag ( " status:recoverable:%s:%s \n " , disk - > name , esc_tag ( file - > sub ) ) ;
msg_info ( " recoverable %s \n " , fmt_term ( disk , file - > sub ) ) ;
2014-12-29 08:50:20 +01:00
} else {
2015-02-22 14:31:03 +01:00
/* we don't use msg_verbose() because it also goes into the log */
2016-01-05 10:42:46 +01:00
if ( msg_level > = MSG_VERBOSE ) {
2026-05-29 11:01:44 +02:00
log_tag ( " status:correct:%s:%s \n " , disk - > name , esc_tag ( file - > sub ) ) ;
msg_info ( " correct %s \n " , fmt_term ( disk , file - > sub ) ) ;
2016-01-05 10:42:46 +01:00
}
2014-12-29 08:50:20 +01:00
}
}
close_and_continue :
2026-04-27 15:43:06 +02:00
/*
* If the opened file is the correct one , close it
* in case of excluded and fragmented files it ' s possible
* that the opened file is not the current one
*/
2014-12-29 08:50:20 +01:00
if ( handle [ j ] . file = = file ) {
2026-04-27 15:43:06 +02:00
/*
* Ensure to close the file just after finishing with it
* to avoid keeping it open without any possible use
*/
2014-12-29 08:50:20 +01:00
ret = handle_close ( & handle [ j ] ) ;
if ( ret ! = 0 ) {
/* LCOV_EXCL_START */
2026-06-04 00:34:51 +02:00
log_tag ( " %s:% " PRIu64 " :%s:%s: Close error. %s. \n " , es ( errno ) , i , disk - > name , esc_tag ( file - > sub ) , strerror ( errno ) ) ;
2026-01-24 19:19:11 +01:00
log_fatal_errno ( errno , disk - > name ) ;
2014-12-29 08:50:20 +01:00
return - 1 ;
/* LCOV_EXCL_STOP */
}
}
}
return 0 ;
}
/**
* Check if we have to process the specified block index : : i .
*/
2014-12-29 19:11:03 +01:00
static int block_is_enabled ( struct snapraid_state * state , block_off_t i , struct snapraid_handle * handle , unsigned diskmax )
2014-12-29 08:50:20 +01:00
{
unsigned j ;
2015-10-17 17:36:40 +02:00
unsigned l ;
2014-12-29 08:50:20 +01:00
2021-07-08 07:38:24 +02:00
/* filter for bad blocks */
if ( state - > opt . badblockonly ) {
snapraid_info info ;
/* get block specific info */
info = info_get ( & state - > infoarr , i ) ;
/*
* Filter specifically only for bad blocks
*/
return info_get_bad ( info ) ;
}
2021-05-22 22:17:42 +02:00
/* filter for the parity */
2021-07-08 07:38:24 +02:00
if ( state - > opt . badfileonly ) {
2021-05-22 09:34:40 +02:00
snapraid_info info ;
/* get block specific info */
info = info_get ( & state - > infoarr , i ) ;
2021-05-22 22:17:42 +02:00
/*
* If the block is bad , it has to be processed
*
* This is not necessary in normal cases because if a block is bad ,
* it necessary needs to have a file related to it , and files with
* bad blocks are fully included .
*
* But some files may be excluded by additional filter options ,
* so it ' s not always true , and this ensures to always check all
* the bad blocks .
*/
if ( info_get_bad ( info ) )
2015-10-17 17:36:40 +02:00
return 1 ;
2021-05-22 22:17:42 +02:00
} else {
/* if a parity is not excluded, include all blocks, even unused ones */
for ( l = 0 ; l < state - > level ; + + l ) {
if ( ! state - > parity [ l ] . is_excluded_by_filter ) {
return 1 ;
}
2015-10-17 17:36:40 +02:00
}
}
2021-05-22 22:17:42 +02:00
/* filter for the files */
2014-12-29 08:50:20 +01:00
for ( j = 0 ; j < diskmax ; + + j ) {
struct snapraid_block * block ;
/* if no disk, nothing to check */
if ( ! handle [ j ] . disk )
continue ;
2016-01-16 14:26:05 +01:00
block = fs_par2block_find ( handle [ j ] . disk , i ) ;
2014-12-29 08:50:20 +01:00
2026-04-27 15:43:06 +02:00
/*
* Try to recover all files , even the ones without hash
* because in some cases we can recover also them
*/
2015-10-03 10:43:02 +02:00
if ( block_has_file ( block ) ) {
struct snapraid_file * file = fs_par2file_get ( handle [ j ] . disk , i , 0 ) ;
if ( ! file_flag_has ( file , FILE_IS_EXCLUDED ) ) { /* only if the file is not filtered out */
2015-10-17 17:36:40 +02:00
return 1 ;
2015-10-03 10:43:02 +02:00
}
2014-12-29 08:50:20 +01:00
}
}
2015-10-17 17:36:40 +02:00
return 0 ;
2014-12-29 08:50:20 +01:00
}
2026-09-07 23:15:47 +02:00
/**
* Write recovered data to files and update file status flags .
*/
static int file_recover ( struct snapraid_state * state , int fix , int partial , block_off_t block_pos , struct failed_struct * failed , unsigned failed_count , void * * buffer , unsigned * recovered_error )
{
unsigned j ;
for ( j = 0 ; j < failed_count ; + + j ) {
int ret ;
/* nothing to do if it doesn't need recovering */
if ( ! failed [ j ] . is_bad )
continue ;
/* nothing to do if it could not be recovered */
2026-09-08 08:15:27 +02:00
if ( failed [ j ] . recovery = = RECOVERY_NONE )
2026-09-07 23:15:47 +02:00
continue ;
if ( ! fix ) {
/*
* In check mode , report as recoverable only blocks whose
* recovered contents can be certified as the required CURRENT data .
*
* repair ( ) may return success after validating the RAID solution while
2026-09-08 08:15:27 +02:00
* leaving an unsynchronized bad block marked RECOVERY_OUTOFDATE . Such a block
2026-09-07 23:15:47 +02:00
* is still unrecoverable as CURRENT data and must make the containing
* file DAMAGED rather than FIXED .
*/
2026-09-08 08:15:27 +02:00
if ( failed [ j ] . recovery = = RECOVERY_OUTOFDATE )
2026-09-07 23:15:47 +02:00
file_flag_set ( failed [ j ] . file , FILE_IS_DAMAGED ) ;
else
file_flag_set ( failed [ j ] . file , FILE_IS_FIXED ) ;
continue ;
}
/* do not fix if the file is excluded */
if ( file_flag_has ( failed [ j ] . file , FILE_IS_EXCLUDED )
| | ( state - > opt . syncedonly & & file_flag_has ( failed [ j ] . file , FILE_IS_UNSYNCED ) ) )
continue ;
/*
* Write the best recovered candidate even when it is out - of - date . Such data
* may still be useful to the user or to a later multistep recovery .
*
* An out - of - date candidate is useful best - effort data ,
* but it cannot be certified as the required CURRENT
* contents . Persist this fact before modifying the file
* so that a SIGKILL cannot leave unproven recovered data
* under the normal filename .
*/
2026-09-08 08:15:27 +02:00
if ( failed [ j ] . recovery = = RECOVERY_OUTOFDATE ) {
2026-09-07 23:15:47 +02:00
file_flag_set ( failed [ j ] . file , FILE_IS_DAMAGED ) ;
/*
* Quarantine is a file - level operation because it renames the whole file .
* A partial - S / - B fix may still write the selected best - effort block , but it
* must not rename or quarantine the complete file outside the requested range .
*/
if ( ! partial ) {
ret = handle_quarantine ( failed [ j ] . handle , failed [ j ] . file , state - > file_mode ) ;
if ( ret = = - 1 ) {
/* LCOV_EXCL_START */
log_tag ( " %s:% " PRIu64 " :%s:%s: Quarantine error. %s. \n " , es ( errno ) , block_pos , failed [ j ] . disk - > name , esc_tag ( failed [ j ] . file - > sub ) , strerror ( errno ) ) ;
log_fatal_errno ( errno , failed [ j ] . disk - > name ) ;
log_fatal ( errno , " Stopping at block % " PRIu64 " \n " , block_pos ) ;
return - 1 ;
/* LCOV_EXCL_STOP */
}
}
}
ret = handle_write ( failed [ j ] . handle , failed [ j ] . file_pos , buffer [ failed [ j ] . index ] , state - > block_size ) ;
if ( ret = = - 1 ) {
/* LCOV_EXCL_START */
int saved_errno = errno ;
/* mark the file as damaged */
file_flag_set ( failed [ j ] . file , FILE_IS_DAMAGED ) ;
/*
* Quarantine is a file - level operation because it renames the whole file .
* A partial - S / - B fix must leave the complete file under its normal name even
* when a selected block write fails ; FILE_IS_DAMAGED still records the failure .
*/
if ( ! partial ) {
ret = handle_quarantine ( failed [ j ] . handle , failed [ j ] . file , state - > file_mode ) ;
if ( ret = = - 1 ) {
log_tag ( " %s:% " PRIu64 " :%s:%s: Quarantine error. %s. \n " , es ( errno ) , block_pos , failed [ j ] . disk - > name , esc_tag ( failed [ j ] . file - > sub ) , strerror ( errno ) ) ;
log_fatal_errno ( errno , failed [ j ] . disk - > name ) ;
}
}
errno = saved_errno ;
log_tag ( " %s:% " PRIu64 " :%s:%s: Write error. %s. \n " , es ( errno ) , block_pos , failed [ j ] . disk - > name , esc_tag ( failed [ j ] . file - > sub ) , strerror ( errno ) ) ;
log_fatal_errno ( errno , failed [ j ] . disk - > name ) ;
log_fatal ( errno , " Stopping at block % " PRIu64 " \n " , block_pos ) ;
return - 1 ;
/* LCOV_EXCL_STOP */
}
/* if we are not sure that the recovered content is uptodate */
2026-09-08 08:15:27 +02:00
if ( failed [ j ] . recovery = = RECOVERY_OUTOFDATE )
2026-09-07 23:15:47 +02:00
continue ;
/*
* Mark the file as containing some fixes
* note that it could be also marked as damaged in other iterations
*/
file_flag_set ( failed [ j ] . file , FILE_IS_FIXED ) ;
log_tag ( " fixed:% " PRIu64 " :%s:%s: Fixed data error at position % " PRIu64 " \n " , block_pos , failed [ j ] . disk - > name , esc_tag ( failed [ j ] . file - > sub ) , failed [ j ] . file_pos ) ;
+ + * recovered_error ;
}
return 0 ;
}
2026-08-17 23:03:28 +02:00
static int state_check_process ( struct snapraid_state * state , int fix , struct snapraid_parity_handle * * parity , block_off_t blockstart , block_off_t blockmax , int partial )
2011-03-04 20:59:21 +01:00
{
struct snapraid_handle * handle ;
2011-09-23 21:48:50 +02:00
unsigned diskmax ;
2011-03-12 10:09:59 +01:00
block_off_t i ;
2011-03-04 20:59:21 +01:00
unsigned j ;
2011-04-23 21:38:26 +02:00
void * buffer_alloc ;
2013-12-27 13:56:56 +01:00
void * * buffer ;
2026-09-07 23:15:47 +02:00
void * * buffer_scratch ;
2011-04-23 21:38:26 +02:00
unsigned buffermax ;
2026-06-04 00:34:51 +02:00
ssize_t ret ;
2011-03-28 20:55:57 +02:00
data_off_t countsize ;
block_off_t countpos ;
block_off_t countmax ;
2026-01-25 21:25:03 +01:00
unsigned soft_error ;
unsigned io_error ;
unsigned silent_error ;
2011-03-04 20:59:21 +01:00
unsigned unrecoverable_error ;
unsigned recovered_error ;
2011-06-11 12:05:02 +02:00
struct failed_struct * failed ;
2012-11-02 14:07:18 +01:00
unsigned * failed_map ;
2013-09-15 21:12:41 +02:00
unsigned l ;
2021-05-22 11:37:36 +02:00
bit_vect_t * block_enabled ;
2025-03-27 12:57:41 -07:00
struct snapraid_bw bw ;
2011-03-04 20:59:21 +01:00
2016-01-18 22:38:01 +01:00
handle = handle_mapping ( state , & diskmax ) ;
2011-09-23 21:48:50 +02:00
2026-01-17 21:16:21 +01:00
/* initialize the bandwidth context */
2025-03-27 12:57:41 -07:00
bw_init ( & bw , state - > opt . bwlimit ) ;
/* share the bandwidth context with all handles */
for ( j = 0 ; j < diskmax ; + + j )
handle [ j ] . bw = & bw ;
for ( j = 0 ; j < state - > level ; + + j )
if ( parity [ j ] )
parity [ j ] - > bw = & bw ;
2026-09-07 23:15:47 +02:00
/* we need 1 * data + 2 * parity + 1 * scratch */
buffermax = diskmax + 3 * state - > level ;
2011-04-23 21:38:26 +02:00
2026-06-02 21:32:21 +02:00
buffer = malloc_nofail_vector_align ( buffermax , state - > block_size , & buffer_alloc ) ;
2013-10-27 17:31:10 +01:00
if ( ! state - > opt . skip_self )
2014-01-02 17:51:50 +01:00
mtest_vector ( buffermax , state - > block_size , buffer ) ;
2013-09-05 22:29:58 +02:00
2026-09-07 23:15:47 +02:00
buffer_scratch = buffer + diskmax + 2 * state - > level ;
2026-07-06 18:38:50 +02:00
failed = nalloc_nofail ( diskmax , sizeof ( struct failed_struct ) ) ;
failed_map = nalloc_nofail ( diskmax , sizeof ( unsigned ) ) ;
2011-06-11 12:05:02 +02:00
2026-01-25 21:25:03 +01:00
soft_error = 0 ;
io_error = 0 ;
silent_error = 0 ;
2011-03-04 20:59:21 +01:00
unrecoverable_error = 0 ;
recovered_error = 0 ;
2021-05-22 08:46:53 +02:00
msg_progress ( " Selecting... \n " ) ;
2011-03-30 00:14:43 +02:00
/* first count the number of blocks to process */
countmax = 0 ;
2021-05-22 11:37:36 +02:00
block_enabled = calloc_nofail ( 1 , bit_vect_size ( blockmax ) ) ; /* preinitialize to 0 */
2014-09-27 00:32:39 +02:00
for ( i = blockstart ; i < blockmax ; + + i ) {
2014-12-29 08:50:20 +01:00
if ( ! block_is_enabled ( state , i , handle , diskmax ) )
2011-03-30 00:14:43 +02:00
continue ;
2021-05-22 11:37:36 +02:00
bit_vect_set ( block_enabled , i ) ;
2011-03-30 00:14:43 +02:00
+ + countmax ;
}
2021-05-22 08:46:53 +02:00
if ( fix )
msg_progress ( " Fixing... \n " ) ;
else if ( ! state - > opt . auditonly )
msg_progress ( " Checking... \n " ) ;
else
msg_progress ( " Hashing... \n " ) ;
2011-08-26 14:18:38 +02:00
/* check all the blocks in files */
2011-03-28 20:55:57 +02:00
countsize = 0 ;
countpos = 0 ;
2026-02-09 23:49:02 +01:00
int alert = state_progress_begin ( state , blockstart , blockmax , countmax ) ;
if ( alert > 0 )
2025-09-24 11:32:10 +02:00
goto end ;
2026-02-09 23:49:02 +01:00
if ( alert < 0 )
goto bail ;
2025-09-24 11:32:10 +02:00
2014-09-27 00:32:39 +02:00
for ( i = blockstart ; i < blockmax ; + + i ) {
2011-04-23 21:38:26 +02:00
unsigned failed_count ;
2013-02-24 19:43:53 +01:00
int valid_parity ;
2017-03-26 18:10:46 +02:00
int used_parity ;
2013-07-01 23:08:25 +02:00
snapraid_info info ;
int rehash ;
2011-03-04 20:59:21 +01:00
2021-05-22 11:37:36 +02:00
if ( ! bit_vect_test ( block_enabled , i ) ) {
2021-05-22 21:02:51 +02:00
/* continue with the next block */
2011-03-04 20:59:21 +01:00
continue ;
2014-12-29 08:50:20 +01:00
}
2011-03-04 20:59:21 +01:00
2026-04-27 15:43:06 +02:00
/*
* If we have valid parity , and it makes sense to check its content .
* If we already know that the parity is invalid , we just read the file
* but we don ' t report parity errors
* Note that with auditonly , we anyway skip the full parity check ,
* because we also don ' t read it at all
*/
2013-02-24 19:43:53 +01:00
valid_parity = 1 ;
2012-10-07 23:18:10 +02:00
2017-03-26 18:10:46 +02:00
/* If the parity is used by at least one file */
used_parity = 0 ;
2012-10-07 23:18:10 +02:00
/* keep track of the number of failed blocks */
failed_count = 0 ;
2011-03-23 18:54:08 +01:00
2013-10-25 18:03:05 +02:00
/* get block specific info */
2013-07-01 23:08:25 +02:00
info = info_get ( & state - > infoarr , i ) ;
2013-10-25 18:03:05 +02:00
/* if we have to use the old hash */
2013-07-01 23:08:25 +02:00
rehash = info_get_rehash ( info ) ;
2011-03-23 18:54:08 +01:00
/* for each disk, process the block */
2014-09-27 00:32:39 +02:00
for ( j = 0 ; j < diskmax ; + + j ) {
2026-06-04 00:34:51 +02:00
ssize_t read_size ;
2016-04-07 21:39:02 +02:00
unsigned char hash [ HASH_MAX ] ;
2014-10-14 21:37:28 +02:00
struct snapraid_disk * disk ;
2011-03-04 20:59:21 +01:00
struct snapraid_block * block ;
2014-10-14 21:37:28 +02:00
struct snapraid_file * file ;
2015-10-03 10:43:02 +02:00
block_off_t file_pos ;
2012-02-09 23:17:36 +01:00
unsigned block_state ;
2011-03-04 20:59:21 +01:00
2011-10-01 19:18:32 +02:00
/* if the disk position is not used */
2014-10-14 21:37:28 +02:00
disk = handle [ j ] . disk ;
if ( ! disk ) {
2011-10-01 19:18:32 +02:00
/* use an empty block */
memset ( buffer [ j ] , 0 , state - > block_size ) ;
continue ;
}
/* if the disk block is not used */
2016-01-16 14:26:05 +01:00
block = fs_par2block_find ( disk , i ) ;
if ( block = = BLOCK_NULL ) {
2011-04-23 21:38:26 +02:00
/* use an empty block */
memset ( buffer [ j ] , 0 , state - > block_size ) ;
2011-03-04 20:59:21 +01:00
continue ;
2011-04-23 21:38:26 +02:00
}
2011-03-04 20:59:21 +01:00
2013-02-24 19:02:53 +01:00
/* get the state of the block */
block_state = block_state_get ( block ) ;
2013-02-24 19:43:53 +01:00
/* if the parity is not valid */
if ( block_has_invalid_parity ( block ) ) {
2026-04-27 15:43:06 +02:00
/*
* Mark the parity as invalid , and don ' t try to check / fix it
* because it will be recomputed at the next sync
*/
2013-02-24 19:43:53 +01:00
valid_parity = 0 ;
2014-02-01 17:57:26 +01:00
/* follow */
2013-02-24 19:43:53 +01:00
}
2014-01-14 22:10:47 +01:00
/* if the block is DELETED */
2013-02-24 19:02:53 +01:00
if ( block_state = = BLOCK_STATE_DELETED ) {
2011-04-23 21:38:26 +02:00
/* use an empty block */
memset ( buffer [ j ] , 0 , state - > block_size ) ;
2013-02-24 19:02:53 +01:00
2026-04-27 15:43:06 +02:00
/*
2026-08-28 09:36:12 +02:00
* DELETED is not a file repair target . It is kept only because its OLD
* contribution may still be present in parity and may be required by the
* pre - sync recovery hypothesis .
2026-04-27 15:43:06 +02:00
*/
2026-08-28 09:36:12 +02:00
failed [ failed_count ] . is_bad = 0 ;
2026-09-08 08:15:27 +02:00
failed [ failed_count ] . recovery = RECOVERY_NONE ;
2012-02-09 23:17:36 +01:00
failed [ failed_count ] . index = j ;
2013-02-24 19:02:53 +01:00
failed [ failed_count ] . block = block ;
2015-10-03 10:43:02 +02:00
failed [ failed_count ] . disk = disk ;
failed [ failed_count ] . file = 0 ;
failed [ failed_count ] . file_pos = 0 ;
2012-02-09 23:17:36 +01:00
failed [ failed_count ] . handle = 0 ;
+ + failed_count ;
2011-03-04 20:59:21 +01:00
continue ;
2011-04-23 21:38:26 +02:00
}
2011-03-04 20:59:21 +01:00
2017-03-26 18:10:46 +02:00
/* here we are sure that the parity is used by a file */
used_parity = 1 ;
2014-10-14 21:37:28 +02:00
/* get the file of this block */
2015-10-03 10:43:02 +02:00
file = fs_par2file_get ( disk , i , & file_pos ) ;
2014-10-14 21:37:28 +02:00
2014-12-28 17:52:49 +01:00
/* if we are only hashing, we can skip excluded files and don't even read them */
2014-10-14 21:37:28 +02:00
if ( state - > opt . auditonly & & file_flag_has ( file , FILE_IS_EXCLUDED ) ) {
2026-04-27 15:43:06 +02:00
/*
* Use an empty block
* in true , this is unnecessary , because we are not checking any parity
* but we keep it for completeness
*/
2012-10-07 23:18:10 +02:00
memset ( buffer [ j ] , 0 , state - > block_size ) ;
continue ;
}
2013-04-28 19:01:36 +02:00
/* if the file is closed or different than the current one */
2014-10-14 21:37:28 +02:00
if ( handle [ j ] . file = = 0 | | handle [ j ] . file ! = file ) {
2026-01-24 19:19:11 +01:00
/* keep a pointer at the file we are going to close for error reporting */
struct snapraid_file * report = handle [ j ] . file ;
2011-08-31 23:33:43 +02:00
ret = handle_close ( & handle [ j ] ) ;
if ( ret = = - 1 ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-06-03 23:06:27 +02:00
log_tag ( " %s:% " PRIu64 " :%s:%s: Close error. %s. \n " , es ( errno ) , i , disk - > name , esc_tag ( report - > sub ) , strerror ( errno ) ) ;
2026-01-24 19:19:11 +01:00
log_fatal_errno ( errno , disk - > name ) ;
2026-06-03 23:06:27 +02:00
log_fatal ( errno , " Stopping at block % " PRIu64 " \n " , i ) ;
2026-01-24 19:19:11 +01:00
2011-08-31 23:33:43 +02:00
+ + unrecoverable_error ;
goto bail ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2011-08-31 23:33:43 +02:00
}
2011-03-18 22:38:11 +01:00
2013-04-28 19:01:36 +02:00
/* if fixing, and the file is not excluded, we must open for writing */
2014-10-14 21:37:28 +02:00
if ( fix & & ! file_flag_has ( file , FILE_IS_EXCLUDED ) ) {
2013-04-28 19:01:36 +02:00
/* if fixing, create the file, open for writing and resize if required */
2014-10-14 21:37:28 +02:00
ret = handle_create ( & handle [ j ] , file , state - > file_mode ) ;
2013-04-28 19:01:36 +02:00
if ( ret = = - 1 ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-06-03 23:06:27 +02:00
log_tag ( " %s:% " PRIu64 " :%s:%s: Create error. %s. \n " , es ( errno ) , i , disk - > name , esc_tag ( file - > sub ) , strerror ( errno ) ) ;
2026-01-24 19:19:11 +01:00
log_fatal_errno ( errno , disk - > name ) ;
2026-06-03 23:06:27 +02:00
log_fatal ( errno , " Stopping at block % " PRIu64 " \n " , i ) ;
2026-01-24 19:19:11 +01:00
2013-04-28 19:01:36 +02:00
+ + unrecoverable_error ;
goto bail ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2011-06-11 09:38:19 +02:00
}
2011-04-28 21:38:25 +02:00
2013-04-28 19:01:36 +02:00
/* check if the file was just created */
if ( handle [ j ] . created ! = 0 ) {
2026-04-27 15:43:06 +02:00
/*
* If fragmented , it may be reopened , so remember that the file
* was originally missing
*/
2014-10-14 21:37:28 +02:00
file_flag_set ( file , FILE_IS_CREATED ) ;
2013-04-28 19:01:36 +02:00
}
} else {
2015-10-15 19:34:58 +02:00
/* open the file only for reading */
2026-01-24 19:19:11 +01:00
if ( ! file_flag_has ( file , FILE_IS_MISSING ) ) {
2026-06-03 20:49:36 +02:00
ret = handle_open ( & handle [ j ] , file , state - > file_mode , state - > opt . expected_missing ? log_expected : 0 ) ;
2026-01-24 19:19:11 +01:00
} else {
errno = ENOENT ;
2015-10-15 19:34:58 +02:00
ret = - 1 ; /* if the file is missing, we cannot open it */
2026-01-24 19:19:11 +01:00
}
2013-04-28 19:01:36 +02:00
if ( ret = = - 1 ) {
/* save the failed block for the check/fix */
failed [ failed_count ] . is_bad = 1 ;
2026-09-08 08:15:27 +02:00
failed [ failed_count ] . recovery = RECOVERY_NONE ;
2013-04-28 19:01:36 +02:00
failed [ failed_count ] . index = j ;
failed [ failed_count ] . block = block ;
2015-10-03 10:43:02 +02:00
failed [ failed_count ] . disk = disk ;
failed [ failed_count ] . file = file ;
failed [ failed_count ] . file_pos = file_pos ;
2013-04-28 19:01:36 +02:00
failed [ failed_count ] . handle = & handle [ j ] ;
+ + failed_count ;
2026-06-03 23:06:27 +02:00
log_tag ( " %s:% " PRIu64 " :%s:%s: Open error at position % " PRIu64 " . %s. \n " , es ( errno ) , i , disk - > name , esc_tag ( file - > sub ) , file_pos , strerror ( errno ) ) ;
2026-01-26 09:42:03 +01:00
2026-02-22 14:58:06 +01:00
if ( is_hw ( errno ) ) {
2026-01-25 21:25:03 +01:00
+ + io_error ;
} else {
+ + soft_error ;
}
2015-10-15 19:34:58 +02:00
2026-04-27 15:43:06 +02:00
/*
* Mark the file as missing , to avoid to retry to open it again
* note that this can be done only if we are not fixing it
* otherwise , it could be recreated
*/
2015-10-15 19:34:58 +02:00
file_flag_set ( file , FILE_IS_MISSING ) ;
2013-04-28 19:01:36 +02:00
continue ;
}
2014-12-28 19:11:58 +01:00
}
2011-04-28 21:38:25 +02:00
2014-12-29 08:50:20 +01:00
/* if it's the first open, and not excluded */
if ( ! file_flag_has ( file , FILE_IS_OPENED )
& & ! file_flag_has ( file , FILE_IS_EXCLUDED ) ) {
/* check if the file is changed */
2015-10-03 10:43:02 +02:00
if ( handle [ j ] . st . st_size ! = file - > size
| | handle [ j ] . st . st_mtime ! = file - > mtime_sec
| | STAT_NSEC ( & handle [ j ] . st ) ! = file - > mtime_nsec
2016-04-10 22:44:40 +02:00
/* don't check the inode to support file-system without persistent inodes */
2014-12-29 08:50:20 +01:00
) {
/* report that the file is not synced */
file_flag_set ( file , FILE_IS_UNSYNCED ) ;
}
}
2026-08-28 09:36:12 +02:00
/*
* Missing data inside a short file is handled block by block by normal
* recovery writes , which extend the file as needed .
*
* Extra data past the expected end is different : it has no content block and
* therefore no later block recovery can remove it . Fix must truncate that
* untracked tail explicitly .
*/
2014-12-29 08:50:20 +01:00
if ( ! file_flag_has ( file , FILE_IS_OPENED )
2014-12-28 19:11:58 +01:00
& & ! file_flag_has ( file , FILE_IS_EXCLUDED )
2014-12-29 08:50:20 +01:00
& & ! ( state - > opt . syncedonly & & file_flag_has ( file , FILE_IS_UNSYNCED ) )
2014-12-28 19:11:58 +01:00
& & handle [ j ] . st . st_size > file - > size
) {
2026-02-01 20:21:28 +01:00
log_error ( ESOFT , " File '%s' is larger than expected. \n " , handle [ j ] . path ) ;
2026-06-03 23:06:27 +02:00
log_tag ( " error:% " PRIu64 " :%s:%s: Size error \n " , i , disk - > name , esc_tag ( file - > sub ) ) ;
2026-01-25 21:25:03 +01:00
+ + soft_error ;
2014-12-28 19:11:58 +01:00
if ( fix ) {
ret = handle_truncate ( & handle [ j ] , file ) ;
if ( ret = = - 1 ) {
/* LCOV_EXCL_START */
2026-06-03 23:06:27 +02:00
log_tag ( " %s:% " PRIu64 " :%s:%s: Truncate error. %s. \n " , es ( errno ) , i , disk - > name , esc_tag ( file - > sub ) , strerror ( errno ) ) ;
2026-01-24 19:19:11 +01:00
log_fatal_errno ( errno , disk - > name ) ;
2026-06-03 23:06:27 +02:00
log_fatal ( errno , " Stopping at block % " PRIu64 " \n " , i ) ;
2026-01-24 19:19:11 +01:00
2014-12-28 19:11:58 +01:00
+ + unrecoverable_error ;
goto bail ;
/* LCOV_EXCL_STOP */
}
2011-04-28 21:38:25 +02:00
2026-08-29 21:25:49 +02:00
file_flag_set ( file , FILE_IS_FIXED ) ;
2026-06-03 23:06:27 +02:00
log_tag ( " fixed:% " PRIu64 " :%s:%s: Fixed size \n " , i , disk - > name , esc_tag ( file - > sub ) ) ;
2014-12-28 19:11:58 +01:00
+ + recovered_error ;
2013-04-28 19:01:36 +02:00
}
2011-04-28 21:38:25 +02:00
}
2014-12-29 08:50:20 +01:00
2026-04-27 15:43:06 +02:00
/*
* Mark the file as opened at least one time
* this is used to avoid to check the unsynced and size
* more than one time , in case the file is reopened later
*/
2014-12-29 08:50:20 +01:00
file_flag_set ( file , FILE_IS_OPENED ) ;
2011-03-04 20:59:21 +01:00
}
2012-10-07 23:18:10 +02:00
/* read from the file */
2026-01-27 00:07:41 +01:00
if ( file_flag_has ( file , FILE_IS_MISSING ) ) {
/* if the file is reported missing, don't even try to read it */
errno = ENOENT ;
read_size = - 1 ;
} else {
2026-06-03 20:49:36 +02:00
read_size = handle_read ( & handle [ j ] , file_pos , buffer [ j ] , state - > block_size , state - > opt . expected_missing ? log_expected : 0 ) ;
2026-01-27 00:07:41 +01:00
}
2011-03-04 20:59:21 +01:00
if ( read_size = = - 1 ) {
2011-06-11 12:05:02 +02:00
/* save the failed block for the check/fix */
2014-02-01 17:57:26 +01:00
failed [ failed_count ] . is_bad = 1 ; /* it's bad because we cannot read it */
2026-09-08 08:15:27 +02:00
failed [ failed_count ] . recovery = RECOVERY_NONE ;
2011-06-11 12:05:02 +02:00
failed [ failed_count ] . index = j ;
failed [ failed_count ] . block = block ;
2015-10-03 10:43:02 +02:00
failed [ failed_count ] . disk = disk ;
failed [ failed_count ] . file = file ;
failed [ failed_count ] . file_pos = file_pos ;
2011-06-11 12:05:02 +02:00
failed [ failed_count ] . handle = & handle [ j ] ;
2011-04-23 21:38:26 +02:00
+ + failed_count ;
2011-03-04 20:59:21 +01:00
2026-06-03 23:06:27 +02:00
log_tag ( " %s:% " PRIu64 " :%s:%s: Read error at position % " PRIu64 " . %s. \n " , es ( errno ) , i , disk - > name , esc_tag ( file - > sub ) , file_pos , strerror ( errno ) ) ;
2026-01-25 21:25:03 +01:00
2026-02-22 14:58:06 +01:00
if ( is_hw ( errno ) ) {
2026-01-25 21:25:03 +01:00
+ + io_error ;
} else {
+ + soft_error ;
}
2026-01-27 00:07:41 +01:00
/* if we are reading at the end, mark the file as missing to avoid to try to read it again at the next block */
if ( errno = = ENOENT ) {
file_flag_set ( file , FILE_IS_MISSING ) ;
}
2011-03-04 20:59:21 +01:00
continue ;
}
2012-02-09 23:17:36 +01:00
countsize + = read_size ;
2011-03-04 20:59:21 +01:00
2026-04-27 15:43:06 +02:00
/*
* Always insert CHG blocks , the repair functions needs all of them
* because the parity may be still referring at the old state
* and the repair must be aware of it
*/
2014-02-01 17:57:26 +01:00
if ( block_state = = BLOCK_STATE_CHG ) {
2026-04-27 15:43:06 +02:00
/*
2026-08-17 21:57:02 +02:00
* A CHG block has no hash for the current data . The stored hash
* refers to the previous content , and after an interrupted sync
* parity may refer either to the old or to the new content .
*
* If the block is readable , preserve it and assume it is the best
* available copy of the current data . We must not try to validate it
* against block - > hash , as a match would only identify the old data
* and a mismatch would not prove that the current data is correct .
*
2026-08-29 21:25:49 +02:00
* This assumption applies only to data belonging to the current recovery
* attempt . A CHG from a previous . unrecoverable file cannot safely be reused :
* it may itself be an ambiguous recovery result whose CURRENT generation was
* not proven . Once persisted , that provenance cannot be inferred from the
* bytes alone .
2026-04-27 15:43:06 +02:00
*/
2014-02-01 17:57:26 +01:00
failed [ failed_count ] . is_bad = 0 ; /* we assume the CHG block correct */
2026-09-08 08:15:27 +02:00
failed [ failed_count ] . recovery = RECOVERY_NONE ;
2011-06-11 12:05:02 +02:00
failed [ failed_count ] . index = j ;
failed [ failed_count ] . block = block ;
2015-10-03 10:43:02 +02:00
failed [ failed_count ] . disk = disk ;
failed [ failed_count ] . file = file ;
failed [ failed_count ] . file_pos = file_pos ;
2011-06-11 12:05:02 +02:00
failed [ failed_count ] . handle = & handle [ j ] ;
2011-04-23 21:38:26 +02:00
+ + failed_count ;
2011-03-04 20:59:21 +01:00
continue ;
}
2026-08-28 12:11:05 +02:00
assert ( block_state = = BLOCK_STATE_BLK | | block_state = = BLOCK_STATE_REP | | block_state = = BLOCK_STATE_REBUILD ) ;
2012-02-09 23:17:36 +01:00
2014-02-01 17:57:26 +01:00
/* compute the hash of the block just read */
if ( rehash ) {
2026-08-30 23:09:29 +02:00
memhash_block ( state - > prevhash , state - > prevhashseed , hash , buffer [ j ] , read_size , state - > block_size ) ;
2014-02-01 17:57:26 +01:00
} else {
2026-08-30 23:09:29 +02:00
memhash_block ( state - > hash , state - > hashseed , hash , buffer [ j ] , read_size , state - > block_size ) ;
2014-02-01 17:57:26 +01:00
}
/* compare the hash */
2016-04-07 21:39:02 +02:00
if ( memcmp ( hash , block - > hash , BLOCK_HASH_SIZE ) ! = 0 ) {
unsigned diff = memdiff ( hash , block - > hash , BLOCK_HASH_SIZE ) ;
2015-09-12 15:19:52 +02:00
2014-02-01 17:57:26 +01:00
/* save the failed block for the check/fix */
failed [ failed_count ] . is_bad = 1 ; /* it's bad because the hash doesn't match */
2026-09-08 08:15:27 +02:00
failed [ failed_count ] . recovery = RECOVERY_NONE ;
2014-02-01 17:57:26 +01:00
failed [ failed_count ] . index = j ;
failed [ failed_count ] . block = block ;
2015-10-03 10:43:02 +02:00
failed [ failed_count ] . disk = disk ;
failed [ failed_count ] . file = file ;
failed [ failed_count ] . file_pos = file_pos ;
2014-02-01 17:57:26 +01:00
failed [ failed_count ] . handle = & handle [ j ] ;
+ + failed_count ;
2026-09-15 22:48:28 +02:00
log_tag ( " error_data:% " PRIu64 " :%s:%s: Data error at position % " PRIu64 " , diff hash bits %u/%zu \n " , i , disk - > name , esc_tag ( file - > sub ) , file_pos , diff , BLOCK_HASH_SIZE * 8 ) ;
2026-01-25 21:25:03 +01:00
+ + silent_error ;
2014-02-01 17:57:26 +01:00
continue ;
}
2026-04-27 15:43:06 +02:00
/*
2026-08-28 09:36:12 +02:00
* A readable REP has already passed its NEW / current hash check , so its current
* data is known good . It must nevertheless participate in repair because an
* interrupted sync may have left physical parity containing its OLD
* contribution .
*
* Unlike CHG , the REP stored hash cannot identify that OLD contribution
* because it describes the NEW data .
2026-04-27 15:43:06 +02:00
*/
2014-02-01 17:57:26 +01:00
if ( block_state = = BLOCK_STATE_REP ) {
failed [ failed_count ] . is_bad = 0 ; /* it's not bad */
2026-09-08 08:15:27 +02:00
failed [ failed_count ] . recovery = RECOVERY_NONE ;
2014-02-01 17:57:26 +01:00
failed [ failed_count ] . index = j ;
failed [ failed_count ] . block = block ;
2015-10-03 10:43:02 +02:00
failed [ failed_count ] . disk = disk ;
failed [ failed_count ] . file = file ;
failed [ failed_count ] . file_pos = file_pos ;
2014-02-01 17:57:26 +01:00
failed [ failed_count ] . handle = & handle [ j ] ;
+ + failed_count ;
continue ;
2012-02-09 23:17:36 +01:00
}
2011-03-04 20:59:21 +01:00
}
2012-10-07 23:18:10 +02:00
/* now read and check the parity if requested */
2014-02-12 22:04:14 +01:00
if ( ! state - > opt . auditonly ) {
2013-12-27 13:56:56 +01:00
void * buffer_recov [ LEV_MAX ] ;
2011-05-05 21:12:42 +02:00
2012-10-07 23:18:10 +02:00
/* buffers for parity read and not computed */
2014-09-27 00:32:39 +02:00
for ( l = 0 ; l < state - > level ; + + l )
2013-09-18 21:56:06 +02:00
buffer_recov [ l ] = buffer [ diskmax + state - > level + l ] ;
2014-09-27 00:32:39 +02:00
for ( ; l < LEV_MAX ; + + l )
2013-09-18 21:56:06 +02:00
buffer_recov [ l ] = 0 ;
2012-10-07 23:18:10 +02:00
/* read the parity */
2014-09-27 00:32:39 +02:00
for ( l = 0 ; l < state - > level ; + + l ) {
2013-09-15 21:12:41 +02:00
if ( parity [ l ] ) {
2026-06-03 20:49:36 +02:00
ret = parity_read ( parity [ l ] , i , buffer_recov [ l ] , state - > block_size ) ;
2013-09-11 23:30:32 +02:00
if ( ret = = - 1 ) {
2026-06-03 23:06:27 +02:00
log_tag ( " parity_%s:% " PRIu64 " :%s: Read error. %s. \n " , es ( errno ) , i , lev_config_name ( l ) , strerror ( errno ) ) ;
2026-01-24 19:19:11 +01:00
2026-08-28 09:36:12 +02:00
/*
* NULL means that this parity level cannot be trusted as an input to recovery .
* The same marker is later used for a parity block that was read successfully
* but failed comparison with recomputed parity .
*
* During fix , an accessible non - excluded parity marked this way is also the
* one that needs to be rewritten from the recomputed value .
*/
buffer_recov [ l ] = 0 ;
2013-09-11 23:30:32 +02:00
2026-02-22 14:58:06 +01:00
if ( is_hw ( errno ) ) {
2026-01-25 21:25:03 +01:00
+ + io_error ;
} else {
+ + soft_error ;
}
2013-09-11 23:30:32 +02:00
}
} else {
2013-09-15 21:12:41 +02:00
buffer_recov [ l ] = 0 ;
2013-09-11 23:30:32 +02:00
}
}
2013-02-24 11:10:10 +01:00
/* try all the recovering strategies */
2026-09-07 23:15:47 +02:00
ret = repair ( state , rehash , i , diskmax , failed , failed_map , failed_count , buffer , buffer_recov , buffer_scratch ) ;
2012-10-07 23:18:10 +02:00
if ( ret ! = 0 ) {
/* increment the number of errors */
if ( ret > 0 )
2026-01-25 21:25:03 +01:00
silent_error + = ret ;
2012-10-07 23:18:10 +02:00
+ + unrecoverable_error ;
2026-09-07 23:15:47 +02:00
/* print a list of all unrecoverable errors in files */
2014-09-27 00:32:39 +02:00
for ( j = 0 ; j < failed_count ; + + j ) {
2026-09-08 08:15:27 +02:00
if ( failed [ j ] . is_bad & & failed [ j ] . recovery ! = RECOVERY_CURRENT )
2026-06-03 23:06:27 +02:00
log_tag ( " unrecoverable:% " PRIu64 " :%s:%s: Unrecoverable error at position % " PRIu64 " \n " , i , failed [ j ] . disk - > name , esc_tag ( failed [ j ] . file - > sub ) , failed [ j ] . file_pos ) ;
2012-02-09 23:17:36 +01:00
}
2026-09-07 23:15:47 +02:00
/* update files that have blocks fixed by import/search */
if ( file_recover ( state , fix , partial , i , failed , failed_count , buffer , & recovered_error ) ! = 0 ) {
+ + unrecoverable_error ;
goto bail ;
}
/* keep track of damaged files for blocks that could not be recovered */
2014-09-27 00:32:39 +02:00
for ( j = 0 ; j < failed_count ; + + j ) {
2026-09-08 08:15:27 +02:00
if ( ! failed [ j ] . is_bad | | failed [ j ] . recovery = = RECOVERY_CURRENT )
2026-08-29 21:25:49 +02:00
continue ;
file_flag_set ( failed [ j ] . file , FILE_IS_DAMAGED ) ;
/*
* Persist the unrecoverable state immediately for a full fix .
* Quarantine is a file - level operation because it renames the whole file .
* A partial - S / - B fix operates only at block level , so keep FILE_IS_DAMAGED
* but leave the file under its normal name when partial is set .
*/
2026-09-02 16:31:21 +02:00
if ( fix
& & ! file_flag_has ( failed [ j ] . file , FILE_IS_EXCLUDED )
2026-08-29 21:25:49 +02:00
& & ! ( state - > opt . syncedonly & & file_flag_has ( failed [ j ] . file , FILE_IS_UNSYNCED ) )
& & ! partial
) {
ret = handle_quarantine ( failed [ j ] . handle , failed [ j ] . file , state - > file_mode ) ;
if ( ret = = - 1 ) {
/* LCOV_EXCL_START */
log_tag ( " %s:% " PRIu64 " :%s:%s: Quarantine error. %s. \n " , es ( errno ) , i , failed [ j ] . disk - > name , esc_tag ( failed [ j ] . file - > sub ) , strerror ( errno ) ) ;
log_fatal_errno ( errno , failed [ j ] . disk - > name ) ;
log_fatal ( errno , " Stopping at block % " PRIu64 " \n " , i ) ;
+ + unrecoverable_error ;
goto bail ;
/* LCOV_EXCL_STOP */
}
}
2011-03-04 20:59:21 +01:00
}
2012-10-07 23:18:10 +02:00
} else {
2026-04-27 15:43:06 +02:00
/*
2026-08-28 09:36:12 +02:00
* repair ( ) returning success means that a RAID solution was validated under
* one of the parity - history hypotheses . It does not necessarily mean that
* every bad unsynced block was recovered to its CURRENT version .
*
2026-09-08 08:15:27 +02:00
* A bad block marked RECOVERY_OUTOFDATE is therefore still an unrecoverable current
2026-08-28 09:36:12 +02:00
* data error even though the stripe - level RAID recovery succeeded .
2026-04-27 15:43:06 +02:00
*/
2012-10-07 23:18:10 +02:00
int partial_recover_error = 0 ;
2011-03-04 20:59:21 +01:00
2012-10-07 23:18:10 +02:00
/* print a list of all the errors in files */
2014-09-27 00:32:39 +02:00
for ( j = 0 ; j < failed_count ; + + j ) {
2026-09-08 08:15:27 +02:00
if ( failed [ j ] . is_bad & & failed [ j ] . recovery = = RECOVERY_OUTOFDATE ) {
2012-10-07 23:18:10 +02:00
+ + partial_recover_error ;
2026-06-03 23:06:27 +02:00
log_tag ( " unrecoverable:% " PRIu64 " :%s:%s: Unrecoverable unsynced error at position % " PRIu64 " \n " , i , failed [ j ] . disk - > name , esc_tag ( failed [ j ] . file - > sub ) , failed [ j ] . file_pos ) ;
2011-04-23 21:38:26 +02:00
}
2011-03-18 22:38:11 +01:00
}
2012-10-07 23:18:10 +02:00
if ( partial_recover_error ! = 0 ) {
2026-01-25 21:25:03 +01:00
silent_error + = partial_recover_error ;
2012-10-07 23:18:10 +02:00
+ + unrecoverable_error ;
}
2011-03-04 20:59:21 +01:00
2017-03-26 18:10:46 +02:00
/*
* Check parities , but only if all the blocks have it computed and it ' s used .
*
* If you check / fix after a partial sync , it ' s OK to have parity errors
* on the blocks with invalid parity and doesn ' t make sense to try to fix it .
*
* It ' s also OK to have data errors on unused parity , because sync doesn ' t
* update it .
*/
if ( used_parity & & valid_parity ) {
2012-10-07 23:18:10 +02:00
/* check the parity */
2014-09-27 00:32:39 +02:00
for ( l = 0 ; l < state - > level ; + + l ) {
2013-09-15 21:12:41 +02:00
if ( buffer_recov [ l ] ! = 0 & & memcmp ( buffer_recov [ l ] , buffer [ diskmax + l ] , state - > block_size ) ! = 0 ) {
2015-09-12 15:19:52 +02:00
unsigned diff = memdiff ( buffer_recov [ l ] , buffer [ diskmax + l ] , state - > block_size ) ;
2015-10-17 17:36:40 +02:00
/* mark that the read parity is wrong, setting ptr to 0 */
2013-09-15 21:12:41 +02:00
buffer_recov [ l ] = 0 ;
2013-09-11 23:30:32 +02:00
2026-09-15 22:48:28 +02:00
log_tag ( " parity_error_data:% " PRIu64 " :%s: Data error, diff parity bits %u/%u \n " , i , lev_config_name ( l ) , diff , state - > block_size * 8 ) ;
2026-01-25 21:25:03 +01:00
+ + silent_error ;
2013-09-11 23:30:32 +02:00
}
}
2012-10-07 23:18:10 +02:00
}
2012-02-09 23:17:36 +01:00
2026-09-07 23:15:47 +02:00
/* update the fixed files */
if ( file_recover ( state , fix , partial , i , failed , failed_count , buffer , & recovered_error ) ! = 0 ) {
+ + unrecoverable_error ;
goto bail ;
}
2011-04-23 21:38:26 +02:00
2026-09-07 23:15:47 +02:00
if ( fix ) {
2017-03-26 18:10:46 +02:00
/*
* Update parity only if all the blocks have it computed and it ' s used .
*
* If you check / fix after a partial sync , you do not want to fix parity
* for blocks that are going to have it computed in the sync completion .
*
2026-09-09 09:58:56 +02:00
* For unused parity there is no need to write or allocate it because no live
* file depends on that position and its physical contents do not matter .
2017-03-26 18:10:46 +02:00
*/
if ( used_parity & & valid_parity ) {
2012-10-07 23:18:10 +02:00
/* update the parity */
2014-09-27 00:32:39 +02:00
for ( l = 0 ; l < state - > level ; + + l ) {
2015-10-17 17:36:40 +02:00
/* if the parity on disk is wrong */
if ( buffer_recov [ l ] = = 0
2016-04-10 22:44:40 +02:00
/* and we have access at the parity */
2015-10-17 17:36:40 +02:00
& & parity [ l ] ! = 0
2016-04-10 22:44:40 +02:00
/* and the parity is not excluded */
2017-03-26 18:10:46 +02:00
& & ! state - > parity [ l ] . is_excluded_by_filter
2015-10-17 17:36:40 +02:00
) {
2026-09-09 09:58:56 +02:00
ret = parity_write ( parity [ l ] , i , buffer [ diskmax + l ] , state - > block_size , state - > opt . skip_fallocate ) ;
2013-09-11 23:30:32 +02:00
if ( ret = = - 1 ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-09-15 22:48:28 +02:00
log_tag ( " parity_%s:% " PRIu64 " :%s: Write error. %s. \n " , es ( errno ) , i , lev_config_name ( l ) , strerror ( errno ) ) ;
2026-01-24 19:19:11 +01:00
log_fatal_errno ( errno , lev_config_name ( l ) ) ;
2026-06-03 23:06:27 +02:00
log_fatal ( errno , " Stopping at block % " PRIu64 " \n " , i ) ;
2026-01-24 19:19:11 +01:00
2013-09-11 23:30:32 +02:00
+ + unrecoverable_error ;
goto bail ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-09-11 23:30:32 +02:00
}
2026-06-03 23:06:27 +02:00
log_tag ( " parity_fixed:% " PRIu64 " :%s: Fixed data error \n " , i , lev_config_name ( l ) ) ;
2013-09-11 23:30:32 +02:00
+ + recovered_error ;
}
}
2011-04-23 21:38:26 +02:00
}
2013-03-02 23:49:29 +01:00
}
}
} else {
2026-04-27 15:43:06 +02:00
/*
* If we are not checking , we just set the DAMAGED flag
* to report that the file is damaged , and we don ' t know if we can fix it
*/
2014-09-27 00:32:39 +02:00
for ( j = 0 ; j < failed_count ; + + j ) {
2013-03-02 23:49:29 +01:00
if ( failed [ j ] . is_bad ) {
2015-10-03 10:43:02 +02:00
file_flag_set ( failed [ j ] . file , FILE_IS_DAMAGED ) ;
2011-04-23 21:38:26 +02:00
}
2011-03-04 20:59:21 +01:00
}
}
2014-12-29 08:50:20 +01:00
/* post process the files */
2026-08-17 23:03:28 +02:00
ret = file_post ( state , fix , partial , i , handle , diskmax ) ;
2014-12-29 08:50:20 +01:00
if ( ret = = - 1 ) {
2016-11-06 18:27:44 +01:00
/* LCOV_EXCL_START */
2026-06-03 23:06:27 +02:00
log_fatal ( errno , " Stopping at block % " PRIu64 " \n " , i ) ;
2026-01-24 19:19:11 +01:00
2014-12-29 08:50:20 +01:00
+ + unrecoverable_error ;
goto bail ;
2016-11-06 18:27:44 +01:00
/* LCOV_EXCL_STOP */
2011-08-31 23:33:43 +02:00
}
2011-03-04 20:59:21 +01:00
/* count the number of processed block */
2011-03-28 20:55:57 +02:00
+ + countpos ;
2011-03-04 20:59:21 +01:00
/* progress */
2016-01-27 20:37:31 +01:00
if ( state_progress ( state , 0 , i , countpos , countmax , countsize ) ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-09-06 23:20:21 +02:00
goto end ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2011-03-18 22:38:11 +01:00
}
2025-09-21 19:54:10 +02:00
/* thermal control */
if ( state_thermal_alarm ( state ) ) {
/* until now is misc */
state_usage_misc ( state ) ;
state_progress_stop ( state ) ;
state_thermal_cooldown ( state ) ;
state_progress_restart ( state ) ;
/* drop until now */
state_usage_waste ( state ) ;
2026-09-06 23:20:21 +02:00
if ( os_signal_interrupt ( ) )
goto end ;
2025-09-21 19:54:10 +02:00
}
2011-03-04 20:59:21 +01:00
}
2026-08-28 09:36:12 +02:00
/*
* These objects have no parity block position , so an explicit - S / - B block
* range cannot select them . Check / fix them separately after block processing ,
* including during an otherwise partial block - range operation .
*/
2013-01-27 16:48:53 +01:00
/* for each disk, recover empty files, symlinks and empty dirs */
2014-09-27 00:32:39 +02:00
for ( i = 0 ; i < diskmax ; + + i ) {
2011-10-15 08:39:52 +02:00
tommy_node * node ;
struct snapraid_disk * disk ;
if ( ! handle [ i ] . disk )
continue ;
2013-08-26 20:20:36 +02:00
/* for each empty file in the disk */
2011-10-15 08:39:52 +02:00
disk = handle [ i ] . disk ;
node = disk - > filelist ;
while ( node ) {
char path [ PATH_MAX ] ;
struct stat st ;
struct snapraid_file * file ;
2020-05-02 14:20:47 +02:00
int unsuccessful = 0 ;
2011-10-15 08:39:52 +02:00
file = node - > data ;
node = node - > next ; /* next node */
/* if not empty, it's already checked and continue to the next one */
if ( file - > size ! = 0 ) {
continue ;
}
/* if excluded continue to the next one */
if ( file_flag_has ( file , FILE_IS_EXCLUDED ) ) {
continue ;
}
/* stat the file */
pathprint ( path , sizeof ( path ) , " %s%s " , disk - > dir , file - > sub ) ;
ret = stat ( path , & st ) ;
if ( ret = = - 1 ) {
2020-05-02 14:20:47 +02:00
unsuccessful = 1 ;
2011-10-15 08:39:52 +02:00
2026-02-01 20:21:28 +01:00
log_error ( errno , " Error stating empty file '%s'. %s. \n " , path , strerror ( errno ) ) ;
2026-05-29 11:01:44 +02:00
log_tag ( " empty_%s:%s:%s: Empty file stat error \n " , es ( errno ) , disk - > name , esc_tag ( file - > sub ) ) ;
2026-01-25 21:25:03 +01:00
2026-02-22 14:58:06 +01:00
if ( is_hw ( errno ) ) {
2026-01-25 21:25:03 +01:00
+ + io_error ;
} else {
+ + soft_error ;
}
2011-10-15 08:39:52 +02:00
} else if ( ! S_ISREG ( st . st_mode ) ) {
2020-05-02 14:20:47 +02:00
unsuccessful = 1 ;
2011-10-15 08:39:52 +02:00
2026-02-01 20:21:28 +01:00
log_error ( ESOFT , " Error stating empty file '%s' for not regular file. \n " , path ) ;
2026-05-29 11:01:44 +02:00
log_tag ( " empty_error:%s:%s: Empty file error for not regular file \n " , disk - > name , esc_tag ( file - > sub ) ) ;
2026-01-25 21:25:03 +01:00
+ + soft_error ;
2011-10-15 08:39:52 +02:00
} else if ( st . st_size ! = 0 ) {
2020-05-02 14:20:47 +02:00
unsuccessful = 1 ;
2011-10-15 08:39:52 +02:00
2026-02-01 20:21:28 +01:00
log_error ( ESOFT , " Error stating empty file '%s' for not empty file. \n " , path ) ;
2026-05-29 11:01:44 +02:00
log_tag ( " empty_error:%s:%s: Empty file error for size '% " PRIu64 " ' \n " , disk - > name , esc_tag ( file - > sub ) , ( uint64_t ) st . st_size ) ;
2026-01-25 21:25:03 +01:00
+ + soft_error ;
2011-10-15 08:39:52 +02:00
}
2020-05-02 14:20:47 +02:00
if ( fix & & unsuccessful ) {
2011-10-15 08:39:52 +02:00
int f ;
/* create the ancestor directories */
2013-02-20 13:29:58 +01:00
ret = mkancestor ( path ) ;
2011-10-15 08:39:52 +02:00
if ( ret ! = 0 ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( errno , " Error creating ancestor '%s%s'. %s. \n " , disk - > dir , file - > sub , strerror ( errno ) ) ;
2026-09-15 22:48:28 +02:00
log_tag ( " empty_%s:%s:%s: Create ancestor error. %s. \n " , es ( errno ) , disk - > name , esc_tag ( file - > sub ) , strerror ( errno ) ) ;
2026-01-24 19:19:11 +01:00
log_fatal_errno ( errno , disk - > name ) ;
2026-02-01 20:21:28 +01:00
log_fatal ( errno , " Stopping \n " ) ;
2026-01-24 19:19:11 +01:00
2011-10-15 08:39:52 +02:00
+ + unrecoverable_error ;
goto bail ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2011-10-15 08:39:52 +02:00
}
2026-04-27 15:43:06 +02:00
/*
* Create it
* O_NOFOLLOW : do not follow links to ensure to open the real file
*/
2011-10-15 08:39:52 +02:00
f = open ( path , O_WRONLY | O_CREAT | O_TRUNC | O_BINARY | O_NOFOLLOW , 0600 ) ;
if ( f = = - 1 ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( errno , " Error creating '%s%s'. %s. \n " , disk - > dir , file - > sub , strerror ( errno ) ) ;
2026-09-15 22:48:28 +02:00
log_tag ( " empty_%s:%s:%s: Create error. %s. \n " , es ( errno ) , disk - > name , esc_tag ( file - > sub ) , strerror ( errno ) ) ;
2026-01-24 19:19:11 +01:00
log_fatal_errno ( errno , disk - > name ) ;
2026-02-01 20:21:28 +01:00
log_fatal ( errno , " Stopping \n " ) ;
2026-01-24 19:19:11 +01:00
2011-10-15 08:39:52 +02:00
+ + unrecoverable_error ;
goto bail ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2011-10-15 08:39:52 +02:00
}
2013-08-26 20:20:36 +02:00
/* set the original modification time */
2014-05-18 15:11:18 +02:00
ret = fmtime ( f , file - > mtime_sec , file - > mtime_nsec ) ;
2013-08-26 20:20:36 +02:00
if ( ret ! = 0 ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( errno , " Error timing '%s%s'. %s. \n " , disk - > dir , file - > sub , strerror ( errno ) ) ;
2026-09-15 22:48:28 +02:00
log_tag ( " empty_%s:%s:%s: Time error. %s. \n " , es ( errno ) , disk - > name , esc_tag ( file - > sub ) , strerror ( errno ) ) ;
2026-01-24 19:19:11 +01:00
log_fatal_errno ( errno , disk - > name ) ;
2026-02-01 20:21:28 +01:00
log_fatal ( errno , " Stopping \n " ) ;
2026-01-24 19:19:11 +01:00
2013-08-26 20:20:36 +02:00
close ( f ) ;
+ + unrecoverable_error ;
goto bail ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-08-26 20:20:36 +02:00
}
2011-10-15 08:39:52 +02:00
/* close it */
2013-08-26 20:20:36 +02:00
ret = close ( f ) ;
if ( ret ! = 0 ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( errno , " Error closing '%s%s'. %s. \n " , disk - > dir , file - > sub , strerror ( errno ) ) ;
2026-09-15 22:48:28 +02:00
log_tag ( " empty_%s:%s:%s: Close error. %s. \n " , es ( errno ) , disk - > name , esc_tag ( file - > sub ) , strerror ( errno ) ) ;
2026-01-24 19:19:11 +01:00
log_fatal_errno ( errno , disk - > name ) ;
2026-02-01 20:21:28 +01:00
log_fatal ( errno , " Stopping \n " ) ;
2026-01-24 19:19:11 +01:00
2013-08-26 20:20:36 +02:00
+ + unrecoverable_error ;
goto bail ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-08-26 20:20:36 +02:00
}
2011-10-15 08:39:52 +02:00
2026-05-29 11:01:44 +02:00
log_tag ( " empty_fixed:%s:%s: Fixed empty file \n " , disk - > name , esc_tag ( file - > sub ) ) ;
2011-10-15 08:39:52 +02:00
+ + recovered_error ;
2013-03-01 20:13:56 +01:00
2026-05-29 11:01:44 +02:00
log_tag ( " status:recovered:%s:%s \n " , disk - > name , esc_tag ( file - > sub ) ) ;
msg_info ( " recovered %s \n " , fmt_term ( disk , file - > sub ) ) ;
2011-10-15 08:39:52 +02:00
}
}
2011-08-26 14:18:38 +02:00
/* for each link in the disk */
2011-10-01 19:18:32 +02:00
disk = handle [ i ] . disk ;
2011-08-26 14:18:38 +02:00
node = disk - > linklist ;
while ( node ) {
char path [ PATH_MAX ] ;
2013-01-27 18:06:15 +01:00
char pathto [ PATH_MAX ] ;
2011-08-26 14:18:38 +02:00
char linkto [ PATH_MAX ] ;
2013-01-27 18:06:15 +01:00
struct stat st ;
struct stat stto ;
2015-10-08 22:19:52 +02:00
struct snapraid_link * slink ;
2020-05-02 14:20:47 +02:00
int unsuccessful = 0 ;
2013-03-01 18:48:13 +01:00
int unrecoverable = 0 ;
2011-08-26 14:18:38 +02:00
2015-10-08 22:19:52 +02:00
slink = node - > data ;
2011-08-26 14:18:38 +02:00
node = node - > next ; /* next node */
/* if excluded continue to the next one */
2015-10-08 22:19:52 +02:00
if ( link_flag_has ( slink , FILE_IS_EXCLUDED ) ) {
2011-08-26 14:18:38 +02:00
continue ;
}
2015-10-08 22:19:52 +02:00
if ( link_flag_has ( slink , FILE_IS_HARDLINK ) ) {
2013-01-27 18:06:15 +01:00
/* stat the link */
2015-10-08 22:19:52 +02:00
pathprint ( path , sizeof ( path ) , " %s%s " , disk - > dir , slink - > sub ) ;
2013-01-27 18:06:15 +01:00
ret = stat ( path , & st ) ;
if ( ret = = - 1 ) {
2020-05-02 14:20:47 +02:00
unsuccessful = 1 ;
2011-08-26 14:18:38 +02:00
2026-02-01 20:21:28 +01:00
log_error ( errno , " Error stating hardlink '%s'. %s. \n " , path , strerror ( errno ) ) ;
2026-05-29 11:01:44 +02:00
log_tag ( " hardlink_%s:%s:%s:%s: Hardlink stat error. %s. \n " , es ( errno ) , disk - > name , esc_tag ( slink - > sub ) , esc_tag ( slink - > linkto ) , strerror ( errno ) ) ;
2026-01-25 21:25:03 +01:00
2026-02-22 14:58:06 +01:00
if ( is_hw ( errno ) ) {
2026-01-25 21:25:03 +01:00
+ + io_error ;
} else {
+ + soft_error ;
}
2013-01-27 18:06:15 +01:00
} else if ( ! S_ISREG ( st . st_mode ) ) {
2020-05-02 14:20:47 +02:00
unsuccessful = 1 ;
2011-08-26 14:18:38 +02:00
2026-02-01 20:21:28 +01:00
log_error ( ESOFT , " Error stating hardlink '%s' for not regular file. \n " , path ) ;
2026-05-29 11:01:44 +02:00
log_tag ( " hardlink_error:%s:%s:%s: Hardlink error for not regular file \n " , disk - > name , esc_tag ( slink - > sub ) , esc_tag ( slink - > linkto ) ) ;
2026-01-25 21:25:03 +01:00
+ + soft_error ;
2013-01-27 18:06:15 +01:00
}
/* stat the "to" file */
2015-10-08 22:19:52 +02:00
pathprint ( pathto , sizeof ( pathto ) , " %s%s " , disk - > dir , slink - > linkto ) ;
2013-01-27 18:06:15 +01:00
ret = stat ( pathto , & stto ) ;
if ( ret = = - 1 ) {
2020-05-02 14:20:47 +02:00
unsuccessful = 1 ;
2013-01-27 18:06:15 +01:00
2013-03-01 18:48:13 +01:00
if ( errno = = ENOENT ) {
unrecoverable = 1 ;
2013-06-29 18:53:58 +02:00
if ( fix ) {
2026-04-27 15:43:06 +02:00
/*
* If the target doesn ' t exist , it ' s unrecoverable
* because we cannot create an hardlink of a file that
* doesn ' t exists
* but in check , we can assume that fixing will recover
* such missing file , so we assume a less drastic error
*/
2026-01-25 21:25:03 +01:00
+ + unrecoverable_error ;
2013-06-29 18:53:58 +02:00
}
2013-03-01 18:48:13 +01:00
}
2026-02-01 20:21:28 +01:00
log_error ( errno , " Error stating hardlink-to '%s'. %s. \n " , pathto , strerror ( errno ) ) ;
2026-05-29 11:01:44 +02:00
log_tag ( " hardlink_%s:%s:%s:%s: Hardlink to stat error. %s. \n " , es ( errno ) , disk - > name , esc_tag ( slink - > sub ) , esc_tag ( slink - > linkto ) , strerror ( errno ) ) ;
2026-01-25 21:25:03 +01:00
2026-02-22 14:58:06 +01:00
if ( is_hw ( errno ) ) {
2026-01-25 21:25:03 +01:00
+ + io_error ;
} else {
+ + soft_error ;
}
2013-02-24 19:02:53 +01:00
} else if ( ! S_ISREG ( stto . st_mode ) ) {
2020-05-02 14:20:47 +02:00
unsuccessful = 1 ;
2013-01-27 18:06:15 +01:00
2026-02-01 20:21:28 +01:00
log_error ( ESOFT , " Error stating hardlink-to '%s' for not regular file. \n " , path ) ;
2026-05-29 11:01:44 +02:00
log_tag ( " hardlink_error:%s:%s:%s: Hardlink-to error for not regular file \n " , disk - > name , esc_tag ( slink - > sub ) , esc_tag ( slink - > linkto ) ) ;
2026-01-25 21:25:03 +01:00
+ + soft_error ;
2026-07-30 16:20:26 +02:00
} else if ( ! unsuccessful & & st . st_ino ! = INODE_INVALID & & stto . st_ino ! = INODE_INVALID & & st . st_ino ! = stto . st_ino ) {
2020-05-02 14:20:47 +02:00
unsuccessful = 1 ;
2013-01-27 18:06:15 +01:00
2026-02-01 20:21:28 +01:00
log_error ( ESOFT , " Mismatch hardlink '%s' and '%s'. Different inode. \n " , path , pathto ) ;
2026-05-29 11:01:44 +02:00
log_tag ( " hardlink_error:%s:%s:%s: Hardlink mismatch for different inode \n " , disk - > name , esc_tag ( slink - > sub ) , esc_tag ( slink - > linkto ) ) ;
2026-01-25 21:25:03 +01:00
+ + soft_error ;
2013-01-27 18:06:15 +01:00
}
2011-08-26 14:18:38 +02:00
} else {
2013-01-27 18:06:15 +01:00
/* read the symlink */
2015-10-08 22:19:52 +02:00
pathprint ( path , sizeof ( path ) , " %s%s " , disk - > dir , slink - > sub ) ;
2013-01-27 18:06:15 +01:00
ret = readlink ( path , linkto , sizeof ( linkto ) ) ;
if ( ret < 0 ) {
2020-05-02 14:20:47 +02:00
unsuccessful = 1 ;
2011-08-26 14:18:38 +02:00
2026-02-01 20:21:28 +01:00
log_error ( errno , " Error reading symlink '%s'. %s. \n " , path , strerror ( errno ) ) ;
2026-05-29 11:01:44 +02:00
log_tag ( " symlink_%s:%s:%s: Symlink read error. %s. \n " , es ( errno ) , disk - > name , esc_tag ( slink - > sub ) , strerror ( errno ) ) ;
2026-01-25 21:25:03 +01:00
2026-02-22 14:58:06 +01:00
if ( is_hw ( errno ) ) {
2026-01-25 21:25:03 +01:00
+ + io_error ;
} else {
+ + soft_error ;
}
2013-02-21 18:50:27 +01:00
} else if ( ret > = PATH_MAX ) {
2020-05-02 14:20:47 +02:00
unsuccessful = 1 ;
2013-01-27 18:06:15 +01:00
2026-02-01 20:21:28 +01:00
log_error ( ESOFT , " Error reading symlink '%s'. Symlink too long. \n " , path ) ;
2026-05-29 11:01:44 +02:00
log_tag ( " symlink_error:%s:%s: Symlink too long \n " , disk - > name , esc_tag ( slink - > sub ) ) ;
2026-01-25 21:25:03 +01:00
+ + soft_error ;
2013-01-27 18:06:15 +01:00
} else {
linkto [ ret ] = 0 ;
2015-10-08 22:19:52 +02:00
if ( strcmp ( linkto , slink - > linkto ) ! = 0 ) {
2020-05-02 14:20:47 +02:00
unsuccessful = 1 ;
2013-01-27 18:06:15 +01:00
2026-05-29 11:01:44 +02:00
log_tag ( " symlink_error:%s:%s: Symlink data error '%s' instead of '%s' \n " , disk - > name , esc_tag ( slink - > sub ) , linkto , slink - > linkto ) ;
2026-01-25 21:25:03 +01:00
+ + soft_error ;
2013-01-27 18:06:15 +01:00
}
2011-08-26 14:18:38 +02:00
}
}
2020-05-02 14:20:47 +02:00
if ( fix & & unsuccessful & & ! unrecoverable ) {
2026-01-24 19:19:11 +01:00
const char * link = link_flag_has ( slink , FILE_IS_HARDLINK ) ? " hard " : " sym " ;
2011-08-26 14:18:38 +02:00
/* create the ancestor directories */
2013-02-20 13:29:58 +01:00
ret = mkancestor ( path ) ;
2011-08-26 14:18:38 +02:00
if ( ret ! = 0 ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( errno , " Error creating ancestor '%s%s'. %s. \n " , disk - > dir , slink - > sub , strerror ( errno ) ) ;
2026-09-15 22:48:28 +02:00
log_tag ( " %slink_%s:%s:%s: Create ancestor error. %s. \n " , link , es ( errno ) , disk - > name , esc_tag ( slink - > sub ) , strerror ( errno ) ) ;
2026-01-24 19:19:11 +01:00
log_fatal_errno ( errno , disk - > name ) ;
2026-02-01 20:21:28 +01:00
log_fatal ( errno , " Stopping \n " ) ;
2026-01-24 19:19:11 +01:00
2011-08-26 14:18:38 +02:00
+ + unrecoverable_error ;
goto bail ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2011-08-26 14:18:38 +02:00
}
/* if it exists, it must be deleted before recreating */
ret = remove ( path ) ;
if ( ret ! = 0 & & errno ! = ENOENT ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( errno , " Error removing '%s%s'. %s. \n " , disk - > dir , slink - > sub , strerror ( errno ) ) ;
2026-09-15 22:48:28 +02:00
log_tag ( " %slink_%s:%s:%s: Remove error. %s. \n " , link , es ( errno ) , disk - > name , esc_tag ( slink - > sub ) , strerror ( errno ) ) ;
2026-01-24 19:19:11 +01:00
log_fatal_errno ( errno , disk - > name ) ;
2026-02-01 20:21:28 +01:00
log_fatal ( errno , " Stopping \n " ) ;
2026-01-24 19:19:11 +01:00
2011-08-26 14:18:38 +02:00
+ + unrecoverable_error ;
goto bail ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2011-08-26 14:18:38 +02:00
}
/* create it */
2015-10-08 22:19:52 +02:00
if ( link_flag_has ( slink , FILE_IS_HARDLINK ) ) {
2013-01-27 18:06:15 +01:00
ret = hardlink ( pathto , path ) ;
if ( ret ! = 0 ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( errno , " Error writing hardlink '%s' to '%s'. %s. \n " , path , pathto , strerror ( errno ) ) ;
2026-09-15 22:48:28 +02:00
log_tag ( " hardlink_%s:%s:%s: Hardlink error. %s. \n " , es ( errno ) , disk - > name , esc_tag ( slink - > sub ) , strerror ( errno ) ) ;
2026-01-24 19:19:11 +01:00
log_fatal_errno ( errno , disk - > name ) ;
2026-02-01 20:21:28 +01:00
log_fatal ( errno , " Stopping \n " ) ;
2026-01-24 19:19:11 +01:00
2013-01-27 18:06:15 +01:00
+ + unrecoverable_error ;
goto bail ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2011-08-26 14:18:38 +02:00
}
2026-05-29 11:01:44 +02:00
log_tag ( " hardlink_fixed:%s:%s: Fixed hardlink error \n " , disk - > name , esc_tag ( slink - > sub ) ) ;
2013-01-27 18:06:15 +01:00
+ + recovered_error ;
} else {
2015-10-08 22:19:52 +02:00
ret = symlink ( slink - > linkto , path ) ;
2013-01-27 18:06:15 +01:00
if ( ret ! = 0 ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( errno , " Error writing symlink '%s' to '%s'. %s. \n " , path , slink - > linkto , strerror ( errno ) ) ;
2026-09-15 22:48:28 +02:00
log_tag ( " symlink_%s:%s:%s: Hardlink error. %s. \n " , es ( errno ) , disk - > name , esc_tag ( slink - > sub ) , strerror ( errno ) ) ;
2026-01-24 19:19:11 +01:00
log_fatal_errno ( errno , disk - > name ) ;
2026-02-01 20:21:28 +01:00
log_fatal ( errno , " Stopping \n " ) ;
2026-01-24 19:19:11 +01:00
2013-01-27 18:06:15 +01:00
+ + unrecoverable_error ;
goto bail ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-01-27 18:06:15 +01:00
}
2026-05-29 11:01:44 +02:00
log_tag ( " symlink_fixed:%s:%s: Fixed symlink error \n " , disk - > name , esc_tag ( slink - > sub ) ) ;
2013-01-27 18:06:15 +01:00
+ + recovered_error ;
}
2013-03-01 20:13:56 +01:00
2026-05-29 11:01:44 +02:00
log_tag ( " status:recovered:%s:%s \n " , disk - > name , esc_tag ( slink - > sub ) ) ;
msg_info ( " recovered %s \n " , fmt_term ( disk , slink - > sub ) ) ;
2013-01-27 16:44:04 +01:00
}
}
/* for each dir in the disk */
disk = handle [ i ] . disk ;
node = disk - > dirlist ;
while ( node ) {
char path [ PATH_MAX ] ;
struct stat st ;
struct snapraid_dir * dir ;
2020-05-02 14:20:47 +02:00
int unsuccessful = 0 ;
2013-01-27 16:44:04 +01:00
dir = node - > data ;
node = node - > next ; /* next node */
/* if excluded continue to the next one */
if ( dir_flag_has ( dir , FILE_IS_EXCLUDED ) ) {
continue ;
}
/* stat the dir */
pathprint ( path , sizeof ( path ) , " %s%s " , disk - > dir , dir - > sub ) ;
ret = stat ( path , & st ) ;
if ( ret = = - 1 ) {
2020-05-02 14:20:47 +02:00
unsuccessful = 1 ;
2013-01-27 16:44:04 +01:00
2026-02-01 20:21:28 +01:00
log_error ( errno , " Error stating dir '%s'. %s. \n " , path , strerror ( errno ) ) ;
2026-05-29 11:01:44 +02:00
log_tag ( " dir_%s:%s:%s: Dir stat error. %s. \n " , es ( errno ) , disk - > name , esc_tag ( dir - > sub ) , strerror ( errno ) ) ;
2026-01-25 21:25:03 +01:00
2026-02-22 14:58:06 +01:00
if ( is_hw ( errno ) ) {
2026-01-25 21:25:03 +01:00
+ + io_error ;
} else {
+ + soft_error ;
}
2013-01-27 16:44:04 +01:00
} else if ( ! S_ISDIR ( st . st_mode ) ) {
2020-05-02 14:20:47 +02:00
unsuccessful = 1 ;
2013-01-27 16:44:04 +01:00
2026-05-29 11:01:44 +02:00
log_tag ( " dir_error:%s:%s: Dir error for not directory \n " , disk - > name , esc_tag ( dir - > sub ) ) ;
2026-01-25 21:25:03 +01:00
+ + soft_error ;
2013-01-27 16:44:04 +01:00
}
2020-05-02 14:20:47 +02:00
if ( fix & & unsuccessful ) {
2013-01-27 16:44:04 +01:00
/* create the ancestor directories */
2013-02-20 13:29:58 +01:00
ret = mkancestor ( path ) ;
2013-01-27 16:44:04 +01:00
if ( ret ! = 0 ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( errno , " Error creating ancestor '%s%s'. %s. \n " , disk - > dir , dir - > sub , strerror ( errno ) ) ;
2026-09-15 22:48:28 +02:00
log_tag ( " dir_%s:%s:%s: Create ancestor error. %s. \n " , es ( errno ) , disk - > name , esc_tag ( dir - > sub ) , strerror ( errno ) ) ;
2026-01-24 19:19:11 +01:00
log_fatal_errno ( errno , disk - > name ) ;
2026-02-01 20:21:28 +01:00
log_fatal ( errno , " Stopping \n " ) ;
2026-01-24 19:19:11 +01:00
2013-01-27 16:44:04 +01:00
+ + unrecoverable_error ;
goto bail ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-01-27 16:44:04 +01:00
}
/* create it */
ret = mkdir ( path , S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH ) ;
2026-05-21 00:20:42 +02:00
if ( ret ! = 0 & & errno ! = EEXIST ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( errno , " Error creating directory '%s%s'. %s. \n " , disk - > dir , dir - > sub , strerror ( errno ) ) ;
2026-09-15 22:48:28 +02:00
log_tag ( " dir_%s:%s:%s: Create directory error. %s. \n " , es ( errno ) , disk - > name , esc_tag ( dir - > sub ) , strerror ( errno ) ) ;
2026-01-24 19:19:11 +01:00
log_fatal_errno ( errno , disk - > name ) ;
2026-02-01 20:21:28 +01:00
log_fatal ( errno , " Stopping \n " ) ;
2026-01-24 19:19:11 +01:00
2013-01-27 16:44:04 +01:00
+ + unrecoverable_error ;
goto bail ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-01-27 16:44:04 +01:00
}
2026-05-29 11:01:44 +02:00
log_tag ( " dir_fixed:%s:%s: Fixed dir error \n " , disk - > name , esc_tag ( dir - > sub ) ) ;
2013-01-27 16:44:04 +01:00
+ + recovered_error ;
2013-03-01 20:13:56 +01:00
2026-05-29 11:01:44 +02:00
log_tag ( " status:recovered:%s:%s \n " , disk - > name , esc_tag ( dir - > sub ) ) ;
msg_info ( " recovered %s \n " , fmt_term ( disk , dir - > sub ) ) ;
2011-08-26 14:18:38 +02:00
}
}
}
2025-09-24 11:32:10 +02:00
end :
2025-10-13 11:46:36 +02:00
state_progress_end ( state , countpos , countmax , countsize , " Nothing to check. \n " ) ;
2011-03-04 20:59:21 +01:00
2011-03-18 22:38:11 +01:00
bail :
2013-05-31 23:34:59 +02:00
/* close all the files left open */
2014-09-27 00:32:39 +02:00
for ( j = 0 ; j < diskmax ; + + j ) {
2015-09-02 19:29:35 +02:00
struct snapraid_file * file = handle [ j ] . file ;
struct snapraid_disk * disk = handle [ j ] . disk ;
2013-05-31 23:34:59 +02:00
ret = handle_close ( & handle [ j ] ) ;
if ( ret = = - 1 ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-07-06 22:31:11 +02:00
/*
* If handle_close fails , the handle was open ( f ! = - 1 ) , which
* guarantees that both file and disk pointers are valid .
*/
2026-06-03 23:06:27 +02:00
log_tag ( " %s:% " PRIu64 " :%s:%s: Close error. %s. \n " , es ( errno ) , blockmax , disk - > name , esc_tag ( file - > sub ) , strerror ( errno ) ) ;
2026-01-24 19:19:11 +01:00
log_fatal_errno ( errno , disk - > name ) ;
2013-05-31 23:34:59 +02:00
+ + unrecoverable_error ;
/* continue, as we are already exiting */
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-05-31 23:34:59 +02:00
}
}
2013-04-28 19:01:36 +02:00
2026-01-25 21:25:03 +01:00
if ( soft_error | | io_error | | silent_error | | recovered_error | | unrecoverable_error ) {
2015-02-22 14:31:03 +01:00
msg_status ( " \n " ) ;
2026-01-25 21:25:03 +01:00
msg_status ( " %8u soft errors \n " , soft_error ) ;
msg_status ( " %8u io errors \n " , io_error ) ;
msg_status ( " %8u data errors \n " , silent_error ) ;
2011-03-17 16:17:59 +01:00
if ( fix ) {
2015-02-22 14:31:03 +01:00
msg_status ( " %8u recovered errors \n " , recovered_error ) ;
2011-03-17 16:17:59 +01:00
}
2013-10-19 13:24:35 +02:00
if ( unrecoverable_error ) {
2015-02-22 14:31:03 +01:00
msg_status ( " %8u UNRECOVERABLE errors \n " , unrecoverable_error ) ;
2013-10-19 13:24:35 +02:00
} else {
2012-10-07 23:18:10 +02:00
/* without checking, we don't know if they are really recoverable or not */
2014-02-12 22:04:14 +01:00
if ( ! state - > opt . auditonly )
2015-02-22 14:31:03 +01:00
msg_status ( " %8u unrecoverable errors \n " , unrecoverable_error ) ;
2012-10-07 23:18:10 +02:00
}
2011-03-04 20:59:21 +01:00
}
2026-02-01 20:21:28 +01:00
if ( ( soft_error | | io_error | | silent_error ) & & ! fix ) {
if ( soft_error )
log_fatal ( ESOFT , " WARNING! There are soft errors! \n " ) ;
if ( io_error )
log_fatal ( EIO , " DANGER! Unexpected input/output errors! \n " ) ;
if ( silent_error )
log_fatal ( EDATA , " DANGER! Unexpected silent data errors! \n " ) ;
}
2015-02-22 14:31:03 +01:00
if ( unrecoverable_error )
2026-02-01 20:21:28 +01:00
log_fatal ( ESOFT , " DANGER! Unrecoverable errors detected! \n " ) ;
2015-02-22 14:31:03 +01:00
2026-01-25 21:25:03 +01:00
log_tag ( " summary:error_soft:%u \n " , soft_error ) ;
log_tag ( " summary:error_io:%u \n " , io_error ) ;
log_tag ( " summary:error_data:%u \n " , silent_error ) ;
2013-10-03 22:44:33 +02:00
if ( fix )
2015-04-20 22:07:42 +02:00
log_tag ( " summary:error_recovered:%u \n " , recovered_error ) ;
2014-02-12 22:04:14 +01:00
if ( ! state - > opt . auditonly )
2015-04-20 22:07:42 +02:00
log_tag ( " summary:error_unrecoverable:%u \n " , unrecoverable_error ) ;
2013-10-03 22:44:33 +02:00
if ( fix ) {
2026-01-25 21:25:03 +01:00
if ( soft_error + io_error + silent_error + recovered_error + unrecoverable_error = = 0 )
2015-04-20 22:07:42 +02:00
log_tag ( " summary:exit:ok \n " ) ;
2013-10-03 22:44:33 +02:00
else if ( unrecoverable_error = = 0 )
2015-04-20 22:07:42 +02:00
log_tag ( " summary:exit:recovered \n " ) ;
2013-10-03 22:44:33 +02:00
else
2015-04-20 22:07:42 +02:00
log_tag ( " summary:exit:unrecoverable \n " ) ;
2014-02-12 22:04:14 +01:00
} else if ( ! state - > opt . auditonly ) {
2026-01-25 21:25:03 +01:00
if ( soft_error + io_error + silent_error + unrecoverable_error = = 0 )
2015-04-20 22:07:42 +02:00
log_tag ( " summary:exit:ok \n " ) ;
2013-10-03 22:44:33 +02:00
else if ( unrecoverable_error = = 0 )
2015-04-20 22:07:42 +02:00
log_tag ( " summary:exit:recoverable \n " ) ;
2013-10-03 22:44:33 +02:00
else
2015-04-20 22:07:42 +02:00
log_tag ( " summary:exit:unrecoverable \n " ) ;
2013-10-03 22:44:33 +02:00
} else { /* audit only */
2026-01-25 21:25:03 +01:00
if ( soft_error + silent_error + io_error = = 0 )
2015-04-20 22:07:42 +02:00
log_tag ( " summary:exit:ok \n " ) ;
2026-01-25 21:25:03 +01:00
else if ( silent_error + io_error = = 0 )
log_tag ( " summary:exit:warning \n " ) ;
2013-10-03 22:44:33 +02:00
else
2015-04-20 22:07:42 +02:00
log_tag ( " summary:exit:error \n " ) ;
2013-09-30 22:24:30 +02:00
}
2015-04-20 22:07:42 +02:00
log_flush ( ) ;
2013-09-30 22:24:30 +02:00
2011-06-11 12:05:02 +02:00
free ( failed ) ;
2012-11-02 14:07:18 +01:00
free ( failed_map ) ;
2021-05-22 11:37:36 +02:00
free ( block_enabled ) ;
2011-03-04 20:59:21 +01:00
free ( handle ) ;
2011-04-23 21:38:26 +02:00
free ( buffer_alloc ) ;
free ( buffer ) ;
2026-08-12 00:22:27 +02:00
bw_done ( & bw ) ;
2015-10-07 22:07:20 +02:00
/* fail if some error are present after the run */
2011-04-23 21:38:26 +02:00
if ( fix ) {
2013-06-29 18:11:36 +02:00
if ( state - > opt . expect_unrecoverable ) {
2011-04-23 21:38:26 +02:00
if ( unrecoverable_error = = 0 )
return - 1 ;
} else {
if ( unrecoverable_error ! = 0 )
return - 1 ;
}
} else {
2013-06-29 18:11:36 +02:00
if ( state - > opt . expect_unrecoverable ) {
2011-04-23 21:38:26 +02:00
if ( unrecoverable_error = = 0 )
return - 1 ;
2013-06-29 18:11:36 +02:00
} else if ( state - > opt . expect_recoverable ) {
2026-01-25 21:25:03 +01:00
if ( unrecoverable_error ! = 0 )
return - 1 ;
if ( soft_error + silent_error + io_error = = 0 )
2011-04-23 21:38:26 +02:00
return - 1 ;
} else {
2026-01-25 21:25:03 +01:00
if ( unrecoverable_error ! = 0 )
return - 1 ;
if ( soft_error + silent_error + io_error ! = 0 )
2011-04-23 21:38:26 +02:00
return - 1 ;
}
}
2011-03-04 20:59:21 +01:00
2026-02-09 23:49:02 +01:00
if ( alert < 0 )
return - 1 ;
2011-03-18 22:38:11 +01:00
return 0 ;
2011-03-04 20:59:21 +01:00
}
2014-02-12 22:04:14 +01:00
int state_check ( struct snapraid_state * state , int fix , block_off_t blockstart , block_off_t blockcount )
2011-03-04 20:59:21 +01:00
{
2011-03-12 10:09:59 +01:00
block_off_t blockmax ;
data_off_t size ;
2011-03-04 20:59:21 +01:00
int ret ;
2026-08-17 23:03:28 +02:00
int partial ;
2014-11-09 10:25:39 +01:00
struct snapraid_parity_handle parity [ LEV_MAX ] ;
struct snapraid_parity_handle * parity_ptr [ LEV_MAX ] ;
2026-01-25 21:25:03 +01:00
unsigned process_error ;
2013-09-15 21:12:41 +02:00
unsigned l ;
2011-03-04 20:59:21 +01:00
2015-02-22 14:31:03 +01:00
msg_progress ( " Initializing... \n " ) ;
2011-03-04 20:59:21 +01:00
2026-08-17 23:03:28 +02:00
/*
2026-08-28 09:36:12 +02:00
* - S / - - start and - B / - - count allow check / fix to operate on an arbitrary
* parity range instead of processing from the beginning . In particular ,
* recovery of a later section can be retried while an earlier section is
* missing or damaged .
*
* A partial fix therefore does not establish that preceding or following
* parity is valid ; it covers only the requested range .
*
* Keep partial based on the explicit - S / - B request rather than the clipped
* range : a nonzero - B remains partial even if it reaches the end of parity .
2026-08-17 23:03:28 +02:00
*/
partial = blockstart ! = 0 | | blockcount ! = 0 ;
2026-09-08 11:55:11 +02:00
/*
* Fix is best - effort on each stripe . If a stripe cannot be recovered , it
* is left unfixed and processing continues with the following stripes .
* Consequently , repaired parity is not guaranteed to be contiguous : an
* unrecoverable stripe may remain as a hole between successfully repaired
* stripes . This is intentional , as stopping would prevent recovery of all
* subsequent recoverable stripes .
*/
2015-02-01 18:48:50 +01:00
blockmax = parity_allocated_size ( state ) ;
2011-03-12 10:09:59 +01:00
size = blockmax * ( data_off_t ) state - > block_size ;
2011-03-04 20:59:21 +01:00
2011-03-17 16:17:59 +01:00
if ( blockstart > blockmax ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-06-03 23:06:27 +02:00
log_fatal ( EUSER , " Error in the specified starting block % " PRIu64 " . It's larger than the parity size % " PRIu64 " . \n " , blockstart , blockmax ) ;
2011-03-04 20:59:21 +01:00
exit ( EXIT_FAILURE ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2011-03-04 20:59:21 +01:00
}
2011-03-23 18:54:08 +01:00
/* adjust the number of block to process */
2026-06-03 19:00:24 +02:00
if ( blockcount ! = 0 & & blockcount < blockmax - blockstart ) {
2011-03-23 18:54:08 +01:00
blockmax = blockstart + blockcount ;
}
2011-03-04 20:59:21 +01:00
if ( fix ) {
2026-04-27 15:43:06 +02:00
/*
* If fixing , create the file and open for writing
* if it fails , we cannot continue
*/
2014-09-27 00:32:39 +02:00
for ( l = 0 ; l < state - > level ; + + l ) {
2015-10-16 23:17:38 +02:00
/* skip parity disks that are not accessible */
if ( state - > parity [ l ] . skip_access ) {
parity_ptr [ l ] = 0 ;
continue ;
}
2013-09-15 21:12:41 +02:00
parity_ptr [ l ] = & parity [ l ] ;
2013-09-11 23:30:32 +02:00
2019-05-19 16:46:03 +02:00
/* if the parity is excluded */
if ( state - > parity [ l ] . is_excluded_by_filter ) {
2026-08-28 09:36:12 +02:00
/*
* Excluded means " do not modify " , not " do not use " .
*
* An excluded parity can still provide a recovery equation or the independent
* parity check required when recovered data has no trustworthy hash , so keep
* it available read - only whenever possible .
*/
2019-05-19 16:46:03 +02:00
ret = parity_open ( parity_ptr [ l ] , & state - > parity [ l ] , l , state - > file_mode , state - > block_size , state - > opt . parity_limit_size ) ;
if ( ret = = - 1 ) {
2026-06-03 23:06:27 +02:00
log_tag ( " parity_%s:% " PRIu64 " :%s: Open error. %s. \n " , es ( errno ) , blockmax , lev_config_name ( l ) , strerror ( errno ) ) ;
2026-02-22 14:58:06 +01:00
if ( is_hw ( errno ) ) {
2026-01-24 19:19:11 +01:00
log_fatal_errno ( errno , lev_config_name ( l ) ) ;
exit ( EXIT_FAILURE ) ;
2026-01-26 09:42:03 +01:00
}
2026-01-24 19:19:11 +01:00
2019-05-19 16:46:03 +02:00
/* continue anyway */
parity_ptr [ l ] = 0 ;
}
} else {
/* open for writing */
ret = parity_create ( parity_ptr [ l ] , & state - > parity [ l ] , l , state - > file_mode , state - > block_size , state - > opt . parity_limit_size ) ;
if ( ret = = - 1 ) {
/* LCOV_EXCL_START */
2026-01-24 19:19:11 +01:00
log_tag ( " parity_%s:%u:%s: Create error. %s. \n " , es ( errno ) , 0 , lev_config_name ( l ) , strerror ( errno ) ) ;
log_fatal_errno ( errno , lev_config_name ( l ) ) ;
2019-05-19 16:46:03 +02:00
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
2026-08-28 22:43:51 +02:00
/*
2026-09-09 09:58:56 +02:00
* Fix restores the physical state described by the content file .
2026-08-28 22:43:51 +02:00
*
2026-09-09 09:58:56 +02:00
* For an existing parity level , preserve every persisted split boundary
* while limiting the physical layout to the parity extent actually required
* by the content . Preceding splits needed to reach an existing later split
* are restored immediately ; the remaining tail grows lazily as repaired
* parity blocks are written .
2026-08-28 22:43:51 +02:00
*
2026-09-09 09:58:56 +02:00
* A new parity level has no persisted layout yet ( PARITY_SIZE_INVALID ) , so
* its split layout must instead be allocated from scratch .
2026-08-28 22:43:51 +02:00
*/
2026-09-09 09:58:56 +02:00
if ( state - > parity [ l ] . split_map [ 0 ] . size = = PARITY_SIZE_INVALID )
ret = parity_chsize ( parity_ptr [ l ] , & state - > parity [ l ] , 0 , size , state - > block_size , state - > opt . skip_fallocate , state - > opt . skip_space_holder ) ;
else
ret = parity_restore ( parity_ptr [ l ] , size , state - > block_size , state - > opt . skip_fallocate ) ;
2019-05-19 16:46:03 +02:00
if ( ret = = - 1 ) {
/* LCOV_EXCL_START */
2026-01-24 19:19:11 +01:00
log_tag ( " parity_%s:%u:%s: Create error. %s. \n " , es ( errno ) , 0 , lev_config_name ( l ) , strerror ( errno ) ) ;
log_fatal_errno ( errno , lev_config_name ( l ) ) ;
2019-05-19 16:46:03 +02:00
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
2013-09-11 23:30:32 +02:00
}
}
2014-02-12 22:04:14 +01:00
} else if ( ! state - > opt . auditonly ) {
2026-04-27 15:43:06 +02:00
/*
* If checking , open the file for reading
* it may fail if the file doesn ' t exist , in this case we continue to check the files
*/
2014-09-27 00:32:39 +02:00
for ( l = 0 ; l < state - > level ; + + l ) {
2013-09-15 21:12:41 +02:00
parity_ptr [ l ] = & parity [ l ] ;
2016-09-28 20:32:50 +02:00
ret = parity_open ( parity_ptr [ l ] , & state - > parity [ l ] , l , state - > file_mode , state - > block_size , state - > opt . parity_limit_size ) ;
2011-10-09 07:51:25 +02:00
if ( ret = = - 1 ) {
2026-06-03 23:06:27 +02:00
log_tag ( " parity_%s:% " PRIu64 " :%s: Open error. %s. \n " , es ( errno ) , blockmax , lev_config_name ( l ) , strerror ( errno ) ) ;
2026-02-22 14:58:06 +01:00
if ( is_hw ( errno ) ) {
2026-01-24 19:19:11 +01:00
log_fatal_errno ( errno , lev_config_name ( l ) ) ;
exit ( EXIT_FAILURE ) ;
}
2015-02-22 14:31:03 +01:00
msg_status ( " No accessible %s file, only files will be checked. \n " , lev_name ( l ) ) ;
2026-01-24 19:19:11 +01:00
2011-04-23 21:38:26 +02:00
/* continue anyway */
2013-09-15 21:12:41 +02:00
parity_ptr [ l ] = 0 ;
2011-04-23 21:38:26 +02:00
}
2013-09-11 23:30:32 +02:00
}
2012-10-07 23:18:10 +02:00
} else {
/* otherwise don't use any parity */
2014-09-27 00:32:39 +02:00
for ( l = 0 ; l < state - > level ; + + l )
2013-09-15 21:12:41 +02:00
parity_ptr [ l ] = 0 ;
2011-03-04 20:59:21 +01:00
}
2026-01-25 21:25:03 +01:00
process_error = 0 ;
2011-03-19 22:00:08 +01:00
2011-03-17 16:17:59 +01:00
/* skip degenerated cases of empty parity, or skipping all */
if ( blockstart < blockmax ) {
2026-08-17 23:03:28 +02:00
ret = state_check_process ( state , fix , parity_ptr , blockstart , blockmax , partial ) ;
2011-03-17 16:17:59 +01:00
if ( ret = = - 1 ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-01-25 21:25:03 +01:00
+ + process_error ;
2011-03-19 22:00:08 +01:00
/* continue, as we are already exiting */
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2011-03-17 16:17:59 +01:00
}
2011-03-04 20:59:21 +01:00
}
2011-03-18 22:38:11 +01:00
/* try to close only if opened */
2014-09-27 00:32:39 +02:00
for ( l = 0 ; l < state - > level ; + + l ) {
2013-09-15 21:12:41 +02:00
if ( parity_ptr [ l ] ) {
ret = parity_close ( parity_ptr [ l ] ) ;
2013-09-11 23:30:32 +02:00
if ( ret = = - 1 ) {
2019-05-21 22:33:24 +02:00
/* LCOV_EXCL_START */
2026-06-03 23:06:27 +02:00
log_tag ( " parity_%s:% " PRIu64 " :%s: Close error. %s. \n " , es ( errno ) , blockmax , lev_config_name ( l ) , strerror ( errno ) ) ;
2026-01-24 19:19:11 +01:00
log_fatal_errno ( errno , lev_config_name ( l ) ) ;
2026-01-25 21:25:03 +01:00
+ + process_error ;
2013-09-11 23:30:32 +02:00
/* continue, as we are already exiting */
2019-05-21 22:33:24 +02:00
/* LCOV_EXCL_STOP */
2013-09-11 23:30:32 +02:00
}
}
}
2026-01-25 21:25:03 +01:00
if ( process_error ! = 0 )
2013-06-29 17:53:40 +02:00
return - 1 ;
2026-08-14 01:18:03 +02:00
2026-09-20 15:56:14 +02:00
if ( log_hardware_errors ( ) ! = 0 ) {
msg_status ( " Everything done, but hardware errors were encountered in the process \n " ) ;
return - 1 ;
}
2026-08-14 01:18:03 +02:00
msg_status ( " Everything OK \n " ) ;
2013-06-29 17:53:40 +02:00
return 0 ;
2011-03-19 22:00:08 +01:00
}
2026-09-02 16:31:21 +02:00