#!/usr/bin/perl

#********************************************************************
#*
#* File: update_afs_cellservdb.pl
#* Author: Jim Doyle <jrd@bu.edu> 09-11-96
#* Description: 
#*         
#*       Usage:  update_afs_cellservdb.pl [-f]
#*
#*       Batch job for nightly update for AFS CellServDB file.
#*
#*       - Looks for new CellServDB in /afs/bu.edu/common/etc/CellServDB
#*       - Reliably updates local machine copy. Run 'fs newcell' for
#*         every cell which has new server location information.
#*       - Errors are logged to stderr and syslog daemon.notice level.
#*       - -f flag (or any command line argument) FORCES an update
#*         to occur regardless of modtime comparison between files.
#*
#*   Wacked by Stan Naymola to work on fnal.gov    7/25/00
#*   Wacked by Troy Dawson to correctly work with logger   8/8/00
#*
#*
#*
#*
#*
#********************************************************************
#* _COPYRIGHT_INSERT_
#********************************************************************
#* _RELEASE_INFORMATION_INSERT_
#********************************************************************
#*                     SOURCE CONTROL INFORMATION
#*
#* Repository: /afs/bu.edu/usr/it/jrd/src
#*     Module: afs_update_celldb
#*
#* 
#* $RCSFile$
#* $Revision: 1.4 $
#* $Name:  $
#*
#********************************************************************


###########################################################
#  CONSTANTS:     These are best not to change.
###########################################################

$CURRENT_CELLSERVDB="/usr/vice/etc/CellServDB";
$RELEASE_CELLSERVDB="/afs/fnal.gov/common/etc/CellServDB";
$OLD_CELLSERVDB="/usr/vice/etc/CellServDB.Old";
$NEW_CELLSERVDB="/usr/vice/etc/CellServDB.New";
$verbose = $ARGV[0] eq "-v" ? 1 : undef;

###########################################################
# Get local cell name
###########################################################

open (THISCELL, "/usr/vice/etc/ThisCell");
$thiscell = <THISCELL>;
$thiscell =~ s/\s+//g;

if ( $thiscell eq "" )
 {
    &logerr("FAILED: Cannot determine local cell name.\n");
    &logerr("        check /usr/vice/etc/ThisCell\n");
    exit(1);
 }

###########################################################
#
# Check to see if the master CellDB has been updated.
# If so, install it, otherwise, there is nothing else
# to do.
#
###########################################################

if ((-M $CURRENT_CELLSERVDB > -M $RELEASE_CELLSERVDB) || $ARGV[0] )
 {
   #
   # Release CellServDB is more up to date than local, copy it.
   # 
  
   &InstallNewCellServDB();
  
 }
else
 {
      # We have no work to do.

      exit(0);
 }

###########################################################
# Open and parse the new and old cellservdb.
# safety test. Make sure New cellservdb has an entry for
# the local cell.
###########################################################

%oldcelldb = &ParseCellServFile($OLD_CELLSERVDB);
%newcelldb = &ParseCellServFile($RELEASE_CELLSERVDB);

@oldcells = keys %oldcelldb;
@newcells = keys %newcelldb;

if ( ! $newcelldb{$thiscell} )
 {
    printf STDERR "FAILED. New celldb does not exist. \n";
    printf STDERR "        OR Newcelldb has no local cell entries\n";
    exit(1);
 }

###########################################################
# Diff the cell configurations, and issue FS newcell on 
# any cell entries that differ between old and new.
###########################################################

foreach $cell (@newcells)
 {
    $samecellinfo = &CompareIPAddrList($newcelldb{$cell}, 
	                              $oldcelldb{$cell});
    if ( $samecellinfo == 0 )
     {
       &FsNewCell($cell, $newcelldb{$cell});
     }
 }

exit 0;

###########################################################################
#
# FUNCTION:  CellArrayPtr ParseCellServFile( CellServDB )
#
# [ IN ] :  string CellServDB       filename of AFS CellServDB file
# [ RETURNS ]:  CellArray            Associative array returned containing
#                                    list cell <-> servers pairs.
#
###########################################################################
#
#   Parses an AFS CellServDB file and stores the data in an associative
#   arrayed keyed by cellname. The value of the array is a space separated
#   list of IP addresses of AFS database server hosts for that AFS cell.
#   The array is returned , rather than passed as an IN args by reference.
#
###########################################################################

