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 {matches_found} function comments and {struct_matches_found} struct/enum comments to vmlinux.h"
151 );
152 Ok(output)
153}
154
155fn build_kernel_struct_map(kernel_dir: &str) -> HashMap<String, String> {
156 let files: Vec<_> = walkdir::WalkDir::new(kernel_dir)
158 .into_iter()
159 .filter_map(|e| e.ok())
160 .filter(|e| {
161 let path = e.path();
162 path.is_file() && path.extension().is_some_and(|ext| ext == "c" || ext == "h")
163 })
164 .map(|e| e.path().to_path_buf())
165 .collect();
166
167 println!("Found {} files to process", files.len());
168
169 let mut struct_map = HashMap::new();
171
172 for file in files {
173 let content = match fs::read_to_string(&file) {
174 Ok(content) => content,
175 Err(e) => {
176 eprintln!("Error reading {}: {}", file.display(), e);
177 continue;
178 }
179 };
180
181 let mut current_comment = String::new();
182 let mut in_comment = false;
183 let mut last_comment_line = 0;
184 let mut line_number = 0;
185
186 for line in content.lines() {
187 line_number += 1;
188
189 if line.contains("/**") {
190 in_comment = true;
191 current_comment.clear();
192 current_comment.push_str(line);
193 current_comment.push('\n');
194 } else if in_comment {
195 if line.contains("*/") {
196 current_comment.push_str(line);
197 in_comment = false;
198 last_comment_line = line_number;
199 } else {
200 current_comment.push_str(line);
201 current_comment.push('\n');
202 }
203 } else if !current_comment.is_empty()
204 && (line.trim().starts_with("struct") || line.trim().starts_with("enum"))
205 && line.trim().ends_with(" {")
206 && line
207 .trim()
208 .split_terminator(' ')
209 .collect::<Vec<&str>>()
210 .len()
211 == 3
212 && (line_number - last_comment_line <= 1)
213 {
214 let mut key =
215 line.trim().split_terminator(' ').collect::<Vec<&str>>()[1].to_string();
216 if line.contains("struct") {
217 key = "struct ".to_string() + key.as_str() + " {";
218 } else if line.contains("enum") {
219 key = "enum ".to_string() + key.as_str() + " {";
220 }
221 struct_map.insert(key.clone(), current_comment.clone());
222 current_comment.clear();
223 } else if !line.is_empty()
224 && !current_comment.is_empty()
225 && (line_number - last_comment_line > 1)
226 {
227 current_comment.clear();
229 }
230 }
231 }
232
233 println!("Processing complete");
234 struct_map
235}
236
237fn main() {
238 let args = Args::parse();
239
240 let comments_map = build_bpf_kfunc_map(&args.kernel_dir);
242 println!(
243 "Found {} __bpf_kfunc declarations with comments",
244 comments_map.len()
245 );
246
247 let struct_map = build_kernel_struct_map(&args.kernel_dir);
249 println!("Found {} structs and enums with comments", struct_map.len());
250
251 match annotate_vmlinux(&args.vmlinux_h, &comments_map, &struct_map) {
253 Ok(annotated_content) => {
254 if let Err(e) = fs::write(&args.output, annotated_content) {
256 eprintln!("Error writing annotated file: {e}");
257 process::exit(1);
258 }
259 println!("Successfully wrote annotated vmlinux.h to {}", args.output);
260 }
261 Err(e) => {
262 eprintln!("Error processing vmlinux.h: {e}");
263 process::exit(1);
264 }
265 }
266}