@@ -2,6 +2,7 @@ package vm
22
33import (
44 "context"
5+ "encoding/json"
56 "fmt"
67 "log/slog"
78 "os"
@@ -103,10 +104,15 @@ func (vm *vm) StartFromSnapshot(ctx context.Context, globalSnapshotPath, jailSna
103104
104105 // Copy snapshot from global storage into the jail
105106 jailHostPath := getInstanceDir (vm .id ) + jailSnapshotPath
106- if err := copyDir (globalSnapshotPath , jailHostPath ); err != nil {
107+ if err := copyDirWithLinks (globalSnapshotPath , jailHostPath ); err != nil {
107108 return fmt .Errorf ("failed to copy snapshot to jail: %w" , err )
108109 }
109110
111+ // Patch config.json with current device paths (rootfs, tap device)
112+ if err := vm .patchSnapshotConfig (jailHostPath ); err != nil {
113+ return fmt .Errorf ("failed to patch snapshot config: %w" , err )
114+ }
115+
110116 // Chown to ravel-jailer user so CloudHypervisor can read
111117 jailerUid , jailerGid , err := setupRavelJailerUser ()
112118 if err != nil {
@@ -116,7 +122,7 @@ func (vm *vm) StartFromSnapshot(ctx context.Context, globalSnapshotPath, jailSna
116122 return fmt .Errorf ("failed to chown snapshot directory: %w" , err )
117123 }
118124
119- slog .Debug ("snapshot copied to jail " , "from" , globalSnapshotPath , "to" , jailHostPath )
125+ slog .Debug ("snapshot copied and patched " , "from" , globalSnapshotPath , "to" , jailHostPath )
120126
121127 err = vm .cmd .Start ()
122128 if err != nil {
@@ -166,8 +172,66 @@ func (vm *vm) StartFromSnapshot(ctx context.Context, globalSnapshotPath, jailSna
166172 return nil
167173}
168174
169- // copyDir copies a directory from src to dst
170- func copyDir (src , dst string ) error {
175+ // patchSnapshotConfig updates the snapshot config.json with current device paths
176+ func (vm * vm ) patchSnapshotConfig (snapshotPath string ) error {
177+ configPath := snapshotPath + "/config.json"
178+
179+ // Read the config
180+ data , err := os .ReadFile (configPath )
181+ if err != nil {
182+ return fmt .Errorf ("failed to read config.json: %w" , err )
183+ }
184+
185+ // Parse as generic JSON to preserve structure
186+ var config map [string ]interface {}
187+ if err := json .Unmarshal (data , & config ); err != nil {
188+ return fmt .Errorf ("failed to parse config.json: %w" , err )
189+ }
190+
191+ // Update disk path - get the current rootfs from vmConfig
192+ if disks , ok := config ["disks" ].([]interface {}); ok && len (disks ) > 0 {
193+ if disk , ok := disks [0 ].(map [string ]interface {}); ok {
194+ // Get the new rootfs path from vmConfig
195+ if vm .vmConfig .Disks != nil && len (* vm .vmConfig .Disks ) > 0 {
196+ newRootfs := (* vm .vmConfig .Disks )[0 ].Path
197+ oldRootfs := disk ["path" ]
198+ disk ["path" ] = newRootfs
199+ slog .Debug ("patched rootfs path" , "old" , oldRootfs , "new" , newRootfs )
200+ }
201+ }
202+ }
203+
204+ // Update tap device name
205+ if nets , ok := config ["net" ].([]interface {}); ok && len (nets ) > 0 {
206+ if net , ok := nets [0 ].(map [string ]interface {}); ok {
207+ // Get the new tap device from vmConfig
208+ if vm .vmConfig .Net != nil && len (* vm .vmConfig .Net ) > 0 {
209+ newTap := (* vm .vmConfig .Net )[0 ].Tap
210+ if newTap != nil {
211+ oldTap := net ["tap" ]
212+ net ["tap" ] = * newTap
213+ slog .Debug ("patched tap device" , "old" , oldTap , "new" , * newTap )
214+ }
215+ }
216+ }
217+ }
218+
219+ // Write back the modified config
220+ newData , err := json .MarshalIndent (config , "" , " " )
221+ if err != nil {
222+ return fmt .Errorf ("failed to marshal config.json: %w" , err )
223+ }
224+
225+ if err := os .WriteFile (configPath , newData , 0600 ); err != nil {
226+ return fmt .Errorf ("failed to write config.json: %w" , err )
227+ }
228+
229+ return nil
230+ }
231+
232+ // copyDirWithLinks copies a directory from src to dst, using hard links for large files
233+ // config.json is always copied (not linked) since we need to modify it
234+ func copyDirWithLinks (src , dst string ) error {
171235 if err := os .MkdirAll (dst , 0755 ); err != nil {
172236 return err
173237 }
@@ -182,12 +246,24 @@ func copyDir(src, dst string) error {
182246 dstPath := dst + "/" + entry .Name ()
183247
184248 if entry .IsDir () {
185- if err := copyDir (srcPath , dstPath ); err != nil {
249+ if err := copyDirWithLinks (srcPath , dstPath ); err != nil {
186250 return err
187251 }
188252 } else {
189- if err := copyFile (srcPath , dstPath ); err != nil {
190- return err
253+ // Always copy config.json since we need to modify it
254+ if entry .Name () == "config.json" {
255+ if err := copyFile (srcPath , dstPath ); err != nil {
256+ return err
257+ }
258+ continue
259+ }
260+
261+ // Try hard link first (instant, no copy), fall back to copy
262+ if err := os .Link (srcPath , dstPath ); err != nil {
263+ // Hard link failed (maybe cross-device), fall back to copy
264+ if err := copyFile (srcPath , dstPath ); err != nil {
265+ return err
266+ }
191267 }
192268 }
193269 }
0 commit comments