Learning Pipes and Processes - c

I'm trying to get a better understanding of pipes and processes. I want to implement multiple chained pipes like cat test.txt | sort | uniq -c. I started my code with the cat test.txt, but it isn't working. It compiles, but when I provide a file name in the command line, for example, ./hwk ./test.txt. Nothing returns. Can someone take a look and give me some hints? I want to use loops because I want to be able to add more pipes. I know there's a lot of issues in my code, so I hope someone can give me some guidance on this topic. Thanks.
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>
#define SIZE 1024
int main (int argc, char **argv)
{
int num_pipe = 1;
int commands = num_pipe + 1; //number of commands is one more than the number of pipes
int fds[num_pipe * 2];
int status;
pid_t pid;
char *str_ptr;
//Pass Command
char *arrayOfCommands[] = {"cat", NULL};
//Setting up pipes
int i;
for (i = 0; i < num_pipe; i++){
if(pipe(fds + i * 2) == -1) {
perror("Error creating pipes");
exit(1);
}
}
int j = 0;
for (i = 0; i < commands - 1; ++i) {
pid = fork();
if (pid == 0) {
if (i < commands) {
if (dup2(fds[j+1], 1) < 0) {
perror("dup2 error");
exit(EXIT_FAILURE);
}
}
if (j != 0) {
if(dup2(fds[j-2], 0) < 0) {
perror("dup2 error");
exit(EXIT_FAILURE);
}
}
for (i = 0; i < 2*num_pipe; i++) {
close(fds[i]);
}
if (execvp(arrayOfCommands[0], arrayOfCommands) < 0) {
perror("Array error");
exit(EXIT_FAILURE);
}
}
else if (pid < 0){
perror("Error");
exit(EXIT_FAILURE);
}
j += 2;
}
for (i = 0; i < 2 * num_pipe; i++){
close(fds[i]);
}
for (i = 0; i < num_pipe + 1; i++) {
wait(&status);
}
return 0;
}

