func (t *tracerImpl) Run() (err error) { if t.cmd.SysProcAttr == nil { t.cmd.SysProcAttr = &syscall.SysProcAttr{Ptrace: true} } else { t.cmd.SysProcAttr.Ptrace = true } runtime.LockOSThread() if err = t.cmd.Start(); err != nil { return } var waitStatus syscall.WaitStatus if _, err = syscall.Wait4(t.cmd.Process.Pid, &waitStatus, 0, nil); err != nil { return } if waitStatus.Exited() { return } // Set options to detect our syscalls if err = syscall.PtraceSetOptions(t.cmd.Process.Pid, syscall.PTRACE_O_TRACESYSGOOD); err != nil { return } var regsEntry, regsExit syscall.PtraceRegs // Get first syscall if err = syscall.PtraceGetRegs(t.cmd.Process.Pid, ®sEntry); err != nil { return } var exited bool for { if exited, err = wait_for_syscall(t.cmd.Process.Pid); exited || err != nil { return } // Get syscall info if err = syscall.PtraceGetRegs(t.cmd.Process.Pid, ®sEntry); err != nil { return } // Enter syscall t.callback(regsEntry, false) if exited, err = wait_for_syscall(t.cmd.Process.Pid); exited || err != nil { return } // Get syscall returned value if err = syscall.PtraceGetRegs(t.cmd.Process.Pid, ®sExit); err != nil { return } t.callback(regsExit, true) } }
func babySit(process *os.Process) int { // Forward all signals to the app sigchan := make(chan os.Signal, 1) sigutil.CatchAll(sigchan) go func() { for sig := range sigchan { if sig == syscall.SIGCHLD { continue } process.Signal(sig) } }() // Wait for the app to exit. Also, as pid 1 it's our job to reap all // orphaned zombies. var wstatus syscall.WaitStatus for { pid, err := syscall.Wait4(-1, &wstatus, 0, nil) if err == nil && pid == process.Pid { break } } return wstatus.ExitStatus() }
func (d *driver) Terminate(p *execdriver.Command) error { // lets check the start time for the process state, err := libcontainer.GetState(filepath.Join(d.root, p.ID)) if err != nil { if !os.IsNotExist(err) { return err } // TODO: Remove this part for version 1.2.0 // This is added only to ensure smooth upgrades from pre 1.1.0 to 1.1.0 data, err := ioutil.ReadFile(filepath.Join(d.root, p.ID, "start")) if err != nil { // if we don't have the data on disk then we can assume the process is gone // because this is only removed after we know the process has stopped if os.IsNotExist(err) { return nil } return err } state = &libcontainer.State{InitStartTime: string(data)} } currentStartTime, err := system.GetProcessStartTime(p.ProcessConfig.Process.Pid) if err != nil { return err } if state.InitStartTime == currentStartTime { err = syscall.Kill(p.ProcessConfig.Process.Pid, 9) syscall.Wait4(p.ProcessConfig.Process.Pid, nil, 0, nil) } d.removeContainerRoot(p.ID) return err }
// StartReaper starts a goroutine to reap processes if called from a process // that has pid 1. func StartReaper() { if os.Getpid() == 1 { glog.V(4).Infof("Launching reaper") go func() { sigs := make(chan os.Signal, 1) signal.Notify(sigs, syscall.SIGCHLD) for { // Wait for a child to terminate sig := <-sigs glog.V(4).Infof("Signal received: %v", sig) for { // Reap processes glog.V(4).Infof("Waiting to reap") cpid, _ := syscall.Wait4(-1, nil, syscall.WNOHANG, nil) if cpid < 1 { glog.V(4).Infof("No more processes to reap.") break } glog.V(4).Infof("Reaped process with pid %d", cpid) } } }() } }
func (p *ProcessReaper) reap() { for { p.log.Debug("reap") var status syscall.WaitStatus var rusage syscall.Rusage wpid, err := syscall.Wait4(-1, &status, syscall.WNOHANG, &rusage) if wpid == 0 || (wpid == -1 && err.Error() == "no child processes") { break } if err != nil { p.log.Error("reaper-wait-error", err, lager.Data{"wpid": wpid}) break } p.log.Info("reaped", lager.Data{"pid": wpid, "status": status, "rusage": rusage}) if ch, ok := p.waitChan(wpid); ok { ch <- status.ExitStatus() p.log.Info("wait-once-sent-exit-status", lager.Data{"pid": wpid, "status": status, "rusage": rusage}) } else { p.log.Info("wait-once-not-found", lager.Data{"pid": wpid, "status": status, "rusage": rusage}) } } }
func (d *driver) Terminate(c *execdriver.Command) error { defer d.cleanContainer(c.ID) // lets check the start time for the process active := d.activeContainers[c.ID] if active == nil { return fmt.Errorf("active container for %s does not exist", c.ID) } state, err := active.State() if err != nil { return err } pid := state.InitProcessPid currentStartTime, err := system.GetProcessStartTime(pid) if err != nil { return err } if state.InitProcessStartTime == currentStartTime { err = syscall.Kill(pid, 9) syscall.Wait4(pid, nil, 0, nil) } return err }
// checkForDeath tries to clean up zombies and then checks if the // process group is empty. // // It returns "true" if the answer is yes and there are no grace pings left // func (n *nelly) checkForDeath() bool { // Check if there are any zombies to eat. Process.Wait() doesn't // support the POSIX WNOHANG for portability reasons, so let's use // the syscall.Wait4() which is POSIX-only. var w syscall.WaitStatus rusage := syscall.Rusage{} zpid, err := syscall.Wait4(-1, &w, syscall.WNOHANG, &rusage) if err != nil { n.Error("Error in Wait4: %s", err.Error()) } if zpid > 0 { n.Error("Ate a tasty zombie (pid was %d, status was %d)", zpid, w.ExitStatus()) } if n.processGroupIsEmpty() { n.startGracePings-- if n.startGracePings <= 0 { n.Error("Process group [%d] empty - exiting and hoping init sorts it all out", n.pgid) return true } else { n.Error("Process group [%d] empty - grace pings left [%d]", n.pgid, n.startGracePings) } } else { // We've had a good ping, no more Mr Nice Guy n.startGracePings = 0 } return false }
func monitor(active chan bool, notify chan notification) { for { monitoring := <-active for monitoring { var rusage syscall.Rusage var status syscall.WaitStatus options := syscall.WUNTRACED pid, err := syscall.Wait4(-1, &status, options, &rusage) if err != nil { println("Wait4:", err.Error()) } if pid <= 0 { break } if status.Stopped() { if pid == task0.Job.Group { incoming <- syscall.SIGTSTP } continue } if status.Signaled() { if status.Signal() == syscall.SIGINT && pid == task0.Job.Group { incoming <- syscall.SIGINT } status += 128 } notify <- notification{pid, status} monitoring = <-active } } }
// getPassword gets input hidden from the terminal from a user. This is // accomplished by turning off terminal echo, reading input from the user and // finally turning on terminal echo. func getPassword() (password string, err error) { sig := make(chan os.Signal, 10) brk := make(chan bool) // File descriptors for stdin, stdout, and stderr. fd := []uintptr{os.Stdin.Fd(), os.Stdout.Fd(), os.Stderr.Fd()} // Setup notifications of termination signals to channel sig, create a process to // watch for these signals so we can turn back on echo if need be. signal.Notify(sig, syscall.SIGHUP, syscall.SIGINT, syscall.SIGKILL, syscall.SIGQUIT, syscall.SIGTERM) go catchSignal(fd, sig, brk) // Turn off the terminal echo. pid, err := echoOff(fd) if err != nil { return "", err } // Turn on the terminal echo and stop listening for signals. defer signal.Stop(sig) defer close(brk) defer echoOn(fd) syscall.Wait4(pid, nil, 0, nil) line, err := readline() if err == nil { password = strings.TrimSpace(line) } else { err = fmt.Errorf("failed during password entry: %s", err) } return password, err }
func babySit(process *os.Process) int { log := logger.New("fn", "babySit") // Forward all signals to the app sigchan := make(chan os.Signal, 1) sigutil.CatchAll(sigchan) go func() { for sig := range sigchan { log.Info("received signal", "type", sig) if sig == syscall.SIGCHLD { continue } log.Info("forwarding signal to command", "type", sig) process.Signal(sig) } }() // Wait for the app to exit. Also, as pid 1 it's our job to reap all // orphaned zombies. var wstatus syscall.WaitStatus for { pid, err := syscall.Wait4(-1, &wstatus, 0, nil) if err == nil && pid == process.Pid { break } } if wstatus.Signaled() { log.Info("command exited due to signal") return 0 } return wstatus.ExitStatus() }
// StartProcess kicks off the event loop and forever waits for signals from // the traced process. This is currently done in a super-silly fashion and will // hopefully benefit from Go channels/goroutines in the future. func (p *Process) StartProcess() (ret int) { var status syscall.WaitStatus L: for { _, err := syscall.Wait4( /*p.Pid*/ -1, &status, 0, nil) p.isRunning = false switch { // status == 0 means terminated?? case status.Exited() || status == 0 || err != nil: ret = status.ExitStatus() break L case status.Stopped(): if bp, hit := p.InBreakpoint(); hit { p.handleBreakpoint(bp) } //case status.Continued(): //case status.CoreDump(): //case status.Signaled(): //case status.ExitStatus(): //case status.StopSignal(): //case status.TrapCause(): default: // fmt.Printf("Got status: %v\n", status) } p.Continue() } return }
func handleSigchld(mpid int) *resultPack { for { var status syscall.WaitStatus var spid int var err error spid, err = syscall.Wait4(-mpid, &status, syscall.WNOHANG|syscall.WALL, nil) if err != nil { poePanic(err, "wait4 failed") } else if spid == 0 { return nil } if spid == mpid && status.Exited() { return &resultPack{POE_SUCCESS, status.ExitStatus(), ""} } else if spid == mpid && status.Signaled() { return &resultPack{POE_SIGNALED, -1, fmt.Sprintf("Program terminated with signal %d (%s)", int(status.Signal()), status.Signal().String())} } else if status.Stopped() { e := status >> 16 & 0xff switch e { case PTRACE_EVENT_SECCOMP: if res := handleSyscall(spid); res != nil { return res } case syscall.PTRACE_EVENT_CLONE, syscall.PTRACE_EVENT_FORK, syscall.PTRACE_EVENT_VFORK: syscall.PtraceCont(spid, 0) default: syscall.PtraceCont(spid, int(status.StopSignal())) } } } }
func waitForContainerToExit(dir string, containerPid int, signals chan os.Signal) (exitCode int) { for range signals { for { var status syscall.WaitStatus var rusage syscall.Rusage wpid, err := syscall.Wait4(-1, &status, syscall.WNOHANG, &rusage) if err != nil || wpid <= 0 { break // wait for next SIGCHLD } if wpid == containerPid { exitCode = status.ExitStatus() if status.Signaled() { exitCode = 128 + int(status.Signal()) } ioWg.Wait() // wait for full output to be collected check(ioutil.WriteFile(filepath.Join(dir, "exitcode"), []byte(strconv.Itoa(exitCode)), 0700)) return exitCode } } } panic("ran out of signals") // cant happen }
func (c *CloneParams) CloneFrozen() (int, error) { pid := callClone(c) // TODO: clone errors? c.CommWriter.Close() c.stdhandles.Close() c.comm = make(chan CommStatus) go commReader(c.CommReader, c.comm) var status syscall.WaitStatus for { wpid, err := syscall.Wait4(pid, &status, 0, nil) // TODO: rusage if err != nil { return -1, os.NewSyscallError("Wait4", err) } if wpid == pid { break } } if status.Stopped() && status.StopSignal() == syscall.SIGTRAP { return pid, nil } if status.Exited() { co, ok := <-c.comm if ok { return -1, childError(co) } return -1, fmt.Errorf("DAFUQ") } err := syscall.Kill(pid, syscall.SIGKILL) if err != nil { return -1, os.NewSyscallError("Kill", err) } return -1, fmt.Errorf("traps, signals, dafuq is this") }
func ChildWaitingFunc(pid int, sig chan *ChildWaitData) { var status syscall.WaitStatus var rusage syscall.Rusage result := &ChildWaitData{} for { wpid, err := syscall.Wait4(pid, &status, syscall.WUNTRACED|syscall.WCONTINUED, &rusage) if wpid != pid { continue } if status.Exited() { result.ExitCode = uint32(status.ExitStatus()) break } if status.Stopped() { result.SuccessCode |= EF_STOPPED result.StopSignal = uint32(status.StopSignal()) syscall.Kill(pid, syscall.SIGKILL) } if status.Signaled() { result.SuccessCode |= EF_KILLED_BY_OTHER result.KillSignal = uint32(status.Signal()) break } if err != nil { break } } result.RusageCpuUser = time.Nanosecond * time.Duration(rusage.Utime.Nano()) result.RusageCpuKernel = time.Nanosecond * time.Duration(rusage.Stime.Nano()) sig <- result close(sig) }
func wait(pid int) (cpid int, status syscall.WaitStatus, err error) { cpid, err = syscall.Wait4(pid, &status, syscall.WALL, nil) if err != nil { return 0, 0, err } return cpid, status, nil }
// echoOn turns back on the terminal echo. func echoOn(fd []uintptr) { // Turn on the terminal echo. pid, e := syscall.ForkExec(sttyArg0, sttyArgvEOn, &syscall.ProcAttr{Dir: "", Files: fd}) if e == nil { syscall.Wait4(pid, nil, 0, nil) } }
// Reap reaps all child processes for the calling process and returns their // exit information func Reap(wait bool) (exits []Exit, err error) { var ( ws syscall.WaitStatus rus syscall.Rusage ) flag := syscall.WNOHANG if wait { flag = 0 } for { pid, err := syscall.Wait4(-1, &ws, flag, &rus) if err != nil { if err == syscall.ECHILD { return exits, nil } return exits, err } if pid <= 0 { return exits, nil } exits = append(exits, Exit{ Pid: pid, Status: exitStatus(ws), }) } }
func (m *Module) IsRunning() bool { pid := 0 if m.Name == "internal:shared" { pid = m.SyncProcess.Pid } else { pid = m.MainProcess.Pid } var waitstatus syscall.WaitStatus wpid, err := syscall.Wait4(pid, &waitstatus, syscall.WNOHANG|syscall.WUNTRACED, nil) if err != nil { // When would this happen? log.Println("Unable to get process wait status:", err) // Assume it is not running return false } // If status is not available, the pid is 0. if wpid == 0 { return true } if waitstatus.Exited() { delete(modules, m.Name) return false } return true }
// Called on running checks, to determine if they have finished // running. // // If the Check has not finished executing, returns false. // // If the Check has been running for longer than its Timeout, // a SIGTERM (and failing that a SIGKILL) is issued to forcibly // terminate the rogue Check process. In either case, this returns // as if the check has not yet finished, and Reap() will need to be // called again to fully reap the Check // // If the Check has finished execution (on its own, or via forced // termination), it will return true. // // Once complete, some additional meta-stats for the check execution // are appended to the check output, to be submit up to bolo func (self *Check) Reap() bool { pid := self.process.Process.Pid var ws syscall.WaitStatus status, err := syscall.Wait4(pid, &ws, syscall.WNOHANG, nil) if err != nil { log.Error("Error waiting on check %s[%d]: %s", self.Name, pid, err.Error()) return false } if status == 0 { // self to see if we need to sigkill due to failed sigterm if time.Now().After(self.started_at.Add(time.Duration(self.Timeout+2) * time.Second)) { log.Warn("Check %s[%d] has been running too long, sending SIGKILL", self.Name, pid) if err := syscall.Kill(pid, syscall.SIGKILL); err != nil { log.Error("Error sending SIGKILL to check %s[%d]: %s", self.Name, pid, err.Error()) } self.sig_kill = true } // self to see if we need to sigterm due to self timeout expiry if !self.sig_kill && time.Now().After(self.started_at.Add(time.Duration(self.Timeout)*time.Second)) { log.Warn("Check %s[%d] has been running too long, sending SIGTERM", self.Name, pid) if err := syscall.Kill(pid, syscall.SIGTERM); err != nil { log.Error("Error sending SIGTERM to check %s[%d]: %s", self.Name, pid, err.Error()) } self.sig_term = true } return false } self.ended_at = time.Now() self.running = false self.duration = time.Since(self.started_at) self.latency = self.started_at.Sub(self.next_run) self.output = string(self.stdout.Bytes()) self.err_msg = string(self.stderr.Bytes()) if ws.Exited() { self.rc = ws.ExitStatus() } else { log.Debug("Check %s[%d] exited abnormally (signaled/stopped). Setting rc to UNKNOWN", self.Name, pid) self.rc = UNKNOWN } if self.rc > UNKNOWN { log.Debug("Check %s[%d] returned with an invalid exit code. Setting rc to UNKOWN", self.Name, pid) self.rc = UNKNOWN } self.reschedule() if self.ended_at.After(self.next_run) { timeout_triggered := "not reached" if self.sig_term || self.sig_kill { timeout_triggered = "reached" } log.Warn("Check %s[%d] took %0.3f seconds to run, at interval %d (timeout of %d was %s)", self.Name, pid, self.duration.Seconds(), self.Every, self.Timeout, timeout_triggered) } return true }
func echoOn(pa syscall.ProcAttr) { pid, err := syscall.ForkExec(sttyCmd, sttyArgvEchoOn, &pa) if err == nil { syscall.Wait4(pid, &waitStatus, 0, nil) } else { fmt.Printf("Error setting echo on: %s\n", err) } }
func wait(pid int) (wpid int, status syscall.WaitStatus, err error) { wpid, err = syscall.Wait4(pid, &status, syscall.WALL, nil) if err != nil { return 0, 0, err } fmt.Printf("\t\twait: wpid=%5d, status=0x%06x\n", wpid, status) return wpid, status, nil }
func main() { flag.Parse() configureLog() // Load configuration conf, err := configuration.FromFile(configFilePath) if err != nil { log.Fatal(err) } eventBus := event_bus.New() // Wait for died children to avoid zombies signalChannel := make(chan os.Signal, 2) signal.Notify(signalChannel, os.Interrupt, syscall.SIGCHLD) go func() { for { sig := <-signalChannel if sig == syscall.SIGCHLD { r := syscall.Rusage{} syscall.Wait4(-1, nil, 0, &r) } } }() // Create StatsD client conf.StatsD.CreateClient() // Create Zookeeper connection zkConn := listenToZookeeper(conf, eventBus) // Create the storage backend storage, err := service.NewZKStorage(zkConn, conf.Bamboo.Zookeeper) if err != nil { log.Panicf("Failed to create ZK storage: %v", err) } appStorage, err := application.NewZKStorage(zkConn, conf.Bamboo.Zookeeper) if err != nil { log.Panicf("Failed to create application ZK storage: %v", err) } // Register handlers handlers := event_bus.Handlers{Conf: &conf, Storage: storage, AppStorage: appStorage} eventBus.Register(handlers.MarathonEventHandler) eventBus.Register(handlers.ServiceEventHandler) eventBus.Register(handlers.WeightEventHandler) eventBus.Publish(event_bus.MarathonEvent{EventType: "bamboo_startup", Timestamp: time.Now().Format(time.RFC3339)}) // Handle gracefully exit registerOSSignals() // load config api.LoadConfig(conf) // Start server initServer(&conf, storage, appStorage, eventBus) }
func Run(host string, port int) int { fmt.Printf("--> binding to %s:%d\n", host, port) addr, err := net.ResolveTCPAddr("tcp", fmt.Sprintf("%s:%d", host, port)) if err != nil { fmt.Fprintf(os.Stderr, "%s\n", err) return -1 } ln, err := net.ListenTCP("tcp", addr) if err != nil { fmt.Fprintf(os.Stderr, "%s\n", err) return -1 } sigs := make(chan os.Signal, 1) signal.Notify(sigs, syscall.SIGCHLD) go func() { var status syscall.WaitStatus var rusage syscall.Rusage for { <-sigs _, err := syscall.Wait4(-1, &status, 0, &rusage) if err != nil { fmt.Fprintf(os.Stderr, "%s\n", err) } } }() for { conn, err := ln.Accept() if err != nil { fmt.Fprintf(os.Stderr, "%s\n", err) continue } fmt.Printf(".") ret, _, errno := syscall.Syscall(syscall.SYS_FORK, 0, 0, 0) if errno != 0 { return int(errno) } if ret != 0 { conn.Close() // parent process close continue } if err := handleClient(conn); err != nil { fmt.Fprintf(os.Stderr, "%s\n", err) continue } conn.Close() // child process close os.Exit(0) // child process exit } return 0 }
func processManagerLoop() { defer close(subscribeNotifications) defer close(unsubscribeNotifications) sigchlds := make(chan os.Signal, 1000) signal.Notify(sigchlds, syscall.SIGCHLD) defer signal.Stop(sigchlds) defer close(sigchlds) subscriptions := make(map[int]chan pidChangeNotification) for { select { case subscribe := <-subscribeNotifications: { _, assigned := subscriptions[subscribe.pid] if !assigned { subscriptions[subscribe.pid] = subscribe.replyChan } else { panic("Duplicate subscription for a PID") } } case unsubscribe := <-unsubscribeNotifications: { delete(subscriptions, unsubscribe.pid) } case <-sigchlds: { var wstatus syscall.WaitStatus var rusage syscall.Rusage waitPid, err := syscall.Wait4(-1, &wstatus, 0, &rusage) if err == nil { subscription, assigned := subscriptions[waitPid] if assigned { notification := pidChangeNotification{ pid: waitPid, wstatus: wstatus, rusage: rusage, } select { case subscription <- notification: { } default: { log.Info("PID Change subscription channel full, deleting entry") delete(subscriptions, waitPid) } } } else { log.Info("PID notification received without actor to send it to: ", waitPid) } } else { log.Error("Error waiting for PID: ", err) } } } } }
// waitPID waits for 'wpid' to exit and prints the exit status of // 'wpid'. Returns error on failure, nil otherwise. func waitPid(wpid int) error { var wstatus syscall.WaitStatus if *debug { log.Printf("Waiting for pid %d to exit", wpid) } if out, err := syscall.Wait4(wpid, &wstatus, 0, nil); out != wpid { return fmt.Errorf("failed while waiting for process %d to exit. error: %s", wpid, err) } return nil }
func readPassword(pid int) string { rd := bufio.NewReader(os.Stdin) syscall.Wait4(pid, &ws, 0, nil) line, err := rd.ReadString('\n') if err == nil { return strings.TrimSpace(line) } return "" }
func ShotgunZombieProcs(t *testing.T) { for { pid, err := syscall.Wait4(-1, nil, syscall.WNOHANG, nil) if err != nil { Fatalf(t, "Unexpected Wait4 error: %s", err) } else if pid == 0 { return } } }
// Call calls an external command. func (e ExternalCmd) Call(ec *EvalCtx, argVals []Value, opts map[string]Value) { if len(opts) > 0 { throw(ErrExternalCmdOpts) } if util.DontSearch(e.Name) { stat, err := os.Stat(e.Name) if err == nil && stat.IsDir() { // implicit cd if len(argVals) > 0 { throw(ErrCdNoArg) } cdInner(e.Name, ec) return } } files := make([]uintptr, len(ec.ports)) for i, port := range ec.ports { if port == nil || port.File == nil { files[i] = fdNil } else { files[i] = port.File.Fd() } } args := make([]string, len(argVals)+1) for i, a := range argVals { // NOTE Maybe we should enfore string arguments instead of coercing all // args into string args[i+1] = ToString(a) } sys := syscall.SysProcAttr{Setpgid: ec.background} attr := syscall.ProcAttr{Env: os.Environ(), Files: files[:], Sys: &sys} path, err := ec.Search(e.Name) if err != nil { throw(err) } args[0] = path pid, err := syscall.ForkExec(path, args, &attr) if err != nil { throw(errors.New("forkExec: " + err.Error())) } var ws syscall.WaitStatus _, err = syscall.Wait4(pid, &ws, syscall.WUNTRACED, nil) if err != nil { throw(fmt.Errorf("wait: %s", err.Error())) } else { maybeThrow(NewExternalCmdExit(e.Name, ws, pid)) } }
func run() int { flag.Parse() runtime := flag.Args()[1] // e.g. runc dir := flag.Args()[2] // bundlePath for run, processPath for exec containerId := flag.Args()[3] signals := make(chan os.Signal, 100) signal.Notify(signals, syscall.SIGCHLD) fd3 := os.NewFile(3, "/proc/self/fd/3") logFile := fmt.Sprintf("/proc/%d/fd/4", os.Getpid()) logFD := os.NewFile(4, "/proc/self/fd/4") syncPipe := os.NewFile(5, "/proc/self/fd/5") pidFilePath := filepath.Join(dir, "pidfile") stdin, stdout, stderr, winsz := openPipes(dir) syncPipe.Write([]byte{0}) var runcStartCmd *exec.Cmd if *tty { ttySlave := setupTty(stdin, stdout, pidFilePath, winsz, garden.WindowSize{Rows: *rows, Columns: *cols}) runcStartCmd = exec.Command(runtime, "-debug", "-log", logFile, "exec", "-d", "-tty", "-console", ttySlave.Name(), "-p", fmt.Sprintf("/proc/%d/fd/0", os.Getpid()), "-pid-file", pidFilePath, containerId) } else { runcStartCmd = exec.Command(runtime, "-debug", "-log", logFile, "exec", "-p", fmt.Sprintf("/proc/%d/fd/0", os.Getpid()), "-d", "-pid-file", pidFilePath, containerId) runcStartCmd.Stdin = stdin runcStartCmd.Stdout = stdout runcStartCmd.Stderr = stderr } // we need to be the subreaper so we can wait on the detached container process system.SetSubreaper(os.Getpid()) if err := runcStartCmd.Start(); err != nil { fd3.Write([]byte{2}) return 2 } var status syscall.WaitStatus var rusage syscall.Rusage _, err := syscall.Wait4(runcStartCmd.Process.Pid, &status, 0, &rusage) check(err) // Start succeeded but Wait4 failed, this can only be a programmer error logFD.Close() // No more logs from runc so close fd fd3.Write([]byte{byte(status.ExitStatus())}) if status.ExitStatus() != 0 { return 3 // nothing to wait for, container didn't launch } containerPid, err := parsePid(pidFilePath) check(err) return waitForContainerToExit(dir, containerPid, signals) }