Why isn't write(2) returning EINTR? - c

I've been reading about EINTR on write(2) etc, and trying to determine whether I need to check for it in my program. As a sanity check, I tried to write a program that would run into it. The program loops forever, writing repeatedly to a file.
Then, in a separate shell, I run:
while true; do pkill -HUP test; done
However, the only output I see from test.c is the .s from the signal handler. Why isn't the SIGHUP causing write(2) to fail?
test.c:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
void hup_handler(int sig)
{
printf(".");
fflush(stdout);
}
int main()
{
struct sigaction act;
act.sa_handler = hup_handler;
act.sa_flags = 0;
sigemptyset(&act.sa_mask);
sigaction(SIGHUP, &act, NULL);
int fd = open("testfile", O_WRONLY);
char* buf = malloc(1024*1024*128);
for (;;)
{
if (lseek(fd, 0, SEEK_SET) == -1)
{
printf("lseek failed: %s\n", strerror(errno));
}
if (write(fd, buf, sizeof(buf)) != sizeof(buf))
{
printf("write failed: %s\n", strerror(errno));
}
}
}

Linux tends to avoid EINTR on writes to/reads from files; see discussion here. While a process is blocking on a disk write, it may be placed in an uninterruptible sleep state (process code D) which indicates that it cannot be interrupted at that time. This depends on the device driver; the online copy of Linux Device Drivers, 3rd Edition is a good reference for how this appears from the kernel side.
You still need to handle EINTR for other platforms which may not behave the same, or for pipes and sockets where EINTR definitely can occur.
Note that you're only writing sizeof(void *) bytes at a time:
char* buf = malloc(1024*1024*128);
if (write(fd, buf, sizeof(buf)) != sizeof(buf))
This should be
const size_t BUF_SIZE = 1024*1024*128;
char* buf = malloc(BUF_SIZE);
if (write(fd, buf, BUF_SIZE) != BUF_SIZE)

There are 2 possibilities:
You're writing very few bytes, since you're misusing the sizeof operator. Thus the write happens instantaneously and it never gets interrupted - you're only writing 4 or 8 bytes at a time
Somehow the syscall gets restarted, as if you applied SA_RESTART to sigaction
In your code, since buf is a pointer, sizeof(buf) yields the size of the pointer on your machine, not the (much bigger) allocated space

If you check the manual page for EINTR
The call was interrupted by a signal before any data was written
Also from the signal(7) manual page:
read(2), readv(2), write(2), writev(2), and ioctl(2) calls on "slow" devices. A "slow" device is one where the I/O call may block for an indefinite time, for example, a terminal, pipe, or socket. (A disk is not a slow device according to this definition.) If an I/O call on a slow device has already transferred some data by the time it is interrupted by a signal handler, then the call will return a success status (normally, the number of bytes transferred).
Taking these two together, if writing to a file on a disk, and write has started to write (even if only one single byte has been written) the return from that write call will be a success.

Related

Robust graceful shutdown of an application

