cmd/go: don't lock .mod and .sum files for read in overlay

On Plan 9, locking a file requires a chmod call. In general, the go
command should not modify files in the overlay, even metadata. With
this change, we won't lock these files for reading.

The go command already reported errors when attempting to write these
files if they were in the overlay, but this change moves those checks
to the point of access for clearer error
messages. cmd/go/internal/lockedfile no longer imports
cmd/go/internal/fsys.

Fixes #44700

Change-Id: Ib544459dd6cf56ca0f7a27b3285f045f08040d7e
Reviewed-on: https://go-review.googlesource.com/c/go/+/333012
Trust: Jay Conrod <jayconrod@google.com>
Run-TryBot: Jay Conrod <jayconrod@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Bryan C. Mills <bcmills@google.com>
diff --git a/src/cmd/go/internal/lockedfile/lockedfile_filelock.go b/src/cmd/go/internal/lockedfile/lockedfile_filelock.go
index 729df5c..e4923f6 100644
--- a/src/cmd/go/internal/lockedfile/lockedfile_filelock.go
+++ b/src/cmd/go/internal/lockedfile/lockedfile_filelock.go
@@ -11,7 +11,6 @@
 	"io/fs"
 	"os"
 
-	"cmd/go/internal/fsys"
 	"cmd/go/internal/lockedfile/internal/filelock"
 )
 
@@ -21,7 +20,7 @@
 	// calls for Linux and Windows anyway, so it's simpler to use that approach
 	// consistently.
 
-	f, err := fsys.OpenFile(name, flag&^os.O_TRUNC, perm)
+	f, err := os.OpenFile(name, flag&^os.O_TRUNC, perm)
 	if err != nil {
 		return nil, err
 	}
diff --git a/src/cmd/go/internal/lockedfile/lockedfile_plan9.go b/src/cmd/go/internal/lockedfile/lockedfile_plan9.go
index 3d4b97d..979118b 100644
--- a/src/cmd/go/internal/lockedfile/lockedfile_plan9.go
+++ b/src/cmd/go/internal/lockedfile/lockedfile_plan9.go
@@ -13,8 +13,6 @@
 	"os"
 	"strings"
 	"time"
-
-	"cmd/go/internal/fsys"
 )
 
 // Opening an exclusive-use file returns an error.
@@ -59,7 +57,7 @@
 	// If the file was unpacked or created by some other program, it might not
 	// have the ModeExclusive bit set. Set it before we call OpenFile, so that we
 	// can be confident that a successful OpenFile implies exclusive use.
