Linking pipes in my shell implementation

I am currently writing my own wrapper implementation in C. I figured out the principle behind piping and fds redirection. However, some specific behavior with pipes caught my attention:

  • cat | ls (or any command that doesn't read from stdin as an end element in a pipe).

In this case, what happens in the shell is that ls is executed and cat asks for one line before exiting (resulting in SIGPIPE, I think). I tried to follow this tutorial to better understand how multiple channels work: http://web.cse.ohio-state.edu/~mamrak.1/CIS762/pipes_lab_notes.html

Below is the code I wrote to try and reproduce the behavior I am looking for:

char    *cmd1[] = {"/bin/cat", NULL};
char    *cmd2[] = {"/bin/ls", NULL};
int     pdes[2];
pid_t   child;

if (!(child = fork()))
{
    pipe(pdes);
    if (!fork())
    {
        close(pdes[0]);
        dup2(pdes[1], STDOUT_FILENO);
        /* cat command gets executed here */
        execvp(cmd1[0], cmd1);
    }
    else
    {
        close(pdes[1]);
        dup2(pdes[0], STDIN_FILENO);
        /* ls command gets executed here */
        execvp(cmd2[0], cmd2);
    }
}
wait(NULL);

      

I am aware of the security bugs of this implementation, but this is for testing purposes only. The problem with this code, as I understand it, is that whenever ls is executed, it just exits and then cat runs in the background somehow (and in my case it doesn't work because it tries to read into zsh request time as my program exits). I can't seem to find a solution to make it work the way it should. Because if I wait for commands one by one, commands like cat / dev / random | head -c 10 will run forever ...

If anyone has a solution to this problem, or at least some guidance, we would be very grateful.

+3


source to share


1 answer


After looking at the comments from @thatotherguy, here is the solution I found as implemented in my code. Please keep in mind that the pipe and fork calls should be checked for errors, but this version should be as simple as possible. Additional calls to exit are also needed for some of my builtins.

void    exec_pipe(t_ast *tree, t_sh *sh)
{
    int     pdes[2];
    int     status;
    pid_t   child_right;
    pid_t   child_left;

    pipe(pdes);
    if (!(child_left = fork()))
    {
        close(pdes[READ_END]);
        dup2(pdes[WRITE_END], STDOUT_FILENO);
        /* Execute command to the left of the tree */
        exit(execute_cmd(tree->left, sh));
    }
    if (!(child_right = fork()))
    {
        close(pdes[WRITE_END]);
        dup2(pdes[READ_END], STDIN_FILENO);
        /* Recursive call or execution of last command */
        if (tree->right->type == PIPE_NODE)
            exec_pipe(tree->right, sh);
        else
            exit(execute_cmd(tree->right, sh));
    }
    /* Should not forget to close both ends of the pipe */
    close(pdes[WRITE_END]);
    close(pdes[READ_END]);
    wait(NULL);
    waitpid(child_right, &status, 0);
    exit(get_status(status));
}

      

I was confused with the original link I posted and the various ways to handle chained pipes. From the POSIX link posted below my original question ( http://pubs.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_09_02 ) the following appears:



If the pipeline is not in the background (see Asynchronous Lists), the shell must wait for the last command specified in the pipeline to complete, and may also wait for all commands to complete.

Both behaviors are accepted: waiting for the last command or waiting for them all. I decided to implement the second behavior to stick with what bash / zsh would do.

0


source







All Articles