// Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Package exec runs external commands. It wraps os.StartProcess to make it // easier to remap stdin and stdout, connect I/O with pipes, and do other // adjustments. package exec import ( "bytes" "errors" "io" "os" "strconv" "syscall" ) // Error records the name of a binary that failed to be be executed // and the reason it failed. type Error struct { Name string Err error } func (e *Error) Error() string { return "exec: " + strconv.Quote(e.Name) + ": " + e.Err.Error() } // Cmd represents an external command being prepared or run. type Cmd struct { // 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 the null device (os.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 the null device (os.DevNull). // // If Stdout and Stderr are the same writer, at most one // goroutine at a time will call Write. Stdout io.Writer Stderr io.Writer // ExtraFiles specifies additional open files to be inherited by the // new process. It does not include standard input, standard output, or // standard error. If non-nil, entry i becomes file descriptor 3+i. // // BUG: on OS X 10.6, child processes may sometimes inherit unwanted fds. // http://golang.org/issue/2603 ExtraFiles []*os.File // SysProcAttr holds optional, operating system-specific attributes. // Run passes it to os.StartProcess as the os.ProcAttr's Sys field. SysProcAttr *syscall.SysProcAttr // Process is the underlying process, once started. Process *os.Process // ProcessState contains information about an exited process, // available after a call to Wait or Run. ProcessState *os.ProcessState err 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() error errch chan error // one send per goroutine } // 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, } } // 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 } 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 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() error { _, err := io.Copy(pw, c.Stdin) if err1 := pw.Close(); err == nil { err = err1 } return err }) return pr, nil } func (c *Cmd) stdout() (f *os.File, err error) { return c.writerDescriptor(c.Stdout) } func (c *Cmd) stderr() (f *os.File, err error) { if c.Stderr != nil && interfaceEqual(c.Stderr, c.Stdout) { return c.childFiles[1], nil } return c.writerDescriptor(c.Stderr) } func (c *Cmd) writerDescriptor(w io.Writer) (f *os.File, err 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() 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. // // If the command fails to run or doesn't complete successfully, the // error is of type *ExitError. Other error types may be // returned for I/O problems. func (c *Cmd) Run() 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() error { if c.err != nil { return c.err } if c.Process != nil { return errors.New("exec: already started") } type F func(*Cmd) (*os.File, 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) } c.childFiles = append(c.childFiles, c.ExtraFiles...) var err error c.Process, err = os.StartProcess(c.Path, c.argv(), &os.ProcAttr{ Dir: c.Dir, Files: c.childFiles, Env: c.envv(), Sys: c.SysProcAttr, }) if err != nil { return err } for _, fd := range c.closeAfterStart { fd.Close() } c.errch = make(chan error, len(c.goroutine)) for _, fn := range c.goroutine { go func(fn func() error) { c.errch <- fn() }(fn) } return nil } // An ExitError reports an unsuccessful exit by a command. type ExitError struct { *os.ProcessState } func (e *ExitError) Error() string { return e.ProcessState.String() } // 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 *ExitError. Other error types may be // returned for I/O problems. func (c *Cmd) Wait() error { if c.Process == nil { return errors.New("exec: not started") } if c.finished { return errors.New("exec: Wait was already called") } c.finished = true state, err := c.Process.Wait() c.ProcessState = state var copyError error for _ = range c.goroutine { if err := <-c.errch; err != nil && copyError == nil { copyError = err } } for _, fd := range c.closeAfterWait { fd.Close() } if err != nil { return err } else if !state.Success() { return &ExitError{state} } return copyError } // Output runs the command and returns its standard output. func (c *Cmd) Output() ([]byte, error) { if c.Stdout != nil { return nil, errors.New("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, error) { if c.Stdout != nil { return nil, errors.New("exec: Stdout already set") } if c.Stderr != nil { return nil, errors.New("exec: Stderr already set") } var b bytes.Buffer c.Stdout = &b c.Stderr = &b err := c.Run() return b.Bytes(), err } // StdinPipe returns a pipe that will be connected to the command's // standard input when the command starts. func (c *Cmd) StdinPipe() (io.WriteCloser, error) { if c.Stdin != nil { return nil, errors.New("exec: Stdin already set") } if c.Process != nil { return nil, errors.New("exec: StdinPipe after process started") } pr, pw, err := os.Pipe() if err != nil { return nil, err } c.Stdin = pr c.closeAfterStart = append(c.closeAfterStart, pr) c.closeAfterWait = append(c.closeAfterWait, pw) return pw, nil } // StdoutPipe returns a pipe that will be connected to the command's // standard output when the command starts. // The pipe will be closed automatically after Wait sees the command exit. func (c *Cmd) StdoutPipe() (io.ReadCloser, error) { if c.Stdout != nil { return nil, errors.New("exec: Stdout already set") } if c.Process != nil { return nil, errors.New("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.closeAfterWait, pr) return pr, nil } // StderrPipe returns a pipe that will be connected to the command's // standard error when the command starts. // The pipe will be closed automatically after Wait sees the command exit. func (c *Cmd) StderrPipe() (io.ReadCloser, error) { if c.Stderr != nil { return nil, errors.New("exec: Stderr already set") } if c.Process != nil { return nil, errors.New("exec: StderrPipe after process started") } pr, pw, err := os.Pipe() if err != nil { return nil, err } c.Stderr = pw c.closeAfterStart = append(c.closeAfterStart, pw) c.closeAfterWait = append(c.closeAfterWait, pr) return pr, nil }