def __init__(self, signals=None, setmask=False): """ Initialize a new mask object. :param signals: List of signals to block :param setmask: A flag that controls if ``SIG_SETMASK`` should be used over ``SIG_BLOCK`` and ``SIG_UNBLOCK``. For details see :meth:`block()` and :meth:`unblock()`. .. note:: The masking function (``sigprocmask(2)`` or ``pthread_sigmask(3)``) is not called until :meth:`block()` is called. """ if signals is None: self._signals = frozenset() else: self._signals = frozenset(signals) self._setmask = setmask self._mask = sigset_t() self._old_mask = None # old mask is only used for SIG_SETMASK self._is_active = False sigemptyset(self._mask) for signal in self.signals: sigaddset(self._mask, signal)
def get(cls): """ Use ``pthread_sigmask(2)`` to obtain the mask of blocked signals :returns: A fresh :class:`pthread_sigmask` object. The returned object behaves as it was constructed with the list of currently blocked signals, ``setmask=False`` and as if the :meth:`block()` was immediately called. That is, calling :meth:`unblock()` will will cause those signals not to be blocked anymore while calling :meth:`block()` will re-block them (if they were unblocked after this method returns). """ mask = sigset_t() sigemptyset(mask) _pthread_sigmask(0, None, mask) signals = [] for sig_num in range(1, NSIG): if sigismember(mask, sig_num): signals.append(sig_num) self = cls(signals) self._is_active = True self._old_mask = mask return self
def get(cls): """ Use the masking function (``sigprocmask(2)`` or ``pthread_sigmask(3)``) to obtain the mask of blocked signals :returns: A fresh :class:`sigprocmask` object. The returned object behaves as it was constructed with the list of currently blocked signals, ``setmask=False`` and as if the :meth:`block()` was immediately called. That is, calling :meth:`unblock()` will will cause those signals not to be blocked anymore while calling :meth:`block()` will re-block them (if they were unblocked after this method returns). """ mask = sigset_t() sigemptyset(mask) cls._do_mask(0, None, mask) signals = [] for sig_num in range(1, NSIG): if sigismember(mask, sig_num): signals.append(sig_num) self = cls(signals) self._is_active = True self._old_mask = mask return self
def __init__(self, signals=None, setmask=False): """ Initialize a new pthread_sigmask object. :param signals: List of signals to block :param setmask: A flag that controls if ``SIG_SETMASK`` should be used over ``SIG_BLOCK`` and ``SIG_UNBLOCK``. For details see :meth:`block()` and :meth:`unblock()`. .. note:: ``pthread_sigmask(2)`` is not called until :meth:`block()` is called. """ if signals is None: self._signals = frozenset() else: self._signals = frozenset(signals) self._setmask = setmask self._mask = sigset_t() self._old_mask = None # old mask is only used for SIG_SETMASK self._is_active = False sigemptyset(self._mask) for signal in self.signals: sigaddset(self._mask, signal)
def create_signal_mask(): """Create signal mask for blocking/unblocking signals. :return: """ mask = sigset_t() sigemptyset(mask) for sig in [SIGINT, SIGQUIT, SIGTERM]: sigaddset(mask, sig) return mask
def update(self, signals): """ Update the mask of signals this signalfd reacts to :param signals: A replacement set of signal numbers to monitor :raises ValueError: If :meth:`closed()` is True """ if self._sfd < 0: _err_closed() mask = sigset_t() sigemptyset(mask) if signals is not None: for signal in signals: sigaddset(mask, signal) # flags are ignored when sfd is not -1 _signalfd(self._sfd, mask, 0) self._signals = frozenset(signals)
def block(self): """ Use ``pthread_sigmask(2)`` to block signals. This method uses either ``SIG_SETMASK`` or ``SIG_BLOCK``, depending on how the object was constructed. After this method is called, the subsequent call to :meth:`unblock()` will undo its effects. .. note:: This method is a no-op if signal blocking is currently active (as determined by :meth:`is_active` returning True). """ if self._is_active: return if self._setmask: self._old_mask = sigset_t() sigemptyset(self._old_mask) _pthread_sigmask(SIG_SETMASK, self._mask, self._old_mask) else: _pthread_sigmask(SIG_BLOCK, self._mask, None) self._is_active = True
def signals(self, new_signals): # Convert signals to frozendict as we depend on that below new_signals = frozenset(new_signals) # Reset the mask to what signals describes sigemptyset(self._mask) for signal in new_signals: sigaddset(self._mask, signal) # If we're active, re-apply the changes if self.is_active: # In setmask mode we can just overwrite the old values directly if self._setmask: # NOTE: we're not updating self._old_mask here. This way # unblock will trully restore everything despite modifications # to signals that happened after the call to block() self._do_mask(SIG_SETMASK, self._mask, None) else: # in the non-setmask mode, let's just apply the delta delta_mask = sigset_t() # Let's start blocking the new signals first added_signals = new_signals - self._signals if added_signals: sigemptyset(delta_mask) for signal in added_signals: sigaddset(delta_mask, signal) self._do_mask(SIG_BLOCK, delta_mask, None) # Let's unblock signals next removed_signals = self._signals - new_signals if removed_signals: sigemptyset(delta_mask) for signal in removed_signals: sigaddset(delta_mask, signal) self._do_mask(SIG_UNBLOCK, delta_mask, None) # Reset signals to the new value self._signals = new_signals
def signals(self, new_signals): # Convert signals to frozendict as we depend on that below new_signals = frozenset(new_signals) # Reset the mask to what signals describes sigemptyset(self._mask) for signal in new_signals: sigaddset(self._mask, signal) # If we're active, re-apply the changes if self.is_active: # In setmask mode we can just overwrite the old values directly if self._setmask: # NOTE: we're not updating self._old_mask here. This way # unblock will trully restore everything despite modifications # to signals that happened after the call to block() _pthread_sigmask(SIG_SETMASK, self._mask, None) else: # in the non-setmask mode, let's just apply the delta delta_mask = sigset_t() # Let's start blocking the new signals first added_signals = new_signals - self._signals if added_signals: sigemptyset(delta_mask) for signal in added_signals: sigaddset(delta_mask, signal) _pthread_sigmask(SIG_BLOCK, delta_mask, None) # Let's unblock signals next removed_signals = self._signals - new_signals if removed_signals: sigemptyset(delta_mask) for signal in removed_signals: sigaddset(delta_mask, signal) _pthread_sigmask(SIG_UNBLOCK, delta_mask, None) # Reset signals to the new value self._signals = new_signals
def block(self): """ Use the masking function (``sigprocmask(2)`` or ``pthread_sigmask(3)``) to block signals. This method uses either ``SIG_SETMASK`` or ``SIG_BLOCK``, depending on how the object was constructed. After this method is called, the subsequent call to :meth:`unblock()` will undo its effects. .. note:: This method is a no-op if signal blocking is currently active (as determined by :meth:`is_active` returning True). """ if self._is_active: return if self._setmask: self._old_mask = sigset_t() sigemptyset(self._old_mask) self._do_mask(SIG_SETMASK, self._mask, self._old_mask) else: self._do_mask(SIG_BLOCK, self._mask, None) self._is_active = True
def main(): # Block signals so that they aren't handled # according to their default dispositions mask = sigset_t() fdsi = signalfd_siginfo() sigemptyset(mask) sigaddset(mask, SIGINT) sigaddset(mask, SIGQUIT) sigprocmask(SIG_BLOCK, mask, None) # Get a signalfd descriptor sfd = signalfd(-1, mask, SFD_CLOEXEC) # Get a epoll descriptor epollfd = epoll_create1(EPOLL_CLOEXEC) ev = epoll_event() ev.events = EPOLLIN ev.data.fd = sfd epoll_ctl(epollfd, EPOLL_CTL_ADD, sfd, byref(ev)) MAX_EVENTS = 10 events = (epoll_event * MAX_EVENTS)() with fdopen(sfd, 'rb', 0) as sfd_stream: while True: nfds = epoll_wait( epollfd, cast(byref(events), POINTER(epoll_event)), MAX_EVENTS, -1) for event_id in range(nfds): if events[event_id].data.fd == sfd: # Read the next delivered signal sfd_stream.readinto(fdsi) if fdsi.ssi_signo == SIGINT: print("Got SIGINT") elif fdsi.ssi_signo == SIGQUIT: print("Got SIGQUIT") return else: print("Read unexpected signal") else: raise Exception("unexpected fd?") close(sfd) close(epollfd)
def main(): # Block signals so that they aren't handled # according to their default dispositions mask = sigset_t() fdsi = signalfd_siginfo() sigemptyset(mask) sigaddset(mask, SIGINT) sigaddset(mask, SIGQUIT) sigprocmask(SIG_BLOCK, mask, None) # Get a signalfd descriptor sfd = signalfd(-1, mask, 0) with fdopen(sfd, 'rb', 0) as sfd_stream: while True: # Read the next delivered signal sfd_stream.readinto(fdsi) if fdsi.ssi_signo == SIGINT: print("Got SIGINT") elif fdsi.ssi_signo == SIGQUIT: print("Got SIGQUIT") return else: print("Read unexpected signal")
def __init__(self, signals=None, flags=0): """ Create a signalfd() descriptor reacting to a set of signals. :param signals: A set of signal numbers to include in the mask of signals passed to signalfd(2). :param flags: A bit-mask of flags to pass to signalfd(2). You should pass SFD_CLOEXEC here, to ensure that a other threads don't inadvertedly fork and leak this descriptor. You can also pass SFD_NONBLOCK to make reads from the signalfd object non-blocking. """ self._sfd = -1 self._signals = frozenset() mask = sigset_t() sigemptyset(mask) if signals is None: signals = () for signal in signals: sigaddset(mask, signal) self._sfd = _signalfd(-1, mask, flags) self._signals = frozenset(signals)
def main(): # Block signals so that they aren't handled # according to their default dispositions mask = sigset_t() fdsi = signalfd_siginfo() sigemptyset(mask) sigaddset(mask, SIGINT) sigaddset(mask, SIGQUIT) sigaddset(mask, SIGCHLD) sigaddset(mask, SIGPIPE) print("Blocking signals") sigprocmask(SIG_BLOCK, mask, None) # Get a signalfd descriptor sfd = signalfd(-1, mask, SFD_CLOEXEC | SFD_NONBLOCK) print("Got signalfd", sfd) # Get a epoll descriptor ep = epoll() print("Got epollfd", ep.fileno()) print("Adding signalfd fd {} to epoll".format(sfd)) ep.register(sfd, EPOLLIN) # Get two pair of pipes, one for stdout and one for stderr stdout_pair = (c_int * 2)() pipe2(byref(stdout_pair), O_CLOEXEC | O_NONBLOCK) print("Got stdout pipe pair", stdout_pair[0], stdout_pair[1]) print("Adding pipe fd {} to epoll".format(stdout_pair[0])) ep.register(stdout_pair[0], EPOLLIN) stderr_pair = (c_int * 2)() pipe2(byref(stderr_pair), O_CLOEXEC | O_NONBLOCK) print("Got stderr pipe pair", stderr_pair[0], stderr_pair[1]) print("Adding pipe fd {} to epoll".format(stdout_pair[0])) ep.register(stderr_pair[0], EPOLLIN) prog = argv[1:] if not prog: prog = ['echo', 'usage: demo.py PROG [ARGS]'] print("Going to start program:", prog) # Fork :-) pid = fork() if pid == 0: # Child. # NOTE: we are not closing any of the pipe ends. Why? Because they are # all O_CLOEXEC and will thus not live across the execlp call down # below. dup3(stdout_pair[1], 1, 0) dup3(stderr_pair[1], 2, 0) execlp(prog[0], *prog) return -1 else: close(stdout_pair[1]) close(stderr_pair[1]) with fdopen(sfd, 'rb', 0) as sfd_stream: waiting_for = set(['stdout', 'stderr', 'proc']) while waiting_for: print("Waiting for events...", ' '.join(waiting_for), flush=True) event_list = ep.poll(maxevents=10) print("epoll_wait() read {} events".format(len(event_list))) for fd, events in event_list: print("[event]") event_bits = [] if events & EPOLLIN == EPOLLIN: event_bits.append('EPOLLIN') if events & EPOLLOUT == EPOLLOUT: event_bits.append('EPOLLOUT') if events & EPOLLRDHUP == EPOLLRDHUP: event_bits.append('EPOLLRDHUP') if events & EPOLLPRI == EPOLLPRI: event_bits.append('EPOLLPRI') if events & EPOLLERR == EPOLLERR: event_bits.append('EPOLLERR') if events & EPOLLHUP == EPOLLHUP: event_bits.append('EPOLLHUP') if fd == sfd: fd_name = 'signalfd()' elif fd == stdout_pair[0]: fd_name = 'stdout pipe2()' elif fd == stderr_pair[0]: fd_name = 'stderr pipe2()' else: fd_name = "???" print(" events: {} ({})".format( events, ' | '.join(event_bits))) print(" fd: {} ({})".format(fd, fd_name)) if fd == sfd: print("signalfd() descriptor ready") if events & EPOLLIN: print("Reading data from signalfd()...") # Read the next delivered signal sfd_stream.readinto(fdsi) if fdsi.ssi_signo == SIGINT: print("Got SIGINT") elif fdsi.ssi_signo == SIGQUIT: print("Got SIGQUIT") raise SystemExit("exiting prematurly") elif fdsi.ssi_signo == SIGCHLD: print("Got SIGCHLD") waitid_result = waitid( P_PID, pid, WNOHANG | WEXITED | WSTOPPED | WCONTINUED | WUNTRACED) if waitid_result is None: print("child not ready") else: print("child event") print("si_pid:", waitid_result.si_pid) print("si_uid:", waitid_result.si_uid) print("si_signo:", waitid_result.si_signo) assert waitid_result.si_signo == SIGCHLD print("si_status:", waitid_result.si_status) print("si_code:", waitid_result.si_code) if waitid_result.si_code == CLD_EXITED: # assert WIFEXITED(waitid_result.si_status) print("child exited normally") print("exit code:", WEXITSTATUS(waitid_result.si_status)) waiting_for.remove('proc') if 'stdout' in waiting_for: ep.unregister(stdout_pair[0]) close(stdout_pair[0]) waiting_for.remove('stdout') if 'stderr' in waiting_for: ep.unregister(stderr_pair[0]) close(stderr_pair[0]) waiting_for.remove('stderr') elif waitid_result.si_code == CLD_KILLED: assert WIFSIGNALED(waitid_result.si_status) print("child was killed by signal") print("death signal:", waitid_result.si_status) waiting_for.remove('proc') elif waitid_result.si_code == CLD_DUMPED: assert WIFSIGNALED(waitid_result.si_status) print("core:", WCOREDUMP(waitid_result.si_status)) elif waitid_result.si_code == CLD_STOPPED: print("child was stopped") print("stop signal:", waitid_result.si_status) elif waitid_result.si_code == CLD_TRAPPED: print("child was trapped") # TODO: we could explore trap stuff here elif waitid_result.si_code == CLD_CONTINUED: print("child was continued") else: raise SystemExit( "Unknown CLD_ code: {}".format( waitid_result.si_code)) elif fdsi.ssi_signo == SIGPIPE: print("Got SIGPIPE") else: print("Read unexpected signal: {}".format( fdsi.ssi_signo)) elif fd == stdout_pair[0]: print("pipe() (stdout) descriptor ready") if events & EPOLLIN: print("Reading data from stdout...") data = read(stdout_pair[0], PIPE_BUF) print("Read {} bytes from stdout".format(len(data))) print(data) if events & EPOLLHUP: print("Removing stdout pipe from epoll") ep.unregister(stdout_pair[0]) print("Closing stdout pipe") close(stdout_pair[0]) waiting_for.remove('stdout') elif fd == stderr_pair[0]: print("pipe() (stderr) descriptor ready") if events & EPOLLIN: print("Reading data from stdout...") data = read(stderr_pair[0], PIPE_BUF) print("Read {} bytes from stderr".format(len(data))) print(data) if events & EPOLLHUP: print("Removing stderr pipe from epoll") ep.unregister(stderr_pair[0]) print("Closing stderr pipe") close(stderr_pair[0]) waiting_for.remove('stderr') else: # FIXME: we are still getting weird activation events on fd # 0 (stdin) with events == 0 (nothing). I cannot explain # this yet. print("Unexpected descriptor ready:", fd) assert not waiting_for print("Closing", ep) ep.close() print("Unblocking signals") sigprocmask(SIG_UNBLOCK, mask, None) print("Exiting normally")