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 cap-std, use in grubconfigs #674

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
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
115 changes: 113 additions & 2 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ path = "src/main.rs"
[dependencies]
anyhow = "1.0"
bincode = "1.3.2"
cap-std-ext = "4.0.0"
chrono = { version = "0.4.38", features = ["serde"] }
clap = { version = "3.2", default-features = false, features = ["cargo", "derive", "std", "suggestions"] }
env_logger = "0.10"
Expand Down
55 changes: 39 additions & 16 deletions src/grubconfigs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,13 @@ use std::fmt::Write;
use std::path::{Path, PathBuf};

use anyhow::{anyhow, Context, Result};
use cap_std::fs::{Dir, DirBuilder, DirBuilderExt, MetadataExt};
use cap_std_ext::cap_std;
use cap_std_ext::cap_std::fs::{Permissions, PermissionsExt};
use cap_std_ext::dirext::CapStdExtDirExt;
use fn_error_context::context;
use openat_ext::OpenatDirExt;

use crate::util;

/// The subdirectory of /boot we use
const GRUB2DIR: &str = "grub2";
Expand All @@ -17,27 +22,34 @@ pub(crate) fn install(
installed_efi_vendor: Option<&str>,
write_uuid: bool,
) -> Result<()> {
let bootdir = &target_root.sub_dir("boot").context("Opening /boot")?;
let target_root = &util::reopen_dir(target_root)?;
let bootdir = &target_root.open_dir("boot").context("Opening /boot")?;
let boot_is_mount = {
let root_dev = target_root.self_metadata()?.stat().st_dev;
let boot_dev = bootdir.self_metadata()?.stat().st_dev;
let root_dev = target_root.dir_metadata()?.dev();
let boot_dev = bootdir.dir_metadata()?.dev();
log::debug!("root_dev={root_dev} boot_dev={boot_dev}");
root_dev != boot_dev
};

if !bootdir.exists(GRUB2DIR)? {
bootdir.create_dir(GRUB2DIR, 0o700)?;
if !bootdir.try_exists(GRUB2DIR)? {
let mut db = DirBuilder::new();
db.mode(0o700);
bootdir.create_dir_with(GRUB2DIR, &db)?;
}

let mut config = std::fs::read_to_string(Path::new(CONFIGDIR).join("grub-static-pre.cfg"))?;

let dropindir = openat::Dir::open(&Path::new(CONFIGDIR).join(DROPINDIR))?;
let dropindir = Dir::open_ambient_dir(
&Path::new(CONFIGDIR).join(DROPINDIR),
cap_std::ambient_authority(),
)?;
// Sort the files for reproducibility
let mut entries = dropindir
.list_dir(".")?
.entries()?
.map(|e| e.map_err(anyhow::Error::msg))
.collect::<Result<Vec<_>>>()?;
entries.sort_by(|a, b| a.file_name().cmp(b.file_name()));
// cc https://github.com/rust-lang/rust/issues/85573#issuecomment-2195271304
entries.sort_by(|a, b| a.file_name().cmp(&b.file_name()));
for ent in entries {
let name = ent.file_name();
let name = name
Expand All @@ -49,7 +61,7 @@ pub(crate) fn install(
}
writeln!(config, "source $prefix/{name}")?;
dropindir
.copy_file_at(name, bootdir, format!("{GRUB2DIR}/{name}"))
.copy(name, bootdir, format!("{GRUB2DIR}/{name}"))
.with_context(|| format!("Copying {name}"))?;
println!("Installed {name}");
}
Expand All @@ -59,21 +71,27 @@ pub(crate) fn install(
config.push_str(post.as_str());
}

let rperms = Permissions::from_mode(0o644);
bootdir
.write_file_contents(format!("{GRUB2DIR}/grub.cfg"), 0o644, config.as_bytes())
.atomic_write_with_perms(
format!("{GRUB2DIR}/grub.cfg"),
config.as_bytes(),
rperms.clone(),
)
.context("Copying grub-static.cfg")?;
println!("Installed: grub.cfg");

let uuid_path = if write_uuid {
let target_fs = if boot_is_mount { bootdir } else { target_root };
let bootfs_meta = crate::filesystem::inspect_filesystem(target_fs, ".")?;
let target_fs_dir = &util::reopen_legacy_dir(target_fs)?;
let bootfs_meta = crate::filesystem::inspect_filesystem(target_fs_dir, ".")?;
let bootfs_uuid = bootfs_meta
.uuid
.ok_or_else(|| anyhow::anyhow!("Failed to find UUID for boot"))?;
let grub2_uuid_contents = format!("set BOOT_UUID=\"{bootfs_uuid}\"\n");
let uuid_path = format!("{GRUB2DIR}/bootuuid.cfg");
bootdir
.write_file_contents(&uuid_path, 0o644, grub2_uuid_contents)
.atomic_write_with_perms(&uuid_path, grub2_uuid_contents, rperms)
.context("Writing bootuuid.cfg")?;
Some(uuid_path)
} else {
Expand All @@ -85,19 +103,23 @@ pub(crate) fn install(
let vendor = PathBuf::from(vendordir);
let target = &vendor.join("grub.cfg");
let dest_efidir = target_root
.sub_dir_optional("boot/efi/EFI")
.open_dir_optional("boot/efi/EFI")
.context("Opening /boot/efi/EFI")?;
if let Some(efidir) = dest_efidir {
efidir
Copy link
Member

@HuijingHei HuijingHei Jul 1, 2024

Choose a reason for hiding this comment

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

CI error: error: boot data installation failed: Installing static GRUB configs: Copying static EFI: a path led outside of the filesystem

To copy /usr/lib/bootupd/grub2-static/grub-static-efi.cfg to boot/efi/EFI/fedora/grub.cfg, refer to doc, grub-static-efi.cfg should be relative to self, might use target_root instead of efidir, but not sure if it works as CONFIGDIR is not relative path.

.copy_file(&Path::new(CONFIGDIR).join("grub-static-efi.cfg"), target)
.copy(
&Path::new(CONFIGDIR).join("grub-static-efi.cfg"),
&efidir,
target,
)
.context("Copying static EFI")?;
println!("Installed: {target:?}");
if let Some(uuid_path) = uuid_path {
// SAFETY: we always have a filename
let filename = Path::new(&uuid_path).file_name().unwrap();
let target = &vendor.join(filename);
bootdir
.copy_file_at(uuid_path, &efidir, target)
.copy(uuid_path, &efidir, target)
.context("Writing bootuuid.cfg to efi dir")?;
}
}
Expand All @@ -109,6 +131,7 @@ pub(crate) fn install(
#[cfg(test)]
mod tests {
use super::*;
use openat_ext::OpenatDirExt;

#[test]
#[ignore]
Expand Down
12 changes: 12 additions & 0 deletions src/util.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
use std::collections::HashSet;
use std::os::fd::{AsRawFd, BorrowedFd};
use std::path::Path;
use std::process::Command;

use anyhow::{bail, Context, Result};
use cap_std_ext::cap_std::fs::Dir;
use openat_ext::OpenatDirExt;

pub(crate) trait CommandRunExt {
Expand Down Expand Up @@ -99,3 +101,13 @@ pub(crate) fn cmd_output(cmd: &mut Command) -> Result<String> {
String::from_utf8(result.stdout)
.with_context(|| format!("decoding as UTF-8 output of `{:#?}`", cmd))
}

// Re-open an [`openat::Dir`] via the cap-std version.
pub(crate) fn reopen_dir(d: &openat::Dir) -> Result<Dir> {
Dir::reopen_dir(&unsafe { BorrowedFd::borrow_raw(d.as_raw_fd()) }).map_err(Into::into)
}

// Re-open an [`cap_std::fs::Dir`] as a legacy openat::Dir.
pub(crate) fn reopen_legacy_dir(d: &Dir) -> Result<openat::Dir> {
openat::Dir::open(format!("/proc/self/fd/{}", d.as_raw_fd())).map_err(Into::into)
}