Skip to content

Commit

Permalink
Merge pull request #671 from jlebon/pr/bind-boot
Browse files Browse the repository at this point in the history
  • Loading branch information
jlebon authored Nov 16, 2021
2 parents 157e0e4 + 0507011 commit 77fb2fd
Show file tree
Hide file tree
Showing 5 changed files with 137 additions and 1 deletion.
12 changes: 12 additions & 0 deletions src/bin/rdcore/cmdline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ use structopt::StructOpt;
pub enum Cmd {
/// Generate rootmap kargs and optionally inject into BLS configs
Rootmap(RootmapConfig),
/// Generate bootmap kargs and binds bootfs to rootfs and GRUB
BindBoot(BindBootConfig),
/// Modify kargs in BLS configs
Kargs(KargsConfig),
/// Copy data from stdin to stdout, checking piecewise hashes
Expand All @@ -56,6 +58,16 @@ pub struct RootmapConfig {
pub root_mount: String,
}

#[derive(Debug, StructOpt)]
pub struct BindBootConfig {
/// Path to rootfs mount
#[structopt(value_name = "ROOT_MOUNT")]
pub root_mount: String,
/// Path to bootfs mount
#[structopt(value_name = "BOOT_MOUNT")]
pub boot_mount: String,
}

#[derive(Debug, StructOpt)]
pub struct KargsConfig {
// see comment block in rootmap command above
Expand Down
1 change: 1 addition & 0 deletions src/bin/rdcore/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ fn main() -> Result<()> {
match Cmd::from_args() {
Cmd::Kargs(c) => kargs::kargs(&c),
Cmd::Rootmap(c) => rootmap::rootmap(&c),
Cmd::BindBoot(c) => rootmap::bind_boot(&c),
Cmd::StreamHash(c) => stream_hash::stream_hash(&c),
Cmd::VerifyUniqueFsLabel(c) => unique_fs::verify_unique_fs(&c),
}
Expand Down
70 changes: 70 additions & 0 deletions src/bin/rdcore/rootmap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -218,3 +218,73 @@ fn get_luks_uuid(device: &Path) -> Result<String> {
.trim()
.into())
}

pub fn bind_boot(config: &BindBootConfig) -> Result<()> {
let boot_mount = Mount::from_existing(&config.boot_mount)?;
let root_mount = Mount::from_existing(&config.root_mount)?;
let boot_uuid = boot_mount.get_filesystem_uuid()?;
let root_uuid = root_mount.get_filesystem_uuid()?;

let kargs = vec![format!("boot=UUID={}", boot_uuid)];
let changed = visit_bls_entry_options(boot_mount.mountpoint(), |orig_options: &str| {
if !orig_options.starts_with("boot=") && !orig_options.contains(" boot=") {
bls_entry_options_delete_and_append_kargs(orig_options, &[], &kargs, &[])
} else {
// boot= karg already exists; let's not add anything
Ok(None)
}
})
.context("appending boot kargs")?;

// put it in /run also for the first boot real root mount
// https://github.com/coreos/fedora-coreos-config/blob/8661649009/overlay.d/05core/usr/lib/systemd/system-generators/coreos-boot-mount-generator#L105-L108
if changed {
let boot_uuid_run = Path::new("/run/coreos/bootfs_uuid");
let parent = boot_uuid_run.parent().unwrap();
std::fs::create_dir_all(parent)
.with_context(|| format!("creating {}", parent.display()))?;
std::fs::write(boot_uuid_run, format!("{}\n", &boot_uuid))
.with_context(|| format!("writing {}", boot_uuid_run.display()))?;
}

// bind rootfs to bootfs
let root_uuid_stamp = boot_mount.mountpoint().join(".root_uuid");
if root_uuid_stamp.exists() {
let bound_root_uuid = std::fs::read_to_string(&root_uuid_stamp)
.with_context(|| format!("reading {}", root_uuid_stamp.display()))?;
let bound_root_uuid = bound_root_uuid.trim();
// Let it slide if it already matches the rootfs... that shouldn't happen unless the user
// is trying to force a rerun of Ignition. In that case, we'll have nicer errors and
// warnings elsewhere.
if bound_root_uuid != root_uuid {
bail!(
"boot filesystem already bound to a root filesystem (UUID: {})",
bound_root_uuid
);
}
} else {
std::fs::write(&root_uuid_stamp, format!("{}\n", root_uuid))
.with_context(|| format!("writing {}", root_uuid_stamp.display()))?;
}

// now bind GRUB to bootfs
#[cfg(not(target_arch = "s390x"))]
{
let grub_bios_path = boot_mount.mountpoint().join("grub2/bootuuid.cfg");
write_boot_uuid_grub2_dropin(&boot_uuid, grub_bios_path)?;
}

for esp in find_esps()? {
let mount = Mount::try_mount(&esp, "vfat", mount::MsFlags::empty())?;
let vendor_dir = find_efi_vendor_dir(&mount)?;
let grub_efi_path = vendor_dir.join("bootuuid.cfg");
write_boot_uuid_grub2_dropin(&boot_uuid, grub_efi_path)?;
}
Ok(())
}

fn write_boot_uuid_grub2_dropin<P: AsRef<Path>>(uuid: &str, p: P) -> Result<()> {
let p = p.as_ref();
std::fs::write(p, format!("set BOOT_UUID=\"{}\"\n", uuid))
.with_context(|| format!("writing {}", p.display()))
}
51 changes: 51 additions & 0 deletions src/blockdev.rs
Original file line number Diff line number Diff line change
Expand Up @@ -877,6 +877,57 @@ pub fn lsblk(dev: &Path, with_deps: bool) -> Result<Vec<HashMap<String, String>>
Ok(result)
}

pub fn find_esps() -> Result<Vec<String>> {
const ESP_TYPE_GUID: &str = "c12a7328-f81f-11d2-ba4b-00a0c93ec93b";
let mut cmd = Command::new("lsblk");
// Older lsblk, e.g. in CentOS 7.6, doesn't support PATH, but --paths option
cmd.arg("--pairs")
.arg("--paths")
.arg("--output")
.arg("NAME,PARTTYPE");
let output = cmd_output(&mut cmd)?;
output
.lines()
.filter_map(|line| {
let dev = split_lsblk_line(line);
if dev.get("PARTTYPE").map(|t| t.as_str()) == Some(ESP_TYPE_GUID) {
Some(
dev.get("NAME")
.cloned()
.ok_or_else(|| anyhow!("ESP device with missing NAME")),
)
} else {
None
}
})
.collect()
}

/// This is basically a Rust version of:
/// https://github.com/coreos/coreos-assembler/blob/d3c7ec094a02/src/cmd-buildextend-live#L492-L495
pub fn find_efi_vendor_dir(efi_mount: &Mount) -> Result<PathBuf> {
let p = efi_mount.mountpoint().join("EFI");
let mut vendor_dir: Vec<PathBuf> = Vec::new();
for ent in p.read_dir()? {
let ent = ent.with_context(|| format!("reading directory entry in {}", p.display()))?;
if ent.file_name() == "BOOT" {
continue;
}
if !ent.file_type()?.is_dir() {
continue;
}
vendor_dir.push(ent.path());
}
if vendor_dir.len() != 1 {
bail!(
"Expected one vendor dir on {}, got {}",
efi_mount.device(),
vendor_dir.len()
);
}
Ok(vendor_dir.pop().unwrap())
}

/// 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 Down
4 changes: 3 additions & 1 deletion src/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -607,6 +607,8 @@ fn bls_entry_options_write_platform(orig_options: &str, platform: &str) -> Resul
///
/// Note that on s390x, this does not handle the call to `zipl`. We expect it to be done at a
/// higher level if needed for batching purposes.
///
/// Returns `true` if BLS content was modified.
pub fn visit_bls_entry(
mountpoint: &Path,
f: impl Fn(&str) -> Result<Option<String>>,
Expand Down Expand Up @@ -675,7 +677,7 @@ pub fn visit_bls_entry(

/// Wrapper around `visit_bls_entry` to specifically visit just the BLS entry's `options` line and
/// optionally update it if the function returns new content. Errors out if none or more than one
/// `options` field was found.
/// `options` field was found. Returns `true` if BLS content was modified.
pub fn visit_bls_entry_options(
mountpoint: &Path,
f: impl Fn(&str) -> Result<Option<String>>,
Expand Down

0 comments on commit 77fb2fd

Please sign in to comment.