~xubuntu-dev/ubuntu-cdimage/xubuntu-base

« back to all changes in this revision

Viewing changes to bin/debmirror

  • Committer: Colin Watson
  • Date: 2004-07-14 12:23:45 UTC
  • Revision ID: Arch-1:colin.watson@canonical.com--2004%cdimage--mainline--0--base-0
import from little
Import cdimage wrapper and utility scripts from little.warthogs.hbd.com.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/perl -w
 
2
 
 
3
# TODO: I'd like to be able to tell it to get some extra files, by name.
 
4
# I'm thinking Contents files. It would be really nice if it could pull
 
5
# a whole directory -- think project/trace, or disks-i386...
 
6
# TODO: It would probably be cleaner and easier to learn if it took
 
7
# apt-style lines to tell where to mirror from and what portions to use.
 
8
 
 
9
=head1 NAME
 
10
 
 
11
debmirror - Debian partial mirror script, with ftp and package pool support
 
12
 
 
13
=head1 SYNOPSIS
 
14
 
 
15
debmirror [options] mirrordir
 
16
 
 
17
=head1 DESCRIPTION
 
18
 
 
19
This program downloads and maintains a partial local Debian mirror. It can
 
20
mirror any combination of architectures, distributions, and sections. Files
 
21
are transferred by ftp, and package pools are fully supported. It also does
 
22
locking and updates trace files.
 
23
 
 
24
To support package pools, this program mirrors in three steps.
 
25
 
 
26
=over 4
 
27
 
 
28
=item 1. download Packages and Sources files
 
29
 
 
30
First it downloads all Packages and Sources files for the subset of Debian it
 
31
was instructed to get.
 
32
 
 
33
=item 2. clean up unknown files
 
34
 
 
35
Any files and directories on the local mirror that are not in the list are
 
36
removed.
 
37
 
 
38
=item 3. download everything else
 
39
 
 
40
The Packages and Sources files are scanned, to build up a list of all the
 
41
files they refer to. A few other miscellaneous files are added to the list.
 
42
Then the program makes sure that each file in the list is present on the 
 
43
local mirror and is up-to-date, using file size (and optionally md5sum) checks.
 
44
Any necessary files are downloaded.
 
45
 
 
46
=back
 
47
 
 
48
=cut
 
49
 
 
50
sub usage {
 
51
        warn join(" ", @_)."\n" if @_;
 
52
        warn <<EOF;
 
53
Usage: $0 [--debug] [--progress] [--verbose] [--source|--nosource]
 
54
          [--md5sums|--nomd5sums] [--passive] [--host=remotehost]
 
55
          [--user=remoteusername] [--method=ftp|rsync] [--timeout=seconds]
 
56
          [--root=directory] [--dist=foo[,bar,..] ...]
 
57
          [--section=foo[,bar,..] ...] [--arch=foo[,bar,..] ...]
 
58
          [--cleanup|--nocleanup] [--skippackages] [--adddir=directory]
 
59
          [--ignore=regex] [--getcontents] [--exclude=regex] [--include=regex]
 
60
          [--max-batch=number]
 
61
          [--help]
 
62
          mirrordir
 
63
 
 
64
For details, see man page.
 
65
EOF
 
66
        exit(1);
 
67
}
 
68
 
 
69
=head1 OPTIONS
 
70
 
 
71
=over 4
 
72
 
 
73
=item mirrordir
 
74
 
 
75
This required parameter specifies where the local mirror directory is. If the
 
76
directory does not exist, it will be created. Be careful; telling this 
 
77
program that your home directory is the mirrordir is guarenteed to replace
 
78
your home directory with a Debian mirror!
 
79
 
 
80
=item --debug
 
81
 
 
82
Enables verbose debug output, including ftp protocol dump.
 
83
 
 
84
=item --progress -p
 
85
 
 
86
Displays progress bars as files are downloaded.
 
87
 
 
88
=item --verbose -v
 
89
 
 
90
Displays progress between file downloads.
 
91
 
 
92
=item --source
 
93
 
 
94
Include source in the mirror (default).
 
95
 
 
96
=item --nosource
 
97
 
 
98
Do not include source.
 
99
 
 
100
=item --md5sums -m
 
101
 
 
102
Use md5sums to determine if files on the local mirror that are the correct
 
103
size actually have the correct content. Not enabled by default, because
 
104
it is too paranoid, and too slow.
 
105
 
 
106
=item --passive
 
107
 
 
108
Download in passive mode.
 
109
 
 
110
=item --host=remotehost -h
 
111
 
 
112
Specify the remote host to mirror from. Defaults to ftp.debian.org,
 
113
you are strongly encouraged to find a closer mirror.
 
114
 
 
115
=item --user=remoteusername -u
 
116
 
 
117
Specify the remote user name to use to log to the remote host. Helpful when
 
118
dealing with braindamaged proxy servers. Defaults to anonymous.
 
