#!/usr/bin/perl -w

##############################################
#
# aims2sync.pl - Database Server Sync. Daemon
# 
# Author: Dan Dengate <Dan.Dengate@CERN.CH>
#
# The daemon tries to maintain a persistant connection to the database.
# If no connection is present or one cannot be created, the daemon locks 
# (itself and the SOAP interfaces), informs the server administrators and 
# sleeps. When a connection has been restored, the above are actioned in 
# reverse.
#
# pxehost and pexboot media are maintained on the logic of anything that
# has changed in the last 30 seconds (since running) should be built.
#
# Bugs
#    - If a configuration value changes in the DB, the daemon must be restarted.
#
# FIXME:
#    - sig die catcher, so that $LOCK is removed.
#
##############################################

use strict;
use POSIX qw(setsid strftime);
use File::Path;
use IO::File;
use Digest::MD5;
use Data::Dumper;
use Date::Manip;
use Date::Parse;
use aims2server::conf;
use DBI;
use DBD::Oracle;

$|=0;

##############################################
# Catch icky-things.
##############################################
$SIG{'INT' } = 'interrupt';
$SIG{'HUP' } = 'interrupt';
$SIG{'ABRT'} = 'interrupt';
$SIG{'QUIT'} = 'interrupt';
$SIG{'TRAP'} = 'interrupt';
$SIG{'STOP'} = 'interrupt';
$SIG{'TERM'} = 'interrupt';
$SIG{__DIE__} = 'interrupt';

##############################################
my $TFTP_ROOT = my $TFTP_CONF = my $TFTP_BOOT = my $TFTP_ELILO = my $KSURL = "";
my $SERVER_ADMIN = "lxsoft-admins\@cern.ch";

##############################################
my $DELAY 	= 10;				# FIXME: $conf{'SYNC_DELAY'}....
my $PROGRAM	= "aims2sync"; 			# FIXME: $conf{'SYNC_NAME'} ? $conf{'SYNC_NAME'} : die "SYNC_NAME is undefined.\n";
my $LOCK	= "/data/internal/aims2/aims2sync.RUNNING"; 	# FIXME: $conf{'SYNC_LOCK'} ? $conf{'SYNC_LOCK'} :  die "SYNC_LOCK is undefined.\n";
my $LOG 	= "/var/log/aims2/aims2sync.log";    # FIXME: $conf{'SYNC_LOG'} ? $conf{'SYNC_LOG'} : die "SYNC_LOG is undefined.\n";
my $DBSTATUS	= "/data/internal/aims2/aims2.DBDOWN";		# FIXME: $conf{'SYNC_STATUS'}....

my $SYNCMASK    = $aims2conf::aims2sync_mask ? $aims2conf::aims2sync_mask : 0; # OK, so if we do not know who we are, we sync all the time ..
my $SYNCWHICH   = $aims2conf::aims2sync_which ? $aims2conf::aims2sync_which : 0; # OK, so if we do not know who we are, we sync all the time ..
my $FULLRAN    = 0;


###############################################
# some functions for re-done syncs: needs to be re-done again.. I'm afraid :-(
###############################################

sub interfacesynced {
   my ($configs,$interface) = @_;
   return 1 if ($SYNCWHICH == 1 && $configs->{$interface}{SYNCED1} eq 'Y');
   return 1 if ($SYNCWHICH == 2 && $configs->{$interface}{SYNCED2} eq 'Y'); 
   return 1 if ($SYNCWHICH == 3 && $configs->{$interface}{SYNCED3} eq 'Y');
# ok this is stupid but temporary for server migration
   return 1 if ($SYNCWHICH == 4 && $configs->{$interface}{SYNCED4} eq 'Y');
   return 1 if ($SYNCWHICH == 5 && $configs->{$interface}{SYNCED5} eq 'Y');
   return 1 if ($SYNCWHICH == 6 && $configs->{$interface}{SYNCED6} eq 'Y');

   return 0;
}

