//
// Syd: rock-solid application kernel
// src/magic.rs: magic symlink utilities
//
// Copyright (c) 2025 Ali Polatel <alip@chesswob.org>
// SPDX-License-Identifier: GPL-3.0

use std::os::fd::RawFd;

use btoi::btoi;
use libc::pid_t;
use memchr::{
    arch::all::{is_equal, is_prefix},
    memchr,
};
use nix::{errno::Errno, unistd::Pid};

use crate::path::{XPath, XPathBuf};

const FD: &[u8] = b"/fd/";
const PROC: &[u8] = b"/proc/";
const TASK: &[u8] = b"/task/";

/// Represents a proc(5) magic symlink.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ProcMagic {
    /// `/proc/self`
    Pid {
        /// Owner process or thread ID used in the `/proc/self` path.
        pid: Pid,
    },
    /// `proc/thread-self`
    Tid {
        /// Owner thread ID used in the `/proc/thread-self` path.
        pid: Pid,
        /// Owner thread group ID used in the `/proc/thread-self` path.
        tgid: Pid,
    },
    /// `/proc/<pid>/fd/<n>`
    Fd {
        /// Owner process or thread ID used in the `/proc/<pid>` path.
        pid: Pid,
        /// File descriptor number `<n>` within that process.
        fd: RawFd,
    },
    /// `/proc/<pid>/cwd`
    Cwd {
        /// Owner process or thread ID used in the `/proc/<pid>` path.
        pid: Pid,
    },
    /// `/proc/<pid>/root`
    Root {
        /// Owner process or thread ID used in the `/proc/<pid>` path.
        pid: Pid,
    },
    /// `/proc/<pid>/exe`
    Exe {
        /// Owner process or thread ID used in the `/proc/<pid>` path.
        pid: Pid,
    },
    /// `/proc/<pid>[/task/<tid>]/ns/<kind>`
    Ns {
        /// Owner process or thread whose namespace entry is referenced.
        pid: Pid,
        /// Namespace entry kind under `/proc/<pid>/ns/` (e.g., `Net`, `Mnt`).
        kind: NsKind,
    },
}

impl ProcMagic {
    /// Detect and classify /proc magic symlinks (fd/cwd/root/exe/ns).
    ///
    /// This function does not check for `/proc/self` and `/proc/thread-self`.
    pub fn check_link(
        expected_pid: Pid,
        path: &XPath,
        restrict_magiclinks: bool,
    ) -> Result<Option<Self>, Errno> {
        if !path.is_proc() {
            return Ok(None); // not under /proc
        }
        // Strip "/proc"
        let after_proc = &path.as_bytes()[PROC.len()..];

        // Parse "<pid>"
        let pid_sep_index = memchr(b'/', after_proc).unwrap_or(after_proc.len());
        let pid_component = &after_proc[..pid_sep_index];
        if pid_component.is_empty() || pid_component.iter().any(|c| !c.is_ascii_digit()) {
            return Ok(None); // e.g., /proc/mounts, /proc/sys, etc.
        }

        // When restricted, enforce that the first PID matches the caller's PID.
        let mut current_pid = if restrict_magiclinks {
            let parsed = bytes_to_pid(pid_component)?;
            if parsed != expected_pid {
                // PID mismatch detected!
                //
                // SAFETY: Note, ideally we want to return
                // ENOENT here for stealth, however this
                // confuses programs such as pipewire when
                // they're checking for flatpak support.
                // Check for pw_check_flatpak() function
                // in pipewire source code for more information.
                return Err(Errno::EACCES);
            }
            parsed
        } else {
            bytes_to_pid(pid_component)?
        };

        // Remainder starts at "/" or is empty.
        let mut remainder = &after_proc[pid_sep_index..];

        // Optional "/task/<tid>"
        if is_prefix(remainder, TASK) {
            let after_task = &remainder[TASK.len()..]; // begins at "<tid>..." or ""
            let tid_end = memchr(b'/', after_task).unwrap_or(after_task.len());
            if tid_end == 0 {
                return Ok(None); // "/proc/<pid>/task/" with no <tid>
            }
            current_pid = bytes_to_pid(&after_task[..tid_end])?;
            remainder = &after_task[tid_end..]; // now "/" or ""
        }

        // Detect specific magic entries (all zero-alloc on input slices).
        if is_prefix(remainder, FD) {
            // /proc/<pid>/fd/<n>  — require digits only.
            let fd_digits = &remainder[FD.len()..];
            if fd_digits.is_empty() || fd_digits.iter().any(|c| !c.is_ascii_digit()) {
                return Ok(None);
            }
            let fd_value = bytes_to_fd(fd_digits)?;
            return Ok(Some(Self::Fd {
                pid: current_pid,
                fd: fd_value,
            }));
        }

        if is_equal(remainder, b"/cwd") {
            return Ok(Some(Self::Cwd { pid: current_pid }));
        }
        if is_equal(remainder, b"/root") {
            return Ok(Some(Self::Root { pid: current_pid }));
        }
        if is_equal(remainder, b"/exe") {
            return Ok(Some(Self::Exe { pid: current_pid }));
        }

        // Namespaces: "/ns/<name>" with no additional '/'
        if remainder.starts_with(b"/ns/") {
            let name = &remainder[b"/ns/".len()..];
            if !name.is_empty() && memchr::memchr(b'/', name).is_none() {
                if let Ok(kind) = NsKind::try_from(name) {
                    return Ok(Some(ProcMagic::Ns {
                        pid: current_pid,
                        kind,
                    }));
                }
            }
        }

        Ok(None)
    }

