#!/usr/bin/perl
# Update the skel within all VSes
# Copyright (c) 2001 Idaya Ltd.  All rights reserved.
# Written by Nick Burrett <nick@dsvr.net>
#
use File::Compare;
use DirHandle;
use VSD;

sub checktreeadd {
my $file;
my $dir = $_[0];
my $skel = $_[1];
my $d = new DirHandle $dir;
if (defined $d) {
while (defined ($file = $d->read)) {
# List of files not to be checked or copied
next if $file =~ /^\.\.?$/;
next if $file =~ /vsd.*/;
next if $file =~ /vsboot/;
next if $file =~ /virtuald/;
next if $file =~ /bevs/;
next if $file =~ /libvsd.a/;
next if $file =~ /freevsd.conf/;
next if $file =~ /vsd.conf/;
next if $file =~ /reboot/;
next if $file =~ /halt/;
next if $file =~ /shutdown/;
next if $file =~ /poweroff/;

if (-d "$dir/$file" && ! -d "$skel/$dir/$file") {
 push (@adddir, "$dir/$file");
 next;
}
&checktreeadd ("$dir/$file", "$skel") if (-d "$dir/$file" && ! -l
"$dir/$file");
if (-e "$dir/$file" && ! -e "$skel/$dir/$file") {
   push (@add, "$dir/$file");
   print ("adding $dir/$file\n");
}
if (-f "$dir/$file" && -f "$skel/$dir/$file") {
 push (@changed, "$dir/$file") if (compare ("$dir/$file",
"$skel/$dir/$file") == 1);
}
}
undef $d;
}
}

sub checktreedel {
my $file;
my $dir = $_[0];
my $skel = $_[1];
my $d = new DirHandle "$skel/$dir";
if (defined $d) {
while (defined ($file = $d->read)) {
next if $file =~ /^\.\.?$/;
if (-d "$skel/$dir/$file" && ! -d "$dir/$file") {
 push (@deldir, "$dir/$file");
 next;
}
&checktreedel ("$dir/$file", "$skel") if (-d "$skel/$dir/$file" &&
! -l "$skel/$dir/$file");
push (@del, "$dir/$file") if (! -e "$dir/$file" && -e
"$skel/$dir/$file");
}
undef $d;
}
}

sub checkadd {
my $file = $_[0];
my $skel = $_[1];
push (@add, "$file") if (-e "$file" && ! -e "$skel/$file");
if (-e "$file" && -e "$skel/$file") {
push (@changed, "$file") if (compare ("$file", "$skel/$file") == 1);
}
}

sub checkdel {
my $file = $_[0];
my $skel = $_[1];
push (@del, "$file") if (! -e "$file" && -e "$skel/$file");
}


