#!/usr/bin/perl # Author: Atif Ghaffar # version 0.1 # You may find the later versions of this program at # http://atif.developer.ch use strict; BEGIN { my $usage=qq| Normal USAGE: ---------------- $0 directory [directory2] [directory3] ... [directoryN] To create a set of identical directories on the remote host if they dont exists ------------------------------------------------------------------------------- CREATE_DIRS=1 $0 directory [directory2] [directory3] ... [directoryN] To mirror all files to the remote host. This can be done as the initial setup. ------------------------------------------------------------------------------ INIT_MIRROR=1 $0 directory [directory2] [directory3] ... [directoryN] To have VERBOSE messages about what this script is doing -------------------------------------------------------- DEBUG=1 [INIT_MIRROR=1] [CREATE_DIRS=1] $0 directory [directory2] [directory3] ... [directoryN] \n|; if (@ARGV){ for (@ARGV){ unless (-d $_ && -e $_ ){ # check if the argument is a directory print "$_ is not a directory\n"; print $usage; exit; } } } else { # show the usage unless a directory is speocified print $usage; exit; } } use vars qw($directory $cmd $event $dir $file $filepath $dirname); #load some modules. use File::PathConvert qw(realpath); use File::Basename; use File::Find; use SGI::FAM; #start a fam object my $fam=new SGI::FAM; my $event; #define the rsh command. This could be rsh, ssh or whatever my $rsh="ssh -l root "; #define the rsync command with the flags that you want my $rsync="rsync -rlopgztC --delete -e 'ssh -l root' "; #define replica hosts separated by space my @replicaHosts=qw(host1 host2 host3); #fill up the @directories list my @directories; find(sub { -d && -e && push @directories, $File::Find::name; }, @ARGV); for (@directories){ #convert symlinks to realpath $directory=realpath($_); #get some stats about this directory my ($dev,$ino,$mode,$nlink,$uid,$gid) = stat($directory); $mode=sprintf "%04o", $mode & 07777; #create identical directories on replica hosts if environment variable CREATE_DIRS is set. if ($ENV{'CREATE_DIRS'} || $ENV{'INIT_MIRROR'}){ for my $host(@replicaHosts){ $cmd="$rsh $host 'mkdir -p $directory; chmod $mode $directory; chown $uid.$gid $directory'"; print "$cmd\n" if $ENV{'DEBUG'}; system ("$cmd 2>/dev/null"); } } print "setting monitor on $directory\n" if $ENV{'DEBUG'}; $fam->monitor(realpath($_)); } # if there is a request to initiate a mirror then lets do it. # directories must already have had been created above if ($ENV{'INIT_MIRROR'}){ for (@ARGV){ $directory=realpath($_); for my $host(@replicaHosts){ $cmd ="$rsync $directory $host:$directory"; system ("$cmd 2>/dev/null"); print "$cmd\n" if $ENV{'DEBUG'}; } } } # now running the main loop which will recieve events from fam # this should actually be forked into a background process. # for the timebeing you can run it with & # perhaps I will use POE at somepoint with this while (1) { do { $event=$fam->next_event; $dir=$fam->which($event); $file=$event->filename; #dont copy swap files next if $file=~/(\.swp|\~)$/; if ($dir eq $file){ $file=""; } #set correct filename. dir/file my $filepath="$dir/$file"; #remove multiple / from filepath $filepath=~s/\/+/\//g; #set dirname $dirname=dirname($filepath); # if there a change or create event then # rsync the file to the replica hosts if ($event->type =~/^(change|create)$/){ for my $host(@replicaHosts){ $cmd="$rsync $filepath $host:$dirname/"; print "$cmd\n" if $ENV{'DEBUG'}; system ("$cmd 2>&1 >/dev/null"); } } # if the file or directory is deleted # then delete it on the server too # This needs some testing if ($event->type =~/^(delete)$/){ for my $host(@replicaHosts){ if (-d $filepath){ $cmd="$rsh $host 'rm -rf $filepath'"; } else { $cmd="$rsh $host 'rm $filepath'"; } print "$cmd\n" if $ENV{'DEBUG'}; system ("$cmd 2>&1 >/dev/null"); } } } while $fam->pending; } __END__ For more info see perldoc SGI::FAM Man pages fam(1m) fam(3x) monitor(1)