termios & non-blocking I/O

Yar Tikhiy yar at freebsd.org
Tue Apr 8 09:47:51 PDT 2003


Hello everybody,

I was shown a curious feature of FreeBSD.  (The source of a test
program illustrating it is attached at the the of this message.)

Let's consider a non-blocking file descriptor that correspons to a
terminal in raw mode.  Let's also assume read(2) is issued on it
when there is no data to read.

If for this terminal MIN > 0 and TIME == 0, read(2) will return -1
and set errno to EAGAIN.

OTOH, if MIN == 0 and TIME > 0, read(2) will return 0.

While not in disagreement with POSIX[1], such a behaviour has at
least one unwelcome consequence:  If a program has been compiled
with ``-pthread'', the TIME counter won't work on terminal descriptors
that are in blocking mode from the program's point of view -- read(2)
will instantly return 0 on them.  That is because the following
scenario will happen:

1) libc_r sets non-blocking mode on a descriptor as soon as a device
is opened (that is how i/o in user-land threads work);
2) the program sets the TIME counter through tcsetattr(3);
3) the program issues read(2), which ends up in the actual read()
syscall, which in turn returns 0 to libc_r (assuming there is no
data to read);
4) libc_r thinks this is the EOF indicator, so it instantly returns
0 to the program;
5) the program breaks.

Notice, that MIN works right with libc_r since read() syscall will
return -1/EAGAIN, which is correctly understood by libc_r: it will
block the current thread until there is data to read.

Shouldn't both TIME and MIN cases be uniform in returning -1/EAGAIN
on non-blocking descriptors?

References:
[1] IEEE Std 1003.1, 2003 Edition, General Terminal Interface,
Non-Canonical Mode Input Processing.
http://www.opengroup.org/onlinepubs/007904975/basedefs/xbd_chap11.html

-- 
Yar

=========================== cut here ==============================
#include <sys/types.h>
#include <ctype.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>

int
main(int argc, char **argv)
{
	int c, fd, flags, i, n;
	int nblock = 0;
	int vmin = 0, vtime = 0;
	struct termios ts;
	char *port = "/dev/cuaa1";
	char buf[256];

	while ((c = getopt(argc, argv, "f:hm:nt:")) != -1)
		switch (c) {
		case 'f':
			port = optarg;
			break;
		case 'm':
			vmin = atoi(optarg);
			break;
		case 'n':
			nblock = 1;
			break;
		case 't':
			vtime = atoi(optarg);
			break;
		default:
			fprintf(stderr, "usage: termtest [-n] [-f special] [-m MIN] [-t TIME]\n");
			fprintf(stderr, "\t-n -- turn on non-blocking mode explicitly\n");
			fprintf(stderr, "\t-f -- specify a device to use\n");
			fprintf(stderr, "\t-m -- set MIN value\n");
			fprintf(stderr, "\t-t -- set TIME value (in 0.1 sec units)\n");
			exit(2);
		}

	if ((fd = open(port, O_RDWR | O_NOCTTY)) == -1)
		err(2, "open");

	if (nblock) {
		if ((flags = fcntl(fd, F_GETFL, 0)) == -1)
			err(2, "getfl");
		flags |= O_NONBLOCK;
		if (fcntl(fd, F_SETFL, flags) == -1)
			err(2, "setfl");
	}

	if (tcgetattr(fd, &ts) == -1)
		err(2, "tcgetattr");
	cfmakeraw(&ts);
	if (cfsetspeed(&ts, B9600) == -1)
		err(2, "cfsetspeed");
	ts.c_cflag |= CLOCAL;
	ts.c_cflag &= ~(PARENB | PARODD);
	ts.c_cflag &= ~CSIZE;
	ts.c_cflag |= CS8;
	ts.c_cflag &= ~CSTOPB;
	ts.c_cc[VMIN] = vmin;
	ts.c_cc[VTIME] = vtime;
	if (tcsetattr(fd, TCSAFLUSH, &ts) == -1)
		err(2, "tcsetattr");

	n = read(fd, buf, sizeof(buf));
	if (n == -1)
		err(2, "read");
	else if (n < 1)
		errx(2, "short read: %d bytes", n);
	printf("Read %d bytes:\n", n);
	for (i = 0; i < n; i++)
		printf("%#04x - %c\n", buf[i], isprint(buf[i]) ? buf[i] : '?');

	close(fd);
	return (0);
}


More information about the freebsd-arch mailing list