119
 
 
120
=item --method=ftp|rsync -e
 
121
 
 
122
Specify the method to download files. Currentrly, supported methods are
 
123
ftp or rsync. To connect a rsync server, you need to put ':' prefix in
 
124
the root directory  (i.e. ":debian", which means host::debian).
 
125
 
 
126
=item --timeout=seconds -t
 
127
 
 
128
Specifies the timeout to use for network operations (either FTP or rsync).
 
129
Set this to a higher value if you experience failed downloads. Defaults
 
130
to 300 seconds.
 
131
 
 
132
=item --root=directory -r directory
 
133
 
 
134
Specifies the directory on the remote host that is the root of the Debian
 
135
archive. Defaults to "/debian", which will work for most mirrors. The root
 
136
directory has a dists/ subdirectory.
 
137
 
 
138
=item --dist=foo[,bar,..] -d foo
 
139
 
 
140
Specify the distribution (woody, sarge, sid) of Debian to mirror. This
 
141
switch may be used multiple times, and multiple distributions may be
 
142
specified at once, separated by commas. Using the links (stable,
 
143
testing, unstable) does not have the expected results but you may add
 
144
those links manually. Defaults to mirroring sid.
 
145
 
 
146
=item --section=foo[,bar,..] -s foo
 
147
 
 
148
Specify the section of Debian to mirror. Defaults to main,contrib,non-free.
 
149
 
 
150
Note that to mirror the debian-installer's part of the archive, you can
 
151
use 'main/debian-installer'
 
152
 
 
153
=item --arch=foo[,bar,..] -a foo
 
154
 
 
155
Specify the architectures to mirror. The default is --arch=i386.
 
156
Specifying --arch=none will mirror no archs.
 
157
 
 
158
=item --cleanup
 
159
 
 
160
Do clean up any unknown files and directories on the local mirror (see
 
161
step 2 above). On by default.
 
162
 
 
163
=item --nocleanup
 
164
 
 
165
Do not clean up the local mirror after mirroring is complete.
 
166
 
 
167
=item --ignore=regex
 
168
 
 
169
Never delete any files whose filenames match the regex. May be used multiple times.
 
170
 
 
171
=item --exclude=regex
 
172
 
 
173
Never download any files whose filenames match the regex. May be used multiple times.
 
174
 
 
175
=item --include=regex
 
176
 
 
177
Don't exclude any files whose filenames match the regex. May be used multiple times.
 
178
 
 
179
=item --skippackages
 
180
 
 
181
Don't re-download Packages and Sources files. Useful if you know they are
 
182
up-to-date.
 
183
 
 
184
=item --adddir directory
 
185
 
 
186
Also download Packages and Sources files from the specified directory
 
187
on the remote host (the directory is relative to the root of the
 
188
Debian archive). If you used this option for "--adddir
 
189
dists/proposed-updates" in the past use "--dist
 
190
<dist>-proposed-updates" now. This feature is now obsolete and will be
 
191
removed soon.
 
192
 
 
193
=item --getcontents
 
194
 
 
195
Download Contents.arch.gz files.
 
196
 
 
197
=item --max-batch=number
 
198
 
 
199
Download at most max-batch number of files (and ignore rest)
 
200
 
 
201
=item --help
 
202
 
 
203
Display a usage summary.
 
204
 
 
205
=back
 
206
 
 
207
=head1 EXAMPLES
 
208
 
 
209
 debmirror /mirror/debian
 
210
 
 
211
Simply make a mirror in /mirror/debian, using all defaults.
 
212
 
 
213
 debmirror /mirror/debian --ignore=non-US/
 
214
 debmirror /mirror/debian/non-US -h non-us.debian.org -r /debian-non-US \
 
215
        -d sid/non-US -s main,contrib,non-free
 
216
 
 
217
Make one full mirror, and suppliment it with a mirror of non-US, in a
 
218
directory inside.
 
219
 
 
220
 debmirror -a i386,sparc -s main -h ftp.kernel.org \
 
221
        -d unstable -d testing /home/me/debian/mirror --nosource \
 
222
        --progress
 
223
 
 
224
Make a mirror of i386 and sparc binaries, main only, and include both unstable
 
225
and testing versions of Debian. Download from ftp.kernel.org.
 
226
 
 
227
 debmirror -e rsync -r :debian /home/me/debian/mirror
 
228
 
 
229
Make a mirror using rsync. rsync server is ftp.debian.org::debian.
 
230
 
 
231
=cut
 
232
 
 
233
use strict;
 
234
use Cwd;
 
235
use Net::FTP;
 
236
use Getopt::Long;
 
237
use File::Temp qw/ tempfile /;
 
238
use LockFile::Simple;
 
239
use Compress::Zlib;
 
240
use Digest::MD5;
 
241
 
 
242
# Yeah, I use too many global variables in this program.
 
243
my ($debug, $progress, $verbose, $passive, $skippackages, $getcontents);
 
