mirror of
https://github.com/amadvance/snapraid.git
synced 2026-10-26 10:51:54 -04:00
3413 lines
88 KiB
C
3413 lines
88 KiB
C
/*
|
|
* 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 "state.h"
|
|
#include "util.h"
|
|
#include "parity.h"
|
|
#include "cpu.h"
|
|
|
|
void state_init(struct snapraid_state* state)
|
|
{
|
|
state->filter_hidden = 0;
|
|
state->autosave = 0;
|
|
state->need_write = 0;
|
|
state->block_size = 256 * 1024; /* default 256 KiB */
|
|
state->parity[0] = 0;
|
|
state->parity_device = 0;
|
|
state->qarity[0] = 0;
|
|
state->qarity_device = 0;
|
|
state->pool[0] = 0;
|
|
state->pool_device = 0;
|
|
state->lockfile[0] = 0;
|
|
state->level = 1; /* default is the lowest protection */
|
|
state->loaded_blockmax = 0;
|
|
|
|
tommy_list_init(&state->disklist);
|
|
tommy_list_init(&state->maplist);
|
|
tommy_list_init(&state->contentlist);
|
|
tommy_list_init(&state->filterlist);
|
|
tommy_list_init(&state->importlist);
|
|
tommy_hashdyn_init(&state->importset);
|
|
tommy_hashdyn_init(&state->previmportset);
|
|
tommy_arrayof_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);
|
|
tommy_list_foreach(&state->filterlist, (tommy_foreach_func*)filter_free);
|
|
tommy_list_foreach(&state->importlist, (tommy_foreach_func*)import_file_free);
|
|
tommy_hashdyn_done(&state->importset);
|
|
tommy_hashdyn_done(&state->previmportset);
|
|
tommy_arrayof_done(&state->infoarr);
|
|
}
|
|
|
|
/**
|
|
* Checks the configuration.
|
|
*/
|
|
static void state_config_check(struct snapraid_state* state, const char* path)
|
|
{
|
|
tommy_node* i;
|
|
|
|
if (state->parity[0] == 0) {
|
|
fprintf(stderr, "No 'parity' specification in '%s'\n", path);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
if (tommy_list_empty(&state->contentlist)) {
|
|
fprintf(stderr, "No 'content' specification in '%s'\n", path);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
/* checks for equal paths */
|
|
for(i=state->contentlist;i!=0;i=i->next) {
|
|
struct snapraid_content* content = i->data;
|
|
|
|
if (pathcmp(state->parity, content->content) == 0) {
|
|
fprintf(stderr, "Same path used for 'parity' and 'content' as '%s'\n", content->content);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
if (state->qarity[0] != 0 && pathcmp(state->qarity, content->content) == 0) {
|
|
fprintf(stderr, "Same path used for 'qarity' and 'content' as '%s'\n", content->content);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
}
|
|
|
|
/* check if all the data and parity disks are different */
|
|
if (!state->opt.skip_device) {
|
|
unsigned diskcount = 0;
|
|
|
|
for(i=state->disklist;i!=0;i=i->next) {
|
|
tommy_node* j;
|
|
struct snapraid_disk* disk = i->data;
|
|
|
|
#ifdef _WIN32
|
|
if (disk->device == 0) {
|
|
fprintf(stderr, "Disk '%s' has a zero serial number.\n", disk->dir);
|
|
fprintf(stderr, "This is not necessarely wrong, but for using SnapRAID\n");
|
|
fprintf(stderr, "it's better to change the serial number of the disk.\n");
|
|
fprintf(stderr, "Try using the 'VolumeID' tool by 'Mark Russinovich'.\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
#endif
|
|
|
|
if (!state->opt.force_device) {
|
|
for(j=i->next;j!=0;j=j->next) {
|
|
struct snapraid_disk* other = j->data;
|
|
if (disk->device == other->device) {
|
|
fprintf(stderr, "Disks '%s' and '%s' are on the same device.\n", disk->dir, other->dir);
|
|
#ifdef _WIN32
|
|
fprintf(stderr, "Both have the serial number '%"PRIx64"'.\n", disk->device);
|
|
#endif
|
|
/* in "fix" we allow to continue anyway */
|
|
if (strcmp(state->command, "fix") == 0) {
|
|
fprintf(stderr, "You can '%s' anyway, using 'snapraid --force-device %s'.\n", state->command, state->command);
|
|
}
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (disk->device == state->parity_device) {
|
|
fprintf(stderr, "Disk '%s' and parity '%s' are on the same device.\n", disk->dir, state->parity);
|
|
#ifdef _WIN32
|
|
fprintf(stderr, "Both have the serial number '%"PRIx64"'.\n", disk->device);
|
|
#endif
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
if (state->qarity[0] != 0 && disk->device == state->qarity_device) {
|
|
fprintf(stderr, "Disk '%s' and parity '%s' are on the same device.\n", disk->dir, state->qarity);
|
|
#ifdef _WIN32
|
|
fprintf(stderr, "Both have the serial number '%"PRIx64"'.\n", disk->device);
|
|
#endif
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
if (state->pool[0] != 0 && disk->device == state->pool_device) {
|
|
fprintf(stderr, "Disk '%s' and pool '%s' are on the same device.\n", disk->dir, state->pool);
|
|
#ifdef _WIN32
|
|
fprintf(stderr, "Both have the serial number '%"PRIx64"'.\n", disk->device);
|
|
#endif
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
++diskcount;
|
|
}
|
|
|
|
if (diskcount > 255) {
|
|
/* RAID6 P/Q parity works for up to 255 drives, no more */
|
|
fprintf(stderr, "Too many disks. No more than 255.\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
if (state->parity_device == 0) {
|
|
fprintf(stderr, "Disk '%s' has a zero serial number.\n", state->parity);
|
|
fprintf(stderr, "This is not necessarely wrong, but for using SnapRAID\n");
|
|
fprintf(stderr, "it's better to change the serial number of the disk.\n");
|
|
fprintf(stderr, "Try using the 'VolumeID' tool by 'Mark Russinovich'.\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
if (state->qarity[0] != 0 && state->qarity_device == 0) {
|
|
fprintf(stderr, "Disk '%s' has a zero serial number.\n", state->qarity);
|
|
fprintf(stderr, "This is not necessarely wrong, but for using SnapRAID\n");
|
|
fprintf(stderr, "it's better to change the serial number of the disk.\n");
|
|
fprintf(stderr, "Try using the 'VolumeID' tool by 'Mark Russinovich'.\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
if (state->pool[0] != 0 && state->pool_device == 0) {
|
|
fprintf(stderr, "Disk '%s' has a zero serial number.\n", state->pool);
|
|
fprintf(stderr, "This is not necessarely wrong, but for using SnapRAID\n");
|
|
fprintf(stderr, "it's better to change the serial number of the disk.\n");
|
|
fprintf(stderr, "Try using the 'VolumeID' tool by 'Mark Russinovich'.\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
#endif
|
|
|
|
if (state->qarity[0] != 0 && state->parity_device == state->qarity_device) {
|
|
fprintf(stderr, "Parity '%s' and '%s' are on the same device.\n", state->parity, state->qarity);
|
|
#ifdef _WIN32
|
|
fprintf(stderr, "Both have the serial number '%"PRIx64"'.\n", state->parity_device);
|
|
#endif
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
if (state->pool[0] != 0 && state->pool_device == state->parity_device) {
|
|
fprintf(stderr, "Pool '%s' and parity '%s' are on the same device.\n", state->pool, state->parity);
|
|
#ifdef _WIN32
|
|
fprintf(stderr, "Both have the serial number '%"PRIx64"'.\n", state->pool_device);
|
|
#endif
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
if (state->pool[0] != 0 && state->qarity[0] != 0 && state->pool_device == state->qarity_device) {
|
|
fprintf(stderr, "Pool '%s' and parity '%s' are on the same device.\n", state->pool, state->qarity);
|
|
#ifdef _WIN32
|
|
fprintf(stderr, "Both have the serial number '%"PRIx64"'.\n", state->pool_device);
|
|
#endif
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
}
|
|
|
|
/* count the content files */
|
|
if (!state->opt.skip_device) {
|
|
unsigned content_count;
|
|
|
|
content_count = 0;
|
|
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 */
|
|
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;
|
|
}
|
|
|
|
if (content_count < state->level+1) {
|
|
fprintf(stderr, "You must have at least %d 'content' files in different disks.\n", state->level+1);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
}
|
|
}
|
|
|
|
void state_config(struct snapraid_state* state, const char* path, const char* command, struct snapraid_option* opt)
|
|
{
|
|
STREAM* f;
|
|
unsigned line;
|
|
tommy_node* i;
|
|
|
|
/* copy the options */
|
|
state->opt = *opt;
|
|
|
|
/* if unsed, sort by physical order */
|
|
if (!state->opt.force_order)
|
|
state->opt.force_order = SORT_PHYSICAL;
|
|
|
|
state->command = command;
|
|
|
|
if (state->opt.gui) {
|
|
fprintf(stdlog, "version:%s\n", PACKAGE_VERSION);
|
|
fprintf(stdlog, "conf:%s\n", path);
|
|
fflush(stdlog);
|
|
}
|
|
|
|
f = sopen_read(path);
|
|
if (!f) {
|
|
if (errno == ENOENT) {
|
|
fprintf(stderr, "No configuration file found at '%s'\n", path);
|
|
} else if (errno == EACCES) {
|
|
fprintf(stderr, "You do not have rights to access the configuration file '%s'\n", path);
|
|
} else {
|
|
fprintf(stderr, "Error opening the configuration file '%s'. %s.\n", path, strerror(errno));
|
|
}
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
line = 1;
|
|
while (1) {
|
|
char tag[PATH_MAX];
|
|
char buffer[PATH_MAX];
|
|
int ret;
|
|
int c;
|
|
|
|
/* skip initial spaces */
|
|
sgetspace(f);
|
|
|
|
/* read the command */
|
|
ret = sgettok(f, tag, sizeof(tag));
|
|
if (ret < 0) {
|
|
fprintf(stderr, "Error reading the configuration file '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
/* skip spaces after the command */
|
|
sgetspace(f);
|
|
|
|
if (strcmp(tag, "block_size") == 0) {
|
|
ret = sgetu32(f, &state->block_size);
|
|
if (ret < 0) {
|
|
fprintf(stderr, "Invalid 'block_size' specification in '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
if (state->block_size < 1) {
|
|
fprintf(stderr, "Too small 'block_size' specification in '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
if (state->block_size > 16*1024) {
|
|
fprintf(stderr, "Too big 'block_size' specification in '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
/* check if it's a power of 2 */
|
|
if ((state->block_size & (state->block_size - 1)) != 0) {
|
|
fprintf(stderr, "Not power of 2 'block_size' specification in '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
state->block_size *= 1024;
|
|
} else if (strcmp(tag, "parity") == 0) {
|
|
char device[PATH_MAX];
|
|
char* slash;
|
|
struct stat st;
|
|
|
|
if (*state->parity) {
|
|
fprintf(stderr, "Multiple 'parity' specification in '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
ret = sgetlasttok(f, buffer, sizeof(buffer));
|
|
if (ret < 0) {
|
|
fprintf(stderr, "Invalid 'parity' specification in '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
if (!*buffer) {
|
|
fprintf(stderr, "Empty 'parity' specification in '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
pathimport(state->parity, sizeof(state->parity), buffer);
|
|
|
|
/* 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) {
|
|
fprintf(stderr, "Error accessing 'parity' dir '%s' specification in '%s' at line %u\n", device, path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
state->parity_device = st.st_dev;
|
|
|
|
/* set lock file */
|
|
pathcpy(state->lockfile, sizeof(state->lockfile), state->parity);
|
|
pathcat(state->lockfile, sizeof(state->lockfile), ".lock");
|
|
} else if (strcmp(tag, "q-parity") == 0) {
|
|
char device[PATH_MAX];
|
|
char* slash;
|
|
struct stat st;
|
|
|
|
if (*state->qarity) {
|
|
fprintf(stderr, "Multiple 'q-parity' specification in '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
ret = sgetlasttok(f, buffer, sizeof(buffer));
|
|
if (ret < 0) {
|
|
fprintf(stderr, "Invalid 'q-parity' specification in '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
if (!*buffer) {
|
|
fprintf(stderr, "Empty 'q-parity' specification in '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
pathimport(state->qarity, sizeof(state->qarity), buffer);
|
|
|
|
/* get the device of the directory containing the qparity file */
|
|
pathimport(device, sizeof(device), buffer);
|
|
slash = strrchr(device, '/');
|
|
if (slash)
|
|
*slash = 0;
|
|
else
|
|
pathcpy(device, sizeof(device), ".");
|
|
if (stat(device, &st) != 0) {
|
|
fprintf(stderr, "Error accessing 'qarity' dir '%s' specification in '%s' at line %u\n", device, path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
state->qarity_device = st.st_dev;
|
|
|
|
/* we have two level of parity */
|
|
state->level = 2;
|
|
} else if (strcmp(tag, "pool") == 0) {
|
|
struct stat st;
|
|
|
|
if (*state->pool) {
|
|
fprintf(stderr, "Multiple 'pool' specification in '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
ret = sgetlasttok(f, buffer, sizeof(buffer));
|
|
if (ret < 0) {
|
|
fprintf(stderr, "Invalid 'pool' specification in '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
if (!*buffer) {
|
|
fprintf(stderr, "Empty 'pool' specification in '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
pathimport(state->pool, sizeof(state->pool), buffer);
|
|
|
|
/* get the device of the directory containing the pool tree */
|
|
if (stat(buffer, &st) != 0) {
|
|
fprintf(stderr, "Error accessing 'pool' dir '%s' specification in '%s' at line %u\n", buffer, path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
state->pool_device = st.st_dev;
|
|
} else if (strcmp(tag, "content") == 0) {
|
|
struct snapraid_content* content;
|
|
char device[PATH_MAX];
|
|
char* slash;
|
|
struct stat st;
|
|
|
|
ret = sgetlasttok(f, buffer, sizeof(buffer));
|
|
if (ret < 0) {
|
|
fprintf(stderr, "Invalid 'content' specification in '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
if (pathcmp(buffer, "/dev/null") == 0 || pathcmp(buffer, "NUL") == 0) {
|
|
fprintf(stderr, "You cannot use the null device as 'content' specification in '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
if (!*buffer) {
|
|
fprintf(stderr, "Empty 'content' specification in '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
/* 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) {
|
|
fprintf(stderr, "Error accessing 'content' dir '%s' specification in '%s' at line %u\n", device, path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
content = content_alloc(buffer, st.st_dev);
|
|
|
|
tommy_list_insert_tail(&state->contentlist, &content->node, content);
|
|
} else if (strcmp(tag, "disk") == 0) {
|
|
char dir[PATH_MAX];
|
|
char device[PATH_MAX];
|
|
struct stat st;
|
|
struct snapraid_disk* disk;
|
|
|
|
ret = sgettok(f, buffer, sizeof(buffer));
|
|
if (ret < 0) {
|
|
fprintf(stderr, "Invalid 'disk' name specification in '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
if (!*buffer) {
|
|
fprintf(stderr, "Empty 'disk' name specification in '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
sgetspace(f);
|
|
|
|
ret = sgetlasttok(f, dir, sizeof(dir));
|
|
if (ret < 0) {
|
|
fprintf(stderr, "Invalid 'disk' dir specification in '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
if (!*dir) {
|
|
fprintf(stderr, "Empty 'disk' dir specification in '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
/* get the device of the dir */
|
|
pathimport(device, sizeof(device), dir);
|
|
if (stat(device, &st) != 0) {
|
|
fprintf(stderr, "Error accessing 'disk' '%s' specification in '%s' at line %u\n", dir, device, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
/* check if the disk name already exists */
|
|
for(i=state->disklist;i!=0;i=i->next) {
|
|
disk = i->data;
|
|
if (strcmp(disk->name, buffer) == 0)
|
|
break;
|
|
}
|
|
if (i) {
|
|
fprintf(stderr, "Duplicate disk name '%s' at line %u\n", buffer, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
disk = disk_alloc(buffer, dir, st.st_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) {
|
|
struct snapraid_filter* filter;
|
|
|
|
ret = sgetlasttok(f, buffer, sizeof(buffer));
|
|
if (ret < 0) {
|
|
fprintf(stderr, "Invalid 'exclude' specification in '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
if (!*buffer) {
|
|
fprintf(stderr, "Empty 'exclude' specification in '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
filter = filter_alloc_file(-1, buffer);
|
|
if (!filter) {
|
|
fprintf(stderr, "Invalid 'exclude' specification '%s' in '%s' at line %u\n", buffer, path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
tommy_list_insert_tail(&state->filterlist, &filter->node, filter);
|
|
} else if (strcmp(tag, "include") == 0) {
|
|
struct snapraid_filter* filter;
|
|
|
|
ret = sgetlasttok(f, buffer, sizeof(buffer));
|
|
if (ret < 0) {
|
|
fprintf(stderr, "Invalid 'include' specification in '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
if (!*buffer) {
|
|
fprintf(stderr, "Empty 'include' specification in '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
filter = filter_alloc_file(1, buffer);
|
|
if (!filter) {
|
|
fprintf(stderr, "Invalid 'include' specification '%s' in '%s' at line %u\n", buffer, path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
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) {
|
|
fprintf(stderr, "Invalid 'autosave' specification in '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
if (!*buffer) {
|
|
fprintf(stderr, "Empty 'autosave' specification in '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
state->autosave = strtoul(buffer, &e, 0);
|
|
|
|
if (!e || *e) {
|
|
fprintf(stderr, "Invalid 'autosave' specification in '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
/* convert to GiB */
|
|
state->autosave *= 1024 * 1024 * 1024;
|
|
} else if (tag[0] == 0) {
|
|
/* allow empty lines */
|
|
} else if (tag[0] == '#') {
|
|
ret = sgetline(f, buffer, sizeof(buffer));
|
|
if (ret < 0) {
|
|
fprintf(stderr, "Invalid comment in '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
} else {
|
|
fprintf(stderr, "Invalid command '%s' in '%s' at line %u\n", tag, path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
/* skip final spaces */
|
|
sgetspace(f);
|
|
|
|
/* next line */
|
|
c = sgeteol(f);
|
|
if (c == EOF) {
|
|
break;
|
|
}
|
|
if (c != '\n') {
|
|
fprintf(stderr, "Extra data in '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
++line;
|
|
}
|
|
|
|
if (serror(f)) {
|
|
fprintf(stderr, "Error reading the configuration file '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
sclose(f);
|
|
|
|
state_config_check(state, path);
|
|
|
|
/* select the default hash */
|
|
if (state->opt.force_murmur3) {
|
|
state->besthash = HASH_MURMUR3;
|
|
} else if (state->opt.force_spooky2) {
|
|
state->besthash = HASH_SPOOKY2;
|
|
} else {
|
|
#if defined(__i386__) || defined(__x86_64__)
|
|
if (sizeof(void*) == 4 && !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;
|
|
|
|
/* by default use a random hash seed */
|
|
randomize(state->hashseed, HASH_SIZE);
|
|
|
|
/* no previous hash by default */
|
|
state->prevhash = HASH_UNDEFINED;
|
|
|
|
/* intentionally not set the prevhashseed */
|
|
|
|
if (state->opt.gui) {
|
|
tommy_node* i;
|
|
fprintf(stdlog, "blocksize:%u\n", state->block_size);
|
|
for(i=state->disklist;i!=0;i=i->next) {
|
|
struct snapraid_disk* disk = i->data;
|
|
fprintf(stdlog, "disk:%s:%s\n", disk->name, disk->dir);
|
|
}
|
|
if (state->qarity[0] != 0)
|
|
fprintf(stdlog, "mode:raid6\n");
|
|
else
|
|
fprintf(stdlog, "mode:raid5\n");
|
|
fprintf(stdlog, "parity:%s\n", state->parity);
|
|
if (state->qarity[0] != 0)
|
|
fprintf(stdlog, "qarity:%s\n", state->qarity);
|
|
if (state->pool[0] != 0)
|
|
fprintf(stdlog, "pool:%s\n", state->pool);
|
|
fflush(stdlog);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Updates the disk mapping if required.
|
|
*/
|
|
static void state_map(struct snapraid_state* state)
|
|
{
|
|
unsigned hole;
|
|
tommy_node* i;
|
|
unsigned uuid_mismatch;
|
|
|
|
/* removes all the mapping without a disk */
|
|
/* this happens when a disk is removed from the configuration file */
|
|
for(i=state->maplist;i!=0;) {
|
|
struct snapraid_map* map = i->data;
|
|
struct snapraid_disk* disk;
|
|
tommy_node* j;
|
|
|
|
for(j=state->disklist;j!=0;j=j->next) {
|
|
disk = j->data;
|
|
if (strcmp(disk->name, map->name) == 0) {
|
|
/* disk found */
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* go to the next mapping before removing */
|
|
i = i->next;
|
|
|
|
if (j==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 */
|
|
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 */
|
|
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)
|
|
continue;
|
|
|
|
/* mapping not found, search for an hole */
|
|
while (1) {
|
|
for(j=state->maplist;j!=0;j=j->next) {
|
|
map = j->data;
|
|
if (map->position == hole) {
|
|
/* position already used */
|
|
break;
|
|
}
|
|
}
|
|
if (j==0) {
|
|
/* hole found */
|
|
break;
|
|
}
|
|
|
|
/* try with the next one */
|
|
++hole;
|
|
}
|
|
|
|
/* insert the new mapping */
|
|
map = map_alloc(disk->name, hole, "");
|
|
|
|
tommy_list_insert_tail(&state->maplist, &map->node, map);
|
|
}
|
|
|
|
/* checks if mapping match the disk uuid */
|
|
uuid_mismatch = 0;
|
|
for(i=state->maplist;i!=0;i=i->next) {
|
|
struct snapraid_map* map = i->data;
|
|
struct snapraid_disk* disk;
|
|
tommy_node* j;
|
|
char uuid[UUID_MAX];
|
|
int ret;
|
|
|
|
for(j=state->disklist;j!=0;j=j->next) {
|
|
disk = j->data;
|
|
if (strcmp(disk->name, map->name) == 0) {
|
|
/* disk found */
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (j==0) {
|
|
fprintf(stderr, "Internal incosistency for mapping '%s'\n", map->name);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
ret = devuuid(disk->device, uuid, sizeof(uuid));
|
|
if (ret != 0) {
|
|
/* uuid not available, just ignore */
|
|
continue;
|
|
}
|
|
|
|
/* if the uuid is changed */
|
|
if (strcmp(uuid, map->uuid) != 0) {
|
|
/* if the previous uuid is available */
|
|
if (map->uuid[0] != 0) {
|
|
/* count the number of uuid change */
|
|
++uuid_mismatch;
|
|
fprintf(stderr, "UUID change for disk '%s' from '%s' to '%s'\n", disk->name, map->uuid, uuid);
|
|
}
|
|
|
|
/* 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;
|
|
}
|
|
}
|
|
|
|
if (!state->opt.force_uuid && uuid_mismatch > state->level) {
|
|
fprintf(stderr, "Too many disks have UUID changed from the latest 'sync'.\n");
|
|
fprintf(stderr, "If this happens because you really replaced them,\n");
|
|
fprintf(stderr, "you can '%s' anyway, using 'snapraid --force-uuid %s'.\n", state->command, state->command);
|
|
fprintf(stderr, "Instead, it's possible that you messed up the disk mount points,\n");
|
|
fprintf(stderr, "and you have to restore the mount points at the state of the latest sync.\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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 */
|
|
for(i=state->maplist;i!=0;i=i->next) {
|
|
struct snapraid_map* map = i->data;
|
|
tommy_node* j;
|
|
for(j=i->next;j!=0;j=j->next) {
|
|
struct snapraid_map* other = j->data;
|
|
if (strcmp(map->name, other->name) == 0) {
|
|
fprintf(stderr, "Colliding 'map' disk specification in '%s'\n", path);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
if (map->position == other->position) {
|
|
fprintf(stderr, "Colliding 'map' index specification in '%s'\n", path);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Checks if a block position is used by at least one disk.
|
|
*/
|
|
static int is_block_used(struct snapraid_state* state, block_off_t pos)
|
|
{
|
|
tommy_node* i;
|
|
|
|
/* check for each disk if block is really used */
|
|
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 (block != BLOCK_EMPTY)
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
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;
|
|
block_off_t blockmax;
|
|
unsigned line;
|
|
unsigned count_file;
|
|
unsigned count_block;
|
|
unsigned count_hardlink;
|
|
unsigned count_symlink;
|
|
unsigned count_dir;
|
|
|
|
disk = 0;
|
|
file = 0;
|
|
line = 1;
|
|
blockidx = 0;
|
|
blockmax = 0;
|
|
count_file = 0;
|
|
count_block = 0;
|
|
count_hardlink = 0;
|
|
count_symlink = 0;
|
|
count_dir = 0;
|
|
|
|
while (1) {
|
|
char buffer[PATH_MAX];
|
|
char tag[PATH_MAX];
|
|
int ret;
|
|
int c;
|
|
|
|
/* read the command */
|
|
ret = sgettok(f, tag, sizeof(tag));
|
|
if (ret < 0) {
|
|
fprintf(stderr, "Error reading the configuration file '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
/* 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) {
|
|
/* "blk"/""new"/"chg" command */
|
|
block_off_t v_pos;
|
|
struct snapraid_block* block;
|
|
snapraid_info info;
|
|
|
|
if (!file) {
|
|
fprintf(stderr, "Unexpected '%s' specification in '%s' at line %u\n", tag, path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
ret = sgetu32(f, &v_pos);
|
|
if (ret < 0) {
|
|
fprintf(stderr, "Invalid '%s' specification in '%s' at line %u\n", tag, path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
if (blockidx >= file->blockmax) {
|
|
fprintf(stderr, "Internal inconsistency in '%s' specification in '%s' at line %u\n", tag, path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
block = &file->blockvec[blockidx];
|
|
|
|
if (block->parity_pos != POS_INVALID) {
|
|
fprintf(stderr, "Internal inconsistency in '%s' specification in '%s' at line %u\n", tag, path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
/* set the parity only if present */
|
|
switch (tag[0]) {
|
|
case 'b' :
|
|
block_state_set(block, BLOCK_STATE_BLK);
|
|
break;
|
|
case 'n' :
|
|
block_state_set(block, BLOCK_STATE_NEW);
|
|
break;
|
|
case 'c' :
|
|
block_state_set(block, BLOCK_STATE_CHG);
|
|
break;
|
|
}
|
|
|
|
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', and not for 'new' */
|
|
if (tag[0] != 'n') {
|
|
c = sgetc(f);
|
|
if (c != ' ') {
|
|
fprintf(stderr, "Invalid '%s' specification in '%s' at line %u\n", tag, path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
/* set the hash only if present */
|
|
ret = sgethex(f, block->hash, HASH_SIZE);
|
|
if (ret < 0) {
|
|
fprintf(stderr, "Invalid '%s' specification in '%s' at line %u\n", tag, path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
}
|
|
|
|
/* we must not overwrite existing blocks */
|
|
if (disk_block_get(disk, v_pos) != BLOCK_EMPTY) {
|
|
fprintf(stderr, "Internal inconsistency for '%s' specification in '%s' at line %u\n", tag, path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
/* insert the block in the block array */
|
|
tommy_array_grow(&disk->blockarr, v_pos + 1);
|
|
tommy_array_set(&disk->blockarr, v_pos, block);
|
|
|
|
/* check for termination of the block list */
|
|
++blockidx;
|
|
if (blockidx == file->blockmax) {
|
|
file = 0;
|
|
disk = 0;
|
|
}
|
|
|
|
/* set a fake info block, in case of upgrading from an old version */
|
|
/* the real block info will overwrite this */
|
|
info = info_make(save_time, 0, 0);
|
|
|
|
/* insert the info in the array */
|
|
info_set(&state->infoarr, v_pos, info);
|
|
|
|
/* stat */
|
|
++count_block;
|
|
} else if (strcmp(tag, "inf") == 0) {
|
|
/* "inf" command */
|
|
block_off_t v_pos;
|
|
snapraid_info info;
|
|
int rehash;
|
|
int bad;
|
|
uint32_t t;
|
|
|
|
ret = sgetu32(f, &v_pos);
|
|
if (ret < 0) {
|
|
fprintf(stderr, "Invalid 'inf' specification in '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
c = sgetc(f);
|
|
if (c != ' ') {
|
|
fprintf(stderr, "Invalid 'inf' specification in '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
ret = sgetu32(f, &t);
|
|
if (ret < 0) {
|
|
fprintf(stderr, "Invalid 'inf' specification in '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
/* read extra tags if present */
|
|
rehash = 0;
|
|
bad = 0;
|
|
c = sgetc(f);
|
|
while (c == ' ') {
|
|
ret = sgettok(f, buffer, sizeof(buffer));
|
|
if (ret < 0) {
|
|
fprintf(stderr, "Invalid 'inf' specification in '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
if (strcmp(buffer, "bad") == 0) {
|
|
bad = 1;
|
|
} else if (strcmp(buffer, "rehash") == 0) {
|
|
rehash = 1;
|
|
|
|
if (state->prevhash == HASH_UNDEFINED) {
|
|
fprintf(stderr, "Internal incosistency for missing previous checksum in '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
} else {
|
|
fprintf(stderr, "Invalid 'inf' specification '%s' in '%s' at line %u\n", buffer, path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
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) {
|
|
fprintf(stderr, "Unexpected 'off' specification in '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
/* allocate a new deleted block */
|
|
deleted = deleted_alloc();
|
|
|
|
/* insert it in the list of deleted blocks */
|
|
tommy_list_insert_tail(&disk->deletedlist, &deleted->node, deleted);
|
|
|
|
ret = sgetu32(f, &v_pos);
|
|
if (ret < 0) {
|
|
fprintf(stderr, "Invalid 'off' specification in '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
deleted->block.parity_pos = v_pos;
|
|
|
|
/* read the hash */
|
|
c = sgetc(f);
|
|
if (c != ' ') {
|
|
fprintf(stderr, "Invalid 'off' specification in '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
/* set the hash only if present */
|
|
ret = sgethex(f, deleted->block.hash, HASH_SIZE);
|
|
if (ret < 0) {
|
|
fprintf(stderr, "Invalid 'off' specification in '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
/* we must not overwrite existing blocks */
|
|
if (disk_block_get(disk, v_pos) != BLOCK_EMPTY) {
|
|
fprintf(stderr, "Internal inconsistency for 'off' specification in '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
/* insert the block in the block array */
|
|
tommy_array_grow(&disk->blockarr, v_pos + 1);
|
|
tommy_array_set(&disk->blockarr, v_pos, &deleted->block);
|
|
} else if (strcmp(tag, "file") == 0) {
|
|
/* 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) {
|
|
fprintf(stderr, "Missing 'blk' specification in '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
ret = sgettok(f, buffer, sizeof(buffer));
|
|
if (ret < 0) {
|
|
fprintf(stderr, "Invalid 'file' specification in '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
c = sgetc(f);
|
|
if (c != ' ') {
|
|
fprintf(stderr, "Invalid 'file' specification in '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
ret = sgetu64(f, &v_size);
|
|
if (ret < 0) {
|
|
fprintf(stderr, "Invalid 'file' specification in '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
c = sgetc(f);
|
|
if (c != ' ') {
|
|
fprintf(stderr, "Invalid 'file' specification in '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
ret = sgetu64(f, &v_mtime_sec);
|
|
if (ret < 0) {
|
|
fprintf(stderr, "Invalid 'file' specification in '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
c = sgetc(f);
|
|
if (c == '.') { /* the nanosecond field is present only from version 1.14 */
|
|
ret = sgetu32(f, &v_mtime_nsec);
|
|
if (ret < 0) {
|
|
fprintf(stderr, "Invalid 'file' specification in '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
/* ignore nanoseconds if asked to find by name */
|
|
if (state->opt.force_by_name)
|
|
v_mtime_nsec = STAT_NSEC_INVALID;
|
|
|
|
c = sgetc(f);
|
|
} else {
|
|
/* use a special value, meaning that we don't have this information */
|
|
v_mtime_nsec = STAT_NSEC_INVALID;
|
|
}
|
|
|
|
if (c != ' ') {
|
|
fprintf(stderr, "Invalid 'file' specification in '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
ret = sgetu64(f, &v_inode);
|
|
if (ret < 0) {
|
|
fprintf(stderr, "Invalid 'file' specification in '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
c = sgetc(f);
|
|
if (c != ' ') {
|
|
fprintf(stderr, "Invalid 'file' specification in '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
ret = sgetline(f, sub, sizeof(sub));
|
|
if (ret < 0) {
|
|
fprintf(stderr, "Invalid 'file' specification in '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
if (!*sub) {
|
|
fprintf(stderr, "Invalid 'file' specification in '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
/* find the disk */
|
|
disk = find_disk(state, buffer);
|
|
if (!disk) {
|
|
fprintf(stderr, "Disk named '%s' not found in '%s' at line %u\n", buffer, path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
/* 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_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) {
|
|
fprintf(stderr, "Missing 'blk' specification in '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
ret = sgettok(f, buffer, sizeof(buffer));
|
|
if (ret < 0) {
|
|
fprintf(stderr, "Invalid 'hole' specification in '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
/* find the disk */
|
|
disk = find_disk(state, buffer);
|
|
if (!disk) {
|
|
fprintf(stderr, "Disk named '%s' not found in '%s' at line %u\n", buffer, path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
} 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) {
|
|
fprintf(stderr, "Invalid 'symlink' specification in '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
c = sgetc(f);
|
|
if (c != ' ') {
|
|
fprintf(stderr, "Invalid 'symlink' specification in '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
ret = sgetline(f, sub, sizeof(sub));
|
|
if (ret < 0) {
|
|
fprintf(stderr, "Invalid 'symlink' specification in '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
c = sgeteol(f);
|
|
if (c != '\n') {
|
|
fprintf(stderr, "Invalid 'symlink' specification in '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
++line;
|
|
|
|
ret = sgettok(f, tokento, sizeof(tokento));
|
|
if (ret < 0) {
|
|
fprintf(stderr, "Invalid 'symlink' specification in '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
if (strcmp(tokento, "to") != 0) {
|
|
fprintf(stderr, "Invalid 'symlink' specification in '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
c = sgetc(f);
|
|
if (c != ' ') {
|
|
fprintf(stderr, "Invalid 'symlink' specification in '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
ret = sgetline(f, linkto, sizeof(linkto));
|
|
if (ret < 0) {
|
|
fprintf(stderr, "Invalid 'symlink' specification in '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
if (!*sub || !*linkto) {
|
|
fprintf(stderr, "Invalid 'symlink' specification in '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
/* find the disk */
|
|
disk = find_disk(state, buffer);
|
|
if (!disk) {
|
|
fprintf(stderr, "Disk named '%s' not found in '%s' at line %u\n", buffer, path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
/* 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) {
|
|
fprintf(stderr, "Invalid 'hardlink' specification in '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
c = sgetc(f);
|
|
if (c != ' ') {
|
|
fprintf(stderr, "Invalid 'hardlink' specification in '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
ret = sgetline(f, sub, sizeof(sub));
|
|
if (ret < 0) {
|
|
fprintf(stderr, "Invalid 'hardlink' specification in '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
c = sgeteol(f);
|
|
if (c != '\n') {
|
|
fprintf(stderr, "Invalid 'hardlink' specification in '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
++line;
|
|
|
|
ret = sgettok(f, tokento, sizeof(tokento));
|
|
if (ret < 0) {
|
|
fprintf(stderr, "Invalid 'hardlink' specification in '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
if (strcmp(tokento, "to") != 0) {
|
|
fprintf(stderr, "Invalid 'hardlink' specification in '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
c = sgetc(f);
|
|
if (c != ' ') {
|
|
fprintf(stderr, "Invalid 'hardlink' specification in '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
ret = sgetline(f, linkto, sizeof(linkto));
|
|
if (ret < 0) {
|
|
fprintf(stderr, "Invalid 'hardlink' specification in '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
if (!*sub || !*linkto) {
|
|
fprintf(stderr, "Invalid 'hardlink' specification in '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
/* find the disk */
|
|
disk = find_disk(state, buffer);
|
|
if (!disk) {
|
|
fprintf(stderr, "Disk named '%s' not found in '%s' at line %u\n", buffer, path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
/* 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) {
|
|
fprintf(stderr, "Invalid 'dir' specification in '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
c = sgetc(f);
|
|
if (c != ' ') {
|
|
fprintf(stderr, "Invalid 'dir' specification in '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
ret = sgetline(f, sub, sizeof(sub));
|
|
if (ret < 0) {
|
|
fprintf(stderr, "Invalid 'dir' specification in '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
if (!*sub) {
|
|
fprintf(stderr, "Invalid 'dir' specification in '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
/* find the disk */
|
|
disk = find_disk(state, buffer);
|
|
if (!disk) {
|
|
fprintf(stderr, "Disk named '%s' not found in '%s' at line %u\n", buffer, path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
/* 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 (strcmp(tag, "checksum") == 0) {
|
|
|
|
ret = sgettok(f, buffer, sizeof(buffer));
|
|
if (ret < 0) {
|
|
fprintf(stderr, "Invalid 'checksum' specification in '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
if (strcmp(buffer, "murmur3") == 0) {
|
|
state->hash = HASH_MURMUR3;
|
|
} else if (strcmp(buffer, "spooky2") == 0) {
|
|
state->hash = HASH_SPOOKY2;
|
|
} else {
|
|
fprintf(stderr, "Invalid 'checksum' specification '%s' in '%s' at line %u\n", buffer, path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
c = sgetc(f);
|
|
if (c == ' ') {
|
|
/* read the seed if present */
|
|
ret = sgethex(f, state->hashseed, HASH_SIZE);
|
|
if (ret < 0) {
|
|
fprintf(stderr, "Invalid 'seed' specification in '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
} else {
|
|
sungetc(c, f);
|
|
memset(state->hashseed, 0, HASH_SIZE);
|
|
}
|
|
} else if (strcmp(tag, "prevchecksum") == 0) {
|
|
|
|
ret = sgettok(f, buffer, sizeof(buffer));
|
|
if (ret < 0) {
|
|
fprintf(stderr, "Invalid 'prevchecksum' specification in '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
if (strcmp(buffer, "murmur3") == 0) {
|
|
state->prevhash = HASH_MURMUR3;
|
|
} else if (strcmp(buffer, "spooky2") == 0) {
|
|
state->prevhash = HASH_SPOOKY2;
|
|
} else {
|
|
fprintf(stderr, "Invalid 'prevchecksum' specification '%s' in '%s' at line %u\n", buffer, path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
c = sgetc(f);
|
|
if (c != ' ') {
|
|
fprintf(stderr, "Invalid 'prevchecksum' specification '%s' in '%s' at line %u\n", buffer, path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
/* read the seed */
|
|
ret = sgethex(f, state->prevhashseed, HASH_SIZE);
|
|
if (ret < 0) {
|
|
fprintf(stderr, "Invalid 'seed' specification in '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
} else if (strcmp(tag, "blksize") == 0) {
|
|
block_off_t blksize;
|
|
|
|
ret = sgetu32(f, &blksize);
|
|
if (ret < 0) {
|
|
fprintf(stderr, "Invalid 'blksize' specification in '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
if (blksize != state->block_size) {
|
|
fprintf(stderr, "Mismatching 'blksize' and 'block_size' specification in '%s' at line %u\n", path, line);
|
|
fprintf(stderr, "Please restore the 'block_size' value in the configuration file to '%u'\n", blksize / 1024);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
} else if (strcmp(tag, "map") == 0) {
|
|
struct snapraid_map* map;
|
|
char uuid[UUID_MAX];
|
|
uint32_t v_pos;
|
|
|
|
ret = sgettok(f, buffer, sizeof(buffer));
|
|
if (ret < 0) {
|
|
fprintf(stderr, "Invalid 'map' specification in '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
c = sgetc(f);
|
|
if (c != ' ') {
|
|
fprintf(stderr, "Invalid 'map' specification in '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
ret = sgetu32(f, &v_pos);
|
|
if (ret < 0) {
|
|
fprintf(stderr, "Invalid 'map' specification in '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
c = sgetc(f);
|
|
if (c != ' ') {
|
|
sungetc(c, f);
|
|
uuid[0] = 0;
|
|
} else {
|
|
/* read the uuid */
|
|
ret = sgettok(f, uuid, sizeof(uuid));
|
|
if (ret < 0) {
|
|
fprintf(stderr, "Invalid 'map' specification in '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
}
|
|
|
|
map = map_alloc(buffer, v_pos, uuid);
|
|
|
|
tommy_list_insert_tail(&state->maplist, &map->node, map);
|
|
} else if (strcmp(tag, "sign") == 0) {
|
|
uint32_t sign;
|
|
|
|
ret = sgetu32(f, &sign);
|
|
if (ret < 0) {
|
|
fprintf(stderr, "Invalid 'sign' specification in '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
/* sign check not supported anymore for the text content file */
|
|
} else if (tag[0] == 0) {
|
|
/* allow empty lines */
|
|
sgetspace(f);
|
|
} else if (tag[0] == '#') {
|
|
ret = sgetline(f, buffer, sizeof(buffer));
|
|
if (ret < 0) {
|
|
fprintf(stderr, "Invalid comment in '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
} else {
|
|
fprintf(stderr, "Invalid command '%s' in '%s' at line %u\n", tag, path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
/* next line */
|
|
c = sgeteol(f);
|
|
if (c == EOF) {
|
|
break;
|
|
}
|
|
if (c != '\n') {
|
|
fprintf(stderr, "Extra data in '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
++line;
|
|
}
|
|
|
|
if (serror(f)) {
|
|
fprintf(stderr, "Error reading the content file '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
if (file) {
|
|
fprintf(stderr, "Missing 'blk' specification in '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
/* check that the stored parity size matches the loaded state */
|
|
if (blockmax != parity_size(state)) {
|
|
fprintf(stderr, "Internal incosistency in parity size in '%s' at line %u\n", path, line);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
state->loaded_blockmax = blockmax;
|
|
|
|
if (state->opt.verbose) {
|
|
printf("\tfile %u\n", count_file);
|
|
printf("\tblock %u\n", count_block);
|
|
printf("\thardlink %u\n", count_hardlink);
|
|
printf("\tsymlink %u\n", count_symlink);
|
|
printf("\temptydir %u\n", count_dir);
|
|
}
|
|
}
|
|
|
|
static void state_write_text(struct snapraid_state* state, STREAM* f)
|
|
{
|
|
unsigned count_file;
|
|
unsigned count_block;
|
|
unsigned count_hardlink;
|
|
unsigned count_symlink;
|
|
unsigned count_dir;
|
|
tommy_node* i;
|
|
block_off_t b;
|
|
block_off_t blockmax;
|
|
|
|
count_file = 0;
|
|
count_block = 0;
|
|
count_hardlink = 0;
|
|
count_symlink = 0;
|
|
count_dir = 0;
|
|
|
|
/* blocks of all array */
|
|
blockmax = parity_size(state);
|
|
|
|
sputsl("blksize ", f);
|
|
sputu32(state->block_size, f);
|
|
sputeol(f);
|
|
if (serror(f)) {
|
|
fprintf(stderr, "Error writing the content file '%s'. %s.\n", serrorfile(f), strerror(errno));
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
if (state->hash == HASH_MURMUR3) {
|
|
sputsl("checksum murmur3", f);
|
|
} else if (state->hash == HASH_SPOOKY2) {
|
|
sputsl("checksum spooky2", f);
|
|
} else {
|
|
fprintf(stderr, "Unexpected hash when writing the content file '%s'.\n", serrorfile(f));
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
sputc(' ', f);
|
|
sputhex(state->hashseed, HASH_SIZE, f);
|
|
sputeol(f);
|
|
if (serror(f)) {
|
|
fprintf(stderr, "Error writing the content file '%s'. %s.\n", serrorfile(f), strerror(errno));
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
/* previous hash only if present */
|
|
if (state->prevhash != HASH_UNDEFINED) {
|
|
|
|
/* check if the previous hash is still needed */
|
|
for(b=0;b<blockmax;++b) {
|
|
snapraid_info info;
|
|
|
|
info = info_get(&state->infoarr, b);
|
|
|
|
if (info == 0) {
|
|
/* skip it */
|
|
continue;
|
|
}
|
|
|
|
if (info_get_rehash(info)) {
|
|
/* stop the process */
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* if at least one rehash tag found, we have to save the previous hash */
|
|
if (b < blockmax) {
|
|
if (state->prevhash == HASH_MURMUR3) {
|
|
sputsl("prevchecksum murmur3", f);
|
|
} else if (state->hash == HASH_SPOOKY2) {
|
|
sputsl("prevchecksum spooky2", f);
|
|
} else {
|
|
fprintf(stderr, "Unexpected prevhash when writing the content file '%s'.\n", serrorfile(f));
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
sputc(' ', f);
|
|
sputhex(state->prevhashseed, HASH_SIZE, f);
|
|
sputeol(f);
|
|
if (serror(f)) {
|
|
fprintf(stderr, "Error writing the content file '%s'. %s.\n", serrorfile(f), strerror(errno));
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* for each map */
|
|
for(i=state->maplist;i!=0;i=i->next) {
|
|
struct snapraid_map* map = i->data;
|
|
sputsl("map ", f);
|
|
sputs(map->name, f);
|
|
sputc(' ', f);
|
|
sputu32(map->position, f);
|
|
|
|
/* if there is an uuid, print it */
|
|
if (map->uuid[0]) {
|
|
sputc(' ', f);
|
|
sputs(map->uuid, f);
|
|
}
|
|
sputeol(f);
|
|
if (serror(f)) {
|
|
fprintf(stderr, "Error writing the content file '%s'. %s.\n", serrorfile(f), strerror(errno));
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
}
|
|
|
|
/* for each disk */
|
|
for(i=state->disklist;i!=0;i=i->next) {
|
|
tommy_node* j;
|
|
struct snapraid_disk* disk = i->data;
|
|
int first_deleted;
|
|
block_off_t blockdiskmax;
|
|
|
|
/* for each file */
|
|
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)) {
|
|
fprintf(stderr, "Error writing the content file '%s'. %s.\n", serrorfile(f), strerror(errno));
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
/* for each block in the file */
|
|
for(b=0;b<file->blockmax;++b) {
|
|
struct snapraid_block* block = &file->blockvec[b];
|
|
unsigned block_state;
|
|
|
|
block_state = block_state_get(block);
|
|
switch (block_state) {
|
|
case BLOCK_STATE_BLK :
|
|
sputsl("blk ", f);
|
|
break;
|
|
case BLOCK_STATE_NEW :
|
|
sputsl("new ", f);
|
|
break;
|
|
case BLOCK_STATE_CHG :
|
|
sputsl("chg ", f);
|
|
break;
|
|
default:
|
|
fprintf(stderr, "Internal state inconsistency in saving for block %u state %u\n", block->parity_pos, block_state);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
sputu32(block->parity_pos, f);
|
|
|
|
if (block_state != BLOCK_STATE_NEW) {
|
|
sputc(' ', f);
|
|
sputhex(block->hash, HASH_SIZE, f);
|
|
}
|
|
|
|
sputeol(f);
|
|
if (serror(f)) {
|
|
fprintf(stderr, "Error writing the content file '%s'. %s.\n", serrorfile(f), strerror(errno));
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
++count_block;
|
|
}
|
|
|
|
++count_file;
|
|
}
|
|
|
|
/* for each link */
|
|
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)) {
|
|
fprintf(stderr, "Error writing the content file '%s'. %s.\n", serrorfile(f), strerror(errno));
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
}
|
|
|
|
/* for each dir */
|
|
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)) {
|
|
fprintf(stderr, "Error writing the content file '%s'. %s.\n", serrorfile(f), strerror(errno));
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
++count_dir;
|
|
}
|
|
|
|
/* maximum block for this disk */
|
|
blockdiskmax = tommy_array_size(&disk->blockarr);
|
|
if (blockdiskmax > blockmax)
|
|
blockdiskmax = blockmax;
|
|
|
|
/* for each deleted block in the disk */
|
|
first_deleted = 1;
|
|
for(b=0;b<blockdiskmax;++b) {
|
|
struct snapraid_block* block = tommy_array_get(&disk->blockarr, b);
|
|
if (block_state_get(block) == BLOCK_STATE_DELETED) {
|
|
if (first_deleted) {
|
|
first_deleted = 0;
|
|
|
|
sputsl("hole ", f);
|
|
sputs(disk->name, f);
|
|
sputeol(f);
|
|
if (serror(f)) {
|
|
fprintf(stderr, "Error writing the content file '%s'. %s.\n", serrorfile(f), strerror(errno));
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
}
|
|
|
|
sputsl("off ", f);
|
|
|
|
sputu32(b, f);
|
|
|
|
sputc(' ', f);
|
|
sputhex(block->hash, HASH_SIZE, f);
|
|
|
|
sputeol(f);
|
|
if (serror(f)) {
|
|
fprintf(stderr, "Error writing the content file '%s'. %s.\n", serrorfile(f), strerror(errno));
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* for each block */
|
|
for(b=0;b<blockmax;++b) {
|
|
snapraid_info info;
|
|
|
|
info = info_get(&state->infoarr, b);
|
|
|
|
/* check for each disk if block is really used */
|
|
for(i=state->disklist;i!=0;i=i->next) {
|
|
struct snapraid_disk* disk = i->data;
|
|
struct snapraid_block* block;
|
|
|
|
block = disk_block_get(disk, b);
|
|
|
|
if (block != BLOCK_EMPTY)
|
|
break;
|
|
}
|
|
|
|
/* if processed all, it's empty for all */
|
|
if (i == 0)
|
|
info = 0;
|
|
|
|
/* save only stuffs different than 0 */
|
|
if (info != 0) {
|
|
sputsl("inf ", f);
|
|
|
|
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)) {
|
|
fprintf(stderr, "Error writing the content file '%s'. %s.\n", serrorfile(f), strerror(errno));
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (serror(f)) {
|
|
fprintf(stderr, "Error writing the content file '%s'. %s.\n", serrorfile(f), strerror(errno));
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
if (state->opt.verbose) {
|
|
printf("\tfile %u\n", count_file);
|
|
printf("\tblock %u\n", count_block);
|
|
printf("\thardlink %u\n", count_hardlink);
|
|
printf("\tsymlink %u\n", count_symlink);
|
|
printf("\temptydir %u\n", count_dir);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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)) {
|
|
fprintf(stderr, "Unexpected end of content file '%s' at offset %"PRIi64"\n", path, stell(f));
|
|
fprintf(stderr, "This content file is truncated. Use an alternate copy.\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
if (serror(f)) {
|
|
fprintf(stderr, "Error reading the content file '%s' at offset %"PRIi64"\n", path, stell(f));
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
fprintf(stderr, "Decoding error in '%s' at offset %"PRIi64"\n", path, stell(f));
|
|
|
|
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)) {
|
|
fprintf(stderr, "Error flushing the content file '%s' at offset %"PRIi64"\n", path, stell(f));
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
/* 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);
|
|
|
|
if (crc_computed != crc_stored) {
|
|
fprintf(stderr, "Mismatching CRC in '%s'\n", path);
|
|
fprintf(stderr, "This content file is damaged! Use an alternate copy.\n");
|
|
exit(EXIT_FAILURE);
|
|
} else {
|
|
fprintf(stderr, "The file CRC is correct! This seems a SnapRAID bug!\n");
|
|
}
|
|
}
|
|
|
|
static void state_read_binary(struct snapraid_state* state, const char* path, STREAM* f)
|
|
{
|
|
block_off_t blockmax;
|
|
unsigned count_file;
|
|
unsigned count_block;
|
|
unsigned count_hardlink;
|
|
unsigned count_symlink;
|
|
unsigned count_dir;
|
|
int crc_checked;
|
|
char buffer[PATH_MAX];
|
|
int ret;
|
|
|
|
blockmax = 0;
|
|
count_file = 0;
|
|
count_block = 0;
|
|
count_hardlink = 0;
|
|
count_symlink = 0;
|
|
count_dir = 0;
|
|
crc_checked = 0;
|
|
|
|
ret = sread(f, buffer, 12);
|
|
if (ret < 0 || memcmp(buffer, "SNAPCNT1\n\3\0\0", 12) != 0) {
|
|
decoding_error(path, f);
|
|
fprintf(stderr, "Invalid header!\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
while (1) {
|
|
char buffer[PATH_MAX];
|
|
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;
|
|
|
|
ret = sgetbs(f, buffer, sizeof(buffer));
|
|
if (ret < 0) {
|
|
decoding_error(path, f);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
ret = sgetb64(f, &v_size);
|
|
if (ret < 0) {
|
|
decoding_error(path, f);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
/* check for impossible file size to avoid to crash for a too big allocation */
|
|
if (v_size / state->block_size > blockmax) {
|
|
decoding_error(path, f);
|
|
fprintf(stderr, "Internal inconsistency in file size!\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
ret = sgetb64(f, &v_mtime_sec);
|
|
if (ret < 0) {
|
|
decoding_error(path, f);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
ret = sgetb32(f, &v_mtime_nsec);
|
|
if (ret < 0) {
|
|
decoding_error(path, f);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
/* ignore nanoseconds if asked to find by name */
|
|
if (state->opt.force_by_name)
|
|
v_mtime_nsec = STAT_NSEC_INVALID;
|
|
|
|
ret = sgetb64(f, &v_inode);
|
|
if (ret < 0) {
|
|
decoding_error(path, f);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
ret = sgetbs(f, sub, sizeof(sub));
|
|
if (ret < 0) {
|
|
decoding_error(path, f);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
if (!*sub) {
|
|
decoding_error(path, f);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
/* find the disk */
|
|
disk = find_disk(state, buffer);
|
|
if (!disk) {
|
|
decoding_error(path, f);
|
|
fprintf(stderr, "Disk named '%s' not found!\n", buffer);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
/* 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_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 */
|
|
c = sgetc(f);
|
|
|
|
ret = sgetb32(f, &v_pos);
|
|
if (ret < 0) {
|
|
decoding_error(path, f);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
ret = sgetb32(f, &v_count);
|
|
if (ret < 0) {
|
|
decoding_error(path, f);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
if (v_idx + v_count > file->blockmax) {
|
|
decoding_error(path, f);
|
|
fprintf(stderr, "Internal inconsistency in block number!\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
if (v_pos + v_count > blockmax) {
|
|
decoding_error(path, f);
|
|
fprintf(stderr, "Internal inconsistency in block size!\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
/* grow the array */
|
|
tommy_array_grow(&disk->blockarr, v_pos + v_count);
|
|
|
|
/* fill the blocks in the run */
|
|
while (v_count) {
|
|
struct snapraid_block* block = &file->blockvec[v_idx];
|
|
|
|
if (block->parity_pos != POS_INVALID) {
|
|
decoding_error(path, f);
|
|
fprintf(stderr, "Internal inconsistency in block position!\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
switch (c) {
|
|
case 'b' :
|
|
block_state_set(block, BLOCK_STATE_BLK);
|
|
break;
|
|
case 'n' :
|
|
block_state_set(block, BLOCK_STATE_NEW);
|
|
break;
|
|
case 'g' :
|
|
block_state_set(block, BLOCK_STATE_CHG);
|
|
break;
|
|
default:
|
|
decoding_error(path, f);
|
|
fprintf(stderr, "Invalid block type!\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
block->parity_pos = v_pos;
|
|
|
|
/* read the hash only for 'blk/chg', and not for 'new' */
|
|
if (c != 'n') {
|
|
/* set the hash only if present */
|
|
ret = sread(f, block->hash, HASH_SIZE);
|
|
if (ret < 0) {
|
|
decoding_error(path, f);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
}
|
|
|
|
/* we must not overwrite existing blocks */
|
|
if (disk_block_get(disk, v_pos) != BLOCK_EMPTY) {
|
|
decoding_error(path, f);
|
|
fprintf(stderr, "Internal inconsistency in block existence!\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
/* insert the block in the block array */
|
|
tommy_array_set(&disk->blockarr, v_pos, block);
|
|
|
|
/* stat */
|
|
++count_block;
|
|
|
|
/* 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) {
|
|
decoding_error(path, f);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
ret = sgetb32(f, &blockmax);
|
|
if (ret < 0) {
|
|
decoding_error(path, f);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
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) {
|
|
decoding_error(path, f);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
if (v_pos + v_count > blockmax) {
|
|
decoding_error(path, f);
|
|
fprintf(stderr, "Internal inconsistency in info size!\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
ret = sgetb32(f, &flag);
|
|
if (ret < 0) {
|
|
decoding_error(path, f);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
/* if there is an info */
|
|
if ((flag & 1) != 0) {
|
|
/* read the time */
|
|
ret = sgetb32(f, &t);
|
|
if (ret < 0) {
|
|
decoding_error(path, f);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
/* analyze the flags */
|
|
bad = (flag & 2) != 0;
|
|
rehash = (flag & 4) != 0;
|
|
|
|
if (rehash && state->prevhash == HASH_UNDEFINED) {
|
|
decoding_error(path, f);
|
|
fprintf(stderr, "Internal incosistency for missing previous checksum!\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
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);
|
|
|
|
/* go to next block */
|
|
++v_pos;
|
|
--v_count;
|
|
}
|
|
}
|
|
} else if (c == 'h') {
|
|
/* hole */
|
|
uint32_t v_pos;
|
|
block_off_t blockdiskmax;
|
|
struct snapraid_disk* disk;
|
|
|
|
ret = sgetbs(f, buffer, sizeof(buffer));
|
|
if (ret < 0) {
|
|
decoding_error(path, f);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
/* find the disk */
|
|
disk = find_disk(state, buffer);
|
|
if (!disk) {
|
|
decoding_error(path, f);
|
|
fprintf(stderr, "Disk named '%s' not found!\n", buffer);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
ret = sgetb32(f, &blockdiskmax);
|
|
if (ret < 0) {
|
|
decoding_error(path, f);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
if (blockdiskmax > blockmax) {
|
|
fprintf(stderr, "Internal inconsistency in disk size!\n");
|
|
decoding_error(path, f);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
/* grow the array */
|
|
tommy_array_grow(&disk->blockarr, blockdiskmax);
|
|
|
|
v_pos = 0;
|
|
while (v_pos < blockdiskmax) {
|
|
uint32_t v_count;
|
|
|
|
ret = sgetb32(f, &v_count);
|
|
if (ret < 0) {
|
|
decoding_error(path, f);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
if (v_pos + v_count > blockdiskmax) {
|
|
decoding_error(path, f);
|
|
fprintf(stderr, "Internal inconsistency in hole size!\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
/* get the subcommand */
|
|
c = sgetc(f);
|
|
|
|
switch (c) {
|
|
case 'o' :
|
|
/* if it's a run of deleted blocks */
|
|
while (v_count) {
|
|
struct snapraid_deleted* deleted;
|
|
|
|
/* allocate a new deleted block */
|
|
deleted = deleted_alloc();
|
|
|
|
/* insert it in the list of deleted blocks */
|
|
tommy_list_insert_tail(&disk->deletedlist, &deleted->node, deleted);
|
|
|
|
/* set the position */
|
|
deleted->block.parity_pos = v_pos;
|
|
|
|
/* read the hash */
|
|
ret = sread(f, deleted->block.hash, HASH_SIZE);
|
|
if (ret < 0) {
|
|
decoding_error(path, f);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
/* we must not overwrite existing blocks */
|
|
if (disk_block_get(disk, v_pos) != BLOCK_EMPTY) {
|
|
decoding_error(path, f);
|
|
fprintf(stderr, "Internal inconsistency for used hole!\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
/* insert the block in the block array */
|
|
tommy_array_set(&disk->blockarr, v_pos, &deleted->block);
|
|
|
|
/* go to next block */
|
|
++v_pos;
|
|
--v_count;
|
|
}
|
|
break;
|
|
case 'O' :
|
|
/* go to the next run */
|
|
v_pos += v_count;
|
|
break;
|
|
default:
|
|
decoding_error(path, f);
|
|
fprintf(stderr, "Invalid hole type!\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
}
|
|
} else if (c == 's') {
|
|
/* symlink */
|
|
char sub[PATH_MAX];
|
|
char linkto[PATH_MAX];
|
|
struct snapraid_link* link;
|
|
struct snapraid_disk* disk;
|
|
|
|
ret = sgetbs(f, buffer, sizeof(buffer));
|
|
if (ret < 0) {
|
|
decoding_error(path, f);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
ret = sgetbs(f, sub, sizeof(sub));
|
|
if (ret < 0) {
|
|
decoding_error(path, f);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
ret = sgetbs(f, linkto, sizeof(linkto));
|
|
if (ret < 0) {
|
|
decoding_error(path, f);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
if (!*sub || !*linkto) {
|
|
decoding_error(path, f);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
/* find the disk */
|
|
disk = find_disk(state, buffer);
|
|
if (!disk) {
|
|
decoding_error(path, f);
|
|
fprintf(stderr, "Disk named '%s' not found!\n", buffer);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
/* 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;
|
|
|
|
ret = sgetbs(f, buffer, sizeof(buffer));
|
|
if (ret < 0) {
|
|
decoding_error(path, f);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
ret = sgetbs(f, sub, sizeof(sub));
|
|
if (ret < 0) {
|
|
decoding_error(path, f);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
ret = sgetbs(f, linkto, sizeof(linkto));
|
|
if (ret < 0) {
|
|
decoding_error(path, f);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
if (!*sub || !*linkto) {
|
|
decoding_error(path, f);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
/* find the disk */
|
|
disk = find_disk(state, buffer);
|
|
if (!disk) {
|
|
decoding_error(path, f);
|
|
fprintf(stderr, "Disk named '%s' not found!\n", buffer);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
/* 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;
|
|
|
|
ret = sgetbs(f, buffer, sizeof(buffer));
|
|
if (ret < 0) {
|
|
decoding_error(path, f);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
ret = sgetbs(f, sub, sizeof(sub));
|
|
if (ret < 0) {
|
|
decoding_error(path, f);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
if (!*sub) {
|
|
decoding_error(path, f);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
/* find the disk */
|
|
disk = find_disk(state, buffer);
|
|
if (!disk) {
|
|
decoding_error(path, f);
|
|
fprintf(stderr, "Disk named '%s' not found!\n", buffer);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
/* 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 */
|
|
c = sgetc(f);
|
|
|
|
switch (c) {
|
|
case 'u' :
|
|
state->hash = HASH_MURMUR3;
|
|
break;
|
|
case 'k' :
|
|
state->hash = HASH_SPOOKY2;
|
|
break;
|
|
default:
|
|
decoding_error(path, f);
|
|
fprintf(stderr, "Invalid checksum!\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
/* read the seed */
|
|
ret = sread(f, state->hashseed, HASH_SIZE);
|
|
if (ret < 0) {
|
|
decoding_error(path, f);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
} else if (c == 'C') {
|
|
/* get the subcommand */
|
|
c = sgetc(f);
|
|
|
|
switch (c) {
|
|
case 'u' :
|
|
state->prevhash = HASH_MURMUR3;
|
|
break;
|
|
case 'k' :
|
|
state->prevhash = HASH_SPOOKY2;
|
|
break;
|
|
default:
|
|
decoding_error(path, f);
|
|
fprintf(stderr, "Invalid checksum!\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
/* read the seed */
|
|
ret = sread(f, state->prevhashseed, HASH_SIZE);
|
|
if (ret < 0) {
|
|
decoding_error(path, f);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
} else if (c == 'z') {
|
|
block_off_t blksize;
|
|
|
|
ret = sgetb32(f, &blksize);
|
|
if (ret < 0) {
|
|
decoding_error(path, f);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
if (blksize != state->block_size) {
|
|
decoding_error(path, f);
|
|
fprintf(stderr, "Mismatching 'blksize' and 'block_size' specification!\n");
|
|
fprintf(stderr, "Please restore the 'block_size' value in the configuration file to '%u'\n", blksize / 1024);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
} else if (c == 'm') {
|
|
struct snapraid_map* map;
|
|
char uuid[UUID_MAX];
|
|
uint32_t v_pos;
|
|
|
|
ret = sgetbs(f, buffer, sizeof(buffer));
|
|
if (ret < 0) {
|
|
decoding_error(path, f);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
ret = sgetb32(f, &v_pos);
|
|
if (ret < 0) {
|
|
decoding_error(path, f);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
/* read the uuid */
|
|
ret = sgetbs(f, uuid, sizeof(uuid));
|
|
if (ret < 0) {
|
|
decoding_error(path, f);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
map = map_alloc(buffer, v_pos, uuid);
|
|
|
|
tommy_list_insert_tail(&state->maplist, &map->node, map);
|
|
} 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) {
|
|
/* here don't call decoding_error() because it's too late to get the crc */
|
|
fprintf(stderr, "Error reading the CRC in '%s' at offset %"PRIi64"\n", path, stell(f));
|
|
fprintf(stderr, "This content file is damaged! Use an alternate copy.\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
if (crc_stored != crc_computed) {
|
|
/* here don't call decoding_error() because it's too late to get the crc */
|
|
fprintf(stderr, "Mismatching CRC in '%s'\n", path);
|
|
fprintf(stderr, "This content file is damaged! Use an alternate copy.\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
crc_checked = 1;
|
|
} else {
|
|
decoding_error(path, f);
|
|
fprintf(stderr, "Invalid command '%c'!\n", (char)c);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
}
|
|
|
|
if (serror(f)) {
|
|
fprintf(stderr, "Error reading the content file '%s' at offset %"PRIi64"\n", path, stell(f));
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
if (!crc_checked) {
|
|
fprintf(stderr, "Finished reading '%s' without finding the CRC\n", path);
|
|
fprintf(stderr, "This content file is truncated or damaged! Use an alternate copy.\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
/* check that the stored parity size matches the loaded state */
|
|
if (blockmax != parity_size(state)) {
|
|
fprintf(stderr, "Internal incosistency in parity size in '%s' at offset %"PRIi64"\n", path, stell(f));
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
state->loaded_blockmax = blockmax;
|
|
|
|
if (state->opt.verbose) {
|
|
printf("\tfile %u\n", count_file);
|
|
printf("\tblock %u\n", count_block);
|
|
printf("\thardlink %u\n", count_hardlink);
|
|
printf("\tsymlink %u\n", count_symlink);
|
|
printf("\temptydir %u\n", count_dir);
|
|
}
|
|
}
|
|
|
|
static void state_write_binary(struct snapraid_state* state, STREAM* f)
|
|
{
|
|
unsigned count_file;
|
|
unsigned count_block;
|
|
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;
|
|
|
|
count_file = 0;
|
|
count_block = 0;
|
|
count_hardlink = 0;
|
|
count_symlink = 0;
|
|
count_dir = 0;
|
|
|
|
/* blocks of all array */
|
|
blockmax = parity_size(state);
|
|
|
|
/* write header */
|
|
swrite("SNAPCNT1\n\3\0\0", 12, f);
|
|
|
|
/* 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 */
|
|
for(b=0;b<blockmax;++b) {
|
|
/* if the position is used */
|
|
if (is_block_used(state, b)) {
|
|
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);
|
|
}
|
|
}
|
|
|
|
sputc('z', f);
|
|
sputb32(state->block_size, f);
|
|
if (serror(f)) {
|
|
fprintf(stderr, "Error writing the content file '%s'. %s.\n", serrorfile(f), strerror(errno));
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
sputc('c', f);
|
|
if (state->hash == HASH_MURMUR3) {
|
|
sputc('u', f);
|
|
} else if (state->hash == HASH_SPOOKY2) {
|
|
sputc('k', f);
|
|
} else {
|
|
fprintf(stderr, "Unexpected hash when writing the content file '%s'.\n", serrorfile(f));
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
swrite(state->hashseed, HASH_SIZE, f);
|
|
if (serror(f)) {
|
|
fprintf(stderr, "Error writing the content file '%s'. %s.\n", serrorfile(f), strerror(errno));
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
/* 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->hash == HASH_SPOOKY2) {
|
|
sputc('k', f);
|
|
} else {
|
|
fprintf(stderr, "Unexpected prevhash when writing the content file '%s'.\n", serrorfile(f));
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
swrite(state->prevhashseed, HASH_SIZE, f);
|
|
if (serror(f)) {
|
|
fprintf(stderr, "Error writing the content file '%s'. %s.\n", serrorfile(f), strerror(errno));
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* for each map */
|
|
for(i=state->maplist;i!=0;i=i->next) {
|
|
struct snapraid_map* map = i->data;
|
|
sputc('m', f);
|
|
sputbs(map->name, f);
|
|
sputb32(map->position, f);
|
|
sputbs(map->uuid, f);
|
|
if (serror(f)) {
|
|
fprintf(stderr, "Error writing the content file '%s'. %s.\n", serrorfile(f), strerror(errno));
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
}
|
|
|
|
/* save the info */
|
|
sputc('i', f);
|
|
|
|
sputb32(info_oldest, f);
|
|
|
|
/* write blockmax, this is also going to be used in a lot of checks */
|
|
sputb32(blockmax, f);
|
|
|
|
/* write the info for each block */
|
|
begin = 0;
|
|
while (begin < blockmax) {
|
|
snapraid_info info;
|
|
block_off_t end;
|
|
time_t t;
|
|
unsigned flag;
|
|
|
|
info = info_get(&state->infoarr, begin);
|
|
|
|
/* find the end of run of blocks */
|
|
end = begin + 1;
|
|
while (end < blockmax
|
|
&& info == info_get(&state->infoarr, end))
|
|
{
|
|
++end;
|
|
}
|
|
|
|
sputb32(end - begin, f);
|
|
|
|
/* 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);
|
|
|
|
t = info_get_time(info) - info_oldest;
|
|
sputb32(t, f);
|
|
} else {
|
|
/* write a special 0 flag to mark missing info */
|
|
sputb32(0, f);
|
|
}
|
|
|
|
if (serror(f)) {
|
|
fprintf(stderr, "Error writing the content file '%s'. %s.\n", serrorfile(f), strerror(errno));
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
/* next begin position */
|
|
begin = end;
|
|
}
|
|
|
|
/* for each disk */
|
|
for(i=state->disklist;i!=0;i=i->next) {
|
|
tommy_node* j;
|
|
struct snapraid_disk* disk = i->data;
|
|
block_off_t blockdiskmax;
|
|
|
|
/* for each file */
|
|
for(j=disk->filelist;j!=0;j=j->next) {
|
|
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);
|
|
sputbs(disk->name, f);
|
|
sputb64(size, f);
|
|
sputb64(mtime_sec, f);
|
|
sputb32(mtime_nsec, f);
|
|
sputb64(inode, f);
|
|
sputbs(file->sub, f);
|
|
if (serror(f)) {
|
|
fprintf(stderr, "Error writing the content file '%s'. %s.\n", serrorfile(f), strerror(errno));
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
/* 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;
|
|
|
|
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)
|
|
&& blockvec[end-1].parity_pos + 1 == blockvec[end].parity_pos)
|
|
{
|
|
++end;
|
|
}
|
|
|
|
switch (block_state) {
|
|
case BLOCK_STATE_BLK :
|
|
sputc('b', f);
|
|
break;
|
|
case BLOCK_STATE_NEW :
|
|
sputc('n', f);
|
|
break;
|
|
case BLOCK_STATE_CHG :
|
|
sputc('g', f);
|
|
break;
|
|
default:
|
|
fprintf(stderr, "Internal state inconsistency in saving for block %u state %u\n", v_pos, block_state);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
sputb32(v_pos, f);
|
|
|
|
v_count = end - begin;
|
|
sputb32(v_count, f);
|
|
|
|
if (block_state != BLOCK_STATE_NEW) {
|
|
for(b=begin;b<end;++b) {
|
|
swrite(blockvec[b].hash, HASH_SIZE, f);
|
|
}
|
|
}
|
|
|
|
if (serror(f)) {
|
|
fprintf(stderr, "Error writing the content file '%s'. %s.\n", serrorfile(f), strerror(errno));
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
count_block += end - begin;
|
|
|
|
/* next begin position */
|
|
begin = end;
|
|
}
|
|
|
|
++count_file;
|
|
}
|
|
|
|
/* for each link */
|
|
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 :
|
|
sputc('a', f);
|
|
++count_hardlink;
|
|
break;
|
|
case FILE_IS_SYMLINK :
|
|
sputc('s', f);
|
|
++count_symlink;
|
|
break;
|
|
}
|
|
|
|
sputbs(disk->name, f);
|
|
sputbs(link->sub, f);
|
|
sputbs(link->linkto, f);
|
|
if (serror(f)) {
|
|
fprintf(stderr, "Error writing the content file '%s'. %s.\n", serrorfile(f), strerror(errno));
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
}
|
|
|
|
/* for each dir */
|
|
for(j=disk->dirlist;j!=0;j=j->next) {
|
|
struct snapraid_dir* dir = j->data;
|
|
|
|
sputc('r', f);
|
|
sputbs(disk->name, f);
|
|
sputbs(dir->sub, f);
|
|
if (serror(f)) {
|
|
fprintf(stderr, "Error writing the content file '%s'. %s.\n", serrorfile(f), strerror(errno));
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
++count_dir;
|
|
}
|
|
|
|
/* maximum block for this disk */
|
|
blockdiskmax = tommy_array_size(&disk->blockarr);
|
|
|
|
/* skip blocks over the end of the parity */
|
|
if (blockdiskmax > blockmax)
|
|
blockdiskmax = blockmax;
|
|
|
|
/* skip empty blocks at the end of the disk */
|
|
while (blockdiskmax > 0 && disk_block_get(disk, blockdiskmax - 1) == BLOCK_EMPTY)
|
|
--blockdiskmax;
|
|
|
|
/* write the hole list only if the disk is not completely empty */
|
|
/* this is required to write no reference to a removed disk */
|
|
if (blockdiskmax) {
|
|
/* write the list of deleted blocks */
|
|
sputc('h', f);
|
|
sputbs(disk->name, f);
|
|
sputb32(blockdiskmax, f);
|
|
|
|
if (serror(f)) {
|
|
fprintf(stderr, "Error writing the content file '%s'. %s.\n", serrorfile(f), strerror(errno));
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
/* write the deleted info for each block */
|
|
begin = 0;
|
|
while (begin < blockdiskmax) {
|
|
int is_deleted;
|
|
block_off_t end;
|
|
|
|
is_deleted = is_block_deleted(disk, begin);
|
|
|
|
/* find the end of run of blocks */
|
|
end = begin + 1;
|
|
while (end < blockdiskmax
|
|
&& is_deleted == is_block_deleted(disk, end))
|
|
{
|
|
++end;
|
|
}
|
|
|
|
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);
|
|
|
|
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;
|
|
}
|
|
|
|
if (serror(f)) {
|
|
fprintf(stderr, "Error writing the content file '%s'. %s.\n", serrorfile(f), strerror(errno));
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
sputc('N', f);
|
|
sputble32(scrc(f), f);
|
|
if (serror(f)) {
|
|
fprintf(stderr, "Error writing the content file '%s'. %s.\n", serrorfile(f), strerror(errno));
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
if (state->opt.verbose) {
|
|
printf("\tfile %u\n", count_file);
|
|
printf("\tblock %u\n", count_block);
|
|
printf("\thardlink %u\n", count_hardlink);
|
|
printf("\tsymlink %u\n", count_symlink);
|
|
printf("\temptydir %u\n", count_dir);
|
|
}
|
|
}
|
|
|
|
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);
|
|
|
|
if (state->opt.gui) {
|
|
fprintf(stdlog, "content:%s\n", path);
|
|
fflush(stdlog);
|
|
}
|
|
printf("Loading state from %s...\n", path);
|
|
|
|
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) {
|
|
fprintf(stderr, "Error opening the content file '%s'. %s.\n", path, strerror(errno));
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
/* otherwise continue */
|
|
if (node->next) {
|
|
fprintf(stderr, "warning: Content file '%s' not found, trying with another copy...\n", path);
|
|
}
|
|
}
|
|
|
|
node = node->next;
|
|
}
|
|
|
|
/* if not found, assume empty */
|
|
if (!f) {
|
|
/* create the initial mapping */
|
|
state_map(state);
|
|
|
|
fprintf(stderr, "warning: No content file found. Assuming empty.\n");
|
|
return;
|
|
}
|
|
|
|
/* get the date of the content file */
|
|
ret = fstat(shandle(f), &st);
|
|
if (ret != 0) {
|
|
fprintf(stderr, "Error stating the content file '%s'. %s.\n", path, strerror(errno));
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
/* 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;
|
|
memset(state->prevhashseed, 0, HASH_SIZE);
|
|
|
|
/* get the first char to detect the file type */
|
|
c = sgetc(f);
|
|
sungetc(c, f);
|
|
|
|
/* guess the file type from the first char */
|
|
if (c == 'S') {
|
|
state_read_binary(state, path, f);
|
|
} else {
|
|
state_read_text(state, path, f, st.st_mtime);
|
|
}
|
|
|
|
sclose(f);
|
|
|
|
if (state->hash == HASH_UNDEFINED) {
|
|
fprintf(stderr, "The checksum to use is not specified.\n");
|
|
fprintf(stderr, "This happens because you are likely upgrading from SnapRAID 1.0.\n");
|
|
fprintf(stderr, "To use a new SnapRAID you must restart from scratch,\n");
|
|
fprintf(stderr, "deleting all the content and parity files.\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
/* update the mapping */
|
|
state_map(state);
|
|
|
|
state_content_check(state, path);
|
|
}
|
|
|
|
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;
|
|
printf("Saving state to %s...\n", content->content);
|
|
++count_content;
|
|
i = i->next;
|
|
}
|
|
|
|
/* open all the content files */
|
|
f = sopen_multi_write(count_content);
|
|
if (!f) {
|
|
fprintf(stderr, "Error opening the content files.\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
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) {
|
|
fprintf(stderr, "Error opening the content file '%s'. %s.\n", tmp, strerror(errno));
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
++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) {
|
|
fprintf(stderr, "Error writing the content file '%s', in flush(). %s.\n", serrorfile(f), strerror(errno));
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
#if HAVE_FSYNC
|
|
if (ssync(f) != 0) {
|
|
fprintf(stderr, "Error writing the content file '%s' in sync(). %s.\n", serrorfile(f), strerror(errno));
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
#endif
|
|
|
|
if (sclose(f) != 0) {
|
|
fprintf(stderr, "Error writing the content file '%s' in close(). %s.\n", serrorfile(f), strerror(errno));
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
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 (rename(tmp, content->content) != 0) {
|
|
fprintf(stderr, "Error renaming the content file '%s' to '%s' in rename(). %s.\n", tmp, content->content, strerror(errno));
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
i = i->next;
|
|
}
|
|
|
|
state->need_write = 0; /* no write needed anymore */
|
|
}
|
|
|
|
void state_filter(struct snapraid_state* state, tommy_list* filterlist_file, tommy_list* filterlist_disk, int filter_missing, int filter_error)
|
|
{
|
|
tommy_node* i;
|
|
|
|
/* if no filter, include all */
|
|
if (!filter_missing && !filter_error && tommy_list_empty(filterlist_file) && tommy_list_empty(filterlist_disk))
|
|
return;
|
|
|
|
printf("Filtering...\n");
|
|
|
|
if (state->opt.verbose) {
|
|
tommy_node* k;
|
|
for(k=tommy_list_head(filterlist_disk);k!=0;k=k->next) {
|
|
struct snapraid_filter* filter = k->data;
|
|
printf("\t%s", filter->pattern);
|
|
if (filter->is_disk)
|
|
printf("//");
|
|
printf("\n");
|
|
}
|
|
for(k=tommy_list_head(filterlist_file);k!=0;k=k->next) {
|
|
struct snapraid_filter* filter = k->data;
|
|
printf("\t%s", filter->pattern);
|
|
if (filter->is_dir)
|
|
printf("/");
|
|
printf("\n");
|
|
}
|
|
if (filter_missing)
|
|
printf("\t<missing>\n");
|
|
if (filter_error)
|
|
printf("\t<error>\n");
|
|
}
|
|
|
|
/* for each disk */
|
|
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)
|
|
printf("Scanning disk %s...\n", disk->name);
|
|
|
|
/* for each file */
|
|
for(j=tommy_list_head(&disk->filelist);j!=0;j=j->next) {
|
|
struct snapraid_file* file = j->data;
|
|
|
|
if (filter_path(filterlist_disk, disk->name, file->sub) != 0
|
|
|| filter_path(filterlist_file, disk->name, file->sub) != 0
|
|
|| filter_existence(filter_missing, disk->dir, file->sub) != 0
|
|
|| filter_correctness(filter_error, &state->infoarr, file) != 0
|
|
) {
|
|
file_flag_set(file, FILE_IS_EXCLUDED);
|
|
}
|
|
}
|
|
|
|
/* for each link */
|
|
for(j=tommy_list_head(&disk->linklist);j!=0;j=j->next) {
|
|
struct snapraid_link* link = j->data;
|
|
|
|
if (filter_path(filterlist_disk, disk->name, link->sub) != 0
|
|
|| filter_path(filterlist_file, disk->name, link->sub) != 0
|
|
|| filter_existence(filter_missing, disk->dir, link->sub) != 0
|
|
) {
|
|
link_flag_set(link, FILE_IS_EXCLUDED);
|
|
}
|
|
}
|
|
|
|
/* for each dir */
|
|
for(j=tommy_list_head(&disk->dirlist);j!=0;j=j->next) {
|
|
struct snapraid_dir* dir = j->data;
|
|
|
|
if (filter_dir(filterlist_disk, disk->name, dir->sub) != 0
|
|
|| filter_dir(filterlist_file, 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)
|
|
{
|
|
if (state->opt.gui) {
|
|
fprintf(stdlog,"run:begin:%u:%u:%u\n", blockstart, blockmax, countmax);
|
|
fflush(stdlog);
|
|
} else {
|
|
time_t now;
|
|
|
|
now = time(0);
|
|
|
|
state->progress_start = now;
|
|
state->progress_last = now;
|
|
state->progress_subtract = 0;
|
|
}
|
|
|
|
/* stop if requested */
|
|
if (global_interrupt) {
|
|
if (!state->opt.gui) {
|
|
printf("Not starting for interruption\n");
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
void state_progress_end(struct snapraid_state* state, block_off_t countpos, block_off_t countmax, data_off_t countsize)
|
|
{
|
|
if (state->opt.gui) {
|
|
fprintf(stdlog, "run:end\n");
|
|
fflush(stdlog);
|
|
} else {
|
|
unsigned countsize_MiB = (countsize + 1024*1024 - 1) / (1024*1024);
|
|
|
|
if (countmax)
|
|
printf("%u%% completed, %u MiB processed\n", countpos * 100 / countmax, countsize_MiB);
|
|
else
|
|
printf("Nothing to do\n");
|
|
}
|
|
}
|
|
|
|
#define PROGRESS_CLEAR " "
|
|
|
|
void state_progress_stop(struct snapraid_state* state)
|
|
{
|
|
time_t now;
|
|
|
|
now = time(0);
|
|
|
|
printf("\n");
|
|
|
|
state->progress_interruption = now;
|
|
}
|
|
|
|
void state_progress_restart(struct snapraid_state* state)
|
|
{
|
|
time_t now;
|
|
|
|
now = time(0);
|
|
|
|
if (now >= state->progress_interruption) /* avoid degenerated cases when the clock is manually adjusted */
|
|
state->progress_subtract += now - state->progress_interruption;
|
|
}
|
|
|
|
int state_progress(struct snapraid_state* state, block_off_t blockpos, block_off_t countpos, block_off_t countmax, data_off_t countsize)
|
|
{
|
|
if (state->opt.gui) {
|
|
fprintf(stdlog, "run:pos:%u:%u:%"PRIu64"\n", blockpos, countpos, countsize);
|
|
fflush(stdlog);
|
|
} else {
|
|
time_t now;
|
|
|
|
now = time(0);
|
|
|
|
if (state->progress_last != now) {
|
|
time_t delta = now - state->progress_start - state->progress_subtract;
|
|
|
|
printf("%u%%, %u MiB", countpos * 100 / countmax, (unsigned)(countsize / (1024*1024)));
|
|
|
|
if (delta != 0) {
|
|
printf(", %u MiB/s", (unsigned)(countsize / (1024*1024) / delta));
|
|
}
|
|
|
|
if (delta > 5 && countpos > 0) {
|
|
unsigned m, h;
|
|
data_off_t to_do = countmax - countpos;
|
|
|
|
m = to_do * delta / (60 * countpos);
|
|
|
|
h = m / 60;
|
|
m = m % 60;
|
|
|
|
printf(", %u:%02u ETA%s", h, m, PROGRESS_CLEAR);
|
|
}
|
|
printf("\r");
|
|
fflush(stdout);
|
|
state->progress_last = now;
|
|
}
|
|
}
|
|
|
|
/* stop if requested */
|
|
if (global_interrupt) {
|
|
if (!state->opt.gui) {
|
|
printf("\n");
|
|
printf("Stopping for interruption at block %u\n", blockpos);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|