#define _GNU_SOURCE
#define _FILE_OFFSET_BITS 64

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <inttypes.h>
#include <errno.h>
#include <limits.h>
#include <string.h>
#include <getopt.h>
#include <stdbool.h>

#include "dev.h"
#include "vm.h"

typedef long long ns_t;

#define returnif(x) do { typeof(x) __x = (x); if (__x < 0) return (__x); } while (0)

static ns_t ns_min(int count, ns_t data[])
{
	int i;
	ns_t min = LLONG_MAX;

	for (i=0; i<count; i++) {
		if (data[i] < min)
			min = data[i];
	}

	return min;
}

#if 0
static void ns_min_elements(int count, ns_t target[], ns_t new[])
{
	int i;

	for (i=0; i<count; i++) {
		if (new[i] < target[i] || target[i] == 0)
			target[i] = new[i];
	}
}
#endif

static ns_t ns_max(int count, ns_t data[])
{
	int i;
	ns_t max = 0;

	for (i=0; i<count; i++) {
		if (data[i] > max)
			max = data[i];
	}

	return max;
}

static ns_t ns_avg(int count, ns_t data[])
{
	int i;
	ns_t sum = 0;

	for (i=0; i<count; i++) {
		sum += data[i];
	}

	return sum / i;
}

static void format_ns(char *out, ns_t ns)
{
	if (ns < 1000)
		snprintf(out, 8, "%lldns", ns);
	else if (ns < 1000 * 1000)
		snprintf(out, 8, "%.3gµs", ns / 1000.0);
	else if (ns < 1000 * 1000 * 1000)
		snprintf(out, 8, "%.3gms", ns / 1000000.0);
	else {
		snprintf(out, 8, "%.4gs", ns / 1000000000.0);
	}
}

static inline void print_ns(ns_t ns)
{
	char buf[8];
	format_ns(buf, ns);
	puts(buf);
}

static void regression(ns_t ns[], off_t bytes[], int count, ns_t *atime, float *throughput)
{
	int i;
	float sum_x = 0, sum_xx = 0;
	float sum_y = 0, sum_xy = 0;
	float slope, intercept;
	char buf[8];

	for (i = 0; i < count; i++) {
		sum_x	+= (float)bytes[i];
		sum_xx	+= (float)bytes[i] * (float)bytes[i];
		sum_y	+= (float)ns[i];
		sum_xy	+= (float)ns[i] * (float)bytes[i];
	}

	/* standard linear regression method */
	slope = (float)(count * sum_xy - sum_x * sum_y) /
		(float)(count * sum_xx - sum_x * sum_x);
	intercept = (sum_y - slope * sum_x) / count;

	format_ns(buf, intercept);
	printf("%g MB/s, %s access time\n", 1000.0 / slope, buf);

	*atime = intercept;
	*throughput = 1000.0 / slope;
}

static int time_read_interval(struct device *dev, int count, ns_t results[],
				 size_t size, off_t offset, off_t interval)
{
	int i;
	off_t pos;
	ns_t ret;

	for (i=0; i < count; i++) {
		pos = offset + i * interval;
		ret = time_read(dev, pos, size);
		returnif (ret);

		if (results[i] == 0 || results[i] > ret)
			results[i] = ret;
	}

	return 0;
}

static void print_one_blocksize(int count, ns_t *times, off_t blocksize)
{
	char min[8], avg[8], max[8];

	format_ns(min, ns_min(count, times));
	format_ns(avg, ns_avg(count, times));
	format_ns(max, ns_max(count, times));

	printf("%lld bytes: min %s avg %s max %s: %g MB/s\n", (long long)blocksize,
		 min, avg, max, blocksize / (ns_min(count, times) / 1000.0));
}

static int try_interval(struct device *dev, long blocksize, ns_t *min_time, int count)
{
	int ret;
	ns_t times[count];
	memset(times, 0, sizeof(times));

	ret = time_read_interval(dev, count, times, blocksize, 0, blocksize * 9);
	returnif (ret);

	print_one_blocksize(count, times, blocksize);
	*min_time = ns_min(count, times);

	return 0;
}

static int try_intervals(struct device *dev, int count, int rounds)
{
	const int ignore = 3;
	ns_t min[rounds];
	off_t bytes[rounds];
	ns_t atime;
	float throughput;
	int i;

	for (i=0; i<rounds; i++) {
		bytes[i] = 512l << i;
		try_interval(dev, bytes[i], &min[i], count);

	}

	regression(min + ignore, bytes + ignore, rounds - ignore, &atime, &throughput);

	for (i=0; i<rounds; i++) {
		printf("bytes %lld, time %lld overhead %g\n", (long long)bytes[i], min[i],
			min[i] - atime - bytes[i] * 1000 / throughput);
	}

	return 0;
}

