#[cfg(test)]
mod tests;
use core::ffi::c_int;
use core::iter::FusedIterator;
use core::mem::{offset_of, size_of};
use std::path::{Path, PathBuf};
use nom::branch::alt as nom_alt;
use nom::bytes::complete::{tag as nom_tag, take as nom_take};
use nom::combinator::{into as nom_into, peek as nom_peek};
use nom::number::complete::{u32 as nom_u32, u64 as nom_u64};
use nom::number::Endianness;
use nom::sequence::terminated as nom_terminated;
use static_assertions::assert_eq_size;
use crate::utils::{map_file, CStringTable, HashTableIter};
use crate::{CacheProvider, DataModel, Error, Result};
static CACHE_FILE_PATH: &str = "/var/run/ld.so.hints";
const MAGIC: u32 = 0x4c_44_48_69_u32;
const MAGIC_LE32: [u8; 4] = MAGIC.to_le_bytes();
const MAGIC_BE32: [u8; 4] = MAGIC.to_be_bytes();
const MAGIC_LE64: [u8; 8] = (MAGIC as u64).to_le_bytes();
const MAGIC_BE64: [u8; 8] = (MAGIC as u64).to_be_bytes();
const VERSION_2: u32 = 2;
const VERSION_2_LE32: [u8; 4] = VERSION_2.to_le_bytes();
const VERSION_2_BE32: [u8; 4] = VERSION_2.to_be_bytes();
const VERSION_2_LE64: [u8; 8] = (VERSION_2 as u64).to_le_bytes();
const VERSION_2_BE64: [u8; 8] = (VERSION_2 as u64).to_be_bytes();
const MAX_DEWEY: usize = 8;
#[repr(C)]
struct Bucket {
name_index: c_int,
path_index: c_int,
dewey: [c_int; MAX_DEWEY],
dewey_count: c_int,
next: c_int,
}
type ParsedFields = (u64, u64, u64, u64, u64, u64);
#[derive(Debug)]
struct ParsedHeader {
byte_order: Endianness,
hash_table: u64,
bucket_count: u64,
string_table: u64,
string_table_size: u64,
end_of_hints: u64,
_dir_list: u64,
}
impl ParsedHeader {
fn parse(path: &Path, bytes: &[u8]) -> Result<Self> {
assert_eq_size!(u32, c_int);
let (input, (data_model, byte_order)) =
Self::parse_byte_order(bytes).map_err(|r| Error::from_nom_parse(r, bytes, path))?;
let (_input, fields) = Self::parse_fields(input, data_model, byte_order)
.map_err(|r| Error::from_nom_parse(r, input, path))?;
let result = Self {
byte_order,
hash_table: fields.0,
bucket_count: fields.1,
string_table: fields.2,
string_table_size: fields.3,
end_of_hints: fields.4,
_dir_list: fields.5,
};
result.validate(path, bytes).map(|()| result)
}
fn parse_byte_order(bytes: &[u8]) -> nom::IResult<&[u8], (DataModel, Endianness)> {
use nom::Parser;
nom_alt((
nom_terminated(nom_tag(&MAGIC_LE64[..]), nom_tag(&VERSION_2_LE64[..]))
.map(|_| (DataModel::LP64, Endianness::Little)),
nom_terminated(nom_tag(&MAGIC_BE64[..]), nom_tag(&VERSION_2_BE64[..]))
.map(|_| (DataModel::LP64, Endianness::Big)),
nom_terminated(nom_tag(&MAGIC_LE32[..]), nom_tag(&VERSION_2_LE32[..]))
.map(|_| (DataModel::ILP32, Endianness::Little)),
nom_terminated(nom_tag(&MAGIC_BE32[..]), nom_tag(&VERSION_2_BE32[..]))
.map(|_| (DataModel::ILP32, Endianness::Big)),
))
.parse(bytes)
}
fn parse_fields(
input: &[u8],
data_model: DataModel,
byte_order: Endianness,
) -> nom::IResult<&[u8], ParsedFields> {
use nom::Parser;
match data_model {
DataModel::ILP32 => {
let parse_u32 = nom_u32(byte_order);
let mut parser = (
nom_into(&parse_u32),
nom_into(&parse_u32),
nom_into(&parse_u32),
nom_into(&parse_u32),
nom_into(&parse_u32),
nom_into(&parse_u32),
);
parser.parse(input)
}
DataModel::LP64 => {
let parse_u64 = nom_u64(byte_order);
let mut parser = (
&parse_u64, &parse_u64, &parse_u64, &parse_u64, &parse_u64, &parse_u64,
);
parser.parse(input)
}
}
}
fn validate(&self, path: &Path, bytes: &[u8]) -> Result<()> {
use nom::Parser;
let hash_table_size = self.bucket_count.saturating_mul(size_of::<Bucket>() as u64);
let hash_table_end = self.hash_table.saturating_add(hash_table_size);
let string_table_end = self.string_table.saturating_add(self.string_table_size);
let min_size = hash_table_end.max(string_table_end).max(self.end_of_hints);
let min_size = usize::try_from(min_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,
hash_table: u64,
bucket_count: u64,
string_table: u64,
string_table_size: u64,
}
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,
hash_table: header.hash_table,
bucket_count: header.bucket_count,
string_table: header.string_table,
string_table_size: header.string_table_size,
})
}
pub fn iter(&self) -> Result<impl FusedIterator<Item = Result<crate::Entry<'_>>> + '_> {
let hash_table_range = self.hash_table_range()?;
let string_table_range = self.string_table_range()?;
Ok(HashTableIter::<{ size_of::<Bucket>() }, _>::new(
&self.map[hash_table_range],
CStringTable::new(&self.map[string_table_range], &self.path),
self.next_hash_table_entry_parser(),
))
}
fn hash_table_range(&self) -> Result<core::ops::Range<usize>> {
let start = usize::try_from(self.hash_table)?;
let size = self.bucket_count.saturating_mul(size_of::<Bucket>() as u64);
let end = usize::try_from(self.hash_table.saturating_add(size))?;
Ok(start..end)
}
fn string_table_range(&self) -> Result<core::ops::Range<usize>> {
let start = usize::try_from(self.string_table)?;
let end = usize::try_from(self.string_table.saturating_add(self.string_table_size))?;
Ok(start..end)
}
fn next_hash_table_entry_parser(
&self,
) -> impl nom::Parser<&[u8], Output = (u32, u32), Error = nom::error::Error<&[u8]>> {
(
nom_u32(self.byte_order),
nom_terminated(
nom_u32(self.byte_order),
nom_take(size_of::<Bucket>().wrapping_sub(offset_of!(Bucket, dewey))),
),
)
}
}
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))
}
}