@@ -245,6 +245,8 @@ var TestVersionSwitch string
245
245
func Exec (gotoolchain string ) {
246
246
log .SetPrefix ("go: " )
247
247
248
+ writeBits = sysWriteBits ()
249
+
248
250
count , _ := strconv .Atoi (os .Getenv (countEnv ))
249
251
if count >= maxSwitch - 10 {
250
252
fmt .Fprintf (os .Stderr , "go: switching from go%v to %v [depth %d]\n " , gover .Local (), gotoolchain , count )
@@ -357,10 +359,101 @@ func Exec(gotoolchain string) {
357
359
}
358
360
}
359
361
362
+ srcUGoMod := filepath .Join (dir , "src/_go.mod" )
363
+ srcGoMod := filepath .Join (dir , "src/go.mod" )
364
+ if size (srcGoMod ) != size (srcUGoMod ) {
365
+ err := filepath .WalkDir (dir , func (path string , d fs.DirEntry , err error ) error {
366
+ if err != nil {
367
+ return err
368
+ }
369
+ if path == srcUGoMod {
370
+ // Leave for last, in case we are racing with another go command.
371
+ return nil
372
+ }
373
+ if pdir , name := filepath .Split (path ); name == "_go.mod" {
374
+ if err := raceSafeCopy (path , pdir + "go.mod" ); err != nil {
375
+ return err
376
+ }
377
+ }
378
+ return nil
379
+ })
380
+ // Handle src/go.mod; this is the signal to other racing go commands
381
+ // that everything is okay and they can skip this step.
382
+ if err == nil {
383
+ err = raceSafeCopy (srcUGoMod , srcGoMod )
384
+ }
385
+ if err != nil {
386
+ base .Fatalf ("download %s: %v" , gotoolchain , err )
387
+ }
388
+ }
389
+
360
390
// Reinvoke the go command.
361
391
execGoToolchain (gotoolchain , dir , filepath .Join (dir , "bin/go" ))
362
392
}
363
393
394
+ func size (path string ) int64 {
395
+ info , err := os .Stat (path )
396
+ if err != nil {
397
+ return - 1
398
+ }
399
+ return info .Size ()
400
+ }
401
+
402
+ var writeBits fs.FileMode
403
+
404
+ // raceSafeCopy copies the file old to the file new, being careful to ensure
405
+ // that if multiple go commands call raceSafeCopy(old, new) at the same time,
406
+ // they don't interfere with each other: both will succeed and return and
407
+ // later observe the correct content in new. Like in the build cache, we arrange
408
+ // this by opening new without truncation and then writing the content.
409
+ // Both go commands can do this simultaneously and will write the same thing
410
+ // (old never changes content).
411
+ func raceSafeCopy (old , new string ) error {
412
+ oldInfo , err := os .Stat (old )
413
+ if err != nil {
414
+ return err
415
+ }
416
+ newInfo , err := os .Stat (new )
417
+ if err == nil && newInfo .Size () == oldInfo .Size () {
418
+ return nil
419
+ }
420
+ data , err := os .ReadFile (old )
421
+ if err != nil {
422
+ return err
423
+ }
424
+ // The module cache has unwritable directories by default.
425
+ // Restore the user write bit in the directory so we can create
426
+ // the new go.mod file. We clear it again at the end on a
427
+ // best-effort basis (ignoring failures).
428
+ dir := filepath .Dir (old )
429
+ info , err := os .Stat (dir )
430
+ if err != nil {
431
+ return err
432
+ }
433
+ if err := os .Chmod (dir , info .Mode ()| writeBits ); err != nil {
434
+ return err
435
+ }
436
+ defer os .Chmod (dir , info .Mode ())
437
+ // Note: create the file writable, so that a racing go command
438
+ // doesn't get an error before we store the actual data.
439
+ f , err := os .OpenFile (new , os .O_CREATE | os .O_WRONLY , writeBits &^0o111 )
440
+ if err != nil {
441
+ // If OpenFile failed because a racing go command completed our work
442
+ // (and then OpenFile failed because the directory or file is now read-only),
443
+ // count that as a success.
444
+ if size (old ) == size (new ) {
445
+ return nil
446
+ }
447
+ return err
448
+ }
449
+ defer os .Chmod (new , oldInfo .Mode ())
450
+ if _ , err := f .Write (data ); err != nil {
451
+ f .Close ()
452
+ return err
453
+ }
454
+ return f .Close ()
455
+ }
456
+
364
457
// modGoToolchain finds the enclosing go.work or go.mod file
365
458
// and returns the go version and toolchain lines from the file.
366
459
// The toolchain line overrides the version line
0 commit comments