//
// Syd: rock-solid application kernel
// src/utils/syd-mdwe.rs: Run a command under Memory-Deny-Write-Execute protections
//
// Copyright (c) 2024, 2025, 2026 Ali Polatel <alip@chesswob.org>
//
// SPDX-License-Identifier: GPL-3.0

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

use std::{
    env,
    ffi::OsString,
    process::{Command, ExitCode},
};

use syd::{
    config::{ENV_SH, SYD_SH},
    confine::{confine_mdwe, confine_scmp_wx_all, run_cmd},
};

// Set global allocator to GrapheneOS allocator.
#[cfg(all(
    not(coverage),
    not(feature = "prof"),
    not(target_os = "android"),
    not(target_arch = "riscv64"),
    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_mdwe = false;
    let mut opt_scmp = false;
    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('m') => opt_mdwe = true,
            Short('s') => opt_scmp = true,
            Value(prog) => {
                opt_cmd = prog;
                opt_arg.extend(parser.raw_args()?);
            }
            _ => return Err(arg.unexpected().into()),
        }
    }

    // PR_SET_MDWE is fatal only if -m is given.
    let opt_mdwe_fatal = opt_mdwe;
    if !opt_mdwe && !opt_scmp {
        // Default is to enable both.
        opt_mdwe = true;
        opt_scmp = true;
    }

    if opt_mdwe {
        if let Err(errno) = confine_mdwe(false) {
            eprintln!("prctl failed to set Memory-Deny-Write-Execute: {errno}!");
            if opt_mdwe_fatal {
                return Err(errno.into());
            }
        }
    }

    if opt_scmp {
        if let Err(error) = confine_scmp_wx_all() {
            eprintln!("seccomp failed to set W^X restrictions: {error}!");
            return Err(error);
        }
    }

    let mut cmd = Command::new(opt_cmd);
    let cmd = cmd.args(opt_arg);
    Ok(ExitCode::from(run_cmd(cmd)))
}

fn help() {
    println!("Usage: syd-mdwe [-hms] {{command [args..]}}");
    println!("Run a command under Memory-Deny-Write-Execute protections.");
    println!("Use -m to enable protections using prctl(2) PR_SET_MDWE (default).");
    println!("Use -s to enable protections using seccomp(2) (use with -m to enable both).");
}
