//
// Syd: rock-solid unikernel
// src/fs.rs: Filesystem utilities
//
// Copyright (c) 2023, 2024 Ali Polatel <alip@chesswob.org>
// Based in part upon uutils coreutils package's src/lib/features/fs.rs which is:
//   (c) Joseph Crail <jbcrail@gmail.com>
//   (c) Jian Zeng <anonymousknight96 AT gmail.com>
// Tests base based in part upon gnulib packages' tests/test-canonicalize.c which is:
//   (c) Free Software Foundation, Inc.
//
// SPDX-License-Identifier: GPL-3.0-or-later

//! Set of functions to manage files and symlinks

// SAFETY: PathBuf is only used for the to_path_buf() routine.
#[allow(clippy::disallowed_types)]
use std::{
    borrow::Cow,
    collections::VecDeque,
    ffi::{CStr, CString, OsStr, OsString},
    fs::{metadata, set_permissions, File},
    hash::Hash,
    io::Write,
    os::{
        fd::{AsRawFd, FromRawFd, OwnedFd, RawFd},
        unix::{
            ffi::{OsStrExt, OsStringExt},
            fs::PermissionsExt,
        },
    },
    path::{Path, PathBuf},
};

use memchr::{
    arch::all::{is_prefix, is_suffix},
    memrchr,
};
use nix::{
    errno::Errno,
    fcntl::{fcntl, openat2, FcntlArg, FdFlag, OFlag, OpenHow, ResolveFlag, SealFlag},
    sched::CloneCb,
    sys::{inotify::AddWatchFlags, stat::SFlag},
    unistd::Pid,
    NixPath,
};
use rustc_hash::FxHashSet;
use serde::{Serialize, Serializer};
use smallvec::SmallVec;

use crate::{
    compat::{fstatx, statx, FileStatx, STATX_INO, STATX_MODE, STATX_SIZE, STATX_TYPE},
    config::MAGIC_PREFIX,
    proc::{proc_fd, proc_tgid},
};

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

/// A safe constant to use for short paths.
pub const PATH_MIN: usize = 128;

/// A struct that represents a path built on a `SmallVec` to reduce allocations.
#[derive(Clone, Hash, Eq, PartialEq)]
pub struct SmallPathBuf(SmallVec<[u8; PATH_MAX]>);

impl SmallPathBuf {
    /// Create a new, empty `SmallPathBuf`.
    pub fn new() -> Self {
        Self(SmallVec::new())
    }

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

    /// 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 `Err`.
    pub fn strip_prefix(&self, base: &Path) -> Result<&Path, Errno> {
        let self_bytes = self.as_slice();
        let base_bytes = base.as_os_str().as_bytes();

        // Check if self starts with base
        if self_bytes.starts_with(base_bytes) {
            // Determine the remainder after the base.
            let remainder = &self_bytes[base_bytes.len()..];

            // Check if there is anything left after the base.
            if remainder.is_empty() {
                // If the remainder is empty, return an empty path.
                Ok(Path::new(""))
            } 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.
                Ok(Path::new(OsStr::from_bytes(&remainder[1..])))
            } else {
                // If the path doesn't start with '/', it means base is not a directory prefix.
                Err(Errno::ENOENT)
            }
        } else {
            Err(Errno::ENOENT)
        }
    }

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

    /// Check if path is absolute.
    pub fn is_absolute(&self) -> bool {
        self.as_ref().is_absolute()
    }

    /// Check if path is relative.
    pub fn is_relative(&self) -> bool {
        self.as_ref().is_relative()
    }

    /// Determine whether base is a prefix of path.
    pub fn starts_with(&self, base: &Path) -> bool {
        self.as_ref().starts_with(base)
    }

    /// Append a path component, managing separators correctly.
    pub fn push(&mut self, path: &Path) {
        if path.is_absolute() {
            // Absolute path replaces pbuf.
            self.0.clear();
        } else if self
            .as_os_str()
            .as_bytes()
            .last()
            .map(|&b| b != b'/')
            .unwrap_or(true)
        {
            // Add separator if needed (last!=/ or empty path).
            self.0.push(b'/');
        }
        // Append new path part.
        self.append_bytes(path.as_os_str().as_bytes());
    }

    /// Remove the last path component.
    pub fn pop(&mut self) {
        let (root, _) = path_split(self.as_ref());
        self.0.truncate(root.len());
    }

    /// Convert to a `PathBuf` for final use outside of performance-critical sections.
    #[allow(clippy::disallowed_types)]
    pub fn to_path_buf(&self) -> PathBuf {
        PathBuf::from(OsStr::from_bytes(&self.0))
    }

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

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

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

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

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

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

    /// Sets the length of the internal vector.
    ///
    /// # Safety
    ///
    /// This will explicitly set the size of the vector, without
    /// actually modifying its buffers, so it is up to the caller that
    /// the vector is actually the specified size.
    pub unsafe fn set_len(&mut self, new_len: usize) {
        self.0.set_len(new_len)
    }

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

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

    /// Convert a `SmallPathBuf` to a `Vec`, without reallocating if the
    /// underlying `SmallVec` has already spilled onto the heap.
    pub fn into_vec(self) -> Vec<u8> {
        self.0.into_vec()
    }
}

impl std::fmt::Display for SmallPathBuf {
    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 SmallPathBuf {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_tuple("SmallPathBuf")
            .field(&mask_path(self.as_path()))
            .finish()
    }
}

impl Default for SmallPathBuf {
    #[inline]
    fn default() -> Self {
        Self::new()
    }
}

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

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

impl AsRef<Path> for SmallPathBuf {
    #[inline]
    fn as_ref(&self) -> &Path {
        self.as_path()
    }
}

impl From<&[u8]> for SmallPathBuf {
    #[inline]
    fn from(bytes: &[u8]) -> Self {
        let mut buf = Self::new();
        buf.append_bytes(bytes);
        buf
    }
}

impl From<&str> for SmallPathBuf {
    #[inline]
    fn from(s: &str) -> Self {
        let mut buf = Self::new();
        buf.append_bytes(s.as_bytes());
        buf
    }
}

impl From<String> for SmallPathBuf {
    #[inline]
    fn from(s: String) -> Self {
        Self::from(s.as_str())
    }
}

impl From<&Path> for SmallPathBuf {
    fn from(path: &Path) -> Self {
        let mut buf = Self::new();
        buf.append_bytes(path.as_os_str().as_bytes());
        buf
    }
}

#[allow(clippy::disallowed_types)]
impl From<PathBuf> for SmallPathBuf {
    fn from(pathbuf: PathBuf) -> Self {
        Self::from(pathbuf.as_path())
    }
}

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

impl NixPath for SmallPathBuf {
    fn is_empty(&self) -> bool {
        NixPath::is_empty(self.as_os_str())
    }

    fn len(&self) -> usize {
        NixPath::len(self.as_os_str())
    }

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

/// A struct that represents a short path built on a `SmallVec` to reduce allocations.
#[derive(Clone, Hash, Eq, PartialEq)]
pub struct TinyPathBuf(SmallVec<[u8; PATH_MIN]>);

impl TinyPathBuf {
    /// Create a new, empty `TinyPathBuf`.
    pub fn new() -> Self {
        Self(SmallVec::new())
    }

    /// Append a path component, managing separators correctly.
    pub fn push(&mut self, path: &Path) {
        if path.is_absolute() {
            // Absolute path replaces pbuf.
            self.0.clear();
        } else if self
            .as_os_str()
            .as_bytes()
            .last()
            .map(|&b| b != b'/')
            .unwrap_or(true)
        {
            // Add separator if needed (last!=/ or empty path).
            self.0.push(b'/');
        }
        // Append new path part.
        self.append_bytes(path.as_os_str().as_bytes());
    }

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

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

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

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

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

impl Default for TinyPathBuf {
    #[inline]
    fn default() -> Self {
        Self::new()
    }
}

impl AsRef<Path> for TinyPathBuf {
    #[inline]
    fn as_ref(&self) -> &Path {
        self.as_path()
    }
}

pub(crate) const MFD_CLOEXEC: nix::libc::c_uint = nix::libc::MFD_CLOEXEC;
pub(crate) const MFD_ALLOW_SEALING: nix::libc::c_uint = nix::libc::MFD_ALLOW_SEALING;
pub(crate) const MFD_NOEXEC_SEAL: nix::libc::c_uint = nix::libc::MFD_NOEXEC_SEAL;
pub(crate) const MFD_EXEC: nix::libc::c_uint = nix::libc::MFD_EXEC;

/// Information to uniquely identify a file.
pub struct FileInformation {
    /// Inode number
    pub ino: u64,
    /// Device major number
    pub dev_major: u32,
    /// Device minor number
    pub dev_minor: u32,
    /// File mode
    pub mode: u16,
}

impl FileInformation {
    /// Get information for a given link.
    pub fn from_link<P: AsRef<Path>>(path: P) -> Result<Self, Errno> {
        // Note, we deliberately don't pass STATX_MODE in flags here, as
        // the only caller canonicalize() do not need this field.
        let stat = retry_on_eintr(|| {
            statx(
                None,
                path.as_ref(),
                nix::libc::AT_SYMLINK_NOFOLLOW as nix::libc::c_uint,
                STATX_INO,
            )
        })?;

        Ok(Self {
            ino: stat.stx_ino,
            dev_major: stat.stx_dev_major,
            dev_minor: stat.stx_dev_minor,
            mode: 0,
        })
    }

    /// Get information from a given path.
    pub fn from_path<P: AsRef<Path>>(path: P) -> Result<Self, Errno> {
        let stat = retry_on_eintr(|| statx(None, path.as_ref(), 0, STATX_MODE | STATX_INO))?;

        Ok(Self {
            ino: stat.stx_ino,
            dev_major: stat.stx_dev_major,
            dev_minor: stat.stx_dev_minor,
            mode: stat.stx_mode,
        })
    }
}

impl PartialEq for FileInformation {
    fn eq(&self, other: &Self) -> bool {
        self.ino == other.ino
            && self.dev_major == other.dev_major
            && self.dev_minor == other.dev_minor
    }
}

impl Eq for FileInformation {}

impl Hash for FileInformation {
    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
        self.ino.hash(state);
        self.dev_major.hash(state);
        self.dev_minor.hash(state);
    }
}

/// Controls how missing components should be handled when canonicalizing a path.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum MissingHandling {
    /// Last component may exist, other components must exist.
    Normal,

    /// All components must exist.
    Existing,

    /// Last component must not exist, other componenets must exist.
    Missing,
}

impl Default for MissingHandling {
    fn default() -> Self {
        Self::Normal
    }
}

impl Serialize for MissingHandling {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let variant_str = match *self {
            MissingHandling::Normal => "normal",
            MissingHandling::Existing => "exist",
            MissingHandling::Missing => "miss",
        };

        serializer.serialize_str(variant_str)
    }
}

/// This function creates an Open File Description (OFD) lock.
pub fn lock_fd<F: AsRawFd>(fd: &F, write: bool, wait: bool) -> Result<(), Errno> {
    let fd = fd.as_raw_fd();
    #[allow(clippy::cast_possible_truncation)]
    let lock = nix::libc::flock {
        l_type: if write {
            nix::libc::F_WRLCK
        } else {
            nix::libc::F_RDLCK
        } as i16,
        l_whence: nix::libc::SEEK_SET as i16,
        l_start: 0,
        l_len: 1, // Lock the first byte.
        l_pid: 0, // The kernel sets this value.
    };
    retry_on_eintr(|| {
        fcntl(
            fd,
            if wait {
                FcntlArg::F_OFD_SETLKW(&lock)
            } else {
                FcntlArg::F_OFD_SETLK(&lock)
            },
        )
    })
    .map(drop)
}

