use crate::object::{TopologyObject, TopologyObjectID};
#[cfg(doc)]
use crate::topology::Topology;
use derive_more::From;
use errno::Errno;
#[allow(unused)]
#[cfg(test)]
use similar_asserts::assert_eq;
use std::{
error::Error,
ffi::{c_int, c_uint},
fmt::Debug,
ptr::NonNull,
};
use thiserror::Error;
struct ErrnoGuard(Errno);
impl ErrnoGuard {
fn new(errno: Errno) -> Self {
let old_errno = errno::errno();
errno::set_errno(errno);
Self(old_errno)
}
}
impl Drop for ErrnoGuard {
fn drop(&mut self) {
errno::set_errno(self.0);
}
}
fn check_errno<R: Copy + Ord>(
callback: impl FnOnce() -> R,
lowest_good_value: R,
) -> (R, Option<Errno>) {
let _guard = ErrnoGuard::new(Errno(0));
let result = callback();
fn interpret_errno(should_check_errno: bool) -> Option<Errno> {
should_check_errno
.then(|| {
let errno = errno::errno();
(errno != Errno(0)).then_some(errno)
})
.flatten()
}
(result, interpret_errno(result < lowest_good_value))
}
#[derive(Copy, Clone, Debug, Error, Eq, Hash, PartialEq)]
#[error("{api} failed with errno {errno:?}")]
pub struct RawHwlocError {
pub api: &'static str,
#[cfg_attr(windows, doc = "")]
#[cfg_attr(windows, doc = "Note that even for entry points where hwloc is")]
#[cfg_attr(windows, doc = "documented to set errno, this member may be")]
#[cfg_attr(windows, doc = "`None` on Windows. This happens because ")]
#[cfg_attr(windows, doc = "Windows has multiple implementations of its")]
#[cfg_attr(windows, doc = "standard C library, called C Run-Times (CRTs),")]
#[cfg_attr(windows, doc = "and getting your application to be linked")]
#[cfg_attr(windows, doc = "against the same CRT as your hwloc DLL is")]
#[cfg_attr(windows, doc = "basically a matter of fragile guesswork...")]
pub errno: Option<Errno>,
}
pub(crate) fn call_hwloc_ptr_mut<T>(
api: &'static str,
call: impl FnOnce() -> *mut T,
) -> Result<NonNull<T>, RawHwlocError> {
let (result, errno) = check_errno(call, 1 as *mut T);
NonNull::new(result).ok_or(RawHwlocError { api, errno })
}
pub(crate) fn call_hwloc_ptr<T>(
api: &'static str,
call: impl FnOnce() -> *const T,
) -> Result<NonNull<T>, RawHwlocError> {
call_hwloc_ptr_mut(api, || call().cast_mut())
}
pub(crate) fn call_hwloc_zero_or_minus1(
api: &'static str,
call: impl FnOnce() -> c_int,
) -> Result<(), RawHwlocError> {
fn check_raw_result(raw_result: Result<c_uint, RawHwlocError>) -> Result<(), RawHwlocError> {
match raw_result {
Ok(0) => Ok(()),
Ok(positive) => unreachable!(
"Unexpected integer > 0 from hwloc function that should return 0 or -1: {positive}"
),
Err(e) => Err(e),
}
}
check_raw_result(call_hwloc_positive_or_minus1(api, call))
}
pub(crate) fn call_hwloc_bool(
api: &'static str,
call: impl FnOnce() -> c_int,
) -> Result<bool, RawHwlocError> {
fn check_raw_result(
api: &'static str,
raw_result: Result<c_uint, RawHwlocError>,
) -> Result<bool, RawHwlocError> {
match raw_result {
Ok(1) => Ok(true),
Ok(0) => Ok(false),
Ok(other) => unreachable!("Got unexpected boolean value {other} from {api}"),
Err(e) => Err(e),
}
}
check_raw_result(api, call_hwloc_positive_or_minus1(api, call))
}
pub(crate) fn call_hwloc_positive_or_minus1(
api: &'static str,
call: impl FnOnce() -> c_int,
) -> Result<c_uint, RawHwlocError> {
fn check_raw_result(
raw_result: Result<c_int, RawNegIntError>,
) -> Result<c_uint, RawHwlocError> {
match raw_result {
Ok(positive) => {
Ok(c_uint::try_from(positive).expect("Cannot happen due to 0 threshold above"))
}
Err(RawNegIntError {
api,
result: -1,
errno,
}) => Err(RawHwlocError { api, errno }),
Err(other_err) => {
unreachable!("Unexpected negative integer != -1 from hwloc function: {other_err}")
}
}
}
check_raw_result(call_hwloc_int_raw(api, call, 0))
}
#[derive(Copy, Clone, Debug, Error, Eq, Hash, PartialEq)]
#[error("{api} failed with result {result} and errno {errno:?}")]
pub(crate) struct RawNegIntError {
pub(crate) api: &'static str,
pub(crate) result: c_int,
pub(crate) errno: Option<Errno>,
}
pub(crate) fn call_hwloc_int_raw(
api: &'static str,
call: impl FnOnce() -> c_int,
lowest_good_value: c_int,
) -> Result<c_int, RawNegIntError> {
fn check_raw_result(
api: &'static str,
(result, errno): (c_int, Option<Errno>),
lowest_good_value: c_int,
) -> Result<c_int, RawNegIntError> {
(result >= lowest_good_value)
.then_some(result)
.ok_or(RawNegIntError { api, result, errno })
}
check_raw_result(api, check_errno(call, lowest_good_value), lowest_good_value)
}
#[derive(Copy, Clone, Debug, Eq, Error, Hash, PartialEq)]
pub enum HybridError<RustError: Error> {
#[error(transparent)]
Rust(#[from] RustError),
#[error(transparent)]
Hwloc(RawHwlocError),
}
impl<RustError: Error> HybridError<RustError> {
pub fn expect_only_hwloc(self, msg: &str) -> RawHwlocError {
match self {
Self::Hwloc(e) => e,
Self::Rust(e) => panic!("{msg} (got unexpected Rust error {e}"),
}
}
}
#[derive(Copy, Clone, Debug, Default, Eq, Error, Hash, Ord, PartialEq, PartialOrd)]
#[error("can't pass a string with NUL chars to hwloc")]
pub struct NulError;
#[derive(Copy, Clone, Debug, Default, Eq, Error, From, Hash, Ord, PartialEq, PartialOrd)]
#[error("parameter {0:?} isn't valid for this operation")]
pub struct ParameterError<Parameter: Debug>(pub Parameter);
pub type FlagsError<Flags> = ParameterError<Flags>;
#[allow(missing_copy_implementations)]
#[derive(Clone, Debug, Eq, Error, Hash, PartialEq)]
#[error("object #{0} doesn't belong to this topology")]
pub struct ForeignObjectError(TopologyObjectID);
impl<'topology> From<&'topology TopologyObject> for ForeignObjectError {
fn from(object: &'topology TopologyObject) -> Self {
Self(object.global_persistent_index())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::tests::assert_panics;
use proptest::prelude::*;
#[allow(unused)]
use similar_asserts::assert_eq;
use static_assertions::{assert_impl_all, assert_not_impl_any, assert_type_eq_all};
use std::{
fmt::{self, Binary, LowerExp, LowerHex, Octal, Pointer, UpperExp, UpperHex},
hash::Hash,
io::{self, Read},
num::{NonZeroU32, NonZeroUsize},
ops::Deref,
panic::UnwindSafe,
ptr,
};
assert_type_eq_all!(FlagsError<()>, ParameterError<()>);
assert_impl_all!(ForeignObjectError:
Clone, Error, Hash, Sized, Sync, Unpin, UnwindSafe
);
assert_not_impl_any!(ForeignObjectError:
Binary, Copy, Default, Deref, Drop, IntoIterator, LowerExp, LowerHex,
Octal, PartialOrd, Pointer, Read, UpperExp, UpperHex, fmt::Write,
io::Write
);
assert_impl_all!(HybridError<NulError>:
Copy, Error, Hash, Sized, Sync, Unpin, UnwindSafe
);
assert_not_impl_any!(HybridError<NulError>:
Binary, Default, Deref, Drop, IntoIterator, LowerExp, LowerHex, Octal,
PartialOrd, Pointer, Read, UpperExp, UpperHex, fmt::Write, io::Write
);
assert_impl_all!(NulError:
Copy, Default, Error, Hash, Ord, Sized, Sync, Unpin, UnwindSafe
);
assert_not_impl_any!(NulError:
Binary, Deref, Drop, IntoIterator, LowerExp, LowerHex, Octal, Pointer,
Read, UpperExp, UpperHex, fmt::Write, io::Write
);
assert_impl_all!(ParameterError<()>:
Copy, Default, Error, Hash, Ord, Sized, Sync, Unpin, UnwindSafe
);
assert_not_impl_any!(ParameterError<()>:
Binary, Deref, Drop, IntoIterator, LowerExp, LowerHex, Octal, Pointer,
Read, UpperExp, UpperHex, fmt::Write, io::Write
);
assert_impl_all!(RawHwlocError:
Copy, Error, Hash, Sized, Sync, Unpin, UnwindSafe
);
assert_not_impl_any!(RawHwlocError:
Binary, Default, Deref, Drop, IntoIterator, LowerExp, LowerHex, Octal,
PartialOrd, Pointer, Read, UpperExp, UpperHex, fmt::Write, io::Write
);
proptest! {
#[test]
fn check_errno_normal(
output: i128,
lowest_good: i128,
start_errno: i32,
new_errno: NonZeroU32,
) {
let start_errno = Errno(start_errno.wrapping_abs());
let new_errno = Errno(i32::from_ne_bytes(new_errno.get().to_ne_bytes()).wrapping_abs());
let _errno_guard = ErrnoGuard::new(start_errno);
let expected_errno = (output < lowest_good).then_some(new_errno);
prop_assert_eq!(
super::check_errno(
|| {
errno::set_errno(new_errno);
output
},
lowest_good
),
(output, expected_errno)
);
prop_assert_eq!(errno::errno(), start_errno);
}
#[test]
fn check_errno_in_vain(output: i128, lowest_good: i128, start_errno: i32) {
let start_errno = Errno(start_errno.wrapping_abs());
let _errno_guard = ErrnoGuard::new(start_errno);
prop_assert_eq!(super::check_errno(|| output, lowest_good), (output, None));
prop_assert_eq!(errno::errno(), start_errno);
}
#[test]
fn ptr_success(nonnull: NonZeroUsize, start_errno: i32, new_errno: NonZeroU32) {
let start_errno = Errno(start_errno.wrapping_abs());
let new_errno = Errno(i32::from_ne_bytes(new_errno.get().to_ne_bytes()).wrapping_abs());
let _errno_guard = ErrnoGuard::new(start_errno);
let api = "foo";
let nonnull_ptr = NonNull::new(nonnull.get() as *mut u8).unwrap();
prop_assert_eq!(
super::call_hwloc_ptr(api, || {
errno::set_errno(new_errno);
nonnull_ptr.as_ptr()
}),
Ok(nonnull_ptr)
);
prop_assert_eq!(errno::errno(), start_errno);
prop_assert_eq!(
super::call_hwloc_ptr_mut(api, || {
errno::set_errno(new_errno);
nonnull_ptr.as_ptr()
}),
Ok(nonnull_ptr)
);
prop_assert_eq!(errno::errno(), start_errno);
}
#[test]
fn ptr_fail_with_errno(start_errno: i32, new_errno: NonZeroU32) {
let start_errno = Errno(start_errno.wrapping_abs());
let new_errno = Errno(i32::from_ne_bytes(new_errno.get().to_ne_bytes()).wrapping_abs());
let _errno_guard = ErrnoGuard::new(start_errno);
let api = "bar";
let null_ptr = ptr::null_mut::<Vec<f32>>();
prop_assert_eq!(
super::call_hwloc_ptr(api, || {
errno::set_errno(new_errno);
null_ptr
}),
Err(RawHwlocError {
api,
errno: Some(new_errno)
})
);
prop_assert_eq!(errno::errno(), start_errno);
prop_assert_eq!(
super::call_hwloc_ptr_mut(api, || {
errno::set_errno(new_errno);
null_ptr
}),
Err(RawHwlocError {
api,
errno: Some(new_errno)
})
);
prop_assert_eq!(errno::errno(), start_errno);
}
#[test]
fn ptr_fail_wo_errno(start_errno: i32) {
let start_errno = Errno(start_errno.wrapping_abs());
let _errno_guard = ErrnoGuard::new(start_errno);
let api = "baz";
let null_ptr = ptr::null_mut::<String>();
prop_assert_eq!(
super::call_hwloc_ptr(api, || { null_ptr }),
Err(RawHwlocError { api, errno: None })
);
prop_assert_eq!(errno::errno(), start_errno);
prop_assert_eq!(
super::call_hwloc_ptr_mut(api, || { null_ptr }),
Err(RawHwlocError { api, errno: None })
);
prop_assert_eq!(errno::errno(), start_errno);
}
#[test]
fn int_normal_general(output: i32, start_errno: i32, new_errno: NonZeroU32) {
let start_errno = Errno(start_errno.wrapping_abs());
let new_errno = Errno(i32::from_ne_bytes(new_errno.get().to_ne_bytes()).wrapping_abs());
let _errno_guard = ErrnoGuard::new(start_errno);
let api = "abc";
let call = || {
let res = super::call_hwloc_positive_or_minus1(api, || {
errno::set_errno(new_errno);
output
});
prop_assert_eq!(errno::errno(), start_errno);
Ok(res)
};
match output {
bad if bad < -1 => assert_panics(call)?,
-1 => prop_assert_eq!(
call()?,
Err(RawHwlocError {
api,
errno: Some(new_errno)
})
),
positive => prop_assert_eq!(call()?, Ok(u32::try_from(positive).unwrap())),
}
}
#[test]
fn int_normal_err_with_errno(start_errno: i32, new_errno: NonZeroU32) {
let start_errno = Errno(start_errno.wrapping_abs());
let new_errno = Errno(i32::from_ne_bytes(new_errno.get().to_ne_bytes()).wrapping_abs());
let _errno_guard = ErrnoGuard::new(start_errno);
let api = "def";
prop_assert_eq!(
super::call_hwloc_positive_or_minus1(api, || {
errno::set_errno(new_errno);
-1
}),
Err(RawHwlocError {
api,
errno: Some(new_errno)
})
);
prop_assert_eq!(errno::errno(), start_errno);
}
#[test]
fn int_normal_err_wo_errno(start_errno: i32) {
let start_errno = Errno(start_errno.wrapping_abs());
let _errno_guard = ErrnoGuard::new(start_errno);
let api = "ghi";
prop_assert_eq!(
super::call_hwloc_positive_or_minus1(api, || -1),
Err(RawHwlocError { api, errno: None })
);
prop_assert_eq!(errno::errno(), start_errno);
}
#[test]
fn int_raw_with_errno(
output: i32,
lowest_good_value: i32,
start_errno: i32,
new_errno: NonZeroU32,
) {
let start_errno = Errno(start_errno.wrapping_abs());
let new_errno = Errno(i32::from_ne_bytes(new_errno.get().to_ne_bytes()).wrapping_abs());
let _errno_guard = ErrnoGuard::new(start_errno);
let api = "jkl";
let result = super::call_hwloc_int_raw(
api,
|| {
errno::set_errno(new_errno);
output
},
lowest_good_value,
);
prop_assert_eq!(errno::errno(), start_errno);
if output >= lowest_good_value {
prop_assert_eq!(result, Ok(output));
} else {
prop_assert_eq!(
result,
Err(RawNegIntError {
api,
result: output,
errno: Some(new_errno),
})
);
}
}
#[test]
fn int_raw_wo_errno(output: i32, lowest_good_value: i32, start_errno: i32) {
let start_errno = Errno(start_errno.wrapping_abs());
let _errno_guard = ErrnoGuard::new(start_errno);
let api = "opq";
let result = super::call_hwloc_int_raw(api, || output, lowest_good_value);
prop_assert_eq!(errno::errno(), start_errno);
if output >= lowest_good_value {
prop_assert_eq!(result, Ok(output));
} else {
prop_assert_eq!(
result,
Err(RawNegIntError {
api,
result: output,
errno: None,
})
);
}
}
#[test]
fn bool_general(output: i32, start_errno: i32, new_errno: NonZeroU32) {
let start_errno = Errno(start_errno.wrapping_abs());
let new_errno = Errno(i32::from_ne_bytes(new_errno.get().to_ne_bytes()).wrapping_abs());
let _errno_guard = ErrnoGuard::new(start_errno);
let api = "rst";
let call = || {
let res = super::call_hwloc_bool(api, || {
errno::set_errno(new_errno);
output
});
prop_assert_eq!(errno::errno(), start_errno);
Ok(res)
};
match output {
-1 => prop_assert_eq!(
call()?,
Err(RawHwlocError {
api,
errno: Some(new_errno)
})
),
0 => prop_assert_eq!(call()?, Ok(false)),
1 => prop_assert_eq!(call()?, Ok(true)),
_ => assert_panics(call)?,
}
}
#[test]
fn bool_err_with_errno(start_errno: i32, new_errno: NonZeroU32) {
let start_errno = Errno(start_errno.wrapping_abs());
let new_errno = Errno(i32::from_ne_bytes(new_errno.get().to_ne_bytes()).wrapping_abs());
let _errno_guard = ErrnoGuard::new(start_errno);
let api = "uvw";
prop_assert_eq!(
super::call_hwloc_bool(api, || {
errno::set_errno(new_errno);
-1
}),
Err(RawHwlocError {
api,
errno: Some(new_errno)
})
);
prop_assert_eq!(errno::errno(), start_errno);
}
#[test]
fn bool_err_wo_errno(start_errno: i32) {
let start_errno = Errno(start_errno.wrapping_abs());
let _errno_guard = ErrnoGuard::new(start_errno);
let api = "xyz";
prop_assert_eq!(
super::call_hwloc_bool(api, || -1),
Err(RawHwlocError { api, errno: None })
);
prop_assert_eq!(errno::errno(), start_errno);
}
#[test]
fn bool_success(output: bool, start_errno: i32, new_errno: NonZeroU32) {
let start_errno = Errno(start_errno.wrapping_abs());
let new_errno = Errno(i32::from_ne_bytes(new_errno.get().to_ne_bytes()).wrapping_abs());
let _errno_guard = ErrnoGuard::new(start_errno);
let api = "cthulhu_phtagn";
prop_assert_eq!(
super::call_hwloc_bool(api, || {
errno::set_errno(new_errno);
i32::from(output)
}),
Ok(output)
);
prop_assert_eq!(errno::errno(), start_errno);
}
#[test]
fn parameter_error_from(x: i128) {
prop_assert_eq!(ParameterError::from(x), ParameterError(x));
}
}
fn any_hybrid_error() -> impl Strategy<Value = HybridError<ParameterError<u64>>> {
prop_oneof![
any::<u64>().prop_map(|p| HybridError::Rust(ParameterError(p))),
any::<Option<i32>>().prop_map(|errno| HybridError::Hwloc(RawHwlocError {
api: "hwloc_fancy_api",
errno: errno.map(Errno),
}))
]
}
proptest! {
#[test]
fn expect_only_hwloc(hybrid in any_hybrid_error()) {
match &hybrid {
HybridError::Rust(_) => assert_panics(|| hybrid.expect_only_hwloc("will happen"))?,
HybridError::Hwloc(h) => prop_assert_eq!(hybrid.expect_only_hwloc("won't happen"), *h),
}
}
}
}