snapraid/cmdline/state.c

4912 lines
128 KiB
C
Raw Normal View History

/*
* Copyright (C) 2011 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 "elem.h"
#include "import.h"
#include "search.h"
#include "state.h"
#include "support.h"
2013-07-01 23:08:25 +02:00
#include "parity.h"
#include "stream.h"
#include "raid/raid.h"
#include "raid/cpu.h"
2013-09-15 21:12:41 +02:00
const char* lev_name(unsigned l)
{
switch (l) {
case 0 : return "Parity";
case 1 : return "2-Parity";
case 2 : return "3-Parity";
case 3 : return "4-Parity";
case 4 : return "5-Parity";
case 5 : return "6-Parity";
2013-09-15 21:12:41 +02:00
}
return 0;
}
const char* lev_config_name(unsigned l)
{
switch (l) {
case 0 : return "parity";
case 1 : return "2-parity";
case 2 : return "3-parity";
case 3 : return "4-parity";
case 4 : return "5-parity";
case 5 : return "6-parity";
2013-09-15 21:12:41 +02:00
}
return 0;
}
const char* lev_raid_name(unsigned mode, unsigned n)
{
switch (n) {
2013-11-13 21:35:44 +01:00
case 1 : return "par1";
case 2 : return "par2";
case 3 : if (mode == RAID_MODE_CAUCHY)
return "par3";
else
2013-11-17 14:27:31 +01:00
return "parz";
2013-11-13 21:35:44 +01:00
case 4 : return "par4";
case 5 : return "par5";
case 6 : return "par6";
}
return 0;
}
void state_init(struct snapraid_state* state)
{
2013-09-15 21:12:41 +02:00
unsigned l;
memset(&state->opt, 0, sizeof(state->opt));
state->filter_hidden = 0;
state->autosave = 0;
state->need_write = 0;
state->checked_read = 0;
2011-03-20 10:49:29 +01:00
state->block_size = 256 * 1024; /* default 256 KiB */
2013-11-13 21:35:44 +01:00
state->raid_mode = RAID_MODE_CAUCHY;
state->file_mode = MODE_SEQUENTIAL;
2014-09-27 00:32:39 +02:00
for (l = 0; l < LEV_MAX; ++l) {
2014-11-09 10:32:00 +01:00
state->parity[l].path[0] = 0;
state->parity[l].uuid[0] = 0;
2014-11-09 10:32:00 +01:00
state->parity[l].device = 0;
state->parity[l].total_blocks = 0;
state->parity[l].free_blocks = 0;
state->parity[l].tick = 0;
2013-09-15 21:12:41 +02:00
}
state->tick_io = 0;
state->tick_cpu = 0;
state->tick_last = tick();
state->share[0] = 0;
state->pool[0] = 0;
state->pool_device = 0;
state->lockfile[0] = 0;
state->level = 1; /* default is the lowest protection */
state->clear_past_hash = 0;
state->no_conf = 0;
tommy_list_init(&state->disklist);
tommy_list_init(&state->maplist);
tommy_list_init(&state->contentlist);
2011-04-26 18:51:42 +02:00
tommy_list_init(&state->filterlist);
tommy_list_init(&state->importlist);
tommy_hashdyn_init(&state->importset);
2013-07-01 23:08:25 +02:00
tommy_hashdyn_init(&state->previmportset);
tommy_hashdyn_init(&state->searchset);
tommy_arrayblkof_init(&state->infoarr, sizeof(snapraid_info));
}
void state_done(struct snapraid_state* state)
{
tommy_list_foreach(&state->disklist, (tommy_foreach_func*)disk_free);
tommy_list_foreach(&state->maplist, (tommy_foreach_func*)map_free);
tommy_list_foreach(&state->contentlist, (tommy_foreach_func*)content_free);
2011-04-26 18:51:42 +02:00
tommy_list_foreach(&state->filterlist, (tommy_foreach_func*)filter_free);
tommy_list_foreach(&state->importlist, (tommy_foreach_func*)import_file_free);
tommy_hashdyn_foreach(&state->searchset, (tommy_foreach_func*)search_file_free);
tommy_hashdyn_done(&state->importset);
2013-07-01 23:08:25 +02:00
tommy_hashdyn_done(&state->previmportset);
tommy_hashdyn_done(&state->searchset);
tommy_arrayblkof_done(&state->infoarr);
}
/**
* Checks the configuration.
*/
static void state_config_check(struct snapraid_state* state, const char* path, tommy_list* filterlist_disk)
{
tommy_node* i;
2013-09-15 21:12:41 +02:00
unsigned l;
/* check for parity level */
2013-11-13 21:35:44 +01:00
if (state->raid_mode == RAID_MODE_VANDERMONDE) {
if (state->level > 3) {
/* LCOV_EXCL_START */
ferr("If you use the z-parity you cannot have more than 3 parities.\n");
exit(EXIT_FAILURE);
/* LCOV_EXCL_STIO */
}
}
2014-09-27 00:32:39 +02:00
for (l = 0; l < state->level; ++l) {
2014-11-09 10:32:00 +01:00
if (state->parity[l].path[0] == 0) {
/* LCOV_EXCL_START */
ferr("No '%s' specification in '%s'\n", lev_config_name(l), path);
2013-09-15 21:12:41 +02:00
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2013-09-15 21:12:41 +02:00
}
2013-09-11 23:30:32 +02:00
}
if (tommy_list_empty(&state->contentlist)) {
/* LCOV_EXCL_START */
ferr("No 'content' specification in '%s'\n", path);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
/* checks for equal paths */
2014-09-27 00:32:39 +02:00
for (i = state->contentlist; i != 0; i = i->next) {
struct snapraid_content* content = i->data;
2014-09-27 00:32:39 +02:00
for (l = 0; l < state->level; ++l) {
2014-11-09 10:32:00 +01:00
if (pathcmp(state->parity[l].path, content->content) == 0) {
/* LCOV_EXCL_START */
ferr("Same path used for '%s' and 'content' as '%s'\n", lev_config_name(l), content->content);
2013-09-15 21:12:41 +02:00
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2013-09-15 21:12:41 +02:00
}
}
}
/* check device of data disks */
if (!state->opt.skip_device && !state->opt.skip_disk_access) {
2014-09-27 00:32:39 +02:00
for (i = state->disklist; i != 0; i = i->next) {
tommy_node* j;
struct snapraid_disk* disk = i->data;
#ifdef _WIN32
if (disk->device == 0) {
/* LCOV_EXCL_START */
ferr("Disk '%s' has a zero serial number.\n", disk->dir);
ferr("This is not necessarely wrong, but for using SnapRAID\n");
ferr("it's better to change the serial number of the disk.\n");
ferr("Try using the 'VolumeID' tool by 'Mark Russinovich'.\n");
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
#endif
if (!state->opt.force_device) {
2014-09-27 00:32:39 +02:00
for (j = i->next; j != 0; j = j->next) {
struct snapraid_disk* other = j->data;
if (disk->device == other->device) {
/* LCOV_EXCL_START */
ferr("Disks '%s' and '%s' are on the same device.\n", disk->dir, other->dir);
#ifdef _WIN32
ferr("Both have the serial number '%" PRIx64 "'.\n", disk->device);
ferr("Try using the 'VolumeID' tool by 'Mark Russinovich'\n");
ferr("to change one of the disk serial.\n");
#endif
/* in "fix" we allow to continue anyway */
if (strcmp(state->command, "fix") == 0) {
ferr("You can '%s' anyway, using 'snapraid --force-device %s'.\n", state->command, state->command);
}
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
}
}
if (!state->opt.skip_parity_access) {
for (l = 0; l < state->level; ++l) {
if (disk->device == state->parity[l].device) {
/* LCOV_EXCL_START */
ferr("Disk '%s' and %s '%s' are on the same device.\n", disk->dir, lev_name(l), state->parity[l].path);
#ifdef _WIN32
ferr("Both have the serial number '%" PRIx64 "'.\n", disk->device);
ferr("Try using the 'VolumeID' tool by 'Mark Russinovich'\n");
ferr("to change one of the disk serial.\n");
#endif
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
2013-09-15 21:12:41 +02:00
}
}
if (state->pool[0] != 0 && disk->device == state->pool_device) {
/* LCOV_EXCL_START */
ferr("Disk '%s' and pool '%s' are on the same device.\n", disk->dir, state->pool);
#ifdef _WIN32
ferr("Both have the serial number '%" PRIx64 "'.\n", disk->device);
ferr("Try using the 'VolumeID' tool by 'Mark Russinovich'\n");
ferr("to change one of the disk serial.\n");
#endif
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
}
}
/* check device of parity disks */
if (!state->opt.skip_device && !state->opt.skip_parity_access) {
#ifdef _WIN32
2014-09-27 00:32:39 +02:00
for (l = 0; l < state->level; ++l) {
2014-11-09 10:32:00 +01:00
if (state->parity[l].device == 0) {
/* LCOV_EXCL_START */
ferr("Disk '%s' has a zero serial number.\n", state->parity[l].path);
ferr("This is not necessarely wrong, but for using SnapRAID\n");
ferr("it's better to change the serial number of the disk.\n");
ferr("Try using the 'VolumeID' tool by 'Mark Russinovich'.\n");
2013-09-15 21:12:41 +02:00
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2013-09-15 21:12:41 +02:00
}
2013-09-11 23:30:32 +02:00
}
#endif
2014-09-27 00:32:39 +02:00
for (l = 0; l < state->level; ++l) {
2013-09-15 21:12:41 +02:00
unsigned j;
2014-09-27 00:32:39 +02:00
for (j = l + 1; j < state->level; ++j) {
2014-11-09 10:32:00 +01:00
if (state->parity[l].device == state->parity[j].device) {
/* LCOV_EXCL_START */
ferr("Parity '%s' and '%s' are on the same device.\n", state->parity[l].path, state->parity[j].path);
#ifdef _WIN32
ferr("Both have the serial number '%" PRIx64 "'.\n", state->parity[l].device);
ferr("Try using the 'VolumeID' tool by 'Mark Russinovich'\n");
ferr("to change one of the disk serial.\n");
2014-09-27 00:32:39 +02:00
#endif
2013-09-15 21:12:41 +02:00
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2013-09-15 21:12:41 +02:00
}
}
2013-09-11 23:30:32 +02:00
}
2014-09-27 00:32:39 +02:00
for (l = 0; l < state->level; ++l) {
2014-11-09 10:32:00 +01:00
if (state->pool[0] != 0 && state->pool_device == state->parity[l].device) {
/* LCOV_EXCL_START */
ferr("Pool '%s' and parity '%s' are on the same device.\n", state->pool, state->parity[l].path);
2013-09-11 23:30:32 +02:00
#ifdef _WIN32
ferr("Both have the serial number '%" PRIx64 "'.\n", state->pool_device);
ferr("Try using the 'VolumeID' tool by 'Mark Russinovich'\n");
ferr("to change one of the disk serial.\n");
#endif
2013-09-15 21:12:41 +02:00
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2013-09-15 21:12:41 +02:00
}
}
}
/* check device of pool disk */
#ifdef _WIN32
if (!state->opt.skip_device) {
if (state->pool[0] != 0 && state->pool_device == 0) {
/* LCOV_EXCL_START */
ferr("Disk '%s' has a zero serial number.\n", state->pool);
ferr("This is not necessarely wrong, but for using SnapRAID\n");
ferr("it's better to change the serial number of the disk.\n");
ferr("Try using the 'VolumeID' tool by 'Mark Russinovich'.\n");
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
}
#endif
/* count the content files */
if (!state->opt.skip_device) {
unsigned content_count;
content_count = 0;
2014-09-27 00:32:39 +02:00
for (i = state->contentlist; i != 0; i = i->next) {
tommy_node* j;
struct snapraid_content* content = i->data;
/* check if there are others in the same disk */
2014-09-27 00:32:39 +02:00
for (j = i->next; j != 0; j = j->next) {
struct snapraid_content* other = j->data;
if (content->device == other->device) {
break;
}
}
if (j != 0) {
/* skip it */
continue;
}
++content_count;
}
2014-09-27 00:32:39 +02:00
if (content_count < state->level + 1) {
/* LCOV_EXCL_START */
ferr("You must have at least %d 'content' files in different disks.\n", state->level + 1);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
}
/* check for speed */
#ifdef CONFIG_X86
if (!raid_cpu_has_ssse3())
#endif
2013-11-13 21:35:44 +01:00
if (state->raid_mode == RAID_MODE_CAUCHY) {
if (state->level == 3) {
ferr("WARNING! Your CPU doesn't have a fast implementation for triple parity.\n");
ferr("WARNING! It's recommended to switch to 'z-parity' instead than '3-parity'.\n");
} else if (state->level > 3) {
ferr("WARNING! Your CPU doesn't have a fast implementation beyond triple parity.\n");
ferr("WARNING! It's recommended to reduce the parity levels to triple parity.\n");
}
}
/* ensure that specified filter disks are valid ones */
2014-09-27 00:32:39 +02:00
for (i = tommy_list_head(filterlist_disk); i != 0; i = i->next) {
tommy_node* j;
struct snapraid_filter* filter = i->data;
2014-09-27 00:32:39 +02:00
for (j = state->disklist; j != 0; j = j->next) {
struct snapraid_disk* disk = j->data;
if (fnmatch(filter->pattern, disk->name, FNM_CASEINSENSITIVE_FOR_WIN) == 0)
break;
}
if (j == 0) {
/* LCOV_EXCL_START */
ferr("Option -d, --filter-disk %s doesn't match any disk.\n", filter->pattern);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
}
}
void state_config(struct snapraid_state* state, const char* path, const char* command, struct snapraid_option* opt, tommy_list* filterlist_disk)
{
2011-06-26 11:10:51 +02:00
STREAM* f;
unsigned line;
tommy_node* i;
unsigned l;
/* copy the options */
state->opt = *opt;
/* if unset, sort by physical order */
if (!state->opt.force_order)
state->opt.force_order = SORT_PHYSICAL;
/* adjust file mode */
if (state->opt.skip_sequential)
state->file_mode &= ~MODE_SEQUENTIAL;
/* store current command */
state->command = command;
ftag("conf:%s\n", path);
2011-06-20 20:35:04 +02:00
2011-06-26 11:16:47 +02:00
f = sopen_read(path);
if (!f) {
/* LCOV_EXCL_START */
if (errno == ENOENT) {
ferr("No configuration file found at '%s'\n", path);
} else if (errno == EACCES) {
ferr("You do not have rights to access the configuration file '%s'\n", path);
} else {
ferr("Error opening the configuration file '%s'. %s.\n", path, strerror(errno));
}
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
2011-06-23 20:28:52 +02:00
line = 1;
while (1) {
2011-06-23 20:28:52 +02:00
char tag[PATH_MAX];
char buffer[PATH_MAX];
int ret;
2011-06-23 20:28:52 +02:00
int c;
2011-06-23 20:28:52 +02:00
/* skip initial spaces */
2011-06-26 11:10:51 +02:00
sgetspace(f);
2011-06-23 20:28:52 +02:00
/* read the command */
2011-06-26 11:10:51 +02:00
ret = sgettok(f, tag, sizeof(tag));
if (ret < 0) {
/* LCOV_EXCL_START */
ferr("Error reading the configuration file '%s' at line %u\n", path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
2011-06-23 20:28:52 +02:00
/* skip spaces after the command */
2011-06-26 11:10:51 +02:00
sgetspace(f);
if (strcmp(tag, "blocksize") == 0
2014-09-27 00:32:39 +02:00
/* block_size is the old format of the option */
|| strcmp(tag, "block_size") == 0) {
2011-06-26 11:10:51 +02:00
ret = sgetu32(f, &state->block_size);
if (ret < 0) {
/* LCOV_EXCL_START */
ferr("Invalid 'blocksize' specification in '%s' at line %u\n", path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
if (state->block_size < 1) {
/* LCOV_EXCL_START */
ferr("Too small 'blocksize' specification in '%s' at line %u\n", path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
2014-09-27 00:32:39 +02:00
if (state->block_size > 16 * 1024) {
/* LCOV_EXCL_START */
ferr("Too big 'blocksize' specification in '%s' at line %u\n", path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
/* check if it's a power of 2 */
if ((state->block_size & (state->block_size - 1)) != 0) {
/* LCOV_EXCL_START */
ferr("Not power of 2 'blocksize' specification in '%s' at line %u\n", path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
state->block_size *= 1024;
} else if (strcmp(tag, "parity") == 0
|| strcmp(tag, "q-parity") == 0
|| strcmp(tag, "r-parity") == 0
|| strcmp(tag, "1-parity") == 0
|| strcmp(tag, "2-parity") == 0
|| strcmp(tag, "3-parity") == 0
|| strcmp(tag, "4-parity") == 0
|| strcmp(tag, "5-parity") == 0
|| strcmp(tag, "6-parity") == 0
|| strcmp(tag, "z-parity") == 0
) {
char device[PATH_MAX];
char* slash;
2014-03-10 21:59:25 +01:00
unsigned level;
2013-09-15 21:12:41 +02:00
switch (tag[0]) {
2014-03-10 21:59:25 +01:00
case 'p' : level = 0; break;
case 'q' : level = 1; break;
case 'r' : level = 2; break;
case '1' : level = 0; break;
case '2' : level = 1; break;
case '3' : level = 2; break;
case '4' : level = 3; break;
case '5' : level = 4; break;
case '6' : level = 5; break;
case 'z' :
2014-03-10 21:59:25 +01:00
level = 2;
2013-11-13 21:35:44 +01:00
state->raid_mode = RAID_MODE_VANDERMONDE;
break;
2014-09-27 00:32:39 +02:00
default :
/* LCOV_EXCL_START */
ferr("Invalid '%s' specification in '%s' at line %u\n", tag, path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
2011-06-23 20:28:52 +02:00
2014-11-09 10:32:00 +01:00
if (*state->parity[level].path) {
/* LCOV_EXCL_START */
ferr("Multiple '%s' specification in '%s' at line %u\n", tag, path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
2011-06-23 20:28:52 +02:00
ret = sgetlasttok(f, buffer, sizeof(buffer));
if (ret < 0) {
/* LCOV_EXCL_START */
ferr("Invalid '%s' specification in '%s' at line %u\n", tag, path, line);
2011-06-23 20:28:52 +02:00
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2011-06-23 20:28:52 +02:00
}
if (!*buffer) {
/* LCOV_EXCL_START */
ferr("Empty '%s' specification in '%s' at line %u\n", tag, path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
2014-11-09 10:32:00 +01:00
pathimport(state->parity[level].path, sizeof(state->parity[level].path), buffer);
2014-03-11 22:35:23 +01:00
if (!state->opt.skip_parity_access) {
struct stat st;
/* get the device of the directory containing the parity file */
pathimport(device, sizeof(device), buffer);
slash = strrchr(device, '/');
if (slash)
*slash = 0;
else
pathcpy(device, sizeof(device), ".");
if (stat(device, &st) != 0) {
/* LCOV_EXCL_START */
ferr("Error accessing 'parity' dir '%s' specification in '%s' at line %u\n", device, path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
2013-09-11 23:30:32 +02:00
2014-11-09 10:32:00 +01:00
state->parity[level].device = st.st_dev;
} else {
/* if parity is skipped, uses a fake device */
state->parity[level].device = 0;
}
2013-09-11 23:30:32 +02:00
2013-09-15 21:12:41 +02:00
/* adjust the level */
2014-03-10 21:59:25 +01:00
if (state->level < level + 1)
state->level = level + 1;
} else if (strcmp(tag, "share") == 0) {
if (*state->share) {
/* LCOV_EXCL_START */
ferr("Multiple 'share' specification in '%s' at line %u\n", path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
ret = sgetlasttok(f, buffer, sizeof(buffer));
if (ret < 0) {
/* LCOV_EXCL_START */
ferr("Invalid 'share' specification in '%s' at line %u\n", path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
if (!*buffer) {
/* LCOV_EXCL_START */
ferr("Empty 'share' specification in '%s' at line %u\n", path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
pathimport(state->share, sizeof(state->share), buffer);
} else if (strcmp(tag, "pool") == 0) {
struct stat st;
if (*state->pool) {
/* LCOV_EXCL_START */
ferr("Multiple 'pool' specification in '%s' at line %u\n", path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
ret = sgetlasttok(f, buffer, sizeof(buffer));
if (ret < 0) {
/* LCOV_EXCL_START */
ferr("Invalid 'pool' specification in '%s' at line %u\n", path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
if (!*buffer) {
/* LCOV_EXCL_START */
ferr("Empty 'pool' specification in '%s' at line %u\n", path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
pathimport(state->pool, sizeof(state->pool), buffer);
/* get the device of the directory containing the pool tree */
if (stat(buffer, &st) != 0) {
/* LCOV_EXCL_START */
ferr("Error accessing 'pool' dir '%s' specification in '%s' at line %u\n", buffer, path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
state->pool_device = st.st_dev;
} else if (strcmp(tag, "content") == 0) {
2011-06-23 20:28:52 +02:00
struct snapraid_content* content;
char device[PATH_MAX];
char* slash;
struct stat st;
2011-06-23 20:28:52 +02:00
ret = sgetlasttok(f, buffer, sizeof(buffer));
if (ret < 0) {
/* LCOV_EXCL_START */
ferr("Invalid 'content' specification in '%s' at line %u\n", path, line);
2011-06-23 20:28:52 +02:00
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2011-06-23 20:28:52 +02:00
}
if (pathcmp(buffer, "/dev/null") == 0 || pathcmp(buffer, "NUL") == 0) {
/* LCOV_EXCL_START */
ferr("You cannot use the null device as 'content' specification in '%s' at line %u\n", path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
if (!*buffer) {
/* LCOV_EXCL_START */
ferr("Empty 'content' specification in '%s' at line %u\n", path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
/* check if the content file is already specified */
2014-09-27 00:32:39 +02:00
for (i = state->contentlist; i != 0; i = i->next) {
content = i->data;
if (pathcmp(content->content, buffer) == 0)
break;
}
if (i) {
/* LCOV_EXCL_START */
ferr("Duplicate 'content' specification in '%s' at line %u\n", path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
/* get the device of the directory containing the content file */
pathimport(device, sizeof(device), buffer);
slash = strrchr(device, '/');
if (slash)
*slash = 0;
else
pathcpy(device, sizeof(device), ".");
if (stat(device, &st) != 0) {
/* LCOV_EXCL_START */
ferr("Error accessing 'content' dir '%s' specification in '%s' at line %u\n", device, path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
/* set the lock file at the first content file */
if (tommy_list_empty(&state->contentlist)) {
pathcpy(state->lockfile, sizeof(state->lockfile), buffer);
pathcat(state->lockfile, sizeof(state->lockfile), ".lock");
}
content = content_alloc(buffer, st.st_dev);
2011-06-23 20:28:52 +02:00
tommy_list_insert_tail(&state->contentlist, &content->node, content);
} else if (strcmp(tag, "disk") == 0) {
2011-06-23 20:28:52 +02:00
char dir[PATH_MAX];
char device[PATH_MAX];
struct snapraid_disk* disk;
uint64_t dev;
2011-06-23 20:28:52 +02:00
2011-06-26 11:10:51 +02:00
ret = sgettok(f, buffer, sizeof(buffer));
if (ret < 0) {
/* LCOV_EXCL_START */
ferr("Invalid 'disk' name specification in '%s' at line %u\n", path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
if (!*buffer) {
/* LCOV_EXCL_START */
ferr("Empty 'disk' name specification in '%s' at line %u\n", path, line);
2011-06-23 20:28:52 +02:00
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2011-06-23 20:28:52 +02:00
}
2011-06-26 11:10:51 +02:00
sgetspace(f);
2011-06-23 20:28:52 +02:00
ret = sgetlasttok(f, dir, sizeof(dir));
if (ret < 0) {
/* LCOV_EXCL_START */
ferr("Invalid 'disk' dir specification in '%s' at line %u\n", path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
if (!*dir) {
/* LCOV_EXCL_START */
ferr("Empty 'disk' dir specification in '%s' at line %u\n", path, line);
2011-06-23 20:28:52 +02:00
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2011-06-23 20:28:52 +02:00
}
/* get the device of the dir */
pathimport(device, sizeof(device), dir);
/* check if the disk name already exists */
2014-09-27 00:32:39 +02:00
for (i = state->disklist; i != 0; i = i->next) {
disk = i->data;
if (strcmp(disk->name, buffer) == 0)
break;
}
if (i) {
/* LCOV_EXCL_START */
ferr("Duplicate disk name '%s' at line %u\n", buffer, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
if (!state->opt.skip_disk_access) {
struct stat st;
if (stat(device, &st) != 0) {
/* LCOV_EXCL_START */
ferr("Error accessing 'disk' '%s' specification in '%s' at line %u\n", dir, device, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
dev = st.st_dev;
} else {
/* if data disks are skipped, uses a fake device */
dev = 0;
}
disk = disk_alloc(buffer, dir, dev);
tommy_list_insert_tail(&state->disklist, &disk->node, disk);
} else if (strcmp(tag, "nohidden") == 0) {
state->filter_hidden = 1;
} else if (strcmp(tag, "exclude") == 0) {
2011-06-23 20:28:52 +02:00
struct snapraid_filter* filter;
ret = sgetlasttok(f, buffer, sizeof(buffer));
if (ret < 0) {
/* LCOV_EXCL_START */
ferr("Invalid 'exclude' specification in '%s' at line %u\n", path, line);
2011-06-23 20:28:52 +02:00
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2011-06-23 20:28:52 +02:00
}
if (!*buffer) {
/* LCOV_EXCL_START */
ferr("Empty 'exclude' specification in '%s' at line %u\n", path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
filter = filter_alloc_file(-1, buffer);
if (!filter) {
/* LCOV_EXCL_START */
ferr("Invalid 'exclude' specification '%s' in '%s' at line %u\n", buffer, path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
2011-04-26 18:51:42 +02:00
tommy_list_insert_tail(&state->filterlist, &filter->node, filter);
} else if (strcmp(tag, "include") == 0) {
2011-06-23 20:28:52 +02:00
struct snapraid_filter* filter;
ret = sgetlasttok(f, buffer, sizeof(buffer));
if (ret < 0) {
/* LCOV_EXCL_START */
ferr("Invalid 'include' specification in '%s' at line %u\n", path, line);
2011-06-23 20:28:52 +02:00
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2011-06-23 20:28:52 +02:00
}
if (!*buffer) {
/* LCOV_EXCL_START */
ferr("Empty 'include' specification in '%s' at line %u\n", path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
filter = filter_alloc_file(1, buffer);
2011-04-26 18:51:42 +02:00
if (!filter) {
/* LCOV_EXCL_START */
ferr("Invalid 'include' specification '%s' in '%s' at line %u\n", buffer, path, line);
2011-04-26 18:51:42 +02:00
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2011-04-26 18:51:42 +02:00
}
tommy_list_insert_tail(&state->filterlist, &filter->node, filter);
} else if (strcmp(tag, "autosave") == 0) {
char* e;
ret = sgetlasttok(f, buffer, sizeof(buffer));
if (ret < 0) {
/* LCOV_EXCL_START */
ferr("Invalid 'autosave' specification in '%s' at line %u\n", path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
if (!*buffer) {
/* LCOV_EXCL_START */
ferr("Empty 'autosave' specification in '%s' at line %u\n", path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
state->autosave = strtoul(buffer, &e, 0);
if (!e || *e) {
/* LCOV_EXCL_START */
ferr("Invalid 'autosave' specification in '%s' at line %u\n", path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
/* convert to GiB */
state->autosave *= 1024 * 1024 * 1024;
2011-06-23 20:28:52 +02:00
} else if (tag[0] == 0) {
/* allow empty lines */
} else if (tag[0] == '#') {
2011-06-26 11:10:51 +02:00
ret = sgetline(f, buffer, sizeof(buffer));
if (ret < 0) {
/* LCOV_EXCL_START */
ferr("Invalid comment in '%s' at line %u\n", path, line);
2011-06-23 20:28:52 +02:00
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2011-06-23 20:28:52 +02:00
}
} else {
/* LCOV_EXCL_START */
ferr("Invalid command '%s' in '%s' at line %u\n", tag, path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
2011-06-23 20:28:52 +02:00
/* skip final spaces */
2011-06-26 11:10:51 +02:00
sgetspace(f);
2011-06-23 20:28:52 +02:00
/* next line */
2011-06-26 11:10:51 +02:00
c = sgeteol(f);
2011-06-23 20:28:52 +02:00
if (c == EOF) {
break;
}
if (c != '\n') {
/* LCOV_EXCL_START */
ferr("Extra data in '%s' at line %u\n", path, line);
2011-06-23 20:28:52 +02:00
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2011-06-23 20:28:52 +02:00
}
++line;
}
2011-06-26 11:10:51 +02:00
if (serror(f)) {
/* LCOV_EXCL_START */
ferr("Error reading the configuration file '%s' at line %u\n", path, line);
2011-06-23 20:28:52 +02:00
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
2011-06-26 11:10:51 +02:00
sclose(f);
state_config_check(state, path, filterlist_disk);
/* select the default hash */
if (state->opt.force_murmur3) {
state->besthash = HASH_MURMUR3;
} else if (state->opt.force_spooky2) {
state->besthash = HASH_SPOOKY2;
} else {
#ifdef CONFIG_X86
if (sizeof(void*) == 4 && !raid_cpu_has_slowmult())
state->besthash = HASH_MURMUR3;
else
state->besthash = HASH_SPOOKY2;
#else
if (sizeof(void*) == 4)
state->besthash = HASH_MURMUR3;
else
state->besthash = HASH_SPOOKY2;
#endif
}
/* by default use the best hash */
state->hash = state->besthash;
2013-06-16 00:33:46 +02:00
/* by default use a random hash seed */
if (randomize(state->hashseed, HASH_SIZE) != 0) {
/* LCOV_EXCL_START */
ferr("Failed to get random values.\n");
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
2013-06-16 00:33:46 +02:00
2013-07-01 23:08:25 +02:00
/* no previous hash by default */
state->prevhash = HASH_UNDEFINED;
/* intentionally not set the prevhashseed, if used valgrind will warn about it */
2013-07-01 23:08:25 +02:00
ftag("blocksize:%u\n", state->block_size);
2014-09-27 00:32:39 +02:00
for (i = state->disklist; i != 0; i = i->next) {
struct snapraid_disk* disk = i->data;
ftag("disk:%s:%s\n", disk->name, disk->dir);
}
2013-09-15 21:12:41 +02:00
ftag("mode:%s\n", lev_raid_name(state->raid_mode, state->level));
2014-09-27 00:32:39 +02:00
for (l = 0; l < state->level; ++l)
ftag("%s:%s\n", lev_config_name(l), state->parity[l].path);
if (state->pool[0] != 0)
ftag("pool:%s\n", state->pool);
if (state->share[0] != 0)
ftag("share:%s\n", state->share);
if (state->autosave != 0)
ftag("autosave:%" PRIu64 "\n", state->autosave);
for (i = tommy_list_head(&state->filterlist); i != 0; i = i->next) {
char out[PATH_MAX];
struct snapraid_filter* filter = i->data;
ftag("filter:%s\n", filter_type(filter, out, sizeof(out)));
}
if (state->filter_hidden)
ftag("filter:nohidden:\n");
fflush_log();
}
2014-11-09 10:51:48 +01:00
/**
* Finds a disk by name.
*/
static struct snapraid_disk* find_disk(struct snapraid_state* state, const char* name)
{
tommy_node* i;
for (i = state->disklist; i != 0; i = i->next) {
struct snapraid_disk* disk = i->data;
if (strcmp(disk->name, name) == 0)
return disk;
}
if (state->no_conf) {
/* without a configuration file, add disks automatically */
struct snapraid_disk* disk;
disk = disk_alloc(name, "DUMMY/", -1);
tommy_list_insert_tail(&state->disklist, &disk->node, disk);
return disk;
}
return 0;
}
/**
* Updates the disk mapping if required.
*/
static void state_map(struct snapraid_state* state)
{
unsigned hole;
tommy_node* i;
unsigned uuid_mismatch;
unsigned diskcount;
unsigned l;
/* removes all the mapping without a disk */
/* this happens when a disk is removed from the configuration file */
/* From SnapRAID 4.0 mappings are automatically removed if a disk is not used */
/* when saving the content file, but we keep this code to import older content files. */
2014-09-27 00:32:39 +02:00
for (i = state->maplist; i != 0; ) {
struct snapraid_map* map = i->data;
struct snapraid_disk* disk;
2014-11-09 10:51:48 +01:00
disk = find_disk(state, map->name);
/* go to the next mapping before removing */
i = i->next;
2014-09-27 00:32:39 +02:00
2014-11-09 10:51:48 +01:00
if (disk == 0) {
/* disk not found, remove the mapping */
tommy_list_remove_existing(&state->maplist, &map->node);
map_free(map);
}
}
/* maps each unmapped disk present in the configuration file in the first available hole */
/* this happens when you add disks for the first time in the configuration file */
hole = 0; /* first position to try */
2014-09-27 00:32:39 +02:00
for (i = state->disklist; i != 0; i = i->next) {
struct snapraid_disk* disk = i->data;
struct snapraid_map* map;
tommy_node* j;
/* check if the disk is already mapped */
2014-09-27 00:32:39 +02:00
for (j = state->maplist; j != 0; j = j->next) {
map = j->data;
if (strcmp(disk->name, map->name) == 0) {
/* mapping found */
break;
}
}
if (j != 0) {
/* mapping is present, then copy the free blocks into to disk */
disk->total_blocks = map->total_blocks;
disk->free_blocks = map->free_blocks;
continue;
}
/* mapping not found, search for an hole */
while (1) {
2014-09-27 00:32:39 +02:00
for (j = state->maplist; j != 0; j = j->next) {
map = j->data;
if (map->position == hole) {
/* position already used */
break;
}
}
2014-09-27 00:32:39 +02:00
if (j == 0) {
/* hole found */
break;
}
/* try with the next one */
++hole;
}
/* insert the new mapping */
map = map_alloc(disk->name, hole, 0, 0, "");
tommy_list_insert_tail(&state->maplist, &map->node, map);
}
/* without configuration don't check for number of data disks or uuid changes */
2013-11-30 18:00:49 +01:00
if (state->no_conf)
return;
2014-11-09 19:51:58 +01:00
/* counter for the number of UUID mismatches */
uuid_mismatch = 0;
/* checks if mapping match the disk uuid */
if (!state->opt.skip_disk_access) {
for (i = state->maplist; i != 0; i = i->next) {
struct snapraid_map* map = i->data;
struct snapraid_disk* disk;
char uuid[UUID_MAX];
int ret;
disk = find_disk(state, map->name);
if (disk == 0) {
/* LCOV_EXCL_START */
ferr("Internal inconsistency for mapping '%s'\n", map->name);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
ret = devuuid(disk->device, uuid, sizeof(uuid));
if (ret != 0) {
/* uuid not available, just ignore but marks the disk with unsupported UUID */
disk->has_unsupported_uuid = 1;
continue;
}
/* if the uuid is changed */
if (strcmp(uuid, map->uuid) != 0) {
/* mark the disk as with an UUID change */
disk->has_different_uuid = 1;
/* if the previous uuid is available */
if (map->uuid[0] != 0) {
/* count the number of uuid change */
++uuid_mismatch;
ferr("UUID change for disk '%s' from '%s' to '%s'\n", disk->name, map->uuid, uuid);
} else {
/* no message here, because having a disk without */
/* UUID is the normal state of an empty disk */
disk->had_empty_uuid = 1;
}
/* update the uuid in the mapping, */
pathcpy(map->uuid, sizeof(map->uuid), uuid);
/* write the new state with the new uuid */
state->need_write = 1;
}
}
}
/* checks the parity uuid */
if (!state->opt.skip_parity_access) {
for (l = 0; l < state->level; ++l) {
char uuid[UUID_MAX];
int ret;
2013-03-04 21:44:29 +01:00
ret = devuuid(state->parity[l].device, uuid, sizeof(uuid));
if (ret != 0) {
/* uuid not available, just ignore */
continue;
}
2014-11-09 19:51:58 +01:00
/* if the uuid is changed */
if (strcmp(uuid, state->parity[l].uuid) != 0) {
/* if the previous uuid is available */
if (state->parity[l].uuid[0] != 0) {
/* count the number of uuid change */
++uuid_mismatch;
ferr("UUID change for parity '%s' from '%s' to '%s'\n", lev_config_name(l), state->parity[l].uuid, uuid);
2014-11-09 19:51:58 +01:00
}
/* update the uuid */
pathcpy(state->parity[l].uuid, sizeof(state->parity[l].uuid), uuid);
/* write the new state with the new uuid */
state->need_write = 1;
}
}
}
if (!state->opt.force_uuid && uuid_mismatch > state->level) {
/* LCOV_EXCL_START */
ferr("Too many disks have UUID changed from the latest 'sync'.\n");
ferr("If this happens because you really replaced them,\n");
ferr("you can '%s' anyway, using 'snapraid --force-uuid %s'.\n", state->command, state->command);
ferr("Instead, it's possible that you messed up the disk mount points,\n");
ferr("and you have to restore the mount points at the state of the latest sync.\n");
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
/* count the number of data disks, including holes left after removing some */
diskcount = 0;
2014-09-27 00:32:39 +02:00
for (i = state->maplist; i != 0; i = i->next) {
struct snapraid_map* map = i->data;
if (map->position + 1 > diskcount)
diskcount = map->position + 1;
}
/* ensure to don't go over the limit of the RAID engine */
2013-10-28 22:55:32 +01:00
if (diskcount > RAID_DATA_MAX) {
/* LCOV_EXCL_START */
ferr("Too many data disks. No more than %u.\n", RAID_DATA_MAX);
2013-10-28 22:55:32 +01:00
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2013-10-28 22:55:32 +01:00
}
/* now count the real number of data disks, excluding holes left after removing some */
diskcount = tommy_list_count(&state->maplist);
/* recommend number of parities */
if (diskcount >= 36 && state->level < 6) {
ferr("WARNING! With %u disks it's recommended to use six parity levels.\n", diskcount);
} else if (diskcount >= 29 && state->level < 5) {
ferr("WARNING! With %u disks it's recommended to use five parity levels.\n", diskcount);
} else if (diskcount >= 22 && state->level < 4) {
ferr("WARNING! With %u disks it's recommended to use four parity levels.\n", diskcount);
} else if (diskcount >= 15 && state->level < 3) {
ferr("WARNING! With %u disks it's recommended to use three parity levels.\n", diskcount);
2013-11-13 21:35:44 +01:00
} else if (diskcount >= 5 && state->level < 2) {
ferr("WARNING! With %u disks it's recommended to use two parity levels.\n", diskcount);
}
}
2011-06-20 20:35:04 +02:00
void state_refresh(struct snapraid_state* state)
{
tommy_node* i;
unsigned l;
/* for all disks */
for (i = state->maplist; i != 0; i = i->next) {
struct snapraid_map* map = i->data;
struct snapraid_disk* disk;
uint64_t total_space;
uint64_t free_space;
int ret;
disk = find_disk(state, map->name);
if (disk == 0) {
/* LCOV_EXCL_START */
ferr("Internal inconsistency for mapping '%s'\n", map->name);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
ret = fsinfo(disk->dir, 0, &total_space, &free_space);
if (ret != 0) {
/* LCOV_EXCL_START */
ferr("Error accessing disk '%s' to get filesystem info. %s.\n", disk->dir, strerror(errno));
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
/* set the new free blocks */
map->total_blocks = total_space / state->block_size;
map->free_blocks = free_space / state->block_size;
/* also update the disk info */
disk->total_blocks = map->total_blocks;
disk->free_blocks = map->free_blocks;
}
/* for all parities */
for (l = 0; l < state->level; ++l) {
uint64_t total_space;
uint64_t free_space;
int ret;
ret = fsinfo(state->parity[l].path, 0, &total_space, &free_space);
if (ret != 0) {
/* LCOV_EXCL_START */
ferr("Error accessing file '%s' to get filesystem info. %s.\n", state->parity[l].path, strerror(errno));
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
/* set the new free blocks */
state->parity[l].total_blocks = total_space / state->block_size;
state->parity[l].free_blocks = free_space / state->block_size;
}
/* note what we don't set need_write = 1, because we don't want */
/* to update the content file only for the free space info. */
}
/**
* Checks the content.
*/
static void state_content_check(struct snapraid_state* state, const char* path)
{
tommy_node* i;
/* checks that any map has different name and position */
2014-09-27 00:32:39 +02:00
for (i = state->maplist; i != 0; i = i->next) {
struct snapraid_map* map = i->data;
tommy_node* j;
2014-09-27 00:32:39 +02:00
for (j = i->next; j != 0; j = j->next) {
struct snapraid_map* other = j->data;
if (strcmp(map->name, other->name) == 0) {
/* LCOV_EXCL_START */
ferr("Colliding 'map' disk specification in '%s'\n", path);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
if (map->position == other->position) {
/* LCOV_EXCL_START */
ferr("Colliding 'map' index specification in '%s'\n", path);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
2011-06-20 20:35:04 +02:00
}
}
}
2013-08-17 16:17:53 +02:00
/**
* Checks if the position is REQUIRED, or we can completely clear it from the state.
*
* Note that position with only DELETED blocks are discarged.
2013-08-17 16:17:53 +02:00
*/
static int position_is_required(struct snapraid_state* state, block_off_t pos)
2013-08-17 16:17:53 +02:00
{
tommy_node* i;
/* check for each disk */
2014-09-27 00:32:39 +02:00
for (i = state->disklist; i != 0; i = i->next) {
2013-08-17 16:17:53 +02:00
struct snapraid_disk* disk = i->data;
struct snapraid_block* block = disk_block_get(disk, pos);
/* if we have at least one file, the position is needed */
if (block_has_file(block))
2013-08-17 16:17:53 +02:00
return 1;
}
return 0;
}
/**
* Checks if the info block is REQUIREQ.
*
* This is used to ensure that we keep the last check used for scrubbing.
* and the we add it when importing old context files.
*
* Note that you can have position without info blocks, for example
2014-03-04 19:34:55 +01:00
* if all the blocks are not synced.
*
* Note also that not requiring an info block, doesn't mean that if present it
* can be discarded.
*/
static int info_is_required(struct snapraid_state* state, block_off_t pos)
{
tommy_node* i;
/* check for each disk */
2014-09-27 00:32:39 +02:00
for (i = state->disklist; i != 0; i = i->next) {
struct snapraid_disk* disk = i->data;
struct snapraid_block* block = disk_block_get(disk, pos);
2014-03-04 19:34:55 +01:00
/* if we have at least one synced file, the info is required */
if (block_state_get(block) == BLOCK_STATE_BLK)
return 1;
}
return 0;
}
static void position_clear_deleted(struct snapraid_state* state, block_off_t pos)
{
tommy_node* i;
/* check for each disk if block is really used */
2014-09-27 00:32:39 +02:00
for (i = state->disklist; i != 0; i = i->next) {
struct snapraid_disk* disk = i->data;
struct snapraid_block* block = disk_block_get(disk, pos);
/* if the block is deleted */
if (block_state_get(block) == BLOCK_STATE_DELETED) {
/* set it to empty */
tommy_arrayblk_set(&disk->blockarr, pos, BLOCK_EMPTY);
}
}
}
2013-08-17 16:17:53 +02:00
/**
* Checks if a block position in a disk is deleted.
*/
static int is_block_deleted(struct snapraid_disk* disk, block_off_t pos)
{
struct snapraid_block* block = disk_block_get(disk, pos);
return block_state_get(block) == BLOCK_STATE_DELETED;
}
static void state_read_text(struct snapraid_state* state, const char* path, STREAM* f, time_t save_time)
{
struct snapraid_disk* disk;
struct snapraid_file* file;
block_off_t blockidx;
2012-10-09 23:02:46 +02:00
block_off_t blockmax;
unsigned line;
unsigned count_file;
unsigned count_hardlink;
unsigned count_symlink;
unsigned count_dir;
block_off_t b;
2013-08-17 16:17:53 +02:00
disk = 0;
file = 0;
line = 1;
blockidx = 0;
blockmax = 0;
count_file = 0;
count_hardlink = 0;
count_symlink = 0;
count_dir = 0;
while (1) {
2011-06-23 20:28:52 +02:00
char buffer[PATH_MAX];
2011-06-26 11:10:51 +02:00
char tag[PATH_MAX];
int ret;
2011-06-23 20:28:52 +02:00
int c;
2011-06-26 11:10:51 +02:00
/* read the command */
ret = sgettok(f, tag, sizeof(tag));
if (ret < 0) {
/* LCOV_EXCL_START */
ferr("Error reading the configuration file '%s' at line %u\n", path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
2011-06-26 11:10:51 +02:00
/* skip only one space if present */
c = sgetc(f);
if (c != ' ') {
sungetc(c, f);
}
if (strcmp(tag, "blk") == 0 || strcmp(tag, "new") == 0
|| strcmp(tag, "chg") == 0 || strcmp(tag, "rep") == 0
) {
/* "blk"/""new"/"chg"/"rep" command */
block_off_t v_pos;
struct snapraid_block* block;
if (!file) {
/* LCOV_EXCL_START */
ferr("Unexpected '%s' specification in '%s' at line %u\n", tag, path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
2011-06-26 11:10:51 +02:00
ret = sgetu32(f, &v_pos);
if (ret < 0) {
/* LCOV_EXCL_START */
ferr("Invalid '%s' specification in '%s' at line %u\n", tag, path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
if (blockidx >= file->blockmax) {
/* LCOV_EXCL_START */
ferr("Internal inconsistency in '%s' specification in '%s' at line %u\n", tag, path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
block = &file->blockvec[blockidx];
if (block->parity_pos != POS_INVALID) {
/* LCOV_EXCL_START */
ferr("Internal inconsistency in '%s' specification in '%s' at line %u\n", tag, path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
2011-12-31 12:09:05 +01:00
/* set the parity only if present */
switch (tag[0]) {
case 'b' :
block_state_set(block, BLOCK_STATE_BLK);
break;
case 'n' :
/* deprecated NEW blocks are converted to CHG ones */
block_state_set(block, BLOCK_STATE_CHG);
break;
case 'c' :
block_state_set(block, BLOCK_STATE_CHG);
break;
case 'r' :
block_state_set(block, BLOCK_STATE_REP);
break;
2011-12-31 12:09:05 +01:00
}
block->parity_pos = v_pos;
/* keep track of the max block number used in parity */
if (v_pos + 1 > blockmax)
blockmax = v_pos + 1;
/* read the hash only for 'blk/chg/rep', and not for 'new' */
if (tag[0] != 'n') {
c = sgetc(f);
2011-06-23 20:28:52 +02:00
if (c != ' ') {
/* LCOV_EXCL_START */
ferr("Invalid '%s' specification in '%s' at line %u\n", tag, path, line);
2011-06-23 20:28:52 +02:00
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2011-06-23 20:28:52 +02:00
}
2011-06-26 11:10:51 +02:00
ret = sgethex(f, block->hash, HASH_SIZE);
if (ret < 0) {
/* LCOV_EXCL_START */
ferr("Invalid '%s' specification in '%s' at line %u\n", tag, path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
} else {
/* set the ZERO hash for deprecated NEW blocks */
hash_zero_set(block->hash);
}
/* if the block contains a hash of past data */
/* and we are clearing such undeterminated hashes */
if (state->clear_past_hash
&& block_has_past_hash(block)
) {
/* set the hash value to INVALID */
hash_invalid_set(block->hash);
}
/* if we are disabling the copy optimization */
/* we want also to clear any already previously stored information */
/* in other sync commands */
/* note that this is required only in sync, and we detect */
/* this using the clear_past_hash flag */
if (state->clear_past_hash
&& state->opt.force_nocopy
&& block_state_get(block) == BLOCK_STATE_REP
) {
/* set the hash value to INVALID */
hash_invalid_set(block->hash);
/* convert from REP to CHG block */
block_state_set(block, BLOCK_STATE_CHG);
}
/* if we want a full sync, marks block as invalid parity */
/* note that we do this after the force_nocopy option */
/* to avoid to mixup the two things */
if (state->opt.force_full
&& block_state_get(block) == BLOCK_STATE_BLK) {
/* convert from BLK to REP */
block_state_set(block, BLOCK_STATE_REP);
}
/* we must not overwrite existing blocks */
if (disk_block_get(disk, v_pos) != BLOCK_EMPTY) {
/* LCOV_EXCL_START */
ferr("Internal inconsistency for '%s' specification in '%s' at line %u\n", tag, path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
/* insert the block in the block array */
tommy_arrayblk_grow(&disk->blockarr, v_pos + 1);
tommy_arrayblk_set(&disk->blockarr, v_pos, block);
/* check for termination of the block list */
++blockidx;
if (blockidx == file->blockmax) {
file = 0;
disk = 0;
}
} else if (strcmp(tag, "inf") == 0) {
/* "inf" command */
block_off_t v_pos;
snapraid_info info;
2013-07-01 23:08:25 +02:00
int rehash;
int bad;
uint32_t t;
ret = sgetu32(f, &v_pos);
if (ret < 0) {
/* LCOV_EXCL_START */
ferr("Invalid 'inf' specification in '%s' at line %u\n", path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
if (v_pos >= blockmax) {
/* LCOV_EXCL_START */
ferr("Internal inconsistency in position for 'inf' specification in '%s' at line %u\n", path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
c = sgetc(f);
if (c != ' ') {
/* LCOV_EXCL_START */
ferr("Invalid 'inf' specification in '%s' at line %u\n", path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
ret = sgetu32(f, &t);
if (ret < 0) {
/* LCOV_EXCL_START */
ferr("Invalid 'inf' specification in '%s' at line %u\n", path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
2013-07-01 23:08:25 +02:00
/* read extra tags if present */
rehash = 0;
bad = 0;
c = sgetc(f);
while (c == ' ') {
ret = sgettok(f, buffer, sizeof(buffer));
if (ret < 0) {
/* LCOV_EXCL_START */
ferr("Invalid 'inf' specification in '%s' at line %u\n", path, line);
2013-07-01 23:08:25 +02:00
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2013-07-01 23:08:25 +02:00
}
if (strcmp(buffer, "bad") == 0) {
bad = 1;
} else if (strcmp(buffer, "rehash") == 0) {
rehash = 1;
if (state->prevhash == HASH_UNDEFINED) {
/* LCOV_EXCL_START */
ferr("Internal inconsistency for missing previous checksum in '%s' at line %u\n", path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
2013-07-01 23:08:25 +02:00
} else {
/* LCOV_EXCL_START */
ferr("Invalid 'inf' specification '%s' in '%s' at line %u\n", buffer, path, line);
2013-07-01 23:08:25 +02:00
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2013-07-01 23:08:25 +02:00
}
c = sgetc(f);
}
sungetc(c, f);
info = info_make(t, bad, rehash);
/* insert the info in the array */
info_set(&state->infoarr, v_pos, info);
} else if (strcmp(tag, "off") == 0) {
/* "off" command */
block_off_t v_pos;
struct snapraid_deleted* deleted;
if (!disk) {
/* LCOV_EXCL_START */
ferr("Unexpected 'off' specification in '%s' at line %u\n", path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
/* allocate a new deleted block */
deleted = deleted_alloc();
ret = sgetu32(f, &v_pos);
if (ret < 0) {
/* LCOV_EXCL_START */
ferr("Invalid 'off' specification in '%s' at line %u\n", path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
deleted->block.parity_pos = v_pos;
/* read the hash */
c = sgetc(f);
if (c != ' ') {
/* LCOV_EXCL_START */
ferr("Invalid 'off' specification in '%s' at line %u\n", path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
/* set the hash only if present */
ret = sgethex(f, deleted->block.hash, HASH_SIZE);
if (ret < 0) {
/* LCOV_EXCL_START */
ferr("Invalid 'off' specification in '%s' at line %u\n", path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
/* if we are clearing undeterminated hashes */
if (state->clear_past_hash) {
/* set the hash value to INVALID */
hash_invalid_set(deleted->block.hash);
}
/* we must not overwrite existing blocks */
if (disk_block_get(disk, v_pos) != BLOCK_EMPTY) {
/* LCOV_EXCL_START */
ferr("Internal inconsistency for 'off' specification in '%s' at line %u\n", path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
2014-06-15 16:08:06 +02:00
/* insert it in the list of deleted blocks */
tommy_list_insert_tail(&disk->deletedlist, &deleted->node, deleted);
/* insert the block in the block array */
tommy_arrayblk_grow(&disk->blockarr, v_pos + 1);
tommy_arrayblk_set(&disk->blockarr, v_pos, &deleted->block);
2011-06-26 11:10:51 +02:00
} else if (strcmp(tag, "file") == 0) {
2011-06-23 20:28:52 +02:00
/* file */
char sub[PATH_MAX];
uint64_t v_size;
uint64_t v_mtime_sec;
uint32_t v_mtime_nsec;
uint64_t v_inode;
if (file) {
/* LCOV_EXCL_START */
ferr("Missing 'blk' specification in '%s' at line %u\n", path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
2011-06-26 11:10:51 +02:00
ret = sgettok(f, buffer, sizeof(buffer));
if (ret < 0) {
/* LCOV_EXCL_START */
ferr("Invalid 'file' specification in '%s' at line %u\n", path, line);
2011-06-23 20:28:52 +02:00
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2011-06-23 20:28:52 +02:00
}
2011-06-26 11:10:51 +02:00
c = sgetc(f);
2011-06-23 20:28:52 +02:00
if (c != ' ') {
/* LCOV_EXCL_START */
ferr("Invalid 'file' specification in '%s' at line %u\n", path, line);
2011-06-23 20:28:52 +02:00
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2011-06-23 20:28:52 +02:00
}
2011-06-26 11:10:51 +02:00
ret = sgetu64(f, &v_size);
if (ret < 0) {
/* LCOV_EXCL_START */
ferr("Invalid 'file' specification in '%s' at line %u\n", path, line);
2011-06-23 20:28:52 +02:00
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2011-06-23 20:28:52 +02:00
}
2011-06-26 11:10:51 +02:00
c = sgetc(f);
2011-06-23 20:28:52 +02:00
if (c != ' ') {
/* LCOV_EXCL_START */
ferr("Invalid 'file' specification in '%s' at line %u\n", path, line);
2011-06-23 20:28:52 +02:00
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2011-06-23 20:28:52 +02:00
}
ret = sgetu64(f, &v_mtime_sec);
if (ret < 0) {
/* LCOV_EXCL_START */
ferr("Invalid 'file' specification in '%s' at line %u\n", path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
2011-06-26 11:10:51 +02:00
c = sgetc(f);
if (c == '.') { /* the nanosecond field is present only from version 1.14 */
ret = sgetu32(f, &v_mtime_nsec);
if (ret < 0) {
/* LCOV_EXCL_START */
ferr("Invalid 'file' specification in '%s' at line %u\n", path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
c = sgetc(f);
} else {
/* use a special value, meaning that we don't have this information */
v_mtime_nsec = STAT_NSEC_INVALID;
}
2011-06-23 20:28:52 +02:00
if (c != ' ') {
/* LCOV_EXCL_START */
ferr("Invalid 'file' specification in '%s' at line %u\n", path, line);
2011-06-23 20:28:52 +02:00
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2011-06-23 20:28:52 +02:00
}
2011-06-26 11:10:51 +02:00
ret = sgetu64(f, &v_inode);
if (ret < 0) {
/* LCOV_EXCL_START */
ferr("Invalid 'file' specification in '%s' at line %u\n", path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
2011-06-26 11:10:51 +02:00
c = sgetc(f);
2011-06-23 20:28:52 +02:00
if (c != ' ') {
/* LCOV_EXCL_START */
ferr("Invalid 'file' specification in '%s' at line %u\n", path, line);
2011-06-23 20:28:52 +02:00
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2011-06-23 20:28:52 +02:00
}
2011-06-26 11:10:51 +02:00
ret = sgetline(f, sub, sizeof(sub));
if (ret < 0) {
/* LCOV_EXCL_START */
ferr("Invalid 'file' specification in '%s' at line %u\n", path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
if (!*sub) {
/* LCOV_EXCL_START */
ferr("Invalid 'file' specification in '%s' at line %u\n", path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
/* find the disk */
2013-08-17 16:17:53 +02:00
disk = find_disk(state, buffer);
if (!disk) {
/* LCOV_EXCL_START */
ferr("Disk named '%s' not found in '%s' at line %u\n", buffer, path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
/* allocate the file */
file = file_alloc(state->block_size, sub, v_size, v_mtime_sec, v_mtime_nsec, v_inode, 0);
/* insert the file in the file containers */
tommy_hashdyn_insert(&disk->inodeset, &file->nodeset, file, file_inode_hash(file->inode));
tommy_hashdyn_insert(&disk->pathset, &file->pathset, file, file_path_hash(file->sub));
tommy_hashdyn_insert(&disk->stampset, &file->stampset, file, file_stamp_hash(file->size, file->mtime_sec, file->mtime_nsec));
tommy_list_insert_tail(&disk->filelist, &file->nodelist, file);
/* start the block allocation of the file */
blockidx = 0;
/* check for empty file */
if (blockidx == file->blockmax) {
file = 0;
disk = 0;
}
/* stat */
++count_file;
} else if (strcmp(tag, "hole") == 0) {
/* hole */
if (file) {
/* LCOV_EXCL_START */
ferr("Missing 'blk' specification in '%s' at line %u\n", path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
ret = sgettok(f, buffer, sizeof(buffer));
if (ret < 0) {
/* LCOV_EXCL_START */
ferr("Invalid 'hole' specification in '%s' at line %u\n", path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
/* find the disk */
2013-08-17 16:17:53 +02:00
disk = find_disk(state, buffer);
if (!disk) {
/* LCOV_EXCL_START */
ferr("Disk named '%s' not found in '%s' at line %u\n", buffer, path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
} else if (strcmp(tag, "symlink") == 0) {
/* symlink */
char sub[PATH_MAX];
char linkto[PATH_MAX];
char tokento[32];
struct snapraid_link* link;
ret = sgettok(f, buffer, sizeof(buffer));
if (ret < 0) {
/* LCOV_EXCL_START */
ferr("Invalid 'symlink' specification in '%s' at line %u\n", path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
c = sgetc(f);
if (c != ' ') {
/* LCOV_EXCL_START */
ferr("Invalid 'symlink' specification in '%s' at line %u\n", path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
ret = sgetline(f, sub, sizeof(sub));
if (ret < 0) {
/* LCOV_EXCL_START */
ferr("Invalid 'symlink' specification in '%s' at line %u\n", path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
c = sgeteol(f);
if (c != '\n') {
/* LCOV_EXCL_START */
ferr("Invalid 'symlink' specification in '%s' at line %u\n", path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
++line;
ret = sgettok(f, tokento, sizeof(tokento));
if (ret < 0) {
/* LCOV_EXCL_START */
ferr("Invalid 'symlink' specification in '%s' at line %u\n", path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
if (strcmp(tokento, "to") != 0) {
/* LCOV_EXCL_START */
ferr("Invalid 'symlink' specification in '%s' at line %u\n", path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
c = sgetc(f);
if (c != ' ') {
/* LCOV_EXCL_START */
ferr("Invalid 'symlink' specification in '%s' at line %u\n", path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
ret = sgetline(f, linkto, sizeof(linkto));
if (ret < 0) {
/* LCOV_EXCL_START */
ferr("Invalid 'symlink' specification in '%s' at line %u\n", path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
if (!*sub || !*linkto) {
/* LCOV_EXCL_START */
ferr("Invalid 'symlink' specification in '%s' at line %u\n", path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
/* find the disk */
2013-08-17 16:17:53 +02:00
disk = find_disk(state, buffer);
if (!disk) {
/* LCOV_EXCL_START */
ferr("Disk named '%s' not found in '%s' at line %u\n", buffer, path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
/* allocate the link as symbolic link */
link = link_alloc(sub, linkto, FILE_IS_SYMLINK);
/* insert the link in the link containers */
tommy_hashdyn_insert(&disk->linkset, &link->nodeset, link, link_name_hash(link->sub));
tommy_list_insert_tail(&disk->linklist, &link->nodelist, link);
/* stat */
++count_symlink;
} else if (strcmp(tag, "hardlink") == 0) {
/* hardlink */
char sub[PATH_MAX];
char linkto[PATH_MAX];
char tokento[32];
struct snapraid_link* link;
ret = sgettok(f, buffer, sizeof(buffer));
if (ret < 0) {
/* LCOV_EXCL_START */
ferr("Invalid 'hardlink' specification in '%s' at line %u\n", path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
c = sgetc(f);
if (c != ' ') {
/* LCOV_EXCL_START */
ferr("Invalid 'hardlink' specification in '%s' at line %u\n", path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
ret = sgetline(f, sub, sizeof(sub));
if (ret < 0) {
/* LCOV_EXCL_START */
ferr("Invalid 'hardlink' specification in '%s' at line %u\n", path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
c = sgeteol(f);
if (c != '\n') {
/* LCOV_EXCL_START */
ferr("Invalid 'hardlink' specification in '%s' at line %u\n", path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
++line;
ret = sgettok(f, tokento, sizeof(tokento));
if (ret < 0) {
/* LCOV_EXCL_START */
ferr("Invalid 'hardlink' specification in '%s' at line %u\n", path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
if (strcmp(tokento, "to") != 0) {
/* LCOV_EXCL_START */
ferr("Invalid 'hardlink' specification in '%s' at line %u\n", path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
c = sgetc(f);
if (c != ' ') {
/* LCOV_EXCL_START */
ferr("Invalid 'hardlink' specification in '%s' at line %u\n", path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
ret = sgetline(f, linkto, sizeof(linkto));
if (ret < 0) {
/* LCOV_EXCL_START */
ferr("Invalid 'hardlink' specification in '%s' at line %u\n", path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
if (!*sub || !*linkto) {
/* LCOV_EXCL_START */
ferr("Invalid 'hardlink' specification in '%s' at line %u\n", path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
/* find the disk */
2013-08-17 16:17:53 +02:00
disk = find_disk(state, buffer);
if (!disk) {
/* LCOV_EXCL_START */
ferr("Disk named '%s' not found in '%s' at line %u\n", buffer, path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
/* allocate the link as hard link */
link = link_alloc(sub, linkto, FILE_IS_HARDLINK);
/* insert the link in the link containers */
tommy_hashdyn_insert(&disk->linkset, &link->nodeset, link, link_name_hash(link->sub));
tommy_list_insert_tail(&disk->linklist, &link->nodelist, link);
/* stat */
++count_hardlink;
} else if (strcmp(tag, "dir") == 0) {
/* dir */
char sub[PATH_MAX];
struct snapraid_dir* dir;
ret = sgettok(f, buffer, sizeof(buffer));
if (ret < 0) {
/* LCOV_EXCL_START */
ferr("Invalid 'dir' specification in '%s' at line %u\n", path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
c = sgetc(f);
if (c != ' ') {
/* LCOV_EXCL_START */
ferr("Invalid 'dir' specification in '%s' at line %u\n", path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
ret = sgetline(f, sub, sizeof(sub));
if (ret < 0) {
/* LCOV_EXCL_START */
ferr("Invalid 'dir' specification in '%s' at line %u\n", path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
if (!*sub) {
/* LCOV_EXCL_START */
ferr("Invalid 'dir' specification in '%s' at line %u\n", path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
/* find the disk */
2013-08-17 16:17:53 +02:00
disk = find_disk(state, buffer);
if (!disk) {
/* LCOV_EXCL_START */
ferr("Disk named '%s' not found in '%s' at line %u\n", buffer, path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
/* allocate the dir */
dir = dir_alloc(sub);
/* insert the dir in the dir containers */
tommy_hashdyn_insert(&disk->dirset, &dir->nodeset, dir, dir_name_hash(dir->sub));
tommy_list_insert_tail(&disk->dirlist, &dir->nodelist, dir);
/* stat */
++count_dir;
2011-06-26 11:10:51 +02:00
} else if (strcmp(tag, "checksum") == 0) {
2011-12-31 12:09:05 +01:00
2011-06-26 11:10:51 +02:00
ret = sgettok(f, buffer, sizeof(buffer));
if (ret < 0) {
/* LCOV_EXCL_START */
ferr("Invalid 'checksum' specification in '%s' at line %u\n", path, line);
2011-06-23 20:28:52 +02:00
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2011-06-23 20:28:52 +02:00
}
2013-07-01 23:08:25 +02:00
if (strcmp(buffer, "murmur3") == 0) {
2011-06-23 20:28:52 +02:00
state->hash = HASH_MURMUR3;
} else if (strcmp(buffer, "spooky2") == 0) {
state->hash = HASH_SPOOKY2;
2011-06-23 20:28:52 +02:00
} else {
/* LCOV_EXCL_START */
ferr("Invalid 'checksum' specification '%s' in '%s' at line %u\n", buffer, path, line);
2011-06-23 20:28:52 +02:00
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2011-06-23 20:28:52 +02:00
}
2013-06-16 00:33:46 +02:00
c = sgetc(f);
if (c == ' ') {
/* read the seed if present */
ret = sgethex(f, state->hashseed, HASH_SIZE);
if (ret < 0) {
/* LCOV_EXCL_START */
ferr("Invalid 'seed' specification in '%s' at line %u\n", path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
} else {
sungetc(c, f);
memset(state->hashseed, 0, HASH_SIZE);
2013-06-16 00:33:46 +02:00
}
2013-07-01 23:08:25 +02:00
} else if (strcmp(tag, "prevchecksum") == 0) {
ret = sgettok(f, buffer, sizeof(buffer));
if (ret < 0) {
/* LCOV_EXCL_START */
ferr("Invalid 'prevchecksum' specification in '%s' at line %u\n", path, line);
2013-07-01 23:08:25 +02:00
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2013-07-01 23:08:25 +02:00
}
if (strcmp(buffer, "murmur3") == 0) {
state->prevhash = HASH_MURMUR3;
} else if (strcmp(buffer, "spooky2") == 0) {
state->prevhash = HASH_SPOOKY2;
} else {
/* LCOV_EXCL_START */
ferr("Invalid 'prevchecksum' specification '%s' in '%s' at line %u\n", buffer, path, line);
2013-07-01 23:08:25 +02:00
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2013-07-01 23:08:25 +02:00
}
c = sgetc(f);
if (c != ' ') {
/* LCOV_EXCL_START */
ferr("Invalid 'prevchecksum' specification '%s' in '%s' at line %u\n", buffer, path, line);
2013-07-01 23:08:25 +02:00
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2013-07-01 23:08:25 +02:00
}
/* read the seed */
ret = sgethex(f, state->prevhashseed, HASH_SIZE);
if (ret < 0) {
/* LCOV_EXCL_START */
ferr("Invalid 'seed' specification in '%s' at line %u\n", path, line);
2013-07-01 23:08:25 +02:00
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2013-07-01 23:08:25 +02:00
}
2011-06-26 11:10:51 +02:00
} else if (strcmp(tag, "blksize") == 0) {
block_off_t blksize;
2011-06-26 11:10:51 +02:00
ret = sgetu32(f, &blksize);
if (ret < 0) {
/* LCOV_EXCL_START */
ferr("Invalid 'blksize' specification in '%s' at line %u\n", path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
2013-11-26 18:43:12 +01:00
/* without configuration, auto assign the block size */
if (state->no_conf) {
state->block_size = blksize;
}
if (blksize != state->block_size) {
/* LCOV_EXCL_START */
ferr("Mismatching 'blksize' and 'blocksize' specification in '%s' at line %u\n", path, line);
ferr("Please restore the 'blocksize' value in the configuration file to '%u'\n", blksize / 1024);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
} else if (strcmp(tag, "map") == 0 || strcmp(tag, "mapping") == 0) {
struct snapraid_map* map;
char uuid[UUID_MAX];
uint32_t v_pos;
uint32_t v_total_blocks;
uint32_t v_free_blocks;
ret = sgettok(f, buffer, sizeof(buffer));
if (ret < 0) {
/* LCOV_EXCL_START */
ferr("Invalid 'map' specification in '%s' at line %u\n", path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
c = sgetc(f);
if (c != ' ') {
/* LCOV_EXCL_START */
ferr("Invalid 'map' specification in '%s' at line %u\n", path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
ret = sgetu32(f, &v_pos);
if (ret < 0) {
/* LCOV_EXCL_START */
ferr("Invalid 'map' specification in '%s' at line %u\n", path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
/* from SnapRAID 7.0 the 'mapping' command includes the free space */
if (strcmp(tag, "mapping") == 0) {
c = sgetc(f);
if (c != ' ') {
/* LCOV_EXCL_START */
ferr("Invalid 'map' specification in '%s' at line %u\n", path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
ret = sgetu32(f, &v_total_blocks);
if (ret < 0) {
/* LCOV_EXCL_START */
ferr("Invalid 'map' specification in '%s' at line %u\n", path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
c = sgetc(f);
if (c != ' ') {
/* LCOV_EXCL_START */
ferr("Invalid 'map' specification in '%s' at line %u\n", path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
ret = sgetu32(f, &v_free_blocks);
if (ret < 0) {
/* LCOV_EXCL_START */
ferr("Invalid 'map' specification in '%s' at line %u\n", path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
} else {
v_total_blocks = 0;
v_free_blocks = 0;
}
/* check if the uuid is present */
/* uuid must be the latest one as it could be also empty */
c = sgetc(f);
if (c != ' ') {
sungetc(c, f);
uuid[0] = 0;
} else {
/* read the uuid */
ret = sgettok(f, uuid, sizeof(uuid));
if (ret < 0) {
/* LCOV_EXCL_START */
ferr("Invalid 'map' specification in '%s' at line %u\n", path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
}
map = map_alloc(buffer, v_pos, v_total_blocks, v_free_blocks, uuid);
tommy_list_insert_tail(&state->maplist, &map->node, map);
} else if (strcmp(tag, "parity") == 0) {
/* from SnapRAID 7.0 the 'parity' command includes the free space */
char uuid[UUID_MAX];
uint32_t v_level;
uint32_t v_total_blocks;
uint32_t v_free_blocks;
ret = sgetu32(f, &v_level);
if (ret < 0) {
/* LCOV_EXCL_START */
ferr("Invalid 'parity' specification in '%s' at line %u\n", path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
c = sgetc(f);
if (c != ' ') {
/* LCOV_EXCL_START */
ferr("Invalid 'parity' specification in '%s' at line %u\n", path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
ret = sgetu32(f, &v_total_blocks);
if (ret < 0) {
/* LCOV_EXCL_START */
ferr("Invalid 'parity' specification in '%s' at line %u\n", path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
c = sgetc(f);
if (c != ' ') {
/* LCOV_EXCL_START */
ferr("Invalid 'parity' specification in '%s' at line %u\n", path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
ret = sgetu32(f, &v_free_blocks);
if (ret < 0) {
/* LCOV_EXCL_START */
ferr("Invalid 'parity' specification in '%s' at line %u\n", path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
/* check if the uuid is present */
/* uuid must be the latest one as it could be also empty */
c = sgetc(f);
if (c != ' ') {
sungetc(c, f);
uuid[0] = 0;
} else {
/* read the uuid */
ret = sgettok(f, uuid, sizeof(uuid));
if (ret < 0) {
/* LCOV_EXCL_START */
ferr("Invalid 'map' specification in '%s' at line %u\n", path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
}
2014-11-09 19:37:25 +01:00
/* auto configure if configuration is missing */
if (state->no_conf && v_level < LEV_MAX && v_level >= state->level)
state->level = v_level + 1;
/* if we use this parity entry */
if (v_level < state->level) {
/* set the parity info */
pathcpy(state->parity[v_level].uuid, sizeof(state->parity[v_level].uuid), uuid);
state->parity[v_level].total_blocks = v_total_blocks;
state->parity[v_level].free_blocks = v_free_blocks;
}
2011-12-31 12:09:05 +01:00
} else if (strcmp(tag, "sign") == 0) {
uint32_t sign;
2011-12-31 12:09:05 +01:00
ret = sgetu32(f, &sign);
if (ret < 0) {
/* LCOV_EXCL_START */
ferr("Invalid 'sign' specification in '%s' at line %u\n", path, line);
2011-12-31 12:09:05 +01:00
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2011-12-31 12:09:05 +01:00
}
/* sign check not supported anymore for the text content file */
2011-06-26 11:10:51 +02:00
} else if (tag[0] == 0) {
/* allow empty lines */
sgetspace(f);
} else if (tag[0] == '#') {
ret = sgetline(f, buffer, sizeof(buffer));
if (ret < 0) {
/* LCOV_EXCL_START */
ferr("Invalid comment in '%s' at line %u\n", path, line);
2011-06-26 11:10:51 +02:00
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2011-06-26 11:10:51 +02:00
}
} else {
/* LCOV_EXCL_START */
ferr("Invalid command '%s' in '%s' at line %u\n", tag, path, line);
2011-06-26 11:10:51 +02:00
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2011-06-23 20:28:52 +02:00
}
/* next line */
2011-06-26 11:10:51 +02:00
c = sgeteol(f);
2011-06-23 20:28:52 +02:00
if (c == EOF) {
break;
}
if (c != '\n') {
/* LCOV_EXCL_START */
ferr("Extra data in '%s' at line %u\n", path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
2011-06-23 20:28:52 +02:00
++line;
}
2011-06-26 11:10:51 +02:00
if (serror(f)) {
/* LCOV_EXCL_START */
ferr("Error reading the content file '%s' at line %u\n", path, line);
2011-06-23 20:28:52 +02:00
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
if (file) {
/* LCOV_EXCL_START */
ferr("Missing 'blk' specification in '%s' at line %u\n", path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
2013-08-17 16:17:53 +02:00
/* check that the stored parity size matches the loaded state */
if (blockmax != parity_allocated_size(state)) {
/* LCOV_EXCL_START */
ferr("Internal inconsistency in parity size in '%s' at line %u\n", path, line);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
/* to be backward compatible with old context files without info */
/* create info records if missing */
2014-09-27 00:32:39 +02:00
for (b = 0; b < blockmax; ++b) {
/* if the position is used */
if (info_is_required(state, b)) {
snapraid_info info = info_get(&state->infoarr, b);
/* if no info is present */
if (!info) {
/* LCOV_EXCL_START */
/* set a fake info block */
info = info_make(save_time, 0, 0);
/* insert the info in the array */
info_set(&state->infoarr, b, info);
/* LCOV_EXCL_STOP */
}
}
}
if (state->opt.verbose) {
fout("%8u files\n", count_file);
fout("%8u hardlinks\n", count_hardlink);
fout("%8u symlinks\n", count_symlink);
fout("%8u empty dirs\n", count_dir);
}
}
2013-08-17 16:17:53 +02:00
static void state_write_text(struct snapraid_state* state, STREAM* f)
{
unsigned count_file;
unsigned count_hardlink;
unsigned count_symlink;
unsigned count_dir;
tommy_node* i;
block_off_t b;
2013-07-01 23:08:25 +02:00
block_off_t blockmax;
int info_has_rehash;
unsigned l;
count_file = 0;
count_hardlink = 0;
count_symlink = 0;
count_dir = 0;
2013-07-01 23:08:25 +02:00
/* blocks of all array */
blockmax = parity_allocated_size(state);
/* clear the info for unused blocks */
/* and get some other info */
info_has_rehash = 0; /* if there is a rehash info */
2014-09-27 00:32:39 +02:00
for (b = 0; b < blockmax; ++b) {
/* if the position is used */
if (position_is_required(state, b)) {
snapraid_info info = info_get(&state->infoarr, b);
/* only if there is some info to store */
if (info) {
if (info_get_rehash(info))
info_has_rehash = 1;
}
} else {
/* clear any previous info */
info_set(&state->infoarr, b, 0);
/* and clear any deleted blocks */
position_clear_deleted(state, b);
}
}
sputsl("blksize ", f);
sputu32(state->block_size, f);
sputeol(f);
if (serror(f)) {
/* LCOV_EXCL_START */
ferr("Error writing the content file '%s'. %s.\n", serrorfile(f), strerror(errno));
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
2013-07-01 23:08:25 +02:00
if (state->hash == HASH_MURMUR3) {
sputsl("checksum murmur3", f);
} else if (state->hash == HASH_SPOOKY2) {
sputsl("checksum spooky2", f);
} else {
/* LCOV_EXCL_START */
ferr("Unexpected hash when writing the content file '%s'.\n", serrorfile(f));
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2011-12-31 12:09:05 +01:00
}
sputc(' ', f);
2013-06-16 00:33:46 +02:00
sputhex(state->hashseed, HASH_SIZE, f);
sputeol(f);
if (serror(f)) {
/* LCOV_EXCL_START */
ferr("Error writing the content file '%s'. %s.\n", serrorfile(f), strerror(errno));
2013-06-16 00:33:46 +02:00
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2013-06-16 00:33:46 +02:00
}
/* previous hash only present */
2013-07-01 23:08:25 +02:00
if (state->prevhash != HASH_UNDEFINED) {
/* if at least one rehash tag found, we have to save the previous hash */
if (info_has_rehash) {
2013-07-01 23:08:25 +02:00
if (state->prevhash == HASH_MURMUR3) {
sputsl("prevchecksum murmur3", f);
} else if (state->prevhash == HASH_SPOOKY2) {
2013-07-01 23:08:25 +02:00
sputsl("prevchecksum spooky2", f);
} else {
/* LCOV_EXCL_START */
ferr("Unexpected prevhash when writing the content file '%s'.\n", serrorfile(f));
2013-07-01 23:08:25 +02:00
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2013-07-01 23:08:25 +02:00
}
sputc(' ', f);
sputhex(state->prevhashseed, HASH_SIZE, f);
sputeol(f);
if (serror(f)) {
/* LCOV_EXCL_START */
ferr("Error writing the content file '%s'. %s.\n", serrorfile(f), strerror(errno));
2013-07-01 23:08:25 +02:00
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2013-07-01 23:08:25 +02:00
}
}
}
/* for each map */
2014-09-27 00:32:39 +02:00
for (i = state->maplist; i != 0; i = i->next) {
struct snapraid_map* map = i->data;
struct snapraid_disk* disk;
/* find the disk for this mapping */
disk = find_disk(state, map->name);
if (!disk) {
/* LCOV_EXCL_START */
ferr("Internal inconsistency for unmapped disk '%s'\n", map->name);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
/* save the mapping only for not empty disks */
if (!disk_is_empty(disk, blockmax)) {
sputsl("mapping ", f);
sputs(map->name, f);
sputc(' ', f);
sputu32(map->position, f);
sputc(' ', f);
sputu32(map->total_blocks, f);
sputc(' ', f);
sputu32(map->free_blocks, f);
/* if there is an uuid, print it */
if (map->uuid[0]) {
sputc(' ', f);
sputs(map->uuid, f);
}
sputeol(f);
if (serror(f)) {
/* LCOV_EXCL_START */
ferr("Error writing the content file '%s'. %s.\n", serrorfile(f), strerror(errno));
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
/* mark the disk as with mapping */
2014-11-09 10:51:48 +01:00
disk->mapping_idx = 0;
} else {
/* mark the disk as without mapping */
2014-11-09 10:51:48 +01:00
disk->mapping_idx = -1;
}
}
/* for each parity */
for (l = 0; l < state->level; ++l) {
sputsl("parity ", f);
sputu32(l, f);
sputc(' ', f);
sputu32(state->parity[l].total_blocks, f);
sputc(' ', f);
sputu32(state->parity[l].free_blocks, f);
/* if there is an uuid, print it */
if (state->parity[l].uuid[0]) {
sputc(' ', f);
sputs(state->parity[l].uuid, f);
}
sputeol(f);
if (serror(f)) {
/* LCOV_EXCL_START */
ferr("Error writing the content file '%s'. %s.\n", serrorfile(f), strerror(errno));
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
}
/* for each disk */
2014-09-27 00:32:39 +02:00
for (i = state->disklist; i != 0; i = i->next) {
tommy_node* j;
struct snapraid_disk* disk = i->data;
/* if the disk is not mapped, skip it */
2014-11-09 10:51:48 +01:00
if (disk->mapping_idx < 0)
continue;
/* for each file */
2014-09-27 00:32:39 +02:00
for (j = disk->filelist; j != 0; j = j->next) {
struct snapraid_file* file = j->data;
uint64_t size;
uint64_t mtime_sec;
int32_t mtime_nsec;
uint64_t inode;
size = file->size;
mtime_sec = file->mtime_sec;
mtime_nsec = file->mtime_nsec;
inode = file->inode;
sputsl("file ", f);
sputs(disk->name, f);
sputc(' ', f);
sputu64(size, f);
sputc(' ', f);
sputu64(mtime_sec, f);
if (mtime_nsec != STAT_NSEC_INVALID) {
sputc('.', f);
sputu32(mtime_nsec, f);
}
sputc(' ', f);
sputu64(inode, f);
sputc(' ', f);
sputs(file->sub, f);
sputeol(f);
if (serror(f)) {
/* LCOV_EXCL_START */
ferr("Error writing the content file '%s'. %s.\n", serrorfile(f), strerror(errno));
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
/* for each block in the file */
2014-09-27 00:32:39 +02:00
for (b = 0; b < file->blockmax; ++b) {
struct snapraid_block* block = &file->blockvec[b];
unsigned block_state = block_state_get(block);
/* consistency check */
if (block != disk_block_get(disk, block->parity_pos)) {
/* LCOV_EXCL_START */
ferr("Internal inconsistency mismatch for block %u state %u in file '%s'\n", block->parity_pos, block_state, file->sub);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
switch (block_state) {
case BLOCK_STATE_BLK :
sputsl("blk ", f);
break;
case BLOCK_STATE_CHG :
sputsl("chg ", f);
break;
case BLOCK_STATE_REP :
sputsl("rep ", f);
break;
2014-09-27 00:32:39 +02:00
default :
/* LCOV_EXCL_START */
ferr("Internal inconsistency in state for block %u state %u\n", block->parity_pos, block_state);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
2011-12-31 12:09:05 +01:00
sputu32(block->parity_pos, f);
sputc(' ', f);
sputhex(block->hash, HASH_SIZE, f);
2011-12-31 12:09:05 +01:00
sputeol(f);
if (serror(f)) {
/* LCOV_EXCL_START */
ferr("Error writing the content file '%s'. %s.\n", serrorfile(f), strerror(errno));
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
}
++count_file;
}
/* for each link */
2014-09-27 00:32:39 +02:00
for (j = disk->linklist; j != 0; j = j->next) {
struct snapraid_link* link = j->data;
switch (link_flag_get(link, FILE_IS_LINK_MASK)) {
case FILE_IS_HARDLINK :
sputsl("hardlink ", f);
++count_hardlink;
break;
case FILE_IS_SYMLINK :
sputsl("symlink ", f);
++count_symlink;
break;
}
sputs(disk->name, f);
sputc(' ', f);
sputs(link->sub, f);
sputeol(f);
sputsl("to ", f);
sputs(link->linkto, f);
sputeol(f);
if (serror(f)) {
/* LCOV_EXCL_START */
ferr("Error writing the content file '%s'. %s.\n", serrorfile(f), strerror(errno));
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
}
/* for each dir */
2014-09-27 00:32:39 +02:00
for (j = disk->dirlist; j != 0; j = j->next) {
struct snapraid_dir* dir = j->data;
sputsl("dir ", f);
sputs(disk->name, f);
sputc(' ', f);
sputs(dir->sub, f);
sputeol(f);
if (serror(f)) {
/* LCOV_EXCL_START */
ferr("Error writing the content file '%s'. %s.\n", serrorfile(f), strerror(errno));
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
++count_dir;
}
/* deleted blocks of the disk */
sputsl("hole ", f);
sputs(disk->name, f);
sputeol(f);
if (serror(f)) {
/* LCOV_EXCL_START */
ferr("Error writing the content file '%s'. %s.\n", serrorfile(f), strerror(errno));
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
2014-09-27 00:32:39 +02:00
for (b = 0; b < blockmax; ++b) {
if (is_block_deleted(disk, b)) {
struct snapraid_block* block = disk_block_get(disk, b);
/* consistency check */
if (block->parity_pos != b) {
/* LCOV_EXCL_START */
ferr("Internal inconsistency in delete position %u/%u!\n", block->parity_pos, b);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
2014-09-27 00:32:39 +02:00
sputsl("off ", f);
sputu32(b, f);
sputc(' ', f);
sputhex(block->hash, HASH_SIZE, f);
sputeol(f);
if (serror(f)) {
/* LCOV_EXCL_START */
ferr("Error writing the content file '%s'. %s.\n", serrorfile(f), strerror(errno));
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
}
}
}
/* write the info for each block */
2014-09-27 00:32:39 +02:00
for (b = 0; b < blockmax; ++b) {
snapraid_info info = info_get(&state->infoarr, b);
2013-07-01 23:08:25 +02:00
/* save only stuffs different than 0 */
if (info != 0) {
sputsl("inf ", f);
2013-07-01 23:08:25 +02:00
sputu32(b, f);
sputc(' ', f);
sputu32(info, f);
if (info_get_bad(info)) {
sputsl(" bad", f);
}
if (info_get_rehash(info)) {
sputsl(" rehash", f);
}
sputeol(f);
if (serror(f)) {
/* LCOV_EXCL_START */
ferr("Error writing the content file '%s'. %s.\n", serrorfile(f), strerror(errno));
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
}
}
2011-12-31 12:09:05 +01:00
if (serror(f)) {
/* LCOV_EXCL_START */
ferr("Error writing the content file '%s'. %s.\n", serrorfile(f), strerror(errno));
2011-12-31 12:09:05 +01:00
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2011-12-31 12:09:05 +01:00
}
2013-08-17 16:17:53 +02:00
if (state->opt.verbose) {
fout("%8u files\n", count_file);
fout("%8u hardlinks\n", count_hardlink);
fout("%8u symlinks\n", count_symlink);
fout("%8u empty dirs\n", count_dir);
2013-08-17 16:17:53 +02:00
}
}
2013-08-17 16:17:53 +02:00
/**
* Flush the file checking the final CRC.
* We exploit the fact that the CRC is always stored in the last 4 bytes.
*/
static void decoding_error(const char* path, STREAM* f)
{
unsigned char buf[4];
uint32_t crc_stored;
uint32_t crc_computed;
if (seof(f)) {
/* LCOV_EXCL_START */
ferr("Unexpected end of content file '%s' at offset %" PRIi64 "\n", path, stell(f));
ferr("This content file is truncated. Use an alternate copy.\n");
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
2013-08-17 16:17:53 +02:00
if (serror(f)) {
/* LCOV_EXCL_START */
ferr("Error reading the content file '%s' at offset %" PRIi64 "\n", path, stell(f));
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
ferr("Decoding error in '%s' at offset %" PRIi64 "\n", path, stell(f));
2013-08-17 16:17:53 +02:00
buf[0] = 0;
buf[1] = 0;
buf[2] = 0;
buf[3] = 0;
/* read until the end of the file */
while (1) {
int c = sgetc(f);
if (c == EOF) {
break;
}
/* keep the last four bytes */
buf[0] = buf[1];
buf[1] = buf[2];
buf[2] = buf[3];
buf[3] = c;
}
if (serror(f)) {
/* LCOV_EXCL_START */
ferr("Error flushing the content file '%s' at offset %" PRIi64 "\n", path, stell(f));
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
2013-08-17 16:17:53 +02:00
/* get the stored crc from the last four bytes */
crc_stored = buf[0] | (uint32_t)buf[1] << 8 | (uint32_t)buf[2] << 16 | (uint32_t)buf[3] << 24;
/* get the computed crc */
crc_computed = scrc(f);
/* adjust the stored crc to include itself */
crc_stored = crc32c(crc_stored, buf, 4);
2013-08-17 16:17:53 +02:00
if (crc_computed != crc_stored) {
ferr("Mismatching CRC in '%s'\n", path);
ferr("This content file is damaged! Use an alternate copy.\n");
2013-08-17 16:17:53 +02:00
exit(EXIT_FAILURE);
} else {
ferr("The file CRC is correct!\n");
}
2013-08-17 16:17:53 +02:00
}
2013-08-17 16:17:53 +02:00
static void state_read_binary(struct snapraid_state* state, const char* path, STREAM* f)
{
block_off_t blockmax;
unsigned count_file;
unsigned count_hardlink;
unsigned count_symlink;
unsigned count_dir;
int crc_checked;
char buffer[PATH_MAX];
int ret;
tommy_array disk_mapping;
uint32_t mapping_max;
2013-08-17 16:17:53 +02:00
blockmax = 0;
count_file = 0;
count_hardlink = 0;
count_symlink = 0;
count_dir = 0;
crc_checked = 0;
mapping_max = 0;
tommy_array_init(&disk_mapping);
2013-08-17 16:17:53 +02:00
ret = sread(f, buffer, 12);
if (ret < 0) {
/* LCOV_EXCL_START */
2013-08-17 16:17:53 +02:00
decoding_error(path, f);
ferr("Invalid header!\n");
2013-08-17 16:17:53 +02:00
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
/*
* File format versions:
* - SNAPCNT1/SnapRAID 4.0 First version.
* - SNAPCNT2/SnapRAID 7.0 Adds entries 'M' and 'P', to add free_blocks support.
* The previous 'm' entry is now deprecated, but supported for importing.
* Similarly for text file, we add 'mapping' and 'parity' deprecating 'map'.
*/
if (memcmp(buffer, "SNAPCNT1\n\3\0\0", 12) != 0
&& memcmp(buffer, "SNAPCNT2\n\3\0\0", 12) != 0) {
/* LCOV_EXCL_START */
if (memcmp(buffer, "SNAPCNT", 7) != 0) {
decoding_error(path, f);
ferr("Invalid header!\n");
} else {
ferr("The content file '%s' was generated with a newer version of SnapRAID!\n", path);
}
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
2013-08-17 16:17:53 +02:00
while (1) {
int c;
/* read the command */
c = sgetc(f);
if (c == EOF) {
break;
}
if (c == 'f') {
/* file */
char sub[PATH_MAX];
uint64_t v_size;
uint64_t v_mtime_sec;
uint32_t v_mtime_nsec;
uint64_t v_inode;
uint32_t v_idx;
struct snapraid_file* file;
struct snapraid_disk* disk;
uint32_t mapping;
2013-08-17 16:17:53 +02:00
ret = sgetb32(f, &mapping);
if (ret < 0 || mapping >= mapping_max) {
/* LCOV_EXCL_START */
2013-08-17 16:17:53 +02:00
decoding_error(path, f);
ferr("Internal inconsistency in mapping index!\n");
2013-08-17 16:17:53 +02:00
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
disk = tommy_array_get(&disk_mapping, mapping);
2013-08-17 16:17:53 +02:00
ret = sgetb64(f, &v_size);
if (ret < 0) {
/* LCOV_EXCL_START */
2013-08-17 16:17:53 +02:00
decoding_error(path, f);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
/* check for impossible file size to avoid to crash for a too big allocation */
if (v_size / state->block_size > blockmax) {
/* LCOV_EXCL_START */
decoding_error(path, f);
ferr("Internal inconsistency in file size too big!\n");
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
2013-08-17 16:17:53 +02:00
ret = sgetb64(f, &v_mtime_sec);
if (ret < 0) {
/* LCOV_EXCL_START */
2013-08-17 16:17:53 +02:00
decoding_error(path, f);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
ret = sgetb32(f, &v_mtime_nsec);
if (ret < 0) {
/* LCOV_EXCL_START */
2013-08-17 16:17:53 +02:00
decoding_error(path, f);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
/* STAT_NSEC_INVALID is encoded as 0 */
if (v_mtime_nsec == 0)
v_mtime_nsec = STAT_NSEC_INVALID;
else
--v_mtime_nsec;
2013-08-17 16:17:53 +02:00
ret = sgetb64(f, &v_inode);
if (ret < 0) {
/* LCOV_EXCL_START */
2013-08-17 16:17:53 +02:00
decoding_error(path, f);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
ret = sgetbs(f, sub, sizeof(sub));
if (ret < 0) {
/* LCOV_EXCL_START */
2013-08-17 16:17:53 +02:00
decoding_error(path, f);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
if (!*sub) {
/* LCOV_EXCL_START */
2013-08-17 16:17:53 +02:00
decoding_error(path, f);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
/* allocate the file */
file = file_alloc(state->block_size, sub, v_size, v_mtime_sec, v_mtime_nsec, v_inode, 0);
/* insert the file in the file containers */
tommy_hashdyn_insert(&disk->inodeset, &file->nodeset, file, file_inode_hash(file->inode));
tommy_hashdyn_insert(&disk->pathset, &file->pathset, file, file_path_hash(file->sub));
tommy_hashdyn_insert(&disk->stampset, &file->stampset, file, file_stamp_hash(file->size, file->mtime_sec, file->mtime_nsec));
2013-08-17 16:17:53 +02:00
tommy_list_insert_tail(&disk->filelist, &file->nodelist, file);
/* reads all the blocks */
v_idx = 0;
while (v_idx < file->blockmax) {
block_off_t v_pos;
uint32_t v_count;
/* get the "subcommand */
2013-08-17 16:17:53 +02:00
c = sgetc(f);
ret = sgetb32(f, &v_pos);
if (ret < 0) {
/* LCOV_EXCL_START */
2013-08-17 16:17:53 +02:00
decoding_error(path, f);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
ret = sgetb32(f, &v_count);
if (ret < 0) {
/* LCOV_EXCL_START */
2013-08-17 16:17:53 +02:00
decoding_error(path, f);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
if (v_idx + v_count > file->blockmax) {
/* LCOV_EXCL_START */
2013-08-17 16:17:53 +02:00
decoding_error(path, f);
ferr("Internal inconsistency in block number!\n");
2013-08-17 16:17:53 +02:00
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
if (v_pos + v_count > blockmax) {
/* LCOV_EXCL_START */
ferr("Internal inconsistency in block size %u/%u!\n", blockmax, v_pos + v_count);
decoding_error(path, f);
exit(EXIT_FAILURE);
/* LCOV_EXCL_START */
2013-08-17 16:17:53 +02:00
}
/* grow the array */
tommy_arrayblk_grow(&disk->blockarr, v_pos + v_count);
2013-08-17 16:17:53 +02:00
/* fill the blocks in the run */
while (v_count) {
struct snapraid_block* block = &file->blockvec[v_idx];
if (block->parity_pos != POS_INVALID) {
/* LCOV_EXCL_START */
2013-08-17 16:17:53 +02:00
decoding_error(path, f);
ferr("Internal inconsistency in block position!\n");
2013-08-17 16:17:53 +02:00
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
switch (c) {
case 'b' :
block_state_set(block, BLOCK_STATE_BLK);
break;
case 'n' :
/* deprecated NEW blocks are converted to CHG ones */
block_state_set(block, BLOCK_STATE_CHG);
2013-08-17 16:17:53 +02:00
break;
case 'g' :
block_state_set(block, BLOCK_STATE_CHG);
break;
case 'p' :
block_state_set(block, BLOCK_STATE_REP);
break;
2014-09-27 00:32:39 +02:00
default :
/* LCOV_EXCL_START */
2013-08-17 16:17:53 +02:00
decoding_error(path, f);
ferr("Invalid block type!\n");
2013-08-17 16:17:53 +02:00
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
block->parity_pos = v_pos;
/* read the hash only for 'blk/chg/rep', and not for 'new' */
2013-08-17 16:17:53 +02:00
if (c != 'n') {
ret = sread(f, block->hash, HASH_SIZE);
if (ret < 0) {
/* LCOV_EXCL_START */
2013-08-17 16:17:53 +02:00
decoding_error(path, f);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
} else {
/* set the ZERO hash for deprecated NEW blocks */
hash_zero_set(block->hash);
}
/* if the block contains a hash of past data */
/* and we are clearing such undeterminated hashes */
if (state->clear_past_hash
&& block_has_past_hash(block)
) {
/* set the hash value to INVALID */
hash_invalid_set(block->hash);
2013-08-17 16:17:53 +02:00
}
/* if we are disabling the copy optimization */
/* we want also to clear any already previously stored information */
/* in other sync commands */
/* note that this is required only in sync, and we detect */
/* this using the clear_past_hash flag */
if (state->clear_past_hash
&& state->opt.force_nocopy
&& block_state_get(block) == BLOCK_STATE_REP
) {
/* set the hash value to INVALID */
hash_invalid_set(block->hash);
/* convert from REP to CHG block */
block_state_set(block, BLOCK_STATE_CHG);
}
/* if we want a full sync, marks block as invalid parity */
/* note that we do this after the force_nocopy option */
/* to avoid to mixup the two things */
if (state->opt.force_full
&& block_state_get(block) == BLOCK_STATE_BLK) {
/* convert from BLK to REP */
block_state_set(block, BLOCK_STATE_REP);
}
2013-08-17 16:17:53 +02:00
/* we must not overwrite existing blocks */
if (disk_block_get(disk, v_pos) != BLOCK_EMPTY) {
/* LCOV_EXCL_START */
2013-08-17 16:17:53 +02:00
decoding_error(path, f);
ferr("Internal inconsistency in block existence!\n");
2013-08-17 16:17:53 +02:00
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
/* insert the block in the block array */
tommy_arrayblk_set(&disk->blockarr, v_pos, block);
2013-08-17 16:17:53 +02:00
/* go to the next block */
++v_idx;
++v_pos;
--v_count;
}
}
/* stat */
++count_file;
} else if (c == 'i') {
/* "inf" command */
snapraid_info info;
uint32_t v_pos;
uint32_t v_oldest;
ret = sgetb32(f, &v_oldest);
if (ret < 0) {
/* LCOV_EXCL_START */
2013-08-17 16:17:53 +02:00
decoding_error(path, f);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
v_pos = 0;
while (v_pos < blockmax) {
int rehash;
int bad;
uint32_t t;
uint32_t flag;
uint32_t v_count;
ret = sgetb32(f, &v_count);
if (ret < 0) {
/* LCOV_EXCL_START */
2013-08-17 16:17:53 +02:00
decoding_error(path, f);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
if (v_pos + v_count > blockmax) {
/* LCOV_EXCL_START */
ferr("Internal inconsistency in info size %u/%u!\n", blockmax, v_pos + v_count);
decoding_error(path, f);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
ret = sgetb32(f, &flag);
if (ret < 0) {
/* LCOV_EXCL_START */
2013-08-17 16:17:53 +02:00
decoding_error(path, f);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
/* if there is an info */
if ((flag & 1) != 0) {
/* read the time */
ret = sgetb32(f, &t);
if (ret < 0) {
/* LCOV_EXCL_START */
2013-08-17 16:17:53 +02:00
decoding_error(path, f);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
/* analyze the flags */
bad = (flag & 2) != 0;
rehash = (flag & 4) != 0;
if (rehash && state->prevhash == HASH_UNDEFINED) {
/* LCOV_EXCL_START */
2013-08-17 16:17:53 +02:00
decoding_error(path, f);
ferr("Internal inconsistency for missing previous checksum!\n");
2013-08-17 16:17:53 +02:00
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
info = info_make(t + v_oldest, bad, rehash);
} else {
info = 0;
}
while (v_count) {
/* insert the info in the array */
info_set(&state->infoarr, v_pos, info);
/* ensure that an info is present only for used positions */
if (info_is_required(state, v_pos)) {
if (!info) {
/* LCOV_EXCL_START */
decoding_error(path, f);
ferr("Internal inconsistency for missing info!\n");
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
} else {
/* extra info are accepted for backward compatibility */
/* they are discarged at the first write */
}
2013-08-17 16:17:53 +02:00
/* go to next block */
++v_pos;
--v_count;
}
}
} else if (c == 'h') {
/* hole */
uint32_t v_pos;
struct snapraid_disk* disk;
uint32_t mapping;
2013-08-17 16:17:53 +02:00
ret = sgetb32(f, &mapping);
if (ret < 0 || mapping >= mapping_max) {
/* LCOV_EXCL_START */
2013-08-17 16:17:53 +02:00
decoding_error(path, f);
ferr("Internal inconsistency in mapping index!\n");
2013-08-17 16:17:53 +02:00
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
disk = tommy_array_get(&disk_mapping, mapping);
2013-08-17 16:17:53 +02:00
v_pos = 0;
while (v_pos < blockmax) {
2013-08-17 16:17:53 +02:00
uint32_t v_count;
ret = sgetb32(f, &v_count);
if (ret < 0) {
/* LCOV_EXCL_START */
2013-08-17 16:17:53 +02:00
decoding_error(path, f);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
if (v_pos + v_count > blockmax) {
/* LCOV_EXCL_START */
ferr("Internal inconsistency in hole size %u/%u!\n", blockmax, v_pos + v_count);
decoding_error(path, f);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
/* get the subcommand */
c = sgetc(f);
2013-08-17 16:17:53 +02:00
switch (c) {
case 'o' :
/* grow the array */
tommy_arrayblk_grow(&disk->blockarr, v_pos + v_count);
/* if it's a run of deleted blocks */
2013-08-17 16:17:53 +02:00
while (v_count) {
struct snapraid_deleted* deleted;
/* allocate a new deleted block */
deleted = deleted_alloc();
/* set the position */
deleted->block.parity_pos = v_pos;
/* read the hash */
ret = sread(f, deleted->block.hash, HASH_SIZE);
if (ret < 0) {
/* LCOV_EXCL_START */
2013-08-17 16:17:53 +02:00
decoding_error(path, f);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
/* if we are clearing undeterminated hashes */
if (state->clear_past_hash) {
/* set the hash value to INVALID */
hash_invalid_set(deleted->block.hash);
}
2013-08-17 16:17:53 +02:00
/* we must not overwrite existing blocks */
if (disk_block_get(disk, v_pos) != BLOCK_EMPTY) {
/* LCOV_EXCL_START */
ferr("Internal inconsistency for used hole at pos %u!\n", v_pos);
2014-06-15 16:08:06 +02:00
if (state->opt.skip_content_check) {
ferr("Overriding as used.\n");
2014-06-15 16:08:06 +02:00
deleted_free(deleted);
} else {
decoding_error(path, f);
exit(EXIT_FAILURE);
}
/* LCOV_EXCL_STOP */
2014-06-15 16:08:06 +02:00
} else {
/* insert it in the list of deleted blocks */
tommy_list_insert_tail(&disk->deletedlist, &deleted->node, deleted);
2013-08-17 16:17:53 +02:00
2014-06-15 16:08:06 +02:00
/* insert the block in the block array */
tommy_arrayblk_set(&disk->blockarr, v_pos, &deleted->block);
}
2013-08-17 16:17:53 +02:00
/* go to next block */
++v_pos;
--v_count;
}
break;
case 'O' :
2013-08-17 16:17:53 +02:00
/* go to the next run */
v_pos += v_count;
break;
2014-09-27 00:32:39 +02:00
default :
/* LCOV_EXCL_START */
decoding_error(path, f);
ferr("Invalid hole type!\n");
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
}
} else if (c == 's') {
/* symlink */
char sub[PATH_MAX];
char linkto[PATH_MAX];
struct snapraid_link* link;
struct snapraid_disk* disk;
uint32_t mapping;
2013-08-17 16:17:53 +02:00
ret = sgetb32(f, &mapping);
if (ret < 0 || mapping >= mapping_max) {
/* LCOV_EXCL_START */
2013-08-17 16:17:53 +02:00
decoding_error(path, f);
ferr("Internal inconsistency in mapping index!\n");
2013-08-17 16:17:53 +02:00
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
disk = tommy_array_get(&disk_mapping, mapping);
2013-08-17 16:17:53 +02:00
ret = sgetbs(f, sub, sizeof(sub));
if (ret < 0) {
/* LCOV_EXCL_START */
2013-08-17 16:17:53 +02:00
decoding_error(path, f);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
ret = sgetbs(f, linkto, sizeof(linkto));
if (ret < 0) {
/* LCOV_EXCL_START */
2013-08-17 16:17:53 +02:00
decoding_error(path, f);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
if (!*sub || !*linkto) {
/* LCOV_EXCL_START */
2013-08-17 16:17:53 +02:00
decoding_error(path, f);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
/* allocate the link as symbolic link */
link = link_alloc(sub, linkto, FILE_IS_SYMLINK);
/* insert the link in the link containers */
tommy_hashdyn_insert(&disk->linkset, &link->nodeset, link, link_name_hash(link->sub));
tommy_list_insert_tail(&disk->linklist, &link->nodelist, link);
/* stat */
++count_symlink;
} else if (c == 'a') {
/* hardlink */
char sub[PATH_MAX];
char linkto[PATH_MAX];
struct snapraid_link* link;
struct snapraid_disk* disk;
uint32_t mapping;
2013-08-17 16:17:53 +02:00
ret = sgetb32(f, &mapping);
if (ret < 0 || mapping >= mapping_max) {
/* LCOV_EXCL_START */
2013-08-17 16:17:53 +02:00
decoding_error(path, f);
ferr("Internal inconsistency in mapping index!\n");
2013-08-17 16:17:53 +02:00
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
disk = tommy_array_get(&disk_mapping, mapping);
2013-08-17 16:17:53 +02:00
ret = sgetbs(f, sub, sizeof(sub));
if (ret < 0) {
/* LCOV_EXCL_START */
2013-08-17 16:17:53 +02:00
decoding_error(path, f);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
ret = sgetbs(f, linkto, sizeof(linkto));
if (ret < 0) {
/* LCOV_EXCL_START */
2013-08-17 16:17:53 +02:00
decoding_error(path, f);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
if (!*sub || !*linkto) {
/* LCOV_EXCL_START */
2013-08-17 16:17:53 +02:00
decoding_error(path, f);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
/* allocate the link as hard link */
link = link_alloc(sub, linkto, FILE_IS_HARDLINK);
/* insert the link in the link containers */
tommy_hashdyn_insert(&disk->linkset, &link->nodeset, link, link_name_hash(link->sub));
tommy_list_insert_tail(&disk->linklist, &link->nodelist, link);
/* stat */
++count_hardlink;
} else if (c == 'r') {
/* dir */
char sub[PATH_MAX];
struct snapraid_dir* dir;
struct snapraid_disk* disk;
uint32_t mapping;
2013-08-17 16:17:53 +02:00
ret = sgetb32(f, &mapping);
if (ret < 0 || mapping >= mapping_max) {
/* LCOV_EXCL_START */
2013-08-17 16:17:53 +02:00
decoding_error(path, f);
ferr("Internal inconsistency in mapping index!\n");
2013-08-17 16:17:53 +02:00
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
disk = tommy_array_get(&disk_mapping, mapping);
2013-08-17 16:17:53 +02:00
ret = sgetbs(f, sub, sizeof(sub));
if (ret < 0) {
/* LCOV_EXCL_START */
2013-08-17 16:17:53 +02:00
decoding_error(path, f);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
if (!*sub) {
/* LCOV_EXCL_START */
2013-08-17 16:17:53 +02:00
decoding_error(path, f);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
/* allocate the dir */
dir = dir_alloc(sub);
/* insert the dir in the dir containers */
tommy_hashdyn_insert(&disk->dirset, &dir->nodeset, dir, dir_name_hash(dir->sub));
tommy_list_insert_tail(&disk->dirlist, &dir->nodelist, dir);
/* stat */
++count_dir;
} else if (c == 'c') {
/* get the subcommand */
2013-08-17 16:17:53 +02:00
c = sgetc(f);
2013-08-17 16:17:53 +02:00
switch (c) {
case 'u' :
state->hash = HASH_MURMUR3;
break;
case 'k' :
state->hash = HASH_SPOOKY2;
break;
2014-09-27 00:32:39 +02:00
default :
/* LCOV_EXCL_START */
2013-08-17 16:17:53 +02:00
decoding_error(path, f);
ferr("Invalid checksum!\n");
2013-08-17 16:17:53 +02:00
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
/* read the seed */
ret = sread(f, state->hashseed, HASH_SIZE);
if (ret < 0) {
/* LCOV_EXCL_START */
2013-08-17 16:17:53 +02:00
decoding_error(path, f);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
} else if (c == 'C') {
/* get the subcommand */
2013-08-17 16:17:53 +02:00
c = sgetc(f);
2013-08-17 16:17:53 +02:00
switch (c) {
case 'u' :
state->prevhash = HASH_MURMUR3;
break;
case 'k' :
state->prevhash = HASH_SPOOKY2;
break;
2014-09-27 00:32:39 +02:00
default :
/* LCOV_EXCL_START */
2013-08-17 16:17:53 +02:00
decoding_error(path, f);
ferr("Invalid checksum!\n");
2013-08-17 16:17:53 +02:00
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
/* read the seed */
ret = sread(f, state->prevhashseed, HASH_SIZE);
if (ret < 0) {
/* LCOV_EXCL_START */
2013-08-17 16:17:53 +02:00
decoding_error(path, f);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
} else if (c == 'z') {
block_off_t blksize;
ret = sgetb32(f, &blksize);
if (ret < 0) {
/* LCOV_EXCL_START */
2013-08-17 16:17:53 +02:00
decoding_error(path, f);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
2013-11-26 18:43:12 +01:00
/* without configuration, auto assign the block size */
if (state->no_conf) {
state->block_size = blksize;
}
2013-08-17 16:17:53 +02:00
if (blksize != state->block_size) {
/* LCOV_EXCL_START */
2013-08-17 16:17:53 +02:00
decoding_error(path, f);
ferr("Mismatching 'blksize' and 'blocksize' specification!\n");
ferr("Please restore the 'blocksize' value in the configuration file to '%u'\n", blksize / 1024);
2013-08-17 16:17:53 +02:00
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
} else if (c == 'x') {
ret = sgetb32(f, &blockmax);
if (ret < 0) {
/* LCOV_EXCL_START */
decoding_error(path, f);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
} else if (c == 'm' || c == 'M') {
2013-08-17 16:17:53 +02:00
struct snapraid_map* map;
char uuid[UUID_MAX];
uint32_t v_pos;
uint32_t v_total_blocks;
uint32_t v_free_blocks;
struct snapraid_disk* disk;
2013-08-17 16:17:53 +02:00
ret = sgetbs(f, buffer, sizeof(buffer));
if (ret < 0) {
/* LCOV_EXCL_START */
2013-08-17 16:17:53 +02:00
decoding_error(path, f);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
ret = sgetb32(f, &v_pos);
if (ret < 0) {
/* LCOV_EXCL_START */
2013-08-17 16:17:53 +02:00
decoding_error(path, f);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
/* from SnapRAID 7.0 the 'M' command includes the free space */
if (c == 'M') {
ret = sgetb32(f, &v_total_blocks);
if (ret < 0) {
/* LCOV_EXCL_START */
decoding_error(path, f);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
ret = sgetb32(f, &v_free_blocks);
if (ret < 0) {
/* LCOV_EXCL_START */
decoding_error(path, f);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
} else {
v_total_blocks = 0;
v_free_blocks = 0;
}
2013-08-17 16:17:53 +02:00
/* read the uuid */
ret = sgetbs(f, uuid, sizeof(uuid));
if (ret < 0) {
/* LCOV_EXCL_START */
2013-08-17 16:17:53 +02:00
decoding_error(path, f);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
map = map_alloc(buffer, v_pos, v_total_blocks, v_free_blocks, uuid);
2013-08-17 16:17:53 +02:00
tommy_list_insert_tail(&state->maplist, &map->node, map);
/* find the disk */
disk = find_disk(state, buffer);
if (!disk) {
/* LCOV_EXCL_START */
decoding_error(path, f);
ferr("Disk named '%s' not present in the configuration file!\n", buffer);
ferr("If you have removed it from the configuration file, please restore it\n");
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
/* insert in the mapping vector */
tommy_array_grow(&disk_mapping, mapping_max + 1);
tommy_array_set(&disk_mapping, mapping_max, disk);
++mapping_max;
} else if (c == 'P') {
/* from SnapRAID 7.0 the 'P' command includes the free space */
char uuid[UUID_MAX];
uint32_t v_level;
uint32_t v_total_blocks;
uint32_t v_free_blocks;
ret = sgetb32(f, &v_level);
if (ret < 0) {
/* LCOV_EXCL_START */
decoding_error(path, f);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
ret = sgetb32(f, &v_total_blocks);
if (ret < 0) {
/* LCOV_EXCL_START */
decoding_error(path, f);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
ret = sgetb32(f, &v_free_blocks);
if (ret < 0) {
/* LCOV_EXCL_START */
decoding_error(path, f);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
ret = sgetbs(f, uuid, sizeof(uuid));
if (ret < 0) {
/* LCOV_EXCL_START */
decoding_error(path, f);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
2014-11-09 19:37:25 +01:00
/* auto configure if configuration is missing */
if (state->no_conf && v_level < LEV_MAX && v_level >= state->level)
state->level = v_level + 1;
/* if we use this parity entry */
if (v_level < state->level) {
/* set the parity info */
pathcpy(state->parity[v_level].uuid, sizeof(state->parity[v_level].uuid), uuid);
state->parity[v_level].total_blocks = v_total_blocks;
state->parity[v_level].free_blocks = v_free_blocks;
}
2013-08-17 16:17:53 +02:00
} else if (c == 'N') {
uint32_t crc_stored;
uint32_t crc_computed;
/* get the crc before reading it from the file */
crc_computed = scrc(f);
ret = sgetble32(f, &crc_stored);
if (ret < 0) {
/* LCOV_EXCL_START */
2013-08-17 16:17:53 +02:00
/* here don't call decoding_error() because it's too late to get the crc */
ferr("Error reading the CRC in '%s' at offset %" PRIi64 "\n", path, stell(f));
ferr("This content file is damaged! Use an alternate copy.\n");
2013-08-17 16:17:53 +02:00
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
if (crc_stored != crc_computed) {
/* LCOV_EXCL_START */
2013-08-17 16:17:53 +02:00
/* here don't call decoding_error() because it's too late to get the crc */
ferr("Mismatching CRC in '%s'\n", path);
ferr("This content file is damaged! Use an alternate copy.\n");
2013-08-17 16:17:53 +02:00
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
crc_checked = 1;
} else {
/* LCOV_EXCL_START */
2013-08-17 16:17:53 +02:00
decoding_error(path, f);
ferr("Invalid command '%c'!\n", (char)c);
2013-08-17 16:17:53 +02:00
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
}
tommy_array_done(&disk_mapping);
2013-08-17 16:17:53 +02:00
if (serror(f)) {
/* LCOV_EXCL_START */
ferr("Error reading the content file '%s' at offset %" PRIi64 "\n", path, stell(f));
2013-08-17 16:17:53 +02:00
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
if (!crc_checked) {
/* LCOV_EXCL_START */
ferr("Finished reading '%s' without finding the CRC\n", path);
ferr("This content file is truncated or damaged! Use an alternate copy.\n");
2013-08-17 16:17:53 +02:00
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
/* check that the stored parity size matches the loaded state */
if (blockmax != parity_allocated_size(state)) {
/* LCOV_EXCL_START */
ferr("Internal inconsistency in parity size %u/%u in '%s' at offset %" PRIi64 "\n", blockmax, parity_allocated_size(state), path, stell(f));
if (state->opt.skip_content_check) {
ferr("Overriding.\n");
blockmax = parity_allocated_size(state);
} else {
exit(EXIT_FAILURE);
}
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
2013-08-17 16:17:53 +02:00
if (state->opt.verbose) {
fout("%8u files\n", count_file);
fout("%8u hardlinks\n", count_hardlink);
fout("%8u symlinks\n", count_symlink);
fout("%8u empty dirs\n", count_dir);
2013-08-17 16:17:53 +02:00
}
}
static void state_write_binary(struct snapraid_state* state, STREAM* f)
{
unsigned count_file;
unsigned count_hardlink;
unsigned count_symlink;
unsigned count_dir;
tommy_node* i;
block_off_t b;
block_off_t blockmax;
block_off_t begin;
time_t info_oldest;
int info_has_rehash;
int mapping_idx;
unsigned l;
uint32_t crc;
2013-08-17 16:17:53 +02:00
count_file = 0;
count_hardlink = 0;
count_symlink = 0;
count_dir = 0;
/* blocks of all array */
blockmax = parity_allocated_size(state);
2013-08-17 16:17:53 +02:00
/* clear the info for unused blocks */
/* and get some other info */
info_oldest = 0; /* oldest time in info */
info_has_rehash = 0; /* if there is a rehash info */
2014-09-27 00:32:39 +02:00
for (b = 0; b < blockmax; ++b) {
2013-08-17 16:17:53 +02:00
/* if the position is used */
if (position_is_required(state, b)) {
2013-08-17 16:17:53 +02:00
snapraid_info info = info_get(&state->infoarr, b);
/* only if there is some info to store */
if (info) {
time_t time = info_get_time(info);
if (!info_oldest || time < info_oldest)
info_oldest = time;
if (info_get_rehash(info))
info_has_rehash = 1;
}
} else {
/* clear any previous info */
info_set(&state->infoarr, b, 0);
/* and clear any deleted blocks */
position_clear_deleted(state, b);
2013-08-17 16:17:53 +02:00
}
}
/* write header */
swrite("SNAPCNT2\n\3\0\0", 12, f);
/* write block size and block max */
2013-08-17 16:17:53 +02:00
sputc('z', f);
sputb32(state->block_size, f);
sputc('x', f);
sputb32(blockmax, f);
2013-08-17 16:17:53 +02:00
if (serror(f)) {
/* LCOV_EXCL_START */
ferr("Error writing the content file '%s'. %s.\n", serrorfile(f), strerror(errno));
2013-08-17 16:17:53 +02:00
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
sputc('c', f);
if (state->hash == HASH_MURMUR3) {
sputc('u', f);
} else if (state->hash == HASH_SPOOKY2) {
sputc('k', f);
} else {
/* LCOV_EXCL_START */
ferr("Unexpected hash when writing the content file '%s'.\n", serrorfile(f));
2013-08-17 16:17:53 +02:00
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
swrite(state->hashseed, HASH_SIZE, f);
if (serror(f)) {
/* LCOV_EXCL_START */
ferr("Error writing the content file '%s'. %s.\n", serrorfile(f), strerror(errno));
2013-08-17 16:17:53 +02:00
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
/* previous hash only present */
if (state->prevhash != HASH_UNDEFINED) {
/* if at least one rehash tag found, we have to save the previous hash */
if (info_has_rehash) {
sputc('C', f);
if (state->prevhash == HASH_MURMUR3) {
sputc('u', f);
} else if (state->prevhash == HASH_SPOOKY2) {
2013-08-17 16:17:53 +02:00
sputc('k', f);
} else {
/* LCOV_EXCL_START */
ferr("Unexpected prevhash when writing the content file '%s'.\n", serrorfile(f));
2013-08-17 16:17:53 +02:00
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
swrite(state->prevhashseed, HASH_SIZE, f);
if (serror(f)) {
/* LCOV_EXCL_START */
ferr("Error writing the content file '%s'. %s.\n", serrorfile(f), strerror(errno));
2013-08-17 16:17:53 +02:00
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
}
}
/* for each map */
mapping_idx = 0;
2014-09-27 00:32:39 +02:00
for (i = state->maplist; i != 0; i = i->next) {
2013-08-17 16:17:53 +02:00
struct snapraid_map* map = i->data;
struct snapraid_disk* disk;
/* find the disk for this mapping */
disk = find_disk(state, map->name);
if (!disk) {
/* LCOV_EXCL_START */
ferr("Internal inconsistency for unmapped disk '%s'\n", map->name);
2013-08-17 16:17:53 +02:00
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
/* save the mapping only for not empty disks */
if (!disk_is_empty(disk, blockmax)) {
sputc('M', f);
sputbs(map->name, f);
sputb32(map->position, f);
sputb32(map->total_blocks, f);
sputb32(map->free_blocks, f);
sputbs(map->uuid, f);
if (serror(f)) {
/* LCOV_EXCL_START */
ferr("Error writing the content file '%s'. %s.\n", serrorfile(f), strerror(errno));
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
/* assign the mapping index used to identify disks */
2014-11-09 10:51:48 +01:00
disk->mapping_idx = mapping_idx;
++mapping_idx;
} else {
/* mark the disk as without mapping */
2014-11-09 10:51:48 +01:00
disk->mapping_idx = -1;
}
2013-08-17 16:17:53 +02:00
}
/* for each parity */
for (l = 0; l < state->level; ++l) {
sputc('P', f);
sputb32(l, f);
sputb32(state->parity[l].total_blocks, f);
sputb32(state->parity[l].free_blocks, f);
sputbs(state->parity[l].uuid, f);
if (serror(f)) {
/* LCOV_EXCL_START */
ferr("Error writing the content file '%s'. %s.\n", serrorfile(f), strerror(errno));
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
}
2013-08-17 16:17:53 +02:00
/* for each disk */
2014-09-27 00:32:39 +02:00
for (i = state->disklist; i != 0; i = i->next) {
2013-08-17 16:17:53 +02:00
tommy_node* j;
struct snapraid_disk* disk = i->data;
/* if the disk is not mapped, skip it */
2014-11-09 10:51:48 +01:00
if (disk->mapping_idx < 0)
continue;
2013-08-17 16:17:53 +02:00
/* for each file */
2014-09-27 00:32:39 +02:00
for (j = disk->filelist; j != 0; j = j->next) {
2013-08-17 16:17:53 +02:00
struct snapraid_file* file = j->data;
struct snapraid_block* blockvec = file->blockvec;
uint64_t size;
uint64_t mtime_sec;
int32_t mtime_nsec;
uint64_t inode;
size = file->size;
mtime_sec = file->mtime_sec;
mtime_nsec = file->mtime_nsec;
inode = file->inode;
sputc('f', f);
2014-11-09 10:51:48 +01:00
sputb32(disk->mapping_idx, f);
2013-08-17 16:17:53 +02:00
sputb64(size, f);
sputb64(mtime_sec, f);
/* encode STAT_NSEC_INVALID as 0 */
if (mtime_nsec == STAT_NSEC_INVALID)
sputb32(0, f);
else
sputb32(mtime_nsec + 1, f);
2013-08-17 16:17:53 +02:00
sputb64(inode, f);
sputbs(file->sub, f);
if (serror(f)) {
/* LCOV_EXCL_START */
ferr("Error writing the content file '%s'. %s.\n", serrorfile(f), strerror(errno));
2013-08-17 16:17:53 +02:00
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
/* for all the blocks of the file */
begin = 0;
while (begin < file->blockmax) {
unsigned block_state = block_state_get(blockvec + begin);
uint32_t v_pos = blockvec[begin].parity_pos;
uint32_t v_count;
2014-09-27 00:32:39 +02:00
2013-08-17 16:17:53 +02:00
block_off_t end;
/* find the end of run of blocks */
end = begin + 1;
while (end < file->blockmax
&& block_state == block_state_get(blockvec + end)
2014-09-27 00:32:39 +02:00
&& blockvec[end - 1].parity_pos + 1 == blockvec[end].parity_pos
) {
2013-08-17 16:17:53 +02:00
++end;
}
switch (block_state) {
case BLOCK_STATE_BLK :
sputc('b', f);
break;
case BLOCK_STATE_CHG :
sputc('g', f);
break;
case BLOCK_STATE_REP :
sputc('p', f);
break;
2014-09-27 00:32:39 +02:00
default :
/* LCOV_EXCL_START */
ferr("Internal inconsistency in state for block %u state %u\n", v_pos, block_state);
2013-08-17 16:17:53 +02:00
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
sputb32(v_pos, f);
v_count = end - begin;
sputb32(v_count, f);
/* write hashes */
2014-09-27 00:32:39 +02:00
for (b = begin; b < end; ++b) {
struct snapraid_block* block = blockvec + b;
/* consistency check */
if (block != disk_block_get(disk, block->parity_pos)) {
/* LCOV_EXCL_START */
ferr("Internal inconsistency in mismatch for block %u state %u in file '%s'\n", block->parity_pos, block_state, file->sub);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
swrite(block->hash, HASH_SIZE, f);
2013-08-17 16:17:53 +02:00
}
if (serror(f)) {
/* LCOV_EXCL_START */
ferr("Error writing the content file '%s'. %s.\n", serrorfile(f), strerror(errno));
2013-08-17 16:17:53 +02:00
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
/* next begin position */
begin = end;
}
++count_file;
}
/* for each link */
2014-09-27 00:32:39 +02:00
for (j = disk->linklist; j != 0; j = j->next) {
2013-08-17 16:17:53 +02:00
struct snapraid_link* link = j->data;
switch (link_flag_get(link, FILE_IS_LINK_MASK)) {
case FILE_IS_HARDLINK :
sputc('a', f);
++count_hardlink;
break;
case FILE_IS_SYMLINK :
sputc('s', f);
++count_symlink;
break;
}
2014-11-09 10:51:48 +01:00
sputb32(disk->mapping_idx, f);
2013-08-17 16:17:53 +02:00
sputbs(link->sub, f);
sputbs(link->linkto, f);
if (serror(f)) {
/* LCOV_EXCL_START */
ferr("Error writing the content file '%s'. %s.\n", serrorfile(f), strerror(errno));
2013-08-17 16:17:53 +02:00
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
}
/* for each dir */
2014-09-27 00:32:39 +02:00
for (j = disk->dirlist; j != 0; j = j->next) {
2013-08-17 16:17:53 +02:00
struct snapraid_dir* dir = j->data;
sputc('r', f);
2014-11-09 10:51:48 +01:00
sputb32(disk->mapping_idx, f);
2013-08-17 16:17:53 +02:00
sputbs(dir->sub, f);
if (serror(f)) {
/* LCOV_EXCL_START */
ferr("Error writing the content file '%s'. %s.\n", serrorfile(f), strerror(errno));
2013-08-17 16:17:53 +02:00
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
++count_dir;
}
/* deleted blocks of the disk */
sputc('h', f);
2014-11-09 10:51:48 +01:00
sputb32(disk->mapping_idx, f);
if (serror(f)) {
/* LCOV_EXCL_START */
ferr("Error writing the content file '%s'. %s.\n", serrorfile(f), strerror(errno));
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
begin = 0;
while (begin < blockmax) {
int is_deleted;
block_off_t end;
is_deleted = is_block_deleted(disk, begin);
2013-08-17 16:17:53 +02:00
/* find the end of run of blocks */
end = begin + 1;
while (end < blockmax
2014-09-27 00:32:39 +02:00
&& is_deleted == is_block_deleted(disk, end)
) {
++end;
}
2013-08-17 16:17:53 +02:00
sputb32(end - begin, f);
if (is_deleted) {
/* write the run of deleted blocks with hash */
sputc('o', f);
/* write all the hash */
while (begin < end) {
struct snapraid_block* block = disk_block_get(disk, begin);
/* consistency check */
if (block->parity_pos != begin) {
/* LCOV_EXCL_START */
ferr("Internal inconsistency in delete position %u/%u!\n", block->parity_pos, begin);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
swrite(block->hash, HASH_SIZE, f);
++begin;
}
} else {
/* write the run of blocks without hash */
/* they can be either used or empty blocks */
sputc('O', f);
/* next begin position */
begin = end;
}
2013-08-17 16:17:53 +02:00
if (serror(f)) {
/* LCOV_EXCL_START */
ferr("Error writing the content file '%s'. %s.\n", serrorfile(f), strerror(errno));
2013-08-17 16:17:53 +02:00
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
}
}
2013-08-17 16:17:53 +02:00
/* write the info for each block */
sputc('i', f);
sputb32(info_oldest, f);
begin = 0;
while (begin < blockmax) {
snapraid_info info;
block_off_t end;
time_t t;
unsigned flag;
2013-08-17 16:17:53 +02:00
info = info_get(&state->infoarr, begin);
2013-08-17 16:17:53 +02:00
/* find the end of run of blocks */
end = begin + 1;
while (end < blockmax
2014-09-27 00:32:39 +02:00
&& info == info_get(&state->infoarr, end)
) {
++end;
}
2013-08-17 16:17:53 +02:00
sputb32(end - begin, f);
2013-08-17 16:17:53 +02:00
/* if there is info */
if (info) {
/* other flags */
flag = 1; /* info is present */
if (info_get_bad(info))
flag |= 2;
if (info_get_rehash(info))
flag |= 4;
sputb32(flag, f);
2013-08-17 16:17:53 +02:00
t = info_get_time(info) - info_oldest;
sputb32(t, f);
} else {
/* write a special 0 flag to mark missing info */
sputb32(0, f);
}
2013-08-17 16:17:53 +02:00
if (serror(f)) {
/* LCOV_EXCL_START */
ferr("Error writing the content file '%s'. %s.\n", serrorfile(f), strerror(errno));
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
/* next begin position */
begin = end;
2013-08-17 16:17:53 +02:00
}
sputc('N', f);
/* flush data written to the disk */
if (sflush(f)) {
/* LCOV_EXCL_START */
ferr("Error writing the content file '%s' (in flush before crc). %s.\n", serrorfile(f), strerror(errno));
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
/* get the file crc */
crc = scrc(f);
/* compare the crc of the data written to file */
/* with the one of the data written to the stream */
if (crc != scrc_stream(f)) {
/* LCOV_EXCL_START */
ferr("CRC mismatch writing the content stream.\n");
ferr("DANGER! Your RAM memory is broken! DO NOT PROCEED UNTIL FIXED!\n");
ferr("Try running a memory test like http://www.memtest86.com/\n");
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
sputble32(crc, f);
2013-08-17 16:17:53 +02:00
if (serror(f)) {
/* LCOV_EXCL_START */
ferr("Error writing the content file '%s'. %s.\n", serrorfile(f), strerror(errno));
2013-08-17 16:17:53 +02:00
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
if (state->opt.verbose) {
fout("%8u files\n", count_file);
fout("%8u hardlinks\n", count_hardlink);
fout("%8u symlinks\n", count_symlink);
fout("%8u empty dirs\n", count_dir);
2013-08-17 16:17:53 +02:00
}
}
void state_read(struct snapraid_state* state)
{
STREAM* f;
char path[PATH_MAX];
struct stat st;
tommy_node* node;
int ret;
int c;
/* iterate over all the available content files and load the first one present */
f = 0;
node = tommy_list_head(&state->contentlist);
while (node) {
struct snapraid_content* content = node->data;
pathcpy(path, sizeof(path), content->content);
2013-11-26 18:43:12 +01:00
if (!state->no_conf) {
ftag("content:%s\n", path);
fflush_log();
2013-11-26 18:43:12 +01:00
}
fout("Loading state from %s...\n", path);
2013-08-17 16:17:53 +02:00
f = sopen_read(path);
if (f != 0) {
/* if openend stop the search */
break;
} else {
/* if it's real error of an existing file, abort */
if (errno != ENOENT) {
/* LCOV_EXCL_START */
ferr("Error opening the content file '%s'. %s.\n", path, strerror(errno));
2013-08-17 16:17:53 +02:00
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
/* otherwise continue */
if (node->next) {
ferr("WARNING! Content file '%s' not found, trying with another copy...\n", path);
/* ensure to rewrite all the content files */
state->need_write = 1;
2013-08-17 16:17:53 +02:00
}
}
/* next content file */
2013-08-17 16:17:53 +02:00
node = node->next;
}
/* if not found, assume empty */
if (!f) {
ferr("No content file found. Assuming empty.\n");
2013-11-26 18:43:12 +01:00
2013-08-17 16:17:53 +02:00
/* create the initial mapping */
state_map(state);
return;
}
/* get the stat of the content file */
2013-08-17 16:17:53 +02:00
ret = fstat(shandle(f), &st);
if (ret != 0) {
/* LCOV_EXCL_START */
ferr("Error stating the content file '%s'. %s.\n", path, strerror(errno));
2013-08-17 16:17:53 +02:00
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
/* go further to check other content files */
while (node) {
char other_path[PATH_MAX];
struct stat other_st;
struct snapraid_content* content = node->data;
pathcpy(other_path, sizeof(other_path), content->content);
ret = stat(other_path, &other_st);
if (ret != 0) {
/* allow missing content files, but not any other kind of error */
if (errno != ENOENT) {
/* LCOV_EXCL_START */
ferr("Error stating the content file '%s'. %s.\n", other_path, strerror(errno));
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
/* ensure to rewrite all the content files */
state->need_write = 1;
} else {
/* if the size is different */
if (other_st.st_size != st.st_size) {
ferr("WARNING! Content files '%s' and '%s' have a different size!\n", path, other_path);
ferr("Likely one of the two is broken!\n");
2014-09-27 00:32:39 +02:00
/* ensure to rewrite all the content files */
state->need_write = 1;
}
}
/* next content file */
node = node->next;
}
2013-08-17 16:17:53 +02:00
/* start with a undefined default. */
/* it's for compatibility with version 1.0 where MD5 was implicit. */
state->hash = HASH_UNDEFINED;
/* start with a zero seed, it was the default in old versions */
memset(state->hashseed, 0, HASH_SIZE);
/* previous hash, start with an undefined value */
state->prevhash = HASH_UNDEFINED;
/* intentionally not set the prevhashseed, if used valgrind will warn about it */
2013-08-17 16:17:53 +02:00
/* get the first char to detect the file type */
c = sgetc(f);
sungetc(c, f);
/* guess the file type from the first char */
2013-08-17 16:17:53 +02:00
if (c == 'S') {
state_read_binary(state, path, f);
} else {
state_read_text(state, path, f, st.st_mtime);
/* force a rewrite to convert to binary */
state->need_write = 1;
2013-08-17 16:17:53 +02:00
}
sclose(f);
if (state->hash == HASH_UNDEFINED) {
/* LCOV_EXCL_START */
ferr("The checksum to use is not specified.\n");
ferr("This happens because you are likely upgrading from SnapRAID 1.0.\n");
ferr("To use a new SnapRAID you must restart from scratch,\n");
ferr("deleting all the content and parity files.\n");
2013-08-17 16:17:53 +02:00
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
/* update the mapping */
state_map(state);
state_content_check(state, path);
/* mark that we read the content file, and it passed all the checks */
state->checked_read = 1;
2013-08-17 16:17:53 +02:00
}
void state_write(struct snapraid_state* state)
{
STREAM* f;
unsigned count_content;
tommy_node* i;
unsigned k;
/* count the content files */
count_content = 0;
i = tommy_list_head(&state->contentlist);
while (i) {
struct snapraid_content* content = i->data;
fout("Saving state to %s...\n", content->content);
2013-08-17 16:17:53 +02:00
++count_content;
i = i->next;
}
/* open all the content files */
f = sopen_multi_write(count_content);
if (!f) {
/* LCOV_EXCL_START */
ferr("Error opening the content files.\n");
2013-08-17 16:17:53 +02:00
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
k = 0;
i = tommy_list_head(&state->contentlist);
while (i) {
struct snapraid_content* content = i->data;
char tmp[PATH_MAX];
pathprint(tmp, sizeof(tmp), "%s.tmp", content->content);
if (sopen_multi_file(f, k, tmp) != 0) {
/* LCOV_EXCL_START */
ferr("Error opening the content file '%s'. %s.\n", tmp, strerror(errno));
2013-08-17 16:17:53 +02:00
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
++k;
i = i->next;
}
if (state->opt.force_content_text)
state_write_text(state, f);
else
state_write_binary(state, f);
/* Use the sequence fflush() -> fsync() -> fclose() -> rename() to ensure */
/* than even in a system crash event we have one valid copy of the file. */
if (sflush(f) != 0) {
/* LCOV_EXCL_START */
ferr("Error writing the content file '%s', in flush(). %s.\n", serrorfile(f), strerror(errno));
2013-08-17 16:17:53 +02:00
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
#if HAVE_FSYNC
if (ssync(f) != 0) {
/* LCOV_EXCL_START */
ferr("Error writing the content file '%s' in sync(). %s.\n", serrorfile(f), strerror(errno));
2013-08-17 16:17:53 +02:00
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
#endif
if (sclose(f) != 0) {
/* LCOV_EXCL_START */
ferr("Error closing the content files in close(). %s.\n", strerror(errno));
2013-08-17 16:17:53 +02:00
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
i = tommy_list_head(&state->contentlist);
while (i) {
struct snapraid_content* content = i->data;
char tmp[PATH_MAX];
/* now renames the just written copy with the correct name */
2013-08-17 16:17:53 +02:00
pathprint(tmp, sizeof(tmp), "%s.tmp", content->content);
if (rename(tmp, content->content) != 0) {
/* LCOV_EXCL_START */
ferr("Error renaming the content file '%s' to '%s' in rename(). %s.\n", tmp, content->content, strerror(errno));
2013-08-17 16:17:53 +02:00
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
2013-08-17 16:17:53 +02:00
i = i->next;
}
state->need_write = 0; /* no write needed anymore */
state->checked_read = 0; /* what we wrote is not checked in read */
}
void state_filter(struct snapraid_state* state, tommy_list* filterlist_file, tommy_list* filterlist_disk, int filter_missing, int filter_error)
{
tommy_node* i;
2011-04-26 18:51:42 +02:00
/* if no filter, include all */
if (!filter_missing && !filter_error && tommy_list_empty(filterlist_file) && tommy_list_empty(filterlist_disk))
return;
fout("Filtering...\n");
if (state->opt.verbose) {
tommy_node* k;
2014-09-27 00:32:39 +02:00
for (k = tommy_list_head(filterlist_disk); k != 0; k = k->next) {
struct snapraid_filter* filter = k->data;
fout("\t%s%s\n", filter->pattern, filter->is_disk ? "//" : "");
}
2014-09-27 00:32:39 +02:00
for (k = tommy_list_head(filterlist_file); k != 0; k = k->next) {
struct snapraid_filter* filter = k->data;
fout("\t%s%s\n", filter->pattern, filter->is_dir ? "/" : "");
}
if (filter_missing)
fout("\t<missing>\n");
if (filter_error)
fout("\t<error>\n");
}
/* for each disk */
2014-09-27 00:32:39 +02:00
for (i = state->disklist; i != 0; i = i->next) {
tommy_node* j;
struct snapraid_disk* disk = i->data;
/* if we filter for presence, we have to access the disk, so better to print something */
if (filter_missing)
fout("Scanning disk %s...\n", disk->name);
/* for each file */
2014-09-27 00:32:39 +02:00
for (j = tommy_list_head(&disk->filelist); j != 0; j = j->next) {
struct snapraid_file* file = j->data;
if (filter_path(filterlist_disk, 0, disk->name, file->sub) != 0
|| filter_path(filterlist_file, 0, disk->name, file->sub) != 0
|| filter_existence(filter_missing, disk->dir, file->sub) != 0
|| filter_correctness(filter_error, &state->infoarr, file) != 0
) {
2011-04-28 23:09:19 +02:00
file_flag_set(file, FILE_IS_EXCLUDED);
}
}
/* for each link */
2014-09-27 00:32:39 +02:00
for (j = tommy_list_head(&disk->linklist); j != 0; j = j->next) {
struct snapraid_link* link = j->data;
if (filter_path(filterlist_disk, 0, disk->name, link->sub) != 0
|| filter_path(filterlist_file, 0, disk->name, link->sub) != 0
|| filter_existence(filter_missing, disk->dir, link->sub) != 0
) {
link_flag_set(link, FILE_IS_EXCLUDED);
}
}
/* for each dir */
2014-09-27 00:32:39 +02:00
for (j = tommy_list_head(&disk->dirlist); j != 0; j = j->next) {
struct snapraid_dir* dir = j->data;
if (filter_dir(filterlist_disk, 0, disk->name, dir->sub) != 0
|| filter_dir(filterlist_file, 0, disk->name, dir->sub) != 0
|| filter_existence(filter_missing, disk->dir, dir->sub) != 0
) {
dir_flag_set(dir, FILE_IS_EXCLUDED);
}
}
}
}
int state_progress_begin(struct snapraid_state* state, block_off_t blockstart, block_off_t blockmax, block_off_t countmax)
2011-06-20 20:35:04 +02:00
{
time_t now;
if (state->opt.gui) {
ftag("run:begin:%u:%u:%u\n", blockstart, blockmax, countmax);
fflush_log();
}
2011-06-20 20:35:04 +02:00
now = time(0);
2011-06-20 20:35:04 +02:00
state->progress_whole_start = now;
state->progress_tick = 0;
state->progress_ptr = 0;
state->progress_wasted = 0;
/* stop if requested */
if (global_interrupt) {
2014-03-20 22:10:19 +01:00
/* LCOV_EXCL_START */
if (!state->opt.gui) {
fout("Not starting for interruption\n");
}
ftag("sigint:0: SIGINT received\n");
fflush_log();
return 0;
2014-03-20 22:10:19 +01:00
/* LCOV_EXCL_STOP */
}
return 1;
2011-06-20 20:35:04 +02:00
}
void state_progress_end(struct snapraid_state* state, block_off_t countpos, block_off_t countmax, data_off_t countsize)
{
if (state->opt.gui) {
ftag("run:end\n");
fflush_log();
} else if (countmax == 0) {
fout("Nothing to do\n");
} else if (!state->opt.quiet) {
time_t now;
time_t elapsed;
2014-09-27 00:32:39 +02:00
unsigned countsize_MiB = (countsize + 1024 * 1024 - 1) / (1024 * 1024);
2011-06-20 20:35:04 +02:00
now = time(0);
elapsed = now - state->progress_whole_start - state->progress_wasted;
printf("%u%% completed, %u MiB processed", countpos * 100 / countmax, countsize_MiB);
if (elapsed >= 60)
printf(" in %u:%02u", (unsigned)(elapsed / 3600), (unsigned)((elapsed % 3600) / 60));
printf("\n");
2011-06-20 20:35:04 +02:00
}
}
void state_progress_stop(struct snapraid_state* state)
{
time_t now;
now = time(0);
if (!state->opt.gui && !state->opt.quiet)
printf("\n");
2014-09-27 00:32:39 +02:00
state->progress_interruption = now;
}
void state_progress_restart(struct snapraid_state* state)
{
time_t now;
now = time(0);
/* reset the progress counter */
state->progress_tick = 0;
state->progress_ptr = 0;
if (now >= state->progress_interruption) /* avoid degenerated cases when the clock is manually adjusted */
state->progress_wasted += now - state->progress_interruption;
}
#define PROGRESS_CLEAR " "
2011-06-20 20:35:04 +02:00
int state_progress(struct snapraid_state* state, block_off_t blockpos, block_off_t countpos, block_off_t countmax, data_off_t countsize)
{
time_t now;
int pred;
now = time(0);
/* previous position */
pred = state->progress_ptr + PROGRESS_MAX - 1;
if (pred >= PROGRESS_MAX)
pred -= PROGRESS_MAX;
/* if the previous measure is different */
if (state->progress_tick == 0
|| state->progress_time[pred] != now
) {
uint64_t tick_total;
uint64_t tick_cpu;
time_t elapsed;
unsigned out_perc = 0;
unsigned out_speed = 0;
unsigned out_cpu = 0;
unsigned out_eta = 0;
tick_total = state->tick_cpu + state->tick_io;
tick_cpu = state->tick_cpu;
elapsed = now - state->progress_whole_start - state->progress_wasted;
/* completion percentage */
if (countmax)
out_perc = countpos * 100 / countmax;
/* if we have at least 5 measures */
if (state->progress_tick >= 5
2014-11-12 20:47:07 +01:00
/* or if we are running in test mode, with at least one measure */
|| (state->opt.force_progress && state->progress_tick >= 1)
) {
int oldest;
int past;
time_t delta_time;
block_off_t delta_pos;
data_off_t delta_size;
uint64_t delta_tick_total;
uint64_t delta_tick_cpu;
/* number of past measures */
past = state->progress_tick;
/* drop the oldest ones, to promptly */
/* skip the startup phase */
past -= past / 5;
/* check how much we can go in the past */
if (past >= PROGRESS_MAX) {
/* the vector is filled, so we are already in position */
/* to get the possible oldest one */
oldest = state->progress_ptr;
} else {
/* go backward the number of positions selected */
oldest = state->progress_ptr + PROGRESS_MAX - past;
if (oldest >= PROGRESS_MAX)
oldest -= PROGRESS_MAX;
}
delta_time = now - state->progress_time[oldest];
delta_pos = countpos - state->progress_pos[oldest];
delta_size = countsize - state->progress_size[oldest];
delta_tick_total = tick_total - state->progress_tick_total[oldest];
delta_tick_cpu = tick_cpu - state->progress_tick_cpu[oldest];
/* estimate the speed in MiB/s */
if (delta_time != 0)
out_speed = (unsigned)(delta_size / (1024 * 1024) / delta_time);
/* estimate the cpu usage percentage */
if (delta_tick_total != 0)
out_cpu = (unsigned)(delta_tick_cpu * 100U / delta_tick_total);
/* estimate the remaining time in minutes */
if (delta_pos != 0)
out_eta = (countmax - countpos) * delta_time / (60 * delta_pos);
}
if (state->opt.gui) {
ftag("run:pos:%u:%u:%" PRIu64 ":%u:%u:%u:%u:%" PRIu64 "\n", blockpos, countpos, countsize, out_perc, out_eta, out_speed, out_cpu, (uint64_t)elapsed);
fflush_log();
} else if (!state->opt.quiet) {
/* note that here we don't use fout() becase we don't want this in the log */
printf("%u%%, %u MiB", out_perc, (unsigned)(countsize / (1024 * 1024)));
if (out_speed)
printf(", %u MiB/s", out_speed);
if (out_cpu)
printf(", CPU %u%%", out_cpu);
if (out_eta)
printf(", %u:%02u ETA", out_eta / 60, out_eta % 60);
printf("%s\r", PROGRESS_CLEAR);
2011-06-20 20:35:04 +02:00
fflush(stdout);
}
/* store the new measure */
state->progress_time[state->progress_ptr] = now;
state->progress_pos[state->progress_ptr] = countpos;
state->progress_size[state->progress_ptr] = countsize;
state->progress_tick_cpu[state->progress_ptr] = tick_cpu;
state->progress_tick_total[state->progress_ptr] = tick_total;
/* next position */
++state->progress_ptr;
if (state->progress_ptr >= PROGRESS_MAX)
state->progress_ptr -= PROGRESS_MAX;
/* one more measure */
++state->progress_tick;
}
/* stop if requested */
if (global_interrupt) {
2014-03-20 22:10:19 +01:00
/* LCOV_EXCL_START */
if (!state->opt.gui) {
ferr("\n");
2015-02-03 18:53:23 +01:00
ferr("Stopping for interruption at block %u\n", blockpos);
2011-06-20 20:35:04 +02:00
}
ftag("sigint:%u: SIGINT received\n", blockpos);
fflush_log();
return 1;
2014-03-20 22:10:19 +01:00
/* LCOV_EXCL_STOP */
}
return 0;
}
void state_usage_print(struct snapraid_state* state)
{
uint64_t tick_total;
uint64_t tick_max;
tommy_node* i;
unsigned l;
tick_max = 0;
tick_total = 0;
2014-09-27 00:32:39 +02:00
for (i = state->disklist; i != 0; i = i->next) {
struct snapraid_disk* disk = i->data;
tick_total += disk->tick;
if (disk->tick > tick_max)
tick_max = disk->tick;
}
2014-09-27 00:32:39 +02:00
for (l = 0; l < state->level; ++l) {
tick_total += state->parity[l].tick;
if (state->parity[l].tick > tick_max)
tick_max = state->parity[l].tick;
}
if (!tick_total)
return;
printf("Time for disk:");
2014-09-27 00:32:39 +02:00
for (i = state->disklist; i != 0; i = i->next) {
struct snapraid_disk* disk = i->data;
if (disk->tick == tick_max)
printf(" %s:", disk->name);
else
printf(" ");
2014-09-27 00:32:39 +02:00
printf("%" PRIu64 "%%", disk->tick * 100U / tick_total);
}
printf("\n");
printf("Time for parity:");
2014-09-27 00:32:39 +02:00
for (l = 0; l < state->level; ++l) {
if (state->parity[l].tick == tick_max)
printf(" %s:", lev_config_name(l));
else
printf(" ");
printf("%" PRIu64 "%%", state->parity[l].tick * 100U / tick_total);
}
printf("\n");
}
void generate_configuration(const char* path)
{
struct snapraid_state state;
struct snapraid_content* content;
unsigned i;
tommy_node* j;
state_init(&state);
/* mark that we are without a configuration file */
state.no_conf = 1;
/* create the dummy content entry */
content = content_alloc(path, -1);
/* adds the content entry */
tommy_list_insert_tail(&state.contentlist, &content->node, content);
/* read the content file */
state_read(&state);
/* output a dummy configuration file */
fout("# Configuration file generated from %s\n", path);
fout("\n");
fout("# Use this blocksize\n");
fout("blocksize %u\n", state.block_size / 1024);
fout("\n");
2014-11-09 19:37:25 +01:00
for (i = 0; i < state.level; ++i) {
fout("# Set the correct path for the %s file\n", lev_name(i));
2014-11-09 19:37:25 +01:00
if (state.parity[i].uuid[0])
fout("# The file was in the disk with id '%s'\n", state.parity[i].uuid);
fout("%s ENTER_HERE_THE_PARITY_FILE\n", lev_config_name(i));
fout("\n");
}
fout("# Add any other content file\n");
fout("content %s\n", path);
fout("\n");
2014-09-27 00:32:39 +02:00
for (j = state.maplist; j; j = j->next) {
struct snapraid_map* map = j->data;
struct snapraid_disk* disk;
fout("# Set the correct dir for disk '%s'\n", map->name);
2014-11-09 19:37:25 +01:00
if (map->uuid[0])
fout("# Disk '%s' is the one with id '%s'\n", map->name, map->uuid);
disk = find_disk(&state, map->name);
if (disk && disk->filelist) {
struct snapraid_file* file = disk->filelist->data;
if (file) {
fout("# and containing: %s\n", file->sub);
}
}
fout("disk %s ENTER_HERE_THE_DIR\n", map->name);
fout("\n");
}
state_done(&state);
}