This reverts CL 381374.
Reason for revert: broke tests for x/sys/execabs.
Updates #43724.
Updates #43947.
Change-Id: I9eb3adb5728dead66dbd20f6afe1e7a77e2a26f1
Reviewed-on: https://go-review.googlesource.com/c/go/+/403058
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Run-TryBot: Dmitri Shuralyov <dmitshur@golang.org>
Reviewed-by: Dmitri Shuralyov <dmitshur@golang.org>
Auto-Submit: Dmitri Shuralyov <dmitshur@golang.org>
TryBot-Result: Gopher Robot <gobot@golang.org>
Auto-Submit: Bryan Mills <bcmills@google.com>
+++ /dev/null
-pkg os/exec, type Cmd struct, Err error #43724
-pkg os/exec, var ErrDot error #43724
var (
goarch string
gorootBin string
- gorootBinGo string
gohostarch string
gohostos string
goos string
goroot = filepath.Clean(b)
gorootBin = pathf("%s/bin", goroot)
- // Don't run just 'go' because the build infrastructure
- // runs cmd/dist inside go/bin often, and on Windows
- // it will be found in the current directory and refuse to exec.
- // All exec calls rewrite "go" into gorootBinGo.
- gorootBinGo = pathf("%s/bin/go", goroot)
-
b = os.Getenv("GOROOT_FINAL")
if b == "" {
b = goroot
gogcflags = os.Getenv("GO_GCFLAGS")
var t tester
-
var noRebuild bool
flag.BoolVar(&t.listMode, "list", false, "list available tests")
flag.BoolVar(&t.rebuild, "rebuild", false, "rebuild everything first")
func (t *tester) run() {
timelog("start", "dist test")
- os.Setenv("PATH", fmt.Sprintf("%s%c%s", gorootBin, os.PathListSeparator, os.Getenv("PATH")))
+ var exeSuffix string
+ if goos == "windows" {
+ exeSuffix = ".exe"
+ }
+ if _, err := os.Stat(filepath.Join(gorootBin, "go"+exeSuffix)); err == nil {
+ os.Setenv("PATH", fmt.Sprintf("%s%c%s", gorootBin, os.PathListSeparator, os.Getenv("PATH")))
+ }
- cmd := exec.Command(gorootBinGo, "env", "CGO_ENABLED")
+ cmd := exec.Command("go", "env", "CGO_ENABLED")
cmd.Stderr = new(bytes.Buffer)
slurp, err := cmd.Output()
if err != nil {
args = append(args, "-run=^$")
}
args = append(args, stdMatches...)
- cmd := exec.Command(gorootBinGo, args...)
+ cmd := exec.Command("go", args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
args = append(args, "-bench=.*")
}
args = append(args, benchMatches...)
- cmd := exec.Command(gorootBinGo, args...)
+ cmd := exec.Command("go", args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
} else {
// Use a format string to only list packages and commands that have tests.
const format = "{{if (or .TestGoFiles .XTestGoFiles)}}{{.ImportPath}}{{end}}"
- cmd := exec.Command(gorootBinGo, "list", "-f", format)
+ cmd := exec.Command("go", "list", "-f", format)
if t.race {
cmd.Args = append(cmd.Args, "-tags=race")
}
fmt.Println("skipping terminal test; stdout/stderr not terminals")
return nil
}
- cmd := exec.Command(gorootBinGo, "test")
+ cmd := exec.Command("go", "test")
setDir(cmd, filepath.Join(os.Getenv("GOROOT"), "src/cmd/go/testdata/testterminal18153"))
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
}
list = out
- bin = list[0]
- if bin == "go" {
- bin = gorootBinGo
- }
- return bin, list[1:]
+ return list[0], list[1:]
}
func (t *tester) addCmd(dt *distTest, dir string, cmdline ...interface{}) *exec.Cmd {
}
func (t *tester) runHostTest(dir, pkg string) error {
- out, err := exec.Command(gorootBinGo, "env", "GOEXE", "GOTMPDIR").Output()
+ out, err := exec.Command("go", "env", "GOEXE", "GOTMPDIR").Output()
if err != nil {
return err
}
errprintf("run: %s\n", strings.Join(cmd, " "))
}
- bin := cmd[0]
- if bin == "go" {
- bin = gorootBinGo
- }
- xcmd := exec.Command(bin, cmd[1:]...)
+ xcmd := exec.Command(cmd[0], cmd[1:]...)
setDir(xcmd, dir)
var data []byte
var err error
[windows] exists -exec p/gcc.bat p/clang.bat
! exists p/bug.txt
! go build -x
-stderr '^cgo: C compiler "(clang|gcc)" not found: exec: "(clang|gcc)": cannot run executable found relative to current directory'
+stderr '^cgo: exec (clang|gcc): (clang|gcc) resolves to executable relative to current directory \(.[/\\](clang|gcc)(.bat)?\)$'
! exists p/bug.txt
-- go.mod --
import (
"context"
+ "fmt"
"os/exec"
+ "path/filepath"
+ "reflect"
+ "unsafe"
)
var ErrNotFound = exec.ErrNotFound
ExitError = exec.ExitError
)
+func relError(file, path string) error {
+ return fmt.Errorf("%s resolves to executable relative to current directory (.%c%s)", file, filepath.Separator, path)
+}
+
func LookPath(file string) (string, error) {
- return exec.LookPath(file)
+ path, err := exec.LookPath(file)
+ if err != nil {
+ return "", err
+ }
+ if filepath.Base(file) == file && !filepath.IsAbs(path) {
+ return "", relError(file, path)
+ }
+ return path, nil
+}
+
+func fixCmd(name string, cmd *exec.Cmd) {
+ if filepath.Base(name) == name && !filepath.IsAbs(cmd.Path) {
+ // exec.Command was called with a bare binary name and
+ // exec.LookPath returned a path which is not absolute.
+ // Set cmd.lookPathErr and clear cmd.Path so that it
+ // cannot be run.
+ lookPathErr := (*error)(unsafe.Pointer(reflect.ValueOf(cmd).Elem().FieldByName("lookPathErr").Addr().Pointer()))
+ if *lookPathErr == nil {
+ *lookPathErr = relError(name, cmd.Path)
+ }
+ cmd.Path = ""
+ }
}
func CommandContext(ctx context.Context, name string, arg ...string) *exec.Cmd {
- return exec.CommandContext(ctx, name, arg...)
+ cmd := exec.CommandContext(ctx, name, arg...)
+ fixCmd(name, cmd)
+ return cmd
+
}
func Command(name string, arg ...string) *exec.Cmd {
- return exec.Command(name, arg...)
+ cmd := exec.Command(name, arg...)
+ fixCmd(name, cmd)
+ return cmd
}
--- /dev/null
+// Copyright 2020 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.
+
+package execabs
+
+import (
+ "context"
+ "fmt"
+ "internal/testenv"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "runtime"
+ "testing"
+)
+
+func TestFixCmd(t *testing.T) {
+ cmd := &exec.Cmd{Path: "hello"}
+ fixCmd("hello", cmd)
+ if cmd.Path != "" {
+ t.Error("fixCmd didn't clear cmd.Path")
+ }
+ expectedErr := fmt.Sprintf("hello resolves to executable relative to current directory (.%chello)", filepath.Separator)
+ if err := cmd.Run(); err == nil {
+ t.Fatal("Command.Run didn't fail")
+ } else if err.Error() != expectedErr {
+ t.Fatalf("Command.Run returned unexpected error: want %q, got %q", expectedErr, err.Error())
+ }
+}
+
+func TestCommand(t *testing.T) {
+ testenv.MustHaveExec(t)
+
+ for _, cmd := range []func(string) *Cmd{
+ func(s string) *Cmd { return Command(s) },
+ func(s string) *Cmd { return CommandContext(context.Background(), s) },
+ } {
+ tmpDir := t.TempDir()
+ executable := "execabs-test"
+ if runtime.GOOS == "windows" {
+ executable += ".exe"
+ }
+ if err := os.WriteFile(filepath.Join(tmpDir, executable), []byte{1, 2, 3}, 0111); err != nil {
+ t.Fatalf("os.WriteFile failed: %s", err)
+ }
+ cwd, err := os.Getwd()
+ if err != nil {
+ t.Fatalf("os.Getwd failed: %s", err)
+ }
+ defer os.Chdir(cwd)
+ if err = os.Chdir(tmpDir); err != nil {
+ t.Fatalf("os.Chdir failed: %s", err)
+ }
+ if runtime.GOOS != "windows" {
+ // add "." to PATH so that exec.LookPath looks in the current directory on
+ // non-windows platforms as well
+ origPath := os.Getenv("PATH")
+ defer os.Setenv("PATH", origPath)
+ os.Setenv("PATH", fmt.Sprintf(".:%s", origPath))
+ }
+ expectedErr := fmt.Sprintf("execabs-test resolves to executable relative to current directory (.%c%s)", filepath.Separator, executable)
+ if err = cmd("execabs-test").Run(); err == nil {
+ t.Fatalf("Command.Run didn't fail when exec.LookPath returned a relative path")
+ } else if err.Error() != expectedErr {
+ t.Errorf("Command.Run returned unexpected error: want %q, got %q", expectedErr, err.Error())
+ }
+ }
+}
+
+func TestLookPath(t *testing.T) {
+ testenv.MustHaveExec(t)
+
+ tmpDir := t.TempDir()
+ executable := "execabs-test"
+ if runtime.GOOS == "windows" {
+ executable += ".exe"
+ }
+ if err := os.WriteFile(filepath.Join(tmpDir, executable), []byte{1, 2, 3}, 0111); err != nil {
+ t.Fatalf("os.WriteFile failed: %s", err)
+ }
+ cwd, err := os.Getwd()
+ if err != nil {
+ t.Fatalf("os.Getwd failed: %s", err)
+ }
+ defer os.Chdir(cwd)
+ if err = os.Chdir(tmpDir); err != nil {
+ t.Fatalf("os.Chdir failed: %s", err)
+ }
+ if runtime.GOOS != "windows" {
+ // add "." to PATH so that exec.LookPath looks in the current directory on
+ // non-windows platforms as well
+ origPath := os.Getenv("PATH")
+ defer os.Setenv("PATH", origPath)
+ os.Setenv("PATH", fmt.Sprintf(".:%s", origPath))
+ }
+ expectedErr := fmt.Sprintf("execabs-test resolves to executable relative to current directory (.%c%s)", filepath.Separator, executable)
+ if _, err := LookPath("execabs-test"); err == nil {
+ t.Fatalf("LookPath didn't fail when finding a non-relative path")
+ } else if err.Error() != expectedErr {
+ t.Errorf("LookPath returned unexpected error: want %q, got %q", expectedErr, err.Error())
+ }
+}
+++ /dev/null
-// Copyright 2020 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.
-
-package exec_test
-
-import (
- "errors"
- "internal/testenv"
- "io/ioutil"
- "os"
- . "os/exec"
- "path/filepath"
- "runtime"
- "strings"
- "testing"
-)
-
-func TestLookPath(t *testing.T) {
- testenv.MustHaveExec(t)
-
- tmpDir := filepath.Join(t.TempDir(), "testdir")
- if err := os.Mkdir(tmpDir, 0777); err != nil {
- t.Fatal(err)
- }
-
- executable := "execabs-test"
- if runtime.GOOS == "windows" {
- executable += ".exe"
- }
- if err := ioutil.WriteFile(filepath.Join(tmpDir, executable), []byte{1, 2, 3}, 0777); err != nil {
- t.Fatal(err)
- }
- cwd, err := os.Getwd()
- if err != nil {
- t.Fatal(err)
- }
- defer func() {
- if err := os.Chdir(cwd); err != nil {
- panic(err)
- }
- }()
- if err = os.Chdir(tmpDir); err != nil {
- t.Fatal(err)
- }
- origPath := os.Getenv("PATH")
- defer os.Setenv("PATH", origPath)
-
- // Add "." to PATH so that exec.LookPath looks in the current directory on all systems.
- // And try to trick it with "../testdir" too.
- for _, dir := range []string{".", "../testdir"} {
- os.Setenv("PATH", dir+string(filepath.ListSeparator)+origPath)
- t.Run("PATH="+dir, func(t *testing.T) {
- good := dir + "/execabs-test"
- if found, err := LookPath(good); err != nil || !strings.HasPrefix(found, good) {
- t.Fatalf("LookPath(%q) = %q, %v, want \"%s...\", nil", good, found, err, good)
- }
- if runtime.GOOS == "windows" {
- good = dir + `\execabs-test`
- if found, err := LookPath(good); err != nil || !strings.HasPrefix(found, good) {
- t.Fatalf("LookPath(%q) = %q, %v, want \"%s...\", nil", good, found, err, good)
- }
- }
-
- if _, err := LookPath("execabs-test"); err == nil {
- t.Fatalf("LookPath didn't fail when finding a non-relative path")
- } else if !errors.Is(err, ErrDot) {
- t.Fatalf("LookPath returned unexpected error: want Is ErrDot, got %q", err)
- }
-
- cmd := Command("execabs-test")
- if cmd.Err == nil {
- t.Fatalf("Command didn't fail when finding a non-relative path")
- } else if !errors.Is(cmd.Err, ErrDot) {
- t.Fatalf("Command returned unexpected error: want Is ErrDot, got %q", cmd.Err)
- }
- cmd.Err = nil
-
- // Clearing cmd.Err should let the execution proceed,
- // and it should fail because it's not a valid binary.
- if err := cmd.Run(); err == nil {
- t.Fatalf("Run did not fail: expected exec error")
- } else if errors.Is(err, ErrDot) {
- t.Fatalf("Run returned unexpected error ErrDot: want error like ENOEXEC: %q", err)
- }
- })
- }
-}
// Note that the examples in this package assume a Unix system.
// They may not run on Windows, and they do not run in the Go Playground
// used by golang.org and godoc.org.
-//
-// Executables in the current directory
-//
-// The functions Command and LookPath look for a program
-// in the directories listed in the current path, following the
-// conventions of the host operating system.
-// Operating systems have for decades included the current
-// directory in this search, sometimes implicitly and sometimes
-// configured explicitly that way by default.
-// Modern practice is that including the current directory
-// is usually unexpected and often leads to security problems.
-//
-// To avoid those security problems, as of Go 1.19, this package will not resolve a program
-// using an implicit or explicit path entry relative to the current directory.
-// That is, if you run exec.LookPath("go"), it will not successfully return
-// ./go on Unix nor .\go.exe on Windows, no matter how the path is configured.
-// Instead, if the usual path algorithms would result in that answer,
-// these functions return an error err satisfying errors.Is(err, ErrDot).
-//
-// For example, consider these two program snippets:
-//
-// path, err := exec.LookPath("prog")
-// if err != nil {
-// log.Fatal(err)
-// }
-// use(path)
-//
-// and
-//
-// cmd := exec.Command("prog")
-// if err := cmd.Run(); err != nil {
-// log.Fatal(err)
-// }
-//
-// These will not find and run ./prog or .\prog.exe,
-// no matter how the current path is configured.
-//
-// Code that always wants to run a program from the current directory
-// can be rewritten to say "./prog" instead of "prog".
-//
-// Code that insists on including results from relative path entries
-// can instead override the error using an errors.Is check:
-//
-// path, err := exec.LookPath("prog")
-// if errors.Is(err, exec.ErrDot) {
-// err = nil
-// }
-// if err != nil {
-// log.Fatal(err)
-// }
-// use(path)
-//
-// and
-//
-// cmd := exec.Command("prog")
-// if errors.Is(cmd.Err, exec.ErrDot) {
-// cmd.Err = nil
-// }
-// if err := cmd.Run(); err != nil {
-// log.Fatal(err)
-// }
-//
-// Before adding such overrides, make sure you understand the
-// security implications of doing so.
-// See https://go.dev/blog/path-security for more information.
package exec
import (
ProcessState *os.ProcessState
ctx context.Context // nil means none
- Err error // LookPath error, if any.
+ lookPathErr error // LookPath error, if any.
finished bool // when Wait was called
childFiles []*os.File
closeAfterStart []io.Closer
}
if filepath.Base(name) == name {
if lp, err := LookPath(name); err != nil {
- cmd.Err = err
+ cmd.lookPathErr = err
} else {
cmd.Path = lp
}
// In particular, it is not suitable for use as input to a shell.
// The output of String may vary across Go releases.
func (c *Cmd) String() string {
- if c.Err != nil {
+ if c.lookPathErr != nil {
// failed to resolve path; report the original requested path (plus args)
return strings.Join(c.Args, " ")
}
// lookExtensions does not search PATH, instead it converts `prog` into `.\prog`.
func lookExtensions(path, dir string) (string, error) {
if filepath.Base(path) == path {
- path = "." + string(filepath.Separator) + path
+ path = filepath.Join(".", path)
}
if dir == "" {
return LookPath(path)
// The Wait method will return the exit code and release associated resources
// once the command exits.
func (c *Cmd) Start() error {
- if c.Err != nil {
+ if c.lookPathErr != nil {
c.closeDescriptors(c.closeAfterStart)
c.closeDescriptors(c.closeAfterWait)
- return c.Err
+ return c.lookPathErr
}
if runtime.GOOS == "windows" {
lp, err := lookExtensions(c.Path, c.Dir)
}
return append(env, "SYSTEMROOT="+os.Getenv("SYSTEMROOT"))
}
-
-// ErrDot indicates that a path lookup resolved to an executable
-// in the current directory due to ‘.’ being in the path, either
-// implicitly or explicitly. See the package documentation for details.
-//
-// Note that functions in this package do not return ErrDot directly.
-// Code should use errors.Is(err, ErrDot), not err == ErrDot,
-// to test whether a returned error err is due to this condition.
-var ErrDot = errors.New("cannot run executable found relative to current directory")
// directories named by the path environment variable.
// If file begins with "/", "#", "./", or "../", it is tried
// directly and the path is not consulted.
-// On success, the result is an absolute path.
-//
-// In older versions of Go, LookPath could return a path relative to the current directory.
-// As of Go 1.19, LookPath will instead return that path along with an error satisfying
-// errors.Is(err, ErrDot). See the package documentation for more details.
+// The result may be an absolute path or a path relative to the current directory.
func LookPath(file string) (string, error) {
// skip the path lookup for these prefixes
skip := []string{"/", "#", "./", "../"}
for _, dir := range filepath.SplitList(path) {
path := filepath.Join(dir, file)
if err := findExecutable(path); err == nil {
- if !filepath.IsAbs(path) {
- return path, &Error{file, ErrDot}
- }
return path, nil
}
}
// LookPath searches for an executable named file in the
// directories named by the PATH environment variable.
// If file contains a slash, it is tried directly and the PATH is not consulted.
-// Otherwise, on success, the result is an absolute path.
-//
-// In older versions of Go, LookPath could return a path relative to the current directory.
-// As of Go 1.19, LookPath will instead return that path along with an error satisfying
-// errors.Is(err, ErrDot). See the package documentation for more details.
+// The result may be an absolute path or a path relative to the current directory.
func LookPath(file string) (string, error) {
// NOTE(rsc): I wish we could use the Plan 9 behavior here
// (only bypass the path if file begins with / or ./ or ../)
}
path := filepath.Join(dir, file)
if err := findExecutable(path); err == nil {
- if !filepath.IsAbs(path) {
- return path, &Error{file, ErrDot}
- }
return path, nil
}
}
"os"
"path/filepath"
"strings"
- "syscall"
)
// ErrNotFound is the error resulting if a path search failed to find an executable file.
// LookPath searches for an executable named file in the
// directories named by the PATH environment variable.
+// If file contains a slash, it is tried directly and the PATH is not consulted.
// LookPath also uses PATHEXT environment variable to match
// a suitable candidate.
-// If file contains a slash, it is tried directly and the PATH is not consulted.
-// Otherwise, on success, the result is an absolute path.
-//
-// In older versions of Go, LookPath could return a path relative to the current directory.
-// As of Go 1.19, LookPath will instead return that path along with an error satisfying
-// errors.Is(err, ErrDot). See the package documentation for more details.
+// The result may be an absolute path or a path relative to the current directory.
func LookPath(file string) (string, error) {
var exts []string
x := os.Getenv(`PATHEXT`)
}
if strings.ContainsAny(file, `:\/`) {
- f, err := findExecutable(file, exts)
- if err == nil {
+ if f, err := findExecutable(file, exts); err == nil {
return f, nil
+ } else {
+ return "", &Error{file, err}
}
- return "", &Error{file, err}
}
-
- // On Windows, creating the NoDefaultCurrentDirectoryInExePath
- // environment variable (with any value or no value!) signals that
- // path lookups should skip the current directory.
- // In theory we are supposed to call NeedCurrentDirectoryForExePathW
- // "as the registry location of this environment variable can change"
- // but that seems exceedingly unlikely: it would break all users who
- // have configured their environment this way!
- // https://docs.microsoft.com/en-us/windows/win32/api/processenv/nf-processenv-needcurrentdirectoryforexepathw
- // See also go.dev/issue/43947.
- if _, found := syscall.Getenv("NoDefaultCurrentDirectoryInExePath"); !found {
- if f, err := findExecutable(filepath.Join(".", file), exts); err == nil {
- return f, &Error{file, ErrDot}
- }
+ if f, err := findExecutable(filepath.Join(".", file), exts); err == nil {
+ return f, nil
}
-
path := os.Getenv("path")
for _, dir := range filepath.SplitList(path) {
if f, err := findExecutable(filepath.Join(dir, file), exts); err == nil {
- if !filepath.IsAbs(f) {
- return f, &Error{file, ErrDot}
- }
return f, nil
}
}
package exec_test
import (
- "errors"
"fmt"
"internal/testenv"
"io"
func cmdExec(args ...string) {
cmd := exec.Command(args[1])
cmd.Dir = args[0]
- if errors.Is(cmd.Err, exec.ErrDot) {
- cmd.Err = nil
- }
output, err := cmd.CombinedOutput()
if err != nil {
fmt.Fprintf(os.Stderr, "Child: %s %s", err, string(output))
},
}
-func TestLookPathWindows(t *testing.T) {
+func TestLookPath(t *testing.T) {
tmp := t.TempDir()
printpathExe := buildPrintPathExe(t, tmp)