relibc: implement error()/error_at_line() as real symbols (not header inlines)

The header-only static-inline error()/error_at_line() collided with gnulib's own
lib/error.c ('redefinition of error', building m4): autotools link-detects a
system error(), skips its replacement, then the inline in <error.h> clashes with
gnulib's definition. Implement them properly in relibc (src/header/error) with
the glibc globals (error_message_count, error_one_per_line, error_print_progname)
and make <error.h> a pure extern declaration. Fixes m4/gnulib and keeps
mesa/libelf (the original <error.h> consumers) working via the real symbol.
This commit is contained in:
2026-08-01 04:30:41 +03:00
parent 29752835c2
commit 1a6d3221b2
3 changed files with 163 additions and 41 deletions
+21 -41
View File
@@ -2,56 +2,36 @@
*
* 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.
* 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 <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);
}
}
/* 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
}
+141
View File
@@ -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 <https://www.gnu.org/software/libc/manual/html_node/Error-Messages.html>.
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<unsafe extern "C" fn()> = 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 <https://www.gnu.org/software/libc/manual/html_node/Error-Messages.html>.
///
/// 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) };
}
}
+1
View File
@@ -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;