github.com/cloudfoundry-attic/garden-linux@v0.333.2-candidate/process_tracker/process.go (about) 1 package process_tracker 2 3 import ( 4 "bufio" 5 "errors" 6 "fmt" 7 "io/ioutil" 8 "os" 9 "os/exec" 10 "path" 11 "path/filepath" 12 "strings" 13 "sync" 14 "syscall" 15 16 "github.com/cloudfoundry-incubator/garden" 17 "github.com/cloudfoundry/gunk/command_runner" 18 "github.com/pivotal-golang/lager" 19 20 "github.com/cloudfoundry-incubator/garden-linux/iodaemon/link" 21 "github.com/cloudfoundry-incubator/garden-linux/process_tracker/writer" 22 ) 23 24 //go:generate counterfeiter -o fake_signaller/fake_signaller.go . Signaller 25 type Signaller interface { 26 Signal(*SignalRequest) error 27 } 28 29 //go:generate counterfeiter -o fake_msg_sender/fake_msg_sender.go . MsgSender 30 type MsgSender interface { 31 SendMsg(msg []byte) error 32 } 33 34 type SignalRequest struct { 35 Pid string 36 Signal syscall.Signal 37 Link MsgSender 38 } 39 40 type Process struct { 41 logger lager.Logger 42 43 id string 44 45 containerPath string 46 runner command_runner.CommandRunner 47 48 runningLink *sync.Once 49 linked chan struct{} 50 link *link.Link 51 52 exited chan struct{} 53 exitStatus int 54 exitErr error 55 56 stdin writer.FanIn 57 stdout writer.FanOut 58 stderr writer.FanOut 59 60 signaller Signaller 61 } 62 63 func NewProcess( 64 logger lager.Logger, 65 id string, 66 containerPath string, 67 runner command_runner.CommandRunner, 68 signaller Signaller, 69 ) *Process { 70 return &Process{ 71 logger: logger, 72 73 id: id, 74 75 containerPath: containerPath, 76 runner: runner, 77 78 runningLink: &sync.Once{}, 79 80 linked: make(chan struct{}), 81 82 exited: make(chan struct{}), 83 84 stdin: writer.NewFanIn(), 85 stdout: writer.NewFanOut(), 86 stderr: writer.NewFanOut(), 87 signaller: signaller, 88 } 89 } 90 91 func (p *Process) ID() string { 92 return p.id 93 } 94 95 func (p *Process) Wait() (int, error) { 96 <-p.exited 97 return p.exitStatus, p.exitErr 98 } 99 100 func (p *Process) SetTTY(tty garden.TTYSpec) error { 101 <-p.linked 102 103 if tty.WindowSize != nil { 104 return p.link.SetWindowSize(tty.WindowSize.Columns, tty.WindowSize.Rows) 105 } 106 107 return nil 108 } 109 110 func (p *Process) Signal(signal garden.Signal) error { 111 <-p.linked 112 113 request := &SignalRequest{Pid: p.id, Link: p.link} 114 115 switch signal { 116 case garden.SignalKill: 117 request.Signal = syscall.SIGKILL 118 case garden.SignalTerminate: 119 request.Signal = syscall.SIGTERM 120 default: 121 return fmt.Errorf("process_tracker: failed to send signal: unknown signal: %d", signal) 122 } 123 124 return p.signaller.Signal(request) 125 } 126 127 func (p *Process) Spawn(cmd *exec.Cmd, tty *garden.TTYSpec) (ready, active chan error) { 128 ready = make(chan error, 1) 129 active = make(chan error, 1) 130 131 spawnPath := path.Join(p.containerPath, "bin", "iodaemon") 132 processSock := path.Join(p.containerPath, "processes", fmt.Sprintf("%s.sock", p.ID())) 133 straceOutput := path.Join(p.containerPath, "processes", fmt.Sprintf("%s.strace", p.ID())) 134 135 os.MkdirAll(filepath.Dir(processSock), 0755) 136 137 flags := []string{ 138 "-f", 139 "-tt", 140 "-T", 141 "-o", 142 straceOutput, 143 spawnPath, 144 } 145 146 if tty != nil { 147 flags = append(flags, "-tty") 148 149 if tty.WindowSize != nil { 150 flags = append( 151 flags, 152 fmt.Sprintf("-windowColumns=%d", tty.WindowSize.Columns), 153 fmt.Sprintf("-windowRows=%d", tty.WindowSize.Rows), 154 ) 155 } 156 } 157 158 flags = append(flags, "spawn", processSock) 159 160 spawn := exec.Command("strace", append(flags, cmd.Args...)...) 161 spawn.Env = cmd.Env 162 163 spawnR, err := spawn.StdoutPipe() 164 if err != nil { 165 ready <- err 166 return 167 } 168 spawnOut := bufio.NewReader(spawnR) 169 170 spawnErrR, err := spawn.StderrPipe() 171 if err != nil { 172 ready <- err 173 return 174 } 175 spawnErr := bufio.NewReader(spawnErrR) 176 177 err = p.runner.Start(spawn) 178 if err != nil { 179 ready <- err 180 return 181 } 182 183 go func() { 184 waitFor := func(expectedLog string) error { 185 p.logger.Info("waiting for " + expectedLog) 186 log, err := spawnOut.ReadBytes('\n') 187 if err != nil { 188 stderrContents, readErr := ioutil.ReadAll(spawnErr) 189 cmdErr := spawn.Wait() 190 if readErr != nil { 191 p.logger.Error("errored waiting for "+expectedLog, err, lager.Data{"log": string(log), "readErr": readErr, "socket": processSock, "cmdErr": cmdErr}) 192 return err 193 } 194 195 p.logger.Error("errored waiting for "+expectedLog, err, lager.Data{"log": string(log), "stderr": string(stderrContents), "socket": processSock, "cmdErr": cmdErr}) 196 return err 197 } 198 199 if !strings.Contains(string(log), expectedLog) { 200 stderrContents, readErr := ioutil.ReadAll(spawnErr) 201 cmdErr := spawn.Wait() 202 if readErr != nil { 203 p.logger.Error("errored waiting for "+expectedLog, err, lager.Data{"log": string(log), "readErr": readErr, "socket": processSock, "cmdErr": cmdErr}) 204 return err 205 } 206 207 p.logger.Error("errored waiting for "+expectedLog, err, lager.Data{"stderr": string(stderrContents), "log": string(log), "socket": processSock, "cmdErr": cmdErr}) 208 return errors.New("mismatched log from iodaemon") 209 } 210 211 return nil 212 } 213 214 if waitFor("ready") != nil { 215 return 216 } 217 218 ready <- nil 219 220 if waitFor("listener-accepted") != nil { 221 return 222 } 223 if waitFor("unix-rights") != nil { 224 return 225 } 226 if waitFor("write-msg-unix") != nil { 227 return 228 } 229 if waitFor("accepted-connection") != nil { 230 return 231 } 232 if waitFor("cmd-start") != nil { 233 return 234 } 235 if waitFor("cmd-started") != nil { 236 return 237 } 238 239 if waitFor("active") != nil { 240 return 241 } 242 243 active <- nil 244 245 err = spawn.Wait() 246 // delete strace only on clean exit 247 if err != nil { 248 p.logger.Error("iodaemon-failed", err, lager.Data{"pid": spawn.Process.Pid}) 249 return 250 } 251 252 os.Remove(straceOutput) 253 }() 254 255 return 256 } 257 258 func (p *Process) Link() { 259 p.runningLink.Do(p.runLinker) 260 } 261 262 func (p *Process) Attach(processIO garden.ProcessIO) { 263 if processIO.Stdin != nil { 264 p.stdin.AddSource(processIO.Stdin) 265 } 266 267 if processIO.Stdout != nil { 268 p.stdout.AddSink(processIO.Stdout) 269 } 270 271 if processIO.Stderr != nil { 272 p.stderr.AddSink(processIO.Stderr) 273 } 274 } 275 276 // This is guarded by runningLink so will only run once per Process per garden. 277 func (p *Process) runLinker() { 278 processSock := path.Join(p.containerPath, "processes", fmt.Sprintf("%s.sock", p.ID())) 279 280 p.logger.Info("creating-link-to-iodaemon", lager.Data{"socket-path": processSock}) 281 link, err := link.Create(p.logger.Session("link-create"), processSock, p.stdout, p.stderr) 282 if err != nil { 283 p.logger.Error("creating-link-failed", err, lager.Data{"socket-path": processSock}) 284 p.completed(-1, err) 285 return 286 } 287 p.logger.Info("created-link-to-iodaemon", lager.Data{"socket-path": processSock, "err": err}) 288 289 p.stdin.AddSink(link) 290 291 p.link = link 292 close(p.linked) 293 294 p.completed(p.link.Wait()) 295 296 // don't leak stdin pipe 297 p.stdin.Close() 298 } 299 300 func (p *Process) completed(exitStatus int, err error) { 301 p.exitStatus = exitStatus 302 p.exitErr = err 303 close(p.exited) 304 }