blob: b0d995d52a42dc69260271cc8f6b36fc75cbc8ea [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"
Jiri Simsa5293dcb2014-05-10 09:56:38 -070011 "syscall"
12 "time"
Cosmos Nicolaoubfcac5f2014-05-22 21:57:35 -070013
Jiri Simsa519c5072014-09-17 21:37:57 -070014 "veyron.io/veyron/veyron2/vlog"
Cosmos Nicolaou251a4d82014-09-30 22:28:45 -070015
16 "veyron.io/veyron/veyron/lib/timekeeper"
Jiri Simsa5293dcb2014-05-10 09:56:38 -070017)
18
19var (
Cosmos Nicolaou1c18c1c2014-10-08 16:37:10 -070020 ErrAuthTimeout = errors.New("timeout in auth handshake")
Jiri Simsa5293dcb2014-05-10 09:56:38 -070021 ErrTimeout = errors.New("timeout waiting for child")
22 ErrSecretTooLarge = errors.New("secret is too large")
23)
24
25// A ParentHandle is the Parent process' means of managing a single child.
26type ParentHandle struct {
Cosmos Nicolaouee7abc22014-05-27 10:50:03 -070027 c *exec.Cmd
Cosmos Nicolaou486d3492014-09-30 22:21:20 -070028 config Config
Cosmos Nicolaouee7abc22014-05-27 10:50:03 -070029 secret string
30 statusRead *os.File
31 statusWrite *os.File
32 tk timekeeper.TimeKeeper
Jiri Simsa5293dcb2014-05-10 09:56:38 -070033}
34
35// ParentHandleOpt is an option for NewParentHandle.
36type ParentHandleOpt interface {
Jiri Simsac199bc12014-05-30 12:52:24 -070037 // ExecParentHandleOpt is a signature 'dummy' method for the
38 // interface.
Jiri Simsa5293dcb2014-05-10 09:56:38 -070039 ExecParentHandleOpt()
40}
41
Bogdan Capritaa4d9ee42014-06-20 16:42:53 -070042// ConfigOpt can be used to seed the parent handle with a
43// config to be passed to the child.
44type ConfigOpt struct {
Cosmos Nicolaou486d3492014-09-30 22:21:20 -070045 Config
Bogdan Capritaa4d9ee42014-06-20 16:42:53 -070046}
Jiri Simsac199bc12014-05-30 12:52:24 -070047
Bogdan Capritaa4d9ee42014-06-20 16:42:53 -070048// ExecParentHandleOpt makes ConfigOpt an instance of
Jiri Simsac199bc12014-05-30 12:52:24 -070049// ParentHandleOpt.
Bogdan Capritaa4d9ee42014-06-20 16:42:53 -070050func (ConfigOpt) ExecParentHandleOpt() {}
Jiri Simsac199bc12014-05-30 12:52:24 -070051
52// SecretOpt can be used to seed the parent handle with a custom secret.
53type SecretOpt string
54
55// ExecParentHandleOpt makes SecretOpt an instance of ParentHandleOpt.
Bogdan Capritaa4d9ee42014-06-20 16:42:53 -070056func (SecretOpt) ExecParentHandleOpt() {}
Jiri Simsac199bc12014-05-30 12:52:24 -070057
Jiri Simsa5293dcb2014-05-10 09:56:38 -070058// TimeKeeperOpt can be used to seed the parent handle with a custom timekeeper.
59type TimeKeeperOpt struct {
Cosmos Nicolaouee7abc22014-05-27 10:50:03 -070060 timekeeper.TimeKeeper
Jiri Simsa5293dcb2014-05-10 09:56:38 -070061}
62
63// ExecParentHandleOpt makes TimeKeeperOpt an instance of ParentHandleOpt.
Bogdan Capritaa4d9ee42014-06-20 16:42:53 -070064func (TimeKeeperOpt) ExecParentHandleOpt() {}
Jiri Simsa5293dcb2014-05-10 09:56:38 -070065
66// NewParentHandle creates a ParentHandle for the child process represented by
67// an instance of exec.Cmd.
Jiri Simsac199bc12014-05-30 12:52:24 -070068func NewParentHandle(c *exec.Cmd, opts ...ParentHandleOpt) *ParentHandle {
Cosmos Nicolaou486d3492014-09-30 22:21:20 -070069 cfg, secret := NewConfig(), ""
Jiri Simsac199bc12014-05-30 12:52:24 -070070 tk := timekeeper.RealTime()
Jiri Simsa5293dcb2014-05-10 09:56:38 -070071 for _, opt := range opts {
72 switch v := opt.(type) {
Bogdan Capritaa4d9ee42014-06-20 16:42:53 -070073 case ConfigOpt:
74 cfg = v
Jiri Simsac199bc12014-05-30 12:52:24 -070075 case SecretOpt:
76 secret = string(v)
Jiri Simsa5293dcb2014-05-10 09:56:38 -070077 case TimeKeeperOpt:
Cosmos Nicolaouee7abc22014-05-27 10:50:03 -070078 tk = v
Jiri Simsa5293dcb2014-05-10 09:56:38 -070079 default:
80 vlog.Errorf("Unrecognized parent option: %v", v)
81 }
82 }
Jiri Simsa5293dcb2014-05-10 09:56:38 -070083 return &ParentHandle{
Jiri Simsa24e87aa2014-06-09 09:27:34 -070084 c: c,
Bogdan Capritaa4d9ee42014-06-20 16:42:53 -070085 config: cfg,
Jiri Simsa24e87aa2014-06-09 09:27:34 -070086 secret: secret,
87 tk: tk,
Jiri Simsa5293dcb2014-05-10 09:56:38 -070088 }
89}
90
91// Start starts the child process, sharing a secret with it and
92// setting up a communication channel over which to read its status.
93func (p *ParentHandle) Start() error {
Cosmos Nicolaoue5b41502014-10-29 22:55:09 -070094 // Make sure that there are no instances of the VersionVariable
95 // already in the environment (which can happen when a subprocess
96 // creates a subprocess etc)
97 nenv := make([]string, 0, len(p.c.Env)+1)
98 for _, e := range p.c.Env {
99 if strings.HasPrefix(e, VersionVariable+"=") {
100 continue
101 }
102 nenv = append(nenv, e)
103 }
104 p.c.Env = append(nenv, VersionVariable+"="+version1)
105
Jiri Simsac199bc12014-05-30 12:52:24 -0700106 // Create anonymous pipe for communicating data between the child
107 // and the parent.
108 dataRead, dataWrite, err := os.Pipe()
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700109 if err != nil {
110 return err
111 }
Jiri Simsac199bc12014-05-30 12:52:24 -0700112 defer dataRead.Close()
113 defer dataWrite.Close()
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700114 statusRead, statusWrite, err := os.Pipe()
115 if err != nil {
116 return err
117 }
118 p.statusRead = statusRead
119 p.statusWrite = statusWrite
Jiri Simsac199bc12014-05-30 12:52:24 -0700120 // Add the parent-child pipes to cmd.ExtraFiles, offsetting all
121 // existing file descriptors accordingly.
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700122 extraFiles := make([]*os.File, len(p.c.ExtraFiles)+2)
Jiri Simsac199bc12014-05-30 12:52:24 -0700123 extraFiles[0] = dataRead
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700124 extraFiles[1] = statusWrite
125 for i, _ := range p.c.ExtraFiles {
126 extraFiles[i+2] = p.c.ExtraFiles[i]
127 }
128 p.c.ExtraFiles = extraFiles
Jiri Simsac199bc12014-05-30 12:52:24 -0700129 // Start the child process.
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700130 if err := p.c.Start(); err != nil {
131 p.statusWrite.Close()
132 p.statusRead.Close()
133 return err
134 }
Jiri Simsac199bc12014-05-30 12:52:24 -0700135 // Pass data to the child using a pipe.
Bogdan Capritaa4d9ee42014-06-20 16:42:53 -0700136 serializedConfig, err := p.config.Serialize()
137 if err != nil {
138 return err
139 }
140 if err := encodeString(dataWrite, serializedConfig); err != nil {
Jiri Simsac199bc12014-05-30 12:52:24 -0700141 p.statusWrite.Close()
142 p.statusRead.Close()
143 return err
144 }
Jiri Simsa84059da2014-06-02 17:22:05 -0700145 if err := encodeString(dataWrite, p.secret); err != nil {
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700146 p.statusWrite.Close()
147 p.statusRead.Close()
148 return err
149 }
150 return nil
151}
152
Cosmos Nicolaouee7abc22014-05-27 10:50:03 -0700153func waitForStatus(c chan string, e chan error, r *os.File) {
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700154 buf := make([]byte, 100)
155 n, err := r.Read(buf)
156 if err != nil {
157 e <- err
158 } else {
159 c <- string(buf[:n])
160 }
161 r.Close()
162 close(c)
163 close(e)
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700164}
165
166// WaitForReady will wait for the child process to become ready.
167func (p *ParentHandle) WaitForReady(timeout time.Duration) error {
168 defer p.statusWrite.Close()
169 c := make(chan string, 1)
170 e := make(chan error, 1)
Cosmos Nicolaouee7abc22014-05-27 10:50:03 -0700171 go waitForStatus(c, e, p.statusRead)
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700172 for {
173 select {
174 case err := <-e:
Cosmos Nicolaou1c18c1c2014-10-08 16:37:10 -0700175 if err != nil {
176 return err
177 }
178 // waitForStatus has closed the channel, but we may not
179 // have read the message from it yet.
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700180 case st := <-c:
181 if st == readyStatus {
182 return nil
183 }
Cosmos Nicolaou1c18c1c2014-10-08 16:37:10 -0700184 if strings.HasPrefix(st, failedStatus) {
185 return fmt.Errorf("%s", strings.TrimPrefix(st, failedStatus))
186 }
187 if len(st) > 0 {
188 return fmt.Errorf("unrecognised status from subprocess: %q", st)
189 }
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700190 case <-p.tk.After(timeout):
191 // Make sure that the read in waitForStatus
192 // returns now.
193 p.statusWrite.Write([]byte("quit"))
194 return ErrTimeout
195 }
196 }
197 panic("unreachable")
198}
199
200// Wait will wait for the child process to terminate of its own accord.
201// It returns nil if the process exited cleanly with an exit status of 0,
202// any other exit code or error will result in an appropriate error return
203func (p *ParentHandle) Wait(timeout time.Duration) error {
204 c := make(chan error, 1)
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700205 go func() {
206 c <- p.c.Wait()
207 close(c)
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700208 }()
209 // If timeout is zero time.After will panic; we handle zero specially
210 // to mean infinite timeout.
211 if timeout > 0 {
212 select {
213 case <-p.tk.After(timeout):
214 return ErrTimeout
215 case err := <-c:
216 return err
217 }
218 } else {
219 return <-c
220 }
221 panic("unreachable")
222}
223
Cosmos Nicolaouee7abc22014-05-27 10:50:03 -0700224// Pid returns the pid of the child, 0 if the child process doesn't exist
225func (p *ParentHandle) Pid() int {
226 if p.c.Process != nil {
227 return p.c.Process.Pid
228 }
229 return 0
230}
231
232// Exists returns true if the child process exists and can be signal'ed
233func (p *ParentHandle) Exists() bool {
234 if p.c.Process != nil {
235 return syscall.Kill(p.c.Process.Pid, 0) == nil
236 }
237 return false
238}
239
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700240// Kill kills the child process.
241func (p *ParentHandle) Kill() error {
242 return p.c.Process.Kill()
243}
244
245// Signal sends the given signal to the child process.
246func (p *ParentHandle) Signal(sig syscall.Signal) error {
247 return syscall.Kill(p.c.Process.Pid, sig)
248}
249
250// Clean will clean up state, including killing the child process.
251func (p *ParentHandle) Clean() error {
252 if err := p.Kill(); err != nil {
253 return err
254 }
255 return p.c.Wait()
256}
Jiri Simsac199bc12014-05-30 12:52:24 -0700257
Jiri Simsa84059da2014-06-02 17:22:05 -0700258func encodeString(w io.Writer, data string) error {
Jiri Simsac199bc12014-05-30 12:52:24 -0700259 l := len(data)
260 if err := binary.Write(w, binary.BigEndian, int64(l)); err != nil {
261 return err
262 }
263 if n, err := w.Write([]byte(data)); err != nil || n != l {
264 if err != nil {
265 return err
266 } else {
267 return errors.New("partial write")
268 }
269 }
270 return nil
271}