RainingChain
RainingChain

Reputation: 7746

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;
}

Upvotes: 0

Views: 378

Answers (1)

Jonathan Leffler
Jonathan Leffler

Reputation: 753725

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.

Upvotes: 2

Related Questions