blob: d656d0a908090d47ab0c67b098c4c4283da26eba [file] [log] [blame]
Cosmos Nicolaou62613842014-08-25 21:57:37 -07001package modules
2
3import (
Cosmos Nicolaou62613842014-08-25 21:57:37 -07004 "flag"
Cosmos Nicolaou62613842014-08-25 21:57:37 -07005 "io"
Cosmos Nicolaou62613842014-08-25 21:57:37 -07006 "os"
7 "os/exec"
8 "strings"
9 "sync"
10 "time"
11
Cosmos Nicolaou486d3492014-09-30 22:21:20 -070012 vexec "veyron.io/veyron/veyron/lib/exec"
Cosmos Nicolaou2cb05fb2014-10-23 13:37:32 -070013 "veyron.io/veyron/veyron2/vlog"
Cosmos Nicolaou62613842014-08-25 21:57:37 -070014)
15
16// execHandle implements both the command and Handle interfaces.
17type execHandle struct {
18 mu sync.Mutex
19 cmd *exec.Cmd
Cosmos Nicolaou2cb05fb2014-10-23 13:37:32 -070020 name string
Cosmos Nicolaou62613842014-08-25 21:57:37 -070021 entryPoint string
22 handle *vexec.ParentHandle
Cosmos Nicolaou66afced2014-09-15 22:12:43 -070023 sh *Shell
Cosmos Nicolaou62613842014-08-25 21:57:37 -070024 stderr *os.File
Cosmos Nicolaou9ca249d2014-09-18 15:07:12 -070025 stdout io.ReadCloser
Cosmos Nicolaou62613842014-08-25 21:57:37 -070026 stdin io.WriteCloser
27}
28
29func testFlags() []string {
30 var fl []string
31 // pass logging flags to any subprocesses
32 for fname, fval := range vlog.Log.ExplicitlySetFlags() {
33 fl = append(fl, "--"+fname+"="+fval)
34 }
35 timeout := flag.Lookup("test.timeout")
36 if timeout == nil {
37 // not a go test binary
38 return fl
39 }
40 // must be a go test binary
41 fl = append(fl, "-test.run=TestHelperProcess")
42 val := timeout.Value.(flag.Getter).Get().(time.Duration)
43 if val.String() != timeout.DefValue {
44 // use supplied command value for subprocesses
45 fl = append(fl, "--test.timeout="+timeout.Value.String())
46 } else {
47 // translate default value into 1m for subproccesses
48 fl = append(fl, "--test.timeout=1m")
49 }
50 return fl
51}
52
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -070053// IsTestHelperProces returns true if it is called in via
54// -run=TestHelperProcess which normally only ever happens for subprocesses
55// run from tests.
Cosmos Nicolaou62613842014-08-25 21:57:37 -070056func IsTestHelperProcess() bool {
57 runFlag := flag.Lookup("test.run")
58 if runFlag == nil {
59 return false
60 }
61 return runFlag.Value.String() == "TestHelperProcess"
62}
63
Cosmos Nicolaou2cb05fb2014-10-23 13:37:32 -070064func newExecHandle(name string) command {
Cosmos Nicolaoud4f00562014-11-17 20:35:48 -080065 return &execHandle{name: name, entryPoint: shellEntryPoint + "=" + name}
Cosmos Nicolaou62613842014-08-25 21:57:37 -070066}
67
Cosmos Nicolaou9ca249d2014-09-18 15:07:12 -070068func (eh *execHandle) Stdout() io.Reader {
Cosmos Nicolaou62613842014-08-25 21:57:37 -070069 eh.mu.Lock()
70 defer eh.mu.Unlock()
71 return eh.stdout
72}
73
74func (eh *execHandle) Stderr() io.Reader {
75 eh.mu.Lock()
76 defer eh.mu.Unlock()
77 return eh.stderr
78}
79
Cosmos Nicolaou66afced2014-09-15 22:12:43 -070080func (eh *execHandle) Stdin() io.Writer {
Cosmos Nicolaou62613842014-08-25 21:57:37 -070081 eh.mu.Lock()
82 defer eh.mu.Unlock()
83 return eh.stdin
84}
85
Cosmos Nicolaou66afced2014-09-15 22:12:43 -070086func (eh *execHandle) CloseStdin() {
87 eh.mu.Lock()
88 eh.stdin.Close()
89 eh.mu.Unlock()
90}
91
Cosmos Nicolaou2cb05fb2014-10-23 13:37:32 -070092func (eh *execHandle) envelope(sh *Shell, env []string, args ...string) ([]string, []string) {
Cosmos Nicolaoue664f3f2014-10-20 17:40:05 -070093 newargs := []string{os.Args[0]}
94 newargs = append(newargs, testFlags()...)
95 newargs = append(newargs, args...)
Cosmos Nicolaoud4f00562014-11-17 20:35:48 -080096 // Be careful to remove any existing shellEntryPoint env vars. This
Cosmos Nicolaoue5b41502014-10-29 22:55:09 -070097 // can happen when subprocesses run other subprocesses etc.
98 cleaned := make([]string, 0, len(env)+1)
99 for _, e := range env {
Cosmos Nicolaoud4f00562014-11-17 20:35:48 -0800100 if strings.HasPrefix(e, shellEntryPoint+"=") {
Cosmos Nicolaoue5b41502014-10-29 22:55:09 -0700101 continue
102 }
103 cleaned = append(cleaned, e)
104 }
105 return newargs, append(cleaned, eh.entryPoint)
Cosmos Nicolaoue664f3f2014-10-20 17:40:05 -0700106}
107
Cosmos Nicolaou2cb05fb2014-10-23 13:37:32 -0700108func (eh *execHandle) start(sh *Shell, env []string, args ...string) (Handle, error) {
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700109 eh.mu.Lock()
110 defer eh.mu.Unlock()
Cosmos Nicolaou66afced2014-09-15 22:12:43 -0700111 eh.sh = sh
Cosmos Nicolaou244d3442014-10-26 14:19:42 -0700112 newargs, newenv := eh.envelope(sh, env, args[1:]...)
113 cmd := exec.Command(os.Args[0], newargs[1:]...)
114 cmd.Env = newenv
115 stderr, err := newLogfile(strings.TrimLeft(eh.name, "-\n\t "))
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700116 if err != nil {
117 return nil, err
118 }
119 cmd.Stderr = stderr
120 stdout, err := cmd.StdoutPipe()
121 if err != nil {
122 return nil, err
123 }
124 stdin, err := cmd.StdinPipe()
125 if err != nil {
126 return nil, err
127 }
128
Jiri Simsa37893392014-11-07 10:55:45 -0800129 handle := vexec.NewParentHandle(cmd, vexec.ConfigOpt{Config: sh.config})
Cosmos Nicolaou9ca249d2014-09-18 15:07:12 -0700130 eh.stdout = stdout
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700131 eh.stderr = stderr
132 eh.stdin = stdin
133 eh.handle = handle
134 eh.cmd = cmd
Cosmos Nicolaou2cb05fb2014-10-23 13:37:32 -0700135 vlog.VI(1).Infof("Start: %q args: %v", eh.name, cmd.Args)
136 vlog.VI(2).Infof("Start: %q env: %v", eh.name, cmd.Env)
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700137 if err := handle.Start(); err != nil {
138 return nil, err
139 }
Cosmos Nicolaoue5b41502014-10-29 22:55:09 -0700140 vlog.VI(1).Infof("Started: %q, pid %d", eh.name, cmd.Process.Pid)
141 err = handle.WaitForReady(sh.startTimeout)
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700142 return eh, err
143}
144
Cosmos Nicolaoucc581722014-10-07 12:45:39 -0700145func (eh *execHandle) Pid() int {
146 return eh.cmd.Process.Pid
147}
148
Cosmos Nicolaoubbae3882014-10-02 22:58:19 -0700149func (eh *execHandle) Shutdown(stdout, stderr io.Writer) error {
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700150 eh.mu.Lock()
151 defer eh.mu.Unlock()
Cosmos Nicolaou2cb05fb2014-10-23 13:37:32 -0700152 vlog.VI(1).Infof("Shutdown: %q", eh.name)
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700153 eh.stdin.Close()
Cosmos Nicolaoubbae3882014-10-02 22:58:19 -0700154 logFile := eh.stderr.Name()
Cosmos Nicolaoue664f3f2014-10-20 17:40:05 -0700155 defer eh.sh.Forget(eh)
Cosmos Nicolaoubbae3882014-10-02 22:58:19 -0700156
157 defer func() {
158 os.Remove(logFile)
159 }()
160
Cosmos Nicolaou2cb05fb2014-10-23 13:37:32 -0700161 // TODO(cnicolaou): make this configurable
162 timeout := 10 * time.Second
Cosmos Nicolaoubbae3882014-10-02 22:58:19 -0700163 if stdout == nil && stderr == nil {
Cosmos Nicolaou2cb05fb2014-10-23 13:37:32 -0700164 return eh.handle.Wait(timeout)
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700165 }
Cosmos Nicolaoubbae3882014-10-02 22:58:19 -0700166
Cosmos Nicolaou2cb05fb2014-10-23 13:37:32 -0700167 if stdout != nil {
168 // Read from stdin before waiting for the child process to ensure
169 // that we get to read all of its output.
170 readTo(eh.stdout, stdout)
171 }
172
173 procErr := eh.handle.Wait(timeout)
Cosmos Nicolaoubbae3882014-10-02 22:58:19 -0700174
175 // Stderr is buffered to a file, so we can safely read it after we
176 // wait for the process.
177 eh.stderr.Close()
Cosmos Nicolaoue664f3f2014-10-20 17:40:05 -0700178 if stderr != nil {
179 stderrFile, err := os.Open(logFile)
180 if err != nil {
Cosmos Nicolaou2cb05fb2014-10-23 13:37:32 -0700181 vlog.VI(1).Infof("failed to open %q: %s\n", logFile, err)
Cosmos Nicolaoue664f3f2014-10-20 17:40:05 -0700182 return procErr
183 }
184 readTo(stderrFile, stderr)
185 stderrFile.Close()
Cosmos Nicolaoubbae3882014-10-02 22:58:19 -0700186 }
Cosmos Nicolaoubbae3882014-10-02 22:58:19 -0700187 return procErr
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700188}