#!/usr/bin/perl
#
#
# $Header: /home/pat/perl/RCS/mirror.pl,v 1.4 1993/04/27 18:31:36 pat Exp pat $
#
#
#  [ -h host ]    remote host to mirror
#  [ -m mailto ]  mail results to user $mailto
#  [ -l local ]   local directory prefix (i.e. /mirror)
#  [ -t tmpdir ]  specify directory for temporary files
#  [ -d ]         debug mode
#  [ -n ]         no delete mode; do not delete old files
#  [ -v ]         verbose mode; mainly for running mirror interactively
#

require 'getopts.pl';
require 'date.pl';

&Getopts('h:m:l:t:f:dnv');

$now = &date(time);

# set signal handlers just in case
$SIG{'INT'}  = 'sig_handler';
$SIG{'QUIT'} = 'sig_handler';

# need to have a remote host specified
unless ($opt_h) { die "no mirror host specified\n"; }

$rhost=$opt_h;			# remote host
chop($lhost = `hostname`);	# local host

# send results to someone
$mailto = $opt_m || 'root';

# local prefix for directory
$lpref = $opt_l || '/';

if ($lpref ne '' && ! -d $lpref)
{
    print STDERR "$lpref: mirror directory does not exist!\n";
    exit 1;
}

# set log file name
if (defined($opt_f)) { $log = $opt_f; }
if ($log)
{
    open(LOG,">>$log") ||
	die "mirror: could not open log file $log\n";;
}

# set the temp directory
$tmpdir = $opt_t || $ENV{'TMPDIR'} || "/tmp";
$tmp_rlist = "$tmpdir/.mirror_rlist.$$";
$tmp_llist = "$tmpdir/.mirror_llist.$$";

if ($opt_d)
{
    select(STDERR); $| = 1;       # make stderr unbuffered
    select(STDOUT); $| = 1;       # make stdout unbuffered

    print "remote host is ",$rhost,"\n";
    print "verbose mode is ",$opt_v ? "on" : "off","\n";
    print "debug mode is ",$opt_d ? "on" : "off","\n";
    print "nodelete mode is ",$opt_n ? "on" : "off","\n";
    print "mailto is ",$mailto,"\n";
    print "tmpdir is ",$tmpdir,"\n";
    print "log file is \"",$log,"\"\n";
    print "list of remote files is $tmp_rlist\n";
    print "database of local files is $tmp_llist\n";
    print "\n";
}

