Broken pipe with built_in command like echo

131 views Asked by At

I have some issue in my program.

I have a school homework where I have to reproduce some features of bash The homework is almost done however I have an issue with built_in command and pipes indeed I have a broken pipe error whenever a built_in command is the last pipeline like such : ls | echo (this line will produce a broken pipe error) where as this one echo | ls will work just fine. after some research I found out what was the problem, I have this error because for built_in command (meaning that I don't use execve to call them) I don't fork them into a child process while the other command are forked, so my guess is that the built_command close the read_end of the pipe way too fast while ls is still writing into the pipe. I handled this error with forking a process too for the built_in commands. However I was wondering if they were a solution without forking the built_in commands as this would use unnecessary resources.

void    execute_routine(t_data *data, t_cmd *cmd)
{
    pid_t   pid_ret;

    if (data -> s_pipes && !ft_strcmp(cmd -> prev_stop, "|")
        && cmd -> prev_cmd -> p_close)
        close_fd(data, "bash", &data -> s_pipes -> s_pipes[1]);
    if (!built_in(data, cmd, 0))
    {
        pid_ret = fork();
        if (pid_ret < 0)
            print_err_and_exit(data, NULL, "bash", 1);
        if (pid_ret == 0)
            forking(cmd);
        cmd -> pid = pid_ret;
    }
    handle_pipes(data, cmd);
}

Here above, is the function that will execute each command get by the help of readline, you can see that if the command is a built_in we won't fork it and it will be handled right into that function for the case of echo here is the function :

int echo(t_data *data, t_cmd *cmd)
{
    int fd;

    data -> status = 1;
    if (open_check_files_built_in(cmd, cmd -> tab))
        return (1);
    fd = where_to_write(data, cmd);
    if (ft_tab_len(cmd -> args) == 1)
    {
        if (write_to_fd(data, "\n", fd) < 0)
            return (print_err_built_in("bash", 1));
        data -> status = 0;
        return (1);
    }
    if (write_args_(data, cmd, fd))
        return (1);
    if (cmd -> last_in && cmd -> last_in -> type == IN)
        close_fd_built_in(&cmd -> last_in -> fd);
    if (cmd -> last_out)
        close_fd_built_in(&cmd -> last_out -> fd);
    data -> status = 0;
    return (1);
}

for echo I only look for out redirection and writing into the returned fd. When I am done with the function I the pipe in that function.

void    handle_pipes(t_data *data, t_cmd *cmd)
{
    if (data -> s_pipes && data -> prev_pipes == -1
        && !ft_strcmp(cmd -> prev_stop, "|"))
        close_fd(data, "bash", &data -> s_pipes -> read_end -> s_pipes[0]);
    close_fd(data, "bash error", &data -> prev_pipes);
    if (data -> inited)
    {
        data -> prev_pipes = data -> pipes[0];
        close_fd(data, "bash pipes close", &data -> pipes[1]);
    }
    data -> inited = 0;
}

After that I wait all of my process with the function below

   int i;

   i = -1;
   while (cmds[++i])
   {
        if (cmds[i]-> pid && waitpid(
                cmds[i]-> pid, &data -> status, 0) < 0 && errno != ECHILD)
            print_err_and_exit(data, NULL, "Error with waitpid", 1);
    }

Like I said earlier the only differences between built_in command and the others is that built_in commands are not forked and directly handled in the parent process while the others are forked and handled in the child process. Although I have the broken pipe error signal the pipeline still output the expected result, for example ls | echo bonjour would still output bonjour to STDOUT but the error SIGPIPE would be sent to waitpid by the process handling the ls command, while echo bonjour | ls will also work fine and return a status code of 0 and not 13.

1

There are 1 answers

1
Eric Marceau On

The issue is that you misunderstand the nature of "echo" vs the nature of "ls", and again the nature of "pipes"

"ls" is a potentially multi-line function. It generates a stream.

A "pipe" is a mechanism to create an inter-process communication between one process creating a stream, and another process using that stream as input.

Here is where you failed to understand the nature of "echo": echo is NOT a multi-line (a.k.a. stream) -oriented tool! It is a single line tool geared to a "single" parameter string. That string could be multi-line, but that is passed to echo which only interprets it as a single parameter, without caring what the contents are.

Even the man page makes no reference to echo accepting input from stdin! In other words, it doesn't even open the pipe stream.

Hope that clarifies what you perceive as a mystery regarding the "ls | echo" pipeline.