// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Copyright (C) 2018-2024 Oracle.  All Rights Reserved.
 * Author: Darrick J. Wong <djwong@kernel.org>
 */
#include "xfs.h"
#include <pthread.h>
#include <stdlib.h>
#include <paths.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/statvfs.h>
#include "platform_defs.h"
#include "input.h"
#include "libfrog/paths.h"
#include "xfs_scrub.h"
#include "common.h"
#include "descr.h"
#include "unicrash.h"
#include "progress.h"
#include "libfrog/histogram.h"
#include "libfrog/systemd.h"

/*
 * XFS Online Metadata Scrub (and Repair)
 *
 * The XFS scrubber uses custom XFS ioctls to probe more deeply into the
 * internals of the filesystem.  It takes advantage of scrubbing ioctls
 * to check all the records stored in a metadata object and to
 * cross-reference those records against the other filesystem metadata.
 *
 * After the program gathers command line arguments to figure out
 * exactly what the program is going to do, scrub execution is split up
 * into several separate phases:
 *
 * The "find geometry" phase queries XFS for the filesystem geometry.
 * The block devices for the data, realtime, and log devices are opened.
 * Kernel ioctls are test-queried to see if they actually work (the scrub
 * ioctl in particular), and any other filesystem-specific information
 * is gathered.
 *
 * In the "check internal metadata" phase, we call the metadata scrub
 * ioctl to check the filesystem's internal per-AG btrees.  This
 * includes the AG superblock, AGF, AGFL, and AGI headers, freespace
 * btrees, the regular and free inode btrees, the reverse mapping
 * btrees, and the reference counting btrees.  If the realtime device is
 * enabled, the realtime bitmap and reverse mapping btrees are checked.
 * Quotas, if enabled, are also checked in this phase.
 *
 * Each AG (and the realtime device) has its metadata checked in a
 * separate thread for better performance.  Errors in the internal
 * metadata can be fixed here prior to the inode scan; refer to the
 * section about the "repair filesystem" phase for more information.
 *
 * The "scan all inodes" phase uses BULKSTAT to scan all the inodes in
 * an AG in disk order.  The BULKSTAT information provides enough
 * information to construct a file handle that is used to check the
 * following parts of every file:
 *
 *  - The inode record
 *  - All three block forks (data, attr, CoW)
 *  - If it's a symlink, the symlink target.
 *  - If it's a directory, the directory entries.
 *  - All extended attributes
 *  - The parent pointer
 *
 * Multiple threads are started to check each the inodes of each AG in
 * parallel.  Errors in file metadata can be fixed here; see the section
 * about the "repair filesystem" phase for more information.
 *
 * Next comes the (configurable) "repair filesystem" phase.  The user
 * can instruct this program to fix all problems encountered; to fix
 * only optimality problems and leave the corruptions; or not to touch
 * the filesystem at all.  Any metadata repairs that did not succeed in
 * the previous two phases are retried here; if there are uncorrectable
 * errors, xfs_scrub stops here.
 *
 * To perform the actual repairs (or optimizations), we iterate all the
 * items on the per-AG action item list and ask the kernel to repair
 * them.  Items which are successfully repaired are removed from the
 * list.  If an item is not acted upon successfully (or the kernel asks us
 * to try again), we retry the actions until there is nothing left to
 * fix or we fail to make forward progress.  In that event, the
 * unfinished items are recorded as errors.  If there are no errors at
 * this point, we call FSTRIM on the filesystem.
 *
 * The next phase is the "check directory tree" phase.  In this phase,
 * every directory is opened (via file handle) to confirm that each
 * directory is connected to the root.  Directory entries are checked
 * for ambiguous Unicode normalization mappings, which is to say that we
 * look for pairs of entries whose utf-8 strings normalize to the same
 * code point sequence and map to different inodes, because that could
 * be used to trick a user into opening the wrong file.  The names of
 * extended attributes are checked for Unicode normalization collisions.
 *
 * In the "verify data file integrity" phase, we employ GETFSMAP to read
 * the reverse-mappings of all AGs and issue direct-reads of the
 * underlying disk blocks.  We rely on the underlying storage to have
 * checksummed the data blocks appropriately.  Multiple threads are
 * started to check each AG in parallel; a separate thread pool is used
 * to handle the direct reads.
 *
 * In the "check summary counters" phase, use GETFSMAP to tally up the
 * blocks and BULKSTAT to tally up the inodes we saw and compare that to
 * the statfs output.  This gives the user a rough estimate of how
 * thorough the scrub was.
 */

