Skip to content

Commit 7552751

Browse files
committed
composefs-oci: add support for splitfdstream
Assisted-by: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
1 parent 1550e8d commit 7552751

5 files changed

Lines changed: 446 additions & 2 deletions

File tree

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ composefs-ioctls = { version = "0.3.0", path = "crates/composefs-ioctls", defaul
2222
composefs-oci = { version = "0.3.0", path = "crates/composefs-oci", default-features = false }
2323
composefs-boot = { version = "0.3.0", path = "crates/composefs-boot", default-features = false }
2424
composefs-http = { version = "0.3.0", path = "crates/composefs-http", default-features = false }
25+
splitfdstream = { version = "0.3.0", path = "crates/splitfdstream", default-features = false }
2526

2627
[profile.dev.package.sha2]
2728
# this is *really* slow otherwise

crates/composefs-oci/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,12 @@ fn-error-context = "0.2"
1616
async-compression = { version = "0.4.0", default-features = false, features = ["tokio", "zstd", "gzip"] }
1717
bytes = { version = "1", default-features = false }
1818
composefs = { workspace = true }
19+
splitfdstream = { workspace = true }
1920
containers-image-proxy = { version = "0.9.2", default-features = false }
2021
hex = { version = "0.4.0", default-features = false }
2122
indicatif = { version = "0.17.0", default-features = false, features = ["tokio"] }
2223
oci-spec = { version = "0.8.0", default-features = false }
23-
rustix = { version = "1.0.0", features = ["fs"] }
24+
rustix = { version = "1.0.0", features = ["fs", "net"] }
2425
serde = { version = "1.0", default-features = false, features = ["derive"] }
2526
serde_json = { version = "1.0", default-features = false, features = ["std"] }
2627
sha2 = { version = "0.10.1", default-features = false }

crates/composefs-oci/src/lib.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,13 @@
1515
pub mod image;
1616
pub mod oci_image;
1717
pub mod skopeo;
18+
pub mod splitfdstream;
1819
pub mod tar;
1920

21+
pub use splitfdstream::{
22+
import_complete_image_from_splitfdstream, import_from_splitfdstream, CompleteImageImportResult,
23+
};
24+
2025
use std::{collections::HashMap, io::Read, sync::Arc};
2126

2227
use anyhow::{bail, ensure, Context, Result};
@@ -39,7 +44,7 @@ pub use skopeo::{pull_image, PullResult};
3944

4045
type ContentAndVerity<ObjectID> = (String, ObjectID);
4146

42-
fn layer_identifier(diff_id: &str) -> String {
47+
pub(crate) fn layer_identifier(diff_id: &str) -> String {
4348
format!("oci-layer-{diff_id}")
4449
}
4550

crates/composefs-oci/src/oci_image.rs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -594,6 +594,53 @@ pub fn write_manifest<ObjectID: FsVerityHashValue>(
594594
Ok((manifest_digest.to_string(), id))
595595
}
596596

597+
/// Writes a manifest to the repository from raw JSON bytes.
598+
///
599+
/// Unlike [`write_manifest`], this preserves the exact JSON bytes from the
600+
/// original source, avoiding digest mismatches from re-serialization.
601+
pub fn write_manifest_raw<ObjectID: FsVerityHashValue>(
602+
repo: &Arc<Repository<ObjectID>>,
603+
manifest_json: &[u8],
604+
manifest_digest: &str,
605+
config_verity: &ObjectID,
606+
layer_verities: &HashMap<Box<str>, ObjectID>,
607+
reference: Option<&str>,
608+
) -> Result<(String, ObjectID)> {
609+
let content_id = manifest_identifier(manifest_digest);
610+
611+
if let Some(verity) = repo.has_stream(&content_id)? {
612+
if let Some(name) = reference {
613+
tag_image(repo, manifest_digest, name)?;
614+
}
615+
return Ok((manifest_digest.to_string(), verity));
616+
}
617+
618+
let computed = hash(manifest_json);
619+
ensure!(
620+
manifest_digest == computed,
621+
"Manifest digest mismatch: expected {manifest_digest}, got {computed}"
622+
);
623+
624+
let manifest: ImageManifest =
625+
serde_json::from_slice(manifest_json).context("parsing manifest JSON")?;
626+
627+
let mut stream = repo.create_stream(OCI_MANIFEST_CONTENT_TYPE);
628+
629+
let config_key = format!("config:{}", manifest.config().digest());
630+
stream.add_named_stream_ref(&config_key, config_verity);
631+
632+
for (diff_id, verity) in layer_verities {
633+
stream.add_named_stream_ref(diff_id, verity);
634+
}
635+
636+
stream.write_external(manifest_json)?;
637+
638+
let oci_ref = reference.map(oci_ref_path);
639+
let id = repo.write_stream(stream, &content_id, oci_ref.as_deref())?;
640+
641+
Ok((manifest_digest.to_string(), id))
642+
}
643+
597644
/// Checks if a manifest exists.
598645
pub fn has_manifest<ObjectID: FsVerityHashValue>(
599646
repo: &Repository<ObjectID>,

0 commit comments

Comments
 (0)