1 //! A program which generates a linux-headers installation and runs bindgen
2 //! over the headers, for each supported architecture.
3 
4 use bindgen::{builder, EnumVariation};
5 use std::collections::HashSet;
6 use std::fs::File;
7 use std::io::{BufRead, BufReader, Read, Write};
8 use std::path::Path;
9 use std::process::Command;
10 use std::{env, fs};
11 
12 #[allow(unused_doc_comments)]
13 const LINUX_VERSION: &str = "v5.17";
14 
15 /// Some commonly used features.
16 const DEFAULT_FEATURES: &str = "\"general\", \"errno\"";
17 
mainnull18 fn main() {
19     let mut args = env::args();
20     let _exe = args.next().unwrap();
21     let cmd = args.next();
22 
23     // This is the main invocation path.
24     assert!(cmd.is_none());
25     assert!(args.next().is_none());
26 
27     git_init();
28 
29     let out = tempdir::TempDir::new("linux-raw-sys").unwrap();
30     let out_dir = out.path();
31     let linux_headers = out_dir.join("linux-headers");
32     let linux_include = linux_headers.join("include");
33 
34     // Clean up any modules from previous builds.
35     for entry in fs::read_dir("../src").unwrap() {
36         let entry = entry.unwrap();
37         assert!(!entry.path().to_str().unwrap().ends_with("."));
38         if entry.file_type().unwrap().is_dir() {
39             fs::remove_dir_all(entry.path()).ok();
40         }
41     }
42 
43     // Edit ../src/lib.rs
44     let mut src_lib_rs_in = File::open("../src/lib.rs").unwrap();
45     let mut src_lib_rs_contents = String::new();
46     src_lib_rs_in
47         .read_to_string(&mut src_lib_rs_contents)
48         .unwrap();
49     let edit_at = src_lib_rs_contents
50         .find("// The rest of this file is auto-generated!\n")
51         .unwrap();
52     src_lib_rs_contents = src_lib_rs_contents[..edit_at].to_owned();
53 
54     let mut src_lib_rs = File::create("../src/lib.rs").unwrap();
55     src_lib_rs
56         .write_all(src_lib_rs_contents.as_bytes())
57         .unwrap();
58     src_lib_rs
59         .write_all("// The rest of this file is auto-generated!\n".as_bytes())
60         .unwrap();
61 
62     // Edit ../Cargo.toml
63     let mut cargo_toml_in = File::open("../Cargo.toml").unwrap();
64     let mut cargo_toml_contents = String::new();
65     cargo_toml_in
66         .read_to_string(&mut cargo_toml_contents)
67         .unwrap();
68     let edit_at = cargo_toml_contents
69         .find("# The rest of this file is auto-generated!\n")
70         .unwrap();
71     cargo_toml_contents = cargo_toml_contents[..edit_at].to_owned();
72 
73     // Generate Cargo.toml
74     let mut cargo_toml = File::create("../Cargo.toml").unwrap();
75     cargo_toml
76         .write_all(cargo_toml_contents.as_bytes())
77         .unwrap();
78     cargo_toml
79         .write_all("# The rest of this file is auto-generated!\n".as_bytes())
80         .unwrap();
81     writeln!(cargo_toml, "[features]").unwrap();
82 
83     let mut features: HashSet<String> = HashSet::new();
84 
85     let linux_version = LINUX_VERSION;
86     // Checkout a specific version of Linux.
87     git_checkout(linux_version);
88 
89     let mut linux_archs = fs::read_dir(&format!("linux/arch"))
90         .unwrap()
91         .map(|entry| entry.unwrap())
92         .collect::<Vec<_>>();
93     // Sort archs list as filesystem iteration order is non-deterministic
94     linux_archs.sort_by_key(|entry| entry.file_name());
95     for linux_arch_entry in linux_archs {
96         if !linux_arch_entry.file_type().unwrap().is_dir() {
97             continue;
98         }
99         let linux_arch = linux_arch_entry.file_name().to_str().unwrap().to_owned();
100 
101         let rust_arches = rust_arches(&linux_arch);
102         if rust_arches.is_empty() {
103             continue;
104         }
105 
106         fs::create_dir_all(&linux_headers).unwrap();
107 
108         let mut headers_made = false;
109         for rust_arch in rust_arches {
110             if !headers_made {
111                 make_headers_install(&linux_arch, &linux_headers);
112                 headers_made = true;
113             }
114 
115             eprintln!(
116                 "Generating all bindings for Linux {} architecture {}",
117                 linux_version, rust_arch
118             );
119 
120             let src_arch = format!("../src/{}", rust_arch);
121 
122             fs::create_dir_all(&src_arch).unwrap();
123 
124             let mut modules = fs::read_dir("modules")
125                 .unwrap()
126                 .map(|entry| entry.unwrap())
127                 .collect::<Vec<_>>();
128             // Sort module list as filesystem iteration order is non-deterministic
129             modules.sort_by_key(|entry| entry.file_name());
130             for mod_entry in modules {
131                 let header_name = mod_entry.path();
132                 let mod_name = header_name.file_stem().unwrap().to_str().unwrap();
133                 let mod_rs = format!("{}/{}.rs", src_arch, mod_name);
134 
135                 writeln!(src_lib_rs, "#[cfg(feature = \"{}\")]", mod_name).unwrap();
136                 if *rust_arch == "x32" {
137                     writeln!(src_lib_rs, "#[cfg(all(target_arch = \"x86_64\", target_pointer_width = \"32\"))]").unwrap();
138                 } else if *rust_arch == "x86_64" {
139                     writeln!(src_lib_rs, "#[cfg(all(target_arch = \"x86_64\", target_pointer_width = \"64\"))]").unwrap();
140                 } else {
141                     writeln!(src_lib_rs, "#[cfg(target_arch = \"{}\")]", rust_arch).unwrap();
142                 }
143                 writeln!(src_lib_rs, "#[path = \"{}/{}.rs\"]", rust_arch, mod_name).unwrap();
144                 writeln!(src_lib_rs, "pub mod {};", mod_name).unwrap();
145 
146                 run_bindgen(
147                     linux_include.to_str().unwrap(),
148                     header_name.to_str().unwrap(),
149                     &mod_rs,
150                     mod_name,
151                     rust_arch,
152                     linux_version,
153                 );
154 
155                 // Collect all unique feature names across all architectures.
156                 if features.insert(mod_name.to_owned()) {
157                     writeln!(cargo_toml, "{} = []", mod_name).unwrap();
158                 }
159             }
160         }
161 
162         fs::remove_dir_all(&linux_headers).unwrap();
163     }
164 
165     writeln!(cargo_toml, "default = [\"std\", {}]", DEFAULT_FEATURES).unwrap();
166     writeln!(cargo_toml, "std = []").unwrap();
167     writeln!(cargo_toml, "no_std = []").unwrap();
168     writeln!(
169         cargo_toml,
170         "rustc-dep-of-std = [\"core\", \"compiler_builtins\", \"no_std\"]"
171     )
172     .unwrap();
173 
174     eprintln!("All bindings generated!");
175 }
176 
git_initnull177 fn git_init() {
178     // Clone the linux kernel source repo if necessary. Ignore exit code as it will
179     // be non-zero in case it was already cloned.
180     //
181     // Use a treeless partial clone to save disk space and clone time.
182     // See <https://github.blog/2020-12-21-get-up-to-speed-with-partial-clone-and-shallow-clone/>
183     // for more info on partial clones.
184     //
185     // Note: this is not using the official repo
186     // <git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git> but
187     // the github fork as the server of the official repo doesn't recognize
188     // filtering.
189     if !Path::new("linux/.git").exists() {
190         assert!(Command::new("git")
191             .arg("clone")
192             .arg("https://github.com/torvalds/linux.git")
193             .arg("--filter=tree:0")
194             .arg("--no-checkout")
195             .status()
196             .unwrap()
197             .success());
198     }
199 
200     // Setup sparse checkout. This greatly reduces the amount of objects necessary
201     // to checkout the tree.
202     assert!(Command::new("git")
203         .arg("sparse-checkout")
204         .arg("init")
205         .current_dir("linux")
206         .status()
207         .unwrap()
208         .success());
209 
210     fs::write(
211         "linux/.git/info/sparse-checkout",
212         "/*
213 !/*/
214 /include/
215 /arch/
216 /scripts/
217 /tools/",
218     )
219     .unwrap();
220 }
221 
222 fn git_checkout(rev: &str) {
223     // Delete any generated files from previous versions.
224     assert!(Command::new("git")
225         .arg("clean")
226         .arg("-f")
227         .arg("-d")
228         .current_dir("linux")
229         .status()
230         .unwrap()
231         .success());
232 
233     // Check out the given revision.
234     assert!(Command::new("git")
235         .arg("checkout")
236         .arg(rev)
237         .arg("-f")
238         .current_dir("linux")
239         .status()
240         .unwrap()
241         .success());
242 
243     // Delete any untracked generated files from previous versions.
244     assert!(Command::new("git")
245         .arg("clean")
246         .arg("-f")
247         .arg("-d")
248         .current_dir("linux")
249         .status()
250         .unwrap()
251         .success());
252 }
253 
254 fn make_headers_install(linux_arch: &str, linux_headers: &Path) {
255     assert!(Command::new("make")
256         .arg(format!("headers_install"))
257         .arg(format!("ARCH={}", linux_arch))
258         .arg(format!(
259             "INSTALL_HDR_PATH={}",
260             fs::canonicalize(&linux_headers).unwrap().to_str().unwrap()
261         ))
262         .current_dir("linux")
263         .status()
264         .unwrap()
265         .success());
266 }
267 
268 fn rust_arches(linux_arch: &str) -> &[&str] {
269     match linux_arch {
270         "arm" => &["arm"],
271         "arm64" => &["aarch64"],
272         "avr32" => &["avr"],
273         // hexagon gets build errors; disable it for now
274         "hexagon" => &[],
275         "mips" => &["mips", "mips64"],
276         "powerpc" => &["powerpc", "powerpc64"],
277         "riscv" => &["riscv32", "riscv64"],
278         "s390" => &["s390x"],
279         "sparc" => &["sparc", "sparc64"],
280         "x86" => &["x86", "x86_64", "x32"],
281         "alpha" | "cris" | "h8300" | "m68k" | "microblaze" | "mn10300" | "score" | "blackfin"
282         | "frv" | "ia64" | "m32r" | "m68knommu" | "parisc" | "sh" | "um" | "xtensa"
283         | "unicore32" | "c6x" | "nios2" | "openrisc" | "csky" | "arc" | "nds32" | "metag"
284         | "tile" => &[],
285         _ => panic!("unrecognized arch: {}", linux_arch),
286     }
287 }
288 
289 fn run_bindgen(
290     linux_include: &str,
291     header_name: &str,
292     mod_rs: &str,
293     mod_name: &str,
294     rust_arch: &str,
295     linux_version: &str,
296 ) {
297     let clang_target = compute_clang_target(rust_arch);
298 
299     eprintln!(
300         "Generating bindings for {} on Linux {} architecture {}",
301         mod_name, linux_version, rust_arch
302     );
303 
304     let mut builder = builder()
305         // The generated bindings are quite large, so use a few simple options
306         // to keep the file sizes down.
307         .rustfmt_configuration_file(Some(Path::new("bindgen-rustfmt.toml").to_owned()))
308         .layout_tests(false)
309         .generate_comments(false)
310         .default_enum_style(EnumVariation::Rust {
311             non_exhaustive: true,
312         })
313         .array_pointers_in_arguments(true)
314         .derive_debug(true)
315         .clang_arg(&format!("--target={}", clang_target))
316         .clang_arg("-DBITS_PER_LONG=(__SIZEOF_LONG__*__CHAR_BIT__)")
317         .clang_arg("-nostdinc")
318         .clang_arg("-I")
319         .clang_arg(linux_include)
320         .clang_arg("-I")
321         .clang_arg("include")
322         .blocklist_item("NULL");
323 
324     // Avoid duplicating ioctl names in the `general` module.
325     if mod_name == "general" {
326         for ioctl in BufReader::new(File::open("ioctl/generated.txt").unwrap()).lines() {
327             builder = builder.blocklist_item(ioctl.unwrap());
328         }
329     }
330 
331     let bindings = builder
332         .use_core()
333         .ctypes_prefix("crate::ctypes")
334         .header(header_name)
335         .generate()
336         .expect(&format!("generate bindings for {}", mod_name));
337     bindings
338         .write_to_file(mod_rs)
339         .expect(&format!("write_to_file for {}", mod_name));
340 }
341 
342 fn compute_clang_target(rust_arch: &str) -> String {
343     if rust_arch == "x86" {
344         format!("i686-unknown-linux")
345     } else if rust_arch == "x32" {
346         format!("x86_64-unknown-linux-gnux32")
347     } else {
348         format!("{}-unknown-linux", rust_arch)
349     }
350 }
351