summaryrefslogtreecommitdiff
path: root/devtools/bin/McCopy.pl
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/bin/McCopy.pl')
-rw-r--r--devtools/bin/McCopy.pl1321
1 files changed, 1321 insertions, 0 deletions
diff --git a/devtools/bin/McCopy.pl b/devtools/bin/McCopy.pl
new file mode 100644
index 0000000..a5ddd2a
--- /dev/null
+++ b/devtools/bin/McCopy.pl
@@ -0,0 +1,1321 @@
+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);
+
+