Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 1 | // 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 |
| 4 | // registered with and executed within a context, defined by the Shell type. |
| 5 | // The Shell is analagous to the original UNIX shell and maintains a |
| 6 | // key, value store of variables that is accessible to all of the commands that |
| 7 | // it hosts. These variables may be referenced by the arguments passed to |
| 8 | // commands. |
| 9 | // |
| 10 | // Commands are added to a shell in two ways: one for a subprocess and another |
| 11 | // for an inprocess function. |
| 12 | // |
| 13 | // - subprocesses are added using the AddSubprocess method in the parent |
| 14 | // and by the modules.RegisterChild function in the child process (typically |
| 15 | // RegisterChild is called from an init function). modules.Dispatch must |
| 16 | // be called in the child process to execute the subprocess 'Main' function |
| 17 | // provided to RegisterChild. |
| 18 | // - inprocess functions are added using the AddFunction method. |
| 19 | // |
| 20 | // In all cases commands are started by invoking the Start method on the |
| 21 | // Shell with the name of the command to run. An instance of the Handle |
| 22 | // interface is returned which can be used to interact with the function |
| 23 | // or subprocess, and in particular to read/write data from/to it using io |
| 24 | // channels that follow the stdin, stdout, stderr convention. |
| 25 | // |
| 26 | // A simple protocol must be followed by all commands, namely, they |
| 27 | // should wait for their stdin stream to be closed before exiting. The |
| 28 | // caller can then coordinate with any command by writing to that stdin |
| 29 | // stream and reading responses from the stdout stream, and it can close |
Cosmos Nicolaou | 66afced | 2014-09-15 22:12:43 -0700 | [diff] [blame] | 30 | // stdin when it's ready for the command to exit using the CloseStdin method |
| 31 | // on the command's handle. |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 32 | // |
| 33 | // The signature of the function that implements the command is the |
| 34 | // same for both types of command and is defined by the Main function type. |
| 35 | // In particular stdin, stdout and stderr are provided as parameters, as is |
| 36 | // a map representation of the shell's environment. |
Cosmos Nicolaou | 9afe120 | 2014-09-19 13:45:57 -0700 | [diff] [blame] | 37 | // |
| 38 | // If a Shell is created within a unit test then it will automatically |
| 39 | // generate a security ID, write it to a file and set the appropriate |
| 40 | // environment variable to refer to it. |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 41 | package modules |
| 42 | |
| 43 | import ( |
Cosmos Nicolaou | 9afe120 | 2014-09-19 13:45:57 -0700 | [diff] [blame] | 44 | "flag" |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 45 | "io" |
Cosmos Nicolaou | 9afe120 | 2014-09-19 13:45:57 -0700 | [diff] [blame] | 46 | "io/ioutil" |
| 47 | "os" |
Cosmos Nicolaou | 0f0e877 | 2014-09-10 21:29:25 -0700 | [diff] [blame] | 48 | "strings" |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 49 | "sync" |
Cosmos Nicolaou | e5b4150 | 2014-10-29 22:55:09 -0700 | [diff] [blame] | 50 | "time" |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 51 | |
Jiri Simsa | 519c507 | 2014-09-17 21:37:57 -0700 | [diff] [blame] | 52 | "veyron.io/veyron/veyron2/vlog" |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 53 | ) |
| 54 | |
| 55 | // Shell represents the context within which commands are run. |
| 56 | type Shell struct { |
Cosmos Nicolaou | e5b4150 | 2014-10-29 22:55:09 -0700 | [diff] [blame] | 57 | mu sync.Mutex |
| 58 | env map[string]string |
| 59 | cmds map[string]*commandDesc |
| 60 | handles map[Handle]struct{} |
| 61 | credDir string |
| 62 | startTimeout time.Duration |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 63 | } |
| 64 | |
| 65 | type commandDesc struct { |
| 66 | factory func() command |
| 67 | help string |
| 68 | } |
| 69 | |
Cosmos Nicolaou | 1e78ccc | 2014-10-09 08:10:26 -0700 | [diff] [blame] | 70 | type childEntryPoint struct { |
| 71 | fn Main |
| 72 | help string |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 73 | } |
| 74 | |
Cosmos Nicolaou | 1e78ccc | 2014-10-09 08:10:26 -0700 | [diff] [blame] | 75 | type childRegistrar struct { |
| 76 | sync.Mutex |
| 77 | mains map[string]*childEntryPoint |
| 78 | } |
| 79 | |
| 80 | var child = &childRegistrar{mains: make(map[string]*childEntryPoint)} |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 81 | |
Cosmos Nicolaou | 9afe120 | 2014-09-19 13:45:57 -0700 | [diff] [blame] | 82 | // NewShell creates a new instance of Shell. If this new instance is |
Cosmos Nicolaou | f47699b | 2014-10-10 14:36:23 -0700 | [diff] [blame] | 83 | // is a test and no credentials have been configured in the environment |
| 84 | // via VEYRON_CREDENTIALS then CreateAndUseNewCredentials will be used to |
| 85 | // configure a new ID for the shell and its children. |
Cosmos Nicolaou | 1e78ccc | 2014-10-09 08:10:26 -0700 | [diff] [blame] | 86 | // NewShell takes optional regexp patterns that can be used to specify |
| 87 | // subprocess commands that are implemented in the same binary as this shell |
| 88 | // (i.e. have been registered using modules.RegisterChild) to be |
| 89 | // automatically added to it. If the patterns fail to match any such command |
| 90 | // then they have no effect. |
| 91 | func NewShell(patterns ...string) *Shell { |
Cosmos Nicolaou | 9ca249d | 2014-09-18 15:07:12 -0700 | [diff] [blame] | 92 | // TODO(cnicolaou): should create a new identity if one doesn't |
| 93 | // already exist |
Cosmos Nicolaou | 9afe120 | 2014-09-19 13:45:57 -0700 | [diff] [blame] | 94 | sh := &Shell{ |
Cosmos Nicolaou | e5b4150 | 2014-10-29 22:55:09 -0700 | [diff] [blame] | 95 | env: make(map[string]string), |
| 96 | cmds: make(map[string]*commandDesc), |
| 97 | handles: make(map[Handle]struct{}), |
| 98 | startTimeout: time.Minute, |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 99 | } |
Cosmos Nicolaou | f47699b | 2014-10-10 14:36:23 -0700 | [diff] [blame] | 100 | if flag.Lookup("test.run") != nil && os.Getenv("VEYRON_CREDENTIALS") == "" { |
| 101 | if err := sh.CreateAndUseNewCredentials(); err != nil { |
| 102 | // TODO(cnicolaou): return an error rather than panic. |
Cosmos Nicolaou | 9afe120 | 2014-09-19 13:45:57 -0700 | [diff] [blame] | 103 | panic(err) |
| 104 | } |
| 105 | } |
Cosmos Nicolaou | 1e78ccc | 2014-10-09 08:10:26 -0700 | [diff] [blame] | 106 | for _, pattern := range patterns { |
| 107 | child.addSubprocesses(sh, pattern) |
| 108 | } |
Cosmos Nicolaou | 9afe120 | 2014-09-19 13:45:57 -0700 | [diff] [blame] | 109 | return sh |
| 110 | } |
| 111 | |
Cosmos Nicolaou | f47699b | 2014-10-10 14:36:23 -0700 | [diff] [blame] | 112 | // CreateAndUseNewCredentials setups a new credentials directory and then |
| 113 | // configures the shell and all of its children to use to it. |
Cosmos Nicolaou | 2cb05fb | 2014-10-23 13:37:32 -0700 | [diff] [blame] | 114 | // |
| 115 | // TODO(cnicolaou): this should use the principal already setup |
| 116 | // with the runtime if the runtime has been initialized, if not, |
| 117 | // it should create a new principal. As of now, this approach only works |
| 118 | // for child processes that talk to each other, but not to the parent |
| 119 | // process that started them since it's running with a different set of |
| 120 | // credentials setup elsewhere. When this change is made it should |
| 121 | // be possible to remove creating credentials in many unit tests. |
Cosmos Nicolaou | f47699b | 2014-10-10 14:36:23 -0700 | [diff] [blame] | 122 | func (sh *Shell) CreateAndUseNewCredentials() error { |
| 123 | dir, err := ioutil.TempDir("", "veyron_credentials") |
Cosmos Nicolaou | 9afe120 | 2014-09-19 13:45:57 -0700 | [diff] [blame] | 124 | if err != nil { |
| 125 | return err |
| 126 | } |
Cosmos Nicolaou | f47699b | 2014-10-10 14:36:23 -0700 | [diff] [blame] | 127 | sh.credDir = dir |
| 128 | sh.SetVar("VEYRON_CREDENTIALS", sh.credDir) |
Cosmos Nicolaou | 9afe120 | 2014-09-19 13:45:57 -0700 | [diff] [blame] | 129 | return nil |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 130 | } |
| 131 | |
| 132 | type Main func(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error |
| 133 | |
| 134 | // AddSubprocess adds a new command to the Shell that will be run |
| 135 | // as a subprocess. In addition, the child process must call RegisterChild |
| 136 | // using the same name used here and provide the function to be executed |
| 137 | // in the child. |
Cosmos Nicolaou | 1e78ccc | 2014-10-09 08:10:26 -0700 | [diff] [blame] | 138 | func (sh *Shell) AddSubprocess(name, help string) { |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 139 | if !child.hasCommand(name) { |
| 140 | vlog.Infof("Warning: %q is not registered with modules.Dispatcher", name) |
| 141 | } |
Cosmos Nicolaou | 1e78ccc | 2014-10-09 08:10:26 -0700 | [diff] [blame] | 142 | sh.addSubprocess(name, help) |
| 143 | } |
| 144 | |
| 145 | func (sh *Shell) addSubprocess(name string, help string) { |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 146 | sh.mu.Lock() |
Cosmos Nicolaou | 2cb05fb | 2014-10-23 13:37:32 -0700 | [diff] [blame] | 147 | sh.cmds[name] = &commandDesc{func() command { return newExecHandle(name) }, help} |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 148 | sh.mu.Unlock() |
| 149 | } |
| 150 | |
| 151 | // AddFunction adds a new command to the Shell that will be run |
| 152 | // within the current process. |
| 153 | func (sh *Shell) AddFunction(name string, main Main, help string) { |
| 154 | sh.mu.Lock() |
Cosmos Nicolaou | 2cb05fb | 2014-10-23 13:37:32 -0700 | [diff] [blame] | 155 | sh.cmds[name] = &commandDesc{func() command { return newFunctionHandle(name, main) }, help} |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 156 | sh.mu.Unlock() |
| 157 | } |
| 158 | |
| 159 | // String returns a string representation of the Shell, which is a |
Cosmos Nicolaou | addf483 | 2014-09-10 21:36:54 -0700 | [diff] [blame] | 160 | // list of the commands currently available in the shell. |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 161 | func (sh *Shell) String() string { |
| 162 | sh.mu.Lock() |
| 163 | defer sh.mu.Unlock() |
| 164 | h := "" |
Cosmos Nicolaou | 0f0e877 | 2014-09-10 21:29:25 -0700 | [diff] [blame] | 165 | for n, _ := range sh.cmds { |
| 166 | h += n + ", " |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 167 | } |
Cosmos Nicolaou | 0f0e877 | 2014-09-10 21:29:25 -0700 | [diff] [blame] | 168 | return strings.TrimRight(h, ", ") |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 169 | } |
| 170 | |
| 171 | // Help returns the help message for the specified command. |
| 172 | func (sh *Shell) Help(command string) string { |
| 173 | sh.mu.Lock() |
| 174 | defer sh.mu.Unlock() |
| 175 | if c := sh.cmds[command]; c != nil { |
Cosmos Nicolaou | 0f0e877 | 2014-09-10 21:29:25 -0700 | [diff] [blame] | 176 | return command + ": " + c.help |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 177 | } |
| 178 | return "" |
| 179 | } |
| 180 | |
| 181 | // Start starts the specified command, it returns a Handle which can be used |
Cosmos Nicolaou | 612ad38 | 2014-10-29 19:41:35 -0700 | [diff] [blame] | 182 | // for interacting with that command. The OS and shell environment variables |
| 183 | // are merged with the ones supplied as a parameter; the parameter specified |
| 184 | // ones override the Shell and the Shell ones override the OS ones. |
| 185 | // |
| 186 | // The Shell tracks all of the Handles that it creates so that it can shut |
Cosmos Nicolaou | e5b4150 | 2014-10-29 22:55:09 -0700 | [diff] [blame] | 187 | // them down when asked to. The returned Handle may be non-nil even when an |
| 188 | // error is returned, in which case it may be used to retrieve any output |
| 189 | // from the failed command. |
Cosmos Nicolaou | 612ad38 | 2014-10-29 19:41:35 -0700 | [diff] [blame] | 190 | // |
Cosmos Nicolaou | 1e78ccc | 2014-10-09 08:10:26 -0700 | [diff] [blame] | 191 | // Commands may have already been registered with the Shell using AddFunction |
| 192 | // or AddSubprocess, but if not, they will treated as subprocess commands |
| 193 | // and an attempt made to run them. Such 'dynamically' started subprocess |
| 194 | // commands are not remembered the by Shell and do not provide a 'help' |
| 195 | // message etc; their handles are remembered and will be acted on by |
| 196 | // the Cleanup method. If the non-registered subprocess command does not |
| 197 | // exist then the Start command will return an error. |
Cosmos Nicolaou | 612ad38 | 2014-10-29 19:41:35 -0700 | [diff] [blame] | 198 | func (sh *Shell) Start(name string, env []string, args ...string) (Handle, error) { |
Cosmos Nicolaou | 2cb05fb | 2014-10-23 13:37:32 -0700 | [diff] [blame] | 199 | cenv := sh.MergedEnv(env) |
Cosmos Nicolaou | e664f3f | 2014-10-20 17:40:05 -0700 | [diff] [blame] | 200 | cmd := sh.getCommand(name) |
Cosmos Nicolaou | 1e78ccc | 2014-10-09 08:10:26 -0700 | [diff] [blame] | 201 | expanded := append([]string{name}, sh.expand(args...)...) |
Cosmos Nicolaou | 2cb05fb | 2014-10-23 13:37:32 -0700 | [diff] [blame] | 202 | h, err := cmd.factory().start(sh, cenv, expanded...) |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 203 | if err != nil { |
Cosmos Nicolaou | e5b4150 | 2014-10-29 22:55:09 -0700 | [diff] [blame] | 204 | // If the error is a timeout, then h can be used to recover |
| 205 | // any output from the process. |
| 206 | return h, err |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 207 | } |
| 208 | sh.mu.Lock() |
| 209 | sh.handles[h] = struct{}{} |
| 210 | sh.mu.Unlock() |
| 211 | return h, nil |
| 212 | } |
| 213 | |
Cosmos Nicolaou | e664f3f | 2014-10-20 17:40:05 -0700 | [diff] [blame] | 214 | func (sh *Shell) getCommand(name string) *commandDesc { |
| 215 | sh.mu.Lock() |
| 216 | cmd := sh.cmds[name] |
| 217 | sh.mu.Unlock() |
| 218 | if cmd == nil { |
Cosmos Nicolaou | 2cb05fb | 2014-10-23 13:37:32 -0700 | [diff] [blame] | 219 | cmd = &commandDesc{func() command { return newExecHandle(name) }, ""} |
Cosmos Nicolaou | e664f3f | 2014-10-20 17:40:05 -0700 | [diff] [blame] | 220 | } |
| 221 | return cmd |
| 222 | } |
| 223 | |
Cosmos Nicolaou | e5b4150 | 2014-10-29 22:55:09 -0700 | [diff] [blame] | 224 | // SetStartTimeout sets the timeout for starting subcommands. |
| 225 | func (sh *Shell) SetStartTimeout(d time.Duration) { |
| 226 | sh.startTimeout = d |
| 227 | } |
| 228 | |
Cosmos Nicolaou | e664f3f | 2014-10-20 17:40:05 -0700 | [diff] [blame] | 229 | // CommandEnvelope returns the command line and environment that would be |
| 230 | // used for running the subprocess or function if it were started with the |
| 231 | // specifed arguments. |
Cosmos Nicolaou | 2cb05fb | 2014-10-23 13:37:32 -0700 | [diff] [blame] | 232 | func (sh *Shell) CommandEnvelope(name string, env []string, args ...string) ([]string, []string) { |
| 233 | return sh.getCommand(name).factory().envelope(sh, sh.MergedEnv(env), args...) |
Cosmos Nicolaou | e664f3f | 2014-10-20 17:40:05 -0700 | [diff] [blame] | 234 | } |
| 235 | |
| 236 | // Forget tells the Shell to stop tracking the supplied Handle. This is |
| 237 | // generally used when the application wants to control the order that |
| 238 | // commands are shutdown in. |
| 239 | func (sh *Shell) Forget(h Handle) { |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 240 | sh.mu.Lock() |
| 241 | delete(sh.handles, h) |
| 242 | sh.mu.Unlock() |
| 243 | } |
| 244 | |
| 245 | func (sh *Shell) expand(args ...string) []string { |
| 246 | exp := []string{} |
| 247 | for _, a := range args { |
| 248 | if len(a) > 0 && a[0] == '$' { |
| 249 | if v, present := sh.env[a[1:]]; present { |
| 250 | exp = append(exp, v) |
| 251 | continue |
| 252 | } |
| 253 | } |
| 254 | exp = append(exp, a) |
| 255 | } |
| 256 | return exp |
| 257 | } |
| 258 | |
| 259 | // GetVar returns the variable associated with the specified key |
| 260 | // and an indication of whether it is defined or not. |
| 261 | func (sh *Shell) GetVar(key string) (string, bool) { |
| 262 | sh.mu.Lock() |
| 263 | defer sh.mu.Unlock() |
| 264 | v, present := sh.env[key] |
| 265 | return v, present |
| 266 | } |
| 267 | |
| 268 | // SetVar sets the value to be associated with key. |
| 269 | func (sh *Shell) SetVar(key, value string) { |
| 270 | sh.mu.Lock() |
| 271 | defer sh.mu.Unlock() |
| 272 | // TODO(cnicolaou): expand value |
| 273 | sh.env[key] = value |
| 274 | } |
| 275 | |
Cosmos Nicolaou | 2cb05fb | 2014-10-23 13:37:32 -0700 | [diff] [blame] | 276 | // ClearVar removes the speficied variable from the Shell's environment |
| 277 | func (sh *Shell) ClearVar(key string) { |
| 278 | sh.mu.Lock() |
| 279 | defer sh.mu.Unlock() |
| 280 | delete(sh.env, key) |
| 281 | } |
| 282 | |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 283 | // Env returns the entire set of environment variables associated with this |
| 284 | // Shell as a string slice. |
| 285 | func (sh *Shell) Env() []string { |
| 286 | vars := []string{} |
| 287 | sh.mu.Lock() |
| 288 | defer sh.mu.Unlock() |
| 289 | for k, v := range sh.env { |
| 290 | vars = append(vars, k+"="+v) |
| 291 | } |
| 292 | return vars |
| 293 | } |
| 294 | |
| 295 | // Cleanup calls Shutdown on all of the Handles currently being tracked |
Cosmos Nicolaou | bbae388 | 2014-10-02 22:58:19 -0700 | [diff] [blame] | 296 | // by the Shell and writes to stdout and stderr as per the Shutdown |
Cosmos Nicolaou | 2cb05fb | 2014-10-23 13:37:32 -0700 | [diff] [blame] | 297 | // method in the Handle interface. Cleanup returns the error from the |
| 298 | // last Shutdown that returned a non-nil error. The order that the |
| 299 | // Shutdown routines are executed is not defined. |
| 300 | func (sh *Shell) Cleanup(stdout, stderr io.Writer) error { |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 301 | sh.mu.Lock() |
Cosmos Nicolaou | 66afced | 2014-09-15 22:12:43 -0700 | [diff] [blame] | 302 | handles := make(map[Handle]struct{}) |
| 303 | for k, v := range sh.handles { |
| 304 | handles[k] = v |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 305 | } |
| 306 | sh.handles = make(map[Handle]struct{}) |
Cosmos Nicolaou | 66afced | 2014-09-15 22:12:43 -0700 | [diff] [blame] | 307 | sh.mu.Unlock() |
Cosmos Nicolaou | 2cb05fb | 2014-10-23 13:37:32 -0700 | [diff] [blame] | 308 | var err error |
Cosmos Nicolaou | 66afced | 2014-09-15 22:12:43 -0700 | [diff] [blame] | 309 | for k, _ := range handles { |
Cosmos Nicolaou | 2cb05fb | 2014-10-23 13:37:32 -0700 | [diff] [blame] | 310 | cerr := k.Shutdown(stdout, stderr) |
| 311 | if cerr != nil { |
| 312 | err = cerr |
| 313 | } |
Cosmos Nicolaou | 66afced | 2014-09-15 22:12:43 -0700 | [diff] [blame] | 314 | } |
Cosmos Nicolaou | f47699b | 2014-10-10 14:36:23 -0700 | [diff] [blame] | 315 | if len(sh.credDir) > 0 { |
| 316 | os.RemoveAll(sh.credDir) |
Cosmos Nicolaou | 9afe120 | 2014-09-19 13:45:57 -0700 | [diff] [blame] | 317 | } |
Cosmos Nicolaou | 2cb05fb | 2014-10-23 13:37:32 -0700 | [diff] [blame] | 318 | return err |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 319 | } |
| 320 | |
Cosmos Nicolaou | e664f3f | 2014-10-20 17:40:05 -0700 | [diff] [blame] | 321 | // MergedEnv returns a slice that contains the merged set of environment |
Cosmos Nicolaou | 2cb05fb | 2014-10-23 13:37:32 -0700 | [diff] [blame] | 322 | // variables from the OS environment, those in this Shell and those provided |
| 323 | // as a parameter to it. It prefers values from its parameter over those |
| 324 | // from the Shell, over those from the OS. |
| 325 | func (sh *Shell) MergedEnv(env []string) []string { |
| 326 | osmap := envSliceToMap(os.Environ()) |
| 327 | evmap := envSliceToMap(env) |
| 328 | sh.mu.Lock() |
| 329 | m1 := mergeMaps(osmap, sh.env) |
| 330 | defer sh.mu.Unlock() |
| 331 | m2 := mergeMaps(m1, evmap) |
| 332 | r := []string{} |
| 333 | for k, v := range m2 { |
| 334 | r = append(r, k+"="+v) |
Cosmos Nicolaou | e664f3f | 2014-10-20 17:40:05 -0700 | [diff] [blame] | 335 | } |
Cosmos Nicolaou | 2cb05fb | 2014-10-23 13:37:32 -0700 | [diff] [blame] | 336 | return r |
Cosmos Nicolaou | e664f3f | 2014-10-20 17:40:05 -0700 | [diff] [blame] | 337 | } |
| 338 | |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 339 | // Handle represents a running command. |
| 340 | type Handle interface { |
Cosmos Nicolaou | 9ca249d | 2014-09-18 15:07:12 -0700 | [diff] [blame] | 341 | // Stdout returns a reader to the running command's stdout stream. |
| 342 | Stdout() io.Reader |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 343 | |
Cosmos Nicolaou | 9ca249d | 2014-09-18 15:07:12 -0700 | [diff] [blame] | 344 | // Stderr returns a reader to the running command's stderr |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 345 | // stream. |
| 346 | Stderr() io.Reader |
| 347 | |
| 348 | // Stdin returns a writer to the running command's stdin. The |
| 349 | // convention is for commands to wait for stdin to be closed before |
| 350 | // they exit, thus the caller should close stdin when it wants the |
| 351 | // command to exit cleanly. |
Cosmos Nicolaou | 66afced | 2014-09-15 22:12:43 -0700 | [diff] [blame] | 352 | Stdin() io.Writer |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 353 | |
Cosmos Nicolaou | 66afced | 2014-09-15 22:12:43 -0700 | [diff] [blame] | 354 | // CloseStdin closes stdin in a manner that avoids a data race |
| 355 | // between any current readers on it. |
| 356 | CloseStdin() |
| 357 | |
| 358 | // Shutdown closes the Stdin for the command and then reads output |
Cosmos Nicolaou | bbae388 | 2014-10-02 22:58:19 -0700 | [diff] [blame] | 359 | // from the command's stdout until it encounters EOF, waits for |
| 360 | // the command to complete and then reads all of its stderr output. |
| 361 | // The stdout and stderr contents are written to the corresponding |
| 362 | // io.Writers if they are non-nil, otherwise the content is discarded. |
| 363 | Shutdown(stdout, stderr io.Writer) error |
Cosmos Nicolaou | cc58172 | 2014-10-07 12:45:39 -0700 | [diff] [blame] | 364 | |
| 365 | // Pid returns the pid of the process running the command |
| 366 | Pid() int |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 367 | } |
| 368 | |
| 369 | // command is used to abstract the implementations of inprocess and subprocess |
| 370 | // commands. |
| 371 | type command interface { |
Cosmos Nicolaou | 2cb05fb | 2014-10-23 13:37:32 -0700 | [diff] [blame] | 372 | envelope(sh *Shell, env []string, args ...string) ([]string, []string) |
| 373 | start(sh *Shell, env []string, args ...string) (Handle, error) |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 374 | } |