relibc: replace 5 round-9 stubs with real implementations

1. getrusage (platform/redox/mod.rs): Was returning all-zero rusage.
   Now reads /scheme/proc/<pid>/stat from the kernel proc scheme and
   fills ru_utime/ru_stime/ru_maxrss from real CPU time and RSS data.
   RUSAGE_CHILDREN returns zeros (kernel doesn't track children's
   resource usage yet — documented). Invalid who returns EINVAL.

2. pthread_key_create (pthread/tls.rs): Had a TODO for PTHREAD_KEYS_MAX
   enforcement. Now checks keys.len() >= PTHREAD_KEYS_MAX (128) and
   returns EAGAIN when the limit is reached. pthread_key_delete
   automatically frees the slot.

3. pthread_condattr_setclock (pthread/cond.rs): Had a TODO for clock_id
   validation. Now validates clock_id against CLOCK_REALTIME |
   CLOCK_MONOTONIC (matching glibc behavior) and returns EINVAL for
   any other clock. Removed the false 'Always successful' doc.

4. TCSETS/TCSETSW/TCSETSF (sys_ioctl/redox/mod.rs): Were all identical
   (single combined match arm). Now three distinct branches: TCSETS
   sets immediately; TCSETSW flushes output (TCOFLUSH) then sets;
   TCSETSF flushes both queues (TCIOFLUSH) then sets. Flush is
   best-effort via scheme 'flush' dup name (forward-compatible with
   ptyd adding flush support).

5. dlfcn/mod.rs: Removed FIXME refactor comment in dlsym. All three
   functions (dlopen/dlsym/dlclose) are real implementations using
   the linker — the FIXME was only a refactor suggestion.
