461 lines
17 KiB
C
461 lines
17 KiB
C
// Runtime coverage for P6 POSIX signal delivery, waits, spawn state, and exceptions.
|
|
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <signal.h>
|
|
#include <unistd.h>
|
|
#include <pthread.h>
|
|
#include <spawn.h>
|
|
#include <fcntl.h>
|
|
#include <sys/wait.h>
|
|
#include <ntlite/syscall.h>
|
|
#include <ntlite/posix_extensions.h>
|
|
|
|
#define GROUP_READ_FD 50
|
|
#define GROUP_WRITE_FD 51
|
|
|
|
static volatile sig_atomic_t gHandlerCount;
|
|
static volatile uint32_t gHandlerTid;
|
|
static volatile int gWorkerReady;
|
|
static volatile int gWorkerInheritedBlock;
|
|
static volatile int gWorkerStop;
|
|
static volatile sig_atomic_t gNestedCount;
|
|
static volatile sig_atomic_t gAlarmCount;
|
|
static volatile sig_atomic_t gPipeCount;
|
|
|
|
// Writes one test diagnostic line to the native console.
|
|
static void print(const char* text) {
|
|
SYS_WRITE_CONSOLE_REQUEST request = { sizeof(request), SysSvFromCstr(text) };
|
|
(void)SysWriteConsole(&request);
|
|
}
|
|
|
|
// Exits immediately after one failed assertion.
|
|
static void fail(const char* message) {
|
|
print("posix_signal_smoke: FAIL: ");
|
|
print(message);
|
|
print("\n");
|
|
_exit(1);
|
|
}
|
|
|
|
// Returns whether the compatibility command line contains one child marker.
|
|
static bool child_mode(const char* marker) {
|
|
const char* command = posix_get_command_line();
|
|
return command && marker && strstr(command, marker) != (char*)0;
|
|
}
|
|
|
|
// Resolves the current executable path from the compatibility command line.
|
|
static void get_self_path(char* path, size_t capacity) {
|
|
const char* command = posix_get_command_line();
|
|
size_t length = 0;
|
|
while (command && command[length] && command[length] != ' ' && command[length] != '\t' &&
|
|
length + 1u < capacity) {
|
|
path[length] = command[length];
|
|
++length;
|
|
}
|
|
path[length] = '\0';
|
|
if (length == 0 || (command[length] && command[length] != ' ' && command[length] != '\t'))
|
|
_exit(90);
|
|
}
|
|
|
|
// Moves one descriptor to an exact number and closes the source alias.
|
|
static void move_fd(int source, int target) {
|
|
if (source < 0 || dup2(source, target) != target)
|
|
fail("control dup2 failed");
|
|
if (source != target && close(source) != 0)
|
|
fail("control source close failed");
|
|
}
|
|
|
|
// Records the native thread selected for one caught user signal.
|
|
static void signal_handler(int signalNumber) {
|
|
if (signalNumber != SIGUSR1) return;
|
|
gHandlerTid = SysGetCurrentTid();
|
|
++gHandlerCount;
|
|
}
|
|
|
|
// Queues the same signal once while its first handler activation masks it.
|
|
static void nested_handler(int signalNumber) {
|
|
if (signalNumber != SIGUSR1) return;
|
|
++gNestedCount;
|
|
if (gNestedCount == 1 && raise(SIGUSR1) != 0)
|
|
_exit(91);
|
|
}
|
|
|
|
// Records one SIGALRM used to interrupt alertable POSIX waits.
|
|
static void alarm_handler(int signalNumber) {
|
|
if (signalNumber == SIGALRM) ++gAlarmCount;
|
|
}
|
|
|
|
// Records SIGPIPE generation while allowing write() to return EPIPE.
|
|
static void pipe_handler(int signalNumber) {
|
|
if (signalNumber == SIGPIPE) ++gPipeCount;
|
|
}
|
|
|
|
// Terminates the process-group child from asynchronous SIGUSR1 delivery.
|
|
static void group_handler(int signalNumber) {
|
|
if (signalNumber == SIGUSR1) _exit(61);
|
|
}
|
|
|
|
// Terminates the exception child after native access-fault translation.
|
|
static void fault_handler(int signalNumber) {
|
|
if (signalNumber == SIGSEGV) _exit(62);
|
|
}
|
|
|
|
// Keeps one registered unblocked POSIX thread available for process-directed delivery.
|
|
static void* signal_worker(void* argument) {
|
|
(void)argument;
|
|
sigset_t current = 0;
|
|
if (sigprocmask(SIG_SETMASK, (const sigset_t*)0, ¤t) != 0)
|
|
return (void*)(uintptr_t)1u;
|
|
gWorkerInheritedBlock = sigismember(¤t, SIGUSR1) == 1;
|
|
|
|
sigset_t unblock = 0;
|
|
if (sigemptyset(&unblock) != 0 || sigaddset(&unblock, SIGUSR1) != 0 ||
|
|
sigprocmask(SIG_UNBLOCK, &unblock, (sigset_t*)0) != 0)
|
|
return (void*)(uintptr_t)1u;
|
|
|
|
gWorkerReady = 1;
|
|
while (!gWorkerStop && gHandlerCount < 3) (void)SysYield();
|
|
return (void*)0;
|
|
}
|
|
|
|
// Validates signal mask/default attributes before ordinary child entry proceeds.
|
|
static void run_spawn_attribute_child(void) {
|
|
sigset_t current = 0;
|
|
if (sigprocmask(SIG_SETMASK, (const sigset_t*)0, ¤t) != 0 ||
|
|
sigismember(¤t, SIGUSR1) != 1)
|
|
_exit(70);
|
|
struct sigaction action;
|
|
if (sigaction(SIGUSR2, (const struct sigaction*)0, &action) != 0 ||
|
|
action.sa_handler != SIG_DFL)
|
|
_exit(71);
|
|
_exit(0);
|
|
}
|
|
|
|
// Announces readiness, then waits for one process-group-directed SIGUSR1.
|
|
static void run_group_child(void) {
|
|
struct sigaction action;
|
|
action.sa_handler = group_handler;
|
|
action.sa_mask = 0;
|
|
action.sa_flags = 0;
|
|
if (sigaction(SIGUSR1, &action, (struct sigaction*)0) != 0)
|
|
_exit(72);
|
|
if (write(GROUP_WRITE_FD, "R", 1) != 1)
|
|
_exit(73);
|
|
(void)close(GROUP_WRITE_FD);
|
|
for (;;) (void)pause();
|
|
}
|
|
|
|
// Waits for the parent to exercise one ordinary default-terminate disposition.
|
|
static void run_default_child(void) {
|
|
for (;;) (void)pause();
|
|
}
|
|
|
|
// Triggers one native access fault after installing a POSIX SIGSEGV handler.
|
|
static void run_exception_child(void) {
|
|
struct sigaction action;
|
|
action.sa_handler = fault_handler;
|
|
action.sa_mask = 0;
|
|
action.sa_flags = 0;
|
|
if (sigaction(SIGSEGV, &action, (struct sigaction*)0) != 0)
|
|
_exit(74);
|
|
*(volatile uint64_t*)(uintptr_t)1u = 0x1234u;
|
|
_exit(75);
|
|
}
|
|
|
|
// Spawns one child and requires one exact ordinary exit result.
|
|
static void wait_for_child(pid_t child, int expectedExit) {
|
|
int status = -1;
|
|
if (waitpid(child, &status, 0) != child || !WIFEXITED(status) ||
|
|
WEXITSTATUS(status) != expectedExit)
|
|
fail("child exit result");
|
|
}
|
|
|
|
// Verifies spawn-time signal masks and default-disposition reset policy.
|
|
static void test_spawn_signal_attributes(const char* self) {
|
|
struct sigaction ignore;
|
|
ignore.sa_handler = SIG_IGN;
|
|
ignore.sa_mask = 0;
|
|
ignore.sa_flags = 0;
|
|
if (sigaction(SIGUSR2, &ignore, (struct sigaction*)0) != 0)
|
|
fail("install inherited ignore");
|
|
|
|
sigset_t mask = 0;
|
|
sigset_t defaults = 0;
|
|
if (sigemptyset(&mask) != 0 || sigaddset(&mask, SIGUSR1) != 0 ||
|
|
sigemptyset(&defaults) != 0 || sigaddset(&defaults, SIGUSR2) != 0)
|
|
fail("spawn signal sets");
|
|
|
|
posix_spawnattr_t attributes;
|
|
if (posix_spawnattr_init(&attributes) != 0 ||
|
|
posix_spawnattr_setsigmask(&attributes, &mask) != 0 ||
|
|
posix_spawnattr_setsigdefault(&attributes, &defaults) != 0 ||
|
|
posix_spawnattr_setflags(&attributes, POSIX_SPAWN_SETSIGMASK | POSIX_SPAWN_SETSIGDEF) != 0)
|
|
fail("spawn signal attributes");
|
|
sigset_t check = 0;
|
|
if (posix_spawnattr_getsigmask(&attributes, &check) != 0 || check != mask ||
|
|
posix_spawnattr_getsigdefault(&attributes, &check) != 0 || check != defaults)
|
|
fail("spawn signal attribute getters");
|
|
|
|
char* argv[] = { (char*)self, "--p6-spawn-attrs", (char*)0 };
|
|
pid_t child = (pid_t)-1;
|
|
if (posix_spawn(&child, self, (const posix_spawn_file_actions_t*)0,
|
|
&attributes, argv, (char* const*)0) != 0 || child <= 0)
|
|
fail("spawn signal child");
|
|
if (posix_spawnattr_destroy(&attributes) != 0)
|
|
fail("spawn attribute destroy");
|
|
wait_for_child(child, 0);
|
|
|
|
struct sigaction reset;
|
|
reset.sa_handler = SIG_DFL;
|
|
reset.sa_mask = 0;
|
|
reset.sa_flags = 0;
|
|
if (sigaction(SIGUSR2, &reset, (struct sigaction*)0) != 0)
|
|
fail("restore SIGUSR2 default");
|
|
}
|
|
|
|
// Verifies actual process-group routing to a child in a newly-created group.
|
|
static void test_process_group_delivery(const char* self) {
|
|
int descriptors[2];
|
|
if (pipe(descriptors) != 0)
|
|
fail("group pipe");
|
|
move_fd(descriptors[0], GROUP_READ_FD);
|
|
move_fd(descriptors[1], GROUP_WRITE_FD);
|
|
if (fcntl(GROUP_READ_FD, F_SETFD, FD_CLOEXEC) != 0)
|
|
fail("group read CLOEXEC");
|
|
|
|
posix_spawnattr_t attributes;
|
|
if (posix_spawnattr_init(&attributes) != 0 ||
|
|
posix_spawnattr_setpgroup(&attributes, 0) != 0 ||
|
|
posix_spawnattr_setflags(&attributes, POSIX_SPAWN_SETPGROUP) != 0)
|
|
fail("group spawn attributes");
|
|
char* argv[] = { (char*)self, "--p6-group-child", (char*)0 };
|
|
pid_t child = (pid_t)-1;
|
|
if (posix_spawn(&child, self, (const posix_spawn_file_actions_t*)0,
|
|
&attributes, argv, (char* const*)0) != 0 || child <= 0)
|
|
fail("group child spawn");
|
|
if (posix_spawnattr_destroy(&attributes) != 0)
|
|
fail("group spawn attribute destroy");
|
|
if (close(GROUP_WRITE_FD) != 0)
|
|
fail("group parent write close");
|
|
|
|
char ready = 0;
|
|
if (read(GROUP_READ_FD, &ready, 1) != 1 || ready != 'R')
|
|
fail("group child readiness");
|
|
if (close(GROUP_READ_FD) != 0)
|
|
fail("group parent read close");
|
|
if (kill(-child, SIGUSR1) != 0)
|
|
fail("process-group signal delivery");
|
|
wait_for_child(child, 61);
|
|
}
|
|
|
|
// Verifies an ordinary default disposition terminates the target process.
|
|
static void test_default_disposition(const char* self) {
|
|
char* argv[] = { (char*)self, "--p6-default", (char*)0 };
|
|
pid_t child = (pid_t)-1;
|
|
if (posix_spawn(&child, self, (const posix_spawn_file_actions_t*)0,
|
|
(const posix_spawnattr_t*)0, argv, (char* const*)0) != 0 || child <= 0)
|
|
fail("default-disposition child spawn");
|
|
if (kill(child, SIGTERM) != 0)
|
|
fail("default-disposition signal");
|
|
wait_for_child(child, 128 + SIGTERM);
|
|
}
|
|
|
|
// Verifies selected native exceptions enter the POSIX signal adapter.
|
|
static void test_exception_translation(const char* self) {
|
|
char* argv[] = { (char*)self, "--p6-exception", (char*)0 };
|
|
pid_t child = (pid_t)-1;
|
|
if (posix_spawn(&child, self, (const posix_spawn_file_actions_t*)0,
|
|
(const posix_spawnattr_t*)0, argv, (char* const*)0) != 0 || child <= 0)
|
|
fail("exception child spawn");
|
|
wait_for_child(child, 62);
|
|
}
|
|
|
|
// Verifies alarm-backed waits and EINTR behavior for a blocked pipe read.
|
|
static void test_signal_waits(void) {
|
|
struct sigaction action;
|
|
action.sa_handler = alarm_handler;
|
|
action.sa_mask = 0;
|
|
action.sa_flags = 0;
|
|
if (sigaction(SIGALRM, &action, (struct sigaction*)0) != 0)
|
|
fail("install SIGALRM handler");
|
|
|
|
if (alarm(3) != 0)
|
|
fail("initial alarm remaining");
|
|
unsigned int remaining = alarm(0);
|
|
if (remaining < 2u || remaining > 3u)
|
|
fail("alarm replacement remaining");
|
|
|
|
sigset_t alarmSet = 0;
|
|
sigset_t previous = 0;
|
|
if (sigemptyset(&alarmSet) != 0 || sigaddset(&alarmSet, SIGALRM) != 0 ||
|
|
sigprocmask(SIG_BLOCK, &alarmSet, &previous) != 0)
|
|
fail("block SIGALRM");
|
|
sigset_t temporary = previous;
|
|
if (sigdelset(&temporary, SIGALRM) != 0)
|
|
fail("sigsuspend mask");
|
|
sig_atomic_t before = gAlarmCount;
|
|
(void)alarm(1);
|
|
errno = 0;
|
|
if (sigsuspend(&temporary) != -1 || errno != EINTR || gAlarmCount != before + 1)
|
|
fail("sigsuspend signal wake");
|
|
sigset_t current = 0;
|
|
if (sigprocmask(SIG_SETMASK, (const sigset_t*)0, ¤t) != 0 ||
|
|
sigismember(¤t, SIGALRM) != 1)
|
|
fail("sigsuspend mask restoration");
|
|
if (sigprocmask(SIG_SETMASK, &previous, (sigset_t*)0) != 0)
|
|
fail("restore SIGALRM mask");
|
|
|
|
before = gAlarmCount;
|
|
(void)alarm(1);
|
|
errno = 0;
|
|
if (pause() != -1 || errno != EINTR || gAlarmCount != before + 1)
|
|
fail("pause signal wake");
|
|
|
|
before = gAlarmCount;
|
|
(void)alarm(1);
|
|
remaining = sleep(3);
|
|
if (gAlarmCount != before + 1 || remaining < 1u || remaining > 2u)
|
|
fail("sleep interruption");
|
|
|
|
int descriptors[2];
|
|
if (pipe(descriptors) != 0)
|
|
fail("EINTR pipe create");
|
|
before = gAlarmCount;
|
|
(void)alarm(1);
|
|
char byte = 0;
|
|
errno = 0;
|
|
if (read(descriptors[0], &byte, 1) != -1 || errno != EINTR || gAlarmCount != before + 1)
|
|
fail("blocked pipe read EINTR");
|
|
(void)alarm(0);
|
|
if (close(descriptors[0]) != 0 || close(descriptors[1]) != 0)
|
|
fail("EINTR pipe cleanup");
|
|
|
|
action.sa_handler = pipe_handler;
|
|
action.sa_mask = 0;
|
|
action.sa_flags = 0;
|
|
if (sigaction(SIGPIPE, &action, (struct sigaction*)0) != 0)
|
|
fail("install SIGPIPE handler");
|
|
if (pipe(descriptors) != 0 || close(descriptors[0]) != 0)
|
|
fail("SIGPIPE pipe setup");
|
|
sig_atomic_t pipeBefore = gPipeCount;
|
|
errno = 0;
|
|
if (write(descriptors[1], "P", 1) != -1 || errno != EPIPE ||
|
|
gPipeCount != pipeBefore + 1)
|
|
fail("SIGPIPE broken-pipe delivery");
|
|
if (close(descriptors[1]) != 0)
|
|
fail("SIGPIPE pipe cleanup");
|
|
}
|
|
|
|
// Exercises the complete P6 signal contract used by later exec/vfork stages.
|
|
int main(void) {
|
|
if (child_mode("--p6-spawn-attrs")) run_spawn_attribute_child();
|
|
if (child_mode("--p6-group-child")) run_group_child();
|
|
if (child_mode("--p6-default")) run_default_child();
|
|
if (child_mode("--p6-exception")) run_exception_child();
|
|
|
|
print("posix_signal_smoke: ");
|
|
|
|
sigset_t set;
|
|
if (sigemptyset(&set) != 0 || set != 0) fail("sigemptyset");
|
|
if (sigaddset(&set, SIGUSR1) != 0 || sigismember(&set, SIGUSR1) != 1)
|
|
fail("sigaddset/sigismember");
|
|
if (sigdelset(&set, SIGUSR1) != 0 || sigismember(&set, SIGUSR1) != 0)
|
|
fail("sigdelset");
|
|
if (sigfillset(&set) != 0 || sigismember(&set, SIGKILL) != 1)
|
|
fail("sigfillset");
|
|
errno = 0;
|
|
if (sigaddset(&set, 0) != -1 || errno != EINVAL) fail("invalid signal set member");
|
|
|
|
struct sigaction action;
|
|
action.sa_handler = signal_handler;
|
|
action.sa_mask = 0;
|
|
action.sa_flags = 0;
|
|
if (sigaction(SIGUSR1, &action, (struct sigaction*)0) != 0)
|
|
fail("install handler");
|
|
|
|
if (raise(SIGUSR1) != 0 || gHandlerCount != 1 || gHandlerTid != SysGetCurrentTid())
|
|
fail("raise delivery");
|
|
|
|
sigemptyset(&set);
|
|
sigaddset(&set, SIGUSR1);
|
|
if (sigprocmask(SIG_BLOCK, &set, (sigset_t*)0) != 0) fail("block signal");
|
|
if (raise(SIGUSR1) != 0 || gHandlerCount != 1) fail("blocked raise delivered");
|
|
sigset_t pending = 0;
|
|
if (sigpending(&pending) != 0 || sigismember(&pending, SIGUSR1) != 1)
|
|
fail("thread pending signal");
|
|
if (sigprocmask(SIG_UNBLOCK, &set, (sigset_t*)0) != 0 || gHandlerCount != 2)
|
|
fail("pending delivery after unblock");
|
|
if (sigpending(&pending) != 0 || sigismember(&pending, SIGUSR1) != 0)
|
|
fail("pending signal not cleared");
|
|
|
|
struct sigaction nested;
|
|
nested.sa_handler = nested_handler;
|
|
nested.sa_mask = 0;
|
|
nested.sa_flags = 0;
|
|
if (sigaction(SIGUSR1, &nested, (struct sigaction*)0) != 0 ||
|
|
raise(SIGUSR1) != 0 || gNestedCount != 2)
|
|
fail("nested pending delivery");
|
|
if (sigaction(SIGUSR1, &action, (struct sigaction*)0) != 0)
|
|
fail("restore handler after nested delivery");
|
|
|
|
struct sigaction ignore;
|
|
ignore.sa_handler = SIG_IGN;
|
|
ignore.sa_mask = 0;
|
|
ignore.sa_flags = 0;
|
|
if (sigaction(SIGUSR1, &ignore, (struct sigaction*)0) != 0) fail("ignore disposition");
|
|
if (kill(getpid(), SIGUSR1) != 0) fail("ignored kill");
|
|
for (uint32_t i = 0; i < 32u; ++i) (void)SysYield();
|
|
if (gHandlerCount != 2) fail("ignored signal reached handler");
|
|
if (sigaction(SIGUSR1, &action, (struct sigaction*)0) != 0) fail("restore handler");
|
|
|
|
if (sigprocmask(SIG_BLOCK, &set, (sigset_t*)0) != 0) fail("block main for routing");
|
|
pthread_t worker;
|
|
if (pthread_create(&worker, (const pthread_attr_t*)0, signal_worker, (void*)0) != 0)
|
|
fail("pthread_create");
|
|
while (!gWorkerReady) (void)SysYield();
|
|
if (!gWorkerInheritedBlock) fail("pthread signal-mask inheritance");
|
|
|
|
if (kill(getpid(), SIGUSR1) != 0) fail("process-directed kill");
|
|
for (uint32_t i = 0; i < 10000u && gHandlerCount < 3; ++i) (void)SysYield();
|
|
if (gHandlerCount != 3 || gHandlerTid != worker.Tid)
|
|
fail("process-directed target selection");
|
|
gWorkerStop = 1;
|
|
void* workerResult = (void*)0;
|
|
if (pthread_join(worker, &workerResult) != 0 || workerResult != (void*)0)
|
|
fail("pthread_join");
|
|
if (sigprocmask(SIG_UNBLOCK, &set, (sigset_t*)0) != 0) fail("unblock main");
|
|
|
|
if (kill(0, 0) != 0) fail("current process-group existence");
|
|
pid_t group = getpgrp();
|
|
if (group <= 0 || kill(-group, 0) != 0) fail("explicit process-group existence");
|
|
errno = 0;
|
|
if (kill((pid_t)0x7fffffff, 0) != -1 || errno != ESRCH)
|
|
fail("missing process ESRCH");
|
|
|
|
struct sigaction killAction;
|
|
killAction.sa_handler = signal_handler;
|
|
killAction.sa_mask = 0;
|
|
killAction.sa_flags = 0;
|
|
errno = 0;
|
|
if (sigaction(SIGKILL, &killAction, (struct sigaction*)0) != -1 || errno != EINVAL)
|
|
fail("SIGKILL catch accepted");
|
|
sigset_t killSet;
|
|
sigemptyset(&killSet);
|
|
sigaddset(&killSet, SIGKILL);
|
|
if (sigprocmask(SIG_BLOCK, &killSet, &pending) != 0) fail("SIGKILL block request");
|
|
if (sigprocmask(SIG_SETMASK, &pending, (sigset_t*)0) != 0) fail("restore signal mask");
|
|
|
|
char self[512];
|
|
get_self_path(self, sizeof(self));
|
|
test_spawn_signal_attributes(self);
|
|
test_process_group_delivery(self);
|
|
test_default_disposition(self);
|
|
test_exception_translation(self);
|
|
test_signal_waits();
|
|
|
|
print("PASS\n");
|
|
return 0;
|
|
}
|