//
// Syd: rock-solid unikernel
// src/lib.rs: Common utility functions
//
// Copyright (c) 2023, 2024 Ali Polatel <alip@chesswob.org>
// likely and unlikely functions are based on the endorphin crate which is:
//    Copyright (c) 2021 Jun Ryoung Ju (junryoungju@gmail.com)
//    SPDX-License-Identifier: MIT
//
// SPDX-License-Identifier: GPL-3.0-or-later

//! # syd: The ☮ther SⒶndbøx
//!
//! [![Shine On You Crazy Diamond!](https://img.shields.io/badge/Shine%20On%20You%20Crazy%20Diamond!-8A2BE2)](https://en.wikipedia.org/wiki/Syd_Barrett)
//! [![license](https://img.shields.io/crates/l/jja.svg)](https://git.sr.ht/~alip/syd/tree/main/item/COPYING)
//! [![msrv](https://img.shields.io/badge/rustc-1.70%2B-green?style=plastic)](https://blog.rust-lang.org/2023/06/01/Rust-1.70.0.html)
//! [![build status](https://builds.sr.ht/~alip/syd.svg)](https://builds.sr.ht/~alip/syd?)
//! [![maintenance-status](https://img.shields.io/badge/maintenance-actively--developed-brightgreen.svg)](https://git.sr.ht/~alip/syd)
//! [![dependency status](https://deps.rs/repo/sourcehut/~alip/syd/status.svg)](https://deps.rs/repo/sourcehut/~alip/syd)
//! [![repology](https://repology.org/badge/latest-versions/syd.svg)](https://repology.org/project/syd/versions)
//!
//! [![syd](https://git.sr.ht/~alip/syd/blob/main/data/syd.png)](https://todo.sr.ht/~alip/syd)
//! [![GNU](https://web.archive.org/web/20221222061733if_/https://dev.exherbo.org/~alip/images/gnu.png)](https://www.gnu.org/philosophy/philosophy.html)
//! [![Linux](https://chesswob.org/jja/tux.png)](https://www.kernel.org/category/about.html)
//! [![Exherbo](https://web.archive.org/web/20230518155203if_/https://dev.exherbo.org/~alip/images/zebrapig.png)](https://www.exherbolinux.org/docs/gettingstarted.html)
//! [![musl libc](https://www.chesswob.org/jja/musl-inside.png)](https://www.musl-libc.org/)
//! [![libsecc☮mp](https://web.archive.org/web/20221222061720if_/https://dev.exherbo.org/~alip/images/libseccomp.png)](https://github.com/seccomp/libseccomp)
//! [![Paludis](http://paludis.exherbolinux.org/paludis_270.png)](https://paludis.exherbolinux.org)
//!
//! syd is a **seccomp**(2) based sandboxing utility for modern Linux\[\>=5.6\]
//! machines to sandbox unwanted process access to filesystem and network resources.
//! syd requires *no root access* and *no ptrace* rights. All you need is a
//! recent Linux kernel and libsecc☮mp which is available on many different
//! architectures, including **x86**, **x86\_64**, **x32**, **arm**, **aarch64**,
//! **mips**, **mips64**... This makes it very easy for a regular user to use. This is
//! the motto of syd: *bring easy, simple, flexible and powerful access restriction
//! to the Linux user!*
//!
//! The basic idea of syd is to run a command under certain restrictions. These
//! restrictions define which system calls the command is permitted to run and which
//! argument values are permitted for the given system call. The restrictions may be
//! applied via two ways.  *seccomp-bpf* can be used to apply simple Secure Computing
//! user filters to run sandboxing fully on kernel space, and *seccomp-notify*
//! functionality can be used to run sandboxing on kernel space and fallback to user
//! space to dereference pointer arguments of system calls (**See
//! [Security](#security) about `TOCTOU` et. al**), which are one of
//! **[pathname](https://en.wikipedia.org/wiki/Path_(computing))**, **[UNIX socket
//! address](https://en.wikipedia.org/wiki/Unix_domain_socket)**,
//! **[IPv4](https://en.wikipedia.org/wiki/IPv4)** or
//! **[IPv6](https://en.wikipedia.org/wiki/IPv6)** network address, and make dynamic
//! decisions using [Unix shell style patterns](https://docs.rs/globset) such as
//! `allow/write+/home/syd/***`, or `allow/write+/run/user/*/pulse` for
//! **[pathnames](https://en.wikipedia.org/wiki/Path_(computing))**, and using
//! **[CIDR](https://docs.rs/ipnetwork)** notation such as
//! `allow/net/connect+127.0.0.1/8!9050`, or
//! `allow/net/connect+::1/8!9050` for
//! **[IPv4](https://en.wikipedia.org/wiki/IPv4)** and
//! **[IPv6](https://en.wikipedia.org/wiki/IPv6)** addresses and perform an action
//! which is by default denying the system call with an appropriate error, which is
//! usually **access denied**, aka `EACCES`. For default disallowed system calls,
//! such as `ptrace` or `process_vm_writev` (**See [Security](#security) about
//! `TOCTOU` et. al**) syd returns `EACCES` as well.
//!
//! To be able to use syd, you need a recent Linux kernel with the system calls
//! **pidfd_getfd**, **pidfd_send_signal**. The Secure Computing facility of the
//! Linux kernel should support the **SECCOMP_USER_NOTIF_FLAG_CONTINUE** operation.
//! It is recommended to have the **CONFIG_CROSS_MEMORY_ATTACH** kernel option
//! enabled, if this option is not enabled, syd will fallback to reading/writing
//! from `/proc/$pid/mem`. Linux-5.11 or later is recommended.

// We like clean and simple code with documentation.
// Keep in sync with main.rs.
#![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)]

/// Utilities for caching
pub mod cache;
/// Compatibility code for different libcs
#[allow(missing_docs)]
pub mod compat;
/// Static configuration, edit & recompile!
pub mod config;
/// ELF parser
pub mod elf;
/// Error types and error handling code.
pub mod err;
/// Filesystem utilities
pub mod fs;
/// Utilities for hashing
pub mod hash;
/// Secure computing hooks
pub mod hook;
/// Simple logging on standard error using JSON lines
pub mod log;
/// /proc utilities
pub mod proc;
/// Sandbox configuration
pub mod sandbox;
/// Execute program as sealed anonymous file
pub mod seal;
/// Shell-style wildcard matching
#[allow(clippy::arithmetic_side_effects)]
pub mod wildmatch;

// Vendored crates:
/// Interface to Linux capabilities
#[allow(dead_code)]
#[allow(missing_docs)]
#[allow(clippy::arithmetic_side_effects)]
#[allow(clippy::cast_possible_truncation)]
#[allow(clippy::disallowed_types)]
#[allow(clippy::missing_safety_doc)]
#[allow(clippy::undocumented_unsafe_blocks)]
pub mod caps;
/// Interface to LandLock LSM
#[allow(dead_code)]
#[allow(missing_docs)]
#[allow(unused_imports)]
#[allow(clippy::as_underscore)]
#[allow(clippy::cast_possible_truncation)]
#[allow(clippy::decimal_literal_representation)]
#[allow(clippy::disallowed_types)]
#[allow(clippy::missing_safety_doc)]
#[allow(clippy::type_complexity)]
#[allow(clippy::undocumented_unsafe_blocks)]
pub mod landlock;
/// Read the ELF dependency tree
#[allow(dead_code)]
#[allow(clippy::disallowed_methods)]
#[allow(clippy::manual_flatten)]
#[allow(clippy::too_many_arguments)]
#[allow(missing_docs)]
/// rusty_pool: Self growing / shrinking `ThreadPool` implementation
pub(crate) mod pool;
/// The low-level interface for linux namespaces (containers)
pub mod unshare;

#[allow(clippy::disallowed_types)]
use std::{
    arch::asm,
    ffi::OsStr,
    os::fd::{AsRawFd, RawFd},
    path::{Path, PathBuf},
    process::exit,
};

use landlock::{
    path_beneath_rules, Access, AccessFs, RestrictionStatus, Ruleset, RulesetAttr,
    RulesetCreatedAttr, RulesetError,
};
use lexis::ToName;
use libseccomp::{ScmpAction, ScmpArch, ScmpFilterContext, ScmpSyscall, ScmpVersion};
use nix::{
    dir::Dir,
    errno::Errno,
    fcntl::OFlag,
    libc::c_int,
    mount::MsFlags,
    sys::{
        signal::{sigaction, signal, SaFlags, SigAction, SigHandler, SigSet, Signal},
        socket::{socket, AddressFamily, SockFlag, SockType},
        stat::Mode,
        utsname::uname,
        wait::{waitpid, WaitStatus},
    },
    unistd::{fork, ForkResult, Gid, Group, Pid, Uid, User},
};
use serde::Serialize;

