77 "fmt"
88 "hash/crc32"
99 "os"
10+ "path/filepath"
1011 "strings"
1112 "time"
1213
@@ -21,7 +22,7 @@ import (
2122
2223// forkInstance creates a new instance by cloning a stopped or standby source
2324// instance. It returns the newly created fork and the requested final target
24- // state; callers apply the target state transition outside the source lock.
25+ // state; callers apply remaining target state transitions outside the source lock.
2526func (m * manager ) forkInstance (ctx context.Context , id string , req ForkInstanceRequest ) (* Instance , State , error ) {
2627 log := logger .FromContext (ctx )
2728 log .InfoContext (ctx , "forking instance" , "source_instance_id" , id , "fork_name" , req .Name )
@@ -68,6 +69,22 @@ func (m *manager) forkInstance(ctx context.Context, id string, req ForkInstanceR
6869 }
6970 }
7071 }
72+
73+ // For Firecracker running-source forks, restoring the fork may temporarily alias
74+ // the source data directory. Restore the fork while source remains standby and
75+ // under lock, then restore the source.
76+ if forkErr == nil && targetState == StateRunning {
77+ restoredFork , err := m .applyForkTargetState (ctx , forked .Id , StateRunning )
78+ if err != nil {
79+ forkErr = fmt .Errorf ("restore forked instance before source restore: %w" , err )
80+ if cleanupErr := m .cleanupForkInstanceOnError (ctx , forked .Id ); cleanupErr != nil {
81+ forkErr = fmt .Errorf ("%v; additionally failed to cleanup forked instance %s: %v" , forkErr , forked .Id , cleanupErr )
82+ }
83+ } else {
84+ forked = restoredFork
85+ }
86+ }
87+
7188 log .InfoContext (ctx , "restoring source instance after running fork" , "source_instance_id" , id )
7289 _ , restoreErr := m .restoreInstance (ctx , id )
7390
@@ -193,6 +210,13 @@ func (m *manager) forkInstanceFromStoppedOrStandby(ctx context.Context, id strin
193210 return nil , err
194211 }
195212
213+ existsByMetadata , err := m .instanceNameExists (req .Name )
214+ if err != nil {
215+ return nil , fmt .Errorf ("check instance name availability: %w" , err )
216+ }
217+ if existsByMetadata {
218+ return nil , fmt .Errorf ("%w: instance name '%s' already exists" , ErrAlreadyExists , req .Name )
219+ }
196220 if stored .NetworkEnabled {
197221 exists , err := m .networkManager .NameExists (ctx , req .Name , "" )
198222 if err != nil {
@@ -325,6 +349,25 @@ func validateForkVolumeSafety(volumes []VolumeAttachment) error {
325349 return nil
326350}
327351
352+ func (m * manager ) instanceNameExists (name string ) (bool , error ) {
353+ metaFiles , err := m .listMetadataFiles ()
354+ if err != nil {
355+ return false , err
356+ }
357+
358+ for _ , metaFile := range metaFiles {
359+ id := filepath .Base (filepath .Dir (metaFile ))
360+ meta , err := m .loadMetadata (id )
361+ if err != nil {
362+ continue
363+ }
364+ if meta .Name == name {
365+ return true , nil
366+ }
367+ }
368+ return false , nil
369+ }
370+
328371func resolveForkTargetState (requested State , sourceState State ) (State , error ) {
329372 if requested == "" {
330373 switch sourceState {
0 commit comments