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.

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

mercurial