4

I've been searching for a solution to my problem for a long time now that's why i'm turning to you:

Consider this piece of code:

static char done = 0;
static void sigHandler(void)
{
    done = 1;
}

int user_input()
{
    return (getchar() == 'q') ? 0 : 1;
}

int main(void)
{
    signal(SIGTERM, sigHandler);
    signal(SIGINT, sigHandler);
    while (user_input() != 0 && !done)
        usleep(1000);
    printf("exiting\n");
    return 0;
}

Expected behavior: The program exits when user inputs q then enter. If CTRL+C is pressed, it is caught by the sigHandler function which sets the flag 'done' to 1 and exits the program.

Observed behavior: The CTRL+C character is eaten by the getchar() call, and the sigHandler function is never executed. When CTRL+C and then enter is pressed, the sigHandler function is called and the program exits.

Could someone with more experience and knowledge help me on that one?

Thanks for your input :)

Mwiza
  • 7,780
  • 3
  • 46
  • 42
julien.d
  • 63
  • 1
  • 5
  • Even if that's what i didn't want to do in the beginning, I have solved my problem by not using a flag and just plain exiting from the handler. – julien.d Nov 02 '11 at 14:16

3 Answers3

6

The code is actually working as expected - you are not testing the done flag until after you return from user_input(), which is why you need to enter an additional character after the control-C.

If you want to abort the call to getchar when you get a control-C then you'll probably have to do something ugly, e.g. use setjmp/longjmp.

Paul R
  • 208,748
  • 37
  • 389
  • 560
6

The Ctrl-C character is eaten by the getchar() call, and the sigHandler function is never executed.

Ctrl-C is not eaten by getchar; it results in a signal being delivered and sigHandler being run. This sets done and returns. Only then is getchar called, which eats the newline and after that, done is checked so the program exits.

Btw., a signal handler takes an int argument, not void.

Fred Foo
  • 355,277
  • 75
  • 744
  • 836
6

There IS a way to abort the call without resorting to ugly hacks (contrarily to what Paul R said). You should use sigaction() with sa_flags set to 0 instead of signal().

Besides, the signal(2) manual says:

Avoid its use: use sigaction(2) instead.

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

static char done = 0;
static void sigHandler(int signum)
{
    done = 1;
}

int user_input()
{
    return (getchar() == 'q') ? 0 : 1;
}

int main(void)
{
    struct sigaction sa;
    memset(&sa, 0, sizeof(struct sigaction));
    sa.sa_handler = sigHandler;
    sa.sa_flags = 0;// not SA_RESTART!;

    sigaction(SIGINT, &sa, NULL);
    sigaction(SIGTERM, &sa, NULL);

    while (user_input() != 0 && !done)
        usleep(1000);
    printf("exiting\n");
    return 0;
}

Normally, after catching and handling a signal, most (I'm not sure if not all) syscalls will be restarted. This way, after handling the sigint signal, your getchar function will continue as if nothing happened. You can change this behavior by calling sigaction with sa_flags=0.

This way, after handling SIGINT, getchar will return -1 and errno will be set to "Interrupted system call" (I don't remember the constant name right now).

You would also have to rewrite your user_input() function to handle the case when returning -1.

Grimflo
  • 116
  • 4
  • If you set SA_RESTART in sa_flags, then system calls are supposed to restart. In practice, this is often not implemented correctly so it is best to not set SA_RESTART and expect that system calls may terminate as a result of a signal. However, getchar is not a system call, and it will not return prematurely as the result of a signal. – William Pursell Nov 02 '11 at 21:13
  • @William Pursell: While getchar is obviously not a system call, a call to the system call read (identified by number __NR_read) is made somewhere in getchar. Without this call you wouldn't be able to read anything from stdin regardless of the high level function name. And that system call will or will not be restarted depending on the sigaction flags. If still in doubt, just compile the code I posted and see it actually works! :P – Grimflo Nov 02 '11 at 23:18
  • Yes, getchar calls read. But getchar does not return upon receipt of a signal, because getchar checks if the read fails because of a signal and restarts it. SA_RESTART is totally irrelevant to whether or not getchar returns. – William Pursell Nov 03 '11 at 00:42