Skip to content

Commit 4a17907

Browse files
committed
disks: Begin skeletal new "disks" crate
The disks crate will support enumeration of various disks in time, but for now we're solely focusing on NVME and SCSI (really all libata managed devices) By keeping these distinct for enumeration we'll be able to tacke on composite and multipath devices without causing ourselves confusion down the road. Note, child partitions are deliberately ignored. Signed-off-by: Ikey Doherty <[email protected]>
1 parent d1a7103 commit 4a17907

File tree

5 files changed

+140
-0
lines changed

5 files changed

+140
-0
lines changed

Cargo.lock

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/disks/Cargo.toml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[package]
2+
name = "disks"
3+
version = "0.1.0"
4+
edition = "2021"
5+
description = "A library for working with disks and partitions"
6+
7+
[dependencies]
8+
regex = "1"

crates/disks/src/lib.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// SPDX-FileCopyrightText: Copyright © 2025 Serpent OS Developers
2+
//
3+
// SPDX-License-Identifier: MPL-2.0
4+
5+
use std::{fs, path::PathBuf};
6+
7+
pub mod nvme;
8+
pub mod scsi;
9+
10+
const SYSFS_DIR: &str = "/sys/class/block";
11+
12+
#[derive(Debug)]
13+
pub struct Disk {
14+
/// Partial-name, ie "sda"
15+
pub name: String,
16+
17+
// Number of sectors (* 512 sector size for data size)
18+
pub sectors: u64,
19+
}
20+
21+
impl Disk {
22+
fn from_sysfs_block_name(name: impl AsRef<str>) -> Self {
23+
let name = name.as_ref().to_owned();
24+
let entry = PathBuf::from(SYSFS_DIR).join(&name);
25+
26+
// Determine number of blocks
27+
let block_file = entry.join("size");
28+
let sectors = fs::read_to_string(block_file)
29+
.ok()
30+
.and_then(|s| s.trim().parse::<u64>().ok())
31+
.unwrap_or(0);
32+
33+
Self { name, sectors }
34+
}
35+
36+
/// Return usable size
37+
/// TODO: Grab the block size from the system. We know Linux is built on 512s though.
38+
pub fn size_in_bytes(&self) -> u64 {
39+
self.sectors * 512
40+
}
41+
}

crates/disks/src/nvme.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// SPDX-FileCopyrightText: Copyright © 2025 Serpent OS Developers
2+
//
3+
// SPDX-License-Identifier: MPL-2.0
4+
5+
//! NVME device enumeration and handling
6+
//!
7+
//! This module provides functionality to enumerate and handle NVME devices.
8+
9+
use std::{fs, io};
10+
11+
use regex::Regex;
12+
13+
use crate::{Disk, SYSFS_DIR};
14+
15+
pub fn enumerate() -> io::Result<Vec<Disk>> {
16+
// Filter for NVME block devices in format nvmeXnY where X and Y are digits
17+
// Exclude partitions (nvmeXnYpZ) and character devices
18+
let nvme_pattern = Regex::new(r"^nvme\d+n\d+$").unwrap();
19+
20+
let items = fs::read_dir(SYSFS_DIR)?
21+
.filter_map(Result::ok)
22+
.filter_map(|e| Some(e.file_name().to_str()?.to_owned()))
23+
.filter(|name| nvme_pattern.is_match(name))
24+
.map(Disk::from_sysfs_block_name)
25+
.collect();
26+
Ok(items)
27+
}
28+
29+
#[cfg(test)]
30+
mod tests {
31+
use super::*;
32+
33+
#[test]
34+
fn test_enumerate() {
35+
let devices = enumerate().expect("failed to collect nvme disks");
36+
eprintln!("nvme devices: {devices:?}");
37+
for device in devices.iter() {
38+
let mut size = device.size_in_bytes() as f64;
39+
size /= 1024.0 * 1024.0 * 1024.0;
40+
// Cheeky emulation of `fdisk -l` output
41+
eprintln!(
42+
"Disk /dev/{}: {:.2} GiB, {} bytes, {} sectors",
43+
device.name,
44+
size,
45+
device.size_in_bytes(),
46+
device.sectors
47+
);
48+
}
49+
}
50+
}

crates/disks/src/scsi.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// SPDX-FileCopyrightText: Copyright © 2025 Serpent OS Developers
2+
//
3+
// SPDX-License-Identifier: MPL-2.0
4+
5+
//! SCSI device enumeration and handling
6+
//!
7+
//! OK. Not quite true. Per modern conventions, all libata devices are also considered SCSI devices.
8+
//! This means all `/dev/sd*` devices.
9+
10+
use std::{fs, io};
11+
12+
use crate::{Disk, SYSFS_DIR};
13+
14+
pub fn enumerate() -> io::Result<Vec<Disk>> {
15+
// Filtered list of SCSI devices whose paths begin with "sd" but not ending with a digit
16+
let items = fs::read_dir(SYSFS_DIR)?
17+
.filter_map(Result::ok)
18+
.filter_map(|e| Some(e.file_name().to_str()?.to_owned()))
19+
.filter(|e| e.starts_with("sd") && e[2..].chars().all(char::is_alphabetic))
20+
.map(Disk::from_sysfs_block_name)
21+
.collect();
22+
Ok(items)
23+
}
24+
25+
#[cfg(test)]
26+
mod tests {
27+
use super::*;
28+
29+
#[test]
30+
fn test_enumerate() {
31+
let devices = enumerate().expect("Failed to enumerate SCSI devices");
32+
eprintln!("scsi devices: {devices:?}");
33+
}
34+
}

0 commit comments

Comments
 (0)