diff --git a/src/header/semaphore/mod.rs b/src/header/semaphore/mod.rs index 623a4ce399..a334c7e074 100644 --- a/src/header/semaphore/mod.rs +++ b/src/header/semaphore/mod.rs @@ -144,8 +144,7 @@ pub unsafe extern "C" fn sem_init(sem: *mut sem_t, _pshared: c_int, value: c_uin #[unsafe(no_mangle)] pub unsafe extern "C" fn sem_open( name: *const c_char, - oflag: c_int, - mut __valist: ... + oflag: c_int, /* (va_list) mode: mode_t, value: c_uint */ ) -> *mut sem_t { if name.is_null() { ERRNO.set(EINVAL); return SEM_FAILED_PTR; } let name_bytes = unsafe { CStr::from_ptr(name) }.to_bytes().to_vec(); @@ -157,10 +156,11 @@ pub unsafe extern "C" fn sem_open( with_named_sems(|map| { if let Some(e) = map.get(&name_bytes) { e.refs.fetch_add(1, Ordering::Relaxed); } }); return ptr as *mut NamedSemaphore as *mut sem_t; } - let (mode, value): (mode_t, c_uint) = if create { - (unsafe { __valist.arg::() }, unsafe { __valist.arg::() }) - } else { - (0, 0) + let (mode, value): (mode_t, c_uint) = { + let oflag_ptr: *const c_int = &oflag; + let mode_ptr = unsafe { oflag_ptr.add(1) as *const mode_t }; + let value_ptr = unsafe { oflag_ptr.add(2) as *const c_uint }; + (unsafe { *mode_ptr }, if create { unsafe { *value_ptr } } else { 0 }) }; let ptr = unsafe { map_named_semaphore(name, oflag, mode, value) }; if ptr == SEM_FAILED_PTR { return SEM_FAILED_PTR; } diff --git a/src/ld_so/tcb.rs b/src/ld_so/tcb.rs index 2f90ad8cb6..2d872d43da 100644 --- a/src/ld_so/tcb.rs +++ b/src/ld_so/tcb.rs @@ -107,6 +107,7 @@ impl Tcb { stack_base: core::ptr::null_mut(), stack_size: 0, os_tid: UnsafeCell::new(OsTid::default()), + robust_list_head: UnsafeCell::new(core::ptr::null_mut()), }, dtv_ptr: ptr::null_mut(), diff --git a/src/pthread/mod.rs b/src/pthread/mod.rs index 8243a48079..0cd5d1d088 100644 --- a/src/pthread/mod.rs +++ b/src/pthread/mod.rs @@ -33,6 +33,8 @@ pub unsafe fn init() { stack_size: 0, os_tid: UnsafeCell::new(Sys::current_os_tid()), + + robust_list_head: UnsafeCell::new(core::ptr::null_mut()), }; #[cfg(target_os = "redox")] @@ -60,6 +62,7 @@ pub unsafe fn terminate_from_main_thread() { bitflags::bitflags! { pub struct PthreadFlags: usize { const DETACHED = 1; + const FINISHED = 2; } } @@ -74,6 +77,14 @@ pub struct Pthread { pub(crate) stack_size: usize, pub os_tid: UnsafeCell, + + /// Head of the per-thread robust mutex list, used by the kernel + /// during thread exit to mark any held robust mutexes with + /// `FUTEX_OWNER_DIED` so a future `pthread_mutex_lock` on a + /// dead-owner mutex can recover via `EOWNERDEAD`. Maintained as + /// an `UnsafeCell<*mut RobustMutexNode>` because the kernel walks + /// it during thread exit. `null` = empty list. + pub(crate) robust_list_head: UnsafeCell<*mut crate::sync::pthread_mutex::RobustMutexNode>, } #[derive(Clone, Copy, Debug, Default, Ord, Eq, PartialOrd, PartialEq)] @@ -306,6 +317,9 @@ pub unsafe fn exit_current_thread(retval: Retval) -> ! { // deallocated. unsafe { dealloc_thread(this) }; } else { + // Mark the thread as finished so that pthread_kill() can return ESRCH + // instead of delivering a signal to a thread that has already exited. + this.flags.fetch_or(PthreadFlags::FINISHED.bits(), Ordering::AcqRel); // When joinable, the return value should be made available to other threads. unsafe { this.waitval.post(retval) }; } @@ -438,3 +452,60 @@ impl Pshared { } } } + +/// Return `true` if the given mutex owner ID refers to a thread that +/// is still alive in this process. Used by `pthread_mutex_lock` to +/// detect robust-mutex dead-owner recovery (a thread exited while +/// holding a robust mutex; the kernel marked it with `FUTEX_OWNER_DIED` +/// and a future lock can recover via `EOWNERDEAD`). +/// +/// On Redox, the owner ID is the kernel's `thread_fd` for the +/// thread. The kernel's `proc:` scheme lets us query whether a thread +/// handle is still alive by attempting an `open` on its name handle +/// (EOPNOTSUPP / EBADF / ENOENT -> dead). +/// +/// On Linux, the owner ID is the `tid` (kernel task ID). A pthread +/// has not exited when its `tid` is in the live-process set; we +/// approximate this by checking our own `OS_TID_TO_PTHREAD` map, +/// which is populated at thread creation and removed at thread exit. +/// +/// The fallback `false` is safe: a `false` return causes the lock +/// path to fall back to `EOWNERDEAD` recovery (treating the lock as +/// orphaned), which is the POSIX-allowed behavior for robust mutexes. +pub fn mutex_owner_id_is_live(owner: u32) -> bool { + if owner == 0 { + return false; + } + #[cfg(target_os = "redox")] + { + // For Redox, attempt to open the thread's "name" handle via + // the proc scheme. If the thread is alive, the open succeeds; + // if it has exited and been reaped, the open returns ENOENT. + let path = format!("proc:{}/name", owner); + let cstr = match crate::c_str::CStr::from_bytes_with_nul(path.as_bytes()) { + Ok(c) => c, + Err(_) => return false, + }; + let fd = crate::platform::Sys::open(cstr.as_ptr(), crate::header::fcntl::O_RDONLY, 0); + if fd >= 0 { + let _ = crate::platform::Sys::close(fd); + true + } else { + false + } + } + #[cfg(target_os = "linux")] + { + // For Linux, the OS_TID_TO_PTHREAD map is maintained at + // thread creation (insert) and exit (remove). If owner is + // in the map, the thread is alive. We acquire the map lock + // briefly to avoid a TOCTOU race. + crate::pthread::OS_TID_TO_PTHREAD + .lock() + .iter() + .any(|(_os_tid, pthread)| { + let tid = unsafe { (*pthread).os_tid.get().read() }; + tid.thread_id as u32 == owner + }) + } +}