diff options
author | Michael Stapelberg <stapelberg@debian.org> | 2013-03-04 21:27:43 +0100 |
---|---|---|
committer | Michael Stapelberg <michael@stapelberg.de> | 2013-03-04 21:27:43 +0100 |
commit | ad47422646a18ffcb47cec916ef7393c923f2e76 (patch) | |
tree | 7c7861fb3d9539d61c1dcfd5b8dadee974c25760 /src/pkg/os/exec | |
parent | 2c8d5d584a79781ca41bb6f4b396893fbbac5b97 (diff) | |
parent | 04b08da9af0c450d645ab7389d1467308cfc2db8 (diff) | |
download | golang-ad47422646a18ffcb47cec916ef7393c923f2e76.tar.gz |
Merge tag 'upstream/1.1_hg20130304' into debian-sid
Upstream version 1.1~hg20130304
Diffstat (limited to 'src/pkg/os/exec')
-rw-r--r-- | src/pkg/os/exec/exec.go | 10 | ||||
-rw-r--r-- | src/pkg/os/exec/exec_test.go | 134 | ||||
-rw-r--r-- | src/pkg/os/exec/lp_plan9.go | 3 | ||||
-rw-r--r-- | src/pkg/os/exec/lp_unix.go | 3 | ||||
-rw-r--r-- | src/pkg/os/exec/lp_unix_test.go | 55 | ||||
-rw-r--r-- | src/pkg/os/exec/lp_windows.go | 35 |
6 files changed, 220 insertions, 20 deletions
diff --git a/src/pkg/os/exec/exec.go b/src/pkg/os/exec/exec.go index 9a8e18170..8368491b0 100644 --- a/src/pkg/os/exec/exec.go +++ b/src/pkg/os/exec/exec.go @@ -16,7 +16,7 @@ import ( "syscall" ) -// Error records the name of a binary that failed to be be executed +// Error records the name of a binary that failed to be executed // and the reason it failed. type Error struct { Name string @@ -37,7 +37,7 @@ type Cmd struct { // 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 @@ -143,6 +143,9 @@ func (c *Cmd) argv() []string { 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 } @@ -182,6 +185,9 @@ func (c *Cmd) stderr() (f *os.File, err error) { 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 } diff --git a/src/pkg/os/exec/exec_test.go b/src/pkg/os/exec/exec_test.go index 52f4bce3a..611ac0267 100644 --- a/src/pkg/os/exec/exec_test.go +++ b/src/pkg/os/exec/exec_test.go @@ -14,6 +14,7 @@ import ( "net/http" "net/http/httptest" "os" + "path/filepath" "runtime" "strconv" "strings" @@ -83,10 +84,16 @@ func TestNoExistBinary(t *testing.T) { func TestExitStatus(t *testing.T) { // Test that exit values are returned correctly - err := helperCommand("exit", "42").Run() + cmd := helperCommand("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.(*ExitError); ok { - if s, e := werr.Error(), "exit status 42"; s != e { - t.Errorf("from exit 42 got exit %q, want %q", s, e) + if s := werr.Error(); s != want { + t.Errorf("from exit 42 got exit %q, want %q", s, want) } } else { t.Fatalf("expected *ExitError from exit 42; got %T: %v", err, err) @@ -144,18 +151,36 @@ func TestPipes(t *testing.T) { check("Wait", err) } +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 TestExtraFiles(t *testing.T) { if runtime.GOOS == "windows" { - t.Logf("no operating system support; skipping") - return + t.Skip("no operating system support; skipping") } // Ensure that file descriptors have not already been leaked into // our environment. - for fd := os.Stderr.Fd() + 1; fd <= 101; fd++ { - err := os.NewFile(fd, "").Close() - if err == nil { - t.Logf("Something already leaked - closed fd %d", fd) + if !testedAlreadyLeaked { + testedAlreadyLeaked = true + for fd := basefds(); fd <= 101; fd++ { + err := os.NewFile(fd, "").Close() + if err == nil { + t.Logf("Something already leaked - closed fd %d", fd) + } } } @@ -167,6 +192,18 @@ func TestExtraFiles(t *testing.T) { } 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.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -193,13 +230,65 @@ func TestExtraFiles(t *testing.T) { } c := helperCommand("read3") + var stdout, stderr bytes.Buffer + c.Stdout = &stdout + c.Stderr = &stderr c.ExtraFiles = []*os.File{tf} - bs, err := c.CombinedOutput() + err = c.Run() if err != nil { - t.Fatalf("CombinedOutput: %v; output %q", err, bs) + t.Fatalf("Run: %v; stdout %q, stderr %q", err, stdout.Bytes(), stderr.Bytes()) } - if string(bs) != text { - t.Errorf("got %q; want %q", string(bs), text) + 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 *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("describefiles") + ca.ExtraFiles = []*os.File{listenerFile(la)} + lb := listen() + cb := helperCommand("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() } } @@ -287,10 +376,15 @@ func TestHelperProcess(*testing.T) { // 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 default: // Now verify that there are no other open fds. var files []*os.File - for wantfd := os.Stderr.Fd() + 2; wantfd <= 100; wantfd++ { + 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) @@ -314,10 +408,20 @@ func TestHelperProcess(*testing.T) { // what we do with fd3 as long as we refer to it; // closing it is the easy choice. fd3.Close() - os.Stderr.Write(bs) + os.Stdout.Write(bs) case "exit": n, _ := strconv.Atoi(args[0]) os.Exit(n) + case "describefiles": + for fd := uintptr(3); fd < 25; fd++ { + f := os.NewFile(fd, fmt.Sprintf("fd-%d", fd)) + ln, err := net.FileListener(f) + if err == nil { + fmt.Printf("fd%d: listener %s\n", fd, ln.Addr()) + ln.Close() + } + } + 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 index 0e229e03e..6846a35c8 100644 --- a/src/pkg/os/exec/lp_plan9.go +++ b/src/pkg/os/exec/lp_plan9.go @@ -8,7 +8,6 @@ import ( "errors" "os" "strings" - "syscall" ) // ErrNotFound is the error resulting if a path search failed to find an executable file. @@ -22,7 +21,7 @@ func findExecutable(file string) error { if m := d.Mode(); !m.IsDir() && m&0111 != 0 { return nil } - return syscall.EPERM + return os.ErrPermission } // LookPath searches for an executable binary named file diff --git a/src/pkg/os/exec/lp_unix.go b/src/pkg/os/exec/lp_unix.go index 216322199..1d1ec07da 100644 --- a/src/pkg/os/exec/lp_unix.go +++ b/src/pkg/os/exec/lp_unix.go @@ -42,6 +42,9 @@ func LookPath(file string) (string, error) { 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 "." diff --git a/src/pkg/os/exec/lp_unix_test.go b/src/pkg/os/exec/lp_unix_test.go new file mode 100644 index 000000000..625d78486 --- /dev/null +++ b/src/pkg/os/exec/lp_unix_test.go @@ -0,0 +1,55 @@ +// 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 freebsd linux netbsd openbsd + +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 index d8351d7e6..7c7289bce 100644 --- a/src/pkg/os/exec/lp_windows.go +++ b/src/pkg/os/exec/lp_windows.go @@ -72,7 +72,7 @@ func LookPath(file string) (f string, err error) { return } if pathenv := os.Getenv(`PATH`); pathenv != `` { - for _, dir := range strings.Split(pathenv, `;`) { + for _, dir := range splitList(pathenv) { if f, err = findExecutable(dir+`\`+file, exts); err == nil { return } @@ -80,3 +80,36 @@ func LookPath(file string) (f string, err error) { } 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 +} |