dbus_udisks2/
smart.rs

1//! Types related to the S.M.A.R.T. data of drives.
2
3use crate::utils::*;
4use num_enum::TryFromPrimitive;
5use std::convert::TryFrom;
6use std::fmt;
7use std::str::FromStr;
8use std::time::Duration;
9
10pub(crate) const DEST: &str = "org.freedesktop.UDisks2.Drive.Ata";
11pub(crate) const UPDATE: &str = "SmartUpdate";
12pub(crate) const GET_ATTRS: &str = "SmartGetAttributes";
13pub(crate) const ENABLED: &str = "SmartEnabled";
14pub(crate) const SUPPORTED: &str = "SmartSupported";
15pub(crate) const UPDATED: &str = "SmartUpdated";
16pub(crate) const FAILING: &str = "SmartFailing";
17pub(crate) const TIME_POWER_ON: &str = "SmartPowerOnSeconds";
18pub(crate) const TEMPERATURE: &str = "SmartTemperature";
19pub(crate) const FAILING_ATTRS_COUNT: &str = "SmartNumAttributesFailing";
20pub(crate) const PAST_FAILING_ATTRS_COUNT: &str = "SmartNumAttributesFailedInThePast";
21pub(crate) const BAD_SECTORS: &str = "SmartNumBadSectors";
22pub(crate) const STATUS: &str = "SmartSelftestStatus";
23pub(crate) type RawSmartAttribute = (u8, String, u16, i32, i32, i32, i64, i32, KeyVariant);
24
25#[derive(Debug, Eq, PartialEq, Copy, Clone, Hash)]
26#[non_exhaustive]
27/// The status of a S.M.A.R.T. test.
28pub enum SmartStatus {
29    /// Last self-test was a success (or never ran).
30    Success,
31    /// Last self-test was aborted.
32    Aborted,
33    /// Last self-test was interrupted.
34    Interrupted,
35    /// Last self-test did not complete.
36    Fatal,
37    /// Last self-test failed (Unknown).
38    UnknownError,
39    /// Last self-test failed (Electrical).
40    ElectricalError,
41    /// Last self-test failed (Servo).
42    ServoError,
43    /// Last self-test failed (Read).
44    ReadError,
45    /// Last self-test failed (Damage).
46    HandlingError,
47    /// Self-test is currently in progress.
48    InProgress,
49    /// Unknown status
50    Unknown,
51}
52
53impl FromStr for SmartStatus {
54    type Err = ();
55
56    fn from_str(s: &str) -> Result<Self, Self::Err> {
57        match s {
58            "success" => Ok(SmartStatus::Success),
59            "aborted" => Ok(SmartStatus::Aborted),
60            "interrupted" => Ok(SmartStatus::Interrupted),
61            "fatal" => Ok(SmartStatus::Fatal),
62            "error_unknown" => Ok(SmartStatus::UnknownError),
63            "error_electrical" => Ok(SmartStatus::ElectricalError),
64            "error_servo" => Ok(SmartStatus::ServoError),
65            "error_read" => Ok(SmartStatus::ReadError),
66            "error_handling" => Ok(SmartStatus::HandlingError),
67            "inprogress" => Ok(SmartStatus::InProgress),
68            _ => Err(()),
69        }
70    }
71}
72
73#[derive(Clone, Debug)]
74/// Whether a drive supports S.M.A.R.T. or not.
75pub enum SmartValue {
76    /// The drive does not support S.M.A.R.T.
77    NotSupported,
78    /// The drive supports S.M.A.R.T., but it's not enabled.
79    NotEnabled,
80    /// S.M.A.R.T. is supported and enabled, but it's never been read. Call
81    /// [`smart_update`][crate::UDisks2::smart_update]
82    /// ([async version][crate::AsyncUDisks2::smart_update]).
83    NotUpdated,
84    Enabled(SmartData),
85}
86
87#[derive(Clone, Debug)]
88/// The S.M.A.R.T. data of a drive.
89pub struct SmartData {
90    pub attributes: Vec<SmartAttribute>,
91    /// The point in time (seconds since the Unix Epoch) that the SMART status was updated.
92    pub updated: u64,
93    /// Set to `true` if disk is about to fail.
94    ///
95    /// This value is read from the disk itself and does not include any interpretation.
96    pub failing: bool,
97    /// The amount of time the disk has been powered on (according to SMART data) or 0 if unknown.
98    pub time_powered_on: u64,
99    /// The temperature (in Kelvin) of the disk according to SMART data or 0 if unknown.
100    pub temperature: f64,
101    /// The number of attributes failing right now or -1 if unknown.
102    pub failing_attrs_count: i32,
103    /// The number of attributes that have failed in the past or -1 if unknown.
104    pub past_failing_attrs_count: i32,
105    /// The number of bad sectors (ie. pending and reallocated) or -1 if unknown.
106    pub bad_sectors: i64,
107    /// The status of the last self-test.
108    pub status: SmartStatus,
109}
110
111#[derive(Debug, Eq, PartialEq, TryFromPrimitive, Copy, Clone, Hash)]
112#[repr(u8)]
113#[non_exhaustive]
114pub enum PrettyUnit {
115    Dimensionless = 1,
116    Milliseconds,
117    Sectors,
118    Millikelvin,
119}
120
121#[derive(Eq, PartialEq, Copy, Clone, Hash)]
122pub struct PrettyValue {
123    pub value: i64,
124    pub unit: PrettyUnit,
125}
126
127impl fmt::Debug for PrettyValue {
128    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
129        write!(f, "{} / ", self)?;
130        f.debug_struct("PrettyValue")
131            .field("value", &self.value)
132            .field("unit", &self.unit)
133            .finish()
134    }
135}
136
137impl fmt::Display for PrettyValue {
138    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
139        match self.unit {
140            PrettyUnit::Dimensionless => write!(f, "{}", self.value),
141            PrettyUnit::Milliseconds => write!(f, "{:?}", Duration::from_millis(self.value as u64)),
142            PrettyUnit::Sectors => write!(f, "{} sectors", self.value),
143            PrettyUnit::Millikelvin => {
144                write!(f, "{:.1} degrees C", self.value as f32 / 1000. - 273.15)
145            }
146        }
147    }
148}
149
150#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
151pub enum SmartAssessment {
152    Failing,
153    FailedInPast,
154    Ok,
155}
156
157#[derive(Clone, Debug)]
158/// A S.M.A.R.T. attribute.
159pub struct SmartAttribute {
160    /// Attribute Identifier
161    pub id: u8,
162    /// The identifier as a string.
163    pub name: String,
164    /// 16-bit attribute flags (bit 0 is prefail/oldage, bit 1 is online/offline).
165    pub flags: u16,
166    /// The current value or -1 if unknown.
167    pub normalized: i32,
168    /// The worst value of -1 if unknown.
169    pub worst: i32,
170    /// The threshold or -1 if unknown.
171    pub threshold: i32,
172    /// An interpretation of the value
173    pub pretty: Option<PrettyValue>,
174}
175
176impl SmartAttribute {
177    // from https://github.com/smartmontools/smartmontools/blob/ff9fbe7300064cc6ec45a78c162f0166c770c4b0/smartmontools/atacmds.h#L150
178
179    /// Whether this attribute determines if the drive is failing (`true`) or old (`false`).
180    ///
181    /// From SFF 8035i Revision 2 page 19:
182    ///
183    /// Bit 0 (pre-failure/advisory bit) - If the value of this bit equals zero, an attribute value
184    /// less than or equal to its corresponding attribute threshold indicates an advisory condition
185    /// where the usage or age of the device has exceeded its intended design life period. If the
186    /// value of this bit equals one, an attribute value less than or equal to its corresponding
187    /// attribute threshold indicates a prefailure condition where imminent loss of data is being
188    /// predicted.
189    ///
190    /// ---
191    ///
192    /// From [SMART Attribute Overview](http://www.t13.org/Documents/UploadedDocuments/docs2005/e05171r0-ACS-SMARTAttributes_Overview.pdf):
193    ///
194    /// 0: Advisory: The usage of age of the device has exceeded its intended design life period \
195    /// 1: Pre-failure notification: Failure is predicted within 24 hours
196    pub fn pre_fail(&self) -> bool {
197        self.flags & 0x01 > 0
198    }
199    /// From SFF 8035i Revision 2 page 19:
200    ///
201    /// Bit 1 (on-line data collection bit) - If the value of this bit equals zero, then the
202    /// attribute value is updated only during off-line data collection activities. If the value of
203    /// this bit equals one, then the attribute value is updated during normal operation of the
204    /// device or during both normal operation and off-line testing.
205    pub fn online(&self) -> bool {
206        self.flags & 0x02 > 0
207    }
208    /// `true`: speed/performance
209    pub fn performance(&self) -> bool {
210        self.flags & 0x04 > 0
211    }
212    pub fn error_rate(&self) -> bool {
213        self.flags & 0x08 > 0
214    }
215    pub fn event_count(&self) -> bool {
216        self.flags & 0x10 > 0
217    }
218    pub fn self_preserving(&self) -> bool {
219        self.flags & 0x20 > 0
220    }
221    /// Assess the disk's state.
222    ///
223    /// From https://github.com/GNOME/gnome-disk-utility/blob/5baa52eff3036fc59648bc2e10c4d4ec69dec50b/src/disks/gduatasmartdialog.c#L678
224    pub fn assessment(&self) -> SmartAssessment {
225        if self.normalized > 0 && self.threshold > 0 && self.normalized <= self.threshold {
226            SmartAssessment::Failing
227        } else if self.worst > 0 && self.threshold > 0 && self.worst <= self.threshold {
228            SmartAssessment::FailedInPast
229        } else {
230            SmartAssessment::Ok
231        }
232    }
233}
234
235impl From<RawSmartAttribute> for SmartAttribute {
236    fn from(
237        (id, name, flags, value, worst, threshold, pretty_value, pretty_unit, _expansion): RawSmartAttribute,
238    ) -> Self {
239        let pretty = PrettyUnit::try_from(pretty_unit as u8)
240            .map(|unit| PrettyValue {
241                value: pretty_value,
242                unit,
243            })
244            .ok();
245        SmartAttribute {
246            id,
247            name,
248            flags,
249            normalized: value,
250            worst,
251            threshold,
252            pretty,
253        }
254    }
255}