1#[cfg(test)]
10mod tests;
11
12use core::ffi::c_int;
13use core::iter::FusedIterator;
14use core::mem::{offset_of, size_of};
15use std::path::{Path, PathBuf};
16
17use nom::branch::alt as nom_alt;
18use nom::bytes::complete::{tag as nom_tag, take as nom_take};
19use nom::combinator::{into as nom_into, peek as nom_peek};
20use nom::number::complete::{u32 as nom_u32, u64 as nom_u64};
21use nom::number::Endianness;
22use nom::sequence::terminated as nom_terminated;
23use static_assertions::assert_eq_size;
24
25use crate::utils::{map_file, CStringTable, HashTableIter};
26use crate::{CacheProvider, DataModel, Error, Result};
27
28static CACHE_FILE_PATH: &str = "/var/run/ld.so.hints";
29
30const MAGIC: u32 = 0x4c_44_48_69_u32;
31const MAGIC_LE32: [u8; 4] = MAGIC.to_le_bytes();
32const MAGIC_BE32: [u8; 4] = MAGIC.to_be_bytes();
33const MAGIC_LE64: [u8; 8] = (MAGIC as u64).to_le_bytes();
34const MAGIC_BE64: [u8; 8] = (MAGIC as u64).to_be_bytes();
35
36const VERSION_2: u32 = 2;
39const VERSION_2_LE32: [u8; 4] = VERSION_2.to_le_bytes();
40const VERSION_2_BE32: [u8; 4] = VERSION_2.to_be_bytes();
41const VERSION_2_LE64: [u8; 8] = (VERSION_2 as u64).to_le_bytes();
42const VERSION_2_BE64: [u8; 8] = (VERSION_2 as u64).to_be_bytes();
43
44const MAX_DEWEY: usize = 8;
46
47#[repr(C)]
71struct Bucket {
72 name_index: c_int,
74 path_index: c_int,
76 dewey: [c_int; MAX_DEWEY],
78 dewey_count: c_int,
80 next: c_int,
82}
83
84type ParsedFields = (u64, u64, u64, u64, u64, u64);
85
86#[derive(Debug)]
87struct ParsedHeader {
88 byte_order: Endianness,
89 hash_table: u64,
90 bucket_count: u64,
91 string_table: u64,
92 string_table_size: u64,
93 end_of_hints: u64,
94 _dir_list: u64,
95}
96
97impl ParsedHeader {
98 fn parse(path: &Path, bytes: &[u8]) -> Result<Self> {
99 assert_eq_size!(u32, c_int);
100
101 let (input, (data_model, byte_order)) =
102 Self::parse_byte_order(bytes).map_err(|r| Error::from_nom_parse(r, bytes, path))?;
103
104 let (_input, fields) = Self::parse_fields(input, data_model, byte_order)
105 .map_err(|r| Error::from_nom_parse(r, input, path))?;
106
107 let result = Self {
108 byte_order,
109 hash_table: fields.0,
110 bucket_count: fields.1,
111 string_table: fields.2,
112 string_table_size: fields.3,
113 end_of_hints: fields.4,
114 _dir_list: fields.5,
115 };
116
117 result.validate(path, bytes).map(|()| result)
118 }
119
120 fn parse_byte_order(bytes: &[u8]) -> nom::IResult<&[u8], (DataModel, Endianness)> {
121 use nom::Parser;
122
123 nom_alt((
124 nom_terminated(nom_tag(&MAGIC_LE64[..]), nom_tag(&VERSION_2_LE64[..]))
125 .map(|_| (DataModel::LP64, Endianness::Little)),
126 nom_terminated(nom_tag(&MAGIC_BE64[..]), nom_tag(&VERSION_2_BE64[..]))
127 .map(|_| (DataModel::LP64, Endianness::Big)),
128 nom_terminated(nom_tag(&MAGIC_LE32[..]), nom_tag(&VERSION_2_LE32[..]))
129 .map(|_| (DataModel::ILP32, Endianness::Little)),
130 nom_terminated(nom_tag(&MAGIC_BE32[..]), nom_tag(&VERSION_2_BE32[..]))
131 .map(|_| (DataModel::ILP32, Endianness::Big)),
132 ))
133 .parse(bytes)
134 }
135
136 fn parse_fields(
137 input: &[u8],
138 data_model: DataModel,
139 byte_order: Endianness,
140 ) -> nom::IResult<&[u8], ParsedFields> {
141 use nom::Parser;
142
143 match data_model {
144 DataModel::ILP32 => {
145 let parse_u32 = nom_u32(byte_order);
146 let mut parser = (
147 nom_into(&parse_u32),
148 nom_into(&parse_u32),
149 nom_into(&parse_u32),
150 nom_into(&parse_u32),
151 nom_into(&parse_u32),
152 nom_into(&parse_u32),
153 );
154 parser.parse(input)
155 }
156 DataModel::LP64 => {
157 let parse_u64 = nom_u64(byte_order);
158 let mut parser = (
159 &parse_u64, &parse_u64, &parse_u64, &parse_u64, &parse_u64, &parse_u64,
160 );
161 parser.parse(input)
162 }
163 }
164 }
165
166 fn validate(&self, path: &Path, bytes: &[u8]) -> Result<()> {
167 use nom::Parser;
168
169 let hash_table_size = self.bucket_count.saturating_mul(size_of::<Bucket>() as u64);
170 let hash_table_end = self.hash_table.saturating_add(hash_table_size);
171 let string_table_end = self.string_table.saturating_add(self.string_table_size);
172
173 let min_size = hash_table_end.max(string_table_end).max(self.end_of_hints);
174 let min_size = usize::try_from(min_size)?;
175
176 nom_peek(nom_take(min_size))
177 .parse(bytes)
178 .map(|_| ())
179 .map_err(|r| Error::from_nom_parse(r, bytes, path))
180 }
181}
182
183#[derive(Debug)]
188pub struct Cache {
189 path: PathBuf,
190 map: memmap2::Mmap,
191 byte_order: Endianness,
192 hash_table: u64,
193 bucket_count: u64,
194 string_table: u64,
195 string_table_size: u64,
196}
197
198impl Cache {
199 pub fn load_default() -> Result<Self> {
201 Self::load(CACHE_FILE_PATH)
202 }
203
204 pub fn load(path: impl AsRef<Path>) -> Result<Self> {
206 let path = path.as_ref();
207 let map = map_file(path)?;
208 let header = ParsedHeader::parse(path, &map)?;
209
210 Ok(Self {
211 path: path.into(),
212 map,
213 byte_order: header.byte_order,
214 hash_table: header.hash_table,
215 bucket_count: header.bucket_count,
216 string_table: header.string_table,
217 string_table_size: header.string_table_size,
218 })
219 }
220
221 pub fn iter(&self) -> Result<impl FusedIterator<Item = Result<crate::Entry<'_>>> + '_> {
223 let hash_table_range = self.hash_table_range()?;
224 let string_table_range = self.string_table_range()?;
225
226 Ok(HashTableIter::<{ size_of::<Bucket>() }, _>::new(
227 &self.map[hash_table_range],
228 CStringTable::new(&self.map[string_table_range], &self.path),
229 self.next_hash_table_entry_parser(),
230 ))
231 }
232
233 fn hash_table_range(&self) -> Result<core::ops::Range<usize>> {
234 let start = usize::try_from(self.hash_table)?;
235 let size = self.bucket_count.saturating_mul(size_of::<Bucket>() as u64);
236 let end = usize::try_from(self.hash_table.saturating_add(size))?;
237 Ok(start..end)
238 }
239
240 fn string_table_range(&self) -> Result<core::ops::Range<usize>> {
241 let start = usize::try_from(self.string_table)?;
242 let end = usize::try_from(self.string_table.saturating_add(self.string_table_size))?;
243 Ok(start..end)
244 }
245
246 fn next_hash_table_entry_parser(
247 &self,
248 ) -> impl nom::Parser<&[u8], Output = (u32, u32), Error = nom::error::Error<&[u8]>> {
249 (
250 nom_u32(self.byte_order),
251 nom_terminated(
252 nom_u32(self.byte_order),
253 nom_take(size_of::<Bucket>().wrapping_sub(offset_of!(Bucket, dewey))),
254 ),
255 )
256 }
257}
258
259impl CacheProvider for Cache {
260 fn entries_iter<'cache>(
261 &'cache self,
262 ) -> Result<Box<dyn FusedIterator<Item = Result<crate::Entry<'cache>>> + 'cache>> {
263 let iter = self.iter()?;
264 Ok(Box::new(iter))
265 }
266}