diff --git a/Cargo.lock b/Cargo.lock index 0d37ab14..fdd01e00 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -150,6 +150,21 @@ dependencies = [ "generic-array", ] +[[package]] +name = "blockdev" +version = "0.0.0" +dependencies = [ + "anyhow", + "bootc-utils", + "camino", + "fn-error-context", + "indoc", + "regex", + "serde", + "serde_json", + "tracing", +] + [[package]] name = "bootc" version = "0.1.9" @@ -170,6 +185,7 @@ dependencies = [ "anstream", "anstyle", "anyhow", + "blockdev", "bootc-utils", "camino", "cap-std-ext", diff --git a/Cargo.toml b/Cargo.toml index 33fad410..bb82d992 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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] diff --git a/blockdev/Cargo.toml b/blockdev/Cargo.toml new file mode 100644 index 00000000..c73f7723 --- /dev/null +++ b/blockdev/Cargo.toml @@ -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" \ No newline at end of file diff --git a/lib/src/blockdev.rs b/blockdev/src/blockdev.rs similarity index 82% rename from lib/src/blockdev.rs rename to blockdev/src/blockdev.rs index 4c6355c3..f89f0859 100644 --- a/lib/src/blockdev.rs +++ b/blockdev/src/blockdev.rs @@ -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; @@ -23,34 +20,34 @@ struct DevicesOutput { #[allow(dead_code)] #[derive(Debug, Deserialize)] -pub(crate) struct Device { - pub(crate) name: String, - pub(crate) serial: Option, - pub(crate) model: Option, - pub(crate) partlabel: Option, - pub(crate) children: Option>, - pub(crate) size: u64, +pub struct Device { + pub name: String, + pub serial: Option, + pub model: Option, + pub partlabel: Option, + pub children: Option>, + pub size: u64, #[serde(rename = "maj:min")] - pub(crate) maj_min: Option, + pub maj_min: Option, // 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, + pub start: Option, // Filesystem-related properties - pub(crate) label: Option, - pub(crate) fstype: Option, - pub(crate) path: Option, + pub label: Option, + pub fstype: Option, + pub path: Option, } 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()) } @@ -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 @@ -89,7 +86,7 @@ impl Device { } #[context("Listing device {dev}")] -pub(crate) fn list_dev(dev: &Utf8Path) -> Result { +pub fn list_dev(dev: &Utf8Path) -> Result { let mut devs: DevicesOutput = Command::new("lsblk") .args(["-J", "-b", "-O"]) .arg(dev) @@ -111,19 +108,19 @@ 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, - pub(crate) name: Option, + pub parttype: String, + pub uuid: Option, + pub name: Option, } #[derive(Debug, Deserialize, PartialEq, Eq)] #[serde(rename_all = "kebab-case")] -pub(crate) enum PartitionType { +pub enum PartitionType { Dos, Gpt, Unknown(String), @@ -131,32 +128,32 @@ pub(crate) enum PartitionType { #[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, + // pub unit: String, + // pub firstlba: u64, + // pub lastlba: u64, + // pub sectorsize: u64, + pub partitions: Vec, } 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) @@ -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 { +pub fn partitions_of(dev: &Utf8Path) -> Result { 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, +pub struct LoopbackDevice { + pub dev: Option, } -#[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 { + pub fn new(path: &Path) -> Result { let direct_io = match env::var("BOOTC_DIRECT_IO") { Ok(val) => { if val == "on" { @@ -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() } @@ -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` @@ -259,7 +253,7 @@ fn split_lsblk_line(line: &str) -> HashMap { /// 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> { +pub fn find_parent_devices(device: &str) -> Result> { let output = Command::new("lsblk") // Older lsblk, e.g. in CentOS 7.6, doesn't support PATH, but --paths option .arg("--pairs") @@ -291,8 +285,7 @@ pub(crate) fn find_parent_devices(device: &str) -> Result> { } /// Parse a string into mibibytes -#[cfg(feature = "install-to-disk")] -pub(crate) fn parse_size_mib(mut s: &str) -> Result { +pub fn parse_size_mib(mut s: &str) -> Result { let suffixes = [ ("MiB", 1u64), ("M", 1u64), diff --git a/lib/Cargo.toml b/lib/Cargo.toml index 7d17bdd1..c748197c 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -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"] } diff --git a/lib/src/bootloader.rs b/lib/src/bootloader.rs index c079552a..65becea4 100644 --- a/lib/src/bootloader.rs +++ b/lib/src/bootloader.rs @@ -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"; @@ -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 diff --git a/lib/src/install.rs b/lib/src/install.rs index 475f65d6..98112a4e 100644 --- a/lib/src/install.rs +++ b/lib/src/install.rs @@ -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, - 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, @@ -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}")) } } @@ -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 { @@ -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; }; @@ -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 { diff --git a/lib/src/install/baseline.rs b/lib/src/install/baseline.rs index 4d6903e4..e8f02b0b 100644 --- a/lib/src/install/baseline.rs +++ b/lib/src/install/baseline.rs @@ -100,7 +100,7 @@ fn mkfs<'a>( wipe: bool, opts: impl IntoIterator, ) -> Result { - let devinfo = crate::blockdev::list_dev(dev.into())?; + let devinfo = bootc_blockdev::list_dev(dev.into())?; let size = ostree_ext::glib::format_size(devinfo.size); let u = uuid::Uuid::new_v4(); let mut t = Task::new( @@ -174,7 +174,7 @@ pub(crate) fn install_create_rootfs( .ok_or_else(|| anyhow::anyhow!("No root filesystem specified"))?; // Verify that the target is empty (if not already wiped in particular, but it's // also good to verify that the wipe worked) - let device = crate::blockdev::list_dev(&opts.device)?; + let device = bootc_blockdev::list_dev(&opts.device)?; // Canonicalize devpath let devpath: Utf8PathBuf = device.path().into(); @@ -228,7 +228,7 @@ pub(crate) fn install_create_rootfs( let root_size = opts .root_size .as_deref() - .map(crate::blockdev::parse_size_mib) + .map(bootc_blockdev::parse_size_mib) .transpose() .context("Parsing root size")?; @@ -317,7 +317,7 @@ pub(crate) fn install_create_rootfs( udev_settle()?; // Re-read what we wrote into structured information - let base_partitions = &crate::blockdev::partitions_of(&devpath)?; + let base_partitions = &bootc_blockdev::partitions_of(&devpath)?; let root_partition = base_partitions.find_partno(rootpn)?; if root_partition.parttype.as_str() != LINUX_PARTTYPE { @@ -433,7 +433,7 @@ pub(crate) fn install_create_rootfs( BlockSetup::Direct => None, BlockSetup::Tpm2Luks => Some(luks_name.to_string()), }; - let device_info = crate::blockdev::partitions_of(&devpath)?; + let device_info = bootc_blockdev::partitions_of(&devpath)?; Ok(RootSetup { luks_device, device_info, diff --git a/lib/src/lib.rs b/lib/src/lib.rs index a661ba35..85ad8f02 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -30,7 +30,6 @@ mod utils; #[cfg(feature = "docgen")] mod docgen; -mod blockdev; mod bootloader; mod containerenv; mod install;