2

I have a program A doing the following:

  1. Read 2 Bytes from input
  2. Print read input
  3. execing into program B.

Program B does the following

  1. Read 2 Bytes from input
  2. Print input

To make it concrete following are programs A, B:

A:

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

int main(){
  char a[3];
  fgets(a, 3, stdin);
  printf("%s\n", a);
  char* args[] = {NULL};
  execv("./read2", args);
}

B:

#include <stdio.h>

int main(){
   char a[] = "hy";
   fgets(a, 3, stdin);
   printf("%s\n", a);
}

When I execute it like echo 'abcd' | ./A, I expected the following output

ab
cd

But I get ab hy

Why isn't B reading from its standard input?

4

TLDR: the parent MUST use unbuffered IO if the subsequent process needs to read exactly from where the parent left off.

With not-buffered IO, the program behaves correctly:

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

char buf[3];

int main(int argc, char *argv[])
{
    read(STDIN_FILENO, buf, 2);
    printf("%s '%s'\n", *argv, buf);
    if (strcmp(*argv, "./childtu") == 0) return 0;
    execl("./readtwo", "./childtu", (char *) 0);
}

As run via

$ make readtwo            
cc     readtwo.c   -o readtwo
$ echo abcdefg | ./readtwo
./readtwo 'ab'
./childtu 'cd'
$ 

Buffered IO (via fgets) in the parent is the problem, as the child will only be able to read from standard input if there's more input than what the parent reads off in advance:

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

char buf[3];

int main(int argc, char *argv[])
{
    fgets(buf, 3, stdin);
    printf("%s '%s'\n", *argv, buf);
    if (strcmp(*argv, "./childtu") == 0) return 0;
    execl("./readtwo", "./childtu", (char *) 0);
}

If curious one could binary search for the exact buffer size, or see what is set in the kernel:

$ perl -e 'print(("a")x99999)' | ./readtwo
./readtwo 'aa'
./childtu 'aa'
$ 

With strace (or similar) we can observe how much the parent process reads from standard input (fd 0):

$ echo asdf | strace -o blah ./readtwo
./readtwo 'as'
./childtu ''
$ fgrep 'read(0' blah
read(0, "asdf\n", 4096)                 = 5
read(0, "", 4096)                       = 0
$

Here, the parent wanted 4096 bytes (but only got five), and the exec'd process got zero as there's nothing left. So don't use a buffered read in the parent process if this is a problem.

  • Strange. If I replace my fgets with read, it works indeed – nitishch Apr 4 '17 at 17:19
  • @nitishch not strange at all, it's to be expected with buffered I/O – roaima Apr 4 '17 at 20:54
  • Sorry. I don't understand. Can you elaborate the comment as an answer – nitishch Apr 5 '17 at 5:02

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.