diff --git a/fish-rust/src/builtins/bg.rs b/fish-rust/src/builtins/bg.rs index b6688faa6..acac4d8d8 100644 --- a/fish-rust/src/builtins/bg.rs +++ b/fish-rust/src/builtins/bg.rs @@ -36,10 +36,7 @@ fn send_to_bg( job.command().from_ffi() )); - unsafe { - std::mem::transmute::<&ffi::job_group_t, &crate::job_group::JobGroup>(job.ffi_group()) - } - .set_is_foreground(false); + job.get_job_group().set_is_foreground(false); if !job.ffi_resume() { return STATUS_CMD_ERROR; diff --git a/fish-rust/src/ffi.rs b/fish-rust/src/ffi.rs index f6a3392db..55fd59dfd 100644 --- a/fish-rust/src/ffi.rs +++ b/fish-rust/src/ffi.rs @@ -4,6 +4,7 @@ use ::std::pin::Pin; #[rustfmt::skip] use ::std::slice; use crate::env::{EnvMode, EnvStackRef, EnvStackRefFFI}; +use crate::job_group::JobGroup; pub use crate::wait_handle::{ WaitHandleRef, WaitHandleRefFFI, WaitHandleStore, WaitHandleStoreFFI, }; @@ -150,6 +151,8 @@ include_cpp! { generate!("make_autoload_ffi") generate!("perform_autoload_ffi") generate!("complete_get_wrap_targets_ffi") + + generate!("is_thompson_shell_script") } impl parser_t { @@ -270,6 +273,10 @@ impl job_t { let ffi_procs = self.ffi_processes(); unsafe { slice::from_raw_parts_mut(ffi_procs.procs, ffi_procs.count) } } + + pub fn get_job_group(&self) -> &JobGroup { + unsafe { ::std::mem::transmute::<&job_group_t, &JobGroup>(self.ffi_group()) } + } } impl process_t { diff --git a/fish-rust/src/lib.rs b/fish-rust/src/lib.rs index 16d6c5a4e..0c20913a2 100644 --- a/fish-rust/src/lib.rs +++ b/fish-rust/src/lib.rs @@ -60,6 +60,7 @@ mod reader; mod redirection; mod signal; mod smoke; +mod spawn; mod termsize; mod threads; mod timer; diff --git a/fish-rust/src/redirection.rs b/fish-rust/src/redirection.rs index f53783506..1a225f07c 100644 --- a/fish-rust/src/redirection.rs +++ b/fish-rust/src/redirection.rs @@ -64,7 +64,7 @@ mod redirection_ffi { } extern "Rust" { - fn get_actions(self: &Dup2List) -> &Vec; + fn get_actions(self: &Dup2List) -> &[Dup2Action]; #[cxx_name = "dup2_list_resolve_chain"] fn dup2_list_resolve_chain_ffi(io_chain: &CxxVector) -> Dup2List; fn fd_for_target_fd(self: &Dup2List, target: i32) -> i32; @@ -195,7 +195,7 @@ fn dup2_list_resolve_chain_ffi(io_chain: &CxxVector) -> Dup2List { impl Dup2List { /// \return the list of dup2 actions. - fn get_actions(&self) -> &Vec { + pub fn get_actions(&self) -> &[Dup2Action] { &self.actions } diff --git a/fish-rust/src/spawn.rs b/fish-rust/src/spawn.rs new file mode 100644 index 000000000..0c64601a2 --- /dev/null +++ b/fish-rust/src/spawn.rs @@ -0,0 +1,228 @@ +//! Wrappers around posix_spawn. + +use crate::ffi::{self, job_t}; +use crate::redirection::Dup2List; +use crate::signal::get_signals_with_handlers; +use errno::{self, Errno}; +use libc::{self, c_char, posix_spawn_file_actions_t, posix_spawnattr_t}; +use std::ffi::{CStr, CString}; + +// The posix_spawn family of functions is unusual in that it returns errno codes directly in the return value, not via errno. +// This converts to an error if nonzero. +fn check_fail(res: i32) -> Result<(), Errno> { + match res { + 0 => Ok(()), + err => Err(Errno(err)), + } +} + +/// Basic RAII wrapper around posix_spawnattr_t. +struct Attr(posix_spawnattr_t); + +impl Attr { + fn new() -> Result { + unsafe { + let mut attr: posix_spawnattr_t = std::mem::zeroed(); + check_fail(libc::posix_spawnattr_init(&mut attr))?; + Ok(Self(attr)) + } + } + + fn set_flags(&mut self, flags: libc::c_short) -> Result<(), Errno> { + unsafe { check_fail(libc::posix_spawnattr_setflags(&mut self.0, flags)) } + } + + fn set_pgroup(&mut self, pgroup: libc::pid_t) -> Result<(), Errno> { + unsafe { check_fail(libc::posix_spawnattr_setpgroup(&mut self.0, pgroup)) } + } + + fn set_sigdefault(&mut self, sigs: &libc::sigset_t) -> Result<(), Errno> { + unsafe { check_fail(libc::posix_spawnattr_setsigdefault(&mut self.0, sigs)) } + } + + fn set_sigmask(&mut self, sigs: &libc::sigset_t) -> Result<(), Errno> { + unsafe { check_fail(libc::posix_spawnattr_setsigmask(&mut self.0, sigs)) } + } +} + +impl Drop for Attr { + fn drop(&mut self) { + unsafe { + let _ = libc::posix_spawnattr_destroy(&mut self.0); + } + } +} + +/// Basic RAII wrapper around posix_spawn_file_actions_t; +struct FileActions(posix_spawn_file_actions_t); + +impl FileActions { + fn new() -> Result { + unsafe { + let mut actions: posix_spawn_file_actions_t = std::mem::zeroed(); + check_fail(libc::posix_spawn_file_actions_init(&mut actions))?; + Ok(Self(actions)) + } + } + + fn add_close(&mut self, fd: libc::c_int) -> Result<(), Errno> { + unsafe { check_fail(libc::posix_spawn_file_actions_addclose(&mut self.0, fd)) } + } + + fn add_dup2(&mut self, src: libc::c_int, target: libc::c_int) -> Result<(), Errno> { + unsafe { + check_fail(libc::posix_spawn_file_actions_adddup2( + &mut self.0, + src, + target, + )) + } + } +} + +impl Drop for FileActions { + fn drop(&mut self) { + unsafe { + let _ = libc::posix_spawn_file_actions_destroy(&mut self.0); + } + } +} + +/// A RAII type which wraps up posix_spawn's data structures. +pub struct PosixSpawner { + attr: Attr, + actions: FileActions, +} + +impl PosixSpawner { + pub fn new(j: &job_t, dup2s: &Dup2List) -> Result { + let mut attr = Attr::new()?; + let mut actions = FileActions::new()?; + + // desired_pgid tracks the pgroup for the process. If it is none, the pgroup is left unchanged. + // If it is zero, create a new pgroup from the pid. If it is >0, join that pgroup. + let desired_pgid = if let Some(pgid) = j.get_job_group().get_pgid() { + Some(pgid) + } else if j.get_procs()[0].as_ref().unwrap().get_leads_pgrp() { + Some(0) + } else { + None + }; + + // Set the handling for job control signals back to the default. + let reset_signal_handlers = true; + + // Remove all signal blocks. + let reset_sigmask = true; + + // Set our flags. + let mut flags: i32 = 0; + if reset_signal_handlers { + flags |= libc::POSIX_SPAWN_SETSIGDEF; + } + if reset_sigmask { + flags |= libc::POSIX_SPAWN_SETSIGMASK; + } + if desired_pgid.is_some() { + flags |= libc::POSIX_SPAWN_SETPGROUP; + } + attr.set_flags(flags.try_into().expect("Flags should fit in c_short"))?; + + if let Some(desired_pgid) = desired_pgid { + attr.set_pgroup(desired_pgid)?; + } + + // Everybody gets default handlers. + if reset_signal_handlers { + let mut sigdefault: libc::sigset_t = unsafe { std::mem::zeroed() }; + get_signals_with_handlers(&mut sigdefault); + attr.set_sigdefault(&sigdefault)?; + } + + // No signals blocked. + if reset_sigmask { + let mut sigmask = unsafe { std::mem::zeroed() }; + unsafe { libc::sigemptyset(&mut sigmask) }; + blocked_signals_for_job(j, &mut sigmask); + attr.set_sigmask(&sigmask)?; + } + + // Apply our dup2s. + for act in dup2s.get_actions() { + if act.target < 0 { + actions.add_close(act.src)?; + } else { + actions.add_dup2(act.src, act.target)?; + } + } + Ok(PosixSpawner { attr, actions }) + } + + // Consume this spawner, attempting to spawn a new process. + pub fn spawn( + self, + cmd: *const c_char, + argv: *const *mut c_char, + envp: *const *mut c_char, + ) -> Result { + let mut pid = -1; + let spawned = check_fail(unsafe { + libc::posix_spawn(&mut pid, cmd, &self.actions.0, &self.attr.0, argv, envp) + }); + if spawned.is_ok() { + return Ok(pid); + } + let spawn_err = spawned.unwrap_err(); + + // 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 spawn_err.0 == libc::ENOEXEC && ffi::is_thompson_shell_script(cmd) { + // Create a new argv with /bin/sh prepended. + let interp = get_path_bshell(); + let mut argv2 = vec![interp.as_ptr() as *mut c_char]; + + // The command to call should use the full path, + // not what we would pass as argv0. + let cmd2: CString = CString::new(unsafe { CStr::from_ptr(cmd).to_bytes() }).unwrap(); + argv2.push(cmd2.as_ptr() as *mut c_char); + for i in 1.. { + let ptr = unsafe { argv.offset(i).read() }; + if ptr.is_null() { + break; + } + argv2.push(ptr); + } + argv2.push(std::ptr::null_mut()); + check_fail(unsafe { + libc::posix_spawn( + &mut pid, + interp.as_ptr(), + &self.actions.0, + &self.attr.0, + argv2.as_ptr(), + envp, + ) + })?; + return Ok(pid); + } + Err(spawn_err) + } +} + +fn blocked_signals_for_job(job: &job_t, sigmask: &mut libc::sigset_t) { + // Block some signals in background jobs for which job control is turned off (#6828). + if !job.is_foreground() && !job.wants_job_control() { + unsafe { + libc::sigaddset(sigmask, libc::SIGINT); + libc::sigaddset(sigmask, libc::SIGQUIT); + } + } +} + +fn get_path_bshell() -> CString { + // TODO: this should really use _PATH_BSHELL, but this is only used in an edge case for posix_spawns + // which fail to run Thompson shell scripts; we simply assume it is /bin/sh. + CString::new("/bin/sh").unwrap() +} diff --git a/src/exec.h b/src/exec.h index 19c0741af..acdabbdd4 100644 --- a/src/exec.h +++ b/src/exec.h @@ -44,4 +44,8 @@ int exec_subshell_for_expand(const wcstring &cmd, parser_t &parser, /// Add signals that should be masked for external processes in this job. bool blocked_signals_for_job(const job_t &job, sigset_t *sigmask); +/// This function checks the beginning of a file to see if it's safe to +/// pass to the system interpreter when execve() returns ENOEXEC. +bool is_thompson_shell_script(const char *path); + #endif diff --git a/src/proc.h b/src/proc.h index f0ef9cc33..6f9b1c4bf 100644 --- a/src/proc.h +++ b/src/proc.h @@ -291,6 +291,9 @@ class process_t { /// \return whether this process type is internal (block, function, or builtin). bool is_internal() const; + /// \return whether this process leads its process group. + bool get_leads_pgrp() const { return leads_pgrp; } + /// \return the wait handle for the process, if it exists. rust::Box *get_wait_handle_ffi() const;