0

I have to interrupt a read call if ctrl-c is pressed, using signal. I wrote this (simplified) sample code:

#include <unistd.h>
#include <sys/wait.h>

int should_stop = 0;

void sighandler(int signal)
{
    write(1, "\nctrl-c has been pressed\n", 25);
    should_stop = 1;
}

void read_function()
{
    char c;

    while (!should_stop)
        read(0, &c, 1);
    //Do some stuff and return someting
}

int main()
{
    signal(SIGINT, &sighandler);
    read_function();
    write(1, "read_function is over\n", 22);
    return (0);
}

As read is a blocking call (as far as I understood), the should_stop global variable will not be evaluated once read has been called. So I don't know how I could interrupt the read call by pressing ctrl-c.

Another constraint is that I'm only allowed to use those functions:

- write
- read
- fork
- wait
- signal
- kill
- exit

So I can't use select to set a timeout value. As I also need the return value of read_function, I can't use fork and just exit the process with a different signal handler.

Is there another way to interrupt the read call?

lfalkau
  • 896
  • 7
  • 21
  • You read to not initialized buffer. You have no memory allocated for buf. That is UB. Are you sure that is the actual code? – vtronko Apr 03 '20 at 08:46
  • This is simplified code, without any initialization / error checking, in my real code my buffer is initialized – lfalkau Apr 03 '20 at 08:48
  • 1
    You provided invalid code for the reason stated above, it doesn't comply with [mcve]. You can edit `char*` to be `char[1]` to make this code non-UB. – vtronko Apr 03 '20 at 08:49
  • It was an error, I wanted to put a char c instead of a char *buf, I just edited the post – lfalkau Apr 03 '20 at 09:11
  • 1
    You have an illegal [access of a shared variable from a signal handler](https://wiki.sei.cmu.edu/confluence/display/c/SIG31-C.+Do+not+access+shared+objects+in+signal+handlers) that makes your code unpredictable. The compiler may optimize out every read of `should_stop` but the first because it knows that `read` can't modify `should_stop` and accessing `should_stop` from a signal handler is prohibited by the standard. Use `volatile sig_atomic_t` to comply with C standard section 7.14.1.1. – David Schwartz Apr 03 '20 at 09:22

2 Answers2

2

This is what currently happens: when you send an interrupt signal from keyboard, the signal handler comes into action, writes the \nctrl-c has been pressed\n message to your console and sets the should_stop variable. Then, control is returned back to read(0, &buf, 1) statement. As the stdin is buffered, read won't be over until it meets a newline. If you press Enter afterwards — read reads one bit and returns. After that, a condition should_stop is checked again, and since it contains 1 value now — loop is over.

Now, we want to modify that behavior so that your program is gracefully shut down after SIGINT.

From man 7 signal:

If  a  blocked  call  to one of the following interfaces is interrupted by a
signal handler, then the call is automatically restarted  after  the  signal
handler  returns  if  the SA_RESTART flag was used; otherwise the call fails
with the error EINTR:

From man 2 signal:

certain  blocking  system calls are automatically
restarted if interrupted by a signal handler (see signal(7)).  The  BSD  se‐
mantics are equivalent to calling sigaction(2) with the following flags:

   sa.sa_flags = SA_RESTART;

So, here's how we employ sigaction(2) for our case:

int main()
{
    struct sigaction sa;
    sigemptyset(&sa.sa_mask);
    sa.sa_handler = sighandler;
    sa.sa_flags = 0;

    sigaction(SIGINT, &sa, NULL);
    read_function();
    write(1, "read_function is over\n", 22);
    return (0);
}

This way, upon interruption by signal handler, read(2) returns with EINTR error and is not restarted.

signal(2) is generally inferior to sigaction(2) when it comes to portability of the code, you can read about it here

vtronko
  • 478
  • 3
  • 10
  • I just corrected my mistake. I know the behaviour of this code, but I want the signal caught to interrupt the read immediatly, without wait for a newline, and without even waiting for any character. I also know the while statement can't solve my problem, it's here to illustrate what I'm trying to do – lfalkau Apr 03 '20 at 09:14
  • I'm not allowed to use sigaction, but that's the better answer for now – lfalkau Apr 03 '20 at 12:29
1

It should work provided you declare the should_stop variable to be volatile. This will instruct the compiler to re-read it from memory on every access:

...
volatile int should_stop = 0;
...

Simply depending on your system, a read call may be restarted after a signal, and you will have to hit return after the Ctrl-C to end the program. By default, my FreeBSD 11 bos behaves like that.

If you want the read call not to be restarted, you should explicitely ask for that behaviour with siginterrupt:

...
signal(SIGINT, &sighandler);
siginterrupt(SIGINT, 1);
...

That way, the program will stop immediately after the Ctrl-C

Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252
  • `volatile sig_atomic_t should_stop = 0;` then? This way platform will choose underlying type for flag on its own. I get that it will be `int` on most platforms, but still. – vtronko Apr 03 '20 at 09:41
  • It doesn't change anything, it already have this behaviour, I think the compiler doesn't optimize the while statement in my case, anyway, it's better to set should_stop as volatile you're right, but I want the signal to interrupt the read call immediatly, as I doesn't need to hit enter to make the function exit – lfalkau Apr 03 '20 at 10:07
  • @SergeBallesta `CONFORMING TO 4.3BSD, POSIX.1-2001. POSIX.1-2008 marks siginterrupt() as obsolete, recommending the use of sigaction(2) with the SA_RESTART flag instead.` See my reply :) – vtronko Apr 03 '20 at 10:43
  • This is exactly what I was searching for! Unfortunatly, I'm not allowed to use this function, but maybe there's a something to give to the second signal's argument, to do same, let me check – lfalkau Apr 03 '20 at 11:01
  • Ok there isn't, I'll wait a bit in case of other ideas, else I'll mark your answer as correct as it's the best solution to my question for now – lfalkau Apr 03 '20 at 11:12
  • `sigaction(2)` is what you are looking for, it's even defined in the same header. It allows the customization while `signal(2)` doesn't. – vtronko Apr 03 '20 at 11:22
  • Yes but I'm not allowed to use it neither – lfalkau Apr 03 '20 at 12:24
  • 2
    @Bccyv: The Posix signal system is indeed configurable, but if you are not satisfied with the default and cannot configure it, I cannot imagine a way... – Serge Ballesta Apr 03 '20 at 12:35
  • 1
    I think it's just not possible with the constraints I have, anyway, thanks a lot for taking this time helping me! – lfalkau Apr 03 '20 at 12:53