//
// Syd: rock-solid application kernel
// src/path.rs: Path handling for UNIX
//
// Copyright (c) 2024, 2025 Ali Polatel <alip@chesswob.org>
// Based in part upon David A. Wheeler's SafeName LSM patches which is:
//   Copyright (C) 2016 David A. Wheeler
//   SPDX-License-Identifier: GPL-2.0
//
// SPDX-License-Identifier: GPL-3.0

use std::{
    borrow::{Borrow, Cow},
    cmp::Ordering,
    collections::VecDeque,
    ffi::{CStr, OsStr, OsString},
    ops::{Deref, RangeBounds},
    os::{
        fd::RawFd,
        unix::ffi::{OsStrExt, OsStringExt},
    },
    path::{Component, Path, PathBuf},
    sync::{Arc, LazyLock},
};

use btoi::btoi;
use itoa::Integer;
use memchr::{
    arch::all::{is_equal, is_prefix, is_suffix},
    memchr, memmem, memrchr,
};
use nix::{
    errno::Errno,
    fcntl::{OFlag, AT_FDCWD},
    libc::pid_t,
    unistd::Pid,
    NixPath,
};
use tinyvec::TinyVec;

use crate::{
    compat::{openat2, OpenHow, ResolveFlag},
    config::MAGIC_PREFIX,
    fs::tgkill,
    log::log_untrusted_buf,
    lookup::FileType,
    retry::retry_on_eintr,
};

/// Generate a formatted `XPathBuf`.
#[macro_export]
macro_rules! xpath {
    ($($arg:tt)*) => {
        XPathBuf::from(format!($($arg)*))
    };
}

/// A safe constant to use as PATH_MAX without relying on libc.
pub const PATH_MAX: usize = 4096;

/// A safe default size to use for paths.
pub const PATH_MIN: usize = 64;

/// Default capacity of the internal `TinyVec` of `XPathBuf`.
pub const PATH_CAP: usize = 400;

// This pointer is confined by seccomp for use with openat(2) for getdir_long().
static DOTDOT: LazyLock<u64> = LazyLock::new(|| c"..".as_ptr() as *const libc::c_char as u64);

#[inline(always)]
pub(crate) fn dotdot_with_nul() -> u64 {
    *DOTDOT
}

/// `PathBuf` for UNIX.
// SAFETY: k1 == k2 -> hash(k1) == hash(k2) always holds for our PartialEq impl.
#[allow(clippy::derived_hash_with_manual_eq)]
#[derive(Clone, Hash, Ord, PartialOrd)]
pub struct XPathBuf(pub(crate) TinyVec<[u8; PATH_CAP]>);

impl Default for XPathBuf {
    fn default() -> Self {
        Self(TinyVec::with_capacity(PATH_CAP))
    }
}

impl Eq for XPathBuf {}

impl PartialEq for XPathBuf {
    fn eq(&self, other: &Self) -> bool {
        is_equal(&self.0, &other.0)
    }
}

impl PartialEq<XPath> for XPathBuf {
    fn eq(&self, other: &XPath) -> bool {
        is_equal(self.as_bytes(), other.as_bytes())
    }
}

impl PartialEq<XPathBuf> for XPath {
    fn eq(&self, other: &XPathBuf) -> bool {
        is_equal(self.as_bytes(), other.as_bytes())
    }
}

impl Deref for XPathBuf {
    type Target = XPath;

    fn deref(&self) -> &XPath {
        XPath::from_bytes(&self.0)
    }
}

impl Borrow<XPath> for XPathBuf {
    fn borrow(&self) -> &XPath {
        self.deref()
    }
}

impl Borrow<XPath> for Arc<XPathBuf> {
    fn borrow(&self) -> &XPath {
        self.deref()
    }
}

/// A borrowed slice of an XPathBuf.
// SAFETY: k1 == k2 ⇒ hash(k1) == hash(k2) always holds for our PartialEq impl.
#[allow(clippy::derived_hash_with_manual_eq)]
#[repr(transparent)]
#[derive(Hash, Ord, PartialOrd)]
pub struct XPath(OsStr);

impl Eq for XPath {}

impl PartialEq for XPath {
    fn eq(&self, other: &Self) -> bool {
        is_equal(self.0.as_bytes(), other.0.as_bytes())
    }
}

impl ToOwned for XPath {
    type Owned = XPathBuf;

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

impl AsRef<XPath> for XPathBuf {
    fn as_ref(&self) -> &XPath {
        self.as_xpath()
    }
}

impl AsRef<Path> for XPathBuf {
    fn as_ref(&self) -> &Path {
        self.as_path()
    }
}

impl AsRef<OsStr> for XPathBuf {
    fn as_ref(&self) -> &OsStr {
        self.as_os_str()
    }
}

impl From<&XPath> for XPathBuf {
    fn from(path: &XPath) -> Self {
        path.as_bytes().into()
    }
}

impl From<PathBuf> for XPathBuf {
    fn from(pbuf: PathBuf) -> Self {
        pbuf.into_os_string().into()
    }
}

impl From<&OsStr> for XPathBuf {
    fn from(ostr: &OsStr) -> Self {
        ostr.as_bytes().into()
    }
}

impl From<OsString> for XPathBuf {
    fn from(os: OsString) -> Self {
        if os.as_bytes().len() <= PATH_CAP {
            os.as_bytes().into()
        } else {
            Self(TinyVec::Heap(os.into_vec()))
        }
    }
}

impl From<String> for XPathBuf {
    fn from(s: String) -> Self {
        if s.len() <= PATH_CAP {
            s.as_bytes().into()
        } else {
            Self(TinyVec::Heap(s.into_bytes()))
        }
    }
}

impl From<&str> for XPathBuf {
    fn from(s: &str) -> Self {
        let mut tv = TinyVec::new();
        tv.extend_from_slice(s.as_bytes());
        Self(tv)
    }
}

impl From<Cow<'_, str>> for XPathBuf {
    fn from(cow: Cow<'_, str>) -> Self {
        if cow.len() <= PATH_CAP {
            return cow.as_bytes().into();
        }

        match cow {
            Cow::Borrowed(s) => Self::from(s),
            Cow::Owned(s) => Self::from(s),
        }
    }
}

impl From<&[u8]> for XPathBuf {
    fn from(bytes: &[u8]) -> Self {
        let mut tv = TinyVec::new();
        tv.extend_from_slice(bytes);
        Self(tv)
    }
}

impl From<Vec<u8>> for XPathBuf {
    fn from(vec: Vec<u8>) -> Self {
        if vec.len() <= PATH_CAP {
            vec.as_slice().into()
        } else {
            Self(TinyVec::Heap(vec))
        }
    }
}

impl From<VecDeque<u8>> for XPathBuf {
    fn from(mut vec: VecDeque<u8>) -> Self {
        if vec.len() <= PATH_CAP {
            vec.make_contiguous();
            vec.as_slices().0.into()
        } else {
            Self(TinyVec::Heap(Vec::from(vec)))
        }
    }
}

impl From<pid_t> for XPathBuf {
    fn from(pid: pid_t) -> Self {
        let mut buf = itoa::Buffer::new();
        buf.format(pid).into()
    }
}

impl std::ops::Deref for XPath {
    type Target = Path;

    fn deref(&self) -> &Self::Target {
        self.as_path()
    }
}

impl AsRef<Path> for XPath {
    fn as_ref(&self) -> &Path {
        self.as_path()
    }
}

impl AsRef<OsStr> for XPath {
    fn as_ref(&self) -> &OsStr {
        self.as_os_str()
    }
}

impl std::fmt::Display for XPathBuf {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        // SAFETY: Mask control characters in path.
        write!(f, "{}", mask_path(self.as_path()))
    }
}

impl std::fmt::Debug for XPathBuf {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        // SAFETY: Mask control characters in path.
        write!(f, "{}", mask_path(self.as_path()))
    }
}

impl serde::Serialize for XPathBuf {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        // SAFETY: Display masks control characters.
        serializer.serialize_str(&format!("{self}"))
    }
}

impl NixPath for XPathBuf {
    fn is_empty(&self) -> bool {
        self.0.is_empty()
    }

    fn len(&self) -> usize {
        self.0.len()
    }

    fn with_nix_path<T, F>(&self, f: F) -> Result<T, Errno>
    where
        F: FnOnce(&CStr) -> T,
    {
        self.as_os_str().with_nix_path(f)
    }
}

impl std::fmt::Display for XPath {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        // SAFETY: Mask control characters in path.
        write!(f, "{}", mask_path(self.as_path()))
    }
}

impl std::fmt::Debug for XPath {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        // SAFETY: Mask control characters in path.
        write!(f, "{}", mask_path(self.as_path()))
    }
}

impl serde::Serialize for XPath {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        // SAFETY: Display masks control characters.
        serializer.serialize_str(&format!("{self}"))
    }
}

impl NixPath for XPath {
    fn is_empty(&self) -> bool {
        self.0.is_empty()
    }

    fn len(&self) -> usize {
        self.0.len()
    }

    fn with_nix_path<T, F>(&self, f: F) -> Result<T, Errno>
    where
        F: FnOnce(&CStr) -> T,
    {
        self.as_os_str().with_nix_path(f)
    }
}

impl XPath {
    /// Detects unsafe paths.
    ///
    /// List of restrictions:
    /// 1. Block devices can not be listed with readdir() regardless of path.
    /// 2. readdir(/proc) returns current pid as the only process id.
    /// 3. /proc/$pid where $pid == Syd -> ENOENT.
    /// 4. File name must not contain forbidden characters if `safe_name` is true.
    ///
    /// # SAFETY
    /// 1. `self` must be an absolute pathname.
    /// 2. `self` must be canonicalized and normalized.
    ///
    /// Note, returning error here denies access,
    /// regardless of the state of sandboxing.
    pub fn check(
        &self,
        pid: Pid,
        file_type: Option<&FileType>,
        dir_entry: Option<&XPath>,
        safe_name: bool,
        restrict_mkbdev: bool,
    ) -> Result<(), Errno> {
        //
        // RESTRICTION 1: Prevent listing block devices and files of unknown type.
        //
        // SAFETY: Prevent listing block devices and files of unknown type.
        // Block device restriction may be relaxed with trace/allow_unsafe_mkbdev:1.
        if file_type == Some(&FileType::Unk)
            || (restrict_mkbdev && file_type == Some(&FileType::Blk))
        {
            return Err(Errno::ENOENT);
        }
        // END OF RESTRICTION 1

        //
        // RESTRICTION 2: Restrict file names to allowed characters as necessary.
        //
        // SAFETY: Prevent accessing file names which may be misinterpreted by shells.
        // Note, we skip checking procfs so we don't prevent pipe/socket access
        // unintentionally. Similarly we skip checking memory fd names which do not
        // represent actual file paths.
        // As of version 3.48.0, we return EILSEQ rather than EINVAL which matches ZFS.
        let is_mfd = matches!(file_type, Some(FileType::Mfd));
        let is_proc_dir = self.starts_with(b"/proc");
        if safe_name && !is_mfd && !is_proc_dir {
            self.check_name()?;
        }
        // END OF RESTRICTION 2

        // Remaining restrictions apply to procfs only.
        let (is_proc, proc_pid) = if is_proc_dir {
            const LEN: usize = b"/proc".len();
            let mut proc_pid = None;
            let is_proc = self.len() == LEN;

            if is_proc {
                // If this is `/proc' directory entries may refer to PIDs.
                if let Some(p) = dir_entry {
                    proc_pid = btoi::<libc::pid_t>(p.as_bytes()).ok();
                }
            }

            if proc_pid.is_none()
                && self
                    .get(LEN + 1)
                    .map(|c| c.is_ascii_digit())
                    .unwrap_or(false)
            {
                let path = self.as_bytes();
                let path = &path[LEN + 1..];
                let pidx = memchr(b'/', path).unwrap_or(path.len());
                proc_pid = btoi::<libc::pid_t>(&path[..pidx]).ok();
            }

            (is_proc, proc_pid)
        } else {
            return Ok(());
        };
        let proc_pid = if let Some(pid) = proc_pid {
            Pid::from_raw(pid)
        } else {
            return Ok(());
        };
        if proc_pid == pid {
            return Ok(());
        }

        //
        // RESTRICTION 2: Protect readdir(/proc).
        //
        // SAFETY: Prevent /proc process tree traversal.
        if is_proc {
            return Err(Errno::ENOENT);
        }
        // END OF RESTRICTION 2

        //
        // RESTRICTION 3: Protect Syd procfs.
        //
        // SAFETY: Protect Syd /proc directory!
        //
        // Step 1: Protect Syd thread group.
        let syd_pid = Pid::this();
        if proc_pid == syd_pid {
            return Err(Errno::ENOENT);
        }
        //
        // Step 2: Protect all Syd threads.
        if tgkill(syd_pid, proc_pid, 0).is_ok() {
            return Err(Errno::ENOENT);
        }
        // END OF RESTRICTION 3

        // TODO: Add more restrictions as needed.
        Ok(())
    }

