Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 1 | package modules |
| 2 | |
| 3 | import ( |
| 4 | "bufio" |
| 5 | "flag" |
| 6 | "fmt" |
| 7 | "io" |
| 8 | "io/ioutil" |
| 9 | "os" |
| 10 | "os/exec" |
| 11 | "strings" |
| 12 | "sync" |
| 13 | "time" |
| 14 | |
Jiri Simsa | 519c507 | 2014-09-17 21:37:57 -0700 | [diff] [blame] | 15 | "veyron.io/veyron/veyron2/vlog" |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 16 | |
Cosmos Nicolaou | 486d349 | 2014-09-30 22:21:20 -0700 | [diff] [blame] | 17 | vexec "veyron.io/veyron/veyron/lib/exec" |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 18 | ) |
| 19 | |
| 20 | // execHandle implements both the command and Handle interfaces. |
| 21 | type execHandle struct { |
| 22 | mu sync.Mutex |
| 23 | cmd *exec.Cmd |
| 24 | entryPoint string |
| 25 | handle *vexec.ParentHandle |
Cosmos Nicolaou | 66afced | 2014-09-15 22:12:43 -0700 | [diff] [blame] | 26 | sh *Shell |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 27 | stderr *os.File |
Cosmos Nicolaou | 9ca249d | 2014-09-18 15:07:12 -0700 | [diff] [blame] | 28 | stdout io.ReadCloser |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 29 | stdin io.WriteCloser |
| 30 | } |
| 31 | |
| 32 | func testFlags() []string { |
| 33 | var fl []string |
| 34 | // pass logging flags to any subprocesses |
| 35 | for fname, fval := range vlog.Log.ExplicitlySetFlags() { |
| 36 | fl = append(fl, "--"+fname+"="+fval) |
| 37 | } |
| 38 | timeout := flag.Lookup("test.timeout") |
| 39 | if timeout == nil { |
| 40 | // not a go test binary |
| 41 | return fl |
| 42 | } |
| 43 | // must be a go test binary |
| 44 | fl = append(fl, "-test.run=TestHelperProcess") |
| 45 | val := timeout.Value.(flag.Getter).Get().(time.Duration) |
| 46 | if val.String() != timeout.DefValue { |
| 47 | // use supplied command value for subprocesses |
| 48 | fl = append(fl, "--test.timeout="+timeout.Value.String()) |
| 49 | } else { |
| 50 | // translate default value into 1m for subproccesses |
| 51 | fl = append(fl, "--test.timeout=1m") |
| 52 | } |
| 53 | return fl |
| 54 | } |
| 55 | |
Cosmos Nicolaou | 9c9918d | 2014-09-23 08:45:56 -0700 | [diff] [blame] | 56 | // IsTestHelperProces returns true if it is called in via |
| 57 | // -run=TestHelperProcess which normally only ever happens for subprocesses |
| 58 | // run from tests. |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 59 | func IsTestHelperProcess() bool { |
| 60 | runFlag := flag.Lookup("test.run") |
| 61 | if runFlag == nil { |
| 62 | return false |
| 63 | } |
| 64 | return runFlag.Value.String() == "TestHelperProcess" |
| 65 | } |
| 66 | |
| 67 | func newExecHandle(entryPoint string) command { |
| 68 | return &execHandle{entryPoint: entryPoint} |
| 69 | } |
| 70 | |
Cosmos Nicolaou | 9ca249d | 2014-09-18 15:07:12 -0700 | [diff] [blame] | 71 | func (eh *execHandle) Stdout() io.Reader { |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 72 | eh.mu.Lock() |
| 73 | defer eh.mu.Unlock() |
| 74 | return eh.stdout |
| 75 | } |
| 76 | |
| 77 | func (eh *execHandle) Stderr() io.Reader { |
| 78 | eh.mu.Lock() |
| 79 | defer eh.mu.Unlock() |
| 80 | return eh.stderr |
| 81 | } |
| 82 | |
Cosmos Nicolaou | 66afced | 2014-09-15 22:12:43 -0700 | [diff] [blame] | 83 | func (eh *execHandle) Stdin() io.Writer { |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 84 | eh.mu.Lock() |
| 85 | defer eh.mu.Unlock() |
| 86 | return eh.stdin |
| 87 | } |
| 88 | |
Cosmos Nicolaou | 66afced | 2014-09-15 22:12:43 -0700 | [diff] [blame] | 89 | func (eh *execHandle) CloseStdin() { |
| 90 | eh.mu.Lock() |
| 91 | eh.stdin.Close() |
| 92 | eh.mu.Unlock() |
| 93 | } |
| 94 | |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 95 | // mergeOSEnv returns a slice contained the merged set of environment |
| 96 | // variables from the OS environment and those in this Shell, preferring |
| 97 | // values in the Shell environment over those found in the OS environment. |
| 98 | func (sh *Shell) mergeOSEnvSlice() []string { |
| 99 | merged := sh.mergeOSEnv() |
| 100 | env := []string{} |
| 101 | for k, v := range merged { |
| 102 | env = append(env, k+"="+v) |
| 103 | } |
| 104 | return env |
| 105 | } |
| 106 | |
| 107 | func osEnvironMap() map[string]string { |
| 108 | m := make(map[string]string) |
| 109 | for _, osv := range os.Environ() { |
| 110 | if len(osv) == 0 { |
| 111 | continue |
| 112 | } |
| 113 | parts := strings.SplitN(osv, "=", 2) |
| 114 | key := parts[0] |
| 115 | if len(parts) == 2 { |
| 116 | m[key] = parts[1] |
| 117 | } else { |
| 118 | m[key] = "" |
| 119 | } |
| 120 | } |
| 121 | return m |
| 122 | } |
| 123 | func (sh *Shell) mergeOSEnv() map[string]string { |
| 124 | merged := osEnvironMap() |
| 125 | sh.mu.Lock() |
| 126 | for k, v := range sh.env { |
| 127 | merged[k] = v |
| 128 | } |
| 129 | sh.mu.Unlock() |
| 130 | return merged |
| 131 | } |
| 132 | |
| 133 | func (eh *execHandle) start(sh *Shell, args ...string) (Handle, error) { |
| 134 | eh.mu.Lock() |
| 135 | defer eh.mu.Unlock() |
Cosmos Nicolaou | 66afced | 2014-09-15 22:12:43 -0700 | [diff] [blame] | 136 | eh.sh = sh |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 137 | newargs := append(testFlags(), args...) |
| 138 | cmd := exec.Command(os.Args[0], newargs...) |
| 139 | cmd.Env = append(sh.mergeOSEnvSlice(), eh.entryPoint) |
| 140 | stderr, err := ioutil.TempFile("", "__modules__"+strings.TrimLeft(eh.entryPoint, "-\n\t ")) |
| 141 | if err != nil { |
| 142 | return nil, err |
| 143 | } |
| 144 | cmd.Stderr = stderr |
| 145 | stdout, err := cmd.StdoutPipe() |
| 146 | if err != nil { |
| 147 | return nil, err |
| 148 | } |
| 149 | stdin, err := cmd.StdinPipe() |
| 150 | if err != nil { |
| 151 | return nil, err |
| 152 | } |
| 153 | |
| 154 | handle := vexec.NewParentHandle(cmd) |
Cosmos Nicolaou | 9ca249d | 2014-09-18 15:07:12 -0700 | [diff] [blame] | 155 | eh.stdout = stdout |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 156 | eh.stderr = stderr |
| 157 | eh.stdin = stdin |
| 158 | eh.handle = handle |
| 159 | eh.cmd = cmd |
| 160 | if err := handle.Start(); err != nil { |
| 161 | return nil, err |
| 162 | } |
Matt Rosencrantz | 281729f | 2014-09-19 10:45:53 -0700 | [diff] [blame] | 163 | err = handle.WaitForReady(10 * time.Second) |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 164 | return eh, err |
| 165 | } |
| 166 | |
Cosmos Nicolaou | 66afced | 2014-09-15 22:12:43 -0700 | [diff] [blame] | 167 | func (eh *execHandle) Shutdown(output io.Writer) error { |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 168 | eh.mu.Lock() |
| 169 | defer eh.mu.Unlock() |
| 170 | eh.stdin.Close() |
Cosmos Nicolaou | 66afced | 2014-09-15 22:12:43 -0700 | [diff] [blame] | 171 | defer eh.sh.forget(eh) |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 172 | if eh.stderr != nil { |
| 173 | defer func() { |
| 174 | eh.stderr.Close() |
| 175 | os.Remove(eh.stderr.Name()) |
| 176 | }() |
| 177 | if output == nil { |
Cosmos Nicolaou | 66afced | 2014-09-15 22:12:43 -0700 | [diff] [blame] | 178 | return eh.cmd.Wait() |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 179 | } |
| 180 | if _, err := eh.stderr.Seek(0, 0); err != nil { |
Cosmos Nicolaou | 66afced | 2014-09-15 22:12:43 -0700 | [diff] [blame] | 181 | return eh.cmd.Wait() |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 182 | } |
| 183 | scanner := bufio.NewScanner(eh.stderr) |
| 184 | for scanner.Scan() { |
| 185 | fmt.Fprintf(output, "%s\n", scanner.Text()) |
| 186 | } |
| 187 | } |
Cosmos Nicolaou | 66afced | 2014-09-15 22:12:43 -0700 | [diff] [blame] | 188 | return eh.cmd.Wait() |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 189 | } |
| 190 | |
Cosmos Nicolaou | 9c9918d | 2014-09-23 08:45:56 -0700 | [diff] [blame] | 191 | const ShellEntryPoint = "VEYRON_SHELL_HELPER_PROCESS_ENTRY_POINT" |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 192 | |
| 193 | func RegisterChild(name string, main Main) { |
| 194 | child.Lock() |
| 195 | defer child.Unlock() |
| 196 | child.mains[name] = main |
| 197 | } |
| 198 | |
| 199 | func Dispatch() error { |
| 200 | return child.dispatch() |
| 201 | } |
| 202 | |
| 203 | func (child *childRegistrar) hasCommand(name string) bool { |
| 204 | child.Lock() |
| 205 | _, present := child.mains[name] |
| 206 | child.Unlock() |
| 207 | return present |
| 208 | } |
| 209 | |
| 210 | func (child *childRegistrar) dispatch() error { |
Cosmos Nicolaou | 9c9918d | 2014-09-23 08:45:56 -0700 | [diff] [blame] | 211 | command := os.Getenv(ShellEntryPoint) |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 212 | if len(command) == 0 { |
Cosmos Nicolaou | 9c9918d | 2014-09-23 08:45:56 -0700 | [diff] [blame] | 213 | return fmt.Errorf("Failed to find entrypoint %q", ShellEntryPoint) |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 214 | } |
| 215 | child.Lock() |
| 216 | m := child.mains[command] |
| 217 | child.Unlock() |
| 218 | if m == nil { |
| 219 | return fmt.Errorf("Shell command %q not registered", command) |
| 220 | } |
| 221 | go func(pid int) { |
| 222 | for { |
| 223 | _, err := os.FindProcess(pid) |
| 224 | if err != nil { |
| 225 | fmt.Fprintf(os.Stderr, "Looks like our parent exited: %v", err) |
| 226 | os.Exit(1) |
| 227 | } |
| 228 | time.Sleep(time.Second) |
| 229 | } |
| 230 | }(os.Getppid()) |
| 231 | ch, err := vexec.GetChildHandle() |
| 232 | if err == nil { |
| 233 | // Only signal that the child is ready if we successfully get |
| 234 | // a child handle. We most likely failed to get a child handle |
| 235 | // because the subprocess was run directly from the command line. |
| 236 | ch.SetReady() |
| 237 | } |
| 238 | return m(os.Stdin, os.Stdout, os.Stderr, osEnvironMap(), flag.Args()...) |
| 239 | } |
Cosmos Nicolaou | addf483 | 2014-09-10 21:36:54 -0700 | [diff] [blame] | 240 | |
| 241 | // WaitForEof returns when a read on its io.Reader parameter returns io.EOF |
| 242 | func WaitForEOF(stdin io.Reader) { |
| 243 | buf := [1024]byte{} |
| 244 | for { |
| 245 | if _, err := stdin.Read(buf[:]); err == io.EOF { |
| 246 | return |
| 247 | } |
| 248 | } |
| 249 | } |