use crate::{
    err::SydResult,
    landlock::{RulesetStatus, ABI},
};

/* Data structures */
#[derive(Debug, Eq, PartialEq)]
pub(crate) struct Sydcall(ScmpSyscall, ScmpArch);

impl std::fmt::Display for Sydcall {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self.0.get_name_by_arch(self.1).ok() {
            Some(name) => write!(f, "{name}"),
            None => write!(f, "?"),
        }
    }
}

impl Serialize for Sydcall {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        match self.0.get_name_by_arch(self.1).ok() {
            Some(name) => serializer.serialize_str(&name),
            None => serializer.serialize_none(),
        }
    }
}

impl std::hash::Hash for Sydcall {
    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
        i32::from(self.0).hash(state);
        match self.1 {
            ScmpArch::X86 => 0,
            ScmpArch::X8664 => 1,
            ScmpArch::X32 => 2,
            ScmpArch::Arm => 3,
            ScmpArch::Aarch64 => 4,
            ScmpArch::Mips => 5,
            ScmpArch::Mips64 => 6,
            ScmpArch::Mips64N32 => 7,
            ScmpArch::Mipsel => 8,
            ScmpArch::Mipsel64 => 9,
            ScmpArch::Mipsel64N32 => 10,
            ScmpArch::Ppc => 11,
            ScmpArch::Ppc64 => 12,
            ScmpArch::Ppc64Le => 13,
            ScmpArch::S390 => 14,
            ScmpArch::S390X => 15,
            ScmpArch::Parisc => 16,
            ScmpArch::Parisc64 => 17,
            ScmpArch::Riscv64 => 18,
            _ => u8::MAX,
        }
        .hash(state);
    }
}

pub(crate) struct SydArch(ScmpArch);

impl Serialize for SydArch {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        let arch = format!("{:?}", self.0).to_ascii_lowercase();
        let arch = if arch == { "x8664" } { "x86_64" } else { &arch };
        serializer.serialize_str(arch)
    }
}

// Execution domain constants, taken from sys/personality.h
const PER_LINUX: c_int = 0;
const PER_LINUX_32BIT: c_int = PER_LINUX | ADDR_LIMIT_32BIT;
const PER_LINUX_FDPIC: c_int = PER_LINUX | FDPIC_FUNCPTRS;
const PER_SVR4: c_int = 1 | STICKY_TIMEOUTS | MMAP_PAGE_ZERO;
const PER_SVR3: c_int = 2 | STICKY_TIMEOUTS | SHORT_INODE;
const PER_SCOSVR3: c_int = 3 | STICKY_TIMEOUTS | WHOLE_SECONDS | SHORT_INODE;
const PER_OSR5: c_int = 3 | STICKY_TIMEOUTS | WHOLE_SECONDS;
const PER_WYSEV386: c_int = 4 | STICKY_TIMEOUTS | SHORT_INODE;
const PER_ISCR4: c_int = 5 | STICKY_TIMEOUTS;
const PER_BSD: c_int = 6;
const PER_SUNOS: c_int = PER_BSD | STICKY_TIMEOUTS;
const PER_XENIX: c_int = 7 | STICKY_TIMEOUTS | SHORT_INODE;
const PER_LINUX32: c_int = 8;
const PER_LINUX32_3GB: c_int = PER_LINUX32 | ADDR_LIMIT_3GB;
const PER_IRIX32: c_int = 9 | STICKY_TIMEOUTS;
const PER_IRIXN32: c_int = 0xa | STICKY_TIMEOUTS;
const PER_IRIX64: c_int = 0x0b | STICKY_TIMEOUTS;
const PER_RISCOS: c_int = 0xc;
const PER_SOLARIS: c_int = 0xd | STICKY_TIMEOUTS;
const PER_UW7: c_int = 0xe | STICKY_TIMEOUTS | MMAP_PAGE_ZERO;
const PER_OSF4: c_int = 0xf;
const PER_HPUX: c_int = 0x10;
const PER_MASK: c_int = 0xff;

// Flag constants, taken from sys/personality.h
const UNAME26: c_int = 0x0020000;
const ADDR_NO_RANDOMIZE: c_int = 0x0040000;
const FDPIC_FUNCPTRS: c_int = 0x0080000;
const MMAP_PAGE_ZERO: c_int = 0x0100000;
const ADDR_COMPAT_LAYOUT: c_int = 0x0200000;
const READ_IMPLIES_EXEC: c_int = 0x0400000;
const ADDR_LIMIT_32BIT: c_int = 0x0800000;
const SHORT_INODE: c_int = 0x1000000;
const WHOLE_SECONDS: c_int = 0x2000000;
const STICKY_TIMEOUTS: c_int = 0x4000000;
const ADDR_LIMIT_3GB: c_int = 0x8000000;

// A type that wraps personality(2) return value and implements Display.
struct SydPersona(pub c_int);

impl std::fmt::Display for SydPersona {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        let domain = match self.0 & PER_MASK {
            PER_LINUX => "linux",
            PER_LINUX_32BIT => "linux_32bit",
            PER_LINUX_FDPIC => "linux_fdpic",
            PER_SVR4 => "svr4",
            PER_SVR3 => "svr3",
            PER_SCOSVR3 => "scosvr3",
            PER_OSR5 => "osr5",
            PER_WYSEV386 => "wysev386",
            PER_ISCR4 => "iscr4",
            PER_BSD => "bsd",
            PER_SUNOS => "sunos",
            PER_XENIX => "xenix",
            PER_LINUX32 => "linux32",
            PER_LINUX32_3GB => "linux32_3gb",
            PER_IRIX32 => "irix32",
            PER_IRIXN32 => "irixn32",
            PER_IRIX64 => "irix64",
            PER_RISCOS => "riscos",
            PER_SOLARIS => "solaris",
            PER_UW7 => "uw7",
            PER_OSF4 => "osf4",
            PER_HPUX => "hpux",
            _ => "unknown",
        };

        let flags = [
            (UNAME26, "uname26"),
            (ADDR_NO_RANDOMIZE, "addr-no-randomize"),
            (FDPIC_FUNCPTRS, "fdpic-funcptrs"),
            (MMAP_PAGE_ZERO, "mmap-page-zero"),
            (ADDR_COMPAT_LAYOUT, "addr-compat-layout"),
            (READ_IMPLIES_EXEC, "read-implies-exec"),
            (ADDR_LIMIT_32BIT, "addr-limit-32bit"),
            (SHORT_INODE, "short-inode"),
            (WHOLE_SECONDS, "whole-seconds"),
            (STICKY_TIMEOUTS, "sticky-timeouts"),
            (ADDR_LIMIT_3GB, "addr-limit-3gb"),
        ]
        .iter()
        .filter_map(|&(flag, name)| {
            if self.0 & flag == flag {
                Some(name)
            } else {
                None
            }
        })
        .collect::<Vec<_>>()
        .join(",");

        if flags.is_empty() {
            write!(f, "{domain}")
        } else {
            write!(f, "{domain},{flags}")
        }
    }
}

/// SydMsFlags wraps MsFlags and provides from_name.
/// This is already available in newer bitflags versions.
pub(crate) struct SydMsFlags(MsFlags);

impl SydMsFlags {
    fn from_name(name: &str) -> Option<Self> {
        match name {
            "ro" => Some(SydMsFlags(MsFlags::MS_RDONLY)),
            "nosuid" => Some(SydMsFlags(MsFlags::MS_NOSUID)),
            "nodev" => Some(SydMsFlags(MsFlags::MS_NODEV)),
            "noexec" => Some(SydMsFlags(MsFlags::MS_NOEXEC)),
            "sync" => Some(SydMsFlags(MsFlags::MS_SYNCHRONOUS)),
            "remount" => Some(SydMsFlags(MsFlags::MS_REMOUNT)),
            "mandlock" => Some(SydMsFlags(MsFlags::MS_MANDLOCK)),
            "dirsync" => Some(SydMsFlags(MsFlags::MS_DIRSYNC)),
            "noatime" => Some(SydMsFlags(MsFlags::MS_NOATIME)),
            "nodiratime" => Some(SydMsFlags(MsFlags::MS_NODIRATIME)),
            "bind" => Some(SydMsFlags(MsFlags::MS_BIND)),
            "move" => Some(SydMsFlags(MsFlags::MS_MOVE)),
            "rec" => Some(SydMsFlags(MsFlags::MS_REC)),
            "silent" => Some(SydMsFlags(MsFlags::MS_SILENT)),
            "posixacl" => Some(SydMsFlags(MsFlags::MS_POSIXACL)),
            "unbindable" => Some(SydMsFlags(MsFlags::MS_UNBINDABLE)),
            "private" => Some(SydMsFlags(MsFlags::MS_PRIVATE)),
            "slave" => Some(SydMsFlags(MsFlags::MS_SLAVE)),
            "shared" => Some(SydMsFlags(MsFlags::MS_SHARED)),
            "relatime" => Some(SydMsFlags(MsFlags::MS_RELATIME)),
            "i_version" => Some(SydMsFlags(MsFlags::MS_I_VERSION)),
            "strictatime" => Some(SydMsFlags(MsFlags::MS_STRICTATIME)),
            "lazytime" => Some(SydMsFlags(MsFlags::MS_LAZYTIME)),
            // Deprecated: Should only be used in-kernel.
            //"kernmount" => Some(SydMsFlags(MsFlags::MS_KERNMOUNT)),
            // "active" => Some(SydMsFlags(MsFlags::MS_ACTIVE)),
            // "nouser" => Some(SydMsFlags(MsFlags::MS_NOUSER)),
            _ => None,
        }
    }