foreach $fs (@ARGV)		# loop over file systems
{
    @rfiles=();
    %lfiles=();

    # get a listing of everything on the remote directory

    open(RLIST,">$tmp_rlist") ||
	die "mirror: could not open temp file $tmp_rlist\n";
    
    $rpref='/net/'.$rhost;	# NFS prefix
    $rdir=$rpref.$fs;		# remote directory
    
    if ($opt_v) { print "listing $rdir\n"; }
    open(FIND,"find $rdir -print |");
    $n=0;
    while (<FIND>)
    {
	++$n;
	print RLIST $_;
    }

    if ($opt_v) { print "$rdir contains $n files\n"; }
    close(FIND); close(RLIST);

    $ldir=$lpref.$fs;
    if ( -d $ldir )
    {
	if ($opt_v) { print "listing $ldir\n"; }
	# get a listing of the corresponding local directory

	dbmopen(lfiles,$tmp_llist,0600) ||
	    die "mirror: could not open temporary dbm database ".
		$tmp_llist."\n";
	open(FIND,"find $ldir -print |");
	while (<FIND>)
	{
	    chop;
	    $lfiles{"$_"} = (stat($_))[9];
	}
	close(FIND);
    }
    else
    {
	if ($opt_d)
	{
	    print STDERR "$ldir doesn't exists; trying to create\n";
	}
	&mkdirheir($ldir,0755);
	$msg .= "\tcreated directory $ldir\n";
    }

    # close dbm file and reset
    dbmclose lfiles;
    undef %lfiles;
    # reopen dbm file
    dbmopen(lfiles,$tmp_llist,0600) ||
	die "mirror: could not open temporary dbm database $tmp_llist\n";    
    
    if ($opt_v) { print "reading back list of remote files\n"; }
    
    # now open the temp file for reading
    open(RLIST,"<$tmp_rlist");

    $n=0;
    # consider all of the files
    while ($rfile = <RLIST>)
    {
	++$n;
	chop($rfile);
	($file) = ($rfile =~ /^$rpref(.*)/); # strip off remote prefix
	$lfile=$lpref.$file;	# prepend local prefix
	
	($r_dev,$r_ino,$r_mode,$r_nlink,$r_uid,$r_gid,$r_rdev,$r_size,
	 $r_atime,$r_mtime,$r_ctime) = stat($rfile);

	if ($opt_d)
	{
##	    print STDERR " file = \"$file\"\n";
	    print STDERR "lfile = \"$lfile\"\n";
	    print STDERR "rfile = \"$rfile\"\n";
	}
	
	if (!$lfiles{$lfile})	# file does not exist locally
	{
	    if ($opt_d)
	    { print STDERR "$lfile does NOT already exist\n"; }
	    
	    if ( -l $rfile )	# file is a symbolic link
	    {
		$to = readlink($rfile);
		if ($to)
		{
#		    if ( -e $to ) # target exists
#		    {
			unless (symlink($to,$lfile))
			{
			    $msg .=
				"\tWARNING: symlink failed, $!; ".
				    "$lfile -> $to\n";
			}
			else
			{
			    utime($r_atime,$r_mtime,$lfile);
			    $msg .= "\tcreated symbolic link $lfile -> $to\n";
			}
#		    }
#		    else
#		    {
#			$msg .= "\tWARNING: $rfile points to nonexistant file "
#			    . "$to\n";
#		    }
		}
		else
		{
		    $msg .= "\tWARNING: null symlink from $rfile\n";
		}
	    }
	    elsif ( -d $rfile )	# need to create a new directory
	    {
		mkdir($lfile,$r_mode)
		    || warn "couldn't create $lfile at ",__LINE__,"\n";
		chown($r_uid,$lfile);
		utime($r_atime,$r_mtime,$lfile);
		$msg .= "\tcreated directory $lfile\n";
	    }
	    elsif ( -f $rfile )
	    {
		$cmd = "cp $rfile $lfile";
		system $cmd;
		if ($?>>8)
		{
		    print STDERR "error executing system command :\n";
		    print STDERR "\t\"$cmd\n";
		    $msg .= "\tERROR copying $rfile to $lfile\n";
		}
		else
		{
		    chown($r_uid,$lfile);
		    chmod($r_mode,$lfile);
		    utime($r_atime,$r_mtime,$lfile);
		    $msg .= "\tcopied $rfile to $lfile\n";
		}
	    }
	    else
	    {
		$msg .= "\tWARNING: don't know what to do with $rfile\n";
	    }
	}
	else			# file DOES exist on local system
	{
	    if ($opt_d)
	    {
		print STDERR "$lfile DOES already exist\n";
	    }

	    if ( -l $rfile )	# symbolic link
	    {
		$rfilep = readlink($rfile);
		if ( ! -l $lfile ) # hey, this isn't a link!
		{
		    $msg .=
			"\tWARNING: $lfile needs to be a link to $rfilep\n";
		    $junk = delete $lfiles{$lfile};
		    if (!$junk)
		    {
			$msg .=
			    "WARNING: associative delete of $lfile failed!\n";
			if ($opt_v)
			{
			    print "associative delete of $lfile failed!\n";
			}
		    }
		    next;
		}
		$lfilep = readlink($lfile);
		if ($lfilep eq $lfilep)	# links match, we're ok
		{
		    $junk = delete $lfiles{$lfile};
		    if (!$junk)
		    {
			$msg .=
			    "WARNING: associative delete of $lfile failed!\n";
			if ($opt_v)
			{
			    print "associative delete of $lfile failed!\n";
			}
		    }
		    next;
		}
		# else we need to fix the symlink
		unlink($lfile);
		symlink($rfilep,$lfile);
		utime($r_atime,$r_mtime,$lfile);
		$msg .= "\tfixed symbolic link; $lfile -> $rfilep\n";
		$junk = delete $lfiles{$lfile};
		if (!$junk)
		{
		    $msg .=
			"WARNING: associative delete of $lfile failed!\n";
		    if ($opt_v)
		    {
			print "associative delete of $lfile failed!\n";
		    }
		}
		next;
	    }

	    # if we got here, then the file in question is not a symlink
	    
	    ($l_dev,$l_ino,$l_mode,$l_nlink,$l_uid,$l_gid,$l_rdev,$l_size,
	     $l_atime,$l_mtime,$l_ctime) = stat($lfile);

	    if (($l_mtime == $r_mtime) && # modification times must match
		($l_size == $r_size)) # and sizes must match
	    {
		# we're OK; no action needed
		$junk = delete $lfiles{$lfile};
		if (!$junk)
		{
		    $msg .=
			"WARNING: associative delete of $lfile failed!\n";
		    if ($opt_v)
		    {
			print "associative delete of $lfile failed!\n";
		    }
		}
		next;
	    }
	    else
	    {
		if ( -f $rfile )	# regular file
		{
		    $cmd = "cp $rfile $lfile";
		    system $cmd;
		    if ($?>>8)
		    {
			print STDERR "error executing system command :\n";
			print STDERR "\t\"$cmd\n";
			$msg .= "\tERROR copying $rfile to $lfile\n";
		    }
		    else
		    {
			chown($r_uid,$lfile);
			chmod($r_mode,$lfile);
			utime($r_atime,$r_mtime,$lfile);
			$msg .= "\tcopied $rfile to $lfile\n";
		    }
		}
		elsif ( -d $rfile ) # directory
		{
		    ;		# don't worry about directories changing
		}
		else
		{
		    $msg .= "\tWARNING: don't know what to do with $rfile\n";
		}
	    }
	    # remove this file from the local list
	    $junk = delete $lfiles{$lfile};
	    if (!$junk)
	    {
		print STDERR
		    "WARNING: associative delete of \"$lfile\" failed!\n";
	    }
##	    reset lfiles;
	}
	
	if ($opt_d) { print STDERR "\n"; }
	
    }				# end of file loop
    close RLIST;

    # debug stuff
    if (%lfiles)
    {
	$msg .= "here's what's left in \%lfiles:\n\n";
	while (($file,$mtime) = each %lfiles)
	{
	    $msg .= "\t$file\n";
	}
    }
    
    # if not in "no delete" mode, toss old files
    unless ($opt_n)
    {
	if (%lfiles)		# there are leftover files
	{
	    if ($opt_d)
	    {
		print STDERR "associative array still contains: \n";
		while (($file,$mtime) = each %lfiles)
		{
		    print STDERR "\t$file\n";
		}
	    }
	    
	    while (($file,$mtime) = each %lfiles) {
		if ( ! -l $file && -d $file ) { next; } # save these for later
		unlink $file;
		$msg .= "\tdeleted $file\n";
		delete $lfiles{$lfile};
	    }

	    # at this point, we should only have directories left, if any
	    while (($file,$mtime) = each %lfiles) {
		rmdir $file;
		$msg .= "\tremoved directory $file\n";
		delete $lfiles{$lfile};
	    }
	}
    }
    else
    {
	$msg .= "\nthe following files do not appear on the remote host:\n\n";
	while (($file,$mtime) = each %lfiles) {
	    $msg .= "\t$file\n";
	}
    }

    # close the temp files
    unless ($opt_d)
    {
	close(RLIST);
	unlink($tmp_rlist);
	
	dbmclose(lfiles);
	unlink($tmp_llist.".dir",$tmp_llist.".pag");
    }
}


