diff --git a/include/signal.h b/include/signal.h new file mode 100644 index 0000000..6d2289d --- /dev/null +++ b/include/signal.h @@ -0,0 +1,436 @@ +#ifndef VLIBC_SIGNAL_H +#define VLIBC_SIGNAL_H + +/* + * vlibc — . + * + * Signal handling (POSIX.1-2008 base) plus the XSI/obsolete conveniences. + * The core calls ride the kernel's rt_* signal ABI (SYS_rt_sigaction, + * SYS_rt_sigprocmask, SYS_rt_sigpending, SYS_rt_sigsuspend, + * SYS_rt_sigtimedwait, SYS_rt_sigqueueinfo) with the x86_64 kernel + * sigset size of one 64-bit word. + * + * Level 1 (onlyposix): signal numbers, SIG_DFL/SIG_IGN/SIG_ERR, the + * sigset_t manipulation and query calls, sigaction, + * sigprocmask, sigpending, sigsuspend, sigwait, + * sigwaitinfo, sigtimedwait, kill, killpg, raise, + * pause, alarm, abort, sigqueue, and the signal() + * convenience. + * Level 2 (muslmimic): sigaltstack (and stack_t / SS_* / SIGSTKSZ), + * ualarm, siginterrupt, psignal, psiginfo (XSI / + * obsolete — sigaction/sigprocmask/alarm are POSIX + * base and stay at level 1). + * + * The layout facts below are x86_64 kernel-ABI: + * + * - The kernel sigset_t is 8 bytes (one word); signal N occupies bit + * N-1, signals 1..64 are usable (32..64 are the realtime signals). + * The same single-word sigset_t is declared by and + * under the shared VLIBC_SIGSET_T_DEFINED guard so pselect/ + * ppoll and the sig* API agree on the type no matter the include order. + * - SIG_DFL/SIG_IGN/SIG_ERR are the magic handler values 0/1/-1 cast to + * the handler type; the kernel dispatches on the raw value. + * - The PUBLIC struct sigaction is the POSIX layout (handler, mask, + * int flags, restorer). The kernel's rt_sigaction ABI layout is + * DIFFERENT (handler, flags, restorer, mask — flags is a full word); + * src/signal/sigaction.c converts between the two. + * - SA_RESTORER (0x04000000) is the kernel-private bit that makes the + * kernel jump to the user-supplied sa_restorer trampoline when the + * handler returns; every real (non-DFL/IGN) handler install sets it + * (see sigaction.c). It is masked out of the flags reported back. + * + * siginfo_t is the minimal 128-byte layout shared with + * under the VLIBC_INTERNAL_SIGINFO_DEFINED guard (see the block below); + * either header may be included first. No SIGEV_* / SI_* constants are + * defined: the signal-handling details behind them are owned by a later + * todo and nothing here needs the names. + */ + +#include + +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * struct timespec is defined by ; a forward declaration is enough + * for sigtimedwait()'s pointer parameter, and the two headers may be + * included in either order. + */ +struct timespec; + +/* Signal numbers (x86_64 asm-generic values; kernel-ABI facts). */ +#define SIGHUP 1 /* hangup detected on controlling terminal */ +#define SIGINT 2 /* interactive attention (^C) */ +#define SIGQUIT 3 /* interactive quit (^\) */ +#define SIGILL 4 /* illegal instruction */ +#define SIGTRAP 5 /* trace/breakpoint trap */ +#define SIGABRT 6 /* abnormal termination (abort()) */ +#define SIGBUS 7 /* bus error */ +#define SIGFPE 8 /* floating-point exception */ +#define SIGKILL 9 /* kill (cannot be caught or ignored) */ +#define SIGUSR1 10 /* user-defined signal 1 */ +#define SIGSEGV 11 /* invalid memory reference */ +#define SIGUSR2 12 /* user-defined signal 2 */ +#define SIGPIPE 13 /* write on a pipe with no reader */ +#define SIGALRM 14 /* real-time timer expired (alarm/ualarm/setitimer) */ +#define SIGTERM 15 /* termination request */ +#define SIGSTKFLT 16 /* stack fault (unused) */ +#define SIGCHLD 17 /* child stopped or terminated */ +#define SIGCONT 18 /* continue if stopped */ +#define SIGSTOP 19 /* stop (cannot be caught or ignored) */ +#define SIGTSTP 20 /* interactive stop (^Z) */ +#define SIGTTIN 21 /* background process read from terminal */ +#define SIGTTOU 22 /* background process write to terminal */ +#define SIGURG 23 /* urgent condition on a socket */ +#define SIGXCPU 24 /* CPU time limit exceeded */ +#define SIGXFSZ 25 /* file size limit exceeded */ +#define SIGVTALRM 26 /* virtual timer expired */ +#define SIGPROF 27 /* profiling timer expired */ +#define SIGWINCH 28 /* window size change */ +#define SIGIO 29 /* I/O now possible */ +#define SIGPWR 30 /* power failure */ +#define SIGSYS 31 /* bad system call */ + +/* Realtime signals: the 32..64 range, usable for user-defined purposes. */ +#define SIGRTMIN 32 +#define SIGRTMAX 64 + +/* One more than the highest signal number; signals 1..64 are usable. */ +#define NSIG 65 + +/* The magic handler values. The kernel dispatches on the raw value. */ +#define SIG_DFL ((void (*)(int))0) /* default action */ +#define SIG_IGN ((void (*)(int))1) /* ignore the signal */ +#define SIG_ERR ((void (*)(int)) - 1) /* signal() error return */ + +/* + * Integer type that can be accessed as an atomic entity even when an + * asynchronous signal interrupts the access (volatile sig_atomic_t is the + * conventional spelling). int is atomic on x86_64. + */ +typedef int sig_atomic_t; + +/* sigprocmask()/pthread_sigmask() how values (kernel-identical). */ +#define SIG_BLOCK 0 /* add the given set to the blocked mask */ +#define SIG_UNBLOCK 1 /* remove the given set from the blocked mask */ +#define SIG_SETMASK 2 /* replace the blocked mask with the given set */ + +#ifndef VLIBC_SIGSET_T_DEFINED +#define VLIBC_SIGSET_T_DEFINED +/* + * Signal mask type: a single 64-bit word — the x86_64 Linux sigset_t, + * where signal N is bit N-1 (see setjmp.h, whose sigsetjmp stores this + * same word). The typedef is shared with and + * under this guard, so pselect()/ppoll() and the sig* API agree on the + * type regardless of include order. + */ +typedef unsigned long sigset_t; +#endif + +/* + * Value carried with a queued (realtime) signal. sival_int and sival_ptr + * are alternative views of the same 64-bit payload. + */ +union sigval +{ + int sival_int; /* integer payload */ + void *sival_ptr; /* pointer payload */ +}; + +/* POSIX spelling of the payload type. */ +typedef union sigval sigval_t; + +/* + * Description of a delivered signal: number, errno, code and one of the + * per-code unions. This is the minimal x86_64 kernel layout (128 bytes) + * shared with , which fills si_pid/si_uid/si_status/si_utime/ + * si_stime for waitid(); the two headers define this exact block under the + * common VLIBC_INTERNAL_SIGINFO_DEFINED guard, so whichever is included + * first wins and the other skips — the type is identical either way. The + * kernel copies the full 128 bytes on signal delivery (sigaction with + * SA_SIGINFO, sigwaitinfo, sigtimedwait), so the size must stay 128 and + * the pad member guarantees it; the fields behind each si_code value + * (si_addr, si_value, timers, ...) are owned by a later todo. + */ +#ifndef VLIBC_INTERNAL_SIGINFO_DEFINED +#define VLIBC_INTERNAL_SIGINFO_DEFINED +typedef struct +{ + int si_signo; + int si_errno; + int si_code; + union + { + struct + { + pid_t si_pid; /* 16 */ + uid_t si_uid; /* 20 */ + int si_status; /* 24 */ + long si_utime; /* 32 — 8-byte clock_t, matches the kernel/glibc ABI */ + long si_stime; /* 40 */ + }; + int vlibc_siginfo_pad[28]; /* union sized 112 so the struct stays 128 */ + }; +} siginfo_t; + +_Static_assert(sizeof(siginfo_t) == 128, "siginfo_t must match the kernel size"); +_Static_assert(offsetof(siginfo_t, si_pid) == 16, "si_pid must sit at offset 16"); +#endif + +/* + * Action taken when signal sig is delivered. sa_handler and sa_sigaction + * are two views of the same slot (POSIX: sa_sigaction is used when + * SA_SIGINFO is set and receives (sig, siginfo_t *, void *) instead of + * just sig). The layout — handler, mask, flags, restorer — is the PUBLIC + * POSIX order; the kernel ABI uses a different order (see the file-top + * note) that sigaction() translates. + */ +struct sigaction +{ + union + { + void (*sa_handler)(int); /* SIG_DFL/SIG_IGN/a handler */ + void (*sa_sigaction)(int, siginfo_t *, void *); /* SA_SIGINFO form */ + }; + sigset_t sa_mask; /* signals additionally blocked in the handler */ + int sa_flags; /* SA_* bits below */ + void (*sa_restorer)(void); /* kernel-private; do not set (see top) */ +}; + +_Static_assert(sizeof(struct sigaction) == 32, "struct sigaction must be 32 bytes"); +_Static_assert(offsetof(struct sigaction, sa_mask) == 8, "sa_mask must sit at offset 8"); +_Static_assert(offsetof(struct sigaction, sa_flags) == 16, "sa_flags must sit at offset 16"); +_Static_assert(offsetof(struct sigaction, sa_restorer) == 24, "sa_restorer must sit at offset 24"); + +/* sa_flags bits (x86_64 kernel values; SA_RESTORER is kernel-private). */ +#define SA_NOCLDSTOP 1 /* do not generate SIGCHLD when children stop */ +#define SA_NOCLDWAIT 2 /* do not leave zombies on child exit */ +#define SA_SIGINFO 4 /* call the handler with the siginfo form */ +#define SA_RESTORER 0x04000000 /* handler returns via sa_restorer (kernel) */ +#define SA_ONSTACK 0x08000000 /* run the handler on the alternate stack */ +#define SA_RESTART 0x10000000 /* restart interrupted syscalls after the handler */ +#define SA_NODEFER 0x40000000 /* do not block the delivered signal in its handler */ +#define SA_RESETHAND 0x80000000 /* reset the disposition to SIG_DFL on entry */ + +/* + * Install the action act for signal sig (NULL leaves it unchanged) and + * store the previous action through oact (NULL skips the store). Return + * 0, or -1 with errno set. sig must be in 1..64 and (for a catching + * action) not SIGKILL/SIGSTOP; the kernel rejects invalid values with + * EINVAL. Real handler pointers are installed with SA_RESTORER and the + * internal return trampoline so the handler may return normally. + */ +int +sigaction(int sig, const struct sigaction *restrict act, struct sigaction *restrict oact); + +/* + * Inspect or change the calling thread's blocked-signal mask. how is + * SIG_BLOCK/SIG_UNBLOCK/SIG_SETMASK; set is the mask operand (NULL + * queries without changing: how is then ignored) and the previous mask is + * stored through oldset (NULL skips the store). Return 0, or -1 with + * errno set. + */ +int +sigprocmask(int how, const sigset_t *restrict set, sigset_t *restrict oldset); + +/* Store the set of blocked-and-pending signals through set. 0 or -1. */ +int +sigpending(sigset_t *set); + +/* + * Atomically install mask as the blocked mask and wait until a signal + * whose delivery is not blocked by mask is caught; when its handler + * returns, the previous mask is restored and sigsuspend returns -1 with + * errno EINTR. + */ +int +sigsuspend(const sigset_t *mask); + +/* Empty set: no signal blocked, and no member set. Always 0. */ +int +sigemptyset(sigset_t *set); + +/* Set every signal 1..64 blocked/member. Always 0. */ +int +sigfillset(sigset_t *set); + +/* + * Add/remove signal sig to/from set. Return 0, or -1 with errno EINVAL + * when sig is outside 1..64. + */ +int +sigaddset(sigset_t *set, int sig); +int +sigdelset(sigset_t *set, int sig); + +/* + * Nonzero when sig is a member of set, 0 otherwise, or -1 with errno + * EINVAL when sig is outside 1..64. + */ +int +sigismember(const sigset_t *set, int sig); + +/* + * Synchronously wait for one of the signals in set (which should be + * blocked in the calling thread) and store the delivered signal number + * through sig. Return 0 on success, or the error number (EINTR is + * retried internally). The signal is consumed and never delivered to a + * handler. + */ +int +sigwait(const sigset_t *restrict set, int *restrict sig); + +/* + * Like sigwait, but return the delivered signal number directly (or -1 + * with errno set) and, when info is not NULL, store the full 128-byte + * kernel siginfo through it. + */ +int +sigwaitinfo(const sigset_t *restrict set, siginfo_t *restrict info); + +/* + * sigwaitinfo bounded by timeout (relative; NULL waits indefinitely). + * Returns the signal number, 0 is never returned for a successful wait, + * or -1 with errno set (EAGAIN on timeout, EINTR if a handler ran). + */ +int +sigtimedwait(const sigset_t *restrict set, siginfo_t *restrict info, + const struct timespec *restrict timeout); + +/* + * Send signal sig to the process pid (negative pid targets a process + * group; 0 targets the caller's process group; see killpg). Return 0, or + * -1 with errno set. Permission, existence and signal validity are + * checked by the kernel. + */ +int +kill(pid_t pid, int sig); + +/* + * Send signal sig to every process in the process group pgrp (0 selects + * the caller's process group). Return 0, or -1 with errno set. + */ +int +killpg(pid_t pgrp, int sig); + +/* + * Send signal sig to the calling thread (thread-directed, so it is + * delivered even when another thread of the process has it blocked). + * Return 0, or -1 with errno set. + */ +int +raise(int sig); + +/* + * Wait until a signal is caught, then return -1 with errno EINTR. If the + * process is terminated by the signal instead, pause never returns. + */ +int +pause(void); + +/* + * Schedule delivery of SIGALRM to the calling process after seconds + * seconds (0 cancels any pending alarm). Return the number of seconds + * remaining on any previously scheduled alarm, or 0. + */ +unsigned +alarm(unsigned seconds); + +/* + * Abnormally terminate the calling process: raise SIGABRT with default + * disposition — if SIGABRT is blocked it is unblocked first, and if it + * is caught or ignored the disposition is reset to SIG_DFL and the raise + * repeated. If that still returns, terminate with exit status 134 as a + * last resort. abort never returns. + */ +__attribute__((noreturn)) void +abort(void); + +/* + * Send signal sig with payload value to process pid, as if by a kernel + * queue operation (the kernel sees a negative si_code, SI_QUEUE). Return + * 0, or -1 with errno set. + */ +int +sigqueue(pid_t pid, int sig, const union sigval value); + +/* + * Install handler as the action for sig with SA_RESTART (BSD semantics: + * syscalls interrupted by the signal are restarted). Return the previous + * handler, or SIG_ERR with errno set. + */ +void (*signal(int sig, void (*handler)(int)))(int); + +#if VLIBC_LEVEL_GE(2) +/* Level 2 (muslmimic): XSI and obsolete conveniences. */ + +/* + * Alternate signal stack descriptor. ss_sp/ss_size name a region the + * kernel switches to when delivering a handler installed with SA_ONSTACK + * (see sigaltstack below). stack_t is the POSIX spelling. + */ +typedef struct sigaltstack +{ + void *ss_sp; /* stack base or current stack base */ + int ss_flags; /* SS_ONSTACK/SS_DISABLE */ + size_t ss_size; /* stack bytes */ +} stack_t; + +/* ss_flags values. */ +#define SS_ONSTACK 1 /* the process is currently executing on this stack */ +#define SS_DISABLE 2 /* the alternate stack is currently disabled */ + +/* Minimum / default alternate stack sizes (x86_64 values). */ +#define MINSIGSTKSZ 2048 +#define SIGSTKSZ 8192 + +/* + * Install ss as the alternate signal stack (NULL leaves it unchanged) + * and store the previous descriptor through oss (NULL skips the store). + * Return 0, or -1 with errno set. + */ +int +sigaltstack(const stack_t *restrict ss, stack_t *restrict oss); + +/* + * Schedule SIGALRM after usecs microseconds, repeating every interval + * microseconds (0 fires once). Return the number of microseconds + * remaining on any previously scheduled alarm, or -1 on error. + */ +useconds_t +ualarm(useconds_t usecs, useconds_t interval); + +/* + * Toggle the SA_RESTART bit of sig's disposition: flag nonzero removes + * it (interrupted syscalls return EINTR, System V semantics), flag zero + * restores it. Return 0, or -1 with errno set. + */ +int +siginterrupt(int sig, int flag); + +/* + * Write s (when non-NULL and nonempty), ": " and strsignal(sig) to + * stderr. The output is a diagnostic only. + */ +void +psignal(int sig, const char *s); + +/* + * Like psignal, but the signal number and details are taken from the + * siginfo si (si_signo names the signal). si may be NULL, in which case + * only the message prefix is written. + */ +void +psiginfo(const siginfo_t *si, const char *s); +#endif /* VLIBC_LEVEL_GE(2) */ + +#ifdef __cplusplus +} +#endif + +#endif /* VLIBC_SIGNAL_H */ diff --git a/src/signal/abort.c b/src/signal/abort.c new file mode 100644 index 0000000..a32f91c --- /dev/null +++ b/src/signal/abort.c @@ -0,0 +1,46 @@ +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include "../internal/syscall.h" + +/* + * abort: raise SIGABRT with default disposition, then die regardless. + * + * The sequence follows POSIX: first the blocked mask is cleared for + * SIGABRT (a blocked SIGABRT would silently pend and abort would look + * like success), then SIGABRT is raised with whatever disposition is + * installed. If that raise returned, the disposition was caught (handler + * that returned) or ignored — so the disposition is reset to SIG_DFL and + * the raise repeated, which terminates the process. If even the default + * raise returned (impossible in a healthy kernel), _exit with 128+6 + * (= 134, the shell convention for death-by-SIGABRT) is the last resort. + * All of it is raw syscalls and the functions in this directory: abort + * must work from a half-initialized runtime and never touch stdio. + */ + +__attribute__((noreturn)) void +abort(void) +{ + sigset_t unblock; + struct sigaction dfl; + + /* Make sure SIGABRT is not blocked, then try the installed action. */ + sigemptyset(&unblock); + sigaddset(&unblock, SIGABRT); + sigprocmask(SIG_UNBLOCK, &unblock, NULL); + raise(SIGABRT); + + /* Returned: caught by a returning handler, or ignored. Go default. */ + dfl.sa_handler = SIG_DFL; + dfl.sa_flags = 0; + sigemptyset(&dfl.sa_mask); + sigaction(SIGABRT, &dfl, NULL); + raise(SIGABRT); + + /* Still here: the kernel failed to deliver. Die with the SIGABRT code. */ + __syscall1(SYS_exit_group, 134); + __builtin_unreachable(); +} diff --git a/src/signal/alarm.c b/src/signal/alarm.c new file mode 100644 index 0000000..15c9036 --- /dev/null +++ b/src/signal/alarm.c @@ -0,0 +1,23 @@ +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include "../internal/syscall.h" + +/* + * alarm: schedule a one-shot SIGALRM in seconds seconds. + * + * Pure SYS_alarm pass-through: the kernel arms the private per-process + * alarm timer, returns the seconds remaining on any alarm it just + * replaced (0 when none was pending), and treats 0 as "cancel the pending + * alarm". The raw result is always non-negative, so syscall_ret cannot + * mistake it for an error. + */ + +unsigned +alarm(unsigned seconds) +{ + return (unsigned)syscall_ret(__syscall1(SYS_alarm, (long)seconds)); +} diff --git a/src/signal/kill.c b/src/signal/kill.c new file mode 100644 index 0000000..640be7e --- /dev/null +++ b/src/signal/kill.c @@ -0,0 +1,38 @@ +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include + +#include "../internal/syscall.h" + +/* + * kill/killpg: process- and process-group-directed signals over SYS_kill. + * + * kill passes its pid straight to the kernel, which interprets it exactly + * as POSIX specifies: pid > 0 is a single process, pid == 0 the caller's + * process group, pid == -1 every process the caller may signal, and + * pid < -1 the process group -pid. killpg(pgrp, sig) is equivalent to + * kill(-pgrp, sig): a negative pgrp is not a valid group id and is + * rejected with EINVAL here (mirroring musl) rather than inverted into a + * positive-pid send by accident. + */ + +int +kill(pid_t pid, int sig) +{ + return syscall_ret(__syscall2(SYS_kill, (long)pid, sig)); +} + +int +killpg(pid_t pgrp, int sig) +{ + if (pgrp < 0) + { + errno = EINVAL; + return -1; + } + return kill(-pgrp, sig); +} diff --git a/src/signal/pause.c b/src/signal/pause.c new file mode 100644 index 0000000..10accb6 --- /dev/null +++ b/src/signal/pause.c @@ -0,0 +1,23 @@ +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +/* + * pause: sleep until a signal is caught. + * + * pause() must wait with nothing blocked, so it is sigsuspend on an empty + * mask. When a caught signal's handler returns, sigsuspend reports -1 + * with errno EINTR (never restarted by the kernel), which is exactly what + * POSIX specifies for pause. A signal whose default action terminates the + * process makes pause never return. + */ + +int +pause(void) +{ + sigset_t empty = 0; + + return sigsuspend(&empty); +} diff --git a/src/signal/psiginfo.c b/src/signal/psiginfo.c new file mode 100644 index 0000000..a0d8b49 --- /dev/null +++ b/src/signal/psiginfo.c @@ -0,0 +1,64 @@ +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include + +#include "../internal/syscall.h" + +#if VLIBC_LEVEL_GE(2) + +/* + * psignal/psiginfo: print a signal diagnostic to stderr (XSI). + * + * Both write the message argument s (when present), ": ", and + * strsignal()'s description of the signal, then a newline, directly to + * fd 2 with raw SYS_write — no stdio, so the calls work even when the + * runtime is only partially up. psignal takes the signal number; + * psiginfo reads si_signo out of a filled siginfo (a NULL si prints only + * the message prefix). + */ + +/* Write the NUL-terminated string s to fd 2. */ +static void +write_stderr(const char *s) +{ + if (s != NULL) + { + __syscall3(SYS_write, 2, (long)s, (long)strlen(s)); + } +} + +/* Write the shared ": \n" shape to stderr. */ +static void +signal_message(const char *s, const char *description) +{ + write_stderr(s); + if (s != NULL && s[0] != '\0') + { + write_stderr(": "); + } + write_stderr(description); + write_stderr("\n"); +} + +void +psignal(int sig, const char *s) +{ + signal_message(s, strsignal(sig)); +} + +void +psiginfo(const siginfo_t *si, const char *s) +{ + if (si == NULL) + { + signal_message(s, ""); + return; + } + signal_message(s, strsignal(si->si_signo)); +} + +#endif /* VLIBC_LEVEL_GE(2) */ diff --git a/src/signal/raise.c b/src/signal/raise.c new file mode 100644 index 0000000..31f7774 --- /dev/null +++ b/src/signal/raise.c @@ -0,0 +1,26 @@ +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include "../internal/syscall.h" + +/* + * raise: deliver sig to the calling thread. + * + * POSIX requires raise to be thread-directed (the calling thread must + * receive the signal even when sibling threads of the same process block + * it), so this rides SYS_tgkill with the calling thread's pid and tid, + * not the process-directed SYS_kill. The pid/tid come from raw syscalls: + * they are kernel reads, and gettid also has no thread-library + * dependency. The library is single-threaded today, but a process-> + * directed raise would already be the wrong thread once threads exist. + */ + +int +raise(int sig) +{ + return syscall_ret( + __syscall3(SYS_tgkill, __syscall0(SYS_getpid), __syscall0(SYS_gettid), (long)sig)); +} diff --git a/src/signal/sigaction.c b/src/signal/sigaction.c new file mode 100644 index 0000000..4ab7026 --- /dev/null +++ b/src/signal/sigaction.c @@ -0,0 +1,114 @@ +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include + +#include "../internal/syscall.h" + +/* + * sigaction: install or query a signal disposition over SYS_rt_sigaction. + * + * The kernel's x86_64 sigaction ABI is NOT the POSIX struct layout: + * + * POSIX (public, struct sigaction): handler, mask, flags(int), restorer + * kernel (rt_sigaction argument): handler, flags(64-bit), restorer, + * mask + * + * and the kernel copies a full 32-byte word for the flags slot, so the + * upper 32 bits of that slot must be zero (they hold the padding of the + * int-typed public flags). This file therefore marshals between a + * zeroed kernel-layout struct and the public struct, field by field. + * + * The kernel does not fall back to the vDSO return stub on x86_64: when a + * real (non-SIG_DFL/SIG_IGN) handler is installed it MUST carry + * SA_RESTORER plus a sa_restorer trampoline, or delivery crashes when the + * handler returns. The trampoline below is the classic two-instruction + * sequence (mov $SYS_rt_sigreturn; syscall) — the same shape glibc and + * musl ship — entered with the stack already positioned at the kernel's + * rt_sigframe, so it must be naked: any prologue would shift the stack + * and make rt_sigreturn restore garbage. SA_RESTORER is masked out of the + * flags reported through oact so callers only ever see the POSIX bits. + * + * No signal validity checking happens here: the kernel rejects out-of + * range numbers (EINVAL) and attempts to catch SIGKILL/SIGSTOP, and this + * wrapper is a pure pass-through of its results. + */ + +/* + * The kernel-layout sigaction (see above). sa_restorer and the trampoline + * are only attached for real handlers. + */ +struct vlibc_kernel_sigaction +{ + void (*vlibc_handler)(int); /* offset 0: handler or sa_sigaction */ + unsigned long vlibc_flags; /* offset 8: full 64-bit flags word */ + void (*vlibc_restorer)(void); /* offset 16: return trampoline */ + unsigned long vlibc_mask; /* offset 24: blocked-during-handler set */ +}; + +_Static_assert(sizeof(struct vlibc_kernel_sigaction) == 32, "kernel sigaction must be 32 bytes"); +_Static_assert(offsetof(struct vlibc_kernel_sigaction, vlibc_flags) == 8, + "kernel flags must sit at offset 8"); +_Static_assert(offsetof(struct vlibc_kernel_sigaction, vlibc_restorer) == 16, + "kernel restorer must sit at offset 16"); +_Static_assert(offsetof(struct vlibc_kernel_sigaction, vlibc_mask) == 24, + "kernel mask must sit at offset 24"); + +/* + * The rt_sigreturn trampoline every real handler returns through. Naked: + * it is entered by the handler's ret with the stack already pointing at + * the kernel's rt_sigframe, so no prologue/epilogue may run. + */ +static void __attribute__((naked, noreturn)) +vlibc_restore_rt(void) +{ + __asm__ volatile("movq $15, %rax\n\tsyscall"); +} + +int +sigaction(int sig, const struct sigaction *restrict act, struct sigaction *restrict oact) +{ + struct vlibc_kernel_sigaction kact; + struct vlibc_kernel_sigaction kold; + struct vlibc_kernel_sigaction *kactp; + long r; + + if (act != NULL) + { + /* Marshal the public struct into the kernel layout (see top). */ + kactp = &kact; + kact.vlibc_handler = act->sa_handler; + kact.vlibc_flags = (unsigned long)act->sa_flags; + kact.vlibc_restorer = NULL; + kact.vlibc_mask = (unsigned long)act->sa_mask; + if (act->sa_handler != SIG_DFL && act->sa_handler != SIG_IGN) + { + kact.vlibc_flags |= (unsigned long)SA_RESTORER; + kact.vlibc_restorer = vlibc_restore_rt; + } + } + else + { + kactp = NULL; + } + + r = __syscall4(SYS_rt_sigaction, sig, (long)kactp, oact != NULL ? (long)&kold : 0L, + (long)sizeof(sigset_t)); + if (r < 0) + { + return syscall_ret(r); + } + + if (oact != NULL) + { + /* Marshal the kernel layout back into the public struct. */ + oact->sa_handler = kold.vlibc_handler; + oact->sa_mask = (sigset_t)kold.vlibc_mask; + oact->sa_flags = (int)(kold.vlibc_flags & ~(unsigned long)SA_RESTORER); + oact->sa_restorer = NULL; + } + return 0; +} diff --git a/src/signal/sigaltstack.c b/src/signal/sigaltstack.c new file mode 100644 index 0000000..0995fa8 --- /dev/null +++ b/src/signal/sigaltstack.c @@ -0,0 +1,25 @@ +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include "../internal/syscall.h" + +#if VLIBC_LEVEL_GE(2) + +/* + * sigaltstack: install or query the alternate signal stack over + * SYS_sigaltstack (ss, oss). The kernel's stack_t — base pointer, flags + * int, size — is byte-identical to the public stack_t, so it passes + * through unmodified. A handler installed with SA_ONSTACK runs on the + * alternate stack; the kernel restores the ordinary stack on return. + */ + +int +sigaltstack(const stack_t *restrict ss, stack_t *restrict oss) +{ + return syscall_ret(__syscall2(SYS_sigaltstack, (long)ss, (long)oss)); +} + +#endif /* VLIBC_LEVEL_GE(2) */ diff --git a/src/signal/siginterrupt.c b/src/signal/siginterrupt.c new file mode 100644 index 0000000..72c2d85 --- /dev/null +++ b/src/signal/siginterrupt.c @@ -0,0 +1,40 @@ +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#if VLIBC_LEVEL_GE(2) + +/* + * siginterrupt: toggle the SA_RESTART bit of sig's disposition. + * + * sig is the XSI way to switch between System V semantics (flag nonzero: + * interrupted syscalls return EINTR — SA_RESTART cleared) and BSD + * semantics (flag zero: syscalls restart — SA_RESTART set) for one + * signal. The current action is read, its SA_RESTART bit updated, and the + * action reinstalled through sigaction, which re-attaches the return + * trampoline as needed. + */ + +int +siginterrupt(int sig, int flag) +{ + struct sigaction sa; + + if (sigaction(sig, NULL, &sa) == -1) + { + return -1; + } + if (flag != 0) + { + sa.sa_flags &= ~SA_RESTART; + } + else + { + sa.sa_flags |= SA_RESTART; + } + return sigaction(sig, &sa, NULL); +} + +#endif /* VLIBC_LEVEL_GE(2) */ diff --git a/src/signal/signal.c b/src/signal/signal.c new file mode 100644 index 0000000..6e11ffe --- /dev/null +++ b/src/signal/signal.c @@ -0,0 +1,29 @@ +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +/* + * signal: the System V convenience, defined on top of sigaction with BSD + * restart semantics. It installs handler for sig with SA_RESTART (syscalls + * interrupted by the signal are restarted) and an empty supplementary + * mask, then returns the previous disposition. SIG_DFL/SIG_IGN pass + * through unchanged. Errors are reported as SIG_ERR with errno set. + */ + +void (*signal(int sig, void (*handler)(int)))(int) +{ + struct sigaction sa; + struct sigaction old; + + sa.sa_handler = handler; + sa.sa_flags = SA_RESTART; + sigemptyset(&sa.sa_mask); + + if (sigaction(sig, &sa, &old) == -1) + { + return SIG_ERR; + } + return old.sa_handler; +} diff --git a/src/signal/sigprocmask.c b/src/signal/sigprocmask.c new file mode 100644 index 0000000..dbdfc03 --- /dev/null +++ b/src/signal/sigprocmask.c @@ -0,0 +1,32 @@ +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include "../internal/syscall.h" + +/* + * sigprocmask/sigpending: the blocked-mask pair over the rt_* syscalls. + * + * sigprocmask rides SYS_rt_sigprocmask (how, set, oldset, sigsetsize). + * The kernel reads the one-word sigset when set is non-NULL and applies + * it per how; a NULL set with SIG_BLOCK is the POSIX-blessed way to query + * the current mask (how is then ignored). sigpending rides SYS_rt_sigpending + * (set, sigsetsize) and reports the signals that are BOTH blocked and + * pending. Both are pure pass-throughs: syscall_ret turns the kernel's + * -errno convention into -1 + errno. + */ + +int +sigprocmask(int how, const sigset_t *restrict set, sigset_t *restrict oldset) +{ + return syscall_ret( + __syscall4(SYS_rt_sigprocmask, how, (long)set, (long)oldset, (long)sizeof(sigset_t))); +} + +int +sigpending(sigset_t *set) +{ + return syscall_ret(__syscall2(SYS_rt_sigpending, (long)set, (long)sizeof(sigset_t))); +} diff --git a/src/signal/sigqueue.c b/src/signal/sigqueue.c new file mode 100644 index 0000000..2c038e3 --- /dev/null +++ b/src/signal/sigqueue.c @@ -0,0 +1,57 @@ +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include "../internal/syscall.h" + +/* + * sigqueue: send sig with an attached sigval payload to process pid over + * SYS_rt_sigqueueinfo (pid, sig, siginfo *). + * + * The kernel validates and forwards a full 128-byte siginfo; the sender + * must place the payload where the kernel's SI_QUEUE union reads it: + * si_code at offset 8 (a negative value — SI_QUEUE is -1 — because the + * kernel refuses si_code >= 0 with EPERM: only the kernel may send with a + * positive code) and the sigval at offset 24 (after the pid/uid words the + * kernel reserves for the receiving side). The private struct below pins + * exactly that; si_signo is set for completeness though the kernel + * overwrites it with the sig argument. + */ +struct vlibc_rt_sigqueue_info +{ + int si_signo; /* 0 */ + int si_errno; /* 4 */ + int si_code; /* 8 */ + union + { + struct + { + pid_t si_pid; /* 16 */ + uid_t si_uid; /* 20 */ + union sigval si_value; /* 24 */ + }; + int vlibc_pad[28]; /* union sized 112 so the struct stays 128 */ + }; +}; + +_Static_assert(sizeof(struct vlibc_rt_sigqueue_info) == 128, + "rt_sigqueueinfo needs a full 128-byte siginfo"); +_Static_assert(offsetof(struct vlibc_rt_sigqueue_info, si_code) == 8, + "si_code must sit at offset 8"); + +int +sigqueue(pid_t pid, int sig, const union sigval value) +{ + struct vlibc_rt_sigqueue_info si; + + si.si_signo = sig; + si.si_errno = 0; + si.si_code = -1; /* SI_QUEUE; negative per the kernel's from-user check */ + si.si_pid = 0; + si.si_uid = 0; + si.si_value = value; + + return syscall_ret(__syscall3(SYS_rt_sigqueueinfo, (long)pid, sig, (long)&si)); +} diff --git a/src/signal/sigset.c b/src/signal/sigset.c new file mode 100644 index 0000000..23924e7 --- /dev/null +++ b/src/signal/sigset.c @@ -0,0 +1,71 @@ +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include + +/* + * sigemptyset/sigfillset/sigaddset/sigdelset/sigismember: the sigset_t + * manipulation API over the single 64-bit word kernel sigset (signal N + * is bit N-1). No syscalls are involved; only the out-of-range checks + * report EINVAL. Signals 1..64 are addressable (SIGRTMAX == 64), so the + * shift for the highest signal is 1UL << 63. + */ + +/* True when sig names a usable signal (1..NSIG-1, i.e. 1..64). */ +static int +vlibc_sig_valid(int sig) +{ + return sig >= 1 && sig < NSIG; +} + +int +sigemptyset(sigset_t *set) +{ + *set = 0; + return 0; +} + +int +sigfillset(sigset_t *set) +{ + *set = ~(sigset_t)0; + return 0; +} + +int +sigaddset(sigset_t *set, int sig) +{ + if (!vlibc_sig_valid(sig)) + { + errno = EINVAL; + return -1; + } + *set |= (sigset_t)1 << (sig - 1); + return 0; +} + +int +sigdelset(sigset_t *set, int sig) +{ + if (!vlibc_sig_valid(sig)) + { + errno = EINVAL; + return -1; + } + *set &= ~((sigset_t)1 << (sig - 1)); + return 0; +} + +int +sigismember(const sigset_t *set, int sig) +{ + if (!vlibc_sig_valid(sig)) + { + errno = EINVAL; + return -1; + } + return (int)((*set >> (sig - 1)) & 1); +} diff --git a/src/signal/sigsuspend.c b/src/signal/sigsuspend.c new file mode 100644 index 0000000..13c5c3a --- /dev/null +++ b/src/signal/sigsuspend.c @@ -0,0 +1,24 @@ +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include "../internal/syscall.h" + +/* + * sigsuspend: atomically swap in mask and sleep until a signal is caught. + * + * SYS_rt_sigsuspend (mask, sigsetsize) installs mask for the duration of + * the wait and restores the caller's previous mask when the catching + * handler returns; it always returns -EINTR then (it is never restarted), + * which syscall_ret reports as -1 with errno EINTR. A signal whose + * default action terminates or stops the process makes sigsuspend never + * return, exactly as POSIX requires. + */ + +int +sigsuspend(const sigset_t *mask) +{ + return syscall_ret(__syscall2(SYS_rt_sigsuspend, (long)mask, (long)sizeof(sigset_t))); +} diff --git a/src/signal/sigwait.c b/src/signal/sigwait.c new file mode 100644 index 0000000..2d2f43e --- /dev/null +++ b/src/signal/sigwait.c @@ -0,0 +1,57 @@ +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include + +#include "../internal/syscall.h" + +/* + * sigwait/sigwaitinfo/sigtimedwait: synchronous signal consumption over + * SYS_rt_sigtimedwait (set, siginfo *, timeout, sigsetsize). The syscall + * dequeues one of the signals in set (consuming it, so no handler ever + * runs for it), returns its number, and — when info is non-NULL — copies + * the full 128-byte kernel siginfo through it. + * + * The kernel's timespec timeout is relative; NULL waits indefinitely. + * EINTR (a handler for some OTHER signal ran) is retried by sigwait — + * whose contract is to return 0 with the signal stored, or an error + * NUMBER (never -1/errno). sigwaitinfo/sigtimedwait instead surface the + * kernel result directly via syscall_ret: the signal number, or -1 with + * errno set (EAGAIN on timeout). + */ + +int +sigwait(const sigset_t *restrict set, int *restrict sig) +{ + long r; + + do + { + r = __syscall4(SYS_rt_sigtimedwait, (long)set, 0L, 0L, (long)sizeof(sigset_t)); + } while (r == -EINTR); + + if (r < 0) + { + return (int)-r; /* error number, per the sigwait contract */ + } + *sig = (int)r; + return 0; +} + +int +sigwaitinfo(const sigset_t *restrict set, siginfo_t *restrict info) +{ + return syscall_ret( + __syscall4(SYS_rt_sigtimedwait, (long)set, (long)info, 0L, (long)sizeof(sigset_t))); +} + +int +sigtimedwait(const sigset_t *restrict set, siginfo_t *restrict info, + const struct timespec *restrict timeout) +{ + return syscall_ret(__syscall4(SYS_rt_sigtimedwait, (long)set, (long)info, (long)timeout, + (long)sizeof(sigset_t))); +} diff --git a/src/signal/ualarm.c b/src/signal/ualarm.c new file mode 100644 index 0000000..5597b86 --- /dev/null +++ b/src/signal/ualarm.c @@ -0,0 +1,44 @@ +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include + +#include "../internal/syscall.h" + +#if VLIBC_LEVEL_GE(2) + +/* + * ualarm: schedule a SIGALRM in usecs microseconds, repeating every + * interval microseconds (0 = one-shot), over SYS_setitimer(ITIMER_REAL). + * + * The kernel itimerval layout matches vlibc's struct itimerval from + * exactly (two timevals of long seconds + long microseconds), + * so the public struct passes through unmodified. The previous alarm is + * returned in microseconds, converted from the old it_value the kernel + * reports; -1 surfaces syscall failures (useconds_t is unsigned, so -1 is + * the all-ones value, the standard error spelling for this XSI call). + */ + +useconds_t +ualarm(useconds_t usecs, useconds_t interval) +{ + struct itimerval new_timer; + struct itimerval old_timer; + + new_timer.it_interval.tv_sec = (time_t)(interval / 1000000); + new_timer.it_interval.tv_usec = (suseconds_t)(interval % 1000000); + new_timer.it_value.tv_sec = (time_t)(usecs / 1000000); + new_timer.it_value.tv_usec = (suseconds_t)(usecs % 1000000); + + if (syscall_ret(__syscall3(SYS_setitimer, ITIMER_REAL, (long)&new_timer, (long)&old_timer)) == + -1) + { + return (useconds_t)-1; + } + return (useconds_t)old_timer.it_value.tv_sec * 1000000 + (useconds_t)old_timer.it_value.tv_usec; +} + +#endif /* VLIBC_LEVEL_GE(2) */ diff --git a/tests/test_signal.c b/tests/test_signal.c new file mode 100644 index 0000000..09328d3 --- /dev/null +++ b/tests/test_signal.c @@ -0,0 +1,479 @@ +/* + * vlibc — signal handling test (todo 28). + * + * Exercises the whole surface end to end: + * + * 1. sigaction installs a SIGUSR1 handler; raise() delivers it; the + * handler runs exactly once and the process resumes after it (the + * SA_RESTORER return trampoline works). Reading the action back with + * act == NULL returns the same handler with SA_RESTORER masked out. + * 2. sigprocmask SIG_BLOCK + raise makes the signal pending; + * sigpending/sigismember report it; SIG_UNBLOCK delivers it to the + * handler. + * 3. sigwait consumes a blocked+pending SIGUSR1 (handler never runs). + * 4. sigqueue(self, SIGUSR2, value) with an SA_SIGINFO handler: the + * 3-arg handler runs and sees si_code == SI_QUEUE (-1). + * 5. alarm(1) + pause(): the SIGALRM handler runs and pause returns -1. + * 6. signal() installs a BSD (SA_RESTART) handler and returns the + * previous sigaction-installed handler. + * 7. abort() in a forked child kills it with SIGABRT (raw fork/wait4, + * independent of the parallel fork todo). + * 8. kill(pid, SIGKILL) on a sleeping child: reaped WIFSIGNALED with + * WTERMSIG == SIGKILL. + * + * Level-2 gated section (compile with -DVLIBC_LEVEL=2): + * 9. sigaltstack installs an alternate stack and an SA_ONSTACK SIGUSR2 + * handler provably runs on it (its locals land inside the stack). + * 10. ualarm fires SIGALRM into the installed handler. + * 11. siginterrupt clears and restores SA_RESTART. + * 12. psignal/psiginfo write their stderr diagnostics. + * + * The -f mode runs the failure shapes — sigaction with sig -1/0/SIGKILL, + * sigprocmask with an invalid how, sigaddset/sigismember with out-of-range + * signals, killpg with a negative pgrp — asserting return values only + * (errno is never read: vlibc's errno slot collides with the host TCB, + * so every library errno write is bracketed with a save/restore of the + * slot, house pattern). -f exits via raw SYS_exit_group. + * + * No host libc headers are included (the -Iinclude path would shadow + * GCC's internal headers); diagnostics go through raw SYS_write. Not part + * of the library proper; compiled manually for this todo (the tests/ + + * make check wiring is owned by a later todo). + */ + +#include + +#include + +#include "../src/internal/syscall.h" + +/* Kernel timespec (vlibc is not included by this TU). */ +struct t28_timespec +{ + long tv_sec; + long tv_nsec; +}; + +static int failures; + +static volatile sig_atomic_t usr1_count; +static volatile sig_atomic_t alarm_count; +static volatile sig_atomic_t bsd_count; +static volatile sig_atomic_t usr2_count; +static volatile int usr2_code; +static volatile sig_atomic_t astk_fired; +static volatile unsigned long astk_local_addr; + +/* Write a NUL-terminated string to fd via the raw syscall layer. The + * optimize attribute keeps GCC from lowering the length loop into a + * strlen call, which would leave a vlibc-owned symbol undefined in this + * host-linked standalone binary (house idiom, see src/string). */ +static __attribute__((optimize("no-tree-loop-distribute-patterns"))) void +say(int fd, const char *s) +{ + long n = 0; + + while (s[n] != '\0') + { + n++; + } + __syscall3(SYS_write, fd, (long)s, n); +} + +static void +check(int cond, const char *what) +{ + if (cond) + { + say(1, "PASS: "); + } + else + { + say(1, "FAIL: "); + failures++; + } + say(1, what); + say(1, "\n"); +} + +/* + * Host-TCB slot-1 bracket: the library's errno write on a negative path + * lands at %fs:0+8, glibc's dtv pointer. Save and restore it around each + * such call; only vlibc/raw-syscall code runs in between (task 13 + * technique). + */ +static unsigned long +tcb_slot1(void) +{ + return *(unsigned long *)((char *)__builtin_thread_pointer() + 8); +} + +static void +tcb_slot1_set(unsigned long v) +{ + *(unsigned long *)((char *)__builtin_thread_pointer() + 8) = v; +} + +/* Terminate the current thread via the raw syscall (never returns). */ +static void +t28_child_exit(int code) +{ + __syscall1(SYS_exit, code); + __builtin_unreachable(); +} + +static void +h_usr1(int sig) +{ + (void)sig; + usr1_count++; +} + +static void +h_alarm(int sig) +{ + (void)sig; + alarm_count++; +} + +static void +h_bsd(int sig) +{ + (void)sig; + bsd_count++; +} + +/* SA_SIGINFO handler: records si_code (kernel SI_QUEUE == -1 for sigqueue). */ +static void +h_usr2_info(int sig, siginfo_t *si, void *ctx) +{ + (void)sig; + (void)ctx; + usr2_count++; + usr2_code = si->si_code; +} + +/* 1. sigaction install + raise + readback. */ +static void +scenario_sigaction_raise(void) +{ + struct sigaction act; + struct sigaction old; + + usr1_count = 0; + act.sa_handler = h_usr1; + sigemptyset(&act.sa_mask); + act.sa_flags = 0; + check(sigaction(SIGUSR1, &act, NULL) == 0, "sigaction installs a SIGUSR1 handler"); + check(raise(SIGUSR1) == 0, "raise(SIGUSR1) delivers synchronously"); + check(usr1_count == 1, "SIGUSR1 handler ran exactly once and returned (resumed)"); + check(sigaction(SIGUSR1, NULL, &old) == 0 && old.sa_handler == h_usr1 && + (old.sa_flags & SA_RESTORER) == 0, + "sigaction act==NULL reads back the handler, SA_RESTORER masked out"); +} + +/* 2. Block, pend, inspect, unblock-deliver. */ +static void +scenario_sigprocmask_pending(void) +{ + sigset_t block; + sigset_t pend; + sig_atomic_t before = usr1_count; + + sigemptyset(&block); + sigaddset(&block, SIGUSR1); + check(sigprocmask(SIG_BLOCK, &block, NULL) == 0, "sigprocmask SIG_BLOCK SIGUSR1"); + check(raise(SIGUSR1) == 0, "raise while blocked succeeds (signal pends)"); + sigemptyset(&pend); + check(sigpending(&pend) == 0, "sigpending reports pending signals"); + check(sigismember(&pend, SIGUSR1) == 1, "sigpending contains SIGUSR1"); + check(sigismember(&pend, SIGALRM) == 0, "sigpending holds no unrelated signal"); + check(sigprocmask(SIG_UNBLOCK, &block, NULL) == 0, + "sigprocmask SIG_UNBLOCK runs the pending handler"); + check(usr1_count == before + 1, "the pending SIGUSR1 was delivered on unblock"); +} + +/* 3. sigwait consumes a blocked signal; the handler never runs. */ +static void +scenario_sigwait(void) +{ + sigset_t set; + int got = -1; + sig_atomic_t before = usr1_count; + + sigemptyset(&set); + sigaddset(&set, SIGUSR1); + check(sigprocmask(SIG_BLOCK, &set, NULL) == 0, "sigwait: SIGUSR1 blocked"); + check(raise(SIGUSR1) == 0, "sigwait: pending SIGUSR1 raised"); + check(sigwait(&set, &got) == 0, "sigwait returns 0 (signal consumed)"); + check(got == SIGUSR1, "sigwait reports SIGUSR1"); + check(usr1_count == before, "sigwait consumed the signal: handler did not run"); + check(sigprocmask(SIG_UNBLOCK, &set, NULL) == 0, "sigwait: mask restored after"); +} + +/* 4. sigqueue + SA_SIGINFO delivery (payload union + kernel siginfo). */ +static void +scenario_sigqueue(void) +{ + struct sigaction act; + union sigval val; + long self = __syscall0(SYS_getpid); + + usr2_count = 0; + usr2_code = 0; + act.sa_sigaction = h_usr2_info; + sigemptyset(&act.sa_mask); + act.sa_flags = SA_SIGINFO; + check(sigaction(SIGUSR2, &act, NULL) == 0, "install an SA_SIGINFO SIGUSR2 handler"); + val.sival_int = 42; + check(sigqueue((pid_t)self, SIGUSR2, val) == 0, "sigqueue(self, SIGUSR2) delivers"); + check(usr2_count == 1, "SA_SIGINFO handler ran once (3-arg form)"); + check(usr2_code == -1, "SA_SIGINFO handler saw si_code SI_QUEUE (-1)"); +} + +/* 5. alarm(1) + pause(): the handler runs, pause returns -1/EINTR. */ +static void +scenario_alarm_pause(void) +{ + struct sigaction act; + unsigned long saved; + + alarm_count = 0; + act.sa_handler = h_alarm; + sigemptyset(&act.sa_mask); + act.sa_flags = 0; + check(sigaction(SIGALRM, &act, NULL) == 0, "install a SIGALRM handler"); + check(alarm(1) == 0, "alarm(1) arms with no previous alarm"); + saved = tcb_slot1(); /* pause() reports via errno EINTR (library write) */ + check(pause() == -1, "pause() returns -1 after the SIGALRM handler ran"); + tcb_slot1_set(saved); + check(alarm_count == 1, "SIGALRM handler ran during pause"); + alarm(0); /* cancel any leftover of the 1 s alarm */ +} + +/* 6. signal(): BSD handler install, returns the previous disposition. */ +static void +scenario_signal_fn(void) +{ + void (*prev)(int); + struct sigaction cur; + + bsd_count = 0; + prev = signal(SIGUSR1, h_bsd); + check(prev == h_usr1, "signal() returns the previous (sigaction) handler"); + check(raise(SIGUSR1) == 0, "signal()-installed handler receives raise()"); + check(bsd_count == 1, "signal() BSD handler ran once"); + check(sigaction(SIGUSR1, NULL, &cur) == 0 && (cur.sa_flags & SA_RESTART) != 0, + "signal() installed with SA_RESTART (BSD semantics)"); +} + +/* 7. abort() in a forked child: dies by SIGABRT (raw fork + wait4). */ +static void +scenario_abort_child(void) +{ + long pid = __syscall0(SYS_fork); + int st = 0; + long r; + + if (pid == 0) + { + abort(); + t28_child_exit(0); + } + if (pid > 0) + { + r = __syscall4(SYS_wait4, pid, (long)&st, 0, 0); + check(r == pid && WIFSIGNALED(st) && WTERMSIG(st) == SIGABRT, + "abort() kills the child with SIGABRT"); + } + else + { + check(0, "fork for the abort child"); + } +} + +/* 8. kill(pid, SIGKILL) on a sleeping child: reaped WIFSIGNALED. */ +static void +scenario_kill_child(void) +{ + long pid = __syscall0(SYS_fork); + int st = 0; + long r; + + if (pid == 0) + { + struct t28_timespec ts = {.tv_sec = 10, .tv_nsec = 0}; + + __syscall2(SYS_nanosleep, (long)&ts, 0); + t28_child_exit(0); + } + if (pid > 0) + { + check(kill((pid_t)pid, SIGKILL) == 0, "kill(pid, SIGKILL) succeeds"); + r = __syscall4(SYS_wait4, pid, (long)&st, 0, 0); + check(r == pid && WIFSIGNALED(st) && WTERMSIG(st) == SIGKILL, + "SIGKILLed child is reaped WIFSIGNALED WTERMSIG 9"); + } + else + { + check(0, "fork for the SIGKILL child"); + } +} + +#if VLIBC_LEVEL_GE(2) +/* Level 2 (muslmimic): sigaltstack / ualarm / siginterrupt / psignal. */ + +static char t28_alt_stack[SIGSTKSZ * 2]; + +/* SA_ONSTACK handler: records where its own frame landed. */ +static void +h_astk(int sig) +{ + volatile unsigned long local = 0; + + (void)sig; + astk_local_addr = (unsigned long)&local; + astk_fired = 1; +} + +/* 9. Alternate stack: an SA_ONSTACK handler provably runs on it. */ +static void +scenario_sigaltstack(void) +{ + struct sigaction act; + stack_t ss; + stack_t cur; + stack_t disable; + unsigned long base = (unsigned long)t28_alt_stack; + int on_range; + + astk_fired = 0; + astk_local_addr = 0; + ss.ss_sp = t28_alt_stack; + ss.ss_flags = 0; + ss.ss_size = sizeof t28_alt_stack; + check(sigaltstack(&ss, NULL) == 0, "sigaltstack installs an alternate stack"); + check(sigaltstack(NULL, &cur) == 0, "sigaltstack reads the current stack back"); + check(cur.ss_sp == t28_alt_stack && cur.ss_flags == 0 && cur.ss_size == sizeof t28_alt_stack, + "sigaltstack readback matches the installed descriptor"); + + act.sa_handler = h_astk; + sigemptyset(&act.sa_mask); + act.sa_flags = SA_ONSTACK; + check(sigaction(SIGUSR2, &act, NULL) == 0, "install an SA_ONSTACK SIGUSR2 handler"); + check(raise(SIGUSR2) == 0, "raise SIGUSR2 with the alternate stack armed"); + on_range = + astk_fired == 1 && astk_local_addr >= base && astk_local_addr < base + sizeof t28_alt_stack; + check(on_range, "SA_ONSTACK handler ran with its frame on the alternate stack"); + + disable.ss_sp = NULL; + disable.ss_flags = SS_DISABLE; + disable.ss_size = 0; + check(sigaltstack(&disable, NULL) == 0, "sigaltstack disables the alternate stack"); + act.sa_handler = SIG_DFL; + act.sa_flags = 0; + sigaction(SIGUSR2, &act, NULL); +} + +/* 10. ualarm fires SIGALRM into the installed handler. */ +static void +scenario_ualarm(void) +{ + int i; + int fired = 0; + + alarm_count = 0; + check(ualarm(200000, 0) != (useconds_t)-1, "ualarm(200 ms, 0) arms an itimer"); + for (i = 0; i < 5000 && alarm_count == 0; i++) + { + struct t28_timespec ts = {.tv_sec = 0, .tv_nsec = 1000000L}; /* 1 ms */ + + __syscall2(SYS_nanosleep, (long)&ts, 0); + } + fired = alarm_count > 0; + check(fired, "ualarm fired SIGALRM within 5 s"); + ualarm(0, 0); /* cancel the timer */ +} + +/* 11. siginterrupt toggles SA_RESTART on the current action. */ +static void +scenario_siginterrupt(void) +{ + struct sigaction cur; + + check(siginterrupt(SIGUSR1, 1) == 0, "siginterrupt(sig, 1) succeeds"); + check(sigaction(SIGUSR1, NULL, &cur) == 0 && (cur.sa_flags & SA_RESTART) == 0, + "siginterrupt(sig, 1) cleared SA_RESTART"); + check(siginterrupt(SIGUSR1, 0) == 0, "siginterrupt(sig, 0) succeeds"); + check(sigaction(SIGUSR1, NULL, &cur) == 0 && (cur.sa_flags & SA_RESTART) != 0, + "siginterrupt(sig, 0) restored SA_RESTART"); +} +#endif /* VLIBC_LEVEL_GE(2) */ + +/* The failure scenarios, run alone under -f (writes errno; see top). */ +static int +failure_scenarios(void) +{ + struct sigaction act; + sigset_t set; + unsigned long saved = tcb_slot1(); + + act.sa_handler = SIG_DFL; + sigemptyset(&act.sa_mask); + act.sa_flags = 0; + check(sigaction(-1, &act, NULL) == -1, "-f: sigaction(-1, ...) returns -1"); + check(sigaction(0, &act, NULL) == -1, "-f: sigaction(0, ...) returns -1"); + check(sigaction(SIGKILL, &act, NULL) == -1, "-f: sigaction(SIGKILL, catch) returns -1"); + sigemptyset(&set); + check(sigprocmask(99, &set, NULL) == -1, "-f: sigprocmask(invalid how) returns -1"); + check(sigaddset(&set, 0) == -1, "-f: sigaddset(sig 0) returns -1"); + check(sigaddset(&set, NSIG) == -1, "-f: sigaddset(sig NSIG) returns -1"); + check(sigismember(&set, 0) == -1, "-f: sigismember(sig 0) returns -1"); + check(killpg(-5, SIGUSR1) == -1, "-f: killpg(negative pgrp) returns -1"); + + tcb_slot1_set(saved); + return failures; +} + +int +main(int argc, char **argv) +{ + if (argc == 2 && argv[1][0] == '-' && argv[1][1] == 'f') + { + /* + * The failure scenarios write errno inside the library; under the + * host libc that slot is glibc's private TLS state, so leave via + * the raw syscall without running host cleanup. + */ + int rc = failure_scenarios(); + + __syscall1(SYS_exit_group, rc); + return rc; /* not reached */ + } + + scenario_sigaction_raise(); + scenario_sigprocmask_pending(); + scenario_sigwait(); + scenario_sigqueue(); + scenario_alarm_pause(); + scenario_signal_fn(); + scenario_abort_child(); + scenario_kill_child(); +#if VLIBC_LEVEL_GE(2) + scenario_sigaltstack(); + scenario_ualarm(); + scenario_siginterrupt(); + psignal(SIGUSR1, "t28 psignal"); + psiginfo(NULL, "t28 psiginfo(NULL)"); +#endif + + if (failures > 0) + { + say(2, "FAILED ("); + say(2, "some checks failed"); + say(2, ")\n"); + return 1; + } + say(1, "all signal tests passed\n"); + return 0; +}