/*
 * Known debug tweaks (pass -d and set the environment variable):
 * XFS_SCRUB_FORCE_ERROR	-- pretend all metadata is corrupt
 * XFS_SCRUB_FORCE_REPAIR	-- repair all metadata even if it's ok
 * XFS_SCRUB_NO_KERNEL		-- pretend there is no kernel ioctl
 * XFS_SCRUB_PHASE		-- run only this scrub phase
 * XFS_SCRUB_THREADS		-- start exactly this number of threads
 * XFS_SCRUB_DISK_ERROR_INTERVAL-- simulate a disk error every this many bytes
 * XFS_SCRUB_DISK_VERIFY_SKIP	-- pretend disk verify read calls succeeded
 * XFS_SCRUB_FORCE_SINGLE	-- fall back to ioctl-per-item scrubbing
 *
 * Available even in non-debug mode:
 * SERVICE_MODE			-- compress all error codes to 1 for LSB
 *				   service action compliance
 */

/* Program name; needed for libfrog error reports. */
char				*progname = "xfs_scrub";

/* Debug level; higher values mean more verbosity. */
unsigned int			debug;

/* Display resource usage at the end of each phase? */
static bool			display_rusage;

/* Background mode; higher values insert more pauses between scrub calls. */
unsigned int			bg_mode;

/* Number of threads we're allowed to use. */
unsigned int			force_nr_threads;

/* Verbosity; higher values print more information. */
bool				verbose;

/* Should we scrub the data blocks? */
static bool			scrub_data;

/* Size of a memory page. */
long				page_size;

/* Should we FSTRIM after a successful run? */
bool				want_fstrim = true;

/* If stdout/stderr are ttys, we can use richer terminal control. */
bool				stderr_isatty;
bool				stdout_isatty;

/*
 * If we are running as a service, we need to be careful about what
 * error codes we return to the calling process.
 */
bool				is_service;

/* Set to true if the kernel supports XFS_SCRUB_IFLAG_FORCE_REBUILD */
bool				use_force_rebuild;

/* Should we count informational messages as warnings? */
bool				info_is_warning;

#define SCRUB_RET_SUCCESS	(0)	/* no problems left behind */
#define SCRUB_RET_CORRUPT	(1)	/* corruption remains on fs */
#define SCRUB_RET_UNOPTIMIZED	(2)	/* fs could be optimized */
#define SCRUB_RET_OPERROR	(4)	/* operational problems */
#define SCRUB_RET_SYNTAX	(8)	/* cmdline args rejected */

static void __attribute__((noreturn))
usage(void)
{
	fprintf(stderr, _("Usage: %s [OPTIONS] mountpoint\n"), progname);
	fprintf(stderr, "\n");
	fprintf(stderr, _("Options:\n"));
	fprintf(stderr, _("  -a count     Stop after this many errors are found.\n"));
	fprintf(stderr, _("  -b           Background mode.\n"));
	fprintf(stderr, _("  -C fd        Print progress information to this fd.\n"));
	fprintf(stderr, _("  -e behavior  What to do if errors are found.\n"));
	fprintf(stderr, _("  -k           Do not FITRIM the free space.\n"));
	fprintf(stderr, _("  -m path      Path to /etc/mtab.\n"));
	fprintf(stderr, _("  -n           Dry run.  Do not modify anything.\n"));
	fprintf(stderr, _("  -p           Only optimize, do not fix corruptions.\n"));
	fprintf(stderr, _("  -T           Display timing/usage information.\n"));
	fprintf(stderr, _("  -v           Verbose output.\n"));
	fprintf(stderr, _("  -V           Print version.\n"));
	fprintf(stderr, _("  -x           Scrub file data too.\n"));

	exit(SCRUB_RET_SYNTAX);
}