# This subroutine actually does all of the work
sub upgrade {

$disk = shift (@_);
@vs = @_;

$vsboot = "/usr/sbin/vsboot";
@add = ();
@adddir = ();
@del = ();
@deldir = ();
@changed = ();
chdir "/";
checktreeadd "/lib", "$disk/skel";
checktreedel "/lib", "$disk/skel";
@dirs = glob ("/usr/*");
foreach (@dirs) {
next if ($_ eq "/usr/sbin");
next if ($_ eq "/usr/libexec");
next if ($_ eq "/usr/local");
next if ($_ eq "/usr/src");
next if ($_ eq "/usr/tmp");
printf ("checking $_\n");
checktreeadd "$_", "$disk/skel";
checktreedel "$_", "$disk/skel";
}

foreach $file (@bin_progs) { checkadd "/bin/$file", "$disk/skel"; }
foreach $file (@bin_progs) { checkdel "/bin/$file", "$disk/skel"; }
foreach $file (@sbin_progs) { checkadd "/sbin/$file", "$disk/skel"; }
foreach $file (@sbin_progs) { checkdel "/sbin/$file", "$disk/skel"; }
foreach $file (@usr_sbin_progs) { checkadd "/usr/sbin/$file",
"$disk/skel"; }
foreach $file (@usr_sbin_progs) { checkdel "/usr/sbin/$file",
"$disk/skel"; }
foreach $file (@usr_libexec_progs) { checkadd "/usr/libexec/$file",
"$disk/skel"; }
foreach $file (@usr_libexec_progs) { checkdel "/usr/libexec/$file",
"$disk/skel"; }

#print "Add directory: @adddir\n\n";
#print "Add: @add\n\n";
#print "Del: @del\n\n";
#print "Del directory: @deldir\n\n";
#print "Change: @changed\n\n";

# Add new directories
print "Add directory: @adddir\n";
foreach $file (@adddir) {
if (-l "$disk/skel/$file") {
 unlink ("$disk/skel/$file");
}
system ("cp", "-a", "$file", "$disk/skel/$file");
foreach (@vs) {
 if (-l "$disk/vs/$_/$file") {
     unlink ("$disk/vs/$_/$file");
 }
 system ("cp", "-al", "$disk/skel/$file", "$disk/vs/$_/$file");
}
}

print "Add: @add\n";
# Add files to the skel
foreach $file (@add) {
if (-l "$disk/skel/$file") {
 unlink ("$disk/skel/$file");
}
system ("cp", "-a", "$file", "$disk/skel/$file");
}
#Add FreeVSD Patched glibc.
@rpms = glob ("/usr/local/share/freevsd/pkgs/glibc-*.rpm");
#create rpm temp dirs.
mkdir ("$disk/skel/var", 0755);
mkdir ("$disk/skel/var/lib", 0755);
mkdir ("$disk/skel/var/tmp", 0755);
system ("cp", "-a", "/var/lib/rpm", "$disk/skel/var/lib");
system ("rpm", "-Uvh", "--force", "--root=$disk/skel", @rpms);
#clean rpm temp dirs.
system ("rm", "-rf", "$disk/skel/var");

# Add files from the skel to each virtual server
$pidc = 0;
foreach (@vs) {
my $server = $_;
if (! defined ($pid[$pidc] = fork ())) {
die "cannot fork: $!";
} elsif ($pid[$pidc] == 0) {
# child
my $file;
foreach $file (@add) {
   if (-l "$disk/vs/$server/$file") {
       unlink ("$disk/vs/$server/$file");
   }
   system ("cp", "-al", "$disk/skel/$file", "$disk/vs/$server/$file");
}
exit 0;
} else {
# parent
$pidc ++;
if ($pidc == 12) {
   # Reap children
   for ($x = 0; $x < $pidc; $x++) {
       waitpid ($pid[$x], 0);
   }
   $pidc = 0;
}
}
}

# Reap children
for ($x = 0; $x < $pidc; $x++) {
waitpid ($pid[$x], 0);
}

# Stop any running virtual servers
#system ("$vsboot", "--stop", "--part", "0");

print "Del: @del\n";
# Delete files from the skel
foreach $file (@del) { unlink ("$disk/skel/$file"); }

# Delete files from each virtual server
$pidc = 0;
foreach (@vs) {
my $server = $_;
if (! defined ($pid[$pidc] = fork ())) {
die "cannot fork: $!";
} elsif ($pid[$pidc] == 0) {
# child
my $file;
foreach $file (@del) {
   unlink ("$disk/vs/$server/$file");
}
exit 0;
} else {
# parent
$pidc ++;
if ($pidc == 12) {
   # Reap children
   for ($x = 0; $x < $pidc; $x++) {
       waitpid ($pid[$x], 0);
   }
$pidc = 0;
}
}
}

# Reap children
for ($x = 0; $x < $pidc; $x++) {
waitpid ($pid[$x], 0);
}


print "Del directory: @deldir\n";
foreach $file (@deldir) {
system ("rm", "-rf", "$disk/skel/$file");
foreach (@vs) { system ("rm", "-rf", "$disk/vs/$_/$file"); }
}

# Update any changed files
print "Change: @changed\n";
foreach $file (@changed) {
if (-l "$file") {
 unlink ("$disk/skel/$file");
 system ("cp", "-a", "$file", "$disk/skel/$file");
 foreach (@vs) {
     unlink ("$disk/vs/$_/$file");
     system ("cp", "-al", "$disk/skel/$file", "$disk/vs/$_/$file");
 }
} else {
 system ("cp", "-a", "$file", "$disk/skel/$file");
}
}

} # End of upgrade subroutine

# Warning: prints out beta software warning, and waits for a continue
sub print_warning
{

  printf ("########################################################################\n");
  printf ("#                                                                      #\n");
  printf ("#                   WARNING: vsd-refreshskel.pl                        #\n");
  printf ("#                   ===========================                        #\n");
  printf ("#                                                                      #\n");
  printf ("########################################################################\n");
  printf ("\n");
  printf ("  Warning: This script is to be considered as BETA Software, and has    \n");
  printf ("           been designed with a specific purpose in mind. If there is   \n");
  printf ("           ANY software installed on the host server that you do not    \n");
  printf ("           want on your virtual server, then do not run this script     \n");
  printf ("           (This includes non vsd-patched standard utils). If you are   \n");
  printf ("           not sure, then please do not run this script.                \n");
  printf ("\n");
  printf ("Are you sure you want to continue? [N]: "); 

  if (confirmn()){
    return 1; 
  }
  else {
    return 0;
  }
}

# Sub to read Y/N response, default answer N
sub confirmn {
    $_ = <>;
    return 1 if (/^y.*/i);
    return 0;
}

# Prints out usage message and exits with a status of -1
sub usage {

  printf ("vsd-refresh.pl: usage:       [--all] \n");
  printf ("                     [--partition dir1 [dir2]]\n");
  printf ("                     [--vs vsone [vstwo]]\n");
  exit (-1);

}

# Script starts here:

# Print warning message & check return status:
if (! print_warning){
  exit -1;
}

