1mod config;
8mod digest;
9mod fsutil;
10mod media_types;
11pub(crate) mod reference;
12
13use std::path::{Path, PathBuf};
14
15use hyperlight_common::flatbuffer_wrappers::host_function_details::HostFunctionDetails;
16use hyperlight_common::vmem::PAGE_SIZE;
17use oci_spec::image::{
18 Descriptor, DescriptorBuilder, ImageIndex, ImageIndexBuilder, ImageManifest,
19 ImageManifestBuilder, MediaType, SCHEMA_VERSION,
20};
21
22use self::config::{Arch, CpuVendor, HostFunction, Hypervisor, MemoryLayout, OciSnapshotConfig};
23use self::digest::{Digest256, oci_digest, parse_oci_digest, verify_blob_bytes, verify_blob_file};
24use self::fsutil::{put_blob, put_blob_if_absent, read_bounded, replace_file_atomic};
25use self::media_types::{
26 ANNOTATION_ARCH, ANNOTATION_CPU, ANNOTATION_HYPERVISOR, ANNOTATION_REF_NAME,
27};
28pub(super) use self::media_types::{
29 MT_CONFIG_CURRENT, MT_CONFIG_V1, MT_SNAPSHOT_CURRENT, MT_SNAPSHOT_V1, SNAPSHOT_ABI_VERSION,
30};
31use self::reference::{OciDigest, OciReference, OciTag};
32use super::{NextAction, Snapshot};
33use crate::mem::layout::SandboxMemoryLayout;
34use crate::mem::memory_region::MemoryRegionFlags;
35use crate::mem::shared_mem::{ReadonlySharedMemory, SharedMemory};
36
37pub(super) const OCI_LAYOUT_VERSION: &str = "1.0.0";
38
39#[doc(hidden)]
43pub fn host_cpu_vendor_golden_tag() -> Option<&'static str> {
44 CpuVendor::current().golden_tag()
45}
46
47const MAX_JSON_BLOB_SIZE: u64 = 1024 * 1024;
51
52fn check_json_blob_size(what: &str, len: usize) -> crate::Result<()> {
56 if len as u64 > MAX_JSON_BLOB_SIZE {
57 return Err(crate::new_error!(
58 "{} of {} bytes exceeds the {} byte maximum for a snapshot artifact",
59 what,
60 len,
61 MAX_JSON_BLOB_SIZE
62 ));
63 }
64 Ok(())
65}
66
67fn select_manifest<'a>(
74 index: &'a ImageIndex,
75 reference: &OciReference,
76 path: &Path,
77) -> crate::Result<&'a Descriptor> {
78 match reference {
79 OciReference::Tag(tag) => {
80 let mut matching = index.manifests().iter().filter(|d| {
81 d.annotations()
82 .as_ref()
83 .and_then(|a| a.get(ANNOTATION_REF_NAME))
84 .map(|s| s.as_str() == tag.as_str())
85 .unwrap_or(false)
86 });
87 match (matching.next(), matching.next()) {
88 (None, _) => {
89 let known: Vec<&str> = index
90 .manifests()
91 .iter()
92 .filter_map(|d| {
93 d.annotations()
94 .as_ref()
95 .and_then(|a| a.get(ANNOTATION_REF_NAME))
96 .map(|s| s.as_str())
97 })
98 .collect();
99 Err(crate::new_error!(
100 "no manifest tagged {:?} in OCI layout {:?}. Available tags: {:?}",
101 tag.as_str(),
102 path,
103 known
104 ))
105 }
106 (Some(_), Some(_)) => Err(crate::new_error!(
107 "OCI layout {:?} has multiple manifests tagged {:?}; tags must be unique",
108 path,
109 tag.as_str()
110 )),
111 (Some(d), None) => Ok(d),
112 }
113 }
114 OciReference::Digest(digest) => index
115 .manifests()
116 .iter()
117 .find(|d| d.digest().to_string() == digest.as_str())
118 .ok_or_else(|| {
119 crate::new_error!(
120 "no manifest with digest {} in OCI layout {:?}",
121 digest.as_str(),
122 path
123 )
124 }),
125 }
126}
127
128fn read_layout_marker(path: &Path) -> crate::Result<()> {
129 let layout_bytes = read_bounded(&path.join("oci-layout"), MAX_JSON_BLOB_SIZE)
130 .map_err(|e| crate::new_error!("failed to read oci-layout: {}", e))?;
131 let layout_json: serde_json::Value = serde_json::from_slice(&layout_bytes)
132 .map_err(|e| crate::new_error!("oci-layout is not valid JSON: {}", e))?;
133 let v = layout_json
134 .get("imageLayoutVersion")
135 .and_then(|v| v.as_str())
136 .ok_or_else(|| crate::new_error!("oci-layout missing imageLayoutVersion field"))?;
137 if v != OCI_LAYOUT_VERSION {
138 return Err(crate::new_error!(
139 "unsupported OCI image layout version {:?} (expected {:?})",
140 v,
141 OCI_LAYOUT_VERSION
142 ));
143 }
144 Ok(())
145}
146
147fn load_manifest(
148 path: &Path,
149 blobs_dir: &Path,
150 reference: &OciReference,
151 verify_blobs: bool,
152) -> crate::Result<ImageManifest> {
153 let index_bytes = read_bounded(&path.join("index.json"), MAX_JSON_BLOB_SIZE)
154 .map_err(|e| crate::new_error!("failed to read index.json: {}", e))?;
155 let index: ImageIndex = serde_json::from_slice(&index_bytes)
156 .map_err(|e| crate::new_error!("failed to parse index.json: {}", e))?;
157 if index.schema_version() != SCHEMA_VERSION {
158 return Err(crate::new_error!(
159 "unsupported OCI index schemaVersion {} (expected {})",
160 index.schema_version(),
161 SCHEMA_VERSION
162 ));
163 }
164 if let Some(media_type) = index.media_type()
165 && !matches!(media_type, MediaType::ImageIndex)
166 {
167 return Err(crate::new_error!(
168 "OCI index has unexpected media type {} (expected {})",
169 media_type.to_string(),
170 MediaType::ImageIndex.to_string()
171 ));
172 }
173 let manifest_desc = select_manifest(&index, reference, path)?;
174 if !matches!(manifest_desc.media_type(), MediaType::ImageManifest) {
175 return Err(crate::new_error!(
176 "manifest descriptor for {} has unexpected media type {} (expected {})",
177 reference,
178 manifest_desc.media_type().to_string(),
179 MediaType::ImageManifest.to_string()
180 ));
181 }
182 let manifest_hex = parse_oci_digest(manifest_desc.digest())?;
183 let manifest_path = blobs_dir.join(&manifest_hex);
184 let manifest_bytes = read_bounded(&manifest_path, MAX_JSON_BLOB_SIZE)?;
185 if manifest_bytes.len() as u64 != manifest_desc.size() {
186 return Err(crate::new_error!(
187 "OCI manifest size mismatch: descriptor says {}, file is {}",
188 manifest_desc.size(),
189 manifest_bytes.len()
190 ));
191 }
192 if verify_blobs {
193 verify_blob_bytes("manifest", &manifest_bytes, &manifest_hex)?;
194 }
195 let manifest: ImageManifest = serde_json::from_slice(&manifest_bytes)
196 .map_err(|e| crate::new_error!("failed to parse OCI manifest JSON: {}", e))?;
197 if manifest.schema_version() != SCHEMA_VERSION {
198 return Err(crate::new_error!(
199 "unsupported OCI manifest schemaVersion {} (expected {})",
200 manifest.schema_version(),
201 SCHEMA_VERSION
202 ));
203 }
204 if let Some(media_type) = manifest.media_type()
205 && !matches!(media_type, MediaType::ImageManifest)
206 {
207 return Err(crate::new_error!(
208 "OCI manifest has unexpected media type {} (expected {})",
209 media_type.to_string(),
210 MediaType::ImageManifest.to_string()
211 ));
212 }
213 Ok(manifest)
214}
215
216fn load_config(
217 blobs_dir: &Path,
218 cfg_desc: &Descriptor,
219 verify_blobs: bool,
220) -> crate::Result<OciSnapshotConfig> {
221 let cfg_hex = parse_oci_digest(cfg_desc.digest())?;
222 let cfg_path = blobs_dir.join(&cfg_hex);
223 let cfg_bytes = read_bounded(&cfg_path, MAX_JSON_BLOB_SIZE)?;
224 if cfg_bytes.len() as u64 != cfg_desc.size() {
225 return Err(crate::new_error!(
226 "config blob size mismatch: descriptor says {}, file is {}",
227 cfg_desc.size(),
228 cfg_bytes.len()
229 ));
230 }
231 if verify_blobs {
232 verify_blob_bytes("config", &cfg_bytes, &cfg_hex)?;
233 }
234 let cfg: OciSnapshotConfig = serde_json::from_slice(&cfg_bytes)
235 .map_err(|e| crate::new_error!("failed to parse Hyperlight config JSON: {}", e))?;
236 cfg.validate_for_load()?;
237 Ok(cfg)
238}
239
240fn open_snapshot_blob(
241 blobs_dir: &Path,
242 snap_desc: &Descriptor,
243 expected_blob_len: u64,
244 verify_blobs: bool,
245) -> crate::Result<std::fs::File> {
246 let snap_hex = parse_oci_digest(snap_desc.digest())?;
247 let snap_path = blobs_dir.join(&snap_hex);
248
249 let mut snap_file = self::fsutil::open_no_follow(&snap_path)?;
250
251 let snap_file_len = snap_file
252 .metadata()
253 .map_err(|e| crate::new_error!("failed to stat snapshot blob: {}", e))?
254 .len();
255 if snap_file_len != expected_blob_len {
256 return Err(crate::new_error!(
257 "snapshot blob size mismatch: file is {} bytes, expected {} (memory_size)",
258 snap_file_len,
259 expected_blob_len,
260 ));
261 }
262 if snap_file_len != snap_desc.size() {
263 return Err(crate::new_error!(
264 "snapshot blob size {} disagrees with OCI descriptor size {}",
265 snap_file_len,
266 snap_desc.size()
267 ));
268 }
269 if verify_blobs {
270 verify_blob_file("snapshot", &mut snap_file, &snap_hex)?;
271 }
272 Ok(snap_file)
273}
274
275impl Snapshot {
276 pub fn save(&self, path: impl AsRef<Path>, tag: &OciTag) -> crate::Result<OciDigest> {
340 let path = path.as_ref();
341
342 let cfg = self.build_config()?;
345 let cfg_bytes = serde_json::to_vec_pretty(&cfg)
346 .map_err(|e| crate::new_error!("failed to serialise config JSON: {}", e))?;
347 check_json_blob_size("config blob", cfg_bytes.len())?;
348
349 match path.parent() {
353 Some(p) if !p.as_os_str().is_empty() => {
354 let parent_meta = std::fs::metadata(p).map_err(|e| {
355 crate::new_error!("save: parent directory {:?} not accessible: {}", p, e)
356 })?;
357 if !parent_meta.is_dir() {
358 return Err(crate::new_error!(
359 "save: parent of {:?} is not a directory",
360 path
361 ));
362 }
363 }
364 _ => {}
365 }
366 match std::fs::create_dir(path) {
367 Ok(()) => {}
368 Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists => {
369 let meta = std::fs::metadata(path)
370 .map_err(|e| crate::new_error!("save: failed to stat {:?}: {}", path, e))?;
371 if !meta.is_dir() {
372 return Err(crate::new_error!(
373 "save: {:?} exists and is not a directory",
374 path
375 ));
376 }
377 }
378 Err(e) => {
379 return Err(crate::new_error!(
380 "save: failed to create layout dir {:?}: {}",
381 path,
382 e
383 ));
384 }
385 }
386
387 let layout_marker = path.join("oci-layout");
392 let marker_existed = layout_marker
393 .try_exists()
394 .map_err(|e| crate::new_error!("save: failed to stat {:?}: {}", layout_marker, e))?;
395 if marker_existed {
396 let bytes = read_bounded(&layout_marker, MAX_JSON_BLOB_SIZE).map_err(|e| {
397 crate::new_error!("save: failed to read existing oci-layout: {}", e)
398 })?;
399 let v: serde_json::Value = serde_json::from_slice(&bytes).map_err(|e| {
400 crate::new_error!("save: existing oci-layout is not valid JSON: {}", e)
401 })?;
402 match v.get("imageLayoutVersion").and_then(|s| s.as_str()) {
403 Some(s) if s == OCI_LAYOUT_VERSION => {}
404 Some(other) => {
405 return Err(crate::new_error!(
406 "save: existing imageLayoutVersion {:?} is unsupported (expected {:?})",
407 other,
408 OCI_LAYOUT_VERSION
409 ));
410 }
411 None => {
412 return Err(crate::new_error!(
413 "save: existing oci-layout is missing imageLayoutVersion"
414 ));
415 }
416 }
417 }
418
419 let index_path = path.join("index.json");
420 let index_existed = index_path
421 .try_exists()
422 .map_err(|e| crate::new_error!("save: failed to stat {:?}: {}", index_path, e))?;
423 let mut manifests: Vec<Descriptor> = if index_existed {
424 let bytes = read_bounded(&index_path, MAX_JSON_BLOB_SIZE).map_err(|e| {
425 crate::new_error!("save: failed to read existing index.json: {}", e)
426 })?;
427 let existing: ImageIndex = serde_json::from_slice(&bytes).map_err(|e| {
428 crate::new_error!(
429 "save: existing index.json is not a valid OCI image index: {}",
430 e
431 )
432 })?;
433 existing.manifests().to_vec()
434 } else {
435 Vec::new()
436 };
437
438 let new_desc = self.write_blobs_and_build_descriptor(path, tag, &cfg, &cfg_bytes)?;
439 let written_digest = OciDigest::from_oci_spec_digest(new_desc.digest());
440
441 manifests.retain(|d| {
445 d.annotations()
446 .as_ref()
447 .and_then(|a| a.get(ANNOTATION_REF_NAME))
448 .map(|s| s.as_str() != tag.as_str())
449 .unwrap_or(true)
450 });
451 manifests.push(new_desc);
452
453 let index = ImageIndexBuilder::default()
454 .schema_version(SCHEMA_VERSION)
455 .media_type(MediaType::ImageIndex)
456 .manifests(manifests)
457 .build()
458 .map_err(|e| crate::new_error!("failed to build OCI index: {}", e))?;
459 let index_bytes = serde_json::to_vec_pretty(&index)
460 .map_err(|e| crate::new_error!("failed to serialise OCI index: {}", e))?;
461 check_json_blob_size("index.json", index_bytes.len())?;
462
463 if !marker_existed {
467 let layout_bytes = serde_json::to_vec(&serde_json::json!({
468 "imageLayoutVersion": OCI_LAYOUT_VERSION,
469 }))
470 .map_err(|e| crate::new_error!("failed to serialise oci-layout: {}", e))?;
471 replace_file_atomic(&layout_marker, &layout_bytes)?;
472 }
473
474 replace_file_atomic(&index_path, &index_bytes)?;
476
477 Ok(written_digest)
478 }
479
480 fn write_blobs_and_build_descriptor(
481 &self,
482 dir: &Path,
483 tag: &OciTag,
484 cfg: &OciSnapshotConfig,
485 cfg_bytes: &[u8],
486 ) -> crate::Result<Descriptor> {
487 let memory_bytes = self.memory.as_slice();
488 let memory_size = memory_bytes.len();
489 if memory_size == 0 || !memory_size.is_multiple_of(PAGE_SIZE) {
490 return Err(crate::new_error!(
491 "snapshot memory size {} must be a non-zero multiple of PAGE_SIZE",
492 memory_size
493 ));
494 }
495
496 let blobs_dir = dir.join("blobs").join("sha256");
497 std::fs::create_dir_all(&blobs_dir).map_err(|e| {
498 crate::new_error!("failed to create OCI blobs dir {:?}: {}", blobs_dir, e)
499 })?;
500
501 let snapshot_digest = Digest256::from_bytes(memory_bytes);
503 put_blob_if_absent(&blobs_dir, &snapshot_digest, memory_bytes)?;
504
505 let cfg_digest = Digest256::from_bytes(cfg_bytes);
507 put_blob(&blobs_dir, &cfg_digest, cfg_bytes)?;
508
509 let config_descriptor = DescriptorBuilder::default()
511 .media_type(MediaType::Other(MT_CONFIG_CURRENT.to_string()))
512 .digest(oci_digest(&cfg_digest)?)
513 .size(cfg_bytes.len() as u64)
514 .build()
515 .map_err(|e| crate::new_error!("failed to build config descriptor: {}", e))?;
516 let snapshot_descriptor = DescriptorBuilder::default()
517 .media_type(MediaType::Other(MT_SNAPSHOT_CURRENT.to_string()))
518 .digest(oci_digest(&snapshot_digest)?)
519 .size(memory_size as u64)
520 .build()
521 .map_err(|e| crate::new_error!("failed to build snapshot descriptor: {}", e))?;
522 let manifest = ImageManifestBuilder::default()
527 .schema_version(SCHEMA_VERSION)
528 .media_type(MediaType::ImageManifest)
529 .artifact_type(MediaType::Other(MT_CONFIG_CURRENT.to_string()))
530 .config(config_descriptor)
531 .layers(vec![snapshot_descriptor])
532 .build()
533 .map_err(|e| crate::new_error!("failed to build OCI manifest: {}", e))?;
534 let manifest_bytes = serde_json::to_vec_pretty(&manifest)
535 .map_err(|e| crate::new_error!("failed to serialise OCI manifest: {}", e))?;
536 check_json_blob_size("manifest blob", manifest_bytes.len())?;
537 let manifest_digest = Digest256::from_bytes(&manifest_bytes);
538 put_blob(&blobs_dir, &manifest_digest, &manifest_bytes)?;
539
540 let mut anns = std::collections::HashMap::new();
541 anns.insert(ANNOTATION_REF_NAME.to_string(), tag.as_str().to_string());
542 anns.insert(ANNOTATION_ARCH.to_string(), cfg.arch.as_str().to_string());
543 anns.insert(
544 ANNOTATION_HYPERVISOR.to_string(),
545 cfg.hypervisor.as_str().to_string(),
546 );
547 anns.insert(
548 ANNOTATION_CPU.to_string(),
549 cfg.cpu_vendor.as_str().to_string(),
550 );
551 DescriptorBuilder::default()
552 .media_type(MediaType::ImageManifest)
553 .digest(oci_digest(&manifest_digest)?)
554 .size(manifest_bytes.len() as u64)
555 .annotations(anns)
556 .build()
557 .map_err(|e| crate::new_error!("failed to build manifest descriptor: {}", e))
558 }
559
560 fn build_config(&self) -> crate::Result<OciSnapshotConfig> {
561 let (entrypoint_addr, sregs) = match (self.next_action, self.sregs.as_ref()) {
562 (NextAction::Call(addr), Some(sregs)) => (addr, sregs),
563 (NextAction::Call(_), None) => {
564 return Err(crate::new_error!(
565 "snapshot inconsistent: Call entrypoint must have sregs"
566 ));
567 }
568 (NextAction::Initialise(_), _) => {
569 return Err(crate::new_error!(
570 "pre-init snapshots cannot be persisted. Only a snapshot taken after the guest has run can be saved to disk"
571 ));
572 }
573 #[cfg(test)]
574 (NextAction::None, _) => {
575 return Err(crate::new_error!(
576 "snapshot with NextAction::None cannot be persisted"
577 ));
578 }
579 };
580
581 let host_functions = match &self.host_functions.host_functions {
582 Some(v) => v.iter().map(HostFunction::from).collect(),
583 None => Vec::new(),
584 };
585
586 let l = &self.layout;
587 Ok(OciSnapshotConfig {
588 hyperlight_version: env!("CARGO_PKG_VERSION").to_string(),
589 arch: Arch::current(),
590 abi_version: SNAPSHOT_ABI_VERSION,
591 hypervisor: Hypervisor::current()
592 .ok_or_else(|| crate::new_error!("no hypervisor available to tag snapshot"))?,
593 cpu_vendor: CpuVendor::current(),
594 stack_top_gva: self.stack_top_gva,
595 entrypoint_addr,
596 original_entrypoint_addr: self.original_entrypoint,
597 sregs: *sregs,
598 #[cfg(target_arch = "x86_64")]
599 msrs: self
600 .msrs
601 .as_ref()
602 .ok_or_else(|| crate::new_error!("snapshot has no MSR state"))?
603 .clone(),
604 layout: MemoryLayout {
605 input_data_size: l.input_data_size(),
606 output_data_size: l.output_data_size(),
607 heap_size: l.heap_size(),
608 code_size: l.code_size(),
609 init_data_size: l.init_data_size(),
610 init_data_permissions: l.init_data_permissions().map(|f| f.bits()),
611 scratch_size: l.get_scratch_size(),
612 snapshot_size: l.snapshot_size(),
613 pt_size: l.pt_size(),
614 },
615 memory_size: self.memory.mem_size() as u64,
616 host_functions,
617 snapshot_generation: self.snapshot_generation,
618 })
619 }
620
621 pub fn load(path: impl AsRef<Path>, reference: impl Into<OciReference>) -> crate::Result<Self> {
687 Self::load_inner(path.as_ref(), &reference.into(), false)
688 }
689
690 pub fn checked_load(
703 path: impl AsRef<Path>,
704 reference: impl Into<OciReference>,
705 ) -> crate::Result<Self> {
706 Self::load_inner(path.as_ref(), &reference.into(), true)
707 }
708
709 fn load_inner(
710 path: &Path,
711 reference: &OciReference,
712 verify_blobs: bool,
713 ) -> crate::Result<Self> {
714 let meta = std::fs::metadata(path)
715 .map_err(|e| crate::new_error!("load failed to stat {:?}: {}", path, e))?;
716 if !meta.is_dir() {
717 return Err(crate::new_error!("load path {:?} is not a directory", path));
718 }
719
720 let blobs_dir: PathBuf = path.join("blobs").join("sha256");
721
722 read_layout_marker(path)?;
724
725 let manifest = load_manifest(path, &blobs_dir, reference, verify_blobs)?;
733 let cfg_desc = manifest.config();
734 let cfg_media = cfg_desc.media_type().to_string();
737 match cfg_media.as_str() {
738 MT_CONFIG_V1 => {}
739 other => {
740 return Err(crate::new_error!(
741 "unexpected config media type {:?} (supported: {:?})",
742 other,
743 MT_CONFIG_V1
744 ));
745 }
746 }
747 match manifest.artifact_type() {
754 Some(at) if at.to_string() == cfg_media => {}
755 Some(at) => {
756 return Err(crate::new_error!(
757 "OCI manifest artifactType {:?} does not match config media type {:?}",
758 at.to_string(),
759 cfg_media
760 ));
761 }
762 None => {
763 return Err(crate::new_error!(
764 "OCI manifest is missing required artifactType (expected {:?})",
765 cfg_media
766 ));
767 }
768 }
769 let layers = manifest.layers();
770 if layers.len() != 1 {
771 return Err(crate::new_error!(
772 "expected exactly one OCI layer (the snapshot), found {}",
773 layers.len()
774 ));
775 }
776 let snap_desc = &layers[0];
777 let snap_media = snap_desc.media_type().to_string();
778 match snap_media.as_str() {
779 MT_SNAPSHOT_V1 => {}
780 other => {
781 return Err(crate::new_error!(
782 "unexpected snapshot layer media type {:?} (supported: {:?})",
783 other,
784 MT_SNAPSHOT_V1
785 ));
786 }
787 }
788
789 let cfg = load_config(&blobs_dir, cfg_desc, verify_blobs)?;
791
792 let snap_file = open_snapshot_blob(&blobs_dir, snap_desc, cfg.memory_size, verify_blobs)?;
796
797 let mut sbox_cfg = crate::sandbox::SandboxConfiguration::default();
799 sbox_cfg.set_input_data_size(cfg.layout.input_data_size);
800 sbox_cfg.set_output_data_size(cfg.layout.output_data_size);
801 sbox_cfg.set_heap_size(cfg.layout.heap_size as u64);
802 sbox_cfg.set_scratch_size(cfg.layout.scratch_size);
803 let init_data_perms = match cfg.layout.init_data_permissions {
804 None => None,
805 Some(bits) => Some(MemoryRegionFlags::from_bits(bits).ok_or_else(|| {
806 crate::new_error!(
807 "snapshot init_data_permissions {:#x} contains unknown flag bits",
808 bits
809 )
810 })?),
811 };
812 let mut layout = SandboxMemoryLayout::new(
813 sbox_cfg,
814 cfg.layout.code_size,
815 cfg.layout.init_data_size,
816 init_data_perms,
817 )?;
818 if let Some(pt) = cfg.layout.pt_size {
820 layout.set_pt_size(pt)?;
821 }
822 layout.set_snapshot_size(cfg.layout.snapshot_size);
823
824 let required_memory_size = layout.get_memory_size()? as u64;
832 if (layout.snapshot_size() as u64) < required_memory_size {
833 return Err(crate::new_error!(
834 "snapshot snapshot_size ({}) is smaller than the layout size ({})",
835 layout.snapshot_size(),
836 required_memory_size
837 ));
838 }
839
840 let memory = ReadonlySharedMemory::from_file(&snap_file, layout.snapshot_size())?;
851
852 if memory.mem_size() as u64 != cfg.memory_size {
859 return Err(crate::new_error!(
860 "mapped snapshot size ({}) does not match config memory_size ({}); the blob may have changed during loading",
861 memory.mem_size(),
862 cfg.memory_size
863 ));
864 }
865
866 let next_action = NextAction::Call(cfg.entrypoint_addr);
868
869 let snapshot_generation = cfg.snapshot_generation;
871 let host_funcs_vec: Vec<
872 hyperlight_common::flatbuffer_wrappers::host_function_definition::HostFunctionDefinition,
873 > = cfg.host_functions.into_iter().map(Into::into).collect();
874 let host_functions = if host_funcs_vec.is_empty() {
875 HostFunctionDetails {
876 host_functions: None,
877 }
878 } else {
879 HostFunctionDetails {
880 host_functions: Some(host_funcs_vec),
881 }
882 };
883
884 Ok(Snapshot {
885 layout,
886 memory,
887 load_info: crate::mem::exe::LoadInfo::dummy(),
888 stack_top_gva: cfg.stack_top_gva,
889 sregs: Some(cfg.sregs),
890 #[cfg(target_arch = "x86_64")]
891 msrs: Some(cfg.msrs),
892 next_action,
893 original_entrypoint: cfg.original_entrypoint_addr,
894 snapshot_generation,
895 host_functions,
896 })
897 }
898}