I am working through the homework in OSTEP, and am doing the following exercise:
Write a program that creates two children, and connects the standard output of one to the standard input of the other, using the pipe() system call.
Here's how I understand what I need to do: to connect the standard output of the first child I should make the pipe with pipe(pipe_name)
in the parent process, do one fork()
to create the first child, then use dup2( pipe_name[1],STDOUT_FILENO);
to change the standard output stream. After that, any printf
statement will send the text to the pipe. Then I go back to the parent process and do another fork()
to create a second child, and do dup2( pipe_name[0],STDIN_FILENO);
to change the input.
Is this correct? If so, how does the second child get to read what's in the pipe? I am trying to do
char words;
read(pipe_name[0], words, 1000);
but my output is just 0
, not the string I printed earlier. Is there something wrong with my C syntax, or is it more fundamental (I am just learning C as I do this, so the former is a definite possibility).
Here's my full code:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
int
main(int argc, char *argv[])
{ int pipe_name[2];
printf("hello world (pid:%d)\n", (int) getpid());
if(pipe(pipe_name)<0){ printf("mission failed! We'll gettem next time...\n");
} else {
int rc = fork();
if (rc < 0) {
// fork failed; exit
fprintf(stderr, "fork failed\n");
exit(1);
} else if (rc == 0) {
// child (new process)
printf("hello, I am child (pid:%d)\n", (int) getpid());
dup2( pipe_name[1],STDOUT_FILENO);
printf("hello, I am child (pid:%d)\n", (int) getpid());
} else {
// parent goes down this path (original process)
wait(NULL);
int rc2 = fork();
if (rc2 < 0) {
// fork failed; exit
fprintf(stderr, "fork failed\n");
exit(1);
} else if (rc2 == 0) {
dup2( pipe_name[1],STDIN_FILENO);
char words;
read(STDIN_FILENO, words, 1000);
printf("My sibling says:\n %d\n", words);
} else { wait(NULL);
// parent goes down this path (original process)
printf("hello, I am parent of %d (pid:%d)\n",
rc2, (int) getpid());
}
}
return 0;
}}
and the output is
hello world (pid:30421)
hello, I am child (pid:30422)
My sibling says:
0
hello, I am parent of 30423 (pid:30421)
These are the primary issues affecting your code:
How do you plan to read up to 1000 characters into a single character
char words; read(STDIN_FILENO, words, 1000);
If you read characters, you need to format it as a string (%s
, not %d
).
Duplicating the write end of the pipe to standard input means that read operations will fail
dup2(pipe_name[1],STDIN_FILENO);
That 1 needs to be 0.
What's written on the pipe will not be a null-terminated string; it will be a byte array. You have to make sure it is a string.
It's a good idea to make sure that the unused ends of pipes are closed promptly. This isn't as much of a problem here since you only do one read operation, rather than reading until EOF. Nevertheless, it is best to develop good habits from the start.
Also, errors should be reported on stderr
, and programs should exit with a non-zero status when something goes wrong.
Here's some revised code that addresses these issues:
/* SO 7818-3904 */
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
static void wait_loop(void)
{
int corpse;
int status;
while ((corpse = wait(&status)) != -1)
{
fprintf(stderr, "%d: PID %d exited with status 0x%.4X\n",
(int)getpid(), corpse, status);
}
}
int
main(void)
{
int pipe_name[2];
printf("hello world (pid:%d)\n", (int)getpid());
if (pipe(pipe_name) < 0)
{
fprintf(stderr, "mission failed! We'll gettem next time...\n");
exit(1);
}
int rc1 = fork();
if (rc1 < 0)
{
fprintf(stderr, "fork 1 failed\n");
exit(1);
}
else if (rc1 == 0)
{
printf("hello, I am child 1 (pid:%d)\n", (int)getpid());
dup2(pipe_name[1], STDOUT_FILENO);
close(pipe_name[0]);
close(pipe_name[1]);
printf("hello, I am child 1 (pid:%d)\n", (int)getpid());
}
else
{
wait_loop(); /* Not really necessary here */
int rc2 = fork();
if (rc2 < 0)
{
fprintf(stderr, "fork 2 failed\n");
exit(1);
}
else if (rc2 == 0)
{
close(pipe_name[1]);
printf("hello, I am child 2 (pid:%d)\n", (int)getpid());
dup2(pipe_name[0], STDIN_FILENO);
close(pipe_name[0]);
char words[1000];
int nbytes = read(STDIN_FILENO, words, sizeof(words));
if (nbytes > 0)
{
words[nbytes] = '\0';
printf("My sibling says:\n%d: [%s]\n", nbytes, words);
}
else
{
fprintf(stderr, "%d: Got value %d from read(): %d %s\n",
(int)getpid(), nbytes, errno, strerror(errno));
exit(1);
}
}
else
{
close(pipe_name[0]);
close(pipe_name[1]);
wait_loop();
// parent goes down this path (original process)
printf("%d: hello, I am parent of %d and %d\n",
(int)getpid(), rc1, rc2);
}
}
return 0;
}
Sample output:
hello world (pid:23510)
hello, I am child 1 (pid:23511)
23510: PID 23511 exited with status 0x0000
hello, I am child 2 (pid:23512)
My sibling says:
32: [hello, I am child 1 (pid:23511)
]
23510: PID 23512 exited with status 0x0000
23510: hello, I am parent of 23511 and 23512
Note that I put the waiting code into a function and print out the values returned — I regard wait(NULL)
as an indication of sloppiness. Your process might have children it didn't know about (unlikely, but possible). And getting the output helps with debugging.
The first wait loop isn't really necessary, though in this context it does no harm. However, if the children transferred enough data (these days, more than 64 KiB, but POSIX only guarantees that a pipe can hold 4 KiB), then the early wait loop will prevent the reader from starting and the writer will never finish because it is waiting on some process to empty the pipe, and that won't happen. This is a deadlock, something to be avoided.