/*
 * Linear feedback shift register
 *
 * We use this to randomize the block positions for random-access
 * tests. Unlike real random data, we know that within 2^bits
 * accesses, every possible value up to 2^bits will be seen
 * exactly once, with the exception of zero, for which we have
 * a special treatment.
 */
static int lfsr(unsigned short v, unsigned int bits)
{
	unsigned short bit;

	if (v >= (1 << bits)) {
		fprintf(stderr, "flashbench: internal error\n");
		exit(-EINVAL);
	}

	if (v == 0)
		v = ((1 << bits) - 1) & 0xace1;

	switch (bits) {
	case 8: /* x^8 + x^6 + x^5 + x^4 + 1 */
		bit = ((v >> 0) ^ (v >> 2) ^ (v >> 3) ^ (v >> 4)) & 1;
		break;
	case 9: /* x9 + x5 + 1 */
		bit = ((v >> 0) ^ (v >> 4)) & 1;
		break;
	case 10: /* x10 + x7 + 1 */
		bit = ((v >> 0) ^ (v >> 3)) & 1;
		break;
	case 11: /* x11 + x9 + 1 */
		bit = ((v >> 0) ^ (v >> 2)) & 1;
		break;
	case 12:
		bit = ((v >> 0) ^ (v >> 1) ^ (v >> 2) ^ (v >> 8)) & 1;
		break;
	case 13: /* x^13 + x^12 + x^11 + x^8 + 1 */
		bit = ((v >> 0) ^ (v >> 1) ^ (v >> 2) ^ (v >> 5)) & 1;
		break;
	case 14: /* x^14 + x^13 + x^12 + x^2 + 1 */
		bit = ((v >> 0) ^ (v >> 1) ^ (v >> 2) ^ (v >> 12)) & 1;
		break;
	case 15: /* x^15 + x^14 + 1 */
		bit = ((v >> 0) ^ (v >> 1) ) & 1;
		break;
	case 16: /* x^16 + x^14 + x^13 + x^11 + 1 */
		bit = ((v >> 0) ^ (v >> 2) ^ (v >> 3) ^ (v >> 5) ) & 1;
		break;
	default:
		fprintf(stderr, "flashbench: internal error\n");
		exit(-EINVAL);
	}

	return v >> 1 | bit << (bits - 1);
}

static int try_scatter_io(struct device *dev, int tries, int scatter_order,
			int scatter_span, int blocksize, FILE *out)
{
	int i, j;
	const int count = 1 << scatter_order;
	ns_t time;
	ns_t min[count];
	unsigned long pos;

	memset(min, 0, sizeof(min));
	for (i = 0; i < tries; i++) {
		pos = 0;
		for (j = 0; j < count; j++) {
			time = time_read(dev, (pos * blocksize), scatter_span * blocksize);
			returnif (time);

			if (i == 0 || time < min[pos])
				min[pos] = time;

			pos = lfsr(pos, scatter_order);
		}
	}

	for (j = 0; j < count; j++) {
		fprintf(out, "%f	%f\n", j * blocksize / (1024 * 1024.0), min[j] / 1000000.0);
	}

	return 0;
}

static unsigned int find_order(unsigned int large, unsigned int small)
{
	unsigned int o;

	for (o=1; small < large; small <<= 1)
		o++;

	return o;
}

static int try_read_alignment(struct device *dev, int tries, int count,
				off_t maxalign, off_t align, size_t blocksize)
{
	ns_t pre[count], on[count], post[count];
	char pre_s[8], on_s[8], post_s[8], diff_s[8];
	int i, ret;

	memset(pre, 0, sizeof(pre));
	memset(on, 0, sizeof(on));
	memset(post, 0, sizeof(post));

	for (i = 0; i < tries; i++) {
		ret = time_read_interval(dev, count, pre, blocksize,
					 align - blocksize, maxalign);
		returnif(ret);

		ret = time_read_interval(dev, count, on, blocksize,
					 align - blocksize / 2, maxalign);
		returnif(ret);

		ret = time_read_interval(dev, count, post, blocksize,
					 align, maxalign);
		returnif(ret);
	}

	format_ns(pre_s,  ns_avg(count, pre));
	format_ns(on_s,   ns_avg(count, on));
	format_ns(post_s, ns_avg(count, post));
	format_ns(diff_s, ns_avg(count, on) - (ns_avg(count, pre) + ns_avg(count, post)) / 2);
	printf("align %lld\tpre %s\ton %s\tpost %s\tdiff %s\n", (long long)align, pre_s, on_s, post_s, diff_s);

	return 0;
}

