xref: /illumos-gate/usr/src/cmd/fm/scripts/dictck.pl (revision e71ca95c)
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