Beispiel #1
0
void io_print(const io_chain_t &chain)
{
    if (chain.empty())
    {
        fprintf(stderr, "Empty chain %p\n", &chain);
        return;
    }

    fprintf(stderr, "Chain %p (%ld items):\n", &chain, (long)chain.size());
    for (size_t i=0; i < chain.size(); i++)
    {
        const io_data_t *io = chain.at(i);
        fprintf(stderr, "\t%lu: fd:%d, input:%s, ", (unsigned long)i, io->fd, io->is_input ? "yes" : "no");
        switch (io->io_mode)
        {
            case IO_FILE:
                fprintf(stderr, "file (%s)\n", io->filename_cstr);
                break;
            case IO_PIPE:
                fprintf(stderr, "pipe {%d, %d}\n", io->param1.pipe_fd[0], io->param1.pipe_fd[1]);
                break;
            case IO_FD:
                fprintf(stderr, "FD map %d -> %d\n", io->param1.old_fd, io->fd);
                break;
            case IO_BUFFER:
                fprintf(stderr, "buffer %p (size %lu)\n", io->out_buffer_ptr(), io->out_buffer_size());
                break;
            case IO_CLOSE:
                fprintf(stderr, "close %d\n", io->fd);
                break;
        }
    }
}
Beispiel #2
0
void io_chain_t::duplicate_prepend(const io_chain_t &src)
{
    /* Prepend a duplicate of src before this. Start by inserting a bunch of NULLs (so we only have to reallocate once) and then replace them. */
    this->insert(this->begin(), src.size(), NULL);
    for (size_t idx = 0; idx < src.size(); idx++)
    {
        const io_data_t *src_data = src.at(idx);
        this->at(idx) = new io_data_t(*src_data);
    }
}
Beispiel #3
0
/// Read from descriptors until they are empty.
///
/// \param j the job to test
static void read_try(job_t *j) {
    io_buffer_t *buff = NULL;

    // Find the last buffer, which is the one we want to read from.
    const io_chain_t chain = j->all_io_redirections();
    for (size_t idx = 0; idx < chain.size(); idx++) {
        io_data_t *d = chain.at(idx).get();
        if (d->io_mode == IO_BUFFER) {
            buff = static_cast<io_buffer_t *>(d);
        }
    }

    if (buff) {
        debug(3, L"proc::read_try('%ls')", j->command_wcstr());
        while (1) {
            char b[BUFFER_SIZE];
            long l;

            l = read_blocked(buff->pipe_fd[0], b, BUFFER_SIZE);
            if (l == 0) {
                break;
            } else if (l < 0) {
                if (errno != EAGAIN) {
                    debug(1, _(L"An error occured while reading output from code block"));
                    wperror(L"read_try");
                }
                break;
            } else {
                buff->out_buffer_append(b, l);
            }
        }
    }
}
Beispiel #4
0
/// Check if there are buffers associated with the job, and select on them for a while if available.
///
/// \param j the job to test
///
/// \return 1 if buffers were available, zero otherwise
static int select_try(job_t *j) {
    fd_set fds;
    int maxfd = -1;

    FD_ZERO(&fds);

    const io_chain_t chain = j->all_io_redirections();
    for (size_t idx = 0; idx < chain.size(); idx++) {
        const io_data_t *io = chain.at(idx).get();
        if (io->io_mode == IO_BUFFER) {
            const io_pipe_t *io_pipe = static_cast<const io_pipe_t *>(io);
            int fd = io_pipe->pipe_fd[0];
            // fwprintf( stderr, L"fd %d on job %ls\n", fd, j->command );
            FD_SET(fd, &fds);
            maxfd = maxi(maxfd, fd);
            debug(3, L"select_try on %d", fd);
        }
    }

    if (maxfd >= 0) {
        int retval;
        struct timeval tv;

        tv.tv_sec = 0;
        tv.tv_usec = 10000;

        retval = select(maxfd + 1, &fds, 0, 0, &tv);
        if (retval == 0) {
            debug(3, L"select_try hit timeout");
        }
        return retval > 0;
    }

    return -1;
}
Beispiel #5
0
void io_print(const io_chain_t &chain)
{
    if (chain.empty())
    {
        fprintf(stderr, "Empty chain %p\n", &chain);
        return;
    }

    fprintf(stderr, "Chain %p (%ld items):\n", &chain, (long)chain.size());
    for (size_t i=0; i < chain.size(); i++)
    {
        const shared_ptr<const io_data_t> &io = chain.at(i);
        fprintf(stderr, "\t%lu: fd:%d, ", (unsigned long)i, io->fd);
        io->print();
    }
}
Beispiel #6
0
static bool chain_contains_redirection_to_real_file(const io_chain_t &io_chain) {
    bool result = false;
    for (size_t idx = 0; idx < io_chain.size(); idx++) {
        const io_data_t *io = io_chain.at(idx).get();
        if (redirection_is_to_real_file(io)) {
            result = true;
            break;
        }
    }
    return result;
}
Beispiel #7
0
/**
   Check if the specified fd is used as a part of a pipeline in the
   specidied set of IO redirections.
   This is called after fork().

   \param fd the fd to search for
   \param io_chain the set of io redirections to search in
*/
static bool use_fd_in_pipe(int fd, const io_chain_t &io_chain)
{
    for (size_t idx = 0; idx < io_chain.size(); idx++)
    {
        const shared_ptr<const io_data_t> &io = io_chain.at(idx);
        if ((io->io_mode == IO_BUFFER) ||
                (io->io_mode == IO_PIPE))
        {
            CAST_INIT(const io_pipe_t *, io_pipe, io.get());
            if (io_pipe->pipe_fd[0] == fd || io_pipe->pipe_fd[1] == fd)
                return true;
        }
    }
Beispiel #8
0
// This isn't used so the lint tools were complaining about its presence. I'm keeping it in the
// source because it could be useful for debugging.
void io_print(const io_chain_t &chain)
{
    if (chain.empty())
    {
        std::fwprintf(stderr, L"Empty chain %p\n", &chain);
        return;
    }

    std::fwprintf(stderr, L"Chain %p (%ld items):\n", &chain, (long)chain.size());
    for (size_t i=0; i < chain.size(); i++)
    {
        const shared_ptr<io_data_t> &io = chain.at(i);
        if (io.get() == NULL)
        {
            std::fwprintf(stderr, L"\t(null)\n");
        }
        else
        {
            std::fwprintf(stderr, L"\t%lu: fd:%d, ", (unsigned long)i, io->fd);
            io->print();
        }
    }
}
Beispiel #9
0
/** Make sure the fd used by each redirection is not used by a pipe. Note that while this does not modify the vector, it does modify the IO redirections within (gulp) */
static void free_redirected_fds_from_pipes(const io_chain_t &io_chain)
{
    size_t max = io_chain.size();
    for (size_t i = 0; i < max; i++)
    {
        int fd_to_free = io_chain.at(i)->fd;

        /* We only have to worry about fds beyond the three standard ones */
        if (fd_to_free <= 2)
            continue;

        /* Make sure the fd is not used by a pipe */
        for (size_t j = 0; j < max; j++)
        {
            /* We're only interested in pipes */
            io_data_t *io = io_chain.at(j).get();
            if (io->io_mode != IO_PIPE && io->io_mode != IO_BUFFER)
                continue;

            CAST_INIT(io_pipe_t *, possible_conflict, io);
            /* If the pipe is a conflict, dup it to some other value */
            for (int k=0; k<2; k++)
            {
                /* If it's not a conflict, we don't care */
                if (possible_conflict->pipe_fd[k] != fd_to_free)
                    continue;

                /* Repeat until we have a replacement fd */
                int replacement_fd = -1;
                while (replacement_fd < 0)
                {
                    replacement_fd = dup(fd_to_free);
                    if (replacement_fd == -1 && errno != EINTR)
                    {
                        debug_safe_int(1, FD_ERROR, fd_to_free);
                        safe_perror("dup");
                        FATAL_EXIT();
                    }
                }
                possible_conflict->pipe_fd[k] = replacement_fd;
            }
        }
    }
}
Beispiel #10
0
bool fork_actions_make_spawn_properties(posix_spawnattr_t *attr, posix_spawn_file_actions_t *actions, job_t *j, process_t *p, const io_chain_t &io_chain)
{
    /* Initialize the output */
    if (posix_spawnattr_init(attr) != 0)
    {
        return false;
    }

    if (posix_spawn_file_actions_init(actions) != 0)
    {
        posix_spawnattr_destroy(attr);
        return false;
    }

    bool should_set_parent_group_id = false;
    int desired_parent_group_id = 0;
    if (job_get_flag(j, JOB_CONTROL))
    {
        should_set_parent_group_id = true;

        // PCA: I'm quite fuzzy on process groups,
        // but I believe that the default value of 0
        // means that the process becomes its own
        // group leader, which is what set_child_group did
        // in this case. So we want this to be 0 if j->pgid is 0.
        desired_parent_group_id = j->pgid;
    }

    /* Set the handling for job control signals back to the default.  */
    bool reset_signal_handlers = true;

    /* Remove all signal blocks */
    bool reset_sigmask = true;

    /* Set our flags */
    short flags = 0;
    if (reset_signal_handlers)
        flags |= POSIX_SPAWN_SETSIGDEF;
    if (reset_sigmask)
        flags |= POSIX_SPAWN_SETSIGMASK;
    if (should_set_parent_group_id)
        flags |= POSIX_SPAWN_SETPGROUP;

    int err = 0;
    if (! err)
        err = posix_spawnattr_setflags(attr, flags);

    if (! err && should_set_parent_group_id)
        err = posix_spawnattr_setpgroup(attr, desired_parent_group_id);

    /* Everybody gets default handlers */
    if (! err && reset_signal_handlers)
    {
        sigset_t sigdefault;
        get_signals_with_handlers(&sigdefault);
        err = posix_spawnattr_setsigdefault(attr, &sigdefault);
    }

    /* No signals blocked */
    sigset_t sigmask;
    sigemptyset(&sigmask);
    if (! err && reset_sigmask)
        err = posix_spawnattr_setsigmask(attr, &sigmask);
    
    for (size_t idx = 0; idx < io_chain.size(); idx++)
    {
        const shared_ptr<const io_data_t> io = io_chain.at(idx);

        if (io->io_mode == IO_FD)
        {
            CAST_INIT(const io_fd_t *, io_fd, io.get());
            if (io->fd == io_fd->old_fd)
                continue;
        }

        switch (io->io_mode)
        {
            case IO_CLOSE:
            {
                if (! err)
                    err = posix_spawn_file_actions_addclose(actions, io->fd);
                break;
            }

            case IO_FILE:
            {
                CAST_INIT(const io_file_t *, io_file, io.get());
                if (! err)
                    err = posix_spawn_file_actions_addopen(actions, io->fd, io_file->filename_cstr, io_file->flags /* mode */, OPEN_MASK);
                break;
            }

            case IO_FD:
            {
                CAST_INIT(const io_fd_t *, io_fd, io.get());
                if (! err)
                    err = posix_spawn_file_actions_adddup2(actions, io_fd->old_fd /* from */, io->fd /* to */);
                break;
            }

            case IO_BUFFER:
            case IO_PIPE:
            {
                CAST_INIT(const io_pipe_t *, io_pipe, io.get());
                unsigned int write_pipe_idx = (io_pipe->is_input ? 0 : 1);
                int from_fd = io_pipe->pipe_fd[write_pipe_idx];
                int to_fd = io->fd;
                if (! err)
                    err = posix_spawn_file_actions_adddup2(actions, from_fd, to_fd);


                if (write_pipe_idx > 0)
                {
                    if (! err)
                        err = posix_spawn_file_actions_addclose(actions, io_pipe->pipe_fd[0]);
                    if (! err)
                        err = posix_spawn_file_actions_addclose(actions, io_pipe->pipe_fd[1]);
                }
                else
                {
                    if (! err)
                        err = posix_spawn_file_actions_addclose(actions, io_pipe->pipe_fd[0]);

                }
                break;
            }
        }
    }
Beispiel #11
0
/**
   Set up a childs io redirections. Should only be called by
   setup_child_process(). Does the following: First it closes any open
   file descriptors not related to the child by calling
   close_unused_internal_pipes() and closing the universal variable
   server file descriptor. It then goes on to perform all the
   redirections described by \c io.

   \param io the list of IO redirections for the child

   \return 0 on sucess, -1 on failiure
*/
static int handle_child_io(const io_chain_t &io_chain)
{
    for (size_t idx = 0; idx < io_chain.size(); idx++)
    {
        const io_data_t *io = io_chain.at(idx).get();
        int tmp;

        if (io->io_mode == IO_FD && io->fd == static_cast<const io_fd_t*>(io)->old_fd)
        {
            continue;
        }

        switch (io->io_mode)
        {
            case IO_CLOSE:
            {
                if (log_redirections) fprintf(stderr, "%d: close %d\n", getpid(), io->fd);
                if (close(io->fd))
                {
                    debug_safe_int(0, "Failed to close file descriptor %s", io->fd);
                    safe_perror("close");
                }
                break;
            }

            case IO_FILE:
            {
                // Here we definitely do not want to set CLO_EXEC because our child needs access
                CAST_INIT(const io_file_t *, io_file, io);
                if ((tmp=open(io_file->filename_cstr,
                              io_file->flags, OPEN_MASK))==-1)
                {
                    if ((io_file->flags & O_EXCL) &&
                            (errno ==EEXIST))
                    {
                        debug_safe(1, NOCLOB_ERROR, io_file->filename_cstr);
                    }
                    else
                    {
                        debug_safe(1, FILE_ERROR, io_file->filename_cstr);
                        safe_perror("open");
                    }

                    return -1;
                }
                else if (tmp != io->fd)
                {
                    /*
                      This call will sometimes fail, but that is ok,
                      this is just a precausion.
                    */
                    close(io->fd);

                    if (dup2(tmp, io->fd) == -1)
                    {
                        debug_safe_int(1,  FD_ERROR, io->fd);
                        safe_perror("dup2");
                        return -1;
                    }
                    exec_close(tmp);
                }
                break;
            }

            case IO_FD:
            {
                int old_fd = static_cast<const io_fd_t *>(io)->old_fd;
                if (log_redirections) fprintf(stderr, "%d: fd dup %d to %d\n", getpid(), old_fd, io->fd);

                /*
                  This call will sometimes fail, but that is ok,
                  this is just a precausion.
                */
                close(io->fd);


                if (dup2(old_fd, io->fd) == -1)
                {
                    debug_safe_int(1, FD_ERROR, io->fd);
                    safe_perror("dup2");
                    return -1;
                }
                break;
            }

            case IO_BUFFER:
            case IO_PIPE:
            {
                CAST_INIT(const io_pipe_t *, io_pipe, io);
                /* If write_pipe_idx is 0, it means we're connecting to the read end (first pipe fd). If it's 1, we're connecting to the write end (second pipe fd). */
                unsigned int write_pipe_idx = (io_pipe->is_input ? 0 : 1);
                /*
                        debug( 0,
                             L"%ls %ls on fd %d (%d %d)",
                             write_pipe?L"write":L"read",
                             (io->io_mode == IO_BUFFER)?L"buffer":L"pipe",
                             io->fd,
                             io->pipe_fd[0],
                             io->pipe_fd[1]);
                */
                if (log_redirections) fprintf(stderr, "%d: %s dup %d to %d\n", getpid(), io->io_mode == IO_BUFFER ? "buffer" : "pipe", io_pipe->pipe_fd[write_pipe_idx], io->fd);
                if (dup2(io_pipe->pipe_fd[write_pipe_idx], io->fd) != io->fd)
                {
                    debug_safe(1, LOCAL_PIPE_ERROR);
                    safe_perror("dup2");
                    return -1;
                }

                if (io_pipe->pipe_fd[0] >= 0)
                    exec_close(io_pipe->pipe_fd[0]);
                if (io_pipe->pipe_fd[1] >= 0)
                    exec_close(io_pipe->pipe_fd[1]);
                break;
            }

        }
    }

    return 0;

}
Beispiel #12
0
void exec_job(parser_t &parser, shared_ptr<job_t> j) {
    assert(j && "null job_t passed to exec_job!");

    // Set to true if something goes wrong while exec:ing the job, in which case the cleanup code
    // will kick in.
    bool exec_error = false;

    // If fish was invoked with -n or --no-execute, then no_exec will be set and we do nothing.
    if (no_exec) {
        return;
    }

    // Unfortunately `exec_job()` is called recursively when functions are encountered, with a new
    // job id (and therefore pgrp) each time, but always from the main thread. This breaks terminal
    // control since new pgrps take terminal control away from commands upstream in a different pgrp.
    // We try to work around this with a heuristic to determine whether to reuse the same pgrp as the
    // last-spawned pgrp if part of an existing job pipeline (keeping in mind that new jobs are
    // recursively started for both foreground and background jobs, and that a function can expand
    // to more than one external command, one (or more?) of which may want to read from upstream or
    // write to downstream of a pipe.
    // By keeping track of (select) "jobs in flight" we can try to marshall newly-created external
    // processes into existing pgrps. Fixes #3952.
    // This is a HACK and the correct solution would be to pass the active job through the pipeline
    // to the newly established parser context so that the funtion as parsed and evaluated can be
    // directly associated with this job and not a new one, BUT sometimes functions NEED to start a
    // new job. This HACK seeks a compromise by letting functions trigger the unilateral creation of
    // a new job, but reusing the "parent" job's existing pgrp in case of terminal control.
    static std::stack<decltype(j)> active_jobs;
    // There's an assumption that there's a one-to-one mapping between jobs under job control and
    // pgrps. When we share a parent job's pgrp, we risk reaping its processes before it is fully
    // constructed, causing later setpgrp(2) calls to fails (#5219). While the parent job is still
    // under construction, child jobs have job_flag_t::WAIT_BY_PROCESS set to prevent early repaing.
    // We store them here until the parent job is constructed, at which point it unsets this flag.
    static std::stack<decltype(j)> child_jobs;

    auto parent_job = active_jobs.empty() ? nullptr : active_jobs.top();
    bool job_pushed = false;
    if (j->get_flag(job_flag_t::TERMINAL) && j->get_flag(job_flag_t::JOB_CONTROL)) {
        // This will be popped before this job leaves exec_job
        active_jobs.push(j);
        job_pushed = true;
    }

    if (parent_job && j->processes.front()->type == EXTERNAL) {
        if (parent_job->pgid != INVALID_PID) {
            j->pgid = parent_job->pgid;
            j->set_flag(job_flag_t::JOB_CONTROL, true);
            j->set_flag(job_flag_t::NESTED, true);
            j->set_flag(job_flag_t::WAIT_BY_PROCESS, true);
            child_jobs.push(j);
        }
    }

    // Verify that all IO_BUFFERs are output. We used to support a (single, hacked-in) magical input
    // IO_BUFFER used by fish_pager, but now the claim is that there are no more clients and it is
    // removed. This assertion double-checks that.
    size_t stdout_read_limit = 0;
    const io_chain_t all_ios = j->all_io_redirections();
    for (size_t idx = 0; idx < all_ios.size(); idx++) {
        const shared_ptr<io_data_t> &io = all_ios.at(idx);

        if ((io->io_mode == IO_BUFFER)) {
            io_buffer_t *io_buffer = static_cast<io_buffer_t *>(io.get());
            assert(!io_buffer->is_input);
            stdout_read_limit = io_buffer->buffer().limit();
        }
    }

    if (j->processes.front()->type == INTERNAL_EXEC) {
        internal_exec(j.get(), std::move(all_ios));
        DIE("this should be unreachable");
    }

    // We may have block IOs that conflict with fd redirections. For example, we may have a command
    // with a redireciton like <&3; we may also have chosen 3 as the fd for our pipe. Ensure we have
    // no conflicts.
    for (const auto io : all_ios) {
        if (io->io_mode == IO_BUFFER) {
            auto *io_buffer = static_cast<io_buffer_t *>(io.get());
            if (!io_buffer->avoid_conflicts_with_io_chain(all_ios)) {
                // We could not avoid conflicts, probably due to fd exhaustion. Mark an error.
                exec_error = true;
                job_mark_process_as_failed(j.get(), j->processes.front().get());
                break;
            }
        }
    }

    // This loop loops over every process_t in the job, starting it as appropriate. This turns out
    // to be rather complex, since a process_t can be one of many rather different things.
    //
    // The loop also has to handle pipelining between the jobs.
    //
    // We can have up to three pipes "in flight" at a time:
    //
    // 1. The pipe the current process should read from (courtesy of the previous process)
    // 2. The pipe that the current process should write to
    // 3. The pipe that the next process should read from (courtesy of us)
    //
    autoclose_fd_t pipe_next_read;
    for (std::unique_ptr<process_t> &unique_p : j->processes) {
        autoclose_fd_t current_read = std::move(pipe_next_read);
        if (!exec_process_in_job(parser, unique_p.get(), j.get(), std::move(current_read),
                                 &pipe_next_read, all_ios, stdout_read_limit)) {
            exec_error = true;
            break;
        }
    }
    pipe_next_read.close();

    debug(3, L"Created job %d from command '%ls' with pgrp %d", j->job_id, j->command_wcstr(),
          j->pgid);

    j->set_flag(job_flag_t::CONSTRUCTED, true);
    if (!j->is_foreground()) {
        proc_last_bg_pid = j->pgid;
        env_set(L"last_pid", ENV_GLOBAL, { to_string(proc_last_bg_pid) });
    }

    if (job_pushed) {
        active_jobs.pop();
    }

    if (!parent_job) {
        // Unset WAIT_BY_PROCESS on all child jobs. We could leave it, but this speeds up the
        // execution of `process_mark_finished_children()`.
        while (!child_jobs.empty()) {
            auto child = child_jobs.top();
            child_jobs.pop();

            child->set_flag(job_flag_t::WAIT_BY_PROCESS, false);
        }
    }

    if (!exec_error) {
        j->continue_job(false);
    } else {
        // Mark the errored job as not in the foreground. I can't fully justify whether this is the
        // right place, but it prevents sanity_lose from complaining.
        j->set_flag(job_flag_t::FOREGROUND, false);
    }
}
Beispiel #13
0
/// Make a copy of the specified io redirection chain, but change file redirection into fd
/// redirection. This makes the redirection chain suitable for use as block-level io, since the file
/// won't be repeatedly reopened for every command in the block, which would reset the cursor
/// position.
///
/// \return true on success, false on failure. Returns the output chain and opened_fds by reference.
static bool io_transmogrify(const io_chain_t &in_chain, io_chain_t *out_chain,
                            std::vector<int> *out_opened_fds) {
    ASSERT_IS_MAIN_THREAD();
    assert(out_chain != NULL && out_opened_fds != NULL);
    assert(out_chain->empty());

    // Just to be clear what we do for an empty chain.
    if (in_chain.empty()) {
        return true;
    }

    bool success = true;

    // Make our chain of redirections.
    io_chain_t result_chain;

    // In the event we can't finish transmorgrifying, we'll have to close all the files we opened.
    std::vector<int> opened_fds;

    for (size_t idx = 0; idx < in_chain.size(); idx++) {
        const shared_ptr<io_data_t> &in = in_chain.at(idx);
        shared_ptr<io_data_t> out;  // gets allocated via new

        switch (in->io_mode) {
            case IO_PIPE:
            case IO_FD:
            case IO_BUFFER:
            case IO_CLOSE: {
                // These redirections don't need transmogrification. They can be passed through.
                out = in;
                break;
            }
            case IO_FILE: {
                // Transmogrify file redirections.
                int fd;
                io_file_t *in_file = static_cast<io_file_t *>(in.get());
                if ((fd = open(in_file->filename_cstr, in_file->flags, OPEN_MASK)) == -1) {
                    debug(1, FILE_ERROR, in_file->filename_cstr);

                    wperror(L"open");
                    success = false;
                    break;
                }

                opened_fds.push_back(fd);
                out.reset(new io_fd_t(in->fd, fd, false));
                break;
            }
        }

        if (out.get() != NULL) result_chain.push_back(out);

        // Don't go any further if we failed.
        if (!success) {
            break;
        }
    }

    // Now either return success, or clean up.
    if (success) {
        *out_chain = std::move(result_chain);
        *out_opened_fds = std::move(opened_fds);
    } else {
        result_chain.clear();
        io_cleanup_fds(opened_fds);
    }
    return success;
}