If I understand what you're after, you want to be able to handle (1) input from the keyboard, (2) signals, and (3) potentially other sources of events. If that's correct, then this might be the start of what you're after:
#include <errno.h>
#include <poll.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
static int pipe_fds[2];
static void handler(const int signo)
{
#define msg "received SIGTSTP\n"
write(pipe_fds[1], msg, strlen(msg));
#undef msg
}
int main(void)
{
if (pipe(pipe_fds) < 0) {
perror("pipe");
}
struct sigaction sa = {
.sa_handler = handler,
.sa_flags = SA_RESTART | SA_NOCLDSTOP,
};
if (sigemptyset(&sa.sa_mask) < 0) {
perror("sigemptyset");
return 1;
}
if (sigaction(SIGTSTP, &sa, NULL) < 0) {
perror("sigaction");
return 1;
}
struct pollfd fds[] = {
{
.fd = STDIN_FILENO,
.events = POLL_IN,
},
{
.fd = pipe_fds[0],
.events = POLL_IN,
},
};
const int num_fds = 2;
char buffer[1024] = {};
for (;;) {
const int ret = poll(fds, num_fds, -1);
if (ret < 0) {
if (errno == EINTR) {
// Receiving SIGTSTP can cause poll() to return
// -1 with errno = EINTR. Ignore that.
continue;
}
perror("poll");
return 1;
}
for (int i = 0; i < num_fds; ++i) {
if (fds[i].revents & POLL_IN) {
const int count = read(fds[i].fd, buffer, sizeof(buffer) - 1);
buffer[count - 1] = '\0';
printf("Read '%s' from file descriptor %d\n", buffer, fds[i].fd);
}
}
}
return 0;
}
The main()
function start off by creating a pipe; the program uses the pipe to communicate between the signal handler function (in signal context) and the main program. Next, it sets of the signal handler for the SIGTSTP
signal, much as you described above.
After that, it creates an array of struct pollfd
named fds
. Each entry in this array corresponds to a file descriptor the program is interested in monitoring activity. The first entry in the array is the file descriptor for standard input. The second is the read-end of the above-mentioned pipe. If you wanted to extend this example to handle other sources of events --- events that are associated with file descriptors --- then this would be the place to do it, just add additional elements to the fds
array with the appropriate file descriptors.
It then enters an event loop using poll
. With the -1
timeout, poll
will block until (1) it gets activity on one of the registered file descriptors or (2) a signal interrupts it (such as the receipt of SIGTSTP
). The program, therefore, checks the return value of poll
and if it's less than 0, explicitly checks for and ignores the EINTR
(interrupted by system call) error.
If poll()
returned because of activity, then the revents
field of the associated struct pollfd
will be marked accordingly. It will then read from the associated file descriptor and print a message.
Here's a sample run:
$ ./a.out
Hello!
Read 'Hello!' from file descriptor 0
How are you?
Read 'How are you?' from file descriptor 0
^ZRead 'received SIGTSTP' from file descriptor 3
Good
Read 'Good' from file descriptor 0
^C
$
In the example run, it read Hello!
followed by How are you?
from the keyboard. In both cases, the program responded by reading what I typed and printing a response. Next, I generated the SIGTSTP
signal, it read received SIGTSTP
from the pipe, and printed the response. Next, it read Good
from the keyboard and printed a response. Finally I interrupted the program with Ctrl-C
and the program terminated.
Note that it is possible for read
to return fewer bytes that are available. For simplicity, I didn't check for that condition. Based on the sources of events that you want to handle, you might want to do so.
sprintf()
in the context of a signal handler, it's not guaranteed to be reentrant; seeman 7 signal-safety
for a list of safe functions. It's fine for debugging if you're conscious of the fact it could cause unexpected crash, but you'll probably want to remove it when you're done. – Andy Dalton Jul 25 '20 at 22:33