├── .gitignore ├── LICENSE ├── README.md ├── example_test.go ├── exec.go ├── osexec_test.go └── wercker.yml /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Dave Cheney 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![wercker status](https://app.wercker.com/status/9b4ccf2acb54dcc7cb10f3e933203f28/m "wercker status")](https://app.wercker.com/project/bykey/9b4ccf2acb54dcc7cb10f3e933203f28) 2 | 3 | 4 | -------------------------------------------------------------------------------- /example_test.go: -------------------------------------------------------------------------------- 1 | // +build !windows,!plan9 2 | 3 | package exec_test 4 | 5 | import ( 6 | "log" 7 | "os" 8 | 9 | "github.com/pkg/exec" 10 | ) 11 | 12 | func ExampleCmd_Run() { 13 | cmd := exec.Command("git", "status") 14 | err := cmd.Run() 15 | if err != nil { 16 | log.Fatal(err) 17 | } 18 | } 19 | 20 | func ExampleCmd_Run_dir() { 21 | cmd := exec.Command("git", "status") 22 | // change working directory to /tmp and run git status. 23 | err := cmd.Run(exec.Dir("/tmp")) 24 | if err != nil { 25 | log.Fatal(err) 26 | } 27 | } 28 | 29 | func ExampleCmd_Run_stdout() { 30 | cmd := exec.Command("git", "status") 31 | // change working directory to /tmp, pass os.Stdout to the child 32 | // and run git status. 33 | err := cmd.Run( 34 | exec.Dir("/tmp"), 35 | exec.Stdout(os.Stdout), 36 | ) 37 | if err != nil { 38 | log.Fatal(err) 39 | } 40 | } 41 | 42 | func ExampleCmd_Run_before() { 43 | cmd := exec.Command("/usr/sleep", "60s") 44 | // set a before and after function 45 | err := cmd.Run( 46 | exec.BeforeFunc(func(c *exec.Cmd) error { 47 | log.Println("About to call:", c.Args) 48 | return nil 49 | }), 50 | exec.AfterFunc(func(c *exec.Cmd) error { 51 | log.Println("Finised calling:", c.Args) 52 | return nil 53 | }), 54 | ) 55 | if err != nil { 56 | log.Fatal(err) 57 | } 58 | } 59 | 60 | func ExampleCmd_Output_dir() { 61 | cmd := exec.Command("git", "status") 62 | // change working directory to /tmp, run git status and 63 | // capture the stdout of the child. 64 | out, err := cmd.Output(exec.Dir("/tmp")) 65 | if err != nil { 66 | log.Fatal(err) 67 | } 68 | log.Printf("%s", out) 69 | } 70 | 71 | func ExampleCmd_Start() { 72 | // start httpd server and redirect the childs stderr 73 | // to the parent's stdout. 74 | cmd := exec.Command("httpd") 75 | err := cmd.Start(exec.Stderr(os.Stdout)) 76 | if err != nil { 77 | log.Fatal(err) 78 | } 79 | } 80 | 81 | func ExampleCmd_Start_Setenv() { 82 | // set or overwrite an environment variable. 83 | cmd := exec.Command("go") 84 | err := cmd.Start(exec.Setenv("GOPATH", "/foo")) 85 | if err != nil { 86 | log.Fatal(err) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /exec.go: -------------------------------------------------------------------------------- 1 | // package exec is a universal wrapper around the os/exec package. 2 | package exec 3 | 4 | import ( 5 | "bytes" 6 | "errors" 7 | "io" 8 | "os" 9 | "os/exec" 10 | "strings" 11 | ) 12 | 13 | // System executes the command specified in command by calling /bin/sh -c command, and returns after the command has been completed. Stdin, Stdout, and Stderr are plumbed through to the child, but this behaviour can be modified by opts. 14 | func System(command string, opts ...func(*Cmd) error) error { 15 | args := strings.Fields(command) 16 | args = append([]string{"-c"}, args...) 17 | cmd := Command("/bin/sh", args...) 18 | opts = append([]func(*Cmd) error{ 19 | Stdin(os.Stdin), 20 | Stdout(os.Stdout), 21 | Stderr(os.Stderr), 22 | }, opts...) 23 | return cmd.Run(opts...) 24 | } 25 | 26 | // LookPath searches for an executable binary named file in the directories 27 | // named by the PATH environment variable. If file contains a slash, it is 28 | // tried directly and the PATH is not consulted. The result may be an 29 | // absolute path or a path relative to the current directory. 30 | func LookPath(file string) (string, error) { return exec.LookPath(file) } 31 | 32 | // Command returns a Cmd to execute the named program with the given arguments. 33 | func Command(name string, args ...string) *Cmd { 34 | return &Cmd{ 35 | Cmd: exec.Command(name, args...), 36 | initalised: true, 37 | } 38 | } 39 | 40 | // Cmd represents a command to be run. 41 | // Cmd must be created by calling Command. 42 | // Cmd cannot be reused after calling its Run or Start methods. 43 | type Cmd struct { 44 | *exec.Cmd 45 | initalised bool 46 | waited bool 47 | before, after func(*Cmd) error 48 | } 49 | 50 | // Run starts the specified command and waits for it to complete. 51 | // 52 | // The returned error is nil if the command runs, has no problems 53 | // copying stdin, stdout, and stderr, and exits with a zero exit 54 | // status. 55 | // 56 | // If the command fails to run or doesn't complete successfully, the 57 | // error is of type *ExitError. Other error types may be 58 | // returned for I/O problems. 59 | func (c *Cmd) Run(opts ...func(*Cmd) error) error { 60 | if err := c.Start(opts...); err != nil { 61 | return err 62 | } 63 | return c.Wait() 64 | } 65 | 66 | // Start starts the specified command but does not wait for it to complete. 67 | // 68 | // The Wait method will return the exit code and release associated resources 69 | // once the command exits. 70 | func (c *Cmd) Start(opts ...func(*Cmd) error) error { 71 | if !c.initalised { 72 | return errors.New("exec: command not initalised") 73 | } 74 | if err := applyDefaultOptions(c); err != nil { 75 | return err 76 | } 77 | if err := applyOptions(c, opts...); err != nil { 78 | return err 79 | } 80 | if c.before != nil { 81 | if err := c.before(c); err != nil { 82 | return err 83 | } 84 | } 85 | return c.Cmd.Start() 86 | } 87 | 88 | // Wait waits for the command to exit. 89 | // It must have been started by Start. 90 | func (c *Cmd) Wait() (err error) { 91 | if c.waited { 92 | return errors.New("exec: Wait was already called") 93 | } 94 | c.waited = true 95 | defer func() { 96 | if c.after == nil { 97 | return 98 | } 99 | errAfter := c.after(c) 100 | if err == nil { 101 | err = errAfter 102 | } 103 | }() 104 | return c.Cmd.Wait() 105 | } 106 | 107 | // Stdin specifies the process's standard input. 108 | func Stdin(r io.Reader) func(*Cmd) error { 109 | return func(c *Cmd) error { 110 | if c.Stdin != nil { 111 | return errors.New("exec: Stdin already set") 112 | } 113 | c.Stdin = r 114 | return nil 115 | } 116 | } 117 | 118 | // Stdout specifies the process's standard output. 119 | func Stdout(w io.Writer) func(*Cmd) error { 120 | return func(c *Cmd) error { 121 | if c.Stdout != nil { 122 | return errors.New("exec: Stdout already set") 123 | } 124 | c.Stdout = w 125 | return nil 126 | } 127 | } 128 | 129 | // Stderr specifies the process's standard error.. 130 | func Stderr(w io.Writer) func(*Cmd) error { 131 | return func(c *Cmd) error { 132 | if c.Stderr != nil { 133 | return errors.New("exec: Stderr already set") 134 | } 135 | c.Stderr = w 136 | return nil 137 | } 138 | } 139 | 140 | // BeforeFunc runs fn just prior to executing the command. If an error 141 | // is returned, the command will not be run. 142 | func BeforeFunc(fn func(*Cmd) error) func(*Cmd) error { 143 | return func(c *Cmd) error { 144 | if c.before != nil { 145 | return errors.New("exec: BeforeFunc already set") 146 | } 147 | c.before = fn 148 | return nil 149 | } 150 | } 151 | 152 | // AfterFunc runs fn just after to executing the command. If an error 153 | // is returned, it will be returned providing the command exited cleanly. 154 | func AfterFunc(fn func(*Cmd) error) func(*Cmd) error { 155 | return func(c *Cmd) error { 156 | if c.after != nil { 157 | return errors.New("exec: AfterFunc already set") 158 | } 159 | c.after = fn 160 | return nil 161 | } 162 | } 163 | 164 | // Setenv applies (or overwrites) childs environment key. 165 | func Setenv(key, val string) func(*Cmd) error { 166 | return func(c *Cmd) error { 167 | key += "=" 168 | for i := range c.Env { 169 | if strings.HasPrefix(c.Env[i], key) { 170 | c.Env[i] = key + val 171 | return nil 172 | } 173 | } 174 | c.Env = append(c.Env, key+val) 175 | return nil 176 | } 177 | } 178 | 179 | // Output runs the command and returns its standard output. 180 | func (c *Cmd) Output(opts ...func(*Cmd) error) ([]byte, error) { 181 | var b bytes.Buffer 182 | opts = append([]func(*Cmd) error{Stdout(&b)}, opts...) 183 | err := c.Run(opts...) 184 | return b.Bytes(), err 185 | } 186 | 187 | // Dir specifies the working directory of the command. 188 | // If Dir is empty, the command executes in the calling 189 | // process's current directory. 190 | func Dir(dir string) func(*Cmd) error { 191 | return func(c *Cmd) error { 192 | c.Dir = dir 193 | return nil 194 | } 195 | } 196 | 197 | func applyDefaultOptions(c *Cmd) error { 198 | if c.Env == nil { 199 | c.Env = os.Environ() 200 | } 201 | return nil 202 | } 203 | 204 | func applyOptions(c *Cmd, opts ...func(*Cmd) error) error { 205 | for _, opt := range opts { 206 | if err := opt(c); err != nil { 207 | return err 208 | } 209 | } 210 | return nil 211 | } 212 | -------------------------------------------------------------------------------- /osexec_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2009 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Use an external test to avoid os/exec -> net/http -> crypto/x509 -> os/exec 6 | // circular dependency on non-cgo darwin. 7 | 8 | package exec_test 9 | 10 | import ( 11 | "bufio" 12 | "bytes" 13 | "fmt" 14 | "io" 15 | "io/ioutil" 16 | "log" 17 | "net" 18 | "net/http" 19 | "net/http/httptest" 20 | "os" 21 | osexec "os/exec" 22 | "path/filepath" 23 | "runtime" 24 | "strconv" 25 | "strings" 26 | "testing" 27 | "time" 28 | 29 | "github.com/pkg/exec" 30 | ) 31 | 32 | // iOS cannot fork 33 | var iOS = runtime.GOOS == "darwin" && (runtime.GOARCH == "arm" || runtime.GOARCH == "arm64") 34 | 35 | func helperCommand(t *testing.T, s ...string) *exec.Cmd { 36 | if runtime.GOOS == "nacl" || iOS { 37 | t.Skipf("skipping on %s/%s, cannot fork", runtime.GOOS, runtime.GOARCH) 38 | } 39 | cs := []string{"-test.run=TestHelperProcess", "--"} 40 | cs = append(cs, s...) 41 | cmd := exec.Command(os.Args[0], cs...) 42 | cmd.Env = []string{"GO_WANT_HELPER_PROCESS=1"} 43 | return cmd 44 | } 45 | 46 | func TestEcho(t *testing.T) { 47 | bs, err := helperCommand(t, "echo", "foo bar", "baz").Output() 48 | if err != nil { 49 | t.Errorf("echo: %v", err) 50 | } 51 | if g, e := string(bs), "foo bar baz\n"; g != e { 52 | t.Errorf("echo: want %q, got %q", e, g) 53 | } 54 | } 55 | 56 | func TestCommandRelativeName(t *testing.T) { 57 | if iOS { 58 | t.Skip("skipping on darwin/%s, cannot fork", runtime.GOARCH) 59 | } 60 | 61 | // Run our own binary as a relative path 62 | // (e.g. "_test/exec.test") our parent directory. 63 | base := filepath.Base(os.Args[0]) // "exec.test" 64 | dir := filepath.Dir(os.Args[0]) // "/tmp/go-buildNNNN/os/exec/_test" 65 | if dir == "." { 66 | t.Skip("skipping; running test at root somehow") 67 | } 68 | parentDir := filepath.Dir(dir) // "/tmp/go-buildNNNN/os/exec" 69 | dirBase := filepath.Base(dir) // "_test" 70 | if dirBase == "." { 71 | t.Skipf("skipping; unexpected shallow dir of %q", dir) 72 | } 73 | 74 | cmd := exec.Command(filepath.Join(dirBase, base), "-test.run=TestHelperProcess", "--", "echo", "foo") 75 | cmd.Dir = parentDir 76 | cmd.Env = []string{"GO_WANT_HELPER_PROCESS=1"} 77 | 78 | out, err := cmd.Output() 79 | if err != nil { 80 | t.Errorf("echo: %v", err) 81 | } 82 | if g, e := string(out), "foo\n"; g != e { 83 | t.Errorf("echo: want %q, got %q", e, g) 84 | } 85 | } 86 | 87 | func TestCatStdin(t *testing.T) { 88 | // Cat, testing stdin and stdout. 89 | input := "Input string\nLine 2" 90 | p := helperCommand(t, "cat") 91 | p.Stdin = strings.NewReader(input) 92 | bs, err := p.Output() 93 | if err != nil { 94 | t.Errorf("cat: %v", err) 95 | } 96 | s := string(bs) 97 | if s != input { 98 | t.Errorf("cat: want %q, got %q", input, s) 99 | } 100 | } 101 | 102 | func TestCatGoodAndBadFile(t *testing.T) { 103 | // Testing combined output and error values. 104 | bs, err := helperCommand(t, "cat", "/bogus/file.foo", "osexec_test.go").CombinedOutput() 105 | if _, ok := err.(*osexec.ExitError); !ok { 106 | t.Errorf("expected *exec.ExitError from cat combined; got %T: %v", err, err) 107 | } 108 | s := string(bs) 109 | sp := strings.SplitN(s, "\n", 2) 110 | if len(sp) != 2 { 111 | t.Fatalf("expected two lines from cat; got %q", s) 112 | } 113 | errLine, body := sp[0], sp[1] 114 | if !strings.HasPrefix(errLine, "Error: open /bogus/file.foo") { 115 | t.Errorf("expected stderr to complain about file; got %q", errLine) 116 | } 117 | if !strings.Contains(body, "func TestHelperProcess(t *testing.T)") { 118 | t.Errorf("expected test code; got %q (len %d)", body, len(body)) 119 | } 120 | } 121 | 122 | func TestNoExistBinary(t *testing.T) { 123 | // Can't run a non-existent binary 124 | err := exec.Command("/no-exist-binary").Run() 125 | if err == nil { 126 | t.Error("expected error from /no-exist-binary") 127 | } 128 | } 129 | 130 | func TestExitStatus(t *testing.T) { 131 | // Test that exit values are returned correctly 132 | cmd := helperCommand(t, "exit", "42") 133 | err := cmd.Run() 134 | want := "exit status 42" 135 | switch runtime.GOOS { 136 | case "plan9": 137 | want = fmt.Sprintf("exit status: '%s %d: 42'", filepath.Base(cmd.Path), cmd.ProcessState.Pid()) 138 | } 139 | if werr, ok := err.(*osexec.ExitError); ok { 140 | if s := werr.Error(); s != want { 141 | t.Errorf("from exit 42 got exit %q, want %q", s, want) 142 | } 143 | } else { 144 | t.Fatalf("expected *exec.ExitError from exit 42; got %T: %v", err, err) 145 | } 146 | } 147 | 148 | func TestPipes(t *testing.T) { 149 | check := func(what string, err error) { 150 | if err != nil { 151 | t.Fatalf("%s: %v", what, err) 152 | } 153 | } 154 | // Cat, testing stdin and stdout. 155 | c := helperCommand(t, "pipetest") 156 | stdin, err := c.StdinPipe() 157 | check("StdinPipe", err) 158 | stdout, err := c.StdoutPipe() 159 | check("StdoutPipe", err) 160 | stderr, err := c.StderrPipe() 161 | check("StderrPipe", err) 162 | 163 | outbr := bufio.NewReader(stdout) 164 | errbr := bufio.NewReader(stderr) 165 | line := func(what string, br *bufio.Reader) string { 166 | line, _, err := br.ReadLine() 167 | if err != nil { 168 | t.Fatalf("%s: %v", what, err) 169 | } 170 | return string(line) 171 | } 172 | 173 | err = c.Start() 174 | check("Start", err) 175 | 176 | _, err = stdin.Write([]byte("O:I am output\n")) 177 | check("first stdin Write", err) 178 | if g, e := line("first output line", outbr), "O:I am output"; g != e { 179 | t.Errorf("got %q, want %q", g, e) 180 | } 181 | 182 | _, err = stdin.Write([]byte("E:I am error\n")) 183 | check("second stdin Write", err) 184 | if g, e := line("first error line", errbr), "E:I am error"; g != e { 185 | t.Errorf("got %q, want %q", g, e) 186 | } 187 | 188 | _, err = stdin.Write([]byte("O:I am output2\n")) 189 | check("third stdin Write 3", err) 190 | if g, e := line("second output line", outbr), "O:I am output2"; g != e { 191 | t.Errorf("got %q, want %q", g, e) 192 | } 193 | 194 | stdin.Close() 195 | err = c.Wait() 196 | check("Wait", err) 197 | } 198 | 199 | const stdinCloseTestString = "Some test string." 200 | 201 | // Issue 6270. 202 | func TestStdinClose(t *testing.T) { 203 | check := func(what string, err error) { 204 | if err != nil { 205 | t.Fatalf("%s: %v", what, err) 206 | } 207 | } 208 | cmd := helperCommand(t, "stdinClose") 209 | stdin, err := cmd.StdinPipe() 210 | check("StdinPipe", err) 211 | // Check that we can access methods of the underlying os.File.` 212 | if _, ok := stdin.(interface { 213 | Fd() uintptr 214 | }); !ok { 215 | t.Error("can't access methods of underlying *os.File") 216 | } 217 | check("Start", cmd.Start()) 218 | go func() { 219 | _, err := io.Copy(stdin, strings.NewReader(stdinCloseTestString)) 220 | check("Copy", err) 221 | // Before the fix, this next line would race with cmd.Wait. 222 | check("Close", stdin.Close()) 223 | }() 224 | check("Wait", cmd.Wait()) 225 | } 226 | 227 | // Issue 5071 228 | func TestPipeLookPathLeak(t *testing.T) { 229 | fd0, lsof0 := numOpenFDS(t) 230 | for i := 0; i < 4; i++ { 231 | cmd := exec.Command("something-that-does-not-exist-binary") 232 | cmd.StdoutPipe() 233 | cmd.StderrPipe() 234 | cmd.StdinPipe() 235 | if err := cmd.Run(); err == nil { 236 | t.Fatal("unexpected success") 237 | } 238 | } 239 | for triesLeft := 3; triesLeft >= 0; triesLeft-- { 240 | open, lsof := numOpenFDS(t) 241 | fdGrowth := open - fd0 242 | if fdGrowth > 2 { 243 | if triesLeft > 0 { 244 | // Work around what appears to be a race with Linux's 245 | // proc filesystem (as used by lsof). It seems to only 246 | // be eventually consistent. Give it awhile to settle. 247 | // See golang.org/issue/7808 248 | time.Sleep(100 * time.Millisecond) 249 | continue 250 | } 251 | t.Errorf("leaked %d fds; want ~0; have:\n%s\noriginally:\n%s", fdGrowth, lsof, lsof0) 252 | } 253 | break 254 | } 255 | } 256 | 257 | func numOpenFDS(t *testing.T) (n int, lsof []byte) { 258 | if runtime.GOOS == "android" { 259 | // Android's stock lsof does not obey the -p option, 260 | // so extra filtering is needed. (golang.org/issue/10206) 261 | return numOpenFDsAndroid(t) 262 | } 263 | 264 | lsof, err := exec.Command("lsof", "-b", "-n", "-p", strconv.Itoa(os.Getpid())).Output() 265 | if err != nil { 266 | t.Skip("skipping test; error finding or running lsof") 267 | } 268 | return bytes.Count(lsof, []byte("\n")), lsof 269 | } 270 | 271 | func numOpenFDsAndroid(t *testing.T) (n int, lsof []byte) { 272 | raw, err := exec.Command("lsof").Output() 273 | if err != nil { 274 | t.Skip("skipping test; error finding or running lsof") 275 | } 276 | 277 | // First find the PID column index by parsing the first line, and 278 | // select lines containing pid in the column. 279 | pid := []byte(strconv.Itoa(os.Getpid())) 280 | pidCol := -1 281 | 282 | s := bufio.NewScanner(bytes.NewReader(raw)) 283 | for s.Scan() { 284 | line := s.Bytes() 285 | fields := bytes.Fields(line) 286 | if pidCol < 0 { 287 | for i, v := range fields { 288 | if bytes.Equal(v, []byte("PID")) { 289 | pidCol = i 290 | break 291 | } 292 | } 293 | lsof = append(lsof, line...) 294 | continue 295 | } 296 | if bytes.Equal(fields[pidCol], pid) { 297 | lsof = append(lsof, '\n') 298 | lsof = append(lsof, line...) 299 | } 300 | } 301 | if pidCol < 0 { 302 | t.Fatal("error processing lsof output: unexpected header format") 303 | } 304 | if err := s.Err(); err != nil { 305 | t.Fatalf("error processing lsof output: %v", err) 306 | } 307 | return bytes.Count(lsof, []byte("\n")), lsof 308 | } 309 | 310 | var testedAlreadyLeaked = false 311 | 312 | // basefds returns the number of expected file descriptors 313 | // to be present in a process at start. 314 | func basefds() uintptr { 315 | return os.Stderr.Fd() + 1 316 | } 317 | 318 | func closeUnexpectedFds(t *testing.T, m string) { 319 | for fd := basefds(); fd <= 101; fd++ { 320 | err := os.NewFile(fd, "").Close() 321 | if err == nil { 322 | t.Logf("%s: Something already leaked - closed fd %d", m, fd) 323 | } 324 | } 325 | } 326 | 327 | func TestExtraFilesFDShuffle(t *testing.T) { 328 | t.Skip("flaky test; see http://golang.org/issue/5780") 329 | switch runtime.GOOS { 330 | case "darwin": 331 | // TODO(cnicolaou): http://golang.org/issue/2603 332 | // leads to leaked file descriptors in this test when it's 333 | // run from a builder. 334 | closeUnexpectedFds(t, "TestExtraFilesFDShuffle") 335 | case "netbsd": 336 | // http://golang.org/issue/3955 337 | closeUnexpectedFds(t, "TestExtraFilesFDShuffle") 338 | case "windows": 339 | t.Skip("no operating system support; skipping") 340 | } 341 | 342 | // syscall.StartProcess maps all the FDs passed to it in 343 | // ProcAttr.Files (the concatenation of stdin,stdout,stderr and 344 | // ExtraFiles) into consecutive FDs in the child, that is: 345 | // Files{11, 12, 6, 7, 9, 3} should result in the file 346 | // represented by FD 11 in the parent being made available as 0 347 | // in the child, 12 as 1, etc. 348 | // 349 | // We want to test that FDs in the child do not get overwritten 350 | // by one another as this shuffle occurs. The original implementation 351 | // was buggy in that in some data dependent cases it would ovewrite 352 | // stderr in the child with one of the ExtraFile members. 353 | // Testing for this case is difficult because it relies on using 354 | // the same FD values as that case. In particular, an FD of 3 355 | // must be at an index of 4 or higher in ProcAttr.Files and 356 | // the FD of the write end of the Stderr pipe (as obtained by 357 | // StderrPipe()) must be the same as the size of ProcAttr.Files; 358 | // therefore we test that the read end of this pipe (which is what 359 | // is returned to the parent by StderrPipe() being one less than 360 | // the size of ProcAttr.Files, i.e. 3+len(cmd.ExtraFiles). 361 | // 362 | // Moving this test case around within the overall tests may 363 | // affect the FDs obtained and hence the checks to catch these cases. 364 | npipes := 2 365 | c := helperCommand(t, "extraFilesAndPipes", strconv.Itoa(npipes+1)) 366 | rd, wr, _ := os.Pipe() 367 | defer rd.Close() 368 | if rd.Fd() != 3 { 369 | t.Errorf("bad test value for test pipe: fd %d", rd.Fd()) 370 | } 371 | stderr, _ := c.StderrPipe() 372 | wr.WriteString("_LAST") 373 | wr.Close() 374 | 375 | pipes := make([]struct { 376 | r, w *os.File 377 | }, npipes) 378 | data := []string{"a", "b"} 379 | 380 | for i := 0; i < npipes; i++ { 381 | r, w, err := os.Pipe() 382 | if err != nil { 383 | t.Fatalf("unexpected error creating pipe: %s", err) 384 | } 385 | pipes[i].r = r 386 | pipes[i].w = w 387 | w.WriteString(data[i]) 388 | c.ExtraFiles = append(c.ExtraFiles, pipes[i].r) 389 | defer func() { 390 | r.Close() 391 | w.Close() 392 | }() 393 | } 394 | // Put fd 3 at the end. 395 | c.ExtraFiles = append(c.ExtraFiles, rd) 396 | 397 | stderrFd := int(stderr.(*os.File).Fd()) 398 | if stderrFd != ((len(c.ExtraFiles) + 3) - 1) { 399 | t.Errorf("bad test value for stderr pipe") 400 | } 401 | 402 | expected := "child: " + strings.Join(data, "") + "_LAST" 403 | 404 | err := c.Start() 405 | if err != nil { 406 | t.Fatalf("Run: %v", err) 407 | } 408 | ch := make(chan string, 1) 409 | go func(ch chan string) { 410 | buf := make([]byte, 512) 411 | n, err := stderr.Read(buf) 412 | if err != nil { 413 | t.Fatalf("Read: %s", err) 414 | ch <- err.Error() 415 | } else { 416 | ch <- string(buf[:n]) 417 | } 418 | close(ch) 419 | }(ch) 420 | select { 421 | case m := <-ch: 422 | if m != expected { 423 | t.Errorf("Read: '%s' not '%s'", m, expected) 424 | } 425 | case <-time.After(5 * time.Second): 426 | t.Errorf("Read timedout") 427 | } 428 | c.Wait() 429 | } 430 | 431 | func TestExtraFiles(t *testing.T) { 432 | switch runtime.GOOS { 433 | case "nacl", "windows": 434 | t.Skipf("skipping test on %q", runtime.GOOS) 435 | } 436 | if iOS { 437 | t.Skipf("skipping test on %s/%s, cannot fork", runtime.GOOS, runtime.GOARCH) 438 | } 439 | 440 | // Ensure that file descriptors have not already been leaked into 441 | // our environment. 442 | if !testedAlreadyLeaked { 443 | testedAlreadyLeaked = true 444 | closeUnexpectedFds(t, "TestExtraFiles") 445 | } 446 | 447 | // Force network usage, to verify the epoll (or whatever) fd 448 | // doesn't leak to the child, 449 | ln, err := net.Listen("tcp", "127.0.0.1:0") 450 | if err != nil { 451 | t.Fatal(err) 452 | } 453 | defer ln.Close() 454 | 455 | // Make sure duplicated fds don't leak to the child. 456 | f, err := ln.(*net.TCPListener).File() 457 | if err != nil { 458 | t.Fatal(err) 459 | } 460 | defer f.Close() 461 | ln2, err := net.FileListener(f) 462 | if err != nil { 463 | t.Fatal(err) 464 | } 465 | defer ln2.Close() 466 | 467 | // Force TLS root certs to be loaded (which might involve 468 | // cgo), to make sure none of that potential C code leaks fds. 469 | ts := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})) 470 | // quiet expected TLS handshake error "remote error: bad certificate" 471 | ts.Config.ErrorLog = log.New(ioutil.Discard, "", 0) 472 | ts.StartTLS() 473 | defer ts.Close() 474 | _, err = http.Get(ts.URL) 475 | if err == nil { 476 | t.Errorf("success trying to fetch %s; want an error", ts.URL) 477 | } 478 | 479 | tf, err := ioutil.TempFile("", "") 480 | if err != nil { 481 | t.Fatalf("TempFile: %v", err) 482 | } 483 | defer os.Remove(tf.Name()) 484 | defer tf.Close() 485 | 486 | const text = "Hello, fd 3!" 487 | _, err = tf.Write([]byte(text)) 488 | if err != nil { 489 | t.Fatalf("Write: %v", err) 490 | } 491 | _, err = tf.Seek(0, os.SEEK_SET) 492 | if err != nil { 493 | t.Fatalf("Seek: %v", err) 494 | } 495 | 496 | c := helperCommand(t, "read3") 497 | var stdout, stderr bytes.Buffer 498 | c.Stdout = &stdout 499 | c.Stderr = &stderr 500 | c.ExtraFiles = []*os.File{tf} 501 | err = c.Run() 502 | if err != nil { 503 | t.Fatalf("Run: %v; stdout %q, stderr %q", err, stdout.Bytes(), stderr.Bytes()) 504 | } 505 | if stdout.String() != text { 506 | t.Errorf("got stdout %q, stderr %q; want %q on stdout", stdout.String(), stderr.String(), text) 507 | } 508 | } 509 | 510 | func TestExtraFilesRace(t *testing.T) { 511 | if runtime.GOOS == "windows" { 512 | t.Skip("no operating system support; skipping") 513 | } 514 | listen := func() net.Listener { 515 | ln, err := net.Listen("tcp", "127.0.0.1:0") 516 | if err != nil { 517 | t.Fatal(err) 518 | } 519 | return ln 520 | } 521 | listenerFile := func(ln net.Listener) *os.File { 522 | f, err := ln.(*net.TCPListener).File() 523 | if err != nil { 524 | t.Fatal(err) 525 | } 526 | return f 527 | } 528 | runCommand := func(c *exec.Cmd, out chan<- string) { 529 | bout, err := c.CombinedOutput() 530 | if err != nil { 531 | out <- "ERROR:" + err.Error() 532 | } else { 533 | out <- string(bout) 534 | } 535 | } 536 | 537 | for i := 0; i < 10; i++ { 538 | la := listen() 539 | ca := helperCommand(t, "describefiles") 540 | ca.ExtraFiles = []*os.File{listenerFile(la)} 541 | lb := listen() 542 | cb := helperCommand(t, "describefiles") 543 | cb.ExtraFiles = []*os.File{listenerFile(lb)} 544 | ares := make(chan string) 545 | bres := make(chan string) 546 | go runCommand(ca, ares) 547 | go runCommand(cb, bres) 548 | if got, want := <-ares, fmt.Sprintf("fd3: listener %s\n", la.Addr()); got != want { 549 | t.Errorf("iteration %d, process A got:\n%s\nwant:\n%s\n", i, got, want) 550 | } 551 | if got, want := <-bres, fmt.Sprintf("fd3: listener %s\n", lb.Addr()); got != want { 552 | t.Errorf("iteration %d, process B got:\n%s\nwant:\n%s\n", i, got, want) 553 | } 554 | la.Close() 555 | lb.Close() 556 | for _, f := range ca.ExtraFiles { 557 | f.Close() 558 | } 559 | for _, f := range cb.ExtraFiles { 560 | f.Close() 561 | } 562 | 563 | } 564 | } 565 | 566 | // TestHelperProcess isn't a real test. It's used as a helper process 567 | // for TestParameterRun. 568 | func TestHelperProcess(*testing.T) { 569 | if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" { 570 | return 571 | } 572 | defer os.Exit(0) 573 | 574 | // Determine which command to use to display open files. 575 | ofcmd := "lsof" 576 | switch runtime.GOOS { 577 | case "dragonfly", "freebsd", "netbsd", "openbsd": 578 | ofcmd = "fstat" 579 | case "plan9": 580 | ofcmd = "/bin/cat" 581 | } 582 | 583 | args := os.Args 584 | for len(args) > 0 { 585 | if args[0] == "--" { 586 | args = args[1:] 587 | break 588 | } 589 | args = args[1:] 590 | } 591 | if len(args) == 0 { 592 | fmt.Fprintf(os.Stderr, "No command\n") 593 | os.Exit(2) 594 | } 595 | 596 | cmd, args := args[0], args[1:] 597 | switch cmd { 598 | case "echo": 599 | iargs := []interface{}{} 600 | for _, s := range args { 601 | iargs = append(iargs, s) 602 | } 603 | fmt.Println(iargs...) 604 | case "cat": 605 | if len(args) == 0 { 606 | io.Copy(os.Stdout, os.Stdin) 607 | return 608 | } 609 | exit := 0 610 | for _, fn := range args { 611 | f, err := os.Open(fn) 612 | if err != nil { 613 | fmt.Fprintf(os.Stderr, "Error: %v\n", err) 614 | exit = 2 615 | } else { 616 | defer f.Close() 617 | io.Copy(os.Stdout, f) 618 | } 619 | } 620 | os.Exit(exit) 621 | case "pipetest": 622 | bufr := bufio.NewReader(os.Stdin) 623 | for { 624 | line, _, err := bufr.ReadLine() 625 | if err == io.EOF { 626 | break 627 | } else if err != nil { 628 | os.Exit(1) 629 | } 630 | if bytes.HasPrefix(line, []byte("O:")) { 631 | os.Stdout.Write(line) 632 | os.Stdout.Write([]byte{'\n'}) 633 | } else if bytes.HasPrefix(line, []byte("E:")) { 634 | os.Stderr.Write(line) 635 | os.Stderr.Write([]byte{'\n'}) 636 | } else { 637 | os.Exit(1) 638 | } 639 | } 640 | case "stdinClose": 641 | b, err := ioutil.ReadAll(os.Stdin) 642 | if err != nil { 643 | fmt.Fprintf(os.Stderr, "Error: %v\n", err) 644 | os.Exit(1) 645 | } 646 | if s := string(b); s != stdinCloseTestString { 647 | fmt.Fprintf(os.Stderr, "Error: Read %q, want %q", s, stdinCloseTestString) 648 | os.Exit(1) 649 | } 650 | os.Exit(0) 651 | case "read3": // read fd 3 652 | fd3 := os.NewFile(3, "fd3") 653 | bs, err := ioutil.ReadAll(fd3) 654 | if err != nil { 655 | fmt.Printf("ReadAll from fd 3: %v", err) 656 | os.Exit(1) 657 | } 658 | switch runtime.GOOS { 659 | case "dragonfly": 660 | // TODO(jsing): Determine why DragonFly is leaking 661 | // file descriptors... 662 | case "darwin": 663 | // TODO(bradfitz): broken? Sometimes. 664 | // http://golang.org/issue/2603 665 | // Skip this additional part of the test for now. 666 | case "netbsd": 667 | // TODO(jsing): This currently fails on NetBSD due to 668 | // the cloned file descriptors that result from opening 669 | // /dev/urandom. 670 | // http://golang.org/issue/3955 671 | case "plan9": 672 | // TODO(0intro): Determine why Plan 9 is leaking 673 | // file descriptors. 674 | // http://golang.org/issue/7118 675 | case "solaris": 676 | // TODO(aram): This fails on Solaris because libc opens 677 | // its own files, as it sees fit. Darwin does the same, 678 | // see: http://golang.org/issue/2603 679 | default: 680 | // Now verify that there are no other open fds. 681 | var files []*os.File 682 | for wantfd := basefds() + 1; wantfd <= 100; wantfd++ { 683 | f, err := os.Open(os.Args[0]) 684 | if err != nil { 685 | fmt.Printf("error opening file with expected fd %d: %v", wantfd, err) 686 | os.Exit(1) 687 | } 688 | if got := f.Fd(); got != wantfd { 689 | fmt.Printf("leaked parent file. fd = %d; want %d\n", got, wantfd) 690 | var args []string 691 | switch runtime.GOOS { 692 | case "plan9": 693 | args = []string{fmt.Sprintf("/proc/%d/fd", os.Getpid())} 694 | default: 695 | args = []string{"-p", fmt.Sprint(os.Getpid())} 696 | } 697 | out, _ := exec.Command(ofcmd, args...).CombinedOutput() 698 | fmt.Print(string(out)) 699 | os.Exit(1) 700 | } 701 | files = append(files, f) 702 | } 703 | for _, f := range files { 704 | f.Close() 705 | } 706 | } 707 | // Referring to fd3 here ensures that it is not 708 | // garbage collected, and therefore closed, while 709 | // executing the wantfd loop above. It doesn't matter 710 | // what we do with fd3 as long as we refer to it; 711 | // closing it is the easy choice. 712 | fd3.Close() 713 | os.Stdout.Write(bs) 714 | case "exit": 715 | n, _ := strconv.Atoi(args[0]) 716 | os.Exit(n) 717 | case "describefiles": 718 | f := os.NewFile(3, fmt.Sprintf("fd3")) 719 | ln, err := net.FileListener(f) 720 | if err == nil { 721 | fmt.Printf("fd3: listener %s\n", ln.Addr()) 722 | ln.Close() 723 | } 724 | os.Exit(0) 725 | case "extraFilesAndPipes": 726 | n, _ := strconv.Atoi(args[0]) 727 | pipes := make([]*os.File, n) 728 | for i := 0; i < n; i++ { 729 | pipes[i] = os.NewFile(uintptr(3+i), strconv.Itoa(i)) 730 | } 731 | response := "" 732 | for i, r := range pipes { 733 | ch := make(chan string, 1) 734 | go func(c chan string) { 735 | buf := make([]byte, 10) 736 | n, err := r.Read(buf) 737 | if err != nil { 738 | fmt.Fprintf(os.Stderr, "Child: read error: %v on pipe %d\n", err, i) 739 | os.Exit(1) 740 | } 741 | c <- string(buf[:n]) 742 | close(c) 743 | }(ch) 744 | select { 745 | case m := <-ch: 746 | response = response + m 747 | case <-time.After(5 * time.Second): 748 | fmt.Fprintf(os.Stderr, "Child: Timeout reading from pipe: %d\n", i) 749 | os.Exit(1) 750 | } 751 | } 752 | fmt.Fprintf(os.Stderr, "child: %s", response) 753 | os.Exit(0) 754 | case "exec": 755 | cmd := exec.Command(args[1]) 756 | cmd.Dir = args[0] 757 | output, err := cmd.CombinedOutput() 758 | if err != nil { 759 | fmt.Fprintf(os.Stderr, "Child: %s %s", err, string(output)) 760 | os.Exit(1) 761 | } 762 | fmt.Printf("%s", string(output)) 763 | os.Exit(0) 764 | case "lookpath": 765 | p, err := exec.LookPath(args[0]) 766 | if err != nil { 767 | fmt.Fprintf(os.Stderr, "LookPath failed: %v\n", err) 768 | os.Exit(1) 769 | } 770 | fmt.Print(p) 771 | os.Exit(0) 772 | default: 773 | fmt.Fprintf(os.Stderr, "Unknown command %q\n", cmd) 774 | os.Exit(2) 775 | } 776 | } 777 | -------------------------------------------------------------------------------- /wercker.yml: -------------------------------------------------------------------------------- 1 | box: wercker/golang 2 | --------------------------------------------------------------------------------