/*
 * DF-2799 — AB-BA deadlock: jail_lock <-> per-CPU gd_sysctllock.
 *
 * Lock cycle (stock kernel):
 *
 *   CPU A (root, jail(2)):  sys_jail takes jail_lock EXCLUSIVE
 *   (kern_jail.c:275) and holds it across kern_jail() ->
 *   prison_sysctl_create() (kern_jail.c:223, called under the same lock)
 *   -> SYSCTL_ADD_NODE/SYSCTL_ADD_BIT64 -> sysctl_add_oid() ->
 *   SYSCTL_XLOCK() == _sysctl_xlock() which takes EVERY CPU's
 *   gd_sysctllock EXCLUSIVE in order 0..ncpu-1 (kern_sysctl.c:442,1641-51).
 *
 *   CPU B (unprivileged, `sysctl kern.jail.list`): userland_sysctl takes
 *   its own CPU's gd_sysctllock SHARED across the whole sysctl_root()
 *   dispatch INCLUDING the handler (kern_sysctl.c:1572-73,1480-81); the
 *   sysctl_jail_list handler then blocks acquiring jail_lock
 *   (kern_jail.c:696) — while still holding CPU B's gd_sysctllock.
 *
 *   A holds jail_lock, waits for CPU B's gd lock (inside _sysctl_xlock).
 *   B holds its gd lock, waits for jail_lock.  Neither lockmgr ever
 *   times out -> permanent kernel deadlock.  Cascades: jail_lock is
 *   global (all jail(2)/jail_attach(2)/prison_free/jailed_ip callers
 *   wedge) and CPU A already holds gd locks 0..B-1 exclusively (sysctl
 *   readers on those CPUs wedge too).
 *
 * This program is the root side: churn jail(2) forever; every child
 * jail()s itself then exits.  The create-window inside each jail() is
 * milliseconds wide (nlookup + chroot + prison_sysctl_create), so the
 * cycle closes almost immediately once a reader is parked on jail_lock.
 */
#include <sys/param.h>
#include <sys/jail.h>
#include <sys/syscall.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>

int
main(int argc, char **argv)
{
	int iters = argc > 1 ? atoi(argv[1]) : 100000;

	if (getuid() != 0) {
		printf("must run as root\n");
		return (2);
	}
	for (int i = 0; i < iters; i++) {
		pid_t pid = fork();
		if (pid == 0) {
			struct jail j;
			memset(&j, 0, sizeof(j));
			j.version = 1;
			j.path = "/tmp";
			j.hostname = "churn";
			j.n_ips = 0;
			j.ips = NULL;
			int jid = syscall(SYS_jail, &j);
			if (jid < 0 && errno != EPERM) {
				/* failed jail() (e.g. ENOTDIR path) still
				 * exercises prison_sysctl_create + out2 */
			}
			_exit(0);
		}
		/* reap, but slowly — let a few children overlap */
		if (i % 64 == 63) {
			int st;
			while (waitpid(-1, &st, WNOHANG) > 0)
				;
		}
	}
	int st;
	while (waitpid(-1, &st, 0) > 0)
		;
	printf("churn done\n");
	return (0);
}
