github.com/opencontainers/runc@v1.2.0-rc.1.0.20240520010911-492dc558cdd6/signals.go (about) 1 package main 2 3 import ( 4 "os" 5 "os/signal" 6 7 "github.com/opencontainers/runc/libcontainer" 8 "github.com/opencontainers/runc/libcontainer/system" 9 "github.com/opencontainers/runc/libcontainer/utils" 10 11 "github.com/sirupsen/logrus" 12 "golang.org/x/sys/unix" 13 ) 14 15 const signalBufferSize = 2048 16 17 // newSignalHandler returns a signal handler for processing SIGCHLD and SIGWINCH signals 18 // while still forwarding all other signals to the process. 19 // If notifySocket is present, use it to read systemd notifications from the container and 20 // forward them to notifySocketHost. 21 func newSignalHandler(enableSubreaper bool, notifySocket *notifySocket) *signalHandler { 22 if enableSubreaper { 23 // set us as the subreaper before registering the signal handler for the container 24 if err := system.SetSubreaper(1); err != nil { 25 logrus.Warn(err) 26 } 27 } 28 // ensure that we have a large buffer size so that we do not miss any signals 29 // in case we are not processing them fast enough. 30 s := make(chan os.Signal, signalBufferSize) 31 // handle all signals for the process. 32 signal.Notify(s) 33 return &signalHandler{ 34 signals: s, 35 notifySocket: notifySocket, 36 } 37 } 38 39 // exit models a process exit status with the pid and 40 // exit status. 41 type exit struct { 42 pid int 43 status int 44 } 45 46 type signalHandler struct { 47 signals chan os.Signal 48 notifySocket *notifySocket 49 } 50 51 // forward handles the main signal event loop forwarding, resizing, or reaping depending 52 // on the signal received. 53 func (h *signalHandler) forward(process *libcontainer.Process, tty *tty, detach bool) (int, error) { 54 // make sure we know the pid of our main process so that we can return 55 // after it dies. 56 if detach && h.notifySocket == nil { 57 return 0, nil 58 } 59 60 pid1, err := process.Pid() 61 if err != nil { 62 return -1, err 63 } 64 65 if h.notifySocket != nil { 66 if detach { 67 _ = h.notifySocket.run(pid1) 68 return 0, nil 69 } 70 _ = h.notifySocket.run(os.Getpid()) 71 go func() { _ = h.notifySocket.run(0) }() 72 } 73 74 // Perform the initial tty resize. Always ignore errors resizing because 75 // stdout might have disappeared (due to races with when SIGHUP is sent). 76 _ = tty.resize() 77 // Handle and forward signals. 78 for s := range h.signals { 79 switch s { 80 case unix.SIGWINCH: 81 // Ignore errors resizing, as above. 82 _ = tty.resize() 83 case unix.SIGCHLD: 84 exits, err := h.reap() 85 if err != nil { 86 logrus.Error(err) 87 } 88 for _, e := range exits { 89 logrus.WithFields(logrus.Fields{ 90 "pid": e.pid, 91 "status": e.status, 92 }).Debug("process exited") 93 if e.pid == pid1 { 94 // call Wait() on the process even though we already have the exit 95 // status because we must ensure that any of the go specific process 96 // fun such as flushing pipes are complete before we return. 97 _, _ = process.Wait() 98 return e.status, nil 99 } 100 } 101 case unix.SIGURG: 102 // SIGURG is used by go runtime for async preemptive 103 // scheduling, so runc receives it from time to time, 104 // and it should not be forwarded to the container. 105 // Do nothing. 106 default: 107 us := s.(unix.Signal) 108 logrus.Debugf("forwarding signal %d (%s) to %d", int(us), unix.SignalName(us), pid1) 109 if err := unix.Kill(pid1, us); err != nil { 110 logrus.Error(err) 111 } 112 } 113 } 114 return -1, nil 115 } 116 117 // reap runs wait4 in a loop until we have finished processing any existing exits 118 // then returns all exits to the main event loop for further processing. 119 func (h *signalHandler) reap() (exits []exit, err error) { 120 var ( 121 ws unix.WaitStatus 122 rus unix.Rusage 123 ) 124 for { 125 pid, err := unix.Wait4(-1, &ws, unix.WNOHANG, &rus) 126 if err != nil { 127 if err == unix.ECHILD { 128 return exits, nil 129 } 130 return nil, err 131 } 132 if pid <= 0 { 133 return exits, nil 134 } 135 exits = append(exits, exit{ 136 pid: pid, 137 status: utils.ExitStatus(ws), 138 }) 139 } 140 }