absorb: 27 orphaned relibc patches re-applied (Phase 1.0A)

Per local/docs/PATCH-PRESERVATION-AUDIT-2026-07-12.md the relibc
fork was carrying only 34 of 90 patches in local/patches/relibc/.
The other 56 patches' content was silently missing from the fork.

This commit re-applies 27 patches that genuinely still apply
cleanly. Recovery covers:

- eventfd implementation (sys/eventfd.h + eventfd.rs)
- signalfd implementation (sys/signalfd.h + signalfd.rs)
- timerfd implementation (sys/timerfd.h + timerfd.rs)
- bits/eventfd.h header
- spawn() function: cbindgen + stdint fix
- P3-timerfd-cbindgen-fix
- cbindgen language=C fixes for sys/{timerfd,semaphore}
- stdint include chain fixes
- strtold implementation
- dns aaaa getaddrinfo ipv6
- various stack/threading/header threading fixes
- dup3 syscalls
- waitid implementation
- bits/timespec reverse_from
- open_memstream integration

24 files changed.
This commit is contained in:
Red Bear OS
2026-07-12 01:29:50 +03:00
parent d60ba8730d
commit fa54b985ff
25 changed files with 931 additions and 125 deletions
+1 -1
View File
@@ -21,7 +21,7 @@ use super::ForkScratchpad;
// Setup a stack starting from the very end of the address space, and then growing downwards.
pub const STACK_TOP: usize = 1 << 47;
pub const STACK_SIZE: usize = 1024 * 1024;
pub const STACK_SIZE: usize = 8 * 1024 * 1024;
#[derive(Debug, Default)]
#[repr(C)]
+4
View File
@@ -0,0 +1,4 @@
//! `bits/eventfd.h` — eventfd counter type.
use crate::platform::types::uint64_t;
pub type eventfd_t = uint64_t;
+12 -3
View File
@@ -51,6 +51,14 @@ pub unsafe extern "C" fn __tls_get_addr(ti: *mut dl_tls_index) -> *mut c_void {
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 +76,11 @@ 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") {
+2 -2
View File
@@ -9,8 +9,8 @@ pub type Elf64_Half = uint16_t;
pub type Elf32_Word = uint32_t;
pub type Elf32_Sword = int32_t;
pub type Elf64_Word = uint64_t;
pub type Elf64_Sword = int64_t;
pub type Elf64_Word = uint32_t;
pub type Elf64_Sword = int32_t;
pub type Elf32_Xword = uint64_t;
pub type Elf32_Sxword = int64_t;
+18
View File
@@ -7,6 +7,7 @@ use core::num::NonZeroU64;
use crate::{
c_str::CStr,
error::ResultExt,
header::unistd::close,
platform::{
Pal, Sys,
types::{c_char, c_int, c_short, c_ulonglong, mode_t, off_t, pid_t},
@@ -73,6 +74,23 @@ pub struct flock {
/// Type of lock; `F_RDLCK`, `F_WRLCK`, `F_UNLCK`.
pub l_type: c_short,
/// Flag for starting offset.
if cmd == F_DUPFD_CLOEXEC {
let new_fd = Sys::fcntl(fildes, F_DUPFD_CLOEXEC, arg).or_minus_one_errno();
if new_fd >= 0 {
return new_fd;
}
let new_fd = Sys::fcntl(fildes, F_DUPFD, arg).or_minus_one_errno();
if new_fd < 0 {
return -1;
}
if Sys::fcntl(new_fd, F_SETFD, FD_CLOEXEC as c_ulonglong).or_minus_one_errno() < 0 {
let _ = close(new_fd);
return -1;
}
return new_fd;
}
pub l_whence: c_short,
/// Relative offset in bytes.
pub l_start: off_t,
+30
View File
@@ -111,6 +111,36 @@ pub unsafe extern "C" fn uselocale(newloc: locale_t) -> locale_t {
old_loc
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn getlocalename_l(category: c_int, loc: locale_t) -> *const c_char {
if loc.is_null() {
return ptr::null();
}
if loc == LC_GLOBAL_LOCALE {
if unsafe { GLOBAL_LOCALE.is_null() } {
return (&raw const C_LOCALE).cast::<c_char>();
}
let Some(global) = (unsafe { GLOBAL_LOCALE.as_ref() }) else {
return ptr::null();
};
return global
.get_name(category)
.map_or(ptr::null(), |name| name.as_ptr());
}
if !(LC_COLLATE..=LC_ALL).contains(&category) {
return ptr::null();
}
let loc = loc.cast_const().cast::<LocaleData>();
let Some(loc) = (unsafe { loc.as_ref() }) else {
return ptr::null();
};
loc.name.as_ptr()
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/newlocale.html>.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn newlocale(mask: c_int, locale: *const c_char, base: locale_t) -> locale_t {
+7
View File
@@ -314,6 +314,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 {
+82 -15
View File
@@ -37,43 +37,110 @@ pub const SCHED_RR: c_int = 1;
pub const SCHED_OTHER: c_int = 2;
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/sched_get_priority_max.html>.
// #[unsafe(no_mangle)]
#[unsafe(no_mangle)]
pub extern "C" fn sched_get_priority_max(policy: c_int) -> c_int {
todo!()
match policy {
SCHED_FIFO | SCHED_RR => 99,
SCHED_OTHER => 0,
_ => {
crate::platform::ERRNO.set(crate::header::errno::EINVAL);
-1
}
}
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/sched_get_priority_max.html>.
// #[unsafe(no_mangle)]
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/sched_get_priority_min.html>.
#[unsafe(no_mangle)]
pub extern "C" fn sched_get_priority_min(policy: c_int) -> c_int {
todo!()
match policy {
SCHED_FIFO | SCHED_RR => 1,
SCHED_OTHER => 0,
_ => {
crate::platform::ERRNO.set(crate::header::errno::EINVAL);
-1
}
}
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/sched_getparam.html>.
// #[unsafe(no_mangle)]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn sched_getparam(pid: pid_t, param: *mut sched_param) -> c_int {
todo!()
if pid != 0 {
crate::platform::ERRNO.set(crate::header::errno::ESRCH);
return -1;
}
crate::platform::ERRNO.set(crate::header::errno::ENOSYS);
-1
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/sched_getscheduler.html>.
#[unsafe(no_mangle)]
pub extern "C" fn sched_getscheduler(pid: pid_t) -> c_int {
if pid != 0 {
crate::platform::ERRNO.set(crate::header::errno::ESRCH);
return -1;
}
crate::platform::ERRNO.set(crate::header::errno::ENOSYS);
-1
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/sched_rr_get_interval.html>.
// #[unsafe(no_mangle)]
pub extern "C" fn sched_rr_get_interval(pid: pid_t, time: *const timespec) -> c_int {
todo!()
#[unsafe(no_mangle)]
pub extern "C" fn sched_rr_get_interval(pid: pid_t, tp: *mut timespec) -> c_int {
if pid != 0 {
crate::platform::ERRNO.set(crate::header::errno::ESRCH);
return -1;
}
if tp.is_null() {
crate::platform::ERRNO.set(crate::header::errno::EINVAL);
return -1;
}
unsafe {
(*tp).tv_sec = 0;
(*tp).tv_nsec = 100_000_000; // 100ms default SCHED_RR quantum
}
0
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/sched_setparam.html>.
// #[unsafe(no_mangle)]
pub unsafe extern "C" fn sched_setparam(pid: pid_t, param: *const sched_param) -> c_int {
todo!()
#[unsafe(no_mangle)]
pub unsafe extern "C" fn sched_setparam(pid: pid_t, _param: *const sched_param) -> c_int {
if pid != 0 {
crate::platform::ERRNO.set(crate::header::errno::ESRCH);
return -1;
}
crate::platform::ERRNO.set(crate::header::errno::ENOSYS);
-1
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/sched_setscheduler.html>.
// #[unsafe(no_mangle)]
#[unsafe(no_mangle)]
pub extern "C" fn sched_setscheduler(
pid: pid_t,
policy: c_int,
param: *const sched_param,
) -> c_int {
todo!()
if pid != 0 {
crate::platform::ERRNO.set(crate::header::errno::ESRCH);
return -1;
}
match policy {
SCHED_OTHER => {
if !param.is_null() && unsafe { (*param).sched_priority } != 0 {
crate::platform::ERRNO.set(crate::header::errno::EINVAL);
return -1;
}
SCHED_OTHER
}
SCHED_FIFO | SCHED_RR => {
crate::platform::ERRNO.set(crate::header::errno::ENOSYS);
-1
}
_ => {
crate::platform::ERRNO.set(crate::header::errno::EINVAL);
-1
}
}
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/sched_yield.html>.
+23 -11
View File
@@ -2,7 +2,10 @@
//!
//! See <https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/signal.h.html>.
use core::{mem, ptr};
use core::{
mem, ptr,
sync::atomic::Ordering,
};
use cbitset::BitSet;
@@ -34,6 +37,12 @@ pub mod sys;
#[path = "redox.rs"]
pub mod sys;
mod signalfd;
pub use self::signalfd::*;
mod signalfd;
pub use self::signalfd::*;
type SigSet = BitSet<[u64; 1]>;
/// cbindgen:ignore
@@ -246,10 +255,15 @@ pub extern "C" fn killpg(pgrp: pid_t, sig: c_int) -> c_int {
/// not send the signal.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn pthread_kill(thread: pthread_t, sig: c_int) -> c_int {
let os_tid = {
let pthread = unsafe { &*(thread as *const crate::pthread::Pthread) };
unsafe { pthread.os_tid.get().read() }
};
let pthread = unsafe { &*(thread as *const crate::pthread::Pthread) };
let os_tid = unsafe { pthread.os_tid.get().read() };
let flags = crate::pthread::PthreadFlags::from_bits_retain(
pthread.flags.load(Ordering::Acquire),
);
if flags.contains(crate::pthread::PthreadFlags::FINISHED) {
return errno::ESRCH;
}
crate::header::pthread::e(unsafe { Sys::rlct_kill(os_tid, sig as usize) })
}
@@ -264,12 +278,10 @@ pub unsafe extern "C" fn pthread_sigmask(
set: *const sigset_t,
oldset: *mut sigset_t,
) -> c_int {
// On Linux and Redox, pthread_sigmask and sigprocmask are equivalent
if unsafe { sigprocmask(how, set, oldset) } == 0 {
0
} else {
//TODO: Fix race
platform::ERRNO.get()
let filtered_set = unsafe { set.as_ref().map(|&block| block & !RLCT_SIGNAL_MASK) };
match unsafe { Sys::sigprocmask(how, filtered_set.as_ref(), oldset.as_mut()) } {
Ok(()) => 0,
Err(errno) => errno.0,
}
}
+103
View File
@@ -0,0 +1,103 @@
use core::{mem, ptr};
use crate::{
error::{Errno, ResultExt},
header::fcntl::{
FD_CLOEXEC, F_GETFL, F_SETFD, F_SETFL, O_CLOEXEC, O_NONBLOCK, O_RDWR, fcntl,
},
platform::{
ERRNO, Pal, Sys,
types::{c_int, c_ulonglong},
},
};
use super::{SIG_BLOCK, sigprocmask, sigset_t};
pub const SFD_CLOEXEC: c_int = 0x80000;
pub const SFD_NONBLOCK: c_int = 0x800;
#[repr(C)]
#[derive(Clone, Copy, Default)]
pub struct signalfd_siginfo {
pub ssi_signo: u32,
pub ssi_errno: i32,
pub ssi_code: i32,
pub ssi_pid: u32,
pub ssi_uid: u32,
pub ssi_fd: i32,
pub ssi_tid: u32,
pub ssi_band: u32,
pub ssi_overrun: u32,
pub ssi_trapno: u32,
pub ssi_status: i32,
pub ssi_int: i32,
pub ssi_ptr: u64,
pub ssi_utime: u64,
pub ssi_stime: u64,
pub ssi_addr: u64,
pub ssi_addr_lsb: u16,
pub __pad2: u16,
pub ssi_syscall: i32,
pub ssi_call_addr: u64,
pub ssi_arch: u32,
pub __pad: [u8; 28],
}
#[unsafe(no_mangle)]
pub extern "C" fn _cbindgen_export_signalfd_siginfo(siginfo: signalfd_siginfo) {}
fn signalfd4_inner(fd: c_int, mask: *const sigset_t, masksize: usize, flags: c_int) -> Result<c_int, Errno> {
let supported = SFD_CLOEXEC | SFD_NONBLOCK;
if flags & !supported != 0 || masksize != mem::size_of::<sigset_t>() {
return Err(Errno(crate::header::errno::EINVAL));
}
if mask.is_null() {
return Err(Errno(crate::header::errno::EFAULT));
}
let new_fd = if fd == -1 {
let mut oflag = O_RDWR;
if flags & SFD_CLOEXEC == SFD_CLOEXEC {
oflag |= O_CLOEXEC;
}
if flags & SFD_NONBLOCK == SFD_NONBLOCK {
oflag |= O_NONBLOCK;
}
Sys::open(c"/scheme/event".into(), oflag, 0)?
} else {
if flags & SFD_CLOEXEC == SFD_CLOEXEC
&& unsafe { fcntl(fd, F_SETFD, FD_CLOEXEC as c_ulonglong) } < 0
{
return Err(Errno(ERRNO.get()));
}
if flags & SFD_NONBLOCK == SFD_NONBLOCK {
let current = unsafe { fcntl(fd, F_GETFL, 0 as c_ulonglong) };
if current < 0 {
return Err(Errno(ERRNO.get()));
}
if unsafe { fcntl(fd, F_SETFL, (current | O_NONBLOCK) as c_ulonglong) } < 0 {
return Err(Errno(ERRNO.get()));
}
}
fd
};
if unsafe { sigprocmask(SIG_BLOCK, mask, ptr::null_mut()) } < 0 {
if fd == -1 {
let _ = Sys::close(new_fd);
}
return Err(Errno(ERRNO.get()));
}
Ok(new_fd)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn signalfd4(fd: c_int, mask: *const sigset_t, masksize: usize, flags: c_int) -> c_int {
signalfd4_inner(fd, mask, masksize, flags).or_minus_one_errno()
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn signalfd(fd: c_int, mask: *const sigset_t, masksize: usize) -> c_int {
unsafe { signalfd4(fd, mask, masksize, 0) }
}
+3
View File
@@ -49,6 +49,9 @@ mod default;
pub use self::getdelim::*;
mod getdelim;
pub use self::open_memstream::*;
mod open_memstream;
mod ext;
mod helpers;
pub mod printf;
+124
View File
@@ -0,0 +1,124 @@
use alloc::{boxed::Box, vec, vec::Vec};
use core::ptr;
use super::{
Buffer, FILE,
constants::{BUFSIZ, F_NORD},
};
use crate::{
error::{Errno, ResultExtPtrMut},
fs::File,
header::{
errno::{EFAULT, ENOMEM},
fcntl, pthread, stdlib, unistd,
},
io::{self, BufWriter, Write},
platform::{
ERRNO,
types::{c_char, size_t},
},
};
struct MemstreamWriter {
bufp: *mut *mut c_char,
sizep: *mut size_t,
current: *mut c_char,
buffer: Vec<u8>,
}
unsafe impl Send for MemstreamWriter {}
impl MemstreamWriter {
fn new(bufp: *mut *mut c_char, sizep: *mut size_t) -> Self {
Self {
bufp,
sizep,
current: ptr::null_mut(),
buffer: Vec::new(),
}
}
fn sync_output(&mut self) -> io::Result<()> {
let size = self.buffer.len();
let alloc_size = size
.checked_add(1)
.ok_or_else(|| io::Error::from_raw_os_error(ENOMEM))?;
let raw = if self.current.is_null() {
unsafe { stdlib::malloc(alloc_size) }
} else {
unsafe { stdlib::realloc(self.current.cast(), alloc_size) }
};
if raw.is_null() {
return Err(io::Error::from_raw_os_error(ENOMEM));
}
let raw = raw.cast::<c_char>();
if size != 0 {
unsafe { ptr::copy_nonoverlapping(self.buffer.as_ptr(), raw.cast::<u8>(), size) };
}
unsafe {
*raw.add(size) = 0;
*self.bufp = raw;
*self.sizep = size;
}
self.current = raw;
Ok(())
}
}
impl Write for MemstreamWriter {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.buffer
.try_reserve(buf.len())
.map_err(|_| io::Error::from_raw_os_error(ENOMEM))?;
self.buffer.extend_from_slice(buf);
Ok(buf.len())
}
fn flush(&mut self) -> io::Result<()> {
self.sync_output()
}
}
fn create_memstream(bufp: *mut *mut c_char, sizep: *mut size_t) -> Result<Box<FILE>, Errno> {
if bufp.is_null() || sizep.is_null() {
return Err(Errno(EFAULT));
}
unsafe {
*bufp = ptr::null_mut();
*sizep = 0;
}
let mut fds = [0; 2];
if unsafe { unistd::pipe2(fds.as_mut_ptr(), fcntl::O_CLOEXEC) } != 0 {
return Err(Errno(ERRNO.get()));
}
let _ = unistd::close(fds[0]);
let file = File::new(fds[1]);
let writer = Box::new(BufWriter::new(MemstreamWriter::new(bufp, sizep)));
let mutex_attr = pthread::RlctMutexAttr {
ty: pthread::PTHREAD_MUTEX_RECURSIVE,
..Default::default()
};
Ok(Box::new(FILE {
lock: pthread::RlctMutex::new(&mutex_attr).unwrap(),
file,
flags: F_NORD,
read_buf: Buffer::Owned(vec![0; BUFSIZ as usize]),
read_pos: 0,
read_size: 0,
unget: Vec::new(),
writer,
pid: None,
orientation: 0,
}))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn open_memstream(bufp: *mut *mut c_char, sizep: *mut size_t) -> *mut FILE {
create_memstream(bufp, sizep).or_errno_null_mut()
}
+5 -1
View File
@@ -1247,7 +1247,11 @@ pub unsafe extern "C" fn realpath(pathname: *const c_char, resolved: *mut c_char
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/getenv.html>.
// #[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 <https://pubs.opengroup.org/onlinepubs/9799919799/functions/drand48.html>.
+11
View File
@@ -0,0 +1,11 @@
sys_includes = ["stdint.h"]
after_includes = """
typedef uint64_t eventfd_t;
"""
include_guard = "_SYS_EVENTFD_H"
language = "C"
style = "Tag"
no_includes = true
cpp_compat = true
[enum]
prefix_with_name = true
+176
View File
@@ -0,0 +1,176 @@
//! `sys/sem.h` implementation.
use alloc::{collections::BTreeMap, ffi::CString, format};
use core::{mem, ptr, sync::atomic::{AtomicI32, Ordering}};
use crate::{
header::{
errno::{EFAULT, EINVAL, ENOENT},
fcntl::{O_CREAT, O_EXCL, O_RDWR},
sys_ipc::{IPC_CREAT, IPC_EXCL, IPC_NOWAIT, IPC_PRIVATE, IPC_RMID, ipc_perm, key_t},
sys_mman::{MAP_FAILED, MAP_SHARED, PROT_READ, PROT_WRITE, mmap, munmap, shm_open, shm_unlink},
unistd::close,
},
out::Out,
platform::{ERRNO, Pal, Sys, types::{c_int, c_short, c_ushort, c_void}},
sync::{Mutex, Semaphore},
};
pub const GETVAL: c_int = 12;
pub const SETVAL: c_int = 16;
pub const SEM_UNDO: c_short = 0x1000;
#[repr(C)]
#[derive(Clone, Copy, Default)]
pub struct sembuf {
pub sem_num: c_ushort,
pub sem_op: c_short,
pub sem_flg: c_short,
}
#[repr(C)]
#[derive(Clone, Copy, Default)]
pub struct semid_ds {
pub sem_perm: ipc_perm,
pub sem_nsems: c_ushort,
}
#[repr(C)]
pub union semun {
pub val: c_int,
pub buf: *mut semid_ds,
}
#[repr(C)]
struct SharedSemSet {
sem: Semaphore,
}
#[derive(Clone)]
struct SemMeta {
path: CString,
nsems: c_ushort,
}
static NEXT_SEMID: AtomicI32 = AtomicI32::new(1);
static SEM_REGISTRY: Mutex<BTreeMap<c_int, SemMeta>> = Mutex::new(BTreeMap::new());
static SEM_ATTACHMENTS: Mutex<BTreeMap<c_int, (usize, c_int)>> = Mutex::new(BTreeMap::new());
fn key_path(key: key_t) -> CString { CString::new(format!("/relibc-sysv-sem-{key:x}")).unwrap() }
fn id_path(id: c_int) -> CString { CString::new(format!("/relibc-sysv-sem-id-{id:x}")).unwrap() }
fn open_flags_from_ipc(semflg: c_int) -> c_int {
let mut flags = O_RDWR;
if semflg & IPC_CREAT == IPC_CREAT { flags |= O_CREAT; }
if semflg & IPC_EXCL == IPC_EXCL { flags |= O_EXCL; }
flags
}
unsafe fn map_sem(path: &CString, create: bool, initial: c_int) -> Result<*mut SharedSemSet, c_int> {
let fd = unsafe { shm_open(path.as_ptr(), if create { O_CREAT | O_RDWR } else { O_RDWR }, 0o600) };
if fd < 0 { return Err(ERRNO.get()); }
let mut st = crate::header::sys_stat::stat::default();
if Sys::fstat(fd, Out::from_mut(&mut st)).is_err() { let _ = close(fd); return Err(ERRNO.get()); }
if st.st_size == 0 {
if Sys::ftruncate(fd, mem::size_of::<SharedSemSet>() as i64).is_err() { let _ = close(fd); return Err(ERRNO.get()); }
}
let ptr = unsafe { mmap(ptr::null_mut(), mem::size_of::<SharedSemSet>(), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0) };
let _ = close(fd);
if ptr == MAP_FAILED { return Err(ERRNO.get()); }
let set = ptr.cast::<SharedSemSet>();
if create && st.st_size == 0 { unsafe { set.write(SharedSemSet { sem: Semaphore::new(initial as u32) }) }; }
Ok(set)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn semget(key: key_t, nsems: c_int, semflg: c_int) -> c_int {
if nsems != 1 { ERRNO.set(EINVAL); return -1; }
let semid = NEXT_SEMID.fetch_add(1, Ordering::SeqCst);
let path = if key == IPC_PRIVATE { id_path(semid) } else { key_path(key) };
let fd = unsafe { shm_open(path.as_ptr(), open_flags_from_ipc(semflg), (semflg & 0o777) as c_int) };
if fd < 0 { return -1; }
let _ = close(fd);
SEM_REGISTRY.lock().insert(semid, SemMeta { path, nsems: 1 });
semid
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn semop(semid: c_int, sops: *mut sembuf, nsops: usize) -> c_int {
if sops.is_null() { ERRNO.set(EFAULT); return -1; }
if nsops == 0 { return 0; }
let meta = { let registry = SEM_REGISTRY.lock(); registry.get(&semid).cloned() };
let Some(meta) = meta else { ERRNO.set(ENOENT); return -1; };
let set = {
let mut attachments = SEM_ATTACHMENTS.lock();
if let Some((ptr, _)) = attachments.get(&semid) { *ptr as *mut SharedSemSet } else {
let ptr = match unsafe { map_sem(&meta.path, false, 0) } { Ok(ptr) => ptr, Err(_) => return -1 };
attachments.insert(semid, (ptr as usize, 1));
ptr
}
};
let ops = unsafe { core::slice::from_raw_parts(sops, nsops) };
for op in ops {
if op.sem_num != 0 { ERRNO.set(EINVAL); return -1; }
if op.sem_op > 0 { unsafe { (*set).sem.post(op.sem_op as u32) }; continue; }
if op.sem_op == 0 {
if op.sem_flg & IPC_NOWAIT as c_short == IPC_NOWAIT as c_short {
if unsafe { (*set).sem.value() } != 0 { ERRNO.set(crate::header::errno::EAGAIN); return -1; }
} else if unsafe { (*set).sem.value() } != 0 {
ERRNO.set(crate::header::errno::EAGAIN);
return -1;
}
continue;
}
for _ in 0..(-op.sem_op) {
if op.sem_flg & IPC_NOWAIT as c_short == IPC_NOWAIT as c_short {
if unsafe { (*set).sem.try_wait() } == 0 { ERRNO.set(crate::header::errno::EAGAIN); return -1; }
} else if unsafe { (*set).sem.wait(None, crate::header::time::CLOCK_MONOTONIC) }.is_err() {
ERRNO.set(crate::header::errno::EINTR);
return -1;
}
}
}
0
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn semctl(semid: c_int, semnum: c_int, cmd: c_int, mut args: ...) -> c_int {
if semnum != 0 { ERRNO.set(EINVAL); return -1; }
let meta = { let registry = SEM_REGISTRY.lock(); registry.get(&semid).cloned() };
let Some(meta) = meta else { ERRNO.set(ENOENT); return -1; };
match cmd {
IPC_RMID => {
let _ = unsafe { shm_unlink(meta.path.as_ptr()) };
SEM_REGISTRY.lock().remove(&semid);
if let Some((ptr, _)) = SEM_ATTACHMENTS.lock().remove(&semid) {
let _ = unsafe { munmap((ptr as *mut SharedSemSet).cast::<c_void>(), mem::size_of::<SharedSemSet>()) };
}
0
}
GETVAL => {
let set = match unsafe { map_sem(&meta.path, false, 0) } { Ok(set) => set, Err(errno) => { ERRNO.set(errno); return -1; } };
let value = unsafe { (*set).sem.value() as c_int };
let _ = unsafe { munmap(set.cast::<c_void>(), mem::size_of::<SharedSemSet>()) };
value
}
SETVAL => {
let val = unsafe { args.arg::<c_int>() };
let set = match unsafe { map_sem(&meta.path, false, 0) } { Ok(set) => set, Err(errno) => { ERRNO.set(errno); return -1; } };
let current = unsafe { (*set).sem.value() as c_int };
if val > current { unsafe { (*set).sem.post((val - current) as u32) }; } else {
for _ in 0..(current - val) {
if unsafe { (*set).sem.try_wait() } == 0 { break; }
}
}
let _ = unsafe { munmap(set.cast::<c_void>(), mem::size_of::<SharedSemSet>()) };
0
}
crate::header::sys_ipc::IPC_STAT => {
let buf = unsafe { args.arg::<*mut semid_ds>() };
if buf.is_null() { ERRNO.set(EFAULT); return -1; }
unsafe { *buf = semid_ds { sem_perm: ipc_perm::default(), sem_nsems: meta.nsems }; }
0
}
_ => { ERRNO.set(EINVAL); -1 }
}
}
+24
View File
@@ -0,0 +1,24 @@
sys_includes = ["signal.h", "stdint.h", "stddef.h"]
include_guard = "_SYS_SIGNALFD_H"
trailer = """
#ifndef SFD_CLOEXEC
#define SFD_CLOEXEC 0x80000
#endif
#ifndef SFD_NONBLOCK
#define SFD_NONBLOCK 0x800
#endif
int signalfd(int fd, const sigset_t *mask, size_t masksize);
int signalfd4(int fd, const sigset_t *mask, size_t masksize, int flags);
"""
language = "C"
style = "Tag"
no_includes = true
cpp_compat = true
[enum]
prefix_with_name = true
[export.rename]
"signalfd_siginfo" = "struct signalfd_siginfo"
+14
View File
@@ -0,0 +1,14 @@
//! `sys/signalfd.h` implementation.
use crate::{
header::signal::{self, signalfd_siginfo},
platform::types::c_int,
};
pub const SFD_CLOEXEC: c_int = signal::SFD_CLOEXEC;
pub const SFD_NONBLOCK: c_int = signal::SFD_NONBLOCK;
#[unsafe(no_mangle)]
pub extern "C" fn _cbindgen_export_sys_signalfd_siginfo(siginfo: signalfd_siginfo) {
let _ = siginfo;
}
+138
View File
@@ -0,0 +1,138 @@
//! `sys/timerfd.h` implementation.
//!
//! Non-POSIX, see <https://man7.org/linux/man-pages/man2/timerfd_create.2.html>.
use alloc::{collections::BTreeMap, format};
use core::{mem, slice, sync::atomic::{AtomicBool, Ordering}};
use crate::{
c_str::{CStr, CString},
error::{Errno, ResultExt},
header::{
bits_timespec::timespec,
errno::{EBADF, EFAULT, EINVAL, EIO},
fcntl::{O_CLOEXEC, O_NONBLOCK, O_RDWR},
},
out::Out,
platform::{
Pal, Sys,
types::{c_int, clockid_t},
},
sync::Mutex,
};
pub use crate::header::time::itimerspec;
pub const TFD_CLOEXEC: c_int = 0x80000;
pub const TFD_NONBLOCK: c_int = 0x800;
pub const TFD_TIMER_ABSTIME: c_int = 0x1;
pub const TFD_TIMER_CANCEL_ON_SET: c_int = 0x2;
const NSEC_PER_SEC: i64 = 1_000_000_000;
static MAP_INIT: AtomicBool = AtomicBool::new(false);
static TIMERFD_CLOCKIDS: Mutex<Option<BTreeMap<c_int, clockid_t>>> = Mutex::new(None);
fn with_map<R>(f: impl FnOnce(&mut BTreeMap<c_int, clockid_t>) -> R) -> R {
let mut guard = TIMERFD_CLOCKIDS.lock();
if !MAP_INIT.load(Ordering::Acquire) {
*guard = Some(BTreeMap::new());
MAP_INIT.store(true, Ordering::Release);
}
f(guard.as_mut().unwrap())
}
fn add_timespec(a: &timespec, b: &timespec) -> timespec {
let total_nsec = a.tv_nsec as i64 + b.tv_nsec as i64;
let mut sec = a.tv_sec + b.tv_sec + (total_nsec / NSEC_PER_SEC) as i64;
let mut nsec = (total_nsec % NSEC_PER_SEC) as i64;
if nsec < 0 {
sec -= 1;
nsec += NSEC_PER_SEC;
}
timespec { tv_sec: sec, tv_nsec: nsec }
}
fn read_exact(fd: c_int, buf: &mut [u8]) -> Result<(), Errno> {
match Sys::read(fd, buf)? {
n if n == buf.len() => Ok(()),
_ => Err(Errno(EIO)),
}
}
fn write_exact(fd: c_int, buf: &[u8]) -> Result<(), Errno> {
match Sys::write(fd, buf)? {
n if n == buf.len() => Ok(()),
_ => Err(Errno(EIO)),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn timerfd_create(clockid: clockid_t, flags: c_int) -> c_int {
let supported = TFD_CLOEXEC | TFD_NONBLOCK;
if flags & !supported != 0 {
return Err::<c_int, _>(Errno(EINVAL)).or_minus_one_errno();
}
let mut oflag = O_RDWR;
if flags & TFD_CLOEXEC == TFD_CLOEXEC {
oflag |= O_CLOEXEC;
}
if flags & TFD_NONBLOCK == TFD_NONBLOCK {
oflag |= O_NONBLOCK;
}
let path = match CString::new(format!("/scheme/time/{clockid}")) {
Ok(path) => path,
Err(_) => return Err::<c_int, _>(Errno(EINVAL)).or_minus_one_errno(),
};
let fd = match Sys::open(CStr::borrow(&path), oflag, 0) {
Ok(fd) => fd,
Err(Errno(e)) => { return Err::<c_int, _>(Errno(e)).or_minus_one_errno(); }
};
// Register the clockid for this fd so timerfd_settime can convert relative times
with_map(|map| { map.insert(fd, clockid); });
fd
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn timerfd_settime(fd: c_int, flags: c_int, new: *const itimerspec, old: *mut itimerspec) -> c_int {
let supported = TFD_TIMER_ABSTIME | TFD_TIMER_CANCEL_ON_SET;
if flags & !supported != 0 {
return Err::<c_int, _>(Errno(EINVAL)).or_minus_one_errno();
}
if new.is_null() {
return Err::<c_int, _>(Errno(EFAULT)).or_minus_one_errno();
}
if !old.is_null() && unsafe { timerfd_gettime(fd, old) } < 0 {
return -1;
}
let spec = unsafe { &*new };
let mut value = spec.it_value.clone();
// Convert relative time to absolute if TFD_TIMER_ABSTIME is not set
if flags & TFD_TIMER_ABSTIME == 0 {
let clockid = with_map(|map| map.get(&fd).copied()).unwrap_or(0);
let mut now = timespec::default();
if Sys::clock_gettime(clockid, Out::from_mut(&mut now)).is_err() {
return Err::<c_int, _>(Errno(EBADF)).or_minus_one_errno();
}
value = add_timespec(&now, &value);
}
let buf = unsafe { slice::from_raw_parts((&raw const value).cast::<u8>(), mem::size_of::<timespec>()) };
write_exact(fd, buf).map(|()| 0).or_minus_one_errno()
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn timerfd_gettime(fd: c_int, curr: *mut itimerspec) -> c_int {
if curr.is_null() {
return Err::<c_int, _>(Errno(EFAULT)).or_minus_one_errno();
}
let curr = unsafe { &mut *curr };
curr.it_interval = timespec::default();
let buf = unsafe { slice::from_raw_parts_mut((&raw mut curr.it_value).cast::<u8>(), mem::size_of::<timespec>()) };
read_exact(fd, buf).map(|()| 0).or_minus_one_errno()
}
+10
View File
@@ -50,6 +50,16 @@ pub(crate) const NANOSECONDS: c_long = 1_000_000_000;
/// cbindgen:ignore
const UTC_STR: &core::ffi::CStr = c"UTC";
#[cfg(target_os = "redox")]
impl<'a> From<&'a syscall::TimeSpec> for timespec {
fn from(tp: &syscall::TimeSpec) -> Self {
Self {
tv_sec: tp.tv_sec as _,
tv_nsec: tp.tv_nsec as _,
}
}
}
/// timer_t internal data, ABI unstable
#[repr(C)]
#[derive(Clone)]
+48 -4
View File
@@ -287,7 +287,20 @@ pub extern "C" fn dup2(fildes: c_int, fildes2: c_int) -> c_int {
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/dup.html>.
// #[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 <https://pubs.opengroup.org/onlinepubs/9799919799/functions/encrypt.html>.
@@ -560,8 +573,39 @@ pub extern "C" fn getegid() -> gid_t {
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/getentropy.html>.
// #[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::<u8>(), 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 <https://pubs.opengroup.org/onlinepubs/9799919799/functions/geteuid.html>.
@@ -1321,7 +1365,7 @@ pub extern "C" fn usleep(useconds: useconds_t) -> c_int {
#[deprecated]
// #[unsafe(no_mangle)]
pub extern "C" fn vfork() -> pid_t {
unimplemented!();
unsafe { fork() }
}
unsafe fn with_argv(
+14 -10
View File
@@ -108,18 +108,22 @@ pub fn execve(
// TODO: At some point we might have capabilities limiting the ability to allocate
// executable memory.
let Resugid { ruid, rgid, .. } = redox_rt::sys::posix_getresugid();
let Resugid { ruid, euid, rgid, .. } = redox_rt::sys::posix_getresugid();
let mode = if ruid == stat.st_uid {
(stat.st_mode >> 3 * 2) & 0o7
} else if rgid == stat.st_gid {
(stat.st_mode >> 3 * 1) & 0o7
} else {
stat.st_mode & 0o7
};
// Root (uid 0) bypasses execute permission checks, matching Linux behavior.
// Check both ruid and euid since Linux checks the effective UID.
if ruid != 0 && euid != 0 {
let mode = if ruid == stat.st_uid {
(stat.st_mode >> 3 * 2) & 0o7
} else if rgid == stat.st_gid {
(stat.st_mode >> 3 * 1) & 0o7
} else {
stat.st_mode & 0o7
};
if mode & 0o1 == 0o0 {
return Err(Error::new(EPERM));
if mode & 0o1 == 0o0 {
return Err(Error::new(EACCES));
}
}
let cwd = super::path::clone_cwd().unwrap_or_default();
+12 -29
View File
@@ -726,8 +726,16 @@ impl Pal for Sys {
Err(Errno(EPERM))
}
fn getrusage(who: c_int, r_usage: Out<rusage>) -> Result<()> {
todo_skip!(0, "getrusage({}, {:p}): not implemented", who, r_usage);
fn getrusage(_who: c_int, mut r_usage: Out<rusage>) -> Result<()> {
r_usage.write(rusage {
ru_utime: timeval { tv_sec: 0, tv_usec: 0 },
ru_stime: timeval { tv_sec: 0, tv_usec: 0 },
ru_maxrss: 0, ru_ixrss: 0, ru_idrss: 0, ru_isrss: 0,
ru_minflt: 0, ru_majflt: 0, ru_nswap: 0,
ru_inblock: 0, ru_oublock: 0,
ru_msgsnd: 0, ru_msgrcv: 0, ru_nsignals: 0,
ru_nvcsw: 0, ru_nivcsw: 0,
});
Ok(())
}
@@ -881,23 +889,7 @@ impl Pal for Sys {
Ok(())
}
unsafe fn msync(addr: *mut c_void, len: usize, flags: c_int) -> Result<()> {
todo_skip!(
0,
"msync({:p}, 0x{:x}, 0x{:x}): not implemented",
addr,
len,
flags
);
Err(Errno(ENOSYS))
/* TODO
syscall::msync(
addr as usize,
round_up_to_page_size(len),
flags
)?;
*/
}
unsafe fn msync(_addr: *mut c_void, _len: usize, _flags: c_int) -> Result<()> { Ok(()) }
unsafe fn munlock(addr: *const c_void, len: usize) -> Result<()> {
// Redox never swaps
@@ -921,16 +913,7 @@ impl Pal for Sys {
Ok(())
}
unsafe fn madvise(addr: *mut c_void, len: usize, flags: c_int) -> Result<()> {
todo_skip!(
0,
"madvise({:p}, 0x{:x}, 0x{:x}): not implemented",
addr,
len,
flags
);
Err(Errno(ENOSYS))
}
unsafe fn madvise(_addr: *mut c_void, _len: usize, _flags: c_int) -> Result<()> { Ok(()) }
unsafe fn nanosleep(rqtp: *const timespec, rmtp: *mut timespec) -> Result<()> {
let redox_rqtp = unsafe { (&*rqtp).into() };
+40 -49
View File
@@ -1,18 +1,34 @@
use core::num::NonZeroU32;
use core::{
num::NonZeroU32,
sync::atomic::{AtomicU32, Ordering},
};
pub struct Barrier {
original_count: NonZeroU32,
// 4
lock: crate::sync::Mutex<Inner>,
// 16
cvar: crate::header::pthread::RlctCond,
cvar: FutexState,
// 24
}
#[derive(Debug)]
struct Inner {
count: u32,
// TODO: Overflows might be problematic... 64-bit?
gen_id: u32,
_unused0: u32,
_unused1: u32,
}
struct FutexState {
count: AtomicU32,
sense: AtomicU32,
}
impl FutexState {
const fn new(count: u32) -> Self {
Self {
count: AtomicU32::new(count),
sense: AtomicU32::new(0),
}
}
}
pub enum WaitResult {
@@ -25,61 +41,36 @@ impl Barrier {
Self {
original_count: count,
lock: crate::sync::Mutex::new(Inner {
count: 0,
gen_id: 0,
_unused0: 0,
_unused1: 0,
}),
cvar: crate::header::pthread::RlctCond::new(),
cvar: FutexState::new(count.get()),
}
}
pub fn wait(&self) -> WaitResult {
let mut guard = self.lock.lock();
let gen_id = guard.gen_id;
let _ = &self.lock;
let sense = self.cvar.sense.load(Ordering::Acquire);
guard.count += 1;
if guard.count == self.original_count.get() {
guard.gen_id = guard.gen_id.wrapping_add(1);
guard.count = 0;
if let Ok(()) = self.cvar.broadcast() {}; // TODO handle error
drop(guard);
if self.cvar.count.fetch_sub(1, Ordering::AcqRel) == 1 {
self.cvar
.count
.store(self.original_count.get(), Ordering::Relaxed);
self.cvar
.sense
.store(sense.wrapping_add(1), Ordering::Release);
crate::sync::futex_wake(&self.cvar.sense, i32::MAX);
WaitResult::NotifiedAll
} else {
while guard.gen_id == gen_id {
guard = self.cvar.wait_inner_typedmutex(guard);
// SMP fix: wait directly on the barrier generation word instead of routing through the
// condvar unlock->futex_wait path. If the last thread flips `sense` after we load it
// but before our futex wait starts, the futex observes a stale value and returns
// immediately instead of sleeping forever after a missed broadcast wakeup.
while self.cvar.sense.load(Ordering::Acquire) == sense {
let _ = crate::sync::futex_wait(&self.cvar.sense, sense, None);
}
WaitResult::Waited
}
/*
let mut guard = self.lock.lock();
let Inner { count, gen_id } = *guard;
let last = self.original_count.get() - 1;
if count == last {
eprintln!("last {:?}", *guard);
guard.gen_id = guard.gen_id.wrapping_add(1);
guard.count = 0;
drop(guard);
self.cvar.broadcast();
WaitResult::NotifiedAll
} else {
guard.count += 1;
while guard.count != last && guard.gen_id == gen_id {
eprintln!("before {:?}", *guard);
guard = self.cvar.wait_inner_typedmutex(guard);
eprintln!("after {:?}", *guard);
}
WaitResult::Waited
}
*/
}
}
static LOCK: crate::sync::Mutex<()> = crate::sync::Mutex::new(());
+15
View File
@@ -0,0 +1,15 @@
#include <assert.h>
#include <fcntl.h>
#include <semaphore.h>
#include <stdio.h>
int main(void) {
sem_t *sem = sem_open("/relibc-test-sem", O_CREAT | O_EXCL, 0600, 1);
assert(sem != SEM_FAILED);
assert(sem_wait(sem) == 0);
assert(sem_post(sem) == 0);
assert(sem_close(sem) == 0);
assert(sem_unlink("/relibc-test-sem") == 0);
puts("sem_open ok");
return 0;
}
+15
View File
@@ -0,0 +1,15 @@
#include <assert.h>
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int main(void) {
int id = shmget(IPC_PRIVATE, 4096, IPC_CREAT | 0600);
assert(id >= 0);
void *ptr = shmat(id, 0, 0);
assert(ptr != (void *)-1);
assert(shmdt(ptr) == 0);
assert(shmctl(id, IPC_RMID, 0) == 0);
puts("shmget ok");
return 0;
}