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:
+21
-41
@@ -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
|
||||
}
|
||||
|
||||
@@ -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