I called this mainly minor adaptation of your program p3.c, compiling it to produce p3. Since there's only one command (cat) being invoked, I juggled things so that it will work correctly. When run as ./p3 p3.c, it prints out the content of the source code.
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
static void err_exit(const char *str);
int main (int argc, char **argv)
{
int num_pipe = 0; // Just cat - no pipes
int commands = num_pipe + 1; // Number of commands is one more than the number of pipes
int fds[num_pipe * 2 + 1]; // Avoid size 0 array
char *arrayOfCommands[3] = { "cat", NULL, NULL};
if (argc != 2)
err_exit("Missing filename argument");
arrayOfCommands[1] = argv[1];
for (int i = 0; i < num_pipe; i++)
{
if (pipe(fds + i * 2) == -1)
err_exit("Error creating pipes");
}
int j = 0;
for (int i = 0; i < commands; ++i)
{
pid_t pid = fork();
if (pid == 0)
{
printf("%d: %s %s\n", (int)getpid(), arrayOfCommands[0], arrayOfCommands[1]);
fflush(stdout);
if (i < commands-1 && dup2(fds[j+1], 1) < 0)
err_exit("dup2 error");
if (j != 0 && dup2(fds[j-2], 0) < 0)
err_exit("dup2 error");
for (i = 0; i < 2*num_pipe; i++)
close(fds[i]);
execvp(arrayOfCommands[0], arrayOfCommands);
err_exit("Array error");
}
else if (pid < 0)
err_exit("Error");
j += 2;
}
for (int i = 0; i < 2 * num_pipe; i++)
close(fds[i]);
for (int i = 0; i < num_pipe + 1; i++)
{
int status;
pid_t pid = wait(&status);
printf("PID %d exited 0x%.4X\n", (int)pid, status);
}
return 0;
}
static void err_exit(const char *str)
{
perror(str);
exit(EXIT_FAILURE);
}
Check that works for you. Then you'll need to work out how you're going to create a second command. Your arrayOfCommands isn't going to help directly. You'll need another array of strings in some shape or form.
An extension to run cat file | rev. The changes are really quite minor. I created a_cat to handle the cat command, a_rev for the rev command, and a_cmds as the array of commands. It was also necessary to fix a loop on i to a loop on k.
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/wait.h>
static void err_exit(const char *str);
int main (int argc, char **argv)
{
int num_pipe = 1;
int commands = num_pipe + 1; //number of commands is one more than the number of pipes
int fds[num_pipe * 2 + 1]; // Avoid size 0 array
char *a_cat[3] = { "cat", NULL, NULL};
char *a_rev[2] = { "rev", NULL};
char **a_cmds[] = { a_cat, a_rev };
if (argc != 2)
err_exit("Missing filename argument");
a_cat[1] = argv[1];
for (int i = 0; i < num_pipe; i++)
{
if (pipe(fds + i * 2) == -1)
err_exit("Error creating pipes");
}
int j = 0;
for (int i = 0; i < commands; ++i)
{
pid_t pid = fork();
if (pid == 0)
{
printf("%d: %s\n", (int)getpid(), a_cmds[i][0]);
fflush(stdout);
if (i < commands-1 && dup2(fds[j+1], 1) < 0)
err_exit("dup2 error");
if (j != 0 && dup2(fds[j-2], 0) < 0)
err_exit("dup2 error");
for (int k = 0; k < 2*num_pipe; k++)
close(fds[k]);
execvp(a_cmds[i][0], a_cmds[i]);
err_exit("Array error");
}
else if (pid < 0)
err_exit("Error");
j += 2;
}
for (int i = 0; i < 2 * num_pipe; i++)
close(fds[i]);
for (int i = 0; i < num_pipe + 1; i++)
{
int status;
pid_t pid = wait(&status);
printf("PID %d exited 0x%.4X\n", (int)pid, status);
}
return 0;
}
static void err_exit(const char *str)
{
perror(str);
exit(EXIT_FAILURE);
}

You aren't passing your program's command-line arguments through to the "cat" child process. You initialize arrayOfCommands like so -> char *arrayOfCommands[] = {"cat", NULL}; <- then you pass it as-is to the execvp() function as the second argument.

Okay your first problem is that in the line:
execvp(arrayOfCommands[0], arrayOfCommands);
you are using arrayOfCommands but I am not sure how you're populating arrayOfCommands for the case where the text file is not being displayed. I mean are you setting arrayOfCommands like the following earlier in the code:
char *arrayOfCommands[] = {"cat", "./test.txt", NULL};
If I understand you correctly your program is called hwk and for whatever reason you think ./hwk ./test.txt should be parsed but that means you should be parsing argv.
Okay now that that's out of the way let's look at the bigger problem of how you are setting things up.
So when a shell parses out pipes it does there's quite a bit going on. Consider the following:
foo fooparam1 fooparam2 | bar barparam1 | baz bazparam1 bazparam2
The shell uses recursion to solve the problem:
foo fooparam1 fooparam2 | ( bar barparam1 | baz bazparam1 bazparam2 )
So it would look SOMETHING like:
spawn_sub_pipes(const char *str) {
char *cmd = strtok(str, "|");
char *rest = strtok(NULL, "|");
int fds[2];
pipe(fds[]);
int pid = fork();
if ( pid < 0 ) {
perror("pipe error");
exit(-1);
}
if ( pid ) { /* parent is the writer */
close(fds[0]); /* close reading pipe */
dup2(fds[1], 1); /* we attach stdout to the pipe */
}
if ( pid == 0 ) {
close(fds[1]);
dup2(fds[0], 0); /* attach the pipe to stdin */
if ( rest ) { /* fork next children */
spawn_sub_pipes(rest);
}
execvpe(cmd);
}
}
IMPORTANT NOTE
I have just written the above code out without testing it. Get the idea from it but don't use it verbatim.

