git2/tracing.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 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
use std::{
ffi::CStr,
sync::atomic::{AtomicPtr, Ordering},
};
use libc::{c_char, c_int};
use crate::{panic, raw, util::Binding, Error};
/// Available tracing levels. When tracing is set to a particular level,
/// callers will be provided tracing at the given level and all lower levels.
#[derive(Copy, Clone, Debug)]
pub enum TraceLevel {
/// No tracing will be performed.
None,
/// Severe errors that may impact the program's execution
Fatal,
/// Errors that do not impact the program's execution
Error,
/// Warnings that suggest abnormal data
Warn,
/// Informational messages about program execution
Info,
/// Detailed data that allows for debugging
Debug,
/// Exceptionally detailed debugging data
Trace,
}
impl Binding for TraceLevel {
type Raw = raw::git_trace_level_t;
unsafe fn from_raw(raw: raw::git_trace_level_t) -> Self {
match raw {
raw::GIT_TRACE_NONE => Self::None,
raw::GIT_TRACE_FATAL => Self::Fatal,
raw::GIT_TRACE_ERROR => Self::Error,
raw::GIT_TRACE_WARN => Self::Warn,
raw::GIT_TRACE_INFO => Self::Info,
raw::GIT_TRACE_DEBUG => Self::Debug,
raw::GIT_TRACE_TRACE => Self::Trace,
_ => panic!("Unknown git trace level"),
}
}
fn raw(&self) -> raw::git_trace_level_t {
match *self {
Self::None => raw::GIT_TRACE_NONE,
Self::Fatal => raw::GIT_TRACE_FATAL,
Self::Error => raw::GIT_TRACE_ERROR,
Self::Warn => raw::GIT_TRACE_WARN,
Self::Info => raw::GIT_TRACE_INFO,
Self::Debug => raw::GIT_TRACE_DEBUG,
Self::Trace => raw::GIT_TRACE_TRACE,
}
}
}
/// Callback type used to pass tracing events to the subscriber.
/// see `trace_set` to register a subscriber.
pub type TracingCb = fn(TraceLevel, &[u8]);
/// Use an atomic pointer to store the global tracing subscriber function.
static CALLBACK: AtomicPtr<()> = AtomicPtr::new(std::ptr::null_mut());
/// Set the global subscriber called when libgit2 produces a tracing message.
pub fn trace_set(level: TraceLevel, cb: TracingCb) -> Result<(), Error> {
// Store the callback in the global atomic.
CALLBACK.store(cb as *mut (), Ordering::SeqCst);
// git_trace_set returns 0 if there was no error.
let return_code: c_int = unsafe { raw::git_trace_set(level.raw(), Some(tracing_cb_c)) };
if return_code != 0 {
Err(Error::last_error(return_code))
} else {
Ok(())
}
}
/// The tracing callback we pass to libgit2 (C ABI compatible).
extern "C" fn tracing_cb_c(level: raw::git_trace_level_t, msg: *const c_char) {
// Load the callback function pointer from the global atomic.
let cb: *mut () = CALLBACK.load(Ordering::SeqCst);
// Transmute the callback pointer into the function pointer we know it to be.
//
// SAFETY: We only ever set the callback pointer with something cast from a TracingCb
// so transmuting back to a TracingCb is safe. This is notably not an integer-to-pointer
// transmute as described in the mem::transmute documentation and is in-line with the
// example in that documentation for casing between *const () to fn pointers.
let cb: TracingCb = unsafe { std::mem::transmute(cb) };
// If libgit2 passes us a message that is null, drop it and do not pass it to the callback.
// This is to avoid ever exposing rust code to a null ref, which would be Undefined Behavior.
if msg.is_null() {
return;
}
// Convert the message from a *const c_char to a &[u8] and pass it to the callback.
//
// SAFETY: We've just checked that the pointer is not null. The other safety requirements are left to
// libgit2 to enforce -- namely that it gives us a valid, nul-terminated, C string, that that string exists
// entirely in one allocation, that the string will not be mutated once passed to us, and that the nul-terminator is
// within isize::MAX bytes from the given pointers data address.
let msg: &CStr = unsafe { CStr::from_ptr(msg) };
// Convert from a CStr to &[u8] to pass to the rust code callback.
let msg: &[u8] = CStr::to_bytes(msg);
// Do the remaining part of this function in a panic wrapper, to catch any panics it produces.
panic::wrap(|| {
// Convert the raw trace level into a type we can pass to the rust callback fn.
//
// SAFETY: Currently the implementation of this function (above) may panic, but is only marked as unsafe to match
// the trait definition, thus we can consider this call safe.
let level: TraceLevel = unsafe { Binding::from_raw(level) };
// Call the user-supplied callback (which may panic).
(cb)(level, msg);
});
}
#[cfg(test)]
mod tests {
use super::TraceLevel;
// Test that using the above function to set a tracing callback doesn't panic.
#[test]
fn smoke() {
super::trace_set(TraceLevel::Trace, |level, msg| {
dbg!(level, msg);
})
.expect("libgit2 can set global trace callback");
}
}