1use crate::object::{TopologyObject, TopologyObjectID};
13#[cfg(doc)]
14use crate::topology::Topology;
15use derive_more::From;
16use errno::Errno;
17#[allow(unused)]
18#[cfg(test)]
19use similar_asserts::assert_eq;
20use std::{
21 error::Error,
22 ffi::{c_int, c_uint},
23 fmt::Debug,
24 ptr::NonNull,
25};
26use thiserror::Error;
27
28struct ErrnoGuard(Errno);
30impl ErrnoGuard {
32 fn new(errno: Errno) -> Self {
34 let old_errno = errno::errno();
35 errno::set_errno(errno);
36 Self(old_errno)
37 }
38}
39impl Drop for ErrnoGuard {
41 fn drop(&mut self) {
42 errno::set_errno(self.0);
43 }
44}
45
46fn check_errno<R: Copy + Ord>(
55 callback: impl FnOnce() -> R,
56 lowest_good_value: R,
57) -> (R, Option<Errno>) {
58 let _guard = ErrnoGuard::new(Errno(0));
59 let result = callback();
60 fn interpret_errno(should_check_errno: bool) -> Option<Errno> {
62 should_check_errno
63 .then(|| {
64 let errno = errno::errno();
65 (errno != Errno(0)).then_some(errno)
66 })
67 .flatten()
68 }
69 (result, interpret_errno(result < lowest_good_value))
70}
71
72#[derive(Copy, Clone, Debug, Error, Eq, Hash, PartialEq)]
86#[error("{api} failed with errno {errno:?}")]
87pub struct RawHwlocError {
88 pub api: &'static str,
90
91 #[cfg_attr(windows, doc = "")]
93 #[cfg_attr(windows, doc = "Note that even for entry points where hwloc is")]
94 #[cfg_attr(windows, doc = "documented to set errno, this member may be")]
95 #[cfg_attr(windows, doc = "`None` on Windows. This happens because ")]
96 #[cfg_attr(windows, doc = "Windows has multiple implementations of its")]
97 #[cfg_attr(windows, doc = "standard C library, called C Run-Times (CRTs),")]
98 #[cfg_attr(windows, doc = "and getting your application to be linked")]
99 #[cfg_attr(windows, doc = "against the same CRT as your hwloc DLL is")]
100 #[cfg_attr(windows, doc = "basically a matter of fragile guesswork...")]
101 pub errno: Option<Errno>,
102}
103
104pub(crate) fn call_hwloc_ptr_mut<T>(
110 api: &'static str,
111 call: impl FnOnce() -> *mut T,
112) -> Result<NonNull<T>, RawHwlocError> {
113 let (result, errno) = check_errno(call, 1 as *mut T);
114 NonNull::new(result).ok_or(RawHwlocError { api, errno })
115}
116
117pub(crate) fn call_hwloc_ptr<T>(
123 api: &'static str,
124 call: impl FnOnce() -> *const T,
125) -> Result<NonNull<T>, RawHwlocError> {
126 call_hwloc_ptr_mut(api, || call().cast_mut())
127}
128
129pub(crate) fn call_hwloc_zero_or_minus1(
140 api: &'static str,
141 call: impl FnOnce() -> c_int,
142) -> Result<(), RawHwlocError> {
143 fn check_raw_result(raw_result: Result<c_uint, RawHwlocError>) -> Result<(), RawHwlocError> {
145 match raw_result {
146 Ok(0) => Ok(()),
147 Ok(positive) => unreachable!(
148 "Unexpected integer > 0 from hwloc function that should return 0 or -1: {positive}"
149 ),
150 Err(e) => Err(e),
151 }
152 }
153 check_raw_result(call_hwloc_positive_or_minus1(api, call))
154}
155
156pub(crate) fn call_hwloc_bool(
159 api: &'static str,
160 call: impl FnOnce() -> c_int,
161) -> Result<bool, RawHwlocError> {
162 fn check_raw_result(
164 api: &'static str,
165 raw_result: Result<c_uint, RawHwlocError>,
166 ) -> Result<bool, RawHwlocError> {
167 match raw_result {
168 Ok(1) => Ok(true),
169 Ok(0) => Ok(false),
170 Ok(other) => unreachable!("Got unexpected boolean value {other} from {api}"),
171 Err(e) => Err(e),
172 }
173 }
174 check_raw_result(api, call_hwloc_positive_or_minus1(api, call))
175}
176
177pub(crate) fn call_hwloc_positive_or_minus1(
187 api: &'static str,
188 call: impl FnOnce() -> c_int,
189) -> Result<c_uint, RawHwlocError> {
190 fn check_raw_result(
192 raw_result: Result<c_int, RawNegIntError>,
193 ) -> Result<c_uint, RawHwlocError> {
194 match raw_result {
195 Ok(positive) => {
196 Ok(c_uint::try_from(positive).expect("Cannot happen due to 0 threshold above"))
197 }
198 Err(RawNegIntError {
199 api,
200 result: -1,
201 errno,
202 }) => Err(RawHwlocError { api, errno }),
203 Err(other_err) => {
204 unreachable!("Unexpected negative integer != -1 from hwloc function: {other_err}")
205 }
206 }
207 }
208 check_raw_result(call_hwloc_int_raw(api, call, 0))
209}
210
211#[derive(Copy, Clone, Debug, Error, Eq, Hash, PartialEq)]
218#[error("{api} failed with result {result} and errno {errno:?}")]
219pub(crate) struct RawNegIntError {
220 pub(crate) api: &'static str,
222
223 pub(crate) result: c_int,
225
226 pub(crate) errno: Option<Errno>,
228}
229pub(crate) fn call_hwloc_int_raw(
234 api: &'static str,
235 call: impl FnOnce() -> c_int,
236 lowest_good_value: c_int,
237) -> Result<c_int, RawNegIntError> {
238 fn check_raw_result(
240 api: &'static str,
241 (result, errno): (c_int, Option<Errno>),
242 lowest_good_value: c_int,
243 ) -> Result<c_int, RawNegIntError> {
244 (result >= lowest_good_value)
245 .then_some(result)
246 .ok_or(RawNegIntError { api, result, errno })
247 }
248 check_raw_result(api, check_errno(call, lowest_good_value), lowest_good_value)
249}
250
251#[derive(Copy, Clone, Debug, Eq, Error, Hash, PartialEq)]
262pub enum HybridError<RustError: Error> {
263 #[error(transparent)]
265 Rust(#[from] RustError),
266
267 #[error(transparent)]
269 Hwloc(RawHwlocError),
276}
277impl<RustError: Error> HybridError<RustError> {
279 pub fn expect_only_hwloc(self, msg: &str) -> RawHwlocError {
282 match self {
283 Self::Hwloc(e) => e,
284 Self::Rust(e) => panic!("{msg} (got unexpected Rust error {e}"),
285 }
286 }
287}
288
289#[derive(Copy, Clone, Debug, Default, Eq, Error, Hash, Ord, PartialEq, PartialOrd)]
299#[error("can't pass a string with NUL chars to hwloc")]
300pub struct NulError;
301
302#[derive(Copy, Clone, Debug, Default, Eq, Error, From, Hash, Ord, PartialEq, PartialOrd)]
309#[error("parameter {0:?} isn't valid for this operation")]
310pub struct ParameterError<Parameter: Debug>(pub Parameter);
311
312pub type FlagsError<Flags> = ParameterError<Flags>;
322
323#[allow(missing_copy_implementations)]
337#[derive(Clone, Debug, Eq, Error, Hash, PartialEq)]
338#[error("object #{0} doesn't belong to this topology")]
339pub struct ForeignObjectError(TopologyObjectID);
340impl<'topology> From<&'topology TopologyObject> for ForeignObjectError {
342 fn from(object: &'topology TopologyObject) -> Self {
343 Self(object.global_persistent_index())
344 }
345}
346
347#[cfg(test)]
348mod tests {
349 use super::*;
350 use crate::tests::assert_panics;
351 use proptest::prelude::*;
352 #[allow(unused)]
353 use similar_asserts::assert_eq;
354 use static_assertions::{assert_impl_all, assert_not_impl_any, assert_type_eq_all};
355 use std::{
356 fmt::{self, Binary, LowerExp, LowerHex, Octal, Pointer, UpperExp, UpperHex},
357 hash::Hash,
358 io::{self, Read},
359 num::{NonZeroU32, NonZeroUsize},
360 ops::Deref,
361 panic::UnwindSafe,
362 ptr,
363 };
364
365 assert_type_eq_all!(FlagsError<()>, ParameterError<()>);
368 assert_impl_all!(ForeignObjectError:
369 Clone, Error, Hash, Sized, Sync, Unpin, UnwindSafe
370 );
371 assert_not_impl_any!(ForeignObjectError:
372 Binary, Copy, Default, Deref, Drop, IntoIterator, LowerExp, LowerHex,
373 Octal, PartialOrd, Pointer, Read, UpperExp, UpperHex, fmt::Write,
374 io::Write
375 );
376 assert_impl_all!(HybridError<NulError>:
377 Copy, Error, Hash, Sized, Sync, Unpin, UnwindSafe
378 );
379 assert_not_impl_any!(HybridError<NulError>:
380 Binary, Default, Deref, Drop, IntoIterator, LowerExp, LowerHex, Octal,
381 PartialOrd, Pointer, Read, UpperExp, UpperHex, fmt::Write, io::Write
382 );
383 assert_impl_all!(NulError:
384 Copy, Default, Error, Hash, Ord, Sized, Sync, Unpin, UnwindSafe
385 );
386 assert_not_impl_any!(NulError:
387 Binary, Deref, Drop, IntoIterator, LowerExp, LowerHex, Octal, Pointer,
388 Read, UpperExp, UpperHex, fmt::Write, io::Write
389 );
390 assert_impl_all!(ParameterError<()>:
391 Copy, Default, Error, Hash, Ord, Sized, Sync, Unpin, UnwindSafe
392 );
393 assert_not_impl_any!(ParameterError<()>:
394 Binary, Deref, Drop, IntoIterator, LowerExp, LowerHex, Octal, Pointer,
395 Read, UpperExp, UpperHex, fmt::Write, io::Write
396 );
397 assert_impl_all!(RawHwlocError:
398 Copy, Error, Hash, Sized, Sync, Unpin, UnwindSafe
399 );
400 assert_not_impl_any!(RawHwlocError:
401 Binary, Default, Deref, Drop, IntoIterator, LowerExp, LowerHex, Octal,
402 PartialOrd, Pointer, Read, UpperExp, UpperHex, fmt::Write, io::Write
403 );
404
405 proptest! {
406 #[test]
407 fn check_errno_normal(
408 output: i128,
409 lowest_good: i128,
410 start_errno: i32,
411 new_errno: NonZeroU32,
412 ) {
413 let start_errno = Errno(start_errno.wrapping_abs());
415 let new_errno = Errno(i32::from_ne_bytes(new_errno.get().to_ne_bytes()).wrapping_abs());
416 let _errno_guard = ErrnoGuard::new(start_errno);
417
418 let expected_errno = (output < lowest_good).then_some(new_errno);
420 prop_assert_eq!(
421 super::check_errno(
422 || {
423 errno::set_errno(new_errno);
424 output
425 },
426 lowest_good
427 ),
428 (output, expected_errno)
429 );
430 prop_assert_eq!(errno::errno(), start_errno);
431 }
432
433 #[test]
434 fn check_errno_in_vain(output: i128, lowest_good: i128, start_errno: i32) {
435 let start_errno = Errno(start_errno.wrapping_abs());
437 let _errno_guard = ErrnoGuard::new(start_errno);
438 prop_assert_eq!(super::check_errno(|| output, lowest_good), (output, None));
439 prop_assert_eq!(errno::errno(), start_errno);
440 }
441
442 #[test]
443 fn ptr_success(nonnull: NonZeroUsize, start_errno: i32, new_errno: NonZeroU32) {
444 let start_errno = Errno(start_errno.wrapping_abs());
446 let new_errno = Errno(i32::from_ne_bytes(new_errno.get().to_ne_bytes()).wrapping_abs());
447 let _errno_guard = ErrnoGuard::new(start_errno);
448 let api = "foo";
449 let nonnull_ptr = NonNull::new(nonnull.get() as *mut u8).unwrap();
450
451 prop_assert_eq!(
453 super::call_hwloc_ptr(api, || {
454 errno::set_errno(new_errno);
455 nonnull_ptr.as_ptr()
456 }),
457 Ok(nonnull_ptr)
458 );
459 prop_assert_eq!(errno::errno(), start_errno);
460 prop_assert_eq!(
461 super::call_hwloc_ptr_mut(api, || {
462 errno::set_errno(new_errno);
463 nonnull_ptr.as_ptr()
464 }),
465 Ok(nonnull_ptr)
466 );
467 prop_assert_eq!(errno::errno(), start_errno);
468 }
469
470 #[test]
471 fn ptr_fail_with_errno(start_errno: i32, new_errno: NonZeroU32) {
472 let start_errno = Errno(start_errno.wrapping_abs());
474 let new_errno = Errno(i32::from_ne_bytes(new_errno.get().to_ne_bytes()).wrapping_abs());
475 let _errno_guard = ErrnoGuard::new(start_errno);
476 let api = "bar";
477
478 let null_ptr = ptr::null_mut::<Vec<f32>>();
480 prop_assert_eq!(
481 super::call_hwloc_ptr(api, || {
482 errno::set_errno(new_errno);
483 null_ptr
484 }),
485 Err(RawHwlocError {
486 api,
487 errno: Some(new_errno)
488 })
489 );
490 prop_assert_eq!(errno::errno(), start_errno);
491 prop_assert_eq!(
492 super::call_hwloc_ptr_mut(api, || {
493 errno::set_errno(new_errno);
494 null_ptr
495 }),
496 Err(RawHwlocError {
497 api,
498 errno: Some(new_errno)
499 })
500 );
501 prop_assert_eq!(errno::errno(), start_errno);
502 }
503
504 #[test]
505 fn ptr_fail_wo_errno(start_errno: i32) {
506 let start_errno = Errno(start_errno.wrapping_abs());
508 let _errno_guard = ErrnoGuard::new(start_errno);
509 let api = "baz";
510 let null_ptr = ptr::null_mut::<String>();
511
512 prop_assert_eq!(
514 super::call_hwloc_ptr(api, || { null_ptr }),
515 Err(RawHwlocError { api, errno: None })
516 );
517 prop_assert_eq!(errno::errno(), start_errno);
518 prop_assert_eq!(
519 super::call_hwloc_ptr_mut(api, || { null_ptr }),
520 Err(RawHwlocError { api, errno: None })
521 );
522 prop_assert_eq!(errno::errno(), start_errno);
523 }
524
525 #[test]
526 fn int_normal_general(output: i32, start_errno: i32, new_errno: NonZeroU32) {
527 let start_errno = Errno(start_errno.wrapping_abs());
529 let new_errno = Errno(i32::from_ne_bytes(new_errno.get().to_ne_bytes()).wrapping_abs());
530 let _errno_guard = ErrnoGuard::new(start_errno);
531 let api = "abc";
532
533 let call = || {
535 let res = super::call_hwloc_positive_or_minus1(api, || {
536 errno::set_errno(new_errno);
537 output
538 });
539 prop_assert_eq!(errno::errno(), start_errno);
540 Ok(res)
541 };
542
543 match output {
545 bad if bad < -1 => assert_panics(call)?,
546 -1 => prop_assert_eq!(
547 call()?,
548 Err(RawHwlocError {
549 api,
550 errno: Some(new_errno)
551 })
552 ),
553 positive => prop_assert_eq!(call()?, Ok(u32::try_from(positive).unwrap())),
554 }
555 }
556
557 #[test]
558 fn int_normal_err_with_errno(start_errno: i32, new_errno: NonZeroU32) {
559 let start_errno = Errno(start_errno.wrapping_abs());
561 let new_errno = Errno(i32::from_ne_bytes(new_errno.get().to_ne_bytes()).wrapping_abs());
562 let _errno_guard = ErrnoGuard::new(start_errno);
563 let api = "def";
564
565 prop_assert_eq!(
567 super::call_hwloc_positive_or_minus1(api, || {
568 errno::set_errno(new_errno);
569 -1
570 }),
571 Err(RawHwlocError {
572 api,
573 errno: Some(new_errno)
574 })
575 );
576 prop_assert_eq!(errno::errno(), start_errno);
577 }
578
579 #[test]
580 fn int_normal_err_wo_errno(start_errno: i32) {
581 let start_errno = Errno(start_errno.wrapping_abs());
583 let _errno_guard = ErrnoGuard::new(start_errno);
584 let api = "ghi";
585
586 prop_assert_eq!(
588 super::call_hwloc_positive_or_minus1(api, || -1),
589 Err(RawHwlocError { api, errno: None })
590 );
591 prop_assert_eq!(errno::errno(), start_errno);
592 }
593
594 #[test]
595 fn int_raw_with_errno(
596 output: i32,
597 lowest_good_value: i32,
598 start_errno: i32,
599 new_errno: NonZeroU32,
600 ) {
601 let start_errno = Errno(start_errno.wrapping_abs());
603 let new_errno = Errno(i32::from_ne_bytes(new_errno.get().to_ne_bytes()).wrapping_abs());
604 let _errno_guard = ErrnoGuard::new(start_errno);
605 let api = "jkl";
606
607 let result = super::call_hwloc_int_raw(
609 api,
610 || {
611 errno::set_errno(new_errno);
612 output
613 },
614 lowest_good_value,
615 );
616 prop_assert_eq!(errno::errno(), start_errno);
617
618 if output >= lowest_good_value {
620 prop_assert_eq!(result, Ok(output));
621 } else {
622 prop_assert_eq!(
623 result,
624 Err(RawNegIntError {
625 api,
626 result: output,
627 errno: Some(new_errno),
628 })
629 );
630 }
631 }
632
633 #[test]
634 fn int_raw_wo_errno(output: i32, lowest_good_value: i32, start_errno: i32) {
635 let start_errno = Errno(start_errno.wrapping_abs());
637 let _errno_guard = ErrnoGuard::new(start_errno);
638 let api = "opq";
639
640 let result = super::call_hwloc_int_raw(api, || output, lowest_good_value);
642 prop_assert_eq!(errno::errno(), start_errno);
643
644 if output >= lowest_good_value {
646 prop_assert_eq!(result, Ok(output));
647 } else {
648 prop_assert_eq!(
649 result,
650 Err(RawNegIntError {
651 api,
652 result: output,
653 errno: None,
654 })
655 );
656 }
657 }
658
659 #[test]
660 fn bool_general(output: i32, start_errno: i32, new_errno: NonZeroU32) {
661 let start_errno = Errno(start_errno.wrapping_abs());
663 let new_errno = Errno(i32::from_ne_bytes(new_errno.get().to_ne_bytes()).wrapping_abs());
664 let _errno_guard = ErrnoGuard::new(start_errno);
665 let api = "rst";
666
667 let call = || {
669 let res = super::call_hwloc_bool(api, || {
670 errno::set_errno(new_errno);
671 output
672 });
673 prop_assert_eq!(errno::errno(), start_errno);
674 Ok(res)
675 };
676
677 match output {
679 -1 => prop_assert_eq!(
680 call()?,
681 Err(RawHwlocError {
682 api,
683 errno: Some(new_errno)
684 })
685 ),
686 0 => prop_assert_eq!(call()?, Ok(false)),
687 1 => prop_assert_eq!(call()?, Ok(true)),
688 _ => assert_panics(call)?,
689 }
690 }
691
692 #[test]
693 fn bool_err_with_errno(start_errno: i32, new_errno: NonZeroU32) {
694 let start_errno = Errno(start_errno.wrapping_abs());
696 let new_errno = Errno(i32::from_ne_bytes(new_errno.get().to_ne_bytes()).wrapping_abs());
697 let _errno_guard = ErrnoGuard::new(start_errno);
698 let api = "uvw";
699
700 prop_assert_eq!(
702 super::call_hwloc_bool(api, || {
703 errno::set_errno(new_errno);
704 -1
705 }),
706 Err(RawHwlocError {
707 api,
708 errno: Some(new_errno)
709 })
710 );
711 prop_assert_eq!(errno::errno(), start_errno);
712 }
713
714 #[test]
715 fn bool_err_wo_errno(start_errno: i32) {
716 let start_errno = Errno(start_errno.wrapping_abs());
718 let _errno_guard = ErrnoGuard::new(start_errno);
719 let api = "xyz";
720
721 prop_assert_eq!(
723 super::call_hwloc_bool(api, || -1),
724 Err(RawHwlocError { api, errno: None })
725 );
726 prop_assert_eq!(errno::errno(), start_errno);
727 }
728
729 #[test]
730 fn bool_success(output: bool, start_errno: i32, new_errno: NonZeroU32) {
731 let start_errno = Errno(start_errno.wrapping_abs());
733 let new_errno = Errno(i32::from_ne_bytes(new_errno.get().to_ne_bytes()).wrapping_abs());
734 let _errno_guard = ErrnoGuard::new(start_errno);
735 let api = "cthulhu_phtagn";
736
737 prop_assert_eq!(
739 super::call_hwloc_bool(api, || {
740 errno::set_errno(new_errno);
741 i32::from(output)
742 }),
743 Ok(output)
744 );
745 prop_assert_eq!(errno::errno(), start_errno);
746 }
747
748 #[test]
749 fn parameter_error_from(x: i128) {
750 prop_assert_eq!(ParameterError::from(x), ParameterError(x));
751 }
752 }
753
754 fn any_hybrid_error() -> impl Strategy<Value = HybridError<ParameterError<u64>>> {
756 prop_oneof![
757 any::<u64>().prop_map(|p| HybridError::Rust(ParameterError(p))),
758 any::<Option<i32>>().prop_map(|errno| HybridError::Hwloc(RawHwlocError {
759 api: "hwloc_fancy_api",
760 errno: errno.map(Errno),
761 }))
762 ]
763 }
764 proptest! {
766 #[test]
767 fn expect_only_hwloc(hybrid in any_hybrid_error()) {
768 match &hybrid {
769 HybridError::Rust(_) => assert_panics(|| hybrid.expect_only_hwloc("will happen"))?,
770 HybridError::Hwloc(h) => prop_assert_eq!(hybrid.expect_only_hwloc("won't happen"), *h),
771 }
772 }
773 }
774}