1use super::{
22 btf, btf__free, btf__get_nr_types, btf__name_by_offset, btf__parse_elf, btf__parse_raw,
23 btf__type_by_id, btf_dump, btf_dump__dump_type, btf_dump__free, btf_dump__new, btf_dump_opts,
24 libbpf_find_kernel_btf, vdprintf,
25};
26use libc::{c_char, c_void};
27use regex::RegexSet;
28use std::env;
29use std::ffi::{CStr, CString};
30use std::fs::File;
31use std::io::{self, Write};
32use std::mem::{self, MaybeUninit};
33use std::os::unix::io::{FromRawFd, IntoRawFd, RawFd};
34use std::path::Path;
35use std::path::PathBuf;
36use std::ptr;
37pub const ENV_VMLINUX_PATH: &'static str = "REDBPF_VMLINUX";
38
39struct RawFdWrapper(RawFd);
41impl Drop for RawFdWrapper {
42 fn drop(&mut self) {
43 unsafe {
44 File::from_raw_fd(self.0);
45 }
46 }
47}
48
49impl From<File> for RawFdWrapper {
50 fn from(fobj: File) -> RawFdWrapper {
51 RawFdWrapper(fobj.into_raw_fd())
52 }
53}
54
55impl From<RawFdWrapper> for File {
56 fn from(rawfd: RawFdWrapper) -> File {
57 let fobj = unsafe { File::from_raw_fd(rawfd.0) };
58 mem::forget(rawfd);
59 fobj
60 }
61}
62
63struct BtfDumpWrapper(*mut btf_dump);
65impl Drop for BtfDumpWrapper {
66 fn drop(&mut self) {
67 unsafe {
68 btf_dump__free(self.0);
69 }
70 }
71}
72
73#[derive(Debug)]
76pub enum TypeGenError {
77 VmlinuxParsingError,
79 VmlinuxNotFound,
81 InvalidPath,
83 IO(io::Error),
85 RegexError,
87 DumpError,
88}
89
90type Result<T> = std::result::Result<T, TypeGenError>;
91
92pub struct VmlinuxBtfDump {
94 allowlist: Option<Vec<String>>,
95 btfptr: *mut btf,
96}
97
98impl VmlinuxBtfDump {
99 pub fn with_system_default() -> Result<Self> {
102 let btfptr = unsafe { libbpf_find_kernel_btf() };
103 if (btfptr as isize) < 0 {
104 return Err(TypeGenError::VmlinuxNotFound);
105 }
106
107 Ok(VmlinuxBtfDump {
108 allowlist: None,
109 btfptr,
110 })
111 }
112
113 pub fn with_elf_file(elf_file: impl AsRef<Path>) -> Result<Self> {
115 if !elf_file.as_ref().exists() {
116 return Err(TypeGenError::VmlinuxNotFound);
117 }
118
119 let elf_str = elf_file
120 .as_ref()
121 .to_str()
122 .ok_or(TypeGenError::InvalidPath)?;
123
124 let cvmlinux_path = CString::new(elf_str).unwrap();
125 let btfptr = unsafe { btf__parse_elf(cvmlinux_path.as_ptr(), ptr::null_mut()) };
126 if (btfptr as isize) < 0 {
127 return Err(TypeGenError::VmlinuxParsingError);
128 }
129
130 Ok(VmlinuxBtfDump {
131 allowlist: None,
132 btfptr,
133 })
134 }
135
136 pub fn with_raw_file(raw: impl AsRef<Path>) -> Result<Self> {
138 if !raw.as_ref().exists() {
139 return Err(TypeGenError::VmlinuxNotFound);
140 }
141
142 let raw_str = raw.as_ref().to_str().ok_or(TypeGenError::InvalidPath)?;
143
144 let cpath = CString::new(raw_str).unwrap();
145 let btfptr = unsafe { btf__parse_raw(cpath.as_ptr()) };
146 if (btfptr as isize) < 0 {
147 return Err(TypeGenError::VmlinuxParsingError);
148 }
149
150 Ok(VmlinuxBtfDump {
151 allowlist: None,
152 btfptr,
153 })
154 }
155
156 pub fn allowlist(mut self, pattern: &str) -> Self {
158 let allowlist: &mut Vec<String> = if let Some(allowlist) = &mut self.allowlist {
159 allowlist
160 } else {
161 self.allowlist = Some(vec![]);
162 self.allowlist.as_mut().unwrap()
163 };
164 allowlist.push(pattern.to_string());
165 self
166 }
167
168 pub fn generate(self, outfile: impl AsRef<Path>) -> Result<()> {
171 let mut fobj = File::create(&outfile).or_else(|e| Err(TypeGenError::IO(e)))?;
172 let header_name = outfile.as_ref().file_name().unwrap();
173 let guard_name = header_name
174 .to_str()
175 .unwrap()
176 .to_uppercase()
177 .chars()
178 .map(|c| match c {
179 'A'..='Z' => c,
180 _ => '_',
181 })
182 .collect::<String>();
183 let guardstr = format!(
184 "#ifndef __{guard_name}__\n#define __{guard_name}__\n\n",
185 guard_name = guard_name
186 );
187 fobj.write_all(guardstr.as_bytes())
188 .or_else(|e| Err(TypeGenError::IO(e)))?;
189 fobj.flush().or_else(|e| Err(TypeGenError::IO(e)))?;
190 let mut rawfd: RawFdWrapper = fobj.into();
191 let white_re = if let Some(ref allowlist) = self.allowlist {
192 Some(RegexSet::new(allowlist).or(Err(TypeGenError::RegexError))?)
193 } else {
194 None
195 };
196 unsafe {
197 let dump_opts = {
198 let mut uninit = MaybeUninit::<btf_dump_opts>::zeroed();
199 (*uninit.as_mut_ptr()).ctx = &mut rawfd as *mut _ as *mut _;
200 uninit.assume_init()
201 };
202 let dumpptr = btf_dump__new(
203 self.btfptr,
204 ptr::null(),
205 &dump_opts as *const _,
206 Some(vdprintf_wrapper),
207 );
208 if (dumpptr as isize) < 0 {
209 return Err(TypeGenError::DumpError);
210 }
211 let dumpptr = BtfDumpWrapper(dumpptr);
212 for type_id in 1..=btf__get_nr_types(self.btfptr) {
213 let btftypeptr = btf__type_by_id(self.btfptr, type_id);
214 let nameptr = btf__name_by_offset(self.btfptr, (*btftypeptr).name_off);
215 if nameptr.is_null() {
216 continue;
217 }
218
219 let cname = CStr::from_ptr(nameptr);
220 let namestr = cname.to_str().or(Err(TypeGenError::DumpError))?;
221 if let Some(wre) = &white_re {
222 if !wre.is_match(namestr) {
223 continue;
224 }
225 }
226 if btf_dump__dump_type(dumpptr.0, type_id) < 0 {
227 return Err(TypeGenError::DumpError);
228 }
229 }
230 }
231
232 let mut fobj: File = rawfd.into();
233 fobj.write_all(b"#endif\n")
234 .or_else(|e| Err(TypeGenError::IO(e)))?;
235
236 Ok(())
237 }
238}
239
240impl Drop for VmlinuxBtfDump {
241 fn drop(&mut self) {
242 unsafe {
243 btf__free(self.btfptr);
244 }
245 }
246}
247
248#[cfg(not(any(target_arch = "arm", target_arch = "aarch64")))]
250unsafe extern "C" fn vdprintf_wrapper(
251 ctx: *mut c_void,
252 format: *const c_char,
253 va_list: *mut super::__va_list_tag,
254) {
255 let rawfd_wrapper = &*(ctx as *mut RawFdWrapper);
256 vdprintf(rawfd_wrapper.0, format, va_list);
257}
258
259#[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
261unsafe extern "C" fn vdprintf_wrapper(
262 ctx: *mut c_void,
263 format: *const c_char,
264 #[cfg(target_env = "musl")] va_list: super::__isoc_va_list,
265 #[cfg(not(target_env = "musl"))] va_list: super::__gnuc_va_list,
266) {
267 let rawfd_wrapper = &*(ctx as *mut RawFdWrapper);
268 vdprintf(rawfd_wrapper.0, format, va_list);
269}
270
271pub fn get_custom_vmlinux_path() -> Option<PathBuf> {
272 Some(PathBuf::from(env::var(ENV_VMLINUX_PATH).ok()?))
273}
274
275pub fn vmlinux_btf_dump() -> Result<VmlinuxBtfDump> {
280 if let Some(path) = get_custom_vmlinux_path() {
281 if path.to_str().unwrap() == "system" {
282 VmlinuxBtfDump::with_system_default()
283 } else {
284 VmlinuxBtfDump::with_raw_file(&path).or_else(|_| VmlinuxBtfDump::with_elf_file(&path))
285 }
286 } else {
287 VmlinuxBtfDump::with_system_default()
288 }
289}