//
// Syd: rock-solid application kernel
// src/compat.rs: Compatibility code for different libcs
//
// Copyright (c) 2023, 2024, 2025 Ali Polatel <alip@chesswob.org>
// waitid and WaitStatus are based in part upon nix which is:
//     Copyright (c) nix authors
//     SPDX-License-Identifier: MIT
//
// SPDX-License-Identifier: GPL-3.0

#![allow(non_camel_case_types)]

use std::{
    cmp::Ordering,
    os::fd::{AsFd, AsRawFd, RawFd},
};

use memchr::arch::all::is_equal;
use nix::{
    errno::Errno,
    fcntl::{AtFlags, OFlag},
    sys::{
        epoll::EpollOp,
        socket::SockaddrLike,
        wait::{Id, WaitPidFlag, WaitStatus as NixWaitStatus},
    },
    unistd::Pid,
    NixPath,
};
use once_cell::sync::Lazy;
use serde::{ser::SerializeMap, Serialize, Serializer};

use crate::{fs::FileType, libseccomp::ScmpSyscall, IoctlRequest, XPath};

#[repr(C)]
pub(crate) struct TimeSpec64 {
    pub(crate) tv_sec: i64,
    pub(crate) tv_nsec: i64,
}

#[repr(C)]
pub(crate) struct TimeSpec32 {
    pub(crate) tv_sec: i32,
    pub(crate) tv_nsec: i32,
}

/// Replacement for slice::trim_ascii_start which is Rust>=1.80.0.
#[inline]
pub const fn trim_ascii_start(mut bytes: &[u8]) -> &[u8] {
    // Note: A pattern matching based approach (instead of indexing)
    // allows making the function const.
    while let [first, rest @ ..] = bytes {
        if first.is_ascii_whitespace() {
            bytes = rest;
        } else {
            break;
        }
    }
    bytes
}

/// Replacement for slice::trim_ascii_end which is Rust>=1.80.0.
#[inline]
pub const fn trim_ascii_end(mut bytes: &[u8]) -> &[u8] {
    // Note: A pattern matching based approach (instead of indexing)
    // allows making the function const.
    while let [rest @ .., last] = bytes {
        if last.is_ascii_whitespace() {
            bytes = rest;
        } else {
            break;
        }
    }
    bytes
}

/// Replacement for slice::trim_ascii which is Rust>=1.80.0.
#[inline]
pub const fn trim_ascii(bytes: &[u8]) -> &[u8] {
    trim_ascii_end(trim_ascii_start(bytes))
}

/// Aligns the given length to the nearest 4-byte boundary.
///
/// This function is useful for ensuring that data structures in a 32-bit architecture
/// are correctly aligned to 4 bytes as required by many system interfaces for proper operation.
///
/// # Arguments
///
/// * `len` - The length that needs to be aligned.
///
/// # Returns
///
/// The smallest length that is a multiple of 4 bytes and is not less than `len`.
pub(crate) const fn cmsg_align_32(len: usize) -> usize {
    len.saturating_add(3) & !3
}

/// Calculates the total space required for a control message including data and padding on a 32-bit system.
///
/// This function is specifically tailored for 32-bit architectures where control message
/// headers and data need to be aligned to 4-byte boundaries.
///
/// # Arguments
///
/// * `length` - The length of the data in the control message.
///
/// # Returns
///
/// The total space in bytes required to store the control message, ensuring proper alignment.
pub(crate) const fn cmsg_space_32(length: u32) -> usize {
    cmsg_align_32((length as usize).saturating_add(cmsg_align_32(std::mem::size_of::<cmsghdr32>())))
}

/// Computes the byte length of a control message's header and data for a 32-bit system.
///
/// This function helps in determining the correct length for control messages where the
/// header and data must be aligned to 4-byte boundaries in a 32-bit architecture.
///
/// # Arguments
///
/// * `length` - The length of the data part of the control message.
///
/// # Returns
///
/// The combined length of the control message header and the data, aligned as required.
pub(crate) const fn cmsg_len_32(length: u32) -> usize {
    cmsg_align_32(std::mem::size_of::<cmsghdr32>()).saturating_add(length as usize)
}

/// This structure represents the Linux 32-bit data structure 'struct stat'
#[repr(C)]
pub struct stat32 {
    /// Device ID.
    pub st_dev: libc::dev_t,

    /// Inode number (32-bit).
    pub st_ino: u32,

    /// Number of hard links.
    pub st_nlink: libc::nlink_t,

    /// File mode.
    pub st_mode: libc::mode_t,

    /// User ID of owner.
    pub st_uid: libc::uid_t,

    /// Group ID of owner.
    pub st_gid: libc::gid_t,

    /// Padding.
    __pad0: libc::c_int,

    /// Device ID (if special file).
    pub st_rdev: libc::dev_t,

    /// Total size, in bytes (32-bit).
    pub st_size: i32,

    /// Block size for filesystem I/O.
    pub st_blksize: libc::blksize_t,

    /// Number of 512B blocks allocated (32-bit).
    pub st_blocks: i32,

    /// Time of last access (32-bit).
    pub st_atime: i32,

    /// Nanoseconds of last access (32-bit).
    pub st_atime_nsec: i32,

    /// Time of last modification (32-bit).
    pub st_mtime: i32,

    /// Nanoseconds of last modification (32-bit).
    pub st_mtime_nsec: i32,

    /// Time of last status change (32-bit).
    pub st_ctime: i32,

    /// Nanoseconds of last status change (32-bit).
    pub st_ctime_nsec: i32,

    /// Reserved for future use (32-bit).
    __unused: [i32; 3],
}

impl From<libc::stat64> for stat32 {
    #[allow(clippy::cast_possible_truncation)]
    fn from(stat: libc::stat64) -> Self {
        Self {
            st_dev: stat.st_dev,
            st_ino: stat.st_ino as u32,
            st_nlink: stat.st_nlink,
            st_mode: stat.st_mode,
            st_uid: stat.st_uid,
            st_gid: stat.st_gid,
            __pad0: 0,
            st_rdev: stat.st_rdev,
            st_size: stat.st_size as i32,
            st_blksize: stat.st_blksize,
            st_blocks: stat.st_blocks as i32,
            st_atime: stat.st_atime as i32,
            st_atime_nsec: stat.st_atime_nsec as i32,
            st_mtime: stat.st_mtime as i32,
            st_mtime_nsec: stat.st_mtime_nsec as i32,
            st_ctime: stat.st_ctime as i32,
            st_ctime_nsec: stat.st_ctime_nsec as i32,
            __unused: [0; 3],
        }
    }
}

