Skip to content
Open
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
23 changes: 17 additions & 6 deletions Cargo.lock

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

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -114,3 +114,6 @@ todo = "deny"
needless_borrow = "allow"
needless_borrows_for_generic_args = "allow"

[patch."https://github.com/composefs/composefs-rs"]
cfsctl = { git = "https://github.com/cgwalters/composefs-rs", rev = "d62471bc89d4852f2aab91c7948466e3a3942021" }

131 changes: 45 additions & 86 deletions crates/lib/src/bootc_composefs/boot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,10 @@
//! 1. **Primary**: New/upgraded deployment (default boot target)
//! 2. **Secondary**: Currently booted deployment (rollback option)

use std::ffi::OsStr;
use std::fs::create_dir_all;
use std::io::Write;
use std::path::Path;
use std::sync::Arc;

use anyhow::{Context, Result, anyhow, bail};
use bootc_kernel_cmdline::utf8::{Cmdline, Parameter, ParameterKey};
Expand All @@ -81,43 +81,27 @@ use clap::ValueEnum;
use composefs::fs::read_file;
use composefs::fsverity::{FsVerityHashValue, Sha512HashValue};
use composefs::tree::RegularFile;
use composefs_boot::BootOps;
use composefs_boot::bootloader::{
BootEntry as ComposefsBootEntry, EFI_ADDON_DIR_EXT, EFI_ADDON_FILE_EXT, EFI_EXT, PEType,
UsrLibModulesVmlinuz,
UsrLibModulesVmlinuz, get_boot_resources,
};
use composefs_boot::{cmdline::get_cmdline_composefs, os_release::OsReleaseInfo, uki};
use composefs_oci::image::create_filesystem as create_composefs_filesystem;
use fn_error_context::context;
use rustix::{mount::MountFlags, path::Arg};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

use crate::bootc_composefs::state::{get_booted_bls, write_composefs_state};
use crate::bootc_kargs::compute_new_kargs;
use crate::composefs_consts::{TYPE1_BOOT_DIR_PREFIX, TYPE1_ENT_PATH, TYPE1_ENT_PATH_STAGED};
use crate::parsers::bls_config::{BLSConfig, BLSConfigType};
use crate::task::Task;
use crate::{
bootc_composefs::repo::get_imgref,
composefs_consts::{TYPE1_ENT_PATH, TYPE1_ENT_PATH_STAGED},
};
use crate::{
bootc_composefs::repo::open_composefs_repo,
store::{ComposefsFilesystem, Storage},
};
use crate::{
bootc_composefs::state::{get_booted_bls, write_composefs_state},
composefs_consts::TYPE1_BOOT_DIR_PREFIX,
};
use crate::{
bootc_composefs::status::get_container_manifest_and_config, bootc_kargs::compute_new_kargs,
};
use crate::{bootc_composefs::repo::open_composefs_repo, store::Storage};
use crate::{bootc_composefs::status::get_sorted_grub_uki_boot_entries, install::PostFetchState};
use crate::{
composefs_consts::UKI_NAME_PREFIX,
parsers::bls_config::{BLSConfig, BLSConfigType},
};
use crate::{
composefs_consts::{
BOOT_LOADER_ENTRIES, COMPOSEFS_CMDLINE, ORIGIN_KEY_BOOT, ORIGIN_KEY_BOOT_DIGEST,
STAGED_BOOT_LOADER_ENTRIES, STATE_DIR_ABS, USER_CFG, USER_CFG_STAGED,
STAGED_BOOT_LOADER_ENTRIES, STATE_DIR_ABS, UKI_NAME_PREFIX, USER_CFG, USER_CFG_STAGED,
},
spec::{Bootloader, Host},
};
Expand Down Expand Up @@ -149,23 +133,9 @@ pub(crate) const BOOTC_UKI_DIR: &str = "EFI/Linux/bootc";