#ifndef RUSAGE_BOTH
# define RUSAGE_BOTH		(-2)
#endif

/* Get resource usage for ourselves and all children. */
static int
scrub_getrusage(
	struct rusage		*usage)
{
	struct rusage		cusage;
	int			err;

	err = getrusage(RUSAGE_BOTH, usage);
	if (!err)
		return err;

	err = getrusage(RUSAGE_SELF, usage);
	if (err)
		return err;

	err = getrusage(RUSAGE_CHILDREN, &cusage);
	if (err)
		return err;

	usage->ru_minflt += cusage.ru_minflt;
	usage->ru_majflt += cusage.ru_majflt;
	usage->ru_nswap += cusage.ru_nswap;
	usage->ru_inblock += cusage.ru_inblock;
	usage->ru_oublock += cusage.ru_oublock;
	usage->ru_msgsnd += cusage.ru_msgsnd;
	usage->ru_msgrcv += cusage.ru_msgrcv;
	usage->ru_nsignals += cusage.ru_nsignals;
	usage->ru_nvcsw += cusage.ru_nvcsw;
	usage->ru_nivcsw += cusage.ru_nivcsw;
	return 0;
}

/*
 * Scrub Phase Dispatch
 *
 * The operations of the scrub program are split up into several
 * different phases.  Each phase builds upon the metadata checked in the
 * previous phase, which is to say that we may skip phase (X + 1) if our
 * scans in phase (X) reveal corruption.  A phase may be skipped
 * entirely.
 */

/* Resource usage for each phase. */
struct phase_rusage {
	struct rusage		ruse;
	struct timeval		time;
	unsigned long long	verified_bytes;
	void			*brk_start;
	const char		*descr;
};

/* Operations for each phase. */
#define DATASCAN_DUMMY_FN	((void *)1)
#define REPAIR_DUMMY_FN		((void *)2)
#define FSTRIM_DUMMY_FN		((void *)3)
struct phase_ops {
	char		*descr;
	int		(*fn)(struct scrub_ctx *ctx);
	int		(*estimate_work)(struct scrub_ctx *ctx, uint64_t *items,
					 unsigned int *threads, int *rshift);
	bool		must_run;
};

/* Start tracking resource usage for a phase. */
static int
phase_start(
	struct phase_rusage	*pi,
	unsigned int		phase,
	const char		*descr)
{
	int			error;

	memset(pi, 0, sizeof(*pi));
	error = scrub_getrusage(&pi->ruse);
	if (error) {
		perror(_("getrusage"));
		return error;
	}
	pi->brk_start = sbrk(0);

	error = gettimeofday(&pi->time, NULL);
	if (error) {
		perror(_("gettimeofday"));
		return error;
	}

	pi->descr = descr;
	if ((verbose || display_rusage) && descr) {
		fprintf(stdout, _("Phase %u: %s\n"), phase, descr);
		fflush(stdout);
	}
	return error;
}

static inline unsigned long long
kbytes(unsigned long long x)
{
	return (x + 1023) / 1024;
}

static void
report_mem_usage(
	const char			*phase,
	const struct phase_rusage	*pi)
{
#if defined(HAVE_MALLINFO2) || defined(HAVE_MALLINFO)
# ifdef HAVE_MALLINFO2
	struct mallinfo2		mall_now = mallinfo2();
# else
	struct mallinfo			mall_now = mallinfo();
# endif
	fprintf(stdout, _("%sMemory used: %lluk/%lluk (%lluk/%lluk), "),
		phase,
		kbytes(mall_now.arena), kbytes(mall_now.hblkhd),
		kbytes(mall_now.uordblks), kbytes(mall_now.fordblks));
#else
	fprintf(stdout, _("%sMemory used: %lluk, "),
		phase,
		kbytes(((char *) sbrk(0)) - ((char *) pi->brk_start)));
#endif
}

