require "flush.pl";

#
# Arguments
#

if($#ARGV < 1) {
    print STDERR "Usage: job_start.pl job_number station [port]\n";
    exit 1;
}


$JOB_NUMBER   = $ARGV[0];
$STATION      = $ARGV[1];
$PORT         = $ARGV[2];


#
# Maximum execution time in seconds before timeout.
# NEOS timeout set to one week on 09/01/1999
#

$MAX = 604800;

#
# These parameters are meant to make flock calls more readable.
#

$LOCK_SH = 1;
$LOCK_EX = 2;
$LOCK_NB = 4;
$LOCK_UN = 8;


if (-f "$ENV{SERVER_LIB}/txt/announcement.txt") {
  $ANNOUNCEMENT = `cat $ENV{SERVER_LIB}/txt/announcement.txt`;
}

$SIG{'ALRM'} = \&handler;

#
# Go to the job's directory, get job type, and title of solver
#

chdir("$ENV{SERVER_JOBS}/job.$JOB_NUMBER");

open(CAT, "job.type") or &handler("No job type file found for job $JOB_NUMBER.\n");
chomp($TOKEN = <CAT>);
$TOKEN =~ /(.*):(.*)/;			     
close CAT;

if ($TOKEN =~ /^ADMIN:.*$/) {
  $SOLVERS = $ENV{SERVER_ADMIN_SOLVERS};
} else {
  $SOLVERS = $ENV{SERVER_SOLVERS};
} 

if (-f "$SOLVERS/$TOKEN/TITLE") {
    open(CAT, "$SOLVERS/$TOKEN/TITLE") or &handler("Could not open file TITLE in $SOLVERS/$TOKEN.\n");
    chomp($TITLE = <CAT>); 
    close CAT;
} else {
    $TITLE = $TOKEN; 
}


#
# Write job.results header
#

if (-f "job.results") {
    system("mv job.results job.results.tmp") and &handler("job_start.pl: $!");
}
open(CAT, "job.start") or &handler("Could not read from job.start file for job $JOB_NUMBER.\n");
chomp($START_TIME = <CAT>);	
close CAT;
$END_TIME   = "<END_TIME_GOES_HERE>";
				# 
$DISCLAIMER = `cat $ENV{SERVER_LIB}/txt/disclaimer.txt 2>/dev/null`;
open(FINAL_RESULTS,">job.results") or &handler("job_start.pl: Could not open job results file for job $JOB_NUMBER\n");
write FINAL_RESULTS; 
close(FINAL_RESULTS);
if (-f "job.results.tmp") {
    system("cat job.results.tmp >> job.results") and &handler("job_start.pl: Could not open job results file for job $JOB_NUMBER\n");
    system("rm -f job.results.tmp") and &handler("job_start.pl: Could not delete $JOB_NUMBER/job.results.tmp");
}
@statList = stat("job.results");
$size = $statList[7];


#
# DONE file will exist if job was killed
#
			        
if(-f "$ENV{SERVER_JOBS}/job.$JOB_NUMBER/DONE") {
    open(LOCKFILE, ">job.lock");
    close(LOCKFILE);
    open(RES, ">>$ENV{SERVER_JOBS}/job.$JOB_NUMBER/job.results");
    print RES "Job dequeued by administrative Kill Job solver.\n";
    close RES;
    &email;
    &forward;
    &log;
    exit 0;
}
			       

