Repo: ovfor4/ccshell/module-test/self_pipe.c

Problem

Consider

if (not_signald())
    read(STDIN_FILENO, s, len);

No matter SA_RESTART is set or not, if signal’d between check and read, there will be race

Conclusion: signal delivering is an instant event, but we want it to be durable event, and need to find some calls to return immediately if such events happen; and if we check such flags manually, atomicity is bad, so race can happen if it’s signal’d between these statements

select(2) or poll(2) can check multiple file descriptors at the same time, so if we write something to the pipe, it’s durable, so we can wait for either stdin or our pipe of signal handler

Limits of select(2)

POSIX standard says, without specifying the size

The <sys/select.h> header shall define the fd_set type as a structure.

On many implementations, the size is very limited for modern applications

  • on Linux

select() can monitor only file descriptors numbers that are less than FD_SETSIZE (1024) […] and this limitation will not change"

  • on FreeBSD

The default size of FD_SETSIZE is currently 1024.

“Luckily”, we can define FD_SETSIZE or use a hack (malloc manually and just get a pointer), but it’s not portable, and it’s dependent on the platform

Notes for poll(2)

select(2) is an exception of SA_RESTART

If SA_RESTART has been set for the interrupting signal, it is implementation-defined whether the function restarts or returns with [EINTR].

poll(2) shall NOT be an exception, that is, if signal’d, it shall be restarted per standard

If set, and a function specified as interruptible is interrupted by this signal, the function shall restart and shall not fail with [EINTR] unless otherwise specified

However, Linux, FreeBSD, Darwin are bad kernels

  • on Linux, signal(7)

The following interfaces are never restarted after being interrupted by a signal handler, regardless of the use of SA_RESTART; they always fail with the error EINTR when interrupted by a signal handler: […] poll(2)"

  • on FreeBSD, documentation doesn’t specify the behavior of poll(2), but we can find the answer in source code
// freebsd/sys/kern/sys_generic.c
// kern_poll_kfds()
/* poll is not restarted after signals... */
if (error == ERESTART)
	error = EINTR;
if (error == EWOULDBLOCK)
	error = 0;
  • on Darwin, like FreeBSD, it doesn’t specify the behavior too, but we can still find similar code
// darwin/bsd/kern/sys_generic.c
// poll_nocancel()
/* poll is not restarted after signals... */
if (error == ERESTART) {
	error = EINTR;
}
if (error == 0) {
	error = copyout(fds, uap->fds, nfds * sizeof(struct pollfd));
	*retval = rfds;
}

But we can simply wrap it, so it can auto restart no matter SA_RESTART is set or not, nor the implementation restarts it or not

If it would be auto restarted, poll should not return as if signal’d

Otherrise, when signal’d, poll should return as if signal’d first, and then we restart to check things in pipe, and this should return immediately (our signal handlers should put something into pipe)

int my_poll(struct pollfd fds[], nfds_t nfds)
{
    while (true)
    {
        int ret = poll(fds, nfds, -1);
        // if not signal'd
        if (!(ret == -1 && errno == EINTR))
            return ret;
    }
}