123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404 |
- #! /usr/bin/perl
- # $OpenBSD: update-patches,v 1.16 2017/05/25 23:15:41 espie Exp $
- # Copyright (c) 2017
- # Marc Espie. All rights reserved.
- # Redistribution and use in source and binary forms, with or without
- # modification, are permitted provided that the following conditions
- # are met:
- # 1. Redistributions of code must retain the above copyright
- # notice, this list of conditions and the following disclaimer.
- # 2. Neither the name of OpenBSD nor the names of its contributors
- # may be used to endorse or promote products derived from this software
- # without specific prior written permission.
- #
- # THIS SOFTWARE IS PROVIDED BY ITS AUTHOR AND THE OpenBSD project ``AS IS'' AND
- # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- # ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
- # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
- # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
- # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
- # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
- # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- # SUCH DAMAGE.
- use File::Find;
- use strict;
- use warnings;
- use feature qw(say);
- # our "normal" output is STDERR
- open my $oldout, '>&STDOUT';
- open STDOUT, '>&STDERR';
- # grab env stuff
- my ($distorig, $patchorig, $wrkdist, $patchdir, $patch_list) =
- ($ENV{DISTORIG}, $ENV{PATCHORIG}, $ENV{WRKDIST}, $ENV{PATCHDIR},
- $ENV{PATCH_LIST});
- if ($patchorig ne '.orig') {
- say "PATCHORIG=$patchorig";
- }
- my $force = defined($ENV{FORCE_REGEN});
- my $verbose = defined($ENV{PATCH_VERBOSE});
- # protect against dirty stuff
- $wrkdist =~ s/\/$//g;
- my @diff_args;
- # XXX more processing maybe ?
- if (defined $ENV{DIFF_ARGS}) {
- push(@diff_args, split(/\s+/, $ENV{DIFF_ARGS}));
- }
- if (!-d $wrkdist) {
- say "WRKDIST=$wrkdist is not a directory";
- exit 1;
- }
- my ($actual, $saved, $done, $nochange);
- my @edit;
- my $kw_re = qr{\$(
- Author|CVSHeader|Date|Header|Id|Name|Locker|Log|
- RCSFile|Revision|Source|State|OpenBSD
- )\b.*\$}x;
- sub fuzz_chunk
- {
- my $chunk = shift;
- return 0 if @{$chunk->{lines}} < 4;
- my $zap = 0;
- my $fuzzed = 0;
- if ($chunk->{lines}[0] =~ m/^\s/ &&
- $chunk->{lines}[0] =~ m/$kw_re/) {
- $zap = 1;
- }
- if ($chunk->{lines}[0] =~ m/^\s/ &&
- $chunk->{lines}[1] =~ m/^\s/ &&
- $chunk->{lines}[1] =~ m/$kw_re/) {
- $zap = 2;
- }
- while ($zap) {
- shift @{$chunk->{lines}};
- $chunk->{oldstart}++;
- $chunk->{newstart}++;
- $chunk->{oldplus}--;
- $chunk->{newplus}--;
- $zap--;
- $fuzzed = 1;
- }
- if ($chunk->{lines}[-1] =~ m/^\s/ &&
- $chunk->{lines}[-1] =~ m/$kw_re/) {
- $zap=1;
- }
- if ($chunk->{lines}[-1] =~ m/^\s/ &&
- $chunk->{lines}[-2] =~ m/^\s/ &&
- $chunk->{lines}[-2] =~ m/$kw_re/) {
- $zap=2;
- }
- while ($zap) {
- pop @{$chunk->{lines}};
- $chunk->{oldplus}--;
- $chunk->{newplus}--;
- $zap--;
- $fuzzed = 1;
- }
- return $fuzzed;
- }
- sub may_fuzz_patch
- {
- my ($stem, $list) = @_;
- my $try_fuzz = 0;
- for my $l (@$list) {
- if ($l =~ m/$kw_re/) {
- $try_fuzz = 1;
- last;
- }
- }
- return unless $try_fuzz;
- my @lines = @$list;
- if (@lines < 2) {
- return;
- }
- # extract the header
- my $h1 = shift @lines;
- my $h2 = shift @lines;
- # cut up the patch
- my $patch = [];
- my $chunk;
- my $fuzzed = 0;
- while (@lines > 0) {
- my $l = shift @lines;
- if ($l =~ m/^\@\@\s+\-(\d+)\,(\d+)\s+\+(\d+)\,(\d+)\s+\@\@$/) {
- if (defined $chunk) {
- if ($chunk->{fuzzable} && fuzz_chunk($chunk)) {
- $fuzzed = 1;
- }
- push(@$patch, $chunk);
- }
- $chunk = {oldstart => $1, oldplus => $2,
- newstart => $3, newplus => $4};
- } else {
- return if !defined $chunk;
- if ($l =~ m/$kw_re/) {
- $chunk->{fuzzable} = 1;
- }
- push(@{$chunk->{lines}}, $l);
- }
- }
- if (defined $chunk) {
- if ($chunk->{fuzzable} && fuzz_chunk($chunk)) {
- $fuzzed = 1;
- }
- push(@$patch, $chunk);
- }
- return unless $fuzzed;
- say "*** Patch for $stem fuzzed because of CVS keywords" if $verbose;
- @$list = ($h1, $h2);
- for my $chunk (@{$patch}) {
- push(@$list, '@@ -'.$chunk->{oldstart}.','.$chunk->{oldplus}.
- ' +'.$chunk->{newstart}.','.$chunk->{newplus}.
- ' @@'."\n");
- push(@$list, @{$chunk->{lines}});
- }
- }
- sub create_patch
- {
- my ($src, $dst, $stem) = @_;
- say "Processing $stem" if $verbose;
- open(my $file, "-|", "diff", "-u", "-p", "-a", @diff_args, "-L",
- "$stem.orig", "-L", $stem, "--", $src, $dst) or die;
- my @lines = <$file>;
- close $file;
- my $comment = "!OpenBSD!\n";
- $comment =~ tr/!/$/;
- may_fuzz_patch($stem, \@lines);
- return {stem => $stem, patch => \@lines,
- filename => patch_name($stem),
- comment => [$comment, "\n"] };
- }
- sub parse_existing_patch
- {
- my $filename = shift;
- open (my $f, '<', $filename) or die;
- my (@comment, $src, @patch);
- while (<$f>) {
- if (m/^Index:\s+(\S.*)/) {
- $src = $1;
- while (<$f>) {
- push(@patch, $_);
- }
- last;
- }
- # XXX have to do *two* matches so that $1 is okay
- # otherwise if $patchorig = 'sthg.orig' this will fail
- if (m/^\-\-\-\s+(\S.*)\Q$patchorig\E/ ||
- m/^\-\-\-\s+(\S.*)\.orig/) {
- push(@patch, $_);
- $src = $1;
- while (<$f>) {
- push(@patch, $_);
- }
- last;
- }
- push(@comment, $_);
- }
- return {stem => $src, filename => $filename,
- comment => \@comment, patch => \@patch};
- }
- sub write_patch
- {
- my $p = shift;
- if (-f $p->{filename}) {
- rename $p->{filename}, $p->{filename}.".orig" or die;
- }
- open(my $f, '>', $p->{filename}) or die;
- for my $l (@{$p->{comment}}) {
- print $f $l;
- }
- if (defined $p->{stem}) {
- print $f "Index: $p->{stem}\n";
- }
- for my $l (@{$p->{patch}}) {
- print $f $l;
- }
- close $f or die;
- }
- sub patch_name
- {
- my $arg = shift;
- $arg =~ s/[\s\/\.]/_/g;
- return "patch-$arg";
- }
- sub description
- {
- my $p = shift;
- if ($p->{filename} ne patch_name($p->{stem})) {
- return "$p->{filename} for $p->{stem}";
- } else {
- return "for $p->{stem}";
- }
- }
- sub patches_differ
- {
- my ($a, $b) = @_;
- if (@{$a->{patch}} != @{$b->{patch}}) {
- return 1;
- }
- my @m = @{$b->{patch}};
- for my $l (@{$a->{patch}}) {
- my $m = shift @m;
- next if $l =~ m/^(\-\-\-|\+\+\+)\s+\Q$a->{stem}\E/;
- return 1 if $l ne $m;
- }
- return 0;
- }
- sub identical_msg
- {
- my $name = shift;
- return "$name and $name$distorig are identical";
- }
- # figure out which files to work with
- find({wanted =>
- sub {
- return if -l $_;
- return unless -f _;
- return unless m/\Q$patchorig\E$/;
- return if $_ eq 'Oops.rej.orig';
- return if m/\Q$distorig\E$/;
- # avoid double reporting patches
- my $src = $File::Find::name;
- my $dst = $src;
- $dst =~ s/\Q$patchorig\E$//;
- # don't double-report
- return if $dst =~ m/^(.*)\.beforesubst$/ && -f $1.$patchorig;
- my $stem = $dst;
- $stem =~ s/^\Q$wrkdist\E\///;
- my $attach = '';
- if (-f "$dst.beforesubst") {
- $dst = "$dst.beforesubst";
- $attach = '.beforesubst';
- } elsif (!-f $dst) {
- say "$stem not found";
- return;
- }
- require File::Compare;
- if (File::Compare::compare($src, $dst) == 0) {
- if ($verbose) {
- say identical_msg($stem);
- } else {
- $nochange->{$stem} = 1;
- }
- return;
- }
- my $p = create_patch($src, $dst, $stem);
- $actual->{$p->{stem}} = $p;
- }, follow => 1, follow_skip => 2 }, $wrkdist);
- # do we have patches ?
- if (keys %$actual) {
- unless (-d $patchdir) {
- require File::Path;
- File::Path::make_path($patchdir) or die;
- }
- }
- if (chdir($patchdir)) {
- # figure out which patch is which
- for my $i (glob $patch_list) {
- next unless -f $i;
- next if $i =~ m/(\.orig|\.rej|\~)$/;
- $done->{$i} = 1;
- my $parsed = parse_existing_patch($i);
- if (!defined $parsed->{stem}) {
- say "*** File $i is not a proper patch";
- $parsed->{stem} = $i;
- }
- $saved->{$parsed->{stem}} = $parsed;
- }
- }
- # handle patches
- for my $k (sort keys %$actual) {
- my $p = $actual->{"$k"};
- # is there already a patch ? we need to compare
- if (exists $saved->{$k}) {
- my $o = $saved->{$k};
- my $differ = patches_differ($o, $p);
- $o->{accounted} = 1;
- next unless $differ || $force;
- $o->{patch} = $p->{patch};
- write_patch($o);
- next unless $differ;
- say "Patch ", description($o), " updated";
- system {"diff"} ('diff', '-u', @diff_args, '--',
- $o->{filename}.".orig", $o->{filename}) if $verbose;
- push(@edit, $o->{filename});
- } else {
- say "New patch ", description($p);
- write_patch($p);
- # register it as known so we don't reparse
- $saved->{$p->{stem}} = $p;
- $done->{$p->{filename}} = 1;
- $p->{accounted} = 1;
- push(@edit, $p->{filename});
- }
- }
- # parse supplementary files
- if (chdir($patchdir)) {
- for my $i (glob '*') {
- next unless -f $i;
- next if $i =~ m/(\.orig|\.rej|\~)$/;
- next if $done->{$i};
- my $parsed = parse_existing_patch($i);
- $parsed->{stem} //= $i;
- $saved->{$parsed->{stem}} = $parsed;
- }
- }
- #for my $k (sort {$a->{filename} cmp $b->{filename}} keys %$old) {
- for my $k (sort keys %$saved) {
- my $p = $saved->{"$k"};
- if (!$p->{accounted}) {
- if ($nochange->{$p->{stem}}) {
- say identical_msg($p->{stem});
- }
- say "*** Patch ", description($p), " not accounted for";
- }
- my ($warned_newline, $warned_keyword) = (0, 0);
- for my $l (@{$p->{patch}}) {
- if ($l =~ m/^\\ No newline at end of file/) {
- say "*** Patch ", description($p), " misses newline at end of file"
- unless $warned_newline;
- $warned_newline = 1;
- } elsif ($l =~ m/$kw_re/) {
- say "*** Patch ", description($p), " contains CVS keyword"
- unless $warned_keyword;
- $warned_keyword = 1;
- }
- }
- }
- say $oldout join(' ', @edit);
|