diff --git a/Cargo.toml b/Cargo.toml index 6dd8ee7b0..5d9372c4d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,6 +36,9 @@ anyhow = "1.0.82" camino = "1.1.6" canon-json = "0.2.1" cap-std-ext = "4.0.3" +composefs = { git = "https://github.com/containers/composefs-rs", rev = "28d4721f77f973f0e394d60d6a69d9b39cb38d7f", package = "composefs", features = ["rhel9"] } +composefs-boot = { git = "https://github.com/containers/composefs-rs", rev = "28d4721f77f973f0e394d60d6a69d9b39cb38d7f", package = "composefs-boot" } +composefs-oci = { git = "https://github.com/containers/composefs-rs", rev = "28d4721f77f973f0e394d60d6a69d9b39cb38d7f", package = "composefs-oci" } chrono = { version = "0.4.38", default-features = false } clap = "4.5.4" clap_mangen = { version = "0.2.20" } diff --git a/crates/lib/src/cli.rs b/crates/lib/src/cli.rs index 940659214..e2f298a09 100644 --- a/crates/lib/src/cli.rs +++ b/crates/lib/src/cli.rs @@ -19,6 +19,7 @@ use ostree::gio; use ostree_container::store::PrepareResult; use ostree_ext::composefs::fsverity; use ostree_ext::composefs::fsverity::FsVerityHashValue; +use ostree_ext::composefs::splitstream::SplitStreamWriter; use ostree_ext::container as ostree_container; use ostree_ext::container_utils::ostree_booted; use ostree_ext::keyfileext::KeyFileExt; @@ -462,6 +463,8 @@ pub(crate) enum InternalsOpts { #[clap(allow_hyphen_values = true)] args: Vec, }, + /// Ensure that a composefs repository is initialized + TestComposefs, /// Loopback device cleanup helper (internal use only) LoopbackCleanupHelper { /// Device path to clean up @@ -1226,6 +1229,18 @@ async fn run_from_opt(opt: Opt) -> Result<()> { ) .await } + InternalsOpts::TestComposefs => { + // This is a stub to be replaced + let storage = get_storage().await?; + let cfs = storage.get_ensure_composefs()?; + let testdata = b"some test data"; + let testdata_digest = openssl::sha::sha256(testdata); + let mut w = SplitStreamWriter::new(&cfs, None, Some(testdata_digest)); + w.write_inline(testdata); + let object = cfs.write_stream(w, Some("testobject"))?.to_hex(); + assert_eq!(object, "5d94ceb0b2bb3a78237e0a74bc030a262239ab5f47754a5eb2e42941056b64cb21035d64a8f7c2f156e34b820802fa51884de2b1f7dc3a41b9878fc543cd9b07"); + Ok(()) + } // We don't depend on fsverity-utils today, so re-expose some helpful CLI tools. InternalsOpts::Fsverity(args) => match args { FsverityOpts::Measure { path } => { diff --git a/crates/lib/src/store/mod.rs b/crates/lib/src/store/mod.rs index 9c52a7b4f..bd3e8c5aa 100644 --- a/crates/lib/src/store/mod.rs +++ b/crates/lib/src/store/mod.rs @@ -1,18 +1,20 @@ use std::cell::OnceCell; use std::env; use std::ops::Deref; +use std::sync::Arc; use anyhow::{Context, Result}; use cap_std_ext::cap_std; -use cap_std_ext::cap_std::fs::Dir; +use cap_std_ext::cap_std::fs::{Dir, DirBuilder, DirBuilderExt as _}; use cap_std_ext::dirext::CapStdExtDirExt; use clap::ValueEnum; use fn_error_context::context; use ostree_ext::container::OstreeImageReference; use ostree_ext::keyfileext::KeyFileExt; -use ostree_ext::ostree; use ostree_ext::sysroot::SysrootLock; +use ostree_ext::{composefs, ostree}; +use rustix::fs::Mode; use crate::lsm; use crate::spec::ImageStatus; @@ -20,14 +22,37 @@ use crate::utils::deployment_fd; mod ostree_container; +/// See https://github.com/containers/composefs-rs/issues/159 +pub type ComposefsRepository = + composefs::repository::Repository; + +/// Path to the physical root +pub const SYSROOT: &str = "sysroot"; + +/// The toplevel composefs directory path +pub const COMPOSEFS: &str = "composefs"; +pub const COMPOSEFS_MODE: Mode = Mode::from_raw_mode(0o700); + /// The path to the bootc root directory, relative to the physical /// system root pub(crate) const BOOTC_ROOT: &str = "ostree/bootc"; pub(crate) struct Storage { + /// Directory holding the physical root + pub physical_root: Dir, + + /// The OSTree storage pub sysroot: SysrootLock, - run: Dir, + /// The composefs storage + pub composefs: OnceCell>, + /// The containers-image storage used foR LBIs imgstore: OnceCell, + + /// Our runtime state + run: Dir, + + /// This is a stub abstraction that tries to hide ostree + /// that we aren't really using right now pub store: Box, } @@ -71,12 +96,29 @@ impl Storage { }), Err(_) => crate::spec::Store::default(), }; - let store = load(store); + // ostree has historically always relied on + // having ostree -> sysroot/ostree as a symlink in the image to + // make it so that code doesn't need to distinguish between booted + // vs offline target. The ostree code all just looks at the ostree/ + // directory, and will follow the link in the booted case. + // + // For composefs we aren't going to do a similar thing, so here + // we need to explicitly distinguish the two and the storage + // here hence holds a reference to the physical root. + let ostree_sysroot_dir = crate::utils::sysroot_dir(&sysroot)?; + let physical_root = if sysroot.is_booted() { + ostree_sysroot_dir.open_dir(SYSROOT)? + } else { + ostree_sysroot_dir + }; + Ok(Self { + physical_root, sysroot, run, + composefs: Default::default(), store, imgstore: Default::default(), }) @@ -111,6 +153,31 @@ impl Storage { Ok(self.imgstore.get_or_init(|| imgstore)) } + pub(crate) fn get_ensure_composefs(&self) -> Result> { + if let Some(composefs) = self.composefs.get() { + return Ok(Arc::clone(composefs)); + } + + let mut db = DirBuilder::new(); + db.mode(COMPOSEFS_MODE.as_raw_mode()); + self.physical_root.ensure_dir_with(COMPOSEFS, &db)?; + + let mut composefs = + ComposefsRepository::open_path(&self.physical_root.open_dir(COMPOSEFS)?, ".")?; + + // Bootstrap verity off of the ostree state. In practice this means disabled by + // default right now. + let ostree_repo = &self.sysroot.repo(); + let ostree_verity = ostree_ext::fsverity::is_verity_enabled(ostree_repo)?; + if !ostree_verity.enabled { + tracing::debug!("Setting insecure mode for composefs repo"); + composefs.set_insecure(true); + } + let composefs = Arc::new(composefs); + let r = Arc::clone(self.composefs.get_or_init(|| composefs)); + Ok(r) + } + /// Update the mtime on the storage root directory #[context("Updating storage root mtime")] pub(crate) fn update_mtime(&self) -> Result<()> { diff --git a/crates/ostree-ext/Cargo.toml b/crates/ostree-ext/Cargo.toml index 6b9cc5441..9085dbe42 100644 --- a/crates/ostree-ext/Cargo.toml +++ b/crates/ostree-ext/Cargo.toml @@ -16,9 +16,9 @@ ostree = { features = ["v2025_2"], version = "0.20" } anyhow = { workspace = true } bootc-utils = { package = "bootc-internal-utils", path = "../utils", version = "0.0.0" } camino = { workspace = true, features = ["serde1"] } -composefs = { git = "https://github.com/containers/composefs-rs", rev = "28d4721f77f973f0e394d60d6a69d9b39cb38d7f", package = "composefs", features = ["rhel9"] } -composefs-boot = { git = "https://github.com/containers/composefs-rs", rev = "28d4721f77f973f0e394d60d6a69d9b39cb38d7f", package = "composefs-boot" } -composefs-oci = { git = "https://github.com/containers/composefs-rs", rev = "28d4721f77f973f0e394d60d6a69d9b39cb38d7f", package = "composefs-oci" } +composefs = { workspace = true } +composefs-boot = { workspace = true } +composefs-oci = { workspace = true } chrono = { workspace = true } clap = { workspace = true, features = ["derive","cargo"] } clap_mangen = { workspace = true, optional = true } diff --git a/tmt/tests/booted/readonly/030-test-composefs.nu b/tmt/tests/booted/readonly/030-test-composefs.nu new file mode 100644 index 000000000..1c185bfb6 --- /dev/null +++ b/tmt/tests/booted/readonly/030-test-composefs.nu @@ -0,0 +1,8 @@ +use std assert +use tap.nu + +tap begin "composefs integration smoke test" + +bootc internals test-composefs + +tap ok