Jiri Simsa | d7616c9 | 2015-03-24 23:44:30 -0700 | [diff] [blame] | 1 | // Copyright 2015 The Vanadium Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style |
| 3 | // license that can be found in the LICENSE file. |
| 4 | |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 5 | package exec |
| 6 | |
| 7 | import ( |
Bogdan Caprita | 66ca353 | 2015-02-05 21:08:10 -0800 | [diff] [blame] | 8 | "bytes" |
Jiri Simsa | c199bc1 | 2014-05-30 12:52:24 -0700 | [diff] [blame] | 9 | "encoding/binary" |
Cosmos Nicolaou | 1c18c1c | 2014-10-08 16:37:10 -0700 | [diff] [blame] | 10 | "fmt" |
Jiri Simsa | c199bc1 | 2014-05-30 12:52:24 -0700 | [diff] [blame] | 11 | "io" |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 12 | "os" |
| 13 | "os/exec" |
Robert Kroeger | b5d6bda | 2015-01-30 16:10:49 -0800 | [diff] [blame] | 14 | "strconv" |
Cosmos Nicolaou | 1c18c1c | 2014-10-08 16:37:10 -0700 | [diff] [blame] | 15 | "strings" |
Bogdan Caprita | 650b162 | 2014-11-21 15:11:05 -0800 | [diff] [blame] | 16 | "sync" |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 17 | "syscall" |
| 18 | "time" |
Cosmos Nicolaou | bfcac5f | 2014-05-22 21:57:35 -0700 | [diff] [blame] | 19 | |
Mike Burrows | ccca2f4 | 2015-03-27 13:57:29 -0700 | [diff] [blame] | 20 | "v.io/v23/verror" |
Todd Wang | 3bb46f5 | 2015-05-14 22:01:18 -0700 | [diff] [blame] | 21 | "v.io/x/lib/envvar" |
Jiri Simsa | 337af23 | 2015-02-27 14:36:46 -0800 | [diff] [blame] | 22 | "v.io/x/lib/vlog" |
Jiri Simsa | ffceefa | 2015-02-28 11:03:34 -0800 | [diff] [blame] | 23 | "v.io/x/ref/lib/timekeeper" |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 24 | ) |
| 25 | |
Mike Burrows | ccca2f4 | 2015-03-27 13:57:29 -0700 | [diff] [blame] | 26 | const pkgPath = "v.io/x/ref/lib/exec" |
| 27 | |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 28 | var ( |
Mike Burrows | ccca2f4 | 2015-03-27 13:57:29 -0700 | [diff] [blame] | 29 | ErrAuthTimeout = verror.Register(pkgPath+".ErrAuthTimeout", verror.NoRetry, "{1:}{2:} timeout in auth handshake{:_}") |
| 30 | ErrTimeout = verror.Register(pkgPath+".ErrTimeout", verror.NoRetry, "{1:}{2:} timeout waiting for child{:_}") |
| 31 | ErrSecretTooLarge = verror.Register(pkgPath+".ErrSecretTooLarge", verror.NoRetry, "{1:}{2:} secret is too large{:_}") |
| 32 | ErrNotUsingProtocol = verror.Register(pkgPath+".ErrNotUsingProtocol", verror.NoRetry, "{1:}{2:} not using parent/child exec protocol{:_}") |
| 33 | |
| 34 | errFailedStatus = verror.Register(pkgPath+".errFailedStatus", verror.NoRetry, "{1:}{2:} {_}") |
| 35 | errUnrecognizedStatus = verror.Register(pkgPath+".errUnrecognizedStatus", verror.NoRetry, "{1:}{2:} unrecognised status from subprocess{:_}") |
| 36 | errUnexpectedType = verror.Register(pkgPath+".errUnexpectedType", verror.NoRetry, "{1:}{2:} unexpected type {3}{:_}") |
| 37 | errNoSuchProcess = verror.Register(pkgPath+".errNoSuchProcess", verror.NoRetry, "{1:}{2:} no such process{:_}") |
| 38 | errPartialWrite = verror.Register(pkgPath+".errPartialWrite", verror.NoRetry, "{1:}{2:} partial write{:_}") |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 39 | ) |
| 40 | |
| 41 | // A ParentHandle is the Parent process' means of managing a single child. |
| 42 | type ParentHandle struct { |
Cosmos Nicolaou | ee7abc2 | 2014-05-27 10:50:03 -0700 | [diff] [blame] | 43 | c *exec.Cmd |
Cosmos Nicolaou | 486d349 | 2014-09-30 22:21:20 -0700 | [diff] [blame] | 44 | config Config |
Cosmos Nicolaou | b6cd396 | 2015-03-08 22:57:03 -0700 | [diff] [blame] | 45 | protocol bool // true if we're using the parent/child protocol. |
Cosmos Nicolaou | ee7abc2 | 2014-05-27 10:50:03 -0700 | [diff] [blame] | 46 | secret string |
| 47 | statusRead *os.File |
| 48 | statusWrite *os.File |
| 49 | tk timekeeper.TimeKeeper |
Bogdan Caprita | 650b162 | 2014-11-21 15:11:05 -0800 | [diff] [blame] | 50 | waitDone bool |
| 51 | waitErr error |
| 52 | waitLock sync.Mutex |
Robert Kroeger | b5d6bda | 2015-01-30 16:10:49 -0800 | [diff] [blame] | 53 | callbackPid int |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 54 | } |
| 55 | |
| 56 | // ParentHandleOpt is an option for NewParentHandle. |
| 57 | type ParentHandleOpt interface { |
Jiri Simsa | c199bc1 | 2014-05-30 12:52:24 -0700 | [diff] [blame] | 58 | // ExecParentHandleOpt is a signature 'dummy' method for the |
| 59 | // interface. |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 60 | ExecParentHandleOpt() |
| 61 | } |
| 62 | |
Bogdan Caprita | a4d9ee4 | 2014-06-20 16:42:53 -0700 | [diff] [blame] | 63 | // ConfigOpt can be used to seed the parent handle with a |
| 64 | // config to be passed to the child. |
| 65 | type ConfigOpt struct { |
Cosmos Nicolaou | 486d349 | 2014-09-30 22:21:20 -0700 | [diff] [blame] | 66 | Config |
Bogdan Caprita | a4d9ee4 | 2014-06-20 16:42:53 -0700 | [diff] [blame] | 67 | } |
Jiri Simsa | c199bc1 | 2014-05-30 12:52:24 -0700 | [diff] [blame] | 68 | |
Bogdan Caprita | a4d9ee4 | 2014-06-20 16:42:53 -0700 | [diff] [blame] | 69 | // ExecParentHandleOpt makes ConfigOpt an instance of |
Jiri Simsa | c199bc1 | 2014-05-30 12:52:24 -0700 | [diff] [blame] | 70 | // ParentHandleOpt. |
Bogdan Caprita | a4d9ee4 | 2014-06-20 16:42:53 -0700 | [diff] [blame] | 71 | func (ConfigOpt) ExecParentHandleOpt() {} |
Jiri Simsa | c199bc1 | 2014-05-30 12:52:24 -0700 | [diff] [blame] | 72 | |
| 73 | // SecretOpt can be used to seed the parent handle with a custom secret. |
| 74 | type SecretOpt string |
| 75 | |
| 76 | // ExecParentHandleOpt makes SecretOpt an instance of ParentHandleOpt. |
Bogdan Caprita | a4d9ee4 | 2014-06-20 16:42:53 -0700 | [diff] [blame] | 77 | func (SecretOpt) ExecParentHandleOpt() {} |
Jiri Simsa | c199bc1 | 2014-05-30 12:52:24 -0700 | [diff] [blame] | 78 | |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 79 | // TimeKeeperOpt can be used to seed the parent handle with a custom timekeeper. |
| 80 | type TimeKeeperOpt struct { |
Cosmos Nicolaou | ee7abc2 | 2014-05-27 10:50:03 -0700 | [diff] [blame] | 81 | timekeeper.TimeKeeper |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 82 | } |
| 83 | |
| 84 | // ExecParentHandleOpt makes TimeKeeperOpt an instance of ParentHandleOpt. |
Bogdan Caprita | a4d9ee4 | 2014-06-20 16:42:53 -0700 | [diff] [blame] | 85 | func (TimeKeeperOpt) ExecParentHandleOpt() {} |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 86 | |
Cosmos Nicolaou | b6cd396 | 2015-03-08 22:57:03 -0700 | [diff] [blame] | 87 | // UseExecProtocolOpt can be used to control whether parent/child handshake |
| 88 | // protocol is used. WaitForReady will return immediately with an error if |
Cosmos Nicolaou | 52e9a22 | 2015-03-16 21:57:16 -0700 | [diff] [blame] | 89 | // this option is set to false. The defaults behaviour is to assume that |
| 90 | // the exec protocol is used. If it is not used, then the Start method |
| 91 | // will not create shared file descriptors to use for the exec protocol. |
Cosmos Nicolaou | b6cd396 | 2015-03-08 22:57:03 -0700 | [diff] [blame] | 92 | type UseExecProtocolOpt bool |
| 93 | |
| 94 | func (UseExecProtocolOpt) ExecParentHandleOpt() {} |
| 95 | |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 96 | // NewParentHandle creates a ParentHandle for the child process represented by |
| 97 | // an instance of exec.Cmd. |
Jiri Simsa | c199bc1 | 2014-05-30 12:52:24 -0700 | [diff] [blame] | 98 | func NewParentHandle(c *exec.Cmd, opts ...ParentHandleOpt) *ParentHandle { |
Cosmos Nicolaou | 486d349 | 2014-09-30 22:21:20 -0700 | [diff] [blame] | 99 | cfg, secret := NewConfig(), "" |
Jiri Simsa | c199bc1 | 2014-05-30 12:52:24 -0700 | [diff] [blame] | 100 | tk := timekeeper.RealTime() |
Cosmos Nicolaou | b6cd396 | 2015-03-08 22:57:03 -0700 | [diff] [blame] | 101 | protocol := true |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 102 | for _, opt := range opts { |
| 103 | switch v := opt.(type) { |
Bogdan Caprita | a4d9ee4 | 2014-06-20 16:42:53 -0700 | [diff] [blame] | 104 | case ConfigOpt: |
| 105 | cfg = v |
Jiri Simsa | c199bc1 | 2014-05-30 12:52:24 -0700 | [diff] [blame] | 106 | case SecretOpt: |
| 107 | secret = string(v) |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 108 | case TimeKeeperOpt: |
Cosmos Nicolaou | ee7abc2 | 2014-05-27 10:50:03 -0700 | [diff] [blame] | 109 | tk = v |
Cosmos Nicolaou | b6cd396 | 2015-03-08 22:57:03 -0700 | [diff] [blame] | 110 | case UseExecProtocolOpt: |
| 111 | protocol = bool(v) |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 112 | default: |
| 113 | vlog.Errorf("Unrecognized parent option: %v", v) |
| 114 | } |
| 115 | } |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 116 | return &ParentHandle{ |
Cosmos Nicolaou | b6cd396 | 2015-03-08 22:57:03 -0700 | [diff] [blame] | 117 | c: c, |
| 118 | protocol: protocol, |
| 119 | config: cfg, |
| 120 | secret: secret, |
| 121 | tk: tk, |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 122 | } |
| 123 | } |
| 124 | |
| 125 | // Start starts the child process, sharing a secret with it and |
| 126 | // setting up a communication channel over which to read its status. |
| 127 | func (p *ParentHandle) Start() error { |
Todd Wang | 3bb46f5 | 2015-05-14 22:01:18 -0700 | [diff] [blame] | 128 | env := envvar.SliceToMap(p.c.Env) |
Cosmos Nicolaou | b6cd396 | 2015-03-08 22:57:03 -0700 | [diff] [blame] | 129 | if !p.protocol { |
Todd Wang | 3bb46f5 | 2015-05-14 22:01:18 -0700 | [diff] [blame] | 130 | // Ensure ExecVersionVariable is not set if we're not using the protocol. |
| 131 | delete(env, ExecVersionVariable) |
| 132 | p.c.Env = envvar.MapToSlice(env) |
Cosmos Nicolaou | b6cd396 | 2015-03-08 22:57:03 -0700 | [diff] [blame] | 133 | return p.c.Start() |
| 134 | } |
Todd Wang | 3bb46f5 | 2015-05-14 22:01:18 -0700 | [diff] [blame] | 135 | // Ensure ExecVersionVariable is always set if we are using the protocol. |
| 136 | env[ExecVersionVariable] = version1 |
| 137 | p.c.Env = envvar.MapToSlice(env) |
Cosmos Nicolaou | e5b4150 | 2014-10-29 22:55:09 -0700 | [diff] [blame] | 138 | |
Jiri Simsa | c199bc1 | 2014-05-30 12:52:24 -0700 | [diff] [blame] | 139 | // Create anonymous pipe for communicating data between the child |
| 140 | // and the parent. |
Bogdan Caprita | 7f49167 | 2014-11-13 14:51:08 -0800 | [diff] [blame] | 141 | // TODO(caprita): As per ribrdb@, Go's exec does not prune the set |
| 142 | // of file descriptors passed down to the child process, and hence |
| 143 | // a child may get access to the files meant for another child. |
| 144 | // Do we need to ensure only one thread is allowed to create these |
| 145 | // pipes at any time? |
Jiri Simsa | c199bc1 | 2014-05-30 12:52:24 -0700 | [diff] [blame] | 146 | dataRead, dataWrite, err := os.Pipe() |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 147 | if err != nil { |
| 148 | return err |
| 149 | } |
Jiri Simsa | c199bc1 | 2014-05-30 12:52:24 -0700 | [diff] [blame] | 150 | defer dataRead.Close() |
| 151 | defer dataWrite.Close() |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 152 | statusRead, statusWrite, err := os.Pipe() |
| 153 | if err != nil { |
| 154 | return err |
| 155 | } |
| 156 | p.statusRead = statusRead |
| 157 | p.statusWrite = statusWrite |
Jiri Simsa | c199bc1 | 2014-05-30 12:52:24 -0700 | [diff] [blame] | 158 | // Add the parent-child pipes to cmd.ExtraFiles, offsetting all |
| 159 | // existing file descriptors accordingly. |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 160 | extraFiles := make([]*os.File, len(p.c.ExtraFiles)+2) |
Jiri Simsa | c199bc1 | 2014-05-30 12:52:24 -0700 | [diff] [blame] | 161 | extraFiles[0] = dataRead |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 162 | extraFiles[1] = statusWrite |
| 163 | for i, _ := range p.c.ExtraFiles { |
| 164 | extraFiles[i+2] = p.c.ExtraFiles[i] |
| 165 | } |
| 166 | p.c.ExtraFiles = extraFiles |
Jiri Simsa | c199bc1 | 2014-05-30 12:52:24 -0700 | [diff] [blame] | 167 | // Start the child process. |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 168 | if err := p.c.Start(); err != nil { |
| 169 | p.statusWrite.Close() |
| 170 | p.statusRead.Close() |
| 171 | return err |
| 172 | } |
Jiri Simsa | c199bc1 | 2014-05-30 12:52:24 -0700 | [diff] [blame] | 173 | // Pass data to the child using a pipe. |
Bogdan Caprita | a4d9ee4 | 2014-06-20 16:42:53 -0700 | [diff] [blame] | 174 | serializedConfig, err := p.config.Serialize() |
| 175 | if err != nil { |
| 176 | return err |
| 177 | } |
| 178 | if err := encodeString(dataWrite, serializedConfig); err != nil { |
Jiri Simsa | c199bc1 | 2014-05-30 12:52:24 -0700 | [diff] [blame] | 179 | p.statusWrite.Close() |
| 180 | p.statusRead.Close() |
| 181 | return err |
| 182 | } |
Jiri Simsa | 84059da | 2014-06-02 17:22:05 -0700 | [diff] [blame] | 183 | if err := encodeString(dataWrite, p.secret); err != nil { |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 184 | p.statusWrite.Close() |
| 185 | p.statusRead.Close() |
| 186 | return err |
| 187 | } |
| 188 | return nil |
| 189 | } |
| 190 | |
Bogdan Caprita | 66ca353 | 2015-02-05 21:08:10 -0800 | [diff] [blame] | 191 | // copy is like io.Copy, but it also treats the receipt of the special eofChar |
| 192 | // byte to mean io.EOF. |
| 193 | func copy(w io.Writer, r io.Reader) (err error) { |
| 194 | buf := make([]byte, 1024) |
| 195 | for { |
| 196 | nRead, errRead := r.Read(buf) |
| 197 | if nRead > 0 { |
| 198 | if eofCharIndex := bytes.IndexByte(buf[:nRead], eofChar); eofCharIndex != -1 { |
| 199 | nRead = eofCharIndex |
| 200 | errRead = io.EOF |
| 201 | } |
| 202 | nWrite, errWrite := w.Write(buf[:nRead]) |
| 203 | if errWrite != nil { |
| 204 | err = errWrite |
| 205 | break |
| 206 | } |
| 207 | if nRead != nWrite { |
| 208 | err = io.ErrShortWrite |
| 209 | break |
| 210 | } |
| 211 | } |
| 212 | if errRead == io.EOF { |
| 213 | break |
| 214 | } |
| 215 | if errRead != nil { |
| 216 | err = errRead |
| 217 | break |
| 218 | } |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 219 | } |
Bogdan Caprita | 66ca353 | 2015-02-05 21:08:10 -0800 | [diff] [blame] | 220 | return |
| 221 | } |
| 222 | |
| 223 | func waitForStatus(c chan interface{}, r *os.File) { |
| 224 | var readBytes bytes.Buffer |
| 225 | err := copy(&readBytes, r) |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 226 | r.Close() |
Bogdan Caprita | 66ca353 | 2015-02-05 21:08:10 -0800 | [diff] [blame] | 227 | if err != nil { |
| 228 | c <- err |
| 229 | } else { |
| 230 | c <- readBytes.String() |
| 231 | } |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 232 | close(c) |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 233 | } |
| 234 | |
| 235 | // WaitForReady will wait for the child process to become ready. |
| 236 | func (p *ParentHandle) WaitForReady(timeout time.Duration) error { |
Cosmos Nicolaou | b6cd396 | 2015-03-08 22:57:03 -0700 | [diff] [blame] | 237 | if !p.protocol { |
Mike Burrows | ccca2f4 | 2015-03-27 13:57:29 -0700 | [diff] [blame] | 238 | return verror.New(ErrNotUsingProtocol, nil) |
Cosmos Nicolaou | b6cd396 | 2015-03-08 22:57:03 -0700 | [diff] [blame] | 239 | } |
Bogdan Caprita | 66ca353 | 2015-02-05 21:08:10 -0800 | [diff] [blame] | 240 | // An invariant of WaitForReady is that both statusWrite and statusRead |
| 241 | // get closed before WaitForStatus returns (statusRead gets closed by |
| 242 | // waitForStatus). |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 243 | defer p.statusWrite.Close() |
Bogdan Caprita | 66ca353 | 2015-02-05 21:08:10 -0800 | [diff] [blame] | 244 | c := make(chan interface{}, 1) |
| 245 | go waitForStatus(c, p.statusRead) |
| 246 | // TODO(caprita): This can be simplified further by doing the reading |
| 247 | // from the status pipe here, and instead moving the timeout listener to |
| 248 | // a separate goroutine. |
| 249 | select { |
| 250 | case msg := <-c: |
| 251 | switch m := msg.(type) { |
| 252 | case error: |
| 253 | return m |
| 254 | case string: |
| 255 | if strings.HasPrefix(m, readyStatus) { |
| 256 | pid, err := strconv.Atoi(m[len(readyStatus):]) |
Robert Kroeger | b5d6bda | 2015-01-30 16:10:49 -0800 | [diff] [blame] | 257 | if err != nil { |
| 258 | return err |
| 259 | } |
| 260 | p.callbackPid = pid |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 261 | return nil |
| 262 | } |
Bogdan Caprita | 66ca353 | 2015-02-05 21:08:10 -0800 | [diff] [blame] | 263 | if strings.HasPrefix(m, failedStatus) { |
Mike Burrows | ccca2f4 | 2015-03-27 13:57:29 -0700 | [diff] [blame] | 264 | return verror.New(errFailedStatus, nil, strings.TrimPrefix(m, failedStatus)) |
Cosmos Nicolaou | 1c18c1c | 2014-10-08 16:37:10 -0700 | [diff] [blame] | 265 | } |
Mike Burrows | ccca2f4 | 2015-03-27 13:57:29 -0700 | [diff] [blame] | 266 | return verror.New(errUnrecognizedStatus, nil, m) |
Bogdan Caprita | 66ca353 | 2015-02-05 21:08:10 -0800 | [diff] [blame] | 267 | default: |
Mike Burrows | ccca2f4 | 2015-03-27 13:57:29 -0700 | [diff] [blame] | 268 | return verror.New(errUnexpectedType, nil, fmt.Sprintf("%T", m)) |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 269 | } |
Bogdan Caprita | 66ca353 | 2015-02-05 21:08:10 -0800 | [diff] [blame] | 270 | case <-p.tk.After(timeout): |
| 271 | vlog.Errorf("Timed out waiting for child status") |
| 272 | // By writing the special eofChar byte to the pipe, we ensure |
| 273 | // that waitForStatus returns: the copy function treats eofChar |
| 274 | // to indicate end of read input. Note, copy could have |
| 275 | // finished for other reasons already (receipt of eofChar from |
| 276 | // the child process). Note, closing the pipe from the child |
| 277 | // (explicitly or due to crash) would NOT cause copy to read |
| 278 | // io.EOF, since we keep the statusWrite open in the parent. |
| 279 | // Hence, a child crash will eventually trigger this timeout. |
| 280 | p.statusWrite.Write([]byte{eofChar}) |
| 281 | // Before returning, waitForStatus will close r, and then close |
| 282 | // c. Waiting on c ensures that r.Close() in waitForStatus |
| 283 | // already executed. |
| 284 | <-c |
Mike Burrows | ccca2f4 | 2015-03-27 13:57:29 -0700 | [diff] [blame] | 285 | return verror.New(ErrTimeout, nil) |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 286 | } |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 287 | } |
| 288 | |
Bogdan Caprita | 650b162 | 2014-11-21 15:11:05 -0800 | [diff] [blame] | 289 | // wait performs the Wait on the underlying command under lock, and only once |
| 290 | // (subsequent wait calls block until the Wait is finished). It's ok to call |
| 291 | // wait multiple times, and in parallel. The error from the initial Wait is |
| 292 | // cached and returned for all subsequent calls. |
| 293 | func (p *ParentHandle) wait() error { |
| 294 | p.waitLock.Lock() |
| 295 | defer p.waitLock.Unlock() |
| 296 | if p.waitDone { |
| 297 | return p.waitErr |
| 298 | } |
| 299 | p.waitErr = p.c.Wait() |
| 300 | p.waitDone = true |
| 301 | return p.waitErr |
| 302 | } |
| 303 | |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 304 | // Wait will wait for the child process to terminate of its own accord. |
| 305 | // It returns nil if the process exited cleanly with an exit status of 0, |
| 306 | // any other exit code or error will result in an appropriate error return |
| 307 | func (p *ParentHandle) Wait(timeout time.Duration) error { |
| 308 | c := make(chan error, 1) |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 309 | go func() { |
Bogdan Caprita | 650b162 | 2014-11-21 15:11:05 -0800 | [diff] [blame] | 310 | c <- p.wait() |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 311 | close(c) |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 312 | }() |
| 313 | // If timeout is zero time.After will panic; we handle zero specially |
| 314 | // to mean infinite timeout. |
| 315 | if timeout > 0 { |
| 316 | select { |
| 317 | case <-p.tk.After(timeout): |
Mike Burrows | ccca2f4 | 2015-03-27 13:57:29 -0700 | [diff] [blame] | 318 | return verror.New(ErrTimeout, nil) |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 319 | case err := <-c: |
| 320 | return err |
| 321 | } |
| 322 | } else { |
| 323 | return <-c |
| 324 | } |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 325 | } |
| 326 | |
Cosmos Nicolaou | ee7abc2 | 2014-05-27 10:50:03 -0700 | [diff] [blame] | 327 | // Pid returns the pid of the child, 0 if the child process doesn't exist |
| 328 | func (p *ParentHandle) Pid() int { |
| 329 | if p.c.Process != nil { |
| 330 | return p.c.Process.Pid |
| 331 | } |
| 332 | return 0 |
| 333 | } |
| 334 | |
Robert Kroeger | b5d6bda | 2015-01-30 16:10:49 -0800 | [diff] [blame] | 335 | // ChildPid returns the pid of a child process as reported by its status |
| 336 | // callback. |
| 337 | func (p *ParentHandle) ChildPid() int { |
| 338 | return p.callbackPid |
| 339 | } |
| 340 | |
Cosmos Nicolaou | ee7abc2 | 2014-05-27 10:50:03 -0700 | [diff] [blame] | 341 | // Exists returns true if the child process exists and can be signal'ed |
| 342 | func (p *ParentHandle) Exists() bool { |
| 343 | if p.c.Process != nil { |
| 344 | return syscall.Kill(p.c.Process.Pid, 0) == nil |
| 345 | } |
| 346 | return false |
| 347 | } |
| 348 | |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 349 | // Kill kills the child process. |
| 350 | func (p *ParentHandle) Kill() error { |
Robin Thellend | 4490352 | 2015-02-06 12:55:26 -0800 | [diff] [blame] | 351 | if p.c.Process == nil { |
Mike Burrows | ccca2f4 | 2015-03-27 13:57:29 -0700 | [diff] [blame] | 352 | return verror.New(errNoSuchProcess, nil) |
Robin Thellend | 4490352 | 2015-02-06 12:55:26 -0800 | [diff] [blame] | 353 | } |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 354 | return p.c.Process.Kill() |
| 355 | } |
| 356 | |
| 357 | // Signal sends the given signal to the child process. |
| 358 | func (p *ParentHandle) Signal(sig syscall.Signal) error { |
Robin Thellend | 4490352 | 2015-02-06 12:55:26 -0800 | [diff] [blame] | 359 | if p.c.Process == nil { |
Mike Burrows | ccca2f4 | 2015-03-27 13:57:29 -0700 | [diff] [blame] | 360 | return verror.New(errNoSuchProcess, nil) |
Robin Thellend | 4490352 | 2015-02-06 12:55:26 -0800 | [diff] [blame] | 361 | } |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 362 | return syscall.Kill(p.c.Process.Pid, sig) |
| 363 | } |
| 364 | |
| 365 | // Clean will clean up state, including killing the child process. |
| 366 | func (p *ParentHandle) Clean() error { |
| 367 | if err := p.Kill(); err != nil { |
| 368 | return err |
| 369 | } |
Bogdan Caprita | 650b162 | 2014-11-21 15:11:05 -0800 | [diff] [blame] | 370 | return p.wait() |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 371 | } |
Jiri Simsa | c199bc1 | 2014-05-30 12:52:24 -0700 | [diff] [blame] | 372 | |
Jiri Simsa | 84059da | 2014-06-02 17:22:05 -0700 | [diff] [blame] | 373 | func encodeString(w io.Writer, data string) error { |
Jiri Simsa | c199bc1 | 2014-05-30 12:52:24 -0700 | [diff] [blame] | 374 | l := len(data) |
| 375 | if err := binary.Write(w, binary.BigEndian, int64(l)); err != nil { |
| 376 | return err |
| 377 | } |
| 378 | if n, err := w.Write([]byte(data)); err != nil || n != l { |
| 379 | if err != nil { |
| 380 | return err |
| 381 | } else { |
Mike Burrows | ccca2f4 | 2015-03-27 13:57:29 -0700 | [diff] [blame] | 382 | return verror.New(errPartialWrite, nil) |
Jiri Simsa | c199bc1 | 2014-05-30 12:52:24 -0700 | [diff] [blame] | 383 | } |
| 384 | } |
| 385 | return nil |
| 386 | } |