/// This structure represents the Linux 32-bit data structure 'struct iovec'
#[repr(C)]
pub struct iovec32 {
    iov_base: u32,
    iov_len: u32,
}

impl From<iovec32> for libc::iovec {
    fn from(src: iovec32) -> Self {
        libc::iovec {
            iov_base: src.iov_base as *mut _,
            iov_len: src.iov_len as usize,
        }
    }
}

/// This structure represents the Linux 32-bit data structure 'struct mmmsghdr'
#[derive(Copy, Clone)]
#[repr(C)]
pub struct mmsghdr32 {
    pub msg_hdr: msghdr32,
    pub msg_len: u32,
}

/// This structure represents the Linux native data structure 'struct mmsghdr'
#[derive(Copy, Clone)]
#[repr(C)]
pub struct mmsghdr {
    pub msg_hdr: msghdr,
    pub msg_len: libc::c_uint,
}

impl From<mmsghdr32> for mmsghdr {
    fn from(src: mmsghdr32) -> Self {
        mmsghdr {
            msg_hdr: msghdr::from(src.msg_hdr),
            msg_len: src.msg_len,
        }
    }
}

impl From<mmsghdr> for mmsghdr32 {
    fn from(src: mmsghdr) -> Self {
        mmsghdr32 {
            msg_hdr: msghdr32::from(src.msg_hdr),
            msg_len: src.msg_len,
        }
    }
}

/// This structure represents the Linux 32-bit data structure 'struct cmsghdr'
#[repr(C)]
pub struct cmsghdr32 {
    pub cmsg_len: u32,
    pub cmsg_level: i32,
    pub cmsg_type: i32,
}

/// This structure represents the Linux native data structure 'struct cmsghdr'
#[repr(C)]
pub struct cmsghdr {
    pub cmsg_len: libc::size_t,
    pub cmsg_level: libc::c_int,
    pub cmsg_type: libc::c_int,
}

impl From<cmsghdr32> for cmsghdr {
    fn from(src: cmsghdr32) -> Self {
        cmsghdr {
            cmsg_len: src.cmsg_len as libc::size_t,
            cmsg_level: src.cmsg_level,
            cmsg_type: src.cmsg_type,
        }
    }
}

/// This structure represents the Linux 32-bit data structure 'struct msghdr'
#[derive(Copy, Clone)]
#[repr(C)]
pub struct msghdr32 {
    // In 32-bit systems, pointers are 32-bit.
    pub msg_name: u32,       // Use u32 to represent a 32-bit pointer.
    pub msg_namelen: u32,    // socklen_t is typically 32-bit.
    pub msg_iov: u32,        // Use u32 to represent a 32-bit pointer to iovec.
    pub msg_iovlen: u32,     // size_t is 32-bit on 32-bit systems.
    pub msg_control: u32,    // Use u32 to represent a 32-bit pointer.
    pub msg_controllen: u32, // size_t is 32-bit on 32-bit systems.
    pub msg_flags: i32,      // c_int remains the same (32-bit).
}

/// This structure represents the Linux native data structure 'struct msghdr'
#[derive(Copy, Clone)]
#[repr(C)]
pub struct msghdr {
    pub msg_name: *mut libc::c_void,
    pub msg_namelen: libc::socklen_t,
    pub msg_iov: *mut libc::iovec,
    pub msg_iovlen: libc::size_t,
    pub msg_control: *mut libc::c_void,
    pub msg_controllen: libc::size_t,
    pub msg_flags: libc::c_int,
}

impl From<msghdr32> for msghdr {
    fn from(msg: msghdr32) -> Self {
        msghdr {
            msg_name: msg.msg_name as *mut libc::c_void,
            msg_namelen: msg.msg_namelen as libc::socklen_t,
            msg_iov: msg.msg_iov as *mut libc::iovec,
            msg_iovlen: msg.msg_iovlen as libc::size_t,
            msg_control: msg.msg_control as *mut libc::c_void,
            msg_controllen: msg.msg_controllen as libc::size_t,
            msg_flags: msg.msg_flags as libc::c_int,
        }
    }
}

#[allow(clippy::unnecessary_cast)]
#[allow(clippy::cast_possible_truncation)]
impl From<msghdr> for msghdr32 {
    fn from(msg: msghdr) -> Self {
        msghdr32 {
            msg_name: msg.msg_name as u32,
            msg_namelen: msg.msg_namelen as u32,
            msg_iov: msg.msg_iov as u32,
            msg_iovlen: msg.msg_iovlen as u32,
            msg_control: msg.msg_control as u32,
            msg_controllen: msg.msg_controllen as u32,
            msg_flags: msg.msg_flags as i32,
        }
    }
}

/// Rust equivalent of the Linux kernel's struct xattr_args:
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct XattrArgs {
    /// 64-bit pointer to user buffer.
    pub value: u64,

    /// Size of the buffer.
    pub size: u32,

    /// XATTR_ flags (e.g., XATTR_CREATE or XATTR_REPLACE). Should be 0
    /// for getxattrat(2).
    pub flags: u32,
}

// Note getxattrat may not be available,
// and libc::SYS_getxattrat may not be defined.
// Therefore we query the number using libseccomp.
static SYS_GETXATTRAT: Lazy<libc::c_long> = Lazy::new(|| {
    ScmpSyscall::from_name("getxattrat")
        .map(i32::from)
        .map(libc::c_long::from)
        .unwrap_or(0)
});

