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 <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>
This commit is contained in:
Sean Liao 2025-10-19 20:00:55 +01:00
parent 557b4d6e0f
commit 7c9fa4d5e9
2 changed files with 43 additions and 10 deletions

View 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 {

View file

@ -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() {}