blob: ce5e1a153f40538a68c1008d2ae5dc554b313483 [file] [log] [blame]
#!/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);