blob: 569cd7ae4f48d4097682e9ca31494636ffe019fc [file] [log] [blame]
Cosmos Nicolaou62613842014-08-25 21:57:37 -07001// Package modules provides a mechanism for running commonly used services
2// as subprocesses and client functionality for accessing those services.
3// Such services and functions are collectively called 'commands' and are
Cosmos Nicolaoud4f00562014-11-17 20:35:48 -08004// registered with a single, per-process, registry, but executed within a
5// context, defined by the Shell type. The Shell is analagous to the original
6// UNIX shell and maintains a key, value store of variables that is accessible
7// to all of the commands that it hosts. These variables may be referenced by
8// the arguments passed to commands.
Cosmos Nicolaou62613842014-08-25 21:57:37 -07009//
Cosmos Nicolaoud4f00562014-11-17 20:35:48 -080010// Commands are added to the registry in two ways:
11// - via RegisterChild for a subprocess
12// - via RegisterFunction for an in-process function
Cosmos Nicolaou62613842014-08-25 21:57:37 -070013//
14// In all cases commands are started by invoking the Start method on the
15// Shell with the name of the command to run. An instance of the Handle
16// interface is returned which can be used to interact with the function
17// or subprocess, and in particular to read/write data from/to it using io
18// channels that follow the stdin, stdout, stderr convention.
19//
20// A simple protocol must be followed by all commands, namely, they
21// should wait for their stdin stream to be closed before exiting. The
22// caller can then coordinate with any command by writing to that stdin
23// stream and reading responses from the stdout stream, and it can close
Cosmos Nicolaou66afced2014-09-15 22:12:43 -070024// stdin when it's ready for the command to exit using the CloseStdin method
25// on the command's handle.
Cosmos Nicolaou62613842014-08-25 21:57:37 -070026//
27// The signature of the function that implements the command is the
28// same for both types of command and is defined by the Main function type.
29// In particular stdin, stdout and stderr are provided as parameters, as is
30// a map representation of the shell's environment.
Cosmos Nicolaou9afe1202014-09-19 13:45:57 -070031//
Ryan Browna08a2212015-01-15 15:40:10 -080032// By default, every Shell created by NewShell starts a security agent
33// to manage principals for child processes. These default
34// credentials can be overridden by passing a nil context to NewShell
35// then specifying VeyronCredentials in the environment provided as a
Ankur9f957942014-11-24 16:34:18 -080036// parameter to the Start method.
Cosmos Nicolaou62613842014-08-25 21:57:37 -070037package modules
38
39import (
James Ring9d9489d2015-01-27 15:48:07 -080040 "errors"
Cosmos Nicolaoud4f00562014-11-17 20:35:48 -080041 "fmt"
Cosmos Nicolaou62613842014-08-25 21:57:37 -070042 "io"
Cosmos Nicolaou9afe1202014-09-19 13:45:57 -070043 "io/ioutil"
44 "os"
Cosmos Nicolaou62613842014-08-25 21:57:37 -070045 "sync"
Ryan Browna08a2212015-01-15 15:40:10 -080046 "syscall"
Cosmos Nicolaoue5b41502014-10-29 22:55:09 -070047 "time"
Cosmos Nicolaou62613842014-08-25 21:57:37 -070048
Jiri Simsa764efb72014-12-25 20:57:03 -080049 "v.io/core/veyron2/security"
Cosmos Nicolaou344cc4a2014-11-26 15:38:43 -080050
Jiri Simsa764efb72014-12-25 20:57:03 -080051 "v.io/core/veyron/lib/exec"
52 "v.io/core/veyron/lib/flags/consts"
Ryan Browna08a2212015-01-15 15:40:10 -080053 "v.io/core/veyron/security/agent"
54 "v.io/core/veyron/security/agent/keymgr"
55 "v.io/core/veyron2"
56 "v.io/core/veyron2/context"
Ankur9f957942014-11-24 16:34:18 -080057)
58
59const (
60 shellBlessingExtension = "test-shell"
61 childBlessingExtension = "child"
Cosmos Nicolaou62613842014-08-25 21:57:37 -070062)
63
64// Shell represents the context within which commands are run.
65type Shell struct {
Ankur9f957942014-11-24 16:34:18 -080066 mu sync.Mutex
67 env map[string]string
68 handles map[Handle]struct{}
Ryan Browna08a2212015-01-15 15:40:10 -080069 // tmpCredDir is the temporary directory created by this
70 // shell. This must be removed when the shell is cleaned up.
71 tempCredDir string
Bogdan Caprita490a4512014-11-20 21:12:19 -080072 startTimeout, waitTimeout time.Duration
73 config exec.Config
Cosmos Nicolaou344cc4a2014-11-26 15:38:43 -080074 principal security.Principal
Ryan Browna08a2212015-01-15 15:40:10 -080075 agent *keymgr.Agent
76 ctx *context.T
77 cancelCtx func()
Cosmos Nicolaou62613842014-08-25 21:57:37 -070078}
79
Ryan Browna08a2212015-01-15 15:40:10 -080080// NewShell creates a new instance of Shell.
Ankur50ab9882015-02-17 12:17:17 -080081//
Ryan Browna08a2212015-01-15 15:40:10 -080082// If ctx is non-nil, the shell will manage Principals for child processes.
Ankur50ab9882015-02-17 12:17:17 -080083//
84// If p is non-nil, any child process created has its principal blessed
85// by the default blessings of 'p', Else any child process created has its
86// principal blessed by the default blessings of ctx's principal.
Ryan Browna08a2212015-01-15 15:40:10 -080087func NewShell(ctx *context.T, p security.Principal) (*Shell, error) {
Cosmos Nicolaou9afe1202014-09-19 13:45:57 -070088 sh := &Shell{
Cosmos Nicolaoue5b41502014-10-29 22:55:09 -070089 env: make(map[string]string),
Cosmos Nicolaoue5b41502014-10-29 22:55:09 -070090 handles: make(map[Handle]struct{}),
91 startTimeout: time.Minute,
Bogdan Caprita490a4512014-11-20 21:12:19 -080092 waitTimeout: 10 * time.Second,
Jiri Simsa37893392014-11-07 10:55:45 -080093 config: exec.NewConfig(),
Cosmos Nicolaou62613842014-08-25 21:57:37 -070094 }
Ryan Browna08a2212015-01-15 15:40:10 -080095 if ctx == nil {
Cosmos Nicolaou344cc4a2014-11-26 15:38:43 -080096 return sh, nil
Cosmos Nicolaou9afe1202014-09-19 13:45:57 -070097 }
Ryan Browna08a2212015-01-15 15:40:10 -080098 var err error
99 ctx, sh.cancelCtx = context.WithCancel(ctx)
Suharsh Sivakumaraf862a52015-02-04 13:50:47 -0800100 if ctx, err = veyron2.SetNewStreamManager(ctx); err != nil {
Ryan Browna08a2212015-01-15 15:40:10 -0800101 return nil, err
102 }
103 sh.ctx = ctx
104
105 if sh.tempCredDir, err = ioutil.TempDir("", "shell_credentials"); err != nil {
106 return nil, err
107 }
108 if sh.agent, err = keymgr.NewLocalAgent(ctx, sh.tempCredDir, nil); err != nil {
109 return nil, err
110 }
Ryan Browna08a2212015-01-15 15:40:10 -0800111 sh.principal = p
Ankur50ab9882015-02-17 12:17:17 -0800112 if sh.principal == nil {
113 sh.principal = veyron2.GetPrincipal(ctx)
Cosmos Nicolaou344cc4a2014-11-26 15:38:43 -0800114 }
Cosmos Nicolaou344cc4a2014-11-26 15:38:43 -0800115 return sh, nil
Cosmos Nicolaou9afe1202014-09-19 13:45:57 -0700116}
117
Ankur50ab9882015-02-17 12:17:17 -0800118// NewChildCredentials creates a new principal, served via the
Cosmos Nicolaou82d00d82015-02-10 21:31:00 -0800119// security agent, that have a blessing from this shell's principal.
Ankur50ab9882015-02-17 12:17:17 -0800120// All processes started by this shell will have access to such a
121// principal.
Cosmos Nicolaou82d00d82015-02-10 21:31:00 -0800122func (sh *Shell) NewChildCredentials() (c *os.File, err error) {
Ryan Browna08a2212015-01-15 15:40:10 -0800123 if sh.ctx == nil {
124 return nil, nil
Ankur9f957942014-11-24 16:34:18 -0800125 }
Ankur50ab9882015-02-17 12:17:17 -0800126
127 // Create child principal.
Ryan Browna08a2212015-01-15 15:40:10 -0800128 _, conn, err := sh.agent.NewPrincipal(sh.ctx, true)
Ankur9f957942014-11-24 16:34:18 -0800129 if err != nil {
Ryan Browna08a2212015-01-15 15:40:10 -0800130 return nil, err
Ankur9f957942014-11-24 16:34:18 -0800131 }
Bogdan Caprita6613fc42015-01-28 11:54:23 -0800132 defer func() {
133 if err != nil {
134 conn.Close()
135 }
136 }()
Ryan Browna08a2212015-01-15 15:40:10 -0800137 ctx, cancel := context.WithCancel(sh.ctx)
Bogdan Caprita6613fc42015-01-28 11:54:23 -0800138 defer cancel()
Suharsh Sivakumaraf862a52015-02-04 13:50:47 -0800139 if ctx, err = veyron2.SetNewStreamManager(ctx); err != nil {
Ryan Browna08a2212015-01-15 15:40:10 -0800140 return nil, err
141 }
Bogdan Capritabb37c542015-01-22 10:21:57 -0800142 syscall.ForkLock.RLock()
Ryan Browna08a2212015-01-15 15:40:10 -0800143 fd, err := syscall.Dup(int(conn.Fd()))
Ankur9f957942014-11-24 16:34:18 -0800144 if err != nil {
Bogdan Capritabb37c542015-01-22 10:21:57 -0800145 syscall.ForkLock.RUnlock()
Ryan Browna08a2212015-01-15 15:40:10 -0800146 return nil, err
147 }
148 syscall.CloseOnExec(fd)
Bogdan Capritabb37c542015-01-22 10:21:57 -0800149 syscall.ForkLock.RUnlock()
Matt Rosencrantz99cc06e2015-01-16 10:25:11 -0800150 p, err := agent.NewAgentPrincipal(ctx, fd, veyron2.GetClient(ctx))
Ryan Browna08a2212015-01-15 15:40:10 -0800151 if err != nil {
Bogdan Caprita6613fc42015-01-28 11:54:23 -0800152 syscall.Close(fd)
Ryan Browna08a2212015-01-15 15:40:10 -0800153 return nil, err
Ankur9f957942014-11-24 16:34:18 -0800154 }
Ankur50ab9882015-02-17 12:17:17 -0800155
156 // Bless the child principal with blessings derived from the default blessings
157 // of shell's principal.
158 blessings := sh.principal.BlessingStore().Default()
159 blessingForChild, err := sh.principal.Bless(p.PublicKey(), blessings, childBlessingExtension, security.UnconstrainedUse())
Ankur9f957942014-11-24 16:34:18 -0800160 if err != nil {
Ryan Browna08a2212015-01-15 15:40:10 -0800161 return nil, err
Ankur9f957942014-11-24 16:34:18 -0800162 }
Cosmos Nicolaou344cc4a2014-11-26 15:38:43 -0800163 if err := p.BlessingStore().SetDefault(blessingForChild); err != nil {
Ryan Browna08a2212015-01-15 15:40:10 -0800164 return nil, err
Ankur9f957942014-11-24 16:34:18 -0800165 }
Cosmos Nicolaou344cc4a2014-11-26 15:38:43 -0800166 if _, err := p.BlessingStore().Set(blessingForChild, security.AllPrincipals); err != nil {
Ryan Browna08a2212015-01-15 15:40:10 -0800167 return nil, err
Ankur9f957942014-11-24 16:34:18 -0800168 }
Cosmos Nicolaou344cc4a2014-11-26 15:38:43 -0800169 if err := p.AddToRoots(blessingForChild); err != nil {
Ryan Browna08a2212015-01-15 15:40:10 -0800170 return nil, err
Ankur9f957942014-11-24 16:34:18 -0800171 }
172
Ryan Browna08a2212015-01-15 15:40:10 -0800173 return conn, nil
Ankur9f957942014-11-24 16:34:18 -0800174}
175
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700176type Main func(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error
177
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700178// String returns a string representation of the Shell, which is a
Cosmos Nicolaouaddf4832014-09-10 21:36:54 -0700179// list of the commands currently available in the shell.
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700180func (sh *Shell) String() string {
Cosmos Nicolaoud4f00562014-11-17 20:35:48 -0800181 return registry.help("")
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700182}
183
184// Help returns the help message for the specified command.
185func (sh *Shell) Help(command string) string {
Cosmos Nicolaoud4f00562014-11-17 20:35:48 -0800186 return registry.help(command)
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700187}
188
James Ring9d9489d2015-01-27 15:48:07 -0800189func (sh *Shell) StartExternalCommand(env []string, args ...string) (Handle, error) {
190 if len(args) == 0 {
191 return nil, errors.New("no arguments specified to StartExternalCommand")
192 }
193 c := newExecHandleForExternalCommand(args[0])
194 return sh.startCommand(c, env, args...)
195}
196
Ankur9f957942014-11-24 16:34:18 -0800197// Start starts the specified command, it returns a Handle which can be
198// used for interacting with that command.
199//
200// The environment variables for the command are set by merging variables
201// from the OS environment, those in this Shell and those provided as a
202// parameter to it. In general, it prefers values from its parameter over
203// those from the Shell, over those from the OS. However, the VeyronCredentials
Ryan Brown10a4ade2015-02-10 13:17:18 -0800204// and agent FdEnvVar variables will never use the value from the Shell or OS.
Ankur9f957942014-11-24 16:34:18 -0800205//
Ryan Brown10a4ade2015-02-10 13:17:18 -0800206// If the shell is managing principals, the command is configured to
207// connect to the shell's agent.
208// To override this, or if the shell is not managing principals, set
209// the VeyronCredentials environment variable in the 'env' parameter.
Cosmos Nicolaou612ad382014-10-29 19:41:35 -0700210//
211// The Shell tracks all of the Handles that it creates so that it can shut
Cosmos Nicolaoue5b41502014-10-29 22:55:09 -0700212// them down when asked to. The returned Handle may be non-nil even when an
213// error is returned, in which case it may be used to retrieve any output
214// from the failed command.
Cosmos Nicolaou612ad382014-10-29 19:41:35 -0700215//
Cosmos Nicolaoud4f00562014-11-17 20:35:48 -0800216// Commands must have already been registered using RegisterFunction
217// or RegisterChild.
Cosmos Nicolaou612ad382014-10-29 19:41:35 -0700218func (sh *Shell) Start(name string, env []string, args ...string) (Handle, error) {
James Ring9d9489d2015-01-27 15:48:07 -0800219 cmd := registry.getCommand(name)
220 if cmd == nil {
221 return nil, fmt.Errorf("%s: not registered", name)
222 }
223 expanded := append([]string{name}, sh.expand(args...)...)
224 c := cmd.factory()
225 h, err := sh.startCommand(c, env, expanded...)
226 if err != nil {
227 // If the error is a timeout, then h can be used to recover
228 // any output from the process.
229 return h, err
230 }
231
232 if err := h.WaitForReady(sh.waitTimeout); err != nil {
233 return h, err
234 }
235 return h, nil
236}
237
238func (sh *Shell) startCommand(c command, env []string, args ...string) (Handle, error) {
Ankur9f957942014-11-24 16:34:18 -0800239 cenv, err := sh.setupCommandEnv(env)
240 if err != nil {
241 return nil, err
242 }
Cosmos Nicolaou82d00d82015-02-10 21:31:00 -0800243 p, err := sh.NewChildCredentials()
Ryan Browna08a2212015-01-15 15:40:10 -0800244 if err != nil {
245 return nil, err
246 }
James Ring9d9489d2015-01-27 15:48:07 -0800247
248 h, err := c.start(sh, p, cenv, args...)
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700249 if err != nil {
James Ring9d9489d2015-01-27 15:48:07 -0800250 return nil, err
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700251 }
252 sh.mu.Lock()
253 sh.handles[h] = struct{}{}
254 sh.mu.Unlock()
255 return h, nil
256}
257
Cosmos Nicolaoue5b41502014-10-29 22:55:09 -0700258// SetStartTimeout sets the timeout for starting subcommands.
259func (sh *Shell) SetStartTimeout(d time.Duration) {
260 sh.startTimeout = d
261}
262
Bogdan Caprita490a4512014-11-20 21:12:19 -0800263// SetWaitTimeout sets the timeout for waiting on subcommands to complete.
264func (sh *Shell) SetWaitTimeout(d time.Duration) {
265 sh.waitTimeout = d
266}
267
Cosmos Nicolaoue664f3f2014-10-20 17:40:05 -0700268// CommandEnvelope returns the command line and environment that would be
269// used for running the subprocess or function if it were started with the
270// specifed arguments.
Cosmos Nicolaou2cb05fb2014-10-23 13:37:32 -0700271func (sh *Shell) CommandEnvelope(name string, env []string, args ...string) ([]string, []string) {
Cosmos Nicolaoud4f00562014-11-17 20:35:48 -0800272 cmd := registry.getCommand(name)
273 if cmd == nil {
274 return []string{}, []string{}
275 }
Ankur9f957942014-11-24 16:34:18 -0800276 menv, err := sh.setupCommandEnv(env)
277 if err != nil {
278 return []string{}, []string{}
279 }
280 return cmd.factory().envelope(sh, menv, args...)
Cosmos Nicolaoue664f3f2014-10-20 17:40:05 -0700281}
282
283// Forget tells the Shell to stop tracking the supplied Handle. This is
284// generally used when the application wants to control the order that
285// commands are shutdown in.
286func (sh *Shell) Forget(h Handle) {
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700287 sh.mu.Lock()
288 delete(sh.handles, h)
289 sh.mu.Unlock()
290}
291
292func (sh *Shell) expand(args ...string) []string {
293 exp := []string{}
294 for _, a := range args {
295 if len(a) > 0 && a[0] == '$' {
296 if v, present := sh.env[a[1:]]; present {
297 exp = append(exp, v)
298 continue
299 }
300 }
301 exp = append(exp, a)
302 }
303 return exp
304}
305
306// GetVar returns the variable associated with the specified key
307// and an indication of whether it is defined or not.
308func (sh *Shell) GetVar(key string) (string, bool) {
309 sh.mu.Lock()
310 defer sh.mu.Unlock()
311 v, present := sh.env[key]
312 return v, present
313}
314
315// SetVar sets the value to be associated with key.
316func (sh *Shell) SetVar(key, value string) {
317 sh.mu.Lock()
318 defer sh.mu.Unlock()
319 // TODO(cnicolaou): expand value
320 sh.env[key] = value
321}
322
Cosmos Nicolaou2cb05fb2014-10-23 13:37:32 -0700323// ClearVar removes the speficied variable from the Shell's environment
324func (sh *Shell) ClearVar(key string) {
325 sh.mu.Lock()
326 defer sh.mu.Unlock()
327 delete(sh.env, key)
328}
329
Jiri Simsa37893392014-11-07 10:55:45 -0800330// GetConfigKey returns the value associated with the specified key in
331// the Shell's config and an indication of whether it is defined or
332// not.
333func (sh *Shell) GetConfigKey(key string) (string, bool) {
334 v, err := sh.config.Get(key)
335 return v, err == nil
336}
337
338// SetConfigKey sets the value of the specified key in the Shell's
339// config.
340func (sh *Shell) SetConfigKey(key, value string) {
341 sh.config.Set(key, value)
342}
343
344// ClearConfigKey removes the speficied key from the Shell's config.
345func (sh *Shell) ClearConfigKey(key string) {
346 sh.config.Clear(key)
347}
348
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700349// Env returns the entire set of environment variables associated with this
350// Shell as a string slice.
351func (sh *Shell) Env() []string {
352 vars := []string{}
353 sh.mu.Lock()
354 defer sh.mu.Unlock()
355 for k, v := range sh.env {
356 vars = append(vars, k+"="+v)
357 }
358 return vars
359}
360
361// Cleanup calls Shutdown on all of the Handles currently being tracked
Cosmos Nicolaoubbae3882014-10-02 22:58:19 -0700362// by the Shell and writes to stdout and stderr as per the Shutdown
Cosmos Nicolaou2cb05fb2014-10-23 13:37:32 -0700363// method in the Handle interface. Cleanup returns the error from the
364// last Shutdown that returned a non-nil error. The order that the
365// Shutdown routines are executed is not defined.
366func (sh *Shell) Cleanup(stdout, stderr io.Writer) error {
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700367 sh.mu.Lock()
Cosmos Nicolaou66afced2014-09-15 22:12:43 -0700368 handles := make(map[Handle]struct{})
369 for k, v := range sh.handles {
370 handles[k] = v
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700371 }
372 sh.handles = make(map[Handle]struct{})
Cosmos Nicolaou66afced2014-09-15 22:12:43 -0700373 sh.mu.Unlock()
Cosmos Nicolaou2cb05fb2014-10-23 13:37:32 -0700374 var err error
Cosmos Nicolaou66afced2014-09-15 22:12:43 -0700375 for k, _ := range handles {
Cosmos Nicolaou2cb05fb2014-10-23 13:37:32 -0700376 cerr := k.Shutdown(stdout, stderr)
377 if cerr != nil {
378 err = cerr
379 }
Cosmos Nicolaou66afced2014-09-15 22:12:43 -0700380 }
Ankur9f957942014-11-24 16:34:18 -0800381
Ryan Browna08a2212015-01-15 15:40:10 -0800382 if sh.cancelCtx != nil {
383 // Note(ribrdb, caprita): This will shutdown the agents. If there
384 // were errors shutting down it is possible there could be child
385 // processes still running, and stopping the agent may cause
386 // additional failures.
387 sh.cancelCtx()
Cosmos Nicolaou9afe1202014-09-19 13:45:57 -0700388 }
Ryan Browna08a2212015-01-15 15:40:10 -0800389 os.RemoveAll(sh.tempCredDir)
Cosmos Nicolaou2cb05fb2014-10-23 13:37:32 -0700390 return err
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700391}
392
Ankur9f957942014-11-24 16:34:18 -0800393func (sh *Shell) setupCommandEnv(env []string) ([]string, error) {
Cosmos Nicolaou2cb05fb2014-10-23 13:37:32 -0700394 osmap := envSliceToMap(os.Environ())
395 evmap := envSliceToMap(env)
Ankur9f957942014-11-24 16:34:18 -0800396
Cosmos Nicolaou2cb05fb2014-10-23 13:37:32 -0700397 sh.mu.Lock()
Cosmos Nicolaou2cb05fb2014-10-23 13:37:32 -0700398 defer sh.mu.Unlock()
Ankur9f957942014-11-24 16:34:18 -0800399 m1 := mergeMaps(osmap, sh.env)
400 // Clear any VeyronCredentials directory in m1 as we never
401 // want the child to directly use the directory specified
402 // by the shell's VeyronCredentials.
403 delete(m1, consts.VeyronCredentials)
Ryan Browna08a2212015-01-15 15:40:10 -0800404 delete(m1, agent.FdVarName)
Ankur9f957942014-11-24 16:34:18 -0800405
Cosmos Nicolaou2cb05fb2014-10-23 13:37:32 -0700406 m2 := mergeMaps(m1, evmap)
407 r := []string{}
408 for k, v := range m2 {
409 r = append(r, k+"="+v)
Cosmos Nicolaoue664f3f2014-10-20 17:40:05 -0700410 }
Ankur9f957942014-11-24 16:34:18 -0800411 return r, nil
Cosmos Nicolaoue664f3f2014-10-20 17:40:05 -0700412}
413
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700414// Handle represents a running command.
415type Handle interface {
Cosmos Nicolaou9ca249d2014-09-18 15:07:12 -0700416 // Stdout returns a reader to the running command's stdout stream.
417 Stdout() io.Reader
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700418
Cosmos Nicolaou9ca249d2014-09-18 15:07:12 -0700419 // Stderr returns a reader to the running command's stderr
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700420 // stream.
421 Stderr() io.Reader
422
423 // Stdin returns a writer to the running command's stdin. The
424 // convention is for commands to wait for stdin to be closed before
425 // they exit, thus the caller should close stdin when it wants the
426 // command to exit cleanly.
Cosmos Nicolaou66afced2014-09-15 22:12:43 -0700427 Stdin() io.Writer
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700428
Cosmos Nicolaou66afced2014-09-15 22:12:43 -0700429 // CloseStdin closes stdin in a manner that avoids a data race
430 // between any current readers on it.
431 CloseStdin()
432
433 // Shutdown closes the Stdin for the command and then reads output
Cosmos Nicolaoubbae3882014-10-02 22:58:19 -0700434 // from the command's stdout until it encounters EOF, waits for
435 // the command to complete and then reads all of its stderr output.
436 // The stdout and stderr contents are written to the corresponding
437 // io.Writers if they are non-nil, otherwise the content is discarded.
438 Shutdown(stdout, stderr io.Writer) error
Cosmos Nicolaoucc581722014-10-07 12:45:39 -0700439
440 // Pid returns the pid of the process running the command
441 Pid() int
James Ring9d9489d2015-01-27 15:48:07 -0800442
443 // WaitForReady waits until the child process signals to us that it is
444 // ready. If this does not occur within the given timeout duration, a
445 // timeout error is returned.
446 WaitForReady(timeout time.Duration) error
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700447}
448
449// command is used to abstract the implementations of inprocess and subprocess
450// commands.
451type command interface {
Cosmos Nicolaou2cb05fb2014-10-23 13:37:32 -0700452 envelope(sh *Shell, env []string, args ...string) ([]string, []string)
Ryan Browna08a2212015-01-15 15:40:10 -0800453 start(sh *Shell, agent *os.File, env []string, args ...string) (Handle, error)
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700454}