[dev] [sbase][bug] cat -u is not unbuffered

From: Ryan Wilson <ryan_AT_enosys.org>
Date: Tue, 01 Mar 2016 00:23:52 -0600

So I was studying sbase and found a bug in cat. POSIX says that cat -u should "Write bytes from the input file to the standard output without delay as each is read." But if you try feeding cat input slowly you can see this is not the case:

    # Feed one byte per second to cat:
    $ yes | pv -q -L1 | ./cat -u
    <no output>

It should print one byte per second, but instead it hangs. Running it under ltrace -S reveals the problem:

    __libc_start_main(0x804877a, 2, 0xbff42644, 0x8048c10 <unfinished ...>
    setbuf(0xb7728d40, 0) = <void>
    fread(0xbff4054c, 1, 8192, 0xb7728580 <unfinished ...>
    SYS_read(8192, "y", 3076835683) = 1
    SYS_read(4096, "\n", 3076835683) = 1
    SYS_read(4096, "y", 3076835683) = 1
    SYS_read(4096, "\n", 3076835683) = 1
    SYS_read(4096, "y", 3076835683) = 1
    SYS_read(4096, "\n", 3076835683) = 1

cat disabled stdio buffering for stdout, but that is not sufficient. Input is still buffered because fread blocks until it can fill the entire 8192 byte buffer. Note that setbuf(stdin, NULL) is no fix either, because fread still wants to fill the entire buffer you give it.

The fix is to either:

* Do a getchar/putchar loop and copy one byte at a time. Sucks, but that's the only way to not block on reads with stdio. That's how cat originally did it in Unix v7, and that's how the BSDs still do it for certain options.
* Do a read(2)/write(2) loop. That's what concat() did until commit c0d36e00, but the problem is other things use concat() and its not safe for them to mix raw IO with stdio IO.

Received on Tue Mar 01 2016 - 07:23:52 CET

This archive was generated by hypermail 2.3.0 : Tue Mar 01 2016 - 07:36:05 CET