//
// Syd: rock-solid application kernel
// src/syd.rs: Main entry point
//
// Copyright (c) 2023, 2024, 2025 Ali Polatel <alip@chesswob.org>
// Proxying code is based in part upon socksns crate which is:
//     Copyright (c) 2020 Steven Engler
//     SPDX-License-Identifier: MIT
//
// SPDX-License-Identifier: GPL-3.0

//! Syd: rock-solid application kernel
//! Main entry point.

// We like clean and simple code with documentation.
#![deny(missing_docs)]
#![deny(clippy::allow_attributes_without_reason)]
#![deny(clippy::arithmetic_side_effects)]
#![deny(clippy::as_ptr_cast_mut)]
#![deny(clippy::as_underscore)]
#![deny(clippy::assertions_on_result_states)]
#![deny(clippy::borrow_as_ptr)]
#![deny(clippy::branches_sharing_code)]
#![deny(clippy::case_sensitive_file_extension_comparisons)]
#![deny(clippy::cast_lossless)]
#![deny(clippy::cast_possible_truncation)]
#![deny(clippy::cast_possible_wrap)]
#![deny(clippy::cast_precision_loss)]
#![deny(clippy::cast_ptr_alignment)]
#![deny(clippy::cast_sign_loss)]
#![deny(clippy::checked_conversions)]
#![deny(clippy::clear_with_drain)]
#![deny(clippy::clone_on_ref_ptr)]
#![deny(clippy::cloned_instead_of_copied)]
#![deny(clippy::cognitive_complexity)]
#![deny(clippy::collection_is_never_read)]
#![deny(clippy::copy_iterator)]
#![deny(clippy::create_dir)]
#![deny(clippy::dbg_macro)]
#![deny(clippy::debug_assert_with_mut_call)]
#![deny(clippy::decimal_literal_representation)]
#![deny(clippy::default_trait_access)]
#![deny(clippy::default_union_representation)]
#![deny(clippy::derive_partial_eq_without_eq)]
#![deny(clippy::doc_link_with_quotes)]
#![deny(clippy::doc_markdown)]
#![deny(clippy::explicit_into_iter_loop)]
#![deny(clippy::explicit_iter_loop)]
#![deny(clippy::fallible_impl_from)]
#![deny(clippy::missing_safety_doc)]
#![deny(clippy::undocumented_unsafe_blocks)]

use std::{
    collections::{HashMap, HashSet},
    env,
    ffi::{CString, OsString},
    fs::{File, OpenOptions},
    io::{BufWriter, Write},
    net::{IpAddr, SocketAddrV4, SocketAddrV6},
    os::{
        fd::{AsRawFd, RawFd},
        unix::{ffi::OsStrExt, fs::OpenOptionsExt, net::UnixStream, process::CommandExt},
    },
    path::Path,
    process::{ExitCode, Stdio},
    str::FromStr,
};

use ahash::RandomState;
use nix::{
    errno::Errno,
    libc::setdomainname,
    sched::{unshare, CloneFlags},
    sys::{
        socket::{bind, socket, AddressFamily, SockFlag, SockType, SockaddrIn, SockaddrIn6},
        wait::{Id, WaitPidFlag},
    },
    time::{clock_gettime, ClockId},
    unistd::{close, fork, getgid, getpid, getuid, sethostname, ForkResult},
    NixPath,
};
use sendfd::SendWithFd;
use syd::{
    bring_up_loopback, caps,
    compat::{waitid, WaitStatus},
    config::*,
    err::{err2no, SydResult},
    error,
    fs::set_cloexec,
    hook::{ExportMode, Supervisor},
    info,
    libseccomp::{ScmpAction, ScmpFilterContext, ScmpSyscall},
    log::log_init,
    nsflags_name,
    path::XPathBuf,
    sandbox::Sandbox,
    scmp_cmp,
    syslog::LogLevel,
    unshare::{GidMap, UidMap},
    warn,
};

