diff --git a/local/patches/relibc/P3-clock-nanosleep.patch b/local/patches/relibc/P3-clock-nanosleep.patch new file mode 100644 index 00000000..4119b7ea --- /dev/null +++ b/local/patches/relibc/P3-clock-nanosleep.patch @@ -0,0 +1,43 @@ +diff --git a/src/header/time/mod.rs b/src/header/time/mod.rs +--- a/src/header/time/mod.rs ++++ b/src/header/time/mod.rs +@@ -6,8 +6,8 @@ + c_str::{CStr, CString}, + error::{Errno, ResultExt}, + header::{ + bits_timespec::timespec, +- errno::{EFAULT, ENOMEM, EOVERFLOW, ETIMEDOUT}, ++ errno::{EFAULT, EINVAL, ENOMEM, EOVERFLOW, ETIMEDOUT}, + signal::sigevent, + stdlib::getenv, + unistd::readlink, +@@ -275,12 +275,28 @@ + /// See . + // #[unsafe(no_mangle)] + pub extern "C" fn clock_nanosleep( + clock_id: clockid_t, + flags: c_int, + rqtp: *const timespec, + rmtp: *mut timespec, + ) -> c_int { +- unimplemented!(); ++ match clock_id { ++ CLOCK_REALTIME | CLOCK_MONOTONIC => { ++ if flags == TIMER_ABSTIME { ++ // Absolute-time sleep requires converting to a relative timeout. ++ // For CLOCK_MONOTONIC this is straightforward; for CLOCK_REALTIME ++ // we would need clock-delta computation. Return EINVAL until ++ // timespec_realtime_to_monotonic integration is validated. ++ return EINVAL; ++ } ++ match unsafe { Sys::nanosleep(rqtp, rmtp) } { ++ Ok(()) => 0, ++ Err(Errno(ETIMEDOUT)) => ETIMEDOUT, ++ Err(Errno(e)) => e, ++ } ++ } ++ _ => EINVAL, ++ } + } + + /// See . diff --git a/local/patches/relibc/P3-dup3.patch b/local/patches/relibc/P3-dup3.patch new file mode 100644 index 00000000..59e84880 --- /dev/null +++ b/local/patches/relibc/P3-dup3.patch @@ -0,0 +1,25 @@ +diff --git a/src/header/unistd/mod.rs b/src/header/unistd/mod.rs +--- a/src/header/unistd/mod.rs ++++ b/src/header/unistd/mod.rs +@@ -273,7 +273,20 @@ + /// See . + // #[unsafe(no_mangle)] + pub extern "C" fn dup3(fildes: c_int, fildes2: c_int, flag: c_int) -> c_int { +- unimplemented!(); ++ // dup3 requires fildes != fildes2 (unlike dup2 which is a no-op in that case) ++ if fildes == fildes2 { ++ ERRNO.set(EINVAL); ++ return -1; ++ } ++ match Sys::dup2(fildes, fildes2) { ++ Ok(newfd) => { ++ if flag & fcntl::O_CLOEXEC != 0 { ++ let _ = Sys::fcntl(newfd, fcntl::F_SETFD, fcntl::FD_CLOEXEC as c_ulonglong); ++ } ++ newfd ++ } ++ Err(Errno(e)) => { ERRNO.set(e); -1 } ++ } + } + + // See . diff --git a/local/patches/relibc/P3-getentropy.patch b/local/patches/relibc/P3-getentropy.patch new file mode 100644 index 00000000..720da57b --- /dev/null +++ b/local/patches/relibc/P3-getentropy.patch @@ -0,0 +1,45 @@ +diff --git a/src/header/unistd/mod.rs b/src/header/unistd/mod.rs +--- a/src/header/unistd/mod.rs ++++ b/src/header/unistd/mod.rs +@@ -537,8 +537,39 @@ + + /// See . + // #[unsafe(no_mangle)] +-pub extern "C" fn getentropy(buffer: *mut c_void, length: size_t) -> c_int { +- unimplemented!(); ++pub unsafe extern "C" fn getentropy(buffer: *mut c_void, length: size_t) -> c_int { ++ // POSIX limits getentropy to 256 bytes per call ++ const GETENTROPY_MAX: size_t = 256; ++ ++ if length > GETENTROPY_MAX { ++ ERRNO.set(EINVAL); ++ return -1; ++ } ++ ++ let path = unsafe { CStr::from_ptr(c"/scheme/rand".as_ptr().cast()) }; ++ let fd = match Sys::open(path, fcntl::O_RDONLY, 0) { ++ Ok(fd) => fd, ++ Err(Errno(e)) => { ++ ERRNO.set(e); ++ return -1; ++ } ++ }; ++ ++ let buf = unsafe { slice::from_raw_parts_mut(buffer.cast::(), length) }; ++ let mut filled = 0usize; ++ while filled < length { ++ match Sys::read(fd, &mut buf[filled..]) { ++ Ok(0) => break, ++ Ok(n) => filled += n, ++ Err(Errno(e)) => { ++ let _ = Sys::close(fd); ++ ERRNO.set(e); ++ return -1; ++ } ++ } ++ } ++ let _ = Sys::close(fd); ++ if filled < length { ERRNO.set(errno::EIO); -1 } else { 0 } + } + + /// See . diff --git a/local/patches/relibc/P3-named-semaphores.patch b/local/patches/relibc/P3-named-semaphores.patch new file mode 100644 index 00000000..a33f1a15 --- /dev/null +++ b/local/patches/relibc/P3-named-semaphores.patch @@ -0,0 +1,182 @@ +--- a/src/header/semaphore/mod.rs 2026-04-25 17:07:53.742796721 +0100 ++++ b/src/header/semaphore/mod.rs 2026-04-25 17:08:54.527084219 +0100 +@@ -2,12 +2,24 @@ + //! + //! See . + ++use core::mem::size_of; ++ + use crate::{ ++ c_str::CStr, + header::{ + bits_timespec::timespec, ++ errno::{EEXIST, EINVAL}, ++ fcntl::{O_CREAT, O_EXCL, O_RDWR}, ++ sys_mman::{ ++ mmap, munmap, shm_open, shm_unlink, MAP_SHARED, MAP_FAILED, PROT_READ, PROT_WRITE, ++ }, + time::{CLOCK_MONOTONIC, CLOCK_REALTIME}, ++ unistd::{close, ftruncate}, ++ }, ++ platform::{ ++ ERRNO, ++ types::{c_char, c_int, c_long, c_uint, clockid_t, c_void, mode_t, off_t, size_t}, + }, +- platform::types::{c_char, c_int, c_long, c_uint, clockid_t}, + }; + + /// See . +@@ -18,12 +30,17 @@ + pub size: [c_char; 4], + pub align: c_long, + } ++ ++/// Pointer value returned by `sem_open` on failure. ++/// cbindgen:ignore ++pub const SEM_FAILED: *mut sem_t = usize::MAX as *mut sem_t; ++ + pub type RlctSempahore = crate::sync::Semaphore; + + /// See . +-// #[unsafe(no_mangle)] ++#[unsafe(no_mangle)] + pub unsafe extern "C" fn sem_close(sem: *mut sem_t) -> c_int { +- todo!("named semaphores") ++ unsafe { munmap(sem.cast::(), size_of::()) } + } + + /// See . +@@ -50,13 +67,105 @@ + } + + /// See . +-// TODO: va_list +-// #[unsafe(no_mangle)] ++#[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, ++ mut __valist: ... + ) -> *mut sem_t { +- todo!("named semaphores") ++ // Validate name: must start with '/', no embedded '/'. ++ if name.is_null() { ++ ERRNO.set(EINVAL); ++ return SEM_FAILED; ++ } ++ ++ let name_c = unsafe { CStr::from_ptr(name) }; ++ let name_bytes = name_c.to_bytes(); ++ if name_bytes.is_empty() || name_bytes[0] != b'/' { ++ ERRNO.set(EINVAL); ++ return SEM_FAILED; ++ } ++ if name_bytes[1..].iter().any(|&b| b == b'/') { ++ ERRNO.set(EINVAL); ++ return SEM_FAILED; ++ } ++ ++ let creat = oflag & O_CREAT == O_CREAT; ++ let excl = oflag & O_EXCL == O_EXCL; ++ ++ let (mode, value): (mode_t, c_uint) = if creat { ++ ( ++ unsafe { __valist.arg::() }, ++ unsafe { __valist.arg::() }, ++ ) ++ } else { ++ (0, 0) ++ }; ++ ++ // Open or create the shared memory backing. ++ let (fd, created) = if creat && excl { ++ // O_CREAT | O_EXCL: must create exclusively. ++ let fd = unsafe { shm_open(name, O_CREAT | O_EXCL | O_RDWR, mode) }; ++ if fd < 0 { ++ return SEM_FAILED; ++ } ++ (fd, true) ++ } else if creat { ++ // O_CREAT without O_EXCL: try exclusive first, fall back to open. ++ let fd = unsafe { shm_open(name, O_CREAT | O_EXCL | O_RDWR, mode) }; ++ if fd >= 0 { ++ (fd, true) ++ } else if ERRNO.get() == EEXIST { ++ let fd = unsafe { shm_open(name, O_RDWR, 0) }; ++ if fd < 0 { ++ return SEM_FAILED; ++ } ++ (fd, false) ++ } else { ++ return SEM_FAILED; ++ } ++ } else { ++ // No O_CREAT: open existing. ++ let fd = unsafe { shm_open(name, O_RDWR, 0) }; ++ if fd < 0 { ++ return SEM_FAILED; ++ } ++ (fd, false) ++ }; ++ ++ // Set size if we created the backing. ++ if created { ++ if unsafe { ftruncate(fd, size_of::() as off_t) } < 0 { ++ let _ = unsafe { close(fd) }; ++ return SEM_FAILED; ++ } ++ } ++ ++ // Map the shared memory. ++ let ptr = unsafe { ++ mmap( ++ core::ptr::null_mut(), ++ size_of::(), ++ PROT_READ | PROT_WRITE, ++ MAP_SHARED, ++ fd, ++ 0, ++ ) ++ }; ++ let _ = unsafe { close(fd) }; ++ ++ if ptr == MAP_FAILED { ++ return SEM_FAILED; ++ } ++ ++ let sem_ptr = ptr.cast::(); ++ ++ // Initialize the semaphore value if we created the backing. ++ if created { ++ unsafe { sem_ptr.cast::().write(RlctSempahore::new(value)) }; ++ } ++ ++ sem_ptr + } + + /// See . +@@ -76,9 +185,9 @@ + } + + /// See . +-// #[unsafe(no_mangle)] ++#[unsafe(no_mangle)] + pub unsafe extern "C" fn sem_unlink(name: *const c_char) -> c_int { +- todo!("named semaphores") ++ unsafe { shm_unlink(name) } + } + + /// See . +--- a/src/header/semaphore/cbindgen.toml 2026-04-25 17:07:53.743979154 +0100 ++++ b/src/header/semaphore/cbindgen.toml 2026-04-25 17:09:18.310792692 +0100 +@@ -3,6 +3,9 @@ + after_includes = """ + #include // for timespec + """ ++trailer = """ ++#define SEM_FAILED ((sem_t *) -1) ++""" + language = "C" + style = "Type" + no_includes = true diff --git a/local/patches/relibc/P3-pthread-yield.patch b/local/patches/relibc/P3-pthread-yield.patch new file mode 100644 index 00000000..fdb0b6d8 --- /dev/null +++ b/local/patches/relibc/P3-pthread-yield.patch @@ -0,0 +1,21 @@ +diff --git a/src/header/pthread/mod.rs b/src/header/pthread/mod.rs +--- a/src/header/pthread/mod.rs ++++ b/src/header/pthread/mod.rs +@@ -304,9 +304,17 @@ + /// See . + #[unsafe(no_mangle)] + pub unsafe extern "C" fn pthread_testcancel() { + unsafe { pthread::testcancel() }; + } + ++/// ++/// ++/// Non-standard GNU extension. Prefer `sched_yield()` instead. ++// #[unsafe(no_mangle)] ++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 { diff --git a/local/patches/relibc/P3-secure-getenv.patch b/local/patches/relibc/P3-secure-getenv.patch new file mode 100644 index 00000000..5a116512 --- /dev/null +++ b/local/patches/relibc/P3-secure-getenv.patch @@ -0,0 +1,16 @@ +diff --git a/src/header/stdlib/mod.rs b/src/header/stdlib/mod.rs +--- a/src/header/stdlib/mod.rs ++++ b/src/header/stdlib/mod.rs +@@ -1233,7 +1233,11 @@ + /// See . + // #[unsafe(no_mangle)] + pub unsafe extern "C" fn secure_getenv(name: *const c_char) -> *mut c_char { +- unimplemented!(); ++ // Redox does not support setuid/setgid binaries, so there is no ++ // elevated-privilege case to guard against. Always delegate to getenv. ++ // If setuid support is ever added, this must check real vs effective ++ // uid/gid and return NULL when they differ. ++ unsafe { getenv(name) } + } + + /// See . diff --git a/local/patches/relibc/P3-tls-get-addr-panic-fix.patch b/local/patches/relibc/P3-tls-get-addr-panic-fix.patch new file mode 100644 index 00000000..c0ecf95e --- /dev/null +++ b/local/patches/relibc/P3-tls-get-addr-panic-fix.patch @@ -0,0 +1,36 @@ +diff --git a/src/header/dl-tls/mod.rs b/src/header/dl-tls/mod.rs +--- a/src/header/dl-tls/mod.rs ++++ b/src/header/dl-tls/mod.rs +@@ -49,8 +49,18 @@ pub unsafe extern "C" fn __tls_get_addr(ti: *mut dl_tls_index) -> *mut c_void { + let module_tls = unsafe { + // FIXME(andypython): master.align + let layout = Layout::from_size_align_unchecked(master.segment_size, 16); + let ptr = alloc(layout); + ++ if ptr.is_null() { ++ eprintln!( ++ "__tls_get_addr: TLS allocation failed for module {:#x} (size {})", ++ ti.ti_module, master.segment_size ++ ); ++ return ptr::null_mut(); ++ } ++ + ptr::copy_nonoverlapping(master.ptr, ptr, master.image_size); + ptr::write_bytes( + ptr.add(master.image_size), +@@ -68,10 +78,14 @@ pub unsafe extern "C" fn __tls_get_addr(ti: *mut dl_tls_index) -> *mut c_void { + let mut ptr = tcb.dtv_mut()[dtv_index]; + + if ptr.is_null() { +- panic!( +- "__tls_get_addr({ti:p}: {:#x}, {:#x})", +- ti.ti_module, ti.ti_offset ++ eprintln!( ++ "__tls_get_addr({:p}: {:#x}, {:#x}): \ ++ DTV entry is null, module {} TLS not available", ++ ti, ti.ti_module, ti.ti_offset, ti.ti_module + ); ++ return ptr::null_mut(); + } + + if cfg!(target_arch = "riscv64") { diff --git a/local/patches/relibc/P3-vfork.patch b/local/patches/relibc/P3-vfork.patch new file mode 100644 index 00000000..a127693d --- /dev/null +++ b/local/patches/relibc/P3-vfork.patch @@ -0,0 +1,25 @@ +diff --git a/src/header/unistd/mod.rs b/src/header/unistd/mod.rs +--- a/src/header/unistd/mod.rs ++++ b/src/header/unistd/mod.rs +@@ -1214,12 +1214,20 @@ + /// See . + /// + /// # Deprecation + /// The `vfork()` function was marked obsolescent in the Open Group Base + /// Specifications Issue 6, and removed in Issue 7. + #[deprecated] + // #[unsafe(no_mangle)] + pub extern "C" fn vfork() -> pid_t { +- unimplemented!(); ++ // Simplified implementation: Redox uses copy-on-write fork semantics, ++ // which provides the memory-sharing benefits of traditional vfork ++ // without the dangerous shared-address-space semantics. This wrapper ++ // simply delegates to fork(). ++ // ++ // callers that depend on the parent being suspended until the child ++ // calls exec or _exit will still work correctly, just without the ++ // scheduling guarantee that vfork traditionally provided. ++ unsafe { fork() } + } + + unsafe fn with_argv(