forked from rust-lang/git2-rs
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtracing.rs
141 lines (117 loc) · 5.14 KB
/
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
141
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 {
// Unwrap here is fine since `Error::last_error` always returns `Some`.
Err(Error::last_error(return_code).unwrap())
} 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");
}
}