I've written my own shell in C, and when I run ls | grep .c, I get nothing. Although unpiped commands are working fine, like ls. Here's my code:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include "shell.h"
#include "builtins.h"
#include "makeargv.h"
void shell()
{
pid_t shell_pid;
int i;
int flag = 1;
int argc0;
int argc1;
int fdl[2];
int fdr[2];
size_t input_size;
char cwd[128]; //this is being toggled
char *delim0;
char *delim1;
char *lastarg;
char *input;
char *debugdescriptor;
char **argvp;
char **firstargs;
shell_pid = getpid();
do
{
// Retrieve PID & CWD of the parent process.
getcwd(cwd, (128 * sizeof(char)));
printf("{%i}%s$ ", shell_pid, cwd);
// Retrieve input from stdin.
input = NULL;
input_size = 0;
getline(&input, &input_size, stdin);
//seperates the input into pipe-delimited arguments("tokens")
delim1 = "|\n";
argc1 = makeargv(input, delim1, &argvp);
//got some debugging tools here
//debugdescriptor = "PIPE-SEPERATED";
//debug_args(&argvp, &argc1, debugdescriptor);
//check for quit and cd first
delim0 = " ";
argc0 = makeargv(argvp[0], delim0, &firstargs);
//more debugging tools here
//debugdescriptor = "FIRST ARGS";
//debug_args(&firstargs, &argc0, debugdescriptor);
//exit
if((i = strcmp(firstargs[0],"exit")) == 0 || (i = strcmp(firstargs[0],"quit")) == 0)
{
printf("===========SHELL TERMINATED==============\n\n");
flag = 0;
}
//cd
else if((i = strcmp(firstargs[0],"cd")) == 0)
{
chdir(firstargs[1]);
}
else // Create a child process to handle user input.
{
char **thisarg;
int childlayer = 0;
pid_t pid = fork();
wait(0);
if(pid == 0)
childlayer++;
int tokens = argc1 - 1;
if(argc1 == 1 && pid == 0)
{
makeargv(argvp[tokens], delim0, &thisarg);
execvp(thisarg[0], thisarg);
}
else //more than 1 arguement, (has pipes)
{
while(pid == 0 && childlayer < argc1){
if(childlayer == 1){ //rightmost
pipe(fdl);
pid = fork();
wait(0);
if(pid == 0)
childlayer++;
if(pid > 0){
close(fdl[1]);
dup2(fdl[0], STDIN_FILENO); //sets the final output to write to STDIN
execute(childlayer, argc1, &argvp);
}
}
else if(childlayer > 1 && childlayer < argc1-1){ //middle args
pipe(fdr);
fdr[1] = fdl[1];
fdr[0] = fdl[0];
dup2(fdr[1], STDOUT_FILENO);
pipe(fdl);
pid = fork();
wait(0);
if(pid == 0)
childlayer++;
if(pid > 0){
close(fdl[1]);
dup2(fdl[0], STDIN_FILENO);
execute(childlayer, argc1, &argvp);
}
}
else{ //leftmost
pipe(fdr);
fdr[0] = fdl[0];
fdr[1] = fdl[1];
close(fdr[0]);
dup2(fdr[1], STDOUT_FILENO);
execute(childlayer, argc1, &argvp);
}
}
}
}
}while(flag == 1);
}
I think I may be getting stuck in a child process when I use the pipes, but I haven't been able to see where. Thanks.
You are almost certainly failing to close all your file descriptors. One source of such an error is your dup2 calls.
After:
dup2(fdr[1], STDOUT_FILENO);
you should call
close(fdr[1]);
Why do you wait(0) immediately after fork()? In the child, this will return immediately with an error, but in the parent, it will block until the child exits. I'm having a hard time following how the pipeline is established because each child is forking off the next child in the pipeline. I'm guessing the wait(0) is creating a chicken-and-egg problem; the parent can't start until the child exits, but the child can't exit because it needs input from the parent. Wouldn't it be simpler if the shell process just looped over the pipeline components and forked each one itself, and then waited for them all to finish?
Related
I'm trying to write a C program to simulate the piping of two or more processes together, like ls | sort | wc so running my code as ./driver ls sort wc should show the same result. I think I'm really close, but I can't seem to find the bug in my code below. Any help would be appreciated, I'm really stumped here.
I think I understand what is supposed to happen, but Im crossing my wires somehow in making it happen. The parent should fork child processes who in turn reroute their STDOUT to the write end of a pipe(a). Any child who is created beyond the first child should consider the read end of this pipe(a) as its STDIN, as well as redirect it's own output to a pipe(b) of it's own.
Say a third process is piped. It should consider the read end of the pipe(b) as STDIN, and again pipe its output to the write end of a new pipe(c) before executing the requested command.
The last case is the when the final process is passed to the pipe. In this example, a fourth process would consider the read end of the pipe(c) but should not need to redirect the STDOUT, just send it to STDOUT as normal.
#include <errno.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#define FORK_CHILD 0
static void error_and_exit(void) {
fprintf(stderr, "Error: %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
static int fork_or_die(void) {
int pid = fork();
if (pid == -1) {
error_and_exit();
}
return pid;
}
int main(const int argc, const char * argv[])
{
int processes = argc - 1;
int apipe[argc - 1][2];
int pid;
int result = -1;
for (int i = 0; i < processes; i++) {
result = pipe(apipe[i]);
if (result == -1) {
error_and_exit();
}
}
for (int i = 0; i < processes; i++) {
pid = fork_or_die();
// Child process executes process
if (pid == FORK_CHILD) {
// If we are not the first program in the pipe
if (i > 1) {
// Use the output from the previous program in the pipe as our input
// Check the read end of the pipe and STDIN are different descriptors
if (apipe[i - 1][0] != STDIN_FILENO) {
// Send the read end of the pipe to STDIN
if (dup2(apipe[i - 1][0], STDIN_FILENO) == -1) {
error_and_exit();
}
}
}
// Before we execute a process, bind the write end of the pipe to STDOUT
// Don't do this to the last process in the pipe, just send output to STDOUT as normal
if (i < processes - 1) {
// Check the write end of the pipe and STDOUT are different descriptors
if (apipe[i][1] != STDOUT_FILENO) {
// Send the write end of the pipe to STDOUT
if (dup2(apipe[i][1], STDOUT_FILENO) == -1) {
error_and_exit();
}
}
}
// Child executes requested process
if (execlp(argv[i + 1], argv[i + 1], (char *)NULL) == -1) {
error_and_exit();
}
wait(NULL);
}
// Parent does nothing until loop exits (waits for children)
}
return 0;
}
I've spotted three issues with your code:
As You have decided to index children starting from 0, not one but two processes will skip this part of code. The simplest fix that comes to my mind right now is to change the 1 to 0 or > to >=.
// If we are not the first program in the pipe
if (i > 1) {
You are calling wait in code that is not executed by parent. Moving the call outside the if (pid == FORK_CHILD) branch won't help as parent will wait for one child to finish before another one is started. Child process's write operation may wait for next child to consume some data and make place in buffer. Simplest solution that comes to my mind right now is to move wait calls to another loop.
You keep all pipe's descriptor open in parent and child processes. You should close it before wait loop in parent and before execlp in forked processes. The programs like grep, sort won't finish unless they receive EOF in their incoming streams. The pipe won't send EOF as long as at least one write descriptor is still open.
The code with minimal changes applied:
#include <errno.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#define FORK_CHILD 0
static void error_and_exit(void) {
fprintf(stderr, "Error: %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
static int fork_or_die(void) {
int pid = fork();
if (pid == -1) {
error_and_exit();
}
return pid;
}
int main(const int argc, const char * argv[])
{
int processes = argc - 1;
int apipe[argc - 1][2];
int pid;
int result = -1;
for (int i = 0; i < processes; i++) {
result = pipe(apipe[i]);
if (result == -1) {
error_and_exit();
}
}
for (int i = 0; i < processes; i++) {
pid = fork_or_die();
// Child process executes process
if (pid == FORK_CHILD) {
// If we are not the first program in the pipe
if (i > 0) {
// Use the output from the previous program in the pipe as our input
// Check the read end of the pipe and STDIN are different descriptors
if (apipe[i - 1][0] != STDIN_FILENO) {
// Send the read end of the pipe to STDIN
if (dup2(apipe[i - 1][0], STDIN_FILENO) == -1) {
error_and_exit();
}
}
}
// Before we execute a process, bind the write end of the pipe to STDOUT
// Don't do this to the last process in the pipe, just send output to STDOUT as normal
if (i < processes - 1) {
// Check the write end of the pipe and STDOUT are different descriptors
if (apipe[i][1] != STDOUT_FILENO) {
// Send the write end of the pipe to STDOUT
if (dup2(apipe[i][1], STDOUT_FILENO) == -1) {
error_and_exit();
}
}
}
for (int j = 0; j < processes; j++) {
if(close(apipe[j][0]) == -1)
error_and_exit();
if(close(apipe[j][1]) == -1)
error_and_exit();
}
// Child executes requested process
if (execlp(argv[i + 1], argv[i + 1], (char *)NULL) == -1) {
error_and_exit();
}
}
}
// Parent does nothing until loop exits (waits for children)
for (int i = 0; i < processes; i++) {
if(close(apipe[i][0]) == -1)
error_and_exit();
if(close(apipe[i][1]) == -1)
error_and_exit();
}
for (int i = 0; i < processes; i++) {
wait(NULL);
}
return 0;
}
Here I have a program that works fine. This code runs a bash into the parent process and the child process saves stdout from parent process in file called 'toto'. So in the 'toto' file, there are all the outputs of the commands that have been run in the bash from parent process.
#include <stdio.h>
#include <unistd.h>
int main(int argc, char **argv){
int fd1[2];
int pid;
if(pipe(fd1) == -1){
perror("pipe failed\n");
return 1;
}
if((pid = fork()) == -1){
perror("fork failed\n");
return 2;
}
else if(pid == 0){
close(fd1[1]);
dup2(fd1[0], 0);
close(fd1[0]);
argv[0] = "-a";
argv[1] = "toto";
argv[2] = NULL;
execve("/usr/bin/tee", argv,NULL);
return 3;
}
else{
close(fd1[0]);
dup2(fd1[1],1);
close(fd1[1]);
execve("/bin/bash",NULL,NULL);
return 4;
}
return 0;
}
I would also like to save in the 'toto' file the name of the command run into the bash.
Do you think it is possible and if so, how would you do this ?
Thanks ;)
I've an input file whose content is abcefghz. I want to use a pipe so that a child process p1 sends 1-letter per time to his parent process which will converts this string using the ASCII code+1 (abcefghz will become cdfghi{). This new string will be send through another pipe to another child process which will print the result on an output file.
This is the code:
int main (int argc, char **argv)
{
pid_t pid1, pid2;
int inputFile, outputFile;
char stringaDalFile[256];
char successivo;
char stringaRisultato[256];
int fd1[2], fd2[2]; // Pipe
inputFile = open(argv[1], O_RDONLY);
outputFile = open(argv[2], O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR);
pipe(fd1);
pipe(fd2);
pid1 = fork();
if (pid1 == 0) {
while ( (nread=read(inputFile, stringaDalFile, 1)) > 0) {
close(fd1[0]);
write(fd1[1], stringaDalFile, 1);
}
close(inputFile);
}
else {
close(fd1[1]);
while ( read(fd1[0], stringaDalFile, 1) > 0 ) {
successivo = converti(stringaDalFile[0]);
write(fd2[1], &successivo, 1);
}
}
pid2 = fork();
if (pid2 == 0) {
close(fd2[1]);
if (read(fd2[0], stringaRisultato, 1) == -1) {
perror("Errore");
exit(1);
}
else {
while ( read(fd2[0], stringaRisultato, 1) > 0 ) {
write(STDOUT_FILENO, stringaRisultato, strlen(stringaRisultato)); //dbg
write(outputFile, stringaRisultato, strlen(stringaRisultato));
}
}
close(outputFile);
exit(0);
}
return 0;
}
char converti (char carattere)
{
return carattere+1;
}
Unfortunately, this seems to not work at 100%, the string is converted but the program enters what seems an infinite loop:
If I CTRL-C and gedit file2.txt, this is its content:
.
How do I fix this?
There are a few problems in your code:
The real problem, here:
write(outputFile, stringaRisultato, strlen(stringaRisultato));
you're using strlen(stringaRisultato) when stringaRisultato only has one valid character at the beginning and no NUL-terminator after it. Just use 1 instead.
You are only reading one character at a time, you don't need a string of 256 characters. Change stringaDalFile and stringaRisultato into two single char variables: carattereDalFile and carattereRisultante.
In the first child (inside if (pid1 == 0)) you are doing close(fd1[0]) in a loop. You should move it out of the while.
Again in the first child, you're not doing exit(0). This is what causes your program to keep running.
This line inside the second child:
if (read(fd2[0], stringaRisultato, 1) == -1) {
is only useful if you want to skip the first character. Is that intended? If not, remove that if and only use the while (read(...) > 0).
You are writing everything to the pipe fd2[1] before starting the second child (that will read from it). If the input file is too large (some KB) this will result in filling the pipe internal buffer and will block your program on the next write() that it tries to perform, making it never end. To fix this, you should start the two child processes together. This would require changing the structure and logic of the code, but it's doable.
Since I don't think that's the real problem here and this program is most probably written for educational purposes I'll leave that to you and fix the rest of the above points.
Working code:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
char converti (char carattere);
int main (int argc, char **argv)
{
pid_t pid1, pid2;
int inputFile, outputFile;
char carattereDalFile, carattereRisultante, successivo; // renamed
int fd1[2], fd2[2];
inputFile = open(argv[1], O_RDONLY);
outputFile = open(argv[2], O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR);
pipe(fd1);
pipe(fd2);
pid1 = fork();
if (pid1 == 0) {
close(fd1[0]); // <== moved out of the while loop
while (read(inputFile, &carattereDalFile, 1) > 0) {
write(fd1[1], &carattereDalFile, 1);
}
close(inputFile);
exit(0); // <== added
} else {
close(fd1[1]);
while (read(fd1[0], &carattereDalFile, 1) > 0) {
successivo = converti(carattereDalFile);
write(fd2[1], &successivo, 1);
}
}
pid2 = fork();
if (pid2 == 0) {
close(fd2[1]);
if (read(fd2[0], &carattereRisultante, 1) == -1) {
perror("Errore");
exit(1);
} else {
while (read(fd2[0], &carattereRisultante, 1) > 0) {
write(outputFile, &carattereRisultante, 1);
}
}
close(outputFile);
exit(0);
}
return 0;
}
char converti (char carattere)
{
return carattere+1;
}
read returns the number of byte read in so you should use that to add the NUL terminating character to the string it's populated like this
while ( (bytesread = read(inputFile, stringaDalFile, 1)) > 0) {
stringaDalFile[bytesread] = '\0';
....
I'm trying to create the process tree shown in the picture. Basically if the level is even I want to create one child process and terminate the parent process. If the level is odd I wanna create two child processes and then terminate the parent process. I have written a program right now but I think it's so hard to visualize what process tree my program is actually creating. I've written some comments to the code to explain how I've been thinking. I also want to output the PID of the bottom children of the tree which my code doesn't do correctly.
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc, char *argv[]){
pid_t pid, ppid;
int n, i;
int childstate;
int count = 0;
if(argc != 2){
printf("Wrong number of arguments");
exit(-1);
}
n = atoi(argv[1]);
fork(); //start process 0
for(i = 1; i < n + 1; i++){
if(i % 2 != 0){
fork(); //if odd level start 1 child process
if(getpid() == 0){
kill (getppid(), 9); //terminate parent process
}
} else {
if(fork() > 0){ //start new process
fork(); //if new process is not a child start another process
if(getpid() == 0){
kill (getppid(), 9); //terminate parent process
}
}
}
if(i == n){ //print pid of leaves (not working correctly)
printf("Process: %d \n", getpid());
}
}
return 0;
}
I also want to output the PID of the bottom children of the tree which my code doesn't do correctly.
Have your processes output the tree in Dot language, and use Graphviz to output the tree.
For example, if you save the following as say tree.c:
#define _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
int process(const unsigned int level, const unsigned int maxlevel, FILE *dot)
{
int status = EXIT_SUCCESS, childstatus;
unsigned int children, i;
pid_t p, child[2];
if (dot) {
/* Output a node for this child, */
fprintf(dot, " \"%ld\" [ label=\"Process %ld\" ];\n", (long)getpid(), (long)getpid());
/* and if not at the top level (0), an edge from our parent. */
if (level)
fprintf(dot, " \"%ld\" -> \"%ld\";\n", (long)getppid(), (long)getpid());
fflush(dot);
}
/* No more forking? */
if (level >= maxlevel) {
if (level)
exit(status);
else
return status;
}
/* Odd levels create two child processes, even one. */
if (level & 1)
children = 2;
else
children = 1;
/* Fork the child processes, */
for (i = 0; i < children; i++) {
child[i] = fork();
if (child[i] == -1) {
fprintf(stderr, "Cannot fork: %s.\n", strerror(errno));
exit(EXIT_FAILURE);
} else
if (!child[i]) {
/* have each child run process() and nothing else, */
exit(process(level + 1, maxlevel, dot));
}
/* This line is run in parent only. */
}
/* and wait for them. */
for (i = 0; i < children; i++) {
if (child[i] != -1) {
do {
p = waitpid(child[i], &childstatus, 0);
} while (p == -1 && errno == EINTR);
if (p != child[i])
status = EXIT_FAILURE;
} else
status = EXIT_FAILURE;
}
if (level)
exit(status);
else
return status;
}
int dot_process_tree(const int levels, FILE *out)
{
int retval = EXIT_SUCCESS;
if (out) {
fprintf(out, "digraph {\n");
fflush(out);
}
if (levels > 0)
retval = process(0, levels - 1, out);
if (out) {
fprintf(out, "}\n");
fflush(out);
}
return retval;
}
int main(void)
{
return dot_process_tree(5, stdout);
}
and compile and run it using
reset ; gcc -Wall -Wextra -O2 tree.c -o tree && ./tree | dot -Tx11
you'll get a nice graphic process tree. (Use dot -Tsvg > out.svg or dot -Tpng > out.png to save it as an SVG or PNG image.) On my system:
Do note that there is no reason why the process IDs should be in the tree order. Although e.g. Linux hands them off in a rather ordered fashion, they can be in any order, even totally random. So do not make any assumptions on the PIDs.
The Dot language itself is simple. The output of the above program is something like
digraph {
"12375" [ label="Process 12375" ];
"12377" [ label="Process 12377" ];
"12375" -> "12377";
"12378" [ label="Process 12378" ];
"12377" -> "12378";
"12379" [ label="Process 12379" ];
"12377" -> "12379";
"12380" [ label="Process 12380" ];
"12378" -> "12380";
"12381" [ label="Process 12381" ];
"12379" -> "12381";
"12382" [ label="Process 12382" ];
"12380" -> "12382";
"12384" [ label="Process 12384" ];
"12381" -> "12384";
"12383" [ label="Process 12383" ];
"12380" -> "12383";
"12385" [ label="Process 12385" ];
"12381" -> "12385";
}
which should be obvious; nodes are named by the process ID, and [ label="Title" ] sets the text in the node. It is not from the same run as the diagram above, so the process IDs differ.
In Dot, numbers do need to be quoted if used as a name, but if a name starts with a letter, you don't need to quote it. See Graphviz documentation for further details. (The Node, Edge and Graph Attributes page is the one you usually need.)
If you want the level display in each node, use
fprintf(dot, " \"%ld\" [ label=\"Process %ld, level %u\" ];\n", (long)getpid(), (long)getpid(), level + 1);
in process(). (It uses level 0 forwards, with all nonzero levels being child processes, and level 0 being the original process. That's why level 0 returns, and all other levels exit().)
From you description, your basic logic should be:
void fork_loop(int level, int stop) {
if (level > stop) return;
if (is_even(level)) {
fork_child(level, stop);
exit(0);
} else {
fork_child(level, stop);
fork_child(level, stop);
exit(0);
}
}
Where fork_child() calls fork(). The child process would call fork_loop(level+1, stop), while the parent would return.
fork(); //if odd level start 1 child process
if (getpid() == 0){
kill (getppid(), 9); //terminate parent process
}
This logic is wrong: getpid() does not return 0 / fork doesn't return a pid in the child process - it just returns 0 to signify that it is the child process - it can know parent's pid by calling getpid before.
The logic should be:
pid_t child = fork();
if (child > 0) {
// use exit instead of kill! exit terminates this process
exit(0);
}
if (child < 0) {
... an error occurred in fork ...
}
The getpid can never be zero. As I mentioned in my top comments, you want the parent to wait on children, not the other way round and too many forks.
Here's a cleaned up version that I think works:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
int
main(int argc, char *argv[])
{
pid_t pid;
pid_t ppid;
int i;
int n;
int pcur;
int pcnt;
if (argc != 2) {
printf("Wrong number of arguments");
exit(-1);
}
n = atoi(argv[1]);
pid = fork(); // start process 0
if (pid != 0) {
wait(NULL);
n = -5;
}
for (i = 1; i < n + 1; i++) {
// odd/even level -- get number of children to start
// NOTE: you may need to reverse this if
if (i % 2 != 0)
pcnt = 1;
else
pcnt = 2;
// get parent pid
ppid = getpid();
// do the forks
for (pcur = 0; pcur < pcnt; ++pcur)
fork();
// get current pid
pid = getpid();
// parent should wait on children
if (pid == ppid) {
while (wait(NULL) >= 0);
break;
}
// print pid of leaves (not working correctly)
if (i == n) {
printf("Process: %d\n", pid);
}
}
return 0;
}
I've compared this to previous posts involving piping and I can't seem to find the problem. Everything in the parent seems to be closed as it should. It works fine when I type in a valid command (ex "ls | grep a) but if it is not a valid command (ex "ls | grup a) the program stops responding to user input (it keeps running but it just doesn't do anything when you enter a command)
Main function:
int main() {
int i;
char **args;
int pipeCheck = 0;
int argCount = -1;
int blank = 0;
while(1) {
args = getln();
if (args[0] != NULL){
blank = 1;
if (strcmp(args[0],"exit")==0) exit(0);
}
for(i = 0; args[i] != NULL; i++) {
if (strcmp(args[i], "|")==0){
pipeCheck = i;
}
}
if (pipeCheck != 0){
args[pipeCheck] = NULL;
directPipe(args, pipeCheck, argCount, ampCheck);
}
}
}
This is the function for piping in my program:
int directPipe(char ** args, int fileNumber, int argCount,int ampCheck){
int fd[2];
int child1,child2;
int status;
int i;
char * piped[10000];
int count = 0;
for (i = (fileNumber+1); args[i] != NULL; i++){
piped[count] = args[i];
count++;
}
piped[count] = NULL;
printf("\nPipe attempted...\n");
pipe(fd);
child1 = fork();
if (child1==0){
close(1);
dup(fd[1]);
close(fd[0]);
close(fd[1]);
execvp(args[0], args);
printf("Unknown command, please try again.");
exit(0);
}
child2 = fork();
if (child2 ==0){
close(0);
close(fd[1]);
dup(fd[0]);
close(fd[0]);
execvp(piped[0], piped);
printf("Unknown command, please try again.");
exit(0);
}
close(fd[1]);
close(fd[0]);
if (ampCheck == 0){
while (wait(&status) != child1);
while (wait(&status) != child2);
}
else{
printf("\nampCheck = %d",ampCheck);
sigset(child2, printer());
}
return (0);
}
Your problem is the pair of wait() loops:
while (wait(&status) != child1);
while (wait(&status) != child2);
In your scenario, the second child dies before the first does, so your collect its corpse in the first loop, but ignore it. Then the second loop goes into a busy wait because there are no children left any more.
At minimum, you need to do:
int corpse;
while ((corpse = wait(&status)) != -1 && corpse != child1 && corpse != child2)
;
while ((corpse = wait(&status)) != -1 && corpse != child1 && corpse != child2)
;
This handles children dying in either order — but only the two children. For a more general pipeline (three or more processes), you have to work harder — and use a single loop. The more general form will be something like:
int corpse;
while ((corpse = wait(&status)) != -1)
{
if (record_death_of_child(corpse, status) == -1)
break;
}
where your process creation code records the PIDs of the created processes, and the record_death_of_child() code deals with that list of PIDS and returns -1 when there are no more children to wait for in the current pipeline (and 0 otherwise). Or you can have it use some other heuristic to determine when to exit the loop. Note that if you have long running jobs in the background, any of them could die and that corpse would be collected in the loop. The 'record death' function would need to deal with such processes too — they can no longer be brought into the foreground, for example, and you need to report that they exited, etc.
You might end up using waitpid(), too, since you can arrange for that to not hang while there's a background process that's still running using WNOHANG.