244
my (@dists, @sections, @arches, @extra_dirs, @ignores, @excludes, @includes);
 
245
my $check_md5sums = 0;
 
246
my $cleanup=1;
 
247
my $do_source=1;
 
248
my $host="ftp.debian.org";
 
249
my $user="anonymous";
 
250
my $remoteroot="/debian";
 
251
my $download_method="ftp";
 
252
my $timeout=300;
 
253
my $max_batch=0;
 
254
my $num_errors=0;
 
255
my $bytes_to_get=0;
 
256
my $bytes_gotten=0;
 
257
my $start_time = time;
 
258
my @errlog;
 
259
 
 
260
# This hash holds all the files we know about, If the hash key is false,
 
261
# the file already exists in the mirror (or is locally created) and does not
 
262
# need to be downloaded, if it is true the file needs to be downloaded.
 
263
# Filenames should be relative to $mirrordir.
 
264
my %files;
 
265
 
 
266
my $help;
 
267
GetOptions(
 
268
        'debug'         => \$debug,
 
269
        'progress|p'    => \$progress,
 
270
        'verbose|v'     => \$verbose,
 
271
        'source!'       => \$do_source,
 
272
        'md5sums|m'     => \$check_md5sums,
 
273
        'passive!'      => \$passive,
 
274
        'host|h=s'      => \$host,
 
275
        'user|u=s'      => \$user,
 
276
        'root|r=s'      => \$remoteroot,
 
277
        'dist|d=s'      => \@dists,
 
278
        'section|s=s'   => \@sections,
 
279
        'arch|a=s'      => \@arches,
 
280
        'adddir=s'      => \@extra_dirs,
 
281
        'cleanup!'      => \$cleanup,
 
282
        'ignore=s'      => \@ignores,
 
283
        'exclude=s'     => \@excludes,
 
284
        'include=s'     => \@includes,
 
285
        'skippackages'  => \$skippackages,
 
286
        'getcontents'   => \$getcontents,
 
287
        'method|e=s'    => \$download_method,
 
288
        'timeout|t=s'   => \$timeout,
 
289
        'max-batch=s'   => \$max_batch,
 
290
        'help' => \$help,
 
291
) or usage;
 
292
usage if $help;
 
293
 
 
294
# This parameter is so important that it is the only required parameter.
 
295
my $mirrordir=shift or usage("mirrordir not specified");
 
296
 
 
297
# Post-process arrays. Allow commas to seperate values the user entered.
 
298
# If the user entered nothing, provide defaults.
 
299
@dists=split(/,/,join(',',@dists));
 
300
@dists=qw(sid) unless @dists;
 
301
@sections=split(/,/,join(',',@sections));
 
302
@sections=qw(main contrib non-free) unless @sections;
 
303
@arches=split(/,/,join(',',@arches));
 
304
@arches=qw(i386) unless @arches;
 
305
@arches=() if (join(',',@arches) eq "none");
 
306
 
 
307
# Display configuration.
 
308
$|=1 if $debug;
 
309
say("Mirroring to $mirrordir from $download_method://$user:$host/$remoteroot/");
 
310
say("Arches: ".join(",", @arches));
 
311
say("Dists: ".join(",", @dists));
 
312
say("Sections: ".join(",", @sections));
 
313
say("Including source.") if $do_source;
 
314
say("Passive mode on.") if $passive;
 
315
say("Checking md5sums.") if $check_md5sums;
 
316
say("Will NOT clean up.") unless $cleanup;
 
317
say("Download at most $max_batch files.") if ($max_batch > 0);
 
318
 
 
319
my $md5;
 
320
$md5=Digest::MD5->new;
 
321
 
 
322
# Set up mirror directory and resolve $mirrordir to a full path for
 
323
# locking and rsync
 
324
make_dir($mirrordir) if ( ! -d $mirrordir);
 
325
die "You need write permissions on $mirrordir" if (!-w $mirrordir);
 
326
chdir($mirrordir) or die "chdir $mirrordir: $!";
 
327
$mirrordir = cwd();
 
328
 
 
329
# Handle the lock file. This is the same method used by official
 
330
# Debian push mirrors.
 
331
my $hostname=`hostname -f 2>/dev/null || hostname`;
 
332
chomp $hostname;
 
333
my $lockfile="Archive-Update-in-Progress-$hostname";
 
334
$files{$lockfile}=1;
 
335
say("Attempting to get lock, this might take 2 minutes before it fails.");
 
336
my $lockmgr = LockFile::Simple->make(-format => "%f/$lockfile", -max => 12,
 
337
                                     -delay => 10, -nfs => 1, -autoclean => 1,
 
338
                                     -warn => 1, -stale => 1);
 
339
my $lock = $lockmgr->lock("$mirrordir")
 
340
        or die "$lockfile exists or you lack proper permissions; aborting";
 
341
$SIG{INT}=sub { $lock->release; exit 1 };
 
