Home > Software design >  Execute with pipe in c
Execute with pipe in c

Time:11-19

A problem was issued during the production of the minishell, using pipe() and fork() to apply the pipeline, while producing code that performs the same role as "command | command" in bash

This code above has made it perform the same role as "ls -al | head" and is actually done and terminated

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



int main (){
    int fd[2], status;
    pipe(fd);
    if(fork() == 0){
        close(fd[0]);
        dup2(fd[1], STDOUT_FILENO);
        close(fd[1]);
        execlp("ls", "ls", "-al", NULL);
    }
    else
        wait(&status);
    if(fork() == 0){
        close(fd[1]);
        dup2(fd[0], STDIN_FILENO);
        close(fd[0]);
        execlp("head", "head", NULL);
    }
    else 
        wait(&status);
    close(fd[1]);
    close(fd[0]);

}

This code has the same function as "ls-al | greptxt", and it does exactly the output, but the code does not exit because it does not advance further in the second wait.

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



int main (){
    int fd[2], status;
    pipe(fd);
    if(fork() == 0){
        close(fd[0]);
        dup2(fd[1], STDOUT_FILENO);
        close(fd[1]);
        execlp("ls", "ls", "-al", NULL);
    }
    else
        wait(&status);
    if(fork() == 0){
        close(fd[1]);
        dup2(fd[0], STDIN_FILENO);
        close(fd[0]);
        execlp("grep", "grep", "txt", NULL);
    }
    else 
        wait(&status);
    close(fd[1]);
    close(fd[0]);

}

I think the grep does not end because if the pipeline tries to input after the end of the value that is currently in the pipe, it does not recognize it and continues to wait for another value to enter the pipeline. But I don't know how to solve this.

Currently, the method applied to solve this problem is implemented by using usleep() to remove the child process after a certain time, but this is too unstable. I certainly hope that the parent process will resume when the child process completes the execute.

CodePudding user response:

You aren't closing enough file descriptors in the parent process.

Also, you must run all the processes in the pipeline before waiting for any of them to finish. Suppose the first process creates a gigabyte of data. It will fill the pipe buffer (usually 64 Kib these days, but it used to be 5 Kib once upon another millennium, and POSIX only mandates a pipe buffer size of at least 4 KiB). And then it will block, meaning it won't exit, so the wait() won't complete, so the second process won't be started. You avoid that by starting all the processes, then the parent closes its copy of the pipe (so that the process can get EOF properly), and only then goes into a loop to wait() for the commands to complete.

So, you need to:

  1. Remove the first wait().
  2. Close the pipe in the parent process.
  3. Replace the second wait() with a loop that executes wait() until there are no children left.
  4. Or use waitpid() in a loop and wait for the two children to die, waiting on each child PID in turn (and keeping a record of the PIDs, therefore).

Note that fork() can fail. Often you should diagnose that. It probably won't matter here, but a lot of programming is about taking care of errors.


Rule of thumb: If you dup2() one end of a pipe to standard input or standard output, close both of the original file descriptors returned by pipe() as soon as possible. In particular, you should close them before using any of the exec*() family of functions.

The rule also applies if you duplicate the descriptors with either dup() or fcntl() with F_DUPFD or F_DUPFD_CLOEXEC.


If the parent process will not communicate with any of its children via the pipe, it must ensure that it closes both ends of the pipe early enough (before waiting, for example) so that its children can receive EOF indications on read (or get SIGPIPE signals or write errors on write), rather than blocking indefinitely. Even if the parent uses the pipe without using dup2(), it should normally close at least one end of the pipe — it is extremely rare for a program to read and write on both ends of a single pipe.

Note that the O_CLOEXEC option to open(), and the FD_CLOEXEC and F_DUPFD_CLOEXEC options to fcntl() can also factor into this discussion.

If you use posix_spawn() and its extensive family of support functions (21 functions in total), you will need to review how to close file descriptors in the spawned process (posix_spawn_file_actions_addclose(), etc.).

Note that using dup2(a, b) is safer than using close(b); dup(a); for a variety of reasons. One is that if you want to force the file descriptor to a larger than usual number, dup2() is the only sensible way to do that. Another is that if a is the same as b (e.g. both 0), then dup2() handles it correctly (it doesn't close b before duplicating a) whereas the separate close() and dup() fails horribly. This is an unlikely, but not impossible, circumstance.

  • Related