Implement BSD's err.h
This commit is contained in:
committed by
Jeremy Soller
parent
6e165e2ef0
commit
a4826bd48c
@@ -0,0 +1,5 @@
|
||||
sys_includes = ["stdarg.h", "stdio.h", "features.h"]
|
||||
include_guard = "_RELIBC_ERR_H"
|
||||
language = "C"
|
||||
no_includes = true
|
||||
cpp_compat = true
|
||||
@@ -0,0 +1,205 @@
|
||||
//! `err.h` implementation.
|
||||
//!
|
||||
//! See <https://man.freebsd.org/cgi/man.cgi?err>
|
||||
//!
|
||||
//! `err.h` is a BSD extension to the C library which provides functions for printing formatted
|
||||
//! errors. Errors are printed to [`stdio::stderr`] by default or to a file set by
|
||||
//! [`err_set_file`]. This family of functions is non-portable, but it is also supported by `glibc`
|
||||
//! and `musl`.
|
||||
//!
|
||||
//! The functions come in sets of three. Each of them print the program binary name (the last path
|
||||
//! segment of arg[0]) and an optional user message along with these differences:
|
||||
//! * No suffix: Prints an error message for ERRNO based on [`strerror`]
|
||||
//! * `c` suffix: Prints an error message for an arbitrary error code
|
||||
//! * `x` suffix: Does not print an error code
|
||||
//!
|
||||
//! For example, `err` does not have a suffix so it would print the program name, the user message,
|
||||
//! and an error string for ERRNO. `errc` would operate in the same way except the functions takes
|
||||
//! an error code for which to print an error string.
|
||||
|
||||
// Allow is intentional. Almost every line of the simple functions below are unsafe.
|
||||
// unsafe_op_in_unsafe_fn only adds visual noise or a needless indentation here.
|
||||
#![allow(unsafe_op_in_unsafe_fn)]
|
||||
|
||||
use core::{
|
||||
ffi::{c_char, c_int, VaList as va_list},
|
||||
ptr,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
c_str::CStr,
|
||||
header::{
|
||||
stdio::{self, fprintf, fputc, fputs, vfprintf, FILE},
|
||||
stdlib::exit,
|
||||
string::strerror,
|
||||
},
|
||||
platform::{self, ERRNO},
|
||||
};
|
||||
|
||||
// Optional callback from user invoked on exit.
|
||||
type ExitCallback = Option<unsafe extern "C" fn(c_int)>;
|
||||
static mut on_exit: ExitCallback = None;
|
||||
|
||||
// Messages from this module are written to this sink.
|
||||
static mut error_sink: *mut FILE = ptr::null_mut();
|
||||
|
||||
/// Set global [`FILE`] sink to write errors and warnings.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn err_set_file(fp: *mut FILE) {
|
||||
if fp.is_null() {
|
||||
error_sink = stdio::stderr;
|
||||
} else {
|
||||
error_sink = fp;
|
||||
}
|
||||
}
|
||||
|
||||
/// Set or remove a callback to invoke before exiting on error.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn err_set_exit(ef: ExitCallback) {
|
||||
on_exit = ef;
|
||||
}
|
||||
|
||||
/// Print a user message then an error message for [`ERRNO`] followed by exiting with `eval`.
|
||||
///
|
||||
/// The message format is `progname: fmt: strerror(ERRNO)`
|
||||
///
|
||||
/// # Return
|
||||
/// Does not return. Exits with `eval` as an error code.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn err(eval: c_int, fmt: *const c_char, mut va_list: ...) -> ! {
|
||||
let code = Some(ERRNO.get());
|
||||
err_exit(eval, code, fmt, va_list.as_va_list())
|
||||
}
|
||||
|
||||
/// Print a user message then an error message for `code` before exiting with `eval` as a return.
|
||||
///
|
||||
/// The message format is `progname: fmt: strerror(code)`
|
||||
///
|
||||
/// # Return
|
||||
/// Exits with `eval` as an error code.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn errc(eval: c_int, code: c_int, fmt: *const c_char, mut va_list: ...) -> ! {
|
||||
err_exit(eval, Some(code), fmt, va_list.as_va_list())
|
||||
}
|
||||
|
||||
/// Print a user message then exits with `eval` as a return.
|
||||
///
|
||||
/// The message format is `progname: fmt`
|
||||
///
|
||||
/// # Return
|
||||
/// Exits with `eval` as an error code.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn errx(eval: c_int, fmt: *const c_char, mut va_list: ...) -> ! {
|
||||
err_exit(eval, None, fmt, va_list.as_va_list())
|
||||
}
|
||||
|
||||
/// Print a user message and then an error message for [`ERRNO`].
|
||||
///
|
||||
/// The message format is `progname: fmt: strerror(ERRNO)`
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn warn(fmt: *const c_char, mut va_list: ...) {
|
||||
let code = Some(ERRNO.get());
|
||||
display_message(code, fmt, va_list.as_va_list());
|
||||
}
|
||||
|
||||
/// Print a user message then an error message for `code`.
|
||||
///
|
||||
/// The message format is `progname: fmt: strerror(code)`
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn warnc(code: c_int, fmt: *const c_char, mut va_list: ...) {
|
||||
display_message(Some(code), fmt, va_list.as_va_list());
|
||||
}
|
||||
|
||||
/// Print a user message as a warning.
|
||||
///
|
||||
/// The message format is `progname: fmt`
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn warnx(fmt: *const c_char, mut va_list: ...) {
|
||||
display_message(None, fmt, va_list.as_va_list());
|
||||
}
|
||||
|
||||
/// See [`err`].
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn verr(eval: c_int, fmt: *const c_char, args: va_list) -> ! {
|
||||
let code = Some(ERRNO.get());
|
||||
err_exit(eval, code, fmt, args);
|
||||
}
|
||||
|
||||
/// See [`errc`].
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn verrc(eval: c_int, code: c_int, fmt: *const c_char, args: va_list) -> ! {
|
||||
err_exit(eval, Some(code), fmt, args)
|
||||
}
|
||||
|
||||
/// See [`errx`];
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn verrx(eval: c_int, fmt: *const c_char, args: va_list) -> ! {
|
||||
err_exit(eval, None, fmt, args)
|
||||
}
|
||||
|
||||
/// See [`warn`].
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn vwarn(fmt: *const c_char, args: va_list) {
|
||||
let code = Some(ERRNO.get());
|
||||
display_message(code, fmt, args);
|
||||
}
|
||||
|
||||
/// See [`warnc`].
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn vwarnc(code: c_int, fmt: *const c_char, args: va_list) {
|
||||
display_message(Some(code), fmt, args);
|
||||
}
|
||||
|
||||
/// See [`warnx`].
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn vwarnx(fmt: *const c_char, args: va_list) {
|
||||
display_message(None, fmt, args);
|
||||
}
|
||||
|
||||
// Write error messages for err and warn to the currently set sink.
|
||||
unsafe fn display_message(code: Option<c_int>, fmt: *const c_char, args: va_list) {
|
||||
/// SAFETY:
|
||||
/// * error_sink is only null once on start but otherwise always stderr or a user set file
|
||||
/// * User is trusted to pass in a valid file pointer if err_set_file is used
|
||||
if error_sink.is_null() {
|
||||
error_sink = stdio::stderr;
|
||||
}
|
||||
let sink = error_sink;
|
||||
|
||||
// "progname:" is always printed
|
||||
// SAFETY:
|
||||
// * program_invocation_short_name is never null as it is set on start
|
||||
// * program_invocation_short_name is not globally mutable so the user can't mangle it
|
||||
fprintf(
|
||||
sink,
|
||||
c"%s".as_ptr(),
|
||||
platform::program_invocation_short_name,
|
||||
);
|
||||
|
||||
// Print user message if any
|
||||
if !fmt.is_null() {
|
||||
fputs(c": ".as_ptr(), sink);
|
||||
vfprintf(sink, fmt, args);
|
||||
}
|
||||
|
||||
// Print error message for non-x functions
|
||||
if let Some(code) = code {
|
||||
let message = strerror(code);
|
||||
fprintf(sink, c": %s".as_ptr(), message);
|
||||
}
|
||||
|
||||
// Always write new line
|
||||
fputc(b'\n'.into(), sink);
|
||||
}
|
||||
|
||||
// Write an error message as per err and then exit.
|
||||
unsafe fn err_exit(eval: c_int, code: Option<c_int>, fmt: *const c_char, args: va_list) -> ! {
|
||||
display_message(code, fmt, args);
|
||||
|
||||
if let Some(callback) = on_exit {
|
||||
// errx will hit the unwrap.
|
||||
callback(code.unwrap_or_else(|| ERRNO.get()));
|
||||
}
|
||||
|
||||
exit(eval);
|
||||
}
|
||||
@@ -18,6 +18,7 @@ pub mod dl_tls;
|
||||
pub mod dlfcn;
|
||||
pub mod elf;
|
||||
pub mod endian;
|
||||
pub mod err;
|
||||
pub mod errno;
|
||||
pub mod fcntl;
|
||||
pub mod float;
|
||||
|
||||
@@ -15,6 +15,7 @@ EXPECT_NAMES=\
|
||||
destructor \
|
||||
dirent/scandir \
|
||||
endian \
|
||||
err \
|
||||
errno \
|
||||
error \
|
||||
fcntl/create \
|
||||
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
#include <err.h>
|
||||
#include <errno.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
__attribute__((nonnull(2)))
|
||||
static void vwarn_test(int code, const char* fmt, ...) {
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
vwarnc(code, fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
static void log_to_stdout(void) {
|
||||
err_set_file(stdout);
|
||||
warnc(ENOENT, "Dang it, Bobby.");
|
||||
err_set_file(NULL);
|
||||
}
|
||||
|
||||
void user_callback(int code) {
|
||||
printf("Exiting due to error code: %d\n", code);
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
err_set_exit(user_callback);
|
||||
|
||||
// Set errno to a known value for verifiable messages
|
||||
// (Also, "Owner died" is just too funny not to use)
|
||||
errno = EOWNERDEAD;
|
||||
|
||||
warn("Ran out of coffee");
|
||||
warnx("%s pulled out your ethernet cable", "Cat");
|
||||
warnc(EACCES, "Eat %d cookies", 42);
|
||||
|
||||
vwarn_test(EBADE, "Potato, %s", "krumpli");
|
||||
|
||||
// Set the sink to stdout then back to stderr
|
||||
log_to_stdout();
|
||||
warnc(EPERM,
|
||||
"I'm sorry, Dave. I'm afraid I can't do that."
|
||||
);
|
||||
|
||||
// As long as one err function works they should all work since
|
||||
// two functions handle everything internally.
|
||||
errc(EXIT_SUCCESS, EUSERS, "Bye. It's crowded.");
|
||||
|
||||
// Unreachable
|
||||
puts("err did not exit");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
err: Ran out of coffee: Owner died
|
||||
err: Cat pulled out your ethernet cable
|
||||
err: Eat 42 cookies: Permission denied
|
||||
err: Potato, krumpli: Invalid exchange
|
||||
err: I'm sorry, Dave. I'm afraid I can't do that.: Operation not permitted
|
||||
err: Bye. It's crowded.: Too many users
|
||||
@@ -0,0 +1,2 @@
|
||||
err: Dang it, Bobby.: No such file or directory
|
||||
Exiting due to error code: 87
|
||||
@@ -0,0 +1,6 @@
|
||||
err: Ran out of coffee: Owner died
|
||||
err: Cat pulled out your ethernet cable
|
||||
err: Eat 42 cookies: Permission denied
|
||||
err: Potato, krumpli: Invalid exchange
|
||||
err: I'm sorry, Dave. I'm afraid I can't do that.: Operation not permitted
|
||||
err: Bye. It's crowded.: Too many users
|
||||
@@ -0,0 +1,2 @@
|
||||
err: Dang it, Bobby.: No such file or directory
|
||||
Exiting due to error code: 87
|
||||
@@ -17,6 +17,7 @@ EXPECT_NAMES=(\
|
||||
destructor \
|
||||
dirent/scandir \
|
||||
endian \
|
||||
err \
|
||||
errno \
|
||||
error \
|
||||
fcntl/create \
|
||||
|
||||
Reference in New Issue
Block a user