/// Safe getxattrat(2) wrapper which is new in Linux>=6.13.
///
/// # Safety
///
/// This function dereferences `name` argument which is a raw pointer.
/// It is safe for this pointer to be NULL but otherwise it must point
/// to valid memory and the caller must ensure that.
pub unsafe fn getxattrat<F: AsRawFd, P: ?Sized + NixPath>(
    dirfd: Option<&F>,
    path: &P,
    name: *const libc::c_char,
    args: &mut XattrArgs,
    flags: AtFlags,
) -> Result<usize, Errno> {
    let sysno = if *SYS_GETXATTRAT > 0 {
        *SYS_GETXATTRAT
    } else {
        return Err(Errno::ENOSYS);
    };

    path.with_nix_path(|c_path| {
        #[allow(clippy::cast_possible_truncation)]
        #[allow(clippy::cast_sign_loss)]
        Errno::result(
            // SAFETY: In libc we trust.
            unsafe {
                libc::syscall(
                    sysno as libc::c_long,
                    dirfd.map(|fd| fd.as_raw_fd()).unwrap_or(libc::AT_FDCWD),
                    c_path.as_ptr(),
                    flags.bits(),
                    name,
                    args as *mut XattrArgs,
                    std::mem::size_of::<XattrArgs>(),
                )
            },
        )
        .map(|r| r as usize)
    })?
}

// Note setxattrat may not be available,
// and libc::SYS_setxattrat may not be defined.
// Therefore we query the number using libseccomp.
static SYS_SETXATTRAT: Lazy<libc::c_long> = Lazy::new(|| {
    ScmpSyscall::from_name("setxattrat")
        .map(i32::from)
        .map(libc::c_long::from)
        .unwrap_or(0)
});

/// Safe setxattrat(2) wrapper which is new in Linux>=6.13.
///
/// # Safety
///
/// This function dereferences `name` argument which is a raw pointer.
/// It is safe for this pointer to be NULL but otherwise it must point
/// to valid memory and the caller must ensure that.
pub unsafe fn setxattrat<F: AsRawFd, P: ?Sized + NixPath>(
    dirfd: Option<&F>,
    path: &P,
    name: *const libc::c_char,
    args: &XattrArgs,
    flags: AtFlags,
) -> Result<(), Errno> {
    let sysno = if *SYS_SETXATTRAT > 0 {
        *SYS_SETXATTRAT
    } else {
        return Err(Errno::ENOSYS);
    };

    path.with_nix_path(|c_path| {
        // SAFETY: In libc we trust.
        Errno::result(unsafe {
            libc::syscall(
                sysno as libc::c_long,
                dirfd.map(|fd| fd.as_raw_fd()).unwrap_or(libc::AT_FDCWD),
                c_path.as_ptr(),
                flags.bits(),
                name,
                args as *const XattrArgs,
                std::mem::size_of::<XattrArgs>(),
            )
        })
        .map(drop)
    })?
}

// Note listxattrat may not be available,
// and libc::SYS_listxattrat may not be defined.
// Therefore we query the number using libseccomp.
static SYS_LISTXATTRAT: Lazy<libc::c_long> = Lazy::new(|| {
    ScmpSyscall::from_name("listxattrat")
        .map(i32::from)
        .map(libc::c_long::from)
        .unwrap_or(0)
});

/// Safe listxattrat(2) wrapper which is new in Linux>=6.13.
///
/// # Safety
///
/// This function dereferences `addr` argument which is a raw pointer.
/// It is safe for this pointer to be NULL but otherwise it must point
/// to valid memory and the caller must ensure that.
pub unsafe fn listxattrat<F: AsRawFd, P: ?Sized + NixPath>(
    dirfd: Option<&F>,
    path: &P,
    flags: AtFlags,
    addr: *mut libc::c_char,
    size: usize,
) -> Result<usize, Errno> {
    let sysno = if *SYS_LISTXATTRAT > 0 {
        *SYS_LISTXATTRAT
    } else {
        return Err(Errno::ENOSYS);
    };

    path.with_nix_path(|c_path| {
        #[allow(clippy::cast_possible_truncation)]
        #[allow(clippy::cast_sign_loss)]
        Errno::result(
            // SAFETY: In libc we trust.
            unsafe {
                libc::syscall(
                    sysno as libc::c_long,
                    dirfd.map(|fd| fd.as_raw_fd()).unwrap_or(libc::AT_FDCWD),
                    c_path.as_ptr(),
                    flags.bits(),
                    addr,
                    size,
                )
            },
        )
        .map(|r| r as usize)
    })?
}

// Note removexattrat may not be available,
// and libc::SYS_removexattrat may not be defined.
// Therefore we query the number using libseccomp.
static SYS_REMOVEXATTRAT: Lazy<libc::c_long> = Lazy::new(|| {
    ScmpSyscall::from_name("removexattrat")
        .map(i32::from)
        .map(libc::c_long::from)
        .unwrap_or(0)
});

/// Safe removexattrat(2) wrapper, new in Linux>=6.13.
///
/// # Safety
///
/// This function dereferences `name` argument which is a raw pointer.
/// It is safe for this pointer to be NULL but otherwise it must point
/// to valid memory and the caller must ensure that.
pub unsafe fn removexattrat<F: AsRawFd, P: ?Sized + NixPath>(
    dirfd: Option<&F>,
    path: &P,
    name: *const libc::c_char,
    flags: AtFlags,
) -> Result<(), Errno> {
    let sysno = if *SYS_REMOVEXATTRAT > 0 {
        *SYS_REMOVEXATTRAT
    } else {
        return Err(Errno::ENOSYS);
    };

    path.with_nix_path(|c_path| {
        // SAFETY: In libc we trust.
        Errno::result(unsafe {
            libc::syscall(
                sysno as libc::c_long,
                dirfd.map(|fd| fd.as_raw_fd()).unwrap_or(libc::AT_FDCWD),
                c_path.as_ptr(),
                flags.bits(),
                name,
            )
        })
        .map(drop)
    })?
}

/// This structure represents the Linux data structure `struct statx_timestamp`
#[repr(C)]
#[derive(Copy, Clone, Debug, Default)]
pub struct FileStatxTimestamp {
    pub tv_sec: i64,
    pub tv_nsec: u32,
    __statx_timestamp_pad1: [i32; 1],
}

impl PartialEq for FileStatxTimestamp {
    fn eq(&self, other: &Self) -> bool {
        self.tv_sec == other.tv_sec && self.tv_nsec == other.tv_nsec
    }
}

impl Eq for FileStatxTimestamp {}

impl PartialOrd for FileStatxTimestamp {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

impl Ord for FileStatxTimestamp {
    fn cmp(&self, other: &Self) -> Ordering {
        match self.tv_sec.cmp(&other.tv_sec) {
            Ordering::Equal => self.tv_nsec.cmp(&other.tv_nsec),
            ord => ord,
        }
    }
}

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

