13

I am trying to implement pipe in C. eg - $ ls | wc | wc

I have written the following code -

#include<stdio.h>
#include<stdlib.h>
#include <unistd.h>

void run_cmd(char *cmd, int* fd_in, int* fd_out)
{
    int c = fork();

    if (c==0)
    {
        if (fd_in != NULL)
        {
            close(fd_in[1]);
            dup2(fd_in[0], 0);
        }
        if (fd_out != NULL)
        {
            close(fd_out[0]);
            dup2(fd_out[1],1);
        }
        execlp(cmd, cmd, NULL);
    }
}

int main(int argc, char **argv)
{
    int fd_1[2], fd_2[2], i;
    pipe(fd_1);
    pipe(fd_2);

    run_cmd(argv[1], NULL, fd_1);

    for( i=2; i<argc-1; i++)
    {
        if (i%2 == 0)
            run_cmd(argv[i], fd_1, fd_2);
        else
            run_cmd(argv[i], fd_2, fd_1);
    }
    if (i%2 == 0)
        run_cmd(argv[i], fd_1, NULL);
    else
        run_cmd(argv[i], fd_2, NULL);
}

This works fine with two arguments, eg - $./a.out ls wc

But when I try with more than two arguments it does not work.

Would anyone please tell me what's wrong with my code, or any other way to do this?

halfer
  • 19,824
  • 17
  • 99
  • 186
ac-lap
  • 1,623
  • 2
  • 15
  • 27
  • 3
    creating two pipes is not enough. You need a new pipe between every two processes. – Ingo Leonhardt Feb 20 '14 at 17:07
  • related: [Connecting n commands with pipes in a shell?](http://stackoverflow.com/q/8082932/4279). Here's a code example: [`pipeline-three-processes.c`](https://gist.github.com/zed/7540510) – jfs Feb 20 '14 at 17:11
  • 1
    @IngoLeonhardt but at any point only two pipes are being used, one for reading and one for writing. – ac-lap Feb 20 '14 at 17:12
  • I must admit that at the moment I don't see the problem if you have exactly three processes. Having four or more processes, reusing `fd_1` as pipe between the processes `argv[3]` and `argv[4]` definitey would cause problems – Ingo Leonhardt Feb 20 '14 at 17:22

2 Answers2

27

This does virtually no error checking, but why so complicated?

int main (int argc, char ** argv) {
    int i;

    for( i=1; i<argc-1; i++)
    {
        int pd[2];
        pipe(pd);

        if (!fork()) {
            dup2(pd[1], 1); // remap output back to parent
            execlp(argv[i], argv[i], NULL);
            perror("exec");
            abort();
        }

        // remap output from previous child to input
        dup2(pd[0], 0);
        close(pd[1]);
    }

    execlp(argv[i], argv[i], NULL);
    perror("exec");
    abort();
}
Sergey L.
  • 21,822
  • 5
  • 49
  • 75
  • 1
    why don't you call `close(pd[0])` after `dup2(pd[0], 0)`? – jfs Feb 20 '14 at 21:58
  • 1
    @J.F.Sebastian Although it's good practice it's not really necessary to close read ends of pipes. No one will know about that file descriptor and will be listening on it. I have to close the write end though because whoever is reading on the read end will be waiting for `EOF`. I also wrote that this is a minimal function that works, but does virtually no error checking. – Sergey L. Feb 21 '14 at 11:47
  • 1
    Does it prevent from generating SIGPIPE/EPIPE If a child that reads the pipe dies? – jfs Feb 24 '14 at 23:31
2

If your are still interested in why your source didn't work (Sergey's solution is better anyway):

The problem is not closing the write side of fd_1 in the parent process. Thus both argv[1] and parent have been writers to that pipe and that caused the confusion. Please don't ask for more details (esp. why the prob doesn't occur if you use only one pipe) but your original source will run with tree processes if you just add a close( fd_1[1] ); after the first call of run_cmd()

Ingo Leonhardt
  • 9,435
  • 2
  • 24
  • 33