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