diff options
Diffstat (limited to 'library/std/src/sys/pal')
29 files changed, 12 insertions, 5307 deletions
diff --git a/library/std/src/sys/pal/hermit/mod.rs b/library/std/src/sys/pal/hermit/mod.rs index 608245bd430..67eab96fa40 100644 --- a/library/std/src/sys/pal/hermit/mod.rs +++ b/library/std/src/sys/pal/hermit/mod.rs @@ -25,8 +25,6 @@ pub mod futex; pub mod os; #[path = "../unsupported/pipe.rs"] pub mod pipe; -#[path = "../unsupported/process.rs"] -pub mod process; pub mod thread; pub mod time; diff --git a/library/std/src/sys/pal/sgx/mod.rs b/library/std/src/sys/pal/sgx/mod.rs index bb419c2530e..fe43cfd2caf 100644 --- a/library/std/src/sys/pal/sgx/mod.rs +++ b/library/std/src/sys/pal/sgx/mod.rs @@ -16,8 +16,6 @@ mod libunwind_integration; pub mod os; #[path = "../unsupported/pipe.rs"] pub mod pipe; -#[path = "../unsupported/process.rs"] -pub mod process; pub mod thread; pub mod thread_parking; pub mod time; diff --git a/library/std/src/sys/pal/solid/mod.rs b/library/std/src/sys/pal/solid/mod.rs index e4a61fdcfe3..22052a168fd 100644 --- a/library/std/src/sys/pal/solid/mod.rs +++ b/library/std/src/sys/pal/solid/mod.rs @@ -25,8 +25,6 @@ pub(crate) mod error; pub mod os; #[path = "../unsupported/pipe.rs"] pub mod pipe; -#[path = "../unsupported/process.rs"] -pub mod process; pub use self::itron::{thread, thread_parking}; pub mod time; diff --git a/library/std/src/sys/pal/teeos/mod.rs b/library/std/src/sys/pal/teeos/mod.rs index 41b25121592..c1921a2f40d 100644 --- a/library/std/src/sys/pal/teeos/mod.rs +++ b/library/std/src/sys/pal/teeos/mod.rs @@ -14,8 +14,6 @@ pub mod env; pub mod os; #[path = "../unsupported/pipe.rs"] pub mod pipe; -#[path = "../unsupported/process.rs"] -pub mod process; pub mod thread; #[allow(non_upper_case_globals)] #[path = "../unix/time.rs"] diff --git a/library/std/src/sys/pal/uefi/mod.rs b/library/std/src/sys/pal/uefi/mod.rs index 714dc392688..fc12ec63b5c 100644 --- a/library/std/src/sys/pal/uefi/mod.rs +++ b/library/std/src/sys/pal/uefi/mod.rs @@ -19,7 +19,6 @@ pub mod helpers; pub mod os; #[path = "../unsupported/pipe.rs"] pub mod pipe; -pub mod process; pub mod thread; pub mod time; diff --git a/library/std/src/sys/pal/uefi/process.rs b/library/std/src/sys/pal/uefi/process.rs deleted file mode 100644 index 1203d51e531..00000000000 --- a/library/std/src/sys/pal/uefi/process.rs +++ /dev/null @@ -1,786 +0,0 @@ -use r_efi::protocols::simple_text_output; - -use super::helpers; -use crate::collections::BTreeMap; -pub use crate::ffi::OsString as EnvKey; -use crate::ffi::{OsStr, OsString}; -use crate::num::{NonZero, NonZeroI32}; -use crate::path::Path; -use crate::sys::fs::File; -use crate::sys::pipe::AnonPipe; -use crate::sys::unsupported; -use crate::sys_common::process::{CommandEnv, CommandEnvs}; -use crate::{fmt, io}; - -//////////////////////////////////////////////////////////////////////////////// -// Command -//////////////////////////////////////////////////////////////////////////////// - -#[derive(Debug)] -pub struct Command { - prog: OsString, - args: Vec<OsString>, - stdout: Option<Stdio>, - stderr: Option<Stdio>, - env: CommandEnv, -} - -// passed back to std::process with the pipes connected to the child, if any -// were requested -pub struct StdioPipes { - pub stdin: Option<AnonPipe>, - pub stdout: Option<AnonPipe>, - pub stderr: Option<AnonPipe>, -} - -#[derive(Copy, Clone, Debug)] -pub enum Stdio { - Inherit, - Null, - MakePipe, -} - -impl Command { - pub fn new(program: &OsStr) -> Command { - Command { - prog: program.to_os_string(), - args: Vec::new(), - stdout: None, - stderr: None, - env: Default::default(), - } - } - - pub fn arg(&mut self, arg: &OsStr) { - self.args.push(arg.to_os_string()); - } - - pub fn env_mut(&mut self) -> &mut CommandEnv { - &mut self.env - } - - pub fn cwd(&mut self, _dir: &OsStr) { - panic!("unsupported") - } - - pub fn stdin(&mut self, _stdin: Stdio) { - panic!("unsupported") - } - - pub fn stdout(&mut self, stdout: Stdio) { - self.stdout = Some(stdout); - } - - pub fn stderr(&mut self, stderr: Stdio) { - self.stderr = Some(stderr); - } - - pub fn get_program(&self) -> &OsStr { - self.prog.as_ref() - } - - pub fn get_args(&self) -> CommandArgs<'_> { - CommandArgs { iter: self.args.iter() } - } - - pub fn get_envs(&self) -> CommandEnvs<'_> { - self.env.iter() - } - - pub fn get_current_dir(&self) -> Option<&Path> { - None - } - - pub fn spawn( - &mut self, - _default: Stdio, - _needs_stdin: bool, - ) -> io::Result<(Process, StdioPipes)> { - unsupported() - } - - fn create_pipe( - s: Stdio, - ) -> io::Result<Option<helpers::OwnedProtocol<uefi_command_internal::PipeProtocol>>> { - match s { - Stdio::MakePipe => unsafe { - helpers::OwnedProtocol::create( - uefi_command_internal::PipeProtocol::new(), - simple_text_output::PROTOCOL_GUID, - ) - } - .map(Some), - Stdio::Null => unsafe { - helpers::OwnedProtocol::create( - uefi_command_internal::PipeProtocol::null(), - simple_text_output::PROTOCOL_GUID, - ) - } - .map(Some), - Stdio::Inherit => Ok(None), - } - } - - pub fn output(&mut self) -> io::Result<(ExitStatus, Vec<u8>, Vec<u8>)> { - let mut cmd = uefi_command_internal::Image::load_image(&self.prog)?; - - // UEFI adds the bin name by default - if !self.args.is_empty() { - let args = uefi_command_internal::create_args(&self.prog, &self.args); - cmd.set_args(args); - } - - // Setup Stdout - let stdout = self.stdout.unwrap_or(Stdio::MakePipe); - let stdout = Self::create_pipe(stdout)?; - if let Some(con) = stdout { - cmd.stdout_init(con) - } else { - cmd.stdout_inherit() - }; - - // Setup Stderr - let stderr = self.stderr.unwrap_or(Stdio::MakePipe); - let stderr = Self::create_pipe(stderr)?; - if let Some(con) = stderr { - cmd.stderr_init(con) - } else { - cmd.stderr_inherit() - }; - - let env = env_changes(&self.env); - - // Set any new vars - if let Some(e) = &env { - for (k, (_, v)) in e { - match v { - Some(v) => unsafe { crate::env::set_var(k, v) }, - None => unsafe { crate::env::remove_var(k) }, - } - } - } - - let stat = cmd.start_image()?; - - // Rollback any env changes - if let Some(e) = env { - for (k, (v, _)) in e { - match v { - Some(v) => unsafe { crate::env::set_var(k, v) }, - None => unsafe { crate::env::remove_var(k) }, - } - } - } - - let stdout = cmd.stdout()?; - let stderr = cmd.stderr()?; - - Ok((ExitStatus(stat), stdout, stderr)) - } -} - -impl From<AnonPipe> for Stdio { - fn from(pipe: AnonPipe) -> Stdio { - pipe.diverge() - } -} - -impl From<io::Stdout> for Stdio { - fn from(_: io::Stdout) -> Stdio { - // FIXME: This is wrong. - // Instead, the Stdio we have here should be a unit struct. - panic!("unsupported") - } -} - -impl From<io::Stderr> for Stdio { - fn from(_: io::Stderr) -> Stdio { - // FIXME: This is wrong. - // Instead, the Stdio we have here should be a unit struct. - panic!("unsupported") - } -} - -impl From<File> for Stdio { - fn from(_file: File) -> Stdio { - // FIXME: This is wrong. - // Instead, the Stdio we have here should be a unit struct. - panic!("unsupported") - } -} - -#[derive(PartialEq, Eq, Clone, Copy, Debug)] -#[non_exhaustive] -pub struct ExitStatus(r_efi::efi::Status); - -impl ExitStatus { - pub fn exit_ok(&self) -> Result<(), ExitStatusError> { - if self.0 == r_efi::efi::Status::SUCCESS { Ok(()) } else { Err(ExitStatusError(self.0)) } - } - - pub fn code(&self) -> Option<i32> { - Some(self.0.as_usize() as i32) - } -} - -impl fmt::Display for ExitStatus { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let err_str = super::os::error_string(self.0.as_usize()); - write!(f, "{}", err_str) - } -} - -impl Default for ExitStatus { - fn default() -> Self { - ExitStatus(r_efi::efi::Status::SUCCESS) - } -} - -#[derive(Clone, Copy, PartialEq, Eq)] -pub struct ExitStatusError(r_efi::efi::Status); - -impl fmt::Debug for ExitStatusError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let err_str = super::os::error_string(self.0.as_usize()); - write!(f, "{}", err_str) - } -} - -impl Into<ExitStatus> for ExitStatusError { - fn into(self) -> ExitStatus { - ExitStatus(self.0) - } -} - -impl ExitStatusError { - pub fn code(self) -> Option<NonZero<i32>> { - NonZeroI32::new(self.0.as_usize() as i32) - } -} - -#[derive(PartialEq, Eq, Clone, Copy, Debug)] -pub struct ExitCode(bool); - -impl ExitCode { - pub const SUCCESS: ExitCode = ExitCode(false); - pub const FAILURE: ExitCode = ExitCode(true); - - pub fn as_i32(&self) -> i32 { - self.0 as i32 - } -} - -impl From<u8> for ExitCode { - fn from(code: u8) -> Self { - match code { - 0 => Self::SUCCESS, - 1..=255 => Self::FAILURE, - } - } -} - -pub struct Process(!); - -impl Process { - pub fn id(&self) -> u32 { - self.0 - } - - pub fn kill(&mut self) -> io::Result<()> { - self.0 - } - - pub fn wait(&mut self) -> io::Result<ExitStatus> { - self.0 - } - - pub fn try_wait(&mut self) -> io::Result<Option<ExitStatus>> { - self.0 - } -} - -pub struct CommandArgs<'a> { - iter: crate::slice::Iter<'a, OsString>, -} - -impl<'a> Iterator for CommandArgs<'a> { - type Item = &'a OsStr; - - fn next(&mut self) -> Option<&'a OsStr> { - self.iter.next().map(|x| x.as_ref()) - } - - fn size_hint(&self) -> (usize, Option<usize>) { - self.iter.size_hint() - } -} - -impl<'a> ExactSizeIterator for CommandArgs<'a> { - fn len(&self) -> usize { - self.iter.len() - } - - fn is_empty(&self) -> bool { - self.iter.is_empty() - } -} - -impl<'a> fmt::Debug for CommandArgs<'a> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_list().entries(self.iter.clone()).finish() - } -} - -#[allow(dead_code)] -mod uefi_command_internal { - use r_efi::protocols::{loaded_image, simple_text_output}; - - use super::super::helpers; - use crate::ffi::{OsStr, OsString}; - use crate::io::{self, const_error}; - use crate::mem::MaybeUninit; - use crate::os::uefi::env::{boot_services, image_handle, system_table}; - use crate::os::uefi::ffi::{OsStrExt, OsStringExt}; - use crate::ptr::NonNull; - use crate::slice; - use crate::sys::pal::uefi::helpers::OwnedTable; - use crate::sys_common::wstr::WStrUnits; - - pub struct Image { - handle: NonNull<crate::ffi::c_void>, - stdout: Option<helpers::OwnedProtocol<PipeProtocol>>, - stderr: Option<helpers::OwnedProtocol<PipeProtocol>>, - st: OwnedTable<r_efi::efi::SystemTable>, - args: Option<(*mut u16, usize)>, - } - - impl Image { - pub fn load_image(p: &OsStr) -> io::Result<Self> { - let path = helpers::OwnedDevicePath::from_text(p)?; - let boot_services: NonNull<r_efi::efi::BootServices> = boot_services() - .ok_or_else(|| const_error!(io::ErrorKind::NotFound, "Boot Services not found"))? - .cast(); - let mut child_handle: MaybeUninit<r_efi::efi::Handle> = MaybeUninit::uninit(); - let image_handle = image_handle(); - - let r = unsafe { - ((*boot_services.as_ptr()).load_image)( - r_efi::efi::Boolean::FALSE, - image_handle.as_ptr(), - path.as_ptr(), - crate::ptr::null_mut(), - 0, - child_handle.as_mut_ptr(), - ) - }; - - if r.is_error() { - Err(io::Error::from_raw_os_error(r.as_usize())) - } else { - let child_handle = unsafe { child_handle.assume_init() }; - let child_handle = NonNull::new(child_handle).unwrap(); - - let loaded_image: NonNull<loaded_image::Protocol> = - helpers::open_protocol(child_handle, loaded_image::PROTOCOL_GUID).unwrap(); - let st = OwnedTable::from_table(unsafe { (*loaded_image.as_ptr()).system_table }); - - Ok(Self { handle: child_handle, stdout: None, stderr: None, st, args: None }) - } - } - - pub(crate) fn start_image(&mut self) -> io::Result<r_efi::efi::Status> { - self.update_st_crc32()?; - - // Use our system table instead of the default one - let loaded_image: NonNull<loaded_image::Protocol> = - helpers::open_protocol(self.handle, loaded_image::PROTOCOL_GUID).unwrap(); - unsafe { - (*loaded_image.as_ptr()).system_table = self.st.as_mut_ptr(); - } - - let boot_services: NonNull<r_efi::efi::BootServices> = boot_services() - .ok_or_else(|| const_error!(io::ErrorKind::NotFound, "Boot Services not found"))? - .cast(); - let mut exit_data_size: usize = 0; - let mut exit_data: MaybeUninit<*mut u16> = MaybeUninit::uninit(); - - let r = unsafe { - ((*boot_services.as_ptr()).start_image)( - self.handle.as_ptr(), - &mut exit_data_size, - exit_data.as_mut_ptr(), - ) - }; - - // Drop exitdata - if exit_data_size != 0 { - unsafe { - let exit_data = exit_data.assume_init(); - ((*boot_services.as_ptr()).free_pool)(exit_data as *mut crate::ffi::c_void); - } - } - - Ok(r) - } - - fn set_stdout( - &mut self, - handle: r_efi::efi::Handle, - protocol: *mut simple_text_output::Protocol, - ) { - unsafe { - (*self.st.as_mut_ptr()).console_out_handle = handle; - (*self.st.as_mut_ptr()).con_out = protocol; - } - } - - fn set_stderr( - &mut self, - handle: r_efi::efi::Handle, - protocol: *mut simple_text_output::Protocol, - ) { - unsafe { - (*self.st.as_mut_ptr()).standard_error_handle = handle; - (*self.st.as_mut_ptr()).std_err = protocol; - } - } - - pub fn stdout_init(&mut self, protocol: helpers::OwnedProtocol<PipeProtocol>) { - self.set_stdout( - protocol.handle().as_ptr(), - protocol.as_ref() as *const PipeProtocol as *mut simple_text_output::Protocol, - ); - self.stdout = Some(protocol); - } - - pub fn stdout_inherit(&mut self) { - let st: NonNull<r_efi::efi::SystemTable> = system_table().cast(); - unsafe { self.set_stdout((*st.as_ptr()).console_out_handle, (*st.as_ptr()).con_out) } - } - - pub fn stderr_init(&mut self, protocol: helpers::OwnedProtocol<PipeProtocol>) { - self.set_stderr( - protocol.handle().as_ptr(), - protocol.as_ref() as *const PipeProtocol as *mut simple_text_output::Protocol, - ); - self.stderr = Some(protocol); - } - - pub fn stderr_inherit(&mut self) { - let st: NonNull<r_efi::efi::SystemTable> = system_table().cast(); - unsafe { self.set_stderr((*st.as_ptr()).standard_error_handle, (*st.as_ptr()).std_err) } - } - - pub fn stderr(&self) -> io::Result<Vec<u8>> { - match &self.stderr { - Some(stderr) => stderr.as_ref().utf8(), - None => Ok(Vec::new()), - } - } - - pub fn stdout(&self) -> io::Result<Vec<u8>> { - match &self.stdout { - Some(stdout) => stdout.as_ref().utf8(), - None => Ok(Vec::new()), - } - } - - pub fn set_args(&mut self, args: Box<[u16]>) { - let loaded_image: NonNull<loaded_image::Protocol> = - helpers::open_protocol(self.handle, loaded_image::PROTOCOL_GUID).unwrap(); - - let len = args.len(); - let args_size: u32 = (len * size_of::<u16>()).try_into().unwrap(); - let ptr = Box::into_raw(args).as_mut_ptr(); - - unsafe { - (*loaded_image.as_ptr()).load_options = ptr as *mut crate::ffi::c_void; - (*loaded_image.as_ptr()).load_options_size = args_size; - } - - self.args = Some((ptr, len)); - } - - fn update_st_crc32(&mut self) -> io::Result<()> { - let bt: NonNull<r_efi::efi::BootServices> = boot_services().unwrap().cast(); - let st_size = unsafe { (*self.st.as_ptr()).hdr.header_size as usize }; - let mut crc32: u32 = 0; - - // Set crc to 0 before calculation - unsafe { - (*self.st.as_mut_ptr()).hdr.crc32 = 0; - } - - let r = unsafe { - ((*bt.as_ptr()).calculate_crc32)( - self.st.as_mut_ptr() as *mut crate::ffi::c_void, - st_size, - &mut crc32, - ) - }; - - if r.is_error() { - Err(io::Error::from_raw_os_error(r.as_usize())) - } else { - unsafe { - (*self.st.as_mut_ptr()).hdr.crc32 = crc32; - } - Ok(()) - } - } - } - - impl Drop for Image { - fn drop(&mut self) { - if let Some(bt) = boot_services() { - let bt: NonNull<r_efi::efi::BootServices> = bt.cast(); - unsafe { - ((*bt.as_ptr()).unload_image)(self.handle.as_ptr()); - } - } - - if let Some((ptr, len)) = self.args { - let _ = unsafe { Box::from_raw(crate::ptr::slice_from_raw_parts_mut(ptr, len)) }; - } - } - } - - #[repr(C)] - pub struct PipeProtocol { - reset: simple_text_output::ProtocolReset, - output_string: simple_text_output::ProtocolOutputString, - test_string: simple_text_output::ProtocolTestString, - query_mode: simple_text_output::ProtocolQueryMode, - set_mode: simple_text_output::ProtocolSetMode, - set_attribute: simple_text_output::ProtocolSetAttribute, - clear_screen: simple_text_output::ProtocolClearScreen, - set_cursor_position: simple_text_output::ProtocolSetCursorPosition, - enable_cursor: simple_text_output::ProtocolEnableCursor, - mode: *mut simple_text_output::Mode, - _buffer: Vec<u16>, - } - - impl PipeProtocol { - pub fn new() -> Self { - let mode = Box::new(simple_text_output::Mode { - max_mode: 0, - mode: 0, - attribute: 0, - cursor_column: 0, - cursor_row: 0, - cursor_visible: r_efi::efi::Boolean::FALSE, - }); - Self { - reset: Self::reset, - output_string: Self::output_string, - test_string: Self::test_string, - query_mode: Self::query_mode, - set_mode: Self::set_mode, - set_attribute: Self::set_attribute, - clear_screen: Self::clear_screen, - set_cursor_position: Self::set_cursor_position, - enable_cursor: Self::enable_cursor, - mode: Box::into_raw(mode), - _buffer: Vec::new(), - } - } - - pub fn null() -> Self { - let mode = Box::new(simple_text_output::Mode { - max_mode: 0, - mode: 0, - attribute: 0, - cursor_column: 0, - cursor_row: 0, - cursor_visible: r_efi::efi::Boolean::FALSE, - }); - Self { - reset: Self::reset_null, - output_string: Self::output_string_null, - test_string: Self::test_string, - query_mode: Self::query_mode, - set_mode: Self::set_mode, - set_attribute: Self::set_attribute, - clear_screen: Self::clear_screen, - set_cursor_position: Self::set_cursor_position, - enable_cursor: Self::enable_cursor, - mode: Box::into_raw(mode), - _buffer: Vec::new(), - } - } - - pub fn utf8(&self) -> io::Result<Vec<u8>> { - OsString::from_wide(&self._buffer) - .into_string() - .map(Into::into) - .map_err(|_| const_error!(io::ErrorKind::Other, "UTF-8 conversion failed")) - } - - extern "efiapi" fn reset( - proto: *mut simple_text_output::Protocol, - _: r_efi::efi::Boolean, - ) -> r_efi::efi::Status { - let proto: *mut PipeProtocol = proto.cast(); - unsafe { - (*proto)._buffer.clear(); - } - r_efi::efi::Status::SUCCESS - } - - extern "efiapi" fn reset_null( - _: *mut simple_text_output::Protocol, - _: r_efi::efi::Boolean, - ) -> r_efi::efi::Status { - r_efi::efi::Status::SUCCESS - } - - extern "efiapi" fn output_string( - proto: *mut simple_text_output::Protocol, - buf: *mut r_efi::efi::Char16, - ) -> r_efi::efi::Status { - let proto: *mut PipeProtocol = proto.cast(); - let buf_len = unsafe { - if let Some(x) = WStrUnits::new(buf) { - x.count() - } else { - return r_efi::efi::Status::INVALID_PARAMETER; - } - }; - let buf_slice = unsafe { slice::from_raw_parts(buf, buf_len) }; - - unsafe { - (*proto)._buffer.extend_from_slice(buf_slice); - }; - - r_efi::efi::Status::SUCCESS - } - - extern "efiapi" fn output_string_null( - _: *mut simple_text_output::Protocol, - _: *mut r_efi::efi::Char16, - ) -> r_efi::efi::Status { - r_efi::efi::Status::SUCCESS - } - - extern "efiapi" fn test_string( - _: *mut simple_text_output::Protocol, - _: *mut r_efi::efi::Char16, - ) -> r_efi::efi::Status { - r_efi::efi::Status::SUCCESS - } - - extern "efiapi" fn query_mode( - _: *mut simple_text_output::Protocol, - _: usize, - _: *mut usize, - _: *mut usize, - ) -> r_efi::efi::Status { - r_efi::efi::Status::UNSUPPORTED - } - - extern "efiapi" fn set_mode( - _: *mut simple_text_output::Protocol, - _: usize, - ) -> r_efi::efi::Status { - r_efi::efi::Status::UNSUPPORTED - } - - extern "efiapi" fn set_attribute( - _: *mut simple_text_output::Protocol, - _: usize, - ) -> r_efi::efi::Status { - r_efi::efi::Status::UNSUPPORTED - } - - extern "efiapi" fn clear_screen( - _: *mut simple_text_output::Protocol, - ) -> r_efi::efi::Status { - r_efi::efi::Status::UNSUPPORTED - } - - extern "efiapi" fn set_cursor_position( - _: *mut simple_text_output::Protocol, - _: usize, - _: usize, - ) -> r_efi::efi::Status { - r_efi::efi::Status::UNSUPPORTED - } - - extern "efiapi" fn enable_cursor( - _: *mut simple_text_output::Protocol, - _: r_efi::efi::Boolean, - ) -> r_efi::efi::Status { - r_efi::efi::Status::UNSUPPORTED - } - } - - impl Drop for PipeProtocol { - fn drop(&mut self) { - unsafe { - let _ = Box::from_raw(self.mode); - } - } - } - - pub fn create_args(prog: &OsStr, args: &[OsString]) -> Box<[u16]> { - const QUOTE: u16 = 0x0022; - const SPACE: u16 = 0x0020; - const CARET: u16 = 0x005e; - const NULL: u16 = 0; - - // This is the lower bound on the final length under the assumption that - // the arguments only contain ASCII characters. - let mut res = Vec::with_capacity(args.iter().map(|arg| arg.len() + 3).sum()); - - // Wrap program name in quotes to avoid any problems - res.push(QUOTE); - res.extend(prog.encode_wide()); - res.push(QUOTE); - - for arg in args { - res.push(SPACE); - - // Wrap the argument in quotes to be treat as single arg - res.push(QUOTE); - for c in arg.encode_wide() { - // CARET in quotes is used to escape CARET or QUOTE - if c == QUOTE || c == CARET { - res.push(CARET); - } - res.push(c); - } - res.push(QUOTE); - } - - res.into_boxed_slice() - } -} - -/// Create a map of environment variable changes. Allows efficient setting and rolling back of -/// environment variable changes. -/// -/// Entry: (Old Value, New Value) -fn env_changes(env: &CommandEnv) -> Option<BTreeMap<EnvKey, (Option<OsString>, Option<OsString>)>> { - if env.is_unchanged() { - return None; - } - - let mut result = BTreeMap::<EnvKey, (Option<OsString>, Option<OsString>)>::new(); - - // Check if we want to clear all prior variables - if env.does_clear() { - for (k, v) in crate::env::vars_os() { - result.insert(k.into(), (Some(v), None)); - } - } - - for (k, v) in env.iter() { - let v: Option<OsString> = v.map(Into::into); - result - .entry(k.into()) - .and_modify(|cur| *cur = (cur.0.clone(), v.clone())) - .or_insert((crate::env::var_os(k), v)); - } - - Some(result) -} diff --git a/library/std/src/sys/pal/unix/process/zircon.rs b/library/std/src/sys/pal/unix/fuchsia.rs index 7932bd26d76..7932bd26d76 100644 --- a/library/std/src/sys/pal/unix/process/zircon.rs +++ b/library/std/src/sys/pal/unix/fuchsia.rs diff --git a/library/std/src/sys/pal/unix/mod.rs b/library/std/src/sys/pal/unix/mod.rs index e2e537b7bd3..413fda1d8d8 100644 --- a/library/std/src/sys/pal/unix/mod.rs +++ b/library/std/src/sys/pal/unix/mod.rs @@ -9,6 +9,8 @@ pub mod weak; pub mod args; pub mod env; pub mod fd; +#[cfg(target_os = "fuchsia")] +pub mod fuchsia; pub mod futex; #[cfg(any(target_os = "linux", target_os = "android"))] pub mod kernel_copy; @@ -16,7 +18,6 @@ pub mod kernel_copy; pub mod linux; pub mod os; pub mod pipe; -pub mod process; pub mod stack_overflow; pub mod sync; pub mod thread; @@ -419,7 +420,7 @@ cfg_if::cfg_if! { } #[cfg(any(target_os = "espidf", target_os = "horizon", target_os = "vita", target_os = "nuttx"))] -mod unsupported { +pub mod unsupported { use crate::io; pub fn unsupported<T>() -> io::Result<T> { diff --git a/library/std/src/sys/pal/unix/process/mod.rs b/library/std/src/sys/pal/unix/process/mod.rs deleted file mode 100644 index 2751d51c44d..00000000000 --- a/library/std/src/sys/pal/unix/process/mod.rs +++ /dev/null @@ -1,27 +0,0 @@ -pub use self::process_common::{Command, CommandArgs, ExitCode, Stdio, StdioPipes}; -pub use self::process_inner::{ExitStatus, ExitStatusError, Process}; -pub use crate::ffi::OsString as EnvKey; - -#[cfg_attr(any(target_os = "espidf", target_os = "horizon", target_os = "nuttx"), allow(unused))] -mod process_common; - -#[cfg(any(target_os = "espidf", target_os = "horizon", target_os = "vita", target_os = "nuttx"))] -mod process_unsupported; - -cfg_if::cfg_if! { - if #[cfg(target_os = "fuchsia")] { - #[path = "process_fuchsia.rs"] - mod process_inner; - mod zircon; - } else if #[cfg(target_os = "vxworks")] { - #[path = "process_vxworks.rs"] - mod process_inner; - } else if #[cfg(any(target_os = "espidf", target_os = "horizon", target_os = "vita", target_os = "nuttx"))] { - mod process_inner { - pub use super::process_unsupported::*; - } - } else { - #[path = "process_unix.rs"] - mod process_inner; - } -} diff --git a/library/std/src/sys/pal/unix/process/process_common.rs b/library/std/src/sys/pal/unix/process/process_common.rs deleted file mode 100644 index a1c747c8df4..00000000000 --- a/library/std/src/sys/pal/unix/process/process_common.rs +++ /dev/null @@ -1,674 +0,0 @@ -#[cfg(all(test, not(target_os = "emscripten")))] -mod tests; - -use libc::{EXIT_FAILURE, EXIT_SUCCESS, c_char, c_int, gid_t, pid_t, uid_t}; - -use crate::collections::BTreeMap; -use crate::ffi::{CStr, CString, OsStr, OsString}; -use crate::os::unix::prelude::*; -use crate::path::Path; -use crate::sys::fd::FileDesc; -use crate::sys::fs::File; -#[cfg(not(target_os = "fuchsia"))] -use crate::sys::fs::OpenOptions; -use crate::sys::pipe::{self, AnonPipe}; -use crate::sys_common::process::{CommandEnv, CommandEnvs}; -use crate::sys_common::{FromInner, IntoInner}; -use crate::{fmt, io, ptr}; - -cfg_if::cfg_if! { - if #[cfg(target_os = "fuchsia")] { - // fuchsia doesn't have /dev/null - } else if #[cfg(target_os = "vxworks")] { - const DEV_NULL: &CStr = c"/null"; - } else { - const DEV_NULL: &CStr = c"/dev/null"; - } -} - -// Android with api less than 21 define sig* functions inline, so it is not -// available for dynamic link. Implementing sigemptyset and sigaddset allow us -// to support older Android version (independent of libc version). -// The following implementations are based on -// https://github.com/aosp-mirror/platform_bionic/blob/ad8dcd6023294b646e5a8288c0ed431b0845da49/libc/include/android/legacy_signal_inlines.h -cfg_if::cfg_if! { - if #[cfg(target_os = "android")] { - #[allow(dead_code)] - pub unsafe fn sigemptyset(set: *mut libc::sigset_t) -> libc::c_int { - set.write_bytes(0u8, 1); - return 0; - } - - #[allow(dead_code)] - pub unsafe fn sigaddset(set: *mut libc::sigset_t, signum: libc::c_int) -> libc::c_int { - use crate::slice; - use libc::{c_ulong, sigset_t}; - - // The implementations from bionic (android libc) type pun `sigset_t` as an - // array of `c_ulong`. This works, but lets add a smoke check to make sure - // that doesn't change. - const _: () = assert!( - align_of::<c_ulong>() == align_of::<sigset_t>() - && (size_of::<sigset_t>() % size_of::<c_ulong>()) == 0 - ); - - let bit = (signum - 1) as usize; - if set.is_null() || bit >= (8 * size_of::<sigset_t>()) { - crate::sys::pal::unix::os::set_errno(libc::EINVAL); - return -1; - } - let raw = slice::from_raw_parts_mut( - set as *mut c_ulong, - size_of::<sigset_t>() / size_of::<c_ulong>(), - ); - const LONG_BIT: usize = size_of::<c_ulong>() * 8; - raw[bit / LONG_BIT] |= 1 << (bit % LONG_BIT); - return 0; - } - } else { - #[allow(unused_imports)] - pub use libc::{sigemptyset, sigaddset}; - } -} - -//////////////////////////////////////////////////////////////////////////////// -// Command -//////////////////////////////////////////////////////////////////////////////// - -pub struct Command { - program: CString, - args: Vec<CString>, - /// Exactly what will be passed to `execvp`. - /// - /// First element is a pointer to `program`, followed by pointers to - /// `args`, followed by a `null`. Be careful when modifying `program` or - /// `args` to properly update this as well. - argv: Argv, - env: CommandEnv, - - program_kind: ProgramKind, - cwd: Option<CString>, - uid: Option<uid_t>, - gid: Option<gid_t>, - saw_nul: bool, - closures: Vec<Box<dyn FnMut() -> io::Result<()> + Send + Sync>>, - groups: Option<Box<[gid_t]>>, - stdin: Option<Stdio>, - stdout: Option<Stdio>, - stderr: Option<Stdio>, - #[cfg(target_os = "linux")] - create_pidfd: bool, - pgroup: Option<pid_t>, -} - -// Create a new type for argv, so that we can make it `Send` and `Sync` -struct Argv(Vec<*const c_char>); - -// It is safe to make `Argv` `Send` and `Sync`, because it contains -// pointers to memory owned by `Command.args` -unsafe impl Send for Argv {} -unsafe impl Sync for Argv {} - -// passed back to std::process with the pipes connected to the child, if any -// were requested -pub struct StdioPipes { - pub stdin: Option<AnonPipe>, - pub stdout: Option<AnonPipe>, - pub stderr: Option<AnonPipe>, -} - -// passed to do_exec() with configuration of what the child stdio should look -// like -#[cfg_attr(target_os = "vita", allow(dead_code))] -pub struct ChildPipes { - pub stdin: ChildStdio, - pub stdout: ChildStdio, - pub stderr: ChildStdio, -} - -pub enum ChildStdio { - Inherit, - Explicit(c_int), - Owned(FileDesc), - - // On Fuchsia, null stdio is the default, so we simply don't specify - // any actions at the time of spawning. - #[cfg(target_os = "fuchsia")] - Null, -} - -#[derive(Debug)] -pub enum Stdio { - Inherit, - Null, - MakePipe, - Fd(FileDesc), - StaticFd(BorrowedFd<'static>), -} - -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub enum ProgramKind { - /// A program that would be looked up on the PATH (e.g. `ls`) - PathLookup, - /// A relative path (e.g. `my-dir/foo`, `../foo`, `./foo`) - Relative, - /// An absolute path. - Absolute, -} - -impl ProgramKind { - fn new(program: &OsStr) -> Self { - if program.as_encoded_bytes().starts_with(b"/") { - Self::Absolute - } else if program.as_encoded_bytes().contains(&b'/') { - // If the program has more than one component in it, it is a relative path. - Self::Relative - } else { - Self::PathLookup - } - } -} - -impl Command { - #[cfg(not(target_os = "linux"))] - pub fn new(program: &OsStr) -> Command { - let mut saw_nul = false; - let program_kind = ProgramKind::new(program.as_ref()); - let program = os2c(program, &mut saw_nul); - Command { - argv: Argv(vec![program.as_ptr(), ptr::null()]), - args: vec![program.clone()], - program, - program_kind, - env: Default::default(), - cwd: None, - uid: None, - gid: None, - saw_nul, - closures: Vec::new(), - groups: None, - stdin: None, - stdout: None, - stderr: None, - pgroup: None, - } - } - - #[cfg(target_os = "linux")] - pub fn new(program: &OsStr) -> Command { - let mut saw_nul = false; - let program_kind = ProgramKind::new(program.as_ref()); - let program = os2c(program, &mut saw_nul); - Command { - argv: Argv(vec![program.as_ptr(), ptr::null()]), - args: vec![program.clone()], - program, - program_kind, - env: Default::default(), - cwd: None, - uid: None, - gid: None, - saw_nul, - closures: Vec::new(), - groups: None, - stdin: None, - stdout: None, - stderr: None, - create_pidfd: false, - pgroup: None, - } - } - - pub fn set_arg_0(&mut self, arg: &OsStr) { - // Set a new arg0 - let arg = os2c(arg, &mut self.saw_nul); - debug_assert!(self.argv.0.len() > 1); - self.argv.0[0] = arg.as_ptr(); - self.args[0] = arg; - } - - pub fn arg(&mut self, arg: &OsStr) { - // Overwrite the trailing null pointer in `argv` and then add a new null - // pointer. - let arg = os2c(arg, &mut self.saw_nul); - self.argv.0[self.args.len()] = arg.as_ptr(); - self.argv.0.push(ptr::null()); - - // Also make sure we keep track of the owned value to schedule a - // destructor for this memory. - self.args.push(arg); - } - - pub fn cwd(&mut self, dir: &OsStr) { - self.cwd = Some(os2c(dir, &mut self.saw_nul)); - } - pub fn uid(&mut self, id: uid_t) { - self.uid = Some(id); - } - pub fn gid(&mut self, id: gid_t) { - self.gid = Some(id); - } - pub fn groups(&mut self, groups: &[gid_t]) { - self.groups = Some(Box::from(groups)); - } - pub fn pgroup(&mut self, pgroup: pid_t) { - self.pgroup = Some(pgroup); - } - - #[cfg(target_os = "linux")] - pub fn create_pidfd(&mut self, val: bool) { - self.create_pidfd = val; - } - - #[cfg(not(target_os = "linux"))] - #[allow(dead_code)] - pub fn get_create_pidfd(&self) -> bool { - false - } - - #[cfg(target_os = "linux")] - pub fn get_create_pidfd(&self) -> bool { - self.create_pidfd - } - - pub fn saw_nul(&self) -> bool { - self.saw_nul - } - - pub fn get_program(&self) -> &OsStr { - OsStr::from_bytes(self.program.as_bytes()) - } - - #[allow(dead_code)] - pub fn get_program_kind(&self) -> ProgramKind { - self.program_kind - } - - pub fn get_args(&self) -> CommandArgs<'_> { - let mut iter = self.args.iter(); - iter.next(); - CommandArgs { iter } - } - - pub fn get_envs(&self) -> CommandEnvs<'_> { - self.env.iter() - } - - pub fn get_current_dir(&self) -> Option<&Path> { - self.cwd.as_ref().map(|cs| Path::new(OsStr::from_bytes(cs.as_bytes()))) - } - - pub fn get_argv(&self) -> &Vec<*const c_char> { - &self.argv.0 - } - - pub fn get_program_cstr(&self) -> &CStr { - &*self.program - } - - #[allow(dead_code)] - pub fn get_cwd(&self) -> Option<&CStr> { - self.cwd.as_deref() - } - #[allow(dead_code)] - pub fn get_uid(&self) -> Option<uid_t> { - self.uid - } - #[allow(dead_code)] - pub fn get_gid(&self) -> Option<gid_t> { - self.gid - } - #[allow(dead_code)] - pub fn get_groups(&self) -> Option<&[gid_t]> { - self.groups.as_deref() - } - #[allow(dead_code)] - pub fn get_pgroup(&self) -> Option<pid_t> { - self.pgroup - } - - pub fn get_closures(&mut self) -> &mut Vec<Box<dyn FnMut() -> io::Result<()> + Send + Sync>> { - &mut self.closures - } - - pub unsafe fn pre_exec(&mut self, f: Box<dyn FnMut() -> io::Result<()> + Send + Sync>) { - self.closures.push(f); - } - - pub fn stdin(&mut self, stdin: Stdio) { - self.stdin = Some(stdin); - } - - pub fn stdout(&mut self, stdout: Stdio) { - self.stdout = Some(stdout); - } - - pub fn stderr(&mut self, stderr: Stdio) { - self.stderr = Some(stderr); - } - - pub fn env_mut(&mut self) -> &mut CommandEnv { - &mut self.env - } - - pub fn capture_env(&mut self) -> Option<CStringArray> { - let maybe_env = self.env.capture_if_changed(); - maybe_env.map(|env| construct_envp(env, &mut self.saw_nul)) - } - - #[allow(dead_code)] - pub fn env_saw_path(&self) -> bool { - self.env.have_changed_path() - } - - #[allow(dead_code)] - pub fn program_is_path(&self) -> bool { - self.program.to_bytes().contains(&b'/') - } - - pub fn setup_io( - &self, - default: Stdio, - needs_stdin: bool, - ) -> io::Result<(StdioPipes, ChildPipes)> { - let null = Stdio::Null; - let default_stdin = if needs_stdin { &default } else { &null }; - let stdin = self.stdin.as_ref().unwrap_or(default_stdin); - let stdout = self.stdout.as_ref().unwrap_or(&default); - let stderr = self.stderr.as_ref().unwrap_or(&default); - let (their_stdin, our_stdin) = stdin.to_child_stdio(true)?; - let (their_stdout, our_stdout) = stdout.to_child_stdio(false)?; - let (their_stderr, our_stderr) = stderr.to_child_stdio(false)?; - let ours = StdioPipes { stdin: our_stdin, stdout: our_stdout, stderr: our_stderr }; - let theirs = ChildPipes { stdin: their_stdin, stdout: their_stdout, stderr: their_stderr }; - Ok((ours, theirs)) - } -} - -fn os2c(s: &OsStr, saw_nul: &mut bool) -> CString { - CString::new(s.as_bytes()).unwrap_or_else(|_e| { - *saw_nul = true; - c"<string-with-nul>".to_owned() - }) -} - -// Helper type to manage ownership of the strings within a C-style array. -pub struct CStringArray { - items: Vec<CString>, - ptrs: Vec<*const c_char>, -} - -impl CStringArray { - pub fn with_capacity(capacity: usize) -> Self { - let mut result = CStringArray { - items: Vec::with_capacity(capacity), - ptrs: Vec::with_capacity(capacity + 1), - }; - result.ptrs.push(ptr::null()); - result - } - pub fn push(&mut self, item: CString) { - let l = self.ptrs.len(); - self.ptrs[l - 1] = item.as_ptr(); - self.ptrs.push(ptr::null()); - self.items.push(item); - } - pub fn as_ptr(&self) -> *const *const c_char { - self.ptrs.as_ptr() - } -} - -fn construct_envp(env: BTreeMap<OsString, OsString>, saw_nul: &mut bool) -> CStringArray { - let mut result = CStringArray::with_capacity(env.len()); - for (mut k, v) in env { - // Reserve additional space for '=' and null terminator - k.reserve_exact(v.len() + 2); - k.push("="); - k.push(&v); - - // Add the new entry into the array - if let Ok(item) = CString::new(k.into_vec()) { - result.push(item); - } else { - *saw_nul = true; - } - } - - result -} - -impl Stdio { - pub fn to_child_stdio(&self, readable: bool) -> io::Result<(ChildStdio, Option<AnonPipe>)> { - match *self { - Stdio::Inherit => Ok((ChildStdio::Inherit, None)), - - // Make sure that the source descriptors are not an stdio - // descriptor, otherwise the order which we set the child's - // descriptors may blow away a descriptor which we are hoping to - // save. For example, suppose we want the child's stderr to be the - // parent's stdout, and the child's stdout to be the parent's - // stderr. No matter which we dup first, the second will get - // overwritten prematurely. - Stdio::Fd(ref fd) => { - if fd.as_raw_fd() >= 0 && fd.as_raw_fd() <= libc::STDERR_FILENO { - Ok((ChildStdio::Owned(fd.duplicate()?), None)) - } else { - Ok((ChildStdio::Explicit(fd.as_raw_fd()), None)) - } - } - - Stdio::StaticFd(fd) => { - let fd = FileDesc::from_inner(fd.try_clone_to_owned()?); - Ok((ChildStdio::Owned(fd), None)) - } - - Stdio::MakePipe => { - let (reader, writer) = pipe::anon_pipe()?; - let (ours, theirs) = if readable { (writer, reader) } else { (reader, writer) }; - Ok((ChildStdio::Owned(theirs.into_inner()), Some(ours))) - } - - #[cfg(not(target_os = "fuchsia"))] - Stdio::Null => { - let mut opts = OpenOptions::new(); - opts.read(readable); - opts.write(!readable); - let fd = File::open_c(DEV_NULL, &opts)?; - Ok((ChildStdio::Owned(fd.into_inner()), None)) - } - - #[cfg(target_os = "fuchsia")] - Stdio::Null => Ok((ChildStdio::Null, None)), - } - } -} - -impl From<AnonPipe> for Stdio { - fn from(pipe: AnonPipe) -> Stdio { - Stdio::Fd(pipe.into_inner()) - } -} - -impl From<FileDesc> for Stdio { - fn from(fd: FileDesc) -> Stdio { - Stdio::Fd(fd) - } -} - -impl From<File> for Stdio { - fn from(file: File) -> Stdio { - Stdio::Fd(file.into_inner()) - } -} - -impl From<io::Stdout> for Stdio { - fn from(_: io::Stdout) -> Stdio { - // This ought really to be is Stdio::StaticFd(input_argument.as_fd()). - // But AsFd::as_fd takes its argument by reference, and yields - // a bounded lifetime, so it's no use here. There is no AsStaticFd. - // - // Additionally AsFd is only implemented for the *locked* versions. - // We don't want to lock them here. (The implications of not locking - // are the same as those for process::Stdio::inherit().) - // - // Arguably the hypothetical AsStaticFd and AsFd<'static> - // should be implemented for io::Stdout, not just for StdoutLocked. - Stdio::StaticFd(unsafe { BorrowedFd::borrow_raw(libc::STDOUT_FILENO) }) - } -} - -impl From<io::Stderr> for Stdio { - fn from(_: io::Stderr) -> Stdio { - Stdio::StaticFd(unsafe { BorrowedFd::borrow_raw(libc::STDERR_FILENO) }) - } -} - -impl ChildStdio { - pub fn fd(&self) -> Option<c_int> { - match *self { - ChildStdio::Inherit => None, - ChildStdio::Explicit(fd) => Some(fd), - ChildStdio::Owned(ref fd) => Some(fd.as_raw_fd()), - - #[cfg(target_os = "fuchsia")] - ChildStdio::Null => None, - } - } -} - -impl fmt::Debug for Command { - // show all attributes but `self.closures` which does not implement `Debug` - // and `self.argv` which is not useful for debugging - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if f.alternate() { - let mut debug_command = f.debug_struct("Command"); - debug_command.field("program", &self.program).field("args", &self.args); - if !self.env.is_unchanged() { - debug_command.field("env", &self.env); - } - - if self.cwd.is_some() { - debug_command.field("cwd", &self.cwd); - } - if self.uid.is_some() { - debug_command.field("uid", &self.uid); - } - if self.gid.is_some() { - debug_command.field("gid", &self.gid); - } - - if self.groups.is_some() { - debug_command.field("groups", &self.groups); - } - - if self.stdin.is_some() { - debug_command.field("stdin", &self.stdin); - } - if self.stdout.is_some() { - debug_command.field("stdout", &self.stdout); - } - if self.stderr.is_some() { - debug_command.field("stderr", &self.stderr); - } - if self.pgroup.is_some() { - debug_command.field("pgroup", &self.pgroup); - } - - #[cfg(target_os = "linux")] - { - debug_command.field("create_pidfd", &self.create_pidfd); - } - - debug_command.finish() - } else { - if let Some(ref cwd) = self.cwd { - write!(f, "cd {cwd:?} && ")?; - } - if self.env.does_clear() { - write!(f, "env -i ")?; - // Altered env vars will be printed next, that should exactly work as expected. - } else { - // Removed env vars need the command to be wrapped in `env`. - let mut any_removed = false; - for (key, value_opt) in self.get_envs() { - if value_opt.is_none() { - if !any_removed { - write!(f, "env ")?; - any_removed = true; - } - write!(f, "-u {} ", key.to_string_lossy())?; - } - } - } - // Altered env vars can just be added in front of the program. - for (key, value_opt) in self.get_envs() { - if let Some(value) = value_opt { - write!(f, "{}={value:?} ", key.to_string_lossy())?; - } - } - if self.program != self.args[0] { - write!(f, "[{:?}] ", self.program)?; - } - write!(f, "{:?}", self.args[0])?; - - for arg in &self.args[1..] { - write!(f, " {:?}", arg)?; - } - Ok(()) - } - } -} - -#[derive(PartialEq, Eq, Clone, Copy)] -pub struct ExitCode(u8); - -impl fmt::Debug for ExitCode { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_tuple("unix_exit_status").field(&self.0).finish() - } -} - -impl ExitCode { - pub const SUCCESS: ExitCode = ExitCode(EXIT_SUCCESS as _); - pub const FAILURE: ExitCode = ExitCode(EXIT_FAILURE as _); - - #[inline] - pub fn as_i32(&self) -> i32 { - self.0 as i32 - } -} - -impl From<u8> for ExitCode { - fn from(code: u8) -> Self { - Self(code) - } -} - -pub struct CommandArgs<'a> { - iter: crate::slice::Iter<'a, CString>, -} - -impl<'a> Iterator for CommandArgs<'a> { - type Item = &'a OsStr; - fn next(&mut self) -> Option<&'a OsStr> { - self.iter.next().map(|cs| OsStr::from_bytes(cs.as_bytes())) - } - fn size_hint(&self) -> (usize, Option<usize>) { - self.iter.size_hint() - } -} - -impl<'a> ExactSizeIterator for CommandArgs<'a> { - fn len(&self) -> usize { - self.iter.len() - } - fn is_empty(&self) -> bool { - self.iter.is_empty() - } -} - -impl<'a> fmt::Debug for CommandArgs<'a> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_list().entries(self.iter.clone()).finish() - } -} diff --git a/library/std/src/sys/pal/unix/process/process_common/tests.rs b/library/std/src/sys/pal/unix/process/process_common/tests.rs deleted file mode 100644 index e5c8dd6e341..00000000000 --- a/library/std/src/sys/pal/unix/process/process_common/tests.rs +++ /dev/null @@ -1,192 +0,0 @@ -use super::*; -use crate::ffi::OsStr; -use crate::sys::{cvt, cvt_nz}; -use crate::{mem, ptr}; - -macro_rules! t { - ($e:expr) => { - match $e { - Ok(t) => t, - Err(e) => panic!("received error for `{}`: {}", stringify!($e), e), - } - }; -} - -#[test] -#[cfg_attr( - any( - // See #14232 for more information, but it appears that signal delivery to a - // newly spawned process may just be raced in the macOS, so to prevent this - // test from being flaky we ignore it on macOS. - target_os = "macos", - // When run under our current QEMU emulation test suite this test fails, - // although the reason isn't very clear as to why. For now this test is - // ignored there. - target_arch = "arm", - target_arch = "aarch64", - target_arch = "riscv64", - ), - ignore -)] -fn test_process_mask() { - // Test to make sure that a signal mask *does* get inherited. - fn test_inner(mut cmd: Command) { - unsafe { - let mut set = mem::MaybeUninit::<libc::sigset_t>::uninit(); - let mut old_set = mem::MaybeUninit::<libc::sigset_t>::uninit(); - t!(cvt(sigemptyset(set.as_mut_ptr()))); - t!(cvt(sigaddset(set.as_mut_ptr(), libc::SIGINT))); - t!(cvt_nz(libc::pthread_sigmask( - libc::SIG_SETMASK, - set.as_ptr(), - old_set.as_mut_ptr() - ))); - - cmd.stdin(Stdio::MakePipe); - cmd.stdout(Stdio::MakePipe); - - let (mut cat, mut pipes) = t!(cmd.spawn(Stdio::Null, true)); - let stdin_write = pipes.stdin.take().unwrap(); - let stdout_read = pipes.stdout.take().unwrap(); - - t!(cvt_nz(libc::pthread_sigmask(libc::SIG_SETMASK, old_set.as_ptr(), ptr::null_mut()))); - - t!(cvt(libc::kill(cat.id() as libc::pid_t, libc::SIGINT))); - // We need to wait until SIGINT is definitely delivered. The - // easiest way is to write something to cat, and try to read it - // back: if SIGINT is unmasked, it'll get delivered when cat is - // next scheduled. - let _ = stdin_write.write(b"Hello"); - drop(stdin_write); - - // Exactly 5 bytes should be read. - let mut buf = [0; 5]; - let ret = t!(stdout_read.read(&mut buf)); - assert_eq!(ret, 5); - assert_eq!(&buf, b"Hello"); - - t!(cat.wait()); - } - } - - // A plain `Command::new` uses the posix_spawn path on many platforms. - let cmd = Command::new(OsStr::new("cat")); - test_inner(cmd); - - // Specifying `pre_exec` forces the fork/exec path. - let mut cmd = Command::new(OsStr::new("cat")); - unsafe { cmd.pre_exec(Box::new(|| Ok(()))) }; - test_inner(cmd); -} - -#[test] -#[cfg_attr( - any( - // See test_process_mask - target_os = "macos", - target_arch = "arm", - target_arch = "aarch64", - target_arch = "riscv64", - ), - ignore -)] -fn test_process_group_posix_spawn() { - unsafe { - // Spawn a cat subprocess that's just going to hang since there is no I/O. - let mut cmd = Command::new(OsStr::new("cat")); - cmd.pgroup(0); - cmd.stdin(Stdio::MakePipe); - cmd.stdout(Stdio::MakePipe); - let (mut cat, _pipes) = t!(cmd.spawn(Stdio::Null, true)); - - // Check that we can kill its process group, which means there *is* one. - t!(cvt(libc::kill(-(cat.id() as libc::pid_t), libc::SIGINT))); - - t!(cat.wait()); - } -} - -#[test] -#[cfg_attr( - any( - // See test_process_mask - target_os = "macos", - target_arch = "arm", - target_arch = "aarch64", - target_arch = "riscv64", - ), - ignore -)] -fn test_process_group_no_posix_spawn() { - unsafe { - // Same as above, create hang-y cat. This time, force using the non-posix_spawnp path. - let mut cmd = Command::new(OsStr::new("cat")); - cmd.pgroup(0); - cmd.pre_exec(Box::new(|| Ok(()))); // pre_exec forces fork + exec - cmd.stdin(Stdio::MakePipe); - cmd.stdout(Stdio::MakePipe); - let (mut cat, _pipes) = t!(cmd.spawn(Stdio::Null, true)); - - // Check that we can kill its process group, which means there *is* one. - t!(cvt(libc::kill(-(cat.id() as libc::pid_t), libc::SIGINT))); - - t!(cat.wait()); - } -} - -#[test] -fn test_program_kind() { - let vectors = &[ - ("foo", ProgramKind::PathLookup), - ("foo.out", ProgramKind::PathLookup), - ("./foo", ProgramKind::Relative), - ("../foo", ProgramKind::Relative), - ("dir/foo", ProgramKind::Relative), - // Note that paths on Unix can't contain / in them, so this is actually the directory "fo\\" - // followed by the file "o". - ("fo\\/o", ProgramKind::Relative), - ("/foo", ProgramKind::Absolute), - ("/dir/../foo", ProgramKind::Absolute), - ]; - - for (program, expected_kind) in vectors { - assert_eq!( - ProgramKind::new(program.as_ref()), - *expected_kind, - "actual != expected program kind for input {program}", - ); - } -} - -// Test that Rust std handles wait status values (`ExitStatus`) the way that Unix does, -// at least for the values which represent a Unix exit status (`ExitCode`). -// Should work on every #[cfg(unix)] platform. However: -#[cfg(not(any( - // Fuchsia is not Unix and has totally broken std::os::unix. - // https://github.com/rust-lang/rust/issues/58590#issuecomment-836535609 - target_os = "fuchsia", -)))] -#[test] -fn unix_exit_statuses() { - use crate::num::NonZero; - use crate::os::unix::process::ExitStatusExt; - use crate::process::*; - - for exit_code in 0..=0xff { - // FIXME impl From<ExitCode> for ExitStatus and then test that here too; - // the two ExitStatus values should be the same - let raw_wait_status = exit_code << 8; - let exit_status = ExitStatus::from_raw(raw_wait_status); - - assert_eq!(exit_status.code(), Some(exit_code)); - - if let Ok(nz) = NonZero::try_from(exit_code) { - assert!(!exit_status.success()); - let es_error = exit_status.exit_ok().unwrap_err(); - assert_eq!(es_error.code().unwrap(), i32::from(nz)); - } else { - assert!(exit_status.success()); - assert_eq!(exit_status.exit_ok(), Ok(())); - } - } -} diff --git a/library/std/src/sys/pal/unix/process/process_fuchsia.rs b/library/std/src/sys/pal/unix/process/process_fuchsia.rs deleted file mode 100644 index 05c9ace470e..00000000000 --- a/library/std/src/sys/pal/unix/process/process_fuchsia.rs +++ /dev/null @@ -1,326 +0,0 @@ -use libc::{c_int, size_t}; - -use crate::num::NonZero; -use crate::sys::process::process_common::*; -use crate::sys::process::zircon::{Handle, zx_handle_t}; -use crate::{fmt, io, mem, ptr}; - -//////////////////////////////////////////////////////////////////////////////// -// Command -//////////////////////////////////////////////////////////////////////////////// - -impl Command { - pub fn spawn( - &mut self, - default: Stdio, - needs_stdin: bool, - ) -> io::Result<(Process, StdioPipes)> { - let envp = self.capture_env(); - - if self.saw_nul() { - return Err(io::const_error!( - io::ErrorKind::InvalidInput, - "nul byte found in provided data", - )); - } - - let (ours, theirs) = self.setup_io(default, needs_stdin)?; - - let process_handle = unsafe { self.do_exec(theirs, envp.as_ref())? }; - - Ok((Process { handle: Handle::new(process_handle) }, ours)) - } - - pub fn output(&mut self) -> io::Result<(ExitStatus, Vec<u8>, Vec<u8>)> { - let (proc, pipes) = self.spawn(Stdio::MakePipe, false)?; - crate::sys_common::process::wait_with_output(proc, pipes) - } - - pub fn exec(&mut self, default: Stdio) -> io::Error { - if self.saw_nul() { - return io::const_error!( - io::ErrorKind::InvalidInput, - "nul byte found in provided data", - ); - } - - match self.setup_io(default, true) { - Ok((_, _)) => { - // FIXME: This is tough because we don't support the exec syscalls - unimplemented!(); - } - Err(e) => e, - } - } - - unsafe fn do_exec( - &mut self, - stdio: ChildPipes, - maybe_envp: Option<&CStringArray>, - ) -> io::Result<zx_handle_t> { - use crate::sys::process::zircon::*; - - let envp = match maybe_envp { - // None means to clone the current environment, which is done in the - // flags below. - None => ptr::null(), - Some(envp) => envp.as_ptr(), - }; - - let make_action = |local_io: &ChildStdio, target_fd| -> io::Result<fdio_spawn_action_t> { - if let Some(local_fd) = local_io.fd() { - Ok(fdio_spawn_action_t { - action: FDIO_SPAWN_ACTION_TRANSFER_FD, - local_fd, - target_fd, - ..Default::default() - }) - } else { - if let ChildStdio::Null = local_io { - // acts as no-op - return Ok(Default::default()); - } - - let mut handle = ZX_HANDLE_INVALID; - let status = fdio_fd_clone(target_fd, &mut handle); - if status == ERR_INVALID_ARGS || status == ERR_NOT_SUPPORTED { - // This descriptor is closed; skip it rather than generating an - // error. - return Ok(Default::default()); - } - zx_cvt(status)?; - - let mut cloned_fd = 0; - zx_cvt(fdio_fd_create(handle, &mut cloned_fd))?; - - Ok(fdio_spawn_action_t { - action: FDIO_SPAWN_ACTION_TRANSFER_FD, - local_fd: cloned_fd as i32, - target_fd, - ..Default::default() - }) - } - }; - - // Clone stdin, stdout, and stderr - let action1 = make_action(&stdio.stdin, 0)?; - let action2 = make_action(&stdio.stdout, 1)?; - let action3 = make_action(&stdio.stderr, 2)?; - let actions = [action1, action2, action3]; - - // We don't want FileDesc::drop to be called on any stdio. fdio_spawn_etc - // always consumes transferred file descriptors. - mem::forget(stdio); - - for callback in self.get_closures().iter_mut() { - callback()?; - } - - let mut process_handle: zx_handle_t = 0; - zx_cvt(fdio_spawn_etc( - ZX_HANDLE_INVALID, - FDIO_SPAWN_CLONE_JOB - | FDIO_SPAWN_CLONE_LDSVC - | FDIO_SPAWN_CLONE_NAMESPACE - | FDIO_SPAWN_CLONE_ENVIRON // this is ignored when envp is non-null - | FDIO_SPAWN_CLONE_UTC_CLOCK, - self.get_program_cstr().as_ptr(), - self.get_argv().as_ptr(), - envp, - actions.len() as size_t, - actions.as_ptr(), - &mut process_handle, - ptr::null_mut(), - ))?; - // FIXME: See if we want to do something with that err_msg - - Ok(process_handle) - } -} - -//////////////////////////////////////////////////////////////////////////////// -// Processes -//////////////////////////////////////////////////////////////////////////////// - -pub struct Process { - handle: Handle, -} - -impl Process { - pub fn id(&self) -> u32 { - self.handle.raw() as u32 - } - - pub fn kill(&mut self) -> io::Result<()> { - use crate::sys::process::zircon::*; - - unsafe { - zx_cvt(zx_task_kill(self.handle.raw()))?; - } - - Ok(()) - } - - pub fn wait(&mut self) -> io::Result<ExitStatus> { - use crate::sys::process::zircon::*; - - let mut proc_info: zx_info_process_t = Default::default(); - let mut actual: size_t = 0; - let mut avail: size_t = 0; - - unsafe { - zx_cvt(zx_object_wait_one( - self.handle.raw(), - ZX_TASK_TERMINATED, - ZX_TIME_INFINITE, - ptr::null_mut(), - ))?; - zx_cvt(zx_object_get_info( - self.handle.raw(), - ZX_INFO_PROCESS, - (&raw mut proc_info) as *mut libc::c_void, - size_of::<zx_info_process_t>(), - &mut actual, - &mut avail, - ))?; - } - if actual != 1 { - return Err(io::const_error!( - io::ErrorKind::InvalidData, - "failed to get exit status of process", - )); - } - Ok(ExitStatus(proc_info.return_code)) - } - - pub fn try_wait(&mut self) -> io::Result<Option<ExitStatus>> { - use crate::sys::process::zircon::*; - - let mut proc_info: zx_info_process_t = Default::default(); - let mut actual: size_t = 0; - let mut avail: size_t = 0; - - unsafe { - let status = - zx_object_wait_one(self.handle.raw(), ZX_TASK_TERMINATED, 0, ptr::null_mut()); - match status { - 0 => {} // Success - x if x == ERR_TIMED_OUT => { - return Ok(None); - } - _ => { - panic!("Failed to wait on process handle: {status}"); - } - } - zx_cvt(zx_object_get_info( - self.handle.raw(), - ZX_INFO_PROCESS, - (&raw mut proc_info) as *mut libc::c_void, - size_of::<zx_info_process_t>(), - &mut actual, - &mut avail, - ))?; - } - if actual != 1 { - return Err(io::const_error!( - io::ErrorKind::InvalidData, - "failed to get exit status of process", - )); - } - Ok(Some(ExitStatus(proc_info.return_code))) - } -} - -#[derive(PartialEq, Eq, Clone, Copy, Debug, Default)] -pub struct ExitStatus(i64); - -impl ExitStatus { - pub fn exit_ok(&self) -> Result<(), ExitStatusError> { - match NonZero::try_from(self.0) { - /* was nonzero */ Ok(failure) => Err(ExitStatusError(failure)), - /* was zero, couldn't convert */ Err(_) => Ok(()), - } - } - - pub fn code(&self) -> Option<i32> { - // FIXME: support extracting return code as an i64 - self.0.try_into().ok() - } - - pub fn signal(&self) -> Option<i32> { - None - } - - // FIXME: The actually-Unix implementation in process_unix.rs uses WSTOPSIG, WCOREDUMP et al. - // I infer from the implementation of `success`, `code` and `signal` above that these are not - // available on Fuchsia. - // - // It does not appear that Fuchsia is Unix-like enough to implement ExitStatus (or indeed many - // other things from std::os::unix) properly. This veneer is always going to be a bodge. So - // while I don't know if these implementations are actually correct, I think they will do for - // now at least. - pub fn core_dumped(&self) -> bool { - false - } - pub fn stopped_signal(&self) -> Option<i32> { - None - } - pub fn continued(&self) -> bool { - false - } - - pub fn into_raw(&self) -> c_int { - // We don't know what someone who calls into_raw() will do with this value, but it should - // have the conventional Unix representation. Despite the fact that this is not - // standardised in SuS or POSIX, all Unix systems encode the signal and exit status the - // same way. (Ie the WIFEXITED, WEXITSTATUS etc. macros have identical behavior on every - // Unix.) - // - // The caller of `std::os::unix::into_raw` is probably wanting a Unix exit status, and may - // do their own shifting and masking, or even pass the status to another computer running a - // different Unix variant. - // - // The other view would be to say that the caller on Fuchsia ought to know that `into_raw` - // will give a raw Fuchsia status (whatever that is - I don't know, personally). That is - // not possible here because we must return a c_int because that's what Unix (including - // SuS and POSIX) say a wait status is, but Fuchsia apparently uses a u64, so it won't - // necessarily fit. - // - // It seems to me that the right answer would be to provide std::os::fuchsia with its - // own ExitStatusExt, rather that trying to provide a not very convincing imitation of - // Unix. Ie, std::os::unix::process:ExitStatusExt ought not to exist on Fuchsia. But - // fixing this up that is beyond the scope of my efforts now. - let exit_status_as_if_unix: u8 = self.0.try_into().expect("Fuchsia process return code bigger than 8 bits, but std::os::unix::ExitStatusExt::into_raw() was called to try to convert the value into a traditional Unix-style wait status, which cannot represent values greater than 255."); - let wait_status_as_if_unix = (exit_status_as_if_unix as c_int) << 8; - wait_status_as_if_unix - } -} - -/// Converts a raw `c_int` to a type-safe `ExitStatus` by wrapping it without copying. -impl From<c_int> for ExitStatus { - fn from(a: c_int) -> ExitStatus { - ExitStatus(a as i64) - } -} - -impl fmt::Display for ExitStatus { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "exit code: {}", self.0) - } -} - -#[derive(PartialEq, Eq, Clone, Copy, Debug)] -pub struct ExitStatusError(NonZero<i64>); - -impl Into<ExitStatus> for ExitStatusError { - fn into(self) -> ExitStatus { - ExitStatus(self.0.into()) - } -} - -impl ExitStatusError { - pub fn code(self) -> Option<NonZero<i32>> { - // fixme: affected by the same bug as ExitStatus::code() - ExitStatus(self.0.into()).code().map(|st| st.try_into().unwrap()) - } -} diff --git a/library/std/src/sys/pal/unix/process/process_unix.rs b/library/std/src/sys/pal/unix/process/process_unix.rs deleted file mode 100644 index f19512233d8..00000000000 --- a/library/std/src/sys/pal/unix/process/process_unix.rs +++ /dev/null @@ -1,1270 +0,0 @@ -#[cfg(target_os = "vxworks")] -use libc::RTP_ID as pid_t; -#[cfg(not(target_os = "vxworks"))] -use libc::{c_int, pid_t}; -#[cfg(not(any( - target_os = "vxworks", - target_os = "l4re", - target_os = "tvos", - target_os = "watchos", -)))] -use libc::{gid_t, uid_t}; - -use crate::io::{self, Error, ErrorKind}; -use crate::num::NonZero; -use crate::sys::cvt; -#[cfg(target_os = "linux")] -use crate::sys::pal::unix::linux::pidfd::PidFd; -use crate::sys::process::process_common::*; -use crate::{fmt, mem, sys}; - -cfg_if::cfg_if! { - if #[cfg(target_os = "nto")] { - use crate::thread; - use libc::{c_char, posix_spawn_file_actions_t, posix_spawnattr_t}; - use crate::time::Duration; - use crate::sync::LazyLock; - // Get smallest amount of time we can sleep. - // Return a common value if it cannot be determined. - fn get_clock_resolution() -> Duration { - static MIN_DELAY: LazyLock<Duration, fn() -> Duration> = LazyLock::new(|| { - let mut mindelay = libc::timespec { tv_sec: 0, tv_nsec: 0 }; - if unsafe { libc::clock_getres(libc::CLOCK_MONOTONIC, &mut mindelay) } == 0 - { - Duration::from_nanos(mindelay.tv_nsec as u64) - } else { - Duration::from_millis(1) - } - }); - *MIN_DELAY - } - // Arbitrary minimum sleep duration for retrying fork/spawn - const MIN_FORKSPAWN_SLEEP: Duration = Duration::from_nanos(1); - // Maximum duration of sleeping before giving up and returning an error - const MAX_FORKSPAWN_SLEEP: Duration = Duration::from_millis(1000); - } -} - -//////////////////////////////////////////////////////////////////////////////// -// Command -//////////////////////////////////////////////////////////////////////////////// - -impl Command { - pub fn spawn( - &mut self, - default: Stdio, - needs_stdin: bool, - ) -> io::Result<(Process, StdioPipes)> { - const CLOEXEC_MSG_FOOTER: [u8; 4] = *b"NOEX"; - - let envp = self.capture_env(); - - if self.saw_nul() { - return Err(io::const_error!( - ErrorKind::InvalidInput, - "nul byte found in provided data", - )); - } - - let (ours, theirs) = self.setup_io(default, needs_stdin)?; - - if let Some(ret) = self.posix_spawn(&theirs, envp.as_ref())? { - return Ok((ret, ours)); - } - - #[cfg(target_os = "linux")] - let (input, output) = sys::net::Socket::new_pair(libc::AF_UNIX, libc::SOCK_SEQPACKET)?; - - #[cfg(not(target_os = "linux"))] - let (input, output) = sys::pipe::anon_pipe()?; - - // Whatever happens after the fork is almost for sure going to touch or - // look at the environment in one way or another (PATH in `execvp` or - // accessing the `environ` pointer ourselves). Make sure no other thread - // is accessing the environment when we do the fork itself. - // - // Note that as soon as we're done with the fork there's no need to hold - // a lock any more because the parent won't do anything and the child is - // in its own process. Thus the parent drops the lock guard immediately. - // The child calls `mem::forget` to leak the lock, which is crucial because - // releasing a lock is not async-signal-safe. - let env_lock = sys::os::env_read_lock(); - let pid = unsafe { self.do_fork()? }; - - if pid == 0 { - crate::panic::always_abort(); - mem::forget(env_lock); // avoid non-async-signal-safe unlocking - drop(input); - #[cfg(target_os = "linux")] - if self.get_create_pidfd() { - self.send_pidfd(&output); - } - let Err(err) = unsafe { self.do_exec(theirs, envp.as_ref()) }; - let errno = err.raw_os_error().unwrap_or(libc::EINVAL) as u32; - let errno = errno.to_be_bytes(); - let bytes = [ - errno[0], - errno[1], - errno[2], - errno[3], - CLOEXEC_MSG_FOOTER[0], - CLOEXEC_MSG_FOOTER[1], - CLOEXEC_MSG_FOOTER[2], - CLOEXEC_MSG_FOOTER[3], - ]; - // pipe I/O up to PIPE_BUF bytes should be atomic, and then - // we want to be sure we *don't* run at_exit destructors as - // we're being torn down regardless - rtassert!(output.write(&bytes).is_ok()); - unsafe { libc::_exit(1) } - } - - drop(env_lock); - drop(output); - - #[cfg(target_os = "linux")] - let pidfd = if self.get_create_pidfd() { self.recv_pidfd(&input) } else { -1 }; - - #[cfg(not(target_os = "linux"))] - let pidfd = -1; - - // Safety: We obtained the pidfd (on Linux) using SOCK_SEQPACKET, so it's valid. - let mut p = unsafe { Process::new(pid, pidfd) }; - let mut bytes = [0; 8]; - - // loop to handle EINTR - loop { - match input.read(&mut bytes) { - Ok(0) => return Ok((p, ours)), - Ok(8) => { - let (errno, footer) = bytes.split_at(4); - assert_eq!( - CLOEXEC_MSG_FOOTER, footer, - "Validation on the CLOEXEC pipe failed: {:?}", - bytes - ); - let errno = i32::from_be_bytes(errno.try_into().unwrap()); - assert!(p.wait().is_ok(), "wait() should either return Ok or panic"); - return Err(Error::from_raw_os_error(errno)); - } - Err(ref e) if e.is_interrupted() => {} - Err(e) => { - assert!(p.wait().is_ok(), "wait() should either return Ok or panic"); - panic!("the CLOEXEC pipe failed: {e:?}") - } - Ok(..) => { - // pipe I/O up to PIPE_BUF bytes should be atomic - // similarly SOCK_SEQPACKET messages should arrive whole - assert!(p.wait().is_ok(), "wait() should either return Ok or panic"); - panic!("short read on the CLOEXEC pipe") - } - } - } - } - - pub fn output(&mut self) -> io::Result<(ExitStatus, Vec<u8>, Vec<u8>)> { - let (proc, pipes) = self.spawn(Stdio::MakePipe, false)?; - crate::sys_common::process::wait_with_output(proc, pipes) - } - - // WatchOS and TVOS headers mark the `fork`/`exec*` functions with - // `__WATCHOS_PROHIBITED __TVOS_PROHIBITED`, and indicate that the - // `posix_spawn*` functions should be used instead. It isn't entirely clear - // what `PROHIBITED` means here (e.g. if calls to these functions are - // allowed to exist in dead code), but it sounds bad, so we go out of our - // way to avoid that all-together. - #[cfg(any(target_os = "tvos", target_os = "watchos"))] - const ERR_APPLE_TV_WATCH_NO_FORK_EXEC: Error = io::const_error!( - ErrorKind::Unsupported, - "`fork`+`exec`-based process spawning is not supported on this target", - ); - - #[cfg(any(target_os = "tvos", target_os = "watchos"))] - unsafe fn do_fork(&mut self) -> Result<pid_t, io::Error> { - return Err(Self::ERR_APPLE_TV_WATCH_NO_FORK_EXEC); - } - - // Attempts to fork the process. If successful, returns Ok((0, -1)) - // in the child, and Ok((child_pid, -1)) in the parent. - #[cfg(not(any(target_os = "watchos", target_os = "tvos", target_os = "nto")))] - unsafe fn do_fork(&mut self) -> Result<pid_t, io::Error> { - cvt(libc::fork()) - } - - // On QNX Neutrino, fork can fail with EBADF in case "another thread might have opened - // or closed a file descriptor while the fork() was occurring". - // Documentation says "... or try calling fork() again". This is what we do here. - // See also https://www.qnx.com/developers/docs/7.1/#com.qnx.doc.neutrino.lib_ref/topic/f/fork.html - #[cfg(target_os = "nto")] - unsafe fn do_fork(&mut self) -> Result<pid_t, io::Error> { - use crate::sys::os::errno; - - let mut delay = MIN_FORKSPAWN_SLEEP; - - loop { - let r = libc::fork(); - if r == -1 as libc::pid_t && errno() as libc::c_int == libc::EBADF { - if delay < get_clock_resolution() { - // We cannot sleep this short (it would be longer). - // Yield instead. - thread::yield_now(); - } else if delay < MAX_FORKSPAWN_SLEEP { - thread::sleep(delay); - } else { - return Err(io::const_error!( - ErrorKind::WouldBlock, - "forking returned EBADF too often", - )); - } - delay *= 2; - continue; - } else { - return cvt(r); - } - } - } - - pub fn exec(&mut self, default: Stdio) -> io::Error { - let envp = self.capture_env(); - - if self.saw_nul() { - return io::const_error!(ErrorKind::InvalidInput, "nul byte found in provided data"); - } - - match self.setup_io(default, true) { - Ok((_, theirs)) => { - unsafe { - // Similar to when forking, we want to ensure that access to - // the environment is synchronized, so make sure to grab the - // environment lock before we try to exec. - let _lock = sys::os::env_read_lock(); - - let Err(e) = self.do_exec(theirs, envp.as_ref()); - e - } - } - Err(e) => e, - } - } - - // And at this point we've reached a special time in the life of the - // child. The child must now be considered hamstrung and unable to - // do anything other than syscalls really. Consider the following - // scenario: - // - // 1. Thread A of process 1 grabs the malloc() mutex - // 2. Thread B of process 1 forks(), creating thread C - // 3. Thread C of process 2 then attempts to malloc() - // 4. The memory of process 2 is the same as the memory of - // process 1, so the mutex is locked. - // - // This situation looks a lot like deadlock, right? It turns out - // that this is what pthread_atfork() takes care of, which is - // presumably implemented across platforms. The first thing that - // threads to *before* forking is to do things like grab the malloc - // mutex, and then after the fork they unlock it. - // - // Despite this information, libnative's spawn has been witnessed to - // deadlock on both macOS and FreeBSD. I'm not entirely sure why, but - // all collected backtraces point at malloc/free traffic in the - // child spawned process. - // - // For this reason, the block of code below should contain 0 - // invocations of either malloc of free (or their related friends). - // - // As an example of not having malloc/free traffic, we don't close - // this file descriptor by dropping the FileDesc (which contains an - // allocation). Instead we just close it manually. This will never - // have the drop glue anyway because this code never returns (the - // child will either exec() or invoke libc::exit) - #[cfg(not(any(target_os = "tvos", target_os = "watchos")))] - unsafe fn do_exec( - &mut self, - stdio: ChildPipes, - maybe_envp: Option<&CStringArray>, - ) -> Result<!, io::Error> { - use crate::sys::{self, cvt_r}; - - if let Some(fd) = stdio.stdin.fd() { - cvt_r(|| libc::dup2(fd, libc::STDIN_FILENO))?; - } - if let Some(fd) = stdio.stdout.fd() { - cvt_r(|| libc::dup2(fd, libc::STDOUT_FILENO))?; - } - if let Some(fd) = stdio.stderr.fd() { - cvt_r(|| libc::dup2(fd, libc::STDERR_FILENO))?; - } - - #[cfg(not(target_os = "l4re"))] - { - if let Some(_g) = self.get_groups() { - //FIXME: Redox kernel does not support setgroups yet - #[cfg(not(target_os = "redox"))] - cvt(libc::setgroups(_g.len().try_into().unwrap(), _g.as_ptr()))?; - } - if let Some(u) = self.get_gid() { - cvt(libc::setgid(u as gid_t))?; - } - if let Some(u) = self.get_uid() { - // When dropping privileges from root, the `setgroups` call - // will remove any extraneous groups. We only drop groups - // if we have CAP_SETGID and we weren't given an explicit - // set of groups. If we don't call this, then even though our - // uid has dropped, we may still have groups that enable us to - // do super-user things. - //FIXME: Redox kernel does not support setgroups yet - #[cfg(not(target_os = "redox"))] - if self.get_groups().is_none() { - let res = cvt(libc::setgroups(0, crate::ptr::null())); - if let Err(e) = res { - // Here we ignore the case of not having CAP_SETGID. - // An alternative would be to require CAP_SETGID (in - // addition to CAP_SETUID) for setting the UID. - if e.raw_os_error() != Some(libc::EPERM) { - return Err(e.into()); - } - } - } - cvt(libc::setuid(u as uid_t))?; - } - } - if let Some(cwd) = self.get_cwd() { - cvt(libc::chdir(cwd.as_ptr()))?; - } - - if let Some(pgroup) = self.get_pgroup() { - cvt(libc::setpgid(0, pgroup))?; - } - - // emscripten has no signal support. - #[cfg(not(target_os = "emscripten"))] - { - // Inherit the signal mask from the parent rather than resetting it (i.e. do not call - // pthread_sigmask). - - // If -Zon-broken-pipe is used, don't reset SIGPIPE to SIG_DFL. - // If -Zon-broken-pipe is not used, reset SIGPIPE to SIG_DFL for backward compatibility. - // - // -Zon-broken-pipe is an opportunity to change the default here. - if !crate::sys::pal::on_broken_pipe_flag_used() { - #[cfg(target_os = "android")] // see issue #88585 - { - let mut action: libc::sigaction = mem::zeroed(); - action.sa_sigaction = libc::SIG_DFL; - cvt(libc::sigaction(libc::SIGPIPE, &action, crate::ptr::null_mut()))?; - } - #[cfg(not(target_os = "android"))] - { - let ret = sys::signal(libc::SIGPIPE, libc::SIG_DFL); - if ret == libc::SIG_ERR { - return Err(io::Error::last_os_error()); - } - } - #[cfg(target_os = "hurd")] - { - let ret = sys::signal(libc::SIGLOST, libc::SIG_DFL); - if ret == libc::SIG_ERR { - return Err(io::Error::last_os_error()); - } - } - } - } - - for callback in self.get_closures().iter_mut() { - callback()?; - } - - // Although we're performing an exec here we may also return with an - // error from this function (without actually exec'ing) in which case we - // want to be sure to restore the global environment back to what it - // once was, ensuring that our temporary override, when free'd, doesn't - // corrupt our process's environment. - let mut _reset = None; - if let Some(envp) = maybe_envp { - struct Reset(*const *const libc::c_char); - - impl Drop for Reset { - fn drop(&mut self) { - unsafe { - *sys::os::environ() = self.0; - } - } - } - - _reset = Some(Reset(*sys::os::environ())); - *sys::os::environ() = envp.as_ptr(); - } - - libc::execvp(self.get_program_cstr().as_ptr(), self.get_argv().as_ptr()); - Err(io::Error::last_os_error()) - } - - #[cfg(any(target_os = "tvos", target_os = "watchos"))] - unsafe fn do_exec( - &mut self, - _stdio: ChildPipes, - _maybe_envp: Option<&CStringArray>, - ) -> Result<!, io::Error> { - return Err(Self::ERR_APPLE_TV_WATCH_NO_FORK_EXEC); - } - - #[cfg(not(any( - target_os = "freebsd", - target_os = "illumos", - all(target_os = "linux", target_env = "gnu"), - all(target_os = "linux", target_env = "musl"), - target_os = "nto", - target_vendor = "apple", - )))] - fn posix_spawn( - &mut self, - _: &ChildPipes, - _: Option<&CStringArray>, - ) -> io::Result<Option<Process>> { - Ok(None) - } - - // Only support platforms for which posix_spawn() can return ENOENT - // directly. - #[cfg(any( - target_os = "freebsd", - target_os = "illumos", - all(target_os = "linux", target_env = "gnu"), - all(target_os = "linux", target_env = "musl"), - target_os = "nto", - target_vendor = "apple", - ))] - // FIXME(#115199): Rust currently omits weak function definitions - // and its metadata from LLVM IR. - #[cfg_attr(target_os = "linux", no_sanitize(cfi))] - fn posix_spawn( - &mut self, - stdio: &ChildPipes, - envp: Option<&CStringArray>, - ) -> io::Result<Option<Process>> { - #[cfg(target_os = "linux")] - use core::sync::atomic::{AtomicU8, Ordering}; - - use crate::mem::MaybeUninit; - use crate::sys::{self, cvt_nz, on_broken_pipe_flag_used}; - - if self.get_gid().is_some() - || self.get_uid().is_some() - || (self.env_saw_path() && !self.program_is_path()) - || !self.get_closures().is_empty() - || self.get_groups().is_some() - { - return Ok(None); - } - - cfg_if::cfg_if! { - if #[cfg(target_os = "linux")] { - use crate::sys::weak::weak; - - weak! { - fn pidfd_spawnp( - *mut libc::c_int, - *const libc::c_char, - *const libc::posix_spawn_file_actions_t, - *const libc::posix_spawnattr_t, - *const *mut libc::c_char, - *const *mut libc::c_char - ) -> libc::c_int - } - - weak! { fn pidfd_getpid(libc::c_int) -> libc::c_int } - - static PIDFD_SUPPORTED: AtomicU8 = AtomicU8::new(0); - const UNKNOWN: u8 = 0; - const SPAWN: u8 = 1; - // Obtaining a pidfd via the fork+exec path might work - const FORK_EXEC: u8 = 2; - // Neither pidfd_spawn nor fork/exec will get us a pidfd. - // Instead we'll just posix_spawn if the other preconditions are met. - const NO: u8 = 3; - - if self.get_create_pidfd() { - let mut support = PIDFD_SUPPORTED.load(Ordering::Relaxed); - if support == FORK_EXEC { - return Ok(None); - } - if support == UNKNOWN { - support = NO; - let our_pid = crate::process::id(); - let pidfd = cvt(unsafe { libc::syscall(libc::SYS_pidfd_open, our_pid, 0) } as c_int); - match pidfd { - Ok(pidfd) => { - support = FORK_EXEC; - if let Some(Ok(pid)) = pidfd_getpid.get().map(|f| cvt(unsafe { f(pidfd) } as i32)) { - if pidfd_spawnp.get().is_some() && pid as u32 == our_pid { - support = SPAWN - } - } - unsafe { libc::close(pidfd) }; - } - Err(e) if e.raw_os_error() == Some(libc::EMFILE) => { - // We're temporarily(?) out of file descriptors. In this case obtaining a pidfd would also fail - // Don't update the support flag so we can probe again later. - return Err(e) - } - _ => {} - } - PIDFD_SUPPORTED.store(support, Ordering::Relaxed); - if support == FORK_EXEC { - return Ok(None); - } - } - core::assert_matches::debug_assert_matches!(support, SPAWN | NO); - } - } else { - if self.get_create_pidfd() { - unreachable!("only implemented on linux") - } - } - } - - // Only glibc 2.24+ posix_spawn() supports returning ENOENT directly. - #[cfg(all(target_os = "linux", target_env = "gnu"))] - { - if let Some(version) = sys::os::glibc_version() { - if version < (2, 24) { - return Ok(None); - } - } else { - return Ok(None); - } - } - - // On QNX Neutrino, posix_spawnp can fail with EBADF in case "another thread might have opened - // or closed a file descriptor while the posix_spawn() was occurring". - // Documentation says "... or try calling posix_spawn() again". This is what we do here. - // See also http://www.qnx.com/developers/docs/7.1/#com.qnx.doc.neutrino.lib_ref/topic/p/posix_spawn.html - #[cfg(target_os = "nto")] - unsafe fn retrying_libc_posix_spawnp( - pid: *mut pid_t, - file: *const c_char, - file_actions: *const posix_spawn_file_actions_t, - attrp: *const posix_spawnattr_t, - argv: *const *mut c_char, - envp: *const *mut c_char, - ) -> io::Result<i32> { - let mut delay = MIN_FORKSPAWN_SLEEP; - loop { - match libc::posix_spawnp(pid, file, file_actions, attrp, argv, envp) { - libc::EBADF => { - if delay < get_clock_resolution() { - // We cannot sleep this short (it would be longer). - // Yield instead. - thread::yield_now(); - } else if delay < MAX_FORKSPAWN_SLEEP { - thread::sleep(delay); - } else { - return Err(io::const_error!( - ErrorKind::WouldBlock, - "posix_spawnp returned EBADF too often", - )); - } - delay *= 2; - continue; - } - r => { - return Ok(r); - } - } - } - } - - type PosixSpawnAddChdirFn = unsafe extern "C" fn( - *mut libc::posix_spawn_file_actions_t, - *const libc::c_char, - ) -> libc::c_int; - - /// Get the function pointer for adding a chdir action to a - /// `posix_spawn_file_actions_t`, if available, assuming a dynamic libc. - /// - /// Some platforms can set a new working directory for a spawned process in the - /// `posix_spawn` path. This function looks up the function pointer for adding - /// such an action to a `posix_spawn_file_actions_t` struct. - #[cfg(not(all(target_os = "linux", target_env = "musl")))] - fn get_posix_spawn_addchdir() -> Option<PosixSpawnAddChdirFn> { - use crate::sys::weak::weak; - - // POSIX.1-2024 standardizes this function: - // https://pubs.opengroup.org/onlinepubs/9799919799/functions/posix_spawn_file_actions_addchdir.html. - // The _np version is more widely available, though, so try that first. - - weak! { - fn posix_spawn_file_actions_addchdir_np( - *mut libc::posix_spawn_file_actions_t, - *const libc::c_char - ) -> libc::c_int - } - - weak! { - fn posix_spawn_file_actions_addchdir( - *mut libc::posix_spawn_file_actions_t, - *const libc::c_char - ) -> libc::c_int - } - - posix_spawn_file_actions_addchdir_np - .get() - .or_else(|| posix_spawn_file_actions_addchdir.get()) - } - - /// Get the function pointer for adding a chdir action to a - /// `posix_spawn_file_actions_t`, if available, on platforms where the function - /// is known to exist. - /// - /// Weak symbol lookup doesn't work with statically linked libcs, so in cases - /// where static linking is possible we need to either check for the presence - /// of the symbol at compile time or know about it upfront. - #[cfg(all(target_os = "linux", target_env = "musl"))] - fn get_posix_spawn_addchdir() -> Option<PosixSpawnAddChdirFn> { - // Our minimum required musl supports this function, so we can just use it. - Some(libc::posix_spawn_file_actions_addchdir_np) - } - - let addchdir = match self.get_cwd() { - Some(cwd) => { - if cfg!(target_vendor = "apple") { - // There is a bug in macOS where a relative executable - // path like "../myprogram" will cause `posix_spawn` to - // successfully launch the program, but erroneously return - // ENOENT when used with posix_spawn_file_actions_addchdir_np - // which was introduced in macOS 10.15. - if self.get_program_kind() == ProgramKind::Relative { - return Ok(None); - } - } - // Check for the availability of the posix_spawn addchdir - // function now. If it isn't available, bail and use the - // fork/exec path. - match get_posix_spawn_addchdir() { - Some(f) => Some((f, cwd)), - None => return Ok(None), - } - } - None => None, - }; - - let pgroup = self.get_pgroup(); - - struct PosixSpawnFileActions<'a>(&'a mut MaybeUninit<libc::posix_spawn_file_actions_t>); - - impl Drop for PosixSpawnFileActions<'_> { - fn drop(&mut self) { - unsafe { - libc::posix_spawn_file_actions_destroy(self.0.as_mut_ptr()); - } - } - } - - struct PosixSpawnattr<'a>(&'a mut MaybeUninit<libc::posix_spawnattr_t>); - - impl Drop for PosixSpawnattr<'_> { - fn drop(&mut self) { - unsafe { - libc::posix_spawnattr_destroy(self.0.as_mut_ptr()); - } - } - } - - unsafe { - let mut attrs = MaybeUninit::uninit(); - cvt_nz(libc::posix_spawnattr_init(attrs.as_mut_ptr()))?; - let attrs = PosixSpawnattr(&mut attrs); - - let mut flags = 0; - - let mut file_actions = MaybeUninit::uninit(); - cvt_nz(libc::posix_spawn_file_actions_init(file_actions.as_mut_ptr()))?; - let file_actions = PosixSpawnFileActions(&mut file_actions); - - if let Some(fd) = stdio.stdin.fd() { - cvt_nz(libc::posix_spawn_file_actions_adddup2( - file_actions.0.as_mut_ptr(), - fd, - libc::STDIN_FILENO, - ))?; - } - if let Some(fd) = stdio.stdout.fd() { - cvt_nz(libc::posix_spawn_file_actions_adddup2( - file_actions.0.as_mut_ptr(), - fd, - libc::STDOUT_FILENO, - ))?; - } - if let Some(fd) = stdio.stderr.fd() { - cvt_nz(libc::posix_spawn_file_actions_adddup2( - file_actions.0.as_mut_ptr(), - fd, - libc::STDERR_FILENO, - ))?; - } - if let Some((f, cwd)) = addchdir { - cvt_nz(f(file_actions.0.as_mut_ptr(), cwd.as_ptr()))?; - } - - if let Some(pgroup) = pgroup { - flags |= libc::POSIX_SPAWN_SETPGROUP; - cvt_nz(libc::posix_spawnattr_setpgroup(attrs.0.as_mut_ptr(), pgroup))?; - } - - // Inherit the signal mask from this process rather than resetting it (i.e. do not call - // posix_spawnattr_setsigmask). - - // If -Zon-broken-pipe is used, don't reset SIGPIPE to SIG_DFL. - // If -Zon-broken-pipe is not used, reset SIGPIPE to SIG_DFL for backward compatibility. - // - // -Zon-broken-pipe is an opportunity to change the default here. - if !on_broken_pipe_flag_used() { - let mut default_set = MaybeUninit::<libc::sigset_t>::uninit(); - cvt(sigemptyset(default_set.as_mut_ptr()))?; - cvt(sigaddset(default_set.as_mut_ptr(), libc::SIGPIPE))?; - #[cfg(target_os = "hurd")] - { - cvt(sigaddset(default_set.as_mut_ptr(), libc::SIGLOST))?; - } - cvt_nz(libc::posix_spawnattr_setsigdefault( - attrs.0.as_mut_ptr(), - default_set.as_ptr(), - ))?; - flags |= libc::POSIX_SPAWN_SETSIGDEF; - } - - cvt_nz(libc::posix_spawnattr_setflags(attrs.0.as_mut_ptr(), flags as _))?; - - // Make sure we synchronize access to the global `environ` resource - let _env_lock = sys::os::env_read_lock(); - let envp = envp.map(|c| c.as_ptr()).unwrap_or_else(|| *sys::os::environ() as *const _); - - #[cfg(not(target_os = "nto"))] - let spawn_fn = libc::posix_spawnp; - #[cfg(target_os = "nto")] - let spawn_fn = retrying_libc_posix_spawnp; - - #[cfg(target_os = "linux")] - if self.get_create_pidfd() && PIDFD_SUPPORTED.load(Ordering::Relaxed) == SPAWN { - let mut pidfd: libc::c_int = -1; - let spawn_res = pidfd_spawnp.get().unwrap()( - &mut pidfd, - self.get_program_cstr().as_ptr(), - file_actions.0.as_ptr(), - attrs.0.as_ptr(), - self.get_argv().as_ptr() as *const _, - envp as *const _, - ); - - let spawn_res = cvt_nz(spawn_res); - if let Err(ref e) = spawn_res - && e.raw_os_error() == Some(libc::ENOSYS) - { - PIDFD_SUPPORTED.store(FORK_EXEC, Ordering::Relaxed); - return Ok(None); - } - spawn_res?; - - let pid = match cvt(pidfd_getpid.get().unwrap()(pidfd)) { - Ok(pid) => pid, - Err(e) => { - // The child has been spawned and we are holding its pidfd. - // But we cannot obtain its pid even though pidfd_getpid support was verified earlier. - // This might happen if libc can't open procfs because the file descriptor limit has been reached. - libc::close(pidfd); - return Err(Error::new( - e.kind(), - "pidfd_spawnp succeeded but the child's PID could not be obtained", - )); - } - }; - - return Ok(Some(Process::new(pid, pidfd))); - } - - // Safety: -1 indicates we don't have a pidfd. - let mut p = Process::new(0, -1); - - let spawn_res = spawn_fn( - &mut p.pid, - self.get_program_cstr().as_ptr(), - file_actions.0.as_ptr(), - attrs.0.as_ptr(), - self.get_argv().as_ptr() as *const _, - envp as *const _, - ); - - #[cfg(target_os = "nto")] - let spawn_res = spawn_res?; - - cvt_nz(spawn_res)?; - Ok(Some(p)) - } - } - - #[cfg(target_os = "linux")] - fn send_pidfd(&self, sock: &crate::sys::net::Socket) { - use libc::{CMSG_DATA, CMSG_FIRSTHDR, CMSG_LEN, CMSG_SPACE, SCM_RIGHTS, SOL_SOCKET}; - - use crate::io::IoSlice; - use crate::os::fd::RawFd; - use crate::sys::cvt_r; - - unsafe { - let child_pid = libc::getpid(); - // pidfd_open sets CLOEXEC by default - let pidfd = libc::syscall(libc::SYS_pidfd_open, child_pid, 0); - - let fds: [c_int; 1] = [pidfd as RawFd]; - - const SCM_MSG_LEN: usize = size_of::<[c_int; 1]>(); - - #[repr(C)] - union Cmsg { - buf: [u8; unsafe { CMSG_SPACE(SCM_MSG_LEN as u32) as usize }], - _align: libc::cmsghdr, - } - - let mut cmsg: Cmsg = mem::zeroed(); - - // 0-length message to send through the socket so we can pass along the fd - let mut iov = [IoSlice::new(b"")]; - let mut msg: libc::msghdr = mem::zeroed(); - - msg.msg_iov = (&raw mut iov) as *mut _; - msg.msg_iovlen = 1; - - // only attach cmsg if we successfully acquired the pidfd - if pidfd >= 0 { - msg.msg_controllen = size_of_val(&cmsg.buf) as _; - msg.msg_control = (&raw mut cmsg.buf) as *mut _; - - let hdr = CMSG_FIRSTHDR((&raw mut msg) as *mut _); - (*hdr).cmsg_level = SOL_SOCKET; - (*hdr).cmsg_type = SCM_RIGHTS; - (*hdr).cmsg_len = CMSG_LEN(SCM_MSG_LEN as _) as _; - let data = CMSG_DATA(hdr); - crate::ptr::copy_nonoverlapping( - fds.as_ptr().cast::<u8>(), - data as *mut _, - SCM_MSG_LEN, - ); - } - - // we send the 0-length message even if we failed to acquire the pidfd - // so we get a consistent SEQPACKET order - match cvt_r(|| libc::sendmsg(sock.as_raw(), &msg, 0)) { - Ok(0) => {} - other => rtabort!("failed to communicate with parent process. {:?}", other), - } - } - } - - #[cfg(target_os = "linux")] - fn recv_pidfd(&self, sock: &crate::sys::net::Socket) -> pid_t { - use libc::{CMSG_DATA, CMSG_FIRSTHDR, CMSG_LEN, CMSG_SPACE, SCM_RIGHTS, SOL_SOCKET}; - - use crate::io::IoSliceMut; - use crate::sys::cvt_r; - - unsafe { - const SCM_MSG_LEN: usize = size_of::<[c_int; 1]>(); - - #[repr(C)] - union Cmsg { - _buf: [u8; unsafe { CMSG_SPACE(SCM_MSG_LEN as u32) as usize }], - _align: libc::cmsghdr, - } - let mut cmsg: Cmsg = mem::zeroed(); - // 0-length read to get the fd - let mut iov = [IoSliceMut::new(&mut [])]; - - let mut msg: libc::msghdr = mem::zeroed(); - - msg.msg_iov = (&raw mut iov) as *mut _; - msg.msg_iovlen = 1; - msg.msg_controllen = size_of::<Cmsg>() as _; - msg.msg_control = (&raw mut cmsg) as *mut _; - - match cvt_r(|| libc::recvmsg(sock.as_raw(), &mut msg, libc::MSG_CMSG_CLOEXEC)) { - Err(_) => return -1, - Ok(_) => {} - } - - let hdr = CMSG_FIRSTHDR((&raw mut msg) as *mut _); - if hdr.is_null() - || (*hdr).cmsg_level != SOL_SOCKET - || (*hdr).cmsg_type != SCM_RIGHTS - || (*hdr).cmsg_len != CMSG_LEN(SCM_MSG_LEN as _) as _ - { - return -1; - } - let data = CMSG_DATA(hdr); - - let mut fds = [-1 as c_int]; - - crate::ptr::copy_nonoverlapping( - data as *const _, - fds.as_mut_ptr().cast::<u8>(), - SCM_MSG_LEN, - ); - - fds[0] - } - } -} - -//////////////////////////////////////////////////////////////////////////////// -// Processes -//////////////////////////////////////////////////////////////////////////////// - -/// The unique ID of the process (this should never be negative). -pub struct Process { - pid: pid_t, - status: Option<ExitStatus>, - // On Linux, stores the pidfd created for this child. - // This is None if the user did not request pidfd creation, - // or if the pidfd could not be created for some reason - // (e.g. the `pidfd_open` syscall was not available). - #[cfg(target_os = "linux")] - pidfd: Option<PidFd>, -} - -impl Process { - #[cfg(target_os = "linux")] - /// # Safety - /// - /// `pidfd` must either be -1 (representing no file descriptor) or a valid, exclusively owned file - /// descriptor (See [I/O Safety]). - /// - /// [I/O Safety]: crate::io#io-safety - unsafe fn new(pid: pid_t, pidfd: pid_t) -> Self { - use crate::os::unix::io::FromRawFd; - use crate::sys_common::FromInner; - // Safety: If `pidfd` is nonnegative, we assume it's valid and otherwise unowned. - let pidfd = (pidfd >= 0).then(|| PidFd::from_inner(sys::fd::FileDesc::from_raw_fd(pidfd))); - Process { pid, status: None, pidfd } - } - - #[cfg(not(target_os = "linux"))] - unsafe fn new(pid: pid_t, _pidfd: pid_t) -> Self { - Process { pid, status: None } - } - - pub fn id(&self) -> u32 { - self.pid as u32 - } - - pub fn kill(&mut self) -> io::Result<()> { - // If we've already waited on this process then the pid can be recycled - // and used for another process, and we probably shouldn't be killing - // random processes, so return Ok because the process has exited already. - if self.status.is_some() { - return Ok(()); - } - #[cfg(target_os = "linux")] - if let Some(pid_fd) = self.pidfd.as_ref() { - // pidfd_send_signal predates pidfd_open. so if we were able to get an fd then sending signals will work too - return pid_fd.kill(); - } - cvt(unsafe { libc::kill(self.pid, libc::SIGKILL) }).map(drop) - } - - pub fn wait(&mut self) -> io::Result<ExitStatus> { - use crate::sys::cvt_r; - if let Some(status) = self.status { - return Ok(status); - } - #[cfg(target_os = "linux")] - if let Some(pid_fd) = self.pidfd.as_ref() { - let status = pid_fd.wait()?; - self.status = Some(status); - return Ok(status); - } - let mut status = 0 as c_int; - cvt_r(|| unsafe { libc::waitpid(self.pid, &mut status, 0) })?; - self.status = Some(ExitStatus::new(status)); - Ok(ExitStatus::new(status)) - } - - pub fn try_wait(&mut self) -> io::Result<Option<ExitStatus>> { - if let Some(status) = self.status { - return Ok(Some(status)); - } - #[cfg(target_os = "linux")] - if let Some(pid_fd) = self.pidfd.as_ref() { - let status = pid_fd.try_wait()?; - if let Some(status) = status { - self.status = Some(status) - } - return Ok(status); - } - let mut status = 0 as c_int; - let pid = cvt(unsafe { libc::waitpid(self.pid, &mut status, libc::WNOHANG) })?; - if pid == 0 { - Ok(None) - } else { - self.status = Some(ExitStatus::new(status)); - Ok(Some(ExitStatus::new(status))) - } - } -} - -/// Unix exit statuses -// -// This is not actually an "exit status" in Unix terminology. Rather, it is a "wait status". -// See the discussion in comments and doc comments for `std::process::ExitStatus`. -#[derive(PartialEq, Eq, Clone, Copy, Default)] -pub struct ExitStatus(c_int); - -impl fmt::Debug for ExitStatus { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_tuple("unix_wait_status").field(&self.0).finish() - } -} - -impl ExitStatus { - pub fn new(status: c_int) -> ExitStatus { - ExitStatus(status) - } - - #[cfg(target_os = "linux")] - pub fn from_waitid_siginfo(siginfo: libc::siginfo_t) -> ExitStatus { - let status = unsafe { siginfo.si_status() }; - - match siginfo.si_code { - libc::CLD_EXITED => ExitStatus((status & 0xff) << 8), - libc::CLD_KILLED => ExitStatus(status), - libc::CLD_DUMPED => ExitStatus(status | 0x80), - libc::CLD_CONTINUED => ExitStatus(0xffff), - libc::CLD_STOPPED | libc::CLD_TRAPPED => ExitStatus(((status & 0xff) << 8) | 0x7f), - _ => unreachable!("waitid() should only return the above codes"), - } - } - - fn exited(&self) -> bool { - libc::WIFEXITED(self.0) - } - - pub fn exit_ok(&self) -> Result<(), ExitStatusError> { - // This assumes that WIFEXITED(status) && WEXITSTATUS==0 corresponds to status==0. This is - // true on all actual versions of Unix, is widely assumed, and is specified in SuS - // https://pubs.opengroup.org/onlinepubs/9699919799/functions/wait.html. If it is not - // true for a platform pretending to be Unix, the tests (our doctests, and also - // process_unix/tests.rs) will spot it. `ExitStatusError::code` assumes this too. - match NonZero::try_from(self.0) { - /* was nonzero */ Ok(failure) => Err(ExitStatusError(failure)), - /* was zero, couldn't convert */ Err(_) => Ok(()), - } - } - - pub fn code(&self) -> Option<i32> { - self.exited().then(|| libc::WEXITSTATUS(self.0)) - } - - pub fn signal(&self) -> Option<i32> { - libc::WIFSIGNALED(self.0).then(|| libc::WTERMSIG(self.0)) - } - - pub fn core_dumped(&self) -> bool { - libc::WIFSIGNALED(self.0) && libc::WCOREDUMP(self.0) - } - - pub fn stopped_signal(&self) -> Option<i32> { - libc::WIFSTOPPED(self.0).then(|| libc::WSTOPSIG(self.0)) - } - - pub fn continued(&self) -> bool { - libc::WIFCONTINUED(self.0) - } - - pub fn into_raw(&self) -> c_int { - self.0 - } -} - -/// Converts a raw `c_int` to a type-safe `ExitStatus` by wrapping it without copying. -impl From<c_int> for ExitStatus { - fn from(a: c_int) -> ExitStatus { - ExitStatus(a) - } -} - -/// Converts a signal number to a readable, searchable name. -/// -/// This string should be displayed right after the signal number. -/// If a signal is unrecognized, it returns the empty string, so that -/// you just get the number like "0". If it is recognized, you'll get -/// something like "9 (SIGKILL)". -fn signal_string(signal: i32) -> &'static str { - match signal { - libc::SIGHUP => " (SIGHUP)", - libc::SIGINT => " (SIGINT)", - libc::SIGQUIT => " (SIGQUIT)", - libc::SIGILL => " (SIGILL)", - libc::SIGTRAP => " (SIGTRAP)", - libc::SIGABRT => " (SIGABRT)", - #[cfg(not(target_os = "l4re"))] - libc::SIGBUS => " (SIGBUS)", - libc::SIGFPE => " (SIGFPE)", - libc::SIGKILL => " (SIGKILL)", - #[cfg(not(target_os = "l4re"))] - libc::SIGUSR1 => " (SIGUSR1)", - libc::SIGSEGV => " (SIGSEGV)", - #[cfg(not(target_os = "l4re"))] - libc::SIGUSR2 => " (SIGUSR2)", - libc::SIGPIPE => " (SIGPIPE)", - libc::SIGALRM => " (SIGALRM)", - libc::SIGTERM => " (SIGTERM)", - #[cfg(not(target_os = "l4re"))] - libc::SIGCHLD => " (SIGCHLD)", - #[cfg(not(target_os = "l4re"))] - libc::SIGCONT => " (SIGCONT)", - #[cfg(not(target_os = "l4re"))] - libc::SIGSTOP => " (SIGSTOP)", - #[cfg(not(target_os = "l4re"))] - libc::SIGTSTP => " (SIGTSTP)", - #[cfg(not(target_os = "l4re"))] - libc::SIGTTIN => " (SIGTTIN)", - #[cfg(not(target_os = "l4re"))] - libc::SIGTTOU => " (SIGTTOU)", - #[cfg(not(target_os = "l4re"))] - libc::SIGURG => " (SIGURG)", - #[cfg(not(target_os = "l4re"))] - libc::SIGXCPU => " (SIGXCPU)", - #[cfg(not(any(target_os = "l4re", target_os = "rtems")))] - libc::SIGXFSZ => " (SIGXFSZ)", - #[cfg(not(any(target_os = "l4re", target_os = "rtems")))] - libc::SIGVTALRM => " (SIGVTALRM)", - #[cfg(not(target_os = "l4re"))] - libc::SIGPROF => " (SIGPROF)", - #[cfg(not(any(target_os = "l4re", target_os = "rtems")))] - libc::SIGWINCH => " (SIGWINCH)", - #[cfg(not(any(target_os = "haiku", target_os = "l4re")))] - libc::SIGIO => " (SIGIO)", - #[cfg(target_os = "haiku")] - libc::SIGPOLL => " (SIGPOLL)", - #[cfg(not(target_os = "l4re"))] - libc::SIGSYS => " (SIGSYS)", - // For information on Linux signals, run `man 7 signal` - #[cfg(all( - target_os = "linux", - any( - target_arch = "x86_64", - target_arch = "x86", - target_arch = "arm", - target_arch = "aarch64" - ) - ))] - libc::SIGSTKFLT => " (SIGSTKFLT)", - #[cfg(any(target_os = "linux", target_os = "nto", target_os = "cygwin"))] - libc::SIGPWR => " (SIGPWR)", - #[cfg(any( - target_os = "freebsd", - target_os = "netbsd", - target_os = "openbsd", - target_os = "dragonfly", - target_os = "nto", - target_vendor = "apple", - target_os = "cygwin", - ))] - libc::SIGEMT => " (SIGEMT)", - #[cfg(any( - target_os = "freebsd", - target_os = "netbsd", - target_os = "openbsd", - target_os = "dragonfly", - target_vendor = "apple", - ))] - libc::SIGINFO => " (SIGINFO)", - #[cfg(target_os = "hurd")] - libc::SIGLOST => " (SIGLOST)", - #[cfg(target_os = "freebsd")] - libc::SIGTHR => " (SIGTHR)", - #[cfg(target_os = "freebsd")] - libc::SIGLIBRT => " (SIGLIBRT)", - _ => "", - } -} - -impl fmt::Display for ExitStatus { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if let Some(code) = self.code() { - write!(f, "exit status: {code}") - } else if let Some(signal) = self.signal() { - let signal_string = signal_string(signal); - if self.core_dumped() { - write!(f, "signal: {signal}{signal_string} (core dumped)") - } else { - write!(f, "signal: {signal}{signal_string}") - } - } else if let Some(signal) = self.stopped_signal() { - let signal_string = signal_string(signal); - write!(f, "stopped (not terminated) by signal: {signal}{signal_string}") - } else if self.continued() { - write!(f, "continued (WIFCONTINUED)") - } else { - write!(f, "unrecognised wait status: {} {:#x}", self.0, self.0) - } - } -} - -#[derive(PartialEq, Eq, Clone, Copy)] -pub struct ExitStatusError(NonZero<c_int>); - -impl Into<ExitStatus> for ExitStatusError { - fn into(self) -> ExitStatus { - ExitStatus(self.0.into()) - } -} - -impl fmt::Debug for ExitStatusError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_tuple("unix_wait_status").field(&self.0).finish() - } -} - -impl ExitStatusError { - pub fn code(self) -> Option<NonZero<i32>> { - ExitStatus(self.0.into()).code().map(|st| st.try_into().unwrap()) - } -} - -#[cfg(target_os = "linux")] -mod linux_child_ext { - - use crate::os::linux::process as os; - use crate::sys::pal::unix::ErrorKind; - use crate::sys::pal::unix::linux::pidfd as imp; - use crate::sys_common::FromInner; - use crate::{io, mem}; - - #[unstable(feature = "linux_pidfd", issue = "82971")] - impl crate::os::linux::process::ChildExt for crate::process::Child { - fn pidfd(&self) -> io::Result<&os::PidFd> { - self.handle - .pidfd - .as_ref() - // SAFETY: The os type is a transparent wrapper, therefore we can transmute references - .map(|fd| unsafe { mem::transmute::<&imp::PidFd, &os::PidFd>(fd) }) - .ok_or_else(|| io::const_error!(ErrorKind::Uncategorized, "no pidfd was created.")) - } - - fn into_pidfd(mut self) -> Result<os::PidFd, Self> { - self.handle - .pidfd - .take() - .map(|fd| <os::PidFd as FromInner<imp::PidFd>>::from_inner(fd)) - .ok_or_else(|| self) - } - } -} - -#[cfg(test)] -#[path = "process_unix/tests.rs"] -mod tests; - -// See [`process_unsupported_wait_status::compare_with_linux`]; -#[cfg(all(test, target_os = "linux"))] -#[path = "process_unsupported/wait_status.rs"] -mod process_unsupported_wait_status; diff --git a/library/std/src/sys/pal/unix/process/process_unix/tests.rs b/library/std/src/sys/pal/unix/process/process_unix/tests.rs deleted file mode 100644 index f4d6ac6b4e3..00000000000 --- a/library/std/src/sys/pal/unix/process/process_unix/tests.rs +++ /dev/null @@ -1,75 +0,0 @@ -use crate::os::unix::process::{CommandExt, ExitStatusExt}; -use crate::panic::catch_unwind; -use crate::process::Command; - -// Many of the other aspects of this situation, including heap alloc concurrency -// safety etc., are tested in tests/ui/process/process-panic-after-fork.rs - -#[test] -fn exitstatus_display_tests() { - // In practice this is the same on every Unix. - // If some weird platform turns out to be different, and this test fails, use #[cfg]. - use crate::os::unix::process::ExitStatusExt; - use crate::process::ExitStatus; - - let t = |v, s| assert_eq!(s, format!("{}", <ExitStatus as ExitStatusExt>::from_raw(v))); - - t(0x0000f, "signal: 15 (SIGTERM)"); - t(0x0008b, "signal: 11 (SIGSEGV) (core dumped)"); - t(0x00000, "exit status: 0"); - t(0x0ff00, "exit status: 255"); - - // On MacOS, 0x0137f is WIFCONTINUED, not WIFSTOPPED. Probably *BSD is similar. - // https://github.com/rust-lang/rust/pull/82749#issuecomment-790525956 - // The purpose of this test is to test our string formatting, not our understanding of the wait - // status magic numbers. So restrict these to Linux. - if cfg!(target_os = "linux") { - #[cfg(any(target_arch = "mips", target_arch = "mips64"))] - t(0x0137f, "stopped (not terminated) by signal: 19 (SIGPWR)"); - - #[cfg(any(target_arch = "sparc", target_arch = "sparc64"))] - t(0x0137f, "stopped (not terminated) by signal: 19 (SIGCONT)"); - - #[cfg(not(any( - target_arch = "mips", - target_arch = "mips64", - target_arch = "sparc", - target_arch = "sparc64" - )))] - t(0x0137f, "stopped (not terminated) by signal: 19 (SIGSTOP)"); - - t(0x0ffff, "continued (WIFCONTINUED)"); - } - - // Testing "unrecognised wait status" is hard because the wait.h macros typically - // assume that the value came from wait and isn't mad. With the glibc I have here - // this works: - if cfg!(all(target_os = "linux", target_env = "gnu")) { - t(0x000ff, "unrecognised wait status: 255 0xff"); - } -} - -#[test] -#[cfg_attr(target_os = "emscripten", ignore)] -fn test_command_fork_no_unwind() { - let got = catch_unwind(|| { - let mut c = Command::new("echo"); - c.arg("hi"); - unsafe { - c.pre_exec(|| panic!("{}", "crash now!")); - } - let st = c.status().expect("failed to get command status"); - dbg!(st); - st - }); - dbg!(&got); - let status = got.expect("panic unexpectedly propagated"); - dbg!(status); - let signal = status.signal().expect("expected child process to die of signal"); - assert!( - signal == libc::SIGABRT - || signal == libc::SIGILL - || signal == libc::SIGTRAP - || signal == libc::SIGSEGV - ); -} diff --git a/library/std/src/sys/pal/unix/process/process_unsupported.rs b/library/std/src/sys/pal/unix/process/process_unsupported.rs deleted file mode 100644 index c58548835ff..00000000000 --- a/library/std/src/sys/pal/unix/process/process_unsupported.rs +++ /dev/null @@ -1,72 +0,0 @@ -use libc::{c_int, pid_t}; - -use crate::io; -use crate::num::NonZero; -use crate::sys::pal::unix::unsupported::*; -use crate::sys::process::process_common::*; - -//////////////////////////////////////////////////////////////////////////////// -// Command -//////////////////////////////////////////////////////////////////////////////// - -impl Command { - pub fn spawn( - &mut self, - _default: Stdio, - _needs_stdin: bool, - ) -> io::Result<(Process, StdioPipes)> { - unsupported() - } - - pub fn output(&mut self) -> io::Result<(ExitStatus, Vec<u8>, Vec<u8>)> { - unsupported() - } - - pub fn exec(&mut self, _default: Stdio) -> io::Error { - unsupported_err() - } -} - -//////////////////////////////////////////////////////////////////////////////// -// Processes -//////////////////////////////////////////////////////////////////////////////// - -pub struct Process { - _handle: pid_t, -} - -impl Process { - pub fn id(&self) -> u32 { - 0 - } - - pub fn kill(&mut self) -> io::Result<()> { - unsupported() - } - - pub fn wait(&mut self) -> io::Result<ExitStatus> { - unsupported() - } - - pub fn try_wait(&mut self) -> io::Result<Option<ExitStatus>> { - unsupported() - } -} - -mod wait_status; -pub use wait_status::ExitStatus; - -#[derive(PartialEq, Eq, Clone, Copy, Debug)] -pub struct ExitStatusError(NonZero<c_int>); - -impl Into<ExitStatus> for ExitStatusError { - fn into(self) -> ExitStatus { - ExitStatus::from(c_int::from(self.0)) - } -} - -impl ExitStatusError { - pub fn code(self) -> Option<NonZero<i32>> { - ExitStatus::from(c_int::from(self.0)).code().map(|st| st.try_into().unwrap()) - } -} diff --git a/library/std/src/sys/pal/unix/process/process_unsupported/wait_status.rs b/library/std/src/sys/pal/unix/process/process_unsupported/wait_status.rs deleted file mode 100644 index f04036bde49..00000000000 --- a/library/std/src/sys/pal/unix/process/process_unsupported/wait_status.rs +++ /dev/null @@ -1,83 +0,0 @@ -//! Emulated wait status for non-Unix #[cfg(unix) platforms -//! -//! Separate module to facilitate testing against a real Unix implementation. - -use super::ExitStatusError; -use crate::ffi::c_int; -use crate::fmt; -use crate::num::NonZero; - -/// Emulated wait status for use by `process_unsupported.rs` -/// -/// Uses the "traditional unix" encoding. For use on platfors which are `#[cfg(unix)]` -/// but do not actually support subprocesses at all. -/// -/// These platforms aren't Unix, but are simply pretending to be for porting convenience. -/// So, we provide a faithful pretence here. -#[derive(PartialEq, Eq, Clone, Copy, Debug, Default)] -pub struct ExitStatus { - wait_status: c_int, -} - -/// Converts a raw `c_int` to a type-safe `ExitStatus` by wrapping it -impl From<c_int> for ExitStatus { - fn from(wait_status: c_int) -> ExitStatus { - ExitStatus { wait_status } - } -} - -impl fmt::Display for ExitStatus { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "emulated wait status: {}", self.wait_status) - } -} - -impl ExitStatus { - pub fn code(&self) -> Option<i32> { - // Linux and FreeBSD both agree that values linux 0x80 - // count as "WIFEXITED" even though this is quite mad. - // Likewise the macros disregard all the high bits, so are happy to declare - // out-of-range values to be WIFEXITED, WIFSTOPPED, etc. - let w = self.wait_status; - if (w & 0x7f) == 0 { Some((w & 0xff00) >> 8) } else { None } - } - - #[allow(unused)] - pub fn exit_ok(&self) -> Result<(), ExitStatusError> { - // This assumes that WIFEXITED(status) && WEXITSTATUS==0 corresponds to status==0. This is - // true on all actual versions of Unix, is widely assumed, and is specified in SuS - // https://pubs.opengroup.org/onlinepubs/9699919799/functions/wait.html. If it is not - // true for a platform pretending to be Unix, the tests (our doctests, and also - // process_unix/tests.rs) will spot it. `ExitStatusError::code` assumes this too. - match NonZero::try_from(self.wait_status) { - /* was nonzero */ Ok(failure) => Err(ExitStatusError(failure)), - /* was zero, couldn't convert */ Err(_) => Ok(()), - } - } - - pub fn signal(&self) -> Option<i32> { - let signal = self.wait_status & 0x007f; - if signal > 0 && signal < 0x7f { Some(signal) } else { None } - } - - pub fn core_dumped(&self) -> bool { - self.signal().is_some() && (self.wait_status & 0x80) != 0 - } - - pub fn stopped_signal(&self) -> Option<i32> { - let w = self.wait_status; - if (w & 0xff) == 0x7f { Some((w & 0xff00) >> 8) } else { None } - } - - pub fn continued(&self) -> bool { - self.wait_status == 0xffff - } - - pub fn into_raw(&self) -> c_int { - self.wait_status - } -} - -#[cfg(test)] -#[path = "wait_status/tests.rs"] // needed because of strange layout of process_unsupported -mod tests; diff --git a/library/std/src/sys/pal/unix/process/process_unsupported/wait_status/tests.rs b/library/std/src/sys/pal/unix/process/process_unsupported/wait_status/tests.rs deleted file mode 100644 index 5132eab10a1..00000000000 --- a/library/std/src/sys/pal/unix/process/process_unsupported/wait_status/tests.rs +++ /dev/null @@ -1,36 +0,0 @@ -// Note that tests in this file are run on Linux as well as on platforms using process_unsupported - -// Test that our emulation exactly matches Linux -// -// This test runs *on Linux* but it tests -// the implementation used on non-Unix `#[cfg(unix)]` platforms. -// -// I.e. we're using Linux as a proxy for "trad unix". -#[cfg(target_os = "linux")] -#[test] -fn compare_with_linux() { - use super::ExitStatus as Emulated; - use crate::os::unix::process::ExitStatusExt as _; - use crate::process::ExitStatus as Real; - - // Check that we handle out-of-range values similarly, too. - for wstatus in -0xf_ffff..0xf_ffff { - let emulated = Emulated::from(wstatus); - let real = Real::from_raw(wstatus); - - macro_rules! compare { { $method:ident } => { - assert_eq!( - emulated.$method(), - real.$method(), - "{wstatus:#x}.{}()", - stringify!($method), - ); - } } - compare!(code); - compare!(signal); - compare!(core_dumped); - compare!(stopped_signal); - compare!(continued); - compare!(into_raw); - } -} diff --git a/library/std/src/sys/pal/unix/process/process_vxworks.rs b/library/std/src/sys/pal/unix/process/process_vxworks.rs deleted file mode 100644 index e2c1b6a0326..00000000000 --- a/library/std/src/sys/pal/unix/process/process_vxworks.rs +++ /dev/null @@ -1,268 +0,0 @@ -#![forbid(unsafe_op_in_unsafe_fn)] -use libc::{self, RTP_ID, c_char, c_int}; - -use crate::io::{self, ErrorKind}; -use crate::num::NonZero; -use crate::sys::cvt; -use crate::sys::pal::unix::thread; -use crate::sys::process::process_common::*; -use crate::{fmt, sys}; - -//////////////////////////////////////////////////////////////////////////////// -// Command -//////////////////////////////////////////////////////////////////////////////// - -impl Command { - pub fn spawn( - &mut self, - default: Stdio, - needs_stdin: bool, - ) -> io::Result<(Process, StdioPipes)> { - use crate::sys::cvt_r; - let envp = self.capture_env(); - - if self.saw_nul() { - return Err(io::const_error!( - ErrorKind::InvalidInput, - "nul byte found in provided data", - )); - } - let (ours, theirs) = self.setup_io(default, needs_stdin)?; - let mut p = Process { pid: 0, status: None }; - - unsafe { - macro_rules! t { - ($e:expr) => { - match $e { - Ok(e) => e, - Err(e) => return Err(e.into()), - } - }; - } - - let mut orig_stdin = libc::STDIN_FILENO; - let mut orig_stdout = libc::STDOUT_FILENO; - let mut orig_stderr = libc::STDERR_FILENO; - - if let Some(fd) = theirs.stdin.fd() { - orig_stdin = t!(cvt_r(|| libc::dup(libc::STDIN_FILENO))); - t!(cvt_r(|| libc::dup2(fd, libc::STDIN_FILENO))); - } - if let Some(fd) = theirs.stdout.fd() { - orig_stdout = t!(cvt_r(|| libc::dup(libc::STDOUT_FILENO))); - t!(cvt_r(|| libc::dup2(fd, libc::STDOUT_FILENO))); - } - if let Some(fd) = theirs.stderr.fd() { - orig_stderr = t!(cvt_r(|| libc::dup(libc::STDERR_FILENO))); - t!(cvt_r(|| libc::dup2(fd, libc::STDERR_FILENO))); - } - - if let Some(cwd) = self.get_cwd() { - t!(cvt(libc::chdir(cwd.as_ptr()))); - } - - // pre_exec closures are ignored on VxWorks - let _ = self.get_closures(); - - let c_envp = envp - .as_ref() - .map(|c| c.as_ptr()) - .unwrap_or_else(|| *sys::os::environ() as *const _); - let stack_size = crate::cmp::max( - crate::env::var_os("RUST_MIN_STACK") - .and_then(|s| s.to_str().and_then(|s| s.parse().ok())) - .unwrap_or(thread::DEFAULT_MIN_STACK_SIZE), - libc::PTHREAD_STACK_MIN, - ); - - // ensure that access to the environment is synchronized - let _lock = sys::os::env_read_lock(); - - let ret = libc::rtpSpawn( - self.get_program_cstr().as_ptr(), - self.get_argv().as_ptr() as *mut *const c_char, // argv - c_envp as *mut *const c_char, - 100 as c_int, // initial priority - stack_size, // initial stack size. - 0, // options - 0, // task options - ); - - // Because FileDesc was not used, each duplicated file descriptor - // needs to be closed manually - if orig_stdin != libc::STDIN_FILENO { - t!(cvt_r(|| libc::dup2(orig_stdin, libc::STDIN_FILENO))); - libc::close(orig_stdin); - } - if orig_stdout != libc::STDOUT_FILENO { - t!(cvt_r(|| libc::dup2(orig_stdout, libc::STDOUT_FILENO))); - libc::close(orig_stdout); - } - if orig_stderr != libc::STDERR_FILENO { - t!(cvt_r(|| libc::dup2(orig_stderr, libc::STDERR_FILENO))); - libc::close(orig_stderr); - } - - if ret != libc::RTP_ID_ERROR { - p.pid = ret; - Ok((p, ours)) - } else { - Err(io::Error::last_os_error()) - } - } - } - - pub fn output(&mut self) -> io::Result<(ExitStatus, Vec<u8>, Vec<u8>)> { - let (proc, pipes) = self.spawn(Stdio::MakePipe, false)?; - crate::sys_common::process::wait_with_output(proc, pipes) - } - - pub fn exec(&mut self, default: Stdio) -> io::Error { - let ret = Command::spawn(self, default, false); - match ret { - Ok(t) => unsafe { - let mut status = 0 as c_int; - libc::waitpid(t.0.pid, &mut status, 0); - libc::exit(0); - }, - Err(e) => e, - } - } -} - -//////////////////////////////////////////////////////////////////////////////// -// Processes -//////////////////////////////////////////////////////////////////////////////// - -/// The unique id of the process (this should never be negative). -pub struct Process { - pid: RTP_ID, - status: Option<ExitStatus>, -} - -impl Process { - pub fn id(&self) -> u32 { - self.pid as u32 - } - - pub fn kill(&mut self) -> io::Result<()> { - // If we've already waited on this process then the pid can be recycled - // and used for another process, and we probably shouldn't be killing - // random processes, so return Ok because the process has exited already. - if self.status.is_some() { - Ok(()) - } else { - cvt(unsafe { libc::kill(self.pid, libc::SIGKILL) }).map(drop) - } - } - - pub fn wait(&mut self) -> io::Result<ExitStatus> { - use crate::sys::cvt_r; - if let Some(status) = self.status { - return Ok(status); - } - let mut status = 0 as c_int; - cvt_r(|| unsafe { libc::waitpid(self.pid, &mut status, 0) })?; - self.status = Some(ExitStatus::new(status)); - Ok(ExitStatus::new(status)) - } - - pub fn try_wait(&mut self) -> io::Result<Option<ExitStatus>> { - if let Some(status) = self.status { - return Ok(Some(status)); - } - let mut status = 0 as c_int; - let pid = cvt(unsafe { libc::waitpid(self.pid, &mut status, libc::WNOHANG) })?; - if pid == 0 { - Ok(None) - } else { - self.status = Some(ExitStatus::new(status)); - Ok(Some(ExitStatus::new(status))) - } - } -} - -/// Unix exit statuses -#[derive(PartialEq, Eq, Clone, Copy, Debug, Default)] -pub struct ExitStatus(c_int); - -impl ExitStatus { - pub fn new(status: c_int) -> ExitStatus { - ExitStatus(status) - } - - fn exited(&self) -> bool { - libc::WIFEXITED(self.0) - } - - pub fn exit_ok(&self) -> Result<(), ExitStatusError> { - // This assumes that WIFEXITED(status) && WEXITSTATUS==0 corresponds to status==0. This is - // true on all actual versions of Unix, is widely assumed, and is specified in SuS - // https://pubs.opengroup.org/onlinepubs/9699919799/functions/wait.html. If it is not - // true for a platform pretending to be Unix, the tests (our doctests, and also - // process_unix/tests.rs) will spot it. `ExitStatusError::code` assumes this too. - match NonZero::try_from(self.0) { - Ok(failure) => Err(ExitStatusError(failure)), - Err(_) => Ok(()), - } - } - - pub fn code(&self) -> Option<i32> { - if self.exited() { Some(libc::WEXITSTATUS(self.0)) } else { None } - } - - pub fn signal(&self) -> Option<i32> { - if !self.exited() { Some(libc::WTERMSIG(self.0)) } else { None } - } - - pub fn core_dumped(&self) -> bool { - // This method is not yet properly implemented on VxWorks - false - } - - pub fn stopped_signal(&self) -> Option<i32> { - if libc::WIFSTOPPED(self.0) { Some(libc::WSTOPSIG(self.0)) } else { None } - } - - pub fn continued(&self) -> bool { - // This method is not yet properly implemented on VxWorks - false - } - - pub fn into_raw(&self) -> c_int { - self.0 - } -} - -/// Converts a raw `c_int` to a type-safe `ExitStatus` by wrapping it without copying. -impl From<c_int> for ExitStatus { - fn from(a: c_int) -> ExitStatus { - ExitStatus(a) - } -} - -impl fmt::Display for ExitStatus { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if let Some(code) = self.code() { - write!(f, "exit code: {code}") - } else { - let signal = self.signal().unwrap(); - write!(f, "signal: {signal}") - } - } -} - -#[derive(PartialEq, Eq, Clone, Copy, Debug)] -pub struct ExitStatusError(NonZero<c_int>); - -impl Into<ExitStatus> for ExitStatusError { - fn into(self) -> ExitStatus { - ExitStatus(self.0.into()) - } -} - -impl ExitStatusError { - pub fn code(self) -> Option<NonZero<i32>> { - ExitStatus(self.0.into()).code().map(|st| st.try_into().unwrap()) - } -} diff --git a/library/std/src/sys/pal/unsupported/mod.rs b/library/std/src/sys/pal/unsupported/mod.rs index bcea699f3b2..38838b915b5 100644 --- a/library/std/src/sys/pal/unsupported/mod.rs +++ b/library/std/src/sys/pal/unsupported/mod.rs @@ -4,7 +4,6 @@ pub mod args; pub mod env; pub mod os; pub mod pipe; -pub mod process; pub mod thread; pub mod time; diff --git a/library/std/src/sys/pal/unsupported/process.rs b/library/std/src/sys/pal/unsupported/process.rs deleted file mode 100644 index fee81744f09..00000000000 --- a/library/std/src/sys/pal/unsupported/process.rs +++ /dev/null @@ -1,322 +0,0 @@ -pub use crate::ffi::OsString as EnvKey; -use crate::ffi::{OsStr, OsString}; -use crate::num::NonZero; -use crate::path::Path; -use crate::sys::fs::File; -use crate::sys::pipe::AnonPipe; -use crate::sys::unsupported; -use crate::sys_common::process::{CommandEnv, CommandEnvs}; -use crate::{fmt, io}; - -//////////////////////////////////////////////////////////////////////////////// -// Command -//////////////////////////////////////////////////////////////////////////////// - -pub struct Command { - program: OsString, - args: Vec<OsString>, - env: CommandEnv, - - cwd: Option<OsString>, - stdin: Option<Stdio>, - stdout: Option<Stdio>, - stderr: Option<Stdio>, -} - -// passed back to std::process with the pipes connected to the child, if any -// were requested -pub struct StdioPipes { - pub stdin: Option<AnonPipe>, - pub stdout: Option<AnonPipe>, - pub stderr: Option<AnonPipe>, -} - -#[derive(Debug)] -pub enum Stdio { - Inherit, - Null, - MakePipe, - ParentStdout, - ParentStderr, - #[allow(dead_code)] // This variant exists only for the Debug impl - InheritFile(File), -} - -impl Command { - pub fn new(program: &OsStr) -> Command { - Command { - program: program.to_owned(), - args: vec![program.to_owned()], - env: Default::default(), - cwd: None, - stdin: None, - stdout: None, - stderr: None, - } - } - - pub fn arg(&mut self, arg: &OsStr) { - self.args.push(arg.to_owned()); - } - - pub fn env_mut(&mut self) -> &mut CommandEnv { - &mut self.env - } - - pub fn cwd(&mut self, dir: &OsStr) { - self.cwd = Some(dir.to_owned()); - } - - pub fn stdin(&mut self, stdin: Stdio) { - self.stdin = Some(stdin); - } - - pub fn stdout(&mut self, stdout: Stdio) { - self.stdout = Some(stdout); - } - - pub fn stderr(&mut self, stderr: Stdio) { - self.stderr = Some(stderr); - } - - pub fn get_program(&self) -> &OsStr { - &self.program - } - - pub fn get_args(&self) -> CommandArgs<'_> { - let mut iter = self.args.iter(); - iter.next(); - CommandArgs { iter } - } - - pub fn get_envs(&self) -> CommandEnvs<'_> { - self.env.iter() - } - - pub fn get_current_dir(&self) -> Option<&Path> { - self.cwd.as_ref().map(|cs| Path::new(cs)) - } - - pub fn spawn( - &mut self, - _default: Stdio, - _needs_stdin: bool, - ) -> io::Result<(Process, StdioPipes)> { - unsupported() - } - - pub fn output(&mut self) -> io::Result<(ExitStatus, Vec<u8>, Vec<u8>)> { - unsupported() - } -} - -impl From<AnonPipe> for Stdio { - fn from(pipe: AnonPipe) -> Stdio { - pipe.diverge() - } -} - -impl From<io::Stdout> for Stdio { - fn from(_: io::Stdout) -> Stdio { - Stdio::ParentStdout - } -} - -impl From<io::Stderr> for Stdio { - fn from(_: io::Stderr) -> Stdio { - Stdio::ParentStderr - } -} - -impl From<File> for Stdio { - fn from(file: File) -> Stdio { - Stdio::InheritFile(file) - } -} - -impl fmt::Debug for Command { - // show all attributes - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if f.alternate() { - let mut debug_command = f.debug_struct("Command"); - debug_command.field("program", &self.program).field("args", &self.args); - if !self.env.is_unchanged() { - debug_command.field("env", &self.env); - } - - if self.cwd.is_some() { - debug_command.field("cwd", &self.cwd); - } - - if self.stdin.is_some() { - debug_command.field("stdin", &self.stdin); - } - if self.stdout.is_some() { - debug_command.field("stdout", &self.stdout); - } - if self.stderr.is_some() { - debug_command.field("stderr", &self.stderr); - } - - debug_command.finish() - } else { - if let Some(ref cwd) = self.cwd { - write!(f, "cd {cwd:?} && ")?; - } - if self.env.does_clear() { - write!(f, "env -i ")?; - // Altered env vars will be printed next, that should exactly work as expected. - } else { - // Removed env vars need the command to be wrapped in `env`. - let mut any_removed = false; - for (key, value_opt) in self.get_envs() { - if value_opt.is_none() { - if !any_removed { - write!(f, "env ")?; - any_removed = true; - } - write!(f, "-u {} ", key.to_string_lossy())?; - } - } - } - // Altered env vars can just be added in front of the program. - for (key, value_opt) in self.get_envs() { - if let Some(value) = value_opt { - write!(f, "{}={value:?} ", key.to_string_lossy())?; - } - } - if self.program != self.args[0] { - write!(f, "[{:?}] ", self.program)?; - } - write!(f, "{:?}", self.args[0])?; - - for arg in &self.args[1..] { - write!(f, " {:?}", arg)?; - } - Ok(()) - } - } -} - -#[derive(PartialEq, Eq, Clone, Copy, Debug, Default)] -#[non_exhaustive] -pub struct ExitStatus(); - -impl ExitStatus { - pub fn exit_ok(&self) -> Result<(), ExitStatusError> { - Ok(()) - } - - pub fn code(&self) -> Option<i32> { - Some(0) - } -} - -impl fmt::Display for ExitStatus { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "<dummy exit status>") - } -} - -pub struct ExitStatusError(!); - -impl Clone for ExitStatusError { - fn clone(&self) -> ExitStatusError { - self.0 - } -} - -impl Copy for ExitStatusError {} - -impl PartialEq for ExitStatusError { - fn eq(&self, _other: &ExitStatusError) -> bool { - self.0 - } -} - -impl Eq for ExitStatusError {} - -impl fmt::Debug for ExitStatusError { - fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.0 - } -} - -impl Into<ExitStatus> for ExitStatusError { - fn into(self) -> ExitStatus { - self.0 - } -} - -impl ExitStatusError { - pub fn code(self) -> Option<NonZero<i32>> { - self.0 - } -} - -#[derive(PartialEq, Eq, Clone, Copy, Debug)] -pub struct ExitCode(u8); - -impl ExitCode { - pub const SUCCESS: ExitCode = ExitCode(0); - pub const FAILURE: ExitCode = ExitCode(1); - - pub fn as_i32(&self) -> i32 { - self.0 as i32 - } -} - -impl From<u8> for ExitCode { - fn from(code: u8) -> Self { - Self(code) - } -} - -pub struct Process(!); - -impl Process { - pub fn id(&self) -> u32 { - self.0 - } - - pub fn kill(&mut self) -> io::Result<()> { - self.0 - } - - pub fn wait(&mut self) -> io::Result<ExitStatus> { - self.0 - } - - pub fn try_wait(&mut self) -> io::Result<Option<ExitStatus>> { - self.0 - } -} - -pub struct CommandArgs<'a> { - iter: crate::slice::Iter<'a, OsString>, -} - -impl<'a> Iterator for CommandArgs<'a> { - type Item = &'a OsStr; - fn next(&mut self) -> Option<&'a OsStr> { - self.iter.next().map(|os| &**os) - } - fn size_hint(&self) -> (usize, Option<usize>) { - self.iter.size_hint() - } -} - -impl<'a> ExactSizeIterator for CommandArgs<'a> { - fn len(&self) -> usize { - self.iter.len() - } - fn is_empty(&self) -> bool { - self.iter.is_empty() - } -} - -impl<'a> fmt::Debug for CommandArgs<'a> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_list().entries(self.iter.clone()).finish() - } -} diff --git a/library/std/src/sys/pal/wasi/mod.rs b/library/std/src/sys/pal/wasi/mod.rs index c89832857dd..cdd613f76b6 100644 --- a/library/std/src/sys/pal/wasi/mod.rs +++ b/library/std/src/sys/pal/wasi/mod.rs @@ -23,8 +23,6 @@ pub mod futex; pub mod os; #[path = "../unsupported/pipe.rs"] pub mod pipe; -#[path = "../unsupported/process.rs"] -pub mod process; pub mod thread; pub mod time; diff --git a/library/std/src/sys/pal/wasip2/mod.rs b/library/std/src/sys/pal/wasip2/mod.rs index 3008ba88753..6ac28f1bf4f 100644 --- a/library/std/src/sys/pal/wasip2/mod.rs +++ b/library/std/src/sys/pal/wasip2/mod.rs @@ -20,8 +20,6 @@ pub mod futex; pub mod os; #[path = "../unsupported/pipe.rs"] pub mod pipe; -#[path = "../unsupported/process.rs"] -pub mod process; #[path = "../wasi/thread.rs"] pub mod thread; #[path = "../wasi/time.rs"] diff --git a/library/std/src/sys/pal/wasm/mod.rs b/library/std/src/sys/pal/wasm/mod.rs index 175fe75357f..8d39b70d039 100644 --- a/library/std/src/sys/pal/wasm/mod.rs +++ b/library/std/src/sys/pal/wasm/mod.rs @@ -23,8 +23,6 @@ pub mod env; pub mod os; #[path = "../unsupported/pipe.rs"] pub mod pipe; -#[path = "../unsupported/process.rs"] -pub mod process; #[path = "../unsupported/time.rs"] pub mod time; diff --git a/library/std/src/sys/pal/windows/args.rs b/library/std/src/sys/pal/windows/args.rs index 3447a0157e4..d973743639a 100644 --- a/library/std/src/sys/pal/windows/args.rs +++ b/library/std/src/sys/pal/windows/args.rs @@ -6,13 +6,13 @@ #[cfg(test)] mod tests; +use super::ensure_no_nuls; use super::os::current_exe; use crate::ffi::{OsStr, OsString}; use crate::num::NonZero; use crate::os::windows::prelude::*; use crate::path::{Path, PathBuf}; use crate::sys::path::get_long_path; -use crate::sys::process::ensure_no_nuls; use crate::sys::{c, to_u16s}; use crate::sys_common::AsInner; use crate::sys_common::wstr::WStrUnits; diff --git a/library/std/src/sys/pal/windows/mod.rs b/library/std/src/sys/pal/windows/mod.rs index 6eb68f3a3bc..bdf0cc2c59c 100644 --- a/library/std/src/sys/pal/windows/mod.rs +++ b/library/std/src/sys/pal/windows/mod.rs @@ -22,7 +22,6 @@ pub mod futex; pub mod handle; pub mod os; pub mod pipe; -pub mod process; pub mod thread; pub mod time; cfg_if::cfg_if! { @@ -287,6 +286,14 @@ pub fn truncate_utf16_at_nul(v: &[u16]) -> &[u16] { } } +pub fn ensure_no_nuls<T: AsRef<OsStr>>(s: T) -> crate::io::Result<T> { + if s.as_ref().encode_wide().any(|b| b == 0) { + Err(crate::io::const_error!(ErrorKind::InvalidInput, "nul byte found in provided data")) + } else { + Ok(s) + } +} + pub trait IsZero { fn is_zero(&self) -> bool; } diff --git a/library/std/src/sys/pal/windows/process.rs b/library/std/src/sys/pal/windows/process.rs deleted file mode 100644 index 50e4baba607..00000000000 --- a/library/std/src/sys/pal/windows/process.rs +++ /dev/null @@ -1,929 +0,0 @@ -#![unstable(feature = "process_internals", issue = "none")] - -#[cfg(test)] -mod tests; - -use core::ffi::c_void; - -use super::api::{self, WinError}; -use crate::collections::BTreeMap; -use crate::env::consts::{EXE_EXTENSION, EXE_SUFFIX}; -use crate::ffi::{OsStr, OsString}; -use crate::io::{self, Error, ErrorKind}; -use crate::num::NonZero; -use crate::os::windows::ffi::{OsStrExt, OsStringExt}; -use crate::os::windows::io::{AsHandle, AsRawHandle, BorrowedHandle, FromRawHandle, IntoRawHandle}; -use crate::os::windows::process::ProcThreadAttributeList; -use crate::path::{Path, PathBuf}; -use crate::sync::Mutex; -use crate::sys::args::{self, Arg}; -use crate::sys::c::{self, EXIT_FAILURE, EXIT_SUCCESS}; -use crate::sys::fs::{File, OpenOptions}; -use crate::sys::handle::Handle; -use crate::sys::pipe::{self, AnonPipe}; -use crate::sys::{cvt, path, stdio}; -use crate::sys_common::IntoInner; -use crate::sys_common::process::{CommandEnv, CommandEnvs}; -use crate::{cmp, env, fmt, ptr}; - -//////////////////////////////////////////////////////////////////////////////// -// Command -//////////////////////////////////////////////////////////////////////////////// - -#[derive(Clone, Debug, Eq)] -#[doc(hidden)] -pub struct EnvKey { - os_string: OsString, - // This stores a UTF-16 encoded string to workaround the mismatch between - // Rust's OsString (WTF-8) and the Windows API string type (UTF-16). - // Normally converting on every API call is acceptable but here - // `c::CompareStringOrdinal` will be called for every use of `==`. - utf16: Vec<u16>, -} - -impl EnvKey { - fn new<T: Into<OsString>>(key: T) -> Self { - EnvKey::from(key.into()) - } -} - -// Comparing Windows environment variable keys[1] are behaviorally the -// composition of two operations[2]: -// -// 1. Case-fold both strings. This is done using a language-independent -// uppercase mapping that's unique to Windows (albeit based on data from an -// older Unicode spec). It only operates on individual UTF-16 code units so -// surrogates are left unchanged. This uppercase mapping can potentially change -// between Windows versions. -// -// 2. Perform an ordinal comparison of the strings. A comparison using ordinal -// is just a comparison based on the numerical value of each UTF-16 code unit[3]. -// -// Because the case-folding mapping is unique to Windows and not guaranteed to -// be stable, we ask the OS to compare the strings for us. This is done by -// calling `CompareStringOrdinal`[4] with `bIgnoreCase` set to `TRUE`. -// -// [1] https://docs.microsoft.com/en-us/dotnet/standard/base-types/best-practices-strings#choosing-a-stringcomparison-member-for-your-method-call -// [2] https://docs.microsoft.com/en-us/dotnet/standard/base-types/best-practices-strings#stringtoupper-and-stringtolower -// [3] https://docs.microsoft.com/en-us/dotnet/api/system.stringcomparison?view=net-5.0#System_StringComparison_Ordinal -// [4] https://docs.microsoft.com/en-us/windows/win32/api/stringapiset/nf-stringapiset-comparestringordinal -impl Ord for EnvKey { - fn cmp(&self, other: &Self) -> cmp::Ordering { - unsafe { - let result = c::CompareStringOrdinal( - self.utf16.as_ptr(), - self.utf16.len() as _, - other.utf16.as_ptr(), - other.utf16.len() as _, - c::TRUE, - ); - match result { - c::CSTR_LESS_THAN => cmp::Ordering::Less, - c::CSTR_EQUAL => cmp::Ordering::Equal, - c::CSTR_GREATER_THAN => cmp::Ordering::Greater, - // `CompareStringOrdinal` should never fail so long as the parameters are correct. - _ => panic!("comparing environment keys failed: {}", Error::last_os_error()), - } - } - } -} -impl PartialOrd for EnvKey { - fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> { - Some(self.cmp(other)) - } -} -impl PartialEq for EnvKey { - fn eq(&self, other: &Self) -> bool { - if self.utf16.len() != other.utf16.len() { - false - } else { - self.cmp(other) == cmp::Ordering::Equal - } - } -} -impl PartialOrd<str> for EnvKey { - fn partial_cmp(&self, other: &str) -> Option<cmp::Ordering> { - Some(self.cmp(&EnvKey::new(other))) - } -} -impl PartialEq<str> for EnvKey { - fn eq(&self, other: &str) -> bool { - if self.os_string.len() != other.len() { - false - } else { - self.cmp(&EnvKey::new(other)) == cmp::Ordering::Equal - } - } -} - -// Environment variable keys should preserve their original case even though -// they are compared using a caseless string mapping. -impl From<OsString> for EnvKey { - fn from(k: OsString) -> Self { - EnvKey { utf16: k.encode_wide().collect(), os_string: k } - } -} - -impl From<EnvKey> for OsString { - fn from(k: EnvKey) -> Self { - k.os_string - } -} - -impl From<&OsStr> for EnvKey { - fn from(k: &OsStr) -> Self { - Self::from(k.to_os_string()) - } -} - -impl AsRef<OsStr> for EnvKey { - fn as_ref(&self) -> &OsStr { - &self.os_string - } -} - -pub(crate) fn ensure_no_nuls<T: AsRef<OsStr>>(s: T) -> io::Result<T> { - if s.as_ref().encode_wide().any(|b| b == 0) { - Err(io::const_error!(ErrorKind::InvalidInput, "nul byte found in provided data")) - } else { - Ok(s) - } -} - -pub struct Command { - program: OsString, - args: Vec<Arg>, - env: CommandEnv, - cwd: Option<OsString>, - flags: u32, - show_window: Option<u16>, - detach: bool, // not currently exposed in std::process - stdin: Option<Stdio>, - stdout: Option<Stdio>, - stderr: Option<Stdio>, - force_quotes_enabled: bool, -} - -pub enum Stdio { - Inherit, - InheritSpecific { from_stdio_id: u32 }, - Null, - MakePipe, - Pipe(AnonPipe), - Handle(Handle), -} - -pub struct StdioPipes { - pub stdin: Option<AnonPipe>, - pub stdout: Option<AnonPipe>, - pub stderr: Option<AnonPipe>, -} - -impl Command { - pub fn new(program: &OsStr) -> Command { - Command { - program: program.to_os_string(), - args: Vec::new(), - env: Default::default(), - cwd: None, - flags: 0, - show_window: None, - detach: false, - stdin: None, - stdout: None, - stderr: None, - force_quotes_enabled: false, - } - } - - pub fn arg(&mut self, arg: &OsStr) { - self.args.push(Arg::Regular(arg.to_os_string())) - } - pub fn env_mut(&mut self) -> &mut CommandEnv { - &mut self.env - } - pub fn cwd(&mut self, dir: &OsStr) { - self.cwd = Some(dir.to_os_string()) - } - pub fn stdin(&mut self, stdin: Stdio) { - self.stdin = Some(stdin); - } - pub fn stdout(&mut self, stdout: Stdio) { - self.stdout = Some(stdout); - } - pub fn stderr(&mut self, stderr: Stdio) { - self.stderr = Some(stderr); - } - pub fn creation_flags(&mut self, flags: u32) { - self.flags = flags; - } - pub fn show_window(&mut self, cmd_show: Option<u16>) { - self.show_window = cmd_show; - } - - pub fn force_quotes(&mut self, enabled: bool) { - self.force_quotes_enabled = enabled; - } - - pub fn raw_arg(&mut self, command_str_to_append: &OsStr) { - self.args.push(Arg::Raw(command_str_to_append.to_os_string())) - } - - pub fn get_program(&self) -> &OsStr { - &self.program - } - - pub fn get_args(&self) -> CommandArgs<'_> { - let iter = self.args.iter(); - CommandArgs { iter } - } - - pub fn get_envs(&self) -> CommandEnvs<'_> { - self.env.iter() - } - - pub fn get_current_dir(&self) -> Option<&Path> { - self.cwd.as_ref().map(Path::new) - } - - pub fn spawn( - &mut self, - default: Stdio, - needs_stdin: bool, - ) -> io::Result<(Process, StdioPipes)> { - self.spawn_with_attributes(default, needs_stdin, None) - } - - pub fn spawn_with_attributes( - &mut self, - default: Stdio, - needs_stdin: bool, - proc_thread_attribute_list: Option<&ProcThreadAttributeList<'_>>, - ) -> io::Result<(Process, StdioPipes)> { - let env_saw_path = self.env.have_changed_path(); - let maybe_env = self.env.capture_if_changed(); - - let child_paths = if env_saw_path && let Some(env) = maybe_env.as_ref() { - env.get(&EnvKey::new("PATH")).map(|s| s.as_os_str()) - } else { - None - }; - let program = resolve_exe(&self.program, || env::var_os("PATH"), child_paths)?; - let has_bat_extension = |program: &[u16]| { - matches!( - // Case insensitive "ends_with" of UTF-16 encoded ".bat" or ".cmd" - program.len().checked_sub(4).and_then(|i| program.get(i..)), - Some([46, 98 | 66, 97 | 65, 116 | 84] | [46, 99 | 67, 109 | 77, 100 | 68]) - ) - }; - let is_batch_file = if path::is_verbatim(&program) { - has_bat_extension(&program[..program.len() - 1]) - } else { - super::fill_utf16_buf( - |buffer, size| unsafe { - // resolve the path so we can test the final file name. - c::GetFullPathNameW(program.as_ptr(), size, buffer, ptr::null_mut()) - }, - |program| has_bat_extension(program), - )? - }; - let (program, mut cmd_str) = if is_batch_file { - ( - command_prompt()?, - args::make_bat_command_line(&program, &self.args, self.force_quotes_enabled)?, - ) - } else { - let cmd_str = make_command_line(&self.program, &self.args, self.force_quotes_enabled)?; - (program, cmd_str) - }; - cmd_str.push(0); // add null terminator - - // stolen from the libuv code. - let mut flags = self.flags | c::CREATE_UNICODE_ENVIRONMENT; - if self.detach { - flags |= c::DETACHED_PROCESS | c::CREATE_NEW_PROCESS_GROUP; - } - - let (envp, _data) = make_envp(maybe_env)?; - let (dirp, _data) = make_dirp(self.cwd.as_ref())?; - let mut pi = zeroed_process_information(); - - // Prepare all stdio handles to be inherited by the child. This - // currently involves duplicating any existing ones with the ability to - // be inherited by child processes. Note, however, that once an - // inheritable handle is created, *any* spawned child will inherit that - // handle. We only want our own child to inherit this handle, so we wrap - // the remaining portion of this spawn in a mutex. - // - // For more information, msdn also has an article about this race: - // https://support.microsoft.com/kb/315939 - static CREATE_PROCESS_LOCK: Mutex<()> = Mutex::new(()); - - let _guard = CREATE_PROCESS_LOCK.lock(); - - let mut pipes = StdioPipes { stdin: None, stdout: None, stderr: None }; - let null = Stdio::Null; - let default_stdin = if needs_stdin { &default } else { &null }; - let stdin = self.stdin.as_ref().unwrap_or(default_stdin); - let stdout = self.stdout.as_ref().unwrap_or(&default); - let stderr = self.stderr.as_ref().unwrap_or(&default); - let stdin = stdin.to_handle(c::STD_INPUT_HANDLE, &mut pipes.stdin)?; - let stdout = stdout.to_handle(c::STD_OUTPUT_HANDLE, &mut pipes.stdout)?; - let stderr = stderr.to_handle(c::STD_ERROR_HANDLE, &mut pipes.stderr)?; - - let mut si = zeroed_startupinfo(); - - // If at least one of stdin, stdout or stderr are set (i.e. are non null) - // then set the `hStd` fields in `STARTUPINFO`. - // Otherwise skip this and allow the OS to apply its default behavior. - // This provides more consistent behavior between Win7 and Win8+. - let is_set = |stdio: &Handle| !stdio.as_raw_handle().is_null(); - if is_set(&stderr) || is_set(&stdout) || is_set(&stdin) { - si.dwFlags |= c::STARTF_USESTDHANDLES; - si.hStdInput = stdin.as_raw_handle(); - si.hStdOutput = stdout.as_raw_handle(); - si.hStdError = stderr.as_raw_handle(); - } - - if let Some(cmd_show) = self.show_window { - si.dwFlags |= c::STARTF_USESHOWWINDOW; - si.wShowWindow = cmd_show; - } - - let si_ptr: *mut c::STARTUPINFOW; - - let mut si_ex; - - if let Some(proc_thread_attribute_list) = proc_thread_attribute_list { - si.cb = size_of::<c::STARTUPINFOEXW>() as u32; - flags |= c::EXTENDED_STARTUPINFO_PRESENT; - - si_ex = c::STARTUPINFOEXW { - StartupInfo: si, - // SAFETY: Casting this `*const` pointer to a `*mut` pointer is "safe" - // here because windows does not internally mutate the attribute list. - // Ideally this should be reflected in the interface of the `windows-sys` crate. - lpAttributeList: proc_thread_attribute_list.as_ptr().cast::<c_void>().cast_mut(), - }; - si_ptr = (&raw mut si_ex) as _; - } else { - si.cb = size_of::<c::STARTUPINFOW>() as u32; - si_ptr = (&raw mut si) as _; - } - - unsafe { - cvt(c::CreateProcessW( - program.as_ptr(), - cmd_str.as_mut_ptr(), - ptr::null_mut(), - ptr::null_mut(), - c::TRUE, - flags, - envp, - dirp, - si_ptr, - &mut pi, - )) - }?; - - unsafe { - Ok(( - Process { - handle: Handle::from_raw_handle(pi.hProcess), - main_thread_handle: Handle::from_raw_handle(pi.hThread), - }, - pipes, - )) - } - } - - pub fn output(&mut self) -> io::Result<(ExitStatus, Vec<u8>, Vec<u8>)> { - let (proc, pipes) = self.spawn(Stdio::MakePipe, false)?; - crate::sys_common::process::wait_with_output(proc, pipes) - } -} - -impl fmt::Debug for Command { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.program.fmt(f)?; - for arg in &self.args { - f.write_str(" ")?; - match arg { - Arg::Regular(s) => s.fmt(f), - Arg::Raw(s) => f.write_str(&s.to_string_lossy()), - }?; - } - Ok(()) - } -} - -// Resolve `exe_path` to the executable name. -// -// * If the path is simply a file name then use the paths given by `search_paths` to find the executable. -// * Otherwise use the `exe_path` as given. -// -// This function may also append `.exe` to the name. The rationale for doing so is as follows: -// -// It is a very strong convention that Windows executables have the `exe` extension. -// In Rust, it is common to omit this extension. -// Therefore this functions first assumes `.exe` was intended. -// It falls back to the plain file name if a full path is given and the extension is omitted -// or if only a file name is given and it already contains an extension. -fn resolve_exe<'a>( - exe_path: &'a OsStr, - parent_paths: impl FnOnce() -> Option<OsString>, - child_paths: Option<&OsStr>, -) -> io::Result<Vec<u16>> { - // Early return if there is no filename. - if exe_path.is_empty() || path::has_trailing_slash(exe_path) { - return Err(io::const_error!(io::ErrorKind::InvalidInput, "program path has no file name")); - } - // Test if the file name has the `exe` extension. - // This does a case-insensitive `ends_with`. - let has_exe_suffix = if exe_path.len() >= EXE_SUFFIX.len() { - exe_path.as_encoded_bytes()[exe_path.len() - EXE_SUFFIX.len()..] - .eq_ignore_ascii_case(EXE_SUFFIX.as_bytes()) - } else { - false - }; - - // If `exe_path` is an absolute path or a sub-path then don't search `PATH` for it. - if !path::is_file_name(exe_path) { - if has_exe_suffix { - // The application name is a path to a `.exe` file. - // Let `CreateProcessW` figure out if it exists or not. - return args::to_user_path(Path::new(exe_path)); - } - let mut path = PathBuf::from(exe_path); - - // Append `.exe` if not already there. - path = path::append_suffix(path, EXE_SUFFIX.as_ref()); - if let Some(path) = program_exists(&path) { - return Ok(path); - } else { - // It's ok to use `set_extension` here because the intent is to - // remove the extension that was just added. - path.set_extension(""); - return args::to_user_path(&path); - } - } else { - ensure_no_nuls(exe_path)?; - // From the `CreateProcessW` docs: - // > If the file name does not contain an extension, .exe is appended. - // Note that this rule only applies when searching paths. - let has_extension = exe_path.as_encoded_bytes().contains(&b'.'); - - // Search the directories given by `search_paths`. - let result = search_paths(parent_paths, child_paths, |mut path| { - path.push(exe_path); - if !has_extension { - path.set_extension(EXE_EXTENSION); - } - program_exists(&path) - }); - if let Some(path) = result { - return Ok(path); - } - } - // If we get here then the executable cannot be found. - Err(io::const_error!(io::ErrorKind::NotFound, "program not found")) -} - -// Calls `f` for every path that should be used to find an executable. -// Returns once `f` returns the path to an executable or all paths have been searched. -fn search_paths<Paths, Exists>( - parent_paths: Paths, - child_paths: Option<&OsStr>, - mut exists: Exists, -) -> Option<Vec<u16>> -where - Paths: FnOnce() -> Option<OsString>, - Exists: FnMut(PathBuf) -> Option<Vec<u16>>, -{ - // 1. Child paths - // This is for consistency with Rust's historic behavior. - if let Some(paths) = child_paths { - for path in env::split_paths(paths).filter(|p| !p.as_os_str().is_empty()) { - if let Some(path) = exists(path) { - return Some(path); - } - } - } - - // 2. Application path - if let Ok(mut app_path) = env::current_exe() { - app_path.pop(); - if let Some(path) = exists(app_path) { - return Some(path); - } - } - - // 3 & 4. System paths - // SAFETY: This uses `fill_utf16_buf` to safely call the OS functions. - unsafe { - if let Ok(Some(path)) = super::fill_utf16_buf( - |buf, size| c::GetSystemDirectoryW(buf, size), - |buf| exists(PathBuf::from(OsString::from_wide(buf))), - ) { - return Some(path); - } - #[cfg(not(target_vendor = "uwp"))] - { - if let Ok(Some(path)) = super::fill_utf16_buf( - |buf, size| c::GetWindowsDirectoryW(buf, size), - |buf| exists(PathBuf::from(OsString::from_wide(buf))), - ) { - return Some(path); - } - } - } - - // 5. Parent paths - if let Some(parent_paths) = parent_paths() { - for path in env::split_paths(&parent_paths).filter(|p| !p.as_os_str().is_empty()) { - if let Some(path) = exists(path) { - return Some(path); - } - } - } - None -} - -/// Checks if a file exists without following symlinks. -fn program_exists(path: &Path) -> Option<Vec<u16>> { - unsafe { - let path = args::to_user_path(path).ok()?; - // Getting attributes using `GetFileAttributesW` does not follow symlinks - // and it will almost always be successful if the link exists. - // There are some exceptions for special system files (e.g. the pagefile) - // but these are not executable. - if c::GetFileAttributesW(path.as_ptr()) == c::INVALID_FILE_ATTRIBUTES { - None - } else { - Some(path) - } - } -} - -impl Stdio { - fn to_handle(&self, stdio_id: u32, pipe: &mut Option<AnonPipe>) -> io::Result<Handle> { - let use_stdio_id = |stdio_id| match stdio::get_handle(stdio_id) { - Ok(io) => unsafe { - let io = Handle::from_raw_handle(io); - let ret = io.duplicate(0, true, c::DUPLICATE_SAME_ACCESS); - let _ = io.into_raw_handle(); // Don't close the handle - ret - }, - // If no stdio handle is available, then propagate the null value. - Err(..) => unsafe { Ok(Handle::from_raw_handle(ptr::null_mut())) }, - }; - match *self { - Stdio::Inherit => use_stdio_id(stdio_id), - Stdio::InheritSpecific { from_stdio_id } => use_stdio_id(from_stdio_id), - - Stdio::MakePipe => { - let ours_readable = stdio_id != c::STD_INPUT_HANDLE; - let pipes = pipe::anon_pipe(ours_readable, true)?; - *pipe = Some(pipes.ours); - Ok(pipes.theirs.into_handle()) - } - - Stdio::Pipe(ref source) => { - let ours_readable = stdio_id != c::STD_INPUT_HANDLE; - pipe::spawn_pipe_relay(source, ours_readable, true).map(AnonPipe::into_handle) - } - - Stdio::Handle(ref handle) => handle.duplicate(0, true, c::DUPLICATE_SAME_ACCESS), - - // Open up a reference to NUL with appropriate read/write - // permissions as well as the ability to be inherited to child - // processes (as this is about to be inherited). - Stdio::Null => { - let size = size_of::<c::SECURITY_ATTRIBUTES>(); - let mut sa = c::SECURITY_ATTRIBUTES { - nLength: size as u32, - lpSecurityDescriptor: ptr::null_mut(), - bInheritHandle: 1, - }; - let mut opts = OpenOptions::new(); - opts.read(stdio_id == c::STD_INPUT_HANDLE); - opts.write(stdio_id != c::STD_INPUT_HANDLE); - opts.security_attributes(&mut sa); - File::open(Path::new(r"\\.\NUL"), &opts).map(|file| file.into_inner()) - } - } - } -} - -impl From<AnonPipe> for Stdio { - fn from(pipe: AnonPipe) -> Stdio { - Stdio::Pipe(pipe) - } -} - -impl From<Handle> for Stdio { - fn from(pipe: Handle) -> Stdio { - Stdio::Handle(pipe) - } -} - -impl From<File> for Stdio { - fn from(file: File) -> Stdio { - Stdio::Handle(file.into_inner()) - } -} - -impl From<io::Stdout> for Stdio { - fn from(_: io::Stdout) -> Stdio { - Stdio::InheritSpecific { from_stdio_id: c::STD_OUTPUT_HANDLE } - } -} - -impl From<io::Stderr> for Stdio { - fn from(_: io::Stderr) -> Stdio { - Stdio::InheritSpecific { from_stdio_id: c::STD_ERROR_HANDLE } - } -} - -//////////////////////////////////////////////////////////////////////////////// -// Processes -//////////////////////////////////////////////////////////////////////////////// - -/// A value representing a child process. -/// -/// The lifetime of this value is linked to the lifetime of the actual -/// process - the Process destructor calls self.finish() which waits -/// for the process to terminate. -pub struct Process { - handle: Handle, - main_thread_handle: Handle, -} - -impl Process { - pub fn kill(&mut self) -> io::Result<()> { - let result = unsafe { c::TerminateProcess(self.handle.as_raw_handle(), 1) }; - if result == c::FALSE { - let error = api::get_last_error(); - // TerminateProcess returns ERROR_ACCESS_DENIED if the process has already been - // terminated (by us, or for any other reason). So check if the process was actually - // terminated, and if so, do not return an error. - if error != WinError::ACCESS_DENIED || self.try_wait().is_err() { - return Err(crate::io::Error::from_raw_os_error(error.code as i32)); - } - } - Ok(()) - } - - pub fn id(&self) -> u32 { - unsafe { c::GetProcessId(self.handle.as_raw_handle()) } - } - - pub fn main_thread_handle(&self) -> BorrowedHandle<'_> { - self.main_thread_handle.as_handle() - } - - pub fn wait(&mut self) -> io::Result<ExitStatus> { - unsafe { - let res = c::WaitForSingleObject(self.handle.as_raw_handle(), c::INFINITE); - if res != c::WAIT_OBJECT_0 { - return Err(Error::last_os_error()); - } - let mut status = 0; - cvt(c::GetExitCodeProcess(self.handle.as_raw_handle(), &mut status))?; - Ok(ExitStatus(status)) - } - } - - pub fn try_wait(&mut self) -> io::Result<Option<ExitStatus>> { - unsafe { - match c::WaitForSingleObject(self.handle.as_raw_handle(), 0) { - c::WAIT_OBJECT_0 => {} - c::WAIT_TIMEOUT => { - return Ok(None); - } - _ => return Err(io::Error::last_os_error()), - } - let mut status = 0; - cvt(c::GetExitCodeProcess(self.handle.as_raw_handle(), &mut status))?; - Ok(Some(ExitStatus(status))) - } - } - - pub fn handle(&self) -> &Handle { - &self.handle - } - - pub fn into_handle(self) -> Handle { - self.handle - } -} - -#[derive(PartialEq, Eq, Clone, Copy, Debug, Default)] -pub struct ExitStatus(u32); - -impl ExitStatus { - pub fn exit_ok(&self) -> Result<(), ExitStatusError> { - match NonZero::<u32>::try_from(self.0) { - /* was nonzero */ Ok(failure) => Err(ExitStatusError(failure)), - /* was zero, couldn't convert */ Err(_) => Ok(()), - } - } - pub fn code(&self) -> Option<i32> { - Some(self.0 as i32) - } -} - -/// Converts a raw `u32` to a type-safe `ExitStatus` by wrapping it without copying. -impl From<u32> for ExitStatus { - fn from(u: u32) -> ExitStatus { - ExitStatus(u) - } -} - -impl fmt::Display for ExitStatus { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - // Windows exit codes with the high bit set typically mean some form of - // unhandled exception or warning. In this scenario printing the exit - // code in decimal doesn't always make sense because it's a very large - // and somewhat gibberish number. The hex code is a bit more - // recognizable and easier to search for, so print that. - if self.0 & 0x80000000 != 0 { - write!(f, "exit code: {:#x}", self.0) - } else { - write!(f, "exit code: {}", self.0) - } - } -} - -#[derive(PartialEq, Eq, Clone, Copy, Debug)] -pub struct ExitStatusError(NonZero<u32>); - -impl Into<ExitStatus> for ExitStatusError { - fn into(self) -> ExitStatus { - ExitStatus(self.0.into()) - } -} - -impl ExitStatusError { - pub fn code(self) -> Option<NonZero<i32>> { - Some((u32::from(self.0) as i32).try_into().unwrap()) - } -} - -#[derive(PartialEq, Eq, Clone, Copy, Debug)] -pub struct ExitCode(u32); - -impl ExitCode { - pub const SUCCESS: ExitCode = ExitCode(EXIT_SUCCESS as _); - pub const FAILURE: ExitCode = ExitCode(EXIT_FAILURE as _); - - #[inline] - pub fn as_i32(&self) -> i32 { - self.0 as i32 - } -} - -impl From<u8> for ExitCode { - fn from(code: u8) -> Self { - ExitCode(u32::from(code)) - } -} - -impl From<u32> for ExitCode { - fn from(code: u32) -> Self { - ExitCode(u32::from(code)) - } -} - -fn zeroed_startupinfo() -> c::STARTUPINFOW { - c::STARTUPINFOW { - cb: 0, - lpReserved: ptr::null_mut(), - lpDesktop: ptr::null_mut(), - lpTitle: ptr::null_mut(), - dwX: 0, - dwY: 0, - dwXSize: 0, - dwYSize: 0, - dwXCountChars: 0, - dwYCountChars: 0, - dwFillAttribute: 0, - dwFlags: 0, - wShowWindow: 0, - cbReserved2: 0, - lpReserved2: ptr::null_mut(), - hStdInput: ptr::null_mut(), - hStdOutput: ptr::null_mut(), - hStdError: ptr::null_mut(), - } -} - -fn zeroed_process_information() -> c::PROCESS_INFORMATION { - c::PROCESS_INFORMATION { - hProcess: ptr::null_mut(), - hThread: ptr::null_mut(), - dwProcessId: 0, - dwThreadId: 0, - } -} - -// Produces a wide string *without terminating null*; returns an error if -// `prog` or any of the `args` contain a nul. -fn make_command_line(argv0: &OsStr, args: &[Arg], force_quotes: bool) -> io::Result<Vec<u16>> { - // Encode the command and arguments in a command line string such - // that the spawned process may recover them using CommandLineToArgvW. - let mut cmd: Vec<u16> = Vec::new(); - - // Always quote the program name so CreateProcess to avoid ambiguity when - // the child process parses its arguments. - // Note that quotes aren't escaped here because they can't be used in arg0. - // But that's ok because file paths can't contain quotes. - cmd.push(b'"' as u16); - cmd.extend(argv0.encode_wide()); - cmd.push(b'"' as u16); - - for arg in args { - cmd.push(' ' as u16); - args::append_arg(&mut cmd, arg, force_quotes)?; - } - Ok(cmd) -} - -// Get `cmd.exe` for use with bat scripts, encoded as a UTF-16 string. -fn command_prompt() -> io::Result<Vec<u16>> { - let mut system: Vec<u16> = super::fill_utf16_buf( - |buf, size| unsafe { c::GetSystemDirectoryW(buf, size) }, - |buf| buf.into(), - )?; - system.extend("\\cmd.exe".encode_utf16().chain([0])); - Ok(system) -} - -fn make_envp(maybe_env: Option<BTreeMap<EnvKey, OsString>>) -> io::Result<(*mut c_void, Vec<u16>)> { - // On Windows we pass an "environment block" which is not a char**, but - // rather a concatenation of null-terminated k=v\0 sequences, with a final - // \0 to terminate. - if let Some(env) = maybe_env { - let mut blk = Vec::new(); - - // If there are no environment variables to set then signal this by - // pushing a null. - if env.is_empty() { - blk.push(0); - } - - for (k, v) in env { - ensure_no_nuls(k.os_string)?; - blk.extend(k.utf16); - blk.push('=' as u16); - blk.extend(ensure_no_nuls(v)?.encode_wide()); - blk.push(0); - } - blk.push(0); - Ok((blk.as_mut_ptr() as *mut c_void, blk)) - } else { - Ok((ptr::null_mut(), Vec::new())) - } -} - -fn make_dirp(d: Option<&OsString>) -> io::Result<(*const u16, Vec<u16>)> { - match d { - Some(dir) => { - let mut dir_str: Vec<u16> = ensure_no_nuls(dir)?.encode_wide().collect(); - dir_str.push(0); - Ok((dir_str.as_ptr(), dir_str)) - } - None => Ok((ptr::null(), Vec::new())), - } -} - -pub struct CommandArgs<'a> { - iter: crate::slice::Iter<'a, Arg>, -} - -impl<'a> Iterator for CommandArgs<'a> { - type Item = &'a OsStr; - fn next(&mut self) -> Option<&'a OsStr> { - self.iter.next().map(|arg| match arg { - Arg::Regular(s) | Arg::Raw(s) => s.as_ref(), - }) - } - fn size_hint(&self) -> (usize, Option<usize>) { - self.iter.size_hint() - } -} - -impl<'a> ExactSizeIterator for CommandArgs<'a> { - fn len(&self) -> usize { - self.iter.len() - } - fn is_empty(&self) -> bool { - self.iter.is_empty() - } -} - -impl<'a> fmt::Debug for CommandArgs<'a> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_list().entries(self.iter.clone()).finish() - } -} diff --git a/library/std/src/sys/pal/windows/process/tests.rs b/library/std/src/sys/pal/windows/process/tests.rs deleted file mode 100644 index 1377e12162f..00000000000 --- a/library/std/src/sys/pal/windows/process/tests.rs +++ /dev/null @@ -1,223 +0,0 @@ -use super::{Arg, make_command_line}; -use crate::env; -use crate::ffi::{OsStr, OsString}; -use crate::process::Command; - -#[test] -fn test_raw_args() { - let command_line = &make_command_line( - OsStr::new("quoted exe"), - &[ - Arg::Regular(OsString::from("quote me")), - Arg::Raw(OsString::from("quote me *not*")), - Arg::Raw(OsString::from("\t\\")), - Arg::Raw(OsString::from("internal \\\"backslash-\"quote")), - Arg::Regular(OsString::from("optional-quotes")), - ], - false, - ) - .unwrap(); - assert_eq!( - String::from_utf16(command_line).unwrap(), - "\"quoted exe\" \"quote me\" quote me *not* \t\\ internal \\\"backslash-\"quote optional-quotes" - ); -} - -#[test] -fn test_thread_handle() { - use crate::os::windows::io::BorrowedHandle; - use crate::os::windows::process::{ChildExt, CommandExt}; - const CREATE_SUSPENDED: u32 = 0x00000004; - - let p = Command::new("cmd").args(&["/C", "exit 0"]).creation_flags(CREATE_SUSPENDED).spawn(); - assert!(p.is_ok()); - let mut p = p.unwrap(); - - unsafe extern "system" { - fn ResumeThread(_: BorrowedHandle<'_>) -> u32; - } - unsafe { - ResumeThread(p.main_thread_handle()); - } - - crate::thread::sleep(crate::time::Duration::from_millis(100)); - - let res = p.try_wait(); - assert!(res.is_ok()); - assert!(res.unwrap().is_some()); - assert!(p.try_wait().unwrap().unwrap().success()); -} - -#[test] -fn test_make_command_line() { - fn test_wrapper(prog: &str, args: &[&str], force_quotes: bool) -> String { - let command_line = &make_command_line( - OsStr::new(prog), - &args.iter().map(|a| Arg::Regular(OsString::from(a))).collect::<Vec<_>>(), - force_quotes, - ) - .unwrap(); - String::from_utf16(command_line).unwrap() - } - - assert_eq!(test_wrapper("prog", &["aaa", "bbb", "ccc"], false), "\"prog\" aaa bbb ccc"); - - assert_eq!(test_wrapper("prog", &[r"C:\"], false), r#""prog" C:\"#); - assert_eq!(test_wrapper("prog", &[r"2slashes\\"], false), r#""prog" 2slashes\\"#); - assert_eq!(test_wrapper("prog", &[r" C:\"], false), r#""prog" " C:\\""#); - assert_eq!(test_wrapper("prog", &[r" 2slashes\\"], false), r#""prog" " 2slashes\\\\""#); - - assert_eq!( - test_wrapper("C:\\Program Files\\blah\\blah.exe", &["aaa"], false), - "\"C:\\Program Files\\blah\\blah.exe\" aaa" - ); - assert_eq!( - test_wrapper("C:\\Program Files\\blah\\blah.exe", &["aaa", "v*"], false), - "\"C:\\Program Files\\blah\\blah.exe\" aaa v*" - ); - assert_eq!( - test_wrapper("C:\\Program Files\\blah\\blah.exe", &["aaa", "v*"], true), - "\"C:\\Program Files\\blah\\blah.exe\" \"aaa\" \"v*\"" - ); - assert_eq!( - test_wrapper("C:\\Program Files\\test", &["aa\"bb"], false), - "\"C:\\Program Files\\test\" aa\\\"bb" - ); - assert_eq!(test_wrapper("echo", &["a b c"], false), "\"echo\" \"a b c\""); - assert_eq!( - test_wrapper("echo", &["\" \\\" \\", "\\"], false), - "\"echo\" \"\\\" \\\\\\\" \\\\\" \\" - ); - assert_eq!( - test_wrapper("\u{03c0}\u{042f}\u{97f3}\u{00e6}\u{221e}", &[], false), - "\"\u{03c0}\u{042f}\u{97f3}\u{00e6}\u{221e}\"" - ); -} - -// On Windows, environment args are case preserving but comparisons are case-insensitive. -// See: #85242 -#[test] -fn windows_env_unicode_case() { - let test_cases = [ - ("ä", "Ä"), - ("ß", "SS"), - ("Ä", "Ö"), - ("Ä", "Ö"), - ("I", "İ"), - ("I", "i"), - ("I", "ı"), - ("i", "I"), - ("i", "İ"), - ("i", "ı"), - ("İ", "I"), - ("İ", "i"), - ("İ", "ı"), - ("ı", "I"), - ("ı", "i"), - ("ı", "İ"), - ("ä", "Ä"), - ("ß", "SS"), - ("Ä", "Ö"), - ("Ä", "Ö"), - ("I", "İ"), - ("I", "i"), - ("I", "ı"), - ("i", "I"), - ("i", "İ"), - ("i", "ı"), - ("İ", "I"), - ("İ", "i"), - ("İ", "ı"), - ("ı", "I"), - ("ı", "i"), - ("ı", "İ"), - ]; - // Test that `cmd.env` matches `env::set_var` when setting two strings that - // may (or may not) be case-folded when compared. - for (a, b) in test_cases.iter() { - let mut cmd = Command::new("cmd"); - cmd.env(a, "1"); - cmd.env(b, "2"); - unsafe { - env::set_var(a, "1"); - env::set_var(b, "2"); - } - - for (key, value) in cmd.get_envs() { - assert_eq!( - env::var(key).ok(), - value.map(|s| s.to_string_lossy().into_owned()), - "command environment mismatch: {a} {b}", - ); - } - } -} - -// UWP applications run in a restricted environment which means this test may not work. -#[cfg(not(target_vendor = "uwp"))] -#[test] -fn windows_exe_resolver() { - use super::resolve_exe; - use crate::io; - use crate::sys::fs::symlink; - use crate::test_helpers::tmpdir; - - let env_paths = || env::var_os("PATH"); - - // Test a full path, with and without the `exe` extension. - let mut current_exe = env::current_exe().unwrap(); - assert!(resolve_exe(current_exe.as_ref(), env_paths, None).is_ok()); - current_exe.set_extension(""); - assert!(resolve_exe(current_exe.as_ref(), env_paths, None).is_ok()); - - // Test lone file names. - assert!(resolve_exe(OsStr::new("cmd"), env_paths, None).is_ok()); - assert!(resolve_exe(OsStr::new("cmd.exe"), env_paths, None).is_ok()); - assert!(resolve_exe(OsStr::new("cmd.EXE"), env_paths, None).is_ok()); - assert!(resolve_exe(OsStr::new("fc"), env_paths, None).is_ok()); - - // Invalid file names should return InvalidInput. - assert_eq!( - resolve_exe(OsStr::new(""), env_paths, None).unwrap_err().kind(), - io::ErrorKind::InvalidInput - ); - assert_eq!( - resolve_exe(OsStr::new("\0"), env_paths, None).unwrap_err().kind(), - io::ErrorKind::InvalidInput - ); - // Trailing slash, therefore there's no file name component. - assert_eq!( - resolve_exe(OsStr::new(r"C:\Path\to\"), env_paths, None).unwrap_err().kind(), - io::ErrorKind::InvalidInput - ); - - /* - Some of the following tests may need to be changed if you are deliberately - changing the behavior of `resolve_exe`. - */ - - let empty_paths = || None; - - // The resolver looks in system directories even when `PATH` is empty. - assert!(resolve_exe(OsStr::new("cmd.exe"), empty_paths, None).is_ok()); - - // The application's directory is also searched. - let current_exe = env::current_exe().unwrap(); - assert!(resolve_exe(current_exe.file_name().unwrap().as_ref(), empty_paths, None).is_ok()); - - // Create a temporary path and add a broken symlink. - let temp = tmpdir(); - let mut exe_path = temp.path().to_owned(); - exe_path.push("exists.exe"); - - // A broken symlink should still be resolved. - // Skip this check if not in CI and creating symlinks isn't possible. - let is_ci = env::var("CI").is_ok(); - let result = symlink("<DOES NOT EXIST>".as_ref(), &exe_path); - if is_ci || result.is_ok() { - result.unwrap(); - assert!( - resolve_exe(OsStr::new("exists.exe"), empty_paths, Some(temp.path().as_ref())).is_ok() - ); - } -} diff --git a/library/std/src/sys/pal/xous/mod.rs b/library/std/src/sys/pal/xous/mod.rs index 7d823012ad1..58926e2beb1 100644 --- a/library/std/src/sys/pal/xous/mod.rs +++ b/library/std/src/sys/pal/xous/mod.rs @@ -6,8 +6,6 @@ pub mod env; pub mod os; #[path = "../unsupported/pipe.rs"] pub mod pipe; -#[path = "../unsupported/process.rs"] -pub mod process; pub mod thread; pub mod time; diff --git a/library/std/src/sys/pal/zkvm/mod.rs b/library/std/src/sys/pal/zkvm/mod.rs index 499e2787201..4659dad16e8 100644 --- a/library/std/src/sys/pal/zkvm/mod.rs +++ b/library/std/src/sys/pal/zkvm/mod.rs @@ -17,8 +17,6 @@ pub mod env; pub mod os; #[path = "../unsupported/pipe.rs"] pub mod pipe; -#[path = "../unsupported/process.rs"] -pub mod process; #[path = "../unsupported/thread.rs"] pub mod thread; #[path = "../unsupported/time.rs"] |