        map.serialize_entry("sec", &self.tv_sec)?;
        map.serialize_entry("nsec", &self.tv_nsec)?;

        map.end()
    }
}

/// This structure represents the Linux data structure `struct statx`
#[repr(C)]
#[derive(Copy, Clone, Debug)]
pub struct FileStatx {
    pub stx_mask: u32,   // What results were written [uncond]
    stx_blksize: u32,    // Preferred general I/O size [uncond]
    stx_attributes: u64, // Flags conveying information about the file [uncond]

    pub stx_nlink: u32, // Number of hard links
    pub stx_uid: u32,   // User ID of owner
    pub stx_gid: u32,   // Group ID of owner
    pub stx_mode: u16,  // File mode
    __statx_pad1: [u16; 1],

    pub stx_ino: u64,         // Inode number
    pub stx_size: u64,        // File size
    stx_blocks: u64,          // Number of 512-byte blocks allocated
    stx_attributes_mask: u64, // Mask to show what's supported in stx_attributes

    pub stx_atime: FileStatxTimestamp, // Last access time
    stx_btime: FileStatxTimestamp,     // File creation time
    pub stx_ctime: FileStatxTimestamp, // Last attribute change time
    pub stx_mtime: FileStatxTimestamp, // Last data modification time

    pub stx_rdev_major: u32, // Device ID of special file [if bdev/cdev]
    pub stx_rdev_minor: u32,

    // Note, these are not not public on purpose
    // as they return inconsistent values on filesytems
    // such as btrfs and overlayfs. `stx_mnt_id` should
    // be used instead.
    pub(crate) stx_dev_major: u32, // ID of device containing file [uncond]
    pub(crate) stx_dev_minor: u32,

    pub stx_mnt_id: u64,
    stx_dio_mem_align: u32,    // Memory buffer alignment for direct I/O
    stx_dio_offset_align: u32, // File offset alignment for direct I/O

    __statx_pad2: [u64; 12], // Spare space for future expansion
}

impl FileStatx {
    pub(crate) fn file_mode(&self) -> libc::mode_t {
        libc::mode_t::from(self.stx_mode) & !libc::S_IFMT
    }

    pub(crate) fn file_type(&self) -> FileType {
        FileType::from(libc::mode_t::from(self.stx_mode))
    }
}

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

        map.serialize_entry("mask", &self.stx_mask)?;
        map.serialize_entry("nlink", &self.stx_nlink)?;
        map.serialize_entry("uid", &self.stx_uid)?;
        map.serialize_entry("gid", &self.stx_gid)?;
        map.serialize_entry("mode", &self.stx_mode)?;
        map.serialize_entry("file_mode", &self.file_mode())?;
        map.serialize_entry("file_type", &self.file_type())?;
        map.serialize_entry("ino", &self.stx_ino)?;
        map.serialize_entry("size", &self.stx_size)?;
        map.serialize_entry("atime", &self.stx_atime)?;
        map.serialize_entry("ctime", &self.stx_ctime)?;
        map.serialize_entry("mtime", &self.stx_mtime)?;
        map.serialize_entry("rdev_major", &self.stx_rdev_major)?;
        map.serialize_entry("rdev_minor", &self.stx_rdev_minor)?;
        map.serialize_entry("mnt_id", &self.stx_mnt_id)?;

        map.end()
    }
}

/// An iterator over directory entries obtained via the `getdents64`
/// system call.
///
/// This iterator yields `DirEntry` instances by reading from a
/// directory file descriptor.
///
/// # Safety
///
/// This struct uses unsafe code to interact with the `getdents64`
/// system call and to parse the resulting buffer into `dirent64`
/// structures. It manages the allocation and deallocation of the buffer
/// used for reading directory entries.
pub struct DirIter {
    buffer: *mut u8,
    bufsiz: usize,
    memsiz: usize,
    offset: usize,
}

const DIRENT_ALIGN: usize = std::mem::align_of::<libc::dirent64>();

impl DirIter {
    /// Creates a new `DirIter` for the given directory file descriptor.
    ///
    /// # Parameters
    ///
    /// - `fd`: The open directory file descriptor.
    /// - `bufsiz`: The size of directory entries to read in bytes.
    ///
    /// # Returns
    ///
    /// `Ok(DirIter)` if successful, or `Err(Errno)` if an error occurs.
    pub fn new<F: AsRawFd>(fd: &F, bufsiz: usize) -> Result<Self, Errno> {
        let layout =
            std::alloc::Layout::from_size_align(bufsiz, DIRENT_ALIGN).or(Err(Errno::EINVAL))?;

        // SAFETY: Allocate buffer with proper alignment and size.
        let buffer = unsafe { std::alloc::alloc(layout) };
        if buffer.is_null() {
            // Yes EINVAL, getdents64 does not return ENOMEM!
            return Err(Errno::EINVAL);
        }

        let retsiz = sys_getdents64(fd.as_raw_fd(), buffer.cast(), bufsiz)?;
        if retsiz == 0 {
            // EOF
            return Err(Errno::UnknownErrno);
        }

        Ok(Self {
            buffer,
            bufsiz: retsiz,
            memsiz: bufsiz,
            offset: 0,
        })
    }
}

impl<'a> Iterator for &'a mut DirIter {
    type Item = DirEntry<'a>;

    #[allow(clippy::arithmetic_side_effects)]
    fn next(&mut self) -> Option<Self::Item> {
        if self.offset >= self.bufsiz {
            return None;
        }

        // SAFETY: Parse the next dirent safely by borrowing from the buffer.
        unsafe {
            #[allow(clippy::cast_ptr_alignment)]
            let dirent_ptr = self.buffer.add(self.offset).cast::<libc::dirent64>();
            let d_reclen = (*dirent_ptr).d_reclen as usize;

            // Calculate the name length safely.
            let namelen = libc::strlen((*dirent_ptr).d_name.as_ptr());

            // Borrow the bytes of the `dirent64` structure from the buffer.
            let dirent = std::slice::from_raw_parts(dirent_ptr.cast::<u8>(), d_reclen);

            self.offset += d_reclen;
            Some(DirEntry { dirent, namelen })
        }
    }
}

