#![warn(clippy::all, clippy::pedantic)]
#![allow(clippy::missing_errors_doc)]
pub mod callback;
pub mod db;
pub mod engine;
mod error;
pub mod fmap;
pub mod layer_attr;
pub mod scan_settings;
pub mod version;
pub mod cvd;
#[cfg(windows)]
pub mod windows_fd;
use clamav_sys::{cl_error_t, cl_init, cl_initialize_crypto};
pub use engine::Error as EngineError;
pub use error::Error as ClamError;
use lazy_static::lazy_static;
use std::{
ffi::CStr,
pin::Pin,
sync::{Arc, Mutex, Once},
};
use tokio::io::AsyncRead;
lazy_static! {
static ref CLAMAV_MESSAGE_CALLBACK: Arc<Mutex<Option<MsgCallback>>> = Arc::new(Mutex::new(None));
}
pub fn initialize() -> Result<(), ClamError> {
static ONCE: Once = Once::new();
static mut RESULT: cl_error_t = cl_error_t::CL_SUCCESS;
unsafe {
extern "C" fn cleanup() {
unsafe {
clamav_sys::cl_cleanup_crypto();
}
}
ONCE.call_once(|| {
RESULT = cl_init(clamav_sys::CL_INIT_DEFAULT);
if RESULT == cl_error_t::CL_SUCCESS {
cl_initialize_crypto();
libc::atexit(cleanup);
}
});
match RESULT {
cl_error_t::CL_SUCCESS => Ok(()),
_ => Err(ClamError::new(RESULT)),
}
}
}
#[must_use]
pub fn version() -> String {
let ver = unsafe { clamav_sys::cl_retver() };
if ver.is_null() {
String::new()
} else {
unsafe { std::ffi::CStr::from_ptr(ver).to_string_lossy().to_string() }
}
}
pub type MsgCallback = Box<dyn Fn(log::Level, &str, &str) + Send>;
#[allow(clippy::missing_panics_doc)]
pub fn set_msg_callback(cb: MsgCallback) {
unsafe {
*(CLAMAV_MESSAGE_CALLBACK.lock().unwrap()) = Some(cb);
clamav_sys::cl_set_clcb_msg(Some(clcb_msg_wrapper));
}
}
unsafe extern "C" fn clcb_msg_wrapper(
severity: clamav_sys::cl_msg,
fullmsg: *const i8,
msg: *const i8,
_context: *mut libc::c_void,
) {
let log_level = match severity {
clamav_sys::cl_msg::CL_MSG_WARN => log::Level::Warn,
clamav_sys::cl_msg::CL_MSG_ERROR => log::Level::Error,
_ => log::Level::Info,
};
if let Ok(cb) = CLAMAV_MESSAGE_CALLBACK.lock() {
if let Some(cb) = &*cb {
let fullmsg = CStr::from_ptr(fullmsg).to_string_lossy().to_string();
let msg = CStr::from_ptr(msg).to_string_lossy().to_string();
cb(log_level, &fullmsg, &msg);
} else {
unreachable!()
}
}
}
pub type ContentHandle = Pin<Box<dyn AsyncRead + Send>>;
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashMap;
lazy_static! {
static ref TEST_STORE: Arc<Mutex<HashMap<String, String>>> = Arc::new(Mutex::new(HashMap::new()));
}
#[test]
fn initialize_success() {
assert!(initialize().is_ok(), "initialize should succeed");
}
#[tokio::test]
async fn clcb_msg_override() {
const KEY: &str = module_path!();
fn cb(_severity: log::Level, _fullmsg: &str, msg: &str) {
let mut test_store = TEST_STORE.lock().unwrap();
(*test_store).insert(KEY.into(), msg.into());
}
{
let mut test_store = TEST_STORE.lock().unwrap();
(*test_store).insert(KEY.into(), String::default());
}
set_msg_callback(Box::new(cb));
let clam_engine = crate::engine::Engine::new();
assert!(
clam_engine.load_databases("/no-such-path").await.is_err(),
"database load should have failed"
);
let test_store = TEST_STORE.lock().unwrap();
let msg = (*test_store)
.get(KEY)
.expect(concat!("value of ", module_path!()));
assert!(msg.contains("/no-such-path"));
}
}