mirror of
https://github.com/fish-shell/fish-shell.git
synced 2025-01-19 20:12:45 +08:00
Port PosixSpawner to Rust
PosixSpawner is our wrapper around posix_spawn.
This commit is contained in:
parent
408ab86090
commit
245f7db5b3
|
@ -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;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -60,6 +60,7 @@ mod reader;
|
|||
mod redirection;
|
||||
mod signal;
|
||||
mod smoke;
|
||||
mod spawn;
|
||||
mod termsize;
|
||||
mod threads;
|
||||
mod timer;
|
||||
|
|
|
@ -64,7 +64,7 @@ mod redirection_ffi {
|
|||
}
|
||||
|
||||
extern "Rust" {
|
||||
fn get_actions(self: &Dup2List) -> &Vec<Dup2Action>;
|
||||
fn get_actions(self: &Dup2List) -> &[Dup2Action];
|
||||
#[cxx_name = "dup2_list_resolve_chain"]
|
||||
fn dup2_list_resolve_chain_ffi(io_chain: &CxxVector<Dup2Action>) -> Dup2List;
|
||||
fn fd_for_target_fd(self: &Dup2List, target: i32) -> i32;
|
||||
|
@ -195,7 +195,7 @@ fn dup2_list_resolve_chain_ffi(io_chain: &CxxVector<Dup2Action>) -> Dup2List {
|
|||
|
||||
impl Dup2List {
|
||||
/// \return the list of dup2 actions.
|
||||
fn get_actions(&self) -> &Vec<Dup2Action> {
|
||||
pub fn get_actions(&self) -> &[Dup2Action] {
|
||||
&self.actions
|
||||
}
|
||||
|
||||
|
|
228
fish-rust/src/spawn.rs
Normal file
228
fish-rust/src/spawn.rs
Normal file
|
@ -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<Self, Errno> {
|
||||
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<Self, Errno> {
|
||||
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<PosixSpawner, Errno> {
|
||||
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<libc::pid_t, Errno> {
|
||||
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()
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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<WaitHandleRefFFI> *get_wait_handle_ffi() const;
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user