File Coverage

blib/lib/File/Repl.pm
Criterion Covered Total %
statement 232 464 50.0
branch 107 368 29.0
condition 7 69 10.1
subroutine 19 30 63.3
pod 7 10 70.0
total 372 941 39.5


line stmt bran cond sub pod time code
1             # File::Repl
2             #
3             # Version
4             # $Source: C:/src/perl/File/Repl/RCS/Repl.pm $
5             # $Revision: 2.1 $
6             # $State: Exp $
7             #
8             # Start comments/code here - will not be processed into manual pages
9             #
10             # Copyright © Dave Roberts 2000,2001
11             #
12             # Revision history:
13             # $Log: Repl.pm $
14             # Revision 2.1 2015/07/15 20:51:29 Dave.Roberts
15             # added timestamp info for reading directories
16             #
17             # Revision 2.0 2015/07/15 20:00:30 dave
18             # New major version, now with Win32::AdminMisc depandency removed as this
19             # module becomes more difficult to acquire and build for recent Perl releases
20             #
21             # Revision 1.1 2015/07/15 19:58:06 Dave.Roberts
22             # Initial revision
23             #
24             # Revision 1.31 2014/01/25 21:27:59 Dave.Roberts
25             # as advised from CPAN testing modified to include
26             # =encoding utf8
27             # and
28             # escape the < and > characters in the pod with E and
29             # E respectively.
30             #
31             # Revision 1.29 2010/05/04 15:02:05 Dave.Roberts
32             # corrected documentation - layout near Update method was incorrect
33             #
34             # Revision 1.28 2010/04/27 14:55:00 Dave.Roberts
35             # minor code improvements in output messages for the Delete method
36             #
37             # Revision 1.27 2010/04/13 08:36:52 Dave.Roberts
38             # added functionality for testing negative ages. This allows files older than the age
39             # specified to be selected (excluding all files younger)
40             #
41             # Revision 1.26 2010/04/12 16:29:57 Dave.Roberts
42             # added Version method to return the File::Repl version
43             # corrected silly mistake in documentation - in definition of %con hash
44             #
45             # Revision 1.25 2010/04/12 16:04:54 Dave.Roberts
46             # added example script for tombstoning
47             # removed windows linefeed characters from file
48             #
49             # Revision 1.24 2010/04/07 02:00:11 Dave.Roberts
50             # modified code to remove the use of a hash as a reference - this was generating warnings
51             # as this use of a hash has beeen depreciated.
52             #
53             # Revision 1.21 2002/02/07 10:37:39 Dave.Roberts
54             # corrected mode identified for Update method (the check used previously
55             # was invalid), and also synopsis for use of Update method (args incorrectly
56             # ordered)
57             #
58             # Revision 1.20 2002/01/09 12:51:17 Dave.Roberts
59             # corrected errors in tombstoning of directories - subs $del and
60             # $deltree in particular
61             #
62             # Revision 1.19 2001/11/21 21:28:19 Dave.Roberts
63             # resolved error in determining file age, especially when the 'a' file is
64             # missing
65             # evaluated the current time at start (set $runtime), and then removed
66             # many "time" calls
67             #
68             # Revision 1.18 2001/08/22 07:10:41 Dave.Roberts
69             # logic change so that we don't use the Win32::API on win9x machines
70             #
71             # Revision 1.17 2001/08/03 09:38:29 Dave.Roberts
72             # corrected code error (lines 572/3) where $$ was incorrectly used
73             # corrected code error (lines 572/3) where $$ was incorrectly used in truncation code
74             #
75             # Revision 1.16 2001/08/02 22:09:02 Dave.Roberts
76             # corrected code for the Rename routine
77             #
78             # Revision 1.15 2001/07/17 21:05:43 Dave.Roberts
79             # small changes to _arraysort - simplifying code
80             #
81             # Revision 1.14 2001/07/12 21:51:50 jj768
82             # additional documentation - and minor code changes
83             #
84             # Revision 1.13 2001/07/12 15:18:43 Dave.Roberts
85             # code tidy up and reorganisation
86             # fixed logic errors (A>B! mode in Update method was not copying new files from A to B), also for A
87             # removed several local variables and used referred object directly
88             #
89             # Revision 1.12 2001/07/11 10:30:16 Dave.Roberts
90             # resolved various errors introduced in 1.11 - mainly associsated with reference errors
91             # rehacked fc subroutine - to give more logical messages
92             # still in need of more documentation - esp of object reference returned and associated variables
93             #
94             # Revision 1.11 2001/07/06 14:52:53 jj768
95             # double referencing of blessed object removed (from New method) and subsequent
96             # methods updated. Requires Testing.
97             # Update and other methods now return reference to data arrays and hashs evaluated
98             # during method call
99             #
100             # Revision 1.10 2001/07/06 08:23:48 Dave.Roberts
101             # code changes to allow the colume info to be detected correctly using Win32::AdminMisc
102             # when a drive letter is specified (was only working with UNC names)
103             #
104             # Revision 1.9 2001/06/27 13:35:53 Dave.Roberts
105             # minor presentation changes
106             #
107             # Revision 1.8 2001/06/27 12:59:22 jj768
108             # logic to prevent "Use of uninitialized value in pattern match (m//)" errors on use of $vol{FileSystemName}
109             #
110             # Revision 1.6 2001/06/21 12:32:15 jj768
111             # *** empty log message ***
112             #
113             # Revision 1.5 2001/06/20 20:39:21 Dave.Roberts
114             # minor header changes
115             #
116             # Revision 1.4 2001/06/20 19:55:21 jj768
117             # re-built module source files as per perlmodnew manpage
118             #
119             #
120             #******************************************************************************
121              
122             package File::Repl;
123              
124             require 5.005_62;
125 1     1   8945 use strict;
  1         2  
  1         28  
126 1     1   4 use warnings;
  1         2  
  1         32  
127 1     1   4 use Carp;
  1         1  
  1         67  
128 1     1   4 use File::Find;
  1         2  
  1         61  
129 1     1   735 use File::Copy;
  1         5088  
  1         56  
130 1     1   6 use File::Basename;
  1         1  
  1         81  
131 1     1   5 use constant FALSE => 0;
  1         2  
  1         58  
132 1     1   5 use constant TRUE => 1;
  1         1  
  1         43  
133 1     1   5 use constant TIME_ZONE_ID_INVALID => 0xFFFFFFFF;
  1         1  
  1         7320  
