#!/usr/bin/perl
# $Id: vctl 32413 2015-05-18 21:46:48Z atsybulnik $
# Version Control
#
# Name:	vctl
# Usage vctl <command>
#
# This script is working from root account only
#
use strict;

# VARS -------------------------------------------------------------------------
my ($P) = (split(/\//, $0))[-1];#name of the program
my $CMD= shift  @ARGV;
my $LABEL='undefined';		# software label
my $LABEL_CTIME;		# data when label is created
my $REV='undefined';		# software revision
my $VER;			# software ver   (ex: 2.4.0, 2.4.2, 3.0.1)
my $BRAND;			# software brand (ex: SKM,SAMS)
my $BUILD;			# software build (ex: 1, 7, 24031_20110929)
my $EDITION;                    # software edition (ex: CIRRUS STRATUS OPTIMUS ALTIMUS)
my $TAG='';			# software tag   (ex: 3.3.1, 3.5.3)
my $OS;                         # operation system (ex: el4,el5)
my $HOST=`hostname`;chomp $HOST;# hostname
my $URI=`hostname -f 2>/dev/null`; chomp $URI;
my $STYPE;                      # master|node
my $UNI;                        # unique node id
my $LEVEL;			# current vpatch level
my $VLEVEL;			# vpatch level from the vpack
my $MATURITY='VIRGIN';          # ACTIVE or VIRGIN
my $APL='/opt/sarch';		# application's root
my $VDIR="$APL/vpatch/var";     # vpatch directories 
my $VPATCH;			# name of the current patch
my $SYSID='undefined';		# system identificator
my $STATUS='undefined';         # application status
my $INSTALL_RESULT='undefined'; # result of installation
my $SFX=time;			# timestamp suffix
my $CLOG;			# name for current log
my $LOG="$VDIR/logs/$SFX.log";	# name for main log
my $AUSR='apl';			# application owner
$ENV{APL}=$APL;			# set env variable

BEGIN { # Fix LD_LIBRARY_PATH if needed
    if (@ARGV and $ARGV[0] =~ /^(sysid|edition|info)$/) {
        my $libpath = $ENV{LD_LIBRARY_PATH};
	my $zmk_so_path = "/opt/sarch/mgears/bin";
	unless ($libpath) {	
	    $libpath = $zmk_so_path;
	}
	elsif ($libpath !~ m{$zmk_so_path}) {
    	    $libpath .= ":$zmk_so_path";
	}
	else {
	    $libpath = "";
	}
	if ($libpath) {
	    $ENV{LD_LIBRARY_PATH} = $libpath;
	    exec '/bin/env', $^X, $0, @ARGV;
	}
    }
}
                        
# PROC -------------------------------------------------------------------------
sub usage {
#-------------------------------------------------------------------------------
   my $err=shift;
   print STDERR "\n$err\n" if $err;
   print STDERR "\nVersion Control\n\nUsage:\n"
        ."  $P level     - show current release and patch level\n"
        ."  $P ver       - show version\n"
        ."  $P label     - show label\n"
        ."  $P tag       - show build tag\n"
        ."  $P brand     - show brand\n"
        ."  $P edition   - show edition\n"
        ."  $P rev       - show revison\n"
        ."  $P maturity  - show maturity of system\n"
        ."  $P sysid     - show system Id\n"
        ."  $P verid     - show version Id\n"
        ."  $P os        - show operation system id\n"
        ."  $P uni       - show unique node id\n"
        ."  $P host      - show hostname\n"
        ."  $P stype     - show system type (master|node)\n"
        ."  $P status    - show the status  (run|stop|starting|stopping|down)\n"
        ."  $P info      - show info\n"
        ."  $P list      - show applied patches\n"
        ."  $P history   - show a history of patch applications\n"
        ."  $P check     - check a presence of any applicable patch\n"
        ."  $P desc      - show description for available patches \n"
        ."  $P apply     - apply curent patch (run from ROOT only)\n"
        ."  $P undo      - undo latest patch (run from ROOT only)\n\n";
}

#-------------------------------------------------------------------------------
sub Log { # print message to log 
#-------------------------------------------------------------------------------
   my $msg=shift;
   open  CLOG, ">>$CLOG";
   print CLOG $msg;
   close CLOG;
   if($LOG) {
      open  LOG, ">>$LOG";
      print LOG $msg;
      close LOG;
   }
}

#-------------------------------------------------------------------------------
sub prlog { # print message to screen and place in a log
#-------------------------------------------------------------------------------
   my $msg=shift;
   print $msg;
   Log $msg;
}

#-------------------------------------------------------------------------------
sub Err { # print error on screen and exit 1
#-------------------------------------------------------------------------------
   print STDERR @_;
   exit 1;
}

#-------------------------------------------------------------------------------
sub Errlog { # print error to the log and screen then exit 1
#-------------------------------------------------------------------------------
   Log "\nERROR: @_\n";
   print STDERR "\nERROR: @_\n";
   exit 1;
}

#-------------------------------------------------------------------------------
sub prepare { # ! TBD Error handling
#-------------------------------------------------------------------------------
# ex:
# base-2.4.0-RELEASE_RHEL_5_SKM
# va-skm-2.4.2-int20070907_1
#
   if(open(VER,"$APL/base/etc/VERSION")) {
     $LABEL=<VER>;
     ($LABEL_CTIME)=(stat(VER))[10];
     close VER;
     chomp $LABEL;
   }
   if (open(TAG,"$APL/base/etc/TAG")) {
     $TAG=<TAG>;
     close TAG;
     chomp $TAG;
   }
   if (open(REV,"$APL/base/etc/REV")) {
     $REV=<REV>;
     close REV;
     chomp $REV;
   }
   if (open(OS,"$APL/base/etc/OS")) {
     $OS=<OS>;
     close OS;
     chomp $OS;
   }
   if (open(BRAND,"$APL/base/etc/MOD")) {
     $BRAND=<BRAND>;
     close BRAND;
     chomp $BRAND;
   }
   if (open(RESULT,"$APL/var/log/activate/result")) {
     my %result=map{/(^\w+)=(.+)/} grep {/^\w+=.+/} <RESULT>;
     $INSTALL_RESULT=$result{INSTALL_RESULT};
     close RESULT;
   }
   if (open(CONF,"$APL/var/conf/node/conf")) {
       my %conf=map{/(^\w+)=(.+)/} grep {/^\w+=.+/} <CONF>;
       close CONF;
       $UNI=$conf{UNI} if defined $conf{UNI};
   }
   eval { # this call may be done prior activation
     require "/opt/sarch/common/lib/x86_64-linux-thread-multi/Master/Zmk.pm";
   
     if(!$SYSID or $SYSID eq 'undefined')     {
       $SYSID=Master::Zmk::VASerial();
     }
     $EDITION=Master::Zmk::VAEdition();
   };
   $STYPE=( -f "$APL/var/conf/master/s_master")?'master':'node';
   if ( -f "$VDIR/stat/level" ) {
     open LEVEL, "$VDIR/stat/level";
     $LEVEL=<LEVEL>;
     close LEVEL;
     chomp $LEVEL;
   } else {  # no patches are installed yet
     $LEVEL=0;
   }
   if( -r "$APL/var/wd/status") {
     open STATUS, "$APL/var/wd/status";
     $STATUS=<STATUS>;
     close STATUS;
     chomp $STATUS;
   }
   if(opendir(DIR,"$APL/var/conf")) {
      my @dir= grep { /^a?\d+$/ } readdir(DIR);
      closedir DIR;
      $MATURITY=(@dir)?'ACTIVE':'VIRGIN'; 
   } else {
     $MATURITY='BROKEN';
   }
   $_=$LABEL;
   my $ucBRAND; # temp, never used
   ($VER,$ucBRAND,$BUILD)=($2,uc($1),$3) if /^\w+-(\w+)-\w+-(\d+\.\d+\.\d+)-(\w+)\./;
   ($VER,$ucBRAND,$BUILD)=($2,uc($1),$3) if /^\w+-(\w+)-(\d+\.\d+\.\d+)-(\w+)\./;
}

#-------------------------------------------------------------------------------
sub show_info { 
#-------------------------------------------------------------------------------
  my $arg=shift;
  print "\nlabel=$LABEL level=$LEVEL\n" if $arg eq 'level';
  print "$LABEL\n"                      if $arg eq 'label';  
  print "$BRAND\n"                      if $arg eq 'brand';  
  print "$BUILD\n"                      if $arg eq 'build';
  print "$EDITION\n"                    if $arg eq 'edition';
  print "$VER\n"                        if $arg eq 'ver';  
  print "$TAG\n"                        if $arg eq 'tag';  
  print "$REV\n"                        if $arg eq 'rev';
  print "$OS\n"                         if $arg eq 'os';
  print "$UNI\n"                        if $arg eq 'uni';
  print "$HOST\n"                       if $arg eq 'host';
  print "$URI\n"                        if ($arg eq 'uri' && $URI);
  print "$MATURITY\n"                   if $arg eq 'maturity';
  print "$STYPE\n"                      if $arg eq 'stype';
  print "$STATUS\n"                     if $arg eq 'status';
  print "$LABEL-p$LEVEL\n"              if $arg eq 'verid';
  print "$SYSID\n"                      if $arg eq 'sysid';
  print "label=$LABEL\n"
       ."level=$LEVEL\n"
       ."ver=$VER\n"
       ."brand=$BRAND\n"
       ."build=$BUILD\n"
       ."edition=$EDITION\n"
       ."tag=$TAG\n"                      
       ."rev=$REV\n"
       ."os=$OS\n"
       ."uni=$UNI\n"
       ."host=$HOST\n"
       ."uri=$URI\n"
       ."maturity=$MATURITY\n"
       ."stype=$STYPE\n"
       ."sysid=$SYSID\n"
       ."status=$STATUS\n"
       ."install_result=$INSTALL_RESULT\n"
       ."verid=$LABEL-p$LEVEL\n"
	."time=" . time() . "\n"        if $arg eq 'info';  
}

#-------------------------------------------------------------------------------
sub apl_status { # return status of the system: UP/DOWN
#-------------------------------------------------------------------------------
  `ps -e | grep -q cam_patrol`;
  return ($?)?'DOWN':'UP';
}

#-------------------------------------------------------------------------------
sub apl_restart { # restart application; may be done at the end of the script
#-------------------------------------------------------------------------------
    prlog "\nRestarting Application ...\n";
    system('(sleep 1;/etc/init.d/patrol stop;sleep 1; /etc/init.d/patrol start)>/dev/null 2>&1 &');
} 

#-------------------------------------------------------------------------------
sub show_list {
#-------------------------------------------------------------------------------
  if(-f "$VDIR/stat/list") {
    open LIST,"$VDIR/stat/list";
    print while(<LIST>);
    close LIST;
  }else {  # not patches are installed yet 
    print "\nNo patches have been installed\n\n";
  }
}
#-------------------------------------------------------------------------------
sub show_history {
#-------------------------------------------------------------------------------
  printf "installation\t%s\n", scalar localtime $LABEL_CTIME;
  if(-f "$VDIR/stat/list") {
    open LIST,"$VDIR/stat/list";
    print while(<LIST>);
    close LIST;
  }
}
#-------------------------------------------------------------------------------
sub get_highest_VLEVEL {
#-------------------------------------------------------------------------------
  opendir(DIR, "$VDIR/ibox") || die "can't opendir $VDIR/ibox: $!";
  ($VLEVEL) = sort {$b <=> $a} map { /^$LABEL-p(\d+).vpatch$/ } readdir(DIR);
  closedir DIR;
}

#-------------------------------------------------------------------------------
sub check_pack {
#-------------------------------------------------------------------------------
  get_highest_VLEVEL();
  if(!$VLEVEL) {
    print "No incoming patches found for templeate: $LABEL-p*.vpatch\n";
    return;
  }
  print "\nFound patch-pack  : $LABEL-p$VLEVEL.vpatch\n";
  if($VLEVEL<=$LEVEL) {
    print "Patches to install: NOTHING\n\n"; 
    return;
  }
  printf"Patches to install: %d - %d \n\n",$LEVEL+1,$VLEVEL;
}

#-------------------------------------------------------------------------------
sub desc_pack {
#-------------------------------------------------------------------------------
  check_pack;
  return if $VLEVEL<=$LEVEL;            # nothing to install & describe
  `rm -rf $VDIR/tmp/desc`;              # ! TBD Error handling
  mkdir("$VDIR/tmp/desc",0700) || die "can't create dir $VDIR/tmp/desc: $!"; 
  `cd $VDIR/tmp/desc && cpio -imdu  '*/README' <$VDIR/ibox/$LABEL-p$VLEVEL.vpatch 2>/dev/null`; # !TBD ERROR h
  opendir(DIR, "$VDIR/tmp/desc") || die "cannot opendir $VDIR/tmp/desc: $!";
  my @readme = sort grep  { -f "$VDIR/tmp/desc/$_/README" }  readdir(DIR);
  closedir DIR;
  foreach (@readme) {
    print "-----------------------------------------------------------[$_]---\n";
    my ($nn)=/^vpatch.(\d+)$/;            # patch number
    if ($nn<=$LEVEL) {
        print "Already installed, skipping\n";
    } else {
        open   README, "$VDIR/tmp/desc/$_/README";
        print <README>;
        close  README;
   }
  }
}


#-------------------------------------------------------------------------------
sub flags_list { #Checking Global flags: REBOOT, RESTART, STOP
#-------------------------------------------------------------------------------
  get_highest_VLEVEL();
  `rm -rf $VDIR/tmp/flags`;              # ! TBD Error handling
  mkdir("$VDIR/tmp/flags",0700) || die "can't create dir $VDIR/tmp/flags: $!"; 
  `cd $VDIR/tmp/flags && cpio -imdu  <$VDIR/ibox/$LABEL-p$VLEVEL.vpatch 2>/dev/null`; # !TBD ERROR h
  my %flags;
  opendir(DIR, "$VDIR/tmp/flags") || die "cannot opendir $VDIR/tmp/flags: $!";
  while(my $patch=readdir(DIR)) {
    my ($nn)=$patch=~/^vpatch.(\d+)$/;   # patch number
    next if ($nn <= $LEVEL);             # ignoring old patch
    map {$flags{$_}=1} grep { -f "$VDIR/tmp/flags/$patch/$_" } ('REBOOT','RESTART','STOP');
  }
  closedir DIR;
  #-----------------clean flag inconsistency
#  delete $flags{STOP};
  delete $flags{RESTART} if (defined $flags{REBOOT}) && (defined $flags{RESTART});
  keys %flags;
}

#-------------------------------------------------------------------------------
sub apply_pack {
#-------------------------------------------------------------------------------
  Errlog "\n'$P apply' -  should be run from ROOT\n\n" if $>>0;
  check_pack;
  return if $VLEVEL<=$LEVEL;              # nothing to install & describe
  my @flgs=flags_list;
  my $apl_status=apl_status;              # is application is running or down?
  Errlog "The Service pack requires stoping system before application\n" 
                                       if (grep {/^STOP$/} @flgs) && $apl_status eq 'UP';
  `rm -rf $VDIR/run/vpack`;               # ! TBD Error handling
  mkdir("$VDIR/run/vpack",0755) || Errlog "can't create dir $VDIR/run/vpack: $!";
  `cd $VDIR/run/vpack && cpio -imdu <$VDIR/ibox/$LABEL-p$VLEVEL.vpatch 2>/dev/null`;
  Errlog "Cannot unpack $LABEL-p$VLEVEL.vpatch" if  $?!=0;
  opendir(DIR, "$VDIR/run/vpack") || Errlog "cannot opendir $VDIR/run/vpack: $!";
  my @packs = sort grep  { /^vpatch.\d+$/ }  readdir(DIR);
  closedir DIR;
  foreach (@packs) {
    print "-----------------------------------------------------------[$_]---\n";
    my ($nn)=/^vpatch.(\d+)$/;          # patch number
    if ($nn<=$LEVEL) {
        print "Already installed, skipping\n";
    } else {# =========================== patch application 
        $VPATCH=$_;                     # set the name for current vpatch
        $CLOG="$VDIR/logs/$VPATCH/$SFX/$VPATCH.log"; # set the name for a log
        mkdir("$VDIR/logs/$VPATCH"     ,0755) if not -d "$VDIR/logs/$VPATCH";
        mkdir("$VDIR/logs/$VPATCH/$SFX",0755) if not -d "$VDIR/logs/$VPATCH/$SFX";
        mkdir("$VDIR/undo/$VPATCH"     ,0755) if not -d "$VDIR/undo/$VPATCH";
        mkdir("$VDIR/undo/$VPATCH/$SFX",0755) if not -d "$VDIR/undo/$VPATCH/$SFX";
        my $logs="$VDIR/logs/$VPATCH/$SFX"; # set the directory to store logs
        my $run="$VDIR/run/vpack/$VPATCH";# directory to run
     #-------------------------------- actual application
        open (LIST, "$run/LIST") || die "Cannot apply $VPATCH, LIST is missing";
        prlog "backing up files...\n";
        Log $_ foreach(<LIST>);
        `cd $APL && cat $run/LIST | cpio -oc >$VDIR/undo/$VPATCH/$SFX/undo.cpio 2>$logs/cpio.log`;  # !TBD ERROR h
        close LIST;
        opendir(DIR, "$run") || die "cannot opendir $run: $!";
        my @cmds = sort {$a <=> $b} grep  { /^\d+-.*sh$/ }  readdir(DIR);
        close DIR;
        foreach my $cmd (@cmds) {
           next if $cmd=~/99-restart.sysh/;           # skip restart
           prlog "applying $cmd\n";
           `su $AUSR -lc 'cd $run && /bin/bash ./$cmd 2>&1' >$logs/$cmd.log 2>&1` if $cmd=~/\.sh$/;
           `(cd $run && /bin/bash ./$cmd 2>&1) >$logs/$cmd.log 2>&1`               if $cmd=~/\.sysh$/;
           Errlog "$cmd: fails\n$VPATCH is NOT applied![$?]" if $?!=0;
           if(-f "$run/u$cmd") {
              prlog "storring undo u$cmd\n";
              rename "$run/u$cmd","$VDIR/undo/$VPATCH/$SFX/u$cmd";
           }
        }
        rename("$run/LIST","$VDIR/undo/$VPATCH/$SFX/LIST");
        rename("$run/UNDO","$VDIR/undo/$VPATCH/$SFX/UNDO") if -f "$run/UNDO";
     #--------------------------------- commit
        open(LIST,">>$VDIR/stat/list");
        printf LIST "$VPATCH\t%s\n", scalar localtime;
        close LIST;
        open(LEVEL,">$VDIR/stat/level");
        printf(LEVEL "%d\n",++$LEVEL);
        close LEVEL;
        prlog  "$VPATCH IS APPLIED SUCCESSFULLY\n";
    }#=================================
  }
  #------------------------------------ pack is done, move to storage
#  rename "$VDIR/ibox/$LABEL-p$VLEVEL.vpatch","$VDIR/sbox/$LABEL-p$VLEVEL.vpatch";
  prlog "\nSERVICE PACK finished executing SUCCESSFULLY\n";
  prlog "\nPlease reboot the system!\n" if grep {/^REBOOT$/}  @flgs;
  apl_restart				if(grep {/^RESTART$/} @flgs) && $apl_status eq 'UP';
  prlog "\nApplication state is 'DOWN'\n"  if apl_status eq 'DOWN';
}

#-------------------------------------------------------------------------------
sub undo_vpatch {
  #--------------------------------- check  
     Errlog "'$P undo' -  should be run from ROOT\n\n" if $>>0;
     Errlog "\n Nothing to undo\n\n'"                  if not $LEVEL;
     my $level=sprintf("%06d",$LEVEL); 
     $VPATCH="vpatch.$level";
     my $first=`ls $VDIR/undo/$VPATCH/ | head -1`;  # first attempt to install patch
     my $last =`ls $VDIR/undo/$VPATCH/ | tail -1`;  # last  attempt to install patch
     chomp $first; chomp $last;
     Errlog "\t$VPATCH cannot be undone!\n" if ! -f "$VDIR/undo/$VPATCH/$last/UNDO"; 
     Errlog "Application should be stopped prior UNDO\n" if apl_status eq 'UP';
     prlog "\nUNDO: $VPATCH\n";
     print "\nPlease confirm undo action [type UNDO]:";
     $_=<STDIN>; chomp;
     Errlog "\tUNDO $VPATCH is aborted![$_!=UNDO]\n" if $_ ne 'UNDO';
  #--------------------------------- undo
     my $logs="$VDIR/logs/$VPATCH/$SFX";
     my $cpio="$VDIR/undo/$VPATCH/$first/undo.cpio";
     my $run="$VDIR/undo/$VPATCH/$last";
     mkdir("$logs",0755) if not -d "$logs";
     Log "UNDO: $cpio\n";
     Log "UNDO: $run/uXX-steps\n";
     Errlog "Cannot find $cpio" if ! -f $cpio;		# cpio should be found!
     prlog "Restoring  ... ------------------------\n";
     `cd $APL &&  cpio -imduv < $cpio 2>/dev/null`;
     Errlog "Cannot undo $cpio" if  $?!=0;
     prlog "Modified files are restored\n";
     opendir(DIR, $run) || ErrLog("cannot opendir $run: $!");
     my @cmds = sort {my ($an)=$a=~/(\d+)/;my ($bn)=$b=~/(\d+)/; $bn<=>$an} grep  { /^u\d+-.*sh$/ }  readdir(DIR);
     close DIR;
     foreach my $cmd (@cmds) {
        prlog "un-doing $cmd\n";
        `su $AUSR -cl 'cd $run && /bin/bash ./$cmd 2>&1' >$logs/$cmd.log 2>&1` if $cmd=~/\.sh$/;
        `(cd $run && /bin/bash ./$cmd 2>&1) >$logs/$cmd.log 2>&1`               if $cmd=~/\.sysh$/;
        Log "$cmd: fails![$?]" if $?!=0;
     }
     prlog "Finalizing ... ------------------------\n"; 
  #--------------------------------- commit
     open(LIST,">>$VDIR/stat/list");
     printf LIST "undone:$level\t%s\n", scalar localtime;
     close LIST;
     open(LEVEL,">$VDIR/stat/level");
     printf(LEVEL "%d\n",$LEVEL-1);
     close LEVEL;
     rename("$VDIR/undo/$VPATCH","$VDIR/undo/undone.$level");
     prlog  "\n$VPATCH IS UNDONE SUCCESSFULLY. Please RESTART the system!\n\n";
}
#-------------------------------------------------------------------------------


# MAIN -------------------------------------------------------------------------

prepare;
if    ($CMD eq 'level'   )  {     show_info $CMD }
elsif ($CMD eq 'ver'     )  {     show_info $CMD }
elsif ($CMD eq 'label'   )  {     show_info $CMD }
elsif ($CMD eq 'tag'     )  {     show_info $CMD }
elsif ($CMD eq 'brand'   )  {     show_info $CMD }
elsif ($CMD eq 'build'   )  {     show_info $CMD }
elsif ($CMD eq 'edition' )  {     show_info $CMD }
elsif ($CMD eq 'rev'     )  {     show_info $CMD }
elsif ($CMD eq 'os'      )  {     show_info $CMD }
elsif ($CMD eq 'uni'     )  {     show_info $CMD }
elsif ($CMD eq 'verid'   )  {     show_info $CMD }
elsif ($CMD eq 'maturity')  {     show_info $CMD }
elsif ($CMD eq 'sysid'   )  {     show_info $CMD }
elsif ($CMD eq 'host'    )  {     show_info $CMD }
elsif ($CMD eq 'stype'   )  {     show_info $CMD }
elsif ($CMD eq 'status'  )  {     show_info $CMD }
elsif ($CMD eq 'info'    )  {     show_info $CMD }
elsif ($CMD eq 'list'    )  {     show_list      }
elsif ($CMD eq 'history' )  {     show_history   }
elsif ($CMD eq 'desc'    )  {     desc_pack      }
elsif ($CMD eq 'check'   )  {     check_pack     }
elsif ($CMD eq 'apply'   )  {     apply_pack     }
elsif ($CMD eq 'undo'    )  {     undo_vpatch    }
else                        {     usage          }


