slave: no need to restore signal handlers; they are automatically
restored as long as they are not SIG_IGN (which they never are in
foot).
spawn(): restore signal mask after fork. This fixes an issue where a
terminal spawned with ctrl+shift+n did not terminate when its shell
exited.
Closes#366
Add fdm_signal_add() and fdm_signal_del(). Signals added to the fdm
will be monitored, and the provided callback called as “soon as
possible” from the main context (i.e not from the signal handler
context).
Monitored signals are *blocked* by default. We use epoll_pwait() to
unblock them while we’re polling. This allows us to do race-free
signal detection.
We use a single handler for all monitored signals; the handler simply
updates the signal’s slot in a global array (sized to fit SIGRTMAX
signals).
When epoll_pwait() returns EINTR, we loop the global array. The
callback associated with each signal that fired is called.
This fixes an issue where foot --server did not exit on SIGINT. This
happened because we never returned out from fdm_poll(), and thus we
never saw ‘aborted’ being set.
There are now three hook priorities: low, normal, high.
High priority hooks are executed *first*. Normal next, and last the
low priority hooks.
The renderer's terminal refresh hook now runs as a normal priority
hook.
This behavior is debatable, but helps in error handling where we're
removing a bunch of descriptors where not all of them have been added
to the FDM yet.
It is perfectly possible, and legal, for a FDM handler to delete
another handler. The problem however is when the epoll returned array
of FD events contain the removed FD handler *after* the handler that
removed it.
That is, if the epoll returned array is:
[FD=13, FD=37]
and the handler for FD=13 removes FD=37, then given the current
implementation, the FD user data (our handler callback etc) will point
to a free:d address.
Add support for this situation by deferring FD handler removal *when
called from another handler*.
This is done by "locking" the FDM struct before looping the handlers
for FDs with events (and unlocking afterwards).
In fdm_del(), we check if the FDM has been locked, in which case the
FD is marked as deleted, and put on a deferred list. But
not *actually* deleted.
Meanwhile, in the FDM poll function, we skip calling handlers for
marked-for-deletion FDs.
Then, when all FDs have been processed, we loop the deferred list and
finally deletes the FDs for real.