/*
 * DF-2832 dirty-writer trigger.
 *
 * Runs as an *unprivileged* user. Hammers a file on the target hammer2
 * mount with pwrite() churn (no fsync) so hammer2_pfs_memory_wait()
 * (sys/vfs/hammer2/hammer2_vfsops.c:2938) hits its stall path and calls
 * trigger_syncer() / trigger_syncer_start() -> vfs_sync.c reads
 * mp->mnt_syncer_ctx lock-free.
 *
 * While this runs, root races `umount -f` on the mount. Any thread caught
 * between the mp->mnt_syncer_ctx load and the atomic RMW in
 * trigger_syncer_start()/trigger_syncer() when vn_syncer_thr_stop() frees
 * the ctx (vfs_sync.c:353-357) performs an atomic add on freed heap.
 *
 * usage: dirty_writer <file> [stop-after-seconds]
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <time.h>

int
main(int argc, char **argv)
{
	const char *path;
	char *buf;
	size_t bufsz = 65536;
	int fd;
	unsigned long iter = 0;
	time_t deadline = (time_t)0x7fffffff;

	if (argc < 2) {
		fprintf(stderr, "usage: %s <file> [secs]\n", argv[0]);
		return (2);
	}
	path = argv[1];
	if (argc > 2)
		deadline = time(NULL) + atoi(argv[2]);

	buf = malloc(bufsz);
	if (!buf)
		return (2);
	{
		int i;
		for (i = 0; i < (int)bufsz; i += 16)
			memcpy(buf + i, "DF2832WITNESSDATA", 16);
	}

	for (;;) {
		if (time(NULL) > deadline)
			break;
		fd = open(path, O_RDWR | O_CREAT, 0644);
		if (fd < 0) {
			/* mount went away (umount -f) -- retry quickly */
			usleep(20000);
			continue;
		}
		while (time(NULL) <= deadline) {
			off_t off = (lrand48() % 1024) * bufsz;
			if (pwrite(fd, buf, bufsz, off) != (ssize_t)bufsz) {
				break;	/* EIO/forced unmount */
			}
			if ((++iter & 0x3f) == 0)
				ftruncate(fd, 0);	/* churn create/delete */
		}
		close(fd);
		if (time(NULL) > deadline)
			break;
		usleep(20000);
	}
	fprintf(stderr, "dirty_writer: %lu writes\n", iter);
	return (0);
}
