File: BackupPC_zipCreate

package info (click to toggle)
backuppc 4.4.0-11
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 4,752 kB
  • sloc: perl: 37,523; sh: 607; javascript: 176; makefile: 38; ansic: 6
file content (335 lines) | stat: -rwxr-xr-x 10,939 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
#!/usr/bin/perl
#============================================================= -*-perl-*-
#
# BackupPC_zipCreate: create a zip archive of an existing dump
# for restore on a client.
#
# DESCRIPTION
#
#   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:
#       -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)
#       -m              force running even if a backup on this host is running
#                       (specifically, don't take the server host mutex)
#
#     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
#     the paths in the zip archive so extracted files can be placed
#     in a location different from their original location.
#
# AUTHOR
#   Guillaume Filion <gfk@users.sourceforge.net>
#   Based on Backup_tarCreate by Craig Barratt <cbarratt@users.sourceforge.net>
#
# COPYRIGHT
#   Copyright (C) 2002-2020  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
#   the Free Software Foundation, either version 3 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, see <http://www.gnu.org/licenses/>.
#
#========================================================================
#
# Version 4.4.0, released 20 Jun 2020.
#
# See http://backuppc.sourceforge.net.
#
#========================================================================

use strict;
no utf8;

use lib "__INSTALLDIR__/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::XS qw( :all );
use BackupPC::Zip::FileMember;
use BackupPC::View;

die("BackupPC::Lib->new failed\n") if ( !(my $bpc = BackupPC::Lib->new) );
my $TopDir = $bpc->TopDir();
my $BinDir = $bpc->BinDir();
my %Conf   = $bpc->Conf();

my %opts;

if ( !getopts("tme: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: utf8)
     -m              force running even if a backup on this host is running
                     (specifically, don't take the server host mutex)
EOF
    exit(1);
}

my $Host;
if ( $opts{h} !~ m{(^|/)\.\.(/|$)} && $opts{h} =~ /^([\w@.\s-]+)$/ ) {
    $Host = $1;
} else {
    print(STDERR "$0: bad host name '$opts{h}'\n");
    exit(1);
}

if ( $opts{n} !~ /^(-?\d+)$/ ) {
    print(STDERR "$0: bad dump number '$opts{n}'\n");
    exit(1);
}
my $Num = $opts{n};

$opts{c} = 0 if ( $opts{c} eq "" );
if ( $opts{c} !~ /^(\d+)$/ ) {
    print(STDERR "$0: invalid compression level '$opts{c}'. 0=none, 9=max\n");
    exit(1);
}
if (   !$opts{m}
    && !defined($bpc->ServerConnect($Conf{ServerHost}, $Conf{ServerPort}))
    && (my $status = $bpc->ServerMesg("hostMutex $Host 1 BackupPC_zipCreate")) =~ /fail/ ) {
    print(STDERR "$0: $status (use -m option to force running)\n");
    exit(1);
}

my $compLevel = $opts{c};

my @Backups    = $bpc->BackupInfoRead($Host);
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++ ) {
    last if ( $Backups[$i]{num} == $Num );
}
if ( $i >= @Backups ) {
    print(STDERR "$0: bad backup number $Num for host $Host\n");
    exit(1);
}

my $Charset = "";    # default: utf8
$Charset = $opts{e} if ( $opts{e} ne "" );

my $PathRemove = $1 if ( $opts{r} =~ /(.+)/ );
my $PathAdd    = $1 if ( $opts{p} =~ /(.+)/ );
if ( $opts{s} =~ m{(^|/)\.\.(/|$)} ) {
    print(STDERR "$0: bad share name '$opts{s}'\n");
    exit(1);
}
my $ShareName = $opts{s};

my $BufSize = 1048576;    # 1MB or 2^20
my(%UidCache, %GidCache);

#my $fh = *STDOUT;
my $fh = new IO::Handle;
$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++;
        return;
    }
    $dir = "/" if ( $dir eq "." );
    $view->find($Num, $ShareName, $dir, 0, \&ZipWriteFile, $zipfh, $zipPathOverride);
}

