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