Executing the ls | wc linux command in c program using 2 processes communicating trough a pipe

I'm currently having problems with the following exercise:

I want to "mimic" the pipe command line ls | wc in linux bash with the following program. What I do is:

  • create a pipe
  • create a reader child and writer child
  • the writer child closes the pipe's reading side and redirects his stdout to the writing side of the pipe
  • the reader child closes the pipe's writing side and makes the reading side of the pipe his stdin
  • both children do an exec, the writer executes the ls program, passes the output through the pipe to the reader that executes the wc program on that output.

When I do ls | wc in linux terminal I get the following result:

8      8     101

But if I execute my program I get the following result:

0      0      0

Here is my program:

#include <stdlib.h> 
#include <errno.h> 
#include <stdio.h>
#include <unistd.h> 
#include <sys/types.h> 
#include <sys/stat.h> 
#include <fcntl.h> 
#include <string.h> 
#include <sys/wait.h>
#include <libgen.h>
#include <signal.h>
#include <errno.h>

int main(void){
        int mypipe[2];
        pid_t pid1, pid2;

        if (pipe(mypipe)<0)
                perror ("pipe error"), exit(1);
        if ((pid1=fork())<0)
                perror ("fork error"), exit(1);

        else if (pid1==0) {
                //reader child
                close (mypipe[1]);
                if (dup2(mypipe[0], STDIN_FILENO)!=STDIN_FILENO)
                        perror ("dup2 error"), exit(1);
                close (mypipe[0]); 
                if (execlp("wc", "wc", NULL)<0)
                        perror("execlp1 error"), exit(1);
                else {  //pid >0, parent
                        if ((pid2=fork())<0)
                                perror ("fork error"), exit(2);
                        else if (pid2==0) {     
                                //writer child
                                close(mypipe[0]);
                                if (dup2(mypipe[1], STDOUT_FILENO) != STDOUT_FILENO)
                                        perror("dup2 error"), exit(1);
                                close (mypipe[1]);
                                if (execlp("ls", "ls", NULL)<0)
                                        perror ("execlp error"), exit(1);
                        }
                        else {  //parent
                                close(mypipe[0]);
                                close(mypipe[1]);

                                waitpid(pid1, NULL, 0);
                                waitpid(pid2, NULL, 0);
                                exit(0);
                        }
                }
        }
return 0;
}

What am I doing wrong? Thanks in advance for the answers!


Solution 1:

Your code is confused. You have many irrelevant headers, and repeat #include <errno.h>. In the main() function, you have:

int main(void)
{
    int mypipe[2];
    pid_t pid1, pid2;

    if (pipe(mypipe) < 0)
        perror("pipe error"), exit(1);
    if ((pid1 = fork()) < 0)
        perror("fork error"), exit(1);
    else if (pid1 == 0)
    {   
        // reader child
        close(mypipe[1]);
        if (dup2(mypipe[0], STDIN_FILENO) != STDIN_FILENO)
            perror("dup2 error"), exit(1);
        close(mypipe[0]);
        if (execlp("wc", "wc", NULL) < 0)
            perror("execlp1 error"), exit(1);
        else            // pid >0, parent
        {   
            …
        }
    }
    return 0;
}

The else if (pid1 == 0) clause is executed by the child. It closes the write end of the pipe, duplicates the read end to standard input and closes the read end of the pipe. It then does execlp() on wc. Only if the code fails to execute wc will the else clause be executed, and then there is only the read end of the pipe left open. Meanwhile, the original process simply exits. That closes the pipe, so the wc command gets no input, and reports 0 0 0 as a result.

You need to rewrite the code. The parent process should wait until both its children execute. Especially while debugging, you should not ignore the exit status of children, and you should report it.

Here's some code that works. Note that it avoids bushy decision trees — it is a linear sequence of if / else if / … / else code. This is easier to understand, in general, than a bushy, multi-level set of conditions.

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>

int main(void)
{
    int fd[2];
    pid_t pid1, pid2;

    if (pipe(fd) < 0)
        perror("pipe error"), exit(1);
    else if ((pid1 = fork()) < 0)
        perror("fork error"), exit(1);
    else if (pid1 == 0)
    {
        /* ls */
        dup2(fd[1], STDOUT_FILENO);
        close(fd[0]);
        close(fd[1]);
        execlp("ls", "ls", (char *)0);
        perror("exec ls");
        exit(1);
    }
    else if ((pid2 = fork()) < 0)
        perror("fork error"), exit(1);
    else if (pid2 == 0)
    {
        /* wc */
        dup2(fd[0], STDIN_FILENO);
        close(fd[0]);
        close(fd[1]);
        execlp("wc", "wc", (char *)0);
        perror("exec wc");
        exit(1);
    }
    else
    {
        close(fd[0]);
        close(fd[1]);
        int status1;
        int status2;
        int corpse1 = waitpid(pid1, &status1, 0);
        int corpse2 = waitpid(pid2, &status2, 0);
        printf("ls: pid = %d, corpse = %d, exit status = 0x%.4X\n", pid1, corpse1, status1);
        printf("ls: pid = %d, corpse = %d, exit status = 0x%.4X\n", pid2, corpse2, status2);
    }
    return 0;
}

A sample run of the program pipe41 on my machine produced:

$ pipe41
     175     175    1954
ls: pid = 44770, corpse = 44770, exit status = 0x0000
ls: pid = 44771, corpse = 44771, exit status = 0x0000
$ ls | wc
     175     175    1954
$