1#[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
15pub type Result<T> = core::result::Result<T, Error>;
17
18#[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 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
90pub trait Listener: fmt::Debug {
92 fn on_lm_sensors_config_error(&self, error: &str, file_name: Option<&Path>, line_number: usize);
94
95 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}