    /// Return remote fd magic symlink path.
    pub fn link_path(self) -> Result<XPathBuf, Errno> {
        match self {
            Self::Pid { .. } => Ok(XPathBuf::from("/proc/self")),
            Self::Tid { .. } => Ok(XPathBuf::from("/proc/thread-self")),
            Self::Fd { pid, fd } => XPathBuf::from_pid_fd(pid, fd),
            Self::Cwd { pid } => XPathBuf::from_cwd(pid),
            Self::Root { pid } => XPathBuf::from_root(pid),
            Self::Exe { pid } => XPathBuf::from_exe(pid),
            Self::Ns { pid, kind } => {
                let mut sym = XPathBuf::from_pid(pid)?;
                sym.try_reserve("/ns/time_for_children".len())
                    .or(Err(Errno::ENOMEM))?;
                sym.push(b"ns");
                sym.push(match kind {
                    NsKind::Cgroup => b"cgroup",
                    NsKind::Ipc => b"ipc",
                    NsKind::Mnt => b"mnt",
                    NsKind::Net => b"net",
                    NsKind::Pid => b"pid",
                    NsKind::PidForChildren => b"pid_for_children",
                    NsKind::Time => b"time",
                    NsKind::TimeForChildren => b"time_for_children",
                    NsKind::User => b"user",
                    NsKind::Uts => b"uts",
                });
                sym.shrink_to_fit();
                Ok(sym)
            }
        }
    }

    /// Return the RawFd used by procfs for this magic link (Ns is EINVAL).
    #[inline]
    pub fn link_fd(self) -> Result<RawFd, Errno> {
        match self {
            Self::Fd { fd, .. } => Ok(fd),
            Self::Cwd { .. } => Ok(libc::AT_FDCWD),
            Self::Root { .. } => Ok(-1),
            Self::Exe { .. } => Ok(-2),
            _ => Err(Errno::EINVAL),
        }
    }

    /// Tell whether the target must be opened as a directory.
    #[inline]
    pub fn want_dir(self) -> bool {
        matches!(self, Self::Cwd { .. } | Self::Root { .. })
    }

    /// Return the last path component for this magic link when under /proc,
    /// e.g. "42" for fd 42, "net" for ns net, or None if not applicable.
    pub fn base(self) -> Result<Option<XPathBuf>, Errno> {
        match self {
            Self::Pid { pid } => Ok(Some(XPathBuf::from_pid(pid)?)),
            Self::Tid { tgid, pid } => Ok(Some(XPathBuf::from_task(tgid, pid)?)),
            Self::Fd { fd, .. } => Ok(Some(XPathBuf::from_fd(fd)?)),
            Self::Ns { kind, .. } => Ok(Some(XPathBuf::try_from(kind)?)),
            _ => Ok(None),
        }
    }

    /// Owning PID/TID of this magic entry.
    pub fn pid(self) -> Pid {
        match self {
            Self::Pid { pid, .. } => pid,
            Self::Tid { pid, .. } => pid,
            Self::Fd { pid, .. } => pid,
            Self::Cwd { pid } => pid,
            Self::Root { pid } => pid,
            Self::Exe { pid } => pid,
            Self::Ns { pid, .. } => pid,
        }
    }
}