134              
135             my($runtime) = time;
136              
137              
138             #**************************************************************
139             # On FAT filesystems, "stat" adds TZ_BIAS to the actual file
140             # times (atime, ctime and mtime) and "utime" subtracts TZ_BIAS
141             # from the supplied parameters before setting file times. To
142             # maintain FAT at UTC time, we need to do the opposite.
143             #
144             # If we don't maintain FAT filesystems at UTC time and the repl
145             # is between FAT and NON-FAT systems, then all files will get
146             # replicated whenever the TZ or Daylight Savings Time changes.
147             #
148             # (NH270301)
149             #
150             my $TZ_BIAS = 0; # global package variable
151             if ($^O eq 'MSWin32') { # is this a win32 system ?
152             if ( eval "use Win32" ) {
153             my($string,$major,$minor,$build,$id) = Win32::GetOSVersion();
154             if ( $id == 2 ) { # Machine is NT (0=Win32s, 1=Win9x etc)
155             eval "use Win32::API";
156             # eval "use Win32::AdminMisc";
157              
158             my $lpTimeZoneInformation = "\0" x 172; # space for struct _TIME_ZONE_INFORMATION
159             my $GetTimeZoneInformation = new Win32::API("kernel32", 'GetTimeZoneInformation', ['P'], 'N');
160             croak "\n ERROR: failed to import GetTimeZoneInformation API function\n" if !$GetTimeZoneInformation;
161             my $ISDST = $GetTimeZoneInformation->Call($lpTimeZoneInformation);
162             croak "\n ERROR: GetTimeZoneInformation returned invalid data: " . Win32::FormatMessage(Win32::GetLastError())
163             if $ISDST == TIME_ZONE_ID_INVALID;
164             my ($Bias,$StandardBias,$DaylightBias) = unpack "l x80 l x80 l", $lpTimeZoneInformation;
165              
166             # $ISDST == 0 - No Daylight Savings in this timezone (no transition dates defined for this tz)
167             # $ISDST == 1 - Standard time
168             # $ISDST == 2 - Daylight Savings time
169              
170             # bias times are returned in minutes - convert to seconds
171             $TZ_BIAS = ($Bias + ($ISDST == 0 ? 0 : ($ISDST == 2 ? $DaylightBias : $StandardBias))) * 60;
172             }
173             }
174             }
175             #**************************************************************
176             require Exporter;
177              
178             our @ISA = qw(Exporter);
179              
180             # Items to export into callers namespace by default. Note: do not export
181             # names by default without a very good reason. Use EXPORT_OK instead.
182             # Do not simply export all your public functions/methods/constants.
183              
184             # This allows declaration use File::Repl ':all';
185             # If you do not need this, moving things directly into @EXPORT or @EXPORT_OK
186             # will save memory.
187             our %EXPORT_TAGS = ( 'all' => [ qw( ) ] );
188             our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
189             our @EXPORT = qw(
190              
191             );
192              
193             our $VERSION = sprintf("%d.%d", q$Revision: 2.1 $ =~ /(\d+)\.(\d+)/);
194              
195             # Preloaded methods go here.
196             #---------------------------------------------------------------------
197             sub New {
198 6     6 1 11513 my $class = shift;
199 6         12 my($conf) = $_[0];
200 6 50       20 croak "\n Usage: File::Repl->New(\$hashref)\n\n" unless (ref($conf) eq "HASH");
201 6         7 my($alist,$blist,$atype,$btype,$key,$xxx,$dira,$dirb,$tmp);
202 6         17 $conf->{dira} =~ s/\\/\//g; # Make dir use forward slash's
203 6         12 $conf->{dirb} =~ s/\\/\//g; # Make dir use forward slash's
204              
205             # To maintain backwards compatibility, check if additional
206             # keys are defined and default to a suitable value (NH271100)
207             my $r_con = {
208             dira => $conf->{dira},
209             dirb => $conf->{dirb},
210             verbose => (defined $conf->{verbose}) ? $conf->{verbose} : 0, # default not verbose
211             agelimit => (defined $conf->{age}) ? $conf->{age} : 0, # default 0 (don't check age)
212             ttl => (defined $conf->{ttl}) ? $conf->{ttl} : 31, # default ttl 31 days
213             nocase => (defined $conf->{nocase}) ? $conf->{nocase} : TRUE, # default nocase TRUE
214             bmark => (defined $conf->{bmark}) ? $conf->{bmark} : FALSE, # default benchmark FALSE
215             recurse => (defined $conf->{recurse}) ? $conf->{recurse} : TRUE, # default recurse TRUE
216 6 100       85 mkdirs => (defined $conf->{mkdirs}) ? $conf->{mkdirs} : FALSE, # default mkdirs FALSE
    100          
    50          
    50          
    50          
    50          
    100          
217             };
218              
219             # Should we continue if dira / dirb dosn't exist ? (NH301200)
220 6 100       19 if ( $r_con->{verbose} >= 3 ) {
221 1         6 printf "\n\nFile:Repl configuration settings:\n";
222 1         5 printf "-------------------------------\n";
223 1         5 foreach $key (keys %$r_con ) {
224 9         64 printf " Key %-10s Value %-30s\n",$key, $r_con->{$key};
225             }
226             }
227              
228             # Build the A list
229 6 50       18 benchmark("init") if $r_con->{bmark};
230 6 50       98 if ( -d $r_con->{dira} ) {
    0          
231 6         165 printf "\treading dira %s\n",$r_con->{dira};
232 6         14 my($start_dir) = time;
233 6 50       14 if ($r_con->{recurse}) {
234             $xxx = sub{
235 56     56   271 ($tmp = $File::Find::name) =~ s/^\Q$r_con->{dira}//; # Remove the start directory portion
236 56 100       2438 ($atype->{$tmp}, $alist->{$tmp}) = (stat($_))[2,9] if $tmp; # Mode is 3rd element, mtime is 10th
237 6         28 };
238 6         358 find(\&$xxx,$r_con->{dira});
239             }else{
240 0 0       0 opendir(DIRA, "$r_con->{dira}") || croak "Can not open $r_con->{dira} directory !!!\n";
241 0         0 while($tmp = readdir(DIRA)) {
242 0         0 $tmp = "/" . $tmp;
243 0 0       0 next if -d $r_con->{dira} . $tmp; # Skip directories
244 0         0 ($atype->{$tmp}, $alist->{$tmp}) = (stat($r_con->{dira} . $tmp))[2,9];
245             }
246 0         0 close DIRA;
247             }
248 6         17 printf "\tcompleted reading dira (%s sec)\n",&elapsed($start_dir);
249             }elsif (!$r_con->{mkdirs}) {
250 0         0 croak "Invalid directory name for dira ($r_con->{dira})\n";
251             }
252 6 50       22 benchmark("build A list") if $r_con->{bmark};
253              
254             # Build the B list
255 6 50       14 benchmark("init") if $r_con->{bmark};
256 6 50       102 if ( $r_con->{dira} eq $r_con->{dirb} ) {
    50          
    0          
257 0         0 $blist = $alist;
258 0         0 $btype = $atype;
259             }elsif ( -d $r_con->{dirb} ) {
260 6         33 printf "\treading dirb %s\n",$r_con->{dirb};
261 6         11 my($start_dir) = time;
262 6 50       12 if ($r_con->{recurse}) {
263             $xxx = sub{
264 26     26   136 ($tmp = $File::Find::name) =~ s/^\Q$r_con->{dirb}//; # Remove the start directory portion
265 26 100       1254 ($btype->{$tmp}, $blist->{$tmp}) = (stat($_))[2,9] if $tmp; # Mode is 3rd element, mtime is 10th
266 6         25 };
267 6         336 find(\&$xxx,$r_con->{dirb});
268             }else{
269 0 0       0 opendir(DIRB, "$r_con->{dirb}") || croak "Can not open $r_con->{dirb} directory !!!\n";
270 0         0 while($tmp = readdir(DIRB)) {
271 0         0 $tmp = "/" . $tmp;
272 0 0       0 next if -d $r_con->{dirb} . $tmp; # Skip directories
273 0         0 ($btype->{$tmp}, $blist->{$tmp}) = (stat($r_con->{dirb} . $tmp))[2,9];
274             }
275 0         0 close DIRB;
276             }
277 6         16 printf "\tcompleted reading dirb (%s sec)\n",&elapsed($start_dir);
278             }elsif (!$r_con->{mkdirs}) {
279 0         0 croak "Invalid directory name for dirb ($r_con->{dirb})\n";
280             }
281 6 50       21 benchmark("build B list") if $r_con->{bmark};
282 6         11 $r_con->{alist} = $alist;
283 6         8 $r_con->{atype} = $atype;
284 6         9 $r_con->{blist} = $blist;
285 6         8 $r_con->{btype} = $btype;
286 6         15 bless $r_con, $class;
287 6         35 return $r_con;
288             }
289             #=====================================================================
290             sub Update {
291 5     5 1 36 return _generic ("Update",@_);
292             }
293             #=====================================================================
294             sub Rename {
295 0     0 1 0 return _generic ("Rename",@_);
296             }
297             #=====================================================================
298             sub Version {
299 1     1 1 27 return $VERSION;
300             }
301             #=====================================================================
302             sub Process {
303 0 0   0 1 0 if ( scalar(@_) eq 3 ) {
    0          
304 0         0 my($r_con,$regex,$sub) =@_;
305 0         0 my($negregex) = '^$'; # Make this impossible to match, nor file or directory
306             # can be of zero length name.
307             }elsif ( scalar(@_) eq 4 ) {
308 0         0 my($r_con,$regex,$negregex,$sub) =@_;
309             }else{
310 0         0 carp ("Try calling the File::Repl->Process method with the right arguments !\n");
311             }
312 0         0 print "The Process method is not implemented\n";
313             }
314             #=====================================================================
315             sub Compress {
316 0 0   0 1 0 if ( scalar(@_) eq 3 ) {
    0          
317 0         0 my($r_con,$regex,$archive) =@_;
318 0         0 my($negregex) = '^$'; # Make this impossible to match, nor file or directory
319             # can be of zero length name.
320             }elsif ( scalar(@_) eq 4 ) {
321 0         0 my($r_con,$regex,$negregex,$mode,$commit) =@_;
322             }else{
323 0         0 carp ("Try calling the File::Repl->Compress method with the right arguments !\n");
324             }
325 0         0 print "The Compress method is not implemented\n";
326             }
327             #=====================================================================
328             sub Delete {
329 0     0 1 0 return _generic ("Delete",@_);
330             }
331             #=====================================================================
332              
333             sub _generic {
334 5     5   10 my ($caller) = shift @_;
335 5         9 my($r_con,$regex,$mode,$commit,$nsub);
336 0         0 my($refa,$refb,$refatype,$refbtype,$agelimit,$verbose);
337 0         0 my($name,$mtime,%mark,$afile,$bfile,$amtime,$bmtime,$fc,$md,$del,$type);
338 0         0 my(@amatch,@bmatch,$benchmark,$tfiles,$common,$aonly,$bonly,$amatch,$bmatch,@temp,%vol);
339 0         0 my($tName,$aName,$bName,$deltree,$truncate,$touch,$mv,$tmp,$atype,$btype);
340 5         7 my ($negregex) = '^$';# Default value - make this impossible to match, neither file nor directory
341 5         6 my $tz_bias_a = 0;
342 5         6 my $tz_bias_b = 0;
343 5         7 my $fudge = 2; # Fudge factor to allow two machines to synch via a removeable drive/disc (A <> DOS <> B)
344              
345 5 50       12 if ($caller eq "Update") {
    0          
    0          
346 5 50       9 if ( scalar(@_) == 4 ) {
    0          
347 5         13 ($r_con,$regex,$mode,$commit) = @_;
348             }elsif ( scalar(@_) == 5 ) {
349 0         0 ($r_con,$regex,$negregex,$mode,$commit) = @_;
350             }else{
351 0         0 carp ("Call the Update method with the right arguments !\n\t\$ref->Update(regex, [noregex,] action, commit)");
352 0         0 print scalar(@_), " Args called ( @_ )\n";
353 0         0 return;
354             }
355 5 50       15 if ( $mode eq "" ) { # Set the default operating mode
356 0         0 $mode = 'a>b';
357 0 0       0 print "using default mode for Update method (a>b)\n" if ($verbose > 1);
358             }
359 5 50       23 if ( $mode !~ /^(A>B!?)|(A<>B)|(A]b)|(a<>b)$/ ) {
360 0         0 carp("Illegal mode used for Update method - legal options are\n\tA>B\tA>B!\tAB\tab\ta<>b\n");
361 0         0 return;
362             }
363             }elsif($caller eq "Delete"){
364 0 0       0 if ( scalar(@_) eq 3 ) {
    0          
365 0         0 ($r_con,$regex,$commit) =@_;
366             }elsif ( scalar(@_) eq 4 ) {
367 0         0 ($r_con,$regex,$negregex,$commit) =@_;
368             }else{
369 0         0 carp ("Call the Delete method with the right arguments !\n\t\$ref->Delete(regex, [noregex], commit)");
370             }
371             }elsif($caller eq "Rename"){
372 0 0       0 if ( scalar(@_) eq 4 ) {
    0          
373 0         0 ($r_con,$regex,$nsub,$commit) =@_;
374             }elsif ( scalar(@_) eq 5 ) {
375 0         0 ($r_con,$regex,$negregex,$nsub,$commit) =@_;
376             }else{
377 0         0 carp ("Call the Rename method with the right arguments !\n\t\$ref->Rename(regex, [noregex], namesub, commit)");
378             }
379             }
380              
381 5         12 my $ttl = $r_con->{ttl} * 86400; # Expiry time for tombstone indicator files in seconds
382 5 50       13 $commit = TRUE unless defined $commit; # Set default commit value
383 5         7 $verbose = $r_con->{verbose};
384 5 100       17 $agelimit = $r_con->{agelimit} ? $r_con->{agelimit} * 86400 : 0; # Determine age limit in seconds
385 5 50       11 $negregex = '^$' unless $negregex; # Ensure no matches if $negregex = ''
386              
387              
388             # Fix for stat/utime on FAT filesystems (NH270301)
389 5 50       11 if ($TZ_BIAS) {
390 0 0 0     0 if ( ( $r_con->{dira} =~ /^([a-z]:)/i ) || # First match a drive letter - ie D:
      0        
391             ( $r_con->{dira} =~ /^([\\\/].\w+[\\\/][a-z0-9\$]+)/i ) || # Else match a share - ie //comp/share or \\comp\share
392             ( Win32::GetCwd() =~ /^([a-z]:)/i ) ) { # Else assume relative path - use CWD
393 0 0       0 $tz_bias_a = $TZ_BIAS if (FsType(1) =~ m/FAT/);
394             }
395 0 0 0     0 if ( ( $r_con->{dirb} =~ /^([a-z]:)/i ) || # First match a drive letter - ie D:
      0        
396             ( $r_con->{dirb} =~ /^([\\\/].\w+[\\\/][a-z0-9\$]+)/i ) || # Else match a share - ie //comp/share or \\comp\share
397             ( Win32::GetCwd() =~ /^([a-z]:)/i ) ) { # Else assume relative path - use CWD
398 0 0       0 $tz_bias_b = $TZ_BIAS if (FsType($1) =~ m/FAT/);
399             }
400 0 0 0     0 $tz_bias_a = $tz_bias_b = 0 if ($tz_bias_a && $tz_bias_b);
401             }
402 5 50       10 if ($caller eq "Update") {
    0          
403 5 100       38 print "Update
404             Regex : $regex
405             NegRegex : $negregex
406             Mode : $mode
407             Commit : $commit
408             AgeLimit : $r_con->{agelimit} days ($agelimit seconds)
409             Tombstone File TTL : $ttl
410             DirA DOS time adj : $tz_bias_a
411             DirB DOS time adj : $tz_bias_b\n\n" if ($verbose >= 3);
412             }elsif ($caller eq "Delete"){
413 0 0       0 print "Delete
414             Regex : $regex
415             NegRegex : $negregex
416             Commit : $commit
417             AgeLimit : $r_con->{agelimit} days ($agelimit seconds)
418             Tombstone File TTL : $ttl
419             DirA DOS time adj : $tz_bias_a\n\n" if ($verbose >= 3);
420             }
421             # Sort files using regex and negregex
422 5 50       13 benchmark("init") if $r_con->{bmark};
423             ($tfiles,$common,$aonly,$bonly,$amatch,$bmatch) =
424 5         13 _arraysort($r_con, $regex, $negregex, $r_con->{nocase}) ;
425 5 50       16 benchmark("match files") if $r_con->{bmark};
426 5         6 $refa = $r_con->{alist};
427 5         9 $refatype = $r_con->{atype};
428 5         6 $refb = $r_con->{blist};
429 5         7 $refbtype = $r_con->{btype};
430             #****************************************************************
431             # sub to copy files and build directory structure
432             #****************************************************************
433             $fc = sub {
434 21     21   40 my($a,$b,$amtime,$bmtime,$disp,$mode) = @_;
435 21         23 my($A,$B,$Amtime,$Bmtime,$Btmp,$age,$msg);
436 21 100       247 print "fc ($a,$b,$amtime,$bmtime,$disp,$mode)\n" if $verbose > 3;
437 21 50       41 if ( $disp eq "-->" ) {
    0          
438 21         25 $A = $a;
439 21         26 $B = $b;
440 21         22 $Amtime = $amtime;
441 21         26 $Bmtime = $bmtime;
442             }elsif( $disp eq "<--" ) {
443 0         0 $A = $b;
444 0         0 $B = $a;
445 0         0 $Amtime = $bmtime;
446 0         0 $Bmtime = $amtime;
447             }else{
448 0         0 print "Illegal display option called ($disp)\n";
449 0         0 return 0;
450             }
451 21         43 $msg = " $a $disp $b";
452 21 50       48 if ( $amtime == 0 ) {
    100          
453 0         0 $age = $bmtime;
454             }elsif ( $bmtime == 0 ) {
455 20         27 $age = $amtime;
456             }else{
457 1 50       3 ($bmtime > $amtime) ? $age = $bmtime : $age = $amtime; # Find the most recent mtime ($age)
458             }
459 21 100       44 if ( $agelimit ){
460 20 50       40 if ($agelimit > 0) { # if $agelimit is positive - ignore files older than the agelimit
461 20 50       45 if ( ( $runtime - $age ) > $agelimit ) { # Test for agelimit exceeded
462             #print "amtime $amtime\nbmtime $bmtime\nage $age\n";
463 0 0       0 printf "%s - exceeds age limit (%3.1d days old - limit is set to less than %3d days)\n",$msg,(time - $age)/86400,$agelimit/86400 if ($verbose > 1);
464 0         0 return FALSE;
465             }
466             }else{ # agelimit is negative - ignore files newer than the agelimit
467 0 0       0 if ( ( $runtime - $age ) > -$agelimit ) { # Test for agelimit exceeded
468             #print "amtime $amtime\nbmtime $bmtime\nage $age\n";
469 0 0       0 printf "%s - is less than the minimum age limit (%3.1d days old - limit is set to greater than %3d days)\n",$msg,(time - $age)/86400,-$agelimit/86400 if ($verbose > 1);
470 0         0 return FALSE;
471             }
472             }
473             }
474 21 100       38 if ( ! $commit ) {
475 10 50       163 print "$msg\n" if ($verbose >= 1);
476 10         73 return TRUE;
477             }
478 11 50       338 return FALSE unless &$md(dirname($B)); # Make sure the parent of the target file exists
479 11 100       211 if ( -f $A ) {
480 6 50       141 if ( -f $B ) {
481 0         0 $Btmp = $B . '.X';
482 0         0 while ( -f $Btmp ) { # Find a temporary file name to copy target to (allows rollback after a copy failure)
483 0         0 $Btmp .= 'X';
484 0         0 print " *************** $Btmp\n"; # kind of error - this temp filename is already in use...
485             }
486 0 0       0 unless ( rename ($B, $Btmp) ) { # rename old copy of $B to $Btmp - to restore if the copy fails
487 0         0 carp "Unable to create temp copy of $B ($Btmp) \n"; # carp if this fails - but continue.....
488 0         0 undef $Btmp;
489             }
490             }
491              
492 6 50       18 if ( copy ($A,$B) ) {
493 6 100       1916 print "$msg\n" if ($verbose >= 1);
494             # ******
495             # this needs modifying for UNIX
496 6 50       183 chmod(0666,$B) if !($mode & 0x02);
497 6 50       147 utime($Amtime,$Amtime,$B) || carp "Failed to set modification time on $B\n";
498 6 50       159 chmod(0444,$B) if !($mode & 0x02);
499             # ******
500 6 50       22 if ( $Btmp ) { # remove the temporary file created
501 0   0     0 unlink $Btmp || carp "Failed to delete temporary file $Btmp\n";
502             }
503 6         33 return TRUE;
504             }else{
505 0         0 carp "$msg - failed to copy $A\n";
506 0 0       0 if ( $Btmp ) {
507 0 0       0 unless ( rename ($Btmp, $B) ) { # restore the temporary file after a copy failure
508 0         0 carp "Unable to restore $B from temp copy of $Btmp following failed file copy\n";
509 0         0 undef $Btmp;
510             }
511             }
512             }
513             }else{
514 5 50       89 if ( ! -d $B ) {
515 0 0       0 mkdir($B,0777) && return TRUE || carp "Unable to create directory $B\n";
516 0 0       0 print "$msg - (new directory)\n" if ($verbose >= 1);
517             }
518             # setting utime doe'nt work on a dir. Maybe FS rules ??
519             }
520 5         17 return FALSE;
521 5         29 };
522              
523             #****************************************************************
524             # sub to test a directory tree exists, and if not to create it
525             #****************************************************************
526             $md = sub {
527 15     15   25 my($Dir) = @_;
528 15 50       31 return TRUE unless $commit;
529 15 100       248 if (! -d $Dir) {
530 5         15 $Dir =~ /(.*)\/([^\/]*)/;
531 5         13 my($parent,$dir) = ($1,$2);
532 5 100       75 &$md($parent) if (!-d $parent); # Create the parent if it does not exist
533 5 50       297 mkdir ($Dir, 0777) || carp "Unable to create directory $Dir\n";
534             }
535 15         313 return(-d $Dir);
536 5         17 };
537              
538             #****************************************************************
539             # sub to delete directories / files
540             #****************************************************************
541             $del = sub {
542 0     0   0 my($targ, $mtime) = @_;
543             #print "del ($targ,$mtime)\n" if $verbose > 3;
544 0         0 my($msg);
545 0 0       0 if (-d $targ) {
    0          
546 0         0 $msg = " rmdir $targ";
547             }elsif (-f $targ) {
548 0         0 $msg = " rm $targ";
549             }
550 0 0 0     0 if ( $mtime && $agelimit ) {
551 0 0       0 if ( $agelimit > 0) { # if $agelimit is positive - ignore files older than the agelimit
552 0 0       0 if ( ( $runtime - $mtime ) > $agelimit ) {
553 0 0       0 printf "%s - exceeds age limit (%3.1d days - limit is newer than %3.1d days))\n", $msg, (time - $mtime)/(86400), $agelimit/(86400) if ( $verbose > 1 );
554 0         0 return FALSE;
555             }
556             }else{ # if $agelimit is negative - ignore files newer than the agelimit
557 0 0       0 if ( ( $runtime - $mtime ) < -$agelimit ) {
558 0 0       0 printf "%s - under age limit (%3.1d days - limit is older than %3.1d days))\n", $msg, (time - $mtime)/(86400), -$agelimit/(86400) if ( $verbose > 1 );
559 0         0 return FALSE;
560             }
561             }
562             }else{
563 0 0       0 print "$msg\n" if (($commit eq 0) & ($verbose >= 1));
564             }
565 0 0       0 return TRUE unless $commit;
566 0         0 print "$msg\n";
567 0 0       0 if (-d $targ) {
    0          
568 0 0       0 rmdir $targ || carp "Unable to delete directory $targ\n";
569 0         0 return ! -d $targ;
570             }elsif (-f $targ) {
571 0   0     0 unlink $targ || carp "Unable to delete file $targ\n";
572 0         0 return ! -f $targ;
573             }else{
574 0         0 print "** DO SOMETHING HERE ** (NOT ORDINARY FILE OR DIRECTORY)\n";
575             }
576 0         0 return FALSE;
577 5         20 };
578              
579             #****************************************************************
580             # sub to delete directory trees
581             #****************************************************************
582             $deltree = sub {
583 0     0   0 my($targ,$dir,$reftime,$reftype,$top) = @_;
584             my $xxx = sub {
585 0         0 ($tmp = $File::Find::name) =~ s/^\Q$dir//; # identify relative filename (to $dir)
586 0 0       0 return if ($tmp eq $targ); # Don't remove top level directory unless $top == TRUE
587 0 0       0 if (&$del($File::Find::name)) {
588 0         0 delete $reftime->{$tmp};
589 0         0 delete $reftype->{$tmp};
590             }
591 0         0 };
592 0         0 finddepth(\&$xxx, $dir . $targ);
593 0 0       0 if ($top) {
594 0         0 chdir $dir;
595 0 0       0 if (rmdir "$dir$targ") {
596 0         0 delete $reftime->{$targ};
597 0         0 delete $reftype->{$targ};
598             }else{
599 0         0 print "failed to rmdir $dir$targ\n";
600             }
601             }else{
602 0 0       0 $commit ? $$reftime->{$targ} = (stat($dir . $targ))[9] : $$reftime->{$targ} = $runtime;
603             }
604 5         27 };
605              
606             #****************************************************************
607             # sub to truncate files to zero length
608             #****************************************************************
609             $truncate = sub {
610 0     0   0 my($file_ref, $mtime_ref) = @_;
611 0 0       0 print " truncate $$file_ref\n" if ($verbose >= 2);
612 0 0       0 if ($commit) {
613 0 0       0 chmod(0666,$$file_ref) || carp "Failed to chmod 0666 $$file_ref\n";
614 0 0       0 truncate($$file_ref, 0) || carp "Failed to truncate $$file_ref\n";
615             }
616 0 0       0 $$mtime_ref = $commit ? (stat($$file_ref))[9] : $runtime;
617 0         0 $$file_ref = undef;
618 0         0 return TRUE;
619 5         22 };
620              
621             #****************************************************************
622             # sub to touch files
623             #****************************************************************
624             $touch = sub {
625 0     0   0 my($file, $mtime_ref, $type_ref) = @_;
626 0 0       0 print " touch $file\n" if ($verbose >= 2);
627 0 0       0 if ($commit) {
628 0 0       0 open(FILE, ">> $file") || carp "Failed to touch $file\n";
629 0         0 close(FILE);
630 0         0 chmod (0666,$file);
631             }
632 0 0       0 ($$type_ref, $$mtime_ref) = $commit ? (stat($file))[2,9] : $runtime;
633 5         20 };
634             #****************************************************************
635             # sub to rename a file or directory
636             #****************************************************************
637             $mv = sub {
638 0     0   0 my($old,$new) = @_;
639 0         0 my($msg) = "mv $old $new";
640 0 0       0 unless ($commit) {
    0          
641 0 0       0 print "$msg\n" if ($verbose > 1);
642 0         0 return TRUE;
643             }elsif ( rename ($old,$new)) {
644 0 0       0 print "$msg\n" if ($verbose > 1);
645 0         0 return TRUE;
646             }else{
647 0         0 print "$msg - Failed \n";
648 0         0 return FALSE;
649             }
650 5         16 };
651              
652 5 50       14 benchmark("init") if $r_con->{bmark};
653 5 50       11 if ( $caller eq "Update" ) {
    0          
    0          
654             #****************************************************************
655             # Delete tombstoned files (NH261100)
656             #****************************************************************
657 5         11 foreach $tName (@$tfiles) {
658 0         0 ($name = $tName) =~ s/.remove$//i;
659              
660             # Delete trees and touch a file with same name
661 0 0       0 if (-d $r_con->{dira} . $tName) {
662 0         0 &$deltree($tName, $r_con->{dira}, $refa, $refatype, TRUE);
663 0         0 &$touch($r_con->{dira} . $tName, \$refa->{$tName}, $refatype->{$tName});
664             }
665 0 0       0 if (-d $r_con->{dirb} . $tName) {
666 0         0 &$deltree($tName, $r_con->{dirb}, $refb, $refbtype, TRUE);
667 0         0 &$touch($r_con->{dirb} . $tName, \$refb->{$tName}, $refbtype->{$tName});
668             }
669              
670             # Delete trees and files
671 0 0       0 if ($r_con->{nocase}) {
672 0         0 ($aName) = grep { /^$name$/i } (keys %$refa);
  0         0  
673 0         0 ($bName) = grep { /^$name$/i } (keys %$refb);
  0         0  
674             }else{
675 0 0       0 $aName = ($refa->{$name}) ? $name : undef;
676 0 0       0 $bName = ($refb->{$name}) ? $name : undef;
677             }
678 0 0       0 if ($aName) {
679 0 0       0 if (-d $r_con->{dira} . $aName) {
680             # Delete dir trees including top level dir
681 0         0 &$deltree($aName, $r_con->{dira}, $refa, $refatype, TRUE);
682             }else{
683 0 0       0 delete $refa->{$aName}, delete $refatype->{$aName} if &$del($r_con->{dira} . $aName);
684             }
685             }
686 0 0       0 if ($bName) {
687 0 0       0 if (-d $r_con->{dirb} . $bName) {
688             # Delete dir trees including top level dir
689 0         0 &$deltree($bName, $r_con->{dirb}, $refb, $refbtype, TRUE);
690             }else{
691 0 0       0 delete $refb->{$bName}, delete $refbtype->{$bName} if &$del($r_con->{dirb} . $bName);
692             }
693             }
694             }
695             #****************************************************************
696             # Remove tombstone indicator files if older than $ttl (NH261100)
697             # Truncate (which will also touch) nonzero byte files (NH070401)
698             #****************************************************************
699 5         10 foreach (@$tfiles) {
700 0 0       0 $afile = $refa->{$_} ? $r_con->{dira} . $_ : undef;
701 0 0       0 $bfile = $refb->{$_} ? $r_con->{dirb} . $_ : undef;
702 0 0 0     0 &$truncate(\$afile, \$refa->{$_}) if ($afile && -s $afile);
703 0 0 0     0 &$truncate(\$bfile, \$refb->{$_}) if ($bfile && -s $bfile);
704 0 0 0     0 delete $refa->{$_}, delete $refatype->{$_} if ($afile && (($refa->{$_} + $ttl) < $runtime) && &$del($afile));
      0        
705 0 0 0     0 delete $refb->{$_}, delete $refbtype->{$_} if ($bfile && (($refb->{$_} + $ttl) < $runtime) && &$del($bfile));
      0        
706             }
707             # Note: modify arrays etc even if $commit is not set. This is required to determine behaviour of code
708             # without changing or deleting files and directories.
709 5 100       20 if ( $mode =~ /^(A>B!?)|(A<>B)$/ ) {
710 2         5 foreach (@$aonly) {
711 20 50       43 next unless exists $refa->{$_};
712 20         35 $afile = $r_con->{dira} . $_;
713 20         27 $amtime = $refa->{$_} - $tz_bias_a + $tz_bias_b;
714 20         27 $atype = $refatype->{$_};
715 20         32 $bfile = $r_con->{dirb} . $_;
716             #print " $afile --> $bfile\n" if ($verbose >= 1);
717 20 100       42 $refb->{$_} = $amtime, $refbtype->{$_} = $refatype->{$_} if &$fc($afile,$bfile,$amtime,0,"-->",$atype);
718             }
719             }
720 5 50       17 if ( $mode =~ /^(AB)$/ ) {
721 0         0 foreach (@$bonly) {
722 0 0       0 next unless exists $refb->{$_};
723 0         0 $afile = $r_con->{dira} . $_;
724 0         0 $bfile = $r_con->{dirb} . $_;
725 0         0 $bmtime = $refb->{$_} - $tz_bias_b + $tz_bias_a;
726 0         0 $btype = $refbtype->{$_};
727             #print " $afile <-- $bfile\n" if ($verbose >= 1);
728 0 0       0 $refa->{$_} = $bmtime, $refatype->{$_} = $refbtype->{$_} if &$fc($afile,$bfile,0,$bmtime,"<--",$btype);
729             }
730             }
731 5 50       11 if ( $mode =~ /^A
732 0         0 foreach (@$aonly) {
733 0 0       0 next unless exists $refa->{$_};
734 0         0 $afile = $r_con->{dira} . $_;
735 0         0 $amtime = $refa->{$_};
736 0 0       0 delete $refa->{$_}, delete $refatype->{$_} if &$del($afile, $amtime);
737             }
738             }
739 5 50       12 if ( $mode =~ /^A>B!$/ ) {
740 0         0 foreach (@$bonly) {
741 0 0       0 next unless exists $refb->{$_};
742 0         0 $bfile = $r_con->{dirb} . $_;
743 0         0 $bmtime = $refb->{$_};
744 0 0       0 delete $refb->{$_}, delete $refbtype->{$_} if &$del($bfile, $bmtime);
745             }
746             }
747              
748 5         15 foreach $aName (keys %$common) {
749             # print "aName $aName\n";
750             # printf "Ref: %s\n",$refa->{$aName};
751 20 50       46 next unless exists $refa->{$aName};
752             # To allow for non case sensitive filesystems
753             # %common key holds the 'a' name and
754             # %common value holds the 'b' name
755 20         31 $bName = $$common{$aName};
756 20         24 $amtime = $refa->{$aName} - $tz_bias_a;
757 20         21 $bmtime = $refb->{$bName} - $tz_bias_b;
758 20         25 $atype = $refatype->{$aName};
759 20         26 $btype = $refbtype->{$bName};
760 20         33 $afile = $r_con->{dira} . $aName;
761 20         28 $bfile = $r_con->{dirb} . $bName;
762              
763             # Skip directories as their time can't be set (NH251100)
764 20 100       387 next if -d $afile;
765              
766 10 100       31 if ( $amtime > ($bmtime + $fudge) ) {
    100          
767 1         2 $amtime += $tz_bias_b;
768 1 50       7 if ( $mode =~ /^(a>b)|(a<>b)|(A>B)|(A>B!)|(A<>B)$/ ) {
769             #if ( -f $afile ) {
770             # print " $afile --> $bfile\n" if ($verbose >= 1);
771             # print " ($amtime) --> ($bmtime)\n" if ($verbose >= 2);
772             #}
773 1 50       3 $refb->{$bName} = $amtime if (&$fc($afile,$bfile,$amtime,$bmtime,"-->",$atype));
774             }
775             }elsif ( ($amtime + $fudge) < $bmtime ) {
776 1         1 $bmtime += $tz_bias_a;
777 1 50       6 if ( $mode =~ /^(ab)|(AB)$/ ) {
778             #if ( -f $afile ) {
779             # print " $afile <-- $bfile\n" if ($verbose >= 1);
780             # print " ($amtime) <-- ($bmtime)\n" if ($verbose >= 2);
781             #}
782 0 0       0 $refa->{$aName} = $bmtime if (&$fc($afile,$bfile,$amtime,$bmtime,"<--",$btype));
783             }
784             }
785             }
786             }elsif( $caller eq "Delete" ) {
787 0         0 foreach my $f (@$amatch) {
788 0 0       0 next unless exists $refa->{$f};
789 0         0 $afile = $r_con->{dira} . $f;
790 0         0 $amtime = $refa->{$f} - $tz_bias_a + $tz_bias_b;
791 0         0 $atype = $refatype->{$f};
792 0 0       0 if (&$del($afile, $amtime)) {
793 0 0       0 if ($commit) {
794 0         0 delete $refa->{$f};
795 0         0 delete $refatype->{$f};
796             # remove reference to this file from the arrays @aonly etc.
797             }
798             }
799             }
800              
801             }elsif( $caller eq "Rename" ) {
802 0         0 $nsub =~ m/^(.)/;
803 0         0 my($sep) = ($1);
804 0 0       0 if ( $nsub =~ m/^$sep(.*)$sep(.*)$sep(.*)?$/ ) {
805 0         0 my($match,$replace,$arg) = ($1,$2,$3);
806             # print "nsub $nsub\n";
807             # print "sep: $sep\nmatch : $match\n replace: $replace\narg: $arg\n";
808 0         0 foreach my $f (@$amatch) {
809 0 0       0 next unless exists $refa->{$f};
810 0         0 my($newname) = $f;
811 0         0 $newname =~ s/$match/$replace/;
812 0 0       0 next if ($newname eq $f); # next file if no change...
813 0         0 $afile = $r_con->{dira} . $f;
814 0         0 my ($Afile) = $r_con->{dira} . $newname;
815 0 0       0 if (&$mv($afile,$Afile)){
816 0         0 $refa->{$Afile} = $refa->{$f};
817 0         0 $refatype->{$Afile} = $refatype->{$f};
818 0         0 delete $refa->{$f};
819 0         0 delete $refatype->{$f};
820             }
821             }
822             }else{
823 0         0 carp "unable to understand substition argument $nsub\n";
824             }
825             }
826              
827             # add references to allow @aonly, @bonly etc to be recalled from the reference
828 5         7 my($retval);
829 5         11 $retval->{amatch} = $amatch;
830 5         7 $retval->{bmatch} = $bmatch;
831 5         8 $retval->{aonly} = $aonly;
832 5         6 $retval->{bonly} = $bonly;
833 5         7 $retval->{common} = $common;
834 5 50       11 benchmark("synch files") if $r_con->{bmark};
835 5         249 return $retval;
836             }
837              
838             #=====================================================================
839             # Support old method call
840             sub SetDefaults {
841 0     0 0 0 return New @_;
842             }
843             #=====================================================================
844             sub _arraysort {
845 5     5   11 my($r_con, $regex, $negregex, $nocase) = @_;
846 5         6 my(@amatch,@bmatch,@tfiles,%common,@aonly,@bonly,@temp,$name,$mtime,$type);
847 0         0 my(@sorted_amatch,@sorted_bmatch,$aName,$bName,$aIndex,$bIndex);
848 5         8 my %dup = ();
849 5         9 my $refa = $r_con->{alist};
850 5         7 my $refatype = $r_con->{atype};
851 5         8 my $refb = $r_con->{blist};
852 5         7 my $refbtype = $r_con->{btype};
853 5 50       10 my $regexextn = $nocase ? '(?i)' : ''; # use regex extention (?i) if working with case insensitive file systems
854              
855             # Find files matching the regex in dira
856 5 100       25 print "Files Matching regex in $r_con->{dira}:\n" if ($r_con->{verbose} >= 4);
857 5         25 foreach $name (keys %$refa) {
858 50 50 33     438 if ( $name && ($name =~ /$regexextn$regex/) && ($name !~ /$regexextn$negregex/) ) {
      33        
859 50         112 push (@amatch,$name);
860             #printf" %s %s %s\n",%$refa->{$name},%$refatype->{$name},$name if ($r_con->{verbose} >= 4);
861             }
862             }
863             # Find files matching the regex in dirb
864 5 100       35 print "Files Matching regex in $r_con->{dirb}:\n" if ($r_con->{verbose} >= 4);
865 5         17 foreach $name (keys %$refb) {
866 20 50 33     175 if ( $name && $name =~ /$regexextn$regex/ && $name !~ /$regexextn$negregex/ ) {
      33        
867 20         47 push (@bmatch,$name);
868             #printf" %s %s %s\n",%$refb->{$name},%$refbtype->{$name},$name if ($r_con->{verbose} >= 4);
869             }
870             }
871             # Build a list of files that have an added extension ".remove" - to be deleted by tombstone routines later
872 5         9 @tfiles = grep { /.+\.remove$/i } @amatch; # get alist files/dirs with .remove extension
  50         195  
873 5         8 push @tfiles, grep { /.+\.remove$/i } @bmatch; # get blist files/dirs with .remove extension
  20         73  
874 5         8 @tfiles = grep { ! $dup{$_} ++ } @tfiles; # remove duplicates
  0         0  
875              
876             # Find elements that are common/unique to @amatch and @bmatch
877             # -put in sorted order so that we can create directories/files
878             # in one sweep (ie we don't try to create a file before its
879             # parent directory exists)
880             #
881             # On non-case sensitive filesystems (e.g Bill's) ignore the case
882             # when testing for matching files. This will still allow repl to
883             # create / update files maintaining their original case. (NH311000)
884 5         7 $aIndex = 0;
885 5         6 $bIndex = 0;
886 5 50       21 @sorted_amatch = $nocase ? sort {lc($a) cmp lc($b) } @amatch : sort @amatch;
  112         160  
887 5 50       14 @sorted_bmatch = $nocase ? sort {lc($a) cmp lc($b) } @bmatch : sort @bmatch;
  45         62  
888              
889 5         14 while ( $aIndex < @sorted_amatch ) {
890 23 100       48 last unless defined $sorted_bmatch[$bIndex]; # End of b list
891 20         24 $aName = $sorted_amatch[$aIndex];
892 20         22 $bName = $sorted_bmatch[$bIndex];
893 20 50 33     62 if ($aName eq $bName || ($nocase && lc($aName) eq lc($bName))) {
    0 66        
      0        
      0        
      0        
894 20         29 $common{$aName} = $bName; # Store $aName as key and $bName as value
895 20         21 $aIndex++;
896 20         42 $bIndex++;
897             }elsif (($nocase && lc($aName) lt lc($bName)) || (!$nocase && $aName lt $bName)) {
898 0         0 push(@aonly,$aName);
899 0         0 $aIndex++;
900             }else{
901 0         0 push(@bonly,$bName);
902 0         0 $bIndex++;
903             }
904             }
905             # Get any remainder of 'a' list
906 5         12 while ( $aIndex < @sorted_amatch ) {
907 30         73 push(@aonly,$sorted_amatch[$aIndex++]);
908             }
909             # Get any remainder of 'b' list
910 5         13 while ($bIndex < @sorted_bmatch) {
911 0         0 push(@bonly,$sorted_bmatch[$bIndex++]);
912             }
913              
914 5         21 @aonly = reverse sort @aonly; # Sort so that file preceed directories - allows deltree to delete
915 5         9 @bonly = reverse sort @bonly; # files before directories
916              
917 5 100       15 if ( $r_con->{verbose} >= 3 ) {
918 1         20 print "Common Files :\n";
919 1         4 foreach (keys %common) {
920 0         0 print " $_\n $common{$_}\n";
921             }
922 1         3 print "A dir only Files :\n";
923 1         3 foreach (@aonly) {
924 10         30 print " $_\n";
925             }
926 1         4 print "B dir only Files :\n";
927 1         2 foreach (@bonly) {
928 0         0 print " $_\n";
929             }
930 1         3 print "\n\nEnd of File Lists ...\n\n";
931             }
932 5         31 return (\@tfiles, \%common, \@aonly, \@bonly, \@amatch, \@bmatch);
933             }
934             #=====================================================================
935             # If this is called with with "init" argument this initialises global variable @times
936             # - a record of user and system times;
937             # Otherwise difference since last init (user and system times) is printed to STDOUT
938             my @times; # global var
939             sub benchmark ($@) {
940 0     0 0 0 my($str,$r1,$u1,$s1) = @_;
941 0 0       0 @times = $r1 ? ($r1,$u1,$s1) : ( $runtime, times), return if $str eq "init";
    0          
942 0 0       0 ($r1,$u1,$s1) = @times unless $r1;
943 0         0 my($r2,$u2,$s2) = ( $runtime, times);
944 0         0 printf " %-13s: %2d secs ( %.2f usr + %.2f sys = %.2f CPU )\n",
945             $str, $r2-$r1, $u2-$u1, $s2-$s1, $u2-$u1 + $s2-$s1;
946             }
947             #=====================================================================
948             sub elapsed{
949 12     12 0 39 my($start_time)=@_;
950 12         18 my($elapsed) = $start_time - time;
951 12         312 return $elapsed;
952             }
953             1;
954             __END__