1#![doc = include_str!("../README.md")]
2#![warn(unsafe_op_in_unsafe_fn, clippy::all, clippy::pedantic)]
3#![allow(
5 clippy::absolute_paths,
6 clippy::arbitrary_source_item_ordering,
7 clippy::default_numeric_fallback,
8 clippy::else_if_without_else,
9 clippy::error_impl_error,
10 clippy::field_scoped_visibility_modifiers,
11 clippy::impl_trait_in_params,
12 clippy::implicit_return,
13 clippy::mem_forget,
14 clippy::min_ident_chars,
15 clippy::missing_docs_in_private_items,
16 clippy::missing_errors_doc,
17 clippy::missing_inline_in_public_items,
18 clippy::missing_trait_methods,
19 clippy::module_name_repetitions,
20 clippy::multiple_unsafe_ops_per_block,
21 clippy::needless_pass_by_value,
22 clippy::pattern_type_mismatch,
23 clippy::print_stderr,
24 clippy::pub_use,
25 clippy::pub_with_shorthand,
26 clippy::question_mark_used,
27 clippy::self_named_module_files,
28 clippy::separated_literal_suffix,
29 clippy::shadow_reuse,
30 clippy::shadow_unrelated,
31 clippy::single_call_fn,
32 clippy::too_many_lines,
33 clippy::unreachable,
34 clippy::unused_trait_names
35)]
36#![cfg_attr(
37 test,
38 allow(
39 clippy::min_ident_chars,
40 clippy::non_ascii_literal,
41 clippy::print_stdout,
42 clippy::undocumented_unsafe_blocks,
43 clippy::unwrap_used,
44 )
45)]
46
47pub mod bus;
48pub mod chip;
49pub mod errors;
50pub mod feature;
51pub mod sub_feature;
52mod utils;
53pub mod value;
54
55#[cfg(test)]
56mod tests;
57
58extern crate alloc;
59
60use core::ffi::CStr;
61use core::ffi::c_short;
62use core::marker::PhantomData;
63use core::ptr;
64use core::sync::atomic;
65use core::sync::atomic::AtomicBool;
66use std::fs::File;
67use std::io;
68use std::path::PathBuf;
69
70use sensors_sys::{
71 SENSORS_BUS_NR_ANY, SENSORS_BUS_TYPE_ANY, libsensors_version, sensors_bus_id,
72 sensors_chip_name, sensors_cleanup, sensors_feature, sensors_init, sensors_subfeature,
73};
74
75use crate::errors::{Error, Listener, Reporter, Result};
76use crate::utils::{API_ACCESS_LOCK, LibCFileStream};
77
78pub use crate::bus::Bus;
79pub use crate::chip::{Chip, ChipRef};
80pub use crate::feature::FeatureRef;
81pub use crate::sub_feature::SubFeatureRef;
82pub use crate::value::Value;
83
84#[derive(Debug, Default)]
86pub struct Initializer {
87 error_listener: Option<Box<dyn Listener>>,
88 config_path: Option<PathBuf>,
89 config_file: Option<File>,
90}
91
92#[derive(Debug)]
94pub struct LMSensors {
95 error_reporter: Reporter,
96}
97
98impl Initializer {
99 #[must_use]
113 pub fn config_path(self, path: impl Into<PathBuf>) -> Self {
114 Self {
115 error_listener: self.error_listener,
116 config_path: Some(path.into()),
117 config_file: None,
118 }
119 }
120
121 #[must_use]
137 pub fn config_file(self, file: File) -> Self {
138 Self {
139 error_listener: self.error_listener,
140 config_path: None,
141 config_file: Some(file),
142 }
143 }
144
145 #[must_use]
179 pub fn error_listener(self, listener: Box<dyn Listener>) -> Self {
180 Self {
181 error_listener: Some(listener),
182 config_path: self.config_path,
183 config_file: self.config_file,
184 }
185 }
186
187 pub fn initialize(self) -> Result<LMSensors> {
198 let config_file_fp = match (self.config_path, self.config_file) {
199 (None, None) => None,
200 (None, Some(config_file)) => LibCFileStream::from_file(config_file).map(Some)?,
201 (Some(config_path), None) => LibCFileStream::from_path(&config_path).map(Some)?,
202 _ => unreachable!(),
203 };
204
205 let error_listener = self
206 .error_listener
207 .map_or_else(ptr::null_mut, |listener| Box::into_raw(Box::new(listener)));
208
209 let result = LMSensors::new(config_file_fp, error_listener);
210
211 if result.is_err() && !error_listener.is_null() {
212 drop(unsafe { Box::from_raw(error_listener) });
214 }
215 result
216 }
217}
218
219static INITIALIZED: AtomicBool = AtomicBool::new(false);
220
221impl LMSensors {
222 #[must_use]
225 pub fn version(&self) -> Option<&str> {
226 self.raw_version().and_then(|version| version.to_str().ok())
227 }
228
229 #[must_use]
231 pub fn raw_version(&self) -> Option<&CStr> {
232 let version = unsafe { libsensors_version };
234 (!version.is_null()).then(|| unsafe { CStr::from_ptr(version) })
237 }
238
239 #[must_use]
247 pub unsafe fn new_chip_ref<'sensors>(
248 &'sensors self,
249 chip: &'sensors sensors_chip_name,
250 ) -> ChipRef<'sensors> {
251 ChipRef(chip)
252 }
253
254 #[must_use]
261 pub unsafe fn new_raw_chip(&'_ self, chip: sensors_chip_name) -> Chip<'_> {
262 Chip {
263 raw: chip,
264 _phantom: &PhantomData,
265 }
266 }
267
268 pub fn new_chip<'sensors>(&'sensors self, name: &str) -> Result<Chip<'sensors>> {
270 Chip::new(name)
271 }
272
273 #[must_use]
275 pub fn new_raw_bus(&self, kind: c_short, number: c_short) -> Bus {
276 Bus(sensors_bus_id {
277 type_: kind,
278 nr: number,
279 })
280 }
281
282 #[must_use]
284 pub fn new_bus(&self, kind: bus::Kind, number: bus::Number) -> Bus {
285 Bus(sensors_bus_id {
286 type_: c_short::from(kind),
287 nr: number.into(),
288 })
289 }
290
291 #[must_use]
293 pub fn default_bus(&self) -> Bus {
294 #[allow(clippy::cast_possible_truncation, clippy::as_conversions)]
295 Bus(sensors_bus_id {
296 type_: SENSORS_BUS_TYPE_ANY as c_short,
297 nr: SENSORS_BUS_NR_ANY as c_short,
298 })
299 }
300
301 #[must_use]
309 pub unsafe fn new_feature_ref<'sensors>(
310 &'sensors self,
311 chip: ChipRef<'sensors>,
312 raw: &'sensors sensors_feature,
313 ) -> FeatureRef<'sensors> {
314 FeatureRef { chip, raw }
315 }
316
317 #[must_use]
326 pub unsafe fn new_sub_feature_ref<'sensors>(
327 &'sensors self,
328 feature: FeatureRef<'sensors>,
329 raw: &'sensors sensors_subfeature,
330 ) -> SubFeatureRef<'sensors> {
331 SubFeatureRef { feature, raw }
332 }
333
334 pub fn chip_iter<'sensors>(
338 &'sensors self,
339 match_pattern: Option<ChipRef<'sensors>>,
340 ) -> crate::chip::Iter<'sensors> {
341 crate::chip::Iter {
342 state: 0,
343 match_pattern,
344 }
345 }
346
347 fn new(
349 config_file_stream: Option<LibCFileStream>,
350 error_listener: *mut Box<dyn Listener>,
351 ) -> Result<Self> {
352 let config_file_fp = config_file_stream
353 .as_ref()
354 .map_or(ptr::null_mut(), LibCFileStream::as_mut_ptr);
355
356 let locked_self = API_ACCESS_LOCK.lock();
357
358 if INITIALIZED.load(atomic::Ordering::Acquire) {
359 drop(locked_self); let err = io::ErrorKind::AlreadyExists.into();
362 return Err(Error::from_io("sensors_init()", err));
363 }
364
365 let error_reporter = Reporter::new(error_listener);
367
368 let r = unsafe { sensors_init(config_file_fp.cast()) };
370 if r == 0 {
371 INITIALIZED.store(true, atomic::Ordering::Release);
372
373 return Ok(Self { error_reporter });
374 }
375
376 error_reporter.restore();
379
380 drop(locked_self); Err(Error::from_lm_sensors("sensors_init()", r))
383 }
384}
385
386impl Drop for LMSensors {
387 fn drop(&mut self) {
389 let error_listener = {
390 let _guard = API_ACCESS_LOCK.lock();
391 unsafe { sensors_cleanup() }
393
394 let error_listener = self.error_reporter.restore();
395
396 INITIALIZED.store(false, atomic::Ordering::Release);
397
398 error_listener
399 };
400
401 if !error_listener.is_null() {
402 drop(unsafe { Box::from_raw(error_listener) });
404 }
405 }
406}