use crate::builtins::shared::{
builtin_run, ErrorCode, STATUS_CMD_ERROR, STATUS_CMD_UNKNOWN, STATUS_NOT_EXECUTABLE,
STATUS_READ_TOO_MUCH,
};
use crate::env::{EnvMode, EnvSetMode, EnvStack, Environment as _, Statuses, READ_BYTE_LIMIT};
#[cfg(have_posix_spawn)]
use crate::env_dispatch::use_posix_spawn;
use crate::fds::{
make_autoclose_pipes, make_fd_blocking, open_cloexec, BorrowedFdFile, PIPE_ERROR,
};
use crate::flog::{flog, flogf};
#[cfg(have_posix_spawn)]
use crate::fork_exec::spawn::PosixSpawner;
use crate::fork_exec::{
blocked_signals_for_job,
postfork::{
child_setup_process, execute_fork, execute_setpgid, report_setpgid_error,
signal_safe_report_exec_error,
},
PATH_BSHELL,
};
use crate::function::{self, FunctionProperties};
use crate::io::{
BufferedOutputStream, FdOutputStream, IoBufferfill, IoChain, IoClose, IoMode, IoPipe,
IoStreams, OutputStream, SeparatedBuffer, StringOutputStream,
};
use crate::nix::isatty;
use crate::null_terminated_array::OwningNullTerminatedArray;
use crate::parser::{Block, BlockId, BlockType, EvalRes, Parser, ParserEnvSetMode};
use crate::prelude::*;
use crate::proc::{
hup_jobs, is_interactive_session, jobs_requiring_warning_on_exit, no_exec,
print_exit_warning_for_jobs, InternalProc, Job, JobGroupRef, Pid, ProcStatus, Process,
ProcessType,
};
use crate::reader::{reader_run_count, restore_term_mode};
use crate::redirection::{dup2_list_resolve_chain, Dup2List};
use crate::threads::{is_forked_child, ThreadPool};
use crate::trace::trace_if_enabled_with_args;
use crate::tty_handoff::TtyHandoff;
use crate::wutil::{fish_wcstol, perror_io};
use errno::{errno, set_errno};
use fish_common::{exit_without_destructors, truncate_at_nul, write_loop, ScopeGuard};
use fish_widestring::{bytes2wcstring, wcs2bytes, wcs2zstring, ToWString as _};
use libc::{
EACCES, ENOENT, ENOEXEC, ENOTDIR, EPIPE, EXIT_FAILURE, EXIT_SUCCESS, STDERR_FILENO,
STDIN_FILENO, STDOUT_FILENO,
};
use nix::{
fcntl::OFlag,
sys::stat,
unistd::{getpgrp, getpid},
};
use std::{
ffi::CStr,
io::{Read as _, Write as _},
mem::MaybeUninit,
num::NonZeroU32,
os::fd::{AsRawFd as _, FromRawFd as _, OwnedFd, RawFd},
slice,
sync::{
atomic::{AtomicUsize, Ordering},
Arc, OnceLock,
},
};
fn exec_thread_pool() -> &'static Arc<ThreadPool> {
static EXEC_THREAD_POOL: OnceLock<Arc<ThreadPool>> = OnceLock::new();
EXEC_THREAD_POOL.get_or_init(|| ThreadPool::new(1, usize::MAX))
}
pub fn exec_job(parser: &Parser, job: &Job, block_io: IoChain) -> bool {
if no_exec() {
return true;
}
if job.processes()[0].is_exec() {
if !allow_exec_with_background_jobs(parser) {
for p in job.processes().iter() {
p.mark_aborted_before_launch();
}
return false;
}
for assignment in &job.processes()[0].variable_assignments {
parser.set_var(
&assignment.variable_name,
ParserEnvSetMode::new(EnvMode::LOCAL | EnvMode::EXPORT),
assignment.values.clone(),
);
}
internal_exec(parser.vars(), parser.is_repainting(), job, block_io);
let status = if job.flags().negate { 0 } else { 1 };
parser.set_last_statuses(Statuses::just(status));
for p in job.processes().iter() {
p.mark_aborted_before_launch();
}
return false;
}
let mut deferred_pipes = PartialPipes::default();
let deferred_process = get_deferred_process(job);
let mut handoff = TtyHandoff::new(|| {});
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];
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);
if Some(i) == deferred_process {
deferred_pipes = proc_pipes;
continue;
}
}
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;
if p.leads_pgrp && job.group().wants_terminal() {
handoff.to_job_group(job.group.as_ref().unwrap());
}
}
drop(pipe_next_read);
if aborted_pipeline && procs_launched == 0 {
return false;
}
if let Some(dp) = deferred_process {
if
aborted_pipeline
|| exec_process_in_job(
parser,
&job.processes()[dp],
job,
block_io.clone(),
deferred_pipes,
&PartialPipes::default(),
true,
)
.is_err()
{
job.processes()[dp].mark_aborted_before_launch();
}
}
flogf!(
exec_job_exec,
"Executed job %d from command '%s'",
job.job_id(),
job.command()
);
job.mark_constructed();
if !job.is_foreground() {
if let Some(last_pid) = job.get_last_pid() {
parser.set_one(
L!("last_pid"),
ParserEnvSetMode::new(EnvMode::GLOBAL),
last_pid.to_wstring(),
);
} else {
parser.set_empty(L!("last_pid"), ParserEnvSetMode::new(EnvMode::GLOBAL));
}
}
if !job.is_initially_background() {
job.continue_job(parser, Some(&block_io));
}
if job.is_stopped() {
handoff.save_tty_modes();
}
true
}
pub fn exec_subshell(
cmd: &wstr,
parser: &Parser,
outputs: Option<&mut Vec<WString>>,
apply_exit_status: bool,
) -> Result<(), ErrorCode> {
let mut break_expand = false;
exec_subshell_internal(
cmd,
parser,
None,
outputs,
&mut break_expand,
apply_exit_status,
false,
)
}
pub fn exec_subshell_for_expand(
cmd: &wstr,
parser: &Parser,
job_group: Option<&JobGroupRef>,
outputs: &mut Vec<WString>,
) -> Result<(), ErrorCode> {
let mut break_expand = true;
let ret = exec_subshell_internal(
cmd,
parser,
job_group,
Some(outputs),
&mut break_expand,
true,
true,
);
if break_expand {
ret
} else {
Ok(())
}
}
static FORK_COUNT: AtomicUsize = AtomicUsize::new(0);
type LaunchResult = Result<(), ()>;
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 => {
STATUS_CMD_UNKNOWN
}
EACCES | ENOEXEC => {
STATUS_NOT_EXECUTABLE
}
#[cfg(apple)]
libc::EBADARCH | libc::EBADMACHO => {
STATUS_NOT_EXECUTABLE
}
_ => {
EXIT_FAILURE
}
}
}
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
}
pub fn is_thompson_shell_script(path: &CStr) -> bool {
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
}
fn signal_safe_launch_process(
_p: &Process,
actual_cmd: &CStr,
argv: &OwningNullTerminatedArray,
envv: &OwningNullTerminatedArray,
) -> ! {
unsafe { libc::execve(actual_cmd.as_ptr(), argv.get(), envv.get()) };
let err = errno();
if err.0 == ENOEXEC && is_thompson_shell_script(actual_cmd) {
const MAXARGS: usize = 128;
let nargs = argv.len();
let argv = unsafe { slice::from_raw_parts(argv.get(), nargs) };
if nargs <= MAXARGS {
let mut argv2 = [std::ptr::null(); 1 + MAXARGS + 1];
let bshell = PATH_BSHELL.as_ptr().cast();
argv2[0] = bshell;
argv2[1..=argv.len()].copy_from_slice(argv);
argv2[1] = actual_cmd.as_ptr();
unsafe {
libc::execve(bshell, &argv2[0], envv.get());
}
}
}
set_errno(err);
signal_safe_report_exec_error(errno().0, actual_cmd, argv, envv);
exit_without_destructors(exit_code_from_exec_error(err.0));
}
fn launch_process_nofork(vars: &EnvStack, p: &Process) -> ! {
assert!(!is_forked_child());
let narrow_strings = p.argv().iter().map(|s| wcs2zstring(s)).collect();
let argv = OwningNullTerminatedArray::new(narrow_strings);
let envp = vars.export_array();
let actual_cmd = wcs2zstring(&p.actual_cmd);
restore_term_mode();
signal_safe_launch_process(p, &actual_cmd, &argv, &envp);
}
#[cfg(have_posix_spawn)]
fn can_use_posix_spawn_for_job(job: &Job, dup2s: &Dup2List) -> bool {
if !use_posix_spawn() {
return false;
}
for action in dup2s.get_actions() {
if action.src == action.target {
return false;
}
}
let wants_terminal = job.group().wants_terminal();
!wants_terminal
}
fn internal_exec(vars: &EnvStack, is_repainting: bool, j: &Job, block_io: IoChain) {
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 = MaybeUninit::uninit();
let mut blocked_signals = unsafe {
libc::sigemptyset(blocked_signals.as_mut_ptr());
blocked_signals.assume_init()
};
let blocked_signals = if blocked_signals_for_job(j, &mut blocked_signals) {
Some(&blocked_signals)
} else {
None
};
let redirs = dup2_list_resolve_chain(&all_ios);
if child_setup_process(
None,
blocked_signals,
false,
&redirs,
) == 0
{
if is_interactive_session() {
let global_exported_mode = EnvMode::GLOBAL | EnvMode::EXPORT;
let shlvl_var = vars.getf(L!("SHLVL"), global_exported_mode);
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"),
EnvSetMode::new(global_exported_mode, is_repainting),
shlvl_str,
);
}
launch_process_nofork(vars, &j.processes()[0]);
}
}
fn run_internal_process(p: &Process, outdata: Vec<u8>, errdata: Vec<u8>, ios: &IoChain) {
p.check_generations_before_launch();
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
}
}
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 %u to write output for proc '%s'",
internal_proc.get_id(),
p.argv0().unwrap()
);
f.dup2s = dup2_list_resolve_chain(ios);
f.src_outfd = f.dup2s.fd_for_target_fd(STDOUT_FILENO);
f.src_errfd = f.dup2s.fd_for_target_fd(STDERR_FILENO);
if f.skip_out() && f.skip_err() {
internal_proc.mark_exited(p.status());
return;
}
f.ios = ios.clone();
f.success_status = p.status();
exec_thread_pool().perform(move || {
let mut status = f.success_status;
if !f.skip_out() {
if let Err(err) = write_loop(&f.src_outfd, &f.outdata) {
if err.raw_os_error() != Some(EPIPE) {
perror_io("write", &err);
}
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() != Some(EPIPE) {
perror_io("write", &err);
}
if status.is_success() {
status = ProcStatus::from_exit_code(1);
}
}
}
f.internal_proc.mark_exited(status);
});
}
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 (%s) 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().status_count += 1;
} else if j.flags().negate {
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);
}
}
#[derive(Copy, Clone)]
pub enum PgroupPolicy {
Inherit, Join(libc::pid_t), Lead, }
fn fork_child_for_process(
job: &Job,
p: &Process,
dup2s: &Dup2List,
pgroup_policy: PgroupPolicy,
child_action: impl FnOnce(&Process),
) -> LaunchResult {
let claim_tty_from = if p.leads_pgrp && job.group().wants_terminal() {
Some(NonZeroU32::new(getpgrp().as_raw() as u32).unwrap())
} else {
None
};
let mut blocked_signals = MaybeUninit::uninit();
let mut blocked_signals = unsafe {
libc::sigemptyset(blocked_signals.as_mut_ptr());
blocked_signals.assume_init()
};
let blocked_signals = if blocked_signals_for_job(job, &mut blocked_signals) {
Some(&blocked_signals)
} else {
None
};
let narrow_cmd = wcs2zstring(job.command());
let narrow_argv0 = wcs2zstring(p.argv0().unwrap_or_default());
let job_id = job.job_id().as_num();
let fork_res = execute_fork();
if fork_res < 0 {
return Err(());
}
let is_parent = fork_res > 0;
let pid: libc::pid_t = if is_parent {
fork_res
} else {
getpid().as_raw()
};
if let Some(pgid) = match pgroup_policy {
PgroupPolicy::Inherit => None,
PgroupPolicy::Join(pgid) => Some(pgid),
PgroupPolicy::Lead => Some(pid),
} {
let err = execute_setpgid(pid, pgid, is_parent);
if err != 0 {
report_setpgid_error(
err,
is_parent,
pid,
pgid,
job_id,
&narrow_cmd,
&narrow_argv0,
);
}
}
if !is_parent {
child_setup_process(claim_tty_from, blocked_signals, true, dup2s);
child_action(p);
panic!("Child process returned control to fork_child!");
}
let pid = Pid::new(pid);
p.set_pid(pid);
if matches!(pgroup_policy, PgroupPolicy::Lead) {
job.group().set_pgid(pid);
}
let count = FORK_COUNT.fetch_add(1, Ordering::Relaxed) + 1;
flogf!(
exec_fork,
"Fork #%d, pid %d fork external command for '%s'",
count,
pid,
p.argv0().unwrap()
);
Ok(())
}
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 {
return OutputStream::Fd(FdOutputStream::new(fd));
};
match io.io_mode() {
IoMode::BufferFill => {
let buffer = io.as_bufferfill().unwrap().buffer();
OutputStream::Buffered(BufferedOutputStream::new(buffer.clone()))
}
IoMode::Close => {
OutputStream::Null
}
IoMode::File => {
OutputStream::Fd(FdOutputStream::new(io.source_fd()))
}
IoMode::Pipe => {
if piped_output_needs_buffering {
OutputStream::String(StringOutputStream::new())
} else {
OutputStream::Fd(FdOutputStream::new(io.source_fd()))
}
}
IoMode::Fd => {
OutputStream::String(StringOutputStream::new())
}
}
}
fn handle_builtin_output(
parser: &Parser,
j: &Job,
p: &Process,
io_chain: &IoChain,
out: &OutputStream,
err: &OutputStream,
) {
assert!(p.is_builtin(), "Process is not a builtin");
let outbuff = wcs2bytes(out.contents());
let errbuff = wcs2bytes(err.contents());
if !outbuff.is_empty() {
let _ = std::io::stdout().flush();
}
if !errbuff.is_empty() {
let _ = std::io::stderr().flush();
}
run_internal_process_or_short_circuit(parser, j, p, outbuff, errbuff, io_chain);
}
fn exec_external_command(
parser: &Parser,
j: &Job,
p: &Process,
proc_io_chain: &IoChain,
) -> LaunchResult {
assert!(p.is_external(), "Process is not external");
let narrow_argv = p.argv().iter().map(|s| wcs2zstring(s)).collect();
let argv = OwningNullTerminatedArray::new(narrow_argv);
let dup2s = dup2_list_resolve_chain(proc_io_chain);
let pgroup_policy = if p.leads_pgrp {
PgroupPolicy::Lead
} else if let Some(pgid) = j.group().get_pgid() {
PgroupPolicy::Join(pgid.as_pid_t())
} else {
PgroupPolicy::Inherit
};
let _ = make_fd_blocking(STDIN_FILENO);
let envv = parser.vars().export_array();
let actual_cmd = wcs2zstring(&p.actual_cmd);
#[cfg(have_posix_spawn)]
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;
let pid = PosixSpawner::new(j, pgroup_policy, &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) => {
signal_safe_report_exec_error(err.0, &actual_cmd, &argv, &envv);
p.status
.set(ProcStatus::from_exit_code(exit_code_from_exec_error(err.0)));
return Err(());
}
};
flogf!(
exec_fork,
"Fork #%d, pid %d: spawn external command '%s' from '%s'",
count,
pid,
p.actual_cmd,
file.as_ref()
.map(|s| s.as_utfstr())
.unwrap_or(L!("<no file>"))
);
let pid = Pid::new(pid);
p.set_pid(pid);
if p.leads_pgrp {
j.group().set_pgid(pid);
execute_setpgid(pid.as_pid_t(), pid.as_pid_t(), true );
}
return Ok(());
}
fork_child_for_process(j, p, &dup2s, pgroup_policy, |p| {
signal_safe_launch_process(p, &actual_cmd, &argv, &envv)
})
}
fn function_prepare_environment(
parser: &Parser,
mut argv: Vec<WString>,
props: &FunctionProperties,
) -> BlockId {
let mut func_name = WString::new();
if !argv.is_empty() {
func_name = argv.remove(0);
}
let fb = parser.push_block(Block::function_block(
func_name,
argv.clone(),
props.shadow_scope,
));
let vars = parser.vars();
let mode = parser.convert_env_set_mode(ParserEnvSetMode::user(EnvMode::LOCAL));
let mut overwrite_argv = false;
for (idx, named_arg) in props.named_arguments.iter().enumerate() {
if named_arg == L!("argv") {
overwrite_argv = true;
}
if idx < argv.len() {
vars.set_one(named_arg, mode, argv[idx].clone());
} else {
vars.set_empty(named_arg, mode);
}
}
for (key, value) in &*props.inherit_vars {
if key == L!("argv") {
overwrite_argv = true;
}
vars.set(key, mode, value.clone());
}
if !overwrite_argv {
vars.set_argv(argv, mode.is_repainting);
}
fb
}
fn function_restore_environment(parser: &Parser, block: BlockId) {
parser.pop_block(block);
parser.libdata_mut().returning = false;
}
type ProcPerformer =
dyn FnOnce(&Parser, Option<&mut OutputStream>, Option<&mut OutputStream>) -> ProcStatus;
fn get_performer_for_block_node(p: &Process, job: &Job, io_chain: &IoChain) -> Box<ProcPerformer> {
let ProcessType::BlockNode(node) = &p.typ else {
panic!("Expected a block node process");
};
let job_group = job.group.clone();
let io_chain = io_chain.clone();
let node = node.clone();
Box::new(move |parser: &Parser, _out, _err| {
parser
.eval_node(&node, &io_chain, job_group.as_ref(), BlockType::top, false)
.status
})
}
fn get_performer_for_function(
p: &Process,
job: &Job,
io_chain: &IoChain,
) -> Result<Box<ProcPerformer>, ()> {
assert!(p.is_function());
let job_group = job.group.clone();
let io_chain = io_chain.clone();
let Some(props) = function::get_props(p.argv0().unwrap()) else {
flog!(
error,
wgettext_fmt!("Unknown function '%s'", p.argv0().unwrap())
);
return Err(());
};
let argv = p.argv().clone();
Ok(Box::new(move |parser: &Parser, _out, _err| {
let fb = function_prepare_environment(parser, argv, &props);
let body_node = props.func_node.child_ref(|n| &n.jobs);
let mut res = parser.eval_node(
&body_node,
&io_chain,
job_group.as_ref(),
BlockType::top,
false,
);
function_restore_environment(parser, fb);
if res.was_empty {
res = EvalRes::new(ProcStatus::from_exit_code(EXIT_SUCCESS));
}
res.status
}))
}
fn exec_block_or_func_process(
parser: &Parser,
j: &Job,
p: &Process,
mut io_chain: IoChain,
piped_output_needs_buffering: bool,
) -> LaunchResult {
assert!(p.is_block_node() || p.is_function());
let mut block_output_bufferfill = None;
if piped_output_needs_buffering {
match IoBufferfill::create() {
Ok(tmp) => {
io_chain.push(tmp.clone());
block_output_bufferfill = Some(tmp);
}
Err(_) => return Err(()),
}
}
let performer = if p.is_block_node() {
get_performer_for_block_node(p, j, &io_chain)
} else {
get_performer_for_function(p, j, &io_chain)?
};
p.status.set(performer(parser, None, None));
let mut buffer_contents = vec![];
if let Some(block_output_bufferfill) = block_output_bufferfill {
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![],
&io_chain,
);
Ok(())
}
fn get_performer_for_builtin(p: &Process, j: &Job, io_chain: &IoChain) -> Box<ProcPerformer> {
assert!(p.is_builtin(), "Process must be a builtin");
let mut stdin_is_directly_redirected = false;
if !p.is_first_in_job {
stdin_is_directly_redirected = true;
} else {
for redir in p.redirection_specs() {
if redir.fd == STDIN_FILENO && !redir.is_close() {
stdin_is_directly_redirected = true;
break;
}
}
}
let job_group = j.group.clone();
let io_chain = io_chain.clone();
let argv = p.argv().clone();
Box::new(
move |parser: &Parser,
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);
let mut local_builtin_stdin = Some(BorrowedFdFile::stdin());
if let Some(inp) = io_chain.io_for_fd(STDIN_FILENO) {
let fd = inp.source_fd();
let ignore_redirect = fd >= 3 && inp.io_mode() == IoMode::Fd;
if fd == -1 {
local_builtin_stdin = None;
} else if !ignore_redirect {
local_builtin_stdin = Some(unsafe { BorrowedFdFile::from_raw_fd(fd) });
}
}
let mut streams = IoStreams::new(output_stream, errput_stream, &io_chain);
streams.job_group = job_group;
streams.stdin_file = 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.is_some_and(|io| io.io_mode() == IoMode::Pipe);
streams.err_is_piped = err_io.is_some_and(|io| io.io_mode() == IoMode::Pipe);
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)
},
)
}
fn exec_builtin_process(
parser: &Parser,
j: &Job,
p: &Process,
io_chain: &IoChain,
piped_output_needs_buffering: bool,
) -> LaunchResult {
assert!(p.is_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, Some(&mut out), Some(&mut err));
p.status.set(status);
handle_builtin_output(parser, j, p, io_chain, &out, &err);
Ok(())
}
#[derive(Default)]
struct PartialPipes {
read: Option<OwnedFd>,
write: Option<OwnedFd>,
}
fn exec_process_in_job(
parser: &Parser,
p: &Process,
j: &Job,
block_io: IoChain,
pipes: PartialPipes,
deferred_pipes: &PartialPipes,
is_deferred_run: bool,
) -> LaunchResult {
trace_if_enabled_with_args(parser, L!(""), p.argv());
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,
fd,
)));
}
if !process_net_io_chain
.append_from_specs(p.redirection_specs(), &parser.vars().get_pwd_slash())
{
return Err(());
}
if let Some(fd) = pipes.read {
let pipe_read = Arc::new(IoPipe::new(STDIN_FILENO, true , fd));
process_net_io_chain.push(pipe_read);
}
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.is_block_node() {
parser.libdata_mut().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.set_var(
&assignment.variable_name,
ParserEnvSetMode::new(EnvMode::LOCAL | EnvMode::EXPORT),
assignment.values.clone(),
);
}
let piped_output_needs_buffering = !p.is_last_in_job && !is_deferred_run;
p.check_generations_before_launch();
match p.typ {
ProcessType::Function | ProcessType::BlockNode(_) => 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)?;
parser.mut_wait_handles().remove_by_pid(p.pid().unwrap());
Ok(())
}
ProcessType::Exec => {
panic!(
"process_type_t::exec process found in pipeline, where it should never be. Aborting."
);
}
}
}
fn get_deferred_process(j: &Job) -> Option<usize> {
if j.processes().len() <= 1 {
return None;
}
if matches!(j.processes()[0].typ, ProcessType::Exec) {
return None;
}
for (i, p) in j.processes().iter().enumerate().rev() {
if !p.is_external() {
return if p.is_last_in_job { None } else { Some(i) };
}
}
None
}
fn abort_pipeline_from(job: &Job, offset: usize) {
for p in job.processes().iter().skip(offset) {
p.mark_aborted_before_launch();
}
}
fn allow_exec_with_background_jobs(parser: &Parser) -> bool {
if !parser.is_interactive() {
return true;
}
let bgs = jobs_requiring_warning_on_exit(parser);
if bgs.is_empty() {
return true;
}
let current_run_count = reader_run_count();
let last_exec_run_count = &mut parser.libdata_mut().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
}
}
fn populate_subshell_output(lst: &mut Vec<WString>, buffer: &SeparatedBuffer, split: bool) {
for elem in buffer.elements() {
let data = &elem.contents;
if elem.is_explicitly_separated() {
lst.push(bytes2wcstring(data));
continue;
}
assert!(
!elem.is_explicitly_separated(),
"should not be explicitly separated"
);
if split {
let mut cursor = 0;
while cursor < data.len() {
let stop = data[cursor..].iter().position(|c| *c == b'\n');
let hit_separator = stop.is_some();
let stop = stop.map_or(data.len(), |rel| cursor + rel);
lst.push(bytes2wcstring(&data[cursor..stop]));
cursor = stop + if hit_separator { 1 } else { 0 };
}
} else {
let trailing_newline = if data.last() == Some(&b'\n') { 1 } else { 0 };
lst.push(bytes2wcstring(&data[..data.len() - trailing_newline]));
}
}
}
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,
) -> Result<(), ErrorCode> {
let _scoped = parser.push_scope(|s| {
s.is_subshell = true;
s.read_limit = 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();
let Ok(bufferfill) = IoBufferfill::create_opts(parser.scope().read_limit, STDOUT_FILENO) else {
*break_expand = true;
return Err(STATUS_CMD_ERROR);
};
let mut io_chain = IoChain::new();
io_chain.push(bufferfill.clone());
let eval_res = parser.eval_with(cmd, &io_chain, job_group, BlockType::subst, false);
let buffer = IoBufferfill::finish(bufferfill);
if buffer.discarded() {
*break_expand = true;
return Err(STATUS_READ_TOO_MUCH);
}
if eval_res.break_expand {
*break_expand = true;
match eval_res.status.status_value() {
0 => return Ok(()),
code => return Err(code),
}
}
if let Some(lst) = lst {
populate_subshell_output(lst, &buffer, split_output);
}
*break_expand = false;
match eval_res.status.status_value() {
0 => Ok(()),
code => Err(code),
}
}