unless ((-f "BAD_PARSE") || (-f "LIMIT_REACHED") || (-f "NO_PORT") || (-f "TIMEOUT") || (-f "QFULL")) {
    if ($TOKEN !~ /^ADMIN:\S*$/) { 

    #
    # remote solvers 
    #

	if(open (PASS, "job.password")) {
	    chomp($JOB_PASS = <PASS>);
	    close PASS;
	}			 
	open (HANDLE, ">>job.out");
	print HANDLE "<CLEAR_SCREEN>\n";
	print HANDLE "\n\n*************************************************************\nExecuting: job #$JOB_NUMBER  with password $JOB_PASS\n*************************************************************\n\n";
	    close (HANDLE);
	
	#
	# Tar-up user data and send to remote solver
	#
	
	chop($files = `cat PARSED_FILES`);
	system("tar -cf data.tar $files job.address") and &handler("job_start.pl: Could not tar files for job $JOB_NUMBER");
	system("gzip data.tar") and &handler("job_start.pl: Could not zip files for job $JOB_NUMBER");
	$files =~ s/job.forward//g; 
	system("rm -f $files") and &handler("job_start.pl: Could not remove $files from job $JOB_NUMBER directory");
	@statList = stat("data.tar.gz");
	$dataSize = $statList[7];
	exec("$^X $ENV{SERVER_BIN}/job_client.pl -request \"begin job $dataSize $JOB_NUMBER -token $TOKEN\" -server $STATION:$PORT -recvBuffer 1 -alarm $MAX >>job.out < data.tar.gz 2>&1");
     die "$!: returned from job_client exec!\n";
    } else {
	
	#
	# local admin_solvers
	#
	
	open(LOCKFILE, ">job.lock");
	flock LOCKFILE, $LOCK_SH;
	open (HANDLE, ">>$ENV{SERVER_JOBS}/job.$JOB_NUMBER/job.out");
	print HANDLE "<CLEAR_SCREEN>\n";
	print HANDLE "Executing on localhost.\n\n";
	close (HANDLE);
	
	chop($DRIVER = `cat $ENV{SERVER_ADMIN_SOLVERS}/$TOKEN/SOLVE`);
	$DRIVER =~ s/<SERVER_PERL>/$ENV{SERVER_PERL}/g;
	$DRIVER =~ s/<SERVER_ADMIN_SOLVERS>/$ENV{SERVER_ADMIN_SOLVERS}/g;
	alarm($MAX);
	$STATUS = system("$DRIVER $JOB_NUMBER >>job.out 2>&1"); 
	alarm(0);
    }
    
    #
    # <END_STANDARD_OUT> is a signal to the socket server to stop
    # reading "job.out"  
    #
				 
    open (HANDLE, ">>$ENV{SERVER_JOBS}/job.$JOB_NUMBER/job.out") or &handler("job_start.pl: Could not open job.out for job $JOB_NUMBER");
    print HANDLE "<END_STANDARD_OUT>\n";
    close (HANDLE);
    
} else {
    open(LOCKFILE, ">job.lock");
    flock LOCKFILE, $LOCK_SH;
    open (HANDLE, ">>$ENV{SERVER_JOBS}/job.$JOB_NUMBER/job.out") or &handler("job_start.pl: Could not open bad job job.out file for job $JOB_NUMBER");
    print HANDLE "<CLEAR_SCREEN>\n";
    print HANDLE "---------Begin Standard output/error--------\n";
    print HANDLE "Error parsing or scheduling job on $ENV{SERVER_NAME} Server.\n";
    print HANDLE "Executing on localhost.\n\n";
    print HANDLE "---------End Standard output/error----------\n";
    print HANDLE "<END_STANDARD_OUT>\n";
    close (HANDLE);
}				 
				
RESULTS: 
    
#
# Add END_TIME to job results.
#    

($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);  
$year += 1900;
$mon  += 1;
$endDate = "$mon/$mday/$year";
$endTime = "$hour:$min:$sec"; 
$END_TIME = "$endDate $endTime";
$length = length($END_TIME);
for ($i=0;$i<(20-$length);$i++) {
  $END_TIME .= " ";
}
open(HANDLE, "+<job.results") or &handler("job_start.pl: Could not open job.results for job $JOB_NUMBER");
seek(HANDLE,0,0);
read(HANDLE,$buffer,$size);
$buffer =~ s/<END_TIME_GOES_HERE>/$END_TIME/;
seek(HANDLE,0,0);
print HANDLE $buffer;
close(HANDLE);

&email;
&forward;
&log;


#
# Signal cleaner to clean
#