/// This function releases an Open File Description (OFD) lock.
pub fn unlock_fd<F: AsRawFd>(fd: &F) -> Result<(), Errno> {
    let fd = fd.as_raw_fd();
    #[allow(clippy::cast_possible_truncation)]
    let lock = nix::libc::flock {
        l_type: nix::libc::F_UNLCK as i16,
        l_whence: nix::libc::SEEK_SET as i16,
        l_start: 0,
        l_len: 1, // Lock the first byte.
        l_pid: 0, // The kernel sets this value.
    };
    retry_on_eintr(|| fcntl(fd, FcntlArg::F_OFD_SETLK(&lock))).map(drop)
}

/// Retries a closure on `EINTR` error.
///
/// This function will call the provided closure, and if the closure
/// returns an `EINTR` error, it will retry the operation until it
/// succeeds or fails with a different error.
#[inline]
pub(crate) fn retry_on_eintr<F, T>(mut f: F) -> Result<T, Errno>
where
    F: FnMut() -> Result<T, Errno>,
{
    loop {
        match f() {
            Err(Errno::EINTR) => continue,
            result => return result,
        }
    }
}

/// Creates an anonymous, non-executable file that lives in memory, and
/// return a file-descriptor to it.
pub fn create_memfd(flags: nix::libc::c_uint) -> Result<OwnedFd, Errno> {
    let name = CString::default();

    // SAFETY: nix' MemFdCreateFlags don't define MFD_EXEC and MFD_NOEXEC_SEAL yet.
    let fd = unsafe { nix::libc::memfd_create(name.as_ptr(), flags | MFD_CLOEXEC) };
    match Errno::result(fd).map(|fd| {
        // SAFETY: memfd_create returns a valid FD.
        unsafe { OwnedFd::from_raw_fd(fd) }
    }) {
        Ok(fd) => Ok(fd),
        Err(Errno::EINVAL) => {
            // 1. Flags included unknown bits.
            // 2. Name was too long.
            // 3. Both MFD_HUGETLB and MFD_ALLOW_SEALING were
            //    specified in flags.
            // In our case only the first one is relevant and
            // indicates MFD_NOEXEC_SEAL is unsupported. To avoid
            // potential confusion, we return EOPNOTSUPP rather
            // EINVAL than in this case.
            Err(Errno::EOPNOTSUPP)
        }
        Err(errno) => Err(errno),
    }
}

/// Seals the memfd for writing.
pub fn seal_memfd<F: AsRawFd>(fd: &F) -> Result<(), Errno> {
    // Seal memory fd.
    fcntl(
        fd.as_raw_fd(),
        FcntlArg::F_ADD_SEALS(
            SealFlag::F_SEAL_SEAL
                | SealFlag::F_SEAL_WRITE
                | SealFlag::F_SEAL_SHRINK
                | SealFlag::F_SEAL_GROW,
        ),
    )
    .map(drop)
}

/// Sets or clears the non-blocking (O_NONBLOCK) flag on a file descriptor.
pub fn set_nonblock<Fd: AsRawFd>(fd: &Fd, state: bool) -> Result<(), Errno> {
    let fd = fd.as_raw_fd();
    let flags = fcntl(fd, FcntlArg::F_GETFL)?;

    let mut new_flags = flags;
    if state {
        new_flags |= OFlag::O_NONBLOCK.bits();
    } else {
        new_flags &= !OFlag::O_NONBLOCK.bits();
    }

    fcntl(fd, FcntlArg::F_SETFL(OFlag::from_bits_truncate(new_flags))).map(drop)
}

/// Sets or clears the close-on-exec (FD_CLOEXEC) flag on a file descriptor.
pub fn set_cloexec<Fd: AsRawFd>(fd: &Fd, state: bool) -> Result<(), Errno> {
    let fd = fd.as_raw_fd();
    let flags = fcntl(fd, FcntlArg::F_GETFD)?;

    let mut new_flags = flags;
    if state {
        new_flags |= FdFlag::FD_CLOEXEC.bits();
    } else {
        new_flags &= !FdFlag::FD_CLOEXEC.bits();
    }

    fcntl(fd, FcntlArg::F_SETFD(FdFlag::from_bits_truncate(new_flags))).map(drop)
}

/// Set pipe max size of the given pipe.
pub fn set_pipemax<Fd: AsRawFd>(fd: &Fd, size: nix::libc::c_int) -> Result<usize, Errno> {
    #[allow(clippy::cast_sign_loss)]
    fcntl(fd.as_raw_fd(), FcntlArg::F_SETPIPE_SZ(size)).map(|r| r as usize)
}

/// Initializes a new fanotify group.
///
/// This function wraps the `fanotify_init` system call.
///
/// # Arguments
///
/// * `flags` - The flags to control the behavior of the fanotify group.
/// * `event_f_flags` - The file descriptor flags for the event.
///
/// # Returns
///
/// A `Result` containing the file descriptor of the new fanotify group
/// if the operation was successful, or an `Err` containing the `Errno`
/// if it failed.
pub fn fanotify_init(
    flags: nix::libc::c_uint,
    event_f_flags: nix::libc::c_uint,
) -> Result<OwnedFd, Errno> {
    // SAFETY: The `fanotify_init` function is inherently unsafe because
    // it performs a system call that initializes a new fanotify group.
    // We ensure safety by:
    // - Passing valid `flags` and `event_f_flags` arguments as expected
    // by the system call.
    // - Checking the return value of `fanotify_init` for errors and
    // converting it to a safe `Result` type.
    let fd = unsafe { nix::libc::fanotify_init(flags, event_f_flags) };

    if fd >= 0 {
        // SAFETY: fanotify_init returns a valid FD.
        Ok(unsafe { OwnedFd::from_raw_fd(fd) })
    } else {
        Err(Errno::last())
    }
}

/// Marks a file or directory for fanotify.
///
/// This function wraps the `fanotify_mark` system call, always passing
/// a null pointer for the path.
///
/// # Arguments
///
/// * `notify_fd` - The fanotify file descriptor.
/// * `flags`     - The flags to control the behavior of the mark.
/// * `mask`      - The event mask to specify which events to notify on.
/// * `dirfd`     - The file descriptor of the file being marked.
///
/// # Returns
///
/// A `Result` which is `Ok` if the operation was successful, or an
/// `Err` containing the `Errno` if it failed.
pub fn fanotify_mark<F: AsRawFd, P: ?Sized + NixPath>(
    notify_fd: &F,
    flags: nix::libc::c_uint,
    mask: u64,
    dirfd: Option<RawFd>,
    path: Option<&P>,
) -> Result<(), Errno> {
    fn with_opt_nix_path<P, T, F>(p: Option<&P>, f: F) -> Result<T, Errno>
    where
        P: ?Sized + NixPath,
        F: FnOnce(*const nix::libc::c_char) -> T,
    {
        match p {
            Some(path) => path.with_nix_path(|p_str| f(p_str.as_ptr())),
            None => Ok(f(std::ptr::null())),
        }
    }

    // SAFETY: The `fanotify_mark` function is inherently unsafe
    // because it involves raw pointers and system-level operations.
    // We ensure safety by:
    // - Handling the optional `path` argument using the
    // `with_opt_nix_path` function.
    // - Using `dirfd.unwrap_or(libc::AT_FDCWD)` to provide a
    // default value for `dirfd` if it's `None`.
    // - Checking the return value of `fanotify_mark` for errors and
    // converting it to a safe `Result` type.
    let res = with_opt_nix_path(path, |p| unsafe {
        nix::libc::fanotify_mark(
            notify_fd.as_raw_fd(),
            flags,
            mask,
            dirfd.unwrap_or(nix::libc::AT_FDCWD),
            p,
        )
    })?;

    Errno::result(res).map(drop)
}

/// Wrapper for inotify_add_watch.
pub fn inotify_add_watch<F: AsRawFd, P: ?Sized + NixPath>(
    fd: &F,
    path: &P,
    mask: AddWatchFlags,
) -> Result<nix::libc::c_int, Errno> {
    // SAFETY: We need this because nix' `WatchDescriptor` is opaque...
    let res = path.with_nix_path(|cstr| unsafe {
        nix::libc::inotify_add_watch(fd.as_raw_fd(), cstr.as_ptr(), mask.bits())
    })?;

    Errno::result(res).map(|wd| wd as nix::libc::c_int)
}

/// Splits a given path into two `Cow<Path>`s: the parent path and the file name.
///
/// # Type Parameters
/// * `P` - A type that can be referenced as a `Path`.
///
/// # Arguments
/// * `path` - A path representing the path to split.
///
/// # Returns
/// A tuple of `(Cow<Path>, Cow<Path>)` where the first element is the parent path and the second is
/// the file name.
///
/// # Panics
/// This function expects normalized absolute path name and may misbehave/panic otherwise.
/// E.g: this function panics on empty path.
///
/// # Behavior
/// - 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.
#[allow(clippy::arithmetic_side_effects)]
pub fn path_split(path: &Path) -> (Cow<'_, Path>, Cow<'_, Path>) {
    let bytes = path.as_os_str().as_bytes();

    // Basic sanity checks.
    debug_assert!(
        !bytes.is_empty(),
        "path_split argument is empty, please submit a bug!"
    );
    if bytes.len() == 1 && bytes[0] == b'/' {
        // Special case for the root path.
        return (Cow::Borrowed(path), Cow::Borrowed(path));
    }

    // 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.
            Cow::Borrowed(Path::new("/"))
        } else {
            // Take everything up to the last non-trailing slash.
            Cow::Borrowed(Path::new(OsStr::from_bytes(&bytes[..idx])))
        };

        let filename_start = idx + 1;
        let filename_end = if has_trailing_slash {
            bytes.len()
        } else {
            effective_length
        };
        let filename_path = Cow::Borrowed(Path::new(OsStr::from_bytes(
            &bytes[filename_start..filename_end],
        )));

        return (parent_path, filename_path);
    }

    // If no slash is found, the whole thing is the filename
    unreachable!("path_split argument is relative, please submit a bug!");
}

/// Returns true if the given `Path` ends with a slash.
#[inline]
pub fn path_ends_with_slash<P: AsRef<Path>>(path: P) -> bool {
    path.as_ref().as_os_str().as_bytes().last() == Some(&b'/')
}

/// Checks if the given file mode represents a sidechannel device.
///
/// A sidechannel device is defined as a character or block device that
/// is world-readable or world-writable. This function checks the file
/// mode to determine if it meets these criteria.
///
/// # Arguments
///
/// * `mode` - The mode of the file to check.
#[inline]
pub fn is_sidechannel_device(mode: nix::libc::mode_t) -> bool {
    mode & (nix::libc::S_IROTH | nix::libc::S_IWOTH) != 0
        && matches!(
            mode & nix::libc::S_IFMT,
            nix::libc::S_IFCHR | nix::libc::S_IFBLK
        )
}

/// Resolve a symbolic link honouring magic proc links.
#[inline]
pub fn resolve_symlink(pid: Pid, path: &Path) -> Result<SmallPathBuf, Errno> {
    // SAFETY: Handle /proc/self and /proc/thread-self magic links
    match path.as_os_str().as_bytes() {
        b"/" => {
            // Special case, `/` is never a symlink.
            Err(Errno::EINVAL)
        }
        b"/proc/self" => {
            let mut b = itoa::Buffer::new();
            Ok(SmallPathBuf::from(b.format(pid.as_raw())))
        }
        b"/proc/thread-self" => {
            let mut b0 = itoa::Buffer::new();
            let mut b1 = itoa::Buffer::new();
            let mut p = SmallPathBuf::from(b0.format(proc_tgid(pid)?.as_raw()));
            p.push(Path::new("task"));
            p.push(Path::new(b1.format(pid.as_raw())));
            Ok(p)
        }
        _ => read_link(path),
    }
}