sub imagesynced {
   my ($pxeboot,$name) = @_;
   return 1 if ($SYNCWHICH == 1 && $pxeboot->{$name}{SYNCED1} eq 'Y');
   return 1 if ($SYNCWHICH == 2 && $pxeboot->{$name}{SYNCED2} eq 'Y');
   return 1 if ($SYNCWHICH == 3 && $pxeboot->{$name}{SYNCED3} eq 'Y');
# ok this is stupid but temporary for server migration
   return 1 if ($SYNCWHICH == 4 && $pxeboot->{$name}{SYNCED4} eq 'Y');
   return 1 if ($SYNCWHICH == 5 && $pxeboot->{$name}{SYNCED5} eq 'Y');
   return 1 if ($SYNCWHICH == 6 && $pxeboot->{$name}{SYNCED6} eq 'Y');
   return 0;
}

sub selsyncfield {
   return "synced1" if ($SYNCWHICH == 1);
   return "synced2" if ($SYNCWHICH == 2);
   return "synced3" if ($SYNCWHICH == 3);
# ok this is stupid but temporary for server migration
   return "synced4" if ($SYNCWHICH == 4);
   return "synced5" if ($SYNCWHICH == 5);
   return "synced6" if ($SYNCWHICH == 6);

   return "synced";
}

##############################################
sub insert0 {
##############################################
   # 7th July =~ 07th July :)
   my ($date) = shift;
   if ($date < 10) {
      return "0$date";
   } 
   return $date;
}

##############################################
sub getdate {
##############################################
   my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$iddst) = localtime(time);
   my $datestring;
   $year += 1900;
   $mon++;
   $mon  = insert0($mon);
   $mday = insert0($mday);
   $min  = insert0($min);
   $datestring = "$year-$mon-$mday $hour:$min";
   return($datestring);
}

##############################################
sub alog {
##############################################
   my ($logfile, $status) = @_;
   # FIXME: This is the date it was logged, not executed.. naughty.
   my $date = getdate();
   if (open(FILE, ">>$logfile")) {
      print FILE ("$date $status\n");
      close FILE;
   }
}

##############################################
sub interrupt {
##############################################
   # Handle things nicely when we croak.
   if(-e $DBSTATUS){
      unlink $DBSTATUS or groan("daemon killed but cannot remove $DBSTATUS : $!");
   }
   if(-e $LOCK){
      unlink $LOCK or groan("daemon killed but cannot remove $LOCK : $!");
   }
   alog($LOG, "caught @_, exiting.");
   printf STDOUT "caught @_ exiting\n";
   die "Exiting.\n";
}

##############################################
sub notify_server_admins
##############################################
{
   my $state = shift;
   open (SENDMAIL, "| /usr/sbin/sendmail -t -oi ");
   print SENDMAIL <<End_of_Mail;
To: $SERVER_ADMIN
Reply-to:$SERVER_ADMIN
Subject: aims2sync - $state
See the subject line.
End_of_Mail
   close(SENDMAIL);
   alog($LOG, "Notified server administrators.");
}


##############################################
sub db_sleep
##############################################
{
   # Go to sleep for a little bit.
   unless(-e $DBSTATUS){
      system("touch", "$DBSTATUS") == 0 or groan("cannot create $DBSTATUS: $!");
      alog($LOG, "lost contact with database.");
      notify_server_admins("Database connection failed... sleeping.");
   }
   sleep 180;
}

##############################################
sub db_connect
##############################################
{
   # Connect to the database. Once connected, return handle.
   # Note, we have to do are own error checking.   
   my $dbconn = $aims2conf::db_conn_string ? $aims2conf::db_conn_string : die "Error: Database server undefined.\n";
   my $dbuser = $aims2conf::db_user ? $aims2conf::db_user : die "Error: Database user undefined.\n";
   my $dbpass = $aims2conf::db_pass ? $aims2conf::db_pass : die "Error: Database password undefined.\n";
   
   # FIXME: Can we use db.pm? Not at the moment.
   my $dbh;
   until( $dbh = DBI->connect($dbconn, $dbuser, $dbpass, {PrintError => 0, RaiseError => 0, LongTruncOk => 0, LongReadLen => 300_000_000}) )
   {
      db_sleep;
   }
   return $dbh;
}

##############################################
sub groan
##############################################
{
   my $groan = shift;
   alog($LOG, "error: $groan");
   notify_server_admins("$groan");
}