impl Drop for DirIter {
    fn drop(&mut self) {
        #[allow(clippy::disallowed_methods)]
        let layout = std::alloc::Layout::from_size_align(self.memsiz, DIRENT_ALIGN).unwrap();

        // SAFETY: Deallocate buffer.
        unsafe { std::alloc::dealloc(self.buffer, layout) };
    }
}

impl std::fmt::Debug for DirIter {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("DirIter")
            .field("bufsiz", &self.bufsiz)
            .field("memsiz", &self.memsiz)
            .field("offset", &self.offset)
            .finish()
    }
}

/// This struct represents a directory entry.
#[derive(Clone)]
pub struct DirEntry<'a> {
    // The `dirent64` structure.
    dirent: &'a [u8],

    // Size of the file name, in bytes.
    namelen: usize,
}

impl std::fmt::Debug for DirEntry<'_> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_tuple("DirEntry")
            .field(&self.as_xpath())
            .field(&self.file_type())
            .finish()
    }
}

impl DirEntry<'_> {
    /// Return an `XPath` representing the name of the directory entry.
    pub fn as_xpath(&self) -> &XPath {
        XPath::from_bytes(self.name_bytes())
    }

    /// Returns the raw bytes of the `dirent64` structure.
    pub fn as_bytes(&self) -> &[u8] {
        self.dirent
    }

    /// Returns true if this is a dot entry.
    /// The special dot entries are `.` and `..`
    pub fn is_dot(&self) -> bool {
        if !self.is_dir() {
            return false;
        }
        let name = self.name_bytes();
        is_equal(name, b"..") || is_equal(name, b".")
    }

    /// Returns true if this is a directory entry.
    pub fn is_dir(&self) -> bool {
        self.file_type().is_dir()
    }

    /// Returns true if this is a regular file entry.
    pub fn is_file(&self) -> bool {
        self.file_type().is_file()
    }

    /// Returns true if this is a symbolic link entry.
    pub fn is_symlink(&self) -> bool {
        self.file_type().is_symlink()
    }

    /// Returns true if this is a block device entry.
    pub fn is_block_device(&self) -> bool {
        self.file_type().is_block_device()
    }

    /// Returns true if this is a character device entry.
    pub fn is_char_device(&self) -> bool {
        self.file_type().is_char_device()
    }

    /// Returns true if this is a FIFO entry.
    pub fn is_fifo(&self) -> bool {
        self.file_type().is_fifo()
    }

    /// Returns true if this is a socket entry.
    pub fn is_socket(&self) -> bool {
        self.file_type().is_socket()
    }

    /// Returns true if this is an unknown entry.
    pub fn is_unknown(&self) -> bool {
        self.file_type().is_unknown()
    }

    /// Returns the file type of the directory entry.
    ///
    /// The return value corresponds to one of the `DT_*` constants defined in `dirent.h`.
    ///
    /// # Safety
    ///
    /// This function assumes that `self.dirent` points to a valid `dirent64` structure,
    /// and that the `d_type` field is accessible without causing undefined behavior.
    pub fn file_type(&self) -> FileType {
        let dirent = self.dirent64();

        // SAFETY: We trust self.dirent points to a valid `dirent64` structure.
        FileType::from(unsafe { (*dirent).d_type })
    }

    /// Return the inode of this `DirEntry`.
    pub fn ino(&self) -> u64 {
        let dirent = self.dirent64();

        // SAFETY: We trust self.dirent points to a valid `dirent64` structure.
        unsafe { (*dirent).d_ino }
    }

    /// Return the size of this `DirEntry`.
    pub fn size(&self) -> usize {
        let dirent = self.dirent64();

        // SAFETY: We trust self.dirent points to a valid `dirent64` structure.
        unsafe { (*dirent).d_reclen as usize }
    }

    /// Return a byte slice of the entry name.
    pub fn name_bytes(&self) -> &[u8] {
        let dirent = self.dirent64();

        // SAFETY: We trust self.dirent points to a valid `dirent64` structure.
        unsafe {
            let d_name = (*dirent).d_name.as_ptr() as *const u8;
            std::slice::from_raw_parts(d_name, self.namelen)
        }
    }

    fn dirent64(&self) -> *const libc::dirent64 {
        // SAFETY: We trust self.dirent points to a valid `dirent64` structure.
        #![allow(clippy::cast_ptr_alignment)]
        self.dirent.as_ptr() as *const libc::dirent64
    }
}

/// Retrieve directory entries from an open directory file descriptor.
///
/// Returns an iterator over `DirEntry` instances.
///
/// # Parameters
///
/// - `fd`: The open directory file descriptor.
/// - `bufsiz`: The size of directory entries to read in bytes.
///
/// # Returns
///
/// `Ok(DirIter)` if successful, or `Err(Errno)` if an error occurs.
///
/// # Safety
///
/// This function calls the `getdents64` system call directly which is
/// an unsafe function.  Ensure that `fd` is a valid open directory file
/// descriptor to avoid undefined behavior.
pub fn getdents64<F: AsRawFd>(fd: &F, bufsiz: usize) -> Result<DirIter, Errno> {
    DirIter::new(fd, bufsiz)
}

/// Wrapper for the `getdents64` syscall.
#[allow(clippy::cast_possible_truncation)]
#[allow(clippy::cast_sign_loss)]
fn sys_getdents64(fd: RawFd, buf: *mut libc::c_void, bytes: usize) -> Result<usize, Errno> {
    Ok(
        // SAFETY: In kernel, we trust.
        Errno::result(unsafe { libc::syscall(nix::libc::SYS_getdents64, fd, buf, bytes) })?
            as usize,
    )
}

/// WaitStatus with support for signals that nix' Signal type don't support.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum WaitStatus {
    Exited(Pid, i32),
    Signaled(Pid, i32, bool),
    Stopped(Pid, i32),
    PtraceEvent(Pid, i32, libc::c_int),
    PtraceSyscall(Pid),
    Continued(Pid),
    StillAlive,
}