    /// Validates a filename based on David A. Wheeler's Safename Linux
    /// Security Module (LSM) rules.
    ///
    /// This function checks if a given filename (not the entire path)
    /// adheres to specific security policies inspired by Wheeler's
    /// Safename LSM. These policies are designed to prevent the
    /// creation of filenames that could be used for malicious purposes,
    /// such as exploiting poorly written scripts or programs.
    ///
    /// The validation rules are:
    ///
    /// 1. **Non-Empty Filename**: The filename must not be empty.
    ///
    /// 2. **Valid UTF-8 Encoding**: The filename must be valid UTF-8.
    ///
    /// 3. **Permitted Characters**:
    ///    - **Initial Byte**: Must be an allowed character, but cannot be:
    ///        - Space `' '` (0x20)
    ///        - Hyphen `'-'` (0x2D)
    ///        - Tilde `'~'` (0x7E)
    ///    - **Middle Bytes**: Each must be an allowed character (if any).
    ///    - **Final Byte**: Must be an allowed character, but cannot be:
    ///        - Space `' '` (0x20)
    ///
    /// 4. **Allowed Character Set**:
    ///    - ASCII printable characters from space `' '` (0x20) to tilde `'~'` (0x7E), inclusive.
    ///    - Extended ASCII characters from 0x80 to 0xFE, inclusive.
    ///    - **Excludes** control characters (0x00-0x1F), delete (0x7F), and 0xFF.
    ///
    /// # Returns
    ///
    /// * `Ok(())` if the filename is valid and safe.
    /// * `Err(Errno::EILSEQ)` if the filename is invalid or unsafe.
    ///
    /// # Errors
    ///
    /// Returns `Err(Errno::EILSEQ)` if any of the validation rules are not met.
    ///
    /// # Security
    ///
    /// Enforcing these rules helps prevent security vulnerabilities
    /// arising from unexpected or malicious filenames, such as command
    /// injection, denial of service, or arbitrary file manipulation.
    #[expect(clippy::arithmetic_side_effects)]
    pub fn check_name(&self) -> Result<(), Errno> {
        let (_, name) = self.split();
        let name = name.as_bytes();
        let len = name.len();

        if len == 0 {
            return Err(Errno::EILSEQ);
        }

        // Check if the filename is valid UTF-8.
        let name_utf8 = std::str::from_utf8(name).or(Err(Errno::EILSEQ))?;

        // Check if first and last character is not whitespace.
        // This includes UTF-8 whitespace.
        if name_utf8
            .chars()
            .nth(0)
            .map(|c| c.is_whitespace())
            .unwrap_or(false)
        {
            return Err(Errno::EILSEQ);
        }
        if name_utf8
            .chars()
            .last()
            .map(|c| c.is_whitespace())
            .unwrap_or(false)
        {
            return Err(Errno::EILSEQ);
        }

        let first_byte = name[0];
        let last_byte = name[len - 1];

        // Check the first byte.
        if !is_permitted_initial(first_byte) {
            return Err(Errno::EILSEQ);
        }

        // Check the middle bytes (if any).
        match len {
            2 => {
                // Only one middle byte to check.
                let middle_byte = name[1];
                if !is_permitted_middle(middle_byte) {
                    return Err(Errno::EILSEQ);
                }
            }
            n if n > 2 => {
                for &b in &name[1..len - 1] {
                    if !is_permitted_middle(b) {
                        return Err(Errno::EILSEQ);
                    }
                }
            }
            _ => {}
        }

        // Check the last byte.
        if !is_permitted_final(last_byte) {
            return Err(Errno::EILSEQ);
        }

        Ok(())
    }