##############################################
sub remove_dead_hosts
##############################################
{
   my($hosts) = @_;
   my @configs; 
   foreach my $hw (keys %$hosts){
      $hw =~ tr/[A-Z]/[a-z]/;
      push(@configs, "01-".$hw);
   }
   opendir(*pxedir, "$TFTP_ROOT$TFTP_CONF") or groan("cannot read contents of TFTP_CONF directory: $!");
      my @contents = grep m/^(01-)+?/ig, readdir *pxedir;
   closedir(*pxedir);
   my %removals = map {$_, 1} @configs;
   my @removal = grep {!$removals {$_}} @contents;
   foreach(@removal) {
      alog($LOG, "removing $_");
      my $file = "$TFTP_ROOT$TFTP_CONF/$_";
      unlink $file or groan("failed to remove $file: $!");
   }
}

##############################################
sub add_new_pxeboot
##############################################
{
   my($dbh, $pxeboot) = @_;
   opendir(*pxedir, "$TFTP_ROOT$TFTP_BOOT") or groan("cannot read contents of TFTP_BOOT directory: $!");
      my @contents = grep m/^\w+?/ig, readdir *pxedir; # FIXME: warning, no regex.
   closedir(*pxedir);
   my %content = map {$_,1} (@contents); 
   my @addons = map {$_} (keys %$pxeboot);
   my @addon = grep {!$content {$_}} @addons;
   foreach my $name (@addon) {
      build_pxeboot ($dbh , $name); 
   }

}

##############################################
sub remove_dead_pxeboot
##############################################
{
   my($pxeboot) = @_;
   opendir(*pxedir, "$TFTP_ROOT$TFTP_BOOT") or groan("cannot read contents of TFTP_BOOT directory: $!");
      my @contents = grep m/^\w+?/ig, readdir *pxedir; # FIXME: warning, no regex.
   closedir(*pxedir);
   my %removals = map {$_, 1} (keys %$pxeboot);
   my @removal = grep {!$removals {$_}} @contents;
   foreach(@removal) {
      alog($LOG, "removing pxeboot $_");
      my $file = "$TFTP_ROOT$TFTP_BOOT/$_";
      rmtree $file or groan("failed to remove $file: $!");
   }
}

##############################################
sub remove_config
##############################################
{
   my($interface, $config) = @_;
   $interface = "01-".lc($interface);
   if(-e "$TFTP_ROOT$TFTP_CONF/$interface"){
      alog($LOG, "removing pxe conf for $interface ($config->{HOSTNAME}).");
      unlink "$TFTP_ROOT$TFTP_CONF/$interface" or groan("cannot remove $interface: $!");
    }
}

##############################################
sub build_pxelinux
##############################################
{
   my($interface, $config) = @_;
   $interface =~ tr/[A-Z]/[a-z]/;
   my $target = $config->{TARGET};
   my $kopts = $config->{KOPTS};
   my $file = "$TFTP_ROOT$TFTP_CONF/01-$interface";
   my $build = new IO::File ">$file";
   if (defined $build) {
      print $build "default linux\n label linux\n";
      print $build " kernel boot/".$target."/vmlinuz\n";
      my $append = " append";
      # Is there an initrd defined?
            if( $config->{INITRD} ){
         $append = $append." initrd=boot/$target/initrd";
      }
      $append = $append." $kopts ks=$KSURL\n";
      print $build $append;
      print $build " ipappend 2\n";
      $build->close;
   }
   system("/bin/chown", "apache:apache", $file);
   alog($LOG, "built pxe conf for $interface ($config->{HOSTNAME}).");
}

##############################################
sub build_elilo
##############################################
{
   # FIXME: Can we have an elilo without an initrd.img?
   my($interface, $config) = @_;
   my $target = $config->{TARGET};
   my $kopts = $config->{KOPTS};
   $interface =~ tr/[A-Z]/[a-z]/;
   my $file = "$TFTP_ROOT$TFTP_CONF/01-$interface";
   my $build = new IO::File ">$file";
   if(defined $build){
      print $build "default=linux\n";
      print $build " image=/aims2prod/boot/$target/vmlinuz\n";
      print $build " label=linux\n read-only\n";
      print $build " initrd=/aims2prod/boot/$target/initrd\n";
      print $build " append=\"$kopts ks=$KSURL\"\n";
      $build->close;
   }
   system("/bin/chown", "apache:apache", $file);
   alog($LOG, "built elilo conf for $interface ($config->{HOSTNAME}).");
}

