// Runtime coverage for P6 POSIX signal delivery, waits, spawn state, and exceptions. #include #include #include #include #include #include #include #include #include #include #include #include #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; }