    /// Convert /proc/${pid} to /proc/self as necessary.
    ///
    /// This must never used in syscalls only in access check.
    pub fn replace_proc_self<'a>(&'a self, pid: Pid) -> Cow<'a, Self> {
        let p = if let Some(p) = self.split_prefix(b"/proc") {
            p
        } else {
            return Cow::Borrowed(self);
        };

        let mut buf = itoa::Buffer::new();
        let pid = buf.format(pid.as_raw());
        let p = if let Some(p) = p.split_prefix(pid.as_bytes()) {
            p
        } else {
            return Cow::Borrowed(self);
        };

        let mut pdir = XPathBuf::from("/proc/self");
        // SAFETY: We do not use XPathBuf::push here,
        // and allow slash in path. This is safe against
        // path traversals because the path is guaranteed
        // to be a canonicalized path.
        pdir.append_byte(b'/');
        pdir.append_bytes(p.as_bytes());

        Cow::Owned(pdir)
    }

    /// Returns a path that, when joined onto `base`, yields `self`.
    ///
    /// Expects normalized, canonical path.
    #[expect(clippy::arithmetic_side_effects)]
    pub fn split_prefix(&self, base: &[u8]) -> Option<&Self> {
        let mut len = base.len();
        if len == 0 {
            return None;
        } else if base == b"/" {
            return Some(self);
        }

        let base = if base[len - 1] == b'/' {
            len -= 1;
            &base[..len - 1]
        } else {
            base
        };

        if !self.starts_with(base) {
            return None;
        }

        let raw = self.as_bytes();
        let len_raw = raw.len();
        if len == len_raw {
            Some(XPath::from_bytes(b""))
        } else if len_raw < len + 1 || raw[len] != b'/' {
            None
        } else {
            Some(XPath::from_bytes(&raw[len + 1..]))
        }
    }

    /// Splits a given path into the parent path and the file name.
    ///
    /// - The function efficiently finds the last `/` in the path and splits at that point.
    /// - Trailing slashes are included in the filename to indicate directory paths.
    /// - For the root path `/`, both parent and filename are the original path reference.
    #[expect(clippy::arithmetic_side_effects)]
    pub fn split(&self) -> (&Self, &Self) {
        // Special cases for the empty and root paths.
        let bytes = match self.get(0) {
            None => return (XPath::from_bytes(b""), XPath::from_bytes(b"")),
            Some(b'/') if self.0.len() == 1 => {
                return (
                    XPath::from_bytes(&self.as_bytes()[..1]),
                    XPath::from_bytes(&self.as_bytes()[..1]),
                )
            }
            _ => self.as_bytes(),
        };

        // Determine if the path ends with a trailing slash.
        let has_trailing_slash = bytes[bytes.len() - 1] == b'/';
        let effective_length = if has_trailing_slash && bytes.len() > 1 {
            bytes.len() - 1
        } else {
            bytes.len()
        };
        let last_slash_index = memrchr(b'/', &bytes[..effective_length]);

        if let Some(idx) = last_slash_index {
            let parent_path = if idx == 0 {
                // The slash is at the beginning, so the parent is root.
                XPath::from_bytes(b"/")
            } else {
                // Take everything up to the last non-trailing slash.
                XPath::from_bytes(&bytes[..idx])
            };

            let filename_start = idx + 1;
            let filename_end = if has_trailing_slash {
                bytes.len()
            } else {
                effective_length
            };
            let filename_path = XPath::from_bytes(&bytes[filename_start..filename_end]);

            return (parent_path, filename_path);
        }

        // If no slash is found, the whole thing is the filename!
        (XPath::from_bytes(b""), self)
    }

    /// Returns a reference to the file extension.
    pub fn extension(&self) -> Option<&Self> {
        let dot = memrchr(b'.', self.as_bytes())?;
        // dot==Some means len>=1.
        #[expect(clippy::arithmetic_side_effects)]
        if dot < self.0.len() - 1 {
            Some(Self::from_bytes(&self.as_bytes()[dot + 1..]))
        } else {
            None
        }
    }

    /// Returns a reference to the parent path.
    pub fn parent(&self) -> &Self {
        Self::from_bytes(&self.as_bytes()[..self.parent_len()])
    }

    /// Determines the length of the parent path.
    #[expect(clippy::arithmetic_side_effects)]
    pub fn parent_len(&self) -> usize {
        // Special cases for the empty and root paths.
        let bytes = match self.get(0) {
            None => return 0,
            Some(b'/') if self.len() == 1 => return 1,
            _ => self.as_bytes(),
        };

        // Determine if the path ends with a trailing slash.
        let has_trailing_slash = bytes[bytes.len() - 1] == b'/';
        let effective_length = if has_trailing_slash && bytes.len() > 1 {
            bytes.len() - 1
        } else {
            bytes.len()
        };
        let last_slash_index = memrchr(b'/', &bytes[..effective_length]);

        if let Some(idx) = last_slash_index {
            return if idx == 0 {
                // The slash is at the beginning, so the parent is root.
                1
            } else {
                // Take everything up to the last non-trailing slash.
                idx
            };
        }

        // If no slash is found, the whole thing is the filename!
        0
    }

    /// Return the depth of the path.
    ///
    /// The depth of a path is equal to the number of directory separators in it.
    pub fn depth(&self) -> usize {
        memchr::arch::all::memchr::One::new(b'/').count(self.as_bytes())
    }

    /// Check if path is a descendant of the given `root` path (RESOLVE_BENEATH compatible).
    /// Both paths must be canonicalized.
    pub fn descendant_of(&self, root: &[u8]) -> bool {
        if is_equal(root, b"/") {
            // Every absolute path is a descendant of "/".
            return true;
        } else if !self.starts_with(root) {
            // `self` does not begin with `root`.
            return false;
        }

        let slen = self.len();
        let rlen = root.len();

        match slen.cmp(&rlen) {
            Ordering::Less => false,
            Ordering::Equal => true,
            Ordering::Greater => self.get(rlen) == Some(b'/'),
        }
    }

    /// Returns a relative path by stripping the root component.
    ///
    /// # Safety
    ///
    /// This function does NOT assume `self` is normalized and performs
    /// a small amount of normalization. Therefore this function is safe
    /// to use with non-normalized paths.
    pub fn strip_root(&self) -> XPathBuf {
        let src = self.as_path();
        let mut dst = XPathBuf::new();

        for comp in src.components() {
            if matches!(
                comp,
                Component::RootDir | Component::Prefix(_) | Component::CurDir
            ) {
                continue;
            }
            if !dst.is_empty() {
                dst.append_byte(b'/');
            }
            dst.append_bytes(comp.as_os_str().as_bytes());
        }

        dst
    }

    /// Returns a path that, when joined onto `base`, yields `self`.
    ///
    /// # Safety
    ///
    /// Assumes `self` is normalized.
    ///
    /// # Errors
    ///
    /// If `base` is not a prefix of self (i.e., `starts_with` returns
    /// `false`), returns `None`.
    pub fn strip_prefix(&self, base: &[u8]) -> Option<&Self> {
        if !self.starts_with(base) {
            return None;
        }

        // Determine the remainder after the base.
        let remainder = &self.as_bytes()[base.len()..];

        // Check if there is anything left after the base.
        if remainder.is_empty() {
            // If the remainder is empty, return an empty path.
            Some(Self::from_bytes(b""))
        } else if remainder[0] == b'/' {
            // Return the slice after the '/', ensuring no leading '/' in the result
            // This is safe due to the assumption of normalized paths.
            Some(Self::from_bytes(&remainder[1..]))
        } else {
            // If the path doesn't start with '/', it means base is not a directory prefix.
            None
        }
    }

    /// Checks if the path ends with a dot component.
    ///
    /// This function iterates through the bytes of the path from end to
    /// start, and determines whether the last component before any
    /// slashes is a dot.
    #[expect(clippy::arithmetic_side_effects)]
    pub fn ends_with_dot(&self) -> bool {
        let bytes = self.as_bytes();

        // Start from the end of the string and move backwards.
        let mut index = bytes.len();
        if index == 0 {
            return false;
        }

        // Skip trailing slashes.
        while index > 0 && bytes[index - 1] == b'/' {
            index -= 1;
        }

        // If the path is empty after removing trailing slashes,
        // it does not end with a dot.
        if index == 0 {
            return false;
        }

        // Check for '.' or '..'
        if bytes[index - 1] == b'.' {
            if index == 1 || bytes[index - 2] == b'/' {
                return true; // Matches '.' or '*/.'
            } else if index > 1
                && bytes[index - 2] == b'.'
                && (index == 2 || bytes[index - 3] == b'/')
            {
                return true; // Matches '..' or '*/..'
            }
        }

        false
    }

    /// Returns true if the path ends with a slash.
    pub fn ends_with_slash(&self) -> bool {
        self.last() == Some(b'/') && !self.is_rootfs()
    }

    /// Check if path has a parent dir component, ie `..`.
    pub fn has_parent_dot(&self) -> bool {
        let bytes = self.as_bytes();

        #[allow(clippy::arithmetic_side_effects)]
        for index in memmem::Finder::new(b"..").find_iter(bytes) {
            let is_dotdot = if index == 0 {
                true
            } else {
                bytes[index - 1] == b'/'
            } && (index + 2 == bytes.len() || bytes[index + 2] == b'/');

            if is_dotdot {
                return true;
            }
        }

        false
    }

    /// Check if path starts with the `MAGIC_PREFIX`.
    pub fn is_magic(&self) -> bool {
        self.starts_with(MAGIC_PREFIX)
    }

    /// Check if path is literally the root path, ie `/`.
    pub fn is_root(&self) -> bool {
        self.is_equal(b"/")
    }

    /// Check if path is the root path, ie `/`.
    ///
    /// Path may consist of multiple slashes and single dots.
    pub fn is_rootfs(&self) -> bool {
        self.as_bytes().iter().all(|b| matches!(*b, b'/' | b'.'))
    }

    /// Check if path points to procfs root dir, ie. `/proc`.
    ///
    /// `self` must be canonicalized.
    pub fn is_procfs(&self) -> bool {
        const PROC_LEN: usize = b"/proc".len();
        const PROC_DIR_LEN: usize = b"/proc/".len();

        match self.len() {
            PROC_LEN if self.is_equal(b"/proc") => true,
            PROC_DIR_LEN if self.is_equal(b"/proc/") => true,
            _ => false,
        }
    }

    /// Check if path points to devfs, ie. starts with `/dev`.
    /// The literal path `/dev` returns false.
    ///
    /// `self` must be canonicalized.
    pub fn is_dev(&self) -> bool {
        self.starts_with(b"/dev/")
    }

    /// Check if path points to procfs, ie. starts with `/proc`.
    /// The literal path `/proc` returns false.
    ///
    /// `self` must be canonicalized.
    pub fn is_proc(&self) -> bool {
        self.starts_with(b"/proc/")
    }

    /// Check if path points to a static path.
    /// See proc_init in config.rs
    pub fn is_static(&self) -> bool {
        self.is_rootfs() || self.is_procfs() || self.is_equal(b"/dev/null")
    }

    /// Check if path points to per-process procfs directory, ie. starts with `/proc/$pid`.
    /// `/proc/$pid` is also accepted among with all descendants of it.
    pub fn is_proc_pid(&self) -> bool {
        if !self.is_proc() {
            return false;
        }
        self.get("/proc/".len())
            .map(|b| b.is_ascii_digit())
            .unwrap_or(false)
    }

    /// Check if path points to `/proc/version`.
    pub fn is_proc_version(&self) -> bool {
        self.is_equal(b"/proc/version")
    }

    /// Check if path points to `/proc/sys/kernel/osrelease`.
    pub fn is_proc_osrelease(&self) -> bool {
        self.is_equal(b"/proc/sys/kernel/osrelease")
    }

    /// Check if path points to machine-id(5), /etc/hostid or /var/adm/hostid.
    pub fn is_machine_id(&self) -> bool {
        const MACHINE_ID: &[&[u8]] = &[
            b"/etc/machine-id",
            b"/etc/hostid",
            b"/var/adm/hostid",
            b"/sys/class/dmi/id/product_uuid",
            b"/sys/devices/virtual/dmi/id/product_uuid",
        ];
        MACHINE_ID.iter().any(|f| self.is_equal(f))
    }

    /// Check if path points to `/proc/self/status` or friends.
    pub fn is_proc_status(&self) -> bool {
        self.starts_with(b"/proc") && self.ends_with(b"/status")
    }

    /// Check if path points to the `/proc/self` link.
    /// If `thread` is true, checks for `/proc/thread-self`.
    pub fn is_proc_self(&self, thread: bool) -> bool {
        if thread {
            is_equal(self.as_bytes(), b"/proc/thread-self")
        } else {
            is_equal(self.as_bytes(), b"/proc/self")
        }
    }

    /// Check if path exists.
    #[expect(clippy::disallowed_methods)]
    pub fn exists(&self, follow: bool) -> bool {
        let flags = if self.is_empty() {
            return false;
        } else if !follow {
            OFlag::O_NOFOLLOW
        } else {
            OFlag::empty()
        };

        let mut how = OpenHow::new().flags(flags | OFlag::O_PATH | OFlag::O_CLOEXEC);
        if !follow {
            how =
                how.resolve(ResolveFlag::RESOLVE_NO_MAGICLINKS | ResolveFlag::RESOLVE_NO_SYMLINKS);
        }

        retry_on_eintr(|| openat2(AT_FDCWD, self, how))
            .map(drop)
            .is_ok()
    }

    /// Check if path is a symlink.
    pub fn is_symlink(&self) -> bool {
        self.as_path().is_symlink()
    }

    /// Check if path is a dir.
    pub fn is_dir(&self) -> bool {
        self.as_path().is_dir()
    }

    /// Check if path is a file.
    pub fn is_file(&self) -> bool {
        self.as_path().is_file()
    }

    /// Check if path is absolute.
    pub fn is_absolute(&self) -> bool {
        self.first() == Some(b'/')
    }

    /// Check if path is relative.
    ///
    /// Empty path is considered relative.
    pub fn is_relative(&self) -> bool {
        !self.is_absolute()
    }

    /// Checks if the path consists only of "." components.
    pub fn is_dot(&self) -> bool {
        // Empty or absolute paths are not "."-only.
        if self.is_empty() || self.is_absolute() {
            return false;
        }

        // Every non-empty component must be exactly ".".
        let mut saw_component = false;
        for comp in self
            .as_bytes()
            .split(|&c| c == b'/')
            .filter(|s| !s.is_empty())
        {
            saw_component = true;
            if comp != b"." {
                return false; // rejects "..", ".hidden", "a", etc.
            }
        }
        saw_component
    }

    /// Determine whether path is equal to the given string.
    pub fn is_equal(&self, s: &[u8]) -> bool {
        is_equal(self.as_bytes(), s)
    }

    /// Determine whether base is a prefix of path.
    pub fn starts_with(&self, base: &[u8]) -> bool {
        is_prefix(self.as_bytes(), base)
    }

    /// Determine whether base is a suffix of path.
    pub fn ends_with(&self, base: &[u8]) -> bool {
        is_suffix(self.as_bytes(), base)
    }

    /// Determine whether path contains the given substring.
    pub fn contains(&self, sub: &[u8]) -> bool {
        memmem::find(self.as_bytes(), sub).is_some()
    }

    /// Determine whether path contains the given character.
    pub fn contains_char(&self, c: u8) -> bool {
        memchr(c, self.as_bytes()).is_some()
    }

    /// Return the index of the given substring in path.
    pub fn find(&self, sub: &[u8]) -> Option<usize> {
        memmem::find(self.as_bytes(), sub)
    }

    /// Return the index of the given character in path.
    pub fn find_char(&self, c: u8) -> Option<usize> {
        memchr(c, self.as_bytes())
    }

    /// Returns the first character of the path.
    /// Empty path returns None.
    pub fn first(&self) -> Option<u8> {
        self.as_bytes().first().copied()
    }

    /// Returns the last character of the path.
    /// Empty path returns None.
    pub fn last(&self) -> Option<u8> {
        self.as_bytes().last().copied()
    }

    /// Returns the character at the specified index.
    /// Returns None if path is shorter.
    pub fn get(&self, index: usize) -> Option<u8> {
        self.as_bytes().get(index).copied()
    }

    /// Convert to a `Path`.
    pub fn as_path(&self) -> &Path {
        Path::new(self.as_os_str())
    }

    /// Creates an owned `XPathBuf` with path adjoined to `self`.
    /// If `path` is absolute, it replaces the current path.
    pub fn join(&self, path: &[u8]) -> XPathBuf {
        let mut owned = self.to_owned();
        owned.push(path);
        owned
    }

    /// Returns an immutable slice of the buffer.
    pub fn as_bytes(&self) -> &[u8] {
        self.0.as_bytes()
    }

    /// Convert to a `OsStr`.
    pub fn as_os_str(&self) -> &OsStr {
        &self.0
    }

    /// Create an `XPath` from a byte slice.
    ///
    /// Use the bytes up to the first nul byte if one exists;
    /// otherwise use the whole byte slice.
    pub fn from_bytes_until_nul(slice: &[u8]) -> &XPath {
        // Find first nul byte if any.
        // Use the whole slice otherwise.
        let nullx = memchr(0, slice).unwrap_or(slice.len());
        let slice = &slice[..nullx];

        // SAFETY: XPath has repr(transparent)
        unsafe { std::mem::transmute(slice) }
    }

    /// Create a new `XPath` from a byte slice.
    pub const fn from_bytes(slice: &[u8]) -> &XPath {
        // SAFETY: XPath has repr(transparent)
        unsafe { std::mem::transmute(slice) }
    }

    /// Create a new `XPath` for the dotdot path, aka `..`
    pub fn dotdot() -> &'static XPath {
        XPath::from_bytes(b"..")
    }

    /// Create a new `XPath` for the dot path, aka `.`
    pub fn dot() -> &'static XPath {
        XPath::from_bytes(b".")
    }

    /// Create a new `XPath` for the root path, aka `/`
    pub fn root() -> &'static XPath {
        XPath::from_bytes(b"/")
    }

    /// Create a new, empty `XPath`
    pub fn empty() -> &'static XPath {
        XPath::from_bytes(b"")
    }

    /// Create a new `XPath` from a byte slice.
    pub fn new<S: AsRef<OsStr> + ?Sized>(s: &S) -> &XPath {
        // SAFETY: XPath has repr(transparent).
        unsafe { &*(s.as_ref() as *const OsStr as *const XPath) }
    }
}

