Jiri Simsa | d7616c9 | 2015-03-24 23:44:30 -0700 | [diff] [blame] | 1 | // 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 Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 5 | package modules |
| 6 | |
| 7 | import ( |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 8 | "flag" |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 9 | "io" |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 10 | "os" |
| 11 | "os/exec" |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 12 | "sync" |
| 13 | "time" |
| 14 | |
Todd Wang | 3bb46f5 | 2015-05-14 22:01:18 -0700 | [diff] [blame] | 15 | "v.io/x/lib/envvar" |
Cosmos Nicolaou | 0e4e392 | 2015-06-10 16:30:09 -0700 | [diff] [blame] | 16 | |
Cosmos Nicolaou | e3b1932 | 2015-06-18 16:05:08 -0700 | [diff] [blame] | 17 | "v.io/v23/logging" |
Cosmos Nicolaou | 0e4e392 | 2015-06-10 16:30:09 -0700 | [diff] [blame] | 18 | "v.io/v23/verror" |
| 19 | |
| 20 | "v.io/x/ref/internal/logger" |
Jiri Simsa | ffceefa | 2015-02-28 11:03:34 -0800 | [diff] [blame] | 21 | vexec "v.io/x/ref/lib/exec" |
Todd Wang | 712eeef | 2015-04-03 17:13:50 -0700 | [diff] [blame] | 22 | "v.io/x/ref/lib/mgmt" |
Cosmos Nicolaou | 1381f8a | 2015-03-13 09:40:34 -0700 | [diff] [blame] | 23 | "v.io/x/ref/test/expect" |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 24 | ) |
| 25 | |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 26 | // execHandle implements both the Handle interface. |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 27 | type execHandle struct { |
Cosmos Nicolaou | 42a1736 | 2015-03-10 16:40:18 -0700 | [diff] [blame] | 28 | *expect.Session |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 29 | 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 Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 41 | } |
| 42 | |
Cosmos Nicolaou | e3b1932 | 2015-06-18 16:05:08 -0700 | [diff] [blame] | 43 | func testFlags(l logging.Logger) []string { |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 44 | var fl []string |
| 45 | // pass logging flags to any subprocesses |
Cosmos Nicolaou | e3b1932 | 2015-06-18 16:05:08 -0700 | [diff] [blame] | 46 | |
| 47 | flags := logger.Manager(l).ExplicitlySetFlags() |
Cosmos Nicolaou | 0e4e392 | 2015-06-10 16:30:09 -0700 | [diff] [blame] | 48 | for fname, fval := range flags { |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 49 | 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 Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 57 | val := timeout.Value.(flag.Getter).Get().(time.Duration) |
| 58 | if val.String() != timeout.DefValue { |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 59 | // use supplied value for subprocesses |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 60 | fl = append(fl, "--test.timeout="+timeout.Value.String()) |
| 61 | } else { |
Bogdan Caprita | 73ca5e7 | 2014-11-24 15:55:48 -0800 | [diff] [blame] | 62 | // 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 Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 66 | } |
| 67 | return fl |
| 68 | } |
| 69 | |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 70 | func newExecHandle(entry, desc string) *execHandle { |
| 71 | return &execHandle{entryPoint: entry, desc: desc, procErrCh: make(chan error, 1)} |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 72 | } |
| 73 | |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 74 | func newExecHandleExternal(prog string) *execHandle { |
| 75 | return &execHandle{entryPoint: prog, desc: prog, procErrCh: make(chan error, 1), external: true} |
James Ring | 9d9489d | 2015-01-27 15:48:07 -0800 | [diff] [blame] | 76 | } |
| 77 | |
Cosmos Nicolaou | 9ca249d | 2014-09-18 15:07:12 -0700 | [diff] [blame] | 78 | func (eh *execHandle) Stdout() io.Reader { |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 79 | eh.mu.Lock() |
| 80 | defer eh.mu.Unlock() |
| 81 | return eh.stdout |
| 82 | } |
| 83 | |
| 84 | func (eh *execHandle) Stderr() io.Reader { |
| 85 | eh.mu.Lock() |
| 86 | defer eh.mu.Unlock() |
| 87 | return eh.stderr |
| 88 | } |
| 89 | |
Cosmos Nicolaou | 66afced | 2014-09-15 22:12:43 -0700 | [diff] [blame] | 90 | func (eh *execHandle) Stdin() io.Writer { |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 91 | eh.mu.Lock() |
| 92 | defer eh.mu.Unlock() |
| 93 | return eh.stdin |
| 94 | } |
| 95 | |
Cosmos Nicolaou | 66afced | 2014-09-15 22:12:43 -0700 | [diff] [blame] | 96 | func (eh *execHandle) CloseStdin() { |
| 97 | eh.mu.Lock() |
| 98 | eh.stdin.Close() |
| 99 | eh.mu.Unlock() |
| 100 | } |
| 101 | |
Todd Wang | 5507c83 | 2015-05-15 22:59:23 -0700 | [diff] [blame] | 102 | func (eh *execHandle) envelope(sh *Shell, env []string, args []string) ([]string, []string) { |
| 103 | if eh.external { |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 104 | newargs := append([]string{eh.entryPoint}, args...) |
Todd Wang | 5507c83 | 2015-05-15 22:59:23 -0700 | [diff] [blame] | 105 | newenv := envvar.SliceToMap(env) |
| 106 | delete(newenv, shellEntryPoint) |
| 107 | return newargs, envvar.MapToSlice(newenv) |
| 108 | } |
Cosmos Nicolaou | e3b1932 | 2015-06-18 16:05:08 -0700 | [diff] [blame] | 109 | newargs := append([]string{os.Args[0]}, testFlags(sh.logger)...) |
Cosmos Nicolaou | e664f3f | 2014-10-20 17:40:05 -0700 | [diff] [blame] | 110 | newargs = append(newargs, args...) |
Todd Wang | 3bb46f5 | 2015-05-14 22:01:18 -0700 | [diff] [blame] | 111 | newenv := envvar.SliceToMap(env) |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 112 | newenv[shellEntryPoint] = eh.entryPoint |
Todd Wang | 3bb46f5 | 2015-05-14 22:01:18 -0700 | [diff] [blame] | 113 | return newargs, envvar.MapToSlice(newenv) |
Cosmos Nicolaou | e664f3f | 2014-10-20 17:40:05 -0700 | [diff] [blame] | 114 | } |
| 115 | |
Ryan Brown | 0438443 | 2015-08-27 16:08:32 -0700 | [diff] [blame] | 116 | func (eh *execHandle) start(sh *Shell, agentPath string, opts *StartOpts, env []string, args []string) (*execHandle, error) { |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 117 | eh.mu.Lock() |
| 118 | defer eh.mu.Unlock() |
Cosmos Nicolaou | 66afced | 2014-09-15 22:12:43 -0700 | [diff] [blame] | 119 | eh.sh = sh |
Cosmos Nicolaou | 42a1736 | 2015-03-10 16:40:18 -0700 | [diff] [blame] | 120 | eh.opts = opts |
Todd Wang | 5507c83 | 2015-05-15 22:59:23 -0700 | [diff] [blame] | 121 | args, env = eh.envelope(sh, env, args) |
| 122 | cmd := exec.Command(args[0], args[1:]...) |
| 123 | cmd.Env = env |
Cosmos Nicolaou | d61e5a8 | 2015-02-22 16:32:48 -0800 | [diff] [blame] | 124 | |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 125 | stderr, err := newLogfile("stderr", eh.entryPoint) |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 126 | if err != nil { |
| 127 | return nil, err |
| 128 | } |
| 129 | cmd.Stderr = stderr |
Bogdan Caprita | 490a451 | 2014-11-20 21:12:19 -0800 | [diff] [blame] | 130 | // 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 Nicolaou | d61e5a8 | 2015-02-22 16:32:48 -0800 | [diff] [blame] | 138 | |
| 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 Nicolaou | 42a1736 | 2015-03-10 16:40:18 -0700 | [diff] [blame] | 142 | if eh.opts.Stdin != nil { |
| 143 | cmd.Stdin = eh.opts.Stdin |
Cosmos Nicolaou | d61e5a8 | 2015-02-22 16:32:48 -0800 | [diff] [blame] | 144 | } else { |
| 145 | stdin, err := cmd.StdinPipe() |
| 146 | if err != nil { |
| 147 | return nil, err |
| 148 | } |
| 149 | eh.stdin = stdin |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 150 | } |
Ryan Brown | a08a221 | 2015-01-15 15:40:10 -0800 | [diff] [blame] | 151 | config := vexec.NewConfig() |
Cosmos Nicolaou | 42a1736 | 2015-03-10 16:40:18 -0700 | [diff] [blame] | 152 | |
| 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 Brown | 0438443 | 2015-08-27 16:08:32 -0700 | [diff] [blame] | 162 | if agentPath != "" { |
| 163 | config.Set(mgmt.SecurityAgentPathConfigKey, agentPath) |
Cosmos Nicolaou | 42a1736 | 2015-03-10 16:40:18 -0700 | [diff] [blame] | 164 | } |
| 165 | execOpts = append(execOpts, vexec.ConfigOpt{Config: config}) |
Ryan Brown | a08a221 | 2015-01-15 15:40:10 -0800 | [diff] [blame] | 166 | } |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 167 | |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 168 | // TODO(cnicolaou): for external programs, vexec should either not be |
Cosmos Nicolaou | d61e5a8 | 2015-02-22 16:32:48 -0800 | [diff] [blame] | 169 | // used or it should taken an option to not use its protocol, and in |
| 170 | // particular to share secrets with children. |
Cosmos Nicolaou | 42a1736 | 2015-03-10 16:40:18 -0700 | [diff] [blame] | 171 | handle := vexec.NewParentHandle(cmd, execOpts...) |
Cosmos Nicolaou | 9ca249d | 2014-09-18 15:07:12 -0700 | [diff] [blame] | 172 | eh.stdout = stdout |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 173 | eh.stderr = stderr |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 174 | eh.handle = handle |
| 175 | eh.cmd = cmd |
Cosmos Nicolaou | e3b1932 | 2015-06-18 16:05:08 -0700 | [diff] [blame] | 176 | 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 Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 179 | if err := handle.Start(); err != nil { |
Cosmos Nicolaou | 42a1736 | 2015-03-10 16:40:18 -0700 | [diff] [blame] | 180 | // 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 Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 187 | } |
Cosmos Nicolaou | 42a1736 | 2015-03-10 16:40:18 -0700 | [diff] [blame] | 188 | 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 Nicolaou | e3b1932 | 2015-06-18 16:05:08 -0700 | [diff] [blame] | 199 | eh.sh.logger.VI(1).Infof("Started: %q, pid %d", eh.desc, cmd.Process.Pid) |
Bogdan Caprita | 490a451 | 2014-11-20 21:12:19 -0800 | [diff] [blame] | 200 | 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 Nicolaou | 42a1736 | 2015-03-10 16:40:18 -0700 | [diff] [blame] | 208 | eh.Session = expect.NewSession(opts.ExpectTesting, stdout, opts.ExpectTimeout) |
| 209 | eh.Session.SetVerbosity(eh.sh.sessionVerbosity) |
James Ring | 9d9489d | 2015-01-27 15:48:07 -0800 | [diff] [blame] | 210 | return eh, nil |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 211 | } |
| 212 | |
Cosmos Nicolaou | cc58172 | 2014-10-07 12:45:39 -0700 | [diff] [blame] | 213 | func (eh *execHandle) Pid() int { |
| 214 | return eh.cmd.Process.Pid |
| 215 | } |
| 216 | |
Cosmos Nicolaou | bbae388 | 2014-10-02 22:58:19 -0700 | [diff] [blame] | 217 | func (eh *execHandle) Shutdown(stdout, stderr io.Writer) error { |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 218 | eh.mu.Lock() |
| 219 | defer eh.mu.Unlock() |
Cosmos Nicolaou | e3b1932 | 2015-06-18 16:05:08 -0700 | [diff] [blame] | 220 | eh.sh.logger.VI(1).Infof("Shutdown: %q", eh.desc) |
| 221 | defer eh.sh.logger.VI(1).Infof("Shutdown: %q [DONE]", eh.desc) |
Cosmos Nicolaou | d61e5a8 | 2015-02-22 16:32:48 -0800 | [diff] [blame] | 222 | if eh.stdin != nil { |
| 223 | eh.stdin.Close() |
| 224 | } |
Cosmos Nicolaou | e664f3f | 2014-10-20 17:40:05 -0700 | [diff] [blame] | 225 | defer eh.sh.Forget(eh) |
Cosmos Nicolaou | bbae388 | 2014-10-02 22:58:19 -0700 | [diff] [blame] | 226 | |
Bogdan Caprita | 490a451 | 2014-11-20 21:12:19 -0800 | [diff] [blame] | 227 | waitStdout := make(chan struct{}) |
Cosmos Nicolaou | 2cb05fb | 2014-10-23 13:37:32 -0700 | [diff] [blame] | 228 | if stdout != nil { |
Bogdan Caprita | 490a451 | 2014-11-20 21:12:19 -0800 | [diff] [blame] | 229 | // Drain stdout. |
| 230 | go func() { |
| 231 | io.Copy(stdout, eh.stdout) |
| 232 | close(waitStdout) |
| 233 | }() |
| 234 | } else { |
| 235 | close(waitStdout) |
Cosmos Nicolaou | 2cb05fb | 2014-10-23 13:37:32 -0700 | [diff] [blame] | 236 | } |
| 237 | |
Bogdan Caprita | 490a451 | 2014-11-20 21:12:19 -0800 | [diff] [blame] | 238 | var procErr error |
| 239 | select { |
| 240 | case procErr = <-eh.procErrCh: |
| 241 | // The child has exited already. |
Cosmos Nicolaou | 42a1736 | 2015-03-10 16:40:18 -0700 | [diff] [blame] | 242 | case <-time.After(eh.opts.ShutdownTimeout): |
Bogdan Caprita | 490a451 | 2014-11-20 21:12:19 -0800 | [diff] [blame] | 243 | // Time out waiting for child to exit. |
Mike Burrows | ccca2f4 | 2015-03-27 13:57:29 -0700 | [diff] [blame] | 244 | procErr = verror.New(vexec.ErrTimeout, nil) |
Bogdan Caprita | 490a451 | 2014-11-20 21:12:19 -0800 | [diff] [blame] | 245 | // Force close stdout to unblock any readers of stdout |
| 246 | // (including the drain loop started above). |
| 247 | eh.stdout.Close() |
Cosmos Nicolaou | bbae388 | 2014-10-02 22:58:19 -0700 | [diff] [blame] | 248 | } |
Bogdan Caprita | 490a451 | 2014-11-20 21:12:19 -0800 | [diff] [blame] | 249 | <-waitStdout |
| 250 | |
| 251 | // Transcribe stderr. |
| 252 | outputFromFile(eh.stderr, stderr) |
Robin Thellend | 1c8a828 | 2014-12-09 10:38:36 -0800 | [diff] [blame] | 253 | os.Remove(eh.stderr.Name()) |
Bogdan Caprita | 490a451 | 2014-11-20 21:12:19 -0800 | [diff] [blame] | 254 | |
Cosmos Nicolaou | bbae388 | 2014-10-02 22:58:19 -0700 | [diff] [blame] | 255 | return procErr |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 256 | } |