-	if fi, err := fsys.Stat(name); err == nil {
+	if fi, err := os.Stat(name); err == nil {
 		if fi.Mode()&fs.ModeExclusive == 0 {
 			if err := os.Chmod(name, fi.Mode()|fs.ModeExclusive); err != nil {
 				return nil, err
@@ -72,7 +70,7 @@
 	nextSleep := 1 * time.Millisecond
 	const maxSleep = 500 * time.Millisecond
 	for {
-		f, err := fsys.OpenFile(name, flag, perm|fs.ModeExclusive)
+		f, err := os.OpenFile(name, flag, perm|fs.ModeExclusive)
 		if err == nil {
 			return f, nil
 		}
diff --git a/src/cmd/go/internal/modfetch/fetch.go b/src/cmd/go/internal/modfetch/fetch.go
index e40593a..d3d30d9 100644
--- a/src/cmd/go/internal/modfetch/fetch.go
+++ b/src/cmd/go/internal/modfetch/fetch.go
@@ -22,6 +22,7 @@
 
 	"cmd/go/internal/base"
 	"cmd/go/internal/cfg"
+	"cmd/go/internal/fsys"
 	"cmd/go/internal/lockedfile"
 	"cmd/go/internal/par"
 	"cmd/go/internal/robustio"
@@ -416,7 +417,18 @@
 
 	goSum.m = make(map[module.Version][]string)
 	goSum.status = make(map[modSum]modSumStatus)
-	data, err := lockedfile.Read(GoSumFile)
+	var (
+		data []byte
+		err  error
+	)
+	if actualSumFile, ok := fsys.OverlayPath(GoSumFile); ok {
+		// Don't lock go.sum if it's part of the overlay.
+		// On Plan 9, locking requires chmod, and we don't want to modify any file
+		// in the overlay. See #44700.
+		data, err = os.ReadFile(actualSumFile)
+	} else {
+		data, err = lockedfile.Read(GoSumFile)
+	}
 	if err != nil && !os.IsNotExist(err) {
 		return false, err
 	}
@@ -716,6 +728,9 @@
 	if cfg.BuildMod == "readonly" {
 		base.Fatalf("go: updates to go.sum needed, disabled by -mod=readonly")
 	}
+	if _, ok := fsys.OverlayPath(GoSumFile); ok {
+		base.Fatalf("go: updates to go.sum needed, but go.sum is part of the overlay specified with -overlay")
+	}
 
 	// Make a best-effort attempt to acquire the side lock, only to exclude
 	// previous versions of the 'go' command from making simultaneous edits.
diff --git a/src/cmd/go/internal/modload/init.go b/src/cmd/go/internal/modload/init.go
index 09136b7..a8cbd9f 100644
--- a/src/cmd/go/internal/modload/init.go
+++ b/src/cmd/go/internal/modload/init.go
@@ -412,7 +412,16 @@
 	}
 
 	gomod := ModFilePath()
-	data, err := lockedfile.Read(gomod)
+	var data []byte
+	var err error
+	if gomodActual, ok := fsys.OverlayPath(gomod); ok {
+		// Don't lock go.mod if it's part of the overlay.
+		// On Plan 9, locking requires chmod, and we don't want to modify any file
+		// in the overlay. See #44700.
+		data, err = os.ReadFile(gomodActual)
+	} else {
+		data, err = lockedfile.Read(gomodActual)
+	}
 	if err != nil {
 		base.Fatalf("go: %v", err)
 	}
@@ -1026,6 +1035,13 @@
 		}
 		return
 	}
+	gomod := ModFilePath()
+	if _, ok := fsys.OverlayPath(gomod); ok {
+		if dirty {
+			base.Fatalf("go: updates to go.mod needed, but go.mod is part of the overlay specified with -overlay")
+		}
+		return
+	}
 
 	new, err := modFile.Format()
 	if err != nil {
diff --git a/src/cmd/go/internal/modload/modfile.go b/src/cmd/go/internal/modload/modfile.go
index 1145ac4..d280945 100644
--- a/src/cmd/go/internal/modload/modfile.go
+++ b/src/cmd/go/internal/modload/modfile.go
@@ -8,6 +8,7 @@
 	"context"
 	"errors"
 	"fmt"
+	"os"
 	"path/filepath"
 	"strings"
 	"sync"
@@ -15,6 +16,7 @@
 
 	"cmd/go/internal/base"
 	"cmd/go/internal/cfg"
+	"cmd/go/internal/fsys"
 	"cmd/go/internal/lockedfile"
 	"cmd/go/internal/modfetch"
 	"cmd/go/internal/par"
@@ -601,8 +603,16 @@
 				dir = filepath.Join(ModRoot(), dir)
 			}
 			gomod := filepath.Join(dir, "go.mod")
-
-			data, err := lockedfile.Read(gomod)
+			var data []byte
+			var err error
+			if gomodActual, ok := fsys.OverlayPath(gomod); ok {
+				// Don't lock go.mod if it's part of the overlay.
+				// On Plan 9, locking requires chmod, and we don't want to modify any file
+				// in the overlay. See #44700.
+				data, err = os.ReadFile(gomodActual)
+			} else {
+				data, err = lockedfile.Read(gomodActual)
+			}
 			if err != nil {
 				return cached{nil, module.VersionError(m, fmt.Errorf("reading %s: %v", base.ShortPath(gomod), err))}
 			}
diff --git a/src/cmd/go/testdata/script/mod_overlay.txt b/src/cmd/go/testdata/script/mod_overlay.txt
index 92e79c7..86ab04b 100644
--- a/src/cmd/go/testdata/script/mod_overlay.txt
+++ b/src/cmd/go/testdata/script/mod_overlay.txt
@@ -21,7 +21,7 @@
 cd $WORK/gopath/src/get-doesnt-add-dep
 cp $WORK/overlay/get_doesnt_add_dep_go_mod $WORK/want_go_mod
 ! go get -d -overlay overlay.json .
-stderr 'overlaid files can''t be opened for write'
+stderr '^go: updates to go.mod needed, but go.mod is part of the overlay specified with -overlay$'
 cmp $WORK/overlay/get_doesnt_add_dep_go_mod $WORK/want_go_mod
 
 # Content of overlaid go.sum is used.
@@ -41,10 +41,10 @@
 # attempting to update the file
 cp incomplete-sum-file $WORK/overlay/overlay-sum-used-correct-sums
 ! go get -d -overlay overlay.json .
-stderr 'overlaid files can''t be opened for write'
+stderr '^go: updates to go.sum needed, but go.sum is part of the overlay specified with -overlay$'
 cmp incomplete-sum-file $WORK/overlay/overlay-sum-used-correct-sums
 ! go mod tidy -overlay overlay.json
-stderr 'overlaid files can''t be opened for write'
+stderr '^go: updates to go.sum needed, but go.sum is part of the overlay specified with -overlay$'
 cmp incomplete-sum-file $WORK/overlay/overlay-sum-used-correct-sums
 
 # -overlay works with -modfile.
@@ -56,7 +56,7 @@
 stdout 'found.the/module'
 # Even with -modfile, overlaid files can't be opened for write.
 ! go get -modfile=alternate.mod -overlay overlay.json -d rsc.io/quote
-stderr 'overlaid files can''t be opened for write'
+stderr '^go: updates to go.mod needed, but go.mod is part of the overlay specified with -overlay$'
 
 # Carving out a module by adding an overlaid go.mod file
 cd $WORK/gopath/src/carve
@@ -78,7 +78,7 @@
 stdout ^carve2/nomod$
 # Editing go.mod file fails because overlay is read only
 ! go get -overlay overlay.json -d rsc.io/quote
-stderr 'overlaid files can''t be opened for write'
+stderr '^go: updates to go.mod needed, but go.mod is part of the overlay specified with -overlay$'
 ! grep rsc.io/quote $WORK/overlay/carve2-nomod-go.mod
 # Editing go.mod file succeeds because we use -modfile to redirect to same file
 go get -overlay overlay.json -modfile $WORK/overlay/carve2-nomod-go.mod -d rsc.io/quote