#[allow(clippy::cognitive_complexity)]
fn main() -> SydResult<ExitCode> {
    use lexopt::prelude::*;

    // Set SIGPIPE handler to default.
    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 parser = lexopt::Parser::from_env();
    let is_login = parser
        .bin_name()
        .map(|name| name.starts_with('-'))
        .unwrap_or(false);
    let mut is_quick = env::var_os(ENV_QUICK_BOOT).is_some();

    // Handle quick options early before reexecution for convenience.
    if !is_login {
        if let Some(raw) = parser.try_raw_args() {
            if let Some(Some(arg)) = raw.peek().map(|arg| arg.to_str()) {
                match arg {
                    "-h" | "--help" => {
                        help();
                        return Ok(ExitCode::SUCCESS);
                    }
                    "-V" | "--version" => {
                        syd::syd_info();
                        return Ok(ExitCode::SUCCESS);
                    }
                    "--el" => {
                        println!("{SYD_EL}");
                        return Ok(ExitCode::SUCCESS);
                    }
                    "--sh" => {
                        println!("{ESYD_SH}");
                        return Ok(ExitCode::SUCCESS);
                    }
                    "-q" => is_quick = true,
                    _ => {}
                }
            }
        }
    }

    // Guard against CVE-2019-5736:
    // Copy /proc/self/exe in an anonymous fd (created via memfd_create), seal it and re-execute it.
    // See:
    // - https://github.com/opencontainers/runc/commit/0a8e4117e7f715d5fbeef398405813ce8e88558b
    // - https://github.com/lxc/lxc/commit/6400238d08cdf1ca20d49bafb85f4e224348bf9d
    // Note: syd's procfs protections is another layer of defense against this.
    if !is_quick {
        syd::seal::ensure_sealed()?;
    }

    // SYD_PID_FN -> Write PID file.
    if let Some(path) = env::var_os(ENV_PID_FN) {
        let mut pid = itoa::Buffer::new();
        let pid_str = pid.format(getpid().as_raw());

        #[allow(clippy::disallowed_methods)]
        let pid_file = OpenOptions::new()
            .create_new(true)
            .write(true)
            .mode(0o400)
            .open(path)?;
        let mut pid_file = BufWriter::new(pid_file);

        pid_file.write_all(pid_str.as_bytes())?;
    }

    // Ignore all signals except SIG{KILL,STOP,PIPE,CHLD,Core}.
    syd::ignore_signals()?;

    // Initialize logging..
    log_init(
        if is_login {
            LogLevel::Info
        } else {
            LogLevel::Warn
        },
        Some(libc::STDERR_FILENO),
    )?;

    // Parse CLI arguments
    let mut export: Option<ExportMode> = None;
    let mut sandbox: Sandbox = Sandbox::default();
    let mut cmd_arg0: Option<OsString> = None;
    let mut cmd_argv: Vec<OsString> = vec![];
    let mut cmd_envp: HashSet<OsString, RandomState> = HashSet::default();

    // SYD_PROXY_{HOST,PORT} -> proxy/ext/{host,port}
    #[allow(clippy::disallowed_methods)]
    match env::var(ENV_PROXY_HOST) {
        Ok(host) => sandbox
            .config(&format!("proxy/ext/host:{host}"))
            .expect(ENV_PROXY_HOST),
        Err(env::VarError::NotPresent) => {}
        Err(error) => panic!("Invalid UTF-8 in {ENV_PROXY_HOST}: {error}"),
    };
    #[allow(clippy::disallowed_methods)]
    match env::var(ENV_PROXY_PORT) {
        Ok(port) => sandbox
            .config(&format!("proxy/ext/port:{port}"))
            .expect(ENV_PROXY_PORT),
        Err(env::VarError::NotPresent) => {}
        Err(error) => panic!("Invalid UTF-8 in {ENV_PROXY_PORT}: {error}"),
    };

    // Initialize Options.
    let mut user_parse = false;
    let user_done = if is_login
        || parser
            .try_raw_args()
            .map(|raw| raw.peek().is_none())
            .unwrap_or(true)
    {
        sandbox.parse_profile("user")?;
        true
    } else {
        false
    };

    // Local options handled by this function.
    let sh = env::var_os(ENV_SH).unwrap_or(OsString::from(SYD_SH));

    while let Some(arg) = parser.next()? {
        match arg {
            /*
             * Basic options
             */
            Short('h') | Long("help") => {
                help();
                return Ok(ExitCode::SUCCESS);
            }
            Short('V') | Long("version") => {
                syd::syd_info();
                return Ok(ExitCode::SUCCESS);
            }
            Long("el") => {
                println!("{SYD_EL}");
                return Ok(ExitCode::SUCCESS);
            }
            Long("sh") => {
                println!("{ESYD_SH}");
                return Ok(ExitCode::SUCCESS);
            }
            Short('q') => {} // Ignore, must be first!

            /*
             * Sandbox options
             */
            Short('E') => {
                export = Some(
                    parser
                        .value()?
                        .parse::<String>()
                        .map(|arg| ExportMode::from_str(&arg))??,
                );
                sandbox.parse_profile("lib")?;
                if export == Some(ExportMode::PseudoFiltercode) {
                    // This variable makes setup_seccomp_parent print rules.
                    env::set_var("SYD_SECX", "1");
                } else {
                    env::remove_var("SYD_SECX");
                }
            }
            Short('x') => sandbox.parse_profile("trace")?,
            Short('m') => {
                let cmd = parser.value().map(XPathBuf::from)?;
                if sandbox.locked() {
                    eprintln!("Failed to execute magic command `{cmd}': sandbox locked!");
                    return Err(Errno::EBUSY.into());
                } else {
                    sandbox.config(&cmd.to_string())?;
                }
            }
            Short('f') => {
                // Login shell compatibility:
                // Parse user profile as necessary.
                user_parse = true;
            }
            Short('l') | Long("login") => {
                // Login shell compatibility:
                // Parse user profile as necessary.
                user_parse = true;
            }
            Short('c') => {
                // When multiple -c arguments are given,
                // only the first one is honoured and
                // the rest is ignored in consistency
                // with how bash and dash behaves.
                user_parse = true;
                if cmd_argv.is_empty() {
                    cmd_argv.push(sh.clone());
                    cmd_argv.push(OsString::from("-c"));
                    cmd_argv.push(parser.value()?);
                }
            }
            Short('P') => {
                let path = parser.value().map(XPathBuf::from)?;
                if sandbox.locked() {
                    eprintln!("Failed to parse config file `{path}': sandbox locked!");
                    return Err(Errno::EBUSY.into());
                }
                sandbox.parse_config_file(&path)?;
            }
            /* We keep --profile for syd-1 compatibility.
             * It's undocumented. */
            Short('p') | Long("profile") => {
                let profile = parser.value()?.parse::<String>()?;
                if sandbox.locked() {
                    eprintln!("Failed to parse profile `{profile}': sandbox locked!");
                    return Err(Errno::EBUSY.into());
                }
                sandbox.parse_profile(&profile)?;
            }

            /*
             * Unshare options
             */
            Short('a') => cmd_arg0 = Some(parser.value()?),
            Short('e') => {
                let value = parser.value()?.parse::<String>()?;
                match value.split_once('=') {
                    Some((var, val)) => {
                        cmd_envp.insert(OsString::from(var));
                        if !val.is_empty() {
                            // This way we give the user the chance to pass-through
                            // denylisted environment variables e.g.
                            //      syd -eLD_LIBRARY_PATH= cmd
                            // is equivalent to
                            //      syd -eLD_LIBRARY_PATH=$LD_LIBRARY_PATH cmd
                            env::set_var(var, val);
                        }
                    }
                    None => {
                        cmd_envp.remove(&OsString::from(value.clone()));
                        env::remove_var(value);
                    }
                }
            }

            // Profiling options.
            #[cfg(feature = "prof")]
            Long("prof") => match parser().value()?.parse()? {
                "cpu" => env::set_var("SYD_PROF", "cpu"),
                "mem" => env::set_var("SYD_PROF", "mem"),
                val => {
                    eprintln!("Invalid profile mode `{val}'!");
                    eprintln!("Expected exactly one of `cpu' or `mem'!");
                    help();
                    return Ok(ExitCode::FAILURE);
                }
            },
            Value(prog) => {
                cmd_argv.push(prog);
                cmd_argv.extend(parser.raw_args()?);
            }
            _ => return Err(arg.unexpected().into()),
        }
    }

    if user_parse && !user_done && !sandbox.locked() {
        sandbox.parse_profile("user")?;
    }

    // Prepare the command to execute, which may be a login shell.
    let argv0 = if !cmd_argv.is_empty() {
        Some(cmd_argv.remove(0))
    } else {
        None
    };

    let argv0 = match (export.is_some(), argv0, is_login) {
        (true, _, _) => OsString::from("true"),
        (false, Some(argv0), false) => argv0,
        (false, None, false) | (false, _, true) => {
            #[allow(clippy::disallowed_methods)]
            if cmd_arg0.is_none() {
                // Allow user to override with -a.
                cmd_arg0 = Some(match Path::new(&sh).file_name() {
                    None => OsString::from("-"),
                    Some(name) => {
                        let mut p = OsString::from("-");
                        p.push(name);
                        p
                    }
                });
            }
            sh
        }
    };

    // SAFETY: We cannot support NEWPID without NEWNS.
    // ie, pid namespace must have its own private /proc.
    if sandbox.unshare_pid() {
        sandbox.set_unshare_mount(true);
    }

    let proxy_debug = env::var_os("SYD_TOR_DEBUG").is_some();
    let proxy = if sandbox.has_proxy() {
        // sandbox/proxy:on implies unshare/net:1
        sandbox.set_unshare_net(true);

        // Step 1: Create a PIDFd of this process.
        // SAFETY: nix has no wrapper for pidfd_open.
        let pidfd = match unsafe {
            nix::libc::syscall(
                nix::libc::SYS_pidfd_open,
                getpid().as_raw(),
                nix::libc::PIDFD_NONBLOCK,
            )
        } {
            e if e < 0 => return Err(Errno::last().into()),
            fd => fd as RawFd,
        };

        // Step 2: Create a UNIX socket pair.
        let (stream_parent, stream_child) = UnixStream::pair()?;

        // Step 3: Unset the CLOEXEC flags on the file descriptors.
        // PIDFds and Rust sockets are automatically CLOEXEC.
        set_cloexec(&pidfd, false)?;
        let fd = stream_parent.as_raw_fd(); // Borrows FD.
        set_cloexec(&fd, false)?;

        // Step 4: Prepare environment of the syd-tor process.
        // Filter the environment variables to only include the list below:
        // 1. PATH
        // 2. LD_LIBRARY_PATH
        // 3. SYD_TOR_DEBUG
        // 4. SYD_TOR_RULES
        let safe_env: HashMap<_, _, RandomState> = env::vars_os()
            .filter(|(key, _)| {
                matches!(
                    key.as_bytes(),
                    b"PATH" | b"LD_LIBRARY_PATH" | b"SYD_TOR_DEBUG" | b"SYD_TOR_RULES"
                )
            })
            .collect();

        // Step 5: Spawn syd-tor process outside the namespace.
        // Pass one end of the socket-pair to it.
        let mut cmd = std::process::Command::new(
            env::var_os("CARGO_BIN_EXE_syd-tor").unwrap_or(OsString::from("syd-tor")),
        );
        if proxy_debug {
            cmd.arg("-d");
        }
        cmd.arg(format!("-p{pidfd}"));
        cmd.arg(format!("-i{fd}"));
        cmd.arg(format!(
            "-o{}:{}",
            sandbox.proxy_ext_addr, sandbox.proxy_ext_port
        ));
        //cmd.arg(format!("-b{}", PIPE_BUF));
        cmd.env_clear();
        cmd.envs(safe_env);
        cmd.current_dir("/");
        cmd.process_group(0);
        cmd.stdin(Stdio::null());
        cmd.stdout(Stdio::null());
        if !proxy_debug {
            cmd.stderr(Stdio::null());
        }
        cmd.spawn()?;
        close(pidfd)?;
        if proxy_debug {
            warn!("ctx": "spawn_proxy",
                "msg": format!("proxy is now forwarding external traffic to {}!{}",
                    sandbox.proxy_ext_addr, sandbox.proxy_ext_port));
        } else {
            info!("ctx": "spawn_proxy",
                "msg": format!("proxy is now forwarding external traffic to {}!{}",
                    sandbox.proxy_ext_addr, sandbox.proxy_ext_port));
        }

        // Step 4: Pass the other end of the socket-pair
        // to the new namespace.
        Some((stream_child, sandbox.proxy_port))
    } else {
        None
    };

    // Set up Linux namespaces if requested. Note,
    // we set it up here before spawning the child so as to
    // include the Syd process into the pid namespace as well
    // such that the sandbox process and syd have the identical
    // view of /proc.
    let namespaces = sandbox.namespaces();
    if namespaces == 0 {
        // No namespace arguments passed, run normally.
        return match Supervisor::run(sandbox, &argv0, cmd_argv, Some(&cmd_envp), cmd_arg0, export) {
            Ok(code) => Ok(ExitCode::from(code)),
            Err(error) => {
                let errno = Errno::last();
                eprintln!("{error:?}");
                Ok(ExitCode::from(u8::try_from(errno as i32).unwrap_or(127)))
            }
        };
    } else {
        let n = nsflags_name(namespaces);
        let s = if n.len() > 1 { "s" } else { "" };
        let n = n.join(", ");
        info!("ctx": "setup_container",
            "msg": format!("setting up Syd container with {n} namespace{s}"));
    }

    let id_buf = if sandbox.unshare_user() {
        // create the UID and GID mappings.
        let uid = getuid().as_raw();
        let gid = getgid().as_raw();

        let map_root = sandbox.map_root();

        let uid_buf = {
            let uid_maps = vec![
                UidMap {
                    inside_uid: if map_root { 0 } else { uid },
                    outside_uid: uid,
                    count: 1,
                }, // Map the current user.
            ];
            let mut buf = Vec::new();
            for map in uid_maps {
                writeln!(
                    &mut buf,
                    "{} {} {}",
                    map.inside_uid, map.outside_uid, map.count
                )?;
            }
            buf
        };

        let gid_buf = {
            let gid_maps = vec![
                GidMap {
                    inside_gid: if map_root { 0 } else { gid },
                    outside_gid: gid,
                    count: 1,
                }, // Map the current group.
            ];
            let mut buf = Vec::new();
            for map in gid_maps {
                writeln!(
                    &mut buf,
                    "{} {} {}",
                    map.inside_gid, map.outside_gid, map.count
                )?;
            }
            buf
        };
        Some((uid_buf, gid_buf))
    } else {
        None
    };

    // Tell the kernel to keep the capabilities after the unshare call.
    // This is important because unshare() can change the user
    // namespace, which often leads to a loss of capabilities.
    caps::securebits::set_keepcaps(true)?;

    // CLONE_NEWTIME may only be used with unshare(2).
    // CloneFlags don't support CLONE_NEWTIME directly so we use retain.
    unshare(CloneFlags::from_bits_retain(namespaces))?;

    // Write uid/gid map for user namespace.
    #[allow(clippy::disallowed_methods)]
    if let Some((ref uid_buf, ref gid_buf)) = id_buf {
        // Write "deny" to /proc/self/setgroups before writing to gid_map.
        File::create("/proc/self/setgroups").and_then(|mut f| f.write_all(b"deny"))?;
        File::create("/proc/self/gid_map").and_then(|mut f| f.write_all(&gid_buf[..]))?;
        File::create("/proc/self/uid_map").and_then(|mut f| f.write_all(&uid_buf[..]))?;

        // Set inheritable mask and ambient caps to retain caps after execve(2).
        caps::securebits::set_keepcaps(true)?;
        let permitted_caps = caps::read(None, caps::CapSet::Permitted)?;
        caps::set(None, caps::CapSet::Inheritable, &permitted_caps)?;
        // Set the same capabilities as ambient, if necessary.
        for cap in permitted_caps {
            caps::raise(None, caps::CapSet::Ambient, cap)?;
        }
    }

    // Bring up loopback device for net namespace.
    if sandbox.unshare_net() {
        // Set up the loopback interface.
        // Warn on errors and continue.
        match bring_up_loopback() {
            Ok(_) => {
                info!("ctx": "bring_up_loopback",
                    "msg": "loopback network device is now up");
            }
            Err(errno) => {
                error!("ctx": "bring_up_loopback_device",
                    "error": format!("bring up loopback network device error: {errno}"));
            }
        }

        // Handle proxy sandboxing.
        // Warn on errors and continue.
        if let Some((stream_child, proxy_port)) = proxy {
            let proxy_addr = sandbox.proxy_addr;
            let ipv = if proxy_addr.is_ipv6() { 6 } else { 4 };
            let fml = if ipv == 6 {
                AddressFamily::Inet6
            } else {
                AddressFamily::Inet
            };
            let lfd = socket(
                fml,
                SockType::Stream,
                SockFlag::SOCK_NONBLOCK | SockFlag::SOCK_CLOEXEC,
                None,
            )?;
            let ret = match proxy_addr {
                IpAddr::V4(addr_v4) => {
                    let sockaddr = SockaddrIn::from(SocketAddrV4::new(addr_v4, proxy_port));
                    bind(lfd.as_raw_fd(), &sockaddr)
                }
                IpAddr::V6(addr_v6) => {
                    let sockaddr = SockaddrIn6::from(SocketAddrV6::new(addr_v6, proxy_port, 0, 0));
                    bind(lfd.as_raw_fd(), &sockaddr)
                }
            };
            match ret {
                Ok(()) => {
                    if proxy_debug {
                        warn!("ctx": "bind_proxy",
                            "msg": format!("proxy is now listening incoming IPv{ipv} requests from {proxy_addr}!{proxy_port}"));
                    } else {
                        info!("ctx": "bind_proxy",
                            "msg": format!("proxy is now listening incoming IPv{ipv} requests from {proxy_addr}!{proxy_port}"));
                    }
                    let buf = [0u8; 1];
                    let fds = [lfd.as_raw_fd()];
                    match stream_child.send_with_fd(&buf, &fds) {
                        Ok(_) => {
                            if proxy_debug {
                                warn!("ctx": "send_proxy_fd",
                                    "msg": format!("proxy fd {} sent to syd-tor, IPv{ipv} traffic forwarding is now started \\o/",
                                        lfd.as_raw_fd()));
                                warn!("ctx": "send_proxy_fd", "syd": "ping", "msg": "Change return success. Going and coming without error.");
                            } else {
                                info!("ctx": "send_proxy_fd",
                                    "msg": format!("proxy fd {} sent to syd-tor, IPv{ipv} traffic forwarding is now started \\o/",
                                        lfd.as_raw_fd()));
                                info!("ctx": "send_proxy_fd", "syd": "ping", "msg": "Change return success. Going and coming without error.");
                            }
                        }
                        Err(error) => {
                            error!("ctx": "send_proxy_fd",
                                "error": format!("proxy fd {} send to syd-tor error: {}, traffic will not be forwarded",
                                    lfd.as_raw_fd(), err2no(&error)));
                        }
                    }
                }
                Err(errno) => {
                    error!("ctx": "bind_proxy",
                        "error": format!("proxy bind to IPv{ipv} {proxy_addr}!{proxy_port} error: {errno}"));
                }
            }
            drop(stream_child);
        }
    }

    // Set host and domain name for uts namespace.
    // Ignore errors.
    if sandbox.unshare_uts() {
        // SAFETY: Domain name must not contain a nul-byte.
        #[allow(clippy::disallowed_methods)]
        let domainname = CString::new(sandbox.domainname.clone()).unwrap();

        // SAFETY: There's no setdomainname wrapper in nix.
        let _ = unsafe { setdomainname(domainname.as_ptr() as *const _, domainname.len()) };
        let _ = sethostname(&sandbox.hostname);
    }

    if sandbox.unshare_time() {
        #[allow(clippy::disallowed_methods)]
        if let Some(offset) = sandbox.time {
            let mut file = File::create("/proc/self/timens_offsets")?;
            let contents = format!("monotonic {offset} 0\nboottime {offset} 0\n");
            file.write_all(contents.as_bytes())?;
            info!("ctx": "set_boot_time", "msg": "set boot time in time namespace");
        } else if let Ok(boottime) = clock_gettime(ClockId::CLOCK_BOOTTIME) {
            // Set uptime to 0 for time namespace. Ignore errors.
            if let Ok(mut file) = File::create("/proc/self/timens_offsets") {
                #[allow(clippy::arithmetic_side_effects)]
                let contents = format!("boottime {} 0\n", -boottime.tv_sec());
                let _ = file.write_all(contents.as_bytes());
            }
            info!("ctx": "set_boot_time", "msg": "reset boot time in time namespace");
        }
    }

    // SAFETY: fork is our safest option here.
    let child = match unsafe { fork() }? {
        ForkResult::Child => {
            let retval =
                match Supervisor::run(sandbox, &argv0, cmd_argv, Some(&cmd_envp), cmd_arg0, export)
                {
                    Ok(retval) => nix::libc::c_int::from(retval),
                    Err(error) => {
                        let errno = Errno::last();
                        eprintln!("{error:?}");
                        errno as i32
                    }
                };
            // SAFETY: _exit is safe to call in child.
            unsafe { nix::libc::_exit(retval) };
        }
        ForkResult::Parent { child, .. } => {
            // Ensure we release all unneeded resources.
            drop(cmd_arg0);
            drop(cmd_argv);
            drop(cmd_envp);
            drop(sandbox);
            // SAFETY: nix does not have a close_range wrapper yet.
            let _ = unsafe {
                nix::libc::syscall(
                    nix::libc::SYS_close_range,
                    nix::libc::STDERR_FILENO + 1,
                    nix::libc::c_int::MAX,
                    0,
                )
            };
            child
        }
    };

    // Only parent ends up here and `child` is child pid.
    // SAFETY: Set up a Landlock sandbox to disallow all access.
    let abi = syd::landlock::ABI::new_current();
    let _ = syd::landlock_operation(abi, &[], &[], &[], &[], true, true);

    // SAFETY: Set up a seccomp filter which only allows
    // 1. write to standard error.
    // 2. waitid and exit.
    // 3. memory allocation syscalls
    // 4. signal handling syscalls
    let mut filter = ScmpFilterContext::new(ScmpAction::KillProcess)?;
    let allow_call = [
        "exit",
        "exit_group",
        "waitid",
        "brk",
        "madvise",
        "mremap",
        "munmap",
        "sigaction",
        "sigaltstack",
        "sigpending",
        "sigprocmask",
        "sigsuspend",
        "sigreturn",
        "rt_sigaction",
        "rt_sigpending",
        "rt_sigprocmask",
        "rt_sigqueueinfo",
        "rt_sigreturn",
        "rt_sigtimedwait",
        "rt_sigtimedwait_time64",
    ];
    for name in allow_call {
        if let Ok(syscall) = ScmpSyscall::from_name(name) {
            filter.add_rule(ScmpAction::Allow, syscall)?;
        }
    }

    // Allow write to standard error.
    for name in ["write", "writev"] {
        if let Ok(syscall) = ScmpSyscall::from_name(name) {
            filter.add_rule_conditional(
                ScmpAction::Allow,
                syscall,
                &[scmp_cmp!($arg0 == nix::libc::STDERR_FILENO as u64)],
            )?;
        }
    }

    // Prevent executable memory.
    const PROT_EXEC: u64 = nix::libc::PROT_EXEC as u64;
    for name in ["mmap", "mmap2", "mprotect"] {
        #[allow(clippy::disallowed_methods)]
        filter.add_rule_conditional(
            ScmpAction::Allow,
            ScmpSyscall::from_name(name).unwrap(),
            &[scmp_cmp!($arg2 & PROT_EXEC == 0)],
        )?;
    }

    filter.load()?;

    loop {
        #[allow(clippy::cast_possible_truncation)]
        #[allow(clippy::cast_sign_loss)]
        break match waitid(Id::Pid(child), WaitPidFlag::WEXITED) {
            Ok(WaitStatus::Exited(_, code)) =>
            {
                #[allow(clippy::cast_possible_truncation)]
                #[allow(clippy::cast_sign_loss)]
                Ok(ExitCode::from(code as u8))
            }
            Ok(WaitStatus::Signaled(_, signal, _)) => {
                Ok(ExitCode::from(128_u8.saturating_add(signal as u8)))
            }
            Ok(WaitStatus::StillAlive) | Err(Errno::EINTR) => continue,
            Ok(_status) => Err(Errno::EINVAL.into()),
            Err(errno) => Err(errno.into()),
        };
    }
}

fn help() {
    println!("syd [-acefhlmpqxEPV] [--] {{command [arg...]}}");
}
