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 |
Cosmos Nicolaou | d4f0056 | 2014-11-17 20:35:48 -0800 | [diff] [blame] | 4 | // 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 Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 9 | // |
Cosmos Nicolaou | d4f0056 | 2014-11-17 20:35:48 -0800 | [diff] [blame] | 10 | // Commands are added to the registry in two ways: |
| 11 | // - via RegisterChild for a subprocess |
| 12 | // - via RegisterFunction for an in-process function |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 13 | // |
| 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 Nicolaou | 66afced | 2014-09-15 22:12:43 -0700 | [diff] [blame] | 24 | // stdin when it's ready for the command to exit using the CloseStdin method |
| 25 | // on the command's handle. |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 26 | // |
| 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 Nicolaou | 9afe120 | 2014-09-19 13:45:57 -0700 | [diff] [blame] | 31 | // |
Ryan Brown | a08a221 | 2015-01-15 15:40:10 -0800 | [diff] [blame] | 32 | // 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 |
Ankur | 9f95794 | 2014-11-24 16:34:18 -0800 | [diff] [blame] | 36 | // parameter to the Start method. |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 37 | package modules |
| 38 | |
| 39 | import ( |
James Ring | 9d9489d | 2015-01-27 15:48:07 -0800 | [diff] [blame] | 40 | "errors" |
Cosmos Nicolaou | d4f0056 | 2014-11-17 20:35:48 -0800 | [diff] [blame] | 41 | "fmt" |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 42 | "io" |
Cosmos Nicolaou | 9afe120 | 2014-09-19 13:45:57 -0700 | [diff] [blame] | 43 | "io/ioutil" |
| 44 | "os" |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 45 | "sync" |
Ryan Brown | a08a221 | 2015-01-15 15:40:10 -0800 | [diff] [blame] | 46 | "syscall" |
Cosmos Nicolaou | e5b4150 | 2014-10-29 22:55:09 -0700 | [diff] [blame] | 47 | "time" |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 48 | |
Jiri Simsa | 764efb7 | 2014-12-25 20:57:03 -0800 | [diff] [blame] | 49 | "v.io/core/veyron2/security" |
Cosmos Nicolaou | 344cc4a | 2014-11-26 15:38:43 -0800 | [diff] [blame] | 50 | |
Jiri Simsa | 764efb7 | 2014-12-25 20:57:03 -0800 | [diff] [blame] | 51 | "v.io/core/veyron/lib/exec" |
| 52 | "v.io/core/veyron/lib/flags/consts" |
Ryan Brown | a08a221 | 2015-01-15 15:40:10 -0800 | [diff] [blame] | 53 | "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" |
Ankur | 9f95794 | 2014-11-24 16:34:18 -0800 | [diff] [blame] | 57 | ) |
| 58 | |
| 59 | const ( |
| 60 | shellBlessingExtension = "test-shell" |
| 61 | childBlessingExtension = "child" |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 62 | ) |
| 63 | |
| 64 | // Shell represents the context within which commands are run. |
| 65 | type Shell struct { |
Ankur | 9f95794 | 2014-11-24 16:34:18 -0800 | [diff] [blame] | 66 | mu sync.Mutex |
| 67 | env map[string]string |
| 68 | handles map[Handle]struct{} |
Ryan Brown | a08a221 | 2015-01-15 15:40:10 -0800 | [diff] [blame] | 69 | // tmpCredDir is the temporary directory created by this |
| 70 | // shell. This must be removed when the shell is cleaned up. |
| 71 | tempCredDir string |
Bogdan Caprita | 490a451 | 2014-11-20 21:12:19 -0800 | [diff] [blame] | 72 | startTimeout, waitTimeout time.Duration |
| 73 | config exec.Config |
Cosmos Nicolaou | 344cc4a | 2014-11-26 15:38:43 -0800 | [diff] [blame] | 74 | principal security.Principal |
Ryan Brown | a08a221 | 2015-01-15 15:40:10 -0800 | [diff] [blame] | 75 | agent *keymgr.Agent |
| 76 | ctx *context.T |
| 77 | cancelCtx func() |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 78 | } |
| 79 | |
Ryan Brown | a08a221 | 2015-01-15 15:40:10 -0800 | [diff] [blame] | 80 | // NewShell creates a new instance of Shell. |
Ankur | 50ab988 | 2015-02-17 12:17:17 -0800 | [diff] [blame^] | 81 | // |
Ryan Brown | a08a221 | 2015-01-15 15:40:10 -0800 | [diff] [blame] | 82 | // If ctx is non-nil, the shell will manage Principals for child processes. |
Ankur | 50ab988 | 2015-02-17 12:17:17 -0800 | [diff] [blame^] | 83 | // |
| 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 Brown | a08a221 | 2015-01-15 15:40:10 -0800 | [diff] [blame] | 87 | func NewShell(ctx *context.T, p security.Principal) (*Shell, error) { |
Cosmos Nicolaou | 9afe120 | 2014-09-19 13:45:57 -0700 | [diff] [blame] | 88 | sh := &Shell{ |
Cosmos Nicolaou | e5b4150 | 2014-10-29 22:55:09 -0700 | [diff] [blame] | 89 | env: make(map[string]string), |
Cosmos Nicolaou | e5b4150 | 2014-10-29 22:55:09 -0700 | [diff] [blame] | 90 | handles: make(map[Handle]struct{}), |
| 91 | startTimeout: time.Minute, |
Bogdan Caprita | 490a451 | 2014-11-20 21:12:19 -0800 | [diff] [blame] | 92 | waitTimeout: 10 * time.Second, |
Jiri Simsa | 3789339 | 2014-11-07 10:55:45 -0800 | [diff] [blame] | 93 | config: exec.NewConfig(), |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 94 | } |
Ryan Brown | a08a221 | 2015-01-15 15:40:10 -0800 | [diff] [blame] | 95 | if ctx == nil { |
Cosmos Nicolaou | 344cc4a | 2014-11-26 15:38:43 -0800 | [diff] [blame] | 96 | return sh, nil |
Cosmos Nicolaou | 9afe120 | 2014-09-19 13:45:57 -0700 | [diff] [blame] | 97 | } |
Ryan Brown | a08a221 | 2015-01-15 15:40:10 -0800 | [diff] [blame] | 98 | var err error |
| 99 | ctx, sh.cancelCtx = context.WithCancel(ctx) |
Suharsh Sivakumar | af862a5 | 2015-02-04 13:50:47 -0800 | [diff] [blame] | 100 | if ctx, err = veyron2.SetNewStreamManager(ctx); err != nil { |
Ryan Brown | a08a221 | 2015-01-15 15:40:10 -0800 | [diff] [blame] | 101 | 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 Brown | a08a221 | 2015-01-15 15:40:10 -0800 | [diff] [blame] | 111 | sh.principal = p |
Ankur | 50ab988 | 2015-02-17 12:17:17 -0800 | [diff] [blame^] | 112 | if sh.principal == nil { |
| 113 | sh.principal = veyron2.GetPrincipal(ctx) |
Cosmos Nicolaou | 344cc4a | 2014-11-26 15:38:43 -0800 | [diff] [blame] | 114 | } |
Cosmos Nicolaou | 344cc4a | 2014-11-26 15:38:43 -0800 | [diff] [blame] | 115 | return sh, nil |
Cosmos Nicolaou | 9afe120 | 2014-09-19 13:45:57 -0700 | [diff] [blame] | 116 | } |
| 117 | |
Ankur | 50ab988 | 2015-02-17 12:17:17 -0800 | [diff] [blame^] | 118 | // NewChildCredentials creates a new principal, served via the |
Cosmos Nicolaou | 82d00d8 | 2015-02-10 21:31:00 -0800 | [diff] [blame] | 119 | // security agent, that have a blessing from this shell's principal. |
Ankur | 50ab988 | 2015-02-17 12:17:17 -0800 | [diff] [blame^] | 120 | // All processes started by this shell will have access to such a |
| 121 | // principal. |
Cosmos Nicolaou | 82d00d8 | 2015-02-10 21:31:00 -0800 | [diff] [blame] | 122 | func (sh *Shell) NewChildCredentials() (c *os.File, err error) { |
Ryan Brown | a08a221 | 2015-01-15 15:40:10 -0800 | [diff] [blame] | 123 | if sh.ctx == nil { |
| 124 | return nil, nil |
Ankur | 9f95794 | 2014-11-24 16:34:18 -0800 | [diff] [blame] | 125 | } |
Ankur | 50ab988 | 2015-02-17 12:17:17 -0800 | [diff] [blame^] | 126 | |
| 127 | // Create child principal. |
Ryan Brown | a08a221 | 2015-01-15 15:40:10 -0800 | [diff] [blame] | 128 | _, conn, err := sh.agent.NewPrincipal(sh.ctx, true) |
Ankur | 9f95794 | 2014-11-24 16:34:18 -0800 | [diff] [blame] | 129 | if err != nil { |
Ryan Brown | a08a221 | 2015-01-15 15:40:10 -0800 | [diff] [blame] | 130 | return nil, err |
Ankur | 9f95794 | 2014-11-24 16:34:18 -0800 | [diff] [blame] | 131 | } |
Bogdan Caprita | 6613fc4 | 2015-01-28 11:54:23 -0800 | [diff] [blame] | 132 | defer func() { |
| 133 | if err != nil { |
| 134 | conn.Close() |
| 135 | } |
| 136 | }() |
Ryan Brown | a08a221 | 2015-01-15 15:40:10 -0800 | [diff] [blame] | 137 | ctx, cancel := context.WithCancel(sh.ctx) |
Bogdan Caprita | 6613fc4 | 2015-01-28 11:54:23 -0800 | [diff] [blame] | 138 | defer cancel() |
Suharsh Sivakumar | af862a5 | 2015-02-04 13:50:47 -0800 | [diff] [blame] | 139 | if ctx, err = veyron2.SetNewStreamManager(ctx); err != nil { |
Ryan Brown | a08a221 | 2015-01-15 15:40:10 -0800 | [diff] [blame] | 140 | return nil, err |
| 141 | } |
Bogdan Caprita | bb37c54 | 2015-01-22 10:21:57 -0800 | [diff] [blame] | 142 | syscall.ForkLock.RLock() |
Ryan Brown | a08a221 | 2015-01-15 15:40:10 -0800 | [diff] [blame] | 143 | fd, err := syscall.Dup(int(conn.Fd())) |
Ankur | 9f95794 | 2014-11-24 16:34:18 -0800 | [diff] [blame] | 144 | if err != nil { |
Bogdan Caprita | bb37c54 | 2015-01-22 10:21:57 -0800 | [diff] [blame] | 145 | syscall.ForkLock.RUnlock() |
Ryan Brown | a08a221 | 2015-01-15 15:40:10 -0800 | [diff] [blame] | 146 | return nil, err |
| 147 | } |
| 148 | syscall.CloseOnExec(fd) |
Bogdan Caprita | bb37c54 | 2015-01-22 10:21:57 -0800 | [diff] [blame] | 149 | syscall.ForkLock.RUnlock() |
Matt Rosencrantz | 99cc06e | 2015-01-16 10:25:11 -0800 | [diff] [blame] | 150 | p, err := agent.NewAgentPrincipal(ctx, fd, veyron2.GetClient(ctx)) |
Ryan Brown | a08a221 | 2015-01-15 15:40:10 -0800 | [diff] [blame] | 151 | if err != nil { |
Bogdan Caprita | 6613fc4 | 2015-01-28 11:54:23 -0800 | [diff] [blame] | 152 | syscall.Close(fd) |
Ryan Brown | a08a221 | 2015-01-15 15:40:10 -0800 | [diff] [blame] | 153 | return nil, err |
Ankur | 9f95794 | 2014-11-24 16:34:18 -0800 | [diff] [blame] | 154 | } |
Ankur | 50ab988 | 2015-02-17 12:17:17 -0800 | [diff] [blame^] | 155 | |
| 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()) |
Ankur | 9f95794 | 2014-11-24 16:34:18 -0800 | [diff] [blame] | 160 | if err != nil { |
Ryan Brown | a08a221 | 2015-01-15 15:40:10 -0800 | [diff] [blame] | 161 | return nil, err |
Ankur | 9f95794 | 2014-11-24 16:34:18 -0800 | [diff] [blame] | 162 | } |
Cosmos Nicolaou | 344cc4a | 2014-11-26 15:38:43 -0800 | [diff] [blame] | 163 | if err := p.BlessingStore().SetDefault(blessingForChild); err != nil { |
Ryan Brown | a08a221 | 2015-01-15 15:40:10 -0800 | [diff] [blame] | 164 | return nil, err |
Ankur | 9f95794 | 2014-11-24 16:34:18 -0800 | [diff] [blame] | 165 | } |
Cosmos Nicolaou | 344cc4a | 2014-11-26 15:38:43 -0800 | [diff] [blame] | 166 | if _, err := p.BlessingStore().Set(blessingForChild, security.AllPrincipals); err != nil { |
Ryan Brown | a08a221 | 2015-01-15 15:40:10 -0800 | [diff] [blame] | 167 | return nil, err |
Ankur | 9f95794 | 2014-11-24 16:34:18 -0800 | [diff] [blame] | 168 | } |
Cosmos Nicolaou | 344cc4a | 2014-11-26 15:38:43 -0800 | [diff] [blame] | 169 | if err := p.AddToRoots(blessingForChild); err != nil { |
Ryan Brown | a08a221 | 2015-01-15 15:40:10 -0800 | [diff] [blame] | 170 | return nil, err |
Ankur | 9f95794 | 2014-11-24 16:34:18 -0800 | [diff] [blame] | 171 | } |
| 172 | |
Ryan Brown | a08a221 | 2015-01-15 15:40:10 -0800 | [diff] [blame] | 173 | return conn, nil |
Ankur | 9f95794 | 2014-11-24 16:34:18 -0800 | [diff] [blame] | 174 | } |
| 175 | |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 176 | type Main func(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error |
| 177 | |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 178 | // String returns a string representation of the Shell, which is a |
Cosmos Nicolaou | addf483 | 2014-09-10 21:36:54 -0700 | [diff] [blame] | 179 | // list of the commands currently available in the shell. |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 180 | func (sh *Shell) String() string { |
Cosmos Nicolaou | d4f0056 | 2014-11-17 20:35:48 -0800 | [diff] [blame] | 181 | return registry.help("") |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 182 | } |
| 183 | |
| 184 | // Help returns the help message for the specified command. |
| 185 | func (sh *Shell) Help(command string) string { |
Cosmos Nicolaou | d4f0056 | 2014-11-17 20:35:48 -0800 | [diff] [blame] | 186 | return registry.help(command) |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 187 | } |
| 188 | |
James Ring | 9d9489d | 2015-01-27 15:48:07 -0800 | [diff] [blame] | 189 | func (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 | |
Ankur | 9f95794 | 2014-11-24 16:34:18 -0800 | [diff] [blame] | 197 | // 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 Brown | 10a4ade | 2015-02-10 13:17:18 -0800 | [diff] [blame] | 204 | // and agent FdEnvVar variables will never use the value from the Shell or OS. |
Ankur | 9f95794 | 2014-11-24 16:34:18 -0800 | [diff] [blame] | 205 | // |
Ryan Brown | 10a4ade | 2015-02-10 13:17:18 -0800 | [diff] [blame] | 206 | // 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 Nicolaou | 612ad38 | 2014-10-29 19:41:35 -0700 | [diff] [blame] | 210 | // |
| 211 | // 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] | 212 | // 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 Nicolaou | 612ad38 | 2014-10-29 19:41:35 -0700 | [diff] [blame] | 215 | // |
Cosmos Nicolaou | d4f0056 | 2014-11-17 20:35:48 -0800 | [diff] [blame] | 216 | // Commands must have already been registered using RegisterFunction |
| 217 | // or RegisterChild. |
Cosmos Nicolaou | 612ad38 | 2014-10-29 19:41:35 -0700 | [diff] [blame] | 218 | func (sh *Shell) Start(name string, env []string, args ...string) (Handle, error) { |
James Ring | 9d9489d | 2015-01-27 15:48:07 -0800 | [diff] [blame] | 219 | 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 | |
| 238 | func (sh *Shell) startCommand(c command, env []string, args ...string) (Handle, error) { |
Ankur | 9f95794 | 2014-11-24 16:34:18 -0800 | [diff] [blame] | 239 | cenv, err := sh.setupCommandEnv(env) |
| 240 | if err != nil { |
| 241 | return nil, err |
| 242 | } |
Cosmos Nicolaou | 82d00d8 | 2015-02-10 21:31:00 -0800 | [diff] [blame] | 243 | p, err := sh.NewChildCredentials() |
Ryan Brown | a08a221 | 2015-01-15 15:40:10 -0800 | [diff] [blame] | 244 | if err != nil { |
| 245 | return nil, err |
| 246 | } |
James Ring | 9d9489d | 2015-01-27 15:48:07 -0800 | [diff] [blame] | 247 | |
| 248 | h, err := c.start(sh, p, cenv, args...) |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 249 | if err != nil { |
James Ring | 9d9489d | 2015-01-27 15:48:07 -0800 | [diff] [blame] | 250 | return nil, err |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 251 | } |
| 252 | sh.mu.Lock() |
| 253 | sh.handles[h] = struct{}{} |
| 254 | sh.mu.Unlock() |
| 255 | return h, nil |
| 256 | } |
| 257 | |
Cosmos Nicolaou | e5b4150 | 2014-10-29 22:55:09 -0700 | [diff] [blame] | 258 | // SetStartTimeout sets the timeout for starting subcommands. |
| 259 | func (sh *Shell) SetStartTimeout(d time.Duration) { |
| 260 | sh.startTimeout = d |
| 261 | } |
| 262 | |
Bogdan Caprita | 490a451 | 2014-11-20 21:12:19 -0800 | [diff] [blame] | 263 | // SetWaitTimeout sets the timeout for waiting on subcommands to complete. |
| 264 | func (sh *Shell) SetWaitTimeout(d time.Duration) { |
| 265 | sh.waitTimeout = d |
| 266 | } |
| 267 | |
Cosmos Nicolaou | e664f3f | 2014-10-20 17:40:05 -0700 | [diff] [blame] | 268 | // 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 Nicolaou | 2cb05fb | 2014-10-23 13:37:32 -0700 | [diff] [blame] | 271 | func (sh *Shell) CommandEnvelope(name string, env []string, args ...string) ([]string, []string) { |
Cosmos Nicolaou | d4f0056 | 2014-11-17 20:35:48 -0800 | [diff] [blame] | 272 | cmd := registry.getCommand(name) |
| 273 | if cmd == nil { |
| 274 | return []string{}, []string{} |
| 275 | } |
Ankur | 9f95794 | 2014-11-24 16:34:18 -0800 | [diff] [blame] | 276 | menv, err := sh.setupCommandEnv(env) |
| 277 | if err != nil { |
| 278 | return []string{}, []string{} |
| 279 | } |
| 280 | return cmd.factory().envelope(sh, menv, args...) |
Cosmos Nicolaou | e664f3f | 2014-10-20 17:40:05 -0700 | [diff] [blame] | 281 | } |
| 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. |
| 286 | func (sh *Shell) Forget(h Handle) { |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 287 | sh.mu.Lock() |
| 288 | delete(sh.handles, h) |
| 289 | sh.mu.Unlock() |
| 290 | } |
| 291 | |
| 292 | func (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. |
| 308 | func (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. |
| 316 | func (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 Nicolaou | 2cb05fb | 2014-10-23 13:37:32 -0700 | [diff] [blame] | 323 | // ClearVar removes the speficied variable from the Shell's environment |
| 324 | func (sh *Shell) ClearVar(key string) { |
| 325 | sh.mu.Lock() |
| 326 | defer sh.mu.Unlock() |
| 327 | delete(sh.env, key) |
| 328 | } |
| 329 | |
Jiri Simsa | 3789339 | 2014-11-07 10:55:45 -0800 | [diff] [blame] | 330 | // 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. |
| 333 | func (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. |
| 340 | func (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. |
| 345 | func (sh *Shell) ClearConfigKey(key string) { |
| 346 | sh.config.Clear(key) |
| 347 | } |
| 348 | |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 349 | // Env returns the entire set of environment variables associated with this |
| 350 | // Shell as a string slice. |
| 351 | func (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 Nicolaou | bbae388 | 2014-10-02 22:58:19 -0700 | [diff] [blame] | 362 | // 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] | 363 | // 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. |
| 366 | func (sh *Shell) Cleanup(stdout, stderr io.Writer) error { |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 367 | sh.mu.Lock() |
Cosmos Nicolaou | 66afced | 2014-09-15 22:12:43 -0700 | [diff] [blame] | 368 | handles := make(map[Handle]struct{}) |
| 369 | for k, v := range sh.handles { |
| 370 | handles[k] = v |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 371 | } |
| 372 | sh.handles = make(map[Handle]struct{}) |
Cosmos Nicolaou | 66afced | 2014-09-15 22:12:43 -0700 | [diff] [blame] | 373 | sh.mu.Unlock() |
Cosmos Nicolaou | 2cb05fb | 2014-10-23 13:37:32 -0700 | [diff] [blame] | 374 | var err error |
Cosmos Nicolaou | 66afced | 2014-09-15 22:12:43 -0700 | [diff] [blame] | 375 | for k, _ := range handles { |
Cosmos Nicolaou | 2cb05fb | 2014-10-23 13:37:32 -0700 | [diff] [blame] | 376 | cerr := k.Shutdown(stdout, stderr) |
| 377 | if cerr != nil { |
| 378 | err = cerr |
| 379 | } |
Cosmos Nicolaou | 66afced | 2014-09-15 22:12:43 -0700 | [diff] [blame] | 380 | } |
Ankur | 9f95794 | 2014-11-24 16:34:18 -0800 | [diff] [blame] | 381 | |
Ryan Brown | a08a221 | 2015-01-15 15:40:10 -0800 | [diff] [blame] | 382 | 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 Nicolaou | 9afe120 | 2014-09-19 13:45:57 -0700 | [diff] [blame] | 388 | } |
Ryan Brown | a08a221 | 2015-01-15 15:40:10 -0800 | [diff] [blame] | 389 | os.RemoveAll(sh.tempCredDir) |
Cosmos Nicolaou | 2cb05fb | 2014-10-23 13:37:32 -0700 | [diff] [blame] | 390 | return err |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 391 | } |
| 392 | |
Ankur | 9f95794 | 2014-11-24 16:34:18 -0800 | [diff] [blame] | 393 | func (sh *Shell) setupCommandEnv(env []string) ([]string, error) { |
Cosmos Nicolaou | 2cb05fb | 2014-10-23 13:37:32 -0700 | [diff] [blame] | 394 | osmap := envSliceToMap(os.Environ()) |
| 395 | evmap := envSliceToMap(env) |
Ankur | 9f95794 | 2014-11-24 16:34:18 -0800 | [diff] [blame] | 396 | |
Cosmos Nicolaou | 2cb05fb | 2014-10-23 13:37:32 -0700 | [diff] [blame] | 397 | sh.mu.Lock() |
Cosmos Nicolaou | 2cb05fb | 2014-10-23 13:37:32 -0700 | [diff] [blame] | 398 | defer sh.mu.Unlock() |
Ankur | 9f95794 | 2014-11-24 16:34:18 -0800 | [diff] [blame] | 399 | 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 Brown | a08a221 | 2015-01-15 15:40:10 -0800 | [diff] [blame] | 404 | delete(m1, agent.FdVarName) |
Ankur | 9f95794 | 2014-11-24 16:34:18 -0800 | [diff] [blame] | 405 | |
Cosmos Nicolaou | 2cb05fb | 2014-10-23 13:37:32 -0700 | [diff] [blame] | 406 | m2 := mergeMaps(m1, evmap) |
| 407 | r := []string{} |
| 408 | for k, v := range m2 { |
| 409 | r = append(r, k+"="+v) |
Cosmos Nicolaou | e664f3f | 2014-10-20 17:40:05 -0700 | [diff] [blame] | 410 | } |
Ankur | 9f95794 | 2014-11-24 16:34:18 -0800 | [diff] [blame] | 411 | return r, nil |
Cosmos Nicolaou | e664f3f | 2014-10-20 17:40:05 -0700 | [diff] [blame] | 412 | } |
| 413 | |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 414 | // Handle represents a running command. |
| 415 | type Handle interface { |
Cosmos Nicolaou | 9ca249d | 2014-09-18 15:07:12 -0700 | [diff] [blame] | 416 | // Stdout returns a reader to the running command's stdout stream. |
| 417 | Stdout() io.Reader |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 418 | |
Cosmos Nicolaou | 9ca249d | 2014-09-18 15:07:12 -0700 | [diff] [blame] | 419 | // Stderr returns a reader to the running command's stderr |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 420 | // 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 Nicolaou | 66afced | 2014-09-15 22:12:43 -0700 | [diff] [blame] | 427 | Stdin() io.Writer |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 428 | |
Cosmos Nicolaou | 66afced | 2014-09-15 22:12:43 -0700 | [diff] [blame] | 429 | // 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 Nicolaou | bbae388 | 2014-10-02 22:58:19 -0700 | [diff] [blame] | 434 | // 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 Nicolaou | cc58172 | 2014-10-07 12:45:39 -0700 | [diff] [blame] | 439 | |
| 440 | // Pid returns the pid of the process running the command |
| 441 | Pid() int |
James Ring | 9d9489d | 2015-01-27 15:48:07 -0800 | [diff] [blame] | 442 | |
| 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 Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 447 | } |
| 448 | |
| 449 | // command is used to abstract the implementations of inprocess and subprocess |
| 450 | // commands. |
| 451 | type command interface { |
Cosmos Nicolaou | 2cb05fb | 2014-10-23 13:37:32 -0700 | [diff] [blame] | 452 | envelope(sh *Shell, env []string, args ...string) ([]string, []string) |
Ryan Brown | a08a221 | 2015-01-15 15:40:10 -0800 | [diff] [blame] | 453 | start(sh *Shell, agent *os.File, env []string, args ...string) (Handle, error) |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 454 | } |