mirror of
https://github.com/amadvance/snapraid.git
synced 2026-10-26 02:49:02 -04:00
Merge branch 'mt'
This commit is contained in:
commit
89477202fe
31 changed files with 2670 additions and 804 deletions
9
HISTORY
9
HISTORY
|
|
@ -1,6 +1,15 @@
|
|||
SnapRAID HISTORY
|
||||
================
|
||||
|
||||
10.0 2016/01
|
||||
===========
|
||||
* Increases the speed of the 'sync' and 'scrub' commands with a new
|
||||
read-ahead implementation using threads. We have one thread for each
|
||||
disk, dedicated exclusively to read data or parity in advance,
|
||||
and to write parity later.
|
||||
This ensures to keep the disks always busy, maximizing the data
|
||||
throughput.
|
||||
|
||||
9.2 2016/01
|
||||
===========
|
||||
* Fixes support for symlinks pointing to an empty target. Before they
|
||||
|
|
|
|||
25
Makefile.am
25
Makefile.am
|
|
@ -16,6 +16,7 @@ snapraid_SOURCES = \
|
|||
raid/tag.c \
|
||||
tommyds/tommy.c \
|
||||
cmdline/snapraid.c \
|
||||
cmdline/io.c \
|
||||
cmdline/util.c \
|
||||
cmdline/stream.c \
|
||||
cmdline/support.c \
|
||||
|
|
@ -70,6 +71,7 @@ noinst_HEADERS = \
|
|||
tommyds/tommytree.c \
|
||||
cmdline/portable.h \
|
||||
cmdline/snapraid.h \
|
||||
cmdline/io.h \
|
||||
cmdline/util.h \
|
||||
cmdline/stream.h \
|
||||
cmdline/support.h \
|
||||
|
|
@ -158,7 +160,7 @@ maintainer-clean-local:
|
|||
# --test-force-order-alpha
|
||||
# Ensures to process files always in the same order despites
|
||||
# the inode, physical location, and dir order assigned by the OS.
|
||||
CHECKFLAGS_BASE = --test-skip-device --test-skip-self --test-force-order-alpha --test-force-progress
|
||||
CHECKFLAGS_BASE = --test-skip-device --test-skip-self --test-force-order-alpha --test-force-progress --no-warnings
|
||||
|
||||
# Physical offset options
|
||||
CHECKFLAGS_PHYSICAL = --test-skip-device --test-skip-self --test-force-order-physical -q -q -q
|
||||
|
|
@ -227,7 +229,7 @@ if HAVE_THREAD_CHECKER
|
|||
$(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) -c $(CONF) sync
|
||||
# Other commands that uses threads
|
||||
$(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) -c $(CONF) test-rewrite
|
||||
$(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) -c $(CONF) scrub
|
||||
$(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) -c $(CONF) scrub -p full
|
||||
else
|
||||
#### COMMAND LINE ####
|
||||
$(MSG) Help
|
||||
|
|
@ -272,7 +274,7 @@ endif
|
|||
$(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) -c $(CONF) check
|
||||
$(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) -c $(CONF) -a check
|
||||
#### CONTROLLED ####
|
||||
@echo -- Filesystem allocation test
|
||||
$(MSG) Filesystem allocation test
|
||||
head -c 8192 /dev/zero > bench/disk1/TEST
|
||||
# Copy the file to trigger the copy optimization check
|
||||
cp -a bench/disk1/TEST bench/disk2/TEST
|
||||
|
|
@ -287,7 +289,7 @@ endif
|
|||
$(FAILENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) -c $(CONF) sync --test-expect-failure
|
||||
# Now sync will complete with -E
|
||||
$(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) -c $(CONF) -E sync
|
||||
@echo -- Filesystem fragmentation test
|
||||
$(MSG) Filesystem fragmentation test
|
||||
head -c 8192 /dev/zero > bench/disk1/TEST1
|
||||
head -c 8192 /dev/zero > bench/disk1/TEST2
|
||||
head -c 8192 /dev/zero > bench/disk1/TEST3
|
||||
|
|
@ -407,7 +409,7 @@ endif
|
|||
mkdir bench/disk3/empty3a
|
||||
mkdir bench/disk3/empty3a/emptyinner
|
||||
mkdir bench/disk3/empty3b
|
||||
echo 1 > bench/disk1/target1
|
||||
echo TARGET1 > bench/disk1/target1
|
||||
ln bench/disk1/target1 bench/disk1/file_hardlink1
|
||||
ln bench/disk1/target1 bench/disk1/file_hardlink2
|
||||
ln bench/disk1/target1 bench/disk1/file_hardlink3
|
||||
|
|
@ -419,13 +421,16 @@ endif
|
|||
$(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) -c $(CONF) status
|
||||
$(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) --test-skip-sequential -c $(PAR1) check
|
||||
#### CHANGE LINKS ####
|
||||
echo 2 > bench/disk1/target2
|
||||
# Use a different size ("22" instead of "1") to ensure to recognize the file different
|
||||
# even if it gets the same timestamp in case subsecond timestamp is no available
|
||||
echo TARGET22 > bench/disk1/target2
|
||||
rm bench/disk1/file_hardlink1
|
||||
ln bench/disk1/target2 bench/disk1/file_hardlink1
|
||||
rm bench/disk1/file_symlink1
|
||||
ln -s bench/disk1/target2 bench/disk1/file_symlink1
|
||||
$(FAILENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) -c $(PAR1) --test-expect-need-sync diff > output.log
|
||||
$(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) -c $(PAR1) sync
|
||||
$(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) -c $(PAR1) sync -l ">&1"
|
||||
$(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) -c $(PAR1) check -l ">&1"
|
||||
#### MISC COMMANDS ####
|
||||
$(MSG) Some commands with a not empty array
|
||||
$(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS_VERBOSE) -c $(PAR1) dup
|
||||
|
|
@ -436,11 +441,11 @@ endif
|
|||
$(MSG) Extend PAR1 to max parity with fix and check
|
||||
$(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) --test-expect-recoverable -c $(CONF) check -l test.log
|
||||
$(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) -c $(CONF) fix -d 2-parity -d 3-parity -d 4-parity -d 5-parity -d 6-parity -l test.log
|
||||
$(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) -c $(CONF) check
|
||||
$(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) -c $(CONF) check -l ">&1"
|
||||
$(MSG) Fix with unaccessible parity and disk
|
||||
$(FAILENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) -c $(NOACCESS) fix --test-expect-failure
|
||||
$(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) -c $(NOACCESS) fix --force-device --test-expect-recoverable -l test.log
|
||||
echo -- Rename a disk
|
||||
$(MSG) Rename a disk
|
||||
$(FAILENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) -c $(RENAME) --test-match-first-uuid sync
|
||||
$(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) -c $(CONF) --test-match-first-uuid sync
|
||||
#### SCRUB ####
|
||||
|
|
@ -892,7 +897,7 @@ endif
|
|||
endif
|
||||
$(MSG) Regression test completed with SUCCESS!
|
||||
$(MSG) Please ignore any error message printed above, they are expected!
|
||||
$(MSG) Everything is OK
|
||||
$(MSG) Everything OK
|
||||
|
||||
# Manual testing
|
||||
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
362
cmdline/dry.c
362
cmdline/dry.c
|
|
@ -22,30 +22,174 @@
|
|||
#include "state.h"
|
||||
#include "parity.h"
|
||||
#include "handle.h"
|
||||
#include "io.h"
|
||||
|
||||
/****************************************************************************/
|
||||
/* dry */
|
||||
|
||||
static int state_dry_process(struct snapraid_state* state, struct snapraid_parity_handle** parity, block_off_t blockstart, block_off_t blockmax)
|
||||
/**
|
||||
* Check if we have to process the specified block index ::i.
|
||||
*/
|
||||
static int block_is_enabled(void* void_plan, block_off_t i)
|
||||
{
|
||||
(void)void_plan;
|
||||
(void)i;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void dry_data_reader(struct snapraid_worker* worker, struct snapraid_task* task)
|
||||
{
|
||||
struct snapraid_io* io = worker->io;
|
||||
struct snapraid_state* state = io->state;
|
||||
struct snapraid_handle* handle = worker->handle;
|
||||
struct snapraid_disk* disk = handle->disk;
|
||||
block_off_t blockcur = task->position;
|
||||
unsigned char* buffer = task->buffer;
|
||||
int ret;
|
||||
|
||||
/* if the disk position is not used */
|
||||
if (!disk) {
|
||||
/* use an empty block */
|
||||
memset(buffer, 0, state->block_size);
|
||||
task->state = TASK_STATE_DONE;
|
||||
return;
|
||||
}
|
||||
|
||||
/* get the block */
|
||||
task->block = fs_par2block_get(disk, blockcur);
|
||||
|
||||
/* if the block is not used */
|
||||
if (!block_has_file(task->block)) {
|
||||
/* use an empty block */
|
||||
memset(buffer, 0, state->block_size);
|
||||
task->state = TASK_STATE_DONE;
|
||||
return;
|
||||
}
|
||||
|
||||
/* get the file of this block */
|
||||
task->file = fs_par2file_get(disk, blockcur, &task->file_pos);
|
||||
|
||||
/* if the file is different than the current one, close it */
|
||||
if (handle->file != 0 && handle->file != task->file) {
|
||||
/* keep a pointer at the file we are going to close for error reporting */
|
||||
struct snapraid_file* report = handle->file;
|
||||
ret = handle_close(handle);
|
||||
if (ret == -1) {
|
||||
/* LCOV_EXCL_START */
|
||||
/* This one is really an unexpected error, because we are only reading */
|
||||
/* and closing a descriptor should never fail */
|
||||
if (errno == EIO) {
|
||||
log_tag("error:%u:%s:%s: Close EIO error. %s\n", blockcur, disk->name, esc(report->sub), strerror(errno));
|
||||
log_fatal("DANGER! Unexpected input/output close error in a data disk, it isn't possible to dry.\n");
|
||||
log_fatal("Ensure that disk '%s' is sane and that file '%s' can be accessed.\n", disk->dir, handle->path);
|
||||
log_fatal("Stopping at block %u\n", blockcur);
|
||||
task->state = TASK_STATE_IOERROR;
|
||||
return;
|
||||
}
|
||||
|
||||
log_tag("error:%u:%s:%s: Close error. %s\n", blockcur, disk->name, esc(report->sub), strerror(errno));
|
||||
log_fatal("WARNING! Unexpected close error in a data disk, it isn't possible to dry.\n");
|
||||
log_fatal("Ensure that file '%s' can be accessed.\n", handle->path);
|
||||
log_fatal("Stopping at block %u\n", blockcur);
|
||||
task->state = TASK_STATE_ERROR;
|
||||
return;
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
}
|
||||
|
||||
ret = handle_open(handle, task->file, state->file_mode, log_error, 0);
|
||||
if (ret == -1) {
|
||||
if (errno == EIO) {
|
||||
/* LCOV_EXCL_START */
|
||||
log_tag("error:%u:%s:%s: Open EIO error. %s\n", blockcur, disk->name, esc(task->file->sub), strerror(errno));
|
||||
log_fatal("DANGER! Unexpected input/output open error in a data disk, it isn't possible to dry.\n");
|
||||
log_fatal("Ensure that disk '%s' is sane and that file '%s' can be accessed.\n", disk->dir, handle->path);
|
||||
log_fatal("Stopping at block %u\n", blockcur);
|
||||
task->state = TASK_STATE_IOERROR;
|
||||
return;
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
|
||||
log_tag("error:%u:%s:%s: Open error. %s\n", blockcur, disk->name, esc(task->file->sub), strerror(errno));
|
||||
task->state = TASK_STATE_ERROR_CONTINUE;
|
||||
return;
|
||||
}
|
||||
|
||||
task->read_size = handle_read(handle, task->file_pos, buffer, state->block_size, log_error, 0);
|
||||
if (task->read_size == -1) {
|
||||
if (errno == EIO) {
|
||||
log_tag("error:%u:%s:%s: Read EIO error at position %u. %s\n", blockcur, disk->name, esc(task->file->sub), task->file_pos, strerror(errno));
|
||||
log_error("Input/Output error in file '%s' at position '%u'\n", handle->path, task->file_pos);
|
||||
task->state = TASK_STATE_IOERROR_CONTINUE;
|
||||
return;
|
||||
}
|
||||
|
||||
log_tag("error:%u:%s:%s: Read error at position %u. %s\n", blockcur, disk->name, esc(task->file->sub), task->file_pos, strerror(errno));
|
||||
task->state = TASK_STATE_ERROR_CONTINUE;
|
||||
return;
|
||||
}
|
||||
|
||||
/* store the path of the opened file */
|
||||
pathcpy(task->path, sizeof(task->path), handle->path);
|
||||
|
||||
task->state = TASK_STATE_DONE;
|
||||
}
|
||||
|
||||
static void dry_parity_reader(struct snapraid_worker* worker, struct snapraid_task* task)
|
||||
{
|
||||
struct snapraid_io* io = worker->io;
|
||||
struct snapraid_state* state = io->state;
|
||||
struct snapraid_parity_handle* parity_handle = worker->parity_handle;
|
||||
unsigned level = parity_handle->level;
|
||||
block_off_t blockcur = task->position;
|
||||
unsigned char* buffer = task->buffer;
|
||||
int ret;
|
||||
|
||||
/* read the parity */
|
||||
ret = parity_read(parity_handle, blockcur, buffer, state->block_size, log_error);
|
||||
if (ret == -1) {
|
||||
if (errno == EIO) {
|
||||
log_tag("parity_error:%u:%s: Read EIO error. %s\n", blockcur, lev_config_name(level), strerror(errno));
|
||||
log_error("Input/Output error in parity '%s' at position '%u'\n", lev_config_name(level), blockcur);
|
||||
task->state = TASK_STATE_IOERROR_CONTINUE;
|
||||
return;
|
||||
}
|
||||
|
||||
log_tag("parity_error:%u:%s: Read error. %s\n", blockcur, lev_config_name(level), strerror(errno));
|
||||
task->state = TASK_STATE_ERROR_CONTINUE;
|
||||
return;
|
||||
}
|
||||
|
||||
task->state = TASK_STATE_DONE;
|
||||
}
|
||||
|
||||
static int state_dry_process(struct snapraid_state* state, struct snapraid_parity_handle* parity_handle, block_off_t blockstart, block_off_t blockmax)
|
||||
{
|
||||
struct snapraid_io io;
|
||||
struct snapraid_handle* handle;
|
||||
unsigned diskmax;
|
||||
block_off_t i;
|
||||
block_off_t blockcur;
|
||||
unsigned j;
|
||||
void* buffer_alloc;
|
||||
unsigned char* buffer_aligned;
|
||||
unsigned buffermax;
|
||||
int ret;
|
||||
data_off_t countsize;
|
||||
block_off_t countpos;
|
||||
block_off_t countmax;
|
||||
unsigned error;
|
||||
unsigned io_error;
|
||||
unsigned l;
|
||||
|
||||
handle = handle_map(state, &diskmax);
|
||||
|
||||
buffer_aligned = malloc_nofail_align(state->block_size, &buffer_alloc);
|
||||
/* we need 1 * data + 2 * parity */
|
||||
buffermax = diskmax + 2 * state->level;
|
||||
|
||||
/* initialize the io threads */
|
||||
io_init(&io, state, buffermax, dry_data_reader, handle, diskmax, dry_parity_reader, 0, parity_handle, state->level);
|
||||
|
||||
error = 0;
|
||||
io_error = 0;
|
||||
|
||||
/* drop until now */
|
||||
state_usage_waste(state);
|
||||
|
|
@ -53,89 +197,129 @@ static int state_dry_process(struct snapraid_state* state, struct snapraid_parit
|
|||
countmax = blockmax - blockstart;
|
||||
countsize = 0;
|
||||
countpos = 0;
|
||||
|
||||
/* start all the worker threads */
|
||||
io_start(&io, blockstart, blockmax, &block_is_enabled, 0);
|
||||
|
||||
state_progress_begin(state, blockstart, blockmax, countmax);
|
||||
for (i = blockstart; i < blockmax; ++i) {
|
||||
while (1) {
|
||||
void** buffer;
|
||||
|
||||
/* go to the next block */
|
||||
blockcur = io_read_next(&io, &buffer);
|
||||
if (blockcur >= blockmax)
|
||||
break;
|
||||
|
||||
/* for each disk, process the block */
|
||||
for (j = 0; j < diskmax; ++j) {
|
||||
struct snapraid_task* task;
|
||||
int read_size;
|
||||
struct snapraid_block* block;
|
||||
struct snapraid_disk* disk = handle[j].disk;
|
||||
struct snapraid_file* file;
|
||||
block_off_t file_pos;
|
||||
|
||||
if (!disk) {
|
||||
/* if no disk, nothing to do */
|
||||
continue;
|
||||
}
|
||||
|
||||
block = fs_par2block_get(disk, i);
|
||||
|
||||
if (!block_has_file(block)) {
|
||||
/* if no file, nothing to do */
|
||||
continue;
|
||||
}
|
||||
|
||||
/* get the file of this block */
|
||||
file = fs_par2file_get(disk, i, &file_pos);
|
||||
struct snapraid_disk* disk;
|
||||
unsigned diskcur;
|
||||
|
||||
/* until now is CPU */
|
||||
state_usage_cpu(state);
|
||||
|
||||
/* if the file is closed or different than the current one */
|
||||
if (handle[j].file == 0 || handle[j].file != file) {
|
||||
/* close the old one, if any */
|
||||
ret = handle_close(&handle[j]);
|
||||
if (ret == -1) {
|
||||
/* LCOV_EXCL_START */
|
||||
log_tag("error:%u:%s:%s: Close error. %s\n", i, disk->name, esc(handle[j].file->sub), strerror(errno));
|
||||
log_fatal("DANGER! Unexpected close error in a data disk, it isn't possible to dry.\n");
|
||||
log_fatal("Stopping at block %u\n", i);
|
||||
++error;
|
||||
goto bail;
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
/* get the next task */
|
||||
task = io_data_read(&io, &diskcur);
|
||||
|
||||
/* open the file only for reading */
|
||||
ret = handle_open(&handle[j], file, state->file_mode, log_fatal, 0);
|
||||
if (ret == -1) {
|
||||
/* LCOV_EXCL_START */
|
||||
log_tag("error:%u:%s:%s: Open error. %s\n", i, disk->name, esc(file->sub), strerror(errno));
|
||||
log_fatal("DANGER! Unexpected open error in a data disk, it isn't possible to dry.\n");
|
||||
log_fatal("Stopping at block %u\n", i);
|
||||
++error;
|
||||
goto bail;
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
}
|
||||
/* get the task results */
|
||||
disk = task->disk;
|
||||
block = task->block;
|
||||
read_size = task->read_size;
|
||||
|
||||
/* read from the file */
|
||||
read_size = handle_read(&handle[j], file_pos, buffer_aligned, state->block_size, log_error, 0);
|
||||
if (read_size == -1) {
|
||||
log_tag("error:%u:%s:%s: Read error at position %u\n", i, disk->name, esc(file->sub), file_pos);
|
||||
++error;
|
||||
/* if the disk position is not used */
|
||||
if (!disk)
|
||||
continue;
|
||||
}
|
||||
|
||||
/* until now is disk */
|
||||
state_usage_disk(state, disk);
|
||||
|
||||
/* if the block is not used */
|
||||
if (!block_has_file(block))
|
||||
continue;
|
||||
|
||||
/* handle error conditions */
|
||||
if (task->state == TASK_STATE_IOERROR) {
|
||||
++io_error;
|
||||
goto bail;
|
||||
}
|
||||
if (task->state == TASK_STATE_ERROR) {
|
||||
++error;
|
||||
goto bail;
|
||||
}
|
||||
if (task->state == TASK_STATE_ERROR_CONTINUE) {
|
||||
++error;
|
||||
continue;
|
||||
}
|
||||
if (task->state == TASK_STATE_IOERROR_CONTINUE) {
|
||||
++io_error;
|
||||
if (io_error >= state->opt.io_error_limit) {
|
||||
/* LCOV_EXCL_START */
|
||||
log_fatal("DANGER! Too many input/output read error in a data disk, it isn't possible to scrub.\n");
|
||||
log_fatal("Ensure that disk '%s' is sane and that file '%s' can be accessed.\n", disk->dir, task->path);
|
||||
log_fatal("Stopping at block %u\n", blockcur);
|
||||
goto bail;
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
|
||||
/* otherwise continue */
|
||||
continue;
|
||||
}
|
||||
if (task->state != TASK_STATE_DONE) {
|
||||
/* LCOV_EXCL_START */
|
||||
log_fatal("Internal inconsistency in task state\n");
|
||||
os_abort();
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
|
||||
countsize += read_size;
|
||||
}
|
||||
|
||||
/* until now is CPU */
|
||||
state_usage_cpu(state);
|
||||
|
||||
/* read the parity */
|
||||
for (l = 0; l < state->level; ++l) {
|
||||
if (parity[l]) {
|
||||
/* until now is CPU */
|
||||
state_usage_cpu(state);
|
||||
struct snapraid_task* task;
|
||||
unsigned levcur;
|
||||
|
||||
ret = parity_read(parity[l], i, buffer_aligned, state->block_size, log_error);
|
||||
if (ret == -1) {
|
||||
log_tag("parity_error:%u:%s: Read error\n", i, lev_config_name(l));
|
||||
++error;
|
||||
task = io_parity_read(&io, &levcur);
|
||||
|
||||
/* until now is parity */
|
||||
state_usage_parity(state, levcur);
|
||||
|
||||
/* handle error conditions */
|
||||
if (task->state == TASK_STATE_IOERROR) {
|
||||
++io_error;
|
||||
goto bail;
|
||||
}
|
||||
if (task->state == TASK_STATE_ERROR) {
|
||||
++error;
|
||||
goto bail;
|
||||
}
|
||||
if (task->state == TASK_STATE_ERROR_CONTINUE) {
|
||||
++error;
|
||||
continue;
|
||||
}
|
||||
if (task->state == TASK_STATE_IOERROR_CONTINUE) {
|
||||
++io_error;
|
||||
if (io_error >= state->opt.io_error_limit) {
|
||||
/* LCOV_EXCL_START */
|
||||
log_fatal("DANGER! Too many input/output read error in the %s disk, it isn't possible to scrub.\n", lev_name(levcur));
|
||||
log_fatal("Ensure that disk '%s' is sane and can be read.\n", lev_config_name(levcur));
|
||||
log_fatal("Stopping at block %u\n", blockcur);
|
||||
goto bail;
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
|
||||
/* until now is parity */
|
||||
state_usage_parity(state, l);
|
||||
continue;
|
||||
}
|
||||
if (task->state != TASK_STATE_DONE) {
|
||||
/* LCOV_EXCL_START */
|
||||
log_fatal("Internal inconsistency in task state\n");
|
||||
os_abort();
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -143,7 +327,7 @@ static int state_dry_process(struct snapraid_state* state, struct snapraid_parit
|
|||
++countpos;
|
||||
|
||||
/* progress */
|
||||
if (state_progress(state, i, countpos, countmax, countsize)) {
|
||||
if (state_progress(state, blockcur, countpos, countmax, countsize)) {
|
||||
/* LCOV_EXCL_START */
|
||||
break;
|
||||
/* LCOV_EXCL_STOP */
|
||||
|
|
@ -155,7 +339,9 @@ static int state_dry_process(struct snapraid_state* state, struct snapraid_parit
|
|||
state_usage_print(state);
|
||||
|
||||
bail:
|
||||
/* close all the files left open */
|
||||
/* stop all the worker threads */
|
||||
io_stop(&io);
|
||||
|
||||
for (j = 0; j < diskmax; ++j) {
|
||||
struct snapraid_file* file = handle[j].file;
|
||||
struct snapraid_disk* disk = handle[j].disk;
|
||||
|
|
@ -170,20 +356,23 @@ bail:
|
|||
}
|
||||
}
|
||||
|
||||
if (error) {
|
||||
if (error || io_error) {
|
||||
msg_status("\n");
|
||||
msg_status("%8u errors\n", error);
|
||||
msg_status("%8u file errors\n", error);
|
||||
msg_status("%8u io errors\n", io_error);
|
||||
} else {
|
||||
msg_status("Everything OK\n");
|
||||
}
|
||||
|
||||
if (error)
|
||||
log_fatal("DANGER! Unexpected errors!\n");
|
||||
if (io_error)
|
||||
log_fatal("DANGER! Unexpected input/output errors!\n");
|
||||
|
||||
free(handle);
|
||||
free(buffer_alloc);
|
||||
io_done(&io);
|
||||
|
||||
if (error != 0)
|
||||
if (error + io_error != 0)
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -192,10 +381,7 @@ void state_dry(struct snapraid_state* state, block_off_t blockstart, block_off_t
|
|||
{
|
||||
block_off_t blockmax;
|
||||
int ret;
|
||||
struct snapraid_parity_handle parity[LEV_MAX];
|
||||
/* the following initialization is to avoid clang warnings about */
|
||||
/* potential state->level change, that never happens */
|
||||
struct snapraid_parity_handle* parity_ptr[LEV_MAX] = { 0 };
|
||||
struct snapraid_parity_handle parity_handle[LEV_MAX];
|
||||
unsigned error;
|
||||
unsigned l;
|
||||
|
||||
|
|
@ -218,12 +404,12 @@ void state_dry(struct snapraid_state* state, block_off_t blockstart, block_off_t
|
|||
/* open the file for reading */
|
||||
/* it may fail if the file doesn't exist, in this case we continue to dry the files */
|
||||
for (l = 0; l < state->level; ++l) {
|
||||
parity_ptr[l] = &parity[l];
|
||||
ret = parity_open(parity_ptr[l], state->parity[l].path, state->file_mode);
|
||||
ret = parity_open(&parity_handle[l], l, state->parity[l].path, state->file_mode);
|
||||
if (ret == -1) {
|
||||
msg_status("No accessible %s file.\n", lev_name(l));
|
||||
/* continue anyway */
|
||||
parity_ptr[l] = 0;
|
||||
/* LCOV_EXCL_START */
|
||||
log_fatal("WARNING! Without an accessible %s file, it isn't possible to dry.\n", lev_name(l));
|
||||
exit(EXIT_FAILURE);
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -231,7 +417,7 @@ void state_dry(struct snapraid_state* state, block_off_t blockstart, block_off_t
|
|||
|
||||
/* skip degenerated cases of empty parity, or skipping all */
|
||||
if (blockstart < blockmax) {
|
||||
ret = state_dry_process(state, parity_ptr, blockstart, blockmax);
|
||||
ret = state_dry_process(state, parity_handle, blockstart, blockmax);
|
||||
if (ret == -1) {
|
||||
/* LCOV_EXCL_START */
|
||||
++error;
|
||||
|
|
@ -242,14 +428,12 @@ void state_dry(struct snapraid_state* state, block_off_t blockstart, block_off_t
|
|||
|
||||
/* try to close only if opened */
|
||||
for (l = 0; l < state->level; ++l) {
|
||||
if (parity_ptr[l]) {
|
||||
ret = parity_close(parity_ptr[l]);
|
||||
if (ret == -1) {
|
||||
/* LCOV_EXCL_START */
|
||||
++error;
|
||||
/* continue, as we are already exiting */
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
ret = parity_close(&parity_handle[l]);
|
||||
if (ret == -1) {
|
||||
/* LCOV_EXCL_START */
|
||||
++error;
|
||||
/* continue, as we are already exiting */
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
115
cmdline/elem.c
115
cmdline/elem.c
|
|
@ -737,6 +737,8 @@ struct snapraid_disk* disk_alloc(const char* name, const char* dir, uint64_t dev
|
|||
/* ensure that the dir terminate with "/" if it isn't empty */
|
||||
pathslash(disk->dir, sizeof(disk->dir));
|
||||
|
||||
pthread_mutex_init(&disk->fs_mutex, 0);
|
||||
|
||||
disk->smartctl[0] = 0;
|
||||
disk->device = dev;
|
||||
disk->tick = 0;
|
||||
|
|
@ -778,9 +780,22 @@ void disk_free(struct snapraid_disk* disk)
|
|||
tommy_hashdyn_done(&disk->linkset);
|
||||
tommy_list_foreach(&disk->dirlist, (tommy_foreach_func*)dir_free);
|
||||
tommy_hashdyn_done(&disk->dirset);
|
||||
|
||||
pthread_mutex_destroy(&disk->fs_mutex);
|
||||
|
||||
free(disk);
|
||||
}
|
||||
|
||||
static inline void fs_lock(struct snapraid_disk* disk)
|
||||
{
|
||||
pthread_mutex_lock(&disk->fs_mutex);
|
||||
}
|
||||
|
||||
static inline void fs_unlock(struct snapraid_disk* disk)
|
||||
{
|
||||
pthread_mutex_unlock(&disk->fs_mutex);
|
||||
}
|
||||
|
||||
struct chunk_disk_empty {
|
||||
block_off_t blockmax;
|
||||
};
|
||||
|
|
@ -788,7 +803,7 @@ struct chunk_disk_empty {
|
|||
/**
|
||||
* Compare the chunk if inside the specified blockmax.
|
||||
*/
|
||||
static int chunk_disk_empty_compare(const void* void_a, const void* void_b)
|
||||
static int chunk_disk_empty_compare_unlock(const void* void_a, const void* void_b)
|
||||
{
|
||||
const struct chunk_disk_empty* arg_a = void_a;
|
||||
const struct snapraid_chunk* arg_b = void_b;
|
||||
|
|
@ -801,7 +816,7 @@ static int chunk_disk_empty_compare(const void* void_a, const void* void_b)
|
|||
return -1;
|
||||
}
|
||||
|
||||
int disk_is_empty(struct snapraid_disk* disk, block_off_t blockmax)
|
||||
int fs_is_empty(struct snapraid_disk* disk, block_off_t blockmax)
|
||||
{
|
||||
struct chunk_disk_empty arg = { blockmax };
|
||||
|
||||
|
|
@ -814,11 +829,16 @@ int disk_is_empty(struct snapraid_disk* disk, block_off_t blockmax)
|
|||
if (!tommy_list_empty(&disk->dirlist))
|
||||
return 0;
|
||||
|
||||
fs_lock(disk);
|
||||
|
||||
/* search for any chunk inside blockmax */
|
||||
if (tommy_tree_search_compare(&disk->fs_parity, chunk_disk_empty_compare, &arg) != 0)
|
||||
if (tommy_tree_search_compare(&disk->fs_parity, chunk_disk_empty_compare_unlock, &arg) != 0) {
|
||||
fs_unlock(disk);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* finally, it's empty */
|
||||
fs_unlock(disk);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
|
@ -831,7 +851,7 @@ struct chunk_disk_size {
|
|||
*
|
||||
* The maximum parity position is stored as size.
|
||||
*/
|
||||
static int chunk_disk_size_compare(const void* void_a, const void* void_b)
|
||||
static int chunk_disk_size_compare_unlock(const void* void_a, const void* void_b)
|
||||
{
|
||||
struct chunk_disk_size* arg_a = (void*)void_a;
|
||||
const struct snapraid_chunk* arg_b = void_b;
|
||||
|
|
@ -844,10 +864,16 @@ static int chunk_disk_size_compare(const void* void_a, const void* void_b)
|
|||
return 1;
|
||||
}
|
||||
|
||||
block_off_t disk_size(struct snapraid_disk* disk)
|
||||
block_off_t fs_size(struct snapraid_disk* disk)
|
||||
{
|
||||
struct chunk_disk_size arg = { 0 };
|
||||
tommy_tree_search_compare(&disk->fs_parity, chunk_disk_size_compare, &arg);
|
||||
|
||||
fs_lock(disk);
|
||||
|
||||
tommy_tree_search_compare(&disk->fs_parity, chunk_disk_size_compare_unlock, &arg);
|
||||
|
||||
fs_unlock(disk);
|
||||
|
||||
return arg.size;
|
||||
}
|
||||
|
||||
|
|
@ -856,7 +882,7 @@ struct chunk_check {
|
|||
int result;
|
||||
};
|
||||
|
||||
void chunk_parity_check_foreach(void* void_arg, void* void_obj)
|
||||
static void chunk_parity_check_foreach_unlock(void* void_arg, void* void_obj)
|
||||
{
|
||||
struct chunk_check* arg = void_arg;
|
||||
const struct snapraid_chunk* obj = void_obj;
|
||||
|
|
@ -906,7 +932,7 @@ void chunk_parity_check_foreach(void* void_arg, void* void_obj)
|
|||
}
|
||||
}
|
||||
|
||||
void chunk_file_check_foreach(void* void_arg, void* void_obj)
|
||||
static void chunk_file_check_foreach_unlock(void* void_arg, void* void_obj)
|
||||
{
|
||||
struct chunk_check* arg = void_arg;
|
||||
const struct snapraid_chunk* obj = void_obj;
|
||||
|
|
@ -1022,13 +1048,17 @@ int fs_check(struct snapraid_disk* disk)
|
|||
/* error count starts from 0 */
|
||||
arg.result = 0;
|
||||
|
||||
fs_lock(disk);
|
||||
|
||||
/* check parity sequence */
|
||||
arg.prev = 0;
|
||||
tommy_tree_foreach_arg(&disk->fs_parity, chunk_parity_check_foreach, &arg);
|
||||
tommy_tree_foreach_arg(&disk->fs_parity, chunk_parity_check_foreach_unlock, &arg);
|
||||
|
||||
/* check file sequence */
|
||||
arg.prev = 0;
|
||||
tommy_tree_foreach_arg(&disk->fs_file, chunk_file_check_foreach, &arg);
|
||||
tommy_tree_foreach_arg(&disk->fs_file, chunk_file_check_foreach_unlock, &arg);
|
||||
|
||||
fs_unlock(disk);
|
||||
|
||||
if (arg.result != 0)
|
||||
return -1;
|
||||
|
|
@ -1043,7 +1073,7 @@ struct chunk_parity_inside {
|
|||
/**
|
||||
* Compare the chunk if containing the specified parity position.
|
||||
*/
|
||||
static int chunk_parity_inside_compare(const void* void_a, const void* void_b)
|
||||
static int chunk_parity_inside_compare_unlock(const void* void_a, const void* void_b)
|
||||
{
|
||||
const struct chunk_parity_inside* arg_a = void_a;
|
||||
const struct snapraid_chunk* arg_b = void_b;
|
||||
|
|
@ -1061,7 +1091,7 @@ static int chunk_parity_inside_compare(const void* void_a, const void* void_b)
|
|||
* The search is optimized for sequential accesses.
|
||||
* \return If not found return 0
|
||||
*/
|
||||
static struct snapraid_chunk* fs_par2chunk_get_ts(struct snapraid_disk* disk, struct snapraid_chunk** fs_last, block_off_t parity_pos)
|
||||
static struct snapraid_chunk* fs_par2chunk_get_unlock(struct snapraid_disk* disk, struct snapraid_chunk** fs_last, block_off_t parity_pos)
|
||||
{
|
||||
struct snapraid_chunk* chunk;
|
||||
|
||||
|
|
@ -1073,7 +1103,7 @@ static struct snapraid_chunk* fs_par2chunk_get_ts(struct snapraid_disk* disk, st
|
|||
chunk = *fs_last;
|
||||
} else {
|
||||
struct chunk_parity_inside arg = { parity_pos };
|
||||
chunk = tommy_tree_search_compare(&disk->fs_parity, chunk_parity_inside_compare, &arg);
|
||||
chunk = tommy_tree_search_compare(&disk->fs_parity, chunk_parity_inside_compare_unlock, &arg);
|
||||
}
|
||||
|
||||
if (!chunk)
|
||||
|
|
@ -1093,7 +1123,7 @@ struct chunk_file_inside {
|
|||
/**
|
||||
* Compare the chunk if containing the specified file position.
|
||||
*/
|
||||
static int chunk_file_inside_compare(const void* void_a, const void* void_b)
|
||||
static int chunk_file_inside_compare_unlock(const void* void_a, const void* void_b)
|
||||
{
|
||||
const struct chunk_file_inside* arg_a = void_a;
|
||||
const struct snapraid_chunk* arg_b = void_b;
|
||||
|
|
@ -1116,7 +1146,7 @@ static int chunk_file_inside_compare(const void* void_a, const void* void_b)
|
|||
* The search is optimized for sequential accesses.
|
||||
* \return If not found return 0
|
||||
*/
|
||||
static struct snapraid_chunk* fs_file2chunk_get_ts(struct snapraid_disk* disk, struct snapraid_chunk** fs_last, struct snapraid_file* file, block_off_t file_pos)
|
||||
static struct snapraid_chunk* fs_file2chunk_get_unlock(struct snapraid_disk* disk, struct snapraid_chunk** fs_last, struct snapraid_file* file, block_off_t file_pos)
|
||||
{
|
||||
struct snapraid_chunk* chunk;
|
||||
|
||||
|
|
@ -1129,7 +1159,7 @@ static struct snapraid_chunk* fs_file2chunk_get_ts(struct snapraid_disk* disk, s
|
|||
chunk = *fs_last;
|
||||
} else {
|
||||
struct chunk_file_inside arg = { file, file_pos };
|
||||
chunk = tommy_tree_search_compare(&disk->fs_file, chunk_file_inside_compare, &arg);
|
||||
chunk = tommy_tree_search_compare(&disk->fs_file, chunk_file_inside_compare_unlock, &arg);
|
||||
}
|
||||
|
||||
if (!chunk)
|
||||
|
|
@ -1141,25 +1171,37 @@ static struct snapraid_chunk* fs_file2chunk_get_ts(struct snapraid_disk* disk, s
|
|||
return chunk;
|
||||
}
|
||||
|
||||
struct snapraid_file* fs_par2file_get_ts(struct snapraid_disk* disk, struct snapraid_chunk** fs_last, block_off_t parity_pos, block_off_t* file_pos)
|
||||
struct snapraid_file* fs_par2file_get(struct snapraid_disk* disk, block_off_t parity_pos, block_off_t* file_pos)
|
||||
{
|
||||
struct snapraid_chunk* chunk;
|
||||
struct snapraid_file* file;
|
||||
|
||||
chunk = fs_par2chunk_get_ts(disk, fs_last, parity_pos);
|
||||
if (!chunk)
|
||||
fs_lock(disk);
|
||||
|
||||
chunk = fs_par2chunk_get_unlock(disk, &disk->fs_last, parity_pos);
|
||||
|
||||
if (!chunk) {
|
||||
fs_unlock(disk);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (file_pos)
|
||||
*file_pos = chunk->file_pos + (parity_pos - chunk->parity_pos);
|
||||
|
||||
return chunk->file;
|
||||
file = chunk->file;
|
||||
|
||||
fs_unlock(disk);
|
||||
return file;
|
||||
}
|
||||
|
||||
block_off_t fs_file2par_get_ts(struct snapraid_disk* disk, struct snapraid_chunk** fs_last, struct snapraid_file* file, block_off_t file_pos)
|
||||
block_off_t fs_file2par_get(struct snapraid_disk* disk, struct snapraid_file* file, block_off_t file_pos)
|
||||
{
|
||||
struct snapraid_chunk* chunk;
|
||||
block_off_t ret;
|
||||
|
||||
chunk = fs_file2chunk_get_ts(disk, fs_last, file, file_pos);
|
||||
fs_lock(disk);
|
||||
|
||||
chunk = fs_file2chunk_get_unlock(disk, &disk->fs_last, file, file_pos);
|
||||
if (!chunk) {
|
||||
/* LCOV_EXCL_START */
|
||||
log_fatal("Internal inconsistency when resolving file '%s' at position '%u/%u' in disk '%s'\n", file->sub, file_pos, file->blockmax, disk->name);
|
||||
|
|
@ -1167,7 +1209,10 @@ block_off_t fs_file2par_get_ts(struct snapraid_disk* disk, struct snapraid_chunk
|
|||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
|
||||
return chunk->parity_pos + (file_pos - chunk->file_pos);
|
||||
ret = chunk->parity_pos + (file_pos - chunk->file_pos);
|
||||
|
||||
fs_unlock(disk);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void fs_allocate(struct snapraid_disk* disk, block_off_t parity_pos, struct snapraid_file* file, block_off_t file_pos)
|
||||
|
|
@ -1176,9 +1221,11 @@ void fs_allocate(struct snapraid_disk* disk, block_off_t parity_pos, struct snap
|
|||
struct snapraid_chunk* parity_chunk;
|
||||
struct snapraid_chunk* file_chunk;
|
||||
|
||||
fs_lock(disk);
|
||||
|
||||
if (file_pos > 0) {
|
||||
/* search an existing chunk for the previous file_pos */
|
||||
chunk = fs_file2chunk_get_ts(disk, &disk->fs_last, file, file_pos - 1);
|
||||
chunk = fs_file2chunk_get_unlock(disk, &disk->fs_last, file, file_pos - 1);
|
||||
|
||||
if (chunk != 0 && parity_pos == chunk->parity_pos + chunk->count) {
|
||||
/* ensure that we are extending the chunk at the end */
|
||||
|
|
@ -1192,6 +1239,7 @@ void fs_allocate(struct snapraid_disk* disk, block_off_t parity_pos, struct snap
|
|||
/* extend the existing chunk */
|
||||
++chunk->count;
|
||||
|
||||
fs_unlock(disk);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
@ -1212,6 +1260,8 @@ void fs_allocate(struct snapraid_disk* disk, block_off_t parity_pos, struct snap
|
|||
|
||||
/* store the last accessed chunk */
|
||||
disk->fs_last = chunk;
|
||||
|
||||
fs_unlock(disk);
|
||||
}
|
||||
|
||||
void fs_deallocate(struct snapraid_disk* disk, block_off_t parity_pos)
|
||||
|
|
@ -1222,7 +1272,9 @@ void fs_deallocate(struct snapraid_disk* disk, block_off_t parity_pos)
|
|||
struct snapraid_chunk* file_chunk;
|
||||
block_off_t first_count, second_count;
|
||||
|
||||
chunk = fs_par2chunk_get_ts(disk, &disk->fs_last, parity_pos);
|
||||
fs_lock(disk);
|
||||
|
||||
chunk = fs_par2chunk_get_unlock(disk, &disk->fs_last, parity_pos);
|
||||
if (!chunk) {
|
||||
/* LCOV_EXCL_START */
|
||||
log_fatal("Internal inconsistency when deallocating parity position '%u' for not existing chunk in disk '%s'\n", parity_pos, disk->name);
|
||||
|
|
@ -1241,6 +1293,8 @@ void fs_deallocate(struct snapraid_disk* disk, block_off_t parity_pos)
|
|||
|
||||
/* clear the last accessed chunk */
|
||||
disk->fs_last = 0;
|
||||
|
||||
fs_unlock(disk);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -1249,12 +1303,16 @@ void fs_deallocate(struct snapraid_disk* disk, block_off_t parity_pos)
|
|||
++chunk->parity_pos;
|
||||
++chunk->file_pos;
|
||||
--chunk->count;
|
||||
|
||||
fs_unlock(disk);
|
||||
return;
|
||||
}
|
||||
|
||||
/* if it's at the end of the chunk, shrink the chunk */
|
||||
if (parity_pos == chunk->parity_pos + chunk->count - 1) {
|
||||
--chunk->count;
|
||||
|
||||
fs_unlock(disk);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -1281,6 +1339,8 @@ void fs_deallocate(struct snapraid_disk* disk, block_off_t parity_pos)
|
|||
|
||||
/* store the last accessed chunk */
|
||||
disk->fs_last = second_chunk;
|
||||
|
||||
fs_unlock(disk);
|
||||
}
|
||||
|
||||
struct snapraid_block* fs_file2block_get(struct snapraid_file* file, block_off_t file_pos)
|
||||
|
|
@ -1295,13 +1355,12 @@ struct snapraid_block* fs_file2block_get(struct snapraid_file* file, block_off_t
|
|||
return &file->blockvec[file_pos];
|
||||
}
|
||||
|
||||
struct snapraid_block* fs_par2block_get_ts(struct snapraid_disk* disk, struct snapraid_chunk** fs_last, block_off_t parity_pos)
|
||||
struct snapraid_block* fs_par2block_get(struct snapraid_disk* disk, block_off_t parity_pos)
|
||||
{
|
||||
struct snapraid_file* file;
|
||||
block_off_t file_pos;
|
||||
|
||||
file = fs_par2file_get_ts(disk, fs_last, parity_pos, &file_pos);
|
||||
|
||||
file = fs_par2file_get(disk, parity_pos, &file_pos);
|
||||
if (!file)
|
||||
return BLOCK_EMPTY;
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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
782
cmdline/io.c
Normal 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
362
cmdline/io.h
Normal 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
|
||||
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
469
cmdline/scrub.c
469
cmdline/scrub.c
|
|
@ -22,6 +22,7 @@
|
|||
#include "state.h"
|
||||
#include "parity.h"
|
||||
#include "handle.h"
|
||||
#include "io.h"
|
||||
#include "raid/raid.h"
|
||||
|
||||
/****************************************************************************/
|
||||
|
|
@ -39,6 +40,7 @@ struct snapraid_rehash {
|
|||
* Scrub plan to use.
|
||||
*/
|
||||
struct snapraid_plan {
|
||||
struct snapraid_state* state;
|
||||
int plan; /**< One of the SCRUB_*. */
|
||||
time_t timelimit; /**< Time limit. Valid only with SCRUB_AUTO. */
|
||||
block_off_t lastlimit; /**< Number of blocks allowed with time exactly at ::timelimit. */
|
||||
|
|
@ -48,13 +50,14 @@ struct snapraid_plan {
|
|||
/**
|
||||
* Check if we have to process the specified block index ::i.
|
||||
*/
|
||||
static int block_is_enabled(struct snapraid_state* state, block_off_t i, struct snapraid_plan* plan)
|
||||
static int block_is_enabled(void* void_plan, block_off_t i)
|
||||
{
|
||||
struct snapraid_plan* plan = void_plan;
|
||||
time_t blocktime;
|
||||
snapraid_info info;
|
||||
|
||||
/* don't scrub unused blocks in all plans */
|
||||
info = info_get(&state->infoarr, i);
|
||||
info = info_get(&plan->state->infoarr, i);
|
||||
if (info == 0)
|
||||
return 0;
|
||||
|
||||
|
|
@ -99,16 +102,156 @@ static int block_is_enabled(struct snapraid_state* state, block_off_t i, struct
|
|||
return 1;
|
||||
}
|
||||
|
||||
static int state_scrub_process(struct snapraid_state* state, struct snapraid_parity_handle** parity, block_off_t blockstart, block_off_t blockmax, struct snapraid_plan* plan, time_t now)
|
||||
static void scrub_data_reader(struct snapraid_worker* worker, struct snapraid_task* task)
|
||||
{
|
||||
struct snapraid_io* io = worker->io;
|
||||
struct snapraid_state* state = io->state;
|
||||
struct snapraid_handle* handle = worker->handle;
|
||||
struct snapraid_disk* disk = handle->disk;
|
||||
block_off_t blockcur = task->position;
|
||||
unsigned char* buffer = task->buffer;
|
||||
int ret;
|
||||
|
||||
/* if the disk position is not used */
|
||||
if (!disk) {
|
||||
/* use an empty block */
|
||||
memset(buffer, 0, state->block_size);
|
||||
task->state = TASK_STATE_DONE;
|
||||
return;
|
||||
}
|
||||
|
||||
/* get the block */
|
||||
task->block = fs_par2block_get(disk, blockcur);
|
||||
|
||||
/* if the block is not used */
|
||||
if (!block_has_file(task->block)) {
|
||||
/* use an empty block */
|
||||
memset(buffer, 0, state->block_size);
|
||||
task->state = TASK_STATE_DONE;
|
||||
return;
|
||||
}
|
||||
|
||||
/* get the file of this block */
|
||||
task->file = fs_par2file_get(disk, blockcur, &task->file_pos);
|
||||
|
||||
/* if the file is different than the current one, close it */
|
||||
if (handle->file != 0 && handle->file != task->file) {
|
||||
/* keep a pointer at the file we are going to close for error reporting */
|
||||
struct snapraid_file* report = handle->file;
|
||||
ret = handle_close(handle);
|
||||
if (ret == -1) {
|
||||
/* LCOV_EXCL_START */
|
||||
/* This one is really an unexpected error, because we are only reading */
|
||||
/* and closing a descriptor should never fail */
|
||||
if (errno == EIO) {
|
||||
log_tag("error:%u:%s:%s: Close EIO error. %s\n", blockcur, disk->name, esc(report->sub), strerror(errno));
|
||||
log_fatal("DANGER! Unexpected input/output close error in a data disk, it isn't possible to scrub.\n");
|
||||
log_fatal("Ensure that disk '%s' is sane and that file '%s' can be accessed.\n", disk->dir, handle->path);
|
||||
log_fatal("Stopping at block %u\n", blockcur);
|
||||
task->state = TASK_STATE_IOERROR;
|
||||
return;
|
||||
}
|
||||
|
||||
log_tag("error:%u:%s:%s: Close error. %s\n", blockcur, disk->name, esc(report->sub), strerror(errno));
|
||||
log_fatal("WARNING! Unexpected close error in a data disk, it isn't possible to scrub.\n");
|
||||
log_fatal("Ensure that file '%s' can be accessed.\n", handle->path);
|
||||
log_fatal("Stopping at block %u\n", blockcur);
|
||||
task->state = TASK_STATE_ERROR;
|
||||
return;
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
}
|
||||
|
||||
ret = handle_open(handle, task->file, state->file_mode, log_error, 0);
|
||||
if (ret == -1) {
|
||||
if (errno == EIO) {
|
||||
/* LCOV_EXCL_START */
|
||||
log_tag("error:%u:%s:%s: Open EIO error. %s\n", blockcur, disk->name, esc(task->file->sub), strerror(errno));
|
||||
log_fatal("DANGER! Unexpected input/output open error in a data disk, it isn't possible to scrub.\n");
|
||||
log_fatal("Ensure that disk '%s' is sane and that file '%s' can be accessed.\n", disk->dir, handle->path);
|
||||
log_fatal("Stopping at block %u\n", blockcur);
|
||||
task->state = TASK_STATE_IOERROR;
|
||||
return;
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
|
||||
log_tag("error:%u:%s:%s: Open error. %s\n", blockcur, disk->name, esc(task->file->sub), strerror(errno));
|
||||
task->state = TASK_STATE_ERROR_CONTINUE;
|
||||
return;
|
||||
}
|
||||
|
||||
/* check if the file is changed */
|
||||
if (handle->st.st_size != task->file->size
|
||||
|| handle->st.st_mtime != task->file->mtime_sec
|
||||
|| STAT_NSEC(&handle->st) != task->file->mtime_nsec
|
||||
/* don't check the inode to support filesystem without persistent inodes */
|
||||
) {
|
||||
/* report that the block and the file are not synced */
|
||||
task->is_timestamp_different = 1;
|
||||
/* follow */
|
||||
}
|
||||
|
||||
/* note that we intentionally don't abort if the file has different attributes */
|
||||
/* from the last sync, as we are expected to return errors if running */
|
||||
/* in an unsynced array. This is just like the check command. */
|
||||
|
||||
task->read_size = handle_read(handle, task->file_pos, buffer, state->block_size, log_error, 0);
|
||||
if (task->read_size == -1) {
|
||||
if (errno == EIO) {
|
||||
log_tag("error:%u:%s:%s: Read EIO error at position %u. %s\n", blockcur, disk->name, esc(task->file->sub), task->file_pos, strerror(errno));
|
||||
log_error("Input/Output error in file '%s' at position '%u'\n", handle->path, task->file_pos);
|
||||
task->state = TASK_STATE_IOERROR_CONTINUE;
|
||||
return;
|
||||
}
|
||||
|
||||
log_tag("error:%u:%s:%s: Read error at position %u. %s\n", blockcur, disk->name, esc(task->file->sub), task->file_pos, strerror(errno));
|
||||
task->state = TASK_STATE_ERROR_CONTINUE;
|
||||
return;
|
||||
}
|
||||
|
||||
/* store the path of the opened file */
|
||||
pathcpy(task->path, sizeof(task->path), handle->path);
|
||||
|
||||
task->state = TASK_STATE_DONE;
|
||||
}
|
||||
|
||||
static void scrub_parity_reader(struct snapraid_worker* worker, struct snapraid_task* task)
|
||||
{
|
||||
struct snapraid_io* io = worker->io;
|
||||
struct snapraid_state* state = io->state;
|
||||
struct snapraid_parity_handle* parity_handle = worker->parity_handle;
|
||||
unsigned level = parity_handle->level;
|
||||
block_off_t blockcur = task->position;
|
||||
unsigned char* buffer = task->buffer;
|
||||
int ret;
|
||||
|
||||
/* read the parity */
|
||||
ret = parity_read(parity_handle, blockcur, buffer, state->block_size, log_error);
|
||||
if (ret == -1) {
|
||||
if (errno == EIO) {
|
||||
log_tag("parity_error:%u:%s: Read EIO error. %s\n", blockcur, lev_config_name(level), strerror(errno));
|
||||
log_error("Input/Output error in parity '%s' at position '%u'\n", lev_config_name(level), blockcur);
|
||||
task->state = TASK_STATE_IOERROR_CONTINUE;
|
||||
return;
|
||||
}
|
||||
|
||||
log_tag("parity_error:%u:%s: Read error. %s\n", blockcur, lev_config_name(level), strerror(errno));
|
||||
task->state = TASK_STATE_ERROR_CONTINUE;
|
||||
return;
|
||||
}
|
||||
|
||||
task->state = TASK_STATE_DONE;
|
||||
}
|
||||
|
||||
static int state_scrub_process(struct snapraid_state* state, struct snapraid_parity_handle* parity_handle, block_off_t blockstart, block_off_t blockmax, struct snapraid_plan* plan, time_t now)
|
||||
{
|
||||
struct snapraid_io io;
|
||||
struct snapraid_handle* handle;
|
||||
void* rehandle_alloc;
|
||||
struct snapraid_rehash* rehandle;
|
||||
unsigned diskmax;
|
||||
block_off_t i;
|
||||
block_off_t blockcur;
|
||||
unsigned j;
|
||||
void* buffer_alloc;
|
||||
void** buffer;
|
||||
unsigned buffermax;
|
||||
data_off_t countsize;
|
||||
block_off_t countpos;
|
||||
|
|
@ -128,12 +271,11 @@ static int state_scrub_process(struct snapraid_state* state, struct snapraid_par
|
|||
/* rehash buffers */
|
||||
rehandle = malloc_nofail_align(diskmax * sizeof(struct snapraid_rehash), &rehandle_alloc);
|
||||
|
||||
/* we need disk + 2 for each parity level buffers */
|
||||
buffermax = diskmax + state->level * 2;
|
||||
/* we need 1 * data + 2 * parity */
|
||||
buffermax = diskmax + 2 * state->level;
|
||||
|
||||
buffer = malloc_nofail_vector_align(diskmax, buffermax, state->block_size, &buffer_alloc);
|
||||
if (!state->opt.skip_self)
|
||||
mtest_vector(buffermax, state->block_size, buffer);
|
||||
/* initialize the io threads */
|
||||
io_init(&io, state, buffermax, scrub_data_reader, handle, diskmax, scrub_parity_reader, 0, parity_handle, state->level);
|
||||
|
||||
error = 0;
|
||||
silent_error = 0;
|
||||
|
|
@ -142,10 +284,9 @@ static int state_scrub_process(struct snapraid_state* state, struct snapraid_par
|
|||
/* first count the number of blocks to process */
|
||||
countmax = 0;
|
||||
plan->countlast = 0;
|
||||
for (i = blockstart; i < blockmax; ++i) {
|
||||
if (!block_is_enabled(state, i, plan))
|
||||
for (blockcur = blockstart; blockcur < blockmax; ++blockcur) {
|
||||
if (!block_is_enabled(plan, blockcur))
|
||||
continue;
|
||||
|
||||
++countmax;
|
||||
}
|
||||
|
||||
|
|
@ -162,17 +303,25 @@ static int state_scrub_process(struct snapraid_state* state, struct snapraid_par
|
|||
countsize = 0;
|
||||
countpos = 0;
|
||||
plan->countlast = 0;
|
||||
|
||||
/* start all the worker threads */
|
||||
io_start(&io, blockstart, blockmax, &block_is_enabled, plan);
|
||||
|
||||
state_progress_begin(state, blockstart, blockmax, countmax);
|
||||
for (i = blockstart; i < blockmax; ++i) {
|
||||
while (1) {
|
||||
unsigned char* buffer_recov[LEV_MAX];
|
||||
snapraid_info info;
|
||||
int error_on_this_block;
|
||||
int silent_error_on_this_block;
|
||||
int io_error_on_this_block;
|
||||
int block_is_unsynced;
|
||||
int rehash;
|
||||
void** buffer;
|
||||
|
||||
if (!block_is_enabled(state, i, plan))
|
||||
continue;
|
||||
/* go to the next block */
|
||||
blockcur = io_read_next(&io, &buffer);
|
||||
if (blockcur >= blockmax)
|
||||
break;
|
||||
|
||||
/* one more block processed for autosave */
|
||||
++autosavedone;
|
||||
|
|
@ -188,45 +337,49 @@ static int state_scrub_process(struct snapraid_state* state, struct snapraid_par
|
|||
block_is_unsynced = 0;
|
||||
|
||||
/* get block specific info */
|
||||
info = info_get(&state->infoarr, i);
|
||||
info = info_get(&state->infoarr, blockcur);
|
||||
|
||||
/* if we have to use the old hash */
|
||||
rehash = info_get_rehash(info);
|
||||
|
||||
/* for each disk, process the block */
|
||||
for (j = 0; j < diskmax; ++j) {
|
||||
struct snapraid_task* task;
|
||||
int read_size;
|
||||
unsigned char hash[HASH_SIZE];
|
||||
struct snapraid_block* block;
|
||||
int file_is_unsynced;
|
||||
struct snapraid_disk* disk = handle[j].disk;
|
||||
struct snapraid_disk* disk;
|
||||
struct snapraid_file* file;
|
||||
block_off_t file_pos;
|
||||
unsigned diskcur;
|
||||
|
||||
/* if the file on this disk is synced */
|
||||
/* if not, silent errors are assumed as expected error */
|
||||
file_is_unsynced = 0;
|
||||
|
||||
/* until now is CPU */
|
||||
state_usage_cpu(state);
|
||||
|
||||
/* get the next task */
|
||||
task = io_data_read(&io, &diskcur);
|
||||
|
||||
/* get the task results */
|
||||
disk = task->disk;
|
||||
block = task->block;
|
||||
file = task->file;
|
||||
file_pos = task->file_pos;
|
||||
read_size = task->read_size;
|
||||
|
||||
/* by default no rehash in case of "continue" */
|
||||
rehandle[j].block = 0;
|
||||
rehandle[diskcur].block = 0;
|
||||
|
||||
/* if the disk position is not used */
|
||||
if (!disk) {
|
||||
/* use an empty block */
|
||||
memset(buffer[j], 0, state->block_size);
|
||||
if (!disk)
|
||||
continue;
|
||||
}
|
||||
|
||||
/* if the block is not used */
|
||||
block = fs_par2block_get(disk, i);
|
||||
if (!block_has_file(block)) {
|
||||
/* use an empty block */
|
||||
memset(buffer[j], 0, state->block_size);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* get the file of this block */
|
||||
file = fs_par2file_get(disk, i, &file_pos);
|
||||
/* until now is disk */
|
||||
state_usage_disk(state, disk);
|
||||
|
||||
/* if the block is unsynced, errors are expected */
|
||||
if (block_has_invalid_parity(block)) {
|
||||
|
|
@ -236,112 +389,65 @@ static int state_scrub_process(struct snapraid_state* state, struct snapraid_par
|
|||
/* follow */
|
||||
}
|
||||
|
||||
/* until now is CPU */
|
||||
state_usage_cpu(state);
|
||||
|
||||
/* if the file is different than the current one, close it */
|
||||
if (handle[j].file != 0 && handle[j].file != file) {
|
||||
/* keep a pointer at the file we are going to close for error reporting */
|
||||
struct snapraid_file* report = handle[j].file;
|
||||
ret = handle_close(&handle[j]);
|
||||
if (ret == -1) {
|
||||
/* LCOV_EXCL_START */
|
||||
/* This one is really an unexpected error, because we are only reading */
|
||||
/* and closing a descriptor should never fail */
|
||||
if (errno == EIO) {
|
||||
log_tag("error:%u:%s:%s: Close EIO error. %s\n", i, disk->name, esc(report->sub), strerror(errno));
|
||||
log_fatal("DANGER! Unexpected input/output close error in a data disk, it isn't possible to scrub.\n");
|
||||
log_fatal("Ensure that disk '%s' is sane and that file '%s' can be accessed.\n", disk->dir, handle[j].path);
|
||||
log_fatal("Stopping at block %u\n", i);
|
||||
++io_error;
|
||||
goto bail;
|
||||
}
|
||||
|
||||
log_tag("error:%u:%s:%s: Close error. %s\n", i, disk->name, esc(report->sub), strerror(errno));
|
||||
log_fatal("WARNING! Unexpected close error in a data disk, it isn't possible to scrub.\n");
|
||||
log_fatal("Ensure that file '%s' can be accessed.\n", handle[j].path);
|
||||
log_fatal("Stopping at block %u\n", i);
|
||||
++error;
|
||||
goto bail;
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
}
|
||||
|
||||
ret = handle_open(&handle[j], file, state->file_mode, log_error, 0);
|
||||
if (ret == -1) {
|
||||
if (errno == EIO) {
|
||||
/* LCOV_EXCL_START */
|
||||
log_tag("error:%u:%s:%s: Open EIO error. %s\n", i, disk->name, esc(file->sub), strerror(errno));
|
||||
log_fatal("DANGER! Unexpected input/output open error in a data disk, it isn't possible to scrub.\n");
|
||||
log_fatal("Ensure that disk '%s' is sane and that file '%s' can be accessed.\n", disk->dir, handle[j].path);
|
||||
log_fatal("Stopping at block %u\n", i);
|
||||
++io_error;
|
||||
goto bail;
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
|
||||
log_tag("error:%u:%s:%s: Open error. %s\n", i, disk->name, esc(file->sub), strerror(errno));
|
||||
++error;
|
||||
error_on_this_block = 1;
|
||||
/* if the block is not used */
|
||||
if (!block_has_file(block))
|
||||
continue;
|
||||
}
|
||||
|
||||
/* check if the file is changed */
|
||||
if (handle[j].st.st_size != file->size
|
||||
|| handle[j].st.st_mtime != file->mtime_sec
|
||||
|| STAT_NSEC(&handle[j].st) != file->mtime_nsec
|
||||
/* don't check the inode to support file-system without persistent inodes */
|
||||
) {
|
||||
/* if the block is unsynced, errors are expected */
|
||||
if (task->is_timestamp_different) {
|
||||
/* report that the block and the file are not synced */
|
||||
block_is_unsynced = 1;
|
||||
file_is_unsynced = 1;
|
||||
/* follow */
|
||||
}
|
||||
|
||||
/* note that we intentionally don't abort if the file has different attributes */
|
||||
/* from the last sync, as we are expected to return errors if running */
|
||||
/* in an unsynced array. This is just like the check command. */
|
||||
|
||||
read_size = handle_read(&handle[j], file_pos, buffer[j], state->block_size, log_error, 0);
|
||||
if (read_size == -1) {
|
||||
if (errno == EIO) {
|
||||
log_tag("error:%u:%s:%s: Read EIO error at position %u. %s\n", i, disk->name, esc(file->sub), file_pos, strerror(errno));
|
||||
if (io_error >= state->opt.io_error_limit) {
|
||||
/* LCOV_EXCL_START */
|
||||
log_fatal("DANGER! Too many input/output read error in a data disk, it isn't possible to scrub.\n");
|
||||
log_fatal("Ensure that disk '%s' is sane and that file '%s' can be accessed.\n", disk->dir, handle[j].path);
|
||||
log_fatal("Stopping at block %u\n", i);
|
||||
++io_error;
|
||||
goto bail;
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
|
||||
log_error("Input/Output error in file '%s' at position '%u'\n", handle[j].path, file_pos);
|
||||
++io_error;
|
||||
io_error_on_this_block = 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
log_tag("error:%u:%s:%s: Read error at position %u. %s\n", i, disk->name, esc(file->sub), file_pos, strerror(errno));
|
||||
/* handle error conditions */
|
||||
if (task->state == TASK_STATE_IOERROR) {
|
||||
++io_error;
|
||||
goto bail;
|
||||
}
|
||||
if (task->state == TASK_STATE_ERROR) {
|
||||
++error;
|
||||
goto bail;
|
||||
}
|
||||
if (task->state == TASK_STATE_ERROR_CONTINUE) {
|
||||
++error;
|
||||
error_on_this_block = 1;
|
||||
continue;
|
||||
}
|
||||
if (task->state == TASK_STATE_IOERROR_CONTINUE) {
|
||||
++io_error;
|
||||
if (io_error >= state->opt.io_error_limit) {
|
||||
/* LCOV_EXCL_START */
|
||||
log_fatal("DANGER! Too many input/output read error in a data disk, it isn't possible to scrub.\n");
|
||||
log_fatal("Ensure that disk '%s' is sane and that file '%s' can be accessed.\n", disk->dir, task->path);
|
||||
log_fatal("Stopping at block %u\n", blockcur);
|
||||
goto bail;
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
|
||||
/* until now is disk */
|
||||
state_usage_disk(state, disk);
|
||||
/* otherwise continue */
|
||||
io_error_on_this_block = 1;
|
||||
continue;
|
||||
}
|
||||
if (task->state != TASK_STATE_DONE) {
|
||||
/* LCOV_EXCL_START */
|
||||
log_fatal("Internal inconsistency in task state\n");
|
||||
os_abort();
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
|
||||
countsize += read_size;
|
||||
|
||||
/* now compute the hash */
|
||||
if (rehash) {
|
||||
memhash(state->prevhash, state->prevhashseed, hash, buffer[j], read_size);
|
||||
memhash(state->prevhash, state->prevhashseed, hash, buffer[diskcur], read_size);
|
||||
|
||||
/* compute the new hash, and store it */
|
||||
rehandle[j].block = block;
|
||||
memhash(state->hash, state->hashseed, rehandle[j].hash, buffer[j], read_size);
|
||||
rehandle[diskcur].block = block;
|
||||
memhash(state->hash, state->hashseed, rehandle[diskcur].hash, buffer[diskcur], read_size);
|
||||
} else {
|
||||
memhash(state->hash, state->hashseed, hash, buffer[j], read_size);
|
||||
memhash(state->hash, state->hashseed, hash, buffer[diskcur], read_size);
|
||||
}
|
||||
|
||||
if (block_has_updated_hash(block)) {
|
||||
|
|
@ -349,14 +455,14 @@ static int state_scrub_process(struct snapraid_state* state, struct snapraid_par
|
|||
if (memcmp(hash, block->hash, HASH_SIZE) != 0) {
|
||||
unsigned diff = memdiff(hash, block->hash, HASH_SIZE);
|
||||
|
||||
log_tag("error:%u:%s:%s: Data error at position %u, diff bits %u\n", i, disk->name, esc(file->sub), file_pos, diff);
|
||||
log_tag("error:%u:%s:%s: Data error at position %u, diff bits %u\n", blockcur, disk->name, esc(file->sub), file_pos, diff);
|
||||
|
||||
/* it's a silent error only if we are dealing with synced files */
|
||||
if (file_is_unsynced) {
|
||||
++error;
|
||||
error_on_this_block = 1;
|
||||
} else {
|
||||
log_error("Data error in file '%s' at position '%u', diff bits %u\n", handle[j].path, file_pos, diff);
|
||||
log_error("Data error in file '%s' at position '%u', diff bits %u\n", task->path, file_pos, diff);
|
||||
++silent_error;
|
||||
silent_error_on_this_block = 1;
|
||||
}
|
||||
|
|
@ -365,52 +471,70 @@ static int state_scrub_process(struct snapraid_state* state, struct snapraid_par
|
|||
}
|
||||
}
|
||||
|
||||
/* if we have read all the data required and it's correct, proceed with the parity check */
|
||||
if (!error_on_this_block && !silent_error_on_this_block && !io_error_on_this_block) {
|
||||
unsigned char* buffer_recov[LEV_MAX];
|
||||
/* buffers for parity read and not computed */
|
||||
for (l = 0; l < state->level; ++l)
|
||||
buffer_recov[l] = buffer[diskmax + state->level + l];
|
||||
for (; l < LEV_MAX; ++l)
|
||||
buffer_recov[l] = 0;
|
||||
|
||||
/* until now is CPU */
|
||||
state_usage_cpu(state);
|
||||
/* until now is CPU */
|
||||
state_usage_cpu(state);
|
||||
|
||||
/* buffers for parity read and not computed */
|
||||
for (l = 0; l < state->level; ++l)
|
||||
buffer_recov[l] = buffer[diskmax + state->level + l];
|
||||
for (; l < LEV_MAX; ++l)
|
||||
buffer_recov[l] = 0;
|
||||
/* read the parity */
|
||||
for (l = 0; l < state->level; ++l) {
|
||||
struct snapraid_task* task;
|
||||
unsigned levcur;
|
||||
|
||||
/* read the parity */
|
||||
for (l = 0; l < state->level; ++l) {
|
||||
ret = parity_read(parity[l], i, buffer_recov[l], state->block_size, log_error);
|
||||
if (ret == -1) {
|
||||
buffer_recov[l] = 0;
|
||||
task = io_parity_read(&io, &levcur);
|
||||
|
||||
if (errno == EIO) {
|
||||
log_tag("parity_error:%u:%s: Read EIO error. %s\n", i, lev_config_name(l), strerror(errno));
|
||||
if (io_error >= state->opt.io_error_limit) {
|
||||
/* LCOV_EXCL_START */
|
||||
log_fatal("DANGER! Too many input/output read error in the %s disk, it isn't possible to scrub.\n", lev_name(l));
|
||||
log_fatal("Ensure that disk '%s' is sane and can be read.\n", lev_config_name(l));
|
||||
log_fatal("Stopping at block %u\n", i);
|
||||
++io_error;
|
||||
goto bail;
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
/* until now is parity */
|
||||
state_usage_parity(state, levcur);
|
||||
|
||||
log_error("Input/Output error in parity '%s' at position '%u'\n", lev_config_name(l), i);
|
||||
++io_error;
|
||||
io_error_on_this_block = 1;
|
||||
continue;
|
||||
}
|
||||
/* handle error conditions */
|
||||
if (task->state == TASK_STATE_IOERROR) {
|
||||
++io_error;
|
||||
goto bail;
|
||||
}
|
||||
if (task->state == TASK_STATE_ERROR) {
|
||||
++error;
|
||||
goto bail;
|
||||
}
|
||||
if (task->state == TASK_STATE_ERROR_CONTINUE) {
|
||||
++error;
|
||||
error_on_this_block = 1;
|
||||
|
||||
log_tag("parity_error:%u:%s: Read error. %s\n", i, lev_config_name(l), strerror(errno));
|
||||
++error;
|
||||
error_on_this_block = 1;
|
||||
continue;
|
||||
/* if continuing on error, clear the missing buffer */
|
||||
buffer_recov[levcur] = 0;
|
||||
continue;
|
||||
}
|
||||
if (task->state == TASK_STATE_IOERROR_CONTINUE) {
|
||||
++io_error;
|
||||
if (io_error >= state->opt.io_error_limit) {
|
||||
/* LCOV_EXCL_START */
|
||||
log_fatal("DANGER! Too many input/output read error in the %s disk, it isn't possible to scrub.\n", lev_name(levcur));
|
||||
log_fatal("Ensure that disk '%s' is sane and can be read.\n", lev_config_name(levcur));
|
||||
log_fatal("Stopping at block %u\n", blockcur);
|
||||
goto bail;
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
|
||||
/* until now is parity */
|
||||
state_usage_parity(state, l);
|
||||
/* otherwise continue */
|
||||
io_error_on_this_block = 1;
|
||||
|
||||
/* if continuing on error, clear the missing buffer */
|
||||
buffer_recov[levcur] = 0;
|
||||
continue;
|
||||
}
|
||||
if (task->state != TASK_STATE_DONE) {
|
||||
/* LCOV_EXCL_START */
|
||||
log_fatal("Internal inconsistency in task state\n");
|
||||
os_abort();
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
}
|
||||
|
||||
/* if we have read all the data required and it's correct, proceed with the parity check */
|
||||
if (!error_on_this_block && !silent_error_on_this_block && !io_error_on_this_block) {
|
||||
|
||||
/* compute the parity */
|
||||
raid_gen(diskmax, state->level, state->block_size, buffer);
|
||||
|
|
@ -420,14 +544,14 @@ static int state_scrub_process(struct snapraid_state* state, struct snapraid_par
|
|||
if (buffer_recov[l] && memcmp(buffer[diskmax + l], buffer_recov[l], state->block_size) != 0) {
|
||||
unsigned diff = memdiff(buffer[diskmax + l], buffer_recov[l], state->block_size);
|
||||
|
||||
log_tag("parity_error:%u:%s: Data error, diff bits %u\n", i, lev_config_name(l), diff);
|
||||
log_tag("parity_error:%u:%s: Data error, diff bits %u\n", blockcur, lev_config_name(l), diff);
|
||||
|
||||
/* it's a silent error only if we are dealing with synced blocks */
|
||||
if (block_is_unsynced) {
|
||||
++error;
|
||||
error_on_this_block = 1;
|
||||
} else {
|
||||
log_fatal("Data error in parity '%s' at position '%u', diff bits %u\n", lev_config_name(l), i, diff);
|
||||
log_fatal("Data error in parity '%s' at position '%u', diff bits %u\n", lev_config_name(l), blockcur, diff);
|
||||
++silent_error;
|
||||
silent_error_on_this_block = 1;
|
||||
}
|
||||
|
|
@ -437,7 +561,7 @@ static int state_scrub_process(struct snapraid_state* state, struct snapraid_par
|
|||
|
||||
if (silent_error_on_this_block || io_error_on_this_block) {
|
||||
/* set the error status keeping other info */
|
||||
info_set(&state->infoarr, i, info_set_bad(info));
|
||||
info_set(&state->infoarr, blockcur, info_set_bad(info));
|
||||
} else if (error_on_this_block) {
|
||||
/* do nothing, as this is a generic error */
|
||||
/* likely caused by a not synced array */
|
||||
|
|
@ -453,7 +577,7 @@ static int state_scrub_process(struct snapraid_state* state, struct snapraid_par
|
|||
|
||||
/* update the time info of the block */
|
||||
/* and clear any other flag */
|
||||
info_set(&state->infoarr, i, info_make(now, 0, 0, 0));
|
||||
info_set(&state->infoarr, blockcur, info_make(now, 0, 0, 0));
|
||||
}
|
||||
|
||||
/* mark the state as needing write */
|
||||
|
|
@ -463,7 +587,7 @@ static int state_scrub_process(struct snapraid_state* state, struct snapraid_par
|
|||
++countpos;
|
||||
|
||||
/* progress */
|
||||
if (state_progress(state, i, countpos, countmax, countsize)) {
|
||||
if (state_progress(state, blockcur, countpos, countmax, countsize)) {
|
||||
/* LCOV_EXCL_START */
|
||||
break;
|
||||
/* LCOV_EXCL_STOP */
|
||||
|
|
@ -527,13 +651,16 @@ static int state_scrub_process(struct snapraid_state* state, struct snapraid_par
|
|||
log_flush();
|
||||
|
||||
bail:
|
||||
/* stop all the worker threads */
|
||||
io_stop(&io);
|
||||
|
||||
for (j = 0; j < diskmax; ++j) {
|
||||
struct snapraid_file* file = handle[j].file;
|
||||
struct snapraid_disk* disk = handle[j].disk;
|
||||
ret = handle_close(&handle[j]);
|
||||
if (ret == -1) {
|
||||
/* LCOV_EXCL_START */
|
||||
log_tag("error:%u:%s:%s: Close error. %s\n", i, disk->name, esc(file->sub), strerror(errno));
|
||||
log_tag("error:%u:%s:%s: Close error. %s\n", blockcur, disk->name, esc(file->sub), strerror(errno));
|
||||
log_fatal("DANGER! Unexpected close error in a data disk.\n");
|
||||
++error;
|
||||
/* continue, as we are already exiting */
|
||||
|
|
@ -542,9 +669,8 @@ bail:
|
|||
}
|
||||
|
||||
free(handle);
|
||||
free(buffer_alloc);
|
||||
free(buffer);
|
||||
free(rehandle_alloc);
|
||||
io_done(&io);
|
||||
|
||||
if (state->opt.expect_recoverable) {
|
||||
if (error + silent_error + io_error == 0)
|
||||
|
|
@ -578,8 +704,7 @@ int state_scrub(struct snapraid_state* state, int plan, int olderthan)
|
|||
block_off_t count;
|
||||
time_t recentlimit;
|
||||
int ret;
|
||||
struct snapraid_parity_handle parity[LEV_MAX];
|
||||
struct snapraid_parity_handle* parity_ptr[LEV_MAX];
|
||||
struct snapraid_parity_handle parity_handle[LEV_MAX];
|
||||
struct snapraid_plan ps;
|
||||
time_t* timemap;
|
||||
unsigned error;
|
||||
|
|
@ -605,6 +730,7 @@ int state_scrub(struct snapraid_state* state, int plan, int olderthan)
|
|||
countlimit = 0;
|
||||
recentlimit = 0;
|
||||
|
||||
ps.state = state;
|
||||
if (state->opt.force_scrub_even) {
|
||||
ps.plan = SCRUB_EVEN;
|
||||
} else if (plan == SCRUB_FULL) {
|
||||
|
|
@ -710,8 +836,7 @@ int state_scrub(struct snapraid_state* state, int plan, int olderthan)
|
|||
|
||||
/* open the file for reading */
|
||||
for (l = 0; l < state->level; ++l) {
|
||||
parity_ptr[l] = &parity[l];
|
||||
ret = parity_open(parity_ptr[l], state->parity[l].path, state->file_mode);
|
||||
ret = parity_open(&parity_handle[l], l, state->parity[l].path, state->file_mode);
|
||||
if (ret == -1) {
|
||||
/* LCOV_EXCL_START */
|
||||
log_fatal("WARNING! Without an accessible %s file, it isn't possible to scrub.\n", lev_name(l));
|
||||
|
|
@ -724,14 +849,14 @@ int state_scrub(struct snapraid_state* state, int plan, int olderthan)
|
|||
|
||||
error = 0;
|
||||
|
||||
ret = state_scrub_process(state, parity_ptr, 0, blockmax, &ps, now);
|
||||
ret = state_scrub_process(state, parity_handle, 0, blockmax, &ps, now);
|
||||
if (ret == -1) {
|
||||
++error;
|
||||
/* continue, as we are already exiting */
|
||||
}
|
||||
|
||||
for (l = 0; l < state->level; ++l) {
|
||||
ret = parity_close(parity_ptr[l]);
|
||||
ret = parity_close(&parity_handle[l]);
|
||||
if (ret == -1) {
|
||||
/* LCOV_EXCL_START */
|
||||
log_fatal("DANGER! Unexpected close error in %s disk.\n", lev_name(l));
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
||||
|
|
|
|||
674
cmdline/sync.c
674
cmdline/sync.c
|
|
@ -22,11 +22,20 @@
|
|||
#include "state.h"
|
||||
#include "parity.h"
|
||||
#include "handle.h"
|
||||
#include "io.h"
|
||||
#include "raid/raid.h"
|
||||
|
||||
/****************************************************************************/
|
||||
/* hash */
|
||||
|
||||
/**
|
||||
* Sync plan to use.
|
||||
*/
|
||||
struct snapraid_plan {
|
||||
unsigned handle_max;
|
||||
struct snapraid_handle* handle_map;
|
||||
};
|
||||
|
||||
static int state_hash_process(struct snapraid_state* state, block_off_t blockstart, block_off_t blockmax, int* skip_sync)
|
||||
{
|
||||
struct snapraid_handle* handle;
|
||||
|
|
@ -393,6 +402,21 @@ struct failed_struct {
|
|||
struct snapraid_block* block; /**< The failed block, or BLOCK_DELETED for a deleted block */
|
||||
};
|
||||
|
||||
/**
|
||||
* Comparison function for sorting by index.
|
||||
*/
|
||||
int failed_compare_by_index(const void* void_a, const void* void_b)
|
||||
{
|
||||
const struct failed_struct* a = void_a;
|
||||
const struct failed_struct* b = void_b;
|
||||
|
||||
if (a->index < b->index)
|
||||
return -1;
|
||||
if (a->index > b->index)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Buffer for storing the new hashes.
|
||||
*/
|
||||
|
|
@ -404,8 +428,9 @@ struct snapraid_rehash {
|
|||
/**
|
||||
* Check if we have to process the specified block index ::i.
|
||||
*/
|
||||
static int block_is_enabled(block_off_t i, struct snapraid_handle* handle, unsigned diskmax)
|
||||
static int block_is_enabled(void* void_plan, block_off_t i)
|
||||
{
|
||||
struct snapraid_plan* plan = void_plan;
|
||||
unsigned j;
|
||||
int one_invalid;
|
||||
int one_valid;
|
||||
|
|
@ -413,14 +438,15 @@ static int block_is_enabled(block_off_t i, struct snapraid_handle* handle, unsig
|
|||
/* for each disk */
|
||||
one_invalid = 0;
|
||||
one_valid = 0;
|
||||
for (j = 0; j < diskmax; ++j) {
|
||||
for (j = 0; j < plan->handle_max; ++j) {
|
||||
struct snapraid_block* block;
|
||||
struct snapraid_disk* disk = plan->handle_map[j].disk;
|
||||
|
||||
/* if no disk, nothing to check */
|
||||
if (!handle[j].disk)
|
||||
if (!disk)
|
||||
continue;
|
||||
|
||||
block = fs_par2block_get(handle[j].disk, i);
|
||||
block = fs_par2block_get(disk, i);
|
||||
|
||||
if (block_has_file(block))
|
||||
one_valid = 1;
|
||||
|
|
@ -436,16 +462,206 @@ static int block_is_enabled(block_off_t i, struct snapraid_handle* handle, unsig
|
|||
return 1;
|
||||
}
|
||||
|
||||
static int state_sync_process(struct snapraid_state* state, struct snapraid_parity_handle** parity, block_off_t blockstart, block_off_t blockmax)
|
||||
static void sync_data_reader(struct snapraid_worker* worker, struct snapraid_task* task)
|
||||
{
|
||||
struct snapraid_io* io = worker->io;
|
||||
struct snapraid_state* state = io->state;
|
||||
struct snapraid_handle* handle = worker->handle;
|
||||
struct snapraid_disk* disk = handle->disk;
|
||||
block_off_t blockcur = task->position;
|
||||
unsigned char* buffer = task->buffer;
|
||||
int ret;
|
||||
|
||||
/* if the disk position is not used */
|
||||
if (!disk) {
|
||||
/* use an empty block */
|
||||
memset(buffer, 0, state->block_size);
|
||||
task->state = TASK_STATE_DONE;
|
||||
return;
|
||||
}
|
||||
|
||||
/* get the block */
|
||||
task->block = fs_par2block_get(disk, blockcur);
|
||||
|
||||
/* if the block has no file, meanining that it's EMPTY or DELETED, */
|
||||
/* it doesn't partecipate in the new parity computation */
|
||||
if (!block_has_file(task->block)) {
|
||||
/* use an empty block */
|
||||
memset(buffer, 0, state->block_size);
|
||||
task->state = TASK_STATE_DONE;
|
||||
return;
|
||||
}
|
||||
|
||||
/* get the file of this block */
|
||||
task->file = fs_par2file_get(disk, blockcur, &task->file_pos);
|
||||
|
||||
/* if the file is different than the current one, close it */
|
||||
if (handle->file != 0 && handle->file != task->file) {
|
||||
/* keep a pointer at the file we are going to close for error reporting */
|
||||
struct snapraid_file* report = handle->file;
|
||||
ret = handle_close(handle);
|
||||
if (ret == -1) {
|
||||
/* LCOV_EXCL_START */
|
||||
/* This one is really an unexpected error, because we are only reading */
|
||||
/* and closing a descriptor should never fail */
|
||||
if (errno == EIO) {
|
||||
log_tag("error:%u:%s:%s: Close EIO error. %s\n", blockcur, disk->name, esc(report->sub), strerror(errno));
|
||||
log_fatal("DANGER! Unexpected input/output close error in a data disk, it isn't possible to sync.\n");
|
||||
log_fatal("Ensure that disk '%s' is sane and that file '%s' can be accessed.\n", disk->dir, handle->path);
|
||||
log_fatal("Stopping at block %u\n", blockcur);
|
||||
task->state = TASK_STATE_IOERROR;
|
||||
return;
|
||||
}
|
||||
|
||||
log_tag("error:%u:%s:%s: Close error. %s\n", blockcur, disk->name, esc(report->sub), strerror(errno));
|
||||
log_fatal("WARNING! Unexpected close error in a data disk, it isn't possible to sync.\n");
|
||||
log_fatal("Ensure that file '%s' can be accessed.\n", handle->path);
|
||||
log_fatal("Stopping at block %u\n", blockcur);
|
||||
task->state = TASK_STATE_ERROR;
|
||||
return;
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
}
|
||||
|
||||
ret = handle_open(handle, task->file, state->file_mode, log_error, 0);
|
||||
if (ret == -1) {
|
||||
if (errno == EIO) {
|
||||
/* LCOV_EXCL_START */
|
||||
log_tag("error:%u:%s:%s: Open EIO error. %s\n", blockcur, disk->name, esc(task->file->sub), strerror(errno));
|
||||
log_fatal("DANGER! Unexpected input/output open error in a data disk, it isn't possible to sync.\n");
|
||||
log_fatal("Ensure that disk '%s' is sane and that file '%s' can be accessed.\n", disk->dir, handle->path);
|
||||
log_fatal("Stopping at block %u\n", blockcur);
|
||||
task->state = TASK_STATE_IOERROR;
|
||||
return;
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
|
||||
if (errno == ENOENT) {
|
||||
log_tag("error:%u:%s:%s: Open ENOENT error. %s\n", blockcur, disk->name, esc(task->file->sub), strerror(errno));
|
||||
log_error("Missing file '%s'.\n", handle->path);
|
||||
log_error("WARNING! You cannot modify data disk during a sync.\n");
|
||||
log_error("Rerun the sync command when finished.\n");
|
||||
/* if the file is missing, it means that it was removed during sync */
|
||||
/* this isn't a serious error, so we skip this block, and continue with others */
|
||||
task->state = TASK_STATE_ERROR_CONTINUE;
|
||||
return;
|
||||
}
|
||||
|
||||
if (errno == EACCES) {
|
||||
log_tag("error:%u:%s:%s: Open EACCES error. %s\n", blockcur, disk->name, esc(task->file->sub), strerror(errno));
|
||||
log_error("No access at file '%s'.\n", handle->path);
|
||||
log_error("WARNING! Please fix the access permission in the data disk.\n");
|
||||
log_error("Rerun the sync command when finished.\n");
|
||||
/* this isn't a serious error, so we skip this block, and continue with others */
|
||||
task->state = TASK_STATE_ERROR_CONTINUE;
|
||||
return;
|
||||
}
|
||||
|
||||
/* LCOV_EXCL_START */
|
||||
log_tag("error:%u:%s:%s: Open error. %s\n", blockcur, disk->name, esc(task->file->sub), strerror(errno));
|
||||
log_fatal("WARNING! Unexpected open error in a data disk, it isn't possible to sync.\n");
|
||||
log_fatal("Ensure that file '%s' can be accessed.\n", handle->path);
|
||||
log_fatal("Stopping to allow recovery. Try with 'snapraid check -f %s'\n", task->file->sub);
|
||||
task->state = TASK_STATE_ERROR;
|
||||
return;
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
|
||||
/* check if the file is changed */
|
||||
if (handle->st.st_size != task->file->size
|
||||
|| handle->st.st_mtime != task->file->mtime_sec
|
||||
|| STAT_NSEC(&handle->st) != task->file->mtime_nsec
|
||||
|| handle->st.st_ino != task->file->inode
|
||||
) {
|
||||
log_tag("error:%u:%s:%s: Unexpected attribute change\n", blockcur, disk->name, esc(task->file->sub));
|
||||
if (handle->st.st_size != task->file->size) {
|
||||
log_error("Unexpected size change at file '%s' from %" PRIu64 " to %" PRIu64 ".\n", handle->path, task->file->size, handle->st.st_size);
|
||||
} else if (handle->st.st_mtime != task->file->mtime_sec
|
||||
|| STAT_NSEC(&handle->st) != task->file->mtime_nsec) {
|
||||
log_error("Unexpected time change at file '%s' from %" PRIu64 ".%d to %" PRIu64 ".%d.\n", handle->path, task->file->mtime_sec, task->file->mtime_nsec, (uint64_t)handle->st.st_mtime, (uint32_t)STAT_NSEC(&handle->st));
|
||||
} else {
|
||||
log_error("Unexpected inode change from %" PRIu64 " to %" PRIu64 " at file '%s'.\n", task->file->inode, (uint64_t)handle->st.st_ino, handle->path);
|
||||
}
|
||||
log_error("WARNING! You cannot modify files during a sync.\n");
|
||||
log_error("Rerun the sync command when finished.\n");
|
||||
/* if the file is changed, it means that it was modified during sync */
|
||||
/* this isn't a serious error, so we skip this block, and continue with others */
|
||||
task->state = TASK_STATE_ERROR_CONTINUE;
|
||||
return;
|
||||
}
|
||||
|
||||
task->read_size = handle_read(handle, task->file_pos, buffer, state->block_size, log_error, 0);
|
||||
if (task->read_size == -1) {
|
||||
/* LCOV_EXCL_START */
|
||||
if (errno == EIO) {
|
||||
log_tag("error:%u:%s:%s: Read EIO error at position %u. %s\n", blockcur, disk->name, esc(task->file->sub), task->file_pos, strerror(errno));
|
||||
log_error("Input/Output error in file '%s' at position '%u'\n", handle->path, task->file_pos);
|
||||
task->state = TASK_STATE_IOERROR_CONTINUE;
|
||||
return;
|
||||
}
|
||||
|
||||
log_tag("error:%u:%s:%s: Read error at position %u. %s\n", blockcur, disk->name, esc(task->file->sub), task->file_pos, strerror(errno));
|
||||
log_fatal("WARNING! Unexpected read error in a data disk, it isn't possible to sync.\n");
|
||||
log_fatal("Ensure that file '%s' can be read.\n", handle->path);
|
||||
log_fatal("Stopping to allow recovery. Try with 'snapraid check -f %s'\n", task->file->sub);
|
||||
task->state = TASK_STATE_ERROR;
|
||||
return;
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
|
||||
/* store the path of the opened file */
|
||||
pathcpy(task->path, sizeof(task->path), handle->path);
|
||||
|
||||
task->state = TASK_STATE_DONE;
|
||||
}
|
||||
|
||||
static void sync_parity_writer(struct snapraid_worker* worker, struct snapraid_task* task)
|
||||
{
|
||||
struct snapraid_io* io = worker->io;
|
||||
struct snapraid_state* state = io->state;
|
||||
struct snapraid_parity_handle* parity_handle = worker->parity_handle;
|
||||
unsigned level = parity_handle->level;
|
||||
block_off_t blockcur = task->position;
|
||||
unsigned char* buffer = task->buffer;
|
||||
int ret;
|
||||
|
||||
/* write parity */
|
||||
ret = parity_write(parity_handle, blockcur, buffer, state->block_size);
|
||||
if (ret == -1) {
|
||||
/* LCOV_EXCL_START */
|
||||
if (errno == EIO) {
|
||||
log_tag("parity_error:%u:%s: Write EIO error. %s\n", blockcur, lev_config_name(level), strerror(errno));
|
||||
log_error("Input/Output error in parity '%s' at position '%u'\n", lev_config_name(level), blockcur);
|
||||
task->state = TASK_STATE_IOERROR_CONTINUE;
|
||||
return;
|
||||
}
|
||||
|
||||
log_tag("parity_error:%u:%s: Write error. %s\n", blockcur, lev_config_name(level), strerror(errno));
|
||||
log_fatal("WARNING! Unexpected write error in the %s disk, it isn't possible to sync.\n", lev_name(level));
|
||||
log_fatal("Ensure that disk '%s' has some free space available.\n", lev_config_name(level));
|
||||
log_fatal("Stopping at block %u\n", blockcur);
|
||||
task->state = TASK_STATE_ERROR;
|
||||
return;
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
|
||||
task->state = TASK_STATE_DONE;
|
||||
}
|
||||
|
||||
static int state_sync_process(struct snapraid_state* state, struct snapraid_parity_handle* parity_handle, block_off_t blockstart, block_off_t blockmax)
|
||||
{
|
||||
struct snapraid_io io;
|
||||
struct snapraid_plan plan;
|
||||
struct snapraid_handle* handle;
|
||||
void* rehandle_alloc;
|
||||
struct snapraid_rehash* rehandle;
|
||||
unsigned diskmax;
|
||||
block_off_t i;
|
||||
block_off_t blockcur;
|
||||
unsigned j;
|
||||
void* buffer_alloc;
|
||||
void** buffer;
|
||||
void* zero_alloc;
|
||||
void** zero;
|
||||
void* copy_alloc;
|
||||
void** copy;
|
||||
unsigned buffermax;
|
||||
data_off_t countsize;
|
||||
block_off_t countpos;
|
||||
|
|
@ -475,16 +691,19 @@ static int state_sync_process(struct snapraid_state* state, struct snapraid_pari
|
|||
/* rehash buffers */
|
||||
rehandle = malloc_nofail_align(diskmax * sizeof(struct snapraid_rehash), &rehandle_alloc);
|
||||
|
||||
/* we need 2 * data + 1 * parity + 1 * zero */
|
||||
buffermax = 2 * diskmax + state->level + 1;
|
||||
/* we need 1 * data + 1 * parity */
|
||||
buffermax = diskmax + state->level;
|
||||
|
||||
buffer = malloc_nofail_vector_align(diskmax, buffermax, state->block_size, &buffer_alloc);
|
||||
if (!state->opt.skip_self)
|
||||
mtest_vector(buffermax, state->block_size, buffer);
|
||||
/* initialize the io threads */
|
||||
io_init(&io, state, buffermax, sync_data_reader, handle, diskmax, 0, sync_parity_writer, parity_handle, state->level);
|
||||
|
||||
/* fill up the zero buffer */
|
||||
memset(buffer[buffermax - 1], 0, state->block_size);
|
||||
raid_zero(buffer[buffermax - 1]);
|
||||
/* allocate the copy buffer */
|
||||
copy = malloc_nofail_vector_align(diskmax, diskmax, state->block_size, ©_alloc);
|
||||
|
||||
/* allocate and fill the zero buffer */
|
||||
zero = malloc_nofail_align(state->block_size, &zero_alloc);
|
||||
memset(zero, 0, state->block_size);
|
||||
raid_zero(zero);
|
||||
|
||||
failed = malloc_nofail(diskmax * sizeof(struct failed_struct));
|
||||
failed_map = malloc_nofail(diskmax * sizeof(unsigned));
|
||||
|
|
@ -495,8 +714,10 @@ static int state_sync_process(struct snapraid_state* state, struct snapraid_pari
|
|||
|
||||
/* first count the number of blocks to process */
|
||||
countmax = 0;
|
||||
for (i = blockstart; i < blockmax; ++i) {
|
||||
if (!block_is_enabled(i, handle, diskmax))
|
||||
plan.handle_max = diskmax;
|
||||
plan.handle_map = handle;
|
||||
for (blockcur = blockstart; blockcur < blockmax; ++blockcur) {
|
||||
if (!block_is_enabled(&plan, blockcur))
|
||||
continue;
|
||||
++countmax;
|
||||
}
|
||||
|
|
@ -513,27 +734,36 @@ static int state_sync_process(struct snapraid_state* state, struct snapraid_pari
|
|||
|
||||
countsize = 0;
|
||||
countpos = 0;
|
||||
|
||||
/* start all the worker threads */
|
||||
io_start(&io, blockstart, blockmax, &block_is_enabled, &plan);
|
||||
|
||||
if (!state_progress_begin(state, blockstart, blockmax, countmax))
|
||||
goto end;
|
||||
|
||||
for (i = blockstart; i < blockmax; ++i) {
|
||||
while (1) {
|
||||
unsigned failed_count;
|
||||
int error_on_this_block;
|
||||
int silent_error_on_this_block;
|
||||
int io_error_on_this_block;
|
||||
int fixed_error_on_this_block;
|
||||
int parity_needs_to_be_updated;
|
||||
int parity_going_to_be_updated;
|
||||
snapraid_info info;
|
||||
int rehash;
|
||||
void** buffer;
|
||||
int writer_error[IO_WRITER_ERROR_MAX];
|
||||
|
||||
if (!block_is_enabled(i, handle, diskmax))
|
||||
continue;
|
||||
/* go to the next block */
|
||||
blockcur = io_read_next(&io, &buffer);
|
||||
if (blockcur >= blockmax)
|
||||
break;
|
||||
|
||||
/* one more block processed for autosave */
|
||||
++autosavedone;
|
||||
--autosavemissing;
|
||||
|
||||
/* by default process the block, and skip it if something go wrong */
|
||||
/* by default process the block, and skip it if something goes wrong */
|
||||
error_on_this_block = 0;
|
||||
silent_error_on_this_block = 0;
|
||||
io_error_on_this_block = 0;
|
||||
|
|
@ -543,19 +773,23 @@ static int state_sync_process(struct snapraid_state* state, struct snapraid_pari
|
|||
failed_count = 0;
|
||||
|
||||
/* get block specific info */
|
||||
info = info_get(&state->infoarr, i);
|
||||
info = info_get(&state->infoarr, blockcur);
|
||||
|
||||
/* if we have to use the old hash */
|
||||
rehash = info_get_rehash(info);
|
||||
|
||||
/* it could happens that all the blocks are EMPTY/BLK and CHG but with the hash */
|
||||
/* if the parity requires to be updated */
|
||||
/* It could happens that all the blocks are EMPTY/BLK and CHG but with the hash */
|
||||
/* still matching because the specific CHG block was not modified. */
|
||||
/* In such case, we can avoid to update parity, because it would be the same as before */
|
||||
/* Note that CHG/DELETED blocks already present in the content file loaded */
|
||||
/* have the hash cleared (::clear_past_hash flag), and then they won't never match the hash. */
|
||||
/* We are treating only CHG blocks created at runtime. */
|
||||
/* In such case, we can avoid to update parity, because it would be the same as before */
|
||||
parity_needs_to_be_updated = state->opt.force_parity_update;
|
||||
|
||||
/* if the parity is going to be updated */
|
||||
parity_going_to_be_updated = 0;
|
||||
|
||||
/* if the block is marked as bad, we force the parity update */
|
||||
/* because the bad block may be the result of a wrong parity */
|
||||
if (info_get_bad(info))
|
||||
|
|
@ -563,26 +797,37 @@ static int state_sync_process(struct snapraid_state* state, struct snapraid_pari
|
|||
|
||||
/* for each disk, process the block */
|
||||
for (j = 0; j < diskmax; ++j) {
|
||||
struct snapraid_task* task;
|
||||
int read_size;
|
||||
unsigned char hash[HASH_SIZE];
|
||||
struct snapraid_block* block;
|
||||
unsigned block_state;
|
||||
struct snapraid_disk* disk = handle[j].disk;
|
||||
struct snapraid_disk* disk;
|
||||
struct snapraid_file* file;
|
||||
block_off_t file_pos;
|
||||
unsigned diskcur;
|
||||
|
||||
/* until now is CPU */
|
||||
state_usage_cpu(state);
|
||||
|
||||
task = io_data_read(&io, &diskcur);
|
||||
|
||||
/* get the results */
|
||||
disk = task->disk;
|
||||
block = task->block;
|
||||
file = task->file;
|
||||
file_pos = task->file_pos;
|
||||
read_size = task->read_size;
|
||||
|
||||
/* by default no rehash in case of "continue" */
|
||||
rehandle[j].block = 0;
|
||||
rehandle[diskcur].block = 0;
|
||||
|
||||
/* if the disk position is not used */
|
||||
if (!disk) {
|
||||
/* use an empty block */
|
||||
memset(buffer[j], 0, state->block_size);
|
||||
if (!disk)
|
||||
continue;
|
||||
}
|
||||
|
||||
/* get the block */
|
||||
block = fs_par2block_get(disk, i);
|
||||
/* until now is disk */
|
||||
state_usage_disk(state, disk);
|
||||
|
||||
/* get the state of the block */
|
||||
block_state = block_state_get(block);
|
||||
|
|
@ -592,7 +837,7 @@ static int state_sync_process(struct snapraid_state* state, struct snapraid_pari
|
|||
if (block_has_invalid_parity(block)) {
|
||||
/* store it in the failed set, because */
|
||||
/* the parity may be still computed with the previous content */
|
||||
failed[failed_count].index = j;
|
||||
failed[failed_count].index = diskcur;
|
||||
failed[failed_count].size = state->block_size;
|
||||
failed[failed_count].block = block;
|
||||
++failed_count;
|
||||
|
|
@ -611,160 +856,55 @@ static int state_sync_process(struct snapraid_state* state, struct snapraid_pari
|
|||
/* follow */
|
||||
}
|
||||
|
||||
/* if the block has no file, meanining that it's EMPTY or DELETED, */
|
||||
/* it doesn't partecipate in the new parity computation */
|
||||
if (!block_has_file(block)) {
|
||||
/* use an empty block */
|
||||
memset(buffer[j], 0, state->block_size);
|
||||
/* if the block is not used */
|
||||
if (!block_has_file(block))
|
||||
continue;
|
||||
|
||||
/* handle error conditions */
|
||||
if (task->state == TASK_STATE_IOERROR) {
|
||||
++io_error;
|
||||
goto bail;
|
||||
}
|
||||
|
||||
/* get the file of this block */
|
||||
file = fs_par2file_get(disk, i, &file_pos);
|
||||
|
||||
/* until now is CPU */
|
||||
state_usage_cpu(state);
|
||||
|
||||
/* if the file is different than the current one, close it */
|
||||
if (handle[j].file != 0 && handle[j].file != file) {
|
||||
/* keep a pointer at the file we are going to close for error reporting */
|
||||
struct snapraid_file* report = handle[j].file;
|
||||
ret = handle_close(&handle[j]);
|
||||
if (ret == -1) {
|
||||
/* LCOV_EXCL_START */
|
||||
/* This one is really an unexpected error, because we are only reading */
|
||||
/* and closing a descriptor should never fail */
|
||||
if (errno == EIO) {
|
||||
log_tag("error:%u:%s:%s: Close EIO error. %s\n", i, disk->name, esc(report->sub), strerror(errno));
|
||||
log_fatal("DANGER! Unexpected input/output close error in a data disk, it isn't possible to sync.\n");
|
||||
log_fatal("Ensure that disk '%s' is sane and that file '%s' can be accessed.\n", disk->dir, handle[j].path);
|
||||
log_fatal("Stopping at block %u\n", i);
|
||||
++io_error;
|
||||
goto bail;
|
||||
}
|
||||
|
||||
log_tag("error:%u:%s:%s: Close error. %s\n", i, disk->name, esc(report->sub), strerror(errno));
|
||||
log_fatal("WARNING! Unexpected close error in a data disk, it isn't possible to sync.\n");
|
||||
log_fatal("Ensure that file '%s' can be accessed.\n", handle[j].path);
|
||||
log_fatal("Stopping at block %u\n", i);
|
||||
++error;
|
||||
goto bail;
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
}
|
||||
|
||||
ret = handle_open(&handle[j], file, state->file_mode, log_error, 0);
|
||||
if (ret == -1) {
|
||||
if (errno == EIO) {
|
||||
/* LCOV_EXCL_START */
|
||||
log_tag("error:%u:%s:%s: Open EIO error. %s\n", i, disk->name, esc(file->sub), strerror(errno));
|
||||
log_fatal("DANGER! Unexpected input/output open error in a data disk, it isn't possible to sync.\n");
|
||||
log_fatal("Ensure that disk '%s' is sane and that file '%s' can be accessed.\n", disk->dir, handle[j].path);
|
||||
log_fatal("Stopping at block %u\n", i);
|
||||
++io_error;
|
||||
goto bail;
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
|
||||
if (errno == ENOENT) {
|
||||
log_tag("error:%u:%s:%s: Open ENOENT error. %s\n", i, disk->name, esc(file->sub), strerror(errno));
|
||||
log_error("Missing file '%s'.\n", handle[j].path);
|
||||
log_error("WARNING! You cannot modify data disk during a sync.\n");
|
||||
log_error("Rerun the sync command when finished.\n");
|
||||
++error;
|
||||
/* if the file is missing, it means that it was removed during sync */
|
||||
/* this isn't a serious error, so we skip this block, and continue with others */
|
||||
error_on_this_block = 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (errno == EACCES) {
|
||||
log_tag("error:%u:%s:%s: Open EACCES error. %s\n", i, disk->name, esc(file->sub), strerror(errno));
|
||||
log_error("No access at file '%s'.\n", handle[j].path);
|
||||
log_error("WARNING! Please fix the access permission in the data disk.\n");
|
||||
log_error("Rerun the sync command when finished.\n");
|
||||
++error;
|
||||
/* this isn't a serious error, so we skip this block, and continue with others */
|
||||
error_on_this_block = 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* LCOV_EXCL_START */
|
||||
log_tag("error:%u:%s:%s: Open error. %s\n", i, disk->name, esc(file->sub), strerror(errno));
|
||||
log_fatal("WARNING! Unexpected open error in a data disk, it isn't possible to sync.\n");
|
||||
log_fatal("Ensure that file '%s' can be accessed.\n", handle[j].path);
|
||||
log_fatal("Stopping to allow recovery. Try with 'snapraid check -f %s'\n", file->sub);
|
||||
if (task->state == TASK_STATE_ERROR) {
|
||||
++error;
|
||||
goto bail;
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
|
||||
/* check if the file is changed */
|
||||
if (handle[j].st.st_size != file->size
|
||||
|| handle[j].st.st_mtime != file->mtime_sec
|
||||
|| STAT_NSEC(&handle[j].st) != file->mtime_nsec
|
||||
|| handle[j].st.st_ino != file->inode
|
||||
) {
|
||||
log_tag("error:%u:%s:%s: Unexpected attribute change\n", i, disk->name, esc(file->sub));
|
||||
if (handle[j].st.st_size != file->size) {
|
||||
log_error("Unexpected size change at file '%s' from %" PRIu64 " to %" PRIu64 ".\n", handle[j].path, file->size, handle[j].st.st_size);
|
||||
} else if (handle[j].st.st_mtime != file->mtime_sec
|
||||
|| STAT_NSEC(&handle[j].st) != file->mtime_nsec) {
|
||||
log_error("Unexpected time change at file '%s' from %" PRIu64 ".%d to %" PRIu64 ".%d.\n", handle[j].path, file->mtime_sec, file->mtime_nsec, (uint64_t)handle[j].st.st_mtime, (uint32_t)STAT_NSEC(&handle[j].st));
|
||||
} else {
|
||||
log_error("Unexpected inode change from %" PRIu64 " to %" PRIu64 " at file '%s'.\n", file->inode, (uint64_t)handle[j].st.st_ino, handle[j].path);
|
||||
}
|
||||
log_error("WARNING! You cannot modify files during a sync.\n");
|
||||
log_error("Rerun the sync command when finished.\n");
|
||||
if (task->state == TASK_STATE_ERROR_CONTINUE) {
|
||||
++error;
|
||||
/* if the file is changed, it means that it was modified during sync */
|
||||
/* this isn't a serious error, so we skip this block, and continue with others */
|
||||
error_on_this_block = 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
read_size = handle_read(&handle[j], file_pos, buffer[j], state->block_size, log_error, 0);
|
||||
if (read_size == -1) {
|
||||
/* LCOV_EXCL_START */
|
||||
if (errno == EIO) {
|
||||
log_tag("error:%u:%s:%s: Read EIO error at position %u. %s\n", i, disk->name, esc(file->sub), file_pos, strerror(errno));
|
||||
if (io_error >= state->opt.io_error_limit) {
|
||||
log_fatal("DANGER! Unexpected input/output read error in a data disk, it isn't possible to sync.\n");
|
||||
log_fatal("Ensure that disk '%s' is sane and that file '%s' can be read.\n", disk->dir, handle[j].path);
|
||||
log_fatal("Stopping at block %u\n", i);
|
||||
++io_error;
|
||||
goto bail;
|
||||
}
|
||||
|
||||
log_error("Input/Output error in file '%s' at position '%u'\n", handle[j].path, file_pos);
|
||||
++io_error;
|
||||
io_error_on_this_block = 1;
|
||||
continue;
|
||||
if (task->state == TASK_STATE_IOERROR_CONTINUE) {
|
||||
++io_error;
|
||||
if (io_error >= state->opt.io_error_limit) {
|
||||
log_fatal("DANGER! Unexpected input/output read error in a data disk, it isn't possible to sync.\n");
|
||||
log_fatal("Ensure that disk '%s' is sane and that file '%s' can be read.\n", disk->dir, task->path);
|
||||
log_fatal("Stopping at block %u\n", blockcur);
|
||||
goto bail;
|
||||
}
|
||||
|
||||
log_tag("error:%u:%s:%s: Read error at position %u. %s\n", i, disk->name, esc(file->sub), file_pos, strerror(errno));
|
||||
log_fatal("WARNING! Unexpected read error in a data disk, it isn't possible to sync.\n");
|
||||
log_fatal("Ensure that file '%s' can be read.\n", handle[j].path);
|
||||
log_fatal("Stopping to allow recovery. Try with 'snapraid check -f %s'\n", file->sub);
|
||||
++error;
|
||||
goto bail;
|
||||
/* otherwise continue */
|
||||
io_error_on_this_block = 1;
|
||||
continue;
|
||||
}
|
||||
if (task->state != TASK_STATE_DONE) {
|
||||
/* LCOV_EXCL_START */
|
||||
log_fatal("Internal inconsistency in task state\n");
|
||||
os_abort();
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
|
||||
/* until now is disk */
|
||||
state_usage_disk(state, disk);
|
||||
|
||||
countsize += read_size;
|
||||
|
||||
/* now compute the hash */
|
||||
if (rehash) {
|
||||
memhash(state->prevhash, state->prevhashseed, hash, buffer[j], read_size);
|
||||
memhash(state->prevhash, state->prevhashseed, hash, buffer[diskcur], read_size);
|
||||
|
||||
/* compute the new hash, and store it */
|
||||
rehandle[j].block = block;
|
||||
memhash(state->hash, state->hashseed, rehandle[j].hash, buffer[j], read_size);
|
||||
rehandle[diskcur].block = block;
|
||||
memhash(state->hash, state->hashseed, rehandle[diskcur].hash, buffer[diskcur], read_size);
|
||||
} else {
|
||||
memhash(state->hash, state->hashseed, hash, buffer[j], read_size);
|
||||
memhash(state->hash, state->hashseed, hash, buffer[diskcur], read_size);
|
||||
}
|
||||
|
||||
if (block_has_updated_hash(block)) {
|
||||
|
|
@ -772,8 +912,8 @@ static int state_sync_process(struct snapraid_state* state, struct snapraid_pari
|
|||
if (memcmp(hash, block->hash, HASH_SIZE) != 0) {
|
||||
/* if the file has invalid parity, it's a REP changed during the sync */
|
||||
if (block_has_invalid_parity(block)) {
|
||||
log_tag("error:%u:%s:%s: Unexpected data change\n", i, disk->name, esc(file->sub));
|
||||
log_error("Data change at file '%s' at position '%u'\n", handle[j].path, file_pos);
|
||||
log_tag("error:%u:%s:%s: Unexpected data change\n", blockcur, disk->name, esc(file->sub));
|
||||
log_error("Data change at file '%s' at position '%u'\n", task->path, file_pos);
|
||||
log_error("WARNING! Unexpected data modification of a file without parity!\n");
|
||||
|
||||
if (file_flag_has(file, FILE_IS_COPY)) {
|
||||
|
|
@ -793,11 +933,11 @@ static int state_sync_process(struct snapraid_state* state, struct snapraid_pari
|
|||
continue;
|
||||
} else { /* otherwise it's a BLK with silent error */
|
||||
unsigned diff = memdiff(hash, block->hash, HASH_SIZE);
|
||||
log_tag("error:%u:%s:%s: Data error at position %u, diff bits %u\n", i, disk->name, esc(file->sub), file_pos, diff);
|
||||
log_error("Data error in file '%s' at position '%u', diff bits %u\n", handle[j].path, file_pos, diff);
|
||||
log_tag("error:%u:%s:%s: Data error at position %u, diff bits %u\n", blockcur, disk->name, esc(file->sub), file_pos, diff);
|
||||
log_error("Data error in file '%s' at position '%u', diff bits %u\n", task->path, file_pos, diff);
|
||||
|
||||
/* save the failed block for the fix */
|
||||
failed[failed_count].index = j;
|
||||
failed[failed_count].index = diskcur;
|
||||
failed[failed_count].size = read_size;
|
||||
failed[failed_count].block = block;
|
||||
++failed_count;
|
||||
|
|
@ -847,11 +987,16 @@ static int state_sync_process(struct snapraid_state* state, struct snapraid_pari
|
|||
unsigned failed_mac;
|
||||
int something_to_recover = 0;
|
||||
|
||||
/* sort the failed vector */
|
||||
/* because with threads it may be in any order */
|
||||
/* but RAID requires the indexes to be sorted */
|
||||
qsort(failed, failed_count, sizeof(failed[0]), failed_compare_by_index);
|
||||
|
||||
/* setup the blocks to recover */
|
||||
failed_mac = 0;
|
||||
for (j = 0; j < failed_count; ++j) {
|
||||
unsigned char* block_buffer = buffer[failed[j].index];
|
||||
unsigned char* block_copy = buffer[diskmax + state->level + failed[j].index];
|
||||
unsigned char* block_copy = copy[failed[j].index];
|
||||
unsigned block_state = block_state_get(failed[j].block);
|
||||
|
||||
/* we try to recover only if at least one BLK is present */
|
||||
|
|
@ -887,29 +1032,29 @@ static int state_sync_process(struct snapraid_state* state, struct snapraid_pari
|
|||
/* we are sure that parity exists because */
|
||||
/* we have at least one BLK block */
|
||||
for (l = 0; l < state->level; ++l) {
|
||||
ret = parity_read(parity[l], i, buffer[diskmax + l], state->block_size, log_error);
|
||||
ret = parity_read(&parity_handle[l], blockcur, buffer[diskmax + l], state->block_size, log_error);
|
||||
if (ret == -1) {
|
||||
/* LCOV_EXCL_START */
|
||||
if (errno == EIO) {
|
||||
log_tag("parity_error:%u:%s: Read EIO error. %s\n", i, lev_config_name(l), strerror(errno));
|
||||
log_tag("parity_error:%u:%s: Read EIO error. %s\n", blockcur, lev_config_name(l), strerror(errno));
|
||||
if (io_error >= state->opt.io_error_limit) {
|
||||
log_fatal("DANGER! Unexpected input/output read error in the %s disk, it isn't possible to sync.\n", lev_name(l));
|
||||
log_fatal("Ensure that disk '%s' is sane and can be read.\n", lev_config_name(l));
|
||||
log_fatal("Stopping at block %u\n", i);
|
||||
log_fatal("Stopping at block %u\n", blockcur);
|
||||
++io_error;
|
||||
goto bail;
|
||||
}
|
||||
|
||||
log_error("Input/Output error in parity '%s' at position '%u'\n", lev_config_name(l), i);
|
||||
log_error("Input/Output error in parity '%s' at position '%u'\n", lev_config_name(l), blockcur);
|
||||
++io_error;
|
||||
io_error_on_this_block = 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
log_tag("parity_error:%u:%s: Read error. %s\n", i, lev_config_name(l), strerror(errno));
|
||||
log_tag("parity_error:%u:%s: Read error. %s\n", blockcur, lev_config_name(l), strerror(errno));
|
||||
log_fatal("WARNING! Unexpected read error in the %s disk, it isn't possible to sync.\n", lev_name(l));
|
||||
log_fatal("Ensure that disk '%s' can be read.\n", lev_config_name(l));
|
||||
log_fatal("Stopping at block %u\n", i);
|
||||
log_fatal("Stopping at block %u\n", blockcur);
|
||||
++error;
|
||||
goto bail;
|
||||
/* LCOV_EXCL_STOP */
|
||||
|
|
@ -931,7 +1076,7 @@ static int state_sync_process(struct snapraid_state* state, struct snapraid_pari
|
|||
for (j = 0; j < failed_count; ++j) {
|
||||
unsigned char hash[HASH_SIZE];
|
||||
unsigned char* block_buffer = buffer[failed[j].index];
|
||||
unsigned char* block_copy = buffer[diskmax + state->level + failed[j].index];
|
||||
unsigned char* block_copy = copy[failed[j].index];
|
||||
unsigned block_state = block_state_get(failed[j].block);
|
||||
|
||||
if (block_state == BLOCK_STATE_BLK) {
|
||||
|
|
@ -977,70 +1122,33 @@ static int state_sync_process(struct snapraid_state* state, struct snapraid_pari
|
|||
/* compute the parity */
|
||||
raid_gen(diskmax, state->level, state->block_size, buffer);
|
||||
|
||||
/* until now is CPU */
|
||||
state_usage_cpu(state);
|
||||
|
||||
/* write the parity */
|
||||
for (l = 0; l < state->level; ++l) {
|
||||
ret = parity_write(parity[l], i, buffer[diskmax + l], state->block_size);
|
||||
if (ret == -1) {
|
||||
/* LCOV_EXCL_START */
|
||||
if (errno == EIO) {
|
||||
log_tag("parity_error:%u:%s: Write EIO error. %s\n", i, lev_config_name(l), strerror(errno));
|
||||
if (io_error >= state->opt.io_error_limit) {
|
||||
log_fatal("DANGER! Unexpected input/output write error in the %s disk, it isn't possible to sync.\n", lev_name(l));
|
||||
log_fatal("Ensure that disk '%s' is sane and can be written.\n", lev_config_name(l));
|
||||
log_fatal("Stopping at block %u\n", i);
|
||||
++io_error;
|
||||
goto bail;
|
||||
}
|
||||
|
||||
log_error("Input/Output error in parity '%s' at position '%u'\n", lev_config_name(l), i);
|
||||
++io_error;
|
||||
io_error_on_this_block = 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
log_tag("parity_error:%u:%s: Write error. %s\n", i, lev_config_name(l), strerror(errno));
|
||||
log_fatal("WARNING! Unexpected write error in the %s disk, it isn't possible to sync.\n", lev_name(l));
|
||||
log_fatal("Ensure that disk '%s' has some free space available.\n", lev_config_name(l));
|
||||
log_fatal("Stopping at block %u\n", i);
|
||||
++error;
|
||||
goto bail;
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
|
||||
/* until now is parity */
|
||||
state_usage_parity(state, l);
|
||||
}
|
||||
/* mark that the parity is going to be written */
|
||||
parity_going_to_be_updated = 1;
|
||||
}
|
||||
|
||||
/* if no error in parity write */
|
||||
if (!io_error_on_this_block) {
|
||||
/* for each disk, mark the blocks as processed */
|
||||
for (j = 0; j < diskmax; ++j) {
|
||||
struct snapraid_block* block;
|
||||
/* for each disk, mark the blocks as processed */
|
||||
for (j = 0; j < diskmax; ++j) {
|
||||
struct snapraid_block* block;
|
||||
|
||||
if (!handle[j].disk)
|
||||
continue;
|
||||
if (!handle[j].disk)
|
||||
continue;
|
||||
|
||||
block = fs_par2block_get(handle[j].disk, i);
|
||||
block = fs_par2block_get(handle[j].disk, blockcur);
|
||||
|
||||
if (block == BLOCK_EMPTY) {
|
||||
/* nothing to do */
|
||||
continue;
|
||||
}
|
||||
|
||||
/* if it's a deleted block */
|
||||
if (block_state_get(block) == BLOCK_STATE_DELETED) {
|
||||
/* the parity is now updated without this block, so it's now empty */
|
||||
fs_deallocate(handle[j].disk, i);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* now all the blocks have the hash and the parity computed */
|
||||
block_state_set(block, BLOCK_STATE_BLK);
|
||||
if (block == BLOCK_EMPTY) {
|
||||
/* nothing to do */
|
||||
continue;
|
||||
}
|
||||
|
||||
/* if it's a deleted block */
|
||||
if (block_state_get(block) == BLOCK_STATE_DELETED) {
|
||||
/* the parity is now updated without this block, so it's now empty */
|
||||
fs_deallocate(handle[j].disk, blockcur);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* now all the blocks have the hash and the parity computed */
|
||||
block_state_set(block, BLOCK_STATE_BLK);
|
||||
}
|
||||
|
||||
/* we update the info block only if we really have updated the parity */
|
||||
|
|
@ -1050,7 +1158,6 @@ static int state_sync_process(struct snapraid_state* state, struct snapraid_pari
|
|||
/* because has no sense to refresh the time for data that we know bad */
|
||||
if (parity_needs_to_be_updated
|
||||
&& !silent_error_on_this_block
|
||||
&& !io_error_on_this_block
|
||||
) {
|
||||
/* if rehash is neeed */
|
||||
if (rehash) {
|
||||
|
|
@ -1063,7 +1170,7 @@ static int state_sync_process(struct snapraid_state* state, struct snapraid_pari
|
|||
|
||||
/* update the time info of the block */
|
||||
/* we are also clearing any previous bad and rehash flag */
|
||||
info_set(&state->infoarr, i, info_make(now, 0, 0, 1));
|
||||
info_set(&state->infoarr, blockcur, info_make(now, 0, 0, 1));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1072,7 +1179,51 @@ static int state_sync_process(struct snapraid_state* state, struct snapraid_pari
|
|||
/* because our correction is in memory only and not yet written */
|
||||
if (silent_error_on_this_block || io_error_on_this_block) {
|
||||
/* set the error status keeping the other info */
|
||||
info_set(&state->infoarr, i, info_set_bad(info));
|
||||
info_set(&state->infoarr, blockcur, info_set_bad(info));
|
||||
}
|
||||
|
||||
/* finally schedule parity write */
|
||||
/* Note that the calls to io_parity_write() are mandatory */
|
||||
/* even if the parity doesn't need to be updated */
|
||||
/* This because we want to keep track of the time usage */
|
||||
state_usage_cpu(state);
|
||||
|
||||
/* write the parity */
|
||||
for (l = 0; l < state->level; ++l) {
|
||||
unsigned levcur;
|
||||
|
||||
io_parity_write(&io, &levcur);
|
||||
|
||||
/* until now is parity */
|
||||
state_usage_parity(state, levcur);
|
||||
}
|
||||
|
||||
/* write finished */
|
||||
io_write_next(&io, blockcur, !parity_going_to_be_updated, writer_error);
|
||||
|
||||
/* handle errors reported */
|
||||
for (j = 0; j < IO_WRITER_ERROR_MAX; ++j) {
|
||||
if (writer_error[j]) {
|
||||
switch (j + IO_WRITER_ERROR_BASE) {
|
||||
case TASK_STATE_IOERROR_CONTINUE :
|
||||
++io_error;
|
||||
if (io_error >= state->opt.io_error_limit) {
|
||||
log_fatal("DANGER! Unexpected input/output write error in a parity disk, it isn't possible to sync.\n");
|
||||
log_fatal("Stopping at block %u\n", blockcur);
|
||||
goto bail;
|
||||
}
|
||||
break;
|
||||
case TASK_STATE_ERROR_CONTINUE :
|
||||
++error;
|
||||
break;
|
||||
case TASK_STATE_IOERROR :
|
||||
++io_error;
|
||||
goto bail;
|
||||
case TASK_STATE_ERROR :
|
||||
++error;
|
||||
goto bail;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* mark the state as needing write */
|
||||
|
|
@ -1082,7 +1233,7 @@ static int state_sync_process(struct snapraid_state* state, struct snapraid_pari
|
|||
++countpos;
|
||||
|
||||
/* progress */
|
||||
if (state_progress(state, i, countpos, countmax, countsize)) {
|
||||
if (state_progress(state, blockcur, countpos, countmax, countsize)) {
|
||||
/* LCOV_EXCL_START */
|
||||
break;
|
||||
/* LCOV_EXCL_STOP */
|
||||
|
|
@ -1093,7 +1244,7 @@ static int state_sync_process(struct snapraid_state* state, struct snapraid_pari
|
|||
&& autosavedone >= autosavelimit /* if we have reached the limit */
|
||||
&& autosavemissing >= autosavelimit) /* if we have at least a full step to do */
|
||||
/* or if we have a forced autosave at the specified block */
|
||||
|| (state->opt.force_autosave_at != 0 && state->opt.force_autosave_at == i)
|
||||
|| (state->opt.force_autosave_at != 0 && state->opt.force_autosave_at == blockcur)
|
||||
) {
|
||||
autosavedone = 0; /* restart the counter */
|
||||
|
||||
|
|
@ -1107,13 +1258,13 @@ static int state_sync_process(struct snapraid_state* state, struct snapraid_pari
|
|||
/* before writing the new content file we ensure that */
|
||||
/* the parity is really written flushing the disk cache */
|
||||
for (l = 0; l < state->level; ++l) {
|
||||
ret = parity_sync(parity[l]);
|
||||
ret = parity_sync(&parity_handle[l]);
|
||||
if (ret == -1) {
|
||||
/* LCOV_EXCL_START */
|
||||
log_tag("parity_error:%u:%s: Sync error\n", i, lev_config_name(l));
|
||||
log_tag("parity_error:%u:%s: Sync error\n", blockcur, lev_config_name(l));
|
||||
log_fatal("DANGER! Unexpected sync error in %s disk.\n", lev_name(l));
|
||||
log_fatal("Ensure that disk '%s' is sane.\n", lev_config_name(l));
|
||||
log_fatal("Stopping at block %u\n", i);
|
||||
log_fatal("Stopping at block %u\n", blockcur);
|
||||
++error;
|
||||
goto bail;
|
||||
/* LCOV_EXCL_STOP */
|
||||
|
|
@ -1138,13 +1289,13 @@ end:
|
|||
/* before returning we ensure that */
|
||||
/* the parity is really written flushing the disk cache */
|
||||
for (l = 0; l < state->level; ++l) {
|
||||
ret = parity_sync(parity[l]);
|
||||
ret = parity_sync(&parity_handle[l]);
|
||||
if (ret == -1) {
|
||||
/* LCOV_EXCL_START */
|
||||
log_tag("parity_error:%u:%s: Sync error\n", i, lev_config_name(l));
|
||||
log_tag("parity_error:%u:%s: Sync error\n", blockcur, lev_config_name(l));
|
||||
log_fatal("DANGER! Unexpected sync error in %s disk.\n", lev_name(l));
|
||||
log_fatal("Ensure that disk '%s' is sane.\n", lev_config_name(l));
|
||||
log_fatal("Stopping at block %u\n", i);
|
||||
log_fatal("Stopping at block %u\n", blockcur);
|
||||
++error;
|
||||
goto bail;
|
||||
/* LCOV_EXCL_STOP */
|
||||
|
|
@ -1183,13 +1334,16 @@ end:
|
|||
log_flush();
|
||||
|
||||
bail:
|
||||
/* stop all the worker threads */
|
||||
io_stop(&io);
|
||||
|
||||
for (j = 0; j < diskmax; ++j) {
|
||||
struct snapraid_file* file = handle[j].file;
|
||||
struct snapraid_disk* disk = handle[j].disk;
|
||||
ret = handle_close(&handle[j]);
|
||||
if (ret == -1) {
|
||||
/* LCOV_EXCL_START */
|
||||
log_tag("error:%u:%s:%s: Close error. %s\n", i, disk->name, esc(file->sub), strerror(errno));
|
||||
log_tag("error:%u:%s:%s: Close error. %s\n", blockcur, disk->name, esc(file->sub), strerror(errno));
|
||||
log_fatal("DANGER! Unexpected close error in a data disk.\n");
|
||||
++error;
|
||||
/* continue, as we are already exiting */
|
||||
|
|
@ -1198,11 +1352,13 @@ bail:
|
|||
}
|
||||
|
||||
free(handle);
|
||||
free(buffer_alloc);
|
||||
free(buffer);
|
||||
free(zero_alloc);
|
||||
free(copy_alloc);
|
||||
free(copy);
|
||||
free(rehandle_alloc);
|
||||
free(failed);
|
||||
free(failed_map);
|
||||
io_done(&io);
|
||||
|
||||
if (state->opt.expect_recoverable) {
|
||||
if (error + silent_error + io_error == 0)
|
||||
|
|
@ -1222,10 +1378,7 @@ int state_sync(struct snapraid_state* state, block_off_t blockstart, block_off_t
|
|||
data_off_t size;
|
||||
data_off_t out_size;
|
||||
int ret;
|
||||
struct snapraid_parity_handle parity[LEV_MAX];
|
||||
/* the following initialization is to avoid clang warnings about */
|
||||
/* potential state->level change, that never happens */
|
||||
struct snapraid_parity_handle* parity_ptr[LEV_MAX] = { 0 };
|
||||
struct snapraid_parity_handle parity_handle[LEV_MAX];
|
||||
unsigned unrecoverable_error;
|
||||
unsigned l;
|
||||
int skip_sync = 0;
|
||||
|
|
@ -1257,8 +1410,7 @@ int state_sync(struct snapraid_state* state, block_off_t blockstart, block_off_t
|
|||
block_off_t parityblocks;
|
||||
|
||||
/* create the file and open for writing */
|
||||
parity_ptr[l] = &parity[l];
|
||||
ret = parity_create(parity_ptr[l], state->parity[l].path, &out_size, state->file_mode);
|
||||
ret = parity_create(&parity_handle[l], l, state->parity[l].path, &out_size, state->file_mode);
|
||||
if (ret == -1) {
|
||||
/* LCOV_EXCL_START */
|
||||
log_fatal("WARNING! Without an accessible %s file, it isn't possible to sync.\n", lev_name(l));
|
||||
|
|
@ -1304,7 +1456,7 @@ int state_sync(struct snapraid_state* state, block_off_t blockstart, block_off_t
|
|||
/* change the size of the parity file, truncating or extending it */
|
||||
/* from this point all the DELETED blocks after the end of the parity are invalid */
|
||||
/* and they are automatically removed when we save the new content file */
|
||||
ret = parity_chsize(parity_ptr[l], size, &out_size, state->opt.skip_fallocate);
|
||||
ret = parity_chsize(&parity_handle[l], size, &out_size, state->opt.skip_fallocate);
|
||||
if (ret == -1) {
|
||||
/* LCOV_EXCL_START */
|
||||
parity_overflow(state, out_size);
|
||||
|
|
@ -1340,7 +1492,7 @@ int state_sync(struct snapraid_state* state, block_off_t blockstart, block_off_t
|
|||
|
||||
/* skip degenerated cases of empty parity, or skipping all */
|
||||
if (blockstart < blockmax) {
|
||||
ret = state_sync_process(state, parity_ptr, blockstart, blockmax);
|
||||
ret = state_sync_process(state, parity_handle, blockstart, blockmax);
|
||||
if (ret == -1) {
|
||||
/* LCOV_EXCL_START */
|
||||
++unrecoverable_error;
|
||||
|
|
@ -1353,7 +1505,7 @@ int state_sync(struct snapraid_state* state, block_off_t blockstart, block_off_t
|
|||
}
|
||||
|
||||
for (l = 0; l < state->level; ++l) {
|
||||
ret = parity_close(parity_ptr[l]);
|
||||
ret = parity_close(&parity_handle[l]);
|
||||
if (ret == -1) {
|
||||
/* LCOV_EXCL_START */
|
||||
log_fatal("DANGER! Unexpected close error in %s disk.\n", lev_name(l));
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
63
configure.ac
63
configure.ac
|
|
@ -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],
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
#!/bin/sh
|
||||
#
|
||||
# Run the Coverage test
|
||||
#
|
||||
|
||||
make distclean
|
||||
|
||||
|
|
|
|||
57
makesan.sh
Executable file
57
makesan.sh
Executable 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
|
||||
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
#!/bin/sh
|
||||
#
|
||||
# Run the Coverity Scan static analyzer
|
||||
#
|
||||
|
||||
rm -r cov-int
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue