build_bpf/
lib.rs

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}