@@ -12,6 +12,7 @@ import (
12
12
"os/exec"
13
13
"path"
14
14
"path/filepath"
15
+ "strconv"
15
16
"strings"
16
17
"time"
17
18
@@ -262,14 +263,11 @@ func Download(ctx context.Context, local, remote string, opts ...Opt) (*Result,
262
263
return res , nil
263
264
}
264
265
}
265
- if err := os .RemoveAll (shad ); err != nil {
266
- return nil , err
267
- }
268
266
if err := os .MkdirAll (shad , 0o700 ); err != nil {
269
267
return nil , err
270
268
}
271
269
shadURL := filepath .Join (shad , "url" )
272
- if err := os . WriteFile (shadURL , []byte (remote ), 0o644 ); err != nil {
270
+ if err := atomicWrite (shadURL , []byte (remote ), 0o644 ); err != nil {
273
271
return nil , err
274
272
}
275
273
if err := downloadHTTP (ctx , shadData , shadTime , shadType , remote , o .description , o .expectedDigest ); err != nil {
@@ -280,7 +278,7 @@ func Download(ctx context.Context, local, remote string, opts ...Opt) (*Result,
280
278
return nil , err
281
279
}
282
280
if shadDigest != "" && o .expectedDigest != "" {
283
- if err := os . WriteFile (shadDigest , []byte (o .expectedDigest .String ()), 0o644 ); err != nil {
281
+ if err := atomicWrite (shadDigest , []byte (o .expectedDigest .String ()), 0o644 ); err != nil {
284
282
return nil , err
285
283
}
286
284
}
@@ -600,10 +598,8 @@ func downloadHTTP(ctx context.Context, localPath, lastModified, contentType, url
600
598
return fmt .Errorf ("downloadHTTP: got empty localPath" )
601
599
}
602
600
logrus .Debugf ("downloading %q into %q" , url , localPath )
603
- localPathTmp := localPath + ".tmp"
604
- if err := os .RemoveAll (localPathTmp ); err != nil {
605
- return err
606
- }
601
+
602
+ localPathTmp := perProcessTempfile (localPath )
607
603
fileWriter , err := os .Create (localPathTmp )
608
604
if err != nil {
609
605
return err
@@ -616,13 +612,13 @@ func downloadHTTP(ctx context.Context, localPath, lastModified, contentType, url
616
612
}
617
613
if lastModified != "" {
618
614
lm := resp .Header .Get ("Last-Modified" )
619
- if err := os . WriteFile (lastModified , []byte (lm ), 0o644 ); err != nil {
615
+ if err := atomicWrite (lastModified , []byte (lm ), 0o644 ); err != nil {
620
616
return err
621
617
}
622
618
}
623
619
if contentType != "" {
624
620
ct := resp .Header .Get ("Content-Type" )
625
- if err := os . WriteFile (contentType , []byte (ct ), 0o644 ); err != nil {
621
+ if err := atomicWrite (contentType , []byte (ct ), 0o644 ); err != nil {
626
622
return err
627
623
}
628
624
}
@@ -674,10 +670,44 @@ func downloadHTTP(ctx context.Context, localPath, lastModified, contentType, url
674
670
if err := fileWriter .Close (); err != nil {
675
671
return err
676
672
}
677
- if err := os .RemoveAll (localPath ); err != nil {
673
+
674
+ return os .Rename (localPathTmp , localPath )
675
+ }
676
+
677
+ // To allow parallel download we use a per-process unique suffix for tempoary
678
+ // files. Renaming the temporary file to the final file is safe without
679
+ // synchronization on posix.
680
+ // https://github.com/lima-vm/lima/issues/2722
681
+ func perProcessTempfile (path string ) string {
682
+ return path + ".tmp." + strconv .FormatInt (int64 (os .Getpid ()), 10 )
683
+ }
684
+
685
+ // atomicWrite writes data to path, creating a new file or replacing existing
686
+ // one. Multiple processess can write to the same path safely. Safe on posix and
687
+ // likely safe on windows when using NTFS.
688
+ func atomicWrite (path string , data []byte , perm os.FileMode ) error {
689
+ tmpPath := perProcessTempfile (path )
690
+ tmp , err := os .OpenFile (tmpPath , os .O_WRONLY | os .O_CREATE | os .O_TRUNC , perm )
691
+ if err != nil {
692
+ return err
693
+ }
694
+ defer func () {
695
+ if err != nil {
696
+ tmp .Close ()
697
+ os .RemoveAll (tmpPath )
698
+ }
699
+ }()
700
+ if _ , err = tmp .Write (data ); err != nil {
678
701
return err
679
702
}
680
- return os .Rename (localPathTmp , localPath )
703
+ if err = tmp .Sync (); err != nil {
704
+ return err
705
+ }
706
+ if err = tmp .Close (); err != nil {
707
+ return err
708
+ }
709
+ err = os .Rename (tmpPath , path )
710
+ return err
681
711
}
682
712
683
713
// CacheEntries returns a map of cache entries.
0 commit comments