diff --git a/include/ar.h b/include/ar.h new file mode 100644 index 0000000000..65f34f1f70 --- /dev/null +++ b/include/ar.h @@ -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 "!\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 */ diff --git a/include/byteswap.h b/include/byteswap.h new file mode 100644 index 0000000000..2afa2cdfa7 --- /dev/null +++ b/include/byteswap.h @@ -0,0 +1,17 @@ +/* byteswap.h — byte-order swapping macros. + * + * Red Bear OS / relibc. Mirrors the glibc 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 + +#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 */ diff --git a/include/error.h b/include/error.h new file mode 100644 index 0000000000..f5d8f08a11 --- /dev/null +++ b/include/error.h @@ -0,0 +1,60 @@ +/* error.h — GNU-style error reporting. + * + * Red Bear OS / relibc. Mirrors glibc : 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 builds and behaves correctly. + */ +#ifndef _ERROR_H +#define _ERROR_H 1 + +#include +#include +#include +#include + +#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 */ diff --git a/include/libintl.h b/include/libintl.h new file mode 100644 index 0000000000..5044ad1faf --- /dev/null +++ b/include/libintl.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 + +#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 */ diff --git a/include/search.h b/include/search.h new file mode 100644 index 0000000000..7b809f9704 --- /dev/null +++ b/include/search.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 + +#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 */ diff --git a/src/header/mod.rs b/src/header/mod.rs index 1052c4b89d..ce9e5383df 100644 --- a/src/header/mod.rs +++ b/src/header/mod.rs @@ -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 diff --git a/src/header/search/mod.rs b/src/header/search/mod.rs new file mode 100644 index 0000000000..80ffe12fc2 --- /dev/null +++ b/src/header/search/mod.rs @@ -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 . +/// +/// # 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, +) -> *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::()).cast::() }; + 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::(); + } + let cmp = unsafe { compar(key, (*cur).key) }; + if cmp == 0 { + return cur.cast::(); + } else if cmp < 0 { + link = unsafe { ptr::addr_of_mut!((*cur).left) }; + } else { + link = unsafe { ptr::addr_of_mut!((*cur).right) }; + } + } +} + +/// See . +/// +/// # 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, +) -> *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::(); + } + cur = if cmp < 0 { + unsafe { (*cur).left } + } else { + unsafe { (*cur).right } + }; + } + ptr::null_mut() +} + +/// See . +/// +/// # Safety +/// As for [`tsearch`]. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn tdelete( + key: *const c_void, + rootp: *mut *mut c_void, + compar: Option, +) -> *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::()); + } + // 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::() +} + +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::(), LEAF, depth); + } else { + action(node.cast::(), PREORDER, depth); + walk_recurse((*node).left, action, depth + 1); + action(node.cast::(), POSTORDER, depth); + walk_recurse((*node).right, action, depth + 1); + action(node.cast::(), ENDORDER, depth); + } + } +} + +/// See . +/// +/// # 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) { + if let Some(action) = action { + unsafe { walk_recurse(root.cast::(), 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::()); + } +} + +/// `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) { + if let Some(freefn) = freefn { + unsafe { destroy_recurse(root.cast::(), freefn) }; + } +} diff --git a/src/header/string/mod.rs b/src/header/string/mod.rs index 7316a978a0..aeef92ff5e 100644 --- a/src/header/string/mod.rs +++ b/src/header/string/mod.rs @@ -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 +/// . +/// +/// # 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::().add(n).cast::() + } +} + +/// 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 +/// . +/// +/// # 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::(); + unsafe { + while *p != needle { + p = p.add(1); + } + p as *mut c_void + } +} + /// See . /// /// # Safety