/*
 * DF-5681 — race_once.c
 *
 * Demonstrate the *kernel* flavor of _PROP_ONCE_RUN (a cmpset-once with
 * no completion barrier, sys/libprop/prop_object_impl.h:302-303)
 * racing the lazy initialization of the interned-number registry in
 * sys/libprop/prop_number.c.
 *
 * The kernel macro is:
 *
 *   #define _PROP_ONCE_DECL(x)   static int x = 0;
 *   #define _PROP_ONCE_RUN(x,f)  if (atomic_cmpset_int(&(x), 0, 1)) f()
 *
 * The cmpset loser does NOT wait for the winner's f() to complete.
 * For prop_number that f() is _prop_number_init() (prop_number.c:169-176):
 * lockinit(&_prop_number_tree_mutex) + _prop_rb_tree_init(...).  A loser
 * that fails the cmpset proceeds straight to lockmgr() on a lock that may
 * be mid-lockinit (lockinit wipes lk_count/lk_lockholder,
 * sys/kern/kern_lock.c:1300-1307 -> mutual exclusion broken) and then to
 * _prop_rb_tree_find() on a tree whose rbt_ops is still NULL (BSS zeroed)
 * -> prop_rb.c:90-93 dereferences rbt->rbt_ops->rbto_compare_key ->
 * NULL-pointer indirect call -> panic.
 *
 * This harness is linked against three library variants built by
 * build.sh from the guest's /usr/src/sys/libprop (byte-identical to the
 * audited sys/libprop):
 *
 *   race_once       -> stock sources.  Userland _PROP_ONCE_RUN is
 *                      pthread_once (HAS completion semantics).  CONTROL:
 *                      must survive.
 *   race_once_ksem  -> same sources with the userland once swapped for a
 *                      faithful port of the _KERNEL cmpset-once macro and
 *                      a 50ms window-widening delay inside
 *                      _prop_number_init (instrumentation only, after the
 *                      mutex init, before the tree init — exactly the
 *                      window the kernel macro leaves open).
 *                      EXPECTED: SIGSEGV in _prop_rb_tree_find.
 *   race_once_ksem warmup
 *                   -> same ksem library, but the harness completes the
 *                      once single-threaded before spawning the racing
 *                      threads.  This is exactly what the proposed kernel
 *                      fix (SYSINIT(SI_SUB_PROP) bootstrap) does.
 *                      CONTROL for the fix: must survive.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <libprop/prop_number.h>
#include <libprop/prop_object.h>

#define NTHR 8
#define NVAL 512

static pthread_barrier_t bar;

static void *
worker(void *arg)
{
	uintptr_t id = (uintptr_t)arg;
	prop_number_t keep[NVAL];

	/* All threads make their FIRST-EVER prop_number call at the same
	 * instant — the kernel first-use window. */
	pthread_barrier_wait(&bar);

	for (int i = 0; i < NVAL; i++) {
		prop_number_t a = prop_number_create_integer(i);
		prop_number_t b = prop_number_create_integer(i);
		if (a == NULL || b == NULL) {
			fprintf(stderr, "t%zu: alloc failed\n", (size_t)id);
			exit(2);
		}
		/* Interning invariant: one object per value.  Broken mutex
		 * or racing tree init shows up here as a duplicate. */
		if (a != b) {
			fprintf(stderr,
			    "t%zu: INTERNING VIOLATION at %d: %p != %p\n",
			    (size_t)id, i, (void *)a, (void *)b);
			exit(3);
		}
		keep[i] = a;
		prop_object_release(b);
	}
	for (int i = 0; i < NVAL; i++)
		prop_object_release(keep[i]);
	printf("t%zu done\n", (size_t)id);
	return NULL;
}

int
main(int argc, char **argv)
{
	pthread_t th[NTHR];
	int warmup = (argc > 1 && strcmp(argv[1], "warmup") == 0);

	if (warmup) {
		/* Emulates the SYSINIT(SI_SUB_PROP) bootstrap fix:
		 * complete the lazy init single-threaded BEFORE any
		 * concurrent first use. */
		prop_number_t p0 = prop_number_create_integer(-1);
		prop_object_release(p0);
		printf("warmup: lazy init completed single-threaded\n");
	}

	pthread_barrier_init(&bar, NULL, NTHR);
	for (uintptr_t i = 0; i < NTHR; i++)
		pthread_create(&th[i], NULL, worker, (void *)i);
	for (int i = 0; i < NTHR; i++)
		pthread_join(th[i], NULL);
	printf("SURVIVED: interning invariant held (%d values x %d threads)\n",
	    NVAL, NTHR);
	return 0;
}
