Merge branch 'spawn' into 'master'

Add support for `posix_spawn` and `posix_spawnp` (Redox OS)

Closes #192

See merge request redox-os/relibc!1333
This commit is contained in:
Mathew John Roberts
2026-06-17 06:18:59 +01:00
18 changed files with 1232 additions and 110 deletions
+1 -1
View File
@@ -126,7 +126,7 @@ pub mod setjmp;
pub mod sgtty;
pub mod shadow;
pub mod signal;
// TODO: spawn.h
pub mod spawn;
// TODO: stdalign.h (likely C implementation)
pub mod stdarg;
// stdatomic.h implemented in C
+20
View File
@@ -0,0 +1,20 @@
# POSIX header spec: https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/spawn.h.html
#
# Spec quotations relating to includes:
# - "The <spawn.h> header shall define the mode_t and pid_t types as described in <sys/types.h>."
# - "The <spawn.h> header shall define the sigset_t type as described in <signal.h>."
# - "The tag sched_param shall be declared as naming an incomplete structure type, the contents of which are described in the <sched.h> header."
# - "Inclusion of the <spawn.h> header may make visible symbols defined in the <sched.h> and <signal.h> headers."
#
# shed.h brings in pid_t
# size_t would have been brought in by signal.h so we can include it here directly
sys_includes = ["sched.h", "bits/sigset-t.h", "bits/mode-t.h", "bits/size-t.h"]
include_guard = "_RELIBC_SPAWN_H"
language = "C"
cpp_compat = true
[enum]
prefix_with_name = true
[export.rename]
"sched_param" = "struct sched_param"
+221
View File
@@ -0,0 +1,221 @@
use alloc::{ffi::CString, vec::Vec};
use core::ptr;
use crate::{
header::errno::EBADF,
platform::types::{c_char, c_int, c_uchar, mode_t, size_t},
};
#[derive(Debug, Clone)]
pub enum Action {
Open {
fd: c_int,
path: CString,
flag: c_int,
mode: mode_t,
},
Close(c_int),
Chdir(CString),
FChdir(c_int),
Dup2(c_int, c_int),
}
struct FileActions(Vec<Action>);
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct posix_spawn_file_actions_t {
__relibc_internal_size: [c_uchar; 24],
__relibc_internal_align: size_t,
}
impl posix_spawn_file_actions_t {
pub fn add_action(&mut self, action: Action) {
let v = ptr::from_mut(self).cast::<FileActions>();
unsafe {
(*v).0.push(action);
}
}
}
pub struct FileActionsIter {
actions: posix_spawn_file_actions_t,
curr: usize,
}
impl Iterator for FileActionsIter {
type Item = Action;
fn next(&mut self) -> Option<Self::Item> {
let actions = unsafe {
ptr::from_mut(&mut self.actions)
.cast::<FileActions>()
.as_ref()
.unwrap()
};
let e = actions.0.get(self.curr)?;
self.curr += 1;
Some((*e).clone())
}
}
impl<'a> IntoIterator for &'a posix_spawn_file_actions_t {
type Item = Action;
type IntoIter = FileActionsIter;
fn into_iter(self) -> Self::IntoIter {
FileActionsIter {
actions: (*self).clone(),
curr: 0,
}
}
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/posix_spawn_file_actions_init.html>
///
/// Panics if `file_actions` is `NULL`.
#[unsafe(no_mangle)]
pub extern "C" fn posix_spawn_file_actions_init(
file_actions: *mut posix_spawn_file_actions_t,
) -> c_int {
if file_actions.is_null() {
panic!("file_actions cannot be NULL");
}
let v = Vec::new();
let actions = FileActions(v);
unsafe {
ptr::write(file_actions.cast::<FileActions>(), actions);
}
0
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/posix_spawn_file_actions_destroy.html>
///
/// Panics if `file_actions` is `NULL`.
///
/// # Safety:
/// If `file_actions` is not `NULL`, then it must be a pointer to a `file_actions` object that was initialised by calling `posix_spawn_file_actions_init`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn posix_spawn_file_actions_destroy(
file_actions: *mut posix_spawn_file_actions_t,
) -> c_int {
if file_actions.is_null() {
panic!("file_actions cannot be NULL");
}
unsafe {
let _ = *(file_actions.cast::<FileActions>());
}
0
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/posix_spawn_file_actions_addopen.html>
///
/// Panics if `file_actions` is `NULL`.
///
/// # Safety:
/// `path` must be a valid null-terminated C string.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn posix_spawn_file_actions_addopen(
file_actions: *mut posix_spawn_file_actions_t,
fd: c_int,
path: *const c_char,
oflag: c_int,
mode: mode_t,
) -> c_int {
let file_actions = unsafe { file_actions.as_mut().expect("file_actions cannot be NULL") };
if fd < 0 {
return EBADF;
}
file_actions.add_action(Action::Open {
fd,
path: if path.is_null() {
CString::new("").unwrap()
} else {
unsafe { CString::from_raw(path as *mut c_char) }
},
flag: oflag,
mode,
});
0
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/posix_spawn_file_actions_addclose.html>
///
/// Panics if `file_actions` is `NULL`.
///
/// # Safety:
/// `file_actions` must be initialised.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn posix_spawn_file_actions_addclose(
file_actions: *mut posix_spawn_file_actions_t,
fd: c_int,
) -> c_int {
let file_actions = unsafe { file_actions.as_mut().expect("file_actions cannot be NULL") };
if fd < 0 {
return EBADF;
}
file_actions.add_action(Action::Close(fd));
0
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/posix_spawn_file_actions_addchdir.html>
///
/// Panics if `file_actions` is `NULL`.
///
/// # Safety:
/// `file_actions` must be initialised and `path` must be a valid null-terminated C string.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn posix_spawn_file_actions_addchdir(
file_actions: *mut posix_spawn_file_actions_t,
path: *const c_char,
) -> c_int {
let file_actions = unsafe { file_actions.as_mut().expect("file_actions cannot be NULL") };
file_actions.add_action(Action::Chdir(unsafe {
if path.is_null() {
CString::new("").unwrap()
} else {
CString::from_raw(path as *mut c_char)
}
}));
0
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/posix_spawn_file_actions_addfchdir.html>
///
/// Panics if `file_actions` is `NULL`.
///
/// # Safety:
/// `file_actions` must be initialised.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn posix_spawn_file_actions_addfchdir(
file_actions: *mut posix_spawn_file_actions_t,
fd: c_int,
) -> c_int {
let file_actions = unsafe { file_actions.as_mut().expect("file_actions cannot be NULL") };
if fd < 0 {
return EBADF;
}
file_actions.add_action(Action::FChdir(fd));
0
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/posix_spawn_file_actions_adddup2.html>
///
/// Panics if `file_actions` is `NULL`.
///
/// # Safety:
/// `file_actions` must be initialised.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn posix_spawn_file_actions_adddup2(
file_actions: *mut posix_spawn_file_actions_t,
fd: c_int,
new: c_int,
) -> c_int {
let file_actions = unsafe { file_actions.as_mut().expect("file_actions cannot be NULL") };
if fd < 0 || new < 0 {
return EBADF;
}
file_actions.add_action(Action::Dup2(fd, new));
0
}
+218
View File
@@ -0,0 +1,218 @@
//! `spawn.h` implementation
//!
//! See <https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/spawn.h.html>.
mod file_actions;
mod spawn_attr;
use core::str::FromStr;
use alloc::{
ffi::CString,
string::{String, ToString},
};
pub use file_actions::{Action, posix_spawn_file_actions_t};
pub use spawn_attr::{Flags, posix_spawnattr_t};
use crate::{
c_str::CStr,
error::{Errno, Result},
header::{
dirent::{opendir, readdir},
errno::{ENOENT, ENOTDIR},
limits::PATH_MAX,
stdlib::getenv,
unistd::{chdir, getcwd},
},
iter::NulTerminated,
platform::{
self, ERRNO, Pal,
types::{c_char, c_int, pid_t},
},
};
unsafe fn spawn(
pid: Option<&mut pid_t>,
mut program: String,
file_actions: Option<&posix_spawn_file_actions_t>,
spawn_attr: Option<&posix_spawnattr_t>,
argv: NulTerminated<*mut c_char>,
envp: Option<NulTerminated<*mut c_char>>,
use_path: bool,
) -> Result<()> {
let mut original_cwd = [0 as c_char; PATH_MAX];
assert!(
unsafe { !getcwd(original_cwd.as_mut_ptr() as *mut c_char, PATH_MAX).is_null() },
"Error getting cwd: {}",
ERRNO.get()
);
let mut path_path = None;
if use_path {
let path = unsafe { getenv(c"PATH".as_ptr()) };
let path_env = unsafe { CStr::from_nullable_ptr(path).unwrap().to_str().unwrap() };
let path_elements = path_env.split(':');
let mut flag = false;
'a: for path_element in path_elements {
let pe = CString::from_str(path_element).unwrap();
let dir = if let Some(dir) =
unsafe { opendir(pe.as_bytes_with_nul().as_ptr() as *const c_char).as_mut() }
{
dir
} else if ERRNO.get() == 0 || ERRNO.get() == ENOTDIR || ERRNO.get() == ENOENT {
continue;
} else {
return Err(Errno(ERRNO.get()));
};
while let Some(dir_ent) = unsafe { readdir(dir).as_ref() } {
let dir_ent_name = unsafe {
CStr::from_ptr(dir_ent.d_name.as_ptr() as *const c_char)
.to_str()
.unwrap()
.to_string()
};
if dir_ent_name == program {
flag = true;
program = format!("{}/{}", path_element, program);
path_path = Some(path_element.to_string());
break 'a;
}
}
}
if !flag {
return Err(Errno(ENOENT));
}
}
let program = CString::from_str(program.as_str()).unwrap();
unsafe {
platform::Sys::spawn(
CStr::from_bytes_with_nul(program.as_bytes_with_nul()).unwrap(),
file_actions,
spawn_attr,
argv,
envp,
path_path,
)
.map(|v| {
if let Some(pid) = pid {
*pid = v;
}
})
.map_err(|e| {
let status = chdir(original_cwd.as_ptr() as *const c_char);
if status != 0 {
panic!("Error switching back to original cwd: {}", ERRNO.get());
}
e
})?;
}
Ok(())
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/posix_spawn.html>
///
/// `argv` must **not** be `NULL` and must contain atleast the program name. `path` must also be **not** `NULL`. Failure to ensure any of this will result in a panic.
///
/// # Safety:
/// `file_actions` and `attrp` must either be `NULL` or be pointers to properly initialised objects. Doing otherwise is undefined behaviour.
///
/// `path` and the elements in `argv` must be a pointers to valid null-terminated character arrays. Failure to ensure any of this will result in undefined behaviour.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn posix_spawn(
pid: *mut pid_t,
path: *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,
) -> c_int {
let argv = {
if argv.is_null() {
panic!("argv cannot be NULL")
} else {
if unsafe { (*argv).is_null() } {
panic!("argv must contain the program name");
}
unsafe { NulTerminated::new(argv).unwrap() }
}
};
let envp = unsafe { NulTerminated::new(envp) };
let program = unsafe {
CStr::from_ptr(path)
.to_str()
.expect("path cannot be NULL")
.to_string()
};
if let Err(e) = unsafe {
spawn(
pid.as_mut(),
program,
file_actions.as_ref(),
attrp.as_ref(),
argv,
envp,
false,
)
} {
return e.0;
}
0
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/posix_spawnp.html>
///
/// `argv` must **not** be `NULL` and must contain atleast the program name. `path` must also be **not** `NULL`. Failure to ensure any of this will result in a panic.
///
/// # Safety:
/// `file_actions` and `attrp` must either be `NULL` or be pointers to properly initialised objects. Doing otherwise is undefined behaviour.
///
/// `path` and the elements in `argv` must be a pointers to valid null-terminated character arrays. Failure to ensure any of this will result in undefined behaviour.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn posix_spawnp(
pid: *mut pid_t,
path: *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,
) -> c_int {
let argv = {
if argv.is_null() {
panic!("argv cannot be NULL")
} else {
if unsafe { (*argv).is_null() } {
panic!("argv must contain the program name");
}
unsafe { NulTerminated::new(argv).unwrap() }
}
};
let envp = unsafe { NulTerminated::new(envp) };
let program = unsafe {
CStr::from_ptr(path)
.to_str()
.expect("path cannot be NULL")
.to_string()
};
if let Err(e) = unsafe {
spawn(
pid.as_mut(),
program.clone(),
file_actions.as_ref(),
attrp.as_ref(),
argv,
envp,
if program.contains('/') { false } else { true },
)
} {
return e.0;
}
0
}
+281
View File
@@ -0,0 +1,281 @@
use core::{
ffi::{c_int, c_short},
mem::zeroed,
};
use bitflags;
use crate::header::{
bits_sigset_t::sigset_t,
errno::EINVAL,
sched::{SCHED_FIFO, SCHED_OTHER, SCHED_RR, sched_param},
sys_types_internal::pid_t,
};
bitflags::bitflags! {
pub struct Flags: c_short
{
const POSIX_SPAWN_RESETIDS = 1;
const POSIX_SPAWN_SETPGROUP = 2;
const POSIX_SPAWN_SETSIGDEF = 3;
const POSIX_SPAWN_SETSIGMASK = 4;
const POSIX_SPAWN_SETSCHEDPARAM = 5;
const POSIX_SPAWN_SETSCHEDULER = 6;
}
}
pub const POSIX_SPAWN_RESETIDS: c_short = 1;
pub const POSIX_SPAWN_SETPGROUP: c_short = 2;
pub const POSIX_SPAWN_SETSIGDEF: c_short = 3;
pub const POSIX_SPAWN_SETSIGMASK: c_short = 4;
pub const POSIX_SPAWN_SETSCHEDPARAM: c_short = 5;
pub const POSIX_SPAWN_SETSCHEDULER: c_short = 6;
#[repr(C)]
pub struct posix_spawnattr_t {
pub param: sched_param,
pub flags: c_short,
pub pgroup: c_int,
policy: c_int,
pub sigdefault: sigset_t,
pub sigmask: sigset_t,
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/posix_spawnattr_init.html>
///
/// Panics is `attr` is `NULL`.
#[unsafe(no_mangle)]
pub extern "C" fn posix_spawnattr_init(attr: *mut posix_spawnattr_t) -> c_int {
unsafe {
let attr = attr.as_mut().expect("posix_spawnattr_t cannot be NULL");
*attr = zeroed();
}
0
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/posix_spawnattr_destroy.html>
///
/// Panics is `attr` is `NULL`.
///
/// # Safety:
/// `attr` must be a pointer to `posix_spawnattr_t` and must at least be initialised.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn posix_spawnattr_destroy(attr: *mut posix_spawnattr_t) -> c_int {
unsafe {
let attr = attr.as_mut().expect("posix_spawnattr_t cannot be NULL");
*attr = zeroed();
}
0
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/posix_spawnattr_setschedparam.html>
///
/// Panics if `attr` or `schedparam` is `NULL`.
///
/// # Safety:
/// `attr` must be initialised.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn posix_spawnattr_setschedparam(
attr: *mut posix_spawnattr_t,
schedparam: *const sched_param,
) -> c_int {
let attr = unsafe { attr.as_mut().expect("posix_spawnattr_t cannot be NULL") };
let schedparam = unsafe { schedparam.as_ref().expect("schedparam cannot be NULL") };
(*attr).param = *schedparam;
0
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/posix_spawnattr_getschedparam.html>
///
/// Panics if `attr` or `schedparam` is `NULL`.
///
/// # Safety:
/// `attr` must be initialised.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn posix_spawnattr_getschedparam(
attr: *const posix_spawnattr_t,
schedparam: *mut sched_param,
) -> c_int {
let attr = unsafe { attr.as_ref().expect("posix_spawnattr_t cannot be NULL") };
let schedparam = unsafe { schedparam.as_mut().expect("schedparam cannot be NULL") };
*schedparam = (*attr).param;
0
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/posix_spawnattr_setschedpolicy.html>
///
/// Panics if `attr` is `NULL`.
///
/// # Safety:
/// `attr` must be initialised.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn posix_spawnattr_setschedpolicy(
attr: *mut posix_spawnattr_t,
schedpolicy: c_int,
) -> c_int {
let attr = unsafe { attr.as_mut().expect("posix_spawnattr_t cannot be NULL") };
match schedpolicy {
SCHED_FIFO | SCHED_RR | SCHED_OTHER => (*attr).policy = schedpolicy,
_ => return EINVAL,
}
0
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/posix_spawnattr_getschedpolicy.html>
///
/// Panics if `attr` or `schedpolicy` is `NULL`.
///
/// # Safety:
/// `attr` must be initialised.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn posix_spawnattr_getschedpolicy(
attr: *const posix_spawnattr_t,
schedpolicy: *mut c_int,
) -> c_int {
let attr = unsafe { attr.as_ref().expect("posix_spawnattr_t cannot be NULL") };
let schedpolicy = unsafe { schedpolicy.as_mut().expect("schedpolicy cannot be NULL") };
*schedpolicy = (*attr).policy;
0
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/posix_spawnattr_setsigdefault.html>
///
/// Panics if `attr` or `sigdefault` is `NULL`.
///
/// # Safety:
/// `attr` must be initialised.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn posix_spawnattr_setsigdefault(
attr: *mut posix_spawnattr_t,
sigdefault: *const sigset_t,
) -> c_int {
let attr = unsafe { attr.as_mut().expect("posix_spawnattr_t cannot be NULL") };
let sigdefault = unsafe { sigdefault.as_ref().expect("sigdefault cannot be NULL") };
(*attr).sigdefault = *sigdefault;
0
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/posix_spawnattr_getsigdefault.html>
///
/// Panics if `attr` or `sigdefault` is `NULL`.
///
/// # Safety:
/// `attr` must be initialised.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn posix_spawnattr_getsigdefault(
attr: *const posix_spawnattr_t,
sigdefault: *mut sigset_t,
) -> c_int {
let attr = unsafe { attr.as_ref().expect("posix_spawnattr_t cannot be NULL") };
let sigdefault = unsafe { sigdefault.as_mut().expect("sigdefault cannot be NULL") };
*sigdefault = (*attr).sigdefault;
0
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/posix_spawnattr_setsigmask.html>
///
/// Panics if `attr` or `sigmask` is `NULL`.
///
/// # Safety:
/// `attr` must be initialised.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn posix_spawnattr_setsigmask(
attr: *mut posix_spawnattr_t,
sigmask: *const sigset_t,
) -> c_int {
let attr = unsafe { attr.as_mut().expect("posix_spawnattr_t cannot be NULL") };
let sigmask = unsafe { sigmask.as_ref().expect("sigmask cannot be NULL") };
(*attr).sigmask = *sigmask;
0
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/posix_spawnattr_getsigmask.html>
///
/// Panics if `attr` or `sigmask` is `NULL`.
///
/// # Safety:
/// `attr` must be initialised.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn posix_spawnattr_getsigmask(
attr: *const posix_spawnattr_t,
sigmask: *mut sigset_t,
) -> c_int {
let attr = unsafe { attr.as_ref().expect("posix_spawnattr_t cannot be NULL") };
let sigmask = unsafe { sigmask.as_mut().expect("sigmask cannot be NULL") };
*sigmask = (*attr).sigmask;
0
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/posix_spawnattr_setflags.html>
///
/// Panics if `attr` is `NULL`.
///
/// # Safety:
/// `attr` must be initialised.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn posix_spawnattr_setflags(
attr: *mut posix_spawnattr_t,
flags: c_short,
) -> c_int {
let attr = unsafe { attr.as_mut().expect("posix_spawnattr_t cannot be NULL") };
match Flags::from_bits(flags) {
Some(v) => (*attr).flags = v.bits(),
None => {
return EINVAL;
}
}
0
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/posix_spawnattr_getflags.html>
///
/// Panics if `attr` or `flags` is `NULL`.
///
/// # Safety:
/// `attr` must be initialised.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn posix_spawnattr_getflags(
attr: *const posix_spawnattr_t,
flags: *mut c_short,
) -> c_int {
let attr = unsafe { attr.as_ref().expect("posix_spawnattr_t cannot be NULL") };
let flags = unsafe { flags.as_mut().expect("flags cannot be NULL") };
*flags = (*attr).flags;
0
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/posix_spawnattr_setpgroup.html>
///
/// Panics if `attr` is `NULL`.
///
/// # Safety:
/// `attr` must be initialised.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn posix_spawnattr_setpgroup(
attr: *mut posix_spawnattr_t,
pgroup: pid_t,
) -> c_int {
let attr = unsafe { attr.as_mut().expect("posix_spawnattr_t cannot be NULL") };
(*attr).pgroup = pgroup;
0
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/posix_spawnattr_getpgroup.html>
///
/// Panics if `attr` or `pgroup` is `NULL`.
///
/// # Safety:
/// `attr` must be initialised.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn posix_spawnattr_getpgroup(
attr: *const posix_spawnattr_t,
pgroup: *mut pid_t,
) -> c_int {
let attr = unsafe { attr.as_ref().expect("posix_spawnattr_t cannot be NULL") };
let pgroup = unsafe { pgroup.as_mut().expect("pgroup cannot be NULL") };
*pgroup = (*attr).pgroup;
0
}