#!/usr/bin/perl -w

##############################################
#
# aims2sync.pl - Database Server Sync. Daemon
#
# Author: Jaroslaw Polok <jaroslaw.polok@cern.ch>
#         Dan Dengate <Dan.Dengate@CERN.CH>
#         Daniel Juarez <djuarezg@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.
#    - If code changes, the daemon must be restarted to take effect
#
# FIXME:
#    - sig die catcher, so that $LOCK is removed.
#
##############################################

use strict;
use POSIX qw(setsid strftime);
use Sys::Syslog qw(:standard :macros);
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;
use File::Find::Rule;

$|=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_CONF_ARM64, $DNSMASQ_CONF_DIR) = "";
my $SERVER_ADMIN = "aims-admins\@cern.ch";

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

my $PROGRAM	  = "aims2sync";
my $DELAY 	  = 10;
my $LOCK	    = "/var/lock/subsys/aims.RUNNING";
my $LOG 	    = "/var/log/aims/aims2sync.log";
my $DBSTATUS	= "/var/lock/subsys/aims.DBDOWN";
my $FULLRAN   = 0;
my $PIDFILE   = "/var/run/aims2sync.pid";
my $TFTP_ROOT = "/tftpboot/aims";
my $TFTP_CONF_BIOS = "/config/bios";
my $TFTP_CONF_LGCY = "/config/lgcy";
my $TFTP_CONF_UEFI = "/config/uefi";
my $TFTP_CONF_ARM64 = "/config/arm64";
my $DNSMASQ_CONF_DIR = "/etc/dnsmasq.d";
my $TFTP_BOOT = "/boot";
my $TMP_UPLOAD = "/tmp/aimshttpuploads";
# Retain uploaded files a maximum of X days before deleting them
my $UPLOAD_RETAIN_DAYS = 1;

my $SERVER    = $aims2conf::aims2_server ? $aims2conf::aims2_server : "aims.cern.ch";
my $KSURL     = "http://".$SERVER."/aims/ks";
my $SYNCWHICH = $aims2conf::aims2sync_which ? $aims2conf::aims2sync_which : 0;

# dont hammer db, we are maybe a test system ?
$DELAY = 120 if ($SYNCWHICH == 0);

##############################################
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');
  return 0;
}

##############################################
sub interfacelgcysynced {
##############################################
  my ($configs,$interface) = @_;
  return 1 if ($SYNCWHICH == 1 && defined($configs->{$interface}{SYNCEDLGCY1}) && $configs->{$interface}{SYNCEDLGCY1} eq 'Y');
  return 1 if ($SYNCWHICH == 2 && defined($configs->{$interface}{SYNCEDLGCY2}) && $configs->{$interface}{SYNCEDLGCY2} eq 'Y');
  return 1 if ($SYNCWHICH == 3 && defined($configs->{$interface}{SYNCEDLGCY3}) && $configs->{$interface}{SYNCEDLGCY3} 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');
  return 0;
}

##############################################
sub selsyncfield {
##############################################
  return "synced1" if ($SYNCWHICH == 1);
  return "synced2" if ($SYNCWHICH == 2);
  return "synced3" if ($SYNCWHICH == 3);
}

##############################################
sub selsynclgcyfield {
##############################################
  return "syncedlgcy1" if ($SYNCWHICH == 1);
  return "syncedlgcy2" if ($SYNCWHICH == 2);
  return "syncedlgcy3" if ($SYNCWHICH == 3);
}

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

  syslog("info",$status."\n");
}

##############################################
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 clean_tmp_uploads
##############################################
{
  my @files = File::Find::Rule->file()
                            ->maxdepth(1)
                            ->in($TMP_UPLOAD);
  for my $file (@files){
    if (-M $file > $UPLOAD_RETAIN_DAYS){
      alog($LOG, "Deleting HTTP tmp file: $file");
      unlink $file or warn $!;
    }
  }
}

