//
// Syd: rock-solid application kernel
// src/utils/syd-ldd.rs: Syd's secure ldd(1) wrapper
//
// Copyright (c) 2023, 2024, 2025, 2026 Ali Polatel <alip@chesswob.org>
//
// SPDX-License-Identifier: GPL-3.0

use std::{
    env,
    fs::canonicalize,
    os::unix::process::CommandExt,
    process::{Command, ExitCode, Stdio},
};

use nix::{
    errno::Errno,
    sys::{
        signal::Signal,
        stat::{umask, Mode},
    },
};
use syd::{
    compat::{set_name, set_pdeathsig},
    err::{err2no, SydResult},
    path::XPathBuf,
};

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

/// Resembles the `which` command, finds a program in PATH.
fn which(command: &str, realpath: bool) -> SydResult<XPathBuf> {
    let out = Command::new("which")
        .arg(command)
        .output()
        .map(|o| o.stdout)?;

    if out.is_empty() {
        return Err(Errno::ENOENT.into());
    }

    let bin = String::from_utf8_lossy(&out);
    let bin = bin.trim();

    Ok(if realpath {
        canonicalize(bin).map(XPathBuf::from)?
    } else {
        XPathBuf::from(bin)
    })
}

syd::main! {
    syd::set_sigpipe_dfl()?;

    // Enter debug mode if SYD_LDD_DEBUG is set:
    // 1. Print command line to be executed.
    // 2. Pass Syd the argument `-pdebug'.
    let opt_debug = env::var_os("SYD_LDD_DEBUG").is_some();

    // Determine Syd path.
    let syd = if which("syd", false).is_ok() {
        "syd"
    } else {
        eprintln!("Syd not found in PATH!");
        return Ok(ExitCode::from(Errno::ENOENT as i32 as u8));
    };

    // Gather path arguments and canonicalize to allow for sandboxing.
    let argv: Vec<String> = std::env::args().skip(1).collect();
    let list: Vec<String> = argv
        .clone()
        .into_iter()
        .filter(|arg| !arg.starts_with('-'))
        .map(|arg| canonicalize(&arg).map(|p| {
            let dst = XPathBuf::from(p);
            format!("-mallow/lpath,read,exec+{dst}")
        }))
        .collect::<Result<_, _>>()?;

    // Extend landlock(7) sandboxing for custom paths.
    let mut lock = Vec::new();
    for item in &list {
        // lpath is not defined for landlock(7).
        lock.push(item.replacen("-mallow/lpath,", "-mallow/lock/", 1));
    }

    // Create Command to execute.
    let mut cmd = Command::new(syd);

    // SAFETY: Close unneeded standard input.
    // This disables PTY sandboxing.
    cmd.stdin(Stdio::null());
    cmd.stdout(Stdio::inherit());
    cmd.stderr(Stdio::inherit());

    // SAFETY:
    // 1. Set parent death signal to SIGKILL.
    // 2. Set umask(2) to a sane value.
    unsafe {
        cmd.pre_exec(|| {
            let _ = set_name(c"syd_ldd");
            set_pdeathsig(Some(Signal::SIGKILL))?;
            umask(Mode::from_bits_truncate(0o777));
            Ok(())
        })
    };

    // Prepare command line arguments.
    cmd.arg("-pldd");
    cmd.arg("-plinux");
    cmd.arg("-plandlock");
    cmd.arg("-prand");
    cmd.arg("-pnomagic");
    cmd.arg("-pnopie");
    if !opt_debug {
        cmd.arg("-mlog/level:error");
    } else {
        cmd.arg("-pdebug");
    }
    cmd.args(list);
    cmd.args(lock);
    cmd.args(["--", "ldd"]);
    cmd.args(&argv);

    // Print command line to be executed if SYD_LDD_DEBUG is set.
    if opt_debug {
        eprintln!("+ {cmd:?}");
    }

    // Execute ldd(1) under Syd.
    let error = cmd.exec();
    Ok(ExitCode::from(err2no(&error) as i32 as u8))
}
