Skip to content

Commit 3d27928

Browse files
rscgopherbot
authored andcommitted
cmd/go: restore go.mod files during toolchain selection
They have to be renamed to _go.mod to make a valid module. Copy them back to go.mod so that 'go test cmd' has a better chance of working. For #57001. Change-Id: Ied6f0dd77928996ab322a55c5606d7f75431e362 Reviewed-on: https://go-review.googlesource.com/c/go/+/504118 TryBot-Result: Gopher Robot <[email protected]> Reviewed-by: Bryan Mills <[email protected]> Auto-Submit: Russ Cox <[email protected]> Run-TryBot: Russ Cox <[email protected]>
1 parent 3b4b7b8 commit 3d27928

File tree

3 files changed

+134
-0
lines changed

3 files changed

+134
-0
lines changed

src/cmd/go/internal/toolchain/select.go

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,8 @@ var TestVersionSwitch string
245245
func Exec(gotoolchain string) {
246246
log.SetPrefix("go: ")
247247

248+
writeBits = sysWriteBits()
249+
248250
count, _ := strconv.Atoi(os.Getenv(countEnv))
249251
if count >= maxSwitch-10 {
250252
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) {
357359
}
358360
}
359361

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+
360390
// Reinvoke the go command.
361391
execGoToolchain(gotoolchain, dir, filepath.Join(dir, "bin/go"))
362392
}
363393

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+
364457
// modGoToolchain finds the enclosing go.work or go.mod file
365458
// and returns the go version and toolchain lines from the file.
366459
// The toolchain line overrides the version line
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Copyright 2023 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
//go:build !(darwin || freebsd || linux || netbsd || openbsd)
6+
7+
package toolchain
8+
9+
import "io/fs"
10+
11+
func sysWriteBits() fs.FileMode {
12+
return 0700
13+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Copyright 2023 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
//go:build darwin || freebsd || linux || netbsd || openbsd
6+
7+
package toolchain
8+
9+
import (
10+
"io/fs"
11+
"syscall"
12+
)
13+
14+
// sysWriteBits determines which bits to OR into the mode to make a directory writable.
15+
// It must be called when there are no other file system operations happening.
16+
func sysWriteBits() fs.FileMode {
17+
// Read current umask. There's no way to read it without also setting it,
18+
// so set it conservatively and then restore the original one.
19+
m := syscall.Umask(0o777)
20+
syscall.Umask(m) // restore bits
21+
if m&0o22 == 0o22 { // group and world are unwritable by default
22+
return 0o700
23+
}
24+
if m&0o2 == 0o2 { // group is writable by default, but not world
25+
return 0o770
26+
}
27+
return 0o777 // everything is writable by default
28+
}

0 commit comments

Comments
 (0)