diff options
author | Ondřej Surý <ondrej@sury.org> | 2011-06-30 15:34:22 +0200 |
---|---|---|
committer | Ondřej Surý <ondrej@sury.org> | 2011-06-30 15:34:22 +0200 |
commit | d39f5aa373a4422f7a5f3ee764fb0f6b0b719d61 (patch) | |
tree | 1833f8b72a4b3a8f00d0d143b079a8fcad01c6ae /src/pkg/exec | |
parent | 8652e6c371b8905498d3d314491d36c58d5f68d5 (diff) | |
download | golang-upstream/58.tar.gz |
Imported Upstream version 58upstream/58
Diffstat (limited to 'src/pkg/exec')
-rw-r--r-- | src/pkg/exec/exec.go | 463 | ||||
-rw-r--r-- | src/pkg/exec/exec_test.go | 284 | ||||
-rw-r--r-- | src/pkg/exec/lp_test.go | 6 | ||||
-rw-r--r-- | src/pkg/exec/lp_unix.go | 21 | ||||
-rw-r--r-- | src/pkg/exec/lp_windows.go | 38 |
5 files changed, 519 insertions, 293 deletions
diff --git a/src/pkg/exec/exec.go b/src/pkg/exec/exec.go index 043f84728..935f24c21 100644 --- a/src/pkg/exec/exec.go +++ b/src/pkg/exec/exec.go @@ -7,198 +7,363 @@ // adjustments. package exec -// BUG(r): This package should be made even easier to use or merged into os. - import ( + "bytes" + "io" "os" "strconv" ) -// Arguments to Run. -const ( - DevNull = iota - PassThrough - Pipe - MergeWithStdout -) +// Error records the name of a binary that failed to be be executed +// and the reason it failed. +type Error struct { + Name string + Error os.Error +} -// A Cmd represents a running command. -// Stdin, Stdout, and Stderr are Files representing pipes -// connected to the running command's standard input, output, and error, -// or else nil, depending on the arguments to Run. -// Process represents the underlying operating system process. +func (e *Error) String() string { + return "exec: " + strconv.Quote(e.Name) + ": " + e.Error.String() +} + +// Cmd represents an external command being prepared or run. type Cmd struct { - Stdin *os.File - Stdout *os.File - Stderr *os.File + // Path is the path of the command to run. + // + // This is the only field that must be set to a non-zero + // value. + Path string + + // Args holds command line arguments, including the command as Args[0]. + // If the Args field is empty or nil, Run uses {Path}. + // + // In typical use, both Path and Args are set by calling Command. + Args []string + + // Env specifies the environment of the process. + // If Env is nil, Run uses the current process's environment. + Env []string + + // Dir specifies the working directory of the command. + // If Dir is the empty string, Run runs the command in the + // calling process's current directory. + Dir string + + // Stdin specifies the process's standard input. + // If Stdin is nil, the process reads from DevNull. + Stdin io.Reader + + // Stdout and Stderr specify the process's standard output and error. + // + // If either is nil, Run connects the + // corresponding file descriptor to /dev/null. + // + // If Stdout and Stderr are are the same writer, at most one + // goroutine at a time will call Write. + Stdout io.Writer + Stderr io.Writer + + // Process is the underlying process, once started. Process *os.Process + + err os.Error // last error (from LookPath, stdin, stdout, stderr) + finished bool // when Wait was called + childFiles []*os.File + closeAfterStart []io.Closer + closeAfterWait []io.Closer + goroutine []func() os.Error + errch chan os.Error // one send per goroutine } -// PathError records the name of a binary that was not -// found on the current $PATH. -type PathError struct { - Name string +// Command returns the Cmd struct to execute the named program with +// the given arguments. +// +// It sets Path and Args in the returned structure and zeroes the +// other fields. +// +// If name contains no path separators, Command uses LookPath to +// resolve the path to a complete name if possible. Otherwise it uses +// name directly. +// +// The returned Cmd's Args field is constructed from the command name +// followed by the elements of arg, so arg should not include the +// command name itself. For example, Command("echo", "hello") +func Command(name string, arg ...string) *Cmd { + aname, err := LookPath(name) + if err != nil { + aname = name + } + return &Cmd{ + Path: aname, + Args: append([]string{name}, arg...), + err: err, + } } -func (e *PathError) String() string { - return "command " + strconv.Quote(e.Name) + " not found in $PATH" +// interfaceEqual protects against panics from doing equality tests on +// two interfaces with non-comparable underlying types +func interfaceEqual(a, b interface{}) bool { + defer func() { + recover() + }() + return a == b } -// Given mode (DevNull, etc), return file for child -// and file to record in Cmd structure. -func modeToFiles(mode, fd int) (*os.File, *os.File, os.Error) { - switch mode { - case DevNull: - rw := os.O_WRONLY - if fd == 0 { - rw = os.O_RDONLY - } - f, err := os.OpenFile(os.DevNull, rw, 0) - return f, nil, err - case PassThrough: - switch fd { - case 0: - return os.Stdin, nil, nil - case 1: - return os.Stdout, nil, nil - case 2: - return os.Stderr, nil, nil - } - case Pipe: - r, w, err := os.Pipe() - if err != nil { - return nil, nil, err - } - if fd == 0 { - return r, w, nil +func (c *Cmd) envv() []string { + if c.Env != nil { + return c.Env + } + return os.Environ() +} + +func (c *Cmd) argv() []string { + if len(c.Args) > 0 { + return c.Args + } + return []string{c.Path} +} + +func (c *Cmd) stdin() (f *os.File, err os.Error) { + if c.Stdin == nil { + f, err = os.Open(os.DevNull) + c.closeAfterStart = append(c.closeAfterStart, f) + return + } + + if f, ok := c.Stdin.(*os.File); ok { + return f, nil + } + + pr, pw, err := os.Pipe() + if err != nil { + return + } + + c.closeAfterStart = append(c.closeAfterStart, pr) + c.closeAfterWait = append(c.closeAfterWait, pw) + c.goroutine = append(c.goroutine, func() os.Error { + _, err := io.Copy(pw, c.Stdin) + if err1 := pw.Close(); err == nil { + err = err1 } - return w, r, nil + return err + }) + return pr, nil +} + +func (c *Cmd) stdout() (f *os.File, err os.Error) { + return c.writerDescriptor(c.Stdout) +} + +func (c *Cmd) stderr() (f *os.File, err os.Error) { + if c.Stderr != nil && interfaceEqual(c.Stderr, c.Stdout) { + return c.childFiles[1], nil } - return nil, nil, os.EINVAL + return c.writerDescriptor(c.Stderr) } -// Run starts the named binary running with -// arguments argv and environment envv. -// If the dir argument is not empty, the child changes -// into the directory before executing the binary. -// It returns a pointer to a new Cmd representing -// the command or an error. +func (c *Cmd) writerDescriptor(w io.Writer) (f *os.File, err os.Error) { + if w == nil { + f, err = os.OpenFile(os.DevNull, os.O_WRONLY, 0) + c.closeAfterStart = append(c.closeAfterStart, f) + return + } + + if f, ok := w.(*os.File); ok { + return f, nil + } + + pr, pw, err := os.Pipe() + if err != nil { + return + } + + c.closeAfterStart = append(c.closeAfterStart, pw) + c.closeAfterWait = append(c.closeAfterWait, pr) + c.goroutine = append(c.goroutine, func() os.Error { + _, err := io.Copy(w, pr) + return err + }) + return pw, nil +} + +// Run starts the specified command and waits for it to complete. +// +// The returned error is nil if the command runs, has no problems +// copying stdin, stdout, and stderr, and exits with a zero exit +// status. // -// The arguments stdin, stdout, and stderr -// specify how to handle standard input, output, and error. -// The choices are DevNull (connect to /dev/null), -// PassThrough (connect to the current process's standard stream), -// Pipe (connect to an operating system pipe), and -// MergeWithStdout (only for standard error; use the same -// file descriptor as was used for standard output). -// If an argument is Pipe, then the corresponding field (Stdin, Stdout, Stderr) -// of the returned Cmd is the other end of the pipe. -// Otherwise the field in Cmd is nil. -func Run(name string, argv, envv []string, dir string, stdin, stdout, stderr int) (c *Cmd, err os.Error) { - c = new(Cmd) - var fd [3]*os.File - - if fd[0], c.Stdin, err = modeToFiles(stdin, 0); err != nil { - goto Error - } - if fd[1], c.Stdout, err = modeToFiles(stdout, 1); err != nil { - goto Error - } - if stderr == MergeWithStdout { - fd[2] = fd[1] - } else if fd[2], c.Stderr, err = modeToFiles(stderr, 2); err != nil { - goto Error - } - - // Run command. - c.Process, err = os.StartProcess(name, argv, &os.ProcAttr{Dir: dir, Files: fd[:], Env: envv}) +// If the command fails to run or doesn't complete successfully, the +// error is of type *os.Waitmsg. Other error types may be +// returned for I/O problems. +func (c *Cmd) Run() os.Error { + if err := c.Start(); err != nil { + return err + } + return c.Wait() +} + +// Start starts the specified command but does not wait for it to complete. +func (c *Cmd) Start() os.Error { + if c.err != nil { + return c.err + } + if c.Process != nil { + return os.NewError("exec: already started") + } + + type F func(*Cmd) (*os.File, os.Error) + for _, setupFd := range []F{(*Cmd).stdin, (*Cmd).stdout, (*Cmd).stderr} { + fd, err := setupFd(c) + if err != nil { + return err + } + c.childFiles = append(c.childFiles, fd) + } + + var err os.Error + c.Process, err = os.StartProcess(c.Path, c.argv(), &os.ProcAttr{ + Dir: c.Dir, + Files: c.childFiles, + Env: c.envv(), + }) if err != nil { - goto Error + return err + } + + for _, fd := range c.closeAfterStart { + fd.Close() } - if fd[0] != os.Stdin { - fd[0].Close() + + c.errch = make(chan os.Error, len(c.goroutine)) + for _, fn := range c.goroutine { + go func(fn func() os.Error) { + c.errch <- fn() + }(fn) } - if fd[1] != os.Stdout { - fd[1].Close() + + return nil +} + +// Wait waits for the command to exit. +// It must have been started by Start. +// +// The returned error is nil if the command runs, has no problems +// copying stdin, stdout, and stderr, and exits with a zero exit +// status. +// +// If the command fails to run or doesn't complete successfully, the +// error is of type *os.Waitmsg. Other error types may be +// returned for I/O problems. +func (c *Cmd) Wait() os.Error { + if c.Process == nil { + return os.NewError("exec: not started") } - if fd[2] != os.Stderr && fd[2] != fd[1] { - fd[2].Close() + if c.finished { + return os.NewError("exec: Wait was already called") } - return c, nil + c.finished = true + msg, err := c.Process.Wait(0) -Error: - if fd[0] != os.Stdin && fd[0] != nil { - fd[0].Close() + var copyError os.Error + for _ = range c.goroutine { + if err := <-c.errch; err != nil && copyError == nil { + copyError = err + } } - if fd[1] != os.Stdout && fd[1] != nil { - fd[1].Close() + + for _, fd := range c.closeAfterWait { + fd.Close() } - if fd[2] != os.Stderr && fd[2] != nil && fd[2] != fd[1] { - fd[2].Close() + + if err != nil { + return err + } else if !msg.Exited() || msg.ExitStatus() != 0 { + return msg } - if c.Stdin != nil { - c.Stdin.Close() + + return copyError +} + +// Output runs the command and returns its standard output. +func (c *Cmd) Output() ([]byte, os.Error) { + if c.Stdout != nil { + return nil, os.NewError("exec: Stdout already set") } + var b bytes.Buffer + c.Stdout = &b + err := c.Run() + return b.Bytes(), err +} + +// CombinedOutput runs the command and returns its combined standard +// output and standard error. +func (c *Cmd) CombinedOutput() ([]byte, os.Error) { if c.Stdout != nil { - c.Stdout.Close() + return nil, os.NewError("exec: Stdout already set") } if c.Stderr != nil { - c.Stderr.Close() + return nil, os.NewError("exec: Stderr already set") } - if c.Process != nil { - c.Process.Release() - } - return nil, err + var b bytes.Buffer + c.Stdout = &b + c.Stderr = &b + err := c.Run() + return b.Bytes(), err } -// Wait waits for the running command c, -// returning the Waitmsg returned when the process exits. -// The options are passed to the process's Wait method. -// Setting options to 0 waits for c to exit; -// other options cause Wait to return for other -// process events; see package os for details. -func (c *Cmd) Wait(options int) (*os.Waitmsg, os.Error) { - if c.Process == nil { - return nil, os.ErrorString("exec: invalid use of Cmd.Wait") +// StdinPipe returns a pipe that will be connected to the command's +// standard input when the command starts. +func (c *Cmd) StdinPipe() (io.WriteCloser, os.Error) { + if c.Stdin != nil { + return nil, os.NewError("exec: Stdin already set") } - w, err := c.Process.Wait(options) - if w != nil && (w.Exited() || w.Signaled()) { - c.Process.Release() - c.Process = nil + if c.Process != nil { + return nil, os.NewError("exec: StdinPipe after process started") + } + pr, pw, err := os.Pipe() + if err != nil { + return nil, err } - return w, err + c.Stdin = pr + c.closeAfterStart = append(c.closeAfterStart, pr) + c.closeAfterWait = append(c.closeAfterStart, pw) + return pw, nil } -// Close waits for the running command c to exit, -// if it hasn't already, and then closes the non-nil file descriptors -// c.Stdin, c.Stdout, and c.Stderr. -func (c *Cmd) Close() os.Error { +// StdoutPipe returns a pipe that will be connected to the command's +// standard output when the command starts. +func (c *Cmd) StdoutPipe() (io.Reader, os.Error) { + if c.Stdout != nil { + return nil, os.NewError("exec: Stdout already set") + } if c.Process != nil { - // Loop on interrupt, but - // ignore other errors -- maybe - // caller has already waited for pid. - _, err := c.Wait(0) - for err == os.EINTR { - _, err = c.Wait(0) - } + return nil, os.NewError("exec: StdoutPipe after process started") } + pr, pw, err := os.Pipe() + if err != nil { + return nil, err + } + c.Stdout = pw + c.closeAfterStart = append(c.closeAfterStart, pw) + c.closeAfterWait = append(c.closeAfterStart, pr) + return pr, nil +} - // Close the FDs that are still open. - var err os.Error - if c.Stdin != nil && c.Stdin.Fd() >= 0 { - if err1 := c.Stdin.Close(); err1 != nil { - err = err1 - } +// StderrPipe returns a pipe that will be connected to the command's +// standard error when the command starts. +func (c *Cmd) StderrPipe() (io.Reader, os.Error) { + if c.Stderr != nil { + return nil, os.NewError("exec: Stderr already set") } - if c.Stdout != nil && c.Stdout.Fd() >= 0 { - if err1 := c.Stdout.Close(); err1 != nil && err != nil { - err = err1 - } + if c.Process != nil { + return nil, os.NewError("exec: StderrPipe after process started") } - if c.Stderr != nil && c.Stderr != c.Stdout && c.Stderr.Fd() >= 0 { - if err1 := c.Stderr.Close(); err1 != nil && err != nil { - err = err1 - } + pr, pw, err := os.Pipe() + if err != nil { + return nil, err } - return err + c.Stderr = pw + c.closeAfterStart = append(c.closeAfterStart, pw) + c.closeAfterWait = append(c.closeAfterStart, pr) + return pr, nil } diff --git a/src/pkg/exec/exec_test.go b/src/pkg/exec/exec_test.go index eb8cd5fec..c45a7d70a 100644 --- a/src/pkg/exec/exec_test.go +++ b/src/pkg/exec/exec_test.go @@ -5,163 +5,211 @@ package exec import ( + "bufio" + "bytes" + "fmt" "io" - "io/ioutil" "testing" "os" + "strconv" + "strings" ) -func run(argv []string, stdin, stdout, stderr int) (p *Cmd, err os.Error) { - exe, err := LookPath(argv[0]) - if err != nil { - return nil, err - } - return Run(exe, argv, nil, "", stdin, stdout, stderr) +func helperCommand(s ...string) *Cmd { + cs := []string{"-test.run=exec.TestHelperProcess", "--"} + cs = append(cs, s...) + cmd := Command(os.Args[0], cs...) + cmd.Env = append([]string{"GO_WANT_HELPER_PROCESS=1"}, os.Environ()...) + return cmd } -func TestRunCat(t *testing.T) { - cmd, err := run([]string{"cat"}, Pipe, Pipe, DevNull) +func TestEcho(t *testing.T) { + bs, err := helperCommand("echo", "foo bar", "baz").Output() if err != nil { - t.Fatal("run:", err) - } - io.WriteString(cmd.Stdin, "hello, world\n") - cmd.Stdin.Close() - buf, err := ioutil.ReadAll(cmd.Stdout) - if err != nil { - t.Fatal("read:", err) - } - if string(buf) != "hello, world\n" { - t.Fatalf("read: got %q", buf) + t.Errorf("echo: %v", err) } - if err = cmd.Close(); err != nil { - t.Fatal("close:", err) + if g, e := string(bs), "foo bar baz\n"; g != e { + t.Errorf("echo: want %q, got %q", e, g) } } -func TestRunEcho(t *testing.T) { - cmd, err := run([]string{"sh", "-c", "echo hello world"}, - DevNull, Pipe, DevNull) +func TestCatStdin(t *testing.T) { + // Cat, testing stdin and stdout. + input := "Input string\nLine 2" + p := helperCommand("cat") + p.Stdin = strings.NewReader(input) + bs, err := p.Output() if err != nil { - t.Fatal("run:", err) + t.Errorf("cat: %v", err) } - buf, err := ioutil.ReadAll(cmd.Stdout) - if err != nil { - t.Fatal("read:", err) - } - if string(buf) != "hello world\n" { - t.Fatalf("read: got %q", buf) - } - if err = cmd.Close(); err != nil { - t.Fatal("close:", err) + s := string(bs) + if s != input { + t.Errorf("cat: want %q, got %q", input, s) } } -func TestStderr(t *testing.T) { - cmd, err := run([]string{"sh", "-c", "echo hello world 1>&2"}, - DevNull, DevNull, Pipe) - if err != nil { - t.Fatal("run:", err) +func TestCatGoodAndBadFile(t *testing.T) { + // Testing combined output and error values. + bs, err := helperCommand("cat", "/bogus/file.foo", "exec_test.go").CombinedOutput() + if _, ok := err.(*os.Waitmsg); !ok { + t.Errorf("expected Waitmsg from cat combined; got %T: %v", err, err) } - buf, err := ioutil.ReadAll(cmd.Stderr) - if err != nil { - t.Fatal("read:", err) + s := string(bs) + sp := strings.Split(s, "\n", 2) + if len(sp) != 2 { + t.Fatalf("expected two lines from cat; got %q", s) } - if string(buf) != "hello world\n" { - t.Fatalf("read: got %q", buf) + errLine, body := sp[0], sp[1] + if !strings.HasPrefix(errLine, "Error: open /bogus/file.foo") { + t.Errorf("expected stderr to complain about file; got %q", errLine) } - if err = cmd.Close(); err != nil { - t.Fatal("close:", err) + if !strings.Contains(body, "func TestHelperProcess(t *testing.T)") { + t.Errorf("expected test code; got %q (len %d)", body, len(body)) } } -func TestMergeWithStdout(t *testing.T) { - cmd, err := run([]string{"sh", "-c", "echo hello world 1>&2"}, - DevNull, Pipe, MergeWithStdout) - if err != nil { - t.Fatal("run:", err) - } - buf, err := ioutil.ReadAll(cmd.Stdout) - if err != nil { - t.Fatal("read:", err) - } - if string(buf) != "hello world\n" { - t.Fatalf("read: got %q", buf) + +func TestNoExistBinary(t *testing.T) { + // Can't run a non-existent binary + err := Command("/no-exist-binary").Run() + if err == nil { + t.Error("expected error from /no-exist-binary") } - if err = cmd.Close(); err != nil { - t.Fatal("close:", err) +} + +func TestExitStatus(t *testing.T) { + // Test that exit values are returned correctly + err := helperCommand("exit", "42").Run() + if werr, ok := err.(*os.Waitmsg); ok { + if s, e := werr.String(), "exit status 42"; s != e { + t.Errorf("from exit 42 got exit %q, want %q", s, e) + } + } else { + t.Fatalf("expected Waitmsg from exit 42; got %T: %v", err, err) } } -func TestAddEnvVar(t *testing.T) { - err := os.Setenv("NEWVAR", "hello world") - if err != nil { - t.Fatal("setenv:", err) +func TestPipes(t *testing.T) { + check := func(what string, err os.Error) { + if err != nil { + t.Fatalf("%s: %v", what, err) + } } - cmd, err := run([]string{"sh", "-c", "echo $NEWVAR"}, - DevNull, Pipe, DevNull) - if err != nil { - t.Fatal("run:", err) + // Cat, testing stdin and stdout. + c := helperCommand("pipetest") + stdin, err := c.StdinPipe() + check("StdinPipe", err) + stdout, err := c.StdoutPipe() + check("StdoutPipe", err) + stderr, err := c.StderrPipe() + check("StderrPipe", err) + + outbr := bufio.NewReader(stdout) + errbr := bufio.NewReader(stderr) + line := func(what string, br *bufio.Reader) string { + line, _, err := br.ReadLine() + if err != nil { + t.Fatalf("%s: %v", what, err) + } + return string(line) } - buf, err := ioutil.ReadAll(cmd.Stdout) - if err != nil { - t.Fatal("read:", err) + + err = c.Start() + check("Start", err) + + _, err = stdin.Write([]byte("O:I am output\n")) + check("first stdin Write", err) + if g, e := line("first output line", outbr), "O:I am output"; g != e { + t.Errorf("got %q, want %q", g, e) } - if string(buf) != "hello world\n" { - t.Fatalf("read: got %q", buf) + + _, err = stdin.Write([]byte("E:I am error\n")) + check("second stdin Write", err) + if g, e := line("first error line", errbr), "E:I am error"; g != e { + t.Errorf("got %q, want %q", g, e) } - if err = cmd.Close(); err != nil { - t.Fatal("close:", err) + + _, err = stdin.Write([]byte("O:I am output2\n")) + check("third stdin Write 3", err) + if g, e := line("second output line", outbr), "O:I am output2"; g != e { + t.Errorf("got %q, want %q", g, e) } -} -var tryargs = []string{ - `2`, - `2 `, - "2 \t", - `2" "`, - `2 ab `, - `2 "ab" `, - `2 \ `, - `2 \\ `, - `2 \" `, - `2 \`, - `2\`, - `2"`, - `2\"`, - `2 "`, - `2 \"`, - ``, - `2 ^ `, - `2 \^`, + stdin.Close() + err = c.Wait() + check("Wait", err) } -func TestArgs(t *testing.T) { - for _, a := range tryargs { - argv := []string{ - "awk", - `BEGIN{printf("%s|%s|%s",ARGV[1],ARGV[2],ARGV[3])}`, - "/dev/null", - a, - "EOF", - } - exe, err := LookPath(argv[0]) - if err != nil { - t.Fatal("run:", err) +// TestHelperProcess isn't a real test. It's used as a helper process +// for TestParameterRun. +func TestHelperProcess(*testing.T) { + if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" { + return + } + defer os.Exit(0) + + args := os.Args + for len(args) > 0 { + if args[0] == "--" { + args = args[1:] + break } - cmd, err := Run(exe, argv, nil, "", DevNull, Pipe, DevNull) - if err != nil { - t.Fatal("run:", err) + args = args[1:] + } + if len(args) == 0 { + fmt.Fprintf(os.Stderr, "No command\n") + os.Exit(2) + } + + cmd, args := args[0], args[1:] + switch cmd { + case "echo": + iargs := []interface{}{} + for _, s := range args { + iargs = append(iargs, s) } - buf, err := ioutil.ReadAll(cmd.Stdout) - if err != nil { - t.Fatal("read:", err) + fmt.Println(iargs...) + case "cat": + if len(args) == 0 { + io.Copy(os.Stdout, os.Stdin) + return } - expect := "/dev/null|" + a + "|EOF" - if string(buf) != expect { - t.Errorf("read: got %q expect %q", buf, expect) + exit := 0 + for _, fn := range args { + f, err := os.Open(fn) + if err != nil { + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + exit = 2 + } else { + defer f.Close() + io.Copy(os.Stdout, f) + } } - if err = cmd.Close(); err != nil { - t.Fatal("close:", err) + os.Exit(exit) + case "pipetest": + bufr := bufio.NewReader(os.Stdin) + for { + line, _, err := bufr.ReadLine() + if err == os.EOF { + break + } else if err != nil { + os.Exit(1) + } + if bytes.HasPrefix(line, []byte("O:")) { + os.Stdout.Write(line) + os.Stdout.Write([]byte{'\n'}) + } else if bytes.HasPrefix(line, []byte("E:")) { + os.Stderr.Write(line) + os.Stderr.Write([]byte{'\n'}) + } else { + os.Exit(1) + } } + case "exit": + n, _ := strconv.Atoi(args[0]) + os.Exit(n) + default: + fmt.Fprintf(os.Stderr, "Unknown command %q\n", cmd) + os.Exit(2) } } diff --git a/src/pkg/exec/lp_test.go b/src/pkg/exec/lp_test.go index 54081771e..77d8e848c 100644 --- a/src/pkg/exec/lp_test.go +++ b/src/pkg/exec/lp_test.go @@ -22,12 +22,12 @@ func TestLookPathNotFound(t *testing.T) { if path != "" { t.Fatalf("LookPath path == %q when err != nil", path) } - perr, ok := err.(*PathError) + perr, ok := err.(*Error) if !ok { - t.Fatal("LookPath error is not a PathError") + t.Fatal("LookPath error is not an exec.Error") } if perr.Name != name { - t.Fatalf("want PathError name %q, got %q", name, perr.Name) + t.Fatalf("want Error name %q, got %q", name, perr.Name) } } } diff --git a/src/pkg/exec/lp_unix.go b/src/pkg/exec/lp_unix.go index 44f84347b..3fc3be832 100644 --- a/src/pkg/exec/lp_unix.go +++ b/src/pkg/exec/lp_unix.go @@ -9,12 +9,18 @@ import ( "strings" ) -func canExec(file string) bool { +// ErrNotFound is the error resulting if a path search failed to find an executable file. +var ErrNotFound = os.ErrorString("executable file not found in $PATH") + +func findExecutable(file string) os.Error { d, err := os.Stat(file) if err != nil { - return false + return err + } + if d.IsRegular() && d.Permission()&0111 != 0 { + return nil } - return d.IsRegular() && d.Permission()&0111 != 0 + return os.EPERM } // LookPath searches for an executable binary named file @@ -26,10 +32,11 @@ func LookPath(file string) (string, os.Error) { // but that would not match all the Unix shells. if strings.Contains(file, "/") { - if canExec(file) { + err := findExecutable(file) + if err == nil { return file, nil } - return "", &PathError{file} + return "", &Error{file, err} } pathenv := os.Getenv("PATH") for _, dir := range strings.Split(pathenv, ":", -1) { @@ -37,9 +44,9 @@ func LookPath(file string) (string, os.Error) { // Unix shell semantics: path element "" means "." dir = "." } - if canExec(dir + "/" + file) { + if err := findExecutable(dir + "/" + file); err == nil { return dir + "/" + file, nil } } - return "", &PathError{file} + return "", &Error{file, ErrNotFound} } diff --git a/src/pkg/exec/lp_windows.go b/src/pkg/exec/lp_windows.go index d357575fd..758861021 100644 --- a/src/pkg/exec/lp_windows.go +++ b/src/pkg/exec/lp_windows.go @@ -9,15 +9,21 @@ import ( "strings" ) -func chkStat(file string) bool { +// ErrNotFound is the error resulting if a path search failed to find an executable file. +var ErrNotFound = os.ErrorString("executable file not found in %PATH%") + +func chkStat(file string) os.Error { d, err := os.Stat(file) if err != nil { - return false + return err + } + if d.IsRegular() { + return nil } - return d.IsRegular() + return os.EPERM } -func canExec(file string, exts []string) (string, bool) { +func findExecutable(file string, exts []string) (string, os.Error) { if len(exts) == 0 { return file, chkStat(file) } @@ -28,14 +34,14 @@ func canExec(file string, exts []string) (string, bool) { } } for _, e := range exts { - if f := file + e; chkStat(f) { - return f, true + if f := file + e; chkStat(f) == nil { + return f, nil } } - return ``, false + return ``, ErrNotFound } -func LookPath(file string) (string, os.Error) { +func LookPath(file string) (f string, err os.Error) { exts := []string{} if x := os.Getenv(`PATHEXT`); x != `` { exts = strings.Split(strings.ToLower(x), `;`, -1) @@ -46,21 +52,21 @@ func LookPath(file string) (string, os.Error) { } } if strings.Contains(file, `\`) || strings.Contains(file, `/`) { - if f, ok := canExec(file, exts); ok { - return f, nil + if f, err = findExecutable(file, exts); err == nil { + return } - return ``, &PathError{file} + return ``, &Error{file, err} } if pathenv := os.Getenv(`PATH`); pathenv == `` { - if f, ok := canExec(`.\`+file, exts); ok { - return f, nil + if f, err = findExecutable(`.\`+file, exts); err == nil { + return } } else { for _, dir := range strings.Split(pathenv, `;`, -1) { - if f, ok := canExec(dir+`\`+file, exts); ok { - return f, nil + if f, err = findExecutable(dir+`\`+file, exts); err == nil { + return } } } - return ``, &PathError{file} + return ``, &Error{file, ErrNotFound} } |