cb56ac28f2e907dd26a2a6882b2da0f53683241f
[BackupPC.git] / bin / BackupPC_zipCreate
1 #!/bin/perl -T
2 #============================================================= -*-perl-*-
3 #
4 # BackupPC_zipCreate: create a zip archive of an existing dump
5 # for restore on a client.
6 #
7 # DESCRIPTION
8 #  
9 #   Usage: BackupPC_zipCreate [-t] [-h host] [-n dumpNum] [-s shareName]
10 #                   [-r pathRemove] [-p pathAdd] [-c compressionLevel]
11 #                   files/directories...
12 #
13 #   Flags:
14 #     Required options:
15 #
16 #       -h host         host from which the zip archive is created
17 #       -n dumpNum      dump number from which the zip archive is created
18 #       -s shareName    share name from which the zip archive is created
19 #
20 #     Other options:
21 #       -t              print summary totals
22 #       -r pathRemove   path prefix that will be replaced with pathAdd
23 #       -p pathAdd      new path prefix
24 #       -c level        compression level (default is 0, no compression)
25 #
26 #     The -h, -n and -s options specify which dump is used to generate
27 #     the zip archive.  The -r and -p options can be used to relocate
28 #     the paths in the zip archive so extracted files can be placed
29 #     in a location different from their original location.
30 #
31 # AUTHOR
32 #   Guillaume Filion <gfk@users.sourceforge.net>
33 #   Based on Backup_tarCreate by Craig Barratt <cbarratt@users.sourceforge.net>
34 #
35 # COPYRIGHT
36 #   Copyright (C) 2002  Craig Barratt and Guillaume Filion
37 #
38 #   This program is free software; you can redistribute it and/or modify
39 #   it under the terms of the GNU General Public License as published by
40 #   the Free Software Foundation; either version 2 of the License, or
41 #   (at your option) any later version.
42 #
43 #   This program is distributed in the hope that it will be useful,
44 #   but WITHOUT ANY WARRANTY; without even the implied warranty of
45 #   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
46 #   GNU General Public License for more details.
47 #
48 #   You should have received a copy of the GNU General Public License
49 #   along with this program; if not, write to the Free Software
50 #   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
51 #
52 #========================================================================
53 #
54 # Version 2.0.0beta1, released 30 Mar 2003.
55 #
56 # See http://backuppc.sourceforge.net.
57 #
58 #========================================================================
59
60 use strict;
61 use lib "/usr/local/BackupPC/lib";
62 use Archive::Zip qw(:ERROR_CODES);
63 use File::Path;
64 use Getopt::Std;
65 use IO::Handle;
66 use BackupPC::Lib;
67 use BackupPC::Attrib qw(:all);
68 use BackupPC::FileZIO;
69 use BackupPC::Zip::FileMember;
70 use BackupPC::View;
71
72 die("BackupPC::Lib->new failed\n") if ( !(my $bpc = BackupPC::Lib->new) );
73 my $TopDir = $bpc->TopDir();
74 my $BinDir = $bpc->BinDir();
75 my %Conf   = $bpc->Conf();
76
77 my %opts;
78
79 if ( !getopts("th:n:p:r:s:c:", \%opts) || @ARGV < 1 ) {
80     print(STDERR "usage: $0 [-t] [-h host] [-n dumpNum] [-s shareName]"
81                . " [-r pathRemove] [-p pathAdd] [-c compressionLevel]"
82                . " files/directories...\n");
83     exit(1);
84 }
85
86 if ( $opts{h} !~ /^([\w\.\s-]+)$/ ) {
87     print(STDERR "$0: bad host name '$opts{h}'\n");
88     exit(1);
89 }
90 my $Host = $opts{h};
91
92 if ( $opts{n} !~ /^(\d+)$/ ) {
93     print(STDERR "$0: bad dump number '$opts{n}'\n");
94     exit(1);
95 }
96 my $Num = $opts{n};
97 $opts{c} = 0 if ( $opts{c} eq "" );
98 if ( $opts{c} !~ /^(\d+)$/ ) {
99     print(STDERR "$0: invalid compression level '$opts{c}'. 0=none, 9=max\n");
100     exit(1);
101 }
102 my $compLevel = $opts{c};
103
104 my @Backups = $bpc->BackupInfoRead($Host);
105 my($i);
106 my $FileCnt = 0;
107 my $ByteCnt = 0;
108 my $DirCnt = 0;
109 my $SpecialCnt = 0;
110 my $ErrorCnt = 0;
111
112 for ( $i = 0 ; $i < @Backups ; $i++ ) {
113     last if ( $Backups[$i]{num} == $Num );
114 }
115 if ( $i >= @Backups ) {
116     print(STDERR "$0: bad backup number $Num for host $Host\n");
117     exit(1);
118 }
119
120 my $PathRemove = $1 if ( $opts{r} =~ /(.+)/ );
121 my $PathAdd    = $1 if ( $opts{p} =~ /(.+)/ );
122 if ( $opts{s} !~ /^([\w\s\.\/\$-]+)$/ ) {
123     print(STDERR "$0: bad share name '$opts{s}'\n");
124     exit(1);
125 }
126 my $ShareName = $opts{s};
127
128 my $BufSize    = 1048576;     # 1MB or 2^20
129 my(%UidCache, %GidCache);
130 #my $fh = *STDOUT;
131 my $fh = new IO::Handle;      
132 $fh->fdopen(fileno(STDOUT),"w");
133 my $zipfh = Archive::Zip->new();
134
135 foreach my $dir ( @ARGV ) {
136     archiveWrite($zipfh, $dir);
137 }
138
139 sub archiveWrite
140 {
141     my($zipfh, $dir, $zipPathOverride) = @_;
142
143     my $view = BackupPC::View->new($bpc, $Host, \@Backups);
144
145     if ( $dir =~ m{(^|/)\.\.(/|$)} || $dir !~ /^(.*)$/ ) {
146         print(STDERR "$0: bad directory '$dir'\n");
147         $ErrorCnt++;
148         return;
149     }
150     $view->find($Num, $ShareName, $dir, 0, \&ZipWriteFile,
151                 $zipfh, $zipPathOverride);
152 }
153
154 # Create Zip file
155 print STDERR "Can't write Zip file\n"
156      unless $zipfh->writeToFileHandle($fh, 0) == Archive::Zip::AZ_OK;
157
158 #
159 # print out totals if requested
160 #
161 if ( $opts{t} ) {
162     print STDERR "Done: $FileCnt files, $ByteCnt bytes, $DirCnt dirs,",
163                  " $SpecialCnt specials ignored, $ErrorCnt errors\n";
164 }
165 exit(0);
166
167 ###########################################################################
168 # Subroutines
169 ###########################################################################
170
171 sub UidLookup
172 {
173     my($uid) = @_;
174
175     $UidCache{$uid} = (getpwuid($uid))[0] if ( !exists($UidCache{$uid}) );
176     return $UidCache{$uid};
177 }
178
179 sub GidLookup
180 {
181     my($gid) = @_;
182
183     $GidCache{$gid} = (getgrgid($gid))[0] if ( !exists($GidCache{$gid}) );
184     return $GidCache{$gid};
185 }
186
187 my $Attr;
188 my $AttrDir;
189
190 sub ZipWriteFile
191 {
192     my($hdr, $zipfh, $zipPathOverride) = @_;
193
194     my $tarPath = $hdr->{relPath};
195     $tarPath = $zipPathOverride if ( defined($zipPathOverride) );
196
197     if ( defined($PathRemove)
198             && substr($tarPath, 0, length($PathRemove)) eq $PathRemove ) {
199         substr($tarPath, 0, length($PathRemove)) = $PathAdd;
200     }
201     $tarPath = "./" . $tarPath if ( $tarPath !~ /^\.\// );
202     $tarPath =~ s{//+}{/}g;
203     $hdr->{name} = $tarPath;
204
205     my $zipmember; # Container to hold the file/directory to zip.
206
207     if ( $hdr->{type} == BPC_FTYPE_DIR ) {
208         #
209         # Directory: just write the header
210         #
211         $hdr->{name} .= "/" if ( $hdr->{name} !~ m{/$} );
212         $zipmember = Archive::Zip::Member->newDirectoryNamed($hdr->{name});
213         $DirCnt++;
214     } elsif ( $hdr->{type} == BPC_FTYPE_FILE ) {
215         #
216         # Regular file: write the header and file
217         #
218         $zipmember = BackupPC::Zip::FileMember->newFromFileNamed(
219                                             $hdr->{fullPath},
220                                             $hdr->{name},
221                                             $hdr->{size},
222                                             $hdr->{compress}
223                                     );
224         $FileCnt++;
225         $ByteCnt += $hdr->{size};
226     } elsif ( $hdr->{type} == BPC_FTYPE_HARDLINK ) {
227         #
228         # Hardlink file: not supported by Zip, so just make a copy
229         # of the pointed-to file.
230         #
231         # Start by reading the contents of the link.
232         #
233         my $f = BackupPC::FileZIO->open($hdr->{fullPath}, 0, $hdr->{compress});
234         if ( !defined($f) ) {
235             print(STDERR "Unable to open file $hdr->{fullPath}\n");
236             $ErrorCnt++;
237             return;
238         }
239         my $data;
240         while ( $f->read(\$data, $BufSize) > 0 ) {
241             $hdr->{linkname} .= $data;
242         }
243         $f->close;
244         #
245         # Dump the original file.  Just call the top-level
246         # routine, so that we save the hassle of dealing with
247         # mangling, merging and attributes.
248         #
249         archiveWrite($zipfh, $hdr->{linkname}, $hdr->{name});
250     } elsif ( $hdr->{type} == BPC_FTYPE_SYMLINK ) {
251         #
252         # Symlinks can't be Zipped. 8(
253         # We could zip the pointed-to dir/file (just like hardlink), but we
254         # have to avoid the infinite-loop case of a symlink pointed to a
255         # directory above us.  Ignore for now.  Could be a comand-line
256         # option later.
257         #
258         $SpecialCnt++;
259     } elsif ( $hdr->{type} == BPC_FTYPE_CHARDEV
260            || $hdr->{type} == BPC_FTYPE_BLOCKDEV
261            || $hdr->{type} == BPC_FTYPE_FIFO ) {
262         #
263         # Special files can't be Zipped. 8(
264         #
265         $SpecialCnt++;
266     } else {
267         print(STDERR "Got unknown type $hdr->{type} for $hdr->{name}\n");
268         $ErrorCnt++;
269     }
270     return if ( !$zipmember );
271     
272     # Set the attributes and permissions
273     $zipmember->setLastModFileDateTimeFromUnix($hdr->{mtime});
274     $zipmember->unixFileAttributes($hdr->{mode});
275     # Zip files don't accept uid and gid, so we put them in the comment field.
276     $zipmember->fileComment("uid=".$hdr->{uid}." gid=".$hdr->{gid})
277             if ( $hdr->{uid} || $hdr->{gid} );
278     
279     # Specify the compression level for this member
280     $zipmember->desiredCompressionLevel($compLevel) if ($compLevel =~ /[0-9]/);
281     
282     # Finally Zip the member
283     $zipfh->addMember($zipmember);
284 }