Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
Expand Down
15 changes: 15 additions & 0 deletions crates/lib/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -462,6 +463,8 @@ pub(crate) enum InternalsOpts {
#[clap(allow_hyphen_values = true)]
args: Vec<OsString>,
},
/// Ensure that a composefs repository is initialized
TestComposefs,
/// Loopback device cleanup helper (internal use only)
LoopbackCleanupHelper {
/// Device path to clean up
Expand Down Expand 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 } => {
Expand Down
75 changes: 71 additions & 4 deletions crates/lib/src/store/mod.rs
Original file line number Diff line number Diff line change
@@ -1,33 +1,58 @@
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;
use crate::utils::deployment_fd;

mod ostree_container;

/// See https://github.com/containers/composefs-rs/issues/159
pub type ComposefsRepository =
composefs::repository::Repository<composefs::fsverity::Sha512HashValue>;

/// 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<Arc<ComposefsRepository>>,
/// The containers-image storage used foR LBIs
imgstore: OnceCell<crate::imgstorage::Storage>,

/// 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<dyn ContainerImageStoreImpl>,
}

Expand Down Expand Up @@ -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(),
})
Expand Down Expand Up @@ -111,6 +153,31 @@ impl Storage {
Ok(self.imgstore.get_or_init(|| imgstore))
}

pub(crate) fn get_ensure_composefs(&self) -> Result<Arc<ComposefsRepository>> {
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)?;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we not care about the returned bool for anything here? We just rely on the fact that if there's an error, we're unwraping it and we will return the error. The returned bool isn't really useful for us in this context I guess?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it just says whether or not the directory existed already which is information we don't need.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that makes sense. It would never make it to the next line if there was an error returned from ensure_dir_with(). There's no logical need to store and reference the returned value.


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)
Comment on lines +177 to +178
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noted your response to Gemini below, you mentioned you only expect this to be called once and don't necessarily require thread safety here. I'm just curious about your use of Arc over rc in that case? Since Arc is slightly more computationally expensive, I'm curious to understand your thought process.

There's every possibility that my lack of Rust experience is causing me to overlook something. In which case, consider this a question for my own educational purposes rather than a functional critique of your PR per se. :)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The underlying API wants Arc https://github.com/containers/composefs-rs/blob/126751dee8a71aa54c9666e69645d2fd1eccb176/crates/composefs/src/repository.rs#L112

Ultimately we definitely don't care about the refcounting overhead here, and in general Arc is needed for things like tokio with work stealing.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In which case, consider this a question for my own educational purposes rather than a functional critique of your PR per se. :)

Yes that's fine! Thanks for looking at the PR!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh, that makes sense then. Thanks for the clarification.

}

/// Update the mtime on the storage root directory
#[context("Updating storage root mtime")]
pub(crate) fn update_mtime(&self) -> Result<()> {
Expand Down
6 changes: 3 additions & 3 deletions crates/ostree-ext/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
8 changes: 8 additions & 0 deletions tmt/tests/booted/readonly/030-test-composefs.nu
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
use std assert
use tap.nu

tap begin "composefs integration smoke test"

bootc internals test-composefs

tap ok