blob: 612020ffb125ddbd5b449fd1699d3b97dbd0b5f8 [file] [log] [blame]
Jiri Simsa5293dcb2014-05-10 09:56:38 -07001package exec
2
3import (
Jiri Simsac199bc12014-05-30 12:52:24 -07004 "encoding/binary"
Jiri Simsa5293dcb2014-05-10 09:56:38 -07005 "errors"
Cosmos Nicolaou1c18c1c2014-10-08 16:37:10 -07006 "fmt"
Jiri Simsac199bc12014-05-30 12:52:24 -07007 "io"
Jiri Simsa5293dcb2014-05-10 09:56:38 -07008 "os"
9 "os/exec"
Cosmos Nicolaou1c18c1c2014-10-08 16:37:10 -070010 "strings"
Bogdan Caprita650b1622014-11-21 15:11:05 -080011 "sync"
Jiri Simsa5293dcb2014-05-10 09:56:38 -070012 "syscall"
13 "time"
Cosmos Nicolaoubfcac5f2014-05-22 21:57:35 -070014
Jiri Simsa764efb72014-12-25 20:57:03 -080015 "v.io/core/veyron2/vlog"
Cosmos Nicolaou251a4d82014-09-30 22:28:45 -070016
Jiri Simsa764efb72014-12-25 20:57:03 -080017 "v.io/core/veyron/lib/timekeeper"
Jiri Simsa5293dcb2014-05-10 09:56:38 -070018)
19
20var (
Cosmos Nicolaou1c18c1c2014-10-08 16:37:10 -070021 ErrAuthTimeout = errors.New("timeout in auth handshake")
Jiri Simsa5293dcb2014-05-10 09:56:38 -070022 ErrTimeout = errors.New("timeout waiting for child")
23 ErrSecretTooLarge = errors.New("secret is too large")
24)
25
26// A ParentHandle is the Parent process' means of managing a single child.
27type ParentHandle struct {
Cosmos Nicolaouee7abc22014-05-27 10:50:03 -070028 c *exec.Cmd
Cosmos Nicolaou486d3492014-09-30 22:21:20 -070029 config Config
Cosmos Nicolaouee7abc22014-05-27 10:50:03 -070030 secret string
31 statusRead *os.File
32 statusWrite *os.File
33 tk timekeeper.TimeKeeper
Bogdan Caprita650b1622014-11-21 15:11:05 -080034 waitDone bool
35 waitErr error
36 waitLock sync.Mutex
Jiri Simsa5293dcb2014-05-10 09:56:38 -070037}
38
39// ParentHandleOpt is an option for NewParentHandle.
40type ParentHandleOpt interface {
Jiri Simsac199bc12014-05-30 12:52:24 -070041 // ExecParentHandleOpt is a signature 'dummy' method for the
42 // interface.
Jiri Simsa5293dcb2014-05-10 09:56:38 -070043 ExecParentHandleOpt()
44}
45
Bogdan Capritaa4d9ee42014-06-20 16:42:53 -070046// ConfigOpt can be used to seed the parent handle with a
47// config to be passed to the child.
48type ConfigOpt struct {
Cosmos Nicolaou486d3492014-09-30 22:21:20 -070049 Config
Bogdan Capritaa4d9ee42014-06-20 16:42:53 -070050}
Jiri Simsac199bc12014-05-30 12:52:24 -070051
Bogdan Capritaa4d9ee42014-06-20 16:42:53 -070052// ExecParentHandleOpt makes ConfigOpt an instance of
Jiri Simsac199bc12014-05-30 12:52:24 -070053// ParentHandleOpt.
Bogdan Capritaa4d9ee42014-06-20 16:42:53 -070054func (ConfigOpt) ExecParentHandleOpt() {}
Jiri Simsac199bc12014-05-30 12:52:24 -070055
56// SecretOpt can be used to seed the parent handle with a custom secret.
57type SecretOpt string
58
59// ExecParentHandleOpt makes SecretOpt an instance of ParentHandleOpt.
Bogdan Capritaa4d9ee42014-06-20 16:42:53 -070060func (SecretOpt) ExecParentHandleOpt() {}
Jiri Simsac199bc12014-05-30 12:52:24 -070061
Jiri Simsa5293dcb2014-05-10 09:56:38 -070062// TimeKeeperOpt can be used to seed the parent handle with a custom timekeeper.
63type TimeKeeperOpt struct {
Cosmos Nicolaouee7abc22014-05-27 10:50:03 -070064 timekeeper.TimeKeeper
Jiri Simsa5293dcb2014-05-10 09:56:38 -070065}
66
67// ExecParentHandleOpt makes TimeKeeperOpt an instance of ParentHandleOpt.
Bogdan Capritaa4d9ee42014-06-20 16:42:53 -070068func (TimeKeeperOpt) ExecParentHandleOpt() {}
Jiri Simsa5293dcb2014-05-10 09:56:38 -070069
70// NewParentHandle creates a ParentHandle for the child process represented by
71// an instance of exec.Cmd.
Jiri Simsac199bc12014-05-30 12:52:24 -070072func NewParentHandle(c *exec.Cmd, opts ...ParentHandleOpt) *ParentHandle {
Cosmos Nicolaou486d3492014-09-30 22:21:20 -070073 cfg, secret := NewConfig(), ""
Jiri Simsac199bc12014-05-30 12:52:24 -070074 tk := timekeeper.RealTime()
Jiri Simsa5293dcb2014-05-10 09:56:38 -070075 for _, opt := range opts {
76 switch v := opt.(type) {
Bogdan Capritaa4d9ee42014-06-20 16:42:53 -070077 case ConfigOpt:
78 cfg = v
Jiri Simsac199bc12014-05-30 12:52:24 -070079 case SecretOpt:
80 secret = string(v)
Jiri Simsa5293dcb2014-05-10 09:56:38 -070081 case TimeKeeperOpt:
Cosmos Nicolaouee7abc22014-05-27 10:50:03 -070082 tk = v
Jiri Simsa5293dcb2014-05-10 09:56:38 -070083 default:
84 vlog.Errorf("Unrecognized parent option: %v", v)
85 }
86 }
Jiri Simsa5293dcb2014-05-10 09:56:38 -070087 return &ParentHandle{
Jiri Simsa24e87aa2014-06-09 09:27:34 -070088 c: c,
Bogdan Capritaa4d9ee42014-06-20 16:42:53 -070089 config: cfg,
Jiri Simsa24e87aa2014-06-09 09:27:34 -070090 secret: secret,
91 tk: tk,
Jiri Simsa5293dcb2014-05-10 09:56:38 -070092 }
93}
94
95// Start starts the child process, sharing a secret with it and
96// setting up a communication channel over which to read its status.
97func (p *ParentHandle) Start() error {
Cosmos Nicolaoue5b41502014-10-29 22:55:09 -070098 // Make sure that there are no instances of the VersionVariable
99 // already in the environment (which can happen when a subprocess
100 // creates a subprocess etc)
101 nenv := make([]string, 0, len(p.c.Env)+1)
102 for _, e := range p.c.Env {
103 if strings.HasPrefix(e, VersionVariable+"=") {
104 continue
105 }
106 nenv = append(nenv, e)
107 }
108 p.c.Env = append(nenv, VersionVariable+"="+version1)
109
Jiri Simsac199bc12014-05-30 12:52:24 -0700110 // Create anonymous pipe for communicating data between the child
111 // and the parent.
Bogdan Caprita7f491672014-11-13 14:51:08 -0800112 // TODO(caprita): As per ribrdb@, Go's exec does not prune the set
113 // of file descriptors passed down to the child process, and hence
114 // a child may get access to the files meant for another child.
115 // Do we need to ensure only one thread is allowed to create these
116 // pipes at any time?
Jiri Simsac199bc12014-05-30 12:52:24 -0700117 dataRead, dataWrite, err := os.Pipe()
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700118 if err != nil {
119 return err
120 }
Jiri Simsac199bc12014-05-30 12:52:24 -0700121 defer dataRead.Close()
122 defer dataWrite.Close()
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700123 statusRead, statusWrite, err := os.Pipe()
124 if err != nil {
125 return err
126 }
127 p.statusRead = statusRead
128 p.statusWrite = statusWrite
Jiri Simsac199bc12014-05-30 12:52:24 -0700129 // Add the parent-child pipes to cmd.ExtraFiles, offsetting all
130 // existing file descriptors accordingly.
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700131 extraFiles := make([]*os.File, len(p.c.ExtraFiles)+2)
Jiri Simsac199bc12014-05-30 12:52:24 -0700132 extraFiles[0] = dataRead
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700133 extraFiles[1] = statusWrite
134 for i, _ := range p.c.ExtraFiles {
135 extraFiles[i+2] = p.c.ExtraFiles[i]
136 }
137 p.c.ExtraFiles = extraFiles
Jiri Simsac199bc12014-05-30 12:52:24 -0700138 // Start the child process.
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700139 if err := p.c.Start(); err != nil {
140 p.statusWrite.Close()
141 p.statusRead.Close()
142 return err
143 }
Jiri Simsac199bc12014-05-30 12:52:24 -0700144 // Pass data to the child using a pipe.
Bogdan Capritaa4d9ee42014-06-20 16:42:53 -0700145 serializedConfig, err := p.config.Serialize()
146 if err != nil {
147 return err
148 }
149 if err := encodeString(dataWrite, serializedConfig); err != nil {
Jiri Simsac199bc12014-05-30 12:52:24 -0700150 p.statusWrite.Close()
151 p.statusRead.Close()
152 return err
153 }
Jiri Simsa84059da2014-06-02 17:22:05 -0700154 if err := encodeString(dataWrite, p.secret); err != nil {
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700155 p.statusWrite.Close()
156 p.statusRead.Close()
157 return err
158 }
159 return nil
160}
161
Cosmos Nicolaouee7abc22014-05-27 10:50:03 -0700162func waitForStatus(c chan string, e chan error, r *os.File) {
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700163 buf := make([]byte, 100)
164 n, err := r.Read(buf)
165 if err != nil {
166 e <- err
167 } else {
168 c <- string(buf[:n])
169 }
170 r.Close()
171 close(c)
172 close(e)
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700173}
174
175// WaitForReady will wait for the child process to become ready.
176func (p *ParentHandle) WaitForReady(timeout time.Duration) error {
177 defer p.statusWrite.Close()
178 c := make(chan string, 1)
179 e := make(chan error, 1)
Cosmos Nicolaouee7abc22014-05-27 10:50:03 -0700180 go waitForStatus(c, e, p.statusRead)
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700181 for {
182 select {
183 case err := <-e:
Cosmos Nicolaou1c18c1c2014-10-08 16:37:10 -0700184 if err != nil {
185 return err
186 }
187 // waitForStatus has closed the channel, but we may not
188 // have read the message from it yet.
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700189 case st := <-c:
190 if st == readyStatus {
191 return nil
192 }
Cosmos Nicolaou1c18c1c2014-10-08 16:37:10 -0700193 if strings.HasPrefix(st, failedStatus) {
194 return fmt.Errorf("%s", strings.TrimPrefix(st, failedStatus))
195 }
196 if len(st) > 0 {
197 return fmt.Errorf("unrecognised status from subprocess: %q", st)
198 }
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700199 case <-p.tk.After(timeout):
200 // Make sure that the read in waitForStatus
201 // returns now.
202 p.statusWrite.Write([]byte("quit"))
203 return ErrTimeout
204 }
205 }
206 panic("unreachable")
207}
208
Bogdan Caprita650b1622014-11-21 15:11:05 -0800209// wait performs the Wait on the underlying command under lock, and only once
210// (subsequent wait calls block until the Wait is finished). It's ok to call
211// wait multiple times, and in parallel. The error from the initial Wait is
212// cached and returned for all subsequent calls.
213func (p *ParentHandle) wait() error {
214 p.waitLock.Lock()
215 defer p.waitLock.Unlock()
216 if p.waitDone {
217 return p.waitErr
218 }
219 p.waitErr = p.c.Wait()
220 p.waitDone = true
221 return p.waitErr
222}
223
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700224// Wait will wait for the child process to terminate of its own accord.
225// It returns nil if the process exited cleanly with an exit status of 0,
226// any other exit code or error will result in an appropriate error return
227func (p *ParentHandle) Wait(timeout time.Duration) error {
228 c := make(chan error, 1)
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700229 go func() {
Bogdan Caprita650b1622014-11-21 15:11:05 -0800230 c <- p.wait()
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700231 close(c)
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700232 }()
233 // If timeout is zero time.After will panic; we handle zero specially
234 // to mean infinite timeout.
235 if timeout > 0 {
236 select {
237 case <-p.tk.After(timeout):
238 return ErrTimeout
239 case err := <-c:
240 return err
241 }
242 } else {
243 return <-c
244 }
245 panic("unreachable")
246}
247
Cosmos Nicolaouee7abc22014-05-27 10:50:03 -0700248// Pid returns the pid of the child, 0 if the child process doesn't exist
249func (p *ParentHandle) Pid() int {
250 if p.c.Process != nil {
251 return p.c.Process.Pid
252 }
253 return 0
254}
255
256// Exists returns true if the child process exists and can be signal'ed
257func (p *ParentHandle) Exists() bool {
258 if p.c.Process != nil {
259 return syscall.Kill(p.c.Process.Pid, 0) == nil
260 }
261 return false
262}
263
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700264// Kill kills the child process.
265func (p *ParentHandle) Kill() error {
266 return p.c.Process.Kill()
267}
268
269// Signal sends the given signal to the child process.
270func (p *ParentHandle) Signal(sig syscall.Signal) error {
271 return syscall.Kill(p.c.Process.Pid, sig)
272}
273
274// Clean will clean up state, including killing the child process.
275func (p *ParentHandle) Clean() error {
276 if err := p.Kill(); err != nil {
277 return err
278 }
Bogdan Caprita650b1622014-11-21 15:11:05 -0800279 return p.wait()
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700280}
Jiri Simsac199bc12014-05-30 12:52:24 -0700281
Jiri Simsa84059da2014-06-02 17:22:05 -0700282func encodeString(w io.Writer, data string) error {
Jiri Simsac199bc12014-05-30 12:52:24 -0700283 l := len(data)
284 if err := binary.Write(w, binary.BigEndian, int64(l)); err != nil {
285 return err
286 }
287 if n, err := w.Write([]byte(data)); err != nil || n != l {
288 if err != nil {
289 return err
290 } else {
291 return errors.New("partial write")
292 }
293 }
294 return nil
295}