Search code examples
cshellfile-iopipepiping

Bad file descriptor error when implementing piping in C


I am trying to implement a sample shell like program which executes the command ls | wc

Using pipes to implement the command. When I execute the command I get the following error.

wc: standard input: Bad file descriptor 0 0 0 wc: -: Bad file descriptor

Please take a look at the code and provide inputs Note: 1) parse is a library which takes in the input typed and returns each command as a linked list with args and necesary data. Parse is working fine 2) I am executing each command in a different subprocess hence the fork

#include <stdio.h>
#include <stdlib.h>
#include "parse.h"

int pip[3][2];
int main(int argc, char *argv[], char *envp[])
{
    Pipe p; 
    Cmd c;
    pipe(pip[0]);
    pipe(pip[1]);   
    pid_t pid;
    pid=fork();
    char *host = "armadillo";
    printf("%s%% ", host);
    p = parse();
    c=p->head;  
    printf("1 \n");
    pid=fork();

    if(pid==0)
    {
        close(pip[0][0]);
        close(STDOUT_FILENO);
        dup2(pip[0][1],STDOUT_FILENO);
        execvp(c->args[0],c->args);
    }
    else
    {
        waitpid(pid,NULL,0);
    }
    printf("2 \n");

    close(pip[0][1]);
    close(pip[0][0]);

    c=c->next;
    printf("%s \n",c->args[0]);
    pid=fork();
    if(pid==0)
    {
        close(STDIN_FILENO);
        dup2(pip[0][0],STDIN_FILENO);
        close(pip[0][1]);
        execvp(c->args[0],c->args);
    }
    else
    {   
        waitpid(pid,NULL,0);
        close(pip[0][1]);
        close(pip[0][0]);
    }

}

Solution

  • I took the lazy way out, and wrote my own rather than fix other code. Treat this as "yet another pipe-fitting example in C", but it might help point out the issues with OP's code.

    /*
     * hard-wired example program exploring how to implement
     *
     *     system("ls | wc");
     *
     * using calls to pipe(2), fork(2), execvp(2) and wait(2)
     */
    
    #include <sys/types.h>
    #include <sys/wait.h>
    #include <errno.h>
    #include <unistd.h>
    #include <stdio.h>
    #include <stdlib.h>
    
    static void
    do_close(int fd)
    {
        if (close(fd) == -1) {
            perror("close");
            exit(1);
        }
    }
    
    static void
    do_execvp(char *const cmd[])
    {
        execvp(cmd[0], cmd);
    
        /*
         * if execvp returns in this text, an error occured.
         */
    
        perror("execvp");
    
        exit(1);
    }
    
    static void
    dup_and_exec(int fd, int *pp, char *const cmd[])
    {
        if (dup2(pp[fd], fd) == -1) {
            perror("dup2");
            exit(1);
        }
    
        do_close(pp[0]);
        do_close(pp[1]);
    
        do_execvp(cmd);
    }
    
    int
    main(void)
    {
        char *const ls_cmd[] = { "ls", 0 };
        char *const wc_cmd[] = { "wc", 0 };
    
        int fds[2];
    
        int w_stat;
        pid_t ls_pid, wc_pid, w_pid;
    
        /* create a single pipe to connect our writer and reader processes */
    
        if (pipe(fds) == -1) {
            perror("pipe");
            exit(1);
        }
    
        /* create the writer process: ls */
    
        ls_pid = fork();
    
        if (ls_pid == -1) {
            perror("fork");
            exit(1);
        }
    
        if (ls_pid == 0) {
            /* this is the child - do the "ls" command */
    
            dup_and_exec(1, fds, ls_cmd);   /* no return from here */
        }
    
        /* create the reader process: wc */
    
        wc_pid = fork();
    
        if (wc_pid == -1) {
            perror("fork");
            exit(1);
        }
    
        if (wc_pid == 0) {
            /* this is the child - do the "wc" command */
    
            dup_and_exec(0, fds, wc_cmd);   /* no return from here */
        }
    
        /* parent process */
    
        /*
         * It's important to close the pipe completely in the parent,
         * so (in particular) there's no process that could be an
         * additional writer to the "write" side of the pipe.
         *
         * We need to arrange things so that our reader process (the "wc"
         * process in this example) will see EOF when the only writer (the
         * "ls" process) closes its output and exits.
         *
         * If this parent process does not close the write side of the pipe,
         * it remains open, since it's shared across fork(2), so the reader
         * (wc) won't ever see EOF and exit, and this parent process won't
         * ever see the wc exit, and everything hangs.
         *
         * The core problems will have started with the parent, which all
         * children know to be true.
         *
         * The next lines also close the "read" side of the pipe, which
         * is a bit cleaner, but won't affect proper operation of this
         * sample program. But closing all un-needed file descriptors is
         * good hygiene: for longer running applications, or for library
         * code that could be called from longer running programs, avoiding
         * any leaks of file descriptors is a good thing.
         */
    
        do_close(fds[0]);
        do_close(fds[1]);
    
        while ((w_pid = wait(&w_stat)) > 0) {
            printf("%s process exited", w_pid == ls_pid ? "ls" : "wc");
            if (WIFEXITED(w_stat)) {
                printf(" (status %d)", WEXITSTATUS(w_stat));
            }
            fputs("\n", stdout);
        }
    
        if (w_pid == -1 && errno != ECHILD) {
            perror("wait");
            exit(1);
        }
    
        return 0;
    }