2011-03-04 20:59:21 +01:00
/*
* Copyright ( C ) 2011 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"
# include "util.h"
# include "elem.h"
# include "state.h"
# include "parity.h"
# include "handle.h"
2011-04-23 21:38:26 +02:00
# include "raid.h"
2011-03-04 20:59:21 +01:00
/****************************************************************************/
/* check */
2011-04-23 21:38:26 +02:00
struct failed_struct {
unsigned index ;
struct snapraid_block * block ;
struct snapraid_handle * handle ;
} ;
2011-04-28 21:38:25 +02:00
/**
* Checks if a block hash matches the specified buffer .
*/
static int blockcmp ( struct snapraid_state * state , struct snapraid_block * block , unsigned char * buffer , unsigned char * buffer_zero )
{
unsigned char hash [ HASH_SIZE ] ;
unsigned size ;
size = block_file_size ( block , state - > block_size ) ;
/* now compute the hash of the valid part */
memhash ( state - > hash , hash , buffer , size ) ;
/* compare the hash */
if ( memcmp ( hash , block - > hash , HASH_SIZE ) ! = 0 ) {
return - 1 ;
}
/* compare to the end of the block */
if ( size < state - > block_size ) {
if ( memcmp ( buffer + size , buffer_zero + size , state - > block_size - size ) ! = 0 ) {
return - 1 ;
}
}
return 0 ;
}
2011-04-23 21:38:26 +02:00
/**
* Repair errors .
2011-10-01 19:18:32 +02:00
* Returns < 0 if failure for missing strategy , > 0 if data is wrong and we cannot rebuild correctly , 0 on success .
2011-04-23 21:38:26 +02:00
* If success , the parity and qarity are computed in the buffer variable .
*/
2011-04-24 10:24:05 +02:00
static int repair ( struct snapraid_state * state , unsigned i , unsigned diskmax , struct failed_struct * failed , unsigned failed_count , unsigned char * * buffer , unsigned char * buffer_parity , unsigned char * buffer_qarity , unsigned char * buffer_zero )
2011-04-23 21:38:26 +02:00
{
2011-04-24 10:24:05 +02:00
unsigned j ;
int error = 0 ;
2011-04-23 21:38:26 +02:00
/* no fix required */
if ( failed_count = = 0 ) {
2011-10-01 19:18:32 +02:00
/* recompute only parity and qarity */
2011-04-24 10:24:05 +02:00
raid_gen ( state - > level , buffer , diskmax , state - > block_size ) ;
2011-04-23 21:38:26 +02:00
return 0 ;
}
if ( failed_count = = 1 & & buffer_parity ! = 0 ) {
2011-04-24 10:24:05 +02:00
/* copy the redundancy to use */
memcpy ( buffer [ diskmax ] , buffer_parity , state - > block_size ) ;
2011-04-23 21:38:26 +02:00
2011-04-24 10:24:05 +02:00
/* recover */
raid5_recov_data ( buffer , diskmax , state - > block_size , failed [ 0 ] . index ) ;
2011-04-23 21:38:26 +02:00
2011-08-31 23:33:43 +02:00
/* check if the recovered blocks are OK */
2011-04-24 10:24:05 +02:00
for ( j = 0 ; j < 1 ; + + j ) {
2011-04-28 21:38:25 +02:00
if ( blockcmp ( state , failed [ j ] . block , buffer [ failed [ j ] . index ] , buffer_zero ) ! = 0 )
break ;
2011-04-23 21:38:26 +02:00
}
2011-04-24 10:24:05 +02:00
if ( j = = 1 ) {
2011-08-31 23:33:43 +02:00
/* recompute all the redundancy information */
2011-04-24 10:24:05 +02:00
raid_gen ( state - > level , buffer , diskmax , state - > block_size ) ;
return 0 ;
2011-04-23 21:38:26 +02:00
}
2011-06-20 20:35:04 +02:00
fprintf ( stderr , " error:%u:parity: Data error \n " , i ) ;
2011-04-24 10:24:05 +02:00
+ + error ;
2011-04-23 21:38:26 +02:00
}
if ( failed_count = = 1 & & buffer_qarity ! = 0 ) {
2011-04-24 10:24:05 +02:00
/* copy the redundancy to use */
memcpy ( buffer [ diskmax + 1 ] , buffer_qarity , state - > block_size ) ;
raid6_recov_datap ( buffer , diskmax , state - > block_size , failed [ 0 ] . index , buffer_zero ) ;
2011-08-31 23:33:43 +02:00
/* check if the recovered blocks are OK */
2011-04-24 10:24:05 +02:00
for ( j = 0 ; j < 1 ; + + j ) {
2011-04-28 21:38:25 +02:00
if ( blockcmp ( state , failed [ j ] . block , buffer [ failed [ j ] . index ] , buffer_zero ) ! = 0 )
break ;
2011-04-24 10:24:05 +02:00
}
if ( j = = 1 ) {
2011-08-31 23:33:43 +02:00
/* recompute all the redundancy information */
2011-04-24 10:24:05 +02:00
raid_gen ( state - > level , buffer , diskmax , state - > block_size ) ;
return 0 ;
}
2011-06-20 20:35:04 +02:00
fprintf ( stderr , " error:%u:qarity: Data error \n " , i ) ;
2011-04-24 10:24:05 +02:00
+ + error ;
2011-04-23 21:38:26 +02:00
}
if ( failed_count = = 2 & & buffer_parity ! = 0 & & buffer_qarity ! = 0 ) {
2011-04-24 10:24:05 +02:00
/* copy the redundancy to use */
memcpy ( buffer [ diskmax ] , buffer_parity , state - > block_size ) ;
memcpy ( buffer [ diskmax + 1 ] , buffer_qarity , state - > block_size ) ;
/* recover */
raid6_recov_2data ( buffer , diskmax , state - > block_size , failed [ 0 ] . index , failed [ 1 ] . index , buffer_zero ) ;
2011-08-31 23:33:43 +02:00
/* check if the recovered blocks are OK */
2011-04-24 10:24:05 +02:00
for ( j = 0 ; j < 2 ; + + j ) {
2011-04-28 21:38:25 +02:00
if ( blockcmp ( state , failed [ j ] . block , buffer [ failed [ j ] . index ] , buffer_zero ) ! = 0 )
break ;
2011-04-24 10:24:05 +02:00
}
if ( j = = 2 ) {
2011-08-31 23:33:43 +02:00
/* recompute all the redundancy information */
2011-04-24 10:24:05 +02:00
raid_gen ( state - > level , buffer , diskmax , state - > block_size ) ;
return 0 ;
}
2011-06-20 20:35:04 +02:00
fprintf ( stderr , " error:%u:parity/qarity: Data error \n " , i ) ;
2011-04-24 10:24:05 +02:00
+ + error ;
2011-04-23 21:38:26 +02:00
}
/* no more stragety to fix */
2011-04-24 10:24:05 +02:00
if ( error )
return error ;
else
return - 1 ;
2011-04-23 21:38:26 +02:00
}
2011-10-09 07:51:25 +02:00
static int state_check_process ( struct snapraid_state * state , int fix , struct snapraid_parity * parity , struct snapraid_parity * qarity , block_off_t blockstart , block_off_t blockmax )
2011-03-04 20:59:21 +01:00
{
struct snapraid_handle * handle ;
2011-09-23 21:48:50 +02:00
unsigned diskmax ;
2011-03-12 10:09:59 +01:00
block_off_t i ;
2011-03-04 20:59:21 +01:00
unsigned j ;
2011-04-23 21:38:26 +02:00
void * buffer_alloc ;
unsigned char * buffer_aligned ;
unsigned char * * buffer ;
unsigned buffermax ;
2011-03-04 20:59:21 +01:00
int ret ;
2011-03-28 20:55:57 +02:00
data_off_t countsize ;
block_off_t countpos ;
block_off_t countmax ;
2011-03-04 20:59:21 +01:00
unsigned error ;
unsigned unrecoverable_error ;
unsigned recovered_error ;
2011-06-11 12:05:02 +02:00
struct failed_struct * failed ;
2011-03-04 20:59:21 +01:00
2011-09-23 21:48:50 +02:00
handle = handle_map ( state , & diskmax ) ;
2011-04-24 10:24:05 +02:00
/* we need disk + 2 for each parity level buffers + 1 zero buffer */
buffermax = diskmax + state - > level * 2 + 1 ;
2011-04-23 21:38:26 +02:00
buffer_aligned = malloc_nofail_align ( buffermax * state - > block_size , & buffer_alloc ) ;
buffer = malloc_nofail ( buffermax * sizeof ( void * ) ) ;
for ( i = 0 ; i < buffermax ; + + i ) {
buffer [ i ] = buffer_aligned + i * state - > block_size ;
}
2011-04-24 10:24:05 +02:00
memset ( buffer [ buffermax - 1 ] , 0 , state - > block_size ) ;
2011-03-04 20:59:21 +01:00
2011-06-11 12:05:02 +02:00
failed = malloc_nofail ( diskmax * sizeof ( struct failed_struct ) ) ;
2011-03-04 20:59:21 +01:00
error = 0 ;
unrecoverable_error = 0 ;
recovered_error = 0 ;
2011-03-30 00:14:43 +02:00
/* first count the number of blocks to process */
countmax = 0 ;
for ( i = blockstart ; i < blockmax ; + + i ) {
int one_tocheck ;
/* for each disk */
one_tocheck = 0 ;
for ( j = 0 ; j < diskmax ; + + j ) {
2011-10-01 19:18:32 +02:00
struct snapraid_block * block = 0 ;
if ( handle [ j ] . disk )
block = disk_block_get ( handle [ j ] . disk , i ) ;
2011-03-30 00:14:43 +02:00
if ( block
2011-04-28 23:09:19 +02:00
& & block_flag_has ( block , BLOCK_HAS_HASH ) /* only if the block is hashed */
& & ! file_flag_has ( block_file_get ( block ) , FILE_IS_EXCLUDED ) /* only if the file is not filtered out */
2011-03-30 00:14:43 +02:00
) {
one_tocheck = 1 ;
break ;
}
}
/* if no block to check skip */
if ( ! one_tocheck )
continue ;
+ + countmax ;
}
2011-08-26 14:18:38 +02:00
/* check all the blocks in files */
2011-03-28 20:55:57 +02:00
countsize = 0 ;
countpos = 0 ;
2011-06-20 20:35:04 +02:00
state_progress_begin ( state , blockstart , blockmax , countmax ) ;
2011-03-04 20:59:21 +01:00
for ( i = blockstart ; i < blockmax ; + + i ) {
2011-04-23 21:38:26 +02:00
unsigned failed_count ;
2011-03-30 00:14:43 +02:00
int one_tocheck ;
2011-03-23 18:54:08 +01:00
int all_parity ;
2011-05-05 21:12:42 +02:00
unsigned char * buffer_parity ;
unsigned char * buffer_qarity ;
unsigned char * buffer_zero ;
2011-03-04 20:59:21 +01:00
2011-03-22 23:23:45 +01:00
/* for each disk */
2011-03-30 00:14:43 +02:00
one_tocheck = 0 ;
2011-03-04 20:59:21 +01:00
for ( j = 0 ; j < diskmax ; + + j ) {
2011-10-01 19:18:32 +02:00
struct snapraid_block * block = 0 ;
if ( handle [ j ] . disk )
block = disk_block_get ( handle [ j ] . disk , i ) ;
2011-03-30 00:14:43 +02:00
if ( block
2011-04-28 23:09:19 +02:00
& & block_flag_has ( block , BLOCK_HAS_HASH ) /* only if the block is hashed */
& & ! file_flag_has ( block_file_get ( block ) , FILE_IS_EXCLUDED ) /* only if the file is not filtered out */
2011-03-30 00:14:43 +02:00
) {
one_tocheck = 1 ;
2011-03-23 18:54:08 +01:00
break ;
2011-03-04 20:59:21 +01:00
}
}
2011-03-30 00:14:43 +02:00
/* if no block to check skip */
if ( ! one_tocheck )
2011-03-04 20:59:21 +01:00
continue ;
2011-03-23 18:54:08 +01:00
all_parity = 1 ; /* if all hashed block have parity computed */
2011-04-23 21:38:26 +02:00
failed_count = 0 ; /* number of failed blocks */
2011-03-23 18:54:08 +01:00
/* for each disk, process the block */
2011-03-04 20:59:21 +01:00
for ( j = 0 ; j < diskmax ; + + j ) {
int read_size ;
2011-04-12 22:16:35 +02:00
unsigned char hash [ HASH_SIZE ] ;
2011-03-04 20:59:21 +01:00
struct snapraid_block * block ;
2011-10-01 19:18:32 +02:00
/* if the disk position is not used */
if ( ! handle [ j ] . disk ) {
/* use an empty block */
memset ( buffer [ j ] , 0 , state - > block_size ) ;
continue ;
}
/* if the disk block is not used */
2011-03-04 20:59:21 +01:00
block = disk_block_get ( handle [ j ] . disk , i ) ;
2011-04-23 21:38:26 +02:00
if ( ! block ) {
/* use an empty block */
memset ( buffer [ j ] , 0 , state - > block_size ) ;
2011-03-04 20:59:21 +01:00
continue ;
2011-04-23 21:38:26 +02:00
}
2011-03-04 20:59:21 +01:00
2011-03-22 23:23:45 +01:00
/* we try to check and fix only if the block is hashed */
2011-05-05 21:12:42 +02:00
/* if no hash is present also no parity is present, */
/* but we expect to had it excluded from the parity computation, */
/* so it's correct to assume it filled with 0 and DO NOT reset all_parity */
2011-04-28 23:09:19 +02:00
if ( ! block_flag_has ( block , BLOCK_HAS_HASH ) ) {
2011-04-23 21:38:26 +02:00
/* use an empty block */
memset ( buffer [ j ] , 0 , state - > block_size ) ;
2011-03-04 20:59:21 +01:00
continue ;
2011-04-23 21:38:26 +02:00
}
2011-03-04 20:59:21 +01:00
2011-03-23 18:54:08 +01:00
/* keep track if at least one hashed block has no parity */
2011-06-12 14:18:36 +02:00
/* if at least one block has no parity, doesn't make sense to check/fix the parity */
2011-05-05 21:12:42 +02:00
/* as errors are the normal condition */
2011-04-28 23:09:19 +02:00
if ( ! block_flag_has ( block , BLOCK_HAS_PARITY ) )
2011-03-23 18:54:08 +01:00
all_parity = 0 ;
2011-08-31 23:33:43 +02:00
/* if the file is different than the current one, close it */
if ( handle [ j ] . file ! = block_file_get ( block ) ) {
ret = handle_close ( & handle [ j ] ) ;
if ( ret = = - 1 ) {
fprintf ( stderr , " DANGER! Unexpected close error in a data disk, it isn't possible to sync. \n " ) ;
printf ( " Stopping at block %u \n " , i ) ;
+ + unrecoverable_error ;
goto bail ;
}
2011-03-18 22:38:11 +01:00
}
2011-03-04 20:59:21 +01:00
if ( fix ) {
2011-04-28 21:38:25 +02:00
/* if fixing, create the file, open for writing and resize if required */
2011-06-11 09:38:19 +02:00
ret = handle_create ( & handle [ j ] , block_file_get ( block ) ) ;
2011-03-18 22:38:11 +01:00
if ( ret = = - 1 ) {
2011-06-11 09:38:19 +02:00
if ( errno = = EACCES ) {
fprintf ( stderr , " WARNING! Please give write permission to the file. \n " ) ;
} else {
fprintf ( stderr , " DANGER! Without a working data disk, it isn't possible to fix errors on it. \n " ) ;
}
2011-06-11 12:05:02 +02:00
printf ( " Stopping at block %u \n " , i ) ;
2011-03-19 22:00:08 +01:00
+ + unrecoverable_error ;
2011-03-18 22:38:11 +01:00
goto bail ;
}
2011-04-28 21:38:25 +02:00
2011-06-12 14:18:36 +02:00
/* check if the file was larger and now truncated */
if ( ret = = 1 ) {
fprintf ( stderr , " File '%s' is larger than expected. \n " , handle [ j ] . path ) ;
2011-06-20 20:35:04 +02:00
fprintf ( stderr , " error:%u:%s:%s: Size error \n " , i , handle [ j ] . disk - > name , block_file_get ( block ) - > sub ) ;
2011-06-12 14:18:36 +02:00
+ + error ;
/* this is already a recovered error */
2011-06-20 20:35:04 +02:00
fprintf ( stderr , " fixed:%u:%s:%s: Fixed size \n " , i , handle [ j ] . disk - > name , block_file_get ( block ) - > sub ) ;
2011-06-12 14:18:36 +02:00
+ + recovered_error ;
2011-04-28 21:38:25 +02:00
}
2011-03-04 20:59:21 +01:00
} else {
/* if checking, open the file for reading */
2011-04-28 23:09:19 +02:00
ret = handle_open ( & handle [ j ] , block_file_get ( block ) ) ;
2011-03-04 20:59:21 +01:00
if ( ret = = - 1 ) {
2011-06-11 12:05:02 +02:00
/* save the failed block for the check/fix */
failed [ failed_count ] . index = j ;
failed [ failed_count ] . block = block ;
failed [ failed_count ] . handle = & handle [ j ] ;
2011-04-23 21:38:26 +02:00
+ + failed_count ;
2011-03-04 20:59:21 +01:00
2011-06-20 20:35:04 +02:00
fprintf ( stderr , " error:%u:%s:%s: Open error at position %u \n " , i , handle [ j ] . disk - > name , block_file_get ( block ) - > sub , block_file_pos ( block ) ) ;
2011-03-04 20:59:21 +01:00
+ + error ;
continue ;
}
2011-04-28 21:38:25 +02:00
/* check if it's a larger file, but not if already notified */
2011-04-28 23:17:06 +02:00
if ( ! file_flag_has ( block_file_get ( block ) , FILE_IS_LARGER )
& & handle [ j ] . st . st_size > block_file_get ( block ) - > size
) {
fprintf ( stderr , " File '%s' is larger than expected. \n " , handle [ j ] . path ) ;
2011-06-20 20:35:04 +02:00
fprintf ( stderr , " error:%u:%s:%s: Size error \n " , i , handle [ j ] . disk - > name , block_file_get ( block ) - > sub ) ;
2011-06-12 14:18:36 +02:00
+ + error ;
2011-04-28 21:38:25 +02:00
/* if fragmented, it may be reopened, so store the notification */
2011-06-11 12:05:02 +02:00
/* to prevent to signal and count the error more than one time */
2011-04-28 23:09:19 +02:00
file_flag_set ( block_file_get ( block ) , FILE_IS_LARGER ) ;
2011-04-28 21:38:25 +02:00
}
2011-03-04 20:59:21 +01:00
}
2011-04-23 21:38:26 +02:00
read_size = handle_read ( & handle [ j ] , block , buffer [ j ] , state - > block_size ) ;
2011-03-04 20:59:21 +01:00
if ( read_size = = - 1 ) {
2011-06-11 12:05:02 +02:00
/* save the failed block for the check/fix */
failed [ failed_count ] . index = j ;
failed [ failed_count ] . block = block ;
failed [ failed_count ] . handle = & handle [ j ] ;
2011-04-23 21:38:26 +02:00
+ + failed_count ;
2011-03-04 20:59:21 +01:00
2011-06-20 20:35:04 +02:00
fprintf ( stderr , " error:%u:%s:%s: Read error at position %u \n " , i , handle [ j ] . disk - > name , block_file_get ( block ) - > sub , block_file_pos ( block ) ) ;
2011-03-04 20:59:21 +01:00
+ + error ;
continue ;
}
/* now compute the hash */
2011-04-23 21:38:26 +02:00
memhash ( state - > hash , hash , buffer [ j ] , read_size ) ;
2011-03-04 20:59:21 +01:00
/* compare the hash */
2011-04-12 22:16:35 +02:00
if ( memcmp ( hash , block - > hash , HASH_SIZE ) ! = 0 ) {
2011-06-11 12:05:02 +02:00
/* save the failed block for the check/fix */
failed [ failed_count ] . index = j ;
failed [ failed_count ] . block = block ;
failed [ failed_count ] . handle = & handle [ j ] ;
2011-04-23 21:38:26 +02:00
+ + failed_count ;
2011-03-04 20:59:21 +01:00
2011-06-20 20:35:04 +02:00
fprintf ( stderr , " error:%u:%s:%s: Data error at position %u \n " , i , handle [ j ] . disk - > name , block_file_get ( block ) - > sub , block_file_pos ( block ) ) ;
2011-03-04 20:59:21 +01:00
+ + error ;
continue ;
}
2011-03-28 20:55:57 +02:00
countsize + = read_size ;
2011-03-04 20:59:21 +01:00
}
2011-05-05 21:12:42 +02:00
/* buffers for parity read and not computed */
if ( state - > level = = 1 ) {
buffer_parity = buffer [ diskmax + 1 ] ;
buffer_qarity = 0 ;
} else {
buffer_parity = buffer [ diskmax + 2 ] ;
buffer_qarity = buffer [ diskmax + 3 ] ;
}
buffer_zero = buffer [ buffermax - 1 ] ;
2011-03-04 20:59:21 +01:00
2011-05-05 21:12:42 +02:00
/* read the parity */
2011-10-09 07:51:25 +02:00
if ( parity ) {
ret = parity_read ( parity , i , buffer_parity , state - > block_size ) ;
2011-05-05 21:12:42 +02:00
if ( ret = = - 1 ) {
buffer_parity = 0 ; /* no parity to use */
2011-06-20 20:35:04 +02:00
fprintf ( stderr , " error:%u:parity: Read error \n " , i ) ;
2011-05-05 21:12:42 +02:00
+ + error ;
2011-04-23 21:38:26 +02:00
}
2011-05-05 21:12:42 +02:00
} else {
buffer_parity = 0 ;
}
2011-04-24 10:24:05 +02:00
2011-05-05 21:12:42 +02:00
/* read the qarity */
if ( state - > level > = 2 ) {
2011-10-09 07:51:25 +02:00
if ( qarity ) {
ret = parity_read ( qarity , i , buffer_qarity , state - > block_size ) ;
2011-04-23 21:38:26 +02:00
if ( ret = = - 1 ) {
2011-05-05 21:12:42 +02:00
buffer_qarity = 0 ; /* no qarity to use */
2011-03-04 20:59:21 +01:00
2011-06-20 20:35:04 +02:00
fprintf ( stderr , " error:%u:qarity: Read error \n " , i ) ;
2011-03-04 20:59:21 +01:00
+ + error ;
}
2011-04-23 21:38:26 +02:00
} else {
2011-05-05 21:12:42 +02:00
buffer_qarity = 0 ;
2011-03-04 20:59:21 +01:00
}
2011-05-05 21:12:42 +02:00
}
2011-03-04 20:59:21 +01:00
2011-05-05 21:12:42 +02:00
ret = repair ( state , i , diskmax , failed , failed_count , buffer , buffer_parity , buffer_qarity , buffer_zero ) ;
if ( ret ! = 0 ) {
/* increment the number of errors */
if ( ret > 0 )
error + = ret ;
+ + unrecoverable_error ;
2011-06-11 12:05:02 +02:00
2011-08-31 23:33:43 +02:00
if ( fix ) {
/* keep track of damaged files */
for ( j = 0 ; j < failed_count ; + + j ) {
file_flag_set ( block_file_get ( failed [ j ] . block ) , FILE_IS_DAMAGED ) ;
}
}
2011-06-11 12:05:02 +02:00
/* print a list of all the errors in files */
for ( j = 0 ; j < failed_count ; + + j ) {
2011-10-09 07:51:25 +02:00
fprintf ( stderr , " unrecoverable:%u:%s:%s: Unrecoverable error at position %u \n " , i , failed [ j ] . handle - > disk - > name , block_file_get ( failed [ j ] . block ) - > sub , block_file_pos ( failed [ j ] . block ) ) ;
2011-06-11 12:05:02 +02:00
}
2011-05-05 21:12:42 +02:00
} else {
/* check parity and q-parity only if all the blocks have it computed */
/* if you check/fix after a partial sync, it's OK to have parity errors on the blocks with invalid parity */
if ( all_parity ) {
2011-04-23 21:38:26 +02:00
/* check the parity */
if ( buffer_parity ! = 0 & & memcmp ( buffer_parity , buffer [ diskmax ] , state - > block_size ) ! = 0 ) {
buffer_parity = 0 ;
2011-03-04 20:59:21 +01:00
2011-06-20 20:35:04 +02:00
fprintf ( stderr , " error:%u:parity: Data error \n " , i ) ;
2011-04-23 21:38:26 +02:00
+ + error ;
2011-03-04 20:59:21 +01:00
}
2011-04-23 21:38:26 +02:00
/* check the qarity */
if ( state - > level > = 2 ) {
if ( buffer_qarity ! = 0 & & memcmp ( buffer_qarity , buffer [ diskmax + 1 ] , state - > block_size ) ! = 0 ) {
buffer_qarity = 0 ;
2011-06-20 20:35:04 +02:00
fprintf ( stderr , " error:%u:qarity: Data error \n " , i ) ;
2011-04-23 21:38:26 +02:00
+ + error ;
}
2011-03-18 22:38:11 +01:00
}
2011-05-05 21:12:42 +02:00
}
2011-03-04 20:59:21 +01:00
2011-05-05 21:12:42 +02:00
if ( fix ) {
/* update the fixed files */
for ( j = 0 ; j < failed_count ; + + j ) {
/* do not fix if the file filtered out */
if ( file_flag_has ( block_file_get ( failed [ j ] . block ) , FILE_IS_EXCLUDED ) )
continue ;
2011-04-23 21:38:26 +02:00
2011-05-05 21:12:42 +02:00
ret = handle_write ( failed [ j ] . handle , failed [ j ] . block , buffer [ failed [ j ] . index ] , state - > block_size ) ;
if ( ret = = - 1 ) {
2011-08-31 23:33:43 +02:00
/* mark the file as damaged */
file_flag_set ( block_file_get ( failed [ j ] . block ) , FILE_IS_DAMAGED ) ;
2011-06-11 09:38:19 +02:00
if ( errno = = EACCES ) {
fprintf ( stderr , " WARNING! Please give write permission to the file. \n " ) ;
} else {
2011-06-11 12:05:02 +02:00
/* we do not use DANGER because it could be ENOSPC which is not always correctly reported */
2011-06-11 09:38:19 +02:00
fprintf ( stderr , " WARNING! Without a working data disk, it isn't possible to fix errors on it. \n " ) ;
}
2011-06-11 12:05:02 +02:00
printf ( " Stopping at block %u \n " , i ) ;
2011-05-05 21:12:42 +02:00
+ + unrecoverable_error ;
goto bail ;
2011-04-23 21:38:26 +02:00
}
2011-08-31 23:33:43 +02:00
/* mark the file as fixed */
file_flag_set ( block_file_get ( failed [ j ] . block ) , FILE_IS_FIXED ) ;
2011-06-20 20:35:04 +02:00
fprintf ( stderr , " fixed:%u:%s:%s: Fixed data error at position %u \n " , i , failed [ j ] . handle - > disk - > name , block_file_get ( failed [ j ] . block ) - > sub , block_file_pos ( failed [ j ] . block ) ) ;
2011-05-05 21:12:42 +02:00
+ + recovered_error ;
}
/* update parity and q-parity only if all the blocks have it computed */
/* if you check/fix after a partial sync, you do not want to fix parity */
/* for blocks that are going to have it computed in the sync completion */
if ( all_parity ) {
2011-04-23 21:38:26 +02:00
/* update the parity */
2011-10-09 07:51:25 +02:00
if ( buffer_parity = = 0 & & parity ) {
ret = parity_write ( parity , i , buffer [ diskmax ] , state - > block_size ) ;
2011-04-23 21:38:26 +02:00
if ( ret = = - 1 ) {
2011-06-11 12:05:02 +02:00
/* we do not use DANGER because it could be ENOSPC which is not always correctly reported */
2011-05-05 21:12:42 +02:00
fprintf ( stderr , " WARNING! Without a working Parity disk, it isn't possible to fix errors on it. \n " ) ;
2011-06-11 12:05:02 +02:00
printf ( " Stopping at block %u \n " , i ) ;
2011-04-23 21:38:26 +02:00
+ + unrecoverable_error ;
goto bail ;
}
2011-06-20 20:35:04 +02:00
fprintf ( stderr , " fixed:%u:parity: Fixed data error \n " , i ) ;
2011-04-23 21:38:26 +02:00
+ + recovered_error ;
}
/* update the qarity */
if ( state - > level > = 2 ) {
2011-10-09 07:51:25 +02:00
if ( buffer_qarity = = 0 & & qarity ) {
ret = parity_write ( qarity , i , buffer [ diskmax + 1 ] , state - > block_size ) ;
2011-04-23 21:38:26 +02:00
if ( ret = = - 1 ) {
2011-06-11 12:05:02 +02:00
/* we do not use DANGER because it could be ENOSPC which is not always correctly reported */
2011-04-23 21:38:26 +02:00
fprintf ( stderr , " WARNING! Without a working Q-Parity disk, it isn't possible to fix errors on it. \n " ) ;
2011-06-11 12:05:02 +02:00
printf ( " Stopping at block %u \n " , i ) ;
2011-04-23 21:38:26 +02:00
+ + unrecoverable_error ;
goto bail ;
}
2011-06-20 20:35:04 +02:00
fprintf ( stderr , " fixed:%u:qarity: Fixed data error \n " , i ) ;
2011-04-24 10:24:05 +02:00
+ + recovered_error ;
}
2011-04-23 21:38:26 +02:00
}
}
2011-03-04 20:59:21 +01:00
}
}
2011-08-31 23:33:43 +02:00
if ( fix ) {
/* for all the files of this block check if we need to fix the modification time */
for ( j = 0 ; j < diskmax ; + + j ) {
2011-10-01 19:18:32 +02:00
struct snapraid_block * block = 0 ;
2011-08-31 23:33:43 +02:00
struct snapraid_file * collide_file ;
uint64_t inode ;
2011-10-01 19:18:32 +02:00
if ( handle [ j ] . disk )
block = disk_block_get ( handle [ j ] . disk , i ) ;
2011-08-31 23:33:43 +02:00
if ( ! block ) {
/* if no block, no file and nothing to do */
continue ;
}
/* if it isn't the last block in the file */
if ( ! block_is_last ( block ) ) {
/* nothing to do */
continue ;
}
2011-10-10 20:42:52 +02:00
/* if the file is damaged, meaning that a fix failed */
if ( file_flag_has ( handle [ j ] . file , FILE_IS_DAMAGED ) ) {
/* rename it */
char path_from [ PATH_MAX ] ;
char path_to [ PATH_MAX ] ;
pathprint ( path_from , sizeof ( path_from ) , " %s%s " , handle [ j ] . disk - > dir , handle [ j ] . file - > sub ) ;
pathprint ( path_to , sizeof ( path_to ) , " %s%s.unrecoverable " , handle [ j ] . disk - > dir , handle [ j ] . file - > sub ) ;
ret = rename ( path_from , path_to ) ;
if ( ret ! = 0 ) {
fprintf ( stderr , " Error renaming '%s' to '%s'. %s. \n " , path_from , path_to , strerror ( errno ) ) ;
fprintf ( stderr , " WARNING! Without a working data disk, it isn't possible to fix errors on it. \n " ) ;
printf ( " Stopping at block %u \n " , i ) ;
+ + unrecoverable_error ;
goto bail ;
}
/* and do not set the time */
continue ;
}
/* if the file is not fixed */
if ( ! file_flag_has ( handle [ j ] . file , FILE_IS_FIXED ) ) {
2011-08-31 23:33:43 +02:00
/* nothing to do */
continue ;
}
inode = handle [ j ] . st . st_ino ;
/* search for the corresponding inode */
collide_file = tommy_hashdyn_search ( & handle [ j ] . disk - > inodeset , file_inode_compare , & inode , file_inode_hash ( inode ) ) ;
/* if the inode is in the database and it refers at a different file name, */
/* we can fix the file time ONLY if the time and size allow to differentiates */
/* between the two files */
/* for example, suppose we delete a bunch of files with all the same size and time, */
/* when recreating them the inodes may be reused in a different order, */
/* and at the next sync some files may have matching inode/size/time even if different name */
/* not allowing sync to detect that the file is changed and not renamed */
if ( ! collide_file /* if not in the databse, there is no collision */
| | strcmp ( collide_file - > sub , handle [ j ] . file - > sub ) = = 0 /* if the name is the same, it's the right collision */
| | collide_file - > size ! = handle [ j ] . file - > size /* if the size is different, the collision is identified */
| | collide_file - > mtime ! = handle [ j ] . file - > mtime /* if the mtime is different, the collision is identified */
) {
/* set the original modification time */
ret = handle_utime ( & handle [ j ] ) ;
if ( ret = = - 1 ) {
/* mark the file as damaged */
file_flag_set ( handle [ j ] . file , FILE_IS_DAMAGED ) ;
fprintf ( stderr , " WARNING! Without a working data disk, it isn't possible to fix errors on it. \n " ) ;
printf ( " Stopping at block %u \n " , i ) ;
+ + unrecoverable_error ;
goto bail ;
}
} else {
fprintf ( stderr , " collision:%s:%s:%s: Not setting modification time to avoid inode collision \n " , handle [ j ] . disk - > name , block_file_get ( block ) - > sub , collide_file - > sub ) ;
}
}
}
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 */
2011-06-20 20:35:04 +02:00
if ( state_progress ( state , i , countpos , countmax , countsize ) ) {
2011-03-04 20:59:21 +01:00
break ;
2011-03-18 22:38:11 +01:00
}
2011-03-04 20:59:21 +01:00
}
2011-10-15 08:39:52 +02:00
/* check all the files with empty size */
/* they have no block, and so skipped by previous checks */
for ( i = 0 ; i < diskmax ; + + i ) {
tommy_node * node ;
struct snapraid_disk * disk ;
if ( ! handle [ i ] . disk )
continue ;
/* for each file in the disk */
disk = handle [ i ] . disk ;
node = disk - > filelist ;
while ( node ) {
char path [ PATH_MAX ] ;
struct stat st ;
struct snapraid_file * file ;
int failed = 0 ;
file = node - > data ;
node = node - > next ; /* next node */
/* if not empty, it's already checked and continue to the next one */
if ( file - > size ! = 0 ) {
continue ;
}
/* if excluded continue to the next one */
if ( file_flag_has ( file , FILE_IS_EXCLUDED ) ) {
continue ;
}
/* stat the file */
pathprint ( path , sizeof ( path ) , " %s%s " , disk - > dir , file - > sub ) ;
ret = stat ( path , & st ) ;
if ( ret = = - 1 ) {
failed = 1 ;
fprintf ( stderr , " Error stating empty file '%s'. %s. \n " , path , strerror ( errno ) ) ;
fprintf ( stderr , " error:%s:%s: Empty file stat error \n " , disk - > name , file - > sub ) ;
+ + error ;
} else if ( ! S_ISREG ( st . st_mode ) ) {
failed = 1 ;
fprintf ( stderr , " error:%s:%s: Empty file error for not regular file \n " , disk - > name , file - > sub ) ;
+ + error ;
} else if ( st . st_size ! = 0 ) {
failed = 1 ;
fprintf ( stderr , " error:%s:%s: Empty file error for size '% " PRIu64 " ' \n " , disk - > name , file - > sub , st . st_size ) ;
+ + error ;
}
if ( fix & & failed ) {
int f ;
/* create the ancestor directories */
ret = handle_ancestor ( path ) ;
if ( ret ! = 0 ) {
fprintf ( stderr , " WARNING! Without a working data disk, it isn't possible to fix errors on it. \n " ) ;
printf ( " Stopping \n " ) ;
+ + unrecoverable_error ;
goto bail ;
}
/* create it */
/* do not follow links to ensure to open the real file */
f = open ( path , O_WRONLY | O_CREAT | O_TRUNC | O_BINARY | O_NOFOLLOW , 0600 ) ;
if ( f = = - 1 ) {
fprintf ( stderr , " Error creating empty file '%s'. %s. \n " , path , strerror ( errno ) ) ;
if ( errno = = EACCES ) {
fprintf ( stderr , " WARNING! Please give write permission to the file. \n " ) ;
} else {
/* we do not use DANGER because it could be ENOSPC which is not always correctly reported */
fprintf ( stderr , " WARNING! Without a working data disk, it isn't possible to fix errors on it. \n " ) ;
}
printf ( " Stopping \n " ) ;
+ + unrecoverable_error ;
goto bail ;
}
/* close it */
close ( f ) ;
fprintf ( stderr , " fixed:%s:%s: Fixed empty file \n " , disk - > name , file - > sub ) ;
+ + recovered_error ;
}
}
}
2011-08-26 14:18:38 +02:00
/* check all the links */
for ( i = 0 ; i < diskmax ; + + i ) {
tommy_node * node ;
2011-10-01 19:18:32 +02:00
struct snapraid_disk * disk ;
if ( ! handle [ i ] . disk )
continue ;
2011-08-26 14:18:38 +02:00
/* for each link in the disk */
2011-10-01 19:18:32 +02:00
disk = handle [ i ] . disk ;
2011-08-26 14:18:38 +02:00
node = disk - > linklist ;
while ( node ) {
char path [ PATH_MAX ] ;
char linkto [ PATH_MAX ] ;
struct snapraid_link * link ;
int failed = 0 ;
link = node - > data ;
node = node - > next ; /* next node */
/* if excluded continue to the next one */
if ( link_flag_has ( link , FILE_IS_EXCLUDED ) ) {
continue ;
}
/* read the symlink */
pathprint ( path , sizeof ( path ) , " %s%s " , disk - > dir , link - > sub ) ;
ret = readlink ( path , linkto , sizeof ( linkto ) ) ;
if ( ret < 0 ) {
failed = 1 ;
fprintf ( stderr , " Error reading symlink '%s'. %s. \n " , path , strerror ( errno ) ) ;
fprintf ( stderr , " symlink_error:%s:%s: Symlink read error \n " , disk - > name , link - > sub ) ;
+ + error ;
} else if ( ret = = sizeof ( linkto ) ) {
failed = 1 ;
fprintf ( stderr , " Error reading symlink '%s'. Symlink too long. \n " , path ) ;
fprintf ( stderr , " symlink_error:%s:%s: Symlink read error \n " , disk - > name , link - > sub ) ;
+ + error ;
} else {
linkto [ ret ] = 0 ;
if ( strcmp ( linkto , link - > linkto ) ! = 0 ) {
failed = 1 ;
fprintf ( stderr , " symlink_error:%s:%s: Symlink data error '%s' instead of '%s' \n " , disk - > name , link - > sub , linkto , link - > linkto ) ;
+ + error ;
}
}
if ( fix & & failed ) {
/* create the ancestor directories */
ret = handle_ancestor ( path ) ;
if ( ret ! = 0 ) {
fprintf ( stderr , " WARNING! Without a working data disk, it isn't possible to fix errors on it. \n " ) ;
printf ( " Stopping \n " ) ;
+ + unrecoverable_error ;
goto bail ;
}
/* if it exists, it must be deleted before recreating */
ret = remove ( path ) ;
if ( ret ! = 0 & & errno ! = ENOENT ) {
fprintf ( stderr , " Error removing '%s'. %s. \n " , path , strerror ( errno ) ) ;
fprintf ( stderr , " WARNING! Without a working data disk, it isn't possible to fix errors on it. \n " ) ;
printf ( " Stopping \n " ) ;
+ + unrecoverable_error ;
goto bail ;
}
/* create it */
ret = symlink ( link - > linkto , path ) ;
if ( ret ! = 0 ) {
fprintf ( stderr , " Error writing symlink '%s'. %s. \n " , path , strerror ( errno ) ) ;
if ( errno = = EACCES ) {
fprintf ( stderr , " WARNING! Please give write permission to the symlink. \n " ) ;
} else {
/* we do not use DANGER because it could be ENOSPC which is not always correctly reported */
fprintf ( stderr , " WARNING! Without a working data disk, it isn't possible to fix errors on it. \n " ) ;
}
printf ( " Stopping \n " ) ;
+ + unrecoverable_error ;
goto bail ;
}
fprintf ( stderr , " symlink_fixed:%s:%s: Fixed symlink error \n " , disk - > name , link - > sub ) ;
+ + recovered_error ;
}
}
}
2011-06-20 20:35:04 +02:00
state_progress_end ( state , countpos , countmax , countsize ) ;
2011-03-04 20:59:21 +01:00
2011-03-18 22:38:11 +01:00
bail :
for ( j = 0 ; j < diskmax ; + + j ) {
ret = handle_close ( & handle [ j ] ) ;
if ( ret = = - 1 ) {
2011-05-05 21:12:42 +02:00
fprintf ( stderr , " DANGER! Unexpected close error in a data disk. \n " ) ;
+ + unrecoverable_error ;
/* continue, as we are already exiting */
2011-03-18 22:38:11 +01:00
}
}
2011-03-17 16:17:59 +01:00
if ( error | | recovered_error | | unrecoverable_error ) {
2011-03-04 20:59:21 +01:00
if ( error )
2011-03-18 22:38:11 +01:00
printf ( " %u read/data errors \n " , error ) ;
2011-03-04 20:59:21 +01:00
else
2011-03-18 22:38:11 +01:00
printf ( " No read/data errors \n " ) ;
2011-03-17 16:17:59 +01:00
if ( fix ) {
if ( recovered_error )
printf ( " %u recovered errors \n " , recovered_error ) ;
else
printf ( " No recovered errors \n " ) ;
}
2011-03-04 20:59:21 +01:00
if ( unrecoverable_error )
printf ( " %u UNRECOVERABLE errors \n " , unrecoverable_error ) ;
else
printf ( " No unrecoverable errors \n " ) ;
} else {
printf ( " No error \n " ) ;
}
2011-06-11 12:05:02 +02:00
free ( failed ) ;
2011-03-04 20:59:21 +01:00
free ( handle ) ;
2011-04-23 21:38:26 +02:00
free ( buffer_alloc ) ;
free ( buffer ) ;
/* fails if some error are present after the run */
if ( fix ) {
if ( state - > expect_unrecoverable ) {
if ( unrecoverable_error = = 0 )
return - 1 ;
} else {
if ( unrecoverable_error ! = 0 )
return - 1 ;
}
} else {
2011-04-24 10:24:05 +02:00
if ( state - > expect_unrecoverable ) {
2011-04-23 21:38:26 +02:00
if ( unrecoverable_error = = 0 )
return - 1 ;
2011-04-24 10:24:05 +02:00
} else if ( state - > expect_recoverable ) {
2011-04-23 21:38:26 +02:00
if ( error = = 0 )
return - 1 ;
} else {
if ( error ! = 0 | | unrecoverable_error ! = 0 )
return - 1 ;
}
}
2011-03-04 20:59:21 +01:00
2011-03-18 22:38:11 +01:00
return 0 ;
2011-03-04 20:59:21 +01:00
}
2011-03-23 18:54:08 +01:00
void state_check ( struct snapraid_state * state , int fix , block_off_t blockstart , block_off_t blockcount )
2011-03-04 20:59:21 +01:00
{
2011-03-12 10:09:59 +01:00
block_off_t blockmax ;
data_off_t size ;
2011-03-04 20:59:21 +01:00
int ret ;
2011-10-09 07:51:25 +02:00
struct snapraid_parity parity ;
struct snapraid_parity qarity ;
struct snapraid_parity * parity_ptr ;
struct snapraid_parity * qarity_ptr ;
2011-04-23 21:38:26 +02:00
unsigned error ;
2011-03-04 20:59:21 +01:00
if ( fix )
printf ( " Checking and fixing... \n " ) ;
else
printf ( " Checking... \n " ) ;
blockmax = parity_resize ( state ) ;
2011-03-12 10:09:59 +01:00
size = blockmax * ( data_off_t ) state - > block_size ;
2011-03-04 20:59:21 +01:00
2011-03-17 16:17:59 +01:00
if ( blockstart > blockmax ) {
2011-03-18 22:38:11 +01:00
fprintf ( stderr , " Error in the specified starting block %u. It's bigger than the parity size %u. \n " , blockstart , blockmax ) ;
2011-03-04 20:59:21 +01:00
exit ( EXIT_FAILURE ) ;
}
2011-03-23 18:54:08 +01:00
/* adjust the number of block to process */
if ( blockcount ! = 0 & & blockstart + blockcount < blockmax ) {
blockmax = blockstart + blockcount ;
}
2011-03-04 20:59:21 +01:00
if ( fix ) {
/* if fixing, create the file and open for writing */
2011-03-18 22:38:11 +01:00
/* if it fails, we cannot continue */
2011-10-09 07:51:25 +02:00
parity_ptr = & parity ;
ret = parity_create ( parity_ptr , state - > parity , size ) ;
if ( ret = = - 1 ) {
2011-04-23 21:38:26 +02:00
fprintf ( stderr , " WARNING! Without an accessible Parity file, it isn't possible to fix any error. \n " ) ;
2011-03-18 22:38:11 +01:00
exit ( EXIT_FAILURE ) ;
}
2011-04-23 21:38:26 +02:00
if ( state - > level > = 2 ) {
2011-10-09 07:51:25 +02:00
qarity_ptr = & qarity ;
ret = parity_create ( qarity_ptr , state - > qarity , size ) ;
if ( ret = = - 1 ) {
2011-04-23 21:38:26 +02:00
fprintf ( stderr , " WARNING! Without an accessible Q-Parity file, it isn't possible to fix any error. \n " ) ;
exit ( EXIT_FAILURE ) ;
}
} else {
2011-10-09 07:51:25 +02:00
qarity_ptr = 0 ;
2011-04-23 21:38:26 +02:00
}
2011-03-04 20:59:21 +01:00
} else {
/* if checking, open the file for reading */
2011-03-18 22:38:11 +01:00
/* it may fail if the file doesn't exist, in this case we continue to check the files */
2011-10-09 07:51:25 +02:00
parity_ptr = & parity ;
ret = parity_open ( parity_ptr , state - > parity ) ;
if ( ret = = - 1 ) {
2011-04-23 21:38:26 +02:00
printf ( " No accessible Parity file, only files will be checked. \n " ) ;
2011-03-18 22:38:11 +01:00
/* continue anyway */
2011-10-09 07:51:25 +02:00
parity_ptr = 0 ;
2011-03-18 22:38:11 +01:00
}
2011-04-23 21:38:26 +02:00
if ( state - > level > = 2 ) {
2011-10-09 07:51:25 +02:00
qarity_ptr = & qarity ;
ret = parity_open ( qarity_ptr , state - > qarity ) ;
if ( ret = = - 1 ) {
2011-04-23 21:38:26 +02:00
printf ( " No accessible Q-Parity file, only files will be checked. \n " ) ;
/* continue anyway */
2011-10-09 07:51:25 +02:00
qarity_ptr = 0 ;
2011-04-23 21:38:26 +02:00
}
} else {
2011-10-09 07:51:25 +02:00
qarity_ptr = 0 ;
2011-04-23 21:38:26 +02:00
}
2011-03-04 20:59:21 +01:00
}
2011-04-23 21:38:26 +02:00
error = 0 ;
2011-03-19 22:00:08 +01:00
2011-03-17 16:17:59 +01:00
/* skip degenerated cases of empty parity, or skipping all */
if ( blockstart < blockmax ) {
2011-10-09 07:51:25 +02:00
ret = state_check_process ( state , fix , parity_ptr , qarity_ptr , blockstart , blockmax ) ;
2011-03-17 16:17:59 +01:00
if ( ret = = - 1 ) {
2011-04-23 21:38:26 +02:00
+ + error ;
2011-03-19 22:00:08 +01:00
/* continue, as we are already exiting */
2011-03-17 16:17:59 +01:00
}
2011-03-04 20:59:21 +01:00
}
2011-03-18 22:38:11 +01:00
/* try to close only if opened */
2011-10-09 07:51:25 +02:00
if ( parity_ptr ) {
ret = parity_close ( parity_ptr ) ;
2011-03-18 22:38:11 +01:00
if ( ret = = - 1 ) {
2011-05-05 21:12:42 +02:00
fprintf ( stderr , " DANGER! Unexpected close error in Parity disk. \n " ) ;
2011-04-23 21:38:26 +02:00
+ + error ;
2011-03-19 22:00:08 +01:00
/* continue, as we are already exiting */
2011-03-18 22:38:11 +01:00
}
2011-03-04 20:59:21 +01:00
}
2011-03-18 22:38:11 +01:00
2011-04-23 21:38:26 +02:00
if ( state - > level > = 2 ) {
2011-10-09 07:51:25 +02:00
if ( qarity_ptr ) {
ret = parity_close ( qarity_ptr ) ;
2011-04-23 21:38:26 +02:00
if ( ret = = - 1 ) {
2011-05-05 21:12:42 +02:00
fprintf ( stderr , " DANGER! Unexpected close error in Q-Parity disk. \n " ) ;
2011-04-23 21:38:26 +02:00
+ + error ;
/* continue, as we are already exiting */
}
}
}
/* abort if error are present */
if ( error ! = 0 )
2011-03-19 22:00:08 +01:00
exit ( EXIT_FAILURE ) ;
}
2011-03-22 23:23:45 +01:00