impl XPathBuf {
    /// Removes consecutive slashes (`/`) from the path in-place,
    /// replacing them with a single slash.
    ///
    /// This method modifies `self` directly.
    pub fn clean_consecutive_slashes(&mut self) {
        let len = match self.len() {
            0 | 1 => return,
            n => n,
        };

        let mut write_pos = 0;
        let mut read_pos = 0;
        #[expect(clippy::arithmetic_side_effects)]
        while read_pos < len {
            if self.0[read_pos] == b'/' {
                // Write a single slash.
                self.0[write_pos] = b'/';
                write_pos += 1;
                read_pos += 1;

                // Skip over consecutive slashes.
                while read_pos < len && self.0[read_pos] == b'/' {
                    read_pos += 1;
                }
            } else {
                // Find the next slash using memchr for efficiency.
                let next_slash = memchr(b'/', &self.0[read_pos..])
                    .map(|pos| pos + read_pos)
                    .unwrap_or(len);

                let segment_len = next_slash - read_pos;

                // Copy the segment of non-slash bytes to the write position if needed.
                if read_pos != write_pos {
                    self.0.copy_within(read_pos..next_slash, write_pos);
                }

                write_pos += segment_len;
                read_pos = next_slash;
            }
        }

        // Truncate the vector to the new length.
        self.0.truncate(write_pos);
    }

    /// Replace the **leading** `old` prefix with `new` in-place.
    ///
    /// - No-op if the buffer does **not** start with `old`, or already starts with `new`,
    ///   or if `old == new`.
    ///
    /// ### Errors
    /// - `EINVAL` if `old` is empty.
    /// - `EOVERFLOW` on arithmetic overflow when growing the buffer.
    /// - `ENOMEM` if additional capacity is required and cannot be reserved.
    ///
    /// This performs at most one `copy_within` of the tail and one prefix overwrite; it
    /// allocates only if `new.len() > old.len()`. Designed for cases like upgrading
    /// `!memfd:` -> `!memfd-hugetlb:`.
    pub fn replace_prefix(&mut self, old: &[u8], new: &[u8]) -> Result<(), Errno> {
        if old.is_empty() {
            return Err(Errno::EINVAL);
        }

        let s = self.as_bytes();

        // If already has the desired prefix exactly, do nothing.
        if is_prefix(s, new) || !is_prefix(s, old) || old == new {
            return Ok(());
        }

        let old_len = old.len();
        let new_len = new.len();
        let len = self.0.len();

        #[expect(clippy::arithmetic_side_effects)]
        match new_len.cmp(&old_len) {
            Ordering::Equal => {
                // Same length: overwrite the prefix.
                self.0[..new_len].copy_from_slice(new);
            }
            Ordering::Less => {
                // Shrink: move tail left, then overwrite head.
                let diff = old_len - new_len;
                if old_len <= len {
                    self.0.copy_within(old_len..len, new_len);
                    let new_total = len.saturating_sub(diff);
                    self.0.truncate(new_total);
                    self.0[..new_len].copy_from_slice(new);
                } else {
                    // Defensive: should not happen if starts_with(old) was true.
                    return Err(Errno::EINVAL);
                }
            }
            Ordering::Greater => {
                // Grow: reserve, extend, move tail right, then overwrite head.
                let add = new_len - old_len;
                let new_total = len.checked_add(add).ok_or(Errno::EOVERFLOW)?;
                self.try_reserve(add)?;
                self.0.resize(new_total, 0);
                // Move previous tail (len - old_len bytes) right by `add`.
                self.0
                    .copy_within(old_len..(new_total - add), old_len + add);
                self.0[..new_len].copy_from_slice(new);
            }
        }

        Ok(())
    }

    /// Remove the given range of bytes from the path buffer, dropping them.
    ///
    /// Panics on out-of-bounds.
    pub fn drain<R: RangeBounds<usize>>(&mut self, range: R) {
        self.0.drain(range);
    }

    /// Create a root magiclink path from the given PID.
    pub fn from_root(pid: Pid) -> Result<Self, Errno> {
        let mut buf = itoa::Buffer::new();
        let mut pfd = Vec::new();
        let len = pid_t::MAX_STR_LEN
            .checked_add("/root".len())
            .ok_or(Errno::EOVERFLOW)?;
        pfd.try_reserve(len).or(Err(Errno::ENOMEM))?;
        pfd.extend_from_slice(buf.format(pid.as_raw()).as_bytes());
        pfd.extend_from_slice(b"/root");
        pfd.shrink_to_fit();
        Ok(pfd.into())
    }

    /// Create a exe magiclink path from the given PID.
    pub fn from_exe(pid: Pid) -> Result<Self, Errno> {
        let mut buf = itoa::Buffer::new();
        let mut pfd = Vec::new();
        let len = pid_t::MAX_STR_LEN
            .checked_add("/exe".len())
            .ok_or(Errno::EOVERFLOW)?;
        pfd.try_reserve(len).or(Err(Errno::ENOMEM))?;
        pfd.extend_from_slice(buf.format(pid.as_raw()).as_bytes());
        pfd.extend_from_slice(b"/exe");
        pfd.shrink_to_fit();
        Ok(pfd.into())
    }

    /// Create a cwd magiclink path from the given PID.
    pub fn from_cwd(pid: Pid) -> Result<Self, Errno> {
        let mut buf = itoa::Buffer::new();
        let mut pfd = Vec::new();
        let len = pid_t::MAX_STR_LEN
            .checked_add("/cwd".len())
            .ok_or(Errno::EOVERFLOW)?;
        pfd.try_reserve(len).or(Err(Errno::ENOMEM))?;
        pfd.extend_from_slice(buf.format(pid.as_raw()).as_bytes());
        pfd.extend_from_slice(b"/cwd");
        pfd.shrink_to_fit();
        Ok(pfd.into())
    }

    /// Create a path from the given PID.
    pub fn from_pid(pid: Pid) -> Result<Self, Errno> {
        let mut buf = itoa::Buffer::new();
        let mut pfd = Vec::new();
        pfd.try_reserve(pid_t::MAX_STR_LEN).or(Err(Errno::ENOMEM))?;
        pfd.extend_from_slice(buf.format(pid.as_raw()).as_bytes());
        pfd.shrink_to_fit();
        Ok(pfd.into())
    }

    /// Create a path from the given TGID and TID.
    pub fn from_task(tgid: Pid, tid: Pid) -> Result<Self, Errno> {
        let mut buf = itoa::Buffer::new();
        let mut pfd = Vec::new();
        let len = pid_t::MAX_STR_LEN
            .checked_mul(2)
            .ok_or(Errno::EOVERFLOW)?
            .checked_add("/task/".len())
            .ok_or(Errno::EOVERFLOW)?;
        pfd.try_reserve(len).or(Err(Errno::ENOMEM))?;
        pfd.extend_from_slice(buf.format(tgid.as_raw()).as_bytes());
        pfd.extend_from_slice(b"/task/");
        pfd.extend_from_slice(buf.format(tid.as_raw()).as_bytes());
        pfd.shrink_to_fit();
        Ok(pfd.into())
    }

    /// Create a path from the given FD.
    pub fn from_fd(fd: RawFd) -> Result<Self, Errno> {
        let mut buf = itoa::Buffer::new();
        let mut pfd = Vec::new();
        pfd.try_reserve(RawFd::MAX_STR_LEN).or(Err(Errno::ENOMEM))?;
        pfd.extend_from_slice(buf.format(fd).as_bytes());
        Ok(pfd.into())
    }

    /// Create a path from the given PID and FD.
    pub fn from_pid_fd(pid: Pid, fd: RawFd) -> Result<Self, Errno> {
        let mut buf = itoa::Buffer::new();
        let mut pfd = Vec::new();
        let len = pid_t::MAX_STR_LEN
            .checked_add(RawFd::MAX_STR_LEN)
            .ok_or(Errno::EOVERFLOW)?
            .checked_add("/fd/".len())
            .ok_or(Errno::EOVERFLOW)?;
        pfd.try_reserve(len).or(Err(Errno::ENOMEM))?;
        pfd.extend_from_slice(buf.format(pid.as_raw()).as_bytes());
        pfd.extend_from_slice(b"/fd/");
        pfd.extend_from_slice(buf.format(fd).as_bytes());
        pfd.shrink_to_fit();
        Ok(pfd.into())
    }

    /// Create a path for the given self-FD.
    ///
    /// Used for _procfs_(5) indirection.
    pub fn from_self_fd(fd: RawFd) -> Result<Self, Errno> {
        // SAFETY:
        // Use /proc/thread-self rather than /proc/self
        // because CLONE_FILES may be in effect!
        const LEN: usize = "thread-self/fd".len() + 1 + RawFd::MAX_STR_LEN;
        let mut pfd = Vec::new();
        pfd.try_reserve(LEN).or(Err(Errno::ENOMEM))?;
        pfd.extend_from_slice(b"thread-self/fd");
        let mut pfd: Self = pfd.into();
        pfd.push_fd(fd);
        pfd.shrink_to_fit();
        Ok(pfd)
    }

    /// Append the formatted FD as a new component.
    pub fn push_pid(&mut self, pid: Pid) {
        let mut buf = itoa::Buffer::new();
        self.push(buf.format(pid.as_raw()).as_bytes())
    }

    /// Append the formatted FD as a new component.
    pub fn push_fd(&mut self, fd: RawFd) {
        let mut buf = itoa::Buffer::new();
        self.push(buf.format(fd).as_bytes())
    }

