Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 30 additions & 1 deletion crates/blockdev/src/blockdev.rs
Original file line number Diff line number Diff line change
Expand Up @@ -382,10 +382,39 @@ pub fn list_dev(dev: &Utf8Path) -> Result<Device> {
.ok_or_else(|| anyhow!("no device output from lsblk for {dev}"))
}

#[context("Finding block device for ZFS dataset {dataset}")]
fn list_dev_for_zfs_dataset(dataset: &str) -> Result<Device> {
let dataset = dataset.strip_prefix("ZFS=").unwrap_or(dataset);
let pool = dataset
.split('/')
.next()
.ok_or_else(|| anyhow!("Invalid ZFS dataset: {dataset}"))?;
Comment on lines +388 to +391
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The split('/').next() call on a string will always return Some (even for an empty string), making the ok_or_else block unreachable. Furthermore, if the dataset string is empty or only contains the ZFS= prefix, pool will be an empty string, which will cause the zpool command to fail later. It's safer to filter out empty strings to ensure a valid pool name is extracted.

Suggested change
let pool = dataset
.split('/')
.next()
.ok_or_else(|| anyhow!("Invalid ZFS dataset: {dataset}"))?;
let pool = dataset
.split('/')
.next()
.filter(|s| !s.is_empty())
.ok_or_else(|| anyhow!("Invalid ZFS dataset: {dataset}"))?;


let output = Command::new("zpool")
.args(["list", "-H", "-v", "-P", pool])
.run_get_string()
.with_context(|| format!("Querying ZFS pool {pool}"))?;

for line in output.lines() {
if line.starts_with('\t') || line.starts_with(' ') {
let dev_path = line.trim_start().split('\t').next().unwrap_or("").trim();
if dev_path.starts_with('/') {
return list_dev(Utf8Path::new(dev_path));
}
}
}

anyhow::bail!("Could not find a block device backing ZFS pool {pool}")
}

/// List the device containing the filesystem mounted at the given directory.
pub fn list_dev_by_dir(dir: &Dir) -> Result<Device> {
let fsinfo = bootc_mount::inspect_filesystem_of_dir(dir)?;
list_dev(&Utf8PathBuf::from(&fsinfo.source))
let source = &fsinfo.source;
if fsinfo.fstype == "zfs" || (!source.starts_with('/') && source.contains('/')) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The heuristic (!source.starts_with('/') && source.contains('/')) is too broad and will incorrectly match network filesystems like NFS (e.g., server:/export). This would cause bootc to attempt running zpool on non-ZFS systems, which might fail if the zpool command is missing or the source is not a valid pool. It is recommended to rely primarily on fsinfo.fstype == "zfs" or a more specific prefix like ZFS=.

Suggested change
if fsinfo.fstype == "zfs" || (!source.starts_with('/') && source.contains('/')) {
if fsinfo.fstype == "zfs" || source.starts_with("ZFS=") {

return list_dev_for_zfs_dataset(source);
}
list_dev(&Utf8PathBuf::from(source))
}

pub struct LoopbackDevice {
Expand Down
Loading