/// Converts a buffer and a length into a PathBuf.
fn wrap_readlink_result(
    mut buffer: SmallVec<[u8; PATH_MAX]>,
    length: nix::libc::ssize_t,
) -> SmallPathBuf {
    // SAFETY: We trust `length` as returned by the kernel.
    #[allow(clippy::cast_sign_loss)]
    unsafe {
        buffer.set_len(length as usize)
    };
    SmallPathBuf(buffer)
}

fn readlink_maybe_at<P: ?Sized + NixPath>(
    dirfd: Option<RawFd>,
    path: &P,
    buffer: &mut SmallVec<[u8; PATH_MAX]>,
) -> Result<nix::libc::ssize_t, Errno> {
    // SAFETY: nix' readlink is not efficient for our usecase.
    path.with_nix_path(|cstr| unsafe {
        let fd = dirfd.unwrap_or(nix::libc::AT_FDCWD);
        let res = nix::libc::readlinkat(
            fd,
            cstr.as_ptr(),
            buffer.as_mut_ptr() as *mut nix::libc::c_char,
            buffer.capacity() as nix::libc::size_t,
        );
        Errno::result(res)
    })?
}

fn inner_readlink<P: ?Sized + NixPath>(
    dirfd: Option<RawFd>,
    path: &P,
) -> Result<SmallPathBuf, Errno> {
    let mut buffer = SmallVec::new();
    let mut buffer_size = PATH_MAX;

    loop {
        #[allow(clippy::arithmetic_side_effects)]
        #[allow(clippy::cast_sign_loss)]
        match readlink_maybe_at(dirfd, path, &mut buffer) {
            Ok(len) if (len as usize) < buffer.capacity() => {
                return Ok(wrap_readlink_result(buffer, len));
            }
            Ok(_) => {
                // Increase buffer size and retry.
                let grow_size = buffer_size
                    .checked_add(PATH_MIN)
                    .ok_or(Errno::ENAMETOOLONG)?;
                if grow_size > (isize::MAX / 2) as usize {
                    return Err(Errno::ENAMETOOLONG);
                }
                buffer
                    .try_reserve_exact(grow_size - buffer_size)
                    .or(Err(Errno::ENOMEM))?;
                buffer_size = grow_size;
            }
            Err(Errno::EINTR) => {} // retry.
            Err(e) => return Err(e),
        }
    }
}

/// Resolve a symbolic link.
pub fn read_link(path: &Path) -> Result<SmallPathBuf, Errno> {
    inner_readlink(None, path)
}

/// Resolve a symbolic link relative to a file descriptor.
pub fn read_linkat<Fd: AsRawFd>(dirfd: Option<&Fd>, path: &Path) -> Result<SmallPathBuf, Errno> {
    inner_readlink(dirfd.map(|fd| fd.as_raw_fd()), path)
}

/// Check if a path exists.
#[inline]
pub fn path_exists(path: &Path) -> Result<(), Errno> {
    retry_on_eintr(|| {
        statx(
            None,
            path,
            nix::libc::AT_SYMLINK_NOFOLLOW as nix::libc::c_uint,
            0,
        )
    })
    .map(drop)
}

/// Checks if a path is a directory.
#[inline]
pub fn path_isdir(path: &Path) -> Result<(), Errno> {
    let stat = retry_on_eintr(|| {
        statx(
            None,
            path,
            nix::libc::AT_SYMLINK_NOFOLLOW as nix::libc::c_uint,
            STATX_TYPE,
        )
    })?;

    #[allow(clippy::cast_possible_truncation)]
    if (stat.stx_mode & nix::libc::S_IFMT as u16) == nix::libc::S_IFDIR as u16 {
        Ok(())
    } else {
        Err(Errno::ENOTDIR)
    }
}

/// Checks if a given 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.
///
/// # Arguments
///
/// * `path`: A reference to a `Box<CStr>` representing the path.
///
/// # Returns
///
/// * `bool`: Returns `true` if the path ends with a dot component, otherwise `false`.
///
/// # Examples
///
/// ```ignore
/// use std::ffi::CString;
/// assert_eq!(path_ends_with_dot(&Box::new(CString::new("some/path/.").unwrap())), true);
/// assert_eq!(path_ends_with_dot(&Box::new(CString::new("some/path/foo").unwrap())), false);
/// assert_eq!(path_ends_with_dot(&Box::new(CString::new("some/path/./").unwrap())), true);
/// assert_eq!(path_ends_with_dot(&Box::new(CString::new("some/path/././././///").unwrap())), true);
/// ```
#[allow(clippy::arithmetic_side_effects)]
#[allow(clippy::if_same_then_else)]
#[inline]
pub(crate) fn path_ends_with_dot(path: &Path) -> bool {
    let bytes = path.as_os_str().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
}

/// A safe version of clone that returns a PidFD,
/// and therefore is not subject to PID-recycling
/// races.
pub fn safe_clone(
    mut cb: CloneCb,
    stack: &mut [u8],
    flags: nix::libc::c_int,
    signal: Option<nix::libc::c_int>,
) -> Result<OwnedFd, Errno> {
    #[allow(clippy::cast_possible_truncation)]
    extern "C" fn callback(data: *mut CloneCb) -> nix::libc::c_int {
        // SAFETY: nix' version does not support CLONE_PIDFD.
        let cb: &mut CloneCb = unsafe { &mut *data };
        (*cb)() as nix::libc::c_int
    }

    let mut pid_fd: nix::libc::c_int = -1;
    let combined: nix::libc::c_int = flags | nix::libc::CLONE_PIDFD | signal.unwrap_or(0);
    // SAFETY: ditto.
    #[allow(clippy::missing_transmute_annotations)]
    let res = unsafe {
        let ptr = stack.as_mut_ptr().add(stack.len());
        let ptr_aligned = ptr.sub(ptr as usize % 16);
        nix::libc::clone(
            std::mem::transmute(callback as extern "C" fn(*mut Box<dyn FnMut() -> isize>) -> i32),
            ptr_aligned as *mut nix::libc::c_void,
            combined,
            std::ptr::addr_of_mut!(cb) as *mut nix::libc::c_void,
            &mut pid_fd,
        )
    };

    Errno::result(res).map(|_| {
        // SAFETY: clone with CLONE_PIDFD returns a valid FD.
        unsafe { OwnedFd::from_raw_fd(pid_fd) }
    })
}

/// Ensure the file we open is a regular file.
/// Ensure we do not block on a fifo if the file is one.
/// Returns (File, FileStatx) on success, Errno on failure.
pub fn safe_open_file(path: &Path) -> Result<(File, FileStatx), Errno> {
    // Open the file in read-only and non-blocking mode.
    // This way we ensure we don't block even if the file is a FIFO.
    let fd = safe_open(
        path,
        OFlag::O_RDONLY | OFlag::O_NONBLOCK | OFlag::O_CLOEXEC | OFlag::O_NOCTTY,
    )
    .map(File::from)?;

    // Check the file type and bail if it's not a regular file.
    let statx = retry_on_eintr(|| fstatx(fd.as_raw_fd(), STATX_INO | STATX_TYPE | STATX_SIZE))?;
    if !is_regular_file(&statx) {
        return Err(Errno::ENOEXEC);
    }

    // Change the file descriptor to blocking mode
    let cur = fcntl(fd.as_raw_fd(), FcntlArg::F_GETFL)?;
    let new = OFlag::from_bits_truncate(cur) & !OFlag::O_NONBLOCK;
    fcntl(fd.as_raw_fd(), FcntlArg::F_SETFL(new))?;

    // Return the File and FileStatx.
    Ok((fd, statx))
}

// Return a safe OpenHow structure.
#[inline]
pub(crate) fn safe_open_how(flags: OFlag) -> OpenHow {
    OpenHow::new()
        .flags(flags | OFlag::O_CLOEXEC | OFlag::O_NOFOLLOW)
        .resolve(ResolveFlag::RESOLVE_NO_MAGICLINKS | ResolveFlag::RESOLVE_NO_SYMLINKS)
}

// Open a path safely using O_PATH and return an OwnedFd.
pub(crate) fn safe_open_path(path: &Path, flags: OFlag) -> Result<OwnedFd, Errno> {
    safe_open(path, OFlag::O_PATH | flags)
}

// Open a path safely and return an OwnedFd.
pub(crate) fn safe_open(path: &Path, flags: OFlag) -> Result<OwnedFd, Errno> {
    let how = safe_open_how(flags);
    retry_on_eintr(|| {
        openat2(nix::libc::AT_FDCWD, path, how).map(|fd|
                // SAFETY: openat2 returns a valid FD.
                unsafe { OwnedFd::from_raw_fd(fd) })
    })
}

/// Get an extended attribute value.
///
/// # Arguments
/// - `fd`    - The file descriptor of the file.
/// - `name`  - The name of the extended attribute.
/// - `value` - The buffer to store the attribute value,
///             if None do a check run.
///
/// # Returns
/// - `Result<usize>` - The size of the extended attribute value.
///
/// # Errors
/// Returns an error if the operation fails.
///
/// # Safety
/// This function is unsafe as it directly interfaces with the libc function `fgetxattr`.
///
/// # See Also
/// [`fgetxattr`](https://man7.org/linux/man-pages/man2/fgetxattr.2.html)
pub fn fgetxattr<F: AsRawFd, P: ?Sized + NixPath>(
    fd: &F,
    name: &P,
    value: Option<&mut [u8]>,
) -> Result<usize, Errno> {
    let (value, len) = match value {
        Some(v) => (
            v.as_mut_ptr() as *mut nix::libc::c_void,
            v.len() as nix::libc::size_t,
        ),
        None => (std::ptr::null_mut(), 0),
    };

    // SAFETY: nix lacks a wrapper for fgetxattr.
    let res = name.with_nix_path(|name_ptr| unsafe {
        nix::libc::fgetxattr(fd.as_raw_fd(), name_ptr.as_ptr(), value, len)
    })?;

    #[allow(clippy::cast_sign_loss)]
    if res == -1 {
        Err(Errno::last())
    } else {
        Ok(res as usize)
    }
}

/// Check if the given FD has POSIX ACLs defined.
pub fn fhasposixacl<F: AsRawFd>(fd: &F) -> bool {
    match fgetxattr(fd, "system.posix_acl_default", None) {
        Err(Errno::ENODATA) => fgetxattr(fd, "system.posix_acl_access", None).is_ok(),
        Ok(_) => true,
        Err(_) => false,
    }
}

/// Set an extended attribute value.
///
/// # Arguments
/// - `fd`    - The file descriptor of the file.
/// - `name`  - The name of the extended attribute.
/// - `value` - The buffer containing the attribute value.
/// - `flags` - Flags to control the operation.
///
/// # Returns
/// - `Result<()>` - Returns an Ok result if the operation succeeds.
///
/// # Errors
/// Returns an error if the operation fails.
///
/// # Safety
/// This function is unsafe as it directly interfaces with the libc function `fsetxattr`.
///
/// # See Also
/// [`fsetxattr`](https://man7.org/linux/man-pages/man2/fsetxattr.2.html)
pub fn fsetxattr<F: AsRawFd, P: ?Sized + NixPath>(
    fd: &F,
    name: &P,
    value: &[u8],
    flags: i32,
) -> Result<(), Errno> {
    // SAFETY: nix lacks a wrapper for fsetxattr.
    let res = name.with_nix_path(|name_ptr| unsafe {
        nix::libc::fsetxattr(
            fd.as_raw_fd(),
            name_ptr.as_ptr(),
            value.as_ptr() as *const nix::libc::c_void,
            value.len() as nix::libc::size_t,
            flags as nix::libc::c_int,
        )
    })?;

    Errno::result(res).map(drop)
}

