log_c2rust/
lib.rs

1//! This crate implements a *backend* to the C logger API in crate `logger`.
2//!
3//! This backend delegates to the Rust logging framework in crate `log`.
4//!
5//! The only other backend to `logger` that we currently use is the
6//! `ShimLogger`.  Once that is migrated to Rust and hooks directly into the
7//! Rust `log` framework, this crate will be the *only* back-end for `logger`.
8//! At that point `logger` can be merged into this crate and simplified.
9//!
10//! TODO: This crate should be `no_std`.
11
12// https://github.com/rust-lang/rfcs/blob/master/text/2585-unsafe-block-in-unsafe-fn.md
13#![deny(unsafe_op_in_unsafe_fn)]
14
15use std::ffi::CStr;
16use std::os::raw::{c_char, c_int};
17
18use log::log_enabled;
19
20/// Flush Rust's log::logger().
21#[unsafe(no_mangle)]
22pub extern "C-unwind" fn rustlogger_flush() {
23    log::logger().flush();
24}
25
26/// Returns the `str` pointed to by `ptr` if it's non-NULL and points
27/// to a UTF-8 null-terminated string.
28/// # Safety
29///
30/// `ptr` must point a NULL-terminated C String if non-null, and must
31/// be immutable for the lifetime of the returned `str`.
32unsafe fn optional_str(ptr: *const c_char) -> Option<&'static str> {
33    if ptr.is_null() {
34        None
35    } else {
36        // Safe if caller obeyed doc'd preconditions.
37        unsafe { std::ffi::CStr::from_ptr(ptr).to_str().ok() }
38    }
39}
40
41pub fn c_to_rust_log_level(level: logger::LogLevel) -> Option<log::Level> {
42    use log::Level::*;
43    match level {
44        logger::_LogLevel_LOGLEVEL_ERROR => Some(Error),
45        logger::_LogLevel_LOGLEVEL_WARNING => Some(Warn),
46        logger::_LogLevel_LOGLEVEL_INFO => Some(Info),
47        logger::_LogLevel_LOGLEVEL_DEBUG => Some(Debug),
48        logger::_LogLevel_LOGLEVEL_TRACE => Some(Trace),
49        logger::_LogLevel_LOGLEVEL_UNSET => None,
50        _ => panic!("Unexpected log level {}", level),
51    }
52}
53
54/// Whether logging is currently enabled for `level`.
55#[unsafe(no_mangle)]
56pub extern "C-unwind" fn rustlogger_isEnabled(level: logger::LogLevel) -> c_int {
57    let level = c_to_rust_log_level(level).unwrap();
58    log_enabled!(level).into()
59}
60
61/// Log to Rust's log::logger().
62///
63/// # Safety
64///
65/// Pointer args must be safely dereferenceable. `format` and `args` must
66/// follow the rules of `sprintf(3)`.
67#[unsafe(no_mangle)]
68pub unsafe extern "C-unwind" fn rustlogger_log(
69    level: logger::LogLevel,
70    file_name: *const c_char,
71    fn_name: *const c_char,
72    line: i32,
73    format: *const c_char,
74    args: va_list::VaList,
75) {
76    let log_level = c_to_rust_log_level(level).unwrap();
77
78    if !log_enabled!(log_level) {
79        return;
80    }
81
82    assert!(!format.is_null());
83    let format = unsafe { CStr::from_ptr(format) };
84    let mut msgbuf = formatting_nostd::FormatBuffer::<500>::new();
85    // SAFETY: Safe if caller provided valid format and args.
86    unsafe { msgbuf.sprintf(format, args) };
87
88    log::logger().log(
89        &log::Record::builder()
90            .level(log_level)
91            // SAFETY: file_name is statically allocated.
92            .file_static(unsafe { optional_str(file_name) })
93            .line(Some(u32::try_from(line).unwrap()))
94            // SAFETY: fn_name is statically allocated.
95            .module_path_static(unsafe { optional_str(fn_name) })
96            .args(format_args!("{}", msgbuf))
97            .build(),
98    );
99}