File Coverage

blib/script/applypatch
Criterion Covered Total %
statement 138 229 60.2
branch 86 206 41.7
condition 13 46 28.2
subroutine 14 18 77.7
pod n/a
total 251 499 50.3


line stmt bran cond sub pod time code
1             #!/usr/local/bin/perl -w
2             # applypatch -- apply a 'makepatch' generated patch kit.
3             # Author : Johan Vromans
4             # Created On : Sat Nov 14 14:34:28 1998
5             # Last Modified By: Johan Vromans
6             # Last Modified On: Fri Oct 26 21:52:01 2012
7             # Update Count : 149
8             # Status : Released
9              
10 1     1   1201 use strict;
  1         2  
  1         50  
11 1     1   7 use Getopt::Long 2.00;
  1         22  
  1         50  
12 1     1   331 use File::Basename;
  1         10  
  1         142  
13 1     1   6 use File::Spec;
  1         2  
  1         33  
14 1     1   5 use IO::File;
  1         2  
  1         238  
15 1     1   6 use Text::ParseWords;
  1         2  
  1         3517  
16              
17             ################ Common stuff ################
18              
19             my $my_package = 'Sciurix';
20             my $my_name = "applypatch";
21             my $my_version = "2.05";
22             my $data_version = '1.0';
23              
24             ################ Globals ################
25              
26             ## Options and defaults.
27              
28             my $dir; # source directory
29             my $check = 0; # check only
30             my $retain = 0; # retain .orig files
31             my $patch = 'patch -p0 -N'; # patch command
32             my $verbose = 0; # verbose processing
33             my $force = 0; # allow continuation after trunc/corruption
34              
35             # Development options (not shown with -help).
36             my $trace = 0; # trace (show process)
37             my $test = 0; # test (no actual processing)
38             my $debug = 0; # extensive debugging info
39              
40             ## Misc
41              
42             my $applypatch = 0; # it's for us
43             my $timestamp; # create date/time of patch kit
44             my @workq = (); # work queue
45              
46             ## Subroutine prototypes
47              
48             sub app_options ();
49             sub app_usage ($);
50             sub copy_input ();
51             sub execute_patch ();
52             sub post_patch ();
53             sub pre_patch ();
54             sub verify_files ();
55              
56             ################ Program parameters ################
57              
58             app_options();
59             $trace ||= $debug;
60             $verbose ||= $trace;
61              
62             ################ Presets ################
63              
64             $patch .= " -s" unless $verbose;
65             my $tmpfile = IO::File->new_tmpfile;
66              
67             ################ The Process ################
68              
69             # Validate input and copy to temp file.
70             copy_input ();
71              
72             # Change dir if requested.
73             (defined $dir) && (chdir ($dir) || die ("Cannot change to $dir: $!\n"));
74              
75             # Verify that we are in the right place.
76             verify_files ();
77              
78             # Exit if just checking.
79             die ("Okay\n") if $test && $check;
80             exit (0) if $check;
81              
82             # Pre patch: create directories and files.
83             pre_patch ();
84              
85             # Run the patch program.
86             execute_patch ();
87              
88             # Post patch: adjust timestamps, remove obsolete files and directories.
89             post_patch ();
90              
91             die ("Okay\n") if $test;
92             exit (0);
93              
94             ################ Subroutines ################
95              
96             sub copy_input () {
97              
98 1     1   2 my $lines = 0; # checksum: #lines
99 1         3 my $bytes = 0; # checksum: #bytes
100 1         1 my $sum = 0; # checksum: system V sum
101 1         3 my $all_lines = 0; # overall checksum: #lines
102 1         2 my $all_bytes = 0; # overall checksum: #bytes
103 1         1 my $all_sum = 0; # overall checksum: system V sum
104 1         2 my $patchdata = 0; # saw patch data
105 1         2 my $pos = 0; # start of patch data
106 1         1 my $endkit = 0; # saw end of kit
107 1         2 my $fail = 0; # failed
108 1         1 my $patch_checksum_okay = 0;# checksum for the patch was okay
109              
110 1 50       4 print STDERR ("Validate input.\n") if $verbose;
111              
112 1 50       2 @ARGV = "-" if !@ARGV;
113 1         2 for my $file (@ARGV) {
114 1         6 my $argv = new IO::File;
115 1 50       75 open($argv, $file) or die "Can't open $file: $!";
116 1         5 binmode($argv);
117 1         31 while ( <$argv> ) {
118 82         102 chomp;
119 82 100       308 if ( /^#### Patch data follows ####/ ) {
    100          
    100          
    100          
    100          
    100          
    100          
    100          
120 1 50       3 print STDERR (": $_\n") if $trace;
121 1         3 $patchdata |= 1; # bit 0 means: start seen
122 1         14 $pos = $tmpfile->getpos;
123 1         2 $lines = $bytes = $sum = 0;
124             }
125             elsif ( /^#### End of Patch data ####/ ) {
126 1 50       3 print STDERR (": $_\n") if $trace;
127 1         1 $patchdata |= 2; # bit 1 means: end seen
128             }
129             elsif ( /^#### ApplyPatch data follows ####/ ) {
130 1 50       4 print STDERR (": $_\n") if $trace;
131 1         1 $applypatch |= 1;
132             }
133             elsif ( /^#### End of ApplyPatch data ####/ ) {
134 1 50       18 print STDERR (": $_\n") if $trace;
135 1         2 $applypatch |= 2;
136             }
137             elsif ( /^#### End of Patch kit (\[created: ([^\]]+)\] )?####/ ) {
138 1 50       3 print STDERR (": $_\n") if $trace;
139 1         2 $endkit = 1;
140 1 50 33     16 if ( defined $timestamp && defined $2 && $2 ne $timestamp ) {
      33        
141 0         0 warn ("Timestamp mismatch ",
142             "in \"#### End of Patch kit\" line.\n",
143             " expecting \"$timestamp\", got \"$2\".\n");
144 0         0 $fail = 1;
145             }
146             }
147             elsif ( /^#### Patch checksum: (\d+) (\d+) (\d+) ####/ ) {
148             # Checksum for patch data only.
149             # This _MUST_ preceed the overall checksum.
150 1 50       72 print STDERR (": $_\n") if $trace;
151 1         3 $patch_checksum_okay = 1;
152 1 50       4 if ( $1 != $lines ) {
153 0         0 warn ("Linecount error: expecting $1, got $lines.\n");
154 0         0 $fail = 1;
155 0         0 $patch_checksum_okay = 0;
156             }
157 1 50       43 if ( $2 != $bytes ) {
158 0         0 warn ("Bytecount error: expecting $2, got $bytes.\n");
159 0         0 $fail = 1;
160 0         0 $patch_checksum_okay = 0;
161             }
162 1 50       6 if ( $3 != $sum ) {
163 0         0 warn ("Checksum error: expecting $3, got $sum.\n");
164 0         0 $fail = 1;
165 0         0 $patch_checksum_okay = 0;
166             }
167             }
168             elsif ( /^#### Checksum: (\d+) (\d+) (\d+) ####/ ) {
169 1 50       3 print STDERR (": $_\n") if $trace;
170 1 50       3 if ( $patch_checksum_okay ) {
171 1 50 33     8 warn ("Warning: Overall linecount mismatch: ".
172             "expecting $1, got $all_lines.\n")
173             unless $1 == $all_lines || !$verbose;
174 1 50 33     8 warn ("Warning: Overall bytecount mismatch: ".
175             "expecting $2, got $all_bytes.\n")
176             unless $2 == $all_bytes || !$verbose;
177 1 50 33     6 warn ("Warning: Overall checksum mismatch: ".
178             "expecting $3, got $all_sum.\n")
179             unless $3 == $all_sum || !$verbose;
180             }
181             else {
182 0 0       0 if ( $1 != $all_lines ) {
183 0         0 warn ("Overall linecount error: ".
184             "expecting $1, got $all_lines.\n");
185 0         0 $fail = 1;
186             }
187 0 0       0 if ( $2 != $all_bytes ) {
188 0         0 warn ("Overall bytecount error: ".
189             "expecting $2, got $all_bytes.\n");
190 0         0 $fail = 1;
191             }
192 0 0       0 if ( $3 != $all_sum ) {
193 0         0 warn ("Overall checksum error: ".
194             "expecting $3, got $all_sum.\n");
195 0         0 $fail = 1;
196             }
197             }
198             }
199             elsif ( $applypatch == 1 ) {
200 28 100       89 if ( /^# Data version\s*:\s*(\d+\.\d+)$/ ) {
    100          
    100          
201 1 50       10 print STDERR (": $_\n") if $trace;
202 1 50       9 if ( $1 > $data_version ) {
203 0         0 warn ("This program is not capable of handling ",
204             "this input data.\n",
205             "Please upgrade to a newer version.\n");
206 0         0 $fail = 1;
207             }
208             }
209             elsif ( /^# Date generated\s*:\s+(.*)$/ ) {
210 1         3 $timestamp = $1;
211             }
212             elsif ( /^# (\S) (.*)$/ ) {
213 2         16 push (@workq, [ $1, shellwords ($2) ]);
214             }
215             }
216             }
217             continue {
218             # Calculate checksum.
219 82         461 $lines++;
220 82         72 $all_lines++;
221 82         91 $_ .= "\n";
222 82         92 $bytes += length ($_);
223 82         78 $all_bytes += length ($_);
224             # System V 'sum' checksum
225 82         131 $sum = ($sum + unpack ("%16C*", $_)) % 65535;
226 82         106 $all_sum = ($all_sum + unpack ("%16C*", $_)) % 65535;
227              
228             # Copy the line to the temp file.
229 82         207 print $tmpfile ($_);
230             }
231 1         14 close($argv);
232             }
233              
234             # If we saw an ApplyPatch data section, it must be reliable.
235 1 50       6 if ( $applypatch == 1 ) {
    50          
236 0         0 warn ("ApplyPatch data section not properly terminated.\n");
237 0         0 $fail = 1;
238             }
239             elsif ( $applypatch == 2 ) {
240 0         0 warn ("ApplyPatch data section not reliable.\n");
241 0         0 $fail = 1;
242             }
243              
244 1 50       3 if ( $applypatch ) {
245             # If we saw a Patch data section, it must be reliable.
246 1 50       5 if ( $patchdata == 0 ) {
    50          
    50          
247 0         0 warn ("Patch data section not delimited.\n");
248 0         0 $fail = 1;
249             }
250             elsif ( $patchdata == 1 ) {
251 0         0 warn ("Patch data section not properly terminated.\n");
252 0         0 $fail = 1;
253             }
254             elsif ( $patchdata == 2 ) {
255 0         0 warn ("Patch data section not reliable.\n");
256 0         0 $fail = 1;
257             }
258              
259 1 50       3 if ($endkit == 0 ) {
260 0         0 warn ("Missing \"#### End of Patch kit\" line.\n");
261 0         0 $fail = 1;
262             }
263             }
264              
265 1 50       2 if ( $fail ) {
266 0 0       0 if ( $force ) {
267 0         0 warn ("WARNING: Verification of patch kit failed, ",
268             "continuing anyway.\n");
269             }
270             else {
271 0         0 die ("Verification of patch kit failed, aborting.\n",
272             "Use \"--force\" to override this.\n");
273             }
274             }
275              
276 1 0       2 print STDERR ($applypatch == 3 ? "Apply" : "",
    50          
277             "Patch kit apparently okay.\n") if $verbose;
278              
279             # Reset file to start of patch data.
280 1         51 $tmpfile->setpos ($pos);
281             }
282              
283             sub verify_files () {
284              
285 1     1   1 my $fail = 0;
286              
287 1 50       2 print STDERR ("Verify source directory.\n") if $verbose;
288              
289 1         4 foreach ( @workq ) {
290 2         15 my ($op, $fn, @args) = @$_;
291              
292 2 50 33     14 if ( $op eq 'c' ) {
    50 33        
    50          
    0          
293 0 0 0     0 if ( -f $fn || -d _ ) {
294 0         0 warn ("Verify error: file $fn must be created, ",
295             "but already exists.\n");
296 0         0 $fail = 1;
297             }
298             }
299             elsif ( $op eq 'C' ) {
300 0 0 0     0 if ( -f $fn || -d _ ) {
301 0         0 warn ("Verify error: directory $fn must be created, ",
302             "but already exists.\n");
303 0         0 $fail = 1;
304             }
305             }
306             elsif ( $op eq 'r' || $op eq 'p' || $op eq 'v' ) {
307 2         27 my $sz = -s $fn;
308 2 50       19 if ( defined $sz ) {
309 2 50       10 if ( $sz != $args[0] ) {
310 0         0 warn ("Verify error: size of $fn should be $args[0], but is ",
311             "$sz.\n");
312 0         0 $fail = 1;
313             }
314             }
315             else {
316 0         0 warn ("Verify error: file $fn is missing.\n");
317 0         0 $fail = 1;
318             }
319             }
320             elsif ( $op eq 'R' ) {
321 0 0       0 unless ( -d $fn ) {
322 0         0 warn ("Verify error: directory $fn must be removed, ",
323             "but does not exist.\n");
324 0         0 $fail = 1;
325             }
326             }
327             }
328              
329 1 50       4 if ( $fail ) {
330 0 0       0 if ( $force ) {
331 0         0 warn ("WARNING: This does not look like expected source ",
332             "directory, continuing anyway.\n");
333             }
334             else {
335 0         0 warn ("Apparently this is not the expected source directory, ",
336             "aborting.\n");
337 0         0 die ("Use \"--force\" to override this.\n");
338             }
339             }
340              
341 1 50       3 print STDERR ("Source directory apparently okay.\n") if $verbose;
342             }
343              
344             sub pre_patch () {
345              
346 1     1   2 foreach ( @workq ) {
347 2         6 my ($op, $fn, $size, $mtime, $mode) = @$_;
348              
349 2 50       5 if ( $op eq 'C' ) {
350 0         0 $mode = oct($mode) & 0777;
351 0 0       0 $mode = 0777 unless $mode; # sanity
352 0 0       0 printf STDERR ("+ mkpath $fn 0%o\n", $mode) if $trace;
353 0 0       0 mkdir ($fn, $mode)
354             || die ("Cannot create directory $fn: $!\n");
355             }
356             }
357              
358 1         2 foreach ( @workq ) {
359 2         4 my ($op, $fn, $size, $mtime, $mode) = @$_;
360              
361 2 50       3 if ( $op eq 'c' ) {
362             #$mode = oct($mode) & 0777;
363             #$mode = 0666 unless $mode; # sanity
364 0 0       0 print STDERR ("+ create $fn\n") if $trace;
365 0 0       0 open (F, '>'.$fn)
366             || die ("Cannot create $fn: $!\n");
367 0         0 close (F);
368             #printf STDERR ("+ chmod 0%o $fn\n", $mode) if $trace;
369             #chmod ($mode, $fn)
370             # || warn sprintf ("WARNING: Cannot chmod 0%o $fn: $!\n", $mode);
371             }
372             }
373              
374             }
375              
376              
377             sub _open_patch () {
378              
379 1     1   7 my $p = new IO::File;
380 1 50       36 $p->open("|$patch") || die ("Cannot open pipe to \"$patch\": $!\n");
381 1         2132 binmode($p);
382              
383 1         25 return $p
384             }
385              
386              
387             sub execute_patch () {
388              
389 1     1   2 my $p;
390              
391 1 50       2 print STDERR ("+ $patch\n") if $trace;
392 1 50       2 if ( $applypatch ) {
393 1         2 my $lines = 0;
394 1         13 while ( <$tmpfile> ) {
395 28         32 chomp;
396 28 50       36 print STDERR ("++ ", $_, "\n") if $debug;
397 28 100       37 next if $_ eq "#### Patch data follows ####";
398 27 100       34 last if $_ eq "#### End of Patch data ####";
399 26 100       49 $p = _open_patch() unless $p;
400 26         63 print $p ($_, "\n");
401 26         71 $lines++;
402             }
403 1 50       19 print STDERR ("+ $lines lines sent to \"$patch\"\n") if $trace;
404             }
405             else {
406 0         0 while ( <$tmpfile> ) {
407 0 0       0 $p = _open_patch() unless $p;
408 0         0 print $p ($_)
409             }
410             }
411 1 50 50     33 defined $p and
412             $p->close || die ("Possible problems with \"$patch\", status = $?.\n");
413             }
414              
415             sub set_utime ($$;$) {
416 2     2   5 my ($fn, $mtime, $mode) = @_;
417 2 50       5 $mode = (stat ($fn))[2] unless defined $mode;
418 2 50       60 chmod (0777, $fn)
419             || warn ("WARNING: Cannot utime/chmod a+rwx $fn: $!\n");
420 2 50       6 print STDERR ("+ utime $fn $mtime (".localtime($mtime).")\n") if $trace;
421             # Set times. Ignore errors for directories since some systems
422             # (like MSWin32) do not allow directories to be stamped.
423 2 50 33     46 utime ($mtime, $mtime, $fn)
424             || -d $fn || warn ("WARNING: utime($mtime,$fn): $!\n");
425 2 50       10 printf STDERR ("+ chmod 0%o $fn\n", $mode) if $trace;
426 2 50       28 chmod ($mode, $fn)
427             || warn sprintf ("WARNING: Cannot utime/chmod 0%o $fn: $!\n", $mode);
428             }
429              
430             sub do_unlink ($) {
431 0     0   0 my ($fn) = @_;
432 0         0 my $mode = (stat($fn))[2];
433 0 0       0 chmod (0777, $fn)
434             || warn ("WARNING: Cannot unlink/chmod a+rwx $fn: $!\n");
435 0 0       0 print STDERR ("+ unlink $fn\n") if $verbose;
436 0 0       0 return if unlink ($fn);
437 0         0 warn ("WARNING: Cannot remove $fn: $!\n");
438 0 0       0 chmod ($mode, $fn)
439             || warn sprintf ("WARNING: Cannot unlink/chmod 0%o $fn: $!\n", $mode);
440             }
441              
442             sub do_rmdir ($) {
443 0     0   0 my ($fn) = @_;
444 0         0 my $mode = (stat($fn))[2];
445 0 0       0 chmod (0777, $fn)
446             || warn ("WARNING: Cannot rmdir/chmod a+rwx $fn: $!\n");
447 0 0       0 print STDERR ("+ rmdir $fn\n") if $verbose;
448 0 0       0 return if rmdir ($fn);
449 0         0 warn ("WARNING: Cannot rmdir $fn: $!\n");
450 0 0       0 chmod ($mode, $fn)
451             || warn sprintf ("WARNING: Cannot rmdir/chmod 0%o $fn: $!\n", $mode);
452             }
453              
454             sub post_patch () {
455              
456 1   50 1   26 my $suffix = $ENV{SIMPLE_BACKUP_SUFFIX} || ".orig";
457              
458 1         11 foreach ( @workq ) {
459 2         36 my ($op, $fn, $size, $mtime, $mode) = @$_;
460              
461 2 50 33     34 if ( $op eq 'c' || $op eq 'C' || $op eq 'p' ) {
    0 33        
    0          
462 2 50       10 if ( defined $mode ) {
463 2         11 $mode = oct($mode) & 0777;
464 2 50       10 $mode = 0666 unless $mode; # sanity
465             }
466 2         10 set_utime ($fn, $mtime, $mode);
467 2 50       6 next if $retain;
468 2         4 $fn .= $suffix;
469 2 50       44 if ( -f $fn ) {
470 0         0 do_unlink ($fn);
471             }
472             }
473             elsif ( $op eq 'r' ) {
474 0 0       0 print STDERR ("+ unlink $fn\n") if $trace;
475             # Be forgiving, maybe patch already removed the file.
476 0 0       0 if ( -e $fn ) {
477 0         0 do_unlink ($fn);
478             }
479             else {
480 0         0 warn ("Apparently, $fn has been removed already.\n");
481             }
482             }
483             elsif ( $op eq 'R' ) {
484 0 0       0 print STDERR ("+ rmdir $fn\n") if $trace;
485             # Maybe some future version of patch will take care of directories.
486 0 0       0 if ( -e $fn ) {
487 0         0 do_rmdir ($fn);
488             }
489             else {
490 0         0 warn ("Apparently, $fn has been removed already.\n");
491             }
492             }
493             }
494              
495             }
496              
497             ################ Options and Help ################
498              
499             sub app_options () {
500 1     1   2 my $help = 0; # handled locally
501              
502             # Process options, if any.
503             # Make sure defaults are set before returning!
504 1 50       5 return unless @ARGV > 0;
505             my @opts = ('check' => \$check,
506             'dir|d=s' => \$dir,
507             'retain' => \$retain,
508             'force' => \$force,
509             'verbose' => \$verbose,
510 0     0   0 'quiet' => sub { $verbose = 0; },
511 1         7 'patch=s' => \$patch,
512             'test' => \$test,
513             'trace' => \$trace,
514             'debug' => \$debug,
515             'help' => \$help);
516            
517 1 50 33     13 (!GetOptions (@opts) || $help) && app_usage (2);
518              
519             }
520              
521             sub app_usage ($) {
522 0     0     my ($exit) = @_;
523 0           print STDERR <
524             Usage: $0 [options] patch-kit
525              
526             -help this message
527             -dir XXX change to this directory before executing
528             -check check, but does not execute
529             -retain retain .orig file after patching
530             -force continue after verification failures
531             -patch XXX the patch command, default "$patch"
532             -quiet no information
533             -verbose verbose information
534             EndOfUsage
535 0 0 0       exit $exit if defined $exit && $exit != 0;
536             }
537              
538             1;
539              
540             __END__