To ensure that all destructors are properly called if the program is terminated from keyboard (Ctrl+C), the approach with signals are used:
a handler, which sets an exit flag, is set for SIGINT
if a blocking call (accept(), read(), connect(), etc) is waiting for completion, it returns -1 and errno is set to EINTR
The problem is that SIGINT can arrive between check for exit flag (while (!finish)) and calling read(). In this case, read() will be blocked until the signal is sent once again.
This is a minimal working example:
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
enum { STDIN, STDOUT, STDERR };
static unsigned char finish=0;
static void handleSignal(int signal) {
finish=1;
}
int main(int argc, char ** e) {
struct sigaction action;
memset(&action, 0, sizeof(action));
action.sa_handler=handleSignal;
action.sa_flags=0;
sigaction(SIGINT, &action, NULL);
char buffer[256];
puts("<<");
while (!finish) {
sleep(2);
ssize_t n=read(STDIN, buffer, sizeof(buffer));
if (n==0) {
// End of stream
finish=1;
}
else if (n<0) {
// Error or interrupt
if (errno!=EINTR)
perror("read");
}
else {
// Convert data to hexadecimal format
for (size_t i=0; i<n; i++)
printf("%02x", buffer[i]);
}
}
puts(">>\n");
return 0;
}
sleep(2) is added for visibility (a real program may perform some preparational work before reading from file descritor).
If there any way of reliable handling of signals without using non-crossplatform things like signalfd()?
The pselect(2) system call was invented to solve this exact problem. It's POSIX, so hopefully cross-platform enough for you.
The purpose of pselect is to atomically unblock some signals, wait for I/O as select() does, and reblock them. So your loop can look something like the following pseudocode:
sigprocmask(SIG_BLOCK, {SIGINT});
while (1) {
if (finish)
graceful_exit();
int ret = pselect(1, {STDIN}, ..., { /* empty signal set */});
if (ret > 0) {
read(STDIN, buf, size); // will not block
// process data
// If you like you can do
sigprocmask(SIG_UNBLOCK, {SIGINT});
// work work work
if (finish)
graceful_exit();
// work work work
sigprocmask(SIG_BLOCK, {SIGINT});
} else {
// handle timeout or other errors
}
}
There is no race here because SIGINT is blocked for the time in between checking the finish flag and the call to pselect, so it cannot be delivered during that window. But the signal is unblocked while pselect is waiting, so if it arrives during that time (or already arrived while it was blocked), pselect will return without further delay. We only call read when pselect has told us it was ready for reading, so it cannot block.
If your program is multithreaded, use pthread_sigmask instead of sigprocmask.
As was noted in comments, you have to make your finish flag volatile, and for best compatibility it should be of type sig_atomic_t.
There is more discussion and another example in the select_tut(2) man page.

pselect() on Linux does not deliver signals if events are pending

