Search code examples
cpipefork

Write the output of the child in shell and in file error.log


I attempt to pipe the output of a child, into a file and write it by Father.

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

int prepare_log(void){
    int fd = open("error.log",
        O_CREAT | O_RDWR | O_APPEND,
        S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH );
    return fd;
}

void log_stderr(int pipe_ends[], int outfile){
    close(pipe_ends[1]);
    dup2(pipe_ends[0],outfile);
    close(pipe_ends[0]);

}
void child(int pipe_ends[], char* argv[]){
    close(pipe_ends[0]);
    dup2(pipe_ends[1],1);
    close(pipe_ends[1]);
    execvp(argv[0], argv);
}

void bury(){
    int status;
    wait(0);

}

int main(){
    int fd = prepare_log();
    char* argv[] = {"seq", "10", NULL};

    int pipe1[2];
    if(pipe(pipe1) == -1){
        printf("Dont' create Pipe\n");
        exit(EXIT_FAILURE);
    }


    pid_t pid = fork();
    if(pid < 0){
        perror("ERROR");
        exit(EXIT_FAILURE);
    } else if(pid > 0){
        log_stderr(pipe1, fd);
        bury();
    } else if (pid == 0){
        child(pipe1,argv);
    }
}

At the moment I try only to pipe the output of child to father and then write it to a file. My final goal is also to display it to the terminal. My idea is to use 3 pipes and redirects the first pipe that we can see in the code, as the input of the 2-nd and 3-rd pipe. Then redirect the output of the 2-nd pipe with dup2(pipe2[1],file1) und the 3-rd with dup2(pipe2[1], stdout).


Solution

  • Program design

    The parent process is going to have to read the response from the child, and arrange to write that information twice, once to the log file and once to the terminal. Or you're going to have to arrange for the output from the child to go to a program like tee which writes copies of its input to multiple destinations.

    More pipes won't help unless you're using one to redirect the output of the child to tee.

    Closing pipe file descriptors

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


    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.

    Sufficiently fixed code

    Here is a fixed version of your program. The logic in log_stderr() is odd; the one thing it doesn't do is write to standard error. I've modified it so it does read from the pipe and write to both standard output and the log file. Note that it takes one read but two writes to replicate the information. Error checking is skimpy to non-existent. The bury() function now reports on the death of a child. The code was saved in pipe71.c and compiled to pipe71.

    #include <fcntl.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/wait.h>
    #include <unistd.h>
    
    static
    int prepare_log(void)
    {
        int fd = open("error.log",
                      O_CREAT | O_RDWR | O_APPEND,
                      S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH );
        return fd;
    }
    
    static
    void log_stderr(int pipe_ends[], int outfile)
    {
        char buffer[512];
        int nbytes;
        close(pipe_ends[1]);
        while ((nbytes = read(pipe_ends[0], buffer, sizeof(buffer))) > 0)
        {
            write(STDOUT_FILENO, buffer, nbytes);
            write(outfile, buffer, nbytes);
        }
        close(pipe_ends[0]);
        close(outfile);
    }
    
    static
    void child(int pipe_ends[], char *argv[])
    {
        dup2(pipe_ends[1], 1);
        close(pipe_ends[0]);
        close(pipe_ends[1]);
        execvp(argv[0], argv);
        fprintf(stderr, "failed to execute %s\n", argv[0]);
        exit(EXIT_FAILURE);
    }
    
    static
    void bury(void)
    {
        int status;
        int corpse;
        while ((corpse = wait(&status)) > 0)
            printf("%d: child %d exited with status 0x%.4X\n", (int)getpid(), corpse, status);
    }
    
    int main(void)
    {
        int fd = prepare_log();
        char *argv[] = {"seq", "10", NULL};
    
        int pipe1[2];
        pipe(pipe1);
    
        pid_t pid = fork();
    
        if (pid > 0)
        {
            log_stderr(pipe1, fd);
            bury();
        }
        else if (pid == 0)
        {
            child(pipe1, argv);
        }
        return 0;
    }
    

    However, it does work:

    $ rm error.log
    $ ./pipe71
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    99989: child 99990 exited with status 0x0000
    $ cat error.log
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    $