//
// Syd: rock-solid application kernel
// src/hash.rs: Utilities for caching
//
// Copyright (c) 2024, 2025 Ali Polatel <alip@chesswob.org>
//
// SPDX-License-Identifier: GPL-3.0

use std::{
    borrow::Borrow,
    collections::HashMap,
    hash::Hash,
    net::IpAddr,
    os::fd::{AsRawFd, RawFd},
    sync::{Arc, Mutex},
};

use ahash::RandomState;
use nix::{
    errno::Errno,
    fcntl::{fcntl, readlinkat, FcntlArg, OFlag},
    sys::stat::mode_t,
    unistd::{close, Pid},
    NixPath,
};
use quick_cache::{
    sync::{Cache, GuardResult},
    Equivalent, Lifecycle, Weighter,
};
use serde::{ser::SerializeMap, Serialize, Serializer};

use crate::{
    compat::{
        fstatx, FileStatxTimestamp, STATX_CTIME, STATX_INO, STATX_MNT_ID, STATX_MNT_ID_UNIQUE,
        STATX_NLINK, STATX_TYPE,
    },
    config::*,
    elf::ExecutableFile,
    fs::{safe_open_raw, CanonicalPath, FileType},
    hook::RemoteProcess,
    libseccomp::ScmpSyscall,
    path::{XPath, XPathBuf},
    sandbox::{Action, Capability},
    ScmpNotifReq, SydArch, SydMemoryMap, SydSigSet,
};

/// Value type for action caches.
/// The boolean represents filtering.
pub type CacheVal = (Action, bool);

/// Key type for the Path Cache.
//#[derive(Clone, Debug, Hash, Eq, PartialEq)]
pub type PathCacheKey = (Capability, XPathBuf);

/// Key type for the IP Address Cache.
/// u16 represents the port.
pub type AddrCacheKey = (Capability, IpAddr, u16);

/// Path action cache to cache security policies about paths.
/// This is similar to Action Vector Cache of SELinux.
#[derive(Debug)]
pub struct PathCache(pub Cache<PathCacheKey, CacheVal>);

/// IP address action cache to cache security policies about internet addresses.
/// This is similar to Action Vector Cache of SELinux.
#[derive(Debug)]
pub struct AddrCache(pub Cache<AddrCacheKey, CacheVal>);

/// Fd map cached fd data type.
#[derive(Debug)]
pub struct CachedFd {
    pub(crate) fd: RawFd,
    pub(crate) i_node: u64,
    pub(crate) mnt_id: u64,
    pub(crate) f_type: FileType,
    pub(crate) c_time: FileStatxTimestamp,
    pub(crate) target: Option<XPathBuf>,
}

impl CachedFd {
    #[inline]
    pub(crate) fn is_static(&self) -> bool {
        self.fd == ROOT_FD()
            || self.fd == DEV_FD()
            || self.fd == PROC_FD()
            || self.fd == SYS_FD()
            || self.fd == NULL_FD()
    }
}

/// Fd map cache key type.
pub type FdCacheKey = XPathBuf;

/// Fd map cache value type.
pub type FdCacheEntry = CachedFd;

/// A weighter for Fd cache entries where specific entries can be pinned
/// to prevent eviction.
///
/// This weighter assigns a weight of 0 to pinned items, which should
/// never be evicted from the cache.  All other items receive a weight
/// of 1, indicating standard retention priority.  This system helps
/// optimize cache management by retaining critical resources in memory,
/// thereby enhancing the performance and reliability of file descriptor
/// operations.
#[derive(Debug, Clone)]
pub struct FdCacheWeighter;

const WEIGHT_MAX: u8 = u8::MAX;
impl Weighter<FdCacheKey, Arc<FdCacheEntry>> for FdCacheWeighter {
    fn weight(&self, key: &FdCacheKey, value: &Arc<FdCacheEntry>) -> u64 {
        // Do not expire pid-fds and static entries.
        match value.as_ref() {
            cached_fd if cached_fd.is_static() => 0,
            _ => {
                let weight = WEIGHT_MAX
                    .saturating_sub(
                        memchr::arch::all::memchr::One::new(b'/')
                            .count(key.as_bytes())
                            .try_into()
                            .unwrap_or(u8::MAX),
                    )
                    .saturating_add(1);
                /*
                crate::debug!("ctx": "cache", "op": "assign_cache_weight",
                    "path": key, "weight": weight);
                */
                weight.into()
            }
        }
    }
}

