1#!/usr/bin/perl -w 2# 3# CDDL HEADER START 4# 5# The contents of this file are subject to the terms of the 6# Common Development and Distribution License (the "License"). 7# You may not use this file except in compliance with the License. 8# 9# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 10# or http://www.opensolaris.org/os/licensing. 11# See the License for the specific language governing permissions 12# and limitations under the License. 13# 14# When distributing Covered Code, include this CDDL HEADER in each 15# file and include the License file at usr/src/OPENSOLARIS.LICENSE. 16# If applicable, add the following below this CDDL HEADER, with the 17# fields enclosed by brackets "[]" replaced with your own identifying 18# information: Portions Copyright [yyyy] [name of copyright owner] 19# 20# CDDL HEADER END 21# 22# ident "%Z%%M% %I% %E% SMI" 23# 24# Copyright 2008 Sun Microsystems, Inc. All rights reserved. 25# Use is subject to license terms. 26# 27# 28# dictck -- Sanity check a .dict file and optionally the corresponding .po file 29# 30# example: dickck FMD.dict FMD.po 31# 32# usage: dickck [-vp] [ -b buildcode ] dictfile [ pofile ] 33# 34# -b specify location of "buildcode" command 35# 36# -p print a .po file template to stdout, based on dictfile given 37# 38# -v verbose, show how code is assembled 39# 40# Note: this program requires the "buildcode" program in your search path. 41# 42 43use strict; 44 45use Getopt::Std; 46 47use vars qw($opt_b $opt_p $opt_v); 48 49my $Myname = $0; # save our name for error messages 50$Myname =~ s,.*/,,; 51 52$SIG{HUP} = $SIG{INT} = $SIG{TERM} = $SIG{__DIE__} = sub { 53 # although fatal, we prepend "WARNING:" to make sure the 54 # commonly-used "nightly" script flags this as lint on the .dict file 55 die "$Myname: WARNING: @_"; 56}; 57 58# 59# usage -- print a usage message and exit 60# 61sub usage { 62 my $msg = shift; 63 64 warn "$Myname: $msg\n" if defined($msg); 65 warn "usage: $Myname [-pv] [ -b buildcode ] dictfile [ pofile ]\n"; 66 exit 1; 67} 68 69my %keys2val; 70my %val2keys; 71my %code2val; 72 73my $buildcode = 'buildcode'; 74 75# 76# the "main" for this script... 77# 78getopts('b:pv') or usage; 79 80my $dictfile = shift; 81my $pofile = shift; 82usage unless defined($dictfile); 83usage if @ARGV; 84$buildcode = $opt_b if defined($opt_b); 85dodict($dictfile); 86dopo($pofile) if defined($pofile); 87exit 0; 88 89# 90# dodict -- load up a .dict file, sanity checking it as we go 91# 92sub dodict { 93 my $name = shift; 94 my $dname; 95 my $line = 0; 96 my $lhs; 97 my $rhs; 98 my %props; 99 my $maxkey = 1; 100 101 if ($name =~ m,([^/]+)\.dict$,) { 102 $dname = $1; 103 } else { 104 die "dictname \"$name\" not something.dict as expected\n"; 105 } 106 107 open(F, $name) or die "$name: $!\n"; 108 print "parsing \"$name\"\n" if $opt_v; 109 while (<F>) { 110 $line++; 111 next if /^\s*#/; 112 chomp; 113 next if /^\s*$/; 114 die "$name:$line: first non-comment line must be FMDICT line\n" 115 unless /^FMDICT:/; 116 print "FMDICT keyword found on line $line\n" if $opt_v; 117 s/FMDICT:\s*//; 118 my $s = $_; 119 while ($s =~ /^\s*([^=\s]+)(.*)$/) { 120 $lhs = $1; 121 $rhs = ""; 122 $s = $+; 123 if ($s =~ /^\s*=\s*(.*)$/) { 124 $s = $+; 125 die "$name:$line: property \"$lhs\" incomplete\n" 126 unless $s ne ""; 127 } 128 if ($s =~ /^"((?:[^"]|\\")*)"(.*)$/) { 129 $s = $+; 130 $rhs = $1; 131 } else { 132 $s =~ /^([^\s]*)(.*)$/; 133 $s = $+; 134 $rhs = $1; 135 } 136 $rhs =~ s/\\(.)/dobs($1)/ge; 137 $props{$lhs} = $rhs; 138 print "property \"$lhs\" value \"$rhs\"\n" if $opt_v; 139 } 140 last; 141 } 142 # check for required headers 143 die "$name: no version property in header\n" 144 unless defined($props{'version'}); 145 die "$name: no name property in header\n" 146 unless defined($props{'name'}); 147 die "$name: no maxkey property in header\n" 148 unless defined($props{'maxkey'}); 149 150 # check version 151 die "$name:$line: unexpected version: \"$props{'version'}\"\n" 152 unless $props{'version'} eq "1"; 153 154 # check name 155 die "$name:$line: name \"$props{'name'}\" doesn't match \"$dname\" from filename\n" 156 unless $props{'name'} eq $dname; 157 158 # check format of maxkey (value checked later) 159 die "$name:$line: maxkey property must be a number\n" 160 unless $props{'maxkey'} =~ /^\d+$/; 161 162 # check for old bits property 163 die "$name: obsolete \"bits\" property found in header\n" 164 if defined($props{'bits'}); 165 166 # parse entries 167 while (<F>) { 168 $line++; 169 chomp; 170 s/#.*//; 171 next if /^\s*$/; 172 die "$name:$line: malformed entry\n" 173 unless /^([^=]+)=(\d+)$/; 174 $lhs = $1; 175 $rhs = $2; 176 177 # make sure keys are sorted 178 my $elhs = join(' ', sort split(/\s/, $lhs)); 179 die "$name:$line: keys not in expected format of:\n" . 180 " \"$elhs\"\n" 181 unless $elhs eq $lhs; 182 183 # check for duplicate or unexpected keys 184 my %keys; 185 foreach my $e (split(/\s/, $lhs)) { 186 die "$name:$line: unknown event type \"$e\"\n" 187 unless $e =~ 188 /^(fault|defect|upset|ereport|list)\..*[^.]$/; 189 die "$name:$line: key repeated: \"$e\"\n" 190 if defined($keys{$e}); 191 $keys{$e} = 1; 192 } 193 $maxkey = keys(%keys) if $maxkey < keys(%keys); 194 195 die "$name:$line: duplicate entry for keys\n" 196 if defined($keys2val{$lhs}); 197 die "$name:$line: duplicate entry for value $rhs\n" 198 if defined($val2keys{$rhs}); 199 $keys2val{$lhs} = $rhs; 200 $val2keys{$rhs} = $lhs; 201 202 open(B, "$buildcode $dname $rhs|") or 203 die "can't run buildcode: $!\n"; 204 my $code = <B>; 205 chomp $code; 206 close(B); 207 print "code: $code keys: $lhs\n" if $opt_v; 208 $code2val{$code} = $rhs; 209 210 if ($opt_p) { 211 print <<EOF; 212# 213# code: $code 214# keys: $lhs 215# 216msgid "$code.type" 217msgstr "XXX" 218msgid "$code.severity" 219msgstr "XXX" 220msgid "$code.description" 221msgstr "XXX" 222msgid "$code.response" 223msgstr "XXX" 224msgid "$code.impact" 225msgstr "XXX" 226msgid "$code.action" 227msgstr "XXX" 228EOF 229 } 230 } 231 232 print "computed maxkey: $maxkey\n" if $opt_v; 233 234 # check maxkey 235 die "$name: maxkey too low, should be $maxkey\n" 236 if $props{'maxkey'} < $maxkey; 237 238 close(F); 239} 240 241# 242# dobs -- handle backslashed sequences 243# 244sub dobs { 245 my $s = shift; 246 247 return "\n" if $s eq 'n'; 248 return "\r" if $s eq 'r'; 249 return "\t" if $s eq 't'; 250 return $s; 251} 252 253# 254# dopo -- sanity check a po file 255# 256sub dopo { 257 my $name = shift; 258 my $line = 0; 259 my $id; 260 my $code; 261 my $suffix; 262 my %ids; 263 264 open(F, $name) or die "$name: $!\n"; 265 print "parsing \"$name\"\n" if $opt_v; 266 while (<F>) { 267 $line++; 268 next if /^\s*#/; 269 chomp; 270 next if /^\s*$/; 271 next unless /^msgid\s*"([^"]+)"$/; 272 $id = $1; 273 next unless $id =~ 274 /^(.*)\.(type|severity|description|response|impact|action)$/; 275 $code = $1; 276 $suffix = $2; 277 die "$name:$line: no dict entry for code \"$code\"\n" 278 unless defined($code2val{$code}); 279 $ids{$id} = $line; 280 } 281 close(F); 282 283 # above checks while reading in file ensured that node code was 284 # mentioned in .po file that didn't exist in .dict file. now 285 # check the other direction: make sure the full set of entries 286 # exist for each code in the .dict file 287 foreach $code (sort keys %code2val) { 288 die "$name: missing entry for \"$code.type\"\n" 289 unless defined($ids{"$code.type"}); 290 die "$name: missing entry for \"$code.severity\"\n" 291 unless defined($ids{"$code.severity"}); 292 die "$name: missing entry for \"$code.description\"\n" 293 unless defined($ids{"$code.description"}); 294 die "$name: missing entry for \"$code.response\"\n" 295 unless defined($ids{"$code.response"}); 296 die "$name: missing entry for \"$code.impact\"\n" 297 unless defined($ids{"$code.impact"}); 298 die "$name: missing entry for \"$code.action\"\n" 299 unless defined($ids{"$code.action"}); 300 } 301} 302