# Open vsd configuration file.
if (-e "/etc/vsd.conf" && -e "/usr/local/etc/vsd.conf"){
  die "vsd-refresh.pl: error: multiple vsd.conf files found. Please Check installation";
}
elsif (-e "/etc/vsd.conf"){
  open (CONFFILE, "/etc/vsd.conf") || die "vsd-refresh.pl: error: unable to open /etc/vsd.conf";
}
elsif (-e "/usr/local/etc/vsd.conf"){
  open (CONFFILE, "/usr/local/etc/vsd.conf") || die "vsd-refresh.pl: error: unable to open /usr/local/etc/vsd.conf";
}
else {
  die "vsd-refresh.pl: error: no vsd.conf file found";
}

# Read vsd Configuration file.
while ($line = <CONFFILE>){
  chomp ($line);
  next if $line =~ /^\s*$/; # skip blank lines
  if ($line =~ /^<Partition (\d+)/){
    $partition_name = join ("_", "partition", $1);
    while ($part_line = <CONFFILE>){
      if ($part_line =~ /^Mount (\S+)$/){
        $part_dir = $1;
        $partition{$partition_name}->{Disk} = $part_dir;
      }
      last if $part_line =~ /<\/Partition>/;
    }
    push (@partition_name_array, $partition_name);
  }
  if ($line =~ /^<VirtualServer (\w+)/){
    $vs = $1;
    while ($vs_line = <CONFFILE>){
      if ($vs_line =~ /^Partition (\d+)$/){
        $partition_name = join ("_", "partition", $1);
        push (@{$partition{$partition_name}->{VirtualServers}}, $vs);
      }
      last if $vs_line =~ /<\/VirtualServer>/;
    } 
  }
}
close (CONFFILE);

# Read freevsd.conf file:
if (-e "/etc/freevsd.conf" && -e "/usr/local/etc/freevsd.conf"){
  die "vsd-refresh.pl: error: multiple freevsd.conf files found. Please Check installation";
}
elsif (-e "/etc/freevsd.conf"){
  do "/etc/freevsd.conf";
}
elsif (-e "/usr/local/etc/freevsd.conf"){
  do "/usr/local/etc/freevsd.conf";
}
else {
  die "vsd-refresh.pl: error: no freevsd.conf file found";
}


# Flags to determine what to do:
$all_flag = 0;
$vs_flag = 0;
$part_flag = 0;

# Start of command line pocessing
if (@ARGV == 0){
  $all_flag++;
}

for ($i = 0; $i < @ARGV; $i++){

  if ($ARGV[$i] =~ /-+all/){
    $all_flag++;
  }
  elsif ($ARGV[$i] =~ /-+partition/) {
    $part_flag++;
    # Extract all partitions from commandline
    for ($i++; ($ARGV[$i] !~ /-+.*/) && ($i < @ARGV); $i++){
      push (@disk_arg, $ARGV[$i]);
    }
    $i--; # put index value back one to counteract last iteration of for loop
  }
  elsif ($ARGV[$i] =~ /-+vs/) {
    $vs_flag++;
    # Extract all vs from commandline
    for ($i++; ($ARGV[$i] !~ /-+.*/) && ($i < @ARGV); $i++){
      push (@vs_arg, $ARGV[$i]);
    }
    $i--; # put index value back one to counteract last iteration of for loop
  }
  else {
    printf ("vsd-refresh.pl: unkown option %s\n", $ARGV[$i]);
    usage ();
  }
}
# End of command line processing

# Double check that partitions & virtual servers exist & execute upgrade
if (($part_flag == 0) && ($vs_flag == 0) && ($all_flag == 1)){
  foreach $hash_entry (@partition_name_array){
    upgrade ($partition{$hash_entry}->{Disk}, @{$partition{$hash_entry}->{VirtualServers}});
  }
}
elsif (($part_flag == 1) && ($vs_flag == 0) && ($all_flag == 0)){
  foreach $hash_entry (@partition_name_array){
    foreach $disk_to_upgrade (@disk_arg){
      if ($partition{$hash_entry}->{Disk} eq $disk_to_upgrade){
        upgrade ($partition{$hash_entry}->{Disk}, @{$partition{$hash_entry}->{VirtualServers}});
      }
      else {
        printf ("vsdrefresh.pl: error: partition %s not found in configuration file\n", 
                                                                           $disk_to_upgrade);
        exit (-1);
      }
    }
  }
}
elsif (($part_flag == 0) && ($vs_flag == 1) && ($all_flag == 0)){
  foreach $hash_entry (@partition_name_array){
    # vs_list is given scope so that it can be reused without worrying about contents
    my @vs_list = ();
    foreach $vs_to_upgrade (@vs_arg){
      foreach $vs_on_partition (@{$partition{$hash_entry}->{VirtualServers}}){
        if ($vs_to_upgrade eq $vs_on_partition){
          push (@vs_list, $vs_to_upgrade);
        }
      }
    }
    upgrade ($partition{$hash_entry}->{Disk}, @vs_list);
  }  
}
else {
  usage ();
}

# Restart virtual servers.
#system ("$vsboot", "--start", "--part", "0");

