use crate::{
capi::error as capi_error,
error::{ErrorExt, ErrorKind},
tests::traits::ErrorImpl,
};
use std::{
ffi::{CStr, CString, OsStr},
fmt,
os::unix::{
ffi::OsStrExt,
io::{FromRawFd, OwnedFd},
},
path::{Path, PathBuf},
ptr,
};
use errno::Errno;
#[derive(Debug, Clone, thiserror::Error)]
pub struct CapiError {
errno: Option<Errno>,
description: String,
}
impl fmt::Display for CapiError {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(fmt, "{}", self.description)?;
if let Some(errno) = self.errno {
write!(fmt, " ({errno})")?;
}
Ok(())
}
}
impl ErrorExt for CapiError {
fn with_wrap<F>(self, context_fn: F) -> Self
where
F: FnOnce() -> String,
{
Self {
errno: self.errno,
description: context_fn() + ": " + &self.description,
}
}
}
impl ErrorImpl for CapiError {
fn kind(&self) -> ErrorKind {
if let Some(errno) = self.errno {
ErrorKind::OsError(Some(errno.0))
} else {
ErrorKind::InternalError
}
}
}
fn fetch_error(res: libc::c_int) -> Result<libc::c_int, CapiError> {
if res >= 0 {
Ok(res)
} else {
match unsafe { capi_error::pathrs_errorinfo(res) } {
Some(err) => {
let errno = match err.saved_errno as i32 {
0 => None,
errno => Some(Errno(errno)),
};
let description = unsafe { CStr::from_ptr(err.description) }
.to_string_lossy()
.to_string();
unsafe { capi_error::pathrs_errorinfo_free(err as *mut _) }
Err(CapiError { errno, description })
}
None => panic!("unknown error id {res}"),
}
}
}
pub(in crate::tests) fn path_to_cstring(path: impl AsRef<Path>) -> CString {
CString::new(path.as_ref().as_os_str().as_bytes())
.expect("normal path conversion shouldn't result in spurious nul bytes")
}
pub(in crate::tests) fn call_capi<Func>(func: Func) -> Result<libc::c_int, CapiError>
where
Func: Fn() -> libc::c_int,
{
fetch_error(func())
}
pub(in crate::tests) fn call_capi_zst<Func>(func: Func) -> Result<(), CapiError>
where
Func: Fn() -> libc::c_int,
{
call_capi(func).map(|val| {
assert_eq!(
val, 0,
"call_capi_zst must only be called on methods that return <= 0: got {val}"
);
})
}
pub(in crate::tests) fn call_capi_fd<Func>(func: Func) -> Result<OwnedFd, CapiError>
where
Func: Fn() -> libc::c_int,
{
call_capi(func).map(|fd| unsafe { OwnedFd::from_raw_fd(fd) })
}
pub(in crate::tests) fn call_capi_readlink<Func>(func: Func) -> Result<PathBuf, CapiError>
where
Func: Fn(*mut libc::c_char, libc::size_t) -> libc::c_int,
{
let mut actual_size = {
let size1 = fetch_error(func(123 as *mut _, 0))? as usize;
let size2 = fetch_error(func(ptr::null_mut(), 1000))? as usize;
assert_eq!(size1, size2, "readlink size should be the same");
size1
};
let mut linkbuf: Vec<u8> = Vec::with_capacity(0);
actual_size /= 2;
while actual_size > linkbuf.capacity() {
linkbuf.reserve(actual_size);
actual_size = fetch_error(func(
linkbuf.as_mut_ptr() as *mut libc::c_char,
linkbuf.capacity(),
))? as usize;
}
unsafe { linkbuf.set_len(actual_size) };
Ok(PathBuf::from(OsStr::from_bytes(
CString::new(linkbuf)
.expect("constructing a CString from the C API's copied CString should work")
.to_bytes(),
)))
}