Path: blob/main/Tools/scripts/chkversion.pl
16123 views
#!/usr/bin/env perl1#2# Copyright (c) 2004 Oliver Eikemeier. All rights reserved.3#4# Redistribution and use in source and binary forms, with or without5# modification, are permitted provided that the following conditions are6# met:7#8# 1. Redistributions of source code must retain the above copyright notice9# this list of conditions and the following disclaimer.10#11# 2. Redistributions in binary form must reproduce the above copyright12# notice, this list of conditions and the following disclaimer in the13# documentation and/or other materials provided with the distribution.14#15# 3. Neither the name of the author nor the names of its contributors may be16# used to endorse or promote products derived from this software without17# specific prior written permission.18#19# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,20# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY21# AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE22# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,23# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT24# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,25# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY26# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT27# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF28# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.29#30# MAINTAINER= [email protected]31#32# PORTVERSION and PKGORIGIN auditing script33#34# This scripts compares version numbers with previously known ones, and35# checks ports for a correct PKGORIGIN. It is primarily intended to be run36# from a (non-root) cron job.37#38# If you just call it with no preparation, it will compare all port versions39# with their INDEX entries and complain if they have gone backwards. You need40# You need an old INDEX for this, of course. An up-to-date INDEX will accomplish41# nothing.42#43# To use the script as intended, do the following (assuming you want to44# run the script as user `ports'):45#46# install port sysutils/pkg_install-devel (optional)47# mkdir -p /var/db/chkversion48# touch /var/db/chkversion/VERSIONS49# chown -R ports /var/db/chkversion50# and enter something like51#52# BLAME=yes (git specific)53# ALLPORTS=yes54# [email protected]55# [email protected]56# 0 */2 * * * /usr/ports/Tools/scripts/chkversion.pl57#58# into `crontab -u ports -e', or run the script by hand if you can spare the time.59#60# If the environment variable BLAME is set and the ports tree is checked61# out by git, every entry is listed with a record of the last git commit.62#6364use v5.20;65use strict;66use warnings;6768use feature qw(signatures);69no warnings qw(experimental::signatures);7071use Cwd 'abs_path';72use File::Find;73use List::Util qw(first);74use POSIX;7576my $portsdir = $ENV{PORTSDIR} // '/usr/ports';77my $versiondir = $ENV{VERSIONDIR} // '/var/db/chkversion';78my $blame = exists $ENV{BLAME};79my $allports = exists $ENV{ALLPORTS};8081my $watch_re = $ENV{WATCH_REGEX} // '';82my $watchm_re = $ENV{WATCHM_REGEX} // '';83my $returnpath = $ENV{RETURNPATH} // '';84my $h_from = $ENV{HEADER_FROM} // $ENV{USER} . '@' . ($ENV{HOST} // `/bin/hostname`);85my $h_replyto = $ENV{HEADER_REPLYTO} // $h_from;86my $rcpt_watch = $ENV{RCPT_WATCH} // '';87my $rcpt_watchm = $ENV{RCPT_WATCHM} // '';88my $rcpt_orig = $ENV{RCPT_ORIGIN} // '';89my $rcpt_vers = $ENV{RCPT_VERSION} // '';90my $cc_author = exists $ENV{CC_AUTHOR};91my $cc_mntnr = exists $ENV{CC_MAINTAINER};9293my $make = '/usr/bin/make';94my $git = '/usr/local/bin/git';95my $sendmail = '/usr/sbin/sendmail';96my $pkg = first { -x $_ } ($ENV{PKG} // '', '/usr/local/sbin/pkg', '/usr/sbin/pkg');9798$watch_re =~ s/ /|/g;99$watchm_re =~ s/ /|/g;100101-d $portsdir or die "Can't find ports tree at $portsdir.\n";102$portsdir = abs_path($portsdir);103104my $versionfile = "$versiondir/VERSIONS";105my $useindex = !-w $versiondir;106107my $starttime = strftime "%a %b %e %G %k:%M:%S %Z", localtime;108109# @output_lines = readfrom(dir, cmd, arg1, arg2, ...)110sub readfrom($dir, @cmd) {111my $CHILD;112if (!open $CHILD, '-|') {113open STDERR, '>', '/dev/null';114chdir $dir if $dir;115exec @cmd;116die;117}118my @childout = <$CHILD>;119close $CHILD;120121map chomp, @childout;122123return wantarray ? @childout : $childout[0];124}125126for (qw(ARCH OPSYS OSREL OSVERSION UID)) {127my @cachedenv = readfrom($portsdir, $make, "-V$_");128$ENV{$_} = $cachedenv[0];129}130131# These map a 2-dir path (editors/vim) to variables set in132# that port's Makefile133my %pkgname;134my %pkgorigin;135my %masterdir;136my %pkgmntnr;137138sub wanted() {139return unless -d;140141# Skip directories we shouldn't descend into142# if (/^.git$/143if (/^\.git$/144|| $File::Find::name =~ m"^$portsdir/(?:Mk|Templates|Tools|distfiles|packages)$"os145|| $File::Find::name =~ m"^$portsdir/[^/]+/pkg$"os)146{147$File::Find::prune = 1;148}149elsif ($File::Find::name =~ m"^$portsdir/([^/]+/[^/]+)$"os) {150$File::Find::prune = 1;151if (-f "$File::Find::name/Makefile") {152my @makevar = readfrom $File::Find::name, $make, qw(-VPKGORIGIN -VPKGNAME -VMAINTAINER -VMASTERDIR);153154# $1 is the current 2-dir path155if ($#makevar == 3 && $makevar[1]) {156# %pkgorigin is the list of dirs that gets monitored. Only monitor a157# path if it matches the PKGORIGIN.158$pkgorigin{$1} = $makevar[0] if $1 ne $makevar[0];159$pkgname{$1} = $makevar[1];160$pkgmntnr{$1} = $makevar[2];161$masterdir{$1} = $makevar[3];162}163}164}165}166167if ($allports) {168find(\&wanted, $portsdir);169}170else {171my @categories = split ' ' => readfrom($portsdir, $make, '-VSUBDIR');172173for my $category (@categories) {174next unless -f "$portsdir/$category/Makefile";175my @ports = split ' ' => readfrom("$portsdir/$category", $make, '-VSUBDIR');176for (map "$category/$_", @ports) {177next unless -f "$portsdir/$_/Makefile";178179my @makevar = readfrom "$portsdir/$_", $make, qw(-VPKGORIGIN -VPKGNAME -VMAINTAINER -VMASTERDIR);180181next if $#makevar != 3 || ! $makevar[1];182$pkgorigin{$_} = $makevar[0] if $_ ne $makevar[0];183$pkgname{$_} = $makevar[1];184$pkgmntnr{$_} = $makevar[2];185$masterdir{$_} = $makevar[3];186}187}188}189190my %backwards;191my %watched;192my %watchedm;193194if ($useindex) {195my $indexname = readfrom $portsdir, $make, '-VINDEXFILE';196$versionfile = "$portsdir/$indexname";197}198199# Read in the old (expected) values200open my $VERSIONS, '<', $versionfile;201while (<$VERSIONS>) {202chomp;203next if /^(#|$)/;204205# These are the old (expected) values206my ($origin, $version, $maintainer);207208if ($useindex) {209($origin, $version, $maintainer) = (split '|')[1,0,5];210# Only keep the 2-dir path (editors/vim)211$origin =~ s,^.*/([^/]+/[^/]+)/?$,$1,;212}213else {214($origin, $version, $maintainer) = split /\t/;215}216if (defined $pkgname{$origin}) {217my $newversion = $pkgname{$origin};218my $oldversion = $version;219220$newversion =~ s/^.*-//;221$oldversion =~ s/^.*-//;222223# If the two values differ, use `pkg version` to find which one is bigger224my $result = $newversion eq $oldversion ? '='225: readfrom '', $pkg, 'version', '-t', $newversion, $oldversion;226227$watched{$origin} = "$version -> $pkgname{$origin}"228if ($watch_re && $result ne '=' && $origin =~ /^(?:$watch_re)$/o);229230$watchedm{$origin} = "(was <$maintainer>) $version -> $pkgname{$origin}"231if ($watchm_re && $maintainer && $pkgmntnr{$origin}232&& $maintainer ne $pkgmntnr{$origin} && $origin =~ /^(?:$watchm_re)$/o);233234if ($result eq '<') {235$backwards{$origin} = "$pkgname{$origin} < $version";236$pkgname{$origin} = $version;237}238}239elsif ($origin) {240$pkgname{$origin} = $version;241$pkgmntnr{$origin} = $maintainer;242}243}244close $VERSIONS;245246if (!$useindex) {247rename $versionfile, "$versionfile.bak";248249open my $VERSIONS, '>', $versionfile;250for (sort keys %pkgname) {251print $VERSIONS "$_\t$pkgname{$_}\t$pkgmntnr{$_}\n";252}253close $VERSIONS;254}255256my %revision;257258# Parses the $FreeBSD$ line to return revision, date, author259sub parsemakefile($portdir) {260open my $MAKEFILE, '<', "$portdir/Makefile";261while (<$MAKEFILE>) {262if (m/^# \$FreeBSD: [^ ]+ (?<rev>\d{6}) (?<date>\d{4}-\d\d-\d\d) [\d:]+Z (?<author>\w+) \$$/) {263close $MAKEFILE;264return ($+{rev}, $+{date}, $+{author});265}266}267close $MAKEFILE;268}269270sub getauthors($ports) {271my %author;272for my $origin (keys %{$ports}) {273if (!$revision{$origin}) {274my ($r, $d, $a) = parsemakefile "$portsdir/$origin";275push @{$revision{$origin}}, $r;276push @{$author{$origin}}, $a;277278if ($masterdir{$origin} ne "$portsdir/$origin") {279($r, $d, $a) = parsemakefile $masterdir{$origin};280push @{$revision{$origin}}, $r;281push @{$author{$origin}}, $a;282}283}284285}286287return %author;288}289290# Gets the Makefile log starting from the last known rev for a port291sub printlog($fh, $portdir, $rev) {292if ($blame && -d "$portsdir/.git") {293my @log = readfrom $portdir, $git, 'log', "${rev}^..HEAD", 'Makefile';294print $fh " | $_\n" for @log;295}296}297298# Git version:299# sub printlog($fh, $portdir, $rev) {300# }301302sub blame($fh, $ports) {303if (%{$ports}) {304for my $origin (sort keys %{$ports}) {305print $fh "- *$origin* <$pkgmntnr{$origin}>: $ports->{$origin}\n";306printlog $fh, "$portsdir/$origin", $revision{$origin}[0];307if ($masterdir{$origin} ne "$portsdir/$origin") {308my $master = $masterdir{$origin};309$master =~ s|^$portsdir/||o;310while ($master =~ s!(^|/)[^/]+/\.\.(?:/|$)!$1!) {}311print $fh " (master: $master)\n";312printlog $fh, $masterdir{$origin}, $revision{$origin}[1];313}314print $fh "\n";315}316print $fh "\n";317}318}319320sub template($from, $rcpt, $replyto, $starttime, $ports) {321my $portlist = join ', ' => sort keys %{$ports};322substr($portlist, 32) = '...'323if length $portlist > 35;324325my %cclist;326my %author = getauthors $ports;327328if ($cc_author) {329for (map @{$author{$_} ? $author{$_} : []}, keys %{$ports}) {330$cclist{"$_\@FreeBSD.org"} = 1331if $_;332}333}334if ($cc_mntnr) {335for (map $pkgmntnr{$_}, keys %{$ports}) {336$cclist{$_} = 1337if $_;338}339}340my $cc = join ', ' => sort keys %cclist;341342my $header = '';343while (<main::DATA>) {344last if /^\.\n?$/;345$header .= $_346=~ s/%%FROM%%/$from/ogr347=~ s/%%RCPT%%/$rcpt/ogr348=~ s/%%CC%%/$cc/ogr349=~ s/%%REPLYTO%%/$replyto/ogr350=~ s/%%SUBJECT%%/$portlist/ogr351=~ s/%%STARTTIME%%/$starttime/ogr;352}353return $header;354}355356sub mail($template, $rcpt, $ports) {357if (%{$ports}) {358# If the RCPT_* variables are empty, just print the mail to STDOUT359if ($rcpt) {360my $MAIL;361if (!open $MAIL, '|-') {362exec $sendmail, qw(-oi -t -f), $returnpath;363die;364}365print $MAIL $template;366blame $MAIL, $ports;367close $MAIL;368} else {369$template =~ s/^.*?\n\n//os;370print $template;371blame *STDOUT, $ports;372}373}374}375376my $tmpl;377378$tmpl = template $h_from, $rcpt_orig, $h_replyto, $starttime, \%pkgorigin;379mail $tmpl, $rcpt_orig, \%pkgorigin;380381$tmpl = template $h_from, $rcpt_vers, $h_replyto, $starttime, \%backwards;382mail $tmpl, $rcpt_vers, \%backwards;383384$tmpl = template $h_from, $rcpt_watch, $h_replyto, $starttime, \%watched;385mail $tmpl, $rcpt_watch, \%watched;386387$tmpl = template $h_from, $rcpt_watch, $h_replyto, $starttime, \%watchedm;388mail $tmpl, $rcpt_watchm, \%watchedm;389390exit((%pkgorigin || %backwards) ? 1 : 0);391392__END__393From: %%FROM%%394To: %%RCPT%%395CC: %%CC%%396Reply-To: %%REPLYTO%%397Subject: Ports with a broken PKGORIGIN: %%SUBJECT%%398X-FreeBSD-Chkversion: PKGORIGIN399400** The following ports have an incorrect PKGORIGIN **401402PKGORIGIN connects packaged or installed ports to the directory they403originated from. This is essential for tools like pkg_version or404portupgrade to work correctly. Wrong PKGORIGINs are often caused by a405wrong order of CATEGORIES after a repocopy.406407Please fix any errors as soon as possible.408409The ports tree was updated at %%STARTTIME%%.410411.412From: %%FROM%%413To: %%RCPT%%414CC: %%CC%%415Reply-To: %%REPLYTO%%416Subject: Ports with version numbers going backwards: %%SUBJECT%%417X-FreeBSD-Chkversion: backwards418419** The following ports have a version number that sorts before a previous one **420421For many package tools to work correctly, it is of utmost importance that422version numbers of a port form a monotonic increasing sequence over time.423Refer to the FreeBSD Porter's Handbook, 'Package Naming Conventions' for424more information. Tools that won't work include pkg_version, portupgrade425and portaudit. A common error is an accidental deletion of PORTEPOCH.426427Please fix any errors as soon as possible.428429The ports tree was updated at %%STARTTIME%%.430431.432From: %%FROM%%433To: %%RCPT%%434Reply-To: %%REPLYTO%%435Subject: Version changes in your watched ports: %%SUBJECT%%436X-FreeBSD-Chkversion: vwatch437438** The following ports have changed version numbers **439440You have requested to be notified of version changes in the following441ports:442443The ports tree was updated at %%STARTTIME%%.444445.446From: %%FROM%%447To: %%RCPT%%448Reply-To: %%REPLYTO%%449Subject: Maintainer changes in your watched ports: %%SUBJECT%%450X-FreeBSD-Chkversion: mwatch451452** The following ports have changed maintainers **453454You have requested to be notified of maintainer changes in the following455ports:456457The ports tree was updated at %%STARTTIME%%.458459.460461462