/// Remove an extended attribute value.
///
/// # Arguments
/// - `fd`    - The file descriptor of the file.
/// - `name`  - The name of the extended attribute.
///
/// # Returns
/// - `Result<()>` - Returns an Ok result if the operation succeeds.
///
/// # Errors
/// Returns an error if the operation fails.
///
/// # Safety
/// This function is unsafe as it directly interfaces with the libc function `fremovexattr`.
///
/// # See Also
/// [`fremovexattr`](https://man7.org/linux/man-pages/man2/fremovexattr.2.html)
pub fn fremovexattr<F: AsRawFd, P: ?Sized + NixPath>(fd: &F, name: &P) -> Result<(), Errno> {
    // SAFETY: nix lacks a wrapper for fremovexattr.
    let res = name.with_nix_path(|name_ptr| unsafe {
        nix::libc::fremovexattr(fd.as_raw_fd(), name_ptr.as_ptr())
    })?;

    Errno::result(res).map(drop)
}

/// Deny access to user.syd* extended attributes.
/// # Safety
/// Dereferences name after a NULL check.
/// If name is not NULL, it must be a valid NUL-terminated C-String.
/// # Security
/// Denies with ENODATA for stealth.
pub unsafe fn denyxattr(name: *const nix::libc::c_char) -> Result<(), Errno> {
    const SYD_XATTR: &[u8] = b"user.syd.";

    if name.is_null() {
        Ok(())
    } else {
        // SAFETY: The pointer from CStr is guaranteed to be valid and
        // null-terminated.
        if unsafe {
            nix::libc::strncmp(
                name,
                SYD_XATTR.as_ptr() as *const nix::libc::c_char,
                SYD_XATTR.len(),
            )
        } == 0
        {
            Err(Errno::ENODATA)
        } else {
            Ok(())
        }
    }
}

/// Filters out extended attribute names that start with "user.syd".
///
/// # Arguments
///
/// * `buf` - A buffer containing the extended attribute names as
///           null-terminated strings.
/// * `n` - The length of valid data in the buffer.
pub fn filterxattr(buf: &[u8], n: usize) -> Result<Vec<u8>, Errno> {
    let mut filtered_buf = Vec::new();
    let mut start = 0;

    #[allow(clippy::arithmetic_side_effects)]
    while start < n {
        if let Some(end) = buf[start..].iter().position(|&c| c == 0) {
            let name = &buf[start..start + end + 1]; // +1 to include the null terminator
            let cstr = CStr::from_bytes_with_nul(name).or(Err(Errno::E2BIG))?;
            if !cstr.to_bytes().starts_with(b"user.syd.") {
                filtered_buf.extend_from_slice(name);
            }
            start += end + 1;
        } else {
            break;
        }
    }

    Ok(filtered_buf)
}

/// Get secure bytes using the OS random number generator.
pub fn getrandom(size: usize) -> Result<Vec<u8>, Errno> {
    let mut buf = vec![0u8; size];
    fillrandom(&mut buf)?;
    Ok(buf)
}

/// Fill the given buffer using the OS random number generator.
pub fn fillrandom(buf: &mut [u8]) -> Result<(), Errno> {
    let size = buf.len();
    let mut tread = 0;

    #[allow(clippy::arithmetic_side_effects)]
    #[allow(clippy::cast_sign_loss)]
    while tread < size {
        // SAFETY: nix has no wrapper for getrandom yet!
        match Errno::result(unsafe {
            nix::libc::getrandom(
                buf.as_mut_ptr().add(tread) as *mut nix::libc::c_void,
                size - tread,
                nix::libc::GRND_RANDOM,
            )
        }) {
            Ok(nread) => tread += nread as usize,
            Err(Errno::EINTR) => {} // Interrupted, try again.
            Err(errno) => return Err(errno),
        }
    }

    Ok(())
}

/// Check if the given File is executable.
pub(crate) fn is_executable(file: &File) -> bool {
    // SAFETY: Our nix version does not have AtFlags::AT_EACCES (TODO).
    match SmallPathBuf::from("").with_nix_path(|cstr| unsafe {
        nix::libc::faccessat(
            file.as_raw_fd(),
            cstr.as_ptr(),
            nix::libc::X_OK,
            nix::libc::AT_EACCESS | nix::libc::AT_EMPTY_PATH,
        )
    }) {
        Ok(res) => Errno::result(res).map(drop).is_ok(),
        Err(_) => false,
    }
}

/// Check if the given metadata points to a regular file.
#[inline]
pub fn is_regular_file(stat: &FileStatx) -> bool {
    (u32::from(stat.stx_mode) & SFlag::S_IFMT.bits()) == SFlag::S_IFREG.bits()
}

/// Parse a FD from a Path.
pub(crate) fn parse_fd<P: AsRef<Path>>(path: P) -> Result<RawFd, Errno> {
    let bytes = path.as_ref().as_os_str().as_bytes();

    // Parsing bytes directly to integer
    let mut fd: RawFd = 0;
    for &b in bytes {
        // Check if the byte is a valid digit
        if !b.is_ascii_digit() {
            return Err(Errno::EBADF);
        }
        fd = fd
            .saturating_mul(10)
            .saturating_add(RawFd::from(b.saturating_sub(b'0')));
    }

    Ok(fd)
}

/// Check if a path is `.' or `..'
#[inline]
pub(crate) fn path_is_dot(path: &Path) -> bool {
    let path = path.as_os_str().as_bytes();
    match path.len() {
        1 if path[0] == b'.' => true,
        2 if path[0] == b'.' && path[1] == b'.' => true,
        _ => false,
    }
}

/// Create file and write the given content.
pub fn cat<P: AsRef<Path>, T: AsRef<[u8]>>(path: P, content: T) -> std::io::Result<()> {
    let mut file = File::create(path)?;
    file.write_all(content.as_ref())?;
    Ok(())
}

/// Make a file executable.
pub fn chmod_x<P: AsRef<Path>>(path: P) -> std::io::Result<()> {
    // Set permissions to make path executable.
    let metadata = metadata(path.as_ref())?;
    let mut permissions = metadata.permissions();
    permissions.set_mode(0o700); // This sets the file executable for the owner (rwx).
    set_permissions(path.as_ref(), permissions)
}

/// Masks all control characters in a path.
///
/// This function takes a path, converts it to a string using the
/// `Display` trait, and replaces any control characters with the '?'
/// character. This is useful for sanitizing paths that might contain
/// control characters which could affect logging systems or terminal
/// output.
///
/// # Arguments
///
/// * `path` - The path to be sanitized, which can be any type that
///            implements `AsRef<Path>`.
///
/// # Returns
///
/// Returns a `String` where all control characters have been replaced
/// by '?'.
pub fn mask_path(path: &Path) -> String {
    path.to_string_lossy()
        .chars()
        .map(|c| if c.is_control() { '?' } else { c })
        .collect()
}

/// Determines if a path contains any Unicode control characters.
///
/// This function checks a path for the presence of control characters
/// as specified in the Unicode standard (U+0000 to U+001F and U+007F to
/// U+009F). It operates directly on the bytes of the path, ensuring
/// efficient processing without converting to a string. This is
/// particularly useful for validating paths that are intended for
/// display or logging, where control characters could cause formatting
/// issues or security vulnerabilities.
///
/// # Arguments
///
/// * `path` - A reference to a path, which can be any type that
///            implements `AsRef<Path>`.
///
/// # Returns
///
/// Returns `true` if the path contains any control characters,
/// otherwise `false`.
#[inline]
pub fn path_has_control_chars(path: &Path) -> bool {
    path.as_os_str()
        .as_bytes()
        .iter()
        .any(|&b| (b <= 0x1F || (0x7F..=0x9F).contains(&b)))
}

#[derive(Debug, PartialEq)]
enum PathComponent<'a> {
    //We handle {Root,Cur}Dir transparently for efficiency.
    //RootDir,
    //CurDir,
    ParentDir,
    Normal(&'a OsStr),
    NormalOwned(OsString),
}

/// Detects unsafe paths (and unsafe debug paths if !debug).
/// These paths are denied access regardless of the state of sandboxing.
#[inline(always)]
pub fn path_unsafe(path: &Path, debug: bool) -> bool {
    let path = path.as_os_str().as_bytes();

    // Notes about paths:
    // /proc/cmdline: We return an empty file for compat. (see: #106)
    // /proc/{kallsyms,modules,version}: Required for perf.
    // /sys/kernel/notes: https://lwn.net/Articles/962782/
    // /sys/devices/system/cpu/cpu*/cpufreq/scaling_cur_freq: https://arxiv.org/pdf/2404.10715
    if is_prefix(path, b"/dev/") {
        const IDX: usize = b"/dev/".len();
        is_prefix(&path[IDX..], b"cpu/")
            || is_suffix(path, b"/kmem")
            || is_suffix(path, b"/mem")
            || is_suffix(path, b"/port")
    } else if is_prefix(path, b"/proc/") {
        const IDX: usize = b"/proc/".len();
        (!debug
            && (is_suffix(path, b"/mem")
                || is_suffix(path, b"/maps")
                || is_suffix(path, b"/auxv")
                || is_suffix(path, b"/environ")
                || is_suffix(path, b"/stack")
                || is_suffix(path, b"/syscall")
                || is_suffix(path, b"/pagemap")
                || is_suffix(path, b"/personality")
                || is_suffix(path, b"/wchan"))
            || is_suffix(path, b"/kallsyms")
            || is_suffix(path, b"/modules")
            || is_suffix(path, b"/version"))
            || is_suffix(path, b"/config")
            || is_suffix(path, b"/config.gz")
            || is_suffix(path, b"/dma")
            || is_prefix(&path[IDX..], b"fs/")
            || is_suffix(path, b"/interrupts")
            || is_suffix(path, b"/iomem")
            || is_suffix(path, b"/ioports")
            || is_prefix(&path[IDX..], b"irq/")
            || is_suffix(path, b"/kcore")
            || is_suffix(path, b"/key-users")
            || is_suffix(path, b"/keys")
            || is_suffix(path, b"/mtrr")
            || is_suffix(path, b"/partitions")
            || is_suffix(path, b"/softirqs")
            || is_suffix(path, b"/swaps")
            || is_suffix(path, b"/sysrq-trigger")
            || is_prefix(&path[IDX..], b"tty/")
            || is_suffix(path, b"/vmallocinfo")
    } else if is_prefix(path, b"/sys/devices/") {
        const IDX: usize = b"/sys/devices/".len();
        is_suffix(&path[IDX..], b"/scaling_cur_freq")
    } else if is_prefix(path, b"/sys/kernel/") {
        const IDX: usize = b"/sys/kernel/".len();
        is_prefix(&path[IDX..], b"boot_params/")
            || is_prefix(&path[IDX..], b"irq/")
            || is_prefix(&path[IDX..], b"slab/")
            || is_suffix(path, b"/vmcoreinfo")
            || (!debug
                && (is_suffix(path, b"/notes")
                    || is_prefix(&path[IDX..], b"debug/")
                    || is_prefix(&path[IDX..], b"tracing/")))
    } else {
        false
    }
}

