Add a test suite for etags
[emacs.git] / test / etags / perl-src / mirror.pl
blob2bbbb76a6170e949830b31babd16a2203b59a02e
1 #!/usr/bin/perl
2 # Make local directories mirror images of a remote sites
5 # Copyright (C) 1990 - 1998 Lee McLoughlin
7 # Permission to use, copy, and distribute this software and its
8 # documentation for any purpose with or without fee is hereby granted,
9 # provided that the above copyright notice appear in all copies and
10 # that both that copyright notice and this permission notice appear
11 # in supporting documentation.
13 # Permission to modify the software is granted, but not the right to
14 # distribute the modified code. Modifications are to be distributed
15 # as patches to released version.
17 # This software is provided "as is" without express or implied warranty.
20 # The Debian system patched this file after installation to add:
21 # ls-lR file patching 2001/09/29
22 # Copyright (C) 1999-2001 Ian Maclaine-cross <iml@debian.org>
23 # and other changes.
24 # Debian patches are copyright by their authors and you may use them only
25 # under the conditions of the General Public License in file GPL.
27 # $Id: mirror.pl,v 2.9 1998/05/29 19:01:07 lmjm Exp lmjm $
28 # $Log: mirror.pl,v $
29 # Revision 2.9 1998/05/29 19:01:07 lmjm
30 # Lots of changes. See CHANGES since 2.8 file.
32 # Revision 2.8 1995/08/06 14:03:52 lmjm
33 # Trap a wider range of signals to aid in debugging under perl5
34 # Avoid looping processing symlinks.
36 # Revision 2.7 1995/08/06 13:59:00 lmjm
37 # No longer require socket.ph in this module.
38 # Escape at signs and dollars for perl5
39 # Make sure proxy_gateway is at least null.
40 # Added ls_fix_mappings, failed_gets_excl, store_remote_listing, passive_ftp
41 # and using_socks.
42 # Stop using dollar star as perl5 has dropped it.
43 # Process local directory listing before connecting to remote.
44 # Allow for remote_account pasword.
45 # Only one arg to undef, for early perl5's
46 # Use all capitals for file descriptors.
47 # Use ftp'close not ftp'quit
48 # Avoid file renaming under MACos
49 # Corrected file deleting.
51 # Revision 2.6 1994/06/10 18:28:27 lmjm
52 # Dropped debug statement.
54 # Revision 2.5 1994/06/06 18:39:21 lmjm
55 # Don't have . in the extra_path.
56 # Have 'internet-gateway' as the default proxy_gateway when INTERNET_HOST unset.
57 # Allow strip_cr (from Andrew).
58 # More symlink handling...
59 # Set type for vms correctly.
60 # Changed response from ftp'delete, also corrected path used.
62 # Revision 2.4 1994/04/29 20:11:09 lmjm
63 # Use correct variable for hostname
65 # Revision 2.3 1994/01/31 18:31:22 lmjm
66 # Allow for funny chars in filenames when calling the shell (Erez).
67 # Added compress_size_floor to avoid compressing small files (David).
68 # Added get_missing to just delete files not on remote system (Pieter).
69 # Don't try to delete old dirs if no time set (Pieter).
70 # Zap .dir$$ files, and keep then in $big_temp.
71 # Pretty print time in comparisons.
72 # Move the large comparision conditionals into routines (David).
73 # Allow for sites with limited filename lengths.
74 # Allow for deleted files when doing deletes.
75 # Don't delete dirs that are really symlinks.
77 # Revision 2.2 1993/12/14 11:09:15 lmjm
78 # Allow for no flock.
79 # Use installed socket.ph.
80 # Allow for system 5.
81 # Use percentage defaults on max_delete_*
82 # Checkout regexps before using.
83 # Allow for extra leading | in local_ignore.
84 # Return better exit codes.
85 # Fixups for recurse_hard.
86 # Smarter symlink handling.
88 # Revision 2.1 1993/06/28 14:59:00 lmjm
89 # Full 2.1 release
93 $#ARGV >= 0 or die("Try `man mirror` for help.\n");
95 # Make sure we don't go recursive processing signals
96 $sigs = 0;
97 $max_sigs = 10;
99 # Am I on windoze?
100 $on_win = ($^O =~ /mswin/i);
101 $path_sep = $on_win ? ';' : ':';
102 $file_sep = $on_win ? '\\' : '/';
103 # Internally mirror uses / but when looking at names from environment allow
104 # for either
105 $file_sep_pat = $on_win ? "[\\/]" : "/"; # allow for c:\win/fred on windoze
108 # Default settings file loaded from a directory in PERLLIB
109 $defaults_file = 'mirror.defaults';
110 $load_defaults = 1;
112 # Try to find the default location of various programs via
113 # the users PATH then using $extra_path
114 if( ! $on_win ){
115 $extra_path = '/usr/local/bin:/usr/new/bin:/usr/public/bin:/usr/ucb:/usr/bin:/bin:/etc:/usr/etc:/usr/local/etc';
117 if( $extra_path ne '' ){
118 $ENV{ 'PATH' } .= $path_sep . $extra_path;
121 &trap_signals();
123 # If compressing a local file to send need somewhere to store the temp
124 # compressed version.
125 $big_temp = '/var/tmp';
127 # Hopefully we have flock.
128 $can_flock = 1;
130 # no debugging by default
131 $debug = 0;
133 # NOTE:
134 # It is not an error for a program not to be found in the path as the user
135 # may be setting it as part of the package details or defaults.
137 # Used by the save_deletes option
138 $mv_prog = 'mv -f';
140 # compress must be able to take the -d arg to cause it to uncompress.
141 $sys_compress_prog = &find_prog( 'compress' );
142 $sys_compress_suffix = 'Z';
144 # Like compress gzip must be able to take -d
145 if( $gzip_prog = &find_prog( 'gzip' ) ){
146 # Force maximum compression with gzip
147 $gzip_level = ' -9';
148 $gzip_prog .= $gzip_level;
149 $gzip_suffix = 'gz';
150 $old_gzip_suffix = 'z';
153 # For remote systems with gzipped patches to their ls-lR.gz file. The
154 # gzipped patch file is a unified diff between old and new ls-lR. The
155 # times file has modification times in decimal epoch seconds of the
156 # old and new ls-lR file on its first and second lines respectively.
157 if( $patch_prog = &find_prog( 'patch' ) ){
158 $patch_local = '-usNT';
159 $patch_UTC = '-usNZ';
161 $patch_suffix = '.pth'; # These suffices distinct
162 $patch_gzip_suffix = '.patch.gz'; # if truncated locally.
163 $times_suffix = '.times';
165 # A mail program that can be called as: "$mail_prog person_list'
166 # Can be overridden with the mail_prog keyword.
167 # If you use $mail_subject to pass extra arguments then remember that
168 # the mail program will need to know how to handle them.
169 $mail_prog = &find_prog( 'mailx' );
170 if( ! $mail_prog ){
171 $mail_prog = &find_prog( 'Mail' );
173 if( ! $mail_prog ){
174 $mail_prog = &find_prog( 'mail' );
177 # Used to remove directory heirarchies. This program is passed the -rf
178 # arguments.
179 $rm_prog = &find_prog( 'rm' );
181 # Generate checksums
182 $sum_prog = &find_prog( 'sum' );
184 # SPECIAL NOTE: This is eval'd, so DONT put double-quotes (") in it.
185 # You can get local variables to appear as in the second example:
186 $mail_subject = '-s \'mirror update\'';
187 # $mail_subject = ' -s \'mirror update of $package\'';
189 # When scanning the local directory, how often to prod the remote
190 # system to keep the connection alive
191 $prod_interval = 60;
193 # Put the directory that mirror is actually in at the start of PERLLIB.
194 $dir = &real_dir_from_path( $0 );
195 unshift( @INC, $dir );
197 # Debian GNU/Linux stores mirror.defaults in /etc/mirror
198 $debian_defs = '/etc/mirror';
199 unshift( @INC, $debian_defs ) if -d $debian_defs;
201 # This, when eval'd, will get the current dir under windows NT/95
202 $win_getcwd = 'Win32::GetCwd';
204 # Make sure that your PERLLIB environment variable can get you
205 # all these or that they are installed.
206 require 'ftp.pl';
207 require 'lsparse.pl';
208 require 'dateconv.pl';
210 # Find some local details
211 # The current directory
212 $home = &cwd();
213 # The hostname
214 $hostname_cmd = &find_prog( 'hostname' );
215 if( $hostname_cmd ne '' ){
216 chop( $hostname = `$hostname_cmd` );
218 if( $hostname eq '' ){
219 $hostname_cmd = &find_prog( 'uname' );
220 if( $hostname_cmd ne '' ){
221 chop( $hostname = `$hostname_cmd -n` );
222 if( $hostname eq '' ){
223 chop( $hostname = `$hostname_cmd -l` );
227 if( $hostname eq '' ){
228 $hostname = 'localhost';
231 if( $hn = (gethostbyname( "$hostname" ))[ 0 ] ){
232 $hostname = $hn;
235 # Some systems hold the username in $USER, some in $LOGNAME.
236 $me = $ENV{'USER'} || $ENV{'LOGNAME'};
238 # Files matching this pattern are usually compressed.
239 $squished = '\.(Z|z|gz)$';
241 # special /bin/sh chars that must be escaped.
242 $shell_metachars = '\"|\$|`|\\\\';
244 # Remote directory parsing fail if not given input every readtime seconds.
245 $parse_time = 600;
247 # Timeout are not fatal unless you get more than this number of them.
248 $max_timeouts = 20;
250 # If connected to a site then this holds the site name.
251 $connected = '';
253 # Umask setting.
254 $curr_umask = sprintf( "0%o", umask );
256 # mapping from a pathname to a number - just to make the keys to assoc arrays
257 # shorter.
258 $map_init = 1; # just so I know 0 is invalid
260 $tmp = "/tmp";
261 @assocs = ( 'local_map', 'remote_map' );
263 # A reasonable set of defaults
264 # these are in the same order as the documentation - except where order is
265 # important
266 $default{ 'hostname' } = $hostname; # The LOCAL hostname
268 $default{ 'package' } = ''; # should be a unique handle for the "lump" to be mirrored
269 $default{ 'site' } = ''; # site to connect to
270 $default{ 'remote_dir' } = ''; # remote directory to mirror
271 $default{ 'local_dir' } = ''; # local directory to copy into
273 $default{ 'remote_user' } = 'anonymous'; # the remote guest account name
274 $default{ 'remote_password' } = "$me\@$hostname";
275 $default{ 'remote_account' } = ''; # remote account name/passwd (for systems
276 # that use it.)
277 # Used for group and gpass. (As in ftp.1 site group/gpass commands.)
278 $default{ 'remote_group' } = '';
279 $default{ 'remote_gpass' } = '';
280 $default{ 'timeout' } = 120; # timeout ftp requests after this many seconds
281 $default{ 'failed_gets_excl' } = ''; # failed messages to ignore while getting,
282 # if failed to ftp'get
283 $default{ 'ftp_port' } = 21; # port number of remote ftp daemon
284 $default{ 'proxy' } = 0; # normally use regular ftp
285 $default{ 'proxy_ftp_port' } = 4514; # default from Sun
286 $default{ 'proxy_gateway' } = "$ENV{ 'INTERNET_HOST' }";
287 # Gateway to use for proxy
288 # (Null if environment not set.)
289 $default{ 'using_socks' } = 0; # Set the default perl version to the non-SOCKS one.
290 $default{ 'passive_ftp' } = 0; # Set the default ftp usage not to use passive (PASV) ftp.
291 $default{ 'retry_call' } = 1; # Retry the call if it fails first time
292 $default{ 'disconnect' } = 0; # Force close at end of package EVEN if
293 # next package is to the same site
294 $default{ 'remote_idle' } = ''; # Set the remote idle timer to this
296 $default{ 'get_patt' } = "."; # regex of pathnames to retrieve
297 $default{ 'exclude_patt' } = ''; # regex of pathnames to ignore
298 $default{ 'local_ignore' } = ''; # regex of local pathnames to totally ignore
299 $default{ 'get_newer' } = 1; # get remote file if its date is newer than local
300 $default{ 'get_size_change' } = 1; # get the file if size if different than local
301 $default{ 'make_bad_symlinks' } = 0; # prevent symlinks to non-existant files
302 $default{ 'follow_local_symlinks' } = ''; # Follow symlinks to pathnames matching this regexp.
303 $default{ 'get_symlink_files' } = 0; # If true gets file and makes symlink otherwise bad.
304 $default{ 'get_missing' } = 1; # Set get_missing to 0 to just delete files not on remote system
305 $default{ 'get_file' } = 1; # perform get, not put by default
306 $default{ 'text_mode' } = 0; # transfer in binary mode by default
307 $default{ 'strip_cr' } = 0; # Delete \r (usefull when transfering from
308 # mainframes -- set text_mode and strip_cr)
309 $default{ 'vms_keep_versions' } = 1; # Keep multiple VMS versions
310 $default{ 'vms_xfer_text' } = 'readme$|info$|listing$|\.c$';
311 # pattern of VMS files to xfer in TEXT mode
312 # (Case insensitive)
313 $default{ 'name_mappings' } = '';# remote to local pathname mappings
314 # used to change layout or zap weird chars
315 # (eg s:old:new)
316 $default{ 'external_mapping' } = '';# remote to local mapping by external routine
317 $default{ 'update_local' } = 0; # Don't just update local dirs
318 $default{ 'max_days' } = 0; # Ignore age of file
319 $default{ 'max_size' } = 0; # If non-zero dont get files larger than this
320 $default{ 'chmod' } = 1; # perform a chmod after an xfer
322 $default{ 'user' } = ''; # UID/user name to give to local pathnames
323 $default{ 'group' } = ''; # GID/group name to give to local pathnames
324 $default{ 'mode_copy' } = 0; # true indicates to copy the mode bits
325 $default{ 'file_mode' } = 0444; # Mode to give files created locally
326 $default{ 'dir_mode' } = 0755; # mode to give directories created locally
327 $default{ 'force' } = 0; # don't force by default
328 $default{ 'umask' } = 07000; # DONT allow setuid things by default
329 $default{ 'use_timelocal' } = 1; # Use local time NOT gmt to timestamp files. (See also the -T flag.)
330 $default{ 'force_times' } = 1; # Force local file times to match the original
332 $default{ 'do_deletes' } = 0; # delete dest files if not in src tree
333 $default{ 'delete_patt' } = '.';# delete only files which match this pattern
334 $default{ 'delete_get_patt' } = 0;# true: set delete_patt to get_patt
335 $default{ 'delete_excl' } = ''; # regex of local pathnames to ignore when deleting
336 $default{ 'max_delete_files' } = '10%'; # Any more than this and DONT delete
337 $default{ 'max_delete_dirs' } = '10%'; # Any more than this and DONT delete
338 $default{ 'save_deletes' } = 0; # save local files if not in remote
339 $default{ 'save_dir' } = 'Old'; # directory in which to create tree for keeping
340 # files no longer in remote
341 $default{ 'store_remote_listing' } = ''; # Where to store remote site listings on local system
343 $default{ 'compress_patt' } = ''; # compress files matching this pattern
344 $default{ 'compress_excl' } = $squished; # dont compress regexp (case insensitive)
345 $default{ 'compress_prog' } = $sys_compress_prog; # Program to compress files.
346 $default{ 'compress_suffix' } = $sys_compress_suffix; # Suffix on compressed files
347 $default{ 'compress_conv_patt' } = '(\.Z|\.taz)$';
348 # compress->gzip files matching this pattern
349 $default{ 'compress_conv_expr' } = 's/\.Z$/.gz/;s/\.taz$/.tgz/';
350 # perl expressions to convert names of files from compress->gzip
351 $default{ 'compress_size_floor' } = 0; # don't compress files < this size
353 $default{ 'split_max' } = 0; # Files > this size can be split up.
354 $default{ 'split_patt' } = ''; # Files must match this pattern to be split
355 $default{ 'split_chunk' } = 100 * 1024; # Size of split-up chunks
357 $default{ 'remote_fs' } = 'unix'; # Remote filestore
358 # Other posibilies dls, netware and vms
359 $default{ 'ls_lR_file' } = ''; # remote file containing ls-lR, patch or
360 # times - else use remote ls
361 $default{ 'local_ls_lR_file' } = ''; # local file containing ls-lR
362 # used when first copying a large remote package
363 # or ls_lR_file is a remote ls-lR patch or times
364 $default{ 'recursive' } = 1; # true indicates to do recursive processing
365 $default{ 'recurse_hard' } = 0; # true indicates have to cwd+ls for each remote
366 # subdirectory - AVOID wherever possible.
367 $default{ 'flags_recursive' } = '-lRat'; # Flags passed to remote dir
368 $default{ 'flags_nonrecursive' } = '-lat'; # Flags passed to remote dir
369 $default{ 'ls_fix_mappings' } = '';# Correct pathnames in remote listings
370 # (eg s:usr/spool/pub/::) to match reality
372 $default{ 'update_log' } = ''; # Filename where an update report is to be kept
373 $default{ 'mail_to' } = ''; # Mail a report to these addresses
374 $default{ 'mail_prog' } = $mail_prog; # the mail program (see $mail_prog)
375 $default{ 'mail_subject' } = $mail_subject; # Subject passed to mail_prog
377 $default{ 'comment' } = ''; # General comment used in report
378 # If mirroring a VERY large directory then it is best to put the assoc
379 # arrays in files (use command line switch -F. to turn on).
380 $default{ 'use_files' } = 0;
381 $default{ 'interactive' } = 0; # noninteractive copy default
382 $default{ 'skip' } = ''; # If set then skip this entry giving value as reason
383 $default{ 'verbose' } = 0; # Verbose messages
384 $default{ 'algorithm' } = 0; # The mirror algorithm to use
385 $default{ 'local_dir_check' } = 0; # Check local_dir exists before mirroring?
387 # I really want to drop this option.
388 $default{ 'delete_source' } = 0;# delete source after xfer (default = NO!!!)
390 @boolean_values = ( 'get_newer', 'get_size_change', 'do_deletes',
391 'update_local', 'force_times', 'retry_call', 'recursive',
392 'mode_copy', 'disconnect', 'interactive', 'text_mode',
393 'force', 'get_file', 'verbose', 'proxy', 'delete_get_patt',
394 'delete_source', 'save_deletes', 'use_files', 'use_timelocal',
395 'make_bad_symlinks', 'get_symlink_files', 'recurse_hard',
396 'get_missing', 'strip_cr', 'passive_ftp', 'using_socks',
397 'local_dir_check' );
399 %boolean_values = ();
400 &set_assoc_from_array( *boolean_values );
402 @regexp_values = ( 'get_patt', 'exclude_patt', 'local_ignore',
403 'delete_patt', 'delete_excl', 'split_patt', 'save_deletes',
404 'compress_patt', 'compress_excl', 'compress_conv_patt',
405 'failed_gets_excl', 'store_remote_listing' );
408 # message levels used by &msg( level, msg )
409 # if you call msg as &msg( msg ) the level is presumed to be just $pr.
410 $pr = 0; # Always print out messages
411 $log = 1; # push this messages onto @log
413 # The max number of directory failures under algorithm 1 before giving up.
414 $max_failed_dirs = 5;
417 # Exit status
418 $exit_status = 0;
419 $exit_status_xfers = 0;
421 # "#defines" for the above
422 $exit_xfers = 16; # Add this to the exit code to show xfers took place
423 $exit_ok = 0;
424 $exit_fail = 1;
425 $exit_fail_noconnect = 2;
427 # -d Turn on debugging - more -d's means more debugging.
428 # -ppattern Just do packages matching pattern.
429 # -Rpattern Skip till the first package name matches pattern then do all.
430 # it and following packages.
431 # -n Do nothing, just show what would be done.
432 # -F Use files for assoc arrays (see also the variable use_files).
433 # -gsite:path
434 # Get all files on given site. If path matches .*/.+ then
435 # it is the name of the directory and the last part is the
436 # pattern of filenames to get. If path matches .*/ then
437 # it is the name of a directory and all its contents are retrieved.
438 # Otherwise path is the pattern to be used in '/'.
439 # -r Same as "-krecursive=false".
440 # -kvar=val set variable to value.
441 # -uusername Same as "-kremote_user=username", prompts for remote_password.
442 # -v Print version and exit.
443 # -T Dont transfer just force local timestamps to match remote.
444 # -N Don't load mirror.defaults.
445 # -L Generate a pretty list of what is being mirrored.
446 # -m Same as "-kmode_copy=true".
448 # -Cconfig_file
449 # -P Same as "-kget_file=false -kinteractive=true".
450 # -G Same as "-kget_file=true -kinteractive=true".
451 # -t Same as "-ktext_mode=true".
452 # -f Same as "-kforce=true".
453 # -sSITENAME Same as "-ksite=SITENAME.
454 # -ULOGFILE Set the upload log to LOGILE - if none given uses
455 # the file $home/upload_log.$mday.$mon.$year
457 # -DUMP Dump perl - to be later undumped -- THIS DOES NOT YET WORK!!!
459 sub msg_version
461 &msg( '$Id: mirror.pl,v 2.9 1998/05/29 19:01:07 lmjm Exp lmjm $' . "\n" );
462 &msg( 'Debian patch version: mirror (2.9-38) Tue Jan 29 07:06:25 2002 UTC.' . "\n" );
463 &msg( 'Copyright conditions are in file /usr/share/doc/mirror/copyright.' . "\n" );
466 parse_args:
467 while( $ARGV[ 0 ] =~ /^-/ ){
468 local( $arg ) = shift;
470 if( $arg eq '-d' ){
471 if( $debug == 2 ){
472 &msg_version();
474 $| = 1;
475 $debug++;
476 next;
479 if( $arg =~ /^-(p)(.*)/ || $arg =~ /^-(R)(.*)/ ){
480 local( $flag, $p ) = ($1, $2);
481 if( $flag eq 'R' ){
482 # Skip all packages till a match is made
483 # then process ALL further packages
484 $skip_till = 1;
486 if( ! $p ){
487 # Must be -p/-R space arg
488 $p = shift;
490 if( $p !~ /[a-zA-Z0-9]/ ){
491 die "Invalid package name to -p of: $p\n";
492 next;
494 # Only mirror the named packages
495 $do_packages{ $p } = 1;
496 $limit_packages = 1;
497 next;
500 if( $arg eq '-n' ){
501 # Do nothing - just show what would be done
502 $dont_do = 1;
503 $debug += 2;
504 $| = 1;
505 next;
508 if( $arg eq '-F' ){
509 # Use files for the dir listings assoc lookups
510 $use_files = 1;
511 $command_line{ 'use_files' } = 1;
512 next;
515 if( $arg eq '-T' ){
516 # Don't actually get any files but just force
517 # local timestamps to be the same on the remote system
518 $timestamp = 1;
519 $command_line{ 'force_times' } = 'true';
520 next;
523 if( $arg =~ /^-g(.*)$/ ){
524 # the next arg is the site:path to get
525 local( $site_path ) = $1;
527 if( ! $site_path ){
528 # Must be -g space arg
529 $site_path = shift;
532 # DONT use the system defaults!
533 $load_defaults = 0;
535 # This is probably interactive so print interactively
536 $| = 1;
538 if( $site_path =~ /(.*):(.*)?/ ){
539 local( $site, $path ) = ($1, $2);
540 push( @get_sites, $site );
541 # Find the directory and files
542 if( $path =~ m|^(.*)/([^/]*)$| ){
543 if( $1 eq '' && $2 eq '' ){
544 push( @get_paths, '/' );
545 push( @get_patt, '.' );
547 elsif( $1 eq '' ){
548 push( @get_paths, '/' );
550 else {
551 push( @get_paths, $1 );
553 if( $2 eq '' ){
554 push( @get_patt, '.' );
556 else {
557 push( @get_patt, "^$2\$" );
560 else {
561 push( @get_paths, '.' );
562 push( @get_patt, "^$path\$" );
565 else {
566 die "expected -gsite:path got $arg";
568 next;
571 if( $arg eq "-r" ){
572 # no recursive copy
573 $command_line{ 'recursive' } = 0;
574 next;
576 # Debian bug #93853, -k keyword=value did not work, jkn@softavenue.fi
577 if( $arg =~ /^-k(.*)/ ){
578 local( $key_val ) = $1;
579 if( ! $key_val ){
580 # Must be -k space key=val
581 $key_val = shift;
583 if( $key_val =~ /(.*)=(.*)/ ){
584 # set the keyword = value
585 if( !defined( $default{ "$1" } ) ){
586 warn "Invalid keyword $1\n";
587 } else {
588 $command_line{ "$1" } = $2;
591 next;
594 if( $arg =~ /^-u(.*)/ ){
595 local( $user ) = $1;
597 if( ! $user ){
598 # must be -u space user
599 $user = shift;
602 # override the user name
603 $command_line{ 'remote_user' } = $user;
604 # and ask for a password
605 $command_line{ 'remote_password' } = &get_passwd( $user );
606 next;
609 if( $arg eq '-N' ){
610 $load_defaults = 0;
611 next;
614 if( $arg eq '-v' ){
615 &msg_version();
616 exit( 0 );
619 if( $arg eq '-L' ){
620 # Generate a pretty list of what is being mirrored
621 $pretty_print = 1;
622 next;
625 if( $arg eq '-m' ){
626 # propagate the mode
627 $command_line{ 'mode_copy' } = 'true';
628 next;
631 # Old command line interface flags
632 if( $arg =~ /^-C(.*)/ ){
633 # specify the config file
634 local( $c ) = $1;
635 if( $c !~ /./ ){
636 die "Must give config file name -Cname ($arg)\n";
638 # Only mirror the named packages
639 push( @config_files, $c);
640 next;
643 if( $arg eq '-P' ){
644 # put files
645 $command_line{ 'get_file' } = 'false';
646 $command_line{ 'interactive' } = 'true';
647 next;
650 if( $arg eq '-G' ){
651 # get files
652 $command_line{ 'get_file' } = 'true';
653 $command_line{ 'interactive' } = 'true';
654 next;
657 if( $arg eq '-t' ){
658 # set the file mode to text
659 $command_line{ 'text_mode' } = 'true';
660 next;
663 if( $arg eq '-f' ){
664 # force file transfers irregardless of date/size matches
665 $command_line{ 'force' } = 'true';
666 next;
669 if( $arg =~ /^-s(.*)/ ){
670 # override the site name
671 $command_line{ 'site' } = $1;
672 next;
675 if( $arg =~ /^-U(.*)/ ){
676 $upload_log = $1;
677 if( $upload_log eq '' ){
678 local( $sec,$min,$hour,$mday,$mon,$year,
679 $wday,$yday,$isdst )
680 = localtime( time );
681 $mon++;
682 $upload_log = "$home/upload_log.$mday.$mon.$year";
685 next;
688 if( $arg eq '-DUMP' ){
689 # THIS DOES NOT YET WORK!!!!!
690 $dumped_version = 1;
691 warn "Dumping perl\n";
692 dump parse_args;
695 warn "Unknown arg $arg, skipping\n";
698 # Handle multiline buffers in a sane way
699 # This is deprecated in perl-5. Someone should add "/m" modifiers to any
700 # regexps that *really* need it, not all.
701 # $* = 1;
703 $interactive = $command_line{ 'interactive' };
705 if( ! $interactive ){
706 local( $c );
708 # The remainder of ARGV are package names
709 foreach $c ( @ARGV ){
710 push( @config_files, $c );
714 if( $interactive && $limit_packages){
715 die "Can not mix -p and interactive";
718 $value{ 'remote_user' } = $default{ 'remote_user' };
719 %value = ();
720 &set_defaults();
722 if( $load_defaults ){
723 local( $dir, $mp );
724 foreach $dir ( @INC ){
725 local( $f ) = "$dir/$defaults_file";
726 if( -f $f ){
727 $mp = $f;
728 last;
731 if( $mp ){
732 &msg( "defaults from $mp\n" ) if $debug > 2;
733 splice( @config_files, 0, 0, $mp );
735 else {
736 warn "No $defaults_file found in perl library path\n";
739 elsif( $debug > 1 ){
740 &msg( "not loading $defaults_file\n" );
744 &interpret_config_files();
746 # Shut down any remaining ftp session
747 &disconnect();
749 &msg( "All done, Exiting\n" ) if $debug;
750 exit( $exit_status + $exit_status_xfers );
753 $key = ''; # The current keyword
754 $value = ''; # the value for the keyword
756 sub interpret_config_files
758 local( $fname );
760 if( $#get_sites >= 0 ){
761 while( $#get_sites >= 0 ){
762 $value{ 'site' } = pop( @get_sites );
763 $value{ 'remote_dir' } = pop( @get_paths );
764 $value{ 'get_patt' } = pop( @get_patt );
765 $value{ 'local_dir' } = '.';
766 $value{ 'remote_user' } = 'anonymous';
767 $exit_status = &do_mirror();
769 return;
773 if( $command_line{ 'interactive' } ){
774 # No config file to read
775 $value{ 'package' } = 'interactive';
776 $exit_status = &do_mirror();
777 return;
780 # if no configuration files were specified use standard input
781 @ARGV = @config_files;
782 &interpret_config();
785 sub interpret_config
787 while( <> ){
788 # Ignore comment and blank lines
789 next if /^\s*#/ || /^\s*$/;
791 &parse_line();
793 # Is this a new package?
794 if( $value{ 'package' } && $key eq 'package' ){
795 # mirror the existing package
796 $exit_status = &do_mirror();
798 # reset
799 &set_defaults();
801 # Make sure I'm at the right place for <> to work!
802 chdir $home;
805 if( $debug > 3 ){
806 &msg( "$key \"$value\"\n" );
809 $value{ $key } = $value;
811 # do an explicit close for each file so $. gets reset
812 if( eof( ARGV ) ){
813 if( $debug > 3 ){
814 &msg( "-- end of config file \"$ARGV\"\n" );
816 close( ARGV );
820 # Mirror the last package in the file
821 if( $value{ 'package' } ){
822 $exit_status = &do_mirror();
826 # parse each line for keyword=value
827 sub parse_line
829 local( $eqpl );
830 local( $cont ) = '&';
832 chop;
833 if( /^\s*([^\s=+]+)\s*([=+])(.*)?$/ ){
834 ($key, $eqpl, $value) = ($1, $2, $3);
835 # If the value ends in the continuation character then
836 # tag the next line on the end (ignoring any leading ws).
837 while( $value =~ /^(.*)$cont$/o && !eof ){
838 $_ = <>;
839 local( $v ) = $1;
840 if( /^\s*(.*)$/ ){
841 $value = $v . $1;
844 if( $debug > 3 ){
845 &msg( "read: $key$eqpl$value\n" );
848 else {
849 warn "unknown input in \"$ARGV\" line $. of: $_\n";
851 if( ! defined( $default{ "$key" } ) ){
852 die "unknown keyword in \"$ARGV\" line $. of: $key\n";
854 if( $eqpl eq '+' ){
855 $value = $value{ $key } . $value;
859 # Initialise the key values to the default settings
860 sub set_defaults
862 %value = %default;
863 undef( $uid );
864 undef( $gid );
867 # Override the current settings with command line values
868 sub command_line_override
870 local( $key, $val, $overrides );
872 while( ($key, $val) = each %command_line ){
873 $overrides++;
874 if( $boolean_values{ $key } ){
875 # a boolean value
876 $value{ $key } = &istrue( $val );
877 } else {
878 # not a boolean value
879 $value{ $key } = $val;
883 if( $debug > 4 ){
884 if( $overrides ){
885 &pr_variables( "keywords after command line override\n" );
887 else {
888 &msg( "No command line overrides\n" );
893 # set each variable $key = $value{ $key }
894 sub set_variables
896 local( $key, $val );
898 while( ($key, $val) = each %value ){
899 # for things like passwords it is nice to have the
900 # real value in a file
901 if( $val =~ /^\<(.*)$/ ){
902 local( $val_name ) = $1;
903 open( VAL_FILE, $val_name ) ||
904 die "can't open value file $val_name\n";
905 $val = <VAL_FILE>;
906 close( VAL_FILE );
907 chop $val if $val =~ /\n$/;
910 if( $boolean_values{ $key } ){
911 # a boolean value
912 eval "\$$key = &istrue( $val )";
914 else {
915 # not a boolan value
916 # Change all \ to \\ since \'s will be escaped in
917 # the following string used in the eval.
918 $val =~ s/([^\\])(')/$1\\$2/g;
919 eval "\$$key = '$val'";
921 if( $key eq 'compress_prog' ){
922 if( $val eq 'compress' ){
923 $compress_prog = $sys_compress_prog;
924 $compress_suffix = $sys_compress_suffix;
926 elsif( $val eq 'gzip' ){
927 if( ! $gzip_prog ){
928 die "Trying to use gzip but not found in PATH\n";
930 $compress_prog = $gzip_prog;
931 $compress_suffix = $gzip_suffix;
933 elsif( $debug > 2 && $compress_prog ne $gzip_prog &&
934 $compress_prog ne $sys_compress_prog ){
935 &msg( "compress_prog ($compress_prog) not compress or gzip, presuming program name\n" .
936 "- user must set compress_suffix\n" );
938 &upd_val( 'compress_prog' );
939 &upd_val( 'compress_suffix' );
942 if( $compress_patt ne '' && $compress_prog eq '' ){
943 &msg( "compress_patt set but no compress_prog so compress_patt reset to nothing" );
944 $compress_patt = '';
948 # Reset the umask if needed.
949 # Do it here to try and get it done as early as possible.
950 # If the user doesn't use octal umasks this will cause umask
951 # to be called again unnecessarily - but that is pretty cheap.
952 if( $umask && $umask != $curr_umask ){
953 local( $val ) = $umask;
954 $val = oct( $val ) if $val =~ /^0/;
955 umask( $val );
956 $curr_umask = sprintf( "0%o", umask );
959 &map_user_group();
962 sub upd_val
964 local( $key ) = @_;
965 if( $package eq 'defaults' ){
966 $default{ $key } = $value{ $key };
970 sub pr_variables
972 local( $msg ) = @_;
973 local( $nle ) = 60;
974 local( $out ) = 0;
975 local( $key, $val, $str );
977 &msg( $msg );
978 if( $get_file ){
979 &msg( "package=$package $site:$remote_dir -> $local_dir\n\t" );
981 else {
982 &msg( "package=$package $local_dir -> $site:$remote_dir\n\t" );
985 for $key ( sort keys( %value ) ){
986 next if $key eq 'package' ||
987 $key eq 'site' ||
988 $key eq 'remote_dir' ||
989 # Don't show passwords when interactive
990 ($interactive && $key eq 'remote_password') ||
991 ($interactive && $key eq 'remote_gpass');
992 # Report the value in the actual variable
993 $val = eval "\$$key";
994 $str = "$key=\"$val\" ";
995 &msg( $str );
996 $out += length( $str );
997 # Output newlines when a line is full
998 if( $out > $nle ){
999 $out = 0;
1000 &msg( "\n\t" );
1003 &msg( "\n" );
1006 # Mirror the package, return exit_status
1007 sub do_mirror
1009 local( $get_one_package ) = 0;
1011 $package = $value{ 'package' };
1013 if( $package eq 'defaults' ){
1014 # This isn't a real site - just a way to change the defaults
1015 %default = %value;
1017 return $exit_ok;
1020 # Only do this package if given by a -Ppack argument
1021 if( $limit_packages && ! $do_packages{ $package } ){
1022 return;
1025 if( $skip_till ){
1026 # Found a package so process all packages from now on
1027 $skip_till = $limit_packages = 0;
1030 local( $exit_status ) = $exit_fail_noconnect; # Presume the worse.
1031 $timeouts = 0;
1033 # set things from the command line arguments
1034 &command_line_override();
1036 if( ! &checkout_regexps() ){
1037 &msg( "skipping package\n\n" );
1038 return $exit_status;
1041 # set each variable $key = $value{ $key }
1042 &set_variables();
1044 # don't trash locally glossed over things with stuff from the remote
1045 if( $local_ignore ){
1046 if( $exclude_patt ){
1047 $exclude_patt .= '|' . $local_ignore;
1049 else {
1050 $exclude_patt = $local_ignore;
1054 if( $debug > 3 ){
1055 &pr_variables( "\n" );
1057 elsif( $package && ! $pretty_print ){
1058 if( $get_patt ){
1059 &msg( "package=$package $site:$remote_dir -> $local_dir\n");
1061 else {
1062 &msg( "package=$package $local_dir -> $site:$remote_dir\n" );
1064 &msg( "algorithm=$algorithm\n") if $algorithm != 0;
1067 # Don't bother if trying to mirror here!
1068 if( !$interactive && !$force && ((gethostbyname( $site ))[0] eq $hostname) ){
1069 &msg( "Skipping $site as it is this local site!\n\n" );
1070 return $exit_ok;
1073 chdir $home;
1075 $max_age = 0;
1076 if( $value{ 'max_days' } ne '0' ){
1077 $max_age = time - ($value{ 'max_days' } * 24 * 60 * 60);
1078 &msg( "max_age = $max_age\n" ) if $debug > 1;
1081 # pull in external code, if required
1082 if( $external_mapping ){
1083 &msg( "Loading external mapping from $external_mapping.\n" ) if $debug > 0 ;
1084 do $external_mapping || die "Cannot load from $external_mapping";
1087 if( $debug ){
1088 # Keep the ftp debugging lower than the rest.
1089 &ftp'debug( $debug - 1);
1091 else {
1092 &ftp'debug( $verbose );
1095 if( $recurse_hard ){
1096 $recursive = 1;
1098 if( $algorithm == 1 ){
1099 $recursive = 0;
1100 $make_bad_symlinks = 1;
1101 $rem_start_len = length( $remote_dir );
1104 if( ! $interactive ){
1105 $ftp'showfd = 'STDOUT';
1107 &ftp'set_timeout( $timeout );
1108 &ftp'set_signals( "main'msg" );
1110 # set passive ftp mode
1111 if( $passive_ftp ){
1112 $ftp'use_pasv = 1;
1115 # Are we using the SOCKS version of perl?
1116 if( $using_socks ){
1117 $chat'using_socks = 1;
1120 # Useful string in prints
1121 $XFER = $get_file ? "get" : "put";
1123 # create the list of items to copy
1124 @transfer_list = ();
1125 if( $interactive ){
1126 if( $algorithm == 1 ){
1127 warn "Cannot use algorithm 1 with interactive, using 0\n";
1128 $algorithm = 0;
1130 # copy the remainder of items from argv to the transfer list
1131 while( @ARGV ){
1132 # copy the local directory
1133 if( @ARGV ){
1134 push( @transfer_list, shift( @ARGV ) );
1137 # copy the remote directory
1138 if( @ARGV ){
1139 push( @transfer_list, shift( @ARGV ) );
1141 else {
1142 die "remote directory must be specified\n";
1145 # copy the pattern, if available
1146 if( @ARGV ){
1147 push( @transfer_list, shift( @ARGV ) );
1148 } else {
1149 push( @transfer_list, $default{ 'get_patt' } );
1153 if( $debug > 1 ){
1154 local( @t );
1155 @t = @transfer_list;
1157 while( @t ){
1158 printf( "local_dir=%s remote_dir=%s patt=%s\n",
1159 shift( @t ), shift( @t ), shift( @t ) );
1163 else {
1164 push( @transfer_list, $local_dir );
1165 push( @transfer_list, $remote_dir );
1166 push( @transfer_list, $get_patt );
1167 if( $algorithm != 1 ){
1168 $get_one_package = 1;
1173 if( $update_local && $get_patt ){
1174 if( $get_patt ne $default{ 'get_patt' } ){
1175 &msg( "Cannot mix get_patt and update_local. get_patt ignored\n" );
1177 $get_patt = '';
1181 if( !$site || (!$interactive && (!$local_dir || !$remote_dir)) ){
1182 &msg( "Insufficient details for package to be fetched\n" );
1183 &msg( "Must give at least: site, remote_user, remote_dir and local_dir\n\n" );
1184 return $exit_status;
1187 if( $pretty_print ){
1188 # Don't actually mirror just print a pretty list
1189 # of what would be mirrored. This is for mailing to
1190 # people
1191 if( $skip ){
1192 return $exit_ok;
1194 &msg( "$package \"$comment\"\n" );
1195 &msg( " $site:$remote_dir --> $local_dir\n\n" );
1196 return $exit_ok;
1199 if( $skip ){
1200 &msg( "Skipping $site:$package because $skip\n\n" );
1201 return $exit_ok;
1204 $split_max = &to_bytes( $split_max );
1205 $split_chunk = &to_bytes( $split_chunk );
1207 if( $split_max && $split_max <= $split_chunk ){
1208 &msg( "split_max <= split_chunk - skipping package\n" );
1209 &msg( " $split_max <= $split_chunk\n\n" );
1210 return $exit_status;
1213 if( $split_chunk && ($split_chunk & 511) ){
1214 &msg( "split_chunk bad size - skipping package\n" );
1215 &msg( " $split_chunk should be a multiple of 512 bytes\n\n" );
1216 return $exit_status;
1219 if( $local_dir_check && ! -d $local_dir ){
1220 &msg( "local_dir $local_dir does not exist - skipping package\n" );
1221 return $exit_status;
1224 if( $get_one_package && $algorithm != 1 ){
1225 # If only getting one package may as well parse the
1226 # local directory listings before connecting to the
1227 # remote site. (With the status_file stuff this info
1228 # can then be reused if something goes wrong.)
1229 if( $use_files ){
1230 &create_assocs();
1233 if( !&get_local_directory_details() ){
1234 &msg( "Cannot get local directory details ($local_dir)\n" );
1235 &disconnect();
1236 &msg( "\n" );
1237 return $exit_status;
1241 local( $con ) = &connect();
1242 if( $con <= 0 ){
1243 &msg( "Cannot connect, skipping package\n" );
1244 &disconnect();
1245 &msg( "\n" );
1246 return $exit_status;
1249 if( $con == 1 ){
1250 &msg( "login as $remote_user\n" ) if $debug > 1;
1251 $curr_remote_user = $remote_user;
1252 if( ! &ftp'login( $remote_user, $remote_password, $remote_account ) ){
1253 &msg( "Cannot login, skipping package\n" );
1254 &disconnect();
1255 &msg( "\n" );
1256 return $exit_status;
1258 $can_restart = (&ftp'restart(0) == 1);
1259 if( $debug > 1 ){
1260 &msg( "Can " . ($can_restart ? '' : "not ") . "do restarts\n" );
1264 else {
1265 # Already connected to this site - so no need to login again
1266 &msg( "Already connected to site $site\n" ) if $debug;
1269 if( ! &ftp'type( $text_mode ? 'A' : 'I' ) ){
1270 &msg( "Cannot set type\n" );
1273 $exit_status = $exit_fail; # ok this is now the worse case
1275 # Mirror thinks in terms of Unix pathnames.
1276 # Ask ftp.pl to map any remote name it is about to use by
1277 # setting the namemap functions.
1278 if( $remote_fs =~ /vms/i ){
1279 $vms = 1;
1280 &ftp'set_namemap( "main'unix2vms", "main'vms2unix" );
1282 else {
1283 $vms = 0;
1284 # No mapping necessary
1285 &ftp'set_namemap( '' );
1288 if( ! $get_file || $remote_idle ){
1289 local( @rhelp ) = &ftp'site_commands();
1290 $remote_has_chmod = grep( $_ eq 'CHMOD', @rhelp);
1291 $remote_has_rename = grep( $_ eq 'RNFR', @rhelp) && grep( $_ eq 'RNTO', @rhelp);
1292 $remote_has_idle = grep( $_ eq 'IDLE', @rhelp);
1293 if( $debug > 2 ){
1294 &msg( "remote site " . ($remote_has_chmod ? "has" : "hasn't") . " got chmod\n" );
1295 &msg( "remote site " . ($remote_has_idle ? "has" : "hasn't") . " got idle\n" );
1299 if( $remote_has_idle && $remote_idle ){
1300 if( ! &ftp'quote( "site idle $remote_idle" ) ){
1301 &msg( "Cannot set remote idle\n" );
1303 elsif( $debug > 2 ){
1304 &msg( "remote idle has been set to $remote_idle\n" );
1308 if( $remote_group ){
1309 if( ! &ftp'quote( "site group $remote_group" ) ){
1310 &msg( "Cannot set remote group\n" );
1312 elsif( $debug > 2 ){
1313 &msg( "remote group has been set to $remote_group\n" );
1317 if( $remote_gpass ){
1318 if( ! &ftp'quote( "site gpass $remote_gpass" ) ){
1319 &msg( "Cannot set remote gpass\n" );
1321 elsif( $debug > 2 ){
1322 &msg( "remote gpass has been set\n" );
1326 @log = ();
1327 $cannot = 0;
1329 local( @sub_dirs );
1330 while( @transfer_list ){
1331 # get files
1332 $local_dir = shift( @transfer_list );
1333 $remote_dir = shift( @transfer_list );
1334 $get_patt = shift( @transfer_list );
1336 # Clear all details
1337 undef( @xfer_dest );
1338 undef( @xfer_src );
1339 undef( @xfer_attribs );
1340 undef( @things_to_make );
1341 undef( @sub_dirs );
1343 if( ! $get_one_package ){
1344 if( $use_files ){
1345 &create_assocs();
1348 if( !&get_local_directory_details() ){
1349 &msg( "Cannot get local directory details ($local_dir)\n" );
1350 &disconnect();
1351 &msg( "\n" );
1352 return $exit_status;
1356 # Create a get_patt from the contents of the local directory
1357 if( $update_local && $#get_top >= 0 ){
1358 $get_patt = '^' . join( '|^', @get_top );
1359 $get_patt =~ s/$squished//g;
1360 &msg( "get_patt = $get_patt\n" ) if $debug;
1363 if( !&get_remote_directory_details() ){
1364 &msg( "Cannot get remote directory details ($remote_dir)\n" );
1365 if( $algorithm == 1 ){
1366 # Skip this directory.
1367 $cannot++;
1368 if( $cannot < $max_failed_dirs ){
1369 next;
1371 # Too many failed directories. Fall thru'
1372 # into disconnect.
1374 &disconnect();
1375 &msg( "\n" );
1376 return $exit_status;
1379 if( $get_file ){
1380 &compare_dirs(
1381 *remote_sorted,
1382 *remote_map, *remote_time,
1383 *remote_size, *remote_type,
1384 *local_sorted,
1385 *local_map, *local_time,
1386 *local_size, *local_type,
1387 *local_keep, *local_keep_totals );
1388 } else {
1389 &compare_dirs(
1390 *local_sorted,
1391 *local_map, *local_time,
1392 *local_size, *local_type,
1393 *remote_sorted,
1394 *remote_map, *remote_time,
1395 *remote_size, *remote_type,
1396 *remote_keep, *remote_keep_totals );
1399 if( $timestamp ){
1400 &set_timestamps();
1401 if( $algorithm == 1 ){
1402 foreach $sd ( @sub_dirs ){
1403 push( @transfer_list, "$local_dir/$sd" );
1404 push( @transfer_list, "$remote_dir/$sd" );
1405 push( @transfer_list, $get_patt );
1409 next;
1412 &make_dirs();
1413 &do_all_transfers();
1415 $exit_status = $exit_ok; # Everything went ok.
1417 if( $get_file ){
1418 # I must have finished with the remote information
1419 # so clear it out.
1420 &clear_remote();
1422 else {
1423 # clear out local info.
1424 &clear_local();
1427 if( $save_deletes ){
1428 # If $save_dir is null, make $save_dir to be
1429 # subdirectory 'Old' under
1430 # current path
1431 if( ( ! defined( $save_dir ) ) || ( $save_dir eq '' ) ){
1432 $save_dir = "$cwd/Old";
1435 # If $save_dir is not absolute, take it as
1436 # subdirectory of current path
1437 if( $save_dir !~ m,^/, ){
1438 $save_dir = "$cwd/$save_dir";
1442 if( $do_deletes || $save_deletes ){
1443 if( $get_file ){
1444 &do_deletes(
1445 *local_sorted,
1446 *local_map,
1447 *local_type, *local_keep,
1448 *local_totals, *local_keep_totals );
1450 else {
1451 &do_deletes(
1452 *remote_sorted,
1453 *remote_map,
1454 *remote_type, *remote_keep,
1455 *remote_totals, *remote_keep_totals );
1459 &make_symlinks();
1460 undef( @things_to_make );
1462 if( $algorithm == 1 ){
1463 foreach $sd ( @sub_dirs ){
1464 push( @transfer_list, "$local_dir/$sd" );
1465 push( @transfer_list, "$remote_dir/$sd" );
1466 push( @transfer_list, $get_patt );
1470 # No more transfers if the connection has died.
1471 last if ! $connected;
1474 &clear_local();
1475 &clear_remote();
1477 if( $use_files ){
1478 # Close and zap.
1479 &delete_assocs();
1482 # Should I force a disconnect now?
1483 if( $connected && $disconnect ){
1484 &disconnect();
1487 if( $dont_do || $timestamp ){
1488 # Don't generate logs/email
1489 &msg( "\n" );
1490 return $exit_status;
1493 local( $now ) = &time_to_standard( time );
1494 if( $update_log ){
1495 if( ! open( LOGG, ">>$update_log" ) ){
1496 &msg( "Cannot append to $update_log because: $!\n\n" );
1497 # Serious but this shouldn't stop mirroring.
1498 # return $exit_fail;
1500 print LOGG "mirroring $package ($site:$remote_dir) completed successfully \@ $now\n";
1501 print LOGG @log;
1502 close( LOGG );
1505 if( $#log >= 0 && $mail_prog ne '' && $mail_to =~ /./ ){
1506 local( $arg );
1507 eval "\$arg = \"$mail_subject\"";
1508 if( ! open( MAIL, "|$mail_prog $arg $mail_to" ) ){
1509 &msg( "Cannot run: $com\n\n" );
1510 return $exit_fail;
1512 if( $get_patt ){
1513 print MAIL "Mirrored $package ($site:$remote_dir -> $local_dir) $comment \@ $now\n";
1515 else {
1516 print MAIL "Mirrored $package ($local_dir -> $site:$remote_dir) $comment \@ $now\n";
1518 print MAIL @log;
1519 close( MAIL );
1521 undef( @log );
1523 &msg( "\n" );
1524 return $exit_status;
1528 sub disconnect
1530 if( $connected ){
1531 &msg( "disconnecting from $connected\n" ) if $debug;
1532 if( ! $ftp'fatalerror ){
1533 &ftp'close();
1535 else {
1536 &ftp'service_closed();
1539 $connected = '';
1542 # Connect to the site
1543 # Return 0 on a fail,
1544 # 1 if a connection was successfully made,
1545 # 2 if already connected to the site
1546 sub connect
1548 local( $attempts ) = 1; # Retry ONCE! Be friendly.
1549 local( $res );
1551 if( $connected eq $site && $curr_remote_user eq $remote_user ){
1552 # Already connected to this site!
1553 return 2;
1556 # Clear out any session active session
1557 &disconnect();
1559 if( $proxy ){
1560 $ftp'proxy = $proxy;
1561 $ftp'proxy_gateway = $proxy_gateway;
1562 $ftp'proxy_ftp_port = $proxy_ftp_port;
1564 $res = &ftp'open( $site, $ftp_port, $retry_call, $attempts );
1565 if( $res == 1 ){
1566 # Connected
1567 $connected = $site;
1569 return $res;
1572 # This just prods the remote ftpd to prevent time-outs
1573 sub prod
1575 return unless $connected;
1577 if( $debug > 2 ){
1578 &msg( " prodding remote ftpd\n" );
1580 &ftp'pwd();
1583 # checkout and fixup any regexps.
1584 # return 0 on an error
1585 sub checkout_regexps
1587 local( $ret ) = 1;
1588 # Check out the regexps
1589 local( $t ) = 'x';
1590 foreach $r ( @regexp_values ){
1591 # regexps should never begin or end with a | or have
1592 # two in a row otherwise the pattern matches everything.
1593 # Use null to match everything if thats what you mean.
1594 $value{ $r } =~ s/\|+/|/g;
1595 $value{ $r } =~ s/^\|//;
1596 $value{ $r } =~ s/\|$//;
1597 local( $val ) = $value{ $r };
1598 next if ! $val;
1599 eval '$t =~ /$val/';
1600 if( $@ ){
1601 local( $err );
1602 chop( $err = $@ );
1603 &msg( "Problem with regexp $r ($err)\n" );
1604 $ret = 0;
1607 return $ret;
1610 sub clear_local
1612 if( ! $use_files ){
1613 undef( %local_map );
1615 undef( @local_sorted );
1616 undef( @local_time );
1617 undef( @local_size );
1618 undef( @local_type );
1619 undef( @local_mode );
1620 undef( @local_keep );
1621 undef( @local_totals );
1622 undef( @local_keep_totals );
1625 sub clear_remote
1627 if( ! $use_files ){
1628 undef( %remote_map );
1630 undef( @remote_sorted );
1631 undef( @remote_time );
1632 undef( @remote_size );
1633 undef( @remote_type );
1634 undef( @remote_mode );
1635 undef( @remote_keep );
1636 undef( @remote_totals );
1637 undef( @remote_keep_totals );
1640 sub get_local_directory_details
1642 local( @dirs, $dir );
1643 local( $last_prodded ) = time; # when I last prodded the remote ftpd
1645 $next_local_mapi = $map_init;
1647 &clear_local();
1649 # Make sure the first elem is 0.
1650 $local_time[ 0 ] = 0;
1651 $local_size[ 0 ] = 0;
1652 $local_type[ 0 ] = 0;
1653 $local_mode[ 0 ] = 0;
1655 @get_top = ();
1657 &msg( "Scanning local directory $local_dir\n" ) if $debug;
1659 if( ! -d $local_dir ){
1660 &msg( "$local_dir no such directory - creating it\n" );
1661 if( $dont_do || $timestamp ){
1662 return 1;
1664 if( &mkdirs( $local_dir ) ){
1665 &msg( $log, "Created local dir $local_dir\n" );
1666 $exit_xfer_status |= $exit_xfers;
1668 else {
1669 &msg( $log, "FAILED to create local dir $local_dir\n" );
1672 if( !chdir( $local_dir ) ){
1673 &msg( "Cannot change directory to $local_dir\n" );
1674 return 0;
1677 if( $local_dir =~ m,^/, ){
1678 $cwd = $local_dir;
1680 else {
1681 &cwd();
1684 # @dirs is the list of all directories to scan
1685 # As subdirs are found they are added to the end of the list
1686 # and as
1687 @dirs = ( "." );
1688 # Most of these variables should be locals in blocks below but
1689 # that seems to tickle a perl bug and causes a lot of memory to
1690 # be wasted.
1691 local( $dir_level ) = 0;
1692 local( $i ) = 0;
1693 local( $path, $time, $size, $type, $mode, $name, $isdir, $value, $follow );
1694 local( $dev,$ino,$fmode,$nlink,$uid,$gid,$rdev,$ssize,
1695 $atime,$mtime,$ctime,$blksize,$blocks );
1696 while( defined( $dir = shift( @dirs ) ) ){
1698 if( !opendir( DIR, $dir ) ){
1699 &msg( "Cannot open local directory $dir, skipping it\n" );
1700 next;
1703 while( defined( $name = readdir( DIR ) ) ){
1704 $isdir = 0;
1706 # Prod the remote system from time to time
1707 # To prevent time outs. Only look once every 50 files
1708 # to save on unnecessary systems calls.
1709 if( ($i % 50 == 0) && time > ($last_prodded + $prod_interval) ){
1710 $last_prodded = time;
1711 &prod();
1713 $i ++;
1715 $path = "$dir/$name";
1716 $path =~ s,(^|/)\./,,;
1717 next if $name eq '.' || $name eq '..' ||
1718 ($local_ignore && $path =~ /$local_ignore/);
1720 $follow = ($follow_local_symlinks ne '' && $path =~ /$follow_local_symlinks/);
1721 if( !$follow && -l $path ){
1722 $value = readlink( $path );
1723 ( $dev,$ino,$fmode,$nlink,$uid,$gid,$rdev,$ssize,
1724 $atime,$mtime,$ctime,$blksize,$blocks ) =
1725 lstat( _ );
1726 $size = $ssize;
1727 $time = $mtime;
1728 $type = "l $value";
1729 $mode = $fmode;
1731 elsif( ($isdir = ($follow ? (-d $path) : (-d _))) ||
1732 -f _ ){
1733 ( $dev,$ino,$fmode,$nlink,$uid,$gid,$rdev,$ssize,
1734 $atime,$mtime,$ctime,$blksize,$blocks ) =
1735 stat( _ );
1736 $size = $ssize;
1737 $time = $mtime;
1738 $mode = $fmode;
1739 if( $isdir ){
1740 push( @dirs, $path ) if $recursive;
1741 $type = 'd';
1743 else {
1744 $type = 'f';
1746 if( $dir_level == 0 && $update_local ){
1747 push( @get_top, $path );
1750 else {
1751 &msg( "unknown file type $path, skipping\n" );
1752 next;
1754 if( $debug > 2){
1755 printf "local: %s %s %s %s 0%o\n",
1756 $path, $size, $time, $type, $mode;
1758 if( $max_age && $time != 0 && $time < $max_age ){
1759 &msg( " too old: $path\n" ) if $debug > 1;
1760 next;
1763 local( $mapi ) = $next_local_mapi++;
1764 # push( @local_sorted, $path );
1765 $local_sorted[ $mapi - 1 ] = $path;
1766 $local_map{ $path } = $mapi;
1767 $local_time[ $mapi ] = $time;
1768 $local_size[ $mapi ] = $size;
1769 $local_type[ $mapi ] = $type;
1770 $local_mode[ $mapi ] = $mode;
1771 if( $type eq 'd' ){
1772 $local_totals[ 0 ]++;
1774 else {
1775 $local_totals[ 1 ]++;
1778 closedir( DIR );
1779 $dir_level++;
1781 if( ! $recursive ){
1782 last;
1785 return 1;
1788 # Return true if the remote directory listing was brought back safely.
1789 sub get_remote_directory_details
1791 local( $type_changed ) = 0;
1792 local( $udirtmp );
1793 local( $storename ) = "/dev/null";
1795 &msg( "Scanning remote directory $remote_dir\n" ) if $debug;
1797 if( $store_remote_listing ){
1798 eval "\$storename = \"$store_remote_listing\"";
1801 $next_remote_mapi = $map_init;
1802 &clear_remote();
1804 # Make sure the first elem is 0.
1805 $remote_time[ 0 ] = 0;
1806 $remote_size[ 0 ] = 0;
1807 $remote_type[ 0 ] = 0;
1808 $remote_mode[ 0 ] = 0;
1810 if( $remote_fs !~ /cms/ && ! &ftp'cwd( $remote_dir ) ){
1811 if( $get_file ){
1812 # no files to get
1813 return 0;
1816 &msg( "Failed to change to remote directory ($remote_dir) trying to create it\n" );
1817 &mkdirs( $remote_dir );
1819 if( ! &ftp'cwd( $remote_dir ) ){
1820 &msg( "Cannot change to remote directory ($remote_dir) because: $ftp'response\n" );
1821 return 0;
1825 local( $rls );
1827 $use_ls = 0;
1829 if( $local_ls_lR_file ){
1830 local( $dirtmp ) = $local_ls_lR_file;
1831 &msg( " Using local file $local_ls_lR_file for remote dir listing\n" ) if $debug;
1832 local( $unsquish );
1833 if( $dirtmp =~ /\.$sys_compress_suffix$/ ){
1834 $unsquish = $sys_compress_prog;
1836 elsif( $dirtmp =~ /\.($gzip_suffix|$old_gzip_suffix)$/ ){
1837 $unsquish = $gzip_prog;
1839 if( defined( $unsquish ) ){
1840 local( $f );
1841 $f = $dirtmp;
1842 $f =~ s/($shell_metachars)/\\$1/g;
1843 $dirtmp = "$unsquish -d < \"$f\" |";
1845 elsif( $ls_lR_file =~ /($times_suffix|$patch_gzip_suffix)$/ ){
1846 return 0 if &patch_ls_lR_file()==0;
1848 if( ! open( DIRTMP, $dirtmp ) ){
1849 &msg( "Cannot open $dirtmp\n" );
1850 return 0;
1852 $rls = "main'DIRTMP";
1853 # Now we don't want to overwrite our input... better test?
1854 if( $local_ls_lR_file eq $storename ){
1855 $storename = "/dev/null";
1858 elsif( $ls_lR_file ){
1859 local( $dirtmp );
1860 $dirtmp = "$big_temp/.dir$$";
1861 if( $ls_lR_file =~ /\.($sys_compress_suffix|$gzip_suffix|$old_gzip_suffix)$/ ){
1862 $dirtmp .= ".$1";
1865 &msg( " Getting directory listing from remote file $ls_lR_file\n" ) if $debug;
1866 if( ! &ftp'get( $ls_lR_file, $dirtmp, 0 ) ){
1867 &msg( "Cannot get dir listing file\n" );
1868 return 0;
1870 local( $unsquish );
1871 if( $dirtmp =~ /\.$sys_compress_suffix$/ ){
1872 $unsquish = $sys_compress_prog;
1874 elsif( $dirtmp =~ /\.($gzip_suffix|$old_gzip_suffix)$/ ){
1875 $unsquish = $gzip_prog;
1877 if( defined( $unsquish ) ){
1878 local( $f, $uf );
1879 $uf = $udirtmp = $dirtmp;
1880 $dirtmp =~ s/($shell_metachars)/\\$1/g;
1881 $f = $dirtmp;
1882 $dirtmp =~ s/\.($sys_compress_suffix|$gzip_suffix|$old_gzip_suffix)$//;
1883 $udirtmp =~ s/\.($sys_compress_suffix|$gzip_suffix|$ol_gzip_suffix)$//;
1884 if( &sys( "$unsquish -d < \"$f\" > \"$dirtmp\"" ) != 0 ){
1885 &msg( "Cannot uncompress directory listing\n" );
1886 return 0;
1888 unlink( $uf );
1890 else {
1891 $udirtmp = $dirtmp;
1894 open( DIRTMP, $dirtmp ) || die "Cannot open $dirtmp";
1895 $rls = "main'DIRTMP";
1897 else {
1898 $use_ls = 1;
1899 if( ! &ftp'type( 'A' ) ){
1900 &msg( "Cannot set type to ascii for dir listing, ignored\n" );
1901 $type_changed = 0;
1903 else {
1904 $type_changed = 1;
1908 $lsparse'fstype = $remote_fs;
1909 $lsparse'name = "$site:$package";
1911 if( $use_ls ){
1912 local( $flags ) = $flags_nonrecursive;
1913 if( $recursive && ! $recurse_hard ){
1914 $flags = $flags_recursive;
1916 $lsparse'report_subdirs = (! $recurse_hard && $algorithm == 0);
1917 if( !&ftp'dir_open( $flags ) ){
1918 &msg( "Cannot get remote directory listing because: $ftp'response\n" );
1919 return 0;
1922 $rls = "ftp'NS";
1925 $rcwd = '';
1926 if( $vms ){
1927 # Strip this off all pathnames to make them
1928 # relative to the remote_dir
1929 $rcwd = $remote_dir;
1931 $dateconv'use_timelocal = $use_timelocal;
1932 if( !&lsparse'reset( $rcwd ) ){
1933 &msg( "$remote_fs: unknown fstype\n" );
1934 return 0;
1936 if( $vms ){
1937 # Need to get in terms of the full pathname
1938 # so add it back in - see unix2vms at end of mirror
1939 $vms_dir = $remote_dir;
1942 if( $storename ne "/dev/null" ){
1943 open( STORE, ">$storename" ) || die( "Cannot write to $storename\n" );
1946 local( $parse_state ) = &parse_remote_details();
1948 close( STORE );
1951 if( $local_ls_lR_file ){
1952 close( DIRTMP );
1954 elsif( $ls_lR_file ){
1955 close( DIRTMP );
1956 unlink( $udirtmp );
1958 else {
1959 # Could optimise this out - but it makes sure that
1960 # the other end gets a command straight after a possibly
1961 # long dir listing.
1962 if( ! &ftp'type( $text_mode ? 'A' : 'I' ) ){
1963 local( $msg ) = "Cannot reset type after dir listing, ";
1964 if( $type_changed ){
1965 # I changed it before - so I must be able to
1966 # change back unless something is wrong
1967 $msg .= "aborting\n";
1968 &msg( $msg );
1969 return 0;
1971 else {
1972 $msg .= "ignoring\n";
1973 &msg( $msg );
1978 # If the other end dropped part way thru make sure the
1979 # higher routines know!
1980 return $parse_state;
1983 # Get remote ls-lR times or mirror gzipped patch files.
1984 sub patch_ls_lR_file
1986 if( ! $patch_prog ){
1987 &msg( "No patch program on PATH\n" );
1988 return 0;
1990 local( $f, $fr, $flb, $flt, $flp, $flz, $frb, $frt );
1991 local( $to , $tn );
1992 $frb = $frt = $ls_lR_file;
1993 $flb = $dirtmp;
1994 &msg( "Patching $flb using $frb\n" ) if $debug;
1995 local( $tlb ) = -f $flb?(stat($flb))[9]:0;
1996 $dateconv'use_timelocal = $use_timelocal;
1997 $flp = "$flb$patch_suffix";
1998 $flz = "$flb$patch_gzip_suffix";
1999 # Get times and patch.
2000 if( $frt =~ /$times_suffix$/ ){
2001 # Use remote times file.
2002 $frb =~ s/$times_suffix$//;
2003 $flt = "$flb$times_suffix";
2004 &ftp'get( $frt, $flt, 0 ) ?
2005 &msg( "Got $frt\n" ):
2006 return 0;
2007 open( FT, $flt );
2008 for( $to, $tn ){
2009 $f = gmtime( <FT> );
2010 $_ = &lstime_to_time( $f );
2012 close( FT );
2013 $f = "$frb$patch_gzip_suffix";
2014 if( $tlb == $to && &ftp'get( $f, $flz, 0 ) &&
2015 ! &sys("$gzip_prog -df <$flz >$flp") ){
2016 &msg( $log, "Got $f\n" );
2017 unlink $flz if ! $debug;
2020 else {
2021 # Get time of remote patch file.
2022 $lsparse'fstype = $remote_fs;
2023 $lsparse'name = "$site:$package";
2024 &lsparse'reset( $remote_dir );
2025 if( ! &ftp'dir_open( "$flags_nonrecursive $frb" ) ){
2026 &msg( "List remote ls-lR patch: $ftp'response\n" );
2027 &ftp'dir_close();
2028 return 0;
2030 local( $p, $s, $trz, $t, $m ) = &lsparse'line( ftp'NS );
2031 &msg( "Remote ls-lR patch:\n$p $s $trz $t $m\n" ) if $debug;
2032 if( ! &ftp'dir_close() ){
2033 &msg( "List remote ls-lR patch: $ftp'response\n" );
2034 return 0;
2036 # If remote time does not match local get remote patch file.
2037 local( $tlz ) = -f $flz?(stat($flz))[9]:0;
2038 if( $trz == $tlz ){
2039 &msg( "No new $frb\n" );
2040 &msg( "age $trz same as $flz\n" ) if $debug;
2042 else {
2043 &ftp'get( $frb, $flz, 0 )?
2044 &msg( $log, "Got $frb $s\n" ):
2045 return 0;
2046 &utime( $trz, $trz, $flz );
2048 # unzip patch and read times.
2049 $frb =~ s/$patch_gzip_suffix$//;
2050 &sys( "$gzip_prog -df <$flz >$flp" ) ?
2051 return 0:
2052 open( FT, $flp );
2053 for( $to, $tn ){
2054 ( $fr, $f ) = split( /\t/, <FT> );
2055 $_ = &lstime_to_time( $f );
2057 close( FT );
2059 # Patch or leave or get new local ls-lR file?
2060 $f = "$patch_prog ";
2061 $f .= $use_timelocal?$patch_local:$patch_UTC;
2062 if( $tlb == $to && ! &sys( "$f $flb $flp" ) ){
2063 &msg( "$flb patched\n" );
2065 elsif( $tlb == $tn ){
2066 &msg( "$flb up to date\n" );
2068 else {
2069 $fr = "$frb.$gzip_suffix";
2070 $f = "$flb.$gzip_suffix";
2071 if( &ftp'get( $fr, $f, 0 ) &&
2072 ! &sys( "$gzip_prog -df $f" ) ){
2073 &utime( $tn, $tn, $flb );
2074 &msg( $log, "Got $fr for $flb\n" );
2076 else {
2077 &msg( "Did not get $fr\nand $ftp'response\n" );
2078 return 0;
2081 unlink $flp, $flt if ! $debug;
2082 if( ! $do_deletes && $exclude_patt =~ /^\.($|\|)/ ){
2083 &msg( "$flb check complete\n" );
2084 next;
2086 return 1;
2089 sub parse_timeout
2091 $parse_timed_out = 1;
2092 die "timeout: parse_remote_details";
2095 sub parse_remote_details
2097 local( $ret );
2098 local( $old_sig );
2100 $parse_timed_out = 0;
2102 if( ! $use_ls ){
2103 # No need to bother with the timers
2104 return &parse_remote_details_real();
2107 # This may timeout
2108 $old_sig = $SIG{ 'ALRM' };
2109 $SIG{ 'ALRM' } = "main\'parse_timeout";
2111 $ret = eval '&parse_remote_details_real()';
2113 &alarm( 0 );
2115 $SIG{ 'ALRM' } = $old_sig;
2117 if( $@ =~ /^timeout/ ){
2118 &msg( "timed out parsing directory details\n" );
2119 return 0;
2121 return $ret;
2125 sub parse_remote_details_real
2127 local( $path, $size, $time, $type, $mode, $rdir, $rcwd );
2128 local( @dir_list );
2129 local( $i ) = 0;
2130 local( $old_path );
2132 if( $use_ls ){
2133 &alarm( $parse_time );
2136 # Need to loop in case $recurse_hard
2137 while( 1 ){
2138 while( !eof( $rls ) ){
2139 ( $path, $size, $time, $type, $mode ) =
2140 &lsparse'line( $rls );
2141 last if $path eq '';
2142 if( $ls_fix_mappings ){
2143 local( $old_path ) = $path;
2144 $_ = $path;
2145 eval $ls_fix_mappings;
2146 if( $_ ne $old_path ){
2147 $path = $_;
2150 next if $name eq '.' || $name eq '..';
2151 if( $debug > 2 ){
2152 printf "remote: %s %s %s %s 0%o\n",
2153 $path, $size, $time, $type, $mode;
2155 if( $use_ls ){
2156 # I just got something so shouldn't timeout
2157 &alarm( $parse_time );
2159 else {
2160 # Prod the remote system from time to time
2161 # To prevent time outs. Only look once every
2162 # 50 files
2163 # to save on unnecessary systems calls.
2164 if( ($i % 50 == 0) &&
2165 time > ($last_prodded + $prod_interval) ){
2166 $last_prodded = time;
2167 &prod();
2169 $i ++;
2172 if( $algorithm == 1 ){
2173 $path0 = substr( $remote_dir, $rem_start_len );
2174 if( $path0 ne '' ){
2175 $path0 .= "/";
2177 $path0 .= $path;
2178 $path0 =~ s,^/,,;
2179 # &msg( "debug: $path0, $remote_dir, $rem_start_len\n" );
2181 else {
2182 $path0 = $path;
2185 if( $exclude_patt && $path0 =~ /$exclude_patt/ ){
2186 &msg( " exclude: $path0\n" ) if $debug > 1;
2187 next;
2190 if( $type eq 'd' ){
2191 push( @dir_list, $path0 );
2194 if( $max_age && $time != 0 && $time < $max_age ){
2195 &msg( " too old: $path0\n" ) if $debug > 1;
2196 next;
2199 # If vms and only keeping the latest version
2200 if( $vms && !$vms_keep_versions ){
2201 # If we already have a file, pick the newer
2202 # TODO: pick the greatest version number
2203 local( $ri ) = $remote_map{ $path };
2204 if( $ri &&
2205 $time > $remote_time[ $ri ] ){
2206 $remote_time[ $ri ] = $time;
2207 $remote_size[ $ri ] = $size;
2208 $remote_type[ $ri ] = $type;
2209 $remote_mode[ $ri ] = $mode;
2210 next;
2214 local( $mapi ) = $next_remote_mapi++;
2215 # push( @remote_sorted, $path );
2216 $remote_sorted[ $mapi - 1 ] = $path;
2217 $remote_map{ $path } = $mapi;
2218 $remote_time[ $mapi ] = $time;
2219 $remote_size[ $mapi ] = $size;
2220 $remote_type[ $mapi ] = $type;
2221 $remote_mode[ $mapi ] = $mode;
2222 if( $type eq 'd' ){
2223 $remote_totals[ 0 ]++;
2225 else {
2226 $remote_totals[ 1 ]++;
2230 if( $use_ls ){
2231 if( ! &ftp'dir_close() ){
2232 &msg( "Failure at end of remote directory" .
2233 " ($rdir) because: $ftp'response\n" );
2234 return 0;
2238 if( $recurse_hard ){
2239 local( $done ) = 0;
2240 while( 1 ){
2241 if( $#dir_list < 0 ){
2242 # Make sure we end in the right directory.
2243 if( ! &ftp'cwd( $remote_dir ) ){
2244 &msg( "Cannot change to remote directory" .
2245 " ($rdir) because: $ftp'response\n" );
2246 return 0;
2248 $done = 1;
2249 last;
2251 $rcwd = shift( @dir_list );
2252 $rdir = "$remote_dir/$rcwd";
2253 if( $debug > 2 ){
2254 print "scanning: $remote_dir / $rcwd\n";
2256 if( ! &ftp'cwd( $rdir ) ){
2257 &msg( "Cannot change to remote directory" .
2258 " ($rdir) because: $ftp'response\n" );
2259 next;
2261 last;
2263 if( $done ){
2264 last;
2266 if( !&ftp'dir_open( $flags_nonrecursive ) ){
2267 &msg( "Cannot get remote directory" .
2268 " listing because: $ftp'response\n" );
2269 return 0;
2271 &lsparse'reset( $rcwd );
2273 # round the loop again.
2274 next;
2277 # All done - snap the loop
2278 last;
2280 return 1;
2283 sub compare_dirs
2285 # This declaration must be "local()" because it modifies global data.
2286 local( *src_paths,
2287 *src_map, *src_time,
2288 *src_size, *src_type,
2289 *dest_paths,
2290 *dest_map, *dest_time,
2291 *dest_size, *dest_type,
2292 *dest_keep, *dest_keep_totals ) = @_;
2293 local( $src_path, $dest_path, $i );
2294 local( $last_prodded ) = time; # when I last prodded the remote ftpd
2296 # Most of these variables should be locals in blocks below but
2297 # that seems to tickle a perl bug and causes a lot of memory to
2298 # be wasted.
2299 local( $desti, $srci, $compress, $srciZ, $srcigz, $split, $dest_path_real );
2300 local( $old_dest_path, $existing_path, $tmp, $restart );
2301 local( $sp, $dp ) = ($#src_paths + 1, $#dest_paths + 1);
2303 &msg( "compare directories (src $sp, dest $dp)\n" ) if $debug;
2304 $total_src_size = 0;
2306 for( $i = 0; $i <= $#src_paths; $i++ ){
2307 $dest_path = $src_path = $src_paths[ $i ];
2309 $desti = $dest_map{ $dest_path };
2310 $srci = $i + 1;
2312 # Prod the remote system from time to time
2313 # To prevent time outs. Only look once every 50 files
2314 # to save on unnecessary systems calls.
2315 if( ($i % 50 == 0) && time > ($last_prodded + $prod_interval) ){
2316 $last_prodded = time;
2317 &prod();
2320 if( $debug > 2 ){
2321 &msg( "Compare src $src_path ($srci): " .
2322 &t2str( $src_time[ $srci ] ) );
2323 &msg( " $src_size[ $srci ] $src_type[ $srci ]\n" );
2326 # I'm about to do a lot of matching on this
2327 study( $src_path );
2329 # Should I compress this file?
2330 # Don't compress this file if trying to do a compress->gzip
2331 # conversion.
2332 $compress = 0;
2333 if( &will_compress( $src_path, $srci ) ){
2334 if( $dest_path !~ /$squished/o ){
2335 $srciZ = $src_map{ "$src_path.$sys_compress_suffix" };
2336 $srcigz = $src_map{ "$src_path.$gzip_suffix" };
2337 if( $srciZ || $srcigz ){
2338 # There is a compressed version
2339 # too! Skip the uncompressed one
2340 &msg( " do not xfer, compressed version exists: $src_path\n" ) if $debug > 1;
2341 next;
2344 $compress = 1;
2345 $dest_path .= '.' . $compress_suffix;
2346 $desti = $dest_map{ $dest_path };
2349 # need to adjust the symlink pointer?
2350 elsif( $src_type[ $srci ] =~ /^l (.*)/ ){
2351 # Am I going to squish the file this points to?
2352 local( $real, $reali, $reali1 );
2353 local( $count ) = 0;
2354 while( $count++ <= 10 ){
2355 $real = &expand_symlink( $src_path, $1 );
2356 $reali = $src_map{ $real };
2357 # Look out for when the symlink loops on itself
2358 if( defined( $reali1 ) && $reali == $reali1 ){
2359 last;
2361 $reali1 = $reali;
2362 last if $src_type[ $reali ] !~ /^l (.*)$/;
2364 if( &will_compress( $real, $reali ) ){
2365 # real is going to be (at least) squished so
2366 # suffix the dest
2367 $dest_path .= '.' . $compress_suffix;
2368 $desti = $dest_map{ $dest_path };
2369 $src_type[ $srci ] .= '.' . $compress_suffix;
2370 &msg( " symlink pointer is now $dest_path\n" ) if $debug > 1;
2371 if( $src_map{ $dest_path } ){
2372 &msg( "do not xfer, $dest_path exists\n" ) if $debug > 1;
2373 next;
2376 if( &will_split( $real, $reali ) ){
2377 $src_type[ $srci ] .= '-split/README';
2378 &msg( " symlink pointer now to $real-split/README'\n" ) if $debug > 1;
2382 # If this is a file that I decided not to compress but the
2383 # remote file is compressed and I want a gziped local version
2384 # then force compression.
2385 # This ignores any compress_excl flags.
2386 if( ! $compress &&
2387 $compress_suffix eq $gzip_suffix &&
2388 $compress_conv_patt && $src_path =~ /$compress_conv_patt/ ){
2389 $_ = $dest_path;
2390 eval $compress_conv_expr;
2391 $dest_path = $_;
2392 # check if dest_path exists in the sources. If it
2393 # does, ignore this file. This is to avoid the
2394 # double mirroring problem if you are using gzip and
2395 # the source site has both foo.Z and foo.gz.
2396 if( $src_map{ $dest_path } ){
2397 &msg( "Skipping $src_path because remote site also has $dest_path\n" ) if $debug > 2;
2398 next;
2400 &msg( " $src_path -> $dest_path\n" ) if $debug > 2;
2401 $desti = $dest_map{ $dest_path };
2402 $compress = 1;
2405 # Am I converting the compression on the file this points to?
2406 if( $src_type[ $srci ] =~ /^l (.*)/ &&
2407 $compress_suffix eq $gzip_suffix ){
2408 local( $value ) = $1;
2409 local( $real ) = &expand_symlink( $src_path, $value );
2410 local( $reali ) = $src_map{ $real };
2411 if( $src_type[ $reali ] ne 'd' &&
2412 $src_type[ $reali ] ne /^l .*/ &&
2413 $compress_conv_patt && $real =~ /$compress_conv_patt/ ){
2414 $dest_path =~ s/$sys_compress_suffix$/$gzip_suffix/;
2415 $desti = $dest_map{ $dest_path };
2416 $value =~ s/$sys_compress_suffix$/$gzip_suffix/;
2417 &msg( " symlink pointer is now $dest_path (conv)\n")
2418 if $debug > 1;
2420 if( $name_mappings || $external_mapping ){
2421 local( $old ) = $value;
2422 $value = &map_name( $value );
2423 if( $value ne $old ){
2424 &msg( " Mapped symlink value is $value\n" ) if $debug > 2;
2428 $src_type[ $srci ] = "l ".$value;
2431 if( $name_mappings || $external_mapping ){
2432 local( $old_dest_path ) = $dest_path;
2433 $dest_path = &map_name( $dest_path );
2434 if( $dest_path ne $old_dest_path ){
2435 $desti = $dest_map{ $dest_path };
2436 &msg( " Mapped name is $dest_path\n" ) if $debug > 2;
2440 # Should this file be split?
2441 $split = 0;
2442 $dest_path_real = undef;
2443 if( &will_split( $src_path, $srci ) ){
2444 $split = 1;
2445 $dest_path_real = $dest_path;
2446 $dest_path .= "-split/part01";
2447 $desti = $dest_map{ $dest_path };
2450 if( $debug > 2 ){
2451 &msg( " dest $dest_path ($desti): " .
2452 &t2str( $dest_time[ $desti ] ) );
2453 &msg( " $dest_size[ $desti ] $dest_type[ $desti ]" );
2454 &msg( " (->$compress_suffix)" ) if $compress;
2455 &msg( " (split)" ) if $split;
2456 &msg( "\n" );
2459 if( $algorithm == 1 ){
2460 $src_path0 = substr( $remote_dir, $rem_start_len );
2461 if( $src_path0 ne '' ){
2462 $src_path0 .= "/";
2464 $src_path0 .= $src_path;
2465 $src_path0 =~ s,^/,,;
2466 #&msg( "debug: $src_path0, $remote_dir, $rem_start_len\n" );
2468 else {
2469 $src_path0 = $src_path;
2472 if( $get_patt && $src_path0 !~ /$get_patt/ ){
2473 &msg( " do not xfer: $src_path0\n" ) if $debug > 1;
2474 next;
2477 # Just create any needed directories (the timestamps
2478 # should be ignored)
2479 if( $src_type[ $srci ] eq 'd' ){
2480 if( $algorithm == 1 ){
2481 if( $exclude_patt && $src_path0 =~ /$exclude_patt/ ){
2482 &msg( " exclude: $src_path0\n" ) if $debug > 1;
2484 else {
2485 $rel_src_path = $src_path;
2486 $rel_src_path =~ s,.*/,,;
2487 push( @sub_dirs, $rel_src_path );
2488 &msg( " adding $rel_src_path\n" ) if $debug;
2491 if( $dest_type[ $desti ] ne 'd' ){
2492 push( @things_to_make, "d $dest_path" );
2493 &msg( " need to mkdir $dest_path\n" ) if $debug > 1;
2495 # keep the directory once made
2496 # (Also if local is really a symlink elsewhere
2497 # it will be kept.)
2498 &keep( $desti, $dest_path, *dest_keep, *dest_keep_totals, *dest_map, 0 );
2499 next;
2502 # Well that just leaves files and symlinks.
2503 # Do various checks on them.
2505 if( $desti && ! $dest_keep[ $desti ] ){
2506 &keep( $desti, $dest_path, *dest_keep, *dest_keep_totals, *dest_map, 1 );
2507 if( $split ){
2508 # Mark all the split parts as kept
2509 local( $dpp, $dps );
2510 ($dpp, $dps) = ($dest_path =~ m,^(.*/)(part[0-9]+)$,);
2511 while( 1 ){
2512 $dps++;
2513 if( !($di = $dest_map{ $dpp . $dps }) ){
2514 last;
2516 &keep( $di, $dpp . $dps, *dest_keep, *dest_keep_totals, *dest_map, 1 );
2518 # And the README
2519 $dps = 'README';
2520 $di = $dest_map{ $dpp . $dps };
2521 if( $di ){
2522 &keep( $di, $dpp . $dps, *dest_keep, *dest_keep_totals, *dest_map, 1 );
2524 # And the directory
2525 chop( $dpp );
2526 $dps = '';
2527 $di = $dest_map{ $dpp . $dps };
2528 if( $di ){
2529 &keep( $di, $dpp . $dps, *dest_keep, *dest_keep_totals, *dest_map, 0 );
2534 local( $update ) = 0;
2536 if( ! $get_missing ){
2537 next;
2540 if( ($max_size > 0) && ($src_size[ $srci ] > $max_size) ){
2541 &msg( " src is too big, no need to xfer it\n" ) if $debug > 2;
2542 next;
2545 if( $force || ! $dest_type[ $desti ] || $timestamp ){
2546 # Either I'm forcing xfers or the file doesn't exist
2547 # either way I should update
2548 $update = 1;
2550 else {
2551 # Maybe the src is newer?
2552 if( $get_newer &&
2553 &compare_times( $src_time[ $srci ], $dest_time[ $desti ] ) ){
2554 &msg( " src is newer, xfer it\n" ) if $debug > 2;
2555 $update = 1;
2557 # or maybe its size has changed?
2558 # don't bother if file was compressed or split as the
2559 # size will have changed anyway
2560 if( !$update &&
2561 !$compress && !$split &&
2562 $get_size_change &&
2563 ($src_type[ $srci ] eq 'f') &&
2564 ($src_size[ $srci ] != $dest_size[ $desti ]) ){
2565 $update = 1;
2566 if( $debug > 2 ){
2567 &msg( " src is different size, xfer it\n" );
2570 # Maybe it has changed type!
2571 if( !$update &&
2572 $src_type[ $srci ] ne $dest_type[ $desti ] ){
2573 $update = 1;
2574 if( $debug > 2 ){
2575 &msg( " src has different type, xfer it\n" );
2580 if( ! $update ){
2581 next;
2584 if( $src_type[ $srci ] =~ /^l (.*)/ ){
2585 # If the symlink hasn't changed then may as well
2586 # leave it alone
2587 if( $src_type[ $srci ] eq $dest_type[ $desti ] ){
2588 next;
2590 # DONT FORGET TO NAME MAP!!!!
2591 $existing_path = $1;
2593 if( $compress_suffix eq $gzip_suffix &&
2594 $compress_conv_patt && $existing_path =~ /$compress_conv_patt/ ){
2595 $_ = $existing_path;
2596 eval $compress_conv_expr;
2597 $existing_path = $_;
2600 push( @things_to_make, "l $dest_path -> $existing_path" );
2601 &msg( " need to symlink $dest_path -> $existing_path\n" ) if $debug > 2;
2602 next;
2605 # Now that the tests are complete use the real dest.
2606 if( defined( $dest_path_real ) ){
2607 $dest_path = $dest_path_real;
2608 $desti = $dest_map{ $dest_path };
2611 $total_src_size += $src_size[ $srci ];
2612 if( $dont_do ){
2613 &msg("Should ");
2615 &msg( "$XFER file $src_path as $dest_path ($src_size[ $srci ])".
2616 ($compress ? " (->$compress_suffix)" : "") .
2617 ($split ? " (split)" : "") . "\n" ) if $debug > 1;
2618 push( @xfer_dest, $dest_path );
2619 push( @xfer_src, $src_path );
2621 # If xfers can be restarted AND
2622 # a temporary file exists from a previous attempt at a
2623 # transfer AND
2624 # the timestamps of the exising temp file and the original
2625 # src file match then flag a restart.
2626 $tmp = &filename_to_tempname( '', $dest_path );
2627 $tmpi = $dest_map{ $tmp };
2628 $restart = '';
2629 #warn "get_file = $get_file, can_restart = $can_restart, dest_size = $dest_size[ $tmpi ], dest_time = $dest_time[ $tmpi ], src_time = $src_time[ $srci ]\n";
2630 if( $get_file &&
2631 $can_restart &&
2632 # Debian bug #24243, mirror-2.9 does not restart, adam@usa.net
2633 $dest_size[ $tmpi ] != 0 ){
2634 if ($dest_time[ $tmpi ] eq $src_time[ $srci ]) {
2635 # Then this is an xfer of the same file
2636 # so just restart where I left off
2637 $restart = 'r';
2638 } elsif ( $debug > 1 ){
2639 &msg ( "Timestamp useless on $tmp\n" );
2642 # x for xfer, c for compress, s for split
2643 push( @xfer_attribs,
2644 "x$restart" .
2645 ($compress ? "c" : "") .
2646 ($split ? "s" : "") );
2648 &msg( "to $XFER $total_src_size bytes\n" ) if $debug > 2;
2651 sub map_name
2653 local( $name ) = @_;
2655 if( $name_mappings ){
2656 local( $old_name ) = $name;
2657 $_ = $name;
2658 eval $name_mappings;
2659 if( $_ ne $old_name ){
2660 $name = $_;
2664 if( $external_mapping ){
2665 $old_name = $name;
2666 local( $tmp ) = &extmap'map( $name );
2667 if( $tmp ne $old_name ){
2668 $name = $tmp;
2671 return $name;
2675 sub set_timestamps
2677 local( $src_path );
2679 &msg( "setting timestamps\n" );
2680 if( ! $get_file ){
2681 &msg( "Cannot set remote timestamps\n" );
2682 return;
2685 local( $dest_path, $dest_loc_mapi, $src_rem_mapi, $rtime );
2687 foreach $src_path ( @xfer_src ){
2688 $dest_path = shift( @xfer_dest );
2689 $dest_loc_mapi = $local_map{ $dest_path };
2690 $src_rem_mapi = $remote_map{ $src_path };
2692 $rtime = $remote_time[ $src_rem_mapi ];
2693 if( $dest_loc_mapi && $local_time[ $dest_loc_mapi ] ne $rtime ){
2694 &set_timestamp( $dest_path, $rtime );
2699 sub set_timestamp
2701 local( $path, $time ) = @_;
2703 local( $pr_time ) = &t2str( $time );
2705 if( $dont_do ){
2706 &msg( "Should set time of $path to $pr_time\n" );
2707 return;
2710 if( $timestamp || $debug > 2 ){
2711 &msg( "Setting time of $path to $pr_time\n" );
2714 if( ! &utime( $time, $time, $path ) ){
2715 &msg( $log, "Cannot set file times for $path to $pr_time because: $!\n" );
2719 sub make_dirs
2721 local( $thing );
2723 foreach $thing ( @things_to_make ){
2724 if( $thing !~ /^d (.*)/ ){
2725 next;
2727 if( $dont_do ){
2728 &msg( "Should mkdir $1\n" );
2730 else {
2731 &mkdirs( $1 );
2736 sub make_symlinks
2738 local( $thing );
2740 thing:
2741 foreach $thing ( @things_to_make ){
2742 if( $thing !~ /^l (.*) -> (.*)/ ){
2743 next;
2745 local( $dest, $existing ) = ($1, $2);
2746 local( $dirpart ) = &dirpart( $dest );
2747 local( $ft ) = &expand_symlink( $dest, $existing );
2748 if( -e $ft ){
2749 &mkdirs( $dirpart ) if ! -d $dirpart;
2750 # symlink to existing file.
2751 # Debian bug #85353 "bad symlink stops listing with -n" <sizif@pier.botik.ru>
2752 &mksymlink( $dest, $existing );
2753 next;
2756 # The existing file doesn't actually exist!
2757 # Has it been compressed, gzipped, split? or worse
2758 # compressed/gzipped AND split. (OK so it could
2759 # be another problem, bad symlink on remote host, file
2760 # that hasn't been xfer'd yet... but this is as good as
2761 # it gets.)
2762 local( $p );
2763 foreach $p (
2764 "\%s.$sys_compress_suffix",
2765 "\%s.$gzip_suffix",
2766 "\%s/README",
2767 "\%s-split/README",
2768 "\%s-split.$sys_compress_suffix/README",
2769 "\%s-split.$gzip_suffix/README" ){
2770 local( $f ) = sprintf( $p, $existing );
2771 if( -e $f ){
2772 &msg( "using $p\n" ) if $debug > 2;
2773 &mksymlink( $dest, $f );
2774 next thing;
2777 if( $make_bad_symlinks ){
2778 &msg( "symlink to non-existant file: $dest -> $existing\n" );
2779 &mksymlink( $dest, $existing );
2781 elsif ( $get_symlink_files ){
2782 # Get file within $local_dir tree and make symlink, iml@debian.org, 2001/09/22.
2783 if( $ft =~ m|\.\./| ){
2784 &msg( "Not getting path $ft\nas not in remote_dir $remote_dir\n" );
2785 &msg( "and not symlinking $dest -> $existing\n" );
2786 next thing;
2788 local( $dl ) = &dirpart( $ft );
2789 &mkdirs( $dl ) if ! -d $dl;
2790 if( &ftp'get( $ft, $ft, 0 ) ){
2791 &msg( $log, "Got $ft\n" );
2792 &mksymlink( $dest, $existing );
2794 else {
2795 &msg( "Did not get $ft\nbecause $ftp'response\n" );
2796 &msg( "so not symlinking $dest -> $existing\n" );
2799 else {
2800 &msg( "Not symlinking $dest -> $existing\n" );
2801 &msg( "as no path $ft\n" );
2806 sub do_all_transfers
2808 local( $src_path );
2809 local( $dest_path, $attribs );
2810 local( $srci );
2812 if( $#xfer_src < 0 ){
2813 &msg( "No files to transfer\n" ) if $algorithm == 0;
2814 return;
2817 # The Macos ftpd cannot reliably rename files
2818 $no_rename = (! $remote_has_rename) || ($remote_fs eq 'macos' && ! $get_file);
2820 foreach $src_path ( @xfer_src ){
2821 if( $get_file ){
2822 $srci = $remote_map{ $src_path };
2824 else {
2825 $srci = $local_map{ $src_path };
2828 $dest_path = shift( @xfer_dest );
2829 $attribs = shift( @xfer_attribs );
2831 if( $dont_do ){
2832 # Skip trying to get the file.
2833 next;
2836 &msg( "Need to $XFER file $src_path as $dest_path ($attribs)\n" ) if $debug > 1;
2838 local( $newpath ) =
2839 &transfer_file( $src_path, $dest_path,
2840 $attribs, $remote_time[ $srci ] );
2841 if( $get_file && $newpath eq '' ){
2842 &msg( $log, "Failed to $XFER file $ftp'response\n" );
2843 if( $ftp'response =~ /timeout|timed out/i ){
2844 $timeouts++;
2846 if( $ftp'fatalerror || $timeouts > $max_timeouts ){
2847 &msg( $log, "Fatal error talking to site, skipping rest of transfers\n" );
2848 &disconnect();
2849 return;
2851 next;
2854 # File will now have been split up.
2855 if( $attribs =~ /s/ ){
2856 next;
2859 &set_attribs( $newpath, $src_path, 'f' );
2861 # we can only force time for local files
2862 if( $force_times && $get_file ){
2863 &set_timestamp( $newpath, $remote_time[ $srci ] );
2869 sub transfer_file
2871 local( $src_path, $dest_path, $attribs, $timestamp ) = @_;
2872 local( $dir, $file, $temp, $compress, $split, $restart, $mesg, $got_mesg );
2874 # Make sure the required directory exists
2875 $dir = "";
2876 if( $dest_path =~ /^(.+\/)([^\/]+)$/ ){
2877 ($dir, $file) = ($1, $2);
2878 if( $dest_type[ $dir ] ne 'd' && &mkdirs( $dir ) ){
2879 &msg( $log, "Created dir $dir\n" );
2882 else {
2883 $file = $dest_path;
2886 $temp = &filename_to_tempname( $dir, $file );
2888 # Interpret the attrib characters
2889 if( $attribs !~ /x/ ){
2890 # Not an xfer!
2891 return '';
2893 if( $attribs =~ /c/ ){
2894 $compress = 1;
2895 $mesg = " and compress";
2897 if( $attribs =~ /s/ ){
2898 $split = 1;
2899 $mesg = " and split";
2901 if( $attribs =~ /r/ ){
2902 $restart = 1;
2905 if( $vms ){
2906 &ftp'type( ($src_path =~ /$vms_xfer_text/i) ? 'A' : 'I' );
2909 if( $remote_fs eq 'macos' && ! $get_file ){
2910 &ftp'type( 'A' );
2913 if( ! $get_file ){
2914 # put the file remotely
2915 local( $src_file ) = $src_path;
2916 local( $comptemp ) = '';
2918 if( $compress ){
2919 # No easy way to tell wether this was compressed or not
2920 # for now just presume that it is.
2921 local( $f ) = $src_file;
2922 $f =~ s/($shell_metachars)/\\$1/g;
2923 $comptemp = "$big_temp/.out$$";
2924 &sys( "$compress_prog < \"$f\" > \"$comptemp\"" );
2925 $src_file = $comptemp;
2928 if( $no_rename ){
2929 $temp = $dest_path;
2932 if( ! &ftp'put( $src_file, $temp, $restart ) ){
2933 &msg( $log, "Failed to put $src_file: $ftp'response\n" );
2934 unlink( $comptemp ) if $comptemp;
2935 return '';
2938 unlink( $comptemp ) if $comptemp;
2939 if( !$no_rename && ! &ftp'rename( $temp, $dest_path ) ){
2940 &msg( $log, "Failed to remote rename $temp to $dest_path: $ftp'response\n" );
2941 return '';
2944 local($filesize) = &filesize( $src_file );
2945 &msg( $log, "Put $src_file $filesize bytes\n" );
2947 &log_upload( $src_file, $dest_path, "", $filesize );
2949 # Some transfers done
2950 $exit_xfer_status |= $exit_xfers;
2952 if( $delete_source ){
2953 unlink( $src_file );
2956 return $dest_path;
2959 # Maybe TODO: Paul Szabo suggest that if recurse_hard is set then
2960 # mirror should chdir to the directory the file is in before getting
2961 # it.
2963 # Get a file
2964 &ftp'dostrip( $strip_cr );
2965 $start_time = time;
2966 if( ! &ftp'get( $src_path, $temp, $restart ) ){
2967 if( !$failed_gets_excl || $ftp'response !~ /$failed_gets_excl/ ){
2968 &msg( $log, "Failed to get $src_path: $ftp'response\n" );
2971 # Time stamp the temp file to allow for a restart
2972 if( -f $temp ){
2973 &utime( $timestamp, $timestamp, $temp );
2974 # Make sure this file is kept
2975 local( $ti ) = $local_map{ $temp };
2976 &keep( $ti, $temp, *local_keep, *local_keep_totals, *local_map, 0 );
2979 return '';
2982 # Some transfers done
2983 $exit_xfer_status |= $exit_xfers;
2985 # delete source file after successful transfer
2986 if( $delete_source ){
2987 if( &ftp'delete( $src_path ) ){
2988 &msg( $log, "Deleted remote $src_path\n");
2990 else {
2991 &msg( $log, "Failed to delete remote $src_path\n");
2995 if( $compress ){
2996 # Prevent the shell from expanding characters
2997 local( $f ) = $temp;
2998 local( $comp );
2999 $f =~ s/($shell_metachars)/\\$1/g;
3000 $temp = "$f.$compress_suffix";
3001 # Am I doing compress to gzip conversion?
3002 if( $compress_conv_patt && $src_path =~ /$compress_conv_patt/ &&
3003 $compress_suffix eq $gzip_suffix ){
3004 $comp = "$sys_compress_prog -d < \"$f\" | $gzip_prog > \"$temp\"";
3006 else {
3007 $comp = "$compress_prog < \"$f\" > \"$temp\"";
3009 &sys( $comp );
3010 $temp =~ s/\\($shell_metachars)/$1/g;
3011 $f =~ s/\\($shell_metachars)/$1/g;
3012 unlink( $f );
3015 local( $filesize ) = &filesize( $temp );
3016 local( $sizemsg ) = $filesize;
3017 local( $srcsize ) = $remote_size[ $remote_map{ $src_path } ];
3018 if( $srcsize > $sizemsg && !$compress ){
3019 # should never happen, right? right ...
3020 $sizemsg .= " (file shrunk from $srcsize!)";
3022 elsif( $srcsize < $sizemsg ){
3023 # compression wasn't such a great idea
3024 $sizemsg .= " (file grew from $srcsize!)";
3027 # Ok - chop it up into bits!
3028 if( $split ){
3029 local( $time ) = 0;
3030 if( $force_times ){
3031 $time = $remote_time[ $remote_map{ $src_path } ];
3033 &bsplit( $temp, $dest_path, $time );
3034 unlink( $temp );
3035 $got_mesg .= " and split";
3037 else {
3038 if( -f $dest_path ){
3039 unlink( $dest_path );
3041 if( ! rename( $temp, $dest_path ) ){
3042 &msg( $log, "Cannot rename $temp to $dest_path: $!\n" );
3046 local( $as ) = '';
3047 if( $src_path ne $dest_path ){
3048 $as = " as $dest_path";
3050 $time_taken = time - $start_time;
3051 &msg( $log, "Got $src_path$as$got_mesg $sizemsg $time_taken\n" );
3052 # Make sure to keep what you just got! It may/may not have
3053 # been compressed or gzipped..
3054 local( $locali ) = $local_map{ $dest_path };
3055 &keep( $locali, $dest_path, *local_keep, *local_keep_totals, *local_map, 1 );
3057 &log_upload( $src_path, $dest_path, $got_mesg, $filesize );
3059 return( $dest_path );
3062 sub filename_to_tempname
3064 local( $dir, $file ) = @_;
3066 local ( $dest_path ) = $file;
3068 if( $dir eq '' ){
3069 if( $dest_path =~ /^(.+\/)([^\/]+)$/ ){
3070 ($dir, $file) = ($1, $2);
3073 else {
3074 $file = $dest_path;
3077 # dir
3078 # LIMITED NAMELEN
3079 # if you are really limited in pathname length then
3080 # change the .in. to just .
3081 if( $remote_fs eq 'macos' && ! $get_file ){
3082 return $dir . "tmp.$file";
3084 return "$dir.in.$file.";
3088 # Open, write, close - to try and ensure that the log will allways be filled
3089 # in.
3090 sub log_upload
3092 local( $src_path, $dest_path, $got_mesg, $size ) = @_;
3094 if( ! $upload_log ){
3095 return;
3098 if( ! open( ULOG, ">>$upload_log" ) ){
3099 print STDERR "Cannot write to $upload_log\n";
3100 return;
3103 &myflock( 'ULOG', $LOCK_EX );
3104 if( $get_files ){
3105 print ULOG "$site:$remote_dir/$src_path -> $local_dir/$dest_path $size ";
3107 else {
3108 print ULOG "$local_dir/$dest_path -> $site:$remote_dir/$src_path $size ";
3110 if( $got_mesg ){
3111 print ULOG "($got_mesg)";
3113 print ULOG "\n";
3114 &myflock( 'ULOG', $LOCK_UN );
3115 close( ULOG );
3118 sub do_deletes
3120 # This declaration must be "local()" because it modifies global data.
3121 local( *src_paths,
3122 *src_map,
3123 *src_type, *src_keep,
3124 *src_totals, *src_keep_totals ) = @_;
3125 local( $files_to_go, $dirs_to_go );
3127 if( ! ($do_deletes || $save_deletes) ){
3128 return;
3131 local( $src_path, $i );
3132 local( $orig_do_deletes ) = $do_deletes;
3133 local( $orig_save_deletes ) = $save_deletes;
3135 local( $del_patt ) = $delete_patt;
3136 if( $delete_get_patt ){
3137 $del_patt = $get_patt;
3140 $files_to_go = $src_totals[ 1 ] - $src_keep_totals[ 1 ];
3141 $dirs_to_go = $src_totals[ 0 ] - $src_keep_totals[ 0 ];
3143 # Adjust totals by considering del_patt
3144 for( $i = $#src_paths; $i >= 0; $i-- ){
3145 $src_path = $src_paths[ $i ];
3146 $srci = $i + 1;
3148 if( !$src_keep[ $srci ] && $src_path !~ /$del_patt/
3149 || $delete_excl && $src_path =~ /$delete_excl/ ){
3150 if( $src_type[ $srci ] =~ "d" ){
3151 $dirs_to_go--;
3153 else {
3154 $files_to_go--;
3159 # Check out file deletions
3160 if( $max_delete_files =~ /^(\d+)\%$/ ){
3161 # There is a % in the value - so its a percentage
3162 local( $per ) = $1;
3163 if( $per <= 0 || 100 < $per ){
3164 &msg( "silly percentage $max_delete_files, not deleting\n" );
3165 $do_deletes = 0;
3166 $save_deletes = 0;
3168 else {
3169 # Don't do more than this percentage of files
3170 # Convert max_delete_files into the number of files
3171 $max_delete_files =
3172 int( $src_totals[ 1 ] * $max_delete_files /100 );
3175 if( $files_to_go > $max_delete_files ){
3176 &msg( "Too many files to delete, not actually deleting ($files_to_go > $max_delete_files)\n" );
3177 $do_deletes = 0;
3178 $save_deletes = 0;
3181 # Check out directory deletions
3182 if( $max_delete_dirs =~ /^(\d+)%$/ ){
3183 # There is a % in the value - so its a percentage
3184 local( $per ) = $1;
3185 if( $per <= 0 || 100 < $per ){
3186 &msg( "silly percentage $max_delete_dirs, not deleting\n" );
3187 $do_deletes = 0;
3188 $save_deletes = 0;
3190 else {
3191 # Don't do more than this percentage of dirs
3192 # Convert max_delete_dirs into the number of dirs
3193 $max_delete_dirs =
3194 int( $src_totals[ 0 ] * $max_delete_dirs / 100 );
3198 if( $dirs_to_go > $max_delete_dirs ){
3199 &msg( "Too many directories to delete, not actually deleting ($dirs_to_go > $max_delete_dirs)\n" );
3200 $do_deletes = 0;
3201 $save_deletes = 0;
3204 # Scan the list backwards so subdirectories are dealt with first
3205 for( $i = $#src_paths; $i >= 0; $i-- ){
3206 $src_path = $src_paths[ $i ];
3207 $srci = $i + 1;
3209 if( $src_keep[ $srci ] ){
3210 # Keep this for sure;
3211 &msg( "Keeping: $src_path\n" ) if $debug > 3;
3212 next;
3215 if( $src_path !~ /$del_patt/ ){
3216 &msg( " not in del_patt: $src_path\n" ) if $debug > 1;
3217 next;
3220 if( $delete_excl && $src_path =~ /$delete_excl/ ){
3221 &msg( " do not delete: $src_path\n" ) if $debug > 1;
3222 next;
3225 if( $save_deletes && $save_dir =~ m,$cwd/(.*), ){
3226 local( $save_dir_tail ) = $1;
3227 if( $save_dir_tail && $src_path =~ m,$save_dir_tail/*, ){
3228 next;
3232 if( $save_deletes ){
3233 &save_delete( $src_path, $src_type[ $srci ] );
3235 else {
3236 &do_delete( $src_path, $src_type[ $srci ] );
3240 $do_deletes = $orig_do_deletes;
3241 $save_deletes = $orig_save_deletes;
3244 # Move aside the given file. Kind is 'd' for dirs and 'f' for files.
3245 sub save_delete
3247 local( $save, $kind ) = @_;
3249 local( $real_save_dir, $save_dest );
3250 eval "\$real_save_dir = \"$save_dir\"";
3253 if( ! $get_file ){
3254 &msg( "NEED TO implement remote save_deletes\n" );
3255 return;
3258 $save_dest = "$real_save_dir/$save";
3260 if( $dont_do ){
3261 &msg( "Should save_delete $save to $save_dest\n" );
3262 return;
3265 if( $kind eq 'd' ){
3266 $save_dest =~ s,/+$,,;
3268 # Make sure it exists
3269 &save_mkdir( $save_dest );
3271 # Zap the original
3272 if( rmdir( $save ) == 1 ){
3273 &msg( $log, "Removed directory $save\n" );
3275 else {
3276 &msg( $log, "UNABLE TO REMOVE DIRECTORY $save\n" );
3278 return;
3281 # Save a file
3283 # Make the directories under $save_dir
3284 local( $dirname );
3285 $dirname = $save_dest;
3286 $dirname =~ s/\/[^\/]+$//;
3287 # Make sure the directory exists to mv the file into.
3288 &save_mkdir( $dirname );
3290 if( rename( $save, $save_dest ) == 1 ){
3291 &msg( $log, "Moved $save to $save_dest\n" );
3293 else {
3294 system "$mv_prog '$save' '$save_dest'";
3295 if( ( $? >> 8 ) == 0 ){
3296 &msg( $log, "Moved $save to $save_dest\n" );
3298 else {
3299 &msg( $log, "UNABLE TO MOVE $save TO $save_dest\n" );
3304 sub save_mkdir
3306 local( $dir ) = @_;
3308 if( ! -d $dir ){
3309 if( &mkdirs( $dir ) ){
3310 &msg( $log, "Created save directory $dir\n" );
3312 else {
3313 &msg( $log, "UNABLE TO CREATE $dir, aborting saves\n" );
3314 $save_deletes = 0;
3319 # Delete the given file. Kind is 'd' for dirs and 'f' for files.
3320 sub do_delete
3322 local( $del, $kind ) = @_;
3324 if( $dont_do ){
3325 &msg( "Should delete $del\n" );
3326 return;
3329 if( $kind eq 'd' ){
3330 $del =~ s,/+$,,;
3331 if( $do_deletes ){
3332 if( $get_file ){
3333 &msg( $log, "rmdir $cwd/$del\n" );
3334 rmdir( "$cwd/$del" ) ||
3335 &msg( $log, "rmdir $cwd/$del failed: $!\n" );
3337 else {
3338 &msg( $log, "delete DIR $del\n" );
3339 &ftp'deldir( "$del" ) ||
3340 &msg( $log, "ftp delete DIR $del failed\n" );
3343 else {
3344 if( $get_file ){
3345 &msg( $log, "NEED TO rmdir $cwd/$del\n" );
3347 else {
3348 &msg( $log, "NEED TO ftp'deldir $del\n" );
3351 return;
3354 # Deleting a file.
3355 if( $do_deletes ){
3356 if( $get_file ){
3357 &msg( $log, "unlink $cwd/$del\n" );
3358 unlink( "$cwd/$del" ) ||
3359 &msg( $log, "unlink $cwd/$del failed: $!\n" );
3361 else {
3362 &msg( $log, "delete FILE $del\n" );
3363 &ftp'delete( "$del" ) ||
3364 &msg( $log, "ftp delete FILE $del failed\n" );
3367 else {
3368 if( $get_file ){
3369 &msg( $log, "NEED TO unlink $cwd/$del\n" );
3371 else {
3372 &msg( $log, "NEED TO ftp'delete $del\n" );
3377 sub filesize
3379 local( $fname ) = @_;
3381 if( ! -f $fname ){
3382 return -1;
3385 return (stat( _ ))[ 7 ];
3389 # Is the value
3390 sub istrue
3392 local( $val ) = @_;
3394 return $val eq '1' || $val eq 'yes' || $val eq 'ok' ||
3395 $val eq 'true';
3398 sub mksymlink
3400 local( $dest_path, $existing_path ) = @_;
3402 if( ! $get_file ){
3403 &msg( "Cannot create symlinks on remote systems ($dest_path -> $existing_path)\n" );
3404 return;
3407 # Debian bug #85353 "bad symlink stops listing with -n" <sizif@pier.botik.ru>
3408 if( $dont_do ){
3409 &msg( "Should symlink $dest_path to $existing_path\n" );
3410 return;
3413 # make the symlink locally
3415 # Zap any exiting file/symlink of that name
3416 if( -d $dest_path && ! -l $dest_path ){
3417 local( $msg ) = "rmdir( $dest_path ) before symlink";
3418 if( ! rmdir( $dest_path ) ){
3419 if( $algorithm == 1 ){
3420 $msg = "rmdir( $local_dir/$dest_path ) before symlink";
3421 &msg( "$msg failed: $!\n" );
3423 &msg( "$msg failed: $!\n" );
3424 return;
3426 elsif( $debug ){
3427 &msg( "$msg\n" );
3430 if( -e $dest_path || -l $dest_path ){
3431 local( $msg ) = "unlink( $dest_path ) before symlink";
3432 if( ! unlink( $dest_path ) ){
3433 &msg( "$msg failed: $!\n" );
3434 return;
3436 elsif( $debug ){
3437 &msg( "$msg\n" );
3441 if( (eval 'symlink("","")', $@ eq '') ){
3442 local( $status ) = '';
3443 if( ! symlink( $existing_path, $dest_path ) ){
3444 $status = "Failed to ";
3446 &msg( $log, $status . "symlink $existing_path to $dest_path\n" );
3447 &chown( $uid, $gid, $dest_path );
3449 else {
3450 &msg( $log, "Tried to create symlink - but not supported locally\n" );
3455 # Make a full directory heirarchy
3456 # returns true if the directory doesn't exist
3457 sub mkdirs
3459 local( $dir ) = @_;
3460 local( @dir, $d, $path );
3462 # If the target directory already exists but is a symlink then
3463 # zap the symlink to recreate it as a directory
3464 if( $get_file && -l $dir ){
3465 unlink( $dir );
3468 # Very often the directory does exist - so return now
3469 return 0 if &dir_exists( $dir );
3471 # Make sure that the target directory exists
3472 @dirs = split( '/', $dir );
3474 # the root directory always exists
3475 $path = '';
3476 if( $dirs[ 0 ] eq '' ){
3477 shift( @dirs );
3478 $path = '/';
3481 foreach $d ( @dirs ){
3482 $path = $path . $d;
3483 if( ! &dir_exists( $path ) ){
3484 &msg( "mkdir $path\n" ) if $debug > 2;
3485 if( ! &make_dir( $path, 0755 ) ){
3486 &msg( "make_dir($path,0755) failed with $err\n" );
3487 return 0;
3489 &set_attribs( $path, $path, 'd' );
3491 $path .= "/";
3493 return 1;
3496 # return 0 on error, 1 on success
3497 sub make_dir
3499 local( $dir, $mode ) = @_;
3500 local( $val );
3502 if( $get_file ){
3503 if( $on_win && $dir =~ /^[a-z]:$/i ){
3504 return 1;
3506 # make a local directory
3507 if( -e $dir || -l $dir ){
3508 unlink( $dir );
3510 $val = mkdir( $dir, $mode );
3511 $err = "$!";
3513 else {
3514 # make a remote directory
3515 $val = &ftp'mkdir( $dir );
3517 # The mkdir might have failed due to bad mode
3518 # So try to chmod it anyway
3519 if( $remote_has_chmod ){
3520 $val = &ftp'chmod( $dir, $mode );
3524 return $val;
3527 # return 1 if $dir exists, 0 if not
3528 sub dir_exists
3530 local( $dir ) = @_;
3531 local( $val );
3533 if( $get_file ){
3534 # check if local directory exists
3535 $val = (-d $dir);
3537 else {
3538 # check if remote directory exists
3539 local($old_dir) = &ftp'pwd();
3541 $val = &ftp'cwd($dir);
3543 # If I didn't manage to change dir should be where I was!
3544 if( $val ){
3545 # go back to the original directory
3546 &ftp'cwd($old_dir) || die "Cannot cd to original remote directory";
3549 return $val;
3552 # Set file/directory attributes
3553 sub set_attribs
3555 local( $path, $src_path, $type ) = @_;
3556 local( $mode );
3558 if( ! $chmod ){
3559 &msg( "dont chmod \"$path\"\n" ) if $debug > 2;
3560 return;
3563 if( $get_file ){
3564 local( $pathi ) = $remote_map{ $src_path };
3565 $mode = $remote_mode[ $pathi ];
3567 else {
3568 local( $pathi ) = $local_map{ $path };
3569 $mode = $local_mode[ $pathi ];
3572 # If I can't figure out the mode or I'm not copying it
3573 # use the default
3574 if( !$mode_copy || !$mode ){
3575 if( $type eq 'f' ){
3576 $mode = $file_mode;
3578 elsif( $type eq 'd' ){
3579 $mode = $dir_mode;
3583 # Convert from octal
3584 # Suggested patch to limit bits being set
3585 # $mode = $mode & 0777;
3586 $mode = oct( $mode ) if $mode =~ /^0/;
3588 if( $get_file ){
3589 # Change local
3591 chmod $mode, $path;
3593 if( $user ne '' && $group ne '' ){
3594 &chown( $uid, $gid, $path );
3597 else {
3598 # change the remote file
3599 if( $remote_has_chmod ){
3600 &ftp'chmod( $path, $mode );
3606 sub get_passwd
3608 local( $user ) = @_;
3609 local( $pass );
3610 local( $| ) = 1;
3612 # prompt for a password
3613 $SIG{ 'INT' } = 'IGNORE';
3614 $SIG{ 'QUIT' } = 'IGNORE';
3616 system "stty -echo </dev/tty >/dev/tty 2>&1";
3617 print "Password for $user: ";
3619 $pass = <STDIN>;
3620 print "\n";
3621 chop( $pass );
3623 system "stty echo </dev/tty >/dev/tty 2>&1";
3625 $SIG{ 'INT' } = 'DEFAULT';
3626 $SIG{ 'QUIT' } = 'DEFAULT';
3628 return $pass;
3631 sub compare_times
3633 # Try and allow for time zone changes (eg when a site
3634 # switches from daylight saving to non daylight saving)
3635 # by ignoring differences of exactly one hour
3637 local( $t1, $t2 ) = @_;
3638 local( $diff ) = ($t1 > $t2 ? $t1 - $t2 : $t2 - $t1);
3640 return ($t1 > $t2) && ($diff != 3600);
3643 sub create_assocs
3645 local( $map );
3647 &delete_assocs();
3649 &msg( "creating assocs ...\n" ) if $debug > 2;
3650 foreach $map ( @assocs ){
3651 eval "\$$map = \"\$big_temp/$map.$$\"";
3652 eval "dbmopen( $map, \$$map, 0644 )";
3654 &msg( "creating assocs done\n" ) if $debug > 2;
3657 sub delete_assocs
3659 local( $map );
3661 &msg( "deleting assocs ...\n" ) if $debug > 2;
3662 foreach $map ( @assocs ){
3663 eval "\$$map = \"\$big_temp/$map.$$\"";
3664 eval "dbmclose( $map )";
3665 &unlink_dbm( eval "\$$map" );
3666 eval "\%$map = ()";
3668 &msg( "deleting assocs done\n" ) if $debug > 2;
3671 sub unlink_dbm
3673 local( $file ) = @_;
3674 unlink "$file.pag" if -e "$file.pag";
3675 unlink "$file.dir" if -e "$file.dir";
3676 unlink "$file.gdbm" if -e "$file.gdbm";
3677 unlink "$file" if -e "$file";
3680 # Chop the tmp file up
3681 sub bsplit
3683 local( $temp, $dest_path, $time ) = @_;
3684 local( $dest_dir ) = "$dest_path-split";
3685 local( $bufsiz ) = 512;
3686 local( $buffer, $in, $sofar );
3688 &msg( "Splitting up $temp into $dest_dir/ ($time)\n" ) if $debug;
3690 # Stomp on the original directories
3691 local( $d ) = $dest_dir;
3692 $d =~ s/($shell_metachars)/\\$1/g;
3693 &sys( "$rm_prog -rf \"$d\"" );
3695 &mkdirs( $dest_dir );
3697 local( $index ) = "00";
3698 local( $part );
3699 open( TMP, $temp ) || die "Cannot open $temp!";
3700 $sofar = $split_chunk; # Force a new file
3701 while( ($in = sysread( TMP, $buffer, $bufsiz )) > 0 ){
3702 if( $sofar >= $split_chunk ){
3703 if( $part ){
3704 close( PART );
3705 if( $time ){
3706 &set_timestamp( $part, $time );
3709 $index++;
3710 $part = "$dest_dir/part$index";
3711 &msg( "creating $part\n" ) if $debug;
3712 open( PART, ">$part" ) || die "Cannot create $part";
3713 # Make sure to keep this!
3714 local( $locali ) = $local_map{ $part };
3715 &keep( $locali, $part, *local_keep, *local_keep_totals, *local_map, 1 );
3716 $sofar = 0;
3718 if( ($out = syswrite( PART, $buffer, $in )) != $in ){
3719 die "Failed to write data to $part";
3721 $sofar += $in;
3723 close( PART );
3724 if( $time ){
3725 &set_timestamp( $part, $time );
3727 close( TMP );
3729 # Generate a readme file about what is in the split directory
3730 local( $readme ) = "$dest_dir/README";
3731 open( README, ">$readme" ) || die "Cannot create $readme";
3732 print README "This directory contains a splitup version of $dest_path\n";
3733 print README "to recreate the original simply concatenate all the\n";
3734 print README "parts back together.\n\nChecksums are:\n\n";
3735 close README;
3736 &sys( "(cd \"$d\" ; $sum_prog part*)>> $readme" );
3739 sub sys
3741 local( $com ) = @_;
3742 &msg( "$com\n" ) if $debug > 2;
3743 return system( $com ) / 256;
3746 # Set up an associative array given all an array of keys.
3747 # @fred = ( 'a' );
3748 # &set_assoc_from_array( *fred )
3749 # Creates => $fred{ 'a' } = 1
3751 sub set_assoc_from_array
3753 # This declaration must be "local()" because it modifies global data.
3754 local( *things ) = @_;
3755 foreach $thing ( @things ){
3756 $things{ $thing } = 1;
3760 sub find_prog
3762 local( $prog ) = @_;
3763 local( $path ) = $ENV{ 'PATH' };
3765 foreach $dir ( split( /$path_sep/, $path ) ){
3766 local( $path ) = $dir . $file_sep . $prog;
3767 if( -x $path ){
3768 return $path;
3770 if( $on_win ){
3771 $path .= ".exe";
3772 if( -x $path ){
3773 return $path;
3777 return '';
3780 sub real_dir_from_path
3782 local( $program ) = @_;
3783 local( @prog_path ) = split( m:$file_sep_pat: , $program ); # dir collection
3784 local( $dir );
3786 while( -l $program ){ # follow symlink
3787 $program = readlink( $program );
3788 if( $program =~ m:^$file_sep_pat: ){ # full path?
3789 @prog_path = (); # start dir collection anew
3791 else {
3792 pop( @prog_path ); # discard file name
3794 push( @prog_path, split( m:$file_sep_pat:, $program ) );# add new parts
3795 $program = join( $file_sep, @prog_path ); # might be a symlink again...
3797 pop( @prog_path );
3798 $dir = join( $file_sep, @prog_path );
3800 if( ! $dir ){
3801 $dir = '.';
3804 return $dir;
3807 sub msg
3809 local( $todo, $msg ) = (0, "");
3811 if( $#_ == 1 ){
3812 ($todo, $msg) = @_;
3814 else {
3815 $todo = 0;
3816 $msg = $_[ 0 ];
3819 # Assign to $0 so when you do a 'ps' it says this!
3820 if( defined $package &&
3821 defined $site &&
3822 defined $remote_dir ){
3823 $0 = "mirror $package:$site:$remote_dir $msg";
3825 else {
3826 $0 = "mirror $msg";
3829 if( $todo & $log ){
3830 push( @log, $msg );
3832 # Not sure about this one. always print the message even if its a log msg.
3833 # else {
3834 print $msg;
3838 sub to_bytes
3840 local( $size ) = @_;
3841 if( $size =~ /^(\d+)\s*(k|b|m)s*$/i ){
3842 $size = $1;
3843 if( $2 =~ /[mM]/ ){
3844 $size *= (1024*1024);
3846 elsif( $2 =~ /[bB]/ ){
3847 $size *= 512;
3849 elsif( $2 =~ /[kK]/ ){
3850 $size *= 1024;
3853 return $size;
3856 # Given a unix filename map it into a vms name.
3857 # $kind is 'f' for files and 'd' for directories
3858 sub unix2vms
3860 local( $v, $kind ) = @_;
3862 if( $v eq '.' || $v eq '/' ){
3863 return "[]";
3866 if( $vms_dir ){
3867 $v = $vms_dir . '/' . $v;
3870 if( $kind eq 'f' ){
3871 # Map a/b/c.txt into [a.b]c.txt
3872 if( $v =~ m,(.*)/([^/]+), ){
3873 local( $dir, $rest ) = ($1, $2);
3874 $dir =~ s,/,.,g;
3875 $v = "[$dir]$rest";
3878 else {
3879 # Map a/b/c into [a.b.c]
3880 $v =~ s,/,.,g;
3881 $v = "[$v]";
3883 return $v;
3886 sub dirpart
3888 local( $path ) = @_;
3889 if( $path =~ m:/: ){
3890 $path =~ s:^(.*)/[^/]+$:$1:;
3892 else {
3893 $path = '.';
3895 return $path;
3898 # Given a filename (not a directory) and what path it symlinks to
3899 # return a, hopefully, non-relative pathname that the symlink
3900 # really points to. This is so it can be used to index into the $src_path
3901 # map.
3902 sub expand_symlink
3904 local( $orig_path, $points_to ) = @_;
3905 local( $dirpart ) = &dirpart( $orig_path );
3907 return &flatten_path( "$dirpart/$points_to" );
3910 # flatten out the effects of dir/.. and /./
3911 # The problem is not flattening out ../.. into nothing! Hence
3912 # the contortions below.
3913 sub flatten_path
3915 local( $path ) = @_;
3916 local( $changed ) = 1;
3917 local( $i );
3919 local( $rooted ) = $path =~ m:^/:;
3920 local( $count ) = 0;
3921 local( $orig_path ) = $path;
3923 $path =~ s:^/::;
3924 $path =~ s:(^|/)\.(/|$)::g;
3925 $path =~ s:/+:/:g;
3927 while( $changed ){
3928 if( $count++ > 100 ){
3929 &msg( $log, "LOOPING in flatten_path orig = $orig_path, path now $path\n" );
3930 last;
3932 local( $in ) = $path;
3933 local( @parts ) = split( /\//, $path );
3934 for( $i = 0; $i <= $#parts; $i++ ){
3935 if( $parts[ $i ] eq '.' ){
3936 $parts[ $i ] = undef;
3937 next;
3939 if( $i > 0 && $parts[ $i ] eq '..' &&
3940 $parts[ $i - 1 ] && $parts[ $i - 1 ] ne '..' ){
3941 $parts[ $i - 1 ] = $parts[ $i ] = undef;
3942 next;
3945 $path = '';
3946 for( $i = 0; $i <= $#parts; $i++ ){
3947 next unless $parts[ $i ];
3948 $path .= '/' if $path ne '';
3949 $path .= $parts[ $i ];
3951 $changed = $in ne $path;
3953 if( $rooted ){
3954 $path = "/$path";
3956 return $path;
3960 # Fix up a package name.
3961 # strip trailing and leading ws and replace awkward characters
3962 # This doesn't guarentee a unique filename.
3963 sub fix_package
3965 local( $package ) = @_;
3966 $package =~ s:[\s/']:_:g;
3967 return $package;
3970 sub will_compress
3972 $src_type[ $_[1] ] eq 'f' &&
3973 $compress_patt && $_[0] =~ /$compress_patt/ &&
3974 ( ! $compress_size_floor ||
3975 $compress_size_floor < $src_size[ $_[1] ] ) &&
3976 !($compress_excl && $_[0] =~ /$compress_excl/i) &&
3977 !($compress_suffix eq $gzip_suffix &&
3978 $compress_conv_patt && $_[0] =~ /$compress_conv_patt/);
3981 sub will_split
3983 $split_max &&
3984 $src_size[ $_[1] ] > $split_max &&
3985 $src_type[ $_[1] ] eq 'f' &&
3986 $split_patt && $_[0] =~ /$split_patt/;
3989 sub myflock
3991 local( $file, $kind ) = @_;
3993 if( ! $can_flock ){
3994 return;
3997 eval( "flock( \$file, $kind )" );
3998 if( $@ =~ /unimplemented/ ){
3999 $can_flock = 0;
4000 warn "flock not unavialable, running unlocked\n";
4004 sub t2str
4006 local( @t );
4007 if( $use_timelocal ){
4008 @t = localtime( $_[0] );
4010 else {
4011 @t = gmtime( $_[0] );
4013 local($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = @t;
4015 # Debian bug #48611, 1969 appeared as 2069, dsb@smart.net.
4016 $year += 1900;
4018 return sprintf( "%04d/%02d/%02d-%02d:%02d:%02d",
4019 $year, $mon + 1, $mday, $hour, $min, $sec );
4022 sub handler
4024 $sigs ++;
4025 if( $sigs > $max_sigs ){
4026 exit( 0 );
4029 local( $sig ) = @_;
4030 local( $msg ) = "Caught a SIG$sig shutting down";
4031 local( $package, $filename, $line ) = caller;
4032 warn "$package:$filename:$line $msg";
4033 exit( 0 );
4036 sub trap_signals
4038 local( $sig );
4039 foreach $sig ( 'HUP', 'INT', 'QUIT', 'ILL',
4040 'TRAP', 'IOT', 'BUS', 'FPE',
4041 'USR1', 'SEGV', 'USR2',
4042 'PIPE', 'ALRM', 'TERM' ){
4043 $SIG{ $sig } = "main\'handler";
4047 sub map_user_group
4049 if( ! defined( $uid ) ){
4050 if( $user =~ /^\d+$/ ){
4051 # User is just a number - presume it is the uid
4052 $uid = $user;
4054 else {
4055 $uid = (getpwnam( $user ))[ 2 ];
4058 if( ! defined( $gid ) ){
4059 if( $group =~ /\d+$/ ){
4060 # Group is just a number - presume it is the gid
4061 $gid = $group;
4063 else {
4064 $gid = (getgrnam( $group ))[ 2 ];
4069 sub keep
4071 local( $pathi, $path, *keep, *keep_totals, *keep_map, $kind ) = @_;
4072 # If pathi is already kept nothing to do
4073 if( $pathi eq '' ){
4074 &msg( " keep $path NOTE null pathi\n" ) if $debug > 3;
4075 return;
4077 if( $keep[ $pathi ] ){
4078 &msg( " keep $path [$pathi] NOTE already kept\n" ) if $debug > 3;
4079 return;
4082 $keep[ $pathi ] = 1;
4083 $keep_totals[ $kind ]++;
4084 &msg( " keep $path\n" ) if $debug > 3;
4086 # Keep all the parent directories
4087 while( $path =~ m,^(.*)/([^/]+)$, ){
4088 $path = $1;
4089 $pathi = $keep_map{ $path };
4090 if( $pathi eq '' ){
4091 &msg( " keep $path NOTE null pathi\n" ) if $debug > 3;
4092 return;
4094 if( $keep[ $pathi ] ){
4095 &msg( " keep $path [$pathi] NOTE already kept\n" ) if $debug > 3;
4096 return;
4099 $keep[ $pathi ] = 1;
4100 $keep_totals[ 0 ]++;
4104 sub alarm
4106 local( $time_to_sig ) = @_;
4107 eval "alarm( $time_to_sig )";
4110 sub chown
4112 local( $uid, $gid, $path ) = @_;
4113 eval "chown ( \$uid, \$gid, \$path )";
4116 sub utime
4118 local( $atime, $mtime, $path ) = @_;
4119 if( ! $on_win ){
4120 return utime( $atime, $mtime, $path );
4123 # On windoze I might have set attribs to allow the time to be changed first
4124 local( $old_mode ) = (stat( $path ))[ 2 ];
4125 local( $tmp_mode ) = $old_mode;
4126 local( $ret );
4128 $tmp_mode |= 0700;
4129 chmod( $tmp_mode, $path );
4130 $ret = utime( $atime, $mtime, $path );
4131 chmod( $old_mode, $path );
4132 return $ret;
4135 sub cwd
4137 local( $lcwd ) = '';
4138 eval "\$lcwd = $win_getcwd";
4140 if( ! ($lcwd eq '' || $lcwd eq $win_getcwd) ){
4141 # Must be on windoze!
4142 $cwd = $lcwd;
4144 else {
4145 # didn't manage it try and run the pwd command instead
4146 chop( $cwd = `pwd` );
4148 return $cwd;