After fixing some infelicities in the code, I came up with a semi-instrumented version of your program like this:
#include <unistd.h>
#include <stdio.h>
#include <string.h>
int main(void)
{
int pid, fds[2], pid1;
char buf[200];
pipe(fds);
pid = fork();
if (pid == 0)
{
close(fds[0]);
printf("Prompt: "); fflush(0);
if (scanf("%199s", buf) != 1)
fprintf(stderr, "scanf() failed\n");
else
write(fds[1], buf, strlen(buf) + 1);
}
else
{
pid1 = fork();
if (pid1 == 0)
{
close(fds[1]);
if (read(fds[0], buf, sizeof(buf)) > 0)
printf("%s\n", buf);
else
fprintf(stderr, "read() failed\n");
}
else
{
/*Line1: wait();*/
}
}
return 0;
}
That compiles cleanly under stringent options (GCC 5.1.0 on Mac OS X 10.10.5):
gcc -O3 -g -std=c11 -Wall -Wextra -Werror p11.c -o p11
When I run it, the output is:
$ ./p11
Prompt: scanf() failed
read() failed
$
The problem is clear; the scanf()
fails. At issue: why?
The wait()
version needs an extra header #include <sys/wait.h>
and the correct calling sequence. I used the paragraph:
else
{
printf("Kids are %d and %d\n", pid, pid1);
int status;
int corpse = wait(&status);
printf("Parent gets PID %d status 0x%.4X\n", corpse, status);
}
When compiled and run, the output is now:
$ ./p11
Kids are 20461 and 20462
Prompt: Albatross
Albatross
Parent gets PID 20461 status 0x0000
$
So, the question becomes: how or why is the standard input of the child process closed when the parent doesn't wait? It is Bash doing some job control that wreaks havoc.
I upgraded the program once more, using int main(int argc, char **argv)
and testing whether the command was passed any arguments:
else if (argc > 1 && argv != 0) // Avoid compilation warning for unused argv
{
printf("Kids are %d and %d\n", pid, pid1);
int status;
int corpse = wait(&status);
printf("Parent gets PID %d status 0x%.4X\n", corpse, status);
}
I've got an Heirloom Shell, which is close to an original Bourne shell. I ran the program under that, and it behaved as I would expect:
$ ./p11
Prompt: $ Albatross
Albatross
$ ./p11 1
Kids are 20483 and 20484
Prompt: Albatross
Albatross
Parent gets PID 20483 status 0x0000
$
Note the $
after the Prompt:
in the first run; that's the shell prompt, but when I type Albatross
, it is (fortunately) read by the child of the p11
process. That's not guaranteed; it could have been the shell that read the input. In the second run, we get to see the parent's output, then the children at work, then the parents exiting message.
So, under classic shells, your code would work as expected. Bash is somehow interfering with the normal operation of child processes. Korn shell behaves like Bash. So does C shell (tcsh
). Attempting dash
, I got interesting behaviour (3 runs):
$ ./p11
Prompt: $ Albatross
scanf() failed
read() failed
dash: 2: Albatross: not found
$ ./p11
Prompt: $ Albatross
scanf() failed
dash: 4: Albatross: not found
$ read() failed
$ ./p11
Prompt: scanf() failed
$ read() failed
$
Note that the first two runs shows dash
reading the input, but the children did not detect problems until after I hit return after typing Albatross. The last time, the children detected problems before I typed anything.
And, back with Bash, redirecting standard input works 'sanely':
$ ./p11 <<< Albatross
Prompt: Albatross
$ ./p11 1 <<< Albatross
Kids are 20555 and 20556
Prompt: Albatross
Parent gets PID 20555 status 0x0000
$
The output Albatross
comes from the second child, of course.
The answer is going to be lurking somewhere in behaviour of job control shells, but it's enough to make me want to go back to life before that.