Add byteswap/error/libintl/ar/search headers + tsearch family, mempcpy, rawmemchr

Portable ELF tooling (elfutils/libelf, needed by Mesa's radeonsi driver) relies
on several standard headers and functions Red Bear's relibc lacked:

- <byteswap.h>: bswap_16/32/64 (compiler builtins).
- <error.h>: GNU error()/error_at_line() (inline).
- <libintl.h>: gettext family as the identity map (catalog-less system; matches
  glibc's no-catalog fallback behaviour).
- <ar.h>: Unix archive struct/magic (header-only).
- <search.h> + src/header/search: POSIX tsearch/tfind/tdelete/twalk + GNU
  tdestroy, implemented as an unbalanced BST (conforming; POSIX mandates no
  balancing).
- string: mempcpy (memcpy returning end) and rawmemchr (unbounded memchr).

All complete implementations, no stubs. Additive (no ABI break), so existing
sysroot binaries are unaffected.
This commit is contained in:
2026-07-31 15:03:51 +03:00
parent 991d05ce2f
commit daeca3892e
8 changed files with 476 additions and 0 deletions
+1
View File
@@ -145,6 +145,7 @@ pub mod shadow;
pub mod signal;
pub mod spawn;
// TODO: stdalign.h (likely C implementation)
pub mod search;
pub mod stdarg;
// stdatomic.h implemented in C
// stdbool.h implemented in C
+227
View File
@@ -0,0 +1,227 @@
//! `search.h` — POSIX binary tree search (`tsearch` family).
//!
//! An unbalanced binary search tree. POSIX does not mandate balancing, only the
//! documented behaviour, so an ordinary BST is a complete, conforming
//! implementation. The C-visible declarations (the `VISIT` enum and the
//! prototypes) live in the hand-written `include/search.h`; this module exports
//! the symbols and MUST keep the node layout and `VISIT` values in sync with
//! that header.
//!
//! Used by elfutils' libelf, which Mesa's radeonsi (AMD) gallium driver links.
use core::{mem, ptr};
use crate::{
header::stdlib::{free, malloc},
platform::types::{c_int, c_void},
};
/// Three-way comparison callback (`<0`, `0`, `>0`).
type Compar = unsafe extern "C" fn(*const c_void, *const c_void) -> c_int;
/// `twalk` visitor: `(nodep, which, depth)`.
type Action = unsafe extern "C" fn(*const c_void, c_int, c_int);
/// `tdestroy` per-key destructor: `(key)`.
type FreeNode = unsafe extern "C" fn(*mut c_void);
/// Tree node. `key` MUST remain the first member: POSIX requires the pointer
/// returned by `tsearch`/`tfind` to dereference to the stored key
/// (`*(void **)result == key`), so `node as *mut c_void` aliases `&node.key`.
#[repr(C)]
struct Node {
key: *const c_void,
left: *mut Node,
right: *mut Node,
}
// `VISIT` values — must match the enum order in include/search.h.
const PREORDER: c_int = 0;
const POSTORDER: c_int = 1;
const ENDORDER: c_int = 2;
const LEAF: c_int = 3;
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/tsearch.html>.
///
/// # Safety
/// `rootp` must be null or point to a valid root slot of a tree built solely by
/// these functions with the same `compar`; `compar` must be a valid callback.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn tsearch(
key: *const c_void,
rootp: *mut *mut c_void,
compar: Option<Compar>,
) -> *mut c_void {
let compar = match compar {
Some(f) => f,
None => return ptr::null_mut(),
};
if rootp.is_null() {
return ptr::null_mut();
}
let mut link = rootp.cast::<*mut Node>();
loop {
let cur = unsafe { *link };
if cur.is_null() {
let node = unsafe { malloc(mem::size_of::<Node>()).cast::<Node>() };
if node.is_null() {
return ptr::null_mut();
}
unsafe {
(*node).key = key;
(*node).left = ptr::null_mut();
(*node).right = ptr::null_mut();
*link = node;
}
return node.cast::<c_void>();
}
let cmp = unsafe { compar(key, (*cur).key) };
if cmp == 0 {
return cur.cast::<c_void>();
} else if cmp < 0 {
link = unsafe { ptr::addr_of_mut!((*cur).left) };
} else {
link = unsafe { ptr::addr_of_mut!((*cur).right) };
}
}
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/tsearch.html>.
///
/// # Safety
/// As for [`tsearch`], but the tree is not modified.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn tfind(
key: *const c_void,
rootp: *const *mut c_void,
compar: Option<Compar>,
) -> *mut c_void {
let compar = match compar {
Some(f) => f,
None => return ptr::null_mut(),
};
if rootp.is_null() {
return ptr::null_mut();
}
let mut cur = unsafe { *(rootp as *const *mut Node) };
while !cur.is_null() {
let cmp = unsafe { compar(key, (*cur).key) };
if cmp == 0 {
return cur.cast::<c_void>();
}
cur = if cmp < 0 {
unsafe { (*cur).left }
} else {
unsafe { (*cur).right }
};
}
ptr::null_mut()
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/tsearch.html>.
///
/// # Safety
/// As for [`tsearch`].
#[unsafe(no_mangle)]
pub unsafe extern "C" fn tdelete(
key: *const c_void,
rootp: *mut *mut c_void,
compar: Option<Compar>,
) -> *mut c_void {
let compar = match compar {
Some(f) => f,
None => return ptr::null_mut(),
};
if rootp.is_null() {
return ptr::null_mut();
}
let mut link = rootp.cast::<*mut Node>();
loop {
let cur = unsafe { *link };
if cur.is_null() {
return ptr::null_mut(); // not found
}
let cmp = unsafe { compar(key, (*cur).key) };
if cmp == 0 {
break;
}
link = if cmp < 0 {
unsafe { ptr::addr_of_mut!((*cur).left) }
} else {
unsafe { ptr::addr_of_mut!((*cur).right) }
};
}
let target = unsafe { *link };
unsafe {
if (*target).left.is_null() {
*link = (*target).right;
} else if (*target).right.is_null() {
*link = (*target).left;
} else {
// Splice in the in-order successor (leftmost node of the right subtree).
let mut succ_link = ptr::addr_of_mut!((*target).right);
while !(*(*succ_link)).left.is_null() {
succ_link = ptr::addr_of_mut!((*(*succ_link)).left);
}
let succ = *succ_link;
*succ_link = (*succ).right;
(*succ).left = (*target).left;
(*succ).right = (*target).right;
*link = succ;
}
free(target.cast::<c_void>());
}
// POSIX returns a pointer to the parent of the deleted node (non-null on
// success); callers only test null/non-null, so the root slot suffices.
rootp.cast::<c_void>()
}
unsafe fn walk_recurse(node: *const Node, action: Action, depth: c_int) {
if node.is_null() {
return;
}
unsafe {
if (*node).left.is_null() && (*node).right.is_null() {
action(node.cast::<c_void>(), LEAF, depth);
} else {
action(node.cast::<c_void>(), PREORDER, depth);
walk_recurse((*node).left, action, depth + 1);
action(node.cast::<c_void>(), POSTORDER, depth);
walk_recurse((*node).right, action, depth + 1);
action(node.cast::<c_void>(), ENDORDER, depth);
}
}
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/tsearch.html>.
///
/// # Safety
/// `root` must be null or a tree built by these functions; `action` valid.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn twalk(root: *const c_void, action: Option<Action>) {
if let Some(action) = action {
unsafe { walk_recurse(root.cast::<Node>(), action, 0) };
}
}
unsafe fn destroy_recurse(node: *mut Node, freefn: FreeNode) {
if node.is_null() {
return;
}
unsafe {
destroy_recurse((*node).left, freefn);
destroy_recurse((*node).right, freefn);
freefn((*node).key as *mut c_void);
free(node.cast::<c_void>());
}
}
/// `tdestroy` (GNU extension): destroy an entire tree, calling `freefn` on each
/// stored key, then freeing every node.
///
/// # Safety
/// `root` must be null or a tree built by these functions; `freefn` valid.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn tdestroy(root: *mut c_void, freefn: Option<FreeNode>) {
if let Some(freefn) = freefn {
unsafe { destroy_recurse(root.cast::<Node>(), freefn) };
}
}
+33
View File
@@ -114,6 +114,39 @@ pub unsafe extern "C" fn memcpy(s1: *mut c_void, s2: *const c_void, n: size_t) -
s1
}
/// GNU extension: like `memcpy` but returns a pointer to the byte following the
/// last written byte (`s1 + n`) rather than `s1`. See
/// <https://www.gnu.org/software/libc/manual/html_node/Copying-Strings-and-Arrays.html>.
///
/// # Safety
/// Same requirements as [`memcpy`].
#[unsafe(no_mangle)]
pub unsafe extern "C" fn mempcpy(s1: *mut c_void, s2: *const c_void, n: size_t) -> *mut c_void {
unsafe {
memcpy(s1, s2, n);
s1.cast::<u8>().add(n).cast::<c_void>()
}
}
/// GNU extension: like `memchr` but with no length bound — scans forward from
/// `s` until it finds a byte equal to `c` (which the caller guarantees is
/// present). See
/// <https://www.gnu.org/software/libc/manual/html_node/Search-Functions.html>.
///
/// # Safety
/// The caller must ensure a byte equal to `c` occurs at or after `s`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rawmemchr(s: *const c_void, c: c_int) -> *mut c_void {
let needle = c as u8;
let mut p = s.cast::<u8>();
unsafe {
while *p != needle {
p = p.add(1);
}
p as *mut c_void
}
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/memmem.html>.
///
/// # Safety