    /// Append a path component, managing separators correctly.
    ///
    /// Panics if `path` is not a single component.
    pub fn push(&mut self, path: &[u8]) {
        // SAFETY: Guard against path traversals, allow leading/trailing slash.
        let path = XPath::from_bytes(path);
        match path.find_char(b'/') {
            None | Some(0) => {}
            Some(n) if n == path.len().saturating_sub(1) => {}
            _ => unreachable!("BUG: Path traversal detected for `{path}'"),
        }
        assert!(
            !path.has_parent_dot(),
            "BUG: Path traversal detected for `{path}'"
        );

        if path.first() == Some(b'/') {
            // Absolute path replaces pbuf.
            self.0.clear();
        } else if self.last().map(|c| c != b'/').unwrap_or(true) {
            // Add separator if needed (last!=/ or empty path).
            self.append_byte(b'/');
        }
        // Append new path part.
        self.append_bytes(path.as_bytes());
    }

    /// Remove the last path component.
    pub fn pop(&mut self) {
        self.truncate(self.parent_len());
    }

    /// Remove the last path component without checks.
    ///
    /// # Safety
    ///
    /// 1. Path must be a normalized absolute path!
    /// 2. Path must not have a trailing slash!
    pub unsafe fn pop_unchecked(&mut self) {
        #[expect(clippy::arithmetic_side_effects)]
        if let Some(idx) = memrchr(b'/', &self.as_bytes()[1..]) {
            self.0.truncate(idx + 1);
        } else if self.0.len() > 1 {
            self.0.truncate(1);
        }
    }

    /// Append raw bytes to the path buffer.
    pub fn append_bytes(&mut self, bytes: &[u8]) {
        self.0.extend(bytes.iter().copied())
    }

    /// Append a raw byte to the path buffer.
    pub fn append_byte(&mut self, byte: u8) {
        self.0.push(byte)
    }

    /// Remove the last byte and return it or None if path is empty.
    pub fn pop_last(&mut self) -> Option<u8> {
        self.0.pop()
    }

    /// Clear the internal `Vec`.
    pub fn clear(&mut self) {
        self.0.clear()
    }

    /// Convert a `XPathBuf` to a `Vec`.
    pub fn into_vec(self) -> Vec<u8> {
        self.0.to_vec()
    }

    /// Convert a `XPathBuf` to an `OsString`.
    pub fn into_os_string(self) -> OsString {
        OsString::from_vec(self.0.to_vec())
    }

    /// Shorten the vector, keeping the first len elements and dropping
    /// the rest. If len is greater than or equal to the vector’s
    /// current length, this has no effect.
    pub fn truncate(&mut self, len: usize) {
        self.0.truncate(len)
    }

    /// Removes and returns the element at position index within the
    /// vector, shifting all elements after it to the left.
    pub fn remove(&mut self, index: usize) -> u8 {
        self.0.remove(index)
    }

    /// Shrink the capacity of the vector as much as possible.
    ///
    /// When possible, this will move data from an external heap buffer
    /// to the vector’s inline storage.
    pub fn shrink_to_fit(&mut self) {
        self.0.shrink_to_fit()
    }

    /// Try to clone an `XPathBuf`, returning `Err(Errno::ENOMEM)` on allocation errors.
    pub fn try_clone(&self) -> Result<Self, Errno> {
        let mut vec = Vec::new();
        vec.try_reserve(self.len()).or(Err(Errno::ENOMEM))?;
        vec.extend_from_slice(self.as_bytes());
        Ok(vec.into())
    }

    /// Reserve capacity for additional more bytes to be inserted.
    /// May reserve more space to avoid frequent allocations.
    pub fn try_reserve(&mut self, additional: usize) -> Result<(), Errno> {
        self.0.try_reserve(additional).or(Err(Errno::ENOMEM))
    }

    /// Resizes the `XPathBuf` in place so that `len` is equal to `new_len`.
    ///
    /// If `new_len` is greater than `len`, the `XPathBuf` is extended by the difference,
    /// with each additional slot filled with value. If `new_len` is less than `len`, the
    /// `XPathBuf` is simply truncated.
    pub fn resize(&mut self, new_len: usize, value: u8) {
        self.0.resize(new_len, value)
    }

    /// Creates an owned `XPathBuf` with path adjoined to `self`.
    /// If `path` is absolute, it replaces the current path.
    pub fn join(&self, path: &[u8]) -> XPathBuf {
        let mut owned = self.clone();
        owned.push(path);
        owned
    }

    /// Returns an immutable slice of the buffer.
    pub fn as_bytes(&self) -> &[u8] {
        &self.0
    }

    /// Convert to a `OsStr`.
    pub fn as_os_str(&self) -> &OsStr {
        OsStr::from_bytes(&self.0)
    }

    /// Convert to a `Path`.
    pub fn as_path(&self) -> &Path {
        Path::new(self.as_os_str())
    }

    /// Convert to a `XPath`.
    pub fn as_xpath(&self) -> &XPath {
        XPath::new(self.as_os_str())
    }

    /// Check if path is a symlink.
    pub fn is_symlink(&self) -> bool {
        self.as_path().is_symlink()
    }

    /// Check if path is a dir.
    pub fn is_dir(&self) -> bool {
        self.as_path().is_dir()
    }

    /// Check if path is a file.
    pub fn is_file(&self) -> bool {
        self.as_path().is_file()
    }

    /// Returns a slice containing the entire path buffer.
    pub fn as_slice(&self) -> &[u8] {
        self.0.as_slice()
    }

    /// Returns a mutable slice containing the entire path buffer.
    pub fn as_mut_slice(&mut self) -> &mut [u8] {
        self.0.as_mut_slice()
    }

    /// Returns a pointer to the internal `Vec`.
    pub fn as_ptr(&self) -> *const u8 {
        self.0.as_ptr()
    }

    /// Returns a mutable pointer to the internal `Vec`.
    pub fn as_mut_ptr(&mut self) -> *mut u8 {
        self.0.as_mut_ptr()
    }

    /// Sets an index in path to the given value.
    /// Panics if the index is invalid.
    pub fn set(&mut self, idx: usize, val: u8) {
        self.0[idx] = val;
    }

    /// Construct an empty `XPathBuf` with capacity pre-allocated.
    pub fn with_capacity(n: usize) -> Self {
        Self(TinyVec::with_capacity(n))
    }

    /// Report capacity of path.
    pub fn capacity(&self) -> usize {
        self.0.capacity()
    }

    /// Constructs a new, empty `XPathBuf`.
    ///
    /// The internal vector will not allocate until elements are pushed onto it.
    pub fn empty() -> Self {
        Self::new()
    }

    /// Constructs a new, empty `XPathBuf`.
    ///
    /// The internal vector will not allocate until elements are pushed onto it.
    pub fn new() -> XPathBuf {
        Self(TinyVec::new())
    }
}

/// Logs an untrusted Path, escaping it as hex if it contains control
/// characters.
pub fn mask_path<P: AsRef<Path> + ?Sized>(path: &P) -> String {
    log_untrusted_buf(path.as_ref().as_os_str().as_bytes()).0
}

fn is_permitted_initial(b: u8) -> bool {
    is_permitted_byte(b) && !matches!(b, b'-' | b' ' | b'~')
}

fn is_permitted_middle(b: u8) -> bool {
    is_permitted_byte(b)
}

fn is_permitted_final(b: u8) -> bool {
    is_permitted_byte(b) && b != b' '
}

fn is_permitted_byte(b: u8) -> bool {
    // As of version 3.38.0 the following characters are removed:
    // 1. `:` which is used commonly across devfs and procfs.
    // 2. `{` and `}` which are used in Firefox profile directories.
    match b {
        b'*' | b'?' | b'[' | b']' | b'"' | b'<' | b'>' | b'|' | b'(' | b')' | b'&' | b'\''
        | b'!' | b'\\' | b';' | b'$' | b'`' => false,
        0x20..=0x7E => true,
        0x80..=0xFE => true,
        _ => false,
    }
}

#[cfg(test)]
mod tests {
    use std::{sync::mpsc, thread};

    use nix::unistd::{gettid, pause};

    use super::*;