/// Known namespace entry names in /proc/<pid>/ns.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NsKind {
    /// cgroup namespace
    Cgroup,
    /// ipc namespace
    Ipc,
    /// mount namespace
    Mnt,
    /// network namespace
    Net,
    /// pid namespace
    Pid,
    /// pid namespace for children
    PidForChildren,
    /// time namespace
    Time,
    /// time namespace for children
    TimeForChildren,
    /// user namespace
    User,
    /// uts namespace
    Uts,
}

impl TryFrom<NsKind> for XPathBuf {
    type Error = Errno;

    fn try_from(kind: NsKind) -> Result<Self, Self::Error> {
        let kind = match kind {
            NsKind::Cgroup => "cgroup",
            NsKind::Ipc => "ipc",
            NsKind::Mnt => "mnt",
            NsKind::Net => "net",
            NsKind::Pid => "pid",
            NsKind::PidForChildren => "pid_for_children",
            NsKind::Time => "time",
            NsKind::TimeForChildren => "time_for_children",
            NsKind::User => "user",
            NsKind::Uts => "uts",
        };
        let mut path = Vec::new();
        path.try_reserve(kind.len()).or(Err(Errno::ENOMEM))?;
        path.extend_from_slice(kind.as_bytes());
        Ok(path.into())
    }
}

impl TryFrom<&[u8]> for NsKind {
    type Error = Errno;

    fn try_from(name: &[u8]) -> Result<Self, Self::Error> {
        match name {
            b"net" => Ok(Self::Net),
            b"mnt" => Ok(Self::Mnt),
            b"pid" => Ok(Self::Pid),
            b"user" => Ok(Self::User),
            b"uts" => Ok(Self::Uts),
            b"ipc" => Ok(Self::Ipc),
            b"cgroup" => Ok(Self::Cgroup),
            b"time" => Ok(Self::Time),
            b"pid_for_children" => Ok(Self::PidForChildren),
            b"time_for_children" => Ok(Self::TimeForChildren),
            _ => Err(Errno::EOPNOTSUPP),
        }
    }
}

fn bytes_to_pid(bytes: &[u8]) -> Result<Pid, Errno> {
    btoi::<pid_t>(bytes)
        .map(Pid::from_raw)
        .or(Err(Errno::EINVAL))
}

