Skip to content

Commit 68c3e29

Browse files
committed
composefs: Update to latest, add unified storage pull path
For registry transports, pull_composefs_repo() now goes through bootc-owned containers-storage first (via podman pull), then imports from there into the composefs repo via cstor (zero-copy reflink/hardlink). This means the source image remains in containers-storage after upgrade, enabling 'podman run <booted-image>'. Non-registry transports (oci:, containers-storage:, docker-daemon:) continue using the direct skopeo path. Also fix composefs_oci::pull() callsite to pass the new zerocopy parameter added in the composefs-rs import-cstor-rs-rebase branch. Clean up GC to use CStorage::create() directly instead of going through storage.get_ensure_imgstore() which requires ostree and fails on composefs-only systems. Remove the unreferenced fsck module. Assisted-by: OpenCode (Claude Opus 4) Signed-off-by: Colin Walters <walters@verbum.org>
1 parent f1926b3 commit 68c3e29

21 files changed

Lines changed: 645 additions & 185 deletions

crates/lib/src/bootc_composefs/boot.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1198,7 +1198,7 @@ fn get_secureboot_keys(fs: &Dir, p: &str) -> Result<Option<SecurebootKeys>> {
11981198
pub(crate) async fn setup_composefs_boot(
11991199
root_setup: &RootSetup,
12001200
state: &State,
1201-
pull_result: &composefs_oci::skopeo::PullResult<Sha512HashValue>,
1201+
pull_result: &composefs_oci::PullResult<Sha512HashValue>,
12021202
allow_missing_fsverity: bool,
12031203
) -> Result<()> {
12041204
const COMPOSEFS_BOOT_SETUP_JOURNAL_ID: &str = "1f0e9d8c7b6a5f4e3d2c1b0a9f8e7d6c5";

crates/lib/src/bootc_composefs/digest.rs

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ use cfsctl::composefs_boot;
1414
use composefs::dumpfile;
1515
use composefs::fsverity::{Algorithm, FsVerityHashValue};
1616
use composefs_boot::BootOps as _;
17-
use rustix::fd::AsFd;
1817
use tempfile::TempDir;
1918

2019
use crate::store::ComposefsRepository;
@@ -68,13 +67,19 @@ pub(crate) async fn compute_composefs_digest(
6867
let (_td_guard, repo) = new_temp_composefs_repo()?;
6968

7069
// Read filesystem from path, transform for boot, compute digest
71-
let cwd_owned: OwnedFd = rustix::fs::CWD.as_fd().try_clone_to_owned()?;
70+
let dirfd: OwnedFd = rustix::fs::open(
71+
path.as_std_path(),
72+
rustix::fs::OFlags::RDONLY | rustix::fs::OFlags::DIRECTORY | rustix::fs::OFlags::CLOEXEC,
73+
rustix::fs::Mode::empty(),
74+
)
75+
.with_context(|| format!("Opening {path}"))?;
7276
let mut fs = composefs::fs::read_container_root(
73-
cwd_owned,
74-
path.as_std_path().to_path_buf(),
77+
dirfd,
78+
std::path::PathBuf::from("."),
7579
Some(repo.clone()),
7680
)
77-
.await?;
81+
.await
82+
.context("Reading container root")?;
7883
fs.transform_for_boot(&repo).context("Preparing for boot")?;
7984
let id = fs.compute_image_id();
8085
let digest = id.to_hex();
@@ -122,7 +127,7 @@ mod tests {
122127
Ok(())
123128
}
124129

125-
#[tokio::test]
130+
#[tokio::test(flavor = "multi_thread")]
126131
async fn test_compute_composefs_digest() {
127132
// Create temp directory with test filesystem structure
128133
let td = tempfile::tempdir().unwrap();

crates/lib/src/bootc_composefs/gc.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,8 @@ pub(crate) async fn composefs_gc(
303303
// deployments that predate the manifest→image link.
304304
let mut live_manifest_digests: Vec<composefs_oci::OciDigest> = Vec::new();
305305
let mut additional_roots = Vec::new();
306+
// Container image names for containers-storage pruning.
307+
let mut live_container_images: std::collections::HashSet<String> = Default::default();
306308

307309
// Read existing tags before the deployment loop so we can search
308310
// them for deployments that lack manifest_digest in their origin.
@@ -324,6 +326,19 @@ pub(crate) async fn composefs_gc(
324326
additional_roots.push(verity.clone());
325327

326328
if let Some(ini) = read_origin(sysroot, verity)? {
329+
// Collect the container image name for containers-storage GC.
330+
if let Some(container_ref) =
331+
ini.get::<String>("origin", ostree_ext::container::deploy::ORIGIN_CONTAINER)
332+
{
333+
// Parse the ostree image reference to extract the bare image name
334+
// (e.g. "quay.io/foo:tag" from "ostree-unverified-image:docker://quay.io/foo:tag")
335+
let image_name = container_ref
336+
.parse::<ostree_ext::container::OstreeImageReference>()
337+
.map(|r| r.imgref.name)
338+
.unwrap_or_else(|_| container_ref.clone());
339+
live_container_images.insert(image_name);
340+
}
341+
327342
if let Some(manifest_digest_str) =
328343
ini.get::<String>(ORIGIN_KEY_IMAGE, ORIGIN_KEY_MANIFEST_DIGEST)
329344
{
@@ -407,6 +422,21 @@ pub(crate) async fn composefs_gc(
407422
.map(|x| x.as_str())
408423
.collect::<Vec<_>>();
409424

425+
// Prune containers-storage: remove images not backing any live deployment.
426+
if !dry_run && !live_container_images.is_empty() {
427+
let subpath = crate::podstorage::CStorage::subpath();
428+
if sysroot.try_exists(&subpath).unwrap_or(false) {
429+
let run = Dir::open_ambient_dir("/run", cap_std_ext::cap_std::ambient_authority())?;
430+
let imgstore = crate::podstorage::CStorage::create(&sysroot, &run, None)?;
431+
let roots: std::collections::HashSet<&str> =
432+
live_container_images.iter().map(|s| s.as_str()).collect();
433+
let pruned = imgstore.prune_except_roots(&roots).await?;
434+
if !pruned.is_empty() {
435+
tracing::info!("Pruned {} images from containers-storage", pruned.len());
436+
}
437+
}
438+
}
439+
410440
// Run garbage collection. Tags root the OCI metadata chain
411441
// (manifest → config → layers). The additional_roots protect EROFS
412442
// images for deployments that predate the manifest→image link;

0 commit comments

Comments
 (0)