//
// Syd: rock-solid application kernel
// src/lib.rs: Common utility functions
//
// Copyright (c) 2023, 2024, 2025, 2026 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

//! # 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.exherbo.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.exherbo.org/paludis_270.png)](https://paludis.exherbo.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 syd.rs.
#![deny(missing_docs)]
#![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)]

/// JSON serializers for syd(2) API
pub mod api;
/// Assembly instruction decoder
#[cfg(feature = "asm")]
pub mod asm;
/// Cgroup v2 management for resource limits
pub mod cgroup;
/// System call argument cookies
pub mod cookie;
/// Utilities to mask sensitive information in proc files
pub(crate) mod mask;
/// Safe mount interface
pub mod mount;
/// System call handlers
#[macro_use]
pub(crate) mod kernel;
/// Worker threads
pub(crate) mod workers;

/// Utilities for caching
pub(crate) mod cache;
/// Compatibility code for different libcs
#[expect(missing_docs)]
pub mod compat;
/// Static configuration, edit & recompile!
pub mod config;
/// Sandboxing utilities
pub mod confine;
/// DNS utilities
pub mod dns;
/// ELF parser
pub mod elf;
/// Error types and error handling code.
pub mod err;
/// File descriptor utilities
pub mod fd;
/// File descriptor map for path canonicalization
pub(crate) mod filemap;
/// Filesystem utilities
pub mod fs;
/// Utilities for hashing
pub mod hash;
/// Secure computing hooks
pub mod hook;
/// I/O utilities
pub mod io;
/// ioctl(2) request decoder
pub mod ioctl;
/// Landlock policy helper library for Syd
pub mod landlock_policy;
/// Simple logging on standard error using JSON lines
pub mod log;
/// Path lookup and canonicalization utilities
pub mod lookup;
/// magic symlink utilities
pub mod magic;
/// Namespace utilities
pub mod namespace;
/// Interface to Open File Description locks
pub mod ofd;
/// /proc and syd(2) nom parsers
pub mod parsers;
/// Path handling for UNIX
pub mod path;
/// /proc utilities
pub mod proc;
/// ptrace(2) utilities
pub mod ptrace;
/// PTY utilities
pub mod pty;
/// seccomp(2) notify request handling
pub mod req;
/// Utilities to handle restarting syscalls
pub mod retry;
/// OS Random Number Generator (RNG) interface
pub mod rng;
/// Sandbox configuration
pub mod sandbox;
/// Execute program as sealed anonymous file
pub mod seal;
/// SealBox<T> for type-safe sealing/protecting
#[expect(clippy::disallowed_types)]
pub mod sealbox;
/// Portable sigset that can handle reserved signals
pub mod sigset;
/// Interface to Linux prctl(2) speculation misfeature interface
pub mod spec;
/// sysinfo(2) interface
pub mod sysinfo;
/// syslog(2) interface
pub mod syslog;
/// Per-thread SIGALRM timer
pub mod timer;
/// Interface to uname(2)
pub mod uts;
/// Shell-style wildcard matching
#[expect(clippy::arithmetic_side_effects)]
pub mod wildmatch;
/// Interface to wordexp(3)
#[cfg(not(target_os = "android"))]
pub mod wordexp;
/// Extended attribute utilities
pub mod xattr;

// Vendored crates:
/// Interface to Linux capabilities
#[expect(missing_docs)]
#[expect(clippy::arithmetic_side_effects)]
#[expect(clippy::undocumented_unsafe_blocks)]
pub mod caps;
/// Interface to LandLock LSM
#[expect(missing_docs)]
#[expect(unused_imports)]
#[expect(clippy::as_underscore)]
#[expect(clippy::borrow_as_ptr)]
#[expect(clippy::cast_lossless)]
#[expect(clippy::cast_possible_truncation)]
#[expect(clippy::decimal_literal_representation)]
#[expect(clippy::default_trait_access)]
#[expect(clippy::disallowed_methods)]
#[expect(clippy::init_numbered_fields)]
#[expect(clippy::undocumented_unsafe_blocks)]
pub mod landlock;
/// rusty_pool: Self growing / shrinking `ThreadPool` implementation
pub(crate) mod pool;
/// The low-level interface for linux namespaces (containers)
pub mod unshare;

use std::ffi::OsStr;

