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:
@@ -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) };
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user