Dimitri Danilov
Dimitri Danilov

Reputation: 1352

Implementation of pipe in C

I just finished my shell interpretor but I think my pipe implementation is wrong.

It's working, basic things like ls | cat -e works but I am afraid of segmentation fault possibilities if the file descriptor is over 60 ko. I found also a infinite loop when I do a cat of a file which is longer than 60 ko. For exemple if a do a cat foo | cat -e where foo is a long file, infinite loop happen.

Or other exemple when I do cat /dev/urandom | cat -e it don't show me any display so it execute first cat /dev/urandom and then cat -e.

This is my code :

int son(int *fd_in, int p[2], t_list *cmd, char **env)
{
    (void)env;
    dup2(*fd_in, 0);
    if (cmd->act != ENDACT && cmd->act != LEFT && cmd->act != DLEFT)
        dup2(p[1], 1);
    close(p[0]);
    execve(cmd->av[0], cmd->av, NULL);
    return (-1);
}

t_list *execute_pipe(t_list *cmd, int *fd_in)
{
    int           p[2];
    pid_t         pid;

    *fd_in = 0;
    while (cmd->act != -1)
    {
        pipe(p);
        if ((pid = fork()) == -1)
            return (NULL);
        else if (pid == 0)
            son(fd_in, p, cmd, NULL);
        else
        {
            wait(NULL);
            close(p[1]);
            *fd_in = p[0];
            if (cmd->act != PIPE)
                return (cmd);
            cmd = cmd->next;
        }
    }
    return (cmd);
}

Upvotes: 3

Views: 3151

Answers (1)

John Bollinger
John Bollinger

Reputation: 181724

Part of the idea of a shell pipeline is that the processes involved run concurrently (or may do). The code you presented actively prevents that from happening by wait()ing on each child process before launching the next one. Among other things, that runs the risk of filling the (OS-level) pipe's buffer before there's anything ready to drain it. That will deadlock or, if you're lucky, generate an error.

At a high level, the procedure should look like this:

  1. [shell] Let C initially be the command for the first segment of the pipe, and set fd0 to be STDIN_FILENO
  2. [shell] Prepare an output file descriptor:
    1. If there are any subsequent commands, create a pipe(), and set fd1 to be the write end of that pipe;
    2. otherwise, set fd1 to be STDOUT_FILENO
  3. [shell] fork() a child in which to run command C. In it:
    1. [child] if fd0 is different from STDIN_FILENO then dup2() fd0 onto STDIN_FILENO and close fd0
    2. [child] if fd1 is different from STDOUT_FILENO then dup2() fd1 onto STDOUT_FILENO and close fd1
    3. [child] exec command C
  4. [shell] if fd0 is different from STDIN_FILENO then close fd0
  5. [shell] if fd1 is different from STDOUT_FILENO then close fd1
  6. [shell] If there are any more commands in the pipe, then
    1. set C to be the next command
    2. set fd0 to be the read end of the pipe from step (2), above
    3. go to step 2 (Prepare an output file descriptor)
  7. [shell] (At this point, all processes in the pipeline have been started.) wait() or waitpid() for all the child processes

Note that that works equally well for a pipeline containing any positive number of commands, including 1.

Upvotes: 5

Related Questions