| #!/usr/bin/perl -w |
| # |
| # kmsg kernel messages check and print tool. |
| # |
| # To check the source code for missing messages the script is called |
| # with check, the name compiler and the compile parameters |
| # kmsg-doc check $(CC) $(c_flags) $< |
| # To create man pages for the messages the script is called with |
| # kmsg-doc print $(CC) $(c_flags) $< |
| # |
| # Copyright IBM Corp. 2008 |
| # Author(s): Martin Schwidefsky <schwidefsky@de.ibm.com> |
| # Michael Holzheu <holzheu@linux.vnet.ibm.com> |
| # |
| |
| use Cwd; |
| use Switch; |
| use bigint; |
| |
| my $errors = 0; |
| my $warnings = 0; |
| my $srctree = ""; |
| my $objtree = ""; |
| my $kmsg_count = 0; |
| |
| sub remove_quotes($) |
| { |
| my ($string) = @_; |
| my $inside = 0; |
| my $slash = 0; |
| my $result = ""; |
| |
| foreach my $str (split(/([\\"])/, $string)) { |
| if ($inside && ($str ne "\"" || $slash)) { |
| $result .= $str; |
| } |
| # Check for backslash before quote |
| if ($str eq "\"") { |
| if (!$slash) { |
| $inside = !$inside; |
| } |
| $slash = 0; |
| } elsif ($str eq "\\") { |
| $slash = !$slash; |
| } elsif ($str ne "") { |
| $slash = 0; |
| } |
| } |
| return $result; |
| } |
| |
| sub string_to_bytes($) |
| { |
| my ($string) = @_; |
| my %is_escape = ('"', 0x22, '\'', 0x27, 'n', 0x0a, 'r', 0x0d, 'b', 0x08, |
| 't', 0x09, 'f', 0x0c, 'a', 0x07, 'v', 0x0b, '?', 0x3f); |
| my (@ar, $slash, $len); |
| |
| # scan string, interpret backslash escapes and write bytes to @ar |
| $len = 0; |
| foreach my $ch (split(//, $string)) { |
| if ($ch eq '\\') { |
| $slash = !$slash; |
| if (!$slash) { |
| $ar[$len] = ord('\\'); |
| $len++; |
| } |
| } elsif ($slash && defined $is_escape{$ch}) { |
| # C99 backslash escapes: \\ \" \' \n \r \b \t \f \a \v \? |
| $ar[$len] = $is_escape{$ch}; |
| $len++; |
| $slash = 0; |
| } elsif ($slash) { |
| # FIXME: C99 backslash escapes \nnn \xhh |
| die("Unknown backslash escape in message $string."); |
| } else { |
| # normal character |
| $ar[$len] = ord($ch); |
| $len++; |
| } |
| } |
| return @ar; |
| } |
| |
| sub calc_jhash($) |
| { |
| my ($string) = @_; |
| my @ar; |
| my ($a, $b, $c, $i, $length, $len); |
| |
| @ar = string_to_bytes($string); |
| $length = @ar; |
| # add dummy elements to @ar to avoid if then else hell |
| push @ar, (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); |
| $a = 0x9e3779b9; |
| $b = 0x9e3779b9; |
| $c = 0; |
| $i = 0; |
| for ($len = $length + 12; $len >= 12; $len -= 12) { |
| if ($len < 24) { |
| # add length for last round |
| $c += $length; |
| } |
| $a += $ar[$i] + ($ar[$i+1]<<8) + ($ar[$i+2]<<16) + ($ar[$i+3]<<24); |
| $b += $ar[$i+4] + ($ar[$i+5]<<8) + ($ar[$i+6]<<16) + ($ar[$i+7]<<24); |
| if ($len >= 24) { |
| $c += $ar[$i+8] + ($ar[$i+9]<<8) + ($ar[$i+10]<<16) + ($ar[$i+11]<<24); |
| } else { |
| $c += ($ar[$i+8]<<8) + ($ar[$i+9]<<16) + ($ar[$i+10]<<24); |
| } |
| $a &= 0xffffffff; $b &= 0xffffffff; $c &= 0xffffffff; |
| $a -= $b; $a -= $c; $a ^= ($c >> 13); $a &= 0xffffffff; |
| $b -= $c; $b -= $a; $b ^= ($a << 8); $b &= 0xffffffff; |
| $c -= $a; $c -= $b; $c ^= ($b >> 13); $c &= 0xffffffff; |
| $a -= $b; $a -= $c; $a ^= ($c >> 12); $a &= 0xffffffff; |
| $b -= $c; $b -= $a; $b ^= ($a << 16); $b &= 0xffffffff; |
| $c -= $a; $c -= $b; $c ^= ($b >> 5); $c &= 0xffffffff; |
| $a -= $b; $a -= $c; $a ^= ($c >> 3); $a &= 0xffffffff; |
| $b -= $c; $b -= $a; $b ^= ($a << 10); $b &= 0xffffffff; |
| $c -= $a; $c -= $b; $c ^= ($b >> 15); $c &= 0xffffffff; |
| $i += 12; |
| } |
| return $c; |
| } |
| |
| sub add_kmsg_desc($$$$$$) |
| { |
| my ($component, $text, $sev, $argv, $desc, $user) = @_; |
| my ($hash, $tag); |
| |
| $text = remove_quotes($text); |
| $hash = substr(sprintf("%08x", calc_jhash($text)), 2, 6); |
| $tag = $component . "." . $hash; |
| |
| if ($kmsg_desc{$tag}) { |
| if ($text ne $kmsg_desc{$tag}->{'TEXT'}) { |
| warn "Duplicate message with tag $tag\n"; |
| warn " --- $kmsg_desc{$tag}->{'TEXT'}\n"; |
| warn " +++ $text\n"; |
| } else { |
| warn "Duplicate message description for \"$text\"\n"; |
| } |
| $errors++; |
| return; |
| } |
| $kmsg_desc{$tag}->{'TEXT'} = $text; |
| $kmsg_desc{$tag}->{'SEV'} = $sev; |
| $kmsg_desc{$tag}->{'ARGV'} = $argv; |
| $kmsg_desc{$tag}->{'DESC'} = $desc; |
| $kmsg_desc{$tag}->{'USER'} = $user; |
| } |
| |
| sub add_kmsg_print($$$$) |
| { |
| my ($component, $sev, $text, $argv) = @_; |
| my ($hash, $tag, $count, $parm); |
| |
| $text = remove_quotes($text); |
| $hash = substr(sprintf("%08x", calc_jhash($text)), 2, 6); |
| $tag = $component . "." . $hash; |
| |
| # Pretty print severity |
| $sev =~ s/"<0>"/Emerg/; |
| $sev =~ s/"<1>"/Alert/; |
| $sev =~ s/"<2>"/Critical/; |
| $sev =~ s/"<3>"/Error/; |
| $sev =~ s/"<4>"/Warning/; |
| $sev =~ s/"<5>"/Notice/; |
| $sev =~ s/"<6>"/Informational/; |
| $sev =~ s/"<7>"/Debug/; |
| $kmsg_print{$kmsg_count}->{'TAG'} = $tag; |
| $kmsg_print{$kmsg_count}->{'TEXT'} = $text; |
| $kmsg_print{$kmsg_count}->{'SEV'} = $sev; |
| $kmsg_print{$kmsg_count}->{'ARGV'} = $argv; |
| $kmsg_count += 1; |
| } |
| |
| sub process_source_file($$) |
| { |
| my ($component, $file) = @_; |
| my $state; |
| my ($text, $sev, $argv, $desc, $user); |
| |
| if (!open(FD, "$file")) { |
| return ""; |
| } |
| |
| $state = 0; |
| while (<FD>) { |
| chomp; |
| # kmsg message component: #define KMSG_COMPONENT "<component>" |
| if (/^#define\s+KMSG_COMPONENT\s+\"(.*)\"[^\"]*$/o) { |
| $component = $1; |
| } |
| if ($state == 0) { |
| # single line kmsg for undocumented messages, format: |
| # /*? Text: "<message>" */ |
| if (/^\s*\/\*\?\s*Text:\s*(\".*\")\s*\*\/\s*$/o) { |
| add_kmsg_desc($component, $1, "", "", "", ""); |
| } |
| # kmsg message start: '/*?' |
| if (/^\s*\/\*\?\s*$/o) { |
| $state = 1; |
| ($text, $sev, $argv, $desc, $user) = ( "", "", "", "", "" ); |
| } |
| } elsif ($state == 1) { |
| # kmsg message end: ' */' |
| if (/^\s*\*\/\s*/o) { |
| add_kmsg_desc($component, $text, $sev, $argv, $desc, $user); |
| $state = 0; |
| } |
| # kmsg message text: ' * Text: "<message>"' |
| elsif (/^\s*\*\s*Text:\s*(\".*\")\s*$/o) { |
| $text = $1; |
| } |
| # kmsg message severity: ' * Severity: <sev>' |
| elsif (/^\s*\*\s*Severity:\s*(\S*)\s*$/o) { |
| $sev = $1; |
| } |
| # kmsg message parameter: ' * Parameter: <argv>' |
| elsif (/^\s*\*\s*Parameter:\s*(\S*)\s*$/o) { |
| if (!defined($1)) { |
| $argv = ""; |
| } else { |
| $argv = $1; |
| } |
| $state = 2; |
| } |
| # kmsg message description start: ' * Description:' |
| elsif (/^\s*\*\s*Description:\s*(\S*)\s*$/o) { |
| if (!defined($1)) { |
| $desc = ""; |
| } else { |
| $desc = $1; |
| } |
| $state = 3; |
| } |
| # kmsg has unrecognizable lines |
| else { |
| warn "Warning(${file}:$.): Cannot understand $_"; |
| $warnings++; |
| $state = 0; |
| } |
| } elsif ($state == 2) { |
| # kmsg message end: ' */' |
| if (/^\s*\*\//o) { |
| warn "Warning(${file}:$.): Missing description, skipping message"; |
| $warnings++; |
| $state = 0; |
| } |
| # kmsg message description start: ' * Description:' |
| elsif (/^\s*\*\s*Description:\s*$/o) { |
| $desc = $1; |
| $state = 3; |
| } |
| # kmsg message parameter line: ' * <argv>' |
| elsif (/^\s*\*(.*)$/o) { |
| $argv .= "\n" . $1; |
| } else { |
| warn "Warning(${file}:$.): Cannot understand $_"; |
| $warnings++; |
| $state = 0; |
| } |
| } elsif ($state == 3) { |
| # kmsg message end: ' */' |
| if (/^\s*\*\/\s*/o) { |
| add_kmsg_desc($component, $text, $sev, $argv, $desc, $user); |
| $state = 0; |
| } |
| # kmsg message description start: ' * User action:' |
| elsif (/^\s*\*\s*User action:\s*$/o) { |
| $user = $1; |
| $state = 4; |
| } |
| # kmsg message description line: ' * <text>' |
| elsif (/^\s*\*\s*(.*)$/o) { |
| $desc .= "\n" . $1; |
| } else { |
| warn "Warning(${file}:$.): Cannot understand $_"; |
| $warnings++; |
| $state = 0; |
| } |
| } elsif ($state == 4) { |
| # kmsg message end: ' */' |
| if (/^\s*\*\/\s*/o) { |
| add_kmsg_desc($component, $text, $sev, $argv, $desc, $user); |
| $state = 0; |
| } |
| # kmsg message user action line: ' * <text>' |
| elsif (/^\s*\*\s*(.*)$/o) { |
| $user .= "\n" . $1; |
| } else { |
| warn "Warning(${file}:$.): Cannot understand $_"; |
| $warnings++; |
| $state = 0; |
| } |
| } |
| } |
| return $component; |
| } |
| |
| sub process_cpp_file($$$$) |
| { |
| my ($cc, $options, $file, $component) = @_; |
| |
| open(FD, "$cc $gcc_options|") or die ("Preprocessing failed."); |
| |
| while (<FD>) { |
| chomp; |
| if (/.*__KMSG_PRINT\(\s*(\S*)\s*_FMT_(.*)_ARGS_\s*(.*)?_END_\s*\)/o) { |
| if ($component ne "") { |
| add_kmsg_print($component, $1, $2, $3); |
| } else { |
| warn "Error(${file}:$.): kmsg without component\n"; |
| $errors++; |
| } |
| } elsif (/.*__KMSG_DEV\(\s*(\S*)\s*_FMT_(.*)_ARGS_\s*(.*)?_END_\s*\)/o) { |
| if ($component ne "") { |
| add_kmsg_print($component, $1, "\"%s: \"" . $2, $3); |
| } else { |
| warn "Error(${file}:$.): kmsg without component\n"; |
| $errors++; |
| } |
| } |
| } |
| } |
| |
| sub check_messages($) |
| { |
| my $component = "@_"; |
| my $failed = 0; |
| |
| for ($i = 0; $i < $kmsg_count; $i++) { |
| $tag = $kmsg_print{$i}->{'TAG'}; |
| if (!defined($kmsg_desc{$tag})) { |
| add_kmsg_desc($component, |
| "\"" . $kmsg_print{$i}->{'TEXT'} . "\"", |
| $kmsg_print{$i}->{'SEV'}, |
| $kmsg_print{$i}->{'ARGV'}, |
| "Please insert description here", |
| "What is the user supposed to do"); |
| $kmsg_desc{$tag}->{'CHECK'} = 1; |
| $failed = 1; |
| warn "$component: Missing description for: ". |
| $kmsg_print{$i}->{'TEXT'}."\n"; |
| $errors++; |
| next; |
| } |
| if ($kmsg_desc{$tag}->{'SEV'} ne "" && |
| $kmsg_desc{$tag}->{'SEV'} ne $kmsg_print{$i}->{'SEV'}) { |
| warn "Message severity mismatch for \"$kmsg_print{$i}->{'TEXT'}\"\n"; |
| warn " --- $kmsg_desc{$tag}->{'SEV'}\n"; |
| warn " +++ $kmsg_print{$i}->{'SEV'}\n"; |
| } |
| } |
| return $failed; |
| } |
| |
| sub print_templates() |
| { |
| print "Templates for missing messages:\n"; |
| foreach $tag ( sort { $kmsg_desc{$a} <=> $kmsg_desc{$b} } keys %kmsg_desc ) { |
| if (!defined($kmsg_desc{$tag}->{'CHECK'})) { |
| next; |
| } |
| print "/*?\n"; |
| print " * Text: \"$kmsg_desc{$tag}->{'TEXT'}\"\n"; |
| print " * Severity: $kmsg_desc{$tag}->{'SEV'}\n"; |
| $argv = $kmsg_desc{$tag}->{'ARGV'}; |
| if ($argv ne "") { |
| print " * Parameter:\n"; |
| @parms = split(/\s*,\s*/,$kmsg_desc{$tag}->{'ARGV'}); |
| $count = 0; |
| foreach $parm (@parms) { |
| $count += 1; |
| if (!($parm eq "")) { |
| print " * \@$count: $parm\n"; |
| } |
| } |
| } |
| print " * Description:\n"; |
| print " * $kmsg_desc{$tag}->{'DESC'}\n"; |
| print " * User action:\n"; |
| print " * $kmsg_desc{$tag}->{'USER'}\n"; |
| print " */\n\n"; |
| } |
| } |
| |
| sub write_man_pages() |
| { |
| my ($i, $file); |
| |
| for ($i = 0; $i < $kmsg_count; $i++) { |
| $tag = $kmsg_print{$i}->{'TAG'}; |
| if (!defined($kmsg_desc{$tag}) || |
| defined($kmsg_desc{$tag}->{'CHECK'}) || |
| $kmsg_desc{$tag}->{'DESC'} eq "") { |
| next; |
| } |
| $file = $objtree . "man/" . $tag . ".9"; |
| if (!open(WR, ">$file")) { |
| warn "Error: Cannot open file $file\n"; |
| $errors++; |
| return; |
| } |
| print WR ".TH \"$tag\" 9 \"Linux Messages\" LINUX\n"; |
| print WR ".SH Message\n"; |
| print WR $tag . ": " . $kmsg_desc{$tag}->{'TEXT'} . "\n"; |
| print WR ".SH Severity\n"; |
| print WR "$kmsg_desc{$tag}->{'SEV'}\n"; |
| $argv = $kmsg_desc{$tag}->{'ARGV'}; |
| if ($argv ne "") { |
| print WR ".SH Parameters\n"; |
| @parms = split(/\s*\n\s*/,$kmsg_desc{$tag}->{'ARGV'}); |
| foreach $parm (@parms) { |
| $parm =~ s/^\s*(.*)\s*$/$1/; |
| if (!($parm eq "")) { |
| print WR "$parm\n\n"; |
| } |
| } |
| } |
| print WR ".SH Description"; |
| print WR "$kmsg_desc{$tag}->{'DESC'}\n"; |
| $user = $kmsg_desc{$tag}->{'USER'}; |
| if ($user ne "") { |
| print WR ".SH User action"; |
| print WR "$user\n"; |
| } |
| } |
| } |
| |
| if (defined($ENV{'srctree'})) { |
| $srctree = "$ENV{'srctree'}" . "/"; |
| } else { |
| $srctree = getcwd; |
| } |
| |
| if (defined($ENV{'objtree'})) { |
| $objtree = "$ENV{'objtree'}" . "/"; |
| } else { |
| $objtree = getcwd; |
| } |
| |
| if (defined($ENV{'SRCARCH'})) { |
| $srcarch = "$ENV{'SRCARCH'}" . "/"; |
| } else { |
| print "kmsg-doc called without a valid \$SRCARCH\n"; |
| exit 1; |
| } |
| |
| $option = shift; |
| |
| $cc = shift; |
| $gcc_options = "-E -D __KMSG_CHECKER "; |
| foreach $tmp (@ARGV) { |
| $tmp =~ s/\(/\\\(/; |
| $tmp =~ s/\)/\\\)/; |
| $gcc_options .= " $tmp"; |
| $filename = $tmp; |
| } |
| |
| $component = process_source_file("", $filename); |
| if ($component ne "") { |
| process_source_file($component, $srctree . "Documentation/kmsg/" . |
| $srcarch . $component); |
| process_source_file($component, $srctree . "Documentation/kmsg/" . |
| $component); |
| } |
| |
| process_cpp_file($cc, $gcc_options, $filename, $component); |
| if ($option eq "check") { |
| if (check_messages($component)) { |
| print_templates(); |
| } |
| } elsif ($option eq "print") { |
| write_man_pages(); |
| } |
| |
| exit($errors); |