/* Report usage stats. */
static int
phase_end(
	struct phase_rusage	*pi,
	unsigned int		phase)
{
	struct rusage		ruse_now;
	struct timeval		time_now;
	char			phasebuf[DESCR_BUFSZ];
	double			dt;
	unsigned long long	in, out;
	unsigned long long	io;
	double			i, o, t;
	double			din, dout, dtot;
	char			*iu, *ou, *tu, *dinu, *doutu, *dtotu;
	int			error;

	if (!display_rusage)
		return 0;

	error = gettimeofday(&time_now, NULL);
	if (error) {
		perror(_("gettimeofday"));
		return error;
	}
	dt = timeval_subtract(&time_now, &pi->time);

	error = scrub_getrusage(&ruse_now);
	if (error) {
		perror(_("getrusage"));
		return error;
	}

	if (phase)
		snprintf(phasebuf, DESCR_BUFSZ, _("Phase %u: "), phase);
	else
		phasebuf[0] = 0;

	report_mem_usage(phasebuf, pi);

	fprintf(stdout, _("time: %5.2f/%5.2f/%5.2fs\n"),
		timeval_subtract(&time_now, &pi->time),
		timeval_subtract(&ruse_now.ru_utime, &pi->ruse.ru_utime),
		timeval_subtract(&ruse_now.ru_stime, &pi->ruse.ru_stime));

	/* I/O usage */
	in =  ((unsigned long long)ruse_now.ru_inblock -
			pi->ruse.ru_inblock) << BBSHIFT;
	out = ((unsigned long long)ruse_now.ru_oublock -
			pi->ruse.ru_oublock) << BBSHIFT;
	io = in + out;
	if (io) {
		i = auto_space_units(in, &iu);
		o = auto_space_units(out, &ou);
		t = auto_space_units(io, &tu);
		din = auto_space_units(in / dt, &dinu);
		dout = auto_space_units(out / dt, &doutu);
		dtot = auto_space_units(io / dt, &dtotu);
		fprintf(stdout,
_("%sI/O: %.1f%s in, %.1f%s out, %.1f%s tot\n"),
			phasebuf, i, iu, o, ou, t, tu);
		fprintf(stdout,
_("%sI/O rate: %.1f%s/s in, %.1f%s/s out, %.1f%s/s tot\n"),
			phasebuf, din, dinu, dout, doutu, dtot, dtotu);
	}
	fflush(stdout);

	return 0;
}

