#!/usr/bin/perl 
#If your copy of perl is not in /usr/bin, please adjust the line above.

=head1 NAME

ZMTk-install: A installation script to install ZENITH-job Management Toolkit (ZM/Tk), user interactive

Version 2.3  (2005.02.20): ZENITH MIGRATION?! job spool area screwed, now let the script decide where are the online files (assuming the top directory starts from /jobspool1).
Version 2.2  (2004.07.27): Cosmetic changes, added  more documentation.
Version 2.1  (2004.06.18): Switched default login program to 'ssh' instead of 'rsh', also use 'scp' instead of 'rcp' as default copy method.
Version 2.0  (2004.06.09): Deal with different shell environment now, works for both 'sh/zsh' and 'tcsh/csh'.
Version 1.2  (2004.04.07): Added locations for NOISY CELL FILES, DEAD MATERIAL FILEs and VERTEX FILES.
Version 1.11 (2004.02.25): Added a new job submission script 'subabc' under the request of Michele Rosin.
Version 1.1  (2004.02.18): Codes optimized. Interactive installation process rewritten.
Version 1.01 (2004.02.13): ZENITH changed job running area to "/jobspool0", added this into installation options.
Version 1.0  (2003.09.11): Initial release

=head1 AUTHOR

Liang Li (liangli@mail.desy.de)

=cut

require 5.003;
use File::Basename;
use Cwd "chdir";
use Cwd;

#####################################
#PREPARE INSTALLATION (HARDCODED)
#####################################

#program name
$program="ZM/Tk";
#version number
$version="2.3";

#file list to install
@files = qw(joba.export reach1 reach2 reach3 cpjob searchjob joba jka);
@files2= qw(runscript.sh sub suba subabc);
@files3= qw(zeusinfi);

#check integrity
for (@files) { die "Error: File '$_' is missing in directory './bin'.\n$program installation can't start!\n" unless -r "bin/$_" && -f "bin/$_" }
for (@files2) { die "Error: File '$_' is missing in directory './bin'.\n$program installation can't start!\n" unless -r "bin/$_" && -f "bin/$_" }
for (@files3) { die "Error: File '$_' is missing in directory './bin'.\n$program installation can't start!\n" unless -r "bin/$_" && -f "bin/$_" }

#job-clients software
@jobclients=qw(jobls jobpurge jobq jobsub);

#check job-clients
for (@jobclients) { die "Error: Can't find ZEUS job-clients software $_ in your PATH.\n" unless ! system("which $_") }

#default paths
$job_path="/jobspool1";
$pwd=cwd();
$script_path="~/bin";
#is it in AFS?
$afs=0;
#ntuple
$extension="ntp";
$ntp_path="";

#use rsh or ssh?
$rsh=0;
$ssh=0;
$rcp=0;
$scp=0;
#always prefer ssh, and no warnings!
$connect="ssh -qx";
$copy="scp";

#default machines
$sub_machine="zenithsub";
$run_machine="zenith";

#NOISY CELL FILES, DEAD MATERIAL FILEs and VERTEX FILES
$NOISYCELL="/afs/desy.de/group/zeus.zsmsm/ZEUSSysSoft/Released/zeus/ZeusUtil/phantom/v2004a/doc/Detectors/UCAL/noise/*.list";
$DEADMATERIAL="/afs/desy.de/group/zeus.zsmsm/ZEUSSysSoft/Manager/zeus/ZeusUtil/phantom/vRelease/doc/Detectors/UCAL/x0maps/dead*"; 
$VERTEX="/afs/desy.de/group/zeus.zsmsm/ZEUSSysSoft/Released/zeus/Programs/orange/v2004a/example_job/dat/run_av_vertex*.txt";

#default shell
$shell=$ENV{SHELL};
die "Error: Unable to identify shell environment. \$SHELL undefined.\n$program installation can't start!\n" unless ( $ENV{SHELL} );
if ( $shell =~ /csh/ ) {
	$shell="csh";
}
else {
	$shell="zsh";
}

#status
$status=0;

