Thu, 04 Oct 2012 20:30:05 +0200
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 |