Skip to content

Commit

Permalink
Merge pull request #1050 from cgwalters/blockdev-crate
Browse files Browse the repository at this point in the history
Split off a blockdev internal crate
  • Loading branch information
HuijingHei authored Jan 23, 2025
2 parents b086321 + 4f3e556 commit 9a58693
Show file tree
Hide file tree
Showing 9 changed files with 102 additions and 68 deletions.
16 changes: 16 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[workspace]
members = ["cli", "lib", "ostree-ext", "xtask", "tests-integration"]
members = ["cli", "lib", "ostree-ext", "blockdev", "xtask", "tests-integration"]
resolver = "2"

[profile.dev]
Expand Down
25 changes: 25 additions & 0 deletions blockdev/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
[package]
description = "Internal blockdev code"
# Should never be published to crates.io
publish = false
edition = "2021"
license = "MIT OR Apache-2.0"
name = "blockdev"
repository = "https://github.com/containers/bootc"
version = "0.0.0"

[dependencies]
anyhow = { workspace = true }
bootc-utils = { path = "../utils" }
camino = { workspace = true, features = ["serde1"] }
fn-error-context = { workspace = true }
regex = "1.10.4"
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
tracing = { workspace = true }

[dev-dependencies]
indoc = "2.0.5"

[lib]
path = "src/blockdev.rs"
97 changes: 45 additions & 52 deletions lib/src/blockdev.rs → blockdev/src/blockdev.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
use std::collections::HashMap;
#[cfg(feature = "install-to-disk")]
use std::env;
#[cfg(feature = "install-to-disk")]
use std::path::Path;
use std::process::Command;
use std::sync::OnceLock;