    // Convert MsFlags to a vector of flag names
    #[allow(clippy::cognitive_complexity)]
    fn to_names(&self) -> Vec<&str> {
        let mut names = Vec::new();

        if self.0.contains(MsFlags::MS_RDONLY) {
            names.push("ro");
        }
        if self.0.contains(MsFlags::MS_NOSUID) {
            names.push("nosuid");
        }
        if self.0.contains(MsFlags::MS_NODEV) {
            names.push("nodev");
        }
        if self.0.contains(MsFlags::MS_NOEXEC) {
            names.push("noexec");
        }
        if self.0.contains(MsFlags::MS_SYNCHRONOUS) {
            names.push("sync");
        }
        if self.0.contains(MsFlags::MS_REMOUNT) {
            names.push("remount");
        }
        if self.0.contains(MsFlags::MS_MANDLOCK) {
            names.push("mandlock");
        }
        if self.0.contains(MsFlags::MS_DIRSYNC) {
            names.push("dirsync");
        }
        if self.0.contains(MsFlags::MS_NOATIME) {
            names.push("noatime");
        }
        if self.0.contains(MsFlags::MS_NODIRATIME) {
            names.push("nodiratime");
        }
        if self.0.contains(MsFlags::MS_BIND) {
            names.push("bind");
        }
        if self.0.contains(MsFlags::MS_MOVE) {
            names.push("move");
        }
        if self.0.contains(MsFlags::MS_REC) {
            names.push("rec");
        }
        if self.0.contains(MsFlags::MS_SILENT) {
            names.push("silent");
        }
        if self.0.contains(MsFlags::MS_POSIXACL) {
            names.push("posixacl");
        }
        if self.0.contains(MsFlags::MS_UNBINDABLE) {
            names.push("unbindable");
        }
        if self.0.contains(MsFlags::MS_PRIVATE) {
            names.push("private");
        }
        if self.0.contains(MsFlags::MS_SLAVE) {
            names.push("slave");
        }
        if self.0.contains(MsFlags::MS_SHARED) {
            names.push("shared");
        }
        if self.0.contains(MsFlags::MS_RELATIME) {
            names.push("relatime");
        }
        if self.0.contains(MsFlags::MS_I_VERSION) {
            names.push("i_version");
        }
        if self.0.contains(MsFlags::MS_STRICTATIME) {
            names.push("strictatime");
        }
        if self.0.contains(MsFlags::MS_LAZYTIME) {
            names.push("lazytime");
        }

        names
    }
}

/* Utilities */

/// Print Syd version information,
/// and information about the system to
/// standard output.
pub fn syd_info() {
    #[allow(clippy::disallowed_methods)]
    let major = env!("CARGO_PKG_VERSION_MAJOR")
        .parse::<u64>()
        .expect("CARGO_PKG_VERSION_MAJOR");
    #[allow(clippy::disallowed_methods)]
    let minor = env!("CARGO_PKG_VERSION_MINOR")
        .parse::<u64>()
        .expect("CARGO_PKG_VERSION_MINOR");
    #[allow(clippy::disallowed_methods)]
    let patch = env!("CARGO_PKG_VERSION_PATCH")
        .parse::<u64>()
        .expect("CARGO_PKG_VERSION_PATCH");
    let hex_version = (major << 16) | (minor << 8) | patch;
    let code_name = hex_version
        .to_name()
        .split('_')
        .map(|word| {
            let mut c = word.chars();
            match c.next() {
                None => String::new(),
                Some(f) => f.to_uppercase().collect::<String>() + c.as_str(),
            }
        })
        .collect::<Vec<String>>()
        .join(" ");

    println!("syd {} ({})", *crate::config::VERSION, code_name);
    println!("Author: Ali Polatel");
    println!("License: GPL-3.0-or-later");

    let feat = [
        #[cfg(debug_assertions)]
        "+debug",
        #[cfg(not(debug_assertions))]
        "-debug",
        #[cfg(feature = "log")]
        "+log",
        #[cfg(not(feature = "log"))]
        "-log",
        #[cfg(feature = "oci")]
        "+oci",
        #[cfg(not(feature = "oci"))]
        "-oci",
    ];
    println!("Features: {}", feat.join(", "));

    let state = match lock_enabled() {
        0 => "fully enforced",
        1 => "partially enforced",
        2 => "not enforced",
        _ => "unsupported",
    };
    println!("Landlock: {state}");

    let libapi = libseccomp::get_api();
    match ScmpVersion::current() {
        Ok(libver) => {
            println!(
                "LibSeccomp: v{}.{}.{} api:{}",
                libver.major, libver.minor, libver.micro, libapi
            );
        }
        Err(error) => {
            println!("LibSeccomp: ? (error: {error})");
        }
    }

    let uname = match uname() {
        Ok(info) => OsStr::to_str(info.release()).unwrap_or("?").to_string(),
        Err(_) => "?".to_string(),
    };
    println!("Host (build): {}", env!("SYD_BUILDHOST"));
    println!(
        "Host (target): {uname} {}",
        seccomp_arch_native_name().unwrap_or("?")
    );
    println!("Target Environment: {}", env!("SYD_TARGET_ENV"));

    // SAFETY: In libc we trust.
    let pers = match unsafe { nix::libc::personality(0xFFFFFFFF) } {
        n if n < 0 => format!("? (error: {})", Errno::last()),
        n => SydPersona(n).to_string(),
    };
    println!("Target Persona: {pers}");

    println!("Target Pointer Width: {}", env!("SYD_TARGET_POINTER_WIDTH"));
    println!(
        "Target CPU Count: {} ({} physical)",
        num_cpus::get(),
        num_cpus::get_physical()
    );
    println!("Target CPU Endian: {}", env!("SYD_TARGET_ENDIAN"));
    println!("Target CPU Features: {}", env!("SYD_TARGET_FEATURE"));
}

#[inline]
pub(crate) fn op2name(op: u8) -> &'static str {
    match op {
        0x2 => "bind",
        0x3 => "connect",
        0x5 => "accept",
        0xb => "sendto",
        0x10 => "sendmsg",
        0x12 => "accept4",
        0x14 => "sendmmsg",
        _ => unreachable!(),
    }
}

#[inline]
pub(crate) fn op2errno(op: u8) -> Errno {
    match op {
        0x2 /*bind*/ => Errno::EADDRNOTAVAIL,
        0x3 /*connect*/ =>  Errno::ECONNREFUSED,
        0x5 | 0x12 /*accept{,4}*/ => Errno::ECONNABORTED,
        _ /*send{to,{m,}msg}*/ => Errno::ENOTCONN,
    }
}

/// Checks LandLock ABI v3 is supported.
/// Returns:
/// - 0: Fully enforced
/// - 1: Partially enforced
/// - 2: Not enforced
/// - 127: Unsupported
#[allow(clippy::disallowed_types)]
pub fn lock_enabled() -> u8 {
    let abi = ABI::V3;
    let path_ro = vec![PathBuf::from("/")];
    let path_rw = vec![PathBuf::from("/")];

    // A helper function to wrap the operations and reduce duplication
    #[allow(clippy::disallowed_methods)]
    fn landlock_operation(
        path_ro: &[PathBuf],
        path_rw: &[PathBuf],
        abi: ABI,
    ) -> Result<RestrictionStatus, RulesetError> {
        let ruleset = Ruleset::default().handle_access(AccessFs::from_all(abi))?;
        let created_ruleset = ruleset.create()?;
        let ro_rules = path_beneath_rules(path_ro, AccessFs::from_read(abi));
        let updated_ruleset = created_ruleset.add_rules(ro_rules)?;
        let rw_rules = path_beneath_rules(path_rw, AccessFs::from_all(abi));
        let final_ruleset = updated_ruleset.add_rules(rw_rules)?;
        final_ruleset.restrict_self().map_err(RulesetError::from)
    }

    match landlock_operation(&path_ro, &path_rw, abi) {
        Ok(status) => match status.ruleset {
            RulesetStatus::FullyEnforced => 0,
            RulesetStatus::PartiallyEnforced => 1,
            RulesetStatus::NotEnforced => 2,
        },
        Err(_) => 127,
    }
}

