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
+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;