]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/go: default to mod=readonly when the go.mod file is read-only
authorBryan C. Mills <bcmills@google.com>
Thu, 31 Oct 2019 20:54:21 +0000 (16:54 -0400)
committerBryan C. Mills <bcmills@google.com>
Fri, 1 Nov 2019 19:01:14 +0000 (19:01 +0000)
Updates #30185
Updates #33326
Updates #34822

Change-Id: Ie13651585898d1bbbf4f779b97ee50b6c7e7ad50
Reviewed-on: https://go-review.googlesource.com/c/go/+/204521
Run-TryBot: Bryan C. Mills <bcmills@google.com>
Reviewed-by: Jay Conrod <jayconrod@google.com>
src/cmd/go/internal/cfg/cfg.go
src/cmd/go/internal/modfetch/repo.go
src/cmd/go/internal/modload/init.go
src/cmd/go/internal/modload/stat_openfile.go [new file with mode: 0644]
src/cmd/go/internal/modload/stat_unix.go [new file with mode: 0644]
src/cmd/go/internal/modload/stat_windows.go [new file with mode: 0644]
src/cmd/go/script_test.go
src/cmd/go/testdata/script/mod_readonly.txt
src/cmd/go/testdata/script/mod_vendor_auto.txt

index b5d6ddca17391562e70ddbac5429484c0f335c70..1f7ece7165c7494ed0244767124a6672d5960e6a 100644 (file)
@@ -27,6 +27,7 @@ var (
        BuildBuildmode         string // -buildmode flag
        BuildContext           = defaultContext()
        BuildMod               string             // -mod flag
+       BuildModReason         string             // reason -mod flag is set, if set by default
        BuildI                 bool               // -i flag
        BuildLinkshared        bool               // -linkshared flag
        BuildMSan              bool               // -msan flag
index 2ecd13cabe4654d2d2922819d960f559010032de..92a486d2cf0e9404f33de0d10a3c318c1547a58a 100644 (file)
@@ -5,7 +5,6 @@
 package modfetch
 
 import (
-       "errors"
        "fmt"
        "io"
        "os"
@@ -215,7 +214,7 @@ func Lookup(proxy, path string) (Repo, error) {
 // lookup returns the module with the given module path.
 func lookup(proxy, path string) (r Repo, err error) {
        if cfg.BuildMod == "vendor" {
-               return nil, errModVendor
+               return nil, errLookupDisabled
        }
 
        if str.GlobsMatchPath(cfg.GONOPROXY, path) {
@@ -239,11 +238,21 @@ func lookup(proxy, path string) (r Repo, err error) {
        }
 }
 
+type lookupDisabledError struct{}
+
+func (lookupDisabledError) Error() string {
+       if cfg.BuildModReason == "" {
+               return fmt.Sprintf("module lookup disabled by -mod=%s", cfg.BuildMod)
+       }
+       return fmt.Sprintf("module lookup disabled by -mod=%s\n\t(%s)", cfg.BuildMod, cfg.BuildModReason)
+}
+
+var errLookupDisabled error = lookupDisabledError{}
+
 var (
-       errModVendor       = errors.New("module lookup disabled by -mod=vendor")
-       errProxyOff        = notExistError("module lookup disabled by GOPROXY=off")
-       errNoproxy   error = notExistError("disabled by GOPRIVATE/GONOPROXY")
-       errUseProxy  error = notExistError("path does not match GOPRIVATE/GONOPROXY")
+       errProxyOff       = notExistError("module lookup disabled by GOPROXY=off")
+       errNoproxy  error = notExistError("disabled by GOPRIVATE/GONOPROXY")
+       errUseProxy error = notExistError("path does not match GOPRIVATE/GONOPROXY")
 )
 
 func lookupDirect(path string) (Repo, error) {
index 48ffe99643f9e24bfe43dd0f287a67690a887ffb..82ec62ea0824c60f82eae671e8720b9eeea29013 100644 (file)
@@ -460,25 +460,36 @@ func setDefaultBuildMod() {
                // manipulate the build list.
                return
        }
-       if modRoot != "" {
-               if fi, err := os.Stat(filepath.Join(modRoot, "vendor")); err == nil && fi.IsDir() {
-                       modGo := "unspecified"
-                       if modFile.Go != nil {
-                               if semver.Compare("v"+modFile.Go.Version, "v1.14") >= 0 {
-                                       // The Go version is at least 1.14, and a vendor directory exists.
-                                       // Set -mod=vendor by default.
-                                       cfg.BuildMod = "vendor"
-                                       return
-                               } else {
-                                       modGo = modFile.Go.Version
-                               }
+       if modRoot == "" {
+               return
+       }
+
+       if fi, err := os.Stat(filepath.Join(modRoot, "vendor")); err == nil && fi.IsDir() {
+               modGo := "unspecified"
+               if modFile.Go != nil {
+                       if semver.Compare("v"+modFile.Go.Version, "v1.14") >= 0 {
+                               // The Go version is at least 1.14, and a vendor directory exists.
+                               // Set -mod=vendor by default.
+                               cfg.BuildMod = "vendor"
+                               cfg.BuildModReason = "Go version in go.mod is at least 1.14 and vendor directory exists."
+                               return
+                       } else {
+                               modGo = modFile.Go.Version
                        }
-                       fmt.Fprintf(os.Stderr, "go: not defaulting to -mod=vendor because go.mod 'go' version is %s\n", modGo)
                }
+
+               // Since a vendor directory exists, we have a non-trivial reason for
+               // choosing -mod=mod, although it probably won't be used for anything.
+               // Record the reason anyway for consistency.
+               // It may be overridden if we switch to mod=readonly below.
+               cfg.BuildModReason = fmt.Sprintf("Go version in go.mod is %s.", modGo)
        }
 
-       // TODO(golang.org/issue/33326): set -mod=readonly implicitly if the go.mod
-       // file is itself read-only?
+       p := ModFilePath()
+       if fi, err := os.Stat(p); err == nil && !hasWritePerm(p, fi) {
+               cfg.BuildMod = "readonly"
+               cfg.BuildModReason = "go.mod file is read-only."
+       }
 }
 
 // checkVendorConsistency verifies that the vendor/modules.txt file matches (if
@@ -858,7 +869,11 @@ func WriteGoMod() {
        if dirty && cfg.BuildMod == "readonly" {
                // If we're about to fail due to -mod=readonly,
                // prefer to report a dirty go.mod over a dirty go.sum
-               base.Fatalf("go: updates to go.mod needed, disabled by -mod=readonly")
+               if cfg.BuildModReason != "" {
+                       base.Fatalf("go: updates to go.mod needed, disabled by -mod=readonly\n\t(%s)", cfg.BuildModReason)
+               } else {
+                       base.Fatalf("go: updates to go.mod needed, disabled by -mod=readonly")
+               }
        }
        // Always update go.sum, even if we didn't change go.mod: we may have
        // downloaded modules that we didn't have before.
diff --git a/src/cmd/go/internal/modload/stat_openfile.go b/src/cmd/go/internal/modload/stat_openfile.go
new file mode 100644 (file)
index 0000000..931aaf1
--- /dev/null
@@ -0,0 +1,27 @@
+// Copyright 2019 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build aix js,wasm plan9
+
+// On plan9, per http://9p.io/magic/man2html/2/access: “Since file permissions
+// are checked by the server and group information is not known to the client,
+// access must open the file to check permissions.”
+//
+// aix and js,wasm are similar, in that they do not define syscall.Access.
+
+package modload
+
+import (
+       "os"
+)
+
+// hasWritePerm reports whether the current user has permission to write to the
+// file with the given info.
+func hasWritePerm(path string, _ os.FileInfo) bool {
+       if f, err := os.OpenFile(path, os.O_WRONLY, 0); err == nil {
+               f.Close()
+               return true
+       }
+       return false
+}
diff --git a/src/cmd/go/internal/modload/stat_unix.go b/src/cmd/go/internal/modload/stat_unix.go
new file mode 100644 (file)
index 0000000..ea3b801
--- /dev/null
@@ -0,0 +1,31 @@
+// Copyright 2019 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build darwin dragonfly freebsd linux netbsd openbsd solaris
+
+package modload
+
+import (
+       "os"
+       "syscall"
+)
+
+// hasWritePerm reports whether the current user has permission to write to the
+// file with the given info.
+//
+// Although the root user on most Unix systems can write to files even without
+// permission, hasWritePerm reports false if no appropriate permission bit is
+// set even if the current user is root.
+func hasWritePerm(path string, fi os.FileInfo) bool {
+       if os.Getuid() == 0 {
+               // The root user can access any file, but we still want to default to
+               // read-only mode if the go.mod file is marked as globally non-writable.
+               // (If the user really intends not to be in readonly mode, they can
+               // pass -mod=mod explicitly.)
+               return fi.Mode()&0222 != 0
+       }
+
+       const W_OK = 0x2
+       return syscall.Access(path, W_OK) == nil
+}
diff --git a/src/cmd/go/internal/modload/stat_windows.go b/src/cmd/go/internal/modload/stat_windows.go
new file mode 100644 (file)
index 0000000..d7826cf
--- /dev/null
@@ -0,0 +1,23 @@
+// Copyright 2019 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build windows
+
+package modload
+
+import (
+       "os"
+)
+
+// hasWritePerm reports whether the current user has permission to write to the
+// file with the given info.
+func hasWritePerm(_ string, fi os.FileInfo) bool {
+       // Windows has a read-only attribute independent of ACLs, so use that to
+       // determine whether the file is intended to be overwritten.
+       //
+       // Per https://golang.org/pkg/os/#Chmod:
+       // “On Windows, only the 0200 bit (owner writable) of mode is used; it
+       // controls whether the file's read-only attribute is set or cleared.”
+       return fi.Mode()&0200 != 0
+}
index 362a10fa86fe1e59d8868ffc3b4e17b1e327762b..942fca85a8d350a21d62b8783b8f54b168d9a69f 100644 (file)
@@ -431,7 +431,11 @@ func (ts *testScript) cmdChmod(neg bool, args []string) {
        if err != nil || perm&uint64(os.ModePerm) != perm {
                ts.fatalf("invalid mode: %s", args[0])
        }
-       for _, path := range args[1:] {
+       for _, arg := range args[1:] {
+               path := arg
+               if !filepath.IsAbs(path) {
+                       path = filepath.Join(ts.cd, arg)
+               }
                err := os.Chmod(path, os.FileMode(perm))
                ts.check(err)
        }
index ff25f4bfe2f46013ba55771d94960107386a06ea..942a8663f6b00cfe7d3358d14b3f910bc9da7a26 100644 (file)
@@ -12,6 +12,14 @@ cp go.mod go.mod.empty
 stderr 'import lookup disabled by -mod=readonly'
 cmp go.mod go.mod.empty
 
+# -mod=readonly should be set implicitly if the go.mod file is read-only
+chmod 0400 go.mod
+env GOFLAGS=
+! go list all
+
+chmod 0600 go.mod
+env GOFLAGS=-mod=readonly
+
 # update go.mod - go get allowed
 go get rsc.io/quote
 grep rsc.io/quote go.mod
@@ -21,11 +29,11 @@ cp go.mod.empty go.mod
 go mod tidy
 
 # -mod=readonly must succeed once go.mod is up-to-date...
-go list
+go list all
 
 # ... even if it needs downloads
 go clean -modcache
-go list
+go list all
 
 # -mod=readonly should reject inconsistent go.mod files
 # (ones that would be rewritten).
index 873644b4383572465e9c9bb59c0527a9d6297688..a15db7ca187cdce32df813728d93fc0ba155607e 100644 (file)
@@ -48,7 +48,6 @@ go list -f {{.Dir}} -tags tools all
 stdout '^'$WORK'[/\\]auto$'
 stdout '^'$GOPATH'[/\\]pkg[/\\]mod[/\\]example.com[/\\]printversion@v1.0.0$'
 stdout '^'$WORK'[/\\]auto[/\\]replacement-version$'
-stderr '^go: not defaulting to -mod=vendor because go.mod .go. version is 1.13$'
 
 go list -m -f '{{.Dir}}' all
 stdout '^'$WORK'[/\\]auto$'
@@ -146,7 +145,6 @@ go list -mod=vendor -f {{.Dir}} -tags tools all
 stdout '^'$WORK'[/\\]auto$'
 stdout '^'$WORK'[/\\]auto[/\\]vendor[/\\]example.com[/\\]printversion$'
 stdout '^'$WORK'[/\\]auto[/\\]vendor[/\\]example.com[/\\]version$'
-! stderr 'not defaulting to -mod=vendor'
 
 # ...but a version mismatch for an explicit dependency should be noticed.
 cp $WORK/modules-bad-1.13.txt vendor/modules.txt