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  }