Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does alarm() cause fgets() to stop waiting?

I am playing around with signals in C. My main function basically asks for some input using fgets(name, 30, stdin), and then sits there and waits. I set an alarm with alarm(3), and I reassigned SIGALRM to call a function myalarm that simply calls system("say PAY ATTENTION"). But after the alarm goes off, fgets() stops waiting for input and my main fn continues on. This happens even if I change myalarm to just set some variable and do nothing with it.

void myalarm(int sig) {
    //system("say PAY ATTENTION");
    int x = 0;
}

int catch_signal(int sig, void (*handler)(int)) { // when a signal comes in, "catch" it and "handle it" in the way you want
    struct sigaction action;  // create a new sigaction
    action.sa_handler = handler;  // set it's sa_handler attribute to the function specified in the header
    sigemptyset(&action.sa_mask);  // "turn all the signals in the sa_mask off?" "set the sa_mask to contian no signals, i.e. nothing is masked?"
    action.sa_flags = 0; // not sure, looks like we aren't using any of the available flags, whatever they may be

    return sigaction(sig, &action, NULL);  // here is where you actually reassign- now when sig is received, it'll do what action tells it
}

int main() {

    if(catch_signal(SIGINT, diediedie)== -1) {
        fprintf(stderr, "Can't map the SIGINT handler");
        exit(2);
    }

    if(catch_signal(SIGALRM, myalarm) == -1) {
        fprintf(stderr, "Can't map the SIGALAM handler\n");
        exit(2);
    }

    alarm(3);

    char name[30];
    printf("Enter your name: ");
    fgets(name, 30, stdin);

    printf("Hello, %s\n", name);

    return 0;

}

Why does alarm() make fgets() stop waiting for input?

Edit: Added code for my catch_signal function, and, as per one of the comments, used sigaction instead of signal, but the issue persisted.

like image 269
Aaron Parisi Avatar asked Sep 10 '17 22:09

Aaron Parisi


2 Answers

The answer is most likely going to be OS/system specific.

(As stated by Retr0spectrum) The fgets() function often makes system calls, such as read(). System calls can terminate if a signal is detected. In the case of this question, the fgets() function has made a system call (likely the read() system call) to read a character from stdin. The SIGALRM causes the system call to terminate, and set errno to EINTR. This also causes the fgets() function to terminate, without reading any characters.

This is not unusual. It's just how the OS implements signals.

To avoid this problem, I will often wrap fgets() function in a loop like this:

do {
   errno=0;
   fgets(name, 30, stdin);
   } while(EINTR == errno);

It will require that you: #include <stdio.h>

(As suggested by TonyB).

like image 200
Mahonri Moriancumer Avatar answered Oct 11 '22 10:10

Mahonri Moriancumer


As to the question of why the alarm signal interrupts the read, there are two reasons:

  1. It's the way Unix used to do it, and it was because it was much easier to implement in the OS. (On the one hand this sounds kind of lame, but the "don't sweat the hard stuff" attitude was responsible in part for the success of Unix in the first place. This is the topic of Richard P. Gabriel's epic Worse Is Better essay.)

  2. It makes it easy to implement a read that times out and gives up, if that's what you want. (See my answer to this other question.)

But, as other comments and answers discuss, the interrupting behavior is somewhat obsolete; most modern systems (Unix and Linux, at least) now automatically restart an interrupted system call such as read, more or less as you were wishing. (Also as pointed out elsewhere, if you know what you're doing you may be able to select between the two behaviors.)

In the end, though, it's a grey area; I'm pretty sure the C standard leaves it unspecified or implementation-defined or undefined what happens if you interrupt a system call with an alarm or other signal.

like image 22
Steve Summit Avatar answered Oct 11 '22 10:10

Steve Summit