/* Run all the phases of the scrubber. */
static bool
run_scrub_phases(
	struct scrub_ctx	*ctx,
	FILE			*progress_fp)
{
	struct phase_ops phases[] =
	{
		{
			.descr = _("Find filesystem geometry."),
			.fn = phase1_func,
			.must_run = true,
		},
		{
			.descr = _("Check internal metadata."),
			.fn = phase2_func,
			.estimate_work = phase2_estimate,
		},
		{
			.descr = _("Scan all inodes."),
			.fn = phase3_func,
			.estimate_work = phase3_estimate,
		},
		{
			.descr = _("Defer filesystem repairs."),
			.fn = REPAIR_DUMMY_FN,
			.estimate_work = phase4_estimate,
		},
		{
			.descr = _("Check directory tree."),
			.fn = phase5_func,
			.estimate_work = phase5_estimate,
		},
		{
			.descr = _("Verify data file integrity."),
			.fn = DATASCAN_DUMMY_FN,
			.estimate_work = phase6_estimate,
		},
		{
			.descr = _("Check summary counters."),
			.fn = phase7_func,
			.must_run = true,
		},
		{
			.descr = _("Trim filesystem storage."),
			.fn = FSTRIM_DUMMY_FN,
			.estimate_work = phase8_estimate,
		},
		{
			NULL
		},
	};
	struct phase_rusage	pi;
	struct phase_ops	*sp;
	uint64_t		max_work;
	unsigned int		debug_phase = 0;
	unsigned int		phase;
	int			rshift;
	int			ret = 0;

	if (debug_tweak_on("XFS_SCRUB_PHASE"))
		debug_phase = atoi(getenv("XFS_SCRUB_PHASE"));

	/* Run all phases of the scrub tool. */
	for (phase = 1, sp = phases; sp->fn; sp++, phase++) {
		/* Turn on certain phases if user said to. */
		if (sp->fn == DATASCAN_DUMMY_FN && scrub_data) {
			sp->fn = phase6_func;
		} else if (sp->fn == FSTRIM_DUMMY_FN && want_fstrim) {
			sp->fn = phase8_func;
		} else if (sp->fn == REPAIR_DUMMY_FN &&
			   ctx->mode == SCRUB_MODE_REPAIR) {
			sp->descr = _("Repair filesystem.");
			sp->fn = phase4_func;
			sp->must_run = true;
		} else if (sp->fn == REPAIR_DUMMY_FN &&
			   ctx->mode == SCRUB_MODE_PREEN) {
			sp->descr = _("Optimize filesystem.");
			sp->fn = phase4_func;
			sp->must_run = true;
		}

		/* Skip certain phases unless they're turned on. */
		if (sp->fn == REPAIR_DUMMY_FN ||
		    sp->fn == DATASCAN_DUMMY_FN ||
		    sp->fn == FSTRIM_DUMMY_FN)
			continue;

		/* Allow debug users to force a particular phase. */
		if (debug_phase && phase != debug_phase && !sp->must_run)
			continue;

		/* Run this phase. */
		ret = phase_start(&pi, phase, sp->descr);
		if (ret)
			break;
		if (sp->estimate_work) {
			unsigned int		work_threads;

			ret = sp->estimate_work(ctx, &max_work,
					&work_threads, &rshift);
			if (ret)
				break;

			/*
			 * The thread that starts the worker threads is also
			 * allowed to contribute to the progress counters and
			 * whatever other per-thread data we need to allocate.
			 */
			work_threads++;
			ret = progress_init_phase(ctx, progress_fp, phase,
					max_work, rshift, work_threads);
			if (ret)
				break;
			ret = descr_init_phase(ctx, work_threads);
		} else {
			ret = progress_init_phase(ctx, NULL, phase, 0, 0, 0);
			if (ret)
				break;
			ret = descr_init_phase(ctx, 1);
		}
		if (ret)
			break;
		ret = sp->fn(ctx);
		if (ret) {
			str_info(ctx, ctx->mntpoint,
_("Scrub aborted after phase %d."),
					phase);
			break;
		}
		progress_end_phase();
		descr_end_phase();
		ret = phase_end(&pi, phase);
		if (ret)
			break;

		/* Did background scrub get canceled on us? */
		if (ctx->mode == SCRUB_MODE_NONE)
			break;

		/* Too many errors? */
		if (scrub_excessive_errors(ctx)) {
			ret = ECANCELED;
			break;
		}
	}

	return ret;
}

static void
report_modifications(
	struct scrub_ctx	*ctx)
{
	if (ctx->repairs == 0 && ctx->preens == 0)
		return;

	if (ctx->repairs && ctx->preens)
		fprintf(stdout,
_("%s: repairs made: %llu; optimizations made: %llu.\n"),
				ctx->mntpoint, ctx->repairs, ctx->preens);
	else if (ctx->preens == 0)
		fprintf(stdout,
_("%s: repairs made: %llu.\n"),
				ctx->mntpoint, ctx->repairs);
	else if (ctx->repairs == 0)
		fprintf(stdout,
_("%s: optimizations made: %llu.\n"),
				ctx->mntpoint, ctx->preens);
	fflush(stdout);
}

static void
report_outcome(
	struct scrub_ctx	*ctx)
{
	unsigned long long	actionable_errors;

	actionable_errors = ctx->corruptions_found + ctx->runtime_errors;

	if (actionable_errors == 0 &&
	    ctx->unfixable_errors == 0 &&
	    ctx->warnings_found == 0) {
		log_info(ctx, _("No problems found."));
		return;
	}

	if (ctx->unfixable_errors) {
		fprintf(stderr, _("%s: unfixable errors found: %llu\n"),
				ctx->mntpoint, ctx->unfixable_errors);
		log_err(ctx, _("unfixable errors found: %llu"),
				ctx->unfixable_errors);
	}

	if (ctx->corruptions_found > 0) {
		fprintf(stderr, _("%s: corruptions found: %llu\n"),
				ctx->mntpoint, ctx->corruptions_found);
		log_err(ctx, _("corruptions found: %llu"),
				ctx->corruptions_found);
	}

