#include #include #include #include #include #include // POSIX says that sigwait/sigwaitinfo/sigtimedwait only return from // a blocking wait in three cases: // // 1. A signal in the "set" argument, which was not pending upon // entry to the call, is sent to the process ("success" case) // 2. A signal that *wasn't blocked* upon entry (and is therefore // *not* a member of the "set") is delivered to the process and // "caught", i.e. a handler is executed for that signal; in this // case these functions MAY return -1 with errno set to EINTR // (probably this should honor SA_RESTART) // 3. sigtimedwait only: the timeout expires. // // Linux seems to have a kernel bug where a call to sigwaitinfo // returns -1 with errno set to EINTR when *any* signal is delivered // that was unblocked on entry and not included in the "set" argument, // even if that signal is *ignored*. This program demonstrates that bug. static volatile sig_atomic_t signal_caught = 0; static void sig_catch(int sig) { signal_caught = (sig_atomic_t)sig; } static void sig_prep(int sig, void (*handler)(int)) { struct sigaction sa; sigfillset(&sa.sa_mask); // minimize odds of races sa.sa_flags = SA_RESTART; sa.sa_handler = handler; if (sigaction(sig, &sa, 0)) { fprintf(stderr, "sigaction(%d) = %s\n", sig, strerror(errno)); exit(1); } } static timer_t timer_prep(int sig, unsigned long delay_ms) { struct sigevent ev; memset(&ev, 0, sizeof ev); ev.sigev_notify = SIGEV_SIGNAL; ev.sigev_signo = sig; timer_t timer; if (timer_create(CLOCK_MONOTONIC, &ev, &timer)) { perror("timer_create"); exit(1); } struct itimerspec interval; memset(&interval, 0, sizeof interval); interval.it_value.tv_sec = delay_ms / 1000; interval.it_value.tv_nsec = (delay_ms % 1000) * 1000000; if (timer_settime(timer, 0, &interval, 0)) { perror("timer_settime"); exit(1); } return timer; } static int test_signals(int other_caught, int other_ignored, int target, const char *label) { printf("Testing %s signals: wait for %d, ignore %d, catch %d:\n", label, target, other_ignored, other_caught); sig_prep(other_caught, sig_catch); sig_prep(other_ignored, SIG_IGN); sig_prep(target, SIG_DFL); // We block only the signal we're going to dequeue with // sigwaitinfo, which is 'target'. // The other two signals will also occur during the wait, // but *should not* interrupt the wait, because one is ignored // and the other is handled by a handler tagged SA_RESTART. sigset_t wait_for; sigemptyset(&wait_for); sigaddset(&wait_for, target); if (sigprocmask(SIG_SETMASK, &wait_for, 0)) { perror("sigprocmask"); exit(1); } // No output from this point until after all three timers have // expired, to avoid delays that might cause a timer to expire // while we're not sitting in sigwaitinfo. int signal_waited_for = 0; int signals_caught[3] = { 0, 0, 0 }; int error_codes[3] = { 0, 0, 0 }; int interrupt_count; timer_t timer_target = timer_prep(target, 30); timer_t timer_caught = timer_prep(other_caught, 20); timer_t timer_ignore = timer_prep(other_ignored, 10); for (interrupt_count = 0; interrupt_count < 3; interrupt_count++) { signal_caught = 0; errno = 0; int sig = sigwaitinfo(&wait_for, 0); if (sig > 0) { signal_waited_for = sig; break; } signals_caught[interrupt_count] = signal_caught; error_codes[interrupt_count] = errno; } timer_delete(timer_target); timer_delete(timer_caught); timer_delete(timer_ignore); int err = 0; if (interrupt_count == 0) { puts(" ok: no interrupts during wait"); } else { err = 1; printf(" FAIL: %d interrupt%s during wait\n", interrupt_count, interrupt_count == 1 ? "" : "s"); for (int i = 0; i < interrupt_count; i++) { printf(" [%d] %s", i, strerror(error_codes[i])); if (signals_caught[i] == 0) { puts("; by ignored signal"); } else { printf("; by caught signal %d\n", signals_caught[i]); signal_caught = signals_caught[i]; } } } if (signal_waited_for == target) { printf(" ok: successfully waited for signal %d\n", target); } else if (signal_waited_for == 0) { err = 1; puts(" ERR: wait interrupted too many times"); } else { err = 1; printf(" ERR: wait ended by unexpected signal %d (!= %d)\n", signal_waited_for, target); } if (signal_caught == other_caught) { printf(" ok: caught signal %d during wait, as expected\n", signal_caught); } else if (signal_caught == 0) { err = 1; puts(" ERR: caught no signals during wait"); } else { err = 1; printf(" ERR: caught unexpected signal %d (!= %d) during wait\n", signal_caught, other_caught); } return err; } int main(void) { // Exercise both traditional and realtime signals. // (Ideally, behavior would be identical.) int err = 0; err |= test_signals(SIGUSR1, SIGUSR2, SIGALRM, "traditional"); err |= test_signals(SIGRTMIN, SIGRTMIN+1, SIGRTMIN+2, "realtime"); return err; }