From b17e618d49c26e5486d9bf98810967d7e37b3a1e Mon Sep 17 00:00:00 2001 From: Andrea Mazzoleni Date: Mon, 5 Oct 2015 21:40:44 +0200 Subject: [PATCH] Add an extensive parity filesystem check With the new mapping it's now possible to check everything in a very fast way. --- cmdline/elem.c | 208 +++++++++++++++++++++++++++++++++++++++++++++--- cmdline/elem.h | 16 ++++ cmdline/scan.c | 6 ++ cmdline/state.c | 54 +++++++------ cmdline/state.h | 6 ++ cmdline/sync.c | 9 ++- 6 files changed, 260 insertions(+), 39 deletions(-) diff --git a/cmdline/elem.c b/cmdline/elem.c index 5f3c1373..2b5628b3 100644 --- a/cmdline/elem.c +++ b/cmdline/elem.c @@ -771,7 +771,7 @@ struct chunk_disk_empty { }; /** - * Search for any block inside the specified blockmax. + * Compare the chunk if inside the specified blockmax. */ static int chunk_disk_empty_compare(const void* void_a, const void* void_b) { @@ -812,7 +812,7 @@ struct chunk_disk_size { }; /** - * Search for the chunk with the highest parity position. + * Compare the chunk by highest parity position. * * The maximum parity position is stored as size. */ @@ -836,12 +836,179 @@ block_off_t disk_size(struct snapraid_disk* disk) return arg.size; } +struct chunk_check { + const struct snapraid_chunk* prev; + int result; +}; + +void chunk_parity_check_foreach(void* void_arg, void* void_obj) +{ + struct chunk_check* arg = void_arg; + const struct snapraid_chunk* obj = void_obj; + const struct snapraid_chunk* prev = arg->prev; + + /* set the next previous block */ + arg->prev = obj; + + /* stop reporting if too many errors */ + if (arg->result > 100) { + /* LCOV_EXCL_START */ + return; + /* LCOV_EXCL_STOP */ + } + + /* check only if there is a previous block */ + if (!prev) + return; + + /* check the order */ + if (prev->parity_pos >= obj->parity_pos) { + /* LCOV_EXCL_START */ + log_fatal("Internal inconsistency in parity order for files '%s' at '%u:%u' and '%s' at '%u:%u'\n", + prev->file->sub, prev->parity_pos, prev->count, obj->file->sub, obj->parity_pos, obj->count); + ++arg->result; + return; + /* LCOV_EXCL_STOP */ + } + + /* check that the chunks don't overlap */ + if (prev->parity_pos + prev->count > obj->parity_pos) { + /* LCOV_EXCL_START */ + log_fatal("Internal inconsistency for parity overlap for files '%s' at '%u:%u' and '%s' at '%u:%u'\n", + prev->file->sub, prev->parity_pos, prev->count, obj->file->sub, obj->parity_pos, obj->count); + ++arg->result; + return; + /* LCOV_EXCL_STOP */ + } +} + +void chunk_file_check_foreach(void* void_arg, void* void_obj) +{ + struct chunk_check* arg = void_arg; + const struct snapraid_chunk* obj = void_obj; + const struct snapraid_chunk* prev = arg->prev; + + /* set the next previous block */ + arg->prev = obj; + + /* stop reporting if too many errors */ + if (arg->result > 100) { + /* LCOV_EXCL_START */ + return; + /* LCOV_EXCL_STOP */ + } + + /* note that for deleted files, some chunks may be missing */ + + /* if the files are different */ + if (!prev || prev->file != obj->file) { + if (prev != 0) { + if (file_flag_has(prev->file, FILE_IS_DELETED)) { + /* check that the chunk doesn't overflow the file */ + if (prev->file_pos + prev->count > prev->file->blockmax) { + /* LCOV_EXCL_START */ + log_fatal("Internal inconsistency in delete end for file '%s' at '%u:%u' overflowing size '%u'\n", + prev->file->sub, prev->file_pos, prev->count, prev->file->blockmax); + ++arg->result; + return; + /* LCOV_EXCL_STOP */ + } + } else { + /* check that the chunk ends the file */ + if (prev->file_pos + prev->count != prev->file->blockmax) { + /* LCOV_EXCL_START */ + log_fatal("Internal inconsistency in file end for file '%s' at '%u:%u' instead of size '%u'\n", + prev->file->sub, prev->file_pos, prev->count, prev->file->blockmax); + ++arg->result; + return; + /* LCOV_EXCL_STOP */ + } + } + } + + if (file_flag_has(obj->file, FILE_IS_DELETED)) { + /* check that the chunk doesn't overflow the file */ + if (obj->file_pos + obj->count > obj->file->blockmax) { + /* LCOV_EXCL_START */ + log_fatal("Internal inconsistency in delete start for file '%s' at '%u:%u' overflowing size '%u'\n", + obj->file->sub, obj->file_pos, obj->count, obj->file->blockmax); + ++arg->result; + return; + /* LCOV_EXCL_STOP */ + } + } else { + /* check that the chunk starts the file */ + if (obj->file_pos != 0) { + /* LCOV_EXCL_START */ + log_fatal("Internal inconsistency in file start for file '%s' at '%u:%u'\n", + obj->file->sub, obj->file_pos, obj->count); + ++arg->result; + return; + /* LCOV_EXCL_STOP */ + } + } + } else { + /* check the order */ + if (prev->file_pos >= obj->file_pos) { + /* LCOV_EXCL_START */ + log_fatal("Internal inconsistency in file order for file '%s' at '%u:%u' and at '%u:%u'\n", + prev->file->sub, prev->file_pos, prev->count, obj->file_pos, obj->count); + ++arg->result; + return; + /* LCOV_EXCL_STOP */ + } + + if (file_flag_has(obj->file, FILE_IS_DELETED)) { + /* check that the chunks don't overlap */ + if (prev->file_pos + prev->count > obj->file_pos) { + /* LCOV_EXCL_START */ + log_fatal("Internal inconsistency in delete sequence for file '%s' at '%u:%u' and at '%u:%u'\n", + prev->file->sub, prev->file_pos, prev->count, obj->file_pos, obj->count); + ++arg->result; + return; + /* LCOV_EXCL_STOP */ + } + } else { + /* check that the chunks are sequential */ + if (prev->file_pos + prev->count != obj->file_pos) { + /* LCOV_EXCL_START */ + log_fatal("Internal inconsistency in file sequence for file '%s' at '%u:%u' and at '%u:%u'\n", + prev->file->sub, prev->file_pos, prev->count, obj->file_pos, obj->count); + ++arg->result; + return; + /* LCOV_EXCL_STOP */ + } + } + } +} + +int fs_check(struct snapraid_disk* disk) +{ + struct chunk_check arg; + + /* error count starts from 0 */ + arg.result = 0; + + /* check parity sequence */ + arg.prev = 0; + tommy_tree_foreach_arg(&disk->fs_parity, chunk_parity_check_foreach, &arg); + + /* check file sequence */ + arg.prev = 0; + tommy_tree_foreach_arg(&disk->fs_file, chunk_file_check_foreach, &arg); + + if (arg.result != 0) + return -1; + + return 0; +} + struct chunk_parity_inside { block_off_t parity_pos; }; /** - * Search for the chunk containing the specified parity position. + * Compare the chunk if containing the specified parity position. */ static int chunk_parity_inside_compare(const void* void_a, const void* void_b) { @@ -856,6 +1023,11 @@ static int chunk_parity_inside_compare(const void* void_a, const void* void_b) return 0; } +/** + * Seach the chunk at the specified parity position. + * The search is optimized for sequential accesses. + * \return If not found return 0 + */ static struct snapraid_chunk* fs_par2chunk_get(struct snapraid_disk* disk, block_off_t parity_pos) { struct snapraid_chunk* chunk; @@ -886,7 +1058,7 @@ struct chunk_file_inside { }; /** - * Search for the chunk containing the specified file position. + * Compare the chunk if containing the specified file position. */ static int chunk_file_inside_compare(const void* void_a, const void* void_b) { @@ -906,6 +1078,11 @@ static int chunk_file_inside_compare(const void* void_a, const void* void_b) return 0; } +/** + * Seach the chunk at the specified file position. + * The search is optimized for sequential accesses. + * \return If not found return 0 + */ static struct snapraid_chunk* fs_file2chunk_get(struct snapraid_disk* disk, struct snapraid_file* file, block_off_t file_pos) { struct snapraid_chunk* chunk; @@ -952,7 +1129,7 @@ block_off_t fs_file2par_get(struct snapraid_disk* disk, struct snapraid_file* fi chunk = fs_file2chunk_get(disk, file, file_pos); if (!chunk) { /* LCOV_EXCL_START */ - log_fatal("Internal inconsistency for a file without parity in disk '%s'\n", disk->name); + log_fatal("Internal inconsistency when resolving a file to a not existing chunk in disk '%s'\n", disk->name); exit(EXIT_FAILURE); /* LCOV_EXCL_STOP */ } @@ -963,16 +1140,18 @@ block_off_t fs_file2par_get(struct snapraid_disk* disk, struct snapraid_file* fi void fs_allocate(struct snapraid_disk* disk, block_off_t parity_pos, struct snapraid_file* file, block_off_t file_pos) { struct snapraid_chunk* chunk; + struct snapraid_chunk* parity_chunk; + struct snapraid_chunk* file_chunk; if (file_pos > 0) { /* search an existing chunk for the previous file_pos */ chunk = fs_file2chunk_get(disk, file, file_pos - 1); if (chunk != 0 && parity_pos == chunk->parity_pos + chunk->count) { - /* ensure that we are really extending a chunk */ + /* ensure that we are extending the chunk at the end */ if (file_pos != chunk->file_pos + chunk->count) { /* LCOV_EXCL_START */ - log_fatal("Internal inconsistency extending a chunk in disk '%s'\n", disk->name); + log_fatal("Internal inconsistency extending a chunk in the middle in disk '%s'\n", disk->name); exit(EXIT_FAILURE); /* LCOV_EXCL_STOP */ } @@ -988,8 +1167,15 @@ void fs_allocate(struct snapraid_disk* disk, block_off_t parity_pos, struct snap chunk = chunk_alloc(parity_pos, file, file_pos, 1); /* insert te chunk in the trees */ - tommy_tree_insert(&disk->fs_parity, &chunk->parity_node, chunk); - tommy_tree_insert(&disk->fs_file, &chunk->file_node, chunk); + parity_chunk = tommy_tree_insert(&disk->fs_parity, &chunk->parity_node, chunk); + file_chunk = tommy_tree_insert(&disk->fs_file, &chunk->file_node, chunk); + + if (parity_chunk != chunk || file_chunk != chunk) { + /* LCOV_EXCL_START */ + log_fatal("Internal inconsistency when allocating a chunk that already exists in disk '%s'\n", disk->name); + exit(EXIT_FAILURE); + /* LCOV_EXCL_STOP */ + } /* store the last accessed chunk */ disk->fs_last = chunk; @@ -1002,7 +1188,7 @@ void fs_deallocate(struct snapraid_disk* disk, block_off_t parity_pos) chunk = fs_par2chunk_get(disk, parity_pos); if (!chunk) { /* LCOV_EXCL_START */ - log_fatal("Internal inconsistency for clearing a not existing block in disk '%s'\n", disk->name); + log_fatal("Internal inconsistency when deallocating a not existing chunk in disk '%s'\n", disk->name); exit(EXIT_FAILURE); /* LCOV_EXCL_STOP */ } @@ -1036,7 +1222,7 @@ void fs_deallocate(struct snapraid_disk* disk, block_off_t parity_pos) } /* LCOV_EXCL_START */ - log_fatal("Internal inconsistency for clearing in the middle of a chunk in disk '%s'\n", disk->name); + log_fatal("Internal inconsistency when deallocating a chunk in the middle in disk '%s'\n", disk->name); exit(EXIT_FAILURE); /* LCOV_EXCL_STOP */ } diff --git a/cmdline/elem.h b/cmdline/elem.h index b6e56b24..fb15a9f3 100644 --- a/cmdline/elem.h +++ b/cmdline/elem.h @@ -229,6 +229,16 @@ struct snapraid_block { */ #define FILE_IS_WITHOUT_INODE 0x200 +/** + * The file is deleted. + * This happens when a file is deleted from the array, + * but it's keep inside the parity until the next sync. + * + * During the filesystem check we needs this information, + * because deleted files may be present only partially. + */ +#define FILE_IS_DELETED 0x400 + #define FILE_IS_HARDLINK 0x1000 /**< If it's an hardlink. */ #define FILE_IS_SYMLINK 0x2000 /**< If it's a file symlink. */ #define FILE_IS_SYMDIR 0x4000 /**< If it's a dir symlink for Windows. Not yet supported. */ @@ -867,6 +877,12 @@ block_off_t disk_size(struct snapraid_disk* disk); */ int disk_is_empty(struct snapraid_disk* disk, block_off_t blockmax); +/** + * Check the filesystem for errors. + * Return 0 if it's OK. + */ +int fs_check(struct snapraid_disk* disk); + /** * Allocate a parity position for the specified file position. * diff --git a/cmdline/scan.c b/cmdline/scan.c index ee26c2e7..94b0e9a4 100644 --- a/cmdline/scan.c +++ b/cmdline/scan.c @@ -317,6 +317,9 @@ static void scan_file_deallocate(struct snapraid_scan* scan, struct snapraid_fil block_state_set(block, BLOCK_STATE_DELETED); } + /* mark the file as deleted */ + file_flag_set(file, FILE_IS_DELETED); + /* insert it in the list of deleted blocks */ tommy_list_insert_tail(&disk->deletedlist, &file->nodelist, file); } @@ -1772,6 +1775,9 @@ static int state_diffscan(struct snapraid_state* state, int is_diff) tommy_list_foreach(&scanlist, (tommy_foreach_func*)free); + /* check the filesystem on all disks */ + state_fscheck(state, "after scan"); + if (is_diff) { /* check for file difference */ if (!no_difference) diff --git a/cmdline/state.c b/cmdline/state.c index 52f9a630..6cc1f4fe 100644 --- a/cmdline/state.c +++ b/cmdline/state.c @@ -1792,15 +1792,6 @@ static void state_read_binary(struct snapraid_state* state, const char* path, ST block_state_set(block, BLOCK_STATE_REP); } - /* we must not overwrite existing blocks */ - if (fs_par2block_get(disk, v_pos) != BLOCK_EMPTY) { - /* LCOV_EXCL_START */ - decoding_error(path, f); - log_fatal("Internal inconsistency in block existence!\n"); - exit(EXIT_FAILURE); - /* LCOV_EXCL_STOP */ - } - /* set the parity association */ fs_allocate(disk, v_pos, file, v_idx); @@ -1958,7 +1949,10 @@ static void state_read_binary(struct snapraid_state* state, const char* path, ST /* if it's a run of deleted blocks */ /* allocate a fake deleted file */ - deleted = file_alloc(state->block_size, "", v_count * state->block_size, 0, 0, 0, 0); + deleted = file_alloc(state->block_size, "", v_count * state->block_size, 0, 0, 0, 0); + + /* mark the file as deleted */ + file_flag_set(deleted, FILE_IS_DELETED); /* insert it in the list of deleted files */ tommy_list_insert_tail(&disk->deletedlist, &deleted->nodelist, deleted); @@ -1986,21 +1980,8 @@ static void state_read_binary(struct snapraid_state* state, const char* path, ST hash_invalid_set(block->hash); } - /* we must not overwrite existing blocks */ - if (fs_par2block_get(disk, v_pos) != BLOCK_EMPTY) { - /* LCOV_EXCL_START */ - log_fatal("Internal inconsistency for used hole at pos %u!\n", v_pos); - if (state->opt.skip_content_check) { - log_fatal("Overriding as used.\n"); - } else { - decoding_error(path, f); - exit(EXIT_FAILURE); - } - /* LCOV_EXCL_STOP */ - } else { - /* insert the block in the block array */ - fs_allocate(disk, v_pos, deleted, v_idx); - } + /* insert the block in the block array */ + fs_allocate(disk, v_pos, deleted, v_idx); /* go to next block */ ++v_pos; @@ -2424,6 +2405,9 @@ static void state_read_binary(struct snapraid_state* state, const char* path, ST /* LCOV_EXCL_STOP */ } + /* check the filesystem on all disks */ + state_fscheck(state, "after read"); + /* check that the stored parity size matches the loaded state */ if (blockmax != parity_allocated_size(state)) { /* LCOV_EXCL_START */ @@ -2467,6 +2451,9 @@ static void state_write_binary(struct snapraid_state* state, STREAM* f) /* blocks of all array */ blockmax = parity_allocated_size(state); + /* check the filesystem on all disks */ + state_fscheck(state, "before write"); + /* clear the info for unused blocks */ /* and get some other info */ info_oldest = 0; /* oldest time in info */ @@ -3474,6 +3461,23 @@ void state_usage_print(struct snapraid_state* state) } } +void state_fscheck(struct snapraid_state* state, const char* ope) +{ + tommy_node* i; + + /* check the filesystem on all disks */ + for (i = state->disklist; i != 0; i = i->next) { + struct snapraid_disk* disk = i->data; + + if (fs_check(disk) != 0) { + /* LCOV_EXCL_START */ + log_fatal("Internal inconsistency in filesystem for disk '%s' %s\n", disk->name, ope); + exit(EXIT_FAILURE); + /* LCOV_EXCL_STOP */ + } + } +} + void generate_configuration(const char* path) { struct snapraid_state state; diff --git a/cmdline/state.h b/cmdline/state.h index 01ff0133..b5ce33bf 100644 --- a/cmdline/state.h +++ b/cmdline/state.h @@ -372,6 +372,12 @@ static inline void state_usage_parity(struct snapraid_state* state, unsigned lev */ void state_usage_print(struct snapraid_state* state); +/** + * Check the filesystem on all disks. + * On error it aborts. + */ +void state_fscheck(struct snapraid_state* state, const char* ope); + /****************************************************************************/ /* misc */ diff --git a/cmdline/sync.c b/cmdline/sync.c index db444d3d..7c9471e2 100644 --- a/cmdline/sync.c +++ b/cmdline/sync.c @@ -1019,9 +1019,12 @@ static int state_sync_process(struct snapraid_state* state, struct snapraid_pari if (!io_error_on_this_block) { /* for each disk, mark the blocks as processed */ for (j = 0; j < diskmax; ++j) { - struct snapraid_block* block = BLOCK_EMPTY; - if (handle[j].disk) - block = fs_par2block_get(handle[j].disk, i); + struct snapraid_block* block; + + if (!handle[j].disk) + continue; + + block = fs_par2block_get(handle[j].disk, i); if (block == BLOCK_EMPTY) { /* nothing to do */