git2/tracing.rs
1use std::{
2 ffi::CStr,
3 sync::atomic::{AtomicPtr, Ordering},
4};
5
6use libc::{c_char, c_int};
7
8use crate::{panic, raw, util::Binding, Error};
9
10/// Available tracing levels. When tracing is set to a particular level,
11/// callers will be provided tracing at the given level and all lower levels.
12#[derive(Copy, Clone, Debug)]
13pub enum TraceLevel {
14 /// No tracing will be performed.
15 None,
16
17 /// Severe errors that may impact the program's execution
18 Fatal,
19
20 /// Errors that do not impact the program's execution
21 Error,
22
23 /// Warnings that suggest abnormal data
24 Warn,
25
26 /// Informational messages about program execution
27 Info,
28
29 /// Detailed data that allows for debugging
30 Debug,
31
32 /// Exceptionally detailed debugging data
33 Trace,
34}
35
36impl Binding for TraceLevel {
37 type Raw = raw::git_trace_level_t;
38 unsafe fn from_raw(raw: raw::git_trace_level_t) -> Self {
39 match raw {
40 raw::GIT_TRACE_NONE => Self::None,
41 raw::GIT_TRACE_FATAL => Self::Fatal,
42 raw::GIT_TRACE_ERROR => Self::Error,
43 raw::GIT_TRACE_WARN => Self::Warn,
44 raw::GIT_TRACE_INFO => Self::Info,
45 raw::GIT_TRACE_DEBUG => Self::Debug,
46 raw::GIT_TRACE_TRACE => Self::Trace,
47 _ => panic!("Unknown git trace level"),
48 }
49 }
50 fn raw(&self) -> raw::git_trace_level_t {
51 match *self {
52 Self::None => raw::GIT_TRACE_NONE,
53 Self::Fatal => raw::GIT_TRACE_FATAL,
54 Self::Error => raw::GIT_TRACE_ERROR,
55 Self::Warn => raw::GIT_TRACE_WARN,
56 Self::Info => raw::GIT_TRACE_INFO,
57 Self::Debug => raw::GIT_TRACE_DEBUG,
58 Self::Trace => raw::GIT_TRACE_TRACE,
59 }
60 }
61}
62
63/// Callback type used to pass tracing events to the subscriber.
64/// see `trace_set` to register a subscriber.
65pub type TracingCb = fn(TraceLevel, &[u8]);
66
67/// Use an atomic pointer to store the global tracing subscriber function.
68static CALLBACK: AtomicPtr<()> = AtomicPtr::new(std::ptr::null_mut());
69
70/// Set the global subscriber called when libgit2 produces a tracing message.
71pub fn trace_set(level: TraceLevel, cb: TracingCb) -> Result<(), Error> {
72 // Store the callback in the global atomic.
73 CALLBACK.store(cb as *mut (), Ordering::SeqCst);
74
75 // git_trace_set returns 0 if there was no error.
76 let return_code: c_int = unsafe { raw::git_trace_set(level.raw(), Some(tracing_cb_c)) };
77
78 if return_code != 0 {
79 Err(Error::last_error(return_code))
80 } else {
81 Ok(())
82 }
83}
84
85/// The tracing callback we pass to libgit2 (C ABI compatible).
86extern "C" fn tracing_cb_c(level: raw::git_trace_level_t, msg: *const c_char) {
87 // Load the callback function pointer from the global atomic.
88 let cb: *mut () = CALLBACK.load(Ordering::SeqCst);
89
90 // Transmute the callback pointer into the function pointer we know it to be.
91 //
92 // SAFETY: We only ever set the callback pointer with something cast from a TracingCb
93 // so transmuting back to a TracingCb is safe. This is notably not an integer-to-pointer
94 // transmute as described in the mem::transmute documentation and is in-line with the
95 // example in that documentation for casing between *const () to fn pointers.
96 let cb: TracingCb = unsafe { std::mem::transmute(cb) };
97
98 // If libgit2 passes us a message that is null, drop it and do not pass it to the callback.
99 // This is to avoid ever exposing rust code to a null ref, which would be Undefined Behavior.
100 if msg.is_null() {
101 return;
102 }
103
104 // Convert the message from a *const c_char to a &[u8] and pass it to the callback.
105 //
106 // SAFETY: We've just checked that the pointer is not null. The other safety requirements are left to
107 // libgit2 to enforce -- namely that it gives us a valid, nul-terminated, C string, that that string exists
108 // entirely in one allocation, that the string will not be mutated once passed to us, and that the nul-terminator is
109 // within isize::MAX bytes from the given pointers data address.
110 let msg: &CStr = unsafe { CStr::from_ptr(msg) };
111
112 // Convert from a CStr to &[u8] to pass to the rust code callback.
113 let msg: &[u8] = CStr::to_bytes(msg);
114
115 // Do the remaining part of this function in a panic wrapper, to catch any panics it produces.
116 panic::wrap(|| {
117 // Convert the raw trace level into a type we can pass to the rust callback fn.
118 //
119 // SAFETY: Currently the implementation of this function (above) may panic, but is only marked as unsafe to match
120 // the trait definition, thus we can consider this call safe.
121 let level: TraceLevel = unsafe { Binding::from_raw(level) };
122
123 // Call the user-supplied callback (which may panic).
124 (cb)(level, msg);
125 });
126}
127
128#[cfg(test)]
129mod tests {
130 use super::TraceLevel;
131
132 // Test that using the above function to set a tracing callback doesn't panic.
133 #[test]
134 fn smoke() {
135 super::trace_set(TraceLevel::Trace, |level, msg| {
136 dbg!(level, msg);
137 })
138 .expect("libgit2 can set global trace callback");
139 }
140}