#get answer (Y or N?) from user's input
sub get_answer {
    my ($answer,$querry);
    chomp($answer = <STDIN>);
    $answer =~ s/^\s*//;                 # Kill leading whitespace
    $answer =~ s/\s*$//;                 # Kill trailing whitespace
    $answer =~ s/^([a-z])(.*)/$1/i;      # Kill trailing non alphabetic
    if ($answer eq "") { $answer="y" }
    if ($answer eq "y" || $answer eq "Y"){ $querry=1 }
    elsif ($answer eq "n" || $answer eq "N") { $querry=0 }
    else { $querry=2 }
}

#get target path
sub get_path {
    my ($answer);
    chomp($answer = <STDIN>);
    $answer =~ s/^\s*//;                 # Kill leading whitespace
    $answer =~ s/\s*$//;                 # Kill trailing whitespace
    $answer =~ s/^(\/){2,}/\//;          # Kill leading extra slash
    $answer =~ s/([^\/:]+)(\/+)$/$1/;    # Kill trailing extra slash
    # tricky! have to convert '~' to absolute path, b.c. perl can't deal with path starts with '~'
    $answer =~ s/^~([^\/]*)(\/*)([^\/]*)/convert_path($1,$2,$3)/e;
    $answer;
}

sub convert_path {
    my ($user)=$_[0];
    if ( $user eq "" ) { $user=$ENV{USER} } 
    my ($path)=(getpwnam($user))[7];
    if ( ! defined $path ) { $path="~$_[0]" }
    return "$path$_[1]$_[2]";
}

#get machine name to submit jobs
sub get_machine {
    my ($answer);
    chomp($answer = <STDIN>);
    $answer =~ s/^\s*//;                 # Kill leading whitespace
    $answer =~ s/\s*$//;                 # Kill trailing whitespace
    $answer;
} 

#get machine name to submit jobs
sub get_extension {
    my ($answer);
    chomp($answer = <STDIN>);
    $answer =~ s/^\s*//;                 # Kill leading whitespace
    $answer =~ s/\s*$//;                 # Kill trailing whitespace
    $answer;
} 

sub get_location {
    my ($answer);
    chomp($answer = <STDIN>);
    $answer =~ s/^\s*//;                 # Kill leading whitespace
    $answer =~ s/\s*$//;                 # Kill trailing whitespace
    $answer =~ s/^(\/){2,}/\//;          # Kill leading extra slash
    $answer =~ s/([^\/]+)(\/+)$/$1/;     # Kill trailing extra slash
    # tricky! have to convert '~' to absolute path, b.c. perl can't deal with path starts with '~'
    $answer =~ s/^~([^\/]*)(\/*)([^\/]*)/convert_path($1,$2,$3)/e;
    $answer;
} 

$basename=basename($0);
$dirname=`pwd`;
chomp($dirname);


#########################################
#CONFIGURATION BEGINS:
#########################################

print STDOUT "\n************************************************************************************\n";
print STDOUT "$program version $version (liangli\@mail.desy.de)\n";
print STDOUT "This script will install Liang Li's ZENITH-job Management Toolkit ($program $version).\n";
print STDOUT "Installation source: $dirname\n";
print STDOUT "Installation help: $dirname/doc/INSTALL (please read before you proceed!)\n";
print STDOUT "Installation components: @files @files2\n";
print STDOUT "Installation script: $basename\n";
print STDOUT "Installation to (DESY) AFS area is highly recommended.\n";
print STDOUT "*************************************************************************************\n\n";

print STDOUT "The following questions will customize your $program installation.\n";
print STDOUT "If you don't understand the question or don't know what the answer is, please press <Enter> key to use the default settings.\n";
print STDOUT "If you input wrong answer, quit and rerun, or, for expert users, directly change settings file 'joba.export' after installation.\n\n";

#1.which machine to submit jobs?
print STDOUT "\n1.Which PCFARM machine do you sumbit your ZENITH-jobs to? [zenithsub] ";
$sub_machine = &get_machine;
if ($sub_machine =~ /^\s*$/ ) { $sub_machine="zenithsub" }

#test jobls
print STDOUT "Connecting to '$sub_machine'...";
$status = !system("jobls '-h$sub_machine' > /dev/null 2>&1");
die "failed.\n\nError: Connection to '$sub_machine' failed! Are you sure '$sub_machine' is accepting ZENITH-jobs?\n" unless ($status == 1);
print STDOUT "OK.\n";

#2.which machine to run script ?
print STDOUT "\n2.Which PCFARM machine do you intend to run $program on (remote running)? [zenith] ";
$run_machine = &get_machine;
if ($run_machine =~ /^\s*$/ ) { $run_machine="zenith" }

#test automatic rsh/ssh
print STDOUT "Testing automatic ssh to remote machine '$run_machine'...";
#see if automatic rsh
$ssh = ! system("ssh -qx '$run_machine' echo > /dev/null 2>&1");
if ($ssh != 1) {
	print STDOUT "failed.\nTesting automatic rsh to remote machine '$run_machine'...";
	$rsh = ! system("rsh '$run_machine' echo > /dev/null 2>&1");
}	
die "failed.\n\nError: Automatic rsh or ssh to remote machine '$run_machine' is not successful.\nTry 'klog'? See 'doc/INSTALL'\n" unless ($rsh == 1 || $ssh == 1);
print STDOUT "OK.\n";
if ($rsh == 1) { $connect="rsh" };

#3.job running area?
#################ZENITH MIGRATION##########QUESTION NO LONGER NEEDED#####
#print STDOUT "\n3.On remote machine '$run_machine', where are your submitted jobs located at? [/jobspool0] ";
#$job_path = &get_path;
#if ($job_path eq "" ) { $job_path="/jobspool0" }

#check if /jobspool0/usr#### there
#if ( $shell=="zsh" ) { 
#	$status=`$connect $run_machine ls $job_path/FAIL 1>/dev/null`;
#}
#else {
#retarded 'csh' couldn't handle error output, have to use 'zsh' to wrap around it
#	$status=`zsh -c '$connect $run_machine ls $job_path/FAIL >/dev/null' 2>&1"`;
#}
#die "\nError: Job area '$job_path' at remote machine '$run_machine' not found.\nProblem with ZENITH? See 'doc/INSTALL'\n" unless ( $status eq "" );
#print STDOUT "'$run_machine:$job_path/' OK.\n";

#3.MY_SCRIPT_DIR
print STDOUT "\n3.Where do you want to install $program $version?\nPrevious installation of $program will be overwritten if at the same location\n";
print STDOUT "Enter path name...[~/bin]: ";
$script_path = &get_path;
if ($script_path eq "" ) { $script_path="$ENV{HOME}/bin" }

#quit if it's $pwd
die "\nError: Can't install $program to itself.\n" unless $script_path ne $pwd;

#test if it's readable and writable
die "\nError: Directory '$script_path' does not exist.\n" unless -d $script_path; # $script_path doesn't work here! bizarre
die "\nError: Don't have read or write permission in directory '$script_path'.\n" unless -w $script_path && -r $script_path; 
#test if it's in AFS
chdir $script_path;
$afs = ! system("fs listacl >/dev/null 2>&1");
if ( $afs != 1 ) {
	print STDOUT "Installing $program to (DESY) AFS area is highly recommended.\n";
	print STDOUT "Are you sure you want to install to non-AFS area? [Y] ";
	$status=&get_answer;
	die "\nQuit.\n" unless ( $status==1 );
}	
print STDOUT "'$script_path'...OK.\n";
#go back to `pwd`
chdir $pwd;

#4.MY_ORANGE_DIR
print STDOUT "\n4.Where is your ORANGE/EAZE/ZENITH job directory (standard ZSMSM hierachical directory tree)?\n";
print STDOUT "Enter path name...[~/OrangeJob]: ";
$orange_path=&get_path;
if ($orange_path eq "") { $orange_path="$ENV{HOME}/OrangeJob" };

#test if it's readable and writable
die "\nError: Directory '$orange_path' does not exist.\n" unless  -d $orange_path;
die "\nError: Don't have read or write permission in directory '$orange_path'.\n" unless -w $orange_path && -r $orange_path; 
die "\nError: Directory '$orange_path/cmd' does not exist.\nPlease check if you have a standard ZSMSM directory. See 'doc/INSTALL'\n" unless -d "$orange_path/cmd";
die "\nError: Directory '$orange_path/cards' does not exist.\nPlease check if you have a standard ZSMSM directory. See 'doc/INSTALL'\n" unless -d "$orange_path/cards"; 
print STDOUT "'$orange_path'...OK.\n";

#5.NTUPLE EXTENSION
print STDOUT "\n5.What is the extension name of the generated ntuple file? [nt/ntp] ";
$extension= &get_extension;
if ($extension =~ /^\s*$/ ) { $extension="nt" }
print STDOUT "You have chosed '*.$extension' as your ntuple filenames.\n";

#6.MY_NTP_DIR
print STDOUT "\n6.Where do you want to dump your ntuples, log files etc...?\n";
print STDOUT "Enter machine name and path name...[amzeus:/data/liangli/ntuple]: ";
$ntp_path = &get_path;
if ($ntp_path eq "") { $ntp_path="amzeus:/data/liangli/ntuple" }
if ($ntp_path !~ /:/) { $ntp_path="$ENV{HOST}:$ntp_path" } 

#special treatment of $ntp_path to avoid fatal errors when $ntp_path="zwisc02:'(^#@#dir`&*"...etc.  It's  NON-SECURE! and causes a system overflow.
# in this case ( path name contains some special characters), neither perl nor shell can catch the exception, have to put '\' in the front
$ntp_path =~ s/([^:a-zA-Z_0-9\-\/\.%+@])/\\$1/g;
#even after this special treatment, path name containing "'`' (single quote and backtick) still problematic, so remove them!
die "\nError: Invalid path name, ' and ` are not allowed.\n" unless $ntp_path !~ /[\'`]/; 
#  still(!) pattern like "**aaa" won't work but don't care anymore (it's secure)

#a very SECURE way of creating 'tmp' dir
#${TMPDIR:-/tmp} should give us correct "TMPDIR" but strange enough, perl doesn't complie the following line!
#$tmp=`$connect $run_machine echo ${TMPDIR:-/tmp}`;
$tmp="/tmp";
$LOOP=1;
$i=$$;
if ( $shell eq "zsh" ) {
	$status=`$connect $run_machine [ -d $tmp -a -w $tmp ] && echo OK 2>&1`;
}	
else {
	$status=`$connect $run_machine [ -d $tmp -a -w $tmp ] && echo OK`;
}
#remove trailing "\n"
chomp($status);

die "\nError: Can't find $tmp on '$run_machine'.\n" unless ( $status eq "OK" ); 
while ( $LOOP ) {
	if ( $shell eq "zsh" ) {
#have to put ' ' around anything* to avoid stupid 'zsh: no matches found' error msg	
		$status=`$connect $run_machine "rm -fr '$tmp/ZMTk-$ENV{USER}*' 2>/dev/null; mkdir -m 0755 $tmp/ZMTk-$ENV{USER}.$i"`;
	}
	else {
		$status=`zsh -c "$connect $run_machine 'rm -fr $tmp/ZMTk-$ENV{USER}* >&/dev/null; mkdir -m 0755 $tmp/ZMTk-$ENV{USER}.$i' 2>&1"`;
	}	
	if ( $status eq "" ) { last }
	$LOOP++;
	$i++;
	if ( $LOOP >= 100 ) { print STDOUT "\nError: The TMPDIR directory ('$tmp') on '$run_machine' may be under attack.\n"}
}	
$tmp="$tmp/ZMTk-$ENV{USER}.$i";

#test scp/rcp  (always prefer ssh&scp, security reasons)
print STDOUT "Testing scp to '$ntp_path'...";
if ( $shell eq "zsh" ) {
	$status= `$connect $run_machine "mkdir -p $tmp/.ZMTk.tmp$$ && scp -r $tmp/.ZMTk.tmp$$ '$ntp_path' 2>&1"`;
}
else {
	$status= `zsh -c "$connect $run_machine 'mkdir -p $tmp/.ZMTk.tmp$$ && scp -r $tmp/.ZMTk.tmp$$ "$ntp_path"' 2>&1"`;
}

if ( $status eq "" ) {
	$scp = 1;
	print STDOUT "OK.\n";
}
else {
	$scp = 0;
	print STDOUT "failed.\n";
	print STDOUT "Testing rcp to '$ntp_path'...";
	if ( $shell eq "zsh" ) {
		$status=`$connect $run_machine "mkdir -p $tmp/.ZMTk.tmp$$ && scp -r $tmp/.ZMTk.tmp$$ '$ntp_path' 2>&1"`;
	}
	else {
		$status=`zsh -c "$connect $run_machine 'mkdir -p $tmp/.ZMTk.tmp$$ && scp -r $tmp/.ZMTk.tmp$$ "$ntp_path"' 2>&1"`;
	}	
	if ( $status eq "" ) { 
		$rcp = 1;
		print STDOUT "OK.\n";
	}
	else {
		$rcp = 0;
		print STDOUT "failed.\n";
	}	
}
die "\nError: Can't automatic rcp or scp from '$run_machine' to '$ntp_path'.\nTry 'klog'? See 'doc/INSTALL'\n" unless ($rcp == 1 || $scp == 1);
if ( $scp==1 ) { $copy="scp" }

#7.Dealing with NOISE, DEAD and VTX
$j=0;
@name=qw (NOISYCELL DEADMATERIAL VERTEX);
#bet you didn't know this is a real FOR loop, hoho~~~
for $i ($NOISYCELL, $DEADMATERIAL, $VERTEX) { 
	if ( $shell eq "zsh" ) {
		$status=`$connect $run_machine ls "$i" 1>/dev/null`;
	}
	else {
		$status=`zsh -c "$connect $run_machine ls $i >/dev/null 2>&1"`;
	}	
	if ( $status ne "" ) {
		print STDOUT "\n$name[$j] files moved? Couldn't find any on '$run_machine:$i'\n";
		print STDOUT "If you need these files, please give the updated location on '$run_machine', otherwise hit 'Enter':\n";
		$new_location=&get_location;
		if ( $new_location eq "" ) { print STDOUT "Looks like you don't need these files anyway, OK.\n"; next }
		if ( $shell eq "zsh" ) {
			$status=`$connect $run_machine ls "$new_location" 1>/dev/null`;
		}
		else {
			$status=`zsh -c "$connect $run_machine ls $new_location >/dev/null 2>&1"`;
		}		
		die "\nError: Couldn't find $name[$j] files at $new_location on $run_machine.\n" unless ( $status eq "");
		$$name[$j]=$new_location;
	}	
	$j++;
}	

#########################################
#INSTALLATION BEGINS:
#########################################

#9.start actual installtion...
print STDOUT "\n*************************************************************************************\n";
print STDOUT "Configuration succeeded!\n";
sleep(1);
print STDOUT "Installing $program $version...\n\n";
sleep(3);
#try to delete remote temporary dir ( ! don't do it now, may need to reuse $tmp in 'joba')
#`$connect $run_machine "rm -fr $tmp >/dev/null 2>&1"`;
#try to delete local temporary dir
@ntp_path=split /:/, $ntp_path, 2;
$connect2=$scp ? "ssh" : "rsh";
if (  $ntp_path[0] ne $ENV{HOST} ) {
# have to put ' ' around .tmp* to avoid stupid 'zsh: no matches found' error msg
	system($connect, $run_machine, "$connect2 $ntp_path[0] rm -fr '$ntp_path[1]/.ZMTk.tmp*' >/dev/null 2>&1"); 	
}	
else {
#here we don't need to put '' around '.tmp*', wonder why?
	system("rm -fr $ntp_path[1]/.ZMTk.tmp* >/dev/null 2>&1");
}

#for the same reason as above, when $script_path contains some special charaters, it'd be taken care of 'specially'
$script_path1=$script_path;
$script_path1 =~ s/([^:a-zA-Z_0-9\-\/\.%+@])/\\$1/g;
`rm -f '$script_path1/joba*' >/dev/null 2>&1`;
open (IN, "bin/joba.export") || die "Error: Can't open output 'bin/joba.export'.\n";
open (OUT,">$script_path/joba.export") || die "Error: Can't output '$script_path/joba.export'.\n";
while (<IN>) {
#customized settings!
	s/MY_NTP_DIR=zwisc01:\/data\/zwisc01\/liangli\/tmp/MY_NTP_DIR="$ntp_path"/;
	s/MY_SCRIPT_DIR=\/afs\/desy\.de\/user\/l\/liangli\/bin/MY_SCRIPT_DIR="$script_path1"/;
	s/SUB_MACHINE=zenithsub/SUB_MACHINE="$sub_machine"/;
	s/RUN_MACHINE=zenith/RUN_MACHINE="$run_machine"/;
	s/JOB_PATH=\/jobspool0/JOB_PATH="$job_path"/;
	s/CONNECT=rsh/CONNECT="$connect"/;
	s/COPY=rcp/COPY="$copy"/;
	s/EXTENSION=ntp/EXTENSION="$extension"/;
	s/TMP=\/tmp/TMP="$tmp"/;	
	print OUT $_;
}	
close(IN);
close(OUT);
print STDOUT "Installing 'joba.export' to '$script_path/' ok.\n";

open (IN, "bin/joba") || die "Error: Can't open 'bin/joba'.\n";
open (OUT,">$script_path/joba") || die "Error: Can't output to '$script_path/joba'.\n";
while (<IN>) {
#if not $afs then zenith can't see $script_path directly, do some additional work, see 'joba'
	if ( ! $afs==1 ) {
		s/^##//;
	}
	print OUT $_;
}	
close(IN);
close(OUT);
die "\nError: Can't chmod '$script_path/joba'\n" unless chmod (0755, "$script_path/joba");
print STDOUT "Installing 'joba' to '$script_path/' ok.\n";

for (@files) { 
	if ( $_ ne "joba" && $_ ne "joba.export" ) {
		`cp 'bin/$_' $script_path >/dev/null 2>&1`;
		die "Error: Copy '$_' to '$script_path/' failed.\n" unless -r "$script_path/$_" && -f "$script_path/$_" && -w "$script_path/$_";
		print STDOUT "Installing '$_' to '$script_path/' ok.\n"
	}
}

open (IN, "bin/sub") || die "Error: Can't open 'bin/sub'.\n";
open (OUT,">$orange_path/cmd/sub") || die "Error: Can't output to '$script_path/sub'.\n";
while (<IN>) {
	s/I_MACH=zenithsub/I_MACH=$sub_machine/;
	s/NOISYCELL=.*/NOISYCELL=$NOISYCELL/;
	s/DEADMATERIAL=.*/DEADMATERIAL=$DEADMATERIAL/;
	s/VERTEX=.*/VERTEX=$VERTEX/;
	print OUT $_;
}	
close(IN);
close(OUT);
die "\nError: Can't chmod '$orange_path/cmd/sub\n" unless chmod (0755, "$orange_path/cmd/sub");
print STDOUT "Installing 'sub' to '$orange_path/cmd/' ok.\n";

for (@files2) {
	if ( $_ ne "sub" ) {
		`cp 'bin/$_' '$orange_path/cmd' >/dev/null 2>&1`;
		die "Error: Copy '$_' to '$orange_path/cmd/' failed.\n" unless  -r "$orange_path/cmd/$_" && -f "$orange_path/cmd/$_" && -w "$orange_path/cmd/$_";
		print STDOUT "Installing '$_' to '$orange_path/cmd/' ok.\n"
	}	
}

open (IN, "bin/zeusinfi") || die "Can't open 'bin/zeusinfi'.\n";
open (OUT,">$orange_path/cards/zeusinfi") || die "Can't open output '$orange_path/cards/zeusinfi'.\n";
while (<IN>) {
#make zeusinfi know some environment settings
	print OUT $_;
	if ( /#\$RUN_MACHINE/ ) {
		print OUT "\$CONNECT=\"$connect\";\n";
		print OUT "\$RUN_MACHINE=\"$run_machine\";\n";
	}
}	
close(IN);
close(OUT);
die "\nError: Can't chmod '$orange_path/cards/zeusinfi'\n" unless chmod (0755, "$orange_path/cards/zeusinfi");
print STDOUT "Installing 'zeusinfi' to '$orange_path/cards/' ok.\n";
	

#10.installation complete, 
print STDOUT "\n*************************************************************************************\n";
print STDOUT "$program $version Installation succeeded!\n";
if ( ! $afs== 1 ) { print STDOUT "
$program was installed to a non-AFS directory, this may cause $program to be unstable and run slower. 
It's highly recommended to reinstall $program to AFS area if possible.\n"
}

#11.bye
print STDOUT "\n*************************************************************************************\n";
print STDOUT "Rerun this script or directly modify configuration file $script_path/joba.export to change your settings.\n";
print STDOUT "Please read documentations in 'doc' carefully before using $program.\n";
print STDOUT "Also, don't forget to add '$script_path' to your environment PATH. See $dirname/doc/README\n";
print STDOUT "Thank you for using $program $version.\n\n";