342
$SIG{TERM}=sub { $lock->release; exit 1 };
 
343
 
 
344
# Register the trace file.
 
345
my $tracefile="project/trace/$hostname";
 
346
$files{$tracefile}=1;
 
347
 
 
348
# Start up ftp.
 
349
my $ftp;
 
350
my %opts = (Debug => $debug, Passive => $passive, Timeout => $timeout);
 
351
 
 
352
my $rsynctempfile;
 
353
END { unlink $rsynctempfile if $rsynctempfile }
 
354
 
 
355
sub init_connection {
 
356
        $_ = $download_method;
 
357
 
 
358
        /^ftp$/ && do {
 
359
                $ftp=Net::FTP->new($host, %opts) or die "$@\n";
 
360
                $ftp->login($user) or die "login failed"; # anonymous
 
361
                $ftp->binary or die "could not set binary mode";
 
362
                $ftp->cwd($remoteroot) or die "cwd to $remoteroot failed";
 
363
                $ftp->hash(*STDOUT,102400) if $progress;
 
364
                return;
 
365
        };
 
366
 
 
367
        /^rsync$/ && do {
 
368
                return;
 
369
        };
 
370
 
 
371
        usage("unknown download method: $_");
 
372
 
 
373
}
 
374
init_connection;
 
375
 
 
376
# fix remoteroot if --method=rsync
 
377
if ($download_method eq "rsync") {
 
378
  $remoteroot = "$host:$remoteroot";
 
379
  if (! ($user eq 'anonymous')) {
 
380
    $remoteroot = "$user\@$remoteroot";
 
381
  }
 
382
};
 
383
 
 
384
say("Get Release files.");
 
385
# Get Release files
 
386
my (%file_lists_md5, %file_lists_size);
 
387
foreach my $dist (@dists) {
 
388
  make_dir ("dists/$dist");
 
389
  remote_get("dists/$dist/Release");
 
390
  remote_get("dists/$dist/Release.gpg");
 
391
  $files{"dists/$dist/Release"}=1;
 
392
  $files{"dists/$dist/Release.gpg"}=1;
 
393
  # FIXME: Should verify these files now
 
394
 
 
395
  # Parse the Release
 
396
  if (open RELEASE, "<dists/$dist/Release") {
 
397
    while (!(<RELEASE> =~ /^MD5Sum:/)) {}
 
398
    $_ = <RELEASE>;
 
399
    while (defined $_ && $_ =~ /^ /) {
 
400
      my ($md5sum, $size, $filename) =
 
401
        (/ ([a-z0-9]+) +(\d+) +(.*)$/);
 
402
      $file_lists_md5{"dists/$dist/$filename"} = $md5sum;
 
403
      $file_lists_size{"dists/$dist/$filename"} = $size;
 
404
      $_ = <RELEASE>;
 
405
    }
 
406
    close RELEASE;
 
407
  }
 
408
}
 
409
 
 
410
say("Get Packages and Sources files and other miscellany.");
 
411
# Get Packages and Sources files and other miscellany.
 
412
my (@package_files, @source_files);
 
413
foreach my $dist (@dists) {
 
414
        foreach my $section (@sections) {
 
415
                foreach my $arch (@arches) {
 
416
                        get_packages("dists/$dist/$section/binary-$arch");
 
417
                }
 
418
                unless ($section =~ m[/]) { # exclude d-i source
 
419
                        get_sources("dists/$dist/$section/source");
 
420
                }
 
421
        }
 
422
        if ($getcontents) {
 
423
                foreach my $arch (@arches) {
 
424
                        next if $arch=~/source/;
 
425
                        remote_get("dists/$dist/Contents-$arch.gz");
 
426
                        $files{"dists/$dist/Contents-$arch.gz"}=1;
 
427
                }
 
428
        }
 
429
}
 
430
foreach (@extra_dirs) {
 
431
        get_packages($_);
 
432
        get_sources($_);
 
433
}
 
434
 
 
435
# close ftp connection to avoid timeouts, will reopen later
 
436
if ($download_method eq 'ftp') { $ftp->quit; }
 
437
 
 
438
# Sanity check. I once nuked a mirror because of this..
 
439
if (@arches && ! @package_files) {
 
440
        die "Failed to download any Packages files!\n";
 
441
}
 
442
if ($do_source && ! @source_files) {
 
443
        die "Failed to download any Sources files!\n";
 
444
}
 
445
 
 
446
if ($num_errors != 0) {
 
447
        die "Failed to download some Package, Sources, Contents or release files!\n";
 
448
}
 
449
 
 
450
say("Parse Packages and Sources files and add to the file list everything therein.");
 
451
# Parse Packages and Sources files and add to the file list everything therein.
 
