-#!/bin/perl -T
+#!/usr/bin/perl
#============================================================= -*-perl-*-
#
# BackupPC_zipCreate: create a zip archive of an existing dump
#
# DESCRIPTION
#
-# Usage: BackupPC_zipCreate [-t] [-h host] [-n dumpNum] [-s shareName]
-# [-r pathRemove] [-p pathAdd] [-c compressionLevel]
-# files/directories...
+# Usage: BackupPC_zipCreate [options] files/directories...
#
# Flags:
# Required options:
-#
# -h host host from which the zip archive is created
# -n dumpNum dump number from which the zip archive is created
+# A negative number means relative to the end (eg -1
+# means the most recent dump, -2 2nd most recent etc).
# -s shareName share name from which the zip archive is created
#
# Other options:
# -r pathRemove path prefix that will be replaced with pathAdd
# -p pathAdd new path prefix
# -c level compression level (default is 0, no compression)
+# -e charset charset for encoding file names (default: cp1252)
#
# The -h, -n and -s options specify which dump is used to generate
# the zip archive. The -r and -p options can be used to relocate
# Based on Backup_tarCreate by Craig Barratt <cbarratt@users.sourceforge.net>
#
# COPYRIGHT
-# Copyright (C) 2002 Craig Barratt and Guillaume Filion
+# Copyright (C) 2002-2009 Craig Barratt and Guillaume Filion
#
# 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
#
#========================================================================
#
-# Version 1.5.0, released 2 Aug 2002.
+# Version 3.2.0, released 31 Jul 2010.
#
# See http://backuppc.sourceforge.net.
#
#========================================================================
use strict;
-use lib "__INSTALLDIR__/lib";
+no utf8;
+use lib "/usr/local/BackupPC/lib";
use Archive::Zip qw(:ERROR_CODES);
use File::Path;
use Getopt::Std;
+use Encode qw/from_to/;
use IO::Handle;
use BackupPC::Lib;
use BackupPC::Attrib qw(:all);
use BackupPC::FileZIO;
use BackupPC::Zip::FileMember;
+use BackupPC::View;
die("BackupPC::Lib->new failed\n") if ( !(my $bpc = BackupPC::Lib->new) );
my $TopDir = $bpc->TopDir();
my %Conf = $bpc->Conf();
my %opts;
-getopts("th:n:p:r:s:c:", \%opts);
-if ( @ARGV < 1 ) {
- print(STDERR "usage: $0 [-t] [-h host] [-n dumpNum] [-s shareName]"
- . " [-r pathRemove] [-p pathAdd] [-c compressionLevel]"
- . " files/directories...\n");
+if ( !getopts("te:h:n:p:r:s:c:", \%opts) || @ARGV < 1 ) {
+ print STDERR <<EOF;
+usage: $0 [options] files/directories...
+ Required options:
+ -h host host from which the zip archive is created
+ -n dumpNum dump number from which the tar archive is created
+ A negative number means relative to the end (eg -1
+ means the most recent dump, -2 2nd most recent etc).
+ -s shareName share name from which the zip archive is created
+
+ Other options:
+ -t print summary totals
+ -r pathRemove path prefix that will be replaced with pathAdd
+ -p pathAdd new path prefix
+ -c level compression level (default is 0, no compression)
+ -e charset charset for encoding file names (default: cp1252)
+EOF
exit(1);
}
-if ( $opts{h} !~ /^([\w\.-]+)$/ ) {
+if ( $opts{h} !~ /^([\w\.\s-]+)$/
+ || $opts{h} =~ m{(^|/)\.\.(/|$)} ) {
print(STDERR "$0: bad host name '$opts{h}'\n");
exit(1);
}
my $Host = $opts{h};
-if ( $opts{n} !~ /^(\d+)$/ ) {
+if ( $opts{n} !~ /^(-?\d+)$/ ) {
print(STDERR "$0: bad dump number '$opts{n}'\n");
exit(1);
}
my $compLevel = $opts{c};
my @Backups = $bpc->BackupInfoRead($Host);
-my($Compress, $Mangle, $CompressF, $MangleF, $NumF, $i);
my $FileCnt = 0;
my $ByteCnt = 0;
my $DirCnt = 0;
my $SpecialCnt = 0;
my $ErrorCnt = 0;
+my $i;
+$Num = $Backups[@Backups + $Num]{num} if ( -@Backups <= $Num && $Num < 0 );
for ( $i = 0 ; $i < @Backups ; $i++ ) {
- if ( !$Backups[$i]{noFill} ) {
- #
- # Remember the most recent filled backup
- #
- $NumF = $Backups[$i]{num};
- $MangleF = $Backups[$i]{mangle};
- $CompressF = $Backups[$i]{compress};
- }
- next if ( $Backups[$i]{num} != $Num );
- $Compress = $Backups[$i]{compress};
- $Mangle = $Backups[$i]{mangle};
- if ( !$Backups[$i]{noFill} ) {
- # no need to back-fill a filled backup
- $NumF = $MangleF = $CompressF = undef;
- }
- last;
+ last if ( $Backups[$i]{num} == $Num );
}
if ( $i >= @Backups ) {
print(STDERR "$0: bad backup number $Num for host $Host\n");
exit(1);
}
+my $Charset = "cp1252";
+$Charset = $opts{e} if ( $opts{e} ne "" );
+
my $PathRemove = $1 if ( $opts{r} =~ /(.+)/ );
my $PathAdd = $1 if ( $opts{p} =~ /(.+)/ );
-if ( $opts{s} !~ /^([\w\s\.\/\$-]+)$/ ) {
+if ( $opts{s} =~ m{(^|/)\.\.(/|$)} ) {
print(STDERR "$0: bad share name '$opts{s}'\n");
exit(1);
}
-my $ShareNameOrig = $opts{s};
-my $ShareName = $Mangle ? $bpc->fileNameEltMangle($ShareNameOrig)
- : $ShareNameOrig;
-my $ShareNameF = $MangleF ? $bpc->fileNameEltMangle($ShareNameOrig)
- : $ShareNameOrig;
+my $ShareName = $opts{s};
my $BufSize = 1048576; # 1MB or 2^20
my(%UidCache, %GidCache);
$fh->fdopen(fileno(STDOUT),"w");
my $zipfh = Archive::Zip->new();
+binmode(STDOUT);
foreach my $dir ( @ARGV ) {
archiveWrite($zipfh, $dir);
}
sub archiveWrite
{
my($zipfh, $dir, $zipPathOverride) = @_;
+
+ my $view = BackupPC::View->new($bpc, $Host, \@Backups);
+
if ( $dir =~ m{(^|/)\.\.(/|$)} || $dir !~ /^(.*)$/ ) {
print(STDERR "$0: bad directory '$dir'\n");
$ErrorCnt++;
- next;
- }
- (my $DirOrig = $1) =~ s{/+$}{};
- $DirOrig =~ s{^\.?/+}{};
- my($Dir, $DirF, $FullPath, $FullPathF);
- if ( $DirOrig eq "" ) {
- $Dir = $DirF = "";
- $FullPath = "$TopDir/pc/$Host/$Num/$ShareName";
- $FullPathF = "$TopDir/pc/$Host/$NumF/$ShareNameF"
- if ( defined($NumF) );
- } else {
- $Dir = $Mangle ? $bpc->fileNameMangle($DirOrig) : $DirOrig;
- $DirF = $MangleF ? $bpc->fileNameMangle($DirOrig) : $DirOrig;
- $FullPath = "$TopDir/pc/$Host/$Num/$ShareName/$Dir";
- $FullPathF = "$TopDir/pc/$Host/$NumF/$ShareNameF/$DirF"
- if ( defined($NumF) );
- }
- if ( -f $FullPath ) {
- ZipWriteFile($zipfh, $FullPath, $Mangle, $Compress, $zipPathOverride);
- } elsif ( -d $FullPath || (defined($NumF) && -d $FullPathF) ) {
- MergeFind($zipfh, $FullPath, $FullPathF);
- } elsif ( defined($NumF) && -f $FullPathF ) {
- ZipWriteFile($zipfh, $FullPathF, $MangleF, $CompressF,
- $zipPathOverride);
- } else {
- print(STDERR "$0: $Host, backup $Num, doesn't have a directory or file"
- . " $ShareNameOrig/$DirOrig\n");
- $ErrorCnt++;
+ return;
}
+ $dir = "/" if ( $dir eq "." );
+ $view->find($Num, $ShareName, $dir, 0, \&ZipWriteFile,
+ $zipfh, $zipPathOverride);
}
# Create Zip file
sub ZipWriteFile
{
- my($zipfh, $fullName, $mangle, $compress, $zipPathOverride) = @_;
- my($tarPath);
+ my($hdr, $zipfh, $zipPathOverride) = @_;
- if ( $fullName =~ m{^\Q$TopDir/pc/$Host/$Num/$ShareName\E(.*)}
- || (defined($NumF)
- && $fullName =~ m{^\Q$TopDir/pc/$Host/$NumF/$ShareNameF\E(.*)}) ) {
- $tarPath = $mangle ? $bpc->fileNameUnmangle($1) : $1;
- } else {
- print(STDERR "Unexpected file name from find: $fullName\n");
- return;
- }
+ my $tarPath = $hdr->{relPath};
$tarPath = $zipPathOverride if ( defined($zipPathOverride) );
- (my $dir = $fullName) =~ s{/([^/]*)$}{};
- my $fileName = $mangle ? $bpc->fileNameUnmangle($1) : $1;
- if ( $mangle && $AttrDir ne $dir ) {
- $AttrDir = $dir;
- $Attr = BackupPC::Attrib->new({ compress => $compress });
- if ( -f $Attr->fileName($dir) && !$Attr->read($dir) ) {
- print(STDERR "Can't read attribute file in $dir\n");
- $ErrorCnt++;
- $Attr = undef;
- }
- }
- my $hdr = $Attr->get($fileName) if ( defined($Attr) );
- if ( !defined($hdr) ) {
- #
- # No attributes. Must be an old style backup. Reconstruct
- # what we can. Painful part is computing the size if compression
- # is on: only method is to uncompress the file.
- #
- my @s = stat($fullName);
- $hdr = {
- type => -d _ ? BPC_FTYPE_DIR : BPC_FTYPE_FILE,
- mode => $s[2],
- uid => $s[4],
- gid => $s[5],
- size => -f _ ? $s[7] : 0,
- mtime => $s[9],
- };
- if ( $compress && -f _ ) {
- #
- # Compute the correct size by reading the whole file
- #
- my $f = BackupPC::FileZIO->open($fullName, 0, $compress);
- if ( !defined($f) ) {
- print(STDERR "Unable to open file $fullName\n");
- $ErrorCnt++;
- return;
- }
- my($data, $size);
- while ( $f->read(\$data, $BufSize) > 0 ) {
- $size += length($data);
- }
- $f->close;
- $hdr->{size} = $size;
- }
- }
+
if ( defined($PathRemove)
&& substr($tarPath, 0, length($PathRemove)) eq $PathRemove ) {
substr($tarPath, 0, length($PathRemove)) = $PathAdd;
}
- $tarPath = "./" . $tarPath if ( $tarPath !~ /^\.\// );
+ $tarPath = $1 if ( $tarPath =~ m{^\.?/+(.*)} );
$tarPath =~ s{//+}{/}g;
$hdr->{name} = $tarPath;
+ return if ( $tarPath eq "." || $tarPath eq "./" || $tarPath eq "" );
+
my $zipmember; # Container to hold the file/directory to zip.
if ( $hdr->{type} == BPC_FTYPE_DIR ) {
# Directory: just write the header
#
$hdr->{name} .= "/" if ( $hdr->{name} !~ m{/$} );
+ from_to($hdr->{name}, "utf8", $Charset) if ( $Charset ne "" );
$zipmember = Archive::Zip::Member->newDirectoryNamed($hdr->{name});
$DirCnt++;
} elsif ( $hdr->{type} == BPC_FTYPE_FILE ) {
#
# Regular file: write the header and file
#
+ from_to($hdr->{name}, "utf8", $Charset) if ( $Charset ne "" );
$zipmember = BackupPC::Zip::FileMember->newFromFileNamed(
- $fullName,
+ $hdr->{fullPath},
$hdr->{name},
$hdr->{size},
- $compress
+ $hdr->{compress}
);
$FileCnt++;
$ByteCnt += $hdr->{size};
#
# Start by reading the contents of the link.
#
- my $f = BackupPC::FileZIO->open($fullName, 0, $compress);
+ my $f = BackupPC::FileZIO->open($hdr->{fullPath}, 0, $hdr->{compress});
if ( !defined($f) ) {
- print(STDERR "Unable to open file $fullName\n");
+ print(STDERR "Unable to open file $hdr->{fullPath}\n");
$ErrorCnt++;
return;
}
}
return if ( !$zipmember );
- # Set the attributes and permissions
- $zipmember->setLastModFileDateTimeFromUnix($hdr->{mtime});
+ #
+ # Set the attributes and permissions. The standard zip file
+ # header cannot handle dates prior to 1/1/1980, or 315561600
+ # unix seconds, so we round up the mtime.
+ #
+ my $mtime = $hdr->{mtime};
+ $mtime = 315561600 if ( $mtime < 315561600 );
+ $zipmember->setLastModFileDateTimeFromUnix($mtime);
$zipmember->unixFileAttributes($hdr->{mode});
# Zip files don't accept uid and gid, so we put them in the comment field.
$zipmember->fileComment("uid=".$hdr->{uid}." gid=".$hdr->{gid})
# Finally Zip the member
$zipfh->addMember($zipmember);
}
-
-#
-# Does a recursive find of $dir, filling in from the (filled dump)
-# directory $dirF. Handles the cases where $dir and $dirF might
-# or might not be mangled etc.
-#
-sub MergeFind
-{
- my($zipfh, $dir, $dirF) = @_;
-
- my(@Dir, $fLast);
- if ( -d $dir ) {
- ZipWriteFile($zipfh, $dir, $Mangle, $Compress);
- } elsif ( -d $dirF ) {
- ZipWriteFile($zipfh, $dirF, $MangleF, $CompressF);
- }
- if ( opendir(DIR, $dir) ) {
- @Dir = readdir(DIR);
- closedir(DIR);
- }
- if ( defined($NumF) && opendir(DIR, $dirF) ) {
- if ( $Mangle == $MangleF ) {
- @Dir = (@Dir, readdir(DIR));
- } else {
- foreach my $f ( readdir(DIR) ) {
- if ( $Mangle ) {
- push(@Dir, $bpc->fileNameMangle($f));
- } else {
- push(@Dir, $bpc->fileNameUnmangle($f));
- }
- }
- }
- }
- foreach my $f ( sort({$a cmp $b} @Dir) ) {
- next if ( $f eq "." || $f eq ".."
- || $f eq $fLast || ($Mangle && $f eq "attrib") );
- $fLast = $f;
- my($fF) = $f;
- if ( $Mangle != $MangleF ) {
- $fF = $Mangle ? $bpc->fileNameUnmangle($f)
- : $bpc->fileNameMangle($f);
- }
- if ( -e "$dir/$f" ) {
- if ( -d "$dir/$f" ) {
- MergeFind($zipfh, "$dir/$f", "$dirF/$fF");
- } else {
- ZipWriteFile($zipfh, "$dir/$f", $Mangle, $Compress);
- }
- } elsif ( -e "$dirF/$fF" ) {
- if ( -d "$dirF/$fF" ) {
- MergeFind($zipfh, "$dir/$f", "$dirF/$fF");
- } else {
- ZipWriteFile($zipfh, "$dirF/$fF", $MangleF, $CompressF);
- }
- } else {
- print(STDERR "$0: Botch on $dir, $dirF, $f, $fF\n");
- $ErrorCnt++;
- }
- }
-}