#[cfg(test)]
mod tests;
use core::iter::FusedIterator;
use core::mem::{offset_of, size_of};
use std::path::{Path, PathBuf};
use nom::bytes::complete::{tag as nom_tag, take as nom_take};
use nom::combinator::peek as nom_peek;
use nom::number::complete::{u32 as nom_u32, u8 as nom_u8};
use nom::number::Endianness;
use nom::sequence::{preceded as nom_preceded, terminated as nom_terminated};
use crate::utils::{map_file, CStringTable, HashTableIter};
use crate::{CacheProvider, Error, Result};
static CACHE_FILE_PATH: &str = "/etc/ld.so.cache";
static MAGIC: &[u8] = b"glibc-ld.so.cache1.1";
#[repr(C)]
struct Header {
magic: [u8; 20],
lib_count: u32,
string_table_size: u32,
flags: u8,
flags_padding: [u8; 3],
extension_offset: u32,
unused: [u32; 3],
}
#[repr(C)]
struct Entry {
flags: u32,
key: u32,
value: u32,
os_version: u32,
hw_cap: u64,
}
#[derive(Debug)]
struct ParsedHeader {
byte_order: Endianness,
lib_count: usize,
string_table_size: usize,
}
impl ParsedHeader {
fn parse(path: &Path, bytes: &[u8]) -> Result<Self> {
let (_input, flags) =
Self::parse_flags(bytes).map_err(|r| Error::from_nom_parse(r, bytes, path))?;
let byte_order = match flags & 0b11 {
0 => Endianness::Native,
1 => {
let r = nom::error::make_error(bytes, nom::error::ErrorKind::IsA);
return Err(Error::from_nom_parse(nom::Err::Error(r), bytes, path));
}
2 => Endianness::Little,
3 => Endianness::Big,
4..=u8::MAX => unreachable!(),
};
let (_input, fields) = Self::parse_fields(bytes, byte_order)
.map_err(|r| Error::from_nom_parse(r, bytes, path))?;
let result = Self {
byte_order,
lib_count: usize::try_from(fields.0)?,
string_table_size: usize::try_from(fields.1)?,
};
result.validate(path, bytes).map(|()| result)
}
fn parse_flags(bytes: &[u8]) -> nom::IResult<&[u8], u8> {
use nom::Parser;
nom_preceded(nom_take(offset_of!(Header, flags)), nom_u8).parse(bytes)
}
fn parse_fields(bytes: &[u8], byte_order: Endianness) -> nom::IResult<&[u8], (u32, u32)> {
use nom::Parser;
(
nom_preceded(nom_tag(MAGIC), nom_u32(byte_order)),
nom_terminated(
nom_u32(byte_order),
nom_take(size_of::<Header>().saturating_sub(offset_of!(Header, flags))),
),
)
.parse(bytes)
}
fn validate(&self, path: &Path, bytes: &[u8]) -> Result<()> {
use nom::Parser;
let max_lib_count = bytes.len().saturating_sub(size_of::<Header>()) / size_of::<Entry>();
let max_string_table_size = bytes
.len()
.saturating_sub(size_of::<Header>())
.saturating_sub(self.lib_count.saturating_mul(size_of::<Entry>()));
if self.lib_count > max_lib_count || self.string_table_size > max_string_table_size {
let r = nom::error::make_error(bytes, nom::error::ErrorKind::TooLarge);
return Err(Error::from_nom_parse(nom::Err::Error(r), bytes, path));
}
let min_size = size_of::<Header>()
.saturating_add(size_of::<Entry>().saturating_mul(self.lib_count))
.saturating_add(self.string_table_size);
nom_peek(nom_take(min_size))
.parse(bytes)
.map(|_| ())
.map_err(|r| Error::from_nom_parse(r, bytes, path))
}
}
#[derive(Debug)]
pub struct Cache {
path: PathBuf,
map: memmap2::Mmap,
byte_order: Endianness,
lib_count: usize,
}
impl Cache {
pub fn load_default() -> Result<Self> {
Self::load(CACHE_FILE_PATH)
}
pub fn load(path: impl AsRef<Path>) -> Result<Self> {
let path = path.as_ref();
let map = map_file(path)?;
let header = ParsedHeader::parse(path, &map)?;
Ok(Self {
path: path.into(),
map,
byte_order: header.byte_order,
lib_count: header.lib_count,
})
}
pub fn iter(&self) -> Result<impl FusedIterator<Item = Result<crate::Entry<'_>>> + '_> {
Ok(HashTableIter::<{ size_of::<Entry>() }, _>::new(
&self.map[self.hash_table_range()],
CStringTable::new(&self.map[self.string_table_range()], &self.path),
self.next_hash_table_entry_parser(),
))
}
fn hash_table_range(&self) -> core::ops::Range<usize> {
let size = self.lib_count.saturating_mul(size_of::<Entry>());
size_of::<Header>()..size_of::<Header>().saturating_add(size)
}
fn string_table_range(&self) -> core::ops::Range<usize> {
0..self.map.len()
}
fn next_hash_table_entry_parser(
&self,
) -> impl nom::Parser<&[u8], Output = (u32, u32), Error = nom::error::Error<&[u8]>> {
(
nom_preceded(nom_take(offset_of!(Entry, key)), nom_u32(self.byte_order)),
nom_terminated(
nom_u32(self.byte_order),
nom_take(size_of::<Entry>().saturating_sub(offset_of!(Entry, os_version))),
),
)
}
}
impl CacheProvider for Cache {
fn entries_iter<'cache>(
&'cache self,
) -> Result<Box<dyn FusedIterator<Item = Result<crate::Entry<'cache>>> + 'cache>> {
let iter = self.iter()?;
Ok(Box::new(iter))
}
}