Exemple #1
0
/// Returns control of the terminal to the shell, and saves the terminal attribute state to the job,
/// so that we can restore the terminal ownership to the job at a later time.
static int terminal_return_from_job(job_t *j) {
    if (tcsetpgrp(STDIN_FILENO, getpgrp()) == -1) {
        if (errno == ENOTTY) redirect_tty_output();
        debug(1, _(L"Could not return shell to foreground"));
        wperror(L"tcsetpgrp");
        return 0;
    }

    // Save jobs terminal modes.
    if (tcgetattr(STDIN_FILENO, &j->tmodes)) {
        if (errno == ENOTTY) redirect_tty_output();
        debug(1, _(L"Could not return shell to foreground"));
        wperror(L"tcgetattr");
        return 0;
    }

// Disabling this per
// https://github.com/adityagodbole/fish-shell/commit/9d229cd18c3e5c25a8bd37e9ddd3b67ddc2d1b72 On
// Linux, 'cd . ; ftp' prevents you from typing into the ftp prompt. See
// https://github.com/fish-shell/fish-shell/issues/121
#if 0
    // Restore the shell's terminal modes.
    if (tcsetattr(STDIN_FILENO, TCSADRAIN, &shell_modes)) {
        debug(1, _(L"Could not return shell to foreground"));
        wperror(L"tcsetattr");
        return 0;
    }
#endif

    return 1;
}
Exemple #2
0
/// Give ownership of the terminal to the specified job.
///
/// \param j The job to give the terminal to.
/// \param cont If this variable is set, we are giving back control to a job that has previously
/// been stopped. In that case, we need to set the terminal attributes to those saved in the job.
static bool terminal_give_to_job(job_t *j, int cont) {
    if (tcsetpgrp(STDIN_FILENO, j->pgid) == -1) {
        if (errno == ENOTTY) redirect_tty_output();
        debug(1, _(L"Could not send job %d ('%ls') to foreground"), j->job_id, j->command_wcstr());
        wperror(L"tcsetpgrp");
        return false;
    }

    if (cont && tcsetattr(STDIN_FILENO, TCSADRAIN, &j->tmodes)) {
        if (errno == ENOTTY) redirect_tty_output();
        debug(1, _(L"Could not send job %d ('%ls') to foreground"), j->job_id, j->command_wcstr());
        wperror(L"tcsetattr");
        return false;
    }
    return true;
}
Exemple #3
0
/// Give ownership of the terminal to the specified job.
///
/// \param j The job to give the terminal to.
/// \param cont If this variable is set, we are giving back control to a job that has previously
/// been stopped. In that case, we need to set the terminal attributes to those saved in the job.
bool terminal_give_to_job(job_t *j, int cont) {
    errno = 0;
    if (j->pgid == 0) {
        debug(2, "terminal_give_to_job() returning early due to no process group");
        return true;
    }

    signal_block();

    // It may not be safe to call tcsetpgrp if we've already done so, as at that point we are no
    // longer the controlling process group for the terminal and no longer have permission to set
    // the process group that is in control, causing tcsetpgrp to return EPERM, even though that's
    // not the documented behavior in tcsetpgrp(3), which instead says other bad things will happen
    // (it says SIGTTOU will be sent to all members of the background *calling* process group, but
    // it's more complicated than that, SIGTTOU may or may not be sent depending on the TTY
    // configuration and whether or not signal handlers for SIGTTOU are installed. Read:
    // http://curiousthing.org/sigttin-sigttou-deep-dive-linux In all cases, our goal here was just
    // to hand over control of the terminal to this process group, which is a no-op if it's already
    // been done.
    if (tcgetpgrp(STDIN_FILENO) == j->pgid) {
        debug(4, L"Process group %d already has control of terminal\n", j->pgid);
    } else {
        debug(4,
              L"Attempting to bring process group to foreground via tcsetpgrp for job->pgid %d\n",
              j->pgid);

        // The tcsetpgrp(2) man page says that EPERM is thrown if "pgrp has a supported value, but
        // is not the process group ID of a process in the same session as the calling process."
        // Since we _guarantee_ that this isn't the case (the child calls setpgid before it calls
        // SIGSTOP, and the child was created in the same session as us), it seems that EPERM is
        // being thrown because of an caching issue - the call to tcsetpgrp isn't seeing the
        // newly-created process group just yet. On this developer's test machine (WSL running Linux
        // 4.4.0), EPERM does indeed disappear on retry. The important thing is that we can
        // guarantee the process isn't going to exit while we wait (which would cause us to possibly
        // block indefinitely).
        while (tcsetpgrp(STDIN_FILENO, j->pgid) != 0) {
            bool pgroup_terminated = false;
            if (errno == EINTR) {
                ;  // Always retry on EINTR, see comments in tcsetattr EINTR code below.
            } else if (errno == EINVAL) {
                // OS X returns EINVAL if the process group no longer lives. Probably other OSes,
                // too. Unlike EPERM below, EINVAL can only happen if the process group has
                // terminated.
                pgroup_terminated = true;
            } else if (errno == EPERM) {
                // Retry so long as this isn't because the process group is dead.
                int wait_result = waitpid(-1 * j->pgid, &wait_result, WNOHANG);
                if (wait_result == -1) {
                    // Note that -1 is technically an "error" for waitpid in the sense that an
                    // invalid argument was specified because no such process group exists any
                    // longer. This is the observed behavior on Linux 4.4.0. a "success" result
                    // would mean processes from the group still exist but is still running in some
                    // state or the other.
                    pgroup_terminated = true;
                } else {
                    // Debug the original tcsetpgrp error (not the waitpid errno) to the log, and
                    // then retry until not EPERM or the process group has exited.
                    debug(2, L"terminal_give_to_job(): EPERM.\n", j->pgid);
                }
            } else {
                if (errno == ENOTTY) redirect_tty_output();
                debug(1, _(L"Could not send job %d ('%ls') with pgid %d to foreground"), j->job_id,
                      j->command_wcstr(), j->pgid);
                wperror(L"tcsetpgrp");
                signal_unblock();
                return false;
            }

            if (pgroup_terminated) {
                // All processes in the process group has exited. Since we force all child procs to
                // SIGSTOP on startup, the only way that can happen is if the very last process in
                // the group terminated, and didn't need to access the terminal, otherwise it would
                // have hung waiting for terminal IO (SIGTTIN). We can ignore this.
                debug(3, L"tcsetpgrp called but process group %d has terminated.\n", j->pgid);
                break;
            }
        }
    }

    if (cont) {
        int result = -1;
        // TODO: Remove this EINTR loop since we have blocked all signals and thus cannot be
        // interrupted. I'm leaving it in place because all of the logic involving controlling
        // terminal management is more than a little opaque and smacks of voodoo programming.
        errno = EINTR;
        while (result == -1 && errno == EINTR) {
            result = tcsetattr(STDIN_FILENO, TCSADRAIN, &j->tmodes);
        }
        if (result == -1) {
            if (errno == ENOTTY) redirect_tty_output();
            debug(1, _(L"Could not send job %d ('%ls') to foreground"), j->job_id,
                  j->command_wcstr());
            wperror(L"tcsetattr");
            signal_unblock();
            return false;
        }
    }

    signal_unblock();
    return true;
}
Exemple #4
0
/// Handle output from a builtin, by printing the contents of builtin_io_streams to the redirections
/// given in io_chain.
static bool handle_builtin_output(job_t *j, process_t *p, io_chain_t *io_chain,
                                  const io_streams_t &builtin_io_streams) {
    assert(p->type == INTERNAL_BUILTIN && "Process is not a builtin");
    // Handle output from builtin commands. In the general case, this means forking of a
    // worker process, that will write out the contents of the stdout and stderr buffers
    // to the correct file descriptor. Since forking is expensive, fish tries to avoid
    // it when possible.
    bool fork_was_skipped = false;

    const shared_ptr<io_data_t> stdout_io = io_chain->get_io_for_fd(STDOUT_FILENO);
    const shared_ptr<io_data_t> stderr_io = io_chain->get_io_for_fd(STDERR_FILENO);

    const output_stream_t &stdout_stream = builtin_io_streams.out;
    const output_stream_t &stderr_stream = builtin_io_streams.err;

    // If we are outputting to a file, we have to actually do it, even if we have no
    // output, so that we can truncate the file. Does not apply to /dev/null.
    bool must_fork = redirection_is_to_real_file(stdout_io.get()) ||
                     redirection_is_to_real_file(stderr_io.get());
    if (!must_fork && p->is_last_in_job) {
        // We are handling reads directly in the main loop. Note that we may still end
        // up forking.
        const bool stdout_is_to_buffer = stdout_io && stdout_io->io_mode == IO_BUFFER;
        const bool no_stdout_output = stdout_stream.empty();
        const bool no_stderr_output = stderr_stream.empty();
        const bool stdout_discarded = stdout_stream.buffer().discarded();

        if (!stdout_discarded && no_stdout_output && no_stderr_output) {
            // The builtin produced no output and is not inside of a pipeline. No
            // need to fork or even output anything.
            debug(4, L"Skipping fork: no output for internal builtin '%ls'", p->argv0());
            fork_was_skipped = true;
        } else if (no_stderr_output && stdout_is_to_buffer) {
            // The builtin produced no stderr, and its stdout is going to an
            // internal buffer. There is no need to fork. This helps out the
            // performance quite a bit in complex completion code.
            // TODO: we're sloppy about handling explicitly separated output.
            // Theoretically we could have explicitly separated output on stdout and
            // also stderr output; in that case we ought to thread the exp-sep output
            // through to the io buffer. We're getting away with this because the only
            // thing that can output exp-sep output is `string split0` which doesn't
            // also produce stderr.
            debug(4, L"Skipping fork: buffered output for internal builtin '%ls'", p->argv0());

            io_buffer_t *io_buffer = static_cast<io_buffer_t *>(stdout_io.get());
            io_buffer->append_from_stream(stdout_stream);
            fork_was_skipped = true;
        } else if (stdout_io.get() == NULL && stderr_io.get() == NULL) {
            // We are writing to normal stdout and stderr. Just do it - no need to fork.
            debug(4, L"Skipping fork: ordinary output for internal builtin '%ls'", p->argv0());
            const std::string outbuff = wcs2string(stdout_stream.contents());
            const std::string errbuff = wcs2string(stderr_stream.contents());
            bool builtin_io_done =
                do_builtin_io(outbuff.data(), outbuff.size(), errbuff.data(), errbuff.size());
            if (!builtin_io_done && errno != EPIPE) {
                redirect_tty_output();  // workaround glibc bug
                debug(0, "!builtin_io_done and errno != EPIPE");
                show_stackframe(L'E');
            }
            if (stdout_discarded) p->status = STATUS_READ_TOO_MUCH;
            fork_was_skipped = true;
        }
    }

    if (fork_was_skipped) {
        p->completed = 1;
        if (p->is_last_in_job) {
            debug(4, L"Set status of job %d (%ls) to %d using short circuit", j->job_id,
                  j->preview().c_str(), p->status);

            int status = p->status;
            proc_set_last_status(j->get_flag(job_flag_t::NEGATE) ? (!status) : status);
        }
    } else {
        // Ok, unfortunately, we have to do a real fork. Bummer. We work hard to make
        // sure we don't have to wait for all our threads to exit, by arranging things
        // so that we don't have to allocate memory or do anything except system calls
        // in the child.
        //
        // These strings may contain embedded nulls, so don't treat them as C strings.
        const std::string outbuff_str = wcs2string(stdout_stream.contents());
        const char *outbuff = outbuff_str.data();
        size_t outbuff_len = outbuff_str.size();

        const std::string errbuff_str = wcs2string(stderr_stream.contents());
        const char *errbuff = errbuff_str.data();
        size_t errbuff_len = errbuff_str.size();

        fflush(stdout);
        fflush(stderr);
        if (!fork_child_for_process(j, p, *io_chain, false, "internal builtin", [&] {
                do_builtin_io(outbuff, outbuff_len, errbuff, errbuff_len);
                exit_without_destructors(p->status);
            })) {
            return false;
        }
    }
    return true;
}