/*
 * DF-5834 PoC: PPS_IOC_FETCH blocks inside pps_fetch_timeout()
 * (sys/kern/kern_clock.c:1626) holding the caller's GLOBAL tty_token
 * (sioioctl() default case, sio.c:2164-2172) -> every other tty operation
 * system-wide blocks for the whole (user-chosen) duration.
 *
 * Single-process fork design: one fd, child wedges, parent measures.
 *
 *   ppslockdemo 0    control: FETCH timeout {0,0} -> no timeout path,
 *                    ioctl returns instantly, token never held.
 *   ppslockdemo 8    child: PPS_IOC_FETCH with 8s timeout (holds tty_token
 *                    ~8s); parent after 2s times PPS_IOC_GETCAP (instant
 *                    once token acquired) -> expect ~6s if wedged.
 *   ppslockdemo inf  child: FETCH with degenerate finite timeout {0 s,1 ns}
 *                    -> tstohz_low()==0 -> tsleep(...,0) sleeps FOREVER,
 *                    holding tty_token until SIGKILL; parent arms alarm(20)
 *                    and reports STILL_BLOCKED if GETCAP never returns.
 *
 * The child pid is written to /tmp/ppschild.pid for orchestration.
 *
 * Build: cc -O2 -Wall -o ppslockdemo ppslockdemo.c
 */
#include <sys/ioctl.h>
#include <sys/signal.h>
#include <sys/time.h>
#include <sys/timepps.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>

static double
monoton(void)
{
	struct timespec ts;

	clock_gettime(CLOCK_MONOTONIC, &ts);
	return ((double)ts.tv_sec + (double)ts.tv_nsec / 1e9);
}

static void
on_alrm(int sig __attribute__((unused)))
{
	/* only armed in "inf" mode */
	printf("parent: GETCAP STILL_BLOCKED after alarm(20) fired -> "
	       "tty_token held indefinitely by sleeping FETCH child\n");
	_exit(3);
}

int
main(int argc, char **argv)
{
	struct pps_fetch_args fa;
	int fd, r, cap = -1, sec;
	double t0;
	pid_t pid;
	const char *mode = argc > 1 ? argv[1] : "0";

	if (!strcmp(mode, "inf"))
		sec = -1;
	else
		sec = atoi(mode);

	fd = open("/dev/ttyd0", O_RDWR | O_NONBLOCK | O_NOCTTY);
	if (fd < 0) {
		perror("open /dev/ttyd0");
		exit(1);
	}

	pid = fork();
	if (pid == 0) {				/* ---- child: the wedge ---- */
		memset(&fa, 0, sizeof(fa));
		fa.tsformat = PPS_TSFMT_TSPEC;
		if (sec < 0) {			/* inf */
			fa.timeout.tv_sec = 0;
			fa.timeout.tv_nsec = 1;
		} else {			/* finite sec (0 = control) */
			fa.timeout.tv_sec = sec;
			fa.timeout.tv_nsec = 0;
		}
		fprintf(stderr, "child %d: PPS_IOC_FETCH timeout=%lds %ldns\n",
			getpid(), fa.timeout.tv_sec, fa.timeout.tv_nsec);
		fflush(stderr);
		r = ioctl(fd, PPS_IOC_FETCH, &fa);
		fprintf(stderr, "child %d: FETCH returned %d errno=%d (%s)\n",
			getpid(), r, errno, strerror(errno));
		_exit(0);
	}
	fprintf(stderr, "parent %d: child=%d mode=%s\n", getpid(), pid, mode);
	fflush(stderr);

	if (sec < 0)
		signal(SIGALRM, on_alrm);

	sleep(2);				/* let the child grab the token */

	if (sec < 0)
		alarm(20);

	t0 = monoton();
	r = ioctl(fd, PPS_IOC_GETCAP, &cap);
	printf("parent: GETCAP r=%d cap=0x%x elapsed=%.3f s errno=%d%s\n",
	       r, cap, monoton() - t0, errno,
	       sec > 0 ? "  <-- WEDGED for ~6s if bug present" :
	       sec < 0 ? "" : "  <-- control, expect ~0.000s");

	if (sec >= 0)
		waitpid(pid, NULL, 0);
	return (0);
}