/// Returns true if we are running under syd.
#[allow(clippy::disallowed_methods)]
pub fn syd_enabled() -> bool {
    // This will not work if the sandbox is locked.
    // Path::new("/dev/syd").exists() || Path::new("/dev/syd").exists()
    // SAFETY: In libc, we trust.
    match unsafe { fork() } {
        Ok(ForkResult::Parent { child, .. }) => {
            match waitpid(child, None) {
                Ok(WaitStatus::Exited(_, code)) => {
                    // Check the child's exit status.
                    // Exit status of 0 means syd is enabled.
                    code == 0
                }
                _ => {
                    // If there's an error waiting on the
                    // child, assume syd is not enabled.
                    false
                }
            }
        }
        Ok(ForkResult::Child) => {
            let mut ctx = match ScmpFilterContext::new_filter(ScmpAction::Allow) {
                Ok(ctx) => ctx,
                Err(_) => exit(1),
            };

            let syscall = ScmpSyscall::from_name("open").unwrap();
            if ctx.add_rule(ScmpAction::Notify, syscall).is_err() {
                exit(1);
            }

            if ctx.load().is_err() && Errno::last() == Errno::EBUSY {
                // seccomp filter exists
                // syd is in business.
                exit(0);
            } else {
                // seccomp filter does not exist
                exit(1);
            }
        }
        Err(_) => {
            // If there's an error forking,
            // assume syd is not enabled.
            false
        }
    }
}

// Returns the name of the libsecc☮mp native architecture.
fn seccomp_arch_native_name() -> Option<&'static str> {
    match ScmpArch::native() {
        ScmpArch::X86 => Some("x86"),
        ScmpArch::X8664 => Some("x86_64"),
        ScmpArch::X32 => Some("x32"),
        ScmpArch::Arm => Some("arm"),
        ScmpArch::Aarch64 => Some("aarch64"),
        ScmpArch::Mips => Some("mips"),
        ScmpArch::Mips64 => Some("mips64"),
        ScmpArch::Mips64N32 => Some("mips64n32"),
        ScmpArch::Mipsel => Some("mipsel"),
        ScmpArch::Mipsel64 => Some("mipsel64"),
        ScmpArch::Mipsel64N32 => Some("mipsel64n32"),
        ScmpArch::Ppc => Some("ppc"),
        ScmpArch::Ppc64 => Some("ppc64"),
        ScmpArch::Ppc64Le => Some("ppc64le"),
        ScmpArch::S390 => Some("s390"),
        ScmpArch::S390X => Some("s390x"),
        ScmpArch::Parisc => Some("parisc"),
        ScmpArch::Parisc64 => Some("parisc64"),
        ScmpArch::Riscv64 => Some("riscv64"),
        _ => None,
    }
}

/// Given a `Uid`, return the user name of the user.
/// On any error conditions, return "nobody".
pub fn get_user_name(uid: Uid) -> String {
    match User::from_uid(uid) {
        Ok(Some(user)) => user.name,
        _ => "nobody".to_string(),
    }
}

/// Given a username, return the home directory of the user.
/// On any error conditions, return "/var/empty".
pub fn get_user_home(username: &str) -> String {
    // Fetch user details.
    match User::from_name(username) {
        Ok(Some(user)) => user.dir.to_string_lossy().to_string(),
        _ => "/var/empty".to_string(),
    }
}

/// Sets the specified signal to be ignored.
pub fn ignore_signal(signal: Signal) -> Result<(), Errno> {
    let sig_action = SigAction::new(
        SigHandler::SigIgn, // Set to ignore
        SaFlags::empty(),
        SigSet::empty(),
    );

    // SAFETY: The unsafe call to `sigaction` is used to set the
    // signal's disposition to "ignore". We're not invoking any handlers
    // or performing any operations that could lead to data races or
    // other undefined behaviors. Hence, it's safe to call in this
    // context.
    unsafe { sigaction(signal, &sig_action) }.map(drop)
}

/// Sets the specified signal to be set to its default action.
pub fn reset_signal(signal: Signal) -> Result<(), Errno> {
    let sig_action = SigAction::new(
        SigHandler::SigDfl, // Set to default
        SaFlags::empty(),
        SigSet::empty(),
    );

    // SAFETY: The unsafe call to `sigaction` is used to set the
    // signal's disposition to "ignore". We're not invoking any handlers
    // or performing any operations that could lead to data races or
    // other undefined behaviors. Hence, it's safe to call in this
    // context.
    unsafe { sigaction(signal, &sig_action) }.map(drop)
}

/// Ignores all signals except SIG{KILL,STOP,PIPE,CHLD},
/// and all signals with default action Core.
pub fn ignore_signals() -> Result<(), Errno> {
    // Iterate through all possible signals and set them to be ignored.
    // Step 1: Normal signals.
    for signal in Signal::iterator() {
        if !matches!(
            signal,
            Signal::SIGKILL | Signal::SIGSTOP | Signal::SIGPIPE | Signal::SIGCHLD
        ) && !is_coredump(signal as i32)
        {
            // 1. Can not ignore SIGKILL and SIGSTOP.
            // 2. Do not need to ignore Signals with default action Core.
            // 3. Ignoring SIGCHLD changes wait semantics which we cannot do.
            // 4. SIGPIPE must be handled outside this function.
            ignore_signal(signal)?;
        }
    }

    // Step 2: Real-time signals.
    for signum in nix::libc::SIGRTMIN()..nix::libc::SIGRTMAX() {
        // SAFETY: nix's signal does not support real-time signals.
        Errno::result(unsafe {
            nix::libc::signal(signum, nix::libc::SIG_IGN as nix::libc::sighandler_t)
        })?;
    }

    Ok(())
}

/// Reset all signals to their default dispositions.
pub fn reset_signals() -> Result<(), Errno> {
    // Iterate through all possible signals and set them to be ignored.
    // Step 1: Normal signals.
    for signal in Signal::iterator() {
        if !matches!(signal, Signal::SIGKILL | Signal::SIGSTOP) {
            // Can not ignore SIGKILL and SIGSTOP.
            reset_signal(signal)?;
        }
    }

    // Step 2: Real-time signals.
    for signum in nix::libc::SIGRTMIN()..nix::libc::SIGRTMAX() {
        // SAFETY: nix's signal does not support real-time signals.
        Errno::result(unsafe {
            nix::libc::signal(signum, nix::libc::SIG_DFL as nix::libc::sighandler_t)
        })?;
    }

    Ok(())
}

const SECCOMP_ARCH_LIST: &[ScmpArch] = &[
    ScmpArch::X86,
    ScmpArch::X8664,
    ScmpArch::X32,
    ScmpArch::Arm,
    ScmpArch::Aarch64,
    ScmpArch::Mips,
    ScmpArch::Mips64,
    ScmpArch::Mips64N32,
    ScmpArch::Mipsel,
    ScmpArch::Mipsel64,
    ScmpArch::Mipsel64N32,
    ScmpArch::Ppc,
    ScmpArch::Ppc64,
    ScmpArch::Ppc64Le,
    ScmpArch::S390,
    ScmpArch::S390X,
    ScmpArch::Parisc,
    ScmpArch::Parisc64,
    ScmpArch::Riscv64,
];

/// Print list of libseccomp's supported architectures
/// Used by `syd --arch list`
pub fn print_seccomp_architectures() {
    let native = ScmpArch::native();
    for arch in SECCOMP_ARCH_LIST {
        let mut repr = format!("{arch:?}").to_ascii_lowercase();
        if repr == "x8664" {
            // Fix potential confusion.
            repr = "x86_64".to_string();
        }
        if *arch == native {
            println!("- {repr} [*]")
        } else {
            println!("- {repr}");
        }
    }
}

