diff --git a/changelog/2475.added.md b/changelog/2475.added.md new file mode 100644 index 0000000000..3d1aab151e --- /dev/null +++ b/changelog/2475.added.md @@ -0,0 +1 @@ +Added wrappers for `posix_spawn` API diff --git a/src/lib.rs b/src/lib.rs index eb6d2f5f3e..02234c0ec7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -184,6 +184,18 @@ pub mod unistd; #[cfg(any(feature = "poll", feature = "event"))] mod poll_timeout; +#[cfg(any( + target_os = "freebsd", + target_os = "haiku", + target_os = "linux", + target_os = "netbsd", + apple_targets +))] +feature! { + #![feature = "process"] + pub mod spawn; +} + use std::ffi::{CStr, CString, OsStr}; use std::mem::MaybeUninit; use std::os::unix::ffi::OsStrExt; diff --git a/src/spawn.rs b/src/spawn.rs new file mode 100644 index 0000000000..48686757dd --- /dev/null +++ b/src/spawn.rs @@ -0,0 +1,426 @@ +//! Safe wrappers around posix_spawn* functions found in the libc "spawn.h" header. + +use std::{ + ffi::CStr, + mem, + os::unix::io::{AsFd, AsRawFd}, +}; + +#[cfg(any(feature = "fs", feature = "term"))] +use crate::fcntl::OFlag; +#[cfg(feature = "signal")] +use crate::sys::signal::SigSet; +#[cfg(feature = "fs")] +use crate::sys::stat::Mode; +use crate::{errno::Errno, unistd::Pid, NixPath, Result}; + +/// A spawn attributes object. See [posix_spawnattr_t](https://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_spawnattr_init.html). +#[repr(transparent)] +#[derive(Debug)] +pub struct PosixSpawnAttr { + attr: libc::posix_spawnattr_t, +} + +impl PosixSpawnAttr { + /// Initialize the spawn attributes object. See + /// [posix_spawnattr_init](https://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_spawnattr_init.html). + #[doc(alias("posix_spawnattr_init"))] + pub fn init() -> Result { + let mut attr = mem::MaybeUninit::uninit(); + let res = unsafe { libc::posix_spawnattr_init(attr.as_mut_ptr()) }; + + Errno::result(res)?; + + let attr = unsafe { attr.assume_init() }; + Ok(PosixSpawnAttr { attr }) + } + + /// Reinitialize the spawn attributes object. + /// This is a wrapper around + /// [posix_spawnattr_destroy](https://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_spawnattr_destroy.html) + /// followed by + /// [posix_spawnattr_init](https://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_spawnattr_init.html). + #[doc(alias("posix_spawnattr_destroy"))] + pub fn reinit(mut self) -> Result { + let res = unsafe { + libc::posix_spawnattr_destroy( + &mut self.attr as *mut libc::posix_spawnattr_t, + ) + }; + Errno::result(res)?; + + let res = unsafe { + libc::posix_spawnattr_init( + &mut self.attr as *mut libc::posix_spawnattr_t, + ) + }; + Errno::result(res)?; + + Ok(self) + } + + /// Set spawn flags. See + /// [posix_spawnattr_setflags](https://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_spawnattr_setflags.html). + #[doc(alias("posix_spawnattr_setflags"))] + pub fn set_flags(&mut self, flags: PosixSpawnFlags) -> Result<()> { + let res = unsafe { + libc::posix_spawnattr_setflags( + &mut self.attr as *mut libc::posix_spawnattr_t, + flags.bits() as libc::c_short, + ) + }; + Errno::result(res)?; + + Ok(()) + } + + /// Get spawn flags. See + /// [posix_spawnattr_getflags](https://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_spawnattr_getflags.html). + #[doc(alias("posix_spawnattr_getflags"))] + pub fn flags(&self) -> Result { + let mut flags: libc::c_short = 0; + let res = unsafe { + libc::posix_spawnattr_getflags( + &self.attr as *const libc::posix_spawnattr_t, + &mut flags, + ) + }; + Errno::result(res)?; + + Ok(PosixSpawnFlags::from_bits_truncate(flags.into())) + } + + /// Set spawn pgroup. See + /// [posix_spawnattr_setpgroup](https://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_spawnattr_setpgroup.html). + #[doc(alias("posix_spawnattr_setpgroup"))] + pub fn set_pgroup(&mut self, pgroup: Pid) -> Result<()> { + let res = unsafe { + libc::posix_spawnattr_setpgroup( + &mut self.attr as *mut libc::posix_spawnattr_t, + pgroup.as_raw(), + ) + }; + Errno::result(res)?; + + Ok(()) + } + + /// Get spawn pgroup. See + /// [posix_spawnattr_getpgroup](https://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_spawnattr_getpgroup.html). + #[doc(alias("posix_spawnattr_getpgroup"))] + pub fn pgroup(&self) -> Result { + let mut pid: libc::pid_t = 0; + + let res = unsafe { + libc::posix_spawnattr_getpgroup( + &self.attr as *const libc::posix_spawnattr_t, + &mut pid, + ) + }; + Errno::result(res)?; + + Ok(Pid::from_raw(pid)) + } + + feature! { + #![feature = "signal"] + /// Set spawn sigdefault. See + /// [posix_spawnattr_setsigdefault](https://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_spawnattr_setsigdefault.html). + #[doc(alias("posix_spawnattr_setsigdefault"))] + pub fn set_sigdefault(&mut self, sigdefault: &SigSet) -> Result<()> { + let res = unsafe { + libc::posix_spawnattr_setsigdefault( + &mut self.attr as *mut libc::posix_spawnattr_t, + sigdefault.as_ref(), + ) + }; + Errno::result(res)?; + + Ok(()) + } + + /// Get spawn sigdefault. See + /// [posix_spawnattr_getsigdefault](https://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_spawnattr_getsigdefault.html). + #[doc(alias("posix_spawnattr_getsigdefault"))] + pub fn sigdefault(&self) -> Result { + let mut sigset = mem::MaybeUninit::uninit(); + + let res = unsafe { + libc::posix_spawnattr_getsigdefault( + &self.attr as *const libc::posix_spawnattr_t, + sigset.as_mut_ptr(), + ) + }; + Errno::result(res)?; + + let sigdefault = + unsafe { SigSet::from_sigset_t_unchecked(sigset.assume_init()) }; + Ok(sigdefault) + } + + /// Set spawn sigmask. See + /// [posix_spawnattr_setsigmask](https://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_spawnattr_setsigmask.html). + #[doc(alias("posix_spawnattr_setsigmask"))] + pub fn set_sigmask(&mut self, sigdefault: &SigSet) -> Result<()> { + let res = unsafe { + libc::posix_spawnattr_setsigmask( + &mut self.attr as *mut libc::posix_spawnattr_t, + sigdefault.as_ref(), + ) + }; + Errno::result(res)?; + + Ok(()) + } + + /// Get spawn sigmask. See + /// [posix_spawnattr_getsigmask](https://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_spawnattr_getsigmask.html). + #[doc(alias("posix_spawnattr_getsigmask"))] + pub fn sigmask(&self) -> Result { + let mut sigset = mem::MaybeUninit::uninit(); + + let res = unsafe { + libc::posix_spawnattr_getsigmask( + &self.attr as *const libc::posix_spawnattr_t, + sigset.as_mut_ptr(), + ) + }; + Errno::result(res)?; + + let sigdefault = + unsafe { SigSet::from_sigset_t_unchecked(sigset.assume_init()) }; + Ok(sigdefault) + } + } +} + +impl Drop for PosixSpawnAttr { + fn drop(&mut self) { + unsafe { + libc::posix_spawnattr_destroy( + &mut self.attr as *mut libc::posix_spawnattr_t, + ); + } + } +} + +libc_bitflags!( + /// Process attributes to be changed in the new process image when invoking [`posix_spawn`] + /// or [`posix_spawnp`]. See + /// [posix_spawn](https://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_spawn.html). + pub struct PosixSpawnFlags: libc::c_int { + /// Reset effective user ID of the child process to parent's real user ID. + POSIX_SPAWN_RESETIDS; + /// Put the child in a process group specified by the spawn-pgroup attribute. See + /// [posix_spawnattr_setpgroup](https://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_spawnattr_setpgroup.html). + POSIX_SPAWN_SETPGROUP; + /// Force set signals to default signal handling in child process. See + /// [posix_spawnattr_setsigdefault](https://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_spawnattr_setsigdefault.html). + #[cfg(feature = "signal")] + POSIX_SPAWN_SETSIGDEF; + /// Set signal mask of child process. See + /// [posix_spawnattr_setsigmask](https://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_spawnattr_setsigmask.html). + #[cfg(feature = "signal")] + POSIX_SPAWN_SETSIGMASK; + // TODO: Add support for the following two flags whenever support for + // https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/sched.h.html + // is added to nix. + // POSIX_SPAWN_SETSCHEDPARAM; + // POSIX_SPAWN_SETSCHEDULER; + } +); + +/// A spawn file actions object. See [posix_spawn_file_actions_t](https://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_spawn_file_actions_addclose.html). +#[repr(transparent)] +#[derive(Debug)] +pub struct PosixSpawnFileActions { + fa: libc::posix_spawn_file_actions_t, +} + +impl PosixSpawnFileActions { + /// Initialize the spawn file actions object. See + /// [posix_spawn_file_actions_init](https://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_spawn_file_actions_init.html). + #[doc(alias("posix_spawn_file_actions_init"))] + pub fn init() -> Result { + let mut actions = mem::MaybeUninit::uninit(); + let res = unsafe { + libc::posix_spawn_file_actions_init(actions.as_mut_ptr()) + }; + Errno::result(res)?; + Ok(unsafe { + PosixSpawnFileActions { + fa: actions.assume_init(), + } + }) + } + + /// Reinitialize the spawn file actions object. + /// This is a wrapper around + /// [posix_spawn_file_actions_destroy](https://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_spawn_file_actions_destroy.html). + /// followed by + /// [posix_spawn_file_actions_init](https://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_spawn_file_actions_init.html). + #[doc(alias("posix_spawn_file_actions_destroy"))] + pub fn reinit(mut self) -> Result { + let res = unsafe { + libc::posix_spawn_file_actions_destroy( + &mut self.fa as *mut libc::posix_spawn_file_actions_t, + ) + }; + Errno::result(res)?; + + let res = unsafe { + libc::posix_spawn_file_actions_init( + &mut self.fa as *mut libc::posix_spawn_file_actions_t, + ) + }; + Errno::result(res)?; + + Ok(self) + } + + /// Add a [dup2](https://pubs.opengroup.org/onlinepubs/9699919799/functions/dup2.html) action. See + /// [posix_spawn_file_actions_adddup2](https://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_spawn_file_actions_adddup2.html). + #[doc(alias("posix_spawn_file_actions_adddup2"))] + pub fn add_dup2( + &mut self, + fd: Fd1, + newfd: Fd2, + ) -> Result<()> { + let res = unsafe { + libc::posix_spawn_file_actions_adddup2( + &mut self.fa as *mut libc::posix_spawn_file_actions_t, + fd.as_fd().as_raw_fd(), + newfd.as_fd().as_raw_fd(), + ) + }; + Errno::result(res)?; + + Ok(()) + } + + feature! { + #![all(feature = "fs", feature = "term")] + /// Add an open action. See + /// [posix_spawn_file_actions_addopen](https://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_spawn_file_actions_addopen.html). + #[doc(alias("posix_spawn_file_actions_addopen"))] + pub fn add_open( + &mut self, + fd: Fd, + path: &P, + oflag: OFlag, + mode: Mode, + ) -> Result<()> { + let res = path.with_nix_path(|cstr| unsafe { + libc::posix_spawn_file_actions_addopen( + &mut self.fa as *mut libc::posix_spawn_file_actions_t, + fd.as_fd().as_raw_fd(), + cstr.as_ptr(), + oflag.bits(), + mode.bits(), + ) + })?; + Errno::result(res)?; + + Ok(()) + } + } + + /// Add a close action. See + /// [posix_spawn_file_actions_addclose](https://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_spawn_file_actions_addclose.html). + #[doc(alias("posix_spawn_file_actions_addclose"))] + pub fn add_close(&mut self, fd: Fd) -> Result<()> { + let res = unsafe { + libc::posix_spawn_file_actions_addclose( + &mut self.fa as *mut libc::posix_spawn_file_actions_t, + fd.as_fd().as_raw_fd(), + ) + }; + Errno::result(res)?; + + Ok(()) + } +} + +impl Drop for PosixSpawnFileActions { + fn drop(&mut self) { + unsafe { + libc::posix_spawn_file_actions_destroy( + &mut self.fa as *mut libc::posix_spawn_file_actions_t, + ); + } + } +} + +// The POSIX standard requires those `args` and `envp` to be of type `*const *mut [c_char]`, +// but implementations won't modify them, making the `mut` type redundant. Considering this, +// Nix does not expose this mutability, but we have to change the interface when calling the +// underlying libc interfaces , this helper function does the conversion job. +// +// SAFETY: +// It is safe to add the mutability in types as implementations won't mutable them. +unsafe fn to_exec_array>(args: &[S]) -> Vec<*mut libc::c_char> { + let mut v: Vec<*mut libc::c_char> = args + .iter() + .map(|s| s.as_ref().as_ptr().cast_mut()) + .collect(); + v.push(std::ptr::null_mut()); + v +} + +/// Create a new child process from the specified process image. See +/// [posix_spawn](https://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_spawn.html). +pub fn posix_spawn, SE: AsRef>( + path: &CStr, + file_actions: &PosixSpawnFileActions, + attr: &PosixSpawnAttr, + args: &[SA], + envp: &[SE], +) -> Result { + let mut pid = 0; + + let res = unsafe { + let args_p = to_exec_array(args); + let env_p = to_exec_array(envp); + + libc::posix_spawn( + &mut pid as *mut libc::pid_t, + path.as_ptr(), + &file_actions.fa as *const libc::posix_spawn_file_actions_t, + &attr.attr as *const libc::posix_spawnattr_t, + args_p.as_ptr(), + env_p.as_ptr(), + ) + }; + + Errno::result(res)?; + Ok(Pid::from_raw(pid)) +} + +/// Create a new child process from the specified process image. See +/// [posix_spawnp](https://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_spawnp.html). +pub fn posix_spawnp, SE: AsRef>( + path: &CStr, + file_actions: &PosixSpawnFileActions, + attr: &PosixSpawnAttr, + args: &[SA], + envp: &[SE], +) -> Result { + let mut pid = 0; + + let res = unsafe { + let args_p = to_exec_array(args); + let env_p = to_exec_array(envp); + + libc::posix_spawnp( + &mut pid as *mut libc::pid_t, + path.as_ptr(), + &file_actions.fa as *const libc::posix_spawn_file_actions_t, + &attr.attr as *const libc::posix_spawnattr_t, + args_p.as_ptr(), + env_p.as_ptr(), + ) + }; + + Errno::result(res)?; + Ok(Pid::from_raw(pid)) +} diff --git a/test/test.rs b/test/test.rs index 3427b2c3db..6b970f8f09 100644 --- a/test/test.rs +++ b/test/test.rs @@ -33,6 +33,15 @@ mod test_pty; mod test_sched; #[cfg(any(linux_android, freebsdlike, apple_targets, solarish))] mod test_sendfile; +#[cfg(any( + target_os = "freebsd", + target_os = "haiku", + target_os = "linux", + target_os = "netbsd", + apple_targets +))] +mod test_spawn; + mod test_time; mod test_unistd; diff --git a/test/test_spawn.rs b/test/test_spawn.rs new file mode 100644 index 0000000000..1283c96ca8 --- /dev/null +++ b/test/test_spawn.rs @@ -0,0 +1,65 @@ +use std::ffi::CString; + +use nix::spawn::{self, PosixSpawnAttr, PosixSpawnFileActions}; +use nix::sys::signal; +use nix::sys::wait::{waitpid, WaitPidFlag, WaitStatus}; + +#[test] +fn spawn_true() { + let bin = &CString::new("true").unwrap(); + let args = &[ + CString::new("true").unwrap(), + CString::new("story").unwrap(), + ]; + let vars: &[CString] = &[]; + let actions = PosixSpawnFileActions::init().unwrap(); + let attr = PosixSpawnAttr::init().unwrap(); + + let pid = spawn::posix_spawnp(bin, &actions, &attr, args, vars).unwrap(); + + let status = waitpid(pid, Some(WaitPidFlag::empty())).unwrap(); + + match status { + WaitStatus::Exited(wpid, ret) => { + assert_eq!(pid, wpid); + assert_eq!(ret, 0); + } + _ => { + panic!("Invalid WaitStatus"); + } + }; +} + +#[test] +fn spawn_sleep() { + let bin = &CString::new("sleep").unwrap(); + let args = &[CString::new("sleep").unwrap(), CString::new("30").unwrap()]; + let vars: &[CString] = &[]; + let actions = PosixSpawnFileActions::init().unwrap(); + let attr = PosixSpawnAttr::init().unwrap(); + + let pid = spawn::posix_spawnp(bin, &actions, &attr, args, vars).unwrap(); + + let status = + waitpid(pid, WaitPidFlag::from_bits(WaitPidFlag::WNOHANG.bits())) + .unwrap(); + match status { + WaitStatus::StillAlive => {} + _ => { + panic!("Invalid WaitStatus"); + } + }; + + signal::kill(pid, signal::SIGTERM).unwrap(); + + let status = waitpid(pid, Some(WaitPidFlag::empty())).unwrap(); + match status { + WaitStatus::Signaled(wpid, wsignal, _) => { + assert_eq!(pid, wpid); + assert_eq!(wsignal, signal::SIGTERM); + } + _ => { + panic!("Invalid WaitStatus"); + } + }; +}