# Author: Atif Ghaffar <atif@developer.ch>
# version 0.1
# You may find the later versions of this program at
# http://atif.developer.ch

use strict;
	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]
	if (@ARGV){
		for (@ARGV){
			unless (-d $_ && -e $_ ){ # check if the argument is a directory
				print "$_ is not a directory\n"; 
				print $usage;
	} else {
		# show the usage unless a directory is speocified
		print $usage;
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
	#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.
		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'};

# if there is a request to initiate a mirror then lets do it.
# directories must already have had been created above
	for (@ARGV){
		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 {
		#dont copy swap files
		next if $file=~/(\.swp|\~)$/;
		if ($dir eq $file){

		#set correct filename. dir/file
		my $filepath="$dir/$file";

		#remove multiple / from filepath

		#set dirname

		# 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;


For more info see
Man pages