github.com/ccccaoqing/test@v0.0.0-20220510085219-3985d23445c0/src/os/exec/exec_test.go (about) 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 "os/exec" 22 "path/filepath" 23 "runtime" 24 "strconv" 25 "strings" 26 "testing" 27 "time" 28 ) 29 30 func helperCommand(t *testing.T, s ...string) *exec.Cmd { 31 if runtime.GOOS == "nacl" { 32 t.Skip("skipping on nacl") 33 } 34 cs := []string{"-test.run=TestHelperProcess", "--"} 35 cs = append(cs, s...) 36 cmd := exec.Command(os.Args[0], cs...) 37 cmd.Env = []string{"GO_WANT_HELPER_PROCESS=1"} 38 return cmd 39 } 40 41 func TestEcho(t *testing.T) { 42 bs, err := helperCommand(t, "echo", "foo bar", "baz").Output() 43 if err != nil { 44 t.Errorf("echo: %v", err) 45 } 46 if g, e := string(bs), "foo bar baz\n"; g != e { 47 t.Errorf("echo: want %q, got %q", e, g) 48 } 49 } 50 51 func TestCommandRelativeName(t *testing.T) { 52 // Run our own binary as a relative path 53 // (e.g. "_test/exec.test") our parent directory. 54 base := filepath.Base(os.Args[0]) // "exec.test" 55 dir := filepath.Dir(os.Args[0]) // "/tmp/go-buildNNNN/os/exec/_test" 56 if dir == "." { 57 t.Skip("skipping; running test at root somehow") 58 } 59 parentDir := filepath.Dir(dir) // "/tmp/go-buildNNNN/os/exec" 60 dirBase := filepath.Base(dir) // "_test" 61 if dirBase == "." { 62 t.Skipf("skipping; unexpected shallow dir of %q", dir) 63 } 64 65 cmd := exec.Command(filepath.Join(dirBase, base), "-test.run=TestHelperProcess", "--", "echo", "foo") 66 cmd.Dir = parentDir 67 cmd.Env = []string{"GO_WANT_HELPER_PROCESS=1"} 68 69 out, err := cmd.Output() 70 if err != nil { 71 t.Errorf("echo: %v", err) 72 } 73 if g, e := string(out), "foo\n"; g != e { 74 t.Errorf("echo: want %q, got %q", e, g) 75 } 76 } 77 78 func TestCatStdin(t *testing.T) { 79 // Cat, testing stdin and stdout. 80 input := "Input string\nLine 2" 81 p := helperCommand(t, "cat") 82 p.Stdin = strings.NewReader(input) 83 bs, err := p.Output() 84 if err != nil { 85 t.Errorf("cat: %v", err) 86 } 87 s := string(bs) 88 if s != input { 89 t.Errorf("cat: want %q, got %q", input, s) 90 } 91 } 92 93 func TestCatGoodAndBadFile(t *testing.T) { 94 // Testing combined output and error values. 95 bs, err := helperCommand(t, "cat", "/bogus/file.foo", "exec_test.go").CombinedOutput() 96 if _, ok := err.(*exec.ExitError); !ok { 97 t.Errorf("expected *exec.ExitError from cat combined; got %T: %v", err, err) 98 } 99 s := string(bs) 100 sp := strings.SplitN(s, "\n", 2) 101 if len(sp) != 2 { 102 t.Fatalf("expected two lines from cat; got %q", s) 103 } 104 errLine, body := sp[0], sp[1] 105 if !strings.HasPrefix(errLine, "Error: open /bogus/file.foo") { 106 t.Errorf("expected stderr to complain about file; got %q", errLine) 107 } 108 if !strings.Contains(body, "func TestHelperProcess(t *testing.T)") { 109 t.Errorf("expected test code; got %q (len %d)", body, len(body)) 110 } 111 } 112 113 func TestNoExistBinary(t *testing.T) { 114 // Can't run a non-existent binary 115 err := exec.Command("/no-exist-binary").Run() 116 if err == nil { 117 t.Error("expected error from /no-exist-binary") 118 } 119 } 120 121 func TestExitStatus(t *testing.T) { 122 // Test that exit values are returned correctly 123 cmd := helperCommand(t, "exit", "42") 124 err := cmd.Run() 125 want := "exit status 42" 126 switch runtime.GOOS { 127 case "plan9": 128 want = fmt.Sprintf("exit status: '%s %d: 42'", filepath.Base(cmd.Path), cmd.ProcessState.Pid()) 129 } 130 if werr, ok := err.(*exec.ExitError); ok { 131 if s := werr.Error(); s != want { 132 t.Errorf("from exit 42 got exit %q, want %q", s, want) 133 } 134 } else { 135 t.Fatalf("expected *exec.ExitError from exit 42; got %T: %v", err, err) 136 } 137 } 138 139 func TestPipes(t *testing.T) { 140 check := func(what string, err error) { 141 if err != nil { 142 t.Fatalf("%s: %v", what, err) 143 } 144 } 145 // Cat, testing stdin and stdout. 146 c := helperCommand(t, "pipetest") 147 stdin, err := c.StdinPipe() 148 check("StdinPipe", err) 149 stdout, err := c.StdoutPipe() 150 check("StdoutPipe", err) 151 stderr, err := c.StderrPipe() 152 check("StderrPipe", err) 153 154 outbr := bufio.NewReader(stdout) 155 errbr := bufio.NewReader(stderr) 156 line := func(what string, br *bufio.Reader) string { 157 line, _, err := br.ReadLine() 158 if err != nil { 159 t.Fatalf("%s: %v", what, err) 160 } 161 return string(line) 162 } 163 164 err = c.Start() 165 check("Start", err) 166 167 _, err = stdin.Write([]byte("O:I am output\n")) 168 check("first stdin Write", err) 169 if g, e := line("first output line", outbr), "O:I am output"; g != e { 170 t.Errorf("got %q, want %q", g, e) 171 } 172 173 _, err = stdin.Write([]byte("E:I am error\n")) 174 check("second stdin Write", err) 175 if g, e := line("first error line", errbr), "E:I am error"; g != e { 176 t.Errorf("got %q, want %q", g, e) 177 } 178 179 _, err = stdin.Write([]byte("O:I am output2\n")) 180 check("third stdin Write 3", err) 181 if g, e := line("second output line", outbr), "O:I am output2"; g != e { 182 t.Errorf("got %q, want %q", g, e) 183 } 184 185 stdin.Close() 186 err = c.Wait() 187 check("Wait", err) 188 } 189 190 const stdinCloseTestString = "Some test string." 191 192 // Issue 6270. 193 func TestStdinClose(t *testing.T) { 194 check := func(what string, err error) { 195 if err != nil { 196 t.Fatalf("%s: %v", what, err) 197 } 198 } 199 cmd := helperCommand(t, "stdinClose") 200 stdin, err := cmd.StdinPipe() 201 check("StdinPipe", err) 202 // Check that we can access methods of the underlying os.File.` 203 if _, ok := stdin.(interface { 204 Fd() uintptr 205 }); !ok { 206 t.Error("can't access methods of underlying *os.File") 207 } 208 check("Start", cmd.Start()) 209 go func() { 210 _, err := io.Copy(stdin, strings.NewReader(stdinCloseTestString)) 211 check("Copy", err) 212 // Before the fix, this next line would race with cmd.Wait. 213 check("Close", stdin.Close()) 214 }() 215 check("Wait", cmd.Wait()) 216 } 217 218 // Issue 5071 219 func TestPipeLookPathLeak(t *testing.T) { 220 fd0, lsof0 := numOpenFDS(t) 221 for i := 0; i < 4; i++ { 222 cmd := exec.Command("something-that-does-not-exist-binary") 223 cmd.StdoutPipe() 224 cmd.StderrPipe() 225 cmd.StdinPipe() 226 if err := cmd.Run(); err == nil { 227 t.Fatal("unexpected success") 228 } 229 } 230 for triesLeft := 3; triesLeft >= 0; triesLeft-- { 231 open, lsof := numOpenFDS(t) 232 fdGrowth := open - fd0 233 if fdGrowth > 2 { 234 if triesLeft > 0 { 235 // Work around what appears to be a race with Linux's 236 // proc filesystem (as used by lsof). It seems to only 237 // be eventually consistent. Give it awhile to settle. 238 // See golang.org/issue/7808 239 time.Sleep(100 * time.Millisecond) 240 continue 241 } 242 t.Errorf("leaked %d fds; want ~0; have:\n%s\noriginally:\n%s", fdGrowth, lsof, lsof0) 243 } 244 break 245 } 246 } 247 248 func numOpenFDS(t *testing.T) (n int, lsof []byte) { 249 lsof, err := exec.Command("lsof", "-b", "-n", "-p", strconv.Itoa(os.Getpid())).Output() 250 if err != nil { 251 t.Skip("skipping test; error finding or running lsof") 252 } 253 return bytes.Count(lsof, []byte("\n")), lsof 254 } 255 256 var testedAlreadyLeaked = false 257 258 // basefds returns the number of expected file descriptors 259 // to be present in a process at start. 260 func basefds() uintptr { 261 return os.Stderr.Fd() + 1 262 } 263 264 func closeUnexpectedFds(t *testing.T, m string) { 265 for fd := basefds(); fd <= 101; fd++ { 266 err := os.NewFile(fd, "").Close() 267 if err == nil { 268 t.Logf("%s: Something already leaked - closed fd %d", m, fd) 269 } 270 } 271 } 272 273 func TestExtraFilesFDShuffle(t *testing.T) { 274 t.Skip("flaky test; see http://golang.org/issue/5780") 275 switch runtime.GOOS { 276 case "darwin": 277 // TODO(cnicolaou): http://golang.org/issue/2603 278 // leads to leaked file descriptors in this test when it's 279 // run from a builder. 280 closeUnexpectedFds(t, "TestExtraFilesFDShuffle") 281 case "netbsd": 282 // http://golang.org/issue/3955 283 closeUnexpectedFds(t, "TestExtraFilesFDShuffle") 284 case "windows": 285 t.Skip("no operating system support; skipping") 286 } 287 288 // syscall.StartProcess maps all the FDs passed to it in 289 // ProcAttr.Files (the concatenation of stdin,stdout,stderr and 290 // ExtraFiles) into consecutive FDs in the child, that is: 291 // Files{11, 12, 6, 7, 9, 3} should result in the file 292 // represented by FD 11 in the parent being made available as 0 293 // in the child, 12 as 1, etc. 294 // 295 // We want to test that FDs in the child do not get overwritten 296 // by one another as this shuffle occurs. The original implementation 297 // was buggy in that in some data dependent cases it would ovewrite 298 // stderr in the child with one of the ExtraFile members. 299 // Testing for this case is difficult because it relies on using 300 // the same FD values as that case. In particular, an FD of 3 301 // must be at an index of 4 or higher in ProcAttr.Files and 302 // the FD of the write end of the Stderr pipe (as obtained by 303 // StderrPipe()) must be the same as the size of ProcAttr.Files; 304 // therefore we test that the read end of this pipe (which is what 305 // is returned to the parent by StderrPipe() being one less than 306 // the size of ProcAttr.Files, i.e. 3+len(cmd.ExtraFiles). 307 // 308 // Moving this test case around within the overall tests may 309 // affect the FDs obtained and hence the checks to catch these cases. 310 npipes := 2 311 c := helperCommand(t, "extraFilesAndPipes", strconv.Itoa(npipes+1)) 312 rd, wr, _ := os.Pipe() 313 defer rd.Close() 314 if rd.Fd() != 3 { 315 t.Errorf("bad test value for test pipe: fd %d", rd.Fd()) 316 } 317 stderr, _ := c.StderrPipe() 318 wr.WriteString("_LAST") 319 wr.Close() 320 321 pipes := make([]struct { 322 r, w *os.File 323 }, npipes) 324 data := []string{"a", "b"} 325 326 for i := 0; i < npipes; i++ { 327 r, w, err := os.Pipe() 328 if err != nil { 329 t.Fatalf("unexpected error creating pipe: %s", err) 330 } 331 pipes[i].r = r 332 pipes[i].w = w 333 w.WriteString(data[i]) 334 c.ExtraFiles = append(c.ExtraFiles, pipes[i].r) 335 defer func() { 336 r.Close() 337 w.Close() 338 }() 339 } 340 // Put fd 3 at the end. 341 c.ExtraFiles = append(c.ExtraFiles, rd) 342 343 stderrFd := int(stderr.(*os.File).Fd()) 344 if stderrFd != ((len(c.ExtraFiles) + 3) - 1) { 345 t.Errorf("bad test value for stderr pipe") 346 } 347 348 expected := "child: " + strings.Join(data, "") + "_LAST" 349 350 err := c.Start() 351 if err != nil { 352 t.Fatalf("Run: %v", err) 353 } 354 ch := make(chan string, 1) 355 go func(ch chan string) { 356 buf := make([]byte, 512) 357 n, err := stderr.Read(buf) 358 if err != nil { 359 t.Fatalf("Read: %s", err) 360 ch <- err.Error() 361 } else { 362 ch <- string(buf[:n]) 363 } 364 close(ch) 365 }(ch) 366 select { 367 case m := <-ch: 368 if m != expected { 369 t.Errorf("Read: '%s' not '%s'", m, expected) 370 } 371 case <-time.After(5 * time.Second): 372 t.Errorf("Read timedout") 373 } 374 c.Wait() 375 } 376 377 func TestExtraFiles(t *testing.T) { 378 switch runtime.GOOS { 379 case "nacl", "windows": 380 t.Skipf("skipping test on %q", runtime.GOOS) 381 } 382 383 // Ensure that file descriptors have not already been leaked into 384 // our environment. 385 if !testedAlreadyLeaked { 386 testedAlreadyLeaked = true 387 closeUnexpectedFds(t, "TestExtraFiles") 388 } 389 390 // Force network usage, to verify the epoll (or whatever) fd 391 // doesn't leak to the child, 392 ln, err := net.Listen("tcp", "127.0.0.1:0") 393 if err != nil { 394 t.Fatal(err) 395 } 396 defer ln.Close() 397 398 // Make sure duplicated fds don't leak to the child. 399 f, err := ln.(*net.TCPListener).File() 400 if err != nil { 401 t.Fatal(err) 402 } 403 defer f.Close() 404 ln2, err := net.FileListener(f) 405 if err != nil { 406 t.Fatal(err) 407 } 408 defer ln2.Close() 409 410 // Force TLS root certs to be loaded (which might involve 411 // cgo), to make sure none of that potential C code leaks fds. 412 ts := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})) 413 // quiet expected TLS handshake error "remote error: bad certificate" 414 ts.Config.ErrorLog = log.New(ioutil.Discard, "", 0) 415 ts.StartTLS() 416 defer ts.Close() 417 _, err = http.Get(ts.URL) 418 if err == nil { 419 t.Errorf("success trying to fetch %s; want an error", ts.URL) 420 } 421 422 tf, err := ioutil.TempFile("", "") 423 if err != nil { 424 t.Fatalf("TempFile: %v", err) 425 } 426 defer os.Remove(tf.Name()) 427 defer tf.Close() 428 429 const text = "Hello, fd 3!" 430 _, err = tf.Write([]byte(text)) 431 if err != nil { 432 t.Fatalf("Write: %v", err) 433 } 434 _, err = tf.Seek(0, os.SEEK_SET) 435 if err != nil { 436 t.Fatalf("Seek: %v", err) 437 } 438 439 c := helperCommand(t, "read3") 440 var stdout, stderr bytes.Buffer 441 c.Stdout = &stdout 442 c.Stderr = &stderr 443 c.ExtraFiles = []*os.File{tf} 444 err = c.Run() 445 if err != nil { 446 t.Fatalf("Run: %v; stdout %q, stderr %q", err, stdout.Bytes(), stderr.Bytes()) 447 } 448 if stdout.String() != text { 449 t.Errorf("got stdout %q, stderr %q; want %q on stdout", stdout.String(), stderr.String(), text) 450 } 451 } 452 453 func TestExtraFilesRace(t *testing.T) { 454 if runtime.GOOS == "windows" { 455 t.Skip("no operating system support; skipping") 456 } 457 listen := func() net.Listener { 458 ln, err := net.Listen("tcp", "127.0.0.1:0") 459 if err != nil { 460 t.Fatal(err) 461 } 462 return ln 463 } 464 listenerFile := func(ln net.Listener) *os.File { 465 f, err := ln.(*net.TCPListener).File() 466 if err != nil { 467 t.Fatal(err) 468 } 469 return f 470 } 471 runCommand := func(c *exec.Cmd, out chan<- string) { 472 bout, err := c.CombinedOutput() 473 if err != nil { 474 out <- "ERROR:" + err.Error() 475 } else { 476 out <- string(bout) 477 } 478 } 479 480 for i := 0; i < 10; i++ { 481 la := listen() 482 ca := helperCommand(t, "describefiles") 483 ca.ExtraFiles = []*os.File{listenerFile(la)} 484 lb := listen() 485 cb := helperCommand(t, "describefiles") 486 cb.ExtraFiles = []*os.File{listenerFile(lb)} 487 ares := make(chan string) 488 bres := make(chan string) 489 go runCommand(ca, ares) 490 go runCommand(cb, bres) 491 if got, want := <-ares, fmt.Sprintf("fd3: listener %s\n", la.Addr()); got != want { 492 t.Errorf("iteration %d, process A got:\n%s\nwant:\n%s\n", i, got, want) 493 } 494 if got, want := <-bres, fmt.Sprintf("fd3: listener %s\n", lb.Addr()); got != want { 495 t.Errorf("iteration %d, process B got:\n%s\nwant:\n%s\n", i, got, want) 496 } 497 la.Close() 498 lb.Close() 499 for _, f := range ca.ExtraFiles { 500 f.Close() 501 } 502 for _, f := range cb.ExtraFiles { 503 f.Close() 504 } 505 506 } 507 } 508 509 // TestHelperProcess isn't a real test. It's used as a helper process 510 // for TestParameterRun. 511 func TestHelperProcess(*testing.T) { 512 if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" { 513 return 514 } 515 defer os.Exit(0) 516 517 // Determine which command to use to display open files. 518 ofcmd := "lsof" 519 switch runtime.GOOS { 520 case "dragonfly", "freebsd", "netbsd", "openbsd": 521 ofcmd = "fstat" 522 case "plan9": 523 ofcmd = "/bin/cat" 524 } 525 526 args := os.Args 527 for len(args) > 0 { 528 if args[0] == "--" { 529 args = args[1:] 530 break 531 } 532 args = args[1:] 533 } 534 if len(args) == 0 { 535 fmt.Fprintf(os.Stderr, "No command\n") 536 os.Exit(2) 537 } 538 539 cmd, args := args[0], args[1:] 540 switch cmd { 541 case "echo": 542 iargs := []interface{}{} 543 for _, s := range args { 544 iargs = append(iargs, s) 545 } 546 fmt.Println(iargs...) 547 case "cat": 548 if len(args) == 0 { 549 io.Copy(os.Stdout, os.Stdin) 550 return 551 } 552 exit := 0 553 for _, fn := range args { 554 f, err := os.Open(fn) 555 if err != nil { 556 fmt.Fprintf(os.Stderr, "Error: %v\n", err) 557 exit = 2 558 } else { 559 defer f.Close() 560 io.Copy(os.Stdout, f) 561 } 562 } 563 os.Exit(exit) 564 case "pipetest": 565 bufr := bufio.NewReader(os.Stdin) 566 for { 567 line, _, err := bufr.ReadLine() 568 if err == io.EOF { 569 break 570 } else if err != nil { 571 os.Exit(1) 572 } 573 if bytes.HasPrefix(line, []byte("O:")) { 574 os.Stdout.Write(line) 575 os.Stdout.Write([]byte{'\n'}) 576 } else if bytes.HasPrefix(line, []byte("E:")) { 577 os.Stderr.Write(line) 578 os.Stderr.Write([]byte{'\n'}) 579 } else { 580 os.Exit(1) 581 } 582 } 583 case "stdinClose": 584 b, err := ioutil.ReadAll(os.Stdin) 585 if err != nil { 586 fmt.Fprintf(os.Stderr, "Error: %v\n", err) 587 os.Exit(1) 588 } 589 if s := string(b); s != stdinCloseTestString { 590 fmt.Fprintf(os.Stderr, "Error: Read %q, want %q", s, stdinCloseTestString) 591 os.Exit(1) 592 } 593 os.Exit(0) 594 case "read3": // read fd 3 595 fd3 := os.NewFile(3, "fd3") 596 bs, err := ioutil.ReadAll(fd3) 597 if err != nil { 598 fmt.Printf("ReadAll from fd 3: %v", err) 599 os.Exit(1) 600 } 601 switch runtime.GOOS { 602 case "dragonfly": 603 // TODO(jsing): Determine why DragonFly is leaking 604 // file descriptors... 605 case "darwin": 606 // TODO(bradfitz): broken? Sometimes. 607 // http://golang.org/issue/2603 608 // Skip this additional part of the test for now. 609 case "netbsd": 610 // TODO(jsing): This currently fails on NetBSD due to 611 // the cloned file descriptors that result from opening 612 // /dev/urandom. 613 // http://golang.org/issue/3955 614 case "plan9": 615 // TODO(0intro): Determine why Plan 9 is leaking 616 // file descriptors. 617 // http://golang.org/issue/7118 618 case "solaris": 619 // TODO(aram): This fails on Solaris because libc opens 620 // its own files, as it sees fit. Darwin does the same, 621 // see: http://golang.org/issue/2603 622 default: 623 // Now verify that there are no other open fds. 624 var files []*os.File 625 for wantfd := basefds() + 1; wantfd <= 100; wantfd++ { 626 f, err := os.Open(os.Args[0]) 627 if err != nil { 628 fmt.Printf("error opening file with expected fd %d: %v", wantfd, err) 629 os.Exit(1) 630 } 631 if got := f.Fd(); got != wantfd { 632 fmt.Printf("leaked parent file. fd = %d; want %d\n", got, wantfd) 633 var args []string 634 switch runtime.GOOS { 635 case "plan9": 636 args = []string{fmt.Sprintf("/proc/%d/fd", os.Getpid())} 637 default: 638 args = []string{"-p", fmt.Sprint(os.Getpid())} 639 } 640 out, _ := exec.Command(ofcmd, args...).CombinedOutput() 641 fmt.Print(string(out)) 642 os.Exit(1) 643 } 644 files = append(files, f) 645 } 646 for _, f := range files { 647 f.Close() 648 } 649 } 650 // Referring to fd3 here ensures that it is not 651 // garbage collected, and therefore closed, while 652 // executing the wantfd loop above. It doesn't matter 653 // what we do with fd3 as long as we refer to it; 654 // closing it is the easy choice. 655 fd3.Close() 656 os.Stdout.Write(bs) 657 case "exit": 658 n, _ := strconv.Atoi(args[0]) 659 os.Exit(n) 660 case "describefiles": 661 f := os.NewFile(3, fmt.Sprintf("fd3")) 662 ln, err := net.FileListener(f) 663 if err == nil { 664 fmt.Printf("fd3: listener %s\n", ln.Addr()) 665 ln.Close() 666 } 667 os.Exit(0) 668 case "extraFilesAndPipes": 669 n, _ := strconv.Atoi(args[0]) 670 pipes := make([]*os.File, n) 671 for i := 0; i < n; i++ { 672 pipes[i] = os.NewFile(uintptr(3+i), strconv.Itoa(i)) 673 } 674 response := "" 675 for i, r := range pipes { 676 ch := make(chan string, 1) 677 go func(c chan string) { 678 buf := make([]byte, 10) 679 n, err := r.Read(buf) 680 if err != nil { 681 fmt.Fprintf(os.Stderr, "Child: read error: %v on pipe %d\n", err, i) 682 os.Exit(1) 683 } 684 c <- string(buf[:n]) 685 close(c) 686 }(ch) 687 select { 688 case m := <-ch: 689 response = response + m 690 case <-time.After(5 * time.Second): 691 fmt.Fprintf(os.Stderr, "Child: Timeout reading from pipe: %d\n", i) 692 os.Exit(1) 693 } 694 } 695 fmt.Fprintf(os.Stderr, "child: %s", response) 696 os.Exit(0) 697 case "exec": 698 cmd := exec.Command(args[1]) 699 cmd.Dir = args[0] 700 output, err := cmd.CombinedOutput() 701 if err != nil { 702 fmt.Fprintf(os.Stderr, "Child: %s %s", err, string(output)) 703 os.Exit(1) 704 } 705 fmt.Printf("%s", string(output)) 706 os.Exit(0) 707 case "lookpath": 708 p, err := exec.LookPath(args[0]) 709 if err != nil { 710 fmt.Fprintf(os.Stderr, "LookPath failed: %v\n", err) 711 os.Exit(1) 712 } 713 fmt.Print(p) 714 os.Exit(0) 715 default: 716 fmt.Fprintf(os.Stderr, "Unknown command %q\n", cmd) 717 os.Exit(2) 718 } 719 }