static int try_read_alignments(struct device *dev, int tries, int blocksize)
{
	const int count = 7;
	int ret;
	off_t align, maxalign;

	/* make sure we can fit eight power-of-two blocks in the device */
	for (maxalign = blocksize * 2; maxalign < dev->size / count; maxalign *= 2)
		;

	for (align = maxalign; align >= blocksize * 2; align /= 2) {
		ret = try_read_alignment(dev, tries, count, maxalign, align, blocksize);
		returnif (ret);
	}

	return 0;
}

static int try_program(struct device *dev)
{
#if 0
	struct operation program[] = {
		{O_REPEAT, 4},
		{O_SEQUENCE, 3},
			{O_PRINT, .string = "Hello, World!\n"},
			{O_DROP},
				{O_PRINTF},
				{O_FORMAT},
				{O_REDUCE, 8, .aggregate = A_AVERAGE},
				{O_LEN_POW2, 8, 512},
				{O_OFF_LIN, 8, 4096 },
				{O_SEQUENCE, 3},
					{O_PRINTF},
						{O_READ},
					{O_NEWLINE},
					{O_DROP},
						{O_READ},
					{O_END},
			{O_NEWLINE},
			{O_END},
		{O_END},
	};
#endif

#if 0
	struct operation program[] = {
		{O_SEQUENCE, 3},
			{O_PRINT, .string="read by size\n"},
			{O_LEN_POW2, 12, 512},
				{O_DROP},
				{O_SEQUENCE, 4},
					{O_PRINTF},
					{O_FORMAT},
					{O_LENGTH},
				{O_PRINT, .string = ": \t"},
				{O_PRINTF},
					{O_FORMAT},
					{O_REDUCE, .aggregate = A_MINIMUM},
					{O_OFF_LIN, 8, 4096 * 1024},
					{O_READ},
				{O_NEWLINE},
				{O_END},
			{O_NEWLINE},
			{O_END},
		{O_END},
	};
#endif

#if 1
	/* show effect of type of access within AU */
	struct operation program[] = {
            /* loop through power of two multiple of one sector */
            {O_LEN_POW2, 13, -512},
            {O_SEQUENCE, 3},
                /* print block size */
                {O_DROP},
                    {O_PRINTF},
                    {O_FORMAT},
                    {O_LENGTH},
                /* start four units into the device, to skip FAT */
                {O_OFF_FIXED, .val = 1024 * 4096 * 4}, {O_DROP},
                    /* print one line of aggregated
                        per second results */
                    {O_PRINTF}, {O_SEQUENCE, 8},
                        /* write one block to clear state of block,
                           ignore result */
//                        {O_DROP}, {O_LEN_FIXED, .val = 1024 * 4096},
//                                {O_WRITE_RAND},
#if 1                   /* linear write zeroes */
                        {O_FORMAT},{O_REDUCE, .aggregate = A_AVERAGE}, {O_BPS},
                                {O_OFF_LIN, 8192, -1}, {O_WRITE_ZERO},
                        /* linear write ones */
                        {O_FORMAT},{O_REDUCE, .aggregate = A_AVERAGE}, {O_BPS},
                                {O_OFF_LIN, 8192, -1}, {O_WRITE_ONE},
#endif
#if 0
			/* Erase */
                        {O_FORMAT},{O_REDUCE, .aggregate = A_TOTAL},// {O_BPS},
                                {O_OFF_LIN, 8192, -1}, {O_ERASE},
                        {O_FORMAT},{O_REDUCE, .aggregate = A_TOTAL},// {O_BPS},
                                {O_OFF_LIN, 8192, -1}, {O_ERASE},
                        /* linear write 0x5a */
                        {O_FORMAT},{O_REDUCE, .aggregate = A_AVERAGE}, {O_BPS},
                                {O_OFF_LIN, 8192, -1}, {O_WRITE_RAND},
#endif
                        /* linear write 0x5a again */
                        {O_FORMAT},{O_REDUCE, .aggregate = A_AVERAGE}, {O_BPS},
                                {O_OFF_LIN, 8192, -1}, {O_WRITE_RAND},
                        /* linear read */
                        {O_FORMAT},{O_REDUCE, .aggregate = A_AVERAGE}, {O_BPS},
                                {O_OFF_LIN, 8192, -1}, {O_READ},
#if 1
                        /* random write zeroes */
                        {O_FORMAT},{O_REDUCE, .aggregate = A_AVERAGE}, {O_BPS},
                                {O_OFF_RAND, 8192, -1}, {O_WRITE_ZERO},
                        /* random write ones */
                        {O_FORMAT},{O_REDUCE, .aggregate = A_AVERAGE}, {O_BPS},
                                {O_OFF_RAND, 8192, -1}, {O_WRITE_ONE},
#endif
#if 0
                        /* random erase */
                        {O_FORMAT},{O_REDUCE, .aggregate = A_AVERAGE},// {O_BPS},
                                {O_OFF_RAND, 8192, -1}, {O_ERASE},
                        {O_FORMAT},{O_REDUCE, .aggregate = A_AVERAGE},// {O_BPS},
                                {O_OFF_RAND, 8192, -1}, {O_ERASE},
                        /* random write 0x5a */
                        {O_FORMAT},{O_REDUCE, .aggregate = A_AVERAGE}, {O_BPS},
                                {O_OFF_RAND, 8192, -1}, {O_WRITE_RAND},
#endif
                        /* random write 0x5a again */
                        {O_FORMAT},{O_REDUCE, .aggregate = A_AVERAGE}, {O_BPS},
                                {O_OFF_RAND, 8192, -1}, {O_WRITE_RAND},
                        /* random read */
                        {O_FORMAT},{O_REDUCE, .aggregate = A_AVERAGE}, {O_BPS},
                                {O_OFF_RAND, 8192, -1}, {O_READ},
                        {O_END},
                {O_NEWLINE},
                {O_END},
            {O_END},
	};
	call(program, dev, 0, 1 * 4096 * 1024, 0);
#endif

	return 0;
}

