// Syd: rock-solid application kernel
// src/utils/syd-pause.rs: Block forever (until signaled), optionally ignoring selected signals
//
// Copyright (c) 2025, 2026 Ali Polatel <alip@chesswob.org>
// Based in part upon s6-pause from the skarnet s6 suite which is:
//   Copyright (c) 2011-2025 Laurent Bercot <ska-skaware@skarnet.org>
//   SPDX-License-Identifier: ISC
//
// SPDX-License-Identifier: GPL-3.0

// SAFETY: This utility has been liberated from unsafe code!
#![forbid(unsafe_code)]

use std::{iter::once, os::unix::ffi::OsStrExt, process::ExitCode};

use nix::{errno::Errno, sys::signal::Signal, unistd::pause};
use syd::ignore_signal;

// Set global allocator to GrapheneOS allocator.
#[cfg(all(
    not(coverage),
    not(feature = "prof"),
    not(target_os = "android"),
    target_page_size_4k,
    target_pointer_width = "64"
))]
#[global_allocator]
static GLOBAL: hardened_malloc::HardenedMalloc = hardened_malloc::HardenedMalloc;

// Set global allocator to tcmalloc if profiling is enabled.
#[cfg(feature = "prof")]
#[global_allocator]
static GLOBAL: tcmalloc::TCMalloc = tcmalloc::TCMalloc;

const MAX_SIGS: usize = 64;

syd::main! {
    use lexopt::prelude::*;

    // Set SIGPIPE handler to default.
    syd::set_sigpipe_dfl()?;

    // Keep all setup in a tight scope,
    // so everything is dropped before pause().
    {
        // Fixed-size buffer; only entries up to nsig are meaningful.
        let mut sigs: [Option<Signal>; MAX_SIGS] = [None; MAX_SIGS];
        let mut nsig: usize = 0;

        let mut parser = lexopt::Parser::from_env();
        while let Some(arg) = parser.next()? {
            match arg {
                Long("--help") => {
                    help();
                    return Ok(ExitCode::SUCCESS);
                }
                Short('t') => push_sig(&mut sigs, &mut nsig, Signal::SIGTERM)?,
                Short('h') => push_sig(&mut sigs, &mut nsig, Signal::SIGHUP)?,
                Short('a') => push_sig(&mut sigs, &mut nsig, Signal::SIGALRM)?,
                Short('q') => push_sig(&mut sigs, &mut nsig, Signal::SIGQUIT)?,
                Short('b') => push_sig(&mut sigs, &mut nsig, Signal::SIGABRT)?,
                Short('i') => push_sig(&mut sigs, &mut nsig, Signal::SIGINT)?,
                Short('p') => parse_siglist(parser.value()?.as_bytes(), &mut sigs, &mut nsig)?,
                _ => return Err(arg.unexpected().into()),
            }
        }

        // Apply ignores (duplicates are harmless).
        for sig in sigs.iter().take(nsig).flatten() {
            ignore_signal(*sig)?;
        }
    }

    // Everything from setup is dropped here;
    // now block until we receive an interrupting signal.
    pause();

    Ok(ExitCode::SUCCESS)
}

fn help() {
    println!("Usage: syd-pause [ -t ] [ -h ] [ -a ] [ -q ] [ -b ] [ -i ] [ -p signal,signal,... ]");
    println!("Block forever (until signaled), optionally ignoring selected signals.");
    println!("Options:");
    println!("  -t         Ignore SIGTERM.");
    println!("  -h         Ignore SIGHUP.");
    println!("  -a         Ignore SIGALRM.");
    println!("  -q         Ignore SIGQUIT.");
    println!("  -b         Ignore SIGABRT.");
    println!("  -i         Ignore SIGINT.");
    println!("  -p signals Ignore a comma-separated list of signal numbers (see signal(7)).");
}

// Push signal into the given buffer.
fn push_sig(
    buf: &mut [Option<Signal>; MAX_SIGS],
    idx: &mut usize,
    sig: Signal,
) -> Result<(), Errno> {
    if *idx >= MAX_SIGS {
        return Err(Errno::EOVERFLOW);
    }
    buf[*idx] = Some(sig);
    *idx += 1;
    Ok(())
}

// Parse comma-separated unsigned decimal integers.
// Empty items and non-digits are rejected.
fn parse_siglist(
    list: &[u8],
    buf: &mut [Option<Signal>; MAX_SIGS],
    idx: &mut usize,
) -> Result<(), Errno> {
    let mut acc: i32 = -1; // -1 means "no digits yet"
    for &b in list.iter().chain(once(&b',')) {
        if b == b',' {
            if acc < 0 {
                return Err(Errno::EINVAL);
            }
            push_sig(buf, idx, Signal::try_from(acc)?)?;
            acc = -1;
        } else if b.is_ascii_digit() {
            let d = (b - b'0') as i32;
            acc = if acc < 0 {
                d
            } else {
                acc.checked_mul(10)
                    .and_then(|v| v.checked_add(d))
                    .ok_or(Errno::EOVERFLOW)?
            };
        } else {
            return Err(Errno::EINVAL);
        }
    }
    Ok(())
}
