fontconfig_cache_parser/ptr.rs
1//! Pointers and offset's within fontconfig's cache.
2//!
3//! Fontconfig's cache is stuctured as a collection of structs.
4//! These structs are encoded in the cache by
5//! writing their bytes into a cache file. (So the format is architecture-dependent.)
6//! The structs reference each other using relative offsets, so for example in the struct
7//!
8//! ```C
9//! struct FcPattern {
10//! int num;
11//! int size;
12//! intptr_t elts_offset;
13//! int ref;
14//! }
15//! ```
16//!
17//! the elements `num`, `size`, and `ref` are just plain old data, and the element `elts_offset`
18//! says that there is some other struct (which happens to be an `FcPatternElt` in this case)
19//! stored at the location `base_offset + elts_offset`, where `base_offset` is the offset
20//! of the `FcPattern`. Note that `elts_offset` is signed: it can be negative.
21//!
22//! We encode these offsets using `Offset`, so for example the struct above gets translated to
23//!
24//! ```ignore
25//! struct PatternData {
26//! num: c_int,
27//! size: c_int,
28//! elts_offset: Offset<PatternElt>,
29//! ref: c_int,
30//! }
31//! ```
32//!
33//! Sometimes, the structs in fontconfig contain pointers instead of offsets, like for example
34//!
35//! ```C
36//! struct FcPatternElt {
37//! FcObject object;
38//! FcValueList *values;
39//! }
40//! ```
41//!
42//! In this case, fontconfig actually handles two cases: if the lowest-order bit of `values` is 0
43//! it's treated as a normal pointer, but if the lowest-order bit is 1 then that bit is set
44//! to zero and `values` is treated as an offset. When the struct came from a cache file that
45//! was serialized to disk (which we always are in this crate), it should always be in the "offset" case.
46//! That is, these pointers get treated almost the same as offsets, except that we need to
47//! sanity-check the low-order bit and then set it to zero. We encode these as `PtrOffset`,
48//! so for example the struct above gets translated to
49//!
50//! ```ignore
51//! struct PatternEltData {
52//! object: c_int,
53//! values: PtrOffset<ValueList>,
54//! }
55
56use bytemuck::AnyBitPattern;
57use std::os::raw::c_int;
58
59use crate::Error;
60
61type Result<T> = std::result::Result<T, Error>;
62
63/// A relative offset to another struct in the cache, which is encoded as a pointer in fontconfig.
64///
65/// See [`Offset`] for more on offsets in fontconfig and how we handle them in this crate.
66#[repr(C)]
67#[derive(Clone, Copy, Debug)]
68pub struct PtrOffset<T: Copy>(pub isize, std::marker::PhantomData<T>);
69
70unsafe impl<T: Copy> bytemuck::Zeroable for PtrOffset<T> {}
71unsafe impl<T: Copy + 'static> bytemuck::Pod for PtrOffset<T> {}
72
73/// This is basically equivalent to `TryInto<Offset<T>, Error=Error>`, but having this
74/// alias makes type inference work better.
75pub trait IntoOffset: AnyBitPattern + Copy {
76 /// Into an offset of what type?
77 type Item: AnyBitPattern + Copy;
78
79 /// Turns `self` into an `Offset`.
80 fn into_offset(self) -> Result<Offset<Self::Item>>;
81}
82
83impl<T: AnyBitPattern + Copy> IntoOffset for PtrOffset<T> {
84 type Item = T;
85
86 fn into_offset(self) -> Result<Offset<T>> {
87 if self.0 & 1 == 0 {
88 Err(Error::BadPointer(self.0))
89 } else {
90 Ok(Offset(self.0 & !1, std::marker::PhantomData))
91 }
92 }
93}
94
95impl<T: AnyBitPattern + Copy> IntoOffset for Offset<T> {
96 type Item = T;
97
98 fn into_offset(self) -> Result<Offset<T>> {
99 Ok(self)
100 }
101}
102
103/// A relative offset to another struct in the cache.
104///
105/// # Implementation details
106///
107#[repr(C)]
108#[derive(Clone, Copy, Debug)]
109pub struct Offset<T: Copy>(isize, std::marker::PhantomData<T>);
110
111pub(crate) fn offset<T: Copy>(off: isize) -> Offset<T> {
112 Offset(off, std::marker::PhantomData)
113}
114
115unsafe impl<T: Copy> bytemuck::Zeroable for Offset<T> {}
116unsafe impl<T: Copy + 'static> bytemuck::Pod for Offset<T> {}
117
118/// A reference to a fontconfig struct that's been serialized in a buffer.
119#[derive(Clone)]
120pub struct Ptr<'buf, S> {
121 /// We point at this `offset`, relative to the buffer.
122 pub offset: isize,
123 /// The buffer that we point into.
124 pub buf: &'buf [u8],
125 pub(crate) marker: std::marker::PhantomData<S>,
126}
127
128impl<'buf, S> std::fmt::Debug for Ptr<'buf, S> {
129 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
130 f.debug_struct("Ptr").field("offset", &self.offset).finish()
131 }
132}
133
134/// A reference to an array of serialized fontconfig structs.
135#[derive(Clone, Debug)]
136pub struct Array<'buf, T> {
137 buf: &'buf [u8],
138 offset: usize,
139 size: isize,
140 marker: std::marker::PhantomData<T>,
141}
142
143impl<'buf, T: AnyBitPattern> Array<'buf, T> {
144 fn new(buf: &'buf [u8], offset: isize, size: c_int) -> Result<Self> {
145 let len = std::mem::size_of::<T>();
146 let total_len = len
147 .checked_mul(size as usize)
148 .ok_or(Error::BadLength(size as isize))?;
149
150 if offset < 0 {
151 Err(Error::BadOffset(offset))
152 } else {
153 let end = (offset as usize)
154 .checked_add(total_len)
155 .ok_or(Error::BadLength(size as isize))?;
156 if end > buf.len() {
157 Err(Error::BadOffset(end as isize))
158 } else {
159 Ok(Array {
160 buf,
161 offset: offset as usize,
162 size: size as isize,
163 marker: std::marker::PhantomData,
164 })
165 }
166 }
167 }
168
169 /// The number of elements in this array.
170 pub fn len(&self) -> usize {
171 self.size as usize
172 }
173
174 /// Is this array empty?
175 pub fn is_empty(&self) -> bool {
176 self.len() == 0
177 }
178
179 /// Retrieve an element at a given index, if that index isn't too big.
180 pub fn get(&self, idx: usize) -> Option<Ptr<'buf, T>> {
181 if (idx as isize) < self.size {
182 let len = std::mem::size_of::<T>() as isize;
183 Some(Ptr {
184 buf: self.buf,
185 offset: self.offset as isize + (idx as isize) * len,
186 marker: std::marker::PhantomData,
187 })
188 } else {
189 None
190 }
191 }
192
193 /// View this array as a rust slice.
194 ///
195 /// This conversion might fail if the alignment is wrong. That definitely won't happen if `T` has
196 /// a two-byte alignment. It's *probably* fine in general, but don't blame me if it isn't.
197 pub fn as_slice(&self) -> Result<&'buf [T]> {
198 let len = std::mem::size_of::<T>() * self.size as usize;
199 bytemuck::try_cast_slice(&self.buf[self.offset..(self.offset + len)]).map_err(|_| {
200 Error::BadAlignment {
201 offset: self.offset,
202 expected_alignment: std::mem::align_of::<T>(),
203 }
204 })
205 }
206}
207
208impl<'buf, T: AnyBitPattern> Iterator for Array<'buf, T> {
209 type Item = Ptr<'buf, T>;
210
211 fn next(&mut self) -> Option<Ptr<'buf, T>> {
212 if self.size <= 0 {
213 None
214 } else {
215 let len = std::mem::size_of::<T>();
216 let ret = Ptr {
217 buf: self.buf,
218 offset: self.offset as isize,
219 marker: std::marker::PhantomData,
220 };
221 self.offset += len;
222 self.size -= 1;
223 Some(ret)
224 }
225 }
226}
227
228impl<'buf> Ptr<'buf, u8> {
229 /// Assuming that this `Ptr<u8>` is pointing to the beginning of a null-terminated string,
230 /// return that string.
231 pub fn str(&self) -> Result<&'buf [u8]> {
232 let offset = self.offset;
233 if offset < 0 || offset > self.buf.len() as isize {
234 Err(Error::BadOffset(offset))
235 } else {
236 let buf = &self.buf[(offset as usize)..];
237 let null_offset = buf
238 .iter()
239 .position(|&c| c == 0)
240 .ok_or(Error::UnterminatedString(offset))?;
241 Ok(&buf[..null_offset])
242 }
243 }
244}
245
246fn offset_to_usize(off: isize) -> Result<usize> {
247 off.try_into().map_err(|_| Error::BadOffset(off))
248}
249
250impl<'buf, S: AnyBitPattern> Ptr<'buf, S> {
251 /// Turn `offset` into a pointer, assuming that it's an offset relative to this pointer.
252 ///
253 /// In order to be certain about which offsets are relative to what, you'll need to check
254 /// the fontconfig source. But generally, offsets stored in a struct are relative to the
255 /// base address of that struct. So for example, to access the `dir` field in
256 /// [`Cache`](crate::Cache) you could call `cache.relative_offset(cache.deref()?.dir)?`.
257 /// This will give you a `Ptr<u8>` pointing to the start of the directory name.
258 pub fn relative_offset<Off: IntoOffset>(&self, offset: Off) -> Result<Ptr<'buf, Off::Item>> {
259 let offset = offset.into_offset()?;
260 Ok(Ptr {
261 buf: self.buf,
262 offset: self
263 .offset
264 .checked_add(offset.0)
265 .ok_or(Error::BadOffset(offset.0))?,
266 marker: std::marker::PhantomData,
267 })
268 }
269
270 /// "Dereference" this pointer, returning a plain struct.
271 pub fn deref(&self) -> Result<S> {
272 let len = std::mem::size_of::<S>() as isize;
273 let offset = offset_to_usize(self.offset)?;
274 let end = offset_to_usize(self.offset.wrapping_add(len))?;
275
276 let slice = self
277 .buf
278 .get(offset..end)
279 .ok_or(Error::BadOffset(self.offset))?;
280
281 // bytemuck should succeed because we checked the length
282 Ok(bytemuck::try_pod_read_unaligned(slice).expect("but we checked the length..."))
283 }
284
285 /// Treating this pointer as a reference to the start of an array of length `count`,
286 /// return an iterator over that array.
287 pub fn array(&self, count: c_int) -> Result<Array<'buf, S>> {
288 Array::new(self.buf, self.offset, count)
289 }
290}