mxsh 0.1.0

Embeddable POSIX-style shell parser and runtime
Documentation
use std::sync::atomic::{AtomicUsize, Ordering};

use super::*;

const MAX_TRAP_SIG: usize = 128;
static PENDING_TRAPS: [AtomicUsize; MAX_TRAP_SIG] = [const { AtomicUsize::new(0) }; MAX_TRAP_SIG];

pub(super) extern "C" fn trap_signal_handler(sig: i32) {
    if sig > 0 && (sig as usize) < MAX_TRAP_SIG {
        PENDING_TRAPS[sig as usize].fetch_add(1, Ordering::Relaxed);
    }
}

pub(super) fn clear_pending_trap(sig: i32) {
    if sig > 0 && (sig as usize) < MAX_TRAP_SIG {
        PENDING_TRAPS[sig as usize].store(0, Ordering::Relaxed);
    }
}

#[cfg(any(
    feature = "frontend",
    all(test, feature = "test-support", feature = "unix-runtime")
))]
pub(crate) fn clear_all_pending_traps() {
    for pending in PENDING_TRAPS.iter().skip(1) {
        pending.store(0, Ordering::Relaxed);
    }
}

pub(super) fn run_pending_traps<R: Runtime>(state: &mut ShellState, runtime: &mut R) {
    let last_status = state.last_status;
    for (sig, pending_slot) in PENDING_TRAPS.iter().enumerate().skip(1) {
        let Some(action) = state.traps.get(&(sig as i32)).cloned() else {
            continue;
        };
        let pending = pending_slot.swap(0, Ordering::Relaxed);
        if pending == 0 {
            continue;
        }
        if action.is_empty() {
            continue;
        }
        for _ in 0..pending {
            let _ = run_string(state, runtime, &action);
        }
        state.set_last_status(last_status);
    }
}

#[cfg(any(
    feature = "frontend",
    all(test, feature = "test-support", feature = "unix-runtime")
))]
pub(super) fn install_restored_traps(state: &mut ShellState) {
    if !state.manage_signals {
        return;
    }
    for (sig, action) in state.traps.clone() {
        if sig <= 0 {
            continue;
        }
        if action.is_empty() {
            state
                .trap_signals
                .handlers
                .entry(sig)
                .or_insert_with(|| unsafe { libc::signal(sig, libc::SIG_IGN) });
            continue;
        }
        state
            .trap_signals
            .handlers
            .entry(sig)
            .or_insert_with(|| unsafe {
                libc::signal(sig, trap_signal_handler as *const () as libc::sighandler_t)
            });
        unsafe {
            libc::signal(sig, trap_signal_handler as *const () as libc::sighandler_t);
        }
    }
}

pub(super) fn builtin_trap(state: &mut ShellState, args: &[String]) -> i32 {
    if args.is_empty() {
        let mut entries: Vec<(i32, String)> = state
            .traps
            .iter()
            .map(|(sig, action)| (*sig, action.clone()))
            .collect();
        entries.sort_by_key(|(sig, _)| *sig);
        for (sig, action) in entries {
            shell_outln(
                state,
                &format!(
                    "trap -- '{}' {}",
                    action.replace('\'', "'\\''"),
                    trap_signal_name(sig)
                ),
            );
        }
        return 0;
    }

    if args.len() == 1 {
        let sig = match parse_trap_signal(&args[0]) {
            Some(sig) => sig,
            None => {
                shell_errln(
                    state,
                    &format!("trap: {}: invalid signal specification", args[0]),
                );
                return 1;
            }
        };
        if let Some(action) = state.traps.get(&sig) {
            shell_outln(
                state,
                &format!(
                    "trap -- '{}' {}",
                    action.replace('\'', "'\\''"),
                    trap_signal_name(sig)
                ),
            );
        }
        return 0;
    }

    let action = &args[0];
    for sig_name in &args[1..] {
        let sig = match parse_trap_signal(sig_name) {
            Some(sig) => sig,
            None => {
                shell_errln(
                    state,
                    &format!("trap: {sig_name}: invalid signal specification"),
                );
                return 1;
            }
        };
        if sig == libc::SIGKILL || sig == libc::SIGSTOP {
            shell_errln(
                state,
                "trap: setting a trap for SIGKILL or SIGSTOP produces undefined results",
            );
            return 1;
        }
        if action == "-" {
            state.traps.remove(&sig);
            clear_pending_trap(sig);
            if sig > 0 && state.manage_signals {
                let handler = state
                    .trap_signals
                    .handlers
                    .remove(&sig)
                    .unwrap_or(libc::SIG_DFL);
                unsafe { libc::signal(sig, handler) };
            }
        } else if action.is_empty() {
            state.traps.insert(sig, action.clone());
            clear_pending_trap(sig);
            if sig > 0 && state.manage_signals {
                state
                    .trap_signals
                    .handlers
                    .entry(sig)
                    .or_insert_with(|| unsafe { libc::signal(sig, libc::SIG_IGN) });
            }
        } else {
            state.traps.insert(sig, action.clone());
            clear_pending_trap(sig);
            if sig > 0 && state.manage_signals {
                state
                    .trap_signals
                    .handlers
                    .entry(sig)
                    .or_insert_with(|| unsafe {
                        libc::signal(sig, trap_signal_handler as *const () as libc::sighandler_t)
                    });
                unsafe {
                    libc::signal(sig, trap_signal_handler as *const () as libc::sighandler_t)
                };
            }
        }
    }
    0
}

