#!/usr/bin/perl # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # mib-t [-Options]... [file]... # # This program chews up symbolic MIBs and produces a C table. The syntax of a # MIB file is rather poorly characterized, and everyone seems to have a # different understanding as to just what should be there. This isn't # surprising, as the ASN.1 language in which they are written was written by # an ISO committee, and those who have the spec generally come to their own # sort of (mis)understanding of what it says. This program handles what I've # seen in actual files, and will likely change as I learn more about this # arcane subject. # # You may name one or more MIB source files on the command line, or feed the # source to this program via standard input. There are a number of output # files as indicated by the flags. They are: $Dfile = "mibtbl"; # Simple "oid var" output file. $Efile = "mib.err"; # Errors and warnings. $Ffile = "mibfld.c"; # JC's SNMP "field" table. $Gfile = "mibfld.h"; # Defs for mibfld.h $Hfile = "mibtbl.h"; # Symbolic codes for variables. $Lfile = "mib.log"; # Debug output. $Mfile = "snmpd.mib"; # Simplified, regularized MIB. $Ofile = "miboid.t"; # MIB OID table. $Tfile = "mibtbl.t"; # MIB table (see mibtbl.c). $Vfile = "mibvar.t"; # MIB VAR table. $Xfile = "mibext.h"; # MIB extern declarations. # -Cmibtbl.h # This writes a list of the "magic" code symbols to "mibtbl.h", in the form # of C #define statements. You may want to edit this file somewhat. # # -Emib.err # This writes the error messages to "mib.err", which is the default. # # -Hmibext.h # This says where to write the extern declarations; default is "mibext.h". # # -Lmib.log # This writes the error messages to "mib.log", which is the default. # # -Msnmpd.mib # This file receives a copy of the entire MIB, after some editing. This is # the file that the various test programs (Get, Next, Walk) read to learn # symbolic names for things. # # -Omibtbl.h # This will write a simple table of numeric and symbolic object identifiers # to the file "mibtbl", which is the default name. # # -Tmibtbl.t # This will write C initializers for the VAR variable[] array. The # mibview.c module #includes "mibtbl.t"; if this program has done its job # right, mibview.c should compile without problems. But you may need to do # a small amount of hand tweaking. # # Author: John Chambers [Dec 1992] # Basic algorithm written for a contract to Digital Equipment Corporation. # Modified extensively on a contract to GTE Government Systems Division. # Modified slightly on a contract to GTE Labs. # # All the above showed little interest in discussing whether I could post # this on various newsgroups, so there is likely no problem with anyone else # using it for their own purposes. You'll almost certainly need to make some # changes for your own needs. If you add any interesting features, let me # know, so I can incorporate them. # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - push(@INC,'sh','../sh'); require "cmagic.pm"; require "cname.pm"; require "oidorder.pm"; require "timewheel.pm"; # Override some perl defaults: $, = ' '; $" = '.'; # Here are some patterns that we will use: $mibvar = '[-_A-Za-z0-9]+'; $psym = '\b([A-Za-z0-9][-+/_A-Za-z0-9]*)\b'; # ASN.1 symbol. $pnum = '([0-9]+)'; # Numeric currently means non-negative integer. $range = '^\.\.+$'; # Two or more dots indicate a range. $DF = '::='; $LB = '{'; $RB = '}'; $LP = '('; $RP = ')'; # For documentation purposes, here's a list of our global tables: %access = (); # Access rights for variables; usually 'RO' or 'NA'. %children = (); # Space-separated list of children of a MIB-tree node. %defval = (); # Default values of variables; not often used. %descr = (); # English description of a variable. %index = (); # Gives list of indices for an Entry group. %indices = (); # Gives number of indices for an Entry group. %min = (); # The min value (for INTEGERs) or length (for STRINGs). %max = (); # The max value (for INTEGERs) or length (for STRINGs). %nlen = (); # Length of objectid, including any indices. %olen = (); # Length of objectid, minus any indices. %onam = (); # Translates variable names to objectids. %cvar = (); # Translates objectids to C's symbolic names. %mvar = (); # Translates objectids to MIB symbolic names. %pad = (); # For strings with min length, the pad char. %parent = (); # Name of the next node upward in the MIB tree. %symnum = (); # Translates symbolic INTEGER values to numeric values. @t = (); # Token list; the next N tokens to be processed. %type = (); # The ASN type of a variable. &inittbl(); # Table initialization. &cmdline(); # Process the command line. &file1(); # Initial boilerplating. &readmib(); # Read the MIB file(s). &prntmib(); # Produce the output file(s). &file2(); # Final boilerplating. exit 0; # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # # This registers the read/write permissions for the named variable. We only # # recognize a few codes; the ones used by SNMP. This might be expanded for # # the local MIB. # # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # sub access { local($name,$val) = @_; $val =~ s/\s+//g; if ($val eq 'read-only') { $val = 'RO' } elsif ($val eq 'read-write') { $val = 'RW' } elsif ($val eq 'write-only') { $val = 'WO' } elsif ($val eq 'not-accessible') { $val = 'NA' } $access{$name} = $val; } # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # # Add a new oid to the tables; return its value. As a side-effect, we write # # the oid-table line, to either STDOUT or D, whichever is selected. As a bit # # of paranoia, we make sure that there's no initial dot in the oid. # # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # sub addoid { local($mname,@l) = @_; local($cname,$l,$on,$ov); ($l = "@l") =~ s/^\.*//; # Dotted-decimal form of name. $cname = &cname($mname); # Convert to C identifier. print L "$mname canonicalized to $cname\n" if ($mname ne $cname); $ov = $mvar{$l} || ''; # Note old value, if any. if ($ov && ($ov ne $cname)) { # Have we already seen this name? print E "$ARGV\[$.\]: Change $l from $mvar{$l} to $cname\n"; } $on = $onam{$cname} || ''; # Note old name, if any. if ($on && ($on ne $l)) { # Has the definition changed? print E "Redefine $cname from '$on' to '$l'\n"; } $onam{$cname} = $l; # Name => oid in dotted-decimal format. $onam{$mname} = $l if ($mname ne $cname); $cvar{$l} = $cname; # Oid in dotted-decimal format => C name. $mvar{$l} = $mname; # Oid in dotted-decimal format => MIB name. print D "$l\t$cname\n"; # Write OID and name. return $l; } # - - - - - - - - - - - - - - - - - - - - - - - - - - # # Return the arg string with all letters capitalized. # # - - - - - - - - - - - - - - - - - - - - - - - - - - # sub caps { local($x) = @_; $x =~ tr/a-z/A-Z/; $x; } # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # # Run thru the command-line args looking for output-file flags. # # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # sub cmdline { for (@ARGV) { if (/^[-+]/) { if (/^[-+][Dd](.*)/) { $Dfile = $1; print "OID file: $Dfile\n"; } elsif (/^[-+][Ee](.*)/) { $Efile = $1; print "Error file: $Efile\n"; } elsif (/^[-+][Hh](.*)/) { $Hfile = $1; print "Headerfile: $Hfile\n"; } elsif (/^[-+][Ll](.*)/) { $Lfile = $1; print "Log file: $Lfile\n"; } elsif (/^[-+][Mm](.*)/) { $Mfile = $1; print "MIB file: $Mfile\n"; } elsif (/^[-+][Oo](.*)/) { $Ofile = $1; print "Oid file: $Ofile\n"; } elsif (/^[-+][Tt](.*)/) { $Tfile = $1; print "Table file: $Tfile\n"; } elsif (/^[-+][Vv](.*)/) { $Vfile = $1; print "Var file: $Vfile\n"; } elsif (/^[-+][Xx](.*)/) { $Xfile = $1; print "Hdr file: $Xfile\n"; } else { print STDERR "Unknown option \"$_\" ignored.\n"; } } else { # print "\b-\tInput file: $_\n"; @FILE = (@FILE , $_); } } @ARGV = @FILE; # Leftovers are input file names. # Open any of the above files. if ($Dfile) { open(D,">$Dfile") || die "Can't write to $Dfile [$!]\n"; select D; $|=1; } if ($Efile) { open(E,">$Efile") || die "Can't write to $Efile [$!]\n"; select E; $|=1; } if ($Hfile) { open(H,">$Hfile") || die "Can't write to $Hfile [$!]\n"; select H; $|=1; } if ($Lfile) { open(L,">$Lfile") || die "Can't write to $Lfile [$!]\n"; select L; $|=1; } if ($Mfile) { open(M,">snmpd.mib") || die "### Can't write to $Mfile [$!]\n"; select M; $|=1; } if ($Ofile) { open(O,">$Ofile") || die "Can't write to $Ofile [$!]\n"; select O; $|=1; } if ($Tfile) { open(T,">$Tfile") || die "Can't write to $Tfile [$!]\n"; select T; $|=1; } if ($Vfile) { open(V,">$Vfile") || die "Can't write to $Vfile [$!]\n"; select X; $|=1; } if ($Xfile) { open(X,">$Xfile") || die "Can't write to $Xfile [$!]\n"; select X; $|=1; } select STDOUT; # To make debug package work. } # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # # Given a symbol, canonicalize it. This can be used to do assorted mapping # # and renaming. # # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # sub Csym { local($sym) = @_; $sym =~ s/\s+//g; $sym = $sym{$sym} || $sym; } # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # # This registers a text description for the named variable. At # # present, we don't use such descriptions, but if we need them # # sometime in the future, we know where to find them. # # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # sub descr { local($name,$text) = @_; $descr{$name} = $text; } # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # # Here is where we generate whatever initial boilerplating is needed for the # # various output files. The biggest thing we do here is to read mibfld.c, to # # extract the names of Fld structs so we can add them to the MIB table. # # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # sub file1 { local($f,$l); if ($Xfile) { print X "#include \"var.h\"\n\n"; } if (open(G,">$Gfile")) { print G "#ifndef mibfld_h\n"; print G "#define mibfld_h\n"; print G "\n"; print G "#ifndef fld_h\n"; print G "#include \"fld.h\"\n"; print G "#endif\n"; print G "\n"; } else { print E "Can't write $Gfile [$!]\n"; $Gfile = ''; # Suppress later output. } if (open(F,"<$Ffile")) { for $l () { next if ($l =~ /^\*/); next if ($l =~ /^\/\*/); # print STDOUT "Line: $l"; if ($l =~ /^#include\s+"G($mibvar)\.f"/) { # #include "GfooBar.f" $f = &cname($1); $get{$f} = "&G$f"; print G "extern Fld G$f;\n" if $Gfile; print L "Fld $f is $get{$f}\n"; } elsif ($l =~ /^#include\s+"S($mibvar)\.f"/) { # #include "SfooBar.f" $f = &cname($1); $set{$f} = "&S$f"; print G "extern Fld S$f;\n" if $Gfile; print L "Set $f is $set{$f}\n"; } elsif ($l =~ /^#define\s*G(\w+)\s*(G$mibvar)/) { # #define GfooBar GbarFoo $f = &cname($1); $get{$f} = "&$2"; print G "#define G$f $2\n" if $Gfile; print L "Def $f is $get{$f}\n"; } elsif ($l =~ /^#define\s*S(\w+)\s*(S$mibvar)/) { # #define SfooBar SbarFoo $f = &cname($1); $set{$f} = "&$2"; print G "#define S$f $2\n" if $Gfile; print L "Def $f is $set{$f}\n"; # } else { # print L "Ignore $Ffile: $l"; # print E "Ignore $Ffile: $l"; } } close(F); } else { print L "### Can't read $Ffile [$!]\n"; print E "### Can't read $Ffile [$!]\n"; print G "/* No fld-type variables in this MIB */\n"; } if ($Gfile) { print G "\n"; print G "#endif\n"; close G; } } # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # # Here is where we generate whatever final boilerplating is needed for the # # various output files. # # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # sub file2 { print M "END\n"; } # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # # Given a string, expand @t until the string appears. The return value is the # # index where it is found. The return value is -1 if we hit EOF first. # # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # sub findstr { local($s) = @_; local($n); while (($n = &havestr($s)) < 0) { if (!&tok(@t + 1)) { print "EOF\n"; return -1; } } $fs = $n; } # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # # Get the oid value for a symbolic name, or '*' if it isn't defined. As a # # kludge, we do a second pass, translating everything to lower case, so we # # can recognize miscapitalized names. This is time-consuming, but we've had a # # lot of problems with this, so it's worth the cpu time. # # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # sub getoid { local($n) = @_; local($k,$kl,$nl); return $onam{$n} if $onam{$n}; ($nl = $n) =~ tr/A-Z/a-z/; for $k (keys %onam) { ($kl = $k) =~ tr/A-Z/a-z/; if ($nl eq $kl) { print E "$ARGV\[$.\]: Rewrite $n as $k\n"; return $onam{$k} } } print E "$ARGV\[$.\]: Can't decode '$n'\n"; return '*'; } # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # # Look thru @t for a string that exactly matches the arg. The return value is # # the index where it is found. The return value is 0 if we hit EOF first. # # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # sub havestr { local($s) = @_; local($n); for $n (0 .. $#t) { if ($t[$n] eq $s) { return $n; } } -1; } # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # # This routine handles the IMPORTS clause. So far, we don't do much with the # # information, because there's no known way to find the MIB definitions in # # the FROM clauses. # # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # sub imports { local($sym,$mib); if (($i = &findstr($RB)) < 0) { print E "$ARGV\[$.\]: Can't find '$RB'\n"; splice(@t,0,1); # To ensure progress. return; } while (&tok(3)) { if ($t[0] eq 'FROM') { $mib = $t[1]; splice(@t,0,2); } elsif ($t[1] eq 'FROM') { $sym = $t[0]; $mib = $t[2]; splice(@t,0,3); } elsif ($t[1] eq ',') { $sym = $t[0]; splice(@t,0,2); } elsif ($t[0] eq ';') { splice(@t,0,1); return; } elsif ($t[0] eq 'IMPORTS') { splice(@t,0,1); } else { print E "$ARGV\[$.\]: Can'd handle \"$t[0]\"\n"; splice(@t,0,1); } } } # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # # This registers an index for the named variable. This routine should find @t # # filled with "name , ... }", where the names are either types or variables # # whose types can be determined. All that we actually do is count them, so # # that we know how many object-instance fields to expect for subsequent # # variables. # # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # sub index { local($name) = @_; local(@list,@new); # local($indx); if (($indx = &findstr($RB)) < 0) { print E "$ARGV\[$.\]: Can't find matching '$RB'\n"; } else { @new = splice(@t,0,$indx); for (@new) { @list = (@list , $_) if ($_ ne ','); } $index{$name} = "@list"; # Names/types of indices. $indices{$name} = @list; # Number of indices. splice(@t,0,1); # Get rid of the right brace. } } # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # # Set up some useful tables. These are mostly string substitutions, though # # any sort of tables may be initialized here. # # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # sub inittbl { # %code = ( # Names in mibtbl's "magic" field. # # No longer anything changed here. # ); %macro = ( # "AtmAddress", "OCTET STRING (SIZE (0 .. 32))", ); %onam = ( # OID table with name as key. 'iso', '1', ); %cvar = ( # OID table with value as key. '1', 'iso', ); %mvar = ( # OID table with value as key. '1', 'iso', ); %sym = ( # Symbolic name simplifications. "Digital_Equipment_Corporation", "DEC", "GTE_Government_Systems", "GTE", "GTE_Laboratories", "GTELabs", "International_Business_Machines", "IBM", "Microsoft", "MS", ); %typ = ( # Short symbols used in VAR table. 'INTEGER', 'INT', 'NETWORKADDRESS', 'ADDR', 'TIMETICKS', 'TCK', 'IPADDRESS', 'IPA', 'STRING', 'STR', 'SEQUENCE', 'SEQ', 'Counter', 'CTR', 'COUNTER', 'CTR', 'COUNTER64', 'C64', 'GAUGE', 'GGE', 'GROUP', 'GRP', 'FaultString', 'BIN', ); } # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # # Canonicalize a line of input. This is used to iron out some problems with # # the way that the MIB is typed by some people. The main thing we look for is # # the use of unquoted names for integer values. We might add other sorts of # # canonicalization here. One warning: This routine must not reduce any line # # to a null string, since the caller will treat a null line as EOF. # # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # sub inline { local ($line); $line = <>; &timewheel() if ($. % 10 == 0); $line =~ s/\s+$/\n/; # Trim away white space. if ($line =~ /^\t+\w.*\s+\(\d+\),*\s*$/) { # Named-value kludge. $line =~ s/^\s*"*/\t\t\t"/; # Insert initial quote. $line =~ s/"*\s*(\(\d+\),*)\s*/"\t$1\n/; # Insert trailing quote. } elsif ($line =~ /^\t+\w.*\s+\(\d+\),*\s*--(.*)$/) { # Named-value with comment. $line =~ s/^\s*"*/\t\t\t"/; # Insert initial quote. $line =~ s/"*\s*(\(\d+\),*)\s*/"\t$1\n/; # Insert trailing quote. } $line; # Make sure the line is returned. } # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # # This returns a code for the object instance for the name. The code is '0', # # 'G', or a string of 'F's, one per object-instance value. This is rather # # kludgy, and uses the fact that the INDEX was attached to the parent node, # # not to this one. Also, we look for IpAddress" items, and convert them to # # four 'F's. # # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # sub inst { local($name) = @_; local($inst,$i,@i,$n,@o,$p,$t); if ($p = $parent{$name}) { @o = split(/\./,$onam{$name}); # Oid list. $olen{$name} = @o; if ($i = $index{$p}) { @i = split(/\./,$i); # List of instance variables. $n = $nlen{$name} = $olen{$name} + @i; # Length of Oid + instance. for (@i) { $t = $type{$_}; if ($type{$_} eq "IpAddress") { $inst .= '.F.F.F.F'; $nlen{$name} += 3; } else { $inst .= '.F'; } } $inst =~ s/^\.//; } elsif ($children{$name}) { $inst = 'G'; } else { $inst = '0'; $n = $nlen{$name} = $olen{$name} + 1; # Length of Oid + instance. } } else { $inst = 'G'; # Root is assumed to be a group. $olen{$name} = 1; } $inst; } # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # # This function takes a name and a definition in the @t vector. The # # definition is scanned, and values are added to the %onam and %mvar tables # # if possible. There are several formats. # # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # sub oid { local($name) = @_; local($i,$o,$xx); if (&findstr($RB) < 0) { print E "$ARGV\[$.\]: Incomplete object definition.\n"; return 0; } while ($t[0] ne $RB) { if (@t > 4 && $t[1] eq $LP && $t[3] eq $RP) { # name(num) $o = &addoid($t[0],$o,$t[2]); &parent($xx,$t[0]) if $xx; $xx = $t[0]; # Remember last list item. splice(@t,0,4); next; } if (@t > 2 && $t[1] > 0) { # name num $o = &addoid($name,&getoid($t[0]),$t[1]); &parent($t[0],$name); $xx = $t[0]; splice(@t,0,2); next; } if (@t > 1) { # name $o .= &getoid($t[0]); &parent($xx,$t[0]) if $xx; $xx = $t[0]; splice(@t,0,1); next; } print E "$ARGV\[$.\]: Can't handle OID '@t'\n"; } splice(@t,0,1); # Delete the right brace. 0; } ## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # ## Here's a routine to determine which of two oid values comes first. This is # ## a bit tricky. We have to split the oid apart on dots, and compare the # ## numbers at corresponding positions until we find a pair that differs. # ## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # #sub oidorder { # local($i,@a,@b); # @a = split(/\./,$a); # @b = split(/\./,$b); ## for ($i=0; $a[$i] && $b[$i]; $i++) { ## return -1 if ($a[$i] < $b[$i]); ## return 1 if ($a[$i] > $b[$i]); ## } ## return 1 if $a[$i]; ## return -1 if $b[$i]; # while (@a && @b) { # From comp.lang.perl # return $i if ($i = (shift(@a) <=> shift(@b))); # } # return -1 if @b; # b is longer. # return 1 if @a; # a is longer. # 0; # They are identical. #} # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # # &parent(x,y,...) enters x as the parent of the rest of the args. The parent # # is also flagged as a group item, overwriting any type it may have had. # # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # sub parent { local($p) = shift(@_); foreach (@_) { $parent{$_} = $p; $children{$p} .= $_ . ' '; } $type{$p} = 'GRP'; } # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # # Dump the MIB table in object-id order. Note that we write to both Hfile and # # Tfile here, if either is open. We insist on writing the table, to STDOUT if # # Tfile isn't open. If Hfile isn't open, we don't bother with writing its # # data (the names of the magic-code symbols). # # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # sub prntmib { local($a,$d,$c,$f,$m,$n,$O,$t,$V); local($cnt,$code,$oid,$type,); local($ss,$mm,$hh,$DD,$MM,$YY) = gmtime; ++$MM; printf V "static char mib_sccs_id[] = \"@(#)SNMP%s %02d/%02d/%02d %02d:%02d UTC\";\n" ,$files,$YY,$MM,$DD,$hh,$mm; print "\b*\tProduce tables ...\n"; print V "\n"; foreach $v (sort(oidorder keys(%mvar))) { &timewheel() if ($cnt++ % 10 == 0); $m = $mvar{$v}; # MIB's name (with hyphens). $n = $cvar{$v}; # C's name (with underscores). print L "MIB name \"$m\" is C name \"$n\"\n"; $i = &inst($m); # C code for instance(s). ($oid = "$v.$i") =~ s/\./,/g; ($code, $c) = &cmagic($m); if ($Hfile && $code) { # Do we need a "magic" code? print H "#define $code\t$c\t/* $m */\n" } $a = $access{$m} || 'XX'; $d = $descr{$m}; $P = $pad{$m} || '0'; $f = ($i eq 'G') ? '0' : 'v_' . $parent{$m}; $t = ($i eq 'G') ? 'GRP' : &caps($type{$m}) || 'OTHER'; $type = &type($t); $f =~ s/Entry$//; $externs{$f} ++; $olen = $olen{$m} || 0; $nlen = $nlen{$m} || 0; $maxnlen = $nlen if $nlen > $maxnlen; # Note longest OID needed. $min = $min{$m} || 0; $max = $max{$m} || 0; $O = &cname("O$n"); # Oid name. $V = &cname("V$n"); # Vid name. print O "Oid $O\[\]= {"; print O "$oid"; # Numeric oid value. print O ",0};\n"; # Add a null terminator to all entries. # print T "\n" if $i eq 'G'; print X "extern Var $V;\n" if (defined $Xfile); print V "Var $V = {$O"; print V ", $olen"; # Olen (Length of Oid portion) print V ", $nlen"; # Nlen (Length including instances */ print V ", $type"; # Type (short) print V ", $min"; # Min val/size. print V ", $max"; # Max val/size. print V ", \"$m\""; # Symbolic name print V "};\n"; print T "{&$V"; # Oid pointer print T ", $code"; # Magic code number (byte) print T ", $min"; # Min val/size. print T ", $max"; # Max val/size. print T ", $P"; # Pad (byte) print T ", $a"; # Access (RO, RW, XX) print T ", $f, "; # Function print T ($get{$n} || '0'); print T ", "; print T ($set{$n} || '0'); print T "},"; # print T "\n\t/* $d */" if $d; # Description. print T "\n"; } print "\b*\tTables done.\n"; print H "#define MAXOIDLEN $maxnlen\n" if $maxnlen; print "\b*\tProduce externs ...\n"; if (defined $Xfile) { for $f (sort keys %externs) { &timewheel() if ($cnt++ % 10 == 0); print X "extern BP $f();\n" if ($f =~ /^[A-Za-z]\w*$/); } } print "\b*\tExterns done.\n"; } # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # # Read the MIB and process each line. Along the way, we open each file named # # in @ARGV, which by now should contain only names of files that contain # # ASN.1 source for MIBs. # # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # sub readmib { local($m); print "\tRead input files ...\n"; while () { &tok(1) || last; # A few single-token constructs exist. if ($t[0] eq ';') { splice(@t,0,1); # Ignore remnant semicolons. next; } if ($t[0] eq "END") { printf L "End of \"$mib\"\n"; printf E "End of \"$mib\"\n"; splice(@t,0,1); # END tokens are not really interesting. next; } &tok(2) || last; # There are lots of two-token constructs. if ($t[1] eq 'OBJECT-TYPE') { # New variable symbol. if ($t[0] eq 'IMPORTS') { $imports = &findstr(';'); @imports = splice(@t,0,$imports); shift @t; # Eat up the semicolon. next; } if (($name = $t[0]) !~ $psym) { print E "$ARGV\[$.\]: Bad symbol '$t[0]' before $t[1]\n"; $name = 'UNKNOWN'; # To allow further processing. } splice(@t,0,2); next; } if ($t[0] eq 'MATCHES' && $t[1] eq 'FOR') { splice(@t,0,2); $matches = &findstr(';'); @matches = splice(@t,0,$matches); shift @t; # Eat up the semicolon. next; } if ($t[0] eq 'ACCESS' || $t[0] eq 'MAX-ACCESS') { &access($name,$t[1]); splice(@t,0,2); next; } if ($t[0] eq 'DESCRIPTION') { $descr{$name} = $t[1]; # Descriptions aren't used. splice(@t,0,2); next; } if ($t[0] eq 'REFERENCE') { $ref{$name} = $t[1]; # References aren't used. splice(@t,0,2); next; } if ($t[0] eq 'INDEX' && $t[1] eq $LB) { splice(@t,0,2); &index($name); next; } if ($t[0] eq 'STATUS') { # It's always "mandatory". splice(@t,0,2); next; } if ($t[0] eq 'SYNTAX') { splice(@t,0,1); &syntax($name); next; } if ($t[0] eq $DF && $t[1] eq $LB) { # "::= { ... }" alone. splice(@t,0,2); &oid($name); next; } if ($t[0] eq 'DEFVAL' && $t[1] eq $LB) { splice(@t,0,2); $i = &findstr($RB); # OID is surrounded by braces. @defval = splice(@t,0,$i); $defval{$name} = $symnum{$defval[0]} || "@defval"; shift @t; # Get rid of the right brace. next; } &tok(3) || last; # Three-token constructs. if ($t[0] eq 'REGISTERED' && $t[1] eq 'AS' && $t[2] eq $LB) { splice(@t,0,3); $i = &findstr($RB); # OID is surrounded by braces. @o = splice(@t,0,$i); $onam{$name} = "@o"; shift @t; # Get rid of the right brace. next; } # &tok(4) || last; # So far, nothing requires 4 tokens. &tok(5) || last; # Lots of constructs require 5 tokens. if ($t[1] eq 'OBJECT' && $t[2] eq 'IDENTIFIER' && $t[3] eq $DF && $t[4] eq $LB) { $objid = $t[0]; splice(@t,0,5); &oid($objid); next; } if ($t[1] eq 'ATTRIBUTE' && $t[2] eq 'WITH' && $t[3] eq 'ATTRIBUTE' && $t[4] eq 'SYNTAX') { $name = $t[0]; splice(@t,0,5); &syntax($name); next; } if ($t[0] eq 'IMPORTS') { &imports(); next; } # We don't actually do anything with "fooEntry ::= SEQUENCE { ... }" if ($t[1] eq $DF && $t[2] eq 'SEQUENCE' && $t[3] eq $LB) { if (($i = &findstr($RB)) < 0) { print E "$ARGV\[$.\]: Can't find '$RB'\n"; splice(@t,0,4); next; } while (@t && $t[0] ne $RB) { splice(@t,0,$i + 1); # Discard them. } next; } # We note the MIB's name in " DEFINITIONS ::= BEGIN". if ($t[1] eq 'DEFINITIONS' && $t[2] eq $DF && $t[3] eq 'BEGIN') { $mib = $t[0]; print D "MIB: $mib\n"; splice(@t,0,4); next; } # Handle the utterly flakey syntax that the GTE people insist on using: # swclcnFwdSCR(CLP0) OBJECT-TYPE # swclcnFwdSCR(CLP0+1) OBJECT-TYPE if ($t[1] eq $LP && $t[3] eq $RP && $t[4] eq 'OBJECT-TYPE') { $name = $t[0] . '-' . $t[2]; print E "$ARGV\[$.\]: Change $t[0] $t[1] $t[2] $t[3] to '$name'\n"; splice(@t,0,4,$name); next; } # We try to handle ASN.1 macro definitions here. The format is # ::= # where the is whatever is on the rest of the line. It's # not at all clear how we should correctly recognize the end of an # ASN.1 macro definition, given the standards-be-damned attitude of # the authors of many MIBs. if ($t[1] eq $DF) { print L "$ARGV\[$.\]: Recognized macro $t[0] $t[1] ...\n"; $m = $t[0]; splice(@t,0,2); $" = ' '; $macro{$m} = "@t"; $" = '.'; print L "$ARGV\[$.\]: Macro \"$m\" is \"$macro{$m}\"\n"; @t = (); next; } print E "$ARGV\[$.\]: Can't handle token '$t[0]'\n"; shift @t; next; } print "\b*\tInput done.\n"; } # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # # Process the SEQUENCE command. At present, we don't do much with this one, # # but maybe in the future ... # # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # sub seq { local($item); # print E "Seq: $SEQUENCE $item\n"; &getblock($RB); } # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # # Eat up input until the next line that ends with the delimiter, and combine # # it all into one long string. This is used primarily to read multi-line # # quoted strings. It is also used to locate the final '}' for a SEQUENCE. # # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # sub getblock { local($dlm) = @_; local($val); while (<>) { close(ARGV) if eof; # s/\s$/ /; # s/\s*--\s*.*//; # Strip off trailing comments. if (/(.*)$dlm\s*$/) { # Look for delimiter plus white stuff. $val .= $1; return $val; } $val .= $_; } print E "$ARGV\[$.\]: EOF in {block}\n"; $val; } # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # # This takes care of the cruft necessary to keep $Line and $ARGV set to the # # correct thing for error messages. # # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # sub newfile { print E "Close $ARGV.\n"; close(ARGV); # $Line = 0; } # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # # This registers the type of the named variable. The type is one or more # # tokens in @t. # # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # sub syntax { local($name) = @_; local($type,$i,$qual,@lst); $type = shift @t; if ($t[0] eq '.') { $qual = $type; $type = $t[1]; splice(@t,0,2); } if ($type =~ /I*NTEGER/i) { $type = 'INTEGER'; # Regularize it. if ($t[0] eq $LP) { # INTEGER ( x .. y ) $i = &findstr($RP); &size($i,$type); } elsif ($t[0] eq $LB) { # INTEGER { x .. y } $i = &findstr($RB); &range($i,$type); } } elsif ($type eq 'OBJECT' || $type eq 'ObjectId') { if ($t[0] eq 'IDENTIFIER') { $type = 'OBJID'; shift @t; } } elsif (($type =~ /OCTET/i) || ($type =~ /TEXTUAL/i)) { if ($t[0] eq 'STRING') { $type = 'STRING'; shift @t; } if ($t[0] eq $LP) { # STRING (SIZE x .. y ) $i = &findstr($RP); &size($i,$type); } } elsif ($type eq 'SINGLE') { if (($t[0] eq 'CHARACTER') && ($t[1] eq 'STRING')) { $type = 'STRING'; splice(@t,0,2,'(','SIZE','(','1',')',')'); &size(6,$type); if ($t[0] eq $LP && $t[2] eq '...' && $t[4] eq $RP) { @lst = splice(@t,0,5); # $" = ' '; print E "$ARGV\[$.\]: Ignoring",@lst,"\n"; # $" = '.'; } } else { print E "$ARGV\[$.\]: Can't handle $type $t[0] $t[1]\n"; } } elsif ($type =~ /FaultString/i) { print L "$ARGV\[$.\]: Recognized FaultString ...\n"; $type = 'BIN'; if ($t[0] eq $LP) { # FaultString ( x .. y ) $i = &findstr($RP); &size($i,$type); } } elsif ($type =~ /DisplayString/i) { print L "$ARGV\[$.\]: Recognized DisplayString ...\n"; $type = 'STRING'; if ($t[0] eq $LP) { # DisplayString ( x .. y ) print L "$ARGV\[$.\]: Recognized DisplayString ( ...\n"; $i = &findstr($RP); &size($i,$type); } } elsif ((($type =~ /Display/i) || ($type =~ /Textual/i)) && ($t[0] =~ /String/i)) { print L "$ARGV\[$.\]: Recognized $type String ...\n"; $type = 'STRING'; shift @t; if ($t[0] eq $LP) { # DisplayString ( x .. y ) print L "$ARGV\[$.\]: Recognized Display String ( ...\n"; $i = &findstr($RP); &size($i,$type); } } elsif ($type eq 'CmotSystemID') { $type = 'ARB'; } elsif ($type eq 'Sequence' || $type eq 'SEQUENCE') { if ($t[0] eq 'of' || $t[0] eq 'OF') { shift @t; # Delete the 'of'. shift @t; # Delete the type. } } $type{$name} = $type; if ($type eq 'STRING') { $pad{$name} = '_'; } elsif ($type eq 'BIN') { $pad{$name} = 0; } fail: } # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # # The next $len items are a parenthesized size field for the current SYNTAX # # clause. We should be called with $t[0] eq $LP. # # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # sub size { local($len,$type) = @_; if (($t[2] =~ $range) && ($t[4] eq $RP)) { # ( M .. N ) &minval($t[1]); &maxval($t[3]); splice(@t,0,5); $len -= 5; } elsif ($t[1] =~ /SIZE/i) { # ( SIZE ( N ) ) print L "$ARGV\[$.\]: Recognized ( SIZE ...\n"; if (($t[3] =~ $range) && ($t[5] eq $RP)) { # ( SIZE M .. N ) print L "$ARGV\[$.\]: Recognized ( SIZE $t[2] .. $t[4] )\n"; &minval($t[2]); &maxval($t[4]); splice(@t,0,6); $len -= 6; } elsif (($t[4] =~ $range) && ($t[6] eq $RP) && ($t[7] eq $RP)) { # ( SIZE ( M .. N ) ) print L "$ARGV\[$.\]: Recognized ( SIZE ( $t[3] .. $t[5] ) )\n"; &minval($t[3]); &maxval($t[5]); splice(@t,0,8); $len -= 8; } elsif ($t[2] eq $LP && $t[4] eq $RP && $t[5] eq $RP) { # ( SIZE ( N ) ) print L "$ARGV\[$.\]: Recognized ( SIZE ( $t[3] ) )\n"; &minval($t[3]); &maxval($t[3]); splice(@t,0,6); $len -= 6; } elsif ($t[3] eq $RP) { # ( SIZE N ) print L "$ARGV\[$.\]: Recognized ( SIZE $t[2] )\n"; &minval($t[2]); &maxval($t[2]); splice(@t,0,4); $len -= 4; } } else { print E "$ARGV\[$.\]: Can't handle $t[0] $t[1] $t[2] ...\n"; splice(@t,0,$len+1) if $len >= 0; # Discard up to the final paren. } if ($len > 0) { print E "$ARGV\[$.\]: ### size($len,$type) didn't use its args.\n"; splice(@t,0,$len); } } # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # Record the param as a possible max value for the current $name. # sub maxval { local($val) = @_; if ($max{$name} < $val || !defined($max{$name})) { $max{$name} = $val; } } # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # Record the param as a possible min value for the current $name. # sub minval { local($val) = @_; if ($min{$name} > $val || !defined($min{$name})) { $min{$name} = $val; } } # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # # The next $len items are a parenthesized range field for the current SYNTAX # # clause. There are two recognized syntaxes: # # SYNTAX INTEGER { 0 .. 1234 } # # This gives an integer's range. # # SYNTAX INTEGER { sym1 (1), sym2 (2) ... symN (N) } # # This gives a list of legal values, with symbolic (display) names. We don't # # actually do anything with the symbolic names; we just note the high and low # # numeric values. # # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # sub range { local($len,$type) = @_; if ($t[0] == $LB) { shift @t; --$len; } item: while ($len > 0 && $t[0] ne $RB) { # Check out one item in a list. if ($t[0] eq $LP) { # Left paren in first position. if ($t[2] eq '..' && $t[4] eq $RP) { # ( X .. Y ) &minval($t[1]); &maxval($t[3]); splice(@t,0,5); $len -= 5; next item; } } if ($t[1] eq $LP && $t[3] eq $RP) { # Symbol ( N ) $symnum{$t[0]} = $t[2]; &minval($t[2]); &maxval($t[2]); splice(@t,0,4); $len -= 4; next item; } if ($t[0] eq ',') { # Commas are ignored. shift @t; --$len; next item; } print E "$ARGV\[$.\]: Ignore '$t[0]'\n"; shift @t; --$len; # Ignore one item. next item; # splice(@t,0,$len); # Ignore the rest. # $len = 0; } if ($len > 0) { print E "$ARGV\[$.\]: ### range($len,$type) didn't use its args.\n"; splice(@t,0,$len); } shift @t if $t[0] eq $RB; # Discard the final right brace. } # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # # Read in enough to make sure that there at least n tokens in the @t list; # # return the number of tokens in @t, or zero for failure if we are at EOF and # # there aren't n tokens left. # # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # sub tok { local($n) = @_; local($line,@line,$mac,$new,$s,$t); while (@t < $n) { close(ARGV) if eof; ($line = &inline()) || return 0; # EOF. $new = 1; if ($. == 1) { # First line of new input file. print D "File $ARGV:\n"; print "\b*\tReading $ARGV ...\n"; $files = "$files $ARGV"; # File list for SCCS id string. } token: while ($line) { # Bite off one token from the line. $line =~ s/^\s*//; # Strip off initial white stuff. print M $& if $new; # Preserve initial white stuff. if (!$line) { # Did that empty out the line? print M "\n" # Preserve line boundaries. if !($line || $new); last token; # Get a new line. } # if ($. == 55 || $. == 56) { # print "Line $..\n"; # } if ($line =~ /^\s*--/) { # Comment. print M "\t" if !$new; print M $line; $line = ''; last; # Comments are the rest of the line. } $new = 0; if (substr($line,0,1) eq '"') { # Quoted string. if ($line =~ s/^"([^"]*)"//) { # Complete on this line. $s = $1; print M "\"$s\""; } elsif ($line =~ s/^"(.*)//) { # Incomplete. $s = $1; print M "\"$s\n"; $s =~ s/\s+$//; $s .= ($t = &getblock('"')); print M "$t\""; } @t = (@t , $s); next; } if ($line =~ s/^END\b//) { # END token. print M "-- END\n"; # Avoid multiple ENDs. @t = (@t , "END"); # Pass it along to parser. next; } if ($line =~ s/^$psym//) { # Symbolic name. if ($mac = $macro{$1}) { print L "$ARGV\[$.\]: Recognized $1\n"; $line = "$mac $line"; next token; # Go back and parse the expansion. } @t = (@t , &Csym($1)); print M " $1";; next; } if ($line =~ s/^$pnum//) { # Number. @t = (@t , $1); print M " $1";; next; } if ($line =~ s/^([(){};,])//) { # ASN.1 punctuation. @t = (@t , $1); print M " $1";; next; } if ($line =~ s/^(::=)//) { # BNF definition. @t = (@t , $1); print M " $1";; next; } if ($line =~ s/^(\.+)//) { # String of dots. printf L "$ARGV\[$.\]: Recognized %d dots.\n", length($1); @t = (@t , $1); print M " $1";; next; } if ($line =~ s/^(.)//) { # Mystery character. print L "$ARGV\[$.\]: Unrecognized char \'$1\'\n"; @t = (@t , $1); print M " $1";; next; } } } return scalar(@t); # Return number of tokens in token list. } # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # # Convert an SNMP type to the form used in the agent's variable[] table. This # # mostly amounts to finding an abbreviation for a longer name. # # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # sub type { local($t) = @_; $typ{$t} || $t; } # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # # Given a variable, this returns a numeric value for its "magic" code. At # # present, we just run a counter up. However, we eventually need to find a # # better way to handle this, if only because the number is going into a # # one-byte field in the C MIB-variable table. # # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # sub Xcode { # local(XSYMBOL,symbol) = @_; }