0

I need to create a program that execute in the shell this command with two pipes and three process: ls | sort | grep r. The code I have done is this:

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

#define WRITE 1
#define READ 0

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

    int fd1[2],fd2[2];
    pid_t pid1,pid2;
    
    if( (pid1 = fork()) < 0)
    {
        perror("fork");
        exit(-1);
    }
    
    if( pipe(fd1) < 0)
    {
        perror("pipe 1");
        exit(-1);
    }
    
    if( pipe(fd2) < 0)
    {
        perror("pipe 2");
        exit(-1);
    }
    
    if( pid1 == 0 )
        pid2 = fork();
        
    if(pid1>0)
    {
        close(fd2[READ]);
        close(fd2[WRITE]);
        close(fd1[READ]);
        dup2(fd1[WRITE],STDOUT_FILENO);
        close(fd1[WRITE]);
        execlp("ls","ls",NULL);
        perror("ls");
        exit(-1);
    }
    
    if(pid2>0)
    {
        close(fd1[WRITE]);
        dup2(fd1[READ],STDIN_FILENO);
        close(fd1[READ]);
        close(fd2[READ]);
        dup2(fd2[WRITE],STDOUT_FILENO);
        close(fd2[WRITE]);
        execlp("sort","sort",NULL);
        perror("sort");
        exit(-1);
    }
    if(pid2==0)
    {
        close(fd1[READ]);
        close(fd1[WRITE]);
        close(fd2[WRITE]);
        dup2(fd2[READ],STDIN_FILENO);
        close(fd2[READ]);
        execlp("grep","grep","r",NULL);
        perror("grep");
        exit(-1);
    }

}

Probably I have wrong with the communication with this two pipe because I'm learning how they works only today. So sorry If I wrong some important thing about pipe. I hope if somebody that can help me with this and explain what I wrong. Thanks.

Brutus
  • 1
  • what do you mean "in the shell"? You're doing the piping work instead of using a shell. If you really *do* want to run a shell command, check out [system](https://man7.org/linux/man-pages/man3/system.3.html) which would make your program trivial – erik258 Feb 02 '23 at 13:51

1 Answers1

2

You created the pipes after the 1st fork (but before the 2nd).

Which means that you called pipe 4 times, 2 times in each process that exist after the 1st fork.

So fd1 and fd2 have one set of values for main process (the one for which pid1>0, aka the one that executes ls), and another for the child and grandchild processes (the 2nd fork is done after the creation of the pipes, so no problem here: the two processes that executes sort and grep do share the same file descriptors).

So for the sort | grep part, no problem. The output of sort is fd2[1], while the input of grep is fd2[0], as you want, fd2 being the result of the same pipe call, that was executed before the 2nd fork, and therefore shared between those 2 processes.

But for the ls | sort part, what you are doing is exactly as if you did something like

    if(fork()){
        int fd[2];
        pipe(fd);
        close(fd[0]);
        dup2(fd[1], 1);
        printf("Hello\n");
        exit(0);
    }else{
        int fd[2];
        pipe(fd);
        close(fd[1]);
        dup2(fd[0], 0);
        char s[100];
        scanf("%s", s);
        printf("Should be hello=%s\n", s);
        exit(0);
    }

Wouldn't work as expected. There is no relationship whatsoever between the fd of the two processes.

So you need to create the pipes before the fork.

At least, the pipes that you intend to share among the processes that will be created by this fork.

I think I know why you did this strange thing. Because of another caveat with 3 process pipe chain: we often see people creating all their pipe before forking twice, having problem making their code work. For another reason: they usually forget that their pipe exist in 3 process, and they need to close all those they don't use. Even in processes that have nothing to do with fd1, both end of fd1 must be closed.

Edit: Another strange thing you did, is closing even fd1[WRITE] even in the process where you want to use it (just after dup2). You can't do that. It is not like after dup2 you can close the original pipe because you would have a copy, or something like that. It is a copy of a descriptor to the same file. If you close the file, it is closed for the copy too.

A rewrite of your code

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

#define WRITE 1
#define READ 0

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

    int fd1[2], fd2[2];
    pid_t pid1,pid2;
    
    if( pipe(fd1) < 0)
    {
        perror("pipe 1");
        exit(-1);
    }

    if( (pid1 = fork()) < 0)
    {
        perror("fork");
        exit(-1);
    }
    
    
    if( pid1 == 0 ){
        if( pipe(fd2) < 0)
        {
            perror("pipe 2");
            exit(-1);
        }
        pid2 = fork();
    }
        
    if(pid1>0)
    {
        // I need to close fd1[READ] that I don't use (and that "sort" will use)
        // I must keep fd1[WRITE] that I use
        // I leave fd2[READ/WRITE] alone, since that variable is unitialized here (I've called "pipe(fd2)" only in the case
        // pid1==0 which it is not here)
        close(fd1[READ]); 
        dup2(fd1[WRITE],STDOUT_FILENO);
        execlp("ls","ls",NULL);
        perror("ls");
        exit(-1);
    }
    
    if(pid2>0)
    {
        // I need to read fd1, but not write it (it is ls that need to write it). 
        // So before doing anything, I close fd1[WRITE]. Must I must keep the other end, fd1[READ] open
        close(fd1[WRITE]);
        dup2(fd1[READ],STDIN_FILENO);
        // Likewise, I need to write on fd2[WRITE], and have "grep" read on the other end. 
        // So I close fd2[READ] this side, and keep fd2[WRITE] open
        close(fd2[READ]);
        dup2(fd2[WRITE],STDOUT_FILENO);
        execlp("sort","sort",NULL);
        perror("sort");
        exit(-1);
    }
    if(pid2==0)
    {
        // Last process. It needs to read fd2[READ], that I keep open, and don't need to write on fd2[WRITE] (that is "sort" job)
        // so I close fd2[WRITE]
        // I don't really case for fd1
        // Situation is not exactly symetrical that for 1st process and fd2 tho. Because for 1st process, fd2 what not even initialized.
        // While here, even if we don't need it, fd1 has been created by our grandfather. We shared it from our father, that shared it from its father
        // So, since we use none of fd1, we need to close both.
        close(fd1[READ]);
        close(fd1[WRITE]);
        close(fd2[WRITE]);
        dup2(fd2[READ],STDIN_FILENO);
        execlp("grep","grep","r",NULL);
        perror("grep");
        exit(-1);
    }

}

Note that this is not the only solution. I could have created pipe(fd2) unconditionally before even the 1st fork, and then close both fd2[0] and fd2[1] in the "ls" process. That would have been ok too.

The points are:

  • If you want to write on a fd[1] in a process, and read what have been written, from another process, on fd[0], then those process must share the same fd values, issued by the same pipe call; so pragmatically, that means that pipe(fd) must have been called before the fork that separated those two processes
  • Don't close the fd?[?] that you use. Even if you use them only through another fd on which you dup2 the fd?[?].
  • Close any fd?[?] that exist in a process, that is that have been created by a pipe call (you must mentally keep tracks of which process have which fd) and you don't need.
chrslg
  • 9,023
  • 5
  • 17
  • 31