impl From<NixWaitStatus> for WaitStatus {
    fn from(status: NixWaitStatus) -> Self {
        match status {
            NixWaitStatus::Exited(pid, code) => WaitStatus::Exited(pid, code),
            NixWaitStatus::Signaled(pid, signal, core_dump) => {
                WaitStatus::Signaled(pid, signal as i32, core_dump)
            }
            NixWaitStatus::Stopped(pid, signal) => WaitStatus::Stopped(pid, signal as i32),
            NixWaitStatus::PtraceEvent(pid, signal, event) => {
                WaitStatus::PtraceEvent(pid, signal as i32, event)
            }
            NixWaitStatus::PtraceSyscall(pid) => WaitStatus::PtraceSyscall(pid),
            NixWaitStatus::Continued(pid) => WaitStatus::Continued(pid),
            NixWaitStatus::StillAlive => WaitStatus::StillAlive,
        }
    }
}

fn exited(status: i32) -> bool {
    libc::WIFEXITED(status)
}

fn exit_status(status: i32) -> i32 {
    libc::WEXITSTATUS(status)
}

fn signaled(status: i32) -> bool {
    libc::WIFSIGNALED(status)
}

fn term_signal(status: i32) -> i32 {
    libc::WTERMSIG(status)
}

fn dumped_core(status: i32) -> bool {
    libc::WCOREDUMP(status)
}

fn stopped(status: i32) -> bool {
    libc::WIFSTOPPED(status)
}

fn stop_signal(status: i32) -> i32 {
    libc::WSTOPSIG(status)
}

fn syscall_stop(status: i32) -> bool {
    // From ptrace(2), setting PTRACE_O_TRACESYSGOOD has the effect
    // of delivering SIGTRAP | 0x80 as the signal number for syscall
    // stops. This allows easily distinguishing syscall stops from
    // genuine SIGTRAP signals.
    libc::WSTOPSIG(status) == libc::SIGTRAP | 0x80
}

fn stop_additional(status: i32) -> libc::c_int {
    (status >> 16) as libc::c_int
}

fn continued(status: i32) -> bool {
    libc::WIFCONTINUED(status)
}

impl WaitStatus {
    pub(crate) fn from_raw(pid: Pid, status: i32) -> WaitStatus {
        if exited(status) {
            WaitStatus::Exited(pid, exit_status(status))
        } else if signaled(status) {
            WaitStatus::Signaled(pid, term_signal(status), dumped_core(status))
        } else if stopped(status) {
            let status_additional = stop_additional(status);
            if syscall_stop(status) {
                WaitStatus::PtraceSyscall(pid)
            } else if status_additional == 0 {
                WaitStatus::Stopped(pid, stop_signal(status))
            } else {
                WaitStatus::PtraceEvent(pid, stop_signal(status), stop_additional(status))
            }
        } else {
            assert!(continued(status));
            WaitStatus::Continued(pid)
        }
    }
}

/// Wrapper for the `waitid` syscall
/// This is identical to nix' waitid except we use our custom WaitStatus.
pub fn waitid(id: Id, flags: WaitPidFlag) -> Result<WaitStatus, Errno> {
    #[allow(clippy::cast_sign_loss)]
    let (idtype, idval) = match id {
        Id::All => (libc::P_ALL, 0),
        Id::Pid(pid) => (libc::P_PID, pid.as_raw() as nix::libc::id_t),
        Id::PGid(pid) => (libc::P_PGID, pid.as_raw() as nix::libc::id_t),
        Id::PIDFd(fd) => (libc::P_PIDFD, fd.as_raw_fd() as nix::libc::id_t),
        _ => unreachable!(),
    };

    // SAFETY: In libc, we trust.
    let siginfo = unsafe {
        // Memory is zeroed rather than uninitialized, as not all platforms
        // initialize the memory in the StillAlive case
        let mut siginfo: libc::siginfo_t = std::mem::zeroed();
        Errno::result(libc::waitid(idtype, idval, &mut siginfo, flags.bits()))?;
        siginfo
    };

    // SAFETY: In libc, we trust.
    let si_pid = unsafe { siginfo.si_pid() };
    if si_pid == 0 {
        return Ok(WaitStatus::StillAlive);
    }

    assert_eq!(siginfo.si_signo, libc::SIGCHLD);

    let pid = Pid::from_raw(si_pid);
    // SAFETY: In libc, we trust.
    let si_status = unsafe { siginfo.si_status() };

    let status = match siginfo.si_code {
        libc::CLD_EXITED => WaitStatus::Exited(pid, si_status),
        libc::CLD_KILLED | nix::libc::CLD_DUMPED => {
            WaitStatus::Signaled(pid, si_status, siginfo.si_code == libc::CLD_DUMPED)
        }
        libc::CLD_STOPPED => WaitStatus::Stopped(pid, si_status),
        libc::CLD_CONTINUED => WaitStatus::Continued(pid),
        libc::CLD_TRAPPED => {
            if si_status == libc::SIGTRAP | 0x80 {
                WaitStatus::PtraceSyscall(pid)
            } else {
                WaitStatus::PtraceEvent(pid, si_status & 0xff, (si_status >> 8) as libc::c_int)
            }
        }
        _ => return Err(Errno::EINVAL),
    };

    Ok(status)
}

pub(crate) fn pipe2_raw(flags: OFlag) -> Result<(RawFd, RawFd), Errno> {
    let mut fds = std::mem::MaybeUninit::<[RawFd; 2]>::uninit();

    // SAFETY: We use this when nix' version which returns an OwnedFd
    // does not work for our purposes e.g. in mini-threads spawned
    // by network syscall handlers.
    let res = unsafe { libc::pipe2(fds.as_mut_ptr().cast(), flags.bits()) };

    Errno::result(res)?;

    // SAFETY: pipe2 returns a valid array of fds.
    let [read, write] = unsafe { fds.assume_init() };
    Ok((read, write))
}

#[allow(clippy::cast_possible_truncation)]
pub(crate) const PF_UNSPEC: libc::sa_family_t = nix::libc::AF_UNSPEC as nix::libc::sa_family_t;
#[allow(clippy::cast_possible_truncation)]
pub(crate) const PF_UNIX: libc::sa_family_t = nix::libc::AF_UNIX as nix::libc::sa_family_t;
#[allow(clippy::cast_possible_truncation)]
pub(crate) const PF_INET: libc::sa_family_t = nix::libc::AF_INET as nix::libc::sa_family_t;
#[allow(clippy::cast_possible_truncation)]
pub(crate) const PF_INET6: libc::sa_family_t = nix::libc::AF_INET6 as nix::libc::sa_family_t;
#[allow(clippy::cast_possible_truncation)]
pub(crate) const PF_ALG: libc::sa_family_t = nix::libc::AF_ALG as nix::libc::sa_family_t;
#[allow(clippy::cast_possible_truncation)]
pub(crate) const PF_NETLINK: libc::sa_family_t = libc::AF_NETLINK as nix::libc::sa_family_t;

