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