Search code examples
clinuxshellpipestdin

implementing pipes in C shell


I am a begginer in programming and I am implementing the pipes in a C shell.

I figured out that some commands using the STDIN, the shell output is starting to work unproperly. For example the cursos is shifted.

This is the case with this kind of commands using directly the STDIN cat | echo hello or tr | ls

I checked if it was not related to a fd not closed ... I am closing the write end of the pipe as soon as I dup2 it. And I close the read end of my pipe in the child processus during pipe of the write end of the pipe.

Here is the code, sorry it is bit messy, it uses an array of int term->redir->pipe_fd to handle the multiple pipe case :

// For each pipe, it creates the pipe in a new instance of the int array term->redir->pipe_fd.  

int     make_pipe(t_term *term)  
{  
    int pipe_stack;  

    term->redir->pipe_stack++;  
    if (!((pipe_stack = term->redir->pipe_stack) < MAX_FD)  
            || pipe(term->redir->pipe_fd[pipe_stack]) == -1)  
    { 
        err_dup();  
        return (close_pipe(term));  
    }  
    return (0);  
}  

// left_pipe dup2 the write end of (pipe[1]) to the STDOUT, and closes the pipe's write end.  

int     left_pipe(t_term *term)  
{  
    int new_fd;  
    int pipe_stack;  

    new_fd = -1;  
    pipe_stack = term->redir->pipe_stack;  
    term->redir->pipe_write = 1;  
    if ((new_fd = dup2(term->redir->pipe_fd[pipe_stack][WRITE_END],
                    STDOUT_FILENO)) == -1)  
        ft_printf("[fd=2]21sh: %d: LEFT Bad file descriptor\n[/fd]",
                term->redir->pipe_fd[pipe_stack][WRITE_END]);  
    close(term->redir->pipe_fd[pipe_stack][WRITE_END]);  
    term->redir->pipe_fd[pipe_stack][WRITE_END] = -1;  
    return ((new_fd == -1) ? 1 : 0);  
}

// then, sh_exec is called, see below. The read end of the pipe is closes in the child processus. 
// right pipe dup2 the STDIN whith the readend of the pipe (pipe[0]) and closes it.

int     right_pipe(t_term *term)  
{  
    int new_fd;  
    int pipe_stack;  

    new_fd = -1;  
    pipe_stack = term->redir->pipe_stack;  
    term->redir->pipe_write = 0;  
    if (pipe_stack > FD_INIT)  
    {  
        term->redir->pipe_read = 1;  
        if ((new_fd = dup2(term->redir->pipe_fd[pipe_stack][READ_END],
                        STDIN_FILENO)) == -1)  
            ft_printf("[fd=2]21sh: %d: RIGHT Bad file descriptor\n[/fd]",
                    term->redir->pipe_fd[pipe_stack][READ_END]);  
        close(term->redir->pipe_fd[pipe_stack][READ_END]);  
        term->redir->pipe_fd[pipe_stack][READ_END] = -1;  
        term->redir->pipe_stack--;  
    }  
    return ((new_fd == -1) ? 1 : 0);  
}

After having called the left pipe, it calls the exec function here below and closes the unused read_end of the pipe (pipe[0]). After having called the right pipe, it calls again the exec function to execute the right part of the command :

void        wait_pipe(char *cmd, int *status)  
{  
    if (waitpid(-1, status, WNOHANG) == -1)  
        print_exec_error(cmd);  
}  

int         sh_exec(char *path, char **cmd, t_term *term)  
{  
    pid_t   father;  
    int     status;  

    status = 0;  
    father = fork();  
    if (father > 0 && term->redir->pipe_stack >= 0)  
        wait_pipe(*cmd, &status);  
    else if (father > 0)  
    {  
        if (waitpid(father, &status, 0) == -1)  
            print_exec_error(*cmd);  
    }  
    if (!father)  
    {  
        if (term->redir->pipe_write)  
            close(term->redir->pipe_fd[term->redir->pipe_stack][READ_END]);  
        if (execve(path, cmd, term->env) == -1)  
        {  
            ft_printf("[fd=2]Filetype unknown\n[/fd]");  
            exit(0);  
        }  
        err_not_found(*cmd);  
    }  
    return (status);  
}  

Besides, I don't understand the bash behavior of cat with the following command cat | echo. It gives back the prompt for one line and then stops, unlikely the cat command alone which continues and waits until it gets a stop signal.

If someone has a clue ?


Solution

  • Besides, I don't understand the bash behavior of cat with the following command cat | echo. It gives back the prompt for one line and then stops, unlikely the cat command alone which continues and waits until it gets a stop signal.

    I'll try to explain what is happening here:

    $ cat | echo
    
    foobar
    $
    

    the first blank line is the echo command printing just \n to stdout (since it doesn't have any argument), then I get the prompt, this is cat waiting for input, I type foobar and I press enter (\n). I suspect that cat prints it's output line-per-line so it tries to write foobar\n to its stdout, but its stdout is "piped" (|) to echo's stdin and echo already exited closing its stdin (since he had nothing more to do), so cat is trying to write to a closed pipe and gets SIGPIPE. The default behaviour of SIGPIPE is to end the process.

    running strace confirms all this:

    $ strace cat | echo
    ...
    read(0, foobar
    "foobar\n", 131072)             = 7
    write(1, "foobar\n", 7)                 = -1 EPIPE (Broken pipe)
    --- SIGPIPE {si_signo=SIGPIPE, si_code=SI_USER, si_pid=6069, si_uid=1000} ---
    +++ killed by SIGPIPE +++
    $