tests/state/
rust.rs

1// SPDX-License-Identifier: Apache-2.0
2use std::ffi::CStr;
3use std::os::unix::fs::DirEntryExt;
4use std::path::{Path, PathBuf};
5use std::{collections::HashMap, fs, process};
6
7use anyhow::{anyhow, Context, Result};
8use libbpf_rs::{libbpf_sys, skel::Skel, MapCore, MapType};
9
10use super::{BPFState, MapState, PinState};
11
12/// Collects a byte-vector copy of each key/value pair of data in the map
13fn read_map(map: &libbpf_rs::Map) -> Result<HashMap<Vec<u8>, Vec<Vec<u8>>>> {
14    let mut data = HashMap::new();
15    if map.map_type().is_percpu() {
16        for key in map.keys() {
17            let val = map.lookup_percpu(key.as_slice(), libbpf_rs::MapFlags::ANY);
18            data.insert(key, val?.context("Map value empty")?);
19        }
20    } else {
21        for key in map.keys() {
22            let val = map.lookup(key.as_slice(), libbpf_rs::MapFlags::ANY);
23            data.insert(key, vec![val?.context("Map value empty")?]);
24        }
25    }
26    Ok(data)
27}
28
29/// Transforms the [MapType] from Rust into the C string representation
30/// from libbpf in order to match the output from bpftool
31fn map_type_to_libbpf_str(map_type: MapType) -> Result<String> {
32    // this unsafe code is required because returning a string from a C library
33    // is inherently unsafe and uses memory not owned by the Rust program
34    let c_str = unsafe {
35        let raw_str = libbpf_sys::libbpf_bpf_map_type_str(map_type.into()) as *mut i8;
36        CStr::from_ptr(raw_str)
37    };
38    Ok(c_str.to_str()?.to_string())
39}
40
41/// Creates a HashMap of [MapState] from each map in a given skeleton
42fn rust_maps(skel: &dyn Skel, ground_truth: &BPFState) -> Result<HashMap<String, MapState>> {
43    let mut map_states = HashMap::new();
44    for map in skel.object().maps() {
45        let info = map.info()?;
46        let name = info.name()?.to_string();
47        let gt_map_state = match ground_truth.maps.get(&name) {
48            Some(map_state) => map_state,
49            None => return Err(anyhow!("\"{}\" map not found in ground truth", name)),
50        };
51        // don't read data if the map isn't static, it won't be tested
52        let data = if gt_map_state.is_static {
53            read_map(&map)?
54        } else {
55            Default::default()
56        };
57        map_states.insert(
58            name,
59            MapState {
60                id: info.info.id,
61                _type: map_type_to_libbpf_str(map.map_type())?,
62                is_static: gt_map_state.is_static,
63                bytes_key: info.info.key_size,
64                bytes_value: info.info.value_size,
65                data,
66            },
67        );
68    }
69    Ok(map_states)
70}
71
72/// Creates a [PinState] from the paths used in a BPF userspace
73fn rust_pins(pin_dir: &PathBuf) -> Result<PinState> {
74    if !Path::new(pin_dir).exists() {
75        return Err(anyhow!(
76            "pin directory {} not found",
77            pin_dir.to_string_lossy()
78        ));
79    }
80    let mut pins = HashMap::new();
81    for entry in fs::read_dir(pin_dir)? {
82        let entry = entry?;
83        pins.insert(entry.file_name().to_string_lossy().to_string(), entry.ino());
84    }
85    Ok(PinState {
86        dir: pin_dir.to_string_lossy().to_string(),
87        pins,
88    })
89}
90
91/// Creates a [BPFState] object from the perspective of the BPF
92/// file-descriptors created and maintained by the Rust userspace
93pub fn rust_state(skel: &dyn Skel, pin_dir: &PathBuf, ground_truth: &BPFState) -> Result<BPFState> {
94    Ok(BPFState {
95        pid: process::id(),
96        maps: rust_maps(skel, ground_truth)?,
97        pins: rust_pins(pin_dir)?,
98    })
99}