lm_sensors/
lib.rs

1#![doc = include_str!("../README.md")]
2#![warn(unsafe_op_in_unsafe_fn, clippy::all, clippy::pedantic)]
3//#![warn(clippy::restriction)]
4#![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/// LM sensors library initializer, producing an instance of [`LMSensors`].
85#[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/// LM sensors library instance, producing instances of [`Chip`]s, [`Bus`]es, etc.
93#[derive(Debug)]
94pub struct LMSensors {
95    error_reporter: Reporter,
96}
97
98impl Initializer {
99    /**
100    Set the path of the configuration file to be read during LM sensors
101    library initialization.
102
103    # Example
104
105    ```rust
106    let sensors = lm_sensors::Initializer::default()
107        .config_path("/dev/null")
108        .initialize()?;
109    # Ok::<(), lm_sensors::errors::Error>(())
110    ```
111    */
112    #[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    /**
122    Set the configuration contents to be used during LM sensors
123    library initialization.
124
125    # Example
126
127    ```rust
128    # use std::fs::File;
129    let config_file = File::open("/dev/null").unwrap();
130    let sensors = lm_sensors::Initializer::default()
131        .config_file(config_file)
132        .initialize()?;
133    # Ok::<(), lm_sensors::errors::Error>(())
134    ```
135    */
136    #[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    /**
146    Set the error listener to be used during LM sensors library initialization.
147
148    # Example
149
150    ```rust
151    #[derive(Debug)]
152    struct EL;
153
154    impl lm_sensors::errors::Listener for EL {
155        fn on_lm_sensors_config_error(&self, error: &str,
156            file_name: Option<&std::path::Path>, line_number: usize)
157        {
158            if let Some(file_name) = file_name {
159                eprintln!("[ERROR] lm-sensors config: {} @{}:{}",
160                          error, file_name.display(), line_number);
161            } else {
162                eprintln!("[ERROR] lm-sensors config: {} @<config>:{}",
163                          error, line_number);
164            }
165        }
166
167        fn on_lm_sensors_fatal_error(&self, error: &str, procedure: &str) {
168            eprintln!("[FATAL] lm-sensors: {} @{}", error, procedure);
169        }
170    }
171
172    let sensors = lm_sensors::Initializer::default()
173        .error_listener(Box::new(EL))
174        .initialize()?;
175    # Ok::<(), lm_sensors::errors::Error>(())
176    ```
177    */
178    #[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    /**
188    Return an instance of a loaded and initialized LM sensors library.
189
190    # Example
191
192    ```rust
193    let sensors = lm_sensors::Initializer::default().initialize()?;
194    # Ok::<(), lm_sensors::errors::Error>(())
195    ```
196    */
197    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            // Safety: error_listener was allocated locally and is now unused.
213            drop(unsafe { Box::from_raw(error_listener) });
214        }
215        result
216    }
217}
218
219static INITIALIZED: AtomicBool = AtomicBool::new(false);
220
221impl LMSensors {
222    /// Returns the version of the LM sensors library,
223    /// if available and valid UTF-8.
224    #[must_use]
225    pub fn version(&self) -> Option<&str> {
226        self.raw_version().and_then(|version| version.to_str().ok())
227    }
228
229    /// Returns the raw version of the LM sensors library, if available.
230    #[must_use]
231    pub fn raw_version(&self) -> Option<&CStr> {
232        // Safety: `libsensors_version` has already been initialized, and is now constant.
233        let version = unsafe { libsensors_version };
234        // Safety: if `libsensors_version` is not null, then it is assumed to be a null-terminated
235        // string.
236        (!version.is_null()).then(|| unsafe { CStr::from_ptr(version) })
237    }
238
239    /// Return a new instance of [`ChipRef`], given a shared reference
240    /// to a raw chip.
241    ///
242    /// # Safety
243    ///
244    /// - The given [`sensors_chip_name`] reference must have been returned from
245    ///   [`sensors_get_detected_chips`].
246    #[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    /// Return a new instance of [`Chip`], given a raw chip.
255    ///
256    /// # Safety
257    ///
258    /// - The given [`sensors_chip_name`] must have been previously initialized
259    ///   by calling [`sensors_parse_chip_name`].
260    #[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    /// Return a new instance of [`Chip`], given a chip name.
269    pub fn new_chip<'sensors>(&'sensors self, name: &str) -> Result<Chip<'sensors>> {
270        Chip::new(name)
271    }
272
273    /// Return a new instance of [`Bus`], given a raw *(bus type, bus number)*.
274    #[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    /// Return a new instance of [`Bus`], given a *(bus type, bus number)*.
283    #[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    /// Return a new default instance of [`Bus`].
292    #[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    /// Return a new instance of [`FeatureRef`] given a shared reference
302    /// to a raw feature.
303    ///
304    /// # Safety
305    ///
306    /// - The given [`sensors_feature`] reference must have been returned from
307    ///   [`sensors_get_features`].
308    #[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    /// Return a new instance of [`SubFeatureRef`] given a shared reference
318    /// to a raw sub-feature.
319    ///
320    /// # Safety
321    ///
322    /// - The given [`sensors_subfeature`] reference must have been returned
323    ///   either from [`sensors_get_all_subfeatures`] or from
324    ///   [`sensors_get_subfeature`].
325    #[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    /// Return an iterator which yields all chips matching the given pattern.
335    ///
336    /// Specifying `None` for the `match_pattern` yields all chips.
337    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    /// See: [`sensors_init`].
348    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); // Unlock early.
360
361            let err = io::ErrorKind::AlreadyExists.into();
362            return Err(Error::from_io("sensors_init()", err));
363        }
364
365        // We're creating the only instance.
366        let error_reporter = Reporter::new(error_listener);
367
368        // Safety: this is assumed to be safe.
369        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        // sensors_init() failed.
377        // Restore previous global state.
378        error_reporter.restore();
379
380        drop(locked_self); // Unlock early.
381
382        Err(Error::from_lm_sensors("sensors_init()", r))
383    }
384}
385
386impl Drop for LMSensors {
387    /// See: [`sensors_cleanup`].
388    fn drop(&mut self) {
389        let error_listener = {
390            let _guard = API_ACCESS_LOCK.lock();
391            // Safety: this is assumed to be safe.
392            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            // Safety: error_listener was allocated before and is now unused.
403            drop(unsafe { Box::from_raw(error_listener) });
404        }
405    }
406}