2 #============================================================= -*-perl-*-
4 # BackupPC_zipCreate: create a zip archive of an existing dump
5 # for restore on a client.
9 # Usage: BackupPC_zipCreate [options] files/directories...
13 # -h host host from which the zip archive is created
14 # -n dumpNum dump number from which the zip archive is created
15 # A negative number means relative to the end (eg -1
16 # means the most recent dump, -2 2nd most recent etc).
17 # -s shareName share name from which the zip archive is created
20 # -t print summary totals
21 # -r pathRemove path prefix that will be replaced with pathAdd
22 # -p pathAdd new path prefix
23 # -c level compression level (default is 0, no compression)
24 # -e charset charset for encoding file names (default: value of
25 # $Conf{ClientCharset} when backup was done)
27 # The -h, -n and -s options specify which dump is used to generate
28 # the zip archive. The -r and -p options can be used to relocate
29 # the paths in the zip archive so extracted files can be placed
30 # in a location different from their original location.
33 # Guillaume Filion <gfk@users.sourceforge.net>
34 # Based on Backup_tarCreate by Craig Barratt <cbarratt@users.sourceforge.net>
37 # Copyright (C) 2002-2003 Craig Barratt and Guillaume Filion
39 # This program is free software; you can redistribute it and/or modify
40 # it under the terms of the GNU General Public License as published by
41 # the Free Software Foundation; either version 2 of the License, or
42 # (at your option) any later version.
44 # This program is distributed in the hope that it will be useful,
45 # but WITHOUT ANY WARRANTY; without even the implied warranty of
46 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
47 # GNU General Public License for more details.
49 # You should have received a copy of the GNU General Public License
50 # along with this program; if not, write to the Free Software
51 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
53 #========================================================================
55 # Version 3.0.0beta0, released 11 Jul 2006.
57 # See http://backuppc.sourceforge.net.
59 #========================================================================
63 use lib "/usr/local/BackupPC/lib";
64 use Archive::Zip qw(:ERROR_CODES);
67 use Encode qw/from_to/;
70 use BackupPC::Attrib qw(:all);
71 use BackupPC::FileZIO;
72 use BackupPC::Zip::FileMember;
75 die("BackupPC::Lib->new failed\n") if ( !(my $bpc = BackupPC::Lib->new) );
76 my $TopDir = $bpc->TopDir();
77 my $BinDir = $bpc->BinDir();
78 my %Conf = $bpc->Conf();
82 if ( !getopts("te:h:n:p:r:s:c:", \%opts) || @ARGV < 1 ) {
84 usage: $0 [options] files/directories...
86 -h host host from which the zip archive is created
87 -n dumpNum dump number from which the tar archive is created
88 A negative number means relative to the end (eg -1
89 means the most recent dump, -2 2nd most recent etc).
90 -s shareName share name from which the zip archive is created
93 -t print summary totals
94 -r pathRemove path prefix that will be replaced with pathAdd
95 -p pathAdd new path prefix
96 -c level compression level (default is 0, no compression)
97 -e charset charset for encoding file names (default: value of
98 \$Conf{ClientCharset} when backup was done)
103 if ( $opts{h} !~ /^([\w\.\s-]+)$/
104 || $opts{h} =~ m{(^|/)\.\.(/|$)} ) {
105 print(STDERR "$0: bad host name '$opts{h}'\n");
110 if ( $opts{n} !~ /^(-?\d+)$/ ) {
111 print(STDERR "$0: bad dump number '$opts{n}'\n");
116 $opts{c} = 0 if ( $opts{c} eq "" );
117 if ( $opts{c} !~ /^(\d+)$/ ) {
118 print(STDERR "$0: invalid compression level '$opts{c}'. 0=none, 9=max\n");
121 my $compLevel = $opts{c};
123 my @Backups = $bpc->BackupInfoRead($Host);
131 $Num = $Backups[@Backups + $Num]{num} if ( -@Backups <= $Num && $Num < 0 );
132 for ( $i = 0 ; $i < @Backups ; $i++ ) {
133 last if ( $Backups[$i]{num} == $Num );
135 if ( $i >= @Backups ) {
136 print(STDERR "$0: bad backup number $Num for host $Host\n");
140 my $Charset = $Backups[$i]{charset};
141 $Charset = $opts{e} if ( $opts{e} ne "" );
143 my $PathRemove = $1 if ( $opts{r} =~ /(.+)/ );
144 my $PathAdd = $1 if ( $opts{p} =~ /(.+)/ );
145 if ( $opts{s} !~ /^([\w\s.@\/$(){}[\]-]+)$/
146 || $opts{s} =~ m{(^|/)\.\.(/|$)} ) {
147 print(STDERR "$0: bad share name '$opts{s}'\n");
150 my $ShareName = $opts{s};
152 my $BufSize = 1048576; # 1MB or 2^20
153 my(%UidCache, %GidCache);
155 my $fh = new IO::Handle;
156 $fh->fdopen(fileno(STDOUT),"w");
157 my $zipfh = Archive::Zip->new();
160 foreach my $dir ( @ARGV ) {
161 archiveWrite($zipfh, $dir);
166 my($zipfh, $dir, $zipPathOverride) = @_;
168 my $view = BackupPC::View->new($bpc, $Host, \@Backups);
170 if ( $dir =~ m{(^|/)\.\.(/|$)} || $dir !~ /^(.*)$/ ) {
171 print(STDERR "$0: bad directory '$dir'\n");
175 $dir = "/" if ( $dir eq "." );
176 $view->find($Num, $ShareName, $dir, 0, \&ZipWriteFile,
177 $zipfh, $zipPathOverride);
181 print STDERR "Can't write Zip file\n"
182 unless $zipfh->writeToFileHandle($fh, 0) == Archive::Zip::AZ_OK;
185 # print out totals if requested
188 print STDERR "Done: $FileCnt files, $ByteCnt bytes, $DirCnt dirs,",
189 " $SpecialCnt specials ignored, $ErrorCnt errors\n";
193 ###########################################################################
195 ###########################################################################
201 $UidCache{$uid} = (getpwuid($uid))[0] if ( !exists($UidCache{$uid}) );
202 return $UidCache{$uid};
209 $GidCache{$gid} = (getgrgid($gid))[0] if ( !exists($GidCache{$gid}) );
210 return $GidCache{$gid};
218 my($hdr, $zipfh, $zipPathOverride) = @_;
220 my $tarPath = $hdr->{relPath};
221 $tarPath = $zipPathOverride if ( defined($zipPathOverride) );
223 if ( defined($PathRemove)
224 && substr($tarPath, 0, length($PathRemove)) eq $PathRemove ) {
225 substr($tarPath, 0, length($PathRemove)) = $PathAdd;
227 $tarPath = $1 if ( $tarPath =~ m{^\.?/+(.*)} );
228 $tarPath =~ s{//+}{/}g;
229 $hdr->{name} = $tarPath;
230 return if ( $tarPath eq "." || $tarPath eq "./" || $tarPath eq "" );
232 my $zipmember; # Container to hold the file/directory to zip.
234 if ( $hdr->{type} == BPC_FTYPE_DIR ) {
236 # Directory: just write the header
238 $hdr->{name} .= "/" if ( $hdr->{name} !~ m{/$} );
239 from_to($hdr->{name}, "utf8", $Charset) if ( $Charset ne "" );
240 $zipmember = Archive::Zip::Member->newDirectoryNamed($hdr->{name});
242 } elsif ( $hdr->{type} == BPC_FTYPE_FILE ) {
244 # Regular file: write the header and file
246 from_to($hdr->{name}, "utf8", $Charset) if ( $Charset ne "" );
247 $zipmember = BackupPC::Zip::FileMember->newFromFileNamed(
254 $ByteCnt += $hdr->{size};
255 } elsif ( $hdr->{type} == BPC_FTYPE_HARDLINK ) {
257 # Hardlink file: not supported by Zip, so just make a copy
258 # of the pointed-to file.
260 # Start by reading the contents of the link.
262 my $f = BackupPC::FileZIO->open($hdr->{fullPath}, 0, $hdr->{compress});
263 if ( !defined($f) ) {
264 print(STDERR "Unable to open file $hdr->{fullPath}\n");
269 while ( $f->read(\$data, $BufSize) > 0 ) {
270 $hdr->{linkname} .= $data;
274 # Dump the original file. Just call the top-level
275 # routine, so that we save the hassle of dealing with
276 # mangling, merging and attributes.
278 archiveWrite($zipfh, $hdr->{linkname}, $hdr->{name});
279 } elsif ( $hdr->{type} == BPC_FTYPE_SYMLINK ) {
281 # Symlinks can't be Zipped. 8(
282 # We could zip the pointed-to dir/file (just like hardlink), but we
283 # have to avoid the infinite-loop case of a symlink pointed to a
284 # directory above us. Ignore for now. Could be a comand-line
288 } elsif ( $hdr->{type} == BPC_FTYPE_CHARDEV
289 || $hdr->{type} == BPC_FTYPE_BLOCKDEV
290 || $hdr->{type} == BPC_FTYPE_FIFO ) {
292 # Special files can't be Zipped. 8(
296 print(STDERR "Got unknown type $hdr->{type} for $hdr->{name}\n");
299 return if ( !$zipmember );
302 # Set the attributes and permissions. The standard zip file
303 # header cannot handle dates prior to 1/1/1980, or 315561600
304 # unix seconds, so we round up the mtime.
306 my $mtime = $hdr->{mtime};
307 $mtime = 315561600 if ( $mtime < 315561600 );
308 $zipmember->setLastModFileDateTimeFromUnix($mtime);
309 $zipmember->unixFileAttributes($hdr->{mode});
310 # Zip files don't accept uid and gid, so we put them in the comment field.
311 $zipmember->fileComment("uid=".$hdr->{uid}." gid=".$hdr->{gid})
312 if ( $hdr->{uid} || $hdr->{gid} );
314 # Specify the compression level for this member
315 $zipmember->desiredCompressionLevel($compLevel) if ($compLevel =~ /[0-9]/);
317 # Finally Zip the member
318 $zipfh->addMember($zipmember);