##############################################
sub build_localboot
##############################################
{
   my($interface, $config) = @_;
   $interface =~ tr/[A-Z]/[a-z]/;
   my $file = "$TFTP_ROOT$TFTP_CONF/01-$interface";
   my $build = new IO::File ">$file";
   if (defined $build) {
      print $build "serial 0\ndefault lboot\n label lboot\nlocalboot 0\n";
      $build->close;
   }
   system("/bin/chown", "apache:apache", $file);
   alog($LOG, "set $interface ($config->{HOSTNAME}) to localboot.");
}

##############################################
sub condense_kopts
##############################################
{
   # Condense PXEBOOT and PXEHOOST KOPTS into one string. Remove duplicates.
   my($host, $image) = @_;
   $host = "" if(!$host);
   $image = "" if(!$image);
   my @kopts = (split(" ",$image), split(" ",$host));
   my %repeat=();
   @kopts = grep {!$repeat{$_}++ } @kopts;
   my $kopts="";
   foreach(@kopts){
      $kopts = $kopts.$_." ";
   }
   $kopts;
}

##############################################
sub get_pxehosts {
##############################################
   
   my $dbh = shift;
   
   my %configs = ();
   
   # Define the SQL query.
   my $sql = <<EOSQL;
SELECT
   i.hw, h.hostname, h.status, t.arch, t.name, h.kopts, t.kopts, t.vmlinuz_source, t.initrd_source, 
   to_char(h.registered, 'yyyy/mm/dd hh24:mi:ss'), to_char(h.enabled, 'yyyy/mm/dd hh24:mi:ss'), 
   to_char(h.disabled, 'yyyy/mm/dd hh24:mi:ss'), h.synced, h.synced1, h.synced2, h.synced3, h.synced4, h.synced5, h.synced6,
   h.noexpiry
   
FROM
   hardware i,
   pxehosts h
LEFT OUTER JOIN
   pxeboot t
ON 
   h.target = t.name
WHERE (h.hostname = i.hostname)
EOSQL
   
   # Prepare to talk to the database.
   # Return ref(%config);
   eval {
      my $prepare = $dbh->prepare($sql) or groan("warning: failed to prepare host query.".$dbh->errstr);
         $prepare->execute();
         #$prepare->bind_columns(undef {});
      while( my($hw,@config) = $prepare->fetchrow_array ){
         (
            $configs{$hw}{HOSTNAME},
	    $configs{$hw}{STATUS},
	    $configs{$hw}{ARCH},
	    $configs{$hw}{TARGET},
	    $configs{$hw}{HOSTKOPTS},
	    $configs{$hw}{TARGETKOPTS},
	    $configs{$hw}{VMLINUZ},
	    $configs{$hw}{INITRD},
	    $configs{$hw}{REG},
	    $configs{$hw}{ON},
	    $configs{$hw}{OFF},
	    $configs{$hw}{SYNCED},
            $configs{$hw}{SYNCED1},
	    $configs{$hw}{SYNCED2},
	    $configs{$hw}{SYNCED3},
            $configs{$hw}{SYNCED4},
            $configs{$hw}{SYNCED5},
            $configs{$hw}{SYNCED6},
            $configs{$hw}{NOEXPIRY},

         ) = @config;
      }
   };
   if($@){
      if($@ =~ $dbh->err){
         groan("warning: failed to query for host changes. see logs.");
         alog($LOG, "Query failed: $@");
      }
   }
   \%configs;
}

##############################################
sub get_pxeboot_list
##############################################
{
   my $dbh = shift;
   my %pxeboot = ();
   my $sql = "SELECT name,to_char(uploaded, 'yyyy/mm/dd hh24:mi:ss'),synced,synced1,synced2,synced3,synced4,synced5,synced6 FROM pxeboot";
   eval {
      my $prepare = $dbh->prepare($sql) or groan("failed to prepare pxeboot query: ".$dbh->errstr);
         $prepare->execute();
      while( my($NAME,@meta) = $prepare->fetchrow_array ){
         (
	    $pxeboot{$NAME}{UPLOADED},
	    $pxeboot{$NAME}{SYNCED},
            $pxeboot{$NAME}{SYNCED1},
	    $pxeboot{$NAME}{SYNCED2},
	    $pxeboot{$NAME}{SYNCED3},
            $pxeboot{$NAME}{SYNCED4},
            $pxeboot{$NAME}{SYNCED5},
            $pxeboot{$NAME}{SYNCED6},

	 ) = @meta;
      }
   };
   if($@){
      if($@ =~ $dbh->err){
         groan("failed execution of pxeboot list query. see logs.");
         alog($LOG, "query failed: $@");
      }
   }
   \%pxeboot;
}

