Shang Jian Ding
Shang Jian Ding

Reputation: 2136

Linux stdin buffering

With the following program and the sample run, I expected to see "stdin contains 9 bytes", but as you can see in the sample run, I got "stdin contains 0 bytes". Why is that? How can I fix this program to get the actual number of unread bytes in stdin?

Program:

#include <stdio.h>
#include <sys/ioctl.h>
#include <unistd.h>

void main() {
    if ( (setvbuf(stdin, NULL, _IONBF, 0) |
            setvbuf(stdout, NULL, _IONBF, 0)) != 0) {

        printf("setting stdin/stdout to unbuffered failed");
        return;
    }

    printf("type some keys\n");
    sleep(3);
    printf("\n");

    unsigned long bytesUnread=0;
    if (ioctl(0, FIONREAD, &bytesUnread) != 0) {
        printf("ioctl error");
        return;
    }
    printf("stdin contains %ld bytes\n", bytesUnread);
}

Sample run:

$ ./a.out          
type some keys         
some keys              
stdin contains 0 bytes 

Here is another sample run where I hit enter, and you can see it worked as expected.

$ ./a.out                 
type some keys
asd

stdin contains 4 bytes

Upvotes: 1

Views: 2368

Answers (2)

R.. GitHub STOP HELPING ICE
R.. GitHub STOP HELPING ICE

Reputation: 215259

Redirect stdin from a file or press enter after pressing a few keys and you will see that your code works as expected, independent of whether you call setvbuf or not, because your problem is not the FILE stream being buffered. The FILE stream isn't even involved in your ioctl. Rather, your problem is that the bytes have not been transmitted yet. They're in the kernel's line editing buffer for the canonical-mode tty, allowing you to backspace, CtrlW, etc. them before sending them.

If you want the tty layer to transmit bytes as they're generated on the terminal, rather than only in aggregate after line editing, you need to take the tty out of canonical mode. The termios.h interfaces are how you do this (see man 3 termios). Generally the easiest way is to tcgetattr, cfmakeraw, then tcsetattr, but cfmakeraw is not entirely portable so it can be preferable to just do the equivalent changes yourself.

Upvotes: 3

Chris Dodd
Chris Dodd

Reputation: 126203

setvbuf sets the output buffering of a FILE stream. You can't change the input buffering, because it makes no sense to do so -- you generally don't know what is available to be read from the underlying file descriptor until you actually read it. Even in cases where you could know (eg, by checking with an ioctl), that might change, due to later events and other threads and processes accessing the file descriptor. So you can never actually know for certain what you are going to read until you read it.

Looking at you sample run, it looks like you are using a terminal and not hitting enter? In which case, the input will be held in the terminal buffer (in case you will later hit backspace), and your ioctl call (which checks the file descriptor's buffer) will not see it.

In any case, getting the amount of data in the input buffer is not useful, as trying to do anything with that knowledge before you actually read it is a race condition -- you have no way of knowing if some other process or driver is in the middle of modifying the buffer. So for any real use, you just want to read the input data and react based on what the (atomic) read system call returns.

Upvotes: 1

Related Questions