"go/parser"
"go/token"
"go/types"
+ exec "internal/execabs"
"io"
"io/ioutil"
"log"
"os"
- "os/exec"
"path/filepath"
"regexp"
"runtime"
import (
"fmt"
+ exec "internal/execabs"
"log"
"os"
- "os/exec"
"path/filepath"
"runtime"
"strings"
"go/ast"
"go/printer"
"go/token"
+ exec "internal/execabs"
"internal/xcoff"
"io"
"io/ioutil"
"os"
- "os/exec"
"path/filepath"
"regexp"
"sort"
"bytes"
"fmt"
"go/token"
+ exec "internal/execabs"
"io/ioutil"
"os"
- "os/exec"
)
// run runs the command argv, feeding in stdin on standard input.
"cmd/internal/src"
"fmt"
"html"
+ exec "internal/execabs"
"io"
"os"
- "os/exec"
"path/filepath"
"strconv"
"strings"
"go/ast"
"go/parser"
"go/token"
+ exec "internal/execabs"
"io"
"os"
- "os/exec"
"path"
"path/filepath"
"runtime"
import (
"os"
- "os/exec"
+ exec "internal/execabs"
"strings"
)
continue
}
if strings.HasPrefix(line, `import "`) || strings.HasPrefix(line, `import . "`) ||
- inBlock && (strings.HasPrefix(line, "\t\"") || strings.HasPrefix(line, "\t. \"")) {
+ inBlock && (strings.HasPrefix(line, "\t\"") || strings.HasPrefix(line, "\t. \"") || strings.HasPrefix(line, "\texec \"")) {
line = strings.Replace(line, `"cmd/`, `"bootstrap/cmd/`, -1)
+ // During bootstrap, must use plain os/exec.
+ line = strings.Replace(line, `exec "internal/execabs"`, `"os/exec"`, -1)
for _, dir := range bootstrapDirs {
if strings.HasPrefix(dir, "cmd/") {
continue
import (
"bytes"
"fmt"
+ exec "internal/execabs"
"log"
"os"
- "os/exec"
"path/filepath"
"regexp"
"strings"
"go/ast"
"go/parser"
"go/token"
+ exec "internal/execabs"
"io/ioutil"
"os"
- "os/exec"
"path/filepath"
"reflect"
"runtime"
"flag"
"fmt"
"go/scanner"
+ exec "internal/execabs"
"log"
"os"
- "os/exec"
"strings"
"sync"
import (
"bytes"
"fmt"
+ exec "internal/execabs"
"io"
"io/ioutil"
urlpkg "net/url"
"os"
- "os/exec"
"path/filepath"
"regexp"
"runtime"
"bufio"
"bytes"
"fmt"
+ exec "internal/execabs"
"io"
"log"
"os"
- "os/exec"
"path/filepath"
"regexp"
"strconv"
"bytes"
"crypto/sha256"
"fmt"
+ exec "internal/execabs"
"io"
"io/ioutil"
"os"
- "os/exec"
"path/filepath"
"strings"
"sync"
"bytes"
"errors"
"fmt"
+ exec "internal/execabs"
"io"
"io/ioutil"
"net/url"
"os"
- "os/exec"
"path/filepath"
"sort"
"strconv"
"errors"
"fmt"
"go/build"
+ exec "internal/execabs"
"io"
"io/ioutil"
"os"
- "os/exec"
"path"
"path/filepath"
"regexp"
import (
"fmt"
+ exec "internal/execabs"
"os"
- "os/exec"
"sort"
"strings"
"encoding/json"
"flag"
"fmt"
+ exec "internal/execabs"
"log"
"os"
- "os/exec"
"path/filepath"
"strings"
"errors"
"fmt"
"go/build"
+ exec "internal/execabs"
"os"
- "os/exec"
"path/filepath"
"runtime"
"strings"
import (
"bytes"
"fmt"
+ exec "internal/execabs"
"io/ioutil"
"os"
- "os/exec"
"strings"
"cmd/go/internal/base"
"encoding/json"
"errors"
"fmt"
+ exec "internal/execabs"
"internal/lazyregexp"
"io"
"io/ioutil"
"log"
"math/rand"
"os"
- "os/exec"
"path/filepath"
"regexp"
"runtime"
import (
"fmt"
+ exec "internal/execabs"
"io/ioutil"
"os"
- "os/exec"
"path/filepath"
"strings"
"io/ioutil"
"log"
"os"
- "os/exec"
+ exec "internal/execabs"
"path/filepath"
"strings"
package browser
import (
+ exec "internal/execabs"
"os"
- "os/exec"
"runtime"
"time"
)
package diff
import (
+ exec "internal/execabs"
"io/ioutil"
"os"
- "os/exec"
"runtime"
)
"cmd/internal/objabi"
"errors"
"fmt"
- "os/exec"
+ exec "internal/execabs"
"sort"
"strconv"
"strings"
package ld
import (
+ exec "internal/execabs"
"os"
- "os/exec"
"path/filepath"
"syscall"
)
"encoding/binary"
"encoding/hex"
"fmt"
+ exec "internal/execabs"
"io"
"io/ioutil"
"log"
"os"
- "os/exec"
"path/filepath"
"runtime"
"sort"
import (
"flag"
"fmt"
+ exec "internal/execabs"
"io"
"os"
- "os/exec"
"cmd/internal/test2json"
)
import (
"bufio"
"fmt"
+ exec "internal/execabs"
"internal/trace"
"io"
"io/ioutil"
"net/http"
"os"
- "os/exec"
"path/filepath"
"runtime"
"sort"
"go/doc"
"go/parser"
"go/token"
+ exec "internal/execabs"
"internal/goroot"
"internal/goversion"
"io"
"io/ioutil"
"log"
"os"
- "os/exec"
pathpkg "path"
"path/filepath"
"runtime"
"internal/lazyregexp": {"L2", "OS", "regexp"},
"internal/lazytemplate": {"L2", "OS", "text/template"},
+ "internal/execabs": {"L2", "OS", "fmt", "context", "reflect"},
+
// L4 is defined as L3+fmt+log+time, because in general once
// you're using L3 packages, use of fmt, log, or time is not a big deal.
"L4": {
"go/constant": {"L4", "go/token", "math/big"},
"go/importer": {"L4", "go/build", "go/internal/gccgoimporter", "go/internal/gcimporter", "go/internal/srcimporter", "go/token", "go/types"},
"go/internal/gcimporter": {"L4", "OS", "go/build", "go/constant", "go/token", "go/types", "text/scanner"},
- "go/internal/gccgoimporter": {"L4", "OS", "debug/elf", "go/constant", "go/token", "go/types", "internal/xcoff", "text/scanner"},
+ "go/internal/gccgoimporter": {"L4", "OS", "debug/elf", "go/constant", "go/token", "go/types", "internal/xcoff", "text/scanner", "internal/execabs"},
"go/internal/srcimporter": {"L4", "OS", "fmt", "go/ast", "go/build", "go/parser", "go/token", "go/types", "path/filepath"},
"go/types": {"L4", "GOPARSER", "container/heap", "go/constant"},
"encoding/pem": {"L4"},
"encoding/xml": {"L4", "encoding"},
"flag": {"L4", "OS"},
- "go/build": {"L4", "OS", "GOPARSER", "internal/goroot", "internal/goversion"},
+ "go/build": {"L4", "OS", "GOPARSER", "internal/goroot", "internal/goversion", "internal/execabs"},
"html": {"L4"},
"image/draw": {"L4", "image/internal/imageutil"},
"image/gif": {"L4", "compress/lzw", "image/color/palette", "image/draw"},
"image/jpeg": {"L4", "image/internal/imageutil"},
"image/png": {"L4", "compress/zlib"},
"index/suffixarray": {"L4", "regexp"},
- "internal/goroot": {"L4", "OS"},
+ "internal/goroot": {"L4", "OS", "internal/execabs"},
"internal/singleflight": {"sync"},
"internal/trace": {"L4", "OS", "container/heap"},
"internal/xcoff": {"L4", "OS", "debug/dwarf"},
import (
"bufio"
"go/types"
+ exec "internal/execabs"
"os"
- "os/exec"
"path/filepath"
"strings"
)
--- /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 is a drop-in replacement for os/exec
+// that requires PATH lookups to find absolute paths.
+// That is, execabs.Command("cmd") runs the same PATH lookup
+// as exec.Command("cmd"), but if the result is a path
+// which is relative, the Run and Start methods will report
+// an error instead of running the executable.
+package execabs
+
+import (
+ "context"
+ "fmt"
+ "os/exec"
+ "path/filepath"
+ "reflect"
+ "unsafe"
+)
+
+var ErrNotFound = exec.ErrNotFound
+
+type (
+ Cmd = exec.Cmd
+ Error = exec.Error
+ ExitError = exec.ExitError
+)
+
+func relError(file, path string) error {
+ return fmt.Errorf("%s resolves to executable in current directory (.%c%s)", file, filepath.Separator, path)
+}
+
+func LookPath(file string) (string, error) {
+ 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 {
+ cmd := exec.CommandContext(ctx, name, arg...)
+ fixCmd(name, cmd)
+ return cmd
+
+}
+
+func Command(name string, arg ...string) *exec.Cmd {
+ 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"
+ "io/ioutil"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "runtime"
+ "testing"
+)
+
+func TestFixCmd(t *testing.T) {
+ cmd := &exec.Cmd{Path: "hello"}
+ fixCmd("hello", cmd)
+ if cmd.Path != "" {
+ t.Errorf("fixCmd didn't clear cmd.Path")
+ }
+ expectedErr := fmt.Sprintf("hello resolves to executable in 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) {
+ for _, cmd := range []func(string) *Cmd{
+ func(s string) *Cmd { return Command(s) },
+ func(s string) *Cmd { return CommandContext(context.Background(), s) },
+ } {
+ tmpDir, err := ioutil.TempDir("", "execabs-test")
+ if err != nil {
+ t.Fatalf("ioutil.TempDir failed: %s", err)
+ }
+ defer os.RemoveAll(tmpDir)
+ executable := "execabs-test"
+ if runtime.GOOS == "windows" {
+ executable += ".exe"
+ }
+ if err = ioutil.WriteFile(filepath.Join(tmpDir, executable), []byte{1, 2, 3}, 0111); err != nil {
+ t.Fatalf("ioutil.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 in 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) {
+ tmpDir, err := ioutil.TempDir("", "execabs-test")
+ if err != nil {
+ t.Fatalf("ioutil.TempDir failed: %s", err)
+ }
+ defer os.RemoveAll(tmpDir)
+ executable := "execabs-test"
+ if runtime.GOOS == "windows" {
+ executable += ".exe"
+ }
+ if err = ioutil.WriteFile(filepath.Join(tmpDir, executable), []byte{1, 2, 3}, 0111); err != nil {
+ t.Fatalf("ioutil.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 in 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())
+ }
+}
package goroot
import (
+ exec "internal/execabs"
"os"
- "os/exec"
"path/filepath"
"strings"
"sync"