/// Generates a VecDeque of path components, optimized for performance and correctness.
/// - Assumes handling of the initial RootDir is done by the caller.
/// - Directly walks the path bytes to construct components, efficiently skipping multiple leading slashes.
/// - Returns an additional bool to check for trailing slash.
#[inline]
fn path_components(path: &Path) -> (VecDeque<PathComponent<'_>>, bool) {
    let path_bytes = path.as_os_str().as_bytes();
    let mut components = VecDeque::with_capacity(16);

    // Create a new searcher for the '/' character.
    let searcher = memchr::arch::all::memchr::One::new(b'/');

    let mut start = 0;
    #[allow(clippy::arithmetic_side_effects)]
    for pos in searcher.iter(path_bytes) {
        if pos > start {
            match &path_bytes[start..pos] {
                b"." => {} // Skip current directory component.
                b".." => components.push_back(PathComponent::ParentDir),
                component => {
                    components.push_back(PathComponent::Normal(OsStr::from_bytes(component)))
                }
            }
        }
        start = pos + 1; // Move past the '/'.
    }

    // Handle the last component if it's not ended by a slash.
    #[allow(clippy::arithmetic_side_effects)]
    let slash = if start < path_bytes.len() {
        match &path_bytes[start..] {
            b"." => {
                // Skip current directory component.
                !components.is_empty() && start >= 1 && path_bytes[start - 1] == b'/'
            }
            b".." => {
                components.push_back(PathComponent::ParentDir);
                false
            }
            component => {
                components.push_back(PathComponent::Normal(OsStr::from_bytes(component)));
                false
            }
        }
    } else {
        true
    };

    (components, slash)
}

/// Adds to a VecDeque of (owning) path components, optimized for performance and correctness.
/// - Assumes handling of the initial RootDir is done by the caller.
/// - Directly walks the path bytes to construct components, efficiently skipping multiple leading slashes.
#[inline]
fn path_components2(path: &Path, components: &mut VecDeque<PathComponent<'_>>) {
    let path_bytes = path.as_os_str().as_bytes();

    // Create a new searcher for the '/' character.
    let searcher = memchr::arch::all::memchr::One::new(b'/');

    let mut last_pos = path_bytes.len();
    #[allow(clippy::arithmetic_side_effects)]
    for pos in searcher.iter(path_bytes).rev() {
        match &path_bytes[pos + 1..last_pos] {
            b"" | b"." => {} // Skip current directory and empty components.
            b".." => components.push_front(PathComponent::ParentDir),
            component => components.push_front(PathComponent::NormalOwned(OsString::from_vec(
                component.to_vec(),
            ))),
        }
        last_pos = pos;
    }

    // Handle the remaining component before the first slash (or the
    // only component if no slashes).
    match &path_bytes[..last_pos] {
        b"" | b"." => {} // Skip current directory and empty components.
        b".." => components.push_front(PathComponent::ParentDir),
        component => components.push_front(PathComponent::NormalOwned(OsString::from_vec(
            component.to_vec(),
        ))),
    }
}

/// Return the canonical, absolute form of a path
///
/// This function is a generalization of [`std::fs::canonicalize`] that
/// allows controlling how symbolic links are resolved and how to deal
/// with missing components. It returns the canonical, absolute form of
/// a path.
///
/// The `miss_mode` parameter controls how missing components are handled.
///
/// The `resolve` is a boolean parameter which controls whether
/// the last component should be resolved or not. Remaining components
/// are always resolved.
///
/// The `nodotdot` is a boolean parameter which controls whether paths
/// with .. component in them should be denied.
///
/// The `debug` is a boolean parameter which controls whether the
/// unsafe path check should allow access to debugging paths such
/// as /proc/kallsyms, /proc/self/m{aps,mem} etc. (required for
/// debugging/tracing tools such as strace, perf, gdb...)
#[allow(clippy::cognitive_complexity)]
#[allow(clippy::too_many_arguments)]
pub fn canonicalize(
    pid: Pid,
    path: &Path,
    cwd: Option<&Path>,
    miss_mode: MissingHandling,
    resolve: bool,
    nodotdot: bool,
    debug: bool,
) -> Result<SmallPathBuf, Errno> {
    let mut result = SmallPathBuf::new();

    if path.is_relative() {
        if let Some(cwd) = cwd {
            result.push(cwd);
        } else if path.is_empty() {
            return Err(Errno::ENOENT);
        } else {
            return Err(Errno::EINVAL);
        }
    } else {
        result.push(Path::new("/"));
    };

    // Used to skip FD parsing for proc_fd.
    #[allow(clippy::arithmetic_side_effects)]
    let pneg = Pid::from_raw(-pid.as_raw());

    // Symbolic Link Loop Detection.
    const SYMLINKS_TO_LOOK_FOR_LOOPS: u8 = 16;
    let mut followed_symlinks = 0;
    let mut visited_files: Option<FxHashSet<FileInformation>> = None;

    let (mut parts, has_to_be_directory) = path_components(path);
    while let Some(part) = parts.pop_front() {
        match part {
            PathComponent::Normal(p) => result.push(Path::new(p)),
            PathComponent::NormalOwned(p) => {
                result.push(Path::new(&p));
            }
            PathComponent::ParentDir => {
                if nodotdot {
                    return Err(Errno::EACCES);
                }
                result.pop();
                if result.as_os_str().as_bytes() == b"/" {
                    // RootDir as part of walking up path of an earlier symlink.
                    continue;
                }
            }
        }

        // We're resolving symbolic links for all path components but last.
        // The resolution of the last component depends on the `resolve` argument.
        if !has_to_be_directory && !resolve && parts.is_empty() {
            break; // Nothing left to do, break out.
        }

        match resolve_symlink(pid, result.as_ref()) {
            Ok(link_path) => {
                let rel = link_path.is_relative();

                // SAFETY: NO_MAGICLINKS
                if proc_fd(pneg, result.as_ref())?.is_some()
                    && (rel || link_path.as_os_str().as_bytes().ends_with(b" (deleted)"))
                {
                    // (a) pipe:42 socket:42 etc, special paths.
                    // (b) file descriptors that point to deleted paths.
                    continue;
                }

                //// Symbolic Link Loop Detection.
                // SAFETY: SYMLINKS_TO_LOOK_FOR_LOOPS is much smaller than u8::MAX.
                #[allow(clippy::arithmetic_side_effects)]
                if followed_symlinks < SYMLINKS_TO_LOOK_FOR_LOOPS {
                    followed_symlinks += 1;
                } else {
                    let stat = FileInformation::from_link(&result)?;
                    if let Some(ref mut visited_files) = visited_files {
                        if !visited_files.insert(stat) {
                            return Err(Errno::ELOOP);
                        }
                    } else {
                        // Allocate the FxHashSet only when it's really necessary.
                        visited_files = Some(FxHashSet::from_iter([stat]));
                    }
                }
                ////

                path_components2(link_path.as_ref(), &mut parts);
                if rel {
                    result.pop();
                } else {
                    // RootDir as part of resolving a symlink to an absolute path!
                    result.push(Path::new("/"));
                }
            }
            Err(Errno::EINVAL) => {
                // File is not a symbolic link, continue.
            }
            Err(errno) => match miss_mode {
                MissingHandling::Existing => return Err(errno),
                MissingHandling::Normal if !parts.is_empty() => return Err(errno),
                _ => {}
            },
        }
    }

    // Deny access to critical paths.
    if path_unsafe(result.as_ref(), debug) {
        // SAFETY: Deny with ENOENT for stealth.
        return Err(Errno::ENOENT);
    }

    // Raise ENOTDIR or ENOENT as necessary.
    match miss_mode {
        MissingHandling::Existing => {
            // Last component must exist, return error if it does not.
            if has_to_be_directory {
                path_isdir(result.as_ref())?;
            } else {
                path_exists(result.as_ref())?;
            }
        }
        MissingHandling::Normal if has_to_be_directory => {
            if let Err(Errno::ENOTDIR) = path_isdir(result.as_ref()) {
                return Err(Errno::ENOTDIR);
            }
        }
        // SAFETY: No check for missing because EEXIST return
        // value may be misused to identify hidden paths.
        _ => {}
    }

    // Preserve trailing slash as necessary
    if has_to_be_directory {
        result.push(Path::new(""));
    }

    Ok(result)
}

#[cfg(test)]
mod tests {
    use std::{
        fs::{self, OpenOptions},
        os::{
            fd::AsRawFd,
            unix::fs::{symlink, OpenOptionsExt},
        },
        process::Command,
        thread::sleep,
        time::{Duration, SystemTime},
    };

    use nix::unistd::getpid;

    use super::{MissingHandling::*, *};

    type TestResult = Result<(), Box<dyn std::error::Error>>;

    // A helper function to get the current atime of a file
    fn get_atime<P: AsRef<Path>>(path: P) -> SystemTime {
        let metadata = fs::metadata(path).expect("Failed to get metadata");
        metadata.accessed().expect("Failed to get accessed time")
    }

    // Helper function to assert that the atime of a file or directory has not changed
    fn assert_atime_unchanged<P: AsRef<Path>, F>(path: P, func: F)
    where
        F: FnOnce() -> Result<SmallPathBuf, Errno>,
    {
        let original_atime_f = get_atime(&path);
        let original_atime_p = get_atime(path.as_ref().parent().unwrap());
        sleep(Duration::from_secs(7));
        assert!(
            func().is_ok(),
            "canonicalize {} failed",
            path.as_ref().display()
        );
        // We allow a 1-second tolerance since some filesystems do not have nanosecond precision.
        let new_atime_f = get_atime(&path);
        let new_atime_p = get_atime(path.as_ref().parent().unwrap());
        assert!(
            new_atime_f <= original_atime_f + Duration::new(1, 0),
            "The atime of the file should not have significantly changed."
        );
        assert!(
            new_atime_p <= original_atime_p + Duration::new(1, 0),
            "The atime of the parent dir should not have significantly changed."
        );
    }

    // std::fs::remove_dir_all stops on the first error.
    // we need something more forceful.
    fn remove_dir_all<P: AsRef<Path>>(path: P) -> std::io::Result<()> {
        let status = Command::new("rm")
            .arg("-rf")
            .arg(path.as_ref().to_string_lossy().to_string())
            .status()?;
        if status.success() {
            Ok(())
        } else {
            Err(std::io::Error::new(
                std::io::ErrorKind::Other,
                format!("Failed to remove directory: {}", path.as_ref().display()),
            ))
        }
    }

    // Helper function to create a symlink chain that eventually loops back to an earlier link
    fn setup_symlink_loop(tmp_dir: &tempfile::TempDir, links: &[(&str, &str)]) {
        for &(src, dst) in links {
            let src_path = tmp_dir.path().join(src);

            // Check and create parent directory for the source if necessary
            if let Some(parent) = src_path.parent() {
                if !parent.exists() {
                    fs::create_dir_all(parent).unwrap();
                }
            }

            // The destination is given relative to the source
            let dst_path = Path::new(dst);

            // Avoid creating a symlink if the source already exists
            if src_path.exists() {
                fs::remove_file(&src_path).unwrap();
            }

            // If the destination is an absolute path or starts with '/', we strip the '/' and prefix with tmp_dir
            let full_dst_path = if dst_path.is_absolute() {
                tmp_dir.path().join(dst_path.strip_prefix("/").unwrap())
            } else {
                src_path.parent().unwrap().join(dst_path)
            };

            // Create parent directories for the destination if they don't exist
            if let Some(parent) = full_dst_path.parent() {
                if !parent.exists() {
                    fs::create_dir_all(parent).unwrap();
                }
            }

            // Create the symlink
            symlink(&full_dst_path, &src_path).expect(&format!(
                "Unable to symlink {} -> {}",
                src_path.display(),
                full_dst_path.display()
            ));
        }
    }

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

