-
Notifications
You must be signed in to change notification settings - Fork 150
/
Copy pathchild.go
116 lines (102 loc) · 2.51 KB
/
child.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
package tableflip
import (
"encoding/gob"
"fmt"
"os"
)
type child struct {
*env
proc process
readyR, namesW *os.File
ready <-chan *os.File
result <-chan error
exited <-chan struct{}
}
func startChild(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, fmt.Errorf("pipe failed: %s", err)
}
namesR, namesW, err := os.Pipe()
if err != nil {
readyR.Close()
readyW.Close()
return nil, fmt.Errorf("pipe failed: %s", err)
}
// Copy passed fds and append the notification pipe
fds := []*os.File{os.Stdin, os.Stdout, os.Stderr, 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
sentinel := fmt.Sprintf("%s=yes", sentinelEnvVar)
var environ []string
for _, val := range env.environ() {
if val != sentinel {
environ = append(environ, val)
}
}
environ = append(environ, sentinel)
proc, err := env.newProc(os.Args[0], os.Args[1:], fds, environ)
if err != nil {
readyR.Close()
readyW.Close()
namesR.Close()
namesW.Close()
return nil, fmt.Errorf("can't start process %s: %s", os.Args[0], err)
}
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)
}