use lexis::ToName;
use libseccomp::ScmpVersion;
use nix::{
    errno::Errno,
    sched::CloneFlags,
    sys::{
        resource::{getrlimit, Resource},
        signal::{sigaction, signal, SaFlags, SigAction, SigHandler, SigSet, Signal},
        utsname::uname,
    },
    unistd::{Gid, Group, Uid, User},
};
use serde::{Serialize, Serializer};

use crate::{
    compat::{fstatx, lsm_list_modules},
    confine::{
        apparmor_enabled, check_cross_memory_attach, check_unix_diag, is_coredump, lock_enabled,
        ns_enabled, seccomp_arch_native_name, selinux_enabled, selinux_enforced, vdso_list_calls,
        SydArch, SydPersona, SCMP_ARCH,
    },
    err::err2no,
    hash::{aes_ctr_info, check_setsockopt_serial_support, hmac_sha256_info, key_ring_validate},
    landlock::ABI,
    path::{XPath, XPathBuf},
    proc::{proc_fs_file_max, proc_fs_nr_open, proc_kernel_randomize_va_space, proc_kernel_taint},
    sealbox::check_mseal_support,
    spec::{speculation_get, SpeculationFeature},
};

/* Macros */

/// Convenience macro to define a main function with correct errno return.
#[macro_export]
macro_rules! main {
    { $($body:tt)* } => {
        fn main() -> std::process::ExitCode {
            match (|| -> syd::err::SydResult<std::process::ExitCode> { $($body)* })() {
                Ok(code) => code,
                Err(err) => {
                    use std::io::Write;

                    let desc = format!("Error: {err}\n");
                    let _ = std::io::stderr().write_all(desc.as_bytes());

                    u8::try_from(
                        err.errno()
                           .map(|e| e as i32)
                           .unwrap_or(128)
                    )
                    .map(std::process::ExitCode::from)
                    .unwrap_or(std::process::ExitCode::FAILURE)
                }
            }
        }
    };
}

/* Utilities */

