Skip to content

Commit 215b824

Browse files
committed
composefs: Use composefs-rs OCI APIs for image metadata and GC
Read manifest+config via OciImage::open() instead of .imginfo sidecar files, with fallback for legacy deployments. Create bootc-owned OCI tags as GC roots so the manifest->config->layer chain stays alive through standard composefs-rs reachability. Replace the manual transform_for_boot sequence with generate_boot_image(). The GC flow is now: prune unreferenced bootc tags, then repo.gc(). Assisted-by: OpenCode (Claude claude-opus-4-6) Signed-off-by: Colin Walters <walters@verbum.org>
1 parent 859287b commit 215b824

File tree

12 files changed

+282
-237
lines changed

12 files changed

+282
-237
lines changed

crates/lib/src/bootc_composefs/boot.rs

Lines changed: 45 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,10 @@
6161
//! 1. **Primary**: New/upgraded deployment (default boot target)
6262
//! 2. **Secondary**: Currently booted deployment (rollback option)
6363
64-
use std::ffi::OsStr;
6564
use std::fs::create_dir_all;
6665
use std::io::Write;
6766
use std::path::Path;
67+
use std::sync::Arc;
6868

6969
use anyhow::{Context, Result, anyhow, bail};
7070
use bootc_kernel_cmdline::utf8::{Cmdline, Parameter, ParameterKey};
@@ -81,43 +81,27 @@ use clap::ValueEnum;
8181
use composefs::fs::read_file;
8282
use composefs::fsverity::{FsVerityHashValue, Sha512HashValue};
8383
use composefs::tree::RegularFile;
84-
use composefs_boot::BootOps;
8584
use composefs_boot::bootloader::{
8685
BootEntry as ComposefsBootEntry, EFI_ADDON_DIR_EXT, EFI_ADDON_FILE_EXT, EFI_EXT, PEType,
87-
UsrLibModulesVmlinuz,
86+
UsrLibModulesVmlinuz, get_boot_resources,
8887
};
8988
use composefs_boot::{cmdline::get_cmdline_composefs, os_release::OsReleaseInfo, uki};
90-
use composefs_oci::image::create_filesystem as create_composefs_filesystem;
9189
use fn_error_context::context;
9290
use rustix::{mount::MountFlags, path::Arg};
9391
use schemars::JsonSchema;
9492
use serde::{Deserialize, Serialize};
9593

94+
use crate::bootc_composefs::state::{get_booted_bls, write_composefs_state};
95+
use crate::bootc_kargs::compute_new_kargs;
96+
use crate::composefs_consts::{TYPE1_BOOT_DIR_PREFIX, TYPE1_ENT_PATH, TYPE1_ENT_PATH_STAGED};
97+
use crate::parsers::bls_config::{BLSConfig, BLSConfigType};
9698
use crate::task::Task;
97-
use crate::{
98-
bootc_composefs::repo::get_imgref,
99-
composefs_consts::{TYPE1_ENT_PATH, TYPE1_ENT_PATH_STAGED},
100-
};
101-
use crate::{
102-
bootc_composefs::repo::open_composefs_repo,
103-
store::{ComposefsFilesystem, Storage},
104-
};
105-
use crate::{
106-
bootc_composefs::state::{get_booted_bls, write_composefs_state},
107-
composefs_consts::TYPE1_BOOT_DIR_PREFIX,
108-
};
109-
use crate::{
110-
bootc_composefs::status::get_container_manifest_and_config, bootc_kargs::compute_new_kargs,
111-
};
99+
use crate::{bootc_composefs::repo::open_composefs_repo, store::Storage};
112100
use crate::{bootc_composefs::status::get_sorted_grub_uki_boot_entries, install::PostFetchState};
113-
use crate::{
114-
composefs_consts::UKI_NAME_PREFIX,
115-
parsers::bls_config::{BLSConfig, BLSConfigType},
116-
};
117101
use crate::{
118102
composefs_consts::{
119103
BOOT_LOADER_ENTRIES, COMPOSEFS_CMDLINE, ORIGIN_KEY_BOOT, ORIGIN_KEY_BOOT_DIGEST,
120-
STAGED_BOOT_LOADER_ENTRIES, STATE_DIR_ABS, USER_CFG, USER_CFG_STAGED,
104+
STAGED_BOOT_LOADER_ENTRIES, STATE_DIR_ABS, UKI_NAME_PREFIX, USER_CFG, USER_CFG_STAGED,
121105
},
122106
spec::{Bootloader, Host},
123107
};
@@ -149,23 +133,9 @@ pub(crate) const BOOTC_UKI_DIR: &str = "EFI/Linux/bootc";
149133

