mirror of
https://github.com/opencontainers/runc.git
synced 2026-07-11 06:03:57 +08:00
fdb100d247
We need to make sure the container is destroyed before closing the stdio for the container. This becomes a big issues when running in the host's pid namespace because the other processes could have inherited the stdio of the initial process. The call to close will just block as they still have the io open. Calling destroy before closing io, especially in the host pid namespace will cause all additional processes to be killed in the container's cgroup. This will allow the io to be closed successfuly. This change makes sure the order for destroy and close is correct as well as ensuring that if any errors encoutered during start or exec will be handled by terminating the process and destroying the container. We cannot use defers here because we need to enforce the correct ordering on destroy. This also sets the subreaper setting for runc so that when running in pid host, runc can wait on the addiontal processes launched by the container, useful on destroy, but also good for reaping the additional processes that were launched. Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
117 lines
2.9 KiB
Go
117 lines
2.9 KiB
Go
// +build linux
|
|
|
|
package main
|
|
|
|
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),
|
|
})
|
|
}
|
|
}
|