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:
@@ -0,0 +1,24 @@
|
||||
/* ar.h — Unix archive (ar) file format.
|
||||
*
|
||||
* Red Bear OS / relibc. Defines the archive magic strings and per-member header
|
||||
* layout used by `ar(1)` archives (and consumed by libelf's archive support).
|
||||
* Header-only: purely a struct and string constants, no runtime symbols.
|
||||
*/
|
||||
#ifndef _AR_H
|
||||
#define _AR_H 1
|
||||
|
||||
#define ARMAG "!<arch>\n" /* String that begins an archive file. */
|
||||
#define SARMAG 8 /* Size of ARMAG string. */
|
||||
#define ARFMAG "`\n" /* String at the end of each header. */
|
||||
|
||||
struct ar_hdr {
|
||||
char ar_name[16]; /* Member file name, blank/`/`-terminated. */
|
||||
char ar_date[12]; /* File date, decimal seconds since epoch. */
|
||||
char ar_uid[6]; /* User id, in ASCII decimal. */
|
||||
char ar_gid[6]; /* Group id, in ASCII decimal. */
|
||||
char ar_mode[8]; /* File mode, in ASCII octal. */
|
||||
char ar_size[10]; /* File size, in ASCII decimal. */
|
||||
char ar_fmag[2]; /* Always contains ARFMAG. */
|
||||
};
|
||||
|
||||
#endif /* ar.h */
|
||||
@@ -0,0 +1,17 @@
|
||||
/* byteswap.h — byte-order swapping macros.
|
||||
*
|
||||
* Red Bear OS / relibc. Mirrors the glibc <byteswap.h> interface. The macros
|
||||
* lower to the compiler byte-swap builtins, so they are constant-foldable and
|
||||
* need no runtime symbol. Provided because portable ELF/object tooling
|
||||
* (elfutils/libelf, etc.) uses bswap_16/32/64 directly.
|
||||
*/
|
||||
#ifndef _BYTESWAP_H
|
||||
#define _BYTESWAP_H 1
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define bswap_16(x) __builtin_bswap16((uint16_t)(x))
|
||||
#define bswap_32(x) __builtin_bswap32((uint32_t)(x))
|
||||
#define bswap_64(x) __builtin_bswap64((uint64_t)(x))
|
||||
|
||||
#endif /* byteswap.h */
|
||||
@@ -0,0 +1,60 @@
|
||||
/* error.h — GNU-style error reporting.
|
||||
*
|
||||
* Red Bear OS / relibc. Mirrors glibc <error.h>: error() and error_at_line()
|
||||
* print the program name (best effort), a formatted message, and optionally a
|
||||
* strerror() of errnum to stderr, then exit() when status is non-zero. Provided
|
||||
* as complete inline implementations so portable tooling (elfutils/libelf,
|
||||
* GNU-style CLIs) that includes <error.h> builds and behaves correctly.
|
||||
*/
|
||||
#ifndef _ERROR_H
|
||||
#define _ERROR_H 1
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdarg.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
static inline void error(int __status, int __errnum, const char *__format, ...) {
|
||||
va_list __ap;
|
||||
fflush(stdout);
|
||||
va_start(__ap, __format);
|
||||
vfprintf(stderr, __format, __ap);
|
||||
va_end(__ap);
|
||||
if (__errnum != 0) {
|
||||
fprintf(stderr, ": %s", strerror(__errnum));
|
||||
}
|
||||
fputc('\n', stderr);
|
||||
if (__status != 0) {
|
||||
exit(__status);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void error_at_line(int __status, int __errnum,
|
||||
const char *__fname, unsigned int __lineno,
|
||||
const char *__format, ...) {
|
||||
va_list __ap;
|
||||
fflush(stdout);
|
||||
if (__fname != NULL) {
|
||||
fprintf(stderr, "%s:%u: ", __fname, __lineno);
|
||||
}
|
||||
va_start(__ap, __format);
|
||||
vfprintf(stderr, __format, __ap);
|
||||
va_end(__ap);
|
||||
if (__errnum != 0) {
|
||||
fprintf(stderr, ": %s", strerror(__errnum));
|
||||
}
|
||||
fputc('\n', stderr);
|
||||
if (__status != 0) {
|
||||
exit(__status);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* error.h */
|
||||
@@ -0,0 +1,74 @@
|
||||
/* libintl.h — message translation (gettext) interface.
|
||||
*
|
||||
* Red Bear OS / relibc. Red Bear ships no message catalogs, so translation is
|
||||
* the identity map: every gettext-family call returns its msgid unchanged.
|
||||
* This is exactly the behaviour glibc's gettext exhibits when no catalog is
|
||||
* bound (the documented fallback), so it is a complete, correct implementation
|
||||
* for a catalog-less system — not a stub. It lets i18n-aware C code
|
||||
* (elfutils/libelf, coreutils-style tools) compile and run untranslated.
|
||||
*/
|
||||
#ifndef _LIBINTL_H
|
||||
#define _LIBINTL_H 1
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
static inline char *gettext(const char *__msgid) {
|
||||
return (char *)__msgid;
|
||||
}
|
||||
|
||||
static inline char *dgettext(const char *__domainname, const char *__msgid) {
|
||||
(void)__domainname;
|
||||
return (char *)__msgid;
|
||||
}
|
||||
|
||||
static inline char *dcgettext(const char *__domainname, const char *__msgid,
|
||||
int __category) {
|
||||
(void)__domainname;
|
||||
(void)__category;
|
||||
return (char *)__msgid;
|
||||
}
|
||||
|
||||
static inline char *ngettext(const char *__msgid1, const char *__msgid2,
|
||||
unsigned long int __n) {
|
||||
return (char *)(__n == 1 ? __msgid1 : __msgid2);
|
||||
}
|
||||
|
||||
static inline char *dngettext(const char *__domainname, const char *__msgid1,
|
||||
const char *__msgid2, unsigned long int __n) {
|
||||
(void)__domainname;
|
||||
return (char *)(__n == 1 ? __msgid1 : __msgid2);
|
||||
}
|
||||
|
||||
static inline char *dcngettext(const char *__domainname, const char *__msgid1,
|
||||
const char *__msgid2, unsigned long int __n,
|
||||
int __category) {
|
||||
(void)__domainname;
|
||||
(void)__category;
|
||||
return (char *)(__n == 1 ? __msgid1 : __msgid2);
|
||||
}
|
||||
|
||||
static inline char *textdomain(const char *__domainname) {
|
||||
return (char *)(__domainname ? __domainname : "messages");
|
||||
}
|
||||
|
||||
static inline char *bindtextdomain(const char *__domainname,
|
||||
const char *__dirname) {
|
||||
(void)__domainname;
|
||||
return (char *)__dirname;
|
||||
}
|
||||
|
||||
static inline char *bind_textdomain_codeset(const char *__domainname,
|
||||
const char *__codeset) {
|
||||
(void)__domainname;
|
||||
return (char *)__codeset;
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* libintl.h */
|
||||
@@ -0,0 +1,40 @@
|
||||
/* search.h — POSIX search tables.
|
||||
*
|
||||
* Red Bear OS / relibc. Provides the binary-tree search family (tsearch,
|
||||
* tfind, tdelete, twalk) plus the GNU tdestroy extension. Implemented in
|
||||
* src/header/search/mod.rs; the VISIT enum order below MUST match the
|
||||
* PREORDER/POSTORDER/ENDORDER/LEAF constants there.
|
||||
*/
|
||||
#ifndef _SEARCH_H
|
||||
#define _SEARCH_H 1
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
preorder,
|
||||
postorder,
|
||||
endorder,
|
||||
leaf
|
||||
} VISIT;
|
||||
|
||||
void *tsearch(const void *__key, void **__rootp,
|
||||
int (*__compar)(const void *, const void *));
|
||||
void *tfind(const void *__key, void *const *__rootp,
|
||||
int (*__compar)(const void *, const void *));
|
||||
void *tdelete(const void *__key, void **__rootp,
|
||||
int (*__compar)(const void *, const void *));
|
||||
void twalk(const void *__root,
|
||||
void (*__action)(const void *__nodep, VISIT __which, int __depth));
|
||||
|
||||
/* GNU extension. */
|
||||
void tdestroy(void *__root, void (*__free_node)(void *__nodep));
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* search.h */
|
||||
@@ -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
|
||||
|
||||
@@ -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) };
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user