pub(crate) enum BootSetupType<'a> {
/// For initial setup, i.e. install to-disk
Setup(
(
&'a RootSetup,
&'a State,
&'a PostFetchState,
&'a ComposefsFilesystem,
),
),
Setup((&'a RootSetup, &'a State, &'a PostFetchState)),
/// For `bootc upgrade`
Upgrade(
(
&'a Storage,
&'a BootedComposefs,
&'a ComposefsFilesystem,
&'a Host,
),
),
Upgrade((&'a Storage, &'a BootedComposefs, &'a Host)),
}

#[derive(
Expand Down Expand Up @@ -448,41 +418,20 @@ fn write_bls_boot_entries_to_disk(
}

/// Parses /usr/lib/os-release and returns (id, title, version)
fn parse_os_release(
fs: &crate::store::ComposefsFilesystem,
repo: &crate::store::ComposefsRepository,
) -> Result<Option<(String, Option<String>, Option<String>)>> {
fn parse_os_release(mounted_fs: &Dir) -> Result<Option<(String, Option<String>, Option<String>)>> {
// Every update should have its own /usr/lib/os-release
let (dir, fname) = fs
.root
.split(OsStr::new("/usr/lib/os-release"))
.context("Getting /usr/lib/os-release")?;

let os_release = dir
.get_file_opt(fname)
.context("Getting /usr/lib/os-release")?;

let Some(os_rel_file) = os_release else {
return Ok(None);
};

let file_contents = match read_file(os_rel_file, repo) {
let file_contents = match mounted_fs.read_to_string("usr/lib/os-release") {
Ok(c) => c,
Err(e) => {
tracing::warn!("Could not read /usr/lib/os-release: {e:?}");
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
return Ok(None);
}
};

let file_contents = match std::str::from_utf8(&file_contents) {
Ok(c) => c,
Err(e) => {
tracing::warn!("/usr/lib/os-release did not have valid UTF-8: {e}");
tracing::warn!("Could not read /usr/lib/os-release: {e:?}");
return Ok(None);
}
};

let parsed = OsReleaseInfo::parse(file_contents);
let parsed = OsReleaseInfo::parse(&file_contents);

let os_id = parsed
.get_value(&["ID"])
Expand Down Expand Up @@ -518,8 +467,8 @@ pub(crate) fn setup_composefs_bls_boot(
) -> Result<String> {
let id_hex = id.to_hex();

let (root_path, esp_device, mut cmdline_refs, fs, bootloader) = match setup_type {
BootSetupType::Setup((root_setup, state, postfetch, fs)) => {
let (root_path, esp_device, mut cmdline_refs, bootloader) = match setup_type {
BootSetupType::Setup((root_setup, state, postfetch)) => {
// root_setup.kargs has [root=UUID=<UUID>, "rw"]
let mut cmdline_options = Cmdline::new();

Expand All @@ -541,12 +490,11 @@ pub(crate) fn setup_composefs_bls_boot(
root_setup.physical_root_path.clone(),
esp_part.path(),
cmdline_options,
fs,
postfetch.detected_bootloader.clone(),
)
}

BootSetupType::Upgrade((storage, booted_cfs, fs, host)) => {
BootSetupType::Upgrade((storage, booted_cfs, host)) => {
let bootloader = host.require_composefs_booted()?.bootloader.clone();

let boot_dir = storage.require_boot_dir()?;
Expand Down Expand Up @@ -583,7 +531,6 @@ pub(crate) fn setup_composefs_bls_boot(
Utf8PathBuf::from("/sysroot"),
esp_dev.path(),
cmdline,
fs,
bootloader,
)
}
Expand Down Expand Up @@ -657,7 +604,7 @@ pub(crate) fn setup_composefs_bls_boot(
let boot_digest = compute_boot_digest(usr_lib_modules_vmlinuz, &repo)
.context("Computing boot digest")?;

let osrel = parse_os_release(fs, &repo)?;
let osrel = parse_os_release(mounted_erofs)?;

let (os_id, title, version, sort_key) = match osrel {
Some((id_str, title_opt, version_opt)) => (
Expand Down Expand Up @@ -1102,7 +1049,7 @@ pub(crate) fn setup_composefs_uki_boot(
) -> Result<String> {
let (root_path, esp_device, bootloader, missing_fsverity_allowed, uki_addons) = match setup_type
{
BootSetupType::Setup((root_setup, state, postfetch, ..)) => {
BootSetupType::Setup((root_setup, state, postfetch)) => {
state.require_no_kargs_for_uki()?;

let esp_part = root_setup.device_info.find_partition_of_esp()?;
Expand All @@ -1116,7 +1063,7 @@ pub(crate) fn setup_composefs_uki_boot(
)
}

BootSetupType::Upgrade((storage, booted_cfs, _, host)) => {
BootSetupType::Upgrade((storage, booted_cfs, host)) => {
let sysroot = Utf8PathBuf::from("/sysroot"); // Still needed for root_path
let bootloader = host.require_composefs_booted()?.bootloader.clone();

Expand Down Expand Up @@ -1259,25 +1206,34 @@ fn get_secureboot_keys(fs: &Dir, p: &str) -> Result<Option<SecurebootKeys>> {
pub(crate) async fn setup_composefs_boot(
root_setup: &RootSetup,
state: &State,
image_id: &str,
pull_result: &composefs_oci::skopeo::PullResult<Sha512HashValue>,
allow_missing_fsverity: bool,
) -> Result<()> {
const COMPOSEFS_BOOT_SETUP_JOURNAL_ID: &str = "1f0e9d8c7b6a5f4e3d2c1b0a9f8e7d6c5";

tracing::info!(
message_id = COMPOSEFS_BOOT_SETUP_JOURNAL_ID,
bootc.operation = "boot_setup",
bootc.image_id = image_id,
bootc.config_digest = pull_result.config_digest,
bootc.allow_missing_fsverity = allow_missing_fsverity,
"Setting up composefs boot",
);

let mut repo = open_composefs_repo(&root_setup.physical_root)?;
repo.set_insecure(allow_missing_fsverity);

let mut fs = create_composefs_filesystem(&repo, image_id, None)?;
let entries = fs.transform_for_boot(&repo)?;
let id = fs.commit_image(&repo, None)?;
let repo = Arc::new(repo);

// Generate the bootable EROFS image (idempotent).
let id = composefs_oci::generate_boot_image(&repo, &pull_result.manifest_digest)
.context("Generating bootable EROFS image")?;

// Get boot entries from the OCI filesystem (untransformed).
let fs = composefs_oci::image::create_filesystem(&*repo, &pull_result.config_digest, None)
.context("Creating composefs filesystem for boot entry discovery")?;
let entries =
get_boot_resources(&fs, &*repo).context("Extracting boot entries from OCI image")?;

let mounted_fs = Dir::reopen_dir(
&repo
.mount(&id.to_hex())
Expand Down Expand Up @@ -1320,16 +1276,23 @@ pub(crate) async fn setup_composefs_boot(

let boot_type = BootType::from(entry);

// Unwrap Arc to pass owned repo to boot setup functions.
let repo = Arc::try_unwrap(repo).map_err(|_| {
anyhow::anyhow!(
"BUG: Arc<Repository> still has other references after boot image generation"
)
})?;

let boot_digest = match boot_type {
BootType::Bls => setup_composefs_bls_boot(
BootSetupType::Setup((&root_setup, &state, &postfetch, &fs)),
BootSetupType::Setup((&root_setup, &state, &postfetch)),
repo,
&id,
entry,
&mounted_fs,
)?,
BootType::Uki => setup_composefs_uki_boot(
BootSetupType::Setup((&root_setup, &state, &postfetch, &fs)),
BootSetupType::Setup((&root_setup, &state, &postfetch)),
repo,
&id,
entries,
Expand All @@ -1343,11 +1306,7 @@ pub(crate) async fn setup_composefs_boot(
None,
boot_type,
boot_digest,
&get_container_manifest_and_config(&get_imgref(
&state.source.imageref.transport.to_string(),
&state.source.imageref.name,
))
.await?,
&pull_result.manifest_digest,
allow_missing_fsverity,
)
.await?;
Expand Down
7 changes: 4 additions & 3 deletions crates/lib/src/bootc_composefs/export.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ pub async fn export_repo_to_image(

let depl_verity = depl_verity.ok_or_else(|| anyhow::anyhow!("Image {source} not found"))?;

let imginfo = get_imginfo(storage, &depl_verity, None).await?;
let imginfo = get_imginfo(storage, &depl_verity)?;

// We want the digest in the form of "sha256:abc123"
let config_digest = format!("{}", imginfo.manifest.config().digest());
Expand All @@ -55,8 +55,9 @@ pub async fn export_repo_to_image(
let oci_dir = OciDir::ensure(tmpdir.try_clone()?).context("Opening OCI")?;

// Use composefs_oci::open_config to get the config and layer map
let (config, layer_map) =
open_config(&*booted_cfs.repo, &config_digest, None).context("Opening config")?;
let open = open_config(&*booted_cfs.repo, &config_digest, None).context("Opening config")?;
let config = open.config;
let layer_map = open.layer_refs;

// We can't guarantee that we'll get the same tar stream as the container image
// So we create new config and manifest
Expand Down
Loading
Loading