@@ -839,22 +839,101 @@ impl SandboxManager {
839839 ) ) ) ;
840840 }
841841
842- // Compute snapshot paths. When source is jailed, snapshot files must go
843- // into the source's chroot (Firecracker can only write within its chroot).
844- let ( api_snapshot_path, api_mem_path, host_snapshot_path, host_mem_path) =
845- if let Some ( ref src_chroot) = source_chroot_root {
846- // Jailed source: write to source chroot, then copy later
847- let api_snap = "/fork_snapshot_file" . to_string ( ) ;
848- let api_mem = "/fork_mem_file" . to_string ( ) ;
849- let host_snap = format ! ( "{}/fork_snapshot_file" , src_chroot) ;
850- let host_mem = format ! ( "{}/fork_mem_file" , src_chroot) ;
851- ( api_snap, api_mem, host_snap, host_mem)
852- } else {
853- // Non-jailed: write directly to fork's sandbox dir
854- let snap = format ! ( "{}/snapshot_file" , fork_sandbox_dir) ;
855- let mem = format ! ( "{}/mem_file" , fork_sandbox_dir) ;
856- ( snap. clone ( ) , mem. clone ( ) , snap, mem)
857- } ;
842+ let fork_snap = format ! ( "{}/snapshot_file" , fork_sandbox_dir) ;
843+ let fork_mem = format ! ( "{}/mem_file" , fork_sandbox_dir) ;
844+
845+ // Compute snapshot paths. When source is jailed, Firecracker can only
846+ // write within the source chroot, so we expose the fork snapshot files
847+ // there via hard links instead of copying them afterward.
848+ let (
849+ api_snapshot_path,
850+ api_mem_path,
851+ host_snapshot_path,
852+ host_mem_path,
853+ linked_snapshot_files,
854+ ) = if let Some ( ref src_chroot) = source_chroot_root {
855+ let api_snap = "/fork_snapshot_file" . to_string ( ) ;
856+ let api_mem = "/fork_mem_file" . to_string ( ) ;
857+ let host_snap = format ! ( "{}/fork_snapshot_file" , src_chroot) ;
858+ let host_mem = format ! ( "{}/fork_mem_file" , src_chroot) ;
859+ ( api_snap, api_mem, host_snap, host_mem, true )
860+ } else {
861+ (
862+ fork_snap. clone ( ) ,
863+ fork_mem. clone ( ) ,
864+ fork_snap. clone ( ) ,
865+ fork_mem. clone ( ) ,
866+ false ,
867+ )
868+ } ;
869+
870+ if linked_snapshot_files {
871+ for path in [ & fork_snap, & fork_mem, & host_snapshot_path, & host_mem_path] {
872+ let _ = tokio:: fs:: remove_file ( path) . await ;
873+ }
874+
875+ for path in [ & fork_snap, & fork_mem] {
876+ if let Err ( e) = tokio:: fs:: File :: create ( path) . await {
877+ self . cleanup_fork_failure (
878+ new_sandbox_id,
879+ slot,
880+ None ,
881+ & format ! ( "snapshot staging failed: {}" , e) ,
882+ )
883+ . await ;
884+ return Err ( SandboxError :: ForkFailed ( format ! (
885+ "failed to stage fork snapshot file {}: {}" ,
886+ path, e
887+ ) ) ) ;
888+ }
889+
890+ if let Err ( e) =
891+ jailer:: chown_to_jailer ( & self . node_config . jailer , std:: path:: Path :: new ( path) )
892+ . await
893+ {
894+ self . cleanup_fork_failure (
895+ new_sandbox_id,
896+ slot,
897+ None ,
898+ & format ! ( "snapshot staging ownership failed: {}" , e) ,
899+ )
900+ . await ;
901+ return Err ( SandboxError :: ForkFailed ( format ! (
902+ "failed to set fork snapshot ownership for {}: {}" ,
903+ path, e
904+ ) ) ) ;
905+ }
906+ }
907+
908+ if let Err ( e) = tokio:: fs:: hard_link ( & fork_snap, & host_snapshot_path) . await {
909+ self . cleanup_fork_failure (
910+ new_sandbox_id,
911+ slot,
912+ None ,
913+ & format ! ( "snapshot hard-link setup failed: {}" , e) ,
914+ )
915+ . await ;
916+ return Err ( SandboxError :: ForkFailed ( format ! (
917+ "failed to hard-link snapshot file into source chroot: {}" ,
918+ e
919+ ) ) ) ;
920+ }
921+
922+ if let Err ( e) = tokio:: fs:: hard_link ( & fork_mem, & host_mem_path) . await {
923+ let _ = tokio:: fs:: remove_file ( & host_snapshot_path) . await ;
924+ self . cleanup_fork_failure (
925+ new_sandbox_id,
926+ slot,
927+ None ,
928+ & format ! ( "mem hard-link setup failed: {}" , e) ,
929+ )
930+ . await ;
931+ return Err ( SandboxError :: ForkFailed ( format ! (
932+ "failed to hard-link memory snapshot file into source chroot: {}" ,
933+ e
934+ ) ) ) ;
935+ }
936+ }
858937 let source_rootfs = if source_chroot_root. is_some ( ) {
859938 let src_paths = self . sandbox_paths ( source_sandbox_id) ;
860939 format ! ( "{}/rootfs.ext4" , src_paths. dir)
@@ -887,6 +966,8 @@ impl SandboxManager {
887966 . await
888967 {
889968 let _ = fc_api. resume_vm ( ) . await ; // Best-effort resume on failure
969+ let _ = tokio:: fs:: remove_file ( & host_snapshot_path) . await ;
970+ let _ = tokio:: fs:: remove_file ( & host_mem_path) . await ;
890971 self . cleanup_fork_failure (
891972 new_sandbox_id,
892973 slot,
@@ -900,6 +981,11 @@ impl SandboxManager {
900981 ) ) ) ;
901982 }
902983
984+ if linked_snapshot_files {
985+ let _ = tokio:: fs:: remove_file ( & host_snapshot_path) . await ;
986+ let _ = tokio:: fs:: remove_file ( & host_mem_path) . await ;
987+ }
988+
903989 // --- Step 3: Clone disk (while source is paused for consistency) ---
904990 let disk_result = if self . node_config . jailer . enabled {
905991 match disk:: clone_disk_to (
@@ -950,68 +1036,6 @@ impl SandboxManager {
9501036 ) ) ) ;
9511037 }
9521038
953- // For jailed source: copy snapshot files from source chroot to fork's dir
954- if source_chroot_root. is_some ( ) {
955- let fork_snap = format ! ( "{}/snapshot_file" , fork_sandbox_dir) ;
956- let fork_mem = format ! ( "{}/mem_file" , fork_sandbox_dir) ;
957- if let Err ( e) = tokio:: fs:: copy ( & host_snapshot_path, & fork_snap) . await {
958- self . cleanup_fork_failure (
959- new_sandbox_id,
960- slot,
961- None ,
962- & format ! ( "copy snapshot to fork failed: {}" , e) ,
963- )
964- . await ;
965- // Clean up temp files from source chroot
966- let _ = tokio:: fs:: remove_file ( & host_snapshot_path) . await ;
967- let _ = tokio:: fs:: remove_file ( & host_mem_path) . await ;
968- return Err ( SandboxError :: ForkFailed ( format ! (
969- "failed to copy snapshot to fork: {}" ,
970- e
971- ) ) ) ;
972- }
973- if let Err ( e) = tokio:: fs:: copy ( & host_mem_path, & fork_mem) . await {
974- self . cleanup_fork_failure (
975- new_sandbox_id,
976- slot,
977- None ,
978- & format ! ( "copy mem to fork failed: {}" , e) ,
979- )
980- . await ;
981- let _ = tokio:: fs:: remove_file ( & host_snapshot_path) . await ;
982- let _ = tokio:: fs:: remove_file ( & host_mem_path) . await ;
983- return Err ( SandboxError :: ForkFailed ( format ! (
984- "failed to copy mem to fork: {}" ,
985- e
986- ) ) ) ;
987- }
988- // Clean up temp files from source chroot
989- let _ = tokio:: fs:: remove_file ( & host_snapshot_path) . await ;
990- let _ = tokio:: fs:: remove_file ( & host_mem_path) . await ;
991- if self . node_config . jailer . enabled {
992- for path in [ & fork_snap, & fork_mem] {
993- if let Err ( e) = jailer:: chown_to_jailer (
994- & self . node_config . jailer ,
995- std:: path:: Path :: new ( path) ,
996- )
997- . await
998- {
999- self . cleanup_fork_failure (
1000- new_sandbox_id,
1001- slot,
1002- None ,
1003- & format ! ( "snapshot artifact ownership setup failed: {}" , e) ,
1004- )
1005- . await ;
1006- return Err ( SandboxError :: ForkFailed ( format ! (
1007- "snapshot artifact ownership setup failed: {}" ,
1008- e
1009- ) ) ) ;
1010- }
1011- }
1012- }
1013- }
1014-
10151039 // --- Step 5: Boot fork from snapshot ---
10161040 let api_socket_path = fork_paths. api_socket . clone ( ) ;
10171041
0 commit comments