/*
 * DF-4749 / DF-4750 PoC — DragonFlyBSD nmdm(4) null-modem driver logic bugs.
 *
 * Run as an UNPRIVILEGED user with /dev/nmdm0A,B at 0666 (make_dev default
 * in nmdminit(), sys/dev/misc/nmdm/nmdm.c:127-130).
 *
 * TEST 1 (DF-4749): pt_prison is never assigned in nmdmopen()
 *   (sys/dev/misc/nmdm/nmdm.c:211: `else if (pti->pt_prison !=
 *   ap->a_cred->cr_prison) return EBUSY;`). pt_prison is kmalloc'd
 *   M_ZERO -> stays NULL forever (grep: only init sites are lines 88/211).
 *   For an unjailed process cr_prison == &prison0 != NULL, so the check
 *   `NULL != &prison0` is ALWAYS true -> any second open() of an
 *   already-open half returns EBUSY. FreeBSD's pty heritage sets
 *   `pti->pt_prison = ap->a_cred->cr_prison` on first open; DFly's nmdm
 *   dropped that assignment. Expected correct behavior: reopen by a
 *   same-prison process succeeds.
 *
 * TEST 2 (DF-4750): TIOCCDTR handler at nmdm.c:564-566 does
 *   `ourpart->modemsignals &= TIOCM_DTR;`  (missing ~) which KEEPS DTR and
 *   clears every other signal bit. Expected: clears DTR only, leaving
 *   RTS/LE intact.
 */
#include <sys/types.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>

int
main(void)
{
	int fd1, fd2, r, sig;

	printf("== DF-4749: reopen of an open nmdm half ==\n");
	fd1 = open("/dev/nmdm0A", O_RDWR | O_NONBLOCK);
	if (fd1 < 0) {
		printf("FAIL setup: open#1(/dev/nmdm0A): %s\n", strerror(errno));
		return (1);
	}
	printf("open#1(/dev/nmdm0A)          = %d  [ok, TS_ISOPEN set]\n", fd1);

	fd2 = open("/dev/nmdm0A", O_RDWR | O_NONBLOCK);
	if (fd2 < 0)
		printf("open#2(/dev/nmdm0A)          = -1 errno=%d (%s)"
		    "  <-- BUG: unjailed reopen must succeed\n",
		    errno, strerror(errno));
	else
		printf("open#2(/dev/nmdm0A)          = %d  [correct: reopen allowed]\n",
		    fd2);
	if (fd2 >= 0)
		close(fd2);

	close(fd1);
	fd2 = open("/dev/nmdm0A", O_RDWR | O_NONBLOCK);
	printf("open#3 after close(#1)       = %d  [%s: fresh first-open works,"
	    " proves EBUSY above came from the pt_prison check]\n",
	    fd2, fd2 >= 0 ? "ok" : strerror(errno));
	if (fd2 >= 0)
		close(fd2);

	printf("\n== DF-4750: TIOCCDTR inverted mask ==\n");
	fd1 = open("/dev/nmdm0A", O_RDWR | O_NONBLOCK);
	if (fd1 < 0) {
		printf("FAIL setup: %s\n", strerror(errno));
		return (1);
	}
	sig = TIOCM_LE | TIOCM_DTR | TIOCM_RTS;
	r = ioctl(fd1, TIOCMSET, &sig);
	printf("TIOCMSET(LE|DTR|RTS)         = %d\n", r);
	r = ioctl(fd1, TIOCMGET, &sig);
	printf("TIOCMGET after SET           = 0x%04x (LE=%d DTR=%d RTS=%d)\n",
	    sig, !!(sig & TIOCM_LE), !!(sig & TIOCM_DTR), !!(sig & TIOCM_RTS));

	r = ioctl(fd1, TIOCCDTR);
	printf("TIOCCDTR                     = %d\n", r);
	r = ioctl(fd1, TIOCMGET, &sig);
	printf("TIOCMGET after TIOCCDTR      = 0x%04x -> DTR=%s RTS=%s\n",
	    sig,
	    (sig & TIOCM_DTR) ? "SET   <-- BUG: must be cleared" : "clear [correct]",
	    (sig & TIOCM_RTS) ? "SET [correct]" : "clear <-- BUG: must stay set");
	close(fd1);

	return (0);
}
