//
// Syd: rock-solid application kernel
// src/utils/syd-pds.rs: Run a command with parent death signal set
//
// Copyright (c) 2024, 2025 Ali Polatel <alip@chesswob.org>
//
// SPDX-License-Identifier: GPL-3.0

use std::{
    env,
    ffi::OsString,
    os::unix::process::CommandExt,
    process::{Command, ExitCode},
    str::FromStr,
};

use nix::{errno::Errno, sys::signal::Signal};
use syd::{compat::set_pdeathsig, config::*};

// 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;

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

    syd::set_sigpipe_dfl()?;

    // Parse CLI options.
    //
    // Note, option parsing is POSIXly correct:
    // POSIX recommends that no more options are parsed after the first
    // positional argument. The other arguments are then all treated as
    // positional arguments.
    // See: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap12.html#tag_12_02
    let mut opt_sig = Signal::SIGKILL;
    let mut opt_cmd = env::var_os(ENV_SH).unwrap_or(OsString::from(SYD_SH));
    let mut opt_arg = Vec::new();

    let mut parser = lexopt::Parser::from_env();
    while let Some(arg) = parser.next()? {
        match arg {
            Short('h') => {
                help();
                return Ok(ExitCode::SUCCESS);
            }
            Short('s') => {
                let sig = parser.value()?;
                opt_sig = match sig.parse::<i32>() {
                    Ok(value) => match Signal::try_from(value) {
                        Ok(value) => value,
                        Err(_) => {
                            eprintln!("syd-pds: Invalid signal specified with -s!");
                            return Err(Errno::EINVAL.into());
                        }
                    },
                    Err(_) => {
                        let mut value = sig.parse::<String>()?.to_ascii_uppercase();
                        if !value.starts_with("SIG") {
                            value = format!("SIG{value}");
                        }
                        match Signal::from_str(&value) {
                            Ok(value) => value,
                            Err(_) => {
                                eprintln!("syd-pds: Invalid signal specified with -s!");
                                return Err(Errno::EINVAL.into());
                            }
                        }
                    }
                };
            }
            Value(prog) => {
                opt_cmd = prog;
                opt_arg.extend(parser.raw_args()?);
            }
            _ => return Err(arg.unexpected().into()),
        }
    }

    // Set parent death signal to the specified signal or SIGKILL.
    set_pdeathsig(Some(opt_sig))?;

    // Execute command, /bin/sh by default.
    //
    // We can not use run_cmd here because if we fork,
    // the parent-death-signal will not work as expected.
    Ok(ExitCode::from(
        127 + Command::new(opt_cmd)
            .args(opt_arg)
            .exec()
            .raw_os_error()
            .unwrap_or(0) as u8,
    ))
}

fn help() {
    println!("Usage: syd-pds [-h] [-s signal] {{command [args...]}}");
    println!("Run a command with parent death signal set.");
    println!("Use -s to specify a signal, defaults to SIGKILL.");
}
