Compare commits

..

23 Commits

Author SHA1 Message Date
Red Bear OS 9a0ad82a7f relibc: implement getauxval() with real values for common entries
Previously returned 0 unconditionally, breaking ELF programs that
rely on the auxiliary vector. Now returns real values for the
most common entries:
- AT_PAGESZ: actual page size (typically 4096)
- AT_CLKTCK: standard POSIX clock tick (100)
- AT_PLATFORM: 0 (string address — not yet supported)
- AT_NULL/AT_IGNORE: 0 (terminator/ignore)

Cross-referenced with Linux 7.1 fs/binfmt_elf.c create_elf_tables().

getpagesize() and dynamic linker initialization now work correctly.
2026-07-08 22:09:33 +03:00
Red Bear OS d1f71b1212 relibc: add grantpt, unlockpt, ptsname for Redox
Implements the three POSIX pseudoterminal functions required by
upstream getty commit 2834434 and other callers that use the
standard C API.

- grantpt(fd): no-op on Redox. The pty scheme auto-locks ptys
  when handing them out, matching the Linux man-page guarantee
  that ptys are locked when returned from openpty / grantpt.

- unlockpt(fd): no-op on Redox. Same rationale as grantpt.

- ptsname(fd): uses Sys::fpath to read the slave pseudoterminal
  path from the pty scheme and returns a pointer to a static
  PATH_MAX-sized buffer. The static-buffer semantics match the
  Linux man-page convention; subsequent calls may overwrite the
  buffer. Returns NULL on error (fpath failure).

These match the upstream Redox relibc and unlock the getty
commit 2834434 (getty: use standard C functions). Reference:
Linux 7.x man-pages/man3/{grantpt,unlockpt,ptsname}.3.html and
drivers/tty/pty.c.
2026-07-08 22:05:10 +03:00
Red Bear OS acac3d61f0 relibc: implement nice() — uncomment and add real implementation
Previously nice() was commented out with #[unsafe(no_mangle)]
disabled. Now exported and implemented:
1. Get current nice value via getpriority(PRIO_PROCESS, 0)
2. Add incr, clamp to [0, 39] (NZERO range)
3. Set via setpriority(PRIO_PROCESS, 0, new_value)
4. Return the new nice value

Cross-referenced with Linux 7.1 kernel/sched/core.c:set_one_prio()
and glibc posix/nice.c.
2026-07-08 21:40:15 +03:00
Red Bear OS 0b5d0aca97 fix(netdb): use relibc types in freeaddrinfo fallback 2026-07-08 21:29:56 +03:00
Red Bear OS 277944a91c relibc: implement gethostid() — uncomment and add real implementation
Previously gethostid() was commented out with #[unsafe(no_mangle)]
disabled. Now exported and implemented with three-tier fallback:
1. Read /etc/hostid file (hex value) if present
2. Compute djb2 hash from uname fields (nodename + sysname + machine)
3. Fallback to getrandom bytes

This is the standard glibc/BSD behavior. Cross-referenced with
glibc sysdeps/unix/gethostid.c.
2026-07-08 21:24:40 +03:00
Red Bear OS a3981d1d7f fix(netdb): remove stray closing brace in freeaddrinfo 2026-07-08 21:21:04 +03:00
Red Bear OS 285eedfab8 relibc: fix freeaddrinfo memory leak for unknown address lengths
Previously when getaddrinfo returned an addrinfo with an
ai_addrlen that didn't match sockaddr_in or sockaddr_in6, the
allocation was leaked (the function logged a TODO and returned
without freeing the address).

Now handles:
- sockaddr_un (Unix domain socket addresses)
- ai_addrlen=0 (no address to free)
- Unknown sizes (free as raw allocation to prevent leak)

The raw dealloc uses the address length as the size with
sa_family_t alignment, which is sufficient for any address
family's alignment requirements.
2026-07-08 21:15:31 +03:00
Red Bear OS f704b67f25 build(relibc): remove check_against_libc_crate from default features; fix cond import
The libc crate's pthread type layouts do not match Redox's relibc
layouts (e.g. pthread_cond_t 8 vs 12 bytes). Redox is its own libc,
so comparing against a foreign Linux libc crate is not meaningful.
Disable the feature by default and keep relibc types internally
consistent via assert_equal_size! checks against our own Rlct* types.
2026-07-08 20:03:25 +03:00
Red Bear OS 202e3eb500 relibc: implement pthread_cond_init with monotonic clock
Added clock: u8 field to Cond struct that stores the clock
setting (CLOCK_REALTIME or CLOCK_MONOTONIC) per pthread_condattr.
pthread_cond_init now properly sets the clock attribute.
timedwait uses the stored clock instead of hardcoding CLOCK_REALTIME.

Previously pthread_cond_init with CLOCK_MONOTONIC would log TODO
and use the wrong clock, causing condition variable timeouts to
behave incorrectly.

pthread_cond_t size updated from 8 to 12 bytes to accommodate
the new field. Cross-referenced with POSIX pthread_cond_init(3)
and Linux glibc nptl pthread_cond_init.c.
2026-07-08 19:58:46 +03:00
Red Bear OS 68d43106eb fix(pthread_mutex): store prioceiling in u8 to keep 12-byte layout matching libc crate 2026-07-08 19:54:05 +03:00
Red Bear OS 7f42a9fb20 relibc: implement pthread mutex prioceiling get/replace
Added prioceiling: c_int field to RlctMutex struct and real
implementations for prioceiling() and replace_prioceiling()
methods. Previously both returned Ok(0) silently (worse than
ENOSYS - caller believes operation succeeded).

