1use libbpf_cargo::SkeletonBuilder;
2#[cfg(feature = "vmlinux-archs")]
3mod tmp_dir;
4
5macro_rules! known_env {
6 ($name:literal) => {
7 std::env::var($name).expect(concat!($name, " must be set in build script"))
8 };
9}
10
11enum DiffError {
12 File1Read,
13 File2Read,
14 Io,
15}
16
17fn try_file_content_differs(file1: &str, file2: &str) -> Result<bool, DiffError> {
18 use std::fs;
19 use std::io::Read;
20 let mut buf1 = Vec::new();
21 let mut buf2 = Vec::new();
22 let mut file1 = fs::File::open(file1).map_err(|_| DiffError::File1Read)?;
23 let mut file2 = fs::File::open(file2).map_err(|_| DiffError::File2Read)?;
24 if file1.read_to_end(&mut buf1).is_err() || file2.read_to_end(&mut buf2).is_err() {
25 Err(DiffError::Io)
26 } else {
27 Ok(buf1 != buf2)
28 }
29}
30
31fn sym_link_when_files_differ(from: &str, to: &str) -> Result<(), std::io::Error> {
32 let differs = try_file_content_differs(from, to);
33 match differs {
34 Err(DiffError::File1Read) => Err(std::io::Error::new(
35 std::io::ErrorKind::NotFound,
36 format!("Link source file not found: {from}"),
37 )),
38 Ok(false) => Ok(()),
39 _ => {
40 std::fs::remove_file(to).ok();
41 std::os::unix::fs::symlink(from, to)?;
42 Ok(())
43 }
44 }
45}
46
47#[cfg(feature = "vmlinux-archs")]
48fn gen_vmlinux_for_archs(dst_dir: &str) -> Result<(), std::io::Error> {
49 let tmp = crate::tmp_dir::TmpDir::new();
50 let tmp = tmp.path.clone();
51 let tmp = tmp.to_str().unwrap();
52 if std::fs::metadata(dst_dir).is_ok() {
53 return Ok(());
54 }
55 if !std::fs::metadata("/sys/kernel/btf/vmlinux").is_ok() {
56 return Err(std::io::Error::new(
57 std::io::ErrorKind::Other,
58 "Error (BTF not enabled on this host)",
59 ));
60 }
61 if !std::process::Command::new("which")
62 .arg("bpftool")
63 .output()
64 .map(|output| output.status.success())
65 .unwrap_or(false)
66 {
67 return Err(std::io::Error::new(
68 std::io::ErrorKind::Other,
69 "Error (bpftool not found)",
70 ));
71 }
72 let gitcloned = std::process::Command::new("git")
73 .arg("clone")
74 .arg("https://github.com/libbpf/libbpf-bootstrap")
75 .arg(format!("{tmp}/libbpf-bootstrap"))
76 .output()?;
77 if !gitcloned.status.success() {
78 return Err(std::io::Error::new(
79 std::io::ErrorKind::Other,
80 "Error (failed to clone) libbpf-bootstrap",
81 ));
82 }
83 let vmlinux_src_dir = format!("{tmp}/libbpf-bootstrap/vmlinux");
84 if !std::fs::metadata(&vmlinux_src_dir).is_ok() {
85 return Err(std::io::Error::new(
86 std::io::ErrorKind::Other,
87 "Error (vmlinux source not found)",
88 ));
89 }
90 let vmlinux_src_arch_dirs = std::process::Command::new("find")
91 .arg(".")
92 .arg("-mindepth")
93 .arg("1")
94 .arg("-maxdepth")
95 .arg("1")
96 .arg("-type")
97 .arg("d")
98 .current_dir(&vmlinux_src_dir)
99 .output()?;
100 let vmlinux_src_arch_dirs = std::str::from_utf8(&vmlinux_src_arch_dirs.stdout)
101 .unwrap()
102 .lines()
103 .filter(|line| !line.is_empty());
104 for arch in vmlinux_src_arch_dirs {
105 let src = format!("{vmlinux_src_dir}/{arch}/vmlinux.h");
106 let dst_dir = format!("{dst_dir}/{arch}");
107 let src = std::path::Path::new(&src).canonicalize().unwrap();
108 if std::fs::metadata(&dst_dir).is_err() {
109 std::fs::create_dir_all(&dst_dir)?;
110 }
111 std::fs::rename(src, &format!("{dst_dir}/vmlinux.h"))?;
112 }
113 Ok(())
114}
115
116fn gen_vmlinux_for_host(dst_dir: &str) -> Result<(), std::io::Error> {
117 let vmlinux_host_dst_dir = format!("{dst_dir}/host");
118 std::fs::create_dir_all(std::path::Path::new(&vmlinux_host_dst_dir))?;
119 let vmlinux_h = format!("{vmlinux_host_dst_dir}/vmlinux.h");
120 let bpftool = std::process::Command::new("bpftool")
121 .arg("btf")
122 .arg("dump")
123 .arg("file")
124 .arg("/sys/kernel/btf/vmlinux")
125 .arg("format")
126 .arg("c")
127 .output()?;
128 if !bpftool.status.success() {
129 return Err(std::io::Error::new(
130 std::io::ErrorKind::Other,
131 "Error (bpftool failed to dump BTF)",
132 ));
133 }
134 std::fs::write(&vmlinux_h, bpftool.stdout)?;
135 Ok(())
136}
137
138fn gen_vmlinux(dst_dir: &str) -> Result<(), std::io::Error> {
139 #[cfg(feature = "vmlinux-archs")]
140 {
141 gen_vmlinux_for_archs(dst_dir)?;
142 }
143 gen_vmlinux_for_host(dst_dir)
144}
145
146
147fn vmlinux_include_dir(vmlinux_hdr_dir: &str, arch: &str) -> String {
148 let archdir = format!("{vmlinux_hdr_dir}/{arch}");
149 if std::fs::metadata(&archdir).is_ok() {
150 archdir
151 } else {
152 format!("{vmlinux_hdr_dir}/host")
153 }
154}
155
156fn gen_skel(
157 prog_src_file: &str,
158 vmlinux_hdr_dir: &str,
159 skel_out_file: &str,
160) -> Result<(), std::io::Error> {
161 SkeletonBuilder::new()
162 .source(prog_src_file)
163 .debug(true)
164 .clang_args(["-I", vmlinux_hdr_dir])
165 .build_and_generate(std::path::Path::new(&skel_out_file))
166 .map_err(|e| {
167 println!(r#"
168To build the elf manually:
169$ clang -g -target bpf -D__TARGET_ARCH_x86 -c src/bpf/<prog>.bpf.c
170Which can be nice to actually see the compiler errors.
171Failed to build BPF program: {e}"#);
172 std::io::ErrorKind::Other
173 })?;
174 Ok(())
175}
176
177fn cargo_crate_manifest_dir() -> String {
178 known_env!("CARGO_MANIFEST_DIR")
179}
180
181fn cargo_out_dir() -> String {
182 known_env!("OUT_DIR")
183}
184
185fn cargo_arch() -> String {
186 known_env!("CARGO_CFG_TARGET_ARCH")
187}
188
189fn kernel_arch() -> String {
190 cargo_arch_to_kernel_arch(&cargo_arch()).to_string()
191}
192
193fn guess_bpf_prog_names() -> impl std::iter::Iterator<Item = String> {
194 let cratedir = cargo_crate_manifest_dir();
195 std::fs::read_dir(&std::path::Path::new(&format!("{cratedir}/src/bpf")))
196 .unwrap()
197 .map(|entry| entry.unwrap().file_name().to_str().unwrap().to_string())
198 .filter(|entry| entry.ends_with(".bpf.c"))
199 .map(|entry| entry.split('.').next().unwrap().to_string())
200}
201
202fn cargo_arch_to_kernel_arch(arch: &str) -> &str {
203 match arch {
204 "aarch64" => "arm64",
205 "loongarch64" => "loongarch",
206 "powerpc64" => "powerpc",
207 "riscv64" => "riscv",
208 "x86_64" => "x86",
209 _ => "host",
210 }
211}
212
213pub fn guess_targets<'a>() -> impl std::iter::Iterator<Item = BuildBpf> + 'a {
214 guess_bpf_prog_names().map(move |prog| {
215 let cratedir = cargo_crate_manifest_dir();
216 let outdir = cargo_out_dir();
217 let bpf_prog_src_file = format!("{cratedir}/src/bpf/{prog}.bpf.c");
218 let vmlinux_base_dir = format!("{outdir}/include/vmlinux");
219 let skel_dst_file = format!("{outdir}/skel_{prog}.rs");
220 BuildBpf {
221 bpf_prog_src_file,
222 vmlinux_base_dir,
223 skel_dst_file,
224 }
225 })
226}
227
228pub struct BuildBpf {
229 bpf_prog_src_file: String,
230 vmlinux_base_dir: String,
231 skel_dst_file: String,
232}
233
234impl BuildBpf {
235 pub fn bpf_prog_name(&self) -> String {
236 let filename = std::path::Path::new(&self.bpf_prog_src_file)
237 .file_name()
238 .unwrap()
239 .to_str()
240 .unwrap();
241 match filename.find('.') {
242 Some(firstdot) => filename[..firstdot].to_string(),
243 None => filename.to_string(),
244 }
245 }
246
247 pub fn try_build(&self) -> Result<&Self, std::io::Error> {
248 println!("cargo:rerun-if-changed={}", self.bpf_prog_src_file);
249 gen_vmlinux(&self.vmlinux_base_dir)?;
250 gen_skel(
251 &self.bpf_prog_src_file,
252 &vmlinux_include_dir(&self.vmlinux_base_dir, &kernel_arch()),
253 &self.skel_dst_file,
254 )?;
255 Ok(self)
256 }
257
258 pub fn must_build(&self) -> &Self {
259 self.try_build().unwrap()
260 }
261
262 pub fn try_sym_link_skel_to(&self, dst: &str) -> Result<&Self, std::io::Error> {
263 println!("cargo:rerun-if-changed={dst}");
264 sym_link_when_files_differ(&self.skel_dst_file, dst)?;
265 Ok(self)
266 }
267
268 pub fn must_sym_link_skel_to(&self, dst: &str) -> &Self {
269 self.try_sym_link_skel_to(dst).unwrap()
270 }
271}