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

rdcore: add bind-boot command #671

Merged
merged 2 commits into from
Nov 16, 2021
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
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<()> {
jlebon marked this conversation as resolved.
Show resolved Hide resolved
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)
jlebon marked this conversation as resolved.
Show resolved Hide resolved
}
})
.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) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another .as_deref() candidate.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Heh, I've also tried this at first, but

error[E0308]: mismatched types
   --> src/blockdev.rs:895:55
    |
895 |             if dev.get("PARTTYPE").as_deref() == Some(ESP_TYPE_GUID) {
    |                                                       ^^^^^^^^^^^^^ expected struct `std::string::String`, found `str`
    |
    = note: expected reference `&std::string::String`
               found reference `&'static str`

I think it's because we're not dealing with an Option<String>, but an Option<&String>.

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 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it'd be cleaner to have let mut vendor_dir: Option<PathBuf> and then move the error check inside like:

if vendor_dir.replace(ent.path()).is_some() {
   bail!(...)

Then there'd be no need for an unwrap() at the exit path.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually did exactly that at first, but decided to match the logic in https://github.com/coreos/coreos-assembler/blob/d3c7ec094a02/src/cmd-buildextend-live#L492-L495 in that respect too since I like how it automatically handles the 0 case too.

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