@@ -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