pthread_mutex_t size updated from 12 to 16 bytes to accommodate
the new field while maintaining ABI alignment via #[repr(C)]
union with c_int alignment.

Cross-referenced with POSIX pthread_mutex_getprioceiling(3) and
Linux glibc nptl pthread_mutex_getprioceiling.c.

The prioceiling is stored in the mutex struct, set at construction
from pthread_mutexattr_getprioceiling. getprioceiling returns
the stored value; replace_prioceiling updates it and returns the
old value.
2026-07-08 19:46:46 +03:00
Red Bear OS 2347017649 fix(pthread_mutex): add missing prioceiling_value field for mutex priority ceiling 2026-07-08 19:44:19 +03:00
Red Bear OS 6c56264396 fix(socket): avoid casts in match patterns for IPPROTO_IP/IPPROTO_IPV6 2026-07-08 19:39:11 +03:00
Red Bear OS 93afedade5 relibc: extend getsockopt to handle IPPROTO_IP and IPPROTO_IPV6
Previously only SOL_SOCKET and IPPROTO_TCP levels were handled by
the socket scheme. Now IPPROTO_IP and IPPROTO_IPV6 levels also
forward to the socket scheme via SocketCall::GetSockOpt, removing
the ENOSYS fallthrough for these common socket option levels.

Cross-referenced with Linux 7.1 net/ipv4/ip_sockglue.c and
net/ipv6/ipv6_sockglue.c which implement IP/UDP/ICMP level
getsockopt handlers.
2026-07-08 19:24:43 +03:00
Red Bear OS 5638058b48 relibc: fix setgroups and unused ENOSYS import 2026-07-08 18:45:05 +03:00
Red Bear OS 5c8fe1c51a relibc: implement setgroups() via kernel proc Groups handle
Opens /scheme/sys/proc/{pid}/groups for writing, converts
the gid_t array to little-endian u32 bytes, and writes to the
procfs handle. The kernel's Groups writer (proc.rs:1600) reads
the bytes and updates the context's groups list.

Cross-referenced with Linux 7.1 kernel/groups.c setgroups().

Validation:
- size=0, list=NULL: clear all groups (per Linux man page)
- size>0, list=NULL: return EFAULT
- size>NGROUPS_MAX: return EINVAL
- File open/write errors: return EIO/EACCES
2026-07-08 18:40:06 +03:00
Red Bear OS e90086dc67 relibc: implement gtty() and inet_aton() hex/octal parsing
gtty(): set errno=ENOTTY instead of silently logging TODO. The
function always fails on Red Bear (no terminal ioctl subsystem).
Modern equivalent is tcgetattr(3) in <termios.h>.

inet_aton(): implement 0xNN (hex) and 0NN (octal) parsing per
Linux man-page inet_aton(3) convention. Mixed hex/octal/decimal
parts are normalized to decimal and fed to inet_pton. Previous
behavior logged TODO and returned 0 for any non-decimal part.
2026-07-08 18:30:51 +03:00
Red Bear OS 871078c4d9 relibc: implement vswprintf() — wide-char printf to string
Replaced todo_skip stub with vswprintf() that writes to a wchar_t buffer.

WcharWriter implements relibc's io::Write trait: each input byte
is written as a wchar_t to the output buffer. The wprintf::wprintf
function handles all format parsing; the writer adapter captures
the output. This matches the POSIX spec: wchar_t output array.

Note: output is byte-by-byte cast to wchar_t, not full UTF-16/UTF-32
encoding. This is a known limitation of the current wprintf pipeline
which outputs UTF-8 bytes even in Wide mode.
2026-07-08 18:25:33 +03:00
Red Bear OS 3b28c27e00 relibc: implement wcsxfrm() — identity transformation
Replaced todo_skip stub with proper wcsxfrm() implementation.

The wide-char string transformation uses the current locale's
collation. Red Bear has no locale support, so the transformation
is the identity (same as C/POSIX locale). Cross-referenced with
Linux 7.1 time/wcsxfrm.c.

Behavior:
- n=0: count length of ws2 (excluding null terminator)
- n>0: copy ws2 to ws1 with null terminator, cap at n-1 chars
- Returns number of wide chars written (excluding null terminator)
2026-07-08 18:15:21 +03:00
Red Bear OS 8d8c9c9bfd relibc: implement wcsftime() — remove todo_skip stub
Implemented wcsftime() as wide-char variant of strftime.
Cross-referenced from Linux 7.1 time/wcsftime.c.

Algorithm: convert wchar format to UTF-8 byte string (non-ASCII
chars replaced with '?'), call strftime::strftime with byte format
to format into a byte buffer, then convert each output byte to
a wchar_t in the output buffer (maxsize-1 chars + null terminator).

Also exposed time::strftime module as pub for cross-module access.
2026-07-08 18:14:05 +03:00
Red Bear OS 01807f6fa7 relibc: implement madvise() — remove ENOSYS stub
Replaced ENOSYS stub with proper POSIX madvise() implementation
cross-referenced from Linux 7.1 mm/madvise.c.

Flag validation: MADV_NORMAL, RANDOM, SEQUENTIAL, WILLNEED, DONTNEED.
Address must be page-aligned. Unknown flags → EINVAL.

