1 |
#!/bin/perl |
2 |
#============================================================= -*-perl-*- |
3 |
# |
4 |
# BackupPC_compressPool: Compress existing pool |
5 |
# |
6 |
# DESCRIPTION |
7 |
# |
8 |
# Usage: BackupPC_compressPool [-t] [-r] <host> |
9 |
# |
10 |
# Flags: |
11 |
# -t test mode: do everything except actually replace the pool files. |
12 |
# Useful for estimating total run time without making any real |
13 |
# changes. |
14 |
# -r read check: re-read the compressed file and compare it against |
15 |
# the original uncompressed file. Can only be used in test mode. |
16 |
# -c # number of children to fork. BackupPC_compressPool can take |
17 |
# a long time to run, so to speed things up it spawns four children, |
18 |
# each working on a different part of the pool. You can change |
19 |
# the number of children with the -c option. |
20 |
# |
21 |
# BackupPC_compressPool is used to convert an uncompressed pool to |
22 |
# a compressed pool. If BackupPC compression is enabled after |
23 |
# uncompressed backups already exist, BackupPC_compressPool can |
24 |
# be used to compress all the old uncompressed backups. |
25 |
# |
26 |
# It is important that BackupPC not run while BackupPC_compressPool |
27 |
# runs. Also, BackupPC_compressPool must run to completion before |
28 |
# BackupPC is restarted. |
29 |
# |
30 |
# AUTHOR |
31 |
# Craig Barratt <cbarratt@users.sourceforge.net> |
32 |
# |
33 |
# COPYRIGHT |
34 |
# Copyright (C) 2001-2003 Craig Barratt |
35 |
# |
36 |
# This program is free software; you can redistribute it and/or modify |
37 |
# it under the terms of the GNU General Public License as published by |
38 |
# the Free Software Foundation; either version 2 of the License, or |
39 |
# (at your option) any later version. |
40 |
# |
41 |
# This program is distributed in the hope that it will be useful, |
42 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
43 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
44 |
# GNU General Public License for more details. |
45 |
# |
46 |
# You should have received a copy of the GNU General Public License |
47 |
# along with this program; if not, write to the Free Software |
48 |
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
49 |
# |
50 |
#======================================================================== |
51 |
# |
52 |
# Version 2.1.2, released 5 Sep 2005. |
53 |
# |
54 |
# See http://backuppc.sourceforge.net. |
55 |
# |
56 |
#======================================================================== |
57 |
|
58 |
use strict; |
59 |
no utf8; |
60 |
|
61 |
use File::Find; |
62 |
use File::Path; |
63 |
use Compress::Zlib; |
64 |
use Getopt::Std; |
65 |
use lib "__INSTALLDIR__/lib"; |
66 |
use BackupPC::Lib; |
67 |
use BackupPC::FileZIO; |
68 |
|
69 |
die("BackupPC::Lib->new failed\n") if ( !(my $bpc = BackupPC::Lib->new) ); |
70 |
$bpc->ChildInit(); |
71 |
my $TopDir = $bpc->TopDir(); |
72 |
my $BinDir = $bpc->BinDir(); |
73 |
my %Conf = $bpc->Conf(); |
74 |
my $PoolDir = "$TopDir/pool"; |
75 |
my $CPoolDir = "$TopDir/cpool"; |
76 |
my $Compress = $Conf{CompressLevel}; |
77 |
my %opts; |
78 |
my $SigName = ""; |
79 |
|
80 |
# |
81 |
# Catch various signals |
82 |
# |
83 |
foreach my $sig ( qw(INT BUS SEGV PIPE TERM ALRM HUP) ) { |
84 |
$SIG{$sig} = \&catch_signal; |
85 |
} |
86 |
|
87 |
$| = 1; |
88 |
|
89 |
my $CompMaxRead = 131072; # 128K |
90 |
my $CompMaxWrite = 6291456; # 6MB |
91 |
|
92 |
if ( !getopts("trc:", \%opts) || @ARGV != 0 ) { |
93 |
print("usage: $0 [-c nChild] [-r] [-t]\n"); |
94 |
exit(1); |
95 |
} |
96 |
my $TestMode = $opts{t}; |
97 |
my $ReadCheck = $opts{r}; |
98 |
my $nChild = $opts{c} || 4; |
99 |
if ( $ReadCheck && !$TestMode ) { |
100 |
print(STDERR "$0: -r (read check) option must have -t (test)\n"); |
101 |
exit(1); |
102 |
} |
103 |
if ( $nChild < 1 || $nChild >= 16 ) { |
104 |
print(STDERR "$0: number of children (-c option) must be from 1 to 16\n"); |
105 |
exit(1); |
106 |
} |
107 |
if ( !BackupPC::FileZIO->compOk ) { |
108 |
print STDERR <<EOF; |
109 |
$0: Compress::Zlib is not installed. You need to install it |
110 |
before running this script. |
111 |
EOF |
112 |
exit(1); |
113 |
} |
114 |
if ( $Compress <= 0 ) { |
115 |
print STDERR <<EOF; |
116 |
$0: compression is not enabled. \%Conf{CompressLevel} needs |
117 |
to be set to a value from 1 to 9. Please edit the config.pl file and |
118 |
re-start $0. |
119 |
EOF |
120 |
exit(1); |
121 |
} |
122 |
|
123 |
my $Errors = 0; |
124 |
my $SubDirDone = 0; |
125 |
my $SubDirCnt = 0; |
126 |
my $SubDirCurr = 0; |
127 |
my $FileCnt = 0; |
128 |
my $FileOrigSz = 0; |
129 |
my $FileCompressSz = 0; |
130 |
|
131 |
my $err = $bpc->ServerConnect($Conf{ServerHost}, $Conf{ServerPort}); |
132 |
if ( $err eq "" ) { |
133 |
print <<EOF; |
134 |
BackupPC is running on $Conf{ServerHost}. You need to stop BackupPC |
135 |
before you can upgrade the code. Depending upon your installation, |
136 |
you could run "/etc/init.d/backuppc stop". |
137 |
EOF |
138 |
exit(1); |
139 |
} |
140 |
|
141 |
umask($Conf{UmaskMode}); |
142 |
|
143 |
sub cpoolFileName |
144 |
{ |
145 |
my($new) = @_; |
146 |
if ( $new !~ m{/(\w/\w/\w)/(\w{32})(_\d+)?$} ) { |
147 |
print("Error: Can't parse filename from $new\n"); |
148 |
$Errors++; |
149 |
return; |
150 |
} |
151 |
my $dir = "$CPoolDir/$1"; |
152 |
$new = "$dir/$2"; |
153 |
mkpath($dir, 0, 0777) if ( !-d $dir ); |
154 |
return $new if ( !-f $new ); |
155 |
for ( my $i = 0 ; ; $i++ ) { |
156 |
return "${new}_$i" if ( !-f "${new}_$i" ); |
157 |
} |
158 |
} |
159 |
|
160 |
sub doCompress |
161 |
{ |
162 |
my $file = ($File::Find::name =~ /(.*)/ && $1); |
163 |
local(*FH, *OUT); |
164 |
my(@s) = stat($file); |
165 |
my($n, $dataIn, $dataOut, $flush, $copy); |
166 |
|
167 |
if ( $SigName ) { |
168 |
print("Child got signal $SigName; quitting\n"); |
169 |
reportStats(); |
170 |
exit(0); |
171 |
} |
172 |
return if ( !-f $file ); |
173 |
my $defl = deflateInit( |
174 |
-Bufsize => 65536, |
175 |
-Level => $Compress, |
176 |
); |
177 |
if ( !open(FH, $TestMode ? "<" : "+<", $file) ) { |
178 |
print("Error: Can't open $file for read/write\n"); |
179 |
$Errors++; |
180 |
return; |
181 |
} |
182 |
binmode(FH); |
183 |
while ( sysread(FH, $dataIn, $CompMaxWrite) > 0 ) { |
184 |
$flush = 0; |
185 |
$FileOrigSz += length($dataIn); |
186 |
my $fragOut = $defl->deflate($dataIn); |
187 |
if ( length($fragOut) < $CompMaxRead ) { |
188 |
# |
189 |
# Compression is too high: to avoid huge memory requirements |
190 |
# on read we need to flush(). |
191 |
# |
192 |
$fragOut .= $defl->flush(); |
193 |
$flush = 1; |
194 |
$defl = deflateInit( |
195 |
-Bufsize => 65536, |
196 |
-Level => $Compress, |
197 |
); |
198 |
} |
199 |
$dataOut .= $fragOut; |
200 |
if ( !$copy && length($dataOut) > $CompMaxWrite ) { |
201 |
if ( !open(OUT, "+>", "$file.__z") ) { |
202 |
print("Error: Can't open $file.__z for write\n"); |
203 |
$Errors++; |
204 |
close(FH); |
205 |
return; |
206 |
} |
207 |
binmode(OUT); |
208 |
$copy = 1; |
209 |
} |
210 |
if ( $copy && $dataOut ne "" ) { |
211 |
if ( syswrite(OUT, $dataOut) != length($dataOut) ) { |
212 |
printf("Error: Can't write %d bytes to %s\n", |
213 |
length($dataOut), "$file.__z"); |
214 |
$Errors++; |
215 |
close(OUT); |
216 |
close(FH); |
217 |
unlink("$file.__z"); |
218 |
return; |
219 |
} |
220 |
$FileCompressSz += length($dataOut); |
221 |
$dataOut = undef; |
222 |
} |
223 |
} |
224 |
if ( !$flush ) { |
225 |
$dataOut .= $defl->flush(); |
226 |
if ( $copy && $dataOut ne "" ) { |
227 |
if ( syswrite(OUT, $dataOut) != length($dataOut) ) { |
228 |
printf("Error: Can't write %d bytes to %s\n", |
229 |
length($dataOut), "$file.__z"); |
230 |
$Errors++; |
231 |
close(OUT); |
232 |
close(FH); |
233 |
unlink("$file.__z"); |
234 |
return; |
235 |
} |
236 |
$FileCompressSz += length($dataOut); |
237 |
$dataOut = undef; |
238 |
} |
239 |
} |
240 |
my $newFile = cpoolFileName($file); |
241 |
if ( $TestMode ) { |
242 |
close(FH); |
243 |
if ( !open(FH, ">", $newFile) ) { |
244 |
print("Error: Can't open $newFile for write\n"); |
245 |
$Errors++; |
246 |
close(FH); |
247 |
unlink("$file.__z"); |
248 |
return; |
249 |
} |
250 |
binmode(FH); |
251 |
} |
252 |
if ( $copy ) { |
253 |
if ( !sysseek(OUT, 0, 0) ) { |
254 |
print("Error: Can't seek $file.__z to 0\n"); |
255 |
$Errors++; |
256 |
} |
257 |
if ( !sysseek(FH, 0, 0) ) { |
258 |
print("Error: Can't seek $newFile to 0\n"); |
259 |
$Errors++; |
260 |
} |
261 |
while ( sysread(OUT, $dataIn, $CompMaxWrite) > 0 ) { |
262 |
if ( syswrite(FH, $dataIn) != length($dataIn) ) { |
263 |
printf("Error: Can't write %d bytes to %s\n", |
264 |
length($dataIn), $file); |
265 |
$Errors++; |
266 |
} |
267 |
} |
268 |
if ( !truncate(FH, sysseek(OUT, 0, 1)) ) { |
269 |
printf("Error: Can't truncate %s to %d\n", |
270 |
$file, sysseek(OUT, 0, 1)); |
271 |
$Errors++; |
272 |
} |
273 |
close(OUT); |
274 |
close(FH); |
275 |
unlink("$file.__z"); |
276 |
} else { |
277 |
if ( !sysseek(FH, 0, 0) ) { |
278 |
print("Error: Can't seek $file to 0\n"); |
279 |
$Errors++; |
280 |
} |
281 |
if ( syswrite(FH, $dataOut) != length($dataOut) ) { |
282 |
printf("Error: Can't write %d bytes to %s\n", |
283 |
length($dataOut), $file); |
284 |
$Errors++; |
285 |
} |
286 |
$FileCompressSz += length($dataOut); |
287 |
if ( !truncate(FH, length($dataOut)) ) { |
288 |
printf("Error: Can't truncate %s to %d\n", $file, length($dataOut)); |
289 |
$Errors++; |
290 |
} |
291 |
close(FH); |
292 |
} |
293 |
if ( $TestMode ) { |
294 |
if ( $ReadCheck ) { |
295 |
checkRead($file, $newFile); |
296 |
} |
297 |
unlink($newFile); |
298 |
} else { |
299 |
rename($file, $newFile); |
300 |
my $atime = $s[8] =~ /(.*)/ && $1; |
301 |
my $mtime = $s[9] =~ /(.*)/ && $1; |
302 |
utime($atime, $mtime, $newFile); |
303 |
} |
304 |
(my $dir = $file) =~ s{/[^/]*$}{}; |
305 |
$FileCnt++; |
306 |
if ( $SubDirCurr ne "" && $SubDirCurr ne $dir ) { |
307 |
$SubDirDone++; |
308 |
$SubDirCurr = $dir; |
309 |
reportStats(); |
310 |
} elsif ( $SubDirCurr eq "" ) { |
311 |
$SubDirCurr = $dir; |
312 |
} |
313 |
} |
314 |
|
315 |
sub reportStats |
316 |
{ |
317 |
print("stats: $SubDirDone $SubDirCnt $FileCnt $FileOrigSz" |
318 |
. " $FileCompressSz $Errors\n"); |
319 |
} |
320 |
|
321 |
sub checkRead |
322 |
{ |
323 |
my($file, $cfile) = @_; |
324 |
return if ( !-f $file || !-f $cfile ); |
325 |
my $f = BackupPC::FileZIO->open($cfile, 0, $Compress) |
326 |
|| die("can't open $cfile for read\n"); |
327 |
my($n, $nd, $r, $d, $d0); |
328 |
local(*FH); |
329 |
|
330 |
if ( !open(FH, "<", $file) ) { |
331 |
print("can't open $file for check\n"); |
332 |
$Errors++; |
333 |
$f->close(); |
334 |
return; |
335 |
} |
336 |
binmode(FH); |
337 |
#print("comparing $file to $cfile\n"); |
338 |
while ( 1 ) { |
339 |
$n = 1 + int(rand($CompMaxRead) + rand(100)); |
340 |
$r = $f->read(\$d, $n); |
341 |
sysread(FH, $d0, $n); |
342 |
if ( $d ne $d0 ) { |
343 |
print("Botch read data on $cfile\n"); |
344 |
} |
345 |
last if ( length($d) == 0 ); |
346 |
} |
347 |
if ( ($r = $f->read(\$d, 100)) != 0 || ($r = $f->read(\$d, 100)) != 0 ) { |
348 |
printf("Botch at EOF on $cfile got $r (%d,%d)\n", |
349 |
sysseek(FH, 0, 1), $n); |
350 |
$Errors++; |
351 |
} |
352 |
$f->close; |
353 |
close(FH); |
354 |
} |
355 |
|
356 |
sub checkReadLine |
357 |
{ |
358 |
my($file, $cfile) = @_; |
359 |
return if ( !-f $file || !-f $cfile ); |
360 |
my $f = BackupPC::FileZIO->open($cfile, 0, $Compress) |
361 |
|| die("can't open $cfile for read\n"); |
362 |
my($n, $nd, $r, $d, $d0); |
363 |
local(*FH); |
364 |
|
365 |
if ( !open(FH, "<", $file) ) { |
366 |
print("can't open $file for check\n"); |
367 |
$Errors++; |
368 |
$f->close(); |
369 |
return; |
370 |
} |
371 |
binmode(FH); |
372 |
while ( 1 ) { |
373 |
$d0 = <FH>; |
374 |
$d = $f->readLine(); |
375 |
if ( $d ne $d0 ) { |
376 |
print("Botch read data on $cfile\n"); |
377 |
} |
378 |
last if ( length($d) == 0 ); |
379 |
} |
380 |
if ( ($r = $f->read(\$d, 100)) != 0 || ($r = $f->read(\$d, 100)) != 0 ) { |
381 |
printf("Botch at EOF on $cfile got $r (%d,%d)\n", |
382 |
sysseek(FH, 0, 1), $n); |
383 |
$Errors++; |
384 |
} |
385 |
$f->close; |
386 |
close(FH); |
387 |
} |
388 |
|
389 |
sub catch_signal |
390 |
{ |
391 |
$SigName = shift; |
392 |
} |
393 |
|
394 |
sub compressHostFiles |
395 |
{ |
396 |
my($host) = @_; |
397 |
my(@Files, @Backups, $fh, $data); |
398 |
local(*FH); |
399 |
|
400 |
if ( !defined($host) ) { |
401 |
for ( my $i = 0 ; ; $i++ ) { |
402 |
last if ( !-f "$TopDir/log/LOG.$i" ); |
403 |
push(@Files, "$TopDir/log/LOG.$i"); |
404 |
} |
405 |
} else { |
406 |
@Backups = $bpc->BackupInfoRead($host); |
407 |
for ( my $i = 0 ; $i < @Backups ; $i++ ) { |
408 |
next if ( $Backups[$i]{compress} ); |
409 |
push(@Files, "$TopDir/pc/$host/SmbLOG.$Backups[$i]{num}"); |
410 |
push(@Files, "$TopDir/pc/$host/XferLOG.$Backups[$i]{num}"); |
411 |
} |
412 |
push(@Files, "$TopDir/pc/$host/SmbLOG.bad"); |
413 |
push(@Files, "$TopDir/pc/$host/XferLOG.bad"); |
414 |
for ( my $i = 0 ; ; $i++ ) { |
415 |
last if ( !-f "$TopDir/pc/$host/LOG.$i" ); |
416 |
push(@Files, "$TopDir/pc/$host/LOG.$i"); |
417 |
} |
418 |
} |
419 |
foreach my $file ( @Files ) { |
420 |
if ( $SigName ) { |
421 |
print("Child got signal $SigName; quitting\n"); |
422 |
reportStats(); |
423 |
exit(0); |
424 |
} |
425 |
next if ( !-f $file ); |
426 |
if ( !BackupPC::FileZIO->compressCopy($file, "$file.z", undef, |
427 |
$Compress, !$TestMode) ) { |
428 |
print("compressCopy($file, $file.z, $Compress, !$TestMode)" |
429 |
. " failed\n"); |
430 |
$Errors++; |
431 |
} elsif ( $TestMode ) { |
432 |
checkReadLine($file, "$file.z") if ( $ReadCheck ); |
433 |
unlink("$file.z"); |
434 |
} |
435 |
} |
436 |
} |
437 |
|
438 |
sub updateHostBackupInfo |
439 |
{ |
440 |
my($host) = @_; |
441 |
if ( !$TestMode ) { |
442 |
my @Backups = $bpc->BackupInfoRead($host); |
443 |
for ( my $i = 0 ; $i < @Backups ; $i++ ) { |
444 |
$Backups[$i]{compress} = $Compress; |
445 |
} |
446 |
$bpc->BackupInfoWrite($host, @Backups); |
447 |
} |
448 |
} |
449 |
|
450 |
my @Dirs = split(//, "0123456789abcdef"); |
451 |
my @Hosts = sort(keys(%{$bpc->HostInfoRead()})); |
452 |
my $FDread; |
453 |
my @Jobs; |
454 |
|
455 |
# |
456 |
# First make sure there are no existing compressed backups |
457 |
# |
458 |
my(%compHosts, $compCnt); |
459 |
for ( my $j = 0 ; $j < @Hosts ; $j++ ) { |
460 |
my $host = $Hosts[$j]; |
461 |
my @Backups = $bpc->BackupInfoRead($host); |
462 |
for ( my $i = 0 ; $i < @Backups ; $i++ ) { |
463 |
next if ( !$Backups[$i]{compress} ); |
464 |
$compHosts{$host}++; |
465 |
$compCnt++; |
466 |
} |
467 |
} |
468 |
if ( $compCnt ) { |
469 |
my $compHostStr = join("\n + ", sort(keys(%compHosts))); |
470 |
print STDERR <<EOF; |
471 |
BackupPC_compressPool: there are $compCnt compressed backups. |
472 |
BackupPC_compressPool can only be run when there are no existing |
473 |
compressed backups. The following hosts have compressed backups: |
474 |
|
475 |
+ $compHostStr |
476 |
|
477 |
If you really want to run BackupPC_compressPool you will need to remove |
478 |
all the existing compressed backups (and /home/pcbackup/data/cpool). |
479 |
Think carefully before you do this. Otherwise, you can just let new |
480 |
compressed backups run and the old uncompressed backups and pool will |
481 |
steadily expire. |
482 |
EOF |
483 |
exit(0); |
484 |
} |
485 |
|
486 |
# |
487 |
# Next spawn $nChild children that actually do all the work. |
488 |
# |
489 |
for ( my $i = 0 ; $i < $nChild ; $i++ ) { |
490 |
local(*CHILD); |
491 |
my $pid; |
492 |
if ( !defined($pid = open(CHILD, "-|")) ) { |
493 |
print("Can't fork\n"); |
494 |
next; |
495 |
} |
496 |
my $nDirs = @Dirs / ($nChild - $i); |
497 |
my $nHosts = @Hosts / ($nChild - $i); |
498 |
if ( !$pid ) { |
499 |
# |
500 |
# This is the child. |
501 |
# First process each of the hosts (compress per-pc log files etc). |
502 |
# |
503 |
for ( my $j = 0 ; $j < $nHosts ; $j++ ) { |
504 |
compressHostFiles($Hosts[$j]); |
505 |
} |
506 |
# |
507 |
# Count the total number of directories so we can estimate the |
508 |
# completion time. We ignore empty directories by reading each |
509 |
# directory and making sure it has at least 3 entries (ie, ".", |
510 |
# ".." and a file). |
511 |
# |
512 |
for ( my $j = 0 ; $j < $nDirs ; $j++ ) { |
513 |
my $thisDir = $Dirs[$j]; |
514 |
next if ( !-d "$PoolDir/$thisDir" ); |
515 |
foreach my $dir ( <$PoolDir/$thisDir/*/*> ) { |
516 |
next if ( !opendir(DIR, $dir) ); |
517 |
my @files = readdir(DIR); |
518 |
closedir(DIR); |
519 |
$SubDirCnt++ if ( @files > 2 ); |
520 |
} |
521 |
} |
522 |
# |
523 |
# Now process each of the directories |
524 |
# |
525 |
for ( my $j = 0 ; $j < $nDirs ; $j++ ) { |
526 |
my $thisDir = shift(@Dirs); |
527 |
next if ( !-d "$PoolDir/$thisDir" ); |
528 |
find({wanted => sub { doCompress($File::Find::name); }, |
529 |
no_chdir => 1}, "$PoolDir/$thisDir"); |
530 |
} |
531 |
# |
532 |
# Last, update the backup info file for each of the hosts |
533 |
# |
534 |
for ( my $j = 0 ; $j < $nHosts ; $j++ ) { |
535 |
updateHostBackupInfo($Hosts[$j]); |
536 |
} |
537 |
$SubDirDone = $SubDirCnt; |
538 |
reportStats(); |
539 |
exit(0); |
540 |
} |
541 |
# |
542 |
# This is the parent. Peel off $nDirs directories, $nHosts hosts, |
543 |
# and continue |
544 |
# |
545 |
$Jobs[$i]{fh} = *CHILD; |
546 |
$Jobs[$i]{pid} = $pid; |
547 |
vec($FDread, fileno($Jobs[$i]{fh}), 1) = 1; |
548 |
splice(@Dirs, 0, $nDirs); |
549 |
splice(@Hosts, 0, $nHosts); |
550 |
} |
551 |
|
552 |
# |
553 |
# compress the main log files (in the parents) |
554 |
# |
555 |
compressHostFiles(undef); |
556 |
|
557 |
# |
558 |
# Now wait for all the children to report results and finish up |
559 |
# |
560 |
my $TimeStart = time; |
561 |
my $DonePct = 0; |
562 |
my $GotSignal = ""; |
563 |
while ( $FDread !~ /^\0*$/ ) { |
564 |
my $ein = $FDread; |
565 |
select(my $rout = $FDread, undef, $ein, undef); |
566 |
if ( $SigName ne $GotSignal ) { |
567 |
print("Got signal $SigName; waiting for $nChild children to cleanup\n"); |
568 |
$GotSignal = $SigName; |
569 |
} |
570 |
for ( my $i = 0 ; $i < $nChild ; $i++ ) { |
571 |
next if ( !vec($rout, fileno($Jobs[$i]{fh}), 1) ); |
572 |
my $data; |
573 |
if ( sysread($Jobs[$i]{fh}, $data, 1024) <= 0 ) { |
574 |
vec($FDread, fileno($Jobs[$i]{fh}), 1) = 0; |
575 |
close($Jobs[$i]{fh}); |
576 |
next; |
577 |
} |
578 |
$Jobs[$i]{mesg} .= $data; |
579 |
while ( $Jobs[$i]{mesg} =~ /(.*?)[\n\r]+(.*)/s ) { |
580 |
my $mesg = $1; |
581 |
$Jobs[$i]{mesg} = $2; |
582 |
if ( $mesg =~ /^stats: (\d+) (\d+) (\d+) (\d+) (\d+) (\d+)/ ) { |
583 |
$Jobs[$i]{SubDirDone} = $1; |
584 |
$Jobs[$i]{SubDirCnt} = $2; |
585 |
$Jobs[$i]{FileCnt} = $3; |
586 |
$Jobs[$i]{FileOrigSz} = $4; |
587 |
$Jobs[$i]{FileCompressSz} = $5; |
588 |
$Jobs[$i]{Errors} = $6; |
589 |
$SubDirDone = $SubDirCnt = $FileCnt = $FileOrigSz = 0; |
590 |
$FileCompressSz = $Errors = 0; |
591 |
my $numReports = 0; |
592 |
for ( my $j = 0 ; $j < $nChild ; $j++ ) { |
593 |
next if ( !defined($Jobs[$j]{SubDirDone}) ); |
594 |
$SubDirDone += $Jobs[$j]{SubDirDone}; |
595 |
$SubDirCnt += $Jobs[$j]{SubDirCnt}; |
596 |
$FileCnt += $Jobs[$j]{FileCnt}; |
597 |
$FileOrigSz += $Jobs[$j]{FileOrigSz}; |
598 |
$FileCompressSz += $Jobs[$j]{FileCompressSz}; |
599 |
$Errors += $Jobs[$j]{Errors}; |
600 |
$numReports++; |
601 |
} |
602 |
$SubDirCnt ||= 1; |
603 |
$FileOrigSz ||= 1; |
604 |
my $pctDone = 100 * $SubDirDone / $SubDirCnt; |
605 |
if ( $numReports == $nChild && $pctDone >= $DonePct + 1 ) { |
606 |
$DonePct = int($pctDone); |
607 |
my $estSecLeft = 1.2 * (time - $TimeStart) |
608 |
* (100 / $pctDone - 1); |
609 |
my $timeStamp = $bpc->timeStamp; |
610 |
printf("%sDone %2.0f%% (%d of %d dirs, %d files," |
611 |
. " %.2fGB raw, %.1f%% reduce, %d errors)\n", |
612 |
$timeStamp, |
613 |
$pctDone, $SubDirDone, $SubDirCnt, $FileCnt, |
614 |
$FileOrigSz / (1024 * 1024 * 1000), |
615 |
100 * (1 - $FileCompressSz / $FileOrigSz)); |
616 |
printf("%s Est complete in %.1f hours (around %s)\n", |
617 |
$timeStamp, $estSecLeft / 3600, |
618 |
$bpc->timeStamp(time + $estSecLeft, 1)) |
619 |
if ( $DonePct < 100 ); |
620 |
} |
621 |
} else { |
622 |
print($mesg, "\n"); |
623 |
} |
624 |
} |
625 |
} |
626 |
} |
627 |
if ( $Errors ) { |
628 |
print("Finished with $Errors errors!!!!\n"); |
629 |
exit(1); |
630 |
} |