flock LOCKFILE, $LOCK_UN;
close(LOCKFILE);
open(TOUCH, ">DONE") or &handler("job_start.pl: Could not touch DONE file for job $JOB_NUMBER");
close TOUCH;

#
# The End
#

#
# Format for job results.
#

format FINAL_RESULTS =
*************************************************************

    @>>>>>>>>>>>>>>>>>>>>>> Server Version @<<<<<
$ENV{SERVER_NAME}, $ENV{SERVER_VERSION}
                                                         
    Job#   : @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<  
$JOB_NUMBER
    Solver : @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<  
$TITLE
    Start  : @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<  
$START_TIME
    End    : @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
$END_TIME
    Host   : @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
$STATION

@*
$ANNOUNCEMENT 

    Disclaimer:

~   ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
$DISCLAIMER
~   ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
$DISCLAIMER 
~   ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
$DISCLAIMER 
~   ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
$DISCLAIMER 
~   ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
$DISCLAIMER 
~   ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
$DISCLAIMER 

*************************************************************

.

sub handler {
  if(($STATUS & 0xff)==0) {
      $STATUS >>=8;
  }
  open(CAT, ">>job.out") or die "job_start.pl: Could not write to job.out via system call for job $JOB_NUMBER";
  print CAT "\n---------Begin Standard output/error--------\n";
  if(defined($_[0])) {
      print CAT "ERROR $STATUS:\n$_[0]\n";
  } else {
      print CAT "ERROR $STATUS:  Your problem either exceeded the time limit of $MAX seconds, \nor the solving station killed your process.\n"; 
  }
  print CAT "        The software administrator has been notified.\n\n";
  print CAT "---------End Standard output/error----------\n";
  print CAT "<END_STANDARD_OUT>\n";
  close CAT;
  open(HANDLE, ">job.results") or die "job_start.pl: Could not open job.results file for job $JOB_NUMBER";
  if(defined($_[0])) {
      print HANDLE "ERROR $STATUS:\n$_[0]\n";
  } else {
      print HANDLE "ERROR $STATUS:  Your problem either exceeded the time limit of $MAX seconds, \nor the solving station killed your process.\n"; 
  }
  print HANDLE "        The software administrator has been notified.\n\n";
  
  close(HANDLE);
  if (-s "$SOLVERS/$TOKEN/CONTACT") {
    chop($CONTACT = `cat $SOLVERS/$TOKEN/CONTACT`);
    open(HANDLE, "|Mail -s '$TOKEN' $CONTACT");
    print HANDLE "Notice for $TOKEN job #$JOB_NUMBER:\n\n";
    if(defined($_[0])) {
	print HANDLE "\n$_[0]\n";
    } else {			
	print HANDLE "ERROR: Your solver timed out after $MAX seconds.\n";
    }
    print HANDLE "       Here is the standard out/error from your code:\n\n";
    print HANDLE "------------------------------------------------------------\n\n";
    $error = `cat job.out`;
    print HANDLE $error;
    close (HANDLE);
  }
  goto RESULTS;
}

sub commsfail {
    &handler("Communications failed with remote solver.  Data lost.");
}
 
sub fsleep {
  select(undef, undef, undef, $_[0]);
}

sub email {
#
# Send results to email addresses
#

    open(CAT, "job.interface") or &handler("Could not open interface file for job $JOB_NUMBER.\n");
    chomp($interface = <CAT>);
    close CAT;

    if ($interface eq "mail") 
    {
	open(CAT, "job.address") or &handler("Could not open address file for job $JOB_NUMBER.\n");
	chomp($address = <CAT>);
	close CAT;

	open(CAT, "job.results") or &handler("Could not open job.results file for reading for job $JOB_NUMBER.\n");
	@results = <CAT>;
	close CAT;
	
	$results = join '', @results;

	open(MAIL, "|sendmail -oi -t") or &handler("job_start.pl: Denied access to sendmail program\n");
	print MAIL <<END_SOLVER_RESULTS;
To: $address 
From: $ENV{SERVER_NAME} Server <$ENV{SERVER_EMAIL}>
Subject: Job #$JOB_NUMBER
	      
$results
END_SOLVER_RESULTS
        close(MAIL);
    }			       
}

