//
// Syd: rock-solid unikernel
// src/syd-lock.rs: Run a command under Landlock
//
// Copyright (c) 2024 Ali Polatel <alip@chesswob.org>
//
// SPDX-License-Identifier: GPL-3.0-or-later

#![allow(clippy::disallowed_types)]

use std::{
    os::unix::process::CommandExt,
    path::PathBuf,
    process::{Command, ExitCode},
};

use getargs::{Opt, Options};
use syd::{
    err::SydResult,
    landlock::{RulesetStatus, ABI},
};

// Set global allocator to mimalloc.
#[global_allocator]
static GLOBAL: mimalloc2_rust::GlobalMiMalloc = mimalloc2_rust::GlobalMiMalloc;

fn main() -> SydResult<ExitCode> {
    syd::set_sigpipe_dfl()?;

    // Configure mimalloc.
    syd::configure_mimalloc();

    let args = std::env::args().skip(1).collect::<Vec<_>>();
    let args: &'static Vec<String> = Box::leak(Box::new(args));
    let mut opts = Options::new(args.iter().map(String::as_str));
    let mut check = false;
    let mut verbose = false;
    let mut path_ro = vec![];
    let mut path_rw = vec![];

    while let Some(opt) = opts.next_opt()? {
        match opt {
            Opt::Short('h') => {
                help();
                return Ok(ExitCode::SUCCESS);
            }
            Opt::Short('c') => {
                check = true;
            }
            Opt::Short('v') => {
                verbose = true;
            }
            Opt::Short('r') => match opts.value() {
                Ok(path) if !path.starts_with('/') => {
                    eprintln!("-r requires an absolute path as argument!");
                    return Ok(ExitCode::FAILURE);
                }
                Ok(path) => path_ro.push(PathBuf::from(path)),
                Err(_) => {
                    eprintln!("-r requires an argument!");
                    return Ok(ExitCode::FAILURE);
                }
            },
            Opt::Short('w') => match opts.value() {
                Ok(path) if !path.starts_with('/') => {
                    eprintln!("-w requires an absolute path as argument!");
                    return Ok(ExitCode::FAILURE);
                }
                Ok(path) => path_rw.push(PathBuf::from(path)),
                Err(_) => {
                    eprintln!("-w requires an argument!");
                    return Ok(ExitCode::FAILURE);
                }
            },
            Opt::Short(opt) => {
                eprintln!("Unknown option: -{opt}!");
                help();
                return Ok(ExitCode::FAILURE);
            }
            Opt::Long(opt) => {
                eprintln!("Unknown option: --{opt}!");
                help();
                return Ok(ExitCode::FAILURE);
            }
        }
    }

    if check {
        return Ok(ExitCode::from(syd::lock_enabled()));
    }

    // Prepare the command to execute.
    let positionals: Vec<&str> = opts.positionals().collect();
    if positionals.is_empty() {
        help();
        return Ok(ExitCode::FAILURE);
    }

    // Set up Landlock sandbox.
    macro_rules! vprintln {
        ($($arg:tt)*) => {
            if verbose {
                eprintln!($($arg)*);
            }
        };
    }
    match syd::landlock_operation(&path_ro, &path_rw, ABI::V3) {
        Ok(status) => match status.ruleset {
            RulesetStatus::FullyEnforced => vprintln!("syd-lock: Landlock is fully enforced."),
            RulesetStatus::PartiallyEnforced => {
                vprintln!("syd-lock: Landlock is partially enforced.")
            }
            RulesetStatus::NotEnforced => {
                eprintln!("syd-lock: Landlock is not enforced!");
                return Ok(ExitCode::FAILURE);
            }
        },
        Err(error) => {
            eprintln!("syd-lock: Landlock unsupported: {error}");
            return Ok(ExitCode::FAILURE);
        }
    };

    // Execute command.
    #[allow(clippy::disallowed_methods)]
    let (command, args) = positionals.split_first().expect("oops");
    Ok(ExitCode::from(
        127 + Command::new(command)
            .args(args)
            .exec()
            .raw_os_error()
            .unwrap_or(0) as u8,
    ))
}

fn help() {
    println!("Usage: syd-lock [-hcv] [-r path]... [-w path]... {{command [args...]}}");
    println!("Run a command under Landlock.");
    println!("Use -r to specify a read-only path, may be repeated.");
    println!("Use -w to specify a read-write path, may be repeated.");
    println!("Use -c to check for Landlock support, rather than running a command.");
    println!("Use -v to increase verbosity.");
}
