1use clap::{ColorChoice, Parser};
2use std::collections::HashMap;
3use std::fs;
4use std::process;
5
6#[derive(Parser, Debug)]
8#[command(author, version, about, long_about = "A tool to annotate vmlinux.h with documentation from kernel sources", color = ColorChoice::Always)]
9struct Args {
10 #[arg(short, long)]
12 kernel_dir: String,
13
14 #[arg(short, long)]
16 vmlinux_h: String,
17
18 #[arg(short, long, default_value = "vmlinux_annotated.h")]
20 output: String,
21}
22
23fn build_bpf_kfunc_map(kernel_dir: &str) -> HashMap<String, String> {
25 let files: Vec<_> = walkdir::WalkDir::new(kernel_dir)
27 .into_iter()
28 .filter_map(|e| e.ok())
29 .filter(|e| {
30 let path = e.path();
31 path.is_file() && path.extension().is_some_and(|ext| ext == "c" || ext == "h")
32 })
33 .map(|e| e.path().to_path_buf())
34 .collect();
35
36 println!("Found {} files to process", files.len());
37
38 let mut comments_map = HashMap::new();
40
41 for file in files {
42 let content = match fs::read_to_string(&file) {
43 Ok(content) => content,
44 Err(e) => {
45 eprintln!("Error reading {}: {}", file.display(), e);
46 continue;
47 }
48 };
49
50 let mut current_comment = String::new();
51 let mut in_comment = false;
52 let mut prev_was_close = false;
53 let mut last_comment_line = 0;
54 let mut line_number = 0;
55
56 for line in content.lines() {
57 line_number += 1;
58
59 if line.contains("/**") {
60 in_comment = true;
61 current_comment.clear();
62 current_comment.push_str(line);
63 current_comment.push('\n');
64 } else if in_comment {
65 if line.contains("*/") {
66 current_comment.push_str(line);
67 in_comment = false;
68 prev_was_close = true;
69 last_comment_line = line_number;
70 } else {
71 current_comment.push_str(line);
72 current_comment.push('\n');
73 }
74 } else if !current_comment.is_empty() && line.contains("__bpf_kfunc") {
75 if line.contains("(") && line.contains(" ") {
76 let sig = (" ".to_owned()
77 + line
78 .trim()
79 .to_string()
80 .split_terminator('(')
81 .collect::<Vec<&str>>()[0]
82 .split_terminator(' ')
83 .collect::<Vec<&str>>()
84 .last()
85 .unwrap()
86 .to_string()
87 .to_owned()
88 .as_str()
89 + "(")
90 .to_string();
91 comments_map.insert(sig, current_comment.trim().to_string());
92 }
93 current_comment.clear();
94 prev_was_close = false;
95 } else if !line.is_empty()
96 && prev_was_close
97 && (line_number - last_comment_line > 1 || line.contains("__bpf_kptr"))
98 {
99 current_comment.clear();
101 prev_was_close = false;
102 }
103 }
104 }
105
106 println!("Processing complete");
107 comments_map
108}
109
110fn annotate_vmlinux(
112 vmlinux_h: &str,
113 comments_map: &HashMap<String, String>,
114 struct_map: &HashMap<String, String>,
115) -> Result<String, std::io::Error> {
116 let content = fs::read_to_string(vmlinux_h)?;
118
119 let mut output = String::new();
121 let mut matches_found = 0;
122 let mut struct_matches_found = 0;
123
124 for line in content.lines() {
125 for (key, comment) in comments_map {
127 if line.contains("extern") && line.contains(key) {
128 output.push_str(comment);
129 output.push('\n');
130 matches_found += 1;
131 break;
132 }
133 }
134
135 for (key, comment) in struct_map {
137 if line.contains(key) && (line.contains("struct") || line.contains("enum")) {
138 output.push_str(comment);
139 output.push('\n');
140 struct_matches_found += 1;
141 break;
142 }
143 }
144
145 output.push_str(line);
146 output.push('\n');
147 }
148
149 println!(
150 "Added {} function comments and {} struct/enum comments to vmlinux.h",
151 matches_found, struct_matches_found
152 );
153 Ok(output)
154}
155
156fn build_kernel_struct_map(kernel_dir: &str) -> HashMap<String, String> {
157 let files: Vec<_> = walkdir::WalkDir::new(kernel_dir)
159 .into_iter()
160 .filter_map(|e| e.ok())
161 .filter(|e| {
162 let path = e.path();
163 path.is_file() && path.extension().is_some_and(|ext| ext == "c" || ext == "h")
164 })
165 .map(|e| e.path().to_path_buf())
166 .collect();
167
168 println!("Found {} files to process", files.len());
169
170 let mut struct_map = HashMap::new();
172
173 for file in files {
174 let content = match fs::read_to_string(&file) {
175 Ok(content) => content,
176 Err(e) => {
177 eprintln!("Error reading {}: {}", file.display(), e);
178 continue;
179 }
180 };
181
182 let mut current_comment = String::new();
183 let mut in_comment = false;
184 let mut last_comment_line = 0;
185 let mut line_number = 0;
186
187 for line in content.lines() {
188 line_number += 1;
189
190 if line.contains("/**") {
191 in_comment = true;
192 current_comment.clear();
193 current_comment.push_str(line);
194 current_comment.push('\n');
195 } else if in_comment {
196 if line.contains("*/") {
197 current_comment.push_str(line);
198 in_comment = false;
199 last_comment_line = line_number;
200 } else {
201 current_comment.push_str(line);
202 current_comment.push('\n');
203 }
204 } else if !current_comment.is_empty()
205 && (line.trim().starts_with("struct") || line.trim().starts_with("enum"))
206 && line.trim().ends_with(" {")
207 && line
208 .trim()
209 .split_terminator(' ')
210 .collect::<Vec<&str>>()
211 .len()
212 == 3
213 && (line_number - last_comment_line <= 1)
214 {
215 let mut key =
216 line.trim().split_terminator(' ').collect::<Vec<&str>>()[1].to_string();
217 if line.contains("struct") {
218 key = "struct ".to_string() + key.as_str() + " {";
219 } else if line.contains("enum") {
220 key = "enum ".to_string() + key.as_str() + " {";
221 }
222 struct_map.insert(key.clone(), current_comment.clone());
223 current_comment.clear();
224 } else if !line.is_empty()
225 && !current_comment.is_empty()
226 && (line_number - last_comment_line > 1)
227 {
228 current_comment.clear();
230 }
231 }
232 }
233
234 println!("Processing complete");
235 struct_map
236}
237
238fn main() {
239 let args = Args::parse();
240
241 let comments_map = build_bpf_kfunc_map(&args.kernel_dir);
243 println!(
244 "Found {} __bpf_kfunc declarations with comments",
245 comments_map.len()
246 );
247
248 let struct_map = build_kernel_struct_map(&args.kernel_dir);
250 println!("Found {} structs and enums with comments", struct_map.len());
251
252 match annotate_vmlinux(&args.vmlinux_h, &comments_map, &struct_map) {
254 Ok(annotated_content) => {
255 if let Err(e) = fs::write(&args.output, annotated_content) {
257 eprintln!("Error writing annotated file: {}", e);
258 process::exit(1);
259 }
260 println!("Successfully wrote annotated vmlinux.h to {}", args.output);
261 }
262 Err(e) => {
263 eprintln!("Error processing vmlinux.h: {}", e);
264 process::exit(1);
265 }
266 }
267}