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
# include "portable.h"
2015-01-02 18:52:04 +01:00
# include "support.h"
2011-03-04 20:59:21 +01:00
# include "elem.h"
# include "state.h"
# include "parity.h"
# include "handle.h"
2015-12-30 23:45:11 +01:00
# include "io.h"
2013-12-28 09:10:00 +01:00
# include "raid/raid.h"
2011-03-04 20:59:21 +01:00
2014-02-02 23:03:37 +01:00
/****************************************************************************/
/* hash */
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 " ;
}
2014-12-27 23:14:06 +01:00
static int state_hash_process ( struct snapraid_state * state , block_off_t blockstart , block_off_t blockmax , int * skip_sync )
2014-02-02 23:03:37 +01:00
{
struct snapraid_handle * handle ;
unsigned diskmax ;
2026-02-09 23:49:02 +01:00
block_off_t blockcur ;
2014-02-02 23:03:37 +01:00
unsigned j ;
void * buffer ;
2016-05-15 14:18:44 +02:00
void * buffer_alloc ;
2014-02-02 23:03:37 +01:00
data_off_t countsize ;
block_off_t countpos ;
block_off_t countmax ;
int ret ;
2026-01-25 21:25:03 +01:00
unsigned soft_error ;
2016-01-10 22:04:16 +01:00
unsigned silent_error ;
2014-12-27 23:14:06 +01:00
unsigned io_error ;
2014-02-02 23:03:37 +01:00
/* maps the disks to handles */
2016-01-18 22:38:01 +01:00
handle = handle_mapping ( state , & diskmax ) ;
2014-02-02 23:03:37 +01:00
/* buffer for reading */
2016-05-15 14:18:44 +02:00
buffer = malloc_nofail_direct ( state - > block_size , & buffer_alloc ) ;
2014-02-03 19:05:22 +01:00
if ( ! state - > opt . skip_self )
mtest_vector ( 1 , state - > block_size , & buffer ) ;
2014-02-02 23:03:37 +01:00
2026-01-25 21:25:03 +01:00
soft_error = 0 ;
2016-01-10 22:04:16 +01:00
silent_error = 0 ;
2014-12-27 23:14:06 +01:00
io_error = 0 ;
2014-02-02 23:03:37 +01:00
/* first count the number of blocks to process */
countmax = 0 ;
2014-09-27 00:32:39 +02:00
for ( j = 0 ; j < diskmax ; + + j ) {
2014-02-02 23:03:37 +01:00
struct snapraid_disk * disk = handle [ j ] . disk ;
2014-12-29 19:11:03 +01:00
/* if no disk, nothing to check */
2014-02-02 23:03:37 +01:00
if ( ! disk )
continue ;
2014-04-04 23:06:00 +02:00
2026-02-09 23:49:02 +01:00
for ( blockcur = blockstart ; blockcur < blockmax ; + + blockcur ) {
2014-02-02 23:03:37 +01:00
struct snapraid_block * block ;
2015-09-29 22:31:13 +02:00
unsigned block_state ;
2014-02-02 23:03:37 +01:00
2026-02-09 23:49:02 +01:00
block = fs_par2block_find ( disk , blockcur ) ;
2014-02-02 23:03:37 +01:00
2015-09-29 22:31:13 +02:00
/* get the state of the block */
block_state = block_state_get ( block ) ;
2014-02-02 23:03:37 +01:00
2015-09-29 22:31:13 +02:00
/* process REP and CHG blocks */
if ( block_state ! = BLOCK_STATE_REP & & block_state ! = BLOCK_STATE_CHG )
2014-02-02 23:03:37 +01:00
continue ;
+ + countmax ;
}
}
2014-06-25 22:32:36 +02:00
/* drop until now */
state_usage_waste ( state ) ;
2014-02-02 23:03:37 +01:00
countsize = 0 ;
countpos = 0 ;
2026-02-09 23:49:02 +01:00
blockcur = blockstart ;
int alert = state_progress_begin ( state , blockstart , blockmax , countmax ) ;
if ( alert > 0 )
2014-09-27 00:23:05 +02:00
goto end ;
2026-02-09 23:49:02 +01:00
if ( alert < 0 )
goto bail ;
2014-09-27 00:23:05 +02:00
2014-09-27 00:32:39 +02:00
for ( j = 0 ; j < diskmax ; + + j ) {
2014-02-02 23:03:37 +01:00
struct snapraid_disk * disk = handle [ j ] . disk ;
2014-12-29 19:11:03 +01:00
/* if no disk, nothing to check */
2014-02-02 23:03:37 +01:00
if ( ! disk )
continue ;
2026-02-09 23:49:02 +01:00
for ( blockcur = blockstart ; blockcur < blockmax ; + + blockcur ) {
2014-02-02 23:03:37 +01:00
snapraid_info info ;
int rehash ;
struct snapraid_block * block ;
int read_size ;
2016-04-07 21:39:02 +02:00
unsigned char hash [ HASH_MAX ] ;
2015-09-29 22:31:13 +02:00
unsigned block_state ;
2015-10-03 10:43:02 +02:00
struct snapraid_file * file ;
block_off_t file_pos ;
2014-02-02 23:03:37 +01:00
2026-02-09 23:49:02 +01:00
block = fs_par2block_find ( disk , blockcur ) ;
2014-02-02 23:03:37 +01:00
2015-09-29 22:31:13 +02:00
/* get the state of the block */
block_state = block_state_get ( block ) ;
2014-02-02 23:03:37 +01:00
2015-09-29 22:31:13 +02:00
/* process REP and CHG blocks */
if ( block_state ! = BLOCK_STATE_REP & & block_state ! = BLOCK_STATE_CHG )
2014-02-02 23:03:37 +01:00
continue ;
2015-10-03 10:43:02 +02:00
/* get the file of this block */
2026-02-09 23:49:02 +01:00
file = fs_par2file_get ( disk , blockcur , & file_pos ) ;
2015-10-03 10:43:02 +02:00
2014-02-02 23:03:37 +01:00
/* get block specific info */
2026-02-09 23:49:02 +01:00
info = info_get ( & state - > infoarr , blockcur ) ;
2014-02-02 23:03:37 +01:00
/* if we have to use the old hash */
rehash = info_get_rehash ( info ) ;
2016-01-26 23:25:01 +01:00
/* until now is misc */
state_usage_misc ( state ) ;
2014-06-25 22:32:36 +02:00
2014-02-02 23:03:37 +01:00
/* if the file is different than the current one, close it */
2015-10-03 10:43:02 +02:00
if ( handle [ j ] . file ! = 0 & & handle [ j ] . file ! = file ) {
2014-12-27 23:14:06 +01:00
/* keep a pointer at the file we are going to close for error reporting */
2015-10-03 10:43:02 +02:00
struct snapraid_file * report = handle [ j ] . file ;
2014-02-02 23:03:37 +01:00
ret = handle_close ( & handle [ j ] ) ;
if ( ret = = - 1 ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-04-27 15:43:06 +02:00
/*
* This one is really an unexpected error , because we are only reading
* and closing a descriptor should never fail
*/
2026-05-29 11:01:44 +02:00
log_tag ( " %s:%u:%s:%s: Close error. %s. \n " , es ( errno ) , blockcur , disk - > name , esc_tag ( report - > sub ) , strerror ( errno ) ) ;
2026-01-24 19:19:11 +01:00
log_fatal_errno ( errno , disk - > name ) ;
2026-02-09 23:49:02 +01:00
log_fatal ( errno , " Stopping at block %u \n " , blockcur ) ;
2026-01-24 19:19:11 +01:00
2026-02-22 14:58:06 +01:00
if ( is_hw ( errno ) ) {
2014-12-27 23:14:06 +01:00
+ + io_error ;
2026-01-24 19:19:11 +01:00
} else {
2026-01-25 21:25:03 +01:00
+ + soft_error ;
2014-12-27 23:14:06 +01:00
}
2014-02-02 23:03:37 +01:00
goto bail ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2014-02-02 23:03:37 +01:00
}
}
2026-06-03 20:49:36 +02:00
ret = handle_open ( & handle [ j ] , file , state - > file_mode , 0 ) ;
2014-02-02 23:03:37 +01:00
if ( ret = = - 1 ) {
2026-05-29 11:01:44 +02:00
log_tag ( " %s:%u:%s:%s: Open error. %s. \n " , es ( errno ) , blockcur , disk - > name , esc_tag ( file - > sub ) , strerror ( errno ) ) ;
2014-02-02 23:03:37 +01:00
if ( errno = = ENOENT ) {
2026-01-24 19:19:11 +01:00
log_error_errno ( errno , disk - > name ) ;
2026-01-25 21:25:03 +01:00
+ + soft_error ;
2026-04-27 15:43:06 +02:00
/*
* If the file is missing , it means that it was removed during sync
* this isn ' t a serious error , so we skip this block , and continue with others
*/
2014-02-02 23:03:37 +01:00
continue ;
2014-04-07 23:12:40 +02:00
}
2014-12-27 23:14:06 +01:00
2014-04-07 23:12:40 +02:00
if ( errno = = EACCES ) {
2026-01-24 19:19:11 +01:00
log_error_errno ( errno , disk - > name ) ;
2026-01-25 21:25:03 +01:00
+ + soft_error ;
2014-02-02 23:03:37 +01:00
/* this isn't a serious error, so we skip this block, and continue with others */
continue ;
}
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-01-24 19:19:11 +01:00
log_fatal_errno ( errno , disk - > name ) ;
2026-02-22 14:58:06 +01:00
if ( is_hw ( errno ) ) {
2026-02-09 23:49:02 +01:00
log_fatal ( errno , " Stopping at block %u \n " , blockcur ) ;
2026-01-24 19:19:11 +01:00
+ + io_error ;
} else {
2026-05-29 11:01:44 +02:00
log_fatal ( errno , " Stopping to allow recovery. Try with 'snapraid check -f /%s' \n " , fmt_poll ( disk , file - > sub ) ) ;
2026-01-25 21:25:03 +01:00
+ + soft_error ;
2026-01-24 19:19:11 +01:00
}
2014-02-02 23:03:37 +01:00
goto bail ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2014-02-02 23:03:37 +01:00
}
/* 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
| | handle [ j ] . st . st_ino ! = file - > inode
2014-02-02 23:03:37 +01:00
) {
2014-04-04 23:06:00 +02:00
if ( handle [ j ] . st . st_size ! = file - > size ) {
2026-05-29 11:01:44 +02:00
log_tag ( " error:%u:%s:%s: Unexpected size change \n " , blockcur , disk - > name , esc_tag ( file - > sub ) ) ;
2026-02-01 20:21:28 +01:00
log_error ( ESOFT , " Unexpected size change at file '%s' from % " PRIu64 " to % " PRIu64 " . \n " , handle [ j ] . path , file - > size , ( uint64_t ) handle [ j ] . st . st_size ) ;
2014-04-04 23:06:00 +02:00
} else if ( handle [ j ] . st . st_mtime ! = file - > mtime_sec
| | STAT_NSEC ( & handle [ j ] . st ) ! = file - > mtime_nsec ) {
2026-05-29 11:01:44 +02:00
log_tag ( " error:%u:%s:%s: Unexpected time change \n " , blockcur , disk - > name , esc_tag ( file - > sub ) ) ;
2026-02-01 20:21:28 +01:00
log_error ( ESOFT , " Unexpected time change at file '%s' from % " PRIu64 " .%d to % " PRIu64 " .%d. \n " , handle [ j ] . path , file - > mtime_sec , file - > mtime_nsec , ( uint64_t ) handle [ j ] . st . st_mtime , STAT_NSEC ( & handle [ j ] . st ) ) ;
2014-02-02 23:03:37 +01:00
} else {
2026-05-29 11:01:44 +02:00
log_tag ( " error:%u:%s:%s: Unexpected inode change \n " , blockcur , disk - > name , esc_tag ( file - > sub ) ) ;
2026-02-01 20:21:28 +01:00
log_error ( ESOFT , " Unexpected inode change from % " PRIu64 " to % " PRIu64 " at file '%s'. \n " , file - > inode , ( uint64_t ) handle [ j ] . st . st_ino , handle [ j ] . path ) ;
2014-02-02 23:03:37 +01:00
}
2026-01-24 19:19:11 +01:00
log_error_errno ( ENOENT , disk - > name ) ; /* same message for ENOENT */
2026-01-25 21:25:03 +01:00
+ + soft_error ;
2026-01-24 19:19:11 +01:00
2026-04-27 15:43:06 +02:00
/*
* If the file is changed , it means that it was modified during sync
* this isn ' t a serious error , so we skip this block , and continue with others
*/
2014-02-02 23:03:37 +01:00
continue ;
}
2026-06-03 20:49:36 +02:00
read_size = handle_read ( & handle [ j ] , file_pos , buffer , state - > block_size , 0 ) ;
2014-02-02 23:03:37 +01:00
if ( read_size = = - 1 ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-05-29 11:01:44 +02:00
log_tag ( " %s:%u:%s:%s: Read error at position %u. %s. \n " , es ( errno ) , blockcur , disk - > name , esc_tag ( file - > sub ) , file_pos , strerror ( errno ) ) ;
2026-01-24 19:19:11 +01:00
log_fatal_errno ( errno , disk - > name ) ;
2026-02-22 14:58:06 +01:00
if ( is_hw ( errno ) ) {
2026-02-09 23:49:02 +01:00
log_fatal ( errno , " Stopping at block %u \n " , blockcur ) ;
2014-12-27 23:14:06 +01:00
+ + io_error ;
2026-01-24 19:19:11 +01:00
} else {
2026-05-29 11:01:44 +02:00
log_fatal ( errno , " Stopping to allow recovery. Try with 'snapraid check -f /%s' \n " , fmt_poll ( disk , file - > sub ) ) ;
2026-01-25 21:25:03 +01:00
+ + soft_error ;
2014-12-27 23:14:06 +01:00
}
2014-02-02 23:03:37 +01:00
goto bail ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2014-02-02 23:03:37 +01:00
}
2014-06-25 22:32:36 +02:00
/* until now is disk */
2016-01-20 21:30:00 +01:00
state_usage_disk ( state , handle , & j , 1 ) ;
2014-06-25 22:32:36 +02:00
2017-11-19 19:33:47 +01:00
state_usage_file ( state , disk , file ) ;
2014-02-02 23:03:37 +01:00
countsize + = read_size ;
/* now compute the hash */
if ( rehash ) {
memhash ( state - > prevhash , state - > prevhashseed , hash , buffer , read_size ) ;
} else {
memhash ( state - > hash , state - > hashseed , hash , buffer , read_size ) ;
}
2016-01-26 23:25:01 +01:00
/* until now is hash */
state_usage_hash ( state ) ;
2015-09-29 22:31:13 +02:00
if ( block_state = = BLOCK_STATE_REP ) {
/* compare the hash */
2016-04-07 21:39:02 +02:00
if ( memcmp ( hash , block - > hash , BLOCK_HASH_SIZE ) ! = 0 ) {
2026-05-29 11:01:44 +02:00
log_tag ( " error_data:%u:%s:%s: Unexpected data change \n " , blockcur , disk - > name , esc_tag ( file - > sub ) ) ;
2026-02-01 20:21:28 +01:00
log_error ( EDATA , " Data change at file '%s' at position '%u' \n " , handle [ j ] . path , file_pos ) ;
log_error ( EDATA , " WARNING! Unexpected data modification of a file without parity! \n " ) ;
2015-09-29 22:31:13 +02:00
if ( file_flag_has ( file , FILE_IS_COPY ) ) {
2026-02-01 20:21:28 +01:00
log_error ( EDATA , " This file was detected as a copy of another file with the same name, size, \n " ) ;
log_error ( EDATA , " and timestamp, but the file data isn't matching the assumed copy. \n " ) ;
log_error ( EDATA , " If this is a false positive, and the files are expected to be different, \n " ) ;
log_error ( EDATA , " you can 'sync' anyway using 'snapraid --force-nocopy sync' \n " ) ;
2015-09-29 22:31:13 +02:00
} else {
2026-02-01 20:21:28 +01:00
log_error ( EDATA , " Try removing the file from the array and rerun the 'sync' command! \n " ) ;
2015-09-29 22:31:13 +02:00
}
2014-02-02 23:03:37 +01:00
2016-01-10 22:04:16 +01:00
+ + silent_error ;
2015-09-29 22:31:13 +02:00
continue ;
}
} else {
/* the only other case is BLOCK_STATE_CHG */
assert ( block_state = = BLOCK_STATE_CHG ) ;
/* copy the hash in the block */
2016-04-07 21:39:02 +02:00
memcpy ( block - > hash , hash , BLOCK_HASH_SIZE ) ;
2015-09-29 22:31:13 +02:00
/* and mark the block as hashed */
block_state_set ( block , BLOCK_STATE_REP ) ;
/* mark the state as needing write */
state - > need_write = 1 ;
}
2014-02-02 23:03:37 +01:00
/* count the number of processed block */
+ + countpos ;
/* progress */
2026-02-09 23:49:02 +01:00
if ( state_progress ( state , 0 , blockcur , countpos , countmax , countsize ) ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-05-21 13:01:50 +02:00
* skip_sync = 1 ; /* avoid to run the next sync due user interruption */
2014-02-02 23:03:37 +01:00
break ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2014-02-02 23:03:37 +01:00
}
}
/* close the last file in the disk */
if ( handle [ j ] . file ! = 0 ) {
2014-12-27 23:14:06 +01:00
/* keep a pointer at the file we are going to close for error reporting */
2015-10-03 10:43:02 +02:00
struct snapraid_file * report = handle [ j ] . file ;
2014-02-02 23:03:37 +01:00
ret = handle_close ( & handle [ j ] ) ;
if ( ret = = - 1 ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-04-27 15:43:06 +02:00
/*
* This one is really an unexpected error , because we are only reading
* and closing a descriptor should never fail
*/
2026-05-29 11:01:44 +02:00
log_tag ( " %s:%u:%s:%s: Close error. %s. \n " , es ( errno ) , blockmax , disk - > name , esc_tag ( report - > 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 at block %u \n " , blockmax ) ;
2026-01-24 19:19:11 +01:00
2026-02-22 14:58:06 +01:00
if ( is_hw ( errno ) ) {
2014-12-27 23:14:06 +01:00
+ + io_error ;
2026-01-24 19:19:11 +01:00
} else {
2026-01-25 21:25:03 +01:00
+ + soft_error ;
2014-12-27 23:14:06 +01:00
}
2014-02-02 23:03:37 +01:00
goto bail ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2014-02-02 23:03:37 +01:00
}
}
}
2014-09-27 00:23:05 +02:00
end :
2025-10-13 11:46:36 +02:00
state_progress_end ( state , countpos , countmax , countsize , " Nothing to hash. \n " ) ;
2014-02-02 23:03:37 +01:00
2026-04-27 15:43:06 +02:00
/*
* Note that at this point no io_error is possible
* because at the first one we bail out
*/
2015-03-26 19:37:21 +01:00
assert ( io_error = = 0 ) ;
2026-01-25 21:25:03 +01:00
if ( soft_error | | io_error | | silent_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 ) ;
2016-01-10 22:04:16 +01:00
msg_status ( " %8u io errors \n " , io_error ) ;
msg_status ( " %8u data errors \n " , silent_error ) ;
2014-02-02 23:03:37 +01:00
} else {
2026-02-08 21:18:19 +01:00
msg_status ( " Everything OK \n " ) ;
2014-02-02 23:03:37 +01:00
}
2026-01-25 21:25:03 +01:00
if ( soft_error )
2026-02-01 20:21:28 +01:00
log_fatal ( ESOFT , " WARNING! Unexpected soft errors! \n " ) ;
2015-02-22 14:31:03 +01:00
2026-01-25 21:25:03 +01:00
log_tag ( " hash_summary:error_soft:%u \n " , soft_error ) ;
2014-02-02 23:03:37 +01:00
2014-12-27 23:14:06 +01:00
/* proceed without bailing out */
goto finish ;
2014-02-02 23:03:37 +01:00
bail :
2014-12-27 23:14:06 +01:00
/* close files left open */
2014-09-27 00:32:39 +02:00
for ( j = 0 ; j < diskmax ; + + j ) {
2015-02-03 18:12:52 +01:00
struct snapraid_file * file = handle [ j ] . file ;
struct snapraid_disk * disk = handle [ j ] . disk ;
2014-02-02 23:03:37 +01:00
ret = handle_close ( & handle [ j ] ) ;
if ( ret = = - 1 ) {
2026-01-13 23:45:31 +01:00
/* LCOV_EXCL_START */
2026-05-29 11:01:44 +02:00
log_tag ( " %s:%u:%s:%s: Close error. %s. \n " , es ( errno ) , blockcur , disk - > name , esc_tag ( file - > sub ) , strerror ( errno ) ) ;
2026-01-24 19:19:11 +01:00
log_fatal_errno ( errno , disk - > name ) ;
2026-02-22 14:58:06 +01:00
if ( is_hw ( errno ) ) {
2026-01-13 23:45:31 +01:00
+ + io_error ;
} else {
2026-01-25 21:25:03 +01:00
+ + soft_error ;
2026-01-13 23:45:31 +01:00
}
2014-02-02 23:03:37 +01:00
/* continue, as we are already exiting */
2026-01-13 23:45:31 +01:00
/* LCOV_EXCL_STOP */
2014-02-02 23:03:37 +01:00
}
}
2014-12-27 23:14:06 +01:00
finish :
2014-02-02 23:03:37 +01:00
free ( handle ) ;
2016-05-15 14:18:44 +02:00
free ( buffer_alloc ) ;
2014-02-08 16:49:24 +01:00
2026-01-25 21:25:03 +01:00
if ( soft_error + io_error + silent_error ! = 0 )
2014-12-27 23:14:06 +01:00
return - 1 ;
2026-02-09 23:49:02 +01:00
if ( alert < 0 )
return - 1 ;
2014-12-27 23:14:06 +01:00
return 0 ;
2014-02-02 23:03:37 +01:00
}
2011-03-04 20:59:21 +01:00
/****************************************************************************/
/* sync */
2016-01-10 23:00:11 +01:00
/**
* Sync plan to use .
*/
struct snapraid_plan {
unsigned handle_max ;
struct snapraid_handle * handle_map ;
2016-10-01 22:19:29 +02:00
int force_full ;
2016-01-10 23:00:11 +01:00
} ;
2014-01-14 22:10:47 +01:00
/**
* A block that failed the hash check , or that was deleted .
*/
struct failed_struct {
unsigned index ; /**< Index of the failed block. */
unsigned size ; /**< Size of the block. */
struct snapraid_block * block ; /**< The failed block, or BLOCK_DELETED for a deleted block */
} ;
2015-12-30 23:45:11 +01:00
/**
* Comparison function for sorting by index .
*/
int failed_compare_by_index ( const void * void_a , const void * void_b )
{
const struct failed_struct * a = void_a ;
const struct failed_struct * b = void_b ;
if ( a - > index < b - > index )
return - 1 ;
if ( a - > index > b - > index )
return 1 ;
return 0 ;
}
2013-07-01 23:08:25 +02:00
/**
* Buffer for storing the new hashes .
*/
struct snapraid_rehash {
2016-04-07 21:39:02 +02:00
unsigned char hash [ HASH_MAX ] ;
2013-07-01 23:08:25 +02:00
struct snapraid_block * block ;
} ;
2014-12-29 19:11:03 +01:00
/**
* Check if we have to process the specified block index : : i .
*/
2021-05-22 11:37:36 +02:00
static int block_is_enabled ( struct snapraid_plan * plan , block_off_t i )
2014-12-29 19:11:03 +01:00
{
unsigned j ;
int one_invalid ;
int one_valid ;
/* for each disk */
one_invalid = 0 ;
one_valid = 0 ;
2015-12-30 23:45:11 +01:00
for ( j = 0 ; j < plan - > handle_max ; + + j ) {
2014-12-29 19:11:03 +01:00
struct snapraid_block * block ;
2015-12-30 23:45:11 +01:00
struct snapraid_disk * disk = plan - > handle_map [ j ] . disk ;
2014-12-29 19:11:03 +01:00
/* if no disk, nothing to check */
2015-12-30 23:45:11 +01:00
if ( ! disk )
2014-12-29 19:11:03 +01:00
continue ;
2016-01-16 14:26:05 +01:00
block = fs_par2block_find ( disk , i ) ;
2014-12-29 19:11:03 +01:00
if ( block_has_file ( block ) )
one_valid = 1 ;
2016-10-01 22:19:29 +02:00
if ( block_has_invalid_parity ( block ) | | plan - > force_full )
2014-12-29 19:11:03 +01:00
one_invalid = 1 ;
}
/* if none valid or none invalid, we don't need to update */
if ( ! one_invalid | | ! one_valid )
return 0 ;
return 1 ;
}
2015-12-30 23:45:11 +01:00
static void sync_data_reader ( struct snapraid_worker * worker , struct snapraid_task * task )
{
struct snapraid_io * io = worker - > io ;
struct snapraid_state * state = io - > state ;
struct snapraid_handle * handle = worker - > handle ;
struct snapraid_disk * disk = handle - > disk ;
block_off_t blockcur = task - > position ;
unsigned char * buffer = task - > buffer ;
int ret ;
/* if the disk position is not used */
if ( ! disk ) {
/* use an empty block */
memset ( buffer , 0 , state - > block_size ) ;
task - > state = TASK_STATE_DONE ;
return ;
}
/* get the block */
2016-01-16 14:26:05 +01:00
task - > block = fs_par2block_find ( disk , blockcur ) ;
2015-12-30 23:45:11 +01:00
2026-04-27 15:43:06 +02:00
/*
* If the block has no file , meaning that it ' s EMPTY or DELETED ,
* it doesn ' t participate in the new parity computation
*/
2015-12-30 23:45:11 +01:00
if ( ! block_has_file ( task - > block ) ) {
/* use an empty block */
memset ( buffer , 0 , state - > block_size ) ;
task - > state = TASK_STATE_DONE ;
return ;
}
/* get the file of this block */
2016-01-01 10:00:18 +01:00
task - > file = fs_par2file_get ( disk , blockcur , & task - > file_pos ) ;
2015-12-30 23:45:11 +01:00
/* if the file is different than the current one, close it */
if ( handle - > file ! = 0 & & handle - > file ! = task - > file ) {
/* keep a pointer at the file we are going to close for error reporting */
struct snapraid_file * report = handle - > file ;
ret = handle_close ( handle ) ;
if ( ret = = - 1 ) {
/* LCOV_EXCL_START */
2026-04-27 15:43:06 +02:00
/*
* This one is really an unexpected error , because we are only reading
* and closing a descriptor should never fail
*/
2026-05-29 11:01:44 +02:00
log_tag ( " %s:%u:%s:%s: Close error. %s. \n " , es ( errno ) , blockcur , disk - > name , esc_tag ( report - > 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 at block %u \n " , blockcur ) ;
2026-01-24 19:19:11 +01:00
2026-02-22 14:58:06 +01:00
if ( is_hw ( errno ) ) {
2015-12-30 23:45:11 +01:00
task - > state = TASK_STATE_IOERROR ;
2026-01-24 19:19:11 +01:00
} else {
task - > state = TASK_STATE_ERROR ;
2015-12-30 23:45:11 +01:00
}
return ;
/* LCOV_EXCL_STOP */
}
}
2026-06-03 20:49:36 +02:00
ret = handle_open ( handle , task - > file , state - > file_mode , 0 ) ;
2015-12-30 23:45:11 +01:00
if ( ret = = - 1 ) {
2026-05-29 11:01:44 +02:00
log_tag ( " %s:%u:%s:%s: Open error. %s. \n " , es ( errno ) , blockcur , disk - > name , esc_tag ( task - > file - > sub ) , strerror ( errno ) ) ;
2015-12-30 23:45:11 +01:00
if ( errno = = ENOENT ) {
2026-01-24 19:19:11 +01:00
log_error_errno ( errno , disk - > name ) ;
2026-04-27 15:43:06 +02:00
/*
* If the file is missing , it means that it was removed during sync
* this isn ' t a serious error , so we skip this block , and continue with others
*/
2015-12-30 23:45:11 +01:00
task - > state = TASK_STATE_ERROR_CONTINUE ;
return ;
}
if ( errno = = EACCES ) {
2026-01-24 19:19:11 +01:00
log_error_errno ( errno , disk - > name ) ;
2015-12-30 23:45:11 +01:00
/* this isn't a serious error, so we skip this block, and continue with others */
task - > state = TASK_STATE_ERROR_CONTINUE ;
return ;
}
/* LCOV_EXCL_START */
2026-01-24 19:19:11 +01:00
log_fatal_errno ( errno , disk - > name ) ;
2026-02-22 14:58:06 +01:00
if ( is_hw ( errno ) ) {
2026-02-01 20:21:28 +01:00
log_fatal ( errno , " Stopping at block %u \n " , blockcur ) ;
2026-01-24 19:19:11 +01:00
task - > state = TASK_STATE_IOERROR ;
} else {
2026-05-29 11:01:44 +02:00
log_fatal ( errno , " Stopping to allow recovery. Try with 'snapraid check -f /%s' \n " , fmt_poll ( disk , task - > file - > sub ) ) ;
2026-01-24 19:19:11 +01:00
task - > state = TASK_STATE_ERROR ;
}
2015-12-30 23:45:11 +01:00
return ;
/* LCOV_EXCL_STOP */
}
/* check if the file is changed */
if ( handle - > st . st_size ! = task - > file - > size
| | handle - > st . st_mtime ! = task - > file - > mtime_sec
| | STAT_NSEC ( & handle - > st ) ! = task - > file - > mtime_nsec
| | handle - > st . st_ino ! = task - > file - > inode
) {
2026-05-29 11:01:44 +02:00
log_tag ( " error:%u:%s:%s: Unexpected attribute change \n " , blockcur , disk - > name , esc_tag ( task - > file - > sub ) ) ;
2015-12-30 23:45:11 +01:00
if ( handle - > st . st_size ! = task - > file - > size ) {
2026-02-01 20:21:28 +01:00
log_error ( ESOFT , " Unexpected size change at file '%s' from % " PRIu64 " to % " PRIu64 " . \n " , handle - > path , task - > file - > size , ( uint64_t ) handle - > st . st_size ) ;
2015-12-30 23:45:11 +01:00
} else if ( handle - > st . st_mtime ! = task - > file - > mtime_sec
| | STAT_NSEC ( & handle - > st ) ! = task - > file - > mtime_nsec ) {
2026-02-01 20:21:28 +01:00
log_error ( ESOFT , " Unexpected time change at file '%s' from % " PRIu64 " .%d to % " PRIu64 " .%d. \n " , handle - > path , task - > file - > mtime_sec , task - > file - > mtime_nsec , ( uint64_t ) handle - > st . st_mtime , STAT_NSEC ( & handle - > st ) ) ;
2015-12-30 23:45:11 +01:00
} else {
2026-02-01 20:21:28 +01:00
log_error ( ESOFT , " Unexpected inode change from % " PRIu64 " to % " PRIu64 " at file '%s'. \n " , task - > file - > inode , ( uint64_t ) handle - > st . st_ino , handle - > path ) ;
2015-12-30 23:45:11 +01:00
}
2026-01-24 19:19:11 +01:00
log_error_errno ( ENOENT , disk - > name ) ; /* same message for ENOENT */
2026-04-27 15:43:06 +02:00
/*
* If the file is changed , it means that it was modified during sync
* this isn ' t a serious error , so we skip this block , and continue with others
*/
2015-12-30 23:45:11 +01:00
task - > state = TASK_STATE_ERROR_CONTINUE ;
return ;
}
2026-06-03 20:49:36 +02:00
task - > read_size = handle_read ( handle , task - > file_pos , buffer , state - > block_size , 0 ) ;
2015-12-30 23:45:11 +01:00
if ( task - > read_size = = - 1 ) {
/* LCOV_EXCL_START */
2026-05-29 11:01:44 +02:00
log_tag ( " %s:%u:%s:%s: Read error at position %u. %s. \n " , es ( errno ) , blockcur , disk - > name , esc_tag ( task - > file - > sub ) , task - > file_pos , strerror ( errno ) ) ;
2026-01-24 19:19:11 +01:00
2026-02-22 14:58:06 +01:00
if ( is_hw ( errno ) ) {
2026-05-30 17:54:31 +02:00
log_fatal_errno ( errno , disk - > name ) ;
2026-01-24 19:19:11 +01:00
/* continue until the error limit is reached */
2015-12-30 23:45:11 +01:00
task - > state = TASK_STATE_IOERROR_CONTINUE ;
2026-01-24 19:19:11 +01:00
} else {
log_fatal_errno ( errno , disk - > name ) ;
2026-05-29 11:01:44 +02:00
log_fatal ( errno , " Stopping to allow recovery. Try with 'snapraid check -f /%s' \n " , fmt_poll ( disk , task - > file - > sub ) ) ;
2026-01-24 19:19:11 +01:00
task - > state = TASK_STATE_ERROR ;
2015-12-30 23:45:11 +01:00
}
return ;
/* LCOV_EXCL_STOP */
}
/* store the path of the opened file */
pathcpy ( task - > path , sizeof ( task - > path ) , handle - > path ) ;
task - > state = TASK_STATE_DONE ;
}
2016-01-04 11:28:34 +01:00
static void sync_parity_writer ( struct snapraid_worker * worker , struct snapraid_task * task )
{
struct snapraid_io * io = worker - > io ;
struct snapraid_state * state = io - > state ;
struct snapraid_parity_handle * parity_handle = worker - > parity_handle ;
unsigned level = parity_handle - > level ;
block_off_t blockcur = task - > position ;
unsigned char * buffer = task - > buffer ;
int ret ;
/* write parity */
ret = parity_write ( parity_handle , blockcur , buffer , state - > block_size ) ;
if ( ret = = - 1 ) {
/* LCOV_EXCL_START */
2026-01-24 19:19:11 +01:00
log_tag ( " parity_%s:%u:%s: Write error. %s. \n " , es ( errno ) , blockcur , lev_config_name ( level ) , strerror ( errno ) ) ;
2026-02-22 14:58:06 +01:00
if ( is_hw ( errno ) ) {
2026-05-30 17:54:31 +02:00
log_fatal_errno ( errno , lev_config_name ( level ) ) ;
2026-01-24 19:19:11 +01:00
/* continue until the error limit is reached */
2016-01-04 11:28:34 +01:00
task - > state = TASK_STATE_IOERROR_CONTINUE ;
2026-01-24 19:19:11 +01:00
} else {
log_fatal_errno ( errno , lev_config_name ( level ) ) ;
2026-02-01 20:21:28 +01:00
log_fatal ( errno , " Stopping at block %u \n " , blockcur ) ;
2026-01-24 19:19:11 +01:00
task - > state = TASK_STATE_ERROR ;
2016-01-04 11:28:34 +01:00
}
return ;
/* LCOV_EXCL_STOP */
}
task - > state = TASK_STATE_DONE ;
}
2016-01-04 11:32:02 +01:00
static int state_sync_process ( struct snapraid_state * state , struct snapraid_parity_handle * parity_handle , block_off_t blockstart , block_off_t blockmax )
2011-03-04 20:59:21 +01:00
{
2015-12-30 23:45:11 +01:00
struct snapraid_io io ;
struct snapraid_plan plan ;
2011-03-04 20:59:21 +01:00
struct snapraid_handle * handle ;
2013-07-01 23:08:25 +02:00
void * rehandle_alloc ;
struct snapraid_rehash * rehandle ;
2011-09-23 21:48:50 +02:00
unsigned diskmax ;
2015-12-30 23:45:11 +01:00
block_off_t blockcur ;
2011-03-04 20:59:21 +01:00
unsigned j ;
2015-12-30 23:45:11 +01:00
void * zero_alloc ;
void * * zero ;
2016-01-05 14:48:39 +01:00
void * copy_alloc ;
void * * copy ;
2011-04-23 21:38:26 +02:00
unsigned buffermax ;
2011-03-28 20:55:57 +02:00
data_off_t countsize ;
block_off_t countpos ;
block_off_t countmax ;
2012-07-21 13:52:22 +02:00
block_off_t autosavedone ;
block_off_t autosavelimit ;
block_off_t autosavemissing ;
2011-03-18 22:38:11 +01:00
int ret ;
2026-01-25 21:25:03 +01:00
unsigned soft_error ;
2013-09-21 19:11:11 +02:00
unsigned silent_error ;
2014-12-27 23:14:06 +01:00
unsigned io_error ;
2013-06-23 22:54:54 +02:00
time_t now ;
2014-01-14 22:10:47 +01:00
struct failed_struct * failed ;
int * failed_map ;
2013-09-15 21:12:41 +02:00
unsigned l ;
2016-01-20 21:30:00 +01:00
unsigned * waiting_map ;
unsigned waiting_mac ;
2021-05-22 11:37:36 +02:00
bit_vect_t * block_enabled ;
2013-06-23 22:54:54 +02:00
/* get the present time */
now = time ( 0 ) ;
2011-03-04 20:59:21 +01:00
2011-09-23 21:48:50 +02:00
/* maps the disks to handles */
2016-01-18 22:38:01 +01:00
handle = handle_mapping ( state , & diskmax ) ;
2011-09-23 21:48:50 +02:00
2013-07-01 23:08:25 +02:00
/* rehash buffers */
rehandle = malloc_nofail_align ( diskmax * sizeof ( struct snapraid_rehash ) , & rehandle_alloc ) ;
2016-01-05 14:48:39 +01:00
/* we need 1 * data + 1 * parity */
buffermax = diskmax + state - > level ;
2011-04-23 21:38:26 +02:00
2015-12-30 23:45:11 +01:00
/* initialize the io threads */
2016-01-10 19:39:43 +01:00
io_init ( & io , state , state - > opt . io_cache , buffermax , sync_data_reader , handle , diskmax , 0 , sync_parity_writer , parity_handle , state - > level ) ;
2011-03-04 20:59:21 +01:00
2016-01-05 14:48:39 +01:00
/* allocate the copy buffer */
copy = malloc_nofail_vector_align ( diskmax , diskmax , state - > block_size , & copy_alloc ) ;
/* allocate and fill the zero buffer */
2015-12-30 23:45:11 +01:00
zero = malloc_nofail_align ( state - > block_size , & zero_alloc ) ;
memset ( zero , 0 , state - > block_size ) ;
raid_zero ( zero ) ;
2014-01-14 22:10:47 +01:00
failed = malloc_nofail ( diskmax * sizeof ( struct failed_struct ) ) ;
failed_map = malloc_nofail ( diskmax * sizeof ( unsigned ) ) ;
2016-01-21 18:52:45 +01:00
/* possibly waiting disks */
waiting_mac = diskmax > RAID_PARITY_MAX ? diskmax : RAID_PARITY_MAX ;
waiting_map = malloc_nofail ( waiting_mac * sizeof ( unsigned ) ) ;
2014-01-14 22:10:47 +01:00
2026-01-25 21:25:03 +01:00
soft_error = 0 ;
2013-09-21 19:11:11 +02:00
silent_error = 0 ;
2014-12-27 23:14:06 +01:00
io_error = 0 ;
2011-03-04 20:59:21 +01:00
2021-05-22 08:46:53 +02:00
msg_progress ( " Selecting... \n " ) ;
2011-03-28 20:55:57 +02:00
/* first count the number of blocks to process */
countmax = 0 ;
2015-12-30 23:45:11 +01:00
plan . handle_max = diskmax ;
plan . handle_map = handle ;
2016-10-01 22:19:29 +02:00
plan . force_full = state - > opt . force_full ;
2021-05-22 11:37:36 +02:00
block_enabled = calloc_nofail ( 1 , bit_vect_size ( blockmax ) ) ; /* preinitialize to 0 */
2015-12-30 23:45:11 +01:00
for ( blockcur = blockstart ; blockcur < blockmax ; + + blockcur ) {
if ( ! block_is_enabled ( & plan , blockcur ) )
2011-03-28 20:55:57 +02:00
continue ;
2021-05-22 11:37:36 +02:00
bit_vect_set ( block_enabled , blockcur ) ;
2011-03-28 20:55:57 +02:00
+ + countmax ;
}
2026-04-27 15:43:06 +02:00
/*
* Compute the autosave size for all disk , even if not read
* this makes sense because the speed should be almost the same
* if the disks are read in parallel
*/
2012-07-21 13:52:22 +02:00
autosavelimit = state - > autosave / ( diskmax * state - > block_size ) ;
autosavemissing = countmax ; /* blocks to do */
autosavedone = 0 ; /* blocks done */
2014-06-25 22:32:36 +02:00
/* drop until now */
state_usage_waste ( state ) ;
2011-03-28 20:55:57 +02:00
countsize = 0 ;
countpos = 0 ;
2026-05-21 12:49:47 +02:00
blockcur = blockstart ;
2015-12-30 23:45:11 +01:00
2021-05-22 08:46:53 +02:00
msg_progress ( " Syncing... \n " ) ;
2015-12-30 23:45:11 +01:00
/* start all the worker threads */
2021-05-22 11:37:36 +02:00
io_start ( & io , blockstart , blockmax , block_enabled ) ;
2015-12-30 23:45:11 +01:00
2026-02-09 23:49:02 +01:00
int alert = state_progress_begin ( state , blockstart , blockmax , countmax ) ;
if ( alert > 0 )
2014-09-27 00:23:05 +02:00
goto end ;
2026-02-09 23:49:02 +01:00
if ( alert < 0 )
goto bail ;
2014-09-27 00:23:05 +02:00
2015-12-30 23:45:11 +01:00
while ( 1 ) {
2014-01-14 22:10:47 +01:00
unsigned failed_count ;
2013-10-03 22:16:29 +02:00
int error_on_this_block ;
int silent_error_on_this_block ;
2014-12-30 09:45:35 +01:00
int io_error_on_this_block ;
2014-01-14 22:10:47 +01:00
int fixed_error_on_this_block ;
2013-09-29 21:01:31 +02:00
int parity_needs_to_be_updated ;
2016-01-04 11:28:34 +01:00
int parity_going_to_be_updated ;
2013-07-01 23:08:25 +02:00
snapraid_info info ;
int rehash ;
2015-12-30 23:45:11 +01:00
void * * buffer ;
2016-01-05 13:20:25 +01:00
int writer_error [ IO_WRITER_ERROR_MAX ] ;
2011-03-04 20:59:21 +01:00
2015-12-30 23:45:11 +01:00
/* go to the next block */
2016-01-04 11:24:04 +01:00
blockcur = io_read_next ( & io , & buffer ) ;
2015-12-30 23:45:11 +01:00
if ( blockcur > = blockmax )
break ;
2011-03-04 20:59:21 +01:00
2016-01-31 16:38:29 +01:00
/* until now is scheduling */
state_usage_sched ( state ) ;
2012-07-21 13:52:22 +02:00
/* one more block processed for autosave */
+ + autosavedone ;
- - autosavemissing ;
2015-12-30 23:45:11 +01:00
/* by default process the block, and skip it if something goes wrong */
2013-10-03 22:16:29 +02:00
error_on_this_block = 0 ;
silent_error_on_this_block = 0 ;
2014-12-30 09:45:35 +01:00
io_error_on_this_block = 0 ;
2014-01-14 22:10:47 +01:00
fixed_error_on_this_block = 0 ;
/* keep track of the number of failed blocks */
failed_count = 0 ;
2011-09-14 22:52:55 +02:00
2013-10-25 18:03:05 +02:00
/* get block specific info */
2015-12-30 23:45:11 +01:00
info = info_get ( & state - > infoarr , blockcur ) ;
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 ) ;
2026-04-26 15:59:58 +02:00
/*
* If the parity requires to be updated
*
* It could happens that all the blocks are EMPTY / BLK and CHG but with the hash
* still matching because the specific CHG block was not modified .
* In such case , we can avoid to update parity , because it would be the same as before
*
* Note that if there is any CHG / DELETED blocks already present in the content
* file loaded , meaning that there are unsynced_blocks , this optimization is disabled
*/
2016-10-01 22:19:29 +02:00
parity_needs_to_be_updated = state - > opt . force_full | | state - > opt . force_parity_update ;
2013-09-29 21:01:31 +02:00
2016-01-04 11:28:34 +01:00
/* if the parity is going to be updated */
parity_going_to_be_updated = 0 ;
2026-04-27 15:43:06 +02:00
/*
* If the block is marked as bad , we force the parity update
* because the bad block may be the result of a wrong parity
*/
2013-09-29 21:01:31 +02:00
if ( info_get_bad ( info ) )
parity_needs_to_be_updated = 1 ;
2011-03-04 20:59:21 +01:00
/* for each disk, process the block */
2014-09-27 00:32:39 +02:00
for ( j = 0 ; j < diskmax ; + + j ) {
2015-12-30 23:45:11 +01:00
struct snapraid_task * task ;
2011-03-04 20:59:21 +01:00
int read_size ;
2016-04-07 21:39:02 +02:00
unsigned char hash [ HASH_MAX ] ;
2011-03-04 20:59:21 +01:00
struct snapraid_block * block ;
2014-02-01 17:57:26 +01:00
unsigned block_state ;
2015-12-30 23:45:11 +01:00
struct snapraid_disk * disk ;
2015-10-03 10:43:02 +02:00
struct snapraid_file * file ;
block_off_t file_pos ;
2015-12-30 23:45:11 +01:00
unsigned diskcur ;
2016-01-26 23:25:01 +01:00
/* until now is misc */
state_usage_misc ( state ) ;
2015-12-30 23:45:11 +01:00
2016-01-20 21:30:00 +01:00
task = io_data_read ( & io , & diskcur , waiting_map , & waiting_mac ) ;
/* until now is disk */
state_usage_disk ( state , handle , waiting_map , waiting_mac ) ;
2015-12-30 23:45:11 +01:00
/* get the results */
disk = task - > disk ;
block = task - > block ;
file = task - > file ;
file_pos = task - > file_pos ;
read_size = task - > read_size ;
2011-03-04 20:59:21 +01:00
2013-07-01 23:08:25 +02:00
/* by default no rehash in case of "continue" */
2015-12-30 23:45:11 +01:00
rehandle [ diskcur ] . block = 0 ;
2013-07-01 23:08:25 +02:00
2011-10-01 19:18:32 +02:00
/* if the disk position is not used */
2015-12-30 23:45:11 +01:00
if ( ! disk )
2011-10-01 19:18:32 +02:00
continue ;
2017-11-19 19:33:47 +01:00
state_usage_file ( state , disk , file ) ;
2014-02-01 17:57:26 +01:00
/* get the state of the block */
block_state = block_state_get ( block ) ;
2013-09-29 21:01:31 +02:00
2026-04-27 15:43:06 +02:00
/*
* If the block has invalid parity ,
* we have to take care of it in case of recover
*/
2014-01-14 22:10:47 +01:00
if ( block_has_invalid_parity ( block ) ) {
2026-04-27 15:43:06 +02:00
/*
* Store it in the failed set , because
* the parity may be still computed with the previous content
*/
2015-12-30 23:45:11 +01:00
failed [ failed_count ] . index = diskcur ;
2014-01-14 22:10:47 +01:00
failed [ failed_count ] . size = state - > block_size ;
failed [ failed_count ] . block = block ;
+ + failed_count ;
2026-04-27 15:43:06 +02:00
/*
* If the block has invalid parity , we have to update the parity
* to include this block change
* This also apply to CHG blocks , but we are going to handle
* later this case to do the updates only if really needed
*/
2014-02-01 17:57:26 +01:00
if ( block_state ! = BLOCK_STATE_CHG )
parity_needs_to_be_updated = 1 ;
2026-04-27 15:43:06 +02:00
/*
* Note that DELETE blocks are skipped in the next check
* and we have to store them in the failed blocks
* before skipping
*/
2014-02-01 17:57:26 +01:00
/* follow */
2013-09-29 21:01:31 +02:00
}
2015-12-30 23:45:11 +01:00
/* if the block is not used */
if ( ! block_has_file ( block ) )
2011-03-04 20:59:21 +01:00
continue ;
2014-12-27 23:14:06 +01:00
2015-12-30 23:45:11 +01:00
/* handle error conditions */
if ( task - > state = = TASK_STATE_IOERROR ) {
2016-11-06 18:27:44 +01:00
/* LCOV_EXCL_START */
2015-12-30 23:45:11 +01:00
+ + io_error ;
goto bail ;
2016-11-06 18:27:44 +01:00
/* LCOV_EXCL_STOP */
2011-03-18 22:38:11 +01:00
}
2015-12-30 23:45:11 +01:00
if ( task - > state = = TASK_STATE_ERROR ) {
2016-11-06 18:27:44 +01:00
/* LCOV_EXCL_START */
2026-01-25 21:25:03 +01:00
+ + soft_error ;
2011-03-19 22:00:08 +01:00
goto bail ;
2016-11-06 18:27:44 +01:00
/* LCOV_EXCL_STOP */
2011-03-19 22:00:08 +01:00
}
2015-12-30 23:45:11 +01:00
if ( task - > state = = TASK_STATE_ERROR_CONTINUE ) {
2026-01-25 21:25:03 +01:00
+ + soft_error ;
2013-10-03 22:16:29 +02:00
error_on_this_block = 1 ;
2011-09-14 22:52:55 +02:00
continue ;
2011-03-18 22:38:11 +01:00
}
2015-12-30 23:45:11 +01:00
if ( task - > state = = TASK_STATE_IOERROR_CONTINUE ) {
+ + io_error ;
2016-01-02 17:13:19 +01:00
if ( io_error > = state - > opt . io_error_limit ) {
2016-11-06 18:27:44 +01:00
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( EIO , " DANGER! Too many input/output errors in the %s disk. It isn't possible to continue. \n " , disk - > dir ) ;
log_fatal ( EIO , " Stopping at block %u \n " , blockcur ) ;
2016-01-02 17:13:19 +01:00
goto bail ;
2016-11-06 18:27:44 +01:00
/* LCOV_EXCL_STOP */
2016-01-02 17:13:19 +01:00
}
/* otherwise continue */
2015-12-30 23:45:11 +01:00
io_error_on_this_block = 1 ;
continue ;
}
if ( task - > state ! = TASK_STATE_DONE ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( EINTERNAL , " Internal inconsistency in task state \n " ) ;
2015-12-30 23:45:11 +01:00
os_abort ( ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2011-03-18 22:38:11 +01:00
}
2011-03-04 20:59:21 +01:00
2012-02-09 23:17:36 +01:00
countsize + = read_size ;
2011-03-04 20:59:21 +01:00
/* now compute the hash */
2013-07-01 23:08:25 +02:00
if ( rehash ) {
2015-12-30 23:45:11 +01:00
memhash ( state - > prevhash , state - > prevhashseed , hash , buffer [ diskcur ] , read_size ) ;
2013-07-01 23:08:25 +02:00
/* compute the new hash, and store it */
2015-12-30 23:45:11 +01:00
rehandle [ diskcur ] . block = block ;
memhash ( state - > hash , state - > hashseed , rehandle [ diskcur ] . hash , buffer [ diskcur ] , read_size ) ;
2013-07-01 23:08:25 +02:00
} else {
2015-12-30 23:45:11 +01:00
memhash ( state - > hash , state - > hashseed , hash , buffer [ diskcur ] , read_size ) ;
2013-07-01 23:08:25 +02:00
}
2011-03-04 20:59:21 +01:00
2016-01-26 23:25:01 +01:00
/* until now is hash */
state_usage_hash ( state ) ;
2013-08-21 22:24:12 +02:00
if ( block_has_updated_hash ( block ) ) {
2011-03-04 20:59:21 +01:00
/* compare the hash */
2016-04-07 21:39:02 +02:00
if ( memcmp ( hash , block - > hash , BLOCK_HASH_SIZE ) ! = 0 ) {
2014-02-01 17:57:26 +01:00
/* if the file has invalid parity, it's a REP changed during the sync */
if ( block_has_invalid_parity ( block ) ) {
2026-05-29 11:01:44 +02:00
log_tag ( " error:%u:%s:%s: Unexpected data change \n " , blockcur , disk - > name , esc_tag ( file - > sub ) ) ;
2026-02-01 20:21:28 +01:00
log_error ( ESOFT , " Data change at file '%s' at position '%u' \n " , task - > path , file_pos ) ;
log_error ( ESOFT , " WARNING! Unexpected data modification of a file without parity! \n " ) ;
2014-05-15 23:45:51 +02:00
if ( file_flag_has ( file , FILE_IS_COPY ) ) {
2026-02-01 20:21:28 +01:00
log_error ( ESOFT , " This file was detected as a copy of another file with the same name, size, \n " ) ;
log_error ( ESOFT , " and timestamp, but the file data isn't matching the assumed copy. \n " ) ;
log_error ( ESOFT , " If this is a false positive, and the files are expected to be different, \n " ) ;
log_error ( ESOFT , " you can 'sync' anyway using 'snapraid --force-nocopy sync' \n " ) ;
2014-05-15 23:45:51 +02:00
} else {
2026-02-01 20:21:28 +01:00
log_error ( ESOFT , " Try removing the file from the array and rerun the 'sync' command! \n " ) ;
2014-05-15 23:45:51 +02:00
}
2014-02-01 17:57:26 +01:00
2026-01-25 21:25:03 +01:00
+ + soft_error ;
2014-02-01 17:57:26 +01:00
2026-04-27 15:43:06 +02:00
/*
* If the file is changed , it means that it was modified during sync
* this isn ' t a serious error , so we skip this block , and continue with others
*/
2014-02-01 17:57:26 +01:00
error_on_this_block = 1 ;
continue ;
} else { /* otherwise it's a BLK with silent error */
2016-04-07 21:39:02 +02:00
unsigned diff = memdiff ( hash , block - > hash , BLOCK_HASH_SIZE ) ;
2026-05-29 11:01:44 +02:00
log_tag ( " error_data:%u:%s:%s: Data error at position %u, diff hash bits %u/%u \n " , blockcur , disk - > name , esc_tag ( file - > sub ) , file_pos , diff , BLOCK_HASH_SIZE * 8 ) ;
2026-02-01 20:21:28 +01:00
log_error ( EDATA , " Data error in file '%s' at position '%u', diff hash bits %u/%u \n " , task - > path , file_pos , diff , BLOCK_HASH_SIZE * 8 ) ;
2014-02-01 17:57:26 +01:00
/* save the failed block for the fix */
2015-12-30 23:45:11 +01:00
failed [ failed_count ] . index = diskcur ;
2014-02-01 17:57:26 +01:00
failed [ failed_count ] . size = read_size ;
failed [ failed_count ] . block = block ;
+ + failed_count ;
2026-04-27 15:43:06 +02:00
/*
* Silent errors are very rare , and are not a signal that a disk
* is going to fail . So , we just continue marking the block as bad
* just like in scrub
*/
2014-02-01 17:57:26 +01:00
+ + silent_error ;
silent_error_on_this_block = 1 ;
continue ;
}
2011-03-04 20:59:21 +01:00
}
} else {
2014-02-01 17:57:26 +01:00
/* if until now the parity doesn't need to be updated */
2013-09-29 21:01:31 +02:00
if ( ! parity_needs_to_be_updated ) {
2026-04-27 15:43:06 +02:00
/*
* For sure it ' s a CHG block , because EMPTY are processed before with " continue "
* and BLK and REP have " block_has_updated_hash() " as 1 , and all the others
* have " parity_needs_to_be_updated " already at 1
*/
2013-09-29 21:01:31 +02:00
assert ( block_state_get ( block ) = = BLOCK_STATE_CHG ) ;
2026-04-26 15:59:58 +02:00
/*
* When a sync is interrupted , the state of the parity is unknown becase
* we don ' t know exactly where the process stopped .
*
* This means that the hash information of the OLD blocks stored in the
* content file for CHG / DELETED blocks may be correct or not .
*
* The sync process uses the hash of CHG blocks to decide if the parity has to be
* recomputed , avoiding the recomputation if the input data is the same as before .
* But in case of an interrupted sync we cannot trust this data , so we
* disable this optimization if there are unsynced blocks .
*
* Note that CHG blocks may be from reading the content file , or from
* scanning the disk for really changed file , like with a different timestamp .
*
* An example for CHG blocks is :
* - One file is added creating a CHG block with ZERO state
* - Sync aborted after updating the parity to the new state ,
* but without saving the content file representing this new BLK state .
* - File is now deleted after the aborted sync
* - Sync again , deleting the blocks over the CHG ones
* with the hash of CHG blocks not representing the real parity state
*
* An example for DELETED blocks is :
* - One file is deleted creating DELETED blocks
* - Sync aborted after , updating the parity to the new state ,
* but without saving the content file representing this new EMPTY state .
* - Another file is added again over the DELETE ones
* with the hash of DELETED blocks not representing the real parity state
*/
/* if the previous sync was completed and the hash represents the data unequivocally */
if ( state - > unsynced_blocks = = 0 & & hash_is_unique ( block - > hash ) ) {
2013-09-29 21:01:31 +02:00
/* check if the hash is changed */
2016-04-07 21:39:02 +02:00
if ( memcmp ( hash , block - > hash , BLOCK_HASH_SIZE ) ! = 0 ) {
2013-09-29 21:01:31 +02:00
/* the block is different, and we must update parity */
parity_needs_to_be_updated = 1 ;
}
} else {
2026-04-26 15:59:58 +02:00
/* if we don't know the hash, always update parity */
2013-09-29 21:01:31 +02:00
parity_needs_to_be_updated = 1 ;
}
}
2014-09-27 00:32:39 +02:00
2026-04-27 15:43:06 +02:00
/*
* Copy the hash in the block , but doesn ' t mark the block as hashed
* this allow in case of skipped block to do not save the failed computation
*/
2016-04-07 21:39:02 +02:00
memcpy ( block - > hash , hash , BLOCK_HASH_SIZE ) ;
2013-07-01 23:08:25 +02:00
2026-04-27 15:43:06 +02:00
/*
* Note that in case of rehash , this is the wrong hash ,
* but it will be overwritten later
*/
2011-03-04 20:59:21 +01:00
}
}
2026-04-27 15:43:06 +02:00
/*
* If we have only silent errors we can try to fix them on - the - fly
* note the fix is not written to disk , but used only to
* compute the new parity
*/
2014-12-30 09:45:35 +01:00
if ( ! error_on_this_block & & ! io_error_on_this_block & & silent_error_on_this_block ) {
2014-01-14 22:10:47 +01:00
unsigned failed_mac ;
2014-02-01 17:57:26 +01:00
int something_to_recover = 0 ;
2011-04-23 21:38:26 +02:00
2026-04-27 15:43:06 +02:00
/*
* Sort the failed vector
* because with threads it may be in any order
* but RAID requires the indexes to be sorted
*/
2015-12-30 23:45:11 +01:00
qsort ( failed , failed_count , sizeof ( failed [ 0 ] ) , failed_compare_by_index ) ;
2014-01-14 22:10:47 +01:00
/* setup the blocks to recover */
failed_mac = 0 ;
2014-09-27 00:32:39 +02:00
for ( j = 0 ; j < failed_count ; + + j ) {
2014-01-14 22:10:47 +01:00
unsigned char * block_buffer = buffer [ failed [ j ] . index ] ;
2016-01-05 14:48:39 +01:00
unsigned char * block_copy = copy [ failed [ j ] . index ] ;
2014-01-14 22:10:47 +01:00
unsigned block_state = block_state_get ( failed [ j ] . block ) ;
2014-02-01 17:57:26 +01:00
/* we try to recover only if at least one BLK is present */
if ( block_state = = BLOCK_STATE_BLK )
something_to_recover = 1 ;
2026-04-27 15:43:06 +02:00
/*
* Save a copy of the content just read
* that it ' s going to be overwritten by the recovering function
*/
2014-01-14 22:10:47 +01:00
memcpy ( block_copy , block_buffer , state - > block_size ) ;
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 filled with 0 , restore this state
* and avoid to recover it
*/
2014-01-14 22:10:47 +01:00
memset ( block_buffer , 0 , state - > block_size ) ;
} else {
2014-02-01 17:57:26 +01:00
/* if we have too many failures, we cannot recover */
if ( failed_mac > = state - > level )
2014-01-14 22:10:47 +01:00
break ;
2014-02-01 17:57:26 +01:00
2014-01-14 22:10:47 +01:00
/* otherwise it has to be recovered */
failed_map [ failed_mac + + ] = failed [ j ] . index ;
}
}
2014-02-01 17:57:26 +01:00
/* if we have something to recover and enough parity */
if ( something_to_recover & & j = = failed_count ) {
2016-01-26 23:25:01 +01:00
/* until now is misc */
state_usage_misc ( state ) ;
2014-06-25 22:32:36 +02:00
2026-04-27 15:43:06 +02:00
/*
* Read the parity
* we are sure that parity exists because
* we have at least one BLK block
*/
2014-09-27 00:32:39 +02:00
for ( l = 0 ; l < state - > level ; + + l ) {
2026-06-03 20:49:36 +02:00
ret = parity_read ( & parity_handle [ l ] , blockcur , buffer [ diskmax + l ] , state - > block_size ) ;
2014-01-14 22:10:47 +01:00
if ( ret = = - 1 ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-01-24 19:19:11 +01:00
log_tag ( " parity_%s:%u:%s: Read error. %s. \n " , es ( errno ) , blockcur , lev_config_name ( l ) , strerror ( errno ) ) ;
2026-02-22 14:58:06 +01:00
if ( is_hw ( errno ) ) {
2026-05-30 17:54:31 +02:00
log_fatal_errno ( errno , lev_config_name ( l ) ) ;
2014-12-30 09:45:35 +01:00
if ( io_error > = state - > opt . io_error_limit ) {
2026-02-01 20:21:28 +01:00
log_fatal ( errno , " DANGER! Too many input/output errors in the %s disk. It isn't possible to continue. \n " , lev_config_name ( l ) ) ;
log_fatal ( errno , " Stopping at block %u \n " , blockcur ) ;
2014-12-30 09:45:35 +01:00
+ + io_error ;
goto bail ;
}
2014-12-27 23:14:06 +01:00
+ + io_error ;
2014-12-30 09:45:35 +01:00
io_error_on_this_block = 1 ;
continue ;
2014-12-27 23:14:06 +01:00
}
2026-01-24 19:19:11 +01:00
log_fatal_errno ( errno , lev_config_name ( l ) ) ;
2026-02-01 20:21:28 +01:00
log_fatal ( errno , " Stopping at block %u \n " , blockcur ) ;
2026-01-25 21:25:03 +01:00
+ + soft_error ;
2014-01-14 22:10:47 +01:00
goto bail ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2014-01-14 22:10:47 +01:00
}
2014-06-25 22:32:36 +02:00
/* until now is parity */
2016-01-20 21:30:00 +01:00
state_usage_parity ( state , & l , 1 ) ;
2014-01-14 22:10:47 +01:00
}
2014-12-30 09:45:35 +01:00
/* if no error in parity read */
if ( ! io_error_on_this_block ) {
2026-04-27 15:43:06 +02:00
/*
* Try to fix the data
* note that this is a simple fix algorithm , that doesn ' t take into
* account the case of a wrong parity
* only ' fix ' supports the most advanced fixing
*/
2014-12-30 09:45:35 +01:00
raid_rec ( failed_mac , failed_map , diskmax , state - > level , state - > block_size , buffer ) ;
2016-01-26 23:25:01 +01:00
/* until now is raid */
state_usage_raid ( state ) ;
2014-12-30 09:45:35 +01:00
/* check the result and prepare the data */
for ( j = 0 ; j < failed_count ; + + j ) {
2016-04-07 21:39:02 +02:00
unsigned char hash [ HASH_MAX ] ;
2014-12-30 09:45:35 +01:00
unsigned char * block_buffer = buffer [ failed [ j ] . index ] ;
2016-01-05 14:48:39 +01:00
unsigned char * block_copy = copy [ failed [ j ] . index ] ;
2014-12-30 09:45:35 +01:00
unsigned block_state = block_state_get ( failed [ j ] . block ) ;
if ( block_state = = BLOCK_STATE_BLK ) {
unsigned size = failed [ j ] . size ;
/* compute the hash of the recovered block */
if ( rehash ) {
memhash ( state - > prevhash , state - > prevhashseed , hash , block_buffer , size ) ;
} else {
memhash ( state - > hash , state - > hashseed , hash , block_buffer , size ) ;
}
2016-01-26 23:25:01 +01:00
/* until now is hash */
state_usage_hash ( state ) ;
2014-12-30 09:45:35 +01:00
/* if the hash doesn't match */
2016-04-07 21:39:02 +02:00
if ( memcmp ( hash , failed [ j ] . block - > hash , BLOCK_HASH_SIZE ) ! = 0 ) {
2014-12-30 09:45:35 +01:00
/* we have not recovered */
break ;
}
/* pad with 0 if needed */
if ( size < state - > block_size )
memset ( block_buffer + size , 0 , state - > block_size - size ) ;
2014-01-14 22:10:47 +01:00
} else {
2026-04-27 15:43:06 +02:00
/*
* Otherwise restore the content
* because we are not interested in the old state
* that it ' s recovered for CHG , REP and DELETED blocks
*/
2014-12-30 09:45:35 +01:00
memcpy ( block_buffer , block_copy , state - > block_size ) ;
2014-01-14 22:10:47 +01:00
}
}
2014-12-30 09:45:35 +01:00
/* if all is processed, we have fixed it */
if ( j = = failed_count )
fixed_error_on_this_block = 1 ;
}
2014-01-14 22:10:47 +01:00
}
}
/* if we have read all the data required and it's correct, proceed with the parity */
2014-12-30 09:45:35 +01:00
if ( ! error_on_this_block & & ! io_error_on_this_block
2014-01-14 22:10:47 +01:00
& & ( ! silent_error_on_this_block | | fixed_error_on_this_block )
) {
2015-10-07 22:07:20 +02:00
/* update the parity only if really needed */
2013-09-29 21:01:31 +02:00
if ( parity_needs_to_be_updated ) {
/* compute the parity */
2014-01-08 17:07:13 +01:00
raid_gen ( diskmax , state - > level , state - > block_size , buffer ) ;
2013-09-29 21:01:31 +02:00
2016-01-26 23:25:01 +01:00
/* until now is raid */
state_usage_raid ( state ) ;
2016-01-04 11:28:34 +01:00
/* mark that the parity is going to be written */
parity_going_to_be_updated = 1 ;
2013-09-11 23:30:32 +02:00
}
2016-01-04 11:28:34 +01:00
/* for each disk, mark the blocks as processed */
for ( j = 0 ; j < diskmax ; + + j ) {
struct snapraid_block * block ;
2015-10-05 21:40:44 +02:00
2016-01-04 11:28:34 +01:00
if ( ! handle [ j ] . disk )
continue ;
2012-02-09 23:17:36 +01:00
2016-01-16 14:26:05 +01:00
block = fs_par2block_find ( handle [ j ] . disk , blockcur ) ;
2012-02-09 23:17:36 +01:00
2016-01-16 14:26:05 +01:00
if ( block = = BLOCK_NULL ) {
2016-01-04 11:28:34 +01:00
/* nothing to do */
continue ;
}
2011-03-18 22:38:11 +01:00
2016-01-04 11:28:34 +01:00
/* if it's a deleted block */
if ( block_state_get ( block ) = = BLOCK_STATE_DELETED ) {
/* the parity is now updated without this block, so it's now empty */
fs_deallocate ( handle [ j ] . disk , blockcur ) ;
continue ;
2014-12-30 09:45:35 +01:00
}
2016-01-04 11:28:34 +01:00
/* now all the blocks have the hash and the parity computed */
block_state_set ( block , BLOCK_STATE_BLK ) ;
2011-09-14 22:52:55 +02:00
}
2013-06-23 22:54:54 +02:00
2026-04-27 15:43:06 +02:00
/*
* We update the info block only if we really have updated the parity
* because otherwise the time / justsynced info would be misleading as we didn ' t
* wrote the parity at this time
* we also update the info block only if no silent error was found
* because has no sense to refresh the time for data that we know bad
*/
2014-01-14 22:10:47 +01:00
if ( parity_needs_to_be_updated
& & ! silent_error_on_this_block
) {
2016-05-23 22:37:18 +02:00
/* if rehash is needed */
2013-09-29 21:01:31 +02:00
if ( rehash ) {
/* store all the new hash already computed */
2014-09-27 00:32:39 +02:00
for ( j = 0 ; j < diskmax ; + + j ) {
2013-09-29 21:01:31 +02:00
if ( rehandle [ j ] . block )
2016-04-07 21:39:02 +02:00
memcpy ( rehandle [ j ] . block - > hash , rehandle [ j ] . hash , BLOCK_HASH_SIZE ) ;
2013-09-29 21:01:31 +02:00
}
2013-07-01 23:08:25 +02:00
}
2026-04-27 15:43:06 +02:00
/*
* Update the time info of the block
* we are also clearing any previous bad and rehash flag
*/
2015-12-30 23:45:11 +01:00
info_set ( & state - > infoarr , blockcur , info_make ( now , 0 , 0 , 1 ) ) ;
2013-09-29 21:01:31 +02:00
}
2014-01-14 22:10:47 +01:00
}
2026-04-27 15:43:06 +02:00
/*
* If a silent ( even if corrected ) or input / output error was found
* mark the block as bad to have check / fix to handle it
* because our correction is in memory only and not yet written
*/
2014-12-30 09:45:35 +01:00
if ( silent_error_on_this_block | | io_error_on_this_block ) {
2013-10-25 18:03:05 +02:00
/* set the error status keeping the other info */
2015-12-30 23:45:11 +01:00
info_set ( & state - > infoarr , blockcur , info_set_bad ( info ) ) ;
2011-03-18 22:38:11 +01:00
}
2011-03-04 20:59:21 +01:00
2026-04-27 15:43:06 +02:00
/*
* Finally schedule parity write
* Note that the calls to io_parity_write ( ) are mandatory
* even if the parity doesn ' t need to be updated
* This because we want to keep track of the time usage
*/
2016-01-26 23:25:01 +01:00
state_usage_misc ( state ) ;
2016-01-04 11:28:34 +01:00
2016-03-15 22:40:13 +01:00
/* write start */
io_write_preset ( & io , blockcur , ! parity_going_to_be_updated ) ;
2016-01-04 11:28:34 +01:00
/* write the parity */
for ( l = 0 ; l < state - > level ; + + l ) {
unsigned levcur ;
2016-01-20 21:30:00 +01:00
io_parity_write ( & io , & levcur , waiting_map , & waiting_mac ) ;
2016-01-04 11:28:34 +01:00
/* until now is parity */
2016-01-20 21:30:00 +01:00
state_usage_parity ( state , waiting_map , waiting_mac ) ;
2016-01-04 11:28:34 +01:00
}
/* write finished */
2016-01-05 13:20:25 +01:00
io_write_next ( & io , blockcur , ! parity_going_to_be_updated , writer_error ) ;
/* handle errors reported */
for ( j = 0 ; j < IO_WRITER_ERROR_MAX ; + + j ) {
if ( writer_error [ j ] ) {
switch ( j + IO_WRITER_ERROR_BASE ) {
case TASK_STATE_IOERROR_CONTINUE :
+ + io_error ;
if ( io_error > = state - > opt . io_error_limit ) {
2016-11-06 18:27:44 +01:00
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( EIO , " DANGER! Too many input/output errors in a parity disk. It isn't possible to continue. \n " ) ;
log_fatal ( EIO , " Stopping at block %u \n " , blockcur ) ;
2016-01-05 13:20:25 +01:00
goto bail ;
2016-11-06 18:27:44 +01:00
/* LCOV_EXCL_STOP */
2016-01-05 13:20:25 +01:00
}
break ;
case TASK_STATE_ERROR_CONTINUE :
2026-01-25 21:25:03 +01:00
+ + soft_error ;
2016-01-05 13:20:25 +01:00
break ;
case TASK_STATE_IOERROR :
2016-11-06 18:27:44 +01:00
/* LCOV_EXCL_START */
2016-01-05 13:20:25 +01:00
+ + io_error ;
goto bail ;
2026-01-18 11:03:07 +01:00
/* LCOV_EXCL_STOP */
2016-01-05 13:20:25 +01:00
case TASK_STATE_ERROR :
2016-11-06 18:27:44 +01:00
/* LCOV_EXCL_START */
2026-01-25 21:25:03 +01:00
+ + soft_error ;
2016-01-05 13:20:25 +01:00
goto bail ;
2016-11-06 18:27:44 +01:00
/* LCOV_EXCL_STOP */
2016-01-05 13:20:25 +01:00
}
}
}
2016-01-04 11:28:34 +01:00
2011-03-19 22:00:08 +01:00
/* mark the state as needing write */
state - > need_write = 1 ;
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 , & io , blockcur , 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
}
2012-03-13 23:59:59 +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 ) ;
/* before spinning down flush all the caches */
ret = state_flush ( state , & io , parity_handle , blockcur ) ;
if ( ret = = - 1 ) {
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( errno , " Stopping at block %u \n " , blockcur ) ;
2026-01-16 00:42:40 +01:00
+ + io_error ;
2025-09-21 19:54:10 +02:00
goto bail ;
/* LCOV_EXCL_STOP */
}
state_thermal_cooldown ( state ) ;
state_progress_restart ( state ) ;
/* drop until now */
state_usage_waste ( state ) ;
}
2012-03-13 23:59:59 +01:00
/* autosave */
2014-11-10 23:06:14 +01:00
if ( ( state - > autosave ! = 0
2012-07-21 13:52:22 +02:00
& & autosavedone > = autosavelimit /* if we have reached the limit */
2014-11-10 23:06:14 +01:00
& & autosavemissing > = autosavelimit ) /* if we have at least a full step to do */
2014-11-12 20:47:07 +01:00
/* or if we have a forced autosave at the specified block */
2015-12-30 23:45:11 +01:00
| | ( state - > opt . force_autosave_at ! = 0 & & state - > opt . force_autosave_at = = blockcur )
2012-07-21 13:52:22 +02:00
) {
autosavedone = 0 ; /* restart the counter */
2012-03-13 23:59:59 +01:00
2016-01-26 23:25:01 +01:00
/* until now is misc */
state_usage_misc ( state ) ;
2014-08-02 10:15:23 +02:00
2012-03-13 23:59:59 +01:00
state_progress_stop ( state ) ;
2015-02-22 14:31:03 +01:00
msg_progress ( " Autosaving... \n " ) ;
2014-11-10 22:46:19 +01:00
2026-04-27 15:43:06 +02:00
/*
* Before writing the new content file we ensure that
* the parity is really written flushing the disk cache
*/
2025-09-25 12:46:15 +02:00
ret = state_flush ( state , & io , parity_handle , blockcur ) ;
if ( ret = = - 1 ) {
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( EIO , " Stopping at block %u \n " , blockcur ) ;
2026-01-16 00:42:40 +01:00
+ + io_error ;
2025-09-25 12:46:15 +02:00
goto bail ;
/* LCOV_EXCL_STOP */
2014-11-10 22:46:19 +01:00
}
/* now we can safely write the content file */
2012-03-13 23:59:59 +01:00
state_write ( state ) ;
state_progress_restart ( state ) ;
2014-06-25 22:32:36 +02:00
/* drop until now */
state_usage_waste ( state ) ;
2012-03-13 23:59:59 +01:00
}
2011-03-04 20:59:21 +01:00
}
2014-09-27 00:23:05 +02:00
end :
2025-10-13 11:46:36 +02:00
state_progress_end ( state , countpos , countmax , countsize , " Nothing to sync. \n " ) ;
2014-06-25 22:32:36 +02:00
2026-04-27 15:43:06 +02:00
/*
* Before returning we ensure that
* the parity is really written flushing the disk cache
*/
2025-09-25 12:46:15 +02:00
ret = state_flush ( state , & io , parity_handle , blockcur ) ;
if ( ret = = - 1 ) {
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( errno , " Stopping at block %u \n " , blockcur ) ;
2026-01-16 00:42:40 +01:00
+ + io_error ;
2025-09-25 12:46:15 +02:00
goto bail ;
/* LCOV_EXCL_STOP */
2015-01-31 16:44:46 +01:00
}
2026-04-27 14:39:38 +02:00
/* now the parity is fully written, no need to keep deallocated files */
state_commit ( state ) ;
2025-10-13 11:46:36 +02:00
/* save the new state if required */
if ( ! state - > opt . kill_after_sync ) {
if ( ( state - > need_write | | state - > opt . force_content_write ) )
state_write ( state ) ;
} else {
2026-02-01 20:21:28 +01:00
log_fatal ( EUSER , " WARNING! Skipped writing state due to --test-kill-after-sync option. \n " ) ;
2025-10-13 11:46:36 +02:00
}
state_usage_print ( state ) ;
2026-01-25 21:25:03 +01:00
if ( soft_error | | silent_error | | io_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 ) ;
2015-02-22 14:31:03 +01:00
msg_status ( " %8u io errors \n " , io_error ) ;
msg_status ( " %8u data errors \n " , silent_error ) ;
2013-09-21 19:11:11 +02:00
} else {
/* print the result only if processed something */
if ( countpos ! = 0 )
2015-02-22 14:31:03 +01:00
msg_status ( " Everything OK \n " ) ;
}
2026-01-25 21:25:03 +01:00
if ( soft_error )
2026-02-01 20:21:28 +01:00
log_fatal ( ESOFT , " WARNING! Unexpected soft errors! \n " ) ;
2015-02-22 14:31:03 +01:00
if ( io_error )
2026-02-01 20:21:28 +01:00
log_fatal ( EIO , " DANGER! Unexpected input/output errors! The failing blocks are now marked as bad! \n " ) ;
2015-02-22 14:31:03 +01:00
if ( silent_error )
2026-02-01 20:21:28 +01:00
log_fatal ( EDATA , " DANGER! Unexpected silent data errors! The failing blocks are now marked as bad! \n " ) ;
2015-02-22 14:31:03 +01:00
if ( io_error | | silent_error ) {
2026-02-01 20:21:28 +01:00
log_fatal ( ESOFT , " Use 'snapraid status' to list the bad blocks. \n " ) ;
log_fatal ( ESOFT , " Use 'snapraid -e fix' to recover. \n " ) ;
2013-09-21 19:11:11 +02:00
}
2026-01-25 21:25:03 +01:00
log_tag ( " summary:error_soft:%u \n " , soft_error ) ;
2015-04-20 22:07:42 +02:00
log_tag ( " summary:error_io:%u \n " , io_error ) ;
log_tag ( " summary:error_data:%u \n " , silent_error ) ;
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-13 23:45:31 +01:00
else if ( silent_error + io_error = = 0 )
2026-01-20 23:52:13 +01:00
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 " ) ;
log_flush ( ) ;
2013-09-30 22:24:30 +02:00
2011-03-18 22:38:11 +01:00
bail :
2015-12-30 23:45:11 +01:00
/* stop all the worker threads */
io_stop ( & io ) ;
2014-09-27 00:32:39 +02:00
for ( j = 0 ; j < diskmax ; + + j ) {
2015-02-03 18:12:52 +01:00
struct snapraid_file * file = handle [ j ] . file ;
struct snapraid_disk * disk = handle [ j ] . disk ;
2011-03-18 22:38:11 +01:00
ret = handle_close ( & handle [ j ] ) ;
if ( ret = = - 1 ) {
2014-12-27 23:14:06 +01:00
/* LCOV_EXCL_START */
2026-05-29 11:01:44 +02:00
log_tag ( " %s:%u:%s:%s: Close error. %s. \n " , es ( errno ) , blockcur , disk - > name , esc_tag ( file - > sub ) , strerror ( errno ) ) ;
2026-01-24 19:19:11 +01:00
log_fatal_errno ( errno , disk - > name ) ;
2026-02-22 14:58:06 +01:00
if ( is_hw ( errno ) ) {
2026-01-13 23:45:31 +01:00
+ + io_error ;
} else {
2026-01-25 21:25:03 +01:00
+ + soft_error ;
2026-01-18 11:03:07 +01:00
}
2011-03-18 22:38:11 +01:00
/* continue, as we are already exiting */
2014-12-27 23:14:06 +01:00
/* LCOV_EXCL_STOP */
2011-03-18 22:38:11 +01:00
}
2011-03-04 20:59:21 +01:00
}
free ( handle ) ;
2015-12-30 23:45:11 +01:00
free ( zero_alloc ) ;
2016-01-05 14:48:39 +01:00
free ( copy_alloc ) ;
free ( copy ) ;
2013-07-01 23:08:25 +02:00
free ( rehandle_alloc ) ;
2014-01-14 22:10:47 +01:00
free ( failed ) ;
free ( failed_map ) ;
2016-01-20 21:30:00 +01:00
free ( waiting_map ) ;
2015-12-30 23:45:11 +01:00
io_done ( & io ) ;
2021-05-22 11:37:36 +02:00
free ( block_enabled ) ;
2011-03-19 22:00:08 +01:00
2013-09-21 19:11:11 +02:00
if ( state - > opt . expect_recoverable ) {
2026-01-25 21:25:03 +01:00
if ( soft_error + silent_error + io_error = = 0 )
2013-09-21 19:11:11 +02:00
return - 1 ;
} else {
2026-01-25 21:25:03 +01:00
if ( soft_error + silent_error + io_error ! = 0 )
2013-09-21 19:11:11 +02:00
return - 1 ;
}
2026-02-09 23:49:02 +01:00
if ( alert < 0 )
return - 1 ;
2011-03-19 22:00:08 +01:00
return 0 ;
2011-03-04 20:59:21 +01:00
}
2011-03-23 18:54:08 +01:00
int state_sync ( struct snapraid_state * state , 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 ;
2015-02-01 18:48:50 +01:00
block_off_t used_paritymax ;
block_off_t file_paritymax ;
2011-03-12 10:09:59 +01:00
data_off_t size ;
2011-03-18 22:38:11 +01:00
int ret ;
2016-01-04 11:32:02 +01:00
struct snapraid_parity_handle parity_handle [ LEV_MAX ] ;
2026-01-25 21:25:03 +01:00
unsigned process_error ;
2013-09-15 21:12:41 +02:00
unsigned l ;
2014-02-08 16:49:24 +01:00
int skip_sync = 0 ;
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 ;
2015-02-01 18:48:50 +01:00
2016-04-26 21:48:39 +02:00
/* minimum size of the parity files we expect */
2015-02-01 18:48:50 +01:00
used_paritymax = parity_used_size ( state ) ;
/* effective size of the parity files */
file_paritymax = 0 ;
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-02-01 20:21:28 +01:00
log_fatal ( EUSER , " Error in the starting block %u. It is larger than the parity size %u. \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-04-23 21:38:26 +02: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
2014-09-27 00:32:39 +02:00
for ( l = 0 ; l < state - > level ; + + l ) {
2016-04-26 21:48:39 +02:00
data_off_t out_size ;
2015-02-01 18:48:50 +01:00
block_off_t parityblocks ;
2013-09-15 21:12:41 +02:00
/* create the file and open for writing */
2016-09-28 20:32:50 +02:00
ret = parity_create ( & parity_handle [ l ] , & state - > parity [ l ] , l , state - > file_mode , state - > block_size , state - > opt . parity_limit_size ) ;
2012-10-09 23:02:46 +02:00
if ( ret = = - 1 ) {
2014-02-03 22:40:51 +01:00
/* 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 ) ) ;
2012-10-09 23:02:46 +02:00
exit ( EXIT_FAILURE ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2012-10-09 23:02:46 +02:00
}
2015-02-01 18:48:50 +01:00
/* number of block in the parity file */
2016-04-26 21:48:39 +02:00
parity_size ( & parity_handle [ l ] , & out_size ) ;
2015-02-01 18:48:50 +01:00
parityblocks = out_size / state - > block_size ;
/* if the file is too small */
if ( parityblocks < used_paritymax ) {
2026-02-01 20:21:28 +01:00
log_fatal ( ESOFT , " WARNING! The %s parity has only %u blocks instead of %u. \n " , lev_name ( l ) , parityblocks , used_paritymax ) ;
2015-02-01 18:48:50 +01:00
}
/* keep the smallest parity number of blocks */
if ( l = = 0 | | file_paritymax > parityblocks )
file_paritymax = parityblocks ;
}
2016-10-01 22:19:29 +02:00
/* if we do a full parity realloc or computation, having a wrong parity size is expected */
if ( ! state - > opt . force_realloc & & ! state - > opt . force_full ) {
2015-02-01 18:48:50 +01:00
/* if the parities are too small */
if ( file_paritymax < used_paritymax ) {
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( ESOFT , " DANGER! One or more the parity files are smaller than expected! \n " ) ;
2015-02-01 18:48:50 +01:00
if ( file_paritymax ! = 0 ) {
2026-02-01 20:21:28 +01:00
log_fatal ( ESOFT , " If this happens because you are using an old content file, \n " ) ;
log_fatal ( ESOFT , " you can 'sync' anyway using 'snapraid --force-full sync' \n " ) ;
log_fatal ( ESOFT , " to force a full rebuild of the parity. \n " ) ;
2015-02-01 18:48:50 +01:00
} else {
2026-02-01 20:21:28 +01:00
log_fatal ( ESOFT , " It's possible that the parity disks are not mounted. \n " ) ;
log_fatal ( ESOFT , " If instead you are adding a new parity level, you can 'sync' using \n " ) ;
log_fatal ( ESOFT , " 'snapraid --force-full sync' to force a full rebuild of the parity. \n " ) ;
2014-06-15 19:01:07 +02:00
}
2015-02-01 18:48:50 +01:00
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
2012-10-09 23:02:46 +02:00
}
2015-02-01 18:48:50 +01:00
}
2012-10-09 23:02:46 +02:00
2026-01-25 21:25:03 +01:00
process_error = 0 ;
2014-02-08 16:49:24 +01:00
2014-02-10 20:53:47 +01:00
if ( state - > opt . prehash ) {
2015-02-22 14:31:03 +01:00
msg_progress ( " Hashing... \n " ) ;
2014-02-02 23:03:37 +01:00
2014-12-27 23:14:06 +01:00
ret = state_hash_process ( state , blockstart , blockmax , & skip_sync ) ;
if ( ret = = - 1 ) {
/* LCOV_EXCL_START */
2026-01-25 21:25:03 +01:00
+ + process_error ;
2026-05-21 13:01:50 +02:00
/* on any error do NOT proceeed with the sync */
skip_sync = 1 ;
2014-12-27 23:14:06 +01:00
/* LCOV_EXCL_STOP */
}
2016-01-10 23:00:11 +01:00
}
2014-02-02 23:03:37 +01:00
2016-01-10 23:00:11 +01:00
if ( ! skip_sync ) {
2016-04-26 21:48:39 +02:00
msg_progress ( " Resizing... \n " ) ;
/* now change the size of all parities */
for ( l = 0 ; l < state - > level ; + + l ) {
int is_modified ;
2026-04-27 15:43:06 +02:00
/*
* Change the size of the parity file , truncating or extending it
* from this point all the DELETED blocks after the end of the parity are invalid
* and they are automatically removed when we save the new content file
*/
2016-10-04 23:56:13 +02:00
ret = parity_chsize ( & parity_handle [ l ] , & state - > parity [ l ] , & is_modified , size , state - > block_size , state - > opt . skip_fallocate , state - > opt . skip_space_holder ) ;
2016-04-26 21:48:39 +02:00
if ( ret = = - 1 ) {
/* LCOV_EXCL_START */
data_off_t out_size ;
parity_size ( & parity_handle [ l ] , & out_size ) ;
parity_overflow ( state , out_size ) ;
2026-02-01 20:21:28 +01:00
log_fatal ( errno , " WARNING! Without a usable %s file, it isn't possible to sync. \n " , lev_name ( l ) ) ;
2016-04-26 21:48:39 +02:00
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
if ( is_modified )
state - > need_write = 1 ;
}
/* after resizing parity files, refresh again the free info */
state_refresh ( state ) ;
2016-01-10 23:00:11 +01:00
/**
* Save the new state before the sync but after the hashing phase
*
* This allows to recover after an aborted sync , and at the same time
* it allows to recover broken copied / moved files identified in the
* hashing phase .
*
* For example , think at this case :
* - Add some files at the array
* - Run a sync command , it will recompute the parity adding the new files
* - Abort the sync command before it stores the new content file
* - Delete the not yet synced files from the array
* - Run a new sync command
*
* The sync command has no way to know that the parity file was modified
* because the files triggering these changes are now deleted and they aren ' t
* listed in the content file .
* Instead , saving the new content file in advance , keeps track of all the parity
* that may be modified .
*/
2016-05-17 23:17:31 +02:00
if ( ! state - > opt . skip_content_write ) {
if ( state - > need_write )
state_write ( state ) ;
} else {
2026-02-01 20:21:28 +01:00
log_fatal ( EUSER , " WARNING! Skipped state write for --test-skip-content-write option. \n " ) ;
2016-05-17 23:17:31 +02:00
}
2014-02-02 23:03:37 +01:00
2014-02-08 16:49:24 +01:00
/* skip degenerated cases of empty parity, or skipping all */
if ( blockstart < blockmax ) {
2016-01-04 11:32:02 +01:00
ret = state_sync_process ( state , parity_handle , blockstart , blockmax ) ;
2014-02-08 16:49:24 +01:00
if ( ret = = - 1 ) {
/* LCOV_EXCL_START */
2026-01-25 21:25:03 +01:00
+ + process_error ;
2014-02-08 16:49:24 +01:00
/* continue, as we are already exiting */
/* LCOV_EXCL_STOP */
}
} else {
2025-10-13 11:46:36 +02:00
msg_status ( " Nothing to sync. \n " ) ;
2011-03-19 22:00:08 +01:00
}
2011-03-17 16:17:59 +01:00
}
2011-03-04 20:59:21 +01:00
2014-09-27 00:32:39 +02:00
for ( l = 0 ; l < state - > level ; + + l ) {
2016-01-04 11:32:02 +01:00
ret = parity_close ( & parity_handle [ l ] ) ;
2013-09-11 23:30:32 +02:00
if ( ret = = - 1 ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-01-24 19:19:11 +01:00
log_tag ( " parity_%s:%u:%s: Close error. %s. \n " , es ( errno ) , blockmax , lev_config_name ( l ) , strerror ( errno ) ) ;
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 */
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-09-11 23:30:32 +02:00
}
}
2026-01-25 21:25:03 +01:00
if ( process_error ! = 0 )
2011-03-19 22:00:08 +01:00
return - 1 ;
return 0 ;
2011-03-04 20:59:21 +01:00
}
2011-03-06 22:52:50 +01:00