As you discovered you cannot invoke SIG_DFL and SIG_IGN per se.
Briefly, imitating normal signal disposition would be quite easy for user-defined sa_handler
s, easy enough for SIG_IGN with the caveat that you'd need to waitpid() in the case of CHLD, and more-or-less straightforward but unpleasant for SIG_DFL, re-raising to let the kernel do its magic.
Does this do what you want?
#include <signal.h>
#include <stdlib.h>
/* Manually dispose of a signal, mimicking the behavior of current
* signal dispositions as best we can. We won't cause EINTR, for
* instance.
*
* FIXME: save and restore errno around the SIG_DFL logic and
* SIG_IGN/CHLD logic.
*/
void dispatch_signal(const int signo) {
int stop = 0;
sigset_t oset;
struct sigaction curact;
sigaction(signo, NULL, &curact);
/* SIG_IGN => noop or soak up child term/stop signals (for CHLD) */
if (SIG_IGN == curact.sa_handler) {
if (SIGCHLD == signo) {
int status;
while (waitpid(-1, &status, WNOHANG|WUNTRACED) > 0) {;}
}
return;
}
/* user defined => invoke it */
if (SIG_DFL != curact.sa_handler) {
curact.sa_handler(signo);
return;
}
/* SIG_DFL => let kernel handle it (mostly).
*
* We handle noop signals ourselves -- "Ign" and "Cont", which we
* can never intercept while stopped.
*/
if (SIGURG == signo || SIGWINCH == signo || SIGCONT == signo) return;
/* Unblock CONT if this is a "Stop" signal, so that we may be
* woken up.
*/
stop = (SIGTSTP == signo || SIGTTIN == signo || SIGTTOU == signo);
if (stop) {
sigset_t sig_cont;
sigemptyset(&sig_cont);
sigaddset(&sig_cont, SIGCONT);
sigprocmask(SIG_UNBLOCK, &sig_cont, &oset);
}
/* Re-raise, letting the kernel do the work:
* - Set exit codes and corefiles for "Term" and "Core"
* - Halt us and signal WUNTRACED'ing parents for "Stop"
* - Do the right thing if we forgot to handle any special
* signals or signals yet to be introduced
*/
kill(getpid(), signo);
/* Re-block CONT, if needed */
if (stop) sigprocmask(SIG_SETMASK, &oset, NULL);
}
UPDATE
(in response to OP's excellent questions)
1: does this slot in after the sigwaitinfo?
Yes. Something like:
... block signals ...
signo = sigwaitinfo(&set, &info);
dispatch_signal(signo);
*2: Why not raise those signals handled by SIG_IGN, they'll be ignored anyway*
It's slightly more efficient to noop in userspace than by three syscalls (re-raise, unmask, re-mask). Moreover, CHLD has special semantics when SIG_IGNored.
3: Why treat SIGCHLD specially?
Originally (check answer edits) I didn't -- re-raised it in the SIG_IGN case,
because IGNored CHLD signals tell the kernel to automatically reap children.
However, I changed it because "natural" CHLD signals carry information about
the terminated process (at least PID, status, and real UID).
User-generated CHLD signals don't carry the same semantics, and, in my testing,
IGNoring them doesn't cause 2.6 to autoreap queued zombies whose SIGCHLD
was "missed." So, I do it myself.
4: Why are "stop" related signals unblocking CONT. Will not invoking the default handler for CONT unstop the process?
If we're stopped (not executing) and CONT is blocked, we will never receive the
signal to wake us up!
5: Why not call raise instead of the kill line you've given?
Personal preference; raise()
would work, too.