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
/****************************************************************************/
/* 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 " ;
}
2012-02-09 23:17:36 +01:00
/**
* A block that failed the hash check , or that was deleted .
*/
2011-04-23 21:38:26 +02:00
struct failed_struct {
2013-02-24 11:10:10 +01:00
/**
2014-02-01 17:57:26 +01:00
* If we know for sure that the block is garbage or missing
* and it needs to be recovered and rewritten to the disk .
2013-02-24 11:10:10 +01:00
*/
int is_bad ;
/**
2026-01-17 21:16:21 +01:00
* If what we have recovered may be not updated data ,
2013-02-24 11:10:10 +01:00
* an old version , or just garbage .
*
* Essentially , it means that we are not sure what we have recovered
* is really correct . It ' s just our best guess .
*
2026-01-17 21:16:21 +01:00
* These " recovered " blocks are also written to the disk if the block is marked as : : is_bad .
2013-02-24 11:10:10 +01:00
* But these files are marked also as FILE_IS_DAMAGED , and then renamed to . unrecoverable .
*
2014-02-01 23:32:53 +01:00
* Note that this could happen only for CHG blocks .
2013-02-24 11:10:10 +01:00
*/
int is_outofdate ;
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 .
2013-02-24 19:02:53 +01:00
* Return = = 0 if equal
2011-04-28 21:38:25 +02:00
*/
2026-06-04 00:34:51 +02:00
static int blockcmp ( struct snapraid_state * state , int rehash , struct snapraid_block * block , size_t pos_size , unsigned char * buffer , unsigned char * buffer_zero )
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
/* now compute the hash of the valid part */
2013-07-01 23:08:25 +02:00
if ( rehash ) {
2015-10-03 10:43:02 +02:00
memhash ( state - > prevhash , state - > prevhashseed , hash , buffer , pos_size ) ;
2013-07-01 23:08:25 +02:00
} else {
2015-10-03 10:43:02 +02:00
memhash ( state - > hash , state - > hashseed , hash , buffer , pos_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 ;
}
/* compare to the end of the block */
2015-10-03 10:43:02 +02:00
if ( pos_size < state - > block_size ) {
if ( memcmp ( buffer + pos_size , buffer_zero + pos_size , state - > block_size - pos_size ) ! = 0 ) {
2011-04-28 21:38:25 +02:00
return - 1 ;
}
}
return 0 ;
}
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
*/
2013-12-27 13:56:56 +01: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 , void * buffer_zero )
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 ) {
2014-02-01 17:57:26 +01:00
/* if we are expected to recover this block */
if ( ! failed [ failed_map [ j ] ] . is_outofdate
2014-09-27 00:32:39 +02:00
/* if the block has a hash to check */
2014-02-01 17:57:26 +01:00
& & block_has_updated_hash ( failed [ failed_map [ j ] ] . block )
) {
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 ) ;
2015-10-03 10:43:02 +02:00
if ( blockcmp ( state , rehash , failed [ failed_map [ j ] ] . block , pos_size , buffer [ failed [ failed_map [ j ] ] . index ] , buffer_zero ) ! = 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
*/
2014-03-18 21:04:25 +01:00
raid_gen ( diskmax , state - > level , state - > block_size , buffer ) ;
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 */
2014-01-08 17:07:13 +01:00
raid_gen ( diskmax , i + 1 , state - > block_size , buffer ) ;
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 */
2014-01-08 17:07:13 +01:00
raid_gen ( diskmax , state - > level , state - > block_size , buffer ) ;
2013-09-18 21:56:06 +02:00
return 1 ;
}
return 0 ;
}
2011-04-23 21:38:26 +02:00
/**
* Repair errors .
2015-10-07 22:07:20 +02:00
* Return < 0 if failure for missing strategy , > 0 if data is wrong and we cannot rebuild correctly , 0 on success .
2026-01-17 21:16:21 +01:00
* If success , the parity is computed in the buffer variable .
2011-04-23 21:38:26 +02:00
*/
2026-06-04 00:34:51 +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 , void * buffer_zero )
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 */
2014-01-08 17:07:13 +01:00
raid_gen ( diskmax , state - > level , state - > block_size , buffer ) ;
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
/*
* Check if there is at least a failed block that can be checked for correctness using the hash
* if there isn ' t , we have to sacrifice a parity block to check that the result is correct
*/
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 ) {
2014-02-01 17:57:26 +01:00
/* if we are expected to recover this block */
if ( ! failed [ failed_map [ i ] ] . is_outofdate
2016-04-10 22:44:40 +02:00
/* if the block has a hash to check */
2014-02-01 17:57:26 +01:00
& & block_has_updated_hash ( failed [ failed_map [ i ] ] . block )
)
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 */
2013-09-18 21:56:06 +02:00
if ( is_hash_matching ( state , rehash , diskmax , failed , failed_map , failed_count , buffer , buffer_zero ) )
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-06-04 00:34:51 +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_zero )
2012-02-09 23:17:36 +01:00
{
int ret ;
int error ;
unsigned j ;
int n ;
2013-02-27 21:07:16 +01:00
int something_to_recover ;
2014-03-04 19:34:55 +01:00
int something_unsynced ;
2012-02-09 23:17:36 +01:00
error = 0 ;
2014-02-01 17:57:26 +01:00
/* if nothing failed, just recompute the parity */
if ( failed_count = = 0 ) {
raid_gen ( diskmax , state - > level , state - > block_size , buffer ) ;
return 0 ;
}
2014-03-18 21:04:25 +01:00
/* logs the status */
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 ;
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
}
if ( hash_is_invalid ( block - > hash ) ) {
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
/*
* Here we have to try two different strategies to recover , because in case the ' sync '
* process is aborted , we don ' t know if the parity data is really updated just like after ' sync ' ,
* or if it still represents the state before the ' sync ' .
*/
2012-06-20 23:05:15 +02:00
2026-04-27 15:43:06 +02:00
/*
* Note that if the ' sync ' ends normally , we don ' t have any DELETED , REP and CHG blocks
* and the two strategies are identical
*/
2014-02-01 17:57:26 +01:00
2026-04-27 15:43:06 +02:00
/*
* As first , we assume that the parity IS updated for the current state
* and that we are going to recover the state after the last ' sync ' .
* In this case , parity contains info from BLK , REP and CHG blocks ,
* but not for DELETED .
* We need to put in the recovering process only the bad blocks , because all the
* others already contains the correct data read from disk , and the parity is correctly computed for them .
* We are interested to recover BLK , REP and CHG blocks if they are marked as bad ,
* but we are not interested in DELETED ones .
*/
2013-02-27 21:07:16 +01:00
2012-02-09 23:17:36 +01:00
n = 0 ;
2013-02-27 21:07:16 +01:00
something_to_recover = 0 ; /* keep track if there is at least one block to fix */
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 ) ;
assert ( block_state ! = BLOCK_STATE_DELETED ) ; /* we cannot have bad DELETED blocks */
2014-02-01 17:57:26 +01:00
/* if we have the hash for it */
if ( ( block_state = = BLOCK_STATE_BLK | | block_state = = BLOCK_STATE_REP )
2014-09-27 00:32:39 +02:00
/* try to fetch the block using the known hash */
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
) {
/* we already have corrected it! */
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 {
2014-02-01 17:57:26 +01:00
/* otherwise try to recover it */
2013-02-27 21:07:16 +01:00
failed_map [ n ] = j ;
+ + n ;
2013-03-19 21:59:13 +01:00
2013-02-27 21:07:16 +01:00
/* we have something to try to recover */
something_to_recover = 1 ;
}
2012-02-09 23:17:36 +01:00
}
}
/* if nothing to fix */
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
2013-10-25 18:03:05 +02:00
/* recompute only the parity */
2014-01-08 17:07:13 +01:00
raid_gen ( diskmax , state - > level , state - > block_size , buffer ) ;
2012-02-09 23:17:36 +01:00
return 0 ;
}
2013-09-15 21:12:41 +02:00
ret = repair_step ( state , rehash , pos , diskmax , failed , failed_map , n , buffer , buffer_recov , buffer_zero ) ;
2012-02-09 23:17:36 +01:00
if ( ret = = 0 ) {
2026-04-27 15:43:06 +02:00
/*
* Reprocess the CHG blocks , for which we don ' t have a hash to check
* if they were BAD we have to use some heuristics to ensure that we have recovered
* the state after the sync . If unsure , we assume the worst case
*/
2012-06-20 23:05:15 +02:00
2014-09-27 00:32:39 +02:00
for ( j = 0 ; j < failed_count ; + + j ) {
2012-02-09 23:17:36 +01:00
/* we take care only of BAD blocks we have to write back */
if ( failed [ j ] . is_bad ) {
unsigned block_state = block_state_get ( failed [ j ] . block ) ;
2012-06-20 23:05:15 +02:00
2026-04-27 15:43:06 +02:00
/*
* BLK and REP blocks are always OK , because at this point
* we have already checked their hash
*/
2014-02-01 17:57:26 +01:00
if ( block_state ! = BLOCK_STATE_CHG ) {
assert ( block_state = = BLOCK_STATE_BLK | | block_state = = BLOCK_STATE_REP ) ;
continue ;
}
/* for CHG blocks we have to 'guess' if they are correct or not */
2026-04-27 15:43:06 +02:00
/*
* If the hash is invalid we cannot check the result
* this could happen if we have lost this information
* after an aborted sync
*/
2014-02-01 17:57:26 +01:00
if ( hash_is_invalid ( failed [ j ] . block - > hash ) ) {
/* it may contain garbage */
failed [ j ] . is_outofdate = 1 ;
2014-03-18 21:04:25 +01:00
2026-01-25 14:06:56 +01:00
log_tag ( " repair:hash_unknown:%u: Unknown hash \n " , j ) ;
2014-02-01 17:57:26 +01:00
} else if ( hash_is_zero ( failed [ j ] . block - > hash ) ) {
2026-04-27 15:43:06 +02:00
/*
* If the block is not filled with 0 , we are sure to have
* restored it to the state after the ' sync '
* instead , if the block is filled with 0 , it could be either that the
* block after the sync is really filled by 0 , or that
* we restored the block before the ' sync ' .
*/
2012-02-09 23:17:36 +01:00
if ( memcmp ( buffer [ failed [ j ] . index ] , buffer_zero , state - > block_size ) = = 0 ) {
/* it may contain garbage */
failed [ j ] . is_outofdate = 1 ;
2014-03-18 21:04:25 +01:00
2026-01-25 14:06:56 +01:00
log_tag ( " repair_hash_unknown:%u: Maybe old zero \n " , j ) ;
2012-02-09 23:17:36 +01:00
}
2014-02-01 17:57:26 +01:00
} else {
2026-04-27 15:43:06 +02:00
/*
* If the hash is different than the previous one , we are sure to have
* restored it to the state after the ' sync '
* instead , if the hash matches , it could be either that the
* block after the sync has this hash , or that
* we restored the block before the ' sync ' .
*/
2026-06-04 00:34:51 +02:00
size_t pos_size = file_block_size ( failed [ j ] . file , failed [ j ] . file_pos , state - > block_size ) ;
2015-10-03 10:43:02 +02:00
if ( blockcmp ( state , rehash , failed [ j ] . block , pos_size , buffer [ failed [ j ] . index ] , buffer_zero ) = = 0 ) {
2013-02-24 19:02:53 +01:00
/* it may contain garbage */
failed [ j ] . is_outofdate = 1 ;
2014-03-18 21:04:25 +01:00
2026-01-25 14:06:56 +01:00
log_tag ( " repair_hash_unknown:%u: Maybe old data \n " , j ) ;
2013-02-24 19:02:53 +01:00
}
2012-02-09 23:17:36 +01:00
}
}
}
2012-06-20 23:05:15 +02:00
2012-02-09 23:17:36 +01:00
return 0 ;
}
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-04-27 15:43:06 +02:00
/*
* Now assume that the parity IS NOT updated at the current state ,
* but still represent the state before the last ' sync ' process .
* In this case , parity contains info from BLK , REP ( old version ) , CHG ( old version ) and DELETED blocks ,
* but not for REP ( new version ) and CHG ( new version ) .
* We are interested to recover BLK ones marked as bad ,
* but we are not interested to recover CHG ( new version ) and REP ( new version ) blocks ,
* even if marked as bad , because we don ' t have parity for them and it ' s just impossible ,
* and we are not interested to recover DELETED ones .
*/
2012-02-09 23:17:36 +01:00
n = 0 ;
2013-02-27 21:07:16 +01:00
something_to_recover = 0 ; /* keep track if there is at least one block to fix */
2014-03-04 19:34:55 +01:00
something_unsynced = 0 ; /* keep track if we have some unsynced info to process */
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
2013-02-27 21:07:16 +01:00
if ( block_state = = BLOCK_STATE_DELETED
| | block_state = = BLOCK_STATE_CHG
2014-02-01 17:57:26 +01:00
| | block_state = = BLOCK_STATE_REP
2012-02-09 23:17:36 +01:00
) {
2026-04-27 15:43:06 +02:00
/*
* If the block is CHG , REP or DELETED , we don ' t have the original content of block ,
* and we must try to recover it .
* This applies to CHG and REP blocks even if they are not marked bad ,
* because the parity is computed with old content , and not with the new one .
* Note that this recovering is done just to make it possible to recover any other BLK one ,
* we are not really interested in DELETED , CHG ( old version ) and REP ( old version ) .
*/
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
/*
* If the block was a ZERO block , restore it to the original 0 as before the ' sync '
* We do this to just allow recovering of other BLK ones
*/
2014-02-01 17:57:26 +01:00
memset ( buffer [ failed [ j ] . index ] , 0 , state - > block_size ) ;
2026-04-27 15:43:06 +02:00
/*
* Note that from now the buffer is definitely lost
* we can do this only because it ' s the last retry of recovering
*/
2014-02-01 17:57:26 +01:00
/* try to fetch the old block using the old hash for CHG and DELETED blocks */
} 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 ) {
2013-02-27 21:07:16 +01:00
2026-04-27 15:43:06 +02:00
/*
* Note that from now the buffer is definitely lost
* we can do this only because it ' s the last retry of recovering
*/
2013-02-27 21:07:16 +01:00
} else {
/* otherwise try to recover it */
failed_map [ n ] = j ;
+ + n ;
2012-02-09 23:17:36 +01:00
2026-04-27 15:43:06 +02:00
/*
* Note that we don ' t set something_to_recover , because we are
* not really interested to recover * only * old blocks .
*/
2013-02-27 21:07:16 +01:00
}
2026-04-27 15:43:06 +02:00
/*
* Avoid using the hash of this block to verify the recovering
* this applies to REP blocks because we are going to recover the old state
* and the REP hash represents the new one
* it also applies to CHG and DELETE blocks because we want to have
* a successful recovering only if a BLK one is matching
*/
2013-02-27 21:07:16 +01:00
failed [ j ] . is_outofdate = 1 ;
} else if ( failed [ j ] . is_bad ) {
2026-04-27 15:43:06 +02:00
/*
* If the block is bad we don ' t know its content , and we try to recover it
* At this point , we can have only BLK ones
*/
2012-02-09 23:17:36 +01:00
2013-02-27 21:07:16 +01:00
assert ( block_state = = BLOCK_STATE_BLK ) ;
2012-02-09 23:17:36 +01:00
2013-02-27 21:07:16 +01:00
/* we have something we are interested to recover */
something_to_recover = 1 ;
2014-02-01 17:57:26 +01:00
2013-02-27 21:07:16 +01:00
/* we try to recover it */
failed_map [ n ] = j ;
+ + n ;
}
}
2012-06-20 23:05:15 +02:00
2026-04-27 15:43:06 +02:00
/*
* If nothing to fix , we just don ' t try
* if nothing unsynced we also don ' t retry , because it ' s the same try as before
*/
2014-03-04 19:34:55 +01:00
if ( something_to_recover & & something_unsynced ) {
2013-09-15 21:12:41 +02:00
ret = repair_step ( state , rehash , pos , diskmax , failed , failed_map , n , buffer , buffer_recov , buffer_zero ) ;
2013-02-27 21:07:16 +01:00
if ( ret = = 0 ) {
2026-04-27 15:43:06 +02:00
/*
* Reprocess the REP and CHG blocks , for which we have recovered an old state
* that we don ' t want to save into disk
* we have already marked them , but we redo it for logging
*/
2014-03-18 21:04:25 +01:00
2014-09-27 00:32:39 +02:00
for ( j = 0 ; j < failed_count ; + + j ) {
2014-03-18 21:04:25 +01:00
/* we take care only of BAD blocks we have to write back */
if ( failed [ j ] . is_bad ) {
unsigned block_state = block_state_get ( failed [ j ] . block ) ;
if ( block_state = = BLOCK_STATE_CHG
| | block_state = = BLOCK_STATE_REP
) {
2026-04-27 15:43:06 +02:00
/*
* Mark that we have restored an old state
* and we don ' t want to write it to the disk
*/
2014-03-18 21:04:25 +01:00
failed [ j ] . is_outofdate = 1 ;
2026-01-25 14:06:56 +01:00
log_tag ( " repair_hash_unknown:%u: Surely old data \n " , j ) ;
2014-03-18 21:04:25 +01:00
}
}
}
2013-02-27 21:07:16 +01:00
return 0 ;
2012-02-09 23:17:36 +01: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-06-04 00:34:51 +02:00
log_tag ( " recover_unsync:% " PRIu64 " :%u: Skipped for%s%s \n " , pos , n ,
2014-03-18 21:04:25 +01:00
! something_to_recover ? " nothing to recover " : " " ,
2020-05-02 14:21:40 +02:00
! something_unsynced ? " nothing unsynced " : " "
2014-03-18 21:04:25 +01:00
) ;
2012-02-09 23:17:36 +01:00
}
/* return the number of failed attempts, or -1 if no strategy */
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-06-04 00:34:51 +02:00
static int file_post ( struct snapraid_state * state , int fix , 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
uint64_t inode ;
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-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 ) ) {
/* rename it to .unrecoverable */
2021-05-22 09:05:21 +02:00
char path [ PATH_MAX ] ;
2014-12-29 08:50:20 +01:00
char path_to [ PATH_MAX ] ;
2021-05-22 09:05:21 +02:00
pathprint ( path , sizeof ( path ) , " %s%s " , disk - > dir , file - > sub ) ;
2014-12-29 08:50:20 +01:00
pathprint ( path_to , sizeof ( path_to ) , " %s%s.unrecoverable " , disk - > dir , file - > sub ) ;
/* ensure to close the file before renaming */
2015-09-02 19:37:24 +02:00
if ( handle [ j ] . file = = file ) {
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 ) ;
2015-09-02 19:37:24 +02:00
return - 1 ;
/* LCOV_EXCL_STOP */
}
2014-12-29 08:50:20 +01:00
}
ret = rename ( path , path_to ) ;
if ( ret ! = 0 ) {
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( errno , " Error renaming '%s%s'. %s. \n " , disk - > dir , file - > sub , strerror ( errno ) ) ;
2026-06-04 00:34:51 +02:00
log_tag ( " %s:% " PRIu64 " :%s:%s: Rename 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 */
}
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
/* and do not set the time if damaged */
goto close_and_continue ;
}
/* if the file is not fixed, meaning that it is untouched */
if ( ! file_flag_has ( file , FILE_IS_FIXED ) ) {
/* 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-04-27 15:43:06 +02:00
/*
* Reopen it as readonly , as to set the mtime readonly access it ' s enough
* we know that the file exists because it has the FILE_IS_FIXED tag
*/
2026-06-03 20:49:36 +02:00
ret = handle_open ( & handle [ j ] , file , state - > file_mode , 0 ) ;
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 */
}
}
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 ) ) ;
2014-12-29 08:50:20 +01:00
inode = handle [ j ] . st . st_ino ;
/* search for the corresponding inode */
collide_file = tommy_hashdyn_search ( & disk - > inodeset , file_inode_compare_to_arg , & inode , file_inode_hash ( inode ) ) ;
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 */
) {
/* set the original modification time */
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
}
} 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
}
2014-11-09 10:25:39 +01: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 )
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 ;
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 ;
2014-12-29 00:01:35 +01:00
/* we need 1 * data + 2 * parity + 1 * zero */
2014-01-14 22:10:47 +01:00
buffermax = diskmax + 2 * state - > level + 1 ;
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
/* fill up the zero buffer */
2014-09-27 00:32:39 +02:00
memset ( buffer [ buffermax - 1 ] , 0 , state - > block_size ) ;
raid_zero ( buffer [ buffermax - 1 ] ) ;
2011-03-04 20:59:21 +01:00
2011-06-11 12:05:02 +02:00
failed = malloc_nofail ( diskmax * sizeof ( struct failed_struct ) ) ;
2012-11-02 14:07:18 +01:00
failed_map = malloc_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
/*
* Store it in the failed set , because potentially
* the parity may be still computed with the previous content
*/
2015-10-03 10:43:02 +02:00
failed [ failed_count ] . is_bad = 0 ; /* note that is_bad==0 <=> file==0 */
2012-02-09 23:17:36 +01:00
failed [ failed_count ] . is_outofdate = 0 ;
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 ;
failed [ failed_count ] . is_outofdate = 0 ;
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 ) ;
}
}
/* if it's the first open, and not excluded and larger */
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-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 */
2012-02-09 23:17:36 +01:00
failed [ failed_count ] . is_outofdate = 0 ;
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
/*
* We DO NOT mark them as bad to avoid to overwrite them with wrong data .
* if we don ' t have a hash , we always assume the first read of the block correct .
*/
2014-02-01 17:57:26 +01:00
failed [ failed_count ] . is_bad = 0 ; /* we assume the CHG block correct */
2012-02-09 23:17:36 +01:00
failed [ failed_count ] . is_outofdate = 0 ;
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 ;
}
2014-02-01 17:57:26 +01:00
assert ( block_state = = BLOCK_STATE_BLK | | block_state = = BLOCK_STATE_REP ) ;
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 ) {
memhash ( state - > prevhash , state - > prevhashseed , hash , buffer [ j ] , read_size ) ;
} else {
memhash ( state - > hash , state - > hashseed , hash , buffer [ j ] , read_size ) ;
}
/* 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 */
failed [ failed_count ] . is_outofdate = 0 ;
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-06-03 23:06:27 +02:00
log_tag ( " error:% " 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
/*
* Always insert REP 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_REP ) {
failed [ failed_count ] . is_bad = 0 ; /* it's not bad */
failed [ failed_count ] . is_outofdate = 0 ;
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 ] ;
void * buffer_zero ;
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 ;
/* the zero buffer is the last one */
2014-09-27 00:32:39 +02:00
buffer_zero = buffer [ buffermax - 1 ] ;
2011-04-24 10:24:05 +02:00
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
2013-09-15 21:12:41 +02:00
buffer_recov [ l ] = 0 ; /* no parity to use */
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 */
2013-09-15 21:12:41 +02:00
ret = repair ( state , rehash , i , diskmax , failed , failed_map , failed_count , buffer , buffer_recov , buffer_zero ) ;
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 ;
/* print a list of all the errors in files */
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 )
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
}
2013-03-02 23:49:29 +01:00
/* keep track of damaged files */
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-03-04 20:59:21 +01:00
}
2012-10-07 23:18:10 +02:00
} else {
2026-04-27 15:43:06 +02:00
/*
* Now counts partial recovers
* note that this could happen only when we have an incomplete ' sync '
* and that we have recovered is the state before the ' sync '
*/
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 ) {
2012-10-07 23:18:10 +02:00
if ( failed [ j ] . is_bad & & failed [ j ] . is_outofdate ) {
+ + 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-06-03 23:06:27 +02:00
log_tag ( " parity_error:% " 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
2015-10-07 22:07:20 +02:00
/* now write recovered files */
2012-10-07 23:18:10 +02:00
if ( fix ) {
/* update the fixed files */
2014-09-27 00:32:39 +02:00
for ( j = 0 ; j < failed_count ; + + j ) {
2012-10-07 23:18:10 +02:00
/* nothing to do if it doesn't need recovering */
if ( ! failed [ j ] . is_bad )
continue ;
2011-08-31 23:33:43 +02:00
2014-12-29 08:50:20 +01:00
/* do not fix if the file is excluded */
2015-10-03 10:43:02 +02:00
if ( file_flag_has ( failed [ j ] . file , FILE_IS_EXCLUDED )
| | ( state - > opt . syncedonly & & file_flag_has ( failed [ j ] . file , FILE_IS_UNSYNCED ) ) )
2012-10-07 23:18:10 +02:00
continue ;
2011-05-05 21:12:42 +02:00
2015-10-03 10:43:02 +02:00
ret = handle_write ( failed [ j ] . handle , failed [ j ] . file_pos , buffer [ failed [ j ] . index ] , state - > block_size ) ;
2011-04-23 21:38:26 +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: Write error. %s. \n " , es ( errno ) , i , failed [ j ] . disk - > name , esc_tag ( failed [ j ] . file - > sub ) , strerror ( errno ) ) ;
2026-01-24 19:19:11 +01:00
log_fatal_errno ( errno , failed [ j ] . 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
2012-10-07 23:18:10 +02:00
/* mark the file as damaged */
2015-10-03 10:43:02 +02:00
file_flag_set ( failed [ j ] . file , FILE_IS_DAMAGED ) ;
2012-10-07 23:18:10 +02:00
2011-04-23 21:38:26 +02:00
+ + unrecoverable_error ;
goto bail ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2011-04-23 21:38:26 +02:00
}
2012-10-07 23:18:10 +02:00
/* if we are not sure that the recovered content is uptodate */
if ( failed [ j ] . is_outofdate ) {
/* mark the file as damaged */
2015-10-03 10:43:02 +02:00
file_flag_set ( failed [ j ] . file , FILE_IS_DAMAGED ) ;
2012-10-07 23:18:10 +02:00
continue ;
}
2026-04-27 15:43:06 +02:00
/*
* Mark the file as containing some fixes
* note that it could be also marked as damaged in other iterations
*/
2015-10-03 10:43:02 +02:00
file_flag_set ( failed [ j ] . file , FILE_IS_FIXED ) ;
2012-10-07 23:18:10 +02:00
2026-06-03 23:06:27 +02:00
log_tag ( " fixed:% " PRIu64 " :%s:%s: Fixed data 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
+ + recovered_error ;
}
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 .
*
* For unused parity there is no need to write it , because when fixing
* we already have allocated space for it on parity file creation ,
* and its content doesn ' t matter .
*/
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
) {
2013-09-15 21:12:41 +02:00
ret = parity_write ( parity [ l ] , i , buffer [ diskmax + l ] , state - > block_size ) ;
2013-09-11 23:30:32 +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: 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 fixing , we just set the FIXED flag
* meaning that we could fix this file if we try
*/
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_FIXED ) ;
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 */
ret = file_post ( state , fix , i , handle , diskmax ) ;
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 */
2011-03-04 20:59:21 +01:00
break ;
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 ) ;
}
2011-03-04 20:59:21 +01:00
}
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-06-03 23:06:27 +02:00
log_tag ( " empty_%s:% " PRIu64 " :%s:%s: Create ancestor 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-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-06-03 23:06:27 +02:00
log_tag ( " empty_%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-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-06-03 23:06:27 +02:00
log_tag ( " empty_%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 ) ;
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-06-03 23:06:27 +02:00
log_tag ( " empty_%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 ) ;
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 ;
2020-05-02 14:20:47 +02:00
} else if ( ! unsuccessful & & st . st_ino ! = stto . st_ino ) {
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-06-03 23:06:27 +02:00
log_tag ( " %slink_%s:% " PRIu64 " :%s:%s: Create ancestor error. %s. \n " , link , es ( errno ) , i , 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-06-03 23:06:27 +02:00
log_tag ( " %slink_%s:% " PRIu64 " :%s:%s: Remove error. %s. \n " , link , es ( errno ) , i , 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-06-03 23:06:27 +02:00
log_tag ( " hardlink_%s:% " PRIu64 " :%s:%s: Hardlink error. %s. \n " , es ( errno ) , i , 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-06-03 23:06:27 +02:00
log_tag ( " symlink_%s:% " PRIu64 " :%s:%s: Hardlink error. %s. \n " , es ( errno ) , i , 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-06-03 23:06:27 +02:00
log_tag ( " dir_%s:% " PRIu64 " :%s:%s: Create ancestor error. %s. \n " , es ( errno ) , i , 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-06-03 23:06:27 +02:00
log_tag ( " dir_%s:% " PRIu64 " :%s:%s: Create directory error. %s. \n " , es ( errno ) , i , 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-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-04-27 15:43:06 +02:00
/*
* Remove all the files created from scratch that have not finished the processing
* it happens only when aborting pressing Ctrl + C or other reason .
*/
2013-05-31 23:34:59 +02:00
if ( fix ) {
/* for each disk */
2014-09-27 00:32:39 +02:00
for ( i = 0 ; i < diskmax ; + + i ) {
2013-05-31 23:34:59 +02:00
tommy_node * node ;
struct snapraid_disk * disk ;
2013-04-28 19:01:36 +02:00
2013-05-31 23:34:59 +02:00
if ( ! handle [ i ] . disk )
continue ;
/* for each file in the disk */
disk = handle [ i ] . disk ;
node = disk - > filelist ;
while ( node ) {
2013-04-28 19:01:36 +02:00
char path [ PATH_MAX ] ;
2013-05-31 23:34:59 +02:00
struct snapraid_file * file ;
file = node - > data ;
node = node - > next ; /* next node */
/* if the file was not created, meaning that it was already existing */
if ( ! file_flag_has ( file , FILE_IS_CREATED ) ) {
/* nothing to do */
continue ;
}
2013-04-28 19:01:36 +02:00
2013-05-31 23:34:59 +02:00
/* if processing was finished */
if ( file_flag_has ( file , FILE_IS_FINISHED ) ) {
/* nothing to do */
continue ;
}
2026-04-27 15:43:06 +02:00
/*
* If the file was originally missing , and processing not yet finished
* we have to throw it away to ensure that at the next run we will retry
* to fix it , in case we select to undelete missing files
*/
2013-05-31 23:34:59 +02:00
pathprint ( path , sizeof ( path ) , " %s%s " , disk - > dir , file - > sub ) ;
2013-04-28 19:01:36 +02:00
2013-06-01 08:31:47 +02:00
ret = remove ( path ) ;
2013-04-28 19:01: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 removing '%s'. %s. \n " , path , strerror ( errno ) ) ;
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-04-28 19:01:36 +02:00
+ + unrecoverable_error ;
/* continue, as we are already exiting */
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-04-28 19:01:36 +02:00
}
}
2011-03-18 22:38:11 +01: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 ) ;
2013-10-19 13:24:35 +02:00
if ( fix )
2015-02-22 14:31:03 +01:00
msg_status ( " Everything OK \n " ) ;
2012-10-07 23:18:10 +02:00
}
2011-03-04 20:59:21 +01:00
} else {
2015-02-22 14:31:03 +01:00
msg_status ( " Everything OK \n " ) ;
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 ) ;
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 ;
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
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 ) {
/* open for reading, and ignore error */
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 */
}
ret = parity_chsize ( parity_ptr [ l ] , & state - > parity [ l ] , 0 , size , state - > block_size , state - > opt . skip_fallocate , state - > opt . skip_space_holder ) ;
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 ) {
2014-02-12 22:04:14 +01:00
ret = state_check_process ( state , fix , parity_ptr , blockstart , blockmax ) ;
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 ] ) {
2019-05-21 22:33:24 +02:00
/* if fixing and not excluded, truncate parity not valid */
if ( fix & & ! state - > parity [ l ] . is_excluded_by_filter ) {
ret = parity_truncate ( parity_ptr [ l ] ) ;
if ( ret = = - 1 ) {
/* LCOV_EXCL_START */
2026-06-03 23:06:27 +02:00
log_tag ( " parity_%s:% " PRIu64 " :%s: Truncate 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 ;
2019-05-21 22:33:24 +02:00
/* continue, as we are already exiting */
/* LCOV_EXCL_STOP */
}
}
2013-09-15 21:12:41 +02:00
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 ;
return 0 ;
2011-03-19 22:00:08 +01:00
}
2025-10-26 18:06:08 +01:00