	if (ctx->runtime_errors > 0) {
		fprintf(stderr, _("%s: operational errors found: %llu\n"),
				ctx->mntpoint, ctx->runtime_errors);
		log_err(ctx, _("operational errors found: %llu"),
				ctx->runtime_errors);
	}

	if (ctx->warnings_found > 0) {
		fprintf(stderr, _("%s: warnings found: %llu\n"), ctx->mntpoint,
				ctx->warnings_found);
		log_warn(ctx, _("warnings found: %llu"), ctx->warnings_found);
	}

	/*
	 * Don't advise the user to run repair unless we were successful in
	 * setting up the scrub and we actually saw corruptions.  Warnings
	 * are not corruptions.
	 */
	if (ctx->scrub_setup_succeeded && actionable_errors > 0) {
		char		*msg;

		if (ctx->mode != SCRUB_MODE_REPAIR)
			msg = _("%s: Re-run xfs_scrub without -n.\n");
		else
			msg = _("%s: Unmount and run xfs_repair.\n");

		fprintf(stderr, msg, ctx->mntpoint);
	}
}

/* Compile-time features discoverable via version strings */
#ifdef HAVE_LIBICU
# define XFS_SCRUB_HAVE_UNICODE	"+"
#else
# define XFS_SCRUB_HAVE_UNICODE	"-"
#endif

/*
 * -o: user-supplied override options
 */
enum o_opt_nums {
	IWARN = 0,
	FSTRIM_PCT,
	AUTOFSCK,
	O_MAX_OPTS,
};

static char *o_opts[] = {
	[IWARN]			= "iwarn",
	[FSTRIM_PCT]		= "fstrim_pct",
	[AUTOFSCK]		= "autofsck",
	[O_MAX_OPTS]		= NULL,
};

static void
parse_o_opts(
	struct scrub_ctx	*ctx,
	char			*p)
{
	double			dval;

	while (*p != '\0')  {
		char		*val;
		char		*endp;

		switch (getsubopt(&p, o_opts, &val))  {
		case IWARN:
			if (val) {
				fprintf(stderr,
 _("iwarn does not take an argument\n"));
				usage();
			}
			info_is_warning = true;
			break;
		case FSTRIM_PCT:
			if (!val) {
				fprintf(stderr,
 _("-o fstrim_pct requires a parameter\n"));
				usage();
			}

			errno = 0;
			dval = strtod(val, &endp);

			if (*endp) {
				fprintf(stderr,
 _("-o fstrim_pct must be a floating point number\n"));
				usage();
			}
			if (errno) {
				fprintf(stderr,
 _("-o fstrim_pct: %s\n"),
						strerror(errno));
				usage();
			}
			if (dval <= 0 || dval > 100) {
				fprintf(stderr,
 _("-o fstrim_pct must be larger than 0 and less than 100\n"));
				usage();
			}

			ctx->fstrim_block_pct = dval / 100.0;
			break;
		case AUTOFSCK:
			if (val) {
				fprintf(stderr,
 _("-o autofsck does not take an argument\n"));
				usage();
			}
			ctx->mode = SCRUB_MODE_NONE;
			break;
		default:
			usage();
			break;
		}
	}
}

enum long_opt_nr {
	LOPT_HELP,
	LOPT_SVCNAME,

	LOPT_MAX,
};

