From 8f76548fd95ffc012f2d34dca6b251be490192f7 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Wed, 18 Nov 2020 08:38:31 -0800 Subject: [PATCH] tempfork/osexec: remove old fork of os/exec This package was a temporary fork of os/exec to fix an EINTR loop bug that was fixed upstream for Go 1.15 in https://github.com/golang/go/commit/8c1db77a92b1d17d3fe07999c5f20602a2080be9 (https://go-review.googlesource.com/c/go/+/232862), in src/os/exec_unix.go: https://github.com/golang/go/commit/8c1db77a92b1d17d3fe07999c5f20602a2080be9#diff-72072cbd53a7240debad8aa506ff7ec795f9cfac7322e779f9bac29a4d0d0bd4 --- cmd/tailscale/depaware.txt | 1 - cmd/tailscaled/depaware.txt | 1 - portlist/netstat_exec.go | 3 +- portlist/portlist_macos.go | 3 +- portlist/portlist_windows.go | 2 +- tempfork/osexec/README.md | 47 -- tempfork/osexec/bench_test.go | 23 - tempfork/osexec/env_test.go | 39 -- tempfork/osexec/example_test.go | 156 ------ tempfork/osexec/exec.go | 797 ------------------------------- tempfork/osexec/exec_unix.go | 24 - tempfork/osexec/exec_windows.go | 23 - tempfork/osexec/internal_test.go | 61 --- tempfork/osexec/lp_js.go | 23 - tempfork/osexec/lp_plan9.go | 55 --- tempfork/osexec/lp_test.go | 33 -- tempfork/osexec/lp_unix.go | 58 --- tempfork/osexec/lp_unix_test.go | 50 -- tempfork/osexec/lp_windows.go | 93 ---- 19 files changed, 3 insertions(+), 1489 deletions(-) delete mode 100644 tempfork/osexec/README.md delete mode 100644 tempfork/osexec/bench_test.go delete mode 100644 tempfork/osexec/env_test.go delete mode 100644 tempfork/osexec/example_test.go delete mode 100644 tempfork/osexec/exec.go delete mode 100644 tempfork/osexec/exec_unix.go delete mode 100644 tempfork/osexec/exec_windows.go delete mode 100644 tempfork/osexec/internal_test.go delete mode 100644 tempfork/osexec/lp_js.go delete mode 100644 tempfork/osexec/lp_plan9.go delete mode 100644 tempfork/osexec/lp_test.go delete mode 100644 tempfork/osexec/lp_unix.go delete mode 100644 tempfork/osexec/lp_unix_test.go delete mode 100644 tempfork/osexec/lp_windows.go diff --git a/cmd/tailscale/depaware.txt b/cmd/tailscale/depaware.txt index 2d8d8610c..ff4685576 100644 --- a/cmd/tailscale/depaware.txt +++ b/cmd/tailscale/depaware.txt @@ -67,7 +67,6 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep tailscale.com/safesocket from tailscale.com/cmd/tailscale/cli 💣 tailscale.com/syncs from tailscale.com/net/interfaces+ tailscale.com/tailcfg from tailscale.com/cmd/tailscale/cli+ - DW tailscale.com/tempfork/osexec from tailscale.com/portlist W tailscale.com/tsconst from tailscale.com/net/interfaces tailscale.com/types/empty from tailscale.com/control/controlclient+ tailscale.com/types/key from tailscale.com/cmd/tailscale/cli+ diff --git a/cmd/tailscaled/depaware.txt b/cmd/tailscaled/depaware.txt index 35975d075..bae586af8 100644 --- a/cmd/tailscaled/depaware.txt +++ b/cmd/tailscaled/depaware.txt @@ -74,7 +74,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de tailscale.com/smallzstd from tailscale.com/ipn/ipnserver+ 💣 tailscale.com/syncs from tailscale.com/net/interfaces+ tailscale.com/tailcfg from tailscale.com/control/controlclient+ - DW tailscale.com/tempfork/osexec from tailscale.com/portlist W tailscale.com/tsconst from tailscale.com/net/interfaces tailscale.com/types/empty from tailscale.com/control/controlclient+ tailscale.com/types/flagtype from tailscale.com/cmd/tailscaled diff --git a/portlist/netstat_exec.go b/portlist/netstat_exec.go index b228dbed3..2ca3fd574 100644 --- a/portlist/netstat_exec.go +++ b/portlist/netstat_exec.go @@ -8,9 +8,8 @@ package portlist import ( "fmt" + "os/exec" "strings" - - exec "tailscale.com/tempfork/osexec" ) var osHideWindow func(*exec.Cmd) // non-nil on Windows; see portlist_windows.go diff --git a/portlist/portlist_macos.go b/portlist/portlist_macos.go index c532a248d..66bd558b8 100644 --- a/portlist/portlist_macos.go +++ b/portlist/portlist_macos.go @@ -12,11 +12,10 @@ import ( "fmt" "log" "os" + "os/exec" "strings" "sync/atomic" "time" - - exec "tailscale.com/tempfork/osexec" ) // We have to run netstat, which is a bit expensive, so don't do it too often. diff --git a/portlist/portlist_windows.go b/portlist/portlist_windows.go index ee6e6cf14..f447988ee 100644 --- a/portlist/portlist_windows.go +++ b/portlist/portlist_windows.go @@ -5,11 +5,11 @@ package portlist import ( + "os/exec" "syscall" "time" "golang.org/x/sys/windows" - exec "tailscale.com/tempfork/osexec" ) // Forking on Windows is insanely expensive, so don't do it too often. diff --git a/tempfork/osexec/README.md b/tempfork/osexec/README.md deleted file mode 100644 index 06a60130c..000000000 --- a/tempfork/osexec/README.md +++ /dev/null @@ -1,47 +0,0 @@ -This is a temporary fork of Go 1.13's os/exec package, -to work around https://github.com/golang/go/issues/36644. - -The main modification (outside of removing some tests that require -internal-only packages to run) is: - -``` -commit 3c66be240f1ee1f1b5f03bed79eb0d9f8c08965a -Author: Avery Pennarun -Date: Sun Jan 19 03:17:30 2020 -0500 - -Cmd.Wait(): handle EINTR return code from os.Process.Wait(). - -This is probably not actually the correct fix; most likely -os.Process.Wait() itself should be fixed to retry on EINTR so that it -never leaks out of that function. But if we're going to patch a -particular module, it's safer to patch a higher-level one like os/exec -rather than the os module itself. - -diff --git a/exec.go b/exec.go -index 17ef003e..5375e673 100644 ---- a/exec.go -+++ b/exec.go -@@ -498,7 +498,21 @@ func (c *Cmd) Wait() error { - } - c.finished = true - -- state, err := c.Process.Wait() -+ var err error -+ var state *os.ProcessState -+ for { -+ state, err = c.Process.Wait() -+ if err != nil { -+ xe, ok := err.(*os.SyscallError) -+ if ok { -+ if xe.Unwrap() == syscall.EINTR { -+ // temporary error, retry wait syscall -+ continue -+ } -+ } -+ } -+ break -+ } - if c.waitDone != nil { - close(c.waitDone) - } -``` diff --git a/tempfork/osexec/bench_test.go b/tempfork/osexec/bench_test.go deleted file mode 100644 index 9a94001e8..000000000 --- a/tempfork/osexec/bench_test.go +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright 2019 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" -) - -func BenchmarkExecHostname(b *testing.B) { - b.ReportAllocs() - path, err := LookPath("hostname") - if err != nil { - b.Fatalf("could not find hostname: %v", err) - } - b.ResetTimer() - for i := 0; i < b.N; i++ { - if err := Command(path).Run(); err != nil { - b.Fatalf("hostname: %v", err) - } - } -} diff --git a/tempfork/osexec/env_test.go b/tempfork/osexec/env_test.go deleted file mode 100644 index b5ac398c2..000000000 --- a/tempfork/osexec/env_test.go +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2017 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 ( - "reflect" - "testing" -) - -func TestDedupEnv(t *testing.T) { - tests := []struct { - noCase bool - in []string - want []string - }{ - { - noCase: true, - in: []string{"k1=v1", "k2=v2", "K1=v3"}, - want: []string{"K1=v3", "k2=v2"}, - }, - { - noCase: false, - in: []string{"k1=v1", "K1=V2", "k1=v3"}, - want: []string{"k1=v3", "K1=V2"}, - }, - { - in: []string{"=a", "=b", "foo", "bar"}, - want: []string{"=b", "foo", "bar"}, - }, - } - for _, tt := range tests { - got := dedupEnvCase(tt.noCase, tt.in) - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("Dedup(%v, %q) = %q; want %q", tt.noCase, tt.in, got, tt.want) - } - } -} diff --git a/tempfork/osexec/example_test.go b/tempfork/osexec/example_test.go deleted file mode 100644 index 62866fa71..000000000 --- a/tempfork/osexec/example_test.go +++ /dev/null @@ -1,156 +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" - "context" - "encoding/json" - "fmt" - "io" - "io/ioutil" - "log" - "os" - "os/exec" - "strings" - "time" -) - -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 ExampleCommand_environment() { - cmd := exec.Command("prog") - cmd.Env = append(os.Environ(), - "FOO=duplicate_value", // ignored - "FOO=actual_value", // this value is used - ) - if err := cmd.Run(); err != nil { - log.Fatal(err) - } -} - -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_Run() { - cmd := exec.Command("sleep", "1") - log.Printf("Running command and waiting for it to finish...") - err := cmd.Run() - log.Printf("Command finished with error: %v", err) -} - -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) -} - -func ExampleCmd_StdinPipe() { - cmd := exec.Command("cat") - stdin, err := cmd.StdinPipe() - if err != nil { - log.Fatal(err) - } - - go func() { - defer stdin.Close() - io.WriteString(stdin, "values written to stdin are passed to cmd's standard input") - }() - - out, err := cmd.CombinedOutput() - if err != nil { - log.Fatal(err) - } - - fmt.Printf("%s\n", out) -} - -func ExampleCmd_StderrPipe() { - cmd := exec.Command("sh", "-c", "echo stdout; echo 1>&2 stderr") - stderr, err := cmd.StderrPipe() - if err != nil { - log.Fatal(err) - } - - if err := cmd.Start(); err != nil { - log.Fatal(err) - } - - slurp, _ := ioutil.ReadAll(stderr) - fmt.Printf("%s\n", slurp) - - if err := cmd.Wait(); err != nil { - log.Fatal(err) - } -} - -func ExampleCmd_CombinedOutput() { - cmd := exec.Command("sh", "-c", "echo stdout; echo 1>&2 stderr") - stdoutStderr, err := cmd.CombinedOutput() - if err != nil { - log.Fatal(err) - } - fmt.Printf("%s\n", stdoutStderr) -} - -func ExampleCommandContext() { - ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) - defer cancel() - - if err := exec.CommandContext(ctx, "sleep", "5").Run(); err != nil { - // This will fail after 100 milliseconds. The 5 second sleep - // will be interrupted. - } -} diff --git a/tempfork/osexec/exec.go b/tempfork/osexec/exec.go deleted file mode 100644 index 5375e6738..000000000 --- a/tempfork/osexec/exec.go +++ /dev/null @@ -1,797 +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. -// -// Unlike the "system" library call from C and other languages, the -// os/exec package intentionally does not invoke the system shell and -// does not expand any glob patterns or handle other expansions, -// pipelines, or redirections typically done by shells. The package -// behaves more like C's "exec" family of functions. To expand glob -// patterns, either call the shell directly, taking care to escape any -// dangerous input, or use the path/filepath package's Glob function. -// To expand environment variables, use package os's ExpandEnv. -// -// 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. -package exec - -import ( - "bytes" - "context" - "errors" - "io" - "os" - "path/filepath" - "runtime" - "strconv" - "strings" - "sync" - "syscall" -) - -// Error is returned by LookPath when it fails to classify a file as an -// executable. -type Error struct { - // Name is the file name for which the error occurred. - Name string - // Err is the underlying error. - Err error -} - -func (e *Error) Error() string { - return "exec: " + strconv.Quote(e.Name) + ": " + e.Err.Error() -} - -func (e *Error) Unwrap() error { return e.Err } - -// Cmd represents an external command being prepared or run. -// -// A Cmd cannot be reused after calling its Run, Output or CombinedOutput -// methods. -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. - // Each entry is of the form "key=value". - // If Env is nil, the new process uses the current process's - // environment. - // If Env contains duplicate environment keys, only the last - // value in the slice for each duplicate key is used. - // As a special case on Windows, SYSTEMROOT is always added if - // missing and not explicitly set to the empty string. - 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). - // - // If Stdin is an *os.File, the process's standard input is connected - // directly to that file. - // - // Otherwise, during the execution of the command a separate - // goroutine reads from Stdin and delivers that data to the command - // over a pipe. In this case, Wait does not complete until the goroutine - // stops copying, either because it has reached the end of Stdin - // (EOF or a read error) or because writing to the pipe returned an error. - 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 either is an *os.File, the corresponding output from the process - // is connected directly to that file. - // - // Otherwise, during the execution of the command a separate goroutine - // reads from the process over a pipe and delivers that data to the - // corresponding Writer. In this case, Wait does not complete until the - // goroutine reaches EOF or encounters an error. - // - // If Stdout and Stderr are the same writer, and have a type that can - // be compared with ==, 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. - // - // ExtraFiles is not supported on Windows. - 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 - - ctx context.Context // nil means none - 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 - waitDone chan struct{} -} - -// 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 name to a complete path if possible. Otherwise it uses name -// directly as Path. -// -// 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"). -// Args[0] is always name, not the possibly resolved Path. -// -// On Windows, processes receive the whole command line as a single string -// and do their own parsing. Command combines and quotes Args into a command -// line string with an algorithm compatible with applications using -// CommandLineToArgvW (which is the most common way). Notable exceptions are -// msiexec.exe and cmd.exe (and thus, all batch files), which have a different -// unquoting algorithm. In these or other similar cases, you can do the -// quoting yourself and provide the full command line in SysProcAttr.CmdLine, -// leaving Args empty. -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 -} - -// CommandContext is like Command but includes a context. -// -// The provided context is used to kill the process (by calling -// os.Process.Kill) if the context becomes done before the command -// completes on its own. -func CommandContext(ctx context.Context, name string, arg ...string) *Cmd { - if ctx == nil { - panic("nil Context") - } - cmd := Command(name, arg...) - cmd.ctx = ctx - return cmd -} - -// String returns a human-readable description of c. -// It is intended only for debugging. -// 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.lookPathErr != nil { - // failed to resolve path; report the original requested path (plus args) - return strings.Join(c.Args, " ") - } - // report the exact executable path (plus args) - b := new(strings.Builder) - b.WriteString(c.Path) - for _, a := range c.Args[1:] { - b.WriteByte(' ') - b.WriteString(a) - } - return b.String() -} - -// 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} -} - -// skipStdinCopyError optionally specifies a function which reports -// whether the provided stdin copy error should be ignored. -// It is non-nil everywhere but Plan 9, which lacks EPIPE. See exec_posix.go. -var skipStdinCopyError func(error) bool - -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 skip := skipStdinCopyError; skip != nil && skip(err) { - err = nil - } - 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) - pr.Close() // in case io.Copy stopped due to write error - 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 starts but does not complete successfully, the error is of -// type *ExitError. Other error types may be returned for other situations. -// -// If the calling goroutine has locked the operating system thread -// with runtime.LockOSThread and modified any inheritable OS-level -// thread state (for example, Linux or Plan 9 name spaces), the new -// process will inherit the caller's thread state. -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") - } - if c.ctx != nil { - select { - case <-c.ctx.Done(): - c.closeDescriptors(c.closeAfterStart) - c.closeDescriptors(c.closeAfterWait) - return c.ctx.Err() - default: - } - } - - c.childFiles = make([]*os.File, 0, 3+len(c.ExtraFiles)) - 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: addCriticalEnv(dedupEnv(c.envv())), - Sys: c.SysProcAttr, - }) - if err != nil { - c.closeDescriptors(c.closeAfterStart) - c.closeDescriptors(c.closeAfterWait) - return err - } - - c.closeDescriptors(c.closeAfterStart) - - // Don't allocate the channel unless there are goroutines to fire. - if len(c.goroutine) > 0 { - c.errch = make(chan error, len(c.goroutine)) - for _, fn := range c.goroutine { - go func(fn func() error) { - c.errch <- fn() - }(fn) - } - } - - if c.ctx != nil { - c.waitDone = make(chan struct{}) - go func() { - select { - case <-c.ctx.Done(): - c.Process.Kill() - case <-c.waitDone: - } - }() - } - - return nil -} - -// An ExitError reports an unsuccessful exit by a command. -type ExitError struct { - *os.ProcessState - - // Stderr holds a subset of the standard error output from the - // Cmd.Output method if standard error was not otherwise being - // collected. - // - // If the error output is long, Stderr may contain only a prefix - // and suffix of the output, with the middle replaced with - // text about the number of omitted bytes. - // - // Stderr is provided for debugging, for inclusion in error messages. - // Users with other needs should redirect Cmd.Stderr as needed. - Stderr []byte -} - -func (e *ExitError) Error() string { - return e.ProcessState.String() -} - -// Wait waits for the command to exit and waits for any copying to -// stdin or copying from stdout or stderr to complete. -// -// The command 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. -// -// If any of c.Stdin, c.Stdout or c.Stderr are not an *os.File, Wait also waits -// for the respective I/O loop copying to or from the process to complete. -// -// 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 - - var err error - var state *os.ProcessState - for { - state, err = c.Process.Wait() - if err != nil { - xe, ok := err.(*os.SyscallError) - if ok { - if xe.Unwrap() == syscall.EINTR { - // temporary error, retry wait syscall - continue - } - } - } - break - } - if c.waitDone != nil { - close(c.waitDone) - } - 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{ProcessState: state} - } - - return copyError -} - -// Output runs the command and returns its standard output. -// Any returned error will usually be of type *ExitError. -// If c.Stderr was nil, Output populates ExitError.Stderr. -func (c *Cmd) Output() ([]byte, error) { - if c.Stdout != nil { - return nil, errors.New("exec: Stdout already set") - } - var stdout bytes.Buffer - c.Stdout = &stdout - - captureErr := c.Stderr == nil - if captureErr { - c.Stderr = &prefixSuffixSaver{N: 32 << 10} - } - - err := c.Run() - if err != nil && captureErr { - if ee, ok := err.(*ExitError); ok { - ee.Stderr = c.Stderr.(*prefixSuffixSaver).Bytes() - } - } - return stdout.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 -} - -// prefixSuffixSaver is an io.Writer which retains the first N bytes -// and the last N bytes written to it. The Bytes() methods reconstructs -// it with a pretty error message. -type prefixSuffixSaver struct { - N int // max size of prefix or suffix - prefix []byte - suffix []byte // ring buffer once len(suffix) == N - suffixOff int // offset to write into suffix - skipped int64 - - // TODO(bradfitz): we could keep one large []byte and use part of it for - // the prefix, reserve space for the '... Omitting N bytes ...' message, - // then the ring buffer suffix, and just rearrange the ring buffer - // suffix when Bytes() is called, but it doesn't seem worth it for - // now just for error messages. It's only ~64KB anyway. -} - -func (w *prefixSuffixSaver) Write(p []byte) (n int, err error) { - lenp := len(p) - p = w.fill(&w.prefix, p) - - // Only keep the last w.N bytes of suffix data. - if overage := len(p) - w.N; overage > 0 { - p = p[overage:] - w.skipped += int64(overage) - } - p = w.fill(&w.suffix, p) - - // w.suffix is full now if p is non-empty. Overwrite it in a circle. - for len(p) > 0 { // 0, 1, or 2 iterations. - n := copy(w.suffix[w.suffixOff:], p) - p = p[n:] - w.skipped += int64(n) - w.suffixOff += n - if w.suffixOff == w.N { - w.suffixOff = 0 - } - } - return lenp, nil -} - -// fill appends up to len(p) bytes of p to *dst, such that *dst does not -// grow larger than w.N. It returns the un-appended suffix of p. -func (w *prefixSuffixSaver) fill(dst *[]byte, p []byte) (pRemain []byte) { - if remain := w.N - len(*dst); remain > 0 { - add := minInt(len(p), remain) - *dst = append(*dst, p[:add]...) - p = p[add:] - } - return p -} - -func (w *prefixSuffixSaver) Bytes() []byte { - if w.suffix == nil { - return w.prefix - } - if w.skipped == 0 { - return append(w.prefix, w.suffix...) - } - var buf bytes.Buffer - buf.Grow(len(w.prefix) + len(w.suffix) + 50) - buf.Write(w.prefix) - buf.WriteString("\n... omitting ") - buf.WriteString(strconv.FormatInt(w.skipped, 10)) - buf.WriteString(" bytes ...\n") - buf.Write(w.suffix[w.suffixOff:]) - buf.Write(w.suffix[:w.suffixOff]) - return buf.Bytes() -} - -func minInt(a, b int) int { - if a < b { - return a - } - return b -} - -// dedupEnv returns a copy of env with any duplicates removed, in favor of -// later values. -// Items not of the normal environment "key=value" form are preserved unchanged. -func dedupEnv(env []string) []string { - return dedupEnvCase(runtime.GOOS == "windows", env) -} - -// dedupEnvCase is dedupEnv with a case option for testing. -// If caseInsensitive is true, the case of keys is ignored. -func dedupEnvCase(caseInsensitive bool, env []string) []string { - out := make([]string, 0, len(env)) - saw := make(map[string]int, len(env)) // key => index into out - for _, kv := range env { - eq := strings.Index(kv, "=") - if eq < 0 { - out = append(out, kv) - continue - } - k := kv[:eq] - if caseInsensitive { - k = strings.ToLower(k) - } - if dupIdx, isDup := saw[k]; isDup { - out[dupIdx] = kv - continue - } - saw[k] = len(out) - out = append(out, kv) - } - return out -} - -// addCriticalEnv adds any critical environment variables that are required -// (or at least almost always required) on the operating system. -// Currently this is only used for Windows. -func addCriticalEnv(env []string) []string { - if runtime.GOOS != "windows" { - return env - } - for _, kv := range env { - eq := strings.Index(kv, "=") - if eq < 0 { - continue - } - k := kv[:eq] - if strings.EqualFold(k, "SYSTEMROOT") { - // We already have it. - return env - } - } - return append(env, "SYSTEMROOT="+os.Getenv("SYSTEMROOT")) -} diff --git a/tempfork/osexec/exec_unix.go b/tempfork/osexec/exec_unix.go deleted file mode 100644 index 9c3e17d23..000000000 --- a/tempfork/osexec/exec_unix.go +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2015 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 !plan9,!windows - -package exec - -import ( - "os" - "syscall" -) - -func init() { - skipStdinCopyError = func(err error) bool { - // Ignore EPIPE errors copying to stdin if the program - // completed successfully otherwise. - // See Issue 9173. - pe, ok := err.(*os.PathError) - return ok && - pe.Op == "write" && pe.Path == "|1" && - pe.Err == syscall.EPIPE - } -} diff --git a/tempfork/osexec/exec_windows.go b/tempfork/osexec/exec_windows.go deleted file mode 100644 index af8cd9721..000000000 --- a/tempfork/osexec/exec_windows.go +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright 2017 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 ( - "os" - "syscall" -) - -func init() { - skipStdinCopyError = func(err error) bool { - // Ignore ERROR_BROKEN_PIPE and ERROR_NO_DATA errors copying - // to stdin if the program completed successfully otherwise. - // See Issue 20445. - const _ERROR_NO_DATA = syscall.Errno(0xe8) - pe, ok := err.(*os.PathError) - return ok && - pe.Op == "write" && pe.Path == "|1" && - (pe.Err == syscall.ERROR_BROKEN_PIPE || pe.Err == _ERROR_NO_DATA) - } -} diff --git a/tempfork/osexec/internal_test.go b/tempfork/osexec/internal_test.go deleted file mode 100644 index 68d517ffb..000000000 --- a/tempfork/osexec/internal_test.go +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright 2015 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 ( - "io" - "testing" -) - -func TestPrefixSuffixSaver(t *testing.T) { - tests := []struct { - N int - writes []string - want string - }{ - { - N: 2, - writes: nil, - want: "", - }, - { - N: 2, - writes: []string{"a"}, - want: "a", - }, - { - N: 2, - writes: []string{"abc", "d"}, - want: "abcd", - }, - { - N: 2, - writes: []string{"abc", "d", "e"}, - want: "ab\n... omitting 1 bytes ...\nde", - }, - { - N: 2, - writes: []string{"ab______________________yz"}, - want: "ab\n... omitting 22 bytes ...\nyz", - }, - { - N: 2, - writes: []string{"ab_______________________y", "z"}, - want: "ab\n... omitting 23 bytes ...\nyz", - }, - } - for i, tt := range tests { - w := &prefixSuffixSaver{N: tt.N} - for _, s := range tt.writes { - n, err := io.WriteString(w, s) - if err != nil || n != len(s) { - t.Errorf("%d. WriteString(%q) = %v, %v; want %v, %v", i, s, n, err, len(s), nil) - } - } - if got := string(w.Bytes()); got != tt.want { - t.Errorf("%d. Bytes = %q; want %q", i, got, tt.want) - } - } -} diff --git a/tempfork/osexec/lp_js.go b/tempfork/osexec/lp_js.go deleted file mode 100644 index 6750fb99b..000000000 --- a/tempfork/osexec/lp_js.go +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright 2018 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 js,wasm - -package exec - -import ( - "errors" -) - -// 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") - -// 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. -// The result may be an absolute path or a path relative to the current directory. -func LookPath(file string) (string, error) { - // Wasm can not execute processes, so act as if there are no executables at all. - return "", &Error{file, ErrNotFound} -} diff --git a/tempfork/osexec/lp_plan9.go b/tempfork/osexec/lp_plan9.go deleted file mode 100644 index 5860cbca4..000000000 --- a/tempfork/osexec/lp_plan9.go +++ /dev/null @@ -1,55 +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" - "path/filepath" - "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 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 filepath.SplitList(path) { - path := filepath.Join(dir, file) - if err := findExecutable(path); err == nil { - return path, nil - } - } - return "", &Error{file, ErrNotFound} -} diff --git a/tempfork/osexec/lp_test.go b/tempfork/osexec/lp_test.go deleted file mode 100644 index 77d8e848c..000000000 --- a/tempfork/osexec/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/tempfork/osexec/lp_unix.go b/tempfork/osexec/lp_unix.go deleted file mode 100644 index 799e0b4ee..000000000 --- a/tempfork/osexec/lp_unix.go +++ /dev/null @@ -1,58 +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 aix darwin dragonfly freebsd linux nacl netbsd openbsd solaris - -package exec - -import ( - "errors" - "os" - "path/filepath" - "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 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} - } - path := os.Getenv("PATH") - for _, dir := range filepath.SplitList(path) { - if dir == "" { - // Unix shell semantics: path element "" means "." - dir = "." - } - path := filepath.Join(dir, file) - if err := findExecutable(path); err == nil { - return path, nil - } - } - return "", &Error{file, ErrNotFound} -} diff --git a/tempfork/osexec/lp_unix_test.go b/tempfork/osexec/lp_unix_test.go deleted file mode 100644 index 5f4a9b44d..000000000 --- a/tempfork/osexec/lp_unix_test.go +++ /dev/null @@ -1,50 +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 aix darwin dragonfly freebsd linux netbsd openbsd solaris - -package exec - -import ( - "os" - "testing" -) - -func TestLookPathUnixEmptyPath(t *testing.T) { - tmp := t.TempDir() - 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/tempfork/osexec/lp_windows.go b/tempfork/osexec/lp_windows.go deleted file mode 100644 index 9ea3d7657..000000000 --- a/tempfork/osexec/lp_windows.go +++ /dev/null @@ -1,93 +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" - "path/filepath" - "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 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) (string, error) { - var exts []string - x := os.Getenv(`PATHEXT`) - if x != "" { - for _, e := range strings.Split(strings.ToLower(x), `;`) { - if e == "" { - continue - } - if e[0] != '.' { - e = "." + e - } - exts = append(exts, e) - } - } else { - exts = []string{".com", ".exe", ".bat", ".cmd"} - } - - if strings.ContainsAny(file, `:\/`) { - if f, err := findExecutable(file, exts); err == nil { - return f, nil - } else { - return "", &Error{file, err} - } - } - 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 { - return f, nil - } - } - return "", &Error{file, ErrNotFound} -}