3

I came across this example code in my studies:

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

char* nums[5] = {"One", "Two", "Three", "Four", "Five"};
char number[6];

void handler(int n) {
    printf(" %s\n", number);
}

int main() {
    signal(SIGTSTP, handler);      // ^Z
    for(int n = 0; ; n++) {
        strcpy(number, nums[n % 5]);
    }
}

Apparently, there is something wrong with this code when it's compiled and ran. Attempting to generate the SIGTSTP with Ctrl+Z, instead of printing out one of the words in "nums" and then exiting, instead prints out the number as expected, but then continues running. It takes another SIGTSTP signal from the keyboard to actually make the program exit.

Why is this?

Edmis
  • 31
  • 1
  • 1
  • 3
  • 4
    Your signal handler doesn't cause the program to exit. Why would you expect it to? If you're using Linux, `signal()` is resetting the disposition to its default prior to calling `handler()`, which is why the second signal behaves differently. – Crowman Oct 18 '16 at 01:48

1 Answers1

4

To answer the two actual questions you had, the program continues running because your signal handler returns, and nothing you have written in your signal handler causes your program to terminate, so your program continues as normal. If you want your program to terminate, you'd need to call _exit() or _Exit() from it.

The reason a second SIGTSTP causes your program to terminate is because signal() is an outdated and unreliable interface that is also not standard across Unix-like platforms. The original behavior is that after registering your signal handler with signal(), when that signal is actually delivered, first the disposition of that signal is reset to SIG_DFL, and then your signal handler is run. So after the first delivery of that signal, your signal handler is not registered anymore, unless you call signal() again. So, when the signal is delivered to your program a second time, your signal handler is not called, and the default action is taken, which in this case is to stop the process. This behavior is different on BSD-derived systems, where the signal handler remains registered. So really, the only reliable thing you can do with signal() is to set the disposition to SIG_IGN or SIG_DFL. For anything else, you should use the modern reliable signals interface with sigaction().

There are some other problems with your program. First, printf(), along with most standard library functions, are not safe to call from a signal handler. POSIX defines a complete list of asynchronous-signal-safe functions that can be safely called from a signal handler. Instead, you should in this case set a flag, of type volatile sig_atomic_t, and set that in your handler.

Second, the way you use strcpy() here in particular is dangerous. Suppose number currently contains "Two". Then, on the next iteration of your loop, it copies in 'T', 'h', 'r', and 'e', the last character overwriting the existing terminating null character. Then the signal is delivered and this string gets passed to printf(). Even without the problems of calling printf() from a signal handler, you're sending it an array of char which is not terminated by a null character, and you'll incur undefined behavior and probably segfault.

Here's a better version of your example, using the sigaction() interface, and not calling unsafe functions from the signal handler:

#define _POSIX_C_SOURCE 200809L

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

char * nums[] = {"One", "Two", "Three", "Four", "Five"};
volatile sig_atomic_t stop;

void handler(int n)
{
    stop = 1;
}

int main(void)
{
    struct sigaction sa;
    sa.sa_handler = handler;
    sa.sa_flags = 0;
    sigemptyset(&sa.sa_mask);

    if ( sigaction(SIGTSTP, &sa, NULL) == -1 ) {
        perror("Couldn't set SIGTSTP handler");
        exit(EXIT_FAILURE);
    }

    int n = 0;
    while ( !stop ) {
        puts(nums[n]);
        n = (n + 1) % 5;
    }

    puts("Signal handler called, exiting.");

    return 0;
}

with output:

paul@horus:~/src/sandbox$ ./sig
One
Two
Three
Four
Five
One
^ZTwo
Three
Four
Five
One
Signal handler called, exiting.
paul@horus:~/src/sandbox$ 

You can see there's a delay between inputting Ctrl-Z and the actual delivery of the signal, which is perfectly normal and to be expected based on how the kernel actually delivers signals.

Crowman
  • 25,242
  • 5
  • 48
  • 56
  • This does change the original behavior by printing in a loop, using `sigprocmask` would have let it conserve the behavior by protecting calls to `strcpy`. – Jean-Baptiste Yunès Oct 18 '16 at 19:08
  • @Jean-BaptisteYunès: Yes, in a different program where it made an observable difference to copy the strings into an array, this would indeed enable that behavior to be preserved. – Crowman Oct 18 '16 at 19:18
  • You could simply move the `puts(nums[n])` outside of the loop to give the observable behavior the OP wants: print a string after TSTP, then exit. – pilcrow Oct 18 '16 at 20:56
  • @pilcrow: Hmmmm. You're right, I managed to read in the original code an output inside the loop that, in fact, isn't there. – Crowman Oct 18 '16 at 20:58