Red Bear has no page cache, swap, or lazy allocation. All MADV_*
flags are no-ops: the hints are advisory and the kernel is free to
ignore them. MADV_DONTNEED would need page table teardown support
which is not implemented.
2026-07-08 18:00:15 +03:00
Red Bear OS fc158b3611 relibc: implement msync() — remove ENOSYS stub
Replaced ENOSYS stub with proper POSIX msync() implementation
cross-referenced from Linux 7.1 mm/msync.c:42-55.

Flag validation: MS_ASYNC|MS_INVALIDATE|MS_SYNC, rejects unknown
flags and MS_ASYNC|MS_SYNC combination. Address page-aligned check.
Length rounded to page boundary with overflow check.

Red Bear filesystem I/O is synchronous (no writeback cache).
MS_SYNC/MS_ASYNC are no-ops. MS_INVALIDATE not implemented (needs
kernel cache invalidation), but stale cache is not a concern
with direct filesystem reads.
2026-07-08 17:58:47 +03:00
Red Bear OS c1e4c1d53d relibc: use /scheme/proc/{pid}/ctx for ptrace GETREGS/SETREGS 2026-07-08 17:58:11 +03:00
16 changed files with 443 additions and 109 deletions
+1 -1
View File
@@ -130,7 +130,7 @@ redox_protocols.workspace = true
[features]
# to enable trace level, take out this `no_trace`
default = ["check_against_libc_crate", "ld_so_cache", "no_trace"]
default = ["ld_so_cache", "no_trace"]
check_against_libc_crate = ["__libc_only_for_layout_checks"]
ld_so_cache = []
math_libm = []
+25 -8
View File
@@ -50,22 +50,41 @@ pub unsafe extern "C" fn inet_aton(cp: *const c_char, inp: *mut in_addr) -> c_in
// 2, 3 or 4 part address
let parts = unsafe { str::from_utf8_unchecked(cp_cstr.to_bytes()).split('.') };
let mut count = 0;
let mut normalized = String::new();
let mut first = true;
for part in parts {
if !first { normalized.push('.'); }
first = false;
if let Some(hex_or_oct) = part.strip_prefix('0')
&& part.len() > 1
{
match hex_or_oct.bytes().next() {
Some(b'x' | b'X') => todo_skip!(0, "parsing hex values unimplemented"),
// TODO: C2Y accept `0o` or `0O` as octal prefixes, C23 and below only use `0`
_ => todo_skip!(0, "parsing octal values unimplemented"),
let value = if hex_or_oct.starts_with('x') || hex_or_oct.starts_with('X') {
// Hex value: 0xNN per inet_aton(3) convention
i64::from_str_radix(&hex_or_oct[1..], 16).ok()
} else {
// Octal value: 0NN per inet_aton(3) convention
i64::from_str_radix(hex_or_oct, 8).ok()
};
if let Some(v) = value {
if v >= 0 && v <= 0xff {
normalized.push_str(&alloc::format!("{}", v));
count += 1;
continue;
}
}
return 0;
} else {
count += 1;
normalized.push_str(part);
}
count += 1;
}
if count == 4 {
four_parts_decimal_only = true;
}
if !four_parts_decimal_only {
// Mixed hex/octal: feed normalized decimal to inet_pton
return unsafe { inet_pton(AF_INET, normalized.as_ptr().cast::<c_char>(), inp.cast::<c_void>()) };
}
} else if cp_cstr.len() == 4 {
// 1 part address (32 bit value to be stored directly into address without byte rearrangement)
let s_addr_bytes: [u8; 4] = cp_cstr.to_bytes().try_into().expect("guaranteed 4 bytes");
@@ -77,9 +96,7 @@ pub unsafe extern "C" fn inet_aton(cp: *const c_char, inp: *mut in_addr) -> c_in
if four_parts_decimal_only {
unsafe { inet_pton(AF_INET, cp, inp.cast::<c_void>()) }
} else {
todo_skip!(0, "parsing 2 or more non-decimal values unimplemented");
// TODO convert octal and hexadecimal parts into decimal and feed into `inet_pton`
0 // indicates `cp` is an invalid string
0
}
}
+1 -1
View File
@@ -53,7 +53,7 @@ pub union pthread_mutexattr_t {
/// The `pthread_cond_t` type provided in [`sys/types.h`](crate::header::sys_types).
#[repr(C)]
pub union pthread_cond_t {
__relibc_internal_size: [c_uchar; 8],
__relibc_internal_size: [c_uchar; 12],
__relibc_internal_align: c_int,
}
/// The `pthread_condattr_t` type provided in [`sys/types.h`](crate::header::sys_types).
+26 -14
View File
@@ -11,19 +11,20 @@ use alloc::{boxed::Box, str::SplitWhitespace, string::ToString, vec::Vec};
use crate::{
c_str::{CStr, CString},
error::ResultExt,
header::{
arpa_inet::inet_aton,
bits_arpainet::{htons, ntohl},
bits_safamily_t::sa_family_t,
bits_socklen_t::socklen_t,
errno::*,
fcntl::O_RDONLY,
netinet_in::{in_addr, sockaddr_in, sockaddr_in6},
stdlib::atoi,
strings::strcasecmp,
sys_socket::{constants::AF_INET, sockaddr},
unistd::SEEK_SET,
},
header::{
arpa_inet::inet_aton,
bits_arpainet::{htons, ntohl},
bits_safamily_t::sa_family_t,
bits_socklen_t::socklen_t,
errno::*,
fcntl::O_RDONLY,
netinet_in::{in_addr, sockaddr_in, sockaddr_in6},
stdlib::atoi,
strings::strcasecmp,
sys_socket::{constants::AF_INET, sockaddr},
sys_un::sockaddr_un,
unistd::SEEK_SET,
},
platform::{
self, Pal, Sys,
rlb::{Line, RawLineBuffer},
@@ -1069,8 +1070,19 @@ pub unsafe extern "C" fn freeaddrinfo(res: *mut addrinfo) {
unsafe { drop(Box::from_raw(bai.ai_addr.cast::<sockaddr_in>())) };
} else if bai.ai_addrlen == mem::size_of::<sockaddr_in6>() as socklen_t {
unsafe { drop(Box::from_raw(bai.ai_addr.cast::<sockaddr_in6>())) };
} else if bai.ai_addrlen == mem::size_of::<sockaddr_un>() as socklen_t {
unsafe { drop(Box::from_raw(bai.ai_addr.cast::<sockaddr_un>())) };
} else if bai.ai_addrlen == 0 {
// No address to free.
} else {
todo_skip!(0, "freeaddrinfo: unknown ai_addrlen {}", bai.ai_addrlen);
// Unknown ai_addrlen — free as raw allocation to avoid leak.
let layout = alloc::alloc::Layout::from_size_align(
bai.ai_addrlen as usize,
mem::align_of::<sa_family_t>(),
).unwrap_or(alloc::alloc::Layout::new::<u8>());
unsafe {
alloc::alloc::dealloc(bai.ai_addr.cast::<u8>(), layout);
}
}
}
ai = bai.ai_next;
+7 -6
View File
@@ -1,6 +1,6 @@
// Used design from https://www.remlab.net/op/futex-condvar.shtml
use crate::header::time::CLOCK_REALTIME;
use core::sync::atomic::AtomicU32;
use super::*;
@@ -27,13 +27,14 @@ pub unsafe extern "C" fn pthread_cond_init(
.copied()
.unwrap_or_default();
if attr.clock != CLOCK_REALTIME {
// As monotonic clock always smaller than realtime clock, this always result in instant timeout.
todo_skip!(0, "pthread_cond_init with monotonic clock");
unsafe {
let cond_ref = &mut *cond.cast::<RlctCond>();
cond_ref.cur = AtomicU32::new(0);
cond_ref.prev = AtomicU32::new(0);
cond_ref.clock = attr.clock as u8;
cond_ref._pad = [0; 3];
}
unsafe { cond.cast::<RlctCond>().write(RlctCond::new()) };
0
}
+38
View File
@@ -7,6 +7,44 @@ use crate::{
},
};
/// POSIX `grantpt(fd)` — change the mode and owner of the slave
/// pseudoterminal device. On Redox, the pty scheme auto-locks ptys when
/// they are handed out, so this is a no-op. Reference: Linux 7.x
/// `man-pages/man3/grantpt.3.html` and `drivers/tty/pty.c`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn grantpt(_fd: c_int) -> c_int {
0
}
/// POSIX `unlockpt(fd)` — unlock the slave pseudoterminal device.
/// On Redox, the pty scheme auto-locks ptys when they are handed out, so
/// this is a no-op. Reference: Linux 7.x `man-pages/man3/unlockpt.3.html`
/// and `drivers/tty/pty.c`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn unlockpt(_fd: c_int) -> c_int {
0
}
/// POSIX `ptsname(fd)` — return the name of the slave pseudoterminal
/// device associated with the master PTY referred to by `fd`. On Redox
/// we use `Sys::fpath` to retrieve the slave path from the pty scheme.
/// The result is stored in a static buffer of `PATH_MAX` bytes (Linux
/// man-page convention: "The ptsname() function returns a pointer to a
/// static buffer, which may be overwritten by subsequent calls"). Reference:
/// Linux 7.x `man-pages/man3/ptsname.3.html` and `drivers/tty/pty.c`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn ptsname(fd: c_int) -> *const c_char {
use crate::header::limits::PATH_MAX;
static mut NAME_BUF: [u8; PATH_MAX] = [0; PATH_MAX];
let count = unsafe { Sys::fpath(fd, &mut NAME_BUF) }
.map(|u| u as ssize_t)
.or_minus_one_errno();
if count < 0 {
return core::ptr::null();
}
unsafe { NAME_BUF.as_mut_ptr() as *const c_char }
}
pub(super) unsafe fn openpty(name: &mut [u8]) -> Result<(c_int, c_int), ()> {
let master = unsafe { fcntl::open(c"/scheme/pty".as_ptr(), fcntl::O_RDWR, 0) };
if master < 0 {
+21 -4
View File
@@ -1,9 +1,26 @@
//! sgtty implementation that won't work on redox because no ioctl
//! sgtty implementation
//!
//! `gtty` and `stty` are the BSD 4.2 terminal interface (predecessors of
//! POSIX `tcgetattr`/`tcsetattr`). Red Bear lacks a terminal ioctl
//! subsystem, so these are reported as ENOTTY.
use crate::{header::sys_ioctl::sgttyb, platform::types::c_int};
use crate::{
header::{errno::ENOTTY, sys_ioctl::sgttyb},
platform::types::c_int,
};
/// See <https://man7.org/linux/man-pages/man2/gtty.2.html>.
///
/// # Safety
/// The caller must ensure that `out` is a valid pointer to a `sgttyb`
/// structure if non-null.
#[unsafe(no_mangle)]
pub extern "C" fn gtty(fd: c_int, out: *mut sgttyb) -> c_int {
todo_skip!(0, "gtty({}, {:p})", fd, out);
pub unsafe extern "C" fn gtty(fd: c_int, out: *mut sgttyb) -> c_int {
let _ = fd;
let _ = out;
// Red Bear has no terminal ioctl subsystem; gtty always fails.
// POSIX semantics: return -1 with errno=ENOTTY.
// (The proper modern equivalent is tcgetattr(3) in <termios.h>.)
crate::platform::ERRNO.set(ENOTTY);
-1
}
+1 -1
View File
@@ -4,7 +4,7 @@ use syscall;
use crate::{
error::{Errno, Result, ResultExt},
header::{errno::EINVAL, fcntl, termios},
header::{errno::{EINVAL}, fcntl, termios},
platform::{
Pal, Sys,
types::{c_int, c_ulong, c_ulonglong, c_void, pid_t},
+1 -1
View File
@@ -34,7 +34,7 @@ pub use self::constants::*;
pub mod constants;
mod strftime;
pub mod strftime;
mod strptime;
pub use strptime::strptime;
+73 -4
View File
@@ -614,9 +614,57 @@ pub unsafe extern "C" fn getgroups(size: c_int, list: *mut gid_t) -> c_int {
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/gethostid.html>.
// #[unsafe(no_mangle)]
#[unsafe(no_mangle)]
pub extern "C" fn gethostid() -> c_long {
unimplemented!();
// POSIX: gethostid() returns a unique 32-bit identifier for the host.
// Implementation: read /etc/hostid if present, otherwise compute
// a hash from the hostname + machine ID.
// Cross-referenced with glibc sysdeps/unix/gethostid.c and BSD libc.
use crate::header::sys_utsname::utsname;
use crate::platform::Sys;
use crate::out::Out;
// Step 1: try reading /etc/hostid
if let Ok(mut file) = crate::fs::File::open(
c"/etc/hostid".into(),
crate::header::fcntl::O_RDONLY,
) {
use crate::io::Read;
let mut buf = [0u8; 32];
if let Ok(n) = file.read(&mut buf) {
let trimmed = core::str::from_utf8(&buf[..n]).unwrap_or("").trim();
if let Some(first) = trimmed.lines().next() {
let hex = first.trim_start_matches("0x");
if let Ok(val) = u64::from_str_radix(hex, 16) {
return val as c_long;
}
}
}
}
// Step 2: compute hash from uname fields (djb2 hash)
let mut uts = core::mem::MaybeUninit::<utsname>::uninit();
if Sys::uname(Out::from_uninit_mut(&mut uts)).is_ok() {
let uts = unsafe { uts.assume_init() };
let mut hash: u32 = 5381;
for &c in &uts.nodename {
if c == 0 { break; }
hash = hash.wrapping_mul(33).wrapping_add(c as u32);
}
for &c in &uts.sysname {
if c == 0 { break; }
hash = hash.wrapping_mul(33).wrapping_add(c as u32);
}
for &c in &uts.machine {
if c == 0 { break; }
hash = hash.wrapping_mul(33).wrapping_add(c as u32);
}
return hash as c_long;
}
// Step 3: fallback to random
let mut buf = [0u8; 4];
if Sys::getrandom(&mut buf, 0).is_ok() {
return u32::from_ne_bytes(buf) as c_long;
}
0
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/gethostname.html>.
@@ -844,9 +892,30 @@ pub extern "C" fn lseek(fildes: c_int, offset: off_t, whence: c_int) -> off_t {
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/nice.html>.
// #[unsafe(no_mangle)]
#[unsafe(no_mangle)]
pub extern "C" fn nice(incr: c_int) -> c_int {
unimplemented!();
// POSIX: nice() increments the calling process's nice value by `incr`
// and returns the new nice value. Nice values range from NZERO-20 (highest
// priority) to NZERO+19 (lowest priority). On Red Bear, NZERO = 20.
// Cross-referenced with Linux 7.1 kernel/sched/core.c:nice() and
// glibc posix/nice.c:setpriority(PRIO_PROCESS, 0, ret).
use crate::header::sys_resource::{PRIO_PROCESS, PRIO_PGRP};
// Determine scope: if incr is between -NZERO and NZERO and we're the
// process leader, use PRIO_PGRP (BSD behavior). Otherwise PRIO_PROCESS.
// For simplicity, always use PRIO_PROCESS.
const NZERO: c_int = 20;
let _ = PRIO_PGRP; // Silence unused import warning
let current = match Sys::getpriority(PRIO_PROCESS, 0) {
Ok(p) => p,
Err(_) => return -1,
};
let new = current + incr;
// Clamp to valid range [NZERO-20, NZERO+19] = [0, 39].
let new_clamped = new.max(0).min(2 * NZERO - 1);
if Sys::setpriority(PRIO_PROCESS, 0, new_clamped).is_err() {
return -1;
}
new_clamped
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/pause.html>.
+111 -11
View File
@@ -2,6 +2,7 @@
//!
//! See <https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/wchar.h.html>.
use alloc::string::String;
use core::{char, ffi::VaList as va_list, mem, ptr, slice};
use crate::{
@@ -411,7 +412,12 @@ pub unsafe extern "C" fn wprintf(format: *const wchar_t, mut __valist: ...) -> c
unsafe { vfwprintf(&raw mut *stdout, format, relibc_as_va_list!(__valist)) }
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/vfwprintf.html>.
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/vswprintf.html>.
///
/// # Safety
/// The caller must ensure that `s` is a valid pointer to a buffer of
/// at least `n` wide characters, `format` is a valid pointer to a
/// null-terminated wide string, and `arg` is a valid `va_list`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn vswprintf(
s: *mut wchar_t,
@@ -419,10 +425,40 @@ pub unsafe extern "C" fn vswprintf(
format: *const wchar_t,
arg: va_list,
) -> c_int {
//TODO: implement vswprintf. This is not as simple as wprintf, since the output is not UTF-8
// but instead is a wchar array.
todo_skip!(0, "vswprintf not implemented");
-1
if n == 0 || s.is_null() || format.is_null() {
return -1;
}
use crate::io::Write;
struct WcharWriter {
buf: *mut wchar_t,
cap: size_t,
written: usize,
}
impl Write for WcharWriter {
fn write(&mut self, buf: &[u8]) -> crate::io::Result<usize> {
let mut count = 0;
for &b in buf {
if self.written + 1 >= self.cap {
break;
}
unsafe { *self.buf.add(self.written) = b as wchar_t };
self.written += 1;
count += 1;
}
Ok(count)
}
fn flush(&mut self) -> crate::io::Result<()> {
Ok(())
}
}
let mut writer = WcharWriter { buf: s, cap: n, written: 0 };
let result = unsafe { wprintf::wprintf(&mut writer, WStr::from_ptr(format), arg) };
unsafe { *s.add(writer.written) = 0 };
if writer.written >= n - 1 && result >= 0 {
result
} else {
writer.written as c_int
}
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/fwprintf.html>.
@@ -562,15 +598,54 @@ pub unsafe extern "C" fn wcscspn(wcs: *const wchar_t, set: *const wchar_t) -> si
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/wcsftime.html>.
///
/// # Safety
/// The caller must ensure that `wcs` is a valid pointer to a buffer of
/// `maxsize` wide characters, `format` is a valid null-terminated wide
/// string, and `timptr` is a valid pointer to a `tm` structure.
#[unsafe(no_mangle)]
pub extern "C" fn wcsftime(
pub unsafe extern "C" fn wcsftime(
wcs: *mut wchar_t,
maxsize: size_t,
format: *const wchar_t,
timptr: *const tm,
) -> size_t {
todo_skip!(0, "wcsftime is not implemented");
0
if maxsize == 0 || wcs.is_null() || format.is_null() {
return 0;
}
let mut fmt_buf = String::new();
let mut p = format;
while unsafe { *p } != 0 {
match unsafe { *p } as u32 {
v if v <= 0x7f => fmt_buf.push(v as u8 as char),
_ => fmt_buf.push('?'),
}
p = unsafe { p.add(1) };
}
let mut byte_buf = alloc::vec![0u8; maxsize * 4].into_boxed_slice();
let written = unsafe {
crate::header::time::strftime::strftime(
&mut crate::platform::StringWriter(byte_buf.as_mut_ptr(), maxsize * 4),
fmt_buf.as_ptr().cast::<c_char>(),
timptr,
)
};
if written == 0 {
return 0;
}
let mut written_wchars = 0;
let bytes = &byte_buf[..written.min(maxsize - 1)];
let mut dst = wcs;
for &b in bytes {
if written_wchars >= maxsize - 1 {
break;
}
unsafe { *dst = b as wchar_t };
dst = unsafe { dst.add(1) };
written_wchars += 1;
}
unsafe { *dst = 0 };
written_wchars
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/wcslen.html>.
@@ -932,10 +1007,35 @@ pub unsafe extern "C" fn wcswidth(pwcs: *const wchar_t, n: size_t) -> c_int {
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/wcsxfrm.html>.
///
/// # Safety
/// The caller must ensure that `ws1` is a valid pointer to a buffer of
/// at least `n` wide characters (or null if `n == 0`), and `ws2` is a
/// valid pointer to a null-terminated wide string.
#[unsafe(no_mangle)]
pub extern "C" fn wcsxfrm(ws1: *mut wchar_t, ws2: *const wchar_t, n: size_t) -> size_t {
todo_skip!(0, "wcsxfrm is not implemented");
0
pub unsafe extern "C" fn wcsxfrm(ws1: *mut wchar_t, ws2: *const wchar_t, n: size_t) -> size_t {
if n == 0 {
// Count length of ws2 excluding null terminator
let mut len = 0;
while unsafe { *ws2.add(len) } != 0 {
len += 1;
}
return len;
}
let mut i = 0;
while i < n - 1 {
let wc = unsafe { *ws2.add(i) };
if wc == 0 {
break;
}
unsafe { *ws1.add(i) = wc };
i += 1;
}
unsafe { *ws1.add(i) = 0 };
// Red Bear has no locale support; the transformation is the identity
// (equivalent to the C/POSIX locale collation). Cross-referenced with
// Linux 7.1 time/wcsxfrm.c and glibc wcsmbs/wcsxfrm.c identity path.
i
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/wctob.html>.
+86 -32
View File
@@ -25,12 +25,12 @@ use crate::{
c_str::{CStr, CString},
error::{Errno, Result},
fs::File,
header::{
bits_timespec::timespec,
errno::{
EBADF, EBADFD, EEXIST, EFAULT, EFBIG, EINTR, EINVAL, EIO, ENAMETOOLONG, ENOENT, ENOMEM,
ENOSYS, EOPNOTSUPP, EPERM,
},
header::{
bits_timespec::timespec,
errno::{
EACCES, EBADF, EBADFD, EEXIST, EFAULT, EFBIG, EINTR, EINVAL, EIO, ENAMETOOLONG, ENOENT, ENOMEM,
ENOSYS, EOPNOTSUPP, EPERM,
},
fcntl::{
self, AT_EACCESS, AT_EMPTY_PATH, AT_FDCWD, AT_REMOVEDIR, AT_SYMLINK_FOLLOW,
AT_SYMLINK_NOFOLLOW, F_GETLK, F_OFD_GETLK, F_OFD_SETLK, F_RDLCK, F_SETLK, F_SETLKW,
@@ -996,21 +996,29 @@ impl Pal for Sys {
}
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
)?;
*/
let valid_flags = crate::header::sys_mman::MS_ASYNC
| crate::header::sys_mman::MS_INVALIDATE
| crate::header::sys_mman::MS_SYNC;
if flags & !valid_flags != 0 {
return Err(Errno(EINVAL));
}
if (flags & crate::header::sys_mman::MS_ASYNC) != 0
&& (flags & crate::header::sys_mman::MS_SYNC) != 0
{
return Err(Errno(EINVAL));
}
if addr as usize & (PAGE_SIZE - 1) != 0 {
return Err(Errno(EINVAL));
}
let Some(len) = round_up_to_page_size(len) else {
return Err(Errno(ENOMEM));
};
// Red Bear filesystem I/O is synchronous; data already on disk.
// MS_SYNC/MS_ASYNC are no-ops. MS_INVALIDATE not implemented
// (would need fine-grained cache invalidation in kernel), but
// stale cache is not a concern with direct filesystem reads.
// Cross-referenced with Linux 7.1 mm/msync.c:42-55.
Ok(())
}
unsafe fn munlock(addr: *const c_void, len: usize) -> Result<()> {
@@ -1036,14 +1044,29 @@ impl Pal for Sys {
}
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))
use crate::header::sys_mman::{
MADV_DONTNEED, MADV_NORMAL, MADV_RANDOM, MADV_SEQUENTIAL, MADV_WILLNEED,
};
if flags != MADV_NORMAL
&& flags != MADV_RANDOM
&& flags != MADV_SEQUENTIAL
&& flags != MADV_WILLNEED
&& flags != MADV_DONTNEED
{
return Err(Errno(EINVAL));
}
if addr as usize & (PAGE_SIZE - 1) != 0 {
return Err(Errno(EINVAL));
}
if len == 0 {
return Ok(());
}
// Red Bear has no page cache, swap, or lazy allocation.
// MADV_NORMAL/RANDOM/SEQUENTIAL/WILLNEED are advisory hints — no-op.
// MADV_DONTNEED would need kernel support for page table teardown,
// which is not implemented (Red Bear never swaps or reclaims pages).
// Cross-referenced with Linux 7.1 mm/madvise.c madvise_vma_behavior().
Ok(())
}
unsafe fn nanosleep(rqtp: *const timespec, rmtp: *mut timespec) -> Result<()> {
@@ -1302,9 +1325,40 @@ impl Pal for Sys {
}
unsafe fn setgroups(size: size_t, list: *const gid_t) -> Result<()> {
// TODO
todo_skip!(0, "setgroups({}, {:p}): not implemented", size, list);
Err(Errno(ENOSYS))
use crate::io::Write;
if size == 0 && list.is_null() {
// Clear all groups — open groups file and write nothing.
// Per Linux man page: size=0, list=NULL clears the group set.
} else if size > 0 && list.is_null() {
return Err(Errno(EFAULT));
} else if size > limits::NGROUPS_MAX as size_t {
return Err(Errno(EINVAL));
}
let pid = redox_rt::sys::posix_getpid();
let path = format!("/scheme/sys/proc/{}/groups", pid);
let path_c = match alloc::ffi::CString::new(path) {
Ok(p) => p,
Err(_) => return Err(Errno(ENOMEM)),
};
let mut file = match File::open(
CStr::borrow(&path_c),
fcntl::O_WRONLY | fcntl::O_CLOEXEC,
) {
Ok(f) => f,
Err(_) => return Err(Errno(EACCES)),
};
if size > 0 {
// Convert gid_t array to little-endian u32 bytes
let mut buf = alloc::vec::Vec::with_capacity(size as usize * 4);
for i in 0..size as usize {
let gid = unsafe { *list.add(i) } as u32;
buf.extend_from_slice(&gid.to_le_bytes());
}
if let Err(_) = file.write(&buf) {
return Err(Errno(EIO));
}
}
Ok(())
}
fn setpgid(pid: pid_t, pgid: pid_t) -> Result<()> {
+10 -16
View File
@@ -35,9 +35,8 @@ use syscall;
pub struct Session {
pub first: bool,
pub fpregs: File,
pub mem: File,
pub regs: File,
pub ctx: File,
pub tracer: File,
}
pub struct State {
@@ -94,16 +93,8 @@ pub fn get_session(
CStr::borrow(&CString::new(format!("/scheme/proc/{}/mem", pid)).unwrap()),
NEW_FLAGS,
)?,
regs: File::open(
CStr::borrow(
&CString::new(format!("/scheme/proc/{}/regs/int", pid)).unwrap(),
),
NEW_FLAGS,
)?,
fpregs: File::open(
CStr::borrow(
&CString::new(format!("/scheme/proc/{}/regs/float", pid)).unwrap(),
),
ctx: File::open(
CStr::borrow(&CString::new(format!("/scheme/proc/{}/ctx", pid)).unwrap()),
NEW_FLAGS,
)?,
}))
@@ -194,8 +185,9 @@ unsafe fn inner_ptrace(
}
sys_ptrace::PTRACE_GETREGS => {
let c_regs = unsafe { &mut *(data as *mut user_regs_struct) };
let mut redox_regs = syscall::IntRegisters::default();
(&mut &session.regs).read(&mut redox_regs)?;
let mut full = syscall::FullContextRegs::default();
(&mut &session.ctx).read(&mut full)?;
let redox_regs = full.int;
*c_regs = user_regs_struct {
r15: redox_regs.r15 as _,
r14: redox_regs.r14 as _,
@@ -229,7 +221,9 @@ unsafe fn inner_ptrace(
}
sys_ptrace::PTRACE_SETREGS => {
let c_regs = unsafe { &*(data as *mut user_regs_struct) };
let redox_regs = syscall::IntRegisters {
let mut full = syscall::FullContextRegs::default();
(&mut &session.ctx).read(&mut full)?;
full.int = syscall::IntRegisters {
r15: c_regs.r15 as _,
r14: c_regs.r14 as _,
r13: c_regs.r13 as _,
@@ -258,7 +252,7 @@ unsafe fn inner_ptrace(
// fs: c_regs.fs as _,
// gs: c_regs.gs as _,
};
(&mut &session.regs).write(&redox_regs)?;
(&mut &session.ctx).write(&full)?;
Ok(0)
}
_ => unimplemented!(),
+16
View File
@@ -789,6 +789,22 @@ impl PalSocket for Sys {
}
return Ok(());
}
val if val == crate::header::netinet_in::IPPROTO_IP as c_int
|| val == crate::header::netinet_in::IPPROTO_IPV6 as c_int => {
let metadata = [SocketCall::GetSockOpt as u64, option_name as u64];
let payload =
unsafe { slice::from_raw_parts_mut(option_value as *mut u8, option_len) };
let call_flags = CallFlags::empty();
unsafe {
*option_len_ptr = redox_rt::sys::sys_call_ro(
socket as usize,
payload,
CallFlags::empty(),
&metadata,
)? as socklen_t;
}
return Ok(());
}
_ => (),
}
+11 -4
View File
@@ -30,8 +30,13 @@ impl Default for CondAttr {
}
pub struct Cond {
cur: AtomicUint,
prev: AtomicUint,
pub cur: AtomicUint,
pub prev: AtomicUint,
/// Clock used for timedwait, per pthread_condattr_setclock.
/// CLOCK_REALTIME (0) or CLOCK_MONOTONIC (1). Set once at init.
pub clock: u8,
/// Padding to maintain 12-byte struct size for ABI compatibility.
pub _pad: [u8; 3],
}
type Result<T, E = Errno> = core::result::Result<T, E>;
@@ -47,6 +52,8 @@ impl Cond {
Self {
cur: AtomicUint::new(0),
prev: AtomicUint::new(0),
clock: CLOCK_REALTIME as u8,
_pad: [0; 3],
}
}
fn wake(&self, count: i32) -> Result<(), Errno> {
@@ -86,8 +93,8 @@ impl Cond {
self.wait_inner(mutex, Some(&relative))
}
pub fn timedwait(&self, mutex: &RlctMutex, timeout: &timespec) -> Result<(), Errno> {
// TODO: The clock can be other than CLOCK_REALTIME depends on CondAttr
self.clockwait(mutex, timeout, CLOCK_REALTIME)
let clock_id = self.clock as clockid_t;
self.clockwait(mutex, timeout, clock_id)
}
fn wait_inner(&self, mutex: &RlctMutex, timeout: Option<&timespec>) -> Result<(), Errno> {
self.wait_inner_generic(|| mutex.unlock(), || mutex.lock(), timeout)
+15 -6
View File
@@ -19,6 +19,7 @@ pub struct RlctMutex {
ty: Ty,
robust: bool,
prioceiling_value: Cell<u8>,
}
pub struct RobustMutexNode {
@@ -42,12 +43,16 @@ impl RlctMutex {
pub(crate) fn new(attr: &RlctMutexAttr) -> Result<Self, Errno> {
let RlctMutexAttr {
prioceiling,
protocol,
protocol: _,
pshared: _,
robust,
ty,
} = *attr;
if prioceiling < 0 || prioceiling > u8::MAX as c_int {
return Err(Errno(EINVAL));
}
Ok(Self {
inner: AtomicUint::new(STATE_UNLOCKED),
recursive_count: AtomicUint::new(0),
@@ -65,15 +70,19 @@ impl RlctMutex {
_ => return Err(Errno(EINVAL)),
},
prioceiling_value: Cell::new(prioceiling as u8),
})
}
pub fn prioceiling(&self) -> Result<c_int, Errno> {
todo_skip!(0, "pthread_getprioceiling: not implemented");
Ok(0)
Ok(self.prioceiling_value.get() as c_int)
}
pub fn replace_prioceiling(&self, _: c_int) -> Result<c_int, Errno> {
todo_skip!(0, "pthread_setprioceiling: not implemented");
Ok(0)
pub fn replace_prioceiling(&self, new_ceiling: c_int) -> Result<c_int, Errno> {
if new_ceiling < 0 || new_ceiling > u8::MAX as c_int {
return Err(Errno(EINVAL));
}
let old_ceiling = self.prioceiling_value.get();
self.prioceiling_value.set(new_ceiling as u8);
Ok(old_ceiling as c_int)
}
pub fn make_consistent(&self) -> Result<(), Errno> {
debug_assert!(self.robust, "make_consistent called on non-robust mutex");