fish-shell/src/exec.rs

1501 lines
54 KiB
Rust
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Functions for executing a program.
//
// Some of the code in this file is based on code from the Glibc manual, though the changes
// performed have been massive.
use crate::builtins::shared::{
builtin_run, STATUS_CMD_ERROR, STATUS_CMD_OK, STATUS_CMD_UNKNOWN, STATUS_NOT_EXECUTABLE,
STATUS_READ_TOO_MUCH,
};
use crate::common::{
exit_without_destructors, scoped_push_replacer, str2wcstring, truncate_at_nul, wcs2string,
wcs2zstring, write_loop, ScopeGuard,
};
use crate::env::{EnvMode, EnvStack, Environment, Statuses, READ_BYTE_LIMIT};
use crate::env_dispatch::use_posix_spawn;
use crate::fds::make_fd_blocking;
use crate::fds::{make_autoclose_pipes, open_cloexec, PIPE_ERROR};
use crate::flog::{FLOG, FLOGF};
use crate::fork_exec::blocked_signals_for_job;
use crate::fork_exec::postfork::{
child_setup_process, execute_fork, execute_setpgid, report_setpgid_error,
safe_report_exec_error,
};
#[cfg(FISH_USE_POSIX_SPAWN)]
use crate::fork_exec::spawn::PosixSpawner;
use crate::function::{self, FunctionProperties};
use crate::io::{
BufferedOutputStream, FdOutputStream, IoBufferfill, IoChain, IoClose, IoMode, IoPipe,
IoStreams, OutputStream, SeparatedBuffer, StringOutputStream,
};
use crate::libc::_PATH_BSHELL;
use crate::nix::isatty;
use crate::null_terminated_array::{
null_terminated_array_length, AsNullTerminatedArray, OwningNullTerminatedArray,
};
use crate::parser::{Block, BlockId, BlockType, EvalRes, Parser};
use crate::proc::{
hup_jobs, is_interactive_session, jobs_requiring_warning_on_exit, no_exec,
print_exit_warning_for_jobs, InternalProc, Job, JobGroupRef, ProcStatus, Process, ProcessType,
TtyTransfer, INVALID_PID,
};
use crate::reader::{reader_run_count, restore_term_mode};
use crate::redirection::{dup2_list_resolve_chain, Dup2List};
use crate::threads::{iothread_perform_cant_wait, is_forked_child};
use crate::trace::trace_if_enabled_with_args;
use crate::wchar::{wstr, WString, L};
use crate::wchar_ext::ToWString;
use crate::wutil::{fish_wcstol, perror};
use crate::wutil::{wgettext, wgettext_fmt};
use errno::{errno, set_errno};
use libc::{
c_char, EACCES, ENOENT, ENOEXEC, ENOTDIR, EPIPE, EXIT_FAILURE, EXIT_SUCCESS, STDERR_FILENO,
STDIN_FILENO, STDOUT_FILENO,
};
use nix::fcntl::OFlag;
use nix::sys::stat;
use std::ffi::CStr;
use std::io::{Read, Write};
use std::os::fd::{AsRawFd, OwnedFd, RawFd};
use std::slice;
use std::sync::atomic::Ordering;
use std::sync::{atomic::AtomicUsize, Arc};
/// Execute the processes specified by \p j in the parser \p.
/// On a true return, the job was successfully launched and the parser will take responsibility for
/// cleaning it up. On a false return, the job could not be launched and the caller must clean it
/// up.
pub fn exec_job(parser: &Parser, job: &Job, block_io: IoChain) -> bool {
// If fish was invoked with -n or --no-execute, then no_exec will be set and we do nothing.
if no_exec() {
return true;
}
// Handle an exec call.
if job.processes()[0].typ == ProcessType::exec {
// If we are interactive, perhaps disallow exec if there are background jobs.
if !allow_exec_with_background_jobs(parser) {
for p in job.processes().iter() {
p.mark_aborted_before_launch();
}
return false;
}
// Apply foo=bar variable assignments
for assignment in &job.processes()[0].variable_assignments {
parser.vars().set(
&assignment.variable_name,
EnvMode::LOCAL | EnvMode::EXPORT,
assignment.values.clone(),
);
}
internal_exec(parser.vars(), job, block_io);
// internal_exec only returns if it failed to set up redirections.
// In case of an successful exec, this code is not reached.
let status = if job.flags().negate { 0 } else { 1 };
parser.set_last_statuses(Statuses::just(status));
// A false return tells the caller to remove the job from the list.
for p in job.processes().iter() {
p.mark_aborted_before_launch();
}
return false;
}
// Get the deferred process, if any. We will have to remember its pipes.
let mut deferred_pipes = PartialPipes::default();
let deferred_process = get_deferred_process(job);
// We may want to transfer tty ownership to the pgroup leader.
let mut transfer = TtyTransfer::new();
// 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)
//
// Lastly, a process may experience a pipeline-aborting error, which prevents launching
// further processes in the pipeline.
let mut pipe_next_read: Option<OwnedFd> = None;
let mut aborted_pipeline = false;
let mut procs_launched = 0;
for i in 0..job.processes().len() {
let p = &job.processes()[i];
// proc_pipes is the pipes applied to this process. That is, it is the read end
// containing the output of the previous process (if any), plus the write end that will
// output to the next process (if any).
let mut proc_pipes = PartialPipes::default();
std::mem::swap(&mut proc_pipes.read, &mut pipe_next_read);
if !p.is_last_in_job {
let Ok(pipes) = make_autoclose_pipes() else {
FLOG!(warning, wgettext!(PIPE_ERROR));
aborted_pipeline = true;
abort_pipeline_from(job, i);
break;
};
pipe_next_read = Some(pipes.read);
proc_pipes.write = Some(pipes.write);
// Save any deferred process for last. By definition, the deferred process can
// never be the last process in the job, so it's safe to nest this in the outer
// `if (!p->is_last_in_job)` block, which makes it clear that `proc_next_read` will
// always be assigned when we `continue` the loop.
if Some(i) == deferred_process {
deferred_pipes = proc_pipes;
continue;
}
}
// Regular process.
if exec_process_in_job(
parser,
p,
job,
block_io.clone(),
proc_pipes,
&deferred_pipes,
false,
)
.is_err()
{
aborted_pipeline = true;
abort_pipeline_from(job, i);
break;
}
procs_launched += 1;
// Transfer tty?
if p.leads_pgrp && job.group().wants_terminal() {
transfer.to_job_group(job.group.as_ref().unwrap());
}
}
drop(pipe_next_read);
// If our pipeline was aborted before any process was successfully launched, then there is
// nothing to reap, and we can perform an early return.
// Note we must never return false if we have launched even one process, since it will not be
// properly reaped; see #7038.
if aborted_pipeline && procs_launched == 0 {
return false;
}
// Ok, at least one thing got launched.
// Handle any deferred process.
if let Some(dp) = deferred_process {
if
// Some other process already aborted our pipeline.
aborted_pipeline
// The deferred proc itself failed to launch.
|| exec_process_in_job(
parser,
&job.processes()[dp],
job,
block_io,
deferred_pipes,
&PartialPipes::default(),
true,
)
.is_err()
{
job.processes()[dp].mark_aborted_before_launch();
}
}
FLOGF!(
exec_job_exec,
"Executed job %d from command '%ls'",
job.job_id(),
job.command()
);
job.mark_constructed();
// If exec_error then a backgrounded job would have been terminated before it was ever assigned
// a pgroup, so error out before setting last_pid.
if !job.is_foreground() {
if let Some(last_pid) = job.get_last_pid() {
parser
.vars()
.set_one(L!("last_pid"), EnvMode::GLOBAL, last_pid.to_wstring());
} else {
parser.vars().set_empty(L!("last_pid"), EnvMode::GLOBAL);
}
}
if !job.is_initially_background() {
job.continue_job(parser);
}
if job.is_stopped() {
transfer.save_tty_modes();
}
transfer.reclaim();
true
}
/// Evaluate a command.
///
/// \param cmd the command to execute
/// \param parser the parser with which to execute code
/// \param outputs if set, the list to insert output into.
/// \param apply_exit_status if set, update $status within the parser, otherwise do not.
///
/// \return a value appropriate for populating $status.
pub fn exec_subshell(
cmd: &wstr,
parser: &Parser,
outputs: Option<&mut Vec<WString>>,
apply_exit_status: bool,
) -> libc::c_int {
let mut break_expand = false;
exec_subshell_internal(
cmd,
parser,
None,
outputs,
&mut break_expand,
apply_exit_status,
false,
)
}
/// Like exec_subshell, but only returns expansion-breaking errors. That is, a zero return means
/// "success" (even though the command may have failed), a non-zero return means that we should
/// halt expansion. If the \p pgid is supplied, then any spawned external commands should join that
/// pgroup.
pub fn exec_subshell_for_expand(
cmd: &wstr,
parser: &Parser,
job_group: Option<&JobGroupRef>,
outputs: &mut Vec<WString>,
) -> libc::c_int {
parser.assert_can_execute();
let mut break_expand = true;
let ret = exec_subshell_internal(
cmd,
parser,
job_group,
Some(outputs),
&mut break_expand,
true,
true,
);
// Only return an error code if we should break expansion.
if break_expand {
ret
} else {
STATUS_CMD_OK.unwrap()
}
}
/// Number of calls to fork() or posix_spawn().
static FORK_COUNT: AtomicUsize = AtomicUsize::new(0);
/// A launch_result_t indicates when a process failed to launch, and therefore the rest of the
/// pipeline should be aborted. This includes failed redirections, fd exhaustion, fork() failures,
/// etc.
type LaunchResult = Result<(), ()>;
/// Given an error \p err returned from either posix_spawn or exec, \return a process exit code.
fn exit_code_from_exec_error(err: libc::c_int) -> libc::c_int {
assert!(err != 0, "Zero is success, not an error");
match err {
ENOENT | ENOTDIR => {
// This indicates either the command was not found, or a file redirection was not found.
// We do not use posix_spawn file redirections so this is always command-not-found.
STATUS_CMD_UNKNOWN.unwrap()
}
EACCES | ENOEXEC => {
// The file is not executable for various reasons.
STATUS_NOT_EXECUTABLE.unwrap()
}
#[cfg(target_os = "macos")]
libc::EBADARCH => {
// This is for e.g. running ARM app on Intel Mac.
STATUS_NOT_EXECUTABLE.unwrap()
}
_ => {
// Generic failure.
EXIT_FAILURE
}
}
}
/// This is a 'looks like text' check.
/// \return true if either there is no NUL byte, or there is a line containing a lowercase letter
/// before the first NUL byte.
fn is_thompson_shell_payload(p: &[u8]) -> bool {
if !p.contains(&b'\0') {
return true;
};
let mut haslower = false;
for c in p {
if c.is_ascii_lowercase() || *c == b'$' || *c == b'`' {
haslower = true;
}
if haslower && *c == b'\n' {
return true;
}
}
false
}
/// This function checks the beginning of a file to see if it's safe to
/// pass to the system interpreter when execve() returns ENOEXEC.
///
/// The motivation is to be able to run classic shell scripts which
/// didn't have shebang, while protecting the user from accidentally
/// running a binary file which may corrupt terminal driver state. We
/// check for lowercase letters because the ASCII magic of binary files
/// is usually uppercase, e.g. PNG, JFIF, MZ, etc. These rules are also
/// flexible enough to permit scripts with concatenated binary content,
/// such as Actually Portable Executable.
/// N.B.: this is called after fork, it must not allocate heap memory.
pub fn is_thompson_shell_script(path: &CStr) -> bool {
// Paths ending in ".fish" are never considered Thompson shell scripts.
if path.to_bytes().ends_with(".fish".as_bytes()) {
return false;
}
let e = errno();
let mut res = false;
if let Ok(mut file) = open_cloexec(path, OFlag::O_RDONLY | OFlag::O_NOCTTY, stat::Mode::empty())
{
let mut buf = [b'\0'; 256];
if let Ok(got) = file.read(&mut buf) {
if is_thompson_shell_payload(&buf[..got]) {
res = true;
}
}
}
set_errno(e);
res
}
/// This function is executed by the child process created by a call to fork(). It should be called
/// after \c child_setup_process. It calls execve to replace the fish process image with the command
/// specified in \c p. It never returns. Called in a forked child! Do not allocate memory, etc.
fn safe_launch_process(
_p: &Process,
actual_cmd: &CStr,
argv: &impl AsNullTerminatedArray<CharType = c_char>,
envv: &impl AsNullTerminatedArray<CharType = c_char>,
) -> ! {
// This function never returns, so we take certain liberties with constness.
unsafe { libc::execve(actual_cmd.as_ptr(), argv.get(), envv.get()) };
let err = errno();
// The shebang wasn't introduced until UNIX Seventh Edition, so if
// the kernel won't run the binary we hand it off to the interpreter
// after performing a binary safety check, recommended by POSIX: a
// line needs to exist before the first \0 with a lowercase letter
if err.0 == ENOEXEC && is_thompson_shell_script(actual_cmd) {
// Construct new argv.
// We must not allocate memory, so only 128 args are supported.
const maxargs: usize = 128;
let nargs = null_terminated_array_length(argv.get());
let argv = unsafe { slice::from_raw_parts(argv.get(), nargs) };
if nargs <= maxargs {
// +1 for /bin/sh, +1 for terminating nullptr
let mut argv2 = [std::ptr::null(); 1 + maxargs + 1];
argv2[0] = _PATH_BSHELL.load(Ordering::Relaxed);
argv2[1..argv.len() + 1].copy_from_slice(argv);
// The command to call should use the full path,
// not what we would pass as argv0.
argv2[1] = actual_cmd.as_ptr();
unsafe {
libc::execve(_PATH_BSHELL.load(Ordering::Relaxed), &argv2[0], envv.get());
}
}
}
set_errno(err);
safe_report_exec_error(errno().0, actual_cmd, argv.get(), envv.get());
exit_without_destructors(exit_code_from_exec_error(err.0));
}
/// This function is similar to launch_process, except it is not called after a fork (i.e. it only
/// calls exec) and therefore it can allocate memory.
fn launch_process_nofork(vars: &EnvStack, p: &Process) -> ! {
assert!(!is_forked_child());
// Construct argv. Ensure the strings stay alive for the duration of this function.
let narrow_strings = p.argv().iter().map(|s| wcs2zstring(s)).collect();
let argv = OwningNullTerminatedArray::new(narrow_strings);
// Construct envp.
let envp = vars.export_array();
let actual_cmd = wcs2zstring(&p.actual_cmd);
// Ensure the terminal modes are what they were before we changed them.
restore_term_mode();
// Bounce to launch_process. This never returns.
safe_launch_process(p, &actual_cmd, &argv, &*envp);
}
// Returns whether we can use posix spawn for a given process in a given job.
//
// To avoid the race between the caller calling tcsetpgrp() and the client checking the
// foreground process group, we don't use posix_spawn if we're going to foreground the process. (If
// we use fork(), we can call tcsetpgrp after the fork, before the exec, and avoid the race).
fn can_use_posix_spawn_for_job(job: &Job, dup2s: &Dup2List) -> bool {
// Is it globally disabled?
if !use_posix_spawn() {
return false;
}
// Hack - do not use posix_spawn if there are self-fd redirections.
// For example if you were to write:
// cmd 6< /dev/null
// it is possible that the open() of /dev/null would result in fd 6. Here even if we attempted
// to add a dup2 action, it would be ignored and the CLO_EXEC bit would remain. So don't use
// posix_spawn in this case; instead we'll call fork() and clear the CLO_EXEC bit manually.
for action in dup2s.get_actions() {
if action.src == action.target {
return false;
}
}
// If this job will be foregrounded, we will call tcsetpgrp(), therefore do not use
// posix_spawn.
let wants_terminal = job.group().wants_terminal();
!wants_terminal
}
fn internal_exec(vars: &EnvStack, j: &Job, block_io: IoChain) {
// Do a regular launch - but without forking first...
let mut all_ios = block_io;
if !all_ios.append_from_specs(j.processes()[0].redirection_specs(), &vars.get_pwd_slash()) {
return;
}
let mut blocked_signals: libc::sigset_t = unsafe { std::mem::zeroed() };
unsafe { libc::sigemptyset(&mut blocked_signals) };
let blocked_signals = if blocked_signals_for_job(j, &mut blocked_signals) {
Some(&blocked_signals)
} else {
None
};
// child_setup_process makes sure signals are properly set up.
let redirs = dup2_list_resolve_chain(&all_ios);
if child_setup_process(
0, /* not claim_tty */
blocked_signals,
false, /* not is_forked */
&redirs,
) == 0
{
// Decrement SHLVL as we're removing ourselves from the shell "stack".
if is_interactive_session() {
let shlvl_var = vars.getf(L!("SHLVL"), EnvMode::GLOBAL | EnvMode::EXPORT);
let mut shlvl_str = L!("0").to_owned();
if let Some(shlvl_var) = shlvl_var {
if let Ok(shlvl) = fish_wcstol(&shlvl_var.as_string()) {
if shlvl > 0 {
shlvl_str = (shlvl - 1).to_wstring();
}
}
}
vars.set_one(L!("SHLVL"), EnvMode::GLOBAL | EnvMode::EXPORT, shlvl_str);
}
// launch_process _never_ returns.
launch_process_nofork(vars, &j.processes()[0]);
}
}
/// Construct an internal process for the process p. In the background, write the data \p outdata to
/// stdout and \p errdata to stderr, respecting the io chain \p ios. For example if target_fd is 1
/// (stdout), and there is a dup2 3->1, then we need to write to fd 3. Then exit the internal
/// process.
fn run_internal_process(p: &Process, outdata: Vec<u8>, errdata: Vec<u8>, ios: &IoChain) {
p.check_generations_before_launch();
// We want both the dup2s and the io_chain_ts to be kept alive by the background thread, because
// they may own an fd that we want to write to. Move them all to a shared_ptr. The strings as
// well (they may be long).
// Construct a little helper struct to make it simpler to move into our closure without copying.
struct WriteFields {
src_outfd: RawFd,
outdata: Vec<u8>,
src_errfd: RawFd,
errdata: Vec<u8>,
ios: IoChain,
dup2s: Dup2List,
internal_proc: Arc<InternalProc>,
success_status: ProcStatus,
}
impl WriteFields {
fn skip_out(&self) -> bool {
self.outdata.is_empty() || self.src_outfd < 0
}
fn skip_err(&self) -> bool {
self.errdata.is_empty() || self.src_errfd < 0
}
}
// Construct and assign the internal process to the real process.
let internal_proc = Arc::new(InternalProc::new());
let old = p.internal_proc.replace(Some(internal_proc.clone()));
assert!(
old.is_none(),
"Replaced p.internal_proc, but it already had a value!"
);
let mut f = Box::new(WriteFields {
src_outfd: -1,
outdata,
src_errfd: -1,
errdata,
ios: IoChain::default(),
dup2s: Dup2List::new(),
internal_proc: internal_proc.clone(),
success_status: ProcStatus::default(),
});
FLOGF!(
proc_internal_proc,
"Created internal proc %llu to write output for proc '%ls'",
internal_proc.get_id(),
p.argv0().unwrap()
);
// Resolve the IO chain.
// Note it's important we do this even if we have no out or err data, because we may have been
// asked to truncate a file (e.g. `echo -n '' > /tmp/truncateme.txt'). The open() in the dup2
// list resolution will ensure this happens.
f.dup2s = dup2_list_resolve_chain(ios);
// Figure out which source fds to write to. If they are closed (unlikely) we just exit
// successfully.
f.src_outfd = f.dup2s.fd_for_target_fd(STDOUT_FILENO);
f.src_errfd = f.dup2s.fd_for_target_fd(STDERR_FILENO);
// If we have nothing to write we can elide the thread.
// TODO: support eliding output to /dev/null.
if f.skip_out() && f.skip_err() {
internal_proc.mark_exited(&p.status);
return;
}
// Ensure that ios stays alive, it may own fds.
f.ios = ios.clone();
// If our process is a builtin, it will have already set its status value. Make sure we
// propagate that if our I/O succeeds and don't read it on a background thread. TODO: have
// builtin_run provide this directly, rather than setting it in the process.
f.success_status = p.status.clone();
iothread_perform_cant_wait(move || {
let mut status = f.success_status.clone();
if !f.skip_out() {
if let Err(err) = write_loop(&f.src_outfd, &f.outdata) {
if err.raw_os_error().unwrap() != EPIPE {
perror("write");
}
if status.is_success() {
status = ProcStatus::from_exit_code(1);
}
}
}
if !f.skip_err() {
if let Err(err) = write_loop(&f.src_errfd, &f.errdata) {
if err.raw_os_error().unwrap() != EPIPE {
perror("write");
}
if status.is_success() {
status = ProcStatus::from_exit_code(1);
}
}
}
f.internal_proc.mark_exited(&status);
});
}
/// If \p outdata or \p errdata are both empty, then mark the process as completed immediately.
/// Otherwise, run an internal process.
fn run_internal_process_or_short_circuit(
parser: &Parser,
j: &Job,
p: &Process,
outdata: Vec<u8>,
errdata: Vec<u8>,
ios: &IoChain,
) {
if outdata.is_empty() && errdata.is_empty() {
p.completed.store(true);
if p.is_last_in_job {
FLOGF!(
exec_job_status,
"Set status of job %d (%ls) to %d using short circuit",
j.job_id(),
j.preview(),
p.status.status_value()
);
if let Some(statuses) = j.get_statuses() {
parser.set_last_statuses(statuses);
parser.libdata_mut().pods.status_count += 1;
} else if j.flags().negate {
// Special handling for `not set var (substitution)`.
// If there is no status, but negation was requested,
// take the last status and negate it.
let mut last_statuses = parser.get_last_statuses();
last_statuses.status = if last_statuses.status == 0 { 1 } else { 0 };
parser.set_last_statuses(last_statuses);
}
}
} else {
run_internal_process(p, outdata, errdata, ios);
}
}
/// Call fork() as part of executing a process \p p in a job \j. Execute \p child_action in the
/// context of the child.
fn fork_child_for_process(
job: &Job,
p: &Process,
dup2s: &Dup2List,
fork_type: &wstr,
child_action: impl FnOnce(&Process),
) -> LaunchResult {
// Claim the tty from fish, if the job wants it and we are the pgroup leader.
let claim_tty_from = if p.leads_pgrp && job.group().wants_terminal() {
unsafe { libc::getpgrp() }
} else {
INVALID_PID
};
// Decide if the job wants to set a custom sigmask.
let mut blocked_signals: libc::sigset_t = unsafe { std::mem::zeroed() };
unsafe { libc::sigemptyset(&mut blocked_signals) };
let blocked_signals = if blocked_signals_for_job(job, &mut blocked_signals) {
Some(&blocked_signals)
} else {
None
};
// Narrow the command name for error reporting before fork,
// to avoid allocations in the forked child.
let narrow_cmd = wcs2zstring(job.command());
let narrow_argv0 = wcs2zstring(p.argv0().unwrap_or_default());
let pid = execute_fork();
if pid < 0 {
return Err(());
}
let is_parent = pid > 0;
// Record the pgroup if this is the leader.
// Both parent and child attempt to send the process to its new group, to resolve the race.
p.set_pid(if is_parent {
pid
} else {
unsafe { libc::getpid() }
});
if p.leads_pgrp {
job.group().set_pgid(pid);
}
{
if let Some(pgid) = job.group().get_pgid() {
let err = execute_setpgid(p.pid(), pgid, is_parent);
if err != 0 {
report_setpgid_error(
err,
is_parent,
p.pid(),
pgid,
job.job_id().as_num(),
&narrow_cmd,
&narrow_argv0,
)
}
}
}
if !is_parent {
// Child process.
child_setup_process(claim_tty_from, blocked_signals, true, dup2s);
child_action(p);
panic!("Child process returned control to fork_child lambda!");
}
let count = FORK_COUNT.fetch_add(1, Ordering::Relaxed) + 1;
FLOGF!(
exec_fork,
"Fork #%d, pid %d: %s for '%ls'",
count,
pid,
fork_type,
p.argv0().unwrap()
);
Ok(())
}
/// \return an newly allocated output stream for the given fd, which is typically stdout or stderr.
/// This inspects the io_chain and decides what sort of output stream to return.
/// If \p piped_output_needs_buffering is set, and if the output is going to a pipe, then the other
/// end then synchronously writing to the pipe risks deadlock, so we must buffer it.
fn create_output_stream_for_builtin(
fd: RawFd,
io_chain: &IoChain,
piped_output_needs_buffering: bool,
) -> OutputStream {
let Some(io) = io_chain.io_for_fd(fd) else {
// Common case of no redirections.
// Just write to the fd directly.
return OutputStream::Fd(FdOutputStream::new(fd));
};
match io.io_mode() {
IoMode::bufferfill => {
// Our IO redirection is to an internal buffer, e.g. a command substitution.
// We will write directly to it.
let buffer = io.as_bufferfill().unwrap().buffer_ref();
OutputStream::Buffered(BufferedOutputStream::new(buffer.clone()))
}
IoMode::close => {
// Like 'echo foo >&-'
OutputStream::Null
}
IoMode::file => {
// Output is to a file which has been opened.
OutputStream::Fd(FdOutputStream::new(io.source_fd()))
}
IoMode::pipe => {
// Output is to a pipe. We may need to buffer.
if piped_output_needs_buffering {
OutputStream::String(StringOutputStream::new())
} else {
OutputStream::Fd(FdOutputStream::new(io.source_fd()))
}
}
IoMode::fd => {
// This is a case like 'echo foo >&5'
// It's uncommon and unclear what should happen.
OutputStream::String(StringOutputStream::new())
}
}
}
/// Handle output from a builtin, by printing the contents of builtin_io_streams to the redirections
/// given in io_chain.
fn handle_builtin_output(
parser: &Parser,
j: &Job,
p: &Process,
io_chain: &IoChain,
out: &OutputStream,
err: &OutputStream,
) {
assert!(p.typ == ProcessType::builtin, "Process is not a builtin");
// Figure out any data remaining to write. We may have none, in which case we can short-circuit.
let outbuff = wcs2string(out.contents());
let errbuff = wcs2string(err.contents());
// Some historical behavior.
if !outbuff.is_empty() {
let _ = std::io::stdout().flush();
}
if !errbuff.is_empty() {
let _ = std::io::stderr().flush();
}
// Construct and run our background process.
run_internal_process_or_short_circuit(parser, j, p, outbuff, errbuff, io_chain);
}
/// Executes an external command.
/// An error return here indicates that the process failed to launch, and the rest of
/// the pipeline should be cancelled.
fn exec_external_command(
parser: &Parser,
j: &Job,
p: &Process,
proc_io_chain: &IoChain,
) -> LaunchResult {
assert!(p.typ == ProcessType::external, "Process is not external");
// Get argv and envv before we fork.
let narrow_argv = p.argv().iter().map(|s| wcs2zstring(s)).collect();
let argv = OwningNullTerminatedArray::new(narrow_argv);
// Convert our IO chain to a dup2 sequence.
let dup2s = dup2_list_resolve_chain(proc_io_chain);
// Ensure that stdin is blocking before we hand it off (see issue #176).
// Note this will also affect stdout and stderr if they refer to the same tty.
let _ = make_fd_blocking(STDIN_FILENO);
let envv = parser.vars().export_array();
let actual_cmd = wcs2zstring(&p.actual_cmd);
#[cfg(FISH_USE_POSIX_SPAWN)]
// Prefer to use posix_spawn, since it's faster on some systems like OS X.
if can_use_posix_spawn_for_job(j, &dup2s) {
let file = &parser.libdata().current_filename;
let count = FORK_COUNT.fetch_add(1, Ordering::Relaxed) + 1; // spawn counts as a fork+exec
let pid = PosixSpawner::new(j, &dup2s).and_then(|mut spawner| {
spawner.spawn(actual_cmd.as_ptr(), argv.get_mut(), envv.get_mut())
});
let pid = match pid {
Ok(pid) => pid,
Err(err) => {
safe_report_exec_error(err.0, &actual_cmd, argv.get(), envv.get());
p.status
.update(&ProcStatus::from_exit_code(exit_code_from_exec_error(
err.0,
)));
return Err(());
}
};
assert!(pid > 0, "Should have either a valid pid, or an error");
// This usleep can be used to test for various race conditions
// (https://github.com/fish-shell/fish-shell/issues/360).
// usleep(10000);
FLOGF!(
exec_fork,
"Fork #%d, pid %d: spawn external command '%s' from '%ls'",
count,
pid,
p.actual_cmd,
file.as_ref()
.map(|s| s.as_utfstr())
.unwrap_or(L!("<no file>"))
);
// these are all things do_fork() takes care of normally (for forked processes):
p.pid.store(pid, Ordering::Relaxed);
if p.leads_pgrp {
j.group().set_pgid(pid);
// posix_spawn should in principle set the pgid before returning.
// In glibc, posix_spawn uses fork() and the pgid group is set on the child side;
// therefore the parent may not have seen it be set yet.
// Ensure it gets set. See #4715, also https://github.com/Microsoft/WSL/issues/2997.
execute_setpgid(pid, pid, true /* is parent */);
}
return Ok(());
}
fork_child_for_process(j, p, &dup2s, L!("external command"), |p| {
safe_launch_process(p, &actual_cmd, &argv, &*envv)
})
}
// Given that we are about to execute a function, push a function block and set up the
// variable environment.
fn function_prepare_environment(
parser: &Parser,
mut argv: Vec<WString>,
props: &FunctionProperties,
) -> BlockId {
// Extract the function name and remaining arguments.
let mut func_name = WString::new();
if !argv.is_empty() {
// Extract and remove the function name from argv.
func_name = argv.remove(0);
}
let fb = parser.push_block(Block::function_block(
func_name,
argv.clone(),
props.shadow_scope,
));
let vars = parser.vars();
// Setup the environment for the function. There are three components of the environment:
// 1. named arguments
// 2. inherited variables
// 3. argv
for (idx, named_arg) in props.named_arguments.iter().enumerate() {
if idx < argv.len() {
vars.set_one(named_arg, EnvMode::LOCAL | EnvMode::USER, argv[idx].clone());
} else {
vars.set_empty(named_arg, EnvMode::LOCAL | EnvMode::USER);
}
}
for (key, value) in &*props.inherit_vars {
vars.set(key, EnvMode::LOCAL | EnvMode::USER, value.clone());
}
vars.set_argv(argv);
fb
}
// Given that we are done executing a function, restore the environment.
fn function_restore_environment(parser: &Parser, block: BlockId) {
parser.pop_block(block);
// If we returned due to a return statement, then stop returning now.
parser.libdata_mut().pods.returning = false;
}
// The "performer" function of a block or function process.
// This accepts a place to execute as \p parser and then executes the result, returning a status.
// This is factored out in this funny way in preparation for concurrent execution.
type ProcPerformer = dyn FnOnce(
&Parser,
&Process,
Option<&mut OutputStream>,
Option<&mut OutputStream>,
) -> ProcStatus;
// \return a function which may be to run the given process \p.
// May return an empty std::function in the rare case that the to-be called fish function no longer
// exists. This is just a dumb artifact of the fact that we only capture the functions name, not its
// properties, when creating the job; thus a race could delete the function before we fetch its
// properties.
fn get_performer_for_process(
p: &Process,
job: &Job,
io_chain: &IoChain,
) -> Option<Box<ProcPerformer>> {
assert!(
[ProcessType::function, ProcessType::block_node].contains(&p.typ),
"Unexpected process type"
);
// We want to capture the job group.
let job_group = job.group.clone();
let io_chain = io_chain.clone();
if p.typ == ProcessType::block_node {
Some(Box::new(move |parser: &Parser, p: &Process, _out, _err| {
let source = p
.block_node_source
.as_ref()
.expect("Process is missing source info");
let node = p
.internal_block_node
.as_ref()
.expect("Process is missing node info");
parser
.eval_node(
source,
unsafe { node.as_ref() },
&io_chain,
job_group.as_ref(),
BlockType::top,
)
.status
}))
} else {
assert!(p.typ == ProcessType::function);
let Some(props) = function::get_props(p.argv0().unwrap()) else {
FLOG!(
error,
wgettext_fmt!("Unknown function '%ls'", p.argv0().unwrap())
);
return None;
};
Some(Box::new(move |parser: &Parser, p: &Process, _out, _err| {
let argv = p.argv();
// Pull out the job list from the function.
let body = &props.func_node.jobs;
let fb = function_prepare_environment(parser, argv.clone(), &props);
let parsed_source = props.func_node.parsed_source_ref();
let mut res = parser.eval_node(
&parsed_source,
body,
&io_chain,
job_group.as_ref(),
BlockType::top,
);
function_restore_environment(parser, fb);
// If the function did not execute anything, treat it as success.
if res.was_empty {
res = EvalRes::new(ProcStatus::from_exit_code(EXIT_SUCCESS));
}
res.status
}))
}
}
/// Execute a block node or function "process".
/// \p piped_output_needs_buffering if true, buffer the output.
fn exec_block_or_func_process(
parser: &Parser,
j: &Job,
p: &Process,
mut io_chain: IoChain,
piped_output_needs_buffering: bool,
) -> LaunchResult {
// Create an output buffer if we're piping to another process.
let mut block_output_bufferfill = None;
if piped_output_needs_buffering {
// Be careful to handle failure, e.g. too many open fds.
match IoBufferfill::create() {
Ok(tmp) => {
// Teach the job about its bufferfill, and add it to our io chain.
io_chain.push(tmp.clone());
block_output_bufferfill = Some(tmp);
}
Err(_) => return Err(()),
}
}
// Get the process performer, and just execute it directly.
// Do it in this scoped way so that the performer function can be eagerly deallocating releasing
// its captured io chain.
if let Some(performer) = get_performer_for_process(p, j, &io_chain) {
p.status.update(&performer(parser, p, None, None));
} else {
return Err(());
}
// If we have a block output buffer, populate it now.
let mut buffer_contents = vec![];
if let Some(block_output_bufferfill) = block_output_bufferfill {
// Remove our write pipe and forget it. This may close the pipe, unless another thread has
// claimed it (background write) or another process has inherited it.
io_chain.remove(&*block_output_bufferfill);
buffer_contents = IoBufferfill::finish(block_output_bufferfill).newline_serialized();
}
run_internal_process_or_short_circuit(
parser,
j,
p,
buffer_contents,
vec![], /* errdata */
&io_chain,
);
Ok(())
}
fn get_performer_for_builtin(p: &Process, j: &Job, io_chain: &IoChain) -> Box<ProcPerformer> {
assert!(p.typ == ProcessType::builtin, "Process must be a builtin");
// Determine if we have a "direct" redirection for stdin.
let mut stdin_is_directly_redirected = false;
if !p.is_first_in_job {
// We must have a pipe
stdin_is_directly_redirected = true;
} else {
// We are not a pipe. Check if there is a redirection local to the process
// that's not io_mode_t::close.
for redir in p.redirection_specs() {
if redir.fd == STDIN_FILENO && !redir.is_close() {
stdin_is_directly_redirected = true;
break;
}
}
}
// Pull out some fields which we want to copy. We don't want to store the process or job in the
// returned closure.
let argv = p.argv().clone();
let job_group = j.group.clone();
let io_chain = io_chain.clone();
// Be careful to not capture p or j by value, as the intent is that this may be run on another
// thread.
Box::new(
move |parser: &Parser,
_p: &Process,
output_stream: Option<&mut OutputStream>,
errput_stream: Option<&mut OutputStream>| {
let output_stream = output_stream.unwrap();
let errput_stream = errput_stream.unwrap();
let out_io = io_chain.io_for_fd(STDOUT_FILENO);
let err_io = io_chain.io_for_fd(STDERR_FILENO);
// Figure out what fd to use for the builtin's stdin.
let mut local_builtin_stdin = STDIN_FILENO;
if let Some(inp) = io_chain.io_for_fd(STDIN_FILENO) {
// Ignore fd redirections from an fd other than the
// standard ones. e.g. in source <&3 don't actually read from fd 3,
// which is internal to fish. We still respect this redirection in
// that we pass it on as a block IO to the code that source runs,
// and therefore this is not an error.
let ignore_redirect = inp.io_mode() == IoMode::fd && inp.source_fd() >= 3;
if !ignore_redirect {
local_builtin_stdin = inp.source_fd();
}
}
// Populate our IoStreams. This is a bag of information for the builtin.
let mut streams = IoStreams::new(output_stream, errput_stream, &io_chain);
streams.job_group = job_group;
streams.stdin_fd = local_builtin_stdin;
streams.stdin_is_directly_redirected = stdin_is_directly_redirected;
streams.out_is_redirected = out_io.is_some();
streams.err_is_redirected = err_io.is_some();
streams.out_is_piped = out_io
.map(|io| io.io_mode() == IoMode::pipe)
.unwrap_or(false);
streams.err_is_piped = err_io
.map(|io| io.io_mode() == IoMode::pipe)
.unwrap_or(false);
// Execute the builtin.
let mut shim_argv: Vec<&wstr> =
argv.iter().map(|s| truncate_at_nul(s.as_ref())).collect();
builtin_run(parser, &mut shim_argv, &mut streams)
},
)
}
/// Executes a builtin "process".
fn exec_builtin_process(
parser: &Parser,
j: &Job,
p: &Process,
io_chain: &IoChain,
piped_output_needs_buffering: bool,
) -> LaunchResult {
assert!(p.typ == ProcessType::builtin, "Process is not a builtin");
let mut out =
create_output_stream_for_builtin(STDOUT_FILENO, io_chain, piped_output_needs_buffering);
let mut err =
create_output_stream_for_builtin(STDERR_FILENO, io_chain, piped_output_needs_buffering);
let performer = get_performer_for_builtin(p, j, io_chain);
let status = performer(parser, p, Some(&mut out), Some(&mut err));
p.status.update(&status);
handle_builtin_output(parser, j, p, io_chain, &out, &err);
Ok(())
}
#[derive(Default)]
struct PartialPipes {
/// Read end of the pipe.
read: Option<OwnedFd>,
/// Write end of the pipe.
write: Option<OwnedFd>,
}
/// Executes a process \p \p in \p job, using the pipes \p pipes (which may have invalid fds if this
/// is the first or last process).
/// \p deferred_pipes represents the pipes from our deferred process; if set ensure they get closed
/// in any child. If \p is_deferred_run is true, then this is a deferred run; this affects how
/// certain buffering works.
/// An error return here indicates that the process failed to launch, and the rest of
/// the pipeline should be cancelled.
fn exec_process_in_job(
parser: &Parser,
p: &Process,
j: &Job,
block_io: IoChain,
pipes: PartialPipes,
deferred_pipes: &PartialPipes,
is_deferred_run: bool,
) -> LaunchResult {
// 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.
// Maybe trace this process.
// TODO: 'and' and 'or' will not show.
trace_if_enabled_with_args(parser, L!(""), p.argv());
// The IO chain for this process.
let mut process_net_io_chain = block_io;
if let Some(fd) = pipes.write {
process_net_io_chain.push(Arc::new(IoPipe::new(
p.pipe_write_fd,
false, /* not input */
fd,
)));
}
// Append IOs from the process's redirection specs.
// This may fail, e.g. a failed redirection.
if !process_net_io_chain
.append_from_specs(p.redirection_specs(), &parser.vars().get_pwd_slash())
{
return Err(());
}
// Read pipe goes last.
if let Some(fd) = pipes.read {
let pipe_read = Arc::new(IoPipe::new(STDIN_FILENO, true /* input */, fd));
process_net_io_chain.push(pipe_read);
}
// If we have stashed pipes, make sure those get closed in the child.
for afd in [&deferred_pipes.read, &deferred_pipes.write]
.into_iter()
.flatten()
{
process_net_io_chain.push(Arc::new(IoClose::new(afd.as_raw_fd())));
}
if p.typ != ProcessType::block_node {
// A simple `begin ... end` should not be considered an execution of a command.
parser.libdata_mut().pods.exec_count += 1;
}
let mut block_id = None;
if !p.variable_assignments.is_empty() {
block_id = Some(parser.push_block(Block::variable_assignment_block()));
}
let _pop_block = ScopeGuard::new((), |()| {
if let Some(block_id) = block_id {
parser.pop_block(block_id);
}
});
for assignment in &p.variable_assignments {
parser.vars().set(
&assignment.variable_name,
EnvMode::LOCAL | EnvMode::EXPORT,
assignment.values.clone(),
);
}
// Decide if outputting to a pipe may deadlock.
// This happens if fish pipes from an internal process into another internal process:
// echo $big | string match...
// Here fish will only run one process at a time, so the pipe buffer may overfill.
// It may also happen when piping internal -> external:
// echo $big | external_proc
// fish wants to run `echo` before launching external_proc, so the pipe may deadlock.
// However if we are a deferred run, it means that we are piping into an external process
// which got launched before us!
let piped_output_needs_buffering = !p.is_last_in_job && !is_deferred_run;
// Execute the process.
p.check_generations_before_launch();
match p.typ {
ProcessType::function | ProcessType::block_node => exec_block_or_func_process(
parser,
j,
p,
process_net_io_chain,
piped_output_needs_buffering,
),
ProcessType::builtin => exec_builtin_process(
parser,
j,
p,
&process_net_io_chain,
piped_output_needs_buffering,
),
ProcessType::external => {
exec_external_command(parser, j, p, &process_net_io_chain)?;
// It's possible (though unlikely) that this is a background process which recycled a
// pid from another, previous background process. Forget any such old process.
parser.mut_wait_handles().remove_by_pid(p.pid());
Ok(())
}
ProcessType::exec => {
// We should have handled exec up above.
panic!("process_type_t::exec process found in pipeline, where it should never be. Aborting.");
}
}
}
// Do we have a fish internal process that pipes into a real process? If so, we are going to
// launch it last (if there's more than one, just the last one). That is to prevent buffering
// from blocking further processes. See #1396.
// Example:
// for i in (seq 1 5); sleep 1; echo $i; end | cat
// This should show the output as it comes, not buffer until the end.
// Any such process (only one per job) will be called the "deferred" process.
fn get_deferred_process(j: &Job) -> Option<usize> {
// Common case is no deferred proc.
if j.processes().len() <= 1 {
return None;
}
// Skip execs, which can only appear at the front.
if j.processes()[0].typ == ProcessType::exec {
return None;
}
// Find the last non-external process, and return it if it pipes into an extenal process.
for (i, p) in j.processes().iter().enumerate().rev() {
if p.typ != ProcessType::external {
return if p.is_last_in_job { None } else { Some(i) };
}
}
None
}
/// Given that we failed to execute process \p failed_proc in job \p job, mark that process and
/// every subsequent process in the pipeline as aborted before launch.
fn abort_pipeline_from(job: &Job, offset: usize) {
for p in job.processes().iter().skip(offset) {
p.mark_aborted_before_launch();
}
}
// Given that we are about to execute an exec() call, check if the parser is interactive and there
// are extant background jobs. If so, warn the user and do not exec().
// \return true if we should allow exec, false to disallow it.
fn allow_exec_with_background_jobs(parser: &Parser) -> bool {
// If we're not interactive, we cannot warn.
if !parser.is_interactive() {
return true;
}
// Construct the list of running background jobs.
let bgs = jobs_requiring_warning_on_exit(parser);
if bgs.is_empty() {
return true;
}
// Compare run counts, so we only warn once.
let current_run_count = reader_run_count();
let last_exec_run_count = &mut parser.libdata_mut().pods.last_exec_run_counter;
if isatty(STDIN_FILENO) && current_run_count - 1 != *last_exec_run_count {
print_exit_warning_for_jobs(&bgs);
*last_exec_run_count = current_run_count;
false
} else {
hup_jobs(&parser.jobs());
true
}
}
/// Populate \p lst with the output of \p buffer, perhaps splitting lines according to \p split.
fn populate_subshell_output(lst: &mut Vec<WString>, buffer: &SeparatedBuffer, split: bool) {
// Walk over all the elements.
for elem in buffer.elements() {
let data = &elem.contents;
if elem.is_explicitly_separated() {
// Just append this one.
lst.push(str2wcstring(data));
continue;
}
// Not explicitly separated. We have to split it explicitly.
assert!(
!elem.is_explicitly_separated(),
"should not be explicitly separated"
);
if split {
let mut cursor = 0;
while cursor < data.len() {
// Look for the next separator.
let stop = data[cursor..].iter().position(|c| *c == b'\n');
let hit_separator = stop.is_some();
// If it's not found, just use the end.
let stop = stop.map(|rel| cursor + rel).unwrap_or(data.len());
// Stop now points at the first character we do not want to copy.
lst.push(str2wcstring(&data[cursor..stop]));
// If we hit a separator, skip over it; otherwise we're at the end.
cursor = stop + if hit_separator { 1 } else { 0 };
}
} else {
// We're not splitting output, but we still want to trim off a trailing newline.
let trailing_newline = if data.last() == Some(&b'\n') { 1 } else { 0 };
lst.push(str2wcstring(&data[..data.len() - trailing_newline]));
}
}
}
/// Execute \p cmd in a subshell in \p parser. If \p lst is not null, populate it with the output.
/// Return $status in \p out_status.
/// If \p job_group is set, any spawned commands should join that job group.
/// If \p apply_exit_status is false, then reset $status back to its original value.
/// \p is_subcmd controls whether we apply a read limit.
/// \p break_expand is used to propagate whether the result should be "expansion breaking" in the
/// sense that subshells used during string expansion should halt that expansion. \return the value
/// of $status.
fn exec_subshell_internal(
cmd: &wstr,
parser: &Parser,
job_group: Option<&JobGroupRef>,
lst: Option<&mut Vec<WString>>,
break_expand: &mut bool,
apply_exit_status: bool,
is_subcmd: bool,
) -> libc::c_int {
parser.assert_can_execute();
let _is_subshell = scoped_push_replacer(
|new_value| std::mem::replace(&mut parser.libdata_mut().pods.is_subshell, new_value),
true,
);
let _read_limit = scoped_push_replacer(
|new_value| std::mem::replace(&mut parser.libdata_mut().pods.read_limit, new_value),
if is_subcmd {
READ_BYTE_LIMIT.load(Ordering::Relaxed)
} else {
0
},
);
let prev_statuses = parser.get_last_statuses();
let _put_back = ScopeGuard::new((), |()| {
if !apply_exit_status {
parser.set_last_statuses(prev_statuses);
}
});
let split_output = parser.vars().get_unless_empty(L!("IFS")).is_some();
// IO buffer creation may fail (e.g. if we have too many open files to make a pipe), so this may
// be null.
let Ok(bufferfill) = IoBufferfill::create_opts(parser.libdata().pods.read_limit, STDOUT_FILENO)
else {
*break_expand = true;
return STATUS_CMD_ERROR.unwrap();
};
let mut io_chain = IoChain::new();
io_chain.push(bufferfill.clone());
let eval_res = parser.eval_with(cmd, &io_chain, job_group, BlockType::subst);
let buffer = IoBufferfill::finish(bufferfill);
if buffer.discarded() {
*break_expand = true;
return STATUS_READ_TOO_MUCH.unwrap();
}
if eval_res.break_expand {
*break_expand = true;
return eval_res.status.status_value();
}
if let Some(lst) = lst {
populate_subshell_output(lst, &buffer, split_output);
}
*break_expand = false;
eval_res.status.status_value()
}