pub(super) fn parse_trap_signal(sig: &str) -> Option<i32> {
    if sig == "0" || sig == "EXIT" {
        return Some(TRAP_EXIT);
    }
    if let Ok(num) = sig.parse::<i32>() {
        return Some(num);
    }
    let upper = sig.strip_prefix("SIG").unwrap_or(sig).to_ascii_uppercase();
    match upper.as_str() {
        "HUP" => Some(libc::SIGHUP),
        "INT" => Some(libc::SIGINT),
        "QUIT" => Some(libc::SIGQUIT),
        "ABRT" => Some(libc::SIGABRT),
        "ALRM" => Some(libc::SIGALRM),
        "TERM" => Some(libc::SIGTERM),
        "CHLD" => Some(libc::SIGCHLD),
        "CONT" => Some(libc::SIGCONT),
        "FPE" => Some(libc::SIGFPE),
        "ILL" => Some(libc::SIGILL),
        "KILL" => Some(libc::SIGKILL),
        "PIPE" => Some(libc::SIGPIPE),
        "SEGV" => Some(libc::SIGSEGV),
        "STOP" => Some(libc::SIGSTOP),
        "TSTP" => Some(libc::SIGTSTP),
        "TTIN" => Some(libc::SIGTTIN),
        "TTOU" => Some(libc::SIGTTOU),
        "USR1" => Some(libc::SIGUSR1),
        "USR2" => Some(libc::SIGUSR2),
        "TRAP" => Some(libc::SIGTRAP),
        "URG" => Some(libc::SIGURG),
        "XCPU" => Some(libc::SIGXCPU),
        "XFSZ" => Some(libc::SIGXFSZ),
        #[cfg(any(target_os = "linux", target_os = "android", target_os = "macos"))]
        "BUS" => Some(libc::SIGBUS),
        #[cfg(any(target_os = "linux", target_os = "android", target_os = "macos"))]
        "PROF" => Some(libc::SIGPROF),
        #[cfg(any(target_os = "linux", target_os = "android", target_os = "macos"))]
        "SYS" => Some(libc::SIGSYS),
        #[cfg(any(target_os = "linux", target_os = "android", target_os = "macos"))]
        "VTALRM" => Some(libc::SIGVTALRM),
        _ => None,
    }
}

pub(super) fn trap_signal_name(sig: i32) -> &'static str {
    match sig {
        TRAP_EXIT => "EXIT",
        libc::SIGABRT => "ABRT",
        libc::SIGALRM => "ALRM",
        #[cfg(any(target_os = "linux", target_os = "android", target_os = "macos"))]
        libc::SIGBUS => "BUS",
        libc::SIGCHLD => "CHLD",
        libc::SIGCONT => "CONT",
        libc::SIGFPE => "FPE",
        libc::SIGHUP => "HUP",
        libc::SIGILL => "ILL",
        libc::SIGINT => "INT",
        libc::SIGKILL => "KILL",
        libc::SIGPIPE => "PIPE",
        #[cfg(any(target_os = "linux", target_os = "android", target_os = "macos"))]
        libc::SIGPROF => "PROF",
        libc::SIGQUIT => "QUIT",
        libc::SIGSEGV => "SEGV",
        libc::SIGSTOP => "STOP",
        libc::SIGTERM => "TERM",
        #[cfg(any(target_os = "linux", target_os = "android", target_os = "macos"))]
        libc::SIGSYS => "SYS",
        libc::SIGTRAP => "TRAP",
        libc::SIGTSTP => "TSTP",
        libc::SIGTTIN => "TTIN",
        libc::SIGTTOU => "TTOU",
        libc::SIGURG => "URG",
        libc::SIGUSR1 => "USR1",
        libc::SIGUSR2 => "USR2",
        #[cfg(any(target_os = "linux", target_os = "android", target_os = "macos"))]
        libc::SIGVTALRM => "VTALRM",
        libc::SIGXCPU => "XCPU",
        libc::SIGXFSZ => "XFSZ",
        _ => "?",
    }
}

#[cfg(any(
    feature = "frontend",
    all(test, feature = "test-support", feature = "unix-runtime")
))]
pub(super) fn run_exit_trap<R: Runtime>(state: &mut ShellState, runtime: &mut R) {
    let Some(action) = state.traps.remove(&TRAP_EXIT) else {
        return;
    };
    let _ = run_string(state, runtime, &action);
}

#[cfg(any(
    feature = "frontend",
    all(test, feature = "test-support", feature = "unix-runtime")
))]
pub(super) fn restore_trap_signals(state: &mut ShellState) {
    if !state.manage_signals {
        return;
    }
    for (sig, handler) in std::mem::take(&mut state.trap_signals.handlers) {
        if sig > 0 {
            unsafe { libc::signal(sig, handler) };
        }
    }
    clear_all_pending_traps();
}