From 40660f4520c9b2eb912d5e2daa5abcc81341e5f1 Mon Sep 17 00:00:00 2001 From: Lars Erik Wik Date: Fri, 26 Jun 2026 14:02:18 +0200 Subject: [PATCH] Map log levels explicitly to the LCH_LOG_* values The logger cast the log crate's Level discriminant directly to the C level, relying on an ordering the crate does not guarantee as an ABI. Add LCH_LOG_* constants in ffi.rs and match on Level explicitly so the mapping is pinned in leech2's own code. Signed-off-by: Lars Erik Wik Co-Authored-By: Claude Opus 4.8 (1M context) --- src/ffi.rs | 11 +++++++++++ src/logger.rs | 15 +++++++++++++-- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/ffi.rs b/src/ffi.rs index 06a9f2f..fb516bf 100644 --- a/src/ffi.rs +++ b/src/ffi.rs @@ -30,6 +30,17 @@ const VALUE_NUMBER: c_int = 2; /// `LCH_VALUE_BOOLEAN` from `leech2.h`. Cell kind tag. const VALUE_BOOLEAN: c_int = 3; +/// `LCH_LOG_ERROR` from `leech2.h`. Log level passed to `lch_log_callback_t`. +pub const LOG_ERROR: i32 = 1; +/// `LCH_LOG_WARN` from `leech2.h`. Log level passed to `lch_log_callback_t`. +pub const LOG_WARN: i32 = 2; +/// `LCH_LOG_INFO` from `leech2.h`. Log level passed to `lch_log_callback_t`. +pub const LOG_INFO: i32 = 3; +/// `LCH_LOG_DEBUG` from `leech2.h`. Log level passed to `lch_log_callback_t`. +pub const LOG_DEBUG: i32 = 4; +/// `LCH_LOG_TRACE` from `leech2.h`. Log level passed to `lch_log_callback_t`. +pub const LOG_TRACE: i32 = 5; + /// Run an FFI body inside `catch_unwind`, returning `default` if a panic is caught. /// Panicking across an `extern "C"` boundary is undefined behavior, so every FFI /// entry point routes its body through this guard as a last line of defense. diff --git a/src/logger.rs b/src/logger.rs index 3973c87..519b028 100644 --- a/src/logger.rs +++ b/src/logger.rs @@ -1,7 +1,9 @@ use std::ffi::{CString, c_char, c_void}; use std::sync::{Once, RwLock}; -use log::{LevelFilter, Log, Metadata, Record}; +use log::{Level, LevelFilter, Log, Metadata, Record}; + +use crate::ffi::{LOG_DEBUG, LOG_ERROR, LOG_INFO, LOG_TRACE, LOG_WARN}; type LogCallback = unsafe extern "C" fn(i32, *const c_char, *mut c_void); @@ -32,7 +34,16 @@ impl Log for CallbackLogger { if let Some(ref state) = *guard { let message = format!("{}", record.args()); if let Ok(cstr) = CString::new(message) { - let level = record.level() as i32; + // Map explicitly to the LCH_LOG_* values rather than casting + // the log crate's Level discriminant, which is not a stable + // ABI contract. + let level = match record.level() { + Level::Error => LOG_ERROR, + Level::Warn => LOG_WARN, + Level::Info => LOG_INFO, + Level::Debug => LOG_DEBUG, + Level::Trace => LOG_TRACE, + }; unsafe { (state.callback)(level, cstr.as_ptr(), state.user_data); }