RustForge

foundry

Foundry OS: the operating system built on the Rustbelt kernel

foundry / import / kernel-user / sigtest.rs
7471 bytes ยท raw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
//! 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;