lm_sensors/
errors.rs

1//! Errors.
2
3#[cfg(test)]
4mod tests;
5
6use core::ffi::{c_char, c_int, c_uint};
7use core::sync::atomic;
8use core::sync::atomic::AtomicPtr;
9use core::{fmt, ptr};
10use std::path::{Path, PathBuf};
11use std::{io, process};
12
13use crate::utils::{CallBacks, lossy_string_from_c_str, path_from_c_str, str_from_c_str};
14
15/// Result of a fallible function.
16pub type Result<T> = core::result::Result<T, Error>;
17
18/// Error of a failed function.
19#[allow(missing_docs, reason = "variant names are self-explanatory.")]
20#[derive(thiserror::Error, Debug)]
21#[non_exhaustive]
22pub enum Error {
23    #[error("{operation} failed: [{number}] {description}")]
24    LMSensors {
25        operation: &'static str,
26        number: c_int,
27        description: String,
28    },
29
30    #[error("{operation} failed")]
31    IO {
32        operation: &'static str,
33        source: io::Error,
34    },
35
36    #[error("{operation} failed on '{path}'")]
37    IO1Path {
38        operation: &'static str,
39        path: PathBuf,
40        source: io::Error,
41    },
42
43    #[error("path {0} is not valid UTF-8")]
44    PathIsNotUTF8(PathBuf),
45
46    #[error(transparent)]
47    UnexpectedNul(#[from] alloc::ffi::NulError),
48
49    #[error(transparent)]
50    InvalidUTF8CString(#[from] alloc::ffi::IntoStringError),
51
52    #[error(transparent)]
53    InvalidUTF8(#[from] core::str::Utf8Error),
54
55    #[error(transparent)]
56    NotInteger(#[from] core::num::ParseIntError),
57}
58
59impl Error {
60    pub(crate) fn from_io(operation: &'static str, source: io::Error) -> Self {
61        Error::IO { operation, source }
62    }
63
64    pub(crate) fn from_io_path(
65        operation: &'static str,
66        path: impl Into<PathBuf>,
67        source: io::Error,
68    ) -> Self {
69        Error::IO1Path {
70            source,
71            operation,
72            path: path.into(),
73        }
74    }
75
76    pub(crate) fn from_lm_sensors(operation: &'static str, number: c_int) -> Self {
77        // Safety: we assume `sensors_strerror()` can be called anytime,
78        // including before `sensors_init()` and after `sensors_cleanup()`.
79        let description = unsafe { sensors_sys::sensors_strerror(number) };
80        let description = lossy_string_from_c_str(description, "").into_owned();
81
82        Error::LMSensors {
83            operation,
84            number: c_int::abs(number),
85            description,
86        }
87    }
88}
89
90/// Listener for fatal errors reported by LM sensors.
91pub trait Listener: fmt::Debug {
92    /// This function is called when a configuration parsing error happens.
93    fn on_lm_sensors_config_error(&self, error: &str, file_name: Option<&Path>, line_number: usize);
94
95    /// This function is called when a fatal error happens,
96    /// *e.g.,* an out of memory situation.
97    ///
98    /// # Warning
99    ///
100    /// Due to requirements of the LM sensors library, this process is aborted
101    /// after this function returns.
102    fn on_lm_sensors_fatal_error(&self, error: &str, procedure: &str);
103}
104
105#[derive(Debug)]
106pub(crate) struct DefaultListener;
107
108impl Listener for DefaultListener {
109    fn on_lm_sensors_config_error(
110        &self,
111        error: &str,
112        file_name: Option<&Path>,
113        line_number: usize,
114    ) {
115        if let Some(file_name) = file_name {
116            eprintln!(
117                "[ERROR] lm-sensors configuration: {}, at file '{}' line {}.",
118                error,
119                file_name.display(),
120                line_number
121            );
122        } else {
123            eprintln!("[ERROR] lm-sensors configuration: {error}, at line {line_number}.");
124        }
125    }
126
127    fn on_lm_sensors_fatal_error(&self, error: &str, procedure: &str) {
128        eprintln!("[FATAL] lm-sensors: {error}, at procedure '{procedure}'.");
129    }
130}
131
132static ERROR_LISTENER: AtomicPtr<Box<dyn Listener>> = AtomicPtr::new(ptr::null_mut());
133
134#[derive(Debug)]
135pub(crate) struct Reporter {
136    previous_error_listener: *mut Box<dyn Listener>,
137    previous_call_backs: CallBacks,
138}
139
140impl Reporter {
141    pub(crate) fn new(error_listener: *mut Box<dyn Listener>) -> Self {
142        let call_backs =
143            CallBacks::new(Self::parse_error, Self::parse_error_wfn, Self::fatal_error);
144
145        let previous_error_listener = ERROR_LISTENER.swap(error_listener, atomic::Ordering::AcqRel);
146        let previous_call_backs = unsafe { call_backs.replace() };
147
148        Self {
149            previous_error_listener,
150            previous_call_backs,
151        }
152    }
153
154    pub(crate) fn restore(&self) -> *mut Box<dyn Listener> {
155        unsafe { self.previous_call_backs.set() };
156        ERROR_LISTENER.swap(self.previous_error_listener, atomic::Ordering::AcqRel)
157    }
158
159    extern "C" fn parse_error(err: *const c_char, line_number: c_int) {
160        Self::parse_error_wfn(err, ptr::null(), line_number);
161    }
162
163    extern "C" fn parse_error_wfn(err: *const c_char, file_name: *const c_char, line_no: c_int) {
164        let error = lossy_string_from_c_str(err, "<unknown-error>");
165        let file_name = path_from_c_str(file_name);
166
167        #[expect(clippy::cast_sign_loss, clippy::as_conversions)]
168        let line_number = line_no.max(1) as c_uint as usize;
169
170        #[expect(clippy::as_conversions)]
171        let listener = unsafe { ERROR_LISTENER.load(atomic::Ordering::Acquire).as_ref() }.map_or(
172            &crate::errors::DefaultListener as &dyn Listener,
173            |listener| &**listener,
174        );
175
176        listener.on_lm_sensors_config_error(&error, file_name, line_number);
177    }
178
179    extern "C" fn fatal_error(procedure: *const c_char, err: *const c_char) {
180        let procedure = str_from_c_str(procedure).unwrap_or("<unknown-procedure>");
181        let error = lossy_string_from_c_str(err, "<unknown-error>");
182
183        #[expect(clippy::as_conversions)]
184        let listener = unsafe { ERROR_LISTENER.load(atomic::Ordering::Acquire).as_ref() }.map_or(
185            &crate::errors::DefaultListener as &dyn Listener,
186            |listener| &**listener,
187        );
188
189        listener.on_lm_sensors_fatal_error(&error, procedure);
190        process::abort();
191    }
192}