lm_sensors/
chip.rs

1//! Chips controlling sensors and actuators.
2
3#[cfg(test)]
4mod tests;
5
6use alloc::ffi::CString;
7use core::ffi::CStr;
8use core::ffi::{c_int, c_uint};
9use core::marker::PhantomData;
10use core::mem::MaybeUninit;
11use core::{fmt, mem, ptr};
12use std::ffi::OsStr;
13use std::io;
14use std::path::Path;
15
16use sensors_sys::{
17    SENSORS_CHIP_NAME_ADDR_ANY, sensors_chip_name, sensors_do_chip_sets, sensors_free_chip_name,
18    sensors_get_detected_chips, sensors_parse_chip_name, sensors_snprintf_chip_name,
19};
20
21use crate::Bus;
22use crate::errors::{Error, Result};
23use crate::utils::API_ACCESS_LOCK;
24
25/// Chip connected to sensors or actuators.
26#[derive(Debug, PartialEq, Eq)]
27pub struct Chip<'sensors> {
28    pub(crate) raw: sensors_chip_name,
29    pub(crate) _phantom: &'sensors PhantomData<crate::LMSensors>,
30}
31
32impl<'sensors> Chip<'sensors> {
33    /// See: [`sensors_parse_chip_name`].
34    pub(crate) fn new(name: &str) -> Result<Self> {
35        // Though undocumented, sensors_parse_chip_name() assumes its output
36        // parameter to be zero-initialized.
37        let c_name = CString::new(name)?;
38        let mut result = MaybeUninit::zeroed();
39
40        let r = {
41            let _guard = API_ACCESS_LOCK.lock();
42            // Safety: `c_name` and `result` are properly initialized.
43            unsafe { sensors_parse_chip_name(c_name.as_ptr(), result.as_mut_ptr()) }
44        };
45
46        if r == 0 {
47            Ok(Self {
48                // Safety: sensors_parse_chip_name() initialized `result`.
49                raw: unsafe { result.assume_init() },
50                _phantom: &PhantomData,
51            })
52        } else {
53            Err(Error::from_lm_sensors("sensors_parse_chip_name()", r))
54        }
55    }
56
57    /// # Safety
58    /// It is the responsibility of the caller to call
59    /// [`sensors_free_chip_name`] on the result.
60    /// Failing to do so leaks memory.
61    #[must_use]
62    pub unsafe fn into_raw_parts(self) -> sensors_chip_name {
63        let raw = self.raw;
64        mem::forget(self);
65        raw
66    }
67
68    /// Returns a shared reference to the raw data structure [`sensors_chip_name`].
69    #[must_use]
70    pub fn raw_ref(&self) -> &sensors_chip_name {
71        &self.raw
72    }
73
74    /// Returns an exclusive reference to the raw data structure [`sensors_chip_name`].
75    ///
76    /// # Safety
77    /// Changing the raw data structure in an unsupported way leads to undefined results.
78    #[must_use]
79    pub unsafe fn raw_mut(&mut self) -> &mut sensors_chip_name {
80        &mut self.raw
81    }
82
83    /// Return a shared reference to this chip.
84    #[must_use]
85    pub fn as_ref(&'sensors self) -> ChipRef<'sensors> {
86        ChipRef(&self.raw)
87    }
88
89    /// Set the bus connected to this chip.
90    ///
91    /// See: [`Chip::do_chip_sets`].
92    pub fn set_bus(&mut self, new_bus: &Bus) {
93        self.raw.bus = new_bus.0;
94    }
95
96    /// Return an iterator which yields all sensors and actuators
97    /// (*a.k.a.,* features) controlled by this chip.
98    pub fn feature_iter(&'sensors self) -> crate::feature::Iter<'sensors> {
99        crate::feature::Iter {
100            chip: ChipRef(&self.raw),
101            state: 0,
102        }
103    }
104
105    /// Return name of this chip, if it is valid UTF-8.
106    pub fn name(&self) -> Result<String> {
107        self.as_ref().name()
108    }
109
110    /// Return the prefix of this chip, if it is valid UTF-8.
111    #[must_use]
112    pub fn prefix(&self) -> Option<Result<&str>> {
113        self.as_ref().prefix()
114    }
115
116    /// Return the path of the driver of this chip, if available.
117    #[cfg(unix)]
118    #[must_use]
119    pub fn path(&self) -> Option<&Path> {
120        self.as_ref().path()
121    }
122
123    /// Return the address of this chip, if available.
124    #[must_use]
125    pub fn address(&self) -> Option<c_int> {
126        self.as_ref().address()
127    }
128
129    /// Execute all set statements for this chip.
130    ///
131    /// See: [`sensors_do_chip_sets`].
132    pub fn do_chip_sets(&self) -> Result<()> {
133        self.as_ref().do_chip_sets()
134    }
135
136    /// Return a copy of the bus connected to this chip.
137    #[must_use]
138    pub fn bus(&self) -> Bus {
139        Bus(self.raw.bus)
140    }
141
142    /// Return the raw name of this chip.
143    ///
144    /// See: [`sensors_snprintf_chip_name`].
145    pub fn raw_name(&self) -> Result<CString> {
146        self.as_ref().raw_name()
147    }
148
149    /// Return the raw prefix of this chip, if available.
150    #[must_use]
151    pub fn raw_prefix(&self) -> Option<&CStr> {
152        self.as_ref().raw_prefix()
153    }
154
155    /// Return the raw path of the driver of this chip, if available.
156    #[must_use]
157    pub fn raw_path(&self) -> Option<&CStr> {
158        self.as_ref().raw_path()
159    }
160
161    /// Return the raw address of this chip, which is either a number,
162    /// or [`SENSORS_CHIP_NAME_ADDR_ANY`].
163    #[must_use]
164    pub fn raw_address(&self) -> c_int {
165        self.raw.addr
166    }
167}
168
169impl Drop for Chip<'_> {
170    /// See: [`sensors_free_chip_name`].
171    fn drop(&mut self) {
172        let _guard = API_ACCESS_LOCK.lock();
173        // Safety: sensors_free_chip_name() is assumed to be safe.
174        unsafe { sensors_free_chip_name(&mut self.raw) }
175    }
176}
177
178impl PartialEq<ChipRef<'_>> for Chip<'_> {
179    fn eq(&self, other: &ChipRef<'_>) -> bool {
180        self.as_ref() == *other
181    }
182}
183
184impl PartialEq<Chip<'_>> for ChipRef<'_> {
185    fn eq(&self, other: &Chip<'_>) -> bool {
186        *self == other.as_ref()
187    }
188}
189
190impl fmt::Display for Chip<'_> {
191    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
192        if let Ok(name) = self.raw_name() {
193            write!(f, "{}", name.to_string_lossy())
194        } else {
195            write!(f, "\u{fffd}")
196        }
197    }
198}
199
200/// Shared reference to a chip connected to sensors or actuators.
201#[derive(Debug, Clone, Copy, Eq)]
202pub struct ChipRef<'sensors>(pub(crate) &'sensors sensors_chip_name);
203
204impl<'sensors> ChipRef<'sensors> {
205    /// Returns a shared reference to the raw data structure [`sensors_chip_name`].
206    #[must_use]
207    pub fn raw_ref(self) -> &'sensors sensors_chip_name {
208        self.0
209    }
210
211    /// Return an iterator which yields all sensors and actuators
212    /// (*a.k.a.,* features) controlled by this chip.
213    pub fn feature_iter(self) -> crate::feature::Iter<'sensors> {
214        crate::feature::Iter {
215            chip: self,
216            state: 0,
217        }
218    }
219
220    /// Return name of this chip, if it is valid UTF-8.
221    pub fn name(self) -> Result<String> {
222        self.raw_name()?.into_string().map_err(Into::into)
223    }
224
225    /// Return the prefix of this chip, if it is valid UTF-8.
226    #[must_use]
227    pub fn prefix(self) -> Option<Result<&'sensors str>> {
228        self.raw_prefix()
229            .map(|prefix| prefix.to_str().map_err(Into::into))
230    }
231
232    /// Return the path of the driver of this chip, if available.
233    #[cfg(unix)]
234    #[must_use]
235    pub fn path(self) -> Option<&'sensors Path> {
236        use std::os::unix::ffi::OsStrExt;
237
238        self.raw_path()
239            .map(CStr::to_bytes)
240            .map(OsStr::from_bytes)
241            .map(Path::new)
242    }
243
244    /// Return the address of this chip, if available.
245    #[must_use]
246    pub fn address(self) -> Option<c_int> {
247        let addr = self.raw_address();
248        (addr != SENSORS_CHIP_NAME_ADDR_ANY).then_some(addr)
249    }
250
251    /// Execute all set statements for this chip.
252    ///
253    /// See: [`sensors_do_chip_sets`].
254    pub fn do_chip_sets(self) -> Result<()> {
255        let r = {
256            let _guard = API_ACCESS_LOCK.lock();
257            // Safety: sensors_do_chip_sets() is assumed to be safe.
258            unsafe { sensors_do_chip_sets(self.0) }
259        };
260
261        if r == 0 {
262            Ok(())
263        } else {
264            Err(Error::from_lm_sensors("sensors_do_chip_sets()", r))
265        }
266    }
267
268    /// Return a copy of the bus connected to this chip.
269    #[must_use]
270    pub fn bus(self) -> Bus {
271        Bus(self.0.bus)
272    }
273
274    /// Return the raw name of this chip.
275    ///
276    /// See: [`sensors_snprintf_chip_name`].
277    pub fn raw_name(self) -> Result<CString> {
278        let (r, mut buffer) = {
279            let _guard = API_ACCESS_LOCK.lock();
280
281            // Safety: sensors_snprintf_chip_name(NULL,0,...) is assumed to be safe.
282            let result = unsafe { sensors_snprintf_chip_name(ptr::null_mut(), 0, self.0) };
283            if result < 0 {
284                (result, Vec::default())
285            } else {
286                #[expect(clippy::cast_sign_loss, clippy::as_conversions)]
287                let mut buffer = vec![0_u8; (result as c_uint as usize).saturating_add(1)];
288
289                // Safety: `buffer` was properly initialized.
290                let result = unsafe {
291                    sensors_snprintf_chip_name(buffer.as_mut_ptr().cast(), buffer.len(), self.0)
292                };
293                (result, buffer)
294            }
295        };
296
297        if r < 0 {
298            Err(Error::from_lm_sensors("sensors_snprintf_chip_name()", r))
299        } else {
300            #[expect(clippy::cast_sign_loss, clippy::as_conversions)]
301            let len = r as c_uint as usize;
302
303            if len >= buffer.len() {
304                // The name was truncated.
305                let err = io::ErrorKind::InvalidData.into();
306                Err(Error::from_io("sensors_snprintf_chip_name()", err))
307            } else {
308                buffer.resize_with(len, Default::default);
309                CString::new(buffer).map_err(Into::into)
310            }
311        }
312    }
313
314    /// Return the raw prefix of this chip, if available.
315    #[must_use]
316    pub fn raw_prefix(self) -> Option<&'sensors CStr> {
317        // Safety: if `prefix` is not null, then it is assumed to be a null-terminated string.
318        (!self.0.prefix.is_null()).then(|| unsafe { CStr::from_ptr(self.0.prefix) })
319    }
320
321    /// Return the raw path of the driver of this chip, if available.
322    #[must_use]
323    pub fn raw_path(self) -> Option<&'sensors CStr> {
324        // Safety: if `path` is not null, then it is assumed to be a null-terminated string.
325        (!self.0.path.is_null()).then(|| unsafe { CStr::from_ptr(self.0.path) })
326    }
327
328    /// Return the raw address of this chip, which is either a number,
329    /// or [`SENSORS_CHIP_NAME_ADDR_ANY`].
330    #[must_use]
331    pub fn raw_address(self) -> c_int {
332        self.0.addr
333    }
334}
335
336impl PartialEq<ChipRef<'_>> for ChipRef<'_> {
337    fn eq(&self, other: &ChipRef<'_>) -> bool {
338        self.raw_address() == other.raw_address()
339            && self.bus() == other.bus()
340            && self.raw_prefix() == other.raw_prefix()
341            && self.raw_path() == other.raw_path()
342    }
343}
344
345impl fmt::Display for ChipRef<'_> {
346    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
347        if let Ok(name) = self.raw_name() {
348            write!(f, "{}", name.to_string_lossy())
349        } else {
350            write!(f, "\u{fffd}")
351        }
352    }
353}
354
355/// Iterator over available chips. Yields [`ChipRef`]s.
356#[derive(Debug)]
357#[must_use]
358pub struct Iter<'sensors> {
359    pub(crate) state: c_int,
360    pub(crate) match_pattern: Option<ChipRef<'sensors>>,
361}
362
363impl<'sensors> Iterator for Iter<'sensors> {
364    type Item = ChipRef<'sensors>;
365
366    /// See: [`sensors_get_detected_chips`].
367    fn next(&mut self) -> Option<Self::Item> {
368        let match_pattern = self
369            .match_pattern
370            .map_or_else(ptr::null, |chip| chip.raw_ref());
371
372        let name = {
373            let _guard = API_ACCESS_LOCK.lock();
374            // Safety: `match_pattern` is null or initialized, and `state` is initialized.
375            unsafe { sensors_get_detected_chips(match_pattern, &mut self.state).as_ref() }
376        };
377
378        name.map(ChipRef)
379    }
380}