Search code examples
cmacoslanguage-lawyersigprocmasksigaction

OS X sigaction incorrectly setting sa_mask


On a macbook (OSX 10.9.5 (13F34)) the following simple program:

#include <stdio.h>
#include <signal.h>

static void nop(int unused) { }

int
main(void) {
    struct sigaction sa, osa;
    sigset_t mask;

    sigemptyset(&sa.sa_mask);
    printf("Errno after sigempty sa_mask: %d\n", errno);
    sigemptyset(&osa.sa_mask);
    printf("Errno after sigempty oldsa_mask: %d\n", errno);
    sa.sa_flags = 0;
    sa.sa_handler = nop;

    sigprocmask(0, NULL, &mask);
    printf("Errno after sigprocmask mask: %d\n", errno);
    printf("%d\n", sigismember(&mask, SIGALRM));

    sigaction(SIGALRM, &sa, &osa);
    printf("Errno after sigaction sa osa: %d\n", errno);
    printf("%d\n", sigismember(&osa.sa_mask, SIGALRM));
    printf("%d\n", sigismember(&sa.sa_mask, SIGALRM));

    return 0;
}

Mysteriously prints:

Errno after sigempty sa_mask: 0
Errno after sigempty oldsa_mask: 0
Errno after sigprocmask mask: 0
0
Errno after sigaction sa osa: 0
1
0

I would expect that the sa_mask member of osa to match mask as given by sigprocmask.

Does POSIX specify any requirements for this field? The only mention of it in the manpages is with regard to unblockable signals like SIGKILL, where that value is unspecified.

On linux, this program prints:

Errno after sigempty sa_mask: 0
Errno after sigempty oldsa_mask: 0
Errno after sigprocmask mask: 0
0
Errno after sigaction sa osa: 0
0
0

as expected.

The gcc version is:

$ gcc --version
Configured with: --prefix=/Applications/Xcode.app/Contents/Developer/usr --with-gxx-include-dir=/usr/include/c++/4.2.1
Apple LLVM version 6.0 (clang-600.0.54) (based on LLVM 3.5svn)
Target: x86_64-apple-darw

The binary is linked against:

/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1197.1.1)

Solution

  • I would expect that the sa_mask member of osa to match mask as given by sigprocmask.

    Your expectation is incorrect.

    Here's a link to the current (as of this writing) official POSIX documentation. Bookmark it!

    Here is what that documentation says about sa_mask:

    Additional set of signals to be blocked during execution of signal-catching function.

    There is no reason to expect osa.sa_mask to match mask. Your mask is the current signal mask. Your osa.sa_mask is an additional signal mask that would have been applied during a call to osa.sa_handler to handle a SIGALRM.

    In your test case, osa.sa_handler is SIG_DFL, so the contents of osa.sa_mask is irrelevant. As far as I know (after a brief search), POSIX says nothing about what osa.sa_mask should be when, as in your test case, the process hasn't set an action for the signal since the most recent exec.

    Furthermore, when the system calls an installed SIGALRM handler, it includes SIGALRM in the signal mask automatically (unless you passed SA_NODEFER or SA_RESETHAND when you installed the handler). Quoting the documentation linked above:

    When a signal is caught by a signal-catching function installed by sigaction(), a new signal mask is calculated and installed for the duration of the signal-catching function (or until a call to either sigprocmask() or sigsuspend() is made). This mask is formed by taking the union of the current signal mask and the value of the sa_mask for the signal being delivered, and unless SA_NODEFER or SA_RESETHAND is set, then including the signal being delivered.

    Thus it's irrelevant whether sa_mask includes SIGALRM if sa_flags is 0. The fact that Linux doesn't include it and OS X does makes no difference to the handling of the signal.

    Note too that it's not clear (to me) that it's legal to pass 0 (instead of one of the defined constants) for the how argument of sigprocmask, even when the set argument is null. But I found that changing it to SIG_BLOCK made no difference.