Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why can I read from STDOUT and get a user's terminal input?

Tags:

c

linux

I'm perplexed. Under OSX and Linux (using BASH, TCSH, FISH, and DASH) this code successfully reads user input when that input is provided directly through the terminal but not when the user input is provided through a pipe.

Even more perplexing though: I don't expect this code to work at all! This code is READING from STDOUT. I'd expect the read call to return an error since I'm essentially reading from a write only pipe.

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

int main(int argc, char** argv) {
  char buffer[11];
  size_t nread;
  if((nread=read(1, buffer, sizeof(buffer)-1)) <= 0) {
    fprintf(stderr, "error!\n");
    return 1;
  }
  buffer[nread] = '\0';
  printf("I read '%s'\n", buffer);
  return 0;
}

To build (assuming you named the sample test.c):

$ gcc -o test test.c

Then, this reads the user input:

$ ./test
abcd
I read 'abcd

'

But, this does not:

$ echo "abcd" | ./test
<< program still waiting for the read to finish

Any insights?

like image 861
Brian Avatar asked Feb 22 '14 16:02

Brian


2 Answers

The terminal implements stdin and stdout as the same read/write pseudo terminal and the two descriptors both map to the same thing by default. However, when you use a pipe the shell creates an actual pipe with distinct read and write ends, the read-end of which replaces the stdin of your program but not the stdout. Thus you cannot read the pipe's input from stdout, which is no longer the same thing as stdin.

Here's a little test showing what happens to the descriptors:

#include <stdio.h>
#include <sys/stat.h>

int main (void) {
    struct stat st;
    (void) fstat(0, &st);
    (void) printf("stdin  dev=%ld node=%ld\n", (long) st.st_dev, (long) st.st_ino);
    (void) fstat(1, &st);
    (void) printf("stdout dev=%ld node=%ld\n", (long) st.st_dev, (long) st.st_ino);
    return 0;
}

And when run:

$ ./fdcheck
stdin  dev=389931976 node=4338
stdout dev=389931976 node=4338
$ echo foo | ./fdcheck
stdin  dev=0 node=-5077412903630272353
stdout dev=389931976 node=4338

Under Linux you can also do:

$ readlink /proc/self/fd/0
/dev/pts/4
$ echo foo | readlink /proc/self/fd/0
pipe:[353335]
$ echo foo | readlink /proc/self/fd/1
/dev/pts/4
like image 162
Arkku Avatar answered Oct 01 '22 17:10

Arkku


Most likely this is working, because on a terminal the stdin and stdout may point to the same object. That this is not correct behaviour you can see when you use pipes, because the output buffer should't be really provide data when read from. So this is maybe some bug or a sideeffect in the implementation of the terminal.

like image 36
Devolus Avatar answered Oct 01 '22 17:10

Devolus