Merge branch 'mt'

This commit is contained in:
Andrea Mazzoleni 2016-01-09 17:32:42 +01:00
commit 89477202fe
31 changed files with 2670 additions and 804 deletions

View file

@ -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

View file

@ -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

View file

@ -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 */

View file

@ -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 */
}
}

View file

@ -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;

View file

@ -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 <parity_pos> and by <file,file_pos>
@ -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.

View file

@ -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));

View file

@ -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));

782
cmdline/io.c Normal file
View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#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 */
}
}
}

362
cmdline/io.h Normal file
View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#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

View file

@ -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) {

View file

@ -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

View file

@ -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;
}

View file

@ -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));

View file

@ -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.

View file

@ -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.

View file

@ -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));

View file

@ -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));

View file

@ -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;
}

View file

@ -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);

View file

@ -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 */

View file

@ -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 */

View file

@ -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, &copy_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));

View file

@ -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;
}

View file

@ -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");

View file

@ -77,12 +77,26 @@ AC_CHECK_MEMBERS([struct statfs.f_type], [], [], [[
#include <sys/statfs.h>
#endif
]])
AC_CHECK_MEMBERS([struct statfs.f_fstypename], [], [], [[
#if HAVE_SYS_PARAM_H
#include <sys/param.h>
#endif
#if HAVE_SYS_MOUNT_H
#include <sys/mount.h>
#endif
#if HAVE_SYS_VFS_H
#include <sys/vfs.h>
#endif
#if HAVE_SYS_STATFS_H
#include <sys/statfs.h>
#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],

View file

@ -1,5 +1,7 @@
#!/bin/sh
#
# Run the Coverage test
#
make distclean

57
makesan.sh Executable file
View file

@ -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

View file

@ -1,5 +1,7 @@
#!/bin/sh
#
# Run the Coverity Scan static analyzer
#
rm -r cov-int

View file

@ -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

View file

@ -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