Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ctrl-C eaten by getchar()

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 :)

like image 434
julien.d Avatar asked Dec 02 '22 00:12

julien.d


2 Answers

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.

like image 151
Grimflo Avatar answered Dec 20 '22 14:12

Grimflo


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.

like image 41
Paul R Avatar answered Dec 20 '22 13:12

Paul R