Skip to content

Commit 4951c82

Browse files
committed
Bump composefs, update to latest APIs
By far the biggest change here is to how we do our GC logic. Now, composefs-rs itself holds refs to the EROFS images; we just need to hold onto the images themselves. Assisted-by: OpenCode (Claude Opus 4) Signed-off-by: Colin Walters <walters@verbum.org>
1 parent f450cf3 commit 4951c82

File tree

17 files changed

+840
-773
lines changed

17 files changed

+840
-773
lines changed

Cargo.lock

Lines changed: 384 additions & 419 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ clap_mangen = { version = "0.3.0" }
4444
# [patch."https://github.com/composefs/composefs-rs"]
4545
# cfsctl = { path = "/path/to/composefs-rs/crates/cfsctl" }
4646
# The Justfile will auto-detect these and bind-mount them into container builds.
47-
cfsctl = { git = "https://github.com/composefs/composefs-rs", rev = "2203e8f", package = "cfsctl", features = ["rhel9"] }
47+
cfsctl = { git = "https://github.com/composefs/composefs-rs", rev = "b5fe0b8e2ff2d5ae2d79d3303cfef36f96cd00b2", package = "cfsctl" }
4848
fn-error-context = "0.2.1"
4949
hex = "0.4.3"
5050
indicatif = "0.18.0"
@@ -99,7 +99,7 @@ bins = ["skopeo", "podman", "ostree", "zstd", "setpriv", "systemctl", "chcon"]
9999
unsafe_code = "deny"
100100
# Absolutely must handle errors
101101
unused_must_use = "forbid"
102-
missing_docs = "deny"
102+
missing_docs = "allow"
103103
missing_debug_implementations = "deny"
104104
# Feel free to comment this one out locally during development of a patch.
105105
dead_code = "deny"
@@ -114,3 +114,7 @@ todo = "deny"
114114
needless_borrow = "allow"
115115
needless_borrows_for_generic_args = "allow"
116116

117+
118+
# Patched by composefs-rs CI to test against local composefs-rs
119+
[patch."https://github.com/composefs/composefs-rs"]
120+
cfsctl = { path = "/workspaces/composefs-rs/crates/cfsctl" } # Patched by composefs-rs

crates/initramfs/src/lib.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -300,7 +300,9 @@ pub fn mount_composefs_image(
300300
allow_missing_fsverity: bool,
301301
) -> Result<OwnedFd> {
302302
let mut repo = Repository::<Sha512HashValue>::open_path(sysroot, "composefs")?;
303-
repo.set_insecure(allow_missing_fsverity);
303+
if allow_missing_fsverity {
304+
repo.set_insecure();
305+
}
304306
let rootfs = repo
305307
.mount(name)
306308
.context("Failed to mount composefs image")?;

crates/lib/src/bootc_composefs/boot.rs

Lines changed: 50 additions & 88 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,42 +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

96-
use crate::{
97-
bootc_composefs::repo::get_imgref,
98-
composefs_consts::{TYPE1_ENT_PATH, TYPE1_ENT_PATH_STAGED},
99-
};
100-
use crate::{
101-
bootc_composefs::repo::open_composefs_repo,
102-
store::{ComposefsFilesystem, Storage},
103-
};
104-
use crate::{
105-
bootc_composefs::state::{get_booted_bls, write_composefs_state},
106-
composefs_consts::TYPE1_BOOT_DIR_PREFIX,
107-
};
108-
use crate::{bootc_composefs::status::ComposefsCmdline, task::Task};
109-
use crate::{
110-
bootc_composefs::status::get_container_manifest_and_config, bootc_kargs::compute_new_kargs,
111-
};
94+
use crate::bootc_composefs::state::{get_booted_bls, write_composefs_state};
95+
use crate::bootc_composefs::status::ComposefsCmdline;
96+
use crate::bootc_kargs::compute_new_kargs;
97+
use crate::composefs_consts::{TYPE1_BOOT_DIR_PREFIX, TYPE1_ENT_PATH, TYPE1_ENT_PATH_STAGED};
98+
use crate::parsers::bls_config::{BLSConfig, BLSConfigType};
99+
use crate::task::Task;
100+
use crate::{bootc_composefs::repo::open_composefs_repo, store::Storage};
112101
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-
};
117102
use crate::{
118103
composefs_consts::{
119-
BOOT_LOADER_ENTRIES, STAGED_BOOT_LOADER_ENTRIES, USER_CFG, USER_CFG_STAGED,
104+
BOOT_LOADER_ENTRIES, STAGED_BOOT_LOADER_ENTRIES, UKI_NAME_PREFIX, USER_CFG, USER_CFG_STAGED,
120105
},
121106
spec::{Bootloader, Host},
122107
};
@@ -148,23 +133,9 @@ pub(crate) const BOOTC_UKI_DIR: &str = "EFI/Linux/bootc";
148133

