├── .gitignore ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE ├── README.md ├── examples └── example.rs ├── rust-toolchain ├── rustfmt.toml └── src └── lib.rs /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | /target/ 3 | **/*.rs.bk 4 | Cargo.lock 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 0.2.6 2 | 3 | - Remove the eprintln statements 4 | 5 | # 0.2.5 6 | 7 | - Fix `PartitionID::get_device_path()` to handle `PartitionSource::Path`s that are already canonicalized. 8 | - Fix change in last version which broke the `PartitionID::get_source()` method. 9 | - Eliminate unnecessary allocations when canonicalizing paths 10 | 11 | # 0.2.4 12 | 13 | - Implement `Display` for `PartitionID` and `PartitionSource`. 14 | 15 | # 0.2.3 16 | 17 | - Derive `Eq` for `PartitionSource` and `PartitionID` 18 | 19 | # 0.2.2 20 | 21 | - Applies a workaround to handle intermittent unavailability of `/dev/disk/by-` path operations. 22 | 23 | # 0.2.1 24 | 25 | - Mark `PartitionIdentifier` fields as public. 26 | - Switch from `failure` error crate to `err-derive` 27 | - Set `rust-toolchain` to `1.24.1` 28 | - Derive `PartialEq` for `Error` 29 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "partition-identity" 3 | version = "0.3.0" 4 | edition = "2021" 5 | repository = "https://github.com/pop-os/partition-identity" 6 | description = "Find the ID of a device by its path, or find a device path by its ID." 7 | license = "MIT" 8 | keywords = ["linux", "partition"] 9 | categories = ["os", "os::unix-apis"] 10 | readme = "README.md" 11 | 12 | [dependencies] 13 | thiserror = "1.0.30" 14 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # partition-identity 2 | 3 | Find the ID of a device by its path, or find a device path by its ID. 4 | 5 | > This crate was developed for inclusion in the [distinst](https://github.com/pop-os/distinst) project. 6 | 7 | ## Example 8 | 9 | ```rust 10 | extern crate partition_identity; 11 | 12 | use partition_identity::{PartitionID, PartitionSource}; 13 | use self::PartitionSource::*; 14 | use std::env; 15 | use std::process::exit; 16 | 17 | fn main() { 18 | let mut args = env::args().skip(1); 19 | match args.next() { 20 | Some(arg) => match arg.as_str() { 21 | "from-path" => { 22 | let mut first = true; 23 | for device in args { 24 | if ! first { println!() } 25 | first = false; 26 | println!("{}:", device); 27 | println!("ID: {:?}", PartitionID::get_source(ID, &device).map(|id| id.id)); 28 | println!("Label: {:?}", PartitionID::get_source(Label, &device).map(|id| id.id)); 29 | println!("PartLabel: {:?}", PartitionID::get_source(PartLabel, &device).map(|id| id.id)); 30 | println!("PartUUID: {:?}", PartitionID::get_source(PartUUID, &device).map(|id| id.id)); 31 | println!("Path: {:?}", PartitionID::get_source(Path, &device).map(|id| id.id)); 32 | println!("UUID: {:?}", PartitionID::get_source(UUID, &device).map(|id| id.id)); 33 | } 34 | } 35 | "by-uuid" => { 36 | for id in args { 37 | let var = PartitionID::new_uuid(id.clone()); 38 | println!("{}: {:?}", id, var.get_device_path()); 39 | } 40 | } 41 | "by-partuuid" => { 42 | for id in args { 43 | let var = PartitionID::new_partuuid(id.clone()); 44 | println!("{}: {:?}", id, var.get_device_path()); 45 | } 46 | } 47 | _ => { 48 | eprintln!("invalid subcommand: valid commansd: [from-path, by-uuid, by-partuuid, ]"); 49 | exit(1); 50 | } 51 | } 52 | None => { 53 | eprintln!("must give subcommand: [from-path, by-uuid, by-partuuid, ]"); 54 | exit(1); 55 | } 56 | } 57 | } 58 | ``` 59 | 60 | ### Example Output 61 | 62 | ``` 63 | $ example by-partuuid d1381cfb-739f-4963-ba02-695df6a42ca7 64 | d1381cfb-739f-4963-ba02-695df6a42ca7: Some("/dev/sda2") 65 | ``` 66 | 67 | ``` 68 | $ example by-uuid ed646eba-b8a3-4c79-8f93-5ee1a25c6ec3 92246269-7259-48e7-ae07-7e0b5027fd2d 69 | ed646eba-b8a3-4c79-8f93-5ee1a25c6ec3: Some("/dev/dm-1") 70 | 92246269-7259-48e7-ae07-7e0b5027fd2d: Some("/dev/sdb3") 71 | ``` 72 | 73 | ``` 74 | $ example from-path /dev/sda{1,2,3,4} /dev/sdb{1,2,3,4} /dev/mapper/{cryptdata,cryptswap,data,data-root} 75 | /dev/sda1: 76 | ID: Some("wwn-0x5002538d403d649a-part1") 77 | Label: None 78 | PartLabel: None 79 | PartUUID: Some("0ff9334f-5649-439a-8138-1aa72e7c6af1") 80 | Path: Some("pci-0000:00:17.0-ata-4-part1") 81 | UUID: None 82 | 83 | /dev/sda2: 84 | ID: Some("wwn-0x5002538d403d649a-part2") 85 | Label: None 86 | PartLabel: None 87 | PartUUID: Some("d1381cfb-739f-4963-ba02-695df6a42ca7") 88 | Path: Some("pci-0000:00:17.0-ata-4-part2") 89 | UUID: Some("0BE5-B90E") 90 | 91 | /dev/sda3: 92 | ID: Some("wwn-0x5002538d403d649a-part3") 93 | Label: None 94 | PartLabel: None 95 | PartUUID: Some("9a1e3789-aa7a-4b1e-98bd-34cc3d1d7117") 96 | Path: Some("pci-0000:00:17.0-ata-4-part3") 97 | UUID: Some("221519d5-eb97-42d4-9b6d-32ccd3b8503e") 98 | 99 | /dev/sda4: 100 | ID: Some("wwn-0x5002538d403d649a-part4") 101 | Label: None 102 | PartLabel: None 103 | PartUUID: Some("a87eb87a-6702-43dc-9e0b-b486d909422e") 104 | Path: Some("pci-0000:00:17.0-ata-4-part4") 105 | UUID: Some("b736abcd-d389-4074-b7dc-f3de743d9ae7") 106 | 107 | /dev/sdb1: 108 | ID: Some("usb-WD_Elements_25A2_575847314142373531363741-0:0-part1") 109 | Label: None 110 | PartLabel: None 111 | PartUUID: Some("c9cc9475-a2ae-4b3f-93e1-5af45ceabfbf") 112 | Path: Some("pci-0000:00:14.0-usb-0:4:1.0-scsi-0:0:0:0-part1") 113 | UUID: Some("A590-DC0F") 114 | 115 | /dev/sdb2: 116 | ID: Some("usb-WD_Elements_25A2_575847314142373531363741-0:0-part2") 117 | Label: None 118 | PartLabel: Some("recovery") 119 | PartUUID: Some("f52d3525-2057-460f-9b3f-46a8b59405f4") 120 | Path: Some("pci-0000:00:14.0-usb-0:4:1.0-scsi-0:0:0:0-part2") 121 | UUID: Some("A590-DBD6") 122 | 123 | /dev/sdb3: 124 | ID: Some("usb-WD_Elements_25A2_575847314142373531363741-0:0-part3") 125 | Label: None 126 | PartLabel: None 127 | PartUUID: Some("8b623345-f890-4b47-a0a6-99a1c772a9bd") 128 | Path: Some("pci-0000:00:14.0-usb-0:4:1.0-scsi-0:0:0:0-part3") 129 | UUID: Some("92246269-7259-48e7-ae07-7e0b5027fd2d") 130 | 131 | /dev/sdb4: 132 | ID: Some("usb-WD_Elements_25A2_575847314142373531363741-0:0-part4") 133 | Label: None 134 | PartLabel: None 135 | PartUUID: Some("f3108bcb-9f2d-43c4-a46b-3e2dd892948b") 136 | Path: Some("pci-0000:00:14.0-usb-0:4:1.0-scsi-0:0:0:0-part4") 137 | UUID: Some("b6639ce6-4bca-4193-9796-39e978fe24cf") 138 | 139 | /dev/mapper/cryptdata: 140 | ID: Some("lvm-pv-uuid-kAJFlt-88Gj-5JwA-T8da-MbAn-k6Jk-YxkB29") 141 | Label: None 142 | PartLabel: None 143 | PartUUID: None 144 | Path: None 145 | UUID: None 146 | 147 | /dev/mapper/cryptswap: 148 | ID: Some("dm-uuid-CRYPT-PLAIN-cryptswap") 149 | Label: None 150 | PartLabel: None 151 | PartUUID: None 152 | Path: None 153 | UUID: Some("fa5b7f8d-97c4-4152-8db6-5e054f03fb54") 154 | 155 | /dev/mapper/data: 156 | ID: None 157 | Label: None 158 | PartLabel: None 159 | PartUUID: None 160 | Path: None 161 | UUID: None 162 | 163 | /dev/mapper/data-root: 164 | ID: Some("dm-uuid-LVM-JdyQEWdAYdvV0dxmGMj6f0poaYgJdR2FGMVKQt3bDCR0xSo1wRPUXYVInczK7ShF") 165 | Label: None 166 | PartLabel: None 167 | PartUUID: None 168 | Path: None 169 | UUID: Some("ed646eba-b8a3-4c79-8f93-5ee1a25c6ec3") 170 | ``` -------------------------------------------------------------------------------- /examples/example.rs: -------------------------------------------------------------------------------- 1 | extern crate partition_identity; 2 | 3 | use partition_identity::{PartitionID, PartitionIdentifiers}; 4 | use std::env; 5 | use std::process::exit; 6 | 7 | fn main() { 8 | let mut args = env::args().skip(1); 9 | match args.next() { 10 | Some(arg) => match arg.as_str() { 11 | "from-path" => { 12 | let mut first = true; 13 | for device in args { 14 | if !first { 15 | println!() 16 | } 17 | first = false; 18 | println!("{}:", device); 19 | println!("{:#?}", PartitionIdentifiers::from_path(device)); 20 | } 21 | } 22 | "by-id" => { 23 | for id in args { 24 | let var = PartitionID::new_id(id.clone()); 25 | println!("{}: {:?}", id, var.get_device_path()); 26 | } 27 | } 28 | "by-uuid" => { 29 | for id in args { 30 | let var = PartitionID::new_uuid(id.clone()); 31 | println!("{}: {:?}", id, var.get_device_path()); 32 | } 33 | } 34 | "by-partuuid" => { 35 | for id in args { 36 | let var = PartitionID::new_partuuid(id.clone()); 37 | println!("{}: {:?}", id, var.get_device_path()); 38 | } 39 | } 40 | "detect-by" => { 41 | for id in args { 42 | let id = match PartitionID::from_disk_by_path(&id) { 43 | Ok(id) => id, 44 | Err(why) => { 45 | eprintln!("{}: {}", id, why); 46 | exit(1); 47 | } 48 | }; 49 | 50 | println!("{:?} = {:?}", id, id.get_device_path()); 51 | } 52 | } 53 | _ => { 54 | eprintln!( 55 | "invalid subcommand: valid commansd: [from-path, by-uuid, by-partuuid, ]" 56 | ); 57 | exit(1); 58 | } 59 | }, 60 | None => { 61 | eprintln!("must give subcommand: [from-path, by-uuid, by-partuuid, ]"); 62 | exit(1); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /rust-toolchain: -------------------------------------------------------------------------------- 1 | 1.59.0 2 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 100 2 | reorder_imports = true 3 | reorder_modules = true 4 | use_field_init_shorthand = true 5 | use_small_heuristics = "Max" 6 | 7 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Find the ID of a device by its path, or find a device path by its ID. 2 | 3 | use thiserror::Error; 4 | use self::PartitionSource::Path as SourcePath; 5 | use self::PartitionSource::*; 6 | use std::borrow::Cow; 7 | use std::fmt::{self, Display, Formatter}; 8 | use std::fs; 9 | use std::path::{Path, PathBuf}; 10 | use std::str::FromStr; 11 | 12 | #[derive(Debug, Error, Hash, Eq, PartialEq)] 13 | pub enum Error { 14 | #[error("the partition ID key was invalid")] 15 | InvalidKey, 16 | #[error("the provided path was not valid in this context")] 17 | InvalidPath, 18 | #[error("the provided `/dev/disk/by-` path was not supported")] 19 | UnknownByPath, 20 | } 21 | 22 | /// Describes a partition identity. 23 | /// 24 | /// A device path may be recovered from this. 25 | /// 26 | /// # Notes 27 | /// 28 | /// This is a struct instead of an enum to make access to the `id` string 29 | /// easier for situations where the variant does not need to be checked. 30 | #[derive(Clone, Debug, Hash, Eq, PartialEq)] 31 | pub struct PartitionID { 32 | pub variant: PartitionSource, 33 | pub id: String, 34 | } 35 | 36 | impl PartitionID { 37 | /// Construct a new `PartitionID` as the given source. 38 | pub fn new(variant: PartitionSource, id: String) -> Self { 39 | Self { variant, id } 40 | } 41 | 42 | /// Construct a new `PartitionID` as a `ID` source. 43 | pub fn new_id(id: String) -> Self { 44 | Self::new(ID, id) 45 | } 46 | 47 | /// Construct a new `PartitionID` as a `Label` source. 48 | pub fn new_label(id: String) -> Self { 49 | Self::new(Label, id) 50 | } 51 | 52 | /// Construct a new `PartitionID` as a `UUID` source. 53 | pub fn new_uuid(id: String) -> Self { 54 | Self::new(UUID, id) 55 | } 56 | 57 | /// Construct a new `PartitionID` as a `PartLabel` source. 58 | pub fn new_partlabel(id: String) -> Self { 59 | Self::new(PartLabel, id) 60 | } 61 | 62 | /// Construct a new `PartitionID` as a `PartUUID` source. 63 | pub fn new_partuuid(id: String) -> Self { 64 | Self::new(PartUUID, id) 65 | } 66 | 67 | /// Construct a new `PartitionID` as a `Path` source. 68 | pub fn new_path(id: String) -> Self { 69 | Self::new(SourcePath, id) 70 | } 71 | 72 | /// Find the device path of this ID. 73 | pub fn get_device_path(&self) -> Option { 74 | if self.variant == PartitionSource::Path && self.id.starts_with("/") { 75 | Some(PathBuf::from(&self.id)) 76 | } else { 77 | from_id(&self.id, &self.variant.disk_by_path()) 78 | } 79 | } 80 | 81 | /// Find the given source ID of the device at the given path. 82 | pub fn get_source>(variant: PartitionSource, path: P) -> Option { 83 | Some(Self { variant, id: find_id(path.as_ref(), &variant.disk_by_path())? }) 84 | } 85 | 86 | /// Find the UUID of the device at the given path. 87 | pub fn get_uuid>(path: P) -> Option { 88 | Self::get_source(UUID, path) 89 | } 90 | 91 | /// Find the PARTUUID of the device at the given path. 92 | pub fn get_partuuid>(path: P) -> Option { 93 | Self::get_source(PartUUID, path) 94 | } 95 | 96 | /// Fetch a partition ID by a `/dev/disk/by-` path. 97 | pub fn from_disk_by_path>(path: S) -> Result { 98 | let path = path.as_ref(); 99 | 100 | let path = if path.starts_with("/dev/disk/by-") { 101 | &path[13..] 102 | } else { 103 | return Err(Error::InvalidPath); 104 | }; 105 | 106 | let id = if path.starts_with("id/") { 107 | Self::new(ID, path[3..].into()) 108 | } else if path.starts_with("label/") { 109 | Self::new(Label, path[6..].into()) 110 | } else if path.starts_with("partlabel/") { 111 | Self::new(PartLabel, path[10..].into()) 112 | } else if path.starts_with("partuuid/") { 113 | Self::new(PartUUID, path[9..].into()) 114 | } else if path.starts_with("path/") { 115 | Self::new(Path, path[5..].into()) 116 | } else if path.starts_with("uuid/") { 117 | Self::new(UUID, path[5..].into()) 118 | } else { 119 | return Err(Error::UnknownByPath); 120 | }; 121 | 122 | Ok(id) 123 | } 124 | } 125 | 126 | impl Display for PartitionID { 127 | fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { 128 | if let PartitionSource::Path = self.variant { 129 | write!(fmt, "{}", self.id) 130 | } else { 131 | write!(fmt, "{}={}", <&'static str>::from(self.variant), self.id) 132 | } 133 | } 134 | } 135 | 136 | impl FromStr for PartitionID { 137 | type Err = Error; 138 | 139 | fn from_str(input: &str) -> Result { 140 | if input.starts_with('/') { 141 | Ok(PartitionID { variant: SourcePath, id: input.to_owned() }) 142 | } else if input.starts_with("ID=") { 143 | Ok(PartitionID { variant: ID, id: input[3..].to_owned() }) 144 | } else if input.starts_with("LABEL=") { 145 | Ok(PartitionID { variant: Label, id: input[6..].to_owned() }) 146 | } else if input.starts_with("PARTLABEL=") { 147 | Ok(PartitionID { variant: PartLabel, id: input[10..].to_owned() }) 148 | } else if input.starts_with("PARTUUID=") { 149 | Ok(PartitionID { variant: PartUUID, id: input[9..].to_owned() }) 150 | } else if input.starts_with("UUID=") { 151 | Ok(PartitionID { variant: UUID, id: input[5..].to_owned() }) 152 | } else { 153 | Err(Error::InvalidKey) 154 | } 155 | } 156 | } 157 | 158 | /// Describes the type of partition identity. 159 | #[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)] 160 | pub enum PartitionSource { 161 | ID, 162 | Label, 163 | PartLabel, 164 | PartUUID, 165 | Path, 166 | UUID, 167 | } 168 | 169 | impl Display for PartitionSource { 170 | fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { 171 | write!(fmt, "{}", <&'static str>::from(*self)) 172 | } 173 | } 174 | 175 | impl From for &'static str { 176 | fn from(pid: PartitionSource) -> &'static str { 177 | match pid { 178 | PartitionSource::ID => "ID", 179 | PartitionSource::Label => "LABEL", 180 | PartitionSource::PartLabel => "PARTLABEL", 181 | PartitionSource::PartUUID => "PARTUUID", 182 | PartitionSource::Path => "PATH", 183 | PartitionSource::UUID => "UUID", 184 | } 185 | } 186 | } 187 | 188 | impl PartitionSource { 189 | fn disk_by_path(self) -> PathBuf { 190 | PathBuf::from(["/dev/disk/by-", &<&'static str>::from(self).to_lowercase()].concat()) 191 | } 192 | } 193 | 194 | /// A collection of all discoverable identifiers for a partition. 195 | #[derive(Debug, Default, Clone, Hash, PartialEq)] 196 | pub struct PartitionIdentifiers { 197 | pub id: Option, 198 | pub label: Option, 199 | pub part_label: Option, 200 | pub part_uuid: Option, 201 | pub path: Option, 202 | pub uuid: Option, 203 | } 204 | 205 | impl PartitionIdentifiers { 206 | /// Fetches all discoverable identifiers for a partition by the path to that partition. 207 | pub fn from_path>(path: P) -> PartitionIdentifiers { 208 | let path = path.as_ref(); 209 | 210 | PartitionIdentifiers { 211 | path: PartitionID::get_source(SourcePath, path).map(|id| id.id), 212 | id: PartitionID::get_source(ID, path).map(|id| id.id), 213 | label: PartitionID::get_source(Label, path).map(|id| id.id), 214 | part_label: PartitionID::get_source(PartLabel, path).map(|id| id.id), 215 | part_uuid: PartitionID::get_source(PartUUID, path).map(|id| id.id), 216 | uuid: PartitionID::get_source(UUID, path).map(|id| id.id), 217 | } 218 | } 219 | 220 | /// Checks if the given identity matches one of the available identifiers. 221 | pub fn matches(&self, id: &PartitionID) -> bool { 222 | match id.variant { 223 | ID => self.id.as_ref().map_or(false, |s| &id.id == s), 224 | Label => self.label.as_ref().map_or(false, |s| &id.id == s), 225 | PartLabel => self.part_label.as_ref().map_or(false, |s| &id.id == s), 226 | PartUUID => self.part_uuid.as_ref().map_or(false, |s| &id.id == s), 227 | SourcePath => self.path.as_ref().map_or(false, |s| &id.id == s), 228 | UUID => self.uuid.as_ref().map_or(false, |s| &id.id == s), 229 | } 230 | } 231 | } 232 | 233 | fn attempt Option>(attempts: u8, wait: u64, mut func: F) -> Option { 234 | let mut tried = 0; 235 | let mut result; 236 | 237 | loop { 238 | result = func(); 239 | if result.is_none() && tried != attempts { 240 | ::std::thread::sleep(::std::time::Duration::from_millis(wait)); 241 | tried += 1; 242 | } else { 243 | return result; 244 | } 245 | } 246 | } 247 | 248 | fn canonicalize<'a>(path: &'a Path) -> Cow<'a, Path> { 249 | // NOTE: It seems that the kernel may intermittently error. 250 | match attempt::(10, 1, || path.canonicalize().ok()) { 251 | Some(path) => Cow::Owned(path), 252 | None => Cow::Borrowed(path), 253 | } 254 | } 255 | 256 | /// Attempts to find the ID from the given path. 257 | fn find_id(path: &Path, uuid_dir: &Path) -> Option { 258 | // NOTE: It seems that the kernel may sometimes intermittently skip directories. 259 | attempt(10, 1, move || { 260 | let dir = uuid_dir.read_dir().ok()?; 261 | find_id_(path, dir) 262 | }) 263 | } 264 | 265 | fn from_id(uuid: &str, uuid_dir: &Path) -> Option { 266 | // NOTE: It seems that the kernel may sometimes intermittently skip directories. 267 | attempt(10, 1, move || { 268 | let dir = uuid_dir.read_dir().ok()?; 269 | from_id_(uuid, dir) 270 | }) 271 | } 272 | 273 | fn find_id_(path: &Path, uuid_dir: fs::ReadDir) -> Option { 274 | let path = canonicalize(path); 275 | for uuid_entry in uuid_dir.filter_map(|entry| entry.ok()) { 276 | let uuid_path = uuid_entry.path(); 277 | let uuid_path = canonicalize(&uuid_path); 278 | if &uuid_path == &path { 279 | if let Some(uuid_entry) = uuid_entry.file_name().to_str() { 280 | return Some(uuid_entry.into()); 281 | } 282 | } 283 | } 284 | 285 | None 286 | } 287 | 288 | fn from_id_(uuid: &str, uuid_dir: fs::ReadDir) -> Option { 289 | for uuid_entry in uuid_dir.filter_map(|entry| entry.ok()) { 290 | let uuid_entry = uuid_entry.path(); 291 | if let Some(name) = uuid_entry.file_name() { 292 | if name == uuid { 293 | if let Ok(uuid_entry) = uuid_entry.canonicalize() { 294 | return Some(uuid_entry); 295 | } 296 | } 297 | } 298 | } 299 | 300 | None 301 | } 302 | 303 | #[cfg(test)] 304 | mod tests { 305 | use super::*; 306 | 307 | #[test] 308 | fn partition_id_from_str() { 309 | assert_eq!( 310 | "/dev/sda1".parse::(), 311 | Ok(PartitionID::new_path("/dev/sda1".into())) 312 | ); 313 | 314 | assert_eq!("ID=abcd".parse::(), Ok(PartitionID::new_id("abcd".into()))); 315 | 316 | assert_eq!("LABEL=abcd".parse::(), Ok(PartitionID::new_label("abcd".into()))); 317 | 318 | assert_eq!( 319 | "PARTLABEL=abcd".parse::(), 320 | Ok(PartitionID::new_partlabel("abcd".into())) 321 | ); 322 | 323 | assert_eq!( 324 | "PARTUUID=abcd".parse::(), 325 | Ok(PartitionID::new_partuuid("abcd".into())) 326 | ); 327 | 328 | assert_eq!("UUID=abcd".parse::(), Ok(PartitionID::new_uuid("abcd".into()))); 329 | } 330 | } 331 | --------------------------------------------------------------------------------