1use std::num::ParseIntError;
7use std::os::unix::fs::DirEntryExt;
8use std::path::PathBuf;
9use std::process::Command;
10use std::{collections::HashMap, path::Path};
11
12use anyhow::{anyhow, Context, Result};
13use libbpf_rs::{skel::Skel, MapCore};
14use serde::Deserialize;
15
16use super::{BPFState, MapState, PinState};
17
18#[derive(Deserialize)]
20struct BPFToolMapDump {
21 key: Vec<String>,
22 value: Vec<String>,
23}
24
25#[derive(Deserialize)]
27struct BPFToolPerCPUMapDumpValue {
28 value: Vec<String>,
29}
30
31#[derive(Deserialize)]
33struct BPFToolPerCPUMapDump {
34 key: Vec<String>,
35 values: Vec<BPFToolPerCPUMapDumpValue>,
36}
37
38fn bpftool_command(args: &str) -> Result<String> {
40 let output = Command::new("bpftool")
41 .args(args.split_whitespace())
42 .output()?;
43 Ok(String::from_utf8(output.stdout)?)
44}
45
46fn bpftool_str_to_bytes(strings: &[String]) -> Result<Vec<u8>, ParseIntError> {
49 strings
50 .iter()
51 .map(|string| parse_int::parse::<u8>(string))
52 .collect()
53}
54
55fn bpftool_maps(skel: &dyn Skel, ground_truth: &BPFState) -> Result<HashMap<String, MapState>> {
58 let mut hashmap = HashMap::new();
59 for map in skel.object().maps() {
60 let name = map
61 .name()
62 .to_str()
63 .context(format!("Cannot convert {:#?}", map.name()))?;
64 let map_id = map.info()?.info.id;
65 let gt_map_state = match ground_truth.maps.get(name) {
66 Some(map_state) => map_state,
67 None => return Err(anyhow!("\"{}\" map not found in ground truth", name)),
68 };
69 let show_str = bpftool_command(&format!("-j map show id {map_id}"))?;
70 let mut state: MapState = serde_json::from_slice(show_str.as_bytes())?;
71 state.is_static = gt_map_state.is_static;
72
73 if state.bytes_key != 0 && state.is_static {
75 let dump_str = bpftool_command(&format!("-j map dump id {}", &state.id))?;
76 if state._type.starts_with("percpu") {
77 let data: Vec<BPFToolPerCPUMapDump> = serde_json::from_slice(dump_str.as_bytes())?;
78 for entry in data.iter() {
79 let key = bpftool_str_to_bytes(&entry.key)?;
80 let mut values = Vec::new();
81 for value in entry.values.iter() {
82 values.push(bpftool_str_to_bytes(&value.value)?);
83 }
84 state.data.insert(key, values);
85 }
86 } else {
87 let data: Vec<BPFToolMapDump> = serde_json::from_slice(dump_str.as_bytes())?;
88 for entry in data {
89 let key = bpftool_str_to_bytes(&entry.key)?;
90 let value = bpftool_str_to_bytes(&entry.value)?;
91 state.data.insert(key, vec![value]);
92 }
93 }
94 }
95 hashmap.insert(name.to_string(), state);
96 }
97 Ok(hashmap)
98}
99
100fn check_pin_dir(pin_dir: &Path) -> Result<()> {
103 if !pin_dir.exists() {
104 return Err(anyhow!(
105 "Pin dir `{}` doesn't exist",
106 pin_dir.to_string_lossy()
107 ));
108 }
109 if !pin_dir.is_dir() {
111 return Err(anyhow!(
112 "Pin dir `{}` is not a directory",
113 pin_dir.to_string_lossy()
114 ));
115 }
116 Ok(())
117}
118
119fn linux_pins(pin_dir: &PathBuf) -> Result<PinState> {
121 check_pin_dir(pin_dir)?;
122 let mut pins = HashMap::new();
123 for pin in std::fs::read_dir(pin_dir)? {
124 let pin = pin?;
125 pins.insert(pin.file_name().to_string_lossy().to_string(), pin.ino());
126 }
127 Ok(PinState {
128 dir: pin_dir.to_string_lossy().to_string(),
129 pins,
130 })
131}
132
133pub fn linux_state(
136 skel: &dyn Skel,
137 pin_dir: &PathBuf,
138 ground_truth: &BPFState,
139) -> Result<BPFState> {
140 Ok(BPFState {
141 maps: bpftool_maps(skel, ground_truth)?,
142 pins: linux_pins(pin_dir)?,
143 ..Default::default()
144 })
145}