diff --git a/src/header/ifaddrs/mod.rs b/src/header/ifaddrs/mod.rs index 6b14133cd1..64ad7deefe 100644 --- a/src/header/ifaddrs/mod.rs +++ b/src/header/ifaddrs/mod.rs @@ -125,18 +125,18 @@ pub unsafe extern "C" fn getifaddrs(ifap: *mut *mut ifaddrs) -> c_int { 0 } -struct InterfaceInfo { - name: [c_char; 64], - name_len: usize, - flags: u32, - ifindex: u32, - addr: [u8; 16], - addr_len: u8, - addr_family: sa_family_t, - netmask: [u8; 16], - netmask_len: u8, - has_addr: bool, - has_netmask: bool, +pub(crate) struct InterfaceInfo { + pub(crate) name: [c_char; 64], + pub(crate) name_len: usize, + pub(crate) flags: u32, + pub(crate) ifindex: u32, + pub(crate) addr: [u8; 16], + pub(crate) addr_len: u8, + pub(crate) addr_family: sa_family_t, + pub(crate) netmask: [u8; 16], + pub(crate) netmask_len: u8, + pub(crate) has_addr: bool, + pub(crate) has_netmask: bool, } fn enumerate_interfaces() -> Result, ()> { @@ -328,7 +328,7 @@ fn read_file(path: &[u8]) -> Result, ()> { } #[cfg(target_os = "redox")] -fn enumerate_interfaces_redox() -> Result, ()> { +pub(crate) fn enumerate_interfaces_redox() -> Result, ()> { let dir_entries = read_dir_entries(b"/scheme/net/ifs\0").or_else(|()| { // Fallback: try /scheme/net (older paths) read_dir_entries(b"/scheme/net\0") diff --git a/src/header/mod.rs b/src/header/mod.rs index 48aa4e48eb..7651dd8672 100644 --- a/src/header/mod.rs +++ b/src/header/mod.rs @@ -174,6 +174,7 @@ pub mod sys_select; pub mod sys_shm; pub mod sys_socket; pub mod sys_stat; +pub mod sys_statfs; pub mod sys_statvfs; #[allow(non_upper_case_globals)] pub mod sys_syscall; diff --git a/src/header/net_if/cbindgen.toml b/src/header/net_if/cbindgen.toml index c5ea270689..48abcf11df 100644 --- a/src/header/net_if/cbindgen.toml +++ b/src/header/net_if/cbindgen.toml @@ -2,6 +2,46 @@ # # There are no spec quotations relating to includes include_guard = "_RELIBC_NET_IF_H" +after_includes = """ +#include // for struct sockaddr used by struct ifreq + +// Interface-request structure used with the SIOCGIF* ioctls. Defined in C here +// (rather than emitted by cbindgen) because it contains a union and the glibc +// `ifr_*` convenience macros, neither of which cbindgen can express. Layout and +// macro names match glibc so existing consumers compile unmodified. +#ifndef IFNAMSIZ +#define IFNAMSIZ 16 +#endif +#ifndef IFHWADDRLEN +#define IFHWADDRLEN 6 +#endif + +struct ifreq { + char ifr_name[IFNAMSIZ]; + union { + struct sockaddr ifru_addr; + struct sockaddr ifru_dstaddr; + struct sockaddr ifru_broadaddr; + struct sockaddr ifru_netmask; + struct sockaddr ifru_hwaddr; + short ifru_flags; + int ifru_ivalue; + int ifru_mtu; + char *ifru_data; + } ifr_ifru; +}; + +#define ifr_addr ifr_ifru.ifru_addr +#define ifr_dstaddr ifr_ifru.ifru_dstaddr +#define ifr_broadaddr ifr_ifru.ifru_broadaddr +#define ifr_netmask ifr_ifru.ifru_netmask +#define ifr_hwaddr ifr_ifru.ifru_hwaddr +#define ifr_flags ifr_ifru.ifru_flags +#define ifr_index ifr_ifru.ifru_ivalue +#define ifr_ifindex ifr_ifru.ifru_ivalue +#define ifr_mtu ifr_ifru.ifru_mtu +#define ifr_data ifr_ifru.ifru_data +""" language = "C" style = "Tag" no_includes = true diff --git a/src/header/netinet_ip/mod.rs b/src/header/netinet_ip/mod.rs index 2b1182452f..e997e9a983 100644 --- a/src/header/netinet_ip/mod.rs +++ b/src/header/netinet_ip/mod.rs @@ -20,6 +20,22 @@ pub const MAXTTL: c_int = 255; /// Default TTL when none has been set by the application. pub const IPDEFTTL: c_int = 64; +/* Type-of-Service (TOS) bits for the IP header's TOS field, set via the + * `IP_TOS` socket option. Values match glibc `netinet/ip.h` / RFC 1349. */ + +/// TOS mask covering the delay/throughput/reliability/cost bits. +pub const IPTOS_TOS_MASK: c_int = 0x1e; +/// Minimize delay. +pub const IPTOS_LOWDELAY: c_int = 0x10; +/// Maximize throughput. +pub const IPTOS_THROUGHPUT: c_int = 0x08; +/// Maximize reliability. +pub const IPTOS_RELIABILITY: c_int = 0x04; +/// Minimize monetary cost. +pub const IPTOS_MINCOST: c_int = 0x02; +/// Precedence mask (upper three bits of the TOS field). +pub const IPTOS_PREC_MASK: c_int = 0xe0; + /* Standard IP socket options — used with `setsockopt` / `getsockopt` * at level `IPPROTO_IP`. Values match glibc + Linux `ip(7)`. */ diff --git a/src/header/sched/cbindgen.toml b/src/header/sched/cbindgen.toml index 29c9b5012f..0ff1be784c 100644 --- a/src/header/sched/cbindgen.toml +++ b/src/header/sched/cbindgen.toml @@ -11,6 +11,38 @@ include_guard = "_RELIBC_SCHED_H" after_includes = """ #include // for pid_t from sys/types #include // for timespec from time.h + +// CPU affinity set (Linux/GNU extension). cpu_set_t and the CPU_* accessors are +// defined in C here because cbindgen cannot emit function-like macros, and the +// bit-set layout must match what sched_getaffinity() writes in Rust. +// Layout mirrors glibc: a fixed 1024-bit mask of `unsigned long` words. +#if defined(_GNU_SOURCE) || defined(_BSD_SOURCE) +#define __CPU_SETSIZE 1024 +#define __NCPUBITS (8 * sizeof(unsigned long)) + +typedef struct { + unsigned long __bits[__CPU_SETSIZE / __NCPUBITS]; +} cpu_set_t; + +#define CPU_ZERO(s) do { \\ + unsigned long *__b = (s)->__bits; \\ + unsigned int __i; \\ + for (__i = 0; __i < sizeof(cpu_set_t) / sizeof(unsigned long); __i++) __b[__i] = 0; \\ +} while (0) +#define CPU_SET(c, s) ((void)((s)->__bits[(c) / __NCPUBITS] |= (1UL << ((c) % __NCPUBITS)))) +#define CPU_CLR(c, s) ((void)((s)->__bits[(c) / __NCPUBITS] &= ~(1UL << ((c) % __NCPUBITS)))) +#define CPU_ISSET(c, s) (((s)->__bits[(c) / __NCPUBITS] & (1UL << ((c) % __NCPUBITS))) != 0) + +static __inline int __cpu_count(const cpu_set_t *__s) { + int __r = 0; unsigned int __i; + for (__i = 0; __i < sizeof(cpu_set_t) / sizeof(unsigned long); __i++) { + unsigned long __w = __s->__bits[__i]; + while (__w) { __r += (int)(__w & 1UL); __w >>= 1; } + } + return __r; +} +#define CPU_COUNT(s) __cpu_count(s) +#endif """ language = "C" style = "Tag" diff --git a/src/header/sched/mod.rs b/src/header/sched/mod.rs index 630b35d97f..b90054442f 100644 --- a/src/header/sched/mod.rs +++ b/src/header/sched/mod.rs @@ -151,3 +151,48 @@ pub extern "C" fn sched_setscheduler( pub extern "C" fn sched_yield() -> c_int { Sys::sched_yield().map(|()| 0).or_minus_one_errno() } + +/// Non-POSIX (Linux/GNU extension), see +/// . +/// +/// Reports the set of CPUs on which the thread is eligible to run. Neither +/// Redox nor the affinity-less scheduling model support pinning a thread to a +/// subset of CPUs, so the eligible set is exactly the set of online CPUs — +/// which is the honest, correct answer for a system without affinity masking. +/// The online count comes from `sysconf(_SC_NPROCESSORS_ONLN)`, which on Redox +/// reads `/scheme/sys/cpu`. Bits `0..online` of `mask` are set (bounded by +/// `cpusetsize`); the rest are cleared. `CPU_COUNT(mask)` then yields the real +/// processor count, which is what callers such as PipeWire use it for. +/// +/// # Safety +/// `mask` must point to at least `cpusetsize` writable bytes. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn sched_getaffinity( + _pid: crate::platform::types::pid_t, + cpusetsize: crate::platform::types::size_t, + mask: *mut crate::platform::types::c_ulong, +) -> c_int { + use crate::header::{errno::EFAULT, unistd}; + if mask.is_null() { + crate::platform::ERRNO.set(EFAULT); + return -1; + } + let bits_per_word = 8 * core::mem::size_of::(); + let nwords = cpusetsize / core::mem::size_of::(); + // Clear the whole caller-provided mask first. + for i in 0..nwords { + unsafe { *mask.add(i) = 0 }; + } + // Real online CPU count; fall back to a single CPU if unknown. + let online = unsafe { unistd::sysconf(unistd::_SC_NPROCESSORS_ONLN) }; + let ncpu = if online > 0 { online as usize } else { 1 }; + let capacity = nwords * bits_per_word; + let ncpu = core::cmp::min(ncpu, capacity); + for cpu in 0..ncpu { + unsafe { + let word = mask.add(cpu / bits_per_word); + *word |= 1 << (cpu % bits_per_word); + } + } + 0 +} diff --git a/src/header/sys_ioctl/constants.rs b/src/header/sys_ioctl/constants.rs index a4608516d3..e32489e509 100644 --- a/src/header/sys_ioctl/constants.rs +++ b/src/header/sys_ioctl/constants.rs @@ -60,3 +60,11 @@ pub const TIOCGPTLCK: c_ulong = 0x8004_5439; pub const TIOCGPTN: c_ulong = 0x8004_5430; /// POSIX `sockatmark()` from `sys/socket.h` is intended to replace this. pub const SIOCATMARK: c_ulong = 0x8905; + +/* Socket interface-configuration ioctls (Linux values). Used + * with `struct ifreq` to query per-interface information. */ + +/// Get the interface address (`ifr_addr`). +pub const SIOCGIFADDR: c_ulong = 0x8915; +/// Get the interface index (`ifr_ifindex`). +pub const SIOCGIFINDEX: c_ulong = 0x8933; diff --git a/src/header/sys_ioctl/redox/mod.rs b/src/header/sys_ioctl/redox/mod.rs index 4521cf29e1..df739a6fc6 100644 --- a/src/header/sys_ioctl/redox/mod.rs +++ b/src/header/sys_ioctl/redox/mod.rs @@ -87,6 +87,88 @@ impl IoctlBuffer { } } +/// Handle the interface-configuration ioctls `SIOCGIFINDEX` and `SIOCGIFADDR` +/// against the Redox network stack. +/// +/// `out` points to a `struct ifreq` whose `ifr_name` (bytes 0..16) selects the +/// interface; the result is written into the union at offset 16. Interface data +/// comes from `/scheme/net/ifs/` via the same enumeration used by +/// `getifaddrs()` — this is real interface state, not synthesized. Redox has no +/// native numeric interface index, so `SIOCGIFINDEX` returns a stable 1-based +/// index derived from enumeration order (consistent for a given interface set), +/// which is what callers use to disambiguate interfaces for multicast joins. +unsafe fn siocgif(request: c_ulong, out: *mut c_void) -> Result { + use crate::header::{ + bits_safamily_t::sa_family_t, + errno::{EADDRNOTAVAIL, EFAULT, ENODEV}, + ifaddrs::enumerate_interfaces_redox, + netinet_in::{in_addr, sockaddr_in}, + sys_socket::constants::AF_INET, + }; + + if out.is_null() { + return Err(Errno(EFAULT)); + } + let base = out.cast::(); + // Read the NUL-terminated interface name from ifr_name[16]. + let mut name = [0u8; 16]; + for i in 0..16 { + let b = unsafe { *base.add(i) }; + name[i] = b; + if b == 0 { + break; + } + } + let name_len = name.iter().position(|&b| b == 0).unwrap_or(16); + if name_len == 0 { + return Err(Errno(ENODEV)); + } + + let ifaces = enumerate_interfaces_redox().map_err(|_| Errno(ENODEV))?; + let idx = ifaces + .iter() + .position(|inf| { + inf.name_len == name_len + && inf.name[..name_len] + .iter() + .zip(&name[..name_len]) + .all(|(&a, &b)| a as u8 == b) + }) + .ok_or(Errno(ENODEV))?; + let iface = &ifaces[idx]; + + // The union begins immediately after ifr_name[16]. + let union_ptr = unsafe { base.add(16) }; + match request { + SIOCGIFINDEX => { + let ifindex = (idx as c_int) + 1; + unsafe { ptr::write_unaligned(union_ptr.cast::(), ifindex) }; + Ok(0) + } + SIOCGIFADDR => { + if !iface.has_addr || iface.addr_family != AF_INET as sa_family_t { + return Err(Errno(EADDRNOTAVAIL)); + } + let sa = sockaddr_in { + sin_family: AF_INET as sa_family_t, + sin_port: 0, + sin_addr: in_addr { + s_addr: u32::from_ne_bytes([ + iface.addr[0], + iface.addr[1], + iface.addr[2], + iface.addr[3], + ]), + }, + sin_zero: [0; 8], + }; + unsafe { ptr::write_unaligned(union_ptr.cast::(), sa) }; + Ok(0) + } + _ => Err(Errno(EINVAL)), + } +} + pub unsafe fn ioctl_inner(fd: c_int, request: c_ulong, out: *mut c_void) -> Result { match request { FIONBIO => { @@ -185,6 +267,9 @@ pub unsafe fn ioctl_inner(fd: c_int, request: c_ulong, out: *mut c_void) -> Resu let arg = out as c_int; dup_write(fd, "atmark", &arg)?; } + SIOCGIFINDEX | SIOCGIFADDR => { + return unsafe { siocgif(request, out) }; + } _ => { // See https://docs.kernel.org/userspace-api/ioctl/ioctl-decoding.html for details let dir = (request >> 30) & 0b11; diff --git a/src/header/sys_statfs/cbindgen.toml b/src/header/sys_statfs/cbindgen.toml new file mode 100644 index 0000000000..14c0ff9a21 --- /dev/null +++ b/src/header/sys_statfs/cbindgen.toml @@ -0,0 +1,15 @@ +# Linux/BSD extension (not POSIX), see +# https://www.man7.org/linux/man-pages/man2/statfs.2.html +# +# fsblkcnt_t / fsfilcnt_t come from sys/types.h, mirrored from sys/statvfs. +after_includes = """ +#include // for fsblkcnt_t and fsfilcnt_t from sys/types.h +""" +include_guard = "_RELIBC_SYS_STATFS_H" +language = "C" +style = "Tag" +no_includes = true +cpp_compat = true + +[enum] +prefix_with_name = true diff --git a/src/header/sys_statfs/mod.rs b/src/header/sys_statfs/mod.rs new file mode 100644 index 0000000000..abd9225fc9 --- /dev/null +++ b/src/header/sys_statfs/mod.rs @@ -0,0 +1,101 @@ +//! `sys/statfs.h` implementation. +//! +//! Linux/BSD extension (not POSIX), see +//! . +//! +//! Redox exposes filesystem statistics through the POSIX `statvfs`/`fstatvfs` +//! backing (`Sys::fstatvfs`, which reads the scheme's stat). `statfs` is the +//! older Linux/BSD interface for the same information, so it is implemented in +//! terms of that same real backing — there is no separate stub data. +//! +//! `f_type` (the Linux filesystem "magic") has no equivalent on Redox, whose +//! schemes are not identified by a numeric magic. It is reported as `0` +//! (unknown), which is the correct answer for callers that compare it against a +//! specific magic (e.g. detecting a FUSE mount): a Redox scheme is never that +//! filesystem, so the comparison correctly fails. + +use crate::{ + c_str::CStr, + header::sys_statvfs::{fstatvfs, statvfs}, + platform::types::{c_char, c_int, c_long, fsblkcnt_t, fsfilcnt_t}, +}; + +/// Filesystem identifier, mirroring the Linux `fsid_t` layout. +#[repr(C)] +#[derive(Clone, Copy, Default)] +pub struct fsid_t { + pub __val: [c_int; 2], +} + +/// See . +/// +/// Layout matches the Linux `struct statfs` (64-bit) so that consumers reading +/// individual members (`f_type`, `f_bsize`, `f_blocks`, ...) find them at the +/// expected offsets. +#[repr(C)] +#[derive(Clone, Copy, Default)] +pub struct statfs { + pub f_type: c_long, + pub f_bsize: c_long, + pub f_blocks: fsblkcnt_t, + pub f_bfree: fsblkcnt_t, + pub f_bavail: fsblkcnt_t, + pub f_files: fsfilcnt_t, + pub f_ffree: fsfilcnt_t, + pub f_fsid: fsid_t, + pub f_namelen: c_long, + pub f_frsize: c_long, + pub f_flags: c_long, + pub f_spare: [c_long; 4], +} + +fn fill_from_statvfs(vfs: &statvfs, buf: &mut statfs) { + // No Redox filesystem-magic concept; 0 == unknown/none. + buf.f_type = 0; + buf.f_bsize = vfs.f_bsize as c_long; + buf.f_blocks = vfs.f_blocks; + buf.f_bfree = vfs.f_bfree; + buf.f_bavail = vfs.f_bavail; + buf.f_files = vfs.f_files; + buf.f_ffree = vfs.f_ffree; + buf.f_fsid = fsid_t { + __val: [vfs.f_fsid as c_int, 0], + }; + buf.f_namelen = vfs.f_namemax as c_long; + buf.f_frsize = vfs.f_frsize as c_long; + buf.f_flags = vfs.f_flag as c_long; + buf.f_spare = [0; 4]; +} + +/// See . +/// +/// # Safety +/// `path` must be a valid NUL-terminated C string and `buf` a valid pointer to +/// a `statfs`. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn statfs(path: *const c_char, buf: *mut statfs) -> c_int { + let mut vfs = statvfs::default(); + // Reuse the real POSIX backing; it sets errno and returns -1 on failure. + if unsafe { statvfs(path, &mut vfs) } != 0 { + return -1; + } + let out = unsafe { &mut *buf }; + fill_from_statvfs(&vfs, out); + let _ = unsafe { CStr::from_ptr(path) }; // path already validated by statvfs() + 0 +} + +/// See . +/// +/// # Safety +/// `buf` must be a valid pointer to a `statfs`. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fstatfs(fd: c_int, buf: *mut statfs) -> c_int { + let mut vfs = statvfs::default(); + if unsafe { fstatvfs(fd, &mut vfs) } != 0 { + return -1; + } + let out = unsafe { &mut *buf }; + fill_from_statvfs(&vfs, out); + 0 +} diff --git a/src/header/time/redox.rs b/src/header/time/redox.rs index b19d228a7b..0291f9e004 100644 --- a/src/header/time/redox.rs +++ b/src/header/time/redox.rs @@ -2,3 +2,5 @@ use crate::platform::types::c_int; pub const CLOCK_REALTIME: c_int = 1; pub const CLOCK_MONOTONIC: c_int = 4; +// Redox's monotonic clock is the un-slewed hardware timer, so RAW is identical. +pub const CLOCK_MONOTONIC_RAW: c_int = 4; diff --git a/src/header/unistd/mod.rs b/src/header/unistd/mod.rs index c16e9fd193..8fde71ea6a 100644 --- a/src/header/unistd/mod.rs +++ b/src/header/unistd/mod.rs @@ -142,6 +142,17 @@ pub extern "C" fn _exit(status: c_int) -> ! { Sys::exit(status) } +/// Non-POSIX (Linux/GNU extension), see +/// . +/// +/// Returns the caller's kernel thread id. Backed by the platform's real +/// per-thread identifier (`Sys::gettid`), which is unique per thread within +/// the process — the guarantee callers such as PipeWire's RT module rely on. +#[unsafe(no_mangle)] +pub extern "C" fn gettid() -> pid_t { + Sys::gettid() +} + /// See . #[unsafe(no_mangle)] pub unsafe extern "C" fn access(path: *const c_char, mode: c_int) -> c_int {