fdf 0.9.3

A fast, multi-threaded filesystem search tool with regex/glob support and extremely pretty colours!
Documentation
use core::marker::Copy;
use core::mem::MaybeUninit;
use core::ops::{Index, IndexMut};
use core::slice::SliceIndex;
mod sealed {
    /// Sealed trait pattern to restrict `ValueType` implementation to i8 and u8 only
    pub trait Sealed {}
    impl Sealed for i8 {}
    impl Sealed for u8 {}
    impl Sealed for u64 {}
    impl Sealed for i64 {}
    impl Sealed for usize {}
    impl Sealed for isize {}
    impl Sealed for i32 {}
    impl Sealed for u32 {}
}

/// Marker trait for valid buffer value types (i8 and u8)
///
/// This trait ensures type safety while allowing the buffer to work with both
/// signed and unsigned byte types, which are equivalent for raw memory operations.
pub trait ValueType: sealed::Sealed + Copy {}

impl ValueType for i8 {}
impl ValueType for u8 {}
impl ValueType for u64 {}
impl ValueType for i64 {}
impl ValueType for usize {}
impl ValueType for isize {}
impl ValueType for i32 {}
impl ValueType for u32 {}

/**
 A optimised, aligned buffer for system call operations

 This buffer provides memory-aligned storage with several key features:
 - Guaranteed 8-byte alignment required by various system calls
 - Zero-cost abstraction for working with raw memory
 - Support for both i8 and u8 types (equivalent for byte operations)
 - Safe access methods with proper bounds checking
 - Lazy initialisation to avoid unnecessary memory writes (initialising with 0's just to overwrite them.)

 # Type Parameters
 - `T`: The element type (i8 or u8)
 - `SIZE`: The fixed capacity of the buffer

 # Safety
 The buffer uses `MaybeUninit` internally, so users must ensure proper
 initialisation before accessing the contents. All unsafe methods document
 their safety requirements.

 # Examples
 ```
 use fdf::fs::AlignedBuffer;

 // Create a new aligned buffer
 // Purposely set a non-aligned amount to show alignment is forced.
 let mut buffer = AlignedBuffer::<u8, 1026>::new(); //You should really use 1024 here.

 // Initialise the buffer with data
 let data = b"Hello, World!";
 unsafe {
     // Copy data into the buffer
     core::ptr::copy_nonoverlapping(
         data.as_ptr(),
         buffer.as_mut_ptr(),
         data.len()
     );

     // Access the initialised data
     let slice = buffer.get_unchecked(0..data.len());
     assert_eq!(slice, data);

     // Modify the buffer contents
     let mut_slice = buffer.get_unchecked_mut(0..data.len());
     mut_slice[0] = b'h'; // Change 'H' to 'h'
     assert_eq!(&mut_slice[0..5], b"hello");
 }

 // The buffer maintains proper alignment for syscalls
 //Protip: NEVER cast a ptr to a usize unless you're extremely sure of what you're doing!
 assert!(buffer.as_ptr().cast::<usize>().addr().is_multiple_of(8),"We expect the buffer to be aligned to 8 bytes")
 ```
*/
#[derive(Debug)]
#[repr(C, align(8))] // Ensure 8-byte alignment,uninitialised memory isn't a concern because it's always actually initialised before use.
pub struct AlignedBuffer<T, const SIZE: usize>
where
    T: ValueType, // Only generic over i8 and u8!
{
    // Generic over size.
    pub(crate) data: MaybeUninit<[T; SIZE]>,
}

impl<T, const SIZE: usize, Idx> Index<Idx> for AlignedBuffer<T, SIZE>
where
    T: ValueType,
    Idx: SliceIndex<[T]>,
{
    type Output = Idx::Output;

    #[inline]
    fn index(&self, index: Idx) -> &Self::Output {
        // SAFETY: The buffer must initialised
        unsafe { self.assume_init().get_unchecked(index) }
    }
}

impl<T, const SIZE: usize, Idx> IndexMut<Idx> for AlignedBuffer<T, SIZE>
where
    T: ValueType,
    Idx: SliceIndex<[T]>,
{
    #[inline]
    fn index_mut(&mut self, index: Idx) -> &mut Self::Output {
        // SAFETY: The buffer must be initialised before access
        unsafe { self.assume_init_mut().get_unchecked_mut(index) }
    }
}

impl<T> Default for AlignedBuffer<T, { crate::fs::types::BUFFER_SIZE }>
where
    T: ValueType
        + Default
        + Copy
        + core::ops::Add
        + core::ops::Sub
        + core::ops::Mul
        + core::ops::Div,
{
    #[inline]
    /// Defaults to the recommended buffer size for getdents(64)/getdirentries(64) on your OS.
    fn default() -> Self {
        Self {
            data: MaybeUninit::new([T::default(); crate::fs::types::BUFFER_SIZE]),
        }
    }
}

