Why can a program (C, C++) in the terminal on linux read stdout and write to stdin?

Question:

Accidentally stumbled upon a mysterious (for me) behavior of the program.

Here is the protocol

avp@avp-ubu1:~/src/ig/tst$ cat tpoll.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

main ()
{
  int lw = write(fileno(stdin),"xaxa\n",5);
  char buf[100];
  int lr = read(fileno(stdout),buf,99);
  buf[(lr > 0)? lr:0] = 0;
  printf ("lw = %d lr = %d buf = [%s]\n",lw,lr,buf);

  exit(0);
}
avp@avp-ubu1:~/src/ig/tst$ gcc tpoll.c
avp@avp-ubu1:~/src/ig/tst$ ./a.out 
xaxa
js39993
lw = 5 lr = 8 buf = [js39993
]
avp@avp-ubu1:~/src/ig/tst$ g++ tpoll.c 
avp@avp-ubu1:~/src/ig/tst$ ./a.out 
xaxa
jsk393
lw = 5 lr = 7 buf = [jsk393
]
avp@avp-ubu1:~/src/ig/tst$ 
avp@avp-ubu1:~/src/ig/tst$ cat /etc/issue
Ubuntu 10.04.4 LTS \n \l

avp@avp-ubu1:~/src/ig/tst$ env | grep TERM
TERM=xterm
COLORTERM=gnome-terminal
avp@avp-ubu1:~/src/ig/tst$ ps -ef | grep term
avp       1536     1  0 Oct24 ?        00:00:03 gnome-terminal
avp       2223  1538  0 01:17 pts/0    00:00:00 grep --color=auto term
avp@avp-ubu1:~/src/ig/tst$ 
avp@avp-ubu1:~/src/ig/tst$

To be honest, I haven't been looking for an answer yet. Perhaps this is a well-known fact, or maybe not.

If anyone knows why this is happening, please explain.

In Windows everything is as expected

c:/Users/avp/src/cc/hashcode $ gcc tpoll.c 
c:/Users/avp/src/cc/hashcode $ ./a
lw = -1 lr = -1 buf = []
c:/Users/avp/src/cc/hashcode $

Actually, ubuntu is under Windows in VirtualBox.

UPD

Added the program and started a little. Apparently sh is usually started with daps of one device and this is inherited. As an exception, I found mpi on the cluster.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>

static void
pristat (char *what, struct stat *sbuf)
{
  printf ("%s stat:\n S_ISREG %s, S_ISCHR %s, S_ISFIFO %s, S_ISSOCK %s\n\
st_dev=%ld st_ino=%ld st_rdev=%ld st_mode=%lx\n", what,
      S_ISREG(sbuf->st_mode)? "Yes":"No",
      S_ISCHR(sbuf->st_mode)? "Yes":"No",
      S_ISFIFO(sbuf->st_mode)? "Yes":"No",
      S_ISSOCK(sbuf->st_mode)? "Yes":"No",
      (long)sbuf->st_dev, (long)sbuf->st_ino, (long)sbuf->st_rdev, (long)sbuf->st_mode);
}

static char*
stdiff (struct stat *sbuf1, struct stat *sbuf2, char *buf)
{
  *buf = 0;
  if (sbuf1->st_dev != sbuf2->st_dev)
    strcat(buf,"st_dev ");
  if (sbuf1->st_ino != sbuf2->st_ino)
    strcat(buf,"st_ino ");
  if (sbuf1->st_mode != sbuf2->st_mode)
    strcat(buf,"st_mode ");
  if (sbuf1->st_nlink != sbuf2->st_nlink)
    strcat(buf,"st_nlink ");
  if (sbuf1->st_uid != sbuf2->st_uid)
    strcat(buf,"st_uid ");
  if (sbuf1->st_gid != sbuf2->st_gid)
    strcat(buf,"st_gid ");
  if (sbuf1->st_rdev != sbuf2->st_rdev)
    strcat(buf,"st_rdev ");
  if (sbuf1->st_size != sbuf2->st_size)
    strcat(buf,"st_size ");
  if (sbuf1->st_blksize != sbuf2->st_blksize)
    strcat(buf,"st_blksize ");
  if (sbuf1->st_blocks != sbuf2->st_blocks)
    strcat(buf,"st_blocks ");
  if (sbuf1->st_atime != sbuf2->st_atime)
    strcat(buf,"st_atime ");
  if (sbuf1->st_mtime != sbuf2->st_mtime)
    strcat(buf,"st_mtime ");
  if (sbuf1->st_ctime != sbuf2->st_ctime)
    strcat(buf,"st_ctime ");
  return buf;
}