##############################################
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.");
    #do not anymore .. we have .. monitoring ? ...
    #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 => 1_000_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_ARM64,"arm64",2);
 remove_dead_hosts_2($hosts,$TFTP_ROOT.$TFTP_CONF_LGCY,"lgcy",0);
 return remove_dead_hosts_2($hosts,$DNSMASQ_CONF_DIR,"lgcy",3);
}


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

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

  if ($type == 3) {
    $suff=".lgcy";
  } elsif ($type == 1 || $type == 2) {
    $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 || $type == 2) {
    @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 != 3);

  $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);

  $cfgfile="$TFTP_ROOT$TFTP_CONF_ARM64/$intf2";

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

}

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

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


 if (defined $build) {

  if (defined($config->{ARCH}) &&
              $config->{ARCH} == 'aarch64') {
    $efisuff="";
    $what="arm64";
  }

  # RHEL6/SLC6 kernels cannot be booted with linuxefi (no EFI stub in kernel)
  if (defined($config->{TARGET}) &&
             ($config->{TARGET} =~ /^SLC6/ ||
              $config->{TARGET} =~ /^RHEL6/ ||
              $config->{TARGET} =~ /^RHEL_6/)) {
    $efisuff="";
  }

  print $build "set default='linux'\n";
  print $build "set timeout=0\n";
  print $build "insmod progress\n";
  print $build "menuentry 'linux' {\n";
  print $build "clear\n";
  print $build "echo -n 'loading vmlinuz ...'\n";

  print $build "linux".$efisuff." (http)/aims/boot/".$config->{TARGET}."/vmlinuz";
  # BOOTIF does not work with 7 ? ...
  #my $append = " BOOTIF=\${net_default_mac} ks=$KSURL ".$config->{KOPTS};
  my $append = " ip=dhcp ks=$KSURL ".$config->{KOPTS};
  print $build $append."\n";

  if( $config->{INITRD} ){
    print $build "clear\n";
    print $build "echo -n 'loading initrd ...'\n";
    print $build "initrd".$efisuff." (http)/aims/boot/".$config->{TARGET}."/initrd\n";
  }
  print $build "}\n";
  $build->close;
  }

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

}

##############################################
sub build_pxelinux
##############################################
{
  my($cfgfile,$interface,$config,$lgcy)=@_;
  my $prefix="http://".$SERVER;
  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, 3);
      } else {
        $file = $file.$TFTP_CONF_BIOS."/01-$interface";
        build_pxelinux($file,$interface,$config,$lgcy);
        remove_config ( $interface, $config, 0);
      }
  } elsif ($type == 1) {
       $file = $file.$TFTP_CONF_UEFI."/grub.cfg-01-$interface";
       build_grub($file,$interface,$config,"uefi");
       remove_config($interface, $config, 1);
  } else {
       $file = $file.$TFTP_CONF_ARM64."/grub.cfg-01-$interface";
       build_grub($file,$interface,$config,"arm64");
       remove_config($interface, $config, 2);
  }
}

##############################################
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.synced1, h.synced2, h.synced3,
  h.noexpiry, h.type, h.lgcy, h.syncedlgcy1, h.syncedlgcy2, h.syncedlgcy3
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}{SYNCED1},
    $configs{$hw}{SYNCED2},
    $configs{$hw}{SYNCED3},
    $configs{$hw}{NOEXPIRY},
    $configs{$hw}{TYPE},
    $configs{$hw}{LGCY},
    $configs{$hw}{SYNCEDLGCY1},
    $configs{$hw}{SYNCEDLGCY2},
    $configs{$hw}{SYNCEDLGCY3},
        ) = @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'), synced1,synced2,synced3 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}{SYNCED1},
    $pxeboot{$NAME}{SYNCED2},
    $pxeboot{$NAME}{SYNCED3},

	 ) = @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) = @_;

  return if ($SYNCWHICH ==0);
 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) = @_;

  return if ($SYNCWHICH ==0);

  my $sql="UPDATE pxehosts SET ".selsynclgcyfield()." = ? where hostname = ?";
# 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) = @_;

 return if ($SYNCWHICH ==0);
 my $sql ="UPDATE pxehosts SET ".selsyncfield()." = ? where hostname = ?";
 #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) = @_;

 return if ($SYNCWHICH ==0);

 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; WHY ?

# add this
#  my $fh;
#  if (open( $fh, "<", $PIDFILE )) {
#    ( $pid ) = <$fh>; chomp $pid; close $fh;
#    if (kill 0, $pid) {
#      die "$0 is already running.\n";
#    }
#  }

  openlog("aims2sync","ndelay,nofatal","local3");

  system("touch", "$LOCK") == 0 or die "cannot lock $LOCK ($?)\n";
  alog($LOG, "Starting $PROGRAM");
  system("echo $$ > $PIDFILE") == 0 or die "cannot write $PIDFILE ($?)\n";

  ##############################################
  # Connect to the database.
  ##############################################
  my $dbh = db_connect;

  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 || !interfacelgcysynced($configs, $interface)) {
                $lgcy+=build_dnsmasq($interface,$configs->{$interface});
                dbupdate_lgcy_sync($dbh, $configs->{$interface},'Y');
                ++$xtn_commit;
             }
           } else {
             if (!$FULLRAN || !interfacelgcysynced($configs, $interface)) {
                $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 (interfacesynced($configs, $interface) && $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;
           }
        }

     }
     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.

     ##############################################
     # Remove old files from temporary http uploads
     ##############################################
     clean_tmp_uploads(); # 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, 'Y');

     }

     # End of processing.
     # Now sleep.

     if ($lgcy) {
      # restart (NOT reload !) dnsmasq
       system("/bin/systemctl restart dnsmasq");
     }

     $FULLRAN=1;
     sleep $DELAY;

  }
  # END.
  die "oops";
}
