From 7c9fa4d5e9b65be396d7794f645b1f409a9bc39f Mon Sep 17 00:00:00 2001 From: Sean Liao Date: Sun, 19 Oct 2025 20:00:55 +0100 Subject: [PATCH] cmd/go: check if build output should overwrite files with renames 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 LUCI-TryBot-Result: Go LUCI Reviewed-by: Michael Matloob Reviewed-by: Ian Alexander Reviewed-by: Michael Matloob --- src/cmd/go/internal/work/shell.go | 33 +++++++++++++------ .../script/build_output_overwrite.txt | 20 +++++++++++ 2 files changed, 43 insertions(+), 10 deletions(-) create mode 100644 src/cmd/go/testdata/script/build_output_overwrite.txt diff --git a/src/cmd/go/internal/work/shell.go b/src/cmd/go/internal/work/shell.go index 284ed26f22..ceff84d81f 100644 --- a/src/cmd/go/internal/work/shell.go +++ b/src/cmd/go/internal/work/shell.go @@ -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 index 0000000000..c7b967ccec --- /dev/null +++ b/src/cmd/go/testdata/script/build_output_overwrite.txt @@ -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() {} -- 2.52.0