sub ParseCellServFile {
local($CellServDB) = @_;
local($line, $serverlist, $cell, $server, %CellServ);

  open (CELLDB, "<$CellServDB");
  $serverlist = "";
  while ($line = <CELLDB>)
   {
     if (!($line =~ m/^>/))
      {
  
        #
        # add dbserver to serverlist of cell
        #

        ( $server ) = split(/\s+/, $line);

	if ($serverlist) { $serverlist .= " $server"; }
	else { $serverlist = $server; }
      }
     else
      {

        # 
        # Start new cell. Store the server list of the
        # previous cell to the assoc array, then start
        # collecting serverlist for the new cell.

	if ($cell) { $CellServ{$cell} = $serverlist; };

        $serverlist = "";

	$line =~ s/^>//;
	($cell) = split(/\s/, $line);

	
      }
   }
   close (CELLDB);
   return %CellServ;
}

###########################################################################
#
# FUNCTION:  CompareIPAddrList( serverlist1, serverlist2)
#
# [ IN ] :  string serverlist1      First List of server IP addresses
#           string serverlist2      Second List of server IP addresses
#
# [ RETURNS ]:  1 if the list of servers are identical.
#           0 if the lists differ, or if one of the lists is null.
#
###########################################################################
#
# Compares two lists of IP addresses. Returns (1) if and only if
# both strings contains exactly the same IP address strings, possible out
# of order.
#
###########################################################################

sub CompareIPAddrList {
local($srvlist1, $srvlist2) = @_;
local(@slist, @slist2, $found, $same, $i, $j);
 
 @slist1 = split(/ /, $srvlist1);
 @slist2 = split(/ /, $srvlist2);
 
  if ( $srvlist1 && $srvlist2 ) { $same = 1; }
  else { $same = 0 };

 $same = 1;

 for ($i=0; ($i < $#slist1 + 1) && $same; $i++ )
  {
      $found = 0;

   for ($j=0; $j < $#slist2 + 1; $j++ ) 
    {
      $found = $found || ( $slist1[$i] eq $slist2[$j] );
    }
      $same = $same && $found;
  }

  return $same;

}

###########################################################################
# 
#
###########################################################################

sub FsNewCell {
local ($cell, $serverlist) = @_;
local ($cmd, $ev);

if ($cell && $serverlist)
  {
    $cmd = sprintf ("/usr/afsws/bin/fs newcell -name %s -servers %s\n", 
	     $cell, $serverlist);

    print $cmd if $verbose;
    $ev = system($cmd);
    if ($ev / 256 > 0)
      {
	&logerr("FAILED command returned nonzero $cmd");
      }
     else
       {
	 system("logger -p daemon.notice -t afscellupdate '$cmd'");
        }
  }
 
}


###########################################################################
#  SUBROUTINE:  InstallNewCellServDB
###########################################################################
#
#  Transactionally copy a new CellServDB into place.
#
###########################################################################


sub InstallNewCellServDB {
local ($ev);

$ev = system ("cp -p $RELEASE_CELLSERVDB $NEW_CELLSERVDB");

if ( ($ev / 256) > 0)
  {
    &logerr("FAILED Problem copying the new cellservdb to local system.\n");
    exit(1);         
  }
else
  {

    # CellServDB.new is in place.
    # Roll-forward. If any step fails that could leave us
    # without a valid CellServDB in place, roll back to the
    # original (CellServDB.old) which is known to work!

    rename ($CURRENT_CELLSERVDB, $OLD_CELLSERVDB);

          ## try:

    if (rename ($NEW_CELLSERVDB, $CURRENT_CELLSERVDB) == 0)
      {
          ## abort/fail:

	&logerr("FAILED. Could not rename CellServDB.new. Rolling back.\n");
	rename ($OLD_CELLSERVDB, $CURRENT_CELLSERVDB);
        exit(1);
      }
          ## commit:
  }

}

###########################################################################
# FUNCTION: logerr
#
#    Logs a message to stderr and SYSLOG service (daemon.notice level)
###########################################################################

sub logerr {
local($msg) = @_;

   printf STDERR "%s\n", $msg;
   system("logger -p daemon.notice -t afscellupdate '$msg'");
}


