#!/usr/bin/perl -w #=================================== mnm ===================================== # Merge and Mirror directories. # # Title: mnm # Description: Merge and Mirror source and destination repositories. # Programmed by: Dale Amon # Revised by: $Author: amon $ # Date: $Date: 2008-08-05 14:01:22 $ # Version: $Revision: 1.1.1.1 $ # # TODO * Should test the 2nd and 3rd columns to make sure # they are readable files if they are local. # * Which of course means we must determine if they # are remote. # #============================================================================== use strict; use Getopt::Long; use Pod::Usage; my %opts; my $PROCESS_NAME = "mnm"; #========================================================================== # Switch handling my ($HELP,$MAN, $TEST,$QUIET) = (0, 0, 0, 0); my $opts = Getopt::Long::Parser->new; $opts->getoptions ( 'test' => \$TEST, 'q|quiet' => \$QUIET, 'h|help' => \$HELP, 'man' => \$MAN ); pod2usage(1) if $HELP; pod2usage(-exitval => 0, -verbose => 2) if $MAN; #-------------------------------------------------------------------------- if ($#ARGV != 0 ) { pod2usage(-exitval => "NOEXIT", -verbose => 1); printf STDERR "$PROCESS_NAME must have a list file, eg.\n" . " $PROCESS_NAME /Admin/BackupList\n" . "Terminating...\n"; exit 1; } my ($LISTFILE) = @ARGV; my ($type,$key,$list,$pnum,$src,$dst, $delete,$id,$fromfile,$compress,$port,$ssh) = ("MERGE","NOKEY","ALL","STD",undef,undef,"","","","","",""); my (@rest); open LIST, "<$LISTFILE"; while ( ) { # skip for blank lines, comments next if (length == 0); next if (/^\s*#/); next if (/^\s*$/); my @line = split; if ($#line < 4) { print STDERR "Skipping mnm record with less than 5 arguments.\n"; next; } elsif ($#line == 4) { ($type,$key,$list,$src,$dst) = @line; print STDERR "Warning: 5 argument mnm record format found. Please" . " change to 6 argument form:\n $type $key $list STD $src $dst"; } elsif ($#line == 5) {($type,$key,$list,$pnum, $src,$dst) = @line;} else {($type,$key,$list,$pnum, $src,$dst,@rest) = @line;} if ($type eq "MERGE") {$delete="";} elsif ($type eq "MIRROR") {$delete="--delete";} else {next;} if ($pnum eq "STD") {$port = "";} else {$port = "-p $pnum"; $compress="z"} if ($key eq "NOKEY") {$id = "";} else {$id = "-i $key"; $compress="z"} if ($list eq "ALL") {$fromfile = "";} else {$fromfile = "--include-from=$list"} if ($port or $id) {$ssh = "--rsh=\"/usr/bin/ssh $id $port\""} # set up the rsync switches my $t = ($TEST) ? "n" : ""; my $q = ($QUIET) ? "" : "v"; my $sw = "-" . $t . "a" . $q; print "$type $src -> $dst\n"; system ("rsync --partial $delete $fromfile $ssh $sw" . "$compress $src $dst") == 0 || print "Failed to $type src -> $dst\n"; # This did not work well at all! # system ("rsync", "--partial", "$delete", "$fromfile", $ssh, $sw, "$src", "$dst") == 0 || print "Failed to $type src -> $dst\n"; } close LIST; #============================================================================== # POD DOCUMENTATION #============================================================================== # You may extract and format the documention section with the 'perldoc' cmd. =head1 NAME mnm - Merge and Mirror sources and archive/backup repositories =head1 SYNOPSIS mnm {-h | --help} {--man } (--test} {-q | --quiet} =head1 Description Read the backup list file one line at a time and for each line carry out an rsync of the source set to the destination repository. The lines carry information on whether to MIRROR or MERGE and whether NOKEY is required or a specified ssh identity is needed. Typical lines in that file would be: MIRROR NOKEY ALL STD /my/srcdir /my/dstdir MERGE /root/.ssh/id_key /Admin/list 2222 /my/archivedir root@foo.baz.com:/my/dstdir MIRROR means make the destination exactly like the source, which implies deletion of files which are at the destination but not in the source; MERGE means just shove files from source into the destination and leave the unmatched files at the destination alone. NOKEY means there is no identify key required; the alternative is the absolute path to an ssh private key to be used to initiate the connection. This is required for machine to machine communications so that password prompts will not occur. ALL means the entire tree of files in the source definition will be copied; otherwise the location should be a file using the rsync include-from syntax. STD means use the standard ssh port, tcp25. Otherwise a private ssh port number should be supplied. This is often useful for sessions port-forwarded through a firewall. The file name must contain quoting if there are meta characters in it, for instance, Air&Space would appear as: Air\&Space This command and the associated list file make it easy to move files all over the place, whether between local disks or between multiple machines over the world. Command switches are: --quiet Don't show what we are doing. The default is to have rsync -q report what files are affected as this is an archive support mechanism that requires a 'paper trail' of what is done. This switch is provided for those who do not care. --test Run in test mode. Rsync commands are executed but no changes are made to the file system. --man Print the pod documentation. --help Print program usage info. -h =head1 Examples None. =head1 Errors and Warnings None. =head1 KNOWN BUGS None. =head1 SEE ALSO rsync, ssh. =head1 AUTHOR Dale Amon =cut #============================================================================= # CVS HISTORY #============================================================================= # $Log: mnm,v $ # Revision 1.1.1.1 2008-08-05 14:01:22 amon # Merge and Mirror program # # 20080215 Dale Amon # Added full switch support. Added support for port # forwarding by allowing specification of an ssh port # in each record. Grandfathered the old 5 arg format. # 20060627 Dale Amon # Added support for include-from option. # 20060616 Dale Amon # Created. 1;