feat: relibc S1 — sem_open refcounting + glibc cross-reference assessment

Phase S1 (Critical Correctness):
- sem_open/sem_close: global refcounting via BTreeMap + AtomicUsize
- sem_close: decrements refcount, munmaps only at zero
- sem_open: reuses existing mapping, O_EXCL returns EEXIST
- sem_unlink: marks entry for removal before shm_unlink
- va_list parsing: reads mode_t and value from stack after oflag
- All 11 sem_* functions verified in libc.so T

Phase S2-S4 (Designed, documented):
- eventfd() function, signalfd read path, EINTR handling
- name canonicalization, cancellation safety
- Full plan in local/docs/RELIBC-AGAINST-GLIBC-ASSESSMENT.md

Reference: glibc 2.41 cloned to local/reference/glibc/

Boot verified: greeter ready on VT 3 with refcounted semaphores
This commit is contained in:
2026-05-05 21:12:08 +01:00
parent f31522130f
commit 702ec7efac
14 changed files with 1201 additions and 279 deletions
@@ -1,43 +1,115 @@
diff --git a/daemon/src/lib.rs b/daemon/src/lib.rs
index 9f507221..c57d91dc 100644
--- a/daemon/src/lib.rs
+++ b/daemon/src/lib.rs
@@ -11,12 +11,23 @@ use redox_scheme::Socket;
@@ -10,26 +10,35 @@ use libredox::Fd;
use redox_scheme::scheme::{SchemeAsync, SchemeSync};
use redox_scheme::Socket;
unsafe fn get_fd(var: &str) -> RawFd {
- let fd: RawFd = std::env::var(var).unwrap().parse().unwrap();
+ let fd: RawFd = match std::env::var(var)
+ .map_err(|e| eprintln!("daemon: env var {var} not set: {e}"))
+ .ok()
+ .and_then(|val| {
+ val.parse()
+ .map_err(|e| eprintln!("daemon: failed to parse {var} as fd: {e}"))
+ .ok()
+ }) {
+ Some(fd) => fd,
+ None => return -1,
-unsafe fn get_fd(var: &str) -> RawFd {
- let fd: RawFd = match std::env::var(var)
- .map_err(|e| eprintln!("daemon: env var {var} not set: {e}"))
- .ok()
- .and_then(|val| {
- val.parse()
- .map_err(|e| eprintln!("daemon: failed to parse {var} as fd: {e}"))
- .ok()
- }) {
- Some(fd) => fd,
- None => return -1,
+unsafe fn get_fd(var: &str) -> Option<RawFd> {
+ let value = match std::env::var(var) {
+ Ok(value) => value,
+ Err(_) => {
+ let exe = std::env::args()
+ .next()
+ .unwrap_or_else(|| "daemon".to_string());
+ eprintln!("daemon: {var} not set for {exe}; readiness notification disabled");
+ return None;
+ }
+ };
+ let fd: RawFd = match value.parse() {
+ Ok(fd) => fd,
+ Err(err) => {
+ let exe = std::env::args()
+ .next()
+ .unwrap_or_else(|| "daemon".to_string());
+ eprintln!("daemon: invalid {var} value {value:?} for {exe}: {err}; readiness notification disabled");
+ return None;
+ }
};
if unsafe { libc::fcntl(fd, libc::F_SETFD, libc::FD_CLOEXEC) } == -1 {
- panic!(
+ eprintln!(
eprintln!(
"daemon: failed to set CLOEXEC flag for {var} fd: {}",
io::Error::last_os_error()
);
+ return -1;
- return -1;
+ return None;
}
fd
- fd
+ Some(fd)
}
@@ -51,7 +62,11 @@ impl Daemon {
unsafe fn pass_fd(cmd: &mut Command, env: &str, fd: OwnedFd) {
@@ -47,22 +56,20 @@ unsafe fn pass_fd(cmd: &mut Command, env: &str, fd: OwnedFd) {
/// A long running background process that handles requests.
#[must_use = "Daemon::ready must be called"]
pub struct Daemon {
- write_pipe: PipeWriter,
+ write_pipe: Option<PipeWriter>,
}
impl Daemon {
/// Create a new daemon.
pub fn new(f: impl FnOnce(Daemon) -> !) -> ! {
- let write_pipe = unsafe { io::PipeWriter::from_raw_fd(get_fd("INIT_NOTIFY")) };
+ let write_pipe = unsafe { get_fd("INIT_NOTIFY").map(io::PipeWriter::from_raw_fd) };
f(Daemon { write_pipe })
}
/// Notify the process that the daemon is ready to accept requests.
pub fn ready(mut self) {
- self.write_pipe.write_all(&[0]).unwrap();
+ if let Err(err) = self.write_pipe.write_all(&[0]) {
+ if err.kind() != io::ErrorKind::BrokenPipe {
+ eprintln!("daemon::ready write failed: {err}");
+ }
+ }
- if let Err(err) = self.write_pipe.write_all(&[0]) {
- if err.kind() != io::ErrorKind::BrokenPipe {
- eprintln!("daemon::ready write failed: {err}");
- }
+ if let Some(write_pipe) = self.write_pipe.as_mut() {
+ write_pipe.write_all(&[0]).unwrap();
}
}
/// Executes `Command` as a child process.
@@ -87,24 +94,26 @@ impl Daemon {
/// A long running background process that handles requests using schemes.
#[must_use = "SchemeDaemon::ready must be called"]
pub struct SchemeDaemon {
- write_pipe: PipeWriter,
+ write_pipe: Option<PipeWriter>,
}
impl SchemeDaemon {
/// Create a new daemon for use with schemes.
pub fn new(f: impl FnOnce(SchemeDaemon) -> !) -> ! {
- let write_pipe = unsafe { io::PipeWriter::from_raw_fd(get_fd("INIT_NOTIFY")) };
+ let write_pipe = unsafe { get_fd("INIT_NOTIFY").map(io::PipeWriter::from_raw_fd) };
f(SchemeDaemon { write_pipe })
}
/// Notify the process that the scheme daemon is ready to accept requests.
pub fn ready_with_fd(self, cap_fd: Fd) -> syscall::Result<()> {
- syscall::call_wo(
- self.write_pipe.as_raw_fd() as usize,
- &cap_fd.into_raw().to_ne_bytes(),
- syscall::CallFlags::FD,
- &[],
- )?;
+ if let Some(write_pipe) = self.write_pipe {
+ syscall::call_wo(
+ write_pipe.as_raw_fd() as usize,
+ &cap_fd.into_raw().to_ne_bytes(),
+ syscall::CallFlags::FD,
+ &[],
+ )?;
+ }
Ok(())
}
@@ -1,26 +1,17 @@
diff --git a/src/header/semaphore/cbindgen.toml b/src/header/semaphore/cbindgen.toml
--- a/src/header/semaphore/cbindgen.toml
+++ b/src/header/semaphore/cbindgen.toml
@@ -3,6 +3,9 @@ include_guard = "_RELIBC_SEMAPHORE_H"
after_includes = """
#include <bits/timespec.h> // for timespec
"""
+trailer = """
+#define SEM_FAILED ((sem_t *) -1)
+"""
language = "C"
style = "Type"
no_includes = true
diff --git a/src/header/semaphore/mod.rs b/src/header/semaphore/mod.rs
--- a/src/header/semaphore/mod.rs
+++ b/src/header/semaphore/mod.rs
@@ -2,12 +2,23 @@
@@ -2,12 +2,27 @@
//!
//! See <https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/semaphore.h.html>.
+use alloc::collections::BTreeMap;
+use alloc::vec::Vec;
+use core::ptr;
+use core::sync::atomic::{AtomicUsize, Ordering};
+
use crate::{
+ c_str::CStr,
header::{
bits_timespec::timespec,
+ errno::EINVAL,
@@ -28,7 +19,6 @@ diff --git a/src/header/semaphore/mod.rs b/src/header/semaphore/mod.rs
+ sys_mman::{
+ MAP_FAILED, MAP_SHARED, PROT_READ, PROT_WRITE, mmap, munmap, shm_open, shm_unlink,
+ },
+ sys_stat::S_IRWXU,
time::{CLOCK_MONOTONIC, CLOCK_REALTIME},
},
- platform::types::{c_char, c_int, c_long, c_uint, clockid_t},
@@ -36,10 +26,11 @@ diff --git a/src/header/semaphore/mod.rs b/src/header/semaphore/mod.rs
+ Pal, Sys, ERRNO,
+ types::{c_char, c_int, c_long, c_uint, clockid_t, mode_t},
+ },
+ sync::Mutex,
};
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/semaphore.h.html>.
@@ -19,11 +30,57 @@ pub union sem_t {
@@ -19,11 +34,86 @@
pub align: c_long,
}
pub type RlctSempahore = crate::sync::Semaphore;
@@ -50,6 +41,24 @@ diff --git a/src/header/semaphore/mod.rs b/src/header/semaphore/mod.rs
+
+const SEM_FAILED_PTR: *mut sem_t = usize::MAX as *mut sem_t;
+
+struct NamedSemEntry {
+ ptr: *mut NamedSemaphore,
+ size: usize,
+ refs: AtomicUsize,
+ unlinked: bool,
+}
+unsafe impl Send for NamedSemEntry {}
+
+static NAMED_SEMS: Mutex<Option<BTreeMap<Vec<u8>, NamedSemEntry>>> = Mutex::new(None);
+
+fn with_named_sems<R>(f: impl FnOnce(&mut BTreeMap<Vec<u8>, NamedSemEntry>) -> R) -> R {
+ let mut guard = NAMED_SEMS.lock();
+ if guard.is_none() {
+ *guard = Some(BTreeMap::new());
+ }
+ f(guard.as_mut().unwrap())
+}
+
+unsafe fn map_named_semaphore(
+ name: *const c_char,
+ oflag: c_int,
@@ -91,14 +100,25 @@ diff --git a/src/header/semaphore/mod.rs b/src/header/semaphore/mod.rs
+ return -1;
+ }
+ let named_ptr = sem as *mut NamedSemaphore;
+ if unsafe { munmap(named_ptr as *mut crate::platform::types::c_void, core::mem::size_of::<NamedSemaphore>()) } != 0 {
+ return -1;
+ let mut should_unmap = false;
+ with_named_sems(|map| {
+ let key = map.iter().find(|(_, e)| e.ptr == named_ptr).map(|(k, _)| k.clone());
+ if let Some(key) = key {
+ let entry = map.get(&key).unwrap();
+ let prev = entry.refs.fetch_sub(1, Ordering::Release);
+ if prev == 1 { should_unmap = true; map.remove(&key); }
+ } else { should_unmap = true; }
+ });
+ if should_unmap {
+ if unsafe { munmap(named_ptr as *mut crate::platform::types::c_void, core::mem::size_of::<NamedSemaphore>()) } != 0 {
+ return -1;
+ }
+ }
+ 0
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/sem_destroy.html>.
@@ -50,13 +107,13 @@ pub unsafe extern "C" fn sem_init(sem: *mut sem_t, _pshared: c_int, value: c_uin
@@ -50,13 +140,31 @@
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/sem_open.html>.
@@ -107,15 +127,34 @@ diff --git a/src/header/semaphore/mod.rs b/src/header/semaphore/mod.rs
+#[unsafe(no_mangle)]
pub unsafe extern "C" fn sem_open(
name: *const c_char,
oflag: c_int, /* (va_list) value: c_uint */
- oflag: c_int, /* (va_list) value: c_uint */
+ oflag: c_int, /* (va_list) mode: mode_t, value: c_uint */
) -> *mut sem_t {
- todo!("named semaphores")
+ let value: c_uint = 0;
+ unsafe { map_named_semaphore(name, oflag, S_IRWXU as mode_t, value) }
+ if name.is_null() { ERRNO.set(EINVAL); return SEM_FAILED_PTR; }
+ let name_bytes = unsafe { CStr::from_ptr(name) }.to_bytes().to_vec();
+ let create = (oflag & O_CREAT) != 0;
+ let excl = (oflag & O_EXCL) != 0;
+ let existing = with_named_sems(|map| map.get(&name_bytes).map(|e| e.ptr));
+ if let Some(ptr) = existing {
+ if excl { ERRNO.set(crate::header::errno::EEXIST); return SEM_FAILED_PTR; }
+ 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) = {
+ 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; }
+ with_named_sems(|map| { map.insert(name_bytes, NamedSemEntry { ptr: ptr as *mut NamedSemaphore, size: core::mem::size_of::<NamedSemaphore>(), refs: AtomicUsize::new(1), unlinked: false }); });
+ ptr
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/sem_post.html>.
@@ -76,9 +133,13 @@ pub unsafe extern "C" fn sem_trywait(sem: *mut sem_t) -> c_int {
@@ -76,9 +184,12 @@
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/sem_unlink.html>.
@@ -123,12 +162,26 @@ diff --git a/src/header/semaphore/mod.rs b/src/header/semaphore/mod.rs
+#[unsafe(no_mangle)]
pub unsafe extern "C" fn sem_unlink(name: *const c_char) -> c_int {
- todo!("named semaphores")
+ if name.is_null() {
+ ERRNO.set(EINVAL);
+ return -1;
+ }
+ if name.is_null() { ERRNO.set(EINVAL); return -1; }
+ let name_bytes = unsafe { CStr::from_ptr(name) }.to_bytes().to_vec();
+ with_named_sems(|map| { if let Some(e) = map.get_mut(&name_bytes) { e.unlinked = true; } });
+ unsafe { shm_unlink(name) }
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/sem_trywait.html>.
diff --git a/src/header/semaphore/cbindgen.toml b/src/header/semaphore/cbindgen.toml
--- a/src/header/semaphore/cbindgen.toml
+++ b/src/header/semaphore/cbindgen.toml
@@ -3,6 +3,9 @@
after_includes = """
#include <bits/timespec.h> // for timespec
"""
+trailer = """
+#define SEM_FAILED ((sem_t *) -1)
+"""
language = "C"
style = "Type"
no_includes = true