//! sigtest -- checks signals: handlers and sigreturn, masks, default //! actions, SIGCHLD, stop/continue, SIGPIPE and interrupted system calls. //! //! A signal handler is a normal function that the kernel makes the program //! "call" at a moment it didn't choose: it saves the interrupted registers //! in a frame on the user stack, runs the handler, and when the handler //! returns (into ulib's `__restore_rt`), rt_sigreturn puts everything back. #![no_std] #![no_main] #[macro_use] #[path = "ulib.rs"] mod ulib; use core::sync::atomic::{AtomicI64, Ordering::SeqCst}; use ulib::*; static HITS: AtomicI64 = AtomicI64::new(0); static LAST_SIG: AtomicI64 = AtomicI64::new(0); static MASK_IN_HANDLER: AtomicI64 = AtomicI64::new(0); static CHLD_PID: AtomicI64 = AtomicI64::new(0); static CHLD_STATUS: AtomicI64 = AtomicI64::new(0); static CHLD_CODE: AtomicI64 = AtomicI64::new(0); extern "C" fn on_signal(sig: i32) { HITS.fetch_add(1, SeqCst); LAST_SIG.store(sig as i64, SeqCst); // While a handler runs, its own signal is blocked. MASK_IN_HANDLER.store(sigprocmask(SIG_BLOCK, 0), SeqCst); } extern "C" fn on_child(_sig: i32, info: *const SigInfo, _ctx: *mut u8) { let info = unsafe { &*info }; CHLD_PID.store(info.addr_or_pid as u32 as i64, SeqCst); CHLD_STATUS.store(info.status as i64, SeqCst); CHLD_CODE.store(info.code as i64, SeqCst); } /// Busy work that keeps values in registers across a signal: if sigreturn /// restored anything wrong, the sum comes out different. #[inline(never)] fn compute_with_signal(sig: i32) -> u64 { let mut x = core::hint::black_box(0x1234_5678_u64); let mut y = core::hint::black_box(0x0bad_cafe_u64); for i in 0..1000u64 { if i == 500 { kill(getpid(), sig); // the handler runs before kill returns } x = x.wrapping_mul(31).wrapping_add(i); y ^= x.rotate_left(7); } x ^ y } /// Fork a child that runs `f` and return its wait4 status. fn in_child(f: fn()) -> i32 { let pid = fork(); if pid == 0 { f(); exit(0); } wait4(pid, 0).map(|(_, s)| s).unwrap_or(-1) } fn main() -> i64 { let mut c = Checks::new("sigtest"); // 1. A handler round trip, with every register restored. sigaction_raw(SIGUSR2, SIG_IGN, 0, 0); let without = compute_with_signal(SIGUSR2); // ignored: nothing happens c.check(HITS.load(SeqCst) == 0, "SIG_IGN: an ignored signal does nothing"); c.check(signal(SIGUSR1, on_signal) == 0, "rt_sigaction installs a handler"); let with = compute_with_signal(SIGUSR1); c.check(HITS.load(SeqCst) == 1 && LAST_SIG.load(SeqCst) == SIGUSR1 as i64, "kill(getpid(), SIGUSR1) runs the handler once, with sig = 10"); c.check(with == without, "after rt_sigreturn the interrupted code computes the same result"); c.check(MASK_IN_HANDLER.load(SeqCst) as u64 & sigbit(SIGUSR1) != 0, "the signal is blocked while its handler runs"); c.check(sigprocmask(SIG_BLOCK, 0) as u64 & sigbit(SIGUSR1) == 0, "... and unblocked again after sigreturn"); // 2. Blocking: a blocked signal waits (pending) until unblocked. sigprocmask(SIG_BLOCK, sigbit(SIGUSR1)); kill(getpid(), SIGUSR1); c.check(HITS.load(SeqCst) == 1, "blocked: kill doesn't run the handler yet"); sigprocmask(SIG_UNBLOCK, sigbit(SIGUSR1)); c.check(HITS.load(SeqCst) == 2, "unblocking delivers the pending signal"); c.check(sigaction_raw(SIGKILL, SIG_IGN, 0, 0) == -22, "SIGKILL can't be caught or ignored (EINVAL)"); // 3. Default actions, seen through wait4's status. let s = in_child(|| { kill(getpid(), SIGTERM); }); c.check(wifsignaled(s) && wtermsig(s) == SIGTERM, "SIGTERM's default action terminates"); let s = in_child(|| { kill(getpid(), SIGQUIT); }); c.check(wifsignaled(s) && wtermsig(s) == SIGQUIT, "SIGQUIT (\"core\") terminates too (no core files here)"); let s = in_child(|| { kill(getpid(), SIGCHLD); // default: ignore kill(getpid(), SIGURG_NUM); }); c.check(wifexited(s) && wexitstatus(s) == 0, "SIGCHLD and SIGURG are ignored by default"); let s = in_child(|| { // Write to a pipe nobody reads: SIGPIPE. let (r, w) = pipe().unwrap(); close(r); write(w, b"x"); }); c.check(wifsignaled(s) && wtermsig(s) == SIGPIPE, "writing to a pipe without readers: SIGPIPE"); let s = in_child(|| { sigaction_raw(SIGPIPE, SIG_IGN, 0, 0); let (r, w) = pipe().unwrap(); close(r); exit(if write(w, b"x") == -32 { 0 } else { 1 }); }); c.check(wifexited(s) && wexitstatus(s) == 0, "... with SIGPIPE ignored, write fails with EPIPE"); // 4. SIGCHLD: the parent hears when a child ends. sigaction_info(SIGCHLD, on_child, SA_RESTART); let pid = fork(); if pid == 0 { exit(3); } while CHLD_PID.load(SeqCst) == 0 { pause(); } c.check(CHLD_PID.load(SeqCst) == pid && CHLD_STATUS.load(SeqCst) == 3 && CHLD_CODE.load(SeqCst) == 1, "SIGCHLD arrives with si_pid, status 3, CLD_EXITED"); c.check(wait(pid) == 3, "and the child can still be reaped"); sigaction_raw(SIGCHLD, SIG_DFL, 0, 0); // 5. Stop and continue. let pid = fork(); if pid == 0 { loop { sleep_ms(10); } } kill(pid, SIGSTOP); let r = wait4(pid, WUNTRACED); c.check(matches!(r, Ok((p, s)) if p == pid && wifstopped(s) && wstopsig(s) == SIGSTOP), "SIGSTOP stops a child; wait4(WUNTRACED) reports it"); kill(pid, SIGCONT); let r = wait4(pid, WCONTINUED); c.check(matches!(r, Ok((_, s)) if wifcontinued(s)), "SIGCONT continues it; wait4(WCONTINUED) reports that"); kill(pid, SIGKILL); let r = wait4(pid, 0); c.check(matches!(r, Ok((_, s)) if wifsignaled(s) && wtermsig(s) == SIGKILL), "SIGKILL ends it"); // 6. Interrupted system calls: a signal during a blocking read. for restart in [false, true] { sigaction_raw(SIGUSR1, on_signal as usize, if restart { SA_RESTART } else { 0 }, 0); let (r, w) = pipe().unwrap(); let me = getpid(); let pid = fork(); if pid == 0 { sleep_ms(120); kill(me, SIGUSR1); // interrupts the parent's read sleep_ms(120); write(w, b"y"); exit(0); } let hits = HITS.load(SeqCst); let mut b = [0u8; 1]; let n = read(r, &mut b); if restart { c.check(n == 1 && HITS.load(SeqCst) == hits + 1, "SA_RESTART: the handler runs, then read continues and gets the data"); } else { c.check(n == -EINTR && HITS.load(SeqCst) == hits + 1, "no SA_RESTART: read returns EINTR after the handler"); } wait(pid); close(r); close(w); } // 7. kill errors and process groups. c.check(kill(99999, SIGTERM) == -ESRCH, "kill of a pid that doesn't exist: ESRCH"); c.check(kill(getpid(), 99) == -22, "kill with signal 99: EINVAL"); c.check(kill(getpid(), 0) == 0, "signal 0 only checks that the process exists"); let pid = fork(); if pid == 0 { setpgid(0, 0); loop { pause(); } } setpgid(pid, pid); c.check(kill(-pid, SIGTERM) == 0, "kill(-pgid) signals a whole process group"); let r = wait4(pid, 0); c.check(matches!(r, Ok((_, s)) if wtermsig(s) == SIGTERM), "the group's member got it"); c.done() } const SIGURG_NUM: i32 = 23;