Date: Tue, 8 Apr 2003 20:46:14 +0400 From: Yar Tikhiy <yar@freebsd.org> To: arch@freebsd.org Subject: termios & non-blocking I/O Message-ID: <20030408164614.GA7236@comp.chem.msu.su>
next in thread | raw e-mail | index | archive | help
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); }
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?20030408164614.GA7236>