Skip to content

Commit 3dfa12f

Browse files
committed
disks: Add loopback device support and load associated disk
Signed-off-by: Ikey Doherty <[email protected]>
1 parent 85fea5c commit 3dfa12f

File tree

2 files changed

+133
-13
lines changed

2 files changed

+133
-13
lines changed

crates/disks/src/lib.rs

Lines changed: 54 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,13 @@
33
// SPDX-License-Identifier: MPL-2.0
44

55
mod disk;
6-
use std::{fs, io, path::PathBuf};
6+
use std::{
7+
fs, io,
8+
path::{Path, PathBuf},
9+
};
710

811
pub use disk::*;
12+
pub mod loopback;
913
pub mod mmc;
1014
pub mod nvme;
1115
pub mod partition;
@@ -20,7 +24,7 @@ const DEVFS_DIR: &str = "/dev";
2024
pub enum BlockDevice {
2125
/// A physical disk device
2226
Disk(Box<Disk>),
23-
Unknown,
27+
Loopback(Box<loopback::Device>),
2428
}
2529

2630
impl BlockDevice {
@@ -33,6 +37,22 @@ impl BlockDevice {
3337
Self::discover_in_sysroot("/")
3438
}
3539

40+
/// Returns the name of the block device.
41+
pub fn name(&self) -> &str {
42+
match self {
43+
BlockDevice::Disk(disk) => disk.name(),
44+
BlockDevice::Loopback(device) => device.name(),
45+
}
46+
}
47+
48+
/// Returns the path to the block device in /dev.
49+
pub fn device(&self) -> &Path {
50+
match self {
51+
BlockDevice::Disk(disk) => disk.device_path(),
52+
BlockDevice::Loopback(device) => device.device_path(),
53+
}
54+
}
55+
3656
/// Discovers block devices in a specified sysroot directory.
3757
///
3858
/// # Arguments
@@ -48,24 +68,28 @@ impl BlockDevice {
4868
let mut devices = Vec::new();
4969

5070
// Iterate over all block devices in sysfs and collect their filenames
51-
let entries = fs::read_dir(&sysfs_dir)?
71+
let mut entries = fs::read_dir(&sysfs_dir)?
5272
.filter_map(Result::ok)
53-
.filter_map(|e| Some(e.file_name().to_str()?.to_owned()));
73+
.filter_map(|e| Some(e.file_name().to_str()?.to_owned()))
74+
.collect::<Vec<_>>();
75+
entries.sort();
5476

5577
// For all the discovered block devices, try to create a Disk instance
5678
// At this point we completely ignore partitions. They come later.
5779
for entry in entries {
58-
let disk = if let Some(disk) = scsi::Disk::from_sysfs_path(&sysfs_dir, &entry) {
59-
Disk::Scsi(disk)
80+
let device = if let Some(disk) = scsi::Disk::from_sysfs_path(&sysfs_dir, &entry) {
81+
BlockDevice::Disk(Box::new(Disk::Scsi(disk)))
6082
} else if let Some(disk) = nvme::Disk::from_sysfs_path(&sysfs_dir, &entry) {
61-
Disk::Nvme(disk)
83+
BlockDevice::Disk(Box::new(Disk::Nvme(disk)))
6284
} else if let Some(disk) = mmc::Disk::from_sysfs_path(&sysfs_dir, &entry) {
63-
Disk::Mmc(disk)
85+
BlockDevice::Disk(Box::new(Disk::Mmc(disk)))
86+
} else if let Some(device) = loopback::Device::from_sysfs_path(&sysfs_dir, &entry) {
87+
BlockDevice::Loopback(Box::new(device))
6488
} else {
6589
continue;
6690
};
6791

68-
devices.push(BlockDevice::Disk(Box::new(disk)));
92+
devices.push(device);
6993
}
7094

7195
Ok(devices)
@@ -80,10 +104,27 @@ mod tests {
80104
fn test_discover() {
81105
let devices = BlockDevice::discover().unwrap();
82106
for device in &devices {
83-
if let BlockDevice::Disk(disk) = device {
84-
println!("{}: {disk}", disk.name());
85-
for partition in disk.partitions() {
86-
println!("├─{} {partition}", partition.name);
107+
match device {
108+
BlockDevice::Disk(disk) => {
109+
println!("{}: {disk}", disk.name());
110+
for partition in disk.partitions() {
111+
println!("├─{} {partition}", partition.name);
112+
}
113+
}
114+
BlockDevice::Loopback(device) => {
115+
if let Some(file) = device.file_path() {
116+
if let Some(disk) = device.disk() {
117+
println!("Loopback device: {} (backing file: {})", device.name(), file.display());
118+
println!("└─Disk: {} ({})", disk.name(), disk.model().unwrap_or("Unknown"));
119+
for partition in disk.partitions() {
120+
println!(" ├─{} {partition}", partition.name);
121+
}
122+
} else {
123+
println!("Loopback device: {} (backing file: {})", device.name(), file.display());
124+
}
125+
} else {
126+
println!("Loopback device: {}", device.name());
127+
}
87128
}
88129
}
89130
}

crates/disks/src/loopback.rs

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
// SPDX-FileCopyrightText: Copyright © 2025 Serpent OS Developers
2+
//
3+
// SPDX-License-Identifier: MPL-2.0
4+
5+
//! Loopback device enumeration and handling.
6+
//!
7+
//! Loopback devices in Linux are block devices that map files to block devices.
8+
//! This module handles enumeration and management of these devices,
9+
//! which appear as `/dev/loop*` block devices.
10+
11+
use std::path::{Path, PathBuf};
12+
13+
use crate::{sysfs, BasicDisk, DiskInit, DEVFS_DIR, SYSFS_DIR};
14+
15+
/// Represents a loop device.
16+
#[derive(Debug)]
17+
pub struct Device {
18+
/// The device name (e.g. "loop0", "loop1")
19+
name: String,
20+
21+
/// Path to the device in /dev
22+
device: PathBuf,
23+
24+
/// Optional backing file path
25+
file: Option<PathBuf>,
26+
27+
/// Optional disk device if the loop device is backed by a disk
28+
disk: Option<BasicDisk>,
29+
}
30+
31+
impl Device {
32+
/// Creates a new Device instance from a sysfs path if the device name matches loop device pattern.
33+
///
34+
/// # Arguments
35+
///
36+
/// * `sysroot` - The root path of the sysfs filesystem
37+
/// * `name` - The device name to check (e.g. "loop0", "loop1")
38+
///
39+
/// # Returns
40+
///
41+
/// * `Some(Device)` if the name matches loop pattern (starts with "loop" followed by numbers)
42+
/// * `None` if the name doesn't match or the device can't be initialized
43+
pub(crate) fn from_sysfs_path(sysroot: &Path, name: &str) -> Option<Self> {
44+
let matching = name.starts_with("loop") && name[4..].chars().all(char::is_numeric);
45+
let node = sysroot.join(SYSFS_DIR).join(name);
46+
let file = sysfs::read::<PathBuf>(sysroot, &node, "loop/backing_file");
47+
let disk = file.as_ref().and_then(|_| BasicDisk::from_sysfs_path(sysroot, name));
48+
if matching {
49+
Some(Self {
50+
name: name.to_owned(),
51+
device: PathBuf::from(DEVFS_DIR).join(name),
52+
file,
53+
disk,
54+
})
55+
} else {
56+
None
57+
}
58+
}
59+
60+
/// Returns the device name.
61+
pub fn name(&self) -> &str {
62+
&self.name
63+
}
64+
65+
/// Returns the device path.
66+
pub fn device_path(&self) -> &Path {
67+
&self.device
68+
}
69+
70+
/// Returns the backing file path.
71+
pub fn file_path(&self) -> Option<&Path> {
72+
self.file.as_deref()
73+
}
74+
75+
/// Returns the disk device if the loop device is backed by a disk.
76+
pub fn disk(&self) -> Option<&BasicDisk> {
77+
self.disk.as_ref()
78+
}
79+
}

0 commit comments

Comments
 (0)