Skip to content

Commit

Permalink
Merge pull request #4 from sorcerersr/data_from_gpt
Browse files Browse the repository at this point in the history
Data from gpt (UUIDs )
  • Loading branch information
sorcerersr committed Jan 24, 2024
2 parents 19970e6 + 365ac0b commit 42d979d
Show file tree
Hide file tree
Showing 10 changed files with 208 additions and 4 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@ Cargo.lock

# These are backup files generated by rustfmt
**/*.rs.bk

.vscode/*
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ authors = ["Stefan Rabmund <sorcerersr@mailbox.org>"]
readme = "crates-io.md"

[dependencies]
gpt = {version = "3.1.0", optional=true}
thiserror = "1.0"

[dev-dependencies]
tempfile = "3"


[[example]]
name = "simple_main"
path = "examples/simple_main.rs"
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@

A rust library (crate) for listing mounted or mountable drives on linux (flash drives, sd-cards, etc.)

Uses the virtual sysfs filesystem (/sys) to gather information about the block devices known by the linux kernel.
Uses the virtual kernel filesystems (/sys, /proc and /dev) to gather information about the block devices known by the linux kernel.
Optionally reads the GUID Partition Table (GPT) to enrich gathered data with informations from the partition table.

## Data

Expand All @@ -17,10 +18,12 @@ Uses the virtual sysfs filesystem (/sys) to gather information about the block d
* size
* partitions
* is removable
* uuid (optionally from GPT)
* partition
* name
* size
* mountpoint (path, filesystem)
* part_uuid (optionally from GPT)

## Example

Expand All @@ -30,6 +33,9 @@ For an simple example see [simple_main.rs](examples/simple_main.rs):
cargo run --example simple_main
```

## Optional Data from GUID Partition Table (GPT)

Currently only the UUID for a device and the PART_UUID of partitions are retreived using the GPT. This needs the feature "gpt" to be enabled.

## License

Expand Down
6 changes: 6 additions & 0 deletions codecov.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
coverage:
status:
project:
default:
target: 80%
threshold: 1%
9 changes: 8 additions & 1 deletion crates-io.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@

A rust library (crate) for listing mounted or mountable drives on linux (flash drives, sd-cards, etc.)

Uses the virtual sysfs filesystem (/sys) to gather information about the block devices known by the linux kernel.
Uses the virtual kernel filesystems (/sys, /proc and /dev) to gather information about the block devices known by the linux kernel.
Optionally reads the GUID Partition Table (GPT) to enrich gathered data with informations from the partition table.

## Data

Expand All @@ -15,10 +16,12 @@ Uses the virtual sysfs filesystem (/sys) to gather information about the block d
* size
* partitions
* is removable
* uuid (optionally from GPT)
* partition
* name
* size
* mountpoint (path, filesystem)
* part_uuid (optionally from GPT)

## Example

Expand All @@ -32,6 +35,10 @@ cargo run --example simple_main

Documentation can be found on [docs.rs](https://docs.rs/drives/latest/drives/).

## Optional Data from GUID Partition Table (GPT)

Currently only the UUID for a device and the PART_UUID of partitions are retreived using the GPT. This needs the feature "gpt" to be enabled.

## License


Expand Down
Binary file added resources/test/gptdisk.img
Binary file not shown.
2 changes: 2 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ pub enum DrivesError {
PathAppendFailed,
#[error("failed to convert file content to u64")]
ConversionToU64Failed,
#[error("failed to convert file content to u32")]
ConversionToU32Failed,
#[error("failed to access directory {directory:?}")]
DiraccessError { directory: String },
#[error("reading mounts from /proc/mounts failed")]
Expand Down
35 changes: 35 additions & 0 deletions src/fs_wrap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,17 @@ pub fn read_file_to_u64(path: &str) -> Result<u64, DrivesError> {
Ok(size_as_u64)
}

pub fn read_file_to_u32(path: &str) -> Result<u32, DrivesError> {
let content = read_file_to_string(Path::new(path))?;

let size_as_u32 = if let Ok(size) = content.parse() {
size
} else {
return Err(DrivesError::ConversionToU32Failed);
};
Ok(size_as_u32)
}

pub fn read_lines<P>(filename: P) -> io::Result<io::Lines<io::BufReader<File>>>
where
P: AsRef<Path>,
Expand Down Expand Up @@ -113,4 +124,28 @@ mod tests {
let result = read_file_to_string(test_file.path());
assert_eq!("content", result.unwrap());
}

#[test]
fn test_read_file_to_32() {
// prepare a temporary file to read from
let mut test_file = NamedTempFile::new().unwrap();
test_file.write_all("2".as_bytes()).unwrap();

// call the method under test
let result = read_file_to_u32(test_file.path().to_str().unwrap());
let expected: u32 = 2;
assert_eq!(expected, result.unwrap());
}

#[test]
fn test_read_file_to_64() {
// prepare a temporary file to read from
let mut test_file = NamedTempFile::new().unwrap();
test_file.write_all("42".as_bytes()).unwrap();

// call the method under test
let result = read_file_to_u64(test_file.path().to_str().unwrap());
let expected: u64 = 42;
assert_eq!(expected, result.unwrap());
}
}
123 changes: 123 additions & 0 deletions src/gpt.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
use crate::Device;
#[cfg(feature = "gpt")]
use gpt;

#[cfg(not(test))]
const DEV_DIR: &str = "/dev/";

#[cfg(test)]
const DEV_DIR: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/resources", "/test");

/// Enumeration for holding the gpt UUID or a reason why it is not available
#[derive(Debug)]
pub enum GptUUID {
/// an io error happened when opening the device for read access
IoError(std::io::Error),
/// the UUID from the partition table (gpt) as a hyphenated string
UUID(String),
/// the feature "gpt" was not enabled
FeatureNotEnabled,
/// reading the gpt was successful, but no header was found
NotAvailable,
}

// when the feature "gpt" is not enabled this function is used
// to set the GptUUID::FeatureNotEnabled value
#[cfg(not(feature = "gpt"))]
pub fn enrich_with_gpt_uuid(mut device: Device) -> Device {
device.uuid = GptUUID::FeatureNotEnabled;
device
}

// When the feature "gpt" is enabled then this function will actually read the
// partition table (gpt) to get the UUID for the device and the partitions
#[cfg(feature = "gpt")]
pub fn enrich_with_gpt_uuid(mut device: Device) -> Device {
let diskpath = std::path::Path::new(DEV_DIR).join(device.name.to_string());
let cfg = gpt::GptConfig::new().writable(false);
match cfg.open(diskpath) {
Err(error) => device.uuid = GptUUID::IoError(error),
Ok(disk) => {
match disk.primary_header() {
None => device.uuid = GptUUID::NotAvailable,
Some(disk_header) => {
device.uuid = GptUUID::UUID(disk_header.disk_guid.as_hyphenated().to_string());
}
};
for partition in device.partitions.iter_mut() {
match disk.partitions().get(&partition.number) {
Some(gpt_partition) => {
partition.part_uuid =
GptUUID::UUID(gpt_partition.part_guid.as_hyphenated().to_string())
}
None => partition.part_uuid = GptUUID::NotAvailable,
}
}
}
};

device
}

#[cfg(test)]
mod tests {

use super::*;

#[cfg(feature = "gpt")]
#[test]
fn test_enrich_with_gpt_uuid() {
use crate::{get_devices, Partition, Size};

let partition1 = Partition {
name: "sda1".to_string(),
size: Size::new(512),
number: 1,
mountpoint: None,
part_uuid: GptUUID::NotAvailable,
};
let partition2 = Partition {
name: "sda2".to_string(),
size: Size::new(512),
number: 2,
mountpoint: None,
part_uuid: GptUUID::NotAvailable,
};

let mut device = Device {
name: "gptdisk.img".to_string(),
partitions: vec![partition1, partition2],
is_removable: false,
model: None,
serial: None,
size: Size::new(42),
uuid: GptUUID::NotAvailable,
};
device = enrich_with_gpt_uuid(device);

match device.uuid {
GptUUID::UUID(uuid) => assert_eq!("f0ce7b2c-74af-47e4-8141-b2fe24ac20cc", uuid),
_ => panic!("No UUID"),
}
match &device
.partitions
.iter()
.find(|&partition| partition.number == 1)
.unwrap()
.part_uuid
{
GptUUID::UUID(uuid) => assert_eq!("3cdd6997-9b47-46f1-a160-49546976c24e", uuid),
_ => panic!("Partition 1 - no UUID"),
}
match &device
.partitions
.iter()
.find(|&partition| partition.number == 2)
.unwrap()
.part_uuid
{
GptUUID::UUID(uuid) => assert_eq!("4d3adf65-ff1b-473c-8f5e-b6c8d228b8d4", uuid),
_ => panic!("Partition 2 - no UUID"),
}
}
}
25 changes: 24 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@ use mounts::Mounts;

mod error;
mod fs_wrap;
mod gpt;
mod mounts;
mod size;

pub use error::DrivesError;
pub use mounts::Mount;
pub use size::{Size, Unit};
pub use gpt::GptUUID;

use std::fs::DirEntry;

Expand All @@ -33,7 +35,10 @@ pub struct Device {
pub model: Option<String>,
/// the hardware serial string
pub serial: Option<String>,
/// size of the device
pub size: Size,
/// the GUID from GPT (needs feature "gpt" to be enabled)
pub uuid: GptUUID,
}

/// partition of a device
Expand All @@ -43,8 +48,12 @@ pub struct Partition {
pub name: String,
/// size of the partition on 512 byte blocks
pub size: Size,
/// the partition number
pub number: u32,
/// the mountpoint if mounted
pub mountpoint: Option<Mount>,
/// the PartUUID from GPT (needs feature "gpt" to be enabled)
pub part_uuid: GptUUID,
}

struct Drives {
Expand Down Expand Up @@ -72,10 +81,13 @@ impl Drives {
if dir_name.starts_with(&base_dir_name) {
let size = fs_wrap::read_file_to_u64(&build_path(&entry, "/size")?)?;
let mount = self.find_mountpoint_for_partition(&mount_points, &dir_name)?;
let number = fs_wrap::read_file_to_u32(&build_path(&entry, "/partition")?)?;
partitions.push(Partition {
name: dir_name,
size: Size::new(size),
number,
mountpoint: mount,
part_uuid: GptUUID::NotAvailable,
});
}
}
Expand Down Expand Up @@ -136,14 +148,16 @@ impl Drives {
let model_and_serial = self.read_model_and_serial_if_available(&entry);
let size = fs_wrap::read_file_to_u64(&build_path(&entry, "/size")?)?;

let device = Device {
let mut device = Device {
name: device_name.clone(),
partitions,
is_removable: removable,
model: model_and_serial.0,
serial: model_and_serial.1,
size: Size::new(size),
uuid: GptUUID::NotAvailable,
};
device = gpt::enrich_with_gpt_uuid(device);
devices.push(device);
}
Ok(devices)
Expand Down Expand Up @@ -194,11 +208,20 @@ mod tests {
fs::create_dir(&part_one_dir_path).unwrap();
size_file = fs::File::create(part_one_dir_path.as_path().join("size")).unwrap();
size_file.write_all("1050624".as_bytes()).unwrap();

let mut partition_file = fs::File::create(part_one_dir_path.as_path().join("partition")).unwrap();
partition_file.write_all("1".as_bytes()).unwrap();



let part_two_dir_path = next_dir_path.join("nvme0n1p2");
fs::create_dir(&part_two_dir_path).unwrap();
size_file = fs::File::create(part_two_dir_path.as_path().join("size")).unwrap();
size_file.write_all("999162511".as_bytes()).unwrap();
let mut partition_file = fs::File::create(part_two_dir_path.as_path().join("partition")).unwrap();
partition_file.write_all("2".as_bytes()).unwrap();


// and create a third dir that isn't following the partition name schema
// and should therefor not be identified as a partition
let power_dir_path = next_dir_path.join("power");
Expand Down

0 comments on commit 42d979d

Please sign in to comment.