    const ENDS_WITH_DOT_TESTS: [EndsWithDotTestCase; 10] = [
        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,
        },
    ];

    fn tempdir() -> Result<PathBuf, Box<dyn std::error::Error>> {
        let path = Path::new(".syd-test");
        std::fs::create_dir_all(path)?;
        let _ = OpenOptions::new()
            .write(true)
            .create(true)
            .mode(0o600)
            .open(path.join("test"))?;
        Ok(path.to_path_buf())
    }

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

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

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

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

        let mut path = SmallPathBuf::from("/tmp/bar");
        path.push(Path::new("baz/"));
        assert_eq!(path, SmallPathBuf::from("/tmp/bar/baz/"));

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

    #[test]
    fn test_path_split() {
        // Test typical path without trailing slash
        let path = PathBuf::from("/foo/bar/baz");
        let (parent, file_name) = path_split(&path);
        assert_eq!(parent, PathBuf::from("/foo/bar"));
        assert_eq!(file_name, PathBuf::from("baz"));

        // Test path with trailing slash
        let path = PathBuf::from("/foo/bar/baz/");
        let (parent, file_name) = path_split(&path);
        assert_eq!(parent, PathBuf::from("/foo/bar"));
        assert_eq!(file_name, PathBuf::from("baz/"));

        // Test root path "/"
        let path = PathBuf::from("/");
        let (parent, file_name) = path_split(&path);
        assert_eq!(parent, PathBuf::from("/"));
        assert_eq!(file_name, PathBuf::from("/"));

        // Test single level path without trailing slash
        let path = PathBuf::from("/foo");
        let (parent, file_name) = path_split(&path);
        assert_eq!(parent, PathBuf::from("/"));
        assert_eq!(file_name, PathBuf::from("foo"));

        // Test single level path with trailing slash
        let path = PathBuf::from("/foo/");
        let (parent, file_name) = path_split(&path);
        assert_eq!(parent, PathBuf::from("/"));
        assert_eq!(file_name, PathBuf::from("foo/"));
    }

    #[test]
    fn test_canonicalize_empty_path() -> TestResult {
        assert_eq!(
            canonicalize(Pid::this(), Path::new(""), None, Normal, true, false, false,),
            Err(Errno::ENOENT)
        );
        assert_eq!(
            canonicalize(
                Pid::this(),
                Path::new(""),
                None,
                Existing,
                true,
                false,
                false,
            ),
            Err(Errno::ENOENT)
        );
        assert_eq!(
            canonicalize(
                Pid::this(),
                Path::new(""),
                None,
                Missing,
                true,
                false,
                false,
            ),
            Err(Errno::ENOENT)
        );
        assert_eq!(
            canonicalize(
                Pid::this(),
                Path::new(""),
                None,
                Normal,
                false,
                false,
                false,
            ),
            Err(Errno::ENOENT)
        );
        assert_eq!(
            canonicalize(
                Pid::this(),
                Path::new(""),
                None,
                Existing,
                false,
                false,
                false,
            ),
            Err(Errno::ENOENT)
        );
        assert_eq!(
            canonicalize(
                Pid::this(),
                Path::new(""),
                None,
                Missing,
                false,
                false,
                false,
            ),
            Err(Errno::ENOENT)
        );

        Ok(())
    }

    #[test]
    fn test_canonicalize_repetitive_slashes() -> TestResult {
        let result_test = canonicalize(
            Pid::this(),
            Path::new("/etc/passwd"),
            None,
            Normal,
            true,
            false,
            false,
        )?;
        let paths = vec![
            "/etc/passwd",
            "/etc//passwd",
            "/etc///passwd",
            "//etc/passwd",
            "//etc//passwd",
            "//etc///passwd",
            "///etc/passwd",
            "///etc//passwd",
            "///etc///passwd",
        ];
        for path in &paths {
            let result = canonicalize(
                Pid::this(),
                Path::new(path),
                None,
                Normal,
                true,
                false,
                false,
            )?;
            assert_eq!(result, result_test);
        }

        Ok(())
    }

    #[test]
    fn test_canonicalize_dots_slashes() -> TestResult {
        let base = tempdir()?;

        let cwd = std::env::current_dir()?.display().to_string();
        let path = base.display().to_string();

        let result1 = canonicalize(
            Pid::this(),
            Path::new(&format!("{path}//./..//{path}/test")),
            Some(Path::new(&cwd)),
            Normal,
            true,
            false,
            false,
        )?
        .display()
        .to_string();
        let result2 = canonicalize(
            Pid::this(),
            Path::new(&format!("{cwd}/{path}//./..//{path}/test")),
            None,
            Existing,
            true,
            false,
            false,
        )?
        .display()
        .to_string();

        assert!(!result1.is_empty(), "result:{result1}");
        assert!(!result2.is_empty(), "result:{result2}");
        assert_eq!(result1, result2);

        Ok(())
    }

    #[test]
    fn test_canonicalize_non_directory_with_slash() -> TestResult {
        let cwd = std::env::current_dir()?.display().to_string();
        let path = tempdir()?.display().to_string();
        let test = format!("{path}/test/");

        assert_eq!(
            canonicalize(
                Pid::this(),
                Path::new(&test),
                Some(Path::new(&cwd)),
                Normal,
                true,
                false,
                false,
            ),
            Err(Errno::ENOTDIR)
        );
        assert_eq!(
            canonicalize(
                Pid::this(),
                Path::new(&test),
                Some(Path::new(&cwd)),
                Existing,
                true,
                false,
                false,
            ),
            Err(Errno::ENOTDIR)
        );
        //SAFETY: Missing is handled by read_path*.
        assert!(canonicalize(
            Pid::this(),
            Path::new(&test),
            Some(Path::new(&cwd)),
            Missing,
            true,
            false,
            false,
        )
        .is_ok());
        assert_eq!(
            canonicalize(
                Pid::this(),
                Path::new(&test),
                Some(Path::new(&cwd)),
                Normal,
                false,
                false,
                false,
            ),
            Err(Errno::ENOTDIR)
        );
        assert_eq!(
            canonicalize(
                Pid::this(),
                Path::new(&test),
                Some(Path::new(&cwd)),
                Existing,
                false,
                false,
                false,
            ),
            Err(Errno::ENOTDIR)
        );
        //SAFETY: Missing is handled by read_path*.
        assert!(canonicalize(
            Pid::this(),
            Path::new(&test),
            Some(Path::new(&cwd)),
            Missing,
            false,
            false,
            false,
        )
        .is_ok());

        Ok(())
    }

    /// FIXME: The asserts return success rather than failure.
    /// Bug or feature?
    #[test]
    #[ignore]
    fn test_canonicalize_missing_directory_returns_enoent() -> TestResult {
        assert_eq!(
            canonicalize(
                Pid::this(),
                Path::new("/zzz/.."),
                None,
                Normal,
                true,
                false,
                false,
            ),
            Err(Errno::ENOENT)
        );
        assert_eq!(
            canonicalize(
                Pid::this(),
                Path::new("/zzz/.."),
                None,
                Existing,
                true,
                false,
                false,
            ),
            Err(Errno::ENOENT)
        );
        assert_eq!(
            canonicalize(
                Pid::this(),
                Path::new("/zzz/.."),
                None,
                Normal,
                false,
                false,
                false,
            ),
            Err(Errno::ENOENT)
        );
        assert_eq!(
            canonicalize(
                Pid::this(),
                Path::new("/zzz/.."),
                None,
                Existing,
                false,
                false,
                false,
            ),
            Err(Errno::ENOENT)
        );

        Ok(())
    }

    #[test]
    fn test_relative_symlink_resolution() -> TestResult {
        // Setup
        let root_test_dir = Path::new("test_root_relative_symlink_resolution");
        let deep_dir = root_test_dir.join("a/b/c");
        let _ = remove_dir_all(&root_test_dir);
        fs::create_dir_all(&root_test_dir.join("d"))?;
        fs::create_dir_all(&deep_dir)?;

        // Create a symlink in "b" that points upwards to "a"
        let rel_link = root_test_dir.join("a/b/rel_link");
        symlink("../..", &rel_link)?;

        // Append /proc/self/cwd to get an absolute path to our symlinked path
        let abs_link_path = Path::new("/proc/self/cwd").join(root_test_dir.join("a/b/rel_link/d"));

        // Call canonicalize
        let result = canonicalize(
            Pid::this(),
            &abs_link_path,
            None,
            MissingHandling::Existing,
            true,
            false,
            false,
        );
        assert!(
            matches!(result, Ok(_)),
            "canonicalize:{} result:{:?}",
            abs_link_path.display(),
            result
        );
        let resolved_path = result.unwrap();

        // We expect the path to be resolved to "test_root/a/d", but we need to canonicalize it
        let expected_path =
            fs::canonicalize(Path::new("/proc/self/cwd").join(root_test_dir.join("d")))?;

        // Cleanup
        let _ = remove_dir_all(&root_test_dir);

        assert_eq!(resolved_path, expected_path.into());

        Ok(())
    }

    // FIXME: This test broke after we removed normalize()
    // The question: Is the test incorrect or is canonicalize()?
    #[ignore]
    #[test]
    fn test_complex_interplay_symlinks_dots() -> TestResult {
        // Setup
        let cwd = Path::new("/proc/self/cwd").canonicalize()?;
        let root_test_dir = cwd.join("test_root_complex_interplay_symlinks_dots");
        let _ = remove_dir_all(&root_test_dir);
        fs::create_dir_all(root_test_dir.join("a/b/c")).unwrap();
        fs::create_dir(root_test_dir.join("d")).unwrap();
        fs::create_dir(root_test_dir.join("e")).unwrap();
        fs::create_dir(root_test_dir.join("x")).unwrap();

        // Create several symlinks
        symlink("./a", root_test_dir.join("link_to_a")).unwrap();
        symlink("e", root_test_dir.join("link_to_e")).unwrap();
        symlink("a/b", root_test_dir.join("link_to_b")).unwrap();
        symlink("../../x", root_test_dir.join("a/b/rel_link")).unwrap();

        let path = root_test_dir.join("link_to_a/../link_to_b/rel_link/../..");
        let resolved_path = canonicalize(
            Pid::this(),
            &path,
            None,
            MissingHandling::Existing,
            true,
            false,
            false,
        )
        .unwrap();

        // Cleanup
        let _ = remove_dir_all(&root_test_dir);

        // Assertion
        assert_eq!(resolved_path, root_test_dir.into());

        Ok(())
    }

    #[test]
    fn test_trailing_slash_handling() -> TestResult {
        let path = SmallPathBuf::from("/usr/");
        let pabs = canonicalize(
            Pid::this(),
            &path,
            None,
            MissingHandling::Normal,
            true,
            false,
            false,
        )
        .unwrap();
        assert_eq!(path, pabs);

        let path = Path::new("/proc/self/");
        let pexp = SmallPathBuf::from(format!("/proc/{}/", getpid()));
        let pabs = canonicalize(
            Pid::this(),
            &path,
            None,
            MissingHandling::Normal,
            true,
            false,
            false,
        )
        .unwrap();
        assert_eq!(pabs, pexp);
        let pabs = canonicalize(
            Pid::this(),
            &path,
            None,
            MissingHandling::Normal,
            false,
            false,
            false,
        )
        .unwrap();
        assert_eq!(pabs, pexp);

        Ok(())
    }

    #[ignore]
    #[test]
    fn test_canonicalize_no_atime_change_normal() -> TestResult {
        let cdir = std::env::current_dir()?;
        let base = cdir.join(tempdir()?);
        let path = base.join("file");
        fs::File::create(&path)?;

        assert_atime_unchanged(&path, || {
            canonicalize(
                Pid::this(),
                &path,
                None,
                MissingHandling::Normal,
                true,
                false,
                false,
            )
        });

        let _ = remove_dir_all(&base);
        Ok(())
    }

    #[ignore]
    #[test]
    fn test_canonicalize_no_atime_change_existing() -> TestResult {
        let cdir = std::env::current_dir()?;
        let base = cdir.join(tempdir()?);
        let path = base.join("file");
        fs::File::create(&path)?;

        assert_atime_unchanged(&path, || {
            canonicalize(
                Pid::this(),
                &path,
                None,
                MissingHandling::Existing,
                true,
                false,
                false,
            )
        });

        let _ = remove_dir_all(&base);
        Ok(())
    }

    #[test]
    fn test_canonicalize_symlink_loop() {
        let tmp_dir = tempfile::tempdir().expect("Failed to create temp dir");
        let dir_path = tmp_dir.path();

        // Create a symlink loop: link_a -> link_b -> link_a
        let mut link_a = dir_path.join("link_a");
        let mut link_b = dir_path.join("link_b");
        symlink(&link_b, &link_a).expect("Failed to create symlink a");
        symlink(&link_a, &link_b).expect("Failed to create symlink b");

        // Now check that canonicalize detects the loop correctly
        let result = canonicalize(
            Pid::this(),
            &link_a,
            None,
            MissingHandling::Normal,
            false,
            false,
            false,
        );
        assert!(result.is_ok(), "{result:?}");

        let result = canonicalize(
            Pid::this(),
            &link_a,
            None,
            MissingHandling::Normal,
            true,
            false,
            false,
        );
        assert_eq!(result, Err(Errno::ELOOP));

        let result = canonicalize(
            Pid::this(),
            &link_a,
            None,
            MissingHandling::Existing,
            false,
            false,
            false,
        );
        assert!(result.is_ok(), "{result:?}");

        let result = canonicalize(
            Pid::this(),
            &link_a,
            None,
            MissingHandling::Existing,
            true,
            false,
            false,
        );
        assert_eq!(result, Err(Errno::ELOOP));

        let result = canonicalize(
            Pid::this(),
            &link_a,
            None,
            MissingHandling::Missing,
            false,
            false,
            false,
        );
        //SAFETY: Missing is handled by read_path*.
        //assert_eq!(result, Err(Errno::EEXIST));
        assert!(result.is_ok(), "{result:?}");

        let result = canonicalize(
            Pid::this(),
            &link_a,
            None,
            MissingHandling::Missing,
            true,
            false,
            false,
        );
        assert_eq!(result, Err(Errno::ELOOP));

        // Add a trailing slash and retest.
        link_a.push("");

        let result = canonicalize(
            Pid::this(),
            &link_a,
            None,
            MissingHandling::Missing,
            false,
            false,
            false,
        );
        assert_eq!(result, Err(Errno::ELOOP));

        let result = canonicalize(
            Pid::this(),
            &link_a,
            None,
            MissingHandling::Missing,
            true,
            false,
            false,
        );
        assert_eq!(result, Err(Errno::ELOOP));

        // Add a trailing slash and retest.
        link_b.push("");

        let result = canonicalize(
            Pid::this(),
            &link_b,
            None,
            MissingHandling::Missing,
            false,
            false,
            false,
        );
        assert_eq!(result, Err(Errno::ELOOP));

        let result = canonicalize(
            Pid::this(),
            &link_b,
            None,
            MissingHandling::Missing,
            true,
            false,
            false,
        );
        assert_eq!(result, Err(Errno::ELOOP));
    }

    #[test]
    fn test_canonicalize_nonexistent_final_component() {
        let tmp_dir = tempfile::tempdir().expect("Failed to create temp dir");
        let dir_path = tmp_dir.path();

        // Create a valid symlink to a non-existent final component
        let mut valid_link = dir_path.join("valid_link");
        let nonexistent_target = dir_path.join("nonexistent");
        symlink(&nonexistent_target, &valid_link)
            .expect("Failed to create symlink to non-existent target");

        // Now check that canonicalize handles the non-existent final component correctly
        let result = canonicalize(
            Pid::this(),
            &valid_link,
            None,
            MissingHandling::Normal,
            false,
            false,
            false,
        );
        assert!(result.is_ok(), "{result:?}");

        let result = canonicalize(
            Pid::this(),
            &valid_link,
            None,
            MissingHandling::Normal,
            true,
            false,
            false,
        );
        assert!(result.is_ok(), "{result:?}");

        let result = canonicalize(
            Pid::this(),
            &valid_link,
            None,
            MissingHandling::Existing,
            false,
            false,
            false,
        );
        assert!(result.is_ok(), "{result:?}");

        let result = canonicalize(
            Pid::this(),
            &valid_link,
            None,
            MissingHandling::Existing,
            true,
            false,
            false,
        );
        assert_eq!(result, Err(Errno::ENOENT));

        let result = canonicalize(
            Pid::this(),
            &valid_link,
            None,
            MissingHandling::Missing,
            false,
            false,
            false,
        );
        //SAFETY: Missing is handled by read_path*.
        //assert_eq!(result, Err(Errno::EEXIST));
        assert!(result.is_ok(), "{result:?}");

        // FIXME: Why oh why?
        let result = canonicalize(
            Pid::this(),
            &valid_link,
            None,
            MissingHandling::Missing,
            true,
            false,
            false,
        );
        assert!(result.is_ok(), "{result:?}");
        // should be: assert_eq!(result, Err(Errno::EEXIST));

        // Add a trailing slash and retest.
        valid_link.push("");

        // FIXME: Why oh why?
        let result = canonicalize(
            Pid::this(),
            &valid_link,
            None,
            MissingHandling::Missing,
            false,
            false,
            false,
        );
        assert!(result.is_ok(), "{result:?}");
        // should be: assert_eq!(result, Err(Errno::EEXIST));

        // FIXME: Why oh why?
        let result = canonicalize(
            Pid::this(),
            &valid_link,
            None,
            MissingHandling::Missing,
            true,
            false,
            false,
        );
        assert!(result.is_ok(), "{result:?}");
        // should be: assert_eq!(result, Err(Errno::EEXIST));
    }

    #[test]
    fn test_canonicalize_self_referential_symlink() {
        let tmp_dir = tempfile::tempdir().expect("Failed to create temp dir");
        let mut symlink_path = tmp_dir.path().join("self_link");
        symlink(&symlink_path, &symlink_path).expect("Failed to create self-referential symlink");

        let result = canonicalize(
            Pid::this(),
            &symlink_path,
            None,
            MissingHandling::Normal,
            false,
            false,
            false,
        );
        assert!(result.is_ok(), "{result:?}");

        let result = canonicalize(
            Pid::this(),
            &symlink_path,
            None,
            MissingHandling::Normal,
            true,
            false,
            false,
        );
        assert_eq!(result, Err(Errno::ELOOP));

        let result = canonicalize(
            Pid::this(),
            &symlink_path,
            None,
            MissingHandling::Existing,
            false,
            false,
            false,
        );
        assert!(result.is_ok(), "{result:?}");

        let result = canonicalize(
            Pid::this(),
            &symlink_path,
            None,
            MissingHandling::Existing,
            true,
            false,
            false,
        );
        assert_eq!(result, Err(Errno::ELOOP));

        let result = canonicalize(
            Pid::this(),
            &symlink_path,
            None,
            MissingHandling::Missing,
            false,
            false,
            false,
        );
        //SAFETY: Missing is handled by read_path*.
        //assert_eq!(result, Err(Errno::EEXIST));
        assert!(result.is_ok(), "{result:?}");

        let result = canonicalize(
            Pid::this(),
            &symlink_path,
            None,
            MissingHandling::Missing,
            true,
            false,
            false,
        );
        assert_eq!(result, Err(Errno::ELOOP));

        // Add a trailing slash and retest.
        symlink_path.push("");

        let result = canonicalize(
            Pid::this(),
            &symlink_path,
            None,
            MissingHandling::Missing,
            false,
            false,
            false,
        );
        assert_eq!(result, Err(Errno::ELOOP));

        let result = canonicalize(
            Pid::this(),
            &symlink_path,
            None,
            MissingHandling::Missing,
            true,
            false,
            false,
        );
        assert_eq!(result, Err(Errno::ELOOP));
    }

    #[test]
    fn test_canonicalize_broken_symlink() {
        let tmp_dir = tempfile::tempdir().expect("Failed to create temp dir");
        let mut broken_link = tmp_dir.path().join("broken_link");
        let nonexistent_target = tmp_dir.path().join("nonexistent_target");
        symlink(&nonexistent_target, &broken_link).expect("Failed to create broken symlink");

        let result = canonicalize(
            Pid::this(),
            &broken_link,
            None,
            MissingHandling::Normal,
            false,
            false,
            false,
        );
        assert!(result.is_ok(), "{result:?}");

        let result = canonicalize(
            Pid::this(),
            &broken_link,
            None,
            MissingHandling::Normal,
            true,
            false,
            false,
        );
        // FIXME: assert_eq!(result, Err(Errno::ENOENT));
        assert!(result.is_ok(), "{result:?}");

        let result = canonicalize(
            Pid::this(),
            &broken_link,
            None,
            MissingHandling::Existing,
            false,
            false,
            false,
        );
        assert!(result.is_ok(), "{result:?}");

        let result = canonicalize(
            Pid::this(),
            &broken_link,
            None,
            MissingHandling::Existing,
            true,
            false,
            false,
        );
        assert_eq!(result, Err(Errno::ENOENT));

        let result = canonicalize(
            Pid::this(),
            &broken_link,
            None,
            MissingHandling::Missing,
            false,
            false,
            false,
        );
        //SAFETY: Missing is handled by read_path*.
        //assert_eq!(result, Err(Errno::EEXIST));
        assert!(result.is_ok(), "{result:?}");

        let result = canonicalize(
            Pid::this(),
            &broken_link,
            None,
            MissingHandling::Missing,
            true,
            false,
            false,
        );
        assert!(result.is_ok(), "{result:?}");

        // Add a trailing slash and retest.
        broken_link.push("");

        let result = canonicalize(
            Pid::this(),
            &broken_link,
            None,
            MissingHandling::Missing,
            false,
            false,
            false,
        );
        assert!(result.is_ok(), "{result:?}");

        let result = canonicalize(
            Pid::this(),
            &broken_link,
            None,
            MissingHandling::Missing,
            true,
            false,
            false,
        );
        assert!(result.is_ok(), "{result:?}");
    }

    #[test]
    fn test_canonicalize_symlink_to_directory() {
        let tmp_dir = tempfile::tempdir().expect("Failed to create temp dir");
        let dir = tmp_dir.path().join("dir");
        fs::create_dir(&dir).expect("Failed to create directory");

        let symlink_path = tmp_dir.path().join("dir_link");
        symlink(&dir, &symlink_path).expect("Failed to create symlink to directory");

        let result = canonicalize(
            Pid::this(),
            &symlink_path,
            None,
            MissingHandling::Normal,
            false,
            false,
            false,
        );
        assert!(result.is_ok(), "{result:?}");
        assert!(result.clone().unwrap().is_symlink(), "{result:?}");

        let result = canonicalize(
            Pid::this(),
            &symlink_path,
            None,
            MissingHandling::Normal,
            true,
            false,
            false,
        );
        assert!(result.is_ok(), "{result:?}");
        assert!(result.clone().unwrap().is_dir(), "{result:?}");

        let result = canonicalize(
            Pid::this(),
            &symlink_path,
            None,
            MissingHandling::Existing,
            false,
            false,
            false,
        );
        assert!(result.is_ok(), "{result:?}");
        assert!(result.clone().unwrap().is_symlink(), "{result:?}");

        let result = canonicalize(
            Pid::this(),
            &symlink_path,
            None,
            MissingHandling::Existing,
            true,
            false,
            false,
        );
        assert!(result.is_ok(), "{result:?}");
        assert!(result.clone().unwrap().is_dir(), "{result:?}");

        let result = canonicalize(
            Pid::this(),
            &symlink_path,
            None,
            MissingHandling::Missing,
            false,
            false,
            false,
        );
        //SAFETY: Missing is handled by read_path*.
        //assert_eq!(result, Err(Errno::EEXIST));
        assert!(result.is_ok(), "{result:?}");

        let result = canonicalize(
            Pid::this(),
            &symlink_path,
            None,
            MissingHandling::Missing,
            true,
            false,
            false,
        );
        //SAFETY: Missing is handled by read_path*.
        //assert_eq!(result, Err(Errno::EEXIST));
        assert!(result.is_ok(), "{result:?}");
    }

    #[test]
    fn test_canonicalize_symlink_chain() {
        let tmp_dir = tempfile::tempdir().expect("Failed to create temp dir");
        let link1 = tmp_dir.path().join("link1");
        let link2 = tmp_dir.path().join("link2");
        let link3 = tmp_dir.path().join("link3");
        let file = tmp_dir.path().join("file");
        fs::write(&file, "content").expect("Failed to write file");

        // Create a chain of symlinks: link1 -> link2 -> link3 -> file
        symlink(&link2, &link1).expect("Failed to create link1");
        symlink(&link3, &link2).expect("Failed to create link2");
        symlink(&file, &link3).expect("Failed to create link3");

        let result = canonicalize(
            Pid::this(),
            &link1,
            None,
            MissingHandling::Normal,
            false,
            false,
            false,
        );
        assert!(result.is_ok(), "{result:?}");
        assert!(result.clone().unwrap().is_symlink(), "{result:?}");

        let result = canonicalize(
            Pid::this(),
            &link1,
            None,
            MissingHandling::Normal,
            true,
            false,
            false,
        );
        assert!(result.is_ok(), "{result:?}");
        assert!(result.clone().unwrap().is_file(), "{result:?}");

        let result = canonicalize(
            Pid::this(),
            &link1,
            None,
            MissingHandling::Existing,
            false,
            false,
            false,
        );
        assert!(result.is_ok(), "{result:?}");
        assert!(result.clone().unwrap().is_symlink(), "{result:?}");

        let result = canonicalize(
            Pid::this(),
            &link1,
            None,
            MissingHandling::Existing,
            true,
            false,
            false,
        );
        assert!(result.is_ok(), "{result:?}");
        assert!(result.clone().unwrap().is_file(), "{result:?}");

        let result = canonicalize(
            Pid::this(),
            &link1,
            None,
            MissingHandling::Missing,
            false,
            false,
            false,
        );
        //SAFETY: Missing is handled by read_path*.
        //assert_eq!(result, Err(Errno::EEXIST));
        assert!(result.is_ok(), "{result:?}");

        let result = canonicalize(
            Pid::this(),
            &link1,
            None,
            MissingHandling::Missing,
            true,
            false,
            false,
        );
        //SAFETY: Missing is handled by read_path*.
        //assert_eq!(result, Err(Errno::EEXIST));
        assert!(result.is_ok(), "{result:?}");
    }

    #[test]
    fn test_canonicalize_complex_symlink_loop_with_intermediate_components() {
        let tmp_dir = tempfile::tempdir().expect("Failed to create temp dir");

        // Setting up a complex symlink scenario
        setup_symlink_loop(
            &tmp_dir,
            &[("a", "b/c"), ("b/c", "d"), ("b/d", "../e"), ("e", "f/../a")],
        );

        let mut path = tmp_dir.path().join("a");

        let result = canonicalize(
            Pid::this(),
            &path,
            None,
            MissingHandling::Normal,
            false,
            false,
            false,
        );
        assert!(result.is_ok(), "{result:?}");
        assert!(result.clone().unwrap().is_symlink(), "{result:?}");

        let result = canonicalize(
            Pid::this(),
            &path,
            None,
            MissingHandling::Normal,
            true,
            false,
            false,
        );
        assert_eq!(result, Err(Errno::ELOOP));

        let result = canonicalize(
            Pid::this(),
            &path,
            None,
            MissingHandling::Existing,
            false,
            false,
            false,
        );
        assert!(result.is_ok(), "{result:?}");
        assert!(result.clone().unwrap().is_symlink(), "{result:?}");

        let result = canonicalize(
            Pid::this(),
            &path,
            None,
            MissingHandling::Existing,
            true,
            false,
            false,
        );
        assert_eq!(result, Err(Errno::ELOOP));

        let result = canonicalize(
            Pid::this(),
            &path,
            None,
            MissingHandling::Missing,
            false,
            false,
            false,
        );
        //SAFETY: Missing is handled by read_path*.
        //assert_eq!(result, Err(Errno::EEXIST));
        assert!(result.is_ok(), "{result:?}");

        let result = canonicalize(
            Pid::this(),
            &path,
            None,
            MissingHandling::Missing,
            true,
            false,
            false,
        );
        assert_eq!(result, Err(Errno::ELOOP));

        // Add a trailing slash and retest.
        path.push("");

        let result = canonicalize(
            Pid::this(),
            &path,
            None,
            MissingHandling::Missing,
            false,
            false,
            false,
        );
        assert_eq!(result, Err(Errno::ELOOP));

        let result = canonicalize(
            Pid::this(),
            &path,
            None,
            MissingHandling::Missing,
            true,
            false,
            false,
        );
        assert_eq!(result, Err(Errno::ELOOP));

        // Add a final component and retest.
        path.push("foo");

        let result = canonicalize(
            Pid::this(),
            &path,
            None,
            MissingHandling::Missing,
            false,
            false,
            false,
        );
        assert_eq!(result, Err(Errno::ELOOP));

        let result = canonicalize(
            Pid::this(),
            &path,
            None,
            MissingHandling::Missing,
            true,
            false,
            false,
        );
        assert_eq!(result, Err(Errno::ELOOP));
    }

    #[test]
    fn test_canonicalize_symlinks_with_dot_and_dotdot_components() {
        let tmp_dir = tempfile::tempdir().expect("Failed to create temp dir");

        // Create a complex directory structure with dots and symlinks
        fs::create_dir_all(tmp_dir.path().join("b")).expect("Failed to create directory b");
        symlink("b", tmp_dir.path().join("a")).expect("Failed to create symlink a -> b");
        symlink("..///e", tmp_dir.path().join("b").join("d"))
            .expect("Failed to create symlink b/d -> ../e");
        symlink("b/.///./d", tmp_dir.path().join("e")).expect("Failed to create symlink e -> b/d");

        let mut path = tmp_dir.path().join("a").join(".").join("d");

        let result = canonicalize(
            Pid::this(),
            &path,
            None,
            MissingHandling::Normal,
            false,
            false,
            false,
        );
        assert!(result.is_ok(), "{result:?}");
        assert!(!result.clone().unwrap().exists(), "{result:?}");

        let result = canonicalize(
            Pid::this(),
            &path,
            None,
            MissingHandling::Normal,
            true,
            false,
            false,
        );
        assert_eq!(result, Err(Errno::ELOOP));

        let result = canonicalize(
            Pid::this(),
            &path,
            None,
            MissingHandling::Existing,
            false,
            false,
            false,
        );
        assert!(result.is_ok(), "{result:?}");
        assert!(!result.clone().unwrap().exists(), "{result:?}");

        let result = canonicalize(
            Pid::this(),
            &path,
            None,
            MissingHandling::Existing,
            true,
            false,
            false,
        );
        assert_eq!(result, Err(Errno::ELOOP));

        let result = canonicalize(
            Pid::this(),
            &path,
            None,
            MissingHandling::Missing,
            false,
            false,
            false,
        );
        //SAFETY: Missing is handled by read_path*.
        //assert_eq!(result, Err(Errno::EEXIST));
        assert!(result.is_ok(), "{result:?}");

        let result = canonicalize(
            Pid::this(),
            &path,
            None,
            MissingHandling::Missing,
            true,
            false,
            false,
        );
        assert_eq!(result, Err(Errno::ELOOP));

        // Add a trailing slash and retest.
        path.push("");

        let result = canonicalize(
            Pid::this(),
            &path,
            None,
            MissingHandling::Missing,
            false,
            false,
            false,
        );
        assert_eq!(result, Err(Errno::ELOOP));

        let result = canonicalize(
            Pid::this(),
            &path,
            None,
            MissingHandling::Missing,
            true,
            false,
            false,
        );
        assert_eq!(result, Err(Errno::ELOOP));

        // Add a final component and retest.
        path.push("foo");

        let result = canonicalize(
            Pid::this(),
            &path,
            None,
            MissingHandling::Missing,
            false,
            false,
            false,
        );
        assert_eq!(result, Err(Errno::ELOOP));

        let result = canonicalize(
            Pid::this(),
            &path,
            None,
            MissingHandling::Missing,
            true,
            false,
            false,
        );
        assert_eq!(result, Err(Errno::ELOOP));
    }

    #[test]
    fn test_canonicalize_proc_self() -> TestResult {
        let path = canonicalize(
            Pid::this(),
            Path::new("/proc/self"),
            None,
            MissingHandling::Normal,
            false,
            false,
            false,
        )?;
        assert_eq!(path, SmallPathBuf::from("/proc/self"));

        Ok(())
    }

    #[test]
    fn test_canon_glob_std() -> TestResult {
        let result = canonicalize(
            Pid::this(),
            Path::new("/proc/self/fd/0"),
            None,
            MissingHandling::Normal,
            true,
            false,
            false,
        );
        assert!(result.is_ok(), "{result:?}");
        let result = canonicalize(
            Pid::this(),
            Path::new("/proc/self/fd/1"),
            None,
            MissingHandling::Normal,
            true,
            false,
            false,
        );
        assert!(result.is_ok(), "{result:?}");
        let result = canonicalize(
            Pid::this(),
            Path::new("/proc/self/fd/2"),
            None,
            MissingHandling::Normal,
            true,
            false,
            false,
        );
        assert!(result.is_ok(), "{result:?}");
        let result = canonicalize(
            Pid::this(),
            Path::new("/proc/self/fd/0"),
            None,
            MissingHandling::Existing,
            true,
            false,
            false,
        );
        assert!(result.is_ok(), "{result:?}");
        let result = canonicalize(
            Pid::this(),
            Path::new("/proc/self/fd/1"),
            None,
            MissingHandling::Existing,
            true,
            false,
            false,
        );
        assert!(result.is_ok(), "{result:?}");
        let result = canonicalize(
            Pid::this(),
            Path::new("/proc/self/fd/2"),
            None,
            MissingHandling::Existing,
            true,
            false,
            false,
        );
        assert!(result.is_ok(), "{result:?}");
        let result = canonicalize(
            Pid::this(),
            Path::new("/proc/self/fd/0"),
            None,
            MissingHandling::Missing,
            true,
            false,
            false,
        );
        //SAFETY: Missing is handled by read_path*.
        //assert_eq!(result, Err(Errno::EEXIST));
        assert!(result.is_ok(), "{result:?}");
        let result = canonicalize(
            Pid::this(),
            Path::new("/proc/self/fd/1"),
            None,
            MissingHandling::Missing,
            true,
            false,
            false,
        );
        //SAFETY: Missing is handled by read_path*.
        //assert_eq!(result, Err(Errno::EEXIST));
        assert!(result.is_ok(), "{result:?}");
        let result = canonicalize(
            Pid::this(),
            Path::new("/proc/self/fd/2"),
            None,
            MissingHandling::Missing,
            true,
            false,
            false,
        );
        //SAFETY: Missing is handled by read_path*.
        //assert_eq!(result, Err(Errno::EEXIST));
        assert!(result.is_ok(), "{result:?}");
        Ok(())
    }

    #[test]
    fn test_canon_glob_pipe() -> TestResult {
        let (read_end, write_end) = std::os::unix::net::UnixStream::pair()?;

        let fd = read_end.as_raw_fd();
        let path = format!("/proc/self/fd/{fd}");
        assert!(canonicalize(
            Pid::this(),
            Path::new(&path),
            None,
            MissingHandling::Normal,
            true,
            false,
            false,
        )
        .is_ok());

        let fd = write_end.as_raw_fd();
        let path = format!("/proc/self/fd/{fd}");
        assert!(canonicalize(
            Pid::this(),
            Path::new(&path),
            None,
            MissingHandling::Normal,
            true,
            false,
            false,
        )
        .is_ok());

        Ok(())
    }
}