// List of libseccomp supported architectures for the current system.
#[cfg(target_arch = "x86_64")]
pub(crate) const SCMP_ARCH: &[ScmpArch] = &[ScmpArch::X8664, ScmpArch::X86, ScmpArch::X32];
#[cfg(target_arch = "x86")]
pub(crate) const SCMP_ARCH: &[ScmpArch] = &[ScmpArch::X86];
#[cfg(target_arch = "arm")]
pub(crate) const SCMP_ARCH: &[ScmpArch] = &[ScmpArch::Arm];
#[cfg(target_arch = "aarch64")]
pub(crate) const SCMP_ARCH: &[ScmpArch] = &[ScmpArch::Aarch64, ScmpArch::Arm];
#[cfg(target_arch = "mips")]
pub(crate) const SCMP_ARCH: &[ScmpArch] = &[ScmpArch::Mips];
#[cfg(target_arch = "mips64")]
pub(crate) const SCMP_ARCH: &[ScmpArch] = &[ScmpArch::Mips64, ScmpArch::Mips64N32, ScmpArch::Mips];
//#[cfg(target_arch = "mips64n32")]
//pub(crate) const SCMP_ARCH: &[ScmpArch] = &[ScmpArch::Mips64N32, ScmpArch::Mips, ScmpArch::Mips64];
//#[cfg(target_arch = "mipsel")]
//pub(crate) const SCMP_ARCH: &[ScmpArch] =
//    &[ScmpArch::Mipsel64, ScmpArch::Mipsel64N32, ScmpArch::Mipsel];
//#[cfg(target_arch = "mipsel64")]
//pub(crate) const SCMP_ARCH: &[ScmpArch] =
//    &[ScmpArch::Mipsel64, ScmpArch::Mipsel, ScmpArch::Mipsel64N32];
//#[cfg(target_arch = "mipsel64n32")]
//pub(crate) const SCMP_ARCH: &[ScmpArch] =
//    &[ScmpArch::Mipsel64N32, ScmpArch::Mipsel, ScmpArch::Mipsel64];
#[cfg(target_arch = "powerpc")]
pub(crate) const SCMP_ARCH: &[ScmpArch] = &[ScmpArch::Ppc];
#[cfg(all(target_arch = "powerpc64", target_endian = "big"))]
pub(crate) const SCMP_ARCH: &[ScmpArch] = &[ScmpArch::Ppc64];
#[cfg(all(target_arch = "powerpc64", target_endian = "little"))]
pub(crate) const SCMP_ARCH: &[ScmpArch] = &[ScmpArch::Ppc64Le];
//#[cfg(target_arch = "parisc")]
//pub(crate) const SCMP_ARCH: &[ScmpArch] = &[ScmpArch::Parisc];
//#[cfg(target_arch = "parisc64")]
//pub(crate) const SCMP_ARCH: &[ScmpArch] = &[ScmpArch::Parisc64, ScmpArch::Parisc];
#[cfg(target_arch = "riscv64")]
pub(crate) const SCMP_ARCH: &[ScmpArch] = &[ScmpArch::Riscv64];
#[cfg(target_arch = "s390x")]
pub(crate) const SCMP_ARCH: &[ScmpArch] = &[ScmpArch::S390X, ScmpArch::S390];

/// Return true if native architecture has the multiplexed socketcall system call.
/// Panics if it cannot determine the native architecture.
pub fn seccomp_native_has_socketcall() -> bool {
    matches!(
        ScmpArch::native(),
        ScmpArch::X86
            | ScmpArch::Mips
            | ScmpArch::Mipsel
            | ScmpArch::Ppc
            | ScmpArch::Ppc64
            | ScmpArch::Ppc64Le
            | ScmpArch::S390
            | ScmpArch::S390X
    )
}

/// Add all supported architectures to the given filter.
pub fn seccomp_add_architectures(ctx: &mut ScmpFilterContext) -> SydResult<()> {
    // Add architectures based on the current architecture
    for arch in SCMP_ARCH {
        seccomp_add_arch(ctx, *arch)?;
    }
    Ok(())
}

fn seccomp_add_arch(ctx: &mut ScmpFilterContext, arch: ScmpArch) -> SydResult<bool> {
    Ok(ctx.add_arch(arch)?)
}

/// Check if arch is 64-bit or 32-bit.
#[inline]
pub fn scmp_arch_bits(arch: ScmpArch) -> usize {
    match arch {
        ScmpArch::X8664
        | ScmpArch::Aarch64
        | ScmpArch::Mips64
        | ScmpArch::Mips64N32
        | ScmpArch::Mipsel64
        | ScmpArch::Mipsel64N32
        | ScmpArch::Ppc64
        | ScmpArch::Ppc64Le
        | ScmpArch::Parisc64
        | ScmpArch::Riscv64
        | ScmpArch::S390X => 64,
        ScmpArch::X86
        | ScmpArch::X32
        | ScmpArch::Arm
        | ScmpArch::Mips
        | ScmpArch::Mipsel
        | ScmpArch::Ppc
        | ScmpArch::Parisc
        | ScmpArch::S390 => 32,
        _ => 64, // sane default for non-exhaustive enum.
    }
}

/// Determine the size of the syscall instruction for the given
/// architecture.
#[inline]
pub fn syscall_instruction_size(arch: ScmpArch) -> u64 {
    match arch {
        ScmpArch::X8664 => 2,       // x86-64: 0x0f 0x05
        ScmpArch::X86 => 2,         // x86: 0xcd 0x80 (int 0x80)
        ScmpArch::X32 => 2,         // x32: 0xcd 0x80 (int 0x80)
        ScmpArch::Arm => 4,         // ARM: svc 0x000000
        ScmpArch::Aarch64 => 4,     // AArch64: svc 0x000000
        ScmpArch::Mips => 4,        // MIPS: syscall
        ScmpArch::Mips64 => 4,      // MIPS64: syscall
        ScmpArch::Mips64N32 => 4,   // MIPS64N32: syscall
        ScmpArch::Mipsel => 4,      // MIPSel: syscall
        ScmpArch::Mipsel64 => 4,    // MIPSel64: syscall
        ScmpArch::Mipsel64N32 => 4, // MIPSel64N32: syscall
        ScmpArch::Ppc => 4,         // PowerPC: sc
        ScmpArch::Ppc64 => 4,       // PowerPC64: sc
        ScmpArch::Ppc64Le => 4,     // PowerPC64Le: sc
        ScmpArch::Parisc => 4,      // PA-RISC: syscall
        ScmpArch::Parisc64 => 4,    // PA-RISC64: syscall
        ScmpArch::Riscv64 => 4,     // RISC-V: ecall
        ScmpArch::S390 => 4,        // S/390: svc
        ScmpArch::S390X => 4,       // S/390X: svc
        _ => 4,                     // Default size for any other non-exhaustive enum variants.
    }
}

/// Helper function to determine if the architecture is big-endian.
#[inline]
pub fn scmp_big_endian(arch: ScmpArch) -> bool {
    matches!(
        arch,
        ScmpArch::Mips
            | ScmpArch::Mips64
            | ScmpArch::Ppc
            | ScmpArch::Ppc64
            | ScmpArch::S390
            | ScmpArch::S390X
            | ScmpArch::Parisc
            | ScmpArch::Parisc64
    )
}

/// Represents seccomp notify data.
/// We redefine this because libseccomp struct is non-exhaustive.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub(crate) struct ScmpNotifData {
    pub(crate) syscall: ScmpSyscall,
    pub(crate) arch: ScmpArch,
    pub(crate) instr_pointer: u64,
    pub(crate) args: [u64; 6],
}

/// Represents a seccomp notify request.
/// We redefine this because libseccomp struct is non-exhaustive.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub(crate) struct ScmpNotifReq {
    pub(crate) id: u64,
    pub(crate) pid: u32,
    pub(crate) flags: u32,
    pub(crate) data: ScmpNotifData,
}

impl ScmpNotifData {
    fn from_sys(data: nix::libc::seccomp_data) -> Result<Self, Errno> {
        Ok(Self {
            syscall: ScmpSyscall::from(data.nr),
            arch: scmp_arch(data.arch)?,
            instr_pointer: data.instruction_pointer,
            args: data.args,
        })
    }
}

impl ScmpNotifReq {
    pub(crate) fn from_sys(req: nix::libc::seccomp_notif) -> Result<Self, Errno> {
        Ok(Self {
            id: req.id,
            pid: req.pid,
            flags: req.flags,
            data: ScmpNotifData::from_sys(req.data)?,
        })
    }

    #[inline]
    pub(crate) fn pid(&self) -> Pid {
        #[allow(clippy::cast_possible_wrap)]
        Pid::from_raw(self.pid as nix::libc::pid_t)
    }
}

