Skip to content

Commit

Permalink
install/blockdev: Break cyclic build dependency
Browse files Browse the repository at this point in the history
The install code and the blockdev code call each other.
Clean up blockdev to only use `bootc-utils`.
Prep for splitting out the blockdev stuff into at least a
separate internal crate; ref
coreos/bootupd#820

Signed-off-by: Colin Walters <[email protected]>
  • Loading branch information
cgwalters committed Jan 20, 2025
1 parent 4210808 commit b23777f
Show file tree
Hide file tree
Showing 3 changed files with 45 additions and 43 deletions.
47 changes: 7 additions & 40 deletions lib/src/blockdev.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,6 @@ use fn_error_context::context;
use regex::Regex;
use serde::Deserialize;

#[cfg(feature = "install-to-disk")]
use crate::install::run_in_host_mountns;
use crate::task::Task;
use bootc_utils::CommandRunExt;

#[derive(Debug, Deserialize)]
Expand Down Expand Up @@ -91,16 +88,6 @@ impl Device {
}
}

#[context("Failed to wipe {dev}")]
#[cfg(feature = "install-to-disk")]
pub(crate) fn wipefs(dev: &Utf8Path) -> Result<()> {
Task::new_and_run(
format!("Wiping device {dev}"),
"wipefs",
["-a", dev.as_str()],
)
}

#[context("Listing device {dev}")]
pub(crate) fn list_dev(dev: &Utf8Path) -> Result<Device> {
let mut devs: DevicesOutput = Command::new("lsblk")
Expand Down Expand Up @@ -187,10 +174,9 @@ impl Partition {

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

Expand All @@ -214,16 +200,15 @@ impl LoopbackDevice {
Err(_e) => "off",
};

let dev = Task::new("losetup", "losetup")
let dev = Command::new("losetup")
.args([
"--show",
format!("--direct-io={direct_io}").as_str(),
"-P",
"--find",
])
.arg(path)
.quiet()
.read()?;
.run_get_string()?;
let dev = Utf8PathBuf::from(dev.trim());
tracing::debug!("Allocated loopback {dev}");
Ok(Self { dev: Some(dev) })
Expand All @@ -242,10 +227,7 @@ impl LoopbackDevice {
tracing::trace!("loopback device already deallocated");
return Ok(());
};
Task::new("losetup", "losetup")
.args(["-d", dev.as_str()])
.quiet()
.run()
Command::new("losetup").args(["-d", dev.as_str()]).run()
}

/// Consume this device, unmounting it.
Expand All @@ -262,21 +244,6 @@ impl Drop for LoopbackDevice {
}
}

#[cfg(feature = "install-to-disk")]
pub(crate) fn udev_settle() -> Result<()> {
// There's a potential window after rereading the partition table where
// udevd hasn't yet received updates from the kernel, settle will return
// immediately, and lsblk won't pick up partition labels. Try to sleep
// our way out of this.
std::thread::sleep(std::time::Duration::from_millis(200));

let st = run_in_host_mountns("udevadm").arg("settle").status()?;
if !st.success() {
anyhow::bail!("Failed to run udevadm settle: {st:?}");
}
Ok(())
}

/// Parse key-value pairs from lsblk --pairs.
/// Newer versions of lsblk support JSON but the one in CentOS 7 doesn't.
fn split_lsblk_line(line: &str) -> HashMap<String, String> {
Expand All @@ -293,15 +260,15 @@ fn split_lsblk_line(line: &str) -> HashMap<String, String> {
/// 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>> {
let output = Task::new_quiet("lsblk")
let output = Command::new("lsblk")
// Older lsblk, e.g. in CentOS 7.6, doesn't support PATH, but --paths option
.arg("--pairs")
.arg("--paths")
.arg("--inverse")
.arg("--output")
.arg("NAME,TYPE")
.arg(device)
.read()?;
.run_get_string()?;
let mut parents = Vec::new();
// skip first line, which is the device itself
for line in output.lines().skip(1) {
Expand Down
31 changes: 28 additions & 3 deletions lib/src/install/baseline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,31 @@ fn mkfs<'a>(
Ok(u)
}

#[context("Failed to wipe {dev}")]
pub(crate) fn wipefs(dev: &Utf8Path) -> Result<()> {
Task::new_and_run(
format!("Wiping device {dev}"),
"wipefs",
["-a", dev.as_str()],
)
}

pub(crate) fn udev_settle() -> Result<()> {
// There's a potential window after rereading the partition table where
// udevd hasn't yet received updates from the kernel, settle will return
// immediately, and lsblk won't pick up partition labels. Try to sleep
// our way out of this.
std::thread::sleep(std::time::Duration::from_millis(200));

let st = super::run_in_host_mountns("udevadm")
.arg("settle")
.status()?;
if !st.success() {
anyhow::bail!("Failed to run udevadm settle: {st:?}");
}
Ok(())
}

#[context("Creating rootfs")]
#[cfg(feature = "install-to-disk")]
pub(crate) fn install_create_rootfs(
Expand Down Expand Up @@ -164,10 +189,10 @@ pub(crate) fn install_create_rootfs(
for child in device.children.iter().flatten() {
let child = child.path();
println!("Wiping {child}");
crate::blockdev::wipefs(Utf8Path::new(&child))?;
wipefs(Utf8Path::new(&child))?;
}
println!("Wiping {dev}");
crate::blockdev::wipefs(dev)?;
wipefs(dev)?;
} else if device.has_children() {
anyhow::bail!(
"Detected existing partitions on {}; use e.g. `wipefs` or --wipe if you intend to overwrite",
Expand Down Expand Up @@ -289,7 +314,7 @@ pub(crate) fn install_create_rootfs(

// Full udev sync; it'd obviously be better to await just the devices
// we're targeting, but this is a simple coarse hammer.
crate::blockdev::udev_settle()?;
udev_settle()?;

// Re-read what we wrote into structured information
let base_partitions = &crate::blockdev::partitions_of(&devpath)?;
Expand Down
10 changes: 10 additions & 0 deletions utils/src/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ pub trait CommandRunExt {
/// and will return an error if the child process exits abnormally.
fn run_get_output(&mut self) -> Result<Box<dyn std::io::BufRead>>;

/// Execute the child process and capture its output as a string.
fn run_get_string(&mut self) -> Result<String>;

/// Execute the child process, parsing its stdout as JSON. This uses `run` internally
/// and will return an error if the child process exits abnormally.
fn run_and_parse_json<T: serde::de::DeserializeOwned>(&mut self) -> Result<T>;
Expand Down Expand Up @@ -118,6 +121,13 @@ impl CommandRunExt for Command {
Ok(Box::new(std::io::BufReader::new(stdout)))
}

fn run_get_string(&mut self) -> Result<String> {
let mut s = String::new();
let mut o = self.run_get_output()?;
o.read_to_string(&mut s)?;
Ok(s)
}

/// Synchronously execute the child, and parse its stdout as JSON.
fn run_and_parse_json<T: serde::de::DeserializeOwned>(&mut self) -> Result<T> {
let output = self.run_get_output()?;
Expand Down

0 comments on commit b23777f

Please sign in to comment.