#if 0
static int try_open_au_oob(struct device *dev, unsigned int erasesize,
			unsigned int blocksize,
			unsigned int count,
			bool random)
{
	/* find maximum number of open AUs */
	struct operation program[] = {
            /* loop through power of two multiple of one sector */
            {O_LEN_POW2, find_order(erasesize, blocksize), -(long long)blocksize},
            {O_SEQUENCE, 4},
                /* print block size */
                {O_DROP},
                    {O_PRINTF},
                    {O_FORMAT},
                    {O_LENGTH},
                /* start 16 MB into the device, to skip FAT */
                {O_OFF_FIXED, .val = 4 * 1024 * 4128}, {O_DROP},
                    /* print one line of aggregated
                        per second results */
                    {O_PRINTF}, {O_FORMAT}, {O_BPS},
                        /* linear write 0x5a */
                        {O_REDUCE, .aggregate = A_MAXIMUM}, {O_REPEAT, 1},
                            {O_REDUCE, .aggregate = A_AVERAGE},
                            { (random ? O_OFF_RAND : O_OFF_LIN),
					erasesize / blocksize, -1},
                            {O_REDUCE, .aggregate = A_AVERAGE}, // {O_BPS},
                            {O_OFF_RAND, count, /* 2 * erasesize */ 2 * 4128 * 1024}, {O_WRITE_RAND},
                {O_DROP},
	                {O_OFF_FIXED, .val = 4 * 1024 * 4128 + 4 * 1024 * 1024}, {O_DROP},
			{O_LEN_FIXED, .val = 32 * 1024},
                        {O_OFF_RAND, count, 2 * 4128 * 1024}, {O_WRITE_RAND},
                {O_NEWLINE},
                {O_END},
            {O_END},
	};
	call(program, dev, 0, erasesize, 0);

	return 0;
}
#endif