/// Print Syd version information,
/// and information about the system to
/// standard output.
#[expect(clippy::cognitive_complexity)]
pub fn syd_info(verbose: bool) -> Result<(), Errno> {
    use crate::config::*;

    printfln!("syd {} ({})", *crate::config::VERSION, syd_code_name())?;
    printfln!("Rock solid application kernel")?;
    printfln!("Author: Ali Polatel <alip@chesswob.org>")?;
    printfln!("License: GPL-3.0-only")?;

    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",
        #[cfg(feature = "prof")]
        "+prof",
        #[cfg(not(feature = "prof"))]
        "-prof",
    ];
    printfln!("Features: {}", feat.join(", "))?;

    if !verbose {
        return Ok(());
    }

    let alloc = if cfg!(all(
        not(feature = "prof"),
        target_page_size_4k,
        target_pointer_width = "64"
    )) {
        "GrapheneOS"
    } else if cfg!(feature = "prof") {
        "TCMalloc"
    } else {
        "Libc"
    };
    printfln!("Allocator: {alloc}")?;

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

    match proc_kernel_taint() {
        Ok(tflags) => printfln!("{tflags}"),
        Err(errno) => printfln!("Kernel may be tainted (error: {errno})."),
    }?;

    let aslr = match proc_kernel_randomize_va_space() {
        Ok(0) => "disabled".to_string(),
        Ok(1) => "enabled (stack, mmap, VDSO; PIE text randomized)".to_string(),
        Ok(2) => "enabled (heap + stack, mmap, VDSO; PIE text randomized)".to_string(),
        Ok(n) => format!("{n} (error: {})", Errno::EINVAL),
        Err(errno) => format!("? (error: {errno})"),
    };
    printfln!("ASLR is {aslr}.")?;

    #[expect(clippy::disallowed_methods)]
    let bpf_jit = match std::fs::read_to_string("/proc/sys/net/core/bpf_jit_enable") {
        Ok(val) => match val.trim() {
            "0" => "disabled".to_string(),
            "1" => "enabled".to_string(),
            "2" => "enabled in debug mode".to_string(),
            n => format!("{n} (error: {})", Errno::EINVAL),
        },
        Err(err) => format!("? (error: {})", err2no(&err)),
    };
    printfln!("BPF JIT compiler is {bpf_jit}.")?;

    let abi = ABI::new_current();
    if abi == ABI::Unsupported {
        printfln!("Landlock is not supported.")?;
    } else {
        let state = lock_enabled(abi);
        let state_verb = match state {
            0 => "fully enforced",
            1 => "partially enforced",
            2 => "not enforced",
            _ => "unsupported",
        };
        printfln!("Landlock ABI {} is {state_verb}.", abi as i32)?;
    }

    printfln!(
        "User namespaces are {}supported.",
        if ns_enabled(CloneFlags::CLONE_NEWUSER).unwrap_or(false) {
            ""
        } else {
            "not "
        }
    )?;

    // Check CONFIG_CROSS_MEMORY_ATTACH.
    let cfg_cma = check_cross_memory_attach();
    printfln!(
        "Cross memory attach is {}supported{}",
        if cfg_cma { "" } else { "not " },
        if cfg_cma {
            "."
        } else {
            " (\x1b[91minsecure\x1b[0m)."
        },
    )?;

    printfln!(
        "Memory sealing is {}supported.",
        if check_mseal_support() { "" } else { "not " }
    )?;

    // Check CONFIG_UNIX_DIAG support.
    let unix_diag = match check_unix_diag() {
        Ok(true) => "supported".to_string(),
        Ok(false) => "not supported".to_string(),
        Err(errno) => format!("unknown (error: {errno})"),
    };
    printfln!("UNIX socket diagnostics are {unix_diag}.")?;

    // Print ALG_SET_KEY_BY_KEY_SERIAL support.
    printfln!(
        "Algorithm sockets {} keyrings(7) support.",
        if check_setsockopt_serial_support() {
            "have"
        } else {
            "doesn't have"
        }
    )?;

    // Print whether session keyring is attached to the user keyring.
    match key_ring_validate() {
        Ok(()) => {
            printfln!("Session keyring is attached to the user keyring.")?;
        }
        Err(errno) => {
            printfln!("Session keyring isn't attached to the user keyring: {errno}!")?;
        }
    }

    // Print ctr(aes) kernel support.
    printfln!("{}", aes_ctr_info())?;
    // Print hmac(sha256) kernel support.
    printfln!("{}", hmac_sha256_info())?;

    // List LSMs.
    let lsms = match lsm_list_modules() {
        Ok(lsms) => lsms
            .into_iter()
            .map(|s| s.to_string())
            .collect::<Vec<String>>()
            .join(", "),
        Err(Errno::ENOENT) => "none loaded".to_string(),
        Err(errno) => format!("? (error: {errno})"),
    };
    printfln!("LSMs: {lsms}.")?;

    // Log SELinux and Apparmor status.
    let selinux = match selinux_enabled() {
        Ok(true) => {
            let enforce = if selinux_enforced().unwrap_or(false) {
                "Enforcing"
            } else {
                "Permissive"
            };
            format!("enabled ({enforce})")
        }
        Ok(false) => "disabled".to_string(),
        Err(errno) => format!("? (error: {errno})"),
    };
    let apparmor = match apparmor_enabled() {
        Ok(true) => "enabled".to_string(),
        Ok(false) => "disabled".to_string(),
        Err(errno) => format!("? (error: {errno})"),
    };
    printfln!("SELinux is {selinux}.")?;
    printfln!("AppArmor is {apparmor}.")?;

    // List vDSO calls.
    match vdso_list_calls() {
        Ok(names) if names.is_empty() => printfln!("No vDSO calls found.")?,
        Ok(names) => {
            let names = names
                .iter()
                .map(|s| s.to_string_lossy())
                .collect::<Vec<_>>()
                .join(", ");
            printfln!("List of vDSO calls: {names}.")?;
        }
        Err(error) => printfln!("List of vDSO calls: ? (error: {error}).")?,
    }

    // Print information on open file limits.
    let (nofile_soft, nofile_hard) = getrlimit(Resource::RLIMIT_NOFILE).unwrap_or((0, 0));
    printf!("Open file limits: {nofile_soft} soft, {nofile_hard} hard, ")?;

    // Lookup system-wide open file limits.
    let file_max = proc_fs_file_max().unwrap_or(0);
    let nr_open = proc_fs_nr_open().unwrap_or(0);
    printfln!("{nr_open} nr_open, {file_max} file-max")?;

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

    // Print detected host Linux kernel version and related features.
    printf!("Host Linux: {}.{} with", KERNEL_VERSION.0, KERNEL_VERSION.1)?;
    printf!(" mmap_min_addr={}", *MMAP_MIN_ADDR)?;
    printf!(", page_size={}", *PAGE_SIZE)?;
    printf!(
        ", {}at_execve_check",
        if *HAVE_AT_EXECVE_CHECK { "+" } else { "-" }
    )?;
    printf!(
        ", {}landlock_scoped_signals",
        if *HAVE_LANDLOCK_SCOPED_SIGNALS {
            "+"
        } else {
            "-"
        }
    )?;
    printf!(
        ", {}madv_guard_install",
        if *HAVE_MADV_GUARD { "+" } else { "-" }
    )?;
    printf!(
        ", {}namespaced_pid_max",
        if *HAVE_NAMESPACED_PID_MAX { "+" } else { "-" }
    )?;
    printf!(
        ", {}pidfd_thread",
        if *HAVE_PIDFD_THREAD { "+" } else { "-" }
    )?;
    printf!(
        ", {}procmap_query",
        if *HAVE_PROCMAP_QUERY { "+" } else { "-" }
    )?;
    printf!(
        ", {}proc_pid_fd_stat_size",
        if *HAVE_PROC_PID_FD_STAT_SIZE {
            "+"
        } else {
            "-"
        }
    )?;
    printf!(
        ", {}pwritev2_rwf_noappend",
        if *HAVE_RWF_NOAPPEND { "+" } else { "-" }
    )?;
    printf!(
        ", {}seccomp_user_notif_fd_sync_wake_up",
        if *HAVE_SECCOMP_USER_NOTIF_FD_SYNC_WAKE_UP {
            "+"
        } else {
            "-"
        }
    )?;
    printfln!(
        ", {}statx_mnt_id_unique",
        if *HAVE_STATX_MNT_ID_UNIQUE { "+" } else { "-" }
    )?;

    // SAFETY: In libc we trust.
    // Note: nix version truncates unknown bits which we don't want.
    let pers = match SydPersona::get() {
        Ok(pers) => pers.to_string(),
        Err(errno) => format!("? (error: {errno})"),
    };

    printfln!(
        "Environment: {}-{pers}-{}",
        env!("SYD_TARGET_ENV"),
        env!("SYD_TARGET_POINTER_WIDTH")
    )?;

    // Log supported architectures.
    let arch = SCMP_ARCH
        .iter()
        .map(SydArch::from)
        .map(|arch| arch.to_string())
        .collect::<Vec<_>>()
        .join(", ");
    printfln!("Architectures: {arch}")?;

    // Log architectures with ipc(2), socketcall(2) multiplexer support.
    let mut has_ipc = Vec::new();
    let mut has_socketcall = Vec::new();
    for arch in SCMP_ARCH.iter().map(SydArch::from) {
        if arch.has_ipc() {
            has_ipc.push(arch.to_string());
        }
        if arch.has_socketcall() {
            has_socketcall.push(arch.to_string());
        }
    }
    if !has_ipc.is_empty() {
        let plurals = if has_ipc.len() > 1 { "s" } else { "" };
        let verbone = if has_ipc.len() == 1 { "s" } else { "" };
        let has_ipc = has_ipc.join(", ");
        printfln!("Architecture{plurals} {has_ipc} support{verbone} ipc(2) multiplexer.")?;
    }
    if !has_socketcall.is_empty() {
        let plurals = if has_socketcall.len() > 1 { "s" } else { "" };
        let verbone = if has_socketcall.len() == 1 { "s" } else { "" };
        let has_socketcall = has_socketcall.join(", ");
        printfln!(
            "Architecture{plurals} {has_socketcall} support{verbone} socketcall(2) multiplexer."
        )?;
    }

    printfln!(
        "CPU: {} ({} cores), {}-endian",
        num_cpus::get(),
        num_cpus::get_physical(),
        env!("SYD_TARGET_ENDIAN")
    )?;
    printfln!("CPUFLAGS: {}", env!("SYD_TARGET_FEATURE"))?;

    for spec_feat in [
        SpeculationFeature::StoreBypass,
        SpeculationFeature::IndirectBranch,
        SpeculationFeature::L1DFlush,
    ] {
        printfln!(
            "{}",
            match speculation_get(spec_feat) {
                Ok(status) => status.to_string(),
                Err(errno) => format!("{spec_feat} status: ? (error: {errno})"),
            }
        )?;
    }

    Ok(())
}

