openpkg/lint-spec.pl

Thu, 04 Oct 2012 20:30:05 +0200

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 04 Oct 2012 20:30:05 +0200
changeset 715
c10fb90893b9
permissions
-rw-r--r--

Correct out of date build configuration, porting to Solaris 11 network
link infrastructure and new libpcap logic. This additionally allows for
device drivers in subdirectories of /dev. Correct packaged nmap
personalities and signatures to work out of the box. Finally, hack
arpd logic to properly close sockets and quit on TERM by repeating
signaling in the run command script. Sadly, all this fails to correct
the run time behaviour of honeyd which fails to bind to the IP layer.

     1 ##
     2 ##  lint-spec.pl -- OpenPKG *.spec File Checker
     3 ##  Copyright (c) 2000-2012 OpenPKG GmbH <http://openpkg.com/>
     4 ##
     5 ##  This software is property of the OpenPKG GmbH, DE MUC HRB 160208.
     6 ##  All rights reserved. Licenses which grant limited permission to use,
     7 ##  copy, modify and distribute this software are available from the
     8 ##  OpenPKG GmbH.
     9 ##
    10 ##  THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED
    11 ##  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
    12 ##  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
    13 ##  IN NO EVENT SHALL THE AUTHORS AND COPYRIGHT HOLDERS AND THEIR
    14 ##  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
    15 ##  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
    16 ##  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
    17 ##  USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
    18 ##  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
    19 ##  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
    20 ##  OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
    21 ##  SUCH DAMAGE.
    22 ##
    24 #   Perl run-time requirement
    25 require 5;
    26 BEGIN {
    27     eval "use Getopt::Long; use IO;";
    28     if ($@) {
    29         print STDERR
    30             "lint-spec: ERROR: This command requires a full-size Perl installation!\n" .
    31             "lint-spec: HINT:  Install OpenPKG \"perl\" package to use this command.\n";
    32         exit(1);
    33     }
    34 }
    36 #   OpenPKG instance prefix
    37 my $my_prefix = $ENV{'OPENPKG_PREFIX'};
    38 delete $ENV{'OPENPKG_PREFIX'};
    40 #   program information
    41 my $progname = "lint-spec";
    42 my $progvers = "1.0.0";
    44 #   parameters (defaults)
    45 my $version = 0;
    46 my $verbose = 0;
    47 my $help    = 0;
    48 my $check   = 'all';
    49 my $tmpdir  = ($ENV{TMPDIR} || $ENV{TEMPDIR} || "/tmp") . "/$progname";
    50 my $rpm     = "$my_prefix/bin/openpkg rpm";
    52 #   exception handling support
    53 $SIG{__DIE__} = sub {
    54     my ($err) = @_;
    55     $err =~ s|\s+at\s+.*||s if (not $verbose);
    56     print STDERR "$progname:ERROR: $err ". ($! ? "($!)" : "") . "\n";
    57     exit(1);
    58 };
    60 #   command line parsing
    61 Getopt::Long::Configure("bundling");
    62 my $result = GetOptions(
    63     'V|version'     => \$version,
    64     'v|verbose'     => \$verbose,
    65     'h|help'        => \$help,
    66     'c|check=s'     => \$check,
    67     't|tmpdir=s'    => \$tmpdir,
    68     'r|rpm=s'       => \$rpm,
    69 ) || die "option parsing failed";
    70 if ($help) {
    71     print "Usage: $progname [options] [SPECFILE ...]\n" .
    72           "Available options:\n" .
    73           " -v,--verbose       enable verbose run-time mode\n" .
    74           " -h,--help          print out this usage page\n" .
    75           " -c,--check=CHECKS  select checks to perform (default='all')\n" .
    76           " -r,--rpm=FILE      filesystem path to RPM program\n" .
    77           " -t,--tmpdir=PATH   filesystem path to temporary directory\n" .
    78           " -V,--version       print program version\n";
    79     exit(0);
    80 }
    81 if ($version) {
    82     print "OpenPKG $progname $progvers\n";
    83     exit(0);
    84 }
    86 #   verbose message printing
    87 sub msg_verbose {
    88     my ($msg) = @_;
    89     print STDERR "$msg\n" if ($verbose);
    90 }
    92 #   warning message printing
    93 sub msg_warning {
    94     my ($msg) = @_;
    95     print STDERR "$progname:WARNING: $msg\n";
    96 }
    98 #   error message printing
    99 sub msg_error {
   100     my ($msg) = @_;
   101     print STDERR "$progname:ERROR: $msg\n";
   102 }
   104 #   determine check list
   105 my @check_list = (qw(
   106     blank
   107     comment
   108     license
   109     header
   110     section
   111     preproc
   112     script
   113     global
   114     sources
   115     digest
   116 ));
   117 my @checks = ();
   118 if ($check eq 'all') {
   119     @checks = @check_list;
   120 }
   121 else {
   122     foreach my $c (split(/,/, $check)) {
   123         if (not grep($c, @check_list)) {
   124             die "invalid check \"$c\"";
   125         }
   126         push(@checks, $c);
   127     }
   128 }
   130 #   global return code
   131 $main::GRC = 0;
   133 #   iterate over all .spec files
   134 foreach my $filename (@ARGV) {
   135     my $io = new IO::File "<$filename"
   136         or die "unable to open file \"$filename\" for reading";
   137     my $spec; { local $/ = undef; $spec = <$io>; }
   138     $io->close;
   139     foreach my $check (@checks) {
   140         eval "\&check_$check(\$filename, \$spec);";
   141     }
   142 }
   144 #   die gracefully
   145 exit($main::GRC);
   147 ##  _________________________________________________________________
   148 ##
   149 ##  COMMON SUBROUTINES
   150 ##  _________________________________________________________________
   151 ##
   153 sub lines {
   154     my ($txt) = @_;
   155     my $l = 0;
   156     $txt =~ s|\n|$l++, ''|sge;
   157     return $l;
   158 }
   160 sub lint_message {
   161     my ($type, $file, $done, $this, $msg) = @_;
   162     if (defined($done) and defined($this)) {
   163         my $start = &lines($done) + 1;
   164         my $end = $start + &lines($this);
   165         my $pos = $start;
   166         $pos .= "-". $end if ($end > $start);
   167         printf("%s:%s: %s:%s: %s\n", $progname, $type, $file, $pos, $msg);
   168     }
   169     else {
   170         printf("%s:%s: %s: %s\n", $progname, $type, $file, $msg);
   171     }
   172 }
   174 sub lint_warning {
   175     my ($file, $done, $this, $msg) = @_;
   176     &lint_message("WARNING", $file, $done, $this, $msg);
   177     $main::GRC = 1 if ($main::GRC < 1);
   178 }
   180 sub lint_error {
   181     my ($file, $done, $this, $msg) = @_;
   182     &lint_message("ERROR", $file, $done, $this, $msg);
   183     $main::GRC = 2 if ($main::GRC < 2);
   184 }
   186 ##  _________________________________________________________________
   187 ##
   188 ##  CHECK "blank": whitespace and blank lines
   189 ##  _________________________________________________________________
   190 ##
   192 sub check_blank {
   193     my ($file, $spec) = @_;
   195     #   check for CR-LF combination
   196     my $done = ''; my $this = ''; my $todo = $spec;
   197     while ($todo =~ m/\r\n/s) {
   198         $done .= $`; $this = $&; $todo = $';
   199         &lint_warning($file, $done, $this, "carriage-return (CR, 0x0d) line-feed (NL, 0x0a) combination (expected just line-feed)");
   200         $done .= $this;
   201     }
   203     #   check for multiple blank lines
   204     $done = ''; $this = ''; $todo = $spec;
   205     while ($todo =~ m/(\r?\n[ \t]*){3,}/s) {
   206         $done .= $`; $this = $&; $todo = $';
   207         &lint_warning($file, $done, $this, "multiple subsequent blank lines (expected single blank line)");
   208         $done .= $this;
   209     }
   211     #   check for trailing whitespaces
   212     $done = ''; $this = ''; $todo = $spec;
   213     while ($todo =~ m/[ \t]+\r?\n/s) {
   214         $done .= $`; $this = $&; $todo = $';
   215         if ($done eq '' or $done =~ m|\n$|s) {
   216             &lint_warning($file, $done, $this, "whitespace on empty line (expected none)");
   217         }
   218         else {
   219             &lint_warning($file, $done, $this, "trailing whitespace (expected none)");
   220         }
   221         $done .= $this;
   222     }
   224     #   check for bogus line continuations
   225     $done = ''; $this = ''; $todo = $spec;
   226     while ($todo =~ m/\\[ \t]*\r?\n(?=[ \t]*\r?\n)/s) {
   227         $done .= $`; $this = $&; $todo = $';
   228         &lint_warning($file, $done, $this, "bogus line continuation for following empty line (expect no line continuation)");
   229         $done .= $this;
   230     }
   232     #   check for leading whitespaces before line continuations
   233     $done = ''; $this = ''; $todo = $spec;
   234     while ($todo =~ m/[ \t]{2,}\\[ \t]*\r?\n/s) {
   235         $done .= $`; $this = $&; $todo = $';
   236         &lint_warning($file, $done, $this, "multiple leading whitespace before line continuation (expected just a single space)");
   237         $done .= $this;
   238     }
   240     #   check for leading tabs
   241     $done = ''; $this = ''; $todo = $spec;
   242     while ($todo =~ m/^ *\t+ *[^ \t]/m) {
   243         $done .= $`; $this = $&; $todo = $';
   244         &lint_warning($file, $done, $this, "leading tabs (expected spaces)");
   245         $done .= $this;
   246     }
   248     #   check for mandatory/wished trailing blank line
   249     if ($spec !~ m|\n\n$|) {
   250         &lint_warning($file, $spec, "", "mandatory/wished trailing blank line missing (expected one)");
   251     }
   252 }
   254 ##  _________________________________________________________________
   255 ##
   256 ##  CHECK "comment": sharp-comments
   257 ##  _________________________________________________________________
   258 ##
   260 sub check_comment {
   261     my ($file, $spec) = @_;
   263     #   check for comment indentation and contents
   264     my $done = ''; my $this = ''; my $todo = $spec;
   265     while ($todo =~ m/^([ \t]*)(#+)([ \t]*)(.*?)$/m) {
   266         $done .= $`; $this = $&; $todo = $';
   267         my ($lead, $sharp, $pad, $text) = ($1, $2, $3, $4);
   268         if (length($lead) % 2 != 0) {
   269             &lint_warning($file, $done, $this, "incorrect comment indentation (expected a multiple of 2 spaces)");
   270         }
   271         if (length($lead) > 1 && length($sharp) > 1) {
   272             &lint_warning($file, $done, $this, "indented comment has introduced with multiple sharps (expected single sharp character)");
   273         }
   274         if (length($pad.$text) > 0 && length($sharp.$pad) % 4 != 0) {
   275             &lint_warning($file, $done, $this, "incorrect comment text padding (expected a multiple of 4 sharps or spaces)");
   276         }
   277         if (length($pad) == 0 && length($text) > 0) {
   278             &lint_warning($file, $done, $this, "missing leading space before comment text (expected padding spaces)");
   279         }
   280         if (length($pad) > 0 && length($text) == 0) {
   281             &lint_warning($file, $done, $this, "empty comment text (expected a reasonable text)");
   282         }
   283         $done .= $this;
   284     }
   286     #   check for comment contents only
   287     if ($file !~ m|openpkg\.spec$|) {
   288         $done = ''; $this = ''; $todo = $spec;
   289         while ($todo =~ m/\n\n[ \t]*#[ \t]+([^\n]+)(?!\n([ \t]*#))/s) {
   290             $done .= $`; $this = $&; $todo = $';
   291             my $text = $1;
   292             if (length($text) > 0 and $text =~ m|^[A-Z][^A-Z]|) {
   293                 &lint_warning($file, $done, $this, "comment text starts with upper-case letter (expected lower-case letter)");
   294             }
   295             $done .= $this;
   296         }
   297     }
   298 }
   300 ##  _________________________________________________________________
   301 ##
   302 ##  CHECK "license": license header
   303 ##  _________________________________________________________________
   304 ##
   306 sub check_license {
   307     my ($file, $spec) = @_;
   309     my $name = "[a-z][a-z0-9-]*";
   310     if ($file =~ m|^.*/([a-z][a-z0-9-]*)\.spec$|) {
   311         $name = $1;
   312     }
   313     elsif ($file =~ m|^([a-z][a-z0-9-]*)\.spec$|) {
   314         $name = $1;
   315     }
   316     my $re = "";
   317     $re .= "##\\n";
   318     $re .= "##  $name\\.spec -- OpenPKG RPM Package Specification\\n";
   319     $re .= "##  Copyright \\(c\\) 200[0-9]-2012 OpenPKG Foundation e\.V\. <http://openpkg\\.net/>\\n";
   320     $re .= "##\\n";
   321     $re .= "##  Permission to use, copy, modify, and distribute this software for\\n";
   322     $re .= "##  any purpose with or without fee is hereby granted, provided that\\n";
   323     $re .= "##  the above copyright notice and this permission notice appear in all\\n";
   324     $re .= "##  copies\\.\\n";
   325     $re .= "##\\n";
   326     $re .= "##  THIS SOFTWARE IS PROVIDED \\`\\`AS IS'' AND ANY EXPRESSED OR IMPLIED\\n";
   327     $re .= "##  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF\\n";
   328     $re .= "##  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED\\.\\n";
   329     $re .= "##  IN NO EVENT SHALL THE AUTHORS AND COPYRIGHT HOLDERS AND THEIR\\n";
   330     $re .= "##  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\\n";
   331     $re .= "##  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES \\(INCLUDING, BUT NOT\\n";
   332     $re .= "##  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF\\n";
   333     $re .= "##  USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION\\) HOWEVER CAUSED AND\\n";
   334     $re .= "##  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\\n";
   335     $re .= "##  OR TORT \\(INCLUDING NEGLIGENCE OR OTHERWISE\\) ARISING IN ANY WAY OUT\\n";
   336     $re .= "##  OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\\n";
   337     $re .= "##  SUCH DAMAGE\\.\\n";
   338     $re .= "##\\n";
   339     if ($name ne "openpkg" and $spec !~ m|^$re|os) {
   340         &lint_warning($file, "", "", "invalid license header");
   341     }
   342 }
   344 ##  _________________________________________________________________
   345 ##
   346 ##  CHECK "header": RPM headers
   347 ##  _________________________________________________________________
   348 ##
   350 sub check_header {
   351     my ($file, $spec) = @_;
   353     my @headers = (qw(
   354         m:Name:^[a-z][a-z0-9-]*$
   355         m:Summary:^[A-Z]\S*(\s+([A-Z]\S*|of|for|from|in|at|on|\(\S+\)))*
   356         m:URL:^((https?|ftp)://.+|-)$
   357         m:Vendor:.+
   358         m:Packager:^(OpenPKG\sFoundation\se\.V\.|OpenPKG\sGmbH)$
   359         m:Distribution:^(OpenPKG|OpenPKG\sCommunity|OpenPKG\sEnterprise)$
   360         m:Class:^(BOOT|CORE|BASE|PLUS|EVAL|JUNK|PRIV)$
   361         m:Group:^[A-Z][a-zA-Z0-9]+$
   362         m:License:.+
   363         m:Version:^[^-]+$
   364         m:Release:^(E?([1-9]\.)?20[0-9][0-9](0[1-9]|1[0-2])(0[1-9]|[1-2][0-9]|3[01])|E?[1-9]\.[0-9]\.\d+|%\{[^\}]+\})$
   365         o:Source\d+:^((https?|ftp)://.+|[^/]+)$
   366         o:Patch\d+:^((https?|ftp)://.+|[^/]+)$
   367         o:Prefix:^%{l_prefix}$
   368         o:BuildRoot:^%{l_buildroot}$
   369         m:BuildPreReq:^(((,\s+)?digest\\(sha1:\%\{(SOURCE|PATCH)\d+\}\\)\s+=\s+[0-9a-fA-F]+)+|(OpenPKG,\sopenpkg\s>=\s\S+)?((,\s)?([a-z][a-z0-9-]*(\:\:with_[a-z][a-z0-9_]+)?|[A-Z][A-Z0-9-]*)(\s(>=?|==?|<=?|!=)\s\S+)?)+)$
   370         m:PreReq:^(OpenPKG,\sopenpkg\s>=\s\S+)?((,\s)?([a-z][a-z0-9-]*(\:\:with_[a-z][a-z0-9_]+)?|[A-Z][A-Z0-9-]*)(\s(>=?|==?|<=?|!=)\s\S+)?)+$
   371         o:AutoReq:^no$
   372         o:AutoReqProv:^no$
   373         o:Provides:^((,\s)?([a-z][a-z0-9-]*(\:\:with_[a-z][a-z0-9_]+)?|[A-Z][A-Z0-9-]*)(\s==?\s\S+)?)+$
   374         o:Conflicts:^((,\s)?([a-z][a-z0-9-]*(\:\:with_[a-z][a-z0-9_]+)?|[A-Z][A-Z0-9-]*)(\s(>=?|==?|<=?|!=)\s\S+)?)+$
   375     ));
   376     my @headers_def = (qw(
   377         Summary:^Badly\sPackaged\sProgram$
   378         Vendor:^John\sDoe$
   379         Group:^Unknown$
   380         License:^DoePL$
   381     ));
   382     my @headers_mult = (qw(
   383         BuildPreReq PreReq Provides Conflicts
   384     ));
   386     my @seen = ();
   387     my %count = ();
   388     my $done = ''; my $this = ''; my $todo = $spec;
   389     while ($todo =~ m/^(\S+):([ \t]*)(.*?)$/m) {
   390         $done .= $`; $this = $&; $todo = $';
   391         my ($header, $pad, $value) = ($1, $2, $3);
   393         #   check for layouting
   394         if (length($value) == 0) {
   395             &lint_error($file, $done, $this, "empty RPM header value");
   396         }
   397         if (length($header.":".$pad) != 14) {
   398             &lint_warning($file, $done, $this, "invalid RPM header name/value padding (expected value at column 15)");
   399         }
   401         #   check for valid header name and value
   402         if (not grep { $header =~ m|^$_$|s } map { m/^[^:]:([^:]+):/, $1 } @headers) {
   403             &lint_error($file, $done, $this, "invalid RPM header name \"$header\"");
   404         }
   405         else {
   406             my $hn = quotemeta((grep { $header =~ m|^$_$|s } map { m/^[^:]+:([^:]+):/, $1 } @headers)[0]);
   407             my $re = (map { m/^[^:]+:${hn}:(.+)$/s } @headers)[0];
   408             my $re_match = $re;
   409             $re_match =~ s|^\(|(?:|sg;
   410             $re_match =~ s|([^\\])\(|\1(?:|sg;
   411             if (    not ($file =~ m|openpkg\.spec$| and $header eq 'Provides' and $value eq '%{l_prefix}')
   412                 and $value !~ m|${re_match}|s) {
   413                 &lint_warning($file, $done, $this, "RPM header \"$header\": " .
   414                               "invalid value \"$value\" (expected to match \"$re\")");
   415             }
   416             my $re_def = (map { m/^${hn}:(.+)$/s } @headers_def)[0];
   417             if (defined($re_def)) {
   418                 my $re_def_match = $re_def;
   419                 $re_def_match =~ s|^\(|(?:|sg;
   420                 $re_def_match =~ s|([^\\])\(|\1(?:|sg;
   421                 if ($value =~ m|${re_def_match}|s) {
   422                     &lint_warning($file, $done, $this, "RPM header \"$header\": " .
   423                                   "default value \"$value\" (expected to match \"$re\", except for this)");
   424                 }
   425             }
   426             if ($header =~ m/^Vendor/ and $value =~ m/et\sal/ and $value !~ m/et\sal\./) {
   427                 &lint_warning($file, $done, $this, "RPM header \"$header\": " .
   428                               "has value \"$value\" (expected \"et al.\" with dot)");
   429             }
   430         }
   431         push(@seen, $header);
   432         $count{$header}++;
   433         $done .= $this;
   434     }
   436     #   check for existence of mandatory headers
   437     foreach my $header (@headers) {
   438         my ($type, $name, $regex) = split(/:/, $header, 3);
   439         if ($type eq 'm') {
   440             if ($file =~ m|openpkg\.spec$| and
   441                 ($name eq "BuildPreReq" or $name eq "PreReq")) {
   442                 #   the bootstrap package is an obvious exception
   443                 next;
   444             }
   445             if (not grep(/^$name$/, @seen)) {
   446                 &lint_warning($file, undef, undef, "mandatory RPM header \"$name\" not found");
   447             }
   448         }
   449     }
   451     #   check for multiple occurrence headers
   452     foreach my $seen (@seen) {
   453         if ($count{$seen} > 1 and not (grep { $_ eq $seen } @headers_mult)) {
   454             &lint_error($file, undef, undef, "RPM header \"$seen\" occurs multiple times (expected just once)");
   455         }
   456     }
   457 }
   459 ##  _________________________________________________________________
   460 ##
   461 ##  CHECK "section": RPM sections
   462 ##  _________________________________________________________________
   463 ##
   465 sub check_section {
   466     my ($file, $spec) = @_;
   468     my $require = qq{
   469         (%define,)*
   470         Name:,
   471         Summary:,
   472         URL:,
   473         Vendor:,
   474         Packager:,
   475         Distribution:,
   476         Class:,
   477         Group:,
   478         License:,
   479         Version:,
   480         Release:,
   481         (%option,)*
   482         (%define,|%undefine,)*
   483         (Source\\d+:,)*
   484         (Patch\\d+:,)*
   485         (%NoSource,)*
   486         (%NoPatch,)*
   487         (Prefix:,)?
   488         (BuildRoot:,)?
   489         ((BuildPreReq:,)
   490          |(PreReq:,))*
   491         (AutoReq:,)?
   492         (AutoReqProv:,)?
   493         (Provides:,)*
   494         (Conflicts:,)*
   495         %description,
   496         (%track,)?
   497         %prep,
   498         %build,
   499         %install,
   500         (%check,)?
   501         %files,
   502         %clean,
   503         (%pre,)?
   504         (%post,)?
   505         (%preun,)?
   506         (%postun,)?
   507         (%trigger,)?
   508         (%triggerin,)?
   509         (%triggerun,)?
   510         (%triggerpostun,)?
   511         (%verifyscript,)?
   512     };
   514     #   check for order of headers
   515     my $sections = "";
   516     my $done = ''; my $this = ''; my $todo = $spec;
   517     while ($todo =~ m/^(\S+:|%\S+).*$/m) {
   518         $done .= $`; $this = $&; $todo = $';
   519         my $section = $1;
   520         next if ($section =~ m/^%(if|else|endif)/);
   521         $sections .= "$section,";
   522         $done .= $this;
   523     }
   524     my $regex = $require;
   525     $regex =~ s|\s+||sg;
   526     if ($sections !~ m/^$regex$/s) {
   527         $regex =~ s|,| |sg;
   528         &lint_error($file, undef, undef, "invalid RPM section order: $sections (expected \"$regex\")");
   529     }
   530 }
   532 ##  _________________________________________________________________
   533 ##
   534 ##  CHECK "preproc": RPM macro pre-processor
   535 ##  _________________________________________________________________
   536 ##
   538 sub check_preproc {
   539     my ($file, $spec) = @_;
   541     my $done = ''; my $this = ''; my $todo = $spec;
   542     while ($todo =~ m/^(%(?:if|else|endif|define|undefine|option))(.*)$/m) {
   543         $done .= $`; $this = $&; $todo = $';
   544         my ($cmd, $args) = ($1, $2);
   545         if ($cmd eq '%if') {
   546             #   FIXME: either try to really parse the boolean expression
   547             #   FIXME: or at least try to guess its correct syntax
   548         }
   549         elsif ($cmd eq '%else' or $cmd eq '%endif') {
   550             if (length($args) > 0) {
   551                 &lint_warning($file, $done, $this, "garbage after pre-processor directive " .
   552                               "\"$cmd\" (expected no arguments)");
   553             }
   554         }
   555         elsif ($cmd eq '%undefine') {
   556             if ($args =~ m|^\s+(\S+)\s*$|) {
   557                 my $var = $1;
   558                 if ($var !~ m/^(V|with)_[a-z][a-zA-Z0-9_]*$/) {
   559                     &lint_warning($file, $done, $this, "unusually named macro: \"$var\" " .
   560                                   "(expected \"(V|with)_[a-z][a-zA-Z0-9_]*\")");
   561                     next;
   562                 }
   563             }
   564             else {
   565                 &lint_error($file, $done, $this, "invalid number of arguments to pre-processor " .
   566                             "directive \"$cmd\" (expected exactly 1 argument)");
   567             }
   568         }
   569         elsif ($cmd eq '%define' or $cmd eq '%option') {
   570             if ($args =~ m|^\s+(\S+)\s+(.*)$|) {
   571                 my ($var, $val) = ($1, $2);
   572                 if ($var !~ m/^(V|with)_[a-z][a-zA-Z0-9_]*$/) {
   573                     &lint_warning($file, $done, $this, "unusually named macro: \"$var\" " .
   574                                   "(expected \"(V|with)_[a-z][a-zA-Z0-9_]*\")");
   575                     next;
   576                 }
   577                 if (length($val) == 0) {
   578                     &lint_error($file, $done, $this, "empty macro value");
   579                 }
   580             }
   581             else {
   582                 &lint_error($file, $done, $this, "invalid number of arguments to pre-processor " .
   583                             "directive \"$cmd\" (expected exactly 2 arguments)");
   584             }
   585         }
   586         $done .= $this;
   587     }
   589     #   check correct if/endif nesting
   590     my @stack = ();
   591     $done = ''; $this = ''; $todo = $spec;
   592     while ($todo =~ m/^(%(?:if|else|endif|define|undefine|option)).*$/m) {
   593         $done .= $`; $this = $&; $todo = $';
   594         my $directive = $1;
   595         if ($directive eq '%if') {
   596             push(@stack, &lines($done . $this));
   597         }
   598         elsif ($directive eq '%endif') {
   599             if (@stack == 0) {
   600                 &lint_error($file, $done, $this, "found \%endif without corresponding opening \%if");
   601                 last;
   602             }
   603             pop(@stack);
   604         }
   605         $done .= $this;
   606     }
   607     my $line;
   608     while (defined($line = pop(@stack))) {
   609         &lint_error($file, undef, undef, "\%if at line $line never closed by \%endif");
   610     }
   612     #   check for indented preprocessor constructs
   613     $done = ''; $this = ''; $todo = $spec;
   614     while ($todo =~ m/^[ \t]+(%(?:if|else|endif|define|undefine|option)).*$/m) {
   615         $done .= $`; $this = $&; $todo = $';
   616         my $directive = $1;
   617         &lint_error($file, $done, $this, "found indented \"$directive\" preprocessor directive (expected no indentation)");
   618         $done .= $this;
   619     }
   620 }
   622 ##  _________________________________________________________________
   623 ##
   624 ##  CHECK "script": shell scripts
   625 ##  _________________________________________________________________
   626 ##
   628 sub check_script {
   629     my ($file, $spec) = @_;
   631     my $done = ''; my $this = ''; my $todo = $spec;
   632     while ($todo =~ m/(\%(?:description|prep|build|install|check|files|clean|pre|post|preun|postun|trigger|triggerin|triggerun|triggerpostun|verifyscript))([^\n]*)\n(.*?\n)(?=\%(?:description|prep|build|install|check|files|clean|pre|post|preun|postun|trigger|triggerin|triggerun|triggerpostun|verifyscript)|$)/s) {
   633         $done .= $`; $this = $&; $todo = $';
   634         my ($section, $args, $script) = ($1, $2, $3);
   636         #   perform checks for a single script section
   637         &check_script_section($file, $done, $this, $section, $args, $script);
   639         $done .= $this;
   640     }
   641 }
   643 sub check_script_section {
   644     my ($file, $outer_done, $outer_this, $section, $args, $script) = @_;
   646     #   skip plain-text/non-scripting section %description
   647     return if ($section eq '%description');
   649     #   remove comment contents
   650     $outer_this =~ s|^[ \t]*#[^\n]*||mg;
   652     #   check shell redirections
   653     my $done = $outer_done; my $this = ''; my $todo = $outer_this;
   654     while (   $todo =~ m/[ \t]+(\d+)?[><][ \t]+\S+/s
   655            or $todo =~ m/[ \t]+[><](\&\d+)?[ \t]+\S+/s) {
   656         $done .= $`; $this = $&; $todo = $';
   657         &lint_warning($file, $done, $this, "section $section: whitespace after shell redirection (expected none)");
   658         $done .= $this;
   659     }
   661     #   utility function: extract a single shell command
   662     sub command_extract {
   663         my ($script) = @_;
   664         my $cmd = '';
   665         while ($script ne '') {
   666             $script =~ s/^([ \t]*'[^']*')/         $cmd .= $1, ''/se && next;
   667             $script =~ s/^([ \t]*"[^"]*")/         $cmd .= $1, ''/se && next;
   668             $script =~ s/^([ \t]*[^ \t;\)\\\r\n]+)/$cmd .= $1, ''/se && next;
   669             $script =~ s/^([ \t]*\\[ \t]*\r?\n)/   $cmd .= $1, ''/se && next;
   670             last;
   671         }
   672         return ($cmd, $script);
   673     }
   675     #   utility function: join a multi-line command
   676     sub multiline_join {
   677         my ($cmd) = @_;
   678         $cmd =~ s/([ \t]*\\[ \t]*\r?\n[ \t]*)/ /sg;
   679         return $cmd;
   680     }
   682     #   utility function: split command into arguments
   683     sub shell_tokenize {
   684         my ($cmd) = @_;
   685         my @cmd = ();
   686         while ($cmd ne '') {
   687             $cmd =~ s/^\s*('[^']*')/push(@cmd, $1), ''/se && next;
   688             $cmd =~ s/^\s*("[^"]*")/push(@cmd, $1), ''/se && next;
   689             $cmd =~ s/^\s*(\S+)/    push(@cmd, $1), ''/se && next;
   690             $cmd =~ s/^\s*$/                        ''/se && last;
   691         }
   692         return @cmd;
   693     }
   695     #   check filesystem path style
   696     $done = $outer_done; $this = ''; $todo = $outer_this;
   697     while ($todo =~ m/\%\{l_shtool\}\s+(\w+)\s+/s) {
   698         $done .= $`; $this = $&; $todo = $';
   699         ($this, $todo) = &command_extract($this . $todo);
   701         #   check for shtool options with no space before argument
   702         my $subthis = $this;
   703         $subthis =~ s/\%{[A-Za-z][A-Za-z0-9_]*}//sg;
   704         $subthis =~ s/\%{[A-Za-z][A-Za-z0-9_]*(?:\s+[^}]+?)?}//sg;
   705         #   remove content of quoted arguments as they might contain unrelated things to catch
   706         $subthis =~ s/'[^']*'/ARG/sg;
   707         $subthis =~ s/"[^"]*"/ARG/sg;
   708         $subthis =~ s/`[^`]*`/ARG/sg;
   709         #   remove us completely if line ends with a quote as this indicates we are a quoted argument to ourselfs
   710         $subthis =~ s/[^']*'$//s;
   711         $subthis =~ s/[^"]*"$//s;
   712         $subthis =~ s/[^`]*`$//s;
   713         $subthis =~ s/[^']*' \\\n//s;
   714         $subthis =~ s/[^"]*" \\\n//s;
   715         $subthis =~ s/[^`]*` \\\n//s;
   716         #   catch command termination by semicolon, pipe, or, and;
   717         $subthis =~ s/[;|&].*$//s;
   718         if ($subthis =~ m/\s-[a-zA-Z]\S/) {
   719             &lint_warning($file, $done, $this, "found use of shtool option with space omitted before argument");
   720         }
   722         #   openpkg-rc is special because does bootstrap things
   723         last if ($file =~ m|openpkg-rc\.spec$|);
   725         my @cmd = &shell_tokenize(&multiline_join($this));
   726         if ($cmd[1] eq 'mkdir') {
   727             #   join flags with their arguments
   728             for (my $i = 2; $i <= $#cmd; $i++) {
   729                 if ($cmd[$i] eq '-m') {
   730                     splice(@cmd, $i, 2, $cmd[$i].$cmd[$i+1]);
   731                 }
   732             }
   733             #   check paths
   734             for (my $i = $#cmd; $i > 1 and $cmd[$i] !~ m|^-| and $cmd[$i] !~ m|^\d+$|; $i--) {
   735                 if ($cmd[$i] =~ m|/$|) {
   736                     &lint_warning($file, $done, $this, "section $section: superfluous trailing slash on " .
   737                                   "created path in \"shtool mkdir\" command (expected none)");
   738                 }
   739             }
   740         }
   741         elsif ($cmd[1] eq 'install') {
   742             #   join flags with their arguments
   743             for (my $i = 2; $i <= $#cmd; $i++) {
   744                 if ($cmd[$i] =~ m/-(e|m|o|g)$/) {
   745                     splice(@cmd, $i, 2, $cmd[$i].$cmd[$i+1]);
   746                 }
   747                 elsif ($cmd[$i] eq '%{SOURCE') {
   748                     splice(@cmd, $i, 2, $cmd[$i].$cmd[$i+1]);
   749                 }
   750                 elsif ($cmd[$i] eq '%{l_value') {
   751                     while ($i < $#cmd and $cmd[$i+1] !~ m|\}$|s) {
   752                         splice(@cmd, $i, 2, $cmd[$i].$cmd[$i+1]);
   753                     }
   754                     splice(@cmd, $i, 2);
   755                     $i--;
   756                 }
   757             }
   759             #   determine last path argument
   760             my $i = $#cmd; $i-- while ($i > 1 and $cmd[$i] !~ m|^-| and $cmd[$i] !~ m|^\d+$|);
   762             #   check paths
   763             if (($#cmd - $i) > 2 and $cmd[-1] !~ m|/$|) {
   764                 #   simple case: multiple sources require target to be a directory
   765                 &lint_warning($file, $done, $this, "section $section: missing trailing slash on " .
   766                               "destination path in \"shtool install\" command (expected one) 1");
   767             }
   768             elsif (($#cmd - $i) == 2 and $cmd[-1] !~ m|/$|) {
   769                 #   complex case: single source, so we can check only known destination paths
   770                 if (   $cmd[-1] =~ m/\%\{l_prefix\}\/(bin|cgi|include|info|lib|pub|sbin)$/
   771                     or $cmd[-1] =~ m/\%\{l_prefix\}\/(etc|libexec|man|share|var)(\/[^\/]+)?$/) {
   772                     &lint_warning($file, $done, $this, "section $section: missing trailing slash on " .
   773                                   "destination path in \"shtool install\" command (expected one) 2");
   774                 }
   775             }
   776             else {
   777                 #   special case: if any path contains '*', then globbing might occur
   778                 for (my $i = 2; $i <= $#cmd; $i++) {
   779                     if ($cmd[$i] =~ m/\*/ and $cmd[-1] !~ m|/$|) {
   780                         &lint_warning($file, $done, $this, "section $section: missing trailing slash on " .
   781                                       "destination path in \"shtool install\" command (expected one) 3");
   782                         last;
   783                     }
   784                 }
   785             }
   786         }
   787         $done .= $this;
   788     }
   790     #   check for redundant 'export PATH' statements
   791     #$done = $outer_done; $this = ''; $todo = $outer_this;
   792     #while ($todo =~ m/\bexport[ \t]+([a-zA-Z_][a-zA-Z0-9_]*[ \t]+)*PATH\b/s) {
   793     #    $done .= $`; $this = $&; $todo = $';
   794     #    &lint_warning($file, $done, $this, "section $section: redundant \"export PATH\" statement (expected none)");
   795     #    $done .= $this;
   796     #}
   798     #   check for obsolete cpp build flags
   799     $done = $outer_done; $this = ''; $todo = $outer_this;
   800     while ($todo =~ m/-I\%\{l_prefix\}\/include(\/([^ \t"';]+?))?[ \t"';]/s) {
   801         $done .= $`; $this = $&; $todo = $';
   802         my ($subdir) = $2;
   803         &lint_warning($file, $done, $this, "section $section: hard-coded C pre-processor path option " .
   804                       "(use \"%{l_cppflags".($subdir ? " $subdir" : "")."}\" instead)");
   805         $done .= $this;
   806     }
   808     #   check for obsolete ld build flags
   809     $done = $outer_done; $this = ''; $todo = $outer_this;
   810     while ($todo =~ m/-L\%\{l_prefix\}\/lib(\/([^ \t"';]+?))?[ \t"';]/s) {
   811         $done .= $`; $this = $&; $todo = $';
   812         my ($subdir) = $2;
   813         &lint_warning($file, $done, $this, "section $section: hard-coded linker path option " .
   814                       "(use \"%{l_ldflags".($subdir ? " $subdir" : "")."}\" instead)");
   815         $done .= $this;
   816     }
   818     #   check for "raw" tool usage
   819     if ($section ne '%description' and $file !~ m|openpkg\.spec$|) {
   820         foreach my $token (&shell_tokenize($outer_this)) {
   821             if ($token =~ m/^(rpmtool|shtool|curl|bash|gzip|bzip2|tar|cc|cxx|make|patch)$/s) {
   822                 &lint_warning($file, undef, undef, "section $section: raw usage of standard tool \"$token\"" .
   823                               " (use \"%{l_${token}}\" instead)");
   824             }
   825         }
   826     }
   828     #   check for setup and patch macro usage
   829     if ($section eq '%prep') {
   830         $done = $outer_done; $this = ''; $todo = $outer_this;
   831         my @tokens = &shell_tokenize($outer_this);
   832         while ($todo =~ m/([^\n]*)\n/s) {
   833             $done .= $`; $this = $&; $todo = $';
   834             if ($this =~ m/^ *%setup.*  .*$/) {
   835                 &lint_warning($file, undef, undef, "section $section: multiple spaces in \"\%setup\" macro" .
   836                               " (reduce to single space)");
   837             }
   838             if ($this =~ m/^ *%setup.*-[ab]$/ and grep(/^\%setup+$/, @tokens) == 1) {
   839                 &lint_warning($file, undef, undef, "section $section: -[ab] option superflous for single \"\%setup\" macro" .
   840                               " (remove it)");
   841             }
   842             if ($this =~ m/^ *%patch.*  .*$/) {
   843                 &lint_warning($file, undef, undef, "section $section: multiple spaces in \"\%patch\" macro" .
   844                               " (reduce to single space)");
   845             }
   846             if ($this =~ m/^ *%patch.*-p +[01]/) {
   847                 &lint_warning($file, undef, undef, "section $section: syntactically wrong space in \"\%patch\" macro" .
   848                               " (use -pN)");
   849             }
   850             if ($this =~ m/^ *%patch.*-P 0\s*$/ and grep(/^\%patch+$/, @tokens) == 1) {
   851                 &lint_warning($file, undef, undef, "section $section: syntactically wrong space in \"\%patch\" macro" .
   852                               " (omit -P 0 for single patch)");
   853             }
   854             if ($this =~ m/^ *%patch.*-P [^0-9]/ and grep(/^\%patch+$/, @tokens)  > 1) {
   855                 &lint_warning($file, undef, undef, "section $section: syntactically wrong space in \"\%patch\" macro" .
   856                               " (use -P for multiple patches)");
   857             }
   858             $done .= $this;
   859         }
   860         if (grep(/^\%setup\d+$/, @tokens)) {
   861             &lint_warning($file, undef, undef, "section $section: numbered \"\%setup\" macro" .
   862                           " (do not use %setup directly followed by a number, replace with %setup ... -[ab])");
   863         }
   864         if (grep(/^\%patch\d+$/, @tokens)) {
   865             &lint_warning($file, undef, undef, "section $section: numbered \"\%patch\" macro" .
   866                           " (do not use %patch directly followed by a number, replace with %patch ... -P)");
   867         }
   868     }
   870     #   check for hard-coded prefix in %pre[un],%post[un] scripts
   871     if ($section =~ m/^\%(preun|postun|pre|post)$/s and $file !~ m|openpkg\.spec$|) {
   872         $done = $outer_done; $this = ''; $todo = $outer_this;
   873         while ($todo =~ m/\%\{l_prefix\}/s) {
   874             $done .= $`; $this = $&; $todo = $';
   875             &lint_warning($file, $done, $this, "section $section: hard-coded prefix (\%{l_prefix}) found " .
   876                           "(use \"\$RPM_INSTALL_PREFIX\" to allow relocation)");
   877             $done .= $this;
   878         }
   879     }
   881     #   check for sub-shell parenthesis style
   882     #   (this is such complicated because the Bourne Shell has a
   883     #   construct "case <value> in <pattern> ) <script> ;; ... esac"
   884     #   where the closing parenthesis makes heavy problems for our
   885     #   check. So we first have to get rid of this. Unfortunately this
   886     #   is again not easy because there exists nested(!) case/esac
   887     #   constructs. Hence, we have to use a small recursive descent
   888     #   parser which replaces the ")" in case/esac constructs with
   889     #   "PCLOSE". A similar thing is done with string literals, although
   890     #   here the job is more trivial).
   891     if ($section !~ m/^\%files$/s) {
   892         $done = $outer_done; $this = ''; $todo = $outer_this;
   893         $todo =~ s/`[^`]*`/STRING/sg;
   894         $todo =~ s/'[^']*'/STRING/sg;
   895         $todo =~ s/"[^"]*"/STRING/sg;
   896         $todo = (&parse_sh("", $todo))[0];
   897         sub parse_sh {
   898             my ($done, $todo) = @_;
   899             while ($todo =~ m/^(.*?)(case|;;)/s) {
   900                 if ($2 eq 'case') {
   901                     ($done, $todo) = &parse_case($done.$1, $2.$');
   902                 }
   903                 else {
   904                     ($done, $todo) = ($done.$1, $2.$');
   905                     return ($done, $todo);
   906                 }
   907             }
   908             ($done, $todo) = ($done . $todo, "");
   909             return ($done, $todo);
   910         }
   911         sub parse_case {
   912             my ($done, $todo) = @_;
   913             $todo =~ m|^\s*case\s+\S+\s+in\b|s or return (undef, undef);
   914             ($done, $todo) = ($done . $&, $');
   915             while ($todo =~ m|^(\s*[^)\n]+)(\))|s) {
   916                 ($done, $todo) = ($done . $1 . "PCLOSE", $');
   917                 ($done, $todo) = &parse_sh($done, $todo) or return (undef, undef);
   918                 $todo =~ m|^\s*;;|s or return (undef, undef);
   919                 ($done, $todo) = ($done . $&, $');
   920             }
   921             $todo =~ m|^\s*esac|s or return (undef, undef);
   922             ($done, $todo) = ($done . $&, $');
   923             return ($done, $todo);
   924         }
   925         if ($file !~ m|openpkg\.spec$| and $todo eq '') {
   926             &lint_warning($file, $outer_done, $outer_this,
   927                           "unable to correctly parse case/esac constructs in shell-script" .
   928                           " (sub-shell parenthesis check skipped)");
   929         }
   930         else {
   931             while ($todo =~ m/\)[ \t]*\n/s) {
   932                 $done .= $`; $this = $&; $todo = $';
   933                 &lint_warning($file, $done, $this, "problematic sub-shell construct without error handling " .
   934                               "(use \"...) || exit \$?\" to make sure it correctly exits on nested failure)");
   935                 $done .= $this;
   936             }
   937         }
   938     }
   939 }
   941 ##  _________________________________________________________________
   942 ##
   943 ##  CHECK "global": globals
   944 ##  _________________________________________________________________
   945 ##
   947 sub check_global {
   948     my ($file, $spec) = @_;
   950     #   check for deprecated use of %{name} macro
   951     my $done = ''; my $this = ''; my $todo = $spec;
   952     while ($todo =~ m/\%\{name\}/s) {
   953         $done .= $`; $this = $&; $todo = $';
   954         &lint_warning($file, $done, $this, "deprecated usage of %{name} macro (expected none)");
   955         $done .= $this;
   956     }
   958     #   FIXME: activate later
   959     #   check for over-long lines
   960     #$done = ''; $this = ''; $todo = $spec;
   961     #while ($todo =~ m/([^\n]{80,})\n/s) {
   962     #    $done .= $`; $this = $&; $todo = $';
   963     #    &lint_warning($file, $done, $this, "over-long line with ".sprintf("%d", length($1))." characters " .
   964     #                  "(expected maximum of 79 characters)");
   965     #    $done .= $this;
   966     #}
   968     #   check for "shtool install" with -e on same line
   969     my $done = ''; my $this = ''; my $todo = $spec;
   970     while ($todo =~ m/%{l_shtool} install[^\n]*-e/s) {
   971         $done .= $`; $this = $&; $todo = $';
   972         &lint_warning($file, $done, $this, "found -e option of \"shtool install\" on same line (expected separate lines)");
   973         $done .= $this;
   974     }
   976     #   check for "shtool install" with multiple -e on same line
   977     my $done = ''; my $this = ''; my $todo = $spec;
   978     while ($todo =~ m/%{l_shtool} install[^\n]+\\\n([^\n]*?-e[^\n]*?-e[^\n]*\\\n)+/s) {
   979         $done .= $`; $this = $&; $todo = $';
   980         &lint_warning($file, $done, $this, "found multiple -e options of \"shtool install\" on same line (expected separate lines)");
   981         $done .= $this;
   982     }
   984     #   check for deprecated use of "shtool install|subst -e @l_...@ %{l_...}"
   985     if ($file !~ m|openpkg-tool|) {
   986         my $done = ''; my $this = ''; my $todo = $spec;
   987         while ($todo =~ m/%{l_shtool} (install|subst)(?:\\\n|[^\n])*?-e(?:\\\n|[^\n])*?\@l_(prefix|[smrn](usr|grp|uid|gid))\@[^n]+%{l_\2}/s) {
   988             $done .= $`; $this = $&; $todo = $';
   989             &lint_warning($file, $done, $this, "deprecated usage of \"shtool $1 -e \@l_$2\@\" (expected use of %{l_value -s -a})");
   990             $done .= $this;
   991         }
   992     }
   994     #   check for constant "root" in %attr
   995     my $done = ''; my $this = ''; my $todo = $spec;
   996     while ($todo =~ m/%attr\(\s*(\d+)\s*,\s*root\s*,/s) {
   997         $done .= $`; $this = $&; $todo = $';
   998         &lint_warning($file, $done, $this, "found constant \"root\" in %attr (expected variable %{l_susr})");
   999         $done .= $this;
  1002     #   check for valid RPM macro usage
  1003     if ($file !~ m|^(.+/)?openpkg\.spec$|s) {
  1004         my $ok = `$rpm --eval '\%dump' 2>&1 | egrep "^-*[0-9][0-9]*: l_" | sed -e 's;^[^:]*: *\\(l_[a-zA-Z0-9_]*\\).*;\\1;' | sort -u`;
  1005         my @ok = split(/\n/, $ok);
  1006         my $todo = $spec;
  1007         $todo =~ s|^([a-zA-Z][a-zA-Z0-9]+):\s+(.+)$|push(@ok, lc($1)), ''|mge;
  1008         $todo =~ s/^\%(?:define|option)\s+(\S+)\s+.+$/push(@ok, $1), ''/mge;
  1009         my $done = ''; my $this = ''; my $todo = $spec;
  1010         while ($todo =~ m|\%\{([a-zA-Z][a-zA-Z0-9_]+)|s) {
  1011             $done .= $`; $this = $&; $todo = $';
  1012             my $name = $1;
  1013             next if ($name =~ m/^(SOURCE|PATCH)\d*$/ or $name =~ m/^(expand|echo|warn|error)$/);
  1014             if (not grep(/^\Q$name\E$/, @ok)) {
  1015                 &lint_error($file, $done, $this, "unknown macro \"\%{$name}\"");
  1017             $done .= $this;
  1022 ##  _________________________________________________________________
  1023 ##
  1024 ##  CHECK "sources": source file consistency
  1025 ##  _________________________________________________________________
  1026 ##
  1028 sub check_sources {
  1029     my ($file, $spec) = @_;
  1031     ##
  1032     ##  PREPARATION
  1033     ##
  1035     my $D = {};
  1036     my $S = {};
  1037     my $i = 99;
  1039     #   determine defines values
  1040     my $done = ''; my $this = ''; my $todo = $spec;
  1041     while ($todo =~ m/^\%(define|option)\s+(\S+)\s+(.+)\s*$/m) {
  1042         $done .= $`; $this = $&; $todo = $';
  1043         my ($var, $val) = ($2, $3);
  1044         $val =~ s|(\%\{([a-zA-Z_][a-zA-Z0-9_]*)\})|defined($D->{$2}) ? $D->{$2} : $1|sge;
  1045         $D->{$var} = $val;
  1046         $done .= $this;
  1048     $done = ''; $this = ''; $todo = $spec;
  1049     while ($todo =~ m/^([a-zA-Z][a-zA-Z0-9_]+):\s+(.*)$/m) {
  1050         $done .= $`; $this = $&; $todo = $';
  1051         my ($var, $val) = (lc($1), $2);
  1052         $val =~ s|(\%\{([a-zA-Z_][a-zA-Z0-9_]*)\})|defined($D->{$2}) ? $D->{$2} : $1|sge;
  1053         $D->{$var} = $val;
  1054         $done .= $this;
  1057     #   expand define values (allow one level of nesting)
  1058     $spec =~ s|(\%\{([a-zA-Z_][a-zA-Z0-9_]*)\})|defined($D->{$2}) ? $D->{$2} : $1|sge;
  1059     $spec =~ s|(\%\{([a-zA-Z_][a-zA-Z0-9_]*)\})|defined($D->{$2}) ? $D->{$2} : $1|sge;
  1061     #   parse Source# and Patch# headers
  1062     $done = ''; $this = ''; $todo = $spec;
  1063     while ($todo =~ m/^(Source|Patch)(\d+):[ \t]*(.*?)$/m) {
  1064         $done .= $`; $this = $&; $todo = $';
  1065         my ($type, $num, $file) = (lc($1), $2, $3);
  1066         my $url = "";
  1067         if ($file =~ m|^(.+/)([^/]+)$|s) {
  1068             ($url, $file) = ($1, $2);
  1070         if (not defined($S->{"$type$num"})) {
  1071             $S->{"$type$num"} = {
  1072                 -type   => $type,
  1073                 -num    => $num,
  1074                 -url    => $url,
  1075                 -file   => $file,
  1076                 -refhdr => 1,
  1077                 -refcmd => 0,
  1078                 -refmac => 0,
  1079                 -refvar => 0,
  1080             };
  1082         else {
  1083             $S->{"$type$num"}->{-refhdr}++;
  1085         $done .= $this;
  1088     #   parse %setup and %patch commands
  1089     $done = ''; $this = ''; $todo = $spec;
  1090     $todo =~ s/^(\s*\%patch)((?:\s+-\S+|\s+-d\s+\S+)*)((?:\s+\d+)+)\s*$/&expand_patch($1, $2, $3)/mge;
  1091     sub expand_patch {
  1092         my ($cmd, $opts, $nums) = @_;
  1093         my $cmds = '';
  1094         $cmds .= "${cmd}0${opts}\n" if ($opts !~ m|\s+-P|s);
  1095         $opts =~ s|\s+-P||s;
  1096         while ($nums =~ s|^\s+(\d+)||) {
  1097             my $num = $1;
  1098             $cmds .= "${cmd}${num}${opts}\n";
  1100         return $cmds;
  1102     while ($todo =~ m/^\s*\%(setup|patch)(\d+)?(\s+.*)?$/m) {
  1103         $done .= $`; $this = $&; $todo = $';
  1104         my ($type, $num, $opts) = ($1, $2, $3);
  1105         $type = 'source' if ($type eq 'setup');
  1106         my $num_force = '';
  1107         if ($opts =~ m/\s-(?:a|b)\s*(\d+)/s) {
  1108             $num_force = $1;
  1110         if ($num ne '' and $num_force ne '' and $num ne $num_force) {
  1111             &lint_warning($file, $done, $this, "macro and enforced $type number conflict: $num <-> $num_force" .
  1112                         " (expected either just enforced number or make them match)");
  1114         $num = $num_force if ($num_force ne '');
  1115         $num = 0 if ($num eq '');
  1116         next if ($opts =~ m|\s-T|s and $num == 0);
  1117         if (not defined($S->{"$type$num"})) {
  1118             $S->{"$type$num"} = {
  1119                 -type   => $type,
  1120                 -num    => $num,
  1121                 -url    => "",
  1122                 -file   => "",
  1123                 -refhdr => 0,
  1124                 -refcmd => 1,
  1125                 -refmac => 0,
  1126                 -refvar => 0,
  1127             };
  1129         else {
  1130             $S->{"$type$num"}->{-refcmd}++;
  1132         $done .= $this;
  1135     #   parse %{SOURCE#} and %{PATCH#} macros
  1136     $done = ''; $this = ''; $todo = $spec;
  1137     while ($todo =~ m/\%\{(SOURCE|PATCH)(\d+)\}/m) {
  1138         $done .= $`; $this = $&; $todo = $';
  1139         my ($type, $num) = (lc($1), $2);
  1140         if (not defined($S->{"$type$num"})) {
  1141             $S->{"$type$num"} = {
  1142                 -type   => $type,
  1143                 -num    => $num,
  1144                 -url    => "",
  1145                 -file   => "",
  1146                 -refhdr => 0,
  1147                 -refcmd => 0,
  1148                 -refmac => 1,
  1149                 -refvar => 0,
  1150             };
  1152         else {
  1153             $S->{"$type$num"}->{-refmac}++;
  1155         $done .= $this;
  1158     #   parse %{SOURCE ...} and %{PATCH ...} macros
  1159     $done = ''; $this = ''; $todo = $spec;
  1160     while ($todo =~ m/\%\{(SOURCE|PATCH)\s+([^\s}]+)\}/m) {
  1161         $done .= $`; $this = $&; $todo = $';
  1162         my ($type, $file) = (lc($1), $2);
  1163         my $key = "";
  1164         foreach my $src (keys(%{$S})) {
  1165             if ($S->{$src}->{-type} eq $type and $S->{$src}->{-file} eq $file) {
  1166                 $key = $src;
  1169         if ($key eq '') {
  1170             $S->{"$type$i"} = {
  1171                 -type   => $type,
  1172                 -num    => $i,
  1173                 -url    => "",
  1174                 -file   => $file,
  1175                 -refhdr => 0,
  1176                 -refcmd => 0,
  1177                 -refmac => 1,
  1178                 -refvar => 0,
  1179             };
  1180             $i++;
  1182         else {
  1183             $S->{$key}->{-refmac}++;
  1185         $done .= $this;
  1188     #   parse $RPM_SOURCE_DIR based references
  1189     $done = ''; $this = ''; $todo = $spec;
  1190     while ($todo =~ m/\$RPM_SOURCE_DIR\/([^\s;"']+)/m) {
  1191         $done .= $`; $this = $&; $todo = $';
  1192         my ($file) = ($1);
  1193         my $key = "";
  1194         foreach my $src (keys(%{$S})) {
  1195             if ($S->{$src}->{-file} eq $file) {
  1196                 $key = $src;
  1199         if ($key eq '') {
  1200             $S->{"source$i"} = {
  1201                 -type   => "source",
  1202                 -num    => $i,
  1203                 -url    => "",
  1204                 -file   => $file,
  1205                 -refhdr => 0,
  1206                 -refcmd => 0,
  1207                 -refmac => 0,
  1208                 -refvar => 1,
  1209             };
  1210             $i++;
  1212         else {
  1213             $S->{$key}->{-refmac}++;
  1215         $done .= $this;
  1218     ##
  1219     ##  CHECKING
  1220     ##
  1222     #   check reference counts
  1223     foreach my $src (%{$S}) {
  1224         my $s = $S->{$src};
  1225         my $str_hdr = ($s->{-type} eq 'source' ? 'Source' : 'Patch' ) . $s->{-num};
  1226         my $str_cmd = ($s->{-type} eq 'source' ? '%setup' : '%patch') . $s->{-num};
  1227         my $str_mac = ($s->{-type} eq 'source' ? '%{SOURCE ...}' : '%{PATCH ...}');
  1228         my $str_var = "\$RPM_SOURCE_DIR";
  1229         if ($file !~ m/\b(openpkg-.*|openpkg|vim|ncurses|openssh)\.spec$/) {
  1230             if ($s->{-refhdr} > 0) {
  1231                 if ($s->{-refcmd} == 0 and $s->{-refmac} == 0 and $s->{-refvar} == 0) {
  1232                     &lint_error($file, undef, undef, "source \"$str_hdr\" defined (file $s->{-file}), but never used" .
  1233                                 " (expected at least one reference via \"$str_cmd\", \"$str_mac\" or \"$str_var\")");
  1235                 elsif ($s->{-refcmd} > 1) {
  1236                     &lint_error($file, undef, undef, "source \"$str_hdr\" defined (file $s->{-file}), but used multiple times" .
  1237                                 " (expected just one reference via \"$str_cmd\")");
  1240             else {
  1241                 if ($s->{-refcmd}+$s->{-refmac}+$s->{-refvar} > 0) {
  1242                     &lint_error($file, undef, undef, "source \"$str_hdr\" not defined, but used ".
  1243                                 ($s->{-file} ? "(file ".$s->{-file}.")" : "").
  1244                                 " (expected at least one definition via \"$str_hdr\")");
  1250     #   check for existing of sources on filesystem
  1251     my $name = $file;
  1252     $name =~ s|.*/([^/]+)$|$1|s;
  1253     $name =~ s|\.spec$||s;
  1254     my $cvsdir = `$rpm --define 'name $name' --eval '%{_specdir}'`;
  1255     $cvsdir =~ s|\n$||s;
  1256     $cvsdir =~ s|/+$||s;
  1257     my $dstdir = `$rpm --define 'name $name' --eval '%{_sourcedir}'`;
  1258     $dstdir =~ s|\n$||s;
  1259     $dstdir =~ s|/+$||s;
  1260     foreach my $src (%{$S}) {
  1261         my $s = $S->{$src};
  1262         next if (not $s->{-refhdr});
  1263         if ($s->{-url} eq '' and not -f $cvsdir."/".$s->{-file} and not -f $dstdir."/".$s->{-file}) {
  1264             &lint_error($file, undef, undef, "source \"$s->{-file}\" neither found in SRC nor DST locations (you have to provide it)");
  1266         elsif ($s->{-url} eq '' and not -f $cvsdir."/".$s->{-file} and -f $dstdir."/".$s->{-file}) {
  1267             &lint_error($file, undef, undef, "source \"$s->{-file}\" not found in SRC, but in DST (expected it in SRC or want URL)");
  1269         elsif ($s->{-url} ne '' and not -f $cvsdir."/".$s->{-file} and not -f $dstdir."/".$s->{-file}) {
  1270             &lint_error($file, undef, undef, "source \"$s->{-file}\" neither found in SRC nor DST locations (you have to download it)");
  1272         elsif ($s->{-url} ne '' and -f $cvsdir."/".$s->{-file} and not -f $dstdir."/".$s->{-file}) {
  1273             &lint_error($file, undef, undef, "source \"$s->{-file}\" not found in DST, but in SRC (expected it in DST or want URL)");
  1275         #if ($s->{-url} ne '' and $s->{-file} !~ m/\d/) {
  1276         #    &lint_warning($file, undef, undef, "vendor source \"$s->{-file}\" not versioned");
  1277         #}
  1280     #   check for patch file naming
  1281     foreach my $src (%{$S}) {
  1282         my $s = $S->{$src};
  1283         next if (not $s->{-refhdr});
  1284         if (    $s->{-url} eq '' and $s->{-type} eq 'patch'
  1285             and $s->{-file} !~ m|^${name}\.patch(\.[^./]+)?$|) {
  1286             &lint_warning($file, undef, undef, "non-canonical patch filename \"$s->{-file}\"" .
  1287                           " (expected to match \"${name}\\.patch(\\.[^./]+)?\$\")");
  1292 ##  _________________________________________________________________
  1293 ##
  1294 ##  CHECK "digest": digest consistency
  1295 ##  _________________________________________________________________
  1296 ##
  1298 sub check_digest {
  1299     my ($file, $spec) = @_;
  1301     ##
  1302     ##  PREPARATION
  1303     ##
  1305     my $D = {};
  1306     my $S = {};
  1308     #   determine defines values
  1309     my $done = ''; my $this = ''; my $todo = $spec;
  1310     while ($todo =~ m/^\%(define|option)\s+(\S+)\s+(.+)\s*$/m) {
  1311         $done .= $`; $this = $&; $todo = $';
  1312         my ($var, $val) = ($2, $3);
  1313         $val =~ s|(\%\{([a-zA-Z_][a-zA-Z0-9_]*)\})|defined($D->{$2}) ? $D->{$2} : $1|sge;
  1314         $D->{$var} = $val;
  1315         $done .= $this;
  1317     $done = ''; $this = ''; $todo = $spec;
  1318     while ($todo =~ m/^([a-zA-Z][a-zA-Z0-9_]+):\s+(.*)$/m) {
  1319         $done .= $`; $this = $&; $todo = $';
  1320         my ($var, $val) = (lc($1), $2);
  1321         $val =~ s|(\%\{([a-zA-Z_][a-zA-Z0-9_]*)\})|defined($D->{$2}) ? $D->{$2} : $1|sge;
  1322         $D->{$var} = $val;
  1323         $done .= $this;
  1326     #   expand define values (allow one level of nesting)
  1327     $spec =~ s|(\%\{([a-zA-Z_][a-zA-Z0-9_]*)\})|defined($D->{$2}) ? $D->{$2} : $1|sge;
  1328     $spec =~ s|(\%\{([a-zA-Z_][a-zA-Z0-9_]*)\})|defined($D->{$2}) ? $D->{$2} : $1|sge;
  1330     #   determine locations
  1331     my $name = $file;
  1332     $name =~ s|.*/([^/]+)$|$1|s;
  1333     $name =~ s|\.spec$||s;
  1334     my $srcdir = `$rpm --define 'name $name' --eval '%{_specdir}'`;
  1335     $srcdir =~ s|\n$||s;
  1336     $srcdir =~ s|/+$||s;
  1337     my $dstdir = `$rpm --define 'name $name' --eval '%{_sourcedir}'`;
  1338     $dstdir =~ s|\n$||s;
  1339     $dstdir =~ s|/+$||s;
  1341     #   parse Source# and Patch# headers
  1342     $done = ''; $this = ''; $todo = $spec;
  1343     while ($todo =~ m/^(Source|Patch)(\d+):[ \t]*(.*?)$/m) {
  1344         $done .= $`; $this = $&; $todo = $';
  1345         my ($type, $num, $url) = (lc($1), $2, $3);
  1346         my $file;
  1347         if ($url =~ m|^.+/([^/]+)$|s) {
  1348             $file = "$dstdir/$1";
  1350         else {
  1351             $file = "$srcdir/$url";
  1353         if (not defined($S->{"$type$num"})) {
  1354             $S->{"$type$num"} = {
  1355                 -type   => $type,
  1356                 -num    => $num,
  1357                 -url    => $url,
  1358                 -file   => $file
  1359             };
  1361         else {
  1362             $S->{"$type$num"}->{-refhdr}++;
  1364         $done .= $this;
  1367     #   parse BuildPreReq headers
  1368     $done = ''; $this = ''; $todo = $spec;
  1369     while ($todo =~ m/^BuildPreReq:[ \t]+digest\(sha1:\%\{(.+?)\}\)[ \t]+=[ \t]+([0-9a-fA-F]+)$/m) {
  1370         $done .= $`; $this = $&; $todo = $';
  1371         my ($macro, $sha1) = ($1, $2);
  1373         my $url = $S->{lc($macro)}->{-url};
  1374         my $filename = $S->{lc($macro)}->{-file};
  1375         my $basename = $filename;
  1376         $basename =~ s/^.+\/([^\/]+)$/$1/s;
  1378         my $sha1_real = `$openssl sha1 $filename 2>/dev/null`;
  1379         $sha1_real =~ s/^SHA1\s*\(.+?\)\s*=\s*([0-9a-fA-F]+)\s*$/$1/s;
  1381         if (lc($sha1) ne lc($sha1_real)) {
  1382             &lint_error($file, undef, undef, "source \"$basename\" (\%{$macro}) has a digest(sha1) requirement of \"$sha1\" (expected \"$sha1_real\")");
  1385         $done .= $this;

mercurial