]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/go: check if build output should overwrite files with renames
authorSean Liao <sean@liao.dev>
Sun, 19 Oct 2025 19:00:55 +0000 (20:00 +0100)
committerSean Liao <sean@liao.dev>
Wed, 22 Oct 2025 20:35:02 +0000 (13:35 -0700)
CopyFile has a check to ensure that only object files are overwritten.
Extend this to moveOrCopyFile, so the check also happens when the source
and destination file are on the same filesystem (when renames are a
valid way of moving files).

Fixes #75970

Change-Id: Ie667301f1c9c00b114cfd91cdf8053ac20fd817b
Reviewed-on: https://go-review.googlesource.com/c/go/+/712960
Reviewed-by: Laurent Demailly <ldemailly@gmail.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Michael Matloob <matloob@google.com>
Reviewed-by: Ian Alexander <jitsu@google.com>
Reviewed-by: Michael Matloob <matloob@golang.org>
src/cmd/go/internal/work/shell.go
src/cmd/go/testdata/script/build_output_overwrite.txt [new file with mode: 0644]

index 284ed26f223d7d32a304651976df039841e7ab33..ceff84d81f831f6181da7eb00d63a34061f24fa8 100644 (file)
@@ -123,6 +123,11 @@ func (sh *Shell) moveOrCopyFile(dst, src string, perm fs.FileMode, force bool) e
                return nil
        }
 
+       err := checkDstOverwrite(dst, force)
+       if err != nil {
+               return err
+       }
+
        // If we can update the mode and rename to the dst, do it.
        // Otherwise fall back to standard copy.
 
@@ -193,16 +198,9 @@ func (sh *Shell) CopyFile(dst, src string, perm fs.FileMode, force bool) error {
        }
        defer sf.Close()
 
-       // Be careful about removing/overwriting dst.
-       // Do not remove/overwrite if dst exists and is a directory
-       // or a non-empty non-object file.
-       if fi, err := os.Stat(dst); err == nil {
-               if fi.IsDir() {
-                       return fmt.Errorf("build output %q already exists and is a directory", dst)
-               }
-               if !force && fi.Mode().IsRegular() && fi.Size() != 0 && !isObject(dst) {
-                       return fmt.Errorf("build output %q already exists and is not an object file", dst)
-               }
+       err = checkDstOverwrite(dst, force)
+       if err != nil {
+               return err
        }
 
        // On Windows, remove lingering ~ file from last attempt.
@@ -247,6 +245,21 @@ func mayberemovefile(s string) {
        os.Remove(s)
 }
 
+// Be careful about removing/overwriting dst.
+// Do not remove/overwrite if dst exists and is a directory
+// or a non-empty non-object file.
+func checkDstOverwrite(dst string, force bool) error {
+       if fi, err := os.Stat(dst); err == nil {
+               if fi.IsDir() {
+                       return fmt.Errorf("build output %q already exists and is a directory", dst)
+               }
+               if !force && fi.Mode().IsRegular() && fi.Size() != 0 && !isObject(dst) {
+                       return fmt.Errorf("build output %q already exists and is not an object file", dst)
+               }
+       }
+       return nil
+}
+
 // writeFile writes the text to file.
 func (sh *Shell) writeFile(file string, text []byte) error {
        if cfg.BuildN || cfg.BuildX {
diff --git a/src/cmd/go/testdata/script/build_output_overwrite.txt b/src/cmd/go/testdata/script/build_output_overwrite.txt
new file mode 100644 (file)
index 0000000..c7b967c
--- /dev/null
@@ -0,0 +1,20 @@
+# windows executables have the .exe extension and won't overwrite source files
+[GOOS:windows] skip
+
+mkdir out
+env GOTMPDIR=$PWD/out
+
+grep 'this should still exist' foo.go
+
+! go build
+stderr 'already exists and is not an object file'
+
+grep 'this should still exist' foo.go
+
+-- go.mod --
+module foo.go
+
+-- foo.go --
+package main // this should still exist
+
+func main() {}