/// Helper function to convert raw arch value to ScmpArch.
/// We need this because ScmpArch::from_sys is not imported.
pub(crate) fn scmp_arch(arch: u32) -> Result<ScmpArch, Errno> {
    match arch {
        libseccomp_sys::SCMP_ARCH_NATIVE => Ok(ScmpArch::Native),
        libseccomp_sys::SCMP_ARCH_X86 => Ok(ScmpArch::X86),
        libseccomp_sys::SCMP_ARCH_X86_64 => Ok(ScmpArch::X8664),
        libseccomp_sys::SCMP_ARCH_X32 => Ok(ScmpArch::X32),
        libseccomp_sys::SCMP_ARCH_ARM => Ok(ScmpArch::Arm),
        libseccomp_sys::SCMP_ARCH_AARCH64 => Ok(ScmpArch::Aarch64),
        libseccomp_sys::SCMP_ARCH_MIPS => Ok(ScmpArch::Mips),
        libseccomp_sys::SCMP_ARCH_MIPS64 => Ok(ScmpArch::Mips64),
        libseccomp_sys::SCMP_ARCH_MIPS64N32 => Ok(ScmpArch::Mips64N32),
        libseccomp_sys::SCMP_ARCH_MIPSEL => Ok(ScmpArch::Mipsel),
        libseccomp_sys::SCMP_ARCH_MIPSEL64 => Ok(ScmpArch::Mipsel64),
        libseccomp_sys::SCMP_ARCH_MIPSEL64N32 => Ok(ScmpArch::Mipsel64N32),
        libseccomp_sys::SCMP_ARCH_PPC => Ok(ScmpArch::Ppc),
        libseccomp_sys::SCMP_ARCH_PPC64 => Ok(ScmpArch::Ppc64),
        libseccomp_sys::SCMP_ARCH_PPC64LE => Ok(ScmpArch::Ppc64Le),
        libseccomp_sys::SCMP_ARCH_S390 => Ok(ScmpArch::S390),
        libseccomp_sys::SCMP_ARCH_S390X => Ok(ScmpArch::S390X),
        libseccomp_sys::SCMP_ARCH_PARISC => Ok(ScmpArch::Parisc),
        libseccomp_sys::SCMP_ARCH_PARISC64 => Ok(ScmpArch::Parisc64),
        libseccomp_sys::SCMP_ARCH_RISCV64 => Ok(ScmpArch::Riscv64),
        _ => Err(Errno::EINVAL),
    }
}

/// CLONE_NEWTIME constant to create time namespaces.
pub const CLONE_NEWTIME: nix::libc::c_int = 128;

pub(crate) const NAMESPACE_FLAGS: &[nix::libc::c_int] = &[
    nix::libc::CLONE_NEWNS,
    nix::libc::CLONE_NEWIPC,
    nix::libc::CLONE_NEWNET,
    nix::libc::CLONE_NEWPID,
    nix::libc::CLONE_NEWUTS,
    nix::libc::CLONE_NEWUSER,
    nix::libc::CLONE_NEWCGROUP,
    CLONE_NEWTIME,
];

pub(crate) const NAMESPACE_FLAGS_ALL: nix::libc::c_int = nix::libc::CLONE_NEWNS
    | nix::libc::CLONE_NEWIPC
    | nix::libc::CLONE_NEWNET
    | nix::libc::CLONE_NEWPID
    | nix::libc::CLONE_NEWUTS
    | nix::libc::CLONE_NEWUSER
    | nix::libc::CLONE_NEWCGROUP
    | CLONE_NEWTIME;

pub(crate) const NAMESPACE_NAMES: &[&str] = &[
    "mount", "ipc", "net", "pid", "uts", "user", "cgroup", "time",
];

/// Convert CLONE namespace flags to a Vector of Strings.
pub fn nsflags_name(flags: nix::libc::c_int) -> Vec<String> {
    let mut names = Vec::with_capacity(NAMESPACE_FLAGS.len());
    for &flag in NAMESPACE_FLAGS {
        if flags & flag != 0 {
            names.push(nsflag_name(flag));
        }
    }

    names
}

/// Convert a CLONE namespace flag to its String representation.
pub fn nsflag_name(flag: nix::libc::c_int) -> String {
    match flag {
        nix::libc::CLONE_NEWNS => "mount",
        nix::libc::CLONE_NEWIPC => "ipc",
        nix::libc::CLONE_NEWNET => "net",
        nix::libc::CLONE_NEWPID => "pid",
        nix::libc::CLONE_NEWUTS => "uts",
        nix::libc::CLONE_NEWUSER => "user",
        nix::libc::CLONE_NEWCGROUP => "cgroup",
        CLONE_NEWTIME => "time",
        _ => "?",
    }
    .to_string()
}

/// A helper function to wrap the Landlock operations and reduce duplication
#[allow(clippy::disallowed_methods)]
#[allow(clippy::disallowed_types)]
pub fn landlock_operation(
    path_ro: &[PathBuf],
    path_rw: &[PathBuf],
    abi: ABI,
) -> Result<RestrictionStatus, RulesetError> {
    let ruleset = Ruleset::default().handle_access(AccessFs::from_all(abi))?;
    let created_ruleset = ruleset.create()?;
    let ro_rules = path_beneath_rules(path_ro, AccessFs::from_read(abi));
    let updated_ruleset = created_ruleset.add_rules(ro_rules)?;
    let rw_rules = path_beneath_rules(path_rw, AccessFs::from_all(abi));
    let final_ruleset = updated_ruleset.add_rules(rw_rules)?;
    final_ruleset.restrict_self().map_err(RulesetError::from)
}

/// Simple human size formatter.
#[allow(clippy::arithmetic_side_effects)]
#[allow(clippy::cast_precision_loss)]
pub fn human_size(bytes: usize) -> String {
    const SIZES: &[char] = &['B', 'K', 'M', 'G', 'T', 'P', 'E'];
    let factor = 1024usize;

    let mut size = bytes as f64;
    let mut i = 0;

    while size > factor as f64 && i < SIZES.len() - 1 {
        size /= factor as f64;
        i += 1;
    }

    format!("{:.2}{}", size, SIZES[i])
}

#[cfg(target_env = "musl")]
pub(crate) type IoctlRequest = nix::libc::c_int;
#[cfg(not(target_env = "musl"))]
pub(crate) type IoctlRequest = nix::libc::c_ulong;

const SIOCGIFFLAGS: IoctlRequest = nix::libc::SIOCGIFFLAGS as IoctlRequest;
const SIOCSIFFLAGS: IoctlRequest = nix::libc::SIOCSIFFLAGS as IoctlRequest;

/// Functionally equivalent to "ifconfig lo up".
pub fn bring_up_loopback() -> Result<(), Errno> {
    // Create a socket
    let sock = socket(
        AddressFamily::Inet,
        SockType::Stream,
        SockFlag::empty(),
        None,
    )?;

    // Prepare the interface request
    let mut ifreq = nix::libc::ifreq {
        #[allow(clippy::cast_possible_wrap)]
        ifr_name: [
            b'l' as nix::libc::c_char,
            b'o' as nix::libc::c_char,
            0,
            0,
            0,
            0,
            0,
            0,
            0,
            0,
            0,
            0,
            0,
            0,
            0,
            0,
        ],
        // SAFETY: Manually initialize ifr_ifru
        ifr_ifru: unsafe { std::mem::zeroed() },
    };

    // SAFETY: Get the current flags
    if unsafe { nix::libc::ioctl(sock.as_raw_fd(), SIOCGIFFLAGS, &mut ifreq) } != 0 {
        return Err(Errno::last());
    }

    // Modify the flags to bring up the interface
    // SAFETY: We're accessing the field of a union here.
    #[allow(clippy::cast_possible_truncation)]
    unsafe {
        ifreq.ifr_ifru.ifru_flags |=
            (nix::libc::IFF_UP | nix::libc::IFF_RUNNING) as nix::libc::c_short
    };

    // SAFETY: Set the new flags
    if unsafe { nix::libc::ioctl(sock.as_raw_fd(), SIOCSIFFLAGS, &mut ifreq) } == 0 {
        Ok(())
    } else {
        Err(Errno::last())
    }
}