/// Pinned lifecycle for FdCache.
pub struct PinnedLifecycle<Key, Val>(std::marker::PhantomData<(Key, Val)>);

impl<Key, Val> Default for PinnedLifecycle<Key, Val> {
    #[inline]
    fn default() -> Self {
        Self(std::marker::PhantomData)
    }
}

impl<Key, Val> Clone for PinnedLifecycle<Key, Val> {
    #[inline]
    fn clone(&self) -> Self {
        Self(std::marker::PhantomData)
    }
}

impl Lifecycle<FdCacheKey, Arc<FdCacheEntry>> for PinnedLifecycle<FdCacheKey, Arc<FdCacheEntry>> {
    type RequestState = Option<()>;

    #[inline]
    fn begin_request(&self) -> Self::RequestState {
        None
    }

    #[inline]
    fn on_evict(&self, _state: &mut Self::RequestState, _key: FdCacheKey, _val: Arc<FdCacheEntry>) {
    }

    #[inline]
    fn is_pinned(&self, _key: &FdCacheKey, val: &Arc<FdCacheEntry>) -> bool {
        // Pin static entries.
        matches!(val.as_ref(), cached_fd if cached_fd.is_static())
    }
}

/// Path to Fd cache.
#[derive(Debug)]
#[allow(clippy::type_complexity)]
pub struct FdCache(
    pub  Cache<
        FdCacheKey,
        Arc<FdCacheEntry>,
        FdCacheWeighter,
        RandomState,
        PinnedLifecycle<FdCacheKey, Arc<FdCacheEntry>>,
    >,
);

/// Metadata on a blocking syscall invocation
#[derive(Copy, Clone, Debug)]
pub struct SysInterrupt {
    /// The thread group ID
    pub tgid: Pid,
    /// Syd handler thread ID
    pub handler: Pid,
    /// System call request
    pub request: ScmpNotifReq,
    /// True if `SA_RESTART` is ignored
    /// (e.g. due to a socket timeout).
    pub ignore_restart: bool,
}

/// This is the data type used to handle syscall interrupts.
#[derive(Debug)]
#[allow(clippy::type_complexity)]
pub struct SysInterruptMap {
    /// Map of blocking syscalls by request id.
    pub sys_block: Arc<Mutex<HashMap<u64, SysInterrupt, RandomState>>>,
    /// Map of restarting signals by TGID.
    /// Used for SA_RESTART tracking.
    pub sig_restart: Arc<Mutex<HashMap<Pid, SydSigSet, RandomState>>>,
}

/// Represents an exec(3) check result
#[derive(Debug)]
pub struct ExecResult {
    pub(crate) file: ExecutableFile,
    pub(crate) arch: u32,
    pub(crate) ip: u64,
    pub(crate) sp: u64,
    pub(crate) args: [u64; 6],
    pub(crate) ip_mem: Option<[u8; 64]>,
    pub(crate) sp_mem: Option<[u8; 64]>,
    pub(crate) memmap: Option<Vec<SydMemoryMap>>,
}

/// Represents a sigreturn(2) check result
#[derive(Debug)]
pub struct SigreturnResult {
    pub(crate) is_realtime: bool,
    pub(crate) ip: u64,
    pub(crate) sp: u64,
    pub(crate) args: [u64; 6],
    pub(crate) ip_mem: Option<[u8; 64]>,
    pub(crate) sp_mem: Option<[u8; 64]>,
}

/// Results map for ptrace(2) hooks chdir, execve, sigaction and sigreturn.
#[derive(Debug)]
#[allow(clippy::type_complexity)]
pub struct SysResultMap<'a> {
    /// syscall-agnostic error map
    pub trace_error: Arc<Mutex<HashMap<RemoteProcess, Errno, RandomState>>>,
    /// chdir(2) result map
    pub trace_chdir: Arc<Mutex<HashMap<RemoteProcess, CanonicalPath<'a>, RandomState>>>,
    /// exec(3) result map
    pub trace_execv: Arc<Mutex<HashMap<RemoteProcess, ExecResult, RandomState>>>,
    /// {rt_,}sigreturn(2) result map
    pub trace_sigret: Arc<Mutex<HashMap<RemoteProcess, SigreturnResult, RandomState>>>,
}

