snapraid/cmdline/snapraid.c
2026-07-13 11:03:55 +02:00

2127 lines
66 KiB
C

// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (C) 2011 Andrea Mazzoleni
#include "portable.h"
#include "snapraid.h"
#include "support.h"
#include "elem.h"
#include "import.h"
#include "search.h"
#include "state.h"
#include "io.h"
#include "raid/raid.h"
#include "locate.h"
/****************************************************************************/
/* misc */
void version(void)
{
msg_status(PACKAGE_NAME " v" VERSION " by Andrea Mazzoleni, " PACKAGE_URL "\n");
}
void usage(const char* conf)
{
version();
printf("Usage: " PACKAGE " status|diff|sync|scrub|list|dup|up|down|probe|touch|smart|pool|check|fix [options]\n");
printf("\n");
printf("Commands:\n");
printf(" status Print the status of the array\n");
printf(" diff Show the changes that needs to be synchronized\n");
printf(" sync Synchronize the state of the array\n");
printf(" scrub Scrub the array\n");
printf(" list List the array content\n");
printf(" dup Find duplicate files\n");
printf(" up Spin-up the array\n");
printf(" down Spin-down the array\n");
printf(" probe Show the spinning status of all disks\n");
printf(" touch Add non-zero ns timestamps to files\n");
printf(" smart SMART attributes of the array\n");
printf(" pool Create or update the virtual view of the array\n");
printf(" check Check the array\n");
printf(" fix Fix the array\n");
printf("\n");
printf("Options:\n");
printf(" " SWITCH_GETOPT_LONG("-c, --conf FILE ", "-c") " Configuration file\n");
printf(" " SWITCH_GETOPT_LONG("-f, --filter PATTERN ", "-f") " Process only files matching the pattern\n");
printf(" " SWITCH_GETOPT_LONG("-d, --filter-disk NAME", "-d") " Process only files in the specified disk\n");
printf(" " SWITCH_GETOPT_LONG("-m, --filter-missing ", "-m") " Process only missing/deleted files\n");
printf(" " SWITCH_GETOPT_LONG("-e, --filter-error ", "-e") " Process only files with errors\n");
printf(" " SWITCH_GETOPT_LONG("-p, --plan PLAN ", "-p") " Define a scrub plan or percentage\n");
printf(" " SWITCH_GETOPT_LONG("-o, --older-than DAYS ", "-o") " Process only the older part of the array\n");
printf(" " SWITCH_GETOPT_LONG("-i, --import DIR ", "-i") " Import deleted files\n");
printf(" " SWITCH_GETOPT_LONG("-l, --log FILE ", "-l") " Log file. Default none\n");
printf(" " SWITCH_GETOPT_LONG("-a, --audit-only ", "-a") " Check only file data and not parity\n");
printf(" " SWITCH_GETOPT_LONG("-h, --pre-hash ", "-h") " Pre-hash all the new data\n");
printf(" " SWITCH_GETOPT_LONG("-Z, --force-zero ", "-Z") " Force syncing of files that get zero size\n");
printf(" " SWITCH_GETOPT_LONG("-E, --force-empty ", "-E") " Force syncing of disks that get empty\n");
printf(" " SWITCH_GETOPT_LONG("-U, --force-uuid ", "-U") " Force commands on disks with uuid changed\n");
printf(" " SWITCH_GETOPT_LONG("-D, --force-device ", "-D") " Force commands with inaccessible/shared disks\n");
printf(" " SWITCH_GETOPT_LONG("-N, --force-nocopy ", "-N") " Force commands disabling the copy detection\n");
/* --force-full, --force-realloc and --force-realloc-tail are not listed as they are dangerous */
printf(" " SWITCH_GETOPT_LONG("-w, --bw-limit RATE ", "-w") " Limit IO bandwidth (M|G)\n");
printf(" " SWITCH_GETOPT_LONG("-v, --verbose ", "-v") " Verbose\n");
printf("\n");
printf("Configuration file: %s\n", conf);
printf("\n");
}
void memory(void)
{
log_tag("memory:used:%" PRIu64 "\n", (uint64_t)malloc_counter_get());
/* size of the block */
log_tag("memory:block:%" PRIu64 "\n", (uint64_t)(sizeof(struct snapraid_block)));
log_tag("memory:extent:%" PRIu64 "\n", (uint64_t)(sizeof(struct snapraid_extent)));
log_tag("memory:file:%" PRIu64 "\n", (uint64_t)(sizeof(struct snapraid_file)));
log_tag("memory:link:%" PRIu64 "\n", (uint64_t)(sizeof(struct snapraid_link)));
log_tag("memory:dir:%" PRIu64 "\n", (uint64_t)(sizeof(struct snapraid_dir)));
msg_progress("Using %u MiB of memory for the file-system.\n", (unsigned)(malloc_counter_get() / MEBI));
}
void test(int argc, char* argv[])
{
int i;
char buffer[ESC_MAX];
/* special testing code */
if (argc < 2 || strcmp(argv[1], "test") != 0)
return;
lock_init();
assert(strcmp(strpolish(strcpy(buffer, "\r \n\xFF")), " ") == 0);
assert(strcmp(strtrim(strcpy(buffer, " trim trim \n\r")), "trim trim") == 0);
assert(strcmp(strlwr(strcpy(buffer, " LoWer\n\r")), " lower\n\r") == 0);
assert(worddigitstr("longneedlestring", "needle") == 0);
assert(worddigitstr("longneedlestring", "") == 0);
assert(worddigitstr("long needle string", "needle") != 0);
assert(worddigitstr("long1needle2string", "needle") != 0);
assert(worddigitstr("long\rneedle3string", "needle") != 0);
assert(worddigitstr("long1needle", "needle") != 0);
assert(worddigitstr("needle2string", "needle") != 0);
assert(worddigitstr("needle", "needle") != 0);
assert(strcmp(esc_tag("simple"), "simple") == 0);
assert(strcmp(esc_tag("line1\nline2"), "line1\\nline2") == 0);
assert(strcmp(esc_tag("line1\rline2"), "line1\\rline2") == 0);
assert(strcmp(esc_tag("key:value"), "key\\dvalue") == 0);
assert(strcmp(esc_tag("C:\\path\\file"), "C\\d\\\\path\\\\file") == 0);
assert(strcmp(esc_tag("A\nB\rC:D\\E"), "A\\nB\\rC\\dD\\\\E") == 0);
assert(strcmp(esc_tag("endwith\\"), "endwith\\\\") == 0);
assert(strcmp(esc_tag(""), "") == 0);
assert(strcmp(esc_tag("\n\r:\\\\"), "\\n\\r\\d\\\\\\\\") == 0);
for (i = 2; i < argc; ++i) {
printf("argv[%d]\n", i);
printf("\t#%s#\n", argv[i]);
printf("\t#%s#\n", esc_shell(argv[i], buffer));
}
#ifdef _WIN32
/* basic cases - no special characters, no quotes needed */
assert(strcmp(esc_shell("simple", buffer), "simple") == 0);
assert(strcmp(esc_shell("file.txt", buffer), "file.txt") == 0);
assert(strcmp(esc_shell("file123", buffer), "file123") == 0);
assert(strcmp(esc_shell("file_name-test.doc", buffer), "file_name-test.doc") == 0);
assert(strcmp(esc_shell(",._+:@/-", buffer), ",._+:@/-") == 0);
assert(strcmp(esc_shell("C:\\Users\\test", buffer), "C:\\Users\\test") == 0);
/* space - requires quoting */
assert(strcmp(esc_shell(" ", buffer), "\" \"") == 0);
assert(strcmp(esc_shell("file name.txt", buffer), "\"file name.txt\"") == 0);
assert(strcmp(esc_shell("my document.doc", buffer), "\"my document.doc\"") == 0);
assert(strcmp(esc_shell(" multiple spaces ", buffer), "\" multiple spaces \"") == 0);
/* tab - requires quoting */
assert(strcmp(esc_shell("\t", buffer), "\"\t\"") == 0);
assert(strcmp(esc_shell("file\tname", buffer), "\"file\tname\"") == 0);
/* newline - requires quoting */
assert(strcmp(esc_shell("\n", buffer), "\"\n\"") == 0);
assert(strcmp(esc_shell("line1\nline2", buffer), "\"line1\nline2\"") == 0);
/* carriage return - requires quoting */
assert(strcmp(esc_shell("\r", buffer), "\"\r\"") == 0);
assert(strcmp(esc_shell("text\r\n", buffer), "\"text\r\n\"") == 0);
/* double quote - requires quoting and escaping with backslash */
assert(strcmp(esc_shell("\"", buffer), "\"\\\"\"") == 0);
assert(strcmp(esc_shell("file\"name", buffer), "\"file\\\"name\"") == 0);
assert(strcmp(esc_shell("\"quoted\"", buffer), "\"\\\"quoted\\\"\"") == 0);
assert(strcmp(esc_shell("say \"hello\"", buffer), "\"say \\\"hello\\\"\"") == 0);
/* ampersand - requires quoting */
assert(strcmp(esc_shell("&", buffer), "\"&\"") == 0);
assert(strcmp(esc_shell("file&name", buffer), "\"file&name\"") == 0);
assert(strcmp(esc_shell("a&b&c", buffer), "\"a&b&c\"") == 0);
assert(strcmp(esc_shell("file & name", buffer), "\"file & name\"") == 0);
/* pipe - requires quoting */
assert(strcmp(esc_shell("|", buffer), "\"|\"") == 0);
assert(strcmp(esc_shell("file|name", buffer), "\"file|name\"") == 0);
assert(strcmp(esc_shell("a | b", buffer), "\"a | b\"") == 0);
/* parentheses - requires quoting */
assert(strcmp(esc_shell("(", buffer), "\"(\"") == 0);
assert(strcmp(esc_shell(")", buffer), "\")\"") == 0);
assert(strcmp(esc_shell("(test)", buffer), "\"(test)\"") == 0);
assert(strcmp(esc_shell("file (1)", buffer), "\"file (1)\"") == 0);
assert(strcmp(esc_shell("file(copy)", buffer), "\"file(copy)\"") == 0);
/* angle brackets - requires quoting */
assert(strcmp(esc_shell("<", buffer), "\"<\"") == 0);
assert(strcmp(esc_shell(">", buffer), "\">\"") == 0);
assert(strcmp(esc_shell("a<b>c", buffer), "\"a<b>c\"") == 0);
assert(strcmp(esc_shell("file > output", buffer), "\"file > output\"") == 0);
/* caret - requires quoting */
assert(strcmp(esc_shell("^", buffer), "\"^\"") == 0);
assert(strcmp(esc_shell("test^test", buffer), "\"test^test\"") == 0);
assert(strcmp(esc_shell("a ^ b", buffer), "\"a ^ b\"") == 0);
/* multiple special chars - requires quoting */
assert(strcmp(esc_shell("&|()<>^", buffer), "\"&|()<>^\"") == 0);
assert(strcmp(esc_shell("test&|test", buffer), "\"test&|test\"") == 0);
assert(strcmp(esc_shell("a & b | c", buffer), "\"a & b | c\"") == 0);
/* percent sign - requires quoting */
assert(strcmp(esc_shell("%", buffer), "\"%\"") == 0);
assert(strcmp(esc_shell("%%", buffer), "\"%%\"") == 0);
assert(strcmp(esc_shell("%PATH%", buffer), "\"%PATH%\"") == 0);
assert(strcmp(esc_shell("test%var%test", buffer), "\"test%var%test\"") == 0);
assert(strcmp(esc_shell("%PATH% file", buffer), "\"%PATH% file\"") == 0);
/* exclamation mark - requires quoting */
assert(strcmp(esc_shell("!", buffer), "\"!\"") == 0);
assert(strcmp(esc_shell("!VAR!", buffer), "\"!VAR!\"") == 0);
assert(strcmp(esc_shell("test!test", buffer), "\"test!test\"") == 0);
assert(strcmp(esc_shell("hello !world!", buffer), "\"hello !world!\"") == 0);
/* equals sign - requires quoting */
assert(strcmp(esc_shell("=", buffer), "\"=\"") == 0);
assert(strcmp(esc_shell("VAR=value", buffer), "\"VAR=value\"") == 0);
assert(strcmp(esc_shell("a=b", buffer), "\"a=b\"") == 0);
/* semicolon - requires quoting */
assert(strcmp(esc_shell(";", buffer), "\";\"") == 0);
assert(strcmp(esc_shell("cmd1;cmd2", buffer), "\"cmd1;cmd2\"") == 0);
/* backslash - no quotes needed when alone or in path */
assert(strcmp(esc_shell("\\", buffer), "\\") == 0);
assert(strcmp(esc_shell("C:\\", buffer), "C:\\") == 0);
assert(strcmp(esc_shell("C:\\Users", buffer), "C:\\Users") == 0);
assert(strcmp(esc_shell("path\\to\\file", buffer), "path\\to\\file") == 0);
assert(strcmp(esc_shell("C:\\folder\\", buffer), "C:\\folder\\") == 0);
/* backslash with space - requires quoting, normal backslash inside */
assert(strcmp(esc_shell("\\ ", buffer), "\"\\ \"") == 0);
assert(strcmp(esc_shell("C:\\ ", buffer), "\"C:\\ \"") == 0);
assert(strcmp(esc_shell("C:\\Program Files", buffer), "\"C:\\Program Files\"") == 0);
/* trailing backslash with quotes - backslashes before closing quote must be doubled */
assert(strcmp(esc_shell("C:\\folder\\ ", buffer), "\"C:\\folder\\ \"") == 0);
assert(strcmp(esc_shell("path\\ ", buffer), "\"path\\ \"") == 0);
assert(strcmp(esc_shell("C:\\My Documents\\", buffer), "\"C:\\My Documents\\\\\"") == 0);
/* backslash before embedded quote - backslash before quote must be doubled */
assert(strcmp(esc_shell("C:\\\"test\"", buffer), "\"C:\\\\\\\"test\\\"\"") == 0);
assert(strcmp(esc_shell("path\\\"file\"", buffer), "\"path\\\\\\\"file\\\"\"") == 0);
/* multiple trailing backslashes before end with quotes */
assert(strcmp(esc_shell("test\\\\ ", buffer), "\"test\\\\ \"") == 0);
assert(strcmp(esc_shell("path\\\\\\\\ ", buffer), "\"path\\\\\\\\ \"") == 0);
/* backslash NOT before quote - normal backslash */
assert(strcmp(esc_shell("test\\file ", buffer), "\"test\\file \"") == 0);
assert(strcmp(esc_shell("a\\b c", buffer), "\"a\\b c\"") == 0);
/* control characters - require quoting */
assert(strcmp(esc_shell("\x01", buffer), "\"\x01\"") == 0);
assert(strcmp(esc_shell("\x1F", buffer), "\"\x1F\"") == 0);
assert(strcmp(esc_shell("\x7F", buffer), "\"\x7F\"") == 0); /* DEL character */
assert(strcmp(esc_shell("test\x01test", buffer), "\"test\x01test\"") == 0);
/* complex real-world examples */
assert(strcmp(esc_shell("C:\\Program Files\\App", buffer), "\"C:\\Program Files\\App\"") == 0);
assert(strcmp(esc_shell("C:\\Program Files (x86)\\", buffer), "\"C:\\Program Files (x86)\\\\\"") == 0);
assert(strcmp(esc_shell("file (copy).txt", buffer), "\"file (copy).txt\"") == 0);
assert(strcmp(esc_shell("setup-v1.0.exe", buffer), "setup-v1.0.exe") == 0);
assert(strcmp(esc_shell("setup v1.0.exe", buffer), "\"setup v1.0.exe\"") == 0);
/* mixed quotes and special chars */
assert(strcmp(esc_shell("say \"hi\" & exit", buffer), "\"say \\\"hi\\\" & exit\"") == 0);
assert(strcmp(esc_shell("test \"a|b\"", buffer), "\"test \\\"a|b\\\"\"") == 0);
/* empty string */
assert(strcmp(esc_shell("", buffer), "") == 0);
/* all safe characters that don't need escaping */
assert(strcmp(esc_shell("abcdefghijklmnopqrstuvwxyz", buffer), "abcdefghijklmnopqrstuvwxyz") == 0);
assert(strcmp(esc_shell("ABCDEFGHIJKLMNOPQRSTUVWXYZ", buffer), "ABCDEFGHIJKLMNOPQRSTUVWXYZ") == 0);
assert(strcmp(esc_shell("0123456789", buffer), "0123456789") == 0);
assert(strcmp(esc_shell("._-+,@:", buffer), "._-+,@:") == 0);
#else
/* basic cases - no special characters */
assert(strcmp(esc_shell("simple", buffer), "simple") == 0);
assert(strcmp(esc_shell("file.txt", buffer), "file.txt") == 0);
assert(strcmp(esc_shell("file123", buffer), "file123") == 0);
assert(strcmp(esc_shell("file_name-test.doc", buffer), "file_name-test.doc") == 0);
assert(strcmp(esc_shell(",._+:@/-", buffer), ",._+:@/-") == 0);
assert(strcmp(esc_shell("/usr/local/bin", buffer), "/usr/local/bin") == 0);
/* empty string */
assert(strcmp(esc_shell("", buffer), "") == 0);
/* space - escape with backslash */
assert(strcmp(esc_shell(" ", buffer), "\\ ") == 0);
assert(strcmp(esc_shell("file name.txt", buffer), "file\\ name.txt") == 0);
assert(strcmp(esc_shell("my document.doc", buffer), "my\\ document.doc") == 0);
assert(strcmp(esc_shell(" spaces ", buffer), "\\ \\ spaces\\ \\ ") == 0);
assert(strcmp(esc_shell("a b c", buffer), "a\\ b\\ c") == 0);
/* tab - escape with backslash */
assert(strcmp(esc_shell("\t", buffer), "\\\t") == 0);
assert(strcmp(esc_shell("file\tname", buffer), "file\\\tname") == 0);
assert(strcmp(esc_shell("\t\t", buffer), "\\\t\\\t") == 0);
/* newline - escape with backslash */
assert(strcmp(esc_shell("\n", buffer), "\\\n") == 0);
assert(strcmp(esc_shell("line1\nline2", buffer), "line1\\\nline2") == 0);
assert(strcmp(esc_shell("\n\n", buffer), "\\\n\\\n") == 0);
/* carriage return - escape with backslash */
assert(strcmp(esc_shell("\r", buffer), "\\\r") == 0);
assert(strcmp(esc_shell("text\r\n", buffer), "text\\\r\\\n") == 0);
/* tilde (home directory expansion) */
assert(strcmp(esc_shell("~", buffer), "\\~") == 0);
assert(strcmp(esc_shell("~/file", buffer), "\\~/file") == 0);
assert(strcmp(esc_shell("file~name", buffer), "file\\~name") == 0);
assert(strcmp(esc_shell("~user", buffer), "\\~user") == 0);
/* backtick (command substitution) */
assert(strcmp(esc_shell("`", buffer), "\\`") == 0);
assert(strcmp(esc_shell("`command`", buffer), "\\`command\\`") == 0);
assert(strcmp(esc_shell("test`test", buffer), "test\\`test") == 0);
assert(strcmp(esc_shell("``", buffer), "\\`\\`") == 0);
/* hash (comment) */
assert(strcmp(esc_shell("#", buffer), "\\#") == 0);
assert(strcmp(esc_shell("#comment", buffer), "\\#comment") == 0);
assert(strcmp(esc_shell("file#name", buffer), "file\\#name") == 0);
assert(strcmp(esc_shell("test#123", buffer), "test\\#123") == 0);
/* dollar sign (variable expansion) */
assert(strcmp(esc_shell("$", buffer), "\\$") == 0);
assert(strcmp(esc_shell("$$", buffer), "\\$\\$") == 0);
assert(strcmp(esc_shell("$VAR", buffer), "\\$VAR") == 0);
assert(strcmp(esc_shell("${VAR}", buffer), "\\$\\{VAR\\}") == 0);
assert(strcmp(esc_shell("test$test", buffer), "test\\$test") == 0);
assert(strcmp(esc_shell("$1", buffer), "\\$1") == 0);
assert(strcmp(esc_shell("$PATH", buffer), "\\$PATH") == 0);
/* ampersand (background job) */
assert(strcmp(esc_shell("&", buffer), "\\&") == 0);
assert(strcmp(esc_shell("&&", buffer), "\\&\\&") == 0);
assert(strcmp(esc_shell("file&name", buffer), "file\\&name") == 0);
assert(strcmp(esc_shell("a&b&c", buffer), "a\\&b\\&c") == 0);
assert(strcmp(esc_shell("cmd1 & cmd2", buffer), "cmd1\\ \\&\\ cmd2") == 0);
/* asterisk (wildcard) */
assert(strcmp(esc_shell("*", buffer), "\\*") == 0);
assert(strcmp(esc_shell("**", buffer), "\\*\\*") == 0);
assert(strcmp(esc_shell("*.txt", buffer), "\\*.txt") == 0);
assert(strcmp(esc_shell("file*name", buffer), "file\\*name") == 0);
assert(strcmp(esc_shell("test*", buffer), "test\\*") == 0);
/* parentheses (subshell) */
assert(strcmp(esc_shell("(", buffer), "\\(") == 0);
assert(strcmp(esc_shell(")", buffer), "\\)") == 0);
assert(strcmp(esc_shell("()", buffer), "\\(\\)") == 0);
assert(strcmp(esc_shell("(test)", buffer), "\\(test\\)") == 0);
assert(strcmp(esc_shell("file(1)", buffer), "file\\(1\\)") == 0);
assert(strcmp(esc_shell("(a)(b)", buffer), "\\(a\\)\\(b\\)") == 0);
/* backslash (escape character) */
assert(strcmp(esc_shell("\\", buffer), "\\\\") == 0);
assert(strcmp(esc_shell("\\\\", buffer), "\\\\\\\\") == 0);
assert(strcmp(esc_shell("path\\to\\file", buffer), "path\\\\to\\\\file") == 0);
assert(strcmp(esc_shell("test\\test", buffer), "test\\\\test") == 0);
assert(strcmp(esc_shell("a\\b\\c", buffer), "a\\\\b\\\\c") == 0);
/* pipe (pipeline) */
assert(strcmp(esc_shell("|", buffer), "\\|") == 0);
assert(strcmp(esc_shell("||", buffer), "\\|\\|") == 0);
assert(strcmp(esc_shell("file|name", buffer), "file\\|name") == 0);
assert(strcmp(esc_shell("a|b|c", buffer), "a\\|b\\|c") == 0);
assert(strcmp(esc_shell("cmd1 | cmd2", buffer), "cmd1\\ \\|\\ cmd2") == 0);
/* square brackets (wildcard) */
assert(strcmp(esc_shell("[", buffer), "\\[") == 0);
assert(strcmp(esc_shell("]", buffer), "\\]") == 0);
assert(strcmp(esc_shell("[]", buffer), "\\[\\]") == 0);
assert(strcmp(esc_shell("[abc]", buffer), "\\[abc\\]") == 0);
assert(strcmp(esc_shell("file[1]", buffer), "file\\[1\\]") == 0);
assert(strcmp(esc_shell("[0-9]", buffer), "\\[0-9\\]") == 0);
/* curly braces (brace expansion) */
assert(strcmp(esc_shell("{", buffer), "\\{") == 0);
assert(strcmp(esc_shell("}", buffer), "\\}") == 0);
assert(strcmp(esc_shell("{}", buffer), "\\{\\}") == 0);
assert(strcmp(esc_shell("{a,b,c}", buffer), "\\{a,b,c\\}") == 0);
assert(strcmp(esc_shell("file{1,2}", buffer), "file\\{1,2\\}") == 0);
assert(strcmp(esc_shell("{1..10}", buffer), "\\{1..10\\}") == 0);
/* semicolon (command separator) */
assert(strcmp(esc_shell(";", buffer), "\\;") == 0);
assert(strcmp(esc_shell(";;", buffer), "\\;\\;") == 0);
assert(strcmp(esc_shell("cmd1;cmd2", buffer), "cmd1\\;cmd2") == 0);
assert(strcmp(esc_shell("test;test", buffer), "test\\;test") == 0);
assert(strcmp(esc_shell("a; b", buffer), "a\\;\\ b") == 0);
/* single quote */
assert(strcmp(esc_shell("'", buffer), "\\'") == 0);
assert(strcmp(esc_shell("''", buffer), "\\'\\'") == 0);
assert(strcmp(esc_shell("'test'", buffer), "\\'test\\'") == 0);
assert(strcmp(esc_shell("file'name", buffer), "file\\'name") == 0);
assert(strcmp(esc_shell("it's", buffer), "it\\'s") == 0);
/* double quote */
assert(strcmp(esc_shell("\"", buffer), "\\\"") == 0);
assert(strcmp(esc_shell("\"\"", buffer), "\\\"\\\"") == 0);
assert(strcmp(esc_shell("\"test\"", buffer), "\\\"test\\\"") == 0);
assert(strcmp(esc_shell("file\"name", buffer), "file\\\"name") == 0);
assert(strcmp(esc_shell("say \"hi\"", buffer), "say\\ \\\"hi\\\"") == 0);
/* angle brackets (redirection) */
assert(strcmp(esc_shell("<", buffer), "\\<") == 0);
assert(strcmp(esc_shell(">", buffer), "\\>") == 0);
assert(strcmp(esc_shell("<<", buffer), "\\<\\<") == 0);
assert(strcmp(esc_shell(">>", buffer), "\\>\\>") == 0);
assert(strcmp(esc_shell("a<b>c", buffer), "a\\<b\\>c") == 0);
assert(strcmp(esc_shell("file>output", buffer), "file\\>output") == 0);
assert(strcmp(esc_shell("cmd < in > out", buffer), "cmd\\ \\<\\ in\\ \\>\\ out") == 0);
/* question mark (wildcard) */
assert(strcmp(esc_shell("?", buffer), "\\?") == 0);
assert(strcmp(esc_shell("??", buffer), "\\?\\?") == 0);
assert(strcmp(esc_shell("file?.txt", buffer), "file\\?.txt") == 0);
assert(strcmp(esc_shell("test?test", buffer), "test\\?test") == 0);
assert(strcmp(esc_shell("file??", buffer), "file\\?\\?") == 0);
/* equals sign (assignment in some contexts) */
assert(strcmp(esc_shell("=", buffer), "\\=") == 0);
assert(strcmp(esc_shell("==", buffer), "\\=\\=") == 0);
assert(strcmp(esc_shell("VAR=value", buffer), "VAR\\=value") == 0);
assert(strcmp(esc_shell("a=b", buffer), "a\\=b") == 0);
assert(strcmp(esc_shell("PATH=/usr/bin", buffer), "PATH\\=/usr/bin") == 0);
/* exclamation mark (history expansion) */
assert(strcmp(esc_shell("!", buffer), "\\!") == 0);
assert(strcmp(esc_shell("!!", buffer), "\\!\\!") == 0);
assert(strcmp(esc_shell("test!test", buffer), "test\\!test") == 0);
assert(strcmp(esc_shell("!$", buffer), "\\!\\$") == 0);
assert(strcmp(esc_shell("!123", buffer), "\\!123") == 0);
/* control characters (0x01-0x1F) - escape with backslash */
assert(strcmp(esc_shell("\x01", buffer), "\\\x01") == 0);
assert(strcmp(esc_shell("\x02", buffer), "\\\x02") == 0);
assert(strcmp(esc_shell("\x1F", buffer), "\\\x1F") == 0);
assert(strcmp(esc_shell("test\x01test", buffer), "test\\\x01test") == 0);
/* DEL character (0x7F) */
assert(strcmp(esc_shell("\x7F", buffer), "\\\x7F") == 0);
assert(strcmp(esc_shell("test\x7Ftest", buffer), "test\\\x7Ftest") == 0);
/* multiple special characters combined */
assert(strcmp(esc_shell("$VAR & $OTHER", buffer), "\\$VAR\\ \\&\\ \\$OTHER") == 0);
assert(strcmp(esc_shell("*.txt | grep test", buffer), "\\*.txt\\ \\|\\ grep\\ test") == 0);
assert(strcmp(esc_shell("file (1) [copy].txt", buffer), "file\\ \\(1\\)\\ \\[copy\\].txt") == 0);
assert(strcmp(esc_shell("a & b | c", buffer), "a\\ \\&\\ b\\ \\|\\ c") == 0);
assert(strcmp(esc_shell("cmd1; cmd2 && cmd3", buffer), "cmd1\\;\\ cmd2\\ \\&\\&\\ cmd3") == 0);
/* complex real-world examples */
assert(strcmp(esc_shell("/home/user/My Documents", buffer), "/home/user/My\\ Documents") == 0);
assert(strcmp(esc_shell("/path/to/file (copy).txt", buffer), "/path/to/file\\ \\(copy\\).txt") == 0);
assert(strcmp(esc_shell("~/project/file-v1.0.tar.gz", buffer), "\\~/project/file-v1.0.tar.gz") == 0);
assert(strcmp(esc_shell("$(whoami)@$(hostname)", buffer), "\\$\\(whoami\\)@\\$\\(hostname\\)") == 0);
assert(strcmp(esc_shell("test && echo 'done'", buffer), "test\\ \\&\\&\\ echo\\ \\'done\\'") == 0);
assert(strcmp(esc_shell("file #1 [important].txt", buffer), "file\\ \\#1\\ \\[important\\].txt") == 0);
assert(strcmp(esc_shell("/tmp/test (1).txt", buffer), "/tmp/test\\ \\(1\\).txt") == 0);
assert(strcmp(esc_shell("var=$HOME/bin:$PATH", buffer), "var\\=\\$HOME/bin:\\$PATH") == 0);
/* edge cases with multiple escapes */
assert(strcmp(esc_shell("a\\ b", buffer), "a\\\\\\ b") == 0);
assert(strcmp(esc_shell("'\"test\"'", buffer), "\\'\\\"test\\\"\\'") == 0);
assert(strcmp(esc_shell("$(echo \"test\")", buffer), "\\$\\(echo\\ \\\"test\\\"\\)") == 0);
/* all safe characters that don't need escaping */
assert(strcmp(esc_shell("abcdefghijklmnopqrstuvwxyz", buffer), "abcdefghijklmnopqrstuvwxyz") == 0);
assert(strcmp(esc_shell("ABCDEFGHIJKLMNOPQRSTUVWXYZ", buffer), "ABCDEFGHIJKLMNOPQRSTUVWXYZ") == 0);
assert(strcmp(esc_shell("0123456789", buffer), "0123456789") == 0);
assert(strcmp(esc_shell("._-+,@:", buffer), "._-+,@:") == 0);
assert(strcmp(esc_shell("/path/to/file", buffer), "/path/to/file") == 0);
assert(strcmp(esc_shell("simple_file-name.txt", buffer), "simple_file-name.txt") == 0);
/* file extensions and versions */
assert(strcmp(esc_shell("file.tar.gz", buffer), "file.tar.gz") == 0);
assert(strcmp(esc_shell("app-v1.2.3.deb", buffer), "app-v1.2.3.deb") == 0);
assert(strcmp(esc_shell("test_2024-01-01.log", buffer), "test_2024-01-01.log") == 0);
#endif
random_seed(0);
assert(random_u8() == 0xAF);
assert(random_u64() == 0x6E789E6AA1B965F4ULL);
printf("Everything OK\n");
lock_done();
exit(EXIT_SUCCESS);
}
/****************************************************************************/
/* log */
void log_open(const char* file)
{
char path[PATH_MAX];
const char* mode;
char text_T[32];
char text_D[32];
time_t t;
struct tm* tm;
#if HAVE_LOCALTIME_R
struct tm tm_res;
#endif
/* leave stdlog at 0 if not specified */
if (file == 0)
return;
t = time(0);
#if HAVE_LOCALTIME_R
tm = localtime_r(&t, &tm_res);
#else
tm = localtime(&t);
#endif
if (tm) {
strftime(text_T, sizeof(text_T), "%H%M%S", tm);
strftime(text_D, sizeof(text_T), "%Y%m%d", tm);
} else {
/* LCOV_EXCL_START */
strcpy(text_T, "invalid");
strcpy(text_D, "invalid");
/* LCOV_EXCL_STOP */
}
/* file mode */
mode = "wt";
if (*file == '>') {
++file;
if (*file == '>') {
mode = "at";
++file;
}
if (file[0] == '&' && file[1] == '1') {
stdlog = stdout;
return;
}
if (file[0] == '&' && file[1] == '2') {
stdlog = stderr;
return;
}
}
/* process the path */
for (*path = 0; *file != 0; ) {
switch (*file) {
case '%' :
++file;
switch (*file) {
case '%' :
pathcatc(path, sizeof(path), '%');
break;
case 'T' :
pathcat(path, sizeof(path), text_T);
break;
case 'D' :
pathcat(path, sizeof(path), text_D);
break;
default :
/* LCOV_EXCL_START */
log_fatal(EUSER, "Invalid type specifier '%c' in the log file.\n", *file);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
break;
default :
pathcatc(path, sizeof(path), *file);
break;
}
++file;
}
stdlog = fopen(path, mode);
if (!stdlog) {
/* LCOV_EXCL_START */
log_fatal(errno, "Error opening the log file '%s'. %s.\n", path, strerror(errno));
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
}
void log_close(const char* file)
{
if (stdlog != stdout && stdlog != stderr && stdlog != 0) {
if (fclose(stdlog) != 0) {
/* LCOV_EXCL_START */
log_fatal(errno, "Error closing the log file '%s'. %s.\n", file, strerror(errno));
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
}
stdlog = 0;
}
/****************************************************************************/
/* main */
/**
* Only long options
*/
#define OPT_NO_WARNINGS 500
#define OPT_GUI 501
#define OPT_GUI_VERBOSE 502
#define OPT_GUI_RESCAN_AFTER 503
#define OPT_GUI_TOUCH_BEFORE 504
#define OPT_GUI_THRESHOLD_REMOVES 505
#define OPT_GUI_THRESHOLD_UPDATES 506
/**
* Test options
*/
#define OPT_TEST_SKIP_SELF 256
#define OPT_TEST_KILL_AFTER_SYNC 257
#define OPT_TEST_EXPECT_UNRECOVERABLE 258
#define OPT_TEST_EXPECT_RECOVERABLE 259
#define OPT_TEST_SKIP_SIGN 260
#define OPT_TEST_SKIP_FALLOCATE 261
#define OPT_TEST_SKIP_DEVICE 262
#define OPT_TEST_FORCE_MURMUR3 264
#define OPT_TEST_FORCE_SPOOKY2 265
#define OPT_TEST_SKIP_LOCK 266
#define OPT_TEST_FORCE_ORDER_PHYSICAL 267
#define OPT_TEST_FORCE_ORDER_INODE 268
#define OPT_TEST_FORCE_ORDER_ALPHA 269
#define OPT_TEST_FORCE_ORDER_DIR 270
#define OPT_TEST_FORCE_SCRUB_AT 271
#define OPT_TEST_FORCE_SCRUB_EVEN 272
#define OPT_TEST_FORCE_CONTENT_WRITE 273
#define OPT_TEST_SKIP_CONTENT_CHECK 275
#define OPT_TEST_SKIP_PARITY_ACCESS 276
#define OPT_TEST_EXPECT_FAILURE 277
#define OPT_TEST_RUN 278
#define OPT_TEST_FORCE_SCAN_WINFIND 279
#define OPT_TEST_IMPORT_CONTENT 280
#define OPT_TEST_FORCE_PROGRESS 281
#define OPT_TEST_SKIP_DISK_ACCESS 282
#define OPT_TEST_FORCE_AUTOSAVE_AT 283
#define OPT_TEST_FAKE_DEVICE 284
#define OPT_TEST_EXPECT_NEED_SYNC 285
#define OPT_TEST_FAKE_UUID 287
#define OPT_TEST_MATCH_FIRST_UUID 288
#define OPT_TEST_FORCE_PARITY_UPDATE 289
#define OPT_TEST_IO_CACHE 290
#define OPT_TEST_IO_STATS 291
#define OPT_TEST_COND_SIGNAL_OUTSIDE 292
#define OPT_TEST_IO_ADVISE_NONE 293
#define OPT_TEST_IO_ADVISE_SEQUENTIAL 294
#define OPT_TEST_IO_ADVISE_FLUSH 295
#define OPT_TEST_IO_ADVISE_FLUSH_WINDOW 296
#define OPT_TEST_IO_ADVISE_DISCARD 297
#define OPT_TEST_IO_ADVISE_DISCARD_WINDOW 298
#define OPT_TEST_IO_ADVISE_DIRECT 299
#define OPT_TEST_PARITY_LIMIT 301
#define OPT_TEST_SKIP_CONTENT_WRITE 302
#define OPT_TEST_SKIP_SPACE_HOLDER 303
#define OPT_TEST_FORMAT 304
#define OPT_TEST_SKIP_MULTI_SCAN 305
#define OPT_TEST_SPEED_PERIOD 307
#define OPT_TEST_SPEED_DISKS_NUMBER 308
#define OPT_TEST_SPEED_BLOCKS_SIZE 309
#define OPT_TEST_KILL_BEFORE_SYNC 310
#if HAVE_GETOPT_LONG
static struct option long_options[] = {
{ "conf", 1, 0, 'c' },
{ "filter", 1, 0, 'f' },
{ "filter-disk", 1, 0, 'd' },
{ "filter-missing", 0, 0, 'm' },
{ "filter-error", 0, 0, 'e' },
{ "filter-block-error", 0, 0, 'b' },
{ "percentage", 1, 0, 'p' }, /* legacy name for --plan */
{ "plan", 1, 0, 'p' },
{ "older-than", 1, 0, 'o' },
{ "start", 1, 0, 'S' },
{ "count", 1, 0, 'B' },
{ "error-limit", 1, 0, 'L' },
{ "import", 1, 0, 'i' },
{ "log", 1, 0, 'l' },
{ "stats", 0, 0, 'A' },
{ "force-zero", 0, 0, 'Z' },
{ "force-empty", 0, 0, 'E' },
{ "force-uuid", 0, 0, 'U' },
{ "force-device", 0, 0, 'D' },
{ "force-nocopy", 0, 0, 'N' },
{ "force-full", 0, 0, 'F' },
{ "force-realloc", 0, 0, 'R' },
{ "force-realloc-tail", 1, 0, 'W' },
{ "bw-limit", 1, 0, 'w' },
{ "audit-only", 0, 0, 'a' },
{ "pre-hash", 0, 0, 'h' },
{ "tail", 1, 0, 't' },
{ "speed-test", 0, 0, 'T' }, /* undocumented speed test command */
{ "speed-test-period", 1, 0, OPT_TEST_SPEED_PERIOD }, /* for how many milliseconds test each feature. Default 1000. */
{ "speed-test-disks-number", 1, 0, OPT_TEST_SPEED_DISKS_NUMBER }, /* how many disk number uses in the speed test. Default 8. */
{ "speed-test-blocks-size", 1, 0, OPT_TEST_SPEED_BLOCKS_SIZE }, /* how big in kBytes should be the blocks in the speed test. Default 256. */
{ "gen-conf", 1, 0, 'C' },
{ "verbose", 0, 0, 'v' },
{ "quiet", 0, 0, 'q' },
{ "help", 0, 0, 'H' },
{ "version", 0, 0, 'V' },
{ "no-warnings", 0, 0, OPT_NO_WARNINGS }, /* disable annoying warnings */
{ "gui", 0, 0, OPT_GUI }, /* undocumented GUI interface option (it was also 'G' in the past) */
{ "gui-verbose", 0, 0, OPT_GUI_VERBOSE }, /* undocumented GUI interface option */
{ "gui-rescan-after", 0, 0, OPT_GUI_RESCAN_AFTER }, /* undocumented GUI, force a rescan after the command to log differences */
{ "gui-touch-before", 0, 0, OPT_GUI_TOUCH_BEFORE }, /* undocumented GUI, force a touch before the command */
{ "gui-threshold-removes", 1, 0, OPT_GUI_THRESHOLD_REMOVES }, /* undocumented GUI, abort sync if too many removed files */
{ "gui-threshold-updates", 1, 0, OPT_GUI_THRESHOLD_UPDATES }, /* undocumented GUI, abort sync if too many updated files */
/* The following are test specific options, DO NOT USE! */
/* After syncing, do not write the new content file */
{ "test-kill-after-sync", 0, 0, OPT_TEST_KILL_AFTER_SYNC },
/* Do not execure syncing after writing the new initial content file */
{ "test-kill-before-sync", 0, 0, OPT_TEST_KILL_BEFORE_SYNC },
/* Exit with failure if after check/fix there ARE NOT unrecoverable errors. */
{ "test-expect-unrecoverable", 0, 0, OPT_TEST_EXPECT_UNRECOVERABLE },
/* Exit with failure if after check/fix there ARE NOT recoverable errors. */
{ "test-expect-recoverable", 0, 0, OPT_TEST_EXPECT_RECOVERABLE },
/* Skip the initial self test */
{ "test-skip-self", 0, 0, OPT_TEST_SKIP_SELF },
/* Skip the initial sign check when reading the content file */
{ "test-skip-sign", 0, 0, OPT_TEST_SKIP_SIGN },
/* Skip the fallocate() when growing the parity files */
{ "test-skip-fallocate", 0, 0, OPT_TEST_SKIP_FALLOCATE },
/* Skip the device check */
{ "test-skip-device", 0, 0, OPT_TEST_SKIP_DEVICE },
/* Force Murmur3 hash */
{ "test-force-murmur3", 0, 0, OPT_TEST_FORCE_MURMUR3 },
/* Force Spooky2 hash */
{ "test-force-spooky2", 0, 0, OPT_TEST_FORCE_SPOOKY2 },
/* Skip the use of lock file */
{ "test-skip-lock", 0, 0, OPT_TEST_SKIP_LOCK },
/* Force a sort order for files */
{ "test-force-order-physical", 0, 0, OPT_TEST_FORCE_ORDER_PHYSICAL },
{ "test-force-order-inode", 0, 0, OPT_TEST_FORCE_ORDER_INODE },
{ "test-force-order-alpha", 0, 0, OPT_TEST_FORCE_ORDER_ALPHA },
{ "test-force-order-dir", 0, 0, OPT_TEST_FORCE_ORDER_DIR },
/* Force scrub of the specified number of blocks */
{ "test-force-scrub-at", 1, 0, OPT_TEST_FORCE_SCRUB_AT },
/* Force scrub of all the even blocks. This is really for testing, don't try it */
{ "test-force-scrub-even", 0, 0, OPT_TEST_FORCE_SCRUB_EVEN },
/* Force write of the content file even if no modification is done */
{ "test-force-content-write", 0, 0, OPT_TEST_FORCE_CONTENT_WRITE },
/* Relax the checks done at the content file */
{ "test-skip-content-check", 0, 0, OPT_TEST_SKIP_CONTENT_CHECK },
/* Skip the parity access */
{ "test-skip-parity-access", 0, 0, OPT_TEST_SKIP_PARITY_ACCESS },
/* Exit generic failure */
{ "test-expect-failure", 0, 0, OPT_TEST_EXPECT_FAILURE },
/* Exit generic need sync */
{ "test-expect-need-sync", 0, 0, OPT_TEST_EXPECT_NEED_SYNC },
#if HAVE_CHECKER
/* Run some command after loading the state and before the command */
{ "test-run", 1, 0, OPT_TEST_RUN },
#endif
/* Use the FindFirst/Next approach in Windows to list files */
{ "test-force-scan-winfind", 0, 0, OPT_TEST_FORCE_SCAN_WINFIND },
/* Alternative import working by data */
{ "test-import-content", 1, 0, OPT_TEST_IMPORT_CONTENT },
/* Force immediate progress state update */
{ "test-force-progress", 0, 0, OPT_TEST_FORCE_PROGRESS },
/* Skip the disk access */
{ "test-skip-disk-access", 0, 0, OPT_TEST_SKIP_DISK_ACCESS },
/* Force autosave at the specified block */
{ "test-force-autosave-at", 1, 0, OPT_TEST_FORCE_AUTOSAVE_AT },
/* Fake device data */
{ "test-fake-device", 0, 0, OPT_TEST_FAKE_DEVICE },
/* Fake UUID */
{ "test-fake-uuid", 0, 0, OPT_TEST_FAKE_UUID },
/* Match first UUID */
{ "test-match-first-uuid", 0, 0, OPT_TEST_MATCH_FIRST_UUID },
/* Force parity update even if all the data hash is already matching */
{ "test-force-parity-update", 0, 0, OPT_TEST_FORCE_PARITY_UPDATE },
/* Number of IO buffers */
{ "test-io-cache", 1, 0, OPT_TEST_IO_CACHE },
/* Print IO stats */
{ "test-io-stats", 0, 0, OPT_TEST_IO_STATS }, /* now replaced by -A, --stats */
/* Signal condition variable outside the mutex */
{ "test-cond-signal-outside", 0, 0, OPT_TEST_COND_SIGNAL_OUTSIDE },
/* Set the io advise to none */
{ "test-io-advise-none", 0, 0, OPT_TEST_IO_ADVISE_NONE },
/* Set the io advise to sequential */
{ "test-io-advise-sequential", 0, 0, OPT_TEST_IO_ADVISE_SEQUENTIAL },
/* Set the io advise to flush */
{ "test-io-advise-flush", 0, 0, OPT_TEST_IO_ADVISE_FLUSH },
/* Set the io advise to flush window */
{ "test-io-advise-flush-window", 0, 0, OPT_TEST_IO_ADVISE_FLUSH_WINDOW },
/* Set the io advise to discard */
{ "test-io-advise-discard", 0, 0, OPT_TEST_IO_ADVISE_DISCARD },
/* Set the io advise to discard window */
{ "test-io-advise-discard-window", 0, 0, OPT_TEST_IO_ADVISE_DISCARD_WINDOW },
/* Set the io advise to direct */
{ "test-io-advise-direct", 0, 0, OPT_TEST_IO_ADVISE_DIRECT },
/* Set an artificial parity limit */
{ "test-parity-limit", 1, 0, OPT_TEST_PARITY_LIMIT },
/* Skip content write */
{ "test-skip-content-write", 0, 0, OPT_TEST_SKIP_CONTENT_WRITE },
/* Skip space holder file in parity disks */
{ "test-skip-space-holder", 0, 0, OPT_TEST_SKIP_SPACE_HOLDER },
/* Set the output format */
{ "test-fmt", 1, 0, OPT_TEST_FORMAT },
/* Skip thread in disk scan */
{ "test-skip-multi-scan", 0, 0, OPT_TEST_SKIP_MULTI_SCAN },
{ 0, 0, 0, 0 }
};
#endif
/*
* Free letters: gIjJkKMnPQruxXz
*
* The 's' letter is used in main.c
* The 'G' letter is free but only from 14.0
*/
#define OPTIONS "t:c:f:d:mebp:o:S:B:L:i:l:AZEUDNFRW:ahTC:vqHVw:"
int parse_option_size(const char* arg, uint64_t* out_size)
{
char* e;
/* parse the number part */
uint64_t size = strtoull(arg, &e, 10);
if (e == arg)
return -1;
/* Handle suffixes */
if ((e[0] == 'k' || e[0] == 'K') && e[1] == 0) {
size *= KILO;
} else if ((e[0] == 'm' || e[0] == 'M') && e[1] == 0) {
size *= MEGA;
} else if ((e[0] == 'g' || e[0] == 'G') && e[1] == 0) {
size *= GIGA;
} else if ((e[0] == 't' || e[0] == 'T') && e[1] == 0) {
size *= TERA;
} else if (e[0] != 0) {
return -1;
}
*out_size = size;
return 0;
}
volatile int global_interrupt = 0;
/* LCOV_EXCL_START */
void signal_handler(int signum)
{
/* report the request of interruption with the signal received */
global_interrupt = signum;
}
/* LCOV_EXCL_STOP */
void signal_init(void)
{
#if HAVE_SIGACTION
struct sigaction sa;
sa.sa_handler = signal_handler;
sigemptyset(&sa.sa_mask);
/* use the SA_RESTART to automatically restart interrupted system calls */
sa.sa_flags = SA_RESTART;
sigaction(SIGHUP, &sa, 0);
sigaction(SIGTERM, &sa, 0);
sigaction(SIGINT, &sa, 0);
#else
signal(SIGINT, signal_handler);
#endif
}
#define OPERATION_DIFF 0
#define OPERATION_SYNC 1
#define OPERATION_CHECK 2
#define OPERATION_FIX 3
#define OPERATION_DRY 4
#define OPERATION_DUP 5
#define OPERATION_LIST 6
#define OPERATION_POOL 7
#define OPERATION_REHASH 8
#define OPERATION_SCRUB 9
#define OPERATION_STATUS 10
#define OPERATION_REWRITE 11
#define OPERATION_READ 12
#define OPERATION_TOUCH 13
#define OPERATION_SPINUP 14
#define OPERATION_SPINDOWN 15
#define OPERATION_DEVICES 16
#define OPERATION_SMART 17
#define OPERATION_PROBE 18
#define OPERATION_LOCATE 19
#define OPERATION_SPINDOWNIFUP 20
int snapraid_main(int argc, char* argv[])
{
int c;
struct snapraid_option opt;
char conf[PATH_MAX];
struct snapraid_state state;
int operation;
block_off_t blockstart;
block_off_t blockcount;
int ret;
tommy_list filterlist_file;
tommy_list filterlist_disk;
int filter_missing;
int filter_error;
int plan100;
int olderthan;
char* e;
const char* command;
const char* import_timestamp;
const char* import_content;
const char* log_file;
int lock;
const char* gen_conf;
#if HAVE_CHECKER
const char* run;
#endif
int speedtest;
int speed_test_period;
int speed_test_disks_number;
int speed_test_blocks_size;
time_t t;
struct tm* tm;
#if HAVE_LOCALTIME_R
struct tm tm_res;
#endif
int i;
test(argc, argv);
lock_init();
/* defaults */
os_default_conf(conf, sizeof(conf), argv[0]);
memset(&opt, 0, sizeof(opt));
opt.io_error_limit = 100;
blockstart = 0;
blockcount = 0;
tommy_list_init(&filterlist_file);
tommy_list_init(&filterlist_disk);
speed_test_period = -1;
speed_test_blocks_size = -1;
speed_test_disks_number = -1;
filter_missing = 0;
filter_error = 0;
plan100 = SCRUB_AUTO;
olderthan = SCRUB_AUTO;
import_timestamp = 0;
import_content = 0;
log_file = 0;
lock = 0;
gen_conf = 0;
speedtest = 0;
#if HAVE_CHECKER
run = 0;
#endif
opterr = 0;
while ((c =
#if HAVE_GETOPT_LONG
getopt_long(argc, argv, OPTIONS, long_options, 0))
#else
getopt(argc, argv, OPTIONS))
#endif
!= EOF) {
switch (c) {
case 'c' :
pathimport(conf, sizeof(conf), optarg);
break;
case 'f' : {
struct snapraid_filter* filter = filter_alloc_file(1, "", optarg);
if (!filter) {
/* LCOV_EXCL_START */
log_fatal(EUSER, "Invalid filter specification '%s'\n", optarg);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
tommy_list_insert_tail(&filterlist_file, &filter->node, filter);
} break;
case 'd' : {
struct snapraid_filter* filter = filter_alloc_disk(1, optarg);
if (!filter) {
/* LCOV_EXCL_START */
log_fatal(EUSER, "Invalid filter specification '%s'\n", optarg);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
tommy_list_insert_tail(&filterlist_disk, &filter->node, filter);
} break;
case 'm' :
filter_missing = 1;
opt.expected_missing = 1;
break;
case 'e' :
/*
* When processing only error, we filter files
* and we apply fixes only to synced ones
*/
filter_error = 1;
opt.badfileonly = 1;
opt.syncedonly = 1;
break;
case 'b' :
/*
* When processing only block with error, we filter both files and blocks
* and we apply fixes only to synced ones
*/
filter_error = 1;
opt.badfileonly = 1;
opt.badblockonly = 1;
opt.syncedonly = 1;
break;
case 'p' :
if (strcmp(optarg, "bad") == 0) {
plan100 = SCRUB_BAD;
} else if (strcmp(optarg, "new") == 0) {
plan100 = SCRUB_NEW;
} else if (strcmp(optarg, "full") == 0) {
plan100 = SCRUB_FULL;
} else {
double plan_double = strtod(optarg, &e);
if (e == optarg
|| *e != 0
|| !isfinite(plan_double)
|| plan_double < 0
|| plan_double > 100
) {
/* LCOV_EXCL_START */
log_fatal(EUSER, "Invalid plan/percentage '%s'\n", optarg);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
/* +0.5 avoids floating point precision error of strtod, like "0.7" -> 0.6999... -> 0.6 */
plan100 = plan_double * 100 + 0.5;
}
break;
case 'o' :
olderthan = strtoul(optarg, &e, 10);
if (e == optarg || *e || olderthan > 1000) {
/* LCOV_EXCL_START */
log_fatal(EUSER, "Invalid number of days '%s'\n", optarg);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
break;
case 'w' : /* --bw-limit */
if (optarg == 0) {
/* LCOV_EXCL_START */
log_fatal(EUSER, "Missing bandwidth limit\n");
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
if (parse_option_size(optarg, &opt.bwlimit) != 0) {
/* LCOV_EXCL_START */
log_fatal(EUSER, "Invalid bandwidth limit '%s'\n", optarg);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
break;
case 'S' :
blockstart = strtoul(optarg, &e, 0);
if (e == optarg || *e) {
/* LCOV_EXCL_START */
log_fatal(EUSER, "Invalid start position '%s'\n", optarg);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
break;
case 'B' :
blockcount = strtoul(optarg, &e, 0);
if (e == optarg || *e) {
/* LCOV_EXCL_START */
log_fatal(EUSER, "Invalid count number '%s'\n", optarg);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
break;
case 'L' :
opt.io_error_limit = strtoul(optarg, &e, 0);
if (e == optarg || *e) {
/* LCOV_EXCL_START */
log_fatal(EUSER, "Invalid error limit number '%s'\n", optarg);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
break;
case 'i' :
if (import_timestamp) {
/* LCOV_EXCL_START */
log_fatal(EUSER, "Import directory '%s' already specified as '%s'\n", optarg, import_timestamp);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
import_timestamp = optarg;
break;
case 't' :
if (parse_option_size(optarg, &opt.parity_tail) != 0) {
/* LCOV_EXCL_START */
log_fatal(EUSER, "Invalid tail size '%s'\n", optarg);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
break;
case OPT_TEST_IMPORT_CONTENT :
if (import_content) {
/* LCOV_EXCL_START */
log_fatal(EUSER, "Import directory '%s' already specified as '%s'\n", optarg, import_content);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
import_content = optarg;
break;
case 'l' :
if (log_file) {
/* LCOV_EXCL_START */
log_fatal(EUSER, "Log file '%s' already specified as '%s'\n", optarg, log_file);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
log_file = optarg;
break;
case 'A' :
opt.force_stats = 1;
break;
case 'Z' :
opt.force_zero = 1;
break;
case 'E' :
opt.force_empty = 1;
break;
case 'U' :
opt.force_uuid = 1;
break;
case 'D' :
opt.force_device = 1;
break;
case 'N' :
opt.force_nocopy = 1;
break;
case 'F' :
opt.force_full = 1;
break;
case 'R' :
opt.force_realloc = 1;
break;
case 'W' :
opt.force_realloc = 1;
if (parse_option_size(optarg, &opt.parity_tail) != 0) {
/* LCOV_EXCL_START */
log_fatal(EUSER, "Invalid tail size '%s'\n", optarg);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
break;
case 'a' :
opt.auditonly = 1;
break;
case 'h' :
opt.prehash = 1;
break;
case 'v' :
++msg_level;
break;
case 'q' :
--msg_level;
break;
case OPT_NO_WARNINGS :
opt.no_warnings = 1;
break;
case OPT_GUI :
opt.gui = 1;
break;
case OPT_GUI_VERBOSE :
opt.gui = 1;
opt.gui_verbose = 1;
break;
case OPT_GUI_RESCAN_AFTER :
opt.gui_rescan_after = 1;
break;
case OPT_GUI_TOUCH_BEFORE :
opt.gui_touch_before = 1;
break;
case OPT_GUI_THRESHOLD_REMOVES :
opt.gui_threshold_removes = strtoul(optarg, &e, 0);
if (e == optarg || *e) {
/* LCOV_EXCL_START */
log_fatal(EUSER, "Invalid threshold '%s'\n", optarg);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
break;
case OPT_GUI_THRESHOLD_UPDATES :
opt.gui_threshold_updates = strtoul(optarg, &e, 0);
if (e == optarg || *e) {
/* LCOV_EXCL_START */
log_fatal(EUSER, "Invalid threshold '%s'\n", optarg);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
break;
case 'H' :
usage(conf);
exit(EXIT_SUCCESS);
case 'V' :
version();
exit(EXIT_SUCCESS);
case 'T' :
speedtest = 1;
break;
case OPT_TEST_SPEED_PERIOD :
speed_test_period = atoi(optarg);
break;
case OPT_TEST_SPEED_DISKS_NUMBER :
speed_test_disks_number = atoi(optarg);
break;
case OPT_TEST_SPEED_BLOCKS_SIZE :
speed_test_blocks_size = atoi(optarg);
break;
case 'C' :
gen_conf = optarg;
break;
case OPT_TEST_KILL_AFTER_SYNC :
opt.kill_after_sync = 1;
break;
case OPT_TEST_KILL_BEFORE_SYNC :
opt.kill_before_sync = 1;
break;
case OPT_TEST_EXPECT_UNRECOVERABLE :
opt.expect_unrecoverable = 1;
break;
case OPT_TEST_EXPECT_RECOVERABLE :
opt.expect_recoverable = 1;
break;
case OPT_TEST_SKIP_SELF :
opt.skip_self = 1;
break;
case OPT_TEST_SKIP_SIGN :
opt.skip_sign = 1;
break;
case OPT_TEST_SKIP_FALLOCATE :
opt.skip_fallocate = 1;
break;
case OPT_TEST_SKIP_DEVICE :
opt.skip_device = 1;
speed_test_period = 50; /* reduce period of the speed test for running the make check */
break;
case OPT_TEST_SKIP_CONTENT_CHECK :
opt.skip_content_check = 1;
break;
case OPT_TEST_SKIP_PARITY_ACCESS :
opt.skip_parity_access = 1;
break;
case OPT_TEST_SKIP_DISK_ACCESS :
opt.skip_disk_access = 1;
break;
case OPT_TEST_FORCE_MURMUR3 :
opt.force_murmur3 = 1;
break;
case OPT_TEST_FORCE_SPOOKY2 :
opt.force_spooky2 = 1;
break;
case OPT_TEST_SKIP_LOCK :
opt.skip_lock = 1;
break;
case OPT_TEST_FORCE_ORDER_PHYSICAL :
opt.force_order = SORT_PHYSICAL;
break;
case OPT_TEST_FORCE_ORDER_INODE :
opt.force_order = SORT_INODE;
break;
case OPT_TEST_FORCE_ORDER_ALPHA :
opt.force_order = SORT_ALPHA;
break;
case OPT_TEST_FORCE_ORDER_DIR :
opt.force_order = SORT_DIR;
break;
case OPT_TEST_FORCE_SCRUB_AT :
opt.force_scrub_at = atoi(optarg);
break;
case OPT_TEST_FORCE_SCRUB_EVEN :
opt.force_scrub_even = 1;
break;
case OPT_TEST_FORCE_CONTENT_WRITE :
opt.force_content_write = 1;
break;
case OPT_TEST_EXPECT_FAILURE :
/* invert the exit codes */
exit_success = 1;
exit_failure = 0;
break;
case OPT_TEST_EXPECT_NEED_SYNC :
/* invert the exit codes */
exit_success = 1;
exit_sync_needed = 0;
break;
#if HAVE_CHECKER
case OPT_TEST_RUN :
run = optarg;
break;
#endif
case OPT_TEST_FORCE_SCAN_WINFIND :
opt.force_scan_winfind = 1;
break;
case OPT_TEST_FORCE_PROGRESS :
opt.force_progress = 1;
break;
case OPT_TEST_FORCE_AUTOSAVE_AT :
opt.force_autosave_at = atoi(optarg);
break;
case OPT_TEST_FAKE_DEVICE :
opt.fake_device = 1;
break;
case OPT_TEST_FAKE_UUID :
opt.fake_uuid = 2;
break;
case OPT_TEST_MATCH_FIRST_UUID :
opt.match_first_uuid = 1;
break;
case OPT_TEST_FORCE_PARITY_UPDATE :
opt.force_parity_update = 1;
break;
case OPT_TEST_IO_CACHE :
opt.io_cache = atoi(optarg);
if (opt.io_cache != 1 && (opt.io_cache < IO_MIN || opt.io_cache > IO_MAX)) {
/* LCOV_EXCL_START */
log_fatal(EUSER, "The IO cache should be between %u and %u.\n", IO_MIN, IO_MAX);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
break;
case OPT_TEST_IO_STATS :
opt.force_stats = 1;
break;
case OPT_TEST_COND_SIGNAL_OUTSIDE :
#if HAVE_THREAD
thread_cond_signal_outside = 1;
#endif
break;
case OPT_TEST_IO_ADVISE_NONE :
opt.file_mode = ADVISE_NONE;
break;
case OPT_TEST_IO_ADVISE_SEQUENTIAL :
opt.file_mode = ADVISE_SEQUENTIAL;
break;
case OPT_TEST_IO_ADVISE_FLUSH :
opt.file_mode = ADVISE_FLUSH;
break;
case OPT_TEST_IO_ADVISE_FLUSH_WINDOW :
opt.file_mode = ADVISE_FLUSH_WINDOW;
break;
case OPT_TEST_IO_ADVISE_DISCARD :
opt.file_mode = ADVISE_DISCARD;
break;
case OPT_TEST_IO_ADVISE_DISCARD_WINDOW :
opt.file_mode = ADVISE_DISCARD_WINDOW;
break;
case OPT_TEST_IO_ADVISE_DIRECT :
opt.file_mode = ADVISE_DIRECT;
break;
case OPT_TEST_PARITY_LIMIT :
opt.parity_limit_size = atoll(optarg);
break;
case OPT_TEST_SKIP_CONTENT_WRITE :
opt.skip_content_write = 1;
break;
case OPT_TEST_SKIP_SPACE_HOLDER :
opt.skip_space_holder = 1;
break;
case OPT_TEST_FORMAT :
if (strcmp(optarg, "file") == 0)
FMT_MODE = FMT_FILE;
else if (strcmp(optarg, "disk") == 0)
FMT_MODE = FMT_DISK;
else if (strcmp(optarg, "path") == 0)
FMT_MODE = FMT_PATH;
else {
/* LCOV_EXCL_START */
log_fatal(EUSER, "Unknown format '%s'\n", optarg);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
break;
case OPT_TEST_SKIP_MULTI_SCAN :
opt.skip_multi_scan = 1;
break;
default :
/* LCOV_EXCL_START */
log_fatal(EUSER, "Unknown option '%c'\n", (char)c);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
}
os_init(opt.force_scan_winfind);
raid_init();
crc32c_init();
random_reseed();
if (speedtest != 0) {
speed(speed_test_period, speed_test_disks_number, speed_test_blocks_size);
os_done();
exit(EXIT_SUCCESS);
}
if (gen_conf != 0) {
generate_configuration(gen_conf);
os_done();
exit(EXIT_SUCCESS);
}
if (optind + 1 != argc) {
/* LCOV_EXCL_START */
usage(conf);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
command = argv[optind];
if (strcmp(command, "diff") == 0) {
operation = OPERATION_DIFF;
} else if (strcmp(argv[optind], "sync") == 0) {
operation = OPERATION_SYNC;
} else if (strcmp(argv[optind], "check") == 0) {
operation = OPERATION_CHECK;
} else if (strcmp(argv[optind], "fix") == 0) {
operation = OPERATION_FIX;
} else if (strcmp(argv[optind], "test-dry") == 0) {
operation = OPERATION_DRY;
} else if (strcmp(argv[optind], "dup") == 0) {
operation = OPERATION_DUP;
} else if (strcmp(argv[optind], "list") == 0) {
operation = OPERATION_LIST;
} else if (strcmp(argv[optind], "pool") == 0) {
operation = OPERATION_POOL;
} else if (strcmp(argv[optind], "rehash") == 0) {
operation = OPERATION_REHASH;
} else if (strcmp(argv[optind], "scrub") == 0) {
operation = OPERATION_SCRUB;
} else if (strcmp(argv[optind], "status") == 0) {
operation = OPERATION_STATUS;
} else if (strcmp(argv[optind], "test-rewrite") == 0) {
operation = OPERATION_REWRITE;
} else if (strcmp(argv[optind], "read") == 0) {
operation = OPERATION_READ;
} else if (strcmp(argv[optind], "touch") == 0) {
operation = OPERATION_TOUCH;
} else if (strcmp(argv[optind], "up") == 0) {
operation = OPERATION_SPINUP;
} else if (strcmp(argv[optind], "down") == 0) {
operation = OPERATION_SPINDOWN;
} else if (strcmp(argv[optind], "test-downifup") == 0) {
operation = OPERATION_SPINDOWNIFUP;
} else if (strcmp(argv[optind], "devices") == 0) {
operation = OPERATION_DEVICES;
} else if (strcmp(argv[optind], "smart") == 0) {
operation = OPERATION_SMART;
} else if (strcmp(argv[optind], "probe") == 0) {
operation = OPERATION_PROBE;
} else if (strcmp(argv[optind], "locate") == 0) {
operation = OPERATION_LOCATE;
} else {
/* LCOV_EXCL_START */
log_fatal(EUSER, "Unknown command '%s'\n", argv[optind]);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
/* check options compatibility */
switch (operation) {
case OPERATION_CHECK :
break;
default :
if (opt.auditonly) {
/* LCOV_EXCL_START */
log_fatal(EUSER, "You cannot use -a, --audit-only with the '%s' command\n", command);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
}
switch (operation) {
case OPERATION_FIX :
case OPERATION_CHECK :
case OPERATION_SMART :
case OPERATION_PROBE :
case OPERATION_DEVICES :
case OPERATION_SPINUP :
case OPERATION_SPINDOWN :
case OPERATION_SPINDOWNIFUP :
break;
default :
if (opt.force_device) {
/* LCOV_EXCL_START */
log_fatal(EUSER, "You cannot use -D, --force-device with the '%s' command\n", command);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
}
switch (operation) {
case OPERATION_SYNC :
case OPERATION_CHECK :
case OPERATION_FIX :
break;
default :
if (opt.force_nocopy) {
/* LCOV_EXCL_START */
log_fatal(EUSER, "You cannot use -N, --force-nocopy with the '%s' command\n", command);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
}
switch (operation) {
case OPERATION_SYNC :
break;
default :
if (opt.prehash) {
/* LCOV_EXCL_START */
log_fatal(EUSER, "You cannot use -h, --pre-hash with the '%s' command\n", command);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
if (opt.force_full) {
/* LCOV_EXCL_START */
log_fatal(EUSER, "You cannot use -F, --force-full with the '%s' command\n", command);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
if (opt.force_realloc) {
/* LCOV_EXCL_START */
log_fatal(EUSER, "You cannot use -R, --force-realloc with the '%s' command\n", command);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
}
if (opt.force_full && opt.force_nocopy) {
/* LCOV_EXCL_START */
log_fatal(EUSER, "You cannot use the -F, --force-full and -N, --force-nocopy options simultaneously\n");
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
if (opt.force_realloc && opt.force_nocopy) {
/* LCOV_EXCL_START */
log_fatal(EUSER, "You cannot use the -R, --force-realloc and -N, --force-nocopy options simultaneously\n");
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
if (opt.force_realloc && opt.force_full) {
/* LCOV_EXCL_START */
log_fatal(EUSER, "You cannot use the -R, --force-realloc and -F, --force-full options simultaneously\n");
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
if (opt.prehash && opt.force_nocopy) {
/* LCOV_EXCL_START */
log_fatal(EUSER, "You cannot use the -h, --pre-hash and -N, --force-nocopy options simultaneously\n");
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
switch (operation) {
case OPERATION_CHECK :
case OPERATION_FIX :
case OPERATION_DRY :
break;
default :
if (!tommy_list_empty(&filterlist_disk)) {
/* LCOV_EXCL_START */
log_fatal(EUSER, "You cannot use -d, --filter-disk with the '%s' command\n", command);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
/* fallthrough */
case OPERATION_SPINUP :
case OPERATION_SPINDOWN :
case OPERATION_SPINDOWNIFUP :
if (!tommy_list_empty(&filterlist_file)) {
/* LCOV_EXCL_START */
log_fatal(EUSER, "You cannot use -f, --filter with the '%s' command\n", command);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
if (filter_missing != 0) {
/* LCOV_EXCL_START */
log_fatal(EUSER, "You cannot use -m, --filter-missing with the '%s' command\n", command);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
if (filter_error != 0) {
/* LCOV_EXCL_START */
log_fatal(EUSER, "You cannot use -e, --filter-error with the '%s' command\n", command);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
}
/*
* Errors must be always fixed on all disks
* because we don't keep the information on what disk is the error
*/
if (filter_error != 0 && !tommy_list_empty(&filterlist_disk)) {
/* LCOV_EXCL_START */
log_fatal(EUSER, "You cannot use -e, --filter-error and -d, --filter-disk simultaneously\n");
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
switch (operation) {
case OPERATION_CHECK :
case OPERATION_FIX :
break;
default :
if (import_timestamp != 0 || import_content != 0) {
/* LCOV_EXCL_START */
log_fatal(EUSER, "Import not allowed with the '%s' command\n", command);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
}
switch (operation) {
case OPERATION_LIST :
case OPERATION_DUP :
case OPERATION_STATUS :
case OPERATION_REWRITE :
case OPERATION_READ :
case OPERATION_REHASH :
/* avoid to check and access data disks if not needed */
opt.skip_disk_access = 1;
break;
}
switch (operation) {
case OPERATION_DIFF :
case OPERATION_LIST :
case OPERATION_DUP :
case OPERATION_POOL :
case OPERATION_STATUS :
case OPERATION_REWRITE :
case OPERATION_READ :
case OPERATION_REHASH :
case OPERATION_TOUCH :
/* avoid to check and access parity disks if not needed */
opt.skip_parity_access = 1;
break;
}
switch (operation) {
case OPERATION_FIX :
case OPERATION_CHECK :
/* avoid to stop processing if a content file is not accessible */
opt.skip_content_access = 1;
break;
}
switch (operation) {
case OPERATION_DIFF :
case OPERATION_LIST :
case OPERATION_DUP :
case OPERATION_POOL :
case OPERATION_TOUCH :
case OPERATION_SPINUP :
case OPERATION_SPINDOWN :
case OPERATION_SPINDOWNIFUP :
case OPERATION_DEVICES :
case OPERATION_SMART :
case OPERATION_PROBE :
opt.skip_self = 1;
break;
}
switch (operation) {
#if HAVE_DIRECT_IO
case OPERATION_SYNC :
case OPERATION_SCRUB :
case OPERATION_DRY :
break;
#endif
default :
/* we allow direct IO only on some commands */
if (opt.file_mode == ADVISE_DIRECT)
opt.file_mode = ADVISE_SEQUENTIAL;
break;
}
switch (operation) {
case OPERATION_DEVICES :
case OPERATION_SMART :
case OPERATION_PROBE :
/* we may need to use these commands during operations */
opt.skip_lock = 1;
break;
}
/* open the log file */
log_open(log_file);
/* print generic info into the log */
t = time(0);
#if HAVE_LOCALTIME_R
tm = localtime_r(&t, &tm_res);
#else
tm = localtime(&t);
#endif
log_tag("version:%s\n", PACKAGE_VERSION);
log_tag("unixtime:%" PRIi64 "\n", (int64_t)t);
if (tm) {
char datetime[64];
strftime(datetime, sizeof(datetime), "%Y-%m-%d %H:%M:%S", tm);
log_tag("time:%s\n", datetime);
}
log_tag("command:%s\n", command);
for (i = 0; i < argc; ++i)
log_tag("argv:%u:%s\n", i, esc_tag(argv[i]));
log_flush();
if (!opt.skip_self)
selftest();
state_init(&state);
/* read the configuration file */
state_config(&state, conf, command, &opt, &filterlist_disk);
/* set the raid mode */
raid_mode(state.raid_mode);
#if HAVE_LOCKFILE
/* create the lock file */
if (!opt.skip_lock && state.lockfile[0]) {
lock = lock_lock(state.lockfile);
if (lock == -1) {
/* LCOV_EXCL_START */
if (errno != EWOULDBLOCK) {
log_fatal(errno, "Failed to create the lock file '%s'. %s.\n", state.lockfile, strerror(errno));
} else {
log_fatal(errno, "The lock file '%s' is already in use!\n", state.lockfile);
log_fatal(errno, "SnapRAID is already in use!\n");
}
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
}
#else
(void)lock;
#endif
ret = 0;
if (operation == OPERATION_DIFF) {
state_read(&state);
/* refresh the size info to log correct info */
state_refresh(&state);
ret = state_diff(&state);
/* abort if sync needed */
if (ret > 0)
exit(EXIT_SYNC_NEEDED);
} else if (operation == OPERATION_SYNC) {
state_read(&state);
/*
* Mark the files that have to be reallocated
* it will happen in inside scan_file_keep() called in state_scan()
*/
if (state.opt.force_realloc)
state_locate_mark_tail_blocks_for_resync(&state, opt.parity_tail);
if (opt.gui_touch_before)
state_touch(&state);
ret = state_snapshot_new(&state);
if (ret != 0) {
exit(EXIT_FAILURE);
}
state_scan(&state);
if (opt.gui_threshold_removes != 0 && state.removed_files >= opt.gui_threshold_removes) {
if (state.need_write)
state_write(&state);
log_fatal(EUSER, "Too many files were removed (%u, limit is %u). Sync aborted.\n", state.removed_files, opt.gui_threshold_removes);
exit(EXIT_SYNC_NEEDED);
}
if (opt.gui_threshold_updates != 0 && state.updated_files >= opt.gui_threshold_updates) {
if (state.need_write)
state_write(&state);
log_fatal(EUSER, "Too many files were updated (%u, limit is %u). Sync aborted.\n", state.updated_files, opt.gui_threshold_updates);
exit(EXIT_SYNC_NEEDED);
}
/* refresh the size info before the content write */
state_refresh(&state);
memory();
/* intercept signals while operating */
signal_init();
#if HAVE_CHECKER
/* run a test command if required */
if (run != 0) {
ret = system(run);
if (ret == -1) {
/* LCOV_EXCL_START */
log_fatal(errno, "Failed to run '%s'.\n", run);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
if (ret != 0) {
/* LCOV_EXCL_START */
log_fatal(EEXTERNAL, "Failed to run '%s' with return code %xh.\n", run, ret);
return -1;
/* LCOV_EXCL_STOP */
}
}
#endif
/*
* Waits some time to ensure that any concurrent modification done at the files,
* using the same mtime read by the scan process, will be read by sync.
* Note that any later modification done, potentially not read by sync, will have
* a different mtime, and it will be synchronized at the next sync.
* The worst case is the FAT file-system with a two seconds resolution for mtime.
* If you don't use FAT, the wait is not needed, because most file-systems have now
* at least microseconds resolution, but better to be safe.
*/
if (!opt.skip_self)
sleep(2);
ret = state_sync(&state, blockstart, blockcount);
/* commit the snapshot as stable */
if (ret == 0)
state_snapshot_commit(&state);
} else if (operation == OPERATION_DRY) {
state_read(&state);
/* refresh the size info to log correct info */
state_refresh(&state);
/* filter */
state_skip(&state);
state_filter(&state, &filterlist_file, &filterlist_disk, filter_missing, filter_error);
memory();
/* intercept signals while operating */
signal_init();
ret = state_dry(&state, blockstart, blockcount);
} else if (operation == OPERATION_REHASH) {
state_read(&state);
state_rehash(&state);
/*
* Intercept signals while operating
*
* Intentionally do this after the touch operation.
*
* The signal protection is meant only for saving the content file,
* or an early stop of long operations.
*/
signal_init();
/* save the new state if required */
if (state.need_write)
state_write(&state);
} else if (operation == OPERATION_SCRUB) {
state_read(&state);
/* refresh the size info before the content write */
state_refresh(&state);
memory();
/* intercept signals while operating */
signal_init();
state_snapshot_read(&state);
ret = state_scrub(&state, plan100, olderthan);
} else if (operation == OPERATION_REWRITE) {
state_read(&state);
/* intercept signals while operating */
signal_init();
state_write(&state);
memory();
} else if (operation == OPERATION_READ) {
state_read(&state);
/* intentionally DO NOT CALL state_refresh() because it spins up disks */
memory();
} else if (operation == OPERATION_TOUCH) {
state_read(&state);
memory();
state_touch(&state);
/*
* Intercept signals while operating
*
* Intentionally do this after the touch operation.
*
* The signal protection is meant only for saving the content file,
* or an early stop of long operations.
*/
signal_init();
if (state.need_write)
state_write(&state);
} else if (operation == OPERATION_SPINUP) {
ret = state_device(&state, DEVICE_UP, &filterlist_disk);
#if HAVE_CHECKER
if (ret < 0)
ret = 0; /* ignore errors in test environment */
#endif
} else if (operation == OPERATION_SPINDOWN) {
ret = state_device(&state, DEVICE_DOWN, &filterlist_disk);
#if HAVE_CHECKER
if (ret < 0)
ret = 0; /* ignore errors in test environment */
#endif
} else if (operation == OPERATION_SPINDOWNIFUP) {
ret = state_device(&state, DEVICE_DOWNIFUP, &filterlist_disk);
#if HAVE_TEST_RUN
if (ret < 0)
ret = 0; /* ignore errors in test environment */
#endif
} else if (operation == OPERATION_DEVICES) {
ret = state_device(&state, DEVICE_LIST, 0);
#if HAVE_CHECKER
if (ret < 0)
ret = 0; /* ignore errors in test environment */
#endif
} else if (operation == OPERATION_SMART) {
ret = state_device(&state, DEVICE_SMART, 0);
#if HAVE_CHECKER
if (ret < 0)
ret = 0; /* ignore errors in test environment */
#endif
} else if (operation == OPERATION_PROBE) {
/* probe also the content file */
state_probe(&state);
ret = state_device(&state, DEVICE_PROBE, 0);
#if HAVE_CHECKER
if (ret < 0)
ret = 0; /* ignore errors in test environment */
#endif
} else if (operation == OPERATION_STATUS) {
state_read(&state);
memory();
state_status(&state);
} else if (operation == OPERATION_LOCATE) {
state_read(&state);
state_locate(&state, opt.parity_tail);
} else if (operation == OPERATION_DUP) {
state_read(&state);
state_dup(&state);
} else if (operation == OPERATION_LIST) {
state_read(&state);
state_list(&state);
} else if (operation == OPERATION_POOL) {
state_read(&state);
state_pool(&state);
} else if (operation == OPERATION_CHECK || operation == OPERATION_FIX) {
state_read(&state);
/* if we are also trying to recover */
if (!state.opt.auditonly) {
/* import the user specified dirs */
if (import_timestamp != 0)
state_search(&state, import_timestamp);
if (import_content != 0)
state_import(&state, import_content);
/* import from all the array */
if (!state.opt.force_nocopy)
state_search_array(&state);
}
/* filter */
state_skip(&state);
state_filter(&state, &filterlist_file, &filterlist_disk, filter_missing, filter_error);
memory();
/* intercept signals while operating */
signal_init();
state_snapshot_write(&state, &filterlist_disk);
if (operation == OPERATION_CHECK) {
ret = state_check(&state, 0, blockstart, blockcount);
} else { /* it's fix */
ret = state_check(&state, 1, blockstart, blockcount);
/* rescan if requested by the GUI */
if (opt.gui_rescan_after)
state_scan(&state);
}
} else {
/* LCOV_EXCL_START */
log_fatal(errno, "Unexpected command '%s'\n", command);
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
/* close log file */
log_close(log_file);
#if HAVE_LOCKFILE
if (!opt.skip_lock && state.lockfile[0]) {
if (lock_unlock(lock) == -1) {
/* LCOV_EXCL_START */
log_fatal(errno, "Failed to close the lock file '%s'. %s.\n", state.lockfile, strerror(errno));
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
}
#endif
state_done(&state);
tommy_list_foreach(&filterlist_file, (tommy_foreach_func*)filter_free);
tommy_list_foreach(&filterlist_disk, (tommy_foreach_func*)filter_free);
os_done();
lock_done();
/* abort if required */
if (ret != 0) {
/* LCOV_EXCL_START */
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
if (global_interrupt) {
/* LCOV_EXCL_START */
#ifdef _WIN32
exit(STATUS_CONTROL_C_EXIT);
#else
/* restore default handler */
signal(global_interrupt, SIG_DFL);
/* raise the signal again*/
raise(global_interrupt);
/* if raise() didn't terminate */
_exit(128 + global_interrupt);
#endif
/* LCOV_EXCL_STOP */
}
return EXIT_SUCCESS;
}