static int try_open_au(struct device *dev, unsigned int erasesize,
			unsigned int blocksize,
			unsigned int count,
			unsigned long long offset,
			bool random)
{
        /* start 16 MB into the device, to skip FAT, round up to full erase blocks */
	if (offset == -1ull)
		offset = (1024 * 1024 * 16 + erasesize - 1) / erasesize * erasesize;

	/* find maximum number of open AUs */
	struct operation program[] = {
            /* loop through power of two multiple of one sector */
            {O_LEN_POW2, find_order(erasesize, blocksize), -(long long)blocksize},
            {O_SEQUENCE, 3},
                /* print block size */
                {O_DROP},
                    {O_PRINTF},
                    {O_FORMAT},
                    {O_LENGTH},
                {O_OFF_FIXED, .val = offset}, {O_DROP},
                    /* print one line of aggregated
                        per second results */
                    {O_PRINTF}, {O_FORMAT}, {O_BPS},
                        /* linear write 0x5a */
                        {O_REDUCE, .aggregate = A_MAXIMUM}, {O_REPEAT, 1},
                            {O_REDUCE, .aggregate = A_AVERAGE},
                            { (random ? O_OFF_RAND : O_OFF_LIN),
					erasesize / blocksize, -1},
                            {O_REDUCE, .aggregate = A_AVERAGE},
                            {O_OFF_RAND, count, 12 * erasesize}, {O_WRITE_RAND},
                {O_NEWLINE},
                {O_END},
            {O_END},
	};
	call(program, dev, 0, erasesize, 0);

	return 0;
}

static int try_find_fat(struct device *dev, unsigned int erasesize,
				unsigned int blocksize,
				unsigned int count,
				bool random)
{
	/* Find FAT Units */
	struct operation program[] = {
            /* loop through power of two multiple of one sector */
            {O_LEN_POW2, find_order(erasesize, blocksize), - (long long)blocksize},
            {O_SEQUENCE, 3},
                /* print block size */
                {O_DROP},
                    {O_PRINTF},
                    {O_FORMAT},
                    {O_LENGTH},
                /* print one line of aggregated
                    per second results */
                {O_PRINTF}, {O_FORMAT},
		    {O_OFF_LIN, count, erasesize},
                        /* linear write 0x5a */
                        {O_REDUCE, .aggregate = A_MAXIMUM}, {O_REPEAT, 1},
                            {O_BPS}, {O_REDUCE, .aggregate = A_AVERAGE},
                            {random ? O_OFF_RAND : O_OFF_LIN, erasesize / blocksize, -1},
                            {O_WRITE_RAND},
                {O_NEWLINE},
                {O_END},
            {O_END},
	};
	call(program, dev, 0, erasesize, 0);

	return 0;
}


static void print_help(const char *name)
{
	printf("%s [OPTION]... [DEVICE]\n", name);
	printf("run tests on DEVICE, pointing to a flash storage medium.\n\n");
	printf("-o, --out=FILE	write output to FILE instead of stdout\n");
	printf("-s, --scatter	run scatter read test\n");
	printf("    --scatter-order=N scatter across 2^N blocks\n");
	printf("    --scatter-span=N span each write across N blocks\n");
	printf("-f, --find-fat	analyse first few erase blocks\n");
	printf("    --fat-nr=N	look through first N erase blocks (default: 6)\n");
	printf("-O, --open-au	find number of open erase blocks\n");
	printf("    --open-au-nr=N try N open erase blocks\n");
	printf("    --offset=N  start at position N\n");
	printf("-r, --random	use pseudorandom access with erase block\n");
	printf("-v, --verbose	increase verbosity of output\n");
	printf("-c, --count=N	run each test N times (default: 8\n");
	printf("-b, --blocksize=N use a blocksize of N (default:16K)\n");
	printf("-e, --erasesize=N use a eraseblock size of N (default:4M)\n");
}

struct arguments {
	const char *dev;
	const char *out;
	bool scatter, interval, program, fat, open_au, align;
	bool random;
	int count;
	int blocksize;
	int erasesize;
	unsigned long long offset;
	int scatter_order;
	int scatter_span;
	int interval_order;
	int fat_nr;
	int open_au_nr;
};

