// SPDX-License-Identifier: GPL-3.0-or-later // Copyright (C) 2011 Andrea Mazzoleni #include "os/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("ac", buffer), "\"ac\"") == 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("ac", buffer), "a\\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; } #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 */ app_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 = strtou(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 = strtoull(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 = strtoull(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 = strtou(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 = strtou(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 = strtou(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(OS_INIT_OPT_AVOID_SLEEP | (opt.force_scan_winfind ? OS_INIT_OPT_WINFIND : 0)); app_init(); raid_init(); crc32c_init(); random_reseed(); if (speedtest != 0) { speed(speed_test_period, speed_test_disks_number, speed_test_blocks_size); app_done(); os_done(); exit(EXIT_SUCCESS); } if (gen_conf != 0) { generate_configuration(gen_conf); app_done(); 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); /* save the new state if required */ if (state.need_write) state_write(&state); } ret = state_snapshot_scan(&state); if (ret != 0) { exit(EXIT_FAILURE); } state_scan(&state); if (opt.gui_threshold_removes != 0 && state.removed_files >= opt.gui_threshold_removes) { log_fatal(EUSER, "Too many files were removed (%" PRIu64 ", 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) { log_fatal(EUSER, "Too many files were updated (%" PRIu64 ", 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 */ os_signal_init(app_signal_handler, app_signal_handler); #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) { ret = state_snapshot_commit(&state); state_snapshot_cleanup(&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 */ os_signal_init(app_signal_handler, app_signal_handler); 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. */ os_signal_init(app_signal_handler, app_signal_handler); /* 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 */ os_signal_init(app_signal_handler, app_signal_handler); state_snapshot_read(&state); ret = state_scrub(&state, plan100, olderthan); if (ret == 0) { state_snapshot_cleanup(&state); } } else if (operation == OPERATION_REWRITE) { state_read(&state); /* intercept signals while operating */ os_signal_init(app_signal_handler, app_signal_handler); 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. */ os_signal_init(app_signal_handler, app_signal_handler); 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 */ os_signal_init(app_signal_handler, app_signal_handler); 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); } if (ret == 0) { state_snapshot_cleanup(&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); app_done(); os_done(); lock_done(); /* abort if required */ if (ret != 0) { /* LCOV_EXCL_START */ exit(EXIT_FAILURE); /* LCOV_EXCL_STOP */ } if (os_signal_interrupt()) { /* LCOV_EXCL_START */ #ifdef _WIN32 exit(STATUS_CONTROL_C_EXIT); #else /* restore default handler */ signal(os_signal_interrupt(), SIG_DFL); /* raise the signal again*/ raise(os_signal_interrupt()); /* if raise() didn't terminate */ _exit(128 + os_signal_interrupt()); #endif /* LCOV_EXCL_STOP */ } return EXIT_SUCCESS; }