blob: 2a779245bb960972563d6526ebab135e318e9a27 [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"
Jiri Simsac199bc12014-05-30 12:52:24 -07006 "io"
Jiri Simsa5293dcb2014-05-10 09:56:38 -07007 "os"
8 "os/exec"
Jiri Simsa5293dcb2014-05-10 09:56:38 -07009 "syscall"
10 "time"
Cosmos Nicolaoubfcac5f2014-05-22 21:57:35 -070011
Cosmos Nicolaouee7abc22014-05-27 10:50:03 -070012 "veyron/lib/testutil/blackbox/parent"
13 // TODO(cnicolaou): move timekeeper out of runtimes
Jiri Simsa5293dcb2014-05-10 09:56:38 -070014 "veyron/runtimes/google/lib/timekeeper"
Cosmos Nicolaoubfcac5f2014-05-22 21:57:35 -070015
Jiri Simsa5293dcb2014-05-10 09:56:38 -070016 "veyron2/vlog"
17)
18
19var (
20 ErrAuthTimeout = errors.New("timout in auth handshake")
21 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
Jiri Simsac199bc12014-05-30 12:52:24 -070028 endpoint string
29 id string
Cosmos Nicolaouee7abc22014-05-27 10:50:03 -070030 secret string
31 statusRead *os.File
32 statusWrite *os.File
33 tk timekeeper.TimeKeeper
Jiri Simsa5293dcb2014-05-10 09:56:38 -070034}
35
36// ParentHandleOpt is an option for NewParentHandle.
37type ParentHandleOpt interface {
Jiri Simsac199bc12014-05-30 12:52:24 -070038 // ExecParentHandleOpt is a signature 'dummy' method for the
39 // interface.
Jiri Simsa5293dcb2014-05-10 09:56:38 -070040 ExecParentHandleOpt()
41}
42
Jiri Simsac199bc12014-05-30 12:52:24 -070043// CallbackEndpointOpt can be used to seed the parent handle with a
44// custom callback endpoint.
45type CallbackEndpointOpt string
46
47// ExecParentHandleOpt makes CallbackEndpointOpt an instance of
48// ParentHandleOpt.
49func (cno CallbackEndpointOpt) ExecParentHandleOpt() {}
50
51// CallbackIDOpt can be used to seed the parent handle with a
52// custom callback ID.
53type CallbackIDOpt string
54
55// ExecParentHandleOpt makes CallbackIDOpt an instance of
56// ParentHandleOpt.
57func (cno CallbackIDOpt) ExecParentHandleOpt() {}
58
59// SecretOpt can be used to seed the parent handle with a custom secret.
60type SecretOpt string
61
62// ExecParentHandleOpt makes SecretOpt an instance of ParentHandleOpt.
63func (so SecretOpt) ExecParentHandleOpt() {}
64
Jiri Simsa5293dcb2014-05-10 09:56:38 -070065// TimeKeeperOpt can be used to seed the parent handle with a custom timekeeper.
66type TimeKeeperOpt struct {
Cosmos Nicolaouee7abc22014-05-27 10:50:03 -070067 timekeeper.TimeKeeper
Jiri Simsa5293dcb2014-05-10 09:56:38 -070068}
69
70// ExecParentHandleOpt makes TimeKeeperOpt an instance of ParentHandleOpt.
71func (tko TimeKeeperOpt) ExecParentHandleOpt() {}
72
73// NewParentHandle creates a ParentHandle for the child process represented by
74// an instance of exec.Cmd.
Jiri Simsac199bc12014-05-30 12:52:24 -070075func NewParentHandle(c *exec.Cmd, opts ...ParentHandleOpt) *ParentHandle {
Jiri Simsa5293dcb2014-05-10 09:56:38 -070076 c.Env = append(c.Env, versionVariable+"="+version1)
Jiri Simsac199bc12014-05-30 12:52:24 -070077 endpoint, id, secret := emptyEndpoint, emptyID, emptySecret
78 tk := timekeeper.RealTime()
Jiri Simsa5293dcb2014-05-10 09:56:38 -070079 for _, opt := range opts {
80 switch v := opt.(type) {
Jiri Simsac199bc12014-05-30 12:52:24 -070081 case CallbackEndpointOpt:
82 endpoint = string(v)
83 case CallbackIDOpt:
84 id = string(v)
85 case SecretOpt:
86 secret = string(v)
Jiri Simsa5293dcb2014-05-10 09:56:38 -070087 case TimeKeeperOpt:
Cosmos Nicolaouee7abc22014-05-27 10:50:03 -070088 tk = v
Jiri Simsa5293dcb2014-05-10 09:56:38 -070089 default:
90 vlog.Errorf("Unrecognized parent option: %v", v)
91 }
92 }
Jiri Simsa5293dcb2014-05-10 09:56:38 -070093 return &ParentHandle{
Jiri Simsac199bc12014-05-30 12:52:24 -070094 c: c,
95 endpoint: endpoint,
96 id: id,
97 secret: secret,
98 tk: tk,
Jiri Simsa5293dcb2014-05-10 09:56:38 -070099 }
100}
101
102// Start starts the child process, sharing a secret with it and
103// setting up a communication channel over which to read its status.
104func (p *ParentHandle) Start() error {
Cosmos Nicolaouee7abc22014-05-27 10:50:03 -0700105 if parent.BlackboxTest(p.c.Env) {
106 if err := parent.InitBlackboxParent(p.c); err != nil {
107 return err
108 }
109 }
Jiri Simsac199bc12014-05-30 12:52:24 -0700110 // Create anonymous pipe for communicating data between the child
111 // and the parent.
112 dataRead, dataWrite, err := os.Pipe()
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700113 if err != nil {
114 return err
115 }
Jiri Simsac199bc12014-05-30 12:52:24 -0700116 defer dataRead.Close()
117 defer dataWrite.Close()
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700118 statusRead, statusWrite, err := os.Pipe()
119 if err != nil {
120 return err
121 }
122 p.statusRead = statusRead
123 p.statusWrite = statusWrite
Jiri Simsac199bc12014-05-30 12:52:24 -0700124 // Add the parent-child pipes to cmd.ExtraFiles, offsetting all
125 // existing file descriptors accordingly.
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700126 extraFiles := make([]*os.File, len(p.c.ExtraFiles)+2)
Jiri Simsac199bc12014-05-30 12:52:24 -0700127 extraFiles[0] = dataRead
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700128 extraFiles[1] = statusWrite
129 for i, _ := range p.c.ExtraFiles {
130 extraFiles[i+2] = p.c.ExtraFiles[i]
131 }
132 p.c.ExtraFiles = extraFiles
Jiri Simsac199bc12014-05-30 12:52:24 -0700133 // Start the child process.
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700134 if err := p.c.Start(); err != nil {
135 p.statusWrite.Close()
136 p.statusRead.Close()
137 return err
138 }
Jiri Simsac199bc12014-05-30 12:52:24 -0700139 // Pass data to the child using a pipe.
140 if err := writeData(dataWrite, p.endpoint); err != nil {
141 p.statusWrite.Close()
142 p.statusRead.Close()
143 return err
144 }
145 if err := writeData(dataWrite, p.id); err != nil {
146 p.statusWrite.Close()
147 p.statusRead.Close()
148 return err
149 }
150 if err := writeData(dataWrite, p.secret); err != nil {
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700151 p.statusWrite.Close()
152 p.statusRead.Close()
153 return err
154 }
155 return nil
156}
157
Cosmos Nicolaouee7abc22014-05-27 10:50:03 -0700158func waitForStatus(c chan string, e chan error, r *os.File) {
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700159 buf := make([]byte, 100)
160 n, err := r.Read(buf)
161 if err != nil {
162 e <- err
163 } else {
164 c <- string(buf[:n])
165 }
166 r.Close()
167 close(c)
168 close(e)
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700169}
170
171// WaitForReady will wait for the child process to become ready.
172func (p *ParentHandle) WaitForReady(timeout time.Duration) error {
173 defer p.statusWrite.Close()
174 c := make(chan string, 1)
175 e := make(chan error, 1)
Cosmos Nicolaouee7abc22014-05-27 10:50:03 -0700176 go waitForStatus(c, e, p.statusRead)
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700177 for {
178 select {
179 case err := <-e:
180 return err
181 case st := <-c:
182 if st == readyStatus {
183 return nil
184 }
185 case <-p.tk.After(timeout):
186 // Make sure that the read in waitForStatus
187 // returns now.
188 p.statusWrite.Write([]byte("quit"))
189 return ErrTimeout
190 }
191 }
192 panic("unreachable")
193}
194
195// Wait will wait for the child process to terminate of its own accord.
196// It returns nil if the process exited cleanly with an exit status of 0,
197// any other exit code or error will result in an appropriate error return
198func (p *ParentHandle) Wait(timeout time.Duration) error {
199 c := make(chan error, 1)
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700200 go func() {
201 c <- p.c.Wait()
202 close(c)
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700203 }()
204 // If timeout is zero time.After will panic; we handle zero specially
205 // to mean infinite timeout.
206 if timeout > 0 {
207 select {
208 case <-p.tk.After(timeout):
209 return ErrTimeout
210 case err := <-c:
211 return err
212 }
213 } else {
214 return <-c
215 }
216 panic("unreachable")
217}
218
Cosmos Nicolaouee7abc22014-05-27 10:50:03 -0700219// Pid returns the pid of the child, 0 if the child process doesn't exist
220func (p *ParentHandle) Pid() int {
221 if p.c.Process != nil {
222 return p.c.Process.Pid
223 }
224 return 0
225}
226
227// Exists returns true if the child process exists and can be signal'ed
228func (p *ParentHandle) Exists() bool {
229 if p.c.Process != nil {
230 return syscall.Kill(p.c.Process.Pid, 0) == nil
231 }
232 return false
233}
234
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700235// Kill kills the child process.
236func (p *ParentHandle) Kill() error {
237 return p.c.Process.Kill()
238}
239
240// Signal sends the given signal to the child process.
241func (p *ParentHandle) Signal(sig syscall.Signal) error {
242 return syscall.Kill(p.c.Process.Pid, sig)
243}
244
245// Clean will clean up state, including killing the child process.
246func (p *ParentHandle) Clean() error {
247 if err := p.Kill(); err != nil {
248 return err
249 }
250 return p.c.Wait()
251}
Jiri Simsac199bc12014-05-30 12:52:24 -0700252
253func writeData(w io.Writer, data string) error {
254 l := len(data)
255 if err := binary.Write(w, binary.BigEndian, int64(l)); err != nil {
256 return err
257 }
258 if n, err := w.Write([]byte(data)); err != nil || n != l {
259 if err != nil {
260 return err
261 } else {
262 return errors.New("partial write")
263 }
264 }
265 return nil
266}