-1

Given this code:

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

void sigint_handler(int h)
{
    printf("Hey! I caught a SIGINT! :)\n");
}

int main()
{
    struct sigaction act;
    act.sa_handler = &sigint_handler;

    if (0 != sigaction(SIGINT, &act, NULL)) {
        perror("unable to setup the SIGINT handler!\n");

        return 1;
    }

    while(1) { }

    return 0;
}

compiled with gcc 7.2.0 (kernel 4.13.12) using the following options: -Wall -pedantic -ansi -std=gnu11.

The first signal is always caught, but sometimes, the second one is not caught, and sometimes it is.

I encountered this bug while spamming Ctrl-C at process' startup.

What did I miss to catch all signals?

Jules Lamur
  • 2,078
  • 1
  • 15
  • 25
  • 2
    Your SIGINT handler has only one line - a call to a function that is not async-signal safe:( – Martin James Jan 03 '18 at 01:30
  • @MartinJames Thank you for your help! It's quickly explained in `signal-safety(7)`. – Jules Lamur Jan 03 '18 at 01:38
  • 1
    You've no idea what the other fields in the `struct sigaction` are set to because you didn't initialize `act`. Maybe you will get better behaviour if you set the documented fields to know values. You can use `write()` in a POSIX signal handler (not in standard C, but fortunately you're not using standard C). You shouldn't use `printf()`, though in this context it is unlikely to cause any trouble. One minor advantage of `write()` — there's no application level buffering to worry about. – Jonathan Leffler Jan 03 '18 at 06:33
  • Is your problem resolved? If so, maybe you should delete this question. – Jonathan Leffler Jan 03 '18 at 06:34
  • 1
    @JonathanLeffler Thanks for your help. I prefer to post an answer based on the problems you've pointed instead of deleting my question, with the hope that it will help someone later. – Jules Lamur Jan 03 '18 at 10:09

2 Answers2

2

As Martin James observed in a comment:

Your SIGINT handler has only one line - a call to a function that is not async-signal safe:(

Somewhat later, I observed:

You've no idea what the other fields in the struct sigaction are set to because you didn't initialize act. Maybe you will get better behaviour if you set the documented fields to known values. You can use write() in a POSIX signal handler (not in standard C, but fortunately you're not using standard C). You shouldn't use printf(), though in this context it is unlikely to cause any trouble. One minor advantage of write() — there's no application level buffering to worry about.

The question How to avoid using printf() in a signal handler discusses which functions can be used in a signal handler. Note that the functions from the <string.h> header such as strlen() and strchr() are not listed amongst those that are async-signal safe. I find that omission puzzling, but that's what POSIX (2008 and earlier) says. (This was accurate for POSIX 2008. One of the changes in POSIX 2016 is that a number of signal-safe routines have been added to the list in Signal Concepts, including both strlen() and strchr() — this makes a lot of sense to me.)

I adapted your code like this:

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

static void sigint_handler(int h)
{
    char message[] = "Hey! I caught a SIGINT x! :\n";
    char *x = message;
    while (*x != 'x' && *x != '\0')
        x++;
    if (*x != '\0')
        *x = (h % 10) + '0';
    write(1, message, sizeof(message) - 1);
}

int main(void)
{
    struct sigaction act = { 0 };
    act.sa_handler = &sigint_handler;

    if (0 != sigaction(SIGINT, &act, NULL))
    {
        perror("Unable to setup the SIGINT handler!\n");
        return 1;
    }

    while (1)
    {
        printf("Pausing for a moment...\n");
        pause();
        printf("You interrupted my dozing\n");
    }

    return 0;
}

The code uses pause() rather than spinning in a busy-loop. It's an unusual system call; it never returns normally (the exec*() family of functions never return normally either).

I compile with stringent warning options:

$ gcc -O3 -g -std=c11 -Wall -Wextra -Werror -Wmissing-prototypes \
>     -Wstrict-prototypes sig13.c -o sig13
$

If I didn't use h (the argument to the signal handler), the code wouldn't compile, so I used it. The code avoids using string handling functions (char *x = strchr(message, 'x'); if (x != 0) *x = (h % 10) + '0'; would be clearer). The function is static because it won't be used outside this file — so there isn't a header to declare it.

When executed (on a Mac running macOS High Sierra 10.13.2, using GCC 7.2.0), it produces output like:

$ ./sig13
Pausing for a moment...
^CHey! I caught a SIGINT 2! :
You interrupted my dozing
Pausing for a moment...
^CHey! I caught a SIGINT 2! :
You interrupted my dozing
Pausing for a moment...
^CHey! I caught a SIGINT 2! :
You interrupted my dozing
Pausing for a moment...
^CHey! I caught a SIGINT 2! :
You interrupted my dozing
Pausing for a moment...
^CHey! I caught a SIGINT 2! :
You interrupted my dozing
Pausing for a moment...
^\Quit: 3
$

The main moral to this is "make sure your variables are properly initialized". A secondary moral is to make sure your signal handler is clean.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • 1
    Thanks for your complete answer. BTW, in `signal-safety(7)`, both `strlen` and `strchr` are listed as *async-signal-safe* with the mention: *"Added in POSIX.1-2016"*. – Jules Lamur Jan 03 '18 at 15:40
  • Hmmm; you're right. I hadn't gone back to look at the list recently, as you can tell. I have some updating to do. Thank you! (And thank you POSIX for putting some sanity into the list of signal safe functions.) – Jonathan Leffler Jan 03 '18 at 15:53
0

I had two problems with my code, first of all, as mentionned by @MartinJames and @j31d0, the printf function is not async-signal-safe, so it can't be used inside the signal handler. It can be easily replaced by the write system-call which is async-signal-safe:

char message[255] = "Hey! I caught a SIGINT :)\n";
write(1, message, 255);

Secondly, the variable act was not properly initialized (as mentionned by @JonathanLeffler):

sigset_t mask;
struct sigaction act;

sigemptyset(&mask);
act.sa_handler = &sigint_handler;
act.sa_mask = mask;
act.sa_flags = 0;

Finally, a working code would then be the following:

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

void sigint_handler(int h)
{
    char message[255] = "Hey! I caught a SIGINT :)\n";
    write(1, message, 255);
}

int main()
{
    sigset_t mask;
    struct sigaction act;

    sigemptyset(&mask);
    act.sa_handler = &sigint_handler;
    act.sa_mask = mask;
    act.sa_flags = 0;

    if (0 != sigaction(SIGINT, &act, NULL)) {
        perror("unable to setup the SIGINT handler!\n");

        return 1;
    }

    while(1) { }

    return 0;
}

Hope this helps!

Jules Lamur
  • 2,078
  • 1
  • 15
  • 25