@@ -14,8 +14,6 @@ mod transport;
1414use std::fs::OpenOptions;
1515use std::path::{Path, PathBuf};
1616
17- use fs2::FileExt;
18-
1917use hyperlight_common::flatbuffer_wrappers::host_function_details::HostFunctionDetails;
2018use hyperlight_common::vmem::PAGE_SIZE;
2119use oci_spec::image::{
@@ -149,6 +147,22 @@ fn read_layout_marker(path: &Path) -> crate::Result<()> {
149147 Ok(())
150148}
151149
150+
151+ /// Validate an existing OCI layout marker, if present.
152+ /// Returns whether the marker already exists.
153+ fn validate_existing_layout(path: &Path) -> crate::Result<bool> {
154+ let marker = path.join("oci-layout");
155+ let exists = marker
156+ .try_exists()
157+ .map_err(|e| crate::new_error!("save: failed to stat {:?}: {}", marker, e))?;
158+
159+ if exists {
160+ read_layout_marker(path)?;
161+ }
162+
163+ Ok(exists)
164+ }
165+
152166fn load_manifest(
153167 path: &Path,
154168 blobs_dir: &Path,
@@ -400,6 +414,14 @@ impl Snapshot {
400414 }
401415 }
402416
417+ // Reject unsupported layouts before writing snapshot data.
418+ validate_existing_layout(path)?;
419+ let layout_marker = path.join("oci-layout");
420+
421+ // Generate content-addressed snapshot blobs outside the index lock.
422+ let new_desc = self.write_blobs_and_build_descriptor(path, tag, &cfg, &cfg_bytes)?;
423+ let written_digest = OciDigest::from_oci_spec_digest(new_desc.digest());
424+
403425 // Serialize layout validation and the index read-modify-write
404426 // transaction across cooperating processes.
405427 // Use a stable lock file, not index.json, which is atomically replaced.
@@ -412,40 +434,13 @@ impl Snapshot {
412434 .map_err(|e| {
413435 crate::new_error!("save: failed to open index lock {:?}: {}", lock_path, e)
414436 })?;
415- lock_file.lock_exclusive ().map_err(|e| {
437+ lock_file.lock ().map_err(|e| {
416438 crate::new_error!("save: failed to acquire index lock {:?}: {}", lock_path, e)
417439 })?;
418440 // File lock is released when lock_file is dropped on all exit paths.
419441
420- // Validate the existing marker while holding the lock.
421- // Invalid layouts are rejected before writing snapshot data.
422- let layout_marker = path.join("oci-layout");
423- let marker_existed = layout_marker
424- .try_exists()
425- .map_err(|e| crate::new_error!("save: failed to stat {:?}: {}", layout_marker, e))?;
426- if marker_existed {
427- let bytes = read_bounded(&layout_marker, MAX_JSON_BLOB_SIZE).map_err(|e| {
428- crate::new_error!("save: failed to read existing oci-layout: {}", e)
429- })?;
430- let v: serde_json::Value = serde_json::from_slice(&bytes).map_err(|e| {
431- crate::new_error!("save: existing oci-layout is not valid JSON: {}", e)
432- })?;
433- match v.get("imageLayoutVersion").and_then(|s| s.as_str()) {
434- Some(s) if s == OCI_LAYOUT_VERSION => {}
435- Some(other) => {
436- return Err(crate::new_error!(
437- "save: existing imageLayoutVersion {:?} is unsupported (expected {:?})",
438- other,
439- OCI_LAYOUT_VERSION
440- ));
441- }
442- None => {
443- return Err(crate::new_error!(
444- "save: existing oci-layout is missing imageLayoutVersion"
445- ));
446- }
447- }
448- }
442+ // Revalidate under the lock in case the layout changed.
443+ let marker_existed = validate_existing_layout(path)?;
449444
450445 let index_path = path.join("index.json");
451446 let index_existed = index_path
@@ -466,9 +461,6 @@ impl Snapshot {
466461 Vec::new()
467462 };
468463
469- let new_desc = self.write_blobs_and_build_descriptor(path, tag, &cfg, &cfg_bytes)?;
470- let written_digest = OciDigest::from_oci_spec_digest(new_desc.digest());
471-
472464 // Replacement is by tag, not by digest: a new snapshot may
473465 // hash to a different value but still claim the same logical
474466 // ref. Blobs from the replaced manifest become orphans.
0 commit comments