static int parse_arguments(int argc, char **argv, struct arguments *args)
{
	static const struct option long_options[] = {
		{ "out", 1, NULL, 'o' },
		{ "scatter", 0, NULL, 's' },
		{ "scatter-order", 1, NULL, 'S' },
		{ "scatter-span", 1, NULL, '$' },
		{ "align", 0, NULL, 'a' },
		{ "interval", 0, NULL, 'i' },
		{ "interval-order", 1, NULL, 'I' },
		{ "findfat", 0, NULL, 'f' },
		{ "fat-nr", 1, NULL, 'F' },
		{ "open-au", 0, NULL, 'O' },
		{ "open-au-nr", 1, NULL, '0' },
		{ "offset", 1, NULL, 't' },
		{ "random", 0, NULL, 'r' },
		{ "verbose", 0, NULL, 'v' },
		{ "count", 1, NULL, 'c' },
		{ "blocksize", 1, NULL, 'b' },
		{ "erasesize", 1, NULL, 'e' },
		{ NULL, 0, NULL, 0 },
	};

	memset(args, 0, sizeof(*args));
	args->count = 8;
	args->scatter_order = 9;
	args->scatter_span = 1;
	args->blocksize = 16384;
	args->offset = -1ull;
	args->erasesize = 4 * 1024 * 1024;
	args->fat_nr = 6;
	args->open_au_nr = 2;

	while (1) {
		int c;

		c = getopt_long(argc, argv, "o:siafF:Ovrc:b:e:p", long_options, &optind);

		if (c == -1)
			break;

		switch (c) {
		case 'o':
			args->out = optarg;
			break;

		case 's':
			args->scatter = 1;
			break;

		case 'S':
			args->scatter_order = atoi(optarg);
			break;

		case '$':
			args->scatter_span = atoi(optarg);
			break;

		case 'a':
			args->align = 1;
			break;

		case 'i':
			args->interval = 1;
			break;

		case 'I':
			args->interval_order = atoi(optarg);
			break;

		case 'f':
			args->fat = 1;
			break;

		case 'F':
			args->fat_nr = atoi(optarg);
			break;

		case 'O':
			args->open_au = 1;
			break;

		case '0':
			args->open_au_nr = atoi(optarg);
			break;

		case 'r':
			args->random = 1;
			break;

		case 'p':
			args->program = 1;
			break;

		case 'v':
			verbose++;
			break;

		case 'c':
			args->count = atoi(optarg);
			break;

		case 'b':
			args->blocksize = atoi(optarg);
			break;

		case 'e':
			args->erasesize = atoi(optarg);
			break;

		case 't':
			args->offset = strtoull(optarg, NULL, 0);
			break;

		case '?':
			print_help(argv[0]);
			return -EINVAL;
			break;
		}
	}

	if (optind != (argc - 1))  {
		fprintf(stderr, "%s: invalid arguments\n", argv[0]);
		return -EINVAL;
	}

	args->dev = argv[optind];

	if (!(args->scatter || args->interval || args->program ||
	      args->fat || args->open_au || args->align)) {
		fprintf(stderr, "%s: need at least one action\n", argv[0]);
		return -EINVAL;
	}

	if (args->scatter && (args->scatter_order > 16)) {
		fprintf(stderr, "%s: scatter_order must be at most 16\n", argv[0]);
		return -EINVAL;
	}

	return 0;
}

static FILE *open_output(const char *filename)
{
	if (!filename || !strcmp(filename, "-"))
		return fdopen(0, "w"); /* write to stdout */

	return fopen(filename, "w+");
}

int main(int argc, char **argv)
{
	struct device dev;
	struct arguments args;
	FILE *output;
	int ret;

	returnif(parse_arguments(argc, argv, &args));

	returnif(setup_dev(&dev, args.dev));

	output = open_output(args.out);
	if (!output) {
		perror(args.out);
		return -errno;
	}

	if (verbose > 1) {
		printf("filename: \"%s\"\n", argv[1]);
		printf("filesize: 0x%llx\n", (unsigned long long)dev.size);
	}

	if (args.scatter) {
		ret = try_scatter_io(&dev, args.count, args.scatter_order,
				 args.scatter_span, args.blocksize, output);
		if (ret < 0) {
			errno = -ret;
			perror("try_scatter_io");
			return ret;
		}
	}

	if (args.fat) {
		ret = try_find_fat(&dev, args.erasesize, args.blocksize,
				   args.fat_nr, args.random);
		if (ret < 0) {
			errno = -ret;
			perror("find_fat");
		}
	}

	if (args.align) {
		ret = try_read_alignments(&dev, args.count, args.blocksize);
		if (ret < 0) {
			errno = -ret;
			perror("try_align");
			return ret;
		}
	}

	if (args.open_au) {
		ret = try_open_au(&dev, args.erasesize, args.blocksize,
				  args.open_au_nr, args.offset, args.random);
		if (ret < 0) {
			errno = -ret;
			perror("find_fat");
			return ret;
		}
	}

	if (args.interval) {
		ret = try_intervals(&dev, args.count, args.interval_order);
		if (ret < 0) {
			errno = -ret;
			perror("try_intervals");
			return ret;
		}
	}

	if (args.program) {
		try_program(&dev);
	}

	return 0;
}
