diff --git a/bin/utest/signal.c b/bin/utest/signal.c index d2ec0e1d87..488b92bdf2 100644 --- a/bin/utest/signal.c +++ b/bin/utest/signal.c @@ -274,6 +274,114 @@ TEST_ADD(signal_sigsuspend, 0) { return 0; } +/* ======= signal_sigtimedwait ======= */ +static int sigtimedwait_child(__unused void *arg) { + pid_t ppid = getppid(); + xkill(ppid, SIGUSR1); + return 0; +} + +TEST_ADD(signal_sigtimedwait, 0) { + xsignal(SIGCONT, sigcont_handler); + sigset_t set, current, waitset; + sigemptyset(&set); + sigaddset(&set, SIGUSR1); + sigaddset(&set, SIGCONT); + xsigprocmask(SIG_SETMASK, &set, NULL); + + spawn(sigtimedwait_child, NULL); + + siginfo_t info; + sigemptyset(&waitset); + sigaddset(&waitset, SIGUSR1); + assert(sigtimedwait(&waitset, &info, NULL) == SIGUSR1); + assert(info.si_signo == SIGUSR1); + + xsigprocmask(SIG_BLOCK, NULL, ¤t); + assert(sigsetequal(&set, ¤t)); + + wait_child_finished(0); + return 0; +} + +/* ======= signal_sigtimedwait_timeout ======= */ +static int sigtimedwait_timeout_child(__unused void *arg) { + ppid = getppid(); + xkill(ppid, SIGUSR1); + xkill(ppid, SIGCONT); + return 0; +} + +TEST_ADD(signal_sigtimedwait_timeout, 0) { + xsignal(SIGCONT, sigcont_handler); + sigset_t set, waitset; + sigemptyset(&set); + sigaddset(&set, SIGUSR1); + xsigprocmask(SIG_SETMASK, &set, NULL); + + siginfo_t info; + sigemptyset(&waitset); + sigaddset(&waitset, SIGUSR1); + timespec_t timeout = { + .tv_nsec = -1, + .tv_sec = -1, + }; + + /* tv_nsec is invalid. */ + syscall_fail(sigtimedwait(&waitset, &info, &timeout), EINVAL); + + /* tv_nsec is valid, but tv_sec < 0. */ + timeout.tv_nsec = 10000000; + syscall_fail(sigtimedwait(&waitset, &info, &timeout), EAGAIN); + + timeout.tv_sec = 0; + /* Should timeout. */ + syscall_fail(sigtimedwait(&waitset, &info, &timeout), EAGAIN); + + spawn(sigtimedwait_timeout_child, NULL); + + /* If we handled sigcont, then SIGUSR1 must be pending. */ + while (!sigcont_handled) + sched_yield(); + + /* Should not block, but receive the signal as it is in the pending mask. */ + timeout.tv_nsec = 0; + assert(sigtimedwait(&waitset, &info, &timeout) == SIGUSR1); + assert(info.si_signo == SIGUSR1); + + wait_child_finished(0); + return 0; +} + +/* ======= signal_sigtimedwait_intr ====== */ +int sigtimedwait_intr_child(void *arg) { + pid_t ppid = getppid(); + while (!sigcont_handled) { + xkill(ppid, SIGCONT); + } + return 0; +} + +TEST_ADD(signal_sigtimedwait_intr, 0) { + xsignal(SIGCONT, sigcont_handler); + sigset_t set, waitset; + sigemptyset(&set); + sigaddset(&set, SIGUSR1); + xsigprocmask(SIG_SETMASK, &set, NULL); + + siginfo_t info; + sigemptyset(&waitset); + sigaddset(&waitset, SIGUSR1); + + pid_t cpid = spawn(sigtimedwait_intr_child, NULL); + + syscall_fail(sigtimedwait(&waitset, &info, NULL), EINTR); + + xkill(cpid, SIGCONT); + wait_child_finished(0); + return 0; +} + /* ======= signal_sigsuspend_stop ======= */ TEST_ADD(signal_sigsuspend_stop, 0) { pid_t ppid = getpid(); diff --git a/etc/rc.init b/etc/rc.init old mode 100644 new mode 100755 diff --git a/etc/rc.shutdown b/etc/rc.shutdown old mode 100644 new mode 100755 diff --git a/sys/kern/signal.c b/sys/kern/signal.c index 63eb1c42a0..0b30668474 100644 --- a/sys/kern/signal.c +++ b/sys/kern/signal.c @@ -206,7 +206,89 @@ int do_sigsuspend(proc_t *p, const sigset_t *mask) { int do_sigtimedwait(proc_t *p, sigset_t waitset, ksiginfo_t *ksi, timespec_t *tsp) { - return ENOTSUP; + int error = 0, timeout = 0; + sigset_t saved_mask; + signo_t sig; + thread_t *td = p->p_thread; + + /* + * Calculate timeout based on tsp. + * + * NULL pointer means an infinite timeout. + * {.tv_sec = 0, .tv_nsec = 0} means to not block. + * Timeout is valid only if 0 <= tv_nsec <= 1e9. If this is not true, + * immediately return EINVAL. If tv_sec < 0 immediately return EAGAIN. + * + * We set the timeout value such that: + * - timeout == -1: do not block + * - timeout == 0: wait indefinitely + * - timeout > 0: wait for timeout. + */ + if (tsp != NULL) { + if (tsp->tv_nsec < 0 || tsp->tv_nsec >= 1000000000) { + return EINVAL; + } + if (tsp->tv_sec < 0) { + return EAGAIN; + } + + timeout = ts2hz(tsp); + if (timeout == 0) { + if (tsp->tv_nsec == 0 && tsp->tv_sec == 0) { + timeout = -1; + } else { + /* Shortest possible timeout */ + timeout = 1; + } + } + } + + bzero(ksi, sizeof(*ksi)); + + /* Silently ignore SIGKILL and SIGSTOP. */ + __sigminusset(&cantmask, &waitset); + + WITH_PROC_LOCK(p) { + /* Unblocking temporarly waited signals so we're woken up upon receiving + * such signal. */ + saved_mask = td->td_sigmask; + __sigminusset(&waitset, &td->td_sigmask); + + /* We need to find pending signal that we also wait for manually, as + * sig_pending may return a pending signal not from waitset. */ + sigset_t pending = td->td_sigpend.sp_set; + __sigandset(&waitset, &pending); + if ((sig = __sigfindset(&pending))) { + sigpend_get(&td->td_sigpend, sig, ksi); + error = 0; + goto out; + } + if (timeout == -1) { + error = EAGAIN; + goto out; + } + + error = + sleepq_wait_timed(&td->td_sigmask, "sigtimedwait()", &p->p_lock, timeout); + + if (error == ETIMEDOUT) { + error = EAGAIN; + goto out; + } + + pending = td->td_sigpend.sp_set; + __sigandset(&waitset, &pending); + if ((sig = __sigfindset(&pending))) { + sigpend_get(&td->td_sigpend, sig, ksi); + error = 0; + } else { + error = EINTR; + } + } + +out: + td->td_sigmask = saved_mask; + return error; } int do_sigpending(proc_t *p, sigset_t *set) { diff --git a/sys/kern/sleepq.c b/sys/kern/sleepq.c index 197aed844e..544cedd94b 100644 --- a/sys/kern/sleepq.c +++ b/sys/kern/sleepq.c @@ -324,7 +324,10 @@ int sleepq_wait_timed(void *wchan, const void *waitpt, mtx_t *mtx, callout_schedule(&td->td_slpcallout, timeout); } - td->td_flags |= (timeout > 0) ? TDF_SLPTIMED : TDF_SLPINTR; + td->td_flags |= TDF_SLPINTR; + if (timeout > 0) + td->td_flags |= TDF_SLPTIMED; + sq_enter(td, sc, wchan, waitpt); /* After wakeup, only one of the following flags may be set: