//
// Syd: rock-solid unikernel
// src/syd-ls.rs: Print the names of the system calls which belong to the given set and exit
//                  If set is prctl, print the list of allowed prctl options
//
// Copyright (c) 2024 Ali Polatel <alip@chesswob.org>
//
// SPDX-License-Identifier: GPL-3.0-or-later

use std::{ffi::OsStr, os::unix::ffi::OsStrExt, path::Path, process::ExitCode};

use libseccomp::ScmpSyscall;
use rustc_hash::FxHashSet;
use syd::{err::SydResult, fs::mask_path};

// 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 mut args = std::env::args();

    match args.nth(1).as_deref() {
        None | Some("-h") => {
            println!("Usage: syd-ls set");
            println!("Print the names of the system calls which belong to the given set and exit.");
            println!(
                "Available sets are cpu, dead, deny, hook, keyring, noop, ptrace, pkey, safe, setid, time, and uring."
            );
            println!("If set is drop, print the list of capabilities that are dropped at startup.");
            println!("If set is env, print the list of unsafe environment variables.");
            println!("If set is prctl, print the list of allowed prctl options.");
        }
        Some("dead") => {
            for name in syd::config::DEAD_SYSCALLS {
                println!("{name}");
            }
        }
        Some("deny") => {
            let mut syscall_set: FxHashSet<_> = syd::config::SAFE_SYSCALLS
                .iter()
                .map(|&s| String::from(s))
                .collect();
            for syscall in syd::config::HOOK_SYSCALLS {
                syscall_set.insert(syscall.to_string());
            }
            let mut list = vec![];
            for syscall_number in 0..=600 {
                let syscall = ScmpSyscall::from(syscall_number);
                if let Ok(name) = syscall.get_name() {
                    if !syscall_set.contains(&name) {
                        list.push(name);
                    }
                }
            }
            list.sort_unstable();
            for name in list {
                println!("{name}");
            }
        }
        Some("cpu") => {
            for name in syd::config::CPU_SYSCALLS {
                println!("{name}");
            }
        }
        Some("hook") => {
            for name in syd::config::HOOK_SYSCALLS {
                println!("{name}");
            }
        }
        Some("keyring") => {
            for name in syd::config::KEYRING_SYSCALLS {
                println!("{name}");
            }
        }
        Some("noop") => {
            for name in syd::config::NOOP_SYSCALLS {
                println!("{name}");
            }
        }
        Some("perf") => {
            for name in syd::config::PERF_SYSCALLS {
                println!("{name}");
            }
        }
        Some("pkey") => {
            for name in syd::config::PKEY_SYSCALLS {
                println!("{name}");
            }
        }
        Some("ptrace") => {
            for name in syd::config::PTRACE_SYSCALLS {
                println!("{name}");
            }
        }
        Some("safe") | Some("allow") => {
            for name in syd::config::SAFE_SYSCALLS {
                println!("{name}");
            }
        }
        Some("setid") => {
            for name in syd::config::SET_ID_SYSCALLS {
                println!("{name}");
            }
        }
        Some("time") => {
            for name in syd::config::TIME_SYSCALLS {
                println!("{name}");
            }
        }
        Some("uring") => {
            for name in syd::config::IOURING_SYSCALLS {
                println!("{name}");
            }
        }
        Some("drop") => {
            for cap in syd::config::CAPS_DROP {
                println!("{cap}");
            }
        }
        Some("env") => {
            for env in syd::config::UNSAFE_ENV {
                let env = mask_path(Path::new(OsStr::from_bytes(env)));
                println!("{env}");
            }
        }
        Some("prctl") => {
            for (_, name) in syd::config::ALLOWLIST_PRCTL {
                println!("{name}");
            }
        }
        Some(set) => {
            eprintln!("No such set: '{set}'");
            return Ok(ExitCode::FAILURE);
        }
    }

    Ok(ExitCode::SUCCESS)
}
