]>
Commit | Line | Data |
---|---|---|
17e855ce MS |
1 | #!/usr/bin/perl -w |
2 | # | |
3 | # kmsg kernel messages check and print tool. | |
4 | # | |
5 | # To check the source code for missing messages the script is called | |
6 | # with check, the name compiler and the compile parameters | |
7 | # kmsg-doc check $(CC) $(c_flags) $< | |
8 | # To create man pages for the messages the script is called with | |
9 | # kmsg-doc print $(CC) $(c_flags) $< | |
10 | # | |
11 | # Copyright IBM Corp. 2008 | |
12 | # Author(s): Martin Schwidefsky <schwidefsky@de.ibm.com> | |
13 | # Michael Holzheu <holzheu@linux.vnet.ibm.com> | |
14 | # | |
15 | ||
16 | use Cwd; | |
17 | use bigint; | |
18 | ||
19 | my $errors = 0; | |
20 | my $warnings = 0; | |
21 | my $srctree = ""; | |
22 | my $objtree = ""; | |
23 | my $kmsg_count = 0; | |
24 | ||
25 | sub remove_quotes($) | |
26 | { | |
27 | my ($string) = @_; | |
28 | my $inside = 0; | |
29 | my $slash = 0; | |
30 | my $result = ""; | |
31 | ||
32 | foreach my $str (split(/([\\"])/, $string)) { | |
33 | if ($inside && ($str ne "\"" || $slash)) { | |
34 | $result .= $str; | |
35 | } | |
36 | # Check for backslash before quote | |
37 | if ($str eq "\"") { | |
38 | if (!$slash) { | |
39 | $inside = !$inside; | |
40 | } | |
41 | $slash = 0; | |
42 | } elsif ($str eq "\\") { | |
43 | $slash = !$slash; | |
44 | } elsif ($str ne "") { | |
45 | $slash = 0; | |
46 | } | |
47 | } | |
48 | return $result; | |
49 | } | |
50 | ||
51 | sub string_to_bytes($) | |
52 | { | |
53 | my ($string) = @_; | |
54 | my %is_escape = ('"', 0x22, '\'', 0x27, 'n', 0x0a, 'r', 0x0d, 'b', 0x08, | |
55 | 't', 0x09, 'f', 0x0c, 'a', 0x07, 'v', 0x0b, '?', 0x3f); | |
56 | my (@ar, $slash, $len); | |
57 | ||
58 | # scan string, interpret backslash escapes and write bytes to @ar | |
59 | $len = 0; | |
60 | foreach my $ch (split(//, $string)) { | |
61 | if ($ch eq '\\') { | |
62 | $slash = !$slash; | |
63 | if (!$slash) { | |
64 | $ar[$len] = ord('\\'); | |
65 | $len++; | |
66 | } | |
67 | } elsif ($slash && defined $is_escape{$ch}) { | |
68 | # C99 backslash escapes: \\ \" \' \n \r \b \t \f \a \v \? | |
69 | $ar[$len] = $is_escape{$ch}; | |
70 | $len++; | |
71 | $slash = 0; | |
72 | } elsif ($slash) { | |
73 | # FIXME: C99 backslash escapes \nnn \xhh | |
74 | die("Unknown backslash escape in message $string."); | |
75 | } else { | |
76 | # normal character | |
77 | $ar[$len] = ord($ch); | |
78 | $len++; | |
79 | } | |
80 | } | |
81 | return @ar; | |
82 | } | |
83 | ||
84 | sub calc_jhash($) | |
85 | { | |
86 | my ($string) = @_; | |
87 | my @ar; | |
88 | my ($a, $b, $c, $i, $length, $len); | |
89 | ||
90 | @ar = string_to_bytes($string); | |
91 | $length = @ar; | |
92 | # add dummy elements to @ar to avoid if then else hell | |
93 | push @ar, (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); | |
94 | $a = 0x9e3779b9; | |
95 | $b = 0x9e3779b9; | |
96 | $c = 0; | |
97 | $i = 0; | |
98 | for ($len = $length + 12; $len >= 12; $len -= 12) { | |
99 | if ($len < 24) { | |
100 | # add length for last round | |
101 | $c += $length; | |
102 | } | |
103 | $a += $ar[$i] + ($ar[$i+1]<<8) + ($ar[$i+2]<<16) + ($ar[$i+3]<<24); | |
104 | $b += $ar[$i+4] + ($ar[$i+5]<<8) + ($ar[$i+6]<<16) + ($ar[$i+7]<<24); | |
105 | if ($len >= 24) { | |
106 | $c += $ar[$i+8] + ($ar[$i+9]<<8) + ($ar[$i+10]<<16) + ($ar[$i+11]<<24); | |
107 | } else { | |
108 | $c += ($ar[$i+8]<<8) + ($ar[$i+9]<<16) + ($ar[$i+10]<<24); | |
109 | } | |
110 | $a &= 0xffffffff; $b &= 0xffffffff; $c &= 0xffffffff; | |
111 | $a -= $b; $a -= $c; $a ^= ($c >> 13); $a &= 0xffffffff; | |
112 | $b -= $c; $b -= $a; $b ^= ($a << 8); $b &= 0xffffffff; | |
113 | $c -= $a; $c -= $b; $c ^= ($b >> 13); $c &= 0xffffffff; | |
114 | $a -= $b; $a -= $c; $a ^= ($c >> 12); $a &= 0xffffffff; | |
115 | $b -= $c; $b -= $a; $b ^= ($a << 16); $b &= 0xffffffff; | |
116 | $c -= $a; $c -= $b; $c ^= ($b >> 5); $c &= 0xffffffff; | |
117 | $a -= $b; $a -= $c; $a ^= ($c >> 3); $a &= 0xffffffff; | |
118 | $b -= $c; $b -= $a; $b ^= ($a << 10); $b &= 0xffffffff; | |
119 | $c -= $a; $c -= $b; $c ^= ($b >> 15); $c &= 0xffffffff; | |
120 | $i += 12; | |
121 | } | |
122 | return $c; | |
123 | } | |
124 | ||
125 | sub add_kmsg_desc($$$$$$) | |
126 | { | |
127 | my ($component, $text, $sev, $argv, $desc, $user) = @_; | |
128 | my ($hash, $tag); | |
129 | ||
130 | $text = remove_quotes($text); | |
131 | $hash = substr(sprintf("%08x", calc_jhash($text)), 2, 6); | |
132 | $tag = $component . "." . $hash; | |
133 | ||
134 | if ($kmsg_desc{$tag}) { | |
135 | if ($text ne $kmsg_desc{$tag}->{'TEXT'}) { | |
136 | warn "Duplicate message with tag $tag\n"; | |
137 | warn " --- $kmsg_desc{$tag}->{'TEXT'}\n"; | |
138 | warn " +++ $text\n"; | |
139 | } else { | |
140 | warn "Duplicate message description for \"$text\"\n"; | |
141 | } | |
142 | $errors++; | |
143 | return; | |
144 | } | |
145 | $kmsg_desc{$tag}->{'TEXT'} = $text; | |
146 | $kmsg_desc{$tag}->{'SEV'} = $sev; | |
147 | $kmsg_desc{$tag}->{'ARGV'} = $argv; | |
148 | $kmsg_desc{$tag}->{'DESC'} = $desc; | |
149 | $kmsg_desc{$tag}->{'USER'} = $user; | |
150 | } | |
151 | ||
152 | sub add_kmsg_print($$$$) | |
153 | { | |
154 | my ($component, $sev, $text, $argv) = @_; | |
155 | my ($hash, $tag, $count, $parm); | |
156 | ||
157 | $text = remove_quotes($text); | |
158 | $hash = substr(sprintf("%08x", calc_jhash($text)), 2, 6); | |
159 | $tag = $component . "." . $hash; | |
160 | ||
161 | # Pretty print severity | |
162 | $sev =~ s/"0"/Emerg/; | |
163 | $sev =~ s/"1"/Alert/; | |
164 | $sev =~ s/"2"/Critical/; | |
165 | $sev =~ s/"3"/Error/; | |
166 | $sev =~ s/"4"/Warning/; | |
167 | $sev =~ s/"5"/Notice/; | |
168 | $sev =~ s/"6"/Informational/; | |
169 | $sev =~ s/"7"/Debug/; | |
170 | $kmsg_print{$kmsg_count}->{'TAG'} = $tag; | |
171 | $kmsg_print{$kmsg_count}->{'TEXT'} = $text; | |
172 | $kmsg_print{$kmsg_count}->{'SEV'} = $sev; | |
173 | $kmsg_print{$kmsg_count}->{'ARGV'} = $argv; | |
174 | $kmsg_count += 1; | |
175 | } | |
176 | ||
177 | sub process_source_file($$) | |
178 | { | |
179 | my ($component, $file) = @_; | |
180 | my $state; | |
181 | my ($text, $sev, $argv, $desc, $user); | |
182 | ||
183 | if (!open(FD, "$file")) { | |
184 | return ""; | |
185 | } | |
186 | ||
187 | $state = 0; | |
188 | while (<FD>) { | |
189 | chomp; | |
190 | # kmsg message component: #define KMSG_COMPONENT "<component>" | |
191 | if (/^#define\s+KMSG_COMPONENT\s+\"(.*)\"[^\"]*$/o) { | |
192 | $component = $1; | |
193 | } | |
194 | if ($state == 0) { | |
195 | # single line kmsg for undocumented messages, format: | |
196 | # /*? Text: "<message>" */ | |
197 | if (/^\s*\/\*\?\s*Text:\s*(\".*\")\s*\*\/\s*$/o) { | |
198 | add_kmsg_desc($component, $1, "", "", "", ""); | |
199 | } | |
200 | # kmsg message start: '/*?' | |
201 | if (/^\s*\/\*\?\s*$/o) { | |
202 | $state = 1; | |
203 | ($text, $sev, $argv, $desc, $user) = ( "", "", "", "", "" ); | |
204 | } | |
205 | } elsif ($state == 1) { | |
206 | # kmsg message end: ' */' | |
207 | if (/^\s*\*\/\s*/o) { | |
208 | add_kmsg_desc($component, $text, $sev, $argv, $desc, $user); | |
209 | $state = 0; | |
210 | } | |
211 | # kmsg message text: ' * Text: "<message>"' | |
212 | elsif (/^\s*\*\s*Text:\s*(\".*\")\s*$/o) { | |
213 | $text = $1; | |
214 | } | |
215 | # kmsg message severity: ' * Severity: <sev>' | |
216 | elsif (/^\s*\*\s*Severity:\s*(\S*)\s*$/o) { | |
217 | $sev = $1; | |
218 | } | |
219 | # kmsg message parameter: ' * Parameter: <argv>' | |
220 | elsif (/^\s*\*\s*Parameter:\s*(\S*)\s*$/o) { | |
221 | if (!defined($1)) { | |
222 | $argv = ""; | |
223 | } else { | |
224 | $argv = $1; | |
225 | } | |
226 | $state = 2; | |
227 | } | |
228 | # kmsg message description start: ' * Description:' | |
229 | elsif (/^\s*\*\s*Description:\s*(\S*)\s*$/o) { | |
230 | if (!defined($1)) { | |
231 | $desc = ""; | |
232 | } else { | |
233 | $desc = $1; | |
234 | } | |
235 | $state = 3; | |
236 | } | |
237 | # kmsg has unrecognizable lines | |
238 | else { | |
239 | warn "Warning(${file}:$.): Cannot understand $_"; | |
240 | $warnings++; | |
241 | $state = 0; | |
242 | } | |
243 | } elsif ($state == 2) { | |
244 | # kmsg message end: ' */' | |
245 | if (/^\s*\*\//o) { | |
246 | warn "Warning(${file}:$.): Missing description, skipping message"; | |
247 | $warnings++; | |
248 | $state = 0; | |
249 | } | |
250 | # kmsg message description start: ' * Description:' | |
251 | elsif (/^\s*\*\s*Description:\s*$/o) { | |
252 | $desc = $1; | |
253 | $state = 3; | |
254 | } | |
255 | # kmsg message parameter line: ' * <argv>' | |
256 | elsif (/^\s*\*(.*)$/o) { | |
257 | $argv .= "\n" . $1; | |
258 | } else { | |
259 | warn "Warning(${file}:$.): Cannot understand $_"; | |
260 | $warnings++; | |
261 | $state = 0; | |
262 | } | |
263 | } elsif ($state == 3) { | |
264 | # kmsg message end: ' */' | |
265 | if (/^\s*\*\/\s*/o) { | |
266 | add_kmsg_desc($component, $text, $sev, $argv, $desc, $user); | |
267 | $state = 0; | |
268 | } | |
269 | # kmsg message description start: ' * User action:' | |
270 | elsif (/^\s*\*\s*User action:\s*$/o) { | |
271 | $user = $1; | |
272 | $state = 4; | |
273 | } | |
274 | # kmsg message description line: ' * <text>' | |
275 | elsif (/^\s*\*\s*(.*)$/o) { | |
276 | $desc .= "\n" . $1; | |
277 | } else { | |
278 | warn "Warning(${file}:$.): Cannot understand $_"; | |
279 | $warnings++; | |
280 | $state = 0; | |
281 | } | |
282 | } elsif ($state == 4) { | |
283 | # kmsg message end: ' */' | |
284 | if (/^\s*\*\/\s*/o) { | |
285 | add_kmsg_desc($component, $text, $sev, $argv, $desc, $user); | |
286 | $state = 0; | |
287 | } | |
288 | # kmsg message user action line: ' * <text>' | |
289 | elsif (/^\s*\*\s*(.*)$/o) { | |
290 | $user .= "\n" . $1; | |
291 | } else { | |
292 | warn "Warning(${file}:$.): Cannot understand $_"; | |
293 | $warnings++; | |
294 | $state = 0; | |
295 | } | |
296 | } | |
297 | } | |
298 | return $component; | |
299 | } | |
300 | ||
301 | sub process_cpp_file($$$$) | |
302 | { | |
303 | my ($cc, $options, $file, $component) = @_; | |
304 | ||
305 | open(FD, "$cc $gcc_options|") or die ("Preprocessing failed."); | |
306 | ||
307 | while (<FD>) { | |
308 | chomp; | |
309 | if (/.*__KMSG_PRINT\(\s*(\S*)\s*(\S*)\s*_FMT_(.*)_ARGS_\s*(.*)?_END_\s*\)/o) { | |
310 | if ($component ne "") { | |
311 | add_kmsg_print($component, $2, $3, $4); | |
312 | } else { | |
313 | warn "Error(${file}:$.): kmsg without component\n"; | |
314 | $errors++; | |
315 | } | |
316 | } elsif (/.*__KMSG_DEV\(\s*(\S*)\s*(\S*)\s*_FMT_(.*)_ARGS_\s*(.*)?_END_\s*\)/o) { | |
317 | if ($component ne "") { | |
318 | add_kmsg_print($component, $2, "\"%s: \"" . $3, $4); | |
319 | } else { | |
320 | warn "Error(${file}:$.): kmsg without component\n"; | |
321 | $errors++; | |
322 | } | |
323 | } | |
324 | } | |
325 | } | |
326 | ||
327 | sub check_messages($) | |
328 | { | |
329 | my $component = "@_"; | |
330 | my $failed = 0; | |
331 | ||
332 | for ($i = 0; $i < $kmsg_count; $i++) { | |
333 | $tag = $kmsg_print{$i}->{'TAG'}; | |
334 | if (!defined($kmsg_desc{$tag})) { | |
335 | add_kmsg_desc($component, | |
336 | "\"" . $kmsg_print{$i}->{'TEXT'} . "\"", | |
337 | $kmsg_print{$i}->{'SEV'}, | |
338 | $kmsg_print{$i}->{'ARGV'}, | |
339 | "Please insert description here", | |
340 | "What is the user supposed to do"); | |
341 | $kmsg_desc{$tag}->{'CHECK'} = 1; | |
342 | $failed = 1; | |
343 | warn "$component: Missing description for: ". | |
344 | $kmsg_print{$i}->{'TEXT'}."\n"; | |
345 | $errors++; | |
346 | next; | |
347 | } | |
348 | if ($kmsg_desc{$tag}->{'SEV'} ne "" && | |
349 | $kmsg_desc{$tag}->{'SEV'} ne $kmsg_print{$i}->{'SEV'}) { | |
350 | warn "Message severity mismatch for \"$kmsg_print{$i}->{'TEXT'}\"\n"; | |
351 | warn " --- $kmsg_desc{$tag}->{'SEV'}\n"; | |
352 | warn " +++ $kmsg_print{$i}->{'SEV'}\n"; | |
353 | } | |
354 | } | |
355 | return $failed; | |
356 | } | |
357 | ||
358 | sub print_templates() | |
359 | { | |
360 | print "Templates for missing messages:\n"; | |
361 | foreach $tag ( sort { $kmsg_desc{$a} <=> $kmsg_desc{$b} } keys %kmsg_desc ) { | |
362 | if (!defined($kmsg_desc{$tag}->{'CHECK'})) { | |
363 | next; | |
364 | } | |
365 | print "/*?\n"; | |
366 | print " * Text: \"$kmsg_desc{$tag}->{'TEXT'}\"\n"; | |
367 | print " * Severity: $kmsg_desc{$tag}->{'SEV'}\n"; | |
368 | $argv = $kmsg_desc{$tag}->{'ARGV'}; | |
369 | if ($argv ne "") { | |
370 | print " * Parameter:\n"; | |
371 | @parms = split(/\s*,\s*/,$kmsg_desc{$tag}->{'ARGV'}); | |
372 | $count = 0; | |
373 | foreach $parm (@parms) { | |
374 | $count += 1; | |
375 | if (!($parm eq "")) { | |
376 | print " * \@$count: $parm\n"; | |
377 | } | |
378 | } | |
379 | } | |
380 | print " * Description:\n"; | |
381 | print " * $kmsg_desc{$tag}->{'DESC'}\n"; | |
382 | print " * User action:\n"; | |
383 | print " * $kmsg_desc{$tag}->{'USER'}\n"; | |
384 | print " */\n\n"; | |
385 | } | |
386 | } | |
387 | ||
388 | sub write_man_pages() | |
389 | { | |
390 | my ($i, $file); | |
391 | ||
392 | for ($i = 0; $i < $kmsg_count; $i++) { | |
393 | $tag = $kmsg_print{$i}->{'TAG'}; | |
394 | if (!defined($kmsg_desc{$tag}) || | |
395 | defined($kmsg_desc{$tag}->{'CHECK'}) || | |
396 | $kmsg_desc{$tag}->{'DESC'} eq "") { | |
397 | next; | |
398 | } | |
399 | $file = $objtree . "man/" . $tag . ".9"; | |
400 | if (!open(WR, ">$file")) { | |
401 | warn "Error: Cannot open file $file\n"; | |
402 | $errors++; | |
403 | return; | |
404 | } | |
405 | print WR ".TH \"$tag\" 9 \"Linux Messages\" LINUX\n"; | |
406 | print WR ".SH Message\n"; | |
407 | print WR $tag . ": " . $kmsg_desc{$tag}->{'TEXT'} . "\n"; | |
408 | print WR ".SH Severity\n"; | |
409 | print WR "$kmsg_desc{$tag}->{'SEV'}\n"; | |
410 | $argv = $kmsg_desc{$tag}->{'ARGV'}; | |
411 | if ($argv ne "") { | |
412 | print WR ".SH Parameters\n"; | |
413 | @parms = split(/\s*\n\s*/,$kmsg_desc{$tag}->{'ARGV'}); | |
414 | foreach $parm (@parms) { | |
415 | $parm =~ s/^\s*(.*)\s*$/$1/; | |
416 | if (!($parm eq "")) { | |
417 | print WR "$parm\n\n"; | |
418 | } | |
419 | } | |
420 | } | |
421 | print WR ".SH Description"; | |
422 | print WR "$kmsg_desc{$tag}->{'DESC'}\n"; | |
423 | $user = $kmsg_desc{$tag}->{'USER'}; | |
424 | if ($user ne "") { | |
425 | print WR ".SH User action"; | |
426 | print WR "$user\n"; | |
427 | } | |
428 | } | |
429 | } | |
430 | ||
431 | if (defined($ENV{'srctree'})) { | |
432 | $srctree = "$ENV{'srctree'}" . "/"; | |
433 | } else { | |
434 | $srctree = getcwd; | |
435 | } | |
436 | ||
437 | if (defined($ENV{'objtree'})) { | |
438 | $objtree = "$ENV{'objtree'}" . "/"; | |
439 | } else { | |
440 | $objtree = getcwd; | |
441 | } | |
442 | ||
443 | if (defined($ENV{'SRCARCH'})) { | |
444 | $srcarch = "$ENV{'SRCARCH'}" . "/"; | |
445 | } else { | |
446 | print "kmsg-doc called without a valid \$SRCARCH\n"; | |
447 | exit 1; | |
448 | } | |
449 | ||
450 | $option = shift; | |
451 | ||
452 | $cc = shift; | |
453 | $gcc_options = "-E -D __KMSG_CHECKER "; | |
454 | foreach $tmp (@ARGV) { | |
455 | $tmp =~ s/\(/\\\(/; | |
456 | $tmp =~ s/\)/\\\)/; | |
457 | $gcc_options .= " $tmp"; | |
458 | $filename = $tmp; | |
459 | } | |
460 | ||
461 | $component = process_source_file("", $filename); | |
462 | if ($component ne "") { | |
463 | process_source_file($component, $srctree . "Documentation/kmsg/" . | |
464 | $srcarch . $component); | |
465 | process_source_file($component, $srctree . "Documentation/kmsg/" . | |
466 | $component); | |
467 | } | |
468 | ||
469 | process_cpp_file($cc, $gcc_options, $filename, $component); | |
470 | if ($option eq "check") { | |
471 | if (check_messages($component)) { | |
472 | print_templates(); | |
473 | } | |
474 | } elsif ($option eq "print") { | |
475 | write_man_pages(); | |
476 | } | |
477 | ||
478 | exit($errors); |