tableflip/child.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)
}