Reputation: 1159
Why does my program not end until I press ENTER in terminal after pressing Ctrl+C?
Here is my code:
static volatile sig_atomic_t keepRunning = 1;
void intHandler(int sig)
{
keepRunning = 0;
}
int main(int argc, char *argv[])
{
signal(SIGINT, intHandler);
int ch;
while((ch = fgetc(stdin)) && keepRunning)
{
...
}
exit(EXIT_SUCCESS);
}
I have setup my while loop to read chars from stdin and to run until the SIGINT
is caught. After that the keepRunning
will be set to 0
and loop should end and terminate the program. However when I hit Ctrl+C my program doesn't accept any input anymore but it doesn't let me type any command in terminal until I press ENTER key. Why is that?
Upvotes: 2
Views: 1354
Reputation: 3308
It is because fgetc()
is blocking the execution, and the way you chose to handle SIGINT - fgetc()
will NOT be interrupted with EINTR (see @AnttiHaapala's answer for further explanation). So only after you press enter, which releases fgetc()
, keepRunning is being evaluated.
The terminal is also buffered, so only when you press enter it will send the chars to the FILE *
buffer and will read by fgetc()
one by one. This is why it exists only after pressing enter, and not other keys.
One of several options to "solve" it is to use nonblocking stdin, signalfd and epoll (if you use linux):
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <sys/epoll.h>
#include <sys/signalfd.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <error.h>
int main(int argc, char *argv[])
{
sigset_t mask;
sigemptyset(&mask);
sigaddset(&mask, SIGINT);
/* Block signals so that they aren't handled
according to their default dispositions */
sigprocmask(SIG_BLOCK, &mask, NULL); // need check
// let's treat signal as fd, so we could add to epoll
int sfd = signalfd(-1, &mask, 0); // need check
int epfd = epoll_create(1); // need check
// add signal to epoll
struct epoll_event ev = { .events = EPOLLIN, .data.fd = sfd };
epoll_ctl(epfd, EPOLL_CTL_ADD, sfd, &ev); // need check
// Make STDIN non-blocking
fcntl(STDIN_FILENO, F_SETFL, fcntl(STDIN_FILENO, F_GETFL) | O_NONBLOCK);
// add STDIN to epoll
ev.data.fd = STDIN_FILENO;
epoll_ctl(epfd, EPOLL_CTL_ADD, STDIN_FILENO, &ev); // need check
char ch;
int keepRunning = 1; // no need to synchronize anymore
while(keepRunning) {
epoll_wait(epfd, &ev, 1, -1); // need check, must be always 1
if (ev.data.fd == sfd) {
printf("signal caught\n");
keepRunning = 0;
} else {
ssize_t r;
while(r = read(STDIN_FILENO, &ch, 1) > 0) {
printf("%c", ch);
}
if (r == 0 && errno == 0) {
/* non-blocking non-eof will return 0 AND EAGAIN errno */
printf("EOF reached\n");
keepRunning = 0;
} else if (errno != EAGAIN) {
perror("read");
keepRunning = 0;
}
}
}
fcntl(STDIN_FILENO, F_SETFL, fcntl(STDIN_FILENO, F_GETFL) & ~O_NONBLOCK);
exit(EXIT_SUCCESS);
}
Also note that I'm not using fgetc()
. Because of buffering nature of FILE *
, it will not work well with nonblocking IO.
The program above is intended for education purposes only and not for "production" use. There are several issue that need attention, for example:
printf()
may easily be slower), it may cause starvation and the signal will not get caught (the inner loop will exit only after input is over/slower).read()
can fill much larger buffer.epoll_wait
can return multiple events instead of 1.Upvotes: 2
Reputation: 133849
Usually system calls return with errno == EINTR
if a signal was delivered when they're blocking, which would cause fgetc
to return early with an error condition as soon as Control-C was hit. The problem is that the signal set by signal
will be set to auto restarting mode, i.e. the underlying read
system call would be restarted as soon as the signal handler completed.
The correct fix would be to remove the automatic restart but it does make it slightly trickier to use correctly. Here we see if the return value is EOF
from fgetc
and then if it is caused by EINTR
and restart the loop if the boolean was not true.
struct sigaction action = {
.sa_flags = 0,
.sa_handler = intHandler
};
sigaction(SIGINT, &action, NULL);
int ch;
while (1) {
ch = fgetc(stdin);
if (ch == EOF) {
if (errno == EINTR) {
if (keepRunning) {
continue;
}
break;
}
break;
}
}
Upvotes: 0