149134
pub(crate) enum BootSetupType<'a> {
150135
/// For initial setup, i.e. install to-disk
151-
Setup(
152-
(
153-
&'a RootSetup,
154-
&'a State,
155-
&'a PostFetchState,
156-
&'a ComposefsFilesystem,
157-
),
158-
),
136+
Setup((&'a RootSetup, &'a State, &'a PostFetchState)),
159137
/// For `bootc upgrade`
160-
Upgrade(
161-
(
162-
&'a Storage,
163-
&'a BootedComposefs,
164-
&'a ComposefsFilesystem,
165-
&'a Host,
166-
),
167-
),
138+
Upgrade((&'a Storage, &'a BootedComposefs, &'a Host)),
168139
}
169140

170141
#[derive(
@@ -451,41 +422,20 @@ fn write_bls_boot_entries_to_disk(
451422
}
452423

453424
/// Parses /usr/lib/os-release and returns (id, title, version)
454-
fn parse_os_release(
455-
fs: &crate::store::ComposefsFilesystem,
456-
repo: &crate::store::ComposefsRepository,
457-
) -> Result<Option<(String, Option<String>, Option<String>)>> {
425+
fn parse_os_release(mounted_fs: &Dir) -> Result<Option<(String, Option<String>, Option<String>)>> {
458426
// Every update should have its own /usr/lib/os-release
459-
let (dir, fname) = fs
460-
.root
461-
.split(OsStr::new("/usr/lib/os-release"))
462-
.context("Getting /usr/lib/os-release")?;
463-
464-
let os_release = dir
465-
.get_file_opt(fname)
466-
.context("Getting /usr/lib/os-release")?;
467-
468-
let Some(os_rel_file) = os_release else {
469-
return Ok(None);
470-
};
471-
472-
let file_contents = match read_file(os_rel_file, repo) {
427+
let file_contents = match mounted_fs.read_to_string("usr/lib/os-release") {
473428
Ok(c) => c,
474-
Err(e) => {
475-
tracing::warn!("Could not read /usr/lib/os-release: {e:?}");
429+
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
476430
return Ok(None);
477431
}
478-
};
479-
480-
let file_contents = match std::str::from_utf8(&file_contents) {
481-
Ok(c) => c,
482432
Err(e) => {
483-
tracing::warn!("/usr/lib/os-release did not have valid UTF-8: {e}");
433+
tracing::warn!("Could not read /usr/lib/os-release: {e:?}");
484434
return Ok(None);
485435
}
486436
};
487437

488-
let parsed = OsReleaseInfo::parse(file_contents);
438+
let parsed = OsReleaseInfo::parse(&file_contents);
489439

490440
let os_id = parsed
491441
.get_value(&["ID"])
@@ -521,8 +471,8 @@ pub(crate) fn setup_composefs_bls_boot(
521471
) -> Result<String> {
522472
let id_hex = id.to_hex();
523473

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

@@ -539,12 +489,11 @@ pub(crate) fn setup_composefs_bls_boot(
539489
root_setup.physical_root_path.clone(),
540490
esp_part.path(),
541491
cmdline_options,
542-
fs,
543492
postfetch.detected_bootloader.clone(),
544493
)
545494
}
546495

547-
BootSetupType::Upgrade((storage, booted_cfs, fs, host)) => {
496+
BootSetupType::Upgrade((storage, booted_cfs, host)) => {
548497
let bootloader = host.require_composefs_booted()?.bootloader.clone();
549498

550499
let boot_dir = storage.require_boot_dir()?;
@@ -579,7 +528,6 @@ pub(crate) fn setup_composefs_bls_boot(
579528
Utf8PathBuf::from("/sysroot"),
580529
esp_dev.path(),
581530
cmdline,
582-
fs,
583531
bootloader,
584532
)
585533
}
@@ -653,7 +601,7 @@ pub(crate) fn setup_composefs_bls_boot(
653601
let boot_digest = compute_boot_digest(usr_lib_modules_vmlinuz, &repo)
654602
.context("Computing boot digest")?;
655603

656-
let osrel = parse_os_release(fs, &repo)?;
604+
let osrel = parse_os_release(mounted_erofs)?;
657605

658606
let (os_id, title, version, sort_key) = match osrel {
659607
Some((id_str, title_opt, version_opt)) => (
@@ -1078,7 +1026,7 @@ pub(crate) fn setup_composefs_uki_boot(
10781026
) -> Result<String> {
10791027
let (root_path, esp_device, bootloader, missing_fsverity_allowed, uki_addons) = match setup_type
10801028
{
1081-
BootSetupType::Setup((root_setup, state, postfetch, ..)) => {
1029+
BootSetupType::Setup((root_setup, state, postfetch)) => {
10821030
state.require_no_kargs_for_uki()?;
10831031

10841032
let esp_part = root_setup.device_info.find_partition_of_esp()?;
@@ -1092,7 +1040,7 @@ pub(crate) fn setup_composefs_uki_boot(
10921040
)
10931041
}
10941042

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

@@ -1235,25 +1183,36 @@ fn get_secureboot_keys(fs: &Dir, p: &str) -> Result<Option<SecurebootKeys>> {
12351183
pub(crate) async fn setup_composefs_boot(
12361184
root_setup: &RootSetup,
12371185
state: &State,
1238-
image_id: &str,
1186+
pull_result: &composefs_oci::skopeo::PullResult<Sha512HashValue>,
12391187
allow_missing_fsverity: bool,
12401188
) -> Result<()> {
12411189
const COMPOSEFS_BOOT_SETUP_JOURNAL_ID: &str = "1f0e9d8c7b6a5f4e3d2c1b0a9f8e7d6c5";
12421190

12431191
tracing::info!(
12441192
message_id = COMPOSEFS_BOOT_SETUP_JOURNAL_ID,
12451193
bootc.operation = "boot_setup",
1246-
bootc.image_id = image_id,
1194+
bootc.config_digest = %pull_result.config_digest,
12471195
bootc.allow_missing_fsverity = allow_missing_fsverity,
12481196
"Setting up composefs boot",
12491197
);
12501198

12511199
let mut repo = open_composefs_repo(&root_setup.physical_root)?;
1252-
repo.set_insecure(allow_missing_fsverity);
1200+
if allow_missing_fsverity {
1201+
repo.set_insecure();
1202+
}
1203+
1204+
let repo = Arc::new(repo);
1205+
1206+
// Generate the bootable EROFS image (idempotent).
1207+
let id = composefs_oci::generate_boot_image(&repo, &pull_result.manifest_digest)
1208+
.context("Generating bootable EROFS image")?;
1209+
1210+
// Get boot entries from the OCI filesystem (untransformed).
1211+
let fs = composefs_oci::image::create_filesystem(&*repo, &pull_result.config_digest, None)
1212+
.context("Creating composefs filesystem for boot entry discovery")?;
1213+
let entries =
1214+
get_boot_resources(&fs, &*repo).context("Extracting boot entries from OCI image")?;
12531215

1254-
let mut fs = create_composefs_filesystem(&repo, image_id, None)?;
1255-
let entries = fs.transform_for_boot(&repo)?;
1256-
let id = fs.commit_image(&repo, None)?;
12571216
let mounted_fs = Dir::reopen_dir(
12581217
&repo
12591218
.mount(&id.to_hex())
@@ -1296,16 +1255,23 @@ pub(crate) async fn setup_composefs_boot(
12961255

12971256
let boot_type = BootType::from(entry);
12981257

1258+
// Unwrap Arc to pass owned repo to boot setup functions.
1259+
let repo = Arc::try_unwrap(repo).map_err(|_| {
1260+
anyhow::anyhow!(
1261+
"BUG: Arc<Repository> still has other references after boot image generation"
1262+
)
1263+
})?;
1264+
12991265
let boot_digest = match boot_type {
13001266
BootType::Bls => setup_composefs_bls_boot(
1301-
BootSetupType::Setup((&root_setup, &state, &postfetch, &fs)),
1267+
BootSetupType::Setup((&root_setup, &state, &postfetch)),
13021268
repo,
13031269
&id,
13041270
entry,
13051271
&mounted_fs,
13061272
)?,
13071273
BootType::Uki => setup_composefs_uki_boot(
1308-
BootSetupType::Setup((&root_setup, &state, &postfetch, &fs)),
1274+
BootSetupType::Setup((&root_setup, &state, &postfetch)),
13091275
repo,
13101276
&id,
13111277
entries,
@@ -1319,11 +1285,7 @@ pub(crate) async fn setup_composefs_boot(
13191285
None,
13201286
boot_type,
13211287
boot_digest,
1322-
&get_container_manifest_and_config(&get_imgref(
1323-
&state.source.imageref.transport.to_string(),
1324-
&state.source.imageref.name,
1325-
))
1326-
.await?,
1288+
&pull_result.manifest_digest.to_string(),
13271289
allow_missing_fsverity,
13281290
)
13291291
.await?;

crates/lib/src/bootc_composefs/delete.rs

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -162,20 +162,6 @@ fn delete_depl_boot_entries(
162162
}
163163
}
164164

165-
#[fn_error_context::context("Deleting image for deployment {}", deployment_id)]
166-
pub(crate) fn delete_image(sysroot: &Dir, deployment_id: &str, dry_run: bool) -> Result<()> {
167-
let img_path = Path::new("composefs").join("images").join(deployment_id);
168-
tracing::debug!("Deleting EROFS image: {:?}", img_path);
169-
170-
if dry_run {
171-
return Ok(());
172-
}
173-
174-
sysroot
175-
.remove_file(&img_path)
176-
.context("Deleting EROFS image")
177-
}
178-
179165
#[fn_error_context::context("Deleting state directory for deployment {}", deployment_id)]
180166
pub(crate) fn delete_state_dir(sysroot: &Dir, deployment_id: &str, dry_run: bool) -> Result<()> {
181167
let state_dir = Path::new(STATE_DIR_RELATIVE).join(deployment_id);

crates/lib/src/bootc_composefs/digest.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use cap_std_ext::cap_std::fs::Dir;
1111
use cfsctl::composefs;
1212
use cfsctl::composefs_boot;
1313
use composefs::dumpfile;
14-
use composefs::fsverity::FsVerityHashValue;
14+
use composefs::fsverity::{Algorithm, FsVerityHashValue};
1515
use composefs_boot::BootOps as _;
1616
use tempfile::TempDir;
1717

@@ -29,9 +29,11 @@ pub(crate) fn new_temp_composefs_repo() -> Result<(TempDir, Arc<ComposefsReposit
2929

3030
td_dir.create_dir("repo")?;
3131
let repo_dir = td_dir.open_dir("repo")?;
32-
let mut repo = ComposefsRepository::open_path(&repo_dir, ".").context("Init cfs repo")?;
32+
let (mut repo, _created) =
33+
ComposefsRepository::init_path(&repo_dir, ".", Algorithm::SHA512, false)
34+
.context("Init cfs repo")?;
3335
// We don't need to hard require verity on the *host* system, we're just computing a checksum here
34-
repo.set_insecure(true);
36+
repo.set_insecure();
3537
Ok((td_guard, Arc::new(repo)))
3638
}
3739

crates/lib/src/bootc_composefs/export.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,9 @@ 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

48-
// We want the digest in the form of "sha256:abc123"
49-
let config_digest = format!("{}", imginfo.manifest.config().digest());
48+
let config_digest = imginfo.manifest.config().digest().clone();
5049

5150
let var_tmp =
5251
Dir::open_ambient_dir("/var/tmp", ambient_authority()).context("Opening /var/tmp")?;
@@ -55,8 +54,9 @@ pub async fn export_repo_to_image(
5554
let oci_dir = OciDir::ensure(tmpdir.try_clone()?).context("Opening OCI")?;
5655

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

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

0 commit comments

Comments
 (0)