# Create Zip file
print STDERR "Can't write Zip file\n"
  unless $zipfh->writeToFileHandle($fh, 0) == Archive::Zip::AZ_OK;

#
# print out totals if requested
#
if ( $opts{t} ) {
    print STDERR "Done: $FileCnt files, $ByteCnt bytes, $DirCnt dirs,",
      " $SpecialCnt specials ignored, $ErrorCnt errors\n";
}
exit(0);

###########################################################################
# Subroutines
###########################################################################

sub UidLookup
{
    my($uid) = @_;

    $UidCache{$uid} = (getpwuid($uid))[0] if ( !exists($UidCache{$uid}) );
    return $UidCache{$uid};
}

sub GidLookup
{
    my($gid) = @_;

    $GidCache{$gid} = (getgrgid($gid))[0] if ( !exists($GidCache{$gid}) );
    return $GidCache{$gid};
}

my $Attr;
my $AttrDir;

sub ZipWriteFile
{
    my($hdr, $zipfh, $zipPathOverride) = @_;

    my $tarPath = $hdr->{relPath};
    $tarPath = $zipPathOverride if ( defined($zipPathOverride) );

    if ( defined($PathRemove)
        && substr($tarPath, 0, length($PathRemove)) eq $PathRemove ) {
        substr($tarPath, 0, length($PathRemove)) = $PathAdd;
    }
    $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($hdr->{fullPath}, $hdr->{name}, $hdr->{size}, $hdr->{compress});
        $FileCnt++;
        $ByteCnt += $hdr->{size};
    } elsif ( $hdr->{type} == BPC_FTYPE_HARDLINK ) {
        #
        # Hardlink file: not supported by Zip, so just make a copy
        # of the pointed-to file.
        #
        # Start by reading the contents of the link.
        #
        my $f = BackupPC::XS::FileZIO::open($hdr->{fullPath}, 0, $hdr->{compress});
        if ( !defined($f) ) {
            print(STDERR "Unable to open file $hdr->{fullPath}\n");
            $ErrorCnt++;
            return;
        }
        my $data;
        while ( $f->read(\$data, $BufSize) > 0 ) {
            $hdr->{linkname} .= $data;
        }
        $f->close;
        #
        # Dump the original file.  Just call the top-level
        # routine, so that we save the hassle of dealing with
        # mangling, merging and attributes.
        #
        archiveWrite($zipfh, $hdr->{linkname}, $hdr->{name});
    } elsif ( $hdr->{type} == BPC_FTYPE_SYMLINK ) {
        #
        # Symlinks can't be Zipped. 8(
        # We could zip the pointed-to dir/file (just like hardlink), but we
        # have to avoid the infinite-loop case of a symlink pointed to a
        # directory above us.  Ignore for now.  Could be a command-line
        # option later.
        #
        $SpecialCnt++;
    } elsif ( $hdr->{type} == BPC_FTYPE_CHARDEV
        || $hdr->{type} == BPC_FTYPE_BLOCKDEV
        || $hdr->{type} == BPC_FTYPE_FIFO ) {
        #
        # Special files can't be Zipped. 8(
        #
        $SpecialCnt++;
    } else {
        print(STDERR "Got unknown type $hdr->{type} for $hdr->{name}\n");
        $ErrorCnt++;
    }
    return if ( !$zipmember );

    #
    # 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})
      if ( $hdr->{uid} || $hdr->{gid} );

    # Specify the compression level for this member
    $zipmember->desiredCompressionLevel($compLevel) if ( $compLevel =~ /[0-9]/ );

    if ( $Charset =~ /^(?:utf[-_]?8)?$/i ) {

        # Set general purpose bit 11 for UTF-8 code page
        $zipmember->{bitFlag} = $zipmember->{bitFlag} | 0x0800;
    } elsif ( $Charset =~ /^cp(?:437|720|737|775|85[02578]|86[069]|874|93[26]|949|950)$/i ) {

        # Set "version made by" field to 0 (MS-DOS) for OEM code pages
        $zipmember->fileAttributeFormat('FA_MSDOS');
    }

    # Finally Zip the member
    $zipfh->addMember($zipmember);
}