From e376d454bb70ed41bbc3eb0358d37fa30c94358d Mon Sep 17 00:00:00 2001 From: Denis Vlasenko Date: Wed, 20 Feb 2008 22:23:24 +0000 Subject: libbb: introduce and use nonblock_safe_read(). Yay! Our shells are immune from this nasty O_NONBLOCK now! function old new delta nonblock_safe_read - 78 +78 file_get 276 295 +19 generateMTFValues 428 435 +7 read_line_input 1776 1772 -4 preadbuffer 543 450 -93 ------------------------------------------------------------------------------ (add/remove: 1/0 grow/shrink: 2/2 up/down: 104/-97) Total: 7 bytes text data bss dec hex filename 615190 715 23924 639829 9c355 busybox_old 615168 715 23924 639807 9c33f busybox_unstripped --- libbb/read.c | 56 +++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) (limited to 'libbb/read.c') diff --git a/libbb/read.c b/libbb/read.c index 502d407c4..2cd86b81b 100644 --- a/libbb/read.c +++ b/libbb/read.c @@ -20,6 +20,58 @@ ssize_t safe_read(int fd, void *buf, size_t count) return n; } +/* Suppose that you are a shell. You start child processes. + * They work and eventually exit. You want to get user input. + * You read stdin. But what happens if last child switched + * its stdin into O_NONBLOCK mode? + * + * *** SURPRISE! It will affect the parent too! *** + * *** BIG SURPRISE! It stays even after child exits! *** + * + * This is a design bug in UNIX API. + * fcntl(0, F_SETFL, fcntl(0, F_GETFL, 0) | O_NONBLOCK); + * will set nonblocking mode not only on _your_ stdin, but + * also on stdin of your parent, etc. + * + * In general, + * fd2 = dup(fd1); + * fcntl(fd2, F_SETFL, fcntl(fd2, F_GETFL, 0) | O_NONBLOCK); + * sets both fd1 and fd2 to O_NONBLOCK. This includes cases + * where duping is done implicitly by fork() etc. + * + * We need + * fcntl(fd2, F_SETFD, fcntl(fd2, F_GETFD, 0) | O_NONBLOCK); + * (note SETFD, not SETFL!) but such thing doesn't exist. + * + * Alternatively, we need nonblocking_read(fd, ...) which doesn't + * require O_NONBLOCK dance at all. Actually, it exists: + * n = recv(fd, buf, len, MSG_DONTWAIT); + * "MSG_DONTWAIT: + * Enables non-blocking operation; if the operation + * would block, EAGAIN is returned." + * but recv() works only for sockets! + * + * So far I don't see any good solution, I can only propose + * that affected readers should be careful and use this routine, + * which detects EAGAIN and uses poll() to wait on the fd. + * Thanksfully, poll() doesn't give rat's ass about O_NONBLOCK flag. + */ +ssize_t nonblock_safe_read(int fd, void *buf, size_t count) +{ + struct pollfd pfd[1]; + ssize_t n; + + while (1) { + n = safe_read(fd, buf, count); + if (n >= 0 || errno != EAGAIN) + return n; + /* fd is in O_NONBLOCK mode. Wait using poll and repeat */ + pfd[0].fd = fd; + pfd[0].events = POLLIN; + safe_poll(pfd, 1, -1); + } +} + /* * Read all of the supplied buffer from a file. * This does multiple reads as necessary. @@ -93,6 +145,7 @@ char *reads(int fd, char *buffer, size_t size) // Read one line a-la fgets. Reads byte-by-byte. // Useful when it is important to not read ahead. +// Bytes are appended to pfx (which must be malloced, or NULL). char *xmalloc_reads(int fd, char *buf) { char *p; @@ -106,7 +159,8 @@ char *xmalloc_reads(int fd, char *buf) p = buf + sz; sz += 128; } - if (safe_read(fd, p, 1) != 1) { /* EOF/error */ + /* nonblock_safe_read() because we are used by e.g. shells */ + if (nonblock_safe_read(fd, p, 1) != 1) { /* EOF/error */ if (p == buf) { /* we read nothing [and buf was NULL initially] */ free(buf); -- cgit v1.2.3