Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Strange sigaction() and getline() interaction

I have a signal handler set up using sigaction like so:

struct sigaction act, oldact;
memset(&act, 0, sizeof(struct sigaction));
act.sa_handler = sig_handler;
sigemptyset(&act.sa_mask);
sigaddset(&act.sa_mask, SIGALRM);
sigaddset(&act.sa_mask, SIGINT);
sigaddset(&act.sa_mask, SIGTERM);
sigaddset(&act.sa_mask, SIGTSTP);
sigaction(SIGALRM, &act, &oldact);
sigaction(SIGINT, &act, &oldact);
sigaction(SIGTERM, &act, &oldact);
sigaction(SIGTSTP, &act, &oldact);
act.sa_flags = 0;

Following this I run a for loop to gather input and print it back out (basically acting like cat).

char *linebuf = NULL;
size_t n = 0;
int len;
while(1){
    len = getline(&linebuf, &n, stdin);
    if(len>0){
        printf("%s", linebuf);
    }
}

However, once I return from handling a signal, getline() no longer blocks for input, and instead consistently returns -1 while setting errno to EINTR which is an interrupted system call. I obviously have intended to interrupt getline(), but how do I reset it so that I can continue reading input?

There were a few similar questions that didn't really solve my problem, but it might help you understand the issue better. 1 2

Another interesting tidbit is that I did NOT have this problem when I was using signal() for my signal handling, but I changed to sigaction() because it is POSIX compliant.

like image 600
Connor Avatar asked Dec 26 '22 19:12

Connor


2 Answers

A very late answer, but I'll share something I learned recently dealing with this. When you set the sa_flags to 0 in the sigaction structure, you cleared the default behavior for read upon getting a signal. When you use the old signal() method, this default behavior is preserved. To get this back in your case you would have to set the sa_flags = SA_RESTART. In this case though, the signal handler would be called, and getline would resume waiting for input if it was not in the middle of a read. You will still get -1 if the signal occurred in the middle of a read, and you would have to clear() the stream before resuming. But if the read was blocked waiting for data and the signal occurs, with SA_RESTART, you immediately go back into the read after the signal handler executes successfully.

I personally wanted to be able to interrupt the getline() so I could nicely terminate a program waiting on a pipe and had trouble because I used the old signal() method to setup my handler. So I would get the signal, but getline would never get interrupted because read was blocked waiting for data and it would just restart the read automatically after my signal handler executed. When I moved to the sigaction structure and set the sa_flags=0, it interrupted the getline causing it to return EINTR basically, and I could send a signal to my process to tell it to shutdown.

I love this forum. I cannot count how many times it has helped solve a sticky issue. Thought I'd give back. Hope this helps the next programmer!

like image 140
GatchamanDad Avatar answered Dec 28 '22 09:12

GatchamanDad


One of the questions you linked to actually has your answer. But first, a couple of references. Note that getline is internally using other functions (probably read).

From the man page of read:

If a read() is interrupted by a signal before it reads any data, it shall return -1 with errno set to [EINTR].

...

... there is an additional function, select(), whose purpose is to pause until specified activity (data to read, space to write, and so on) is detected on specified file descriptors. It is common in applications written for those systems for select() to be used before read() in situations (such as keyboard input) where interruption of I/O due to a signal is desired.

From the man page of select:

The pselect() function shall examine the file descriptor sets ... to see whether some of their descriptors are ready for reading, are ready for writing, or have an exceptional condition pending, respectively.

So you can use select to know when it's safe to start reading. However, that still doesn't prevent read from getting interrupted later. (I just said this for your information).

To actually clear the error flag of your stream, as this answer to one of your linked questions states (although in C++), you need to use the clearerr function to clear any error flags on the stream. Note that the man page (as well as the C standard) doesn't say specifically that it also clears "interrupted" flag, but that is just because that is beyond the scope of C and is in the realm of POSIX.


As a side note, I'd like to say, please don't use memset to clear a struct. C has a very nice way of doing it:

struct sigaction act = {0};

Or if you want to be pedantic, since the first element of struct sigaction could be another struct or an array, you can assign to one of its mandated fields:

struct sigaction act = { .sa_handler = NULL };
like image 43
Shahbaz Avatar answered Dec 28 '22 08:12

Shahbaz