int
main(
	int			argc,
	char			**argv)
{
	struct scrub_ctx	ctx = {
		.fstrim_block_pct = FSTRIM_BLOCK_PCT_DEFAULT,
		.lock		= (pthread_mutex_t)PTHREAD_MUTEX_INITIALIZER,
		.mode		= SCRUB_MODE_REPAIR,
		.error_action	= ERRORS_CONTINUE,
	};
	struct phase_rusage	all_pi;
	char			*mtab = NULL;
	FILE			*progress_fp = NULL;
	struct fs_path		*fsp;
	int			option_index;
	int			vflag = 0;
	int			c;
	int			fd;
	int			ret = SCRUB_RET_SUCCESS;
	int			error;

	hist_init(&ctx.datadev_hist);
	hist_init(&ctx.rtdev_hist);

	progname = basename(argv[0]);
	setlocale(LC_ALL, "");
	bindtextdomain(PACKAGE, LOCALEDIR);
	textdomain(PACKAGE);
	if (unicrash_load()) {
		fprintf(stderr,
	_("%s: couldn't initialize Unicode library.\n"),
				progname);
		goto out_unicrash;
	}

	struct option long_options[] = {
		[LOPT_HELP]	   = {"help", no_argument, NULL, 0 },
		[LOPT_SVCNAME]	   = {"svcname", no_argument, &ctx.print_svcname, 1 },

		[LOPT_MAX]	   = {NULL, 0, NULL, 0 },
	};

	while ((c = getopt_long(argc, argv, "a:bC:de:kM:m:no:pTvxV",
				long_options, &option_index)) != EOF) {
		switch (c) {
		case 0:
			switch (option_index) {
			case LOPT_HELP:
				usage();
				break;
			default:
				break;
			}
			break;
		case 'a':
			ctx.max_errors = cvt_u64(optarg, 10);
			if (errno) {
				perror(optarg);
				usage();
			}
			break;
		case 'b':
			force_nr_threads = 1;
			bg_mode++;
			break;
		case 'C':
			errno = 0;
			fd = cvt_u32(optarg, 10);
			if (errno) {
				perror(optarg);
				usage();
			}
			progress_fp = fdopen(fd, "w");
			if (!progress_fp) {
				perror(optarg);
				usage();
			}
			break;
		case 'd':
			debug++;
			break;
		case 'e':
			if (!strcmp("continue", optarg))
				ctx.error_action = ERRORS_CONTINUE;
			else if (!strcmp("shutdown", optarg))
				ctx.error_action = ERRORS_SHUTDOWN;
			else {
				fprintf(stderr,
	_("Unknown error behavior \"%s\".\n"),
						optarg);
				usage();
			}
			break;
		case 'k':
			want_fstrim = false;
			break;
		case 'M':
			ctx.actual_mntpoint = optarg;
			break;
		case 'm':
			mtab = optarg;
			break;
		case 'n':
			if (ctx.mode != SCRUB_MODE_REPAIR) {
				fprintf(stderr, _("Cannot use -n with -p.\n"));
				usage();
			}
			ctx.mode = SCRUB_MODE_DRY_RUN;
			break;
		case 'o':
			parse_o_opts(&ctx, optarg);
			break;
		case 'p':
			if (ctx.mode != SCRUB_MODE_REPAIR) {
				fprintf(stderr, _("Cannot use -p with -n.\n"));
				usage();
			}
			ctx.mode = SCRUB_MODE_PREEN;
			break;
		case 'T':
			display_rusage = true;
			break;
		case 'v':
			verbose = true;
			break;
		case 'V':
			vflag++;
			break;
		case 'x':
			scrub_data = true;
			break;
		default:
			usage();
		}
	}

	if (vflag) {
		if (vflag == 1)
			fprintf(stdout, _("%s version %s\n"),
					progname, VERSION);
		else
			fprintf(stdout, _("%s version %s %sUnicode\n"),
					progname, VERSION,
					XFS_SCRUB_HAVE_UNICODE);
		fflush(stdout);
		return SCRUB_RET_SUCCESS;
	}

	/* Override thread count if debugger */
	if (debug_tweak_on("XFS_SCRUB_THREADS")) {
		unsigned int	x;

		x = cvt_u32(getenv("XFS_SCRUB_THREADS"), 10);
		if (errno) {
			perror("nr_threads");
			usage();
		}
		force_nr_threads = x;
	}

	if (optind != argc - 1)
		usage();

	ctx.mntpoint = argv[optind];
	if (!ctx.actual_mntpoint)
		ctx.actual_mntpoint = ctx.mntpoint;

	if (ctx.print_svcname) {
		char		unitname[PATH_MAX];
		const char	*template =
			scrub_data ? XFS_SCRUB_MEDIA_SVCNAME :
				     XFS_SCRUB_SVCNAME;

		ret = systemd_path_instance_unit_name(template,
				ctx.mntpoint, unitname, sizeof(unitname));
		if (ret) {
			perror(ctx.mntpoint);
			return EXIT_FAILURE;
		}

		printf("%s\n", unitname);
		return EXIT_SUCCESS;
	}

	stdout_isatty = isatty(STDOUT_FILENO);
	stderr_isatty = isatty(STDERR_FILENO);

	/* If interactive, start the progress bar. */
	if (stdout_isatty && !progress_fp)
		progress_fp = fdopen(1, "w+");

	is_service = systemd_is_service();

	/* Initialize overall phase stats. */
	error = phase_start(&all_pi, 0, NULL);
	if (error)
		return SCRUB_RET_OPERROR;

	/* Find the mount record for the passed-in argument. */
	if (stat(ctx.actual_mntpoint, &ctx.mnt_sb) < 0) {
		fprintf(stderr,
			_("%s: could not stat: %s: %s\n"),
			progname, argv[optind], strerror(errno));
		ctx.runtime_errors++;
		goto out;
	}

	/*
	 * If the user did not specify an explicit mount table, try to use
	 * /proc/mounts if it is available, else /etc/mtab.  We prefer
	 * /proc/mounts because it is kernel controlled, while /etc/mtab
	 * may contain garbage that userspace tools like pam_mounts wrote
	 * into it.
	 */
	if (!mtab) {
		if (access(_PATH_PROC_MOUNTS, R_OK) == 0)
			mtab = _PATH_PROC_MOUNTS;
		else
			mtab = _PATH_MOUNTED;
	}

	fs_table_initialise(0, NULL, 0, NULL);
	fsp = fs_table_lookup_mount(ctx.actual_mntpoint);
	if (!fsp) {
		fprintf(stderr, _("%s: Not a XFS mount point.\n"),
				ctx.mntpoint);
		ret |= SCRUB_RET_SYNTAX;
		goto out;
	}
	memcpy(&ctx.fsinfo, fsp, sizeof(struct fs_path));

	/* Set up a page-aligned buffer for read verification. */
	page_size = sysconf(_SC_PAGESIZE);
	if (page_size < 0) {
		str_errno(&ctx, ctx.mntpoint);
		goto out;
	}

	if (debug_tweak_on("XFS_SCRUB_FORCE_REPAIR"))
		ctx.mode = SCRUB_MODE_REPAIR;

	/* Scrub a filesystem. */
	error = run_scrub_phases(&ctx, progress_fp);
	if (error && ctx.runtime_errors == 0)
		ctx.runtime_errors++;

	/*
	 * Excessive errors will cause the scrub phases to bail out early.
	 * We don't want every thread yelling that into the output, so check
	 * if we hit the threshold and tell the user *once*.
	 */
	if (scrub_excessive_errors(&ctx))
		str_info(&ctx, ctx.mntpoint, _("Too many errors; aborting."));

	if (debug_tweak_on("XFS_SCRUB_FORCE_ERROR"))
		str_info(&ctx, ctx.mntpoint, _("Injecting error."));

	/* Clean up scan data. */
	error = scrub_cleanup(&ctx);
	if (error && ctx.runtime_errors == 0)
		ctx.runtime_errors++;

out:
	report_modifications(&ctx);
	report_outcome(&ctx);

	if (ctx.corruptions_found) {
		if (ctx.error_action == ERRORS_SHUTDOWN)
			xfs_shutdown_fs(&ctx);
		ret |= SCRUB_RET_CORRUPT;
	}
	if (ctx.warnings_found)
		ret |= SCRUB_RET_UNOPTIMIZED;
	if (ctx.runtime_errors)
		ret |= SCRUB_RET_OPERROR;
	phase_end(&all_pi, 0);
	if (progress_fp && fileno(progress_fp) != 1)
		fclose(progress_fp);
out_unicrash:
	unicrash_unload();

	hist_free(&ctx.datadev_hist);
	hist_free(&ctx.rtdev_hist);

	if (is_service) {
		/*
		 * Note: We don't count a lack of kernel support as a service
		 * failure because we haven't determined that there's anything
		 * wrong with the filesystem.
		 */
		if (!ctx.scrub_setup_succeeded)
			ret = 0;
		return systemd_service_exit(ret);
	}

	return ret;
}