/// Print Syd code name.
pub fn syd_code_name() -> String {
    #[expect(clippy::disallowed_methods)]
    let major = env!("CARGO_PKG_VERSION_MAJOR")
        .parse::<u64>()
        .expect("CARGO_PKG_VERSION_MAJOR");
    #[expect(clippy::disallowed_methods)]
    let minor = env!("CARGO_PKG_VERSION_MINOR")
        .parse::<u64>()
        .expect("CARGO_PKG_VERSION_MINOR");
    #[expect(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;
    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(" ")
}

/// 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 "/proc/self/fdinfo".
pub fn get_user_home(username: &str) -> XPathBuf {
    // Fetch user details.
    match User::from_name(username) {
        Ok(Some(user)) => user.dir.into(),
        _ => "/proc/self/fdinfo".into(),
    }
}

/// 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)
}

bitflags::bitflags! {
    /// Represents valid `ignore_signals` options.
    #[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
    pub struct IgnoreSignalOpts: u8 {
        /// Skip ignoring the `SIGALRM` signal.
        const SkipIgnoreAlarm = 1 << 0;
        /// Skip ignoring signals with default action Core.
        const SkipIgnoreCoreDump = 1 << 1;
    }
}

impl Serialize for IgnoreSignalOpts {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let mut opts: Vec<&str> = vec![];