##############################################
sub download_pxeboot
##############################################
{
   my($dbh, $name) = @_;
   # Get pxeboot data.
   my @fields = qw(name vmlinuz_sum initrd_sum vmlinuz_file initrd_file);
   my $fields = join(",",@fields);
   my $sql = "SELECT $fields FROM pxeboot WHERE name = ?";
   my %files = ();
   eval {
      my $prepare = $dbh->prepare($sql) or groan("failed to prepare download of $name");
         $prepare->execute($name);
         $prepare->bind_columns(undef, map{\$_} @fields);
      while( my($NAME,@files) = $prepare->fetchrow_array ){
         (
            $files{$NAME}{VMLINUZ_SUM},
            $files{$NAME}{INITRD_SUM},
            $files{$NAME}{VMLINUZ_BLOB},
            $files{$NAME}{INITRD_BLOB},
         ) = @files;
      }
   };
   if($@){
      if($@ =~ $dbh->err){
         groan("Failed download of pxeboot $name. see logs");
         alog($LOG, "BLOB download failed for $name: $@");
      }
   }
   \%files;
}

##############################################
sub get_file_sum
##############################################
{
   # Get an md5sum from the file.
   my $file = shift;
   my $handle = new IO::File($file, "r");
   my $ident = Digest::MD5->new;
   while (<$handle>) {
      $ident->add($_);
   }
   my $sum = $ident->hexdigest;
   return $sum;
}

##############################################
sub write_file
##############################################
{
   my($file, $data, $sum) = @_;
   
   my $built = 0;
   
   # We may have already written the file ok the first time. Check.
   if(-e $file){
      my $existing = get_file_sum($file);
      if($existing eq $sum){
         alog($LOG, "skipping $file, already built");
         $built = 1;
      }
   }
   
   # If we haven't.
   unless($built > 0){
      my $build = new IO::File ">$file";
      if (defined $build) {
         print $build $data;
         $build->close;
      } else {
         groan("cannot write file $file : $!");
      }
   
      # Check the file built correctly.
      my $SUM = get_file_sum($file);
     
      unless($SUM eq $sum){
         groan("file mismatch with $file");
      }
   }
}

##############################################
sub build_pxeboot
##############################################
{
   my($dbh, $NAME) = @_;

   # Build the files.
   
   my $files = download_pxeboot($dbh, $NAME);
   alog($LOG,"downloaded $NAME");
   my $dir = "$TFTP_ROOT$TFTP_BOOT/$NAME";
   unless(-e $dir){
      mkdir $dir or groan("Cannot create directory for $NAME");
   }
   
   # Create the kernel.
   write_file("$dir/vmlinuz", $files->{$NAME}{VMLINUZ_BLOB}, $files->{$NAME}{VMLINUZ_SUM} ); 
   alog($LOG, "built vmlinuz for $NAME");
   
   # Create initrd.img (if present)
   if( $files->{$NAME}{INITRD_SUM} ){
      write_file("$dir/initrd", $files->{$NAME}{INITRD_BLOB}, $files->{$NAME}{INITRD_SUM} );
      alog($LOG, "built initrd.img for $NAME");
   }

}

sub dbupdate_host_status
{
 my ($dbh, $config,$state) = @_;
 my $sql="UPDATE pxehosts SET status = 2, disabled=CURRENT_TIMESTAMP where hostname = ?";

 eval {
  my $prepare = $dbh->prepare($sql);

  $prepare->execute($config->{HOSTNAME})
 };
 if($@){
      if($@ =~ $dbh->err){
         die "Database Error: Failed to set localboot status for host: $config->{HOSTNAME} $@.\n";
      }
   }
}