/// Check for file descriptor leaks above the standard input, output, and error.
///
/// This function examines the `/proc/self/fd` directory to identify open file descriptors.
/// It prints any open file descriptors other than the standard input (0), output (1), and error (2),
/// indicating potential resource leaks.
///
/// # Parameters
/// - `fd_max`: An optional parameter that sets a maximum file descriptor number to check.
///             If not specified, only the standard file descriptors are considered normal.
///
/// # Returns
/// Returns `true` if leaks are found, otherwise `false`.
pub fn check_fd_leaks(fd_max: Option<RawFd>) -> u32 {
    let proc_fd_path = Path::new("/proc/self/fd");
    let mut dir = match Dir::open(proc_fd_path, OFlag::O_RDONLY, Mode::empty()) {
        Ok(d) => d,
        Err(e) => {
            eprintln!("Failed to open /proc/self/fd: {e}");
            return u32::MAX;
        }
    };

    let mut leaks_found: u32 = 0;
    let dir_fd = dir.as_raw_fd();
    let fd_limit = fd_max.unwrap_or(2); // Default limit only std fds

    for entry in dir.iter() {
        let entry = match entry {
            Ok(e) => e,
            Err(_) => continue,
        };

        let fd_str = entry.file_name().to_string_lossy(); // Use lossy conversion
        let fd = match fd_str.parse::<RawFd>() {
            Ok(fd) => fd,
            Err(_) => continue,
        };

        // Ignore standard file descriptors and the directory stream FD itself
        if fd <= fd_limit || fd == dir_fd {
            continue;
        }

        // Create a PathBuf from the string representation of the file descriptor
        let link_path = proc_fd_path.join(fd_str.into_owned()); // Convert Cow<str> into a String and then into a PathBuf

        match std::fs::read_link(&link_path) {
            Ok(target_path) => {
                eprintln!("!!! Leaked file descriptor {fd} -> {target_path:?} !!!");
                leaks_found = leaks_found.saturating_add(1);
            }
            Err(error) => {
                eprintln!("Failed to read link for FD {fd}: {error}");
            }
        }
    }

    leaks_found
}

/// Parse the given string into a UID.
/// 1. use getpwnam_r(3)
/// 2. parse as integer
pub(crate) fn parse_user(name: &str) -> Result<Uid, Errno> {
    if name.chars().all(|c| c.is_ascii_digit()) {
        Ok(Uid::from_raw(
            name.parse::<nix::libc::uid_t>().or(Err(Errno::EINVAL))?,
        ))
    } else if let Some(user) = User::from_name(name)? {
        Ok(user.uid)
    } else {
        Err(Errno::ENOENT)
    }
}

/// Parse the given string into a GID.
/// 1. use getpwnam_r(3)
/// 2. parse as integer
pub(crate) fn parse_group(name: &str) -> Result<Gid, Errno> {
    if name.chars().all(|c| c.is_ascii_digit()) {
        Ok(Gid::from_raw(
            name.parse::<nix::libc::gid_t>().or(Err(Errno::EINVAL))?,
        ))
    } else if let Some(group) = Group::from_name(name)? {
        Ok(group.gid)
    } else {
        Err(Errno::ENOENT)
    }
}

/// Extends the ioctl value if necessary.
///
/// In musl, ioctl is defined as:
/// `int ioctl(int fd, int req, ...);`
///
/// In glibc, ioctl is defined as:
/// `int ioctl(int fd, unsigned long request, ...);`
///
/// This difference can cause issues when handling ioctl values that are
/// larger than what a signed 32-bit integer can represent.
/// Specifically, values with the high bit set (0x80000000) or the next
/// highest bit set (0x40000000) can be interpreted differently
/// depending on the implementation.
///
/// In a 32-bit signed integer, the high bit (0x80000000) is used as the
/// sign bit, indicating whether the number is positive or negative. If
/// this bit is set, the number is interpreted as negative. The next
/// highest bit (0x40000000) is the largest value that a signed 32-bit
/// integer can represent without becoming negative.
///
/// Therefore, ioctl values that have either of these bits set can cause
/// compatibility issues between musl and glibc. To ensure
/// compatibility, we need to extend such ioctl values to 64 bits by
/// prefixing them with `0xffffffff`, converting them to their unsigned
/// representation.
///
/// # Arguments
///
/// * `value` - The original ioctl value.
///
/// # Returns
///
/// * `Some(extended_value)` - If the value requires extension.
/// * `None` - If the value does not require extension.
#[inline]
pub fn extend_ioctl(value: u64) -> Option<u64> {
    // Check if the high bit (0x80000000) or the next highest bit
    // (0x40000000) is set.  These bits can cause the value to be
    // interpreted as a negative number in a signed 32-bit context.
    if (value & 0x80000000 == 0x80000000) || (value & 0x40000000 == 0x40000000) {
        // If the value requires extension, return the extended value by
        // prefixing with `0xffffffff`.
        Some(0xffffffff00000000 | value)
    } else {
        // If the value does not require extension, return None.
        None
    }
}

/// Drop a Capability from the Effective, Ambient, Inheritable and Permitted capsets.
pub fn safe_drop_cap(cap: caps::Capability) -> Result<(), caps::errors::CapsError> {
    caps::drop(None, caps::CapSet::Effective, cap)?;
    caps::drop(None, caps::CapSet::Ambient, cap)?;
    caps::drop(None, caps::CapSet::Inheritable, cap)?;
    caps::drop(None, caps::CapSet::Permitted, cap)
}

/// Return true if the given signal has default action Core.
#[inline]
#[allow(unreachable_patterns)]
pub(crate) fn is_coredump(sig: i32) -> bool {
    matches!(
        sig,
        nix::libc::SIGABRT
            | nix::libc::SIGBUS
            | nix::libc::SIGFPE
            | nix::libc::SIGILL
            | nix::libc::SIGIOT
            | nix::libc::SIGKILL
            | nix::libc::SIGQUIT
            | nix::libc::SIGSEGV
            | nix::libc::SIGSYS
            | nix::libc::SIGTRAP
            | nix::libc::SIGXCPU
            | nix::libc::SIGXFSZ
    )
}

#[cfg(target_arch = "x86")]
#[inline(always)]
/// Fork fast.
///
/// # Safety
///
/// Unsafe to be fast!
pub unsafe fn fork_fast() {
    asm!(
        "mov eax, 0x2", // 0x2 is the syscall number for fork on x86
        "int 0x80",     // Interrupt to make the syscall
        out("eax") _,
    );
}

#[cfg(target_arch = "x86_64")]
#[inline(always)]
/// Fork fast.
///
/// # Safety
///
/// Unsafe to be fast!
pub unsafe fn fork_fast() {
    // Inline assembly for x86-64
    asm!(
        "mov rax, 57", // 57 is the syscall number for fork on x86-64
        "syscall",
        out("rax") _,
    );
}

#[cfg(target_arch = "aarch64")]
#[inline(always)]
/// Fork fast.
///
/// # Safety
///
/// Unsafe to be fast!
pub unsafe fn fork_fast() {
    asm!(
        "mov x0, 17",  // SIGCHLD
        "mov x1, 0",   // child_stack (null, not recommended)
        "mov x8, 220", // syscall number for clone
        "svc 0",
        options(nostack),
    );
}

#[cfg(target_arch = "arm")]
#[inline(always)]
/// Fork fast.
///
/// # Safety
///
/// Unsafe to be fast!
pub unsafe fn fork_fast() {
    asm!(
        "mov r7, #2", // 2 is the syscall number for fork on ARM
        "swi #0",     // Software interrupt to make the syscall
        out("r0") _,
        options(nostack),
    );
}

/*
 * error[E0658]: inline assembly is not stable yet on this architecture
#[cfg(any(target_arch = "powerpc", target_arch = "powerpc64"))]
#[inline(always)]
/// Fork fast.
///
/// # Safety
///
/// Unsafe to be fast!
pub unsafe fn fork_fast() {
    asm!(
        "li 0, 2",     // Load immediate 2 into register r0 (syscall number for fork)
        "sc",          // System call
        out("r3") _,   // Output from r3 (return value of fork)
    );
}
*/

#[cfg(target_arch = "riscv64")]
#[inline(always)]
/// Fork fast.
///
/// # Safety
///
/// Unsafe to be fast!
pub unsafe fn fork_fast() {
    asm!(
        "li a7, 220",  // syscall number for clone on riscv64
        "li a0, 17",   // SIGCHLD
        "li a1, 0",    // child_stack (null, not recommended)
        "ecall",       // make the syscall
        out("a0") _,   // store return value in a0
        options(nostack),
    );
}

/*
 * error[E0658]: inline assembly is not stable yet on this architecture
#[cfg(any(target_arch = "s390x"))]
#[inline(always)]
/// Fork fast.
///
/// # Safety
///
/// Unsafe to be fast!
pub unsafe fn fork_fast() {
    asm!(
        "lgr %r1, 2", // Load syscall number for fork (2) directly into %r1.
        "svc 0",      // Supervisor Call to invoke the syscall.
    );
}
*/

