Example #1
0
bool io_buffer_t::avoid_conflicts_with_io_chain(const io_chain_t &ios) {
    bool result = pipe_avoid_conflicts_with_io_chain(this->pipe_fd, ios);
    if (!result) {
        wperror(L"dup");
    }
    return result;
}
Example #2
0
maybe_t<autoclose_pipes_t> make_autoclose_pipes(const io_chain_t &ios) {
    int pipes[2] = {-1, -1};

    if (pipe(pipes) < 0) {
        debug(1, PIPE_ERROR);
        wperror(L"pipe");
        return none();
    }
    set_cloexec(pipes[0]);
    set_cloexec(pipes[1]);

    if (!pipe_avoid_conflicts_with_io_chain(pipes, ios)) {
        // The pipes are closed on failure here.
        return none();
    }
    autoclose_pipes_t result;
    result.read = autoclose_fd_t(pipes[0]);
    result.write = autoclose_fd_t(pipes[1]);
    return {std::move(result)};
}
Example #3
0
/// Executes a process \p in job \j, using the read pipe \p pipe_current_read.
/// If the process pipes to a command, the read end of the created pipe is returned in
/// out_pipe_next_read. \returns true on success, false on exec error.
static bool exec_process_in_job(parser_t &parser, process_t *p, job_t *j,
                                autoclose_fd_t pipe_current_read,
                                autoclose_fd_t *out_pipe_next_read, const io_chain_t &all_ios,
                                size_t stdout_read_limit) {
    // The IO chain for this process. It starts with the block IO, then pipes, and then gets any
    // from the process.
    io_chain_t process_net_io_chain = j->block_io_chain();

    // See if we need a pipe.
    const bool pipes_to_next_command = !p->is_last_in_job;

    // The write end of any pipe we create.
    autoclose_fd_t pipe_current_write{};

    // The pipes the current process write to and read from. Unfortunately these can't be just
    // allocated on the stack, since j->io wants shared_ptr.
    //
    // The write pipe (destined for stdout) needs to occur before redirections. For example,
    // with a redirection like this:
    //
    //   `foo 2>&1 | bar`
    //
    // what we want to happen is this:
    //
    //    dup2(pipe, stdout)
    //    dup2(stdout, stderr)
    //
    // so that stdout and stderr both wind up referencing the pipe.
    //
    // The read pipe (destined for stdin) is more ambiguous. Imagine a pipeline like this:
    //
    //   echo alpha | cat < beta.txt
    //
    // Should cat output alpha or beta? bash and ksh output 'beta', tcsh gets it right and
    // complains about ambiguity, and zsh outputs both (!). No shells appear to output 'alpha',
    // so we match bash here. That would mean putting the pipe first, so that it gets trumped by
    // the file redirection.
    //
    // However, eval does this:
    //
    //   echo "begin; $argv "\n" ;end <&3 3<&-" | source 3<&0
    //
    // which depends on the redirection being evaluated before the pipe. So the write end of the
    // pipe comes first, the read pipe of the pipe comes last. See issue #966.
    shared_ptr<io_pipe_t> pipe_write;
    shared_ptr<io_pipe_t> pipe_read;

    // Write pipe goes first.
    if (pipes_to_next_command) {
        pipe_write.reset(new io_pipe_t(p->pipe_write_fd, false));
        process_net_io_chain.push_back(pipe_write);
    }

    // The explicit IO redirections associated with the process.
    process_net_io_chain.append(p->io_chain());

    // Read pipe goes last.
    if (!p->is_first_in_job) {
        pipe_read.reset(new io_pipe_t(p->pipe_read_fd, true));
        // Record the current read in pipe_read.
        pipe_read->pipe_fd[0] = pipe_current_read.fd();
        process_net_io_chain.push_back(pipe_read);
    }

    // This call is used so the global environment variable array is regenerated, if needed,
    // before the fork. That way, we avoid a lot of duplicate work where EVERY child would need
    // to generate it, since that result would not get written back to the parent. This call
    // could be safely removed, but it would result in slightly lower performance - at least on
    // uniprocessor systems.
    if (p->type == EXTERNAL) {
        // Apply universal barrier so we have the most recent uvar changes
        if (!get_proc_had_barrier()) {
            set_proc_had_barrier(true);
            env_universal_barrier();
        }
        env_export_arr();
    }

    // Set up fds that will be used in the pipe.
    if (pipes_to_next_command) {
        // debug( 1, L"%ls|%ls" , p->argv[0], p->next->argv[0]);
        int local_pipe[2] = {-1, -1};
        if (exec_pipe(local_pipe) == -1) {
            debug(1, PIPE_ERROR);
            wperror(L"pipe");
            job_mark_process_as_failed(j, p);
            return false;
        }

        // Ensure our pipe fds not conflict with any fd redirections. E.g. if the process is
        // like 'cat <&5' then fd 5 must not be used by the pipe.
        if (!pipe_avoid_conflicts_with_io_chain(local_pipe, all_ios)) {
            // We failed. The pipes were closed for us.
            wperror(L"dup");
            job_mark_process_as_failed(j, p);
            return false;
        }

        // This tells the redirection about the fds, but the redirection does not close them.
        assert(local_pipe[0] >= 0);
        assert(local_pipe[1] >= 0);
        memcpy(pipe_write->pipe_fd, local_pipe, sizeof(int) * 2);

        // Record our pipes.
        pipe_current_write.reset(local_pipe[1]);
        out_pipe_next_read->reset(local_pipe[0]);
    }

    // Execute the process.
    switch (p->type) {
        case INTERNAL_FUNCTION:
        case INTERNAL_BLOCK_NODE: {
            if (!exec_block_or_func_process(parser, j, p, all_ios, process_net_io_chain)) {
                return false;
            }
            break;
        }

        case INTERNAL_BUILTIN: {
            io_streams_t builtin_io_streams{stdout_read_limit};
            if (!exec_internal_builtin_proc(parser, j, p, pipe_read.get(), process_net_io_chain,
                                            builtin_io_streams)) {
                return false;
            }
            if (!handle_builtin_output(j, p, &process_net_io_chain, builtin_io_streams)) {
                return false;
            }
            break;
        }

        case EXTERNAL: {
            if (!exec_external_command(j, p, process_net_io_chain)) {
                return false;
            }
            break;
        }

        case INTERNAL_EXEC: {
            // We should have handled exec up above.
            DIE("INTERNAL_EXEC process found in pipeline, where it should never be. Aborting.");
            break;
        }
    }
    return true;
}