Skip to content

Commit 2c5fbb5

Browse files
committed
bootc: Adapt to a few composefs-rs API changes
Assisted-by: OpenCode (Claude Sonnet 4) Signed-off-by: Colin Walters <walters@verbum.org>
1 parent cc0d259 commit 2c5fbb5

11 files changed

Lines changed: 115 additions & 47 deletions

File tree

crates/etc-merge/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ impl From<(&cap_std::fs::Metadata, Xattrs)> for MyStat {
5252
st_uid: value.0.uid(),
5353
st_gid: value.0.gid(),
5454
st_mtim_sec: value.0.mtime(),
55+
st_mtim_nsec: value.0.mtime_nsec() as u32,
5556
xattrs: value.1,
5657
})
5758
}

crates/initramfs/src/lib.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ use composefs::{
2828
mountcompat::{overlayfs_set_fd, overlayfs_set_lower_and_data_fds, prepare_mount},
2929
repository::Repository,
3030
};
31-
use composefs_boot::cmdline::get_cmdline_composefs;
31+
use composefs_boot::cmdline::ComposefsCmdline;
3232
use composefs_ctl::composefs;
3333
use composefs_ctl::composefs_boot;
3434

@@ -463,7 +463,10 @@ pub fn setup_root(args: Args) -> Result<()> {
463463
config
464464
};
465465

466-
let (image, insecure) = get_cmdline_composefs::<Sha512HashValue>(&cmdline)?;
466+
let composefs = ComposefsCmdline::<Sha512HashValue>::from_cmdline(&cmdline)?
467+
.context("No composefs= or composefs.digest.v1= karg found")?;
468+
let image = composefs.digest();
469+
let insecure = composefs.is_insecure();
467470

