2

I want to develop a C code that will wait concurrently for both keyboard input and keyboard-generated signals, and will be able to have other I/O (with pipes and files) active.

I have a minimal signal handler which increments a flag (and currently gives a debug):

void Handler (int signo)

{ char msg[80];

ss->nTstp++;
sprintf (msg, "\n%s .. Called Handler %d sig %d ..\n",
    TS(), ss->nTstp, signo);
write (STDERR_FILENO, msg, strlen (msg));

}

I set up the handler thus:

sigemptyset (& ss->sa.sa_mask);
sigaddset (& ss->sa.sa_mask, SIGTSTP);
ss->sa.sa_flags = SA_RESTART | SA_NOCLDSTOP;
ss->sa.sa_handler = Handler;
rc = sigaction (SIGTSTP, & ss->sa, NULL);

My dilemma is that the signal is acknowledged directly, but my terminal input gets restarted automatically. I don't get to act on my handler flag until I hit Enter.

If I do not set SA_RESTART, I get EINTR on terminal input, but I believe I also have to expect EINTR (and have to write a restartable transfer) on every other file descriptor.

Is there a way to disable SA_RESTART on specific file descriptors, or a way to guarantee that other fds will not get EINTR?

Paul_Pedant
  • 8,679
  • Careful calling sprintf() in the context of a signal handler, it's not guaranteed to be reentrant; see man 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
  • Independent of the EINTR issue, you might have to set the NOFLSH bit in the termios c_lflag so that the system doesn't discard pending typein when the user types Control-Z. – Mark Plotnick Jul 26 '20 at 16:19
  • Did the below answer help at all, or did I misunderstand your question? – Andy Dalton Jul 28 '20 at 01:44
  • @Andy Very grateful, and it looks good. I will take a couple of days to wring out all the knowledge from it. – Paul_Pedant Jul 28 '20 at 09:54

1 Answers1

3

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(&amp;sa.sa_mask) &lt; 0) {
    perror(&quot;sigemptyset&quot;);
    return 1;
}

if (sigaction(SIGTSTP, &amp;sa, NULL) &lt; 0) {
    perror(&quot;sigaction&quot;);
    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 &lt; 0) {
        if (errno == EINTR) {
            // Receiving SIGTSTP can cause poll() to return
            // -1 with errno = EINTR.  Ignore that.
            continue;
        }
        perror(&quot;poll&quot;);
        return 1;
    }

    for (int i = 0; i &lt; num_fds; ++i) {
        if (fds[i].revents &amp; POLL_IN) {
            const int count = read(fds[i].fd, buffer, sizeof(buffer) - 1);

            buffer[count - 1] = '\0';
            printf(&quot;Read '%s' from file descriptor %d\n&quot;, 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.

Andy Dalton
  • 13,993