452
{
 
453
        local $/="\n\n";
 
454
        my ($filename, $size, $md5sum, $directory, $exclude, $include,
 
455
            $architecture);
 
456
 
 
457
        my %arches = map { $_ => 1 } (@arches, "all");
 
458
        
 
459
        $exclude =  "(".join("|", @excludes).")" if @excludes;
 
460
        $include =  "(".join("|", @includes).")" if @includes;
 
461
        foreach my $file (@package_files) {
 
462
                my $gunzf = gzopen($file, "rb") or die "$file: $!";
 
463
                my $line;
 
464
                my $res;
 
465
                my $loop = 1;
 
466
                while ($loop) {
 
467
                        my $buf = "";
 
468
                        while(($res = $gunzf->gzreadline($line) > 0)
 
469
                              && !($line =~ /^$/)) {
 
470
                            $buf = $buf . $line;
 
471
                        }
 
472
                        if ($res <= 0) {
 
473
                                $loop = 0;
 
474
                                next;
 
475
                        }
 
476
                        $_ = $buf;    
 
477
                        ($filename)=m/^Filename:\s+(.*)/im;
 
478
                        ($architecture)=m/^Architecture:\s+(.*)/im;
 
479
                        next if (!$arches{$architecture});
 
480
                        if(!(defined($include) && ($filename=~/$include/o))) {
 
481
                            next if (defined($exclude) && $filename=~/$exclude/o);
 
482
                        }
 
483
                        ($size)=m/^Size:\s+(\d+)/im;
 
484
                        ($md5sum)=m/^MD5sum:\s+([A-Za-z0-9]+)/im;
 
485
                        if (check_file($filename, $size, $md5sum)) {
 
486
                          $files{$filename} = 1;
 
487
                        } else {
 
488
                          $files{$filename} = 0;
 
489
                          $bytes_to_get += $size;
 
490
                        }
 
491
                }
 
492
                $gunzf->gzclose();
 
493
        }
 
494
        foreach my $file (@source_files) {
 
495
                my $gunzf = gzopen($file, "rb") or die "$file: $!";
 
496
                my $line;
 
497
                my $res;
 
498
                my $loop = 1;
 
499
                while ($loop) {
 
500
                        my $buf = "";
 
501
                        while(($res = $gunzf->gzreadline($line) > 0)
 
502
                              && !($line =~ /^$/)) {
 
503
                            $buf = $buf . $line;
 
504
                        }
 
505
                        if ($res <= 0) {
 
506
                                $loop = 0;
 
507
                                next;
 
508
                        }
 
509
                        $_ = $buf;    
 
510
                        ($directory) = m/^Directory:\s+(.*)/im;
 
511
                        while (m/^ ([A-Za-z0-9]{32} .*)/mg) {
 
512
                                ($md5sum, $size, $filename)=split(' ', $1, 3);
 
513
                                $filename="$directory/$filename";
 
514
                        if(!(defined($include) && ($filename=~/$include/o))) {                                      next if (defined($exclude) && $filename=~/$exclude/o);
 
515
                                }
 
516
                                if (check_file($filename, $size, $md5sum)) {
 
517
                                  $files{$filename} = 1;
 
518
                                } else {
 
519
                                  $files{$filename} = 0;
 
520
                                  $bytes_to_get += $size;
 
521
                                }
 
522
                        }
 
523
                }
 
524
                $gunzf->gzclose();
 
525
        }
 
526
}
 
527
 
 
528
if ($cleanup) {
 
529
  say("Cleanup mirror.");
 
530
        my $ignore;
 
531
        $ignore = "(".join("|", @ignores).")" if @ignores;
 
532
        # Remove all files in the mirror that we don't know about
 
533
        foreach my $file (`find . -type f`) {
 
534
                chomp $file;
 
535
                $file=~s:^\./::;
 
536
                unless (exists $files{$file} or (defined($ignore) && $file=~/$ignore/o)) {
 
537
                        say("deleting $file");
 
538
                        unlink $file or die "unlink $file: $!";
 
539
                }
 
540
        }
 
541
        # Remove all empty directories. Not done as part of main cleanup
 
542
        # to prevent race problems with pool download code, which
 
543
        # makes directories.. Sort so they are removable in bottom-up
 
544
        # order.
 
545
        system("find . -type d ! -name . ! -name .. | sort -r | xargs rmdir 2>/dev/null");
 
546
}
 
547
 
 
548
say("Download all files that we need to get ($bytes_to_get Bytes).");
 
549
# Download all files that we need to get.
 
