#!/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,$TFTP_CONF,$TFTP_CONF_BIOS,$TFTP_CONF_UEFI,$TFTP_CONF_LGCY,$TFTP_BOOT,$TFTP_ELILO,$KSURL, $DNSMASQ_CONF_DIR) = "";
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	= "/mnt/data2/var/aims2sync.RUNNING"; 	# FIXME: $conf{'SYNC_LOCK'} ? $conf{'SYNC_LOCK'} :  die "SYNC_LOCK is undefined.\n";
my $LOG 	= "/mnt/data2/log/aims2sync.log";    # FIXME: $conf{'SYNC_LOG'} ? $conf{'SYNC_LOG'} : die "SYNC_LOG is undefined.\n";
my $DBSTATUS	= "/mnt/data2/var/aims2.DBDOWN";		# FIXME: $conf{'SYNC_STATUS'}....
my $FULLRAN    = 0;


##############################################
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 => 500_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) = @_;
 remove_dead_hosts_2($hosts,$TFTP_ROOT.$TFTP_CONF_BIOS,"bios",0);
 remove_dead_hosts_2($hosts,$TFTP_ROOT.$TFTP_CONF_UEFI,"uefi",1); 
 remove_dead_hosts_2($hosts,$TFTP_ROOT.$TFTP_CONF_LGCY,"lgcy",0);
 return remove_dead_hosts_2($hosts,$DNSMASQ_CONF_DIR,"lgcy",2); 
}


##############################################
sub remove_dead_hosts_2
##############################################
{
   my($hosts,$dir,$what,$type) = @_;
   my @configs; 

   my $pref="01-";
   my $suff="";

   if ($type == 2) {
     $suff=".lgcy";
   } elsif ($type == 1) {
     $pref="grub.cfg-01-";
   } 
  
   foreach my $hw (keys %$hosts){
      $hw =~ tr/[A-Z]/[a-z]/;
      push (@configs, $pref.$hw.$suff);
      }

   opendir(*pxedir, $dir) or groan("cannot read contents of $dir directory: $!");
   my @contents; 
   if ($type == 1) { 
     @contents = grep m/^(grub.cfg-01-)+?/ig, readdir *pxedir; 
   } else {
     @contents = grep m/^(01-)+?/ig, readdir *pxedir; 
   }
   closedir(*pxedir);
   my %removals = map {$_, 1} @configs;
   my $i=0;
   my @removal = grep {!$removals {$_}} @contents;

   foreach(@removal) {
      alog($LOG, "REM deadhost $_ [$what]");
      my $file = "$dir/$_";
      $i++;
      unlink $file or groan("failed to remove $file: $!");
   }

   return $i;
}

##############################################
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, $what) = @_;
   my $cfgfile;
   my $intf;

   $intf = "01-".lc($interface);
   $cfgfile = $TFTP_ROOT.$TFTP_CONF_LGCY."/".$intf;

   #die "D:\n$cfgfile:\n$config:\n$intf:$interface:\n";

   remove_config_2($cfgfile,$config->{HOSTNAME},$intf,"lgcy") if ($what != 2);

   $cfgfile = "$TFTP_ROOT$TFTP_CONF_BIOS/$intf";

   remove_config_2($cfgfile,$config->{HOSTNAME},$intf,"bios") if ($what != 0);

   my $intf2 = "grub.cfg-".$intf;
   $cfgfile="$TFTP_ROOT$TFTP_CONF_UEFI/$intf2";

   remove_config_2($cfgfile,$config->{HOSTNAME},$intf,"uefi") if ($what != 1);

}

##############################################
sub remove_config_2
##############################################
{
   my ($cfgfile,$hostname, $intf, $what)=@_;

   if ( -e $cfgfile){
      alog($LOG, "REM pxe conf for $intf ($hostname) [$what]");
      unlink $cfgfile or groan("cannot remove $cfgfile: $!");
   }   
}


##############################################
sub build_grub
##############################################
{
 my($cfgfile,$interface,$config)=@_;

 my $build = new IO::File ">$cfgfile";

 if (defined $build) {
 
   print $build "menuentry 'linux' {\n";
   print $build "linuxefi http://linuxsoft.cern.ch/internal/tftpboot/".$config->{TARGET}."/vmlinuz";
   my $append = " ks=$KSURL ".$config->{KOPTS};
   print $build $append."\n";
 
   if( $config->{INITRD} ){
     print $build "initrdefi http://linuxsoft.cern.ch/internal/tftpboot/".$config->{TARGET}."/initrd\n";
   }
   print $build "}\n";
   $build->close;
   }

  system("/bin/chown", "apache:apache", $cfgfile);
  alog($LOG, "ADD pxe conf for 01-$interface ($config->{HOSTNAME}) [uefi]");

}

