diff --git a/HISTORY b/HISTORY index 724d031b..11abec24 100644 --- a/HISTORY +++ b/HISTORY @@ -3,6 +3,10 @@ SnapRAID HISTORY 1.9 2012/2 =========== + * Implemented a more sophisticated recovering in case a harddisk failure + happens during a 'sync' command. + When using RAID6 it improves the chances of recovering data after an + aborted 'sync'. * Fixed the count of new files. 1.8 2012/1 diff --git a/Makefile.am b/Makefile.am index a59916d6..3a8c868d 100644 --- a/Makefile.am +++ b/Makefile.am @@ -139,13 +139,13 @@ check-local: rm test/disk2/2* rm test/disk3/3* $(TESTENV) ./mktest$(EXEEXT) generate 3 3 $(CHECKCOUNT) $(CHECKSIZE) - $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) --test-skip-self -v -c $(srcdir)/test-raid6.conf sync + $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) --test-skip-self -c $(srcdir)/test-raid6.conf sync $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) --test-skip-self -c $(srcdir)/test-raid6.conf check echo --- Move some files, sync and check mv test/disk1/7* test/disk1/_a/ mv test/disk2/7* test/disk2/_a/ mv test/disk3/7* test/disk3/_a/ - $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) --test-skip-self -v -c $(srcdir)/test-raid6.conf sync + $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) --test-skip-self -c $(srcdir)/test-raid6.conf sync $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) --test-skip-self -c $(srcdir)/test-raid6.conf check echo --- Delete one disk, fix and check with raid5 rm -r test/disk3 @@ -154,7 +154,7 @@ check-local: $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) --test-skip-self -c $(srcdir)/test-raid5.conf fix 2>> test.log $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) --test-skip-self -c $(srcdir)/test-raid6.conf check echo --- Sync after all the fixes with an empty drive to test --force-empty - $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) --test-skip-self -v --force-empty -c $(srcdir)/test-raid6.conf sync + $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) --test-skip-self --force-empty -c $(srcdir)/test-raid6.conf sync $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) --test-skip-self -c $(srcdir)/test-raid6.conf check echo --- Delete two disks, fix and check with raid6 rm -r test/disk1 @@ -167,31 +167,85 @@ check-local: $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) --test-skip-self -c $(srcdir)/test-raid5.conf check $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) --test-skip-self -c $(srcdir)/test-raid6.conf check echo --- Sync after all the fixes with an empty drive to test --find-by-name - $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) --test-skip-self -v --find-by-name -c $(srcdir)/test-raid6.conf sync + $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) --test-skip-self --find-by-name -c $(srcdir)/test-raid6.conf sync $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) --test-skip-self -c $(srcdir)/test-raid6.conf check echo --- Delete some files and create some new, sync and check in multiple steps rm test/disk1/4* rm test/disk2/5* rm test/disk3/6* $(TESTENV) ./mktest$(EXEEXT) generate 4 3 $(CHECKCOUNT) $(CHECKSIZE) - $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) --test-skip-self -v -c $(srcdir)/test-raid6.conf -t 100 sync + $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) --test-skip-self -c $(srcdir)/test-raid6.conf -t 100 sync $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) --test-skip-self -c $(srcdir)/test-raid6.conf check 2>> test.log - $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) --test-skip-self -v -c $(srcdir)/test-raid6.conf -t 700 sync + $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) --test-skip-self -c $(srcdir)/test-raid6.conf -t 700 sync $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) --test-skip-self -c $(srcdir)/test-raid6.conf check 2>> test.log - $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) --test-skip-self -v -c $(srcdir)/test-raid6.conf -t 1000 sync + $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) --test-skip-self -c $(srcdir)/test-raid6.conf -t 1000 sync $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) --test-skip-self -c $(srcdir)/test-raid6.conf check 2>> test.log - $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) --test-skip-self -v -c $(srcdir)/test-raid6.conf sync + $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) --test-skip-self -c $(srcdir)/test-raid6.conf sync $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) --test-skip-self -c $(srcdir)/test-raid6.conf check - echo --- Add some files, sync with abort, delete the new files, sync and check + echo --- Abort sync with additions and then delete the new additions cp -a test/disk1/_a test/disk1/_c cp -a test/disk2/_a test/disk2/_c cp -a test/disk3/_a test/disk3/_c - $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) --test-skip-self --test-kill-after-sync -v -c $(srcdir)/test-raid6.conf sync + $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) --test-skip-self --test-kill-after-sync -c $(srcdir)/test-raid6.conf sync rm -r test/disk1/_c rm -r test/disk2/_c rm -r test/disk3/_c - $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) --test-skip-self -v -c $(srcdir)/test-raid6.conf sync + $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) --test-skip-self -c $(srcdir)/test-raid6.conf sync $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) --test-skip-self -c $(srcdir)/test-raid6.conf check + echo --- Abort sync late with more additions than deletion and then delete after + cp -a test/disk1/_a test/disk1/_c + cp -a test/disk1/_b test/disk1/_d + mv test/disk1/_a test/_a + $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) --test-skip-self -c $(srcdir)/test-raid6.conf --test-kill-after-sync sync + rm -r test/disk2/_a + rm -r test/disk2/_b + $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) --test-skip-self -c $(srcdir)/test-raid6.conf fix 2>> test.log + -rm -r test/disk1/_a + rm -r test/disk1/_c + rm -r test/disk1/_d + mv test/_a test/disk1/_a + $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) --test-skip-self -c $(srcdir)/test-raid6.conf sync + echo --- Abort sync late with more deletions than additions and then delete after + cp -a test/disk1/_a test/disk1/_c + mv test/disk1/_a test/_a + mv test/disk1/_b test/_b + $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) --test-skip-self -c $(srcdir)/test-raid6.conf --test-kill-after-sync sync + rm -r test/disk2/_a + rm -r test/disk2/_b + $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) --test-skip-self -c $(srcdir)/test-raid6.conf fix 2>> test.log + -rm -r test/disk1/_a + -rm -r test/disk1/_b + rm -r test/disk1/_c + mv test/_a test/disk1/_a + mv test/_b test/disk1/_b + $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) --test-skip-self -c $(srcdir)/test-raid6.conf sync + echo --- Abort sync early with more additions than deletions and then delete after + cp -a test/disk1/_a test/disk1/_c + cp -a test/disk1/_b test/disk1/_d + mv test/disk1/_a test/_a + $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) --test-skip-self -c $(srcdir)/test-raid6.conf -t 1 --test-kill-after-sync sync + rm -r test/disk2/_a + rm -r test/disk2/_b + $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) --test-skip-self -c $(srcdir)/test-raid6.conf fix 2>> test.log + -rm -r test/disk1/_a + rm -r test/disk1/_c + rm -r test/disk1/_d + mv test/_a test/disk1/_a + $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) --test-skip-self -c $(srcdir)/test-raid6.conf sync + echo --- Abort sync early with more deletions than additions and then delete after + cp -a test/disk1/_a test/disk1/_c + mv test/disk1/_a test/_a + mv test/disk1/_b test/_b + $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) --test-skip-self -c $(srcdir)/test-raid6.conf -t 1 --test-kill-after-sync sync + rm -r test/disk2/_a + rm -r test/disk2/_b + $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) --test-skip-self -c $(srcdir)/test-raid6.conf fix 2>> test.log + -rm -r test/disk1/_a + -rm -r test/disk1/_b + rm -r test/disk1/_c + mv test/_a test/disk1/_a + mv test/_b test/disk1/_b + $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) --test-skip-self -c $(srcdir)/test-raid6.conf sync echo --- Delete some files, fix and check with raid5 rm test/disk1/8* $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) --test-skip-self --test-expect-recoverable -c $(srcdir)/test-raid5.conf check 2>> test.log @@ -301,7 +355,7 @@ check-local: $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) --test-skip-self -c $(srcdir)/test-raid6.conf fix 2>> test.log $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) --test-skip-self -c $(srcdir)/test-raid6.conf check echo --- Sync after all the fixes - $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) --test-skip-self -v -c $(srcdir)/test-raid6.conf sync + $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) --test-skip-self -c $(srcdir)/test-raid6.conf sync $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) --test-skip-self -c $(srcdir)/test-raid6.conf check echo Success! diff --git a/check.c b/check.c index 0f895828..f495dc9e 100644 --- a/check.c +++ b/check.c @@ -27,10 +27,15 @@ /****************************************************************************/ /* check */ +/** + * A block that failed the hash check, or that was deleted. + */ struct failed_struct { - unsigned index; - struct snapraid_block* block; - struct snapraid_handle* handle; + int is_bad; /**< If the block needs to be recovered and rewritten to the disk. */ + int is_outofdate; /**< If that we have recovered may be not updated data, but an old version. */ + unsigned index; /**< Index of the failed block. */ + struct snapraid_block* block; /**< The failed block, or BLOCK_DELETED for a deleted block */ + struct snapraid_handle* handle; /**< The file containing the failed block, or 0 for a deleted block */ }; /** @@ -66,10 +71,12 @@ static int blockcmp(struct snapraid_state* state, struct snapraid_block* block, * Returns <0 if failure for missing strategy, >0 if data is wrong and we cannot rebuild correctly, 0 on success. * If success, the parity and qarity are computed in the buffer variable. */ -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) +static int repair_step(struct snapraid_state* state, unsigned pos, unsigned diskmax, struct failed_struct* failed, unsigned* failed_map, unsigned failed_count, unsigned char** buffer, unsigned char* buffer_parity, unsigned char* buffer_qarity, unsigned char* buffer_zero) { unsigned j; int error = 0; + int has_hash; + int hash_checked; /* no fix required */ if (failed_count == 0) { @@ -78,76 +85,267 @@ static int repair(struct snapraid_state* state, unsigned i, unsigned diskmax, st return 0; } - if (failed_count == 1 && buffer_parity != 0) { + /* check if there is at least a failed block that can be checked for correctness */ + has_hash = 0; + for(j=0;jblock_size); /* recover */ - raid5_recov_data(buffer, diskmax, state->block_size, failed[0].index); + raid5_recov_data(buffer, diskmax, state->block_size, failed[failed_map[0]].index); + + hash_checked = 0; /* keep track if we check at leat one block */ /* check if the recovered blocks are OK */ for(j=0;j<1;++j) { - if (blockcmp(state, failed[j].block, buffer[failed[j].index], buffer_zero) != 0) - break; + if (failed[failed_map[j]].block != BLOCK_DELETED /* if the block it not a deleted one */ + && block_has_hash(failed[failed_map[j]].block) /* if the block can be checked */ + ) { + hash_checked = 1; + if (blockcmp(state, failed[failed_map[j]].block, buffer[failed[failed_map[j]].index], buffer_zero) != 0) + break; + } } - if (j==1) { + /* if we checked something, and no block failed the check */ + if (hash_checked && j==1) { /* recompute all the redundancy information */ raid_gen(state->level, buffer, diskmax, state->block_size); return 0; } - fprintf(stderr, "error:%u:parity: Data error\n", i); + fprintf(stderr, "error:%u:parity: Data error\n", pos); ++error; } - if (failed_count == 1 && buffer_qarity != 0) { + /* RAID6 recovering from q, ignoring p */ + if (failed_count == 1 && buffer_qarity != 0 && has_hash) { /* 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); + raid6_recov_datap(buffer, diskmax, state->block_size, failed[failed_map[0]].index, buffer_zero); + + hash_checked = 0; /* keep track if we check at leat one block */ /* check if the recovered blocks are OK */ for(j=0;j<1;++j) { - if (blockcmp(state, failed[j].block, buffer[failed[j].index], buffer_zero) != 0) - break; + if (failed[failed_map[j]].block != BLOCK_DELETED /* if the block it not a deleted one */ + && block_has_hash(failed[failed_map[j]].block) /* if the block can be checked */ + ) { + hash_checked = 1; + if (blockcmp(state, failed[failed_map[j]].block, buffer[failed[failed_map[j]].index], buffer_zero) != 0) + break; + } } - if (j==1) { + /* if we checked something, and no block failed the check */ + if (hash_checked && j==1) { /* recompute all the redundancy information */ raid_gen(state->level, buffer, diskmax, state->block_size); return 0; } - fprintf(stderr, "error:%u:qarity: Data error\n", i); + fprintf(stderr, "error:%u:qarity: Data error\n", pos); ++error; } - if (failed_count == 2 && buffer_parity != 0 && buffer_qarity != 0) { + /* RAID6 recovering from q, using the restored p to check the result */ + if (failed_count == 1 && buffer_parity != 0 && buffer_qarity != 0 && !has_hash) { + /* copy the redundancy to use */ + memcpy(buffer[diskmax+1], buffer_qarity, state->block_size); + + raid6_recov_datap(buffer, diskmax, state->block_size, failed[failed_map[0]].index, buffer_zero); + + /* if the recovered parity block matches */ + if (memcmp(buffer[diskmax], buffer_parity, state->block_size) == 0) { + /* recompute all the redundancy information */ + raid_gen(state->level, buffer, diskmax, state->block_size); + return 0; + } + + fprintf(stderr, "error:%u:parity/qarity: Data error\n", pos); + ++error; + } + + /* RAID6 recovering from p+q */ + if (failed_count == 2 && buffer_parity != 0 && buffer_qarity != 0 && has_hash) { /* 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); + raid6_recov_2data(buffer, diskmax, state->block_size, failed[failed_map[0]].index, failed[failed_map[1]].index, buffer_zero); + + hash_checked = 0; /* keep track if we check at leat one block */ /* check if the recovered blocks are OK */ for(j=0;j<2;++j) { - if (blockcmp(state, failed[j].block, buffer[failed[j].index], buffer_zero) != 0) - break; + if (failed[failed_map[j]].block != BLOCK_DELETED /* if the block it not a deleted one */ + && block_has_hash(failed[failed_map[j]].block) /* if the block can be checked */ + ) { + hash_checked = 1; + if (blockcmp(state, failed[failed_map[j]].block, buffer[failed[failed_map[j]].index], buffer_zero) != 0) + break; + } } - if (j==2) { + /* if we checked something, and no block failed the check */ + if (hash_checked && j==2) { /* recompute all the redundancy information */ raid_gen(state->level, buffer, diskmax, state->block_size); return 0; } - fprintf(stderr, "error:%u:parity/qarity: Data error\n", i); + fprintf(stderr, "error:%u:parity/qarity: Data error\n", pos); ++error; } - /* no more stragety to fix */ + /* return the number of failed attempts, or -1 if no strategy */ + if (error) + return error; + else + return -1; +} + +static int repair(struct snapraid_state* state, unsigned pos, unsigned diskmax, struct failed_struct* failed, unsigned failed_count, unsigned char** buffer, unsigned char* buffer_parity, unsigned char* buffer_qarity, unsigned char* buffer_zero) +{ + int ret; + int error; + unsigned j; + int n; + int has_bad; + unsigned map[2]; + + error = 0; + + /* now assume that the parity is already computed for the current state */ + /* we need to put in the recovering process only the bad blocks, because all the */ + /* others already contains the correct data. */ + /* this is the normal condition. */ + n = 0; + has_bad = 0; /* keep track if there is at least one block to fix */ + for(j=0;jlevel, buffer, diskmax, state->block_size); + return 0; + } + + ret = repair_step(state, pos, diskmax, failed, map, n, buffer, buffer_parity, buffer_qarity, buffer_zero); + if (ret == 0) { + /* reprocess the blocks for NEW and CHG ones */ + /* if they were BAD we have to use some euristics to check if we have recovered */ + /* the state before or after the sync */ + for(j=0;jblock_size) == 0) { + /* it may contain garbage */ + failed[j].is_outofdate = 1; + } + } + if (block_state == BLOCK_STATE_CHG) { + /* we cannot know if we recoverd the state before or after */ + /* the partial 'sync' process. */ + /* Just to be safe, we assume it is garbage */ + failed[j].is_outofdate = 1; + } + } + } + return 0; + } + if (ret > 0) + error += ret; + + /* now assume that the parity computation was 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 */ + n = 0; + has_bad = 0; /* keep track if there is at least one block to fix */ + for(j=0;jblock_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 */ + failed[j].is_outofdate = 1; + } + } + + /* if nothing to fix */ + if (!has_bad) { + /* recompute only parity and qarity */ + raid_gen(state->level, buffer, diskmax, state->block_size); + return 0; + } + + ret = repair_step(state, pos, diskmax, failed, map, n, buffer, buffer_parity, buffer_qarity, buffer_zero); + if (ret == 0) { + /* reprocess the blocks for NEW and CHG ones */ + for(j=0;j 0) + error += ret; + + /* return the number of failed attempts, or -1 if no strategy */ if (error) return error; else @@ -199,11 +397,11 @@ static int state_check_process(struct snapraid_state* state, int fix, struct sna /* for each disk */ one_tocheck = 0; for(j=0;jblock_size); continue; } - /* we try to check and fix only if the block is hashed */ - /* 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 */ - if (!block_flag_has(block, BLOCK_HAS_HASH)) { + /* if the block is deleted */ + if (block == BLOCK_DELETED) { /* use an empty block */ memset(buffer[j], 0, state->block_size); + + /* store it in the failed set, because potentially */ + /* the parity may be still computed with the previous content */ + failed[failed_count].is_bad = 0; + failed[failed_count].is_outofdate = 0; + failed[failed_count].index = j; + failed[failed_count].block = BLOCK_DELETED; + failed[failed_count].handle = 0; + ++failed_count; continue; } - /* keep track if at least one hashed block has no parity */ - /* if at least one block has no parity, doesn't make sense to check/fix the parity */ - /* as errors are the normal condition */ - if (!block_flag_has(block, BLOCK_HAS_PARITY)) - all_parity = 0; + /* get the state of the block */ + block_state = block_state_get(block); + + /* if the parity is not valid */ + if (block_state != BLOCK_STATE_BLK) { + /* mark the parity as invalid, and don't try to check/fix it */ + /* because it will be recomputed at the next sync */ + check_parity = 0; + } /* if the file is different than the current one, close it */ if (handle[j].file != block_file_get(block)) { @@ -329,6 +538,8 @@ static int state_check_process(struct snapraid_state* state, int fix, struct sna ret = handle_open(&handle[j], block_file_get(block)); if (ret == -1) { /* save the failed block for the check/fix */ + failed[failed_count].is_bad = 1; + failed[failed_count].is_outofdate = 0; failed[failed_count].index = j; failed[failed_count].block = block; failed[failed_count].handle = &handle[j]; @@ -356,6 +567,8 @@ static int state_check_process(struct snapraid_state* state, int fix, struct sna read_size = handle_read(&handle[j], block, buffer[j], state->block_size); if (read_size == -1) { /* save the failed block for the check/fix */ + failed[failed_count].is_bad = 1; + failed[failed_count].is_outofdate = 0; failed[failed_count].index = j; failed[failed_count].block = block; failed[failed_count].handle = &handle[j]; @@ -366,23 +579,42 @@ static int state_check_process(struct snapraid_state* state, int fix, struct sna continue; } - /* now compute the hash */ - memhash(state->hash, hash, buffer[j], read_size); + countsize += read_size; - /* compare the hash */ - if (memcmp(hash, block->hash, HASH_SIZE) != 0) { - /* save the failed block for the check/fix */ + /* 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 */ + /* note that these blocks are never recovered, because we cannot check them */ + failed[failed_count].is_bad = 0; + 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(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)); - ++error; continue; } - countsize += read_size; + /* if the block has the hash */ + if (block_state == BLOCK_STATE_BLK || block_state == BLOCK_STATE_INV) { + /* now compute the hash */ + memhash(state->hash, hash, buffer[j], read_size); + + /* 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; + + 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)); + ++error; + continue; + } + } } /* buffers for parity read and not computed */ @@ -430,21 +662,40 @@ static int state_check_process(struct snapraid_state* state, int fix, struct sna error += ret; ++unrecoverable_error; + /* print a list of all the errors in files */ + for(j=0;jdisk->name, block_file_get(failed[j].block)->sub, block_file_pos(failed[j].block)); + } + if (fix) { /* keep track of damaged files */ for(j=0;jdisk->name, block_file_get(failed[j].block)->sub, block_file_pos(failed[j].block)); + if (failed[j].is_bad && failed[j].is_outofdate) { + ++partial_recover_error; + 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)); + } } - } else { + if (partial_recover_error != 0) { + error += partial_recover_error; + ++unrecoverable_error; + } + /* 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) { + if (check_parity) { /* check the parity */ if (buffer_parity != 0 && memcmp(buffer_parity, buffer[diskmax], state->block_size) != 0) { buffer_parity = 0; @@ -467,6 +718,10 @@ static int state_check_process(struct snapraid_state* state, int fix, struct sna if (fix) { /* update the fixed files */ for(j=0;jblock_size); @@ -536,13 +798,13 @@ static int state_check_process(struct snapraid_state* state, int fix, struct sna if (fix) { /* for all the files of this block check if we need to fix the modification time */ for(j=0;jsub, 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 */ diff --git a/dry.c b/dry.c index 1f437786..a12bd221 100644 --- a/dry.c +++ b/dry.c @@ -53,11 +53,11 @@ static int state_dry_process(struct snapraid_state* state, struct snapraid_parit /* for each disk, process the block */ for(j=0;jblockmax;++i) { memcpy(buf + i * HASH_SIZE, file->blockvec[i].hash, HASH_SIZE); - if (!block_flag_has(&file->blockvec[i], BLOCK_HAS_HASH)) { + if (!block_has_hash(&file->blockvec[i])) { free(buf); return 0; } diff --git a/elem.h b/elem.h index f988f812..634531c0 100644 --- a/elem.h +++ b/elem.h @@ -66,9 +66,48 @@ struct snapraid_filter { struct snapraid_file; -#define BLOCK_HAS_HASH 1 /**< If the hash value is valid. */ -#define BLOCK_HAS_PARITY 2 /**< If the parity block is valid. */ -#define BLOCK_HAS_MASK 3 /**< Mask of all the flags. Used to mix them inside the file pointer. */ +/** + * The block has both the hash and the parity computed. + * This is the normal state of a saved block. + */ +#define BLOCK_STATE_BLK 0 + +/** + * The block is new and not yet hashed, and it's using a space previously empty. + */ +#define BLOCK_STATE_NEW 1 + +/** + * The block has the hash computed, but the parity is invalid. + * This happens when the parity is invalidated by a change in another block. + */ +#define BLOCK_STATE_INV 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. + */ +#define BLOCK_STATE_CHG 3 + +/** + * Mask used to store the previous states of the block. + * Used to mix them inside the file pointer. + */ +#define BLOCK_STATE_MASK 3 + +/** + * This block is a deleted one. + * Note that this state cannot be stored inside the block, + * but it's usually represented by the block ::BLOCK_DELETED. + */ +#define BLOCK_STATE_DELETED 4 + +/** + * This block is an empty one. + * Note that this state cannot be stored inside the block, + * but it's usually represented by the block ::BLOCK_EMPTY. + */ +#define BLOCK_STATE_EMPTY 5 /** * Block of a file. @@ -79,6 +118,16 @@ struct snapraid_block { unsigned char hash[HASH_SIZE]; /**< Hash of the block. */ }; +/** + * Block pointer used to mark unused blocks. + */ +#define BLOCK_EMPTY 0 + +/** + * Block pointer used to mark deleted blocks. + */ +#define BLOCK_DELETED ((struct snapraid_block*)-1) + #define FILE_IS_PRESENT 1 /**< If it's seen as present. */ #define FILE_IS_EXCLUDED 2 /**< If it's an excluded file from the processing. */ #define FILE_IS_LARGER 4 /**< If a larger file was already detected. */ @@ -204,7 +253,7 @@ int filter_content(tommy_list* contentlist, const char* path); */ static inline struct snapraid_file* block_file_get(struct snapraid_block* block) { - return (struct snapraid_file*)(block->file_mixed & ~(uintptr_t)BLOCK_HAS_MASK); + return (struct snapraid_file*)(block->file_mixed & ~(uintptr_t)BLOCK_STATE_MASK); } /** @@ -212,22 +261,33 @@ static inline struct snapraid_file* block_file_get(struct snapraid_block* block) */ static inline void block_file_set(struct snapraid_block* block, struct snapraid_file* file) { - block->file_mixed = (block->file_mixed & ~(uintptr_t)BLOCK_HAS_MASK) | (uintptr_t)file; + block->file_mixed = (block->file_mixed & ~(uintptr_t)BLOCK_STATE_MASK) | (uintptr_t)file; } -static inline int block_flag_has(struct snapraid_block* block, unsigned mask) +static inline unsigned block_state_get(struct snapraid_block* block) { - return (block->file_mixed & mask) == mask; + return block->file_mixed & BLOCK_STATE_MASK; } -static inline void block_flag_set(struct snapraid_block* block, unsigned mask) +static inline void block_state_set(struct snapraid_block* block, unsigned state) { - block->file_mixed |= mask; + assert(state != BLOCK_STATE_EMPTY && state != BLOCK_STATE_DELETED); + block->file_mixed &= ~(uintptr_t)BLOCK_STATE_MASK; + block->file_mixed |= state & BLOCK_STATE_MASK; } -static inline void block_flag_clear(struct snapraid_block* block, unsigned mask) +static inline void block_clear_parity(struct snapraid_block* block) { - block->file_mixed &= ~(uintptr_t)mask; + unsigned state = block_state_get(block); + if (state == BLOCK_STATE_BLK) { + block_state_set(block, BLOCK_STATE_INV); + } +} + +static inline int block_has_hash(struct snapraid_block* block) +{ + unsigned state = block_state_get(block); + return state == BLOCK_STATE_BLK || state == BLOCK_STATE_INV; } /** @@ -240,6 +300,11 @@ block_off_t block_file_pos(struct snapraid_block* block); */ int block_is_last(struct snapraid_block* block); +static inline int block_is_valid(struct snapraid_block* block) +{ + return block != BLOCK_EMPTY && block != BLOCK_DELETED; +} + /** * Gets the size in bytes of the block. * If it's the last block of a file it could be less than block_size. @@ -359,7 +424,7 @@ static inline struct snapraid_block* disk_block_get(struct snapraid_disk* disk, if (pos < tommy_array_size(&disk->blockarr)) return tommy_array_get(&disk->blockarr, pos); else - return 0; + return BLOCK_EMPTY; } /** diff --git a/parity.c b/parity.c index abc2f79d..3d769e7f 100644 --- a/parity.c +++ b/parity.c @@ -37,8 +37,8 @@ block_off_t parity_resize(struct snapraid_state* state) /* start from the declared size */ block_off_t block = tommy_array_size(&disk->blockarr); - /* decrease the block until an allocated block */ - while (block > parity_block && tommy_array_get(&disk->blockarr, block - 1) == 0) + /* decrease the block until an allocated or deleted block */ + while (block > parity_block && !block_is_valid(tommy_array_get(&disk->blockarr, block - 1))) --block; /* get the highest value */ diff --git a/scan.c b/scan.c index 1c894c3c..84294f85 100644 --- a/scan.c +++ b/scan.c @@ -57,16 +57,17 @@ static void scan_file_remove(struct snapraid_state* state, struct snapraid_disk* if (disk->first_free_block > block_pos) disk->first_free_block = block_pos; - /* clear the block */ - tommy_array_set(&disk->blockarr, block_pos, 0); + /* set the block as deleted */ + tommy_array_set(&disk->blockarr, block_pos, BLOCK_DELETED); /* invalidate the block of all the other disks */ for(j=state->disklist;j!=0;j=j->next) { struct snapraid_disk* oth_disk = j->data; struct snapraid_block* oth_block = disk_block_get(oth_disk, block_pos); - if (oth_block) { + + if (block_is_valid(oth_block)) { /* remove the parity info for this block */ - block_flag_clear(oth_block, BLOCK_HAS_PARITY); + block_clear_parity(oth_block); } } } @@ -97,7 +98,7 @@ static void scan_file_insert(struct snapraid_state* state, struct snapraid_disk* block_max = tommy_array_size(&disk->blockarr); for(i=0;iblockmax;++i) { /* find a free block */ - while (block_pos < block_max && tommy_array_get(&disk->blockarr, block_pos) != 0) + while (block_pos < block_max && block_is_valid(tommy_array_get(&disk->blockarr, block_pos))) ++block_pos; /* if not found, allocate a new one */ @@ -106,8 +107,16 @@ static void scan_file_insert(struct snapraid_state* state, struct snapraid_disk* tommy_array_grow(&disk->blockarr, block_max); } - /* set it */ + /* set the position */ file->blockvec[i].parity_pos = block_pos; + + /* set the state depending if the used block was EMPTY or DELETED */ + if (tommy_array_get(&disk->blockarr, block_pos) == BLOCK_EMPTY) + block_state_set(&file->blockvec[i], BLOCK_STATE_NEW); + else + block_state_set(&file->blockvec[i], BLOCK_STATE_CHG); + + /* store in the disk map */ tommy_array_set(&disk->blockarr, block_pos, &file->blockvec[i]); } if (file->blockmax) { diff --git a/snapraid.1 b/snapraid.1 index 140a9cb2..4609003b 100644 --- a/snapraid.1 +++ b/snapraid.1 @@ -851,10 +851,30 @@ BLOCK is the block position in the \(a"parity\(a" file. .PP HASH is the hash of the block. In the last block of the file, the HASH is the hash of only the used part of the block. -.SS inv BLOCK [HASH] +.SS inv BLOCK HASH Like \(a"blk\(a", but inform that the parity of this block is invalid. .PP -The HASH may be missing if not yet computed. +This field is used only when you interrupt manually the \(a"sync\(a" +command. +.SS new BLOCK +Like \(a"blk\(a", but for new allocated blocks for which the hash is not +yet computed. +.PP +This field is used only when you interrupt manually the \(a"sync\(a" +command. +.SS chg BLOCK +Like \(a"blk\(a", but for reallocated blocks for which the hash is not +yet computed. +.PP +This field is used only when you interrupt manually the \(a"sync\(a" +command. +.SS hole DISK +Defines the list of blocks that are deleted from a disk. +.PP +This field is used only when you interrupt manually the \(a"sync\(a" +command. +.SS off BLOCK +Defines the block deleted from a disk. .PP This field is used only when you interrupt manually the \(a"sync\(a" command. diff --git a/snapraid.d b/snapraid.d index 3325ddea..50b4f408 100644 --- a/snapraid.d +++ b/snapraid.d @@ -609,10 +609,34 @@ Content HASH is the hash of the block. In the last block of the file, the HASH is the hash of only the used part of the block. - inv BLOCK [HASH] + inv BLOCK HASH Like "blk", but inform that the parity of this block is invalid. - The HASH may be missing if not yet computed. + This field is used only when you interrupt manually the "sync" + command. + + new BLOCK + Like "blk", but for new allocated blocks for which the hash is not + yet computed. + + This field is used only when you interrupt manually the "sync" + command. + + chg BLOCK + Like "blk", but for reallocated blocks for which the hash is not + yet computed. + + This field is used only when you interrupt manually the "sync" + command. + + hole DISK + Defines the list of blocks that are deleted from a disk. + + This field is used only when you interrupt manually the "sync" + command. + + off BLOCK + Defines the block deleted from a disk. This field is used only when you interrupt manually the "sync" command. diff --git a/snapraid.txt b/snapraid.txt index 2d380ffd..9c1568e0 100644 --- a/snapraid.txt +++ b/snapraid.txt @@ -678,12 +678,44 @@ BLOCK is the block position in the "parity" file. HASH is the hash of the block. In the last block of the file, the HASH is the hash of only the used part of the block. -10.7 inv BLOCK [HASH] ---------------------- +10.7 inv BLOCK HASH +------------------- Like "blk", but inform that the parity of this block is invalid. -The HASH may be missing if not yet computed. +This field is used only when you interrupt manually the "sync" +command. + +10.8 new BLOCK +-------------- + +Like "blk", but for new allocated blocks for which the hash is not +yet computed. + +This field is used only when you interrupt manually the "sync" +command. + +10.9 chg BLOCK +-------------- + +Like "blk", but for reallocated blocks for which the hash is not +yet computed. + +This field is used only when you interrupt manually the "sync" +command. + +10.10 hole DISK +--------------- + +Defines the list of blocks that are deleted from a disk. + +This field is used only when you interrupt manually the "sync" +command. + +10.11 off BLOCK +--------------- + +Defines the block deleted from a disk. This field is used only when you interrupt manually the "sync" command. diff --git a/state.c b/state.c index 1cb8c28b..6dda7e04 100644 --- a/state.c +++ b/state.c @@ -624,8 +624,8 @@ void state_read(struct snapraid_state* state) sungetc(c, f); } - if (strcmp(tag, "blk") == 0 || strcmp(tag, "inv") == 0) { - /* "blk"/"inv" command */ + if (strcmp(tag, "blk") == 0 || strcmp(tag, "inv") == 0 || strcmp(tag, "new") == 0 || strcmp(tag, "chg") == 0) { + /* "blk"/"inv"/"new"/"chg" command */ block_off_t v_pos; struct snapraid_block* block; @@ -653,22 +653,31 @@ void state_read(struct snapraid_state* state) } /* set the parity only if present */ - if (tag[0] == 'b') { - block_flag_set(block, BLOCK_HAS_PARITY); + switch (tag[0]) { + case 'b' : + block_state_set(block, BLOCK_STATE_BLK); hash = oathash8(hash, 'b'); - } else { + break; + case 'i' : + block_state_set(block, BLOCK_STATE_INV); hash = oathash8(hash, 'i'); + break; + case 'n' : + block_state_set(block, BLOCK_STATE_NEW); + hash = oathash8(hash, 'n'); + break; + case 'c' : + block_state_set(block, BLOCK_STATE_CHG); + hash = oathash8(hash, 'g'); + break; } block->parity_pos = v_pos; hash = oathash32(hash, v_pos); - /* check if we are at the end of the line */ - c = sgeteol(f); - if (c == '\n') { - /* no hash present */ - sungetc(c, f); - } else { + /* read the hash only for some state */ + if (tag[0] == 'b' || tag[0] == 'i') { + c = sgeteol(f); if (c != ' ') { fprintf(stderr, "Invalid 'blk' specification in '%s' at line %u\n", path, line); exit(EXIT_FAILURE); @@ -681,14 +690,6 @@ void state_read(struct snapraid_state* state) exit(EXIT_FAILURE); } hash = oathashm(hash, block->hash, HASH_SIZE); - - block_flag_set(block, BLOCK_HAS_HASH); - } - - /* parity implies hash */ - if (block_flag_has(block, BLOCK_HAS_PARITY) && !block_flag_has(block, BLOCK_HAS_HASH)) { - fprintf(stderr, "Internal inconsistency in 'blk' specification in '%s' at line %u\n", path, line); - exit(EXIT_FAILURE); } /* insert the block in the block array */ @@ -704,6 +705,28 @@ void state_read(struct snapraid_state* state) /* stat */ ++count_block; + } else if (strcmp(tag, "off") == 0) { + /* "off" command */ + block_off_t v_pos; + + hash = oathash8(hash, 'o'); + + if (!disk) { + fprintf(stderr, "Unexpected 'off' specification in '%s' at line %u\n", path, line); + exit(EXIT_FAILURE); + } + + ret = sgetu32(f, &v_pos); + if (ret < 0) { + fprintf(stderr, "Invalid 'off' specification in '%s' at line %u\n", path, line); + exit(EXIT_FAILURE); + } + + hash = oathash32(hash, v_pos); + + /* insert the block in the block array */ + tommy_array_grow(&disk->blockarr, v_pos + 1); + tommy_array_set(&disk->blockarr, v_pos, BLOCK_DELETED); } else if (strcmp(tag, "file") == 0) { /* file */ char sub[PATH_MAX]; @@ -813,6 +836,34 @@ void state_read(struct snapraid_state* state) /* stat */ ++count_file; + } else if (strcmp(tag, "hole") == 0) { + /* hole */ + tommy_node* i; + + hash = oathash8(hash, 'h'); + + if (file) { + fprintf(stderr, "Missing 'blk' specification in '%s' at line %u\n", path, line); + exit(EXIT_FAILURE); + } + + ret = sgettok(f, buffer, sizeof(buffer)); + if (ret < 0) { + fprintf(stderr, "Invalid 'hole' specification in '%s' at line %u\n", path, line); + exit(EXIT_FAILURE); + } + hash = oathashs(hash, buffer); + + /* find the disk */ + for(i=state->disklist;i!=0;i=i->next) { + disk = i->data; + if (strcmp(disk->name, buffer) == 0) + break; + } + if (!i) { + fprintf(stderr, "Disk named '%s' not found in '%s' at line %u\n", buffer, path, line); + exit(EXIT_FAILURE); + } } else if (strcmp(tag, "symlink") == 0) { /* symlink */ char sub[PATH_MAX]; @@ -1123,8 +1174,8 @@ void state_write(struct snapraid_state* state) /* for each file */ for(j=disk->filelist;j!=0;j=j->next) { - block_off_t k; struct snapraid_file* file = j->data; + block_off_t k; uint64_t size; uint64_t mtime; uint64_t inode; @@ -1155,22 +1206,35 @@ void state_write(struct snapraid_state* state) exit(EXIT_FAILURE); } - /* for each block */ + /* for each block in the file */ for(k=0;kblockmax;++k) { struct snapraid_block* block = &file->blockvec[k]; + unsigned block_state; - if (block_flag_has(block, BLOCK_HAS_PARITY)) { + block_state = block_state_get(block); + switch (block_state) { + case BLOCK_STATE_BLK : sputsl("blk ", f); hash = oathash8(hash, 'b'); - } else { + break; + case BLOCK_STATE_INV : sputsl("inv ", f); hash = oathash8(hash, 'i'); + break; + case BLOCK_STATE_NEW : + sputsl("new ", f); + hash = oathash8(hash, 'n'); + break; + case BLOCK_STATE_CHG : + sputsl("chg ", f); + hash = oathash8(hash, 'g'); + break; } sputu32(block->parity_pos, f); hash = oathash32(hash, block->parity_pos); - if (block_flag_has(block, BLOCK_HAS_HASH)) { + if (block_state == BLOCK_STATE_BLK || block_state == BLOCK_STATE_INV) { sputc(' ', f); sputhex(block->hash, HASH_SIZE, f); hash = oathashm(hash, block->hash, HASH_SIZE); @@ -1211,6 +1275,47 @@ void state_write(struct snapraid_state* state) ++count_link; } + + { + block_off_t k; + block_off_t blockmax; + int first_deleted; + + first_deleted = 1; + blockmax = tommy_array_size(&disk->blockarr); + + /* for each deleted block in the disk */ + for(k=0;kblockarr, k); + if (block == BLOCK_DELETED) { + if (first_deleted) { + first_deleted = 0; + + sputsl("hole ", f); + hash = oathash8(hash, 'h'); + sputs(disk->name, f); + hash = oathashs(hash, disk->name); + sputeol(f); + if (serror(f)) { + fprintf(stderr, "Error writing the content file '%s'. %s.\n", serrorfile(f), strerror(errno)); + exit(EXIT_FAILURE); + } + } + + sputsl("off ", f); + hash = oathash8(hash, 'o'); + + sputu32(k, f); + hash = oathash32(hash, k); + + sputeol(f); + if (serror(f)) { + fprintf(stderr, "Error writing the content file '%s'. %s.\n", serrorfile(f), strerror(errno)); + exit(EXIT_FAILURE); + } + } + } + } } sputsl("sign ", f); diff --git a/sync.c b/sync.c index 0b21e990..177918ef 100644 --- a/sync.c +++ b/sync.c @@ -65,10 +65,12 @@ static int state_sync_process(struct snapraid_state* state, struct snapraid_pari /* for each disk */ one_invalid = 0; for(j=0;jblockarr, i, BLOCK_EMPTY); + + /* mark the state as needing write */ + state->need_write = 1; + } + } + + /* skip */ continue; + } /* by default process the block, and skip it if something go wrong */ skip_this_block = 0; @@ -123,7 +143,7 @@ static int state_sync_process(struct snapraid_state* state, struct snapraid_pari /* if the disk block is not used */ block = disk_block_get(handle[j].disk, i); - if (!block) { + if (!block_is_valid(block)) { /* use an empty block */ memset(buffer[j], 0, state->block_size); continue; @@ -196,10 +216,12 @@ static int state_sync_process(struct snapraid_state* state, struct snapraid_pari goto bail; } + countsize += read_size; + /* now compute the hash */ memhash(state->hash, hash, buffer[j], read_size); - if (block_flag_has(block, BLOCK_HAS_HASH)) { + if (block_has_hash(block)) { /* compare the hash */ if (memcmp(hash, block->hash, HASH_SIZE) != 0) { fprintf(stderr, "%u: Data error for file %s at position %u\n", i, block_file_get(block)->sub, block_file_pos(block)); @@ -213,8 +235,6 @@ static int state_sync_process(struct snapraid_state* state, struct snapraid_pari /* this allow on error to do not save the failed computation */ memcpy(block->hash, hash, HASH_SIZE); } - - countsize += read_size; } /* if we have read all the data required, proceed with the parity */ @@ -245,14 +265,23 @@ static int state_sync_process(struct snapraid_state* state, struct snapraid_pari /* for each disk, mark the blocks as processed */ for(j=0;jblockarr, i, BLOCK_EMPTY); + continue; + } /* now all the blocks have the hash and the parity computed */ - block_flag_set(block, BLOCK_HAS_HASH | BLOCK_HAS_PARITY); + block_state_set(block, BLOCK_STATE_BLK); } } @@ -293,6 +322,7 @@ int state_sync(struct snapraid_state* state, block_off_t blockstart, block_off_t { block_off_t blockmax; data_off_t size; + tommy_node* i; int ret; struct snapraid_parity parity; struct snapraid_parity qarity; @@ -305,6 +335,22 @@ int state_sync(struct snapraid_state* state, block_off_t blockstart, block_off_t blockmax = parity_resize(state); size = blockmax * (data_off_t)state->block_size; + /* remove all the deleted blocks over the upper limit */ + for(i=state->disklist;i!=0;i=i->next) { + struct snapraid_disk* disk = i->data; + block_off_t diskblockmax = tommy_array_size(&disk->blockarr); + block_off_t block; + + for(block=blockmax;blockblockarr, block) == BLOCK_DELETED) { + tommy_array_set(&disk->blockarr, block, BLOCK_EMPTY); + + /* mark the state as needing write */ + state->need_write = 1; + } + } + } + if (blockstart > blockmax) { fprintf(stderr, "Error in the starting block %u. It's bigger than the parity size %u.\n", blockstart, blockmax); exit(EXIT_FAILURE);