blob: 34401c8644d546423a86a6f0bc9996ad62af4668 [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
Todd Wang8c4e5cc2015-04-09 11:30:52 -07005// Package modules implements a mechanism for running commonly used services as
6// subprocesses, and client functionality for accessing those services. Such
Todd Wang95873902015-05-22 14:21:30 -07007// services and functions are collectively called 'programs' and are managed by
Todd Wang8c4e5cc2015-04-09 11:30:52 -07008// a 'Registry'. The Shell is analagous to the UNIX shell and maintains a key,
9// value store of environment variables and config settings that are accessible
Todd Wang95873902015-05-22 14:21:30 -070010// to the programs that it hosts. Simple variable expansion is supported.
Cosmos Nicolaou62613842014-08-25 21:57:37 -070011//
Todd Wang95873902015-05-22 14:21:30 -070012// Three types of 'programs' may be invoked via a Shell:
Cosmos Nicolaou62613842014-08-25 21:57:37 -070013//
Todd Wang95873902015-05-22 14:21:30 -070014// 1) Functions of type Main as subprocesses via fork/exec.
15// 2) Arbitrary non-Vanadium programs available on the underlying operating
Todd Wang5507c832015-05-15 22:59:23 -070016// system such as '/bin/cp', 'bash' etc.
Todd Wang95873902015-05-22 14:21:30 -070017// 3) Arbitrary Vanadium programs available on the underlying operating system
Todd Wang5507c832015-05-15 22:59:23 -070018// such as precompiled Vanadium services.
Cosmos Nicolaou62613842014-08-25 21:57:37 -070019//
Todd Wang5507c832015-05-15 22:59:23 -070020// The first type requires that the function to be executed is compiled into the
21// binary executing the calls to the Shell. These functions are registered with
22// a single, per-process, registry.
Cosmos Nicolaou9afe1202014-09-19 13:45:57 -070023//
Cosmos Nicolaou42a17362015-03-10 16:40:18 -070024// The second two types allow for arbitrary binaries to be executed. The
Todd Wang95873902015-05-22 14:21:30 -070025// distinction between a Vanadium and non-Vanadium program is that the Vanadium
26// program implements the protocol used by v.io/x/ref/lib/exec package to
Todd Wang5507c832015-05-15 22:59:23 -070027// synchronise between the parent and child processes and to share information
28// such as the ConfigKey key,value store supported by the Shell, a shared
29// secret, shared file descriptors etc.
Cosmos Nicolaou52e9a222015-03-16 21:57:16 -070030//
31// When the exec protocol is not used the only form of communication with the
32// child processes are environment variables and command line flags and any
33// shared file descriptors explicitly created by the parent process and expected
Todd Wang5507c832015-05-15 22:59:23 -070034// by the child process; the Start method will not create any additional file
35// descriptors.
Cosmos Nicolaou42a17362015-03-10 16:40:18 -070036//
37// The registry provides the following functions:
Cosmos Nicolaou42a17362015-03-10 16:40:18 -070038//
Todd Wang95873902015-05-22 14:21:30 -070039// Register: registers a Main function to be executed in a subprocess,
40// the returned Program is typically assigned to a global variable.
41// Dispatch: must be called in the child process to lookup and invoke the
42// requested function. Typically called from TestMain.
Cosmos Nicolaou42a17362015-03-10 16:40:18 -070043//
Todd Wang95873902015-05-22 14:21:30 -070044// The v23 tool can automate generation of TestMain. Adding the comment below
45// to a test file will generate the appropriate code.
Todd Wang5507c832015-05-15 22:59:23 -070046//
47// //go:generate v23 test generate .
Cosmos Nicolaou42a17362015-03-10 16:40:18 -070048//
49// Use 'v23 test generate --help' to get a complete explanation.
50//
Todd Wang95873902015-05-22 14:21:30 -070051// In all cases programs are started by invoking the StartWithOpts method on the
52// Shell with the name of the program to run. An instance of the Handle
Todd Wang5507c832015-05-15 22:59:23 -070053// interface is returned which can be used to interact with the function or
54// subprocess, and in particular to read/write data from/to it using io channels
55// that follow the stdin, stdout, stderr convention. The StartOpts struct is
56// used to control the detailed behaviour of each such invocation. Various
57// helper functions are provided both for creating appropriate instances of
58// StartOpts and for common uses of StartWithOpts.
Cosmos Nicolaou42a17362015-03-10 16:40:18 -070059//
Todd Wang5507c832015-05-15 22:59:23 -070060// Each successful call to StartWithOpts returns a handle representing the
Todd Wang95873902015-05-22 14:21:30 -070061// running program. This handle can be used to gain access to that program's
Todd Wang5507c832015-05-15 22:59:23 -070062// stdin, stdout, stderr and to request or synchronize with its termination via
63// the Shutdown method. The Shutdown method can optionally be used to read any
Todd Wang95873902015-05-22 14:21:30 -070064// remaining output from the programs stdout and stderr. The Shell maintains a
Todd Wang5507c832015-05-15 22:59:23 -070065// record of all such handles and will call Shutdown on them in LIFO order when
66// the Shell's Cleanup method is called.
Cosmos Nicolaou42a17362015-03-10 16:40:18 -070067//
Todd Wang95873902015-05-22 14:21:30 -070068// A simple protocol must be followed by all programs, in particular, they
69// should wait for their stdin stream to be closed before exiting. The caller
70// can then coordinate with any program by writing to that stdin stream and
71// reading responses from the stdout stream, and it can close stdin when it's
72// ready for the program to exit using the CloseStdin method on the program's
73// handle. Any binary or script that follows this protocol can be used as well.
Cosmos Nicolaou42a17362015-03-10 16:40:18 -070074//
Todd Wang5507c832015-05-15 22:59:23 -070075// By default, every Shell created by NewShell starts a security agent to manage
76// principals for child processes. These default credentials can be overridden
77// by passing a nil context to NewShell then specifying VeyronCredentials in the
78// environment provided as a parameter to the StartWithOpts method. It is also
79// possible to specify custom credentials via StartOpts.
Cosmos Nicolaou42a17362015-03-10 16:40:18 -070080//
Todd Wang95873902015-05-22 14:21:30 -070081// Interacting with Programs
Cosmos Nicolaou42a17362015-03-10 16:40:18 -070082//
83// Handle.Stdout(), Stdin(), Stderr():
Todd Wang5507c832015-05-15 22:59:23 -070084//
85// StartWithOpts returns a Handle which can be used to interact with the running
Todd Wang95873902015-05-22 14:21:30 -070086// program. In particular, its Stdin() and Stdout() methods give access to the
Todd Wang5507c832015-05-15 22:59:23 -070087// running process' corresponding stdin and stdout and hence can be used to
88// communicate with it. Stderr is handled differently and is configured so that
89// the child's stderr is written to a log file rather than a pipe. This is in
90// order to maximise the liklihood of capturing stderr output from a crashed
91// child process.
Cosmos Nicolaou42a17362015-03-10 16:40:18 -070092//
93// Handle.Shutdown(stdout, stderr io.Writer):
Todd Wang5507c832015-05-15 22:59:23 -070094//
Todd Wang95873902015-05-22 14:21:30 -070095// The Shutdown method is used to gracefully shutdown a program and to
Todd Wang5507c832015-05-15 22:59:23 -070096// synchronise with its termination. In particular, Shutdown can be used to read
Todd Wang95873902015-05-22 14:21:30 -070097// any unread output from the program's stdout and stderr. Note that since
Todd Wang5507c832015-05-15 22:59:23 -070098// Stderr is buffered to a file, Shutdown is able to return the entire contents
99// of that file. This is useful for debugging misbehaving/crashing child
100// processes.
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700101//
102// Shell.Cleanup(stdout, stderr io.Writer):
Todd Wang5507c832015-05-15 22:59:23 -0700103//
104// The Shell keeps track of all Handles that it has issued and in particular if
105// Shutdown (or Forget) have not been called, it will call Shutdown for each
Todd Wang95873902015-05-22 14:21:30 -0700106// such Handle in LIFO order. This ensures that all programs will be Shutdown
Todd Wang5507c832015-05-15 22:59:23 -0700107// even if the developer does not explicitly take care to do so for every
108// invocation.
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700109//
110// Pipes:
Todd Wang5507c832015-05-15 22:59:23 -0700111//
Todd Wang95873902015-05-22 14:21:30 -0700112// StartWithOpts allows the caller to pass an io.Reader to the program
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700113// (StartOpts.Stdin) for it to read from, rather than creating a new pipe
Todd Wang95873902015-05-22 14:21:30 -0700114// internally. This makes it possible to connect the output of one program to
Todd Wang5507c832015-05-15 22:59:23 -0700115// the input of another directly.
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700116//
117// Command Line Arguments:
Todd Wang5507c832015-05-15 22:59:23 -0700118//
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700119// The arguments passed in calls to Start are appended to any system required
120// ones (e.g. for propagating test timeouts, verbosity etc) and the child
Todd Wang95873902015-05-22 14:21:30 -0700121// process will call the program with the result of flag.Args(). In this way the
Todd Wang5507c832015-05-15 22:59:23 -0700122// caller can provide flags used by libraries in the child process as well as
Todd Wang95873902015-05-22 14:21:30 -0700123// those specific to the program and the program will only receive the args
Todd Wang5507c832015-05-15 22:59:23 -0700124// specific to it. The usual "--" convention can be used to override this
125// default behaviour.
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700126//
127// Caveats:
128//
Todd Wang95873902015-05-22 14:21:30 -0700129// Handle.Shutdown assumes that the child program/process will terminate when
Todd Wang5507c832015-05-15 22:59:23 -0700130// its stdin stream is closed. This assumption is unlikely to be valid for
Todd Wang95873902015-05-22 14:21:30 -0700131// 'external' programs (e.g. /bin/cp) and in these cases Kill or some other
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700132// application specific mechanism will need to be used.
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700133package modules
134
135import (
Cosmos Nicolaou52e9a222015-03-16 21:57:16 -0700136 "errors"
Cosmos Nicolaoud4f00562014-11-17 20:35:48 -0800137 "fmt"
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700138 "io"
Cosmos Nicolaou9afe1202014-09-19 13:45:57 -0700139 "io/ioutil"
140 "os"
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700141 "sync"
Ryan Browna08a2212015-01-15 15:40:10 -0800142 "syscall"
Cosmos Nicolaoue5b41502014-10-29 22:55:09 -0700143 "time"
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700144
Jiri Simsa6ac95222015-02-23 16:11:49 -0800145 "v.io/v23"
146 "v.io/v23/context"
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700147 "v.io/v23/security"
Todd Wang3bb46f52015-05-14 22:01:18 -0700148 "v.io/x/lib/envvar"
Todd Wang8123b5e2015-05-14 18:44:43 -0700149 "v.io/x/ref"
Jiri Simsaffceefa2015-02-28 11:03:34 -0800150 "v.io/x/ref/lib/exec"
Todd Wang88509682015-04-10 10:28:24 -0700151 "v.io/x/ref/services/agent/agentlib"
Todd Wangb3511492015-04-07 23:32:34 -0700152 "v.io/x/ref/services/agent/keymgr"
Cosmos Nicolaou1381f8a2015-03-13 09:40:34 -0700153 "v.io/x/ref/test/expect"
Ankur9f957942014-11-24 16:34:18 -0800154)
155
156const (
157 shellBlessingExtension = "test-shell"
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700158
159 defaultStartTimeout = time.Minute
160 defaultShutdownTimeout = time.Minute
161 defaultExpectTimeout = time.Minute
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700162)
163
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700164var defaultStartOpts = StartOpts{
165 StartTimeout: defaultStartTimeout,
166 ShutdownTimeout: defaultShutdownTimeout,
167 ExpectTimeout: defaultExpectTimeout,
168 ExecProtocol: true,
169}
170
Todd Wang95873902015-05-22 14:21:30 -0700171// Shell represents the context within which programs are run.
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700172type Shell struct {
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700173 mu sync.Mutex
174 env map[string]string
Todd Wang5507c832015-05-15 22:59:23 -0700175 handles map[*execHandle]struct{}
176 lifoHandles []*execHandle
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700177 defaultStartOpts StartOpts
Ryan Browna08a2212015-01-15 15:40:10 -0800178 // tmpCredDir is the temporary directory created by this
179 // shell. This must be removed when the shell is cleaned up.
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700180 tempCredDir string
181 config exec.Config
182 principal security.Principal
183 agent *keymgr.Agent
184 ctx *context.T
185 sessionVerbosity bool
186 cancelCtx func()
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700187}
188
Ryan Browna08a2212015-01-15 15:40:10 -0800189// NewShell creates a new instance of Shell.
Ankur50ab9882015-02-17 12:17:17 -0800190//
Ryan Browna08a2212015-01-15 15:40:10 -0800191// If ctx is non-nil, the shell will manage Principals for child processes.
Ankur50ab9882015-02-17 12:17:17 -0800192//
193// If p is non-nil, any child process created has its principal blessed
194// by the default blessings of 'p', Else any child process created has its
195// principal blessed by the default blessings of ctx's principal.
Cosmos Nicolaou9e909842015-03-17 11:58:59 -0700196//
197// If verbosity is true additional debugging info will be displayed,
198// in particular by the Shutdown.
199//
200// If t is non-nil, then the expect Session created for every invocation
201// will be constructed with that value of t unless overridden by a
202// StartOpts provided to that invocation. Providing a non-nil value of
203// t enables expect.Session to call t.Error, Errorf and Log.
204func NewShell(ctx *context.T, p security.Principal, verbosity bool, t expect.Testing) (*Shell, error) {
Cosmos Nicolaou9afe1202014-09-19 13:45:57 -0700205 sh := &Shell{
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700206 env: make(map[string]string),
Todd Wang5507c832015-05-15 22:59:23 -0700207 handles: make(map[*execHandle]struct{}),
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700208 config: exec.NewConfig(),
209 defaultStartOpts: defaultStartOpts,
Cosmos Nicolaou9e909842015-03-17 11:58:59 -0700210 sessionVerbosity: verbosity,
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700211 }
Cosmos Nicolaou9e909842015-03-17 11:58:59 -0700212 sh.defaultStartOpts = sh.defaultStartOpts.WithSessions(t, time.Minute)
Ryan Browna08a2212015-01-15 15:40:10 -0800213 if ctx == nil {
Cosmos Nicolaou344cc4a2014-11-26 15:38:43 -0800214 return sh, nil
Cosmos Nicolaou9afe1202014-09-19 13:45:57 -0700215 }
Ryan Browna08a2212015-01-15 15:40:10 -0800216 var err error
217 ctx, sh.cancelCtx = context.WithCancel(ctx)
Todd Wangad492042015-04-17 15:58:40 -0700218 if ctx, err = v23.WithNewStreamManager(ctx); err != nil {
Ryan Browna08a2212015-01-15 15:40:10 -0800219 return nil, err
220 }
221 sh.ctx = ctx
222
Cosmos Nicolaou185c0c62015-04-13 21:22:43 -0700223 if sh.tempCredDir, err = ioutil.TempDir("", "shell_credentials-"); err != nil {
Ryan Browna08a2212015-01-15 15:40:10 -0800224 return nil, err
225 }
226 if sh.agent, err = keymgr.NewLocalAgent(ctx, sh.tempCredDir, nil); err != nil {
227 return nil, err
228 }
Ryan Browna08a2212015-01-15 15:40:10 -0800229 sh.principal = p
Ankur50ab9882015-02-17 12:17:17 -0800230 if sh.principal == nil {
Jiri Simsa6ac95222015-02-23 16:11:49 -0800231 sh.principal = v23.GetPrincipal(ctx)
Cosmos Nicolaou344cc4a2014-11-26 15:38:43 -0800232 }
Cosmos Nicolaou344cc4a2014-11-26 15:38:43 -0800233 return sh, nil
Cosmos Nicolaou9afe1202014-09-19 13:45:57 -0700234}
235
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700236// DefaultStartOpts returns the current StartOpts stored with the Shell.
237func (sh *Shell) DefaultStartOpts() StartOpts {
238 return sh.defaultStartOpts
239}
240
241// SetDefaultStartOpts sets the default StartOpts stored with the Shell.
242func (sh *Shell) SetDefaultStartOpts(opts StartOpts) {
243 sh.defaultStartOpts = opts
244}
245
Ryan Brown61d69382015-02-25 11:13:39 -0800246// CustomCredentials encapsulates a Principal which can be shared with
247// one or more processes run by a Shell.
248type CustomCredentials struct {
249 p security.Principal
250 agent *keymgr.Agent
251 id []byte
252}
Ankur50ab9882015-02-17 12:17:17 -0800253
Ryan Brown61d69382015-02-25 11:13:39 -0800254// Principal returns the Principal.
255func (c *CustomCredentials) Principal() security.Principal {
256 return c.p
257}
258
259// File returns a socket which can be used to connect to the agent
260// managing this principal. Typically you would pass this to a child
261// process.
262func (c *CustomCredentials) File() (*os.File, error) {
263 return c.agent.NewConnection(c.id)
264}
265
266func dup(conn *os.File) (int, error) {
Bogdan Capritabb37c542015-01-22 10:21:57 -0800267 syscall.ForkLock.RLock()
Ryan Browna08a2212015-01-15 15:40:10 -0800268 fd, err := syscall.Dup(int(conn.Fd()))
Ankur9f957942014-11-24 16:34:18 -0800269 if err != nil {
Bogdan Capritabb37c542015-01-22 10:21:57 -0800270 syscall.ForkLock.RUnlock()
Ryan Brown61d69382015-02-25 11:13:39 -0800271 return -1, err
Ryan Browna08a2212015-01-15 15:40:10 -0800272 }
273 syscall.CloseOnExec(fd)
Bogdan Capritabb37c542015-01-22 10:21:57 -0800274 syscall.ForkLock.RUnlock()
Ryan Brown61d69382015-02-25 11:13:39 -0800275 return fd, nil
276}
277
Asim Shankar34fb2492015-03-12 10:25:46 -0700278// NewCustomCredentials creates a new Principal for StartWithOpts..
Ryan Brown61d69382015-02-25 11:13:39 -0800279// Returns nil if the shell is not managing principals.
280func (sh *Shell) NewCustomCredentials() (cred *CustomCredentials, err error) {
281 // Create child principal.
282 if sh.ctx == nil {
283 return nil, nil
284 }
285 id, conn, err := sh.agent.NewPrincipal(sh.ctx, true)
286 if err != nil {
287 return nil, err
288 }
289 fd, err := dup(conn)
290 conn.Close()
291 if err != nil {
292 return nil, err
293 }
Ryan Brown7f950a82015-04-20 18:08:39 -0700294 ep, err := v23.NewEndpoint(agentlib.AgentEndpoint(fd))
295 if err != nil {
296 syscall.Close(fd)
297 return nil, err
298 }
299 p, err := agentlib.NewAgentPrincipal(sh.ctx, ep, v23.GetClient(sh.ctx))
Ryan Browna08a2212015-01-15 15:40:10 -0800300 if err != nil {
Bogdan Caprita6613fc42015-01-28 11:54:23 -0800301 syscall.Close(fd)
Ryan Browna08a2212015-01-15 15:40:10 -0800302 return nil, err
Ankur9f957942014-11-24 16:34:18 -0800303 }
Ryan Brown61d69382015-02-25 11:13:39 -0800304 return &CustomCredentials{p, sh.agent, id}, nil
305}
306
Asim Shankar34fb2492015-03-12 10:25:46 -0700307// NewChildCredentials creates a new principal, served via the security agent
308// whose blessings are an extension of this shell's principal (with the
309// provided caveats).
310//
311// All processes started by this shell will recognize the credentials created
312// by this call.
313//
314// Returns nil if the shell is not managing principals.
315//
316// Since the Shell type is intended for tests, it is not required to provide
317// caveats. In production scenarios though, one must think long and hard
318// before blessing anothing principal without any caveats.
319func (sh *Shell) NewChildCredentials(extension string, caveats ...security.Caveat) (c *CustomCredentials, err error) {
Ryan Brown61d69382015-02-25 11:13:39 -0800320 creds, err := sh.NewCustomCredentials()
321 if creds == nil {
322 return nil, err
323 }
Asim Shankar34fb2492015-03-12 10:25:46 -0700324 parent := sh.principal
325 child := creds.p
326 if len(caveats) == 0 {
327 caveats = []security.Caveat{security.UnconstrainedUse()}
328 }
Ankur50ab9882015-02-17 12:17:17 -0800329
330 // Bless the child principal with blessings derived from the default blessings
331 // of shell's principal.
Asim Shankar34fb2492015-03-12 10:25:46 -0700332 blessings, err := parent.Bless(child.PublicKey(), parent.BlessingStore().Default(), extension, caveats[0], caveats[1:]...)
Ankur9f957942014-11-24 16:34:18 -0800333 if err != nil {
Ryan Browna08a2212015-01-15 15:40:10 -0800334 return nil, err
Ankur9f957942014-11-24 16:34:18 -0800335 }
Asim Shankar34fb2492015-03-12 10:25:46 -0700336 if err := child.BlessingStore().SetDefault(blessings); err != nil {
Ryan Browna08a2212015-01-15 15:40:10 -0800337 return nil, err
Ankur9f957942014-11-24 16:34:18 -0800338 }
Asim Shankar34fb2492015-03-12 10:25:46 -0700339 if _, err := child.BlessingStore().Set(blessings, security.AllPrincipals); err != nil {
Ryan Browna08a2212015-01-15 15:40:10 -0800340 return nil, err
Ankur9f957942014-11-24 16:34:18 -0800341 }
Asim Shankar34fb2492015-03-12 10:25:46 -0700342 if err := child.AddToRoots(blessings); err != nil {
Ryan Browna08a2212015-01-15 15:40:10 -0800343 return nil, err
Ankur9f957942014-11-24 16:34:18 -0800344 }
345
Ryan Brown61d69382015-02-25 11:13:39 -0800346 return creds, nil
Ankur9f957942014-11-24 16:34:18 -0800347}
348
Todd Wang95873902015-05-22 14:21:30 -0700349// Env represents the environment for Main functions.
350type Env struct {
351 Stdin io.Reader
352 Stdout io.Writer
353 Stderr io.Writer
354 Vars map[string]string // Environment variables
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700355}
356
Todd Wang95873902015-05-22 14:21:30 -0700357// EnvFromOS returns a new Env based on the underlying OS.
358func EnvFromOS() *Env {
359 return &Env{
360 Stdin: os.Stdin,
361 Stdout: os.Stdout,
362 Stderr: os.Stderr,
363 Vars: envvar.SliceToMap(os.Environ()),
364 }
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700365}
366
Todd Wang95873902015-05-22 14:21:30 -0700367// Main describes the entry-point function type for registered programs.
368type Main func(env *Env, args ...string) error
369
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700370// Start is shorthand for StartWithOpts(sh.DefaultStartOpts(), ...)
Todd Wang95873902015-05-22 14:21:30 -0700371func (sh *Shell) Start(env []string, prog Program, args ...string) (Handle, error) {
372 return sh.StartWithOpts(sh.DefaultStartOpts(), env, prog, args...)
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700373}
374
375// StartOpts represents the options that can be passed to the
376// StartWithOpts method.
377type StartOpts struct {
378 // Error is set when creating/intializing instances of StartOpts
379 // via one of the factory methods and returned when StartWithOpts
380 // is called. This allows usage of of the form:
381 //
382 // err := sh.StartWithOpts(sh.DefaultStartOpts()...)
383 //
384 // as opposed to:
385 //
386 // opts, err := sh.DefaultStartOpts(....)
387 // if err != nil {
388 // panic(...)
389 // }
390 // sh.StartWithOpts(opts, ....)
391 Error error
392
393 // Stdin, if non-nil, will be used as the stdin for the child process.
394 // If this option is set, then the Stdin() method on the returned Handle
395 // will return nil. The client of this API maintains ownership of stdin
396 // and must close it, i.e. the shell will not do so.
397 Stdin io.Reader
398 // Credentials, if non-nil, will be used as the credentials for the
399 // child process. If the creds are nil or the shell is not managing
400 // principals, the credentials are ignored.
401 Credentials *CustomCredentials
402 // ExecProtocol indicates whether the child process is expected to
403 // implement the v.io/x.ref/lib/exec parent/child protocol.
Todd Wang95873902015-05-22 14:21:30 -0700404 // It should be set to false when running non-vanadium programs
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700405 // (e.g. /bin/cp).
406 ExecProtocol bool
Todd Wang95873902015-05-22 14:21:30 -0700407 // External indicates if the program is an external process rather than
408 // a Main function.
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700409 External bool
410 // StartTimeout specifies the amount of time to wait for the
411 // child process to signal its correct intialization for Vanadium
412 // processes that implement the exec parent/child protocol. It has no
413 // effect if External is set to true.
414 StartTimeout time.Duration
415 // ShutdownTimeout specifics the amount of time to wait for the child
416 // process to exit when the Shutdown method is called on that
417 // child's handle.
418 ShutdownTimeout time.Duration
419 // ExpectTesting is used when creating an instance of expect.Session
420 // to embed in Handle.
421 ExpectTesting expect.Testing
422 // ExpectTimeout is the timeout to use with expect.Session.
423 ExpectTimeout time.Duration
424}
425
426// DefaultStartOpts returns an instance of Startops with the current default
427// values. The defaults have values for timeouts, no credentials
428// (StartWithOpts will then create credentials each time it is called),
429// and with ExecProtocol set to true.
430// This is expected to be the common use case.
431func DefaultStartOpts() StartOpts {
432 return defaultStartOpts
433}
434
435// WithCustomCredentials returns an instance of StartOpts with the specified
Asim Shankar34fb2492015-03-12 10:25:46 -0700436// credentials.
437//
438// All other options are set to the current defaults.
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700439func (opts StartOpts) WithCustomCredentials(creds *CustomCredentials) StartOpts {
440 opts.Credentials = creds
441 return opts
442}
443
444// WithSessions returns a copy of opts with the specified expect.Testing and
445// associated timeout.
446func (opts StartOpts) WithSessions(t expect.Testing, timeout time.Duration) StartOpts {
447 opts.ExpectTesting = t
448 opts.ExpectTimeout = timeout
449 return opts
450}
451
452// WithStdin returns a copy of opts with the specified Stdin io.Reader.
453func (opts StartOpts) WithStdin(stdin io.Reader) StartOpts {
454 opts.Stdin = stdin
455 return opts
456}
457
Todd Wang95873902015-05-22 14:21:30 -0700458// NoExecProgram returns a copy of opts with the External option
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700459// enabled and ExecProtocol disabled.
Todd Wang95873902015-05-22 14:21:30 -0700460func (opts StartOpts) NoExecProgram() StartOpts {
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700461 opts.External = true
462 opts.ExecProtocol = false
463 return opts
464}
465
Todd Wang95873902015-05-22 14:21:30 -0700466// ExternalProgram returns a copy of StartOpts with the
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700467// External option enabled.
Todd Wang95873902015-05-22 14:21:30 -0700468func (opts StartOpts) ExternalProgram() StartOpts {
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700469 opts.External = true
470 return opts
471}
472
Cosmos Nicolaou52e9a222015-03-16 21:57:16 -0700473var (
Todd Wang95873902015-05-22 14:21:30 -0700474 ErrNotRegistered = errors.New("program not registered")
Cosmos Nicolaou52e9a222015-03-16 21:57:16 -0700475 ErrNoExecAndCustomCreds = errors.New("ExecProtocol set to false but this invocation is attempting to use custome credentials")
476)
477
Todd Wang95873902015-05-22 14:21:30 -0700478// StartWithOpts starts the specified program according to the supplied
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700479// StartOpts and returns a Handle which can be used for interacting with
Todd Wang95873902015-05-22 14:21:30 -0700480// that program.
Ankur9f957942014-11-24 16:34:18 -0800481//
Todd Wang95873902015-05-22 14:21:30 -0700482// The environment variables for the program are set by merging variables
Ankur9f957942014-11-24 16:34:18 -0800483// from the OS environment, those in this Shell and those provided as a
484// parameter to it. In general, it prefers values from its parameter over
485// those from the Shell, over those from the OS. However, the VeyronCredentials
Ryan Brown10a4ade2015-02-10 13:17:18 -0800486// and agent FdEnvVar variables will never use the value from the Shell or OS.
Ankur9f957942014-11-24 16:34:18 -0800487//
Todd Wang95873902015-05-22 14:21:30 -0700488// If the shell is managing principals, the program is configured to
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700489// connect to the shell's agent. Custom credentials may be specified
490// via StartOpts. If the shell is not managing principals, set
Ryan Brown10a4ade2015-02-10 13:17:18 -0800491// the VeyronCredentials environment variable in the 'env' parameter.
Cosmos Nicolaou612ad382014-10-29 19:41:35 -0700492//
493// The Shell tracks all of the Handles that it creates so that it can shut
Cosmos Nicolaoue5b41502014-10-29 22:55:09 -0700494// them down when asked to. The returned Handle may be non-nil even when an
495// error is returned, in which case it may be used to retrieve any output
Todd Wang95873902015-05-22 14:21:30 -0700496// from the failed program.
Cosmos Nicolaou612ad382014-10-29 19:41:35 -0700497//
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700498// StartWithOpts will return a valid handle for errors that occur during the
499// child processes startup process. It is thus possible to call Shutdown
500// to obtain the error output. Handle will be nil if the error is due to
501// some other reason, such as failure to create pipes/files before starting
502// the child process. A common use will therefore be:
Ryan Brown61d69382015-02-25 11:13:39 -0800503//
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700504// h, err := sh.Start(env, "/bin/echo", "hello")
505// if err != nil {
506// if h != nil {
507// h.Shutdown(nil,os.Stderr)
508// }
509// t.Fatal(err)
510// }
Todd Wang95873902015-05-22 14:21:30 -0700511func (sh *Shell) StartWithOpts(opts StartOpts, env []string, prog Program, args ...string) (Handle, error) {
Todd Wang3bb46f52015-05-14 22:01:18 -0700512 var err error
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700513 if opts.Error != nil {
514 return nil, opts.Error
515 }
Cosmos Nicolaou52e9a222015-03-16 21:57:16 -0700516
Todd Wang95873902015-05-22 14:21:30 -0700517 var info *programInfo
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700518 if opts.External {
Todd Wang95873902015-05-22 14:21:30 -0700519 info = registry.getExternalProgram(prog)
520 } else if info = registry.getProgram(prog); info == nil {
Cosmos Nicolaou52e9a222015-03-16 21:57:16 -0700521 return nil, ErrNotRegistered
James Ring9d9489d2015-01-27 15:48:07 -0800522 }
Cosmos Nicolaou52e9a222015-03-16 21:57:16 -0700523
524 if !opts.ExecProtocol && opts.Credentials != nil {
525 return nil, ErrNoExecAndCustomCreds
526 }
527
528 if sh.ctx != nil && opts.ExecProtocol && opts.Credentials == nil {
Asim Shankar34fb2492015-03-12 10:25:46 -0700529 opts.Credentials, err = sh.NewChildCredentials("child")
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700530 if err != nil {
531 return nil, err
532 }
533 }
Ryan Brown61d69382015-02-25 11:13:39 -0800534
535 var p *os.File
Cosmos Nicolaou52e9a222015-03-16 21:57:16 -0700536 if opts.Credentials != nil {
537 p, err = opts.Credentials.File()
538 if err != nil {
539 return nil, err
Ryan Brown61d69382015-02-25 11:13:39 -0800540 }
Ryan Browna08a2212015-01-15 15:40:10 -0800541 }
James Ring9d9489d2015-01-27 15:48:07 -0800542
Todd Wang95873902015-05-22 14:21:30 -0700543 handle := info.factory()
544 h, err := handle.start(sh, p, &opts, sh.setupProgramEnv(env), sh.expand(args))
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700545 if err != nil {
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700546 return h, err
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700547 }
548 sh.mu.Lock()
549 sh.handles[h] = struct{}{}
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700550 sh.lifoHandles = append(sh.lifoHandles, h)
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700551 sh.mu.Unlock()
552 return h, nil
553}
554
Todd Wang95873902015-05-22 14:21:30 -0700555// ProgramEnvelope returns the command line and environment that would be used
Todd Wang5507c832015-05-15 22:59:23 -0700556// for running the subprocess if it were started with the specifed arguments.
Todd Wang95873902015-05-22 14:21:30 -0700557func (sh *Shell) ProgramEnvelope(env []string, prog Program, args ...string) ([]string, []string) {
558 info := registry.getProgram(prog)
559 if info == nil {
Cosmos Nicolaoud4f00562014-11-17 20:35:48 -0800560 return []string{}, []string{}
561 }
Todd Wang95873902015-05-22 14:21:30 -0700562 return info.factory().envelope(sh, sh.setupProgramEnv(env), sh.expand(args))
Cosmos Nicolaoue664f3f2014-10-20 17:40:05 -0700563}
564
565// Forget tells the Shell to stop tracking the supplied Handle. This is
566// generally used when the application wants to control the order that
Todd Wang95873902015-05-22 14:21:30 -0700567// programs are shutdown in.
Cosmos Nicolaoue664f3f2014-10-20 17:40:05 -0700568func (sh *Shell) Forget(h Handle) {
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700569 sh.mu.Lock()
Todd Wang5507c832015-05-15 22:59:23 -0700570 if handle, ok := h.(*execHandle); ok {
571 delete(sh.handles, handle)
572 }
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700573 sh.mu.Unlock()
574}
575
Todd Wang5507c832015-05-15 22:59:23 -0700576func (sh *Shell) expand(args []string) []string {
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700577 exp := []string{}
578 for _, a := range args {
579 if len(a) > 0 && a[0] == '$' {
580 if v, present := sh.env[a[1:]]; present {
581 exp = append(exp, v)
582 continue
583 }
584 }
585 exp = append(exp, a)
586 }
587 return exp
588}
589
590// GetVar returns the variable associated with the specified key
591// and an indication of whether it is defined or not.
592func (sh *Shell) GetVar(key string) (string, bool) {
593 sh.mu.Lock()
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700594 v, present := sh.env[key]
Todd Wang3bb46f52015-05-14 22:01:18 -0700595 sh.mu.Unlock()
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700596 return v, present
597}
598
599// SetVar sets the value to be associated with key.
600func (sh *Shell) SetVar(key, value string) {
601 sh.mu.Lock()
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700602 // TODO(cnicolaou): expand value
603 sh.env[key] = value
Todd Wang3bb46f52015-05-14 22:01:18 -0700604 sh.mu.Unlock()
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700605}
606
Cosmos Nicolaou2cb05fb2014-10-23 13:37:32 -0700607// ClearVar removes the speficied variable from the Shell's environment
608func (sh *Shell) ClearVar(key string) {
609 sh.mu.Lock()
Cosmos Nicolaou2cb05fb2014-10-23 13:37:32 -0700610 delete(sh.env, key)
Todd Wang3bb46f52015-05-14 22:01:18 -0700611 sh.mu.Unlock()
Cosmos Nicolaou2cb05fb2014-10-23 13:37:32 -0700612}
613
Jiri Simsa37893392014-11-07 10:55:45 -0800614// GetConfigKey returns the value associated with the specified key in
615// the Shell's config and an indication of whether it is defined or
616// not.
617func (sh *Shell) GetConfigKey(key string) (string, bool) {
618 v, err := sh.config.Get(key)
619 return v, err == nil
620}
621
622// SetConfigKey sets the value of the specified key in the Shell's
623// config.
624func (sh *Shell) SetConfigKey(key, value string) {
625 sh.config.Set(key, value)
626}
627
628// ClearConfigKey removes the speficied key from the Shell's config.
629func (sh *Shell) ClearConfigKey(key string) {
630 sh.config.Clear(key)
631}
632
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700633// Env returns the entire set of environment variables associated with this
634// Shell as a string slice.
635func (sh *Shell) Env() []string {
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700636 sh.mu.Lock()
Todd Wang3bb46f52015-05-14 22:01:18 -0700637 vars := envvar.MapToSlice(sh.env)
638 sh.mu.Unlock()
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700639 return vars
640}
641
642// Cleanup calls Shutdown on all of the Handles currently being tracked
Cosmos Nicolaoubbae3882014-10-02 22:58:19 -0700643// by the Shell and writes to stdout and stderr as per the Shutdown
Cosmos Nicolaou2cb05fb2014-10-23 13:37:32 -0700644// method in the Handle interface. Cleanup returns the error from the
645// last Shutdown that returned a non-nil error. The order that the
646// Shutdown routines are executed is not defined.
647func (sh *Shell) Cleanup(stdout, stderr io.Writer) error {
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700648 sh.mu.Lock()
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700649 verbose := sh.sessionVerbosity
650 sh.mu.Unlock()
651
652 writeMsg := func(format string, args ...interface{}) {
653 if !verbose {
654 return
655 }
656 if stderr != nil {
657 fmt.Fprintf(stderr, format, args...)
658 }
659 }
660
Cosmos Nicolaou9fb10342015-04-12 19:37:24 -0700661 writeMsg("---- Shell Cleanup ----\n")
662 defer writeMsg("---- Shell Cleanup Complete ----\n")
663
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700664 sh.mu.Lock()
Todd Wang5507c832015-05-15 22:59:23 -0700665 handles := make([]*execHandle, 0, len(sh.lifoHandles))
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700666 for _, h := range sh.lifoHandles {
667 if _, present := sh.handles[h]; present {
668 handles = append(handles, h)
669 }
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700670 }
Todd Wang5507c832015-05-15 22:59:23 -0700671 sh.handles = make(map[*execHandle]struct{})
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700672 sh.lifoHandles = nil
Cosmos Nicolaou66afced2014-09-15 22:12:43 -0700673 sh.mu.Unlock()
Cosmos Nicolaou2cb05fb2014-10-23 13:37:32 -0700674 var err error
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700675 for i := len(handles); i > 0; i-- {
676 h := handles[i-1]
Todd Wang95873902015-05-22 14:21:30 -0700677 writeMsg("---- Cleanup calling Shutdown on program %q\n", h.desc)
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700678 cerr := h.Shutdown(stdout, stderr)
Cosmos Nicolaou2cb05fb2014-10-23 13:37:32 -0700679 if cerr != nil {
680 err = cerr
681 }
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700682 fn := func() string {
683 if cerr == nil {
684 return ": done"
685 } else {
686 return ": error: " + err.Error()
687 }
688 }
Todd Wang95873902015-05-22 14:21:30 -0700689 writeMsg("---- Shutdown on program %q%s\n", h.desc, fn())
Cosmos Nicolaou66afced2014-09-15 22:12:43 -0700690 }
Ankur9f957942014-11-24 16:34:18 -0800691
Ryan Browna08a2212015-01-15 15:40:10 -0800692 if sh.cancelCtx != nil {
Cosmos Nicolaou9fb10342015-04-12 19:37:24 -0700693 writeMsg("---- Cleanup calling cancelCtx ----\n")
Ryan Browna08a2212015-01-15 15:40:10 -0800694 // Note(ribrdb, caprita): This will shutdown the agents. If there
695 // were errors shutting down it is possible there could be child
696 // processes still running, and stopping the agent may cause
697 // additional failures.
698 sh.cancelCtx()
Cosmos Nicolaou9afe1202014-09-19 13:45:57 -0700699 }
Ryan Browna08a2212015-01-15 15:40:10 -0800700 os.RemoveAll(sh.tempCredDir)
Cosmos Nicolaou2cb05fb2014-10-23 13:37:32 -0700701 return err
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700702}
703
Todd Wang95873902015-05-22 14:21:30 -0700704func (sh *Shell) setupProgramEnv(env []string) []string {
Todd Wang3bb46f52015-05-14 22:01:18 -0700705 osmap := envvar.SliceToMap(os.Environ())
706 evmap := envvar.SliceToMap(env)
Ankur9f957942014-11-24 16:34:18 -0800707
Cosmos Nicolaou2cb05fb2014-10-23 13:37:32 -0700708 sh.mu.Lock()
Cosmos Nicolaou2cb05fb2014-10-23 13:37:32 -0700709 defer sh.mu.Unlock()
Todd Wang3bb46f52015-05-14 22:01:18 -0700710 m1 := envvar.MergeMaps(osmap, sh.env)
Ankur9f957942014-11-24 16:34:18 -0800711 // Clear any VeyronCredentials directory in m1 as we never
712 // want the child to directly use the directory specified
713 // by the shell's VeyronCredentials.
Todd Wang8123b5e2015-05-14 18:44:43 -0700714 delete(m1, ref.EnvCredentials)
715 delete(m1, ref.EnvAgentEndpoint)
Ankur9f957942014-11-24 16:34:18 -0800716
Todd Wang3bb46f52015-05-14 22:01:18 -0700717 m2 := envvar.MergeMaps(m1, evmap)
718 return envvar.MapToSlice(m2)
Cosmos Nicolaoue664f3f2014-10-20 17:40:05 -0700719}
720
Cosmos Nicolaou1381f8a2015-03-13 09:40:34 -0700721// ExpectSession is a subset of v.io/x/ref/tests/expect.Session's methods
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700722// that are embedded in Handle.
723type ExpectSession interface {
724 Expect(expected string)
725 ExpectEOF() error
726 ExpectRE(pattern string, n int) [][]string
727 ExpectSetEventuallyRE(expected ...string) [][]string
728 ExpectSetRE(expected ...string) [][]string
729 ExpectVar(name string) string
730 Expectf(format string, args ...interface{})
731 ReadAll() (string, error)
732 ReadLine() string
733 SetVerbosity(bool)
734 Failed() bool
735 Error() error
736}
737
Todd Wang95873902015-05-22 14:21:30 -0700738// Handle represents a running program.
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700739type Handle interface {
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700740 ExpectSession
741
Todd Wang95873902015-05-22 14:21:30 -0700742 // Stdout returns a reader to the running program's stdout stream.
Cosmos Nicolaou9ca249d2014-09-18 15:07:12 -0700743 Stdout() io.Reader
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700744
Todd Wang95873902015-05-22 14:21:30 -0700745 // Stderr returns a reader to the running program's stderr
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700746 // stream.
747 Stderr() io.Reader
748
Todd Wang95873902015-05-22 14:21:30 -0700749 // Stdin returns a writer to the running program's stdin. The
750 // convention is for programs to wait for stdin to be closed before
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700751 // they exit, thus the caller should close stdin when it wants the
Todd Wang95873902015-05-22 14:21:30 -0700752 // program to exit cleanly.
Cosmos Nicolaou66afced2014-09-15 22:12:43 -0700753 Stdin() io.Writer
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700754
Cosmos Nicolaou66afced2014-09-15 22:12:43 -0700755 // CloseStdin closes stdin in a manner that avoids a data race
756 // between any current readers on it.
757 CloseStdin()
758
Todd Wang95873902015-05-22 14:21:30 -0700759 // Shutdown closes the Stdin for the program and then reads output
760 // from the program's stdout until it encounters EOF, waits for
761 // the program to complete and then reads all of its stderr output.
Cosmos Nicolaoubbae3882014-10-02 22:58:19 -0700762 // The stdout and stderr contents are written to the corresponding
763 // io.Writers if they are non-nil, otherwise the content is discarded.
764 Shutdown(stdout, stderr io.Writer) error
Cosmos Nicolaoucc581722014-10-07 12:45:39 -0700765
Todd Wang95873902015-05-22 14:21:30 -0700766 // Pid returns the pid of the process running the program
Cosmos Nicolaoucc581722014-10-07 12:45:39 -0700767 Pid() int
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700768}