/// Signal map, used by signal counting for SROP mitigation:
/// If a TGID is not in sig_handle_map at the entry of sigreturn(2),
/// we terminate the process because the sigreturn(2) is artificial.
#[derive(Debug)]
#[allow(clippy::type_complexity)]
pub struct SignalMap {
    /// Set of TGIDs that have received count signals for handled signals.
    pub sig_handle: Arc<Mutex<HashMap<Pid, u64, RandomState>>>,
}

impl SysInterrupt {
    pub(crate) fn new(
        request: ScmpNotifReq,
        tgid: Pid,
        handler: Pid,
        ignore_restart: bool,
    ) -> Self {
        Self {
            tgid,
            handler,
            request,
            ignore_restart,
        }
    }
}

impl serde::Serialize for SysInterrupt {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let mut map = serializer.serialize_map(Some(3))?;

        let data = &self.request.data;
        let syscall = ScmpSyscall::get_name_by_arch(data.syscall, data.arch)
            .unwrap_or_else(|_| format!("{}", i32::from(data.syscall)));
        let _ = map.serialize_entry("pid", &self.request.pid);
        let _ = map.serialize_entry("tgid", &self.tgid.as_raw());
        let _ = map.serialize_entry("sys", &syscall);
        let _ = map.serialize_entry("arch", &SydArch(data.arch));
        let _ = map.serialize_entry("args", &data.args);
        let _ = map.serialize_entry("handler", &self.handler.as_raw());
        let _ = map.serialize_entry("ignore_restart", &self.ignore_restart);

        map.end()
    }
}

/// Convenience type to avoid cloning FdCacheKey during lookup.
#[derive(Hash)]
pub struct FdCap<'a>(pub &'a XPath);

impl Equivalent<FdCacheKey> for FdCap<'_> {
    fn equivalent(&self, key: &FdCacheKey) -> bool {
        self.0.is_equal(key.as_bytes())
    }
}

impl ToOwned for FdCap<'_> {
    type Owned = FdCacheKey;

    fn to_owned(&self) -> Self::Owned {
        self.0.to_owned()
    }
}

impl<'a> Borrow<FdCap<'a>> for FdCacheKey {
    fn borrow(&self) -> &FdCap<'a> {
        unreachable!()
    }
}

/// Convenience type to avoid cloning Paths during lookup.
#[derive(Hash)]
pub struct PathCap<'a>(pub Capability, pub &'a XPath);

impl Equivalent<PathCacheKey> for PathCap<'_> {
    fn equivalent(&self, key: &PathCacheKey) -> bool {
        self.0 == key.0 && *self.1 == *key.1
    }
}

impl ToOwned for PathCap<'_> {
    type Owned = PathCacheKey;

    fn to_owned(&self) -> Self::Owned {
        (self.0, XPathBuf::from(self.1))
    }
}

impl<'a> Borrow<PathCap<'a>> for PathCacheKey {
    fn borrow(&self) -> &PathCap<'a> {
        unreachable!();
    }
}

impl Drop for FdCacheEntry {
    #[allow(clippy::cognitive_complexity)]
    fn drop(&mut self) {
        let fd = match self {
            cached_fd if cached_fd.is_static() => {
                // Do not close static file descriptors.
                // The pinned lifecycle should not allow this so let's panic here.
                unreachable!(
                    "BUG: attempt to remove static cache entry with fd:{} and inode:{:#x}",
                    cached_fd.fd, cached_fd.i_node
                );
            }
            cached_fd => {
                /*
                debug!("ctx": "fd_cache", "op": "remove_entry",
                    "msg": format!("removing cache entry with fd:{} and inode:{:#x}",
                        cached_fd.fd, cached_fd.i_node),
                    "fd": cached_fd.fd,
                    "i_node": cached_fd.i_node,
                    "mnt_id": cached_fd.mnt_id,
                    "f_type": cached_fd.f_type,
                    "target": &cached_fd.target);
                */

                cached_fd.fd
            }
        };

        if cfg!(debug_assertions) && fcntl(fd, FcntlArg::F_GETFD) == Err(Errno::EBADF) {
            // File already closed? panic!
            panic!("IO Safety violation: cache file descriptor already closed");
        }

        // All good, close the file.
        // There's nothing we can do on errors.
        let _ = close(fd);
    }
}

impl Serialize for PathCache {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let mut map = serializer.serialize_map(Some(5))?;

        map.serialize_entry("hits", &self.0.hits())?;
        map.serialize_entry("misses", &self.0.misses())?;
        map.serialize_entry("len", &self.0.len())?;
        map.serialize_entry("cap", &self.0.capacity())?;
        //we use UnitWeighter, ie total weight is always equal to the length.
        //map.serialize_entry("weight", &self.0.weight())?;

        map.end()
    }
}

impl Serialize for AddrCache {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let mut map = serializer.serialize_map(Some(5))?;

        map.serialize_entry("hits", &self.0.hits())?;
        map.serialize_entry("misses", &self.0.misses())?;
        map.serialize_entry("len", &self.0.len())?;
        map.serialize_entry("cap", &self.0.capacity())?;
        //we use UnitWeighter, ie total weight is always equal to the length.
        //map.serialize_entry("weight", &self.0.weight())?;

        map.end()
    }
}

impl Serialize for FdCacheEntry {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let mut map = serializer.serialize_map(Some(5))?;
        map.serialize_entry("fd", &self.fd)?;
        map.serialize_entry("i_node", &self.i_node)?;
        map.serialize_entry("mnt_id", &self.mnt_id)?;
        map.serialize_entry("f_type", &self.f_type)?;
        map.serialize_entry("target", &self.target)?;
        map.end()
    }
}

impl Serialize for FdCache {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let mut map = serializer.serialize_map(Some(5))?;

        map.serialize_entry("hits", &self.0.hits())?;
        map.serialize_entry("misses", &self.0.misses())?;
        map.serialize_entry("len", &self.0.len())?;
        map.serialize_entry("cap", &self.0.capacity())?;
        map.serialize_entry("weight", &self.0.weight())?;

        map.end()
    }
}

impl FdCache {
    #[allow(clippy::cognitive_complexity)]
    pub(crate) fn add_path(
        &self,
        path: &XPath,
        fd: RawFd,
        want_dir: bool,
    ) -> Result<Arc<FdCacheEntry>, Errno> {
        // SAFETY: The path does not start with system paths here,
        // the caller is supposed to check for this. Moreover
        // path must not be a symbolic link!

        // Determine type, ctime, inode and mount id.
        let mut mask = STATX_CTIME | STATX_INO;
        mask |= if *HAVE_STATX_MNT_ID_UNIQUE {
            STATX_MNT_ID_UNIQUE
        } else {
            STATX_MNT_ID
        };
        if !want_dir {
            mask |= STATX_TYPE;
        }
        let stx = fstatx(&fd, mask)?;
        let file_type = if want_dir {
            FileType::Dir
        } else {
            FileType::from(mode_t::from(stx.stx_mode))
        };

        // Initialize cache entry.
        let entry = CachedFd {
            fd,
            f_type: file_type,
            i_node: stx.stx_ino,
            mnt_id: stx.stx_mnt_id,
            c_time: stx.stx_ctime,
            target: None,
        };

        /*
        debug!("ctx": "fd_cache", "op": "add_entry",
            "msg": format!("added entry with fd:{} and inode:{:#x} for path `{path}'",
                entry.fd, entry.i_node),
            "cache": &self,
            "path": path,
            "fd": entry.fd,
            "f_type": entry.f_type,
            "target": &entry.target,
            "i_node": entry.i_node,
            "mnt_id": entry.mnt_id,
            "c_time": entry.c_time);
        */

        // Save entry and return a clone.
        let entry = Arc::new(entry);
        self.0.insert(path.to_owned(), Arc::clone(&entry));
        Ok(entry)
    }

    #[allow(clippy::cognitive_complexity)]
    pub(crate) fn get_path(&self, path: &XPath) -> Result<Option<Arc<FdCacheEntry>>, Errno> {
        // SAFETY: Strip trailing slash to ensure sanity.
        let plen = path.len();
        #[allow(clippy::arithmetic_side_effects)]
        let path = if plen > 1 && path.as_bytes()[plen - 1] == b'/' {
            XPath::from_bytes(&path.as_bytes()[..plen - 1])
        } else {
            path
        };

        // SAFETY: Do not cache system paths.
        for pre in SKIP_CACHE_PATH_PREFIXES {
            if path.starts_with(pre) {
                return Ok(None);
            }
        }

        // We loop until we can return a valid entry or error-out.
        // The loop is needed to handle the case where we find an invalid
        // cached entry and must remove it, then attempt again atomically.
        loop {
            match self.0.get_value_or_guard(&FdCap(path), None) {
                // Case 1: We found a value in the cache,
                // verify it before returning.
                GuardResult::Value(cached_fd) => {
                    // If this is a "static" entry,
                    // it doesn't need validation.
                    if cached_fd.is_static() {
                        /*
                        debug!(
                            "ctx": "fd_cache",
                            "op": "hit_entry",
                            "msg": format!(
                                "hit static entry with fd:{} and inode:{:#x} for path `{path}'",
                                cached_fd.fd, cached_fd.i_node
                            ),
                            "cache": &self,
                            "path": path,
                            "fd": cached_fd.fd,
                            "f_type": cached_fd.f_type,
                            "target": &cached_fd.target,
                            "i_node": cached_fd.i_node,
                            "mnt_id": cached_fd.mnt_id,
                            "c_time": cached_fd.c_time
                        );
                        */

                        return Ok(Some(cached_fd));
                    }

                    // Validate via fstatx
                    let mut mask = STATX_CTIME | STATX_INO | STATX_NLINK;
                    mask |= if *HAVE_STATX_MNT_ID_UNIQUE {
                        STATX_MNT_ID_UNIQUE
                    } else {
                        STATX_MNT_ID
                    };

                    let mut is_valid = true;
                    // let mut stx = None;
                    match fstatx(&cached_fd.fd, mask) {
                        Ok(s) => {
                            if s.stx_nlink == 0
                                || s.stx_ino != cached_fd.i_node
                                || s.stx_mnt_id != cached_fd.mnt_id
                                || s.stx_ctime != cached_fd.c_time
                            {
                                is_valid = false;
                            } /* else {
                                  stx = Some(s);
                              }*/
                        }
                        Err(_) => {
                            is_valid = false;
                        }
                    }

                    if is_valid {
                        /*
                        debug!(
                            "ctx": "fd_cache",
                            "op": "hit_entry",
                            "msg": format!(
                                "valid entry with fd:{} and inode:{:#x} for path `{path}'",
                                cached_fd.fd, cached_fd.i_node
                            ),
                            "cache": &self,
                            "path": path,
                            "fd": cached_fd.fd,
                            "f_type": cached_fd.f_type,
                            "target": &cached_fd.target,
                            "i_node": cached_fd.i_node,
                            "mnt_id": cached_fd.mnt_id,
                            "c_time": cached_fd.c_time
                        );
                        */

                        return Ok(Some(cached_fd));
                    }

                    /*
                    // Not valid, remove from cache, and loop to re-insert.
                    if let Some(s) = stx {
                        debug!(
                            "ctx": "fd_cache",
                            "op": "remove_renamed_entry",
                            "msg": format!(
                                "removed renamed entry with fd:{} and inode:{:#x} for path `{path}'",
                                cached_fd.fd, cached_fd.i_node
                            ),
                            "cache": &self,
                            "path": path,
                            "fd": cached_fd.fd,
                            "f_type": cached_fd.f_type,
                            "target": &cached_fd.target,
                            "i_node": &[cached_fd.i_node, s.stx_ino],
                            "mnt_id": &[cached_fd.mnt_id, s.stx_mnt_id],
                            "c_time": &[cached_fd.c_time, s.stx_ctime]
                        );
                    } else {
                        debug!(
                            "ctx": "fd_cache",
                            "op": "remove_deleted_entry",
                            "msg": format!(
                                "removed deleted entry with fd:{} and inode:{:#x} for path `{path}'",
                                cached_fd.fd, cached_fd.i_node
                            ),
                            "cache": &self,
                            "path": path,
                            "fd": cached_fd.fd,
                            "f_type": cached_fd.f_type,
                            "target": &cached_fd.target,
                            "i_node": cached_fd.i_node,
                            "mnt_id": cached_fd.mnt_id,
                            "c_time": cached_fd.c_time
                        );
                    }
                    */

                    // Removing invalid entry from the cache:
                    self.0.remove(&FdCap(path));
                    // Go back to loop and attempt to re-insert properly.
                }

                // Case 2: We got a guard, meaning nothing was in the cache for `path`.
                // We can open the file and insert the new entry atomically.
                GuardResult::Guard(guard) => {
                    let new_entry = {
                        let (parent, base) = path.split();

                        // Try retrieving parent's fd from the cache if it exists.
                        let (parent_fd, base, parent_entry) =
                            if let Some(ref parent_cfd) = self.0.get(&FdCap(parent)) {
                                (
                                    parent_cfd.fd.as_raw_fd(),
                                    base,
                                    Some(Arc::clone(parent_cfd)),
                                )
                            } else if path.is_dev() {
                                let base = XPath::from_bytes(&path.as_bytes()[b"/dev/".len()..]);
                                (DEV_FD(), base, None)
                            } else if path.is_proc() {
                                let base = XPath::from_bytes(&path.as_bytes()[b"/proc/".len()..]);
                                (PROC_FD(), base, None)
                            } else if path.is_sys() {
                                let base = XPath::from_bytes(&path.as_bytes()[b"/sys/".len()..]);
                                (SYS_FD(), base, None)
                            } else {
                                let base = XPath::from_bytes(&path.as_bytes()[b"/".len()..]);
                                (ROOT_FD(), base, None)
                            };

                        // Open the file.
                        let fd = safe_open_raw(
                            Some(&parent_fd),
                            base,
                            OFlag::O_PATH | OFlag::O_NOFOLLOW,
                        )?;
                        drop(parent_entry); // decrement the refcount.

                        // fstatx to gather metadata for caching.
                        let mut mask = STATX_TYPE | STATX_CTIME | STATX_INO;
                        mask |= if *HAVE_STATX_MNT_ID_UNIQUE {
                            STATX_MNT_ID_UNIQUE
                        } else {
                            STATX_MNT_ID
                        };
                        let stx = fstatx(&fd, mask)?;

                        // If it's a symlink, attempt to store target for reference.
                        let file_type = FileType::from(mode_t::from(stx.stx_mode));
                        let target = if file_type.is_symlink() {
                            match readlinkat(Some(fd.as_raw_fd()), XPath::empty())
                                .map(XPathBuf::from)
                            {
                                Ok(t) => Some(t),
                                Err(Errno::EINVAL | Errno::ENOENT) => None,
                                Err(e) => return Err(e),
                            }
                        } else {
                            None
                        };

                        CachedFd {
                            fd,
                            f_type: file_type,
                            i_node: stx.stx_ino,
                            mnt_id: stx.stx_mnt_id,
                            c_time: stx.stx_ctime,
                            target,
                        }
                    };

                    /*
                    debug!(
                        "ctx": "fd_cache",
                        "op": "add_entry",
                        "msg": format!(
                            "added entry with fd:{} and inode:{:#x} for path `{path}'",
                            new_entry.fd, new_entry.i_node
                        ),
                        "cache": &self,
                        "path": path,
                        "fd": new_entry.fd,
                        "f_type": new_entry.f_type,
                        "target": &new_entry.target,
                        "i_node": new_entry.i_node,
                        "mnt_id": new_entry.mnt_id,
                        "c_time": new_entry.c_time
                    );
                    */

                    let entry = Arc::new(new_entry);

                    // Inserting into the cache: once guard.insert(...)
                    // is called, other threads blocked on the same key
                    // can proceed atomically.
                    let _ = guard.insert(Arc::clone(&entry));
                    return Ok(Some(entry));
                }
                GuardResult::Timeout => {
                    // SAFETY: We never pass a timeout, this cannot happen.
                    unreachable!("BUG: FdCache returned invalid timeout!");
                }
            }
        }
    }
}

/// Create a new SysInterruptMap.
pub fn sys_interrupt_map_new() -> SysInterruptMap {
    SysInterruptMap {
        sys_block: Arc::new(Mutex::new(HashMap::with_hasher(RandomState::new()))),
        sig_restart: Arc::new(Mutex::new(HashMap::with_hasher(RandomState::new()))),
    }
}

/// Create a new SysResultMap.
pub fn sys_result_map_new<'a>() -> SysResultMap<'a> {
    SysResultMap {
        trace_error: Arc::new(Mutex::new(HashMap::with_hasher(RandomState::new()))),
        trace_chdir: Arc::new(Mutex::new(HashMap::with_hasher(RandomState::new()))),
        trace_execv: Arc::new(Mutex::new(HashMap::with_hasher(RandomState::new()))),
        trace_sigret: Arc::new(Mutex::new(HashMap::with_hasher(RandomState::new()))),
    }
}

/// Create a new SignalMap.
pub fn signal_map_new() -> SignalMap {
    SignalMap {
        sig_handle: Arc::new(Mutex::new(HashMap::with_hasher(RandomState::new()))),
    }
}

/// Create a new Path Action Cache.
pub fn path_cache_new(estimated_items_capacity: usize) -> PathCache {
    let cache = Cache::<PathCacheKey, CacheVal>::new(estimated_items_capacity);
    PathCache(cache)
}

/// Create a new Address Action Cache.
pub fn addr_cache_new(estimated_items_capacity: usize) -> AddrCache {
    let cache = Cache::<AddrCacheKey, CacheVal>::new(estimated_items_capacity);
    AddrCache(cache)
}

/// Create a new Fd to Path cache.
#[allow(clippy::arithmetic_side_effects)]
#[allow(clippy::disallowed_methods)]
pub fn fd_cache_new(estimated_items_capacity: usize) -> FdCache {
    let weight_max = WEIGHT_MAX as usize + 1;

    // proc_init opens 5 static FDs.
    let items_ecap = estimated_items_capacity
        .max(weight_max)
        .saturating_add(5)
        .saturating_div(weight_max);
    let weight_cap = estimated_items_capacity as u64;

    let cache = Cache::<
        FdCacheKey,
        Arc<FdCacheEntry>,
        FdCacheWeighter,
        RandomState,
        PinnedLifecycle<FdCacheKey, Arc<FdCacheEntry>>,
    >::with(
        items_ecap,
        weight_cap,
        FdCacheWeighter,
        RandomState::new(),
        PinnedLifecycle::default(),
    );

    // Populate file map with `/`, `/dev`, `/proc`, and `sys` directories,
    // and `/dev/null`.
    let mut mask = STATX_CTIME | STATX_INO;
    mask |= if *HAVE_STATX_MNT_ID_UNIQUE {
        STATX_MNT_ID_UNIQUE
    } else {
        STATX_MNT_ID
    };

    let stat = fstatx(&ROOT_FD(), mask).unwrap();
    let root = CachedFd {
        fd: ROOT_FD(),
        f_type: FileType::Dir,
        i_node: stat.stx_ino,
        mnt_id: stat.stx_mnt_id,
        c_time: stat.stx_ctime,
        target: None,
    };
    cache.insert(XPathBuf::from("/"), root.into());

    let stat = fstatx(&DEV_FD(), mask).unwrap();
    let dev = CachedFd {
        fd: DEV_FD(),
        f_type: FileType::Dir,
        i_node: stat.stx_ino,
        mnt_id: stat.stx_mnt_id,
        c_time: stat.stx_ctime,
        target: None,
    };
    cache.insert(XPathBuf::from("/dev"), dev.into());

    let stat = fstatx(&PROC_FD(), mask).unwrap();
    let proc = CachedFd {
        fd: PROC_FD(),
        f_type: FileType::Dir,
        i_node: stat.stx_ino,
        mnt_id: stat.stx_mnt_id,
        c_time: stat.stx_ctime,
        target: None,
    };
    cache.insert(XPathBuf::from("/proc"), proc.into());

    let stat = fstatx(&SYS_FD(), mask).unwrap();
    let sys = CachedFd {
        fd: SYS_FD(),
        f_type: FileType::Dir,
        i_node: stat.stx_ino,
        mnt_id: stat.stx_mnt_id,
        c_time: stat.stx_ctime,
        target: None,
    };
    cache.insert(XPathBuf::from("/sys"), sys.into());

    let stat = fstatx(&NULL_FD(), mask).unwrap();
    let null = CachedFd {
        fd: NULL_FD(),
        f_type: FileType::Chr,
        i_node: stat.stx_ino,
        mnt_id: stat.stx_mnt_id,
        c_time: stat.stx_ctime,
        target: None,
    };
    cache.insert(XPathBuf::from("/dev/null"), null.into());

    FdCache(cache)
}