150134
pub(crate) enum BootSetupType<'a> {
151135
/// For initial setup, i.e. install to-disk
152-
Setup(
153-
(
154-
&'a RootSetup,
155-
&'a State,
156-
&'a PostFetchState,
157-
&'a ComposefsFilesystem,
158-
),
159-
),
136+
Setup((&'a RootSetup, &'a State, &'a PostFetchState)),
160137
/// For `bootc upgrade`
161-
Upgrade(
162-
(
163-
&'a Storage,
164-
&'a BootedComposefs,
165-
&'a ComposefsFilesystem,
166-
&'a Host,
167-
),
168-
),
138+
Upgrade((&'a Storage, &'a BootedComposefs, &'a Host)),
169139
}
170140

171141
#[derive(
@@ -448,41 +418,20 @@ fn write_bls_boot_entries_to_disk(
448418
}
449419

450420
/// Parses /usr/lib/os-release and returns (id, title, version)
451-
fn parse_os_release(
452-
fs: &crate::store::ComposefsFilesystem,
453-
repo: &crate::store::ComposefsRepository,
454-
) -> Result<Option<(String, Option<String>, Option<String>)>> {
421+
fn parse_os_release(mounted_fs: &Dir) -> Result<Option<(String, Option<String>, Option<String>)>> {
455422
// Every update should have its own /usr/lib/os-release
456-
let (dir, fname) = fs
457-
.root
458-
.split(OsStr::new("/usr/lib/os-release"))
459-
.context("Getting /usr/lib/os-release")?;
460-
461-
let os_release = dir
462-
.get_file_opt(fname)
463-
.context("Getting /usr/lib/os-release")?;
464-
465-
let Some(os_rel_file) = os_release else {
466-
return Ok(None);
467-
};
468-
469-
let file_contents = match read_file(os_rel_file, repo) {
423+
let file_contents = match mounted_fs.read_to_string("usr/lib/os-release") {
470424
Ok(c) => c,
471-
Err(e) => {
472-
tracing::warn!("Could not read /usr/lib/os-release: {e:?}");
425+
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
473426
return Ok(None);
474427
}
475-
};
476-
477-
let file_contents = match std::str::from_utf8(&file_contents) {
478-
Ok(c) => c,
479428
Err(e) => {
480-
tracing::warn!("/usr/lib/os-release did not have valid UTF-8: {e}");
429+
tracing::warn!("Could not read /usr/lib/os-release: {e:?}");
481430
return Ok(None);
482431
}
483432
};
484433

485-
let parsed = OsReleaseInfo::parse(file_contents);
434+
let parsed = OsReleaseInfo::parse(&file_contents);
486435

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

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

@@ -541,12 +490,11 @@ pub(crate) fn setup_composefs_bls_boot(
541490
root_setup.physical_root_path.clone(),
542491
esp_part.path(),
543492
cmdline_options,
544-
fs,
545493
postfetch.detected_bootloader.clone(),
546494
)
547495
}
548496

549-
BootSetupType::Upgrade((storage, booted_cfs, fs, host)) => {
497+
BootSetupType::Upgrade((storage, booted_cfs, host)) => {
550498
let bootloader = host.require_composefs_booted()?.bootloader.clone();
551499

552500
let boot_dir = storage.require_boot_dir()?;
@@ -583,7 +531,6 @@ pub(crate) fn setup_composefs_bls_boot(
583531
Utf8PathBuf::from("/sysroot"),
584532
esp_dev.path(),
585533
cmdline,
586-
fs,
587534
bootloader,
588535
)
589536
}
@@ -657,7 +604,7 @@ pub(crate) fn setup_composefs_bls_boot(
657604
let boot_digest = compute_boot_digest(usr_lib_modules_vmlinuz, &repo)
658605
.context("Computing boot digest")?;
659606

660-
let osrel = parse_os_release(fs, &repo)?;
607+
let osrel = parse_os_release(mounted_erofs)?;
661608

662609
let (os_id, title, version, sort_key) = match osrel {
663610
Some((id_str, title_opt, version_opt)) => (
@@ -1102,7 +1049,7 @@ pub(crate) fn setup_composefs_uki_boot(
11021049
) -> Result<String> {
11031050
let (root_path, esp_device, bootloader, missing_fsverity_allowed, uki_addons) = match setup_type
11041051
{
1105-
BootSetupType::Setup((root_setup, state, postfetch, ..)) => {
1052+
BootSetupType::Setup((root_setup, state, postfetch)) => {
11061053
state.require_no_kargs_for_uki()?;
11071054

11081055
let esp_part = root_setup.device_info.find_partition_of_esp()?;
@@ -1116,7 +1063,7 @@ pub(crate) fn setup_composefs_uki_boot(
11161063
)
11171064
}
11181065

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

@@ -1259,25 +1206,34 @@ fn get_secureboot_keys(fs: &Dir, p: &str) -> Result<Option<SecurebootKeys>> {
12591206
pub(crate) async fn setup_composefs_boot(
12601207
root_setup: &RootSetup,
12611208
state: &State,
1262-
image_id: &str,
1209+
pull_result: &composefs_oci::skopeo::PullResult<Sha512HashValue>,
12631210
allow_missing_fsverity: bool,
12641211
) -> Result<()> {
12651212
const COMPOSEFS_BOOT_SETUP_JOURNAL_ID: &str = "1f0e9d8c7b6a5f4e3d2c1b0a9f8e7d6c5";
12661213

12671214
tracing::info!(
12681215
message_id = COMPOSEFS_BOOT_SETUP_JOURNAL_ID,
12691216
bootc.operation = "boot_setup",
1270-
bootc.image_id = image_id,
1217+
bootc.config_digest = pull_result.config_digest,
12711218
bootc.allow_missing_fsverity = allow_missing_fsverity,
12721219
"Setting up composefs boot",
12731220
);
12741221

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

1278-
let mut fs = create_composefs_filesystem(&repo, image_id, None)?;
1279-
let entries = fs.transform_for_boot(&repo)?;
1280-
let id = fs.commit_image(&repo, None)?;
1225+
let repo = Arc::new(repo);
1226+
1227+
// Generate the bootable EROFS image (idempotent).
1228+
let id = composefs_oci::generate_boot_image(&repo, &pull_result.manifest_digest)
1229+
.context("Generating bootable EROFS image")?;
1230+
1231+
// Get boot entries from the OCI filesystem (untransformed).
1232+
let fs = composefs_oci::image::create_filesystem(&*repo, &pull_result.config_digest, None)
1233+
.context("Creating composefs filesystem for boot entry discovery")?;
1234+
let entries =
1235+
get_boot_resources(&fs, &*repo).context("Extracting boot entries from OCI image")?;
1236+
12811237
let mounted_fs = Dir::reopen_dir(
12821238
&repo
12831239
.mount(&id.to_hex())
@@ -1320,16 +1276,23 @@ pub(crate) async fn setup_composefs_boot(
13201276

13211277
let boot_type = BootType::from(entry);
13221278

1279+
// Unwrap Arc to pass owned repo to boot setup functions.
1280+
let repo = Arc::try_unwrap(repo).map_err(|_| {
1281+
anyhow::anyhow!(
1282+
"BUG: Arc<Repository> still has other references after boot image generation"
1283+
)
1284+
})?;
1285+
13231286
let boot_digest = match boot_type {
13241287
BootType::Bls => setup_composefs_bls_boot(
1325-
BootSetupType::Setup((&root_setup, &state, &postfetch, &fs)),
1288+
BootSetupType::Setup((&root_setup, &state, &postfetch)),
13261289
repo,
13271290
&id,
13281291
entry,
13291292
&mounted_fs,
13301293
)?,
13311294
BootType::Uki => setup_composefs_uki_boot(
1332-
BootSetupType::Setup((&root_setup, &state, &postfetch, &fs)),
1295+
BootSetupType::Setup((&root_setup, &state, &postfetch)),
13331296
repo,
13341297
&id,
13351298
entries,
@@ -1343,11 +1306,7 @@ pub(crate) async fn setup_composefs_boot(
13431306
None,
13441307
boot_type,
13451308
boot_digest,
1346-
&get_container_manifest_and_config(&get_imgref(
1347-
&state.source.imageref.transport.to_string(),
1348-
&state.source.imageref.name,
1349-
))
1350-
.await?,
1309+
&pull_result.manifest_digest,
13511310
allow_missing_fsverity,
13521311
)
13531312
.await?;

crates/lib/src/bootc_composefs/export.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ pub async fn export_repo_to_image(
4343

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

46-
let imginfo = get_imginfo(storage, &depl_verity, None).await?;
46+
let imginfo = get_imginfo(storage, &depl_verity)?;
4747

4848
// We want the digest in the form of "sha256:abc123"
4949
let config_digest = format!("{}", imginfo.manifest.config().digest());

0 commit comments

Comments
 (0)