diff options
authorRich Felker <>2011-04-09 01:17:55 -0400
committerRich Felker <>2011-04-09 01:17:55 -0400
commit2cff36a84f268c09f4c9dc5a1340652c8e298dc0 (patch)
parent67e793e5e14fe031569117fd88328268e67ad4ee (diff)
work around a nasty bug in linux readv syscall
according to posix, readv "shall be equivalent to read(), except..." that it places the data into the buffers specified by the iov array. however on linux, when reading from a terminal, each iov element behaves almost like a separate read. this means that if the first iov exactly satisfied the request (e.g. a length-one read of '\n') and the second iov is nonzero length, the syscall will block again after getting the blank line from the terminal until another line is read. simply put, entering a single blank line becomes impossible. the solution, fortunately, is simple. whenever the buffer size is nonzero, reduce the length of the requested read by one byte and let the last byte go through the buffer. this way, readv will already be in the second (and last) iov, and won't re-block on the second iov.
1 files changed, 4 insertions, 3 deletions
diff --git a/src/stdio/__stdio_read.c b/src/stdio/__stdio_read.c
index a2e4cd62..218bd88d 100644
--- a/src/stdio/__stdio_read.c
+++ b/src/stdio/__stdio_read.c
@@ -3,7 +3,7 @@
size_t __stdio_read(FILE *f, unsigned char *buf, size_t len)
struct iovec iov[2] = {
- { .iov_base = buf, .iov_len = len },
+ { .iov_base = buf, .iov_len = len - !!f->buf_size },
{ .iov_base = f->buf, .iov_len = f->buf_size }
ssize_t cnt;
@@ -14,9 +14,10 @@ size_t __stdio_read(FILE *f, unsigned char *buf, size_t len)
f->rpos = f->rend = 0;
return cnt;
- if (cnt <= len) return cnt;
- cnt -= len;
+ if (cnt <= iov[0].iov_len) return cnt;
+ cnt -= iov[0].iov_len;
f->rpos = f->buf;
f->rend = f->buf + cnt;
+ if (f->buf_size) buf[len-1] = *f->rpos++;
return len;