dynamic-loader-cache 0.2.3

Reader of the dynamic loader shared libraries cache
Documentation
// Copyright 2024-2025 Koutheir Attouchi.
// See the "LICENSE.txt" file at the top-level directory of this distribution.
//
// Licensed under the MIT license. This file may not be copied, modified,
// or distributed except according to those terms.

use alloc::borrow::Cow;
use core::ffi::CStr;
use core::iter::FusedIterator;
#[cfg(unix)]
use std::ffi::OsStr;
#[cfg(not(unix))]
use std::ffi::OsString;
use std::fs::File;
use std::path::Path;
#[cfg(not(unix))]
use std::path::PathBuf;

use memmap2::{Mmap, MmapOptions};

use crate::errors::Error;
use crate::Result;

#[cfg(unix)]
pub(crate) fn os_str_from_cstr(cstr: &CStr) -> Result<&OsStr> {
    use std::os::unix::ffi::OsStrExt;

    Ok(OsStr::from_bytes(cstr.to_bytes()))
}

#[cfg(windows)]
pub(crate) fn os_string_from_cstr(cstr: &CStr) -> Result<OsString> {
    use std::os::windows::ffi::OsStringExt;

    let wstr: Vec<_> = cstr.to_str()?.encode_utf16().collect();
    Ok(OsString::from_wide(&wstr))
}

#[cfg(unix)]
pub(crate) fn path_from_cstr(cstr: &CStr) -> Result<&Path> {
    os_str_from_cstr(cstr).map(Path::new)
}

#[cfg(not(unix))]
pub(crate) fn path_buf_from_cstr(cstr: &CStr) -> Result<PathBuf> {
    os_string_from_cstr(cstr).map(PathBuf::from)
}

#[cfg(unix)]
pub(crate) fn path_from_bytes(bytes: &[u8]) -> Result<Cow<Path>> {
    use std::os::unix::ffi::OsStrExt;

    Ok(Cow::Borrowed(Path::new(OsStr::from_bytes(bytes))))
}

#[cfg(windows)]
pub(crate) fn path_from_bytes(bytes: &[u8]) -> Result<Cow<Path>> {
    use std::os::windows::ffi::OsStringExt;

    let wstr: Vec<_> = std::str::from_utf8(bytes)?.encode_utf16().collect();
    Ok(Cow::Owned(PathBuf::from(OsString::from_wide(&wstr))))
}

pub(crate) fn map_file(path: &Path) -> Result<Mmap> {
    let file = File::open(path).map_err(|source| Error::Open {
        source,
        path: path.into(),
    })?;

    let md = file.metadata().map_err(|source| Error::ReadMetaData {
        source,
        path: path.into(),
    })?;

    let size = usize::try_from(md.len()).unwrap_or(usize::MAX);
    if size == 0 {
        return Err(Error::FileIsEmpty { path: path.into() });
    }

    unsafe { MmapOptions::default().len(size).map(&file) }.map_err(|source| Error::MapFile {
        source,
        path: path.into(),
    })
}

#[derive(Debug)]
pub(crate) struct CStringTable<'table> {
    string_table: &'table [u8],
    pub(crate) path: &'table Path,
}

impl<'table> CStringTable<'table> {
    pub(crate) fn new(string_table: &'table [u8], path: &'table Path) -> Self {
        Self { string_table, path }
    }

    pub(crate) fn get(&self, start_index: u32) -> Result<&'table CStr> {
        let start_index = usize::try_from(start_index)?;

        let bytes = self
            .string_table
            .get(start_index..)
            .ok_or_else(|| Error::OffsetIsInvalid {
                path: self.path.into(),
            })?;

        CStr::from_bytes_until_nul(bytes).map_err(Error::from)
    }
}

#[derive(Debug)]
pub(crate) struct HashTableIter<'cache, const BUCKET_SIZE: usize, P> {
    hash_table: &'cache [u8],
    string_table: CStringTable<'cache>,
    next_hash_table_entry_parser: P,
}

impl<'cache, const BUCKET_SIZE: usize, P> HashTableIter<'cache, BUCKET_SIZE, P> {
    pub(crate) fn new(
        hash_table: &'cache [u8],
        string_table: CStringTable<'cache>,
        next_hash_table_entry_parser: P,
    ) -> Self {
        Self {
            hash_table,
            string_table,
            next_hash_table_entry_parser,
        }
    }
}

impl<'cache, const BUCKET_SIZE: usize, P> Iterator for HashTableIter<'cache, BUCKET_SIZE, P>
where
    P: nom::Parser<&'cache [u8], Output = (u32, u32), Error = nom::error::Error<&'cache [u8]>>,
{
    type Item = Result<crate::Entry<'cache>>;

    fn next(&mut self) -> Option<Self::Item> {
        (self.hash_table.len() >= BUCKET_SIZE).then(|| {
            let (input, (key, value)) = self
                .next_hash_table_entry_parser
                .parse(self.hash_table)
                .map_err(|r| Error::from_nom_parse(r, self.hash_table, self.string_table.path))?;

            self.hash_table = input;

            crate::Entry::new_by_string_table_indices(&self.string_table, key, value)
        })
    }

    fn size_hint(&self) -> (usize, Option<usize>) {
        let remaining = self.hash_table.len() / BUCKET_SIZE;
        (remaining, Some(remaining))
    }
}

impl<'cache, const BUCKET_SIZE: usize, P> FusedIterator for HashTableIter<'cache, BUCKET_SIZE, P> where
    P: nom::Parser<&'cache [u8], Output = (u32, u32), Error = nom::error::Error<&'cache [u8]>>
{
}

impl<'cache, const BUCKET_SIZE: usize, P> ExactSizeIterator
    for HashTableIter<'cache, BUCKET_SIZE, P>
where
    P: nom::Parser<&'cache [u8], Output = (u32, u32), Error = nom::error::Error<&'cache [u8]>>,
{
}