550
DOWNLOAD: {
 
551
        init_connection;
 
552
        $_ = $download_method;
 
553
        # Ftp method
 
554
        /^ftp$/ && do {
 
555
                my $dirname;
 
556
                my $i=0;
 
557
                foreach my $file (sort keys %files) {
 
558
                        if (!$files{$file}) {
 
559
                          if (($dirname) = $file =~ m:(.*)/:) {
 
560
                            make_dir($dirname);
 
561
                          }
 
562
                          ftp_get($file);
 
563
                          if ($max_batch > 0 && ++$i >= $max_batch) {
 
564
                            push (@errlog,"Batch limit exceeded, mirror run was partial\n");
 
565
                            last;
 
566
                          }
 
567
                        }
 
568
                }
 
569
                last DOWNLOAD;
 
570
        };
 
571
 
 
572
        # Rsync method
 
573
        /^rsync$/ && do {
 
574
                my $fh;
 
575
                ($fh, $rsynctempfile) = tempfile();
 
576
                my $opt="";
 
577
                my @result;
 
578
                my $i=0;
 
579
                $opt = "--progress" if $progress;
 
580
                $opt = "$opt -v" if $verbose;
 
581
                $opt = "$opt -v" if $debug;
 
582
                foreach my $file (sort keys %files) {
 
583
                        if (!$files{$file}) {
 
584
                                my $dirname;
 
585
                                my @dir;
 
586
                                ($dirname) = $file =~ m:(.*/):;
 
587
                                @dir= split(/\//, $dirname);
 
588
                                for (0..$#dir) {
 
589
                                        push (@result, "" . join('/', @dir[0..$_]) . "/");
 
590
                                }
 
591
                                push (@result, "$file");
 
592
                                if ($max_batch > 0 && ++$i >= $max_batch) {
 
593
                                  print "Batch limit exceeded, mirror run will be partial\n";
 
594
                                  push (@errlog,"Batch limit exceeded, mirror run was partial\n");
 
595
                                  last;
 
596
                                }
 
597
                        }
 
598
                }
 
599
                if (@result) {
 
600
                        @result = sort(@result);
 
601
                        my $prev = "not equal to $result[0]";
 
602
                        @result = grep($_ ne $prev && ($prev = $_, 1), @result);
 
603
                        for (@result) {
 
604
                                print $fh "$_\n";
 
605
                        }
 
606
                }
 
607
                system ("rsync -azI --partial --timeout=$timeout $opt $remoteroot --include-from=$rsynctempfile --exclude='*' $mirrordir");
 
608
                close $fh;
 
609
                last DOWNLOAD;
 
610
        };
 
611
 
 
612
}
 
613
 
 
614
 
 
615
# Finish up. Write out trace file.
 
616
if ($download_method eq 'ftp') { $ftp->quit; }
 
617
make_dir("project/trace");
 
618
open OUT, ">$tracefile" or die "$tracefile: $!";
 
619
print OUT `date -u`;
 
620
close OUT;
 
621
$lock->release;
 
622
 
 
623
my $total_time = time - $start_time;
 
624
my $avg_speed = 0;
 
625
$avg_speed = sprintf("%3.0f",($bytes_gotten / $total_time)) unless ($total_time == 0);
 
626
if ($bytes_gotten == 0) {
 
627
  say("Downloaded files in ".$total_time."s");
 
628
} else {
 
629
  say("Downloaded $bytes_gotten bytes in ".$total_time."s at $avg_speed bytes/s");
 
630
}
 
631
 
 
632
print "Errors:\n ".join(" ",@errlog) if (@errlog);
 
633
 
 
634
if ($num_errors != 0) {
 
635
  print "Failed to download files ($num_errors errors)!\n";
 
636
  exit 1;
 
637
} else {
 
638
  say("Everything OK.");
 
639
}
 
640
 
 
641
exit;
 
642
 
 
643
# Pass this function a filename, a file size (bytes), and a md5sum (hex).
 
644
# It will return true if the md5sum matches.
 
645
sub check_file {
 
646
        my ($filename, $size, $md5sum)=@_;
 
647
        if (-f $filename and $size == -s _) {
 
648
                if ($check_md5sums) {
 
649
                        open HANDLE, $filename or
 
650
                                die "$filename: $!";
 
651
                        $md5->addfile(*HANDLE);
 
652
                        my $digest = $md5->hexdigest;
 
653
                        return ($md5sum eq $digest);
 
654
                }
 
655
                else {
 
656
                        # Assume it is ok, w/o md5 check.
 
657
                        return 1;
 
658
                }
 
659
        }
 
660
        return 0;
 
661
}
 
662
 
 
663
# Check file against md5sum and size from the Release file.
 
664
# It will return true if the md5sum matches.
 
665
sub check_lists {
 
666
  my $file = shift;
 
667
  my $t = $check_md5sums;
 
668
  my $ret = 0;
 
669
  $check_md5sums = 1;
 
670
  if (exists $file_lists_size{$file}) {
 
671
    $ret = check_file ($file, $file_lists_size{$file},
 
672
                       $file_lists_md5{$file});
 
673
  }
 
674
  $check_md5sums = $t;
 
675
  return $ret;
 
676
}
 
677
 
 
678
sub remote_get {
 
679
        my $file=shift;
 
680
        METHOD: {
 
681
                $_ = $download_method;
 
682
                /^ftp$/ && do {
 
683
                        return ftp_get($file);
 
684
                };
 
685
 
 
686
                /^rsync$/ && do {
 
687
                        return rsync_get($file);
 
688
                };
 
689
        }
 
690
}
 
691
 
 
692
# Get a file via ftp, first displaying its filename if progress is on.
 
693
# I should just be able to subclass Net::Ftp and override the get method,
 
694
# but it's late.
 
695
sub ftp_get {
 
696
        my $oldautoflush = $|;
 
697
        $| = 1;
 
698
        my $file=shift;
 
699
        my $percent = 0;
 
700
        $percent = sprintf("%3.0f",(($bytes_gotten/$bytes_to_get)*100)) unless($bytes_to_get == 0);
 
701
 
 
702
        if ($progress) {
 
703
          print "[$percent%] Getting: $file\t #";
 
704
        } elsif ($verbose) {
 
705
          print "[$percent%] Getting: $file";
 
706
        }
 
707
        my $ret=$ftp->get($file, $file);
 
708
        $bytes_gotten += $ftp->size($file) if $ftp->size($file);
 
709
        if ($ret) {
 
710
                my $mtime=$ftp->mdtm($file);
 
711
                utime($mtime, $mtime, $file) if defined $mtime;
 
712
        } else {
 
713
                if ($progress or $verbose) {
 
714
                  warn " failed\n";
 
715
                }
 
716
                push (@errlog,"Download of $file failed: ".$ftp->message);
 
717
                $num_errors++;
 
718
        }
 
719
        $| = $oldautoflush;
 
720
        print "\n" if ($verbose and not $progress);
 
721
        return $ret;
 
722
}
 
723
 
 
724
sub rsync_get {
 
725
        my $file=shift;
 
726
        my $opt="";
 
727
        (my $dirname) = $file =~ m:(.*/):;
 
728
        my @dir= split(/\//, $dirname);
 
729
        for (0..$#dir) {
 
730
                $opt = "$opt --include=" . join('/', @dir[0..$_]) . "/";
 
731
        }
 
732
        $opt = "$opt --progress" if $progress;
 
733
        $opt = "$opt -v" if $debug;
 
734
        system ("rsync -azI --partial --timeout=$timeout $opt $remoteroot --include=$file --exclude='*' $mirrordir");
 
735
        if ($? == 0) {
 
736
          return 1;
 
737
        } else {
 
738
          $num_errors++;
 
739
          return 0;
 
740
        }
 
741
}
 
742
 
 
743
# Get Packages file in the passed subdirectory.
 
744
sub get_packages {
 
745
  my $subdir=shift;
 
746
  make_dir($subdir);
 
747
 
 
748
  if (exists $file_lists_size{"$subdir/Packages.gz"}) {
 
749
    if (!check_lists ("$subdir/Packages.gz")) {
 
750
      say("Packages.gz needs fetch");
 
751
      remote_get("$subdir/Packages.gz");
 
752
      if (check_lists ("$subdir/Packages.gz")) {
 
753
        system("gunzip <$subdir/Packages.gz >$subdir/Packages");
 
754
        system("bzip2 <$subdir/Packages >$subdir/Packages.bz2");
 
755
      } else {
 
756
        say("$subdir/Packages.gz failed md5sum check");
 
757
        push (@errlog,"$subdir/Packages.gz failed md5sum check\n");
 
758
        $num_errors++;
 
759
      }
 
760
    }
 
761
  } else {
 
762
    die "Won't mirror without $subdir/Packages.gz signature in Release";
 
763
  }
 
764
  if (exists $file_lists_size{"$subdir/Packages"}) {
 
765
    if (!check_lists ("$subdir/Packages")) {
 
766
      say("Packages needs fetch");
 
767
      remote_get("$subdir/Packages");
 
768
      if (check_lists ("$subdir/Packages")) {
 
769
        system("bzip2 <$subdir/Packages >$subdir/Packages.bz2");
 
770
      } else {
 
771
        say("$subdir/Packages failed md5sum check");
 
772
        push (@errlog,"$subdir/Packages failed md5sum check\n");
 
773
        $num_errors++;
 
774
      }
 
775
    }
 
776
  }
 
777
  if (exists $file_lists_size{"$subdir/Packages.bz2"}) {
 
778
    if (!check_lists ("$subdir/Packages.bz2")) {
 
779
      say("Packages.bz2 needs fetch");
 
780
      remote_get("$subdir/Packages.bz2");
 
781
      if (!check_lists ("$subdir/Packages.bz2")) {
 
782
        say("$subdir/Packages.bz2 failed md5sum check");
 
783
        push (@errlog,"$subdir/Packages.bz2 failed md5sum check\n");
 
784
        $num_errors++;
 
785
      }
 
786
    }
 
787
  }
 
788
  if (exists $file_lists_size{"$subdir/Release"}) {
 
789
    if (!check_lists ("$subdir/Release")) {
 
790
      say("Release needs fetch");
 
791
      remote_get("$subdir/Release");
 
792
      if (!check_lists ("$subdir/Release")) {
 
793
        say("$subdir/Release failed md5sum check");
 
794
        push (@errlog,"$subdir/Release failed md5sum check\n");
 
795
      }
 
796
    }
 
797
  }
 
798
  push @package_files, "$subdir/Packages.gz";
 
799
  $files{"$subdir/Packages.gz"}=1;
 
800
  $files{"$subdir/Packages.bz2"}=1;
 
801
  $files{"$subdir/Packages"}=1;
 
802
  $files{"$subdir/Release"}=1;
 
803
}
 
804
 
 
805
# Get Sources file
 
806
sub get_sources {
 
807
  my $subdir=shift;
 
808
 
 
809
  if ($do_source) {
 
810
    make_dir($subdir);
 
811
    if (exists $file_lists_size{"$subdir/Sources.gz"}) {
 
812
      if (!check_lists ("$subdir/Sources.gz")) {
 
813
        warn "Sources.gz needs fetch\n";
 
814
        remote_get("$subdir/Sources.gz");
 
815
        if (check_lists ("$subdir/Sources.gz")) {
 
816
          system("gunzip <$subdir/Sources.gz >$subdir/Sources");
 
817
          system("bzip2 <$subdir/Sources >$subdir/Sources.bz2");
 
818
        } else {
 
819
          warn "$subdir/Sources.gz failed md5sum check";
 
820
          $num_errors++;
 
821
        }
 
822
      }
 
823
    } else {
 
824
      die "Won't mirror without $subdir/Sources.gz signature in Release";
 
825
    }
 
826
    if (exists $file_lists_size{"$subdir/Sources"}) {
 
827
      if (!check_lists ("$subdir/Sources")) {
 
828
        warn "Sources needs fetch\n";
 
829
        remote_get("$subdir/Sources");
 
830
        if (check_lists ("$subdir/Sources")) {
 
831
          system("bzip2 <$subdir/Sources >$subdir/Sources.bz2");
 
832
        } else {
 
833
          warn "$subdir/Sources failed md5sum check";
 
834
          $num_errors++;
 
835
        }
 
836
      }
 
837
    }
 
838
    if (exists $file_lists_size{"$subdir/Sources.bz2"}) {
 
839
      if (!check_lists ("$subdir/Sources.bz2")) {
 
840
        warn "Sources.bz2 needs fetch\n";
 
841
        remote_get("$subdir/Sources.bz2");
 
842
        if (!check_lists ("$subdir/Sources.bz2")) {
 
843
          warn "$subdir/Sources.bz2 failed md5sum check";
 
844
          $num_errors++;
 
845
        }
 
846
      }
 
847
    }
 
848
    push @source_files, "$subdir/Sources.gz";
 
849
    $files{"$subdir/Sources.gz"}=1;
 
850
    $files{"$subdir/Sources"}=1;
 
851
    $files{"$subdir/Sources.bz2"}=1;
 
852
  }
 
853
}
 
854
 
 
855
# Make a directory including all needed parents.
 
856
{
 
857
        my %seen;
 
858
        
 
859
        sub make_dir {
 
860
                my $dir=shift;
 
861
                
 
862
                my @parts=split('/', $dir);
 
863
                my $current='';
 
864
                foreach my $part (@parts) {
 
865
                        $current.="$part/";
 
866
                        if (! $seen{$current}) {
 
867
                                if (! -d $current) {
 
868
                                  mkdir ($current, 0755) or die "mkdir failed: $!";
 
869
                                  debug("Created directory: $current");
 
870
                                }
 
871
                                $seen{$current}=1;
 
872
                        }
 
873
                }
 
874
        }
 
875
}
 
876
 
 
877
sub say {
 
878
        print join(' ', @_)."\n" if ($verbose or $progress);
 
879
}
 
880
 
 
881
sub debug {
 
882
        print $0.': '.join(' ', @_)."\n" if $debug;
 
883
}
 
884
 
 
885
=head1 COPYRIGHT
 
886
 
 
887
This program is copyright 2001 by Joey Hess <joeyh@debian.org>, under
 
888
the terms of the GNU GPL, copyright 2003 by Goswin von Brederlow
 
889
<brederlo@informatik.uni-tuebingen.de>.
 
890
 
 
891
The author disclaims any responsibility for any mangling of your system,
 
892
unexpected bandwidth usage bills, meltdown of the Debian mirror network, 
 
893
etc, that this script may cause. See NO WARRANTY section of GPL.
 
894
 
 
895
=head1 AUTHOR
 
896
 
 
897
Current: Goswin von Brederlow <brederlo@informatik.uni-tuebingen.de>
 
898
Previous authors: Joey Hess <joeyh@debian.org>
 
899
                  Joerg Wendland <joergland@debian.org>
 
900
 
 
901
=head1 MOTTO
 
902
 
 
903
Waste bandwith -- put a partial mirror on your laptop today!
 
904
 
 
905
=cut
 
906