Skip to content

Commit

Permalink
disks: Add loopback device support and load associated disk
Browse files Browse the repository at this point in the history
Signed-off-by: Ikey Doherty <[email protected]>
  • Loading branch information
ikeycode committed Jan 21, 2025
1 parent 85fea5c commit 3dfa12f
Show file tree
Hide file tree
Showing 2 changed files with 133 additions and 13 deletions.
67 changes: 54 additions & 13 deletions crates/disks/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,13 @@
// SPDX-License-Identifier: MPL-2.0

mod disk;
use std::{fs, io, path::PathBuf};
use std::{
fs, io,
path::{Path, PathBuf},
};

pub use disk::*;
pub mod loopback;
pub mod mmc;
pub mod nvme;
pub mod partition;
Expand All @@ -20,7 +24,7 @@ const DEVFS_DIR: &str = "/dev";
pub enum BlockDevice {
/// A physical disk device
Disk(Box<Disk>),
Unknown,
Loopback(Box<loopback::Device>),
}

impl BlockDevice {
Expand All @@ -33,6 +37,22 @@ impl BlockDevice {
Self::discover_in_sysroot("/")
}

/// Returns the name of the block device.
pub fn name(&self) -> &str {
match self {
BlockDevice::Disk(disk) => disk.name(),
BlockDevice::Loopback(device) => device.name(),
}
}

/// Returns the path to the block device in /dev.
pub fn device(&self) -> &Path {
match self {
BlockDevice::Disk(disk) => disk.device_path(),
BlockDevice::Loopback(device) => device.device_path(),
}
}

/// Discovers block devices in a specified sysroot directory.
///
/// # Arguments
Expand All @@ -48,24 +68,28 @@ impl BlockDevice {
let mut devices = Vec::new();

// Iterate over all block devices in sysfs and collect their filenames
let entries = fs::read_dir(&sysfs_dir)?
let mut entries = fs::read_dir(&sysfs_dir)?
.filter_map(Result::ok)
.filter_map(|e| Some(e.file_name().to_str()?.to_owned()));
.filter_map(|e| Some(e.file_name().to_str()?.to_owned()))
.collect::<Vec<_>>();
entries.sort();

// For all the discovered block devices, try to create a Disk instance
// At this point we completely ignore partitions. They come later.
for entry in entries {
let disk = if let Some(disk) = scsi::Disk::from_sysfs_path(&sysfs_dir, &entry) {
Disk::Scsi(disk)
let device = if let Some(disk) = scsi::Disk::from_sysfs_path(&sysfs_dir, &entry) {
BlockDevice::Disk(Box::new(Disk::Scsi(disk)))
} else if let Some(disk) = nvme::Disk::from_sysfs_path(&sysfs_dir, &entry) {
Disk::Nvme(disk)
BlockDevice::Disk(Box::new(Disk::Nvme(disk)))
} else if let Some(disk) = mmc::Disk::from_sysfs_path(&sysfs_dir, &entry) {
Disk::Mmc(disk)
BlockDevice::Disk(Box::new(Disk::Mmc(disk)))
} else if let Some(device) = loopback::Device::from_sysfs_path(&sysfs_dir, &entry) {
BlockDevice::Loopback(Box::new(device))
} else {
continue;
};

devices.push(BlockDevice::Disk(Box::new(disk)));
devices.push(device);
}

Ok(devices)
Expand All @@ -80,10 +104,27 @@ mod tests {
fn test_discover() {
let devices = BlockDevice::discover().unwrap();
for device in &devices {
if let BlockDevice::Disk(disk) = device {
println!("{}: {disk}", disk.name());
for partition in disk.partitions() {
println!("├─{} {partition}", partition.name);
match device {
BlockDevice::Disk(disk) => {
println!("{}: {disk}", disk.name());
for partition in disk.partitions() {
println!("├─{} {partition}", partition.name);
}
}
BlockDevice::Loopback(device) => {
if let Some(file) = device.file_path() {
if let Some(disk) = device.disk() {
println!("Loopback device: {} (backing file: {})", device.name(), file.display());
println!("└─Disk: {} ({})", disk.name(), disk.model().unwrap_or("Unknown"));
for partition in disk.partitions() {
println!(" ├─{} {partition}", partition.name);
}
} else {
println!("Loopback device: {} (backing file: {})", device.name(), file.display());
}
} else {
println!("Loopback device: {}", device.name());
}
}
}
}
Expand Down
79 changes: 79 additions & 0 deletions crates/disks/src/loopback.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// SPDX-FileCopyrightText: Copyright © 2025 Serpent OS Developers
//
// SPDX-License-Identifier: MPL-2.0

//! Loopback device enumeration and handling.
//!
//! Loopback devices in Linux are block devices that map files to block devices.
//! This module handles enumeration and management of these devices,
//! which appear as `/dev/loop*` block devices.
use std::path::{Path, PathBuf};

use crate::{sysfs, BasicDisk, DiskInit, DEVFS_DIR, SYSFS_DIR};

/// Represents a loop device.
#[derive(Debug)]
pub struct Device {
/// The device name (e.g. "loop0", "loop1")
name: String,

/// Path to the device in /dev
device: PathBuf,

/// Optional backing file path
file: Option<PathBuf>,

/// Optional disk device if the loop device is backed by a disk
disk: Option<BasicDisk>,
}

impl Device {
/// Creates a new Device instance from a sysfs path if the device name matches loop device pattern.
///
/// # Arguments
///
/// * `sysroot` - The root path of the sysfs filesystem
/// * `name` - The device name to check (e.g. "loop0", "loop1")
///
/// # Returns
///
/// * `Some(Device)` if the name matches loop pattern (starts with "loop" followed by numbers)
/// * `None` if the name doesn't match or the device can't be initialized
pub(crate) fn from_sysfs_path(sysroot: &Path, name: &str) -> Option<Self> {
let matching = name.starts_with("loop") && name[4..].chars().all(char::is_numeric);
let node = sysroot.join(SYSFS_DIR).join(name);
let file = sysfs::read::<PathBuf>(sysroot, &node, "loop/backing_file");
let disk = file.as_ref().and_then(|_| BasicDisk::from_sysfs_path(sysroot, name));
if matching {
Some(Self {
name: name.to_owned(),
device: PathBuf::from(DEVFS_DIR).join(name),
file,
disk,
})
} else {
None
}
}

/// Returns the device name.
pub fn name(&self) -> &str {
&self.name
}

/// Returns the device path.
pub fn device_path(&self) -> &Path {
&self.device
}

/// Returns the backing file path.
pub fn file_path(&self) -> Option<&Path> {
self.file.as_deref()
}

/// Returns the disk device if the loop device is backed by a disk.
pub fn disk(&self) -> Option<&BasicDisk> {
self.disk.as_ref()
}
}

0 comments on commit 3dfa12f

Please sign in to comment.