├── .gitignore ├── README.md ├── Cargo.toml ├── examples ├── example.rs └── async_example.rs ├── LICENSE └── src ├── disks.rs ├── utils.rs ├── drive.rs ├── nonblock.rs ├── lib.rs ├── smart.rs └── block.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # dbus-udisks2 2 | 3 | A Rust API for interacting the UDisks2 DBus API. Currently provides access to information on blocks and drives, as well 4 | as [S.M.A.R.T. data][SMART]. 5 | 6 | [SMART]: https://en.wikipedia.org/wiki/S.M.A.R.T. 7 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dbus-udisks2" 3 | version = "0.3.0" 4 | edition = "2018" 5 | description = "UDisks2 DBus API" 6 | repository = "https://github.com/pop-os/dbus-udisks2" 7 | authors = ["Michael Aaron Murphy "] 8 | license = "MIT" 9 | readme = "README.md" 10 | 11 | [dependencies] 12 | dbus = "0.9.0" 13 | num_enum = "0.5.1" 14 | futures-util = { version = "0.3", features = ["async-await"], optional = true } 15 | 16 | [dev-dependencies] 17 | tokio = { version = "0.2.4", features = ["macros"] } 18 | dbus-tokio = "0.6.0" 19 | 20 | [features] 21 | futures = ["dbus/futures", "futures-util"] 22 | 23 | [package.metadata.docs.rs] 24 | all-features = true 25 | -------------------------------------------------------------------------------- /examples/example.rs: -------------------------------------------------------------------------------- 1 | use dbus_udisks2::{Disks, UDisks2}; 2 | use std::env::args; 3 | 4 | fn main() { 5 | match args().nth(1) { 6 | Some(ref device) => print_block(device), 7 | None => print(), 8 | } 9 | } 10 | 11 | fn print() { 12 | let udisks2 = UDisks2::new().unwrap(); 13 | let disks = Disks::new(&udisks2); 14 | for device in disks.devices { 15 | println!("{:#?}", device); 16 | if let Ok(smart_data) = udisks2.smart_attributes(&device.drive, true) { 17 | println!("{:#?}", smart_data); 18 | } 19 | } 20 | } 21 | 22 | fn print_block(block_name: &str) { 23 | let udisks2 = UDisks2::new().unwrap(); 24 | for block in udisks2.get_blocks() { 25 | if block.device.to_str().unwrap() == block_name { 26 | println!("{:#?}", block); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 System76 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /examples/async_example.rs: -------------------------------------------------------------------------------- 1 | use dbus::nonblock::NonblockReply; 2 | use dbus_udisks2::{AsyncUDisks2, Disks}; 3 | use std::env::args; 4 | use std::ops::Deref; 5 | 6 | #[tokio::main] 7 | async fn main() { 8 | // Connect to the D-Bus session bus (this is blocking, unfortunately). 9 | let (resource, conn) = dbus_tokio::connection::new_system_sync().unwrap(); 10 | 11 | // The resource is a task that should be spawned onto a tokio compatible 12 | // reactor ASAP. If the resource ever finishes, you lost connection to D-Bus. 13 | tokio::spawn(async { 14 | let err = resource.await; 15 | panic!("Lost connection to D-Bus: {}", err); 16 | }); 17 | 18 | match args().nth(1) { 19 | Some(ref device) => print_block(conn, device).await, 20 | None => print(conn).await, 21 | } 22 | } 23 | 24 | async fn print>(conn: C) { 25 | let udisks2 = AsyncUDisks2::new(conn).await.unwrap(); 26 | let disks = Disks::new_async(&udisks2); 27 | for device in disks.devices { 28 | println!("{:#?}", device); 29 | if let Ok(smart_data) = udisks2.smart_attributes(&device.drive, false).await { 30 | println!("{:#?}", smart_data); 31 | } 32 | } 33 | } 34 | 35 | async fn print_block>(conn: C, block_name: &str) { 36 | let udisks2 = AsyncUDisks2::new(conn).await.unwrap(); 37 | for block in udisks2.get_blocks() { 38 | if block.device.to_str().unwrap() == block_name { 39 | println!("{:#?}", block); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/disks.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | /// All of UDisks2's disk information collected into a convenient wrapper. 4 | #[derive(Debug, Default, Clone)] 5 | pub struct Disks { 6 | pub devices: Vec, 7 | } 8 | 9 | /// A collection of UDisks2 drives and their associated blocks. 10 | /// 11 | /// # Implementation Details 12 | /// - Block partitions are sorted by their physical offsets. 13 | #[derive(Debug, Default, Clone)] 14 | pub struct DiskDevice { 15 | pub drive: Drive, 16 | pub parent: Block, 17 | pub partitions: Vec, 18 | } 19 | 20 | impl Disks { 21 | fn new_cache(udisks2: &DiskCache) -> Self { 22 | let mut devices = Vec::new(); 23 | 24 | let mut blocks = Vec::new(); 25 | for block in udisks2.get_blocks() { 26 | blocks.push(block); 27 | } 28 | 29 | for drive in udisks2.get_drives() { 30 | let mut partitions = Vec::new(); 31 | let mut parent = None; 32 | 33 | for block in blocks.iter().filter(|b| b.drive == drive.path) { 34 | if block.table.is_some() { 35 | parent = Some(block.to_owned()); 36 | } else { 37 | partitions.push(block.to_owned()); 38 | } 39 | } 40 | 41 | if let Some(parent) = parent { 42 | partitions.sort_unstable_by_key(|p| p.partition.as_ref().unwrap().offset); 43 | devices.push(DiskDevice { 44 | drive, 45 | parent, 46 | partitions, 47 | }); 48 | } else if partitions.len() == 1 { 49 | // An unpartitioned drive should have only one Block 50 | devices.push(DiskDevice { 51 | drive, 52 | parent: partitions.into_iter().next().unwrap(), 53 | partitions: vec![], 54 | }); 55 | } 56 | } 57 | 58 | Disks { devices } 59 | } 60 | pub fn new(udisks2: &UDisks2) -> Self { 61 | Disks::new_cache(&udisks2.cache) 62 | } 63 | #[cfg(feature = "futures")] 64 | pub fn new_async(udisks2: &crate::AsyncUDisks2) -> Self { 65 | Disks::new_cache(&udisks2.cache) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | use dbus::arg::{RefArg, Variant}; 2 | use std::collections::HashMap; 3 | 4 | pub type KeyVariant = HashMap>>; 5 | pub type DbusObjects = HashMap; 6 | 7 | pub fn get_string(arg: &Variant>) -> Option { 8 | arg.0.as_str().and_then(|x| { 9 | if x.is_empty() { 10 | None 11 | } else { 12 | Some(x.to_owned()) 13 | } 14 | }) 15 | } 16 | 17 | pub fn get_u64(arg: &Variant>) -> u64 { 18 | arg.0.as_u64().unwrap_or(0) 19 | } 20 | 21 | pub fn get_bool(arg: &Variant>) -> bool { 22 | arg.0.as_u64().unwrap_or(0) != 0 23 | } 24 | 25 | pub fn get_string_array(arg: &Variant>) -> Option> { 26 | arg.0.as_iter().and_then(|items| { 27 | let vector = items 28 | .flat_map(|item| item.as_str().map(String::from)) 29 | .collect::>(); 30 | if vector.is_empty() { 31 | None 32 | } else { 33 | Some(vector) 34 | } 35 | }) 36 | } 37 | 38 | pub fn get_byte_array(arg: &Variant>) -> Option { 39 | atostr(arg.0.as_iter()) 40 | } 41 | 42 | pub fn atostr<'a>(array: Option + 'a>>) -> Option { 43 | array.and_then(|bytes| { 44 | let mut inner_vec = bytes 45 | .flat_map(|byte| byte.as_u64().map(|x| x as u8)) 46 | .collect::>(); 47 | 48 | if inner_vec.last() == Some(&0) { 49 | inner_vec.pop(); 50 | } 51 | 52 | String::from_utf8(inner_vec).ok() 53 | }) 54 | } 55 | 56 | pub fn vva(value: &dyn RefArg) -> Option { 57 | let viter = value.as_iter().and_then(|mut i| { 58 | i.next().and_then(|i| { 59 | i.as_iter() 60 | .and_then(|mut i| i.next().and_then(|i| i.as_iter())) 61 | }) 62 | }); 63 | 64 | atostr(viter) 65 | } 66 | 67 | pub fn get_array_of_byte_arrays(arg: &Variant>) -> Option> { 68 | arg.0.as_iter().and_then(|items| { 69 | let vector = items 70 | .flat_map(|item| atostr(item.as_iter())) 71 | .collect::>(); 72 | if vector.is_empty() { 73 | None 74 | } else { 75 | Some(vector) 76 | } 77 | }) 78 | } 79 | 80 | pub(crate) trait ParseFrom { 81 | fn parse_from(path: &str, objects: &DbusObjects) -> Option 82 | where 83 | Self: Sized; 84 | } 85 | -------------------------------------------------------------------------------- /src/drive.rs: -------------------------------------------------------------------------------- 1 | use crate::utils::*; 2 | use crate::DbusObjects; 3 | 4 | #[derive(Clone, Debug, Default)] 5 | pub struct Drive { 6 | pub can_power_off: bool, 7 | pub connection_bus: String, 8 | pub ejectable: bool, 9 | pub id: String, 10 | pub media_available: bool, 11 | pub media_change_detected: bool, 12 | pub media_compatibility: Vec, 13 | pub media_removable: bool, 14 | pub media: Option, 15 | pub model: String, 16 | pub optical: bool, 17 | pub optical_blank: bool, 18 | pub optical_num_tracks: u64, 19 | pub optical_num_audio_tracks: u64, 20 | pub optical_num_data_tracks: u64, 21 | pub optical_num_sessions: u64, 22 | pub path: String, 23 | pub removable: bool, 24 | pub revision: String, 25 | pub rotation_rate: i64, 26 | pub seat: String, 27 | pub serial: String, 28 | pub sibling_id: String, 29 | pub size: u64, 30 | pub sort_key: String, 31 | pub time_detected: u64, 32 | pub time_media_detected: u64, 33 | pub vendor: String, 34 | pub wwn: String, 35 | } 36 | 37 | impl ParseFrom for Drive { 38 | fn parse_from(path: &str, objects: &DbusObjects) -> Option { 39 | if let Some(object) = objects.get("org.freedesktop.UDisks2.Drive") { 40 | let mut drive = Drive::default(); 41 | drive.path = path.to_owned(); 42 | drive.parse(object); 43 | 44 | Some(drive) 45 | } else { 46 | None 47 | } 48 | } 49 | } 50 | 51 | impl Drive { 52 | fn parse(&mut self, objects: &KeyVariant) { 53 | for (key, ref value) in objects { 54 | match key.as_str() { 55 | "CanPowerOff" => self.can_power_off = get_bool(value), 56 | "ConnectionBus" => self.connection_bus = get_string(value).unwrap_or_default(), 57 | "Ejectable" => self.ejectable = get_bool(value), 58 | "Id" => self.id = get_string(value).unwrap_or_default(), 59 | "Media" => self.media = get_string(value), 60 | "MediaAvailable" => self.media_available = get_bool(value), 61 | "MediaChangeDetected" => self.media_change_detected = get_bool(value), 62 | "MediaCompatibility" => { 63 | self.media_compatibility = get_string_array(value).unwrap_or_default() 64 | } 65 | "MediaRemovable" => self.media_removable = get_bool(value), 66 | "Model" => self.model = get_string(value).unwrap_or_default(), 67 | "Optical" => self.optical = get_bool(value), 68 | "OpticalBlank" => self.optical_blank = get_bool(value), 69 | "OpticalNumTracks" => self.optical_num_tracks = get_u64(value), 70 | "OpticalNumAudioTracks" => self.optical_num_audio_tracks = get_u64(value), 71 | "OpticalNumDataTracks" => self.optical_num_data_tracks = get_u64(value), 72 | "OpticalNumSessions" => self.optical_num_sessions = get_u64(value), 73 | "Removable" => self.removable = get_bool(value), 74 | "Revision" => self.revision = get_string(value).unwrap_or_default(), 75 | "RotationRate" => self.rotation_rate = get_u64(value) as i64, 76 | "Seat" => self.seat = get_string(value).unwrap_or_default(), 77 | "Serial" => self.serial = get_string(value).unwrap_or_default(), 78 | "SiblingId" => self.sibling_id = get_string(value).unwrap_or_default(), 79 | "Size" => self.size = get_u64(value), 80 | "SortKey" => self.sort_key = get_string(value).unwrap_or_default(), 81 | "TimeDetected" => self.time_detected = get_u64(value), 82 | "TimeMediaDetected" => self.time_media_detected = get_u64(value), 83 | "Vendor" => self.vendor = get_string(value).unwrap_or_default(), 84 | "WWN" => self.wwn = get_string(value).unwrap_or_default(), 85 | _ => { 86 | #[cfg(debug_assertions)] 87 | eprintln!("unhandled org.freedesktop.UDisks2.Drive::{}", key); 88 | } 89 | } 90 | } 91 | } 92 | } 93 | 94 | impl<'a> From<&'a Drive> for dbus::Path<'a> { 95 | fn from(drive: &'a Drive) -> Self { 96 | (&drive.path).into() 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/nonblock.rs: -------------------------------------------------------------------------------- 1 | use crate::smart::{RawSmartAttribute, SmartData, SmartStatus, SmartValue}; 2 | use crate::utils::KeyVariant; 3 | use crate::{smart, Block, DiskCache, Drive, DEST, NO_WAKEUP, PATH}; 4 | use dbus::arg::Variant; 5 | use dbus::nonblock; 6 | use dbus::nonblock::stdintf::org_freedesktop_dbus::{ObjectManager, Properties}; 7 | use dbus::nonblock::NonblockReply; 8 | use futures_util::join; 9 | use std::ops::Deref; 10 | use std::time::Duration; 11 | 12 | /// Async version of [`UDisks2`][crate::UDisks2]. 13 | /// 14 | /// This requires enabling the `futures` feature flag: 15 | /// ```toml 16 | /// [dependencies] 17 | /// dbus-udisks2 = { version = "0.3", features = ["futures"] } 18 | /// ``` 19 | pub struct AsyncUDisks2 { 20 | conn: C, 21 | pub(crate) cache: DiskCache, 22 | } 23 | 24 | impl<'b, C, T> AsyncUDisks2 25 | where 26 | T: NonblockReply + 'b, 27 | C: Deref, 28 | { 29 | /// ``` 30 | /// # tokio::runtime::Runtime::new().unwrap().block_on(async { 31 | /// // Connect to the D-Bus session bus (this is blocking, unfortunately). 32 | /// let (resource, conn) = dbus_tokio::connection::new_system_sync().unwrap(); 33 | /// 34 | /// // The resource is a task that should be spawned onto a tokio compatible 35 | /// // reactor ASAP. If the resource ever finishes, you lost connection to D-Bus. 36 | /// tokio::spawn(async { 37 | /// let err = resource.await; 38 | /// panic!("Lost connection to D-Bus: {}", err); 39 | /// }); 40 | /// 41 | /// let udisks2 = dbus_udisks2::AsyncUDisks2::new(conn).await.unwrap(); 42 | /// # }); 43 | /// ``` 44 | pub async fn new(conn: C) -> Result { 45 | let mut udisks2 = Self { 46 | conn, 47 | cache: Default::default(), 48 | }; 49 | 50 | udisks2.update().await?; 51 | Ok(udisks2) 52 | } 53 | 54 | fn proxy<'a>(&'a self, path: impl Into>) -> nonblock::Proxy<&T> { 55 | nonblock::Proxy::new(DEST, path, Duration::from_millis(3000), &self.conn) 56 | } 57 | 58 | /// Refresh the managed objects fetched from the DBus server. 59 | pub async fn update(&mut self) -> Result<(), dbus::Error> { 60 | self.cache.0 = self.proxy(PATH).get_managed_objects().await?; 61 | Ok(()) 62 | } 63 | 64 | /// Find the drive that corresponds to the given dbus object path. 65 | pub fn get_drive(&self, path: &str) -> Option { 66 | self.cache.get_drive(path) 67 | } 68 | 69 | /// An iterator of `Drive` objects fetched from the inner cached managed objects. 70 | pub fn get_drives<'a>(&'a self) -> impl Iterator + 'a { 71 | self.cache.get_drives() 72 | } 73 | 74 | /// Find the block that corresponds to the given dbus object path. 75 | pub fn get_block(&self, path: &str) -> Option { 76 | self.cache.get_block(path) 77 | } 78 | 79 | /// An iterator of `Block` objects fetched from the inner cached managed objects. 80 | pub fn get_blocks<'a>(&'a self) -> impl Iterator + 'a { 81 | self.cache.get_blocks() 82 | } 83 | 84 | /// Update the S.M.A.R.T. attributes of a drive. You may pass either a `&`[`Drive`] or `&str` 85 | /// which is a path to a drive, starting with `/org/freedesktop/UDisks2/drives/`. 86 | pub async fn smart_update( 87 | &'b self, 88 | drive: impl Into>, 89 | allow_wakeup: bool, 90 | ) -> Result<(), dbus::Error> { 91 | let proxy = self.proxy(drive); 92 | let mut options = KeyVariant::<&str>::new(); 93 | if !allow_wakeup { 94 | options.insert(NO_WAKEUP, Variant(Box::new(true))); 95 | } 96 | proxy 97 | .method_call(smart::DEST, smart::UPDATE, (options,)) 98 | .await 99 | } 100 | 101 | /// Get the S.M.A.R.T. attributes of a drive. You may pass either a `&`[`Drive`] or `&str` which 102 | /// is a path to a drive, starting with `/org/freedesktop/UDisks2/drives/`. 103 | pub async fn smart_attributes( 104 | &'b self, 105 | drive: impl Into>, 106 | allow_wakeup: bool, 107 | ) -> Result { 108 | let proxy = self.proxy(drive); 109 | if !proxy.get::(smart::DEST, smart::SUPPORTED).await? { 110 | return Ok(SmartValue::NotSupported); 111 | } 112 | if !proxy.get::(smart::DEST, smart::ENABLED).await? { 113 | return Ok(SmartValue::NotEnabled); 114 | } 115 | let updated: u64 = proxy.get(smart::DEST, smart::UPDATED).await?; 116 | if updated == 0 { 117 | return Ok(SmartValue::NotUpdated); 118 | } 119 | let mut options = KeyVariant::<&str>::new(); 120 | if !allow_wakeup { 121 | options.insert(NO_WAKEUP, Variant(Box::new(true))); 122 | } 123 | let (attrs,): (Vec,) = proxy 124 | .method_call(smart::DEST, smart::GET_ATTRS, (options,)) 125 | .await?; 126 | let ( 127 | failing, 128 | time_powered_on, 129 | temperature, 130 | failing_attrs_count, 131 | past_failing_attrs_count, 132 | bad_sectors, 133 | status, 134 | ) = join!( 135 | proxy.get(smart::DEST, smart::FAILING), 136 | proxy.get(smart::DEST, smart::TIME_POWER_ON), 137 | proxy.get(smart::DEST, smart::TEMPERATURE), 138 | proxy.get(smart::DEST, smart::FAILING_ATTRS_COUNT), 139 | proxy.get(smart::DEST, smart::PAST_FAILING_ATTRS_COUNT), 140 | proxy.get(smart::DEST, smart::BAD_SECTORS), 141 | proxy.get::(smart::DEST, smart::STATUS), 142 | ); 143 | Ok(SmartValue::Enabled(SmartData { 144 | updated, 145 | failing: failing?, 146 | time_powered_on: time_powered_on?, 147 | temperature: temperature?, 148 | failing_attrs_count: failing_attrs_count?, 149 | past_failing_attrs_count: past_failing_attrs_count?, 150 | bad_sectors: bad_sectors?, 151 | status: status?.parse().unwrap_or(SmartStatus::Unknown), 152 | attributes: attrs.into_iter().map(Into::into).collect(), 153 | })) 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! You probably want to look at [`UDisks2`] or [`AsyncUDisks2`]. 2 | 3 | use std::collections::HashMap; 4 | use std::ops::Deref; 5 | use std::time::Duration; 6 | 7 | use dbus::arg::Variant; 8 | use dbus::blocking; 9 | use dbus::blocking::stdintf::org_freedesktop_dbus::{ObjectManager, Properties}; 10 | 11 | use crate::smart::{RawSmartAttribute, SmartData, SmartStatus, SmartValue}; 12 | pub use block::*; 13 | pub use disks::*; 14 | pub use drive::*; 15 | #[cfg(feature = "futures")] 16 | pub use nonblock::*; 17 | use utils::*; 18 | 19 | mod block; 20 | mod disks; 21 | mod drive; 22 | #[cfg(feature = "futures")] 23 | mod nonblock; 24 | pub mod smart; 25 | mod utils; 26 | 27 | const DEST: &str = "org.freedesktop.UDisks2"; 28 | const PATH: &str = "/org/freedesktop/UDisks2"; 29 | const NO_WAKEUP: &str = "nowakeup"; 30 | 31 | #[derive(Default)] 32 | struct DiskCache(HashMap, DbusObjects>); 33 | 34 | impl DiskCache { 35 | fn get_object(&self, path: &str) -> Option { 36 | self.0 37 | .iter() 38 | .flat_map(|object| { 39 | if object.0.deref() == path { 40 | T::parse_from(&object.0, &object.1) 41 | } else { 42 | None 43 | } 44 | }) 45 | .next() 46 | } 47 | 48 | /// Find the drive that corresponds to the given dbus object path. 49 | fn get_drive(&self, path: &str) -> Option { 50 | self.get_object::(path) 51 | } 52 | 53 | /// An iterator of `Drive` objects fetched from the inner cached managed objects. 54 | fn get_drives<'a>(&'a self) -> impl Iterator + 'a { 55 | self.0 56 | .iter() 57 | .flat_map(|object| Drive::parse_from(&object.0, &object.1)) 58 | } 59 | 60 | /// Find the block that corresponds to the given dbus object path. 61 | fn get_block(&self, path: &str) -> Option { 62 | self.get_object::(path) 63 | } 64 | 65 | /// An iterator of `Block` objects fetched from the inner cached managed objects. 66 | fn get_blocks<'a>(&'a self) -> impl Iterator + 'a { 67 | self.0 68 | .iter() 69 | .flat_map(|object| Block::parse_from(&object.0, &object.1)) 70 | } 71 | } 72 | 73 | pub struct UDisks2 { 74 | conn: blocking::Connection, 75 | cache: DiskCache, 76 | } 77 | 78 | impl UDisks2 { 79 | pub fn new() -> Result { 80 | let mut udisks2 = Self { 81 | conn: blocking::Connection::new_system()?, 82 | cache: Default::default(), 83 | }; 84 | 85 | udisks2.update()?; 86 | Ok(udisks2) 87 | } 88 | 89 | fn proxy<'a>( 90 | &'a self, 91 | path: impl Into>, 92 | ) -> blocking::Proxy<&blocking::Connection> { 93 | blocking::Proxy::new(DEST, path, Duration::from_millis(3000), &self.conn) 94 | } 95 | 96 | /// Refresh the managed objects fetched from the DBus server. 97 | pub fn update(&mut self) -> Result<(), dbus::Error> { 98 | self.cache.0 = self.proxy(PATH).get_managed_objects()?; 99 | Ok(()) 100 | } 101 | 102 | /// Find the drive that corresponds to the given dbus object path. 103 | pub fn get_drive(&self, path: &str) -> Option { 104 | self.cache.get_drive(path) 105 | } 106 | 107 | /// An iterator of `Drive` objects fetched from the inner cached managed objects. 108 | pub fn get_drives<'a>(&'a self) -> impl Iterator + 'a { 109 | self.cache.get_drives() 110 | } 111 | 112 | /// Find the block that corresponds to the given dbus object path. 113 | pub fn get_block(&self, path: &str) -> Option { 114 | self.cache.get_block(path) 115 | } 116 | 117 | /// An iterator of `Block` objects fetched from the inner cached managed objects. 118 | pub fn get_blocks<'a>(&'a self) -> impl Iterator + 'a { 119 | self.cache.get_blocks() 120 | } 121 | 122 | /// Update the S.M.A.R.T. attributes of a drive. You may pass either a `&`[`Drive`] or `&str` 123 | /// which is a path to a drive, starting with `/org/freedesktop/UDisks2/drives/`. 124 | pub fn smart_update<'a>( 125 | &'a self, 126 | drive: impl Into>, 127 | allow_wakeup: bool, 128 | ) -> Result<(), dbus::Error> { 129 | let proxy = self.proxy(drive); 130 | let mut options = KeyVariant::<&str>::new(); 131 | if !allow_wakeup { 132 | options.insert(NO_WAKEUP, Variant(Box::new(true))); 133 | } 134 | proxy.method_call(smart::DEST, smart::UPDATE, (options,)) 135 | } 136 | 137 | /// Get the S.M.A.R.T. attributes of a drive. You may pass either a `&`[`Drive`] or `&str` which 138 | /// is a path to a drive, starting with `/org/freedesktop/UDisks2/drives/`. 139 | pub fn smart_attributes<'a>( 140 | &'a self, 141 | drive: impl Into>, 142 | allow_wakeup: bool, 143 | ) -> Result { 144 | let proxy = self.proxy(drive); 145 | if !proxy.get::(smart::DEST, smart::SUPPORTED)? { 146 | return Ok(SmartValue::NotSupported); 147 | } 148 | if !proxy.get::(smart::DEST, smart::ENABLED)? { 149 | return Ok(SmartValue::NotEnabled); 150 | } 151 | let updated: u64 = proxy.get(smart::DEST, smart::UPDATED)?; 152 | if updated == 0 { 153 | return Ok(SmartValue::NotUpdated); 154 | } 155 | let mut options = KeyVariant::<&str>::new(); 156 | if !allow_wakeup { 157 | options.insert(NO_WAKEUP, Variant(Box::new(true))); 158 | } 159 | let (attrs,): (Vec,) = 160 | proxy.method_call(smart::DEST, smart::GET_ATTRS, (options,))?; 161 | Ok(SmartValue::Enabled(SmartData { 162 | updated, 163 | failing: proxy.get(smart::DEST, smart::FAILING)?, 164 | time_powered_on: proxy.get(smart::DEST, smart::TIME_POWER_ON)?, 165 | temperature: proxy.get(smart::DEST, smart::TEMPERATURE)?, 166 | failing_attrs_count: proxy.get(smart::DEST, smart::FAILING_ATTRS_COUNT)?, 167 | past_failing_attrs_count: proxy.get(smart::DEST, smart::PAST_FAILING_ATTRS_COUNT)?, 168 | bad_sectors: proxy.get(smart::DEST, smart::BAD_SECTORS)?, 169 | status: proxy 170 | .get::(smart::DEST, smart::STATUS)? 171 | .parse() 172 | .unwrap_or(SmartStatus::Unknown), 173 | attributes: attrs.into_iter().map(Into::into).collect(), 174 | })) 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /src/smart.rs: -------------------------------------------------------------------------------- 1 | //! Types related to the S.M.A.R.T. data of drives. 2 | 3 | use crate::utils::*; 4 | use num_enum::TryFromPrimitive; 5 | use std::convert::TryFrom; 6 | use std::fmt; 7 | use std::str::FromStr; 8 | use std::time::Duration; 9 | 10 | pub(crate) const DEST: &str = "org.freedesktop.UDisks2.Drive.Ata"; 11 | pub(crate) const UPDATE: &str = "SmartUpdate"; 12 | pub(crate) const GET_ATTRS: &str = "SmartGetAttributes"; 13 | pub(crate) const ENABLED: &str = "SmartEnabled"; 14 | pub(crate) const SUPPORTED: &str = "SmartSupported"; 15 | pub(crate) const UPDATED: &str = "SmartUpdated"; 16 | pub(crate) const FAILING: &str = "SmartFailing"; 17 | pub(crate) const TIME_POWER_ON: &str = "SmartPowerOnSeconds"; 18 | pub(crate) const TEMPERATURE: &str = "SmartTemperature"; 19 | pub(crate) const FAILING_ATTRS_COUNT: &str = "SmartNumAttributesFailing"; 20 | pub(crate) const PAST_FAILING_ATTRS_COUNT: &str = "SmartNumAttributesFailedInThePast"; 21 | pub(crate) const BAD_SECTORS: &str = "SmartNumBadSectors"; 22 | pub(crate) const STATUS: &str = "SmartSelftestStatus"; 23 | pub(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. 28 | pub 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 | 53 | impl FromStr for SmartStatus { 54 | type Err = (); 55 | 56 | fn from_str(s: &str) -> Result { 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. 75 | pub 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. 89 | pub struct SmartData { 90 | pub attributes: Vec, 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] 114 | pub enum PrettyUnit { 115 | Dimensionless = 1, 116 | Milliseconds, 117 | Sectors, 118 | Millikelvin, 119 | } 120 | 121 | #[derive(Eq, PartialEq, Copy, Clone, Hash)] 122 | pub struct PrettyValue { 123 | pub value: i64, 124 | pub unit: PrettyUnit, 125 | } 126 | 127 | impl 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 | 137 | impl 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)] 151 | pub enum SmartAssessment { 152 | Failing, 153 | FailedInPast, 154 | Ok, 155 | } 156 | 157 | #[derive(Clone, Debug)] 158 | /// A S.M.A.R.T. attribute. 159 | pub 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, 174 | } 175 | 176 | impl 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 | 235 | impl From 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 | } 256 | -------------------------------------------------------------------------------- /src/block.rs: -------------------------------------------------------------------------------- 1 | use crate::utils::*; 2 | use crate::DbusObjects; 3 | use dbus::arg::RefArg; 4 | use std::path::PathBuf; 5 | 6 | #[derive(Clone, Debug, Default)] 7 | pub struct Block { 8 | pub crypto_backing_device: String, 9 | pub device_number: u64, 10 | pub device: PathBuf, 11 | pub drive: String, 12 | pub encrypted: Option, 13 | pub hint_auto: bool, 14 | pub hint_icon_name: Option, 15 | pub hint_ignore: bool, 16 | pub hint_name: Option, 17 | pub hint_partitionable: bool, 18 | pub hint_symbolic_icon_name: Option, 19 | pub hint_system: bool, 20 | pub id_label: Option, 21 | pub id_type: Option, 22 | pub id_usage: Option, 23 | pub id_uuid: Option, 24 | pub id_version: Option, 25 | pub id: String, 26 | pub loopback: bool, 27 | pub mdraid: PathBuf, 28 | pub mdraid_member: PathBuf, 29 | pub mount_points: Vec, 30 | pub partition: Option, 31 | pub path: String, 32 | pub preferred_device: PathBuf, 33 | pub read_only: bool, 34 | pub size: u64, 35 | pub swapspace: Option, 36 | pub symlinks: Vec, 37 | pub table: Option, 38 | pub userspace_mount_options: Vec, 39 | pub configuration: Option, 40 | } 41 | 42 | impl Block { 43 | /// This will be true if this block contains an encrypted volume. 44 | pub fn is_encrypted(&self) -> bool { 45 | self.encrypted.is_some() 46 | } 47 | 48 | /// If this block contains an encrypted volume, find the block associated with it. 49 | pub fn get_encrypted_block<'a>(&self, within: &'a [Block]) -> Option<&'a Block> { 50 | if self.encrypted.is_some() { 51 | within.iter().find(|b| b.crypto_backing_device == self.path) 52 | } else { 53 | None 54 | } 55 | } 56 | } 57 | 58 | impl ParseFrom for Block { 59 | fn parse_from(path: &str, objects: &DbusObjects) -> Option { 60 | if objects.get("org.freedesktop.UDisks2.Loop").is_some() { 61 | return None; 62 | } 63 | 64 | let mut block = Block::default(); 65 | block.path = path.to_owned(); 66 | 67 | match objects.get("org.freedesktop.UDisks2.Block") { 68 | Some(object) => { 69 | for (key, ref value) in object { 70 | match key.as_str() { 71 | "CryptoBackingDevice" => { 72 | block.crypto_backing_device = get_string(value).unwrap() 73 | } 74 | "Device" => block.device = PathBuf::from(get_byte_array(value).unwrap()), 75 | "DeviceNumber" => block.device_number = get_u64(value), 76 | "Drive" => block.drive = get_string(value).unwrap(), 77 | "HintAuto" => block.hint_auto = get_bool(value), 78 | "HintIconName" => block.hint_icon_name = get_string(value), 79 | "HintIgnore" => block.hint_ignore = get_bool(value), 80 | "HintName" => block.hint_name = get_string(value), 81 | "HintPartitionable" => block.hint_partitionable = get_bool(value), 82 | "HintSymbolicIconName" => block.hint_symbolic_icon_name = get_string(value), 83 | "HintSystem" => block.hint_system = get_bool(value), 84 | "Id" => block.id = get_string(value).unwrap_or_default(), 85 | "IdLabel" => block.id_label = get_string(value), 86 | "IdType" => block.id_type = get_string(value), 87 | "IdUsage" => block.id_usage = get_string(value), 88 | "IdUUID" => block.id_uuid = get_string(value), 89 | "IdVersion" => block.id_version = get_string(value), 90 | "MDRaid" => { 91 | block.mdraid = get_string(value).map(PathBuf::from).unwrap_or_default() 92 | } 93 | "MDRaidMember" => { 94 | block.mdraid_member = 95 | get_string(value).map(PathBuf::from).unwrap_or_default() 96 | } 97 | "PreferredDevice" => { 98 | block.preferred_device = PathBuf::from(get_byte_array(value).unwrap()) 99 | } 100 | "ReadOnly" => block.read_only = get_bool(value), 101 | "Size" => block.size = get_u64(value), 102 | "Symlinks" => { 103 | block.symlinks = get_array_of_byte_arrays(value) 104 | .map(|paths| { 105 | paths.into_iter().map(PathBuf::from).collect::>() 106 | }) 107 | .unwrap_or_default() 108 | } 109 | "UserspaceMountOptions" => { 110 | block.userspace_mount_options = 111 | get_string_array(value).unwrap_or_default() 112 | } 113 | "Configuration" => { 114 | let mut configuration = BlockConfiguration::default(); 115 | for value in value.as_iter().unwrap() { 116 | if let Some(mut iterator) = value.as_iter() { 117 | if let Some(mut iterator) = 118 | iterator.next().and_then(|i| i.as_iter()) 119 | { 120 | if let (Some(key), Some(mut array)) = ( 121 | iterator.next(), 122 | iterator.next().and_then(|i| i.as_iter()), 123 | ) { 124 | if let Some(key) = key.as_str() { 125 | if key == "fstab" { 126 | while let (Some(key), Some(value)) = 127 | (array.next(), array.next()) 128 | { 129 | if let Some(key) = key.as_str() { 130 | match key { 131 | "fsname" => { 132 | configuration.fstab.fsname = 133 | vva(value) 134 | .unwrap_or_default() 135 | } 136 | "dir" => { 137 | configuration.fstab.dir = 138 | vva(value) 139 | .unwrap_or_default() 140 | } 141 | "type" => { 142 | configuration.fstab.type_ = 143 | vva(value) 144 | .unwrap_or_default() 145 | } 146 | "opts" => { 147 | configuration.fstab.opts = 148 | vva(value) 149 | .unwrap_or_default() 150 | } 151 | "freq" => { 152 | configuration.fstab.freq = value 153 | .as_u64() 154 | .unwrap_or_default() 155 | as i32 156 | } 157 | "passno" => { 158 | configuration.fstab.passno = 159 | value 160 | .as_u64() 161 | .unwrap_or_default() 162 | as i32 163 | } 164 | _ => { 165 | eprintln!("unhandled block config fstab key: {:?}, {:?}", key, value); 166 | } 167 | } 168 | } 169 | } 170 | } else if key == "crypttab" { 171 | while let (Some(key), Some(value)) = 172 | (array.next(), array.next()) 173 | { 174 | if let Some(key) = key.as_str() { 175 | match key { 176 | "name" => { 177 | configuration.crypttab.name = 178 | vva(value) 179 | .unwrap_or_default() 180 | } 181 | "device" => { 182 | configuration.crypttab.device = 183 | vva(value) 184 | .unwrap_or_default() 185 | } 186 | "passphrase-path" => { 187 | configuration 188 | .crypttab 189 | .passphrase_path = 190 | vva(value) 191 | .unwrap_or_default() 192 | } 193 | "options" => { 194 | configuration.crypttab.options = 195 | vva(value) 196 | .unwrap_or_default() 197 | } 198 | _ => { 199 | eprintln!("unhandled block config crypttab key: {:?}, {:?}", key, value); 200 | } 201 | } 202 | } 203 | } 204 | } else { 205 | eprintln!("unknown block config key: {}", key); 206 | } 207 | } 208 | } 209 | } 210 | } 211 | } 212 | 213 | block.configuration = Some(configuration); 214 | } 215 | _ => { 216 | #[cfg(debug_assertions)] 217 | eprintln!("unhandled org.freedesktop.UDisks2.Block.{}", key); 218 | eprintln!("value: {:#?}", value); 219 | } 220 | } 221 | } 222 | } 223 | None => return None, 224 | } 225 | 226 | for (key, object) in objects { 227 | match key.as_str() { 228 | "org.freedesktop.UDisks2.Block" => (), 229 | "org.freedesktop.UDisks2.Swapspace" => { 230 | block.swapspace = Some(object.get("Active").map_or(false, get_bool)); 231 | } 232 | "org.freedesktop.UDisks2.PartitionTable" => { 233 | let mut table = PartitionTable::default(); 234 | for (key, ref value) in object { 235 | match key.as_str() { 236 | "Type" => table.type_ = get_string(value).unwrap_or_default(), 237 | "Partitions" => { 238 | table.partitions = get_string_array(value).unwrap_or_default(); 239 | table.partitions.sort_unstable(); 240 | } 241 | _ => { 242 | #[cfg(debug_assertions)] 243 | eprintln!( 244 | "unhandled org.freedesktop.UDisks2.PartitionTable.{}", 245 | key 246 | ); 247 | } 248 | } 249 | } 250 | 251 | block.table = Some(table); 252 | } 253 | "org.freedesktop.UDisks2.Partition" => { 254 | let mut partition = Partition::default(); 255 | for (key, value) in object { 256 | match key.as_str() { 257 | "Type" => partition.type_ = get_string(value).unwrap_or_default(), 258 | "Name" => partition.name = get_string(value).unwrap_or_default(), 259 | "UUID" => partition.uuid = get_string(value).unwrap_or_default(), 260 | "Table" => { 261 | partition.table = 262 | get_string(value).expect("partition is not part of a table") 263 | } 264 | "Flags" => partition.flags = get_u64(value), 265 | "Offset" => partition.offset = get_u64(value), 266 | "Size" => partition.size = get_u64(value), 267 | "Number" => partition.number = get_u64(value) as u32, 268 | "IsContained" => partition.is_contained = get_bool(value), 269 | "IsContainer" => partition.is_container = get_bool(value), 270 | _ => { 271 | #[cfg(debug_assertions)] 272 | eprintln!("unhandled org.freedesktop.UDisks2.Partition.{}", key); 273 | } 274 | } 275 | } 276 | 277 | block.partition = Some(partition); 278 | } 279 | "org.freedesktop.UDisks2.Filesystem" => { 280 | block.mount_points = object 281 | .get("MountPoints") 282 | .and_then(get_array_of_byte_arrays) 283 | .map(|paths| paths.into_iter().map(PathBuf::from).collect::>()) 284 | .unwrap_or_default() 285 | } 286 | "org.freedesktop.UDisks2.Encrypted" => { 287 | let mut encrypted = Encrypted::default(); 288 | for (key, ref value) in object { 289 | match key.as_str() { 290 | "HintEncryptionType" => { 291 | encrypted.hint_encryption_type = 292 | get_string(value).unwrap_or_default() 293 | } 294 | "MetadataSize" => encrypted.metadata_size = get_u64(value), 295 | "CleartextDevice" => { 296 | encrypted.cleartext_device = get_string(value).unwrap_or_default() 297 | } 298 | _ => { 299 | #[cfg(debug_assertions)] 300 | eprintln!("unhandled org.freedesktop.UDisks2.Encrypted.{}", key); 301 | } 302 | } 303 | } 304 | 305 | block.encrypted = Some(encrypted); 306 | } 307 | _ => { 308 | #[cfg(debug_assertions)] 309 | eprintln!("unhandled org.freedesktop.UDisks2.{}", key); 310 | } 311 | } 312 | } 313 | 314 | Some(block) 315 | } 316 | } 317 | 318 | #[derive(Clone, Debug, Default)] 319 | pub struct BlockConfiguration { 320 | pub fstab: BlockConfigurationFstab, 321 | pub crypttab: BlockConfigurationCrypttab, 322 | } 323 | 324 | #[derive(Clone, Debug, Default)] 325 | pub struct BlockConfigurationFstab { 326 | pub fsname: String, 327 | pub dir: String, 328 | pub type_: String, 329 | pub opts: String, 330 | pub freq: i32, 331 | pub passno: i32, 332 | } 333 | 334 | #[derive(Clone, Debug, Default)] 335 | pub struct BlockConfigurationCrypttab { 336 | pub name: String, 337 | pub device: String, 338 | pub passphrase_path: String, 339 | pub options: String, 340 | } 341 | 342 | #[derive(Clone, Debug, Default)] 343 | pub struct Encrypted { 344 | pub hint_encryption_type: String, 345 | pub metadata_size: u64, 346 | pub cleartext_device: String, 347 | } 348 | 349 | #[derive(Clone, Debug, Default)] 350 | pub struct PartitionTable { 351 | pub type_: String, 352 | // Partitions are listed by their dbus paths. 353 | pub partitions: Vec, 354 | } 355 | 356 | #[derive(Clone, Debug, Default)] 357 | pub struct Partition { 358 | // Defines the file system by a type UUID. 359 | pub type_: String, 360 | // An optional label that may be applied to a disk. 361 | pub name: String, 362 | // Points to the dbus path that this partition exists within. 363 | pub table: String, 364 | pub flags: u64, 365 | pub number: u32, 366 | pub offset: u64, 367 | pub size: u64, 368 | pub uuid: String, 369 | pub is_container: bool, 370 | pub is_contained: bool, 371 | } 372 | --------------------------------------------------------------------------------