// +build linux package runc import ( "os" "os/signal" "syscall" "github.com/Sirupsen/logrus" "github.com/opencontainers/runc/libcontainer" "github.com/opencontainers/runc/libcontainer/system" "github.com/opencontainers/runc/libcontainer/utils" ) const signalBufferSize = 2048 // newSignalHandler returns a signal handler for processing SIGCHLD and SIGWINCH signals // while still forwarding all other signals to the process. func newSignalHandler(tty *tty, enableSubreaper bool) *signalHandler { if enableSubreaper { // set us as the subreaper before registering the signal handler for the container if err := system.SetSubreaper(1); err != nil { logrus.Warn(err) } } // ensure that we have a large buffer size so that we do not miss any signals // incase we are not processing them fast enough. s := make(chan os.Signal, signalBufferSize) // handle all signals for the process. signal.Notify(s) return &signalHandler{ tty: tty, signals: s, } } // exit models a process exit status with the pid and // exit status. type exit struct { pid int status int } type signalHandler struct { signals chan os.Signal tty *tty } // forward handles the main signal event loop forwarding, resizing, or reaping depending // on the signal received. func (h *signalHandler) forward(process *libcontainer.Process) (int, error) { // make sure we know the pid of our main process so that we can return // after it dies. pid1, err := process.Pid() if err != nil { return -1, err } // perform the initial tty resize. h.tty.resize() for s := range h.signals { switch s { case syscall.SIGWINCH: h.tty.resize() case syscall.SIGCHLD: exits, err := h.reap() if err != nil { logrus.Error(err) } for _, e := range exits { logrus.WithFields(logrus.Fields{ "pid": e.pid, "status": e.status, }).Debug("process exited") if e.pid == pid1 { // call Wait() on the process even though we already have the exit // status because we must ensure that any of the go specific process // fun such as flushing pipes are complete before we return. process.Wait() return e.status, nil } } default: logrus.Debugf("sending signal to process %s", s) if err := syscall.Kill(pid1, s.(syscall.Signal)); err != nil { logrus.Error(err) } } } return -1, nil } // reap runs wait4 in a loop until we have finished processing any existing exits // then returns all exits to the main event loop for further processing. func (h *signalHandler) reap() (exits []exit, err error) { var ( ws syscall.WaitStatus rus syscall.Rusage ) for { pid, err := syscall.Wait4(-1, &ws, syscall.WNOHANG, &rus) if err != nil { if err == syscall.ECHILD { return exits, nil } return nil, err } if pid <= 0 { return exits, nil } exits = append(exits, exit{ pid: pid, status: utils.ExitStatus(ws), }) } }