chore: commit durable overlay state (configs, patches, recipe symlinks)

Pre-existing work from other sessions committed as durable state:
- local/config/drivers.d/ (8 driver configs)
- local/config/firmware-fallbacks.d/ (3 firmware configs)
- local/patches/base/, kernel/, relibc/ (new patch carriers)
- recipes/system/ symlinks (driver-params, acmd, ecmd, usbaudiod)

pkgar build artifacts and cache intentionally excluded.
This commit is contained in:
2026-05-01 03:11:21 +01:00
parent c1749a4688
commit 2d22c6ad59
30 changed files with 4204 additions and 73 deletions
@@ -0,0 +1,359 @@
diff --git a/src/header/pthread/mod.rs b/src/header/pthread/mod.rs
index c742a42..03c4043 100644
--- a/src/header/pthread/mod.rs
+++ b/src/header/pthread/mod.rs
@@ -3,15 +3,26 @@
//! See <https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/pthread.h.html>.
use alloc::collections::LinkedList;
-use core::{cell::Cell, ptr::NonNull};
+use core::{cell::Cell, mem::size_of, ptr::NonNull};
+
+#[cfg(target_os = "redox")]
+use redox_rt::proc::FdGuard;
+#[cfg(target_os = "linux")]
+use sc::syscall;
+#[cfg(target_os = "redox")]
+use syscall;
use crate::{
error::Errno,
- header::{bits_timespec::timespec, sched::*},
+ header::{
+ bits_timespec::timespec,
+ errno::{EINVAL, ERANGE},
+ sched::*,
+ },
platform::{
Pal, Sys,
types::{
- c_int, c_uchar, c_uint, c_void, clockid_t, pthread_attr_t, pthread_barrier_t,
+ c_char, c_int, c_uchar, c_uint, c_void, clockid_t, pthread_attr_t, pthread_barrier_t,
pthread_barrierattr_t, pthread_cond_t, pthread_condattr_t, pthread_key_t,
pthread_mutex_t, pthread_mutexattr_t, pthread_once_t, pthread_rwlock_t,
pthread_rwlockattr_t, pthread_spinlock_t, pthread_t, size_t,
@@ -20,6 +31,9 @@ use crate::{
pthread,
};
+#[cfg(target_os = "linux")]
+use crate::platform::sys::e_raw;
+
pub fn e(result: Result<(), Errno>) -> i32 {
match result {
Ok(()) => 0,
@@ -27,6 +41,96 @@ pub fn e(result: Result<(), Errno>) -> i32 {
}
}
+const RLCT_AFFINITY_BYTES: usize = size_of::<u64>();
+const RLCT_MAX_AFFINITY_CPUS: usize = u64::BITS as usize;
+
+fn cpuset_bytes<'a>(cpusetsize: size_t, cpuset: *const cpu_set_t) -> Result<&'a [u8], Errno> {
+ if cpuset.is_null() || !(RLCT_AFFINITY_BYTES..=size_of::<cpu_set_t>()).contains(&cpusetsize) {
+ return Err(Errno(EINVAL));
+ }
+
+ Ok(unsafe { core::slice::from_raw_parts(cpuset.cast::<u8>(), cpusetsize) })
+}
+
+fn cpuset_bytes_mut<'a>(cpusetsize: size_t, cpuset: *mut cpu_set_t) -> Result<&'a mut [u8], Errno> {
+ if cpuset.is_null() || !(RLCT_AFFINITY_BYTES..=size_of::<cpu_set_t>()).contains(&cpusetsize) {
+ return Err(Errno(EINVAL));
+ }
+
+ Ok(unsafe { core::slice::from_raw_parts_mut(cpuset.cast::<u8>(), cpusetsize) })
+}
+
+fn cpuset_to_u64(cpusetsize: size_t, cpuset: *const cpu_set_t) -> Result<u64, Errno> {
+ let bytes = cpuset_bytes(cpusetsize, cpuset)?;
+ let mut mask = 0_u64;
+
+ for (byte_index, byte) in bytes.iter().copied().enumerate() {
+ for bit in 0..u8::BITS as usize {
+ if byte & (1 << bit) == 0 {
+ continue;
+ }
+
+ let cpu = byte_index * u8::BITS as usize + bit;
+ if cpu >= RLCT_MAX_AFFINITY_CPUS {
+ return Err(Errno(EINVAL));
+ }
+
+ mask |= 1_u64 << cpu;
+ }
+ }
+
+ Ok(mask)
+}
+
+fn copy_u64_to_cpuset(mask: u64, cpusetsize: size_t, cpuset: *mut cpu_set_t) -> Result<(), Errno> {
+ let bytes = cpuset_bytes_mut(cpusetsize, cpuset)?;
+ bytes.fill(0);
+
+ for (byte_index, dst) in bytes.iter_mut().take(RLCT_AFFINITY_BYTES).enumerate() {
+ *dst = (mask >> (byte_index * u8::BITS as usize)) as u8;
+ }
+
+ Ok(())
+}
+
+#[cfg(target_os = "redox")]
+fn redox_set_thread_affinity(thread: &pthread::Pthread, mask: u64) -> Result<(), Errno> {
+ let mut kernel_cpuset = cpu_set_t::default();
+ kernel_cpuset.__bits[0] = mask;
+
+ let handle = FdGuard::new(unsafe {
+ syscall::dup(thread.os_tid.get().read().thread_fd, b"sched-affinity")?
+ });
+ let _ = handle.write(unsafe {
+ core::slice::from_raw_parts(
+ core::ptr::from_ref(&kernel_cpuset).cast::<u8>(),
+ size_of::<cpu_set_t>(),
+ )
+ })?;
+
+ Ok(())
+}
+
+#[cfg(target_os = "redox")]
+fn redox_get_thread_affinity(thread: &pthread::Pthread) -> Result<u64, Errno> {
+ let handle = FdGuard::new(unsafe {
+ syscall::dup(thread.os_tid.get().read().thread_fd, b"sched-affinity")?
+ });
+ let mut kernel_cpuset = cpu_set_t::default();
+ let _ = handle.read(unsafe {
+ core::slice::from_raw_parts_mut(
+ core::ptr::from_mut(&mut kernel_cpuset).cast::<u8>(),
+ size_of::<cpu_set_t>(),
+ )
+ })?;
+
+ if kernel_cpuset.__bits[1..].iter().any(|bits| *bits != 0) {
+ return Err(Errno(EINVAL));
+ }
+
+ Ok(kernel_cpuset.__bits[0])
+}
+
#[derive(Clone)]
pub(crate) struct RlctAttr {
pub detachstate: c_uchar,
@@ -186,6 +290,43 @@ pub unsafe extern "C" fn pthread_getcpuclockid(
}
}
+/// GNU extension. See <https://man7.org/linux/man-pages/man3/pthread_setaffinity_np.3.html>.
+#[unsafe(no_mangle)]
+pub unsafe extern "C" fn pthread_getaffinity_np(
+ thread: pthread_t,
+ cpusetsize: size_t,
+ cpuset: *mut cpu_set_t,
+) -> c_int {
+ let thread: &pthread::Pthread = unsafe { &*thread.cast() };
+
+ let result = {
+ #[cfg(target_os = "redox")]
+ {
+ redox_get_thread_affinity(thread)
+ .and_then(|mask| copy_u64_to_cpuset(mask, cpusetsize, cpuset))
+ }
+
+ #[cfg(target_os = "linux")]
+ {
+ if cpuset.is_null() {
+ Err(Errno(EINVAL))
+ } else {
+ e_raw(unsafe {
+ syscall!(
+ SCHED_GETAFFINITY,
+ thread.os_tid.get().read().thread_id,
+ cpusetsize,
+ cpuset.cast::<c_void>()
+ )
+ })
+ .map(|_| ())
+ }
+ }
+ };
+
+ e(result)
+}
+
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/pthread_getschedparam.html>.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn pthread_getschedparam(
@@ -235,6 +376,43 @@ pub unsafe extern "C" fn pthread_self() -> pthread_t {
core::ptr::from_ref(unsafe { pthread::current_thread().unwrap_unchecked() }) as *mut _
}
+/// GNU extension. See <https://man7.org/linux/man-pages/man3/pthread_setaffinity_np.3.html>.
+#[unsafe(no_mangle)]
+pub unsafe extern "C" fn pthread_setaffinity_np(
+ thread: pthread_t,
+ cpusetsize: size_t,
+ cpuset: *const cpu_set_t,
+) -> c_int {
+ let thread: &pthread::Pthread = unsafe { &*thread.cast() };
+
+ let result = {
+ #[cfg(target_os = "redox")]
+ {
+ cpuset_to_u64(cpusetsize, cpuset)
+ .and_then(|mask| redox_set_thread_affinity(thread, mask))
+ }
+
+ #[cfg(target_os = "linux")]
+ {
+ if cpuset.is_null() {
+ Err(Errno(EINVAL))
+ } else {
+ e_raw(unsafe {
+ syscall!(
+ SCHED_SETAFFINITY,
+ thread.os_tid.get().read().thread_id,
+ cpusetsize,
+ cpuset.cast::<c_void>()
+ )
+ })
+ .map(|_| ())
+ }
+ }
+ };
+
+ e(result)
+}
+
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/pthread_setcancelstate.html>.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn pthread_setcancelstate(state: c_int, oldstate: *mut c_int) -> c_int {
@@ -307,6 +485,13 @@ pub unsafe extern "C" fn pthread_testcancel() {
unsafe { pthread::testcancel() };
}
+/// <https://man7.org/linux/man-pages/man3/pthread_yield.3.html>
+///
+/// Non-standard GNU extension. Prefer `sched_yield()` instead.
+pub extern "C" fn pthread_yield() {
+ let _ = Sys::sched_yield();
+}
+
// Must be the same struct as defined in the pthread_cleanup_push macro.
#[repr(C)]
pub(crate) struct CleanupLinkedListEntry {
@@ -350,3 +535,84 @@ pub(crate) unsafe fn run_destructor_stack() {
(entry.routine)(entry.arg);
}
}
+
+#[unsafe(no_mangle)]
+pub unsafe extern "C" fn pthread_setname_np(thread: pthread_t, name: *const c_char) -> c_int {
+ if name.is_null() {
+ return EINVAL;
+ }
+
+ let cstr = unsafe { core::ffi::CStr::from_ptr(name) };
+ let name_bytes = cstr.to_bytes();
+ let len = name_bytes.len().min(31);
+
+ #[cfg(target_os = "redox")]
+ {
+ let thread = unsafe { &*thread.cast::<crate::pthread::Pthread>() };
+ let os_tid = unsafe { thread.os_tid.get().read() };
+ let path = alloc::format!("proc:{}/name\0", os_tid.thread_fd);
+ let path_cstr = core::ffi::CStr::from_bytes_with_nul(path.as_bytes()).unwrap();
+ let fd = match Sys::open(path_cstr.into(), crate::header::fcntl::O_WRONLY, 0) {
+ Ok(fd) => fd,
+ Err(Errno(code)) => return code,
+ };
+
+ let result = match Sys::write(fd, &name_bytes[..len]) {
+ Ok(written) if written == len => 0,
+ Ok(_) => crate::header::errno::EIO,
+ Err(Errno(code)) => code,
+ };
+ let _ = Sys::close(fd);
+ result
+ }
+ #[cfg(not(target_os = "redox"))]
+ {
+ let _ = thread;
+ 0
+ }
+}
+
+#[unsafe(no_mangle)]
+pub unsafe extern "C" fn pthread_getname_np(
+ thread: pthread_t,
+ name: *mut c_char,
+ len: size_t,
+) -> c_int {
+ if name.is_null() {
+ return EINVAL;
+ }
+ if len == 0 {
+ return ERANGE;
+ }
+
+ #[cfg(target_os = "redox")]
+ {
+ let thread = unsafe { &*thread.cast::<crate::pthread::Pthread>() };
+ let os_tid = unsafe { thread.os_tid.get().read() };
+ let path = alloc::format!("proc:{}/name\0", os_tid.thread_fd);
+ let path_cstr = core::ffi::CStr::from_bytes_with_nul(path.as_bytes()).unwrap();
+ let fd = match Sys::open(path_cstr.into(), crate::header::fcntl::O_RDONLY, 0) {
+ Ok(fd) => fd,
+ Err(Errno(code)) => return code,
+ };
+
+ let mut buf = [0u8; 31];
+ let result = match Sys::read(fd, &mut buf) {
+ Ok(read) if read < len => {
+ unsafe { core::ptr::copy_nonoverlapping(buf.as_ptr(), name.cast(), read) };
+ unsafe { *name.add(read) = 0 };
+ 0
+ }
+ Ok(_) => ERANGE,
+ Err(Errno(code)) => code,
+ };
+ let _ = Sys::close(fd);
+ result
+ }
+ #[cfg(not(target_os = "redox"))]
+ {
+ let _ = thread;
+ unsafe { *name = 0 };
+ 0
+ }
+}
diff --git a/src/header/sched/cbindgen.toml b/src/header/sched/cbindgen.toml
index b361fa4..d6d959d 100644
--- a/src/header/sched/cbindgen.toml
+++ b/src/header/sched/cbindgen.toml
@@ -5,7 +5,7 @@
# - "[SS|TSP] The <sched.h> header shall define the time_t type as described in <sys/types.h>."
# - "The <sched.h> header shall define the timespec structure as described in <time.h>."
# - "Inclusion of the <sched.h> header may make visible all symbols from the <time.h> header."
-sys_includes = ["sys/types.h"]
+sys_includes = ["sys/types.h", "stdint.h"]
include_guard = "_RELIBC_SCHED_H"
after_includes = """
#include <bits/timespec.h> // for timespec
@@ -20,3 +20,17 @@ prefix_with_name = true
[export.rename]
"timespec" = "struct timespec"
+
+[export]
+include = [
+ "sched_param",
+ "cpu_set_t",
+ "sched_get_priority_max",
+ "sched_get_priority_min",
+ "sched_getparam",
+ "sched_getscheduler",
+ "sched_rr_get_interval",
+ "sched_setparam",
+ "sched_setscheduler",
+ "sched_yield",
+]
@@ -0,0 +1,69 @@
diff --git a/src/header/spawn/cbindgen.toml b/src/header/spawn/cbindgen.toml
new file mode 100644
index 0000000..a9f188f
--- /dev/null
+++ b/src/header/spawn/cbindgen.toml
@@ -0,0 +1,63 @@
+sys_includes = ["sys/types.h", "signal.h", "sched.h"]
+include_guard = "_SPAWN_H"
+after_includes = """
+typedef struct {
+ short __flags;
+ pid_t __pgrp;
+ sigset_t __sd;
+ sigset_t __ss;
+ struct sched_param __sp;
+ int __policy;
+ int __pad[16];
+} posix_spawnattr_t;
+
+typedef struct {
+ int __allocated;
+ int __used;
+ void *__actions;
+ int __pad[16];
+} posix_spawn_file_actions_t;
+"""
+trailer = """
+#define POSIX_SPAWN_RESETIDS 0x01
+#define POSIX_SPAWN_SETPGROUP 0x02
+#define POSIX_SPAWN_SETSIGDEF 0x04
+#define POSIX_SPAWN_SETSIGMASK 0x08
+#define POSIX_SPAWN_SETSCHEDPARAM 0x10
+#define POSIX_SPAWN_SETSCHEDULER 0x20
+
+int posix_spawn(pid_t *__restrict, const char *__restrict,
+ const posix_spawn_file_actions_t *,
+ const posix_spawnattr_t *__restrict,
+ char *const __restrict[], char *const __restrict[]);
+int posix_spawnp(pid_t *__restrict, const char *__restrict,
+ const posix_spawn_file_actions_t *,
+ const posix_spawnattr_t *__restrict,
+ char *const __restrict[], char *const __restrict[]);
+int posix_spawnattr_init(posix_spawnattr_t *);
+int posix_spawnattr_destroy(posix_spawnattr_t *);
+int posix_spawnattr_setflags(posix_spawnattr_t *, short);
+int posix_spawnattr_getflags(const posix_spawnattr_t *__restrict, short *__restrict);
+int posix_spawnattr_setpgroup(posix_spawnattr_t *, pid_t);
+int posix_spawnattr_getpgroup(const posix_spawnattr_t *__restrict, pid_t *__restrict);
+int posix_spawnattr_setsigdefault(posix_spawnattr_t *__restrict, const sigset_t *__restrict);
+int posix_spawnattr_getsigdefault(posix_spawnattr_t *__restrict, sigset_t *__restrict);
+int posix_spawnattr_setsigmask(posix_spawnattr_t *__restrict, const sigset_t *__restrict);
+int posix_spawnattr_getsigmask(posix_spawnattr_t *__restrict, sigset_t *__restrict);
+int posix_spawn_file_actions_init(posix_spawn_file_actions_t *);
+int posix_spawn_file_actions_destroy(posix_spawn_file_actions_t *);
+int posix_spawn_file_actions_adddup2(posix_spawn_file_actions_t *, int, int);
+int posix_spawn_file_actions_addclose(posix_spawn_file_actions_t *, int);
+int posix_spawn_file_actions_addopen(posix_spawn_file_actions_t *__restrict,
+ int, const char *__restrict, int, mode_t);
+"""
+language = "C"
+style = "Type"
+no_includes = true
+cpp_compat = true
+
+[enum]
+prefix_with_name = true
+
+[export]
+include = []
@@ -0,0 +1,35 @@
Fix sys/timerfd.h cbindgen.toml to generate proper C headers instead of C++.
The empty cbindgen.toml from P3-timerfd-relative.patch caused cbindgen to
generate C++ output (cstdarg, constexpr, etc.) in the installed
sys/timerfd.h. C compilers including this header would fail with
"cstdarg: No such file or directory". Add language="C", after_includes
for bits/timespec.h, and explicit export list for the timerfd constants.
diff --git a/src/header/sys_timerfd/cbindgen.toml b/src/header/sys_timerfd/cbindgen.toml
--- a/src/header/sys_timerfd/cbindgen.toml
+++ b/src/header/sys_timerfd/cbindgen.toml
@@ -1,12 +1,23 @@
sys_includes = ["time.h"]
+after_includes = """
+#include <bits/timespec.h> // for itimerspec
+"""
include_guard = "_SYS_TIMERFD_H"
language = "C"
style = "Tag"
no_includes = true
cpp_compat = true
[enum]
prefix_with_name = true
+[export]
+include = [
+ "TFD_CLOEXEC",
+ "TFD_NONBLOCK",
+ "TFD_TIMER_ABSTIME",
+ "TFD_TIMER_CANCEL_ON_SET",
+]
+
[export.rename]
"itimerspec" = "struct itimerspec"
@@ -0,0 +1,188 @@
diff --git a/src/lib.rs b/src/lib.rs
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -57,61 +57,201 @@ pub mod start;
pub mod sync;
-use crate::platform::{Allocator, NEWALLOCATOR};
+use crate::platform::{Allocator, NEWALLOCATOR, Pal, Sys};
#[global_allocator]
static ALLOCATOR: Allocator = NEWALLOCATOR;
+
+const MAX_FATAL_BACKTRACE_FRAMES: usize = 16;
+const MAX_FATAL_FRAME_STRIDE: usize = 1024 * 1024;
+
+#[inline(never)]
+fn write_process_thread_identity(w: &mut platform::FileWriter) {
+ use core::fmt::Write;
+
+ let pid = Sys::getpid();
+ let tid = Sys::gettid();
+
+ match crate::pthread::current_thread() {
+ Some(thread) => {
+ let _ = w.write_fmt(format_args!(
+ "RELIBC CONTEXT: pid={} tid={} pthread={:#x}\n",
+ pid,
+ tid,
+ thread as *const _ as usize,
+ ));
+ }
+ None => {
+ let _ = w.write_fmt(format_args!(
+ "RELIBC CONTEXT: pid={} tid={} pthread=<unavailable>\n",
+ pid, tid,
+ ));
+ }
+ }
+}
+
+#[cfg(any(target_arch = "x86_64", target_arch = "x86", target_arch = "aarch64"))]
+#[inline(never)]
+fn current_frame_pointer() -> *const usize {
+ let frame: *const usize;
+
+ #[cfg(target_arch = "x86_64")]
+ unsafe {
+ core::arch::asm!("mov {}, rbp", out(reg) frame, options(nomem, nostack, preserves_flags));
+ }
+
+ #[cfg(target_arch = "x86")]
+ unsafe {
+ core::arch::asm!("mov {}, ebp", out(reg) frame, options(nomem, nostack, preserves_flags));
+ }
+
+ #[cfg(target_arch = "aarch64")]
+ unsafe {
+ core::arch::asm!("mov {}, x29", out(reg) frame, options(nomem, nostack, preserves_flags));
+ }
+
+ frame
+}
+
+#[cfg(any(target_arch = "x86_64", target_arch = "x86", target_arch = "aarch64"))]
+fn read_backtrace_frame(frame: *const usize) -> Option<(*const usize, usize)> {
+ let align = core::mem::align_of::<usize>();
+ let frame_addr = frame as usize;
+
+ if frame.is_null() || frame_addr % align != 0 {
+ return None;
+ }
+
+ let next_frame = unsafe { frame.read() } as *const usize;
+ let return_address = unsafe { frame.add(1).read() };
+
+ if return_address == 0 {
+ return None;
+ }
+
+ Some((next_frame, return_address))
+}
+
+#[cfg(any(target_arch = "x86_64", target_arch = "x86", target_arch = "aarch64"))]
+fn is_sane_next_backtrace_frame(current: *const usize, next: *const usize) -> bool {
+ let align = core::mem::align_of::<usize>();
+ let current_addr = current as usize;
+ let next_addr = next as usize;
+
+ !next.is_null()
+ && next_addr % align == 0
+ && next_addr > current_addr
+ && next_addr - current_addr <= MAX_FATAL_FRAME_STRIDE
+}
+
+#[inline(never)]
+fn write_best_effort_backtrace(w: &mut platform::FileWriter) {
+ use core::fmt::Write;
+
+ let _ = w.write_str("RELIBC: attempting best-effort backtrace\n");
+
+ #[cfg(any(target_arch = "x86_64", target_arch = "x86", target_arch = "aarch64"))]
+ {
+ let mut frame = current_frame_pointer();
+ let mut wrote_frame = false;
+
+ for frame_index in 0..MAX_FATAL_BACKTRACE_FRAMES {
+ let Some((next_frame, return_address)) = read_backtrace_frame(frame) else {
+ break;
+ };
+
+ wrote_frame = true;
+ let _ = w.write_fmt(format_args!(
+ "RELIBC BACKTRACE[{frame_index:02}]: {:#x}\n",
+ return_address,
+ ));
+
+ if !is_sane_next_backtrace_frame(frame, next_frame) {
+ break;
+ }
+
+ frame = next_frame;
+ }
+
+ if !wrote_frame {
+ let _ = w.write_str("RELIBC: backtrace attempt produced no frames\n");
+ }
+ }
+
+ #[cfg(not(any(target_arch = "x86_64", target_arch = "x86", target_arch = "aarch64")))]
+ {
+ let _ = w.write_str("RELIBC: backtrace unavailable on this architecture\n");
+ }
+}
#[unsafe(no_mangle)]
pub extern "C" fn relibc_panic(pi: &::core::panic::PanicInfo) -> ! {
use core::fmt::Write;
let mut w = platform::FileWriter::new(2);
- let _ = w.write_fmt(format_args!("RELIBC PANIC: {}\n", pi));
+
+ if let Some(location) = pi.location() {
+ let _ = w.write_fmt(format_args!(
+ "RELIBC PANIC LOCATION: {}:{}:{}\n",
+ location.file(),
+ location.line(),
+ location.column(),
+ ));
+ } else {
+ let _ = w.write_str("RELIBC PANIC LOCATION: <unavailable>\n");
+ }
+
+ write_process_thread_identity(&mut w);
+ let _ = w.write_fmt(format_args!("RELIBC PANIC: {}\n", pi));
core::intrinsics::abort();
}
@@ -95,23 +235,27 @@ pub extern "C" fn rust_oom(layout: ::core::alloc::Layout) -> ! {
let mut w = platform::FileWriter::new(2);
let _ = w.write_fmt(format_args!(
- "RELIBC OOM: {} bytes aligned to {} bytes\n",
+ "RELIBC OOM: {} bytes aligned to {} bytes - process will abort\n",
layout.size(),
layout.align()
));
+ write_process_thread_identity(&mut w);
+ write_best_effort_backtrace(&mut w);
core::intrinsics::abort();
}
#[cfg(not(test))]
#[allow(non_snake_case)]
#[linkage = "weak"]
#[unsafe(no_mangle)]
pub extern "C" fn _Unwind_Resume() -> ! {
use core::fmt::Write;
let mut w = platform::FileWriter::new(2);
- let _ = w.write_str("_Unwind_Resume\n");
+ let _ = w.write_str(
+ "RELIBC: _Unwind_Resume called - exception propagation failed, aborting\n",
+ );
+ write_process_thread_identity(&mut w);
core::intrinsics::abort();
}
@@ -0,0 +1,87 @@
Fix ENOTRECOVERABLE returned for non-robust mutexes and register main
thread in OS_TID_TO_PTHREAD.
The robust mutex liveness check (mutex_owner_id_is_live) was returning
ENOTRECOVERABLE for non-robust mutexes when the owner appeared dead.
Per POSIX, the behaviour of a non-robust mutex whose owner has died is
undefined; returning an error crashes every Rust std::sync::Mutex user.
For lock_inner, fall through to spin/futex-wait instead. For try_lock,
return EBUSY instead.
Additionally, pthread::init() never registered the main thread in
OS_TID_TO_PTHREAD, so any mutex owned by the main thread would always
appear to have a dead owner, making the liveness check unreliable.
diff --git a/src/pthread/mod.rs b/src/pthread/mod.rs
index 8243a48..c455a67 100644
--- a/src/pthread/mod.rs
+++ b/src/pthread/mod.rs
@@ -43,9 +43,13 @@ pub unsafe fn init() {
thread.stack_size = STACK_SIZE;
}
- unsafe { Tcb::current() }
- .expect_notls("no TCB present for main thread")
- .pthread = thread;
+ let tcb = unsafe { Tcb::current() }
+ .expect_notls("no TCB present for main thread");
+ tcb.pthread = thread;
+
+ OS_TID_TO_PTHREAD
+ .lock()
+ .insert(Sys::current_os_tid(), ForceSendSync(tcb as *const Tcb as *mut Tcb));
}
//static NEXT_INDEX: AtomicU32 = AtomicU32::new(FIRST_THREAD_IDX + 1);
diff --git a/src/sync/pthread_mutex.rs b/src/sync/pthread_mutex.rs
index af0c429..1b2b3ca 100644
--- a/src/sync/pthread_mutex.rs
+++ b/src/sync/pthread_mutex.rs
@@ -136,14 +136,17 @@ impl RlctMutex {
Err(thread) => {
let owner = thread & INDEX_MASK;
- if !crate::pthread::mutex_owner_id_is_live(owner) {
- if !self.robust {
- return Err(Errno(ENOTRECOVERABLE));
- }
-
+ if !crate::pthread::mutex_owner_id_is_live(owner) && self.robust {
let new_value = (thread & WAITING_BIT) | FUTEX_OWNER_DIED | this_thread;
match self.inner.compare_exchange(
thread,
@@ -152,6 +155,12 @@ impl RlctMutex {
Ok(_) => return self.finish_lock_acquire(true),
Err(_) => continue,
}
+ } else if !crate::pthread::mutex_owner_id_is_live(owner) {
+ // Non-robust mutex with apparently-dead owner: per POSIX the
+ // behaviour is undefined. We conservatively keep spinning /
+ // futex-waiting rather than returning ENOTRECOVERABLE, which
+ // would crash any Rust std::sync::Mutex user.
}
if spins_left > 0 {
@@ -241,14 +250,17 @@ impl RlctMutex {
if current & FUTEX_OWNER_DIED != 0 || (owner != 0 && !crate::pthread::mutex_owner_id_is_live(owner)) {
- if !self.robust {
- return Err(Errno(ENOTRECOVERABLE));
- }
-
+ if self.robust {
let new_value = (current & WAITING_BIT) | FUTEX_OWNER_DIED | this_thread;
match self.inner.compare_exchange(
current,
@@ -257,6 +269,11 @@ impl RlctMutex {
Ok(_) => return self.finish_lock_acquire(true),
Err(_) => continue,
}
+ } else {
+ // Non-robust mutex: owner appears dead but POSIX behaviour is
+ // undefined; report busy rather than ENOTRECOVERABLE.
+ return Err(Errno(EBUSY));
+ }
}
return Err(Errno(EBUSY));
@@ -0,0 +1,112 @@
diff --git a/redox-rt/src/signal.rs b/redox-rt/src/signal.rs
index 022f873..ab96dea 100644
--- a/redox-rt/src/signal.rs
+++ b/redox-rt/src/signal.rs
@@ -1,4 +1,10 @@
-use core::{ffi::c_int, ptr::NonNull, sync::atomic::Ordering};
+use core::{
+ ffi::c_int,
+ hint::unreachable_unchecked,
+ panic::AssertUnwindSafe,
+ ptr::NonNull,
+ sync::atomic::Ordering,
+};
use syscall::{
CallFlags, EAGAIN, EINTR, EINVAL, ENOMEM, EPERM, Error, RawAction, Result, SenderInfo,
@@ -103,6 +109,47 @@ pub struct SiginfoAbi {
pub si_value: usize, // sigval
}
+fn invoke_signal_handler<F: FnOnce()>(f: AssertUnwindSafe<F>) -> bool {
+ fn do_call<F: FnOnce()>(data: *mut u8) {
+ let callback = unsafe { &mut *data.cast::<Option<AssertUnwindSafe<F>>>() };
+ if let Some(callback) = callback.take() {
+ callback.0();
+ }
+ }
+
+ fn do_catch<F: FnOnce()>(_data: *mut u8, _payload: *mut u8) {}
+
+ let mut callback = Some(f);
+ unsafe {
+ core::intrinsics::catch_unwind(
+ do_call::<F>,
+ (&mut callback as *mut Option<AssertUnwindSafe<F>>).cast(),
+ do_catch::<F>,
+ ) != 0
+ }
+}
+
+#[inline(always)]
+unsafe fn return_ignored_signal(
+ os: &RtTcb,
+ stack: &SigStack,
+ signals_were_disabled: bool,
+) {
+ unsafe {
+ (*os.arch.get()).last_sig_was_restart = true;
+ (*os.arch.get()).last_sigstack = NonNull::new(stack.link);
+ }
+
+ if !signals_were_disabled {
+ core::sync::atomic::compiler_fence(Ordering::Release);
+ let control_flags = &os.control.control_flags;
+ control_flags.store(
+ control_flags.load(Ordering::Relaxed) & !SigcontrolFlags::INHIBIT_DELIVERY.bits(),
+ Ordering::Relaxed,
+ );
+ }
+}
+
#[inline(always)]
unsafe fn inner(stack: &mut SigStack) {
let os = unsafe { &Tcb::current().unwrap().os_specific };
@@ -168,7 +215,10 @@ unsafe fn inner(stack: &mut SigStack) {
// and reaching this code. If so, we do already know whether the signal is IGNORED *now*,
// and so we should return early ideally without even temporarily touching the signal mask.
SigactionKind::Ignore => {
- panic!("ctl {:#x?} signal {}", os.control, stack.sig_num)
+ unsafe {
+ return_ignored_signal(os, stack, signals_were_disabled);
+ }
+ return;
}
// this case should be treated equally as the one above
//
@@ -183,7 +233,9 @@ unsafe fn inner(stack: &mut SigStack) {
CallFlags::empty(),
&[ProcCall::Exit as u64, u64::from(sig) << 8],
);
- panic!()
+ // SAFETY: ProcCall::Exit terminates the current process when it succeeds, so reaching
+ // this point would violate the proc manager exit contract.
+ unsafe { unreachable_unchecked() }
}
SigactionKind::Handled { handler } => handler,
};
@@ -224,15 +276,21 @@ unsafe fn inner(stack: &mut SigStack) {
si_uid: sender_uid as i32,
si_value: stack.sival,
};
- unsafe {
+ if invoke_signal_handler(AssertUnwindSafe(|| unsafe {
sigaction(
stack.sig_num as c_int,
core::ptr::addr_of!(info).cast(),
stack as *mut SigStack as *mut (),
)
- };
+ })) {
+ let _ = syscall::write(2, b"redox-rt: sa_siginfo handler panicked; continuing\n");
+ }
} else if let Some(handler) = unsafe { handler.handler } {
- handler(stack.sig_num as c_int);
+ if invoke_signal_handler(AssertUnwindSafe(|| {
+ handler(stack.sig_num as c_int);
+ })) {
+ let _ = syscall::write(2, b"redox-rt: sa_handler panicked; continuing\n");
+ }
}
// Disable signals while we modify the sigmask again
@@ -0,0 +1,101 @@
diff --git a/src/start.rs b/src/start.rs
--- a/src/start.rs
+++ b/src/start.rs
@@ -1,8 +1,6 @@
//! Startup code.
use alloc::{boxed::Box, vec::Vec};
-use core::{intrinsics, ptr};
-
-#[cfg(target_os = "redox")]
-use generic_rt::ExpectTlsFree;
+use core::{fmt::Write, intrinsics, panic::AssertUnwindSafe, ptr};
use crate::{
ALLOCATOR,
@@ -143,6 +141,28 @@ fn io_init() {
stdio::stderr = stdio::default_stderr().get();
}
}
+
+fn catch_unwind<F: FnOnce()>(f: AssertUnwindSafe<F>) -> Result<(), ()> {
+ fn do_call<F: FnOnce()>(data: *mut u8) {
+ let callback = unsafe { &mut *data.cast::<Option<AssertUnwindSafe<F>>>() };
+ if let Some(callback) = callback.take() {
+ callback.0();
+ }
+ }
+
+ fn do_catch<F: FnOnce()>(_data: *mut u8, _payload: *mut u8) {}
+
+ let mut callback = Some(f);
+ let panicked = unsafe {
+ intrinsics::catch_unwind(
+ do_call::<F>,
+ (&mut callback as *mut Option<AssertUnwindSafe<F>>).cast(),
+ do_catch::<F>,
+ ) != 0
+ };
+
+ if panicked { Err(()) } else { Ok(()) }
+}
+
#[cold]
fn abort_startup(args: core::fmt::Arguments<'_>) -> ! {
let mut w = platform::FileWriter::new(2);
@@ -164,15 +184,24 @@ pub unsafe extern "C" fn relibc_start_v1(
unsafe { relibc_verify_host() };
#[cfg(target_os = "redox")]
- let thr_fd = redox_rt::proc::FdGuard::new(
- unsafe {
- crate::platform::get_auxv_raw(sp.auxv().cast(), redox_rt::auxv_defs::AT_REDOX_THR_FD)
- }
- .expect_notls("no thread fd present"),
- )
- .to_upper()
- .expect_notls("failed to move thread fd to upper table");
+ let thr_fd = {
+ let thr_fd = match unsafe {
+ crate::platform::get_auxv_raw(sp.auxv().cast(), redox_rt::auxv_defs::AT_REDOX_THR_FD)
+ } {
+ Some(thr_fd) => thr_fd,
+ None => abort_startup(format_args!(
+ "relibc_start_v1: missing AT_REDOX_THR_FD auxv entry; no thread fd present\n"
+ )),
+ };
+
+ match redox_rt::proc::FdGuard::new(thr_fd).to_upper() {
+ Ok(thr_fd) => thr_fd,
+ Err(err) => abort_startup(format_args!(
+ "relibc_start_v1: failed to move thread fd to upper table: {err:?}\n"
+ )),
+ }
+ };
// Initialize TLS, if necessary
unsafe {
@@ -237,7 +266,10 @@ pub unsafe extern "C" fn relibc_start_v1(
let mut f = unsafe { &__preinit_array_start } as *const _;
#[allow(clippy::op_ref)]
while f < &raw const __preinit_array_end {
- (unsafe { *f })();
+ let func = unsafe { *f };
+ if catch_unwind(AssertUnwindSafe(|| unsafe { (*f)() })).is_err() {
+ log_initializer_panic(".preinit_array", func);
+ }
f = unsafe { f.offset(1) };
}
}
@@ -247,7 +279,10 @@ pub unsafe extern "C" fn relibc_start_v1(
let mut f = unsafe { &__init_array_start } as *const _;
#[allow(clippy::op_ref)]
while f < &raw const __init_array_end {
- (unsafe { *f })();
+ let func = unsafe { *f };
+ if catch_unwind(AssertUnwindSafe(|| unsafe { (*f)() })).is_err() {
+ log_initializer_panic(".init_array", func);
+ }
f = unsafe { f.offset(1) };
}
}
+104
View File
@@ -21,3 +21,107 @@ diff --git a/src/header/fcntl/mod.rs b/src/header/fcntl/mod.rs
+ }
+ return new_fd;
+ }
diff --git a/src/pthread/mod.rs b/src/pthread/mod.rs
--- a/src/pthread/mod.rs
+++ b/src/pthread/mod.rs
@@ -2,6 +2,7 @@
use core::{
cell::UnsafeCell,
+ panic::AssertUnwindSafe,
ptr,
sync::atomic::{AtomicBool, AtomicUsize, Ordering},
};
@@ -208,13 +209,41 @@ pub(crate) unsafe fn create(
}
/// A shim to wrap thread entry points in logic to set up TLS, for example
+fn catch_unwind<F: FnOnce()>(f: AssertUnwindSafe<F>) -> Result<(), ()> {
+ fn do_call<F: FnOnce()>(data: *mut u8) {
+ let callback = unsafe { &mut *data.cast::<Option<AssertUnwindSafe<F>>>() };
+ if let Some(callback) = callback.take() {
+ callback.0();
+ }
+ }
+
+ fn do_catch<F: FnOnce()>(_data: *mut u8, _payload: *mut u8) {}
+
+ let mut callback = Some(f);
+ let panicked = unsafe {
+ core::intrinsics::catch_unwind(
+ do_call::<F>,
+ (&mut callback as *mut Option<AssertUnwindSafe<F>>).cast(),
+ do_catch::<F>,
+ ) != 0
+ };
+
+ if panicked { Err(()) } else { Ok(()) }
+}
+
unsafe extern "C" fn new_thread_shim(
tcb: *mut Tcb,
synchronization_mutex: *const Mutex<u64>,
) -> ! {
- let tcb = unsafe { tcb.as_mut() }.expect_notls("non-null TLS is required");
+ let tcb = match unsafe { tcb.as_mut() } {
+ Some(tcb) => tcb,
+ None => {
+ log::error!("pthread: child thread started without a TCB");
+ unsafe { exit_current_thread(Retval(ptr::null_mut())) }
+ }
+ };
#[cfg(not(target_os = "redox"))]
{
@@ -227,12 +256,23 @@ unsafe extern "C" fn new_thread_shim(
unsafe {
tcb.activate(None);
}
- redox_rt::signal::setup_sighandler(&tcb.os_specific, false);
+ match catch_unwind(AssertUnwindSafe(|| {
+ redox_rt::signal::setup_sighandler(&tcb.os_specific, false)
+ })) {
+ Ok(()) => {}
+ Err(()) => {
+ log::error!("pthread: failed to set up child thread signal handler");
+ unsafe { exit_current_thread(Retval(ptr::null_mut())) }
+ }
+ }
}
let procmask = unsafe { (&*synchronization_mutex).as_ptr().read() };
- unsafe { tcb.copy_masters() }.unwrap();
+ if let Err(err) = unsafe { tcb.copy_masters() } {
+ log::error!("pthread: failed to copy TLS masters for child thread: {err:?}");
+ unsafe { exit_current_thread(Retval(ptr::null_mut())) }
+ }
unsafe { (*tcb).pthread.os_tid.get().write(Sys::current_os_tid()) };
@@ -240,11 +280,21 @@ unsafe extern "C" fn new_thread_shim(
#[cfg(target_os = "redox")]
{
- redox_rt::signal::set_sigmask(Some(procmask), None)
- .expect("failed to set procmask in child thread");
+ if let Err(err) = redox_rt::signal::set_sigmask(Some(procmask), None) {
+ log::error!("pthread: failed to set child thread signal mask: {err:?}");
+ }
}
- let retval = unsafe { entry_point(arg) };
+ let mut retval = ptr::null_mut();
+ match catch_unwind(AssertUnwindSafe(|| {
+ retval = unsafe { entry_point(arg) };
+ })) {
+ Ok(()) => {}
+ Err(()) => {
+ log::error!("pthread: child thread entry point panicked");
+ unsafe { exit_current_thread(Retval(ptr::null_mut())) }
+ }
+ }
unsafe { exit_current_thread(Retval(retval)) }
}