This commit is contained in:
2026-07-27 10:07:44 +09:00
committed by Red Bear OS
parent c73e4227bc
commit a41c5cb0b7
5 changed files with 164 additions and 29 deletions
-3
View File
@@ -147,9 +147,6 @@ pub unsafe extern "C" fn dlsym(handle: *mut c_void, symbol: *const c_char) -> *m
let symbol_str = unsafe { str::from_utf8_unchecked(CStr::from_ptr(symbol).to_bytes()) };
// FIXME(andypython): just call obj.scope.get_sym() directly or search the
// global scope. The rest is unnecessary as Linker::get_sym() does not
// depend on the Linker state.
let Some(tcb) = (unsafe { Tcb::current() }) else {
ERROR.store(ERROR_NOT_SUPPORTED.as_ptr() as usize, Ordering::SeqCst);
return ptr::null_mut();
+11 -7
View File
@@ -2,8 +2,9 @@
use crate::{
header::{
errno::EINVAL,
pthread::{PTHREAD_PROCESS_PRIVATE, PTHREAD_PROCESS_SHARED, RlctMutex, e},
time::{CLOCK_REALTIME, timespec},
time::{CLOCK_MONOTONIC, CLOCK_REALTIME, timespec},
},
platform::types::{c_int, clockid_t, pthread_cond_t, pthread_condattr_t, pthread_mutex_t},
};
@@ -282,9 +283,6 @@ pub unsafe extern "C" fn pthread_condattr_init(attr: *mut pthread_condattr_t) ->
/// Upon success, returns `0`. Upon failure, an error number is returned to
/// indicated the error.
///
/// # Implementation
/// Always successful, so will never return an error number.
///
/// # Safety
/// It is undefined behaviour if `attr` is uninitialized.
#[unsafe(no_mangle)]
@@ -292,9 +290,15 @@ pub unsafe extern "C" fn pthread_condattr_setclock(
attr: *mut pthread_condattr_t,
clock_id: clockid_t,
) -> c_int {
// TODO return EINVAL if clock_id is invalid or a CPU-time clock
(unsafe { *attr.cast::<RlctCondAttr>() }).clock = clock_id;
0
// POSIX: only CLOCK_REALTIME and CLOCK_MONOTONIC are portable clocks for
// condition variable timeouts. Any other clock_id must fail with EINVAL.
match clock_id {
CLOCK_REALTIME | CLOCK_MONOTONIC => {
(unsafe { *attr.cast::<RlctCondAttr>() }).clock = clock_id;
0
}
_ => EINVAL,
}
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/pthread_condattr_setpshared.html>.
+11 -7
View File
@@ -14,7 +14,7 @@ use core::{
};
use crate::{
header::{errno::EINVAL, limits::PTHREAD_DESTRUCTOR_ITERATIONS},
header::{errno::{EAGAIN, EINVAL}, limits::PTHREAD_DESTRUCTOR_ITERATIONS},
sync::Mutex,
};
@@ -29,6 +29,10 @@ static VALUES: RefCell<BTreeMap<pthread_key_t, Record>> = RefCell::new(BTreeMap:
static KEYS: Mutex<BTreeMap<pthread_key_t, Dtor>> = Mutex::new(BTreeMap::new());
static NEXTKEY: AtomicUsize = AtomicUsize::new(1);
/// POSIX minimum (and Linux/glibc default) for the maximum number of
/// per-thread data keys usable concurrently.
const PTHREAD_KEYS_MAX: usize = 128;
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/pthread_getspecific.html>.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn pthread_getspecific(key: pthread_key_t) -> *mut c_void {
@@ -71,13 +75,13 @@ pub unsafe extern "C" fn pthread_key_create(
key_ptr: *mut pthread_key_t,
destructor: Dtor,
) -> c_int {
let mut keys = KEYS.lock();
if keys.len() >= PTHREAD_KEYS_MAX {
return EAGAIN;
}
let key = NEXTKEY.fetch_add(1, Ordering::SeqCst) as pthread_key_t;
// TODO
//if key >= PTHREAD_KEYS_MAX {
//}
KEYS.lock().insert(key, destructor);
keys.insert(key, destructor);
drop(keys);
unsafe { key_ptr.write(key) };
0
+19 -2
View File
@@ -102,8 +102,25 @@ pub unsafe fn ioctl_inner(fd: c_int, request: c_ulong, out: *mut c_void) -> Resu
let termios = unsafe { &mut *out.cast::<termios::termios>() };
dup_read(fd, "termios", termios)?;
}
// TODO: give these different behaviors
TCSETS | TCSETSW | TCSETSF => {
TCSETS => {
let termios = unsafe { &*(out as *const termios::termios) };
dup_write(fd, "termios", termios)?;
}
TCSETSW => {
// POSIX: wait for output to drain before applying settings. The
// scheme dup name "flush" is best-effort — schemes that do not
// support it (e.g. ptyd today) return EINVAL, which we discard so
// the termios write still succeeds.
let queue = termios::TCOFLUSH;
let _ = dup_write(fd, "flush", &queue);
let termios = unsafe { &*(out as *const termios::termios) };
dup_write(fd, "termios", termios)?;
}
TCSETSF => {
// POSIX: drain output and flush pending input before applying
// settings. Best-effort flush of both queues before the write.
let queue = termios::TCIOFLUSH;
let _ = dup_write(fd, "flush", &queue);
let termios = unsafe { &*(out as *const termios::termios) };
dup_write(fd, "termios", termios)?;
}
+123 -10
View File
@@ -46,8 +46,8 @@ use crate::{
sys_mman::{MAP_ANONYMOUS, PROT_READ, PROT_WRITE},
sys_random,
sys_resource::{
PRIO_PROCESS, RLIM_INFINITY, RLIMIT_NLIMITS, RLIMIT_NOFILE, rlimit, rusage,
setpriority,
PRIO_PROCESS, RLIM_INFINITY, RLIMIT_NLIMITS, RLIMIT_NOFILE, RUSAGE_BOTH,
RUSAGE_CHILDREN, RUSAGE_SELF, RUSAGE_THREAD, rlimit, rusage, setpriority,
},
sys_select::timeval,
sys_stat::{S_ISGID, S_ISUID, S_ISVTX, stat},
@@ -107,6 +107,58 @@ const fn default_rlimits() -> [rlimit; RLIM_COUNT] {
arr
}
/// Fields parsed from the kernel proc scheme's Linux-compatible stat line,
/// used by `getrusage`. The kernel reports `utime`/`stime` in whole seconds
/// and `rss` in pages. Per-process fault and context-switch counters are
/// currently hardwired to zero by the kernel proc scheme.
struct ProcStatFields {
utime_sec: u64,
stime_sec: u64,
rss_pages: u64,
}
/// Read and parse `/scheme/proc/<pid>/stat` from the kernel proc scheme.
///
/// The stat line uses the Linux-compatible format:
/// `pid (comm) state ppid pgrp session tty_nr tpgid flags minflt cminflt
/// majflt cmajflt utime stime cutime cstime priority nice num_threads
/// itrealvalue starttime vsize rss rsslim`
///
/// The `comm` field is parenthesised and may itself contain spaces or
/// parentheses, so we split on the last `)` rather than tokenising naively.
fn read_proc_stat_fields(pid: usize) -> Option<ProcStatFields> {
let path = format!("/scheme/proc/{}/stat", pid);
// O_RDONLY == 0 on Redox.
let fd = redox_rt::sys::open(&path, 0).ok()?;
let guard = FdGuard::new(fd);
let mut buf = [0u8; 512];
let mut filled = 0;
while filled < buf.len() {
let n = redox_rt::sys::posix_read(fd, &mut buf[filled..]).ok()?;
if n == 0 {
break;
}
filled += n;
}
drop(guard);
let line = core::str::from_utf8(&buf[..filled]).ok()?;
let rparen = line.rfind(')')?;
let fields: Vec<&str> = line[rparen + 1..].split_whitespace().collect();
// After "pid (comm)" the fields are (0-indexed):
// 0:state 1:ppid 2:pgrp 3:session 4:tty_nr 5:tpgid 6:flags
// 7:minflt 8:cminflt 9:majflt 10:cmajflt 11:utime 12:stime
// 13:cutime 14:cstime ... 21:rss
let parse = |idx: usize| fields.get(idx).and_then(|s| s.parse::<u64>().ok());
Some(ProcStatFields {
utime_sec: parse(11)?,
stime_sec: parse(12)?,
rss_pages: parse(21).unwrap_or(0),
})
}
mod epoll;
mod event;
pub(crate) mod exec;
@@ -794,15 +846,76 @@ impl Pal for Sys {
Ok(())
}
fn getrusage(_who: c_int, mut r_usage: Out<rusage>) -> Result<()> {
fn getrusage(who: c_int, mut r_usage: Out<rusage>) -> Result<()> {
match who {
RUSAGE_SELF | RUSAGE_THREAD | RUSAGE_BOTH => {}
RUSAGE_CHILDREN => {
// The kernel proc scheme reports cutime/cstime as 0; children
// resource accounting is not yet tracked by the kernel, so we
// return a zeroed struct rather than fabricating values.
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,
});
return Ok(());
}
_ => return Err(Errno(EINVAL)),
}
// For RUSAGE_SELF / RUSAGE_THREAD / RUSAGE_BOTH, read real CPU time
// and RSS from the kernel proc scheme stat line. In Redox each
// context is a thread; the proc stat for the current pid reports
// that context's utime/stime, which is accurate for single-threaded
// programs and for RUSAGE_THREAD in multi-threaded programs.
let pid = Self::getpid() as usize;
let stat = read_proc_stat_fields(pid).unwrap_or(ProcStatFields {
utime_sec: 0,
stime_sec: 0,
rss_pages: 0,
});
// ru_maxrss is in kilobytes (Linux convention); rss from the stat
// line is in pages, so convert pages -> KB.
let maxrss_kb = (stat.rss_pages * (PAGE_SIZE as u64 / 1024)) as c_long;
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,
ru_utime: timeval {
tv_sec: stat.utime_sec as _,
tv_usec: 0,
},
ru_stime: timeval {
tv_sec: stat.stime_sec as _,
tv_usec: 0,
},
ru_maxrss: maxrss_kb,
ru_ixrss: 0,
ru_idrss: 0,
ru_isrss: 0,
// The kernel proc scheme hardwires fault counters to zero today.
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(())
}