1#!/usr/bin/perl -w 2 3# manlint - report "errors" in man page(s). 4 5# USAGE: 6# manlint [list of files to check] 7# 8# EXAMPLE: 9# manlint /usr/man/man*/*.* | less 10 11# An error is anything not known to be a safe construct in a man page; 12# see man(7) for more information. 13# Currently it's excessively paranoid, but that's the point -- this 14# program assumes there's a problem, and if it isn't we can add that to the 15# ruleset so that what's safe is explicitly spelled out. 16# Currently this program only examines tmac.an based pages, the normal 17# kind encountered in Linux. This is different than the BSD manddoc format, 18# which is used by a number of man pages. 19 20# (C) 1999 David A. Wheeler (dwheeler@ida.org) 21 22# This program is free software; you can redistribute it and/or modify 23# it under the terms of the GNU General Public License as published by 24# the Free Software Foundation; either version 2 of the License, or 25# (at your option) any later version. 26# 27# This program is distributed in the hope that it will be useful, 28# but WITHOUT ANY WARRANTY; without even the implied warranty of 29# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 30# GNU General Public License for more details. 31# 32# You should have received a copy of the GNU General Public License 33# along with this program; if not, write to the Free Software 34# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 35 36 37require 5.002; # Requires Perl 5.002 because functions are prototyped. 38 39# First, set up configuration. 40 41$debug = 0; 42$errs = $totalerrs = 0; 43$goodfiles = $badfiles = $skipfiles = 0; 44$filename = ''; 45 46# Allow options for small or large safe set; just printing if a file fails 47# instead of detail; auto-skip BSD files. 48 49# This is a list of "safe" macros, with their value being the 50# maximum number of allowed parameters (-1 = any, 0=no parameters allowed) 51%safemacros = ( 52 'TH' => 5, 53 # Font Control: 54 'B' => -1, 'BI' => -1, 'BR' => -1, 55 'I' => -1, 'IB' => -1, 'IR' => -1, 56 'RB' => -1, 'RI' => -1, 'SB' => -1, 'SM' => -1, 57 # tmac.an other macros: 58 'SH' => 1, 59 'LP' => 0, 'P' => 0, 60 'PP' => 0, 61 'RS' => 1, 'RE' => 0, 62 'HP' => 1, 'IP' => 2, 'TP' => 1, 63 'DT' => 0, 'PD' => 1, 'SS' => 1, 64 # We'll allow IX (indexing). 65 'IX' => -1, 66 # I'm adding the UR, UN, and UE macros that will permit embedded URIs. 67 'UR' => 1, 68 'UN' => 1, 69 'UE' => 0, 70 # allowed troff macros 71 '\\"' => -1, # troff comments 72 'ps' => 1, # Point size 73 'ft' => 1, # Font commands (not recommended, may be ignored in some cases) 74 'hy' => 1, # Hyphenation (probably ignored in translation) 75 'bp' => 0, # Force page break; optional parameter forbidden. 76 'ne' => 1, # Need lines (likely to be ignored in translation) 77 'br' => 0, 78 'nf' => 0, # No-fill; insert breaks at end of each line. 79 'fi' => 0, 80 'ig' => 1, 81 '.' => 0, # standard end-of-ignore/end-of-definition. 82 'ce' => 1, # Center next N lines 83 'ad' => 1, 84 'na' => 0, 85 # Will probably need to handle some if. 86 'if' => -1, # LIMITED VERSION. 87 'ie' => -1, # LIMITED VERSION. 88 'el' => -1, 89 'so' => 1, # Handle 'so' for shared man pages 90 'sp' => 1, # Vertical Space - only permit positive values. 91 'de' => 1, # Handling 'macro define' is a pain, but many pages require it. 92 'ds' => -1, # Allow string defines. 93 'in' => 1, # Require that every indent be paired with a negative indent. 94 'ti' => 1, # Temporary indent may be ignored 95 'hy' => 1, # Hypenation almost certainly ignored by anyone else. 96 'nh' => 1, # Again, hyphenation likely ignored. 97 'tr' => 1, # Translations limited, see below. 98); 99 100# Allowed parameters for the ft (font) troff command. 101%allowed_ft_parameter = ( 102 '1' => 1, 103 '2' => 1, 104 '3' => 1, 105 '4' => 1, 106 'R' => 1, 107 'I' => 1, 108 'B' => 1, 109 'P' => 1, 110 'CW' => 1, 111 '' => 1, 112); 113 114%allowed_tr = ( 115 '\\(ts"' => 1, 116 '\\(is\'' => 1, 117 '\\(if`' => 1, 118 '\\(pd"' => 1, 119 '\\(*W-|\(bv\*(Tr' => 1, 120 '\\*(Tr' => 1, 121); 122 123sub problem($) { 124 # Report a problem, if you should. 125 my $message = shift; 126 print "${ARGV}: $message\n"; 127 $errs++; 128} 129 130sub clean_state { 131 %defined_macros = (); 132 $is_skipped = 0; 133} 134 135sub process_line { 136 # Process line already read in $_ (default input line). 137 my $macro; 138 my $parameters; 139 if (m/^[.']\s*([^\s]+)\s*(.*)?/) { 140 $macro=$1; 141 $parameters=$2; 142 $macro =~ s/\s//g; 143 print "Found macro: #${macro}#\n" if $debug; 144 if ($macro =~ m/Dd/) { # Is this the BSD macro set and not a tmac.an set? 145 problem("Uses BSD mandoc conventions instead of tmac.an"); 146 $errs--; # Patch up error count. 147 # print "${ARGV}: Uses BSD mandoc conventions instead of tmac.an.\n"; 148 close(ARGV); # Skip the rest of this file. 149 $is_skipped = 1; 150 return; 151 } 152 if ($macro =~ m/\\"/) {return;} # Skip troff comments. 153 if (exists($defined_macros{$macro})) { 154 return; # ??? Should examine the macro parameters. 155 } 156 if (exists($safemacros{$macro}) ) { 157 # ??? Check parameter count. 158 # ??? Check that .TH is the first macro (note: bash.1, etc., break this) 159 if ( ($macro eq 'if') || ($macro eq 'ie' )) { 160 # Only permit checking 't' or 'n' for now. 161 if ($parameters =~ m/^[tn]\s/) { 162 $_ = $parameters; 163 s/^[tn]\s+//; 164 process_line(); # Re-examine line without the if statement. 165 } else { 166 problem("unsafe use of if/ie"); 167 } 168 # ??? sp: only no-parameter or positive values. 169 } elsif ($macro eq 'de') { 170 $parameters =~ m/^([^\s]+)/; 171 $is_defining = $1; 172 $defined_macros{$is_defining} = 1; 173 } elsif ($macro eq 'so') { 174 $parameters =~ m/^([^\s]+)/; 175 $new_file = $1; 176 while (<$new_file>) { process_line(); } 177 } elsif (($macro eq 'ft') && (defined($parameters)) 178 && (! exists($allowed_ft_parameter{$parameters}))) { 179 problem("forbidden ft parameter $parameters"); 180 } elsif (($macro eq 'tr') && (defined($parameters)) 181 && (! exists($allowed_tr{$parameters}))) { 182 problem("forbidden tr parameter $parameters"); 183 } 184 # ??? 'in': Require that every indent be paired with a negative indent. 185 # ??? For macros with text after them, check their text's escapes. 186 } else { 187 problem("unsafe macro $macro"); 188 } 189 } else { 190 # ??? Regular text; check escape clauses. 191 } 192} 193 194 195# Main loop: Process files, looking for errors. 196 197clean_state(); 198 199while (<>) { 200 if ($ARGV ne $filename) { 201 print "Processing $ARGV; up to now good=$goodfiles bad=$badfiles skip=$skipfiles\n"; 202 $filename=$ARGV; 203 } 204 process_line(); 205} continue { 206 if (eof) { # End of processing this file. 207 close ARGV; # Perl magic to get line #s to be accurate. 208 $totalerrs += $errs; 209 if ($errs) { $badfiles++ } else { 210 if ($is_skipped) {$skipfiles++} else {$goodfiles++}; 211 } 212 $errs = 0; 213 clean_state(); 214 } 215} 216 217print "Number of good files = $goodfiles\n"; 218print "Number of bad files = $badfiles\n"; 219print "Number of skipped files = $skipfiles\n"; 220exit $errs; 221 222# ??? Handle .so better (esp. the error messages) 223# currently error messages don't report the traceback & they should. 224 225 226