예제 #1
0
def signal_handler(sig, frame):
    """Handler for terminating signals. Send CtrlAltDel to the firecracker process on first
    invocation. Forward the signal and kill firecracker on subsequent invocations.

    :sig: signal number
    :frame: current stack frame

    """

    if not hasattr(signal_handler, "SentCtrlAltDel"):

        # Send Ctrl+Alt+Del via the Firecracker API socket so the guest can shut down gracefully.
        shutdown_request = ('PUT /actions HTTP/1.0\r\n'
                            'Content-Type: application/json\r\n'
                            'Content-Length: 33\r\n'
                            '\r\n'
                            '{"action_type": "SendCtrlAltDel"}')
        api_client = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
        api_client.settimeout(0.1)
        api_client.connect(
            bytes(Path(instance_chroot / 'run' / 'firecracker.socket')))
        api_client.send(shutdown_request.encode())
        try:
            while len(api_client.recv(256)) >= 256:
                pass
        except socket.timeout:
            pass
        api_client.close()
        signal_handler.SentCtrlAltDel = True

    else:

        # This is the second signal. If the guest did not react to Ctrl+Alt+Del the first time,
        # it won't do so when sending it again. Pass the signal to the Firecracker process and
        # follow up with SIGKILL.
        if firecracker_process.poll() is None:
            # Our direct child process is still running. Signal it.
            firecracker_process.send_signal(sig)
            try:
                firecracker_process.communicate(timeout=0.25)
            except subprocess.TimeoutExpired:
                firecracker_process.kill()
        else:
            # Our direct child has terminated. This means it forked and we are really waiting
            # for firecracker_pid.
            if type(firecracker_pid) == int:
                os.kill(firecracker_pid, sig)
                time.sleep(0.25)
                try:
                    os.kill(firecracker_pid, signal.SIGKILL)
                except ProcessLookupError:
                    pass
            else:
                signal.pidfd_send_signal(firecracker_pid.fileno(), sig)
                time.sleep(0.25)
                try:
                    signal.pidfd_send_signal(firecracker_pid.fileno(),
                                             signal.SIGKILL)
                except ProcessLookupError:
                    pass
예제 #2
0
def send_signal_safe(own_pid, target_pid):
    """Sends SIGUSR1 to a process if both
    processes have the same associated command name.
    (Race condition free)

    Requires:
        - Python 3.9+
        - Linux 5.1+

    Args:
        own_pid (int): the pid of this process
        target_pid (int):
            the pid of the process to send the signal to
    """
    pidfile = None
    try:
        pidfile = os.open(f'/proc/{target_pid}', os.O_DIRECTORY)
        if is_same_command(own_pid, target_pid):
            signal.pidfd_send_signal(pidfile, signal.SIGUSR1)
    except FileNotFoundError:
        pass
    except OSError as error:
        # not sure if errno is really set..
        # at least the documentation of the used functions says so..
        # see e.g.: https://github.com/python/cpython/commit/7483451577916e693af6d20cf520b2cc7e2174d2#diff-99fb04b208835118fdca0d54b76a00c450da3eaff09d2b53e8a03d63bbe88e30R1279-R1281
        # and https://docs.python.org/3/c-api/exceptions.html#c.PyErr_SetFromErrno

        # caused by either pidfile_open or pidfd_send_signal
        if error.errno != errno.ESRCH:
            raise
        # else: the process is death
    finally:
        if pidfile is not None:
            os.close(pidfile)
예제 #3
0
 def test_pidfd_send_signal(self):
     with self.assertRaises(OSError) as cm:
         signal.pidfd_send_signal(0, signal.SIGINT)
     if cm.exception.errno == errno.ENOSYS:
         self.skipTest("kernel does not support pidfds")
     self.assertEqual(cm.exception.errno, errno.EBADF)
     my_pidfd = os.open(f'/proc/{os.getpid()}', os.O_DIRECTORY)
     self.addCleanup(os.close, my_pidfd)
     with self.assertRaisesRegexp(TypeError, "^siginfo must be None$"):
         signal.pidfd_send_signal(my_pidfd, signal.SIGINT, object(), 0)
     with self.assertRaises(KeyboardInterrupt):
         signal.pidfd_send_signal(my_pidfd, signal.SIGINT)
예제 #4
0
firecracker_process = subprocess.Popen(jailer_cmd)
firecracker_process.communicate()

if args.new_pid_ns:
    # With --new-pid-ns, jailer forks and therefore does not block until firecracker terminates.
    # We need to wait for firecracker before we can exit and clean up the chroot directory.
    with Path(instance_chroot / 'firecracker.pid').open('r') as f:
        # Get the PID of the firecracker process from the file firecracker.pid in the root of the
        # chroot directory.
        firecracker_pid = int(f.read())
        try:
            # pidfd_open is superior but requires Python 3.9+ and Linux 5.3+.
            # If this fails, fall back to traditional process management.
            firecracker_pid = open(os.pidfd_open(firecracker_pid))
        except Exception:
            pass
        while True:
            try:
                # Send signal 0 (no signal), to check if the process is still alive.
                if type(firecracker_pid) == int:
                    os.kill(firecracker_pid, 0)
                else:
                    signal.pidfd_send_signal(firecracker_pid.fileno(), 0)
            except ProcessLookupError:
                # The firecracker process has exited, we can clean up now.
                if type(firecracker_pid) != int:
                    firecracker_pid.close()
                break
            time.sleep(0.25)
sys.exit(firecracker_process.returncode)