/* DF-2770: ptcread TIOCPKT_IOCTL path discards the uiomove error.
 *
 * Phase A (sanity): slave tcsetattr -> master full-buffer read returns
 *   control byte 0x40 (TIOCPKT_IOCTL) + termios bytes.
 * Phase B (test): second tcsetattr -> master reads 2 bytes into
 *   [1 valid byte][1 PROT_NONE byte]: ureadc(0x40) succeeds, then
 *   uiomove(termios,1) faults -> error swallowed, pt_send consumed.
 *   expect (vulnerable): read() == 1, errno 0, only control byte
 *     delivered, termios event lost.
 *   expect (patched):    read() == -1, errno 14 EFAULT (retryable).
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <termios.h>
#include <signal.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/mman.h>

static void
slave_driver(int sfd)
{
	struct termios tio;
	int ext = 1, i;

	/* EXTPROC on: TIOCSETA now queues TIOCPKT_IOCTL packets */
	ioctl(sfd, TIOCEXT, &ext);
	for (i = 0; i < 2; i++) {
		if (tcgetattr(sfd, &tio) == 0) {
			tio.c_cc[VMIN] = 1 + i;
			tcsetattr(sfd, TCSANOW, &tio);
		}
		sleep(2);
	}
	sleep(2);
	_exit(0);
}

int
main(void)
{
	int mfd, sfd, rv, st;
	char good[128];
	unsigned char *twopg, *buf;
	pid_t pid;
	int pkt = 1;

	mfd = posix_openpt(O_RDWR | O_NOCTTY);
	if (mfd < 0) { perror("posix_openpt"); return 1; }
	if (grantpt(mfd) || unlockpt(mfd)) { perror("grantpt"); return 1; }
	if (ioctl(mfd, TIOCPKT, &pkt) < 0) { perror("TIOCPKT"); return 1; }

	pid = fork();
	if (pid == 0) {
		sfd = open(ptsname(mfd), O_RDWR | O_NOCTTY);
		if (sfd < 0) _exit(1);
		slave_driver(sfd);
	}
	sleep(1);

	/* Phase A: good-buffer read of the first TIOCPKT_IOCTL event */
	memset(good, 0, sizeof(good));
	errno = 0;
	rv = read(mfd, good, sizeof(good));
	printf("DF2770 A: full read rv=%d errno=%d ctrl=0x%02x "
	    "(expect 0x40) bytes1..4=%02x %02x %02x %02x\n",
	    rv, errno, rv > 0 ? (unsigned char)good[0] : 0,
	    rv > 4 ? (unsigned char)good[1] : 0,
	    rv > 4 ? (unsigned char)good[2] : 0,
	    rv > 4 ? (unsigned char)good[3] : 0,
	    rv > 4 ? (unsigned char)good[4] : 0);

	/* Phase B: faulting read of the second TIOCPKT_IOCTL event */
	twopg = mmap(NULL, 8192, PROT_READ | PROT_WRITE,
	    MAP_ANON | MAP_PRIVATE, -1, 0);
	if (twopg == MAP_FAILED) { perror("mmap"); return 1; }
	if (mprotect(twopg + 4096, 4096, PROT_NONE) < 0)
		{ perror("mprotect"); return 1; }
	buf = twopg + 4096 - 1;
	buf[0] = 0xAA;

	alarm(6);
	errno = 0;
	rv = read(mfd, buf, 2);
	printf("DF2770 B: faulting read(2) rv=%d errno=%d (%s)\n",
	    rv, errno, strerror(errno));
	printf("DF2770 B: byte0=0x%02x (0x40=event byte copied before "
	    "EFAULT swallow; 0xAA=nothing copied)\n", buf[0]);

	kill(pid, SIGKILL);
	waitpid(pid, &st, 0);
	return 0;
}
