mirror of
https://github.com/golang/go
synced 2024-11-21 15:54:43 -07:00
exec: new API, replace Run with Command
This removes exec.Run and replaces exec.Cmd with a new implementation. The new exec.Cmd represents both a currently-running command and also a command being prepared. It has a good zero value. You can Start + Wait on a Cmd, or simply Run it. Start (and Run) deal with copying stdout, stdin, and stderr between the Cmd's io.Readers and io.Writers. There are convenience methods to capture a command's stdout and/or stderr. R=r, n13m3y3r, rsc, gustavo, alex.brainman, dsymonds, r, adg, duzy.chan, mike.rosset, kevlar CC=golang-dev https://golang.org/cl/4552052
This commit is contained in:
parent
2132a7f575
commit
f259f6ba0a
@ -19,16 +19,11 @@ func run(envv []string, dir string, argv ...string) os.Error {
|
|||||||
log.Println("run", argv)
|
log.Println("run", argv)
|
||||||
}
|
}
|
||||||
argv = useBash(argv)
|
argv = useBash(argv)
|
||||||
bin, err := lookPath(argv[0])
|
cmd := exec.Command(argv[0], argv[1:]...)
|
||||||
if err != nil {
|
cmd.Dir = dir
|
||||||
return err
|
cmd.Env = envv
|
||||||
}
|
cmd.Stderr = os.Stderr
|
||||||
p, err := exec.Run(bin, argv, envv, dir,
|
return cmd.Run()
|
||||||
exec.DevNull, exec.DevNull, exec.PassThrough)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return p.Close()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// runLog runs a process and returns the combined stdout/stderr,
|
// runLog runs a process and returns the combined stdout/stderr,
|
||||||
@ -38,16 +33,7 @@ func runLog(envv []string, logfile, dir string, argv ...string) (output string,
|
|||||||
log.Println("runLog", argv)
|
log.Println("runLog", argv)
|
||||||
}
|
}
|
||||||
argv = useBash(argv)
|
argv = useBash(argv)
|
||||||
bin, err := lookPath(argv[0])
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
p, err := exec.Run(bin, argv, envv, dir,
|
|
||||||
exec.DevNull, exec.Pipe, exec.MergeWithStdout)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer p.Close()
|
|
||||||
b := new(bytes.Buffer)
|
b := new(bytes.Buffer)
|
||||||
var w io.Writer = b
|
var w io.Writer = b
|
||||||
if logfile != "" {
|
if logfile != "" {
|
||||||
@ -58,23 +44,22 @@ func runLog(envv []string, logfile, dir string, argv ...string) (output string,
|
|||||||
defer f.Close()
|
defer f.Close()
|
||||||
w = io.MultiWriter(f, b)
|
w = io.MultiWriter(f, b)
|
||||||
}
|
}
|
||||||
_, err = io.Copy(w, p.Stdout)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
wait, err := p.Wait(0)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return b.String(), wait.WaitStatus.ExitStatus(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// lookPath looks for cmd in $PATH if cmd does not begin with / or ./ or ../.
|
cmd := exec.Command(argv[0], argv[1:]...)
|
||||||
func lookPath(cmd string) (string, os.Error) {
|
cmd.Dir = dir
|
||||||
if strings.HasPrefix(cmd, "/") || strings.HasPrefix(cmd, "./") || strings.HasPrefix(cmd, "../") {
|
cmd.Env = envv
|
||||||
return cmd, nil
|
cmd.Stdout = w
|
||||||
|
cmd.Stderr = w
|
||||||
|
|
||||||
|
err = cmd.Run()
|
||||||
|
output = b.String()
|
||||||
|
if err != nil {
|
||||||
|
if ws, ok := err.(*os.Waitmsg); ok {
|
||||||
|
exitStatus = ws.ExitStatus()
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
return exec.LookPath(cmd)
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// useBash prefixes a list of args with 'bash' if the first argument
|
// useBash prefixes a list of args with 'bash' if the first argument
|
||||||
|
@ -5,7 +5,6 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"exec"
|
"exec"
|
||||||
"flag"
|
"flag"
|
||||||
"http"
|
"http"
|
||||||
@ -140,32 +139,7 @@ func error(w http.ResponseWriter, out []byte, err os.Error) {
|
|||||||
|
|
||||||
// run executes the specified command and returns its output and an error.
|
// run executes the specified command and returns its output and an error.
|
||||||
func run(cmd ...string) ([]byte, os.Error) {
|
func run(cmd ...string) ([]byte, os.Error) {
|
||||||
// find the specified binary
|
return exec.Command(cmd[0], cmd[1:]...).CombinedOutput()
|
||||||
bin, err := exec.LookPath(cmd[0])
|
|
||||||
if err != nil {
|
|
||||||
// report binary as well as the error
|
|
||||||
return nil, os.NewError(cmd[0] + ": " + err.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
// run the binary and read its combined stdout and stderr into a buffer
|
|
||||||
p, err := exec.Run(bin, cmd, os.Environ(), "", exec.DevNull, exec.Pipe, exec.MergeWithStdout)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var buf bytes.Buffer
|
|
||||||
io.Copy(&buf, p.Stdout)
|
|
||||||
w, err := p.Wait(0)
|
|
||||||
p.Close()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// set the error return value if the program had a non-zero exit status
|
|
||||||
if !w.Exited() || w.ExitStatus() != 0 {
|
|
||||||
err = os.ErrorString("running " + cmd[0] + ": " + w.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
return buf.Bytes(), err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var frontPage, output *template.Template // HTML templates
|
var frontPage, output *template.Template // HTML templates
|
||||||
|
@ -248,17 +248,5 @@ func diff(b1, b2 []byte) (data []byte, err os.Error) {
|
|||||||
f1.Write(b1)
|
f1.Write(b1)
|
||||||
f2.Write(b2)
|
f2.Write(b2)
|
||||||
|
|
||||||
diffcmd, err := exec.LookPath("diff")
|
return exec.Command("diff", f1.Name(), f2.Name()).CombinedOutput()
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
c, err := exec.Run(diffcmd, []string{"diff", f1.Name(), f2.Name()}, nil, "",
|
|
||||||
exec.DevNull, exec.Pipe, exec.MergeWithStdout)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer c.Close()
|
|
||||||
|
|
||||||
return ioutil.ReadAll(c.Stdout)
|
|
||||||
}
|
}
|
||||||
|
@ -245,14 +245,14 @@ func gofmtMain() {
|
|||||||
func diff(b1, b2 []byte) (data []byte, err os.Error) {
|
func diff(b1, b2 []byte) (data []byte, err os.Error) {
|
||||||
f1, err := ioutil.TempFile("", "gofmt")
|
f1, err := ioutil.TempFile("", "gofmt")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return
|
||||||
}
|
}
|
||||||
defer os.Remove(f1.Name())
|
defer os.Remove(f1.Name())
|
||||||
defer f1.Close()
|
defer f1.Close()
|
||||||
|
|
||||||
f2, err := ioutil.TempFile("", "gofmt")
|
f2, err := ioutil.TempFile("", "gofmt")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return
|
||||||
}
|
}
|
||||||
defer os.Remove(f2.Name())
|
defer os.Remove(f2.Name())
|
||||||
defer f2.Close()
|
defer f2.Close()
|
||||||
@ -260,17 +260,5 @@ func diff(b1, b2 []byte) (data []byte, err os.Error) {
|
|||||||
f1.Write(b1)
|
f1.Write(b1)
|
||||||
f2.Write(b2)
|
f2.Write(b2)
|
||||||
|
|
||||||
diffcmd, err := exec.LookPath("diff")
|
return exec.Command("diff", "-u", f1.Name(), f2.Name()).CombinedOutput()
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
c, err := exec.Run(diffcmd, []string{"diff", "-u", f1.Name(), f2.Name()},
|
|
||||||
nil, "", exec.DevNull, exec.Pipe, exec.MergeWithStdout)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer c.Close()
|
|
||||||
|
|
||||||
return ioutil.ReadAll(c.Stdout)
|
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,6 @@ import (
|
|||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"go/token"
|
"go/token"
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@ -246,37 +245,22 @@ func quietRun(dir string, stdin []byte, cmd ...string) os.Error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// genRun implements run and quietRun.
|
// genRun implements run and quietRun.
|
||||||
func genRun(dir string, stdin []byte, cmd []string, quiet bool) os.Error {
|
func genRun(dir string, stdin []byte, arg []string, quiet bool) os.Error {
|
||||||
bin, err := exec.LookPath(cmd[0])
|
cmd := exec.Command(arg[0], arg[1:]...)
|
||||||
|
cmd.Stdin = bytes.NewBuffer(stdin)
|
||||||
|
cmd.Dir = dir
|
||||||
|
vlogf("%s: %s %s\n", dir, cmd.Path, strings.Join(arg[1:], " "))
|
||||||
|
out, err := cmd.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
|
||||||
}
|
|
||||||
p, err := exec.Run(bin, cmd, os.Environ(), dir, exec.Pipe, exec.Pipe, exec.MergeWithStdout)
|
|
||||||
vlogf("%s: %s %s\n", dir, bin, strings.Join(cmd[1:], " "))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
go func() {
|
|
||||||
p.Stdin.Write(stdin)
|
|
||||||
p.Stdin.Close()
|
|
||||||
}()
|
|
||||||
var buf bytes.Buffer
|
|
||||||
io.Copy(&buf, p.Stdout)
|
|
||||||
w, err := p.Wait(0)
|
|
||||||
p.Close()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !w.Exited() || w.ExitStatus() != 0 {
|
|
||||||
if !quiet || *verbose {
|
if !quiet || *verbose {
|
||||||
if dir != "" {
|
if dir != "" {
|
||||||
dir = "cd " + dir + "; "
|
dir = "cd " + dir + "; "
|
||||||
}
|
}
|
||||||
fmt.Fprintf(os.Stderr, "%s: === %s%s\n", argv0, dir, strings.Join(cmd, " "))
|
fmt.Fprintf(os.Stderr, "%s: === %s%s\n", cmd.Path, dir, strings.Join(cmd.Args, " "))
|
||||||
os.Stderr.Write(buf.Bytes())
|
os.Stderr.Write(out)
|
||||||
fmt.Fprintf(os.Stderr, "--- %s\n", w)
|
fmt.Fprintf(os.Stderr, "--- %s\n", err)
|
||||||
}
|
}
|
||||||
return os.ErrorString("running " + cmd[0] + ": " + w.String())
|
return os.ErrorString("running " + arg[0] + ": " + err.String())
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,6 @@ import (
|
|||||||
"exec"
|
"exec"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"patch"
|
"patch"
|
||||||
@ -333,6 +332,7 @@ func run(argv []string, input []byte) (out string, err os.Error) {
|
|||||||
err = os.EINVAL
|
err = os.EINVAL
|
||||||
goto Error
|
goto Error
|
||||||
}
|
}
|
||||||
|
|
||||||
prog, ok := lookPathCache[argv[0]]
|
prog, ok := lookPathCache[argv[0]]
|
||||||
if !ok {
|
if !ok {
|
||||||
prog, err = exec.LookPath(argv[0])
|
prog, err = exec.LookPath(argv[0])
|
||||||
@ -341,40 +341,15 @@ func run(argv []string, input []byte) (out string, err os.Error) {
|
|||||||
}
|
}
|
||||||
lookPathCache[argv[0]] = prog
|
lookPathCache[argv[0]] = prog
|
||||||
}
|
}
|
||||||
// fmt.Fprintf(os.Stderr, "%v\n", argv);
|
|
||||||
var cmd *exec.Cmd
|
cmd := exec.Command(prog, argv[1:]...)
|
||||||
if len(input) == 0 {
|
if len(input) > 0 {
|
||||||
cmd, err = exec.Run(prog, argv, os.Environ(), "", exec.DevNull, exec.Pipe, exec.MergeWithStdout)
|
cmd.Stdin = bytes.NewBuffer(input)
|
||||||
if err != nil {
|
|
||||||
goto Error
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
cmd, err = exec.Run(prog, argv, os.Environ(), "", exec.Pipe, exec.Pipe, exec.MergeWithStdout)
|
|
||||||
if err != nil {
|
|
||||||
goto Error
|
|
||||||
}
|
|
||||||
go func() {
|
|
||||||
cmd.Stdin.Write(input)
|
|
||||||
cmd.Stdin.Close()
|
|
||||||
}()
|
|
||||||
}
|
}
|
||||||
defer cmd.Close()
|
bs, err := cmd.CombinedOutput()
|
||||||
var buf bytes.Buffer
|
if err == nil {
|
||||||
_, err = io.Copy(&buf, cmd.Stdout)
|
return string(bs), nil
|
||||||
out = buf.String()
|
|
||||||
if err != nil {
|
|
||||||
cmd.Wait(0)
|
|
||||||
goto Error
|
|
||||||
}
|
}
|
||||||
w, err := cmd.Wait(0)
|
|
||||||
if err != nil {
|
|
||||||
goto Error
|
|
||||||
}
|
|
||||||
if !w.Exited() || w.ExitStatus() != 0 {
|
|
||||||
err = w
|
|
||||||
goto Error
|
|
||||||
}
|
|
||||||
return
|
|
||||||
|
|
||||||
Error:
|
Error:
|
||||||
err = &runError{dup(argv), err}
|
err = &runError{dup(argv), err}
|
||||||
|
@ -7,33 +7,13 @@
|
|||||||
// adjustments.
|
// adjustments.
|
||||||
package exec
|
package exec
|
||||||
|
|
||||||
// BUG(r): This package should be made even easier to use or merged into os.
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Arguments to Run.
|
|
||||||
const (
|
|
||||||
DevNull = iota
|
|
||||||
PassThrough
|
|
||||||
Pipe
|
|
||||||
MergeWithStdout
|
|
||||||
)
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
type Cmd struct {
|
|
||||||
Stdin *os.File
|
|
||||||
Stdout *os.File
|
|
||||||
Stderr *os.File
|
|
||||||
Process *os.Process
|
|
||||||
}
|
|
||||||
|
|
||||||
// PathError records the name of a binary that was not
|
// PathError records the name of a binary that was not
|
||||||
// found on the current $PATH.
|
// found on the current $PATH.
|
||||||
type PathError struct {
|
type PathError struct {
|
||||||
@ -44,161 +24,261 @@ func (e *PathError) String() string {
|
|||||||
return "command " + strconv.Quote(e.Name) + " not found in $PATH"
|
return "command " + strconv.Quote(e.Name) + " not found in $PATH"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Given mode (DevNull, etc), return file for child
|
// Cmd represents an external command being prepared or run.
|
||||||
// and file to record in Cmd structure.
|
type Cmd struct {
|
||||||
func modeToFiles(mode, fd int) (*os.File, *os.File, os.Error) {
|
// Path is the path of the command to run.
|
||||||
switch mode {
|
//
|
||||||
case DevNull:
|
// This is the only field that must be set to a non-zero
|
||||||
rw := os.O_WRONLY
|
// value.
|
||||||
if fd == 0 {
|
Path string
|
||||||
rw = os.O_RDONLY
|
|
||||||
}
|
// Args is the command line arguments, including the command as Args[0].
|
||||||
f, err := os.OpenFile(os.DevNull, rw, 0)
|
// If Args is empty, Run uses {Path}.
|
||||||
return f, nil, err
|
//
|
||||||
case PassThrough:
|
// In typical use, both Path and Args are set by calling Command.
|
||||||
switch fd {
|
Args []string
|
||||||
case 0:
|
|
||||||
return os.Stdin, nil, nil
|
// Env specifies the environment of the process.
|
||||||
case 1:
|
// If Env is nil, Run uses the current process's environment.
|
||||||
return os.Stdout, nil, nil
|
Env []string
|
||||||
case 2:
|
|
||||||
return os.Stderr, nil, nil
|
// Dir specifies the working directory of the command.
|
||||||
}
|
// If Dir is the empty string, Run runs the command in the
|
||||||
case Pipe:
|
// process's current directory.
|
||||||
r, w, err := os.Pipe()
|
Dir string
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
// Stdin specifies the process's standard input.
|
||||||
}
|
// If Stdin is nil, the process reads from DevNull.
|
||||||
if fd == 0 {
|
Stdin io.Reader
|
||||||
return r, w, nil
|
|
||||||
}
|
// Stdout and Stderr specify the process's standard output and error.
|
||||||
return w, r, nil
|
//
|
||||||
}
|
// If either is nil, Run connects the
|
||||||
return nil, nil, os.EINVAL
|
// 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
|
||||||
|
|
||||||
|
err os.Error // last error (from LookPath, stdin, stdout, stderr)
|
||||||
|
process *os.Process
|
||||||
|
childFiles []*os.File
|
||||||
|
closeAfterStart []*os.File
|
||||||
|
closeAfterWait []*os.File
|
||||||
|
goroutine []func() os.Error
|
||||||
|
errch chan os.Error // one send per goroutine
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run starts the named binary running with
|
// Command returns the Cmd struct to execute the named program with
|
||||||
// arguments argv and environment envv.
|
// the given arguments.
|
||||||
// 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.
|
|
||||||
//
|
//
|
||||||
// The arguments stdin, stdout, and stderr
|
// It sets Path and Args in the returned structure and zeroes the
|
||||||
// specify how to handle standard input, output, and error.
|
// other fields.
|
||||||
// The choices are DevNull (connect to /dev/null),
|
//
|
||||||
// PassThrough (connect to the current process's standard stream),
|
// If name contains no path separators, Command uses LookPath to
|
||||||
// Pipe (connect to an operating system pipe), and
|
// resolve the path to a complete name if possible. Otherwise it uses
|
||||||
// MergeWithStdout (only for standard error; use the same
|
// name directly.
|
||||||
// file descriptor as was used for standard output).
|
//
|
||||||
// If an argument is Pipe, then the corresponding field (Stdin, Stdout, Stderr)
|
// The returned Cmd's Args is constructed from the command name
|
||||||
// of the returned Cmd is the other end of the pipe.
|
// followed by the elements of arg, so arg should not include the
|
||||||
// Otherwise the field in Cmd is nil.
|
// command name itself. For example, Command("echo", "hello")
|
||||||
func Run(name string, argv, envv []string, dir string, stdin, stdout, stderr int) (c *Cmd, err os.Error) {
|
func Command(name string, arg ...string) *Cmd {
|
||||||
c = new(Cmd)
|
aname, err := LookPath(name)
|
||||||
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 err != nil {
|
if err != nil {
|
||||||
goto Error
|
aname = name
|
||||||
}
|
}
|
||||||
if fd[0] != os.Stdin {
|
return &Cmd{
|
||||||
fd[0].Close()
|
Path: aname,
|
||||||
|
Args: append([]string{name}, arg...),
|
||||||
|
err: err,
|
||||||
}
|
}
|
||||||
if fd[1] != os.Stdout {
|
|
||||||
fd[1].Close()
|
|
||||||
}
|
|
||||||
if fd[2] != os.Stderr && fd[2] != fd[1] {
|
|
||||||
fd[2].Close()
|
|
||||||
}
|
|
||||||
return c, nil
|
|
||||||
|
|
||||||
Error:
|
|
||||||
if fd[0] != os.Stdin && fd[0] != nil {
|
|
||||||
fd[0].Close()
|
|
||||||
}
|
|
||||||
if fd[1] != os.Stdout && fd[1] != nil {
|
|
||||||
fd[1].Close()
|
|
||||||
}
|
|
||||||
if fd[2] != os.Stderr && fd[2] != nil && fd[2] != fd[1] {
|
|
||||||
fd[2].Close()
|
|
||||||
}
|
|
||||||
if c.Stdin != nil {
|
|
||||||
c.Stdin.Close()
|
|
||||||
}
|
|
||||||
if c.Stdout != nil {
|
|
||||||
c.Stdout.Close()
|
|
||||||
}
|
|
||||||
if c.Stderr != nil {
|
|
||||||
c.Stderr.Close()
|
|
||||||
}
|
|
||||||
if c.Process != nil {
|
|
||||||
c.Process.Release()
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait waits for the running command c,
|
// interfaceEqual protects against panics from doing equality tests on
|
||||||
// returning the Waitmsg returned when the process exits.
|
// two interface with non-comparable underlying types
|
||||||
// The options are passed to the process's Wait method.
|
func interfaceEqual(a, b interface{}) bool {
|
||||||
// Setting options to 0 waits for c to exit;
|
defer func() {
|
||||||
// other options cause Wait to return for other
|
recover()
|
||||||
// process events; see package os for details.
|
}()
|
||||||
func (c *Cmd) Wait(options int) (*os.Waitmsg, os.Error) {
|
return a == b
|
||||||
if c.Process == nil {
|
|
||||||
return nil, os.ErrorString("exec: invalid use of Cmd.Wait")
|
|
||||||
}
|
|
||||||
w, err := c.Process.Wait(options)
|
|
||||||
if w != nil && (w.Exited() || w.Signaled()) {
|
|
||||||
c.Process.Release()
|
|
||||||
c.Process = nil
|
|
||||||
}
|
|
||||||
return w, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close waits for the running command c to exit,
|
func (c *Cmd) envv() []string {
|
||||||
// if it hasn't already, and then closes the non-nil file descriptors
|
if c.Env != nil {
|
||||||
// c.Stdin, c.Stdout, and c.Stderr.
|
return c.Env
|
||||||
func (c *Cmd) Close() os.Error {
|
}
|
||||||
if c.Process != nil {
|
return os.Environ()
|
||||||
// Loop on interrupt, but
|
}
|
||||||
// ignore other errors -- maybe
|
|
||||||
// caller has already waited for pid.
|
func (c *Cmd) argv() []string {
|
||||||
_, err := c.Wait(0)
|
if len(c.Args) > 0 {
|
||||||
for err == os.EINTR {
|
return c.Args
|
||||||
_, err = c.Wait(0)
|
}
|
||||||
|
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 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 c.writerDescriptor(c.Stderr)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 runs 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 *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()
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close the FDs that are still open.
|
|
||||||
var err os.Error
|
var err os.Error
|
||||||
if c.Stdin != nil && c.Stdin.Fd() >= 0 {
|
c.process, err = os.StartProcess(c.Path, c.argv(), &os.ProcAttr{
|
||||||
if err1 := c.Stdin.Close(); err1 != nil {
|
Dir: c.Dir,
|
||||||
err = err1
|
Files: c.childFiles,
|
||||||
}
|
Env: c.envv(),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
if c.Stdout != nil && c.Stdout.Fd() >= 0 {
|
|
||||||
if err1 := c.Stdout.Close(); err1 != nil && err != nil {
|
for _, fd := range c.closeAfterStart {
|
||||||
err = err1
|
fd.Close()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if c.Stderr != nil && c.Stderr != c.Stdout && c.Stderr.Fd() >= 0 {
|
|
||||||
if err1 := c.Stderr.Close(); err1 != nil && err != nil {
|
c.errch = make(chan os.Error, len(c.goroutine))
|
||||||
err = err1
|
for _, fn := range c.goroutine {
|
||||||
}
|
go func(fn func() os.Error) {
|
||||||
|
c.errch <- fn()
|
||||||
|
}(fn)
|
||||||
}
|
}
|
||||||
return err
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cmd) Wait() os.Error {
|
||||||
|
if c.process == nil {
|
||||||
|
return os.NewError("exec: not started")
|
||||||
|
}
|
||||||
|
msg, err := c.process.Wait(0)
|
||||||
|
|
||||||
|
var copyError os.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 !msg.Exited() || msg.ExitStatus() != 0 {
|
||||||
|
return msg
|
||||||
|
}
|
||||||
|
|
||||||
|
return copyError
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output runs the command and returns its standard output.
|
||||||
|
func (c *Cmd) Output() ([]byte, os.Error) {
|
||||||
|
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) {
|
||||||
|
var b bytes.Buffer
|
||||||
|
c.Stdout = &b
|
||||||
|
c.Stderr = &b
|
||||||
|
err := c.Run()
|
||||||
|
return b.Bytes(), err
|
||||||
}
|
}
|
||||||
|
@ -5,163 +5,139 @@
|
|||||||
package exec
|
package exec
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"testing"
|
"testing"
|
||||||
"os"
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func run(argv []string, stdin, stdout, stderr int) (p *Cmd, err os.Error) {
|
func helperCommand(s ...string) *Cmd {
|
||||||
exe, err := LookPath(argv[0])
|
cs := []string{"-test.run=exec.TestHelperProcess", "--"}
|
||||||
if err != nil {
|
cs = append(cs, s...)
|
||||||
return nil, err
|
cmd := Command(os.Args[0], cs...)
|
||||||
}
|
cmd.Env = append([]string{"GO_WANT_HELPER_PROCESS=1"}, os.Environ()...)
|
||||||
return Run(exe, argv, nil, "", stdin, stdout, stderr)
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRunCat(t *testing.T) {
|
func TestEcho(t *testing.T) {
|
||||||
cmd, err := run([]string{"cat"}, Pipe, Pipe, DevNull)
|
bs, err := helperCommand("echo", "foo bar", "baz").Output()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("run:", err)
|
t.Errorf("echo: %v", err)
|
||||||
}
|
}
|
||||||
io.WriteString(cmd.Stdin, "hello, world\n")
|
if g, e := string(bs), "foo bar baz\n"; g != e {
|
||||||
cmd.Stdin.Close()
|
t.Errorf("echo: want %q, got %q", e, g)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRunEcho(t *testing.T) {
|
func TestCatStdin(t *testing.T) {
|
||||||
cmd, err := run([]string{"bash", "-c", "echo hello world"},
|
// Cat, testing stdin and stdout.
|
||||||
DevNull, Pipe, DevNull)
|
input := "Input string\nLine 2"
|
||||||
|
p := helperCommand("cat")
|
||||||
|
p.Stdin = strings.NewReader(input)
|
||||||
|
bs, err := p.Output()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("run:", err)
|
t.Errorf("cat: %v", err)
|
||||||
}
|
}
|
||||||
buf, err := ioutil.ReadAll(cmd.Stdout)
|
s := string(bs)
|
||||||
if err != nil {
|
if s != input {
|
||||||
t.Fatal("read:", err)
|
t.Errorf("cat: want %q, got %q", input, s)
|
||||||
}
|
|
||||||
if string(buf) != "hello world\n" {
|
|
||||||
t.Fatalf("read: got %q", buf)
|
|
||||||
}
|
|
||||||
if err = cmd.Close(); err != nil {
|
|
||||||
t.Fatal("close:", err)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStderr(t *testing.T) {
|
func TestCatGoodAndBadFile(t *testing.T) {
|
||||||
cmd, err := run([]string{"bash", "-c", "echo hello world 1>&2"},
|
// Testing combined output and error values.
|
||||||
DevNull, DevNull, Pipe)
|
bs, err := helperCommand("cat", "/bogus/file.foo", "exec_test.go").CombinedOutput()
|
||||||
if err != nil {
|
if _, ok := err.(*os.Waitmsg); !ok {
|
||||||
t.Fatal("run:", err)
|
t.Errorf("expected Waitmsg from cat combined; got %T: %v", err, err)
|
||||||
}
|
}
|
||||||
buf, err := ioutil.ReadAll(cmd.Stderr)
|
s := string(bs)
|
||||||
if err != nil {
|
sp := strings.Split(s, "\n", 2)
|
||||||
t.Fatal("read:", err)
|
if len(sp) != 2 {
|
||||||
|
t.Fatalf("expected two lines from cat; got %q", s)
|
||||||
}
|
}
|
||||||
if string(buf) != "hello world\n" {
|
errLine, body := sp[0], sp[1]
|
||||||
t.Fatalf("read: got %q", buf)
|
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 {
|
if !strings.Contains(body, "func TestHelperProcess(t *testing.T)") {
|
||||||
t.Fatal("close:", err)
|
t.Errorf("expected test code; got %q (len %d)", body, len(body))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMergeWithStdout(t *testing.T) {
|
|
||||||
cmd, err := run([]string{"bash", "-c", "echo hello world 1>&2"},
|
func TestNoExistBinary(t *testing.T) {
|
||||||
DevNull, Pipe, MergeWithStdout)
|
// Can't run a non-existent binary
|
||||||
if err != nil {
|
err := Command("/no-exist-binary").Run()
|
||||||
t.Fatal("run:", err)
|
if err == nil {
|
||||||
}
|
t.Error("expected error from /no-exist-binary")
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAddEnvVar(t *testing.T) {
|
func TestExitStatus(t *testing.T) {
|
||||||
err := os.Setenv("NEWVAR", "hello world")
|
// Test that exit values are returned correctly
|
||||||
if err != nil {
|
err := helperCommand("exit", "42").Run()
|
||||||
t.Fatal("setenv:", err)
|
if werr, ok := err.(*os.Waitmsg); ok {
|
||||||
}
|
if s, e := werr.String(), "exit status 42"; s != e {
|
||||||
cmd, err := run([]string{"bash", "-c", "echo $NEWVAR"},
|
t.Errorf("from exit 42 got exit %q, want %q", s, e)
|
||||||
DevNull, Pipe, DevNull)
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
if err = cmd.Close(); err != nil {
|
|
||||||
t.Fatal("close:", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var tryargs = []string{
|
|
||||||
`2`,
|
|
||||||
`2 `,
|
|
||||||
"2 \t",
|
|
||||||
`2" "`,
|
|
||||||
`2 ab `,
|
|
||||||
`2 "ab" `,
|
|
||||||
`2 \ `,
|
|
||||||
`2 \\ `,
|
|
||||||
`2 \" `,
|
|
||||||
`2 \`,
|
|
||||||
`2\`,
|
|
||||||
`2"`,
|
|
||||||
`2\"`,
|
|
||||||
`2 "`,
|
|
||||||
`2 \"`,
|
|
||||||
``,
|
|
||||||
`2 ^ `,
|
|
||||||
`2 \^`,
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
cmd, err := Run(exe, argv, nil, "", DevNull, Pipe, DevNull)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("run:", err)
|
|
||||||
}
|
|
||||||
buf, err := ioutil.ReadAll(cmd.Stdout)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("read:", err)
|
|
||||||
}
|
|
||||||
expect := "/dev/null|" + a + "|EOF"
|
|
||||||
if string(buf) != expect {
|
|
||||||
t.Errorf("read: got %q expect %q", buf, expect)
|
|
||||||
}
|
|
||||||
if err = cmd.Close(); err != nil {
|
|
||||||
t.Fatal("close:", err)
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
t.Fatalf("expected Waitmsg from exit 42; got %T: %v", err, 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
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
fmt.Println(iargs...)
|
||||||
|
case "cat":
|
||||||
|
if len(args) == 0 {
|
||||||
|
io.Copy(os.Stdout, os.Stdin)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
os.Exit(exit)
|
||||||
|
case "exit":
|
||||||
|
n, _ := strconv.Atoi(args[0])
|
||||||
|
os.Exit(n)
|
||||||
|
default:
|
||||||
|
fmt.Fprintf(os.Stderr, "Unknown command %q\n", cmd)
|
||||||
|
os.Exit(2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -37,24 +37,14 @@ func init() {
|
|||||||
|
|
||||||
|
|
||||||
func compile(t *testing.T, dirname, filename string) {
|
func compile(t *testing.T, dirname, filename string) {
|
||||||
cmd, err := exec.Run(gcPath, []string{gcPath, filename}, nil, dirname, exec.DevNull, exec.Pipe, exec.MergeWithStdout)
|
cmd := exec.Command(gcPath, filename)
|
||||||
|
cmd.Dir = dirname
|
||||||
|
out, err := cmd.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("%s %s failed: %s", gcName, filename, err)
|
t.Errorf("%s %s failed: %s", gcName, filename, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer cmd.Close()
|
t.Logf("%s", string(out))
|
||||||
|
|
||||||
msg, err := cmd.Wait(0)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("%s %s failed: %s", gcName, filename, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !msg.Exited() || msg.ExitStatus() != 0 {
|
|
||||||
t.Errorf("%s %s failed: exit status = %d", gcName, filename, msg.ExitStatus())
|
|
||||||
output, _ := ioutil.ReadAll(cmd.Stdout)
|
|
||||||
t.Log(string(output))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -156,34 +156,38 @@ func (h *Handler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
|||||||
cwd = "."
|
cwd = "."
|
||||||
}
|
}
|
||||||
|
|
||||||
args := []string{h.Path}
|
internalError := func(err os.Error) {
|
||||||
args = append(args, h.Args...)
|
|
||||||
|
|
||||||
cmd, err := exec.Run(
|
|
||||||
pathBase,
|
|
||||||
args,
|
|
||||||
env,
|
|
||||||
cwd,
|
|
||||||
exec.Pipe, // stdin
|
|
||||||
exec.Pipe, // stdout
|
|
||||||
exec.PassThrough, // stderr (for now)
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
rw.WriteHeader(http.StatusInternalServerError)
|
rw.WriteHeader(http.StatusInternalServerError)
|
||||||
h.printf("CGI error: %v", err)
|
h.printf("CGI error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
stdoutRead, stdoutWrite, err := os.Pipe()
|
||||||
|
if err != nil {
|
||||||
|
internalError(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer func() {
|
|
||||||
cmd.Stdin.Close()
|
|
||||||
cmd.Stdout.Close()
|
|
||||||
cmd.Wait(0) // no zombies
|
|
||||||
}()
|
|
||||||
|
|
||||||
|
cmd := &exec.Cmd{
|
||||||
|
Path: pathBase,
|
||||||
|
Args: append([]string{h.Path}, h.Args...),
|
||||||
|
Dir: cwd,
|
||||||
|
Env: env,
|
||||||
|
Stdout: stdoutWrite,
|
||||||
|
Stderr: os.Stderr, // for now
|
||||||
|
}
|
||||||
if req.ContentLength != 0 {
|
if req.ContentLength != 0 {
|
||||||
go io.Copy(cmd.Stdin, req.Body)
|
cmd.Stdin = req.Body
|
||||||
}
|
}
|
||||||
|
|
||||||
linebody, _ := bufio.NewReaderSize(cmd.Stdout, 1024)
|
err = cmd.Start()
|
||||||
|
if err != nil {
|
||||||
|
internalError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
stdoutWrite.Close()
|
||||||
|
defer cmd.Wait()
|
||||||
|
|
||||||
|
linebody, _ := bufio.NewReaderSize(stdoutRead, 1024)
|
||||||
headers := make(http.Header)
|
headers := make(http.Header)
|
||||||
statusCode := 0
|
statusCode := 0
|
||||||
for {
|
for {
|
||||||
|
@ -17,20 +17,6 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
var cgiScriptWorks = canRun("./testdata/test.cgi")
|
|
||||||
|
|
||||||
func canRun(s string) bool {
|
|
||||||
c, err := exec.Run(s, []string{s}, nil, ".", exec.DevNull, exec.DevNull, exec.DevNull)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
w, err := c.Wait(0)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return w.Exited() && w.ExitStatus() == 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func newRequest(httpreq string) *http.Request {
|
func newRequest(httpreq string) *http.Request {
|
||||||
buf := bufio.NewReader(strings.NewReader(httpreq))
|
buf := bufio.NewReader(strings.NewReader(httpreq))
|
||||||
req, err := http.ReadRequest(buf)
|
req, err := http.ReadRequest(buf)
|
||||||
@ -76,8 +62,15 @@ readlines:
|
|||||||
return rw
|
return rw
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var cgiTested = false
|
||||||
|
var cgiWorks bool
|
||||||
|
|
||||||
func skipTest(t *testing.T) bool {
|
func skipTest(t *testing.T) bool {
|
||||||
if !cgiScriptWorks {
|
if !cgiTested {
|
||||||
|
cgiTested = true
|
||||||
|
cgiWorks = exec.Command("./testdata/test.cgi").Run() == nil
|
||||||
|
}
|
||||||
|
if !cgiWorks {
|
||||||
// No Perl on Windows, needed by test.cgi
|
// No Perl on Windows, needed by test.cgi
|
||||||
// TODO: make the child process be Go, not Perl.
|
// TODO: make the child process be Go, not Perl.
|
||||||
t.Logf("Skipping test: test.cgi failed.")
|
t.Logf("Skipping test: test.cgi failed.")
|
||||||
|
Loading…
Reference in New Issue
Block a user