2013-06-29 17:53:40 +02:00
/*
* Copyright ( C ) 2013 Andrea Mazzoleni
*
* This program is free software : you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation , either version 3 of the License , or
* ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with this program . If not , see < http : //www.gnu.org/licenses/>.
*/
# include "portable.h"
2015-01-02 18:52:04 +01:00
# include "support.h"
2013-06-29 17:53:40 +02:00
# include "elem.h"
# include "state.h"
# include "parity.h"
# include "handle.h"
2015-12-30 11:08:20 +01:00
# include "io.h"
2013-12-28 09:10:00 +01:00
# include "raid/raid.h"
2013-06-29 17:53:40 +02:00
/****************************************************************************/
/* scrub */
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 ;
} ;
2015-11-09 23:21:39 +01:00
/**
* Scrub plan to use .
*/
struct snapraid_plan {
2015-12-29 15:20:45 +01:00
struct snapraid_state * state ;
2015-11-09 23:21:39 +01:00
int plan ; /**< One of the SCRUB_*. */
time_t timelimit ; /**< Time limit. Valid only with SCRUB_AUTO. */
block_off_t lastlimit ; /**< Number of blocks allowed with time exactly at ::timelimit. */
block_off_t countlast ; /**< Counter of blocks with time exactly at ::timelimit. */
} ;
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
{
time_t blocktime ;
snapraid_info info ;
2015-11-09 23:21:39 +01:00
/* don't scrub unused blocks in all plans */
2015-12-29 15:20:45 +01:00
info = info_get ( & plan - > state - > infoarr , i ) ;
2015-11-09 23:21:39 +01:00
if ( info = = 0 )
2014-12-29 19:11:03 +01:00
return 0 ;
2015-11-09 23:21:39 +01:00
/* bad blocks are always scrubbed in all plans */
if ( info_get_bad ( info ) )
return 1 ;
switch ( plan - > plan ) {
case SCRUB_FULL :
/* in 'full' plan everything is scrubbed */
return 1 ;
case SCRUB_EVEN :
/* in 'even' plan, scrub only even blocks */
return i % 2 = = 0 ;
case SCRUB_NEW :
/* in 'sync' plan, only blocks never scrubbed */
return info_get_justsynced ( info ) ;
case SCRUB_BAD :
/* in 'bad' plan, only bad blocks (already reported) */
return 0 ;
}
2014-12-29 19:11:03 +01:00
2015-11-09 23:21:39 +01:00
/* if it's too new */
blocktime = info_get_time ( info ) ;
if ( blocktime > plan - > timelimit ) {
/* skip it */
return 0 ;
}
2014-12-29 19:11:03 +01:00
2015-11-09 23:21:39 +01:00
/* if the time is less than the limit, always include */
/* otherwise, check if we reached the last limit count */
if ( blocktime = = plan - > timelimit ) {
/* if we reached the count limit */
if ( plan - > countlast > = plan - > lastlimit ) {
2014-12-29 19:11:03 +01:00
/* skip it */
return 0 ;
}
2015-11-09 23:21:39 +01:00
+ + plan - > countlast ;
2014-12-29 19:11:03 +01:00
}
return 1 ;
}
2015-12-30 11:08:20 +01:00
static void scrub_data_reader ( struct snapraid_worker * worker , struct snapraid_task * task )
2013-06-29 17:53:40 +02:00
{
2015-12-30 11:08:20 +01:00
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 ;
2016-02-09 23:12:33 +01:00
char esc_buffer [ ESC_MAX ] ;
2015-12-30 11:08:20 +01:00
/* 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 11:08:20 +01:00
/* if the block is not used */
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 11:08:20 +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 */
/* This one is really an unexpected error, because we are only reading */
/* and closing a descriptor should never fail */
if ( errno = = EIO ) {
2016-05-29 18:19:13 +02:00
log_tag ( " error:%u:%s:%s: Close EIO error. %s \n " , blockcur , disk - > name , esc_tag ( report - > sub , esc_buffer ) , strerror ( errno ) ) ;
2015-12-30 11:08:20 +01:00
log_fatal ( " DANGER! Unexpected input/output close error in a data disk, it isn't possible to scrub. \n " ) ;
log_fatal ( " Ensure that disk '%s' is sane and that file '%s' can be accessed. \n " , disk - > dir , handle - > path ) ;
log_fatal ( " Stopping at block %u \n " , blockcur ) ;
task - > state = TASK_STATE_IOERROR ;
return ;
}
2016-05-29 18:19:13 +02:00
log_tag ( " error:%u:%s:%s: Close error. %s \n " , blockcur , disk - > name , esc_tag ( report - > sub , esc_buffer ) , strerror ( errno ) ) ;
2015-12-30 11:08:20 +01:00
log_fatal ( " WARNING! Unexpected close error in a data disk, it isn't possible to scrub. \n " ) ;
log_fatal ( " Ensure that file '%s' can be accessed. \n " , handle - > path ) ;
log_fatal ( " Stopping at block %u \n " , blockcur ) ;
task - > state = TASK_STATE_ERROR ;
return ;
/* LCOV_EXCL_STOP */
}
}
ret = handle_open ( handle , task - > file , state - > file_mode , log_error , 0 ) ;
if ( ret = = - 1 ) {
if ( errno = = EIO ) {
/* LCOV_EXCL_START */
2016-05-29 18:19:13 +02:00
log_tag ( " error:%u:%s:%s: Open EIO error. %s \n " , blockcur , disk - > name , esc_tag ( task - > file - > sub , esc_buffer ) , strerror ( errno ) ) ;
2015-12-30 11:08:20 +01:00
log_fatal ( " DANGER! Unexpected input/output open error in a data disk, it isn't possible to scrub. \n " ) ;
log_fatal ( " Ensure that disk '%s' is sane and that file '%s' can be accessed. \n " , disk - > dir , handle - > path ) ;
log_fatal ( " Stopping at block %u \n " , blockcur ) ;
task - > state = TASK_STATE_IOERROR ;
return ;
/* LCOV_EXCL_STOP */
}
2016-05-29 18:19:13 +02:00
log_tag ( " error:%u:%s:%s: Open error. %s \n " , blockcur , disk - > name , esc_tag ( task - > file - > sub , esc_buffer ) , strerror ( errno ) ) ;
2015-12-30 11:08:20 +01:00
task - > state = TASK_STATE_ERROR_CONTINUE ;
return ;
}
/* 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
2016-04-10 22:44:40 +02:00
/* don't check the inode to support filesystem without persistent inodes */
2015-12-30 11:08:20 +01:00
) {
/* report that the block and the file are not synced */
task - > is_timestamp_different = 1 ;
/* follow */
}
/* note that we intentionally don't abort if the file has different attributes */
/* from the last sync, as we are expected to return errors if running */
/* in an unsynced array. This is just like the check command. */
task - > read_size = handle_read ( handle , task - > file_pos , buffer , state - > block_size , log_error , 0 ) ;
if ( task - > read_size = = - 1 ) {
if ( errno = = EIO ) {
2016-05-29 18:19:13 +02:00
log_tag ( " error:%u:%s:%s: Read EIO error at position %u. %s \n " , blockcur , disk - > name , esc_tag ( task - > file - > sub , esc_buffer ) , task - > file_pos , strerror ( errno ) ) ;
2015-12-30 11:08:20 +01:00
log_error ( " Input/Output error in file '%s' at position '%u' \n " , handle - > path , task - > file_pos ) ;
task - > state = TASK_STATE_IOERROR_CONTINUE ;
return ;
}
2016-05-29 18:19:13 +02:00
log_tag ( " error:%u:%s:%s: Read error at position %u. %s \n " , blockcur , disk - > name , esc_tag ( task - > file - > sub , esc_buffer ) , task - > file_pos , strerror ( errno ) ) ;
2015-12-30 11:08:20 +01:00
task - > state = TASK_STATE_ERROR_CONTINUE ;
return ;
}
/* store the path of the opened file */
pathcpy ( task - > path , sizeof ( task - > path ) , handle - > path ) ;
task - > state = TASK_STATE_DONE ;
}
static void scrub_parity_reader ( 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 ;
/* read the parity */
ret = parity_read ( parity_handle , blockcur , buffer , state - > block_size , log_error ) ;
if ( ret = = - 1 ) {
if ( errno = = EIO ) {
log_tag ( " parity_error:%u:%s: Read EIO error. %s \n " , blockcur , lev_config_name ( level ) , strerror ( errno ) ) ;
log_error ( " Input/Output error in parity '%s' at position '%u' \n " , lev_config_name ( level ) , blockcur ) ;
task - > state = TASK_STATE_IOERROR_CONTINUE ;
return ;
}
log_tag ( " parity_error:%u:%s: Read error. %s \n " , blockcur , lev_config_name ( level ) , strerror ( errno ) ) ;
task - > state = TASK_STATE_ERROR_CONTINUE ;
return ;
}
task - > state = TASK_STATE_DONE ;
}
static int state_scrub_process ( struct snapraid_state * state , struct snapraid_parity_handle * parity_handle , block_off_t blockstart , block_off_t blockmax , struct snapraid_plan * plan , time_t now )
{
struct snapraid_io io ;
2013-06-29 17:53:40 +02:00
struct snapraid_handle * handle ;
2013-07-01 23:08:25 +02:00
void * rehandle_alloc ;
struct snapraid_rehash * rehandle ;
2013-06-29 17:53:40 +02:00
unsigned diskmax ;
2015-12-29 15:20:45 +01:00
block_off_t blockcur ;
2013-06-29 17:53:40 +02:00
unsigned j ;
unsigned buffermax ;
data_off_t countsize ;
block_off_t countpos ;
block_off_t countmax ;
block_off_t autosavedone ;
block_off_t autosavelimit ;
block_off_t autosavemissing ;
int ret ;
unsigned error ;
2013-08-07 22:25:41 +02:00
unsigned silent_error ;
2014-12-07 00:07:36 +01:00
unsigned io_error ;
2013-09-15 21:12:41 +02:00
unsigned l ;
2016-01-20 21:30:00 +01:00
unsigned * waiting_map ;
unsigned waiting_mac ;
2016-02-09 23:12:33 +01:00
char esc_buffer [ ESC_MAX ] ;
2021-05-22 11:37:36 +02:00
bit_vect_t * block_enabled ;
2013-06-29 17:53:40 +02:00
/* maps the disks to handles */
2016-01-18 22:38:01 +01:00
handle = handle_mapping ( state , & diskmax ) ;
2013-06-29 17:53:40 +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 + 2 * parity */
buffermax = diskmax + 2 * state - > level ;
2013-06-29 17:53:40 +02:00
2015-12-30 11:08:20 +01:00
/* initialize the io threads */
2016-01-10 19:39:43 +01:00
io_init ( & io , state , state - > opt . io_cache , buffermax , scrub_data_reader , handle , diskmax , scrub_parity_reader , 0 , parity_handle , state - > level ) ;
2013-06-29 17:53:40 +02:00
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 ) ) ;
2016-01-20 21:30:00 +01:00
2013-06-29 17:53:40 +02:00
error = 0 ;
2013-08-07 22:25:41 +02:00
silent_error = 0 ;
2014-12-07 00:07:36 +01:00
io_error = 0 ;
2013-06-29 17:53:40 +02:00
2021-05-22 08:46:53 +02:00
msg_progress ( " Selecting... \n " ) ;
2013-06-29 17:53:40 +02:00
/* first count the number of blocks to process */
countmax = 0 ;
2015-11-09 23:21:39 +01:00
plan - > countlast = 0 ;
2021-05-22 11:37:36 +02:00
block_enabled = calloc_nofail ( 1 , bit_vect_size ( blockmax ) ) ; /* preinitialize to 0 */
2015-12-29 15:20:45 +01:00
for ( blockcur = blockstart ; blockcur < blockmax ; + + blockcur ) {
if ( ! block_is_enabled ( plan , blockcur ) )
2013-06-29 17:53:40 +02:00
continue ;
2021-05-22 11:37:36 +02:00
bit_vect_set ( block_enabled , blockcur ) ;
2013-06-29 21:26:50 +02:00
+ + countmax ;
2013-06-29 17:53:40 +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 */
autosavelimit = state - > autosave / ( diskmax * state - > block_size ) ;
autosavemissing = countmax ; /* blocks to do */
autosavedone = 0 ; /* blocks done */
2014-09-08 21:18:11 +02:00
/* drop until now */
state_usage_waste ( state ) ;
2013-06-29 17:53:40 +02:00
countsize = 0 ;
countpos = 0 ;
2015-12-29 15:20:45 +01:00
2021-05-22 08:46:53 +02:00
msg_progress ( " Scrubbing... \n " ) ;
2015-12-30 11:08:20 +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 11:08:20 +01:00
2025-09-24 11:32:10 +02:00
if ( ! state_progress_begin ( state , blockstart , blockmax , countmax ) )
goto end ;
2015-12-29 15:20:45 +01:00
while ( 1 ) {
2015-12-30 11:08:20 +01:00
unsigned char * buffer_recov [ LEV_MAX ] ;
2013-06-29 17:53:40 +02:00
snapraid_info info ;
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-03-04 19:34:55 +01:00
int block_is_unsynced ;
2013-07-01 23:08:25 +02:00
int rehash ;
2015-12-30 11:08:20 +01:00
void * * buffer ;
2013-06-29 17:53:40 +02:00
2015-12-29 15:20:45 +01:00
/* go to the next block */
2016-01-04 11:24:04 +01:00
blockcur = io_read_next ( & io , & buffer ) ;
2015-12-29 15:20:45 +01:00
if ( blockcur > = blockmax )
break ;
2013-06-29 17:53:40 +02:00
2016-01-31 16:38:29 +01:00
/* until now is scheduling */
state_usage_sched ( state ) ;
2013-06-29 17:53:40 +02:00
/* one more block processed for autosave */
+ + autosavedone ;
- - autosavemissing ;
2013-07-30 20:49:08 +02:00
/* by default process the block, and skip it if something goes wrong */
2013-06-29 17:53:40 +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 ;
2013-06-29 17:53:40 +02:00
2014-03-04 19:34:55 +01:00
/* if all the blocks at this address are synced */
2014-02-01 17:57:26 +01:00
/* if not, parity is not even checked */
2014-03-04 19:34:55 +01:00
block_is_unsynced = 0 ;
2013-07-31 22:31:57 +02:00
2014-12-29 19:11:03 +01:00
/* get block specific info */
2015-12-29 15:20:45 +01:00
info = info_get ( & state - > infoarr , blockcur ) ;
2014-12-29 19:11:03 +01:00
2013-07-01 23:08:25 +02:00
/* if we have to use the old hash */
rehash = info_get_rehash ( info ) ;
2013-06-29 17:53:40 +02:00
/* for each disk, process the block */
2014-09-27 00:32:39 +02:00
for ( j = 0 ; j < diskmax ; + + j ) {
2015-12-30 11:08:20 +01:00
struct snapraid_task * task ;
2013-06-29 17:53:40 +02:00
int read_size ;
2016-04-07 21:39:02 +02:00
unsigned char hash [ HASH_MAX ] ;
2013-06-29 17:53:40 +02:00
struct snapraid_block * block ;
2014-03-04 19:34:55 +01:00
int file_is_unsynced ;
2015-12-30 11:08:20 +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 11:08:20 +01:00
unsigned diskcur ;
2013-07-31 22:31:57 +02:00
2014-03-04 19:34:55 +01:00
/* if the file on this disk is synced */
2014-02-01 17:57:26 +01:00
/* if not, silent errors are assumed as expected error */
2014-03-04 19:34:55 +01:00
file_is_unsynced = 0 ;
2013-06-29 17:53:40 +02:00
2016-01-26 23:25:01 +01:00
/* until now is misc */
state_usage_misc ( state ) ;
2015-12-30 11:08:20 +01:00
/* get the next task */
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 11:08:20 +01:00
/* get the task results */
disk = task - > disk ;
block = task - > block ;
file = task - > file ;
file_pos = task - > file_pos ;
read_size = task - > read_size ;
2013-07-01 23:08:25 +02:00
/* by default no rehash in case of "continue" */
2015-12-30 11:08:20 +01:00
rehandle [ diskcur ] . block = 0 ;
2013-07-01 23:08:25 +02:00
2013-06-29 17:53:40 +02:00
/* if the disk position is not used */
2015-12-30 11:08:20 +01:00
if ( ! disk )
2013-06-29 17:53:40 +02:00
continue ;
2017-11-19 19:33:47 +01:00
state_usage_file ( state , disk , file ) ;
2014-03-04 19:34:55 +01:00
/* if the block is unsynced, errors are expected */
2014-02-01 17:57:26 +01:00
if ( block_has_invalid_parity ( block ) ) {
2014-03-04 19:34:55 +01:00
/* report that the block and the file are not synced */
block_is_unsynced = 1 ;
file_is_unsynced = 1 ;
2014-02-01 17:57:26 +01:00
/* follow */
}
2015-12-30 11:08:20 +01:00
/* if the block is not used */
if ( ! block_has_file ( block ) )
2013-06-29 17:53:40 +02:00
continue ;
2015-12-30 11:08:20 +01:00
/* if the block is unsynced, errors are expected */
if ( task - > is_timestamp_different ) {
2014-03-04 19:34:55 +01:00
/* report that the block and the file are not synced */
block_is_unsynced = 1 ;
file_is_unsynced = 1 ;
2014-02-05 00:00:29 +01:00
/* follow */
2013-07-31 22:31:57 +02:00
}
2015-12-30 11:08:20 +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 11:08:20 +01:00
+ + io_error ;
goto bail ;
2016-11-06 18:27:44 +01:00
/* LCOV_EXCL_STOP */
2015-12-30 11:08:20 +01:00
}
if ( task - > state = = TASK_STATE_ERROR ) {
2016-11-06 18:27:44 +01:00
/* LCOV_EXCL_START */
2015-12-30 11:08:20 +01:00
+ + error ;
goto bail ;
2016-11-06 18:27:44 +01:00
/* LCOV_EXCL_STOP */
2015-12-30 11:08:20 +01:00
}
if ( task - > state = = TASK_STATE_ERROR_CONTINUE ) {
2014-12-27 23:14:06 +01:00
+ + error ;
error_on_this_block = 1 ;
2013-06-29 17:53:40 +02:00
continue ;
}
2015-12-30 11:08:20 +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 ) {
/* LCOV_EXCL_START */
log_fatal ( " DANGER! Too many input/output read error in a data disk, it isn't possible to scrub. \n " ) ;
log_fatal ( " Ensure that disk '%s' is sane and that file '%s' can be accessed. \n " , disk - > dir , task - > path ) ;
log_fatal ( " Stopping at block %u \n " , blockcur ) ;
goto bail ;
/* LCOV_EXCL_STOP */
}
/* otherwise continue */
2015-12-30 11:08:20 +01:00
io_error_on_this_block = 1 ;
continue ;
}
if ( task - > state ! = TASK_STATE_DONE ) {
/* LCOV_EXCL_START */
log_fatal ( " Internal inconsistency in task state \n " ) ;
os_abort ( ) ;
/* LCOV_EXCL_STOP */
}
2014-09-08 21:18:11 +02:00
2013-06-29 17:53:40 +02:00
countsize + = read_size ;
/* now compute the hash */
2013-07-01 23:08:25 +02:00
if ( rehash ) {
2015-12-30 11:08:20 +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 11:08:20 +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 11:08:20 +01:00
memhash ( state - > hash , state - > hashseed , hash , buffer [ diskcur ] , read_size ) ;
2013-07-01 23:08:25 +02:00
}
2013-06-29 17:53:40 +02: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 ) ) {
2013-06-29 17:53:40 +02:00
/* compare the hash */
2016-04-07 21:39:02 +02:00
if ( memcmp ( hash , block - > hash , BLOCK_HASH_SIZE ) ! = 0 ) {
unsigned diff = memdiff ( hash , block - > hash , BLOCK_HASH_SIZE ) ;
2015-09-12 15:19:52 +02:00
2017-11-03 23:46:45 +01:00
log_tag ( " error:%u:%s:%s: Data error at position %u, diff bits %u/%u \n " , blockcur , disk - > name , esc_tag ( file - > sub , esc_buffer ) , file_pos , diff , BLOCK_HASH_SIZE * 8 ) ;
2013-07-31 22:31:57 +02:00
2014-03-04 19:34:55 +01:00
/* it's a silent error only if we are dealing with synced files */
if ( file_is_unsynced ) {
2013-10-03 21:47:25 +02:00
+ + error ;
2013-07-31 22:31:57 +02:00
error_on_this_block = 1 ;
2013-08-07 22:25:41 +02:00
} else {
2017-11-03 23:46:45 +01:00
log_error ( " Data error in file '%s' at position '%u', diff bits %u/%u \n " , task - > path , file_pos , diff , BLOCK_HASH_SIZE * 8 ) ;
2013-08-07 22:25:41 +02:00
+ + silent_error ;
2013-07-31 22:31:57 +02:00
silent_error_on_this_block = 1 ;
2013-08-07 22:25:41 +02:00
}
2013-06-29 17:53:40 +02:00
continue ;
}
}
}
2015-12-30 11:08:20 +01:00
/* buffers for parity read and not computed */
for ( l = 0 ; l < state - > level ; + + l )
buffer_recov [ l ] = buffer [ diskmax + state - > level + l ] ;
for ( ; l < LEV_MAX ; + + l )
buffer_recov [ l ] = 0 ;
2013-06-29 17:53:40 +02:00
2016-01-26 23:25:01 +01:00
/* until now is misc */
state_usage_misc ( state ) ;
2014-09-08 21:18:11 +02:00
2015-12-30 11:08:20 +01:00
/* read the parity */
for ( l = 0 ; l < state - > level ; + + l ) {
struct snapraid_task * task ;
unsigned levcur ;
2013-06-29 17:53:40 +02:00
2016-01-20 21:30:00 +01:00
task = io_parity_read ( & io , & levcur , waiting_map , & waiting_mac ) ;
2014-02-05 00:00:29 +01:00
2015-12-30 11:08:20 +01:00
/* until now is parity */
2016-01-20 21:30:00 +01:00
state_usage_parity ( state , waiting_map , waiting_mac ) ;
2015-12-30 11:08:20 +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 11:08:20 +01:00
+ + io_error ;
goto bail ;
2016-11-06 18:27:44 +01:00
/* LCOV_EXCL_STOP */
2015-12-30 11:08:20 +01:00
}
if ( task - > state = = TASK_STATE_ERROR ) {
2016-11-06 18:27:44 +01:00
/* LCOV_EXCL_START */
2015-12-30 11:08:20 +01:00
+ + error ;
goto bail ;
2016-11-06 18:27:44 +01:00
/* LCOV_EXCL_STOP */
2015-12-30 11:08:20 +01:00
}
if ( task - > state = = TASK_STATE_ERROR_CONTINUE ) {
+ + error ;
error_on_this_block = 1 ;
/* if continuing on error, clear the missing buffer */
buffer_recov [ levcur ] = 0 ;
continue ;
}
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 ) {
/* LCOV_EXCL_START */
log_fatal ( " DANGER! Too many input/output read error in the %s disk, it isn't possible to scrub. \n " , lev_name ( levcur ) ) ;
log_fatal ( " Ensure that disk '%s' is sane and can be read. \n " , lev_config_name ( levcur ) ) ;
log_fatal ( " Stopping at block %u \n " , blockcur ) ;
goto bail ;
/* LCOV_EXCL_STOP */
}
/* otherwise continue */
2015-12-30 11:08:20 +01:00
io_error_on_this_block = 1 ;
2014-09-08 21:18:11 +02:00
2015-12-30 11:08:20 +01:00
/* if continuing on error, clear the missing buffer */
buffer_recov [ levcur ] = 0 ;
continue ;
}
if ( task - > state ! = TASK_STATE_DONE ) {
/* LCOV_EXCL_START */
log_fatal ( " Internal inconsistency in task state \n " ) ;
os_abort ( ) ;
/* LCOV_EXCL_STOP */
2013-09-11 23:30:32 +02:00
}
2015-12-30 11:08:20 +01:00
}
/* if we have read all the data required and it's correct, proceed with the parity check */
if ( ! error_on_this_block & & ! silent_error_on_this_block & & ! io_error_on_this_block ) {
2013-09-11 23:30:32 +02:00
2013-06-29 17:53:40 +02:00
/* compute the parity */
2014-01-08 17:07:13 +01:00
raid_gen ( diskmax , state - > level , state - > block_size , buffer ) ;
2013-06-29 17:53:40 +02:00
/* compare 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 ] & & memcmp ( buffer [ diskmax + l ] , buffer_recov [ l ] , state - > block_size ) ! = 0 ) {
2015-09-12 15:19:52 +02:00
unsigned diff = memdiff ( buffer [ diskmax + l ] , buffer_recov [ l ] , state - > block_size ) ;
2017-11-03 23:46:45 +01:00
log_tag ( " parity_error:%u:%s: Data error, diff bits %u/%u \n " , blockcur , lev_config_name ( l ) , diff , state - > block_size * 8 ) ;
2013-10-03 21:47:25 +02:00
2014-03-04 19:34:55 +01:00
/* it's a silent error only if we are dealing with synced blocks */
if ( block_is_unsynced ) {
2013-10-03 21:47:25 +02:00
+ + error ;
2013-09-11 23:30:32 +02:00
error_on_this_block = 1 ;
} else {
2017-11-03 23:46:45 +01:00
log_fatal ( " Data error in parity '%s' at position '%u', diff bits %u/%u \n " , lev_config_name ( l ) , blockcur , diff , state - > block_size * 8 ) ;
2013-09-11 23:30:32 +02:00
+ + silent_error ;
silent_error_on_this_block = 1 ;
}
}
}
2016-01-26 23:25:01 +01:00
/* until now is raid */
state_usage_raid ( state ) ;
2013-06-29 17:53:40 +02:00
}
2014-12-30 09:45:35 +01:00
if ( silent_error_on_this_block | | io_error_on_this_block ) {
2015-08-27 18:02:12 +02:00
/* set the error status keeping other info */
2015-12-29 15:20:45 +01:00
info_set ( & state - > infoarr , blockcur , info_set_bad ( info ) ) ;
2013-08-07 22:25:41 +02:00
} else if ( error_on_this_block ) {
/* do nothing, as this is a generic error */
2014-03-04 19:34:55 +01:00
/* likely caused by a not synced array */
2013-06-29 17:53:40 +02:00
} else {
2013-10-25 18:03:05 +02:00
/* if rehash is needed */
2013-07-01 23:08:25 +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-07-01 23:08:25 +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-07-01 23:08:25 +02:00
}
}
2013-06-29 17:53:40 +02:00
/* update the time info of the block */
2013-07-01 23:08:25 +02:00
/* and clear any other flag */
2015-12-29 15:20:45 +01:00
info_set ( & state - > infoarr , blockcur , info_make ( now , 0 , 0 , 0 ) ) ;
2013-06-29 17:53:40 +02:00
}
/* mark the state as needing write */
state - > need_write = 1 ;
/* count the number of processed block */
+ + countpos ;
/* 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 */
2013-06-29 17:53:40 +02:00
break ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-06-29 17:53:40 +02: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 ) ;
}
2013-06-29 17:53:40 +02:00
/* autosave */
if ( state - > autosave ! = 0
& & autosavedone > = autosavelimit /* if we have reached the limit */
& & autosavemissing > = autosavelimit /* if we have at least a full step to do */
) {
autosavedone = 0 ; /* restart the counter */
2016-01-26 23:25:01 +01:00
/* until now is misc */
state_usage_misc ( state ) ;
2014-09-08 21:18:11 +02:00
2013-06-29 17:53:40 +02:00
state_progress_stop ( state ) ;
2015-02-22 14:31:03 +01:00
msg_progress ( " Autosaving... \n " ) ;
2013-06-29 17:53:40 +02:00
state_write ( state ) ;
state_progress_restart ( state ) ;
2014-09-08 21:18:11 +02:00
/* drop until now */
state_usage_waste ( state ) ;
2013-06-29 17:53:40 +02:00
}
}
2025-09-24 11:32:10 +02:00
end :
2013-06-29 17:53:40 +02:00
state_progress_end ( state , countpos , countmax , countsize ) ;
2014-09-08 21:18:11 +02:00
state_usage_print ( state ) ;
2014-12-07 00:07:36 +01:00
if ( error | | silent_error | | io_error ) {
2015-02-22 14:31:03 +01:00
msg_status ( " \n " ) ;
msg_status ( " %8u file errors \n " , error ) ;
msg_status ( " %8u io errors \n " , io_error ) ;
msg_status ( " %8u data errors \n " , silent_error ) ;
2013-08-07 22:25:41 +02:00
} else {
2013-08-07 22:48:48 +02:00
/* print the result only if processed something */
if ( countpos ! = 0 )
2015-02-22 14:31:03 +01:00
msg_status ( " Everything OK \n " ) ;
}
if ( error )
2015-04-20 22:07:42 +02:00
log_fatal ( " WARNING! Unexpected file errors! \n " ) ;
2015-02-22 14:31:03 +01:00
if ( io_error )
2015-04-20 22:07:42 +02:00
log_fatal ( " 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 )
2015-04-20 22:07:42 +02:00
log_fatal ( " DANGER! Unexpected data errors! The failing blocks are now marked as bad! \n " ) ;
2015-02-22 14:31:03 +01:00
if ( io_error | | silent_error ) {
2015-04-20 22:07:42 +02:00
log_fatal ( " Use 'snapraid status' to list the bad blocks. \n " ) ;
2020-04-18 12:18:59 +02:00
log_fatal ( " Use 'snapraid -e fix' to recover them. \n " ) ;
2022-12-05 12:13:40 +01:00
log_fatal ( " Use 'snapraid -p bad scrub' to recheck after fixing to clear the bad state. \n " ) ;
2013-08-07 22:25:41 +02:00
}
2015-04-20 22:07:42 +02:00
log_tag ( " summary:error_file:%u \n " , error ) ;
log_tag ( " summary:error_io:%u \n " , io_error ) ;
log_tag ( " summary:error_data:%u \n " , silent_error ) ;
2014-12-27 23:14:06 +01:00
if ( error + silent_error + io_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
2015-04-20 22:07:42 +02:00
log_tag ( " summary:exit:error \n " ) ;
log_flush ( ) ;
2013-09-30 22:24:30 +02:00
2013-06-29 17:53:40 +02:00
bail :
2015-12-30 11:08:20 +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 ;
2013-06-29 17:53:40 +02:00
ret = handle_close ( & handle [ j ] ) ;
if ( ret = = - 1 ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2016-05-29 18:19:13 +02:00
log_tag ( " error:%u:%s:%s: Close error. %s \n " , blockcur , disk - > name , esc_tag ( file - > sub , esc_buffer ) , strerror ( errno ) ) ;
2015-04-20 22:07:42 +02:00
log_fatal ( " DANGER! Unexpected close error in a data disk. \n " ) ;
2013-06-29 17:53:40 +02:00
+ + error ;
/* continue, as we are already exiting */
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-06-29 17:53:40 +02:00
}
}
free ( handle ) ;
2013-07-01 23:08:25 +02:00
free ( rehandle_alloc ) ;
2016-01-20 21:30:00 +01:00
free ( waiting_map ) ;
2015-12-30 11:08:20 +01:00
io_done ( & io ) ;
2021-05-22 11:37:36 +02:00
free ( block_enabled ) ;
2013-06-29 17:53:40 +02:00
2013-08-18 20:30:10 +02:00
if ( state - > opt . expect_recoverable ) {
2014-12-07 00:07:36 +01:00
if ( error + silent_error + io_error = = 0 )
2013-08-18 20:30:10 +02:00
return - 1 ;
} else {
2014-12-07 00:07:36 +01:00
if ( error + silent_error + io_error ! = 0 )
2013-08-18 20:30:10 +02:00
return - 1 ;
}
2013-06-29 17:53:40 +02:00
return 0 ;
}
2014-02-16 21:05:25 +01:00
/**
2015-10-07 22:07:20 +02:00
* Return a * b / c approximated to the upper value .
2014-02-16 21:05:25 +01:00
*/
static uint32_t md ( uint32_t a , uint32_t b , uint32_t c )
{
uint64_t v = a ;
v * = b ;
v + = c - 1 ;
v / = c ;
return v ;
}
2015-11-10 19:37:23 +01:00
int state_scrub ( struct snapraid_state * state , int plan , int olderthan )
2013-06-29 17:53:40 +02:00
{
block_off_t blockmax ;
block_off_t countlimit ;
block_off_t i ;
2015-09-13 15:48:05 +02:00
block_off_t count ;
2013-06-29 17:53:40 +02:00
time_t recentlimit ;
int ret ;
2015-12-30 11:08:20 +01:00
struct snapraid_parity_handle parity_handle [ LEV_MAX ] ;
2015-11-10 19:37:23 +01:00
struct snapraid_plan ps ;
2015-08-27 18:58:25 +02:00
time_t * timemap ;
2013-06-29 17:53:40 +02:00
unsigned error ;
time_t now ;
2013-09-15 21:12:41 +02:00
unsigned l ;
2013-06-29 17:53:40 +02:00
/* get the present time */
now = time ( 0 ) ;
2015-02-22 14:31:03 +01:00
msg_progress ( " Initializing... \n " ) ;
2013-06-29 17:53:40 +02:00
2015-11-10 19:37:23 +01:00
if ( ( plan = = SCRUB_BAD | | plan = = SCRUB_NEW | | plan = = SCRUB_FULL )
2015-09-13 15:48:05 +02:00
& & olderthan > = 0 ) {
/* LCOV_EXCL_START */
2015-11-10 19:37:23 +01:00
log_fatal ( " You can specify -o, --older-than only with a numeric percentage. \n " ) ;
2015-09-13 15:48:05 +02:00
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
2015-02-01 18:48:50 +01:00
blockmax = parity_allocated_size ( state ) ;
2013-06-29 17:53:40 +02:00
2015-11-09 23:21:39 +01:00
/* preinitialize to avoid warnings */
countlimit = 0 ;
recentlimit = 0 ;
2015-12-29 15:20:45 +01:00
ps . state = state ;
2013-07-01 23:08:25 +02:00
if ( state - > opt . force_scrub_even ) {
2015-11-10 19:37:23 +01:00
ps . plan = SCRUB_EVEN ;
} else if ( plan = = SCRUB_FULL ) {
ps . plan = SCRUB_FULL ;
} else if ( plan = = SCRUB_NEW ) {
ps . plan = SCRUB_NEW ;
} else if ( plan = = SCRUB_BAD ) {
ps . plan = SCRUB_BAD ;
2014-11-10 23:06:14 +01:00
} else if ( state - > opt . force_scrub_at ) {
2013-06-29 18:34:10 +02:00
/* scrub the specified amount of blocks */
2015-11-10 19:37:23 +01:00
ps . plan = SCRUB_AUTO ;
2014-11-10 23:06:14 +01:00
countlimit = state - > opt . force_scrub_at ;
2013-06-29 18:34:10 +02:00
recentlimit = now ;
2014-02-16 21:05:25 +01:00
} else {
2015-11-10 19:37:23 +01:00
ps . plan = SCRUB_AUTO ;
if ( plan > = 0 ) {
countlimit = md ( blockmax , plan , 100 ) ;
2015-09-13 15:48:05 +02:00
} else {
2015-11-12 23:04:49 +01:00
/* by default scrub 8.33% of the array (100/12=8.(3)) */
countlimit = md ( blockmax , 1 , 12 ) ;
2015-09-13 15:48:05 +02:00
}
2014-02-18 00:15:34 +01:00
2015-09-13 15:48:05 +02:00
if ( olderthan > = 0 ) {
2013-08-22 23:03:13 +02:00
recentlimit = now - olderthan * 24 * 3600 ;
2015-09-13 15:48:05 +02:00
} else {
/* by default use a 10 day time limit */
recentlimit = now - 10 * 24 * 3600 ;
}
2013-06-29 18:34:10 +02:00
}
2013-06-29 17:53:40 +02:00
/* identify the time limit */
/* we sort all the block times, and we identify the time limit for which we reach the quota */
/* this allow to process first the oldest blocks */
2015-08-27 18:58:25 +02:00
timemap = malloc_nofail ( blockmax * sizeof ( time_t ) ) ;
2013-06-29 17:53:40 +02:00
/* copy the info in the temp vector */
count = 0 ;
2015-04-20 22:07:42 +02:00
log_tag ( " block_count:%u \n " , blockmax ) ;
2014-09-27 00:32:39 +02:00
for ( i = 0 ; i < blockmax ; + + i ) {
2013-06-29 17:53:40 +02:00
snapraid_info info = info_get ( & state - > infoarr , i ) ;
/* skip unused blocks */
if ( info = = 0 )
continue ;
2015-11-09 23:21:39 +01:00
timemap [ count + + ] = info_get_time ( info ) ;
2013-06-29 17:53:40 +02:00
}
if ( ! count ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2015-04-20 22:07:42 +02:00
log_fatal ( " The array appears to be empty. \n " ) ;
2013-06-29 17:53:40 +02:00
exit ( EXIT_FAILURE ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-06-29 17:53:40 +02:00
}
/* sort it */
2015-08-27 18:58:25 +02:00
qsort ( timemap , count , sizeof ( time_t ) , time_compare ) ;
2013-06-29 17:53:40 +02:00
2014-06-29 10:49:13 +02:00
/* output the info map */
i = 0 ;
2015-04-20 22:07:42 +02:00
log_tag ( " info_count:%u \n " , count ) ;
2014-06-29 10:49:13 +02:00
while ( i < count ) {
unsigned j = i + 1 ;
2015-08-27 18:58:25 +02:00
while ( j < count & & timemap [ i ] = = timemap [ j ] )
2014-06-29 10:49:13 +02:00
+ + j ;
2015-08-27 18:58:25 +02:00
log_tag ( " info_time:% " PRIu64 " :%u \n " , ( uint64_t ) timemap [ i ] , j - i ) ;
2014-06-29 10:49:13 +02:00
i = j ;
}
2015-11-09 23:21:39 +01:00
/* compute the limits from count/recentlimit */
2015-11-10 19:37:23 +01:00
if ( ps . plan = = SCRUB_AUTO ) {
2015-11-09 23:21:39 +01:00
/* no more than the full count */
if ( countlimit > count )
countlimit = count ;
/* decrease until we reach the specific recentlimit */
while ( countlimit > 0 & & timemap [ countlimit - 1 ] > recentlimit )
- - countlimit ;
/* if there is something to scrub */
if ( countlimit > 0 ) {
/* get the most recent time we want to scrub */
2015-11-10 19:37:23 +01:00
ps . timelimit = timemap [ countlimit - 1 ] ;
2015-11-09 23:21:39 +01:00
/* count how many entries for this exact time we have to scrub */
/* if the blocks have all the same time, we end with countlimit == lastlimit */
2015-11-10 19:37:23 +01:00
ps . lastlimit = 1 ;
while ( countlimit > ps . lastlimit & & timemap [ countlimit - ps . lastlimit - 1 ] = = ps . timelimit )
+ + ps . lastlimit ;
2015-11-09 23:21:39 +01:00
} else {
/* if nothing to scrub, disable also other limits */
2015-11-10 19:37:23 +01:00
ps . timelimit = 0 ;
ps . lastlimit = 0 ;
2015-11-09 23:21:39 +01:00
}
2013-06-29 17:53:40 +02:00
2015-11-09 23:21:39 +01:00
log_tag ( " count_limit:%u \n " , countlimit ) ;
2015-11-20 19:29:04 +01:00
log_tag ( " time_limit:% " PRIu64 " \n " , ( uint64_t ) ps . timelimit ) ;
log_tag ( " last_limit:%u \n " , ps . lastlimit ) ;
2013-08-22 21:18:50 +02:00
}
2013-06-29 17:53:40 +02:00
/* free the temp vector */
2015-08-27 18:58:25 +02:00
free ( timemap ) ;
2013-06-29 17:53:40 +02:00
/* open the file for reading */
2014-09-27 00:32:39 +02:00
for ( l = 0 ; l < state - > level ; + + l ) {
2016-09-28 20:32:50 +02:00
ret = parity_open ( & parity_handle [ l ] , & state - > parity [ l ] , l , state - > file_mode , state - > block_size , state - > opt . parity_limit_size ) ;
2013-09-11 23:30:32 +02:00
if ( ret = = - 1 ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2015-04-20 22:07:42 +02:00
log_fatal ( " WARNING! Without an accessible %s file, it isn't possible to scrub. \n " , lev_name ( l ) ) ;
2013-09-11 23:30:32 +02:00
exit ( EXIT_FAILURE ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-09-11 23:30:32 +02:00
}
}
2013-06-29 17:53:40 +02:00
error = 0 ;
2015-12-30 11:08:20 +01:00
ret = state_scrub_process ( state , parity_handle , 0 , blockmax , & ps , now ) ;
2013-06-29 17:53:40 +02:00
if ( ret = = - 1 ) {
+ + error ;
/* continue, as we are already exiting */
}
2014-09-27 00:32:39 +02:00
for ( l = 0 ; l < state - > level ; + + l ) {
2015-12-30 11:08:20 +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 */
2015-04-20 22:07:42 +02:00
log_fatal ( " DANGER! Unexpected close error in %s disk. \n " , lev_name ( l ) ) ;
2013-09-11 23:30:32 +02:00
+ + error ;
/* 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
}
}
2013-06-29 17:53:40 +02:00
/* abort if required */
if ( error ! = 0 )
return - 1 ;
return 0 ;
}