impl<T, const SIZE: usize> AlignedBuffer<T, SIZE>
where
    T: ValueType,
{
    /**
    Creates a new uninitialised aligned buffer

    The buffer will have 8-byte alignment but its contents will be uninitialised.
    You must initialised the buffer before accessing its contents.
    */
    #[must_use]
    #[inline]
    pub const fn new() -> Self {
        Self {
            data: MaybeUninit::uninit(),
        }
    }
    /// Returns the size of which this buffer was created by (counted in raw bytes)
    pub const BUFFER_SIZE: usize = size_of::<T>() * SIZE;

    /// Returns a mutable pointer to the buffer's data
    #[inline]
    #[must_use]
    pub const fn as_mut_ptr(&mut self) -> *mut T {
        self.data.as_mut_ptr().cast()
    }

    /// Returns a const pointer to the buffer's data
    #[inline]
    #[must_use]
    pub const fn as_ptr(&self) -> *const T {
        self.data.as_ptr().cast()
    }

    /**
     Returns a slice of the buffer's contents

     # Safety
     The buffer must be fully initialised before calling this method.
     Accessing uninitialised memory is undefined behavior.
    */
    #[inline]
    pub const unsafe fn as_slice(&self) -> &[T] {
        // SAFETY: Caller must ensure the buffer is fully initialised
        unsafe { &*self.data.as_ptr() }
    }
    /**
     Returns a mutable slice of the buffer's contents

     # Safety
     The buffer must be fully initialised before calling this method.
     Accessing uninitialised memory is undefined behavior.
    */
    #[inline]
    pub const unsafe fn as_mut_slice(&mut self) -> &mut [T] {
        // SAFETY: Caller must ensure the buffer is fully initialised
        unsafe { &mut *self.data.as_mut_ptr() }
    }

    /// Executes the getdents(64) system call using <unistd.h>/direct `libc` syscalls
    /// Supproted on Linux/Android/OpenBSD/NetBSD
    #[inline]
    #[cfg(any(
        target_os = "linux",
        target_os = "android",
        target_os = "openbsd",
        target_os = "netbsd",
        target_os = "solaris",
        target_os = "illumos"
    ))]
    pub fn getdents(&mut self, fd: &crate::fs::FileDes) -> isize {
        // SAFETY: we're passing a valid buffer
        unsafe { crate::util::getdents64(fd.0, self.as_mut_ptr().cast(), Self::BUFFER_SIZE) }
    }

    /// Executes the `getdirentries64` system call
    /// Supported on macOS and FreeBSD.
    ///
    /// On success, the return value is the number of bytes written into this buffer.
    /// A return value of `0` indicates end-of-directory. Negative values indicate an
    /// OS error as returned by the underlying syscall wrapper.
    ///
    /// Caveats
    /// - This method is `unsafe` because the kernel only initialises the first `n`
    ///   bytes it writes. Callers must only read the returned byte count, not the
    ///   whole buffer.
    /// - `basep` is an in/out directory position cookie. It must point to valid,
    ///   writable memory for the duration of the call, and the updated value should
    ///   be preserved if the caller needs to resume iteration correctly.
    ///
    ///
    ///
    /// # Safety
    /// The caller must ensure `fd` refers to an open directory and that `basep`
    /// remains a valid mutable reference for the duration of the call.
    ///  Only Available on macOS and FreeBSD (with dragonfly to be expected in future update)
    #[inline]
    #[cfg(any(target_os = "macos", target_os = "freebsd"))]
    pub unsafe fn getdirentries64(&mut self, fd: &crate::fs::FileDes, basep: &mut i64) -> isize {
        // SAFETY: we're passing a valid buffer and valid base pointer
        unsafe {
            crate::util::getdirentries64(
                fd.0,
                self.as_mut_ptr().cast(),
                Self::BUFFER_SIZE,
                core::ptr::from_mut(basep),
            )
        }
    }

    /**
     Returns a reference to a subslice without doing bounds checking

     # Safety

     The caller must ensure:
     - The buffer is fully initialised
     - The range is within the bounds of the buffer (0..SIZE)
     - The range does not access uninitialised memory
    */
    #[inline]
    pub unsafe fn get_unchecked<R>(&self, range: R) -> &R::Output
    where
        R: SliceIndex<[T]>,
    {
        // SAFETY: Caller must ensure the buffer is initialised and range is valid
        unsafe { self.as_slice().get_unchecked(range) }
    }

    /**
    Returns a mutable reference to a subslice without doing bounds checking

    # Safety
    The range must be within initialised portion of the buffer
    */
    #[inline]
    pub unsafe fn get_unchecked_mut<R>(&mut self, range: R) -> &mut R::Output
    where
        R: SliceIndex<[T]>,
    {
        // SAFETY: Caller must ensure the buffer is fully initialised
        unsafe { self.as_mut_slice().get_unchecked_mut(range) }
    }

    /**
     Assumes the buffer is initialised and returns a reference to the contents

     # Safety
     The caller must guarantee the entire buffer has been properly initialised
     before calling this method. Accessing uninitialised memory is undefined behavior.
    */
    #[inline]
    const unsafe fn assume_init(&self) -> &[T; SIZE] {
        // SAFETY: Caller must ensure the buffer is fully initialised
        unsafe { &*self.data.as_ptr() }
    }

    /**
     Assumes the buffer is initialised and returns a mutable reference to the contents

     # Safety
     The caller must guarantee the entire buffer has been properly initialised
     before calling this method. Accessing uninitialised memory is undefined behavior
    */
    #[inline]
    const unsafe fn assume_init_mut(&mut self) -> &mut [T; SIZE] {
        // SAFETY: Caller must ensure the buffer is fully initialised
        unsafe { &mut *self.data.as_mut_ptr() }
    }
}