        if self.is_empty() {
            return serializer.collect_seq(opts);
        }
        if self.contains(Self::SkipIgnoreAlarm) {
            opts.push("skip_ignore_alarm");
        }
        if self.contains(Self::SkipIgnoreCoreDump) {
            opts.push("skip_ignore_core_dump");
        }

        opts.sort();
        serializer.collect_seq(opts)
    }
}

/// Ignores all signals except SIG{ALRM,CHLD,KILL,STOP}.
///
/// Skips ignoring SIGPROF signal when profiling is enabled with the `prof` feature.
/// Skips ignoring SIGALRM signal if `IgnoreSignalOpts::SkipIgnoreAlarm` is set.
/// Skips ignoring signals with default action Core if `IgnoreSignalOpts::SkipIgnoreCoreDump` is set.
pub fn ignore_signals(opts: IgnoreSignalOpts) -> Result<(), Errno> {
    // Iterate through all possible signals and set them to be ignored.
    // Step 1: Normal signals.
    for signal in Signal::iterator() {
        // 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. SIGPROF is used for profiling.
        match signal {
            Signal::SIGCHLD | Signal::SIGKILL | Signal::SIGSTOP => {}
            #[cfg(feature = "prof")]
            Signal::SIGPROF => {}
            Signal::SIGALRM if opts.contains(IgnoreSignalOpts::SkipIgnoreAlarm) => {}
            signal
                if opts.contains(IgnoreSignalOpts::SkipIgnoreCoreDump)
                    && is_coredump(signal as i32) => {}
            signal => ignore_signal(signal)?,
        }
    }

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

    Ok(())
}

const IOPRIO_CLASS_IDLE: i32 = 3;
const IOPRIO_WHO_PROCESS: i32 = 1;

