use crate::{
capi::{ret::CReturn, utils, utils::Leakable},
error::Error,
};
use std::{
collections::{hash_map::Entry as HashMapEntry, HashMap},
error::Error as StdError,
ffi::CString,
ptr,
sync::Mutex,
};
use libc::{c_char, c_int};
use once_cell::sync::Lazy;
use rand::{self, Rng};
pub const __PATHRS_MAX_ERR_VALUE: CReturn = -4096;
static ERROR_MAP: Lazy<Mutex<HashMap<CReturn, Error>>> = Lazy::new(|| Mutex::new(HashMap::new()));
pub(crate) fn store_error(err: Error) -> CReturn {
let mut err_map = ERROR_MAP.lock().unwrap();
let mut g = rand::rng();
loop {
let idx = g.random_range(CReturn::MIN..__PATHRS_MAX_ERR_VALUE);
match err_map.entry(idx) {
HashMapEntry::Occupied(_) => continue,
HashMapEntry::Vacant(slot) => {
slot.insert(err);
break idx;
}
}
}
}
#[repr(align(8), C)]
pub struct CError {
pub saved_errno: u64,
pub description: *const c_char,
}
impl Leakable for CError {}
impl From<&Error> for CError {
fn from(err: &Error) -> Self {
let desc = {
let mut desc = err.to_string();
let mut err: &dyn StdError = err;
while let Some(next) = err.source() {
desc.push_str(": ");
desc.push_str(&next.to_string());
err = next;
}
CString::new(desc).expect("CString::new(description) failed in CError generation")
};
let saved_errno = err.kind().errno().unwrap_or(0).unsigned_abs();
CError {
saved_errno: saved_errno.into(),
description: desc.into_raw(),
}
}
}
impl Drop for CError {
fn drop(&mut self) {
if !self.description.is_null() {
let description = self.description as *mut c_char;
self.description = ptr::null_mut();
let _ = unsafe { CString::from_raw(description) };
}
}
}
#[no_mangle]
pub unsafe extern "C" fn pathrs_errorinfo(err_id: c_int) -> Option<&'static mut CError> {
let mut err_map = ERROR_MAP.lock().unwrap();
err_map
.remove(&err_id)
.as_ref()
.map(CError::from)
.map(Leakable::leak)
}
utils::symver! {
fn pathrs_errorinfo <- (pathrs_errorinfo, version = "LIBPATHRS_0.1", default);
}
#[no_mangle]
pub unsafe extern "C" fn pathrs_errorinfo_free(ptr: *mut CError) {
if ptr.is_null() {
return;
}
unsafe { (*ptr).free() }
}
utils::symver! {
fn pathrs_errorinfo_free <- (pathrs_errorinfo_free, version = "LIBPATHRS_0.1", default);
}
#[cfg(test)]
mod tests {
use super::*;
use crate::error::{Error, ErrorImpl};
use std::io::Error as IOError;
use pretty_assertions::assert_eq;
#[test]
fn cerror_ioerror_errno() {
let err = Error::from(ErrorImpl::OsError {
operation: "fake operation".into(),
source: IOError::from_raw_os_error(libc::ENOANO),
});
assert_eq!(
err.kind().errno(),
Some(libc::ENOANO),
"basic kind().errno() should return the right error"
);
let cerr = CError::from(&err);
assert_eq!(
cerr.saved_errno,
libc::ENOANO as u64,
"cerror should contain errno for OsError"
);
}
#[test]
fn cerror_einval_errno() {
let err = Error::from(ErrorImpl::InvalidArgument {
name: "fake argument".into(),
description: "fake description".into(),
});
assert_eq!(
err.kind().errno(),
Some(libc::EINVAL),
"InvalidArgument kind().errno() should return the right error"
);
let cerr = CError::from(&err);
assert_eq!(
cerr.saved_errno,
libc::EINVAL as u64,
"cerror should contain EINVAL errno for InvalidArgument"
);
}
#[test]
fn cerror_enosys_errno() {
let err = Error::from(ErrorImpl::NotImplemented {
feature: "fake feature".into(),
});
assert_eq!(
err.kind().errno(),
Some(libc::ENOSYS),
"NotImplemented kind().errno() should return the right error"
);
let cerr = CError::from(&err);
assert_eq!(
cerr.saved_errno,
libc::ENOSYS as u64,
"cerror should contain ENOSYS errno for NotImplemented"
);
}
#[test]
fn cerror_exdev_errno() {
let err = Error::from(ErrorImpl::SafetyViolation {
description: "fake safety violation".into(),
});
assert_eq!(
err.kind().errno(),
Some(libc::EXDEV),
"SafetyViolation kind().errno() should return the right error"
);
let cerr = CError::from(&err);
assert_eq!(
cerr.saved_errno,
libc::EXDEV as u64,
"cerror should contain EXDEV errno for SafetyViolation"
);
}
#[test]
fn cerror_no_errno() {
let parse_err = "a123".parse::<i32>().unwrap_err();
let err = Error::from(parse_err);
assert_eq!(
err.kind().errno(),
None,
"ParseIntError kind().errno() should return no errno"
);
let cerr = CError::from(&err);
assert_eq!(
cerr.saved_errno, 0,
"cerror should contain zero errno for ParseIntError"
);
}
}