Search code examples
csignalsposix

Partial write due to SIGINT


I have a write_full which should write the buffer fully to standard output even if it was interrupted by a signal.
I have a loop which keep write_full a string until quit is changed by a signal handler. Here's the code:

#include <signal.h>
#include <unistd.h>
#include <errno.h>

volatile sig_atomic_t quit = 0;

void sigint_handler(int s)
{
    quit = 1;
}

int write_full(char *buf, size_t len)
{
    while (len > 0) {
        ssize_t written = write(STDOUT_FILENO, buf, len);
        if (written == -1) {
            if (errno == EINTR) { 
                continue; 
            }
            return -1;
        }
        buf += written;
        len -= (size_t)written;
    }
    return 0;
}

int main(void)
{
    struct sigaction act = {
        .sa_handler = sigint_handler
    };
    sigaction(SIGINT, &act, NULL);

    while (!quit) {
        write_full("loop\n", 5);
    }

    write_full("cleanup", 7);

    return 0;
}

I expect the program to write the "loop" fully before printing "cleanup", but I see an output like this:

loop
loop
loop
l^C
cleanup

Why is this happening? I expect it to be something like this:

loop
loop
loop
l^Coop
cleanup

because the write_full should continue writing the "oop\n" part even after the first write was a short write due to interrupt. I put a breakpoint at the signal handler and stepped and it seems like write is reporting that it has written 4 characters even when it only wrote "l" to the stdout. So instead of writing "oop\n" next, it only writes "\n".

l^C
Program received signal SIGINT, Interrupt.

Breakpoint 1, sigint_handler (s=2) at src/main.c:9
9           quit = 1;
(gdb) next
10      }
(gdb) next
write_full (buf=0x4020a0 "loop\n", len=5) at src/main.c:16
16              if (written == -1) {
(gdb) print written
$1 = 4

Why is this happening? And how can I fix this?


Solution

  • Ctrl-C messes up terminal output. The program writes all it should write, but by default the terminal driver cuts the line after Ctrl-C. This is not under control of your program. The cut happens if the driver sees Ctrl-C in the middle of copying of the complete line buffer to the physical device. Input line buffer is also discarded. This pertains to other signal-generating characters as well.

    This is described rather briefly in the stty(1) and termios(3) manual pages.

    This behaviour can be disabled with stty noflsh command.

    You can also redirect to a file to see the complete output.