diff --git a/libc/harness/test_exec.c b/libc/harness/test_exec.c index b659a8490..7402f2c50 100644 --- a/libc/harness/test_exec.c +++ b/libc/harness/test_exec.c @@ -189,8 +189,8 @@ int main(int argc, char *argv[]) return 1; } - if (signalPost(getpid(), atoi(argv[1]), SIGUSR1) != -EINVAL) { - fprintf(stderr, "signalPost didn't return EINVAL, thread still running in exec'd process!\n"); + if (sys_tkill(getpid(), atoi(argv[1]), SIGUSR1) != -EINVAL) { + fprintf(stderr, "sys_tkill didn't return EINVAL, thread still running in exec'd process!\n"); return 1; } } diff --git a/libc/pthread/pthread.c b/libc/pthread/pthread.c index cf53f8f49..3ce8718ca 100644 --- a/libc/pthread/pthread.c +++ b/libc/pthread/pthread.c @@ -107,6 +107,29 @@ static void *test_threadCleanup4(void *arg) } +static void *test_threadCleanupCancel(void *arg) +{ + int *val = (int *)arg; + + pthread_cleanup_push(test_cleanupHandler1, val); + pthread_cleanup_push(test_cleanupHandler2, val); + + pthread_mutex_lock(&thread_args.count_lock); + thread_args.count = 1; + pthread_cond_signal(&thread_args.count_nonzero); /* Signal that the thread is ready */ + pthread_mutex_unlock(&thread_args.count_lock); + + for (;;) { + sleep(1); /* Cancellation point */ + } + + pthread_cleanup_pop(0); + pthread_cleanup_pop(0); + + return NULL; +} + + TEST_GROUP(test_pthread_cond); TEST_GROUP(test_pthread_cleanup); @@ -379,6 +402,31 @@ TEST(test_pthread_cleanup, pthread_cleanup_push_pop_exec_pthread_exit) } +TEST(test_pthread_cleanup, pthread_cleanup_push_pthread_cancel) +{ + pthread_t thread; + int val = 42; + + TEST_ASSERT_EQUAL(0, pthread_mutex_init(&thread_args.count_lock, NULL)); + TEST_ASSERT_EQUAL(0, pthread_cond_init(&thread_args.count_nonzero, NULL)); + thread_args.count = 0; + + TEST_ASSERT_EQUAL_INT(0, pthread_create(&thread, NULL, test_threadCleanupCancel, &val)); + + pthread_mutex_lock(&thread_args.count_lock); + while (thread_args.count == 0) { + /* Wait for thread to enter the loop */ + pthread_cond_wait(&thread_args.count_nonzero, &thread_args.count_lock); + } + pthread_mutex_unlock(&thread_args.count_lock); + + TEST_ASSERT_EQUAL_INT(0, pthread_cancel(thread)); + TEST_ASSERT_EQUAL_INT(0, pthread_join(thread, NULL)); + + TEST_ASSERT_EQUAL(42 * 3 * 2, val); +} + + TEST_GROUP_RUNNER(test_pthread_cond) { RUN_TEST_CASE(test_pthread_cond, pthread_cond_init); @@ -399,4 +447,5 @@ TEST_GROUP_RUNNER(test_pthread_cleanup) RUN_TEST_CASE(test_pthread_cleanup, pthread_cleanup_push_pop_no_exec); RUN_TEST_CASE(test_pthread_cleanup, pthread_cleanup_push_pop_exec); RUN_TEST_CASE(test_pthread_cleanup, pthread_cleanup_push_pop_exec_pthread_exit); + RUN_TEST_CASE(test_pthread_cleanup, pthread_cleanup_push_pthread_cancel); } diff --git a/libc/signal/handler.c b/libc/signal/handler.c index 9c0973133..f29922075 100644 --- a/libc/signal/handler.c +++ b/libc/signal/handler.c @@ -18,6 +18,8 @@ #include #include #include +#include +#include #include @@ -49,9 +51,26 @@ TEST_TEAR_DOWN(handler) static volatile sig_atomic_t handler_haveSignal; static volatile sigset_t handler_sigset; +static volatile sig_atomic_t handler_countdown; +static volatile struct sigaction handler_sigaction; -void sighandler(int sig) +static pid_t safe_fork(void) +{ + pid_t pid; + if ((pid = fork()) < 0) { + if (errno == ENOSYS) { + TEST_IGNORE_MESSAGE("fork syscall not supported"); + } + else { + FAIL("fork"); + } + } + return pid; +} + + +static void sighandler(int sig) { sigset_t set; sigprocmask(SIG_SETMASK, NULL, &set); @@ -63,6 +82,41 @@ void sighandler(int sig) } +static void sighandlerRecursive(int sig) +{ + if (handler_countdown > 0) { + handler_countdown--; + sighandlerRecursive(sig); + } +} + + +static void sighandlerReraise(int sig) +{ + sigset_t set; + sigprocmask(SIG_SETMASK, NULL, &set); + + /* WARN: the write might not be atomic */ + handler_sigset = set; + + if (handler_countdown > 0) { + handler_countdown--; + raise(sig); + } +} + + +static void sighandlerAction(int sig) +{ + struct sigaction sa; + sa = handler_sigaction; + sigaction(sig, &sa, NULL); + if (sig == SIGUSR1) { + raise(sig); + } +} + + /* check if signal mask is set correctly inside and after the sighandler */ TEST(handler, sighandler_sa_mask) { @@ -352,3 +406,487 @@ TEST_GROUP_RUNNER(sigsuspend) RUN_TEST_CASE(sigsuspend, signal_before_handler); RUN_TEST_CASE(sigsuspend, signal_before_two_signals); } + + +TEST_GROUP(sigaction); + + +TEST_SETUP(sigaction) +{ + handler_haveSignal = 0u; + handler_countdown = 5; +} + + +TEST_TEAR_DOWN(sigaction) +{ + /* unblock all signals */ + sigset_t set; + sigemptyset(&set); + sigprocmask(SIG_SETMASK, &set, NULL); + + /* disable any active alarm timer */ + alarm(0); + + /* set default signal disposition for all signals */ + for (int signo = 1; signo < USERSPACE_NSIG; ++signo) { + signal(signo, SIG_DFL); + } +} + + +TEST(sigaction, signal_termination_statuscode) +{ + static const int termination_signals[] = { + SIGILL, SIGSEGV, SIGHUP, SIGINT, SIGQUIT, SIGTRAP, SIGABRT, SIGFPE, SIGKILL, SIGBUS, + SIGSYS, SIGPIPE, SIGALRM, SIGTERM, SIGIO, SIGXCPU, SIGXFSZ, SIGVTALRM, SIGPROF, SIGUSR1, SIGUSR2 + }; + + for (int i = 0; i < sizeof(termination_signals) / sizeof(termination_signals[0]); ++i) { + int sig = termination_signals[i]; + pid_t pid = safe_fork(); + TEST_ASSERT_GREATER_OR_EQUAL(0, pid); + if (pid > 0) { + int code; + waitpid(pid, &code, 0); + TEST_ASSERT_EQUAL_INT(true, WIFSIGNALED(code)); + TEST_ASSERT_EQUAL_HEX32(sig, WTERMSIG(code)); + } + else { + signal(sig, SIG_DFL); + raise(sig); + exit(0); + } + } +} + + +TEST(sigaction, signal_default_ignored) +{ + static const int ignored_signals[] = { SIGURG, SIGCHLD, SIGWINCH }; + + for (int i = 0; i < sizeof(ignored_signals) / sizeof(ignored_signals[0]); ++i) { + int sig = ignored_signals[i]; + pid_t pid = safe_fork(); + TEST_ASSERT_GREATER_OR_EQUAL(0, pid); + if (pid > 0) { + int code; + waitpid(pid, &code, 0); + TEST_ASSERT_EQUAL_INT(true, WIFEXITED(code)); + TEST_ASSERT_EQUAL_HEX32(0, WEXITSTATUS(code)); + } + else { + signal(sig, SIG_DFL); + raise(sig); + exit(0); + } + } +} + + +/* check if signal action is performed on unmasking after being changed */ +TEST(sigaction, unmask_changed_action_handler_to_ignore) +{ + sigset_t masked, empty; + sigemptyset(&empty); + sigemptyset(&masked); + sigaddset(&masked, SIGUSR1); + + sigprocmask(SIG_SETMASK, &masked, NULL); + signal(SIGUSR1, sighandler); + raise(SIGUSR1); + signal(SIGUSR1, SIG_IGN); + sigprocmask(SIG_SETMASK, &empty, NULL); + TEST_ASSERT_EQUAL_HEX32(0, handler_haveSignal); + signal(SIGUSR1, sighandler); + TEST_ASSERT_EQUAL_HEX32(0, handler_haveSignal); +} + + +TEST(sigaction, unmask_changed_action_default_to_ignore) +{ + sigset_t masked, empty; + sigemptyset(&empty); + sigemptyset(&masked); + sigaddset(&masked, SIGUSR1); + + sigprocmask(SIG_SETMASK, &masked, NULL); + signal(SIGUSR1, SIG_DFL); + raise(SIGUSR1); + signal(SIGUSR1, SIG_IGN); + sigprocmask(SIG_SETMASK, &empty, NULL); + TEST_ASSERT_EQUAL_HEX32(0, handler_haveSignal); + signal(SIGUSR1, SIG_DFL); + TEST_ASSERT_EQUAL_HEX32(0, handler_haveSignal); +} + + +TEST(sigaction, unmask_changed_action_default_to_handler) +{ + sigset_t masked, empty; + sigemptyset(&empty); + sigemptyset(&masked); + sigaddset(&masked, SIGUSR1); + + sigprocmask(SIG_SETMASK, &masked, NULL); + signal(SIGUSR1, SIG_DFL); + raise(SIGUSR1); + signal(SIGUSR1, sighandler); + sigprocmask(SIG_SETMASK, &empty, NULL); + TEST_ASSERT_EQUAL_HEX32((1u << SIGUSR1), handler_haveSignal); +} + + +TEST(sigaction, unmask_changed_action_handler_to_default_ignored) +{ + sigset_t masked, empty; + sigemptyset(&empty); + sigemptyset(&masked); + sigaddset(&masked, SIGURG); + + sigprocmask(SIG_SETMASK, &masked, NULL); + signal(SIGURG, sighandler); + raise(SIGURG); + signal(SIGURG, SIG_DFL); + sigprocmask(SIG_SETMASK, &empty, NULL); + TEST_ASSERT_EQUAL_HEX32(0, handler_haveSignal); + signal(SIGURG, sighandler); + TEST_ASSERT_EQUAL_HEX32(0, handler_haveSignal); +} + + +TEST(sigaction, unmask_changed_action_handler_to_default) +{ + sigset_t masked, empty; + sigemptyset(&empty); + sigemptyset(&masked); + sigaddset(&masked, SIGUSR1); + + pid_t pid = safe_fork(); + TEST_ASSERT_GREATER_OR_EQUAL(0, pid); + if (pid > 0) { + int code; + wait(&code); + TEST_ASSERT_EQUAL_INT(true, WIFSIGNALED(code)); + } + else { + handler_haveSignal = 0u; + sigprocmask(SIG_SETMASK, &masked, NULL); + signal(SIGUSR1, sighandler); + raise(SIGUSR1); + signal(SIGUSR1, SIG_DFL); + sigprocmask(SIG_SETMASK, &empty, NULL); + /* POSIX: after pthread_sigmask() changes the currently blocked set of signals it shall determine + * whether there are any pending unblocked signals; if there are any, then at least one of those signals + * shall be delivered before the call to pthread_sigmask() returns + */ + + exit(0); + } +} + + +/* check if masked signal is not delivered when action is modified */ +TEST(sigaction, masked_sigaction) +{ + sigset_t empty; + sigemptyset(&empty); + sigset_t set; + sigemptyset(&set); + sigaddset(&set, SIGUSR1); + + sigprocmask(SIG_SETMASK, &set, NULL); + raise(SIGUSR1); + + struct sigaction act = { + .sa_handler = 0, + .sa_flags = 0, + .sa_mask = empty, + }; + + /* verify that changing masked signal doesn't cause any delivery */ + + signal(SIGUSR1, sighandler); + TEST_ASSERT_EQUAL_HEX32(0, handler_haveSignal); + act.sa_handler = sighandler; + sigaction(SIGUSR1, &act, NULL); + TEST_ASSERT_EQUAL_HEX32(0, handler_haveSignal); + + signal(SIGUSR1, SIG_DFL); + TEST_ASSERT_EQUAL_HEX32(0, handler_haveSignal); + act.sa_handler = SIG_DFL; + sigaction(SIGUSR1, &act, NULL); + TEST_ASSERT_EQUAL_HEX32(0, handler_haveSignal); + + + signal(SIGUSR1, sighandler); + TEST_ASSERT_EQUAL_HEX32(0, handler_haveSignal); + act.sa_handler = sighandler; + sigaction(SIGUSR1, &act, NULL); + TEST_ASSERT_EQUAL_HEX32(0, handler_haveSignal); + + signal(SIGUSR1, SIG_IGN); + TEST_ASSERT_EQUAL_HEX32(0, handler_haveSignal); + act.sa_handler = SIG_IGN; + sigaction(SIGUSR1, &act, NULL); + TEST_ASSERT_EQUAL_HEX32(0, handler_haveSignal); + + + signal(SIGUSR1, sighandler); + TEST_ASSERT_EQUAL_HEX32(0, handler_haveSignal); + act.sa_handler = sighandler; + sigaction(SIGUSR1, &act, NULL); + TEST_ASSERT_EQUAL_HEX32(0, handler_haveSignal); + + raise(SIGUSR1); /* POSIX: setting to SIG_IGN can release pending signal */ + + /* verify that changing other masked signal doesn't cause masked delivery */ + + sigaddset(&set, SIGUSR2); + sigprocmask(SIG_SETMASK, &set, NULL); + + signal(SIGUSR2, SIG_IGN); + TEST_ASSERT_EQUAL_HEX32(0, handler_haveSignal); + act.sa_handler = SIG_IGN; + sigaction(SIGUSR2, &act, NULL); + + signal(SIGUSR2, SIG_DFL); + TEST_ASSERT_EQUAL_HEX32(0, handler_haveSignal); + act.sa_handler = SIG_DFL; + sigaction(SIGUSR2, &act, NULL); + + signal(SIGUSR2, sighandler); + TEST_ASSERT_EQUAL_HEX32(0, handler_haveSignal); + act.sa_handler = sighandler; + sigaction(SIGUSR2, &act, NULL); + + raise(SIGUSR2); + TEST_ASSERT_EQUAL_HEX32(0, handler_haveSignal); + + sigdelset(&set, SIGUSR2); + sigprocmask(SIG_SETMASK, &set, NULL); + TEST_ASSERT_EQUAL_HEX32((1u << SIGUSR2), handler_haveSignal); + + /* verify that changing other unmasked signal doesn't cause masked delivery */ + handler_haveSignal = 0u; + + signal(SIGUSR2, SIG_IGN); + TEST_ASSERT_EQUAL_HEX32(0, handler_haveSignal); + act.sa_handler = SIG_IGN; + sigaction(SIGUSR2, &act, NULL); + + signal(SIGUSR2, SIG_DFL); + TEST_ASSERT_EQUAL_HEX32(0, handler_haveSignal); + act.sa_handler = SIG_DFL; + sigaction(SIGUSR2, &act, NULL); + + signal(SIGUSR2, sighandler); + TEST_ASSERT_EQUAL_HEX32(0, handler_haveSignal); + act.sa_handler = sighandler; + sigaction(SIGUSR2, &act, NULL); + + raise(SIGUSR2); + TEST_ASSERT_EQUAL_HEX32((1u << SIGUSR2), handler_haveSignal); + + /* verify that signal is still pending and will be delivered */ + + sigemptyset(&set); + sigprocmask(SIG_SETMASK, &set, NULL); + + TEST_ASSERT_EQUAL_HEX32((1u << SIGUSR1) | (1u << SIGUSR2), handler_haveSignal); +} + + +TEST(sigaction, handler_recursion_direct) +{ + sigset_t empty; + sigemptyset(&empty); + + struct sigaction act = { + .sa_handler = sighandlerRecursive, + .sa_flags = 0, + .sa_mask = empty, + }; + sigaction(SIGUSR1, &act, NULL); + raise(SIGUSR1); + TEST_ASSERT_EQUAL_INT(0, handler_countdown); +} + + +TEST(sigaction, handler_recursion_raise) +{ + sigset_t set; + sigset_t empty; + sigemptyset(&empty); + + struct sigaction act = { + .sa_handler = sighandlerReraise, + .sa_flags = 0, + .sa_mask = empty, + }; + + sigaction(SIGUSR1, &act, NULL); + raise(SIGUSR1); + while (handler_countdown > 0) { + sigsuspend(&empty); + } + TEST_ASSERT_EQUAL_INT(0, handler_countdown); + set = handler_sigset; + TEST_ASSERT_EQUAL_INT(1, sigismember(&set, SIGUSR1)); +} + + +TEST(sigaction, handler_recursion_raise_nodefer) +{ + sigset_t set; + sigset_t empty; + sigemptyset(&empty); + + struct sigaction act = { + .sa_handler = sighandlerReraise, + .sa_flags = SA_NODEFER, + .sa_mask = empty, + }; + + sigaction(SIGUSR1, &act, NULL); + raise(SIGUSR1); + TEST_ASSERT_EQUAL_INT(0, handler_countdown); + set = handler_sigset; + TEST_ASSERT_EQUAL_INT(0, sigismember(&set, SIGUSR1)); +} + + +TEST(sigaction, sigaction_in_handler_handle) +{ + sigset_t empty; + sigemptyset(&empty); + + struct sigaction action = { + .sa_handler = sighandlerAction, + .sa_flags = 0, + .sa_mask = empty, + }; + handler_sigaction.sa_handler = sighandler; + handler_sigaction.sa_flags = 0; + handler_sigaction.sa_mask = empty; + sigaction(SIGUSR2, &action, NULL); + raise(SIGUSR2); + TEST_ASSERT_EQUAL_HEX32(0u, handler_haveSignal); + raise(SIGUSR2); + TEST_ASSERT_EQUAL_HEX32((1u << SIGUSR2), handler_haveSignal); +} + + +TEST(sigaction, sigaction_in_handler_handle_reraise) +{ + sigset_t empty; + sigemptyset(&empty); + + handler_sigaction.sa_handler = sighandler; + handler_sigaction.sa_flags = 0; + handler_sigaction.sa_mask = empty; + struct sigaction action = { + .sa_handler = sighandlerAction, + .sa_flags = 0, + .sa_mask = empty, + }; + sigaction(SIGUSR1, &action, NULL); + raise(SIGUSR1); + if (handler_haveSignal == 0u) { + sigsuspend(&empty); + } + TEST_ASSERT_EQUAL_HEX32((1u << SIGUSR1), handler_haveSignal); +} + + +TEST(sigaction, sigaction_in_handler_nodefer_handle_reraise) +{ + sigset_t empty; + sigemptyset(&empty); + + handler_sigaction.sa_handler = sighandler; + handler_sigaction.sa_flags = 0; + handler_sigaction.sa_mask = empty; + struct sigaction actionNodefer = { + .sa_handler = sighandlerAction, + .sa_flags = SA_NODEFER, + .sa_mask = empty, + }; + sigaction(SIGUSR1, &actionNodefer, NULL); + raise(SIGUSR1); + TEST_ASSERT_EQUAL_HEX32((1u << SIGUSR1), handler_haveSignal); +} + + +TEST(sigaction, sigaction_in_handler_ignore) +{ + sigset_t empty; + sigemptyset(&empty); + + handler_sigaction.sa_handler = SIG_IGN; + handler_sigaction.sa_flags = 0; + handler_sigaction.sa_mask = empty; + signal(SIGUSR1, &sighandlerAction); + raise(SIGUSR1); + raise(SIGUSR1); + raise(SIGUSR1); + TEST_ASSERT_EQUAL_PTR(SIG_IGN, signal(SIGUSR1, SIG_IGN)); +} + + +TEST(sigaction, sigaction_in_handler_default) +{ + sigset_t empty; + sigemptyset(&empty); + + struct sigaction actionNodefer = { + .sa_handler = sighandlerAction, + .sa_flags = SA_NODEFER, + .sa_mask = empty, + }; + + pid_t pid = safe_fork(); + TEST_ASSERT_GREATER_OR_EQUAL(0, pid); + if (pid > 0) { + int code; + wait(&code); + TEST_ASSERT_EQUAL_INT(true, WIFSIGNALED(code)); + TEST_ASSERT_EQUAL_INT(SIGUSR1, WTERMSIG(code)); + } + else { + handler_sigaction.sa_handler = SIG_DFL; + handler_sigaction.sa_flags = 0; + handler_sigaction.sa_mask = empty; + sigaction(SIGUSR1, &actionNodefer, NULL); + raise(SIGUSR1); + exit(0); + } +} + + +TEST_GROUP_RUNNER(sigaction) +{ + RUN_TEST_CASE(sigaction, signal_termination_statuscode); + RUN_TEST_CASE(sigaction, signal_default_ignored); + + RUN_TEST_CASE(sigaction, unmask_changed_action_handler_to_ignore); + RUN_TEST_CASE(sigaction, unmask_changed_action_default_to_ignore); + RUN_TEST_CASE(sigaction, unmask_changed_action_default_to_handler); + RUN_TEST_CASE(sigaction, unmask_changed_action_handler_to_default_ignored); + RUN_TEST_CASE(sigaction, unmask_changed_action_handler_to_default); + /* initial SIG_IGN is omitted, as: + * POSIX: setting sigaction to SIG_IGN can release pending signal + */ + + RUN_TEST_CASE(sigaction, handler_recursion_direct); + RUN_TEST_CASE(sigaction, handler_recursion_raise); + RUN_TEST_CASE(sigaction, handler_recursion_raise_nodefer); + + RUN_TEST_CASE(sigaction, masked_sigaction); + + RUN_TEST_CASE(sigaction, sigaction_in_handler_handle); + RUN_TEST_CASE(sigaction, sigaction_in_handler_handle_reraise); + RUN_TEST_CASE(sigaction, sigaction_in_handler_nodefer_handle_reraise); + RUN_TEST_CASE(sigaction, sigaction_in_handler_ignore); + RUN_TEST_CASE(sigaction, sigaction_in_handler_default); +} diff --git a/libc/signal/main.c b/libc/signal/main.c index e33b134e9..3da76d1d3 100644 --- a/libc/signal/main.c +++ b/libc/signal/main.c @@ -23,6 +23,7 @@ void runner(void) { RUN_TEST_GROUP(mask); RUN_TEST_GROUP(handler); + RUN_TEST_GROUP(sigaction); RUN_TEST_GROUP(sigsuspend); } diff --git a/proc/test_signals.c b/proc/test_signals.c index 8b5c589f5..42f6ca41e 100644 --- a/proc/test_signals.c +++ b/proc/test_signals.c @@ -38,9 +38,9 @@ void signaler(void) signalMask(0xffff, 0xffff); for (;;) { - signalPost(pid, -1, 1 + rand_r(&seed) % 31); + sys_tkill(pid, -1, 1 + rand_r(&seed) % 31); usleep(rand_r(&seed) % (50 * 1000)); - signalPost(ppid, -1, 1 + rand_r(&seed) % 31); + sys_tkill(ppid, -1, 1 + rand_r(&seed) % 31); usleep(rand_r(&seed) % (50 * 1000)); } }