From 3566c7a9c563b5a6b06108b1e06a001ded690520 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 2 Oct 2026 11:12:01 +0200 Subject: [PATCH] composefs-boot: Make composefs integration optional Allow e.g. bcvk to use the Android boot image and UKI parsers without pulling in the composefs dependency graph. Keep integration enabled by default so existing consumers retain their current APIs. Assisted-by: AI Signed-off-by: Alexander Larsson --- Cargo.toml | 2 +- crates/composefs-boot/Cargo.toml | 17 ++-- crates/composefs-boot/src/integration.rs | 109 +++++++++++++++++++++ crates/composefs-boot/src/lib.rs | 116 +++-------------------- crates/composefs-boot/src/uki.rs | 1 + 5 files changed, 132 insertions(+), 113 deletions(-) create mode 100644 crates/composefs-boot/src/integration.rs diff --git a/Cargo.toml b/Cargo.toml index 2726a77e..40b1572c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,7 +37,7 @@ composefs = { version = "0.9.3", path = "crates/composefs", default-features = f composefs-ctl = { version = "0.9.3", path = "crates/composefs-ctl", default-features = false } composefs-ioctls = { version = "0.9.3", path = "crates/composefs-ioctls", default-features = false } composefs-oci = { version = "0.9.3", path = "crates/composefs-oci", default-features = false } -composefs-boot = { version = "0.9.3", path = "crates/composefs-boot", default-features = false } +composefs-boot = { version = "0.9.3", path = "crates/composefs-boot" } composefs-fuse = { version = "0.9.3", path = "crates/composefs-fuse", default-features = false } composefs-http = { version = "0.9.3", path = "crates/composefs-http", default-features = false } composefs-ostree = { version = "0.9.3", path = "crates/composefs-ostree", default-features = false } diff --git a/crates/composefs-boot/Cargo.toml b/crates/composefs-boot/Cargo.toml index 0e32b453..d89af601 100644 --- a/crates/composefs-boot/Cargo.toml +++ b/crates/composefs-boot/Cargo.toml @@ -10,14 +10,17 @@ repository.workspace = true rust-version.workspace = true version.workspace = true +[features] +default = ["composefs-integration"] +composefs-integration = ["dep:anyhow", "dep:composefs", "dep:fn-error-context", "dep:pcre2", "dep:regex-automata", "dep:rustix"] + [dependencies] -anyhow = { version = "1.0.87", default-features = false } -fn-error-context = "0.2" -composefs = { workspace = true } -hex = { version = "0.4.0", default-features = false, features = ["std"] } -pcre2 = "0.2.11" -regex-automata = { version = "0.4.4", default-features = false, features = ["hybrid", "std", "syntax"] } -rustix = { version = "1.0.0", default-features = false, features = ["fs", "std"] } +anyhow = { version = "1.0.87", default-features = false, optional = true } +fn-error-context = { version = "0.2", optional = true } +composefs = { workspace = true, optional = true } +pcre2 = { version = "0.2.11", optional = true } +regex-automata = { version = "0.4.4", default-features = false, features = ["hybrid", "std", "syntax"], optional = true } +rustix = { version = "1.0.0", default-features = false, features = ["fs", "std"], optional = true } thiserror = { version = "2.0.0", default-features = false } zerocopy = { version = "0.8.0", default-features = false, features = ["derive"] } diff --git a/crates/composefs-boot/src/integration.rs b/crates/composefs-boot/src/integration.rs new file mode 100644 index 00000000..4fc3bde4 --- /dev/null +++ b/crates/composefs-boot/src/integration.rs @@ -0,0 +1,109 @@ +use std::ffi::OsStr; + +use anyhow::Result; +use rustix::fd::AsFd; + +use composefs::{fsverity::FsVerityHashValue, repository::Repository, tree::FileSystem}; + +use crate::{ + bootloader::{BootEntry, get_boot_resources}, + selabel, +}; + +/// These directories are required to exist in images. +/// They may have content in the container, but we don't +/// want to expose them in the final merged root. +/// +/// # /boot +/// +/// This is how sealed UKIs are handled; the UKI in /boot has the composefs +/// digest, so we can't include it in the rendered image. +/// +/// # /sysroot +/// +/// See https://github.com/containers/composefs-rs/issues/164 +/// Basically there is only content here in ostree-container cases, +/// and us traversing there for SELinux labeling will cause problems. +/// The ostree-container code special cases it in a different way, but +/// here we can just ignore it. +const REQUIRED_TOPLEVEL_TO_EMPTY_DIRS: &[&str] = &["boot", "sysroot"]; + +/// Empty the required top-level directories and set their mtime to match /usr. +fn empty_toplevel_dirs(fs: &mut FileSystem) -> Result<()> { + let usr = fs.root.get_directory(OsStr::new("usr"))?; + let usr_mtime_stat_sec = usr.stat.st_mtim_sec; + let usr_mtime_stat_nsec = usr.stat.st_mtim_nsec; + + for d in REQUIRED_TOPLEVEL_TO_EMPTY_DIRS { + let d = fs.root.get_directory_mut(d.as_ref())?; + d.stat.st_mtim_sec = usr_mtime_stat_sec; + d.stat.st_mtim_nsec = usr_mtime_stat_nsec; + d.clear(); + } + + Ok(()) +} + +/// Trait for transforming filesystem images for boot scenarios. +/// +/// This trait provides functionality to prepare composefs filesystem images for booting by +/// extracting boot resources and applying necessary transformations like SELinux labeling. +pub trait BootOps { + /// Transforms a filesystem image for boot by extracting boot entries and applying SELinux labels. + /// + /// This method extracts boot resources from the filesystem, empties required top-level + /// directories (/boot, /sysroot), and applies SELinux security contexts. + /// + /// # Arguments + /// + /// * `repo` - The composefs repository containing filesystem objects + /// + /// # Returns + /// + /// A vector of boot entries extracted from the filesystem (Type 1 BLS entries, Type 2 UKIs, etc.) + fn transform_for_boot( + &mut self, + repo: &Repository, + ) -> Result>>; + + /// Apply boot filesystem transformations using an on-disk directory for file content. + /// + /// This applies the same filesystem transformations as [`BootOps::transform_for_boot`] + /// (emptying /boot and /sysroot, SELinux relabeling) but reads SELinux policy files + /// directly from the on-disk filesystem via a directory fd rather than from the + /// composefs repository. + /// + /// This does not extract boot entries (Type 1 BLS entries, UKIs, etc.) since those + /// are only needed for writing to the boot partition, not for computing the composefs + /// digest. + /// + /// # Arguments + /// + /// * `rootfs` - A directory fd pointing to the root of the on-disk filesystem + fn transform_for_boot_from_dir(&mut self, rootfs: impl AsFd) -> Result<()>; +} + +impl BootOps for FileSystem { + fn transform_for_boot( + &mut self, + repo: &Repository, + ) -> Result>> { + let boot_entries = get_boot_resources(self, repo)?; + empty_toplevel_dirs(self)?; + // Compact the leaves table after clearing directories, so that leaves + // which were only referenced by /boot or /sysroot are removed and + // don't appear as orphans when the filesystem is validated. + self.compact(); + selabel::selabel(self, repo)?; + + Ok(boot_entries) + } + + fn transform_for_boot_from_dir(&mut self, rootfs: impl AsFd) -> Result<()> { + empty_toplevel_dirs(self)?; + // Same as above: compact to remove leaves orphaned by clearing dirs. + self.compact(); + selabel::selabel_from_dir(self, rootfs)?; + Ok(()) + } +} diff --git a/crates/composefs-boot/src/lib.rs b/crates/composefs-boot/src/lib.rs index 46706273..b8f111f0 100644 --- a/crates/composefs-boot/src/lib.rs +++ b/crates/composefs-boot/src/lib.rs @@ -4,124 +4,30 @@ //! scenarios by extracting boot resources, applying SELinux labels, and preparing //! bootloader entries. It supports both Boot Loader Specification (Type 1) entries //! and Unified Kernel Images (Type 2) for UEFI boot. +//! +//! The `composefs-integration` feature is enabled by default. Disable default features +//! to use the Android boot image and UKI parsers without the composefs integration dependencies. #![forbid(unsafe_code)] #![deny(missing_debug_implementations)] pub mod android_boot; +#[cfg(feature = "composefs-integration")] pub mod bootloader; +#[cfg(feature = "composefs-integration")] pub mod cmdline; pub mod os_release; +#[cfg(feature = "composefs-integration")] pub mod selabel; pub mod uki; +#[cfg(feature = "composefs-integration")] pub mod write_boot; #[cfg(doc)] pub mod design; -use std::ffi::OsStr; - -use anyhow::Result; -use rustix::fd::AsFd; - -use composefs::{fsverity::FsVerityHashValue, repository::Repository, tree::FileSystem}; - -use crate::bootloader::{BootEntry, get_boot_resources}; - -/// These directories are required to exist in images. -/// They may have content in the container, but we don't -/// want to expose them in the final merged root. -/// -/// # /boot -/// -/// This is how sealed UKIs are handled; the UKI in /boot has the composefs -/// digest, so we can't include it in the rendered image. -/// -/// # /sysroot -/// -/// See https://github.com/containers/composefs-rs/issues/164 -/// Basically there is only content here in ostree-container cases, -/// and us traversing there for SELinux labeling will cause problems. -/// The ostree-container code special cases it in a different way, but -/// here we can just ignore it. -const REQUIRED_TOPLEVEL_TO_EMPTY_DIRS: &[&str] = &["boot", "sysroot"]; - -/// Empty the required top-level directories and set their mtime to match /usr. -fn empty_toplevel_dirs(fs: &mut FileSystem) -> Result<()> { - let usr = fs.root.get_directory(OsStr::new("usr"))?; - let usr_mtime_stat_sec = usr.stat.st_mtim_sec; - let usr_mtime_stat_nsec = usr.stat.st_mtim_nsec; - - for d in REQUIRED_TOPLEVEL_TO_EMPTY_DIRS { - let d = fs.root.get_directory_mut(d.as_ref())?; - d.stat.st_mtim_sec = usr_mtime_stat_sec; - d.stat.st_mtim_nsec = usr_mtime_stat_nsec; - d.clear(); - } - - Ok(()) -} - -/// Trait for transforming filesystem images for boot scenarios. -/// -/// This trait provides functionality to prepare composefs filesystem images for booting by -/// extracting boot resources and applying necessary transformations like SELinux labeling. -pub trait BootOps { - /// Transforms a filesystem image for boot by extracting boot entries and applying SELinux labels. - /// - /// This method extracts boot resources from the filesystem, empties required top-level - /// directories (/boot, /sysroot), and applies SELinux security contexts. - /// - /// # Arguments - /// - /// * `repo` - The composefs repository containing filesystem objects - /// - /// # Returns - /// - /// A vector of boot entries extracted from the filesystem (Type 1 BLS entries, Type 2 UKIs, etc.) - fn transform_for_boot( - &mut self, - repo: &Repository, - ) -> Result>>; - - /// Apply boot filesystem transformations using an on-disk directory for file content. - /// - /// This applies the same filesystem transformations as [`BootOps::transform_for_boot`] - /// (emptying /boot and /sysroot, SELinux relabeling) but reads SELinux policy files - /// directly from the on-disk filesystem via a directory fd rather than from the - /// composefs repository. - /// - /// This does not extract boot entries (Type 1 BLS entries, UKIs, etc.) since those - /// are only needed for writing to the boot partition, not for computing the composefs - /// digest. - /// - /// # Arguments - /// - /// * `rootfs` - A directory fd pointing to the root of the on-disk filesystem - fn transform_for_boot_from_dir(&mut self, rootfs: impl AsFd) -> Result<()>; -} - -impl BootOps for FileSystem { - fn transform_for_boot( - &mut self, - repo: &Repository, - ) -> Result>> { - let boot_entries = get_boot_resources(self, repo)?; - empty_toplevel_dirs(self)?; - // Compact the leaves table after clearing directories, so that leaves - // which were only referenced by /boot or /sysroot are removed and - // don't appear as orphans when the filesystem is validated. - self.compact(); - selabel::selabel(self, repo)?; - - Ok(boot_entries) - } +#[cfg(feature = "composefs-integration")] +mod integration; - fn transform_for_boot_from_dir(&mut self, rootfs: impl AsFd) -> Result<()> { - empty_toplevel_dirs(self)?; - // Same as above: compact to remove leaves orphaned by clearing dirs. - self.compact(); - selabel::selabel_from_dir(self, rootfs)?; - Ok(()) - } -} +#[cfg(feature = "composefs-integration")] +pub use integration::BootOps; diff --git a/crates/composefs-boot/src/uki.rs b/crates/composefs-boot/src/uki.rs index 6f31f272..f9fc61c4 100644 --- a/crates/composefs-boot/src/uki.rs +++ b/crates/composefs-boot/src/uki.rs @@ -341,6 +341,7 @@ pub(crate) mod test { ) } + #[cfg(feature = "composefs-integration")] pub(crate) fn uki_with_linux_initrd() -> Vec { let linux = b"linux"; let initrd = b"initrd";