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