468471
let new_root = match &args.root_fs {
469472
Some(path) => open_root_fs(path).context("Failed to clone specified root fs")?,

crates/lib/src/bootc_composefs/boot.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,9 @@ use composefs_boot::bootloader::{
8282
BootEntry as ComposefsBootEntry, EFI_ADDON_DIR_EXT, EFI_ADDON_FILE_EXT, EFI_EXT, PEType,
8383
UsrLibModulesVmlinuz, get_boot_resources,
8484
};
85-
use composefs_boot::{cmdline::get_cmdline_composefs, os_release::OsReleaseInfo, uki};
85+
use composefs_boot::{
86+
cmdline::ComposefsCmdline as BootComposefsCmdline, os_release::OsReleaseInfo, uki,
87+
};
8688
use composefs_ctl::composefs;
8789
use composefs_ctl::composefs_boot;
8890
use composefs_ctl::composefs_oci;
@@ -811,8 +813,11 @@ fn write_pe_to_esp(
811813
if matches!(pe_type, PEType::Uki) {
812814
let cmdline = uki::get_cmdline_buffered(&mut uki_reader).context("Getting UKI cmdline")?;
813815

814-
let (composefs_cmdline, missing_verity_allowed_cmdline) =
815-
get_cmdline_composefs::<Sha512HashValue>(&cmdline).context("Parsing composefs=")?;
816+
let parsed_cmdline = BootComposefsCmdline::<Sha512HashValue>::from_cmdline(&cmdline)
817+
.context("Parsing composefs=")?
818+
.context("No composefs= or composefs.digest.v1= karg found in UKI cmdline")?;
819+
let composefs_cmdline = parsed_cmdline.digest().clone();
820+
let missing_verity_allowed_cmdline = parsed_cmdline.is_insecure();
816821

817822
// If the UKI cmdline does not match what the user has passed as cmdline option
818823
// NOTE: This will only be checked for new installs and now upgrades/switches

crates/lib/src/bootc_composefs/digest.rs

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ use camino::Utf8Path;
1010
use cap_std_ext::cap_std;
1111
use cap_std_ext::cap_std::fs::Dir;
1212
use composefs::dumpfile;
13-
use composefs::fsverity::{Algorithm, FsVerityHashValue};
13+
use composefs::erofs::format::FormatVersion;
14+
use composefs::fsverity::FsVerityHashValue;
15+
use composefs::repository::RepositoryConfig;
1416
use composefs_boot::BootOps as _;
1517
use composefs_ctl::composefs;
1618
use composefs_ctl::composefs_boot;
@@ -20,21 +22,26 @@ use crate::store::ComposefsRepository;
2022

2123
/// Creates a temporary composefs repository for computing digests.
2224
///
25+
/// The `erofs_version` controls which EROFS format the digest is computed for:
26+
/// use `FormatVersion::V1` to get a `composefs.digest.v1=` karg (V1 EROFS, C-tool
27+
/// compatible) or `FormatVersion::V2` for the legacy `composefs=` karg.
28+
///
2329
/// Returns the TempDir guard (must be kept alive for the repo to remain valid)
2430
/// and the repository wrapped in Arc.
2531
#[fn_error_context::context("Creating new temp composefs repo")]
26-
pub(crate) fn new_temp_composefs_repo() -> Result<(TempDir, Arc<ComposefsRepository>)> {
32+
pub(crate) fn new_temp_composefs_repo(
33+
erofs_version: FormatVersion,
34+
) -> Result<(TempDir, Arc<ComposefsRepository>)> {
2735
let td_guard = tempfile::tempdir_in("/var/tmp")?;
2836
let td_path = td_guard.path();
2937
let td_dir = Dir::open_ambient_dir(td_path, cap_std::ambient_authority())?;
3038

3139
td_dir.create_dir("repo")?;
3240
let repo_dir = td_dir.open_dir("repo")?;
33-
let (mut repo, _created) =
34-
ComposefsRepository::init_path(&repo_dir, ".", Algorithm::SHA512, false)
35-
.context("Init cfs repo")?;
36-
// We don't need to hard require verity on the *host* system, we're just computing a checksum here
37-
repo.set_insecure();
41+
let mut config = RepositoryConfig::new(composefs::fsverity::Algorithm::SHA512).set_insecure();
42+
config.erofs_formats = composefs::erofs::format::FormatConfig::single(erofs_version);
43+
let (repo, _created) =
44+
ComposefsRepository::init_path(&repo_dir, ".", config).context("Init cfs repo")?;
3845
Ok((td_guard, Arc::new(repo)))
3946
}
4047

@@ -58,13 +65,14 @@ pub(crate) fn new_temp_composefs_repo() -> Result<(TempDir, Arc<ComposefsReposit
5865
#[fn_error_context::context("Computing composefs digest")]
5966
pub(crate) async fn compute_composefs_digest(
6067
path: &Utf8Path,
68+
erofs_version: FormatVersion,
6169
write_dumpfile_to: Option<&Utf8Path>,
6270
) -> Result<String> {
6371
if path.as_str() == "/" {
6472
anyhow::bail!("Cannot operate on active root filesystem; mount separate target instead");
6573
}
6674

67-
let (_td_guard, repo) = new_temp_composefs_repo()?;
75+
let (_td_guard, repo) = new_temp_composefs_repo(erofs_version)?;
6876

6977
// Read filesystem from path, transform for boot, compute digest
7078
let dirfd: OwnedFd = rustix::fs::open(
@@ -81,7 +89,7 @@ pub(crate) async fn compute_composefs_digest(
8189
.await
8290
.context("Reading container root")?;
8391
fs.transform_for_boot(&repo).context("Preparing for boot")?;
84-
let id = fs.compute_image_id();
92+
let id = fs.compute_image_id(erofs_version);
8593
let digest = id.to_hex();
8694

8795
if let Some(dumpfile_path) = write_dumpfile_to {
@@ -135,7 +143,9 @@ mod tests {
135143

136144
// Compute the digest
137145
let path = Utf8Path::from_path(td.path()).unwrap();
138-
let digest = compute_composefs_digest(path, None).await.unwrap();
146+
let digest = compute_composefs_digest(path, FormatVersion::V2, None)
147+
.await
148+
.unwrap();
139149

140150
// Verify it's a valid hex string of expected length (SHA-512 = 128 hex chars)
141151
assert_eq!(
@@ -150,7 +160,9 @@ mod tests {
150160
);
151161

152162
// Verify consistency - computing twice on the same filesystem produces the same result
153-
let digest2 = compute_composefs_digest(path, None).await.unwrap();
163+
let digest2 = compute_composefs_digest(path, FormatVersion::V2, None)
164+
.await
165+
.unwrap();
154166
assert_eq!(
155167
digest, digest2,
156168
"Digest should be consistent across multiple computations"
@@ -159,7 +171,7 @@ mod tests {
159171

160172
#[tokio::test]
161173
async fn test_compute_composefs_digest_rejects_root() {
162-
let result = compute_composefs_digest(Utf8Path::new("/"), None).await;
174+
let result = compute_composefs_digest(Utf8Path::new("/"), FormatVersion::V2, None).await;
163175
assert!(result.is_err());
164176
let err = result.unwrap_err();
165177
let found = err.chain().any(|e| {

crates/lib/src/bootc_composefs/repo.rs

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -99,16 +99,18 @@ pub(crate) async fn initialize_composefs_repository(
9999

100100
crate::store::ensure_composefs_dir(rootfs_dir)?;
101101

102-
let (mut repo, _created) = crate::store::ComposefsRepository::init_path(
103-
rootfs_dir,
104-
"composefs",
105-
composefs::fsverity::Algorithm::SHA512,
106-
!allow_missing_fsverity,
107-
)
108-
.context("Failed to initialize composefs repository")?;
109-
if allow_missing_fsverity {
110-
repo.set_insecure();
111-
}
102+
let config = {
103+
let c =
104+
composefs::repository::RepositoryConfig::new(composefs::fsverity::Algorithm::SHA512);
105+
if allow_missing_fsverity {
106+
c.set_insecure()
107+
} else {
108+
c
109+
}
110+
};
111+
let (repo, _created) =
112+
crate::store::ComposefsRepository::init_path(rootfs_dir, "composefs", config)
113+
.context("Failed to initialize composefs repository")?;
112114

113115
let imgref: containers_image_proxy::ImageReference = state
114116
.source

crates/lib/src/bootc_composefs/update.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ pub(crate) fn validate_update(
141141
let mut fs = create_filesystem(repo, &oci_digest, Some(config_verity))?;
142142
fs.transform_for_boot(&repo)?;
143143

144-
let image_id = fs.compute_image_id();
144+
let image_id = fs.compute_image_id(composefs::erofs::format::FormatVersion::V2);
145145

146146
let all_deployments = host.all_composefs_deployments()?;
147147

crates/lib/src/cli.rs

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ use clap::CommandFactory;
1717
use clap::Parser;
1818
use clap::ValueEnum;
1919
use composefs::dumpfile;
20+
use composefs::erofs::format::FormatVersion;
2021
use composefs::fsverity;
2122
use composefs::fsverity::FsVerityHashValue;
2223
use composefs_ctl::composefs;
@@ -438,6 +439,14 @@ pub(crate) enum ContainerOpts {
438439
#[clap(long)]
439440
allow_missing_verity: bool,
440441

442+
/// EROFS format version to use when computing the composefs digest.
443+
///
444+
/// V1 produces a `composefs.digest.v1=<hex>` karg (C-tool compatible).
445+
/// V2 produces the legacy `composefs=<hex>` karg (composefs-rs native).
446+
/// Must match the format version used when images were committed to the repository.
447+
#[clap(hide = true, long, default_value = "v2")]
448+
erofs_version: ErofsVersionArg,
449+
441450
/// Write a dumpfile to this path
442451
#[clap(long)]
443452
write_dumpfile_to: Option<Utf8PathBuf>,
@@ -484,6 +493,24 @@ pub(crate) enum ContainerOpts {
484493
},
485494
}
486495

496+
/// EROFS format version for `bootc container ukify --erofs-version`.
497+
#[derive(Debug, Clone, Copy, ValueEnum, PartialEq, Eq)]
498+
pub(crate) enum ErofsVersionArg {
499+
/// V1 EROFS (C-tool compatible, `composefs.digest.v1=` karg).
500+
V1,
501+
/// V2 EROFS (composefs-rs native, `composefs=` karg). Default.
502+
V2,
503+
}
504+
505+
impl From<ErofsVersionArg> for FormatVersion {
506+
fn from(v: ErofsVersionArg) -> Self {
507+
match v {
508+
ErofsVersionArg::V1 => FormatVersion::V1,
509+
ErofsVersionArg::V2 => FormatVersion::V2,
510+
}
511+
}
512+
}
513+
487514
#[derive(Debug, Clone, ValueEnum, PartialEq, Eq)]
488515
pub(crate) enum ExportFormat {
489516
/// Export as tar archive
@@ -1841,15 +1868,20 @@ async fn run_from_opt(opt: Opt) -> Result<()> {
18411868
path,
18421869
write_dumpfile_to,
18431870
} => {
1844-
let digest = compute_composefs_digest(&path, write_dumpfile_to.as_deref()).await?;
1871+
let digest = compute_composefs_digest(
1872+
&path,
1873+
FormatVersion::V2,
1874+
write_dumpfile_to.as_deref(),
1875+
)
1876+
.await?;
18451877
println!("{digest}");
18461878
Ok(())
18471879
}
18481880
ContainerOpts::ComputeComposefsDigestFromStorage {
18491881
write_dumpfile_to,
18501882
image,
18511883
} => {
1852-
let (_td_guard, repo) = new_temp_composefs_repo()?;
1884+
let (_td_guard, repo) = new_temp_composefs_repo(FormatVersion::V2)?;
18531885

18541886
let mut proxycfg = crate::deploy::new_proxy_config();
18551887

@@ -1892,7 +1924,7 @@ async fn run_from_opt(opt: Opt) -> Result<()> {
18921924
)
18931925
.context("Populating fs")?;
18941926
fs.transform_for_boot(&repo).context("Preparing for boot")?;
1895-
let id = fs.compute_image_id();
1927+
let id = fs.compute_image_id(composefs::erofs::format::FormatVersion::V2);
18961928
println!("{}", id.to_hex());
18971929

18981930
if let Some(path) = write_dumpfile_to.as_deref() {
@@ -1908,6 +1940,7 @@ async fn run_from_opt(opt: Opt) -> Result<()> {
19081940
rootfs,
19091941
kargs,
19101942
allow_missing_verity,
1943+
erofs_version,
19111944
write_dumpfile_to,
19121945
kernel_dir,
19131946
args,
@@ -1940,6 +1973,7 @@ async fn run_from_opt(opt: Opt) -> Result<()> {
19401973
&args,
19411974
kernel,
19421975
allow_missing_verity,
1976+
erofs_version.into(),
19431977
write_dumpfile_to.as_deref(),
19441978
)
19451979
.await

crates/lib/src/install.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2023,8 +2023,9 @@ async fn install_to_filesystem_impl(
20232023
let (cfs_repo, _created) = crate::store::ComposefsRepository::init_path(
20242024
&rootfs.physical_root,
20252025
crate::store::COMPOSEFS,
2026-
composefs_ctl::composefs::fsverity::Algorithm::SHA512,
2027-
false,
2026+
composefs_ctl::composefs::repository::RepositoryConfig::new(
2027+
composefs_ctl::composefs::fsverity::Algorithm::SHA512,
2028+
),
20282029
)?;
20292030
crate::deploy::check_disk_space_composefs(
20302031
&cfs_repo,

crates/lib/src/store/mod.rs

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ use ostree_ext::{gio, ostree};
111111
use rustix::fs::Mode;
112112

113113
use composefs::fsverity::Sha512HashValue;
114+
use composefs::repository::RepositoryConfig;
114115
use composefs_ctl::composefs;
115116

116117
use crate::bootc_composefs::backwards_compat::bcompat_boot::prepend_custom_prefix;
@@ -618,16 +619,19 @@ impl Storage {
618619
let ostree = self.get_ostree()?;
619620
let ostree_repo = &ostree.repo();
620621
let ostree_verity = ostree_ext::fsverity::is_verity_enabled(ostree_repo)?;
621-
let (mut composefs, _created) = ComposefsRepository::init_path(
622-
self.physical_root.open_dir(COMPOSEFS)?,
623-
".",
624-
composefs::fsverity::Algorithm::SHA512,
625-
ostree_verity.enabled,
626-
)?;
622+
let config = {
623+
let c = RepositoryConfig::new(composefs::fsverity::Algorithm::SHA512);
624+
if !ostree_verity.enabled {
625+
c.set_insecure()
626+
} else {
627+
c
628+
}
629+
};
627630
if !ostree_verity.enabled {
628631
tracing::debug!("Setting insecure mode for composefs repo");
629-
composefs.set_insecure();
630632
}
633+
let (composefs, _created) =
634+
ComposefsRepository::init_path(self.physical_root.open_dir(COMPOSEFS)?, ".", config)?;
631635
let composefs = Arc::new(composefs);
632636
let r = Arc::clone(self.composefs.get_or_init(|| composefs));
633637
Ok(r)

crates/lib/src/testutils.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -164,14 +164,15 @@ impl TestRoot {
164164

165165
// Initialize the composefs repo (creates meta.json)
166166
let repo_dir = root.open_dir("composefs")?;
167-
let (mut repo, _created) = ComposefsRepository::init_path(
167+
let (repo, _created) = ComposefsRepository::init_path(
168168
&repo_dir,
169169
".",
170-
composefs_ctl::composefs::fsverity::Algorithm::SHA512,
171-
false,
170+
composefs_ctl::composefs::repository::RepositoryConfig::new(
171+
composefs_ctl::composefs::fsverity::Algorithm::SHA512,
172+
)
173+
.set_insecure(),
172174
)
173175
.context("Initializing composefs repo")?;
174-
repo.set_insecure();
175176

176177
let mut test_root = Self {
177178
root,

0 commit comments

Comments
 (0)