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