use anyhow::{anyhow, Context, Result};
use camino::Utf8Path;
#[cfg(feature = "install-to-disk")]
use camino::Utf8PathBuf;
use fn_error_context::context;
use regex::Regex;
Expand All @@ -23,34 +20,34 @@ struct DevicesOutput {

#[allow(dead_code)]
#[derive(Debug, Deserialize)]
pub(crate) struct Device {
pub(crate) name: String,
pub(crate) serial: Option<String>,
pub(crate) model: Option<String>,
pub(crate) partlabel: Option<String>,
pub(crate) children: Option<Vec<Device>>,
pub(crate) size: u64,
pub struct Device {
pub name: String,
pub serial: Option<String>,
pub model: Option<String>,
pub partlabel: Option<String>,
pub children: Option<Vec<Device>>,
pub size: u64,
#[serde(rename = "maj:min")]
pub(crate) maj_min: Option<String>,
pub maj_min: Option<String>,
// NOTE this one is not available on older util-linux, and
// will also not exist for whole blockdevs (as opposed to partitions).
pub(crate) start: Option<u64>,
pub start: Option<u64>,

// Filesystem-related properties
pub(crate) label: Option<String>,
pub(crate) fstype: Option<String>,
pub(crate) path: Option<String>,
pub label: Option<String>,
pub fstype: Option<String>,
pub path: Option<String>,
}

impl Device {
#[allow(dead_code)]
// RHEL8's lsblk doesn't have PATH, so we do it
pub(crate) fn path(&self) -> String {
pub fn path(&self) -> String {
self.path.clone().unwrap_or(format!("/dev/{}", &self.name))
}

#[allow(dead_code)]
pub(crate) fn has_children(&self) -> bool {
pub fn has_children(&self) -> bool {
self.children.as_ref().map_or(false, |v| !v.is_empty())
}

Expand All @@ -77,7 +74,7 @@ impl Device {
}

/// Older versions of util-linux may be missing some properties. Backfill them if they're missing.
pub(crate) fn backfill_missing(&mut self) -> Result<()> {
pub fn backfill_missing(&mut self) -> Result<()> {
// Add new properties to backfill here
self.backfill_start()?;
// And recurse to child devices
Expand All @@ -89,7 +86,7 @@ impl Device {
}

#[context("Listing device {dev}")]
pub(crate) fn list_dev(dev: &Utf8Path) -> Result<Device> {
pub fn list_dev(dev: &Utf8Path) -> Result<Device> {
let mut devs: DevicesOutput = Command::new("lsblk")
.args(["-J", "-b", "-O"])
.arg(dev)
Expand All @@ -111,52 +108,52 @@ struct SfDiskOutput {

#[derive(Debug, Deserialize)]
#[allow(dead_code)]
pub(crate) struct Partition {
pub(crate) node: String,
pub(crate) start: u64,
pub(crate) size: u64,
pub struct Partition {
pub node: String,
pub start: u64,
pub size: u64,
#[serde(rename = "type")]
pub(crate) parttype: String,
pub(crate) uuid: Option<String>,
pub(crate) name: Option<String>,
pub parttype: String,
pub uuid: Option<String>,
pub name: Option<String>,
}

#[derive(Debug, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
pub(crate) enum PartitionType {
pub enum PartitionType {
Dos,
Gpt,
Unknown(String),
}

#[derive(Debug, Deserialize)]
#[allow(dead_code)]
pub(crate) struct PartitionTable {
pub(crate) label: PartitionType,
pub(crate) id: String,
pub(crate) device: String,
pub struct PartitionTable {
pub label: PartitionType,
pub id: String,
pub device: String,
// We're not using these fields
// pub(crate) unit: String,
// pub(crate) firstlba: u64,
// pub(crate) lastlba: u64,
// pub(crate) sectorsize: u64,
pub(crate) partitions: Vec<Partition>,
// pub unit: String,
// pub firstlba: u64,
// pub lastlba: u64,
// pub sectorsize: u64,
pub partitions: Vec<Partition>,
}

impl PartitionTable {
/// Find the partition with the given device name
#[allow(dead_code)]
pub(crate) fn find<'a>(&'a self, devname: &str) -> Option<&'a Partition> {
pub fn find<'a>(&'a self, devname: &str) -> Option<&'a Partition> {
self.partitions.iter().find(|p| p.node.as_str() == devname)
}

pub(crate) fn path(&self) -> &Utf8Path {
pub fn path(&self) -> &Utf8Path {
self.device.as_str().into()
}

// Find the partition with the given offset (starting at 1)
#[allow(dead_code)]
pub(crate) fn find_partno(&self, partno: u32) -> Result<&Partition> {
pub fn find_partno(&self, partno: u32) -> Result<&Partition> {
let r = self
.partitions
.get(partno.checked_sub(1).expect("1 based partition offset") as usize)
Expand All @@ -167,28 +164,26 @@ impl PartitionTable {

impl Partition {
#[allow(dead_code)]
pub(crate) fn path(&self) -> &Utf8Path {
pub fn path(&self) -> &Utf8Path {
self.node.as_str().into()
}
}

#[context("Listing partitions of {dev}")]
pub(crate) fn partitions_of(dev: &Utf8Path) -> Result<PartitionTable> {
pub fn partitions_of(dev: &Utf8Path) -> Result<PartitionTable> {
let o: SfDiskOutput = Command::new("sfdisk")
.args(["-J", dev.as_str()])
.run_and_parse_json()?;
Ok(o.partitiontable)
}

#[cfg(feature = "install-to-disk")]
pub(crate) struct LoopbackDevice {
pub(crate) dev: Option<Utf8PathBuf>,
pub struct LoopbackDevice {
pub dev: Option<Utf8PathBuf>,
}

#[cfg(feature = "install-to-disk")]
impl LoopbackDevice {
// Create a new loopback block device targeting the provided file path.
pub(crate) fn new(path: &Path) -> Result<Self> {
pub fn new(path: &Path) -> Result<Self> {
let direct_io = match env::var("BOOTC_DIRECT_IO") {
Ok(val) => {
if val == "on" {
Expand All @@ -215,7 +210,7 @@ impl LoopbackDevice {
}

// Access the path to the loopback block device.
pub(crate) fn path(&self) -> &Utf8Path {
pub fn path(&self) -> &Utf8Path {
// SAFETY: The option cannot be destructured until we are dropped
self.dev.as_deref().unwrap()
}
Expand All @@ -231,12 +226,11 @@ impl LoopbackDevice {
}

/// Consume this device, unmounting it.
pub(crate) fn close(mut self) -> Result<()> {
pub fn close(mut self) -> Result<()> {
self.impl_close()
}
}

#[cfg(feature = "install-to-disk")]
impl Drop for LoopbackDevice {
fn drop(&mut self) {
// Best effort to unmount if we're dropped without invoking `close`
Expand All @@ -259,7 +253,7 @@ fn split_lsblk_line(line: &str) -> HashMap<String, String> {
/// This is a bit fuzzy, but... this function will return every block device in the parent
/// hierarchy of `device` capable of containing other partitions. So e.g. parent devices of type
/// "part" doesn't match, but "disk" and "mpath" does.
pub(crate) fn find_parent_devices(device: &str) -> Result<Vec<String>> {
pub fn find_parent_devices(device: &str) -> Result<Vec<String>> {
let output = Command::new("lsblk")
// Older lsblk, e.g. in CentOS 7.6, doesn't support PATH, but --paths option
.arg("--pairs")
Expand Down Expand Up @@ -291,8 +285,7 @@ pub(crate) fn find_parent_devices(device: &str) -> Result<Vec<String>> {
}

/// Parse a string into mibibytes
#[cfg(feature = "install-to-disk")]
pub(crate) fn parse_size_mib(mut s: &str) -> Result<u64> {
pub fn parse_size_mib(mut s: &str) -> Result<u64> {
let suffixes = [
("MiB", 1u64),
("M", 1u64),
Expand Down
1 change: 1 addition & 0 deletions lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ anstream = "0.6.13"
anstyle = "1.0.6"
anyhow = { workspace = true }
bootc-utils = { path = "../utils" }
bootc-blockdev = { path = "../blockdev", package = "blockdev" }
camino = { workspace = true, features = ["serde1"] }
ostree-ext = { path = "../ostree-ext" }
chrono = { workspace = true, features = ["serde"] }
Expand Down
4 changes: 2 additions & 2 deletions lib/src/bootloader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ use anyhow::{anyhow, bail, Context, Result};
use camino::{Utf8Path, Utf8PathBuf};
use fn_error_context::context;

use crate::blockdev::PartitionTable;
use crate::task::Task;
use bootc_blockdev::PartitionTable;

/// The name of the mountpoint for efi (as a subdirectory of /boot, or at the toplevel)
pub(crate) const EFI_DIR: &str = "efi";
Expand Down Expand Up @@ -69,7 +69,7 @@ pub(crate) fn install_via_zipl(device: &PartitionTable, boot_uuid: &str) -> Resu
// Ensure that the found partition is a part of the target device
let device_path = device.path();

let partitions = crate::blockdev::list_dev(device_path)?
let partitions = bootc_blockdev::list_dev(device_path)?
.children
.with_context(|| format!("no partition found on {device_path}"))?;
let boot_part = partitions
Expand Down
14 changes: 7 additions & 7 deletions lib/src/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -884,7 +884,7 @@ pub(crate) fn exec_in_host_mountns(args: &[std::ffi::OsString]) -> Result<()> {
pub(crate) struct RootSetup {
#[cfg(feature = "install-to-disk")]
luks_device: Option<String>,
device_info: crate::blockdev::PartitionTable,
device_info: bootc_blockdev::PartitionTable,
/// Absolute path to the location where we've mounted the physical
/// root filesystem for the system we're installing.
physical_root_path: Utf8PathBuf,
Expand Down Expand Up @@ -1398,13 +1398,13 @@ async fn install_to_filesystem_impl(state: &State, rootfs: &mut RootSetup) -> Re
let rootfs = &*rootfs;

match &rootfs.device_info.label {
crate::blockdev::PartitionType::Dos => crate::utils::medium_visibility_warning(
bootc_blockdev::PartitionType::Dos => crate::utils::medium_visibility_warning(
"Installing to `dos` format partitions is not recommended",
),
crate::blockdev::PartitionType::Gpt => {
bootc_blockdev::PartitionType::Gpt => {
// The only thing we should be using in general
}
crate::blockdev::PartitionType::Unknown(o) => {
bootc_blockdev::PartitionType::Unknown(o) => {
crate::utils::medium_visibility_warning(&format!("Unknown partition label {o}"))
}
}
Expand Down Expand Up @@ -1480,7 +1480,7 @@ pub(crate) async fn install_to_disk(mut opts: InstallToDiskOpts) -> Result<()> {
let (mut rootfs, loopback) = {
let loopback_dev = if opts.via_loopback {
let loopback_dev =
crate::blockdev::LoopbackDevice::new(block_opts.device.as_std_path())?;
bootc_blockdev::LoopbackDevice::new(block_opts.device.as_std_path())?;
block_opts.device = loopback_dev.path().into();
Some(loopback_dev)
} else {
Expand Down Expand Up @@ -1811,7 +1811,7 @@ pub(crate) async fn install_to_filesystem(
let mut dev = inspect.source;
loop {
tracing::debug!("Finding parents for {dev}");
let mut parents = crate::blockdev::find_parent_devices(&dev)?.into_iter();
let mut parents = bootc_blockdev::find_parent_devices(&dev)?.into_iter();
let Some(parent) = parents.next() else {
break;
};
Expand All @@ -1825,7 +1825,7 @@ pub(crate) async fn install_to_filesystem(
dev
};
tracing::debug!("Backing device: {backing_device}");
let device_info = crate::blockdev::partitions_of(Utf8Path::new(&backing_device))?;
let device_info = bootc_blockdev::partitions_of(Utf8Path::new(&backing_device))?;

let rootarg = format!("root={}", root_info.mount_spec);
let mut boot = if let Some(spec) = fsopts.boot_mount_spec {
Expand Down
Loading

0 comments on commit 9a58693

Please sign in to comment.