##############################################
sub build_pxelinux
##############################################
{
  my($cfgfile,$interface,$config,$lgcy)=@_;
  my $prefix="http://linuxsoft.cern.ch/internal/tftpboot";
  my $boottype="bios";
  if ($lgcy) {
    $prefix="";
    $boottype="lgcy";
  }

  my $build = new IO::File ">$cfgfile";

  if (defined $build) {
     print $build "default linux\n label linux\n";
     print $build " kernel $prefix/aims/boot/".$config->{TARGET}."/vmlinuz\n";
     my $append = " append";
  # Is there an initrd defined?
    if( $config->{INITRD} ){
      $append = $append." initrd=$prefix/aims/boot/".$config->{TARGET}."/initrd";
    }
    $append = $append." ".$config->{KOPTS}." ks=$KSURL\n";
    print $build $append;
    print $build " ipappend 2\n";

    $build->close;
    }

   system("/bin/chown", "apache:apache", $cfgfile);
   alog($LOG, "ADD pxe conf for 01-$interface ($config->{HOSTNAME}) [$boottype]");
}

##############################################
sub build_dnsmasq
##############################################
{
   my ($interface,$config)=@_;
   $interface =~ tr/[A-Z]/[a-z]/;
   my $intf="01-".$interface;
   my $file=$DNSMASQ_CONF_DIR."/".$intf.".lgcy";

   if ( ! -e $file ) { 
     my $build = new IO::File ">$file";

     $interface =~ tr/-/:/;

     if (defined $build) {
       print $build "dhcp-mac=x86PClgcy,$interface\n";
       $build->close;
       alog($LOG, "ADD dnsmasq conf for $intf ($config->{HOSTNAME}) [lgcy]");
       return 1;
     }
  }
  return 0;
}

##############################################
sub remove_dnsmasq
##############################################
{
   my ($interface,$config)=@_;
   $interface =~ tr/[A-Z]/[a-z]/;
   my $intf="01-".$interface;
   my $file=$DNSMASQ_CONF_DIR."/".$intf.".lgcy";

   if (  -e $file ) {
     alog($LOG, "REM dnsmasq conf for $intf ($config->{HOSTNAME}) [lgcy]");
     unlink $file or groan("cannot remove $file: $!");
     return 1;
   }
  return 0;
}


##############################################
sub build_bootloader
##############################################
{
   my($interface, $config) = @_;
   $interface =~ tr/[A-Z]/[a-z]/;
   my $type = $config->{TYPE};
   my $lgcy = $config->{LGCY}; 
   my $file = "$TFTP_ROOT";
 
   $type = 0 if( !defined($type) );
   $lgcy = 0 if( !defined($lgcy) );

   if ($type == 0) {
       if ($lgcy == 1) {
         $file = $file.$TFTP_CONF_LGCY."/01-$interface";
         build_pxelinux($file,$interface,$config,$lgcy);
         remove_config ( $interface, $config, 2);
       } else {
         $file = $file.$TFTP_CONF_BIOS."/01-$interface";
         build_pxelinux($file,$interface,$config,$lgcy); 
         remove_config ( $interface, $config, 0);
       }
   } else {
        $file = $file.$TFTP_CONF_UEFI."/grub.cfg-01-$interface";
        build_grub($file,$interface,$config); 
        remove_config($interface, $config, 1);
   }
}

##############################################
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.noexpiry, h.type, h.lgcy, h.syncedlgcy 
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}{NOEXPIRY},
            $configs{$hw}{TYPE},
            $configs{$hw}{LGCY},
            $configs{$hw}{SYNCEDLGCY},

         ) = @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 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},

	 ) = @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 ($SUM <> $sum)");
      }
   }
}