I'm trying to add a signal handler for proper cleanup to my event-driven application.
My signal handler for SIGINT only changes the value of a global flag variable, which is then checked in the main loop. To avoid races, the signal is blocked at all times, except during the pselect() call. This should cause pending signals to be delivered only during the pselect() call, which should be interrupted and fail with EINTR.
This usually works fine, except if there are already events pending on the monitored file descriptors (e.g. under heavy load, when there's always activity on the file descriptors).
This sample program reproduces the problem:
#include <assert.h>
#include <errno.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <sys/select.h>
#include <fcntl.h>
#include <signal.h>
#include <unistd.h>
volatile sig_atomic_t stop_requested = 0;
void handle_signal(int sig)
{
// Use write() and strlen() instead of printf(), which is not async-signal-safe
const char * out = "Caught stop signal. Exiting.\n";
size_t len = strlen (out);
ssize_t writelen = write(STDOUT_FILENO, out, len);
assert(writelen == (ssize_t) len);
stop_requested = 1;
}
int main(void)
{
int ret;
// Install signal handler
{
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sa.sa_handler = handle_signal;
ret = sigaction(SIGINT, &sa, NULL);
assert(ret == 0);
}
// Block SIGINT
sigset_t old_sigmask;
{
sigset_t blocked;
sigemptyset(&blocked);
sigaddset(&blocked, SIGINT);
ret = sigprocmask(SIG_BLOCK, &blocked, &old_sigmask);
assert(ret == 0);
}
ret = raise(SIGINT);
assert(ret == 0);
// Create pipe and write data to it
int pipefd[2];
ret = pipe(pipefd);
assert(ret == 0);
ssize_t writelen = write(pipefd[1], "foo", 3);
assert(writelen == 3);
while (stop_requested == 0)
{
printf("Calling pselect().\n");
fd_set fds;
FD_ZERO(&fds);
FD_SET(pipefd[0], &fds);
struct timespec * timeout = NULL;
int ret = pselect(pipefd[0] + 1, &fds, NULL, NULL, timeout, &old_sigmask);
assert(ret >= 0 || errno == EINTR);
printf("pselect() returned %d.\n", ret);
if (FD_ISSET(pipefd[0], &fds))
printf("pipe is readable.\n");
sleep(1);
}
printf("Event loop terminated.\n");
}
This program installs a handler for SIGINT, then blocks SIGINT, sends SIGINT to itself (which will not be delivered yet because SIGINT is blocked), creates a pipe and writes some data into the pipe, and then monitors the read end of the pipe for readability.
This readability monitoring is done using pselect(), which is supposed to unblock SIGINT, which should then interrupt the pselect() and call the signal handler.
However, on Linux (I tested on 5.6 and 4.19), the pselect() call returns 1 instead and indicates readability of the pipe, without calling the signal handler. Since this test program does not read the data that was written to the pipe, the file descriptor will never cease to be readable, and the signal handler is never called. In real programs, a similar situation might arise under heavy load, where a lot of data might be available for reading on different file descriptors (e.g. sockets).
On the other hand, on FreeBSD (I tested on 12.1), the signal handler is called, and then pselect() returns -1 and sets errno to EINTR. This is what I expected to happen on Linux as well.
Am I misunderstanding something, or am I using these interfaces incorrectly? Or should I just fall back to the old self-pipe trick, which (I believe) would handle this case better?
This is a type of resource starvation caused by always checking for active resources in the same order. When resources are always checked in the same order, if the resources checked first are busy enough the resources checked later may never get any attention.
See What is starvation?.
The Linux implementation of pselect() apparently checks file descriptors before checking for signals. The BSD implementation does the opposite.
For what it's worth, the POSIX documentation for pselect() states:
If none of the selected descriptors are ready for the requested operation, the pselect() or select() function shall block until at least one of the requested operations becomes ready, until the timeout occurs, or until interrupted by a signal.
A strict reading of that description requires checking the descriptors first. If any descriptor is active, pselect() will return that instead of failing with errno set to EINTR.
In that case, if the descriptors are so busy that one is always active, the signal processing gets starved.
The BSD implementation likely starves active descriptors if signals come in too fast.
One common solution is to always process all active resources every time a select() call or similar returns. But you can't do that with your current design that mixes signals with descriptors because pselect() doesn't even get to checking for a pending signal if there are active descriptors. As #Shawn mentioned in the comments, you can map signals to file descriptors using signalfd(). Then add the descriptor from signalfd() to the file descriptor set passed to pselect().

POSIX partial write() and Signal Interrupts

From the man page of write()
Note that a successful write() may transfer fewer than count bytes.
Such partial writes can occur for various reasons; for example,
because there was insufficient space on the disk device to write all
of the requested bytes, or because a blocked write() to a socket,
pipe, or similar was interrupted by a signal handler after it had
transferred some, but before it had transferred all of the requested
bytes. In the event of a partial write, the caller can make another
write() call to transfer the remaining bytes. The subsequent call
will either transfer further bytes or may result in an error (e.g.,
if the disk is now full).
I have the following questions
1) In the case of write() being interrupted by signal handler after a partial transfer, will write() set the errno to EINTR ?
2) If errno is not set, is there a way to identify such an event with out extra piece of code (Like installing signal disposition and setting a flag value to true) ?
Note : The further write() calls are successful in transferring the remaining bytes after the event of signal interrupt.
To answer your individual numbered questions:
errno is only meaningful after one of the standard functions returns a value indicating an error - for write, -1 - and before any other standard function or application code that might clobber it is called. So no, if write returns a short write, errno will not be set to anything meaningful. If it's equal to EINTR, it just happens to be; this is not something meaningful you can interpret.
The way you identify such an event is by the return value being strictly less than the nbytes argument. This doesn't actually tell you the cause of the short write, so it could be something else like running out of space. If you need to know, you need to arrange for the signal handler to inform you. But in almost all cases you don't actually need to know.
Regarding the note, if write is returning the full nbytes after a signal arriving, the signal handler was non-interrupting. This is the default on Linux with any modern libc (glibc, musl, anything but libc5 basically), and it's almost always the right thing. If you actually want interrupting signals you have to install the signal handler with sigaction and the SA_RESTART flag clear. (And conversely if you're installing signal handlers you want to have the normal, reasonable, non-interrupting behavior, for portability you should use sigaction and set the SA_RESTART flag rather than using the legacy function signal).
Let's try it and see:
#define _GNU_SOURCE
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <signal.h>
static void handle_sigalrm(int sig) {
}
int main(void) {
struct sigaction act;
memset(&act, 0, sizeof act);
act.sa_handler = handle_sigalrm;
sigaction(SIGALRM, &act, NULL);
int fds[2];
pipe(fds);
int bufsize = fcntl(fds[1], F_GETPIPE_SZ) + 10;
char *buf = calloc(bufsize, 1);
ssize_t written;
printf("will attempt to write %d bytes and EINTR is %d\n", bufsize, EINTR);
alarm(1);
errno = 0;
written = write(fds[1], buf, bufsize);
printf("write returned %td and errno is %d\n", written, errno);
return 0;
}
That program makes a pipe that nothing will ever read from, does a write to it that's bigger than the kernel's buffer, and arranges for a signal handler to run while the write is blocking. On my system, it prints this:
will attempt to write 65546 bytes and EINTR is 4
write returned 65536 and errno is 0
Thus, the answer to "In the case of write() being interrupted by signal handler after a partial transfer, will write() set the errno to EINTR?" is "no, it won't".