/// nix' SockAddrLike.family() function does not support AF_ALG.
/// This is a workaround that accesses the underlying sockaddr directly.
pub(crate) fn addr_family<T: SockaddrLike>(addr: &T) -> libc::sa_family_t {
    // SAFETY: This is safe as long as addr.as_ptr() returns a valid pointer to a sockaddr.
    unsafe { (*addr.as_ptr()).sa_family }
}

/// Safe truncate64() wrapper.
pub fn truncate64<P: ?Sized + NixPath>(pathname: &P, len: libc::off64_t) -> Result<(), Errno> {
    // SAFETY: Neither nix nor libc has a wrapper for statx.
    Errno::result(pathname.with_nix_path(|cstr| unsafe { libc::truncate64(cstr.as_ptr(), len) })?)
        .map(drop)
}

/// Safe ftruncate64() wrapper.
pub fn ftruncate64(fd: RawFd, len: libc::off64_t) -> Result<(), Errno> {
    // SAFETY: nix does not have a ftruncate64 wrapper.
    Errno::result(unsafe { libc::ftruncate64(fd, len) }).map(drop)
}

/// Want/got stx_mode & S_IFMT.
pub const STATX_TYPE: libc::c_uint = 0x00000001;
/// Want/got stx_mode & ~S_IFMT.
pub const STATX_MODE: libc::c_uint = 0x00000002;
/// Want/got stx_nlink.
pub const STATX_NLINK: libc::c_uint = 0x00000004;
/// Want/got stx_uid.
pub const STATX_UID: libc::c_uint = 0x00000008;
/// Want/got stx_gid.
pub const STATX_GID: libc::c_uint = 0x00000010;
/// Want/got stx_ctime.
pub const STATX_CTIME: libc::c_uint = 0x00000080;
/// Want/got stx_ino.
pub const STATX_INO: libc::c_uint = 0x00000100;
/// Want/got stx_size.
pub const STATX_SIZE: libc::c_uint = 0x00000200;
/// Want/Got stx_mnt_id.
pub const STATX_MNT_ID: libc::c_uint = 0x00001000;
/// Want/got extended stx_mount_id, requires Linux>=6.8.
pub const STATX_MNT_ID_UNIQUE: libc::c_uint = 0x00004000;
/// Want all the basic stat information.
pub const STATX_BASIC_STATS: libc::c_uint = 0x000007ff;

/// Do what stat(2) does, default.
pub const AT_STATX_SYNC_AS_STAT: libc::c_int = 0x0000;

/// Sync changes with the remote filesystem.
pub const AT_STATX_FORCE_SYNC: libc::c_int = 0x2000;

/// Do not sync with remote filesystem.
pub const AT_STATX_DONT_SYNC: libc::c_int = 0x4000;

/// Safe statx() wrapper.
pub fn statx<F: AsRawFd, P: ?Sized + NixPath>(
    dirfd: Option<&F>,
    pathname: &P,
    flags: libc::c_int,
    mask: libc::c_uint,
) -> Result<FileStatx, Errno> {
    let dirfd = dirfd.map(|fd| fd.as_raw_fd()).unwrap_or(libc::AT_FDCWD);
    let mut dst = std::mem::MaybeUninit::uninit();

    // SAFETY: Neither nix nor libc has a wrapper for statx.
    Errno::result(pathname.with_nix_path(|cstr| unsafe {
        libc::syscall(
            libc::SYS_statx,
            dirfd,
            cstr.as_ptr(),
            flags,
            mask,
            dst.as_mut_ptr(),
        )
    })?)?;

    // SAFETY: statx returned success.
    Ok(unsafe { dst.assume_init() })
}

/// Safe statx() wrapper to use with a FD only.
pub fn fstatx<F: AsRawFd>(fd: &F, mask: libc::c_uint) -> Result<FileStatx, Errno> {
    let fd = fd.as_raw_fd();
    let mut dst = std::mem::MaybeUninit::uninit();

    // SAFETY: Neither nix nor libc has a wrapper for statx.
    Errno::result(unsafe {
        libc::syscall(
            libc::SYS_statx,
            fd,
            c"".as_ptr(),
            libc::AT_EMPTY_PATH,
            mask,
            dst.as_mut_ptr(),
        )
    })?;

    // SAFETY: statx returned success.
    Ok(unsafe { dst.assume_init() })
}

/// Wrapper for struct stat64.
pub(crate) use libc::stat64 as FileStat64;

pub(crate) fn fstatat64<P: ?Sized + NixPath>(
    dirfd: Option<RawFd>,
    pathname: &P,
    flags: libc::c_int,
) -> Result<FileStat64, Errno> {
    let dirfd = dirfd.unwrap_or(libc::AT_FDCWD);
    let mut dst = std::mem::MaybeUninit::uninit();

    // SAFETY: nix does not has a wrapper for fstatat64.
    Errno::result(pathname.with_nix_path(|cstr| unsafe {
        libc::fstatat64(dirfd, cstr.as_ptr(), dst.as_mut_ptr(), flags)
    })?)?;

    // SAFETY: fstatat64 returned success.
    Ok(unsafe { dst.assume_init() })
}

#[cfg(target_os = "freebsd")]
type fs_type_t = u32;
#[cfg(target_os = "android")]
type fs_type_t = libc::c_ulong;
#[cfg(all(target_os = "linux", target_arch = "s390x", not(target_env = "musl")))]
type fs_type_t = libc::c_uint;
#[cfg(all(target_os = "linux", target_env = "musl"))]
type fs_type_t = libc::c_ulong;
#[cfg(all(target_os = "linux", target_env = "ohos"))]
type fs_type_t = libc::c_ulong;
#[cfg(all(target_os = "linux", target_env = "uclibc"))]
type fs_type_t = libc::c_int;
#[cfg(all(
    target_os = "linux",
    not(any(
        target_arch = "s390x",
        target_env = "musl",
        target_env = "ohos",
        target_env = "uclibc"
    ))
))]
type fs_type_t = libc::__fsword_t;

