Use sigaction()
, not signal()
, except when setting the disposition to SIG_DFL
or SIG_IGN
. While signal()
is specified in C, and sigaction()
POSIX.1, you'll need POSIX to do anything meaningful with signals anyway.
Only use async-signal safe functions in signal handlers. Instead of standard I/O (as declared in <stdio.h>
), you can use POSIX low-level I/O (read()
, write()
) to the underlying file descriptors. You do need to avoid using standard I/O to streams that use those same underlying descriptors, though, or the output may be garbled due to buffering in standard I/O.
If you change the signal disposition in the signal handler to ignored, only the first signal (of each caught type) will be caught.
Consider the following example program:
#define _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
/* Helper function: Write a string to a descriptor, keeping errno unchanged.
Returns 0 if success, errno error code if an error occurs. */
static inline int wrs(const int fd, const char *s)
{
/* Invalid descriptor? */
if (fd == -1)
return EBADF;
/* Anything to actually write? */
if (s && *s) {
const int saved_errno = errno;
const char *z = s;
ssize_t n;
int err = 0;
/* Use a loop to find end of s, since strlen() is not async-signal safe. */
while (*z)
z++;
/* Write the string, ignoring EINTR, and allowing short writes. */
while (s < z) {
n = write(fd, s, (size_t)(z - s));
if (n > 0)
s += n;
else
if (n != -1) {
/* This should never occur, so it's an I/O error. */
err = EIO;
break;
} else
if (errno != EINTR) {
/* An actual error occurred. */
err = errno;
break;
}
}
errno = saved_errno;
return err;
} else {
/* Nothing to write. NULL s is silently ignored without error. */
return 0;
}
}
/* Signal handler. Just outputs a line to standard error. */
void catcher(int signum)
{
switch (signum) {
case SIGINT:
wrs(STDERR_FILENO, "Caught INT signal.\n");
return;
default:
wrs(STDERR_FILENO, "Caught a signal.\n");
return;
}
}
/* Helper function to install the signal handler. */
int install_catcher(const int signum)
{
struct sigaction act;
memset(&act, 0, sizeof act);
sigemptyset(&act.sa_mask);
act.sa_handler = catcher;
act.sa_flags = SA_RESTART; /* Do not interrupt "slow" functions */
if (sigaction(signum, &act, NULL) == -1)
return -1; /* Failed */
return 0;
}
/* Helper function to remove embedded NUL characters and CRs and LFs,
as well as leading and trailing whitespace. Assumes data[size] is writable. */
size_t clean(char *data, size_t size)
{
char *const end = data + size;
char *src = data;
char *dst = data;
/* Skip leading ASCII whitespace. */
while (src < end && (*src == '\t' || *src == '\n' || *src == '\v' ||
*src == '\f' || *src == '\r' || *src == ' '))
src++;
/* Copy all but NUL, CR, and LF chars. */
while (src < end)
if (*src != '\0' && *src != '\n' && *src != '\r')
*(dst++) = *(src++);
else
src++;
/* Backtrack trailing ASCII whitespace. */
while (dst > data && (dst[-1] == '\t' || dst[-1] == '\n' || dst[-1] == '\v' ||
dst[-1] == '\n' || dst[-1] == '\r' || dst[-1] == ' '))
dst--;
/* Mark end of string. */
*dst = '\0';
/* Return new length. */
return (size_t)(dst - data);
}
int main(void)
{
char *line = NULL;
size_t size = 0;
ssize_t len;
if (install_catcher(SIGINT)) {
fprintf(stderr, "Cannot install signal handler: %s.\n", strerror(errno));
return EXIT_FAILURE;
}
printf("Type Ctrl+D, 'exit', or 'quit' on an empty line to exit.\n");
while (1) {
len = getline(&line, &size, stdin);
if (len < 0)
break;
clean(line, len);
printf("Read %zd chars: %s\n", len, line);
if (!strcmp(line, "exit") || !strcmp(line, "quit"))
break;
}
return EXIT_SUCCESS;
}
On most POSIXy systems, Ctrl+C in a pseudoterminal also clears the current line buffer, so pressing it in the middle of interactively supplying a line will discard the line (the data not sent to the process).
Note that the ^C
you normally see in pseudoterminals when you press Ctrl+C is a terminal feature, controlled by the ECHO
termios setting. That setting also controls whether keypresses in general are echoed on the terminal.
If you add signal(SIGINT, SIG_IGN)
to catcher(), just after the wrs() line, only the first Ctrl+C will print "Caught INT signal"; any following Ctrl+C will just discard the the incomplete line.
So, if you type, say, Foo
Ctrl+CBar
Enter, you'll see either
Foo^CCaught INT signal.
Bar
Read 4 chars: Bar
or
Foo^CBar
Read 4 chars: Bar
depending on whether the INT signal generated by Ctrl+C is caught by the handler, or ignored, at that point.
To exit, type exit
or quit
at the start of a line, or immediately after a Ctrl+C.
There are no segmentation faults here, so if your code does generate one, it must be due to a bug in your program.
How can I return to execution of my main loop after I enter inthandler?
Signal delivery interrupts the execution for the duration of executing the signal handler; then, the interrupted code continues executing. So, the strictly correct answer to that question is by returning from the signal handler.
If the signal handler is installed with the SA_RESTART
flag set, then the interrupted code should continue as if nothing had happened. If the signal handler is installed without that flag, then interrupting "slow" system calls may return an EINTR error.
The reason errno
must be kept unchanged in a signal handler -- and this is a bug many, many programmers overlook -- is that if an operation in the signal handler changes errno, and the signal handler gets invoked right after a system or C library call failed with an error code, the errno seen by the code will be the wrong one! Granted, this is a rare case (the time window where this can occur is tiny for each system or C library call), but it is a real bug that can occur. When it does occur, it is the kind of Heisenbug that causes developers to rip out their hair, run naked in circles, and generally go more crazy than they already are.
Also note that stderr
is only used in the code path where installing the signal handler fails, because I wanted to be sure not to mix I/O and POSIX low-level I/O to standard error.