NetBSD-Bugs archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]

kern/60784: pty doesn't deliver SIGIO when writable



>Number:         60784
>Category:       kern
>Synopsis:       pty doesn't deliver SIGIO when writable
>Confidential:   no
>Severity:       serious
>Priority:       medium
>Responsible:    kern-bug-people
>State:          open
>Class:          sw-bug
>Submitter-Id:   net
>Arrival-Date:   Thu Sep 24 17:30:01 +0000 2026
>Originator:     Taylor R Campbell
>Release:        current, 11, 10, ...
>Organization:
The Pseudo-Terminal Bugs LLC (it's never the last one)
>Environment:
>Description:

	When either side of a pty (host or application) becomes
	writable, presumably it should deliver SIGIO if subscribed,
	just like poll reports POLLOUT.  But it doesn't.

>How-To-Repeat:
#include <sys/ioctl.h>

#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <pthread.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>

#define	REQUIRE_LIBC(x, fail) do					      \
{									      \
	if ((x) == (fail))						      \
		err(EXIT_FAILURE, "%s", #x);				      \
} while (0)

#define	RL(x)	REQUIRE_LIBC(x, -1)

#define	RZ(x) do							      \
{									      \
	errno = (x);							      \
	if (errno)							      \
		err(EXIT_FAILURE, "%s", #x);				      \
} while (0)

static void
on_sigio(int signo, siginfo_t *si, void *ctx)
{

	(void)signo;
	(void)ctx;
	fprintf(stderr, "SIGIO code=%d band=0x%lx\n",
	    si->si_code, si->si_band);
}

struct context {
	pthread_barrier_t *bar;
	int fd;
	int lowat;
};

static void *
start_reader(void *cookie)
{
	struct context *C = cookie;
	sigset_t mask;
	char *buf;
	ssize_t nread;
	size_t resid = C->lowat;

	REQUIRE_LIBC(buf = malloc(C->lowat), NULL);

	RL(sigemptyset(&mask));
	RL(sigaddset(&mask, SIGIO));
	RL(pthread_sigmask(SIG_BLOCK, &mask, NULL));

	fprintf(stderr, "[thread] waiting for barrier\n");
	(void)pthread_barrier_wait(C->bar);
	fprintf(stderr, "[thread] giving other thread a head start\n");
	sleep(1);
	fprintf(stderr, "[thread] reading first byte\n");
	RL(nread = read(C->fd, buf, 1));
	if (nread != 1)
		errx(EXIT_FAILURE, "nread=%zd", nread);
	resid -= (size_t)nread;
	RL(usleep(1));

	if (C->lowat <= 1) {
		fprintf(stderr, "[thread] lowat=1, our job is done\n");
		goto out;
	}

	while (resid > 0) {
		fprintf(stderr, "[thread] reading more bytes, %zu remain\n",
		    resid);
		RL(nread = read(C->fd, buf + C->lowat - resid, resid));
		fprintf(stderr, "[thread] read %zd bytes\n", nread);
		if ((size_t)nread > resid) {
			errx(EXIT_FAILURE, "nread=%zd resid=%zu",
			    nread, resid);
		}
		resid -= (size_t)nread;
	}

out:	free(buf);
	return NULL;
}

#if 0
#else
static void
ptysetup(int *hostfd, int *appfd)
{
	struct termios t;
	char *pts;

	RL(*hostfd = posix_openpt(O_RDWR|O_NOCTTY));
	RL(grantpt(*hostfd));
	RL(unlockpt(*hostfd));
	REQUIRE_LIBC(pts = ptsname(*hostfd), NULL);
	RL(*appfd = open(pts, O_RDWR|O_NOCTTY));

	RL(tcgetattr(*appfd, &t));
	t.c_lflag &= ~ICANON;	/* block rather than drop input */
	RL(tcsetattr(*appfd, TCSANOW, &t));
}
#endif

static void
fillpipebuf(int writefd)
{
	char buf[BUFSIZ] = {0};
	size_t n = 0;
	ssize_t nwrit;
	int flags;

	RL(flags = fcntl(writefd, F_GETFL));
	RL(fcntl(writefd, F_SETFL, flags|O_NONBLOCK));
	while ((nwrit = write(writefd, buf, sizeof(buf))) != -1)
		n += (size_t)nwrit;
	if (errno != EAGAIN)
		err(EXIT_FAILURE, "wite");
	RL(fcntl(writefd, F_SETFL, flags));
	fprintf(stderr, "filled %d with %zu bytes\n", writefd, n);
}

int
main(void)
{
	sigset_t omask;
	struct sigaction sa;
	int ptyhost, ptyapp, flags;
	pthread_barrier_t bar;
	struct context ctx, *C = &ctx;
	pthread_t t;

	memset(&sa, 0, sizeof(sa));
	sa.sa_sigaction = &on_sigio;
	sa.sa_flags = SA_SIGINFO|SA_RESTART;
	RL(sigemptyset(&sa.sa_mask));
	RL(sigaddset(&sa.sa_mask, SIGIO));
	RL(pthread_sigmask(SIG_BLOCK, &sa.sa_mask, &omask));
	RL(sigaction(SIGIO, &sa, NULL));

#if 0
	int pipefd[2];
	RL(pipe(pipefd));
	ptyhost = pipefd[1];
	ptyapp = pipefd[0];
#else
	ptysetup(&ptyhost, &ptyapp);
#endif
	fillpipebuf(ptyhost);
	RL(fcntl(ptyhost, F_SETOWN, getpid()));
	RL(flags = fcntl(ptyhost, F_GETFL));
	RL(fcntl(ptyhost, F_SETFL, flags | O_ASYNC));

	RZ(pthread_barrier_init(&bar, NULL, 2));

	memset(C, 0, sizeof(*C));
	C->bar = &bar;
	C->fd = ptyapp;
#if 0
	C->lowat = PIPE_BUF;
#else
	RL(ioctl(ptyhost, TIOCGQSIZE, &C->lowat));
#endif
	RZ(pthread_create(&t, NULL, &start_reader, C));

	REQUIRE_LIBC(alarm(2), (unsigned)-1);
	(void)pthread_barrier_wait(&bar);
#if 0
	struct pollfd pfd = { .fd = ptyhost, .events = POLLOUT };
	int nfds;
	RL(nfds = poll(&pfd, 1, -1));
	if (nfds != 1)
		errx(EXIT_FAILURE, "nfds=%d", nfds);
	if ((pfd.events & POLLOUT) == 0)
		errx(EXIT_FAILURE, "pfd.events=0x%x", pfd.events);
	printf("poll says writable\n");
	fflush(stdout);
#else
	if (sigsuspend(&omask) != -1)
		errx(EXIT_FAILURE, "sigsuspend succeeded");
	if (errno != EINTR)
		err(EXIT_FAILURE, "sigsuspend");
#endif
	RL(write(ptyhost, (const char[]){1}, 1));

	RZ(pthread_join(t, NULL));

	return 0;
}

>Fix:

	Something broken about ttwakeup or ttysig?  Something specific
	to ptys and not other terminals?




Home | Main Index | Thread Index | Old Index