partition-identity-0.3.0/.cargo_vcs_info.json0000644000000001360000000000100146740ustar { "git": { "sha1": "e4c705f2e5622386b685912bf31446255bc29be8" }, "path_in_vcs": "" }partition-identity-0.3.0/.gitignore000064400000000000000000000000400072674642500154760ustar 00000000000000 /target/ **/*.rs.bk Cargo.lock partition-identity-0.3.0/CHANGELOG.md000064400000000000000000000013220072674642500153230ustar 00000000000000# 0.2.6 - Remove the eprintln statements # 0.2.5 - Fix `PartitionID::get_device_path()` to handle `PartitionSource::Path`s that are already canonicalized. - Fix change in last version which broke the `PartitionID::get_source()` method. - Eliminate unnecessary allocations when canonicalizing paths # 0.2.4 - Implement `Display` for `PartitionID` and `PartitionSource`. # 0.2.3 - Derive `Eq` for `PartitionSource` and `PartitionID` # 0.2.2 - Applies a workaround to handle intermittent unavailability of `/dev/disk/by-` path operations. # 0.2.1 - Mark `PartitionIdentifier` fields as public. - Switch from `failure` error crate to `err-derive` - Set `rust-toolchain` to `1.24.1` - Derive `PartialEq` for `Error` partition-identity-0.3.0/Cargo.lock0000644000000030530000000000100126500ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "partition-identity" version = "0.3.0" dependencies = [ "thiserror", ] [[package]] name = "proc-macro2" version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" dependencies = [ "unicode-xid", ] [[package]] name = "quote" version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4af2ec4714533fcdf07e886f17025ace8b997b9ce51204ee69b6da831c3da57" dependencies = [ "proc-macro2", ] [[package]] name = "syn" version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea297be220d52398dcc07ce15a209fce436d361735ac1db700cab3b6cdfb9f54" dependencies = [ "proc-macro2", "quote", "unicode-xid", ] [[package]] name = "thiserror" version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "unicode-xid" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" partition-identity-0.3.0/Cargo.toml0000644000000015250000000000100126750ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" name = "partition-identity" version = "0.3.0" description = "Find the ID of a device by its path, or find a device path by its ID." readme = "README.md" keywords = ["linux", "partition"] categories = ["os", "os::unix-apis"] license = "MIT" repository = "https://github.com/pop-os/partition-identity" resolver = "2" [dependencies.thiserror] version = "1.0.30" partition-identity-0.3.0/Cargo.toml.orig000064400000000000000000000005540072674642500164070ustar 00000000000000[package] name = "partition-identity" version = "0.3.0" edition = "2021" repository = "https://github.com/pop-os/partition-identity" description = "Find the ID of a device by its path, or find a device path by its ID." license = "MIT" keywords = ["linux", "partition"] categories = ["os", "os::unix-apis"] readme = "README.md" [dependencies] thiserror = "1.0.30" partition-identity-0.3.0/LICENSE000064400000000000000000000020510072674642500145170ustar 00000000000000MIT License Copyright (c) 2018 System76 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. partition-identity-0.3.0/README.md000064400000000000000000000120560072674642500147770ustar 00000000000000# partition-identity Find the ID of a device by its path, or find a device path by its ID. > This crate was developed for inclusion in the [distinst](https://github.com/pop-os/distinst) project. ## Example ```rust extern crate partition_identity; use partition_identity::{PartitionID, PartitionSource}; use self::PartitionSource::*; use std::env; use std::process::exit; fn main() { let mut args = env::args().skip(1); match args.next() { Some(arg) => match arg.as_str() { "from-path" => { let mut first = true; for device in args { if ! first { println!() } first = false; println!("{}:", device); println!("ID: {:?}", PartitionID::get_source(ID, &device).map(|id| id.id)); println!("Label: {:?}", PartitionID::get_source(Label, &device).map(|id| id.id)); println!("PartLabel: {:?}", PartitionID::get_source(PartLabel, &device).map(|id| id.id)); println!("PartUUID: {:?}", PartitionID::get_source(PartUUID, &device).map(|id| id.id)); println!("Path: {:?}", PartitionID::get_source(Path, &device).map(|id| id.id)); println!("UUID: {:?}", PartitionID::get_source(UUID, &device).map(|id| id.id)); } } "by-uuid" => { for id in args { let var = PartitionID::new_uuid(id.clone()); println!("{}: {:?}", id, var.get_device_path()); } } "by-partuuid" => { for id in args { let var = PartitionID::new_partuuid(id.clone()); println!("{}: {:?}", id, var.get_device_path()); } } _ => { eprintln!("invalid subcommand: valid commansd: [from-path, by-uuid, by-partuuid, ]"); exit(1); } } None => { eprintln!("must give subcommand: [from-path, by-uuid, by-partuuid, ]"); exit(1); } } } ``` ### Example Output ``` $ example by-partuuid d1381cfb-739f-4963-ba02-695df6a42ca7 d1381cfb-739f-4963-ba02-695df6a42ca7: Some("/dev/sda2") ``` ``` $ example by-uuid ed646eba-b8a3-4c79-8f93-5ee1a25c6ec3 92246269-7259-48e7-ae07-7e0b5027fd2d ed646eba-b8a3-4c79-8f93-5ee1a25c6ec3: Some("/dev/dm-1") 92246269-7259-48e7-ae07-7e0b5027fd2d: Some("/dev/sdb3") ``` ``` $ example from-path /dev/sda{1,2,3,4} /dev/sdb{1,2,3,4} /dev/mapper/{cryptdata,cryptswap,data,data-root} /dev/sda1: ID: Some("wwn-0x5002538d403d649a-part1") Label: None PartLabel: None PartUUID: Some("0ff9334f-5649-439a-8138-1aa72e7c6af1") Path: Some("pci-0000:00:17.0-ata-4-part1") UUID: None /dev/sda2: ID: Some("wwn-0x5002538d403d649a-part2") Label: None PartLabel: None PartUUID: Some("d1381cfb-739f-4963-ba02-695df6a42ca7") Path: Some("pci-0000:00:17.0-ata-4-part2") UUID: Some("0BE5-B90E") /dev/sda3: ID: Some("wwn-0x5002538d403d649a-part3") Label: None PartLabel: None PartUUID: Some("9a1e3789-aa7a-4b1e-98bd-34cc3d1d7117") Path: Some("pci-0000:00:17.0-ata-4-part3") UUID: Some("221519d5-eb97-42d4-9b6d-32ccd3b8503e") /dev/sda4: ID: Some("wwn-0x5002538d403d649a-part4") Label: None PartLabel: None PartUUID: Some("a87eb87a-6702-43dc-9e0b-b486d909422e") Path: Some("pci-0000:00:17.0-ata-4-part4") UUID: Some("b736abcd-d389-4074-b7dc-f3de743d9ae7") /dev/sdb1: ID: Some("usb-WD_Elements_25A2_575847314142373531363741-0:0-part1") Label: None PartLabel: None PartUUID: Some("c9cc9475-a2ae-4b3f-93e1-5af45ceabfbf") Path: Some("pci-0000:00:14.0-usb-0:4:1.0-scsi-0:0:0:0-part1") UUID: Some("A590-DC0F") /dev/sdb2: ID: Some("usb-WD_Elements_25A2_575847314142373531363741-0:0-part2") Label: None PartLabel: Some("recovery") PartUUID: Some("f52d3525-2057-460f-9b3f-46a8b59405f4") Path: Some("pci-0000:00:14.0-usb-0:4:1.0-scsi-0:0:0:0-part2") UUID: Some("A590-DBD6") /dev/sdb3: ID: Some("usb-WD_Elements_25A2_575847314142373531363741-0:0-part3") Label: None PartLabel: None PartUUID: Some("8b623345-f890-4b47-a0a6-99a1c772a9bd") Path: Some("pci-0000:00:14.0-usb-0:4:1.0-scsi-0:0:0:0-part3") UUID: Some("92246269-7259-48e7-ae07-7e0b5027fd2d") /dev/sdb4: ID: Some("usb-WD_Elements_25A2_575847314142373531363741-0:0-part4") Label: None PartLabel: None PartUUID: Some("f3108bcb-9f2d-43c4-a46b-3e2dd892948b") Path: Some("pci-0000:00:14.0-usb-0:4:1.0-scsi-0:0:0:0-part4") UUID: Some("b6639ce6-4bca-4193-9796-39e978fe24cf") /dev/mapper/cryptdata: ID: Some("lvm-pv-uuid-kAJFlt-88Gj-5JwA-T8da-MbAn-k6Jk-YxkB29") Label: None PartLabel: None PartUUID: None Path: None UUID: None /dev/mapper/cryptswap: ID: Some("dm-uuid-CRYPT-PLAIN-cryptswap") Label: None PartLabel: None PartUUID: None Path: None UUID: Some("fa5b7f8d-97c4-4152-8db6-5e054f03fb54") /dev/mapper/data: ID: None Label: None PartLabel: None PartUUID: None Path: None UUID: None /dev/mapper/data-root: ID: Some("dm-uuid-LVM-JdyQEWdAYdvV0dxmGMj6f0poaYgJdR2FGMVKQt3bDCR0xSo1wRPUXYVInczK7ShF") Label: None PartLabel: None PartUUID: None Path: None UUID: Some("ed646eba-b8a3-4c79-8f93-5ee1a25c6ec3") ```partition-identity-0.3.0/examples/example.rs000064400000000000000000000041200072674642500173300ustar 00000000000000extern crate partition_identity; use partition_identity::{PartitionID, PartitionIdentifiers}; use std::env; use std::process::exit; fn main() { let mut args = env::args().skip(1); match args.next() { Some(arg) => match arg.as_str() { "from-path" => { let mut first = true; for device in args { if !first { println!() } first = false; println!("{}:", device); println!("{:#?}", PartitionIdentifiers::from_path(device)); } } "by-id" => { for id in args { let var = PartitionID::new_id(id.clone()); println!("{}: {:?}", id, var.get_device_path()); } } "by-uuid" => { for id in args { let var = PartitionID::new_uuid(id.clone()); println!("{}: {:?}", id, var.get_device_path()); } } "by-partuuid" => { for id in args { let var = PartitionID::new_partuuid(id.clone()); println!("{}: {:?}", id, var.get_device_path()); } } "detect-by" => { for id in args { let id = match PartitionID::from_disk_by_path(&id) { Ok(id) => id, Err(why) => { eprintln!("{}: {}", id, why); exit(1); } }; println!("{:?} = {:?}", id, id.get_device_path()); } } _ => { eprintln!( "invalid subcommand: valid commansd: [from-path, by-uuid, by-partuuid, ]" ); exit(1); } }, None => { eprintln!("must give subcommand: [from-path, by-uuid, by-partuuid, ]"); exit(1); } } } partition-identity-0.3.0/rust-toolchain000064400000000000000000000000070072674642500164070ustar 000000000000001.59.0 partition-identity-0.3.0/rustfmt.toml000064400000000000000000000001740072674642500161170ustar 00000000000000max_width = 100 reorder_imports = true reorder_modules = true use_field_init_shorthand = true use_small_heuristics = "Max" partition-identity-0.3.0/src/lib.rs000064400000000000000000000246430072674642500154300ustar 00000000000000//! Find the ID of a device by its path, or find a device path by its ID. use thiserror::Error; use self::PartitionSource::Path as SourcePath; use self::PartitionSource::*; use std::borrow::Cow; use std::fmt::{self, Display, Formatter}; use std::fs; use std::path::{Path, PathBuf}; use std::str::FromStr; #[derive(Debug, Error, Hash, Eq, PartialEq)] pub enum Error { #[error("the partition ID key was invalid")] InvalidKey, #[error("the provided path was not valid in this context")] InvalidPath, #[error("the provided `/dev/disk/by-` path was not supported")] UnknownByPath, } /// Describes a partition identity. /// /// A device path may be recovered from this. /// /// # Notes /// /// This is a struct instead of an enum to make access to the `id` string /// easier for situations where the variant does not need to be checked. #[derive(Clone, Debug, Hash, Eq, PartialEq)] pub struct PartitionID { pub variant: PartitionSource, pub id: String, } impl PartitionID { /// Construct a new `PartitionID` as the given source. pub fn new(variant: PartitionSource, id: String) -> Self { Self { variant, id } } /// Construct a new `PartitionID` as a `ID` source. pub fn new_id(id: String) -> Self { Self::new(ID, id) } /// Construct a new `PartitionID` as a `Label` source. pub fn new_label(id: String) -> Self { Self::new(Label, id) } /// Construct a new `PartitionID` as a `UUID` source. pub fn new_uuid(id: String) -> Self { Self::new(UUID, id) } /// Construct a new `PartitionID` as a `PartLabel` source. pub fn new_partlabel(id: String) -> Self { Self::new(PartLabel, id) } /// Construct a new `PartitionID` as a `PartUUID` source. pub fn new_partuuid(id: String) -> Self { Self::new(PartUUID, id) } /// Construct a new `PartitionID` as a `Path` source. pub fn new_path(id: String) -> Self { Self::new(SourcePath, id) } /// Find the device path of this ID. pub fn get_device_path(&self) -> Option { if self.variant == PartitionSource::Path && self.id.starts_with("/") { Some(PathBuf::from(&self.id)) } else { from_id(&self.id, &self.variant.disk_by_path()) } } /// Find the given source ID of the device at the given path. pub fn get_source>(variant: PartitionSource, path: P) -> Option { Some(Self { variant, id: find_id(path.as_ref(), &variant.disk_by_path())? }) } /// Find the UUID of the device at the given path. pub fn get_uuid>(path: P) -> Option { Self::get_source(UUID, path) } /// Find the PARTUUID of the device at the given path. pub fn get_partuuid>(path: P) -> Option { Self::get_source(PartUUID, path) } /// Fetch a partition ID by a `/dev/disk/by-` path. pub fn from_disk_by_path>(path: S) -> Result { let path = path.as_ref(); let path = if path.starts_with("/dev/disk/by-") { &path[13..] } else { return Err(Error::InvalidPath); }; let id = if path.starts_with("id/") { Self::new(ID, path[3..].into()) } else if path.starts_with("label/") { Self::new(Label, path[6..].into()) } else if path.starts_with("partlabel/") { Self::new(PartLabel, path[10..].into()) } else if path.starts_with("partuuid/") { Self::new(PartUUID, path[9..].into()) } else if path.starts_with("path/") { Self::new(Path, path[5..].into()) } else if path.starts_with("uuid/") { Self::new(UUID, path[5..].into()) } else { return Err(Error::UnknownByPath); }; Ok(id) } } impl Display for PartitionID { fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { if let PartitionSource::Path = self.variant { write!(fmt, "{}", self.id) } else { write!(fmt, "{}={}", <&'static str>::from(self.variant), self.id) } } } impl FromStr for PartitionID { type Err = Error; fn from_str(input: &str) -> Result { if input.starts_with('/') { Ok(PartitionID { variant: SourcePath, id: input.to_owned() }) } else if input.starts_with("ID=") { Ok(PartitionID { variant: ID, id: input[3..].to_owned() }) } else if input.starts_with("LABEL=") { Ok(PartitionID { variant: Label, id: input[6..].to_owned() }) } else if input.starts_with("PARTLABEL=") { Ok(PartitionID { variant: PartLabel, id: input[10..].to_owned() }) } else if input.starts_with("PARTUUID=") { Ok(PartitionID { variant: PartUUID, id: input[9..].to_owned() }) } else if input.starts_with("UUID=") { Ok(PartitionID { variant: UUID, id: input[5..].to_owned() }) } else { Err(Error::InvalidKey) } } } /// Describes the type of partition identity. #[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)] pub enum PartitionSource { ID, Label, PartLabel, PartUUID, Path, UUID, } impl Display for PartitionSource { fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { write!(fmt, "{}", <&'static str>::from(*self)) } } impl From for &'static str { fn from(pid: PartitionSource) -> &'static str { match pid { PartitionSource::ID => "ID", PartitionSource::Label => "LABEL", PartitionSource::PartLabel => "PARTLABEL", PartitionSource::PartUUID => "PARTUUID", PartitionSource::Path => "PATH", PartitionSource::UUID => "UUID", } } } impl PartitionSource { fn disk_by_path(self) -> PathBuf { PathBuf::from(["/dev/disk/by-", &<&'static str>::from(self).to_lowercase()].concat()) } } /// A collection of all discoverable identifiers for a partition. #[derive(Debug, Default, Clone, Hash, PartialEq)] pub struct PartitionIdentifiers { pub id: Option, pub label: Option, pub part_label: Option, pub part_uuid: Option, pub path: Option, pub uuid: Option, } impl PartitionIdentifiers { /// Fetches all discoverable identifiers for a partition by the path to that partition. pub fn from_path>(path: P) -> PartitionIdentifiers { let path = path.as_ref(); PartitionIdentifiers { path: PartitionID::get_source(SourcePath, path).map(|id| id.id), id: PartitionID::get_source(ID, path).map(|id| id.id), label: PartitionID::get_source(Label, path).map(|id| id.id), part_label: PartitionID::get_source(PartLabel, path).map(|id| id.id), part_uuid: PartitionID::get_source(PartUUID, path).map(|id| id.id), uuid: PartitionID::get_source(UUID, path).map(|id| id.id), } } /// Checks if the given identity matches one of the available identifiers. pub fn matches(&self, id: &PartitionID) -> bool { match id.variant { ID => self.id.as_ref().map_or(false, |s| &id.id == s), Label => self.label.as_ref().map_or(false, |s| &id.id == s), PartLabel => self.part_label.as_ref().map_or(false, |s| &id.id == s), PartUUID => self.part_uuid.as_ref().map_or(false, |s| &id.id == s), SourcePath => self.path.as_ref().map_or(false, |s| &id.id == s), UUID => self.uuid.as_ref().map_or(false, |s| &id.id == s), } } } fn attempt Option>(attempts: u8, wait: u64, mut func: F) -> Option { let mut tried = 0; let mut result; loop { result = func(); if result.is_none() && tried != attempts { ::std::thread::sleep(::std::time::Duration::from_millis(wait)); tried += 1; } else { return result; } } } fn canonicalize<'a>(path: &'a Path) -> Cow<'a, Path> { // NOTE: It seems that the kernel may intermittently error. match attempt::(10, 1, || path.canonicalize().ok()) { Some(path) => Cow::Owned(path), None => Cow::Borrowed(path), } } /// Attempts to find the ID from the given path. fn find_id(path: &Path, uuid_dir: &Path) -> Option { // NOTE: It seems that the kernel may sometimes intermittently skip directories. attempt(10, 1, move || { let dir = uuid_dir.read_dir().ok()?; find_id_(path, dir) }) } fn from_id(uuid: &str, uuid_dir: &Path) -> Option { // NOTE: It seems that the kernel may sometimes intermittently skip directories. attempt(10, 1, move || { let dir = uuid_dir.read_dir().ok()?; from_id_(uuid, dir) }) } fn find_id_(path: &Path, uuid_dir: fs::ReadDir) -> Option { let path = canonicalize(path); for uuid_entry in uuid_dir.filter_map(|entry| entry.ok()) { let uuid_path = uuid_entry.path(); let uuid_path = canonicalize(&uuid_path); if &uuid_path == &path { if let Some(uuid_entry) = uuid_entry.file_name().to_str() { return Some(uuid_entry.into()); } } } None } fn from_id_(uuid: &str, uuid_dir: fs::ReadDir) -> Option { for uuid_entry in uuid_dir.filter_map(|entry| entry.ok()) { let uuid_entry = uuid_entry.path(); if let Some(name) = uuid_entry.file_name() { if name == uuid { if let Ok(uuid_entry) = uuid_entry.canonicalize() { return Some(uuid_entry); } } } } None } #[cfg(test)] mod tests { use super::*; #[test] fn partition_id_from_str() { assert_eq!( "/dev/sda1".parse::(), Ok(PartitionID::new_path("/dev/sda1".into())) ); assert_eq!("ID=abcd".parse::(), Ok(PartitionID::new_id("abcd".into()))); assert_eq!("LABEL=abcd".parse::(), Ok(PartitionID::new_label("abcd".into()))); assert_eq!( "PARTLABEL=abcd".parse::(), Ok(PartitionID::new_partlabel("abcd".into())) ); assert_eq!( "PARTUUID=abcd".parse::(), Ok(PartitionID::new_partuuid("abcd".into())) ); assert_eq!("UUID=abcd".parse::(), Ok(PartitionID::new_uuid("abcd".into()))); } }