Related

Multiple fork and printing something before exec

// I have commands in commands[] array
pid_t pid[command_count];
for (int i = 0; i < command_count; i++) {
if ((pid[i]=fork()) == 0) {
printf("--%s\n", commands[i][0]);
execvp(commands[i][0],commands[i]);
_exit(1);
}
if (pid[i] < 0) {
}
}
for (i = 0; i < command_count; i++) {
if (pid[i] > 0) {
int status;
waitpid(pid[i], &status, 0);
}
}
I have the above code and want to run commands at once (paralel) that is in commands array and before each run, want to print the command. For example;
ls | pwd | ls -a
It should print each command name before that command run like
--ls
.. a b.txt
--pwd
/a/a/
--ls -a
*some output*
But it prints like following
--ls
--pwd
--ls -a
.. a b.txt
*some directory as a result of pwd*
*some output*
What could be the reason and how can I fix it?
You will have to use pipe() to create new standard output and optionally standard error file descriptors for each command. Then you can read the pipes in order until each command completes.
Otherwise, because each command is forked into its own process it will run at its own convenience. Text output from commands running all at once and producing output to the same terminal can be mixed up even more than you show here.
Maybe something like
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
struct cmd_data {
const char *cmd;
int fd[2]; // stdout pipe for command
pid_t pid;
int status; // exit status
};
void cmd_launch(struct cmd_data *p, const char *cmd) {
int r;
p->cmd = cmd;
r = pipe(p->fd);
if(r<0) {
perror("pipe");
exit(EXIT_FAILURE);
}
r = fork();
if(r < 0) {
perror("fork");
close(p->fd[0]);
close(p->fd[1]);
} else if( r > 0 ) {
p->pid = r;
close(p->fd[1]);
} else {
close(p->fd[0]);
dup2(p->fd[1], STDOUT_FILENO);
close(p->fd[1]);
r = execlp(cmd, cmd, NULL);
perror("execlp");
exit(EXIT_FAILURE);
}
}
void cmd_join(struct cmd_data *p) {
char buf[4096];
const size_t buflen = sizeof buf;
ssize_t bytes;
printf("-- %s\n", p->cmd);
fflush(stdout);
while( 0 != (bytes = read(p->fd[0], buf, buflen)) ) {
write(STDOUT_FILENO, buf, bytes);
}
close(p->fd[0]);
pid_t r = waitpid(p->pid, &p->status, 0);
if(r<0){
perror("waitpid");
}
printf("-- completed with status %d\n", p->status);
fflush(stdout);
}
int main(int argc, char *argv[]) {
size_t cmd_c = argc - 1;
struct cmd_data *p = calloc(argc, sizeof *p);
size_t i;
for(i = 0; i < cmd_c; ++i) {
cmd_launch(p + i, argv[i + 1]);
}
for(i = 0; i < cmd_c; ++i) {
cmd_join(p + i);
}
free(p);
return EXIT_SUCCESS;
}

bad file descriptor in c program with forks