main ()
{
  int lw = write(fileno(stdin),"xaxa\n",5);
  char buf[1000];
  int lr = read(fileno(stdout),buf,99);
  buf[(lr > 0)? lr:0] = 0;
  printf ("lw = %d lr = %d buf = [%s]\n",lw,lr,buf);
  fprintf(stderr,"Try read stderr:");
  lr = read(fileno(stderr),buf,99);
  buf[(lr > 0)? lr:0] = 0;
  printf ("stderr: lr = %d buf = [%s]\n",lr,buf);

  struct stat sbufi, sbufo, sbufe;
  fstat(fileno(stdin),&sbufi);
  fstat(fileno(stdout),&sbufo);
  fstat(fileno(stderr),&sbufe);

  int diffio = memcmp(&sbufi,&sbufo,sizeof(sbufi)),
    diffoe = memcmp(&sbufi,&sbufo,sizeof(sbufi));

  if (diffio == 0 && diffoe == 0)
    pristat("All",&sbufi);
  else {
    printf ("stdin & stdout differ in %s\nstdout & stderr differ in %s\n",
        stdiff(&sbufi,&sbufo,buf), stdiff(&sbufo,&sbufe,&buf[500]));

    pristat("stdin",&sbufi);
    pristat("stdout",&sbufo);
    pristat("stderr",&sbufe);
  }

  exit(0);
}

This is the usual Xubuntu (in Emacs eshell the same)

avp@avp-xub11:~/src/tst$ gcc tstdio.c 
avp@avp-xub11:~/src/tst$ ./a.out 
xaxa
read STDOUT
lw = 5 lr = 12 buf = [read STDOUT
]
Try read stderr:read STDERR
stderr: lr = 12 buf = [read STDERR
]
All stat:
 S_ISREG No, S_ISCHR Yes, S_ISFIFO No, S_ISSOCK No
st_dev=11 st_ino=6 st_rdev=34819 st_mode=2190
avp@avp-xub11:~/src/tst$ 
avp@avp-xub11:~/src/tst$

This is RedHat on a cluster, first a computing node by mpi, then a "working environment".

[root@manager soft]# mpirun -hosts cn01 ./a.out
lw = -1 lr = -1 buf = []
stderr: lr = -1 buf = []
stdin & stdout differ in st_ino 
stdout & stderr differ in st_ino 
stdin stat:
 S_ISREG No, S_ISCHR No, S_ISFIFO Yes, S_ISSOCK No
st_dev=8 st_ino=333128 st_rdev=0 st_mode=1180
stdout stat:
 S_ISREG No, S_ISCHR No, S_ISFIFO Yes, S_ISSOCK No
st_dev=8 st_ino=333129 st_rdev=0 st_mode=1180
stderr stat:
 S_ISREG No, S_ISCHR No, S_ISFIFO Yes, S_ISSOCK No
st_dev=8 st_ino=333130 st_rdev=0 st_mode=1180
Try read stderr:[root@manager soft]# 
[root@manager soft]# 
[root@manager soft]# ./a.out 
xaxa
jkks
lw = 5 lr = 5 buf = [jkks
]
Try read stderr:Cluster manager
stderr: lr = 16 buf = [Cluster manager
]
All stat:
 S_ISREG No, S_ISCHR Yes, S_ISFIFO No, S_ISSOCK No
st_dev=11 st_ino=3 st_rdev=34816 st_mode=2190
[root@manager soft]#

You can sum it up. When you see this for the first time, it is extremely unexpected (and I stumbled as a result of my inattention in the test program), and then you understand that why not?

Answer:

Heh, funny. Probably all of these standard file descriptors are attached to /dev/tty, which was opened with O_RDWR in all cases.

Scroll to Top