Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add bootc-blockdev and local mod blockdev #824

Merged
merged 4 commits into from
Feb 6, 2025
Merged
Show file tree
Hide file tree
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
21 changes: 20 additions & 1 deletion Cargo.lock

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

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"] }
Expand Down
125 changes: 24 additions & 101 deletions src/bios.rs
Original file line number Diff line number Diff line change
@@ -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<String>,
parttypename: Option<String>,
}

#[derive(Serialize, Deserialize, Debug)]
struct Devices {
blockdevices: Vec<BlockDevice>,
}

#[derive(Default)]
pub(crate) struct Bios {}

impl Bios {
// get target device for running update
fn get_device(&self) -> Result<String> {
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<bool> {
let usr_path = Path::new("/usr/lib/grub");
Expand Down Expand Up @@ -115,37 +68,17 @@ impl Bios {
}

// check bios_boot partition on gpt type disk
fn get_bios_boot_partition(&self) -> Result<Option<String>> {
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::<Devices>(&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<String> {
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
}
}

Expand Down Expand Up @@ -187,7 +120,7 @@ impl Component for Bios {

fn query_adopt(&self) -> Result<Option<Adoptable>> {
#[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);
}
Expand All @@ -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,
Expand All @@ -215,9 +149,13 @@ impl Component for Bios {

fn run_update(&self, sysroot: &openat::Dir, _: &InstalledContent) -> Result<InstalledContent> {
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 {
Expand All @@ -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());
}
}
101 changes: 101 additions & 0 deletions src/blockdev.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
use camino::Utf8Path;
use std::path::Path;

use anyhow::{bail, Context, Result};
use bootc_blockdev::PartitionTable;
use fn_error_context::context;

#[context("get parent devices from mount point boot")]
pub fn get_devices<P: AsRef<Path>>(target_root: P) -> Result<Vec<String>> {
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<P: AsRef<Path>>(target_root: P) -> Result<String> {
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
#[allow(dead_code)]
pub fn get_esp_partition(device: &str) -> Result<Option<String>> {
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 let Some(esp) = esp {
return Ok(Some(esp.node));
}
Ok(None)
}

/// Find all ESP partitions on the devices with mountpoint boot
#[allow(dead_code)]
pub fn find_colocated_esps<P: AsRef<Path>>(target_root: P) -> Result<Vec<String>> {
// 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<Option<String>> {
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 let Some(bios_boot) = bios_boot {
return Ok(Some(bios_boot.node));
}
Ok(None)
}

/// Find all bios_boot partitions on the devices with mountpoint boot
#[allow(dead_code)]
pub fn find_colocated_bios_boot<P: AsRef<Path>>(target_root: P) -> Result<Vec<String>> {
// 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)
}
1 change: 1 addition & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading