//
// Syd: rock-solid application kernel
// src/pool.rs: Self growing / shrinking `ThreadPool` implementation
//
// Copyright (c) 2024, 2025 Ali Polatel <alip@chesswob.org>
// Based in part upon rusty_pool which is:
//     Copyright (c) Robin Friedli <robinfriedli@icloud.com>
//     SPDX-License-Identifier: Apache-2.0
//
// SPDX-License-Identifier: GPL-3.0

// Last sync with rusty_pool:
// Version 0.7.0
// Commit:d56805869ba3cbe47021d5660bbaf19ac5ec4bfb

use std::{
    collections::{hash_map::Entry, HashMap},
    env,
    mem::MaybeUninit,
    net::IpAddr,
    option::Option,
    os::fd::{AsRawFd, BorrowedFd, FromRawFd, OwnedFd, RawFd},
    sync::{
        atomic::{AtomicBool, AtomicUsize, Ordering},
        Arc, Mutex, RwLock,
    },
    thread,
    thread::JoinHandle,
};

use ahash::RandomState;
use nix::{
    errno::Errno,
    fcntl::{splice, tee, OFlag, SpliceFFlags},
    poll::PollTimeout,
    sched::{sched_setaffinity, unshare, CloneFlags, CpuSet},
    sys::{
        epoll::{Epoll, EpollEvent, EpollFlags},
        signal::{sigaction, SaFlags, SigAction, SigHandler, SigSet, Signal},
    },
    unistd::{close, getpid, gettid, lseek64, pipe2, write, Pid, Whence},
};
use quick_cache::sync::GuardResult;
use serde::{ser::SerializeMap, Serialize, Serializer};

use crate::{
    cache::{
        addr_cache_new, path_cache_new, signal_map_new, sys_interrupt_map_new, sys_result_map_new,
        AddrCache, ExecResult, PathCache, PathCap, SignalMap, SigreturnResult, SysInterrupt,
        SysInterruptMap, SysResultMap,
    },
    compat::{epoll_ctl_safe, ftruncate64},
    config::*,
    elf::ExecutableFile,
    err::{err2no, SydResult},
    error, extend_ioctl,
    fs::{lock_fd, retry_on_eintr, seal_memfd, CanonicalPath},
    hash::{
        aes_ctr_enc, aes_ctr_init, hmac_sha256_feed, hmac_sha256_fini, hmac_sha256_init,
        BLOCK_SIZE, HMAC_TAG_SIZE, IV, IV_SIZE,
    },
    hook::{HandlerMap, RemoteProcess, UNotifyEventRequest, SECCOMP_IOCTL_LIST},
    libseccomp::{ScmpAction, ScmpFilterContext, ScmpSyscall},
    libseccomp_sys::{seccomp_notif_resp, seccomp_notify_receive, seccomp_notify_respond},
    path::{dotdot_with_nul, XPathBuf},
    proc::{proc_mmap, proc_status, proc_tgid},
    sandbox::{Action, Capability, Sandbox, SandboxGuard},
    scmp_arch_raw, scmp_cmp, ScmpNotifReq, SydArch, SydMemoryMap, SydSigSet, Sydcall, XPath,
};

const EOWNERDEAD: i32 = -libc::EOWNERDEAD;

// Epoll event to add seccomp fd to epoll (becomes readable when system
// call is interrupted). We specifically zero out the data field to
// distinguish from PidFds.
//
// Quoting: https://idea.popcount.org/2017-02-20-epoll-is-fundamentally-broken-12/
// """
// The best and the only scalable approach is to use recent
// Kernel 4.5+ and use level-triggered events with
// EPOLLEXCLUSIVE flag. This will ensure only one thread is
// woken for an event, avoid "thundering herd" issue and scale
// properly across multiple CPU's.
// """/
#[allow(clippy::cast_sign_loss)]
const SCMP_EPOLL_EVENT: libc::epoll_event = libc::epoll_event {
    events: (libc::EPOLLIN | libc::EPOLLEXCLUSIVE) as u32,
    u64: 0, // zeroed out to distinguish from pid-fds.
};

// Signal handler function for SIGALRM.
extern "C" fn handle_sigalrm(_: libc::c_int) {}

/// PidFd map, used to store pid file descriptors.
#[derive(Debug)]
#[allow(clippy::type_complexity)]
pub struct PidFdMap {
    /// Inner PidFd concurrent dash map.
    pub pidfd: Arc<Mutex<HashMap<Pid, RawFd, RandomState>>>,
    /// A reference to the WorkerCache to clean relevant data on process exit.
    pub(crate) cache: Arc<WorkerCache<'static>>,
}

impl PidFdMap {
    /// Create a new PidFd map.
    pub(crate) fn new(cache: Arc<WorkerCache<'static>>) -> Self {
        Self {
            cache,
            pidfd: Arc::new(Mutex::new(HashMap::default())),
        }
    }

    #[inline]
    pub(crate) fn get_pidfd(&self, pid: Pid) -> Option<RawFd> {
        self.pidfd
            .lock()
            .unwrap_or_else(|err| err.into_inner())
            .get(&pid)
            .copied()
    }

    #[inline]
    pub(crate) fn add_pidfd(&self, pid: Pid, pid_fd: RawFd) -> Result<(), Errno> {
        let mut pidfd = self.pidfd.lock().unwrap_or_else(|err| err.into_inner());
        pidfd.try_reserve(1).or(Err(Errno::ENOMEM))?;
        pidfd.insert(pid, pid_fd);
        Ok(())
    }

    #[inline]
    pub(crate) fn del_pidfd(&self, pid: Pid) {
        // Retire TGID from signal maps.
        self.cache.retire_sig_handle(pid);
        self.cache.retire_sig_restart(pid);

        // Remove preexisting error record for pid.
        let _ = self.cache.get_error(pid);

        // Remove preexisting chdir record for pid.
        let _ = self.cache.get_chdir(pid);

        // Remove preexisting exec record for pid.
        let _ = self.cache.get_exec(pid);

        // Remove preexisting sigreturn record for pid.
        let _ = self.cache.get_sigreturn(pid);

        // Finally, remove the PidFd from the map.
        let mut pidfd = self.pidfd.lock().unwrap_or_else(|err| err.into_inner());
        pidfd.remove(&pid);
    }

    pub(crate) fn pidfd_open(
        &self,
        request_pid: Pid,
        tgid: bool,
        request_id: Option<u64>,
    ) -> Result<RawFd, Errno> {
        let mut pidfd = self.pidfd.lock().unwrap_or_else(|err| err.into_inner());
        if let Some(fd) = pidfd.get(&request_pid).copied() {
            return Ok(fd);
        }

        // Try to allocate space or bail.
        pidfd.try_reserve(1).or(Err(Errno::ENOMEM))?;

        // Use PIDFD_THREAD if available.
        const PIDFD_THREAD: i32 = OFlag::O_EXCL.bits();
        let (pid, flags) = if *HAVE_PIDFD_THREAD {
            (request_pid, PIDFD_THREAD)
        } else if tgid {
            (request_pid, 0)
        } else {
            (proc_tgid(request_pid)?, 0)
        };

        // Open the PIDFd.
        let pid_fd =
            // SAFETY: No libc wrapper for pidfd_open yet.
            Errno::result(unsafe { libc::syscall(libc::SYS_pidfd_open, pid.as_raw(), flags) })
                .map(|fd| fd as RawFd)?;

        if let Some(request_id) = request_id {
            // SAFETY: Validate the PIDFd by validating the request ID if submitted.
            if unsafe {
                crate::libseccomp_sys::seccomp_notify_id_valid(self.cache.scmp, request_id)
            } != 0
            {
                let _ = close(pid_fd);
                return Err(Errno::ESRCH);
            }
        }

        // SAFETY: Add the PIDFd to the epoll instance.
        //
        // Note: EPOLLEXCLUSIVE|EPOLLONESHOT is invalid!
        #[allow(clippy::cast_sign_loss)]
        let event = libc::epoll_event {
            events: (EpollFlags::EPOLLIN | EpollFlags::EPOLLONESHOT).bits() as u32,
            u64: request_pid.as_raw() as u64,
        };

        // SAFETY: In epoll(7) we trust.
        #[allow(clippy::disallowed_methods)]
        epoll_ctl_safe(&self.cache.poll.0, pid_fd, Some(event))
            .expect("BUG: Failed to add PidFd to Epoll!");

        pidfd.insert(request_pid, pid_fd);

        Ok(pid_fd)
    }
}

/// A cache for worker threads.
#[derive(Debug)]
pub(crate) struct WorkerCache<'a> {
    // Shared epoll instance
    pub(crate) poll: Arc<Epoll>,
    // Seccomp-notify fd
    pub(crate) scmp: RawFd,
    // Signal handlers map
    pub(crate) signal_map: SignalMap,
    // System call interrupt map
    pub(crate) sysint_map: SysInterruptMap,
    // System call result map
    pub(crate) sysres_map: SysResultMap<'a>,
    // Path sandbox policy cache
    pub(crate) path_cache: PathCache,
    // IP address sandbox policy cache
    pub(crate) addr_cache: AddrCache,
}

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

        map.serialize_entry("path", &self.path_cache)?;
        map.serialize_entry("addr", &self.addr_cache)?;

        map.end()
    }
}

impl<'a> WorkerCache<'a> {
    /// Check if the given path is append-only (cached).
    pub(crate) fn is_append(&self, sandbox: &SandboxGuard, path: &XPath) -> bool {
        match self
            .path_cache
            .0
            .get_value_or_guard(&PathCap(Capability::CAP_APPEND, path), None)
        {
            GuardResult::Value(result) => result.1,
            GuardResult::Guard(guard) => {
                let result = (Action::Allow, sandbox.is_append(path));
                let _ = guard.insert(result);
                result.1
            }
            GuardResult::Timeout => {
                // SAFETY: We never pass a timeout, this cannot happen.
                unreachable!("BUG: SandboxGuard returned invalid timeout!");
            }
        }
    }

    /// Check if the given path should be encrypted (cached).
    pub(crate) fn is_crypt(&self, sandbox: &SandboxGuard, path: &XPath) -> bool {
        match self
            .path_cache
            .0
            .get_value_or_guard(&PathCap(Capability::CAP_CRYPT, path), None)
        {
            GuardResult::Value(result) => result.1,
            GuardResult::Guard(guard) => {
                let result = (Action::Allow, sandbox.is_crypt(path));
                let _ = guard.insert(result);
                result.1
            }
            GuardResult::Timeout => {
                // SAFETY: We never pass a timeout, this cannot happen.
                unreachable!("BUG: SandboxGuard returned invalid timeout!");
            }
        }
    }

    /// Check if the given path is masked (cached).
    pub(crate) fn is_masked(&self, sandbox: &SandboxGuard, path: &XPath) -> bool {
        match self
            .path_cache
            .0
            .get_value_or_guard(&PathCap(Capability::CAP_MASK, path), None)
        {
            GuardResult::Value(result) => result.1,
            GuardResult::Guard(guard) => {
                let result = (Action::Allow, sandbox.is_masked(path));
                let _ = guard.insert(result);
                result.1
            }
            GuardResult::Timeout => {
                // SAFETY: We never pass a timeout, this cannot happen.
                unreachable!("BUG: SandboxGuard returned invalid timeout!");
            }
        }
    }

    /// Check if the given path is hidden (ie denylisted for stat sandboxing, cached)
    pub(crate) fn is_hidden(&self, sandbox: &SandboxGuard, path: &XPath) -> bool {
        if sandbox.enabled(Capability::CAP_STAT) {
            let (action, _) = self.check_path(sandbox, Capability::CAP_STAT, path);
            action.is_denying()
        } else {
            false
        }
    }

    /// Check IPv{4,6} address for access (cached).
    pub(crate) fn check_ip(
        &self,
        sandbox: &SandboxGuard,
        cap: Capability,
        addr: IpAddr,
        port: u16,
    ) -> (Action, bool) {
        match self
            .addr_cache
            .0
            .get_value_or_guard(&(cap, addr, port), None)
        {
            GuardResult::Value(result) => result,
            GuardResult::Guard(guard) => {
                let result = sandbox.check_ip(cap, addr, port);
                let _ = guard.insert(result);
                result
            }
            GuardResult::Timeout => {
                // SAFETY: We never pass a timeout, this cannot happen.
                unreachable!("BUG: SandboxGuard returned invalid timeout!");
            }
        }
    }

    /// Check UNIX socket for access (cached).
    pub(crate) fn check_unix(
        &self,
        sandbox: &SandboxGuard,
        cap: Capability,
        path: &XPath,
    ) -> (Action, bool) {
        match self
            .path_cache
            .0
            .get_value_or_guard(&PathCap(cap, path), None)
        {
            GuardResult::Value(result) => result,
            GuardResult::Guard(guard) => {
                let result = sandbox.check_unix(cap, path);
                let _ = guard.insert(result);
                result
            }
            GuardResult::Timeout => {
                // SAFETY: We never pass a timeout, this cannot happen.
                unreachable!("BUG: SandboxGuard returned invalid timeout!");
            }
        }
    }

    /// Check path for access (cached).
    pub(crate) fn check_path(
        &self,
        sandbox: &SandboxGuard,
        cap: Capability,
        path: &XPath,
    ) -> (Action, bool) {
        match self
            .path_cache
            .0
            .get_value_or_guard(&PathCap(cap, path), None)
        {
            GuardResult::Value(result) => result,
            GuardResult::Guard(guard) => {
                let result = sandbox.check_path(cap, path);
                let _ = guard.insert(result);
                result
            }
            GuardResult::Timeout => {
                // SAFETY: We never pass a timeout, this cannot happen.
                unreachable!("BUG: SandboxGuard returned invalid timeout!");
            }
        }
    }

    pub(crate) fn new(poll: Arc<Epoll>, scmp: RawFd, path_cap: usize, addr_cap: usize) -> Self {
        Self {
            poll,
            scmp,
            signal_map: signal_map_new(),
            sysint_map: sys_interrupt_map_new(),
            sysres_map: sys_result_map_new(),
            path_cache: path_cache_new(path_cap),
            addr_cache: addr_cache_new(addr_cap),
        }
    }

    // Increment count of handled signals.
    pub(crate) fn inc_sig_handle(&self, request_tgid: Pid) {
        let mut map = self
            .signal_map
            .sig_handle
            .lock()
            .unwrap_or_else(|err| err.into_inner());
        map.entry(request_tgid)
            .and_modify(|v| *v = v.saturating_add(1))
            .or_insert(1);
        // let count = *count;
        drop(map);

        /*
        debug!("ctx": "count_signal",
            "msg": format!("forwarded {count} signals to TGID:{request_tgid}"),
            "pid": request_tgid.as_raw());
        */
    }

    // Decrement count of handled signals, return true if decremented, false if zero.
    #[allow(clippy::cognitive_complexity)]
    pub(crate) fn dec_sig_handle(&self, request_tgid: Pid) -> bool {
        let mut is_dec = false;

        let mut map = self
            .signal_map
            .sig_handle
            .lock()
            .unwrap_or_else(|err| err.into_inner());
        if let Entry::Occupied(mut entry) = map.entry(request_tgid) {
            let count = entry.get_mut();

            /*
            debug!(
                "ctx": "count_signal",
                "msg": format!("returned from one of {count} signals for TGID:{request_tgid}"),
                "pid": request_tgid.as_raw()
            );
            */

            *count = count.saturating_sub(1);
            is_dec = true;

            if *count == 0 {
                let _ = entry.remove();
            }
        } /* else {
              debug!(
                  "ctx": "count_signal",
                  "msg": format!("returned from unknown signal for TGID:{request_tgid}"),
                  "pid": request_tgid.as_raw()
              );
          }*/

        is_dec
    }

    // Delete a TGID from the signal handle map.
    pub(crate) fn retire_sig_handle(&self, tgid: Pid) {
        let mut map = self
            .signal_map
            .sig_handle
            .lock()
            .unwrap_or_else(|err| err.into_inner());
        map.remove(&tgid);
    }

    // Record a chdir result.
    pub(crate) fn add_chdir<'b>(
        &'b self,
        process: RemoteProcess,
        path: CanonicalPath<'a>,
    ) -> Result<(), Errno> {
        let mut map = self
            .sysres_map
            .trace_chdir
            .lock()
            .unwrap_or_else(|err| err.into_inner());
        map.try_reserve(1).or(Err(Errno::ENOMEM))?;
        map.insert(process, path);
        Ok(())
    }

    // Query, remove and return a chdir result.
    #[allow(clippy::type_complexity)]
    pub(crate) fn get_chdir<'b>(&'b self, pid: Pid) -> Option<(RemoteProcess, CanonicalPath<'a>)> {
        let p = RemoteProcess {
            pid,
            pid_fd: libc::AT_FDCWD,
        };

        self.sysres_map
            .trace_chdir
            .lock()
            .unwrap_or_else(|err| err.into_inner())
            .remove_entry(&p)
    }

    // Record an error result.
    pub(crate) fn add_error(&self, process: RemoteProcess, errno: Errno) -> Result<(), Errno> {
        let mut map = self
            .sysres_map
            .trace_error
            .lock()
            .unwrap_or_else(|err| err.into_inner());
        map.try_reserve(1).or(Err(Errno::ENOMEM))?;
        map.insert(process, errno);
        Ok(())
    }

    // Query, remove and return a error result.
    #[allow(clippy::type_complexity)]
    pub(crate) fn get_error(&self, pid: Pid) -> Option<(RemoteProcess, Errno)> {
        let p = RemoteProcess {
            pid,
            pid_fd: libc::AT_FDCWD,
        };

        self.sysres_map
            .trace_error
            .lock()
            .unwrap_or_else(|err| err.into_inner())
            .remove_entry(&p)
    }

    // Record a execv result.
    #[allow(clippy::too_many_arguments)]
    pub(crate) fn add_exec(
        &self,
        process: RemoteProcess,
        file: ExecutableFile,
        arch: u32,
        ip: u64,
        sp: u64,
        args: [u64; 6],
        ip_mem: Option<[u8; 64]>,
        sp_mem: Option<[u8; 64]>,
        memmap: Option<Vec<SydMemoryMap>>,
    ) -> Result<(), Errno> {
        let result = ExecResult {
            file,
            arch,
            ip,
            sp,
            args,
            ip_mem,
            sp_mem,
            memmap,
        };

        let mut map = self
            .sysres_map
            .trace_execv
            .lock()
            .unwrap_or_else(|err| err.into_inner());
        map.try_reserve(1).or(Err(Errno::ENOMEM))?;
        map.insert(process, result);
        Ok(())
    }

    // Query, remove and return a exec result.
    pub(crate) fn get_exec(&self, pid: Pid) -> Option<(RemoteProcess, ExecResult)> {
        let p = RemoteProcess {
            pid,
            pid_fd: libc::AT_FDCWD,
        };

        self.sysres_map
            .trace_execv
            .lock()
            .unwrap_or_else(|err| err.into_inner())
            .remove_entry(&p)
    }

    // Record a sigreturn entry.
    #[allow(clippy::too_many_arguments)]
    pub(crate) fn add_sigreturn(
        &self,
        process: RemoteProcess,
        is_realtime: bool,
        ip: u64,
        sp: u64,
        args: [u64; 6],
        ip_mem: Option<[u8; 64]>,
        sp_mem: Option<[u8; 64]>,
    ) -> Result<(), Errno> {
        let result = SigreturnResult {
            is_realtime,
            ip,
            sp,
            args,
            ip_mem,
            sp_mem,
        };
        let mut map = self
            .sysres_map
            .trace_sigret
            .lock()
            .unwrap_or_else(|err| err.into_inner());
        map.try_reserve(1).or(Err(Errno::ENOMEM))?;
        map.insert(process, result);
        Ok(())
    }

    // Query, remove and return a sigreturn entry info.
    pub(crate) fn get_sigreturn(&self, pid: Pid) -> Option<(RemoteProcess, SigreturnResult)> {
        let p = RemoteProcess {
            pid,
            pid_fd: libc::AT_FDCWD,
        };

        self.sysres_map
            .trace_sigret
            .lock()
            .unwrap_or_else(|err| err.into_inner())
            .remove_entry(&p)
    }

    // Add a restarting signal.
    pub(crate) fn add_sig_restart(&self, request_tgid: Pid, sig: libc::c_int) -> Result<(), Errno> {
        let mut map = self
            .sysint_map
            .sig_restart
            .lock()
            .unwrap_or_else(|err| err.into_inner());
        if let Some(set) = map.get_mut(&request_tgid) {
            set.add(sig);
            return Ok(());
        }

        let mut set = SydSigSet::new(0);
        set.add(sig);

        map.try_reserve(1).or(Err(Errno::ENOMEM))?;
        map.insert(request_tgid, set);

        Ok(())
    }

    // Delete a restarting signal.
    pub(crate) fn del_sig_restart(&self, request_tgid: Pid, sig: libc::c_int) {
        let mut map = self
            .sysint_map
            .sig_restart
            .lock()
            .unwrap_or_else(|err| err.into_inner());
        let set_nil = if let Some(set) = map.get_mut(&request_tgid) {
            set.del(sig);
            set.is_empty()
        } else {
            return;
        };

        if set_nil {
            map.remove(&request_tgid);
        }
    }

    // Delete a TGID from the signal restart map.
    pub(crate) fn retire_sig_restart(&self, tgid: Pid) {
        let mut map = self
            .sysint_map
            .sig_restart
            .lock()
            .unwrap_or_else(|err| err.into_inner());
        map.remove(&tgid);
    }

    // Add a blocked syscall.
    #[allow(clippy::cast_possible_wrap)]
    pub(crate) fn add_sys_block(
        &self,
        request: ScmpNotifReq,
        ignore_restart: bool,
    ) -> Result<(), Errno> {
        let handler_tid = gettid();
        let request_tgid = proc_tgid(Pid::from_raw(request.pid as libc::pid_t))?;
        let interrupt = SysInterrupt::new(request, request_tgid, handler_tid, ignore_restart);

        let (ref lock, ref cvar) = *self.sysint_map.sys_block;
        let mut map = lock.lock().unwrap_or_else(|err| err.into_inner());

        map.try_reserve(1).or(Err(Errno::ENOMEM))?;
        map.insert(request.id, interrupt);

        cvar.notify_one();

        Ok(())
    }

    // Remove a blocked fifo.
    pub(crate) fn del_sys_block(&self, request_id: u64) {
        let (ref lock, ref _cvar) = *self.sysint_map.sys_block;
        let mut map = lock.lock().unwrap_or_else(|err| err.into_inner());
        map.remove(&request_id);
    }
}

/// Self growing / shrinking `ThreadPool` implementation.
#[derive(Clone)]
pub(crate) struct ThreadPool {
    core_size: usize,
    keep_alive: u16,
    safe_setid: bool,
    fd: RawFd,
    pub(crate) epoll: Arc<Epoll>,
    pub(crate) cache: Arc<WorkerCache<'static>>,
    sandbox: Arc<RwLock<Sandbox>>,
    handlers: Arc<HandlerMap>,
    crypt_map: Option<AesMap>,
    should_exit: Arc<AtomicBool>,
    worker_data: Arc<WorkerData>,
}

impl ThreadPool {
    /// Construct a new `ThreadPool` with the specified core pool size,
    /// max pool size and keep_alive time for non-core threads. This
    /// function creates an epoll instance and adds the seccomp fd to it
    /// but it does not spawn any threads.
    ///
    /// `core_size` specifies the amount of threads to keep alive for as
    /// long as the `ThreadPool` exists and the seccomp fd remains open.
    ///
    /// `keep_alive` specifies the duration in milliseconds for which to
    /// keep non-core pool worker threads alive while they do not
    /// receive any work.
    #[allow(clippy::cognitive_complexity)]
    pub(crate) fn new(
        epoll: Epoll,
        fd: RawFd,
        safe_setid: bool,
        core_size: usize,
        keep_alive: u16,
        sandbox: Arc<RwLock<Sandbox>>,
        handlers: Arc<HandlerMap>,
        crypt_map: Option<AesMap>,
        cache_path_cap: usize,
        cache_addr_cap: usize,
    ) -> Result<Self, Errno> {
        // SAFETY: Borrow FD to make I/O safe API hippie.
        let seccomp_fd = unsafe { BorrowedFd::borrow_raw(fd) };
        epoll_ctl_safe(&epoll.0, seccomp_fd.as_raw_fd(), Some(SCMP_EPOLL_EVENT))?;

        let epoll = Arc::new(epoll);
        let cache = Arc::new(WorkerCache::new(
            Arc::clone(&epoll),
            fd,
            cache_path_cap,
            cache_addr_cap,
        ));

        // Create pidfd map.
        let pidfd_map = PidFdMap::new(Arc::clone(&cache));
        PIDFD_MAP.set(pidfd_map).or(Err(Errno::EAGAIN))?;

        // Add a sandbox a reference to the cache to log statistics.
        let mut my_sandbox = SandboxGuard::Write(sandbox.write().unwrap());
        my_sandbox.cache = Some(Arc::clone(&cache));
        drop(my_sandbox); // release the write-lock.

        Ok(Self {
            fd,
            cache,
            sandbox,
            crypt_map,
            handlers,
            core_size,
            keep_alive,
            safe_setid,
            epoll,
            should_exit: Arc::new(AtomicBool::new(false)),
            worker_data: Arc::new(WorkerData::default()),
        })
    }

    /// Boot the thread pool. This is the main entry point.
    #[allow(clippy::cognitive_complexity)]
    pub(crate) fn boot(self) -> Result<Option<JoinHandle<()>>, Errno> {
        // Export seccomp rules if requested.
        // We have to prepare the filter twice if exporting,
        // as we cannot move it safely between threads...
        if std::env::var("SYD_SECX").is_ok() {
            println!("# Syd monitor rules");
            if let Ok(ctx) =
                Worker::prepare_confine(self.fd, self.epoll.0.as_raw_fd(), self.safe_setid, false)
            {
                let _ = ctx.export_pfc(std::io::stdout());
            }

            println!("# Syd interrupter rules");
            if let Ok(ctx) = Interrupter::prepare_confine(self.fd, getpid(), self.safe_setid, false)
            {
                let _ = ctx.export_pfc(std::io::stdout());
            }

            println!("# Syd encryptor rules");
            if let Ok(ctx) = AesWorker::prepare_confine(self.safe_setid, false) {
                let _ = ctx.export_pfc(std::io::stdout());
            }
        }

        // Spawn the AES thread if encryption is on.
        let crypt = {
            let sandbox = self.sandbox.read().unwrap();
            if sandbox.enabled(Capability::CAP_CRYPT) {
                let crypt_fds = sandbox.crypt_setup().unwrap();
                let is_mem_fd = sandbox.crypt_tmp.is_none();
                Some((crypt_fds, is_mem_fd))
            } else {
                None
            }
        };

        // Note, we spawn the AES thread before CPU pinning intentionally,
        // so they get to run on whichever CPU.
        let crypt_handle = if let Some((crypt_fds, is_mem_fd)) = crypt {
            let crypt_map = self.crypt_map.as_ref().map(Arc::clone).unwrap();
            Some(
                self.try_spawn_aes(crypt_fds, crypt_map, is_mem_fd)
                    .expect("spawn AES encryption thread"),
            )
        } else {
            None
        };

        // Ensure the lazy num_cpus::get is called before
        // the CPU pinning below as subsequent invocations
        // is going to return 1.
        let nproc = *NPROC;
        crate::info!("ctx": "boot", "op": "pin_main_thread",
            "msg": format!("detected {nproc} CPUs on the system"),
            "num_cpus": nproc);

        // Attempt to set thread's CPU affinity mask to 0.
        // We pin the main, init and monitor threads to CPU:0.
        // Emulator threads are pinned according to num-cpus.
        let cpu_id = 0;
        let mut cpu_set = CpuSet::new();
        if cpu_set.set(cpu_id).is_ok() {
            match sched_setaffinity(Pid::from_raw(0), &cpu_set) {
                Ok(_) => {
                    crate::info!("ctx": "boot", "op": "pin_main_thread",
                        "msg": format!("pinned main thread to CPU:{cpu_id}"),
                        "cpu": cpu_id);
                }
                Err(errno) => {
                    error!("ctx": "boot", "op": "pin_main_thread",
                        "err": format!("failed to pin main thread to CPU:{cpu_id}: {errno}"),
                        "cpu": cpu_id);
                }
            }
        }

        // Spawn the interrupt thread which will confine itself.
        self.try_spawn_interrupt().map_err(|err| err2no(&err))?;

        // Spawn the monitor thread which may confine itself, and spawn
        // emulator threads. Note, this will panic if it cannot spawn
        // the initial emulator thread which is going to tear everything
        // down.
        self.monitor()?;

        // Return join handle of the encryption thread,
        // so we can wait for ongoing encryption processes
        // before exiting the sandbox.
        Ok(crypt_handle)
    }

    /// Spawn a monitor thread that watches the worker pool busy count,
    /// and spawns new helper threads as necessary. This is done to
    /// ensure a sandbox process cannot DOS Syd by merely exhausting
    /// workers by e.g. opening the read end of a FIFO over and over
    /// again.
    #[allow(clippy::cognitive_complexity)]
    pub(crate) fn monitor(self) -> Result<(), Errno> {
        thread::Builder::new()
            .name("syd_mon".to_string())
            .stack_size(MON_STACK_SIZE)
            .spawn(move || {
                crate::info!("ctx": "boot", "op": "start_monitor_thread",
                    "msg": format!("started monitor thread with pool size set to {} threads and keep alive set to {} seconds",
                        self.core_size,
                        self.keep_alive.saturating_div(1000)),
                    "core_size": self.core_size,
                    "keep_alive": self.keep_alive);

                // SAFETY: If sandbox is locked, confine right away.
                // Pass confined parameter to try_spawn so subsequent
                // spawned threads don't need to reapply the same filter
                // as it is inherited.
                let dry_run = env::var_os(ENV_SKIP_SCMP).is_some();
                let mut confined = if Sandbox::locked_once() {
                    crate::info!("ctx": "confine", "op": "confine_monitor_thread",
                        "msg": format!("monitor thread confined with{} SROP mitigation",
                            if self.safe_setid { "out" } else { "" }));

                    Worker::prepare_confine(self.fd, self.epoll.0.as_raw_fd(), self.safe_setid, dry_run)
                        .expect("prepare monitor thread confinement")
                        .load()
                        .expect("confine monitor thread");

                    true
                } else {
                    crate::info!("ctx": "confine", "op": "confine_emulator_thread",
                        "msg": "emulator threads are running unconfined because sandbox isn't locked yet");
                    false
                };

                crate::info!("ctx": "boot", "op": "start_core_emulator_threads",
                    "msg": format!("starting {} core emulator thread{}, sandboxing started!",
                        self.core_size,
                        if self.core_size > 1 { "s" } else { "" }),
                    "core_size": self.core_size,
                    "keep_alive": self.keep_alive);

                // SAFETY: Panic if we cannot spawn the core threads.
                // There's little sense in continuing in this case.
                for _ in 0..self.core_size {
                    self.try_spawn(confined)
                        .expect("spawn core emulator thread")
                        .unwrap();
                }

                // SAFETY: Wait for grace period to give core emulator
                // threads a chance to spawn themselves.
                std::thread::sleep(MON_GRACE_TIME);

                loop {
                    // Confine as necessary.
                    if !confined && Sandbox::locked_once() {
                        crate::info!("ctx": "confine", "op": "confine_monitor_thread",
                            "msg": format!("monitor thread confined with{} SROP mitigation",
                                if self.safe_setid { "out" } else { "" }));

                        Worker::prepare_confine(self.fd, self.epoll.0.as_raw_fd(), self.safe_setid, dry_run)
                            .expect("prepare monitor thread confinement")
                            .load()
                            .expect("confine monitor thread");

                        confined = true;
                    }

                    if self.should_exit.load(Ordering::Relaxed) {
                        // Time to exit.
                        break;
                    }

                    // Spawn a new thread if all others are busy.
                    match self.try_spawn(confined) {
                        Ok(Some(_)) => {
                            // We have spawned a new emulator thread,
                            // wait for one cycle before reattempting.
                            std::thread::sleep(MON_CYCLE_TIME);
                        }
                        Ok(None) => {
                            // We have idle threads, no need to spawn a new worker.
                            // Wait for grace period before reattempting.
                            std::thread::sleep(MON_GRACE_TIME);
                        }
                        Err(_) => {
                            // Caller try_spawn logs an alert level entry about this.
                            // Wait for grace period before reattempting.
                            std::thread::sleep(MON_GRACE_TIME);
                        }
                    }
                }
            })
            .map(drop)
            .map_err(|err| err2no(&err))
    }

    /// Spawn an interrupt handler thread to unblock Syd syscall
    /// handler threads when the respective sandbox process
    /// receives a non-restarting signal.
    pub(crate) fn try_spawn_interrupt(&self) -> Result<JoinHandle<()>, std::io::Error> {
        // Set up the signal handler for SIGALRM.
        let sig_action = SigAction::new(
            SigHandler::Handler(handle_sigalrm),
            SaFlags::empty(),
            SigSet::empty(),
        );

        // SAFETY: Register the handler for SIGALRM.
        unsafe { sigaction(Signal::SIGALRM, &sig_action) }?;

        Interrupter::new(
            self.fd,
            self.core_size,
            self.safe_setid,
            Arc::clone(&self.should_exit),
            Arc::clone(&self.cache),
            Arc::clone(&self.worker_data),
        )
        .try_spawn()
    }

    /// Try to create a new encryption thread.
    pub(crate) fn try_spawn_aes(
        &self,
        fdalg: (RawFd, RawFd),
        files: AesMap,
        memfd: bool,
    ) -> Result<JoinHandle<()>, std::io::Error> {
        AesWorker::new(fdalg, files, memfd, self.safe_setid).try_spawn()
    }

    /// Try to create a new worker thread as needed.
    /// Returns Ok(Some((JoinHandle, bool))) if spawn succeeded, Ok(None) if no spawn was needed.
    /// The boolean in the success case is true if the thread we spawned was a core thread.
    #[allow(clippy::cognitive_complexity)]
    #[allow(clippy::type_complexity)]
    pub(crate) fn try_spawn(
        &self,
        confined: bool,
    ) -> Result<Option<(JoinHandle<()>, bool)>, std::io::Error> {
        // Create a new worker if there are no idle threads and the
        // current worker count is lower than the max pool size.
        let worker_count_val = self.worker_data.0.load(Ordering::Relaxed);
        let (curr_worker_count, busy_worker_count) = WorkerData::split(worker_count_val);

        let keep_alive = if curr_worker_count < self.core_size {
            // Create a new core worker if current pool size is below
            // core size during the invocation of this function.
            crate::debug!("ctx": "spawn", "dec": "create_new_core_emulator",
                "busy_worker_count": busy_worker_count,
                "curr_worker_count": curr_worker_count,
                "core_size": self.core_size);
            None
        } else if busy_worker_count < curr_worker_count {
            // We have idle threads, no need to spawn a new worker.
            crate::debug!("ctx": "spawn", "dec": "idle_emulator_exists",
                "busy_worker_count": busy_worker_count,
                "curr_worker_count": curr_worker_count,
                "core_size": self.core_size);
            return Ok(None);
        } else if curr_worker_count < *EMU_MAX_SIZE {
            // Create a new helper worker if the current worker count is
            // below the EMU_MAX_SIZE and the pool has been observed to
            // be busy (no idle workers) during the invocation of this
            // function.
            crate::debug!("ctx": "spawn", "dec": "create_new_idle_emulator",
                "busy_worker_count": busy_worker_count,
                "curr_worker_count": curr_worker_count,
                "core_size": self.core_size,
                "keep_alive": self.keep_alive);
            Some(self.keep_alive)
        } else {
            // We cannot spawn anymore workers!
            // Ideally, this should never happen.
            crate::alert!("ctx": "spawn", "dec": "emulator_capacity_exceeded",
                "busy_worker_count": busy_worker_count,
                "curr_worker_count": curr_worker_count,
                "core_size": self.core_size,
                "keep_alive": self.keep_alive);
            return Ok(None);
        };

        // Try to spawn a new worker.
        Ok(Some((
            Worker::new(
                self.fd,
                Arc::clone(&self.epoll),
                Arc::clone(&self.cache),
                Arc::clone(&self.sandbox),
                Arc::clone(&self.handlers),
                keep_alive,
                Arc::clone(&self.should_exit),
                Arc::clone(&self.worker_data),
                self.crypt_map.as_ref().map(Arc::clone),
            )
            .try_spawn(confined)?,
            keep_alive.is_none(),
        )))
    }
}

#[derive(Clone)]
struct Interrupter {
    scmp: RawFd,
    core_size: usize,
    safe_setid: bool,
    should_exit: Arc<AtomicBool>,
    cache: Arc<WorkerCache<'static>>,
    worker_data: Arc<WorkerData>,
}

impl Interrupter {
    fn new(
        scmp: RawFd,
        core_size: usize,
        safe_setid: bool,
        should_exit: Arc<AtomicBool>,
        cache: Arc<WorkerCache<'static>>,
        worker_data: Arc<WorkerData>,
    ) -> Self {
        Self {
            scmp,
            core_size,
            safe_setid,
            should_exit,
            cache,
            worker_data,
        }
    }

    fn try_spawn(self) -> Result<JoinHandle<()>, std::io::Error> {
        thread::Builder::new()
            .name("syd_int".to_string())
            .stack_size(INT_STACK_SIZE)
            .spawn(move || {
                // To be used by tgkill when signaling threads.
                let tgid = getpid();

                let dry_run = env::var_os(ENV_SKIP_SCMP).is_some();

                if !dry_run {
                    // SAFETY: Default panic hook wont play well with seccomp
                    std::panic::set_hook(Box::new(|_| {}));
                }

                // SAFETY: Logging will kill us after seccomp.
                crate::info!("ctx": "confine", "op": "confine_interrupt_thread",
                    "msg": format!("interrupt thread confined with{} SROP mitigation",
                        if self.safe_setid { "out" } else { "" }));

                // SAFETY: Panic if we cannot confine the thread.
                let ctx = Self::prepare_confine(self.scmp, tgid, self.safe_setid, dry_run).unwrap();
                ctx.load().unwrap();

                // Enter main loop.
                self.main(tgid)
            })
    }

    fn main(self, tgid: Pid) {
        loop {
            // Wait for one cycle.
            std::thread::sleep(INT_CYCLE_TIME);

            // Unblock invalidated blocking system calls.
            {
                let (ref lock, ref cvar) = *self.cache.sysint_map.sys_block;
                let mut map = lock.lock().unwrap_or_else(|err| err.into_inner());

                while map.is_empty() {
                    map = cvar.wait(map).unwrap_or_else(|err| err.into_inner());
                }
                map.retain(|_, interrupt| self.handle_interrupt(tgid, *interrupt));
            }

            // Check if it's the time to exit.
            if self.should_exit.load(Ordering::Relaxed) {
                break;
            }
        }
    }

    fn handle_interrupt(&self, tgid: Pid, interrupt: SysInterrupt) -> bool {
        // Check pending signals for the thread.
        #[allow(clippy::cast_possible_wrap)]
        let request_pid = Pid::from_raw(interrupt.request.pid as libc::pid_t);
        let status = if let Ok(status) = proc_status(request_pid) {
            status
        } else {
            // Proces no longer valid, remove.
            return false;
        };

        // SAFETY: Validate request ID to ensure `/proc` read was valid.
        // Note, this function is a hot path where we don't want to run
        // notify_supported() on each call.
        // libseccomp::notify_id_valid(self.scmp, interrupt.request.id).is_err().
        if unsafe {
            crate::libseccomp_sys::seccomp_notify_id_valid(self.scmp, interrupt.request.id)
        } != 0
        {
            // Request no longer valid, remove.
            return false;
        }

        // Check for per-{thread,process} pending signals.
        let mut sigset = status.sig_pending_thread | status.sig_pending_process;

        // Filter out restarting signals per-process,
        // unless ignore_restart is set. This may be the
        // case e.g. when the socket has a timeout for
        // accept and connect.
        // Note, `interrupt.ignore_restart` check
        // was done before calling this function and
        // sigset_restart is only Some if it is false.
        if !interrupt.ignore_restart {
            if let Some(sigset_restart) = self
                .cache
                .sysint_map
                .sig_restart
                .lock()
                .unwrap_or_else(|err| err.into_inner())
                .get(&interrupt.tgid)
            {
                sigset.del_set(*sigset_restart);
            }
        }

        if sigset.is_empty() {
            // No interrupt signals received, keep the entry.
            return true;
        }

        // Interrupt the syscall handler thread.
        // SAFETY: There's no libc wrapper for tgkill.
        match Errno::result(unsafe {
            libc::syscall(
                libc::SYS_tgkill,
                tgid.as_raw(),
                interrupt.handler.as_raw(),
                libc::SIGALRM,
            )
        }) {
            Ok(_) | Err(Errno::ESRCH) => false,
            Err(errno) => {
                // SAFETY: Inter-thread signaling does not work.
                // This is seriously wrong, exit ASAP.
                unsafe { libc::_exit(errno as i32) };
            }
        }
    }

    /// Confine Interrupter thread.
    #[allow(clippy::cognitive_complexity)]
    fn prepare_confine(
        scmp: RawFd,
        tgid: Pid,
        safe_setid: bool,
        dry_run: bool,
    ) -> SydResult<ScmpFilterContext> {
        // Create seccomp filter with default action.
        let act = if dry_run {
            error!("ctx": "confine", "op": "confine_interrupt_thread",
                "msg": "interrupter thread is running unconfined in debug mode");
            ScmpAction::Log
        } else {
            // SAFETY: Set up a Landlock sandbox to disallow:
            // 1. All read access except `/proc` filesystem.
            // 2. All write, network access.
            // 3. Scoped UNIX sockets.
            // We cannot enable scoped signals because we
            // want to signal Syd syscall handler threads
            // that are going to be outside this Landlock
            // sandbox.
            let abi = crate::landlock::ABI::new_current();
            let _ = crate::landlock_operation(
                abi,
                &[XPathBuf::from("/proc")],
                &[],
                &[],
                &[],
                true,
                false,
            );

            ScmpAction::KillProcess
        };
        let mut ctx = ScmpFilterContext::new(act)?;

        // Enforce the NO_NEW_PRIVS functionality before
        // loading the seccomp filter into the kernel.
        ctx.set_ctl_nnp(true)?;

        // DO NOT synchronize filter to all threads.
        // Other threads will self-confine.
        ctx.set_ctl_tsync(false)?;

        // We kill for bad system call and bad arch.
        ctx.set_act_badarch(ScmpAction::KillProcess)?;

        // Use a binary tree sorted by syscall number if possible.
        let _ = ctx.set_ctl_optimize(2);

        // SAFETY: Do NOT add supported architectures to the filter.
        // This ensures Syd can never run a non-native system call,
        // which we do not need at all.
        // seccomp_add_architectures(&mut ctx)?;

        // Allow interrupt handler thread to send the
        // SIGALRM signal to threads in Syd's thread group.
        let sysname = "tgkill";
        #[allow(clippy::cast_sign_loss)]
        match ScmpSyscall::from_name(sysname) {
            Ok(syscall) => {
                ctx.add_rule_conditional(
                    ScmpAction::Allow,
                    syscall,
                    &[
                        scmp_cmp!($arg0 == tgid.as_raw() as u64),
                        scmp_cmp!($arg2 == libc::SIGALRM as u64),
                    ],
                )?;
            }
            Err(_) => {
                crate::info!("ctx": "confine", "op": "allow_syscall",
                    "msg": format!("invalid or unsupported syscall {sysname}"));
            }
        }

        // Allow interrupt handler thread to
        // validate seccomp request IDs using ioctl(2).
        let sysname = "ioctl";
        #[allow(clippy::cast_sign_loss)]
        #[allow(clippy::unnecessary_cast)]
        match ScmpSyscall::from_name(sysname) {
            Ok(syscall) => {
                ctx.add_rule_conditional(
                    ScmpAction::Allow,
                    syscall,
                    &[
                        scmp_cmp!($arg0 == scmp as u64),
                        scmp_cmp!($arg1 == crate::hook::SECCOMP_IOCTL_NOTIF_ID_VALID as u64),
                    ],
                )?;
            }
            Err(_) => {
                crate::info!("ctx": "confine", "op": "allow_syscall",
                    "msg": format!("invalid or unsupported syscall {sysname}"));
            }
        }

        // Allow interrupt handler thread to access
        // `/proc` file system to read information
        // on pending signals.
        // TODO: Restrict this further.
        let sysname = "openat2";
        #[allow(clippy::cast_sign_loss)]
        match ScmpSyscall::from_name(sysname) {
            Ok(syscall) => {
                ctx.add_rule_conditional(
                    ScmpAction::Allow,
                    syscall,
                    &[scmp_cmp!($arg0 == PROC_FD() as u64)],
                )?;
            }
            Err(_) => {
                crate::info!("ctx": "confine", "op": "allow_syscall",
                    "msg": format!("invalid or unsupported syscall {sysname}"));
            }
        }

        // Deny open and stat family with ENOSYS rather than KillProcess.
        // We need this because std::thread::spawn has unwanted
        // side-effects such as opening /sys/devices/system/cpu/online
        // on some architectures.
        for sysname in ["open", "openat", "stat", "lstat", "statx", "newfstatat"] {
            match ScmpSyscall::from_name(sysname) {
                Ok(syscall) => {
                    ctx.add_rule(ScmpAction::Errno(Errno::ENOSYS as i32), syscall)?;
                }
                Err(_) => {
                    crate::info!("ctx": "confine", "op": "allow_syscall",
                        "msg": format!("invalid or unsupported syscall {sysname}"));
                }
            }
        }

        // Allow safe fcntl(2) utility calls.
        for sysname in ["fcntl", "fcntl64"] {
            let syscall = match ScmpSyscall::from_name(sysname) {
                Ok(syscall) => syscall,
                Err(_) => {
                    crate::info!("ctx": "confine", "op": "allow_syscall",
                        "msg": format!("invalid or unsupported syscall {sysname}"));
                    continue;
                }
            };

            for op in INT_FCNTL_OPS {
                ctx.add_rule_conditional(ScmpAction::Allow, syscall, &[scmp_cmp!($arg1 == *op)])?;
            }
        }

        // Allow safe system calls.
        for sysname in INT_SYSCALLS {
            match ScmpSyscall::from_name(sysname) {
                Ok(syscall) => {
                    ctx.add_rule(ScmpAction::Allow, syscall)?;
                }
                Err(_) => {
                    crate::info!("ctx": "confine", "op": "allow_syscall",
                        "msg": format!("invalid or unsupported syscall {sysname}"));
                }
            }
        }

        // Allow futex system calls.
        for sysname in FUTEX_SYSCALLS {
            match ScmpSyscall::from_name(sysname) {
                Ok(syscall) => {
                    ctx.add_rule(ScmpAction::Allow, syscall)?;
                }
                Err(_) => {
                    crate::info!("ctx": "confine", "op": "allow_syscall",
                        "msg": format!("invalid or unsupported syscall {sysname}"));
                }
            }
        }

        // Allow getid system calls.
        for sysname in GET_ID_SYSCALLS {
            match ScmpSyscall::from_name(sysname) {
                Ok(syscall) => {
                    ctx.add_rule(ScmpAction::Allow, syscall)?;
                }
                Err(_) => {
                    crate::info!("ctx": "confine", "op": "allow_syscall",
                        "msg": format!("invalid or unsupported syscall {sysname}"));
                }
            }
        }

        if safe_setid {
            // SAFETY: Main thread confines these further.
            // As these system calls as per-process,
            // the main thread's seccomp rules will apply
            // to us even without TSYNC.
            for sysname in SET_ID_SYSCALLS {
                match ScmpSyscall::from_name(sysname) {
                    Ok(syscall) => {
                        ctx.add_rule(ScmpAction::Allow, syscall)?;
                    }
                    Err(_) => {
                        crate::info!("ctx": "confine", "op": "allow_syscall",
                            "msg": format!("invalid or unsupported syscall {sysname}"));
                    }
                }
            }

            // SAFETY:
            // Signal system calls are necessary to handle reserved signals.
            for sysname in ["sigreturn", "rt_sigreturn"] {
                match ScmpSyscall::from_name(sysname) {
                    Ok(syscall) => {
                        ctx.add_rule(ScmpAction::Allow, syscall)?;
                    }
                    Err(_) => {
                        crate::info!("ctx": "confine", "op": "allow_syscall",
                            "msg": format!("invalid or unsupported syscall {sysname}"));
                    }
                }
            }
        }

        Ok(ctx)
    }
}

#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
pub(crate) enum AesMod {
    Read,
    Append,
    Write,
}

impl From<OFlag> for AesMod {
    fn from(flags: OFlag) -> Self {
        if flags.contains(OFlag::O_WRONLY) || flags.contains(OFlag::O_RDWR) {
            if flags.contains(OFlag::O_APPEND) {
                Self::Append
            } else {
                Self::Write
            }
        } else {
            Self::Read
        }
    }
}

pub(crate) type AesMap = Arc<
    RwLock<(
        HashMap<XPathBuf, (RawFd, OwnedFd, AesMod, IV, u64, bool)>,
        bool,
    )>,
>;

#[derive(Clone)]
struct AesWorker {
    fdalg: (RawFd, RawFd),
    files: AesMap,
    is_memfd: bool,
    safe_setid: bool,
}

impl AesWorker {
    fn new(fdalg: (RawFd, RawFd), files: AesMap, is_memfd: bool, safe_setid: bool) -> Self {
        Self {
            fdalg,
            files,
            is_memfd,
            safe_setid,
        }
    }

    fn try_spawn(self) -> Result<JoinHandle<()>, std::io::Error> {
        thread::Builder::new()
            .name("syd_aes".to_string())
            .stack_size(AES_STACK_SIZE)
            .spawn(move || {
                // SAFETY: Logging will kill us after seccomp.
                crate::info!("ctx": "confine", "op": "confine_aes_thread",
                    "msg": format!("AES thread confined with{} SROP mitigation",
                        if self.safe_setid { "out" } else { "" }));

                let dry_run = env::var_os(ENV_SKIP_SCMP).is_some();

                if !dry_run {
                    // SAFETY: Default panic hook wont play well with seccomp
                    std::panic::set_hook(Box::new(|_| {}));
                }

                // SAFETY: Panic if we cannot confine the thread.
                Self::prepare_confine(self.safe_setid, dry_run)
                    .unwrap()
                    .load()
                    .unwrap();

                // Enter main loop.
                Self::main(self.fdalg, self.files, self.is_memfd)
            })
    }

    fn main(setup_fds: (RawFd, RawFd), files: AesMap, is_memfd: bool) {
        let mut paths = vec![];
        let mut threads = vec![];
        loop {
            // Check if there're any pending encryption requests
            // for which we have not spawned a thread yet.
            // The thread is responsible for removing
            // the entry from the files map. This way
            // we ensure fstat() requests keep working
            // until we're done writing.
            let my_files = files.read().unwrap();
            let finished = my_files.1;
            for (path, (crypt_fd, _, _, _, _, spawned)) in &my_files.0 {
                if !spawned {
                    // SAFETY: crypt_map keys are valid FDs.
                    let crypt_fd = unsafe { OwnedFd::from_raw_fd(*crypt_fd) };
                    paths.push((crypt_fd, path.clone()));
                }
            }
            drop(my_files);

            if paths.is_empty() {
                if finished {
                    // main-thread signaled exit.
                    break;
                } else {
                    // wait a bit and retry.
                    std::thread::sleep(AES_CYCLE_TIME);
                    continue;
                }
            }

            // Mark entries for which we're spawning a thread.
            let mut my_files = files.write().unwrap();
            for (_, path) in &paths {
                if let Some(info) = my_files.0.get_mut(path) {
                    info.5 = true; // spawned=true.
                }
            }
            drop(my_files);

            for (crypt_fd, crypt_path) in paths.drain(..) {
                // Spawn a thread to handle the write.
                let files = Arc::clone(&files);
                threads.push(Self::spawn(
                    setup_fds, crypt_fd, crypt_path, files, is_memfd,
                ));
            }

            // wait for a cycle.
            std::thread::sleep(AES_CYCLE_TIME);
        }

        // wait for the ongoing encryption operations before exiting.
        for thread in threads {
            thread.join().unwrap().unwrap();
        }
    }

    fn spawn(
        setup_fds: (RawFd, RawFd),
        crypt_fd: OwnedFd,
        crypt_path: XPathBuf,
        files: AesMap,
        memfd: bool,
    ) -> JoinHandle<Result<(), Errno>> {
        thread::Builder::new()
            .name("syd_aes".into())
            .stack_size(AES_STACK_SIZE)
            .spawn(move || {
                // Wait until we take a write lock on the encrypted fd.
                // This will succeed once all fds owned by the sandbox
                // process are closed.
                lock_fd(&crypt_fd, true, true)?;
                // All good, sync contents to disk.
                Self::sync(setup_fds, crypt_fd, crypt_path, files, memfd)
            })
            .unwrap()
    }

    #[allow(clippy::arithmetic_side_effects)]
    #[allow(clippy::cognitive_complexity)]
    fn sync(
        setup_fds: (RawFd, RawFd),
        crypt_fd: OwnedFd,
        crypt_path: XPathBuf,
        files: AesMap,
        memfd: bool,
    ) -> Result<(), Errno> {
        let (aes_fd, mac_fd) = setup_fds;

        let (_, enc_fd, file_mode, mut iv, _, _) = {
            let mut files = files.write().unwrap();
            files.0.remove(&crypt_path).unwrap()
        };

        // Seal memfd to ensure no further writes happen.
        if memfd {
            seal_memfd(&crypt_fd).unwrap();
        }

        // Nothing to do if file was readonly.
        let mut is_append = match file_mode {
            AesMod::Read => return Ok(()),
            AesMod::Append => true,
            _ => false,
        };

        // Handle truncation quickly.
        #[allow(clippy::cast_sign_loss)]
        let data_size = lseek64(crypt_fd.as_raw_fd(), 0, Whence::SeekEnd).unwrap() as u64;
        if data_size == 0 {
            ftruncate64(enc_fd.as_raw_fd(), 0).unwrap();
            return Ok(());
        }

        // Handle opened for append but encrypted file is new.
        #[allow(clippy::cast_sign_loss)]
        let mut file_size = lseek64(enc_fd.as_raw_fd(), 0, Whence::SeekEnd)? as u64;
        if is_append && file_size == 0 {
            is_append = false;
        }

        // Handle opened for append but appended nothing quickly.
        if is_append
            && data_size
                <= file_size.saturating_sub((CRYPT_MAGIC.len() + HMAC_TAG_SIZE + IV_SIZE) as u64)
        {
            return Ok(());
        }

        // We handled quick cases, before possibly
        // truncating the encrypted file, let's
        // ensure we open the connections as expected.

        // Initialize HMAC socket and feed magic header and IV.
        let sock_mac = hmac_sha256_init(&mac_fd, false)?;
        hmac_sha256_feed(&sock_mac, CRYPT_MAGIC, true)?;
        hmac_sha256_feed(&sock_mac, iv.as_ref(), true)?;
        let (pipe_rd_mac, pipe_wr_mac) = pipe2(OFlag::O_CLOEXEC)?;

        // Handle last block re-encryption for append.
        if is_append {
            // Adjust file_size to exclude the header.
            let header_size = (CRYPT_MAGIC.len() + HMAC_TAG_SIZE + IV_SIZE) as u64;
            file_size -= header_size;

            // Calculate the offset of the last full block.
            let last_block_offset = if file_size % BLOCK_SIZE as u64 == 0 {
                file_size
            } else {
                file_size - (file_size % BLOCK_SIZE as u64)
            };

            // Adjust the IV counter based on the last full block offset.
            iv.add_counter(last_block_offset);

            // If there is a partial block at the end, we need to re-encrypt it.
            if last_block_offset < file_size {
                // Truncate the encrypted file to remove the partial block.
                let truncate_offset = header_size + last_block_offset;
                retry_on_eintr(|| {
                    ftruncate64(enc_fd.as_raw_fd(), truncate_offset.try_into().unwrap())
                })?;

                // Adjust crypt_fd to read from the last full block offset.
                #[allow(clippy::cast_possible_wrap)]
                lseek64(
                    crypt_fd.as_raw_fd(),
                    last_block_offset as i64,
                    Whence::SeekSet,
                )?;
            } else {
                // No partial block, start reading from the current file size.
                #[allow(clippy::cast_possible_wrap)]
                lseek64(crypt_fd.as_raw_fd(), file_size as i64, Whence::SeekSet)?;
            }

            // Feed existing encrypted data into HMAC calculation until EOF.
            // Read from the encrypted file starting after the header.
            // Here the last partial block is already stripped.
            #[allow(clippy::cast_possible_wrap)]
            lseek64(enc_fd.as_raw_fd(), header_size as i64, Whence::SeekSet)?;
            loop {
                let n = retry_on_eintr(|| {
                    splice(
                        &enc_fd,
                        None,
                        &pipe_wr_mac,
                        None,
                        PIPE_BUF_ALG,
                        SpliceFFlags::empty(),
                    )
                })?;
                if n == 0 {
                    break;
                }

                let mut ncopy = n;
                while ncopy > 0 {
                    let n = retry_on_eintr(|| {
                        splice(
                            &pipe_rd_mac,
                            None,
                            &sock_mac,
                            None,
                            ncopy,
                            SpliceFFlags::SPLICE_F_MORE,
                        )
                    })?;
                    if n == 0 {
                        return Err(Errno::EBADMSG);
                    }
                    ncopy -= n;
                }
            }
        } else {
            // Non-append mode: overwrite the file.

            // Reset crypt_fd to the beginning.
            lseek64(crypt_fd.as_raw_fd(), 0, Whence::SeekSet)?;

            if file_size > 0 {
                // Remove previous content,
                // SAFETY: wipe IV to avoid reuse.
                retry_on_eintr(|| ftruncate64(enc_fd.as_raw_fd(), 0))?;
                lseek64(enc_fd.as_raw_fd(), 0, Whence::SeekSet)?;
            }

            // Write file magic and IV to the beginning of the file.
            // Leave gap for HMAC to write later.
            // SAFETY: We need the write(2) system call to write file
            // magic, HMAC and IV to the file so our seccomp filter
            // unfortunately allows it. We do our best by only allowing
            // writes up the HMAC size, which is 32 bytes. Arguably,
            // pulling a BROP with only 32 bytes of buffer-space allowed
            // to transfer the binary over a socket would be really
            // tedious.
            // Alternatively writing the HMAC & IV to xattrs would be a
            // dangerous (think backups stripping xattrs), and
            // relatively less portable workaround.
            let buf = &CRYPT_MAGIC;
            let mut nwrite = 0;
            while nwrite < buf.len() {
                #[allow(clippy::arithmetic_side_effects)]
                match write(&enc_fd, &buf[nwrite..]) {
                    Ok(0) => return Err(Errno::EINVAL),
                    Ok(n) => nwrite += n,
                    Err(Errno::EINTR) => continue,
                    Err(errno) => return Err(errno),
                }
            }

            // Move the file offset forward by HMAC_TAG_SIZE to leave
            // space for the HMAC tag. This space is going to be a
            // hole until we write back at the end, see lseek(2).
            // lseek64(enc_fd.as_raw_fd(), HMAC_TAG_SIZE as i64, Whence::SeekCur)?;
            // SAFETY: ^^ This is not portable, instead we zero it out!
            // Write HMAC placeholder (zeroed out) to reserve space for HMAC tag.
            let hmac_placeholder = [0u8; HMAC_TAG_SIZE];
            let mut nwrite = 0;
            while nwrite < hmac_placeholder.len() {
                #[allow(clippy::arithmetic_side_effects)]
                match write(&enc_fd, &hmac_placeholder[nwrite..]) {
                    Ok(0) => return Err(Errno::EINVAL),
                    Ok(n) => nwrite += n,
                    Err(Errno::EINTR) => continue,
                    Err(errno) => return Err(errno),
                }
            }

            // Write the IV to the file.
            let buf = iv.as_ref();
            let mut nwrite = 0;
            while nwrite < buf.len() {
                #[allow(clippy::arithmetic_side_effects)]
                match write(&enc_fd, &buf[nwrite..]) {
                    Ok(0) => return Err(Errno::EINVAL),
                    Ok(n) => nwrite += n,
                    Err(Errno::EINTR) => continue,
                    Err(errno) => return Err(errno),
                }
            }
        }

        // Initialize encryption socket, and set IV.
        let sock_enc = aes_ctr_init(&aes_fd, false)?;
        aes_ctr_enc(&sock_enc, &[], Some(&iv), true)?;

        // The IV is no longer needed.
        drop(iv);

        let (pipe_rd_enc, pipe_wr_enc) = pipe2(OFlag::O_CLOEXEC)?;

        // Feed plaintext via zero-copy into the kernel socket.
        let mut nflush = 0;
        loop {
            let nfeed = retry_on_eintr(|| {
                splice(
                    &crypt_fd,
                    None,
                    &pipe_wr_enc,
                    None,
                    PIPE_BUF_ALG,
                    SpliceFFlags::empty(),
                )
            })?;
            if nfeed == 0 {
                break;
            }

            let mut ncopy = nfeed;
            while ncopy > 0 {
                let n = retry_on_eintr(|| {
                    splice(
                        &pipe_rd_enc,
                        None,
                        &sock_enc,
                        None,
                        ncopy,
                        SpliceFFlags::SPLICE_F_MORE,
                    )
                })?;
                if n == 0 {
                    return Err(Errno::EBADMSG);
                }
                ncopy -= n;
            }

            nflush += nfeed;
            #[allow(clippy::cast_possible_truncation)]
            while nflush >= BLOCK_SIZE {
                let len = nflush - (nflush % BLOCK_SIZE);
                let n = retry_on_eintr(|| {
                    splice(
                        &sock_enc,
                        None,
                        &pipe_wr_enc,
                        None,
                        len,
                        SpliceFFlags::SPLICE_F_MORE,
                    )
                })?;
                if n == 0 {
                    return Err(Errno::EBADMSG);
                }

                // Duplicate data from encryption pipe to the MAC pipe using tee(2).
                let mut ntee = n;
                while ntee > 0 {
                    let ntee_size = ntee.min(PIPE_BUF_ALG);
                    let n_tee = retry_on_eintr(|| {
                        tee(&pipe_rd_enc, &pipe_wr_mac, ntee_size, SpliceFFlags::empty())
                    })?;
                    if n_tee == 0 {
                        return Err(Errno::EBADMSG);
                    }
                    ntee -= n_tee;
                }

                // Splice encrypted data to output file.
                let mut ncopy = n;
                while ncopy > 0 {
                    let n = retry_on_eintr(|| {
                        splice(
                            &pipe_rd_enc,
                            None,
                            &enc_fd,
                            None,
                            ncopy,
                            SpliceFFlags::empty(),
                        )
                    })?;
                    if n == 0 {
                        return Err(Errno::EBADMSG);
                    }
                    ncopy -= n;
                    nflush -= n;
                }

                // Splice duplicated data to HMAC socket.
                let mut ncopy_mac = n;
                while ncopy_mac > 0 {
                    let n = retry_on_eintr(|| {
                        splice(
                            &pipe_rd_mac,
                            None,
                            &sock_mac,
                            None,
                            ncopy_mac,
                            SpliceFFlags::SPLICE_F_MORE,
                        )
                    })?;
                    if n == 0 {
                        return Err(Errno::EBADMSG);
                    }
                    ncopy_mac -= n;
                }
            }
        }

        // Finalize encryption with `false`.
        aes_ctr_enc(&sock_enc, &[], None, false)?;

        // Flush the final batch.
        #[allow(clippy::cast_possible_truncation)]
        while nflush > 0 {
            let len = nflush.min(PIPE_BUF_ALG);
            let n = retry_on_eintr(|| {
                splice(
                    &sock_enc,
                    None,
                    &pipe_wr_enc,
                    None,
                    len,
                    SpliceFFlags::empty(),
                )
            })?;
            if n == 0 {
                return Err(Errno::EBADMSG);
            }

            // Duplicate data from encryption pipe to the MAC pipe using tee(2).
            let mut ntee = n;
            while ntee > 0 {
                let ntee_size = ntee.min(PIPE_BUF_ALG);
                let n_tee = retry_on_eintr(|| {
                    tee(&pipe_rd_enc, &pipe_wr_mac, ntee_size, SpliceFFlags::empty())
                })?;
                if n_tee == 0 {
                    return Err(Errno::EBADMSG);
                }
                ntee -= n_tee;
            }

            // Splice encrypted data to output file.
            let mut ncopy = n;
            while ncopy > 0 {
                let n = retry_on_eintr(|| {
                    splice(
                        &pipe_rd_enc,
                        None,
                        &enc_fd,
                        None,
                        ncopy,
                        SpliceFFlags::empty(),
                    )
                })?;
                if n == 0 {
                    return Err(Errno::EBADMSG);
                }
                ncopy -= n;
                nflush -= n;
            }

            // Splice duplicated data to HMAC socket.
            let mut ncopy_mac = n;
            while ncopy_mac > 0 {
                let n = retry_on_eintr(|| {
                    splice(
                        &pipe_rd_mac,
                        None,
                        &sock_mac,
                        None,
                        ncopy_mac,
                        SpliceFFlags::SPLICE_F_MORE,
                    )
                })?;
                if n == 0 {
                    return Err(Errno::EBADMSG);
                }
                ncopy_mac -= n;
            }
        }

        // Finalize HMAC computation and retrieve the tag.
        // SAFETY: This is the only place where we use
        // the read(2) system call hence we allow read(2)
        // system call up to 32 bytes which is the size
        // of the HMAC.
        let hmac_tag = hmac_sha256_fini(&sock_mac)?;

        // Seek back to the position after the magic header.
        #[allow(clippy::cast_possible_wrap)]
        lseek64(
            enc_fd.as_raw_fd(),
            CRYPT_MAGIC.len() as i64,
            Whence::SeekSet,
        )?;

        // Write the HMAC tag to the file.
        let buf = hmac_tag.unsecure();
        let mut nwrite = 0;
        while nwrite < buf.len() {
            #[allow(clippy::arithmetic_side_effects)]
            match write(&enc_fd, &buf[nwrite..]) {
                Ok(0) => return Err(Errno::EINVAL),
                Ok(n) => nwrite += n,
                Err(Errno::EINTR) => continue,
                Err(errno) => return Err(errno),
            }
        }

        // All good, farewell to all OwnedFds!
        Ok(())
    }

    /// Confine Worker thread.
    #[allow(clippy::cognitive_complexity)]
    fn prepare_confine(safe_setid: bool, dry_run: bool) -> SydResult<ScmpFilterContext> {
        // Create seccomp filter with default action.
        let act = if dry_run {
            error!("ctx": "confine", "op": "confine_aes_thread",
                "msg": "AES threads are running unconfined in debug mode");
            ScmpAction::Log
        } else {
            ScmpAction::KillProcess
        };
        let mut ctx = ScmpFilterContext::new(act)?;

        // Enforce the NO_NEW_PRIVS functionality before
        // loading the seccomp filter into the kernel.
        ctx.set_ctl_nnp(true)?;

        // DO NOT synchronize filter to all threads.
        // Main thread will confine itself.
        ctx.set_ctl_tsync(false)?;

        // We kill for bad system call and bad arch.
        ctx.set_act_badarch(ScmpAction::KillProcess)?;

        // Use a binary tree sorted by syscall number if possible.
        let _ = ctx.set_ctl_optimize(2);

        // SAFETY: Do NOT add supported architectures to the filter.
        // This ensures Syd can never run a non-native system call,
        // which we do not need at all.
        // seccomp_add_architectures(&mut ctx)?;

        // Deny open and stat family with ENOSYS rather than KillProcess.
        // We need this because std::thread::spawn has unwanted
        // side-effects such as opening /sys/devices/system/cpu/online
        // on some architectures.
        for sysname in [
            "open",
            "openat",
            "openat2",
            "stat",
            "lstat",
            "statx",
            "newfstatat",
        ] {
            match ScmpSyscall::from_name(sysname) {
                Ok(syscall) => {
                    ctx.add_rule(ScmpAction::Errno(Errno::ENOSYS as i32), syscall)?;
                }
                Err(_) => {
                    crate::info!("ctx": "confine", "op": "allow_syscall",
                        "msg": format!("invalid or unsupported syscall {sysname}"));
                }
            }
        }

        // Allow reads/writes up to HMAC & IV size.
        // See the note in sync_file().
        for sysname in ["read", "write"] {
            match ScmpSyscall::from_name(sysname) {
                Ok(syscall) => {
                    ctx.add_rule_conditional(
                        ScmpAction::Allow,
                        syscall,
                        &[scmp_cmp!($arg2 <= HMAC_TAG_SIZE.max(IV_SIZE) as u64)],
                    )?;
                }
                Err(_) => {
                    crate::info!("ctx": "confine", "op": "allow_syscall",
                        "msg": format!("invalid or unsupported syscall {sysname}"));
                }
            }
        }

        // Allow safe fcntl(2) utility calls.
        for sysname in ["fcntl", "fcntl64"] {
            let syscall = match ScmpSyscall::from_name(sysname) {
                Ok(syscall) => syscall,
                Err(_) => {
                    crate::info!("ctx": "confine", "op": "allow_syscall",
                        "msg": format!("invalid or unsupported syscall {sysname}"));
                    continue;
                }
            };

            for op in AES_FCNTL_OPS {
                ctx.add_rule_conditional(ScmpAction::Allow, syscall, &[scmp_cmp!($arg1 == *op)])?;
            }
        }

        // Allow safe system calls.
        for sysname in AES_SYSCALLS {
            match ScmpSyscall::from_name(sysname) {
                Ok(syscall) => {
                    ctx.add_rule(ScmpAction::Allow, syscall)?;
                }
                Err(_) => {
                    crate::info!("ctx": "confine", "op": "allow_syscall",
                        "msg": format!("invalid or unsupported syscall {sysname}"));
                }
            }
        }

        // Allow futex system calls.
        for sysname in FUTEX_SYSCALLS {
            match ScmpSyscall::from_name(sysname) {
                Ok(syscall) => {
                    ctx.add_rule(ScmpAction::Allow, syscall)?;
                }
                Err(_) => {
                    crate::info!("ctx": "confine", "op": "allow_syscall",
                        "msg": format!("invalid or unsupported syscall {sysname}"));
                }
            }
        }

        // Allow getid system calls.
        for sysname in GET_ID_SYSCALLS {
            match ScmpSyscall::from_name(sysname) {
                Ok(syscall) => {
                    ctx.add_rule(ScmpAction::Allow, syscall)?;
                }
                Err(_) => {
                    crate::info!("ctx": "confine", "op": "allow_syscall",
                        "msg": format!("invalid or unsupported syscall {sysname}"));
                }
            }
        }

        if safe_setid {
            // SAFETY: Main thread confines these further.
            // As these system calls as per-process,
            // the main thread's seccomp rules will apply
            // to us even without TSYNC.
            for sysname in SET_ID_SYSCALLS {
                match ScmpSyscall::from_name(sysname) {
                    Ok(syscall) => {
                        ctx.add_rule(ScmpAction::Allow, syscall)?;
                    }
                    Err(_) => {
                        crate::info!("ctx": "confine", "op": "allow_syscall",
                            "msg": format!("invalid or unsupported syscall {sysname}"));
                    }
                }
            }

            // SAFETY:
            // Signal system calls are necessary to handle reserved signals.
            for sysname in ["sigreturn", "rt_sigreturn"] {
                match ScmpSyscall::from_name(sysname) {
                    Ok(syscall) => {
                        ctx.add_rule(ScmpAction::Allow, syscall)?;
                    }
                    Err(_) => {
                        crate::info!("ctx": "confine", "op": "allow_syscall",
                            "msg": format!("invalid or unsupported syscall {sysname}"));
                    }
                }
            }
        }

        Ok(ctx)
    }
}

#[derive(Clone)]
struct Worker {
    fd: RawFd,
    child: Pid,
    safe_setid: bool,
    wait_all: bool,
    epoll: Arc<Epoll>,
    cache: Arc<WorkerCache<'static>>,
    sandbox: Arc<RwLock<Sandbox>>,
    handlers: Arc<HandlerMap>,
    keep_alive: Option<u16>,
    should_exit: Arc<AtomicBool>,
    worker_data: Arc<WorkerData>,
    crypt_map: Option<AesMap>,
}

impl Worker {
    fn new(
        fd: RawFd,
        epoll: Arc<Epoll>,
        cache: Arc<WorkerCache<'static>>,
        sandbox: Arc<RwLock<Sandbox>>,
        handlers: Arc<HandlerMap>,
        keep_alive: Option<u16>,
        should_exit: Arc<AtomicBool>,
        worker_data: Arc<WorkerData>,
        crypt_map: Option<AesMap>,
    ) -> Self {
        let my_sandbox = SandboxGuard::Read(sandbox.read().unwrap_or_else(|err| err.into_inner()));
        let child = my_sandbox.get_child_pid();
        let wait_all = my_sandbox.exit_wait_all();
        let safe_setid = my_sandbox.allow_safe_setuid() || my_sandbox.allow_safe_setgid();
        drop(my_sandbox); // release the read lock.

        Worker {
            fd,
            child,
            safe_setid,
            wait_all,
            epoll,
            cache,
            sandbox,
            handlers,
            keep_alive,
            should_exit,
            worker_data,
            crypt_map,
        }
    }

    #[allow(clippy::cognitive_complexity)]
    fn try_spawn(self, mut confined: bool) -> Result<JoinHandle<()>, std::io::Error> {
        thread::Builder::new()
            .name("syd_emu".to_string())
            .stack_size(EMU_STACK_SIZE)
            .spawn(move || {
                // Unshare CLONE_FS so cwd and umask are per-thread.
                //
                // SAFETY: We unwrap here and crash the whole process,
                // if this fails as this unsharing is a hard dependency.
                #[allow(clippy::disallowed_methods)]
                unshare(CloneFlags::CLONE_FS).expect("unshare(CLONE_FS)");

                // Create sentinel, that will handle graceful teardown.
                let mut sentinel = Sentinel::new(&self);

                let dry_run = env::var_os(ENV_SKIP_SCMP).is_some();

                if !dry_run {
                    // Set a logging panic hook. The default panic
                    // hook calls system calls not permitted by emulators
                    // such as getcwd(2), stat(2) etc.
                    std::panic::set_hook(Box::new(|info| {
                        let err = match info.payload().downcast_ref::<&'static str>() {
                            Some(s) => *s,
                            None => match info.payload().downcast_ref::<String>() {
                                Some(s) => &**s,
                                None => "?",
                            },
                        };
                        let file = info.location().map(|l| l.file());
                        let line = info.location().map(|l| l.line());
                        error!("ctx": "panic", "err": err, "file": file, "line": line);
                    }));
                }

                // Thread successfully started, increment total worker count.
                let worker_count = self.worker_data.increment_worker_total();

                // Attempt to set thread's CPU affinity mask.
                // We pin the main, init and monitor threads to CPU:0.
                // Emulator threads are pinned based on num-cpus.
                #[allow(clippy::arithmetic_side_effects)]
                let cpu_id = worker_count % *NPROC;
                let mut cpu_set = CpuSet::new();
                if cpu_set.set(cpu_id).is_ok() {
                    match sched_setaffinity(Pid::from_raw(0), &cpu_set) {
                        Ok(_) => {
                            crate::info!("ctx": "boot", "op": "pin_emulator_thread",
                                "msg": format!("pinned emulator thread to CPU:{cpu_id}"),
                                "cpu": cpu_id);
                        }
                        Err(errno) => {
                            error!("ctx": "boot", "op": "pin_emulator_thread",
                                "err": format!("failed to pin emulator thread to CPU:{cpu_id}: {errno}"),
                                "cpu": cpu_id);
                        }
                    }
                }

                loop {
                    // SAFETY: Confine if/once locked.
                    if !confined && Sandbox::locked_once() {
                        crate::info!("ctx": "confine", "op": "confine_emulator_thread",
                            "msg": format!("emulator thread confined with{} SROP mitigation",
                                if self.safe_setid { "out" } else { "" }));

                        // SAFETY: Panic if we cannot confine the thread.
                        Self::prepare_confine(
                            self.fd,
                            self.epoll.0.as_raw_fd(),
                            self.safe_setid,
                            dry_run,
                        )
                        .expect("prepare emulator thread confinement")
                        .load()
                        .expect("confine emulator thread");

                        confined = true;
                    }

                    // Wait for the request to become ready as necessary.
                    // epoll_wait(2) will timeout and exit for non-core threads.
                    if self.poll().is_err() {
                        // Timeout or critical error.
                        // Decrement worker total and exit.
                        self.worker_data.decrement_worker_total();
                        break;
                    }

                    // Receive seccomp notification.
                    let request = if let Ok(request) = self.receive() {
                        request
                    } else {
                        // Critical error, decrement worker total and exit.
                        self.worker_data.decrement_worker_total();
                        break;
                    };

                    if let Some(request) = request {
                        // Mark thread busy.
                        sentinel.seccomp_id = Some(request.id);
                        self.worker_data.increment_worker_busy();

                        // Handle request.
                        self.handle(request);

                        // Mark thread idle again.
                        sentinel.seccomp_id = None;
                        self.worker_data.decrement_worker_busy();
                    } // else process died-midway, continue.
                }
            })
    }

    fn receive(&self) -> Result<Option<ScmpNotifReq>, Errno> {
        // Receive and return request.
        // Break if file descriptor was closed.
        // Ignore rest of the errors as we cannot handle them,
        // e.g: EINTR|ENOENT: task is killed mid-way.
        match self.read() {
            Ok(request) => Ok(Some(request)),
            Err(Errno::EBADF) => Err(Errno::EBADF),
            Err(_) => Ok(None),
        }
    }

    fn read(&self) -> Result<ScmpNotifReq, Errno> {
        // Use libc::seccomp_notif rather than libseccomp_sys's.
        // The latter is opaque and requires us to do a heap
        // allocation which we don't always want.
        let mut req: MaybeUninit<libc::seccomp_notif> = MaybeUninit::zeroed();

        // SAFETY: libseccomp's wrapper allocates each call.
        // Note: EINTR means child killed by signal!
        Errno::result(unsafe { seccomp_notify_receive(self.fd, req.as_mut_ptr().cast()) })?;

        // SAFETY: seccomp_notify_receive returned success.
        // Request is populated and accessing it is safe.
        let req = ScmpNotifReq::from_sys(unsafe { req.assume_init() })?;

        if req.id != 0 && req.pid != 0 {
            Ok(req)
        } else {
            // interrupted/task killed mid-way.
            Err(Errno::EINTR)
        }
    }

    #[allow(clippy::cognitive_complexity)]
    fn handle(&self, req: ScmpNotifReq) {
        // Lookup the system call handler, panic if not found.
        let syscall = Sydcall(req.data.syscall, scmp_arch_raw(req.data.arch));
        let handler = if let Some(handler) = self.handlers.get(&syscall) {
            handler
        } else {
            unreachable!("BUG: Missing hook for request {req:?}!");
        };

        let request = UNotifyEventRequest::new(
            req,
            syscall,
            self.fd,
            Arc::clone(&self.cache),
            Arc::clone(&self.sandbox),
            self.crypt_map.as_ref().map(Arc::clone),
        );
        let response = handler(request);

        if response.id == 0 && response.val == 0 && response.error == 0 && response.flags == 0 {
            // Dummy seccomp response.
            // A previous addfd request has
            // already replied to the request.
            // Nothing left to do here.
            return;
        } else if response.error == EOWNERDEAD {
            // EOWNERDEAD is a pseudo errno used by
            // the stat handler thread to close the
            // seccomp notify fd upon receiving the
            // "ghost" command.
            crate::warn!("ctx": "confine", "op": "enter_ghost_mode", "pid": req.pid,
                "sys": syscall, "arch": SydArch(req.data.arch), "args": req.data.args,
                "src": proc_mmap(req.pid(), req.data.instr_pointer).ok());
        }

        let mut response = libc::seccomp_notif_resp {
            id: response.id,
            val: response.val,
            error: response.error,
            flags: response.flags,
        };
        // libc's data type is not opaque unlike libseccomp's.
        let ptr: *mut seccomp_notif_resp =
            std::ptr::addr_of_mut!(response) as *mut seccomp_notif_resp;

        // SAFETY:
        // 1. libseccomp's version allocates needlessly
        // 2. Nothing we can do on errors, EINTR means child dead.
        unsafe { seccomp_notify_respond(self.fd, ptr) };

        // See above.
        if response.error == EOWNERDEAD {
            // Note, threads blocked on epoll_wait will not
            // wake up even if we close the epoll fd or
            // delete the seccomp fd from epoll wait-list here.
            // That said, they'll never ever wake up again,
            // and therefore will not consume system resources.
            let _ = epoll_ctl_safe(&self.epoll.0, self.fd, None);
            let _ = close(self.fd);

            // Inform the monitor thread to exit.
            self.should_exit.store(true, Ordering::Relaxed);
        }
    }

    fn poll(&self) -> Result<(), Errno> {
        let timeout = if let Some(keep_alive) = self.keep_alive {
            PollTimeout::from(keep_alive)
        } else {
            PollTimeout::NONE
        };

        // Wait for an event and handle EINTR.
        // Retire threads which have exited along the way.
        let mut events = [EpollEvent::empty(); 1];
        loop {
            if self.should_exit.load(Ordering::Relaxed) {
                // Exit notified, do not try to wait on epoll again.
                return Err(Errno::ESRCH);
            }

            match self.epoll.wait(&mut events, timeout) {
                Ok(0) if self.keep_alive.is_some() => return Err(Errno::ETIMEDOUT),
                Ok(0) | Err(Errno::EINTR) => {} // try again.
                Ok(1) if events[0].data() == 0 => return Ok(()), // scmp-req.
                Ok(1) => {
                    // Record retired PID and try again.
                    // These fds are added with EPOLLONESHOT.
                    #[allow(clippy::cast_possible_truncation)]
                    let pid = Pid::from_raw(events[0].data() as libc::pid_t);

                    // Retire the PidFd.
                    if let Some(map) = PIDFD_MAP.get() {
                        map.del_pidfd(pid);
                    }

                    if !self.wait_all && pid == self.child {
                        // Note, threads blocked on epoll_wait will not
                        // wake up even if we close the epoll fd or
                        // delete the seccomp fd from epoll wait-list here.
                        // That said, they'll never ever wake up again,
                        // and therefore will not consume system resources.
                        let _ = epoll_ctl_safe(&self.epoll.0, self.fd, None);
                        let _ = close(self.fd);

                        // Inform the monitor thread to exit.
                        self.should_exit.store(true, Ordering::Relaxed);

                        // Exiting with the eldest process.
                        return Err(Errno::ESRCH);
                    }

                    // If we're waiting for all processes, let's just try again.
                }
                Ok(n) => unreachable!("BUG: epoll_wait returned ${n} unexpectedly!"),
                Err(errno) => return Err(errno),
            };
        }
    }

    /// Confine Worker thread.
    #[allow(clippy::cognitive_complexity)]
    fn prepare_confine(
        seccomp_fd: RawFd,
        epoll_fd: RawFd,
        safe_setid: bool,
        dry_run: bool,
    ) -> SydResult<ScmpFilterContext> {
        // Create seccomp filter with default action.
        let act = if dry_run {
            error!("ctx": "confine", "op": "confine_emulator_thread",
                "msg": "emulator threads are running unconfined in debug mode");
            ScmpAction::Log
        } else {
            ScmpAction::KillProcess
        };
        let mut ctx = ScmpFilterContext::new(act)?;

        // Enforce the NO_NEW_PRIVS functionality before
        // loading the seccomp filter into the kernel.
        ctx.set_ctl_nnp(true)?;

        // DO NOT synchronize filter to all threads.
        // Main thread will confine itself.
        ctx.set_ctl_tsync(false)?;

        // We kill for bad system call and bad arch.
        ctx.set_act_badarch(ScmpAction::KillProcess)?;

        // Use a binary tree sorted by syscall number if possible.
        let _ = ctx.set_ctl_optimize(2);

        // SAFETY: Do NOT add supported architectures to the filter.
        // This ensures Syd can never run a non-native system call,
        // which we do not need at all.
        // seccomp_add_architectures(&mut ctx)?;

        // Deny open and {l,}stat with ENOSYS rather than KillProcess.
        // We need this because std::thread::spawn has unwanted
        // side-effects such as opening /sys/devices/system/cpu/online
        // on some architectures.
        for sysname in ["open", "stat", "lstat"] {
            match ScmpSyscall::from_name(sysname) {
                Ok(syscall) => {
                    ctx.add_rule(ScmpAction::Errno(Errno::ENOSYS as i32), syscall)?;
                }
                Err(_) => {
                    crate::info!("ctx": "confine", "op": "allow_syscall",
                        "msg": format!("invalid or unsupported syscall {sysname}"));
                }
            }
        }

        // Allow a restricted set of ioctl(2) operations to our seccomp fd only.
        let sysname = "ioctl";
        #[allow(clippy::cast_sign_loss)]
        match ScmpSyscall::from_name(sysname) {
            Ok(syscall) => {
                for ioctl_request in SECCOMP_IOCTL_LIST {
                    ctx.add_rule_conditional(
                        ScmpAction::Allow,
                        syscall,
                        &[
                            scmp_cmp!($arg0 == seccomp_fd as u64),
                            scmp_cmp!($arg1 == *ioctl_request),
                        ],
                    )?;

                    if let Some(ioctl_request) = extend_ioctl(*ioctl_request) {
                        ctx.add_rule_conditional(
                            ScmpAction::Allow,
                            syscall,
                            &[
                                scmp_cmp!($arg0 == seccomp_fd as u64),
                                scmp_cmp!($arg1 == ioctl_request),
                            ],
                        )?;
                    }
                }
            }
            Err(_) => {
                crate::info!("ctx": "confine", "op": "allow_syscall",
                    "msg": format!("invalid or unsupported syscall {sysname}"));
            }
        }

        // Allow epoll(7) API to our single epoll fd only.
        //
        // TODO: Move this to EPOLL_SYSCALLS in src/config.rs
        #[allow(clippy::cast_sign_loss)]
        for sysname in [
            // SAFETY: epoll fd is created once at startup,
            // before the seccomp filters have been loaded.
            // "epoll_create",
            // "epoll_create1",
            "epoll_ctl",
            "epoll_ctl_old",
            "epoll_wait",
            "epoll_wait_old",
            "epoll_pwait",
            "epoll_pwait2",
        ] {
            match ScmpSyscall::from_name(sysname) {
                Ok(syscall) => {
                    ctx.add_rule_conditional(
                        ScmpAction::Allow,
                        syscall,
                        &[scmp_cmp!($arg0 == epoll_fd as u64)],
                    )?;
                }
                Err(_) => {
                    crate::info!("ctx": "confine", "op": "allow_syscall",
                        "msg": format!("invalid or unsupported syscall {sysname}"));
                }
            }
        }

        // Allow unshare(2) with CLONE_FS only.
        let sysname = "unshare";
        match ScmpSyscall::from_name(sysname) {
            Ok(syscall) => {
                ctx.add_rule_conditional(
                    ScmpAction::Allow,
                    syscall,
                    &[scmp_cmp!($arg0 == libc::CLONE_FS as u64)],
                )?;
            }
            Err(_) => {
                crate::info!("ctx": "confine", "op": "allow_syscall",
                    "msg": format!("invalid or unsupported syscall {sysname}"));
            }
        }

        // SAFETY: Allow write(2) _only_ if trace/memory_access allows
        // proc_pid_mem(5) access in which case we cannot avoid it.
        // The default is to use cross memory attach with
        // process_vm_{read,write}v(2) unconditionally since
        // version 3.32.6 unless relaxed.
        let sysname = "write";
        if Sandbox::memory_access() == 2 {
            // Allow write(2) access to syd::log::LOG_FD only.
            // If logging is disabled, deny write(2) completely.
            // Note, we do allow the use-case where LOG_FD is
            // intentionally set to a negative value to disable
            // logging, hence we cast directly to an u64 and not
            // use u64::try_from!
            let log_fd = crate::log::LOG_FD.load(std::sync::atomic::Ordering::Relaxed);
            match ScmpSyscall::from_name(sysname) {
                Ok(syscall) => {
                    ctx.add_rule_conditional(
                        ScmpAction::Allow,
                        syscall,
                        &[scmp_cmp!($arg0 == log_fd as u64)],
                    )?;
                }
                Err(_) => {
                    crate::info!("ctx": "confine", "op": "allow_syscall",
                        "msg": format!("invalid or unsupported syscall {sysname}"));
                }
            }
        } else {
            // Allow write(2) globally for proc_pid_mem(5) access.
            match ScmpSyscall::from_name(sysname) {
                Ok(syscall) => {
                    ctx.add_rule(ScmpAction::Allow, syscall)?;
                }
                Err(_) => {
                    crate::info!("ctx": "confine", "op": "allow_syscall",
                        "msg": format!("invalid or unsupported syscall {sysname}"));
                }
            }
        }

        // Allow safe fcntl(2) utility calls.
        for sysname in ["fcntl", "fcntl64"] {
            let syscall = match ScmpSyscall::from_name(sysname) {
                Ok(syscall) => syscall,
                Err(_) => {
                    crate::info!("ctx": "confine", "op": "allow_syscall",
                        "msg": format!("invalid or unsupported syscall {sysname}"));
                    continue;
                }
            };

            for op in EMU_FCNTL_OPS {
                ctx.add_rule_conditional(ScmpAction::Allow, syscall, &[scmp_cmp!($arg1 == *op)])?;
            }
        }

        // Allow safe system calls.
        for sysname in EMU_SYSCALLS {
            match ScmpSyscall::from_name(sysname) {
                Ok(syscall) => {
                    ctx.add_rule(ScmpAction::Allow, syscall)?;
                }
                Err(_) => {
                    crate::info!("ctx": "confine", "op": "allow_syscall",
                        "msg": format!("invalid or unsupported syscall {sysname}"));
                }
            }
        }

        // openat(2) may be used to open the parent directory only by getdir_long().
        // The rest of the attempts are denied with ENOSYS for compat.
        let sysname = "openat";
        #[allow(clippy::cast_sign_loss)]
        match ScmpSyscall::from_name(sysname) {
            Ok(syscall) => {
                let dotdot = dotdot_with_nul();
                let oflags = (libc::O_RDONLY
                    | libc::O_CLOEXEC
                    | libc::O_DIRECTORY
                    | libc::O_LARGEFILE
                    | libc::O_NOCTTY
                    | libc::O_NOFOLLOW) as u64;
                ctx.add_rule_conditional(
                    ScmpAction::Allow,
                    syscall,
                    &[
                        scmp_cmp!($arg0 <= RawFd::MAX as u64),
                        scmp_cmp!($arg1 == dotdot),
                        scmp_cmp!($arg2 & oflags == oflags),
                    ],
                )?;
                ctx.add_rule_conditional(
                    ScmpAction::Errno(Errno::ENOSYS as i32),
                    syscall,
                    &[scmp_cmp!($arg0 > RawFd::MAX as u64)],
                )?;
                ctx.add_rule_conditional(
                    ScmpAction::Errno(Errno::ENOSYS as i32),
                    syscall,
                    &[scmp_cmp!($arg1 != dotdot)],
                )?;
            }
            Err(_) => {
                crate::info!("ctx": "confine", "op": "allow_syscall",
                    "msg": format!("invalid or unsupported syscall {sysname}"));
            }
        }

        // Allow futex system calls.
        for sysname in FUTEX_SYSCALLS {
            match ScmpSyscall::from_name(sysname) {
                Ok(syscall) => {
                    ctx.add_rule(ScmpAction::Allow, syscall)?;
                }
                Err(_) => {
                    crate::info!("ctx": "confine", "op": "allow_syscall",
                        "msg": format!("invalid or unsupported syscall {sysname}"));
                }
            }
        }

        // Allow getid system calls.
        for sysname in GET_ID_SYSCALLS {
            match ScmpSyscall::from_name(sysname) {
                Ok(syscall) => {
                    ctx.add_rule(ScmpAction::Allow, syscall)?;
                }
                Err(_) => {
                    crate::info!("ctx": "confine", "op": "allow_syscall",
                        "msg": format!("invalid or unsupported syscall {sysname}"));
                }
            }
        }

        if safe_setid {
            // SAFETY: Main thread confines these further.
            // As these system calls as per-process,
            // the main thread's seccomp rules will apply
            // to us even without TSYNC.
            for sysname in SET_ID_SYSCALLS {
                match ScmpSyscall::from_name(sysname) {
                    Ok(syscall) => {
                        ctx.add_rule(ScmpAction::Allow, syscall)?;
                    }
                    Err(_) => {
                        crate::info!("ctx": "confine", "op": "allow_syscall",
                            "msg": format!("invalid or unsupported syscall {sysname}"));
                    }
                }
            }

            // SAFETY:
            // 1. cap{g,s}et is necessary to drop CAP_SET{U,G}ID after changing {U,G}ID.
            // 2. Signal system calls are necessary to handle reserved signals.
            // Note, {rt_,}sigreturn is already allowed for emulators to handle SIGALRM.
            for sysname in ["capget", "capset", "sigaction", "rt_sigaction"] {
                match ScmpSyscall::from_name(sysname) {
                    Ok(syscall) => {
                        ctx.add_rule(ScmpAction::Allow, syscall)?;
                    }
                    Err(_) => {
                        crate::info!("ctx": "confine", "op": "allow_syscall",
                            "msg": format!("invalid or unsupported syscall {sysname}"));
                    }
                }
            }
        }

        Ok(ctx)
    }
}

/// Type that exists to manage worker exit on panic.
///
/// This type is constructed once per `Worker` and implements `Drop` to
/// handle proper worker exit in case the worker panics when executing
/// the current task or anywhere else in its work loop. If the
/// `Sentinel` is dropped at the end of the worker's work loop and the
/// current thread is panicking, handle worker exit the same way as if
/// the task completed normally (if the worker panicked while executing
/// a submitted task) then clone the worker and start it with an initial
/// task of `None`.
struct Sentinel<'a> {
    seccomp_id: Option<u64>,
    worker_ref: &'a Worker,
}

impl<'a> Sentinel<'a> {
    fn new(worker_ref: &'a Worker) -> Sentinel<'a> {
        Self {
            seccomp_id: None,
            worker_ref,
        }
    }

    #[allow(clippy::arithmetic_side_effects)]
    fn deny_syscall(&self, seccomp_id: u64, errno: Errno) {
        let mut resp = libc::seccomp_notif_resp {
            id: seccomp_id,
            val: 0,
            error: -(errno as i32),
            flags: 0,
        };
        // libc's data type is not opaque unlike libseccomp's.
        let ptr: *mut seccomp_notif_resp = std::ptr::addr_of_mut!(resp) as *mut seccomp_notif_resp;

        // SAFETY:
        // 1. libseccomp's version allocates needlessly
        // 2. Nothing we can do on errors, EINTR means child dead.
        unsafe { seccomp_notify_respond(self.worker_ref.fd, ptr) };
    }
}

impl Drop for Sentinel<'_> {
    fn drop(&mut self) {
        if thread::panicking() {
            if let Some(seccomp_id) = self.seccomp_id {
                // Busy thread panicked.
                // SAFETY: Deny syscall in progress!
                self.deny_syscall(seccomp_id, Errno::EACCES);
                self.worker_ref.worker_data.decrement_both();
            } else {
                // Idle thread panicked.
                self.worker_ref.worker_data.decrement_worker_total();
            }
        }
    }
}

// The absolute maximum number of workers. This corresponds to the
// maximum value that can be stored within half the bits of usize, as
// two counters (total workers and busy workers) are stored in one
// AtomicUsize.
const BITS: usize = std::mem::size_of::<usize>() * 8;
const MAX_SIZE: usize = (1 << (BITS / 2)) - 1;

const WORKER_BUSY_MASK: usize = MAX_SIZE;
const INCREMENT_TOTAL: usize = 1 << (BITS / 2);
const INCREMENT_BUSY: usize = 1;

/// 1. Struct containing data shared between workers.
/// 2. Struct that stores and handles an `AtomicUsize` that stores the
///    total worker count in the higher half of bits and the busy worker
///    count in the lower half of bits. This allows to to increment /
///    decrement both counters in a single atomic operation.
#[derive(Default)]
struct WorkerData(AtomicUsize);

impl WorkerData {
    fn increment_both(&self) -> (usize, usize) {
        let old_val = self
            .0
            .fetch_add(INCREMENT_TOTAL | INCREMENT_BUSY, Ordering::Relaxed);
        Self::split(old_val)
    }

    fn decrement_both(&self) -> (usize, usize) {
        let old_val = self
            .0
            .fetch_sub(INCREMENT_TOTAL | INCREMENT_BUSY, Ordering::Relaxed);
        Self::split(old_val)
    }

    fn increment_worker_total(&self) -> usize {
        let old_val = self.0.fetch_add(INCREMENT_TOTAL, Ordering::Relaxed);
        Self::total(old_val)
    }

    #[allow(dead_code)]
    fn decrement_worker_total(&self) -> usize {
        let old_val = self.0.fetch_sub(INCREMENT_TOTAL, Ordering::Relaxed);
        Self::total(old_val)
    }

    fn increment_worker_busy(&self) -> usize {
        let old_val = self.0.fetch_add(INCREMENT_BUSY, Ordering::Relaxed);
        Self::busy(old_val)
    }

    fn decrement_worker_busy(&self) -> usize {
        let old_val = self.0.fetch_sub(INCREMENT_BUSY, Ordering::Relaxed);
        Self::busy(old_val)
    }

    fn get_total_count(&self) -> usize {
        Self::total(self.0.load(Ordering::Relaxed))
    }

    fn get_busy_count(&self) -> usize {
        Self::busy(self.0.load(Ordering::Relaxed))
    }

    #[inline]
    fn split(val: usize) -> (usize, usize) {
        let total_count = val >> (BITS / 2);
        let busy_count = val & WORKER_BUSY_MASK;
        (total_count, busy_count)
    }

    #[inline]
    fn total(val: usize) -> usize {
        val >> (BITS / 2)
    }

    #[inline]
    fn busy(val: usize) -> usize {
        val & WORKER_BUSY_MASK
    }
}
