Skip to main content

relibc/header/err/
mod.rs

1//! `err.h` implementation.
2//!
3//! See <https://man.freebsd.org/cgi/man.cgi?err>
4//!
5//! `err.h` is a BSD extension to the C library which provides functions for printing formatted
6//! errors. Errors are printed to [`stdio::stderr`] by default or to a file set by
7//! [`err_set_file`]. This family of functions is non-portable, but it is also supported by `glibc`
8//! and `musl`.
9//!
10//! The functions come in sets of three. Each of them print the program binary name (the last path
11//! segment of `argv[0]`) and an optional user message along with these differences:
12//! * No suffix: Prints an error message for ERRNO based on [`strerror`]
13//! * `c` suffix: Prints an error message for an arbitrary error code
14//! * `x` suffix: Does not print an error code
15//!
16//! For example, `err` does not have a suffix so it would print the program name, the user message,
17//! and an error string for ERRNO. `errc` would operate in the same way except the functions takes
18//! an error code for which to print an error string.
19
20use core::{
21    ffi::{VaList as va_list, c_char, c_int},
22    ptr,
23};
24
25use crate::{
26    header::{
27        stdio::{self, FILE, fprintf, fputc, fputs, vfprintf},
28        stdlib::exit,
29        string::strerror,
30    },
31    platform::{self, ERRNO},
32};
33
34// Optional callback from user invoked on exit.
35type ExitCallback = Option<unsafe extern "C" fn(c_int)>;
36static mut ON_EXIT: ExitCallback = None;
37
38// Messages from this module are written to this sink.
39static mut ERROR_SINK: *mut FILE = ptr::null_mut();
40
41/// Set global [`FILE`] sink to write errors and warnings.
42#[unsafe(no_mangle)]
43pub unsafe extern "C" fn err_set_file(fp: *mut FILE) {
44    if fp.is_null() {
45        unsafe {
46            ERROR_SINK = stdio::stderr;
47        }
48    } else {
49        unsafe {
50            ERROR_SINK = fp;
51        }
52    }
53}
54
55/// Set or remove a callback to invoke before exiting on error.
56#[unsafe(no_mangle)]
57pub unsafe extern "C" fn err_set_exit(ef: ExitCallback) {
58    unsafe {
59        ON_EXIT = ef;
60    }
61}
62
63/// Print a user message then an error message for [`ERRNO`] followed by exiting with `eval`.
64///
65/// The message format is `progname: fmt: strerror(ERRNO)`
66///
67/// # Return
68/// Does not return. Exits with `eval` as an error code.
69#[unsafe(no_mangle)]
70pub unsafe extern "C" fn err(eval: c_int, fmt: *const c_char, va_list: ...) -> ! {
71    let code = Some(ERRNO.get());
72    unsafe { err_exit(eval, code, fmt, va_list) }
73}
74
75/// Print a user message then an error message for `code` before exiting with `eval` as a return.
76///
77/// The message format is `progname: fmt: strerror(code)`
78///
79/// # Return
80/// Exits with `eval` as an error code.
81#[unsafe(no_mangle)]
82pub unsafe extern "C" fn errc(eval: c_int, code: c_int, fmt: *const c_char, va_list: ...) -> ! {
83    unsafe { err_exit(eval, Some(code), fmt, va_list) }
84}
85
86/// Print a user message then exits with `eval` as a return.
87///
88/// The message format is `progname: fmt`
89///
90/// # Return
91/// Exits with `eval` as an error code.
92#[unsafe(no_mangle)]
93pub unsafe extern "C" fn errx(eval: c_int, fmt: *const c_char, va_list: ...) -> ! {
94    unsafe { err_exit(eval, None, fmt, va_list) }
95}
96
97/// Print a user message and then an error message for [`ERRNO`].
98///
99/// The message format is `progname: fmt: strerror(ERRNO)`
100#[unsafe(no_mangle)]
101pub unsafe extern "C" fn warn(fmt: *const c_char, va_list: ...) {
102    let code = Some(ERRNO.get());
103    unsafe {
104        display_message(code, fmt, va_list);
105    }
106}
107
108/// Print a user message then an error message for `code`.
109///
110/// The message format is `progname: fmt: strerror(code)`
111#[unsafe(no_mangle)]
112pub unsafe extern "C" fn warnc(code: c_int, fmt: *const c_char, va_list: ...) {
113    unsafe {
114        display_message(Some(code), fmt, va_list);
115    }
116}
117
118/// Print a user message as a warning.
119///
120/// The message format is `progname: fmt`
121#[unsafe(no_mangle)]
122pub unsafe extern "C" fn warnx(fmt: *const c_char, va_list: ...) {
123    unsafe {
124        display_message(None, fmt, va_list);
125    }
126}
127
128/// See [`err`].
129#[unsafe(no_mangle)]
130pub unsafe extern "C" fn verr(eval: c_int, fmt: *const c_char, args: va_list) -> ! {
131    let code = Some(ERRNO.get());
132    unsafe {
133        err_exit(eval, code, fmt, args);
134    }
135}
136
137/// See [`errc`].
138#[unsafe(no_mangle)]
139pub unsafe extern "C" fn verrc(eval: c_int, code: c_int, fmt: *const c_char, args: va_list) -> ! {
140    unsafe { err_exit(eval, Some(code), fmt, args) }
141}
142
143/// See [`errx`];
144#[unsafe(no_mangle)]
145pub unsafe extern "C" fn verrx(eval: c_int, fmt: *const c_char, args: va_list) -> ! {
146    unsafe { err_exit(eval, None, fmt, args) }
147}
148
149/// See [`warn`].
150#[unsafe(no_mangle)]
151pub unsafe extern "C" fn vwarn(fmt: *const c_char, args: va_list) {
152    let code = Some(ERRNO.get());
153    unsafe {
154        display_message(code, fmt, args);
155    }
156}
157
158/// See [`warnc`].
159#[unsafe(no_mangle)]
160pub unsafe extern "C" fn vwarnc(code: c_int, fmt: *const c_char, args: va_list) {
161    unsafe {
162        display_message(Some(code), fmt, args);
163    }
164}
165
166/// See [`warnx`].
167#[unsafe(no_mangle)]
168pub unsafe extern "C" fn vwarnx(fmt: *const c_char, args: va_list) {
169    unsafe {
170        display_message(None, fmt, args);
171    }
172}
173
174// Write error messages for err and warn to the currently set sink.
175unsafe fn display_message(code: Option<c_int>, fmt: *const c_char, args: va_list) {
176    // SAFETY:
177    // * ERROR_SINK is only null once on start but otherwise always stderr or a user set file
178    // * User is trusted to pass in a valid file pointer if err_set_file is used
179    if unsafe { ERROR_SINK.is_null() } {
180        unsafe {
181            ERROR_SINK = stdio::stderr;
182        }
183    }
184    let sink = unsafe { ERROR_SINK };
185
186    // "progname:" is always printed
187    // SAFETY:
188    // * program_invocation_short_name is never null as it is set on start
189    // * program_invocation_short_name is not globally mutable so the user can't mangle it
190    unsafe {
191        fprintf(
192            sink,
193            c"%s".as_ptr(),
194            platform::program_invocation_short_name,
195        );
196    }
197
198    // Print user message if any
199    if !fmt.is_null() {
200        unsafe {
201            fputs(c": ".as_ptr(), sink);
202            vfprintf(sink, fmt, args);
203        }
204    }
205
206    // Print error message for non-x functions
207    if let Some(code) = code {
208        unsafe {
209            let message = strerror(code);
210            fprintf(sink, c": %s".as_ptr(), message);
211        }
212    }
213
214    // Always write new line
215    unsafe {
216        fputc(b'\n'.into(), sink);
217    }
218}
219
220// Write an error message as per err and then exit.
221unsafe fn err_exit(eval: c_int, code: Option<c_int>, fmt: *const c_char, args: va_list) -> ! {
222    unsafe {
223        display_message(code, fmt, args);
224    }
225
226    if let Some(callback) = unsafe { ON_EXIT } {
227        // errx will hit the unwrap.
228        unsafe {
229            callback(code.unwrap_or_else(|| ERRNO.get()));
230        }
231    }
232
233    unsafe {
234        exit(eval);
235    }
236}