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
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
use std::ffi::c_char;
use std::ffi::c_int;
use std::ffi::c_void;
use std::io;
use std::io::Write;
use std::mem;
use std::sync::Mutex;

use crate::util::LazyLock;

/// An enum representing the different supported print levels.
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
#[repr(u32)]
pub enum PrintLevel {
    /// Print warnings and more severe messages.
    Warn = libbpf_sys::LIBBPF_WARN,
    /// Print general information and more severe messages.
    Info = libbpf_sys::LIBBPF_INFO,
    /// Print debug information and more severe messages.
    Debug = libbpf_sys::LIBBPF_DEBUG,
}

impl From<libbpf_sys::libbpf_print_level> for PrintLevel {
    fn from(level: libbpf_sys::libbpf_print_level) -> Self {
        match level {
            libbpf_sys::LIBBPF_WARN => Self::Warn,
            libbpf_sys::LIBBPF_INFO => Self::Info,
            libbpf_sys::LIBBPF_DEBUG => Self::Debug,
            // shouldn't happen, but anything unknown becomes the highest level
            _ => Self::Warn,
        }
    }
}

/// The type of callback functions suitable for being provided to [`set_print`].
pub type PrintCallback = fn(PrintLevel, String);

/// Mimic the default print functionality of libbpf. This way if the user calls `get_print` when no
/// previous callback had been set, with the intention of restoring it, everything will behave as
/// expected.
fn default_callback(_lvl: PrintLevel, msg: String) {
    let _ = io::stderr().write(msg.as_bytes());
}

// While we can't say that set_print is thread-safe, because we shouldn't assume that of
// libbpf_set_print, we should still make sure that things are sane on the rust side of things.
// Therefore we are using a lock to keep the log level and the callback in sync.
//
// We don't do anything that can panic with the lock held, so we'll unconditionally unwrap() when
// locking the mutex.
//
// Note that default print behavior ignores debug messages.
static PRINT_CB: LazyLock<Mutex<Option<(PrintLevel, PrintCallback)>>> =
    LazyLock::new(|| Mutex::new(Some((PrintLevel::Info, default_callback))));

extern "C" fn outer_print_cb(
    level: libbpf_sys::libbpf_print_level,
    fmtstr: *const c_char,
    // bindgen generated va_list type varies on different platforms, so just use void pointer
    // instead. It's safe because this argument is always a pointer.
    // The pointer of this function would be transmuted and passing to libbpf_set_print below.
    // See <https://github.com/rust-lang/rust-bindgen/issues/2631>
    va_list: *mut c_void,
) -> c_int {
    let level = level.into();
    if let Some((min_level, func)) = { *PRINT_CB.lock().unwrap() } {
        if level <= min_level {
            let msg = match unsafe { vsprintf::vsprintf(fmtstr, va_list) } {
                Ok(s) => s,
                Err(e) => format!("Failed to parse libbpf output: {e}"),
            };
            func(level, msg);
        }
    }
    0 // return value is ignored by libbpf
}

/// Set a callback to receive log messages from libbpf, instead of printing them to stderr.
///
/// # Arguments
///
/// * `callback` - Either a tuple `(min_level, function)` where `min_level` is the lowest priority
///   log message to handle, or `None` to disable all printing.
///
/// This overrides (and is overridden by) [`ObjectBuilder::debug`][crate::ObjectBuilder::debug]
///
/// # Examples
///
/// To pass all messages to the `log` crate:
///
/// ```
/// use libbpf_rs::{PrintLevel, set_print};
///
/// fn print_to_log(level: PrintLevel, msg: String) {
///     match level {
///         PrintLevel::Debug => log::debug!("{}", msg),
///         PrintLevel::Info => log::info!("{}", msg),
///         PrintLevel::Warn => log::warn!("{}", msg),
///     }
/// }
///
/// set_print(Some((PrintLevel::Debug, print_to_log)));
/// ```
///
/// To disable printing completely:
///
/// ```
/// use libbpf_rs::set_print;
/// set_print(None);
/// ```
///
/// To temporarliy suppress output:
///
/// ```
/// use libbpf_rs::set_print;
///
/// let prev = set_print(None);
/// // do things quietly
/// set_print(prev);
/// ```
pub fn set_print(
    mut callback: Option<(PrintLevel, PrintCallback)>,
) -> Option<(PrintLevel, PrintCallback)> {
    // # Safety
    // outer_print_cb has the same function signature as libbpf_print_fn_t
    let real_cb: libbpf_sys::libbpf_print_fn_t =
        unsafe { Some(mem::transmute(outer_print_cb as *const ())) };
    let real_cb: libbpf_sys::libbpf_print_fn_t = callback.as_ref().and(real_cb);
    mem::swap(&mut callback, &mut *PRINT_CB.lock().unwrap());
    unsafe { libbpf_sys::libbpf_set_print(real_cb) };
    callback
}

/// Return the current print callback and level.
///
/// # Examples
///
/// To temporarily suppress output:
///
/// ```
/// use libbpf_rs::{get_print, set_print};
///
/// let prev = get_print();
/// set_print(None);
/// // do things quietly
/// set_print(prev);
/// ```
pub fn get_print() -> Option<(PrintLevel, PrintCallback)> {
    *PRINT_CB.lock().unwrap()
}