if ($opt_d || $opt_v)
{
    print STDERR $msg;
##    exit 0;
}

if ($log)			# need to clean this up
{
    $finish = &date(time);

    print LOG "mirror started at $now\n";
    if ($msg)
    {
	print LOG $msg;
    }
    else
    {
	print LOG "\tno changes made\n";
    }
    print LOG "mirror finished at $finish\n\n";
    close LOG;

}

# if there was output, mail it
if ($msg)
{
    open(MAIL,"| Mail -s \"mirror $ldir changes\" $mailto");
    print MAIL <<_EOF_;
    
	Local host: $lhost
	Remote host: $rhost

	The following changes were made as a result of mirror:

$msg

_EOF_
    
    close MAIL;	
}

exit 0;

#
# create a directory along with intermediate components
#
#

sub mkdirheir			# named for the X11 script
{
    local($dir,$mode)=@_;

    @d=split(m|/|,$dir);

    if ($dir =~ m|^/|)		# if absolute pathname then ...
    {
	shift(@d);
	$di='/';
    }
    while ($_ = shift(@d))	# loop over pathname components
    {
	$di .= $_.'/';
	if ( ! -d $di )
	{
	    mkdir($di,$mode) || return 1;
	}
    }
    0;
}

# generic signal handling routine
sub sig_handler
{
    local($sig) = @_;
    print STDERR "\nreceived SIG$sig; exiting\n";
    if ($msg)
    {
	print STDERR $msg;
    }
    exit 0;
}

# Local Variables:
# mode: perl
# End:

