starry-signal 0.8.2

Signal management library for Starry OS
use ax_cpu::uspace::UserContext;
use linux_raw_sys::general::{SS_DISABLE, SS_ONSTACK};
use starry_signal::{
    SignalActionFlags, SignalDisposition, SignalInfo, SignalOSAction, SignalSet, SignalStack, Signo,
};

mod common;
use common::*;

#[test]
fn dequeue_signal() {
    let (proc, thr) = new_test_env();

    let sig1 = SignalInfo::new_user(Signo::SIGINT, 9, 9, 0);
    assert!(thr.send_signal(sig1));

    let sig2 = SignalInfo::new_user(Signo::SIGTERM, 9, 9, 0);
    assert_eq!(proc.send_signal(sig2), Some(TID));

    let mask = !SignalSet::default();
    assert_eq!(thr.dequeue_signal(&mask).unwrap().signo(), Signo::SIGINT);
    assert_eq!(thr.dequeue_signal(&mask).unwrap().signo(), Signo::SIGTERM);
    assert!(thr.dequeue_signal(&mask).is_none());
}

#[test]
fn handle_signal() {
    let (proc, thr) = new_test_env();

    let signo = Signo::SIGTERM;
    let sig = SignalInfo::new_user(signo, 9, 9, 0);

    unsafe extern "C" fn test_handler(_: i32) {}
    proc.actions().lock()[signo].disposition = SignalDisposition::Handler(test_handler);

    let initial = UserContext::new(0, initial_sp().into(), 0);

    let mut uctx = initial;
    assert!(thr.send_signal(sig.clone()));
    let (_si, result) = thr.check_signals(&mut uctx, None).unwrap();

    assert_eq!(result, SignalOSAction::NoFurtherAction);
    assert_eq!(uctx.ip(), test_handler as *const () as usize);
    assert!(uctx.sp() < initial.sp());
    assert_eq!(uctx.arg0(), signo as usize);
}

#[test]
fn block_ignore_send_signal() {
    let (proc, thr) = new_test_env();

    let signo = Signo::SIGINT;
    let sig = SignalInfo::new_user(signo, 0, 1, 0);
    assert!(thr.send_signal(sig.clone()));
    assert_eq!(
        thr.dequeue_signal(&!SignalSet::default()).unwrap().signo(),
        sig.signo()
    );

    proc.actions().lock()[signo].disposition = SignalDisposition::Ignore;
    assert!(!thr.send_signal(sig.clone()));
    assert!(!thr.pending().has(signo));

    // When a signal is both blocked AND SIG_IGN, POSIX requires it to be
    // queued as pending so that sigtimedwait()/sigwaitinfo() can consume it.
    let mut set = SignalSet::default();
    set.add(signo);
    thr.set_blocked(set);
    assert!(thr.signal_blocked(signo));
    assert!(!thr.send_signal(sig.clone()));
    assert!(thr.pending().has(signo));

    // Drain the pending signal before testing the next disposition change.
    assert_eq!(
        thr.dequeue_signal(&!SignalSet::default()).unwrap().signo(),
        signo
    );

    proc.actions().lock()[signo].disposition = SignalDisposition::Default;
    assert!(!thr.send_signal(sig.clone()));
    assert!(thr.pending().has(signo));

    let empty = SignalSet::default();
    thr.set_blocked(empty);
    assert!(!thr.signal_blocked(signo));
}

#[test]
fn check_signals() {
    let (proc, thr) = new_test_env();

    let mut uctx = UserContext::new(0, initial_sp().into(), 0);

    let signo = Signo::SIGTERM;
    let sig = SignalInfo::new_user(signo, 0, 1, 0);

    assert_eq!(proc.send_signal(sig.clone()), Some(TID));
    let (si, _os_action) = thr.check_signals(&mut uctx, None).unwrap();
    assert_eq!(si.signo(), signo);

    assert!(thr.send_signal(sig.clone()));
    let (si, _os_action) = thr.check_signals(&mut uctx, None).unwrap();
    assert_eq!(si.signo(), signo);
}

#[test]
fn check_signals_with_reports_restartable_delivery() {
    let (proc, thr) = new_test_env();

    let mut uctx = UserContext::new(0, initial_sp().into(), 0);
    let signo = Signo::SIGTERM;
    let sig = SignalInfo::new_user(signo, 0, 1, 0);
    unsafe extern "C" fn test_handler(_: i32) {}

    {
        let actions_arc = proc.actions();
        let mut actions = actions_arc.lock();
        actions[signo].disposition = SignalDisposition::Handler(test_handler);
        actions[signo].flags = SignalActionFlags::RESTART;
    }

    assert!(thr.send_signal(sig));
    let mut observed = None;
    let (si, os_action) = thr
        .check_signals_with(&mut uctx, None, |_, delivered, restartable| {
            observed = Some((delivered.signo(), restartable));
        })
        .unwrap();

    assert_eq!(si.signo(), signo);
    assert_eq!(os_action, SignalOSAction::NoFurtherAction);
    assert_eq!(observed, Some((signo, true)));
}

#[test]
fn restore() {
    let (proc, thr) = new_test_env();

    let signo = Signo::SIGTERM;
    let sig = SignalInfo::new_user(signo, 0, 1, 0);

    unsafe extern "C" fn test_handler(_: i32) {}
    proc.actions().lock()[signo].disposition = SignalDisposition::Handler(test_handler);

    let initial = UserContext::new(0x219, initial_sp().into(), 0);

    let mut uctx = initial;
    assert!(thr.send_signal(sig.clone()));
    let (_si, action) = thr.check_signals(&mut uctx, None).unwrap();
    assert_eq!(action, SignalOSAction::NoFurtherAction);

    prepare_restore_context(&mut uctx);
    thr.restore(&mut uctx).unwrap();

    assert_eq!(uctx.ip(), initial.ip());
    assert_eq!(uctx.sp(), initial.sp());
}

#[test]
fn sigaltstack_reports_active_until_restore() {
    let (proc, thr) = new_test_env();

    let alt_size = 0x4000;
    let alt_top = initial_sp();
    let alt_base = alt_top - alt_size;
    thr.set_stack(SignalStack {
        sp: alt_base,
        flags: 0,
        size: alt_size,
    });

    let signo = Signo::SIGTERM;
    unsafe extern "C" fn test_handler(_: i32) {}
    {
        let actions_arc = proc.actions();
        let mut actions = actions_arc.lock();
        actions[signo].disposition = SignalDisposition::Handler(test_handler);
        actions[signo].flags = SignalActionFlags::ONSTACK;
    }

    let mut uctx = UserContext::new(0x219, initial_sp().into(), 0);
    assert!(thr.send_signal(SignalInfo::new_user(signo, 0, 1, 0)));
    let (_si, action) = thr.check_signals(&mut uctx, None).unwrap();

    assert_eq!(action, SignalOSAction::NoFurtherAction);
    assert!(uctx.sp() >= alt_base);
    assert!(uctx.sp() < alt_top);
    assert_ne!(thr.stack().flags & SS_ONSTACK, 0);

    prepare_restore_context(&mut uctx);
    thr.restore(&mut uctx).unwrap();

    assert_eq!(thr.stack().flags & SS_ONSTACK, 0);
    assert_eq!(thr.stack().flags & SS_DISABLE, 0);
}