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}