log_c2rust/
lib.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
//! This crate implements a *backend* to the C logger API in crate `logger`.
//!
//! This backend delegates to the Rust logging framework in crate `log`.
//!
//! The only other backend to `logger` that we currently use is the
//! `ShimLogger`.  Once that is migrated to Rust and hooks directly into the
//! Rust `log` framework, this crate will be the *only* back-end for `logger`.
//! At that point `logger` can be merged into this crate and simplified.
//!
//! TODO: This crate should be `no_std`.

// https://github.com/rust-lang/rfcs/blob/master/text/2585-unsafe-block-in-unsafe-fn.md
#![deny(unsafe_op_in_unsafe_fn)]

use std::ffi::CStr;
use std::os::raw::{c_char, c_int};

use log::log_enabled;

/// Flush Rust's log::logger().
#[no_mangle]
pub extern "C-unwind" fn rustlogger_flush() {
    log::logger().flush();
}

/// Returns the `str` pointed to by `ptr` if it's non-NULL and points
/// to a UTF-8 null-terminated string.
/// # Safety
///
/// `ptr` must point a NULL-terminated C String if non-null, and must
/// be immutable for the lifetime of the returned `str`.
unsafe fn optional_str(ptr: *const c_char) -> Option<&'static str> {
    if ptr.is_null() {
        None
    } else {
        // Safe if caller obeyed doc'd preconditions.
        unsafe { std::ffi::CStr::from_ptr(ptr).to_str().ok() }
    }
}

pub fn c_to_rust_log_level(level: logger::LogLevel) -> Option<log::Level> {
    use log::Level::*;
    match level {
        logger::_LogLevel_LOGLEVEL_ERROR => Some(Error),
        logger::_LogLevel_LOGLEVEL_WARNING => Some(Warn),
        logger::_LogLevel_LOGLEVEL_INFO => Some(Info),
        logger::_LogLevel_LOGLEVEL_DEBUG => Some(Debug),
        logger::_LogLevel_LOGLEVEL_TRACE => Some(Trace),
        logger::_LogLevel_LOGLEVEL_UNSET => None,
        _ => panic!("Unexpected log level {}", level),
    }
}

/// Whether logging is currently enabled for `level`.
#[no_mangle]
pub extern "C-unwind" fn rustlogger_isEnabled(level: logger::LogLevel) -> c_int {
    let level = c_to_rust_log_level(level).unwrap();
    log_enabled!(level).into()
}

/// Log to Rust's log::logger().
///
/// # Safety
///
/// Pointer args must be safely dereferenceable. `format` and `args` must
/// follow the rules of `sprintf(3)`.
#[no_mangle]
pub unsafe extern "C-unwind" fn rustlogger_log(
    level: logger::LogLevel,
    file_name: *const c_char,
    fn_name: *const c_char,
    line: i32,
    format: *const c_char,
    args: va_list::VaList,
) {
    let log_level = c_to_rust_log_level(level).unwrap();

    if !log_enabled!(log_level) {
        return;
    }

    assert!(!format.is_null());
    let format = unsafe { CStr::from_ptr(format) };
    let mut msgbuf = formatting_nostd::FormatBuffer::<500>::new();
    // SAFETY: Safe if caller provided valid format and args.
    unsafe { msgbuf.sprintf(format, args) };

    log::logger().log(
        &log::Record::builder()
            .level(log_level)
            // SAFETY: file_name is statically allocated.
            .file_static(unsafe { optional_str(file_name) })
            .line(Some(u32::try_from(line).unwrap()))
            // SAFETY: fn_name is statically allocated.
            .module_path_static(unsafe { optional_str(fn_name) })
            .args(format_args!("{}", msgbuf))
            .build(),
    );
}