How to restore original signal handling properties in C

Tried my best to figure this out on my own, but I really do not want to continue tampering with things that I do not fully understand. So for a programming assignment I have to do in C, I need to terminate a program upon the user entering CTRL+D key stroke via a terminal. I tried to isolate that functionality in a smaller test function, but now my CTRL+D behaves as my CTRL+C and CTRL+C does not have any effect, even outside of the program when it finishes executing. This is the program that caused this change:
#include <unistd.h>
#include <stdio.h>
#include <termios.h>
#include <signal.h>
#include <stdlib.h>
void ctrlD(int sig){
printf("\n");
signal(SIGINT, SIG_DFL);
exit(0);
}
int main(){
signal(SIGINT, ctrlD);
while(1) {
printf("Hello\n");
sleep(5);
}
}
The line signal(SIGINT, SIG_DFL); was added afterward upon realizing my CTRL+C no longer worked. I thought it would return the keystrokes to their original functionalities, but to no avail. What do I do to get back the original functionalities while also making this program work with CTRL+D?
***EDIT: This question seems to have gone off the rails a bit. I get now that Ctrl+D is not a signal. Nonetheless, I no longer have the functionality of Ctrl+C anymore when attempting to use it in my MAC OS terminal, and instead Ctrl+D seems to have that exact functionality. HOW exactly can I return each to have the functionality that they had before I went on this haphazard journey?
If your intention is to restore signal's default behavior after executing handler then, pass SA_RESETHAND flag to sa_flags while registering signal action. For example.
struct sigaction act;
memset(&act, 0, sizeof(struct sigaction));
act.sa_flags = SA_RESETHAND;
act.sa_handler = some_handler;
sigaction(SIGINT, &act, NULL);
From sigaction() man
SA_RESETHAND
Restore the signal action to the default upon entry to the signal handler. This flag is meaningful only when
establishing a signal handler.
If you write a program to explore signals, it is much better to write it carefully, using proper POSIX interfaces (sigaction() instead of signal()), and avoiding undefined behaviour (using non-async-signal safe functions in a signal handler).
Consider, for example, the following program:
#define _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <stdio.h>
#include <time.h>
#include <errno.h>
static volatile sig_atomic_t sigint_count = 0;
static void catch_sigint(int signum)
{
if (signum == SIGINT)
sigint_count++;
}
static int install_sigint(void)
{
struct sigaction act;
memset(&act, 0, sizeof act);
sigemptyset(&act.sa_mask);
act.sa_handler = catch_sigint;
act.sa_flags = 0;
if (sigaction(SIGINT, &act, NULL) == -1)
return errno;
return 0;
}
static int install_default(const int signum)
{
struct sigaction act;
memset(&act, 0, sizeof act);
sigemptyset(&act.sa_mask);
act.sa_handler = SIG_DFL;
act.sa_flags = 0;
if (sigaction(signum, &act, NULL) == -1)
return errno;
return 0;
}
int main(void)
{
struct timespec duration;
int result;
if (install_sigint()) {
fprintf(stderr, "Cannot install SIGINT handler: %s.\n", strerror(errno));
return EXIT_FAILURE;
}
duration.tv_sec = 5;
duration.tv_nsec = 0; /* 1/1000000000ths of a second. Nine zeroes. */
printf("Sleeping for %d seconds.\n", (int)duration.tv_sec);
fflush(stdout);
while (1) {
result = nanosleep(&duration, &duration);
if (!result)
break;
if (errno != EINTR) {
fprintf(stderr, "nanosleep() failed: %s.\n", strerror(errno));
return EXIT_FAILURE;
}
/* nanosleep was interrupted by a delivery of a signal. */
if (sigint_count >= 3) {
/* Ctrl+C pressed three or more times. */
if (install_default(SIGINT) == -1) {
fprintf(stderr, "Cannot revert SIGINT to the default handler: %s.\n", strerror(errno));
return EXIT_FAILURE;
}
printf("SIGINT has been reverted to the default handler.\n");
fflush(stderr);
}
}
if (sigint_count > 0)
printf("You pressed Ctrl+C %d time%s.\n", (int)sigint_count, (sigint_count > 1) ? "s" : "");
else
printf("You did not press Ctrl+C at all.\n");
return EXIT_SUCCESS;
}
The #define tells your C library (glibc in particular) that you want POSIX.1-2008 (and later) features from it.
The INT signal handler only increments a volatile sig_atomic_t counter. Note that this type may have a very small range it can represent; 0 to 127, inclusive, should be safe.
The main program waits using the POSIX nanosleep() function. On some systems, sleep() may be implemented via the SIGALRM function, so it is better avoided when using signals otherwise; nanosleep() does not interfere with signals like that at all. Plus, nanosleep() can return the amount of time remaining, if it is interrupted by a signal delivery.
In the main loop, nanosleep() will return 0, if it has slept the entire interval (but note that it may not update the remaining time to 0 in this case). If it is interrupted by the delivery of a signal, it will return -1 with errno == EINTR, and the remaining time updated. (The first pointer is to the duration of the sleep, and the second is to where the remaining time should be stored. You can use the same structure for both.)
Normally, the main loop does only one iteration. It can do more than one iteration, if it is interrupted by the delivery of a signal.
When the main loop detects that sigint_count is at least three, i.e. it has received at least three INT signals, it resets the signal handler back to default.
(Note that both the memset() and the sigemptyset() are important when clearing the struct sigaction structure. The memset() ensures that future code is backwards compatible with older code, by ensuring even padding fields are cleared. And sigemptyset() is the safe way to clear the signal mask (set of signals blocked while the handler runs).)
(In theory, memset() is not async-signal-safe, while both sigemptyset() and sigaction() are. This is why I reset the signal handler in the main program, and not in the signal handler.)
If you want to print from a signal handler, you need to use low-level I/O, because <stdio.h> functions are not async-signal safe. For example, you can use the following function to print strings to standard output:
static int wrerr(const char *p)
{
const int saved_errno = errno;
int retval = 0;
if (p) {
const char *q = p;
ssize_t n;
while (*q)
q++;
while (p < q) {
n = write(STDERR_FILENO, p, (size_t)(q - p));
if (n > 0)
p += n;
else
if (n != -1) {
retval = EIO;
break;
} else
if (errno != EINTR) {
retval = errno;
break;
}
}
}
errno = saved_errno;
return retval;
}
The above wrerr() function is async-signal safe (because it only uses async-signal safe functions itself), and it even keeps errno unchanged. (Many guides forget to mention that it is quite important for a signal handler to keep errno unchanged. Otherwise, when a function is interrupted by a signal handler, and that signal handler modifies errno, the original function will return -1 to indicate an error, but then errno is no longer EINTR!)
You can just use wrerr("INT signal!\n") if you want. The return value from wrerr() is zero if the write was successful, and an errno error code otherwise. It ignores interrupts itself.
Do note that you should not mix stderr output via fprintf() or other <stdio.h> functions with the above (except perhaps for printing error messages when the program aborts). Mixing them is not undefined behaviour, it just may yield surprising results, like wrerr() output appearing in the midst of a fprintf(stderr,...) output.
Its because of exit(0) statement in the handler, when SIGINT is raised, handler strlD gets called and you might thinking why signal(SIGINT,SIG_DFL) didn't work ? Actually it works. But your main process a.out get terminated successfully there itself by calling exit(0). remove exit(0) if you want to restore the behavior of SIGINT.
#include <unistd.h>
#include <stdio.h>
#include <termios.h>
#include <signal.h>
#include <stdlib.h>
void ctrlD(int sig){
//printf("CTRL+C pressed\n");/* just to observe I added one printf
statement, Ideally there shouldn't be any printf here */
signal(SIGINT, SIG_DFL);/*restoring back to original action */
}
int main(){
signal(SIGINT, ctrlD);/*1st time when CTRL+C pressed, handler ctrlD gets called */
while(1) {
printf("Hello\n");
sleep(5);
}
return 0;
}
Also its advisable to use sigaction() instead of signal() as told here What is the difference between sigaction and signal? . Read man 2 sigaction and man 2 exit to check what exit(0) means.
Also this How to avoid using printf in a signal handler?
Edit :
void ctrlD(int sig){
/* printf("CTRL+C pressed \n"); */
signal(SIGINT, SIG_DFL); /* only one time CTRL+C works
after that SIG_DFL will terminate whole process */
}
int main(){
signal(SIGINT, ctrlD); /* if you press CTRL+C then it will go to handler
and terminate */
int ch;
while( ((ch = getchar())!=EOF) ) { /* wait or read char until CTrl+D is not pressed */
printf("Hello : %d \n",ch);/* ASCII equivalent of char */
}
return 0;
}
Thank you everyone who contributed to this question. The resources provided/linked were tremendously helpful in learning more about signals (and that EOF isn't a signal), among the other wealth of information provided.
After some more research, I found out that somehow, either through some accidental bash command gone awry, or perhaps the program posted in my original question itself, I had altered the key mappings for my terminal's stty settings. If anyone finds themselves in this oddly specific situation in the future, I hope this can be of help, as it is what fixed my problem:
Enter the command $ stty -a to see all of your terminals settings, specifically the "cchars" section.
I then saw the reversal, and fixed it like so:
$ stty intr ^C
$ stty eof ^D
Then you can run $ stty -a once again to see that the changes have properly taken effect. Once again, thanks everyone.

how to clear stdout after CTRL - C in linux c

We dont want anything to be printed after user interrupt via CTRL-C. We have tried adding __fpurge as well fflush inside sigInt signal handler, but it is not working.
How can I clear buffered stdout values immediately? I have came across few similar thread but no where i could able to find a working solution .
Few additional info's:
Inside sigInt signal handler even after adding exit(0) , buffer content are getting printed but the processor is killed .
added exit(0) to narrow down the issue , i dont want to kill the processor
I know the above is expected behavior , not sure how to avoid it .
Consider this edited example -- edited; this one does not exit the process:
#define _POSIX_C_SOURCE 200809L /* For nanosleep() */
#include <unistd.h>
#include <stdlib.h>
#include <termios.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <stdio.h>
static void exit_handler(int signum)
{
int fd, result;
/* If the standard streams are connected to a tty,
* tell the kernel to discard already buffered data.
* (That is, in kernel buffers. Not C library buffers.)
*/
if (isatty(STDIN_FILENO))
tcflush(STDIN_FILENO, TCIOFLUSH);
if (isatty(STDOUT_FILENO))
tcflush(STDOUT_FILENO, TCIOFLUSH);
if (isatty(STDERR_FILENO))
tcflush(STDERR_FILENO, TCIOFLUSH);
/* Redirect standard streams to /dev/null,
* so that nothing further is output.
* This is a nasty thing to do, and a code analysis program
* may complain about this; it is suspicious behaviour.
*/
do {
fd = open("/dev/null", O_RDWR);
} while (fd == -1 && errno == EINTR);
if (fd != -1) {
if (fd != STDIN_FILENO)
do {
result = dup2(fd, STDIN_FILENO);
} while (result == -1 && (errno == EINTR || errno == EBUSY));
if (fd != STDOUT_FILENO)
do {
result = dup2(fd, STDOUT_FILENO);
} while (result == -1 && (errno == EINTR || errno == EBUSY));
if (fd != STDERR_FILENO)
do {
result = dup2(fd, STDERR_FILENO);
} while (result == -1 && (errno == EINTR || errno == EBUSY));
if (fd != STDIN_FILENO && fd != STDOUT_FILENO && fd != STDERR_FILENO)
close(fd);
}
}
static int install_exit_handler(const int signum)
{
struct sigaction act;
memset(&act, 0, sizeof act);
sigemptyset(&act.sa_mask);
act.sa_handler = exit_handler;
act.sa_flags = 0;
if (sigaction(signum, &act, NULL) == -1)
return errno;
return 0;
}
int main(void)
{
if (install_exit_handler(SIGINT)) {
fprintf(stderr, "Cannot install signal handler: %s.\n", strerror(errno));
return EXIT_FAILURE;
}
while (1) {
struct timespec t = { .tv_sec = 0, .tv_nsec = 200000000L };
printf("Output\n");
fflush(stdout);
nanosleep(&t, NULL);
}
/* Never reached. */
return EXIT_SUCCESS;
}
When the process receives a SIGINT signal, it will first flush whatever is in kernel terminal buffer, then redirect the standard streams to /dev/null (i.e., nowhere).
Note that you'll need to kill the process by sending it the TERM or KILL signal (i.e. killall ./yourprogname in another terminal).
When you are running the verbose process over a remote connection, quite a lot of information may be in flight at all times. Both the local machine and the remote machine running the process will have their socket buffers nearly full, so the latency may be much larger than ordinarily -- I've seen several second latencies in this case even on fast (GbE) local networks.
This means that propagating the signal from the local machine to the remote machine will take a measurable time; in worst cases on the order of seconds. Only then will the remote process stop outputting data. All pending data will still have to be transmitted from the remote machine to the local machine, and that may take quite a long time. (Typically, the bottleneck is the terminal itself; in most cases it is faster to minimize the terminal, so that it does not try to render any of the text it receives, only buffers it internally.)
This is why Ctrl+C does not, and cannot, stop remote output instantaneously.
In most cases, you'll be using an SSH connection to the remote machine. The protocol does not have a "purge" feature, either, that might help here. Many, myself included, have thought about it -- at least my sausage fingers have accidentally tabbed to the executable file instead of the similarly named output file, and not only gotten the terminal full of garbage, but the special characters in binary files sometimes set the terminal state (see e.g. xterm control sequences, ANSI escape codes) to something unrecoverable (i.e., Ctrl+Z followed by reset Enter does not reset the terminal back to a working state; if it did, kill -KILL %- ; fg would stop the errant command in Bash, and get you your terminal back), and you need to break the connection, which will also terminate all processes started from the same terminal running remotely in the background.
The solution here is to use a terminal multiplexer, like GNU screen, which allows you to connect to and disconnect from the remote machine, without interrupting an existing terminal connection. (To put it simply, screen is your terminal avatar on the remote machine.)
First up, a quote from the C11 standard, emphasis mine:
7.14.1.1 The signal function
5 If the signal occurs other than as the result of calling the abort or raise function, the behaviour is undefined if [...] the signal handler calls any function in the standard library other than the abort function, the _Exit function, the quick_exit function, or the signal function with the first argumentt equal to the signal number corresponding to the signal that caused the invocation of the handler.
This means calling fflush is undefined behaviour.
Looking at the functions you may call, abort and _Exit both leave the flushing of buffers implementation-defined, and quick_exit calls _Exit, so you are out of luck as far as far as the standard is concerned since I could not find the implementation's definition on their behaviour for Linux. (Surprise. Not.)
The only other "terminating" function, exit, does flush the buffers, and you may not call it from the handler in the first place.
So you have to look at Linux-specific functionality. The man page to _exit makes no statement on buffers. The close man page warns against closing file descriptors that may be in use by system calls from other threads, and states that "it is not common for a filesystem to flush the buffers when the stream is closed", meaning that it could happen (i.e. close not guaranteeing that unwritten buffer contents are actually discarded).
At this point, if I were you, I would ask myself "is this such a good idea after all"...
The problem is that neither Posix nor Linux library declares that fpurge nor __fpurge to be safe in a signal handler function. As explained by DevSolar, C language itsel does not declare many safe functions for standard library (at least _Exit, but Posix explicitely allows close and write. So, you can always close the underlying file descriptor which should be 1:
void handler(int sig) {
static char msg[] = "Interrupted";
write(2, msg, sizeof(msg) - 1); // carefully use stderr here
close(1); // foo is displayed if this line is commented out
_Exit(1);
}
int main() {
signal(SIGINT, handler);
printf("bar");
sleep(15);
return 0;
}
When I type Ctrl-C during the sleep it gives as expected:
$ ./foo
^CInterrupted with 2
$
The close system call should be enough, because as it closes the underlying file descriptor. So even if there are later attemps to flush stdout buffer, they will write on a closed file descriptor as as such have no effect at all. The downside is that stdout has been redirected, the program should store the new value of the underlying file descriptor in a global variable.
If you do kill(getpid(), SIGKILL); with in the signal handler (which is async-safe), you would get killed immediately by the OS (as you wanted to exit(0) anyway). Further output is not to be expected any more.
Only problem: you won't be able to clean up poperly, exit gracefully afterwards in the main thread. If you can afford that...

Resources