1use alloc::ffi::CString;
15use core::ffi::{CStr, c_char, c_int, c_void};
16use std::sync::Mutex;
17
18use asdf_core::ErrorCode;
19
20const ERROR_FORMATS: &[Option<&CStr>] = &[
25 None, Some(c"unknown parser state"), Some(c"failed to initialize stream"), Some(c"cannot write to a read-only stream or file"), Some(c"invalid ASDF header"), Some(c"unexpected end of file"), Some(c"invalid block header"), Some(c"block magic mismatch"), Some(c"YAML parser initialization failed"), Some(c"YAML parsing failed"), Some(c"out of memory"), None, Some(c"invalid argument for %s: %s"), Some(c"unknown compression type: %s"), Some(c"compression error: %s"), Some(c"no serializer registered for the %s extension"), Some(c"over limit: %s"), ];
43
44const ERROR_LOG_LEVELS: &[LogLevel] = &[
46 LogLevel::None, LogLevel::Error, LogLevel::Error, LogLevel::Error, LogLevel::Error, LogLevel::Error, LogLevel::Error, LogLevel::Error, LogLevel::Fatal, LogLevel::Error, LogLevel::Fatal, LogLevel::Error, LogLevel::Error, LogLevel::Error, LogLevel::Error, LogLevel::Warn, LogLevel::Error, ];
64
65#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Default)]
70#[repr(i32)]
71pub enum LogLevel {
72 #[default]
74 None = 0,
75 Trace,
77 Debug,
79 Info,
81 Warn,
83 Error,
85 Fatal,
87}
88
89impl LogLevel {
90 pub fn from_name(name: &str) -> Option<Self> {
92 match name.to_ascii_uppercase().as_str() {
93 "NONE" => Some(LogLevel::None),
94 "TRACE" => Some(LogLevel::Trace),
95 "DEBUG" => Some(LogLevel::Debug),
96 "INFO" => Some(LogLevel::Info),
97 "WARN" => Some(LogLevel::Warn),
98 "ERROR" => Some(LogLevel::Error),
99 "FATAL" => Some(LogLevel::Fatal),
100 _ => None,
101 }
102 }
103
104 pub fn as_str(self) -> &'static str {
106 match self {
107 LogLevel::None => "NONE",
108 LogLevel::Trace => "TRACE",
109 LogLevel::Debug => "DEBUG",
110 LogLevel::Info => "INFO",
111 LogLevel::Warn => "WARN",
112 LogLevel::Error => "ERROR",
113 LogLevel::Fatal => "FATAL",
114 }
115 }
116
117 fn from_i32(v: i32) -> Option<Self> {
118 match v {
119 0 => Some(LogLevel::None),
120 1 => Some(LogLevel::Trace),
121 2 => Some(LogLevel::Debug),
122 3 => Some(LogLevel::Info),
123 4 => Some(LogLevel::Warn),
124 5 => Some(LogLevel::Error),
125 6 => Some(LogLevel::Fatal),
126 _ => None,
127 }
128 }
129}
130
131#[derive(Default, Debug)]
136pub struct ErrorState {
137 inner: Mutex<ErrorStateInner>,
138}
139
140#[derive(Default, Debug)]
141struct ErrorStateInner {
142 code: i32,
143 errno: i32,
144 message: Option<CString>,
145}
146
147impl ErrorState {
148 pub fn set(&self, code: i32, message: impl Into<Vec<u8>>) {
150 let mut inner = self.inner.lock().unwrap_or_else(|e| e.into_inner());
151 inner.code = code;
152 inner.errno = 0;
153 inner.message = CString::new(message).ok();
154 }
155
156 pub fn set_system(&self, errnum: i32) {
158 let mut inner = self.inner.lock().unwrap_or_else(|e| e.into_inner());
159 inner.code = ErrorCode::System as i32;
160 inner.errno = errnum;
161 inner.message = CString::new(strerror(errnum)).ok();
162 }
163
164 pub fn set_error(&self, err: &asdf_core::Error) {
166 match err.errno() {
167 Some(n) => {
168 self.set_system(n);
169 let mut inner = self.inner.lock().unwrap_or_else(|e| e.into_inner());
171 inner.message = CString::new(err.message()).ok();
172 }
173 None => self.set(err.code() as i32, err.message()),
174 }
175 }
176
177 pub fn clear(&self) {
179 let mut inner = self.inner.lock().unwrap_or_else(|e| e.into_inner());
180 *inner = ErrorStateInner::default();
181 }
182
183 pub fn code(&self) -> i32 {
185 self.inner.lock().unwrap_or_else(|e| e.into_inner()).code
186 }
187
188 pub fn errno(&self) -> i32 {
190 self.inner.lock().unwrap_or_else(|e| e.into_inner()).errno
191 }
192
193 pub fn message_ptr(&self) -> *const c_char {
199 let inner = self.inner.lock().unwrap_or_else(|e| e.into_inner());
200 match &inner.message {
201 Some(s) => s.as_ptr(),
202 None => core::ptr::null(),
203 }
204 }
205}
206
207#[cfg(unix)]
217fn strerror(errnum: i32) -> String {
218 let mut buffer = [0 as c_char; 256];
219 let rc = unsafe { libc::strerror_r(errnum, buffer.as_mut_ptr(), buffer.len()) };
221 if rc != 0 {
222 return format!("errno {errnum}");
224 }
225 unsafe { crate::ffi::c_string_lossy(buffer.as_ptr()) }.unwrap_or_default()
226}
227
228#[cfg(not(unix))]
234fn strerror(errnum: i32) -> String {
235 unsafe { crate::ffi::c_string_lossy(libc::strerror(errnum)) }
238 .unwrap_or_else(|| format!("errno {errnum}"))
239}
240
241#[unsafe(no_mangle)]
248pub extern "C" fn asdf_shim_error_format(code: c_int) -> *const c_char {
249 let idx = match usize::try_from(code) {
250 Ok(i) => i,
251 Err(_) => return core::ptr::null(),
252 };
253 match ERROR_FORMATS.get(idx) {
254 Some(Some(s)) => s.as_ptr(),
255 _ => core::ptr::null(),
256 }
257}
258
259pub fn error_log_level(code: i32) -> LogLevel {
261 usize::try_from(code)
262 .ok()
263 .and_then(|i| ERROR_LOG_LEVELS.get(i).copied())
264 .unwrap_or(LogLevel::Error)
265}
266
267#[unsafe(no_mangle)]
283pub unsafe extern "C" fn asdf_file_error_oom(
284 file: *mut crate::file_ffi::AsdfFile,
285 src_file: *const c_char,
286 lineno: c_int,
287) {
288 unsafe { asdf_shim_error_set(file.cast(), 0, OOM_CODE, src_file, lineno, OOM_MESSAGE.as_ptr()) }
289}
290
291#[unsafe(no_mangle)]
296pub unsafe extern "C" fn asdf_value_error_oom(
297 value: *mut crate::file_ffi::AsdfValue,
298 src_file: *const c_char,
299 lineno: c_int,
300) {
301 unsafe {
302 asdf_shim_error_set(value.cast(), 1, OOM_CODE, src_file, lineno, OOM_MESSAGE.as_ptr())
303 }
304}
305
306#[unsafe(no_mangle)]
311pub unsafe extern "C" fn asdf_file_error_system(
312 file: *mut crate::file_ffi::AsdfFile,
313 errnum: c_int,
314 src_file: *const c_char,
315 lineno: c_int,
316) {
317 unsafe { asdf_shim_error_set_system(file.cast(), 0, errnum, src_file, lineno) }
318}
319
320#[unsafe(no_mangle)]
325pub unsafe extern "C" fn asdf_value_error_system(
326 value: *mut crate::file_ffi::AsdfValue,
327 errnum: c_int,
328 src_file: *const c_char,
329 lineno: c_int,
330) {
331 unsafe { asdf_shim_error_set_system(value.cast(), 1, errnum, src_file, lineno) }
332}
333
334const OOM_CODE: c_int = ErrorCode::OutOfMemory as c_int;
336const OOM_MESSAGE: &CStr = c"out of memory";
337
338#[unsafe(no_mangle)]
345pub unsafe extern "C" fn asdf_shim_error_set(
346 obj: *mut c_void,
347 is_value: c_int,
348 code: c_int,
349 src_file: *const c_char,
350 lineno: c_int,
351 msg: *const c_char,
352) {
353 crate::panic::guard("asdf_shim_error_set", (), || {
354 let text = unsafe { crate::ffi::c_string_lossy(msg) }.unwrap_or_default();
355 let text = if text.is_empty() {
356 usize::try_from(code)
357 .ok()
358 .and_then(|i| ERROR_FORMATS.get(i).copied().flatten())
359 .map(|s| s.to_string_lossy().into_owned())
360 .unwrap_or_else(|| "unknown error".to_string())
361 } else {
362 text
363 };
364
365 if let Some(state) = unsafe { state_for(obj, is_value) } {
366 state.set(code, text.clone());
367 }
368 unsafe { emit_log(obj, is_value, error_log_level(code), src_file, lineno, &text) };
369 });
370}
371
372#[unsafe(no_mangle)]
377pub unsafe extern "C" fn asdf_shim_error_set_system(
378 obj: *mut c_void,
379 is_value: c_int,
380 errnum: c_int,
381 src_file: *const c_char,
382 lineno: c_int,
383) {
384 crate::panic::guard("asdf_shim_error_set_system", (), || {
385 let text = strerror(errnum);
386 if let Some(state) = unsafe { state_for(obj, is_value) } {
387 state.set_system(errnum);
388 }
389 unsafe { emit_log(obj, is_value, LogLevel::Error, src_file, lineno, &text) };
390 });
391}
392
393#[unsafe(no_mangle)]
399pub unsafe extern "C" fn asdf_shim_log_message(
400 file: *const c_void,
401 level: c_int,
402 src_file: *const c_char,
403 lineno: c_int,
404 msg: *const c_char,
405) {
406 crate::panic::guard("asdf_shim_log_message", (), || {
407 let Some(level) = LogLevel::from_i32(level) else { return };
408 let text = unsafe { crate::ffi::c_string_lossy(msg) }.unwrap_or_default();
409 unsafe { emit_log(file.cast_mut(), 0, level, src_file, lineno, &text) };
410 });
411}
412
413unsafe fn state_for(obj: *mut c_void, is_value: c_int) -> Option<&'static ErrorState> {
418 if obj.is_null() {
419 return None;
420 }
421 let file = if is_value != 0 {
425 crate::file_ffi::value_file(obj.cast::<crate::file_ffi::AsdfValue>())?
426 } else {
427 obj.cast::<crate::file_ffi::AsdfFile>()
428 };
429 crate::file_ffi::error_state(file)
430}
431
432unsafe fn emit_log(
437 _obj: *mut c_void,
438 _is_value: c_int,
439 level: LogLevel,
440 src_file: *const c_char,
441 lineno: c_int,
442 msg: &str,
443) {
444 if level == LogLevel::None || level < default_log_level() {
445 return;
446 }
447 let src = unsafe { crate::ffi::c_string_lossy(src_file) }.unwrap_or_else(|| "?".into());
450 eprintln!("{} libasdf {}:{}: {}", level.as_str(), src, lineno, msg);
451}
452
453pub(crate) fn log_to_file(file: *mut crate::file_ffi::AsdfFile, level: LogLevel, msg: &str) {
459 let config = crate::file_ffi::file_config(file).unwrap_or_default();
460 let threshold =
461 if config.log_level == LogLevel::None { default_log_level() } else { config.log_level };
462 if level == LogLevel::None || level < threshold {
463 return;
464 }
465
466 let line = format!("{} libasdf: {msg}\n", level.as_str());
467 if config.log_stream.is_null() {
468 eprint!("{line}");
469 return;
470 }
471 unsafe {
474 libc::fwrite(
475 line.as_ptr().cast::<c_void>(),
476 1,
477 line.len(),
478 config.log_stream.cast::<libc::FILE>(),
479 );
480 libc::fflush(config.log_stream.cast::<libc::FILE>());
481 }
482}
483
484pub fn default_log_level() -> LogLevel {
488 static CACHED: std::sync::OnceLock<LogLevel> = std::sync::OnceLock::new();
489 *CACHED.get_or_init(|| {
490 std::env::var("ASDF_LOG_LEVEL")
491 .ok()
492 .and_then(|v| LogLevel::from_name(&v))
493 .unwrap_or(LogLevel::Warn)
494 })
495}
496
497#[cfg(test)]
498mod tests {
499 use super::*;
500
501 #[test]
502 fn every_error_code_has_a_table_entry() {
503 assert_eq!(ERROR_FORMATS.len(), 17);
506 assert_eq!(ERROR_LOG_LEVELS.len(), 17);
507 assert_eq!(ERROR_FORMATS.len(), ERROR_LOG_LEVELS.len());
508 }
509
510 #[test]
511 fn format_strings_match_upstream_conversions() {
512 let invalid = ERROR_FORMATS[ErrorCode::InvalidArgument as usize].unwrap();
515 assert_eq!(invalid.to_str().unwrap().matches("%s").count(), 2);
516
517 for code in [
518 ErrorCode::UnknownCompression,
519 ErrorCode::CompressionFailed,
520 ErrorCode::ExtensionNotFound,
521 ErrorCode::OverLimit,
522 ] {
523 let f = ERROR_FORMATS[code as usize].unwrap();
524 assert_eq!(f.to_str().unwrap().matches("%s").count(), 1, "{code:?}");
525 }
526 }
527
528 #[test]
529 fn system_and_none_have_no_format() {
530 assert!(ERROR_FORMATS[ErrorCode::None as usize].is_none());
531 assert!(ERROR_FORMATS[ErrorCode::System as usize].is_none());
532 }
533
534 #[test]
535 fn error_format_lookup_is_bounds_safe() {
536 assert!(!asdf_shim_error_format(ErrorCode::UnexpectedEof as i32).is_null());
537 assert!(asdf_shim_error_format(ErrorCode::None as i32).is_null());
538 assert!(asdf_shim_error_format(9999).is_null());
539 assert!(asdf_shim_error_format(-1).is_null());
540 }
541
542 #[test]
543 fn log_levels_match_upstream() {
544 assert_eq!(error_log_level(ErrorCode::OutOfMemory as i32), LogLevel::Fatal);
545 assert_eq!(error_log_level(ErrorCode::YamlParserInitFailed as i32), LogLevel::Fatal);
546 assert_eq!(error_log_level(ErrorCode::ExtensionNotFound as i32), LogLevel::Warn);
547 assert_eq!(error_log_level(ErrorCode::UnexpectedEof as i32), LogLevel::Error);
548 }
549
550 #[test]
551 fn error_state_round_trips() {
552 let s = ErrorState::default();
553 assert_eq!(s.code(), 0);
554 assert!(s.message_ptr().is_null());
555
556 s.set(ErrorCode::UnexpectedEof as i32, "truncated");
557 assert_eq!(s.code(), ErrorCode::UnexpectedEof as i32);
558 let msg = unsafe { CStr::from_ptr(s.message_ptr()) };
559 assert_eq!(msg.to_str().unwrap(), "truncated");
560
561 s.clear();
562 assert_eq!(s.code(), 0);
563 }
564
565 #[test]
566 fn system_errors_carry_errno() {
567 let s = ErrorState::default();
568 s.set_system(2);
569 assert_eq!(s.code(), ErrorCode::System as i32);
570 assert_eq!(s.errno(), 2);
571 assert!(!s.message_ptr().is_null());
572 }
573
574 #[test]
575 fn messages_with_interior_nul_do_not_panic() {
576 let s = ErrorState::default();
577 s.set(1, "bad\0message");
578 assert!(s.message_ptr().is_null());
580 }
581
582 #[test]
583 fn log_level_names_round_trip() {
584 for level in [
585 LogLevel::None,
586 LogLevel::Trace,
587 LogLevel::Debug,
588 LogLevel::Info,
589 LogLevel::Warn,
590 LogLevel::Error,
591 LogLevel::Fatal,
592 ] {
593 assert_eq!(LogLevel::from_name(level.as_str()), Some(level));
594 assert_eq!(LogLevel::from_i32(level as i32), Some(level));
595 }
596 assert_eq!(LogLevel::from_name("warn"), Some(LogLevel::Warn));
598 assert_eq!(LogLevel::from_name("nonsense"), None);
599 }
600}