diff --git a/include/error.h b/include/error.h index f5d8f08a11..8235c2d027 100644 --- a/include/error.h +++ b/include/error.h @@ -2,56 +2,36 @@ * * 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. + * strerror() of errnum to stderr, then exit() when status is non-zero. + * + * These are DECLARED here and IMPLEMENTED as real exported symbols in relibc + * (src/header/error). They must NOT be static-inline: autotools/gnulib link-test + * for a system error() and, finding one, skip compiling their own lib/error.c — + * a header-only inline collides with that replacement ("redefinition of + * 'error'", seen building m4/gnulib). */ #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); - } -} +/* Count of messages printed by error()/error_at_line(). */ +extern unsigned int error_message_count; -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); - } -} +/* When non-zero, error_at_line() prints each distinct file:line only once. */ +extern int error_one_per_line; + +/* Optional hook to print the program name instead of the default. */ +extern void (*error_print_progname)(void); + +extern void error(int __status, int __errnum, const char *__format, ...) + __attribute__((__format__(__printf__, 3, 4))); + +extern void error_at_line(int __status, int __errnum, const char *__fname, + unsigned int __lineno, const char *__format, ...) + __attribute__((__format__(__printf__, 5, 6))); #ifdef __cplusplus } diff --git a/src/header/error/mod.rs b/src/header/error/mod.rs new file mode 100644 index 0000000000..2eb6ae04d5 --- /dev/null +++ b/src/header/error/mod.rs @@ -0,0 +1,141 @@ +//! `error.h` implementation. +//! +//! GNU extension providing `error()` and `error_at_line()`: print the program +//! name, a formatted message, and optionally `strerror(errnum)` to stderr, then +//! `exit(status)` when `status` is non-zero. Supported by glibc and musl; relied +//! on by a great deal of GNU tooling (m4/gnulib, coreutils, elfutils). +//! +//! Implemented as real exported C symbols (not header-only inlines) so that +//! autotools/gnulib link-detect the system `error()` and do NOT compile their +//! own replacement — a header-only inline collides with gnulib's `lib/error.c` +//! ("redefinition of 'error'"). +//! +//! See . + +use core::{ + ffi::{VaList as va_list, c_char, c_int, c_uint}, + ptr, +}; + +use crate::{ + header::{ + stdio::{self, fputc, fputs, fprintf, vfprintf}, + stdlib::exit, + string::strerror, + }, + platform, +}; + +/// Count of messages emitted by [`error`]/[`error_at_line`] (glibc global). +#[unsafe(no_mangle)] +pub static mut error_message_count: c_uint = 0; + +/// When non-zero, [`error_at_line`] suppresses repeated messages for the same +/// file/line pair (glibc global). +#[unsafe(no_mangle)] +pub static mut error_one_per_line: c_int = 0; + +/// Optional hook printing the program name in place of the default (glibc global). +#[unsafe(no_mangle)] +pub static mut error_print_progname: Option = None; + +// Last file/line reported, for `error_one_per_line` de-duplication. +static mut LAST_FILE: *const c_char = ptr::null(); +static mut LAST_LINE: c_uint = 0; + +unsafe fn print_progname() { + let stderr = unsafe { stdio::stderr }; + if let Some(hook) = unsafe { error_print_progname } { + unsafe { hook() }; + } else { + // glibc prints program_invocation_name (the full argv[0]). + unsafe { fprintf(stderr, c"%s".as_ptr(), platform::program_invocation_name) }; + } +} + +unsafe fn error_body(errnum: c_int, fmt: *const c_char, args: va_list) { + let stderr = unsafe { stdio::stderr }; + // glibc flushes stdout so ordering with normal output is sane. + unsafe { stdio::fflush(stdio::stdout) }; + + unsafe { print_progname() }; + + if !fmt.is_null() { + unsafe { + fputs(c": ".as_ptr(), stderr); + vfprintf(stderr, fmt, args); + } + } + if errnum != 0 { + unsafe { fprintf(stderr, c": %s".as_ptr(), strerror(errnum)) }; + } + unsafe { fputc(b'\n'.into(), stderr) }; + + unsafe { error_message_count = error_message_count.wrapping_add(1) }; +} + +/// See . +/// +/// Prints `program-name: message: strerror(errnum)` to stderr (the `: strerror` +/// part only when `errnum != 0`), increments [`error_message_count`], and calls +/// `exit(status)` when `status` is non-zero. +/// +/// # Safety +/// `format` is a valid printf format matching the variadic arguments. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn error(status: c_int, errnum: c_int, format: *const c_char, args: ...) { + unsafe { error_body(errnum, format, args) }; + if status != 0 { + unsafe { exit(status) }; + } +} + +/// As [`error`], but also reports `fname:lineno` after the program name. +/// +/// When [`error_one_per_line`] is non-zero, consecutive calls with the same +/// `fname`/`lineno` after the first are suppressed. +/// +/// # Safety +/// As for [`error`]; `fname` is null or a valid C string. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn error_at_line( + status: c_int, + errnum: c_int, + fname: *const c_char, + lineno: c_uint, + format: *const c_char, + args: ... +) { + if unsafe { error_one_per_line } != 0 + && !fname.is_null() + && unsafe { fname == LAST_FILE && lineno == LAST_LINE } + { + return; + } + unsafe { + LAST_FILE = fname; + LAST_LINE = lineno; + } + + let stderr = unsafe { stdio::stderr }; + unsafe { stdio::fflush(stdio::stdout) }; + unsafe { print_progname() }; + if !fname.is_null() { + unsafe { fprintf(stderr, c":%s:%u".as_ptr(), fname, lineno) }; + } + if !format.is_null() { + unsafe { + fputs(c": ".as_ptr(), stderr); + vfprintf(stderr, format, args); + } + } + if errnum != 0 { + unsafe { fprintf(stderr, c": %s".as_ptr(), strerror(errnum)) }; + } + unsafe { fputc(b'\n'.into(), stderr) }; + unsafe { error_message_count = error_message_count.wrapping_add(1) }; + + if status != 0 { + unsafe { exit(status) }; + } +} diff --git a/src/header/mod.rs b/src/header/mod.rs index ce9e5383df..48aa4e48eb 100644 --- a/src/header/mod.rs +++ b/src/header/mod.rs @@ -93,6 +93,7 @@ pub mod dlfcn; pub mod elf; pub mod endian; pub mod err; +pub mod error; pub mod errno; pub mod fcntl; pub mod linux_kd;