blob: b2a12da0ab5373cb6d090b94a6bae6f2cab2b63a [file] [log] [blame]
Cosmos Nicolaou62613842014-08-25 21:57:37 -07001package modules
2
3import (
4 "bufio"
5 "flag"
6 "fmt"
7 "io"
8 "io/ioutil"
9 "os"
10 "os/exec"
11 "strings"
12 "sync"
13 "time"
14
Jiri Simsa519c5072014-09-17 21:37:57 -070015 "veyron.io/veyron/veyron2/vlog"
Cosmos Nicolaou62613842014-08-25 21:57:37 -070016
Cosmos Nicolaou486d3492014-09-30 22:21:20 -070017 vexec "veyron.io/veyron/veyron/lib/exec"
Cosmos Nicolaou62613842014-08-25 21:57:37 -070018)
19
20// execHandle implements both the command and Handle interfaces.
21type execHandle struct {
22 mu sync.Mutex
23 cmd *exec.Cmd
24 entryPoint string
25 handle *vexec.ParentHandle
Cosmos Nicolaou66afced2014-09-15 22:12:43 -070026 sh *Shell
Cosmos Nicolaou62613842014-08-25 21:57:37 -070027 stderr *os.File
Cosmos Nicolaou9ca249d2014-09-18 15:07:12 -070028 stdout io.ReadCloser
Cosmos Nicolaou62613842014-08-25 21:57:37 -070029 stdin io.WriteCloser
30}
31
32func 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 Nicolaou9c9918d2014-09-23 08:45:56 -070056// IsTestHelperProces returns true if it is called in via
57// -run=TestHelperProcess which normally only ever happens for subprocesses
58// run from tests.
Cosmos Nicolaou62613842014-08-25 21:57:37 -070059func IsTestHelperProcess() bool {
60 runFlag := flag.Lookup("test.run")
61 if runFlag == nil {
62 return false
63 }
64 return runFlag.Value.String() == "TestHelperProcess"
65}
66
67func newExecHandle(entryPoint string) command {
68 return &execHandle{entryPoint: entryPoint}
69}
70
Cosmos Nicolaou9ca249d2014-09-18 15:07:12 -070071func (eh *execHandle) Stdout() io.Reader {
Cosmos Nicolaou62613842014-08-25 21:57:37 -070072 eh.mu.Lock()
73 defer eh.mu.Unlock()
74 return eh.stdout
75}
76
77func (eh *execHandle) Stderr() io.Reader {
78 eh.mu.Lock()
79 defer eh.mu.Unlock()
80 return eh.stderr
81}
82
Cosmos Nicolaou66afced2014-09-15 22:12:43 -070083func (eh *execHandle) Stdin() io.Writer {
Cosmos Nicolaou62613842014-08-25 21:57:37 -070084 eh.mu.Lock()
85 defer eh.mu.Unlock()
86 return eh.stdin
87}
88
Cosmos Nicolaou66afced2014-09-15 22:12:43 -070089func (eh *execHandle) CloseStdin() {
90 eh.mu.Lock()
91 eh.stdin.Close()
92 eh.mu.Unlock()
93}
94
Cosmos Nicolaou62613842014-08-25 21:57:37 -070095// 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.
98func (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
107func 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}
123func (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
133func (eh *execHandle) start(sh *Shell, args ...string) (Handle, error) {
134 eh.mu.Lock()
135 defer eh.mu.Unlock()
Cosmos Nicolaou66afced2014-09-15 22:12:43 -0700136 eh.sh = sh
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700137 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 Nicolaou9ca249d2014-09-18 15:07:12 -0700155 eh.stdout = stdout
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700156 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 Rosencrantz281729f2014-09-19 10:45:53 -0700163 err = handle.WaitForReady(10 * time.Second)
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700164 return eh, err
165}
166
Cosmos Nicolaou66afced2014-09-15 22:12:43 -0700167func (eh *execHandle) Shutdown(output io.Writer) error {
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700168 eh.mu.Lock()
169 defer eh.mu.Unlock()
170 eh.stdin.Close()
Cosmos Nicolaou66afced2014-09-15 22:12:43 -0700171 defer eh.sh.forget(eh)
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700172 if eh.stderr != nil {
173 defer func() {
174 eh.stderr.Close()
175 os.Remove(eh.stderr.Name())
176 }()
177 if output == nil {
Cosmos Nicolaou66afced2014-09-15 22:12:43 -0700178 return eh.cmd.Wait()
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700179 }
180 if _, err := eh.stderr.Seek(0, 0); err != nil {
Cosmos Nicolaou66afced2014-09-15 22:12:43 -0700181 return eh.cmd.Wait()
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700182 }
183 scanner := bufio.NewScanner(eh.stderr)
184 for scanner.Scan() {
185 fmt.Fprintf(output, "%s\n", scanner.Text())
186 }
187 }
Cosmos Nicolaou66afced2014-09-15 22:12:43 -0700188 return eh.cmd.Wait()
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700189}
190
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700191const ShellEntryPoint = "VEYRON_SHELL_HELPER_PROCESS_ENTRY_POINT"
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700192
193func RegisterChild(name string, main Main) {
194 child.Lock()
195 defer child.Unlock()
196 child.mains[name] = main
197}
198
199func Dispatch() error {
200 return child.dispatch()
201}
202
203func (child *childRegistrar) hasCommand(name string) bool {
204 child.Lock()
205 _, present := child.mains[name]
206 child.Unlock()
207 return present
208}
209
210func (child *childRegistrar) dispatch() error {
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700211 command := os.Getenv(ShellEntryPoint)
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700212 if len(command) == 0 {
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -0700213 return fmt.Errorf("Failed to find entrypoint %q", ShellEntryPoint)
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700214 }
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 Nicolaouaddf4832014-09-10 21:36:54 -0700240
241// WaitForEof returns when a read on its io.Reader parameter returns io.EOF
242func WaitForEOF(stdin io.Reader) {
243 buf := [1024]byte{}
244 for {
245 if _, err := stdin.Read(buf[:]); err == io.EOF {
246 return
247 }
248 }
249}