/*
 * Copyright (C) 2011 Andrea Mazzoleni
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "portable.h"

#include "snapraid.h"
#include "support.h"
#include "elem.h"
#include "import.h"
#include "search.h"
#include "state.h"
#include "io.h"
#include "raid/raid.h"

/****************************************************************************/
/* main */

void version(void)
{
	msg_status(PACKAGE " v" VERSION " by Andrea Mazzoleni, " PACKAGE_URL "\n");
}

void usage(void)
{
	version();

	printf("Usage: " PACKAGE " status|diff|sync|scrub|list|dup|up|down|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("  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", "-f") "  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");
	printf("  " SWITCH_GETOPT_LONG("-F, --force-full      ", "-F") "  Force a full parity computation in sync\n");
	printf("  " SWITCH_GETOPT_LONG("-R, --force-realloc   ", "-R") "  Force a full parity reallocation in sync\n");
	printf("  " SWITCH_GETOPT_LONG("-v, --verbose         ", "-v") "  Verbose\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 for quoting */
	if (argc < 2 || strcmp(argv[1], "test") != 0)
		return;

	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
	assert(strcmp(esc_shell(" ", buffer), "\" \"") == 0);
	assert(strcmp(esc_shell(" \" ", buffer), "\" \"\\\"\" \"") == 0);
	assert(strcmp(esc_shell("&|()<>^", buffer), "^&^|^(^)^<^>^^") == 0);
	assert(strcmp(esc_shell("&|()<>^ ", buffer), "\"&|()<>^ \"") == 0);
#else
	assert(strcmp(esc_shell(",._+:@%%/-", buffer), ",._+:@%%/-") == 0);
	assert(strcmp(esc_shell(" ", buffer), "\\ ") == 0);
#endif

	printf("Everything OK\n");

	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("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("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("Error closing the log file '%s'. %s.\n", file, strerror(errno));
			exit(EXIT_FAILURE);
			/* LCOV_EXCL_STOP */
		}
	}

	stdlog = 0;
}

/****************************************************************************/
/* config */

void config(char* conf, size_t conf_size, const char* argv0)
{
#ifdef _WIN32
	char* slash;

	pathimport(conf, conf_size, argv0);

	slash = strrchr(conf, '/');
	if (slash) {
		slash[1] = 0;
		pathcat(conf, conf_size, PACKAGE ".conf");
	} else {
		pathcpy(conf, conf_size, PACKAGE ".conf");
	}
#else
	(void)argv0;

#ifdef SYSCONFDIR
	/* if it exists, give precedence at sysconfdir, usually /usr/local/etc */
	if (access(SYSCONFDIR "/" PACKAGE ".conf", F_OK) == 0)
		pathcpy(conf, conf_size, SYSCONFDIR "/" PACKAGE ".conf");
	else /* otherwise fallback to plain /etc */
#endif
		pathcpy(conf, conf_size, "/etc/" PACKAGE ".conf");
#endif
}

/****************************************************************************/
/* main */

#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_NO_WARNINGS 286
#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

#if HAVE_GETOPT_LONG
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' },
	{ "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' },
	{ "audit-only", 0, 0, 'a' },
	{ "pre-hash", 0, 0, 'h' },
	{ "speed-test", 0, 0, 'T' }, /* undocumented speed test command */
	{ "gen-conf", 1, 0, 'C' },
	{ "verbose", 0, 0, 'v' },
	{ "quiet", 0, 0, 'q' }, /* undocumented quiet option */
	{ "gui", 0, 0, 'G' }, /* undocumented GUI interface option */
	{ "help", 0, 0, 'H' },
	{ "version", 0, 0, 'V' },

	/* 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 },

	/* 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 },

	/* Run some command after loading the state and before the command */
	{ "test-run", 1, 0, OPT_TEST_RUN },

	/* 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 },

	/* Disable annoying warnings */
	{ "no-warnings", 0, 0, OPT_NO_WARNINGS },

	/* 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 },

	/* 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

#define OPTIONS "c:f:d:mebp:o:S:B:L:i:l:ZEUDNFRahTC:vqHVG"

volatile int global_interrupt = 0;

/* LCOV_EXCL_START */
void signal_handler(int signum)
{
	(void)signum;

	/* report the request of interruption */
	global_interrupt = 1;
}
/* 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);
	sigaction(SIGQUIT, &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

int 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 plan;
	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;
	const char* run;
	int speedtest;
	int period;
	time_t t;
	struct tm* tm;
#if HAVE_LOCALTIME_R
	struct tm tm_res;
#endif
	int i;

	test(argc, argv);

	lock_init();

	/* defaults */
	config(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);
	period = 1000;
	filter_missing = 0;
	filter_error = 0;
	plan = SCRUB_AUTO;
	olderthan = SCRUB_AUTO;
	import_timestamp = 0;
	import_content = 0;
	log_file = 0;
	lock = 0;
	gen_conf = 0;
	speedtest = 0;
	run = 0;

	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("Invalid filter specification '%s'\n", optarg);
				log_fatal("Filters using relative paths are not supported. Ensure to add an initial slash\n");
				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("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) {
				plan = SCRUB_BAD;
			} else if (strcmp(optarg, "new") == 0) {
				plan = SCRUB_NEW;
			} else if (strcmp(optarg, "full") == 0) {
				plan = SCRUB_FULL;
			} else {
				plan = strtoul(optarg, &e, 10);
				if (!e || *e || plan > 100) {
					/* LCOV_EXCL_START */
					log_fatal("Invalid plan/percentage '%s'\n", optarg);
					exit(EXIT_FAILURE);
					/* LCOV_EXCL_STOP */
				}
			}
			break;
		case 'o' :
			olderthan = strtoul(optarg, &e, 10);
			if (!e || *e || olderthan > 1000) {
				/* LCOV_EXCL_START */
				log_fatal("Invalid number of days '%s'\n", optarg);
				exit(EXIT_FAILURE);
				/* LCOV_EXCL_STOP */
			}
			break;
		case 'S' :
			blockstart = strtoul(optarg, &e, 0);
			if (!e || *e) {
				/* LCOV_EXCL_START */
				log_fatal("Invalid start position '%s'\n", optarg);
				exit(EXIT_FAILURE);
				/* LCOV_EXCL_STOP */
			}
			break;
		case 'B' :
			blockcount = strtoul(optarg, &e, 0);
			if (!e || *e) {
				/* LCOV_EXCL_START */
				log_fatal("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 || *e) {
				/* LCOV_EXCL_START */
				log_fatal("Invalid error limit number '%s'\n", optarg);
				exit(EXIT_FAILURE);
				/* LCOV_EXCL_STOP */
			}
			break;
		case 'i' :
			if (import_timestamp) {
				/* LCOV_EXCL_START */
				log_fatal("Import directory '%s' already specified as '%s'\n", optarg, import_timestamp);
				exit(EXIT_FAILURE);
				/* LCOV_EXCL_STOP */
			}
			import_timestamp = optarg;
			break;
		case OPT_TEST_IMPORT_CONTENT :
			if (import_content) {
				/* LCOV_EXCL_START */
				log_fatal("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("Log file '%s' already specified as '%s'\n", optarg, log_file);
				exit(EXIT_FAILURE);
				/* LCOV_EXCL_STOP */
			}
			log_file = optarg;
			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 'a' :
			opt.auditonly = 1;
			break;
		case 'h' :
			opt.prehash = 1;
			break;
		case 'v' :
			++msg_level;
			break;
		case 'q' :
			--msg_level;
			break;
		case 'G' :
			opt.gui = 1;
			break;
		case 'H' :
			usage();
			exit(EXIT_SUCCESS);
		case 'V' :
			version();
			exit(EXIT_SUCCESS);
		case 'T' :
			speedtest = 1;
			break;
		case 'C' :
			gen_conf = optarg;
			break;
		case OPT_TEST_KILL_AFTER_SYNC :
			opt.kill_after_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;
			period = 50; /* reduce period of the speed test */
			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;
		case OPT_TEST_RUN :
			run = optarg;
			break;
		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_NO_WARNINGS :
			opt.no_warnings = 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("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("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("Unknown option '%c'\n", (char)c);
			exit(EXIT_FAILURE);
			/* LCOV_EXCL_STOP */
		}
	}

	os_init(opt.force_scan_winfind);
	raid_init();
	crc32c_init();

	if (speedtest != 0) {
		speed(period);
		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();
		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], "test-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], "devices") == 0) {
		operation = OPERATION_DEVICES;
	} else if (strcmp(argv[optind], "smart") == 0) {
		operation = OPERATION_SMART;
	} else {
		/* LCOV_EXCL_START */
		log_fatal("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("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_DEVICES :
	case OPERATION_SPINUP :
	case OPERATION_SPINDOWN :
		break;
	default :
		if (opt.force_device) {
			/* LCOV_EXCL_START */
			log_fatal("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("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("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("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("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("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("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("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("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("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 :
		if (!tommy_list_empty(&filterlist_file)) {
			/* LCOV_EXCL_START */
			log_fatal("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("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("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("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("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 :
	case OPERATION_SPINUP : /* we want to do it in different threads to avoid blocking */
		/* 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 :
	case OPERATION_SPINUP : /* we want to do it in different threads to avoid blocking */
		/* 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_DEVICES :
	case OPERATION_SMART :
		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 :
		/* we may need to use these commands during operations */
		opt.skip_lock = 1;
		break;
	}

	switch (operation) {
	case OPERATION_SMART :
		/* allow to run without configuration file */
		opt.auto_conf = 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, 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("Failed to create the lock file '%s'. %s.\n", state.lockfile, strerror(errno));
			} else {
				log_fatal("The lock file '%s' is already in use!\n", state.lockfile);
				log_fatal("SnapRAID is already in use!\n");
			}
			exit(EXIT_FAILURE);
			/* LCOV_EXCL_STOP */
		}
	}
#else
	(void)lock;
#endif

	if (operation == OPERATION_DIFF) {
		state_read(&state);

		ret = state_diff(&state);

		/* abort if sync needed */
		if (ret > 0)
			exit(EXIT_SYNC_NEEDED);
	} else if (operation == OPERATION_SYNC) {

		/* in the next state read ensures to clear all the past hashes in case */
		/* we are reading from an incomplete sync */
		/* The indeterminate hash are only for CHG/DELETED blocks for which we don't */
		/* know if the previous interrupted sync was able to update or not the parity. */
		/* The sync process instead needs to trust this information because it's used */
		/* to avoid to recompute the parity if all the input are equals as before. */

		/* In these cases we don't know if the old state is still the one */
		/* stored inside the parity, because after an aborted sync, the parity */
		/* may be or may be not have been updated with the data that may be now */
		/* deleted. Then we reset the hash to a bogus value. */

		/* An example for CHG blocks is: */
		/* - One file is added creating a CHG block with ZERO state */
		/* - Sync aborted after updating the parity to the new state, */
		/*   but without saving the content file representing this new BLK state. */
		/* - File is now deleted after the aborted sync */
		/* - Sync again, deleting the blocks over the CHG ones */
		/*   with the hash of CHG blocks not representing the real parity state */

		/* An example for DELETED blocks is: */
		/* - One file is deleted creating DELETED blocks */
		/* - Sync aborted after, updating the parity to the new state, */
		/*   but without saving the content file representing this new EMPTY state. */
		/* - Another file is added again over the DELETE ones */
		/*   with the hash of DELETED blocks not representing the real parity state */
		state.clear_past_hash = 1;

		state_read(&state);

		state_scan(&state);

		/* refresh the size info before the content write */
		state_refresh(&state);

		memory();

		/* intercept signals while operating */
		signal_init();

		/* run a test command if required */
		if (run != 0) {
			ret = system(run); /* ignore error */
			if (ret != 0) {
				/* LCOV_EXCL_START */
				log_fatal("Error executing command '%s'.\n", run);
				exit(EXIT_FAILURE);
				/* LCOV_EXCL_STOP */
			}
		}

		/* 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);

		/* save the new state if required */
		if (!opt.kill_after_sync) {
			if ((state.need_write || state.opt.force_content_write))
				state_write(&state);
		} else {
			log_fatal("WARNING! Skipped writing state due to --test-kill-after-sync option.\n");
		}

		/* abort if required */
		if (ret != 0) {
			/* LCOV_EXCL_START */
			exit(EXIT_FAILURE);
			/* LCOV_EXCL_STOP */
		}
	} else if (operation == OPERATION_DRY) {
		state_read(&state);

		/* filter */
		state_skip(&state);
		state_filter(&state, &filterlist_file, &filterlist_disk, filter_missing, filter_error);

		memory();

		/* intercept signals while operating */
		signal_init();

		state_dry(&state, blockstart, blockcount);
	} else if (operation == OPERATION_REHASH) {
		state_read(&state);

		/* intercept signals while operating */
		signal_init();

		state_rehash(&state);

		/* save the new state if required */
		if (state.need_write)
			state_write(&state);
	} else if (operation == OPERATION_SCRUB) {
		state_read(&state);

		memory();

		/* intercept signals while operating */
		signal_init();

		ret = state_scrub(&state, plan, olderthan);

		/* save the new state if required */
		if (state.need_write || state.opt.force_content_write)
			state_write(&state);

		/* abort if required */
		if (ret != 0) {
			/* LCOV_EXCL_START */
			exit(EXIT_FAILURE);
			/* LCOV_EXCL_STOP */
		}
	} 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);

		memory();
	} else if (operation == OPERATION_TOUCH) {
		state_read(&state);

		state_touch(&state);

		/* intercept signals while operating */
		signal_init();

		state_write(&state);

		memory();
	} else if (operation == OPERATION_SPINUP) {
		state_device(&state, DEVICE_UP, &filterlist_disk);
	} else if (operation == OPERATION_SPINDOWN) {
		state_device(&state, DEVICE_DOWN, &filterlist_disk);
	} else if (operation == OPERATION_DEVICES) {
		state_device(&state, DEVICE_LIST, 0);
	} else if (operation == OPERATION_SMART) {
		state_device(&state, DEVICE_SMART, 0);
	} else if (operation == OPERATION_STATUS) {
		state_read(&state);

		memory();

		state_status(&state);
	} 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 {
		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();

		if (operation == OPERATION_CHECK) {
			ret = state_check(&state, 0, blockstart, blockcount);
		} else { /* it's fix */
			ret = state_check(&state, 1, blockstart, blockcount);
		}

		/* abort if required */
		if (ret != 0) {
			/* LCOV_EXCL_START */
			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("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();

	return EXIT_SUCCESS;
}

