rawsys_linux/
map.rs

1use super::Sysno;
2use crate::SysnoSet;
3use crate::set::SysnoSetIter;
4use core::fmt;
5use core::mem::MaybeUninit;
6
7type DataArray<T> = [MaybeUninit<T>; Sysno::table_size()];
8
9/// A map of syscalls to a type `T`.
10///
11/// Backed by a static array indexed by the syscall table offset, this provides
12/// O(1) lookups with minimal overhead. Membership is tracked by a companion
13/// `SysnoSet` so that iteration visits only initialized entries.
14///
15/// Intended uses
16/// - Fast dispatch tables (e.g., mapping `Sysno` to handlers).
17/// - Statically initialized description tables via `from_slice`.
18///
19/// Complexity
20/// - `get`/`insert`/`remove`: O(1)
21/// - `iter`/`values`: O(k) where k is the number of set entries
22///
23/// # Examples
24///
25/// ```
26/// # use rawsys_linux::{Sysno, SysnoMap};
27/// struct Point { x: i32, y: i32 }
28///
29/// let mut map = SysnoMap::new();
30/// map.insert(Sysno::openat, Point { x: 1, y: 2 });
31/// assert!(map.get(Sysno::openat).is_some());
32/// ```
33///
34/// Use function callbacks:
35/// ```
36/// # use rawsys_linux::{Sysno, SysnoMap};
37/// let mut map = SysnoMap::<fn() -> i32>::new();
38/// map.insert(Sysno::openat, || 1);
39/// map.insert(Sysno::close, || -1);
40/// assert_eq!(map.get(Sysno::openat).unwrap()(), 1);
41/// assert_eq!(map.get(Sysno::close).unwrap()(), -1);
42/// ```
43///
44/// ```
45/// # use rawsys_linux::{Sysno, SysnoMap};
46/// let mut syscalls = SysnoMap::from_iter([
47///     (Sysno::openat, 0),
48///     (Sysno::close, 42),
49/// ]);
50///
51/// assert!(!syscalls.is_empty());
52/// assert_eq!(syscalls.remove(Sysno::openat), Some(0));
53/// assert_eq!(syscalls.insert(Sysno::close, 4), Some(42));
54/// assert!(syscalls.contains_key(Sysno::close));
55/// assert_eq!(syscalls.get(Sysno::close), Some(&4));
56/// assert_eq!(syscalls.insert(Sysno::close, 11), Some(4));
57/// assert_eq!(syscalls.count(), 1);
58/// assert_eq!(syscalls.remove(Sysno::close), Some(11));
59/// assert!(syscalls.is_empty());
60/// ```
61pub struct SysnoMap<T> {
62    is_set: SysnoSet,
63    data: DataArray<T>,
64}
65
66/// Get internal data index based on sysno value
67#[inline]
68const fn get_idx(sysno: Sysno) -> usize {
69    (sysno.id() as usize) - (Sysno::first().id() as usize)
70}
71
72impl<T> Default for SysnoMap<T> {
73    fn default() -> Self {
74        Self::new()
75    }
76}
77
78impl<T> SysnoMap<T> {
79    /// Initializes an empty syscall map.
80    pub const fn new() -> Self {
81        Self {
82            is_set: SysnoSet::empty(),
83            data: unsafe { MaybeUninit::uninit().assume_init() },
84        }
85    }
86
87    /// Returns true if the map contains the given syscall.
88    pub const fn contains_key(&self, sysno: Sysno) -> bool {
89        self.is_set.contains(sysno)
90    }
91
92    /// Clears the map, removing all syscalls.
93    pub fn clear(&mut self) {
94        for sysno in &self.is_set {
95            unsafe { self.data[get_idx(sysno)].assume_init_drop() }
96        }
97        self.is_set.clear();
98    }
99
100    /// Returns true if the map is empty. Athough this is an O(1) operation
101    /// (because the total number of syscalls is always constant), it must
102    /// always iterate over the whole map to determine if it is empty or not.
103    /// Thus, this may have a large, constant overhead.
104    pub fn is_empty(&self) -> bool {
105        self.is_set.is_empty()
106    }
107
108    /// Returns the number of syscalls in the map. Although This is an O(1)
109    /// operation (because the total number of syscalls is always constant), it
110    /// must always iterate over the whole map to determine how many items it
111    /// has. Thus, this may have a large, constant overhead.
112    pub fn count(&self) -> usize {
113        self.is_set.count()
114    }
115
116    /// Inserts the given syscall into the map. Returns true if the syscall was
117    /// not already in the map.
118    pub fn insert(&mut self, sysno: Sysno, value: T) -> Option<T> {
119        let uninit = &mut self.data[get_idx(sysno)];
120        if self.is_set.insert(sysno) {
121            // Was not already in the set.
122            uninit.write(value);
123            None
124        } else {
125            // Was already in the set.
126            let old = core::mem::replace(uninit, MaybeUninit::new(value));
127            Some(unsafe { old.assume_init() })
128        }
129    }
130
131    /// Removes the given syscall from the map. Returns old value if the syscall
132    /// was in the map.
133    pub fn remove(&mut self, sysno: Sysno) -> Option<T> {
134        if self.is_set.remove(sysno) {
135            let old = core::mem::replace(
136                &mut self.data[get_idx(sysno)],
137                MaybeUninit::uninit(),
138            );
139            Some(unsafe { old.assume_init() })
140        } else {
141            None
142        }
143    }
144
145    /// Returns a reference to the value corresponding to `sysno`. Returns
146    /// `None` if the syscall is not in the map.
147    pub fn get(&self, sysno: Sysno) -> Option<&T> {
148        if self.is_set.contains(sysno) {
149            Some(unsafe { self.data[get_idx(sysno)].assume_init_ref() })
150        } else {
151            None
152        }
153    }
154
155    /// Returns a mutable reference to the value corresponding to `sysno`.
156    /// Returns `None` if the syscall is not in the map.
157    pub fn get_mut(&mut self, sysno: Sysno) -> Option<&mut T> {
158        if self.is_set.contains(sysno) {
159            Some(unsafe { self.data[get_idx(sysno)].assume_init_mut() })
160        } else {
161            None
162        }
163    }
164
165    /// Returns an iterator that iterates over the syscalls contained in the map.
166    pub fn iter(&self) -> SysnoMapIter<'_, T> {
167        SysnoMapIter {
168            iter: self.is_set.iter(),
169            data: &self.data,
170        }
171    }
172
173    /// Returns an iterator that iterates over all enabled values contained in
174    /// the map.
175    pub fn values(&self) -> SysnoMapValues<'_, T> {
176        SysnoMapValues(self.is_set.iter(), &self.data)
177    }
178}
179
180impl<T: Copy> SysnoMap<T> {
181    /// Initialize a syscall map from the given slice. Note that `T` must be
182    /// `Copy` due to `const fn` limitations.
183    ///
184    /// This is useful for constructing a static callback table.
185    ///
186    /// # Example
187    ///
188    /// ```
189    /// use rawsys_linux::{Sysno, SysnoMap};
190    ///
191    /// static CALLBACKS: SysnoMap<fn() -> i32> = SysnoMap::from_slice(&[
192    ///     (Sysno::openat, || 42),
193    ///     (Sysno::close, || 43),
194    /// ]);
195    ///
196    /// static DESCRIPTIONS: SysnoMap<&'static str> = SysnoMap::from_slice(&[
197    ///     (Sysno::openat, "open and possibly create a file"),
198    ///     (Sysno::close, "close a file descriptor"),
199    /// ]);
200    ///
201    /// assert_eq!(CALLBACKS[Sysno::openat](), 42);
202    /// assert_eq!(DESCRIPTIONS[Sysno::close], "close a file descriptor");
203    /// ```
204    pub const fn from_slice(slice: &[(Sysno, T)]) -> Self {
205        let mut data: DataArray<T> =
206            unsafe { MaybeUninit::uninit().assume_init() };
207
208        let mut is_set = SysnoSet::empty();
209
210        // Use while-loop because for-loops are not yet allowed in const-fns.
211        // https://github.com/rust-lang/rust/issues/87575
212        let mut i = 0;
213        while i < slice.len() {
214            let sysno = slice[i].0;
215            let (idx, mask) = SysnoSet::get_idx_mask(sysno);
216            is_set.data[idx] |= mask;
217            data[get_idx(sysno)] = MaybeUninit::new(slice[i].1);
218            i += 1;
219        }
220
221        Self { is_set, data }
222    }
223}
224
225impl<T: Clone> SysnoMap<T> {
226    /// Initializes all possible syscalls in the map with the given default
227    /// value.
228    pub fn init_all(default: &T) -> Self {
229        SysnoSet::all()
230            .iter()
231            .map(|v| (v, default.clone()))
232            .collect()
233    }
234}
235
236impl<T> Drop for SysnoMap<T> {
237    fn drop(&mut self) {
238        self.clear();
239    }
240}
241
242/// An iterator over the syscall (number, value) pairs contained in a
243/// [`SysnoMap`].
244pub struct SysnoMapIter<'a, T> {
245    iter: SysnoSetIter<'a>,
246    data: &'a DataArray<T>,
247}
248
249impl<'a, T> Iterator for SysnoMapIter<'a, T> {
250    type Item = (Sysno, &'a T);
251
252    fn next(&mut self) -> Option<Self::Item> {
253        self.iter.next().map(|sysno| {
254            let value = unsafe { self.data[get_idx(sysno)].assume_init_ref() };
255            (sysno, value)
256        })
257    }
258}
259
260/// An iterator over the syscall values contained in a [`SysnoMap`].
261pub struct SysnoMapValues<'a, T>(SysnoSetIter<'a>, &'a DataArray<T>);
262
263impl<'a, T> Iterator for SysnoMapValues<'a, T> {
264    type Item = &'a T;
265
266    fn next(&mut self) -> Option<Self::Item> {
267        self.0
268            .next()
269            .map(|sysno| unsafe { self.1[get_idx(sysno)].assume_init_ref() })
270    }
271}
272
273impl<T: fmt::Debug> fmt::Debug for SysnoMap<T> {
274    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
275        f.debug_map().entries(self.iter()).finish()
276    }
277}
278
279impl<T> Extend<(Sysno, T)> for SysnoMap<T> {
280    fn extend<I: IntoIterator<Item = (Sysno, T)>>(&mut self, iter: I) {
281        for (sysno, value) in iter {
282            self.insert(sysno, value);
283        }
284    }
285}
286
287impl<T> FromIterator<(Sysno, T)> for SysnoMap<T> {
288    fn from_iter<I>(iter: I) -> Self
289    where
290        I: IntoIterator<Item = (Sysno, T)>,
291    {
292        let mut map = SysnoMap::new();
293        map.extend(iter);
294        map
295    }
296}
297
298impl<'a, T> IntoIterator for &'a SysnoMap<T> {
299    type Item = (Sysno, &'a T);
300    type IntoIter = SysnoMapIter<'a, T>;
301
302    fn into_iter(self) -> Self::IntoIter {
303        self.iter()
304    }
305}
306
307impl<T> core::ops::Index<Sysno> for SysnoMap<T> {
308    type Output = T;
309
310    fn index(&self, sysno: Sysno) -> &T {
311        self.get(sysno).expect("no entry found for key")
312    }
313}
314
315impl<T> core::ops::IndexMut<Sysno> for SysnoMap<T> {
316    fn index_mut(&mut self, sysno: Sysno) -> &mut T {
317        self.get_mut(sysno).expect("no entry found for key")
318    }
319}
320
321#[cfg(test)]
322mod tests {
323    use super::*;
324
325    #[test]
326    fn test_default() {
327        assert_eq!(SysnoMap::<u8>::new().count(), 0);
328    }
329
330    #[test]
331    fn test_is_empty() {
332        let mut map = SysnoMap::new();
333        assert!(map.is_empty());
334        assert_eq!(map.insert(Sysno::openat, 42), None);
335        assert!(!map.is_empty());
336        assert_eq!(map.get(Sysno::openat), Some(&42));
337        map.remove(Sysno::openat);
338        assert!(map.is_empty());
339        assert_eq!(map.get(Sysno::openat), None);
340    }
341
342    #[test]
343    fn test_count() {
344        let mut map = SysnoMap::new();
345        assert_eq!(map.count(), 0);
346        assert_eq!(map.insert(Sysno::openat, 42), None);
347        assert_eq!(map.count(), 1);
348        assert_eq!(map.insert(Sysno::first(), 4), None);
349        assert_eq!(map.count(), 2);
350        assert_eq!(map.insert(Sysno::last(), 5), None);
351        assert_eq!(map.count(), 3);
352        assert_eq!(map.values().sum::<u8>(), 51);
353    }
354
355    #[test]
356    fn test_fn() {
357        let mut map = SysnoMap::<fn() -> i32>::new();
358        map.insert(Sysno::openat, || 1);
359        map.insert(Sysno::close, || -1);
360        assert_eq!(map.get(Sysno::openat).unwrap()(), 1);
361        assert_eq!(map.get(Sysno::close).unwrap()(), -1);
362    }
363
364    #[test]
365    fn test_fn_macro() {
366        type Handler = fn() -> i32;
367        let map = SysnoMap::from_iter([
368            (Sysno::openat, (|| 1) as Handler),
369            (Sysno::close, (|| -1) as Handler),
370        ]);
371        assert_eq!(map.get(Sysno::openat).unwrap()(), 1);
372        assert_eq!(map.get(Sysno::close).unwrap()(), -1);
373    }
374
375    #[test]
376    fn test_insert_remove() {
377        let mut map = SysnoMap::new();
378        assert_eq!(map.insert(Sysno::openat, 42), None);
379        assert!(map.contains_key(Sysno::openat));
380        assert_eq!(map.count(), 1);
381
382        assert_eq!(map.insert(Sysno::openat, 4), Some(42));
383        assert!(map.contains_key(Sysno::openat));
384        assert_eq!(map.count(), 1);
385
386        assert_eq!(map.remove(Sysno::openat), Some(4));
387        assert!(!map.contains_key(Sysno::openat));
388        assert_eq!(map.count(), 0);
389
390        assert_eq!(map.remove(Sysno::openat), None);
391    }
392
393    #[cfg(feature = "std")]
394    #[test]
395    fn test_debug() {
396        let map = SysnoMap::from_iter([(Sysno::read, 42), (Sysno::openat, 10)]);
397        let result = format!("{:?}", map);
398        // The order of the debug output is not guaranteed, so we can't do an
399        // exact match.
400        assert_eq!(result.len(), "{read: 42, openat: 10}".len());
401        assert!(result.starts_with('{'));
402        assert!(result.ends_with('}'));
403        assert!(result.contains(", "));
404        assert!(result.contains("read: 42"));
405        assert!(result.contains("openat: 10"));
406    }
407
408    #[cfg(feature = "std")]
409    #[test]
410    fn test_iter() {
411        let map = SysnoMap::from_iter([(Sysno::read, 42), (Sysno::openat, 10)]);
412        assert_eq!(map.iter().collect::<Vec<_>>().len(), 2);
413    }
414
415    #[test]
416    fn test_into_iter() {
417        let map = SysnoMap::from_iter([(Sysno::read, 42), (Sysno::openat, 10)]);
418        assert_eq!((&map).into_iter().count(), 2);
419    }
420
421    #[test]
422    fn test_init_all() {
423        let map = SysnoMap::init_all(&42);
424        assert_eq!(map.get(Sysno::openat), Some(&42));
425        assert_eq!(map.get(Sysno::close), Some(&42));
426    }
427}