this program is supposed to simulate a posix shell in regards to commands with pipes. The example I've tried to simulate and wanna make work is "ls | nl", but it doesn't and I can't figure out why. I've debugged this code for many hours with no success.
I get the error: "nl: input error: Bad file descriptor", and when I've tried not closing any of the file descriptors or closing only some (or in only one of the forks, or only the parent, etc...), and the errors change, or it works but then nl keeps waiting for input. Anyways, I'm pretty sure the errors are in fork_cmd or fork_cmds and has to do with close.
I've included all the code. I know there's nothing wrong with parser.h. I know this is pretty shitty code but it should still work I think.
I'm probably blind, but I would really appreciate it if someone could help me figure it out. Hopefully it's something that I and maybe others can learn something from.
#include "parser.h"
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <stdbool.h>
#define READ 0
#define WRITE 1
void fork_error() {
perror("fork() failed)");
exit(EXIT_FAILURE);
}
void close_error() {
perror("Couldn't close file descriptor");
exit(EXIT_FAILURE);
}
void fork_cmd(char* argv[], int n, int read_pipe[2], int write_pipe[2], int (*all_fds)[2]) {
pid_t pid;
switch (pid = fork()) {
case -1:
fork_error();
case 0:
if (read_pipe != NULL) {
if (dup2(read_pipe[READ], STDIN_FILENO) < 0) {
perror("Failed to redirect STDIN to pipe");
exit(EXIT_FAILURE);
}
}
if (write_pipe != NULL) {
if (dup2(write_pipe[WRITE], STDOUT_FILENO) < 0) {
perror("Failed to redirect STDOUT to pipe");
exit(EXIT_FAILURE);
}
}
for (int i = 0; i < n - 1; i++) {
if (close(all_fds[i][READ]) == -1 || close(all_fds[i][WRITE] == -1)) {
close_error();
}
}
execvp(argv[0], argv);
perror("execvp");
exit(EXIT_FAILURE);
default:
printf("Pid of %s: %d\n", argv[0], pid);
break;
}
}
void fork_cmds(char* argvs[MAX_COMMANDS][MAX_ARGV], int n, int (*fds)[2]) {
for (int i = 0; i < n; i++) {
if (n == 1) {
fork_cmd(argvs[i], n, NULL, NULL, fds);
}
// n > 1
else if (i == 0) {
fork_cmd(argvs[i], n, NULL, fds[i], fds);
}
else if (i == n - 1) {
fork_cmd(argvs[i], n, fds[i - 1], NULL, fds);
}
else {
fork_cmd(argvs[i], n, fds[i - 1], fds[i], fds);
}
}
for (int i = 0; i < n - 1; i++) {
if (close(fds[i][READ]) == -1 || close(fds[i][WRITE] == -1)) {
close_error();
}
}
}
void get_line(char* buffer, size_t size) {
getline(&buffer, &size, stdin);
buffer[strlen(buffer)-1] = '\0';
}
void wait_for_all_cmds(int n) {
// Not implemented yet!
for (int i = 0; i < n; i++) {
int status;
int pid;
if ((pid = wait(&status)) == -1) {
printf("Wait error");
} else {
printf("PARENT <%ld>: Child with PID = %ld and exit status = %d terminated.\n",
(long) getpid(), (long) pid, WEXITSTATUS(status));
}
}
}
int main() {
int n;
char* argvs[MAX_COMMANDS][MAX_ARGV];
size_t size = 128;
char line[size];
printf(" >> ");
get_line(line, size);
n = parse(line, argvs);
// Debug printouts.
printf("%d commands parsed.\n", n);
print_argvs(argvs);
int (*fds)[2] = malloc(sizeof(int) * 2 * (n - 1)); // should be pointer to arrays of size 2
for (int i = 0; i < n - 1; i++) {
if (pipe(fds[i]) == -1) {
perror("Creating pipe error"); // Creating pipe error: ...
exit(EXIT_FAILURE);
}
printf("pipe %d: read: %d, write: %d\n", i, fds[i][READ], fds[i][WRITE]);
}
fork_cmds(argvs, n, fds);
wait_for_all_cmds(n);
exit(EXIT_SUCCESS);
}
The problem was that one of the parenthesis was at the wrong place in both fork_cmd and fork_cmds, it should be like this of course: close(fds[i][WRITE]). This was the original code:
for (int i = 0; i < n - 1; i++) {
if (close(fds[i][READ]) == -1 || close(fds[i][WRITE] == -1))<--
{
close_error();
}
}

Changing unrelated code gives a segmentation fault. Why is it doing this?

