From ba1d6ed2b6fb8e6a3435ffc5c18482195361fa78 Mon Sep 17 00:00:00 2001 From: Andrea Mazzoleni Date: Sat, 1 Feb 2014 17:57:26 +0100 Subject: [PATCH] Merges NEW and CHG blocks and introduces REP blocks. This is a preparation for using REP blocks for containing hashes of new data. --- check.c | 209 +++++++++++++++++++++++++++++++++----------------------- elem.h | 132 ++++++++++++++++++++++------------- scan.c | 55 +++++++++------ scrub.c | 11 +++ state.c | 87 ++++++++++++++--------- sync.c | 117 ++++++++++++++++++------------- 6 files changed, 376 insertions(+), 235 deletions(-) diff --git a/check.c b/check.c index c1466e82..55d1e163 100644 --- a/check.c +++ b/check.c @@ -34,7 +34,8 @@ */ struct failed_struct { /** - * If the block needs to be recovered and rewritten to the disk. + * If we know for sure that the block is garbage or missing + * and it needs to be recovered and rewritten to the disk. */ int is_bad; @@ -102,7 +103,11 @@ static int is_hash_matching(struct snapraid_state* state, int rehash, unsigned d /* check if the recovered blocks are OK */ for(j=0;jlevel, state->block_size, buffer); + return 0; + } + /* Here we have to try two different strategies to recover, because in case the 'sync' */ /* process is aborted, we don't know if the parity data is really updated just like after 'sync', */ /* or if it still represents the state before the 'sync'. */ - /* As first, we assume that the parity is already computed for the current state */ + /* Note that if the 'sync' ends normally, we don't have any DELETED, REP and CHG blocks */ + /* and the two strategies are identical */ + + /* As first, we assume that the parity IS updated for the current state */ /* and that we are going to recover the state after the last 'sync'. */ - /* This is the normal condition. */ - /* In this case, parity contains correct info for BLK, CHG (new version) and NEW blocks, */ - /* and not for DELETED ones. */ + /* In this case, parity contains info from BLK, REP and CHG blocks, */ + /* but not for DELETED. */ /* We need to put in the recovering process only the bad blocks, because all the */ - /* others already contains the correct data read frin disk, and the parity is correctly computed for them. */ - /* We are interested to recover BLK, CHG and NEW blocks if they are marked as bad, */ + /* others already contains the correct data read from disk, and the parity is correctly computed for them. */ + /* We are interested to recover BLK, REP and CHG blocks if they are marked as bad, */ /* but we are not interested in DELETED ones. */ n = 0; @@ -296,22 +313,14 @@ static int repair(struct snapraid_state* state, int rehash, unsigned pos, unsign assert(block_state != BLOCK_STATE_DELETED); /* we cannot have bad DELETED blocks */ - if (block_state == BLOCK_STATE_BLK) { /* if we have the hash for it */ - /* try to fetch the block using the hash */ - if (state_import_fetch(state, rehash, failed[j].block->hash, buffer[failed[j].index]) == 0) { - /* we have corrected it! */ - } else { - /* otherwise try to recover it */ - failed_map[n] = j; - ++n; - - /* we have something to try to recover */ - something_to_recover = 1; - } + /* if we have the hash for it */ + if ((block_state == BLOCK_STATE_BLK || block_state == BLOCK_STATE_REP) + /* try to fetch the block using the known hash */ + && state_import_fetch(state, rehash, failed[j].block->hash, buffer[failed[j].index]) == 0 + ) { + /* we already have corrected it! */ } else { - assert(block_state == BLOCK_STATE_CHG || block_state == BLOCK_STATE_NEW); - - /* otherwise it's CHG or NEW and we try to recover it */ + /* otherwise try to recover it */ failed_map[n] = j; ++n; @@ -330,7 +339,7 @@ static int repair(struct snapraid_state* state, int rehash, unsigned pos, unsign ret = repair_step(state, rehash, pos, diskmax, failed, failed_map, n, buffer, buffer_recov, buffer_zero); if (ret == 0) { - /* reprocess the blocks for NEW and CHG ones, for which we don't have a hash to check */ + /* reprocess the CHG blocks, for which we don't have a hash to check */ /* if they were BAD we have to use some euristics to ensure that we have recovered */ /* the state after the sync. If unsure, we assume the worst case */ @@ -339,7 +348,22 @@ static int repair(struct snapraid_state* state, int rehash, unsigned pos, unsign if (failed[j].is_bad) { unsigned block_state = block_state_get(failed[j].block); - if (block_state == BLOCK_STATE_NEW) { + /* BLK and REP blocks are always OK, because at this point */ + /* we have already checked their hash */ + if (block_state != BLOCK_STATE_CHG) { + assert(block_state == BLOCK_STATE_BLK || block_state == BLOCK_STATE_REP); + continue; + } + + /* for CHG blocks we have to 'guess' if they are correct or not */ + + /* if the hash is invalid we cannot check the result */ + /* this could happen if we have lost this information */ + /* after an aborted sync */ + if (hash_is_invalid(failed[j].block->hash)) { + /* it may contain garbage */ + failed[j].is_outofdate = 1; + } else if (hash_is_zero(failed[j].block->hash)) { /* if the block is not filled with 0, we are sure to have */ /* restored it to the state after the 'sync' */ /* instead, if the block is filled with 0, it could be either that the */ @@ -349,14 +373,7 @@ static int repair(struct snapraid_state* state, int rehash, unsigned pos, unsign /* it may contain garbage */ failed[j].is_outofdate = 1; } - } else if (block_state == BLOCK_STATE_CHG) { - /* if the hash is a bogus value we cannot check the result */ - /* this could happen if we have lost this information */ - /* after an aborted sync */ - if (memcmp(failed[j].block->hash, buffer_zero, HASH_SIZE) == 0) { - /* it may contain garbage */ - failed[j].is_outofdate = 1; - } else + } else { /* if the hash is different than the previous one, we are sure to have */ /* restored it to the state after the 'sync' */ /* instead, if the hash matches, it could be either that the */ @@ -375,14 +392,13 @@ static int repair(struct snapraid_state* state, int rehash, unsigned pos, unsign if (ret > 0) error += ret; - /* Now assume that the parity computation was not updated at the current state, */ + /* Now assume that the parity IS NOT updated at the current state, */ /* but still represent the state before the last 'sync' process. */ - /* This may happen only if a 'sync' is aborted by a crash or manually. */ - /* In this case, parity contains info for BLK, CHG (old version) and DELETED blocks, */ - /* but not for CHG (new version) and NEW ones. */ + /* In this case, parity contains info from BLK, REP (old version), CHG (old version) and DELETED blocks, */ + /* but not for REP (new version) and CHG (new version). */ /* We are interested to recover BLK ones marked as bad, */ - /* but we are not interested to recover NEW and CHG (new version) blocks, even if marked as bad, */ - /* because we don't have parity for them and it's just impossible, */ + /* but we are not interested to recover CHG (new version) and REP (new version) blocks, */ + /* even if marked as bad, because we don't have parity for them and it's just impossible, */ /* and we are not interested to recover DELETED ones. */ n = 0; something_to_recover = 0; /* keep track if there is at least one block to fix */ @@ -391,16 +407,30 @@ static int repair(struct snapraid_state* state, int rehash, unsigned pos, unsign if (block_state == BLOCK_STATE_DELETED || block_state == BLOCK_STATE_CHG + || block_state == BLOCK_STATE_REP ) { - /* If the block is CHG or DELETED, we don't have the original content of block, */ + /* If the block is CHG, REP or DELETED, we don't have the original content of block, */ /* and we must try to recover it. */ - /* This apply to CHG blocks even if they are not marked bad, */ + /* This apply to CHG and REP blocks even if they are not marked bad, */ /* because the parity is computed with old content, and not with the new one. */ /* Note that this recovering is done just to make possible to recover any other BLK one, */ - /* we are not really interested in DELETED and CHG (old version) ones. */ + /* we are not really interested in DELETED, CHG (old version) and REP (old version). */ + + if (block_state == BLOCK_STATE_CHG + && hash_is_zero(failed[j].block->hash) + ) { + /* If the block was a ZERO block, restore it to the original 0 as before the 'sync' */ + /* We do this to just allow recovering of other BLK ones */ + + memset(buffer[failed[j].index], 0, state->block_size); + /* note that from now the buffer is definitively lost */ + /* we can do this only because it's the last retry of recovering */ + + /* try to fetch the old block using the old hash for CHG and DELETED blocks */ + } else if ((block_state == BLOCK_STATE_CHG || block_state == BLOCK_STATE_DELETED) + && hash_is_real(failed[j].block->hash) + && state_import_fetch(state, rehash, failed[j].block->hash, buffer[failed[j].index]) == 0) { - /* try to fetch the old block using the old hash */ - if (state_import_fetch(state, rehash, failed[j].block->hash, buffer[failed[j].index]) == 0) { /* note that from now the buffer is definitively lost */ /* we can do this only because it's the last retry of recovering */ } else { @@ -416,18 +446,6 @@ static int repair(struct snapraid_state* state, int rehash, unsigned pos, unsign /* note that if the block is not marked as is_bad, */ /* we are not going to write it in the disk */ failed[j].is_outofdate = 1; - } else if (block_state == BLOCK_STATE_NEW) { - /* If the block is NEW, restore it to the original 0 as before the 'sync' */ - /* Also in this case, we do this to just allow recovering of other BLK ones */ - - memset(buffer[failed[j].index], 0, state->block_size); - /* note that from now the buffer is definitively lost */ - /* we can do this only because it's the last retry of recovering */ - - /* mark that we have restored an old state */ - /* note that if the block is not marked as is_bad, */ - /* we are not going to write it in the disk */ - failed[j].is_outofdate = 1; } else if (failed[j].is_bad) { /* If the block is bad we don't know its content, and we try to recover it */ /* At this point, we can have only BLK ones */ @@ -436,7 +454,7 @@ static int repair(struct snapraid_state* state, int rehash, unsigned pos, unsign /* we have something we are interested to recover */ something_to_recover = 1; - + /* we try to recover it */ failed_map[n] = j; ++n; @@ -447,7 +465,7 @@ static int repair(struct snapraid_state* state, int rehash, unsigned pos, unsign if (something_to_recover) { ret = repair_step(state, rehash, pos, diskmax, failed, failed_map, n, buffer, buffer_recov, buffer_zero); if (ret == 0) { - /* we alreay marked as outdated NEW and CHG blocks, we don't need to do it again */ + /* we alreay marked as outdated CHG and REP blocks, we don't need to do it again */ return 0; } if (ret > 0) @@ -608,6 +626,8 @@ static int state_check_process(struct snapraid_state* state, int check, int fix, /* mark the parity as invalid, and don't try to check/fix it */ /* because it will be recomputed at the next sync */ valid_parity = 0; + + /* follow */ } /* if the block is DELETED */ @@ -714,7 +734,7 @@ static int state_check_process(struct snapraid_state* state, int check, int fix, read_size = handle_read(&handle[j], block, buffer[j], state->block_size, stdlog); if (read_size == -1) { /* save the failed block for the check/fix */ - failed[failed_count].is_bad = 1; + failed[failed_count].is_bad = 1; /* it's bad because we cannot read it */ failed[failed_count].is_outofdate = 0; failed[failed_count].index = j; failed[failed_count].block = block; @@ -728,11 +748,13 @@ static int state_check_process(struct snapraid_state* state, int check, int fix, countsize += read_size; - /* if the block is potentially not updated */ - if (block_state == BLOCK_STATE_NEW || block_state == BLOCK_STATE_CHG) { - /* store it in the failed set, because potentially */ - /* the parity may be still computed with the previous content */ - failed[failed_count].is_bad = 0; + /* always insert CHG blocks, the repair functions needs all of them */ + /* because the parity may be still referring at the old state */ + /* and the repair must be aware of it */ + if (block_state == BLOCK_STATE_CHG) { + /* we DO NOT mark them as bad to avoid to overwrite them with wrong data. */ + /* if we don't have a hash, we always assume the first read of the block correct. */ + failed[failed_count].is_bad = 0; /* we assume the CHG block correct */ failed[failed_count].is_outofdate = 0; failed[failed_count].index = j; failed[failed_count].block = block; @@ -741,29 +763,44 @@ static int state_check_process(struct snapraid_state* state, int check, int fix, continue; } - /* if the block has the hash */ - if (block_state == BLOCK_STATE_BLK) { - /* compute the hash of the block just read */ - if (rehash) { - memhash(state->prevhash, state->prevhashseed, hash, buffer[j], read_size); - } else { - memhash(state->hash, state->hashseed, hash, buffer[j], read_size); - } + assert(block_state == BLOCK_STATE_BLK || block_state == BLOCK_STATE_REP); - /* compare the hash */ - if (memcmp(hash, block->hash, HASH_SIZE) != 0) { - /* save the failed block for the check/fix */ - failed[failed_count].is_bad = 1; - failed[failed_count].is_outofdate = 0; - failed[failed_count].index = j; - failed[failed_count].block = block; - failed[failed_count].handle = &handle[j]; - ++failed_count; + /* compute the hash of the block just read */ + if (rehash) { + memhash(state->prevhash, state->prevhashseed, hash, buffer[j], read_size); + } else { + memhash(state->hash, state->hashseed, hash, buffer[j], read_size); + } - fprintf(stdlog, "error:%u:%s:%s: Data error at position %u\n", i, handle[j].disk->name, block_file_get(block)->sub, block_file_pos(block)); - ++error; - continue; - } + /* compare the hash */ + if (memcmp(hash, block->hash, HASH_SIZE) != 0) { + /* save the failed block for the check/fix */ + failed[failed_count].is_bad = 1; /* it's bad because the hash doesn't match */ + failed[failed_count].is_outofdate = 0; + failed[failed_count].index = j; + failed[failed_count].block = block; + failed[failed_count].handle = &handle[j]; + ++failed_count; + + fprintf(stdlog, "error:%u:%s:%s: Data error at position %u\n", i, handle[j].disk->name, block_file_get(block)->sub, block_file_pos(block)); + ++error; + continue; + } + + /* always insert REP blocks, the repair functions needs all of them */ + /* because the parity may be still referring at the old state */ + /* and the repair must be aware of it */ + if (block_state == BLOCK_STATE_REP) { + failed[failed_count].is_bad = 0; /* it's not bad */ + failed[failed_count].is_outofdate = 0; + failed[failed_count].index = j; + failed[failed_count].block = block; + failed[failed_count].handle = &handle[j]; + ++failed_count; + + fprintf(stdlog, "error:%u:%s:%s: Data error at position %u\n", i, handle[j].disk->name, block_file_get(block)->sub, block_file_pos(block)); + ++error; + continue; } } diff --git a/elem.h b/elem.h index 3f944e9a..e87a3a60 100644 --- a/elem.h +++ b/elem.h @@ -77,19 +77,15 @@ struct snapraid_filter { #define BLOCK_STATE_BLK 1 /** - * The block is new and not yet hashed, and it's using a space previously empty. + * The block is new and not yet hashed. + * This happens when a new block overwrite a just removed block, or an empty space. * - * The block hash field IS NOT set. - * The parity for this disk is not updated, but it contains 0. - */ -#define BLOCK_STATE_NEW 2 - -/** - * The block is new and not yet hashed, and it's using the space of a block not existing anymore. - * This happens when a new block overwrite a just removed block. + * The block hash field IS set but it may be also INVALID or ZERO, + * and it represents the hash of the OLD data. + * + * If the block was empty, the hash is set to the special ZERO value. + * If the block was lost, the hash is set to the special INVALID value. * - * The block hash field IS set, and it represents the hash of the previous data, - * but only if it's different by all 0. * The parity for this disk is not updated, but it contains the old data referenced by the hash. * * If the state is read from an incomplete sync, we don't really know if the hash is referring at the @@ -101,7 +97,16 @@ struct snapraid_filter { * Check and fix are instead able to work with unsynched hashes. * Scrub is not affected because it ignores CHG/DELETED blocks. */ -#define BLOCK_STATE_CHG 3 +#define BLOCK_STATE_CHG 2 + +/** + * The block is new and hashed. + * This happens when a new block overwrite a just removed block, or an empty space. + * + * The block hash field IS set, and it represents the hash of the NEW data. + * The parity for this disk is not updated, but it contains the old data referenced by the hash. + */ +#define BLOCK_STATE_REP 3 /** * This block is a deleted one. @@ -462,42 +467,83 @@ static inline void block_state_set(struct snapraid_block* block, unsigned state) } /** - * Checks if the specified block is valid and has an updated hash. + * Checks if the specified block has an updated hash. * - * Note that EMPTY / CHG / NEW / DELETED return 0. + * Note that EMPTY / CHG / DELETED return 0. */ static inline int block_has_updated_hash(const struct snapraid_block* block) { unsigned state = block_state_get(block); - return state == BLOCK_STATE_BLK; + return state == BLOCK_STATE_BLK || state == BLOCK_STATE_REP; } /** - * Checks if the specified block has any kind of hash. - * Even a not updated one. + * Checks if the specified block has an a past hash. * - * Note that for DELETED / CHG with a NULL hash returns 0, - * because in these cases the hash is lost. + * Note that EMPTY / BLK / REP return 0. */ -static inline int block_has_any_hash(const struct snapraid_block* block) +static inline int block_has_past_hash(const struct snapraid_block* block) +{ + unsigned state = block_state_get(block); + + return state == BLOCK_STATE_CHG || state == BLOCK_STATE_DELETED; +} + +/** + * Checks if the specified hash is invalid. + * + * An invalid hash is represented with all bytes at 0x00. + */ +static inline int hash_is_invalid(const unsigned char* hash) { - unsigned block_state = block_state_get(block); unsigned i; - switch (block_state) { - case BLOCK_STATE_BLK : - return 1; - case BLOCK_STATE_CHG : - case BLOCK_STATE_DELETED : - /* the hash is valid only if different than 0 */ - for(i=0;ihash[i] != 0) - return 1; - break; - } + for(i=0;ihash, 0, HASH_SIZE); - break; case BLOCK_STATE_CHG : /* if we have not already cleared the undeterminated hash */ if (!state->clear_undeterminate_hash) { /* in these cases we don't know if the old state is still the one */ /* stored inside the parity, because after an aborted sync, the parity */ - /* may be or may be not have been updated with the new data */ - /* Then we reset the hash to a bogus value */ - /* Note that this condition is possible only if: */ - /* - new files added/modified */ - /* - aborted sync, without saving the content file */ - /* - files deleted after the aborted sync */ - memset(block->hash, 0, HASH_SIZE); + /* may be or may be not have been updated with the data that it's now */ + /* deleted. Then we reset the hash to a bogus value. */ + /* For example: */ + /* - One file is added */ + /* - Sync aborted after updating the parity to the new state, */ + /* but without saving the content file representing this new state. */ + /* - File is now deleted after the aborted sync */ + /* - Sync again, deleting the blocks (exactly here) */ + /* with the hash of CHG block not represeting the real parity state */ + hash_invalid_set(block->hash); } break; + case BLOCK_STATE_REP : + /* we just don't know the old hash, and then we set it to invalid */ + hash_invalid_set(block->hash); + break; default: fprintf(stderr, "Internal state inconsistency in scanning for block %u state %u\n", block->parity_pos, block_state); exit(EXIT_FAILURE); @@ -205,7 +208,7 @@ static void scan_file_deallocate(struct snapraid_state* state, struct snapraid_d } /** - * Checks if a file is completely formed of NEW or CHG blocks, + * Checks if a file is completely formed of NEW/CHG/REP blocks, * and if it has at least one block. */ static int file_is_full_invalid(struct snapraid_file* file) @@ -226,7 +229,7 @@ static int file_is_full_invalid(struct snapraid_file* file) block_state = block_state_get(block); - if (block_state != BLOCK_STATE_NEW && block_state != BLOCK_STATE_CHG) + if (block_state != BLOCK_STATE_CHG && block_state != BLOCK_STATE_REP) return 0; } @@ -290,6 +293,7 @@ static void scan_file_insert(struct snapraid_state* state, struct snapraid_disk* block_max = tommy_arrayblk_size(&disk->blockarr); for(i=0;iblockmax;++i) { struct snapraid_block* block; + unsigned block_state; /* find a free block */ while (block_pos < block_max && block_has_file(tommy_arrayblk_get(&disk->blockarr, block_pos))) @@ -307,12 +311,19 @@ static void scan_file_insert(struct snapraid_state* state, struct snapraid_disk* /* block to overwrite */ block = tommy_arrayblk_get(&disk->blockarr, block_pos); + /* state of the block */ + block_state = block_state_get(block); + /* if the block is an empty one */ - if (block == BLOCK_EMPTY) { - /* we just overwrite it with a NEW one */ - block_state_set(&file->blockvec[i], BLOCK_STATE_NEW); + if (block_state == BLOCK_STATE_EMPTY) { + block_state_set(&file->blockvec[i], BLOCK_STATE_CHG); + + /* the block was empty and filled with zeros */ + /* set the hash to the special ZERO value */ + hash_zero_set(file->blockvec[i].hash); } else { /* otherwise it's a DELETED one */ + assert(block_state == BLOCK_STATE_DELETED); /* if we have not already cleared the undeterminated hash */ if (!state->clear_undeterminate_hash) { @@ -320,11 +331,13 @@ static void scan_file_insert(struct snapraid_state* state, struct snapraid_disk* /* stored inside the parity, because after an aborted sync, the parity */ /* may be or may be not have been updated with the new data */ /* Then we reset the hash to a bogus value */ - /* Note that this condition is possible only if: */ - /* - files are deleted */ - /* - aborted sync, without saving the content file */ - /* - files are readded after the aborted sync */ - memset(block->hash, 0, HASH_SIZE); + /* For example: */ + /* - One file is deleted */ + /* - Sync aborted after, updating the parity to the new state, */ + /* but without saving the content file representing this new state. */ + /* - Another file is added again (exactly here) */ + /* with the hash of DELETED block not represeting the real parity state */ + hash_invalid_set(block->hash); } block_state_set(&file->blockvec[i], BLOCK_STATE_CHG); diff --git a/scrub.c b/scrub.c index e64025b3..1cfe2e74 100644 --- a/scrub.c +++ b/scrub.c @@ -182,6 +182,7 @@ static int state_scrub_process(struct snapraid_state* state, struct snapraid_par silent_error_on_this_block = 0; /* if all the blocks at this address are synched */ + /* if not, parity is not even checked */ block_is_unsynched = 0; /* if we have to use the old hash */ @@ -195,6 +196,7 @@ static int state_scrub_process(struct snapraid_state* state, struct snapraid_par int file_is_unsynched; /* if the file on this disk is synched */ + /* if not, silent errors are assumed as expected error */ file_is_unsynched = 0; /* by default no rehash in case of "continue" */ @@ -215,6 +217,15 @@ static int state_scrub_process(struct snapraid_state* state, struct snapraid_par continue; } + /* if the block is unsynched, errors are expected */ + if (block_has_invalid_parity(block)) { + /* report that the block and the file are not synched */ + block_is_unsynched = 1; + file_is_unsynched = 1; + + /* follow */ + } + /* if the file is different than the current one, close it */ if (handle[j].file != 0 && handle[j].file != block_file_get(block)) { /* keep a pointer at the file we are going to close for error reporting */ diff --git a/state.c b/state.c index 17435512..2b9ca7a0 100644 --- a/state.c +++ b/state.c @@ -1088,8 +1088,10 @@ static void state_read_text(struct snapraid_state* state, const char* path, STRE sungetc(c, f); } - if (strcmp(tag, "blk") == 0 || strcmp(tag, "new") == 0 || strcmp(tag, "chg") == 0) { - /* "blk"/""new"/"chg" command */ + if (strcmp(tag, "blk") == 0 || strcmp(tag, "new") == 0 + || strcmp(tag, "chg") == 0 || strcmp(tag, "rep") == 0 + ) { + /* "blk"/""new"/"chg"/"rep" command */ block_off_t v_pos; struct snapraid_block* block; @@ -1126,11 +1128,15 @@ static void state_read_text(struct snapraid_state* state, const char* path, STRE paritymax = v_pos + 1; break; case 'n' : - block_state_set(block, BLOCK_STATE_NEW); + /* deprecated NEW blocks are converted to CHG ones */ + block_state_set(block, BLOCK_STATE_CHG); break; case 'c' : block_state_set(block, BLOCK_STATE_CHG); break; + case 'p' : + block_state_set(block, BLOCK_STATE_REP); + break; } block->parity_pos = v_pos; @@ -1139,7 +1145,7 @@ static void state_read_text(struct snapraid_state* state, const char* path, STRE if (v_pos + 1 > blockmax) blockmax = v_pos + 1; - /* read the hash only for 'blk/chg', and not for 'new' */ + /* read the hash only for 'blk/chg/rep', and not for 'new' */ if (tag[0] != 'n') { c = sgetc(f); if (c != ' ') { @@ -1147,17 +1153,23 @@ static void state_read_text(struct snapraid_state* state, const char* path, STRE exit(EXIT_FAILURE); } - /* set the hash only if present */ ret = sgethex(f, block->hash, HASH_SIZE); if (ret < 0) { fprintf(stderr, "Invalid '%s' specification in '%s' at line %u\n", tag, path, line); exit(EXIT_FAILURE); } + } else { + /* set the ZERO hash for deprecated NEW blocks */ + hash_zero_set(block->hash); + } - /* clear undeterminated hashes before a sync */ - if (tag[0] != 'b' && state->clear_undeterminate_hash) { - memset(block->hash, 0, HASH_SIZE); - } + /* if the block contains a hash of past data */ + /* and we are clearing such undeterminated hashes */ + if (state->clear_undeterminate_hash + && block_has_past_hash(block) + ) { + /* set the hash value to INVALID */ + hash_invalid_set(block->hash); } /* we must not overwrite existing blocks */ @@ -1281,9 +1293,10 @@ static void state_read_text(struct snapraid_state* state, const char* path, STRE exit(EXIT_FAILURE); } - /* clear undeterminated hashes before a sync */ + /* if we are clearing undeterminated hashes */ if (state->clear_undeterminate_hash) { - memset(deleted->block.hash, 0, HASH_SIZE); + /* set the hash value to INVALID */ + hash_invalid_set(deleted->block.hash); } /* we must not overwrite existing blocks */ @@ -1997,12 +2010,12 @@ static void state_write_text(struct snapraid_state* state, STREAM* f) case BLOCK_STATE_BLK : sputsl("blk ", f); break; - case BLOCK_STATE_NEW : - sputsl("new ", f); - break; case BLOCK_STATE_CHG : sputsl("chg ", f); break; + case BLOCK_STATE_REP : + sputsl("rep ", f); + break; default: fprintf(stderr, "Internal state inconsistency in saving for block %u state %u\n", block->parity_pos, block_state); exit(EXIT_FAILURE); @@ -2010,10 +2023,8 @@ static void state_write_text(struct snapraid_state* state, STREAM* f) sputu32(block->parity_pos, f); - if (block_state != BLOCK_STATE_NEW) { - sputc(' ', f); - sputhex(block->hash, HASH_SIZE, f); - } + sputc(' ', f); + sputhex(block->hash, HASH_SIZE, f); sputeol(f); if (serror(f)) { @@ -2383,11 +2394,15 @@ static void state_read_binary(struct snapraid_state* state, const char* path, ST paritymax = v_pos + 1; break; case 'n' : - block_state_set(block, BLOCK_STATE_NEW); + /* deprecated NEW blocks are converted to CHG ones */ + block_state_set(block, BLOCK_STATE_CHG); break; case 'g' : block_state_set(block, BLOCK_STATE_CHG); break; + case 'p' : + block_state_set(block, BLOCK_STATE_REP); + break; default: decoding_error(path, f); fprintf(stderr, "Invalid block type!\n"); @@ -2396,18 +2411,25 @@ static void state_read_binary(struct snapraid_state* state, const char* path, ST block->parity_pos = v_pos; - /* read the hash only for 'blk/chg', and not for 'new' */ + /* read the hash only for 'blk/chg/rep', and not for 'new' */ if (c != 'n') { - /* set the hash only if present */ ret = sread(f, block->hash, HASH_SIZE); if (ret < 0) { decoding_error(path, f); exit(EXIT_FAILURE); } + } else { + /* set the ZERO hash for deprecated NEW blocks */ + hash_zero_set(block->hash); + } - /* clear undeterminated hashes before a sync */ - if (c != 'b' && state->clear_undeterminate_hash) - memset(block->hash, 0, HASH_SIZE); + /* if the block contains a hash of past data */ + /* and we are clearing such undeterminated hashes */ + if (state->clear_undeterminate_hash + && block_has_past_hash(block) + ) { + /* set the hash value to INVALID */ + hash_invalid_set(block->hash); } /* we must not overwrite existing blocks */ @@ -2573,9 +2595,10 @@ static void state_read_binary(struct snapraid_state* state, const char* path, ST exit(EXIT_FAILURE); } - /* clear undeterminated hashes before a sync */ + /* if we are clearing undeterminated hashes */ if (state->clear_undeterminate_hash) { - memset(deleted->block.hash, 0, HASH_SIZE); + /* set the hash value to INVALID */ + hash_invalid_set(deleted->block.hash); } /* we must not overwrite existing blocks */ @@ -3092,12 +3115,12 @@ static void state_write_binary(struct snapraid_state* state, STREAM* f) case BLOCK_STATE_BLK : sputc('b', f); break; - case BLOCK_STATE_NEW : - sputc('n', f); - break; case BLOCK_STATE_CHG : sputc('g', f); break; + case BLOCK_STATE_REP : + sputc('p', f); + break; default: fprintf(stderr, "Internal state inconsistency in saving for block %u state %u\n", v_pos, block_state); exit(EXIT_FAILURE); @@ -3108,10 +3131,8 @@ static void state_write_binary(struct snapraid_state* state, STREAM* f) v_count = end - begin; sputb32(v_count, f); - if (block_state != BLOCK_STATE_NEW) { - for(b=begin;bblock_size; failed[failed_count].block = block; ++failed_count; - /* now continue with processing */ + /* 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 */ + if (block_state != BLOCK_STATE_CHG) + parity_needs_to_be_updated = 1; + + /* note that DELETE blocks are skipped in the next check */ + /* and we have to store them in the failed blocks */ + /* before skipping */ + + /* follow */ } /* if the block has no file, meanining that it's EMPTY or DELETED, */ @@ -361,34 +366,48 @@ static int state_sync_process(struct snapraid_state* state, struct snapraid_pari if (block_has_updated_hash(block)) { /* compare the hash */ if (memcmp(hash, block->hash, HASH_SIZE) != 0) { - fprintf(stdlog, "error:%u:%s:%s: Data error at position %u\n", i, handle[j].disk->name, handle[j].file->sub, block_file_pos(block)); - fprintf(stderr, "Data error at file '%s' at position '%u'\n", handle[j].path, block_file_pos(block)); - fprintf(stderr, "WARNING! Unexpected data error in a data disk! The block is now marked as bad!\n"); - fprintf(stderr, "Try with 'snapraid -e fix' to recover!\n"); + /* if the file has invalid parity, it's a REP changed during the sync */ + if (block_has_invalid_parity(block)) { + fprintf(stdlog, "error:%u:%s:%s: Unexpected change\n", i, handle[j].disk->name, handle[j].file->sub); + fprintf(stderr, "WARNING! You cannot modify files during a sync.\n"); + fprintf(stderr, "Rerun the sync command when finished.\n"); - /* save the failed block for the fix */ - failed[failed_count].index = j; - failed[failed_count].size = read_size; - failed[failed_count].block = block; - ++failed_count; + ++error; - /* 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 */ - ++silent_error; - silent_error_on_this_block = 1; - continue; + /* 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 */ + error_on_this_block = 1; + continue; + } else { /* otherwise it's a BLK with silent error */ + fprintf(stdlog, "error:%u:%s:%s: Data error at position %u\n", i, handle[j].disk->name, handle[j].file->sub, block_file_pos(block)); + fprintf(stderr, "Data error at file '%s' at position '%u'\n", handle[j].path, block_file_pos(block)); + fprintf(stderr, "WARNING! Unexpected data error in a data disk! The block is now marked as bad!\n"); + fprintf(stderr, "Try with 'snapraid -e fix' to recover!\n"); + + /* save the failed block for the fix */ + failed[failed_count].index = j; + failed[failed_count].size = read_size; + failed[failed_count].block = block; + ++failed_count; + + /* 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 */ + ++silent_error; + silent_error_on_this_block = 1; + continue; + } } } else { - /* if until now the parity doesn't need to be update */ + /* if until now the parity doesn't need to be updated */ if (!parity_needs_to_be_updated) { /* for sure it's a CHG block, because EMPTY are processed before with "continue" */ - /* and BLK have "block_has_updated_hash()" as 1, and all the others */ + /* and BLK and REP have "block_has_updated_hash()" as 1, and all the others */ /* have "parity_needs_to_be_updated" already at 1 */ assert(block_state_get(block) == BLOCK_STATE_CHG); /* if there is an hash */ - if (block_has_any_hash(block)) { + if (hash_is_real(block->hash)) { /* check if the hash is changed */ if (memcmp(hash, block->hash, HASH_SIZE) != 0) { /* the block is different, and we must update parity */ @@ -412,6 +431,7 @@ static int state_sync_process(struct snapraid_state* state, struct snapraid_pari /* if we have only silent errors we can try to fix them on-the-fly */ if (!error_on_this_block && silent_error_on_this_block) { unsigned failed_mac; + int something_to_recover = 0; /* setup the blocks to recover */ failed_mac = 0; @@ -420,29 +440,35 @@ static int state_sync_process(struct snapraid_state* state, struct snapraid_pari unsigned char* block_copy = buffer[diskmax + state->level + failed[j].index]; unsigned block_state = block_state_get(failed[j].block); - /* make a copy of new content just read */ + /* we try to recover only if at least one BLK is present */ + if (block_state == BLOCK_STATE_BLK) + something_to_recover = 1; + + /* save a copy of the content just read */ + /* that it's going to be overwritten by the recovering function */ memcpy(block_copy, block_buffer, state->block_size); - if (block_state == BLOCK_STATE_NEW) { - /* restore the data to parity state */ + if (block_state == BLOCK_STATE_CHG + && hash_is_zero(failed[j].block->hash) + ) { + /* if the block was filled with 0, restore this state */ + /* and avoid to recover it */ memset(block_buffer, 0, state->block_size); } else { - /* if we have too many failure */ - if (failed_mac >= state->level) { - /* we cannot recover */ + /* if we have too many failures, we cannot recover */ + if (failed_mac >= state->level) break; - } - + /* otherwise it has to be recovered */ failed_map[failed_mac++] = failed[j].index; } } - /* if we have enough parity to fix */ - if (j == failed_count) { + /* if we have something to recover and enough parity */ + if (something_to_recover && j == failed_count) { /* read the parity */ - /* we are sure that parity exists because a silent error */ - /* implies a BLK block with compute parity */ + /* we are sure that parity exists because */ + /* we have at least one BLK block */ for(l=0;llevel;++l) { ret = parity_read(parity[l], i, buffer[diskmax+l], state->block_size, stdlog); if (ret == -1) { @@ -485,10 +511,9 @@ static int state_sync_process(struct snapraid_state* state, struct snapraid_pari if (size < state->block_size) memset(block_buffer + size, 0, state->block_size - size); } else { - /* otherwise restore the new content */ - /* because we are not interested in recovered */ - /* state for CHG and DELETED blocks */ - /* and we have to restore also NEW zeroed blocks */ + /* otherwise restore the content */ + /* because we are not interested in the old state */ + /* that it's recovered for CHG, REP and DELETED blocks */ memcpy(block_buffer, block_copy, state->block_size); } }