blob: 44a6a1af406eadf809ccdbc9b73ea9516f775e20 [file] [log] [blame]
Jiri Simsad7616c92015-03-24 23:44:30 -07001// Copyright 2015 The Vanadium 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
Cosmos Nicolaou62613842014-08-25 21:57:37 -07005package modules
6
7import (
Cosmos Nicolaou62613842014-08-25 21:57:37 -07008 "flag"
Cosmos Nicolaou62613842014-08-25 21:57:37 -07009 "io"
Cosmos Nicolaou62613842014-08-25 21:57:37 -070010 "os"
11 "os/exec"
Cosmos Nicolaou62613842014-08-25 21:57:37 -070012 "sync"
13 "time"
14
Todd Wang3bb46f52015-05-14 22:01:18 -070015 "v.io/x/lib/envvar"
Cosmos Nicolaou0e4e3922015-06-10 16:30:09 -070016
Cosmos Nicolaoue3b19322015-06-18 16:05:08 -070017 "v.io/v23/logging"
Cosmos Nicolaou0e4e3922015-06-10 16:30:09 -070018 "v.io/v23/verror"
19
20 "v.io/x/ref/internal/logger"
Jiri Simsaffceefa2015-02-28 11:03:34 -080021 vexec "v.io/x/ref/lib/exec"
Todd Wang712eeef2015-04-03 17:13:50 -070022 "v.io/x/ref/lib/mgmt"
Cosmos Nicolaou1381f8a2015-03-13 09:40:34 -070023 "v.io/x/ref/test/expect"
Cosmos Nicolaou62613842014-08-25 21:57:37 -070024)
25
Todd Wang95873902015-05-22 14:21:30 -070026// execHandle implements both the Handle interface.
Cosmos Nicolaou62613842014-08-25 21:57:37 -070027type execHandle struct {
Cosmos Nicolaou42a17362015-03-10 16:40:18 -070028 *expect.Session
Todd Wang95873902015-05-22 14:21:30 -070029 mu sync.Mutex
30 cmd *exec.Cmd
31 entryPoint string
32 desc string
33 handle *vexec.ParentHandle
34 sh *Shell
35 stderr *os.File
36 stdout io.ReadCloser
37 stdin io.WriteCloser
38 procErrCh chan error
39 opts *StartOpts
40 external bool
Cosmos Nicolaou62613842014-08-25 21:57:37 -070041}
42
Cosmos Nicolaoue3b19322015-06-18 16:05:08 -070043func testFlags(l logging.Logger) []string {
Cosmos Nicolaou62613842014-08-25 21:57:37 -070044 var fl []string
45 // pass logging flags to any subprocesses
Cosmos Nicolaoue3b19322015-06-18 16:05:08 -070046
47 flags := logger.Manager(l).ExplicitlySetFlags()
Cosmos Nicolaou0e4e3922015-06-10 16:30:09 -070048 for fname, fval := range flags {
Cosmos Nicolaou62613842014-08-25 21:57:37 -070049 fl = append(fl, "--"+fname+"="+fval)
50 }
51 timeout := flag.Lookup("test.timeout")
52 if timeout == nil {
53 // not a go test binary
54 return fl
55 }
56 // must be a go test binary
Cosmos Nicolaou62613842014-08-25 21:57:37 -070057 val := timeout.Value.(flag.Getter).Get().(time.Duration)
58 if val.String() != timeout.DefValue {
Todd Wang95873902015-05-22 14:21:30 -070059 // use supplied value for subprocesses
Cosmos Nicolaou62613842014-08-25 21:57:37 -070060 fl = append(fl, "--test.timeout="+timeout.Value.String())
61 } else {
Bogdan Caprita73ca5e72014-11-24 15:55:48 -080062 // translate default value into 3m for subproccesses. The
63 // default of 10m is too long to wait in order to find out that
64 // our subprocess is wedged.
65 fl = append(fl, "--test.timeout=3m")
Cosmos Nicolaou62613842014-08-25 21:57:37 -070066 }
67 return fl
68}
69
Todd Wang95873902015-05-22 14:21:30 -070070func newExecHandle(entry, desc string) *execHandle {
71 return &execHandle{entryPoint: entry, desc: desc, procErrCh: make(chan error, 1)}
Cosmos Nicolaou62613842014-08-25 21:57:37 -070072}
73
Todd Wang95873902015-05-22 14:21:30 -070074func newExecHandleExternal(prog string) *execHandle {
75 return &execHandle{entryPoint: prog, desc: prog, procErrCh: make(chan error, 1), external: true}
James Ring9d9489d2015-01-27 15:48:07 -080076}
77
Cosmos Nicolaou9ca249d2014-09-18 15:07:12 -070078func (eh *execHandle) Stdout() io.Reader {
Cosmos Nicolaou62613842014-08-25 21:57:37 -070079 eh.mu.Lock()
80 defer eh.mu.Unlock()
81 return eh.stdout
82}
83
84func (eh *execHandle) Stderr() io.Reader {
85 eh.mu.Lock()
86 defer eh.mu.Unlock()
87 return eh.stderr
88}
89
Cosmos Nicolaou66afced2014-09-15 22:12:43 -070090func (eh *execHandle) Stdin() io.Writer {
Cosmos Nicolaou62613842014-08-25 21:57:37 -070091 eh.mu.Lock()
92 defer eh.mu.Unlock()
93 return eh.stdin
94}
95
Cosmos Nicolaou66afced2014-09-15 22:12:43 -070096func (eh *execHandle) CloseStdin() {
97 eh.mu.Lock()
98 eh.stdin.Close()
99 eh.mu.Unlock()
100}
101
Todd Wang5507c832015-05-15 22:59:23 -0700102func (eh *execHandle) envelope(sh *Shell, env []string, args []string) ([]string, []string) {
103 if eh.external {
Todd Wang95873902015-05-22 14:21:30 -0700104 newargs := append([]string{eh.entryPoint}, args...)
Todd Wang5507c832015-05-15 22:59:23 -0700105 newenv := envvar.SliceToMap(env)
106 delete(newenv, shellEntryPoint)
107 return newargs, envvar.MapToSlice(newenv)
108 }
Cosmos Nicolaoue3b19322015-06-18 16:05:08 -0700109 newargs := append([]string{os.Args[0]}, testFlags(sh.logger)...)
Cosmos Nicolaoue664f3f2014-10-20 17:40:05 -0700110 newargs = append(newargs, args...)
Todd Wang3bb46f52015-05-14 22:01:18 -0700111 newenv := envvar.SliceToMap(env)
Todd Wang95873902015-05-22 14:21:30 -0700112 newenv[shellEntryPoint] = eh.entryPoint
Todd Wang3bb46f52015-05-14 22:01:18 -0700113 return newargs, envvar.MapToSlice(newenv)
Cosmos Nicolaoue664f3f2014-10-20 17:40:05 -0700114}
115
Ryan Brown04384432015-08-27 16:08:32 -0700116func (eh *execHandle) start(sh *Shell, agentPath string, opts *StartOpts, env []string, args []string) (*execHandle, error) {
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700117 eh.mu.Lock()
118 defer eh.mu.Unlock()
Cosmos Nicolaou66afced2014-09-15 22:12:43 -0700119 eh.sh = sh
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700120 eh.opts = opts
Todd Wang5507c832015-05-15 22:59:23 -0700121 args, env = eh.envelope(sh, env, args)
122 cmd := exec.Command(args[0], args[1:]...)
123 cmd.Env = env
Cosmos Nicolaoud61e5a82015-02-22 16:32:48 -0800124
Todd Wang95873902015-05-22 14:21:30 -0700125 stderr, err := newLogfile("stderr", eh.entryPoint)
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700126 if err != nil {
127 return nil, err
128 }
129 cmd.Stderr = stderr
Bogdan Caprita490a4512014-11-20 21:12:19 -0800130 // We use a custom queue-based Writer implementation for stdout to
131 // decouple the consumers of eh.stdout from the file where the child
132 // sends its output. This avoids data races between closing the file
133 // and reading from it (since cmd.Wait will wait for the all readers to
134 // be done before closing it). It also enables Shutdown to drain stdout
135 // while respecting the timeout.
136 stdout := newRW()
137 cmd.Stdout = stdout
Cosmos Nicolaoud61e5a82015-02-22 16:32:48 -0800138
139 // If we have an explicit stdin to pass to the child, use that,
140 // otherwise create a pipe and return the write side of that pipe
141 // in the handle.
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700142 if eh.opts.Stdin != nil {
143 cmd.Stdin = eh.opts.Stdin
Cosmos Nicolaoud61e5a82015-02-22 16:32:48 -0800144 } else {
145 stdin, err := cmd.StdinPipe()
146 if err != nil {
147 return nil, err
148 }
149 eh.stdin = stdin
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700150 }
Ryan Browna08a2212015-01-15 15:40:10 -0800151 config := vexec.NewConfig()
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700152
153 execOpts := []vexec.ParentHandleOpt{}
154 if !eh.opts.ExecProtocol {
155 execOpts = append(execOpts, vexec.UseExecProtocolOpt(false))
156 } else {
157 serialized, err := sh.config.Serialize()
158 if err != nil {
159 return nil, err
160 }
161 config.MergeFrom(serialized)
Ryan Brown04384432015-08-27 16:08:32 -0700162 if agentPath != "" {
163 config.Set(mgmt.SecurityAgentPathConfigKey, agentPath)
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700164 }
165 execOpts = append(execOpts, vexec.ConfigOpt{Config: config})
Ryan Browna08a2212015-01-15 15:40:10 -0800166 }
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700167
Todd Wang95873902015-05-22 14:21:30 -0700168 // TODO(cnicolaou): for external programs, vexec should either not be
Cosmos Nicolaoud61e5a82015-02-22 16:32:48 -0800169 // used or it should taken an option to not use its protocol, and in
170 // particular to share secrets with children.
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700171 handle := vexec.NewParentHandle(cmd, execOpts...)
Cosmos Nicolaou9ca249d2014-09-18 15:07:12 -0700172 eh.stdout = stdout
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700173 eh.stderr = stderr
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700174 eh.handle = handle
175 eh.cmd = cmd
Cosmos Nicolaoue3b19322015-06-18 16:05:08 -0700176 eh.sh.logger.VI(1).Infof("Start: %q stderr: %s", eh.desc, stderr.Name())
177 eh.sh.logger.VI(1).Infof("Start: %q args: %v", eh.desc, cmd.Args)
178 eh.sh.logger.VI(2).Infof("Start: %q env: %v", eh.desc, cmd.Env)
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700179 if err := handle.Start(); err != nil {
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700180 // The child process failed to start, either because of some setup
181 // error (e.g. creating pipes for it to use), or a bad binary etc.
182 // A handle is returned, so that Shutdown etc may be called, hence
183 // the error must be sent over eh.procErrCh to allow Shutdown to
184 // terminate.
185 eh.procErrCh <- err
186 return eh, err
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700187 }
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700188 if eh.opts.ExecProtocol {
189 if err := eh.handle.WaitForReady(eh.opts.StartTimeout); err != nil {
190 // The child failed to call SetReady, most likely because of bad
191 // command line arguments or some other early exit in the child
192 // process.
193 // As per Start above, a handle is returned and the error
194 // sent over eh.procErrCh.
195 eh.procErrCh <- err
196 return eh, err
197 }
198 }
Cosmos Nicolaoue3b19322015-06-18 16:05:08 -0700199 eh.sh.logger.VI(1).Infof("Started: %q, pid %d", eh.desc, cmd.Process.Pid)
Bogdan Caprita490a4512014-11-20 21:12:19 -0800200 go func() {
201 eh.procErrCh <- eh.handle.Wait(0)
202 // It's now safe to close eh.stdout, since Wait only returns
203 // once all writes from the pipe to the stdout Writer have
204 // completed. Closing eh.stdout lets consumers of stdout wrap
205 // up (they'll receive EOF).
206 eh.stdout.Close()
207 }()
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700208 eh.Session = expect.NewSession(opts.ExpectTesting, stdout, opts.ExpectTimeout)
209 eh.Session.SetVerbosity(eh.sh.sessionVerbosity)
James Ring9d9489d2015-01-27 15:48:07 -0800210 return eh, nil
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700211}
212
Cosmos Nicolaoucc581722014-10-07 12:45:39 -0700213func (eh *execHandle) Pid() int {
214 return eh.cmd.Process.Pid
215}
216
Cosmos Nicolaoubbae3882014-10-02 22:58:19 -0700217func (eh *execHandle) Shutdown(stdout, stderr io.Writer) error {
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700218 eh.mu.Lock()
219 defer eh.mu.Unlock()
Cosmos Nicolaoue3b19322015-06-18 16:05:08 -0700220 eh.sh.logger.VI(1).Infof("Shutdown: %q", eh.desc)
221 defer eh.sh.logger.VI(1).Infof("Shutdown: %q [DONE]", eh.desc)
Cosmos Nicolaoud61e5a82015-02-22 16:32:48 -0800222 if eh.stdin != nil {
223 eh.stdin.Close()
224 }
Cosmos Nicolaoue664f3f2014-10-20 17:40:05 -0700225 defer eh.sh.Forget(eh)
Cosmos Nicolaoubbae3882014-10-02 22:58:19 -0700226
Bogdan Caprita490a4512014-11-20 21:12:19 -0800227 waitStdout := make(chan struct{})
Cosmos Nicolaou2cb05fb2014-10-23 13:37:32 -0700228 if stdout != nil {
Bogdan Caprita490a4512014-11-20 21:12:19 -0800229 // Drain stdout.
230 go func() {
231 io.Copy(stdout, eh.stdout)
232 close(waitStdout)
233 }()
234 } else {
235 close(waitStdout)
Cosmos Nicolaou2cb05fb2014-10-23 13:37:32 -0700236 }
237
Bogdan Caprita490a4512014-11-20 21:12:19 -0800238 var procErr error
239 select {
240 case procErr = <-eh.procErrCh:
241 // The child has exited already.
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700242 case <-time.After(eh.opts.ShutdownTimeout):
Bogdan Caprita490a4512014-11-20 21:12:19 -0800243 // Time out waiting for child to exit.
Mike Burrowsccca2f42015-03-27 13:57:29 -0700244 procErr = verror.New(vexec.ErrTimeout, nil)
Bogdan Caprita490a4512014-11-20 21:12:19 -0800245 // Force close stdout to unblock any readers of stdout
246 // (including the drain loop started above).
247 eh.stdout.Close()
Cosmos Nicolaoubbae3882014-10-02 22:58:19 -0700248 }
Bogdan Caprita490a4512014-11-20 21:12:19 -0800249 <-waitStdout
250
251 // Transcribe stderr.
252 outputFromFile(eh.stderr, stderr)
Robin Thellend1c8a8282014-12-09 10:38:36 -0800253 os.Remove(eh.stderr.Name())
Bogdan Caprita490a4512014-11-20 21:12:19 -0800254
Cosmos Nicolaoubbae3882014-10-02 22:58:19 -0700255 return procErr
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700256}