/// Sets the I/O priority of the current thread to idle.
///
/// This function uses the `ioprio_set` syscall to set the I/O
/// scheduling priority of the current thread to the idle class. The
/// idle I/O class is designed for tasks that should only use disk
/// resources when no other process needs them. When a thread is set to
/// idle, it will not compete with other (non-idle) processes for I/O
/// bandwidth.
///
/// Note that this setting is applied at the thread level in Linux,
/// where each thread is treated as a separate scheduling entity. As a
/// result, calling this function will only affect the I/O priority of
/// the thread from which it is called. If the application is
/// multi-threaded and a global I/O priority change is desired, this
/// function needs to be called from each thread, or specific threads
/// requiring the priority change should be targeted.
///
/// The function does not require any parameters and returns a `Result`:
/// - `Ok(())` on success.
/// - `Err(Errno)` containing Errno.
///
/// # Safety
///
/// This function involves an unsafe block due to the direct system call
/// (`libc::syscall`). The `ioprio_set` syscall is considered
/// unsafe as it directly interfaces with the kernel, bypassing Rust's
/// safety guarantees.  However, the usage in this context is safe given
/// that:
/// - We are specifying `IOPRIO_WHO_PROCESS` with `0`, which correctly
///   targets the current thread.
/// - The `ioprio` value is correctly constructed for the idle I/O
///   class.
///
/// Users of this function do not need to take any special safety precautions.
pub(crate) fn set_io_priority_idle() -> Result<(), Errno> {
    // Set I/O priority: higher bits for the class, lower bits for the priority.
    // IOPRIO_CLASS_IDLE is shifted left by 13 bits to fit the class into higher bits.
    // Priority for idle class is not used, hence set to 0 (lower 13 bits).
    let ioprio = IOPRIO_CLASS_IDLE << 13;

    // SAFETY:
    // The syscall libc::SYS_ioprio_set is used to set the I/O priority
    // of a process. This call is considered unsafe because it involves
    // a direct system call, which bypasses the safety checks and
    // abstractions provided by Rust. However, this usage is safe under
    // the following conditions:
    // 1. The first argument IOPRIO_WHO_PROCESS specifies the target as
    //    a process.
    // 2. The second argument 0 refers to the current process. In the
    //    context of ioprio_set, passing 0 for the 'who' parameter
    //    targets the calling process. This is why getpid() is not
    //    necessary here, as 0 implicitly represents the current
    //    process's PID.
    // 3. The third argument ioprio is correctly constructed with a
    //    valid I/O class and priority, ensuring the syscall behaves as
    //    expected.
    Errno::result(unsafe { libc::syscall(libc::SYS_ioprio_set, IOPRIO_WHO_PROCESS, 0, ioprio) })
        .map(drop)
}

/// Set the current thread's CPU scheduling policy to 'idle'.
///
/// This function sets the CPU scheduling policy of the current thread
/// to SCHED_IDLE, indicating that the thread should only be scheduled
/// to run when the system is idle.
///
/// # Returns
///
/// * `Ok(())` on successful setting of the scheduling policy and priority.
/// * `Err` on failure, with the specific error indicating the cause of the failure.
pub(crate) fn set_cpu_priority_idle() -> Result<(), Errno> {
    // SAFETY: We zero out the sched_param struct. This is safe because:
    // 1. sched_param is a plain data struct with no invariants related
    //    to its fields.
    // 2. All-zero is a valid representation for this struct in the
    //    context of SCHED_IDLE policy.
    let param: libc::sched_param = unsafe { std::mem::zeroed() };

    // SAFETY: The call to libc::sched_setscheduler is safe because:
    // 1. We are passing valid arguments: a PID of 0 for the current
    //    thread, a valid policy (SCHED_IDLE), and a pointer to a
    //    properly initialized sched_param structure.
    // 2. There are no thread-safety issues since the operation only
    //    affects the current thread.
    Errno::result(unsafe {
        libc::sched_setscheduler(0, libc::SCHED_IDLE, std::ptr::addr_of!(param))
    })
    .map(drop)
}

/// Simple human size formatter.
#[expect(clippy::arithmetic_side_effects)]
#[expect(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])
}

/// 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::<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::<libc::gid_t>().or(Err(Errno::EINVAL))?,
        ))
    } else if let Some(group) = Group::from_name(name)? {
        Ok(group.gid)
    } else {
        Err(Errno::ENOENT)
    }
}

/// 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)
}

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

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

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

/// Write the message to the invalid fd -31415.
/// The idea is to look for it in strace logs.
pub fn t(msg: &str) {
    let buf = msg.as_bytes();
    let len = buf.len() as libc::size_t;
    // SAFETY: writing to an invalid fd.
    unsafe { libc::syscall(libc::SYS_write, -31415, buf.as_ptr(), len) };
}

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

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

#[cfg(feature = "prof")]
#[inline(always)]
#[expect(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)]
#[expect(dead_code)]
pub(crate) fn start_cpu_profile(_name: &str) {}

#[cfg(feature = "prof")]
#[inline(always)]
#[expect(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)]
#[expect(dead_code)]
pub(crate) fn stop_cpu_profile() {}

#[cfg(feature = "prof")]
#[inline(always)]
#[expect(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)]
#[expect(dead_code)]
pub(crate) fn start_mem_profile(_name: &str) {}

#[cfg(feature = "prof")]
#[inline(always)]
#[expect(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)]
#[expect(dead_code)]
pub(crate) fn dump_mem_profile(_name: &str) {}

#[cfg(feature = "prof")]
#[inline(always)]
#[expect(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)]
#[expect(dead_code)]
pub(crate) fn stop_mem_profile() {}