const BTRFS_SUPER_MAGIC: fs_type_t = libc::BTRFS_SUPER_MAGIC as fs_type_t;
const OVERLAYFS_SUPER_MAGIC: fs_type_t = libc::OVERLAYFS_SUPER_MAGIC as fs_type_t;

/// Wrapper for struct statfs64
pub(crate) struct Statfs64(libc::statfs64);

impl Statfs64 {
    // WORKAROUND:
    // Check if the file resides on a btrfs|overlayfs.
    // Overlayfs does not report device IDs correctly on
    // fstat, which is a known bug:
    // https://github.com/moby/moby/issues/43512
    // Btrfs has the same issue:
    // https://www.reddit.com/r/btrfs/comments/1clgd8u/different_dev_id_reported_by_statx_and/
    // Assume true on errors for safety.
    pub(crate) fn has_broken_device_ids(&self) -> bool {
        matches!(self.0.f_type, OVERLAYFS_SUPER_MAGIC | BTRFS_SUPER_MAGIC)
    }
}

/// Safe wrapper for fstatfs64
pub(crate) fn fstatfs64<F: AsRawFd>(fd: &F) -> Result<Statfs64, Errno> {
    let mut dst = std::mem::MaybeUninit::uninit();

    // SAFETY: nix does not have a wrapper for fstatfs64.
    Errno::result(unsafe { libc::fstatfs64(fd.as_raw_fd(), dst.as_mut_ptr()) })?;

    // SAFETY: fstatfs64 returned success.
    Ok(Statfs64(unsafe { dst.assume_init() }))
}

/// Safe wrapper for epoll_ctl with detailed error handling
pub fn epoll_ctl_safe<E: AsFd>(
    epoll: &E,
    fd: RawFd,
    event: Option<libc::epoll_event>,
) -> Result<(), Errno> {
    let (result, ignore_errno) = if let Some(mut event) = event {
        (
            // SAFETY: nix deprecated epoll_ctl and Epoll requires an OwnedFd...
            // Ignore EEXIST for EPOLL_CTL_ADD.
            Errno::result(unsafe {
                libc::epoll_ctl(
                    epoll.as_fd().as_raw_fd(),
                    EpollOp::EpollCtlAdd as libc::c_int,
                    fd,
                    &mut event,
                )
            }),
            Errno::EEXIST,
        )
    } else {
        (
            // SAFETY: nix deprecated epoll_ctl and Epoll requires an OwnedFd...
            // Ignore ENOENT for EPOLL_CTL_DEL.
            Errno::result(unsafe {
                libc::epoll_ctl(
                    epoll.as_fd().as_raw_fd(),
                    EpollOp::EpollCtlDel as libc::c_int,
                    fd,
                    std::ptr::null_mut(),
                )
            }),
            Errno::ENOENT,
        )
    };
    match result {
        Ok(_) => Ok(()),
        Err(errno) if errno == ignore_errno => Ok(()),
        Err(errno) => Err(errno),
    }
}

const EPIOCSPARAMS: IoctlRequest = 0x40088a01;
const EPIOCGPARAMS: IoctlRequest = 0x80088a02u32 as IoctlRequest;

/// Epoll parameters
#[repr(C)]
pub struct EpollParams {
    /// Number of usecs to busy poll
    pub busy_poll_usecs: u32,
    /// Max packets per poll
    pub busy_poll_budget: u16,
    /// Boolean preference
    pub prefer_busy_poll: u16,
    // pad the struct to a multiple of 64bits
    // must be zero.
    pad: u8,
}

impl EpollParams {
    /// Create a new EpollParams structure.
    pub fn new(busy_poll_usecs: u32, busy_poll_budget: u16, prefer_busy_poll: bool) -> Self {
        let prefer_busy_poll = if prefer_busy_poll { 1 } else { 0 };
        Self {
            busy_poll_usecs,
            busy_poll_budget,
            prefer_busy_poll,
            pad: 0,
        }
    }
}

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

        // Serialize busy_poll_usecs field.
        map.serialize_entry("busy_poll_usecs", &self.busy_poll_usecs)?;

        // Serialize busy_poll_budget field.
        map.serialize_entry("busy_poll_budget", &self.busy_poll_budget)?;

        // Serialize prefer_busy_poll.
        map.serialize_entry("prefer_busy_poll", &(self.prefer_busy_poll != 0))?;

        map.end()
    }
}

/// Set epoll parameters for the given epoll file descriptor.
/// Requires Linux>=6.9.
pub fn epoll_set_params<F: AsRawFd>(fd: &F, params: &EpollParams) -> Result<(), Errno> {
    // SAFETY: In libc we trust.
    Errno::result(unsafe { libc::ioctl(fd.as_raw_fd(), EPIOCSPARAMS, params) }).map(drop)
}

/// Get epoll parameters for the given epoll file descriptor.
/// Requires Linux>=6.9.
pub fn epoll_get_params<F: AsRawFd>(fd: &F) -> Result<EpollParams, Errno> {
    let mut params = std::mem::MaybeUninit::uninit();

    // SAFETY: In libc we trust.
    Errno::result(unsafe { libc::ioctl(fd.as_raw_fd(), EPIOCGPARAMS, params.as_mut_ptr()) })?;

    // SAFETY: ioctl returned success.
    Ok(unsafe { params.assume_init() })
}

/// Uses getsockopt SO_DOMAIN to get the domain of the given socket.
pub fn getsockdomain<F: AsRawFd>(fd: &F) -> Result<libc::c_int, Errno> {
    #[allow(clippy::cast_possible_truncation)]
    let mut len = std::mem::size_of::<libc::c_int>() as nix::libc::socklen_t;
    let mut fml: libc::c_int = 0;

    // SAFETY: In libc we trust.
    Errno::result(unsafe {
        libc::getsockopt(
            fd.as_raw_fd(),
            libc::SOL_SOCKET,
            libc::SO_DOMAIN,
            std::ptr::addr_of_mut!(fml) as *mut _,
            &mut len,
        )
    })?;

    Ok(fml)
}
