Jiri Simsa | d7616c9 | 2015-03-24 23:44:30 -0700 | [diff] [blame] | 1 | // Copyright 2015 The Vanadium Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style |
| 3 | // license that can be found in the LICENSE file. |
| 4 | |
Todd Wang | 8c4e5cc | 2015-04-09 11:30:52 -0700 | [diff] [blame] | 5 | // Package modules implements a mechanism for running commonly used services as |
| 6 | // subprocesses, and client functionality for accessing those services. Such |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 7 | // services and functions are collectively called 'programs' and are managed by |
Todd Wang | 8c4e5cc | 2015-04-09 11:30:52 -0700 | [diff] [blame] | 8 | // a 'Registry'. The Shell is analagous to the UNIX shell and maintains a key, |
| 9 | // value store of environment variables and config settings that are accessible |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 10 | // to the programs that it hosts. Simple variable expansion is supported. |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 11 | // |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 12 | // Three types of 'programs' may be invoked via a Shell: |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 13 | // |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 14 | // 1) Functions of type Main as subprocesses via fork/exec. |
| 15 | // 2) Arbitrary non-Vanadium programs available on the underlying operating |
Todd Wang | 5507c83 | 2015-05-15 22:59:23 -0700 | [diff] [blame] | 16 | // system such as '/bin/cp', 'bash' etc. |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 17 | // 3) Arbitrary Vanadium programs available on the underlying operating system |
Todd Wang | 5507c83 | 2015-05-15 22:59:23 -0700 | [diff] [blame] | 18 | // such as precompiled Vanadium services. |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 19 | // |
Todd Wang | 5507c83 | 2015-05-15 22:59:23 -0700 | [diff] [blame] | 20 | // The first type requires that the function to be executed is compiled into the |
| 21 | // binary executing the calls to the Shell. These functions are registered with |
| 22 | // a single, per-process, registry. |
Cosmos Nicolaou | 9afe120 | 2014-09-19 13:45:57 -0700 | [diff] [blame] | 23 | // |
Cosmos Nicolaou | 42a1736 | 2015-03-10 16:40:18 -0700 | [diff] [blame] | 24 | // The second two types allow for arbitrary binaries to be executed. The |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 25 | // distinction between a Vanadium and non-Vanadium program is that the Vanadium |
| 26 | // program implements the protocol used by v.io/x/ref/lib/exec package to |
Todd Wang | 5507c83 | 2015-05-15 22:59:23 -0700 | [diff] [blame] | 27 | // synchronise between the parent and child processes and to share information |
| 28 | // such as the ConfigKey key,value store supported by the Shell, a shared |
| 29 | // secret, shared file descriptors etc. |
Cosmos Nicolaou | 52e9a22 | 2015-03-16 21:57:16 -0700 | [diff] [blame] | 30 | // |
| 31 | // When the exec protocol is not used the only form of communication with the |
| 32 | // child processes are environment variables and command line flags and any |
| 33 | // shared file descriptors explicitly created by the parent process and expected |
Todd Wang | 5507c83 | 2015-05-15 22:59:23 -0700 | [diff] [blame] | 34 | // by the child process; the Start method will not create any additional file |
| 35 | // descriptors. |
Cosmos Nicolaou | 42a1736 | 2015-03-10 16:40:18 -0700 | [diff] [blame] | 36 | // |
| 37 | // The registry provides the following functions: |
Cosmos Nicolaou | 42a1736 | 2015-03-10 16:40:18 -0700 | [diff] [blame] | 38 | // |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 39 | // Register: registers a Main function to be executed in a subprocess, |
| 40 | // the returned Program is typically assigned to a global variable. |
| 41 | // Dispatch: must be called in the child process to lookup and invoke the |
| 42 | // requested function. Typically called from TestMain. |
Cosmos Nicolaou | 42a1736 | 2015-03-10 16:40:18 -0700 | [diff] [blame] | 43 | // |
Nicolas Lacasse | 0f07c60 | 2015-09-24 16:57:06 -0700 | [diff] [blame] | 44 | // The jiri tool can automate generation of TestMain. Adding the comment below |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 45 | // to a test file will generate the appropriate code. |
Todd Wang | 5507c83 | 2015-05-15 22:59:23 -0700 | [diff] [blame] | 46 | // |
Nicolas Lacasse | 0f07c60 | 2015-09-24 16:57:06 -0700 | [diff] [blame] | 47 | // //go:generate jiri test generate . |
Cosmos Nicolaou | 42a1736 | 2015-03-10 16:40:18 -0700 | [diff] [blame] | 48 | // |
Nicolas Lacasse | 0f07c60 | 2015-09-24 16:57:06 -0700 | [diff] [blame] | 49 | // Use 'jiri test generate --help' to get a complete explanation. |
Cosmos Nicolaou | 42a1736 | 2015-03-10 16:40:18 -0700 | [diff] [blame] | 50 | // |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 51 | // In all cases programs are started by invoking the StartWithOpts method on the |
| 52 | // Shell with the name of the program to run. An instance of the Handle |
Todd Wang | 5507c83 | 2015-05-15 22:59:23 -0700 | [diff] [blame] | 53 | // interface is returned which can be used to interact with the function or |
| 54 | // subprocess, and in particular to read/write data from/to it using io channels |
| 55 | // that follow the stdin, stdout, stderr convention. The StartOpts struct is |
| 56 | // used to control the detailed behaviour of each such invocation. Various |
| 57 | // helper functions are provided both for creating appropriate instances of |
| 58 | // StartOpts and for common uses of StartWithOpts. |
Cosmos Nicolaou | 42a1736 | 2015-03-10 16:40:18 -0700 | [diff] [blame] | 59 | // |
Todd Wang | 5507c83 | 2015-05-15 22:59:23 -0700 | [diff] [blame] | 60 | // Each successful call to StartWithOpts returns a handle representing the |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 61 | // running program. This handle can be used to gain access to that program's |
Todd Wang | 5507c83 | 2015-05-15 22:59:23 -0700 | [diff] [blame] | 62 | // stdin, stdout, stderr and to request or synchronize with its termination via |
| 63 | // the Shutdown method. The Shutdown method can optionally be used to read any |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 64 | // remaining output from the programs stdout and stderr. The Shell maintains a |
Todd Wang | 5507c83 | 2015-05-15 22:59:23 -0700 | [diff] [blame] | 65 | // record of all such handles and will call Shutdown on them in LIFO order when |
| 66 | // the Shell's Cleanup method is called. |
Cosmos Nicolaou | 42a1736 | 2015-03-10 16:40:18 -0700 | [diff] [blame] | 67 | // |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 68 | // A simple protocol must be followed by all programs, in particular, they |
| 69 | // should wait for their stdin stream to be closed before exiting. The caller |
| 70 | // can then coordinate with any program by writing to that stdin stream and |
| 71 | // reading responses from the stdout stream, and it can close stdin when it's |
| 72 | // ready for the program to exit using the CloseStdin method on the program's |
| 73 | // handle. Any binary or script that follows this protocol can be used as well. |
Cosmos Nicolaou | 42a1736 | 2015-03-10 16:40:18 -0700 | [diff] [blame] | 74 | // |
Todd Wang | 5507c83 | 2015-05-15 22:59:23 -0700 | [diff] [blame] | 75 | // By default, every Shell created by NewShell starts a security agent to manage |
| 76 | // principals for child processes. These default credentials can be overridden |
| 77 | // by passing a nil context to NewShell then specifying VeyronCredentials in the |
| 78 | // environment provided as a parameter to the StartWithOpts method. It is also |
| 79 | // possible to specify custom credentials via StartOpts. |
Cosmos Nicolaou | 42a1736 | 2015-03-10 16:40:18 -0700 | [diff] [blame] | 80 | // |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 81 | // Interacting with Programs |
Cosmos Nicolaou | 42a1736 | 2015-03-10 16:40:18 -0700 | [diff] [blame] | 82 | // |
| 83 | // Handle.Stdout(), Stdin(), Stderr(): |
Todd Wang | 5507c83 | 2015-05-15 22:59:23 -0700 | [diff] [blame] | 84 | // |
| 85 | // StartWithOpts returns a Handle which can be used to interact with the running |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 86 | // program. In particular, its Stdin() and Stdout() methods give access to the |
Todd Wang | 5507c83 | 2015-05-15 22:59:23 -0700 | [diff] [blame] | 87 | // running process' corresponding stdin and stdout and hence can be used to |
| 88 | // communicate with it. Stderr is handled differently and is configured so that |
| 89 | // the child's stderr is written to a log file rather than a pipe. This is in |
| 90 | // order to maximise the liklihood of capturing stderr output from a crashed |
| 91 | // child process. |
Cosmos Nicolaou | 42a1736 | 2015-03-10 16:40:18 -0700 | [diff] [blame] | 92 | // |
| 93 | // Handle.Shutdown(stdout, stderr io.Writer): |
Todd Wang | 5507c83 | 2015-05-15 22:59:23 -0700 | [diff] [blame] | 94 | // |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 95 | // The Shutdown method is used to gracefully shutdown a program and to |
Todd Wang | 5507c83 | 2015-05-15 22:59:23 -0700 | [diff] [blame] | 96 | // synchronise with its termination. In particular, Shutdown can be used to read |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 97 | // any unread output from the program's stdout and stderr. Note that since |
Todd Wang | 5507c83 | 2015-05-15 22:59:23 -0700 | [diff] [blame] | 98 | // Stderr is buffered to a file, Shutdown is able to return the entire contents |
| 99 | // of that file. This is useful for debugging misbehaving/crashing child |
| 100 | // processes. |
Cosmos Nicolaou | 42a1736 | 2015-03-10 16:40:18 -0700 | [diff] [blame] | 101 | // |
| 102 | // Shell.Cleanup(stdout, stderr io.Writer): |
Todd Wang | 5507c83 | 2015-05-15 22:59:23 -0700 | [diff] [blame] | 103 | // |
| 104 | // The Shell keeps track of all Handles that it has issued and in particular if |
| 105 | // Shutdown (or Forget) have not been called, it will call Shutdown for each |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 106 | // such Handle in LIFO order. This ensures that all programs will be Shutdown |
Todd Wang | 5507c83 | 2015-05-15 22:59:23 -0700 | [diff] [blame] | 107 | // even if the developer does not explicitly take care to do so for every |
| 108 | // invocation. |
Cosmos Nicolaou | 42a1736 | 2015-03-10 16:40:18 -0700 | [diff] [blame] | 109 | // |
| 110 | // Pipes: |
Todd Wang | 5507c83 | 2015-05-15 22:59:23 -0700 | [diff] [blame] | 111 | // |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 112 | // StartWithOpts allows the caller to pass an io.Reader to the program |
Cosmos Nicolaou | 42a1736 | 2015-03-10 16:40:18 -0700 | [diff] [blame] | 113 | // (StartOpts.Stdin) for it to read from, rather than creating a new pipe |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 114 | // internally. This makes it possible to connect the output of one program to |
Todd Wang | 5507c83 | 2015-05-15 22:59:23 -0700 | [diff] [blame] | 115 | // the input of another directly. |
Cosmos Nicolaou | 42a1736 | 2015-03-10 16:40:18 -0700 | [diff] [blame] | 116 | // |
| 117 | // Command Line Arguments: |
Todd Wang | 5507c83 | 2015-05-15 22:59:23 -0700 | [diff] [blame] | 118 | // |
Cosmos Nicolaou | 42a1736 | 2015-03-10 16:40:18 -0700 | [diff] [blame] | 119 | // The arguments passed in calls to Start are appended to any system required |
| 120 | // ones (e.g. for propagating test timeouts, verbosity etc) and the child |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 121 | // process will call the program with the result of flag.Args(). In this way the |
Todd Wang | 5507c83 | 2015-05-15 22:59:23 -0700 | [diff] [blame] | 122 | // caller can provide flags used by libraries in the child process as well as |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 123 | // those specific to the program and the program will only receive the args |
Todd Wang | 5507c83 | 2015-05-15 22:59:23 -0700 | [diff] [blame] | 124 | // specific to it. The usual "--" convention can be used to override this |
| 125 | // default behaviour. |
Cosmos Nicolaou | 42a1736 | 2015-03-10 16:40:18 -0700 | [diff] [blame] | 126 | // |
| 127 | // Caveats: |
| 128 | // |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 129 | // Handle.Shutdown assumes that the child program/process will terminate when |
Todd Wang | 5507c83 | 2015-05-15 22:59:23 -0700 | [diff] [blame] | 130 | // its stdin stream is closed. This assumption is unlikely to be valid for |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 131 | // 'external' programs (e.g. /bin/cp) and in these cases Kill or some other |
Cosmos Nicolaou | 42a1736 | 2015-03-10 16:40:18 -0700 | [diff] [blame] | 132 | // application specific mechanism will need to be used. |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 133 | package modules |
| 134 | |
| 135 | import ( |
Cosmos Nicolaou | 52e9a22 | 2015-03-16 21:57:16 -0700 | [diff] [blame] | 136 | "errors" |
Cosmos Nicolaou | d4f0056 | 2014-11-17 20:35:48 -0800 | [diff] [blame] | 137 | "fmt" |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 138 | "io" |
Cosmos Nicolaou | 9afe120 | 2014-09-19 13:45:57 -0700 | [diff] [blame] | 139 | "io/ioutil" |
| 140 | "os" |
Ryan Brown | 0438443 | 2015-08-27 16:08:32 -0700 | [diff] [blame] | 141 | "path/filepath" |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 142 | "sync" |
Ryan Brown | a08a221 | 2015-01-15 15:40:10 -0800 | [diff] [blame] | 143 | "syscall" |
Cosmos Nicolaou | e5b4150 | 2014-10-29 22:55:09 -0700 | [diff] [blame] | 144 | "time" |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 145 | |
Cosmos Nicolaou | e3b1932 | 2015-06-18 16:05:08 -0700 | [diff] [blame] | 146 | "v.io/x/lib/envvar" |
| 147 | |
Jiri Simsa | 6ac9522 | 2015-02-23 16:11:49 -0800 | [diff] [blame] | 148 | "v.io/v23" |
| 149 | "v.io/v23/context" |
Cosmos Nicolaou | e3b1932 | 2015-06-18 16:05:08 -0700 | [diff] [blame] | 150 | "v.io/v23/logging" |
Cosmos Nicolaou | 42a1736 | 2015-03-10 16:40:18 -0700 | [diff] [blame] | 151 | "v.io/v23/security" |
Cosmos Nicolaou | e3b1932 | 2015-06-18 16:05:08 -0700 | [diff] [blame] | 152 | |
Todd Wang | 8123b5e | 2015-05-14 18:44:43 -0700 | [diff] [blame] | 153 | "v.io/x/ref" |
Cosmos Nicolaou | e3b1932 | 2015-06-18 16:05:08 -0700 | [diff] [blame] | 154 | "v.io/x/ref/internal/logger" |
Jiri Simsa | ffceefa | 2015-02-28 11:03:34 -0800 | [diff] [blame] | 155 | "v.io/x/ref/lib/exec" |
Ryan Brown | 0438443 | 2015-08-27 16:08:32 -0700 | [diff] [blame] | 156 | "v.io/x/ref/services/agent" |
Todd Wang | 8850968 | 2015-04-10 10:28:24 -0700 | [diff] [blame] | 157 | "v.io/x/ref/services/agent/agentlib" |
Todd Wang | b351149 | 2015-04-07 23:32:34 -0700 | [diff] [blame] | 158 | "v.io/x/ref/services/agent/keymgr" |
Cosmos Nicolaou | 1381f8a | 2015-03-13 09:40:34 -0700 | [diff] [blame] | 159 | "v.io/x/ref/test/expect" |
Ankur | 9f95794 | 2014-11-24 16:34:18 -0800 | [diff] [blame] | 160 | ) |
| 161 | |
| 162 | const ( |
| 163 | shellBlessingExtension = "test-shell" |
Cosmos Nicolaou | 42a1736 | 2015-03-10 16:40:18 -0700 | [diff] [blame] | 164 | |
| 165 | defaultStartTimeout = time.Minute |
| 166 | defaultShutdownTimeout = time.Minute |
| 167 | defaultExpectTimeout = time.Minute |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 168 | ) |
| 169 | |
Cosmos Nicolaou | 42a1736 | 2015-03-10 16:40:18 -0700 | [diff] [blame] | 170 | var defaultStartOpts = StartOpts{ |
| 171 | StartTimeout: defaultStartTimeout, |
| 172 | ShutdownTimeout: defaultShutdownTimeout, |
| 173 | ExpectTimeout: defaultExpectTimeout, |
| 174 | ExecProtocol: true, |
| 175 | } |
| 176 | |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 177 | // Shell represents the context within which programs are run. |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 178 | type Shell struct { |
Cosmos Nicolaou | 42a1736 | 2015-03-10 16:40:18 -0700 | [diff] [blame] | 179 | mu sync.Mutex |
| 180 | env map[string]string |
Todd Wang | 5507c83 | 2015-05-15 22:59:23 -0700 | [diff] [blame] | 181 | handles map[*execHandle]struct{} |
| 182 | lifoHandles []*execHandle |
Cosmos Nicolaou | 42a1736 | 2015-03-10 16:40:18 -0700 | [diff] [blame] | 183 | defaultStartOpts StartOpts |
Ryan Brown | a08a221 | 2015-01-15 15:40:10 -0800 | [diff] [blame] | 184 | // tmpCredDir is the temporary directory created by this |
| 185 | // shell. This must be removed when the shell is cleaned up. |
Cosmos Nicolaou | 42a1736 | 2015-03-10 16:40:18 -0700 | [diff] [blame] | 186 | tempCredDir string |
| 187 | config exec.Config |
| 188 | principal security.Principal |
Ryan Brown | 0438443 | 2015-08-27 16:08:32 -0700 | [diff] [blame] | 189 | agent agent.KeyManager |
Cosmos Nicolaou | 42a1736 | 2015-03-10 16:40:18 -0700 | [diff] [blame] | 190 | ctx *context.T |
Cosmos Nicolaou | e3b1932 | 2015-06-18 16:05:08 -0700 | [diff] [blame] | 191 | logger logging.Logger |
Cosmos Nicolaou | 42a1736 | 2015-03-10 16:40:18 -0700 | [diff] [blame] | 192 | sessionVerbosity bool |
| 193 | cancelCtx func() |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 194 | } |
| 195 | |
Ryan Brown | a08a221 | 2015-01-15 15:40:10 -0800 | [diff] [blame] | 196 | // NewShell creates a new instance of Shell. |
Ankur | 50ab988 | 2015-02-17 12:17:17 -0800 | [diff] [blame] | 197 | // |
Ryan Brown | a08a221 | 2015-01-15 15:40:10 -0800 | [diff] [blame] | 198 | // If ctx is non-nil, the shell will manage Principals for child processes. |
Ankur | 50ab988 | 2015-02-17 12:17:17 -0800 | [diff] [blame] | 199 | // |
| 200 | // If p is non-nil, any child process created has its principal blessed |
| 201 | // by the default blessings of 'p', Else any child process created has its |
| 202 | // principal blessed by the default blessings of ctx's principal. |
Cosmos Nicolaou | 9e90984 | 2015-03-17 11:58:59 -0700 | [diff] [blame] | 203 | // |
| 204 | // If verbosity is true additional debugging info will be displayed, |
| 205 | // in particular by the Shutdown. |
| 206 | // |
| 207 | // If t is non-nil, then the expect Session created for every invocation |
| 208 | // will be constructed with that value of t unless overridden by a |
| 209 | // StartOpts provided to that invocation. Providing a non-nil value of |
| 210 | // t enables expect.Session to call t.Error, Errorf and Log. |
| 211 | func NewShell(ctx *context.T, p security.Principal, verbosity bool, t expect.Testing) (*Shell, error) { |
Cosmos Nicolaou | 9afe120 | 2014-09-19 13:45:57 -0700 | [diff] [blame] | 212 | sh := &Shell{ |
Cosmos Nicolaou | 42a1736 | 2015-03-10 16:40:18 -0700 | [diff] [blame] | 213 | env: make(map[string]string), |
Todd Wang | 5507c83 | 2015-05-15 22:59:23 -0700 | [diff] [blame] | 214 | handles: make(map[*execHandle]struct{}), |
Cosmos Nicolaou | 42a1736 | 2015-03-10 16:40:18 -0700 | [diff] [blame] | 215 | config: exec.NewConfig(), |
| 216 | defaultStartOpts: defaultStartOpts, |
Cosmos Nicolaou | 9e90984 | 2015-03-17 11:58:59 -0700 | [diff] [blame] | 217 | sessionVerbosity: verbosity, |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 218 | } |
Cosmos Nicolaou | 9e90984 | 2015-03-17 11:58:59 -0700 | [diff] [blame] | 219 | sh.defaultStartOpts = sh.defaultStartOpts.WithSessions(t, time.Minute) |
Ryan Brown | a08a221 | 2015-01-15 15:40:10 -0800 | [diff] [blame] | 220 | if ctx == nil { |
Cosmos Nicolaou | e3b1932 | 2015-06-18 16:05:08 -0700 | [diff] [blame] | 221 | sh.logger = logger.Global() |
Cosmos Nicolaou | 344cc4a | 2014-11-26 15:38:43 -0800 | [diff] [blame] | 222 | return sh, nil |
Cosmos Nicolaou | 9afe120 | 2014-09-19 13:45:57 -0700 | [diff] [blame] | 223 | } |
Ryan Brown | a08a221 | 2015-01-15 15:40:10 -0800 | [diff] [blame] | 224 | var err error |
| 225 | ctx, sh.cancelCtx = context.WithCancel(ctx) |
Todd Wang | ad49204 | 2015-04-17 15:58:40 -0700 | [diff] [blame] | 226 | if ctx, err = v23.WithNewStreamManager(ctx); err != nil { |
Ryan Brown | a08a221 | 2015-01-15 15:40:10 -0800 | [diff] [blame] | 227 | return nil, err |
| 228 | } |
| 229 | sh.ctx = ctx |
Cosmos Nicolaou | e3b1932 | 2015-06-18 16:05:08 -0700 | [diff] [blame] | 230 | sh.logger = ctx |
Ryan Brown | a08a221 | 2015-01-15 15:40:10 -0800 | [diff] [blame] | 231 | |
Bogdan Caprita | 44c7f98 | 2015-09-14 14:39:05 -0700 | [diff] [blame] | 232 | if sh.tempCredDir, err = ioutil.TempDir("", "sh_creds"); err != nil { |
Ryan Brown | a08a221 | 2015-01-15 15:40:10 -0800 | [diff] [blame] | 233 | return nil, err |
| 234 | } |
Ryan Brown | 0438443 | 2015-08-27 16:08:32 -0700 | [diff] [blame] | 235 | if sh.agent, err = keymgr.NewLocalAgent(sh.tempCredDir, nil); err != nil { |
Ryan Brown | a08a221 | 2015-01-15 15:40:10 -0800 | [diff] [blame] | 236 | return nil, err |
| 237 | } |
Ryan Brown | a08a221 | 2015-01-15 15:40:10 -0800 | [diff] [blame] | 238 | sh.principal = p |
Ankur | 50ab988 | 2015-02-17 12:17:17 -0800 | [diff] [blame] | 239 | if sh.principal == nil { |
Jiri Simsa | 6ac9522 | 2015-02-23 16:11:49 -0800 | [diff] [blame] | 240 | sh.principal = v23.GetPrincipal(ctx) |
Cosmos Nicolaou | 344cc4a | 2014-11-26 15:38:43 -0800 | [diff] [blame] | 241 | } |
Cosmos Nicolaou | 344cc4a | 2014-11-26 15:38:43 -0800 | [diff] [blame] | 242 | return sh, nil |
Cosmos Nicolaou | 9afe120 | 2014-09-19 13:45:57 -0700 | [diff] [blame] | 243 | } |
| 244 | |
Cosmos Nicolaou | 42a1736 | 2015-03-10 16:40:18 -0700 | [diff] [blame] | 245 | // DefaultStartOpts returns the current StartOpts stored with the Shell. |
| 246 | func (sh *Shell) DefaultStartOpts() StartOpts { |
| 247 | return sh.defaultStartOpts |
| 248 | } |
| 249 | |
| 250 | // SetDefaultStartOpts sets the default StartOpts stored with the Shell. |
| 251 | func (sh *Shell) SetDefaultStartOpts(opts StartOpts) { |
| 252 | sh.defaultStartOpts = opts |
| 253 | } |
| 254 | |
Ryan Brown | 61d6938 | 2015-02-25 11:13:39 -0800 | [diff] [blame] | 255 | // CustomCredentials encapsulates a Principal which can be shared with |
| 256 | // one or more processes run by a Shell. |
| 257 | type CustomCredentials struct { |
Ryan Brown | 0438443 | 2015-08-27 16:08:32 -0700 | [diff] [blame] | 258 | p security.Principal |
| 259 | path string |
Ryan Brown | 61d6938 | 2015-02-25 11:13:39 -0800 | [diff] [blame] | 260 | } |
Ankur | 50ab988 | 2015-02-17 12:17:17 -0800 | [diff] [blame] | 261 | |
Ryan Brown | 61d6938 | 2015-02-25 11:13:39 -0800 | [diff] [blame] | 262 | // Principal returns the Principal. |
| 263 | func (c *CustomCredentials) Principal() security.Principal { |
| 264 | return c.p |
| 265 | } |
| 266 | |
Ryan Brown | 0438443 | 2015-08-27 16:08:32 -0700 | [diff] [blame] | 267 | // Path returns the path to the credential's agent. |
| 268 | // Typically you would pass this to a child process in EnvAgentPath. |
| 269 | func (c *CustomCredentials) Path() string { |
| 270 | return c.path |
Ryan Brown | 61d6938 | 2015-02-25 11:13:39 -0800 | [diff] [blame] | 271 | } |
| 272 | |
| 273 | func dup(conn *os.File) (int, error) { |
Bogdan Caprita | bb37c54 | 2015-01-22 10:21:57 -0800 | [diff] [blame] | 274 | syscall.ForkLock.RLock() |
Ryan Brown | a08a221 | 2015-01-15 15:40:10 -0800 | [diff] [blame] | 275 | fd, err := syscall.Dup(int(conn.Fd())) |
Ankur | 9f95794 | 2014-11-24 16:34:18 -0800 | [diff] [blame] | 276 | if err != nil { |
Bogdan Caprita | bb37c54 | 2015-01-22 10:21:57 -0800 | [diff] [blame] | 277 | syscall.ForkLock.RUnlock() |
Ryan Brown | 61d6938 | 2015-02-25 11:13:39 -0800 | [diff] [blame] | 278 | return -1, err |
Ryan Brown | a08a221 | 2015-01-15 15:40:10 -0800 | [diff] [blame] | 279 | } |
| 280 | syscall.CloseOnExec(fd) |
Bogdan Caprita | bb37c54 | 2015-01-22 10:21:57 -0800 | [diff] [blame] | 281 | syscall.ForkLock.RUnlock() |
Ryan Brown | 61d6938 | 2015-02-25 11:13:39 -0800 | [diff] [blame] | 282 | return fd, nil |
| 283 | } |
| 284 | |
Bogdan Caprita | 44c7f98 | 2015-09-14 14:39:05 -0700 | [diff] [blame] | 285 | // NewCustomCredentials creates a new Principal for StartWithOpts. |
Ryan Brown | 61d6938 | 2015-02-25 11:13:39 -0800 | [diff] [blame] | 286 | // Returns nil if the shell is not managing principals. |
| 287 | func (sh *Shell) NewCustomCredentials() (cred *CustomCredentials, err error) { |
| 288 | // Create child principal. |
| 289 | if sh.ctx == nil { |
| 290 | return nil, nil |
| 291 | } |
Ryan Brown | 0438443 | 2015-08-27 16:08:32 -0700 | [diff] [blame] | 292 | id, err := sh.agent.NewPrincipal(true) |
Ryan Brown | 61d6938 | 2015-02-25 11:13:39 -0800 | [diff] [blame] | 293 | if err != nil { |
| 294 | return nil, err |
| 295 | } |
Bogdan Caprita | 44c7f98 | 2015-09-14 14:39:05 -0700 | [diff] [blame] | 296 | dir, err := ioutil.TempDir(sh.tempCredDir, "a") |
Ryan Brown | 61d6938 | 2015-02-25 11:13:39 -0800 | [diff] [blame] | 297 | if err != nil { |
| 298 | return nil, err |
| 299 | } |
Ryan Brown | 0438443 | 2015-08-27 16:08:32 -0700 | [diff] [blame] | 300 | path := filepath.Join(dir, "sock") |
| 301 | if err := sh.agent.ServePrincipal(id, path); err != nil { |
Ryan Brown | a08a221 | 2015-01-15 15:40:10 -0800 | [diff] [blame] | 302 | return nil, err |
Ankur | 9f95794 | 2014-11-24 16:34:18 -0800 | [diff] [blame] | 303 | } |
Ryan Brown | 0438443 | 2015-08-27 16:08:32 -0700 | [diff] [blame] | 304 | p, err := agentlib.NewAgentPrincipalX(path) |
Ryan Brown | 8b59ca3 | 2015-08-24 21:50:09 +0000 | [diff] [blame] | 305 | if err != nil { |
Ryan Brown | 8b59ca3 | 2015-08-24 21:50:09 +0000 | [diff] [blame] | 306 | return nil, err |
| 307 | } |
Ryan Brown | 0438443 | 2015-08-27 16:08:32 -0700 | [diff] [blame] | 308 | return &CustomCredentials{p, path}, nil |
Ryan Brown | 61d6938 | 2015-02-25 11:13:39 -0800 | [diff] [blame] | 309 | } |
| 310 | |
Asim Shankar | 34fb249 | 2015-03-12 10:25:46 -0700 | [diff] [blame] | 311 | // NewChildCredentials creates a new principal, served via the security agent |
| 312 | // whose blessings are an extension of this shell's principal (with the |
| 313 | // provided caveats). |
| 314 | // |
| 315 | // All processes started by this shell will recognize the credentials created |
| 316 | // by this call. |
| 317 | // |
| 318 | // Returns nil if the shell is not managing principals. |
| 319 | // |
| 320 | // Since the Shell type is intended for tests, it is not required to provide |
| 321 | // caveats. In production scenarios though, one must think long and hard |
| 322 | // before blessing anothing principal without any caveats. |
Arup Mukherjee | d804828 | 2015-06-26 23:18:44 -0700 | [diff] [blame] | 323 | func (sh *Shell) NewChildCredentials(extension string, caveats ...security.Caveat) (*CustomCredentials, error) { |
Ryan Brown | 61d6938 | 2015-02-25 11:13:39 -0800 | [diff] [blame] | 324 | creds, err := sh.NewCustomCredentials() |
| 325 | if creds == nil { |
| 326 | return nil, err |
| 327 | } |
Arup Mukherjee | d804828 | 2015-06-26 23:18:44 -0700 | [diff] [blame] | 328 | return sh.AddToChildCredentials(creds, extension, caveats...) |
| 329 | } |
| 330 | |
| 331 | func (sh *Shell) AddToChildCredentials(creds *CustomCredentials, extension string, caveats ...security.Caveat) (*CustomCredentials, error) { |
Asim Shankar | 34fb249 | 2015-03-12 10:25:46 -0700 | [diff] [blame] | 332 | parent := sh.principal |
| 333 | child := creds.p |
| 334 | if len(caveats) == 0 { |
| 335 | caveats = []security.Caveat{security.UnconstrainedUse()} |
| 336 | } |
Ankur | 50ab988 | 2015-02-17 12:17:17 -0800 | [diff] [blame] | 337 | |
| 338 | // Bless the child principal with blessings derived from the default blessings |
| 339 | // of shell's principal. |
Asim Shankar | 34fb249 | 2015-03-12 10:25:46 -0700 | [diff] [blame] | 340 | blessings, err := parent.Bless(child.PublicKey(), parent.BlessingStore().Default(), extension, caveats[0], caveats[1:]...) |
Ankur | 9f95794 | 2014-11-24 16:34:18 -0800 | [diff] [blame] | 341 | if err != nil { |
Ryan Brown | a08a221 | 2015-01-15 15:40:10 -0800 | [diff] [blame] | 342 | return nil, err |
Ankur | 9f95794 | 2014-11-24 16:34:18 -0800 | [diff] [blame] | 343 | } |
Arup Mukherjee | d804828 | 2015-06-26 23:18:44 -0700 | [diff] [blame] | 344 | |
| 345 | union, err := security.UnionOfBlessings(child.BlessingStore().Default(), blessings) |
| 346 | if err != nil { |
Ryan Brown | a08a221 | 2015-01-15 15:40:10 -0800 | [diff] [blame] | 347 | return nil, err |
Ankur | 9f95794 | 2014-11-24 16:34:18 -0800 | [diff] [blame] | 348 | } |
Arup Mukherjee | d804828 | 2015-06-26 23:18:44 -0700 | [diff] [blame] | 349 | |
| 350 | if err := child.BlessingStore().SetDefault(union); err != nil { |
| 351 | return nil, err |
| 352 | } |
| 353 | if _, err := child.BlessingStore().Set(union, security.AllPrincipals); err != nil { |
Ryan Brown | a08a221 | 2015-01-15 15:40:10 -0800 | [diff] [blame] | 354 | return nil, err |
Ankur | 9f95794 | 2014-11-24 16:34:18 -0800 | [diff] [blame] | 355 | } |
Asim Shankar | 34fb249 | 2015-03-12 10:25:46 -0700 | [diff] [blame] | 356 | if err := child.AddToRoots(blessings); err != nil { |
Ryan Brown | a08a221 | 2015-01-15 15:40:10 -0800 | [diff] [blame] | 357 | return nil, err |
Ankur | 9f95794 | 2014-11-24 16:34:18 -0800 | [diff] [blame] | 358 | } |
| 359 | |
Ryan Brown | 61d6938 | 2015-02-25 11:13:39 -0800 | [diff] [blame] | 360 | return creds, nil |
Ankur | 9f95794 | 2014-11-24 16:34:18 -0800 | [diff] [blame] | 361 | } |
| 362 | |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 363 | // Env represents the environment for Main functions. |
| 364 | type Env struct { |
| 365 | Stdin io.Reader |
| 366 | Stdout io.Writer |
| 367 | Stderr io.Writer |
| 368 | Vars map[string]string // Environment variables |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 369 | } |
| 370 | |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 371 | // EnvFromOS returns a new Env based on the underlying OS. |
| 372 | func EnvFromOS() *Env { |
| 373 | return &Env{ |
| 374 | Stdin: os.Stdin, |
| 375 | Stdout: os.Stdout, |
| 376 | Stderr: os.Stderr, |
| 377 | Vars: envvar.SliceToMap(os.Environ()), |
| 378 | } |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 379 | } |
| 380 | |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 381 | // Main describes the entry-point function type for registered programs. |
| 382 | type Main func(env *Env, args ...string) error |
| 383 | |
Cosmos Nicolaou | 42a1736 | 2015-03-10 16:40:18 -0700 | [diff] [blame] | 384 | // Start is shorthand for StartWithOpts(sh.DefaultStartOpts(), ...) |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 385 | func (sh *Shell) Start(env []string, prog Program, args ...string) (Handle, error) { |
| 386 | return sh.StartWithOpts(sh.DefaultStartOpts(), env, prog, args...) |
Cosmos Nicolaou | 42a1736 | 2015-03-10 16:40:18 -0700 | [diff] [blame] | 387 | } |
| 388 | |
| 389 | // StartOpts represents the options that can be passed to the |
| 390 | // StartWithOpts method. |
| 391 | type StartOpts struct { |
| 392 | // Error is set when creating/intializing instances of StartOpts |
| 393 | // via one of the factory methods and returned when StartWithOpts |
| 394 | // is called. This allows usage of of the form: |
| 395 | // |
| 396 | // err := sh.StartWithOpts(sh.DefaultStartOpts()...) |
| 397 | // |
| 398 | // as opposed to: |
| 399 | // |
| 400 | // opts, err := sh.DefaultStartOpts(....) |
| 401 | // if err != nil { |
| 402 | // panic(...) |
| 403 | // } |
| 404 | // sh.StartWithOpts(opts, ....) |
| 405 | Error error |
| 406 | |
| 407 | // Stdin, if non-nil, will be used as the stdin for the child process. |
| 408 | // If this option is set, then the Stdin() method on the returned Handle |
| 409 | // will return nil. The client of this API maintains ownership of stdin |
| 410 | // and must close it, i.e. the shell will not do so. |
| 411 | Stdin io.Reader |
| 412 | // Credentials, if non-nil, will be used as the credentials for the |
| 413 | // child process. If the creds are nil or the shell is not managing |
| 414 | // principals, the credentials are ignored. |
| 415 | Credentials *CustomCredentials |
| 416 | // ExecProtocol indicates whether the child process is expected to |
| 417 | // implement the v.io/x.ref/lib/exec parent/child protocol. |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 418 | // It should be set to false when running non-vanadium programs |
Cosmos Nicolaou | 42a1736 | 2015-03-10 16:40:18 -0700 | [diff] [blame] | 419 | // (e.g. /bin/cp). |
| 420 | ExecProtocol bool |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 421 | // External indicates if the program is an external process rather than |
| 422 | // a Main function. |
Cosmos Nicolaou | 42a1736 | 2015-03-10 16:40:18 -0700 | [diff] [blame] | 423 | External bool |
| 424 | // StartTimeout specifies the amount of time to wait for the |
| 425 | // child process to signal its correct intialization for Vanadium |
| 426 | // processes that implement the exec parent/child protocol. It has no |
| 427 | // effect if External is set to true. |
| 428 | StartTimeout time.Duration |
| 429 | // ShutdownTimeout specifics the amount of time to wait for the child |
| 430 | // process to exit when the Shutdown method is called on that |
| 431 | // child's handle. |
| 432 | ShutdownTimeout time.Duration |
| 433 | // ExpectTesting is used when creating an instance of expect.Session |
| 434 | // to embed in Handle. |
| 435 | ExpectTesting expect.Testing |
| 436 | // ExpectTimeout is the timeout to use with expect.Session. |
| 437 | ExpectTimeout time.Duration |
| 438 | } |
| 439 | |
| 440 | // DefaultStartOpts returns an instance of Startops with the current default |
| 441 | // values. The defaults have values for timeouts, no credentials |
| 442 | // (StartWithOpts will then create credentials each time it is called), |
| 443 | // and with ExecProtocol set to true. |
| 444 | // This is expected to be the common use case. |
| 445 | func DefaultStartOpts() StartOpts { |
| 446 | return defaultStartOpts |
| 447 | } |
| 448 | |
| 449 | // WithCustomCredentials returns an instance of StartOpts with the specified |
Asim Shankar | 34fb249 | 2015-03-12 10:25:46 -0700 | [diff] [blame] | 450 | // credentials. |
| 451 | // |
| 452 | // All other options are set to the current defaults. |
Cosmos Nicolaou | 42a1736 | 2015-03-10 16:40:18 -0700 | [diff] [blame] | 453 | func (opts StartOpts) WithCustomCredentials(creds *CustomCredentials) StartOpts { |
| 454 | opts.Credentials = creds |
| 455 | return opts |
| 456 | } |
| 457 | |
| 458 | // WithSessions returns a copy of opts with the specified expect.Testing and |
| 459 | // associated timeout. |
| 460 | func (opts StartOpts) WithSessions(t expect.Testing, timeout time.Duration) StartOpts { |
| 461 | opts.ExpectTesting = t |
| 462 | opts.ExpectTimeout = timeout |
| 463 | return opts |
| 464 | } |
| 465 | |
| 466 | // WithStdin returns a copy of opts with the specified Stdin io.Reader. |
| 467 | func (opts StartOpts) WithStdin(stdin io.Reader) StartOpts { |
| 468 | opts.Stdin = stdin |
| 469 | return opts |
| 470 | } |
| 471 | |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 472 | // NoExecProgram returns a copy of opts with the External option |
Cosmos Nicolaou | 42a1736 | 2015-03-10 16:40:18 -0700 | [diff] [blame] | 473 | // enabled and ExecProtocol disabled. |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 474 | func (opts StartOpts) NoExecProgram() StartOpts { |
Cosmos Nicolaou | 42a1736 | 2015-03-10 16:40:18 -0700 | [diff] [blame] | 475 | opts.External = true |
| 476 | opts.ExecProtocol = false |
| 477 | return opts |
| 478 | } |
| 479 | |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 480 | // ExternalProgram returns a copy of StartOpts with the |
Cosmos Nicolaou | 42a1736 | 2015-03-10 16:40:18 -0700 | [diff] [blame] | 481 | // External option enabled. |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 482 | func (opts StartOpts) ExternalProgram() StartOpts { |
Cosmos Nicolaou | 42a1736 | 2015-03-10 16:40:18 -0700 | [diff] [blame] | 483 | opts.External = true |
| 484 | return opts |
| 485 | } |
| 486 | |
Cosmos Nicolaou | 52e9a22 | 2015-03-16 21:57:16 -0700 | [diff] [blame] | 487 | var ( |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 488 | ErrNotRegistered = errors.New("program not registered") |
Cosmos Nicolaou | 52e9a22 | 2015-03-16 21:57:16 -0700 | [diff] [blame] | 489 | ErrNoExecAndCustomCreds = errors.New("ExecProtocol set to false but this invocation is attempting to use custome credentials") |
| 490 | ) |
| 491 | |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 492 | // StartWithOpts starts the specified program according to the supplied |
Cosmos Nicolaou | 42a1736 | 2015-03-10 16:40:18 -0700 | [diff] [blame] | 493 | // StartOpts and returns a Handle which can be used for interacting with |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 494 | // that program. |
Ankur | 9f95794 | 2014-11-24 16:34:18 -0800 | [diff] [blame] | 495 | // |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 496 | // The environment variables for the program are set by merging variables |
Ankur | 9f95794 | 2014-11-24 16:34:18 -0800 | [diff] [blame] | 497 | // from the OS environment, those in this Shell and those provided as a |
| 498 | // parameter to it. In general, it prefers values from its parameter over |
| 499 | // those from the Shell, over those from the OS. However, the VeyronCredentials |
Ryan Brown | 10a4ade | 2015-02-10 13:17:18 -0800 | [diff] [blame] | 500 | // 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] | 501 | // |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 502 | // If the shell is managing principals, the program is configured to |
Cosmos Nicolaou | 42a1736 | 2015-03-10 16:40:18 -0700 | [diff] [blame] | 503 | // connect to the shell's agent. Custom credentials may be specified |
| 504 | // via StartOpts. If the shell is not managing principals, set |
Ryan Brown | 10a4ade | 2015-02-10 13:17:18 -0800 | [diff] [blame] | 505 | // the VeyronCredentials environment variable in the 'env' parameter. |
Cosmos Nicolaou | 612ad38 | 2014-10-29 19:41:35 -0700 | [diff] [blame] | 506 | // |
| 507 | // 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] | 508 | // them down when asked to. The returned Handle may be non-nil even when an |
| 509 | // error is returned, in which case it may be used to retrieve any output |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 510 | // from the failed program. |
Cosmos Nicolaou | 612ad38 | 2014-10-29 19:41:35 -0700 | [diff] [blame] | 511 | // |
Cosmos Nicolaou | 42a1736 | 2015-03-10 16:40:18 -0700 | [diff] [blame] | 512 | // StartWithOpts will return a valid handle for errors that occur during the |
| 513 | // child processes startup process. It is thus possible to call Shutdown |
| 514 | // to obtain the error output. Handle will be nil if the error is due to |
| 515 | // some other reason, such as failure to create pipes/files before starting |
| 516 | // the child process. A common use will therefore be: |
Ryan Brown | 61d6938 | 2015-02-25 11:13:39 -0800 | [diff] [blame] | 517 | // |
Cosmos Nicolaou | 42a1736 | 2015-03-10 16:40:18 -0700 | [diff] [blame] | 518 | // h, err := sh.Start(env, "/bin/echo", "hello") |
| 519 | // if err != nil { |
| 520 | // if h != nil { |
| 521 | // h.Shutdown(nil,os.Stderr) |
| 522 | // } |
| 523 | // t.Fatal(err) |
| 524 | // } |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 525 | func (sh *Shell) StartWithOpts(opts StartOpts, env []string, prog Program, args ...string) (Handle, error) { |
Todd Wang | 3bb46f5 | 2015-05-14 22:01:18 -0700 | [diff] [blame] | 526 | var err error |
Cosmos Nicolaou | 42a1736 | 2015-03-10 16:40:18 -0700 | [diff] [blame] | 527 | if opts.Error != nil { |
| 528 | return nil, opts.Error |
| 529 | } |
Cosmos Nicolaou | 52e9a22 | 2015-03-16 21:57:16 -0700 | [diff] [blame] | 530 | |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 531 | var info *programInfo |
Cosmos Nicolaou | 42a1736 | 2015-03-10 16:40:18 -0700 | [diff] [blame] | 532 | if opts.External { |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 533 | info = registry.getExternalProgram(prog) |
| 534 | } else if info = registry.getProgram(prog); info == nil { |
Cosmos Nicolaou | 52e9a22 | 2015-03-16 21:57:16 -0700 | [diff] [blame] | 535 | return nil, ErrNotRegistered |
James Ring | 9d9489d | 2015-01-27 15:48:07 -0800 | [diff] [blame] | 536 | } |
Cosmos Nicolaou | 52e9a22 | 2015-03-16 21:57:16 -0700 | [diff] [blame] | 537 | |
| 538 | if !opts.ExecProtocol && opts.Credentials != nil { |
| 539 | return nil, ErrNoExecAndCustomCreds |
| 540 | } |
| 541 | |
| 542 | if sh.ctx != nil && opts.ExecProtocol && opts.Credentials == nil { |
Asim Shankar | 34fb249 | 2015-03-12 10:25:46 -0700 | [diff] [blame] | 543 | opts.Credentials, err = sh.NewChildCredentials("child") |
Cosmos Nicolaou | 42a1736 | 2015-03-10 16:40:18 -0700 | [diff] [blame] | 544 | if err != nil { |
| 545 | return nil, err |
| 546 | } |
| 547 | } |
Ryan Brown | 61d6938 | 2015-02-25 11:13:39 -0800 | [diff] [blame] | 548 | |
Ryan Brown | 0438443 | 2015-08-27 16:08:32 -0700 | [diff] [blame] | 549 | var agentPath string |
Cosmos Nicolaou | 52e9a22 | 2015-03-16 21:57:16 -0700 | [diff] [blame] | 550 | if opts.Credentials != nil { |
Ryan Brown | 0438443 | 2015-08-27 16:08:32 -0700 | [diff] [blame] | 551 | agentPath = opts.Credentials.Path() |
Ryan Brown | a08a221 | 2015-01-15 15:40:10 -0800 | [diff] [blame] | 552 | } |
James Ring | 9d9489d | 2015-01-27 15:48:07 -0800 | [diff] [blame] | 553 | |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 554 | handle := info.factory() |
Ryan Brown | 0438443 | 2015-08-27 16:08:32 -0700 | [diff] [blame] | 555 | h, err := handle.start(sh, agentPath, &opts, sh.setupProgramEnv(env), sh.expand(args)) |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 556 | if err != nil { |
Cosmos Nicolaou | 42a1736 | 2015-03-10 16:40:18 -0700 | [diff] [blame] | 557 | return h, err |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 558 | } |
| 559 | sh.mu.Lock() |
| 560 | sh.handles[h] = struct{}{} |
Cosmos Nicolaou | 42a1736 | 2015-03-10 16:40:18 -0700 | [diff] [blame] | 561 | sh.lifoHandles = append(sh.lifoHandles, h) |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 562 | sh.mu.Unlock() |
| 563 | return h, nil |
| 564 | } |
| 565 | |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 566 | // ProgramEnvelope returns the command line and environment that would be used |
Todd Wang | 5507c83 | 2015-05-15 22:59:23 -0700 | [diff] [blame] | 567 | // for running the subprocess if it were started with the specifed arguments. |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 568 | func (sh *Shell) ProgramEnvelope(env []string, prog Program, args ...string) ([]string, []string) { |
| 569 | info := registry.getProgram(prog) |
| 570 | if info == nil { |
Cosmos Nicolaou | d4f0056 | 2014-11-17 20:35:48 -0800 | [diff] [blame] | 571 | return []string{}, []string{} |
| 572 | } |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 573 | return info.factory().envelope(sh, sh.setupProgramEnv(env), sh.expand(args)) |
Cosmos Nicolaou | e664f3f | 2014-10-20 17:40:05 -0700 | [diff] [blame] | 574 | } |
| 575 | |
| 576 | // Forget tells the Shell to stop tracking the supplied Handle. This is |
| 577 | // generally used when the application wants to control the order that |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 578 | // programs are shutdown in. |
Cosmos Nicolaou | e664f3f | 2014-10-20 17:40:05 -0700 | [diff] [blame] | 579 | func (sh *Shell) Forget(h Handle) { |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 580 | sh.mu.Lock() |
Todd Wang | 5507c83 | 2015-05-15 22:59:23 -0700 | [diff] [blame] | 581 | if handle, ok := h.(*execHandle); ok { |
| 582 | delete(sh.handles, handle) |
| 583 | } |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 584 | sh.mu.Unlock() |
| 585 | } |
| 586 | |
Todd Wang | 5507c83 | 2015-05-15 22:59:23 -0700 | [diff] [blame] | 587 | func (sh *Shell) expand(args []string) []string { |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 588 | exp := []string{} |
| 589 | for _, a := range args { |
| 590 | if len(a) > 0 && a[0] == '$' { |
| 591 | if v, present := sh.env[a[1:]]; present { |
| 592 | exp = append(exp, v) |
| 593 | continue |
| 594 | } |
| 595 | } |
| 596 | exp = append(exp, a) |
| 597 | } |
| 598 | return exp |
| 599 | } |
| 600 | |
| 601 | // GetVar returns the variable associated with the specified key |
| 602 | // and an indication of whether it is defined or not. |
| 603 | func (sh *Shell) GetVar(key string) (string, bool) { |
| 604 | sh.mu.Lock() |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 605 | v, present := sh.env[key] |
Todd Wang | 3bb46f5 | 2015-05-14 22:01:18 -0700 | [diff] [blame] | 606 | sh.mu.Unlock() |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 607 | return v, present |
| 608 | } |
| 609 | |
| 610 | // SetVar sets the value to be associated with key. |
| 611 | func (sh *Shell) SetVar(key, value string) { |
| 612 | sh.mu.Lock() |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 613 | // TODO(cnicolaou): expand value |
| 614 | sh.env[key] = value |
Todd Wang | 3bb46f5 | 2015-05-14 22:01:18 -0700 | [diff] [blame] | 615 | sh.mu.Unlock() |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 616 | } |
| 617 | |
Cosmos Nicolaou | 2cb05fb | 2014-10-23 13:37:32 -0700 | [diff] [blame] | 618 | // ClearVar removes the speficied variable from the Shell's environment |
| 619 | func (sh *Shell) ClearVar(key string) { |
| 620 | sh.mu.Lock() |
Cosmos Nicolaou | 2cb05fb | 2014-10-23 13:37:32 -0700 | [diff] [blame] | 621 | delete(sh.env, key) |
Todd Wang | 3bb46f5 | 2015-05-14 22:01:18 -0700 | [diff] [blame] | 622 | sh.mu.Unlock() |
Cosmos Nicolaou | 2cb05fb | 2014-10-23 13:37:32 -0700 | [diff] [blame] | 623 | } |
| 624 | |
Jiri Simsa | 3789339 | 2014-11-07 10:55:45 -0800 | [diff] [blame] | 625 | // GetConfigKey returns the value associated with the specified key in |
| 626 | // the Shell's config and an indication of whether it is defined or |
| 627 | // not. |
| 628 | func (sh *Shell) GetConfigKey(key string) (string, bool) { |
| 629 | v, err := sh.config.Get(key) |
| 630 | return v, err == nil |
| 631 | } |
| 632 | |
| 633 | // SetConfigKey sets the value of the specified key in the Shell's |
| 634 | // config. |
| 635 | func (sh *Shell) SetConfigKey(key, value string) { |
| 636 | sh.config.Set(key, value) |
| 637 | } |
| 638 | |
| 639 | // ClearConfigKey removes the speficied key from the Shell's config. |
| 640 | func (sh *Shell) ClearConfigKey(key string) { |
| 641 | sh.config.Clear(key) |
| 642 | } |
| 643 | |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 644 | // Env returns the entire set of environment variables associated with this |
| 645 | // Shell as a string slice. |
| 646 | func (sh *Shell) Env() []string { |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 647 | sh.mu.Lock() |
Todd Wang | 3bb46f5 | 2015-05-14 22:01:18 -0700 | [diff] [blame] | 648 | vars := envvar.MapToSlice(sh.env) |
| 649 | sh.mu.Unlock() |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 650 | return vars |
| 651 | } |
| 652 | |
| 653 | // Cleanup calls Shutdown on all of the Handles currently being tracked |
Cosmos Nicolaou | bbae388 | 2014-10-02 22:58:19 -0700 | [diff] [blame] | 654 | // 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] | 655 | // method in the Handle interface. Cleanup returns the error from the |
| 656 | // last Shutdown that returned a non-nil error. The order that the |
| 657 | // Shutdown routines are executed is not defined. |
| 658 | func (sh *Shell) Cleanup(stdout, stderr io.Writer) error { |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 659 | sh.mu.Lock() |
Cosmos Nicolaou | 42a1736 | 2015-03-10 16:40:18 -0700 | [diff] [blame] | 660 | verbose := sh.sessionVerbosity |
| 661 | sh.mu.Unlock() |
| 662 | |
| 663 | writeMsg := func(format string, args ...interface{}) { |
| 664 | if !verbose { |
| 665 | return |
| 666 | } |
| 667 | if stderr != nil { |
| 668 | fmt.Fprintf(stderr, format, args...) |
| 669 | } |
| 670 | } |
| 671 | |
Cosmos Nicolaou | 9fb1034 | 2015-04-12 19:37:24 -0700 | [diff] [blame] | 672 | writeMsg("---- Shell Cleanup ----\n") |
| 673 | defer writeMsg("---- Shell Cleanup Complete ----\n") |
| 674 | |
Cosmos Nicolaou | 42a1736 | 2015-03-10 16:40:18 -0700 | [diff] [blame] | 675 | sh.mu.Lock() |
Todd Wang | 5507c83 | 2015-05-15 22:59:23 -0700 | [diff] [blame] | 676 | handles := make([]*execHandle, 0, len(sh.lifoHandles)) |
Cosmos Nicolaou | 42a1736 | 2015-03-10 16:40:18 -0700 | [diff] [blame] | 677 | for _, h := range sh.lifoHandles { |
| 678 | if _, present := sh.handles[h]; present { |
| 679 | handles = append(handles, h) |
| 680 | } |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 681 | } |
Todd Wang | 5507c83 | 2015-05-15 22:59:23 -0700 | [diff] [blame] | 682 | sh.handles = make(map[*execHandle]struct{}) |
Cosmos Nicolaou | 42a1736 | 2015-03-10 16:40:18 -0700 | [diff] [blame] | 683 | sh.lifoHandles = nil |
Cosmos Nicolaou | 66afced | 2014-09-15 22:12:43 -0700 | [diff] [blame] | 684 | sh.mu.Unlock() |
Cosmos Nicolaou | 2cb05fb | 2014-10-23 13:37:32 -0700 | [diff] [blame] | 685 | var err error |
Cosmos Nicolaou | 42a1736 | 2015-03-10 16:40:18 -0700 | [diff] [blame] | 686 | for i := len(handles); i > 0; i-- { |
| 687 | h := handles[i-1] |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 688 | writeMsg("---- Cleanup calling Shutdown on program %q\n", h.desc) |
Cosmos Nicolaou | 42a1736 | 2015-03-10 16:40:18 -0700 | [diff] [blame] | 689 | cerr := h.Shutdown(stdout, stderr) |
Cosmos Nicolaou | 2cb05fb | 2014-10-23 13:37:32 -0700 | [diff] [blame] | 690 | if cerr != nil { |
| 691 | err = cerr |
| 692 | } |
Cosmos Nicolaou | 42a1736 | 2015-03-10 16:40:18 -0700 | [diff] [blame] | 693 | fn := func() string { |
| 694 | if cerr == nil { |
| 695 | return ": done" |
| 696 | } else { |
| 697 | return ": error: " + err.Error() |
| 698 | } |
| 699 | } |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 700 | writeMsg("---- Shutdown on program %q%s\n", h.desc, fn()) |
Cosmos Nicolaou | 66afced | 2014-09-15 22:12:43 -0700 | [diff] [blame] | 701 | } |
Ankur | 9f95794 | 2014-11-24 16:34:18 -0800 | [diff] [blame] | 702 | |
Ryan Brown | a08a221 | 2015-01-15 15:40:10 -0800 | [diff] [blame] | 703 | if sh.cancelCtx != nil { |
Cosmos Nicolaou | 9fb1034 | 2015-04-12 19:37:24 -0700 | [diff] [blame] | 704 | writeMsg("---- Cleanup calling cancelCtx ----\n") |
Ryan Brown | a08a221 | 2015-01-15 15:40:10 -0800 | [diff] [blame] | 705 | // Note(ribrdb, caprita): This will shutdown the agents. If there |
| 706 | // were errors shutting down it is possible there could be child |
| 707 | // processes still running, and stopping the agent may cause |
| 708 | // additional failures. |
| 709 | sh.cancelCtx() |
Cosmos Nicolaou | 9afe120 | 2014-09-19 13:45:57 -0700 | [diff] [blame] | 710 | } |
Ryan Brown | a08a221 | 2015-01-15 15:40:10 -0800 | [diff] [blame] | 711 | os.RemoveAll(sh.tempCredDir) |
Cosmos Nicolaou | 2cb05fb | 2014-10-23 13:37:32 -0700 | [diff] [blame] | 712 | return err |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 713 | } |
| 714 | |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 715 | func (sh *Shell) setupProgramEnv(env []string) []string { |
Todd Wang | 3bb46f5 | 2015-05-14 22:01:18 -0700 | [diff] [blame] | 716 | osmap := envvar.SliceToMap(os.Environ()) |
| 717 | evmap := envvar.SliceToMap(env) |
Ankur | 9f95794 | 2014-11-24 16:34:18 -0800 | [diff] [blame] | 718 | |
Cosmos Nicolaou | 2cb05fb | 2014-10-23 13:37:32 -0700 | [diff] [blame] | 719 | sh.mu.Lock() |
Cosmos Nicolaou | 2cb05fb | 2014-10-23 13:37:32 -0700 | [diff] [blame] | 720 | defer sh.mu.Unlock() |
Todd Wang | 3bb46f5 | 2015-05-14 22:01:18 -0700 | [diff] [blame] | 721 | m1 := envvar.MergeMaps(osmap, sh.env) |
Ankur | 9f95794 | 2014-11-24 16:34:18 -0800 | [diff] [blame] | 722 | // Clear any VeyronCredentials directory in m1 as we never |
| 723 | // want the child to directly use the directory specified |
| 724 | // by the shell's VeyronCredentials. |
Todd Wang | 8123b5e | 2015-05-14 18:44:43 -0700 | [diff] [blame] | 725 | delete(m1, ref.EnvCredentials) |
| 726 | delete(m1, ref.EnvAgentEndpoint) |
Ryan Brown | 0438443 | 2015-08-27 16:08:32 -0700 | [diff] [blame] | 727 | delete(m1, ref.EnvAgentPath) |
Ankur | 9f95794 | 2014-11-24 16:34:18 -0800 | [diff] [blame] | 728 | |
Todd Wang | 3bb46f5 | 2015-05-14 22:01:18 -0700 | [diff] [blame] | 729 | m2 := envvar.MergeMaps(m1, evmap) |
| 730 | return envvar.MapToSlice(m2) |
Cosmos Nicolaou | e664f3f | 2014-10-20 17:40:05 -0700 | [diff] [blame] | 731 | } |
| 732 | |
Cosmos Nicolaou | 1381f8a | 2015-03-13 09:40:34 -0700 | [diff] [blame] | 733 | // ExpectSession is a subset of v.io/x/ref/tests/expect.Session's methods |
Cosmos Nicolaou | 42a1736 | 2015-03-10 16:40:18 -0700 | [diff] [blame] | 734 | // that are embedded in Handle. |
| 735 | type ExpectSession interface { |
| 736 | Expect(expected string) |
| 737 | ExpectEOF() error |
| 738 | ExpectRE(pattern string, n int) [][]string |
| 739 | ExpectSetEventuallyRE(expected ...string) [][]string |
| 740 | ExpectSetRE(expected ...string) [][]string |
| 741 | ExpectVar(name string) string |
| 742 | Expectf(format string, args ...interface{}) |
| 743 | ReadAll() (string, error) |
| 744 | ReadLine() string |
| 745 | SetVerbosity(bool) |
| 746 | Failed() bool |
| 747 | Error() error |
| 748 | } |
| 749 | |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 750 | // Handle represents a running program. |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 751 | type Handle interface { |
Cosmos Nicolaou | 42a1736 | 2015-03-10 16:40:18 -0700 | [diff] [blame] | 752 | ExpectSession |
| 753 | |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 754 | // Stdout returns a reader to the running program's stdout stream. |
Cosmos Nicolaou | 9ca249d | 2014-09-18 15:07:12 -0700 | [diff] [blame] | 755 | Stdout() io.Reader |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 756 | |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 757 | // Stderr returns a reader to the running program's stderr |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 758 | // stream. |
| 759 | Stderr() io.Reader |
| 760 | |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 761 | // Stdin returns a writer to the running program's stdin. The |
| 762 | // convention is for programs to wait for stdin to be closed before |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 763 | // they exit, thus the caller should close stdin when it wants the |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 764 | // program to exit cleanly. |
Cosmos Nicolaou | 66afced | 2014-09-15 22:12:43 -0700 | [diff] [blame] | 765 | Stdin() io.Writer |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 766 | |
Cosmos Nicolaou | 66afced | 2014-09-15 22:12:43 -0700 | [diff] [blame] | 767 | // CloseStdin closes stdin in a manner that avoids a data race |
| 768 | // between any current readers on it. |
| 769 | CloseStdin() |
| 770 | |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 771 | // Shutdown closes the Stdin for the program and then reads output |
| 772 | // from the program's stdout until it encounters EOF, waits for |
| 773 | // the program to complete and then reads all of its stderr output. |
Cosmos Nicolaou | bbae388 | 2014-10-02 22:58:19 -0700 | [diff] [blame] | 774 | // The stdout and stderr contents are written to the corresponding |
| 775 | // io.Writers if they are non-nil, otherwise the content is discarded. |
| 776 | Shutdown(stdout, stderr io.Writer) error |
Cosmos Nicolaou | cc58172 | 2014-10-07 12:45:39 -0700 | [diff] [blame] | 777 | |
Todd Wang | 9587390 | 2015-05-22 14:21:30 -0700 | [diff] [blame] | 778 | // Pid returns the pid of the process running the program |
Cosmos Nicolaou | cc58172 | 2014-10-07 12:45:39 -0700 | [diff] [blame] | 779 | Pid() int |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 780 | } |