use crate::{
capi::error as capi_error,
error::{ErrorExt, ErrorKind},
tests::traits::ErrorImpl,
};
use std::{
cmp,
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 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 readlink_func = |size| -> Result<PathBuf, CapiError> {
let mut linkbuf: Vec<u8> = Vec::with_capacity(size);
let got_size = fetch_error(func(
linkbuf.as_mut_ptr() as *mut libc::c_char,
linkbuf.capacity(),
))? as usize;
unsafe { linkbuf.set_len(cmp::min(got_size, linkbuf.capacity())) };
Ok(OsStr::from_bytes(
CString::new(linkbuf)
.expect("constructing a CString from the C API's copied CString should work")
.to_bytes(),
)
.into())
};
let actual_linktarget = readlink_func(actual_size)?;
for test_size in [
1,
2,
actual_size / 3,
actual_size / 2,
actual_size - 1, actual_size + 1,
actual_size + 2,
actual_size * 2,
] {
let test_linktarget = readlink_func(test_size)?;
if test_size >= actual_size {
assert_eq!(
actual_linktarget,
test_linktarget,
"readlink(linkbuf_len={test_size} >= actual_size={actual_size}) should return the same link target string",
);
} else {
let actual_linktarget_truncated =
OsStr::from_bytes(&actual_linktarget.as_os_str().as_bytes()[..test_size]);
assert_eq!(
actual_linktarget_truncated,
test_linktarget.as_os_str(),
"readlink(linkbuf_len={test_size} < actual_size={actual_size}) should truncate link target string to {test_size} bytes",
);
}
}
Ok(actual_linktarget)
}