I'm creating my own Shell and I successfully got processes to run in the background by using my is_background function to find a &. It was working fine until i tried to implement redirection of standard output. The chk_if_output function is a part of this as well as the if statement if(out[0] == 1) in the process function. Somehow implementing redirection screwed up the way I implemented background process. If I comment out the redirection code it works again. I get a segmentation fault every time I try to run a background process with the redirection code in the program and I can't for the life of me figure out why. I haven't changed any of the background process code.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>
#define MAX_LINE 80 /* The maximum length command */
int is_background(char *args[], int size){
int background = 0;
if (strcmp(args[size-1 ], "&") == 0){
background = 1;
args[size-1] = NULL;
}
return background;
}
int * chk_if_output(char *args[], int size){
int * out = malloc(2);
out[0] = 0; out[1] = 0;
for (int i = 0; i < size; i++){
if (strcmp(args[i],">") == 0){
out[0] = 1;
out[1] = i;
break;
}
}
return out;
}
void process(char *command, char *params[], int size){
pid_t pid;
int background = is_background(params, size);
int *out = chk_if_output(params, size);
int fd;
int fd2;
pid = fork();
if (pid < 0) {
fprintf(stderr, "Fork Failed\n");
}else if (pid == 0) {
if(out[0] == 1){
for (int i = out[1]; i < size; i++){
params[i] = params[i+1];
}
fd = open(params[out[1]-1],O_RDONLY,0);
dup2(fd,STDIN_FILENO);
close(fd);
fd2 = creat(params[out[1]],0644);
dup2(fd2,STDOUT_FILENO);
close(fd2);
out[0] = 0;
out[1] = 0;
}
execvp(command, params);
}else {
if(background == 1){
waitpid(pid, NULL, 0);
}
background = 0;
}
}
int main(void) {
char *args[MAX_LINE/2 + 1]; /* command line arguments */
int should_run = 1; /* flag to determine when to exit program */
while (should_run) {
char *line;
char *endline;
printf("Leyden_osh>");
fgets(line, MAX_LINE*sizeof line, stdin);
if((endline = strchr(line, '\n')) != NULL){
*endline = '\0';
}
if (strcmp((const char *)line,"exit") == 0){
should_run = 0;
}
int i = 0;
args[i] = strtok(line, " ");
do{
args[++i] = strtok(NULL, " ");
}while(args[i] != NULL);
process(args[0], args, i);
fflush(stdout);
return 0;
}
In the chk_if_output() function, the last element of the array in the loop was NULL.
Fixed it by looping to size -1.

How to make a process ring with fork and pipe?

Currently I'm learning C and I'd like to make a ring of n childs process with forks and pipes where n is a number enter in argument, each child could communicate with the next child in one direction like this.
I tried to do it where each child send to the next child its pid but I don't get what I want for instance if I create 3 childs :
PID:1,i in loop : 0, received : 0
PID:2, i in loop : 1, received : 0
PID:3, i in loop : 2, received : 0
But I should get :
PID:1,i in loop : 0, received : 3
PID:2, i in loop : 1, received : 1
PID:3, i in loop : 2, received : 2
Sometimes I receive a value from one random child to another here is my code, I'm not really comfortable with multiples pipes in a loop.
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, const char * argv[]) {
if(argc != 2) {
fprintf(stderr, "Usage : %s <integer> [> 2]\n", argv[0]);
exit(EXIT_FAILURE);
}
int number_process = atoi(argv[1]);
if(number_process < 2) {
fprintf(stderr, "Usage : %s <integer> [> 2]\n", argv[0]);
exit(EXIT_FAILURE);
}
printf("Création de %d processus pour une élection : \n", number_process);
int i = 0, j = 0, k = 0;
int * t = (int *) malloc((2 * number_process) * sizeof(int));
for(k = 0; k < number_process; k++) {
pipe(&t[2*i]);
}
for(i = 0; i < number_process; i++) {
if(fork() == 0) {
for(j = 0; j < number_process*2; j++) {
if(j != 2*i && j != ((2*i+3)%(number_process*2))) {
close(t[j]);
}
}
close(t[(2*i+1)%(number_process*2)]);
close(t[((2*i+2)%(number_process*2))]);
int pid = (int) getpid();
write(t[(2*i+3)%(number_process*2)], &pid, sizeof(int));
int in = 0;
read(t[i*2], &in, sizeof(int));
printf("%d : %d\n", in, getpid());
exit(EXIT_SUCCESS);
}
}
return (EXIT_SUCCESS);
}
Here are the problems I have found in the program:
No error checking. Without error checking, it is much harder to find the other errors. Note that read() will return a negative result if an error occurs. In this case, you will probably get EBADF from read().
Once you add the error checking, you would investigate the source of the EBADF error, and notice that the pipes are not initialized correctly. This is due to the line pipe(&t[2*i]); which should use k instead of i. Another way to find this error is by using the address sanitizer or Valgrind, both of which would have found the error immediately (without having to change your code at all). Scoping loop variables inside the loops would have also found this problem immediately, so use for (int i = 0; ...) instead of int i; for (i = ; ...).
The close() function is called twice after the end of a loop on files which are already closed. This error is innocuous, however.
The parent process should wait for its children to exit, and it should also close the pipes first.
The program relies on line-buffering in order to work correctly. A good solution is to fflush(stdout) before calling fork().
Here is an updated version with notes:
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
void usage(const char *prog) {
fprintf(stderr, "Usage : %s <integer> [> 2]\n", prog);
exit(1);
}
int main(int argc, const char * argv[]) {
if(argc != 2) {
usage(argv[0]);
}
int number_process = atoi(argv[1]);
if(number_process < 2) {
usage(argv[0]);
}
printf("Création de %d processus pour une élection : \n", number_process);
// Flush stdout before fork.
fflush(stdout);
// Do not cast the result of malloc
// Use sizeof(*pipes) instead of sizeof(int)
// Prefer descriptive variable names
int *pipes = malloc((2 * number_process) * sizeof(*pipes));
if (!pipes) {
perror("malloc");
exit(1);
}
// Scope loop variables in the loop
for (int i = 0; i < number_process; i++) {
int r = pipe(&pipes[2*i]);
if (r == -1) {
perror("pipe");
exit(1);
}
}
for (int i = 0; i < number_process; i++) {
pid_t pid = fork();
if (pid == -1) {
perror("fork");
exit(1);
}
if (pid == 0) {
// Let's avoid copy/pasting 2*i and (2*i+3)%(number_process*2)
// everywhere, which is hard to read
int infd = pipes[2*i];
int outfd = pipes[(2*i+3)%(number_process*2)];
for (int j = 0; j < number_process*2; j++) {
if (pipes[j] != infd && pipes[j] != outfd) {
close(pipes[j]);
}
}
int self = getpid();
ssize_t amt;
amt = write(outfd, &self, sizeof(int));
if (amt == -1) {
perror("write");
exit(1);
}
int in;
ssize_t r = read(pipes[i*2], &in, sizeof(int));
if (r == -1) {
perror("read");
exit(1);
}
printf("%d : %d\n", in, (int)getpid());
exit(0);
}
}
// Close pipes and wait for children to finish
for (int i = 0; i < number_process * 2; i++) {
close(pipes[i]);
}
for (int i = 0; i < number_process; i++) {
wait(NULL);
}
// Return at end of main() is implicitly "return 0".
}

