#! /usr/bin/perl # Copyright (c) 2016 Joey Schulze # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA use strict; use warnings; use File::Copy; use File::Path; use File::DirCompare; use Getopt::Long; my $opt_dry_run; my $opt_verbose; my $opt_source; my $opt_destination; my @mirrordirs; sub help { print < 1; if (!copy $src, $dst) { printf STDERR "copy %s %s failed\n", $src, $dst; return; } my @stat_src = stat $src; my @stat_dst = stat $dst; if ($stat_src[7] != $stat_dst[7]) { printf STDERR "copy %s %s failed (size mismatch)\n", $src, $dst; return; } utime $stat_src[9], $stat_src[9], $dst; } sub compare_process_item { my $a = shift; my $b = shift; if (! $b) { my $dst = $opt_destination . substr($a, length $opt_source); if (-d $a) { printf "mirror %s %s\n", $a, $dst if $opt_verbose; push @mirrordirs, [$a, $dst] unless $opt_dry_run; } else { printf "copy %s %s\n", $a, $dst if $opt_verbose; copy_file $a, $dst unless $opt_dry_run; } } elsif (! $a) { if (-d $b) { printf "rmdir %s\n", $b if $opt_verbose; rmtree $b unless $opt_dry_run; } else { printf "delete %s\n", $b if $opt_verbose; unlink $b unless $opt_dry_run; } } else { if (-f $a && -f $b) { printf "copy %s %s\n", $a, $b if $opt_verbose; copy_file $a, $b unless $opt_dry_run; } elsif (-d $b) { printf "rmdir %s\n", $b if $opt_verbose; rmtree $b unless $opt_dry_run; printf "copy %s %s\n", $a, $b if $opt_verbose; copy_file $a, $b unless $opt_dry_run; } elsif (-d $a) { printf "delete %s\n", $b if $opt_verbose; unlink $b unless $opt_dry_run; my $pos = rindex $b, '/'; my $relpath = substr($b, $pos); printf "mirror %s %s\n", $opt_source.$relpath, $opt_destination.$relpath if $opt_verbose; push @mirrordirs, [$opt_source.$relpath, $opt_destination.$relpath] unless $opt_dry_run; } } } sub mirror_directory { assert_directory $opt_destination; File::DirCompare->compare($opt_source, $opt_destination, \&compare_process_item); } sub mirror_directories { mirror_directory; while (my $item = shift @mirrordirs) { $opt_source = $item->[0]; $opt_destination = $item->[1]; mirror_directory; } } GetOptions ( 'help|h' => \&help, 'verbose|v' => \$opt_verbose, 'dry-run|dryrun' => \$opt_dry_run, ); die "Not enough parameters\n" unless scalar @ARGV == 2; $| = 1; $opt_source = $ARGV[0]; $opt_destination = $ARGV[1]; mirror_directories;