sub forward {
#
# Forward results if job.forward is present
#

    if (-s "job.forward") {
	if (!$results) {
	    open(CAT, "job.results") or &handler("Could not open job.results file for reading for job $JOB_NUMBER.\n");
	    @results = <CAT>;
	    close CAT;
	    $results = join '', @results;
	}
	if (!$address) {
	    open(CAT, "job.address") or &handler("Could not open address file for job $JOB_NUMBER.\n");
	    chomp($address = <CAT>);
	    close CAT;
	}
	open(CAT, "job.forward") or &handler("Could not read from job.forward for job $JOB_NUMBER");
	chomp($forward_address = <CAT>);
	close CAT;

	open(MAIL, "|sendmail -oi -t") or &handler("job_start.pl: Denied access to sendmail program\n");
	print MAIL <<END_SOLVER_RESULTS;
To: $forward_address 
From: $ENV{SERVER_NAME} Server <$ENV{SERVER_EMAIL}>
Subject: Job #$JOB_NUMBER

$results
END_SOLVER_RESULTS
        close(MAIL);
    }

}

sub log {

#
# These parameters are meant to make flock calls more readable.
#

    $LOCK_SH = 1;
    $LOCK_EX = 2;
    $LOCK_NB = 4;
    $LOCK_UN = 8;

    $warnings = " ";
    open(CAT, "job.start") or &handler("Could not read from job.start file for job $JOB_NUMBER.\n");
    chomp($start = <CAT>);	
    close CAT;
				
    $start =~ /(.+) (.+)/;
    $startDate = $1;
    $startTime = $2;
    
#
# note on perl flock:  this may fail if the database
# is being accessed across an nfs
#

    system("touch $ENV{SERVER_VAR}/databases/master") and &handler("job_start.pl: Could not write to master database for job $JOB_NUMBER"); # make sure it exists
    chomp($address = `cat job.address`) if (!$address);
    open(MASTER, "+<$ENV{SERVER_VAR}/databases/master") or &handler("job_start.pl: Could not write to master database for job $JOB_NUMBER");
#
#get an exclusive lock on the database
#wait if necessary
#
    $locked = flock MASTER, $LOCK_EX | $LOCK_NB;
    $wait = 0;
    while(!($locked))
    {
	$wait++;
	if ($wait > 120)
	{
	    # break out of loop
	    # and warn in database
	    $warnings = "Flock FAILED";
	    last;
	}
	sleep(2);
	$warnings = "Slept and flocked";
	$locked = flock MASTER, $LOCK_EX | $LOCK_NB;    
    }

#
# Write to 1K job record in master file
#

    $sought = 200*($JOB_NUMBER-1);
    seek(MASTER, 0, 2);
    $curpos = tell MASTER;
    while(($curpos) < ($sought)) {
	$empty = "";
	print MASTER pack("A10 A10 A8 A10 A8 A16 A30 A50 A50 A7 A", 
			  $empty,$empty,$empty,$empty,$empty,$empty, 
			  $empty,$empty,$empty,$empty,"\n");
	seek(MASTER, 0, 2);
	$curpos = tell MASTER;
	$warnings = $warnings."e"; 
    }
#
# Get the ends of addresses that are too long, since the domains
# are of greater interest than address beginnings with no domains.
#
    $address = substr($address, -50, 50);
    $forward_address = substr($forward_address, -50, 50);
    seek(MASTER, $sought, 0);
    print MASTER pack("A10 A10 A8 A10 A8 A16 A30 A50 A50 A7 A", 
		      $JOB_NUMBER, 
		      $startDate, $startTime, $endDate, $endTime, 
		      $interface, $TOKEN, $address, $forward_address, $warnings, "\n");
    &flush(MASTER);
    flock MASTER, $LOCK_UN;
    close(MASTER);
}				







