blob: 63d6c99347bb701282f679bc7af150f0f3695ccc [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
Jiri Simsa519c5072014-09-17 21:37:57 -070012 "veyron.io/veyron/veyron2/vlog"
Cosmos Nicolaou251a4d82014-09-30 22:28:45 -070013
14 "veyron.io/veyron/veyron/lib/timekeeper"
Jiri Simsa5293dcb2014-05-10 09:56:38 -070015)
16
17var (
18 ErrAuthTimeout = errors.New("timout in auth handshake")
19 ErrTimeout = errors.New("timeout waiting for child")
20 ErrSecretTooLarge = errors.New("secret is too large")
21)
22
23// A ParentHandle is the Parent process' means of managing a single child.
24type ParentHandle struct {
Cosmos Nicolaouee7abc22014-05-27 10:50:03 -070025 c *exec.Cmd
Cosmos Nicolaou486d3492014-09-30 22:21:20 -070026 config Config
Cosmos Nicolaouee7abc22014-05-27 10:50:03 -070027 secret string
28 statusRead *os.File
29 statusWrite *os.File
30 tk timekeeper.TimeKeeper
Jiri Simsa5293dcb2014-05-10 09:56:38 -070031}
32
33// ParentHandleOpt is an option for NewParentHandle.
34type ParentHandleOpt interface {
Jiri Simsac199bc12014-05-30 12:52:24 -070035 // ExecParentHandleOpt is a signature 'dummy' method for the
36 // interface.
Jiri Simsa5293dcb2014-05-10 09:56:38 -070037 ExecParentHandleOpt()
38}
39
Bogdan Capritaa4d9ee42014-06-20 16:42:53 -070040// ConfigOpt can be used to seed the parent handle with a
41// config to be passed to the child.
42type ConfigOpt struct {
Cosmos Nicolaou486d3492014-09-30 22:21:20 -070043 Config
Bogdan Capritaa4d9ee42014-06-20 16:42:53 -070044}
Jiri Simsac199bc12014-05-30 12:52:24 -070045
Bogdan Capritaa4d9ee42014-06-20 16:42:53 -070046// ExecParentHandleOpt makes ConfigOpt an instance of
Jiri Simsac199bc12014-05-30 12:52:24 -070047// ParentHandleOpt.
Bogdan Capritaa4d9ee42014-06-20 16:42:53 -070048func (ConfigOpt) ExecParentHandleOpt() {}
Jiri Simsac199bc12014-05-30 12:52:24 -070049
50// SecretOpt can be used to seed the parent handle with a custom secret.
51type SecretOpt string
52
53// ExecParentHandleOpt makes SecretOpt an instance of ParentHandleOpt.
Bogdan Capritaa4d9ee42014-06-20 16:42:53 -070054func (SecretOpt) ExecParentHandleOpt() {}
Jiri Simsac199bc12014-05-30 12:52:24 -070055
Jiri Simsa5293dcb2014-05-10 09:56:38 -070056// TimeKeeperOpt can be used to seed the parent handle with a custom timekeeper.
57type TimeKeeperOpt struct {
Cosmos Nicolaouee7abc22014-05-27 10:50:03 -070058 timekeeper.TimeKeeper
Jiri Simsa5293dcb2014-05-10 09:56:38 -070059}
60
61// ExecParentHandleOpt makes TimeKeeperOpt an instance of ParentHandleOpt.
Bogdan Capritaa4d9ee42014-06-20 16:42:53 -070062func (TimeKeeperOpt) ExecParentHandleOpt() {}
Jiri Simsa5293dcb2014-05-10 09:56:38 -070063
64// NewParentHandle creates a ParentHandle for the child process represented by
65// an instance of exec.Cmd.
Jiri Simsac199bc12014-05-30 12:52:24 -070066func NewParentHandle(c *exec.Cmd, opts ...ParentHandleOpt) *ParentHandle {
Jiri Simsa5293dcb2014-05-10 09:56:38 -070067 c.Env = append(c.Env, versionVariable+"="+version1)
Cosmos Nicolaou486d3492014-09-30 22:21:20 -070068 cfg, secret := NewConfig(), ""
Jiri Simsac199bc12014-05-30 12:52:24 -070069 tk := timekeeper.RealTime()
Jiri Simsa5293dcb2014-05-10 09:56:38 -070070 for _, opt := range opts {
71 switch v := opt.(type) {
Bogdan Capritaa4d9ee42014-06-20 16:42:53 -070072 case ConfigOpt:
73 cfg = v
Jiri Simsac199bc12014-05-30 12:52:24 -070074 case SecretOpt:
75 secret = string(v)
Jiri Simsa5293dcb2014-05-10 09:56:38 -070076 case TimeKeeperOpt:
Cosmos Nicolaouee7abc22014-05-27 10:50:03 -070077 tk = v
Jiri Simsa5293dcb2014-05-10 09:56:38 -070078 default:
79 vlog.Errorf("Unrecognized parent option: %v", v)
80 }
81 }
Jiri Simsa5293dcb2014-05-10 09:56:38 -070082 return &ParentHandle{
Jiri Simsa24e87aa2014-06-09 09:27:34 -070083 c: c,
Bogdan Capritaa4d9ee42014-06-20 16:42:53 -070084 config: cfg,
Jiri Simsa24e87aa2014-06-09 09:27:34 -070085 secret: secret,
86 tk: tk,
Jiri Simsa5293dcb2014-05-10 09:56:38 -070087 }
88}
89
90// Start starts the child process, sharing a secret with it and
91// setting up a communication channel over which to read its status.
92func (p *ParentHandle) Start() error {
Jiri Simsac199bc12014-05-30 12:52:24 -070093 // Create anonymous pipe for communicating data between the child
94 // and the parent.
95 dataRead, dataWrite, err := os.Pipe()
Jiri Simsa5293dcb2014-05-10 09:56:38 -070096 if err != nil {
97 return err
98 }
Jiri Simsac199bc12014-05-30 12:52:24 -070099 defer dataRead.Close()
100 defer dataWrite.Close()
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700101 statusRead, statusWrite, err := os.Pipe()
102 if err != nil {
103 return err
104 }
105 p.statusRead = statusRead
106 p.statusWrite = statusWrite
Jiri Simsac199bc12014-05-30 12:52:24 -0700107 // Add the parent-child pipes to cmd.ExtraFiles, offsetting all
108 // existing file descriptors accordingly.
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700109 extraFiles := make([]*os.File, len(p.c.ExtraFiles)+2)
Jiri Simsac199bc12014-05-30 12:52:24 -0700110 extraFiles[0] = dataRead
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700111 extraFiles[1] = statusWrite
112 for i, _ := range p.c.ExtraFiles {
113 extraFiles[i+2] = p.c.ExtraFiles[i]
114 }
115 p.c.ExtraFiles = extraFiles
Jiri Simsac199bc12014-05-30 12:52:24 -0700116 // Start the child process.
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700117 if err := p.c.Start(); err != nil {
118 p.statusWrite.Close()
119 p.statusRead.Close()
120 return err
121 }
Jiri Simsac199bc12014-05-30 12:52:24 -0700122 // Pass data to the child using a pipe.
Bogdan Capritaa4d9ee42014-06-20 16:42:53 -0700123 serializedConfig, err := p.config.Serialize()
124 if err != nil {
125 return err
126 }
127 if err := encodeString(dataWrite, serializedConfig); err != nil {
Jiri Simsac199bc12014-05-30 12:52:24 -0700128 p.statusWrite.Close()
129 p.statusRead.Close()
130 return err
131 }
Jiri Simsa84059da2014-06-02 17:22:05 -0700132 if err := encodeString(dataWrite, p.secret); err != nil {
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700133 p.statusWrite.Close()
134 p.statusRead.Close()
135 return err
136 }
137 return nil
138}
139
Cosmos Nicolaouee7abc22014-05-27 10:50:03 -0700140func waitForStatus(c chan string, e chan error, r *os.File) {
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700141 buf := make([]byte, 100)
142 n, err := r.Read(buf)
143 if err != nil {
144 e <- err
145 } else {
146 c <- string(buf[:n])
147 }
148 r.Close()
149 close(c)
150 close(e)
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700151}
152
153// WaitForReady will wait for the child process to become ready.
154func (p *ParentHandle) WaitForReady(timeout time.Duration) error {
155 defer p.statusWrite.Close()
156 c := make(chan string, 1)
157 e := make(chan error, 1)
Cosmos Nicolaouee7abc22014-05-27 10:50:03 -0700158 go waitForStatus(c, e, p.statusRead)
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700159 for {
160 select {
161 case err := <-e:
162 return err
163 case st := <-c:
164 if st == readyStatus {
165 return nil
166 }
167 case <-p.tk.After(timeout):
168 // Make sure that the read in waitForStatus
169 // returns now.
170 p.statusWrite.Write([]byte("quit"))
171 return ErrTimeout
172 }
173 }
174 panic("unreachable")
175}
176
177// Wait will wait for the child process to terminate of its own accord.
178// It returns nil if the process exited cleanly with an exit status of 0,
179// any other exit code or error will result in an appropriate error return
180func (p *ParentHandle) Wait(timeout time.Duration) error {
181 c := make(chan error, 1)
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700182 go func() {
183 c <- p.c.Wait()
184 close(c)
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700185 }()
186 // If timeout is zero time.After will panic; we handle zero specially
187 // to mean infinite timeout.
188 if timeout > 0 {
189 select {
190 case <-p.tk.After(timeout):
191 return ErrTimeout
192 case err := <-c:
193 return err
194 }
195 } else {
196 return <-c
197 }
198 panic("unreachable")
199}
200
Cosmos Nicolaouee7abc22014-05-27 10:50:03 -0700201// Pid returns the pid of the child, 0 if the child process doesn't exist
202func (p *ParentHandle) Pid() int {
203 if p.c.Process != nil {
204 return p.c.Process.Pid
205 }
206 return 0
207}
208
209// Exists returns true if the child process exists and can be signal'ed
210func (p *ParentHandle) Exists() bool {
211 if p.c.Process != nil {
212 return syscall.Kill(p.c.Process.Pid, 0) == nil
213 }
214 return false
215}
216
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700217// Kill kills the child process.
218func (p *ParentHandle) Kill() error {
219 return p.c.Process.Kill()
220}
221
222// Signal sends the given signal to the child process.
223func (p *ParentHandle) Signal(sig syscall.Signal) error {
224 return syscall.Kill(p.c.Process.Pid, sig)
225}
226
227// Clean will clean up state, including killing the child process.
228func (p *ParentHandle) Clean() error {
229 if err := p.Kill(); err != nil {
230 return err
231 }
232 return p.c.Wait()
233}
Jiri Simsac199bc12014-05-30 12:52:24 -0700234
Jiri Simsa84059da2014-06-02 17:22:05 -0700235func encodeString(w io.Writer, data string) error {
Jiri Simsac199bc12014-05-30 12:52:24 -0700236 l := len(data)
237 if err := binary.Write(w, binary.BigEndian, int64(l)); err != nil {
238 return err
239 }
240 if n, err := w.Write([]byte(data)); err != nil || n != l {
241 if err != nil {
242 return err
243 } else {
244 return errors.New("partial write")
245 }
246 }
247 return nil
248}