summaryrefslogtreecommitdiff
path: root/src/pkg/os/exec
diff options
context:
space:
mode:
Diffstat (limited to 'src/pkg/os/exec')
-rw-r--r--src/pkg/os/exec/example_test.go75
-rw-r--r--src/pkg/os/exec/exec.go493
-rw-r--r--src/pkg/os/exec/exec_test.go726
-rw-r--r--src/pkg/os/exec/lp_plan9.go53
-rw-r--r--src/pkg/os/exec/lp_test.go33
-rw-r--r--src/pkg/os/exec/lp_unix.go60
-rw-r--r--src/pkg/os/exec/lp_unix_test.go55
-rw-r--r--src/pkg/os/exec/lp_windows.go123
-rw-r--r--src/pkg/os/exec/lp_windows_test.go573
9 files changed, 0 insertions, 2191 deletions
diff --git a/src/pkg/os/exec/example_test.go b/src/pkg/os/exec/example_test.go
deleted file mode 100644
index 55eaac8ab..000000000
--- a/src/pkg/os/exec/example_test.go
+++ /dev/null
@@ -1,75 +0,0 @@
-// Copyright 2012 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 (
- "bytes"
- "encoding/json"
- "fmt"
- "log"
- "os/exec"
- "strings"
-)
-
-func ExampleLookPath() {
- path, err := exec.LookPath("fortune")
- if err != nil {
- log.Fatal("installing fortune is in your future")
- }
- fmt.Printf("fortune is available at %s\n", path)
-}
-
-func ExampleCommand() {
- cmd := exec.Command("tr", "a-z", "A-Z")
- cmd.Stdin = strings.NewReader("some input")
- var out bytes.Buffer
- cmd.Stdout = &out
- err := cmd.Run()
- if err != nil {
- log.Fatal(err)
- }
- fmt.Printf("in all caps: %q\n", out.String())
-}
-
-func ExampleCmd_Output() {
- out, err := exec.Command("date").Output()
- if err != nil {
- log.Fatal(err)
- }
- fmt.Printf("The date is %s\n", out)
-}
-
-func ExampleCmd_Start() {
- cmd := exec.Command("sleep", "5")
- err := cmd.Start()
- if err != nil {
- log.Fatal(err)
- }
- log.Printf("Waiting for command to finish...")
- err = cmd.Wait()
- log.Printf("Command finished with error: %v", err)
-}
-
-func ExampleCmd_StdoutPipe() {
- cmd := exec.Command("echo", "-n", `{"Name": "Bob", "Age": 32}`)
- stdout, err := cmd.StdoutPipe()
- if err != nil {
- log.Fatal(err)
- }
- if err := cmd.Start(); err != nil {
- log.Fatal(err)
- }
- var person struct {
- Name string
- Age int
- }
- if err := json.NewDecoder(stdout).Decode(&person); err != nil {
- log.Fatal(err)
- }
- if err := cmd.Wait(); err != nil {
- log.Fatal(err)
- }
- fmt.Printf("%s is %d years old\n", person.Name, person.Age)
-}
diff --git a/src/pkg/os/exec/exec.go b/src/pkg/os/exec/exec.go
deleted file mode 100644
index a70ed0d20..000000000
--- a/src/pkg/os/exec/exec.go
+++ /dev/null
@@ -1,493 +0,0 @@
-// Copyright 2009 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 runs external commands. It wraps os.StartProcess to make it
-// easier to remap stdin and stdout, connect I/O with pipes, and do other
-// adjustments.
-package exec
-
-import (
- "bytes"
- "errors"
- "io"
- "os"
- "path/filepath"
- "runtime"
- "strconv"
- "strings"
- "sync"
- "syscall"
-)
-
-// Error records the name of a binary that failed to be executed
-// and the reason it failed.
-type Error struct {
- Name string
- Err error
-}
-
-func (e *Error) Error() string {
- return "exec: " + strconv.Quote(e.Name) + ": " + e.Err.Error()
-}
-
-// Cmd represents an external command being prepared or run.
-type Cmd struct {
- // Path is the path of the command to run.
- //
- // This is the only field that must be set to a non-zero
- // value. If Path is relative, it is evaluated relative
- // to Dir.
- Path string
-
- // Args holds command line arguments, including the command as Args[0].
- // If the Args field is empty or nil, Run uses {Path}.
- //
- // In typical use, both Path and Args are set by calling Command.
- Args []string
-
- // Env specifies the environment of the process.
- // If Env is nil, Run uses the current process's environment.
- Env []string
-
- // Dir specifies the working directory of the command.
- // If Dir is the empty string, Run runs the command in the
- // calling process's current directory.
- Dir string
-
- // Stdin specifies the process's standard input. If Stdin is
- // nil, the process reads from the null device (os.DevNull).
- Stdin io.Reader
-
- // Stdout and Stderr specify the process's standard output and error.
- //
- // If either is nil, Run connects the corresponding file descriptor
- // to the null device (os.DevNull).
- //
- // If Stdout and Stderr are the same writer, at most one
- // goroutine at a time will call Write.
- Stdout io.Writer
- Stderr io.Writer
-
- // ExtraFiles specifies additional open files to be inherited by the
- // new process. It does not include standard input, standard output, or
- // standard error. If non-nil, entry i becomes file descriptor 3+i.
- //
- // BUG: on OS X 10.6, child processes may sometimes inherit unwanted fds.
- // http://golang.org/issue/2603
- ExtraFiles []*os.File
-
- // SysProcAttr holds optional, operating system-specific attributes.
- // Run passes it to os.StartProcess as the os.ProcAttr's Sys field.
- SysProcAttr *syscall.SysProcAttr
-
- // Process is the underlying process, once started.
- Process *os.Process
-
- // ProcessState contains information about an exited process,
- // available after a call to Wait or Run.
- ProcessState *os.ProcessState
-
- lookPathErr error // LookPath error, if any.
- finished bool // when Wait was called
- childFiles []*os.File
- closeAfterStart []io.Closer
- closeAfterWait []io.Closer
- goroutine []func() error
- errch chan error // one send per goroutine
-}
-
-// Command returns the Cmd struct to execute the named program with
-// the given arguments.
-//
-// It sets only the Path and Args in the returned structure.
-//
-// If name contains no path separators, Command uses LookPath to
-// resolve the path to a complete name if possible. Otherwise it uses
-// name directly.
-//
-// The returned Cmd's Args field is constructed from the command name
-// followed by the elements of arg, so arg should not include the
-// command name itself. For example, Command("echo", "hello")
-func Command(name string, arg ...string) *Cmd {
- cmd := &Cmd{
- Path: name,
- Args: append([]string{name}, arg...),
- }
- if filepath.Base(name) == name {
- if lp, err := LookPath(name); err != nil {
- cmd.lookPathErr = err
- } else {
- cmd.Path = lp
- }
- }
- return cmd
-}
-
-// interfaceEqual protects against panics from doing equality tests on
-// two interfaces with non-comparable underlying types.
-func interfaceEqual(a, b interface{}) bool {
- defer func() {
- recover()
- }()
- return a == b
-}
-
-func (c *Cmd) envv() []string {
- if c.Env != nil {
- return c.Env
- }
- return os.Environ()
-}
-
-func (c *Cmd) argv() []string {
- if len(c.Args) > 0 {
- return c.Args
- }
- return []string{c.Path}
-}
-
-func (c *Cmd) stdin() (f *os.File, err error) {
- if c.Stdin == nil {
- f, err = os.Open(os.DevNull)
- if err != nil {
- return
- }
- c.closeAfterStart = append(c.closeAfterStart, f)
- return
- }
-
- if f, ok := c.Stdin.(*os.File); ok {
- return f, nil
- }
-
- pr, pw, err := os.Pipe()
- if err != nil {
- return
- }
-
- c.closeAfterStart = append(c.closeAfterStart, pr)
- c.closeAfterWait = append(c.closeAfterWait, pw)
- c.goroutine = append(c.goroutine, func() error {
- _, err := io.Copy(pw, c.Stdin)
- if err1 := pw.Close(); err == nil {
- err = err1
- }
- return err
- })
- return pr, nil
-}
-
-func (c *Cmd) stdout() (f *os.File, err error) {
- return c.writerDescriptor(c.Stdout)
-}
-
-func (c *Cmd) stderr() (f *os.File, err error) {
- if c.Stderr != nil && interfaceEqual(c.Stderr, c.Stdout) {
- return c.childFiles[1], nil
- }
- return c.writerDescriptor(c.Stderr)
-}
-
-func (c *Cmd) writerDescriptor(w io.Writer) (f *os.File, err error) {
- if w == nil {
- f, err = os.OpenFile(os.DevNull, os.O_WRONLY, 0)
- if err != nil {
- return
- }
- c.closeAfterStart = append(c.closeAfterStart, f)
- return
- }
-
- if f, ok := w.(*os.File); ok {
- return f, nil
- }
-
- pr, pw, err := os.Pipe()
- if err != nil {
- return
- }
-
- c.closeAfterStart = append(c.closeAfterStart, pw)
- c.closeAfterWait = append(c.closeAfterWait, pr)
- c.goroutine = append(c.goroutine, func() error {
- _, err := io.Copy(w, pr)
- return err
- })
- return pw, nil
-}
-
-func (c *Cmd) closeDescriptors(closers []io.Closer) {
- for _, fd := range closers {
- fd.Close()
- }
-}
-
-// Run starts the specified command and waits for it to complete.
-//
-// The returned error is nil if the command runs, has no problems
-// copying stdin, stdout, and stderr, and exits with a zero exit
-// status.
-//
-// If the command fails to run or doesn't complete successfully, the
-// error is of type *ExitError. Other error types may be
-// returned for I/O problems.
-func (c *Cmd) Run() error {
- if err := c.Start(); err != nil {
- return err
- }
- return c.Wait()
-}
-
-// lookExtensions finds windows executable by its dir and path.
-// It uses LookPath to try appropriate extensions.
-// lookExtensions does not search PATH, instead it converts `prog` into `.\prog`.
-func lookExtensions(path, dir string) (string, error) {
- if filepath.Base(path) == path {
- path = filepath.Join(".", path)
- }
- if dir == "" {
- return LookPath(path)
- }
- if filepath.VolumeName(path) != "" {
- return LookPath(path)
- }
- if len(path) > 1 && os.IsPathSeparator(path[0]) {
- return LookPath(path)
- }
- dirandpath := filepath.Join(dir, path)
- // We assume that LookPath will only add file extension.
- lp, err := LookPath(dirandpath)
- if err != nil {
- return "", err
- }
- ext := strings.TrimPrefix(lp, dirandpath)
- return path + ext, nil
-}
-
-// Start starts the specified command but does not wait for it to complete.
-//
-// The Wait method will return the exit code and release associated resources
-// once the command exits.
-func (c *Cmd) Start() error {
- if c.lookPathErr != nil {
- c.closeDescriptors(c.closeAfterStart)
- c.closeDescriptors(c.closeAfterWait)
- return c.lookPathErr
- }
- if runtime.GOOS == "windows" {
- lp, err := lookExtensions(c.Path, c.Dir)
- if err != nil {
- c.closeDescriptors(c.closeAfterStart)
- c.closeDescriptors(c.closeAfterWait)
- return err
- }
- c.Path = lp
- }
- if c.Process != nil {
- return errors.New("exec: already started")
- }
-
- type F func(*Cmd) (*os.File, error)
- for _, setupFd := range []F{(*Cmd).stdin, (*Cmd).stdout, (*Cmd).stderr} {
- fd, err := setupFd(c)
- if err != nil {
- c.closeDescriptors(c.closeAfterStart)
- c.closeDescriptors(c.closeAfterWait)
- return err
- }
- c.childFiles = append(c.childFiles, fd)
- }
- c.childFiles = append(c.childFiles, c.ExtraFiles...)
-
- var err error
- c.Process, err = os.StartProcess(c.Path, c.argv(), &os.ProcAttr{
- Dir: c.Dir,
- Files: c.childFiles,
- Env: c.envv(),
- Sys: c.SysProcAttr,
- })
- if err != nil {
- c.closeDescriptors(c.closeAfterStart)
- c.closeDescriptors(c.closeAfterWait)
- return err
- }
-
- c.closeDescriptors(c.closeAfterStart)
-
- c.errch = make(chan error, len(c.goroutine))
- for _, fn := range c.goroutine {
- go func(fn func() error) {
- c.errch <- fn()
- }(fn)
- }
-
- return nil
-}
-
-// An ExitError reports an unsuccessful exit by a command.
-type ExitError struct {
- *os.ProcessState
-}
-
-func (e *ExitError) Error() string {
- return e.ProcessState.String()
-}
-
-// Wait waits for the command to exit.
-// It must have been started by Start.
-//
-// The returned error is nil if the command runs, has no problems
-// copying stdin, stdout, and stderr, and exits with a zero exit
-// status.
-//
-// If the command fails to run or doesn't complete successfully, the
-// error is of type *ExitError. Other error types may be
-// returned for I/O problems.
-//
-// Wait releases any resources associated with the Cmd.
-func (c *Cmd) Wait() error {
- if c.Process == nil {
- return errors.New("exec: not started")
- }
- if c.finished {
- return errors.New("exec: Wait was already called")
- }
- c.finished = true
- state, err := c.Process.Wait()
- c.ProcessState = state
-
- var copyError error
- for _ = range c.goroutine {
- if err := <-c.errch; err != nil && copyError == nil {
- copyError = err
- }
- }
-
- c.closeDescriptors(c.closeAfterWait)
-
- if err != nil {
- return err
- } else if !state.Success() {
- return &ExitError{state}
- }
-
- return copyError
-}
-
-// Output runs the command and returns its standard output.
-func (c *Cmd) Output() ([]byte, error) {
- if c.Stdout != nil {
- return nil, errors.New("exec: Stdout already set")
- }
- var b bytes.Buffer
- c.Stdout = &b
- err := c.Run()
- return b.Bytes(), err
-}
-
-// CombinedOutput runs the command and returns its combined standard
-// output and standard error.
-func (c *Cmd) CombinedOutput() ([]byte, error) {
- if c.Stdout != nil {
- return nil, errors.New("exec: Stdout already set")
- }
- if c.Stderr != nil {
- return nil, errors.New("exec: Stderr already set")
- }
- var b bytes.Buffer
- c.Stdout = &b
- c.Stderr = &b
- err := c.Run()
- return b.Bytes(), err
-}
-
-// StdinPipe returns a pipe that will be connected to the command's
-// standard input when the command starts.
-// The pipe will be closed automatically after Wait sees the command exit.
-// A caller need only call Close to force the pipe to close sooner.
-// For example, if the command being run will not exit until standard input
-// is closed, the caller must close the pipe.
-func (c *Cmd) StdinPipe() (io.WriteCloser, error) {
- if c.Stdin != nil {
- return nil, errors.New("exec: Stdin already set")
- }
- if c.Process != nil {
- return nil, errors.New("exec: StdinPipe after process started")
- }
- pr, pw, err := os.Pipe()
- if err != nil {
- return nil, err
- }
- c.Stdin = pr
- c.closeAfterStart = append(c.closeAfterStart, pr)
- wc := &closeOnce{File: pw}
- c.closeAfterWait = append(c.closeAfterWait, wc)
- return wc, nil
-}
-
-type closeOnce struct {
- *os.File
-
- once sync.Once
- err error
-}
-
-func (c *closeOnce) Close() error {
- c.once.Do(c.close)
- return c.err
-}
-
-func (c *closeOnce) close() {
- c.err = c.File.Close()
-}
-
-// StdoutPipe returns a pipe that will be connected to the command's
-// standard output when the command starts.
-//
-// Wait will close the pipe after seeing the command exit, so most callers
-// need not close the pipe themselves; however, an implication is that
-// it is incorrect to call Wait before all reads from the pipe have completed.
-// For the same reason, it is incorrect to call Run when using StdoutPipe.
-// See the example for idiomatic usage.
-func (c *Cmd) StdoutPipe() (io.ReadCloser, error) {
- if c.Stdout != nil {
- return nil, errors.New("exec: Stdout already set")
- }
- if c.Process != nil {
- return nil, errors.New("exec: StdoutPipe after process started")
- }
- pr, pw, err := os.Pipe()
- if err != nil {
- return nil, err
- }
- c.Stdout = pw
- c.closeAfterStart = append(c.closeAfterStart, pw)
- c.closeAfterWait = append(c.closeAfterWait, pr)
- return pr, nil
-}
-
-// StderrPipe returns a pipe that will be connected to the command's
-// standard error when the command starts.
-//
-// Wait will close the pipe after seeing the command exit, so most callers
-// need not close the pipe themselves; however, an implication is that
-// it is incorrect to call Wait before all reads from the pipe have completed.
-// For the same reason, it is incorrect to use Run when using StderrPipe.
-// See the StdoutPipe example for idiomatic usage.
-func (c *Cmd) StderrPipe() (io.ReadCloser, error) {
- if c.Stderr != nil {
- return nil, errors.New("exec: Stderr already set")
- }
- if c.Process != nil {
- return nil, errors.New("exec: StderrPipe after process started")
- }
- pr, pw, err := os.Pipe()
- if err != nil {
- return nil, err
- }
- c.Stderr = pw
- c.closeAfterStart = append(c.closeAfterStart, pw)
- c.closeAfterWait = append(c.closeAfterWait, pr)
- return pr, nil
-}
diff --git a/src/pkg/os/exec/exec_test.go b/src/pkg/os/exec/exec_test.go
deleted file mode 100644
index 6f77ac38a..000000000
--- a/src/pkg/os/exec/exec_test.go
+++ /dev/null
@@ -1,726 +0,0 @@
-// Copyright 2009 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.
-
-// Use an external test to avoid os/exec -> net/http -> crypto/x509 -> os/exec
-// circular dependency on non-cgo darwin.
-
-package exec_test
-
-import (
- "bufio"
- "bytes"
- "fmt"
- "io"
- "io/ioutil"
- "log"
- "net"
- "net/http"
- "net/http/httptest"
- "os"
- "os/exec"
- "path/filepath"
- "runtime"
- "strconv"
- "strings"
- "testing"
- "time"
-)
-
-func helperCommand(t *testing.T, s ...string) *exec.Cmd {
- if runtime.GOOS == "nacl" {
- t.Skip("skipping on nacl")
- }
- cs := []string{"-test.run=TestHelperProcess", "--"}
- cs = append(cs, s...)
- cmd := exec.Command(os.Args[0], cs...)
- cmd.Env = []string{"GO_WANT_HELPER_PROCESS=1"}
- return cmd
-}
-
-func TestEcho(t *testing.T) {
- bs, err := helperCommand(t, "echo", "foo bar", "baz").Output()
- if err != nil {
- t.Errorf("echo: %v", err)
- }
- if g, e := string(bs), "foo bar baz\n"; g != e {
- t.Errorf("echo: want %q, got %q", e, g)
- }
-}
-
-func TestCommandRelativeName(t *testing.T) {
- // Run our own binary as a relative path
- // (e.g. "_test/exec.test") our parent directory.
- base := filepath.Base(os.Args[0]) // "exec.test"
- dir := filepath.Dir(os.Args[0]) // "/tmp/go-buildNNNN/os/exec/_test"
- if dir == "." {
- t.Skip("skipping; running test at root somehow")
- }
- parentDir := filepath.Dir(dir) // "/tmp/go-buildNNNN/os/exec"
- dirBase := filepath.Base(dir) // "_test"
- if dirBase == "." {
- t.Skipf("skipping; unexpected shallow dir of %q", dir)
- }
-
- cmd := exec.Command(filepath.Join(dirBase, base), "-test.run=TestHelperProcess", "--", "echo", "foo")
- cmd.Dir = parentDir
- cmd.Env = []string{"GO_WANT_HELPER_PROCESS=1"}
-
- out, err := cmd.Output()
- if err != nil {
- t.Errorf("echo: %v", err)
- }
- if g, e := string(out), "foo\n"; g != e {
- t.Errorf("echo: want %q, got %q", e, g)
- }
-}
-
-func TestCatStdin(t *testing.T) {
- // Cat, testing stdin and stdout.
- input := "Input string\nLine 2"
- p := helperCommand(t, "cat")
- p.Stdin = strings.NewReader(input)
- bs, err := p.Output()
- if err != nil {
- t.Errorf("cat: %v", err)
- }
- s := string(bs)
- if s != input {
- t.Errorf("cat: want %q, got %q", input, s)
- }
-}
-
-func TestCatGoodAndBadFile(t *testing.T) {
- // Testing combined output and error values.
- bs, err := helperCommand(t, "cat", "/bogus/file.foo", "exec_test.go").CombinedOutput()
- if _, ok := err.(*exec.ExitError); !ok {
- t.Errorf("expected *exec.ExitError from cat combined; got %T: %v", err, err)
- }
- s := string(bs)
- sp := strings.SplitN(s, "\n", 2)
- if len(sp) != 2 {
- t.Fatalf("expected two lines from cat; got %q", s)
- }
- errLine, body := sp[0], sp[1]
- if !strings.HasPrefix(errLine, "Error: open /bogus/file.foo") {
- t.Errorf("expected stderr to complain about file; got %q", errLine)
- }
- if !strings.Contains(body, "func TestHelperProcess(t *testing.T)") {
- t.Errorf("expected test code; got %q (len %d)", body, len(body))
- }
-}
-
-func TestNoExistBinary(t *testing.T) {
- // Can't run a non-existent binary
- err := exec.Command("/no-exist-binary").Run()
- if err == nil {
- t.Error("expected error from /no-exist-binary")
- }
-}
-
-func TestExitStatus(t *testing.T) {
- // Test that exit values are returned correctly
- cmd := helperCommand(t, "exit", "42")
- err := cmd.Run()
- want := "exit status 42"
- switch runtime.GOOS {
- case "plan9":
- want = fmt.Sprintf("exit status: '%s %d: 42'", filepath.Base(cmd.Path), cmd.ProcessState.Pid())
- }
- if werr, ok := err.(*exec.ExitError); ok {
- if s := werr.Error(); s != want {
- t.Errorf("from exit 42 got exit %q, want %q", s, want)
- }
- } else {
- t.Fatalf("expected *exec.ExitError from exit 42; got %T: %v", err, err)
- }
-}
-
-func TestPipes(t *testing.T) {
- check := func(what string, err error) {
- if err != nil {
- t.Fatalf("%s: %v", what, err)
- }
- }
- // Cat, testing stdin and stdout.
- c := helperCommand(t, "pipetest")
- stdin, err := c.StdinPipe()
- check("StdinPipe", err)
- stdout, err := c.StdoutPipe()
- check("StdoutPipe", err)
- stderr, err := c.StderrPipe()
- check("StderrPipe", err)
-
- outbr := bufio.NewReader(stdout)
- errbr := bufio.NewReader(stderr)
- line := func(what string, br *bufio.Reader) string {
- line, _, err := br.ReadLine()
- if err != nil {
- t.Fatalf("%s: %v", what, err)
- }
- return string(line)
- }
-
- err = c.Start()
- check("Start", err)
-
- _, err = stdin.Write([]byte("O:I am output\n"))
- check("first stdin Write", err)
- if g, e := line("first output line", outbr), "O:I am output"; g != e {
- t.Errorf("got %q, want %q", g, e)
- }
-
- _, err = stdin.Write([]byte("E:I am error\n"))
- check("second stdin Write", err)
- if g, e := line("first error line", errbr), "E:I am error"; g != e {
- t.Errorf("got %q, want %q", g, e)
- }
-
- _, err = stdin.Write([]byte("O:I am output2\n"))
- check("third stdin Write 3", err)
- if g, e := line("second output line", outbr), "O:I am output2"; g != e {
- t.Errorf("got %q, want %q", g, e)
- }
-
- stdin.Close()
- err = c.Wait()
- check("Wait", err)
-}
-
-const stdinCloseTestString = "Some test string."
-
-// Issue 6270.
-func TestStdinClose(t *testing.T) {
- check := func(what string, err error) {
- if err != nil {
- t.Fatalf("%s: %v", what, err)
- }
- }
- cmd := helperCommand(t, "stdinClose")
- stdin, err := cmd.StdinPipe()
- check("StdinPipe", err)
- // Check that we can access methods of the underlying os.File.`
- if _, ok := stdin.(interface {
- Fd() uintptr
- }); !ok {
- t.Error("can't access methods of underlying *os.File")
- }
- check("Start", cmd.Start())
- go func() {
- _, err := io.Copy(stdin, strings.NewReader(stdinCloseTestString))
- check("Copy", err)
- // Before the fix, this next line would race with cmd.Wait.
- check("Close", stdin.Close())
- }()
- check("Wait", cmd.Wait())
-}
-
-// Issue 5071
-func TestPipeLookPathLeak(t *testing.T) {
- fd0, lsof0 := numOpenFDS(t)
- for i := 0; i < 4; i++ {
- cmd := exec.Command("something-that-does-not-exist-binary")
- cmd.StdoutPipe()
- cmd.StderrPipe()
- cmd.StdinPipe()
- if err := cmd.Run(); err == nil {
- t.Fatal("unexpected success")
- }
- }
- for triesLeft := 3; triesLeft >= 0; triesLeft-- {
- open, lsof := numOpenFDS(t)
- fdGrowth := open - fd0
- if fdGrowth > 2 {
- if triesLeft > 0 {
- // Work around what appears to be a race with Linux's
- // proc filesystem (as used by lsof). It seems to only
- // be eventually consistent. Give it awhile to settle.
- // See golang.org/issue/7808
- time.Sleep(100 * time.Millisecond)
- continue
- }
- t.Errorf("leaked %d fds; want ~0; have:\n%s\noriginally:\n%s", fdGrowth, lsof, lsof0)
- }
- break
- }
-}
-
-func numOpenFDS(t *testing.T) (n int, lsof []byte) {
- lsof, err := exec.Command("lsof", "-n", "-p", strconv.Itoa(os.Getpid())).Output()
- if err != nil {
- t.Skip("skipping test; error finding or running lsof")
- }
- return bytes.Count(lsof, []byte("\n")), lsof
-}
-
-var testedAlreadyLeaked = false
-
-// basefds returns the number of expected file descriptors
-// to be present in a process at start.
-func basefds() uintptr {
- n := os.Stderr.Fd() + 1
-
- // Go runtime for 32-bit Plan 9 requires that /dev/bintime
- // be kept open.
- // See ../../runtime/time_plan9_386.c:/^runtime·nanotime
- if runtime.GOOS == "plan9" && runtime.GOARCH == "386" {
- n++
- }
- return n
-}
-
-func closeUnexpectedFds(t *testing.T, m string) {
- for fd := basefds(); fd <= 101; fd++ {
- err := os.NewFile(fd, "").Close()
- if err == nil {
- t.Logf("%s: Something already leaked - closed fd %d", m, fd)
- }
- }
-}
-
-func TestExtraFilesFDShuffle(t *testing.T) {
- t.Skip("flaky test; see http://golang.org/issue/5780")
- switch runtime.GOOS {
- case "darwin":
- // TODO(cnicolaou): http://golang.org/issue/2603
- // leads to leaked file descriptors in this test when it's
- // run from a builder.
- closeUnexpectedFds(t, "TestExtraFilesFDShuffle")
- case "netbsd":
- // http://golang.org/issue/3955
- closeUnexpectedFds(t, "TestExtraFilesFDShuffle")
- case "windows":
- t.Skip("no operating system support; skipping")
- }
-
- // syscall.StartProcess maps all the FDs passed to it in
- // ProcAttr.Files (the concatenation of stdin,stdout,stderr and
- // ExtraFiles) into consecutive FDs in the child, that is:
- // Files{11, 12, 6, 7, 9, 3} should result in the file
- // represented by FD 11 in the parent being made available as 0
- // in the child, 12 as 1, etc.
- //
- // We want to test that FDs in the child do not get overwritten
- // by one another as this shuffle occurs. The original implementation
- // was buggy in that in some data dependent cases it would ovewrite
- // stderr in the child with one of the ExtraFile members.
- // Testing for this case is difficult because it relies on using
- // the same FD values as that case. In particular, an FD of 3
- // must be at an index of 4 or higher in ProcAttr.Files and
- // the FD of the write end of the Stderr pipe (as obtained by
- // StderrPipe()) must be the same as the size of ProcAttr.Files;
- // therefore we test that the read end of this pipe (which is what
- // is returned to the parent by StderrPipe() being one less than
- // the size of ProcAttr.Files, i.e. 3+len(cmd.ExtraFiles).
- //
- // Moving this test case around within the overall tests may
- // affect the FDs obtained and hence the checks to catch these cases.
- npipes := 2
- c := helperCommand(t, "extraFilesAndPipes", strconv.Itoa(npipes+1))
- rd, wr, _ := os.Pipe()
- defer rd.Close()
- if rd.Fd() != 3 {
- t.Errorf("bad test value for test pipe: fd %d", rd.Fd())
- }
- stderr, _ := c.StderrPipe()
- wr.WriteString("_LAST")
- wr.Close()
-
- pipes := make([]struct {
- r, w *os.File
- }, npipes)
- data := []string{"a", "b"}
-
- for i := 0; i < npipes; i++ {
- r, w, err := os.Pipe()
- if err != nil {
- t.Fatalf("unexpected error creating pipe: %s", err)
- }
- pipes[i].r = r
- pipes[i].w = w
- w.WriteString(data[i])
- c.ExtraFiles = append(c.ExtraFiles, pipes[i].r)
- defer func() {
- r.Close()
- w.Close()
- }()
- }
- // Put fd 3 at the end.
- c.ExtraFiles = append(c.ExtraFiles, rd)
-
- stderrFd := int(stderr.(*os.File).Fd())
- if stderrFd != ((len(c.ExtraFiles) + 3) - 1) {
- t.Errorf("bad test value for stderr pipe")
- }
-
- expected := "child: " + strings.Join(data, "") + "_LAST"
-
- err := c.Start()
- if err != nil {
- t.Fatalf("Run: %v", err)
- }
- ch := make(chan string, 1)
- go func(ch chan string) {
- buf := make([]byte, 512)
- n, err := stderr.Read(buf)
- if err != nil {
- t.Fatalf("Read: %s", err)
- ch <- err.Error()
- } else {
- ch <- string(buf[:n])
- }
- close(ch)
- }(ch)
- select {
- case m := <-ch:
- if m != expected {
- t.Errorf("Read: '%s' not '%s'", m, expected)
- }
- case <-time.After(5 * time.Second):
- t.Errorf("Read timedout")
- }
- c.Wait()
-}
-
-func TestExtraFiles(t *testing.T) {
- if runtime.GOOS == "windows" {
- t.Skip("no operating system support; skipping")
- }
-
- // Ensure that file descriptors have not already been leaked into
- // our environment.
- if !testedAlreadyLeaked {
- testedAlreadyLeaked = true
- closeUnexpectedFds(t, "TestExtraFiles")
- }
-
- // Force network usage, to verify the epoll (or whatever) fd
- // doesn't leak to the child,
- ln, err := net.Listen("tcp", "127.0.0.1:0")
- if err != nil {
- t.Fatal(err)
- }
- defer ln.Close()
-
- // Make sure duplicated fds don't leak to the child.
- f, err := ln.(*net.TCPListener).File()
- if err != nil {
- t.Fatal(err)
- }
- defer f.Close()
- ln2, err := net.FileListener(f)
- if err != nil {
- t.Fatal(err)
- }
- defer ln2.Close()
-
- // Force TLS root certs to be loaded (which might involve
- // cgo), to make sure none of that potential C code leaks fds.
- ts := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
- // quiet expected TLS handshake error "remote error: bad certificate"
- ts.Config.ErrorLog = log.New(ioutil.Discard, "", 0)
- ts.StartTLS()
- defer ts.Close()
- _, err = http.Get(ts.URL)
- if err == nil {
- t.Errorf("success trying to fetch %s; want an error", ts.URL)
- }
-
- tf, err := ioutil.TempFile("", "")
- if err != nil {
- t.Fatalf("TempFile: %v", err)
- }
- defer os.Remove(tf.Name())
- defer tf.Close()
-
- const text = "Hello, fd 3!"
- _, err = tf.Write([]byte(text))
- if err != nil {
- t.Fatalf("Write: %v", err)
- }
- _, err = tf.Seek(0, os.SEEK_SET)
- if err != nil {
- t.Fatalf("Seek: %v", err)
- }
-
- c := helperCommand(t, "read3")
- var stdout, stderr bytes.Buffer
- c.Stdout = &stdout
- c.Stderr = &stderr
- c.ExtraFiles = []*os.File{tf}
- err = c.Run()
- if err != nil {
- t.Fatalf("Run: %v; stdout %q, stderr %q", err, stdout.Bytes(), stderr.Bytes())
- }
- if stdout.String() != text {
- t.Errorf("got stdout %q, stderr %q; want %q on stdout", stdout.String(), stderr.String(), text)
- }
-}
-
-func TestExtraFilesRace(t *testing.T) {
- if runtime.GOOS == "windows" {
- t.Skip("no operating system support; skipping")
- }
- listen := func() net.Listener {
- ln, err := net.Listen("tcp", "127.0.0.1:0")
- if err != nil {
- t.Fatal(err)
- }
- return ln
- }
- listenerFile := func(ln net.Listener) *os.File {
- f, err := ln.(*net.TCPListener).File()
- if err != nil {
- t.Fatal(err)
- }
- return f
- }
- runCommand := func(c *exec.Cmd, out chan<- string) {
- bout, err := c.CombinedOutput()
- if err != nil {
- out <- "ERROR:" + err.Error()
- } else {
- out <- string(bout)
- }
- }
-
- for i := 0; i < 10; i++ {
- la := listen()
- ca := helperCommand(t, "describefiles")
- ca.ExtraFiles = []*os.File{listenerFile(la)}
- lb := listen()
- cb := helperCommand(t, "describefiles")
- cb.ExtraFiles = []*os.File{listenerFile(lb)}
- ares := make(chan string)
- bres := make(chan string)
- go runCommand(ca, ares)
- go runCommand(cb, bres)
- if got, want := <-ares, fmt.Sprintf("fd3: listener %s\n", la.Addr()); got != want {
- t.Errorf("iteration %d, process A got:\n%s\nwant:\n%s\n", i, got, want)
- }
- if got, want := <-bres, fmt.Sprintf("fd3: listener %s\n", lb.Addr()); got != want {
- t.Errorf("iteration %d, process B got:\n%s\nwant:\n%s\n", i, got, want)
- }
- la.Close()
- lb.Close()
- for _, f := range ca.ExtraFiles {
- f.Close()
- }
- for _, f := range cb.ExtraFiles {
- f.Close()
- }
-
- }
-}
-
-// TestHelperProcess isn't a real test. It's used as a helper process
-// for TestParameterRun.
-func TestHelperProcess(*testing.T) {
- if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" {
- return
- }
- defer os.Exit(0)
-
- // Determine which command to use to display open files.
- ofcmd := "lsof"
- switch runtime.GOOS {
- case "dragonfly", "freebsd", "netbsd", "openbsd":
- ofcmd = "fstat"
- case "plan9":
- ofcmd = "/bin/cat"
- }
-
- args := os.Args
- for len(args) > 0 {
- if args[0] == "--" {
- args = args[1:]
- break
- }
- args = args[1:]
- }
- if len(args) == 0 {
- fmt.Fprintf(os.Stderr, "No command\n")
- os.Exit(2)
- }
-
- cmd, args := args[0], args[1:]
- switch cmd {
- case "echo":
- iargs := []interface{}{}
- for _, s := range args {
- iargs = append(iargs, s)
- }
- fmt.Println(iargs...)
- case "cat":
- if len(args) == 0 {
- io.Copy(os.Stdout, os.Stdin)
- return
- }
- exit := 0
- for _, fn := range args {
- f, err := os.Open(fn)
- if err != nil {
- fmt.Fprintf(os.Stderr, "Error: %v\n", err)
- exit = 2
- } else {
- defer f.Close()
- io.Copy(os.Stdout, f)
- }
- }
- os.Exit(exit)
- case "pipetest":
- bufr := bufio.NewReader(os.Stdin)
- for {
- line, _, err := bufr.ReadLine()
- if err == io.EOF {
- break
- } else if err != nil {
- os.Exit(1)
- }
- if bytes.HasPrefix(line, []byte("O:")) {
- os.Stdout.Write(line)
- os.Stdout.Write([]byte{'\n'})
- } else if bytes.HasPrefix(line, []byte("E:")) {
- os.Stderr.Write(line)
- os.Stderr.Write([]byte{'\n'})
- } else {
- os.Exit(1)
- }
- }
- case "stdinClose":
- b, err := ioutil.ReadAll(os.Stdin)
- if err != nil {
- fmt.Fprintf(os.Stderr, "Error: %v\n", err)
- os.Exit(1)
- }
- if s := string(b); s != stdinCloseTestString {
- fmt.Fprintf(os.Stderr, "Error: Read %q, want %q", s, stdinCloseTestString)
- os.Exit(1)
- }
- os.Exit(0)
- case "read3": // read fd 3
- fd3 := os.NewFile(3, "fd3")
- bs, err := ioutil.ReadAll(fd3)
- if err != nil {
- fmt.Printf("ReadAll from fd 3: %v", err)
- os.Exit(1)
- }
- switch runtime.GOOS {
- case "dragonfly":
- // TODO(jsing): Determine why DragonFly is leaking
- // file descriptors...
- case "darwin":
- // TODO(bradfitz): broken? Sometimes.
- // http://golang.org/issue/2603
- // Skip this additional part of the test for now.
- case "netbsd":
- // TODO(jsing): This currently fails on NetBSD due to
- // the cloned file descriptors that result from opening
- // /dev/urandom.
- // http://golang.org/issue/3955
- case "plan9":
- // TODO(0intro): Determine why Plan 9 is leaking
- // file descriptors.
- // http://golang.org/issue/7118
- case "solaris":
- // TODO(aram): This fails on Solaris because libc opens
- // its own files, as it sees fit. Darwin does the same,
- // see: http://golang.org/issue/2603
- default:
- // Now verify that there are no other open fds.
- var files []*os.File
- for wantfd := basefds() + 1; wantfd <= 100; wantfd++ {
- f, err := os.Open(os.Args[0])
- if err != nil {
- fmt.Printf("error opening file with expected fd %d: %v", wantfd, err)
- os.Exit(1)
- }
- if got := f.Fd(); got != wantfd {
- fmt.Printf("leaked parent file. fd = %d; want %d\n", got, wantfd)
- var args []string
- switch runtime.GOOS {
- case "plan9":
- args = []string{fmt.Sprintf("/proc/%d/fd", os.Getpid())}
- default:
- args = []string{"-p", fmt.Sprint(os.Getpid())}
- }
- out, _ := exec.Command(ofcmd, args...).CombinedOutput()
- fmt.Print(string(out))
- os.Exit(1)
- }
- files = append(files, f)
- }
- for _, f := range files {
- f.Close()
- }
- }
- // Referring to fd3 here ensures that it is not
- // garbage collected, and therefore closed, while
- // executing the wantfd loop above. It doesn't matter
- // what we do with fd3 as long as we refer to it;
- // closing it is the easy choice.
- fd3.Close()
- os.Stdout.Write(bs)
- case "exit":
- n, _ := strconv.Atoi(args[0])
- os.Exit(n)
- case "describefiles":
- f := os.NewFile(3, fmt.Sprintf("fd3"))
- ln, err := net.FileListener(f)
- if err == nil {
- fmt.Printf("fd3: listener %s\n", ln.Addr())
- ln.Close()
- }
- os.Exit(0)
- case "extraFilesAndPipes":
- n, _ := strconv.Atoi(args[0])
- pipes := make([]*os.File, n)
- for i := 0; i < n; i++ {
- pipes[i] = os.NewFile(uintptr(3+i), strconv.Itoa(i))
- }
- response := ""
- for i, r := range pipes {
- ch := make(chan string, 1)
- go func(c chan string) {
- buf := make([]byte, 10)
- n, err := r.Read(buf)
- if err != nil {
- fmt.Fprintf(os.Stderr, "Child: read error: %v on pipe %d\n", err, i)
- os.Exit(1)
- }
- c <- string(buf[:n])
- close(c)
- }(ch)
- select {
- case m := <-ch:
- response = response + m
- case <-time.After(5 * time.Second):
- fmt.Fprintf(os.Stderr, "Child: Timeout reading from pipe: %d\n", i)
- os.Exit(1)
- }
- }
- fmt.Fprintf(os.Stderr, "child: %s", response)
- os.Exit(0)
- case "exec":
- cmd := exec.Command(args[1])
- cmd.Dir = args[0]
- output, err := cmd.CombinedOutput()
- if err != nil {
- fmt.Fprintf(os.Stderr, "Child: %s %s", err, string(output))
- os.Exit(1)
- }
- fmt.Printf("%s", string(output))
- os.Exit(0)
- case "lookpath":
- p, err := exec.LookPath(args[0])
- if err != nil {
- fmt.Fprintf(os.Stderr, "LookPath failed: %v\n", err)
- os.Exit(1)
- }
- fmt.Print(p)
- os.Exit(0)
- default:
- fmt.Fprintf(os.Stderr, "Unknown command %q\n", cmd)
- os.Exit(2)
- }
-}
diff --git a/src/pkg/os/exec/lp_plan9.go b/src/pkg/os/exec/lp_plan9.go
deleted file mode 100644
index 5aa8a54ed..000000000
--- a/src/pkg/os/exec/lp_plan9.go
+++ /dev/null
@@ -1,53 +0,0 @@
-// Copyright 2011 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
-
-import (
- "errors"
- "os"
- "strings"
-)
-
-// ErrNotFound is the error resulting if a path search failed to find an executable file.
-var ErrNotFound = errors.New("executable file not found in $path")
-
-func findExecutable(file string) error {
- d, err := os.Stat(file)
- if err != nil {
- return err
- }
- if m := d.Mode(); !m.IsDir() && m&0111 != 0 {
- return nil
- }
- return os.ErrPermission
-}
-
-// LookPath searches for an executable binary named file
-// in the directories named by the path environment variable.
-// If file begins with "/", "#", "./", or "../", it is tried
-// directly and the path is not consulted.
-// 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 _, p := range skip {
- if strings.HasPrefix(file, p) {
- err := findExecutable(file)
- if err == nil {
- return file, nil
- }
- return "", &Error{file, err}
- }
- }
-
- path := os.Getenv("path")
- for _, dir := range strings.Split(path, "\000") {
- if err := findExecutable(dir + "/" + file); err == nil {
- return dir + "/" + file, nil
- }
- }
- return "", &Error{file, ErrNotFound}
-}
diff --git a/src/pkg/os/exec/lp_test.go b/src/pkg/os/exec/lp_test.go
deleted file mode 100644
index 77d8e848c..000000000
--- a/src/pkg/os/exec/lp_test.go
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright 2011 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
-
-import (
- "testing"
-)
-
-var nonExistentPaths = []string{
- "some-non-existent-path",
- "non-existent-path/slashed",
-}
-
-func TestLookPathNotFound(t *testing.T) {
- for _, name := range nonExistentPaths {
- path, err := LookPath(name)
- if err == nil {
- t.Fatalf("LookPath found %q in $PATH", name)
- }
- if path != "" {
- t.Fatalf("LookPath path == %q when err != nil", path)
- }
- perr, ok := err.(*Error)
- if !ok {
- t.Fatal("LookPath error is not an exec.Error")
- }
- if perr.Name != name {
- t.Fatalf("want Error name %q, got %q", name, perr.Name)
- }
- }
-}
diff --git a/src/pkg/os/exec/lp_unix.go b/src/pkg/os/exec/lp_unix.go
deleted file mode 100644
index 3f895d5b3..000000000
--- a/src/pkg/os/exec/lp_unix.go
+++ /dev/null
@@ -1,60 +0,0 @@
-// Copyright 2010 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.
-
-// +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris
-
-package exec
-
-import (
- "errors"
- "os"
- "strings"
-)
-
-// ErrNotFound is the error resulting if a path search failed to find an executable file.
-var ErrNotFound = errors.New("executable file not found in $PATH")
-
-func findExecutable(file string) error {
- d, err := os.Stat(file)
- if err != nil {
- return err
- }
- if m := d.Mode(); !m.IsDir() && m&0111 != 0 {
- return nil
- }
- return os.ErrPermission
-}
-
-// LookPath searches for an executable binary 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.
-// 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 ../)
- // but that would not match all the Unix shells.
-
- if strings.Contains(file, "/") {
- err := findExecutable(file)
- if err == nil {
- return file, nil
- }
- return "", &Error{file, err}
- }
- pathenv := os.Getenv("PATH")
- if pathenv == "" {
- return "", &Error{file, ErrNotFound}
- }
- for _, dir := range strings.Split(pathenv, ":") {
- if dir == "" {
- // Unix shell semantics: path element "" means "."
- dir = "."
- }
- path := dir + "/" + file
- if err := findExecutable(path); err == nil {
- return path, nil
- }
- }
- return "", &Error{file, ErrNotFound}
-}
diff --git a/src/pkg/os/exec/lp_unix_test.go b/src/pkg/os/exec/lp_unix_test.go
deleted file mode 100644
index 051db664a..000000000
--- a/src/pkg/os/exec/lp_unix_test.go
+++ /dev/null
@@ -1,55 +0,0 @@
-// Copyright 2013 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.
-
-// +build darwin dragonfly freebsd linux netbsd openbsd solaris
-
-package exec
-
-import (
- "io/ioutil"
- "os"
- "testing"
-)
-
-func TestLookPathUnixEmptyPath(t *testing.T) {
- tmp, err := ioutil.TempDir("", "TestLookPathUnixEmptyPath")
- if err != nil {
- t.Fatal("TempDir failed: ", err)
- }
- defer os.RemoveAll(tmp)
- wd, err := os.Getwd()
- if err != nil {
- t.Fatal("Getwd failed: ", err)
- }
- err = os.Chdir(tmp)
- if err != nil {
- t.Fatal("Chdir failed: ", err)
- }
- defer os.Chdir(wd)
-
- f, err := os.OpenFile("exec_me", os.O_CREATE|os.O_EXCL, 0700)
- if err != nil {
- t.Fatal("OpenFile failed: ", err)
- }
- err = f.Close()
- if err != nil {
- t.Fatal("Close failed: ", err)
- }
-
- pathenv := os.Getenv("PATH")
- defer os.Setenv("PATH", pathenv)
-
- err = os.Setenv("PATH", "")
- if err != nil {
- t.Fatal("Setenv failed: ", err)
- }
-
- path, err := LookPath("exec_me")
- if err == nil {
- t.Fatal("LookPath found exec_me in empty $PATH")
- }
- if path != "" {
- t.Fatalf("LookPath path == %q when err != nil", path)
- }
-}
diff --git a/src/pkg/os/exec/lp_windows.go b/src/pkg/os/exec/lp_windows.go
deleted file mode 100644
index c3efd67e9..000000000
--- a/src/pkg/os/exec/lp_windows.go
+++ /dev/null
@@ -1,123 +0,0 @@
-// Copyright 2010 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
-
-import (
- "errors"
- "os"
- "strings"
-)
-
-// ErrNotFound is the error resulting if a path search failed to find an executable file.
-var ErrNotFound = errors.New("executable file not found in %PATH%")
-
-func chkStat(file string) error {
- d, err := os.Stat(file)
- if err != nil {
- return err
- }
- if d.IsDir() {
- return os.ErrPermission
- }
- return nil
-}
-
-func hasExt(file string) bool {
- i := strings.LastIndex(file, ".")
- if i < 0 {
- return false
- }
- return strings.LastIndexAny(file, `:\/`) < i
-}
-
-func findExecutable(file string, exts []string) (string, error) {
- if len(exts) == 0 {
- return file, chkStat(file)
- }
- if hasExt(file) {
- if chkStat(file) == nil {
- return file, nil
- }
- }
- for _, e := range exts {
- if f := file + e; chkStat(f) == nil {
- return f, nil
- }
- }
- return ``, os.ErrNotExist
-}
-
-// LookPath searches for an executable binary 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.
-// The result may be an absolute path or a path relative to the current directory.
-func LookPath(file string) (f string, err error) {
- x := os.Getenv(`PATHEXT`)
- if x == `` {
- x = `.COM;.EXE;.BAT;.CMD`
- }
- exts := []string{}
- for _, e := range strings.Split(strings.ToLower(x), `;`) {
- if e == "" {
- continue
- }
- if e[0] != '.' {
- e = "." + e
- }
- exts = append(exts, e)
- }
- if strings.IndexAny(file, `:\/`) != -1 {
- if f, err = findExecutable(file, exts); err == nil {
- return
- }
- return ``, &Error{file, err}
- }
- if f, err = findExecutable(`.\`+file, exts); err == nil {
- return
- }
- if pathenv := os.Getenv(`PATH`); pathenv != `` {
- for _, dir := range splitList(pathenv) {
- if f, err = findExecutable(dir+`\`+file, exts); err == nil {
- return
- }
- }
- }
- return ``, &Error{file, ErrNotFound}
-}
-
-func splitList(path string) []string {
- // The same implementation is used in SplitList in path/filepath;
- // consider changing path/filepath when changing this.
-
- if path == "" {
- return []string{}
- }
-
- // Split path, respecting but preserving quotes.
- list := []string{}
- start := 0
- quo := false
- for i := 0; i < len(path); i++ {
- switch c := path[i]; {
- case c == '"':
- quo = !quo
- case c == os.PathListSeparator && !quo:
- list = append(list, path[start:i])
- start = i + 1
- }
- }
- list = append(list, path[start:])
-
- // Remove quotes.
- for i, s := range list {
- if strings.Contains(s, `"`) {
- list[i] = strings.Replace(s, `"`, ``, -1)
- }
- }
-
- return list
-}
diff --git a/src/pkg/os/exec/lp_windows_test.go b/src/pkg/os/exec/lp_windows_test.go
deleted file mode 100644
index 72df03ed2..000000000
--- a/src/pkg/os/exec/lp_windows_test.go
+++ /dev/null
@@ -1,573 +0,0 @@
-// Copyright 2013 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
-
-import (
- "fmt"
- "io"
- "io/ioutil"
- "os"
- "path/filepath"
- "strconv"
- "strings"
- "testing"
-)
-
-func installExe(t *testing.T, dest, src string) {
- fsrc, err := os.Open(src)
- if err != nil {
- t.Fatal("os.Open failed: ", err)
- }
- defer fsrc.Close()
- fdest, err := os.Create(dest)
- if err != nil {
- t.Fatal("os.Create failed: ", err)
- }
- defer fdest.Close()
- _, err = io.Copy(fdest, fsrc)
- if err != nil {
- t.Fatal("io.Copy failed: ", err)
- }
-}
-
-func installBat(t *testing.T, dest string) {
- f, err := os.Create(dest)
- if err != nil {
- t.Fatalf("failed to create batch file: %v", err)
- }
- defer f.Close()
- fmt.Fprintf(f, "@echo %s\n", dest)
-}
-
-func installProg(t *testing.T, dest, srcExe string) {
- err := os.MkdirAll(filepath.Dir(dest), 0700)
- if err != nil {
- t.Fatal("os.MkdirAll failed: ", err)
- }
- if strings.ToLower(filepath.Ext(dest)) == ".bat" {
- installBat(t, dest)
- return
- }
- installExe(t, dest, srcExe)
-}
-
-type lookPathTest struct {
- rootDir string
- PATH string
- PATHEXT string
- files []string
- searchFor string
- fails bool // test is expected to fail
-}
-
-func (test lookPathTest) runProg(t *testing.T, env []string, args ...string) (string, error) {
- cmd := Command(args[0], args[1:]...)
- cmd.Env = env
- cmd.Dir = test.rootDir
- args[0] = filepath.Base(args[0])
- cmdText := fmt.Sprintf("%q command", strings.Join(args, " "))
- out, err := cmd.CombinedOutput()
- if (err != nil) != test.fails {
- if test.fails {
- t.Fatalf("test=%+v: %s succeeded, but expected to fail", test, cmdText)
- }
- t.Fatalf("test=%+v: %s failed, but expected to succeed: %v - %v", test, cmdText, err, string(out))
- }
- if err != nil {
- return "", fmt.Errorf("test=%+v: %s failed: %v - %v", test, cmdText, err, string(out))
- }
- // normalise program output
- p := string(out)
- // trim terminating \r and \n that batch file outputs
- for len(p) > 0 && (p[len(p)-1] == '\n' || p[len(p)-1] == '\r') {
- p = p[:len(p)-1]
- }
- if !filepath.IsAbs(p) {
- return p, nil
- }
- if p[:len(test.rootDir)] != test.rootDir {
- t.Fatalf("test=%+v: %s output is wrong: %q must have %q prefix", test, cmdText, p, test.rootDir)
- }
- return p[len(test.rootDir)+1:], nil
-}
-
-func updateEnv(env []string, name, value string) []string {
- for i, e := range env {
- if strings.HasPrefix(strings.ToUpper(e), name+"=") {
- env[i] = name + "=" + value
- return env
- }
- }
- return append(env, name+"="+value)
-}
-
-func createEnv(dir, PATH, PATHEXT string) []string {
- env := os.Environ()
- env = updateEnv(env, "PATHEXT", PATHEXT)
- // Add dir in front of every directory in the PATH.
- dirs := splitList(PATH)
- for i := range dirs {
- dirs[i] = filepath.Join(dir, dirs[i])
- }
- path := strings.Join(dirs, ";")
- env = updateEnv(env, "PATH", path)
- return env
-}
-
-// createFiles copies srcPath file into multiply files.
-// It uses dir as preifx for all destination files.
-func createFiles(t *testing.T, dir string, files []string, srcPath string) {
- for _, f := range files {
- installProg(t, filepath.Join(dir, f), srcPath)
- }
-}
-
-func (test lookPathTest) run(t *testing.T, tmpdir, printpathExe string) {
- test.rootDir = tmpdir
- createFiles(t, test.rootDir, test.files, printpathExe)
- env := createEnv(test.rootDir, test.PATH, test.PATHEXT)
- // Run "cmd.exe /c test.searchFor" with new environment and
- // work directory set. All candidates are copies of printpath.exe.
- // These will output their program paths when run.
- should, errCmd := test.runProg(t, env, "cmd", "/c", test.searchFor)
- // Run the lookpath program with new environment and work directory set.
- env = append(env, "GO_WANT_HELPER_PROCESS=1")
- have, errLP := test.runProg(t, env, os.Args[0], "-test.run=TestHelperProcess", "--", "lookpath", test.searchFor)
- // Compare results.
- if errCmd == nil && errLP == nil {
- // both succeeded
- if should != have {
- t.Fatalf("test=%+v failed: expected to find %q, but found %q", test, should, have)
- }
- return
- }
- if errCmd != nil && errLP != nil {
- // both failed -> continue
- return
- }
- if errCmd != nil {
- t.Fatal(errCmd)
- }
- if errLP != nil {
- t.Fatal(errLP)
- }
-}
-
-var lookPathTests = []lookPathTest{
- {
- PATHEXT: `.COM;.EXE;.BAT`,
- PATH: `p1;p2`,
- files: []string{`p1\a.exe`, `p2\a.exe`, `p2\a`},
- searchFor: `a`,
- },
- {
- PATHEXT: `.COM;.EXE;.BAT`,
- PATH: `p1.dir;p2.dir`,
- files: []string{`p1.dir\a`, `p2.dir\a.exe`},
- searchFor: `a`,
- },
- {
- PATHEXT: `.COM;.EXE;.BAT`,
- PATH: `p1;p2`,
- files: []string{`p1\a.exe`, `p2\a.exe`},
- searchFor: `a.exe`,
- },
- {
- PATHEXT: `.COM;.EXE;.BAT`,
- PATH: `p1;p2`,
- files: []string{`p1\a.exe`, `p2\b.exe`},
- searchFor: `b`,
- },
- {
- PATHEXT: `.COM;.EXE;.BAT`,
- PATH: `p1;p2`,
- files: []string{`p1\b`, `p2\a`},
- searchFor: `a`,
- fails: true, // TODO(brainman): do not know why this fails
- },
- // If the command name specifies a path, the shell searches
- // the specified path for an executable file matching
- // the command name. If a match is found, the external
- // command (the executable file) executes.
- {
- PATHEXT: `.COM;.EXE;.BAT`,
- PATH: `p1;p2`,
- files: []string{`p1\a.exe`, `p2\a.exe`},
- searchFor: `p2\a`,
- },
- // If the command name specifies a path, the shell searches
- // the specified path for an executable file matching the command
- // name. ... If no match is found, the shell reports an error
- // and command processing completes.
- {
- PATHEXT: `.COM;.EXE;.BAT`,
- PATH: `p1;p2`,
- files: []string{`p1\b.exe`, `p2\a.exe`},
- searchFor: `p2\b`,
- fails: true,
- },
- // If the command name does not specify a path, the shell
- // searches the current directory for an executable file
- // matching the command name. If a match is found, the external
- // command (the executable file) executes.
- {
- PATHEXT: `.COM;.EXE;.BAT`,
- PATH: `p1;p2`,
- files: []string{`a`, `p1\a.exe`, `p2\a.exe`},
- searchFor: `a`,
- },
- // The shell now searches each directory specified by the
- // PATH environment variable, in the order listed, for an
- // executable file matching the command name. If a match
- // is found, the external command (the executable file) executes.
- {
- PATHEXT: `.COM;.EXE;.BAT`,
- PATH: `p1;p2`,
- files: []string{`p1\a.exe`, `p2\a.exe`},
- searchFor: `a`,
- },
- // The shell now searches each directory specified by the
- // PATH environment variable, in the order listed, for an
- // executable file matching the command name. If no match
- // is found, the shell reports an error and command processing
- // completes.
- {
- PATHEXT: `.COM;.EXE;.BAT`,
- PATH: `p1;p2`,
- files: []string{`p1\a.exe`, `p2\a.exe`},
- searchFor: `b`,
- fails: true,
- },
- // If the command name includes a file extension, the shell
- // searches each directory for the exact file name specified
- // by the command name.
- {
- PATHEXT: `.COM;.EXE;.BAT`,
- PATH: `p1;p2`,
- files: []string{`p1\a.exe`, `p2\a.exe`},
- searchFor: `a.exe`,
- },
- {
- PATHEXT: `.COM;.EXE;.BAT`,
- PATH: `p1;p2`,
- files: []string{`p1\a.exe`, `p2\a.exe`},
- searchFor: `a.com`,
- fails: true, // includes extension and not exact file name match
- },
- {
- PATHEXT: `.COM;.EXE;.BAT`,
- PATH: `p1`,
- files: []string{`p1\a.exe.exe`},
- searchFor: `a.exe`,
- },
- {
- PATHEXT: `.COM;.BAT`,
- PATH: `p1;p2`,
- files: []string{`p1\a.exe`, `p2\a.exe`},
- searchFor: `a.exe`,
- },
- // If the command name does not include a file extension, the shell
- // adds the extensions listed in the PATHEXT environment variable,
- // one by one, and searches the directory for that file name. Note
- // that the shell tries all possible file extensions in a specific
- // directory before moving on to search the next directory
- // (if there is one).
- {
- PATHEXT: `.COM;.EXE`,
- PATH: `p1;p2`,
- files: []string{`p1\a.bat`, `p2\a.exe`},
- searchFor: `a`,
- },
- {
- PATHEXT: `.COM;.EXE;.BAT`,
- PATH: `p1;p2`,
- files: []string{`p1\a.bat`, `p2\a.exe`},
- searchFor: `a`,
- },
- {
- PATHEXT: `.COM;.EXE;.BAT`,
- PATH: `p1;p2`,
- files: []string{`p1\a.bat`, `p1\a.exe`, `p2\a.bat`, `p2\a.exe`},
- searchFor: `a`,
- },
- {
- PATHEXT: `.COM`,
- PATH: `p1;p2`,
- files: []string{`p1\a.bat`, `p2\a.exe`},
- searchFor: `a`,
- fails: true, // tried all extensions in PATHEXT, but none matches
- },
-}
-
-func TestLookPath(t *testing.T) {
- tmp, err := ioutil.TempDir("", "TestLookPath")
- if err != nil {
- t.Fatal("TempDir failed: ", err)
- }
- defer os.RemoveAll(tmp)
-
- printpathExe := buildPrintPathExe(t, tmp)
-
- // Run all tests.
- for i, test := range lookPathTests {
- dir := filepath.Join(tmp, "d"+strconv.Itoa(i))
- err := os.Mkdir(dir, 0700)
- if err != nil {
- t.Fatal("Mkdir failed: ", err)
- }
- test.run(t, dir, printpathExe)
- }
-}
-
-type commandTest struct {
- PATH string
- files []string
- dir string
- arg0 string
- want string
- fails bool // test is expected to fail
-}
-
-func (test commandTest) isSuccess(rootDir, output string, err error) error {
- if err != nil {
- return fmt.Errorf("test=%+v: exec: %v %v", test, err, output)
- }
- path := output
- if path[:len(rootDir)] != rootDir {
- return fmt.Errorf("test=%+v: %q must have %q prefix", test, path, rootDir)
- }
- path = path[len(rootDir)+1:]
- if path != test.want {
- return fmt.Errorf("test=%+v: want %q, got %q", test, test.want, path)
- }
- return nil
-}
-
-func (test commandTest) runOne(rootDir string, env []string, dir, arg0 string) error {
- cmd := Command(os.Args[0], "-test.run=TestHelperProcess", "--", "exec", dir, arg0)
- cmd.Dir = rootDir
- cmd.Env = env
- output, err := cmd.CombinedOutput()
- err = test.isSuccess(rootDir, string(output), err)
- if (err != nil) != test.fails {
- if test.fails {
- return fmt.Errorf("test=%+v: succeeded, but expected to fail", test)
- }
- return err
- }
- return nil
-}
-
-func (test commandTest) run(t *testing.T, rootDir, printpathExe string) {
- createFiles(t, rootDir, test.files, printpathExe)
- PATHEXT := `.COM;.EXE;.BAT`
- env := createEnv(rootDir, test.PATH, PATHEXT)
- env = append(env, "GO_WANT_HELPER_PROCESS=1")
- err := test.runOne(rootDir, env, test.dir, test.arg0)
- if err != nil {
- t.Error(err)
- }
-}
-
-var commandTests = []commandTest{
- // testing commands with no slash, like `a.exe`
- {
- // should find a.exe in current directory
- files: []string{`a.exe`},
- arg0: `a.exe`,
- want: `a.exe`,
- },
- {
- // like above, but add PATH in attempt to break the test
- PATH: `p2;p`,
- files: []string{`a.exe`, `p\a.exe`, `p2\a.exe`},
- arg0: `a.exe`,
- want: `a.exe`,
- },
- {
- // like above, but use "a" instead of "a.exe" for command
- PATH: `p2;p`,
- files: []string{`a.exe`, `p\a.exe`, `p2\a.exe`},
- arg0: `a`,
- want: `a.exe`,
- },
- // testing commands with slash, like `.\a.exe`
- {
- // should find p\a.exe
- files: []string{`p\a.exe`},
- arg0: `p\a.exe`,
- want: `p\a.exe`,
- },
- {
- // like above, but adding `.` in front of executable should still be OK
- files: []string{`p\a.exe`},
- arg0: `.\p\a.exe`,
- want: `p\a.exe`,
- },
- {
- // like above, but with PATH added in attempt to break it
- PATH: `p2`,
- files: []string{`p\a.exe`, `p2\a.exe`},
- arg0: `p\a.exe`,
- want: `p\a.exe`,
- },
- {
- // like above, but make sure .exe is tried even for commands with slash
- PATH: `p2`,
- files: []string{`p\a.exe`, `p2\a.exe`},
- arg0: `p\a`,
- want: `p\a.exe`,
- },
- // tests commands, like `a.exe`, with c.Dir set
- {
- // should not find a.exe in p, becasue LookPath(`a.exe`) will fail
- files: []string{`p\a.exe`},
- dir: `p`,
- arg0: `a.exe`,
- want: `p\a.exe`,
- fails: true,
- },
- {
- // LookPath(`a.exe`) will find `.\a.exe`, but prefixing that with
- // dir `p\a.exe` will refer to not existant file
- files: []string{`a.exe`, `p\not_important_file`},
- dir: `p`,
- arg0: `a.exe`,
- want: `a.exe`,
- fails: true,
- },
- {
- // like above, but making test succeed by installing file
- // in refered destination (so LookPath(`a.exe`) will still
- // find `.\a.exe`, but we successfully execute `p\a.exe`)
- files: []string{`a.exe`, `p\a.exe`},
- dir: `p`,
- arg0: `a.exe`,
- want: `p\a.exe`,
- },
- {
- // like above, but add PATH in attempt to break the test
- PATH: `p2;p`,
- files: []string{`a.exe`, `p\a.exe`, `p2\a.exe`},
- dir: `p`,
- arg0: `a.exe`,
- want: `p\a.exe`,
- },
- {
- // like above, but use "a" instead of "a.exe" for command
- PATH: `p2;p`,
- files: []string{`a.exe`, `p\a.exe`, `p2\a.exe`},
- dir: `p`,
- arg0: `a`,
- want: `p\a.exe`,
- },
- {
- // finds `a.exe` in the PATH regardless of dir set
- // because LookPath returns full path in that case
- PATH: `p2;p`,
- files: []string{`p\a.exe`, `p2\a.exe`},
- dir: `p`,
- arg0: `a.exe`,
- want: `p2\a.exe`,
- },
- // tests commands, like `.\a.exe`, with c.Dir set
- {
- // should use dir when command is path, like ".\a.exe"
- files: []string{`p\a.exe`},
- dir: `p`,
- arg0: `.\a.exe`,
- want: `p\a.exe`,
- },
- {
- // like above, but with PATH added in attempt to break it
- PATH: `p2`,
- files: []string{`p\a.exe`, `p2\a.exe`},
- dir: `p`,
- arg0: `.\a.exe`,
- want: `p\a.exe`,
- },
- {
- // like above, but make sure .exe is tried even for commands with slash
- PATH: `p2`,
- files: []string{`p\a.exe`, `p2\a.exe`},
- dir: `p`,
- arg0: `.\a`,
- want: `p\a.exe`,
- },
-}
-
-func TestCommand(t *testing.T) {
- tmp, err := ioutil.TempDir("", "TestCommand")
- if err != nil {
- t.Fatal("TempDir failed: ", err)
- }
- defer os.RemoveAll(tmp)
-
- printpathExe := buildPrintPathExe(t, tmp)
-
- // Run all tests.
- for i, test := range commandTests {
- dir := filepath.Join(tmp, "d"+strconv.Itoa(i))
- err := os.Mkdir(dir, 0700)
- if err != nil {
- t.Fatal("Mkdir failed: ", err)
- }
- test.run(t, dir, printpathExe)
- }
-}
-
-// buildPrintPathExe creates a Go program that prints its own path.
-// dir is a temp directory where executable will be created.
-// The function returns full path to the created program.
-func buildPrintPathExe(t *testing.T, dir string) string {
- const name = "printpath"
- srcname := name + ".go"
- err := ioutil.WriteFile(filepath.Join(dir, srcname), []byte(printpathSrc), 0644)
- if err != nil {
- t.Fatalf("failed to create source: %v", err)
- }
- if err != nil {
- t.Fatalf("failed to execute template: %v", err)
- }
- outname := name + ".exe"
- cmd := Command("go", "build", "-o", outname, srcname)
- cmd.Dir = dir
- out, err := cmd.CombinedOutput()
- if err != nil {
- t.Fatalf("failed to build executable: %v - %v", err, string(out))
- }
- return filepath.Join(dir, outname)
-}
-
-const printpathSrc = `
-package main
-
-import (
- "os"
- "syscall"
- "unicode/utf16"
- "unsafe"
-)
-
-func getMyName() (string, error) {
- var sysproc = syscall.MustLoadDLL("kernel32.dll").MustFindProc("GetModuleFileNameW")
- b := make([]uint16, syscall.MAX_PATH)
- r, _, err := sysproc.Call(0, uintptr(unsafe.Pointer(&b[0])), uintptr(len(b)))
- n := uint32(r)
- if n == 0 {
- return "", err
- }
- return string(utf16.Decode(b[0:n])), nil
-}
-
-func main() {
- path, err := getMyName()
- if err != nil {
- os.Stderr.Write([]byte("getMyName failed: " + err.Error() + "\n"))
- os.Exit(1)
- }
- os.Stdout.Write([]byte(path))
-}
-`