Add Linux/GNU C-runtime primitives for PipeWire (affinity, gettid, statfs, iface ioctls)

Implement real, Redox-backed versions of the libc facilities PipeWire's
portable code relies on, rather than disabling its modules:

- gettid(): export the real per-thread id (Sys::gettid).
- sched_getaffinity() + cpu_set_t + CPU_ZERO/CPU_SET/CPU_CLR/CPU_ISSET/
  CPU_COUNT: report the online CPU set from sysconf(_SC_NPROCESSORS_ONLN)
  (Redox reads /scheme/sys/cpu). Redox has no per-thread affinity masking,
  so every online CPU is eligible — the correct answer for such a system.
- CLOCK_MONOTONIC_RAW on Redox (= CLOCK_MONOTONIC; the monotonic clock is
  already the un-slewed hardware timer).
- sys/statfs.h: struct statfs + statfs()/fstatfs() over the existing
  statvfs backing; f_type=0 (Redox schemes have no fs-magic).
- net/if.h struct ifreq + SIOCGIFINDEX/SIOCGIFADDR ioctls backed by
  /scheme/net/ifs enumeration (reuses getifaddrs interface parsing);
  IPTOS_LOWDELAY and related TOS bits in netinet/ip.h.

cargo check clean for x86_64-unknown-redox.
This commit is contained in:
2026-08-01 12:46:35 +03:00
parent 1d1e3ebe0e
commit 5995d3c4fc
12 changed files with 369 additions and 13 deletions
+13 -13
View File
@@ -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<Vec<InterfaceInfo>, ()> {
@@ -328,7 +328,7 @@ fn read_file(path: &[u8]) -> Result<Vec<u8>, ()> {
}
#[cfg(target_os = "redox")]
fn enumerate_interfaces_redox() -> Result<Vec<InterfaceInfo>, ()> {
pub(crate) fn enumerate_interfaces_redox() -> Result<Vec<InterfaceInfo>, ()> {
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")
+1
View File
@@ -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;
+40
View File
@@ -2,6 +2,46 @@
#
# There are no spec quotations relating to includes
include_guard = "_RELIBC_NET_IF_H"
after_includes = """
#include <sys/socket.h> // 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 <net/if.h> 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
+16
View File
@@ -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)`. */
+32
View File
@@ -11,6 +11,38 @@ include_guard = "_RELIBC_SCHED_H"
after_includes = """
#include <bits/pid-t.h> // for pid_t from sys/types
#include <bits/timespec.h> // 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"
+45
View File
@@ -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
/// <https://www.man7.org/linux/man-pages/man2/sched_getaffinity.2.html>.
///
/// 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::<crate::platform::types::c_ulong>();
let nwords = cpusetsize / core::mem::size_of::<crate::platform::types::c_ulong>();
// 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
}
+8
View File
@@ -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 <bits/ioctls.h> 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;
+85
View File
@@ -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/<name>` 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<c_int> {
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::<u8>();
// 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::<c_int>(), 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::<sockaddr_in>(), sa) };
Ok(0)
}
_ => Err(Errno(EINVAL)),
}
}
pub unsafe fn ioctl_inner(fd: c_int, request: c_ulong, out: *mut c_void) -> Result<c_int> {
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;
+15
View File
@@ -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 <bits/sys/statvfs.h> // 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
+101
View File
@@ -0,0 +1,101 @@
//! `sys/statfs.h` implementation.
//!
//! Linux/BSD extension (not POSIX), see
//! <https://www.man7.org/linux/man-pages/man2/statfs.2.html>.
//!
//! 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 <https://www.man7.org/linux/man-pages/man2/statfs.2.html>.
///
/// 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 <https://www.man7.org/linux/man-pages/man2/statfs.2.html>.
///
/// # 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 <https://www.man7.org/linux/man-pages/man2/fstatfs.2.html>.
///
/// # 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
}
+2
View File
@@ -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;
+11
View File
@@ -142,6 +142,17 @@ pub extern "C" fn _exit(status: c_int) -> ! {
Sys::exit(status)
}
/// Non-POSIX (Linux/GNU extension), see
/// <https://www.man7.org/linux/man-pages/man2/gettid.2.html>.
///
/// 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 <https://pubs.opengroup.org/onlinepubs/9799919799/functions/access.html>.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn access(path: *const c_char, mode: c_int) -> c_int {