You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1321 lines
29 KiB
1321 lines
29 KiB
use strict; |
|
|
|
# todo: make sure and create the target directory if it doesn't exist. |
|
# todo: make this work either direction |
|
# todo: fix any case problems |
|
|
|
my $g_protocol_version = "mccopy1\n"; |
|
my $g_opt_port = 7070; |
|
my $g_server_md5 = 0; |
|
|
|
use IO::Socket; |
|
|
|
sub GetMD5 |
|
{ |
|
my $filename = shift; |
|
$filename =~ s,/,\\,g; |
|
# print "$filename\n"; |
|
print "."; |
|
my( $cmd ) = "md5.exe < \"$filename\""; |
|
# print $cmd . "\n"; |
|
open MD5, "$cmd|"; |
|
my $out = <MD5>; |
|
close MD5; |
|
$out =~ s/^.*\=\s+//; |
|
$out =~ s/\n//g; |
|
# print "'" . $out . "'" . "\n"; |
|
return $out; |
|
} |
|
|
|
################################################################################################ |
|
# SERVER CODE |
|
################################################################################################ |
|
sub SendFileName |
|
{ |
|
my( $sock ) = shift; |
|
my( $file ) = shift; |
|
my( $isdir ) = -d $file; |
|
|
|
if( $isdir ) |
|
{ |
|
if( $file =~ m/\/\.$/ || # "." |
|
$file =~ m/\/\.\.$/ ) # ".." |
|
{ |
|
return; |
|
} |
|
} |
|
|
|
my( @statinfo ) = stat $file; |
|
if( @statinfo ) |
|
{ |
|
my( $mtime ) = $statinfo[9]; |
|
my( $mode ) = $statinfo[2]; |
|
# my( $mtimestr ) = scalar( localtime( $mtime ) ); |
|
my( $size ) = $statinfo[7]; |
|
if( $isdir ) |
|
{ |
|
print $sock "d\n"; |
|
} |
|
else |
|
{ |
|
print $sock "f\n"; |
|
} |
|
print $sock &RemoveBaseDir( $file ) . "\n"; |
|
print $sock $mtime . "\n"; |
|
printf $sock "%o\n", $mode; |
|
print $sock $size . "\n"; |
|
if( !$isdir && $g_server_md5 ) |
|
{ |
|
print $sock &GetMD5( $file ) . "\n"; |
|
} |
|
# print $file . "\n"; |
|
# print $mtime . "\n"; |
|
# print $mode . "\n"; |
|
# print $md5 . "\n"; |
|
} |
|
else |
|
{ |
|
print "CAN'T STAT $file\n"; |
|
} |
|
|
|
if( $isdir ) |
|
{ |
|
SendDirName( $sock, $file ); |
|
} |
|
} |
|
|
|
|
|
sub SendDirName |
|
{ |
|
my( $sock ) = shift; |
|
my( $dirname ) = shift; |
|
if( $dirname =~ m/\/\.$/ || # "." |
|
$dirname =~ m/\/\.\.$/ ) # ".." |
|
{ |
|
return; |
|
} |
|
|
|
local( *SRCDIR ); |
|
opendir SRCDIR, $dirname; |
|
my( @dir ) = readdir SRCDIR; |
|
closedir SRCDIR; |
|
|
|
my( $item ); |
|
while( $item = shift @dir ) |
|
{ |
|
&SendFileName( $sock, $dirname . "/" . $item ); |
|
} |
|
} |
|
|
|
sub GetFile |
|
{ |
|
my( $sock ) = shift; |
|
my( $filename ) = shift; |
|
my( $localfilename ) = &AddBaseDir( $filename ); |
|
print "GetFile: $filename ($localfilename)\n"; |
|
local( *FILE ); |
|
open FILE, "<$localfilename"; |
|
binmode( FILE ); |
|
my( $filebits ); |
|
seek FILE, 0, 2; |
|
my( $size ) = tell FILE; |
|
if( $size < 0 ) |
|
{ |
|
die "$filename \$size == $size\n"; |
|
} |
|
seek FILE, 0, 0; |
|
read FILE, $filebits, $size; |
|
close FILE; |
|
# print "sending $filename: $size\n"; |
|
print $sock $filebits; |
|
# print "finished sending $filename\n"; |
|
} |
|
|
|
sub HandleCommand |
|
{ |
|
my( $sock ) = shift; |
|
my( $cmd ) = shift; |
|
|
|
if( $cmd =~ m/dirlistmd5\s+(.*)$/ ) |
|
{ |
|
$g_server_md5 = 1; |
|
&SetBaseDir( $1 ); |
|
&SendDirName( $sock, $1 ); |
|
print $sock "\n"; # terminating newline to end reply |
|
} |
|
elsif( $cmd =~ m/dirlist\s+(.*)$/ ) |
|
{ |
|
$g_server_md5 = 0; |
|
&SetBaseDir( $1 ); |
|
&SendDirName( $sock, $1 ); |
|
print $sock "\n"; # terminating newline to end reply |
|
} |
|
elsif( $cmd =~ m/getfile\s+(.*)$/ ) |
|
{ |
|
&GetFile( $sock, $1 ); |
|
} |
|
} |
|
|
|
sub RunServer |
|
{ |
|
my( $hostname ) = `hostname`; |
|
# my( $hostname ) = "localhost"; |
|
$hostname =~ s/\n//; # remove newlines |
|
|
|
my $sock = new IO::Socket::INET ( |
|
LocalHost => $hostname, |
|
LocalPort => $g_opt_port, |
|
Proto => 'tcp', |
|
Listen => 1, |
|
Reuse => 1, |
|
); |
|
die "Could not create socket: $!\n" unless $sock; |
|
|
|
my( $clientnum ) = 0; |
|
while( 1 ) |
|
{ |
|
my $new_sock = $sock->accept(); |
|
print "accept!\n"; |
|
if( fork() == 0 ) |
|
{ |
|
print "$clientnum: opening connection...\n"; |
|
my $version = <$new_sock>; |
|
if( $version ne $g_protocol_version ) |
|
{ |
|
die "wrong protocol version: server: $g_protocol_version client: %version\n"; |
|
} |
|
my $command; |
|
while(defined($command = <$new_sock>)) |
|
{ |
|
print "$clientnum: command: $command"; |
|
&HandleCommand( $new_sock, $command ); |
|
print "$clientnum: done with $command"; |
|
} |
|
print "$clientnum: closing connection...\n"; |
|
close( $new_sock ); |
|
exit; |
|
} |
|
$clientnum++; |
|
} |
|
|
|
close($sock); # never get here. |
|
} |
|
|
|
################################################################################################ |
|
# CLIENT CODE |
|
################################################################################################ |
|
|
|
|
|
# all options that we might care about from rsync: |
|
# -v, --verbose increase verbosity |
|
# -q, --quiet decrease verbosity |
|
# -c, --checksum always checksum |
|
# -a, --archive archive mode |
|
# -r, --recursive recurse into directories |
|
# -R, --relative use relative path names |
|
# -b, --backup make backups (default ~ suffix) |
|
# --backup-dir=DIR put backups in the specified directory |
|
# --suffix=SUFFIX override backup suffix |
|
# -u, --update update only (don't overwrite newer files) |
|
# -p, --perms preserve permissions |
|
# -t, --times preserve times |
|
# -n, --dry-run show what would have been transferred |
|
# --existing only update files that already exist |
|
# --delete delete files that don't exist on the sending side |
|
# --delete-excluded also delete excluded files on the receiving side |
|
# --delete-after delete after transferring, not before |
|
# --max-delete=NUM don't delete more than NUM files |
|
# --force force deletion of directories even if not empty |
|
# --timeout=TIME set IO timeout in seconds |
|
# -I, --ignore-times don't exclude files that match length and time |
|
# --size-only only use file size when determining if a file should be transferred |
|
# -T --temp-dir=DIR create temporary files in directory DIR |
|
# --compare-dest=DIR also compare destination files relative to DIR |
|
# -P equivalent to --partial --progress |
|
# -z, --compress compress file data |
|
# --exclude=PATTERN exclude files matching PATTERN |
|
# --exclude-from=FILE exclude patterns listed in FILE |
|
# --include=PATTERN don't exclude files matching PATTERN |
|
# --include-from=FILE don't exclude patterns listed in FILE |
|
# --version print version number |
|
# --daemon run as a rsync daemon |
|
# --address bind to the specified address |
|
# --stats give some file transfer stats |
|
# --progress show progress during transfer |
|
# -h, --help show this help screen |
|
|
|
# options that are actually implemented: |
|
# |
|
sub Usage |
|
{ |
|
print "\n"; |
|
print "Usage:\n"; |
|
print "perl McCopy.pl --server\n"; |
|
print "or\n"; |
|
print "perl McCopy.pl [options] srcdir dstdir\n"; |
|
print "\n"; |
|
print "ie:\n"; |
|
print "perl McCopy.pl --verbose --recursive remotemachine:u:/hl u:/hl.work\n"; |
|
print "\n"; |
|
print "where \"remotemachine\" is running a McCopy server\n"; |
|
print "\n"; |
|
print "options are:\n"; |
|
print "--server run as server (ignores other options)\n"; |
|
print "--port N (default 7070)\n"; |
|
print "--verbose\n"; |
|
print "--test don't actually copy or delete any files\n"; |
|
print "--ignore-time don't use file mtimes as a criterion for file that need\n"; |
|
print " to be copied.\n"; |
|
print "--ignore-permissions don't use file permissions as a criterion for file that\n"; |
|
print " need to be copied.\n"; |
|
print "--ignore-size don't use file size as a criterion for file that need\n"; |
|
print " to be copied.\n"; |
|
print "--md5 Use md5 checksums\n"; |
|
print "--recursive\n"; |
|
print "--delete-readonly delete readonly files in dst\n"; |
|
print " that don't exist in src\n"; |
|
print "--delete-writable delete writable files in dst\n"; |
|
print "--delete-dirs delete directories in dst that don't exist in src\n"; |
|
print "--clobber-writable-newer\n"; |
|
print " write over upon copy files that have been modified locally\n"; |
|
print "--delete-excluded delete files on dst that are excluded using --exclude\n"; |
|
print "--exclude exclude files containing perl regular expression, ie:\n"; |
|
print " --exclude /.*release.*/i\n"; |
|
print "--include override --exclude for files containing perl regular expression, ie:\n"; |
|
print " --include /.*debughlp.*/i\n"; |
|
print "--mirror same as --recursive --delete-readonly --delete-writable\n"; |
|
print " --clobber-writable-newer --delete-excluded --delete-dirs\n"; |
|
print "--mirror-safe same as --recursive --delete-readonly --delete-dirs\n"; |
|
exit; |
|
} |
|
|
|
# default command line options |
|
my $g_opt_server = 0; |
|
my $g_opt_test = 0; |
|
my $g_opt_verbose = 0; |
|
my $g_opt_recursive = 0; |
|
my $g_opt_deletereadonly = 0; |
|
my $g_opt_deletewritable = 0; |
|
my $g_opt_clobberwritablenewer = 0; |
|
my $g_opt_deleteexcluded = 0; |
|
my $g_opt_deletedirs = 0; |
|
my $g_opt_ignoretime = 0; |
|
my $g_opt_ignoreperms = 0; |
|
my $g_opt_ignoresize = 0; |
|
my @g_opt_exclude; |
|
my @g_opt_include; |
|
my $g_opt_src; |
|
my $g_opt_dst; |
|
my $g_opt_md5; |
|
|
|
my $g_src_is_local; |
|
my $g_src_machine = ""; |
|
my $g_src_path; |
|
|
|
my $g_dst_is_local; |
|
my $g_dst_machine = ""; |
|
my $g_dst_path; |
|
|
|
# indexed by $filename |
|
my %g_remote_mtime; |
|
my %g_remote_mode; |
|
my %g_remote_isdir; |
|
my %g_remote_size; |
|
my %g_remote_md5; |
|
|
|
my %g_local_mtime; |
|
my %g_local_mode; |
|
my %g_local_isdir; |
|
my %g_local_size; |
|
my %g_local_md5; |
|
|
|
my %g_alreadycomparedtime; |
|
my %g_alreadycomparedpermissions; |
|
my %g_alreadycomparedsize; |
|
my %g_alreadycomparedmd5; |
|
|
|
my %g_files_to_delete; |
|
my %g_dirs_to_delete; |
|
my %g_files_to_copy; |
|
my %g_dirs_to_create; |
|
|
|
my $g_max_time_delta = 2; # in seconds |
|
|
|
my $g_basedir; |
|
|
|
sub BackToForwardSlash |
|
{ |
|
my( $path ) = shift; |
|
$path =~ s,\\,/,g; |
|
return $path; |
|
} |
|
|
|
sub ForwardToBackSlash |
|
{ |
|
my( $path ) = shift; |
|
$path =~ s,/,\\,g; |
|
return $path; |
|
} |
|
|
|
sub SetBaseDir |
|
{ |
|
$g_basedir = shift; |
|
# print "\$g_basedir: $g_basedir\n"; |
|
} |
|
|
|
sub RemoveFileName |
|
{ |
|
my( $in ) = shift; |
|
$in = &BackToForwardSlash( $in ); |
|
$in =~ s,/[^/]*$,,; |
|
return $in; |
|
} |
|
|
|
sub RemovePath |
|
{ |
|
my( $in ) = shift; |
|
$in = &BackToForwardSlash( $in ); |
|
$in =~ s,^(.*)/([^/]*)$,$2,; |
|
return $in; |
|
} |
|
|
|
|
|
sub MakeDirHier |
|
{ |
|
my( $in ) = shift; |
|
# print "MakeDirHier( $in )\n"; |
|
$in = &BackToForwardSlash( $in ); |
|
my( @path ); |
|
while( $in =~ m,/, ) # while $in still has a slash |
|
{ |
|
my( $end ) = &RemovePath( $in ); |
|
push @path, $end; |
|
# print $in . "\n"; |
|
$in = &RemoveFileName( $in ); |
|
} |
|
my( $i ); |
|
my( $numelems ) = scalar( @path ); |
|
my( $curpath ); |
|
for( $i = $numelems - 1; $i >= 0; $i-- ) |
|
{ |
|
$curpath .= "/" . $path[$i]; |
|
my( $dir ) = $in . $curpath; |
|
if( !stat $dir ) |
|
{ |
|
print "mkdir $dir\n"; |
|
mkdir $dir, 0777; |
|
} |
|
} |
|
} |
|
|
|
sub RemoveBaseDir |
|
{ |
|
my( $path ) = shift; |
|
# print "removebasedir: $path "; |
|
$path =~ s,^$g_basedir/,,; |
|
# print "$path\n"; |
|
return $path; |
|
} |
|
|
|
sub AddBaseDir |
|
{ |
|
my( $path ) = shift; |
|
return $g_basedir . "/" . $path; |
|
} |
|
|
|
sub FixPath |
|
{ |
|
my( $path ) = shift; |
|
# backslash to forward slash |
|
$path =~ s,\\,/,g; |
|
# remove trailing slash |
|
$path =~ s,/$,,; |
|
return $path; |
|
} |
|
|
|
sub ProcessLongCommand |
|
{ |
|
my( $cmd ) = shift; |
|
if( $cmd =~ m/--verbose/ ) |
|
{ |
|
$g_opt_verbose = 1; |
|
} |
|
elsif( $cmd =~ m/--server/ ) |
|
{ |
|
$g_opt_server = 1; |
|
} |
|
elsif( $cmd =~ m/--test/ ) |
|
{ |
|
$g_opt_test = 1; |
|
} |
|
elsif( $cmd =~ m/--ignore-time/ ) |
|
{ |
|
$g_opt_ignoretime = 1; |
|
} |
|
elsif( $cmd =~ m/--ignore-permissions/ ) |
|
{ |
|
$g_opt_ignoreperms = 1; |
|
} |
|
elsif( $cmd =~ m/--ignore-size/ ) |
|
{ |
|
$g_opt_ignoresize = 1; |
|
} |
|
elsif( $cmd =~ m/--md5/ ) |
|
{ |
|
$g_opt_md5 = 1; |
|
} |
|
elsif( $cmd =~ m/--recursive/ ) |
|
{ |
|
$g_opt_recursive = 1; |
|
} |
|
elsif( $cmd =~ m/--mirror-safe/ ) |
|
{ |
|
$g_opt_recursive = 1; |
|
$g_opt_deletereadonly = 1; |
|
$g_opt_deletewritable = 0; |
|
$g_opt_clobberwritablenewer = 0; |
|
$g_opt_deleteexcluded = 0; |
|
$g_opt_deletedirs = 1; |
|
} |
|
elsif( $cmd =~ m/--mirror/ ) |
|
{ |
|
$g_opt_recursive = 1; |
|
$g_opt_deletereadonly = 1; |
|
$g_opt_deletewritable = 1; |
|
$g_opt_clobberwritablenewer = 1; |
|
$g_opt_deleteexcluded = 1; |
|
$g_opt_deletedirs = 1; |
|
} |
|
elsif( $cmd =~ m/--delete-readonly/ ) |
|
{ |
|
$g_opt_deletereadonly = 1; |
|
} |
|
elsif( $cmd =~ m/--delete-writable/ ) |
|
{ |
|
$g_opt_deletewritable = 1; |
|
} |
|
elsif( $cmd =~ m/--clobber-writable-newer/ ) |
|
{ |
|
$g_opt_clobberwritablenewer = 1; |
|
} |
|
elsif( $cmd =~ m/--delete-excluded/ ) |
|
{ |
|
$g_opt_deleteexcluded = 1; |
|
} |
|
elsif( $cmd =~ m/--delete-dirs/ ) |
|
{ |
|
$g_opt_deletedirs = 1; |
|
} |
|
} |
|
|
|
sub ProcessCommandLine |
|
{ |
|
my( $cmd ); |
|
while( $cmd = shift ) |
|
{ |
|
# hack - special case for exclude since it has an argument |
|
if( $cmd =~ m/^--exclude/ ) |
|
{ |
|
push @g_opt_exclude, shift; |
|
} |
|
elsif( $cmd =~ m/^--include/ ) |
|
{ |
|
push @g_opt_include, shift; |
|
} |
|
elsif( $cmd =~ m/^--port/ ) |
|
{ |
|
$g_opt_port = shift; |
|
} |
|
elsif( $cmd =~ m/^--/ ) |
|
{ |
|
&ProcessLongCommand( $cmd ); |
|
} |
|
elsif( $cmd =~ m/^-/ ) |
|
{ |
|
print "short command $cmd\n"; |
|
} |
|
else |
|
{ |
|
if( !defined( $g_opt_src ) ) |
|
{ |
|
$g_opt_src = &FixPath( $cmd ); |
|
} |
|
elsif( !defined( $g_opt_dst ) ) |
|
{ |
|
$g_opt_dst = &FixPath( $cmd ); |
|
} |
|
else |
|
{ |
|
print "Don't understand $cmd\n"; |
|
&Usage(); |
|
} |
|
} |
|
} |
|
} |
|
|
|
sub PrintOptions |
|
{ |
|
if( $g_opt_verbose ) |
|
{ |
|
print "\n"; |
|
print "Options:\n"; |
|
print "\$g_opt_src = $g_opt_src\n"; |
|
print "\$g_opt_dst = $g_opt_dst\n"; |
|
print "\$g_opt_test = $g_opt_test\n"; |
|
print "\$g_opt_ignoretime = $g_opt_ignoretime\n"; |
|
print "\$g_opt_ignoreperms = $g_opt_ignoreperms\n"; |
|
print "\$g_opt_ignoresize = $g_opt_ignoresize\n"; |
|
print "\$g_opt_verbose = $g_opt_verbose\n"; |
|
print "\$g_opt_recursive = $g_opt_recursive\n"; |
|
print "\$g_opt_deletereadonly = $g_opt_deletereadonly\n"; |
|
print "\$g_opt_deletewritable = $g_opt_deletewritable\n"; |
|
print "\$g_opt_clobberwritablenewer = $g_opt_clobberwritablenewer\n"; |
|
print "\$g_opt_deleteexcluded = $g_opt_deleteexcluded\n"; |
|
print "\@g_opt_exclude = @g_opt_exclude\n"; |
|
print "\n"; |
|
} |
|
} |
|
|
|
sub ValidateOptions |
|
{ |
|
if( $g_opt_server ) |
|
{ |
|
return; |
|
} |
|
if( !defined( $g_opt_src ) ) |
|
{ |
|
print "src not defined\n"; |
|
Usage(); |
|
} |
|
if( !defined( $g_opt_dst ) ) |
|
{ |
|
print "dst not defined\n"; |
|
Usage(); |
|
} |
|
if( !$g_opt_recursive ) |
|
{ |
|
print "--recursive must be used. . non-recursive operation not supported\n"; |
|
Usage(); |
|
} |
|
} |
|
|
|
# src/dst looks like: |
|
# gary:u:/hl2/hl2 |
|
# u:/hl2/hl2 |
|
# /hl2/hl2 |
|
|
|
sub ParseSrcDstPaths |
|
{ |
|
# print $g_opt_src . "\n"; |
|
if( $g_opt_src =~ m/(\S+)\:(\S\:\S*)/ ) |
|
{ |
|
$g_src_is_local = 0; |
|
$g_src_machine = $1; |
|
$g_src_path = $2; |
|
} |
|
elsif( $g_opt_src =~ m/(\S+)\:(\/\/.*)/ ) |
|
{ |
|
# //gary://maxwell/common/ |
|
$g_src_is_local = 0; |
|
$g_src_machine = $1; |
|
$g_src_path = $2; |
|
} |
|
elsif( $g_opt_src =~ m/^(\S:.*)/ ) |
|
{ |
|
$g_src_is_local = 1; |
|
$g_src_path = $1; |
|
} |
|
else |
|
{ |
|
$g_src_is_local = 1; |
|
$g_src_path = $1; |
|
} |
|
|
|
if( $g_opt_dst =~ m/(\S+):(\S:\S+)/ ) |
|
{ |
|
$g_dst_is_local = 0; |
|
$g_dst_machine = $1; |
|
$g_dst_path = $2; |
|
} |
|
elsif( $g_opt_dst =~ m/^(\S:\S+)/ ) |
|
{ |
|
$g_dst_is_local = 1; |
|
$g_dst_path = $1; |
|
} |
|
else |
|
{ |
|
$g_dst_is_local = 1; |
|
$g_dst_path = $1; |
|
} |
|
|
|
if( $g_src_is_local ) |
|
{ |
|
die "my src directories not supported yet. . run the server on the other end.\n"; |
|
} |
|
if( !$g_dst_is_local ) |
|
{ |
|
die "remote dst directories not supported yet. . run the server on the other end.\n"; |
|
} |
|
if( $g_src_is_local == $g_dst_is_local ) |
|
{ |
|
die "src and dst on the same machine not supported. . use robocopy\n"; |
|
} |
|
|
|
&MakeDirHier( $g_dst_path ); |
|
|
|
&SetBaseDir( $g_dst_path ); |
|
|
|
if( $g_opt_verbose ) |
|
{ |
|
print "\n\$g_src_is_local = $g_src_is_local\n"; |
|
print "\$g_src_machine = $g_src_machine\n"; |
|
print "\$g_src_path = $g_src_path\n"; |
|
print "\$g_dst_is_local = $g_dst_is_local\n"; |
|
print "\$g_dst_machine = $g_dst_machine\n"; |
|
print "\$g_dst_path = $g_dst_path\n\n"; |
|
} |
|
} |
|
|
|
&ProcessCommandLine( @ARGV ); |
|
&ValidateOptions(); |
|
&PrintOptions(); |
|
|
|
if( $g_opt_server ) |
|
{ |
|
&RunServer(); |
|
exit; |
|
} |
|
|
|
&ParseSrcDstPaths(); |
|
|
|
sub GetRemoteDirList |
|
{ |
|
my( $sock ) = shift; |
|
my( $remotedirname ) = shift; |
|
if( $g_opt_md5 ) |
|
{ |
|
print $sock "dirlistmd5 $remotedirname\n"; |
|
} |
|
else |
|
{ |
|
print $sock "dirlist $remotedirname\n"; |
|
} |
|
my( $filename, $mtime, $mode, $size ); |
|
my $fileordir; |
|
# while( defined( $fileordir = <$sock> ) ) |
|
while( 1 ) |
|
{ |
|
$fileordir = <$sock>; |
|
die "Lost connection!!!" if !defined( $fileordir ); |
|
last if $fileordir=~ m/^\n$/; |
|
|
|
$filename = <$sock>; |
|
$mtime = <$sock>; |
|
$mode = <$sock>; |
|
$size = <$sock>; |
|
my $md5 = ""; |
|
if( ( $fileordir =~ /f/i ) && $g_opt_md5 ) |
|
{ |
|
$md5 = <$sock>; |
|
} |
|
if( 0 ) |
|
{ |
|
print "file: $filename"; |
|
print "fileordir: $fileordir"; |
|
print "mtime: $mtime"; |
|
print "mode: $mode"; |
|
print "size: $size\n"; |
|
} |
|
$filename =~ s/\n//; |
|
$mtime =~ s/\n//; |
|
$mode =~ s/\n//; |
|
$size =~ s/\n//; |
|
$md5 =~ s/\n//; |
|
if( $fileordir =~ m/d/ ) |
|
{ |
|
# print $filename . " is a dir\n"; |
|
$g_remote_isdir{$filename} = 1; |
|
} |
|
else |
|
{ |
|
$g_remote_isdir{$filename} = 0; |
|
} |
|
$g_remote_mtime{$filename} = $mtime; |
|
# print $g_remote_mtime{$filename}; |
|
$g_remote_mode{$filename} = $mode; |
|
$g_remote_size{$filename} = $size; |
|
if( $g_opt_md5 ) |
|
{ |
|
$g_remote_md5{$filename} = $md5; |
|
} |
|
} |
|
} |
|
|
|
sub GetLocalDirList_File |
|
{ |
|
my( $filename ) = shift; |
|
my( $isdir ) = -d $filename; |
|
if( $isdir ) |
|
{ |
|
if( $filename =~ m/\/\.$/ || # "." |
|
$filename =~ m/\/\.\.$/ ) # ".." |
|
{ |
|
return; |
|
} |
|
} |
|
|
|
if( $isdir ) |
|
{ |
|
GetLocalDirList_Dir( $filename ); |
|
} |
|
my( @statinfo ) = stat $filename; |
|
if( @statinfo ) |
|
{ |
|
my( $mtime ) = $statinfo[9]; |
|
my( $mode ) = $statinfo[2]; |
|
# my( $mtimestr ) = scalar( localtime( $mtime ) ); |
|
my( $size ) = $statinfo[7]; |
|
my( $filename_nobase ) = &RemoveBaseDir( $filename ); |
|
$g_local_isdir{$filename_nobase} = $isdir; |
|
$g_local_mtime{$filename_nobase} = $mtime; |
|
$g_local_mode{$filename_nobase} = sprintf "%o", $mode; |
|
$g_local_size{$filename_nobase} = $size; |
|
if( !$isdir && $g_opt_md5 ) |
|
{ |
|
$g_local_md5{$filename_nobase} = &GetMD5( $filename ); |
|
} |
|
} |
|
else |
|
{ |
|
print "CAN'T STAT $filename\n"; |
|
} |
|
} |
|
|
|
sub GetLocalDirList_Dir |
|
{ |
|
my( $dirname ) = shift; |
|
if( $dirname =~ m/\/\.$/ || # "." |
|
$dirname =~ m/\/\.\.$/ ) # ".." |
|
{ |
|
return; |
|
} |
|
|
|
local( *SRCDIR ); |
|
opendir SRCDIR, $dirname; |
|
my( @dir ) = readdir SRCDIR; |
|
closedir SRCDIR; |
|
|
|
my( $item ); |
|
while( $item = shift @dir ) |
|
{ |
|
&GetLocalDirList_File( $dirname . "/" . $item ); |
|
} |
|
} |
|
|
|
sub IsExcluded |
|
{ |
|
my( $filename ) = shift; |
|
my( $regexp ); |
|
foreach $regexp ( @g_opt_include ) |
|
{ |
|
if( eval "\$filename =~ $regexp" ) |
|
{ |
|
return 0; |
|
} |
|
} |
|
foreach $regexp ( @g_opt_exclude ) |
|
{ |
|
if( eval "\$filename =~ $regexp" ) |
|
{ |
|
if( defined( $g_local_mtime{$filename} ) ) |
|
{ |
|
if( $g_opt_deleteexcluded ) |
|
{ |
|
print "excluding and deleting $filename\n" if( $g_opt_verbose ); |
|
if( defined $g_remote_isdir{$filename} ) |
|
{ |
|
if( $g_remote_isdir{$filename} ) |
|
{ |
|
$g_dirs_to_delete{$filename} = 1; |
|
} |
|
else |
|
{ |
|
$g_files_to_delete{$filename} = 1; |
|
} |
|
} |
|
elsif( defined $g_local_isdir{$filename} ) |
|
{ |
|
if( $g_local_isdir{$filename} ) |
|
{ |
|
$g_dirs_to_delete{$filename} = 1; |
|
} |
|
else |
|
{ |
|
$g_files_to_delete{$filename} = 1; |
|
} |
|
} |
|
else |
|
{ |
|
die; |
|
} |
|
} |
|
else |
|
{ |
|
print "excluding but keeping my copy of $filename\n" if( $g_opt_verbose ); |
|
} |
|
} |
|
else |
|
{ |
|
print "excluding $filename, which doesn't exist locally\n" if( $g_opt_verbose ); |
|
} |
|
return 1; |
|
} |
|
} |
|
return 0; |
|
} |
|
|
|
sub CompareTime |
|
{ |
|
if( $g_opt_ignoretime ) |
|
{ |
|
return; |
|
} |
|
my( $filename ) = shift; |
|
# hack! ignore directories. . seems like robocopy does too. |
|
if( $g_remote_isdir{$filename} || $g_local_isdir{$filename} ) |
|
{ |
|
return; |
|
} |
|
|
|
if( defined( $g_alreadycomparedtime{$filename} ) ) |
|
{ |
|
return; |
|
} |
|
$g_alreadycomparedtime{$filename} = 1; |
|
|
|
# compare times |
|
my( $deltatime ) = $g_local_mtime{$filename} - $g_remote_mtime{$filename}; |
|
# print "g_remote_mtime: " . $g_remote_mtime{$filename} . "\n"; |
|
# print "g_local_mtime: " . $g_local_mtime{$filename} . "\n"; |
|
# print "deltatime: $deltatime\n"; |
|
if( !( ( $deltatime >= -$g_max_time_delta && $deltatime <= $g_max_time_delta ) || |
|
( $deltatime + 3600 >= -$g_max_time_delta && $deltatime + 3600 <= $g_max_time_delta ) || |
|
( $deltatime - 3600 >= -$g_max_time_delta && $deltatime - 3600 <= $g_max_time_delta ) ) ) |
|
{ |
|
$g_files_to_copy{$filename} = 1; |
|
if( $g_opt_verbose ) |
|
{ |
|
print "timedelta of $deltatime for $filename\n"; |
|
} |
|
} |
|
} |
|
|
|
sub ComparePermissions |
|
{ |
|
if( $g_opt_ignoreperms ) |
|
{ |
|
return; |
|
} |
|
my( $filename ) = shift; |
|
if( defined( $g_alreadycomparedpermissions{$filename} ) ) |
|
{ |
|
return; |
|
} |
|
$g_alreadycomparedpermissions{$filename} = 1; |
|
|
|
# compare permissions |
|
if( $g_remote_mode{$filename} != $g_local_mode{$filename} ) |
|
{ |
|
if( !$g_remote_isdir{$filename} ) |
|
{ |
|
$g_files_to_copy{$filename} = 1; |
|
} |
|
else |
|
{ |
|
MakeLocalFileAttribsMatchRemote( $filename ); |
|
} |
|
if( $g_opt_verbose ) |
|
{ |
|
printf "permissions different for $filename: %s %s\n", $g_remote_mode{$filename}, $g_local_mode{$filename}; |
|
} |
|
} |
|
} |
|
|
|
sub CompareSize |
|
{ |
|
if( $g_opt_ignoresize ) |
|
{ |
|
return; |
|
} |
|
my( $filename ) = shift; |
|
if( defined( $g_alreadycomparedsize{$filename} ) ) |
|
{ |
|
return; |
|
} |
|
$g_alreadycomparedsize{$filename} = 1; |
|
|
|
# compare permissions |
|
if( $g_remote_size{$filename} != $g_local_size{$filename} ) |
|
{ |
|
$g_files_to_copy{$filename} = 1; |
|
if( $g_opt_verbose ) |
|
{ |
|
printf "size different for $filename: %d!=%d\n", $g_remote_size{$filename}, $g_local_size{$filename}; |
|
} |
|
} |
|
} |
|
|
|
sub CompareMD5 |
|
{ |
|
if( !$g_opt_md5 ) |
|
{ |
|
return; |
|
} |
|
my( $filename ) = shift; |
|
if( defined( $g_alreadycomparedmd5{$filename} ) ) |
|
{ |
|
return; |
|
} |
|
$g_alreadycomparedmd5{$filename} = 1; |
|
|
|
# compare md5 |
|
if( $g_remote_md5{$filename} ne $g_local_md5{$filename} ) |
|
{ |
|
$g_files_to_copy{$filename} = 1; |
|
if( $g_opt_verbose ) |
|
{ |
|
printf "md5 different for $filename: %s!=%s\n", $g_remote_md5{$filename}, $g_local_md5{$filename}; |
|
} |
|
} |
|
else |
|
{ |
|
my( $diff ) = 0; |
|
my( $deltatime ) = $g_local_mtime{$filename} - $g_remote_mtime{$filename}; |
|
if( $g_remote_size{$filename} != $g_local_size{$filename} ) |
|
{ |
|
$diff = 1; |
|
print "size different\n"; |
|
} |
|
if( $g_remote_mode{$filename} != $g_local_mode{$filename} ) |
|
{ |
|
$diff = 1; |
|
print "mode different\n"; |
|
} |
|
if( !( ( $deltatime >= -$g_max_time_delta && $deltatime <= $g_max_time_delta ) || |
|
( $deltatime + 3600 >= -$g_max_time_delta && $deltatime + 3600 <= $g_max_time_delta ) || |
|
( $deltatime - 3600 >= -$g_max_time_delta && $deltatime - 3600 <= $g_max_time_delta ) ) ) |
|
{ |
|
$diff = 1; |
|
print "time different\n"; |
|
} |
|
if( $diff ) |
|
{ |
|
print "fixing up file attribs for $filename\n"; |
|
MakeLocalFileAttribsMatchRemote( $filename ); |
|
} |
|
} |
|
} |
|
|
|
#print "socket: PeerAddr: \"$g_src_machine\"\n"; |
|
my $sock = new IO::Socket::INET ( |
|
PeerAddr => $g_src_machine, |
|
PeerPort => $g_opt_port, |
|
Proto => 'tcp', |
|
); |
|
die "Could not create socket: $!\n" unless $sock; |
|
|
|
print $sock $g_protocol_version; |
|
my $remotedirname = $g_src_path; |
|
print "Getting remote dirlist for $remotedirname\n"; |
|
&GetRemoteDirList( $sock, $remotedirname ); |
|
|
|
my $localdirname = $g_dst_path; |
|
print "Getting local dirlist for $localdirname\n"; |
|
&GetLocalDirList_Dir( $localdirname ); |
|
|
|
my $remote_filename; |
|
foreach $remote_filename (keys %g_remote_mtime) |
|
{ |
|
next if( &IsExcluded( $remote_filename ) ); |
|
if( !defined( $g_local_mtime{$remote_filename} ) ) |
|
{ |
|
if( $g_remote_isdir{$remote_filename} ) |
|
{ |
|
# print "dir "; |
|
$g_dirs_to_create{$remote_filename} = 1; |
|
} |
|
else |
|
{ |
|
# print "file "; |
|
$g_files_to_copy{$remote_filename} = 1; |
|
} |
|
if( $g_opt_verbose ) |
|
{ |
|
print "doesn't exist locally and will be copied: $remote_filename\n"; |
|
} |
|
} |
|
else |
|
{ |
|
&CompareTime( $remote_filename ); |
|
&ComparePermissions( $remote_filename ); |
|
&CompareSize( $remote_filename ); |
|
&CompareMD5( $remote_filename ); |
|
} |
|
} |
|
|
|
my $local_filename; |
|
foreach $local_filename (keys %g_local_mtime) |
|
{ |
|
next if( &IsExcluded( $local_filename ) ); |
|
if( !defined( $g_remote_mtime{$local_filename} ) ) |
|
{ |
|
if( $g_local_isdir{$local_filename} ) |
|
{ |
|
$g_dirs_to_delete{$local_filename} = 1; |
|
# print "dir "; |
|
} |
|
else |
|
{ |
|
$g_files_to_delete{$local_filename} = 1; |
|
# print "file "; |
|
} |
|
# print $local_filename . " doesn't exist remotely\n"; |
|
} |
|
else |
|
{ |
|
&CompareTime( $local_filename ); |
|
&ComparePermissions( $local_filename ); |
|
&CompareSize( $local_filename ); |
|
} |
|
} |
|
|
|
#%g_files_to_delete; |
|
#%g_dirs_to_delete; |
|
#%g_files_to_copy; |
|
#%g_dirs_to_create; |
|
|
|
sub IsWritable |
|
{ |
|
my( $file ) = shift; |
|
my( $perms ) = oct( $g_local_mode{$file} ); |
|
if( $perms & 2 ) |
|
{ |
|
return 1; |
|
} |
|
else |
|
{ |
|
return 0; |
|
} |
|
} |
|
|
|
sub UnlinkFile |
|
{ |
|
my( $file ) = shift; |
|
|
|
print "del " . &ForwardToBackSlash( $file ) . "\n"; |
|
if( !$g_opt_test ) |
|
{ |
|
chmod 0666, $file || die; |
|
unlink $file || die; |
|
} |
|
} |
|
|
|
sub UnlinkDir |
|
{ |
|
my( $file ) = shift; |
|
|
|
print "rmdir " . &ForwardToBackSlash( $file ) . "\n"; |
|
if( !$g_opt_test ) |
|
{ |
|
chmod 0777, $file || die; |
|
if( !( rmdir $file ) ) |
|
{ |
|
print "Couldn't rmdir " . &ForwardToBackSlash( $file ) . "\n"; |
|
} |
|
} |
|
} |
|
|
|
sub DeleteOrphanFile |
|
{ |
|
my( $file ) = shift; |
|
# print "\$g_local_isdir{$file} = $g_local_isdir{$file}\n"; |
|
# print "DeleteOrphanFile( $file )\n"; |
|
my( $localfile ) = &AddBaseDir( $file ); |
|
my( $iswritable ) = &IsWritable( $file ); |
|
if( $iswritable ) |
|
{ |
|
if( $g_opt_deletewritable ) |
|
{ |
|
&UnlinkFile( &AddBaseDir( $file ) ); |
|
} |
|
else |
|
{ |
|
print "Would have deleted \"$file\" (use --delete-writable to delete)\n"; |
|
} |
|
} |
|
else |
|
{ |
|
if( $g_opt_deletereadonly ) |
|
{ |
|
&UnlinkFile( &AddBaseDir( $file ) ); |
|
} |
|
else |
|
{ |
|
print "Would have deleted \"$file\" (use --delete-readonly to delete)\n"; |
|
} |
|
} |
|
} |
|
|
|
sub DeleteOrphanDir |
|
{ |
|
my $dir = shift; |
|
my( $localdir ) = &AddBaseDir( $dir ); |
|
if( $g_opt_deletedirs ) |
|
{ |
|
&UnlinkDir( $localdir ); |
|
} |
|
else |
|
{ |
|
print "Would have deleted \"$dir\" (use --delete-dirs to delete)\n"; |
|
} |
|
} |
|
|
|
sub MakeLocalFileAttribsMatchRemote |
|
{ |
|
return if( $g_opt_test ); |
|
my( $file ) = shift; |
|
my( $localfilename ) = &AddBaseDir( $file ); |
|
|
|
# Make it writable so that we can tweak permissions/time. |
|
chmod 0666, $localfilename || die $!; |
|
|
|
# Set the time on the file to match the remote |
|
my( @statresult ); |
|
if( !( @statresult = stat $localfilename ) ) |
|
{ |
|
die "couldn't stat $localfilename locally\n"; |
|
} |
|
# @statresult[8] == access time. . we don't care to change this. |
|
utime $statresult[8], $g_remote_mtime{$file}, $localfilename || die $!; |
|
|
|
# Set the permissions on the file to match the remote |
|
chmod oct( $g_remote_mode{$file} ), $localfilename || die $!; |
|
} |
|
|
|
sub CopyFileFromRemote |
|
{ |
|
my( $file ) = shift; |
|
if( &IsWritable( $file ) && !$g_opt_clobberwritablenewer && $g_local_mtime{$file} > $g_remote_mtime{$file} ) |
|
{ |
|
print "Would have copied \"$file\" (use --clobber-writable-newer to copy)\n"; |
|
return; |
|
} |
|
print "copy $file from remote ($g_remote_size{$file} bytes)\n"; |
|
if( !$g_opt_test ) |
|
{ |
|
my( $localfilename ) = &AddBaseDir( $file ); |
|
# make the my version writable so that we can write over it, if it exists |
|
if( $g_local_mtime{$file} ) |
|
{ |
|
chmod 0666, $localfilename || die $!; |
|
} |
|
print $sock "getfile $file\n"; |
|
my( $size ) = $g_remote_size{$file}; |
|
my( $filebits ); |
|
# print "reading $file: $size\n"; |
|
my( $readsize ) = read $sock, $filebits, $size; |
|
if( $readsize != $size ) |
|
{ |
|
die "read size ($readsize) != expected size ($size) for $file\nYou either:\n\t1) lost your connection\n\t2) the remote file has changed since start of mccopy\n"; |
|
} |
|
|
|
# print "finished with $file\n"; |
|
local( *FILE ); |
|
# print "opening $localfilename\n"; |
|
open FILE, ">$localfilename" || die $!; |
|
# print "binmode $localfilename"; |
|
binmode( FILE ) || die $!; |
|
# print "after binmode $localfilename"; |
|
print FILE $filebits; |
|
close FILE || die $!; |
|
# print "closed $localfilename\n"; |
|
|
|
MakeLocalFileAttribsMatchRemote( $file ); |
|
} |
|
} |
|
|
|
sub PrettifyTime |
|
{ |
|
my( $inseconds ) = shift; |
|
my( $hours, $minutes, $seconds ); |
|
my( @blah ) = gmtime( $inseconds ); |
|
$hours = $blah[2]; |
|
$minutes = $blah[1]; |
|
$seconds = $blah[0]; |
|
return sprintf "%02d:%02d:%02d", $hours, $minutes, $seconds; |
|
} |
|
|
|
# remove my files that aren't on remote |
|
my $file; |
|
foreach $file (keys %g_files_to_delete) |
|
{ |
|
&DeleteOrphanFile( $file ); |
|
} |
|
|
|
# remove my dirs that aren't on remote |
|
my $dir; |
|
foreach $dir (sort { length $b <=> length $a } keys( %g_dirs_to_delete ) ) |
|
{ |
|
&DeleteOrphanDir( $dir ); |
|
} |
|
|
|
|
|
# create my dirs that are only on remote |
|
foreach $dir (sort { length $a <=> length $b } keys( %g_dirs_to_create) ) |
|
{ |
|
my( $localdir ) = &AddBaseDir( $dir ); |
|
print "mkdir $localdir, 0777\n"; |
|
if( !$g_opt_test ) |
|
{ |
|
mkdir $localdir, 0777 || die $!; |
|
} |
|
} |
|
|
|
# calculate the total size of files to transfer |
|
my $totalbytes = 0; |
|
foreach $file ( keys %g_files_to_copy ) |
|
{ |
|
$totalbytes += $g_remote_size{$file}; |
|
} |
|
|
|
print "$totalbytes bytes to copy\n"; |
|
|
|
# copy files from remote that are different |
|
my $bytescopied = 0; |
|
my $starttime = time; |
|
foreach $file (sort( keys %g_files_to_copy ) ) |
|
{ |
|
&CopyFileFromRemote( $file ); |
|
$bytescopied += $g_remote_size{$file}; |
|
my $curtime = time; |
|
my $deltatime = $curtime - $starttime; |
|
my $percentdone; |
|
if( $totalbytes ) |
|
{ |
|
$percentdone = ( $bytescopied * 1.0 ) / $totalbytes; |
|
} |
|
if( $totalbytes && $percentdone && $deltatime ) |
|
{ |
|
printf "progress: %.1f%% %s/%s %d bytes/sec\n", |
|
$percentdone * 100, |
|
&PrettifyTime( $deltatime ), |
|
&PrettifyTime( 1.0 / $percentdone * $deltatime ), |
|
$bytescopied / $deltatime; |
|
} |
|
} |
|
|
|
print "done!\n"; |
|
|
|
close($sock); |
|
|
|
|
|
|