diff --git a/HISTORY b/HISTORY index 95d633bb..fb8aacb9 100644 --- a/HISTORY +++ b/HISTORY @@ -1,6 +1,15 @@ SnapRAID HISTORY ================ +10.0 2016/01 +=========== + * Increases the speed of the 'sync' and 'scrub' commands with a new + read-ahead implementation using threads. We have one thread for each + disk, dedicated exclusively to read data or parity in advance, + and to write parity later. + This ensures to keep the disks always busy, maximizing the data + throughput. + 9.2 2016/01 =========== * Fixes support for symlinks pointing to an empty target. Before they diff --git a/Makefile.am b/Makefile.am index 389b54cc..5c903ac0 100644 --- a/Makefile.am +++ b/Makefile.am @@ -16,6 +16,7 @@ snapraid_SOURCES = \ raid/tag.c \ tommyds/tommy.c \ cmdline/snapraid.c \ + cmdline/io.c \ cmdline/util.c \ cmdline/stream.c \ cmdline/support.c \ @@ -70,6 +71,7 @@ noinst_HEADERS = \ tommyds/tommytree.c \ cmdline/portable.h \ cmdline/snapraid.h \ + cmdline/io.h \ cmdline/util.h \ cmdline/stream.h \ cmdline/support.h \ @@ -158,7 +160,7 @@ maintainer-clean-local: # --test-force-order-alpha # Ensures to process files always in the same order despites # the inode, physical location, and dir order assigned by the OS. -CHECKFLAGS_BASE = --test-skip-device --test-skip-self --test-force-order-alpha --test-force-progress +CHECKFLAGS_BASE = --test-skip-device --test-skip-self --test-force-order-alpha --test-force-progress --no-warnings # Physical offset options CHECKFLAGS_PHYSICAL = --test-skip-device --test-skip-self --test-force-order-physical -q -q -q @@ -227,7 +229,7 @@ if HAVE_THREAD_CHECKER $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) -c $(CONF) sync # Other commands that uses threads $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) -c $(CONF) test-rewrite - $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) -c $(CONF) scrub + $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) -c $(CONF) scrub -p full else #### COMMAND LINE #### $(MSG) Help @@ -272,7 +274,7 @@ endif $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) -c $(CONF) check $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) -c $(CONF) -a check #### CONTROLLED #### - @echo -- Filesystem allocation test + $(MSG) Filesystem allocation test head -c 8192 /dev/zero > bench/disk1/TEST # Copy the file to trigger the copy optimization check cp -a bench/disk1/TEST bench/disk2/TEST @@ -287,7 +289,7 @@ endif $(FAILENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) -c $(CONF) sync --test-expect-failure # Now sync will complete with -E $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) -c $(CONF) -E sync - @echo -- Filesystem fragmentation test + $(MSG) Filesystem fragmentation test head -c 8192 /dev/zero > bench/disk1/TEST1 head -c 8192 /dev/zero > bench/disk1/TEST2 head -c 8192 /dev/zero > bench/disk1/TEST3 @@ -407,7 +409,7 @@ endif mkdir bench/disk3/empty3a mkdir bench/disk3/empty3a/emptyinner mkdir bench/disk3/empty3b - echo 1 > bench/disk1/target1 + echo TARGET1 > bench/disk1/target1 ln bench/disk1/target1 bench/disk1/file_hardlink1 ln bench/disk1/target1 bench/disk1/file_hardlink2 ln bench/disk1/target1 bench/disk1/file_hardlink3 @@ -419,13 +421,16 @@ endif $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) -c $(CONF) status $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) --test-skip-sequential -c $(PAR1) check #### CHANGE LINKS #### - echo 2 > bench/disk1/target2 +# Use a different size ("22" instead of "1") to ensure to recognize the file different +# even if it gets the same timestamp in case subsecond timestamp is no available + echo TARGET22 > bench/disk1/target2 rm bench/disk1/file_hardlink1 ln bench/disk1/target2 bench/disk1/file_hardlink1 rm bench/disk1/file_symlink1 ln -s bench/disk1/target2 bench/disk1/file_symlink1 $(FAILENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) -c $(PAR1) --test-expect-need-sync diff > output.log - $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) -c $(PAR1) sync + $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) -c $(PAR1) sync -l ">&1" + $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) -c $(PAR1) check -l ">&1" #### MISC COMMANDS #### $(MSG) Some commands with a not empty array $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS_VERBOSE) -c $(PAR1) dup @@ -436,11 +441,11 @@ endif $(MSG) Extend PAR1 to max parity with fix and check $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) --test-expect-recoverable -c $(CONF) check -l test.log $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) -c $(CONF) fix -d 2-parity -d 3-parity -d 4-parity -d 5-parity -d 6-parity -l test.log - $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) -c $(CONF) check + $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) -c $(CONF) check -l ">&1" $(MSG) Fix with unaccessible parity and disk $(FAILENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) -c $(NOACCESS) fix --test-expect-failure $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) -c $(NOACCESS) fix --force-device --test-expect-recoverable -l test.log - echo -- Rename a disk + $(MSG) Rename a disk $(FAILENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) -c $(RENAME) --test-match-first-uuid sync $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) -c $(CONF) --test-match-first-uuid sync #### SCRUB #### @@ -892,7 +897,7 @@ endif endif $(MSG) Regression test completed with SUCCESS! $(MSG) Please ignore any error message printed above, they are expected! - $(MSG) Everything is OK + $(MSG) Everything OK # Manual testing diff --git a/cmdline/check.c b/cmdline/check.c index 55a35eb5..dd80763e 100644 --- a/cmdline/check.c +++ b/cmdline/check.c @@ -764,10 +764,11 @@ static int file_post(struct snapraid_state* state, int fix, unsigned i, struct s log_tag("status:recoverable:%s:%s\n", disk->name, esc(file->sub)); msg_info("recoverable %s\n", path); } else { - log_tag("status:correct:%s:%s\n", disk->name, esc(file->sub)); /* we don't use msg_verbose() because it also goes into the log */ - if (msg_level >= MSG_VERBOSE) + if (msg_level >= MSG_VERBOSE) { + log_tag("status:correct:%s:%s\n", disk->name, esc(file->sub)); msg_info("correct %s\n", path); + } } } @@ -1942,7 +1943,7 @@ int state_check(struct snapraid_state* state, int fix, block_off_t blockstart, b } parity_ptr[l] = &parity[l]; - ret = parity_create(parity_ptr[l], state->parity[l].path, &out_size, state->file_mode); + ret = parity_create(parity_ptr[l], l, state->parity[l].path, &out_size, state->file_mode); if (ret == -1) { /* LCOV_EXCL_START */ log_fatal("WARNING! Without an accessible %s file, it isn't possible to fix any error.\n", lev_name(l)); @@ -1963,7 +1964,7 @@ int state_check(struct snapraid_state* state, int fix, block_off_t blockstart, b /* it may fail if the file doesn't exist, in this case we continue to check the files */ for (l = 0; l < state->level; ++l) { parity_ptr[l] = &parity[l]; - ret = parity_open(parity_ptr[l], state->parity[l].path, state->file_mode); + ret = parity_open(parity_ptr[l], l, state->parity[l].path, state->file_mode); if (ret == -1) { msg_status("No accessible %s file, only files will be checked.\n", lev_name(l)); /* continue anyway */ diff --git a/cmdline/dry.c b/cmdline/dry.c index c9a460d2..fed8e4a9 100644 --- a/cmdline/dry.c +++ b/cmdline/dry.c @@ -22,30 +22,174 @@ #include "state.h" #include "parity.h" #include "handle.h" +#include "io.h" /****************************************************************************/ /* dry */ -static int state_dry_process(struct snapraid_state* state, struct snapraid_parity_handle** parity, block_off_t blockstart, block_off_t blockmax) +/** + * Check if we have to process the specified block index ::i. + */ +static int block_is_enabled(void* void_plan, block_off_t i) { + (void)void_plan; + (void)i; + + return 1; +} + +static void dry_data_reader(struct snapraid_worker* worker, struct snapraid_task* task) +{ + struct snapraid_io* io = worker->io; + struct snapraid_state* state = io->state; + struct snapraid_handle* handle = worker->handle; + struct snapraid_disk* disk = handle->disk; + block_off_t blockcur = task->position; + unsigned char* buffer = task->buffer; + int ret; + + /* if the disk position is not used */ + if (!disk) { + /* use an empty block */ + memset(buffer, 0, state->block_size); + task->state = TASK_STATE_DONE; + return; + } + + /* get the block */ + task->block = fs_par2block_get(disk, blockcur); + + /* if the block is not used */ + if (!block_has_file(task->block)) { + /* use an empty block */ + memset(buffer, 0, state->block_size); + task->state = TASK_STATE_DONE; + return; + } + + /* get the file of this block */ + task->file = fs_par2file_get(disk, blockcur, &task->file_pos); + + /* if the file is different than the current one, close it */ + if (handle->file != 0 && handle->file != task->file) { + /* keep a pointer at the file we are going to close for error reporting */ + struct snapraid_file* report = handle->file; + ret = handle_close(handle); + if (ret == -1) { + /* LCOV_EXCL_START */ + /* This one is really an unexpected error, because we are only reading */ + /* and closing a descriptor should never fail */ + if (errno == EIO) { + log_tag("error:%u:%s:%s: Close EIO error. %s\n", blockcur, disk->name, esc(report->sub), strerror(errno)); + log_fatal("DANGER! Unexpected input/output close error in a data disk, it isn't possible to dry.\n"); + log_fatal("Ensure that disk '%s' is sane and that file '%s' can be accessed.\n", disk->dir, handle->path); + log_fatal("Stopping at block %u\n", blockcur); + task->state = TASK_STATE_IOERROR; + return; + } + + log_tag("error:%u:%s:%s: Close error. %s\n", blockcur, disk->name, esc(report->sub), strerror(errno)); + log_fatal("WARNING! Unexpected close error in a data disk, it isn't possible to dry.\n"); + log_fatal("Ensure that file '%s' can be accessed.\n", handle->path); + log_fatal("Stopping at block %u\n", blockcur); + task->state = TASK_STATE_ERROR; + return; + /* LCOV_EXCL_STOP */ + } + } + + ret = handle_open(handle, task->file, state->file_mode, log_error, 0); + if (ret == -1) { + if (errno == EIO) { + /* LCOV_EXCL_START */ + log_tag("error:%u:%s:%s: Open EIO error. %s\n", blockcur, disk->name, esc(task->file->sub), strerror(errno)); + log_fatal("DANGER! Unexpected input/output open error in a data disk, it isn't possible to dry.\n"); + log_fatal("Ensure that disk '%s' is sane and that file '%s' can be accessed.\n", disk->dir, handle->path); + log_fatal("Stopping at block %u\n", blockcur); + task->state = TASK_STATE_IOERROR; + return; + /* LCOV_EXCL_STOP */ + } + + log_tag("error:%u:%s:%s: Open error. %s\n", blockcur, disk->name, esc(task->file->sub), strerror(errno)); + task->state = TASK_STATE_ERROR_CONTINUE; + return; + } + + task->read_size = handle_read(handle, task->file_pos, buffer, state->block_size, log_error, 0); + if (task->read_size == -1) { + if (errno == EIO) { + log_tag("error:%u:%s:%s: Read EIO error at position %u. %s\n", blockcur, disk->name, esc(task->file->sub), task->file_pos, strerror(errno)); + log_error("Input/Output error in file '%s' at position '%u'\n", handle->path, task->file_pos); + task->state = TASK_STATE_IOERROR_CONTINUE; + return; + } + + log_tag("error:%u:%s:%s: Read error at position %u. %s\n", blockcur, disk->name, esc(task->file->sub), task->file_pos, strerror(errno)); + task->state = TASK_STATE_ERROR_CONTINUE; + return; + } + + /* store the path of the opened file */ + pathcpy(task->path, sizeof(task->path), handle->path); + + task->state = TASK_STATE_DONE; +} + +static void dry_parity_reader(struct snapraid_worker* worker, struct snapraid_task* task) +{ + struct snapraid_io* io = worker->io; + struct snapraid_state* state = io->state; + struct snapraid_parity_handle* parity_handle = worker->parity_handle; + unsigned level = parity_handle->level; + block_off_t blockcur = task->position; + unsigned char* buffer = task->buffer; + int ret; + + /* read the parity */ + ret = parity_read(parity_handle, blockcur, buffer, state->block_size, log_error); + if (ret == -1) { + if (errno == EIO) { + log_tag("parity_error:%u:%s: Read EIO error. %s\n", blockcur, lev_config_name(level), strerror(errno)); + log_error("Input/Output error in parity '%s' at position '%u'\n", lev_config_name(level), blockcur); + task->state = TASK_STATE_IOERROR_CONTINUE; + return; + } + + log_tag("parity_error:%u:%s: Read error. %s\n", blockcur, lev_config_name(level), strerror(errno)); + task->state = TASK_STATE_ERROR_CONTINUE; + return; + } + + task->state = TASK_STATE_DONE; +} + +static int state_dry_process(struct snapraid_state* state, struct snapraid_parity_handle* parity_handle, block_off_t blockstart, block_off_t blockmax) +{ + struct snapraid_io io; struct snapraid_handle* handle; unsigned diskmax; - block_off_t i; + block_off_t blockcur; unsigned j; - void* buffer_alloc; - unsigned char* buffer_aligned; + unsigned buffermax; int ret; data_off_t countsize; block_off_t countpos; block_off_t countmax; unsigned error; + unsigned io_error; unsigned l; handle = handle_map(state, &diskmax); - buffer_aligned = malloc_nofail_align(state->block_size, &buffer_alloc); + /* we need 1 * data + 2 * parity */ + buffermax = diskmax + 2 * state->level; + + /* initialize the io threads */ + io_init(&io, state, buffermax, dry_data_reader, handle, diskmax, dry_parity_reader, 0, parity_handle, state->level); error = 0; + io_error = 0; /* drop until now */ state_usage_waste(state); @@ -53,89 +197,129 @@ static int state_dry_process(struct snapraid_state* state, struct snapraid_parit countmax = blockmax - blockstart; countsize = 0; countpos = 0; + + /* start all the worker threads */ + io_start(&io, blockstart, blockmax, &block_is_enabled, 0); + state_progress_begin(state, blockstart, blockmax, countmax); - for (i = blockstart; i < blockmax; ++i) { + while (1) { + void** buffer; + + /* go to the next block */ + blockcur = io_read_next(&io, &buffer); + if (blockcur >= blockmax) + break; + /* for each disk, process the block */ for (j = 0; j < diskmax; ++j) { + struct snapraid_task* task; int read_size; struct snapraid_block* block; - struct snapraid_disk* disk = handle[j].disk; - struct snapraid_file* file; - block_off_t file_pos; - - if (!disk) { - /* if no disk, nothing to do */ - continue; - } - - block = fs_par2block_get(disk, i); - - if (!block_has_file(block)) { - /* if no file, nothing to do */ - continue; - } - - /* get the file of this block */ - file = fs_par2file_get(disk, i, &file_pos); + struct snapraid_disk* disk; + unsigned diskcur; /* until now is CPU */ state_usage_cpu(state); - /* if the file is closed or different than the current one */ - if (handle[j].file == 0 || handle[j].file != file) { - /* close the old one, if any */ - ret = handle_close(&handle[j]); - if (ret == -1) { - /* LCOV_EXCL_START */ - log_tag("error:%u:%s:%s: Close error. %s\n", i, disk->name, esc(handle[j].file->sub), strerror(errno)); - log_fatal("DANGER! Unexpected close error in a data disk, it isn't possible to dry.\n"); - log_fatal("Stopping at block %u\n", i); - ++error; - goto bail; - /* LCOV_EXCL_STOP */ - } + /* get the next task */ + task = io_data_read(&io, &diskcur); - /* open the file only for reading */ - ret = handle_open(&handle[j], file, state->file_mode, log_fatal, 0); - if (ret == -1) { - /* LCOV_EXCL_START */ - log_tag("error:%u:%s:%s: Open error. %s\n", i, disk->name, esc(file->sub), strerror(errno)); - log_fatal("DANGER! Unexpected open error in a data disk, it isn't possible to dry.\n"); - log_fatal("Stopping at block %u\n", i); - ++error; - goto bail; - /* LCOV_EXCL_STOP */ - } - } + /* get the task results */ + disk = task->disk; + block = task->block; + read_size = task->read_size; - /* read from the file */ - read_size = handle_read(&handle[j], file_pos, buffer_aligned, state->block_size, log_error, 0); - if (read_size == -1) { - log_tag("error:%u:%s:%s: Read error at position %u\n", i, disk->name, esc(file->sub), file_pos); - ++error; + /* if the disk position is not used */ + if (!disk) continue; - } /* until now is disk */ state_usage_disk(state, disk); + /* if the block is not used */ + if (!block_has_file(block)) + continue; + + /* handle error conditions */ + if (task->state == TASK_STATE_IOERROR) { + ++io_error; + goto bail; + } + if (task->state == TASK_STATE_ERROR) { + ++error; + goto bail; + } + if (task->state == TASK_STATE_ERROR_CONTINUE) { + ++error; + continue; + } + if (task->state == TASK_STATE_IOERROR_CONTINUE) { + ++io_error; + if (io_error >= state->opt.io_error_limit) { + /* LCOV_EXCL_START */ + log_fatal("DANGER! Too many input/output read error in a data disk, it isn't possible to scrub.\n"); + log_fatal("Ensure that disk '%s' is sane and that file '%s' can be accessed.\n", disk->dir, task->path); + log_fatal("Stopping at block %u\n", blockcur); + goto bail; + /* LCOV_EXCL_STOP */ + } + + /* otherwise continue */ + continue; + } + if (task->state != TASK_STATE_DONE) { + /* LCOV_EXCL_START */ + log_fatal("Internal inconsistency in task state\n"); + os_abort(); + /* LCOV_EXCL_STOP */ + } + countsize += read_size; } + /* until now is CPU */ + state_usage_cpu(state); + /* read the parity */ for (l = 0; l < state->level; ++l) { - if (parity[l]) { - /* until now is CPU */ - state_usage_cpu(state); + struct snapraid_task* task; + unsigned levcur; - ret = parity_read(parity[l], i, buffer_aligned, state->block_size, log_error); - if (ret == -1) { - log_tag("parity_error:%u:%s: Read error\n", i, lev_config_name(l)); - ++error; + task = io_parity_read(&io, &levcur); + + /* until now is parity */ + state_usage_parity(state, levcur); + + /* handle error conditions */ + if (task->state == TASK_STATE_IOERROR) { + ++io_error; + goto bail; + } + if (task->state == TASK_STATE_ERROR) { + ++error; + goto bail; + } + if (task->state == TASK_STATE_ERROR_CONTINUE) { + ++error; + continue; + } + if (task->state == TASK_STATE_IOERROR_CONTINUE) { + ++io_error; + if (io_error >= state->opt.io_error_limit) { + /* LCOV_EXCL_START */ + log_fatal("DANGER! Too many input/output read error in the %s disk, it isn't possible to scrub.\n", lev_name(levcur)); + log_fatal("Ensure that disk '%s' is sane and can be read.\n", lev_config_name(levcur)); + log_fatal("Stopping at block %u\n", blockcur); + goto bail; + /* LCOV_EXCL_STOP */ } - - /* until now is parity */ - state_usage_parity(state, l); + continue; + } + if (task->state != TASK_STATE_DONE) { + /* LCOV_EXCL_START */ + log_fatal("Internal inconsistency in task state\n"); + os_abort(); + /* LCOV_EXCL_STOP */ } } @@ -143,7 +327,7 @@ static int state_dry_process(struct snapraid_state* state, struct snapraid_parit ++countpos; /* progress */ - if (state_progress(state, i, countpos, countmax, countsize)) { + if (state_progress(state, blockcur, countpos, countmax, countsize)) { /* LCOV_EXCL_START */ break; /* LCOV_EXCL_STOP */ @@ -155,7 +339,9 @@ static int state_dry_process(struct snapraid_state* state, struct snapraid_parit state_usage_print(state); bail: - /* close all the files left open */ + /* stop all the worker threads */ + io_stop(&io); + for (j = 0; j < diskmax; ++j) { struct snapraid_file* file = handle[j].file; struct snapraid_disk* disk = handle[j].disk; @@ -170,20 +356,23 @@ bail: } } - if (error) { + if (error || io_error) { msg_status("\n"); - msg_status("%8u errors\n", error); + msg_status("%8u file errors\n", error); + msg_status("%8u io errors\n", io_error); } else { msg_status("Everything OK\n"); } if (error) log_fatal("DANGER! Unexpected errors!\n"); + if (io_error) + log_fatal("DANGER! Unexpected input/output errors!\n"); free(handle); - free(buffer_alloc); + io_done(&io); - if (error != 0) + if (error + io_error != 0) return -1; return 0; } @@ -192,10 +381,7 @@ void state_dry(struct snapraid_state* state, block_off_t blockstart, block_off_t { block_off_t blockmax; int ret; - struct snapraid_parity_handle parity[LEV_MAX]; - /* the following initialization is to avoid clang warnings about */ - /* potential state->level change, that never happens */ - struct snapraid_parity_handle* parity_ptr[LEV_MAX] = { 0 }; + struct snapraid_parity_handle parity_handle[LEV_MAX]; unsigned error; unsigned l; @@ -218,12 +404,12 @@ void state_dry(struct snapraid_state* state, block_off_t blockstart, block_off_t /* open the file for reading */ /* it may fail if the file doesn't exist, in this case we continue to dry the files */ for (l = 0; l < state->level; ++l) { - parity_ptr[l] = &parity[l]; - ret = parity_open(parity_ptr[l], state->parity[l].path, state->file_mode); + ret = parity_open(&parity_handle[l], l, state->parity[l].path, state->file_mode); if (ret == -1) { - msg_status("No accessible %s file.\n", lev_name(l)); - /* continue anyway */ - parity_ptr[l] = 0; + /* LCOV_EXCL_START */ + log_fatal("WARNING! Without an accessible %s file, it isn't possible to dry.\n", lev_name(l)); + exit(EXIT_FAILURE); + /* LCOV_EXCL_STOP */ } } @@ -231,7 +417,7 @@ void state_dry(struct snapraid_state* state, block_off_t blockstart, block_off_t /* skip degenerated cases of empty parity, or skipping all */ if (blockstart < blockmax) { - ret = state_dry_process(state, parity_ptr, blockstart, blockmax); + ret = state_dry_process(state, parity_handle, blockstart, blockmax); if (ret == -1) { /* LCOV_EXCL_START */ ++error; @@ -242,14 +428,12 @@ void state_dry(struct snapraid_state* state, block_off_t blockstart, block_off_t /* try to close only if opened */ for (l = 0; l < state->level; ++l) { - if (parity_ptr[l]) { - ret = parity_close(parity_ptr[l]); - if (ret == -1) { - /* LCOV_EXCL_START */ - ++error; - /* continue, as we are already exiting */ - /* LCOV_EXCL_STOP */ - } + ret = parity_close(&parity_handle[l]); + if (ret == -1) { + /* LCOV_EXCL_START */ + ++error; + /* continue, as we are already exiting */ + /* LCOV_EXCL_STOP */ } } diff --git a/cmdline/elem.c b/cmdline/elem.c index 2f8caf61..21187320 100644 --- a/cmdline/elem.c +++ b/cmdline/elem.c @@ -737,6 +737,8 @@ struct snapraid_disk* disk_alloc(const char* name, const char* dir, uint64_t dev /* ensure that the dir terminate with "/" if it isn't empty */ pathslash(disk->dir, sizeof(disk->dir)); + pthread_mutex_init(&disk->fs_mutex, 0); + disk->smartctl[0] = 0; disk->device = dev; disk->tick = 0; @@ -778,9 +780,22 @@ void disk_free(struct snapraid_disk* disk) tommy_hashdyn_done(&disk->linkset); tommy_list_foreach(&disk->dirlist, (tommy_foreach_func*)dir_free); tommy_hashdyn_done(&disk->dirset); + + pthread_mutex_destroy(&disk->fs_mutex); + free(disk); } +static inline void fs_lock(struct snapraid_disk* disk) +{ + pthread_mutex_lock(&disk->fs_mutex); +} + +static inline void fs_unlock(struct snapraid_disk* disk) +{ + pthread_mutex_unlock(&disk->fs_mutex); +} + struct chunk_disk_empty { block_off_t blockmax; }; @@ -788,7 +803,7 @@ struct chunk_disk_empty { /** * Compare the chunk if inside the specified blockmax. */ -static int chunk_disk_empty_compare(const void* void_a, const void* void_b) +static int chunk_disk_empty_compare_unlock(const void* void_a, const void* void_b) { const struct chunk_disk_empty* arg_a = void_a; const struct snapraid_chunk* arg_b = void_b; @@ -801,7 +816,7 @@ static int chunk_disk_empty_compare(const void* void_a, const void* void_b) return -1; } -int disk_is_empty(struct snapraid_disk* disk, block_off_t blockmax) +int fs_is_empty(struct snapraid_disk* disk, block_off_t blockmax) { struct chunk_disk_empty arg = { blockmax }; @@ -814,11 +829,16 @@ int disk_is_empty(struct snapraid_disk* disk, block_off_t blockmax) if (!tommy_list_empty(&disk->dirlist)) return 0; + fs_lock(disk); + /* search for any chunk inside blockmax */ - if (tommy_tree_search_compare(&disk->fs_parity, chunk_disk_empty_compare, &arg) != 0) + if (tommy_tree_search_compare(&disk->fs_parity, chunk_disk_empty_compare_unlock, &arg) != 0) { + fs_unlock(disk); return 0; + } /* finally, it's empty */ + fs_unlock(disk); return 1; } @@ -831,7 +851,7 @@ struct chunk_disk_size { * * The maximum parity position is stored as size. */ -static int chunk_disk_size_compare(const void* void_a, const void* void_b) +static int chunk_disk_size_compare_unlock(const void* void_a, const void* void_b) { struct chunk_disk_size* arg_a = (void*)void_a; const struct snapraid_chunk* arg_b = void_b; @@ -844,10 +864,16 @@ static int chunk_disk_size_compare(const void* void_a, const void* void_b) return 1; } -block_off_t disk_size(struct snapraid_disk* disk) +block_off_t fs_size(struct snapraid_disk* disk) { struct chunk_disk_size arg = { 0 }; - tommy_tree_search_compare(&disk->fs_parity, chunk_disk_size_compare, &arg); + + fs_lock(disk); + + tommy_tree_search_compare(&disk->fs_parity, chunk_disk_size_compare_unlock, &arg); + + fs_unlock(disk); + return arg.size; } @@ -856,7 +882,7 @@ struct chunk_check { int result; }; -void chunk_parity_check_foreach(void* void_arg, void* void_obj) +static void chunk_parity_check_foreach_unlock(void* void_arg, void* void_obj) { struct chunk_check* arg = void_arg; const struct snapraid_chunk* obj = void_obj; @@ -906,7 +932,7 @@ void chunk_parity_check_foreach(void* void_arg, void* void_obj) } } -void chunk_file_check_foreach(void* void_arg, void* void_obj) +static void chunk_file_check_foreach_unlock(void* void_arg, void* void_obj) { struct chunk_check* arg = void_arg; const struct snapraid_chunk* obj = void_obj; @@ -1022,13 +1048,17 @@ int fs_check(struct snapraid_disk* disk) /* error count starts from 0 */ arg.result = 0; + fs_lock(disk); + /* check parity sequence */ arg.prev = 0; - tommy_tree_foreach_arg(&disk->fs_parity, chunk_parity_check_foreach, &arg); + tommy_tree_foreach_arg(&disk->fs_parity, chunk_parity_check_foreach_unlock, &arg); /* check file sequence */ arg.prev = 0; - tommy_tree_foreach_arg(&disk->fs_file, chunk_file_check_foreach, &arg); + tommy_tree_foreach_arg(&disk->fs_file, chunk_file_check_foreach_unlock, &arg); + + fs_unlock(disk); if (arg.result != 0) return -1; @@ -1043,7 +1073,7 @@ struct chunk_parity_inside { /** * Compare the chunk if containing the specified parity position. */ -static int chunk_parity_inside_compare(const void* void_a, const void* void_b) +static int chunk_parity_inside_compare_unlock(const void* void_a, const void* void_b) { const struct chunk_parity_inside* arg_a = void_a; const struct snapraid_chunk* arg_b = void_b; @@ -1061,7 +1091,7 @@ static int chunk_parity_inside_compare(const void* void_a, const void* void_b) * The search is optimized for sequential accesses. * \return If not found return 0 */ -static struct snapraid_chunk* fs_par2chunk_get_ts(struct snapraid_disk* disk, struct snapraid_chunk** fs_last, block_off_t parity_pos) +static struct snapraid_chunk* fs_par2chunk_get_unlock(struct snapraid_disk* disk, struct snapraid_chunk** fs_last, block_off_t parity_pos) { struct snapraid_chunk* chunk; @@ -1073,7 +1103,7 @@ static struct snapraid_chunk* fs_par2chunk_get_ts(struct snapraid_disk* disk, st chunk = *fs_last; } else { struct chunk_parity_inside arg = { parity_pos }; - chunk = tommy_tree_search_compare(&disk->fs_parity, chunk_parity_inside_compare, &arg); + chunk = tommy_tree_search_compare(&disk->fs_parity, chunk_parity_inside_compare_unlock, &arg); } if (!chunk) @@ -1093,7 +1123,7 @@ struct chunk_file_inside { /** * Compare the chunk if containing the specified file position. */ -static int chunk_file_inside_compare(const void* void_a, const void* void_b) +static int chunk_file_inside_compare_unlock(const void* void_a, const void* void_b) { const struct chunk_file_inside* arg_a = void_a; const struct snapraid_chunk* arg_b = void_b; @@ -1116,7 +1146,7 @@ static int chunk_file_inside_compare(const void* void_a, const void* void_b) * The search is optimized for sequential accesses. * \return If not found return 0 */ -static struct snapraid_chunk* fs_file2chunk_get_ts(struct snapraid_disk* disk, struct snapraid_chunk** fs_last, struct snapraid_file* file, block_off_t file_pos) +static struct snapraid_chunk* fs_file2chunk_get_unlock(struct snapraid_disk* disk, struct snapraid_chunk** fs_last, struct snapraid_file* file, block_off_t file_pos) { struct snapraid_chunk* chunk; @@ -1129,7 +1159,7 @@ static struct snapraid_chunk* fs_file2chunk_get_ts(struct snapraid_disk* disk, s chunk = *fs_last; } else { struct chunk_file_inside arg = { file, file_pos }; - chunk = tommy_tree_search_compare(&disk->fs_file, chunk_file_inside_compare, &arg); + chunk = tommy_tree_search_compare(&disk->fs_file, chunk_file_inside_compare_unlock, &arg); } if (!chunk) @@ -1141,25 +1171,37 @@ static struct snapraid_chunk* fs_file2chunk_get_ts(struct snapraid_disk* disk, s return chunk; } -struct snapraid_file* fs_par2file_get_ts(struct snapraid_disk* disk, struct snapraid_chunk** fs_last, block_off_t parity_pos, block_off_t* file_pos) +struct snapraid_file* fs_par2file_get(struct snapraid_disk* disk, block_off_t parity_pos, block_off_t* file_pos) { struct snapraid_chunk* chunk; + struct snapraid_file* file; - chunk = fs_par2chunk_get_ts(disk, fs_last, parity_pos); - if (!chunk) + fs_lock(disk); + + chunk = fs_par2chunk_get_unlock(disk, &disk->fs_last, parity_pos); + + if (!chunk) { + fs_unlock(disk); return 0; + } if (file_pos) *file_pos = chunk->file_pos + (parity_pos - chunk->parity_pos); - return chunk->file; + file = chunk->file; + + fs_unlock(disk); + return file; } -block_off_t fs_file2par_get_ts(struct snapraid_disk* disk, struct snapraid_chunk** fs_last, struct snapraid_file* file, block_off_t file_pos) +block_off_t fs_file2par_get(struct snapraid_disk* disk, struct snapraid_file* file, block_off_t file_pos) { struct snapraid_chunk* chunk; + block_off_t ret; - chunk = fs_file2chunk_get_ts(disk, fs_last, file, file_pos); + fs_lock(disk); + + chunk = fs_file2chunk_get_unlock(disk, &disk->fs_last, file, file_pos); if (!chunk) { /* LCOV_EXCL_START */ log_fatal("Internal inconsistency when resolving file '%s' at position '%u/%u' in disk '%s'\n", file->sub, file_pos, file->blockmax, disk->name); @@ -1167,7 +1209,10 @@ block_off_t fs_file2par_get_ts(struct snapraid_disk* disk, struct snapraid_chunk /* LCOV_EXCL_STOP */ } - return chunk->parity_pos + (file_pos - chunk->file_pos); + ret = chunk->parity_pos + (file_pos - chunk->file_pos); + + fs_unlock(disk); + return ret; } void fs_allocate(struct snapraid_disk* disk, block_off_t parity_pos, struct snapraid_file* file, block_off_t file_pos) @@ -1176,9 +1221,11 @@ void fs_allocate(struct snapraid_disk* disk, block_off_t parity_pos, struct snap struct snapraid_chunk* parity_chunk; struct snapraid_chunk* file_chunk; + fs_lock(disk); + if (file_pos > 0) { /* search an existing chunk for the previous file_pos */ - chunk = fs_file2chunk_get_ts(disk, &disk->fs_last, file, file_pos - 1); + chunk = fs_file2chunk_get_unlock(disk, &disk->fs_last, file, file_pos - 1); if (chunk != 0 && parity_pos == chunk->parity_pos + chunk->count) { /* ensure that we are extending the chunk at the end */ @@ -1192,6 +1239,7 @@ void fs_allocate(struct snapraid_disk* disk, block_off_t parity_pos, struct snap /* extend the existing chunk */ ++chunk->count; + fs_unlock(disk); return; } } @@ -1212,6 +1260,8 @@ void fs_allocate(struct snapraid_disk* disk, block_off_t parity_pos, struct snap /* store the last accessed chunk */ disk->fs_last = chunk; + + fs_unlock(disk); } void fs_deallocate(struct snapraid_disk* disk, block_off_t parity_pos) @@ -1222,7 +1272,9 @@ void fs_deallocate(struct snapraid_disk* disk, block_off_t parity_pos) struct snapraid_chunk* file_chunk; block_off_t first_count, second_count; - chunk = fs_par2chunk_get_ts(disk, &disk->fs_last, parity_pos); + fs_lock(disk); + + chunk = fs_par2chunk_get_unlock(disk, &disk->fs_last, parity_pos); if (!chunk) { /* LCOV_EXCL_START */ log_fatal("Internal inconsistency when deallocating parity position '%u' for not existing chunk in disk '%s'\n", parity_pos, disk->name); @@ -1241,6 +1293,8 @@ void fs_deallocate(struct snapraid_disk* disk, block_off_t parity_pos) /* clear the last accessed chunk */ disk->fs_last = 0; + + fs_unlock(disk); return; } @@ -1249,12 +1303,16 @@ void fs_deallocate(struct snapraid_disk* disk, block_off_t parity_pos) ++chunk->parity_pos; ++chunk->file_pos; --chunk->count; + + fs_unlock(disk); return; } /* if it's at the end of the chunk, shrink the chunk */ if (parity_pos == chunk->parity_pos + chunk->count - 1) { --chunk->count; + + fs_unlock(disk); return; } @@ -1281,6 +1339,8 @@ void fs_deallocate(struct snapraid_disk* disk, block_off_t parity_pos) /* store the last accessed chunk */ disk->fs_last = second_chunk; + + fs_unlock(disk); } struct snapraid_block* fs_file2block_get(struct snapraid_file* file, block_off_t file_pos) @@ -1295,13 +1355,12 @@ struct snapraid_block* fs_file2block_get(struct snapraid_file* file, block_off_t return &file->blockvec[file_pos]; } -struct snapraid_block* fs_par2block_get_ts(struct snapraid_disk* disk, struct snapraid_chunk** fs_last, block_off_t parity_pos) +struct snapraid_block* fs_par2block_get(struct snapraid_disk* disk, block_off_t parity_pos) { struct snapraid_file* file; block_off_t file_pos; - file = fs_par2file_get_ts(disk, fs_last, parity_pos, &file_pos); - + file = fs_par2file_get(disk, parity_pos, &file_pos); if (!file) return BLOCK_EMPTY; diff --git a/cmdline/elem.h b/cmdline/elem.h index 9264ba9b..e1a11a2c 100644 --- a/cmdline/elem.h +++ b/cmdline/elem.h @@ -347,6 +347,17 @@ struct snapraid_disk { int mapping_idx; /**< Index in the mapping vector. Used only as buffer when writing the content file. */ int skip_access; /**< If the disk is inaccessible and it should be skipped. */ + /** + * Mutex for protecting the filesystem structure. + * + * Specifically, this protects ::fs_parity, ::fs_file, and ::fs_last, + * meaning that it protectes only chunks. + * + * Files, links and dirs are not protected as they are not expected to + * change during multithread processing. + */ + pthread_mutex_t fs_mutex; + /** * Mapping of chunks in the parity. * Sorted by and by @@ -878,7 +889,7 @@ void disk_free(struct snapraid_disk* disk); /** * Get the size of the disk in blocks. */ -block_off_t disk_size(struct snapraid_disk* disk); +block_off_t fs_size(struct snapraid_disk* disk); /** * Check if a disk is totally empty and can be discarded from the content file. @@ -886,7 +897,7 @@ block_off_t disk_size(struct snapraid_disk* disk); * and without any DELETED block. * The blockmax is used to limit the search of DELETED block up to blockmax. */ -int disk_is_empty(struct snapraid_disk* disk, block_off_t blockmax); +int fs_is_empty(struct snapraid_disk* disk, block_off_t blockmax); /** * Check the file-system for errors. @@ -919,49 +930,23 @@ void fs_deallocate(struct snapraid_disk* disk, block_off_t pos); */ struct snapraid_block* fs_file2block_get(struct snapraid_file* file, block_off_t file_pos); -/** - * Like fs_file2par_get() but thread-safe. - */ -struct snapraid_file* fs_par2file_get_ts(struct snapraid_disk* disk, struct snapraid_chunk** fs_last, block_off_t parity_pos, block_off_t* file_pos); - /** * Get the file position from the parity position. * Return 0 if no file is using it. - * \note This function is NOT thread-safe as it uses the the disk cache. */ -static inline struct snapraid_file* fs_par2file_get(struct snapraid_disk* disk, block_off_t parity_pos, block_off_t* file_pos) -{ - return fs_par2file_get_ts(disk, &disk->fs_last, parity_pos, file_pos); -} - -/** - * Like fs_file2par_get() but thread-safe. - */ -block_off_t fs_file2par_get_ts(struct snapraid_disk* disk, struct snapraid_chunk** fs_last, struct snapraid_file* file, block_off_t file_pos); +struct snapraid_file* fs_par2file_get(struct snapraid_disk* disk, block_off_t parity_pos, block_off_t* file_pos); /** * Get the parity position from the file position. - * \note This function is NOT thread-safe as it uses the the disk cache. */ -static inline block_off_t fs_file2par_get(struct snapraid_disk* disk, struct snapraid_file* file, block_off_t file_pos) -{ - return fs_file2par_get_ts(disk, &disk->fs_last, file, file_pos); -} - -/** - * Like fs_par2block_get() but thread-safe. - */ -struct snapraid_block* fs_par2block_get_ts(struct snapraid_disk* disk, struct snapraid_chunk** fs_last, block_off_t parity_pos); +block_off_t fs_file2par_get(struct snapraid_disk* disk, struct snapraid_file* file, block_off_t file_pos); /** * Get the block from the parity position. * Return BLOCK_EMPTY==0 if the block is over the end of the disk or not used. * \note This function is NOT thread-safe as it uses the the disk cache. */ -static inline struct snapraid_block* fs_par2block_get(struct snapraid_disk* disk, block_off_t parity_pos) -{ - return fs_par2block_get_ts(disk, &disk->fs_last, parity_pos); -} +struct snapraid_block* fs_par2block_get(struct snapraid_disk* disk, block_off_t parity_pos); /** * Allocate a disk mapping. diff --git a/cmdline/handle.c b/cmdline/handle.c index 531ba1e7..69e9f386 100644 --- a/cmdline/handle.c +++ b/cmdline/handle.c @@ -272,24 +272,9 @@ int handle_read(struct snapraid_handle* handle, block_off_t file_pos, unsigned c read_size = file_block_size(handle->file, file_pos, block_size); -#if !HAVE_PREAD - if (lseek(handle->f, offset, SEEK_SET) != offset) { - /* LCOV_EXCL_START */ - out("Error seeking file '%s' at offset %" PRIu64 ". %s.\n", handle->path, offset, strerror(errno)); - return -1; - /* LCOV_EXCL_STOP */ - } -#endif - count = 0; do { - -#if HAVE_PREAD read_ret = pread(handle->f, block_buffer + count, read_size - count, offset + count); -#else - read_ret = read(handle->f, block_buffer + count, read_size - count); -#endif - if (read_ret < 0) { /* LCOV_EXCL_START */ out("Error reading file '%s' at offset %" PRIu64 " for size %u. %s.\n", handle->path, offset + count, read_size - count, strerror(errno)); @@ -333,18 +318,7 @@ int handle_write(struct snapraid_handle* handle, block_off_t file_pos, unsigned write_size = file_block_size(handle->file, file_pos, block_size); -#if HAVE_PWRITE write_ret = pwrite(handle->f, block_buffer, write_size, offset); -#else - if (lseek(handle->f, offset, SEEK_SET) != offset) { - /* LCOV_EXCL_START */ - log_fatal("Error seeking file '%s'. %s.\n", handle->path, strerror(errno)); - return -1; - /* LCOV_EXCL_STOP */ - } - - write_ret = write(handle->f, block_buffer, write_size); -#endif if (write_ret != (ssize_t)write_size) { /* conversion is safe because block_size is always small */ /* LCOV_EXCL_START */ log_fatal("Error writing file '%s'. %s.\n", handle->path, strerror(errno)); diff --git a/cmdline/import.c b/cmdline/import.c index 0741dd63..c878fb84 100644 --- a/cmdline/import.c +++ b/cmdline/import.c @@ -185,14 +185,7 @@ int state_import_fetch(struct snapraid_state* state, int rehash, struct snapraid /* LCOV_EXCL_STOP */ } - if (lseek(f, block->offset, SEEK_SET) != block->offset) { - /* LCOV_EXCL_START */ - log_fatal("Error seeking file '%s'. %s.\n", path, strerror(errno)); - exit(EXIT_FAILURE); - /* LCOV_EXCL_STOP */ - } - - ret = read(f, buffer, read_size); + ret = pread(f, buffer, read_size, block->offset); if (ret < 0 || (unsigned)ret != read_size) { /* LCOV_EXCL_START */ log_fatal("Error reading file '%s'. %s.\n", path, strerror(errno)); diff --git a/cmdline/io.c b/cmdline/io.c new file mode 100644 index 00000000..afa974e9 --- /dev/null +++ b/cmdline/io.c @@ -0,0 +1,782 @@ +/* + * Copyright (C) 2016 Andrea Mazzoleni + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "portable.h" + +#include "io.h" + +/** + * Implementation note about conditional variables. + * + * In release code the conditional variables are signaled outside the mutex, + * to reduce the number of context switches. + * + * Instead, when testing with helgrind and drd, they are signaled inside the mutex + * to help such tools to see the dependency between the signal and the wait. + * We use the CHECKER define to detect such case. + * + * Here some interesting discussion: + * + * Condvars: signal with mutex locked or not? + * http://www.domaigne.com/blog/computing/condvars-signal-with-mutex-locked-or-not/ + * + * Calling pthread_cond_signal without locking mutex + * http://stackoverflow.com/questions/4544234/calling-pthread-cond-signal-without-locking-mutex/4544494#4544494 + */ + +void io_init(struct snapraid_io* io, struct snapraid_state* state, unsigned buffer_max, + void (*data_reader)(struct snapraid_worker*, struct snapraid_task*), + struct snapraid_handle* handle_map, unsigned handle_max, + void (*parity_reader)(struct snapraid_worker*, struct snapraid_task*), + void (*parity_writer)(struct snapraid_worker*, struct snapraid_task*), + struct snapraid_parity_handle* parity_handle_map, unsigned parity_handle_max) +{ + unsigned i; + size_t allocated; + + pthread_mutex_init(&io->mutex, 0); + pthread_cond_init(&io->read_done, 0); + pthread_cond_init(&io->read_sched, 0); + pthread_cond_init(&io->write_done, 0); + pthread_cond_init(&io->write_sched, 0); + + io->state = state; + + io->buffer_max = buffer_max; + allocated = 0; + for (i = 0; i < IO_MAX; ++i) { + io->buffer_map[i] = malloc_nofail_vector_align(handle_max, buffer_max, state->block_size, &io->buffer_alloc_map[i]); + if (!state->opt.skip_self) + mtest_vector(io->buffer_max, state->block_size, io->buffer_map[i]); + allocated += state->block_size * buffer_max; + } + + msg_progress("Using %u MiB of memory for the MT buffers.\n", (unsigned)(allocated / (1024 * 1024))); + + if (parity_writer) { + io->reader_max = handle_max; + io->writer_max = parity_handle_max; + } else { + io->reader_max = handle_max + parity_handle_max; + io->writer_max = 0; + } + + io->reader_map = malloc_nofail(sizeof(struct snapraid_worker) * io->reader_max); + io->reader_list = malloc_nofail(io->reader_max + 1); + io->writer_map = malloc_nofail(sizeof(struct snapraid_worker) * io->writer_max); + io->writer_list = malloc_nofail(io->writer_max + 1); + + io->data_base = 0; + io->data_count = handle_max; + io->parity_base = handle_max; + io->parity_count = parity_handle_max; + + for (i = 0; i < io->reader_max; ++i) { + struct snapraid_worker* worker = &io->reader_map[i]; + + worker->io = io; + + if (i < handle_max) { + /* it's a data read */ + worker->handle = &handle_map[i]; + worker->parity_handle = 0; + worker->func = data_reader; + + /* data read is put in lower buffer index */ + worker->buffer_skew = 0; + } else { + /* it's a parity read */ + worker->handle = 0; + worker->parity_handle = &parity_handle_map[i - handle_max]; + worker->func = parity_reader; + + /* parity read is put after data and computed parity */ + worker->buffer_skew = parity_handle_max; + } + } + + for (i = 0; i < io->writer_max; ++i) { + struct snapraid_worker* worker = &io->writer_map[i]; + + worker->io = io; + + /* it's a parity write */ + worker->handle = 0; + worker->parity_handle = &parity_handle_map[i]; + worker->func = parity_writer; + + /* parity to write is put after data */ + worker->buffer_skew = handle_max; + } +} + +void io_done(struct snapraid_io* io) +{ + unsigned i; + + for (i = 0; i < IO_MAX; ++i) { + free(io->buffer_map[i]); + free(io->buffer_alloc_map[i]); + } + + free(io->reader_map); + free(io->reader_list); + free(io->writer_map); + free(io->writer_list); + + pthread_mutex_destroy(&io->mutex); + pthread_cond_destroy(&io->read_done); + pthread_cond_destroy(&io->read_sched); + pthread_cond_destroy(&io->write_done); + pthread_cond_destroy(&io->write_sched); +} + +/** + * Get the next block position to process. + */ +static block_off_t io_position_next(struct snapraid_io* io) +{ + block_off_t blockcur; + + /* get the next position */ + while (io->block_next < io->block_max && !io->block_is_enabled(io->block_arg, io->block_next)) + ++io->block_next; + + blockcur = io->block_next; + + /* next block for the next call */ + ++io->block_next; + + return blockcur; +} + +/** + * Setup the next pending task for all readers. + */ +static void io_reader_sched(struct snapraid_io* io, int index, block_off_t blockcur) +{ + unsigned i; + + for (i = 0; i < io->reader_max; ++i) { + struct snapraid_worker* worker = &io->reader_map[i]; + struct snapraid_task* task = &worker->task_map[index]; + + /* setup the new pending task */ + if (blockcur < io->block_max) + task->state = TASK_STATE_READY; + else + task->state = TASK_STATE_EMPTY; + + task->path[0] = 0; + if (worker->handle) + task->disk = worker->handle->disk; + else + task->disk = 0; + task->buffer = io->buffer_map[index][worker->buffer_skew + i]; + task->position = blockcur; + task->block = 0; + task->file = 0; + task->file_pos = 0; + task->read_size = 0; + task->is_timestamp_different = 0; + } +} + +/** + * Setup the next pending task for all writers. + */ +static void io_writer_sched(struct snapraid_io* io, int index, block_off_t blockcur) +{ + unsigned i; + + for (i = 0; i < io->writer_max; ++i) { + struct snapraid_worker* worker = &io->writer_map[i]; + struct snapraid_task* task = &worker->task_map[index]; + + /* setup the new pending task */ + task->state = TASK_STATE_READY; + task->path[0] = 0; + task->disk = 0; + task->buffer = io->buffer_map[index][worker->buffer_skew + i]; + task->position = blockcur; + task->block = 0; + task->file = 0; + task->file_pos = 0; + task->read_size = 0; + task->is_timestamp_different = 0; + } +} + +/** + * Setup an empty next pending task for all writers. + */ +static void io_writer_sched_empty(struct snapraid_io* io, int index, block_off_t blockcur) +{ + unsigned i; + + for (i = 0; i < io->writer_max; ++i) { + struct snapraid_worker* worker = &io->writer_map[i]; + struct snapraid_task* task = &worker->task_map[index]; + + /* setup the new pending task */ + task->state = TASK_STATE_EMPTY; + task->path[0] = 0; + task->disk = 0; + task->buffer = 0; + task->position = blockcur; + task->block = 0; + task->file = 0; + task->file_pos = 0; + task->read_size = 0; + task->is_timestamp_different = 0; + } +} + +/** + * Get the next task to work on for a reader. + * + * This is the synchronization point for workers with the io. + */ +static struct snapraid_task* io_reader_step(struct snapraid_worker* worker) +{ + struct snapraid_io* io = worker->io; + + /* the synchronization is protected by the io mutex */ + pthread_mutex_lock(&io->mutex); + + while (1) { + unsigned next_index; + + /* check if the worker has to exit */ + /* even if there is work to do */ + if (io->done) { + pthread_mutex_unlock(&io->mutex); + return 0; + } + + /* get the next pending task */ + next_index = (worker->index + 1) % IO_MAX; + + /* if the queue of pending tasks is not empty */ + if (next_index != io->reader_index) { + struct snapraid_task* task; + + /* the index that the IO may be waiting for */ + unsigned waiting_index = io->reader_index; + + /* the index that worker just completed */ + unsigned done_index = worker->index; + + /* get the new working task */ + worker->index = next_index; + task = &worker->task_map[worker->index]; + +#ifndef CHECKER + /* without the thread checker unlock before signaling, */ + /* this reduces the number of context switches */ + pthread_mutex_unlock(&io->mutex); +#endif + + /* if the just completed task is at this index */ + if (done_index == waiting_index) + /* notify the IO that a new read is complete */ + pthread_cond_signal(&io->read_done); + +#ifdef CHECKER + pthread_mutex_unlock(&io->mutex); +#endif + + /* return the new task */ + return task; + } + + /* otherwise wait for a read_sched event */ + pthread_cond_wait(&io->read_sched, &io->mutex); + } +} + +/** + * Get the next task to work on for a writer. + * + * This is the synchronization point for workers with the io. + */ +static struct snapraid_task* io_writer_step(struct snapraid_worker* worker, int state) +{ + struct snapraid_io* io = worker->io; + int error_index; + + /* the synchronization is protected by the io mutex */ + pthread_mutex_lock(&io->mutex); + + /* counts the number of errors in the global state */ + error_index = state - IO_WRITER_ERROR_BASE; + if (error_index >= 0 && error_index < IO_WRITER_ERROR_MAX) + ++io->writer_error[error_index]; + + while (1) { + unsigned next_index; + + /* get the next pending task */ + next_index = (worker->index + 1) % IO_MAX; + + /* if the queue of pending tasks is not empty */ + if (next_index != io->writer_index) { + struct snapraid_task* task; + + /* the index that the IO may be waiting for */ + unsigned waiting_index = (io->writer_index + 1) % IO_MAX; + + /* the index that worker just completed */ + unsigned done_index = worker->index; + + /* get the new working task */ + worker->index = next_index; + task = &worker->task_map[worker->index]; + +#ifndef CHECKER + /* without the thread checker unlock before signaling, */ + /* this reduces the number of context switches */ + pthread_mutex_unlock(&io->mutex); +#endif + + /* if the just completed task is at this index */ + if (done_index == waiting_index) + /* notify the IO that a new write is complete */ + pthread_cond_signal(&io->write_done); + +#ifdef CHECKER + pthread_mutex_unlock(&io->mutex); +#endif + + /* return the new task */ + return task; + } + + /* check if the worker has to exit */ + /* but only if there is no work to do */ + if (io->done) { + pthread_mutex_unlock(&io->mutex); + return 0; + } + + /* otherwise wait for a write_sched event */ + pthread_cond_wait(&io->write_sched, &io->mutex); + } +} + +/** + * Get the next block position to operate on. + * + * This is the synchronization point for workers with the io. + */ +block_off_t io_read_next(struct snapraid_io* io, void*** buffer) +{ + block_off_t blockcur_schedule; + block_off_t blockcur_caller; + unsigned i; + + /* get the next parity position to process */ + blockcur_schedule = io_position_next(io); + + /* ensure that all data/parity was read */ + assert(io->reader_list[0] == io->reader_max); + + /* setup the list of workers to process */ + for (i = 0; i <= io->reader_max; ++i) + io->reader_list[i] = i; + + /* the synchronization is protected by the io mutex */ + pthread_mutex_lock(&io->mutex); + + /* schedule the next read */ + io_reader_sched(io, io->reader_index, blockcur_schedule); + + /* set the index for the tasks to return to the caller */ + io->reader_index = (io->reader_index + 1) % IO_MAX; + + /* get the position to operate at high level from one task */ + blockcur_caller = io->reader_map[0].task_map[io->reader_index].position; + + /* set the buffer to use */ + *buffer = io->buffer_map[io->reader_index]; + +#ifndef CHECKER + /* without the thread checker unlock before signaling, */ + /* this reduces the number of context switches */ + pthread_mutex_unlock(&io->mutex); +#endif + + /* signal all the workers that there is a new pending task */ + pthread_cond_broadcast(&io->read_sched); + +#ifdef CHECKER + pthread_mutex_unlock(&io->mutex); +#endif + + return blockcur_caller; +} + +void io_write_next(struct snapraid_io* io, unsigned blockcur, int skip, int* writer_error) +{ + unsigned i; + + /* ensure that all parity was written */ + assert(io->writer_list[0] == io->writer_max); + + /* setup the list of workers to process */ + for (i = 0; i <= io->writer_max; ++i) + io->writer_list[i] = i; + + /* the synchronization is protected by the io mutex */ + pthread_mutex_lock(&io->mutex); + + /* report errors */ + for (i = 0; i < IO_WRITER_ERROR_MAX; ++i) { + writer_error[i] = io->writer_error[i]; + io->writer_error[i] = 0; + } + + if (skip) { + /* skip the next write */ + io_writer_sched_empty(io, io->writer_index, blockcur); + } else { + /* schedule the next write */ + io_writer_sched(io, io->writer_index, blockcur); + } + + /* at this point the writers must be in sync with the readers */ + assert(io->writer_index == io->reader_index); + + /* set the index to be used for the next write */ + io->writer_index = (io->writer_index + 1) % IO_MAX; + +#ifndef CHECKER + /* without the thread checker unlock before signaling, */ + /* this reduces the number of context switches */ + pthread_mutex_unlock(&io->mutex); +#endif + + /* signal all the workers that there is a new pending task */ + pthread_cond_broadcast(&io->write_sched); + +#ifdef CHECKER + pthread_mutex_unlock(&io->mutex); +#endif +} + +static struct snapraid_task* io_task_read(struct snapraid_io* io, unsigned base, unsigned count, unsigned* pos) +{ + /* the synchronization is protected by the io mutex */ + pthread_mutex_lock(&io->mutex); + + while (1) { + unsigned char* let; + unsigned busy_index; + + /* get the index the IO is using */ + /* we must ensure that this index has not a read in progress */ + /* to avoid a concurrent access */ + busy_index = io->reader_index; + + /* search for a worker that has already finished */ + let = &io->reader_list[0]; + while (1) { + unsigned i = *let; + + /* if we are at the end */ + if (i == io->reader_max) + break; + + /* if it's in range */ + if (base <= i && i < base + count) { + struct snapraid_worker* worker; + + worker = &io->reader_map[i]; + + /* if the worker has finished this index */ + if (busy_index != worker->index) { + struct snapraid_task* task; + + task = &worker->task_map[io->reader_index]; + + pthread_mutex_unlock(&io->mutex); + + /* mark the worker as processed */ + /* setting the previous one to point at the next one */ + *let = io->reader_list[i + 1]; + + /* return the position */ + *pos = i - base; + + return task; + } + } + + /* next position to check */ + let = &io->reader_list[i + 1]; + } + + /* if not worker is ready, wait for an event */ + pthread_cond_wait(&io->read_done, &io->mutex); + } +} + +struct snapraid_task* io_data_read(struct snapraid_io* io, unsigned* pos) +{ + return io_task_read(io, io->data_base, io->data_count, pos); +} + +struct snapraid_task* io_parity_read(struct snapraid_io* io, unsigned* pos) +{ + return io_task_read(io, io->parity_base, io->parity_count, pos); +} + +void io_parity_write(struct snapraid_io* io, unsigned* pos) +{ + /* the synchronization is protected by the io mutex */ + pthread_mutex_lock(&io->mutex); + + while (1) { + unsigned char* let; + unsigned busy_index; + + /* get the next index the IO is going to use */ + /* we must ensure that this index has not a write in progress */ + /* to avoid a concurrent access */ + /* note that we are already sure that a write is not in progress */ + /* at the index the IO is using at now */ + busy_index = (io->writer_index + 1) % IO_MAX; + + /* search for a worker that has already finished */ + let = &io->writer_list[0]; + while (1) { + unsigned i = *let; + struct snapraid_worker* worker; + + /* if we are at the end */ + if (i == io->writer_max) + break; + + worker = &io->writer_map[i]; + + /* the two indexes cannot be equal */ + assert(io->writer_index != worker->index); + + /* if the worker has finished this index */ + if (busy_index != worker->index) { + pthread_mutex_unlock(&io->mutex); + + /* mark the worker as processed */ + /* setting the previous one to point at the next one */ + *let = io->writer_list[i + 1]; + + /* return the position */ + *pos = i; + + return; + } + + /* next position to check */ + let = &io->writer_list[i + 1]; + } + + /* if not worker is ready, wait for an event */ + pthread_cond_wait(&io->write_done, &io->mutex); + } +} + +static void io_reader_worker(struct snapraid_worker* worker, struct snapraid_task* task) +{ + /* if we reached the end */ + if (task->position >= worker->io->block_max) { + /* complete a dummy task */ + task->state = TASK_STATE_EMPTY; + } else { + worker->func(worker, task); + } +} + +static void* io_reader_thread(void* arg) +{ + struct snapraid_worker* worker = arg; + + /* force completion of the first task */ + io_reader_worker(worker, &worker->task_map[0]); + + while (1) { + struct snapraid_task* task; + + /* get the new task */ + task = io_reader_step(worker); + + /* if no task, it means to exit */ + if (!task) + break; + + /* nothing more to do */ + if (task->state == TASK_STATE_EMPTY) + continue; + + assert(task->state == TASK_STATE_READY); + + /* work on the assigned task */ + io_reader_worker(worker, task); + } + + return 0; +} + + +static void* io_writer_thread(void* arg) +{ + struct snapraid_worker* worker = arg; + int latest_state = TASK_STATE_DONE; + + while (1) { + struct snapraid_task* task; + + /* get the new task */ + task = io_writer_step(worker, latest_state); + + /* if no task, it means to exit */ + if (!task) + break; + + /* nothing more to do */ + if (task->state == TASK_STATE_EMPTY) { + latest_state = TASK_STATE_DONE; + continue; + } + + assert(task->state == TASK_STATE_READY); + + /* work on the assigned task */ + worker->func(worker, task); + + /* save the resulting state */ + latest_state = task->state; + } + + return 0; +} + +void io_start(struct snapraid_io* io, + block_off_t blockstart, block_off_t blockmax, + int (*block_is_enabled)(void* arg, block_off_t), void* blockarg) +{ + unsigned i; + + io->block_start = blockstart; + io->block_max = blockmax; + io->block_is_enabled = block_is_enabled; + io->block_arg = blockarg; + io->block_next = blockstart; + + io->done = 0; + io->reader_index = IO_MAX - 1; + io->writer_index = 0; + + /* clear writer errors */ + for (i = 0; i < IO_WRITER_ERROR_MAX; ++i) + io->writer_error[i] = 0; + + /* setup the initial read pending tasks, except the latest one, */ + /* the latest will be initialized at the fist io_read_next() call */ + for (i = 0; i < IO_MAX - 1; ++i) { + block_off_t blockcur = io_position_next(io); + + io_reader_sched(io, i, blockcur); + } + + /* setup the lists of workers to process */ + io->reader_list[0] = io->reader_max; + for (i = 0; i <= io->writer_max; ++i) + io->writer_list[i] = i; + + /* start the reader threads */ + for (i = 0; i < io->reader_max; ++i) { + struct snapraid_worker* worker = &io->reader_map[i]; + + worker->index = 0; + + if (pthread_create(&worker->thread, 0, io_reader_thread, worker) != 0) { + /* LCOV_EXCL_START */ + log_fatal("Failed to create reader thread.\n"); + exit(EXIT_FAILURE); + /* LCOV_EXCL_STOP */ + } + } + + /* start the writer threads */ + for (i = 0; i < io->writer_max; ++i) { + struct snapraid_worker* worker = &io->writer_map[i]; + + worker->index = IO_MAX - 1; + + if (pthread_create(&worker->thread, 0, io_writer_thread, worker) != 0) { + /* LCOV_EXCL_START */ + log_fatal("Failed to create writer thread.\n"); + exit(EXIT_FAILURE); + /* LCOV_EXCL_STOP */ + } + } +} + +void io_stop(struct snapraid_io* io) +{ + unsigned i; + + pthread_mutex_lock(&io->mutex); + + /* mark that we are stopping */ + io->done = 1; + + /* signal all the threads to recognize the new state */ + pthread_cond_broadcast(&io->read_sched); + pthread_cond_broadcast(&io->write_sched); + + pthread_mutex_unlock(&io->mutex); + + /* wait for all readers to terminate */ + for (i = 0; i < io->reader_max; ++i) { + struct snapraid_worker* worker = &io->reader_map[i]; + void* retval; + + /* wait for thread termination */ + if (pthread_join(worker->thread, &retval) != 0) { + /* LCOV_EXCL_START */ + log_fatal("Failed to join reader thread.\n"); + exit(EXIT_FAILURE); + /* LCOV_EXCL_STOP */ + } + } + + /* wait for all writers to terminate */ + for (i = 0; i < io->writer_max; ++i) { + struct snapraid_worker* worker = &io->writer_map[i]; + void* retval; + + /* wait for thread termination */ + if (pthread_join(worker->thread, &retval) != 0) { + /* LCOV_EXCL_START */ + log_fatal("Failed to join writer thread.\n"); + exit(EXIT_FAILURE); + /* LCOV_EXCL_STOP */ + } + } +} + diff --git a/cmdline/io.h b/cmdline/io.h new file mode 100644 index 00000000..4134631f --- /dev/null +++ b/cmdline/io.h @@ -0,0 +1,362 @@ +/* + * Copyright (C) 2016 Andrea Mazzoleni + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __IO_H +#define __IO_H + +#include "state.h" +#include "support.h" +#include "handle.h" +#include "parity.h" + +/** + * Number of read-ahead buffers. + * + * More buffers always result in better performance. + * + * This is the scrub performance on my machine with different buffers: + * + * 1 - 380 MiB/s, CPU 26%, speed 100% [SnapRAID 9.2] + * 2 - 426 MiB/s, CPU 46%, speed 112% + * 4 - 452 MiB/s, CPU 54%, speed 118% + * 8 - 487 MiB/s, CPU 60%, speed 128% + * 16 - 505 MiB/s, CPU 63%, speed 132% + * 32 - 520 MiB/s, CPU 64%, speed 136% + * 64 - 524 MiB/s, CPU 65%, speed 137% + * 128 - 525 MiB/s, CPU 66%, speed 138% + */ +#define IO_MAX 32 + +/** + * State of the task. + */ +#define TASK_STATE_IOERROR_CONTINUE -4 /**< IO error. Continuation requested. */ +#define TASK_STATE_ERROR_CONTINUE -3 /**< Generic error. Continuation requested. */ +#define TASK_STATE_IOERROR -2 /**< IO error. Failure requested. */ +#define TASK_STATE_ERROR -1 /**< Generic error. Failure requested. */ +#define TASK_STATE_EMPTY 0 /**< Nothing to do. */ +#define TASK_STATE_READY 1 /**< Ready to start. */ +#define TASK_STATE_DONE 2 /**< Task completed. */ + +/** + * Task of work. + * + * This represents the minimal element of work that worker threads are + * going to be asked to do. + * + * It consists in reading a block of data from a disk. + * + * Note that the disk to use is defined implicitly in the worker thread. + */ +struct snapraid_task { + int state; /**< State of the task. One of the TASK_STATE_*. */ + char path[PATH_MAX]; /**< Path of the file. */ + struct snapraid_disk* disk; /**< Disk of the file. */ + unsigned char* buffer; /**< Where to read the data. */ + block_off_t position; /**< Parity position to read. */ + + /** + * Result of the task. + */ + struct snapraid_block* block; + struct snapraid_file* file; + block_off_t file_pos; + int read_size; /**< Size of the data read. */ + int is_timestamp_different; /**< Report if file has a changed timestamp. */ +}; + +/** + * Worker for tasks. + * + * This represents a worker thread designated to read data + * from a specific disk. + */ +struct snapraid_worker { + pthread_t thread; + + struct snapraid_io* io; /**< Parent pointer. */ + + void (*func)(struct snapraid_worker*, struct snapraid_task*); + + /** + * Handle to data or parity. + * + * Only one of the two is valid, the other is 0. + */ + struct snapraid_handle* handle; /**< Handle at the file on the disk. */ + struct snapraid_parity_handle* parity_handle; /**< Handle at the parity on the disk. */ + + /** + * Vector of tasks. + * + * It's a ring of tasks reused cycle after cycle. + */ + struct snapraid_task task_map[IO_MAX]; + + /** + * The task in progress by the worker thread. + * + * It's an index inside in the ::task_map vector. + */ + unsigned index; + + /** + * Which buffer base index should be used for destination. + */ + unsigned buffer_skew; +}; + +/** + * Number of error kind for writers. + */ +#define IO_WRITER_ERROR_BASE TASK_STATE_IOERROR_CONTINUE +#define IO_WRITER_ERROR_MAX (-IO_WRITER_ERROR_BASE) + +/** + * Reader. + * + * This represents the pool of worker threads dedicated to read + * data from the disks. + */ +struct snapraid_io { + /** + * Mutex used to protect the synchronization + * between the io and the workers. + */ + pthread_mutex_t mutex; + + /** + * Condition for a new read is completed. + * + * The workers signal this condition when a new read is completed. + * The IO waits on this condition when it's waiting for + * a new read to be completed. + */ + pthread_cond_t read_done; + + /** + * Condition for a new read scheduled. + * + * The workers wait on this condition when they are waiting for a new + * read to process. + * The IO signals this condition when new reads are scheduled. + */ + pthread_cond_t read_sched; + + /** + * Condition for a new write is completed. + * + * The workers signal this condition when a new write is completed. + * The IO waits on this condition when it's waiting for + * a new write to be completed. + */ + pthread_cond_t write_done; + + /** + * Condition for a new write scheduled. + * + * The workers wait on this condition when they are waiting for a new + * write to process. + * The IO signals this condition when new writes are scheduled. + */ + pthread_cond_t write_sched; + + struct snapraid_state* state; + + /** + * Base position for workers. + * + * It's the index in the ::worker_map[]. + */ + unsigned data_base; + unsigned data_count; + unsigned parity_base; + unsigned parity_count; + + /** + * Callbacks for workers. + */ + void (*data_reader)(struct snapraid_worker*, struct snapraid_task*); + void (*parity_reader)(struct snapraid_worker*, struct snapraid_task*); + void (*parity_writer)(struct snapraid_worker*, struct snapraid_task*); + + /** + * Blocks mapping. + * + * This info is used to obtain the sequence of block + * positions to process. + */ + block_off_t block_start; + block_off_t block_max; + block_off_t block_next; + int (*block_is_enabled)(void* arg, block_off_t); + void* block_arg; + + /** + * Buffers for data. + * + * A pool of buffers used to store the data read. + */ + unsigned buffer_max; /**< Number of buffers. */ + void* buffer_alloc_map[IO_MAX]; /**< Allocation map for buffers. */ + void** buffer_map[IO_MAX]; /**< Buffers for data. */ + + /** + * Workers. + * + * A vector of readers, each one representing a different thread. + */ + unsigned reader_max; /**< Number of workers. */ + struct snapraid_worker* reader_map; /**< Vector of workers. */ + unsigned writer_max; /**< Number of workers. */ + struct snapraid_worker* writer_map; /**< Vector of workers. */ + + /** + * List of not yet processed workers. + * + * The list has ::reader_max + 1 elements. Each element contains + * the number of the reader to process. + * + * At initialization the list is filled with [0..reader_max]. + * To get the next element to process we use i = list[i + 1]. + * The end is when i == reader_max. + */ + unsigned char* reader_list; + unsigned char* writer_list; + + /** + * Exit condition for all threads. + */ + int done; + + /** + * The task currently used by the caller. + * + * It's a rolling counter, when reaching IO_MAX + * it goes again to 0. + * + * When the caller finish with the current index, + * it's incremented, and a read_sched() signal is sent. + */ + unsigned reader_index; + + /** + * The task currently used by the caller. + * + * It's a rolling counter, when reaching IO_MAX + * it goes again to 0. + * + * When the caller finish with the current index, + * it's incremented, and a write_sched() signal is sent. + */ + unsigned writer_index; + + /** + * Counts the error happening in the writers. + */ + int writer_error[IO_WRITER_ERROR_MAX]; +}; + +/** + * Initialize the InputOutput workers. + * + * \param buffer_max The number of data/parity buffers to allocate. + */ +void io_init(struct snapraid_io* io, struct snapraid_state* state, + unsigned buffer_max, + void (*data_reader)(struct snapraid_worker*, struct snapraid_task*), + struct snapraid_handle* handle_map, unsigned handle_max, + void (*parity_reader)(struct snapraid_worker*, struct snapraid_task*), + void (*parity_writer)(struct snapraid_worker*, struct snapraid_task*), + struct snapraid_parity_handle* parity_handle_map, unsigned parity_handle_max); + +/** + * Deinitialize te InputOutput workers. + */ +void io_done(struct snapraid_io* io); + +/** + * Start all the worker threads. + */ +void io_start(struct snapraid_io* io, + block_off_t blockstart, block_off_t blockmax, + int (*block_is_enabled)(void* arg, block_off_t), void* blockarg); + +/** + * Stop all the worker threads. + */ +void io_stop(struct snapraid_io* io); + +/** + * Next read position. + * + * This call starts the reading process. + * It must be called before io_data_read() and io_parity_read(). + * + * \param io InputOutput context. + * \param buffer The data buffers to use for this position. + * \return The parity position. + */ +block_off_t io_read_next(struct snapraid_io* io, void*** buffer); + +/** + * Read a data block. + * + * It must be called exactly ::handle_max times. + * + * \param io InputOutput context. + * \param pos The position of the data block in the ::handle_map vector. + * \return The completed task. + */ +struct snapraid_task* io_data_read(struct snapraid_io* io, unsigned* diskcur); + +/** + * Read a parity block. + * + * It must be called exactly ::parity_handle_max times. + * + * \param io InputOutput context. + * \param pos The position of the parity block in the ::parity_handle_map vector. + * \return The completed task. + */ +struct snapraid_task* io_parity_read(struct snapraid_io* io, unsigned* levcur); + +/** + * Write of a parity block. + * + * It must be called exactly ::parity_handle_max times. + * + * \param io InputOutput context. + * \param pos The position of the parity block in the ::parity_handle_map vector. + */ +void io_parity_write(struct snapraid_io* io, unsigned* levcur); + +/** + * Next write position. + * + * This call ends the write process. + * It must be called after io_parity_write(). + * + * \param io InputOutput context. + * \param blockcur The parity position to write. + * \param skip Skip the writes, in case parity doesn't need to be updated. + * \param writer_error Return the number of errors. Vector of IO_WRITER_ERROR_MAX elements. + */ +void io_write_next(struct snapraid_io* io, unsigned blockcur, int skip, int* writer_error); + +#endif + diff --git a/cmdline/mingw.c b/cmdline/mingw.c index 98db7915..f9d651d6 100644 --- a/cmdline/mingw.c +++ b/cmdline/mingw.c @@ -1760,7 +1760,7 @@ off_t windows_lseek(int fd, off_t offset, int whence) ssize_t windows_pread(int fd, void* buffer, size_t size, off_t offset) { HANDLE h; - LARGE_INTEGER pos; + OVERLAPPED overlapped; DWORD count; if (fd == -1) { @@ -1775,13 +1775,13 @@ ssize_t windows_pread(int fd, void* buffer, size_t size, off_t offset) } retry: - pos.QuadPart = offset; - if (!SetFilePointerEx(h, pos, 0, FILE_BEGIN)) { - windows_errno(GetLastError()); - return -1; - } + overlapped.Internal = 0; + overlapped.InternalHigh = 0; + overlapped.Offset = offset & 0xFFFFFFFF; + overlapped.OffsetHigh = offset >> 32; + overlapped.hEvent = 0; - if (!ReadFile(h, buffer, size, &count, 0)) { + if (!ReadFile(h, buffer, size, &count, &overlapped)) { DWORD err = GetLastError(); /* * If Windows is not able to allocate memory from the PagedPool for the disk cache @@ -1818,7 +1818,7 @@ retry: ssize_t windows_pwrite(int fd, const void* buffer, size_t size, off_t offset) { HANDLE h; - LARGE_INTEGER pos; + OVERLAPPED overlapped; DWORD count; if (fd == -1) { @@ -1833,13 +1833,13 @@ ssize_t windows_pwrite(int fd, const void* buffer, size_t size, off_t offset) } retry: - pos.QuadPart = offset; - if (!SetFilePointerEx(h, pos, 0, FILE_BEGIN)) { - windows_errno(GetLastError()); - return -1; - } + overlapped.Internal = 0; + overlapped.InternalHigh = 0; + overlapped.Offset = offset & 0xFFFFFFFF; + overlapped.OffsetHigh = offset >> 32; + overlapped.hEvent = 0; - if (!WriteFile(h, buffer, size, &count, 0)) { + if (!WriteFile(h, buffer, size, &count, &overlapped)) { DWORD err = GetLastError(); /* See windows_pread() for comments on this error management */ if (err == ERROR_NO_SYSTEM_RESOURCES) { diff --git a/cmdline/mingw.h b/cmdline/mingw.h index 21f309e1..aeb34def 100644 --- a/cmdline/mingw.h +++ b/cmdline/mingw.h @@ -109,8 +109,6 @@ #define write windows_write #undef lseek #define lseek windows_lseek -#define HAVE_PREAD 1 -#define HAVE_PWRITE 1 #undef pread #define pread windows_pread #undef pwrite diff --git a/cmdline/mktest.c b/cmdline/mktest.c index c781df0d..1df95cc9 100644 --- a/cmdline/mktest.c +++ b/cmdline/mktest.c @@ -647,6 +647,8 @@ int main(int argc, char* argv[]) { int i, j, b; + lock_init(); + if (argc < 2) { help(); exit(EXIT_SUCCESS); @@ -785,6 +787,8 @@ int main(int argc, char* argv[]) /* LCOV_EXCL_STOP */ } + lock_done(); + return 0; } diff --git a/cmdline/parity.c b/cmdline/parity.c index f62a3f41..ab7ce9ac 100644 --- a/cmdline/parity.c +++ b/cmdline/parity.c @@ -37,7 +37,7 @@ block_off_t parity_allocated_size(struct snapraid_state* state) struct snapraid_disk* disk = i->data; /* start from the declared size */ - block_off_t block = disk_size(disk); + block_off_t block = fs_size(disk); /* decrease the block until an allocated one, but part of a file */ /* we don't stop at deleted blocks, because we want to have them cleared */ @@ -64,7 +64,7 @@ block_off_t parity_used_size(struct snapraid_state* state) struct snapraid_disk* disk = i->data; /* start from the declared size */ - block_off_t block = disk_size(disk); + block_off_t block = fs_size(disk); /* decrease the block until an used one */ while (block > parity_block && !block_has_file_and_valid_parity(fs_par2block_get(disk, block - 1))) @@ -149,11 +149,12 @@ void parity_overflow(struct snapraid_state* state, data_off_t size) } } -int parity_create(struct snapraid_parity_handle* parity, const char* path, data_off_t* out_size, int mode) +int parity_create(struct snapraid_parity_handle* parity, unsigned level, const char* path, data_off_t* out_size, int mode) { int ret; int flags; + parity->level = level; pathcpy(parity->path, sizeof(parity->path), path); /* opening in sequential mode in Windows */ @@ -323,11 +324,12 @@ bail: /* LCOV_EXCL_STOP */ } -int parity_open(struct snapraid_parity_handle* parity, const char* path, int mode) +int parity_open(struct snapraid_parity_handle* parity, unsigned level, const char* path, int mode) { int ret; int flags; + parity->level = level; pathcpy(parity->path, sizeof(parity->path), path); /* open for read */ @@ -431,22 +433,7 @@ int parity_write(struct snapraid_parity_handle* parity, block_off_t pos, unsigne offset = pos * (data_off_t)block_size; -#if HAVE_PWRITE write_ret = pwrite(parity->f, block_buffer, block_size, offset); -#else - if (lseek(parity->f, offset, SEEK_SET) != offset) { - /* LCOV_EXCL_START */ - if (errno == ENOSPC) { - log_fatal("Failed to grow parity file '%s' using lseek due lack of space.\n", parity->path); - } else { - log_fatal("Error seeking file '%s'. %s.\n", parity->path, strerror(errno)); - } - return -1; - /* LCOV_EXCL_STOP */ - } - - write_ret = write(parity->f, block_buffer, block_size); -#endif if (write_ret != (ssize_t)block_size) { /* conversion is safe because block_size is always small */ /* LCOV_EXCL_START */ if (errno == ENOSPC) { @@ -483,23 +470,9 @@ int parity_read(struct snapraid_parity_handle* parity, block_off_t pos, unsigned return -1; } -#if !HAVE_PREAD - if (lseek(parity->f, offset, SEEK_SET) != offset) { - /* LCOV_EXCL_START */ - out("Error seeking file '%s' at offset %" PRIu64 ". %s.\n", parity->path, offset, strerror(errno)); - return -1; - /* LCOV_EXCL_STOP */ - } -#endif - count = 0; do { - -#if HAVE_PREAD read_ret = pread(parity->f, block_buffer + count, block_size - count, offset + count); -#else - read_ret = read(parity->f, block_buffer + count, block_size - count); -#endif if (read_ret < 0) { /* LCOV_EXCL_START */ out("Error reading file '%s' at offset %" PRIu64 " for size %u. %s.\n", parity->path, offset + count, block_size - count, strerror(errno)); diff --git a/cmdline/parity.h b/cmdline/parity.h index 066ff3d7..f63376bb 100644 --- a/cmdline/parity.h +++ b/cmdline/parity.h @@ -25,6 +25,7 @@ struct snapraid_parity_handle { char path[PATH_MAX]; /**< Path of the file. */ + unsigned level; /**< Level of the parity. */ int f; /**< Handle of the file. */ struct stat st; /**< Stat info of the opened file. */ data_off_t valid_size; /**< Size of the valid data. */ @@ -74,7 +75,7 @@ void parity_overflow(struct snapraid_state* state, data_off_t size); * Create the parity file. * \param out_size Return the size of the parity file. */ -int parity_create(struct snapraid_parity_handle* parity, const char* path, data_off_t* out_size, int mode); +int parity_create(struct snapraid_parity_handle* parity, unsigned level, const char* path, data_off_t* out_size, int mode); /** * Change the parity size. @@ -85,7 +86,7 @@ int parity_chsize(struct snapraid_parity_handle* parity, data_off_t size, data_o /** * Open an already existing parity file. */ -int parity_open(struct snapraid_parity_handle* parity, const char* path, int mode); +int parity_open(struct snapraid_parity_handle* parity, unsigned level, const char* path, int mode); /** * Flush the parity file in the disk. diff --git a/cmdline/portable.h b/cmdline/portable.h index 2e37eeba..18867f89 100644 --- a/cmdline/portable.h +++ b/cmdline/portable.h @@ -75,8 +75,9 @@ /** - * Architecture. + * Architecture for inline assembly. */ +#if HAVE_ASSEMBLY #if defined(__i386__) #define CONFIG_X86 1 #define CONFIG_X86_32 1 @@ -86,6 +87,7 @@ #define CONFIG_X86 1 #define CONFIG_X86_64 1 #endif +#endif /** * Includes some platform specific headers. diff --git a/cmdline/scrub.c b/cmdline/scrub.c index 3e9073a7..99d27e01 100644 --- a/cmdline/scrub.c +++ b/cmdline/scrub.c @@ -22,6 +22,7 @@ #include "state.h" #include "parity.h" #include "handle.h" +#include "io.h" #include "raid/raid.h" /****************************************************************************/ @@ -39,6 +40,7 @@ struct snapraid_rehash { * Scrub plan to use. */ struct snapraid_plan { + struct snapraid_state* state; int plan; /**< One of the SCRUB_*. */ time_t timelimit; /**< Time limit. Valid only with SCRUB_AUTO. */ block_off_t lastlimit; /**< Number of blocks allowed with time exactly at ::timelimit. */ @@ -48,13 +50,14 @@ struct snapraid_plan { /** * Check if we have to process the specified block index ::i. */ -static int block_is_enabled(struct snapraid_state* state, block_off_t i, struct snapraid_plan* plan) +static int block_is_enabled(void* void_plan, block_off_t i) { + struct snapraid_plan* plan = void_plan; time_t blocktime; snapraid_info info; /* don't scrub unused blocks in all plans */ - info = info_get(&state->infoarr, i); + info = info_get(&plan->state->infoarr, i); if (info == 0) return 0; @@ -99,16 +102,156 @@ static int block_is_enabled(struct snapraid_state* state, block_off_t i, struct return 1; } -static int state_scrub_process(struct snapraid_state* state, struct snapraid_parity_handle** parity, block_off_t blockstart, block_off_t blockmax, struct snapraid_plan* plan, time_t now) +static void scrub_data_reader(struct snapraid_worker* worker, struct snapraid_task* task) { + struct snapraid_io* io = worker->io; + struct snapraid_state* state = io->state; + struct snapraid_handle* handle = worker->handle; + struct snapraid_disk* disk = handle->disk; + block_off_t blockcur = task->position; + unsigned char* buffer = task->buffer; + int ret; + + /* if the disk position is not used */ + if (!disk) { + /* use an empty block */ + memset(buffer, 0, state->block_size); + task->state = TASK_STATE_DONE; + return; + } + + /* get the block */ + task->block = fs_par2block_get(disk, blockcur); + + /* if the block is not used */ + if (!block_has_file(task->block)) { + /* use an empty block */ + memset(buffer, 0, state->block_size); + task->state = TASK_STATE_DONE; + return; + } + + /* get the file of this block */ + task->file = fs_par2file_get(disk, blockcur, &task->file_pos); + + /* if the file is different than the current one, close it */ + if (handle->file != 0 && handle->file != task->file) { + /* keep a pointer at the file we are going to close for error reporting */ + struct snapraid_file* report = handle->file; + ret = handle_close(handle); + if (ret == -1) { + /* LCOV_EXCL_START */ + /* This one is really an unexpected error, because we are only reading */ + /* and closing a descriptor should never fail */ + if (errno == EIO) { + log_tag("error:%u:%s:%s: Close EIO error. %s\n", blockcur, disk->name, esc(report->sub), strerror(errno)); + log_fatal("DANGER! Unexpected input/output close error in a data disk, it isn't possible to scrub.\n"); + log_fatal("Ensure that disk '%s' is sane and that file '%s' can be accessed.\n", disk->dir, handle->path); + log_fatal("Stopping at block %u\n", blockcur); + task->state = TASK_STATE_IOERROR; + return; + } + + log_tag("error:%u:%s:%s: Close error. %s\n", blockcur, disk->name, esc(report->sub), strerror(errno)); + log_fatal("WARNING! Unexpected close error in a data disk, it isn't possible to scrub.\n"); + log_fatal("Ensure that file '%s' can be accessed.\n", handle->path); + log_fatal("Stopping at block %u\n", blockcur); + task->state = TASK_STATE_ERROR; + return; + /* LCOV_EXCL_STOP */ + } + } + + ret = handle_open(handle, task->file, state->file_mode, log_error, 0); + if (ret == -1) { + if (errno == EIO) { + /* LCOV_EXCL_START */ + log_tag("error:%u:%s:%s: Open EIO error. %s\n", blockcur, disk->name, esc(task->file->sub), strerror(errno)); + log_fatal("DANGER! Unexpected input/output open error in a data disk, it isn't possible to scrub.\n"); + log_fatal("Ensure that disk '%s' is sane and that file '%s' can be accessed.\n", disk->dir, handle->path); + log_fatal("Stopping at block %u\n", blockcur); + task->state = TASK_STATE_IOERROR; + return; + /* LCOV_EXCL_STOP */ + } + + log_tag("error:%u:%s:%s: Open error. %s\n", blockcur, disk->name, esc(task->file->sub), strerror(errno)); + task->state = TASK_STATE_ERROR_CONTINUE; + return; + } + + /* check if the file is changed */ + if (handle->st.st_size != task->file->size + || handle->st.st_mtime != task->file->mtime_sec + || STAT_NSEC(&handle->st) != task->file->mtime_nsec + /* don't check the inode to support filesystem without persistent inodes */ + ) { + /* report that the block and the file are not synced */ + task->is_timestamp_different = 1; + /* follow */ + } + + /* note that we intentionally don't abort if the file has different attributes */ + /* from the last sync, as we are expected to return errors if running */ + /* in an unsynced array. This is just like the check command. */ + + task->read_size = handle_read(handle, task->file_pos, buffer, state->block_size, log_error, 0); + if (task->read_size == -1) { + if (errno == EIO) { + log_tag("error:%u:%s:%s: Read EIO error at position %u. %s\n", blockcur, disk->name, esc(task->file->sub), task->file_pos, strerror(errno)); + log_error("Input/Output error in file '%s' at position '%u'\n", handle->path, task->file_pos); + task->state = TASK_STATE_IOERROR_CONTINUE; + return; + } + + log_tag("error:%u:%s:%s: Read error at position %u. %s\n", blockcur, disk->name, esc(task->file->sub), task->file_pos, strerror(errno)); + task->state = TASK_STATE_ERROR_CONTINUE; + return; + } + + /* store the path of the opened file */ + pathcpy(task->path, sizeof(task->path), handle->path); + + task->state = TASK_STATE_DONE; +} + +static void scrub_parity_reader(struct snapraid_worker* worker, struct snapraid_task* task) +{ + struct snapraid_io* io = worker->io; + struct snapraid_state* state = io->state; + struct snapraid_parity_handle* parity_handle = worker->parity_handle; + unsigned level = parity_handle->level; + block_off_t blockcur = task->position; + unsigned char* buffer = task->buffer; + int ret; + + /* read the parity */ + ret = parity_read(parity_handle, blockcur, buffer, state->block_size, log_error); + if (ret == -1) { + if (errno == EIO) { + log_tag("parity_error:%u:%s: Read EIO error. %s\n", blockcur, lev_config_name(level), strerror(errno)); + log_error("Input/Output error in parity '%s' at position '%u'\n", lev_config_name(level), blockcur); + task->state = TASK_STATE_IOERROR_CONTINUE; + return; + } + + log_tag("parity_error:%u:%s: Read error. %s\n", blockcur, lev_config_name(level), strerror(errno)); + task->state = TASK_STATE_ERROR_CONTINUE; + return; + } + + task->state = TASK_STATE_DONE; +} + +static int state_scrub_process(struct snapraid_state* state, struct snapraid_parity_handle* parity_handle, block_off_t blockstart, block_off_t blockmax, struct snapraid_plan* plan, time_t now) +{ + struct snapraid_io io; struct snapraid_handle* handle; void* rehandle_alloc; struct snapraid_rehash* rehandle; unsigned diskmax; - block_off_t i; + block_off_t blockcur; unsigned j; - void* buffer_alloc; - void** buffer; unsigned buffermax; data_off_t countsize; block_off_t countpos; @@ -128,12 +271,11 @@ static int state_scrub_process(struct snapraid_state* state, struct snapraid_par /* rehash buffers */ rehandle = malloc_nofail_align(diskmax * sizeof(struct snapraid_rehash), &rehandle_alloc); - /* we need disk + 2 for each parity level buffers */ - buffermax = diskmax + state->level * 2; + /* we need 1 * data + 2 * parity */ + buffermax = diskmax + 2 * state->level; - buffer = malloc_nofail_vector_align(diskmax, buffermax, state->block_size, &buffer_alloc); - if (!state->opt.skip_self) - mtest_vector(buffermax, state->block_size, buffer); + /* initialize the io threads */ + io_init(&io, state, buffermax, scrub_data_reader, handle, diskmax, scrub_parity_reader, 0, parity_handle, state->level); error = 0; silent_error = 0; @@ -142,10 +284,9 @@ static int state_scrub_process(struct snapraid_state* state, struct snapraid_par /* first count the number of blocks to process */ countmax = 0; plan->countlast = 0; - for (i = blockstart; i < blockmax; ++i) { - if (!block_is_enabled(state, i, plan)) + for (blockcur = blockstart; blockcur < blockmax; ++blockcur) { + if (!block_is_enabled(plan, blockcur)) continue; - ++countmax; } @@ -162,17 +303,25 @@ static int state_scrub_process(struct snapraid_state* state, struct snapraid_par countsize = 0; countpos = 0; plan->countlast = 0; + + /* start all the worker threads */ + io_start(&io, blockstart, blockmax, &block_is_enabled, plan); + state_progress_begin(state, blockstart, blockmax, countmax); - for (i = blockstart; i < blockmax; ++i) { + while (1) { + unsigned char* buffer_recov[LEV_MAX]; snapraid_info info; int error_on_this_block; int silent_error_on_this_block; int io_error_on_this_block; int block_is_unsynced; int rehash; + void** buffer; - if (!block_is_enabled(state, i, plan)) - continue; + /* go to the next block */ + blockcur = io_read_next(&io, &buffer); + if (blockcur >= blockmax) + break; /* one more block processed for autosave */ ++autosavedone; @@ -188,45 +337,49 @@ static int state_scrub_process(struct snapraid_state* state, struct snapraid_par block_is_unsynced = 0; /* get block specific info */ - info = info_get(&state->infoarr, i); + info = info_get(&state->infoarr, blockcur); /* if we have to use the old hash */ rehash = info_get_rehash(info); /* for each disk, process the block */ for (j = 0; j < diskmax; ++j) { + struct snapraid_task* task; int read_size; unsigned char hash[HASH_SIZE]; struct snapraid_block* block; int file_is_unsynced; - struct snapraid_disk* disk = handle[j].disk; + struct snapraid_disk* disk; struct snapraid_file* file; block_off_t file_pos; + unsigned diskcur; /* if the file on this disk is synced */ /* if not, silent errors are assumed as expected error */ file_is_unsynced = 0; + /* until now is CPU */ + state_usage_cpu(state); + + /* get the next task */ + task = io_data_read(&io, &diskcur); + + /* get the task results */ + disk = task->disk; + block = task->block; + file = task->file; + file_pos = task->file_pos; + read_size = task->read_size; + /* by default no rehash in case of "continue" */ - rehandle[j].block = 0; + rehandle[diskcur].block = 0; /* if the disk position is not used */ - if (!disk) { - /* use an empty block */ - memset(buffer[j], 0, state->block_size); + if (!disk) continue; - } - /* if the block is not used */ - block = fs_par2block_get(disk, i); - if (!block_has_file(block)) { - /* use an empty block */ - memset(buffer[j], 0, state->block_size); - continue; - } - - /* get the file of this block */ - file = fs_par2file_get(disk, i, &file_pos); + /* until now is disk */ + state_usage_disk(state, disk); /* if the block is unsynced, errors are expected */ if (block_has_invalid_parity(block)) { @@ -236,112 +389,65 @@ static int state_scrub_process(struct snapraid_state* state, struct snapraid_par /* follow */ } - /* until now is CPU */ - state_usage_cpu(state); - - /* if the file is different than the current one, close it */ - if (handle[j].file != 0 && handle[j].file != file) { - /* keep a pointer at the file we are going to close for error reporting */ - struct snapraid_file* report = handle[j].file; - ret = handle_close(&handle[j]); - if (ret == -1) { - /* LCOV_EXCL_START */ - /* This one is really an unexpected error, because we are only reading */ - /* and closing a descriptor should never fail */ - if (errno == EIO) { - log_tag("error:%u:%s:%s: Close EIO error. %s\n", i, disk->name, esc(report->sub), strerror(errno)); - log_fatal("DANGER! Unexpected input/output close error in a data disk, it isn't possible to scrub.\n"); - log_fatal("Ensure that disk '%s' is sane and that file '%s' can be accessed.\n", disk->dir, handle[j].path); - log_fatal("Stopping at block %u\n", i); - ++io_error; - goto bail; - } - - log_tag("error:%u:%s:%s: Close error. %s\n", i, disk->name, esc(report->sub), strerror(errno)); - log_fatal("WARNING! Unexpected close error in a data disk, it isn't possible to scrub.\n"); - log_fatal("Ensure that file '%s' can be accessed.\n", handle[j].path); - log_fatal("Stopping at block %u\n", i); - ++error; - goto bail; - /* LCOV_EXCL_STOP */ - } - } - - ret = handle_open(&handle[j], file, state->file_mode, log_error, 0); - if (ret == -1) { - if (errno == EIO) { - /* LCOV_EXCL_START */ - log_tag("error:%u:%s:%s: Open EIO error. %s\n", i, disk->name, esc(file->sub), strerror(errno)); - log_fatal("DANGER! Unexpected input/output open error in a data disk, it isn't possible to scrub.\n"); - log_fatal("Ensure that disk '%s' is sane and that file '%s' can be accessed.\n", disk->dir, handle[j].path); - log_fatal("Stopping at block %u\n", i); - ++io_error; - goto bail; - /* LCOV_EXCL_STOP */ - } - - log_tag("error:%u:%s:%s: Open error. %s\n", i, disk->name, esc(file->sub), strerror(errno)); - ++error; - error_on_this_block = 1; + /* if the block is not used */ + if (!block_has_file(block)) continue; - } - /* check if the file is changed */ - if (handle[j].st.st_size != file->size - || handle[j].st.st_mtime != file->mtime_sec - || STAT_NSEC(&handle[j].st) != file->mtime_nsec - /* don't check the inode to support file-system without persistent inodes */ - ) { + /* if the block is unsynced, errors are expected */ + if (task->is_timestamp_different) { /* report that the block and the file are not synced */ block_is_unsynced = 1; file_is_unsynced = 1; /* follow */ } - /* note that we intentionally don't abort if the file has different attributes */ - /* from the last sync, as we are expected to return errors if running */ - /* in an unsynced array. This is just like the check command. */ - - read_size = handle_read(&handle[j], file_pos, buffer[j], state->block_size, log_error, 0); - if (read_size == -1) { - if (errno == EIO) { - log_tag("error:%u:%s:%s: Read EIO error at position %u. %s\n", i, disk->name, esc(file->sub), file_pos, strerror(errno)); - if (io_error >= state->opt.io_error_limit) { - /* LCOV_EXCL_START */ - log_fatal("DANGER! Too many input/output read error in a data disk, it isn't possible to scrub.\n"); - log_fatal("Ensure that disk '%s' is sane and that file '%s' can be accessed.\n", disk->dir, handle[j].path); - log_fatal("Stopping at block %u\n", i); - ++io_error; - goto bail; - /* LCOV_EXCL_STOP */ - } - - log_error("Input/Output error in file '%s' at position '%u'\n", handle[j].path, file_pos); - ++io_error; - io_error_on_this_block = 1; - continue; - } - - log_tag("error:%u:%s:%s: Read error at position %u. %s\n", i, disk->name, esc(file->sub), file_pos, strerror(errno)); + /* handle error conditions */ + if (task->state == TASK_STATE_IOERROR) { + ++io_error; + goto bail; + } + if (task->state == TASK_STATE_ERROR) { + ++error; + goto bail; + } + if (task->state == TASK_STATE_ERROR_CONTINUE) { ++error; error_on_this_block = 1; continue; } + if (task->state == TASK_STATE_IOERROR_CONTINUE) { + ++io_error; + if (io_error >= state->opt.io_error_limit) { + /* LCOV_EXCL_START */ + log_fatal("DANGER! Too many input/output read error in a data disk, it isn't possible to scrub.\n"); + log_fatal("Ensure that disk '%s' is sane and that file '%s' can be accessed.\n", disk->dir, task->path); + log_fatal("Stopping at block %u\n", blockcur); + goto bail; + /* LCOV_EXCL_STOP */ + } - /* until now is disk */ - state_usage_disk(state, disk); + /* otherwise continue */ + io_error_on_this_block = 1; + continue; + } + if (task->state != TASK_STATE_DONE) { + /* LCOV_EXCL_START */ + log_fatal("Internal inconsistency in task state\n"); + os_abort(); + /* LCOV_EXCL_STOP */ + } countsize += read_size; /* now compute the hash */ if (rehash) { - memhash(state->prevhash, state->prevhashseed, hash, buffer[j], read_size); + memhash(state->prevhash, state->prevhashseed, hash, buffer[diskcur], read_size); /* compute the new hash, and store it */ - rehandle[j].block = block; - memhash(state->hash, state->hashseed, rehandle[j].hash, buffer[j], read_size); + rehandle[diskcur].block = block; + memhash(state->hash, state->hashseed, rehandle[diskcur].hash, buffer[diskcur], read_size); } else { - memhash(state->hash, state->hashseed, hash, buffer[j], read_size); + memhash(state->hash, state->hashseed, hash, buffer[diskcur], read_size); } if (block_has_updated_hash(block)) { @@ -349,14 +455,14 @@ static int state_scrub_process(struct snapraid_state* state, struct snapraid_par if (memcmp(hash, block->hash, HASH_SIZE) != 0) { unsigned diff = memdiff(hash, block->hash, HASH_SIZE); - log_tag("error:%u:%s:%s: Data error at position %u, diff bits %u\n", i, disk->name, esc(file->sub), file_pos, diff); + log_tag("error:%u:%s:%s: Data error at position %u, diff bits %u\n", blockcur, disk->name, esc(file->sub), file_pos, diff); /* it's a silent error only if we are dealing with synced files */ if (file_is_unsynced) { ++error; error_on_this_block = 1; } else { - log_error("Data error in file '%s' at position '%u', diff bits %u\n", handle[j].path, file_pos, diff); + log_error("Data error in file '%s' at position '%u', diff bits %u\n", task->path, file_pos, diff); ++silent_error; silent_error_on_this_block = 1; } @@ -365,52 +471,70 @@ static int state_scrub_process(struct snapraid_state* state, struct snapraid_par } } - /* if we have read all the data required and it's correct, proceed with the parity check */ - if (!error_on_this_block && !silent_error_on_this_block && !io_error_on_this_block) { - unsigned char* buffer_recov[LEV_MAX]; + /* buffers for parity read and not computed */ + for (l = 0; l < state->level; ++l) + buffer_recov[l] = buffer[diskmax + state->level + l]; + for (; l < LEV_MAX; ++l) + buffer_recov[l] = 0; - /* until now is CPU */ - state_usage_cpu(state); + /* until now is CPU */ + state_usage_cpu(state); - /* buffers for parity read and not computed */ - for (l = 0; l < state->level; ++l) - buffer_recov[l] = buffer[diskmax + state->level + l]; - for (; l < LEV_MAX; ++l) - buffer_recov[l] = 0; + /* read the parity */ + for (l = 0; l < state->level; ++l) { + struct snapraid_task* task; + unsigned levcur; - /* read the parity */ - for (l = 0; l < state->level; ++l) { - ret = parity_read(parity[l], i, buffer_recov[l], state->block_size, log_error); - if (ret == -1) { - buffer_recov[l] = 0; + task = io_parity_read(&io, &levcur); - if (errno == EIO) { - log_tag("parity_error:%u:%s: Read EIO error. %s\n", i, lev_config_name(l), strerror(errno)); - if (io_error >= state->opt.io_error_limit) { - /* LCOV_EXCL_START */ - log_fatal("DANGER! Too many input/output read error in the %s disk, it isn't possible to scrub.\n", lev_name(l)); - log_fatal("Ensure that disk '%s' is sane and can be read.\n", lev_config_name(l)); - log_fatal("Stopping at block %u\n", i); - ++io_error; - goto bail; - /* LCOV_EXCL_STOP */ - } + /* until now is parity */ + state_usage_parity(state, levcur); - log_error("Input/Output error in parity '%s' at position '%u'\n", lev_config_name(l), i); - ++io_error; - io_error_on_this_block = 1; - continue; - } + /* handle error conditions */ + if (task->state == TASK_STATE_IOERROR) { + ++io_error; + goto bail; + } + if (task->state == TASK_STATE_ERROR) { + ++error; + goto bail; + } + if (task->state == TASK_STATE_ERROR_CONTINUE) { + ++error; + error_on_this_block = 1; - log_tag("parity_error:%u:%s: Read error. %s\n", i, lev_config_name(l), strerror(errno)); - ++error; - error_on_this_block = 1; - continue; + /* if continuing on error, clear the missing buffer */ + buffer_recov[levcur] = 0; + continue; + } + if (task->state == TASK_STATE_IOERROR_CONTINUE) { + ++io_error; + if (io_error >= state->opt.io_error_limit) { + /* LCOV_EXCL_START */ + log_fatal("DANGER! Too many input/output read error in the %s disk, it isn't possible to scrub.\n", lev_name(levcur)); + log_fatal("Ensure that disk '%s' is sane and can be read.\n", lev_config_name(levcur)); + log_fatal("Stopping at block %u\n", blockcur); + goto bail; + /* LCOV_EXCL_STOP */ } - /* until now is parity */ - state_usage_parity(state, l); + /* otherwise continue */ + io_error_on_this_block = 1; + + /* if continuing on error, clear the missing buffer */ + buffer_recov[levcur] = 0; + continue; } + if (task->state != TASK_STATE_DONE) { + /* LCOV_EXCL_START */ + log_fatal("Internal inconsistency in task state\n"); + os_abort(); + /* LCOV_EXCL_STOP */ + } + } + + /* if we have read all the data required and it's correct, proceed with the parity check */ + if (!error_on_this_block && !silent_error_on_this_block && !io_error_on_this_block) { /* compute the parity */ raid_gen(diskmax, state->level, state->block_size, buffer); @@ -420,14 +544,14 @@ static int state_scrub_process(struct snapraid_state* state, struct snapraid_par if (buffer_recov[l] && memcmp(buffer[diskmax + l], buffer_recov[l], state->block_size) != 0) { unsigned diff = memdiff(buffer[diskmax + l], buffer_recov[l], state->block_size); - log_tag("parity_error:%u:%s: Data error, diff bits %u\n", i, lev_config_name(l), diff); + log_tag("parity_error:%u:%s: Data error, diff bits %u\n", blockcur, lev_config_name(l), diff); /* it's a silent error only if we are dealing with synced blocks */ if (block_is_unsynced) { ++error; error_on_this_block = 1; } else { - log_fatal("Data error in parity '%s' at position '%u', diff bits %u\n", lev_config_name(l), i, diff); + log_fatal("Data error in parity '%s' at position '%u', diff bits %u\n", lev_config_name(l), blockcur, diff); ++silent_error; silent_error_on_this_block = 1; } @@ -437,7 +561,7 @@ static int state_scrub_process(struct snapraid_state* state, struct snapraid_par if (silent_error_on_this_block || io_error_on_this_block) { /* set the error status keeping other info */ - info_set(&state->infoarr, i, info_set_bad(info)); + info_set(&state->infoarr, blockcur, info_set_bad(info)); } else if (error_on_this_block) { /* do nothing, as this is a generic error */ /* likely caused by a not synced array */ @@ -453,7 +577,7 @@ static int state_scrub_process(struct snapraid_state* state, struct snapraid_par /* update the time info of the block */ /* and clear any other flag */ - info_set(&state->infoarr, i, info_make(now, 0, 0, 0)); + info_set(&state->infoarr, blockcur, info_make(now, 0, 0, 0)); } /* mark the state as needing write */ @@ -463,7 +587,7 @@ static int state_scrub_process(struct snapraid_state* state, struct snapraid_par ++countpos; /* progress */ - if (state_progress(state, i, countpos, countmax, countsize)) { + if (state_progress(state, blockcur, countpos, countmax, countsize)) { /* LCOV_EXCL_START */ break; /* LCOV_EXCL_STOP */ @@ -527,13 +651,16 @@ static int state_scrub_process(struct snapraid_state* state, struct snapraid_par log_flush(); bail: + /* stop all the worker threads */ + io_stop(&io); + for (j = 0; j < diskmax; ++j) { struct snapraid_file* file = handle[j].file; struct snapraid_disk* disk = handle[j].disk; ret = handle_close(&handle[j]); if (ret == -1) { /* LCOV_EXCL_START */ - log_tag("error:%u:%s:%s: Close error. %s\n", i, disk->name, esc(file->sub), strerror(errno)); + log_tag("error:%u:%s:%s: Close error. %s\n", blockcur, disk->name, esc(file->sub), strerror(errno)); log_fatal("DANGER! Unexpected close error in a data disk.\n"); ++error; /* continue, as we are already exiting */ @@ -542,9 +669,8 @@ bail: } free(handle); - free(buffer_alloc); - free(buffer); free(rehandle_alloc); + io_done(&io); if (state->opt.expect_recoverable) { if (error + silent_error + io_error == 0) @@ -578,8 +704,7 @@ int state_scrub(struct snapraid_state* state, int plan, int olderthan) block_off_t count; time_t recentlimit; int ret; - struct snapraid_parity_handle parity[LEV_MAX]; - struct snapraid_parity_handle* parity_ptr[LEV_MAX]; + struct snapraid_parity_handle parity_handle[LEV_MAX]; struct snapraid_plan ps; time_t* timemap; unsigned error; @@ -605,6 +730,7 @@ int state_scrub(struct snapraid_state* state, int plan, int olderthan) countlimit = 0; recentlimit = 0; + ps.state = state; if (state->opt.force_scrub_even) { ps.plan = SCRUB_EVEN; } else if (plan == SCRUB_FULL) { @@ -710,8 +836,7 @@ int state_scrub(struct snapraid_state* state, int plan, int olderthan) /* open the file for reading */ for (l = 0; l < state->level; ++l) { - parity_ptr[l] = &parity[l]; - ret = parity_open(parity_ptr[l], state->parity[l].path, state->file_mode); + ret = parity_open(&parity_handle[l], l, state->parity[l].path, state->file_mode); if (ret == -1) { /* LCOV_EXCL_START */ log_fatal("WARNING! Without an accessible %s file, it isn't possible to scrub.\n", lev_name(l)); @@ -724,14 +849,14 @@ int state_scrub(struct snapraid_state* state, int plan, int olderthan) error = 0; - ret = state_scrub_process(state, parity_ptr, 0, blockmax, &ps, now); + ret = state_scrub_process(state, parity_handle, 0, blockmax, &ps, now); if (ret == -1) { ++error; /* continue, as we are already exiting */ } for (l = 0; l < state->level; ++l) { - ret = parity_close(parity_ptr[l]); + ret = parity_close(&parity_handle[l]); if (ret == -1) { /* LCOV_EXCL_START */ log_fatal("DANGER! Unexpected close error in %s disk.\n", lev_name(l)); diff --git a/cmdline/search.c b/cmdline/search.c index 09a26283..357e0498 100644 --- a/cmdline/search.c +++ b/cmdline/search.c @@ -89,14 +89,7 @@ int search_file_compare(const void* void_arg, const void* void_data) /* LCOV_EXCL_STOP */ } - if (lseek(f, arg->offset, SEEK_SET) != arg->offset) { - /* LCOV_EXCL_START */ - log_fatal("Error seeking file '%s'. %s.\n", path, strerror(errno)); - exit(EXIT_FAILURE); - /* LCOV_EXCL_STOP */ - } - - ret = read(f, arg->buffer, arg->read_size); + ret = pread(f, arg->buffer, arg->read_size, arg->offset); if (ret < 0 || (unsigned)ret != arg->read_size) { /* LCOV_EXCL_START */ log_fatal("Error reading file '%s'. %s.\n", path, strerror(errno)); diff --git a/cmdline/snapraid.c b/cmdline/snapraid.c index 438c10d8..c52f4d27 100644 --- a/cmdline/snapraid.c +++ b/cmdline/snapraid.c @@ -85,7 +85,7 @@ void memory(void) log_tag("memory:link:%" PRIu64 "\n", (uint64_t)(sizeof(struct snapraid_link))); log_tag("memory:dir:%" PRIu64 "\n", (uint64_t)(sizeof(struct snapraid_dir))); - msg_progress("Using %u MiB of memory.\n", (unsigned)(malloc_counter_get() / (1024 * 1024))); + msg_progress("Using %u MiB of memory for the FileSystem.\n", (unsigned)(malloc_counter_get() / (1024 * 1024))); } /****************************************************************************/ @@ -463,6 +463,8 @@ int main(int argc, char* argv[]) struct tm* tm; int i; + lock_init(); + /* defaults */ config(conf, sizeof(conf), argv[0]); memset(&opt, 0, sizeof(opt)); @@ -1319,6 +1321,7 @@ int main(int argc, char* argv[]) tommy_list_foreach(&filterlist_disk, (tommy_foreach_func*)filter_free); os_done(); + lock_done(); return EXIT_SUCCESS; } diff --git a/cmdline/state.c b/cmdline/state.c index a140d122..d15949f4 100644 --- a/cmdline/state.c +++ b/cmdline/state.c @@ -1599,9 +1599,9 @@ static void fs_position_clear_deleted(struct snapraid_state* state, block_off_t /** * Check if a block position in a disk is deleted. */ -static int fs_is_block_deleted_ts(struct snapraid_disk* disk, struct snapraid_chunk** fs_last, block_off_t pos) +static int fs_is_block_deleted(struct snapraid_disk* disk, block_off_t pos) { - struct snapraid_block* block = fs_par2block_get_ts(disk, fs_last, pos); + struct snapraid_block* block = fs_par2block_get(disk, pos); return block_state_get(block) == BLOCK_STATE_DELETED; } @@ -2735,14 +2735,6 @@ static void* state_write_thread(void* arg) for (i = state->disklist; i != 0; i = i->next) { tommy_node* j; struct snapraid_disk* disk = i->data; - struct snapraid_chunk* fs_last; - - /** - * This is the last accessed chunk in the tree operations. - * Here we are inside a thread, and we cannot use the - * ::disk member to store this information. - */ - fs_last = 0; /* if the disk is not mapped, skip it */ if (disk->mapping_idx < 0) @@ -2783,7 +2775,7 @@ static void* state_write_thread(void* arg) begin = 0; while (begin < file->blockmax) { unsigned v_state = block_state_get(fs_file2block_get(file, begin)); - block_off_t v_pos = fs_file2par_get_ts(disk, &fs_last, file, begin); + block_off_t v_pos = fs_file2par_get(disk, file, begin); uint32_t v_count; block_off_t end; @@ -2793,7 +2785,7 @@ static void* state_write_thread(void* arg) while (end < file->blockmax) { if (v_state != block_state_get(fs_file2block_get(file, end))) break; - if (v_pos + (end - begin) != fs_file2par_get_ts(disk, &fs_last, file, end)) + if (v_pos + (end - begin) != fs_file2par_get(disk,file, end)) break; ++end; } @@ -2898,12 +2890,12 @@ static void* state_write_thread(void* arg) int is_deleted; block_off_t end; - is_deleted = fs_is_block_deleted_ts(disk, &fs_last, begin); + is_deleted = fs_is_block_deleted(disk, begin); /* find the end of run of blocks */ end = begin + 1; while (end < blockmax - && is_deleted == fs_is_block_deleted_ts(disk, &fs_last, end) + && is_deleted == fs_is_block_deleted(disk, end) ) { ++end; } @@ -2916,7 +2908,7 @@ static void* state_write_thread(void* arg) /* write all the hash */ while (begin < end) { - struct snapraid_block* block = fs_par2block_get_ts(disk, &fs_last, begin); + struct snapraid_block* block = fs_par2block_get(disk, begin); swrite(block->hash, HASH_SIZE, f); @@ -3108,7 +3100,7 @@ static void state_write_content(struct snapraid_state* state, uint32_t* out_crc) } /* save the mapping only for not empty disks */ - if (!disk_is_empty(disk, blockmax)) { + if (!fs_is_empty(disk, blockmax)) { /* assign the mapping index used to identify disks */ disk->mapping_idx = mapping_idx; ++mapping_idx; @@ -3976,8 +3968,7 @@ int state_progress(struct snapraid_state* state, block_off_t blockpos, block_off msg_bar("%u%%, %u MiB", out_perc, (unsigned)(countsize / MEGA)); if (out_speed) msg_bar(", %u MiB/s", out_speed); - if (out_cpu) - msg_bar(", CPU %u%%", out_cpu); + msg_bar(", CPU %u%%", out_cpu); if (out_eta) msg_bar(", %u:%02u ETA", out_eta / 60, out_eta % 60); msg_bar("%s\r", PROGRESS_CLEAR); diff --git a/cmdline/support.c b/cmdline/support.c index cc783c56..f29646a4 100644 --- a/cmdline/support.c +++ b/cmdline/support.c @@ -19,29 +19,67 @@ #include "support.h" +/****************************************************************************/ +/* lock */ + +/** + * Locks used externally. + */ +#if HAVE_PTHREAD +static pthread_mutex_t msg_lock; +static pthread_mutex_t memory_lock; +#endif + +void lock_msg(void) +{ +#if HAVE_PTHREAD + pthread_mutex_lock(&msg_lock); +#endif +} + +void unlock_msg(void) +{ +#if HAVE_PTHREAD + pthread_mutex_unlock(&msg_lock); +#endif +} + +void lock_memory(void) +{ +#if HAVE_PTHREAD + pthread_mutex_lock(&memory_lock); +#endif +} + +void unlock_memory(void) +{ +#if HAVE_PTHREAD + pthread_mutex_unlock(&memory_lock); +#endif +} + +void lock_init(void) +{ +#if HAVE_PTHREAD + /* initialize the locks as first operation as log_fatal depends on them */ + pthread_mutex_init(&msg_lock, 0); + pthread_mutex_init(&memory_lock, 0); +#endif +} + +void lock_done(void) +{ +#if HAVE_PTHREAD + pthread_mutex_destroy(&msg_lock); + pthread_mutex_destroy(&memory_lock); +#endif +} + /****************************************************************************/ /* print */ int msg_level = 0; -/** - * Mutex used for printf. - * - * In Windows printf() is not atomic, and multiple threads - * will have output interleaved. - * - * Note that even defining __USE_MINGW_ANSI_STDIO the problem persists. - * - * See for example: - * - * Weird output when I use pthread and printf. - * http://stackoverflow.com/questions/13190254/weird-output-when-i-use-pthread-and-printf - * - * This is also required in other OS because we split output in stdlog in - * two fprintf calls. - */ -static pthread_mutex_t msg_lock = PTHREAD_MUTEX_INITIALIZER; - /* * Note that in the following functions we always flush both * stdout and stderr, because we want to ensure that they mixes @@ -58,7 +96,7 @@ void log_fatal(const char* format, ...) { va_list ap; - pthread_mutex_lock(&msg_lock); + lock_msg(); if (stdlog) { va_start(ap, format); @@ -73,14 +111,14 @@ void log_fatal(const char* format, ...) fflush(stderr); va_end(ap); - pthread_mutex_unlock(&msg_lock); + unlock_msg(); } void log_error(const char* format, ...) { va_list ap; - pthread_mutex_lock(&msg_lock); + lock_msg(); if (stdlog) { va_start(ap, format); @@ -95,14 +133,14 @@ void log_error(const char* format, ...) va_end(ap); } - pthread_mutex_unlock(&msg_lock); + unlock_msg(); } void log_expected(const char* format, ...) { va_list ap; - pthread_mutex_lock(&msg_lock); + lock_msg(); if (stdlog) { va_start(ap, format); @@ -112,14 +150,14 @@ void log_expected(const char* format, ...) va_end(ap); } - pthread_mutex_unlock(&msg_lock); + unlock_msg(); } void log_tag(const char* format, ...) { va_list ap; - pthread_mutex_lock(&msg_lock); + lock_msg(); if (stdlog) { va_start(ap, format); @@ -129,26 +167,26 @@ void log_tag(const char* format, ...) va_end(ap); } - pthread_mutex_unlock(&msg_lock); + unlock_msg(); } void log_flush(void) { - pthread_mutex_lock(&msg_lock); + lock_msg(); if (stdlog) fflush(stdlog); fflush(stdout); fflush(stderr); - pthread_mutex_unlock(&msg_lock); + unlock_msg(); } void msg_status(const char* format, ...) { va_list ap; - pthread_mutex_lock(&msg_lock); + lock_msg(); if (stdlog) { va_start(ap, format); @@ -165,14 +203,14 @@ void msg_status(const char* format, ...) va_end(ap); } - pthread_mutex_unlock(&msg_lock); + unlock_msg(); } void msg_info(const char* format, ...) { va_list ap; - pthread_mutex_lock(&msg_lock); + lock_msg(); /* don't output in stdlog as these messages */ /* are always paired with a msg_tag() call */ @@ -184,14 +222,14 @@ void msg_info(const char* format, ...) va_end(ap); } - pthread_mutex_unlock(&msg_lock); + unlock_msg(); } void msg_progress(const char* format, ...) { va_list ap; - pthread_mutex_lock(&msg_lock); + lock_msg(); if (stdlog) { va_start(ap, format); @@ -208,14 +246,14 @@ void msg_progress(const char* format, ...) va_end(ap); } - pthread_mutex_unlock(&msg_lock); + unlock_msg(); } void msg_bar(const char* format, ...) { va_list ap; - pthread_mutex_lock(&msg_lock); + lock_msg(); /* don't output in stdlog as these messages */ /* are intended for screen only */ @@ -227,14 +265,14 @@ void msg_bar(const char* format, ...) va_end(ap); } - pthread_mutex_unlock(&msg_lock); + unlock_msg(); } void msg_verbose(const char* format, ...) { va_list ap; - pthread_mutex_lock(&msg_lock); + lock_msg(); if (stdlog) { va_start(ap, format); @@ -251,17 +289,17 @@ void msg_verbose(const char* format, ...) va_end(ap); } - pthread_mutex_unlock(&msg_lock); + unlock_msg(); } void msg_flush(void) { - pthread_mutex_lock(&msg_lock); + lock_msg(); fflush(stdout); fflush(stderr); - pthread_mutex_unlock(&msg_lock); + unlock_msg(); } /** @@ -607,35 +645,27 @@ int fmtime(int f, int64_t mtime_sec, int mtime_nsec) */ static size_t mcounter; -/** - * Mutex protection for the global memory counter. - */ -#if HAVE_PTHREAD -pthread_mutex_t mcounter_mutex = PTHREAD_MUTEX_INITIALIZER; -#endif size_t malloc_counter_get(void) { size_t ret; -#if HAVE_PTHREAD - pthread_mutex_lock(&mcounter_mutex); -#endif + + lock_memory(); + ret = mcounter; -#if HAVE_PTHREAD - pthread_mutex_unlock(&mcounter_mutex); -#endif + + unlock_memory(); + return ret; } void malloc_counter_inc(size_t inc) { -#if HAVE_PTHREAD - pthread_mutex_lock(&mcounter_mutex); -#endif + lock_memory(); + mcounter += inc; -#if HAVE_PTHREAD - pthread_mutex_unlock(&mcounter_mutex); -#endif + + unlock_memory(); } /* LCOV_EXCL_START */ diff --git a/cmdline/support.h b/cmdline/support.h index c15210c0..d7162304 100644 --- a/cmdline/support.h +++ b/cmdline/support.h @@ -18,6 +18,40 @@ #ifndef __SUPPORT_H #define __SUPPORT_H +/****************************************************************************/ +/* lock */ + +/** + * Initialize and destroy the locks. + */ +void lock_init(void); +void lock_done(void); + +/** + * Lock used for printf. + * + * In Windows printf() is not atomic, and multiple threads + * will have output interleaved. + * + * Note that even defining __USE_MINGW_ANSI_STDIO the problem persists. + * + * See for example: + * + * Weird output when I use pthread and printf. + * http://stackoverflow.com/questions/13190254/weird-output-when-i-use-pthread-and-printf + * + * This is also required in other OS because we split output in stdlog in + * two fprintf calls. + */ +void lock_msg(void); +void unlock_msg(void); + +/** + * Lock used for memory counter. + */ +void lock_memory(void); +void unlock_memory(void); + /****************************************************************************/ /* log */ diff --git a/cmdline/sync.c b/cmdline/sync.c index e0a2aafe..f3cb58fe 100644 --- a/cmdline/sync.c +++ b/cmdline/sync.c @@ -22,11 +22,20 @@ #include "state.h" #include "parity.h" #include "handle.h" +#include "io.h" #include "raid/raid.h" /****************************************************************************/ /* hash */ +/** + * Sync plan to use. + */ +struct snapraid_plan { + unsigned handle_max; + struct snapraid_handle* handle_map; +}; + static int state_hash_process(struct snapraid_state* state, block_off_t blockstart, block_off_t blockmax, int* skip_sync) { struct snapraid_handle* handle; @@ -393,6 +402,21 @@ struct failed_struct { struct snapraid_block* block; /**< The failed block, or BLOCK_DELETED for a deleted block */ }; +/** + * Comparison function for sorting by index. + */ +int failed_compare_by_index(const void* void_a, const void* void_b) +{ + const struct failed_struct* a = void_a; + const struct failed_struct* b = void_b; + + if (a->index < b->index) + return -1; + if (a->index > b->index) + return 1; + return 0; +} + /** * Buffer for storing the new hashes. */ @@ -404,8 +428,9 @@ struct snapraid_rehash { /** * Check if we have to process the specified block index ::i. */ -static int block_is_enabled(block_off_t i, struct snapraid_handle* handle, unsigned diskmax) +static int block_is_enabled(void* void_plan, block_off_t i) { + struct snapraid_plan* plan = void_plan; unsigned j; int one_invalid; int one_valid; @@ -413,14 +438,15 @@ static int block_is_enabled(block_off_t i, struct snapraid_handle* handle, unsig /* for each disk */ one_invalid = 0; one_valid = 0; - for (j = 0; j < diskmax; ++j) { + for (j = 0; j < plan->handle_max; ++j) { struct snapraid_block* block; + struct snapraid_disk* disk = plan->handle_map[j].disk; /* if no disk, nothing to check */ - if (!handle[j].disk) + if (!disk) continue; - block = fs_par2block_get(handle[j].disk, i); + block = fs_par2block_get(disk, i); if (block_has_file(block)) one_valid = 1; @@ -436,16 +462,206 @@ static int block_is_enabled(block_off_t i, struct snapraid_handle* handle, unsig return 1; } -static int state_sync_process(struct snapraid_state* state, struct snapraid_parity_handle** parity, block_off_t blockstart, block_off_t blockmax) +static void sync_data_reader(struct snapraid_worker* worker, struct snapraid_task* task) { + struct snapraid_io* io = worker->io; + struct snapraid_state* state = io->state; + struct snapraid_handle* handle = worker->handle; + struct snapraid_disk* disk = handle->disk; + block_off_t blockcur = task->position; + unsigned char* buffer = task->buffer; + int ret; + + /* if the disk position is not used */ + if (!disk) { + /* use an empty block */ + memset(buffer, 0, state->block_size); + task->state = TASK_STATE_DONE; + return; + } + + /* get the block */ + task->block = fs_par2block_get(disk, blockcur); + + /* if the block has no file, meanining that it's EMPTY or DELETED, */ + /* it doesn't partecipate in the new parity computation */ + if (!block_has_file(task->block)) { + /* use an empty block */ + memset(buffer, 0, state->block_size); + task->state = TASK_STATE_DONE; + return; + } + + /* get the file of this block */ + task->file = fs_par2file_get(disk, blockcur, &task->file_pos); + + /* if the file is different than the current one, close it */ + if (handle->file != 0 && handle->file != task->file) { + /* keep a pointer at the file we are going to close for error reporting */ + struct snapraid_file* report = handle->file; + ret = handle_close(handle); + if (ret == -1) { + /* LCOV_EXCL_START */ + /* This one is really an unexpected error, because we are only reading */ + /* and closing a descriptor should never fail */ + if (errno == EIO) { + log_tag("error:%u:%s:%s: Close EIO error. %s\n", blockcur, disk->name, esc(report->sub), strerror(errno)); + log_fatal("DANGER! Unexpected input/output close error in a data disk, it isn't possible to sync.\n"); + log_fatal("Ensure that disk '%s' is sane and that file '%s' can be accessed.\n", disk->dir, handle->path); + log_fatal("Stopping at block %u\n", blockcur); + task->state = TASK_STATE_IOERROR; + return; + } + + log_tag("error:%u:%s:%s: Close error. %s\n", blockcur, disk->name, esc(report->sub), strerror(errno)); + log_fatal("WARNING! Unexpected close error in a data disk, it isn't possible to sync.\n"); + log_fatal("Ensure that file '%s' can be accessed.\n", handle->path); + log_fatal("Stopping at block %u\n", blockcur); + task->state = TASK_STATE_ERROR; + return; + /* LCOV_EXCL_STOP */ + } + } + + ret = handle_open(handle, task->file, state->file_mode, log_error, 0); + if (ret == -1) { + if (errno == EIO) { + /* LCOV_EXCL_START */ + log_tag("error:%u:%s:%s: Open EIO error. %s\n", blockcur, disk->name, esc(task->file->sub), strerror(errno)); + log_fatal("DANGER! Unexpected input/output open error in a data disk, it isn't possible to sync.\n"); + log_fatal("Ensure that disk '%s' is sane and that file '%s' can be accessed.\n", disk->dir, handle->path); + log_fatal("Stopping at block %u\n", blockcur); + task->state = TASK_STATE_IOERROR; + return; + /* LCOV_EXCL_STOP */ + } + + if (errno == ENOENT) { + log_tag("error:%u:%s:%s: Open ENOENT error. %s\n", blockcur, disk->name, esc(task->file->sub), strerror(errno)); + log_error("Missing file '%s'.\n", handle->path); + log_error("WARNING! You cannot modify data disk during a sync.\n"); + log_error("Rerun the sync command when finished.\n"); + /* if the file is missing, it means that it was removed during sync */ + /* this isn't a serious error, so we skip this block, and continue with others */ + task->state = TASK_STATE_ERROR_CONTINUE; + return; + } + + if (errno == EACCES) { + log_tag("error:%u:%s:%s: Open EACCES error. %s\n", blockcur, disk->name, esc(task->file->sub), strerror(errno)); + log_error("No access at file '%s'.\n", handle->path); + log_error("WARNING! Please fix the access permission in the data disk.\n"); + log_error("Rerun the sync command when finished.\n"); + /* this isn't a serious error, so we skip this block, and continue with others */ + task->state = TASK_STATE_ERROR_CONTINUE; + return; + } + + /* LCOV_EXCL_START */ + log_tag("error:%u:%s:%s: Open error. %s\n", blockcur, disk->name, esc(task->file->sub), strerror(errno)); + log_fatal("WARNING! Unexpected open error in a data disk, it isn't possible to sync.\n"); + log_fatal("Ensure that file '%s' can be accessed.\n", handle->path); + log_fatal("Stopping to allow recovery. Try with 'snapraid check -f %s'\n", task->file->sub); + task->state = TASK_STATE_ERROR; + return; + /* LCOV_EXCL_STOP */ + } + + /* check if the file is changed */ + if (handle->st.st_size != task->file->size + || handle->st.st_mtime != task->file->mtime_sec + || STAT_NSEC(&handle->st) != task->file->mtime_nsec + || handle->st.st_ino != task->file->inode + ) { + log_tag("error:%u:%s:%s: Unexpected attribute change\n", blockcur, disk->name, esc(task->file->sub)); + if (handle->st.st_size != task->file->size) { + log_error("Unexpected size change at file '%s' from %" PRIu64 " to %" PRIu64 ".\n", handle->path, task->file->size, handle->st.st_size); + } else if (handle->st.st_mtime != task->file->mtime_sec + || STAT_NSEC(&handle->st) != task->file->mtime_nsec) { + log_error("Unexpected time change at file '%s' from %" PRIu64 ".%d to %" PRIu64 ".%d.\n", handle->path, task->file->mtime_sec, task->file->mtime_nsec, (uint64_t)handle->st.st_mtime, (uint32_t)STAT_NSEC(&handle->st)); + } else { + log_error("Unexpected inode change from %" PRIu64 " to %" PRIu64 " at file '%s'.\n", task->file->inode, (uint64_t)handle->st.st_ino, handle->path); + } + log_error("WARNING! You cannot modify files during a sync.\n"); + log_error("Rerun the sync command when finished.\n"); + /* 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 */ + task->state = TASK_STATE_ERROR_CONTINUE; + return; + } + + task->read_size = handle_read(handle, task->file_pos, buffer, state->block_size, log_error, 0); + if (task->read_size == -1) { + /* LCOV_EXCL_START */ + if (errno == EIO) { + log_tag("error:%u:%s:%s: Read EIO error at position %u. %s\n", blockcur, disk->name, esc(task->file->sub), task->file_pos, strerror(errno)); + log_error("Input/Output error in file '%s' at position '%u'\n", handle->path, task->file_pos); + task->state = TASK_STATE_IOERROR_CONTINUE; + return; + } + + log_tag("error:%u:%s:%s: Read error at position %u. %s\n", blockcur, disk->name, esc(task->file->sub), task->file_pos, strerror(errno)); + log_fatal("WARNING! Unexpected read error in a data disk, it isn't possible to sync.\n"); + log_fatal("Ensure that file '%s' can be read.\n", handle->path); + log_fatal("Stopping to allow recovery. Try with 'snapraid check -f %s'\n", task->file->sub); + task->state = TASK_STATE_ERROR; + return; + /* LCOV_EXCL_STOP */ + } + + /* store the path of the opened file */ + pathcpy(task->path, sizeof(task->path), handle->path); + + task->state = TASK_STATE_DONE; +} + +static void sync_parity_writer(struct snapraid_worker* worker, struct snapraid_task* task) +{ + struct snapraid_io* io = worker->io; + struct snapraid_state* state = io->state; + struct snapraid_parity_handle* parity_handle = worker->parity_handle; + unsigned level = parity_handle->level; + block_off_t blockcur = task->position; + unsigned char* buffer = task->buffer; + int ret; + + /* write parity */ + ret = parity_write(parity_handle, blockcur, buffer, state->block_size); + if (ret == -1) { + /* LCOV_EXCL_START */ + if (errno == EIO) { + log_tag("parity_error:%u:%s: Write EIO error. %s\n", blockcur, lev_config_name(level), strerror(errno)); + log_error("Input/Output error in parity '%s' at position '%u'\n", lev_config_name(level), blockcur); + task->state = TASK_STATE_IOERROR_CONTINUE; + return; + } + + log_tag("parity_error:%u:%s: Write error. %s\n", blockcur, lev_config_name(level), strerror(errno)); + log_fatal("WARNING! Unexpected write error in the %s disk, it isn't possible to sync.\n", lev_name(level)); + log_fatal("Ensure that disk '%s' has some free space available.\n", lev_config_name(level)); + log_fatal("Stopping at block %u\n", blockcur); + task->state = TASK_STATE_ERROR; + return; + /* LCOV_EXCL_STOP */ + } + + task->state = TASK_STATE_DONE; +} + +static int state_sync_process(struct snapraid_state* state, struct snapraid_parity_handle* parity_handle, block_off_t blockstart, block_off_t blockmax) +{ + struct snapraid_io io; + struct snapraid_plan plan; struct snapraid_handle* handle; void* rehandle_alloc; struct snapraid_rehash* rehandle; unsigned diskmax; - block_off_t i; + block_off_t blockcur; unsigned j; - void* buffer_alloc; - void** buffer; + void* zero_alloc; + void** zero; + void* copy_alloc; + void** copy; unsigned buffermax; data_off_t countsize; block_off_t countpos; @@ -475,16 +691,19 @@ static int state_sync_process(struct snapraid_state* state, struct snapraid_pari /* rehash buffers */ rehandle = malloc_nofail_align(diskmax * sizeof(struct snapraid_rehash), &rehandle_alloc); - /* we need 2 * data + 1 * parity + 1 * zero */ - buffermax = 2 * diskmax + state->level + 1; + /* we need 1 * data + 1 * parity */ + buffermax = diskmax + state->level; - buffer = malloc_nofail_vector_align(diskmax, buffermax, state->block_size, &buffer_alloc); - if (!state->opt.skip_self) - mtest_vector(buffermax, state->block_size, buffer); + /* initialize the io threads */ + io_init(&io, state, buffermax, sync_data_reader, handle, diskmax, 0, sync_parity_writer, parity_handle, state->level); - /* fill up the zero buffer */ - memset(buffer[buffermax - 1], 0, state->block_size); - raid_zero(buffer[buffermax - 1]); + /* allocate the copy buffer */ + copy = malloc_nofail_vector_align(diskmax, diskmax, state->block_size, ©_alloc); + + /* allocate and fill the zero buffer */ + zero = malloc_nofail_align(state->block_size, &zero_alloc); + memset(zero, 0, state->block_size); + raid_zero(zero); failed = malloc_nofail(diskmax * sizeof(struct failed_struct)); failed_map = malloc_nofail(diskmax * sizeof(unsigned)); @@ -495,8 +714,10 @@ static int state_sync_process(struct snapraid_state* state, struct snapraid_pari /* first count the number of blocks to process */ countmax = 0; - for (i = blockstart; i < blockmax; ++i) { - if (!block_is_enabled(i, handle, diskmax)) + plan.handle_max = diskmax; + plan.handle_map = handle; + for (blockcur = blockstart; blockcur < blockmax; ++blockcur) { + if (!block_is_enabled(&plan, blockcur)) continue; ++countmax; } @@ -513,27 +734,36 @@ static int state_sync_process(struct snapraid_state* state, struct snapraid_pari countsize = 0; countpos = 0; + + /* start all the worker threads */ + io_start(&io, blockstart, blockmax, &block_is_enabled, &plan); + if (!state_progress_begin(state, blockstart, blockmax, countmax)) goto end; - for (i = blockstart; i < blockmax; ++i) { + while (1) { unsigned failed_count; int error_on_this_block; int silent_error_on_this_block; int io_error_on_this_block; int fixed_error_on_this_block; int parity_needs_to_be_updated; + int parity_going_to_be_updated; snapraid_info info; int rehash; + void** buffer; + int writer_error[IO_WRITER_ERROR_MAX]; - if (!block_is_enabled(i, handle, diskmax)) - continue; + /* go to the next block */ + blockcur = io_read_next(&io, &buffer); + if (blockcur >= blockmax) + break; /* one more block processed for autosave */ ++autosavedone; --autosavemissing; - /* by default process the block, and skip it if something go wrong */ + /* by default process the block, and skip it if something goes wrong */ error_on_this_block = 0; silent_error_on_this_block = 0; io_error_on_this_block = 0; @@ -543,19 +773,23 @@ static int state_sync_process(struct snapraid_state* state, struct snapraid_pari failed_count = 0; /* get block specific info */ - info = info_get(&state->infoarr, i); + info = info_get(&state->infoarr, blockcur); /* if we have to use the old hash */ rehash = info_get_rehash(info); - /* it could happens that all the blocks are EMPTY/BLK and CHG but with the hash */ + /* if the parity requires to be updated */ + /* It could happens that all the blocks are EMPTY/BLK and CHG but with the hash */ /* still matching because the specific CHG block was not modified. */ + /* In such case, we can avoid to update parity, because it would be the same as before */ /* Note that CHG/DELETED blocks already present in the content file loaded */ /* have the hash cleared (::clear_past_hash flag), and then they won't never match the hash. */ /* We are treating only CHG blocks created at runtime. */ - /* In such case, we can avoid to update parity, because it would be the same as before */ parity_needs_to_be_updated = state->opt.force_parity_update; + /* if the parity is going to be updated */ + parity_going_to_be_updated = 0; + /* if the block is marked as bad, we force the parity update */ /* because the bad block may be the result of a wrong parity */ if (info_get_bad(info)) @@ -563,26 +797,37 @@ static int state_sync_process(struct snapraid_state* state, struct snapraid_pari /* for each disk, process the block */ for (j = 0; j < diskmax; ++j) { + struct snapraid_task* task; int read_size; unsigned char hash[HASH_SIZE]; struct snapraid_block* block; unsigned block_state; - struct snapraid_disk* disk = handle[j].disk; + struct snapraid_disk* disk; struct snapraid_file* file; block_off_t file_pos; + unsigned diskcur; + + /* until now is CPU */ + state_usage_cpu(state); + + task = io_data_read(&io, &diskcur); + + /* get the results */ + disk = task->disk; + block = task->block; + file = task->file; + file_pos = task->file_pos; + read_size = task->read_size; /* by default no rehash in case of "continue" */ - rehandle[j].block = 0; + rehandle[diskcur].block = 0; /* if the disk position is not used */ - if (!disk) { - /* use an empty block */ - memset(buffer[j], 0, state->block_size); + if (!disk) continue; - } - /* get the block */ - block = fs_par2block_get(disk, i); + /* until now is disk */ + state_usage_disk(state, disk); /* get the state of the block */ block_state = block_state_get(block); @@ -592,7 +837,7 @@ static int state_sync_process(struct snapraid_state* state, struct snapraid_pari if (block_has_invalid_parity(block)) { /* store it in the failed set, because */ /* the parity may be still computed with the previous content */ - failed[failed_count].index = j; + failed[failed_count].index = diskcur; failed[failed_count].size = state->block_size; failed[failed_count].block = block; ++failed_count; @@ -611,160 +856,55 @@ static int state_sync_process(struct snapraid_state* state, struct snapraid_pari /* follow */ } - /* if the block has no file, meanining that it's EMPTY or DELETED, */ - /* it doesn't partecipate in the new parity computation */ - if (!block_has_file(block)) { - /* use an empty block */ - memset(buffer[j], 0, state->block_size); + /* if the block is not used */ + if (!block_has_file(block)) continue; + + /* handle error conditions */ + if (task->state == TASK_STATE_IOERROR) { + ++io_error; + goto bail; } - - /* get the file of this block */ - file = fs_par2file_get(disk, i, &file_pos); - - /* until now is CPU */ - state_usage_cpu(state); - - /* if the file is different than the current one, close it */ - if (handle[j].file != 0 && handle[j].file != file) { - /* keep a pointer at the file we are going to close for error reporting */ - struct snapraid_file* report = handle[j].file; - ret = handle_close(&handle[j]); - if (ret == -1) { - /* LCOV_EXCL_START */ - /* This one is really an unexpected error, because we are only reading */ - /* and closing a descriptor should never fail */ - if (errno == EIO) { - log_tag("error:%u:%s:%s: Close EIO error. %s\n", i, disk->name, esc(report->sub), strerror(errno)); - log_fatal("DANGER! Unexpected input/output close error in a data disk, it isn't possible to sync.\n"); - log_fatal("Ensure that disk '%s' is sane and that file '%s' can be accessed.\n", disk->dir, handle[j].path); - log_fatal("Stopping at block %u\n", i); - ++io_error; - goto bail; - } - - log_tag("error:%u:%s:%s: Close error. %s\n", i, disk->name, esc(report->sub), strerror(errno)); - log_fatal("WARNING! Unexpected close error in a data disk, it isn't possible to sync.\n"); - log_fatal("Ensure that file '%s' can be accessed.\n", handle[j].path); - log_fatal("Stopping at block %u\n", i); - ++error; - goto bail; - /* LCOV_EXCL_STOP */ - } - } - - ret = handle_open(&handle[j], file, state->file_mode, log_error, 0); - if (ret == -1) { - if (errno == EIO) { - /* LCOV_EXCL_START */ - log_tag("error:%u:%s:%s: Open EIO error. %s\n", i, disk->name, esc(file->sub), strerror(errno)); - log_fatal("DANGER! Unexpected input/output open error in a data disk, it isn't possible to sync.\n"); - log_fatal("Ensure that disk '%s' is sane and that file '%s' can be accessed.\n", disk->dir, handle[j].path); - log_fatal("Stopping at block %u\n", i); - ++io_error; - goto bail; - /* LCOV_EXCL_STOP */ - } - - if (errno == ENOENT) { - log_tag("error:%u:%s:%s: Open ENOENT error. %s\n", i, disk->name, esc(file->sub), strerror(errno)); - log_error("Missing file '%s'.\n", handle[j].path); - log_error("WARNING! You cannot modify data disk during a sync.\n"); - log_error("Rerun the sync command when finished.\n"); - ++error; - /* if the file is missing, it means that it was removed during sync */ - /* this isn't a serious error, so we skip this block, and continue with others */ - error_on_this_block = 1; - continue; - } - - if (errno == EACCES) { - log_tag("error:%u:%s:%s: Open EACCES error. %s\n", i, disk->name, esc(file->sub), strerror(errno)); - log_error("No access at file '%s'.\n", handle[j].path); - log_error("WARNING! Please fix the access permission in the data disk.\n"); - log_error("Rerun the sync command when finished.\n"); - ++error; - /* this isn't a serious error, so we skip this block, and continue with others */ - error_on_this_block = 1; - continue; - } - - /* LCOV_EXCL_START */ - log_tag("error:%u:%s:%s: Open error. %s\n", i, disk->name, esc(file->sub), strerror(errno)); - log_fatal("WARNING! Unexpected open error in a data disk, it isn't possible to sync.\n"); - log_fatal("Ensure that file '%s' can be accessed.\n", handle[j].path); - log_fatal("Stopping to allow recovery. Try with 'snapraid check -f %s'\n", file->sub); + if (task->state == TASK_STATE_ERROR) { ++error; goto bail; - /* LCOV_EXCL_STOP */ } - - /* check if the file is changed */ - if (handle[j].st.st_size != file->size - || handle[j].st.st_mtime != file->mtime_sec - || STAT_NSEC(&handle[j].st) != file->mtime_nsec - || handle[j].st.st_ino != file->inode - ) { - log_tag("error:%u:%s:%s: Unexpected attribute change\n", i, disk->name, esc(file->sub)); - if (handle[j].st.st_size != file->size) { - log_error("Unexpected size change at file '%s' from %" PRIu64 " to %" PRIu64 ".\n", handle[j].path, file->size, handle[j].st.st_size); - } else if (handle[j].st.st_mtime != file->mtime_sec - || STAT_NSEC(&handle[j].st) != file->mtime_nsec) { - log_error("Unexpected time change at file '%s' from %" PRIu64 ".%d to %" PRIu64 ".%d.\n", handle[j].path, file->mtime_sec, file->mtime_nsec, (uint64_t)handle[j].st.st_mtime, (uint32_t)STAT_NSEC(&handle[j].st)); - } else { - log_error("Unexpected inode change from %" PRIu64 " to %" PRIu64 " at file '%s'.\n", file->inode, (uint64_t)handle[j].st.st_ino, handle[j].path); - } - log_error("WARNING! You cannot modify files during a sync.\n"); - log_error("Rerun the sync command when finished.\n"); + if (task->state == TASK_STATE_ERROR_CONTINUE) { ++error; - /* 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; } - - read_size = handle_read(&handle[j], file_pos, buffer[j], state->block_size, log_error, 0); - if (read_size == -1) { - /* LCOV_EXCL_START */ - if (errno == EIO) { - log_tag("error:%u:%s:%s: Read EIO error at position %u. %s\n", i, disk->name, esc(file->sub), file_pos, strerror(errno)); - if (io_error >= state->opt.io_error_limit) { - log_fatal("DANGER! Unexpected input/output read error in a data disk, it isn't possible to sync.\n"); - log_fatal("Ensure that disk '%s' is sane and that file '%s' can be read.\n", disk->dir, handle[j].path); - log_fatal("Stopping at block %u\n", i); - ++io_error; - goto bail; - } - - log_error("Input/Output error in file '%s' at position '%u'\n", handle[j].path, file_pos); - ++io_error; - io_error_on_this_block = 1; - continue; + if (task->state == TASK_STATE_IOERROR_CONTINUE) { + ++io_error; + if (io_error >= state->opt.io_error_limit) { + log_fatal("DANGER! Unexpected input/output read error in a data disk, it isn't possible to sync.\n"); + log_fatal("Ensure that disk '%s' is sane and that file '%s' can be read.\n", disk->dir, task->path); + log_fatal("Stopping at block %u\n", blockcur); + goto bail; } - log_tag("error:%u:%s:%s: Read error at position %u. %s\n", i, disk->name, esc(file->sub), file_pos, strerror(errno)); - log_fatal("WARNING! Unexpected read error in a data disk, it isn't possible to sync.\n"); - log_fatal("Ensure that file '%s' can be read.\n", handle[j].path); - log_fatal("Stopping to allow recovery. Try with 'snapraid check -f %s'\n", file->sub); - ++error; - goto bail; + /* otherwise continue */ + io_error_on_this_block = 1; + continue; + } + if (task->state != TASK_STATE_DONE) { + /* LCOV_EXCL_START */ + log_fatal("Internal inconsistency in task state\n"); + os_abort(); /* LCOV_EXCL_STOP */ } - /* until now is disk */ - state_usage_disk(state, disk); - countsize += read_size; /* now compute the hash */ if (rehash) { - memhash(state->prevhash, state->prevhashseed, hash, buffer[j], read_size); + memhash(state->prevhash, state->prevhashseed, hash, buffer[diskcur], read_size); /* compute the new hash, and store it */ - rehandle[j].block = block; - memhash(state->hash, state->hashseed, rehandle[j].hash, buffer[j], read_size); + rehandle[diskcur].block = block; + memhash(state->hash, state->hashseed, rehandle[diskcur].hash, buffer[diskcur], read_size); } else { - memhash(state->hash, state->hashseed, hash, buffer[j], read_size); + memhash(state->hash, state->hashseed, hash, buffer[diskcur], read_size); } if (block_has_updated_hash(block)) { @@ -772,8 +912,8 @@ static int state_sync_process(struct snapraid_state* state, struct snapraid_pari if (memcmp(hash, block->hash, HASH_SIZE) != 0) { /* if the file has invalid parity, it's a REP changed during the sync */ if (block_has_invalid_parity(block)) { - log_tag("error:%u:%s:%s: Unexpected data change\n", i, disk->name, esc(file->sub)); - log_error("Data change at file '%s' at position '%u'\n", handle[j].path, file_pos); + log_tag("error:%u:%s:%s: Unexpected data change\n", blockcur, disk->name, esc(file->sub)); + log_error("Data change at file '%s' at position '%u'\n", task->path, file_pos); log_error("WARNING! Unexpected data modification of a file without parity!\n"); if (file_flag_has(file, FILE_IS_COPY)) { @@ -793,11 +933,11 @@ static int state_sync_process(struct snapraid_state* state, struct snapraid_pari continue; } else { /* otherwise it's a BLK with silent error */ unsigned diff = memdiff(hash, block->hash, HASH_SIZE); - log_tag("error:%u:%s:%s: Data error at position %u, diff bits %u\n", i, disk->name, esc(file->sub), file_pos, diff); - log_error("Data error in file '%s' at position '%u', diff bits %u\n", handle[j].path, file_pos, diff); + log_tag("error:%u:%s:%s: Data error at position %u, diff bits %u\n", blockcur, disk->name, esc(file->sub), file_pos, diff); + log_error("Data error in file '%s' at position '%u', diff bits %u\n", task->path, file_pos, diff); /* save the failed block for the fix */ - failed[failed_count].index = j; + failed[failed_count].index = diskcur; failed[failed_count].size = read_size; failed[failed_count].block = block; ++failed_count; @@ -847,11 +987,16 @@ static int state_sync_process(struct snapraid_state* state, struct snapraid_pari unsigned failed_mac; int something_to_recover = 0; + /* sort the failed vector */ + /* because with threads it may be in any order */ + /* but RAID requires the indexes to be sorted */ + qsort(failed, failed_count, sizeof(failed[0]), failed_compare_by_index); + /* setup the blocks to recover */ failed_mac = 0; for (j = 0; j < failed_count; ++j) { unsigned char* block_buffer = buffer[failed[j].index]; - unsigned char* block_copy = buffer[diskmax + state->level + failed[j].index]; + unsigned char* block_copy = copy[failed[j].index]; unsigned block_state = block_state_get(failed[j].block); /* we try to recover only if at least one BLK is present */ @@ -887,29 +1032,29 @@ static int state_sync_process(struct snapraid_state* state, struct snapraid_pari /* we are sure that parity exists because */ /* we have at least one BLK block */ for (l = 0; l < state->level; ++l) { - ret = parity_read(parity[l], i, buffer[diskmax + l], state->block_size, log_error); + ret = parity_read(&parity_handle[l], blockcur, buffer[diskmax + l], state->block_size, log_error); if (ret == -1) { /* LCOV_EXCL_START */ if (errno == EIO) { - log_tag("parity_error:%u:%s: Read EIO error. %s\n", i, lev_config_name(l), strerror(errno)); + log_tag("parity_error:%u:%s: Read EIO error. %s\n", blockcur, lev_config_name(l), strerror(errno)); if (io_error >= state->opt.io_error_limit) { log_fatal("DANGER! Unexpected input/output read error in the %s disk, it isn't possible to sync.\n", lev_name(l)); log_fatal("Ensure that disk '%s' is sane and can be read.\n", lev_config_name(l)); - log_fatal("Stopping at block %u\n", i); + log_fatal("Stopping at block %u\n", blockcur); ++io_error; goto bail; } - log_error("Input/Output error in parity '%s' at position '%u'\n", lev_config_name(l), i); + log_error("Input/Output error in parity '%s' at position '%u'\n", lev_config_name(l), blockcur); ++io_error; io_error_on_this_block = 1; continue; } - log_tag("parity_error:%u:%s: Read error. %s\n", i, lev_config_name(l), strerror(errno)); + log_tag("parity_error:%u:%s: Read error. %s\n", blockcur, lev_config_name(l), strerror(errno)); log_fatal("WARNING! Unexpected read error in the %s disk, it isn't possible to sync.\n", lev_name(l)); log_fatal("Ensure that disk '%s' can be read.\n", lev_config_name(l)); - log_fatal("Stopping at block %u\n", i); + log_fatal("Stopping at block %u\n", blockcur); ++error; goto bail; /* LCOV_EXCL_STOP */ @@ -931,7 +1076,7 @@ static int state_sync_process(struct snapraid_state* state, struct snapraid_pari for (j = 0; j < failed_count; ++j) { unsigned char hash[HASH_SIZE]; unsigned char* block_buffer = buffer[failed[j].index]; - unsigned char* block_copy = buffer[diskmax + state->level + failed[j].index]; + unsigned char* block_copy = copy[failed[j].index]; unsigned block_state = block_state_get(failed[j].block); if (block_state == BLOCK_STATE_BLK) { @@ -977,70 +1122,33 @@ static int state_sync_process(struct snapraid_state* state, struct snapraid_pari /* compute the parity */ raid_gen(diskmax, state->level, state->block_size, buffer); - /* until now is CPU */ - state_usage_cpu(state); - - /* write the parity */ - for (l = 0; l < state->level; ++l) { - ret = parity_write(parity[l], i, buffer[diskmax + l], state->block_size); - if (ret == -1) { - /* LCOV_EXCL_START */ - if (errno == EIO) { - log_tag("parity_error:%u:%s: Write EIO error. %s\n", i, lev_config_name(l), strerror(errno)); - if (io_error >= state->opt.io_error_limit) { - log_fatal("DANGER! Unexpected input/output write error in the %s disk, it isn't possible to sync.\n", lev_name(l)); - log_fatal("Ensure that disk '%s' is sane and can be written.\n", lev_config_name(l)); - log_fatal("Stopping at block %u\n", i); - ++io_error; - goto bail; - } - - log_error("Input/Output error in parity '%s' at position '%u'\n", lev_config_name(l), i); - ++io_error; - io_error_on_this_block = 1; - continue; - } - - log_tag("parity_error:%u:%s: Write error. %s\n", i, lev_config_name(l), strerror(errno)); - log_fatal("WARNING! Unexpected write error in the %s disk, it isn't possible to sync.\n", lev_name(l)); - log_fatal("Ensure that disk '%s' has some free space available.\n", lev_config_name(l)); - log_fatal("Stopping at block %u\n", i); - ++error; - goto bail; - /* LCOV_EXCL_STOP */ - } - - /* until now is parity */ - state_usage_parity(state, l); - } + /* mark that the parity is going to be written */ + parity_going_to_be_updated = 1; } - /* if no error in parity write */ - if (!io_error_on_this_block) { - /* for each disk, mark the blocks as processed */ - for (j = 0; j < diskmax; ++j) { - struct snapraid_block* block; + /* for each disk, mark the blocks as processed */ + for (j = 0; j < diskmax; ++j) { + struct snapraid_block* block; - if (!handle[j].disk) - continue; + if (!handle[j].disk) + continue; - block = fs_par2block_get(handle[j].disk, i); + block = fs_par2block_get(handle[j].disk, blockcur); - if (block == BLOCK_EMPTY) { - /* nothing to do */ - continue; - } - - /* if it's a deleted block */ - if (block_state_get(block) == BLOCK_STATE_DELETED) { - /* the parity is now updated without this block, so it's now empty */ - fs_deallocate(handle[j].disk, i); - continue; - } - - /* now all the blocks have the hash and the parity computed */ - block_state_set(block, BLOCK_STATE_BLK); + if (block == BLOCK_EMPTY) { + /* nothing to do */ + continue; } + + /* if it's a deleted block */ + if (block_state_get(block) == BLOCK_STATE_DELETED) { + /* the parity is now updated without this block, so it's now empty */ + fs_deallocate(handle[j].disk, blockcur); + continue; + } + + /* now all the blocks have the hash and the parity computed */ + block_state_set(block, BLOCK_STATE_BLK); } /* we update the info block only if we really have updated the parity */ @@ -1050,7 +1158,6 @@ static int state_sync_process(struct snapraid_state* state, struct snapraid_pari /* because has no sense to refresh the time for data that we know bad */ if (parity_needs_to_be_updated && !silent_error_on_this_block - && !io_error_on_this_block ) { /* if rehash is neeed */ if (rehash) { @@ -1063,7 +1170,7 @@ static int state_sync_process(struct snapraid_state* state, struct snapraid_pari /* update the time info of the block */ /* we are also clearing any previous bad and rehash flag */ - info_set(&state->infoarr, i, info_make(now, 0, 0, 1)); + info_set(&state->infoarr, blockcur, info_make(now, 0, 0, 1)); } } @@ -1072,7 +1179,51 @@ static int state_sync_process(struct snapraid_state* state, struct snapraid_pari /* because our correction is in memory only and not yet written */ if (silent_error_on_this_block || io_error_on_this_block) { /* set the error status keeping the other info */ - info_set(&state->infoarr, i, info_set_bad(info)); + info_set(&state->infoarr, blockcur, info_set_bad(info)); + } + + /* finally schedule parity write */ + /* Note that the calls to io_parity_write() are mandatory */ + /* even if the parity doesn't need to be updated */ + /* This because we want to keep track of the time usage */ + state_usage_cpu(state); + + /* write the parity */ + for (l = 0; l < state->level; ++l) { + unsigned levcur; + + io_parity_write(&io, &levcur); + + /* until now is parity */ + state_usage_parity(state, levcur); + } + + /* write finished */ + io_write_next(&io, blockcur, !parity_going_to_be_updated, writer_error); + + /* handle errors reported */ + for (j = 0; j < IO_WRITER_ERROR_MAX; ++j) { + if (writer_error[j]) { + switch (j + IO_WRITER_ERROR_BASE) { + case TASK_STATE_IOERROR_CONTINUE : + ++io_error; + if (io_error >= state->opt.io_error_limit) { + log_fatal("DANGER! Unexpected input/output write error in a parity disk, it isn't possible to sync.\n"); + log_fatal("Stopping at block %u\n", blockcur); + goto bail; + } + break; + case TASK_STATE_ERROR_CONTINUE : + ++error; + break; + case TASK_STATE_IOERROR : + ++io_error; + goto bail; + case TASK_STATE_ERROR : + ++error; + goto bail; + } + } } /* mark the state as needing write */ @@ -1082,7 +1233,7 @@ static int state_sync_process(struct snapraid_state* state, struct snapraid_pari ++countpos; /* progress */ - if (state_progress(state, i, countpos, countmax, countsize)) { + if (state_progress(state, blockcur, countpos, countmax, countsize)) { /* LCOV_EXCL_START */ break; /* LCOV_EXCL_STOP */ @@ -1093,7 +1244,7 @@ static int state_sync_process(struct snapraid_state* state, struct snapraid_pari && autosavedone >= autosavelimit /* if we have reached the limit */ && autosavemissing >= autosavelimit) /* if we have at least a full step to do */ /* or if we have a forced autosave at the specified block */ - || (state->opt.force_autosave_at != 0 && state->opt.force_autosave_at == i) + || (state->opt.force_autosave_at != 0 && state->opt.force_autosave_at == blockcur) ) { autosavedone = 0; /* restart the counter */ @@ -1107,13 +1258,13 @@ static int state_sync_process(struct snapraid_state* state, struct snapraid_pari /* before writing the new content file we ensure that */ /* the parity is really written flushing the disk cache */ for (l = 0; l < state->level; ++l) { - ret = parity_sync(parity[l]); + ret = parity_sync(&parity_handle[l]); if (ret == -1) { /* LCOV_EXCL_START */ - log_tag("parity_error:%u:%s: Sync error\n", i, lev_config_name(l)); + log_tag("parity_error:%u:%s: Sync error\n", blockcur, lev_config_name(l)); log_fatal("DANGER! Unexpected sync error in %s disk.\n", lev_name(l)); log_fatal("Ensure that disk '%s' is sane.\n", lev_config_name(l)); - log_fatal("Stopping at block %u\n", i); + log_fatal("Stopping at block %u\n", blockcur); ++error; goto bail; /* LCOV_EXCL_STOP */ @@ -1138,13 +1289,13 @@ end: /* before returning we ensure that */ /* the parity is really written flushing the disk cache */ for (l = 0; l < state->level; ++l) { - ret = parity_sync(parity[l]); + ret = parity_sync(&parity_handle[l]); if (ret == -1) { /* LCOV_EXCL_START */ - log_tag("parity_error:%u:%s: Sync error\n", i, lev_config_name(l)); + log_tag("parity_error:%u:%s: Sync error\n", blockcur, lev_config_name(l)); log_fatal("DANGER! Unexpected sync error in %s disk.\n", lev_name(l)); log_fatal("Ensure that disk '%s' is sane.\n", lev_config_name(l)); - log_fatal("Stopping at block %u\n", i); + log_fatal("Stopping at block %u\n", blockcur); ++error; goto bail; /* LCOV_EXCL_STOP */ @@ -1183,13 +1334,16 @@ end: log_flush(); bail: + /* stop all the worker threads */ + io_stop(&io); + for (j = 0; j < diskmax; ++j) { struct snapraid_file* file = handle[j].file; struct snapraid_disk* disk = handle[j].disk; ret = handle_close(&handle[j]); if (ret == -1) { /* LCOV_EXCL_START */ - log_tag("error:%u:%s:%s: Close error. %s\n", i, disk->name, esc(file->sub), strerror(errno)); + log_tag("error:%u:%s:%s: Close error. %s\n", blockcur, disk->name, esc(file->sub), strerror(errno)); log_fatal("DANGER! Unexpected close error in a data disk.\n"); ++error; /* continue, as we are already exiting */ @@ -1198,11 +1352,13 @@ bail: } free(handle); - free(buffer_alloc); - free(buffer); + free(zero_alloc); + free(copy_alloc); + free(copy); free(rehandle_alloc); free(failed); free(failed_map); + io_done(&io); if (state->opt.expect_recoverable) { if (error + silent_error + io_error == 0) @@ -1222,10 +1378,7 @@ int state_sync(struct snapraid_state* state, block_off_t blockstart, block_off_t data_off_t size; data_off_t out_size; int ret; - struct snapraid_parity_handle parity[LEV_MAX]; - /* the following initialization is to avoid clang warnings about */ - /* potential state->level change, that never happens */ - struct snapraid_parity_handle* parity_ptr[LEV_MAX] = { 0 }; + struct snapraid_parity_handle parity_handle[LEV_MAX]; unsigned unrecoverable_error; unsigned l; int skip_sync = 0; @@ -1257,8 +1410,7 @@ int state_sync(struct snapraid_state* state, block_off_t blockstart, block_off_t block_off_t parityblocks; /* create the file and open for writing */ - parity_ptr[l] = &parity[l]; - ret = parity_create(parity_ptr[l], state->parity[l].path, &out_size, state->file_mode); + ret = parity_create(&parity_handle[l], l, state->parity[l].path, &out_size, state->file_mode); if (ret == -1) { /* LCOV_EXCL_START */ log_fatal("WARNING! Without an accessible %s file, it isn't possible to sync.\n", lev_name(l)); @@ -1304,7 +1456,7 @@ int state_sync(struct snapraid_state* state, block_off_t blockstart, block_off_t /* change the size of the parity file, truncating or extending it */ /* from this point all the DELETED blocks after the end of the parity are invalid */ /* and they are automatically removed when we save the new content file */ - ret = parity_chsize(parity_ptr[l], size, &out_size, state->opt.skip_fallocate); + ret = parity_chsize(&parity_handle[l], size, &out_size, state->opt.skip_fallocate); if (ret == -1) { /* LCOV_EXCL_START */ parity_overflow(state, out_size); @@ -1340,7 +1492,7 @@ int state_sync(struct snapraid_state* state, block_off_t blockstart, block_off_t /* skip degenerated cases of empty parity, or skipping all */ if (blockstart < blockmax) { - ret = state_sync_process(state, parity_ptr, blockstart, blockmax); + ret = state_sync_process(state, parity_handle, blockstart, blockmax); if (ret == -1) { /* LCOV_EXCL_START */ ++unrecoverable_error; @@ -1353,7 +1505,7 @@ int state_sync(struct snapraid_state* state, block_off_t blockstart, block_off_t } for (l = 0; l < state->level; ++l) { - ret = parity_close(parity_ptr[l]); + ret = parity_close(&parity_handle[l]); if (ret == -1) { /* LCOV_EXCL_START */ log_fatal("DANGER! Unexpected close error in %s disk.\n", lev_name(l)); diff --git a/cmdline/unix.c b/cmdline/unix.c index 6f9d1f28..9be8c232 100644 --- a/cmdline/unix.c +++ b/cmdline/unix.c @@ -297,7 +297,10 @@ int filephy(const char* path, uint64_t size, uint64_t* physical) int fsinfo(const char* path, int* has_persistent_inode, uint64_t* total_space, uint64_t* free_space) { -#if HAVE_STATFS && HAVE_STRUCT_STATFS_F_TYPE + char type[64]; + const char* ptype; + +#if HAVE_STATFS struct statfs st; if (statfs(path, &st) != 0) { @@ -325,9 +328,11 @@ int fsinfo(const char* path, int* has_persistent_inode, uint64_t* total_space, u if (statfs(dir, &st) != 0) return -1; } +#endif /* to get the fs type check "man stat" or "stat -f -t FILE" */ - if (has_persistent_inode) + if (has_persistent_inode) { +#if HAVE_STATFS && HAVE_STRUCT_STATFS_F_TYPE switch (st.f_type) { case 0x65735546 : /* FUSE, "fuseblk" in the stat command */ case 0x4d44 : /* VFAT, "msdos" in the stat command */ @@ -338,22 +343,58 @@ int fsinfo(const char* path, int* has_persistent_inode, uint64_t* total_space, u *has_persistent_inode = 1; break; } +#else + /* by default assume yes */ + *has_persistent_inode = 1; +#endif + } - if (total_space) + if (total_space) { +#if HAVE_STATFS *total_space = st.f_bsize * (uint64_t)st.f_blocks; - if (free_space) +#else + *total_space = 0; +#endif + } + + if (free_space) { +#if HAVE_STATFS *free_space = st.f_bsize * (uint64_t)st.f_bfree; #else - (void)path; - - /* by default assume yes */ - if (has_persistent_inode) - *has_persistent_inode = 1; - if (total_space) - *total_space = 0; - if (free_space) *free_space = 0; #endif + } + +#if HAVE_STATFS && HAVE_STRUCT_STATFS_F_FSTYPENAME + /* get the filesystem type directly from the struct (Mac OS X) */ + (void)type; + ptype = st.f_fstypename; +#elif HAVE_STATFS && HAVE_STRUCT_STATFS_F_TYPE + /* get the filesystem type from f_type (Linux) */ + /* from: https://github.com/influxdata/gopsutil/blob/master/disk/disk_linux.go */ + switch (st.f_type) { + case 0x65735546 : ptype = "fuseblk"; break; + case 0x4D44 : ptype = "vfat/msdos"; break; + case 0xEF53 : ptype = "ext2/3/4"; break; + case 0x6969 : ptype = "nfs"; break; /* remote */ + case 0x6E667364 : ptype = "nfsd"; break; /* remote */ + case 0x517B : ptype = "smb"; break; /* remote */ + case 0x5346544E : ptype = "ntfs"; break; + case 0x52654973 : ptype = "reiserfs"; break; + case 0x3153464A : ptype = "jfs" ; break; + case 0x58465342 : ptype = "xfs"; break; + case 0x9123683E : ptype = "btrfs"; break; + case 0x2FC12FC1 : ptype = "zfs"; break; + default: + snprintf(type, sizeof(type), "0x%X", (unsigned)st.f_type); + ptype = type; + } +#else + (void)type; + ptype = "unknown"; +#endif + + log_tag("statfs:%s: %s \n", ptype, path); return 0; } diff --git a/cmdline/util.c b/cmdline/util.c index de789666..4a52fa8b 100644 --- a/cmdline/util.c +++ b/cmdline/util.c @@ -68,9 +68,39 @@ void* malloc_nofail_test(size_t size) return ptr; } +/** + * Fast memory test. + * + * It's similat at raid_mtest_vector() but faster because + * we have a lot of buffers to verify. + */ +static int fast_mtest_vector(int n, size_t size, void** vv) +{ + int i, j; + + /* test with all the bits */ + for (i = 0; i < 8; ++i) { + unsigned char value = 1 << i; + + /* fill first */ + memset(vv[0], value, size); + + /* copy to the next */ + for (j = 1; j < n; ++j) + memcpy(vv[j], vv[j-1], size); + + /* compare with the previous */ + for (j = 1; j <= n; ++j) + if (memcmp(vv[j % n], vv[j-1], size) != 0) + return -1; + } + + return 0; +} + void mtest_vector(int n, size_t size, void** vv) { - if (raid_mtest_vector(n, size, vv) != 0) { + if (fast_mtest_vector(n, size, vv) != 0) { /* LCOV_EXCL_START */ log_fatal("DANGER! Your RAM memory is broken! DO NOT PROCEED UNTIL FIXED!\n"); log_fatal("Try running a memory test like http://www.memtest86.com/\n"); diff --git a/configure.ac b/configure.ac index 03244d1b..7abaa164 100644 --- a/configure.ac +++ b/configure.ac @@ -77,12 +77,26 @@ AC_CHECK_MEMBERS([struct statfs.f_type], [], [], [[ #include #endif ]]) +AC_CHECK_MEMBERS([struct statfs.f_fstypename], [], [], [[ +#if HAVE_SYS_PARAM_H +#include +#endif +#if HAVE_SYS_MOUNT_H +#include +#endif +#if HAVE_SYS_VFS_H +#include +#endif +#if HAVE_SYS_STATFS_H +#include +#endif +]]) dnl Checks for library functions. AC_CHECK_FUNCS([memset strchr strerror strrchr mkdir gettimeofday strtoul]) AC_CHECK_FUNCS([getopt getopt_long snprintf vsnprintf sigaction]) AC_CHECK_FUNCS([ftruncate fallocate fsync access posix_fallocate posix_fadvise]) -AC_CHECK_FUNCS([pread pwrite getc_unlocked ferror_unlocked fnmatch]) +AC_CHECK_FUNCS([getc_unlocked ferror_unlocked fnmatch]) AC_CHECK_FUNCS([futimes futimens futimesat localtime_r]) AC_CHECK_FUNCS([fstatat flock statfs]) AC_CHECK_FUNCS([mach_absolute_time]) @@ -118,6 +132,14 @@ AC_CHECK_CC_OPT([-Wextra], CFLAGS="$CFLAGS -Wextra", []) AC_CHECK_CC_OPT([-Wuninitialized], CFLAGS="$CFLAGS -Wuninitialized", []) AC_CHECK_CC_OPT([-Wshadow], CFLAGS="$CFLAGS -Wshadow", []) +dnl Checks for asm +AC_ARG_ENABLE([asm], + AS_HELP_STRING([--disable-asm], [Disable inline assembly])) +AS_IF([test "x$enable_asm" != "xno"], + [AC_DEFINE([HAVE_ASSEMBLY], [1], [Define to 1 if inline assembly should be used.])] + +dnl AS_IF(HAVE_ASSEMBLY) NOT closed here + dnl Checks for AS supporting the SSE2 instructions. AC_MSG_CHECKING([for sse2]) asmsse2=no @@ -183,6 +205,9 @@ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ [AC_DEFINE([HAVE_AVX2], [1], [Define to 1 if avx2 is supported by the assembler.]) asmavx2=yes]) AC_MSG_RESULT([$asmavx2]) +dnl AS_IF(HAVE_ASSEMBLY) closed here +) + dnl Checks for test environment AS_CASE([$host], [*-*-mingw*], @@ -228,8 +253,6 @@ AC_ARG_ENABLE([sgcheck], ], []) -AM_CONDITIONAL(HAVE_MEMORY_CHECKER, [test x"$MEMORY_CHECKER" != x]) - AC_ARG_ENABLE([helgrind], [AS_HELP_STRING([--enable-helgrind],[enable the use of helgrind in testing])], [ @@ -250,6 +273,40 @@ AC_ARG_ENABLE([drd], ], []) +AC_ARG_ENABLE([asan], + [AS_HELP_STRING([--enable-asan],[enable the use of AddressSanitizer in testing])], + [ + CFLAGS="$CFLAGS -DCHECKER -fsanitize=address" + MEMORY_CHECKER=1 + ], + []) + +AC_ARG_ENABLE([msan], + [AS_HELP_STRING([--enable-msan],[enable the use of MemorySanitizer in testing])], + [ + CFLAGS="$CFLAGS -DCHECKER -fsanitize=memory" + MEMORY_CHECKER=1 + ], + []) + +AC_ARG_ENABLE([ubsan], + [AS_HELP_STRING([--enable-ubsan],[enable the use of UndefinedBehaviourSanitizer in testing])], + [ + CFLAGS="$CFLAGS -DCHECKER -fsanitize=undefined" + MEMORY_CHECKER=1 + ], + []) + +AC_ARG_ENABLE([tsan], + [AS_HELP_STRING([--enable-tsan],[enable the use of ThreadSanitizer in testing])], + [ + CFLAGS="$CFLAGS -DCHECKER -fsanitize=thread" +dnl Use MEMORY_CHECKER instead of THREAD_CHECKER to run the full and long test + MEMORY_CHECKER=1 + ], + []) + +AM_CONDITIONAL(HAVE_MEMORY_CHECKER, [test x"$MEMORY_CHECKER" != x]) AM_CONDITIONAL(HAVE_THREAD_CHECKER, [test x"$THREAD_CHECKER" != x]) AC_ARG_ENABLE([sde], diff --git a/makecov.sh b/makecov.sh index 49b057bf..f030361e 100755 --- a/makecov.sh +++ b/makecov.sh @@ -1,5 +1,7 @@ #!/bin/sh # +# Run the Coverage test +# make distclean diff --git a/makesan.sh b/makesan.sh new file mode 100755 index 00000000..98b9d7f9 --- /dev/null +++ b/makesan.sh @@ -0,0 +1,57 @@ +#!/bin/sh +# +# Run all the Sanitizers available +# + +# Compiler to use +COMPILER=clang-3.6 + +# Options for configure +# --disable-asm +# Inline assembly is not supported by the Sanitizers +# --without-blkid +# External libraries are not supported by the Sanitizers +OPTIONS="--disable-asm --without-blkid" + +make distclean + +# AddressSanitizer +if ! ./configure --enable-asan $OPTIONS CC=$COMPILER; then + exit 1 +fi + +if ! make check distclean; then + exit 1 +fi + +# UndefinedBehaviourSanitizer +if ! ./configure --enable-ubsan $OPTIONS CC=$COMPILER; then + exit 1 +fi + +if ! make check distclean; then + exit 1 +fi + +# MemorySanitizer +if ! ./configure --enable-msan $OPTIONS CC=$COMPILER; then + exit 1 +fi + +if ! make check distclean; then + exit 1 +fi + +# ThreadSanitizer +if ! ./configure --enable-tsan $OPTIONS CC=$COMPILER; then + exit 1 +fi + +if ! make check distclean; then + exit 1 +fi + +if ! ./configure ; then + exit 1 +fi + diff --git a/makescan.sh b/makescan.sh index 74ed8c59..918c7544 100755 --- a/makescan.sh +++ b/makescan.sh @@ -1,5 +1,7 @@ #!/bin/sh # +# Run the Coverity Scan static analyzer +# rm -r cov-int diff --git a/maketest.sh b/maketest.sh index 3d71a490..63f7d239 100755 --- a/maketest.sh +++ b/maketest.sh @@ -1,8 +1,11 @@ #!/bin/sh # +# Run all the Coverage and Valgrind tests +# make distclean +# Coverage if ! ./configure --enable-coverage --enable-sde; then exit 1 fi @@ -11,6 +14,7 @@ if ! make lcov_reset check lcov_capture lcov_html; then exit 1 fi +# Keep a copy of the coverage results rm -rf cov_release mv cov cov_release @@ -18,6 +22,7 @@ if ! make distclean; then exit 1 fi +# Valgrind if ! ./configure --enable-valgrind; then exit 1 fi @@ -26,6 +31,7 @@ if ! make check distclean; then exit 1 fi +# Helgrind if ! ./configure --enable-helgrind; then exit 1 fi @@ -34,6 +40,7 @@ if ! make check distclean; then exit 1 fi +# Drd if ! ./configure --enable-drd; then exit 1 fi diff --git a/raid/internal.h b/raid/internal.h index ba2f35f6..4465cb97 100644 --- a/raid/internal.h +++ b/raid/internal.h @@ -15,21 +15,6 @@ #ifndef __RAID_INTERNAL_H #define __RAID_INTERNAL_H -/* - * Supported architecture. - * - * Detect the target architecture directly from the compiler. - */ -#if defined(__i386__) -#define CONFIG_X86 1 -#define CONFIG_X86_32 1 -#endif - -#if defined(__x86_64__) -#define CONFIG_X86 1 -#define CONFIG_X86_64 1 -#endif - /* * Supported instruction sets. * @@ -45,19 +30,44 @@ /* Includes the project configuration for HAVE_* defines */ #include "config.h" -#if HAVE_SSE2 /* Enables SSE2 only if the assembler supports it */ +/* If the compiler supports assembly */ +#if HAVE_ASSEMBLY +/* Autodetect from the compiler */ +#if defined(__i386__) +#define CONFIG_X86 1 +#define CONFIG_X86_32 1 +#endif +#if defined(__x86_64__) +#define CONFIG_X86 1 +#define CONFIG_X86_64 1 +#endif +#endif + +/* Enables SSE2, SSSE3, AVX2 only if the assembler supports it */ +#if HAVE_SSE2 #define CONFIG_SSE2 1 #endif - -#if HAVE_SSSE3 /* Enables SSSE3 only if the assembler supports it */ +#if HAVE_SSSE3 #define CONFIG_SSSE3 1 #endif - -#if HAVE_AVX2 /* Enables AVX2 only if the assembler supports it */ +#if HAVE_AVX2 #define CONFIG_AVX2 1 #endif -#else -/* If no config file, assumes the assembler supports everything in x86 */ + +#else /* if HAVE_CONFIG_H is not defined */ + +/* Assume that assembly is always supported */ +#if defined(__i386__) +#define CONFIG_X86 1 +#define CONFIG_X86_32 1 +#endif + +#if defined(__x86_64__) +#define CONFIG_X86 1 +#define CONFIG_X86_64 1 +#endif + +/* Assumes that the assembler supports everything */ #ifdef CONFIG_X86 #define CONFIG_SSE2 1 #define CONFIG_SSSE3 1