diff --git a/kernel/arch/aarch64/signal/delivery.cpp b/kernel/arch/aarch64/signal/delivery.cpp index b1959a06..56eb1e21 100644 --- a/kernel/arch/aarch64/signal/delivery.cpp +++ b/kernel/arch/aarch64/signal/delivery.cpp @@ -1,4 +1,5 @@ #include "signal/delivery.h" +#include "arch/arch_signal.h" #include "sched/fpu.h" #include "sched/sched.h" #include "sched/task.h" @@ -137,3 +138,30 @@ __PRIVILEGED_CODE int64_t restore_signal_frame(trap_frame* tf) { } } // namespace aarch64 + +__PRIVILEGED_CODE int64_t arch::deliver_pending_signal(sched::task* self, + int64_t result) { + uint32_t sig = 0; + signals::k_sigaction act{}; + signals::sig_set_t old_blocked = 0; + if (!signals::take_deliverable(self, &sig, &act, &old_blocked)) { + return result; + } + + // Same restorer contract as x86_64, the bundled musl always passes one + if (!(act.flags & signals::SA_RESTORER) || !act.restorer) { + signals::die_from_signal(signals::SIGSEGV); + } + + if (aarch64::build_signal_frame(aarch64::current_trap_frame(), sig, &act, + old_blocked, result) != 0) { + signals::die_from_signal(signals::SIGSEGV); + } + + // The dispatcher writes this into x0, matching the frame's sig argument + return static_cast(sig); +} + +__PRIVILEGED_CODE int64_t arch::restore_signal_context() { + return aarch64::restore_signal_frame(aarch64::current_trap_frame()); +} diff --git a/kernel/arch/aarch64/syscall/linux_syscalls.h b/kernel/arch/aarch64/syscall/linux_syscalls.h index d69a139d..317cc9b1 100644 --- a/kernel/arch/aarch64/syscall/linux_syscalls.h +++ b/kernel/arch/aarch64/syscall/linux_syscalls.h @@ -43,6 +43,7 @@ constexpr uint64_t TGKILL = 131; constexpr uint64_t RT_SIGACTION = 134; constexpr uint64_t RT_SIGPROCMASK = 135; constexpr uint64_t RT_SIGPENDING = 136; +constexpr uint64_t RT_SIGRETURN = 139; constexpr uint64_t SETPGID = 154; constexpr uint64_t GETPGID = 155; constexpr uint64_t UNAME = 160; diff --git a/kernel/arch/aarch64/trap/trap_frame.h b/kernel/arch/aarch64/trap/trap_frame.h index 62ddf757..5eabe3ff 100644 --- a/kernel/arch/aarch64/trap/trap_frame.h +++ b/kernel/arch/aarch64/trap/trap_frame.h @@ -2,6 +2,7 @@ #define STELLUX_ARCH_AARCH64_TRAP_TRAP_FRAME_H #include "types.h" +#include "sched/task_exec_core.h" namespace aarch64 { @@ -46,6 +47,18 @@ inline uint64_t get_far(const trap_frame* tf) { return tf->far; } +/** + * @brief The current task's saved trap frame. + * Valid only while handling an EL0/EL1t-origin trap, when the frame sits + * immediately below the task's exception stack top (the scheduler pins + * SP_EL1 to system_stack_top on every return to a task-mode context). + * @note Privilege: **required** + */ +__PRIVILEGED_CODE inline trap_frame* current_trap_frame() { + return reinterpret_cast( + this_cpu(current_task_exec)->system_stack_top - sizeof(trap_frame)); +} + } // namespace aarch64 #endif // STELLUX_ARCH_AARCH64_TRAP_TRAP_FRAME_H diff --git a/kernel/arch/arch_signal.h b/kernel/arch/arch_signal.h new file mode 100644 index 00000000..bed24885 --- /dev/null +++ b/kernel/arch/arch_signal.h @@ -0,0 +1,32 @@ +#ifndef STELLUX_ARCH_ARCH_SIGNAL_H +#define STELLUX_ARCH_ARCH_SIGNAL_H + +#include "common/types.h" + +namespace sched { +struct task; +} + +namespace arch { + +/** + * @brief Deliver one pending handler-bound signal at the syscall-return + * boundary by redirecting the saved user context to the handler. + * An action without a restorer or an unwritable user stack kills the task + * with SIGSEGV. Returns the value for the user's result register, so a + * built frame keeps the handler's first argument intact. + * @note Privilege: **required** + */ +__PRIVILEGED_CODE int64_t deliver_pending_signal(sched::task* self, + int64_t result); + +/** + * @brief rt_sigreturn core: restore the interrupted context from the user + * signal frame and return the value to resume in the result register. + * @note Privilege: **required** + */ +__PRIVILEGED_CODE int64_t restore_signal_context(); + +} // namespace arch + +#endif // STELLUX_ARCH_ARCH_SIGNAL_H diff --git a/kernel/arch/x86_64/signal/delivery.cpp b/kernel/arch/x86_64/signal/delivery.cpp index a7b77c2e..4a056542 100644 --- a/kernel/arch/x86_64/signal/delivery.cpp +++ b/kernel/arch/x86_64/signal/delivery.cpp @@ -1,4 +1,5 @@ #include "signal/delivery.h" +#include "arch/arch_signal.h" #include "defs/segments.h" #include "sched/fpu.h" #include "sched/sched.h" @@ -180,3 +181,29 @@ __PRIVILEGED_CODE int64_t restore_signal_frame(syscall_frame* ctx) { } } // namespace x86 + +__PRIVILEGED_CODE int64_t arch::deliver_pending_signal(sched::task* self, + int64_t result) { + uint32_t sig = 0; + signals::k_sigaction act{}; + signals::sig_set_t old_blocked = 0; + if (!signals::take_deliverable(self, &sig, &act, &old_blocked)) { + return result; + } + + // The handler returns through the restorer's rt_sigreturn, + // an action installed without one is undeliverable. + if (!(act.flags & signals::SA_RESTORER) || !act.restorer) { + signals::die_from_signal(signals::SIGSEGV); + } + + if (x86::build_signal_frame(x86::current_syscall_frame(), sig, &act, + old_blocked, result) != 0) { + signals::die_from_signal(signals::SIGSEGV); + } + return result; +} + +__PRIVILEGED_CODE int64_t arch::restore_signal_context() { + return x86::restore_signal_frame(x86::current_syscall_frame()); +} diff --git a/kernel/arch/x86_64/syscall/linux_syscalls.h b/kernel/arch/x86_64/syscall/linux_syscalls.h index 724f55db..aafb5a02 100644 --- a/kernel/arch/x86_64/syscall/linux_syscalls.h +++ b/kernel/arch/x86_64/syscall/linux_syscalls.h @@ -20,6 +20,7 @@ constexpr uint64_t MUNMAP = 11; constexpr uint64_t BRK = 12; constexpr uint64_t RT_SIGACTION = 13; constexpr uint64_t RT_SIGPROCMASK = 14; +constexpr uint64_t RT_SIGRETURN = 15; constexpr uint64_t IOCTL = 16; constexpr uint64_t WRITEV = 20; constexpr uint64_t ACCESS = 21; diff --git a/kernel/signals/signal.cpp b/kernel/signals/signal.cpp index 91804412..ea7933f7 100644 --- a/kernel/signals/signal.cpp +++ b/kernel/signals/signal.cpp @@ -10,7 +10,7 @@ namespace signals { enum class send_verdict : uint8_t { FATAL, // default-terminate, wake the target so it can die IGNORABLE, // droppable unless the target blocks it - HANDLED, // a user handler is installed, leave it pending + HANDLED, // a user handler is installed, wake the target to deliver }; /** @@ -175,7 +175,7 @@ __PRIVILEGED_CODE int32_t send_to_task(sched::task* t, uint32_t sig) { } __atomic_fetch_or(&t->sig.pending, sig_bit(sig), __ATOMIC_ACQ_REL); - if (verdict == send_verdict::FATAL && !is_blocked) { + if (verdict != send_verdict::IGNORABLE && !is_blocked) { wake_for_signal(t); } return OK; @@ -227,7 +227,7 @@ __PRIVILEGED_CODE int32_t send_to_group(sched::thread_group* tg, uint32_t sig) { __atomic_fetch_or(&tg->sig.shared_pending, bit, __ATOMIC_ACQ_REL); - if (verdict == send_verdict::FATAL) { + if (verdict != send_verdict::IGNORABLE) { // Wake one thread with the signal unblocked, leader preferred sched::task* target = nullptr; if (tg->leader && @@ -291,7 +291,7 @@ __PRIVILEGED_CODE uint32_t fatal_pending(sched::task* t) { } __PRIVILEGED_CODE bool interrupt_pending(sched::task* t) { - return t && fatal_pending(t) != 0; + return t && (fatal_pending(t) != 0 || next_deliverable(t) != 0); } __PRIVILEGED_CODE uint32_t next_deliverable(sched::task* t) { @@ -325,6 +325,76 @@ __PRIVILEGED_CODE uint32_t next_deliverable(sched::task* t) { return result; } +__PRIVILEGED_CODE bool take_deliverable(sched::task* t, uint32_t* sig, + k_sigaction* act, + sig_set_t* old_blocked) { + if (!t || !t->group) { + return false; + } + + // Lock-free fast path, the boundary check must stay cheap + sig_set_t blocked = __atomic_load_n(&t->sig.blocked, __ATOMIC_ACQUIRE); + sig_set_t deliverable = (__atomic_load_n(&t->sig.pending, __ATOMIC_ACQUIRE) + | __atomic_load_n(&t->group->sig.shared_pending, __ATOMIC_ACQUIRE)) + & ~blocked; + if (!deliverable) { + return false; + } + + sync::irq_state irq = sync::spin_lock_irqsave(t->group->sig.lock); + + // Re-read under the lock: bits are only cleared by sig.lock holders, + // so the selection cannot race with a discard or another consumer + deliverable = (__atomic_load_n(&t->sig.pending, __ATOMIC_ACQUIRE) + | __atomic_load_n(&t->group->sig.shared_pending, __ATOMIC_ACQUIRE)) + & ~blocked; + + uint32_t selected = 0; + while (deliverable) { + uint32_t s = static_cast(__builtin_ctzll(deliverable)) + 1; + deliverable &= deliverable - 1; + + uintptr_t handler = t->group->sig.actions[s - 1].handler; + if (handler != SIG_DFL && handler != SIG_IGN) { + selected = s; + break; + } + } + + if (!selected) { + sync::spin_unlock_irqrestore(t->group->sig.lock, irq); + return false; + } + + // Consume one pending instance, thread set before the shared set + const sig_set_t keep = ~sig_bit(selected); + if (!(__atomic_fetch_and(&t->sig.pending, keep, __ATOMIC_ACQ_REL) + & sig_bit(selected))) { + __atomic_fetch_and(&t->group->sig.shared_pending, keep, __ATOMIC_ACQ_REL); + } + + *act = t->group->sig.actions[selected - 1]; + + // POSIX: SA_RESETHAND restores the default disposition on delivery + if (act->flags & SA_RESETHAND) { + t->group->sig.actions[selected - 1].handler = SIG_DFL; + } + + sync::spin_unlock_irqrestore(t->group->sig.lock, irq); + + // The handler runs with its sa_mask plus its own signal blocked, + // the frame carries old_blocked for rt_sigreturn to restore + sig_set_t next = blocked | act->mask; + if (!(act->flags & SA_NODEFER)) { + next |= sig_bit(selected); + } + set_blocked(t, SIG_SETMASK, &next, nullptr); + + *sig = selected; + *old_blocked = blocked; + return true; +} + __PRIVILEGED_CODE void die_from_signal(uint32_t sig) { sched::task* self = sched::current(); sched::thread_group* tg = self->group; diff --git a/kernel/signals/signal.h b/kernel/signals/signal.h index 2cfd6ebc..4fef467e 100644 --- a/kernel/signals/signal.h +++ b/kernel/signals/signal.h @@ -52,8 +52,7 @@ __PRIVILEGED_CODE sig_set_t pending_blocked_set(sched::task* t); * @brief Send a thread-directed signal (tkill semantics). * SIGKILL terminates the whole process via the kill machinery. Signals * resolving to ignore are dropped unless the target blocks them. Fatal - * signals wake a blocked target so it can act promptly. Signals with a - * handler installed are left pending for delivery. + * and handler-bound signals wake a blocked target so it can act promptly. * @return OK, ERR_INVAL for a bad signal, ERR_PERM for kernel/idle tasks. * @note Privilege: **required** */ @@ -80,7 +79,7 @@ __PRIVILEGED_CODE uint32_t fatal_pending(sched::task* t); /** * @brief True if a blocking wait must unwind and return EINTR. - * Covers kills and fatal signals (handler delivery extends this later). + * Covers kills, fatal signals, and handler-bound deliveries. * @note Privilege: **required** */ __PRIVILEGED_CODE bool interrupt_pending(sched::task* t); @@ -94,6 +93,19 @@ __PRIVILEGED_CODE bool interrupt_pending(sched::task* t); */ __PRIVILEGED_CODE uint32_t next_deliverable(sched::task* t); +/** + * @brief Consume the lowest-numbered signal ready for handler delivery. + * Clears one pending instance (thread set before shared), snapshots the + * action, applies SA_RESETHAND, and blocks the handler's sa_mask plus the + * signal itself (skipped under SA_NODEFER). old_blocked receives the + * pre-delivery mask the signal frame must carry for rt_sigreturn. + * @return true when a signal was taken and the outputs are valid. + * @note Privilege: **required** + */ +__PRIVILEGED_CODE bool take_deliverable(sched::task* t, uint32_t* sig, + k_sigaction* act, + sig_set_t* old_blocked); + /** * @brief Terminate the current task because of signal sig. * Fatal signals kill the whole process: a non-leader records sig as the diff --git a/kernel/syscall/handlers/sys_signal.cpp b/kernel/syscall/handlers/sys_signal.cpp index c4df0a7d..de8e632a 100644 --- a/kernel/syscall/handlers/sys_signal.cpp +++ b/kernel/syscall/handlers/sys_signal.cpp @@ -1,4 +1,5 @@ #include "syscall/handlers/sys_signal.h" +#include "arch/arch_signal.h" #include "signals/signal.h" #include "sched/sched.h" #include "sched/task.h" @@ -173,6 +174,12 @@ DEFINE_SYSCALL2(rt_sigpending, u_set, sigsetsize) { return 0; } +DEFINE_SYSCALL0(rt_sigreturn) { + // Returns the restored context's saved result value, so the normal + // result write completes the restore instead of clobbering it + return arch::restore_signal_context(); +} + DEFINE_SYSCALL2(kill, u_pid, u_sig) { if (u_sig > signals::NSIG) { return syscall::EINVAL; diff --git a/kernel/syscall/handlers/sys_signal.h b/kernel/syscall/handlers/sys_signal.h index e33087bc..ef2b218d 100644 --- a/kernel/syscall/handlers/sys_signal.h +++ b/kernel/syscall/handlers/sys_signal.h @@ -6,6 +6,7 @@ DECLARE_SYSCALL(rt_sigaction); DECLARE_SYSCALL(rt_sigprocmask); DECLARE_SYSCALL(rt_sigpending); +DECLARE_SYSCALL(rt_sigreturn); DECLARE_SYSCALL(kill); DECLARE_SYSCALL(tkill); DECLARE_SYSCALL(tgkill); diff --git a/kernel/syscall/syscall.cpp b/kernel/syscall/syscall.cpp index dfbbf276..62d039bd 100644 --- a/kernel/syscall/syscall.cpp +++ b/kernel/syscall/syscall.cpp @@ -1,5 +1,6 @@ #include "syscall/syscall.h" #include "syscall/syscall_table.h" +#include "arch/arch_signal.h" #include "sched/task_exec_core.h" #include "sched/sched.h" #include "sched/task.h" @@ -54,6 +55,12 @@ extern "C" __PRIVILEGED_CODE int64_t stlx_syscall_handler( if (fsig) { signals::die_from_signal(fsig); } + + // Handler delivery only when returning to user mode, an elevated + // task keeps its signals pending until it lowers + if (!(self->exec.flags & sched::TASK_FLAG_ELEVATED)) { + result = arch::deliver_pending_signal(self, result); + } } // Return-boundary restore: dynamic runtime elevation follows the selected diff --git a/kernel/syscall/syscall_table.cpp b/kernel/syscall/syscall_table.cpp index 0a92ab7f..50eec10b 100644 --- a/kernel/syscall/syscall_table.cpp +++ b/kernel/syscall/syscall_table.cpp @@ -58,6 +58,7 @@ __PRIVILEGED_CODE void init_syscall_table() { REGISTER_SYSCALL(linux_nr::RT_SIGACTION, rt_sigaction); REGISTER_SYSCALL(linux_nr::RT_SIGPROCMASK, rt_sigprocmask); REGISTER_SYSCALL(linux_nr::RT_SIGPENDING, rt_sigpending); + REGISTER_SYSCALL(linux_nr::RT_SIGRETURN, rt_sigreturn); REGISTER_SYSCALL(linux_nr::KILL, kill); REGISTER_SYSCALL(linux_nr::TKILL, tkill); REGISTER_SYSCALL(linux_nr::TGKILL, tgkill); diff --git a/kernel/tests/signals/signal_interrupt.test.cpp b/kernel/tests/signals/signal_interrupt.test.cpp index c439c857..1f6c0af9 100644 --- a/kernel/tests/signals/signal_interrupt.test.cpp +++ b/kernel/tests/signals/signal_interrupt.test.cpp @@ -121,3 +121,21 @@ TEST(signal_interrupt, interrupt_pending_truth) { RUN_ELEVATED({ intr = signals::interrupt_pending(nullptr); }); EXPECT_FALSE(intr); } + +TEST(signal_interrupt, interrupt_pending_covers_handled_signals) { + signals::k_sigaction act = {}; + act.handler = 0x400000; + RUN_ELEVATED({ + signals::set_action(g_tg, signals::SIGUSR1, &act, nullptr); + }); + + bool intr = false; + g_leader->sig.pending = signals::sig_bit(signals::SIGUSR1); + RUN_ELEVATED({ intr = signals::interrupt_pending(g_leader); }); + EXPECT_TRUE(intr); + + // Blocking the signal removes the interrupt reason + g_leader->sig.blocked = signals::sig_bit(signals::SIGUSR1); + RUN_ELEVATED({ intr = signals::interrupt_pending(g_leader); }); + EXPECT_FALSE(intr); +} diff --git a/kernel/tests/signals/signal_send.test.cpp b/kernel/tests/signals/signal_send.test.cpp index 1751466e..9678ceb8 100644 --- a/kernel/tests/signals/signal_send.test.cpp +++ b/kernel/tests/signals/signal_send.test.cpp @@ -97,7 +97,7 @@ TEST(signal_send, ignored_blocked_send_pends) { EXPECT_EQ(g_thread->sig.pending, signals::sig_bit(signals::SIGCHLD)); } -TEST(signal_send, handled_send_pends_only) { +TEST(signal_send, handled_send_pends) { signals::k_sigaction act = {}; act.handler = 0x400000; int32_t rc = 0; diff --git a/kernel/tests/signals/signal_state.test.cpp b/kernel/tests/signals/signal_state.test.cpp index 8acf89d9..03a7895c 100644 --- a/kernel/tests/signals/signal_state.test.cpp +++ b/kernel/tests/signals/signal_state.test.cpp @@ -264,3 +264,150 @@ TEST(signal_state, pending_blocked_set_combines_sets) { // Only pending signals that are blocked are reported (rt_sigpending) EXPECT_EQ(result, sigint | sigusr1); } + +// Installs a handler for sig with the given flags and sa_mask +static void install_handler(uint32_t sig, uint64_t flags, + signals::sig_set_t mask) { + signals::k_sigaction act = {}; + act.handler = 0x400000; + act.flags = flags; + act.restorer = 0x400100; + act.mask = mask; + RUN_ELEVATED({ signals::set_action(g_tg, sig, &act, nullptr); }); +} + +TEST(signal_state, take_deliverable_consumes_and_masks) { + const signals::sig_set_t handler_mask = signals::sig_bit(signals::SIGUSR2); + install_handler(signals::SIGUSR1, 0, handler_mask); + g_leader->sig.pending = signals::sig_bit(signals::SIGUSR1); + g_leader->sig.blocked = signals::sig_bit(signals::SIGTERM); + + uint32_t sig = 0; + signals::k_sigaction act = {}; + signals::sig_set_t old_blocked = 0; + bool taken = false; + RUN_ELEVATED({ + taken = signals::take_deliverable(g_leader, &sig, &act, &old_blocked); + }); + + EXPECT_TRUE(taken); + EXPECT_EQ(sig, signals::SIGUSR1); + EXPECT_EQ(act.handler, 0x400000UL); + EXPECT_EQ(g_leader->sig.pending, 0ULL); + EXPECT_EQ(old_blocked, signals::sig_bit(signals::SIGTERM)); + + // The handler runs with sa_mask plus its own signal on top of old_blocked + EXPECT_EQ(g_leader->sig.blocked, signals::sig_bit(signals::SIGTERM) + | handler_mask + | signals::sig_bit(signals::SIGUSR1)); +} + +TEST(signal_state, take_deliverable_prefers_thread_set) { + install_handler(signals::SIGUSR1, 0, 0); + g_leader->sig.pending = signals::sig_bit(signals::SIGUSR1); + g_tg->sig.shared_pending = signals::sig_bit(signals::SIGUSR1); + + uint32_t sig = 0; + signals::k_sigaction act = {}; + signals::sig_set_t old_blocked = 0; + bool taken = false; + RUN_ELEVATED({ + taken = signals::take_deliverable(g_leader, &sig, &act, &old_blocked); + }); + + EXPECT_TRUE(taken); + EXPECT_EQ(g_leader->sig.pending, 0ULL); + EXPECT_EQ(g_tg->sig.shared_pending, signals::sig_bit(signals::SIGUSR1)); +} + +TEST(signal_state, take_deliverable_lowest_handled_first) { + install_handler(signals::SIGUSR1, 0, 0); + install_handler(signals::SIGUSR2, 0, 0); + g_leader->sig.pending = signals::sig_bit(signals::SIGUSR1) + | signals::sig_bit(signals::SIGUSR2); + g_leader->sig.blocked = 0; + + uint32_t sig = 0; + signals::k_sigaction act = {}; + signals::sig_set_t old_blocked = 0; + bool taken = false; + RUN_ELEVATED({ + taken = signals::take_deliverable(g_leader, &sig, &act, &old_blocked); + }); + EXPECT_TRUE(taken); + EXPECT_EQ(sig, signals::SIGUSR1); + + // Reset the mask the first take installed, then the next one follows + g_leader->sig.blocked = 0; + RUN_ELEVATED({ + taken = signals::take_deliverable(g_leader, &sig, &act, &old_blocked); + }); + EXPECT_TRUE(taken); + EXPECT_EQ(sig, signals::SIGUSR2); + EXPECT_EQ(g_leader->sig.pending, 0ULL); +} + +TEST(signal_state, take_deliverable_nodefer_leaves_signal_unblocked) { + install_handler(signals::SIGUSR1, signals::SA_NODEFER, 0); + g_leader->sig.pending = signals::sig_bit(signals::SIGUSR1); + + uint32_t sig = 0; + signals::k_sigaction act = {}; + signals::sig_set_t old_blocked = 0; + bool taken = false; + RUN_ELEVATED({ + taken = signals::take_deliverable(g_leader, &sig, &act, &old_blocked); + }); + + EXPECT_TRUE(taken); + EXPECT_EQ(g_leader->sig.blocked, 0ULL); +} + +TEST(signal_state, take_deliverable_resethand_restores_default) { + install_handler(signals::SIGUSR1, signals::SA_RESETHAND, 0); + g_leader->sig.pending = signals::sig_bit(signals::SIGUSR1); + + uint32_t sig = 0; + signals::k_sigaction act = {}; + signals::sig_set_t old_blocked = 0; + bool taken = false; + RUN_ELEVATED({ + taken = signals::take_deliverable(g_leader, &sig, &act, &old_blocked); + }); + + // The snapshot keeps the handler while the stored action resets + EXPECT_TRUE(taken); + EXPECT_EQ(act.handler, 0x400000UL); + EXPECT_EQ(g_tg->sig.actions[signals::SIGUSR1 - 1].handler, signals::SIG_DFL); +} + +TEST(signal_state, take_deliverable_skips_blocked_and_unhandled) { + uint32_t sig = 0; + signals::k_sigaction act = {}; + signals::sig_set_t old_blocked = 0; + bool taken = true; + + // Nothing pending + RUN_ELEVATED({ + taken = signals::take_deliverable(g_leader, &sig, &act, &old_blocked); + }); + EXPECT_FALSE(taken); + + // Pending without a handler stays for the fatal path + g_leader->sig.pending = signals::sig_bit(signals::SIGTERM); + RUN_ELEVATED({ + taken = signals::take_deliverable(g_leader, &sig, &act, &old_blocked); + }); + EXPECT_FALSE(taken); + EXPECT_EQ(g_leader->sig.pending, signals::sig_bit(signals::SIGTERM)); + + // A blocked handled signal is not deliverable + install_handler(signals::SIGUSR1, 0, 0); + g_leader->sig.pending = signals::sig_bit(signals::SIGUSR1); + g_leader->sig.blocked = signals::sig_bit(signals::SIGUSR1); + RUN_ELEVATED({ + taken = signals::take_deliverable(g_leader, &sig, &act, &old_blocked); + }); + EXPECT_FALSE(taken); + EXPECT_EQ(g_leader->sig.pending, signals::sig_bit(signals::SIGUSR1)); +} diff --git a/userland/apps/Makefile b/userland/apps/Makefile index 12dde550..6f33fb8e 100644 --- a/userland/apps/Makefile +++ b/userland/apps/Makefile @@ -4,7 +4,7 @@ APP_DIRS := init hello shell ls cat rm stat touch sleep true false clear ptytest date \ clockbench stlxdm stlxterm doom ping ifconfig nslookup arp udpecho tcpecho \ - fetch polltest dropbear blackjack wordle hangman snake tetris \ + fetch polltest sigtest dropbear blackjack wordle hangman snake tetris \ grep wc head threadtest uname kill cxxtest synctest python vim APP_COUNT := $(words $(APP_DIRS)) diff --git a/userland/apps/sigtest/Makefile b/userland/apps/sigtest/Makefile new file mode 100644 index 00000000..33e2741d --- /dev/null +++ b/userland/apps/sigtest/Makefile @@ -0,0 +1,2 @@ +APP_NAME := sigtest +include ../../mk/app.mk diff --git a/userland/apps/sigtest/src/sigtest.c b/userland/apps/sigtest/src/sigtest.c new file mode 100644 index 00000000..f619c47f --- /dev/null +++ b/userland/apps/sigtest/src/sigtest.c @@ -0,0 +1,202 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include + +#define HELPER_STACK_SIZE (64 * 1024) + +static int passed = 0; +static int failed = 0; + +static void check(const char* name, int cond) { + if (cond) { + printf(" PASS: %s\n", name); + passed++; + } else { + printf(" FAIL: %s\n", name); + failed++; + } +} + +static volatile sig_atomic_t usr1_count = 0; + +static void usr1_handler(int sig) { + (void)sig; + usr1_count++; +} + +static void test_basic_delivery(void) { + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = usr1_handler; + + check("sigaction installs handler", sigaction(SIGUSR1, &sa, NULL) == 0); + + raise(SIGUSR1); + check("handler ran once", usr1_count == 1); + + raise(SIGUSR1); + check("disposition survives delivery", usr1_count == 2); +} + +static volatile sig_atomic_t info_signo = -1; +static volatile sig_atomic_t info_pid = -1; + +static void usr2_handler(int sig, siginfo_t* info, void* ctx) { + (void)sig; + (void)ctx; + info_signo = info->si_signo; + info_pid = (sig_atomic_t)info->si_pid; +} + +static void test_siginfo_delivery(void) { + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + sa.sa_sigaction = usr2_handler; + sa.sa_flags = SA_SIGINFO; + sigaction(SIGUSR2, &sa, NULL); + + raise(SIGUSR2); + check("SA_SIGINFO handler saw si_signo", info_signo == SIGUSR2); + check("synthesized si_pid is 0", info_pid == 0); +} + +static volatile sig_atomic_t defer_depth = 0; +static volatile sig_atomic_t defer_max_depth = 0; +static volatile sig_atomic_t defer_count = 0; + +static void defer_handler(int sig) { + (void)sig; + defer_depth++; + if (defer_depth > defer_max_depth) { + defer_max_depth = defer_depth; + } + defer_count++; + if (defer_count == 1) { + /* Own signal is blocked during the handler, so this must pend */ + raise(SIGUSR1); + } + defer_depth--; +} + +static void test_deferred_reentry(void) { + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = defer_handler; + sigaction(SIGUSR1, &sa, NULL); + + defer_count = 0; + raise(SIGUSR1); + check("re-raise delivered after return", defer_count == 2); + check("handler never nested", defer_max_depth == 1); +} + +static void test_unblock_delivers(void) { + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = usr1_handler; + sigaction(SIGUSR1, &sa, NULL); + + sigset_t set; + sigemptyset(&set); + sigaddset(&set, SIGUSR1); + sigprocmask(SIG_BLOCK, &set, NULL); + + usr1_count = 0; + raise(SIGUSR1); + check("blocked signal stays pending", usr1_count == 0); + + sigset_t pend; + sigpending(&pend); + check("sigpending reports it", sigismember(&pend, SIGUSR1) == 1); + + sigprocmask(SIG_UNBLOCK, &set, NULL); + check("unblock delivers immediately", usr1_count == 1); +} + +static volatile sig_atomic_t eintr_handler_ran = 0; + +static void eintr_handler(int sig) { + (void)sig; + eintr_handler_ran = 1; +} + +static void eintr_helper(void* arg) { + (void)arg; + + /* Keep the signal blocked here so the main thread must receive it */ + sigset_t set; + sigemptyset(&set); + sigaddset(&set, SIGUSR1); + sigprocmask(SIG_BLOCK, &set, NULL); + + usleep(200 * 1000); + kill(getpid(), SIGUSR1); + _exit(0); +} + +static void test_read_eintr(void) { + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + /* No SA_RESTART: the interrupted read must fail with EINTR */ + sa.sa_handler = eintr_handler; + sigaction(SIGUSR1, &sa, NULL); + + int fds[2]; + if (pipe(fds) != 0) { + printf(" SKIP: pipe unavailable\n"); + return; + } + + void* stk = mmap(NULL, HELPER_STACK_SIZE, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0); + if (stk == MAP_FAILED) { + printf(" SKIP: helper stack unavailable\n"); + close(fds[0]); + close(fds[1]); + return; + } + + int h = proc_create_thread(eintr_helper, NULL, + (char*)stk + HELPER_STACK_SIZE, "sig_helper"); + if (h < 0) { + printf(" SKIP: thread creation unavailable\n"); + munmap(stk, HELPER_STACK_SIZE); + close(fds[0]); + close(fds[1]); + return; + } + proc_thread_start(h); + + char byte; + ssize_t n = read(fds[0], &byte, 1); + int saved_errno = errno; + + proc_thread_join(h, NULL); + + check("read interrupted by handler", n == -1); + check("errno is EINTR", saved_errno == EINTR); + check("handler ran before read returned", eintr_handler_ran == 1); + + munmap(stk, HELPER_STACK_SIZE); + close(fds[0]); + close(fds[1]); +} + +int main(void) { + setvbuf(stdout, NULL, _IONBF, 0); + printf("sigtest: running signal delivery tests\n"); + + test_basic_delivery(); + test_siginfo_delivery(); + test_deferred_reentry(); + test_unblock_delivers(); + test_read_eintr(); + + printf("sigtest: %d passed, %d failed\n", passed, failed); + return failed > 0 ? 1 : 0; +}