summaryrefslogtreecommitdiff
path: root/libbb
diff options
context:
space:
mode:
authorDenis Vlasenko2008-02-20 22:23:24 +0000
committerDenis Vlasenko2008-02-20 22:23:24 +0000
commite376d454bb70ed41bbc3eb0358d37fa30c94358d (patch)
treeeb53c600dcde841a7617a19f819ae3e9cfe7fd84 /libbb
parentae86a338b89c1339588226cb2298e1785aaa7b90 (diff)
downloadbusybox-e376d454bb70ed41bbc3eb0358d37fa30c94358d.zip
busybox-e376d454bb70ed41bbc3eb0358d37fa30c94358d.tar.gz
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
Diffstat (limited to 'libbb')
-rw-r--r--libbb/lineedit.c4
-rw-r--r--libbb/read.c56
2 files changed, 57 insertions, 3 deletions
diff --git a/libbb/lineedit.c b/libbb/lineedit.c
index 529344f..9aab637 100644
--- a/libbb/lineedit.c
+++ b/libbb/lineedit.c
@@ -1408,9 +1408,9 @@ int read_line_input(const char *prompt, char *command, int maxsize, line_input_t
parse_and_put_prompt(prompt);
while (1) {
- fflush(stdout);
+ fflush(NULL);
- if (safe_read(STDIN_FILENO, &c, 1) < 1) {
+ if (nonblock_safe_read(STDIN_FILENO, &c, 1) < 1) {
/* if we can't read input then exit */
goto prepare_to_die;
}
diff --git a/libbb/read.c b/libbb/read.c
index 502d407..2cd86b8 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);