| lh | 9ed821d | 2023-04-07 01:36:19 -0700 | [diff] [blame] | 1 | #! /usr/bin/env perl | 
 | 2 | # Copyright 2002-2019 The OpenSSL Project Authors. All Rights Reserved. | 
 | 3 | # | 
 | 4 | # Licensed under the OpenSSL license (the "License").  You may not use | 
 | 5 | # this file except in compliance with the License.  You can obtain a copy | 
 | 6 | # in the file LICENSE in the source distribution or at | 
 | 7 | # https://www.openssl.org/source/license.html | 
 | 8 |  | 
 | 9 |  | 
 | 10 | require 5.10.0; | 
 | 11 | use warnings; | 
 | 12 | use strict; | 
 | 13 | use Pod::Checker; | 
 | 14 | use File::Find; | 
 | 15 | use File::Basename; | 
 | 16 | use File::Spec::Functions; | 
 | 17 | use Getopt::Std; | 
 | 18 | use lib catdir(dirname($0), "perl"); | 
 | 19 | use OpenSSL::Util::Pod; | 
 | 20 |  | 
 | 21 | # Options. | 
 | 22 | our($opt_d); | 
 | 23 | our($opt_h); | 
 | 24 | our($opt_l); | 
 | 25 | our($opt_n); | 
 | 26 | our($opt_p); | 
 | 27 | our($opt_u); | 
 | 28 | our($opt_c); | 
 | 29 |  | 
 | 30 | sub help() | 
 | 31 | { | 
 | 32 |     print <<EOF; | 
 | 33 | Find small errors (nits) in documentation.  Options: | 
 | 34 |     -d Detailed list of undocumented (implies -u) | 
 | 35 |     -l Print bogus links | 
 | 36 |     -n Print nits in POD pages | 
 | 37 |     -p Warn if non-public name documented (implies -n) | 
 | 38 |     -u Count undocumented functions | 
 | 39 |     -h Print this help message | 
 | 40 |     -c List undocumented commands and options | 
 | 41 | EOF | 
 | 42 |     exit; | 
 | 43 | } | 
 | 44 |  | 
 | 45 | my $temp = '/tmp/docnits.txt'; | 
 | 46 | my $OUT; | 
 | 47 | my %public; | 
 | 48 |  | 
 | 49 | my %mandatory_sections = | 
 | 50 |     ( '*'    => [ 'NAME', 'DESCRIPTION', 'COPYRIGHT' ], | 
 | 51 |       1      => [ 'SYNOPSIS', 'OPTIONS' ], | 
 | 52 |       3      => [ 'SYNOPSIS', 'RETURN VALUES' ], | 
 | 53 |       5      => [ ], | 
 | 54 |       7      => [ ] ); | 
 | 55 |  | 
 | 56 | # Cross-check functions in the NAME and SYNOPSIS section. | 
 | 57 | sub name_synopsis() | 
 | 58 | { | 
 | 59 |     my $id = shift; | 
 | 60 |     my $filename = shift; | 
 | 61 |     my $contents = shift; | 
 | 62 |  | 
 | 63 |     # Get NAME section and all words in it. | 
 | 64 |     return unless $contents =~ /=head1 NAME(.*)=head1 SYNOPSIS/ms; | 
 | 65 |     my $tmp = $1; | 
 | 66 |     $tmp =~ tr/\n/ /; | 
 | 67 |     print "$id trailing comma before - in NAME\n" if $tmp =~ /, *-/; | 
 | 68 |     $tmp =~ s/ -.*//g; | 
 | 69 |     $tmp =~ s/  */ /g; | 
 | 70 |     print "$id missing comma in NAME\n" if $tmp =~ /[^,] /; | 
 | 71 |     $tmp =~ s/,//g; | 
 | 72 |  | 
 | 73 |     my $dirname = dirname($filename); | 
 | 74 |     my $simplename = basename($filename); | 
 | 75 |     $simplename =~ s/.pod$//; | 
 | 76 |     my $foundfilename = 0; | 
 | 77 |     my %foundfilenames = (); | 
 | 78 |     my %names; | 
 | 79 |     foreach my $n ( split ' ', $tmp ) { | 
 | 80 |         $names{$n} = 1; | 
 | 81 |         $foundfilename++ if $n eq $simplename; | 
 | 82 |         $foundfilenames{$n} = 1 | 
 | 83 |             if -f "$dirname/$n.pod" && $n ne $simplename; | 
 | 84 |     } | 
 | 85 |     print "$id the following exist as other .pod files:\n", | 
 | 86 |         join(" ", sort keys %foundfilenames), "\n" | 
 | 87 |         if %foundfilenames; | 
 | 88 |     print "$id $simplename (filename) missing from NAME section\n" | 
 | 89 |         unless $foundfilename; | 
 | 90 |     foreach my $n ( keys %names ) { | 
 | 91 |         print "$id $n is not public\n" | 
 | 92 |             if $opt_p and !defined $public{$n}; | 
 | 93 |     } | 
 | 94 |  | 
 | 95 |     # Find all functions in SYNOPSIS | 
 | 96 |     return unless $contents =~ /=head1 SYNOPSIS(.*)=head1 DESCRIPTION/ms; | 
 | 97 |     my $syn = $1; | 
 | 98 |     foreach my $line ( split /\n+/, $syn ) { | 
 | 99 |         my $sym; | 
 | 100 |         $line =~ s/STACK_OF\([^)]+\)/int/g; | 
 | 101 |         $line =~ s/__declspec\([^)]+\)//; | 
 | 102 |         if ( $line =~ /env (\S*)=/ ) { | 
 | 103 |             # environment variable env NAME=... | 
 | 104 |             $sym = $1; | 
 | 105 |         } elsif ( $line =~ /typedef.*\(\*(\S+)\)\(.*/ ) { | 
 | 106 |             # a callback function pointer: typedef ... (*NAME)(... | 
 | 107 |             $sym = $1; | 
 | 108 |         } elsif ( $line =~ /typedef.* (\S+)\(.*/ ) { | 
 | 109 |             # a callback function signature: typedef ... NAME(... | 
 | 110 |             $sym = $1; | 
 | 111 |         } elsif ( $line =~ /typedef.* (\S+);/ ) { | 
 | 112 |             # a simple typedef: typedef ... NAME; | 
 | 113 |             $sym = $1; | 
 | 114 |         } elsif ( $line =~ /enum (\S*) \{/ ) { | 
 | 115 |             # an enumeration: enum ... { | 
 | 116 |             $sym = $1; | 
 | 117 |         } elsif ( $line =~ /#define ([A-Za-z0-9_]+)/ ) { | 
 | 118 |             $sym = $1; | 
 | 119 |         } elsif ( $line =~ /([A-Za-z0-9_]+)\(/ ) { | 
 | 120 |             $sym = $1; | 
 | 121 |         } | 
 | 122 |         else { | 
 | 123 |             next; | 
 | 124 |         } | 
 | 125 |         print "$id $sym missing from NAME section\n" | 
 | 126 |             unless defined $names{$sym}; | 
 | 127 |         $names{$sym} = 2; | 
 | 128 |  | 
 | 129 |         # Do some sanity checks on the prototype. | 
 | 130 |         print "$id prototype missing spaces around commas: $line\n" | 
 | 131 |             if ( $line =~ /[a-z0-9],[^ ]/ ); | 
 | 132 |     } | 
 | 133 |  | 
 | 134 |     foreach my $n ( keys %names ) { | 
 | 135 |         next if $names{$n} == 2; | 
 | 136 |         print "$id $n missing from SYNOPSIS\n"; | 
 | 137 |     } | 
 | 138 | } | 
 | 139 |  | 
 | 140 | # Check if SECTION ($3) is located before BEFORE ($4) | 
 | 141 | sub check_section_location() | 
 | 142 | { | 
 | 143 |     my $id = shift; | 
 | 144 |     my $contents = shift; | 
 | 145 |     my $section = shift; | 
 | 146 |     my $before = shift; | 
 | 147 |  | 
 | 148 |     return | 
 | 149 |         unless $contents =~ /=head1 $section/ and $contents =~ /=head1 $before/; | 
 | 150 |     print "$id $section should be placed before $before section\n" | 
 | 151 |         if $contents =~ /=head1 $before.*=head1 $section/ms; | 
 | 152 | } | 
 | 153 |  | 
 | 154 | sub check() | 
 | 155 | { | 
 | 156 |     my $filename = shift; | 
 | 157 |     my $dirname = basename(dirname($filename)); | 
 | 158 |  | 
 | 159 |     my $contents = ''; | 
 | 160 |     { | 
 | 161 |         local $/ = undef; | 
 | 162 |         open POD, $filename or die "Couldn't open $filename, $!"; | 
 | 163 |         $contents = <POD>; | 
 | 164 |         close POD; | 
 | 165 |     } | 
 | 166 |  | 
 | 167 |     my $id = "${filename}:1:"; | 
 | 168 |  | 
 | 169 |     # Check ordering of some sections in man3 | 
 | 170 |     if ( $filename =~ m|man3/| ) { | 
 | 171 |         &check_section_location($id, $contents, "RETURN VALUES", "EXAMPLES"); | 
 | 172 |         &check_section_location($id, $contents, "SEE ALSO", "HISTORY"); | 
 | 173 |         &check_section_location($id, $contents, "EXAMPLES", "SEE ALSO"); | 
 | 174 |     } | 
 | 175 |  | 
 | 176 |     &name_synopsis($id, $filename, $contents) | 
 | 177 |         unless $contents =~ /=for comment generic/ | 
 | 178 |             or $filename =~ m@man[157]/@; | 
 | 179 |  | 
 | 180 |     print "$id doesn't start with =pod\n" | 
 | 181 |         if $contents !~ /^=pod/; | 
 | 182 |     print "$id doesn't end with =cut\n" | 
 | 183 |         if $contents !~ /=cut\n$/; | 
 | 184 |     print "$id more than one cut line.\n" | 
 | 185 |         if $contents =~ /=cut.*=cut/ms; | 
 | 186 |     print "$id EXAMPLE not EXAMPLES section.\n" | 
 | 187 |         if $contents =~ /=head1 EXAMPLE[^S]/; | 
 | 188 |     print "$id WARNING not WARNINGS section.\n" | 
 | 189 |         if $contents =~ /=head1 WARNING[^S]/; | 
 | 190 |     print "$id missing copyright\n" | 
 | 191 |         if $contents !~ /Copyright .* The OpenSSL Project Authors/; | 
 | 192 |     print "$id copyright not last\n" | 
 | 193 |         if $contents =~ /head1 COPYRIGHT.*=head/ms; | 
 | 194 |     print "$id head2 in All uppercase\n" | 
 | 195 |         if $contents =~ /head2\s+[A-Z ]+\n/; | 
 | 196 |     print "$id extra space after head\n" | 
 | 197 |         if $contents =~ /=head\d\s\s+/; | 
 | 198 |     print "$id period in NAME section\n" | 
 | 199 |         if $contents =~ /=head1 NAME.*\.\n.*=head1 SYNOPSIS/ms; | 
 | 200 |     print "$id POD markup in NAME section\n" | 
 | 201 |         if $contents =~ /=head1 NAME.*[<>].*=head1 SYNOPSIS/ms; | 
 | 202 |     print "$id Duplicate $1 in L<>\n" | 
 | 203 |         if $contents =~ /L<([^>]*)\|([^>]*)>/ && $1 eq $2; | 
 | 204 |     print "$id Bad =over $1\n" | 
 | 205 |         if $contents =~ /=over([^ ][^24])/; | 
 | 206 |     print "$id Possible version style issue\n" | 
 | 207 |         if $contents =~ /OpenSSL version [019]/; | 
 | 208 |  | 
 | 209 |     if ( $contents !~ /=for comment multiple includes/ ) { | 
 | 210 |         # Look for multiple consecutive openssl #include lines | 
 | 211 |         # (non-consecutive lines are okay; see man3/MD5.pod). | 
 | 212 |         if ( $contents =~ /=head1 SYNOPSIS(.*)=head1 DESCRIPTION/ms ) { | 
 | 213 |             my $count = 0; | 
 | 214 |             foreach my $line ( split /\n+/, $1 ) { | 
 | 215 |                 if ( $line =~ m@include <openssl/@ ) { | 
 | 216 |                     print "$id has multiple includes\n" if ++$count == 2; | 
 | 217 |                 } else { | 
 | 218 |                     $count = 0; | 
 | 219 |                 } | 
 | 220 |             } | 
 | 221 |         } | 
 | 222 |     } | 
 | 223 |  | 
 | 224 |     open my $OUT, '>', $temp | 
 | 225 |         or die "Can't open $temp, $!"; | 
 | 226 |     podchecker($filename, $OUT); | 
 | 227 |     close $OUT; | 
 | 228 |     open $OUT, '<', $temp | 
 | 229 |         or die "Can't read $temp, $!"; | 
 | 230 |     while ( <$OUT> ) { | 
 | 231 |         next if /\(section\) in.*deprecated/; | 
 | 232 |         print; | 
 | 233 |     } | 
 | 234 |     close $OUT; | 
 | 235 |     unlink $temp || warn "Can't remove $temp, $!"; | 
 | 236 |  | 
 | 237 |     # Find what section this page is in; assume 3. | 
 | 238 |     my $section = 3; | 
 | 239 |     $section = $1 if $dirname =~ /man([1-9])/; | 
 | 240 |  | 
 | 241 |     foreach ((@{$mandatory_sections{'*'}}, @{$mandatory_sections{$section}})) { | 
 | 242 |         # Skip "return values" if not -s | 
 | 243 |         print "$id: missing $_ head1 section\n" | 
 | 244 |             if $contents !~ /^=head1\s+${_}\s*$/m; | 
 | 245 |     } | 
 | 246 | } | 
 | 247 |  | 
 | 248 | my %dups; | 
 | 249 |  | 
 | 250 | sub parsenum() | 
 | 251 | { | 
 | 252 |     my $file = shift; | 
 | 253 |     my @apis; | 
 | 254 |  | 
 | 255 |     open my $IN, '<', $file | 
 | 256 |         or die "Can't open $file, $!, stopped"; | 
 | 257 |  | 
 | 258 |     while ( <$IN> ) { | 
 | 259 |         next if /^#/; | 
 | 260 |         next if /\bNOEXIST\b/; | 
 | 261 |         next if /\bEXPORT_VAR_AS_FUNC\b/; | 
 | 262 |         my @fields = split(); | 
 | 263 |         die "Malformed line $_" | 
 | 264 |             if scalar @fields != 2 && scalar @fields != 4; | 
 | 265 |         push @apis, $fields[0]; | 
 | 266 |     } | 
 | 267 |  | 
 | 268 |     close $IN; | 
 | 269 |  | 
 | 270 |     print "# Found ", scalar(@apis), " in $file\n" unless $opt_p; | 
 | 271 |     return sort @apis; | 
 | 272 | } | 
 | 273 |  | 
 | 274 | sub getdocced() | 
 | 275 | { | 
 | 276 |     my $dir = shift; | 
 | 277 |     my %return; | 
 | 278 |  | 
 | 279 |     foreach my $pod ( glob("$dir/*.pod") ) { | 
 | 280 |         my %podinfo = extract_pod_info($pod); | 
 | 281 |         foreach my $n ( @{$podinfo{names}} ) { | 
 | 282 |             $return{$n} = $pod; | 
 | 283 |             print "# Duplicate $n in $pod and $dups{$n}\n" | 
 | 284 |                 if defined $dups{$n} && $dups{$n} ne $pod; | 
 | 285 |             $dups{$n} = $pod; | 
 | 286 |         } | 
 | 287 |     } | 
 | 288 |  | 
 | 289 |     return %return; | 
 | 290 | } | 
 | 291 |  | 
 | 292 | my %docced; | 
 | 293 |  | 
 | 294 | sub checkmacros() | 
 | 295 | { | 
 | 296 |     my $count = 0; | 
 | 297 |     my %seen; | 
 | 298 |  | 
 | 299 |     print "# Checking macros (approximate)\n"; | 
 | 300 |     foreach my $f ( glob('include/openssl/*.h') ) { | 
 | 301 |         # Skip some internals we don't want to document yet. | 
 | 302 |         next if $f eq 'include/openssl/asn1.h'; | 
 | 303 |         next if $f eq 'include/openssl/asn1t.h'; | 
 | 304 |         next if $f eq 'include/openssl/err.h'; | 
 | 305 |         open(IN, $f) || die "Can't open $f, $!"; | 
 | 306 |         while ( <IN> ) { | 
 | 307 |             next unless /^#\s*define\s*(\S+)\(/; | 
 | 308 |             my $macro = $1; | 
 | 309 |             next if $docced{$macro} || defined $seen{$macro}; | 
 | 310 |             next if $macro =~ /i2d_/ | 
 | 311 |                 || $macro =~ /d2i_/ | 
 | 312 |                 || $macro =~ /DEPRECATEDIN/ | 
 | 313 |                 || $macro =~ /IMPLEMENT_/ | 
 | 314 |                 || $macro =~ /DECLARE_/; | 
 | 315 |             print "$f:$macro\n" if $opt_d; | 
 | 316 |             $count++; | 
 | 317 |             $seen{$macro} = 1; | 
 | 318 |         } | 
 | 319 |         close(IN); | 
 | 320 |     } | 
 | 321 |     print "# Found $count macros missing (not all should be documented)\n" | 
 | 322 | } | 
 | 323 |  | 
 | 324 | sub printem() | 
 | 325 | { | 
 | 326 |     my $libname = shift; | 
 | 327 |     my $numfile = shift; | 
 | 328 |     my $count = 0; | 
 | 329 |     my %seen; | 
 | 330 |  | 
 | 331 |     foreach my $func ( &parsenum($numfile) ) { | 
 | 332 |         next if $docced{$func} || defined $seen{$func}; | 
 | 333 |  | 
 | 334 |         # Skip ASN1 utilities | 
 | 335 |         next if $func =~ /^ASN1_/; | 
 | 336 |  | 
 | 337 |         print "$libname:$func\n" if $opt_d; | 
 | 338 |         $count++; | 
 | 339 |         $seen{$func} = 1; | 
 | 340 |     } | 
 | 341 |     print "# Found $count missing from $numfile\n\n"; | 
 | 342 | } | 
 | 343 |  | 
 | 344 |  | 
 | 345 | # Collection of links in each POD file. | 
 | 346 | # filename => [ "foo(1)", "bar(3)", ... ] | 
 | 347 | my %link_collection = (); | 
 | 348 | # Collection of names in each POD file. | 
 | 349 | # "name(s)" => filename | 
 | 350 | my %name_collection = (); | 
 | 351 |  | 
 | 352 | sub collectnames { | 
 | 353 |     my $filename = shift; | 
 | 354 |     $filename =~ m|man(\d)/|; | 
 | 355 |     my $section = $1; | 
 | 356 |     my $simplename = basename($filename, ".pod"); | 
 | 357 |     my $id = "${filename}:1:"; | 
 | 358 |  | 
 | 359 |     my $contents = ''; | 
 | 360 |     { | 
 | 361 |         local $/ = undef; | 
 | 362 |         open POD, $filename or die "Couldn't open $filename, $!"; | 
 | 363 |         $contents = <POD>; | 
 | 364 |         close POD; | 
 | 365 |     } | 
 | 366 |  | 
 | 367 |     $contents =~ /=head1 NAME([^=]*)=head1 /ms; | 
 | 368 |     my $tmp = $1; | 
 | 369 |     unless (defined $tmp) { | 
 | 370 |         print "$id weird name section\n"; | 
 | 371 |         return; | 
 | 372 |     } | 
 | 373 |     $tmp =~ tr/\n/ /; | 
 | 374 |     $tmp =~ s/-.*//g; | 
 | 375 |  | 
 | 376 |     my @names = map { s/\s+//g; $_ } split(/,/, $tmp); | 
 | 377 |     unless (grep { $simplename eq $_ } @names) { | 
 | 378 |         print "$id missing $simplename\n"; | 
 | 379 |         push @names, $simplename; | 
 | 380 |     } | 
 | 381 |     foreach my $name (@names) { | 
 | 382 |         next if $name eq ""; | 
 | 383 |         my $name_sec = "$name($section)"; | 
 | 384 |         if (! exists $name_collection{$name_sec}) { | 
 | 385 |             $name_collection{$name_sec} = $filename; | 
 | 386 |         } else { #elsif ($filename ne $name_collection{$name_sec}) { | 
 | 387 |             print "$id $name_sec also in $name_collection{$name_sec}\n"; | 
 | 388 |         } | 
 | 389 |     } | 
 | 390 |  | 
 | 391 |     my @foreign_names = | 
 | 392 |         map { map { s/\s+//g; $_ } split(/,/, $_) } | 
 | 393 |         $contents =~ /=for\s+comment\s+foreign\s+manuals:\s*(.*)\n\n/; | 
 | 394 |     foreach (@foreign_names) { | 
 | 395 |         $name_collection{$_} = undef; # It still exists! | 
 | 396 |     } | 
 | 397 |  | 
 | 398 |     my @links = $contents =~ /L< | 
 | 399 |                               # if the link is of the form L<something|name(s)>, | 
 | 400 |                               # then remove 'something'.  Note that 'something' | 
 | 401 |                               # may contain POD codes as well... | 
 | 402 |                               (?:(?:[^\|]|<[^>]*>)*\|)? | 
 | 403 |                               # we're only interested in references that have | 
 | 404 |                               # a one digit section number | 
 | 405 |                               ([^\/>\(]+\(\d\)) | 
 | 406 |                              /gx; | 
 | 407 |     $link_collection{$filename} = [ @links ]; | 
 | 408 | } | 
 | 409 |  | 
 | 410 | sub checklinks { | 
 | 411 |     foreach my $filename (sort keys %link_collection) { | 
 | 412 |         foreach my $link (@{$link_collection{$filename}}) { | 
 | 413 |             print "${filename}:1: reference to non-existing $link\n" | 
 | 414 |                 unless exists $name_collection{$link}; | 
 | 415 |         } | 
 | 416 |     } | 
 | 417 | } | 
 | 418 |  | 
 | 419 | sub publicize() { | 
 | 420 |     foreach my $name ( &parsenum('util/libcrypto.num') ) { | 
 | 421 |         $public{$name} = 1; | 
 | 422 |     } | 
 | 423 |     foreach my $name ( &parsenum('util/libssl.num') ) { | 
 | 424 |         $public{$name} = 1; | 
 | 425 |     } | 
 | 426 |     foreach my $name ( &parsenum('util/private.num') ) { | 
 | 427 |         $public{$name} = 1; | 
 | 428 |     } | 
 | 429 | } | 
 | 430 |  | 
 | 431 | my %skips = ( | 
 | 432 |     'aes128' => 1, | 
 | 433 |     'aes192' => 1, | 
 | 434 |     'aes256' => 1, | 
 | 435 |     'aria128' => 1, | 
 | 436 |     'aria192' => 1, | 
 | 437 |     'aria256' => 1, | 
 | 438 |     'camellia128' => 1, | 
 | 439 |     'camellia192' => 1, | 
 | 440 |     'camellia256' => 1, | 
 | 441 |     'des' => 1, | 
 | 442 |     'des3' => 1, | 
 | 443 |     'idea' => 1, | 
 | 444 |     '[cipher]' => 1, | 
 | 445 |     '[digest]' => 1, | 
 | 446 | ); | 
 | 447 |  | 
 | 448 | sub checkflags() { | 
 | 449 |     my $cmd = shift; | 
 | 450 |     my %cmdopts; | 
 | 451 |     my %docopts; | 
 | 452 |     my $ok = 1; | 
 | 453 |  | 
 | 454 |     # Get the list of options in the command. | 
 | 455 |     open CFH, "./apps/openssl list --options $cmd|" | 
 | 456 |         || die "Can list options for $cmd, $!"; | 
 | 457 |     while ( <CFH> ) { | 
 | 458 |         chop; | 
 | 459 |         s/ .$//; | 
 | 460 |         $cmdopts{$_} = 1; | 
 | 461 |     } | 
 | 462 |     close CFH; | 
 | 463 |  | 
 | 464 |     # Get the list of flags from the synopsis | 
 | 465 |     open CFH, "<doc/man1/$cmd.pod" | 
 | 466 |         || die "Can't open $cmd.pod, $!"; | 
 | 467 |     while ( <CFH> ) { | 
 | 468 |         chop; | 
 | 469 |         last if /DESCRIPTION/; | 
 | 470 |         next unless /\[B<-([^ >]+)/; | 
 | 471 |         $docopts{$1} = 1; | 
 | 472 |     } | 
 | 473 |     close CFH; | 
 | 474 |  | 
 | 475 |     # See what's in the command not the manpage. | 
 | 476 |     my @undocced = (); | 
 | 477 |     foreach my $k ( keys %cmdopts ) { | 
 | 478 |         push @undocced, $k unless $docopts{$k}; | 
 | 479 |     } | 
 | 480 |     if ( scalar @undocced > 0 ) { | 
 | 481 |         $ok = 0; | 
 | 482 |         foreach ( @undocced ) { | 
 | 483 |             print "doc/man1/$cmd.pod: Missing -$_\n"; | 
 | 484 |         } | 
 | 485 |     } | 
 | 486 |  | 
 | 487 |     # See what's in the command not the manpage. | 
 | 488 |     my @unimpl = (); | 
 | 489 |     foreach my $k ( keys %docopts ) { | 
 | 490 |         push @unimpl, $k unless $cmdopts{$k}; | 
 | 491 |     } | 
 | 492 |     if ( scalar @unimpl > 0 ) { | 
 | 493 |         $ok = 0; | 
 | 494 |         foreach ( @unimpl ) { | 
 | 495 |             next if defined $skips{$_}; | 
 | 496 |             print "doc/man1/$cmd.pod: Not implemented -$_\n"; | 
 | 497 |         } | 
 | 498 |     } | 
 | 499 |  | 
 | 500 |     return $ok; | 
 | 501 | } | 
 | 502 |  | 
 | 503 | getopts('cdlnphu'); | 
 | 504 |  | 
 | 505 | &help() if $opt_h; | 
 | 506 | $opt_n = 1 if $opt_p; | 
 | 507 | $opt_u = 1 if $opt_d; | 
 | 508 |  | 
 | 509 | die "Need one of -[cdlnpu] flags.\n" | 
 | 510 |     unless $opt_c or $opt_l or $opt_n or $opt_u; | 
 | 511 |  | 
 | 512 | if ( $opt_c ) { | 
 | 513 |     my $ok = 1; | 
 | 514 |     my @commands = (); | 
 | 515 |  | 
 | 516 |     # Get list of commands. | 
 | 517 |     open FH, "./apps/openssl list -1 -commands|" | 
 | 518 |         || die "Can't list commands, $!"; | 
 | 519 |     while ( <FH> ) { | 
 | 520 |         chop; | 
 | 521 |         push @commands, $_; | 
 | 522 |     } | 
 | 523 |     close FH; | 
 | 524 |  | 
 | 525 |     # See if each has a manpage. | 
 | 526 |     foreach ( @commands ) { | 
 | 527 |         next if $_ eq 'help' || $_ eq 'exit'; | 
 | 528 |         if ( ! -f "doc/man1/$_.pod" ) { | 
 | 529 |             print "doc/man1/$_.pod does not exist\n"; | 
 | 530 |             $ok = 0; | 
 | 531 |         } else { | 
 | 532 |             $ok = 0 if not &checkflags($_); | 
 | 533 |         } | 
 | 534 |     } | 
 | 535 |  | 
 | 536 |     # See what help is missing. | 
 | 537 |     open FH, "./apps/openssl list --missing-help |" | 
 | 538 |         || die "Can't list missing help, $!"; | 
 | 539 |     while ( <FH> ) { | 
 | 540 |         chop; | 
 | 541 |         my ($cmd, $flag) = split; | 
 | 542 |         print "$cmd has no help for -$flag\n"; | 
 | 543 |         $ok = 0; | 
 | 544 |     } | 
 | 545 |     close FH; | 
 | 546 |  | 
 | 547 |     exit 1 if not $ok; | 
 | 548 | } | 
 | 549 |  | 
 | 550 | if ( $opt_l ) { | 
 | 551 |     foreach (@ARGV ? @ARGV : glob('doc/*/*.pod')) { | 
 | 552 |         collectnames($_); | 
 | 553 |     } | 
 | 554 |     checklinks(); | 
 | 555 | } | 
 | 556 |  | 
 | 557 | if ( $opt_n ) { | 
 | 558 |     &publicize() if $opt_p; | 
 | 559 |     foreach (@ARGV ? @ARGV : glob('doc/*/*.pod')) { | 
 | 560 |         &check($_); | 
 | 561 |     } | 
 | 562 | } | 
 | 563 |  | 
 | 564 | if ( $opt_u ) { | 
 | 565 |     my %temp = &getdocced('doc/man3'); | 
 | 566 |     foreach ( keys %temp ) { | 
 | 567 |         $docced{$_} = $temp{$_}; | 
 | 568 |     } | 
 | 569 |     &printem('crypto', 'util/libcrypto.num'); | 
 | 570 |     &printem('ssl', 'util/libssl.num'); | 
 | 571 |     &checkmacros(); | 
 | 572 | } | 
 | 573 |  | 
 | 574 | exit; |