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:
@@ -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();
|
||||
|
||||
@@ -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>.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user