sub dbupdate_host_sync
{
 my ($dbh, $config,$state) = @_;
 my $sql="UPDATE pxehosts SET ".selsyncfield()." = ? where hostname = ?";

 eval {
  my $prepare = $dbh->prepare($sql);
  
  $prepare->execute($state,$config->{HOSTNAME})
 };
 if($@){
      if($@ =~ $dbh->err){
         die "Database Error: Failed to set sync state for host: $config->{HOSTNAME} $@.\n";
      }
   }
}

sub dbupdate_image_sync 
{
 my($dbh, $name, $syncval) = @_;
 my $sql="UPDATE pxeboot SET ".selsyncfield()." = ? where name = ?"; 
 eval {
   my $prepare = $dbh->prepare($sql);
   $prepare->execute('Y',$name);
 };
 if($@){
      if($@ =~ $dbh->err){
         die "Database Error: Failed to set sync state for image: $name $@.\n";
      }
   } 
}

#---------------------------------------------
# Start the daemon.
#---------------------------------------------
print "Starting daemon\n";

##############################################
unless(my $pid = fork()) {
##############################################
   exit if $pid;
   setsid;
   umask 0;
   if(-e $LOCK){
      die "exit: daemon is already running.\n";
   }
   system("touch", "$LOCK") == 0 or die "cannot lock $LOCK ($?)\n";
   alog($LOG, "Starting $PROGRAM");
   
   ##############################################
   # Connect to the database.
   ##############################################
   my $dbh = db_connect;   
   my $conf = aims2conf->new($dbh);
   $conf = $conf->get;
   
   # Define important configuration values.
   $TFTP_ROOT = $conf->{'TFTP_ROOT'} ? $conf->{'TFTP_ROOT'} : die "TFTP_ROOT is undefined.\n";
   $TFTP_CONF = $conf->{'TFTP_CONF'} ? $conf->{'TFTP_CONF'} : die "TFTP_CONF is undefined.\n";
   $TFTP_BOOT = $TFTP_ROOT.$conf->{'TFTP_BOOT'} ? $conf->{'TFTP_BOOT'} : die "TFTP_BOOT is undefined.\n";
   $TFTP_ELILO = $conf->{'TFTP_ELILO'} ? $conf->{'TFTP_ELILO'} : die "TFTP_ELILO is undefined.\n";
   $KSURL = $conf->{'KS_URL'} ? $conf->{'KS_URL'} : die "KS_URL is undefined.\n";
   
   while(1) {

      ##############################################
      # Check for a database connecton.
      ##############################################
      if(!$dbh->ping){
         alog($LOG, "ping db failed... trying to reconnect");
         until( $dbh = db_connect )
         {
            db_sleep;
         }   
      }
      
      ##############################################
      # The connection must have been restablished.
      ##############################################
      if(-e $DBSTATUS){
         alog($LOG, "reconnected to database.");
         unlink $DBSTATUS or groan("could not remove $DBSTATUS");
         notify_server_admins("Database connection restored.");
      }
      
      ##############################################
      # Get a list of pxehosts and pxeboot.
      ##############################################
      my $configs = get_pxehosts($dbh);    
      my $pxeboot = get_pxeboot_list($dbh); 
      my $nowtime = strftime "%s", localtime();
      # First clear the directory of junk.
      remove_dead_hosts($configs);
      
      # Well, sorry Dan, this is not very smart as method: 
      # what if it has not been running for some time ?
      ##############################################
      # sync margin (last 30 seconds)
      ##############################################
      #my $margin = localtime(time);
      #$margin = ParseDate( $margin );
      #$margin = DateCalc( $margin, "- 30 seconds"); # ;-)
      
      ##############################################
      # Build pxe configurations.
      ##############################################
      my $old_autocommit = $dbh->{AutoCommit};
      my $xtn_commit = 0;
      # $dbh->{AutoCommit} = 0;  # single xtn and commit at the end of the loop - this causes problems with too long update delays and inconsistencies ...
      $dbh->{AutoCommit} = 1;
      foreach my $interface (keys %$configs) {

         ##############################################
         # Expire devices after 24 hours
         # setting these to localboot
         ##############################################  
         if($configs->{$interface}{STATUS} != 2 && $configs->{$interface}{NOEXPIRY} != 1) {
          my $ondate=str2time($configs->{$interface}{ON});
          my $expire=$ondate + 86400;
          if (!defined($ondate) || ($nowtime > $expire)) {
           dbupdate_host_status ( $dbh, $configs->{$interface} );
           ## and force sync on next run
           dbupdate_host_sync( $dbh, $configs->{$interface},'N');
           ++$xtn_commit;
            next;
          }
         }
         ##############################################
         # Devices with no status are removed.
         ##############################################
	 # so are devices set to localboot
	 ##############################################
	 
         if($configs->{$interface}{STATUS} == 0 || 
	    $configs->{$interface}{STATUS} == 2) {
            remove_config( $interface, $configs->{$interface} );
            dbupdate_host_sync( $dbh, $configs->{$interface},'N');
            ++$xtn_commit;
            next;
         }

         # if our sync bit set, do nothing for this host
         next if (interfacesynced($configs, $interface) && $FULLRAN );
         
# We do not do it anymore: localboot (STATUS - 2) and off (STATUS - 0) are treated the same now.
# see above
         ##############################################
         # Devices to set to localboot.
         ##############################################
#         if($configs->{$interface}{STATUS} == 2){
#            build_localboot( $interface, $configs->{$interface} );
#            dbupdate_host_sync( $dbh, $configs->{$interface},'Y');
#            next;
#         }    
         
         ##############################################
         # Device to build configurations for.
         ##############################################
         if ($configs->{$interface}{STATUS} == 1) {
	    # Some important checks.
	    # TARGET
	    unless( $configs->{$interface}{TARGET} ){
               alog($LOG, "No TARGET defined for $configs->{$interface}{HOSTNAME}, skipping $interface.");
               next;
            }
            # ARCH 
            unless( $configs->{$interface}{ARCH} ){
               alog($LOG, "No ARCH defined for $configs->{$interface}{HOSTNAME}, skipping $interface.");
               next;
            }
            
            ##############################################
            # Condense anaconda options.
            ##############################################
            $configs->{$interface}->{KOPTS} = condense_kopts( $configs->{$interface}->{HOSTKOPTS}, $configs->{$interface}->{TARGETKOPTS});
            delete $configs->{$interface}->{HOSTKOPTS};
            delete $configs->{$interface}->{TARGETKOPTS};
            
            ##############################################
            # PXELINUX configuration.
            ##############################################
            if( grep { $configs->{$interface}->{ARCH} eq $_ } qw(i386 x86_64 i686)){
               build_pxelinux( $interface, $configs->{$interface} ); 
               dbupdate_host_sync( $dbh, $configs->{$interface},'Y');
               ++$xtn_commit;
               next;     
            }
            
            ##############################################
            # ELILO configuration.
            ##############################################
            if( grep { $configs->{$interface}->{ARCH} eq $_ } qw(ia32 ia64)){
               build_elilo( $interface, $configs->{$interface} );
               dbupdate_host_sync( $dbh, $configs->{$interface},'Y');
               ++$xtn_commit;
               next;     
            }   
         }
              
      }
      if (!$dbh->{AutoCommit} && $xtn_commit) {
         $dbh->commit or groan($dbh->errstr);
      }
      $dbh->{AutoCommit} = $old_autocommit;
      
      ##############################################
      # Remove junk from /boot/
      ##############################################
      #use Data::Dumper;
      #printf STDERR Dumper($pxeboot);
      remove_dead_pxeboot($pxeboot); # remove unwanted crap.
     
      ##############################################
      # Build new pxeboot media.
      ##############################################
      add_new_pxeboot( $dbh, $pxeboot); 
      
      ##############################################
      # Update existing pxeboot media.
      ##############################################
      foreach my $name (keys %$pxeboot){
      
         ##############################################
         # Is the image new? 
         ##############################################
         #my $uploaded = ParseDate( $pxeboot->{$name}{UPLOADED} );
         #$uploaded = Date_Cmp($margin,$uploaded);
         #unless($uploaded<0 || $uploaded==0) {
         #   next;
         #}
      
         next if (imagesynced($pxeboot,$name) && $FULLRAN);
         ##############################################
         # Build the media.
         ##############################################
         build_pxeboot( $dbh, $name );
         dbupdate_image_sync( $dbh, $name, $pxeboot->{$name}{SYNCED});   
         
      }
      
      # End of processing. 
      # Now sleep.
      $FULLRAN=1;
      sleep $DELAY;
      
   }
   # END.
   die "oops";
}
