* Fixed encoding of email subject header in bin/BackupPC_sendEmail as
[BackupPC.git] / bin / BackupPC_compressPool
diff --git a/bin/BackupPC_compressPool b/bin/BackupPC_compressPool
deleted file mode 100755 (executable)
index e9845ef..0000000
+++ /dev/null
@@ -1,631 +0,0 @@
-#!/bin/perl
-#============================================================= -*-perl-*-
-#
-# BackupPC_compressPool: Compress existing pool
-#
-# DESCRIPTION
-#
-#   Usage: BackupPC_compressPool [-t] [-r] <host>
-#
-#   Flags:
-#     -t     test mode: do everything except actually replace the pool files.
-#            Useful for estimating total run time without making any real
-#            changes.
-#     -r     read check: re-read the compressed file and compare it against
-#            the original uncompressed file.  Can only be used in test mode.
-#     -c #   number of children to fork.  BackupPC_compressPool can take
-#            a long time to run, so to speed things up it spawns four children,
-#            each working on a different part of the pool.  You can change
-#            the number of children with the -c option.
-#
-#   BackupPC_compressPool is used to convert an uncompressed pool to
-#   a compressed pool.  If BackupPC compression is enabled after
-#   uncompressed backups already exist, BackupPC_compressPool can
-#   be used to compress all the old uncompressed backups.
-#
-#   It is important that BackupPC not run while BackupPC_compressPool
-#   runs.  Also, BackupPC_compressPool must run to completion before
-#   BackupPC is restarted.
-#
-# AUTHOR
-#   Craig Barratt  <cbarratt@users.sourceforge.net>
-#
-# COPYRIGHT
-#   Copyright (C) 2001-2007  Craig Barratt
-#
-#   This program is free software; you can redistribute it and/or modify
-#   it under the terms of the GNU General Public License as published by
-#   the Free Software Foundation; either version 2 of the License, or
-#   (at your option) any later version.
-#
-#   This program is distributed in the hope that it will be useful,
-#   but WITHOUT ANY WARRANTY; without even the implied warranty of
-#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-#   GNU General Public License for more details.
-#
-#   You should have received a copy of the GNU General Public License
-#   along with this program; if not, write to the Free Software
-#   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-#
-#========================================================================
-#
-# Version 3.1.0, released 25 Nov 2007.
-#
-# See http://backuppc.sourceforge.net.
-#
-#========================================================================
-
-use strict;
-no  utf8;
-
-use File::Find;
-use File::Path;
-use Compress::Zlib;
-use Getopt::Std;
-use lib "/usr/local/BackupPC/lib";
-use BackupPC::Lib;
-use BackupPC::FileZIO;
-
-die("BackupPC::Lib->new failed\n") if ( !(my $bpc = BackupPC::Lib->new) );
-$bpc->ChildInit();
-my $TopDir   = $bpc->TopDir();
-my $LogDir   = $bpc->LogDir();
-my $BinDir   = $bpc->BinDir();
-my %Conf     = $bpc->Conf();
-my $PoolDir  = "$TopDir/pool";
-my $CPoolDir = "$TopDir/cpool";
-my $Compress = $Conf{CompressLevel};
-my %opts;
-my $SigName = "";
-
-#
-# Catch various signals
-#
-foreach my $sig ( qw(INT BUS SEGV PIPE TERM ALRM HUP) ) {
-    $SIG{$sig} = \&catch_signal;
-}
-
-$| = 1;
-
-my $CompMaxRead  = 131072;          # 128K
-my $CompMaxWrite = 6291456;         # 6MB
-
-if ( !getopts("trc:", \%opts) || @ARGV != 0 ) {
-    print("usage: $0 [-c nChild] [-r] [-t]\n");
-    exit(1);
-}
-my $TestMode  = $opts{t};
-my $ReadCheck = $opts{r};
-my $nChild    = $opts{c} || 4;
-if ( $ReadCheck && !$TestMode ) {
-    print(STDERR "$0: -r (read check) option must have -t (test)\n");
-    exit(1);
-}
-if ( $nChild < 1 || $nChild >= 16 ) {
-    print(STDERR "$0: number of children (-c option) must be from 1 to 16\n");
-    exit(1);
-}
-if ( !BackupPC::FileZIO->compOk ) {
-    print STDERR <<EOF;
-$0: Compress::Zlib is not installed.   You need to install it
-before running this script.
-EOF
-    exit(1);
-}
-if ( $Compress <= 0 ) {
-    print STDERR <<EOF;
-$0: compression is not enabled. \%Conf{CompressLevel} needs
-to be set to a value from 1 to 9.  Please edit the config.pl file and
-re-start $0.
-EOF
-    exit(1);
-}
-
-my $Errors     = 0;
-my $SubDirDone = 0;
-my $SubDirCnt  = 0;
-my $SubDirCurr = 0;
-my $FileCnt    = 0;
-my $FileOrigSz = 0;
-my $FileCompressSz = 0;
-
-my $err = $bpc->ServerConnect($Conf{ServerHost}, $Conf{ServerPort});
-if ( $err eq "" ) {
-    print <<EOF;
-BackupPC is running on $Conf{ServerHost}.  You need to stop BackupPC
-before you can upgrade the code.  Depending upon your installation,
-you could run "/etc/init.d/backuppc stop".
-EOF
-    exit(1);
-}
-
-umask($Conf{UmaskMode});
-
-sub cpoolFileName
-{
-    my($new) = @_;
-    if ( $new !~ m{/(\w/\w/\w)/(\w{32})(_\d+)?$} ) {
-        print("Error: Can't parse filename from $new\n");
-        $Errors++;
-        return;
-    }
-    my $dir = "$CPoolDir/$1";
-    $new = "$dir/$2";
-    mkpath($dir, 0, 0777) if ( !-d $dir );
-    return $new if ( !-f $new );
-    for ( my $i = 0 ; ; $i++ ) {
-        return "${new}_$i" if ( !-f "${new}_$i" );
-    }
-}
-
-sub doCompress
-{
-    my $file = ($File::Find::name =~ /(.*)/ && $1);
-    local(*FH, *OUT);
-    my(@s) = stat($file);
-    my($n, $dataIn, $dataOut, $flush, $copy);
-
-    if ( $SigName ) {
-        print("Child got signal $SigName; quitting\n");
-        reportStats();
-        exit(0);
-    }
-    return if ( !-f $file );
-    my $defl = deflateInit(
-                -Bufsize => 65536,
-                -Level   => $Compress,
-           );
-    if ( !open(FH, $TestMode ? "<" : "+<", $file) ) {
-        print("Error: Can't open $file for read/write\n");
-        $Errors++;
-        return;
-    }
-    binmode(FH);
-    while ( sysread(FH, $dataIn, $CompMaxWrite) > 0 ) {
-        $flush = 0;
-        $FileOrigSz += length($dataIn);
-        my $fragOut = $defl->deflate($dataIn);
-        if ( length($fragOut) < $CompMaxRead ) {
-            #
-            # Compression is too high: to avoid huge memory requirements
-            # on read we need to flush().
-            #
-            $fragOut .= $defl->flush();
-            $flush = 1;
-            $defl = deflateInit(
-                        -Bufsize => 65536,
-                        -Level   => $Compress,
-                   );
-        }
-        $dataOut .= $fragOut;
-        if ( !$copy && length($dataOut) > $CompMaxWrite ) {
-            if ( !open(OUT, "+>", "$file.__z") ) {
-                print("Error: Can't open $file.__z for write\n");
-                $Errors++;
-                close(FH);
-                return;
-            }
-           binmode(OUT);
-            $copy = 1;
-        }
-        if ( $copy && $dataOut ne "" ) {
-            if ( syswrite(OUT, $dataOut) != length($dataOut) ) {
-                printf("Error: Can't write %d bytes to %s\n",
-                                    length($dataOut), "$file.__z");
-                $Errors++;
-                close(OUT);
-                close(FH);
-                unlink("$file.__z");
-                return;
-            }
-            $FileCompressSz += length($dataOut);
-            $dataOut = undef;
-        }
-    }
-    if ( !$flush ) {
-        $dataOut .= $defl->flush();
-        if ( $copy && $dataOut ne "" ) {
-            if ( syswrite(OUT, $dataOut) != length($dataOut) ) {
-                printf("Error: Can't write %d bytes to %s\n",
-                                    length($dataOut), "$file.__z");
-                $Errors++;
-                close(OUT);
-                close(FH);
-                unlink("$file.__z");
-                return;
-            }
-            $FileCompressSz += length($dataOut);
-            $dataOut = undef;
-        }
-    }
-    my $newFile = cpoolFileName($file);
-    if ( $TestMode ) {
-        close(FH);
-        if ( !open(FH, ">", $newFile) ) {
-            print("Error: Can't open $newFile for write\n");
-            $Errors++;
-            close(FH);
-            unlink("$file.__z");
-            return;
-        }
-       binmode(FH);
-    }
-    if ( $copy ) {
-        if ( !sysseek(OUT, 0, 0) ) {
-            print("Error: Can't seek $file.__z to 0\n");
-            $Errors++;
-        }
-        if ( !sysseek(FH, 0, 0) ) {
-            print("Error: Can't seek $newFile to 0\n");
-            $Errors++;
-        }
-        while ( sysread(OUT, $dataIn, $CompMaxWrite) > 0 ) {
-            if ( syswrite(FH, $dataIn) != length($dataIn) ) {
-                printf("Error: Can't write %d bytes to %s\n",
-                                        length($dataIn), $file);
-                $Errors++;
-            }
-        }
-        if ( !truncate(FH, sysseek(OUT, 0, 1)) ) {
-            printf("Error: Can't truncate %s to %d\n",
-                                        $file, sysseek(OUT, 0, 1));
-            $Errors++;
-        }
-        close(OUT);
-        close(FH);
-        unlink("$file.__z");
-    } else {
-        if ( !sysseek(FH, 0, 0) ) {
-            print("Error: Can't seek $file to 0\n");
-            $Errors++;
-        }
-        if ( syswrite(FH, $dataOut) != length($dataOut) ) {
-            printf("Error: Can't write %d bytes to %s\n",
-                                        length($dataOut), $file);
-            $Errors++;
-        }
-        $FileCompressSz += length($dataOut);
-        if ( !truncate(FH, length($dataOut)) ) {
-            printf("Error: Can't truncate %s to %d\n", $file, length($dataOut));
-            $Errors++;
-        }
-        close(FH);
-    }
-    if ( $TestMode ) {
-        if ( $ReadCheck ) {
-            checkRead($file, $newFile);
-        }
-        unlink($newFile);
-    } else {
-        rename($file, $newFile);
-        my $atime = $s[8] =~ /(.*)/ && $1;
-        my $mtime = $s[9] =~ /(.*)/ && $1;
-        utime($atime, $mtime, $newFile);
-    }
-    (my $dir = $file) =~ s{/[^/]*$}{};
-    $FileCnt++;
-    if ( $SubDirCurr ne "" && $SubDirCurr ne $dir ) {
-        $SubDirDone++;
-        $SubDirCurr = $dir;
-        reportStats();
-    } elsif ( $SubDirCurr eq "" ) {
-        $SubDirCurr = $dir;
-    }
-}
-
-sub reportStats
-{
-    print("stats: $SubDirDone $SubDirCnt $FileCnt $FileOrigSz"
-                . " $FileCompressSz $Errors\n");
-}
-
-sub checkRead
-{
-    my($file, $cfile) = @_;
-    return if ( !-f $file || !-f $cfile );
-    my $f = BackupPC::FileZIO->open($cfile, 0, $Compress)
-                                || die("can't open $cfile for read\n");
-    my($n, $nd, $r, $d, $d0);
-    local(*FH);
-
-    if ( !open(FH, "<", $file) ) {
-        print("can't open $file for check\n");
-        $Errors++;
-        $f->close();
-        return;
-    }
-    binmode(FH);
-    #print("comparing $file to $cfile\n");
-    while ( 1 ) {
-        $n = 1 + int(rand($CompMaxRead) + rand(100));
-        $r = $f->read(\$d, $n);
-        sysread(FH, $d0, $n);
-        if ( $d ne $d0 ) {
-            print("Botch read data on $cfile\n");
-        }
-        last if ( length($d) == 0 );
-    }
-    if ( ($r = $f->read(\$d, 100)) != 0 || ($r = $f->read(\$d, 100)) != 0 ) {
-        printf("Botch at EOF on $cfile got $r (%d,%d)\n",
-                        sysseek(FH, 0, 1), $n);
-        $Errors++;
-    }
-    $f->close;
-    close(FH);
-}
-
-sub checkReadLine
-{
-    my($file, $cfile) = @_;
-    return if ( !-f $file || !-f $cfile );
-    my $f = BackupPC::FileZIO->open($cfile, 0, $Compress)
-                                || die("can't open $cfile for read\n");
-    my($n, $nd, $r, $d, $d0);
-    local(*FH);
-
-    if ( !open(FH, "<", $file) ) {
-        print("can't open $file for check\n");
-        $Errors++;
-        $f->close();
-        return;
-    }
-    binmode(FH);
-    while ( 1 ) {
-        $d0 = <FH>;
-        $d  = $f->readLine();
-        if ( $d ne $d0 ) {
-            print("Botch read data on $cfile\n");
-        }
-        last if ( length($d) == 0 );
-    }
-    if ( ($r = $f->read(\$d, 100)) != 0 || ($r = $f->read(\$d, 100)) != 0 ) {
-        printf("Botch at EOF on $cfile got $r (%d,%d)\n",
-                        sysseek(FH, 0, 1), $n);
-        $Errors++;
-    }
-    $f->close;
-    close(FH);
-}
-
-sub catch_signal
-{
-    $SigName = shift;
-}
-
-sub compressHostFiles
-{
-    my($host) = @_;
-    my(@Files, @Backups, $fh, $data);
-    local(*FH);
-
-    if ( !defined($host) ) {
-        for ( my $i = 0 ; ; $i++ ) {
-            last if ( !-f "$LogDir/LOG.$i" );
-            push(@Files, "$LogDir/LOG.$i");
-        }
-    } else {
-        @Backups = $bpc->BackupInfoRead($host);
-        for ( my $i = 0 ; $i < @Backups ; $i++ ) {
-            next if ( $Backups[$i]{compress} );
-            push(@Files, "$TopDir/pc/$host/SmbLOG.$Backups[$i]{num}");
-            push(@Files, "$TopDir/pc/$host/XferLOG.$Backups[$i]{num}");
-        }
-        push(@Files, "$TopDir/pc/$host/SmbLOG.bad");
-        push(@Files, "$TopDir/pc/$host/XferLOG.bad");
-        for ( my $i = 0 ; ; $i++ ) {
-            last if ( !-f "$TopDir/pc/$host/LOG.$i" );
-            push(@Files, "$TopDir/pc/$host/LOG.$i");
-        }
-    }
-    foreach my $file ( @Files ) {
-        if ( $SigName ) {
-            print("Child got signal $SigName; quitting\n");
-            reportStats();
-            exit(0);
-        }
-        next if ( !-f $file );
-        if ( !BackupPC::FileZIO->compressCopy($file, "$file.z", undef,
-                                        $Compress, !$TestMode) ) {
-            print("compressCopy($file, $file.z, $Compress, !$TestMode)"
-                . " failed\n");
-            $Errors++;
-        } elsif ( $TestMode ) {
-            checkReadLine($file, "$file.z") if ( $ReadCheck );
-            unlink("$file.z");
-        }
-    }
-}
-
-sub updateHostBackupInfo
-{
-    my($host) = @_;
-    if ( !$TestMode ) {
-        my @Backups = $bpc->BackupInfoRead($host);
-        for ( my $i = 0 ; $i < @Backups ; $i++ ) {
-            $Backups[$i]{compress} = $Compress;
-        }
-        $bpc->BackupInfoWrite($host, @Backups);
-    }
-}
-
-my @Dirs = split(//, "0123456789abcdef");
-my @Hosts = sort(keys(%{$bpc->HostInfoRead()}));
-my $FDread;
-my @Jobs;
-
-#
-# First make sure there are no existing compressed backups
-#
-my(%compHosts, $compCnt);
-for ( my $j = 0 ; $j < @Hosts ; $j++ ) {
-    my $host = $Hosts[$j];
-    my @Backups = $bpc->BackupInfoRead($host);
-    for ( my $i = 0 ; $i < @Backups ; $i++ ) {
-        next if ( !$Backups[$i]{compress} );
-        $compHosts{$host}++;
-        $compCnt++;
-    }
-}
-if ( $compCnt ) {
-    my $compHostStr = join("\n  + ", sort(keys(%compHosts)));
-    print STDERR <<EOF;
-BackupPC_compressPool: there are $compCnt compressed backups.
-BackupPC_compressPool can only be run when there are no existing
-compressed backups. The following hosts have compressed backups:
-
-  + $compHostStr
-
-If you really want to run BackupPC_compressPool you will need to remove
-all the existing compressed backups (and /home/pcbackup/data/cpool).
-Think carefully before you do this. Otherwise, you can just let new
-compressed backups run and the old uncompressed backups and pool will
-steadily expire.
-EOF
-    exit(0);
-}
-
-#
-# Next spawn $nChild children that actually do all the work.
-#
-for ( my $i = 0 ; $i < $nChild ; $i++ ) {
-    local(*CHILD);
-    my $pid;
-    if ( !defined($pid = open(CHILD, "-|")) ) {
-        print("Can't fork\n");
-        next;
-    }
-    my $nDirs  = @Dirs  / ($nChild - $i);
-    my $nHosts = @Hosts / ($nChild - $i);
-    if ( !$pid ) {
-        #
-        # This is the child.
-        # First process each of the hosts (compress per-pc log files etc).
-        #
-        for ( my $j = 0 ; $j < $nHosts ; $j++ ) {
-            compressHostFiles($Hosts[$j]);
-        }
-        #
-        # Count the total number of directories so we can estimate the
-        # completion time.  We ignore empty directories by reading each
-        # directory and making sure it has at least 3 entries (ie, ".",
-        # ".." and a file).
-        #
-        for ( my $j = 0 ; $j < $nDirs ; $j++ ) {
-            my $thisDir = $Dirs[$j];
-            next if ( !-d "$PoolDir/$thisDir" );
-            foreach my $dir ( <$PoolDir/$thisDir/*/*> ) {
-                next if ( !opendir(DIR, $dir) );
-                my @files = readdir(DIR);
-                closedir(DIR);
-                $SubDirCnt++ if ( @files > 2 );
-            }
-        }
-        #
-        # Now process each of the directories
-        #
-        for ( my $j = 0 ; $j < $nDirs ; $j++ ) {
-            my $thisDir = shift(@Dirs);
-            next if ( !-d "$PoolDir/$thisDir" );
-            find({wanted => sub { doCompress($File::Find::name); },
-                                   no_chdir => 1}, "$PoolDir/$thisDir");
-        }
-        #
-        # Last, update the backup info file for each of the hosts
-        #
-        for ( my $j = 0 ; $j < $nHosts ; $j++ ) {
-            updateHostBackupInfo($Hosts[$j]);
-        }
-        $SubDirDone = $SubDirCnt;
-        reportStats();
-        exit(0);
-    }
-    #
-    # This is the parent.  Peel off $nDirs directories, $nHosts hosts,
-    # and continue
-    #
-    $Jobs[$i]{fh}  = *CHILD;
-    $Jobs[$i]{pid} = $pid;
-    vec($FDread, fileno($Jobs[$i]{fh}), 1) = 1;
-    splice(@Dirs,  0, $nDirs);
-    splice(@Hosts, 0, $nHosts);
-}
-
-#
-# compress the main log files (in the parents)
-#
-compressHostFiles(undef);
-
-#
-# Now wait for all the children to report results and finish up
-#
-my $TimeStart = time;
-my $DonePct   = 0;
-my $GotSignal = "";
-while ( $FDread !~ /^\0*$/ ) {
-    my $ein = $FDread;
-    select(my $rout = $FDread, undef, $ein, undef);
-    if ( $SigName ne $GotSignal ) {
-        print("Got signal $SigName; waiting for $nChild children to cleanup\n");
-        $GotSignal = $SigName;
-    }
-    for ( my $i = 0 ; $i < $nChild ; $i++ ) {
-        next if ( !vec($rout, fileno($Jobs[$i]{fh}), 1) );
-        my $data;
-        if ( sysread($Jobs[$i]{fh}, $data, 1024) <= 0 ) {
-            vec($FDread, fileno($Jobs[$i]{fh}), 1) = 0;
-            close($Jobs[$i]{fh});
-            next;
-        }
-        $Jobs[$i]{mesg} .= $data;
-        while ( $Jobs[$i]{mesg} =~ /(.*?)[\n\r]+(.*)/s ) {
-            my $mesg = $1;
-            $Jobs[$i]{mesg} = $2;
-            if ( $mesg =~ /^stats: (\d+) (\d+) (\d+) (\d+) (\d+) (\d+)/ ) {
-                $Jobs[$i]{SubDirDone}     = $1;
-                $Jobs[$i]{SubDirCnt}      = $2;
-                $Jobs[$i]{FileCnt}        = $3;
-                $Jobs[$i]{FileOrigSz}     = $4;
-                $Jobs[$i]{FileCompressSz} = $5;
-                $Jobs[$i]{Errors}         = $6;
-                $SubDirDone = $SubDirCnt = $FileCnt = $FileOrigSz = 0;
-                $FileCompressSz = $Errors = 0;
-                my $numReports = 0;
-                for ( my $j = 0 ; $j < $nChild ; $j++ ) {
-                    next if ( !defined($Jobs[$j]{SubDirDone}) );
-                    $SubDirDone     += $Jobs[$j]{SubDirDone};
-                    $SubDirCnt      += $Jobs[$j]{SubDirCnt};
-                    $FileCnt        += $Jobs[$j]{FileCnt};
-                    $FileOrigSz     += $Jobs[$j]{FileOrigSz};
-                    $FileCompressSz += $Jobs[$j]{FileCompressSz};
-                    $Errors         += $Jobs[$j]{Errors};
-                    $numReports++;
-                }
-                $SubDirCnt  ||= 1;
-                $FileOrigSz ||= 1;
-                my $pctDone = 100 * $SubDirDone / $SubDirCnt;
-                if ( $numReports == $nChild && $pctDone >= $DonePct + 1 ) {
-                    $DonePct = int($pctDone);
-                    my $estSecLeft = 1.2 * (time - $TimeStart)
-                                         * (100 / $pctDone - 1);
-                    my $timeStamp = $bpc->timeStamp;
-                    printf("%sDone %2.0f%% (%d of %d dirs, %d files,"
-                            . " %.2fGB raw, %.1f%% reduce, %d errors)\n",
-                                $timeStamp,
-                                $pctDone, $SubDirDone, $SubDirCnt, $FileCnt,
-                                $FileOrigSz / (1024 * 1024 * 1000),
-                                100 * (1 - $FileCompressSz / $FileOrigSz));
-                    printf("%s    Est complete in %.1f hours (around %s)\n",
-                                $timeStamp, $estSecLeft / 3600,
-                                $bpc->timeStamp(time + $estSecLeft, 1))
-                                            if ( $DonePct < 100 );
-                }
-            } else {
-                print($mesg, "\n");
-            }
-        }
-    }
-}
-if ( $Errors ) {
-    print("Finished with $Errors errors!!!!\n");
-    exit(1);
-}