##############################################
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_lgcy_sync
{
my ($dbh, $config,$state) = @_;
 my $sql="UPDATE pxehosts SET syncedlgcy = ? where hostname = ?";

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

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

sub dbupdate_host_sync
{
 my ($dbh, $config,$state) = @_;
 my $sql="UPDATE pxehosts SET synced = ? 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 synced = ? 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_ROOT = "/mnt/data1/tftpboot/aims2";
#   $TFTP_CONF = $conf->{'TFTP_CONF'} ? $conf->{'TFTP_CONF'} : die "TFTP_CONF is undefined.\n";
   $TFTP_CONF_BIOS = $conf->{'TFTP_CONF_BIOS'} ? $conf->{'TFTP_CONF_BIOS'} : die "TFTP_CONF_BIOS is undefined.\n";
   $TFTP_CONF_LGCY = $conf->{'TFTP_CONF_LGCY'} ? $conf->{'TFTP_CONF_LGCY'} : die "TFTP_CONF_LGCY is undefined.\n";
   $TFTP_CONF_UEFI = $conf->{'TFTP_CONF_UEFI'} ? $conf->{'TFTP_CONF_UEFI'} : die "TFTP_CONF_UEFI is undefined.\n"; 
   $DNSMASQ_CONF_DIR = $conf->{'DNSMASQ_CONF_DIR'} ? $conf->{'DNSMASQ_CONF_DIR'} : die "DNSMASQ_CONF_DIR is undefined.\n";
   #$TFTP_BOOT = $TFTP_ROOT.$conf->{'TFTP_BOOT'} ? $conf->{'TFTP_BOOT'} : die "TFTP_BOOT is undefined.\n";
   $TFTP_BOOT = "/boot";
   #$TFTP_ELILO = $conf->{'TFTP_ELILO'} ? $conf->{'TFTP_ELILO'} : die "TFTP_ELILO is undefined.\n";
   $TFTP_ELILO = "/aims2/";
   $KSURL = $conf->{'KS_URL'} ? $conf->{'KS_URL'} : die "KS_URL is undefined.\n";
   
   while(1) {

      my $lgcy = 0;

      ##############################################
      # 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.
      $lgcy+=remove_dead_hosts($configs);
      
      ##############################################
      # 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) {


         ##############################################
         # Set dnsmasq workaround
         # ############################################

            if(defined($configs->{$interface}{LGCY}) && $configs->{$interface}{LGCY} == 1) {
              if (!$FULLRAN || $configs->{$interface}{SYNCEDLGCY} eq 'N') {
                 $lgcy+=build_dnsmasq($interface,$configs->{$interface}); 
                 dbupdate_lgcy_sync($dbh, $configs->{$interface},'Y');  
                 ++$xtn_commit;
              }
            } else {
              if (!$FULLRAN || !defined($configs->{$interface}{SYNCEDLGCY}) || $configs->{$interface}{SYNCEDLGCY} eq 'N') {
                 $lgcy+=remove_dnsmasq($interface,$configs->{$interface});
                 dbupdate_lgcy_sync($dbh, $configs->{$interface},'Y');
                 ++$xtn_commit;
              }
            }
        
         ##############################################
         # Expire devices after 24 hours
         # setting these to localboot
         ##############################################  
         if(defined($configs->{$interface}{STATUS}) && $configs->{$interface}{STATUS} != 2 &&
            defined($configs->{$interface}{NOEXPIRY}) && $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(defined($configs->{$interface}{STATUS}) && 
            ($configs->{$interface}{STATUS} == 0 || 
	    $configs->{$interface}{STATUS} == 2)) {
            remove_config( $interface, $configs->{$interface}, -1);
            dbupdate_host_sync( $dbh, $configs->{$interface},'N');
            ++$xtn_commit;
            next;
         }

         # if our sync bit set, do nothing for this host
         next if (defined($configs->{$interface}{SYNCED}) && $configs->{$interface}{SYNCED} eq 'Y'  && $FULLRAN );
         
         ##############################################
         # Device to build configurations for.
         ##############################################
         if (defined($configs->{$interface}{STATUS}) && $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};
            
            ##############################################
            # BOOTLOADER configuration.
            ##############################################
            if( grep { $configs->{$interface}->{ARCH} eq $_ } qw(i386 x86_64 i686 aarch64)){
               build_bootloader( $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 ($pxeboot->{$name}{SYNCED} eq 'Y' && $FULLRAN);
         ##############################################
         # Build the media.
         ##############################################
         build_pxeboot( $dbh, $name );
         dbupdate_image_sync( $dbh, $name, $pxeboot->{$name}{SYNCED});   
         
      }
      
      # End of processing. 
      # Now sleep.

      if ($lgcy) {
       # notify dnsmasq to restart
       my $tmpf1 = new IO::File ">$DNSMASQ_CONF_DIR/dnsmasqprod-restart";
       my $tmpf2 = new IO::File ">$DNSMASQ_CONF_DIR/dnsmasqtest-restart";
       if (defined $tmpf1) {
         print $tmpf1 "restart"; 
         $tmpf1->close;
       }
       if (defined $tmpf2) {
         print $tmpf2 "restart";
         $tmpf2->close;
       }

       $lgcy=0; 
      }

      $FULLRAN=1;
      sleep $DELAY;
      
   }
   # END.
   die "oops";
}
