Search code examples
c++clinuxsignals

Abort all children of a process in its signal handler


I need to send SIGABRT to all children of my process in a signal handler of this process.

in this answer and in this answer and in some others, it is suggested to go through all processes' directories in /proc. UPD: I have just discovered even a better /proc way.

For this, one needs opendir, readdir, closedir, etc. But they are not async-signal-safe. In this answer, it is explained that this is because "opendir() calls malloc(), so you can't run it from within the handler". As far as I understand, the problem is that malloc() can be called by the same thread two times: in the handler and in the working code of the thread. So you get a deadlock, as it happens in this question: malloc inside linux signal handler cause deadlock.

To solve a similar problem Is there an async-signal-safe way of reading a directory listing on Linux?, it is suggested:

If you know in advance which directory you need to read, you could call opendir() outside the signal handler (opendir() calls malloc(), so you can't run it from within the handler) and keep the DIR* in a static variable somewhere.

But in my case I don't know the directories beforehand, as I don’t know which children processes with which pids will exist at the point when the signal handler is called.

Is there any way to safely find and kill all the children processes of my process in its signal handler? Thank you for attention.


Solution

  • The easy way is to use a process group for all the children and send a signal to all processes in it at once.

    Example program:

    #include <signal.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    
    // Called by the parent on a SIGUSR1
    void parent_handler(int signo __attribute__((__unused__))) {
      const char msg[] = "SIGUSR1 received in parent.\n";
      write(STDOUT_FILENO, msg, sizeof msg);
      kill(0, SIGABRT); // Send signal to all processes in its group
    }
    
    // Called by the children on a SIGABRT
    void child_abort_handler(int signo __attribute__((__unused__))) {
      const char msg[] = "SIGABRT received in child.\n";
      write(STDOUT_FILENO, msg, sizeof msg);
      _exit(0);
    }
    
    // Children processes set up a signal handler and wait for a signal
    void do_child(void) {
      struct sigaction act;
      memset(&act, 0, sizeof act);
      act.sa_handler = child_abort_handler;
      sigaction(SIGABRT, &act, NULL);
      pause();
    }
    
    int main(void) {
      // Make sure this process is in a new process group
      if (setpgid(0, 0) < 0) {
        perror("setpgid");
        return EXIT_FAILURE;
      }
    
      // Create some child processes
      for (int i = 0; i < 5; i++) {
        pid_t child = fork();
        if (child < 0) {
          perror("fork");
        } else if (child > 0) {
          do_child();
        }
      }
    
      // Block SIGABRT in parent
      sigset_t mask;
      sigemptyset(&mask);
      sigaddset(&mask, SIGABRT);
      sigprocmask(SIG_BLOCK, &mask, NULL);
    
      // Set up parent SIGUSR1 handler
      struct sigaction act;
      memset(&act, 0, sizeof act);
      act.sa_handler = parent_handler;
      if (sigaction(SIGUSR1, &act, NULL) < 0) {
        perror("parent sigaction");
        kill(0, SIGTERM);
        return EXIT_FAILURE;
      }
    
      printf("Please, type: kill -10 %d\n", (int)getpid());
      fflush(stdout);
    
      pause();
    
      // Exit normally after the sighandler and pause return.
      return 0;
    }
    

    and usage:

    $ gcc -Wall -Wextra -O -o sigtest sigtest.c
    $ ./sigtest &
    Please kill -USR1 12345
    $ kill -USR1 12345
    SIGUSR1 received in parent.
    SIGABRT received in child.
    SIGABRT received in child.
    SIGABRT received in child.
    SIGABRT received in child.
    SIGABRT received in child.
    $