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()) };
|
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 {
|
let Some(tcb) = (unsafe { Tcb::current() }) else {
|
||||||
ERROR.store(ERROR_NOT_SUPPORTED.as_ptr() as usize, Ordering::SeqCst);
|
ERROR.store(ERROR_NOT_SUPPORTED.as_ptr() as usize, Ordering::SeqCst);
|
||||||
return ptr::null_mut();
|
return ptr::null_mut();
|
||||||
|
|||||||
@@ -2,8 +2,9 @@
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
header::{
|
header::{
|
||||||
|
errno::EINVAL,
|
||||||
pthread::{PTHREAD_PROCESS_PRIVATE, PTHREAD_PROCESS_SHARED, RlctMutex, e},
|
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},
|
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
|
/// Upon success, returns `0`. Upon failure, an error number is returned to
|
||||||
/// indicated the error.
|
/// indicated the error.
|
||||||
///
|
///
|
||||||
/// # Implementation
|
|
||||||
/// Always successful, so will never return an error number.
|
|
||||||
///
|
|
||||||
/// # Safety
|
/// # Safety
|
||||||
/// It is undefined behaviour if `attr` is uninitialized.
|
/// It is undefined behaviour if `attr` is uninitialized.
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
@@ -292,9 +290,15 @@ pub unsafe extern "C" fn pthread_condattr_setclock(
|
|||||||
attr: *mut pthread_condattr_t,
|
attr: *mut pthread_condattr_t,
|
||||||
clock_id: clockid_t,
|
clock_id: clockid_t,
|
||||||
) -> c_int {
|
) -> c_int {
|
||||||
// TODO return EINVAL if clock_id is invalid or a CPU-time clock
|
// POSIX: only CLOCK_REALTIME and CLOCK_MONOTONIC are portable clocks for
|
||||||
(unsafe { *attr.cast::<RlctCondAttr>() }).clock = clock_id;
|
// condition variable timeouts. Any other clock_id must fail with EINVAL.
|
||||||
0
|
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>.
|
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/pthread_condattr_setpshared.html>.
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ use core::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
header::{errno::EINVAL, limits::PTHREAD_DESTRUCTOR_ITERATIONS},
|
header::{errno::{EAGAIN, EINVAL}, limits::PTHREAD_DESTRUCTOR_ITERATIONS},
|
||||||
sync::Mutex,
|
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 KEYS: Mutex<BTreeMap<pthread_key_t, Dtor>> = Mutex::new(BTreeMap::new());
|
||||||
static NEXTKEY: AtomicUsize = AtomicUsize::new(1);
|
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>.
|
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/pthread_getspecific.html>.
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
pub unsafe extern "C" fn pthread_getspecific(key: pthread_key_t) -> *mut c_void {
|
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,
|
key_ptr: *mut pthread_key_t,
|
||||||
destructor: Dtor,
|
destructor: Dtor,
|
||||||
) -> c_int {
|
) -> 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;
|
let key = NEXTKEY.fetch_add(1, Ordering::SeqCst) as pthread_key_t;
|
||||||
|
keys.insert(key, destructor);
|
||||||
// TODO
|
drop(keys);
|
||||||
//if key >= PTHREAD_KEYS_MAX {
|
|
||||||
//}
|
|
||||||
|
|
||||||
KEYS.lock().insert(key, destructor);
|
|
||||||
|
|
||||||
unsafe { key_ptr.write(key) };
|
unsafe { key_ptr.write(key) };
|
||||||
0
|
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>() };
|
let termios = unsafe { &mut *out.cast::<termios::termios>() };
|
||||||
dup_read(fd, "termios", termios)?;
|
dup_read(fd, "termios", termios)?;
|
||||||
}
|
}
|
||||||
// TODO: give these different behaviors
|
TCSETS => {
|
||||||
TCSETS | TCSETSW | TCSETSF => {
|
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) };
|
let termios = unsafe { &*(out as *const termios::termios) };
|
||||||
dup_write(fd, "termios", termios)?;
|
dup_write(fd, "termios", termios)?;
|
||||||
}
|
}
|
||||||
|
|||||||
+123
-10
@@ -46,8 +46,8 @@ use crate::{
|
|||||||
sys_mman::{MAP_ANONYMOUS, PROT_READ, PROT_WRITE},
|
sys_mman::{MAP_ANONYMOUS, PROT_READ, PROT_WRITE},
|
||||||
sys_random,
|
sys_random,
|
||||||
sys_resource::{
|
sys_resource::{
|
||||||
PRIO_PROCESS, RLIM_INFINITY, RLIMIT_NLIMITS, RLIMIT_NOFILE, rlimit, rusage,
|
PRIO_PROCESS, RLIM_INFINITY, RLIMIT_NLIMITS, RLIMIT_NOFILE, RUSAGE_BOTH,
|
||||||
setpriority,
|
RUSAGE_CHILDREN, RUSAGE_SELF, RUSAGE_THREAD, rlimit, rusage, setpriority,
|
||||||
},
|
},
|
||||||
sys_select::timeval,
|
sys_select::timeval,
|
||||||
sys_stat::{S_ISGID, S_ISUID, S_ISVTX, stat},
|
sys_stat::{S_ISGID, S_ISUID, S_ISVTX, stat},
|
||||||
@@ -107,6 +107,58 @@ const fn default_rlimits() -> [rlimit; RLIM_COUNT] {
|
|||||||
arr
|
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 epoll;
|
||||||
mod event;
|
mod event;
|
||||||
pub(crate) mod exec;
|
pub(crate) mod exec;
|
||||||
@@ -794,15 +846,76 @@ impl Pal for Sys {
|
|||||||
Ok(())
|
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 {
|
r_usage.write(rusage {
|
||||||
ru_utime: timeval { tv_sec: 0, tv_usec: 0 },
|
ru_utime: timeval {
|
||||||
ru_stime: timeval { tv_sec: 0, tv_usec: 0 },
|
tv_sec: stat.utime_sec as _,
|
||||||
ru_maxrss: 0, ru_ixrss: 0, ru_idrss: 0, ru_isrss: 0,
|
tv_usec: 0,
|
||||||
ru_minflt: 0, ru_majflt: 0, ru_nswap: 0,
|
},
|
||||||
ru_inblock: 0, ru_oublock: 0,
|
ru_stime: timeval {
|
||||||
ru_msgsnd: 0, ru_msgrcv: 0, ru_nsignals: 0,
|
tv_sec: stat.stime_sec as _,
|
||||||
ru_nvcsw: 0, ru_nivcsw: 0,
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user