#[cfg(any(
    target_arch = "powerpc",
    target_arch = "powerpc64",
    target_arch = "s390x"
))]
#[inline(always)]
/// Fork fast.
///
/// # Safety
///
/// Unsafe to be fast!
pub unsafe fn fork_fast() {
    let _ = nix::libc::syscall(nix::libc::SYS_fork);
}

#[cfg(not(any(
    target_arch = "aarch64",
    target_arch = "arm",
    target_arch = "powerpc",
    target_arch = "powerpc64",
    target_arch = "riscv64",
    target_arch = "riscv64",
    target_arch = "s390x",
    target_arch = "x86",
    target_arch = "x86_64",
)))]
#[inline(always)]
/// Fork fast.
///
/// # Safety
///
/// Unsafe to be fast!
pub unsafe fn fork_fast() {
    let _ = fork();
}

/// Set SIGPIPE handler to default.
pub fn set_sigpipe_dfl() -> Result<(), Errno> {
    // SAFETY: The nix::sys::signal::signal function is unsafe because
    // it affects the global state of the program by changing how a
    // signal (SIGPIPE in this case) is handled. It's safe to call here
    // because changing the SIGPIPE signal to its default behavior will
    // not interfere with any other part of this program that could be
    // relying on a custom SIGPIPE signal handler.
    unsafe { signal(Signal::SIGPIPE, SigHandler::SigDfl) }.map(drop)
}

#[allow(dead_code)]
#[inline]
#[cold]
fn cold() {}

#[allow(dead_code)]
#[inline]
pub(crate) fn likely(b: bool) -> bool {
    if !b {
        cold()
    }
    b
}

#[allow(dead_code)]
#[inline]
pub(crate) fn unlikely(b: bool) -> bool {
    if b {
        cold()
    }
    b
}

/// Write the message to the invalid fd -42.
/// The idea is to look for it in strace logs.
/// Only works in debug mode, noop in release.
pub fn t(_msg: &str) {
    #[cfg(feature = "log")]
    let _ = nix::unistd::write(
        // SAFETY: writing to -42 safe, it'll always return EBADF.
        unsafe { std::os::fd::BorrowedFd::borrow_raw(-42) },
        _msg.as_bytes(),
    );
}

#[cfg(feature = "log")]
/// Write a formatted message to an invalid fd.
#[macro_export]
macro_rules! t {
    ($($arg:tt)*) => {{
        syd::t(&format!($($arg)*));
    }}
}

#[cfg(feature = "log")]
/// Write a formatted message to an invalid fd.
#[macro_export]
macro_rules! T {
    ($($arg:tt)*) => {{
        $crate::t(&format!($($arg)*));
    }}
}

#[cfg(not(feature = "log"))]
/// Write a formatted message to an invalid fd.
#[macro_export]
macro_rules! t {
    ($($arg:tt)*) => {{}};
}

#[cfg(not(feature = "log"))]
/// Write a formatted message to an invalid fd.
#[macro_export]
macro_rules! T {
    ($($arg:tt)*) => {{}};
}

#[cfg(feature = "prof")]
#[inline(always)]
#[allow(dead_code)]
pub(crate) fn start_cpu_profile(name: &str) {
    gperftools::profiler::PROFILER
        .lock()
        .expect("lock profiler")
        .start(format!("./syd-cpu-{name}.pprof"))
        .expect("start profiler");
}

#[cfg(not(feature = "prof"))]
#[inline(always)]
#[allow(dead_code)]
pub(crate) fn start_cpu_profile(_name: &str) {}

#[cfg(feature = "prof")]
#[inline(always)]
#[allow(dead_code)]
pub(crate) fn stop_cpu_profile() {
    gperftools::profiler::PROFILER
        .lock()
        .expect("lock profiler")
        .stop()
        .expect("stop profiler");
}

#[cfg(not(feature = "prof"))]
#[inline(always)]
#[allow(dead_code)]
pub(crate) fn stop_cpu_profile() {}

#[cfg(feature = "prof")]
#[inline(always)]
#[allow(dead_code)]
pub(crate) fn start_mem_profile(name: &str) {
    gperftools::heap_profiler::HEAP_PROFILER
        .lock()
        .expect("lock profiler")
        .start(format!("./syd-mem-{name}"))
        .expect("start profiler");
}

#[cfg(not(feature = "prof"))]
#[inline(always)]
#[allow(dead_code)]
pub(crate) fn start_mem_profile(_name: &str) {}

#[cfg(feature = "prof")]
#[inline(always)]
#[allow(dead_code)]
pub(crate) fn dump_mem_profile(name: &str) {
    gperftools::heap_profiler::HEAP_PROFILER
        .lock()
        .expect("lock profiler")
        .dump(format!("./syd-mem-{name}"))
        .expect("dump profiler");
}

#[cfg(not(feature = "prof"))]
#[inline(always)]
#[allow(dead_code)]
pub(crate) fn dump_mem_profile(_name: &str) {}

#[cfg(feature = "prof")]
#[inline(always)]
#[allow(dead_code)]
pub(crate) fn stop_mem_profile() {
    gperftools::heap_profiler::HEAP_PROFILER
        .lock()
        .expect("lock profiler")
        .stop()
        .expect("stop profiler");
}

#[cfg(not(feature = "prof"))]
#[inline(always)]
#[allow(dead_code)]
pub(crate) fn stop_mem_profile() {}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_extend_ioctl() {
        const IOCTLS: &[(u64, bool)] = &[
            (0x5451, false),
            (0x5450, false),
            (0x541B, false),
            (0x5421, false),
            (0x5452, false),
            (0x4B66, false),
            (0x5401, false),
            (0x5402, false),
            (0x5403, false),
            (0x5404, false),
            (0x5405, false),
            (0x5406, false),
            (0x5407, false),
            (0x5408, false),
            (0x5456, false),
            (0x5457, false),
            (0x5413, false),
            (0x5414, false),
            (0x5409, false),
            (0x5425, false),
            (0x5427, false),
            (0x5428, false),
            (0x540A, false),
            (0x5411, false),
            (0x540B, false),
            (0x80045430, true),
            (0x80045432, true),
            (0x5432, false),
            (0x5433, false),
            (0x5434, false),
            (0x5435, false),
            (0x40045436, true),
            (0x5437, false),
            (0x80045438, true),
            (0x80045439, true),
            (0x80045440, true),
            (0x5441, false),
            (0x540E, false),
            (0x540F, false),
            (0x5410, false),
            (0x5429, false),
            (0x540C, false),
            (0x80045440, true),
            (0x540D, false),
            (0x5424, false),
            (0x5423, false),
            (0x5420, false),
            (0x80045438, true),
            (0x40045431, true),
            (0x80045439, true),
            (0x5441, false),
            (0x80086601, true),
            (0x5419, false),
            (0x541A, false),
            (0x8910, false),
            (0x8912, false),
            (0x8913, false),
            (0x8915, false),
            (0x8917, false),
            (0x8919, false),
            (0x891b, false),
            (0x891d, false),
            (0x891f, false),
            (0x892, false),
            (0x8925, false),
            (0x8927, false),
            (0x8929, false),
            (0x8933, false),
            (0x8935, false),
            (0x8938, false),
            (0x8940, false),
            (0x8942, false),
            (0x8947, false),
            (0x8948, false),
            (0x894C, false),
            (0x2400, false),
            (0x2401, false),
            (0x2402, false),
            (0x2403, false),
            (0x2405, false),
            (0x40082404, true),
            (0x40082406, true),
            (0x80082407, true),
            (0x40042408, true),
            (0x40042409, true),
            (0xc008240a, true),
            (0x4008240b, true),
        ];

        for (request, extend) in IOCTLS.iter() {
            if *extend {
                assert!(
                    extend_ioctl(*request).is_some(),
                    "OOPS: {request}->{extend}"
                );
            } else {
                assert!(
                    extend_ioctl(*request).is_none(),
                    "OOPS: {request}->{extend}"
                );
            }
        }
    }
}

/// Configure mimalloc for Syd usecase.
pub fn configure_mimalloc() {
    // Silence errors and warnings.
    mimalloc2_rust::GlobalMiMalloc::option_set(
        mimalloc2_rust::raw::runtime_options::mi_option_max_errors,
        0,
    );
    mimalloc2_rust::GlobalMiMalloc::option_set(
        mimalloc2_rust::raw::runtime_options::mi_option_max_warnings,
        0,
    );

    // Setting N to a higher value like 100 can improve performance
    // (sometimes by a lot) at the cost of potentially using more memory
    // at times. (Currently equal to MON_KEEPALIVE).
    mimalloc2_rust::GlobalMiMalloc::option_set(
        mimalloc2_rust::raw::runtime_options::mi_option_purge_delay,
        250,
    );
}