fn bytes_to_fd(bytes: &[u8]) -> Result<RawFd, Errno> {
    btoi::<RawFd>(bytes).or(Err(Errno::EINVAL))
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::{
        path::{XPath, XPathBuf},
        xpath,
    };

    fn assert_ok_some(result: Result<Option<ProcMagic>, Errno>, expected: ProcMagic) {
        assert_eq!(result, Ok(Some(expected)));
    }
    fn assert_ok_none(result: Result<Option<ProcMagic>, Errno>) {
        assert_eq!(result, Ok(None));
    }
    fn assert_err(result: Result<Option<ProcMagic>, Errno>, e: Errno) {
        assert_eq!(result, Err(e));
    }

    #[test]
    fn test_check_link_port_cwd_restricted_this() {
        let this = Pid::this();
        assert_ok_some(
            ProcMagic::check_link(this, &xpath!("/proc/{this}/cwd"), true),
            ProcMagic::Cwd { pid: this },
        );
    }

    #[test]
    fn test_check_link_port_exe_restricted_this() {
        let this = Pid::this();
        assert_ok_some(
            ProcMagic::check_link(this, &xpath!("/proc/{this}/exe"), true),
            ProcMagic::Exe { pid: this },
        );
    }

    #[test]
    fn test_check_link_port_root_restricted_this() {
        let this = Pid::this();
        assert_ok_some(
            ProcMagic::check_link(this, &xpath!("/proc/{this}/root"), true),
            ProcMagic::Root { pid: this },
        );
    }

    #[test]
    fn test_check_link_port_fd_dir_restricted_that_eacces() {
        let this = Pid::this();
        let that = Pid::from_raw(1);
        assert_err(
            ProcMagic::check_link(this, &xpath!("/proc/{that}/fd"), true),
            Errno::EACCES,
        );
    }

    #[test]
    fn test_check_link_port_fd_dir_unrestricted_that_none() {
        let this = Pid::this();
        let that = Pid::from_raw(1);
        assert_ok_none(ProcMagic::check_link(
            this,
            &xpath!("/proc/{that}/fd"),
            false,
        ));
    }

    #[test]
    fn test_check_link_port_fd_dir_restricted_this_none() {
        let this = Pid::this();
        assert_ok_none(ProcMagic::check_link(
            this,
            &xpath!("/proc/{this}/fd"),
            true,
        ));
    }

    #[test]
    fn test_check_link_port_fd0_restricted_this() {
        let this = Pid::this();
        assert_ok_some(
            ProcMagic::check_link(this, &xpath!("/proc/{this}/fd/0"), true),
            ProcMagic::Fd { pid: this, fd: 0 },
        );
    }

    #[test]
    fn test_check_link_port_fd42_restricted_this() {
        let this = Pid::this();
        assert_ok_some(
            ProcMagic::check_link(this, &xpath!("/proc/{this}/fd/42"), true),
            ProcMagic::Fd { pid: this, fd: 42 },
        );
    }

    #[test]
    fn test_check_link_port_fd1984_restricted_this() {
        let this = Pid::this();
        assert_ok_some(
            ProcMagic::check_link(this, &xpath!("/proc/{this}/fd/1984"), true),
            ProcMagic::Fd {
                pid: this,
                fd: 1984,
            },
        );
    }

    #[test]
    fn test_check_link_port_task_tid_fd7_restricted_that_tid() {
        let this = Pid::this();
        let that = Pid::from_raw(1);
        assert_ok_some(
            ProcMagic::check_link(this, &xpath!("/proc/{this}/task/{that}/fd/7"), true),
            ProcMagic::Fd { pid: that, fd: 7 },
        );
    }

    #[test]
    fn test_check_link_port_cwd_restricted_that_eacces() {
        let this = Pid::this();
        let that = Pid::from_raw(1);
        assert_err(
            ProcMagic::check_link(this, &xpath!("/proc/{that}/cwd"), true),
            Errno::EACCES,
        );
    }

    #[test]
    fn test_check_link_port_exe_restricted_that_eacces() {
        let this = Pid::this();
        let that = Pid::from_raw(1);
        assert_err(
            ProcMagic::check_link(this, &xpath!("/proc/{that}/exe"), true),
            Errno::EACCES,
        );
    }

    #[test]
    fn test_check_link_port_root_restricted_that_eacces() {
        let this = Pid::this();
        let that = Pid::from_raw(1);
        assert_err(
            ProcMagic::check_link(this, &xpath!("/proc/{that}/root"), true),
            Errno::EACCES,
        );
    }

    #[test]
    fn test_check_link_port_fd0_restricted_that_eacces() {
        let this = Pid::this();
        let that = Pid::from_raw(1);
        assert_err(
            ProcMagic::check_link(this, &xpath!("/proc/{that}/fd/0"), true),
            Errno::EACCES,
        );
    }

    #[test]
    fn test_check_link_port_task_mismatch_restricted_eacces() {
        let this = Pid::this();
        let that = Pid::from_raw(this.as_raw() + 123);
        assert_err(
            ProcMagic::check_link(this, &xpath!("/proc/{that}/task/{this}/fd/7"), true),
            Errno::EACCES,
        );
    }

    #[test]
    fn test_check_link_port_cwd_unrestricted_that_ok() {
        let this = Pid::this();
        let that = Pid::from_raw(1);
        assert_ok_some(
            ProcMagic::check_link(this, &xpath!("/proc/{that}/cwd"), false),
            ProcMagic::Cwd { pid: that },
        );
    }

    #[test]
    fn test_check_link_port_exe_unrestricted_that_ok() {
        let this = Pid::this();
        let that = Pid::from_raw(1);
        assert_ok_some(
            ProcMagic::check_link(this, &xpath!("/proc/{that}/exe"), false),
            ProcMagic::Exe { pid: that },
        );
    }

    #[test]
    fn test_check_link_port_root_unrestricted_that_ok() {
        let this = Pid::this();
        let that = Pid::from_raw(1);
        assert_ok_some(
            ProcMagic::check_link(this, &xpath!("/proc/{that}/root"), false),
            ProcMagic::Root { pid: that },
        );
    }

    #[test]
    fn test_check_link_port_fd0_unrestricted_that_ok() {
        let this = Pid::this();
        let that = Pid::from_raw(1);
        assert_ok_some(
            ProcMagic::check_link(this, &xpath!("/proc/{that}/fd/0"), false),
            ProcMagic::Fd { pid: that, fd: 0 },
        );
    }

    #[test]
    fn test_check_link_port_task_unrestricted_ok_tid_becomes_owner() {
        let this = Pid::this();
        let that = Pid::from_raw(this.as_raw() + 77);
        assert_ok_some(
            ProcMagic::check_link(this, &xpath!("/proc/{that}/task/{this}/fd/7"), false),
            ProcMagic::Fd { pid: this, fd: 7 },
        );
    }

    #[test]
    fn test_check_link_not_proc_prefix_dev_null() {
        let this = Pid::this();
        assert_ok_none(ProcMagic::check_link(this, &xpath!("/dev/null"), true));
    }

    #[test]
    fn test_check_link_not_proc_prefix_root() {
        let this = Pid::this();
        assert_ok_none(ProcMagic::check_link(this, &xpath!("/"), true));
    }

    #[test]
    fn test_check_link_pid_component_non_digits() {
        let this = Pid::this();
        assert_ok_none(ProcMagic::check_link(this, &xpath!("/proc/abc/cwd"), true));
    }

    #[test]
    fn test_check_link_pid_component_empty() {
        let this = Pid::this();
        assert_ok_none(ProcMagic::check_link(this, &xpath!("/proc//cwd"), true));
    }

    #[test]
    fn test_check_link_pid_component_mixed_digits() {
        let this = Pid::this();
        assert_ok_none(ProcMagic::check_link(this, &xpath!("/proc/12a/fd/1"), true));
    }

    #[test]
    fn test_check_link_task_without_tid_trailing_slash() {
        let this = Pid::this();
        assert_ok_none(ProcMagic::check_link(
            this,
            &xpath!("/proc/{this}/task/"),
            true,
        ));
    }

    #[test]
    fn test_check_link_task_double_slash_tid_missing() {
        let this = Pid::this();
        assert_ok_none(ProcMagic::check_link(
            this,
            &xpath!("/proc/{this}/task//fd/3"),
            true,
        ));
    }

    #[test]
    fn test_check_link_fd_empty_component() {
        let this = Pid::this();
        assert_ok_none(ProcMagic::check_link(
            this,
            &xpath!("/proc/{this}/fd/"),
            true,
        ));
    }

    #[test]
    fn test_check_link_fd_non_digit_component() {
        let this = Pid::this();
        assert_ok_none(ProcMagic::check_link(
            this,
            &xpath!("/proc/{this}/fd/+1"),
            true,
        ));
    }

    #[test]
    fn test_check_link_fd_letter_component() {
        let this = Pid::this();
        assert_ok_none(ProcMagic::check_link(
            this,
            &xpath!("/proc/{this}/fd/a7"),
            true,
        ));
    }

    #[test]
    fn test_check_link_fd_overflow_triggers_einval() {
        let this = Pid::this();
        let huge = format!("/proc/{}/fd/{}", this.as_raw(), "999999999999999999999999");
        let buf = XPathBuf::from(huge.as_str());
        let x: &XPath = buf.as_ref();
        assert_err(ProcMagic::check_link(this, x, true), Errno::EINVAL);
    }

    #[test]
    fn test_check_link_exact_match_required_for_cwd_extra_segment_none() {
        let this = Pid::this();
        assert_ok_none(ProcMagic::check_link(
            this,
            &xpath!("/proc/{this}/cwd/extra"),
            true,
        ));
    }

    #[test]
    fn test_check_link_exact_match_required_for_exe_extra_segment_none() {
        let this = Pid::this();
        assert_ok_none(ProcMagic::check_link(
            this,
            &xpath!("/proc/{this}/exe/also"),
            true,
        ));
    }

    #[test]
    fn test_check_link_exact_match_required_for_root_extra_segment_none() {
        let this = Pid::this();
        assert_ok_none(ProcMagic::check_link(
            this,
            &xpath!("/proc/{this}/root/too"),
            true,
        ));
    }

    // ---- check_link: a handful of FDs (unique tests, no loops) ----

    #[test]
    fn test_check_link_fd_value_0() {
        let this = Pid::this();
        assert_ok_some(
            ProcMagic::check_link(this, &xpath!("/proc/{this}/fd/0"), true),
            ProcMagic::Fd { pid: this, fd: 0 },
        );
    }

    #[test]
    fn test_check_link_fd_value_5() {
        let this = Pid::this();
        assert_ok_some(
            ProcMagic::check_link(this, &xpath!("/proc/{this}/fd/5"), true),
            ProcMagic::Fd { pid: this, fd: 5 },
        );
    }

    #[test]
    fn test_check_link_fd_value_9() {
        let this = Pid::this();
        assert_ok_some(
            ProcMagic::check_link(this, &xpath!("/proc/{this}/fd/9"), true),
            ProcMagic::Fd { pid: this, fd: 9 },
        );
    }

    #[test]
    fn test_check_link_fd_value_63() {
        let this = Pid::this();
        assert_ok_some(
            ProcMagic::check_link(this, &xpath!("/proc/{this}/fd/63"), true),
            ProcMagic::Fd { pid: this, fd: 63 },
        );
    }

    #[test]
    fn test_check_link_fd_value_100() {
        let this = Pid::this();
        assert_ok_some(
            ProcMagic::check_link(this, &xpath!("/proc/{this}/fd/100"), true),
            ProcMagic::Fd { pid: this, fd: 100 },
        );
    }

    #[test]
    fn test_check_link_fd_value_255() {
        let this = Pid::this();
        assert_ok_some(
            ProcMagic::check_link(this, &xpath!("/proc/{this}/fd/255"), true),
            ProcMagic::Fd { pid: this, fd: 255 },
        );
    }

    #[test]
    fn test_check_link_fd_task_tid_value_3() {
        let this = Pid::this();
        let tid = Pid::from_raw(this.as_raw() + 2000);
        assert_ok_some(
            ProcMagic::check_link(this, &xpath!("/proc/{this}/task/{tid}/fd/3"), true),
            ProcMagic::Fd { pid: tid, fd: 3 },
        );
    }

    #[test]
    fn test_check_link_fd_task_tid_value_11() {
        let this = Pid::this();
        let tid = Pid::from_raw(this.as_raw() + 2001);
        assert_ok_some(
            ProcMagic::check_link(this, &xpath!("/proc/{this}/task/{tid}/fd/11"), true),
            ProcMagic::Fd { pid: tid, fd: 11 },
        );
    }

    #[test]
    fn test_check_link_fd_task_tid_value_90() {
        let this = Pid::this();
        let tid = Pid::from_raw(this.as_raw() + 2002);
        assert_ok_some(
            ProcMagic::check_link(this, &xpath!("/proc/{this}/task/{tid}/fd/90"), true),
            ProcMagic::Fd { pid: tid, fd: 90 },
        );
    }

    #[test]
    fn test_check_link_ns_net_ok() {
        let this = Pid::this();
        assert_ok_some(
            ProcMagic::check_link(this, &xpath!("/proc/{this}/ns/net"), true),
            ProcMagic::Ns {
                pid: this,
                kind: NsKind::Net,
            },
        );
    }

    #[test]
    fn test_check_link_ns_mnt_ok() {
        let this = Pid::this();
        assert_ok_some(
            ProcMagic::check_link(this, &xpath!("/proc/{this}/ns/mnt"), true),
            ProcMagic::Ns {
                pid: this,
                kind: NsKind::Mnt,
            },
        );
    }

    #[test]
    fn test_check_link_ns_pid_ok() {
        let this = Pid::this();
        assert_ok_some(
            ProcMagic::check_link(this, &xpath!("/proc/{this}/ns/pid"), true),
            ProcMagic::Ns {
                pid: this,
                kind: NsKind::Pid,
            },
        );
    }

    #[test]
    fn test_check_link_ns_user_ok() {
        let this = Pid::this();
        assert_ok_some(
            ProcMagic::check_link(this, &xpath!("/proc/{this}/ns/user"), true),
            ProcMagic::Ns {
                pid: this,
                kind: NsKind::User,
            },
        );
    }

    #[test]
    fn test_check_link_ns_time_for_children_ok() {
        let this = Pid::this();
        assert_ok_some(
            ProcMagic::check_link(this, &xpath!("/proc/{this}/ns/time_for_children"), true),
            ProcMagic::Ns {
                pid: this,
                kind: NsKind::TimeForChildren,
            },
        );
    }

    #[test]
    fn test_check_link_ns_pid_for_children_ok() {
        let this = Pid::this();
        assert_ok_some(
            ProcMagic::check_link(this, &xpath!("/proc/{this}/ns/pid_for_children"), true),
            ProcMagic::Ns {
                pid: this,
                kind: NsKind::PidForChildren,
            },
        );
    }

    #[test]
    fn test_check_link_ns_unknown_none() {
        let this = Pid::this();
        assert_ok_none(ProcMagic::check_link(
            this,
            &xpath!("/proc/{this}/ns/foobar"),
            true,
        ));
    }

    #[test]
    fn test_check_link_ns_extra_slash_none() {
        let this = Pid::this();
        assert_ok_none(ProcMagic::check_link(
            this,
            &xpath!("/proc/{this}/ns/net/extra"),
            true,
        ));
    }

    // ---- link_path roundtrips ----

    #[test]
    fn test_link_path_fd_roundtrip() {
        let this = Pid::this();
        let m = ProcMagic::Fd { pid: this, fd: 7 };
        assert_eq!(m.link_path().unwrap(), xpath!("{this}/fd/7"));
    }

    #[test]
    fn test_link_path_cwd_roundtrip() {
        let this = Pid::this();
        assert_eq!(
            ProcMagic::Cwd { pid: this }.link_path().unwrap(),
            xpath!("{this}/cwd")
        );
    }

    #[test]
    fn test_link_path_root_roundtrip() {
        let this = Pid::this();
        assert_eq!(
            ProcMagic::Root { pid: this }.link_path().unwrap(),
            xpath!("{this}/root")
        );
    }

    #[test]
    fn test_link_path_exe_roundtrip() {
        let this = Pid::this();
        assert_eq!(
            ProcMagic::Exe { pid: this }.link_path().unwrap(),
            xpath!("{this}/exe")
        );
    }

    #[test]
    fn test_link_path_ns_net_roundtrip() {
        let this = Pid::this();
        let m = ProcMagic::Ns {
            pid: this,
            kind: NsKind::Net,
        };
        assert_eq!(m.link_path().unwrap(), xpath!("{this}/ns/net"));
    }

    #[test]
    fn test_link_path_ns_uts_roundtrip() {
        let this = Pid::this();
        let m = ProcMagic::Ns {
            pid: this,
            kind: NsKind::Uts,
        };
        assert_eq!(m.link_path().unwrap(), xpath!("{this}/ns/uts"));
    }

    // ---- link_fd values & error ----

    #[test]
    fn test_link_fd_for_fd_returns_value() {
        let this = Pid::this();
        assert_eq!(ProcMagic::Fd { pid: this, fd: 3 }.link_fd(), Ok(3));
    }

    #[test]
    fn test_link_fd_for_cwd_returns_at_fdcwd() {
        let this = Pid::this();
        assert_eq!(ProcMagic::Cwd { pid: this }.link_fd(), Ok(libc::AT_FDCWD));
    }

    #[test]
    fn test_link_fd_for_root_returns_minus_one() {
        let this = Pid::this();
        assert_eq!(ProcMagic::Root { pid: this }.link_fd(), Ok(-1));
    }

    #[test]
    fn test_link_fd_for_ns_is_einval() {
        let this = Pid::this();
        assert_eq!(
            ProcMagic::Ns {
                pid: this,
                kind: NsKind::Net
            }
            .link_fd(),
            Err(Errno::EINVAL)
        );
    }

    #[test]
    fn test_want_dir_true_for_cwd_and_root() {
        let this = Pid::this();
        assert!(ProcMagic::Cwd { pid: this }.want_dir());
        assert!(ProcMagic::Root { pid: this }.want_dir());
    }

    #[test]
    fn test_want_dir_false_for_fd_exe_ns() {
        let this = Pid::this();
        assert!(!ProcMagic::Fd { pid: this, fd: 5 }.want_dir());
        assert!(!ProcMagic::Exe { pid: this }.want_dir());
        assert!(!ProcMagic::Ns {
            pid: this,
            kind: NsKind::Net
        }
        .want_dir());
    }

    // ---- base() ----

    #[test]
    fn test_base_for_fd_returns_number() {
        let this = Pid::this();
        assert_eq!(
            ProcMagic::Fd { pid: this, fd: 42 }.base().unwrap(),
            Some(XPathBuf::from("42"))
        );
    }

    #[test]
    fn test_base_for_ns_returns_kind() {
        let this = Pid::this();
        assert_eq!(
            ProcMagic::Ns {
                pid: this,
                kind: NsKind::Mnt
            }
            .base()
            .unwrap(),
            Some(XPathBuf::from("mnt"))
        );
    }

    #[test]
    fn test_base_for_cwd_root_exe_is_none() {
        let this = Pid::this();
        assert_eq!(ProcMagic::Cwd { pid: this }.base().unwrap(), None);
        assert_eq!(ProcMagic::Root { pid: this }.base().unwrap(), None);
        assert_eq!(ProcMagic::Exe { pid: this }.base().unwrap(), None);
    }

    #[test]
    fn test_pid_for_fd() {
        let this = Pid::from_raw(4242);
        assert_eq!(ProcMagic::Fd { pid: this, fd: 1 }.pid(), this);
    }

    #[test]
    fn test_pid_for_cwd_root_exe() {
        let p = Pid::from_raw(2025);
        assert_eq!(ProcMagic::Cwd { pid: p }.pid(), p);
        assert_eq!(ProcMagic::Root { pid: p }.pid(), p);
        assert_eq!(ProcMagic::Exe { pid: p }.pid(), p);
    }

    #[test]
    fn test_pid_for_ns() {
        let p = Pid::from_raw(77);
        assert_eq!(
            ProcMagic::Ns {
                pid: p,
                kind: NsKind::User
            }
            .pid(),
            p
        );
    }

    #[test]
    fn test_try_from_bytes_known_values_subset() {
        assert_eq!(NsKind::try_from(b"net".as_slice()), Ok(NsKind::Net));
        assert_eq!(NsKind::try_from(b"mnt".as_slice()), Ok(NsKind::Mnt));
        assert_eq!(NsKind::try_from(b"pid".as_slice()), Ok(NsKind::Pid));
        assert_eq!(NsKind::try_from(b"user".as_slice()), Ok(NsKind::User));
        assert_eq!(NsKind::try_from(b"uts".as_slice()), Ok(NsKind::Uts));
        assert_eq!(
            NsKind::try_from(b"time_for_children".as_slice()),
            Ok(NsKind::TimeForChildren)
        );
    }

    #[test]
    fn test_try_from_bytes_unknown_is_eopnotsupp() {
        assert_eq!(
            NsKind::try_from(b"other".as_slice()),
            Err(Errno::EOPNOTSUPP)
        );
    }

    #[test]
    fn test_from_ns_kind_into_xpathbuf_subset() {
        let xb_net: XPathBuf = XPathBuf::try_from(NsKind::Net).unwrap();
        let xb_ipc: XPathBuf = XPathBuf::try_from(NsKind::Ipc).unwrap();
        let xb_cg: XPathBuf = XPathBuf::try_from(NsKind::Cgroup).unwrap();
        assert_eq!(xb_net, XPathBuf::from("net"));
        assert_eq!(xb_ipc, XPathBuf::from("ipc"));
        assert_eq!(xb_cg, XPathBuf::from("cgroup"));
    }

    #[test]
    fn test_bytes_to_pid_zero_and_positive() {
        assert_eq!(bytes_to_pid(b"0"), Ok(Pid::from_raw(0)));
        assert_eq!(bytes_to_pid(b"12345"), Ok(Pid::from_raw(12345)));
    }

    #[test]
    fn test_bytes_to_pid_negative_is_parsed() {
        // bytes_to_pid allows negatives; check_link itself disallows via digit check.
        assert_eq!(bytes_to_pid(b"-7"), Ok(Pid::from_raw(-7)));
    }

    #[test]
    fn test_bytes_to_pid_empty_is_einval() {
        assert_eq!(bytes_to_pid(b""), Err(Errno::EINVAL));
    }

    #[test]
    fn test_bytes_to_pid_overflow_is_einval() {
        assert_eq!(
            bytes_to_pid(b"9999999999999999999999999"),
            Err(Errno::EINVAL)
        );
    }

    #[test]
    fn test_bytes_to_fd_basic_values() {
        assert_eq!(bytes_to_fd(b"0"), Ok(0));
        assert_eq!(bytes_to_fd(b"42"), Ok(42));
        assert_eq!(bytes_to_fd(b"1984"), Ok(1984));
    }

    #[test]
    fn test_bytes_to_fd_invalid_inputs() {
        assert_eq!(bytes_to_fd(b""), Err(Errno::EINVAL));
        assert_eq!(bytes_to_fd(b"1a"), Err(Errno::EINVAL));
        assert_eq!(bytes_to_fd(b"999999999999999999999999"), Err(Errno::EINVAL));
    }
}