C: Pipe and Fork closing. Nothing gets printed

cmds is a list of commands to call. In my case, I'm tring to call ls | grep c. When I run the program, nothing gets printed. It seems grep is waiting for something?
Note: If I only use ls (via execPipe(cmds,1)), everything works.
What is wrong?
int execPipe(char*** cmds,int len){
int i;
int pipefd[100][2];
for(i = 0; i < len; i++)
pipe(pipefd[i]);
i = 0;
for(i = 0; i < len; i++){
if (fork() == 0){
printf("executing #%d %s\n",i,cmds[i][0]);
//i=0: in=sdtin, out=1
//i=1: in=1,out=3
//i=2: in=3,out=5
//i=len in=len*2-1, out=sdtout
close(pipefd[i][0]);
if(i != 0){
dup2(pipefd[i-1][1],0); //read becomes the write of last one
}
if(i != len-1){
dup2(pipefd[i][1],1); //write becomes pipefd[i][1]
}
execvp(cmds[i][0],cmds[i]);
return EXIT_SUCCESS;
}
close(pipefd[i][0]);
close(pipefd[i][1]);
wait(NULL);
}
return 0;
}
int main(){
char*** cmds = malloc(2*sizeof(char**));
cmds[0] = malloc(2*sizeof(char**));
cmds[0][0] = "ls";
cmds[0][1] = NULL;
cmds[1] = malloc(3*sizeof(char**));
cmds[1][0] = "grep";
cmds[1][1] = "c";
cmds[1][2] = NULL;
execPipe(cmds,2);
return 0;
}
This code works:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
static
int execPipe(char ***cmds, int len)
{
int i;
int pids[len];
int pipefd[len][2];
for (i = 0; i < len - 1; i++)
pipe(pipefd[i]);
for (i = 0; i < len; i++)
{
int pid;
if ((pid = fork()) == 0)
{
printf("PID %d: executing #%d %s\n", (int)getpid(), i, cmds[i][0]);
if (i != 0)
{
dup2(pipefd[i - 1][0], 0); // JL: Fix
}
if (i != len - 1)
{
dup2(pipefd[i][1], 1); // write becomes pipefd[i][1]
}
for (int j = 0; j < len - 1; j++) // JL: Fix
{
close(pipefd[j][0]);
close(pipefd[j][1]);
}
execvp(cmds[i][0], cmds[i]);
fprintf(stderr, "Failed to execute command %s\n", cmds[i][0]);
return EXIT_FAILURE;
}
else if (pid < 0)
{
fprintf(stderr, "failed to fork for %s\n", cmds[i][0]);
exit(1);
}
else
pids[i] = pid;
}
for (i = 0; i < len - 1; i++) // JL: Fix
{
close(pipefd[i][0]);
close(pipefd[i][1]);
}
int corpse;
int status;
int kids = len;
while (kids > 0 && (corpse = wait(&status)) > 0)
{
printf("PID %d died with status 0x%.4X\n", corpse, status);
for (i = 0; i < kids; i++)
{
if (pids[i] == corpse)
{
pids[i] = pids[kids-1];
kids--;
break;
}
}
}
return 0;
}
int main(void)
{
char ***cmds = malloc(2 * sizeof(char **));
cmds[0] = malloc(2 * sizeof(char **));
cmds[0][0] = "ls";
cmds[0][1] = NULL;
cmds[1] = malloc(3 * sizeof(char **));
cmds[1][0] = "grep";
cmds[1][1] = "c";
cmds[1][2] = NULL;
execPipe(cmds, 2);
return 0;
}
Comments:
Notice how many close operations there are.
It would be reasonable to factor the close loop into a function that gets called where needed.
You could get away without the first child closing the pipes, but it is silly to break the symmetry.
It is crucial that the parent close the pipes — but only after the pipes are finished with (that is, after all the children are created).
The wait() loop deals with the situation where the parent process had children that it didn't know about that terminate before the children it launches — a rather unusual but far from impossible circumstance. It would be possible simply to wait until all children die, but maybe one of the previously created children isn't going to terminate. The loop waits until all the known children have died and then exits.
A more complex mechanism would only have two pipes open at any one time, even in a 50 process pipeline, rather than opening all 49 pipes at once, but that's a refinement for later.
You should extend this to a 3-process or longer pipeline and check that it works. Possible pipelines include:
who | awk '{print $1}' | sort
who | awk '{print $1}' | sort | uniq -c
who | awk '{print $1}' | sort | uniq -c | sort -n
Beware: the shell removes single quotes.

Resources