    struct CCSTestCase<'a> {
        src: &'a str,
        dst: &'a str,
    }

    const CCS_TESTS: &[CCSTestCase] = &[
        CCSTestCase { src: "/", dst: "/" },
        CCSTestCase {
            src: "///",
            dst: "/",
        },
        CCSTestCase {
            src: "////",
            dst: "/",
        },
        CCSTestCase {
            src: "//home/alip///",
            dst: "/home/alip/",
        },
        CCSTestCase {
            src: "//home/alip///.config///",
            dst: "/home/alip/.config/",
        },
        CCSTestCase {
            src: "//home/alip///.config///htop////",
            dst: "/home/alip/.config/htop/",
        },
        CCSTestCase {
            src: "//home/alip///.config///htop////htoprc",
            dst: "/home/alip/.config/htop/htoprc",
        },
    ];

    #[test]
    fn test_clean_consecutive_slashes() {
        for (idx, test) in CCS_TESTS.iter().enumerate() {
            let mut path = XPathBuf::from(test.src);
            path.clean_consecutive_slashes();
            assert_eq!(
                path,
                XPathBuf::from(test.dst),
                "Test {idx}: {} -> {path} != {}",
                test.src,
                test.dst
            );
        }
    }

    struct EndsWithDotTestCase<'a> {
        path: &'a str,
        test: bool,
    }

    const ENDS_WITH_DOT_TESTS: &[EndsWithDotTestCase] = &[
        EndsWithDotTestCase {
            path: ".",
            test: true,
        },
        EndsWithDotTestCase {
            path: "..",
            test: true,
        },
        EndsWithDotTestCase {
            path: "...",
            test: false,
        },
        EndsWithDotTestCase {
            path: "/.",
            test: true,
        },
        EndsWithDotTestCase {
            path: "/..",
            test: true,
        },
        EndsWithDotTestCase {
            path: "/...",
            test: false,
        },
        EndsWithDotTestCase {
            path: "foo.",
            test: false,
        },
        EndsWithDotTestCase {
            path: "foo./.",
            test: true,
        },
        EndsWithDotTestCase {
            path: "foo/./././/./",
            test: true,
        },
        EndsWithDotTestCase {
            path: "conftest.dir/././././////",
            test: true,
        },
    ];

    #[test]
    fn test_ends_with_dot() {
        for (idx, test) in ENDS_WITH_DOT_TESTS.iter().enumerate() {
            let ends = XPath::from_bytes(test.path.as_bytes()).ends_with_dot();
            assert_eq!(
                test.test, ends,
                "EndsWithDotTestCase {} -> \"{}\": {} != {}",
                idx, test.path, test.test, ends
            );
        }
    }

    #[test]
    fn test_is_dot() {
        let cases = [
            (".", true),
            ("./", true),
            (".///", true),
            ("././", true),
            ("./././", true),
            ("././././", true),
            ("/././", false),
            ("/./././", false),
            (".//././", true),
            ("", false),
            ("/", false),
            ("..", false),
            ("./..", false),
            ("../", false),
            ("././..", false),
            ("./../", false),
            ("./a", false),
            ("a/.", false),
            ("././a", false),
            ("a/./.", false),
            ("./././..", false),
            ("./.hidden", false),
            ("././.hidden", false),
            ("some/./path", false),
            ("./some/path", false),
            ("some/path/.", false),
            ("/some/path", false),
        ];

        for &(input, expected) in &cases {
            let path = XPath::from_bytes(input.as_bytes());
            assert_eq!(path.is_dot(), expected, "Failed on input: {:?}", input);
        }
    }

    #[test]
    fn test_descendant_of() {
        let cases = [
            ("/", "/", true),
            ("/foo", "/", true),
            ("/foo/bar", "/", true),
            ("/foo", "/foo", true),
            ("/foo/bar", "/foo", true),
            ("/foo2", "/foo", false),
            ("/foot", "/foo", false),
            ("/fo", "/foo", false),
            ("/", "/foo", false),
            ("/foo/bar", "/foo/bar", true),
            ("/foo/bar/baz", "/foo/bar", true),
            ("/foo/barbaz", "/foo/bar", false),
            ("/foo", "/foo/bar", false),
        ];

        for &(path, root, expected) in &cases {
            let path = XPath::from_bytes(path.as_bytes());
            assert_eq!(
                path.descendant_of(root.as_bytes()),
                expected,
                "Failed on input: {path:?} of {root}!"
            );
        }
    }

    #[test]
    fn test_has_parent_dot() {
        const TEST_CASES: &[(&[u8], bool)] = &[
            (b"/home/user/..", true),
            (b"/home/user/abc/..", true),
            (b"/path/to/dir/..", true),
            (b"/home/user/abc/xyz/..", true),
            (b"/dir/..", true),
            (b"/dir/..abc", false),
            (b"/../", true),
            (b"..", true),
            (b"/..", true),
            (b"/../../", true),
            (b"/..file/..", true),
            (b"/home/user/abc", false),
            (b"/home/user/abc/xyz", false),
            (b"/dir/abc/xyz/..file", false),
            (b"abc/..file", false),
            (b"dir/abc/..xyz", false),
            (b"dir/abc/xyz/..xyz", false),
            (b"/../", true),
            (b"/abc/../", true),
            (b"/abc/../xyz", true),
            (b"/..", true),
            (b"/dir/..file", false),
            (b"/..file", false),
            (b"abc/..xyz", false),
            (b"file/..name", false),
            (b"/..", true),
            (b"", false),
            (b".", false),
            (b".a", false),
            (b"..", true),
            (b"...", false),
            (b"/long/path/with/some/other/..component", false),
            (b"/long/path/with/..other", false),
            (b"/this/is/a/path/to/..", true),
            (b"/home/abc/def/..", true),
            (b"/dir/..abc/..", true),
            (b"/path/to/../../..", true),
            (b"/path/to/abc/../../../xyz", true),
            (b"/../file", true),
            (b"/file/..", true),
            (b"/..test", false),
            (b"/..test/file", false),
            (b"/some/dir/..test/file", false),
            (b"/path/with/..file", false),
            (b"/home/user", false),
            (b"/usr/local/bin", false),
            (b"/test/dir", false),
            (b"/..", true),
            (b"/../../", true),
            (b"/..file/..", true),
            (b"../dir/abc/..", true),
            (b"dir/..abc/xyz/..", true),
            (b"/path/to/..test/dir/../file/..", true),
            (b"/path/..to/../file", true),
            (b"/..test/file", false),
            (b"/test/dir/..file/..", true),
            (b"/some/dir/..test/file/..", true),
            (b"/path/with/..file", false),
        ];

        for (idx, &(path, expected)) in TEST_CASES.iter().enumerate() {
            let path = XPath::from_bytes(path);
            assert_eq!(
                path.has_parent_dot(),
                expected,
                "FAIL: `{path}' at index:{idx} expected has_parent_dot:{expected}"
            );
        }
    }

    #[test]
    fn test_strip_root() {
        let cases = [
            // Basic relative & empty
            ("", ""),
            (".", ""),
            ("./", ""),
            ("./.", ""),
            ("././", ""),
            ("foo", "foo"),
            ("foo/bar", "foo/bar"),
            ("foo/./bar", "foo/bar"),
            ("./foo/bar", "foo/bar"),
            // Leading root & redundant separators / dots
            ("/", ""),
            ("//", ""),
            ("///", ""),
            ("/.", ""),
            ("/./", ""),
            ("/./.", ""),
            ("/././", ""),
            ("/foo", "foo"),
            ("/foo/", "foo"),
            ("//foo", "foo"),
            ("///foo", "foo"),
            ("/./foo", "foo"),
            ("/././foo", "foo"),
            ("/foo/bar", "foo/bar"),
            ("/foo/bar/", "foo/bar"),
            ("//foo//bar///", "foo/bar"),
            ("/./foo/./bar/.", "foo/bar"),
            ("///.//././foo/bar", "foo/bar"),
            // Dots inside segment names must be preserved
            ("/foo.bar", "foo.bar"),
            ("/foo..bar", "foo..bar"),
            // Parent directory segments must be preserved (no over-normalization)
            ("../foo", "../foo"),
            ("../../foo", "../../foo"),
            ("foo/../bar", "foo/../bar"),
            ("/..", ".."),
            ("/../", ".."),
            ("/../foo", "../foo"),
            ("/foo/..", "foo/.."),
            ("/foo/../bar", "foo/../bar"),
            ("/foo/../../bar", "foo/../../bar"),
            ("/../../etc/passwd", "../../etc/passwd"),
            ("/foo/../../../etc/passwd", "foo/../../../etc/passwd"),
            // "Weird" but valid segments
            ("/ foo", " foo"),
            ("/\u{00A0}foo", "\u{00A0}foo"), // non-breaking space
        ];

        for &(input, expected) in &cases {
            let path = XPath::from_bytes(input.as_bytes());
            let stripped = path.strip_root();
            let expected_path = XPath::from_bytes(expected.as_bytes());

            assert_eq!(
                stripped.as_xpath(),
                expected_path,
                "strip_root failed: input={input:?}, expected={expected:?}, got={:?}",
                stripped
            );
        }
    }

    #[test]
    fn test_path_check_file_type() {
        xpath!("/proc")
            .check(Pid::from_raw(1), Some(&FileType::Dir), None, true, true)
            .unwrap();
        xpath!("/proc")
            .check(
                Pid::from_raw(1),
                Some(&FileType::Dir),
                Some(&XPath::from_bytes(b"self")),
                true,
                true,
            )
            .unwrap();
        xpath!("/proc")
            .check(
                Pid::from_raw(1),
                Some(&FileType::Reg),
                Some(&XPath::from_bytes(b"uptime")),
                true,
                true,
            )
            .unwrap();
        xpath!("/dev/null")
            .check(Pid::from_raw(1), Some(&FileType::Chr), None, true, true)
            .unwrap();
        xpath!("/dev/log")
            .check(Pid::from_raw(1), Some(&FileType::Sock), None, true, true)
            .unwrap();
        xpath!("/dev/fifo")
            .check(Pid::from_raw(1), Some(&FileType::Fifo), None, true, true)
            .unwrap();
        xpath!("/dev/sda1")
            .check(Pid::from_raw(1), Some(&FileType::Blk), None, true, true)
            .unwrap_err();
        xpath!("/dev/lmao")
            .check(Pid::from_raw(1), Some(&FileType::Unk), None, true, true)
            .unwrap_err();
    }

    #[test]
    fn test_path_check_procfs() {
        let this = Pid::from_raw(128);
        let that = Pid::from_raw(256);
        xpath!("/proc")
            .check(
                this,
                Some(&FileType::Dir),
                Some(&xpath!("{this}")),
                true,
                true,
            )
            .unwrap();
        xpath!("/proc/{this}")
            .check(
                this,
                Some(&FileType::Reg),
                Some(&XPath::from_bytes(b"mem")),
                true,
                true,
            )
            .unwrap();
        xpath!("/proc/{this}")
            .check(
                this,
                Some(&FileType::Dir),
                Some(&XPath::from_bytes(b"")),
                true,
                true,
            )
            .unwrap();
        xpath!("/proc/{this}/task")
            .check(
                this,
                Some(&FileType::Dir),
                Some(&xpath!("{this}")),
                true,
                true,
            )
            .unwrap();
        xpath!("/proc")
            .check(
                this,
                Some(&FileType::Dir),
                Some(&xpath!("{that}")),
                true,
                true,
            )
            .unwrap_err();
        xpath!("/proc/{that}")
            .check(
                this,
                Some(&FileType::Reg),
                Some(&XPath::from_bytes(b"")),
                true,
                true,
            )
            .unwrap();
        xpath!("/proc/{that}")
            .check(
                this,
                Some(&FileType::Dir),
                Some(&XPath::from_bytes(b"")),
                true,
                true,
            )
            .unwrap();
        xpath!("/proc/{that}/task")
            .check(
                this,
                Some(&FileType::Dir),
                Some(&xpath!("{that}")),
                true,
                true,
            )
            .unwrap();
    }

    #[test]
    fn test_path_check_procfs_syd_leader() {
        let syd = Pid::this();
        let pid = Pid::from_raw(syd.as_raw() + 1);
        xpath!("/proc")
            .check(
                syd,
                Some(&FileType::Dir),
                Some(&xpath!("{syd}")),
                true,
                true,
            )
            .unwrap();
        xpath!("/proc")
            .check(
                pid,
                Some(&FileType::Dir),
                Some(&xpath!("{syd}")),
                true,
                true,
            )
            .unwrap_err();
        xpath!("/proc/{syd}")
            .check(
                syd,
                Some(&FileType::Reg),
                Some(&XPath::from_bytes(b"")),
                true,
                true,
            )
            .unwrap();
        xpath!("/proc/{syd}")
            .check(
                pid,
                Some(&FileType::Reg),
                Some(&XPath::from_bytes(b"")),
                true,
                true,
            )
            .unwrap_err();
        xpath!("/proc/{syd}")
            .check(
                pid,
                Some(&FileType::Dir),
                Some(&XPath::from_bytes(b"")),
                true,
                true,
            )
            .unwrap_err();
        xpath!("/proc/{syd}/task")
            .check(
                syd,
                Some(&FileType::Dir),
                Some(&xpath!("{syd}")),
                true,
                true,
            )
            .unwrap();
        xpath!("/proc/{syd}/task")
            .check(
                pid,
                Some(&FileType::Dir),
                Some(&xpath!("{syd}")),
                true,
                true,
            )
            .unwrap_err();
    }

    #[test]
    fn test_path_check_procfs_syd_thread() {
        // Spawn a new thread.
        let pid = Pid::this();
        let tid = {
            let (tx, rx) = mpsc::channel();
            thread::spawn(move || {
                tx.send(gettid()).unwrap();
                pause();
            });
            rx.recv().unwrap()
        };
        xpath!("/proc")
            .check(
                tid,
                Some(&FileType::Dir),
                Some(&xpath!("{tid}")),
                true,
                true,
            )
            .unwrap();
        xpath!("/proc/{tid}")
            .check(
                tid,
                Some(&FileType::Reg),
                Some(&XPath::from_bytes(b"")),
                true,
                true,
            )
            .unwrap();
        xpath!("/proc/{tid}")
            .check(
                tid,
                Some(&FileType::Dir),
                Some(&XPath::from_bytes(b"")),
                true,
                true,
            )
            .unwrap();
        xpath!("/proc/{tid}/task")
            .check(
                tid,
                Some(&FileType::Dir),
                Some(&xpath!("{tid}")),
                true,
                true,
            )
            .unwrap();
        xpath!("/proc")
            .check(
                pid,
                Some(&FileType::Dir),
                Some(&xpath!("{tid}")),
                true,
                true,
            )
            .unwrap_err();
        xpath!("/proc/{tid}")
            .check(
                pid,
                Some(&FileType::Reg),
                Some(&XPath::from_bytes(b"")),
                true,
                true,
            )
            .unwrap_err();
        xpath!("/proc/{tid}")
            .check(
                pid,
                Some(&FileType::Dir),
                Some(&XPath::from_bytes(b"")),
                true,
                true,
            )
            .unwrap_err();
        xpath!("/proc/{tid}/task")
            .check(
                pid,
                Some(&FileType::Dir),
                Some(&xpath!("{tid}")),
                true,
                true,
            )
            .unwrap_err();
    }

    #[test]
    fn test_path_split_prefix_absolute() {
        let path = XPathBuf::from("/tmp/foo/bar/baz");

        assert_eq!(path.split_prefix(b"/").unwrap().as_bytes(), path.as_bytes());

        assert!(path.split_prefix(b"/tm").is_none());
        assert_eq!(
            path.split_prefix(b"/tmp").unwrap().as_bytes(),
            b"foo/bar/baz"
        );

        assert!(path.split_prefix(b"/tmp/f").is_none());
        assert_eq!(
            path.split_prefix(b"/tmp/foo/").unwrap().as_bytes(),
            b"bar/baz"
        );

        assert_eq!(
            path.split_prefix(b"/tmp/foo/bar/baz").unwrap().as_bytes(),
            b""
        );
    }

    #[test]
    fn test_path_split_prefix_relative() {
        let path = XPathBuf::from("tmp/foo/bar/baz");

        assert!(path.split_prefix(b"t").is_none());
        assert!(path.split_prefix(b"tm").is_none());

        assert_eq!(
            path.split_prefix(b"tmp").unwrap().as_bytes(),
            b"foo/bar/baz"
        );
        assert_eq!(
            path.split_prefix(b"tmp/").unwrap().as_bytes(),
            b"foo/bar/baz"
        );

        assert_eq!(
            path.split_prefix(b"tmp/foo/bar/baz").unwrap().as_bytes(),
            b""
        );
    }

    #[test]
    fn test_path_pop_unchecked() {
        let mut path = XPathBuf::from("/usr/host/bin/id");
        unsafe { path.pop_unchecked() };
        assert_eq!(path, XPathBuf::from("/usr/host/bin"));
        unsafe { path.pop_unchecked() };
        assert_eq!(path, XPathBuf::from("/usr/host"));
        unsafe { path.pop_unchecked() };
        assert_eq!(path, XPathBuf::from("/usr"));
        unsafe { path.pop_unchecked() };
        assert_eq!(path, XPathBuf::from("/"));
        unsafe { path.pop_unchecked() };
        assert_eq!(path, XPathBuf::from("/"));
    }

    #[test]
    fn test_path_pop() {
        // Truncates self to self.parent.
        // Popping `/' gives itself back.
        let mut path = XPathBuf::from("/spirited/away.rs");
        path.pop();
        assert_eq!(path, XPathBuf::from("/spirited"));
        path.pop();
        assert_eq!(path, XPathBuf::from("/"));
        path.pop();
        assert_eq!(path, XPathBuf::from("/"));
    }

    #[test]
    fn test_path_push_simple() {
        // Pushing a relative path extends the existing path.
        let mut path = XPathBuf::from("/tmp");
        path.push(b"file.bk");
        assert_eq!(path, XPathBuf::from("/tmp/file.bk"));

        // Pushing an absolute path replaces the existing path
        let mut path = XPathBuf::from("/tmp");
        path.push(b"/etc");
        assert_eq!(path, XPathBuf::from("/etc"));

        let mut path = XPathBuf::from("/tmp/bar");
        path.push(b"baz/");
        assert_eq!(path, XPathBuf::from("/tmp/bar/baz/"));

        // Pushing an empty string appends a trailing slash.
        let mut path = XPathBuf::from("/tmp");
        path.push(b"");
        assert_eq!(path, XPathBuf::from("/tmp/"));
        assert_eq!(path.as_os_str().as_bytes(), b"/tmp/");
    }

    #[test]
    #[should_panic]
    fn test_path_push_dotdot_simple() {
        let mut path = XPathBuf::from("/tmp");
        path.push(b"..");
    }

    #[test]
    #[should_panic]
    fn test_path_push_dotdot_with_trailing_slash() {
        let mut path = XPathBuf::from("/tmp");
        path.push(b"../");
    }

    #[test]
    fn test_path_split() {
        // Test typical path without trailing slash
        let path = XPathBuf::from("/foo/bar/baz");
        let (parent, file_name) = path.split();
        assert_eq!(parent, XPath::from_bytes(b"/foo/bar"));
        assert_eq!(file_name, XPath::from_bytes(b"baz"));

        // Test path with trailing slash
        let path = XPathBuf::from("/foo/bar/baz/");
        let (parent, file_name) = path.split();
        assert_eq!(parent, XPath::from_bytes(b"/foo/bar"));
        assert_eq!(file_name, XPath::from_bytes(b"baz/"));

        // Test root path "/"
        let path = XPathBuf::from("/");
        let (parent, file_name) = path.split();
        assert_eq!(parent, XPath::from_bytes(b"/"));
        assert_eq!(file_name, XPath::from_bytes(b"/"));

        // Test single level path without trailing slash
        let path = XPathBuf::from("/foo");
        let (parent, file_name) = path.split();
        assert_eq!(parent, XPath::from_bytes(b"/"));
        assert_eq!(file_name, XPath::from_bytes(b"foo"));

        // Test single level path with trailing slash
        let path = XPathBuf::from("/foo/");
        let (parent, file_name) = path.split();
        assert_eq!(parent, XPath::from_bytes(b"/"));
        assert_eq!(file_name, XPath::from_bytes(b"foo/"));
    }

    #[test]
    fn test_path_is_proc_pid() {
        assert!(XPathBuf::from("/proc/1").is_proc_pid());
        assert!(XPathBuf::from("/proc/1/").is_proc_pid());

        assert!(XPathBuf::from("/proc/123456789").is_proc_pid());
        assert!(XPathBuf::from("/proc/123456789/task").is_proc_pid());

        assert!(!XPathBuf::from("/proc").is_proc_pid());
        assert!(!XPathBuf::from("/proc/").is_proc_pid());

        assert!(!XPathBuf::from("/proc/acpi").is_proc_pid());
        assert!(!XPathBuf::from("/proc/keys").is_proc_pid());

        // FIXME: This should return false, but it does not matter in practise.
        assert!(XPathBuf::from("/proc/0keys").is_proc_pid());

        assert!(!XPathBuf::from("/dev").is_proc_pid());
        assert!(!XPathBuf::from("/dev/0").is_proc_pid());

        assert!(!XPathBuf::from("/pro").is_proc_pid());
        assert!(!XPathBuf::from("/pro/").is_proc_pid());
        assert!(!XPathBuf::from("/pro/1").is_proc_pid());
    }

    #[test]
    fn test_check_name_valid() {
        let valid_filenames = [
            "valid_filename.txt",
            "hello_world",
            "File123",
            "Makefile",
            "こんにちは", // Japanese characters
            "文件",       // Chinese characters
            "emoji😀",    // Starts with permitted character
            "valid~name", // '~' allowed in middle
            "name~",      // '~' allowed at end
            "a",
            "normal",
            "test-file",
            "test_file",
            "file name",
            "file☃name",    // Snowman character
            "name\u{0080}", // Contains 0x80 (allowed)
            "name\u{00FE}", // Contains 0xFE (allowed)
            "😀name",       // Multi-byte character at start
            "name😀",       // Multi-byte character at end
            "😀",           // Single multi-byte character
            "name😀name",   // Multi-byte character in middle
            "na~me",        // '~' allowed in middle
            "name-",        // Hyphen at end (allowed)
            "name_",        // Underscore at end (allowed)
            "name.",        // Period at end (allowed)
            "a\u{0020}b",   // SPACE in the middle (allowed)
            "a\u{00A0}b",   // NO-BREAK SPACE in the middle (allowed)
            "a\u{1680}b",   // OGHAM SPACE MARK in the middle (allowed)
            "a\u{2007}b",   // FIGURE SPACE in the middle (allowed)
            "a\u{202F}b",   // NARROW NO-BREAK SPACE in the middle (allowed)
            "a\u{3000}b",   // IDEOGRAPHIC SPACE in the middle (allowed)
        ];

        for (idx, name) in valid_filenames.iter().enumerate() {
            let name = XPath::new(name);
            assert!(
                name.check_name().is_ok(),
                "Filename {idx} '{name}' should be valid"
            );
        }
    }

    #[test]
    fn test_check_name_invalid() {
        let invalid_filenames: &[&[u8]] = &[
            b"",                             // Empty filename
            b"-",                            // Starts with '-'
            b"*",                            // Starts with '*'
            b"?",                            // Starts with '?'
            b"!",                            // Starts with '!'
            b"$",                            // Starts with '$'
            b"`",                            // Starts with '`'
            b" -",                           // Starts with space
            b"~home",                        // Starts with '~'
            b"*home",                        // Starts with '*'
            b"?home",                        // Starts with '?'
            b"!home",                        // Starts with '!'
            b"$home",                        // Starts with '$'
            b"`home",                        // Starts with '`'
            b"file ",                        // Ends with space
            b"file*",                        // Ends with '*'
            b"file?",                        // Ends with '?'
            b"file!",                        // Ends with '!'
            b"file$",                        // Ends with '$'
            b"file`",                        // Ends with '`'
            b"bad*name",                     // Contains '*'
            b"bad?name",                     // Contains '?'
            b"bad!name",                     // Contains '!'
            b"bad$name",                     // Contains '$'
            b"bad`name",                     // Contains '`'
            b"bad\nname",                    // Contains newline
            b"\0",                           // Null byte
            b"bad\0name",                    // Contains null byte
            b"bad\x7Fname",                  // Contains delete character
            b"bad\xFFname",                  // Contains 0xFF
            b"\x1Fcontrol",                  // Starts with control character
            b"name\x1F",                     // Ends with control character
            b"name\x7F",                     // Ends with delete character
            b"name\xFF",                     // Ends with 0xFF
            b"name ",                        // Ends with space
            b"-name",                        // Starts with '-'
            b" name",                        // Starts with space
            b"~name",                        // Starts with '~'
            b"*name",                        // Starts with '*'
            b"?name",                        // Starts with '?'
            b"!name",                        // Starts with '!'
            b"$name",                        // Starts with '$'
            b"`name",                        // Starts with '`'
            b"name\x19",                     // Contains control character
            b"name\n",                       // Ends with newline
            b"\nname",                       // Starts with newline
            b"na\nme",                       // Contains newline
            b"name\t",                       // Contains tab
            b"name\r",                       // Contains carriage return
            b"name\x1B",                     // Contains escape character
            b"name\x00",                     // Contains null byte
            b"name\x7F",                     // Contains delete character
            b"name\xFF",                     // Contains 0xFF (disallowed)
            b"\xFF",                         // Single byte 0xFF
            b"name\x80\xFF",                 // Contains valid and invalid extended ASCII
            b"name\xC0\xAF",                 // Invalid UTF-8 sequence
            b"\xF0\x28\x8C\xBC",             // Invalid UTF-8 sequence
            b"\xF0\x90\x28\xBC",             // Invalid UTF-8 sequence
            b"\xF0\x28\x8C\x28",             // Invalid UTF-8 sequence
            b"name\xFFname",                 // Contains 0xFF
            b"name\xC3\x28",                 // Invalid UTF-8 sequence
            b"name\xA0\xA1",                 // Invalid UTF-8 sequence
            b"\xE2\x28\xA1",                 // Invalid UTF-8 sequence
            b"\xE2\x82\x28",                 // Invalid UTF-8 sequence
            b"\xF0\x28\x8C\xBC",             // Invalid UTF-8 sequence
            b"\xF0\x90\x28\xBC",             // Invalid UTF-8 sequence
            b"\xF0\x28\x8C\x28",             // Invalid UTF-8 sequence
            b"\xC2\xA0",                     // Non-breaking space
            b"\x20file",                     // leading SPACE U+0020
            b"file\x20",                     // trailing SPACE U+0020
            b"\xC2\xA0file",                 // leading NO-BREAK SPACE U+00A0
            b"file\xE3\x80\x80",             // trailing IDEOGRAPHIC SPACE U+3000
            b"\xE2\x80\xAFfile",             // leading NARROW NO-BREAK SPACE U+202F
            b"\xE2\x81\x9Ffile\xE2\x81\x9F", // both sides MEDIUM MATHEMATICAL SPACE U+205F
        ];

        for (idx, name) in invalid_filenames.iter().enumerate() {
            let name = XPath::from_bytes(name);
            assert!(
                name.check_name().is_err(),
                "Filename {idx} '{name}' should not be valid"
            );
        }
    }

    #[test]
    fn test_check_name_control_characters() {
        for b in 0x00..=0x1F {
            if let Some(c) = char::from_u32(b as u32) {
                let name = format!("name{c}char");
                let name = XPath::new(&name);
                assert!(
                    name.check_name().is_err(),
                    "Filename with control character '\\x{b:02X}' should be invalid",
                );
            }
        }
    }

    #[test]
    fn test_check_name_extended_ascii_characters() {
        for b in 0x80..=0xFE {
            if b == 0xFF {
                continue; // 0xFF is disallowed.
            }
            let mut bytes = b"name".to_vec();
            bytes.push(b);
            bytes.extend_from_slice(b"char");
            let name = OsStr::from_bytes(&bytes);
            let name = XPath::new(name);
            let result = name.check_name();
            if std::str::from_utf8(&bytes).is_ok() {
                assert!(result.is_ok(), "Filename with byte 0x{b:X} should be valid",);
            } else {
                assert!(
                    result.is_err(),
                    "Filename with invalid UTF-8 byte 0x{b:X} should be invalid",
                );
            }
        }
    }

    #[test]
    fn test_check_name_edge_cases() {
        // Filenames with length 1
        let valid_single_chars = [
            "a", "b", "Z", "9", "_", ".", "😀", // Valid multi-byte character
        ];

        for (idx, name) in valid_single_chars.iter().enumerate() {
            let name = XPath::new(name);
            assert!(
                name.check_name().is_ok(),
                "Single-character filename {idx} '{name}' should be valid",
            );
        }

        let invalid_single_chars: &[&[u8]] = &[
            b"-",            // Starts with '-'
            b" ",            // Space character
            b"~",            // Tilde character
            b"*",            // Starts with '*'
            b"?",            // Starts with '?'
            b"\n",           // Newline character
            b"\r",           // Newline character
            b"\x7F",         // Delete character
            b"\x1F",         // Control character
            b"\xFF",         // 0xFF disallowed
            b"\0",           // Null byte
            b"\xC2\xA0",     // Non-breaking space
            b"\x20",         // SPACE U+0020
            b"\xC2\xA0",     // NO-BREAK SPACE U+00A0
            b"\xE1\x9A\x80", // OGHAM SPACE MARK U+1680
            b"\xE2\x80\x87", // FIGURE SPACE U+2007
            b"\xE2\x80\xAF", // NARROW NO-BREAK SPACE U+202F
            b"\xE3\x80\x80", // IDEOGRAPHIC SPACE U+3000
        ];

        for (idx, name) in invalid_single_chars.iter().enumerate() {
            let name = XPath::from_bytes(name);
            assert!(
                name.check_name().is_err(),
                "Single-character filename {idx} '{name}' should be invalid",
            );
        }
    }

    // Helper to build a buffer.
    fn xp(bytes: &[u8]) -> XPathBuf {
        XPathBuf(TinyVec::Heap(bytes.to_vec()))
    }

    #[test]
    fn test_replace_prefix_no_match_is_noop() -> Result<(), Errno> {
        let mut x = xp(b"/ordinary/path");
        x.replace_prefix(b"!memfd:", b"!memfd-hugetlb:")?;
        assert_eq!(x.as_bytes(), b"/ordinary/path");
        Ok(())
    }

    #[test]
    fn test_push_bytes_and_convert_to_vec() {
        let mut base = XPathBuf::from("./");
        assert_eq!(base.as_bytes(), b"./");
        base.append_bytes(b"sigpipe.sock");
        base.append_byte(0);
        let v = base.into_vec();
        assert_eq!(v, b"./sigpipe.sock\0");
    }

    #[test]
    fn test_convert_from_string() {
        let s = String::from("test/path");
        let xb = XPathBuf::from(s);
        assert_eq!(xb.as_bytes(), b"test/path");
        let v = xb.into_vec();
        assert_eq!(v, b"test/path");
    }

    #[test]
    fn test_replace_prefix_equal_length_overwrite() -> Result<(), Errno> {
        let mut x = xp(b"abc:/tail");
        x.replace_prefix(b"abc:", b"XYZ:")?;
        assert_eq!(x.as_bytes(), b"XYZ:/tail");
        Ok(())
    }

    #[test]
    fn test_replace_prefix_grow_old_to_new() -> Result<(), Errno> {
        let mut x = xp(b"!memfd:/some/path");
        x.replace_prefix(b"!memfd:", b"!memfd-hugetlb:")?;
        assert_eq!(x.as_bytes(), b"!memfd-hugetlb:/some/path");
        Ok(())
    }

    #[test]
    fn test_replace_prefix_grow_when_only_prefix_present() -> Result<(), Errno> {
        let mut x = xp(b"!memfd:");
        x.replace_prefix(b"!memfd:", b"!memfd-hugetlb:")?;
        assert_eq!(x.as_bytes(), b"!memfd-hugetlb:");
        Ok(())
    }

    #[test]
    fn test_replace_prefix_idempotent_for_upgrade() -> Result<(), Errno> {
        // Already upgraded; should not duplicate "-hugetlb".
        let mut x = xp(b"!memfd-hugetlb:/already");
        x.replace_prefix(b"!memfd:", b"!memfd-hugetlb:")?;
        assert_eq!(x.as_bytes(), b"!memfd-hugetlb:/already");
        // Re-apply again to ensure stability.
        x.replace_prefix(b"!memfd:", b"!memfd-hugetlb:")?;
        assert_eq!(x.as_bytes(), b"!memfd-hugetlb:/already");
        Ok(())
    }

    #[test]
    fn test_replace_prefix_shrink_old_to_new() -> Result<(), Errno> {
        let mut x = xp(b"prefix-long:/rest");
        x.replace_prefix(b"prefix-long:", b"p:")?;
        assert_eq!(x.as_bytes(), b"p:/rest");
        Ok(())
    }

    #[test]
    fn test_replace_prefix_shrink_when_new_is_prefix_of_old() -> Result<(), Errno> {
        // Ensure we still shrink even though the buffer "starts_with(new)".
        let mut x = xp(b"foobar-long:/zzz");
        x.replace_prefix(b"foobar-long:", b"foobar:")?;
        assert_eq!(x.as_bytes(), b"foobar:/zzz");
        Ok(())
    }

    #[test]
    fn test_replace_prefix_tail_integrity_grow() -> Result<(), Errno> {
        let mut x = xp(b"!memfd:/a/b/c/d/e");
        x.replace_prefix(b"!memfd:", b"!memfd-hugetlb:")?;
        assert_eq!(x.as_bytes(), b"!memfd-hugetlb:/a/b/c/d/e");
        Ok(())
    }

    #[test]
    fn test_replace_prefix_tail_integrity_shrink() -> Result<(), Errno> {
        let mut x = xp(b"longerprefix:/a/b/c");
        x.replace_prefix(b"longerprefix:", b"lp:")?;
        assert_eq!(x.as_bytes(), b"lp:/a/b/c");
        Ok(())
    }

    #[test]
    fn test_replace_prefix_capacity_growth_handled() -> Result<(), Errno> {
        // Force the "grow" code path to hit reserve/resize.
        let mut x = XPathBuf::with_capacity(1);
        x.append_bytes(b"!memfd:/x/y/z");
        let cap_before = x.capacity();
        x.replace_prefix(b"!memfd:", b"!memfd-hugetlb:")?;
        let cap_after = x.capacity();
        assert!(cap_after >= cap_before);
        assert_eq!(x.as_bytes(), b"!memfd-hugetlb:/x/y/z");
        Ok(())
    }

    #[test]
    fn test_replace_prefix_large_tail_stress() -> Result<(), Errno> {
        // Build a long tail to stress copy_within correctness.
        let mut tail = Vec::new();
        for _ in 0..512 {
            tail.extend_from_slice(b"/component");
        }
        let mut p = b"!memfd:".to_vec();
        p.extend_from_slice(&tail);
        let mut x = xp(&p);
        x.replace_prefix(b"!memfd:", b"!memfd-hugetlb:")?;
        let mut expected = b"!memfd-hugetlb:".to_vec();
        expected.extend_from_slice(&tail);
        assert_eq!(x.as_bytes(), expected.as_slice());
        Ok(())
    }

    #[test]
    fn test_replace_prefix_utf8_agnostic_bytes() -> Result<(), Errno> {
        // Ensure function doesn't assume UTF-8; operate on arbitrary bytes.
        let mut x = xp(b"\xF0\x9F\x92\xA9prefix-long:\xFF\x00tail");
        x.replace_prefix(b"\xF0\x9F\x92\xA9prefix-long:", b"\xF0\x9F\x92\xA9p:")?;
        assert_eq!(x.as_bytes(), b"\xF0\x9F\x92\xA9p:\xFF\x00tail");
        Ok(())
    }

    #[test]
    fn test_replace_prefix_empty_old_is_error() {
        let mut x = xp(b"!memfd:/whatever");
        let res = x.replace_prefix(b"", b"!memfd-hugetlb:");
        assert!(matches!(res, Err(Errno::EINVAL)));
    }
}
