3

Consider the following C code for a POSIX system:

#include <stdio.h>
#include <sys/types.h>
#include <signal.h>
#include <unistd.h>

#define CONTINUE_SIGNAL SIGINT

void continue_handler(int sig, siginfo_t *info, void *context)
{
    printf("[P] Continuing process with pid = %d...\n", getpid());
}

int main(void)
{
    struct sigaction act;
    sigset_t mask;
    pid_t pid;

    // Block the CONTINUE_SIGNAL for now.
    sigemptyset(&mask);
    sigaddset(&mask, CONTINUE_SIGNAL);
    sigprocmask(SIG_BLOCK, &mask, NULL);

    if ((pid = fork()) == 0)  {
        printf("[C] This is the child (pid %d).\n", getpid());
        return 0;
    }

    printf("[P] Parent (pid %d) has spawned child (pid %d).\n", getpid(), pid);

    // Call the 'continue_handler' when CONTINUE_SIGNAL is received.
    act.sa_sigaction = continue_handler;
    act.sa_flags = SA_SIGINFO;
    sigaction(CONTINUE_SIGNAL, &act, NULL);
    printf("[P] Waiting for CONTINUE_SIGNAL...\n");

    // Block all signals except CONTINUE_SIGNAL and wait for it to occur.
    sigfillset(&mask);
    sigdelset(&mask, CONTINUE_SIGNAL);
    sigsuspend(&mask);
    printf("[P] Signal received. Exiting...\n");

    return 0;
}

Note that I have removed all error checking to be able to represent it in a more compact form. However, I have verified that all the functions return successfully in both of the following cases.

On a Linux system, the code does exactly what I expect it to do: The parent process spawns a child process and waits for SIGINT before it continues to execute continue_handler.

[P] Parent (pid 804) has spawned child (pid 805).
[P] Waiting for CONTINUE_SIGNAL...
[C] This is the child (pid 805).
  <press Ctrl+C>
[P] Continuing process with pid = 804...
[P] Signal received. Exiting...

On macOS, however, I obtain the following output (without any interaction):

[P] Parent (pid 6024) has spawned child (pid 6025).
[P] Waiting for CONTINUE_SIGNAL...
[C] This is the child (pid 6025).
[P] Signal received. Exiting...

Obviously, the sigsuspend call of the parent process (pid 6024) returns immediately after the child process exits. Although SIGINT does not seem to be triggered, sigsuspend returns an errno of EINTR, i.e., reports a successful termination by one of the non-masked signals. Note that if I prevent the child process from exiting, sigsuspend does not return by itself and instead waits for SIGINT to be delivered.

Is there an issue with the way I am using the API? Or is there maybe a certain degree of flexibility in the POSIX specification that makes both of these behaviors expected?

  • Two things for you to try: (1) Use a completely full mask (all signals blocked) in the initial call to sigprocmask. (2) Install a handler for SIGCHLD as well as SIGINT. Does that handler get called? – zwol Jun 07 '20 at 23:21
  • @zwol I've tried it out: (1) Blocking all signals in the initial `sigprocmask` call does not change the behavior. (2) Installing a handler to `SIGCHLD` while keeping `CONTINUE_SIGNAL` constant does not change the behavior either, i.e., does not result in a call of the new handler. And although you didn't ask for it explicitly: (3) Setting `CONTINUE_SIGNAL` to `SIGCHLD` instead of adding a second handler causes the original handler to be called. –  Jun 07 '20 at 23:28
  • 2
    You can not safely call `printf()` from within a signal handler. In strictly-conforming C, it's not safe to call any library function. Per [footnote 188 of the C 11 standard](https://port70.net/~nsz/c/c11/n1570.html#note188): "Thus, a signal handler cannot, in general, call standard library functions." [POSIX allows for the calling of async-signal-safe functions](https://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html#tag_15_04_03) and only async-signal-safe functions from within a signal handler. `printf()` is not async-signal-safe. – Andrew Henle Jun 07 '20 at 23:56
  • 2
    @AndrewHenle That's not a concern for this program because the signal is delivered only when primary control flow is blocked in sigsuspend. – zwol Jun 08 '20 at 02:06
  • 3
    @bosonic I'm afraid I'm stumped. It almost smells like a bug in OSX's implementation of sigsuspend. Can you test your program on one of the fully open source BSDs? If you can reproduce the same behavior there, it'll be easier to fig into. – zwol Jun 08 '20 at 02:08
  • 2
    This can be demonstrated without the sigaction complication. Set an alarm for five seconds. Fork a child that sleeps for a second or two before terminating. Have the parent `sigsuspend` allowing SIGALRM only. On OS X the parent's `sigsuspend` will be EINTRrupted and execution resumes; whereas on Linux, the parent is terminated by an alarm clock. – pilcrow Jun 08 '20 at 13:32
  • there are several functions that are 'unsafe' to use in a signal handler. The function: `printf()` is one of functions to NOT use. Suggest using: `write( 1, .... );` – user3629249 Jun 08 '20 at 18:55
  • Normally, the parent process waits for the child process to exit via either `wait()` or `waitpid()` which only takes a single line of code, so why complicate things by involving signals and all the related code? – user3629249 Jun 08 '20 at 18:57
  • 1
    @user3629249, signal handlers and normal reaping practices are red herrings. The question here is, Why, on OS X, is `sigsuspend` _with SIGCHLD masked off_ interrupted by child process termination? – pilcrow Jun 08 '20 at 19:36
  • Is SIGCHLD maskable? – user3629249 Jun 08 '20 at 19:48
  • @zwol Thank you for your suggestion. I have now executed the same code on FreeBSD 12.1. On this OS, the application waits for me to issue a `SIGINT` before continuing. I'm afraid that analyzing the FreeBSD sources probably won't help to track this issue down. –  Jun 08 '20 at 20:38
  • @bosonic At this point I think you should report this as a bug to Apple. – zwol Jun 09 '20 at 00:53
  • @zwol I have submitted this "developer feedback," with a simpler demonstration, to Apple as FB7731811. – pilcrow Jun 10 '20 at 12:40

1 Answers1

0

Please read: signals in MAX OS X

especially the statement:

To ignore the signal, func should be SIG_IGN. 

somehow, I do not see that in your code.

user3629249
  • 16,402
  • 1
  • 16
  • 17