114 lines
2.4 KiB
Go
114 lines
2.4 KiB
Go
package tableflip
|
|
|
|
import (
|
|
"encoding/gob"
|
|
"fmt"
|
|
"os"
|
|
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
type child struct {
|
|
*env
|
|
proc process
|
|
readyR, namesW *os.File
|
|
ready <-chan *os.File
|
|
result <-chan error
|
|
exited <-chan struct{}
|
|
}
|
|
|
|
func startChild(wdir string, binpath string, env *env, passedFiles map[fileName]*file) (*child, error) {
|
|
// These pipes are used for communication between parent and child
|
|
// readyW is passed to the child, readyR stays with the parent
|
|
readyR, readyW, err := os.Pipe()
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "pipe failed")
|
|
}
|
|
|
|
namesR, namesW, err := os.Pipe()
|
|
if err != nil {
|
|
readyR.Close()
|
|
readyW.Close()
|
|
return nil, errors.Wrap(err, "pipe failed")
|
|
}
|
|
|
|
// Copy passed fds and append the notification pipe
|
|
fds := []*os.File{readyW, namesR}
|
|
var fdNames [][]string
|
|
for name, file := range passedFiles {
|
|
nameSlice := make([]string, len(name))
|
|
copy(nameSlice, name[:])
|
|
fdNames = append(fdNames, nameSlice)
|
|
fds = append(fds, file.File)
|
|
}
|
|
|
|
// Copy environment and append the notification env vars
|
|
environ := append([]string(nil), env.environ()...)
|
|
environ = append(environ,
|
|
fmt.Sprintf("%s=yes", sentinelEnvVar))
|
|
|
|
proc, err := env.newProc(wdir, binpath, os.Args[1:], fds, environ)
|
|
if err != nil {
|
|
readyR.Close()
|
|
readyW.Close()
|
|
namesR.Close()
|
|
namesW.Close()
|
|
return nil, errors.Wrapf(err, "can't start process %s", binpath)
|
|
}
|
|
|
|
exited := make(chan struct{})
|
|
result := make(chan error, 1)
|
|
ready := make(chan *os.File, 1)
|
|
|
|
c := &child{
|
|
env,
|
|
proc,
|
|
readyR,
|
|
namesW,
|
|
ready,
|
|
result,
|
|
exited,
|
|
}
|
|
go c.writeNames(fdNames)
|
|
go c.waitExit(result, exited)
|
|
go c.waitReady(ready)
|
|
return c, nil
|
|
}
|
|
|
|
func (c *child) String() string {
|
|
return c.proc.String()
|
|
}
|
|
|
|
func (c *child) Kill() {
|
|
c.proc.Signal(os.Kill)
|
|
}
|
|
|
|
func (c *child) waitExit(result chan<- error, exited chan<- struct{}) {
|
|
result <- c.proc.Wait()
|
|
close(exited)
|
|
// Unblock waitReady and writeNames
|
|
c.readyR.Close()
|
|
c.namesW.Close()
|
|
}
|
|
|
|
func (c *child) waitReady(ready chan<- *os.File) {
|
|
var b [1]byte
|
|
if n, _ := c.readyR.Read(b[:]); n > 0 && b[0] == notifyReady {
|
|
// We know that writeNames has exited by this point.
|
|
// Closing the FD now signals to the child that the parent
|
|
// has exited.
|
|
ready <- c.namesW
|
|
}
|
|
c.readyR.Close()
|
|
}
|
|
|
|
func (c *child) writeNames(names [][]string) {
|
|
enc := gob.NewEncoder(c.namesW)
|
|
if names == nil {
|
|
// Gob panics on nil
|
|
_ = enc.Encode([][]string{})
|
|
return
|
|
}
|
|
_ = enc.Encode(names)
|
|
}
|