Question

  • Platform: Linux 3.2.0 x86 (Debian 7)
  • Compiler: GCC 4.7.2 (Debian 4.7.2-5)

I am writing a function that reads a single character from stdin if a character is already present in stdin. If stdin is empty the function is suppose to do nothing and return -1. I googled nonblocking input and was pointed to poll() or select(). First I tried to use select() but I could not get it to work so I tried poll() and reached the same conclusion. I am not sure what these functions do exactly but from what I understand of poll()'s documentation if I call it like so:

struct pollfd pollfds;
pollfds = STDIN_FILENO;
pollfds.events = POLLIN;
poll(pollfds, 1, 0);

if(pollfds.revents & POLLIN) will be true if "Data other than high-priority data may be read without blocking.". But poll() always times out in my test situation. How I test the function could be the problem but the functionality I want is exactly what I am testing for. Here is the function currently and the test situation as well.

#include <poll.h>
#include <stdio.h>
#include <unistd.h>

int ngetc(char *c)
{       
    struct pollfd pollfds;
    pollfds.fd = STDIN_FILENO;
    pollfds.events = POLLIN;

    poll(&pollfds, 1, 0);

    if(pollfds.revents & POLLIN)
    {
            //Bonus points to the persons that can tell me if
            //read() will change the value of '*c' if an error
            //occurs during the read
        read(STDIN_FILENO, c, 1);
            return 0;
    }
    else return -1;
}

//Test Situation:
//Try to read a character left in stdin by an fgets() call
int main()
{
    int ret = 0;
    char c = 0;
    char str[256];

    //Make sure to enter more than 2 characters so that the excess
    //is left in stdin by fgets()
    fgets(str, 2, stdin);

    ret = ngetc(&c);

    printf("ret = %i\nc = %c\n", ret, c);

    return 0;
}
Was it helpful?

Solution

You're doing IO incorrectly, the POSIX manual and all other related documentation explicitly says never to mix IO done on FILE *s and file descriptors. You have very blatantly broken this rule. This rule is in place because FILE *s use buffering an this means that after a call to fgets there will be nothing left for read to get because fgets already read all pending data into a buffer that is kept in the FILE * structure.

So since there's no way to check if an ISO C IO method will block, we have to use file descriptors only.

Since we know that STDIN_FILENO is just the number 0, we can use

fcntl (0, F_SETFL, O_NONBLOCK);

this will turn all reads on file descriptor 0 to non-blocking mode, if you want to use a different file descriptor so that you can leave 0 alone then just use dup to duplicate it.

This way, you can stay away from poll completely and implement ngetc as

ssize_t 
ngetc (char *c)
{
  return read (0, c, 1);
}

or better yet, a macro

#define ngetc(c) (read (0, (c), 1))

Thus you get a simple implementation for what you're looking for.

Edit: If you are still worried about the terminal buffering the input, you can always change the terminal's settings, see How to disable line buffering of input in xterm from program? for more information on how to do this.

Edit: The reason that one could not use fgetc instead of read is for the same reason that using fgets won't work. When one of the FILE * IO functions is run, it reads all the data from the associated file descriptor. But once that happens, poll will never return because it's waiting on a file descriptor that's always empty, and the same thing will happen with read. Thus, I suggest that you follow the advice of the documentation and never mix streams (IO using fgets, fgetc, etc.) and file descriptors (IO using read, write, etc.)

OTHER TIPS

There are two problems in your code.

  1. According to manual of poll, assigning 0 to timeout will return immediately

    If the value of timeout is 0, poll() shall return immediately. If the value of timeout is -1, poll() shall block until a requested event occurs or until the call is interrupted.

  2. fgets does not do what you expect, it is from stdio library and will buffer reads. Suppose you entered 3 letters and press enter, after fgets, the third letter won't be available to poll.

So comment out the fgets line and assign -1 to timeout in poll, and run it again to see if that's what you want.

I did not get the expected behavior with the answer above, and I actually had to take into account this answer as well which set the TTY in non canonical mode.

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <termios.h>

int main(int argc, char *argv[]) 
{
    struct termios t;
    tcgetattr(0, &t);
    t.c_lflag &= ~ICANON;
    tcsetattr(0, TCSANOW, &t);

    fcntl(0, F_SETFL, fcntl(0, F_GETFL) | O_NONBLOCK);

    printf("Starting loop (press i or q)...\n");

    for (int i = 0; ; i++) {
        char c = 0;
        read (0, &c, 1);

        switch (c) {
        case 'i':
            printf("\niteration: %d\n", i);
            break;

        case 'q':
            printf("\n");
            exit(0);
        }
    }

    return 0; 
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top