Team Fortress 2 Source Code as on 22/4/2020
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

  1. use strict;
  2. # todo: make sure and create the target directory if it doesn't exist.
  3. # todo: make this work either direction
  4. # todo: fix any case problems
  5. my $g_protocol_version = "mccopy1\n";
  6. my $g_opt_port = 7070;
  7. my $g_server_md5 = 0;
  8. use IO::Socket;
  9. sub GetMD5
  10. {
  11. my $filename = shift;
  12. $filename =~ s,/,\\,g;
  13. # print "$filename\n";
  14. print ".";
  15. my( $cmd ) = "md5.exe < \"$filename\"";
  16. # print $cmd . "\n";
  17. open MD5, "$cmd|";
  18. my $out = <MD5>;
  19. close MD5;
  20. $out =~ s/^.*\=\s+//;
  21. $out =~ s/\n//g;
  22. # print "'" . $out . "'" . "\n";
  23. return $out;
  24. }
  25. ################################################################################################
  26. # SERVER CODE
  27. ################################################################################################
  28. sub SendFileName
  29. {
  30. my( $sock ) = shift;
  31. my( $file ) = shift;
  32. my( $isdir ) = -d $file;
  33. if( $isdir )
  34. {
  35. if( $file =~ m/\/\.$/ || # "."
  36. $file =~ m/\/\.\.$/ ) # ".."
  37. {
  38. return;
  39. }
  40. }
  41. my( @statinfo ) = stat $file;
  42. if( @statinfo )
  43. {
  44. my( $mtime ) = $statinfo[9];
  45. my( $mode ) = $statinfo[2];
  46. # my( $mtimestr ) = scalar( localtime( $mtime ) );
  47. my( $size ) = $statinfo[7];
  48. if( $isdir )
  49. {
  50. print $sock "d\n";
  51. }
  52. else
  53. {
  54. print $sock "f\n";
  55. }
  56. print $sock &RemoveBaseDir( $file ) . "\n";
  57. print $sock $mtime . "\n";
  58. printf $sock "%o\n", $mode;
  59. print $sock $size . "\n";
  60. if( !$isdir && $g_server_md5 )
  61. {
  62. print $sock &GetMD5( $file ) . "\n";
  63. }
  64. # print $file . "\n";
  65. # print $mtime . "\n";
  66. # print $mode . "\n";
  67. # print $md5 . "\n";
  68. }
  69. else
  70. {
  71. print "CAN'T STAT $file\n";
  72. }
  73. if( $isdir )
  74. {
  75. SendDirName( $sock, $file );
  76. }
  77. }
  78. sub SendDirName
  79. {
  80. my( $sock ) = shift;
  81. my( $dirname ) = shift;
  82. if( $dirname =~ m/\/\.$/ || # "."
  83. $dirname =~ m/\/\.\.$/ ) # ".."
  84. {
  85. return;
  86. }
  87. local( *SRCDIR );
  88. opendir SRCDIR, $dirname;
  89. my( @dir ) = readdir SRCDIR;
  90. closedir SRCDIR;
  91. my( $item );
  92. while( $item = shift @dir )
  93. {
  94. &SendFileName( $sock, $dirname . "/" . $item );
  95. }
  96. }
  97. sub GetFile
  98. {
  99. my( $sock ) = shift;
  100. my( $filename ) = shift;
  101. my( $localfilename ) = &AddBaseDir( $filename );
  102. print "GetFile: $filename ($localfilename)\n";
  103. local( *FILE );
  104. open FILE, "<$localfilename";
  105. binmode( FILE );
  106. my( $filebits );
  107. seek FILE, 0, 2;
  108. my( $size ) = tell FILE;
  109. if( $size < 0 )
  110. {
  111. die "$filename \$size == $size\n";
  112. }
  113. seek FILE, 0, 0;
  114. read FILE, $filebits, $size;
  115. close FILE;
  116. # print "sending $filename: $size\n";
  117. print $sock $filebits;
  118. # print "finished sending $filename\n";
  119. }
  120. sub HandleCommand
  121. {
  122. my( $sock ) = shift;
  123. my( $cmd ) = shift;
  124. if( $cmd =~ m/dirlistmd5\s+(.*)$/ )
  125. {
  126. $g_server_md5 = 1;
  127. &SetBaseDir( $1 );
  128. &SendDirName( $sock, $1 );
  129. print $sock "\n"; # terminating newline to end reply
  130. }
  131. elsif( $cmd =~ m/dirlist\s+(.*)$/ )
  132. {
  133. $g_server_md5 = 0;
  134. &SetBaseDir( $1 );
  135. &SendDirName( $sock, $1 );
  136. print $sock "\n"; # terminating newline to end reply
  137. }
  138. elsif( $cmd =~ m/getfile\s+(.*)$/ )
  139. {
  140. &GetFile( $sock, $1 );
  141. }
  142. }
  143. sub RunServer
  144. {
  145. my( $hostname ) = `hostname`;
  146. # my( $hostname ) = "localhost";
  147. $hostname =~ s/\n//; # remove newlines
  148. my $sock = new IO::Socket::INET (
  149. LocalHost => $hostname,
  150. LocalPort => $g_opt_port,
  151. Proto => 'tcp',
  152. Listen => 1,
  153. Reuse => 1,
  154. );
  155. die "Could not create socket: $!\n" unless $sock;
  156. my( $clientnum ) = 0;
  157. while( 1 )
  158. {
  159. my $new_sock = $sock->accept();
  160. print "accept!\n";
  161. if( fork() == 0 )
  162. {
  163. print "$clientnum: opening connection...\n";
  164. my $version = <$new_sock>;
  165. if( $version ne $g_protocol_version )
  166. {
  167. die "wrong protocol version: server: $g_protocol_version client: %version\n";
  168. }
  169. my $command;
  170. while(defined($command = <$new_sock>))
  171. {
  172. print "$clientnum: command: $command";
  173. &HandleCommand( $new_sock, $command );
  174. print "$clientnum: done with $command";
  175. }
  176. print "$clientnum: closing connection...\n";
  177. close( $new_sock );
  178. exit;
  179. }
  180. $clientnum++;
  181. }
  182. close($sock); # never get here.
  183. }
  184. ################################################################################################
  185. # CLIENT CODE
  186. ################################################################################################
  187. # all options that we might care about from rsync:
  188. # -v, --verbose increase verbosity
  189. # -q, --quiet decrease verbosity
  190. # -c, --checksum always checksum
  191. # -a, --archive archive mode
  192. # -r, --recursive recurse into directories
  193. # -R, --relative use relative path names
  194. # -b, --backup make backups (default ~ suffix)
  195. # --backup-dir=DIR put backups in the specified directory
  196. # --suffix=SUFFIX override backup suffix
  197. # -u, --update update only (don't overwrite newer files)
  198. # -p, --perms preserve permissions
  199. # -t, --times preserve times
  200. # -n, --dry-run show what would have been transferred
  201. # --existing only update files that already exist
  202. # --delete delete files that don't exist on the sending side
  203. # --delete-excluded also delete excluded files on the receiving side
  204. # --delete-after delete after transferring, not before
  205. # --max-delete=NUM don't delete more than NUM files
  206. # --force force deletion of directories even if not empty
  207. # --timeout=TIME set IO timeout in seconds
  208. # -I, --ignore-times don't exclude files that match length and time
  209. # --size-only only use file size when determining if a file should be transferred
  210. # -T --temp-dir=DIR create temporary files in directory DIR
  211. # --compare-dest=DIR also compare destination files relative to DIR
  212. # -P equivalent to --partial --progress
  213. # -z, --compress compress file data
  214. # --exclude=PATTERN exclude files matching PATTERN
  215. # --exclude-from=FILE exclude patterns listed in FILE
  216. # --include=PATTERN don't exclude files matching PATTERN
  217. # --include-from=FILE don't exclude patterns listed in FILE
  218. # --version print version number
  219. # --daemon run as a rsync daemon
  220. # --address bind to the specified address
  221. # --stats give some file transfer stats
  222. # --progress show progress during transfer
  223. # -h, --help show this help screen
  224. # options that are actually implemented:
  225. #
  226. sub Usage
  227. {
  228. print "\n";
  229. print "Usage:\n";
  230. print "perl McCopy.pl --server\n";
  231. print "or\n";
  232. print "perl McCopy.pl [options] srcdir dstdir\n";
  233. print "\n";
  234. print "ie:\n";
  235. print "perl McCopy.pl --verbose --recursive remotemachine:u:/hl u:/hl.work\n";
  236. print "\n";
  237. print "where \"remotemachine\" is running a McCopy server\n";
  238. print "\n";
  239. print "options are:\n";
  240. print "--server run as server (ignores other options)\n";
  241. print "--port N (default 7070)\n";
  242. print "--verbose\n";
  243. print "--test don't actually copy or delete any files\n";
  244. print "--ignore-time don't use file mtimes as a criterion for file that need\n";
  245. print " to be copied.\n";
  246. print "--ignore-permissions don't use file permissions as a criterion for file that\n";
  247. print " need to be copied.\n";
  248. print "--ignore-size don't use file size as a criterion for file that need\n";
  249. print " to be copied.\n";
  250. print "--md5 Use md5 checksums\n";
  251. print "--recursive\n";
  252. print "--delete-readonly delete readonly files in dst\n";
  253. print " that don't exist in src\n";
  254. print "--delete-writable delete writable files in dst\n";
  255. print "--delete-dirs delete directories in dst that don't exist in src\n";
  256. print "--clobber-writable-newer\n";
  257. print " write over upon copy files that have been modified locally\n";
  258. print "--delete-excluded delete files on dst that are excluded using --exclude\n";
  259. print "--exclude exclude files containing perl regular expression, ie:\n";
  260. print " --exclude /.*release.*/i\n";
  261. print "--include override --exclude for files containing perl regular expression, ie:\n";
  262. print " --include /.*debughlp.*/i\n";
  263. print "--mirror same as --recursive --delete-readonly --delete-writable\n";
  264. print " --clobber-writable-newer --delete-excluded --delete-dirs\n";
  265. print "--mirror-safe same as --recursive --delete-readonly --delete-dirs\n";
  266. exit;
  267. }
  268. # default command line options
  269. my $g_opt_server = 0;
  270. my $g_opt_test = 0;
  271. my $g_opt_verbose = 0;
  272. my $g_opt_recursive = 0;
  273. my $g_opt_deletereadonly = 0;
  274. my $g_opt_deletewritable = 0;
  275. my $g_opt_clobberwritablenewer = 0;
  276. my $g_opt_deleteexcluded = 0;
  277. my $g_opt_deletedirs = 0;
  278. my $g_opt_ignoretime = 0;
  279. my $g_opt_ignoreperms = 0;
  280. my $g_opt_ignoresize = 0;
  281. my @g_opt_exclude;
  282. my @g_opt_include;
  283. my $g_opt_src;
  284. my $g_opt_dst;
  285. my $g_opt_md5;
  286. my $g_src_is_local;
  287. my $g_src_machine = "";
  288. my $g_src_path;
  289. my $g_dst_is_local;
  290. my $g_dst_machine = "";
  291. my $g_dst_path;
  292. # indexed by $filename
  293. my %g_remote_mtime;
  294. my %g_remote_mode;
  295. my %g_remote_isdir;
  296. my %g_remote_size;
  297. my %g_remote_md5;
  298. my %g_local_mtime;
  299. my %g_local_mode;
  300. my %g_local_isdir;
  301. my %g_local_size;
  302. my %g_local_md5;
  303. my %g_alreadycomparedtime;
  304. my %g_alreadycomparedpermissions;
  305. my %g_alreadycomparedsize;
  306. my %g_alreadycomparedmd5;
  307. my %g_files_to_delete;
  308. my %g_dirs_to_delete;
  309. my %g_files_to_copy;
  310. my %g_dirs_to_create;
  311. my $g_max_time_delta = 2; # in seconds
  312. my $g_basedir;
  313. sub BackToForwardSlash
  314. {
  315. my( $path ) = shift;
  316. $path =~ s,\\,/,g;
  317. return $path;
  318. }
  319. sub ForwardToBackSlash
  320. {
  321. my( $path ) = shift;
  322. $path =~ s,/,\\,g;
  323. return $path;
  324. }
  325. sub SetBaseDir
  326. {
  327. $g_basedir = shift;
  328. # print "\$g_basedir: $g_basedir\n";
  329. }
  330. sub RemoveFileName
  331. {
  332. my( $in ) = shift;
  333. $in = &BackToForwardSlash( $in );
  334. $in =~ s,/[^/]*$,,;
  335. return $in;
  336. }
  337. sub RemovePath
  338. {
  339. my( $in ) = shift;
  340. $in = &BackToForwardSlash( $in );
  341. $in =~ s,^(.*)/([^/]*)$,$2,;
  342. return $in;
  343. }
  344. sub MakeDirHier
  345. {
  346. my( $in ) = shift;
  347. # print "MakeDirHier( $in )\n";
  348. $in = &BackToForwardSlash( $in );
  349. my( @path );
  350. while( $in =~ m,/, ) # while $in still has a slash
  351. {
  352. my( $end ) = &RemovePath( $in );
  353. push @path, $end;
  354. # print $in . "\n";
  355. $in = &RemoveFileName( $in );
  356. }
  357. my( $i );
  358. my( $numelems ) = scalar( @path );
  359. my( $curpath );
  360. for( $i = $numelems - 1; $i >= 0; $i-- )
  361. {
  362. $curpath .= "/" . $path[$i];
  363. my( $dir ) = $in . $curpath;
  364. if( !stat $dir )
  365. {
  366. print "mkdir $dir\n";
  367. mkdir $dir, 0777;
  368. }
  369. }
  370. }
  371. sub RemoveBaseDir
  372. {
  373. my( $path ) = shift;
  374. # print "removebasedir: $path ";
  375. $path =~ s,^$g_basedir/,,;
  376. # print "$path\n";
  377. return $path;
  378. }
  379. sub AddBaseDir
  380. {
  381. my( $path ) = shift;
  382. return $g_basedir . "/" . $path;
  383. }
  384. sub FixPath
  385. {
  386. my( $path ) = shift;
  387. # backslash to forward slash
  388. $path =~ s,\\,/,g;
  389. # remove trailing slash
  390. $path =~ s,/$,,;
  391. return $path;
  392. }
  393. sub ProcessLongCommand
  394. {
  395. my( $cmd ) = shift;
  396. if( $cmd =~ m/--verbose/ )
  397. {
  398. $g_opt_verbose = 1;
  399. }
  400. elsif( $cmd =~ m/--server/ )
  401. {
  402. $g_opt_server = 1;
  403. }
  404. elsif( $cmd =~ m/--test/ )
  405. {
  406. $g_opt_test = 1;
  407. }
  408. elsif( $cmd =~ m/--ignore-time/ )
  409. {
  410. $g_opt_ignoretime = 1;
  411. }
  412. elsif( $cmd =~ m/--ignore-permissions/ )
  413. {
  414. $g_opt_ignoreperms = 1;
  415. }
  416. elsif( $cmd =~ m/--ignore-size/ )
  417. {
  418. $g_opt_ignoresize = 1;
  419. }
  420. elsif( $cmd =~ m/--md5/ )
  421. {
  422. $g_opt_md5 = 1;
  423. }
  424. elsif( $cmd =~ m/--recursive/ )
  425. {
  426. $g_opt_recursive = 1;
  427. }
  428. elsif( $cmd =~ m/--mirror-safe/ )
  429. {
  430. $g_opt_recursive = 1;
  431. $g_opt_deletereadonly = 1;
  432. $g_opt_deletewritable = 0;
  433. $g_opt_clobberwritablenewer = 0;
  434. $g_opt_deleteexcluded = 0;
  435. $g_opt_deletedirs = 1;
  436. }
  437. elsif( $cmd =~ m/--mirror/ )
  438. {
  439. $g_opt_recursive = 1;
  440. $g_opt_deletereadonly = 1;
  441. $g_opt_deletewritable = 1;
  442. $g_opt_clobberwritablenewer = 1;
  443. $g_opt_deleteexcluded = 1;
  444. $g_opt_deletedirs = 1;
  445. }
  446. elsif( $cmd =~ m/--delete-readonly/ )
  447. {
  448. $g_opt_deletereadonly = 1;
  449. }
  450. elsif( $cmd =~ m/--delete-writable/ )
  451. {
  452. $g_opt_deletewritable = 1;
  453. }
  454. elsif( $cmd =~ m/--clobber-writable-newer/ )
  455. {
  456. $g_opt_clobberwritablenewer = 1;
  457. }
  458. elsif( $cmd =~ m/--delete-excluded/ )
  459. {
  460. $g_opt_deleteexcluded = 1;
  461. }
  462. elsif( $cmd =~ m/--delete-dirs/ )
  463. {
  464. $g_opt_deletedirs = 1;
  465. }
  466. }
  467. sub ProcessCommandLine
  468. {
  469. my( $cmd );
  470. while( $cmd = shift )
  471. {
  472. # hack - special case for exclude since it has an argument
  473. if( $cmd =~ m/^--exclude/ )
  474. {
  475. push @g_opt_exclude, shift;
  476. }
  477. elsif( $cmd =~ m/^--include/ )
  478. {
  479. push @g_opt_include, shift;
  480. }
  481. elsif( $cmd =~ m/^--port/ )
  482. {
  483. $g_opt_port = shift;
  484. }
  485. elsif( $cmd =~ m/^--/ )
  486. {
  487. &ProcessLongCommand( $cmd );
  488. }
  489. elsif( $cmd =~ m/^-/ )
  490. {
  491. print "short command $cmd\n";
  492. }
  493. else
  494. {
  495. if( !defined( $g_opt_src ) )
  496. {
  497. $g_opt_src = &FixPath( $cmd );
  498. }
  499. elsif( !defined( $g_opt_dst ) )
  500. {
  501. $g_opt_dst = &FixPath( $cmd );
  502. }
  503. else
  504. {
  505. print "Don't understand $cmd\n";
  506. &Usage();
  507. }
  508. }
  509. }
  510. }
  511. sub PrintOptions
  512. {
  513. if( $g_opt_verbose )
  514. {
  515. print "\n";
  516. print "Options:\n";
  517. print "\$g_opt_src = $g_opt_src\n";
  518. print "\$g_opt_dst = $g_opt_dst\n";
  519. print "\$g_opt_test = $g_opt_test\n";
  520. print "\$g_opt_ignoretime = $g_opt_ignoretime\n";
  521. print "\$g_opt_ignoreperms = $g_opt_ignoreperms\n";
  522. print "\$g_opt_ignoresize = $g_opt_ignoresize\n";
  523. print "\$g_opt_verbose = $g_opt_verbose\n";
  524. print "\$g_opt_recursive = $g_opt_recursive\n";
  525. print "\$g_opt_deletereadonly = $g_opt_deletereadonly\n";
  526. print "\$g_opt_deletewritable = $g_opt_deletewritable\n";
  527. print "\$g_opt_clobberwritablenewer = $g_opt_clobberwritablenewer\n";
  528. print "\$g_opt_deleteexcluded = $g_opt_deleteexcluded\n";
  529. print "\@g_opt_exclude = @g_opt_exclude\n";
  530. print "\n";
  531. }
  532. }
  533. sub ValidateOptions
  534. {
  535. if( $g_opt_server )
  536. {
  537. return;
  538. }
  539. if( !defined( $g_opt_src ) )
  540. {
  541. print "src not defined\n";
  542. Usage();
  543. }
  544. if( !defined( $g_opt_dst ) )
  545. {
  546. print "dst not defined\n";
  547. Usage();
  548. }
  549. if( !$g_opt_recursive )
  550. {
  551. print "--recursive must be used. . non-recursive operation not supported\n";
  552. Usage();
  553. }
  554. }
  555. # src/dst looks like:
  556. # gary:u:/hl2/hl2
  557. # u:/hl2/hl2
  558. # /hl2/hl2
  559. sub ParseSrcDstPaths
  560. {
  561. # print $g_opt_src . "\n";
  562. if( $g_opt_src =~ m/(\S+)\:(\S\:\S*)/ )
  563. {
  564. $g_src_is_local = 0;
  565. $g_src_machine = $1;
  566. $g_src_path = $2;
  567. }
  568. elsif( $g_opt_src =~ m/(\S+)\:(\/\/.*)/ )
  569. {
  570. # //gary://maxwell/common/
  571. $g_src_is_local = 0;
  572. $g_src_machine = $1;
  573. $g_src_path = $2;
  574. }
  575. elsif( $g_opt_src =~ m/^(\S:.*)/ )
  576. {
  577. $g_src_is_local = 1;
  578. $g_src_path = $1;
  579. }
  580. else
  581. {
  582. $g_src_is_local = 1;
  583. $g_src_path = $1;
  584. }
  585. if( $g_opt_dst =~ m/(\S+):(\S:\S+)/ )
  586. {
  587. $g_dst_is_local = 0;
  588. $g_dst_machine = $1;
  589. $g_dst_path = $2;
  590. }
  591. elsif( $g_opt_dst =~ m/^(\S:\S+)/ )
  592. {
  593. $g_dst_is_local = 1;
  594. $g_dst_path = $1;
  595. }
  596. else
  597. {
  598. $g_dst_is_local = 1;
  599. $g_dst_path = $1;
  600. }
  601. if( $g_src_is_local )
  602. {
  603. die "my src directories not supported yet. . run the server on the other end.\n";
  604. }
  605. if( !$g_dst_is_local )
  606. {
  607. die "remote dst directories not supported yet. . run the server on the other end.\n";
  608. }
  609. if( $g_src_is_local == $g_dst_is_local )
  610. {
  611. die "src and dst on the same machine not supported. . use robocopy\n";
  612. }
  613. &MakeDirHier( $g_dst_path );
  614. &SetBaseDir( $g_dst_path );
  615. if( $g_opt_verbose )
  616. {
  617. print "\n\$g_src_is_local = $g_src_is_local\n";
  618. print "\$g_src_machine = $g_src_machine\n";
  619. print "\$g_src_path = $g_src_path\n";
  620. print "\$g_dst_is_local = $g_dst_is_local\n";
  621. print "\$g_dst_machine = $g_dst_machine\n";
  622. print "\$g_dst_path = $g_dst_path\n\n";
  623. }
  624. }
  625. &ProcessCommandLine( @ARGV );
  626. &ValidateOptions();
  627. &PrintOptions();
  628. if( $g_opt_server )
  629. {
  630. &RunServer();
  631. exit;
  632. }
  633. &ParseSrcDstPaths();
  634. sub GetRemoteDirList
  635. {
  636. my( $sock ) = shift;
  637. my( $remotedirname ) = shift;
  638. if( $g_opt_md5 )
  639. {
  640. print $sock "dirlistmd5 $remotedirname\n";
  641. }
  642. else
  643. {
  644. print $sock "dirlist $remotedirname\n";
  645. }
  646. my( $filename, $mtime, $mode, $size );
  647. my $fileordir;
  648. # while( defined( $fileordir = <$sock> ) )
  649. while( 1 )
  650. {
  651. $fileordir = <$sock>;
  652. die "Lost connection!!!" if !defined( $fileordir );
  653. last if $fileordir=~ m/^\n$/;
  654. $filename = <$sock>;
  655. $mtime = <$sock>;
  656. $mode = <$sock>;
  657. $size = <$sock>;
  658. my $md5 = "";
  659. if( ( $fileordir =~ /f/i ) && $g_opt_md5 )
  660. {
  661. $md5 = <$sock>;
  662. }
  663. if( 0 )
  664. {
  665. print "file: $filename";
  666. print "fileordir: $fileordir";
  667. print "mtime: $mtime";
  668. print "mode: $mode";
  669. print "size: $size\n";
  670. }
  671. $filename =~ s/\n//;
  672. $mtime =~ s/\n//;
  673. $mode =~ s/\n//;
  674. $size =~ s/\n//;
  675. $md5 =~ s/\n//;
  676. if( $fileordir =~ m/d/ )
  677. {
  678. # print $filename . " is a dir\n";
  679. $g_remote_isdir{$filename} = 1;
  680. }
  681. else
  682. {
  683. $g_remote_isdir{$filename} = 0;
  684. }
  685. $g_remote_mtime{$filename} = $mtime;
  686. # print $g_remote_mtime{$filename};
  687. $g_remote_mode{$filename} = $mode;
  688. $g_remote_size{$filename} = $size;
  689. if( $g_opt_md5 )
  690. {
  691. $g_remote_md5{$filename} = $md5;
  692. }
  693. }
  694. }
  695. sub GetLocalDirList_File
  696. {
  697. my( $filename ) = shift;
  698. my( $isdir ) = -d $filename;
  699. if( $isdir )
  700. {
  701. if( $filename =~ m/\/\.$/ || # "."
  702. $filename =~ m/\/\.\.$/ ) # ".."
  703. {
  704. return;
  705. }
  706. }
  707. if( $isdir )
  708. {
  709. GetLocalDirList_Dir( $filename );
  710. }
  711. my( @statinfo ) = stat $filename;
  712. if( @statinfo )
  713. {
  714. my( $mtime ) = $statinfo[9];
  715. my( $mode ) = $statinfo[2];
  716. # my( $mtimestr ) = scalar( localtime( $mtime ) );
  717. my( $size ) = $statinfo[7];
  718. my( $filename_nobase ) = &RemoveBaseDir( $filename );
  719. $g_local_isdir{$filename_nobase} = $isdir;
  720. $g_local_mtime{$filename_nobase} = $mtime;
  721. $g_local_mode{$filename_nobase} = sprintf "%o", $mode;
  722. $g_local_size{$filename_nobase} = $size;
  723. if( !$isdir && $g_opt_md5 )
  724. {
  725. $g_local_md5{$filename_nobase} = &GetMD5( $filename );
  726. }
  727. }
  728. else
  729. {
  730. print "CAN'T STAT $filename\n";
  731. }
  732. }
  733. sub GetLocalDirList_Dir
  734. {
  735. my( $dirname ) = shift;
  736. if( $dirname =~ m/\/\.$/ || # "."
  737. $dirname =~ m/\/\.\.$/ ) # ".."
  738. {
  739. return;
  740. }
  741. local( *SRCDIR );
  742. opendir SRCDIR, $dirname;
  743. my( @dir ) = readdir SRCDIR;
  744. closedir SRCDIR;
  745. my( $item );
  746. while( $item = shift @dir )
  747. {
  748. &GetLocalDirList_File( $dirname . "/" . $item );
  749. }
  750. }
  751. sub IsExcluded
  752. {
  753. my( $filename ) = shift;
  754. my( $regexp );
  755. foreach $regexp ( @g_opt_include )
  756. {
  757. if( eval "\$filename =~ $regexp" )
  758. {
  759. return 0;
  760. }
  761. }
  762. foreach $regexp ( @g_opt_exclude )
  763. {
  764. if( eval "\$filename =~ $regexp" )
  765. {
  766. if( defined( $g_local_mtime{$filename} ) )
  767. {
  768. if( $g_opt_deleteexcluded )
  769. {
  770. print "excluding and deleting $filename\n" if( $g_opt_verbose );
  771. if( defined $g_remote_isdir{$filename} )
  772. {
  773. if( $g_remote_isdir{$filename} )
  774. {
  775. $g_dirs_to_delete{$filename} = 1;
  776. }
  777. else
  778. {
  779. $g_files_to_delete{$filename} = 1;
  780. }
  781. }
  782. elsif( defined $g_local_isdir{$filename} )
  783. {
  784. if( $g_local_isdir{$filename} )
  785. {
  786. $g_dirs_to_delete{$filename} = 1;
  787. }
  788. else
  789. {
  790. $g_files_to_delete{$filename} = 1;
  791. }
  792. }
  793. else
  794. {
  795. die;
  796. }
  797. }
  798. else
  799. {
  800. print "excluding but keeping my copy of $filename\n" if( $g_opt_verbose );
  801. }
  802. }
  803. else
  804. {
  805. print "excluding $filename, which doesn't exist locally\n" if( $g_opt_verbose );
  806. }
  807. return 1;
  808. }
  809. }
  810. return 0;
  811. }
  812. sub CompareTime
  813. {
  814. if( $g_opt_ignoretime )
  815. {
  816. return;
  817. }
  818. my( $filename ) = shift;
  819. # hack! ignore directories. . seems like robocopy does too.
  820. if( $g_remote_isdir{$filename} || $g_local_isdir{$filename} )
  821. {
  822. return;
  823. }
  824. if( defined( $g_alreadycomparedtime{$filename} ) )
  825. {
  826. return;
  827. }
  828. $g_alreadycomparedtime{$filename} = 1;
  829. # compare times
  830. my( $deltatime ) = $g_local_mtime{$filename} - $g_remote_mtime{$filename};
  831. # print "g_remote_mtime: " . $g_remote_mtime{$filename} . "\n";
  832. # print "g_local_mtime: " . $g_local_mtime{$filename} . "\n";
  833. # print "deltatime: $deltatime\n";
  834. if( !( ( $deltatime >= -$g_max_time_delta && $deltatime <= $g_max_time_delta ) ||
  835. ( $deltatime + 3600 >= -$g_max_time_delta && $deltatime + 3600 <= $g_max_time_delta ) ||
  836. ( $deltatime - 3600 >= -$g_max_time_delta && $deltatime - 3600 <= $g_max_time_delta ) ) )
  837. {
  838. $g_files_to_copy{$filename} = 1;
  839. if( $g_opt_verbose )
  840. {
  841. print "timedelta of $deltatime for $filename\n";
  842. }
  843. }
  844. }
  845. sub ComparePermissions
  846. {
  847. if( $g_opt_ignoreperms )
  848. {
  849. return;
  850. }
  851. my( $filename ) = shift;
  852. if( defined( $g_alreadycomparedpermissions{$filename} ) )
  853. {
  854. return;
  855. }
  856. $g_alreadycomparedpermissions{$filename} = 1;
  857. # compare permissions
  858. if( $g_remote_mode{$filename} != $g_local_mode{$filename} )
  859. {
  860. if( !$g_remote_isdir{$filename} )
  861. {
  862. $g_files_to_copy{$filename} = 1;
  863. }
  864. else
  865. {
  866. MakeLocalFileAttribsMatchRemote( $filename );
  867. }
  868. if( $g_opt_verbose )
  869. {
  870. printf "permissions different for $filename: %s %s\n", $g_remote_mode{$filename}, $g_local_mode{$filename};
  871. }
  872. }
  873. }
  874. sub CompareSize
  875. {
  876. if( $g_opt_ignoresize )
  877. {
  878. return;
  879. }
  880. my( $filename ) = shift;
  881. if( defined( $g_alreadycomparedsize{$filename} ) )
  882. {
  883. return;
  884. }
  885. $g_alreadycomparedsize{$filename} = 1;
  886. # compare permissions
  887. if( $g_remote_size{$filename} != $g_local_size{$filename} )
  888. {
  889. $g_files_to_copy{$filename} = 1;
  890. if( $g_opt_verbose )
  891. {
  892. printf "size different for $filename: %d!=%d\n", $g_remote_size{$filename}, $g_local_size{$filename};
  893. }
  894. }
  895. }
  896. sub CompareMD5
  897. {
  898. if( !$g_opt_md5 )
  899. {
  900. return;
  901. }
  902. my( $filename ) = shift;
  903. if( defined( $g_alreadycomparedmd5{$filename} ) )
  904. {
  905. return;
  906. }
  907. $g_alreadycomparedmd5{$filename} = 1;
  908. # compare md5
  909. if( $g_remote_md5{$filename} ne $g_local_md5{$filename} )
  910. {
  911. $g_files_to_copy{$filename} = 1;
  912. if( $g_opt_verbose )
  913. {
  914. printf "md5 different for $filename: %s!=%s\n", $g_remote_md5{$filename}, $g_local_md5{$filename};
  915. }
  916. }
  917. else
  918. {
  919. my( $diff ) = 0;
  920. my( $deltatime ) = $g_local_mtime{$filename} - $g_remote_mtime{$filename};
  921. if( $g_remote_size{$filename} != $g_local_size{$filename} )
  922. {
  923. $diff = 1;
  924. print "size different\n";
  925. }
  926. if( $g_remote_mode{$filename} != $g_local_mode{$filename} )
  927. {
  928. $diff = 1;
  929. print "mode different\n";
  930. }
  931. if( !( ( $deltatime >= -$g_max_time_delta && $deltatime <= $g_max_time_delta ) ||
  932. ( $deltatime + 3600 >= -$g_max_time_delta && $deltatime + 3600 <= $g_max_time_delta ) ||
  933. ( $deltatime - 3600 >= -$g_max_time_delta && $deltatime - 3600 <= $g_max_time_delta ) ) )
  934. {
  935. $diff = 1;
  936. print "time different\n";
  937. }
  938. if( $diff )
  939. {
  940. print "fixing up file attribs for $filename\n";
  941. MakeLocalFileAttribsMatchRemote( $filename );
  942. }
  943. }
  944. }
  945. #print "socket: PeerAddr: \"$g_src_machine\"\n";
  946. my $sock = new IO::Socket::INET (
  947. PeerAddr => $g_src_machine,
  948. PeerPort => $g_opt_port,
  949. Proto => 'tcp',
  950. );
  951. die "Could not create socket: $!\n" unless $sock;
  952. print $sock $g_protocol_version;
  953. my $remotedirname = $g_src_path;
  954. print "Getting remote dirlist for $remotedirname\n";
  955. &GetRemoteDirList( $sock, $remotedirname );
  956. my $localdirname = $g_dst_path;
  957. print "Getting local dirlist for $localdirname\n";
  958. &GetLocalDirList_Dir( $localdirname );
  959. my $remote_filename;
  960. foreach $remote_filename (keys %g_remote_mtime)
  961. {
  962. next if( &IsExcluded( $remote_filename ) );
  963. if( !defined( $g_local_mtime{$remote_filename} ) )
  964. {
  965. if( $g_remote_isdir{$remote_filename} )
  966. {
  967. # print "dir ";
  968. $g_dirs_to_create{$remote_filename} = 1;
  969. }
  970. else
  971. {
  972. # print "file ";
  973. $g_files_to_copy{$remote_filename} = 1;
  974. }
  975. if( $g_opt_verbose )
  976. {
  977. print "doesn't exist locally and will be copied: $remote_filename\n";
  978. }
  979. }
  980. else
  981. {
  982. &CompareTime( $remote_filename );
  983. &ComparePermissions( $remote_filename );
  984. &CompareSize( $remote_filename );
  985. &CompareMD5( $remote_filename );
  986. }
  987. }
  988. my $local_filename;
  989. foreach $local_filename (keys %g_local_mtime)
  990. {
  991. next if( &IsExcluded( $local_filename ) );
  992. if( !defined( $g_remote_mtime{$local_filename} ) )
  993. {
  994. if( $g_local_isdir{$local_filename} )
  995. {
  996. $g_dirs_to_delete{$local_filename} = 1;
  997. # print "dir ";
  998. }
  999. else
  1000. {
  1001. $g_files_to_delete{$local_filename} = 1;
  1002. # print "file ";
  1003. }
  1004. # print $local_filename . " doesn't exist remotely\n";
  1005. }
  1006. else
  1007. {
  1008. &CompareTime( $local_filename );
  1009. &ComparePermissions( $local_filename );
  1010. &CompareSize( $local_filename );
  1011. }
  1012. }
  1013. #%g_files_to_delete;
  1014. #%g_dirs_to_delete;
  1015. #%g_files_to_copy;
  1016. #%g_dirs_to_create;
  1017. sub IsWritable
  1018. {
  1019. my( $file ) = shift;
  1020. my( $perms ) = oct( $g_local_mode{$file} );
  1021. if( $perms & 2 )
  1022. {
  1023. return 1;
  1024. }
  1025. else
  1026. {
  1027. return 0;
  1028. }
  1029. }
  1030. sub UnlinkFile
  1031. {
  1032. my( $file ) = shift;
  1033. print "del " . &ForwardToBackSlash( $file ) . "\n";
  1034. if( !$g_opt_test )
  1035. {
  1036. chmod 0666, $file || die;
  1037. unlink $file || die;
  1038. }
  1039. }
  1040. sub UnlinkDir
  1041. {
  1042. my( $file ) = shift;
  1043. print "rmdir " . &ForwardToBackSlash( $file ) . "\n";
  1044. if( !$g_opt_test )
  1045. {
  1046. chmod 0777, $file || die;
  1047. if( !( rmdir $file ) )
  1048. {
  1049. print "Couldn't rmdir " . &ForwardToBackSlash( $file ) . "\n";
  1050. }
  1051. }
  1052. }
  1053. sub DeleteOrphanFile
  1054. {
  1055. my( $file ) = shift;
  1056. # print "\$g_local_isdir{$file} = $g_local_isdir{$file}\n";
  1057. # print "DeleteOrphanFile( $file )\n";
  1058. my( $localfile ) = &AddBaseDir( $file );
  1059. my( $iswritable ) = &IsWritable( $file );
  1060. if( $iswritable )
  1061. {
  1062. if( $g_opt_deletewritable )
  1063. {
  1064. &UnlinkFile( &AddBaseDir( $file ) );
  1065. }
  1066. else
  1067. {
  1068. print "Would have deleted \"$file\" (use --delete-writable to delete)\n";
  1069. }
  1070. }
  1071. else
  1072. {
  1073. if( $g_opt_deletereadonly )
  1074. {
  1075. &UnlinkFile( &AddBaseDir( $file ) );
  1076. }
  1077. else
  1078. {
  1079. print "Would have deleted \"$file\" (use --delete-readonly to delete)\n";
  1080. }
  1081. }
  1082. }
  1083. sub DeleteOrphanDir
  1084. {
  1085. my $dir = shift;
  1086. my( $localdir ) = &AddBaseDir( $dir );
  1087. if( $g_opt_deletedirs )
  1088. {
  1089. &UnlinkDir( $localdir );
  1090. }
  1091. else
  1092. {
  1093. print "Would have deleted \"$dir\" (use --delete-dirs to delete)\n";
  1094. }
  1095. }
  1096. sub MakeLocalFileAttribsMatchRemote
  1097. {
  1098. return if( $g_opt_test );
  1099. my( $file ) = shift;
  1100. my( $localfilename ) = &AddBaseDir( $file );
  1101. # Make it writable so that we can tweak permissions/time.
  1102. chmod 0666, $localfilename || die $!;
  1103. # Set the time on the file to match the remote
  1104. my( @statresult );
  1105. if( !( @statresult = stat $localfilename ) )
  1106. {
  1107. die "couldn't stat $localfilename locally\n";
  1108. }
  1109. # @statresult[8] == access time. . we don't care to change this.
  1110. utime $statresult[8], $g_remote_mtime{$file}, $localfilename || die $!;
  1111. # Set the permissions on the file to match the remote
  1112. chmod oct( $g_remote_mode{$file} ), $localfilename || die $!;
  1113. }
  1114. sub CopyFileFromRemote
  1115. {
  1116. my( $file ) = shift;
  1117. if( &IsWritable( $file ) && !$g_opt_clobberwritablenewer && $g_local_mtime{$file} > $g_remote_mtime{$file} )
  1118. {
  1119. print "Would have copied \"$file\" (use --clobber-writable-newer to copy)\n";
  1120. return;
  1121. }
  1122. print "copy $file from remote ($g_remote_size{$file} bytes)\n";
  1123. if( !$g_opt_test )
  1124. {
  1125. my( $localfilename ) = &AddBaseDir( $file );
  1126. # make the my version writable so that we can write over it, if it exists
  1127. if( $g_local_mtime{$file} )
  1128. {
  1129. chmod 0666, $localfilename || die $!;
  1130. }
  1131. print $sock "getfile $file\n";
  1132. my( $size ) = $g_remote_size{$file};
  1133. my( $filebits );
  1134. # print "reading $file: $size\n";
  1135. my( $readsize ) = read $sock, $filebits, $size;
  1136. if( $readsize != $size )
  1137. {
  1138. 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";
  1139. }
  1140. # print "finished with $file\n";
  1141. local( *FILE );
  1142. # print "opening $localfilename\n";
  1143. open FILE, ">$localfilename" || die $!;
  1144. # print "binmode $localfilename";
  1145. binmode( FILE ) || die $!;
  1146. # print "after binmode $localfilename";
  1147. print FILE $filebits;
  1148. close FILE || die $!;
  1149. # print "closed $localfilename\n";
  1150. MakeLocalFileAttribsMatchRemote( $file );
  1151. }
  1152. }
  1153. sub PrettifyTime
  1154. {
  1155. my( $inseconds ) = shift;
  1156. my( $hours, $minutes, $seconds );
  1157. my( @blah ) = gmtime( $inseconds );
  1158. $hours = $blah[2];
  1159. $minutes = $blah[1];
  1160. $seconds = $blah[0];
  1161. return sprintf "%02d:%02d:%02d", $hours, $minutes, $seconds;
  1162. }
  1163. # remove my files that aren't on remote
  1164. my $file;
  1165. foreach $file (keys %g_files_to_delete)
  1166. {
  1167. &DeleteOrphanFile( $file );
  1168. }
  1169. # remove my dirs that aren't on remote
  1170. my $dir;
  1171. foreach $dir (sort { length $b <=> length $a } keys( %g_dirs_to_delete ) )
  1172. {
  1173. &DeleteOrphanDir( $dir );
  1174. }
  1175. # create my dirs that are only on remote
  1176. foreach $dir (sort { length $a <=> length $b } keys( %g_dirs_to_create) )
  1177. {
  1178. my( $localdir ) = &AddBaseDir( $dir );
  1179. print "mkdir $localdir, 0777\n";
  1180. if( !$g_opt_test )
  1181. {
  1182. mkdir $localdir, 0777 || die $!;
  1183. }
  1184. }
  1185. # calculate the total size of files to transfer
  1186. my $totalbytes = 0;
  1187. foreach $file ( keys %g_files_to_copy )
  1188. {
  1189. $totalbytes += $g_remote_size{$file};
  1190. }
  1191. print "$totalbytes bytes to copy\n";
  1192. # copy files from remote that are different
  1193. my $bytescopied = 0;
  1194. my $starttime = time;
  1195. foreach $file (sort( keys %g_files_to_copy ) )
  1196. {
  1197. &CopyFileFromRemote( $file );
  1198. $bytescopied += $g_remote_size{$file};
  1199. my $curtime = time;
  1200. my $deltatime = $curtime - $starttime;
  1201. my $percentdone;
  1202. if( $totalbytes )
  1203. {
  1204. $percentdone = ( $bytescopied * 1.0 ) / $totalbytes;
  1205. }
  1206. if( $totalbytes && $percentdone && $deltatime )
  1207. {
  1208. printf "progress: %.1f%% %s/%s %d bytes/sec\n",
  1209. $percentdone * 100,
  1210. &PrettifyTime( $deltatime ),
  1211. &PrettifyTime( 1.0 / $percentdone * $deltatime ),
  1212. $bytescopied / $deltatime;
  1213. }
  1214. }
  1215. print "done!\n";
  1216. close($sock);