From c56e1df209f59b2b8f7d8528450d222f6157d046 Mon Sep 17 00:00:00 2001 From: Huijing Hei Date: Fri, 24 Jan 2025 17:41:53 +0800 Subject: [PATCH 1/4] Add `bootc-blockdev` and local crate `blockdev` See https://github.com/containers/bootc/pull/1050 --- Cargo.lock | 21 +++++++++- Cargo.toml | 3 +- src/blockdev.rs | 103 ++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 1 + 4 files changed, 126 insertions(+), 2 deletions(-) create mode 100644 src/blockdev.rs diff --git a/Cargo.lock b/Cargo.lock index 8c7dd3ea..4ad9cb45 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -153,10 +153,25 @@ dependencies = [ "generic-array", ] +[[package]] +name = "blockdev" +version = "0.0.0" +source = "git+https://github.com/containers/bootc?rev=9a586935e3c88a3802ea4308b0ec364b6448c59e#9a586935e3c88a3802ea4308b0ec364b6448c59e" +dependencies = [ + "anyhow", + "bootc-utils", + "camino", + "fn-error-context", + "regex", + "serde", + "serde_json", + "tracing", +] + [[package]] name = "bootc-utils" version = "0.0.0" -source = "git+https://github.com/containers/bootc#e8bb9f748fdfeb5164667046b3c6678c619ad46c" +source = "git+https://github.com/containers/bootc?rev=9a586935e3c88a3802ea4308b0ec364b6448c59e#9a586935e3c88a3802ea4308b0ec364b6448c59e" dependencies = [ "anyhow", "rustix", @@ -173,6 +188,7 @@ version = "0.2.26" dependencies = [ "anyhow", "bincode", + "blockdev", "bootc-utils", "camino", "cap-std-ext", @@ -217,6 +233,9 @@ name = "camino" version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3" +dependencies = [ + "serde", +] [[package]] name = "cap-primitives" diff --git a/Cargo.toml b/Cargo.toml index 2ceb6a70..05c053c2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,8 @@ path = "src/main.rs" [dependencies] anyhow = "1.0" bincode = "1.3.2" -bootc-utils = { git = "https://github.com/containers/bootc", commit = "e8bb9f748fdfeb5164667046b3c6678c619ad46c" } +bootc-blockdev = { git = "https://github.com/containers/bootc", rev = "9a586935e3c88a3802ea4308b0ec364b6448c59e", package = "blockdev" } +bootc-utils = { git = "https://github.com/containers/bootc", rev = "9a586935e3c88a3802ea4308b0ec364b6448c59e" } cap-std-ext = "4.0.4" camino = "1.1.9" chrono = { version = "0.4.39", features = ["serde"] } diff --git a/src/blockdev.rs b/src/blockdev.rs new file mode 100644 index 00000000..6a1ae796 --- /dev/null +++ b/src/blockdev.rs @@ -0,0 +1,103 @@ +use camino::Utf8Path; +use std::path::Path; + +use anyhow::{bail, Context, Result}; +use fn_error_context::context; +use bootc_blockdev::PartitionTable; + +#[context("get parent devices from mount point boot")] +pub fn get_devices>(target_root: P) -> Result> { + let target_root = target_root.as_ref(); + let bootdir = target_root.join("boot"); + if !bootdir.exists() { + bail!("{} does not exist", bootdir.display()); + } + let bootdir = openat::Dir::open(&bootdir)?; + // Run findmnt to get the source path of mount point boot + let fsinfo = crate::filesystem::inspect_filesystem(&bootdir, ".")?; + // Find the parent devices of the source path + let parent_devices = bootc_blockdev::find_parent_devices(&fsinfo.source) + .with_context(|| format!("while looking for backing devices of {}", fsinfo.source))?; + log::debug!("Find parent devices: {parent_devices:?}"); + Ok(parent_devices) +} + +// Get single device for the target root +pub fn get_single_device>(target_root: P) -> Result { + let mut devices = get_devices(&target_root)?.into_iter(); + let Some(parent) = devices.next() else { + anyhow::bail!( + "Failed to find parent device" + ); + }; + + if let Some(next) = devices.next() { + anyhow::bail!( + "Found multiple parent devices {parent} and {next}; not currently supported" + ); + } + Ok(parent) +} + +/// Find esp partition on the same device +/// using sfdisk to get partitiontable +pub fn get_esp_partition(device: &str) -> Result> { + const ESP_TYPE_GUID: &str = "C12A7328-F81F-11D2-BA4B-00A0C93EC93B"; + let device_info: PartitionTable = bootc_blockdev::partitions_of(Utf8Path::new(device))?; + let esp = device_info + .partitions + .into_iter() + .find(|p| p.parttype.as_str() == ESP_TYPE_GUID); + if esp.is_some() { + return Ok(Some(esp.unwrap().node)); + } + log::debug!("Not found any esp partition"); + Ok(None) +} + +/// Find all ESP partitions on the devices with mountpoint boot +pub fn find_colocated_esps>(target_root: P) -> Result> { + // first, get the parent device + let devices = get_devices(&target_root).with_context(|| "while looking for colocated ESPs")?; + + // now, look for all ESPs on those devices + let mut esps = Vec::new(); + for device in devices { + if let Some(esp) = get_esp_partition(&device)? { + esps.push(esp) + } + } + log::debug!("Find esp partitions: {esps:?}"); + Ok(esps) +} + +/// Find bios_boot partition on the same device +pub fn get_bios_boot_partition(device: &str) -> Result> { + const BIOS_BOOT_TYPE_GUID: &str = "21686148-6449-6E6F-744E-656564454649"; + let device_info = bootc_blockdev::partitions_of(Utf8Path::new(device))?; + let bios_boot = device_info + .partitions + .into_iter() + .find(|p| p.parttype.as_str() == BIOS_BOOT_TYPE_GUID); + if bios_boot.is_some() { + return Ok(Some(bios_boot.unwrap().node)); + } + Ok(None) +} + +/// Find all bios_boot partitions on the devices with mountpoint boot +pub fn find_colocated_bios_boot>(target_root: P) -> Result> { + // first, get the parent device + let devices = + get_devices(&target_root).with_context(|| "looking for colocated bios_boot parts")?; + + // now, look for all bios_boot parts on those devices + let mut bios_boots = Vec::new(); + for device in devices { + if let Some(bios) = get_bios_boot_partition(&device)? { + bios_boots.push(bios) + } + } + log::debug!("Find bios_boot partitions: {bios_boots:?}"); + Ok(bios_boots) +} diff --git a/src/main.rs b/src/main.rs index 7c7cb40c..ac85381f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -18,6 +18,7 @@ Refs: mod backend; #[cfg(any(target_arch = "x86_64", target_arch = "powerpc64"))] mod bios; +mod blockdev; mod bootupd; mod cli; mod component; From 9edab50eb6454d474847a07e9646692b69af97b6 Mon Sep 17 00:00:00 2001 From: Huijing Hei Date: Fri, 24 Jan 2025 18:03:38 +0800 Subject: [PATCH 2/4] Add `bootc-blockdev` and local crate `blockdev` See https://github.com/containers/bootc/pull/1050 --- src/blockdev.rs | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/src/blockdev.rs b/src/blockdev.rs index 6a1ae796..0dda293c 100644 --- a/src/blockdev.rs +++ b/src/blockdev.rs @@ -2,8 +2,8 @@ use camino::Utf8Path; use std::path::Path; use anyhow::{bail, Context, Result}; -use fn_error_context::context; use bootc_blockdev::PartitionTable; +use fn_error_context::context; #[context("get parent devices from mount point boot")] pub fn get_devices>(target_root: P) -> Result> { @@ -26,15 +26,11 @@ pub fn get_devices>(target_root: P) -> Result> { pub fn get_single_device>(target_root: P) -> Result { let mut devices = get_devices(&target_root)?.into_iter(); let Some(parent) = devices.next() else { - anyhow::bail!( - "Failed to find parent device" - ); + anyhow::bail!("Failed to find parent device"); }; if let Some(next) = devices.next() { - anyhow::bail!( - "Found multiple parent devices {parent} and {next}; not currently supported" - ); + anyhow::bail!("Found multiple parent devices {parent} and {next}; not currently supported"); } Ok(parent) } @@ -48,10 +44,9 @@ pub fn get_esp_partition(device: &str) -> Result> { .partitions .into_iter() .find(|p| p.parttype.as_str() == ESP_TYPE_GUID); - if esp.is_some() { - return Ok(Some(esp.unwrap().node)); + if let Some(esp) = esp { + return Ok(Some(esp.node)); } - log::debug!("Not found any esp partition"); Ok(None) } @@ -79,8 +74,8 @@ pub fn get_bios_boot_partition(device: &str) -> Result> { .partitions .into_iter() .find(|p| p.parttype.as_str() == BIOS_BOOT_TYPE_GUID); - if bios_boot.is_some() { - return Ok(Some(bios_boot.unwrap().node)); + if let Some(bios_boot) = bios_boot { + return Ok(Some(bios_boot.node)); } Ok(None) } From 2d0d9555c853dc4d9cbdc7261c760de765ddd8bf Mon Sep 17 00:00:00 2001 From: Huijing Hei Date: Fri, 24 Jan 2025 18:04:58 +0800 Subject: [PATCH 3/4] blockdev.rs: Add `#[allow(dead_code)]` for the unused functions --- src/blockdev.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/blockdev.rs b/src/blockdev.rs index 0dda293c..d2322f4b 100644 --- a/src/blockdev.rs +++ b/src/blockdev.rs @@ -37,6 +37,7 @@ pub fn get_single_device>(target_root: P) -> Result { /// Find esp partition on the same device /// using sfdisk to get partitiontable +#[allow(dead_code)] pub fn get_esp_partition(device: &str) -> Result> { const ESP_TYPE_GUID: &str = "C12A7328-F81F-11D2-BA4B-00A0C93EC93B"; let device_info: PartitionTable = bootc_blockdev::partitions_of(Utf8Path::new(device))?; @@ -51,6 +52,7 @@ pub fn get_esp_partition(device: &str) -> Result> { } /// Find all ESP partitions on the devices with mountpoint boot +#[allow(dead_code)] pub fn find_colocated_esps>(target_root: P) -> Result> { // first, get the parent device let devices = get_devices(&target_root).with_context(|| "while looking for colocated ESPs")?; @@ -81,6 +83,7 @@ pub fn get_bios_boot_partition(device: &str) -> Result> { } /// Find all bios_boot partitions on the devices with mountpoint boot +#[allow(dead_code)] pub fn find_colocated_bios_boot>(target_root: P) -> Result> { // first, get the parent device let devices = From 07c3661512b99b8ae606efe784353858409a99d0 Mon Sep 17 00:00:00 2001 From: Huijing Hei Date: Fri, 24 Jan 2025 18:05:09 +0800 Subject: [PATCH 4/4] bios.rs: update functions to use `blockdev` --- src/bios.rs | 125 ++++++++++------------------------------------------ 1 file changed, 24 insertions(+), 101 deletions(-) diff --git a/src/bios.rs b/src/bios.rs index f8c644e4..2e85f01c 100644 --- a/src/bios.rs +++ b/src/bios.rs @@ -1,68 +1,21 @@ +use anyhow::{bail, Result}; use std::io::prelude::*; +use std::os::unix::io::AsRawFd; use std::path::Path; use std::process::Command; +use crate::blockdev; use crate::component::*; use crate::model::*; use crate::packagesystem; -use anyhow::{bail, Result}; - -use crate::util; -use serde::{Deserialize, Serialize}; // grub2-install file path pub(crate) const GRUB_BIN: &str = "usr/sbin/grub2-install"; -#[derive(Serialize, Deserialize, Debug)] -struct BlockDevice { - path: String, - pttype: Option, - parttypename: Option, -} - -#[derive(Serialize, Deserialize, Debug)] -struct Devices { - blockdevices: Vec, -} - #[derive(Default)] pub(crate) struct Bios {} impl Bios { - // get target device for running update - fn get_device(&self) -> Result { - let mut cmd: Command; - #[cfg(target_arch = "x86_64")] - { - // find /boot partition - cmd = Command::new("findmnt"); - cmd.arg("--noheadings") - .arg("--nofsroot") - .arg("--output") - .arg("SOURCE") - .arg("/boot"); - let partition = util::cmd_output(&mut cmd)?; - - // lsblk to find parent device - cmd = Command::new("lsblk"); - cmd.arg("--paths") - .arg("--noheadings") - .arg("--output") - .arg("PKNAME") - .arg(partition.trim()); - } - - #[cfg(target_arch = "powerpc64")] - { - // get PowerPC-PReP-boot partition - cmd = Command::new("realpath"); - cmd.arg("/dev/disk/by-partlabel/PowerPC-PReP-boot"); - } - - let device = util::cmd_output(&mut cmd)?; - Ok(device) - } - // Return `true` if grub2-modules installed fn check_grub_modules(&self) -> Result { let usr_path = Path::new("/usr/lib/grub"); @@ -115,37 +68,17 @@ impl Bios { } // check bios_boot partition on gpt type disk - fn get_bios_boot_partition(&self) -> Result> { - let target = self.get_device()?; - // lsblk to list children with bios_boot - let output = Command::new("lsblk") - .args([ - "--json", - "--output", - "PATH,PTTYPE,PARTTYPENAME", - target.trim(), - ]) - .output()?; - if !output.status.success() { - std::io::stderr().write_all(&output.stderr)?; - bail!("Failed to run lsblk"); - } - - let output = String::from_utf8(output.stdout)?; - // Parse the JSON string into the `Devices` struct - let Ok(devices) = serde_json::from_str::(&output) else { - bail!("Could not deserialize JSON output from lsblk"); - }; - - // Find the device with the parttypename "BIOS boot" - for device in devices.blockdevices { - if let Some(parttypename) = &device.parttypename { - if parttypename == "BIOS boot" && device.pttype.as_deref() == Some("gpt") { - return Ok(Some(device.path)); - } + fn get_bios_boot_partition(&self) -> Option { + match blockdev::get_single_device("/") { + Ok(device) => { + let bios_boot_part = + blockdev::get_bios_boot_partition(&device).expect("get bios_boot part"); + return bios_boot_part; } + Err(e) => log::warn!("Get error: {}", e), } - Ok(None) + log::debug!("Not found any bios_boot partition"); + None } } @@ -187,7 +120,7 @@ impl Component for Bios { fn query_adopt(&self) -> Result> { #[cfg(target_arch = "x86_64")] - if crate::efi::is_efi_booted()? && self.get_bios_boot_partition()?.is_none() { + if crate::efi::is_efi_booted()? && self.get_bios_boot_partition().is_none() { log::debug!("Skip BIOS adopt"); return Ok(None); } @@ -199,9 +132,10 @@ impl Component for Bios { anyhow::bail!("Failed to find adoptable system") }; - let device = self.get_device()?; - let device = device.trim(); - self.run_grub_install("/", device)?; + let target_root = "/"; + let device = blockdev::get_single_device(&target_root)?; + self.run_grub_install(target_root, &device)?; + log::debug!("Install grub modules on {device}"); Ok(InstalledContent { meta: update.clone(), filetree: None, @@ -215,9 +149,13 @@ impl Component for Bios { fn run_update(&self, sysroot: &openat::Dir, _: &InstalledContent) -> Result { let updatemeta = self.query_update(sysroot)?.expect("update available"); - let device = self.get_device()?; - let device = device.trim(); - self.run_grub_install("/", device)?; + let dest_fd = format!("/proc/self/fd/{}", sysroot.as_raw_fd()); + let dest_root = std::fs::read_link(dest_fd)?; + let device = blockdev::get_single_device(&dest_root)?; + + let dest_root = dest_root.to_string_lossy().into_owned(); + self.run_grub_install(&dest_root, &device)?; + log::debug!("Install grub modules on {device}"); let adopted_from = None; Ok(InstalledContent { @@ -235,18 +173,3 @@ impl Component for Bios { Ok(None) } } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_deserialize_lsblk_output() { - let data = include_str!("../tests/fixtures/example-lsblk-output.json"); - let devices: Devices = serde_json::from_str(&data).expect("JSON was not well-formatted"); - assert_eq!(devices.blockdevices.len(), 7); - assert_eq!(devices.blockdevices[0].path, "/dev/sr0"); - assert!(devices.blockdevices[0].pttype.is_none()); - assert!(devices.blockdevices[0].parttypename.is_none()); - } -}