package Util::Tool; ############################################################################## # # DESCRIPTION: This class provides an interface to external programs. # DESCRIPTION: It handles error checking and logging and allows access # DESCRIPTION: to all aspects of input and output to the program. # DESCRIPTION: Any output to stderr or a non-zero exit status from the program # DESCRIPTION: signals an error. # DESCRIPTION: # DESCRIPTION: Sub-classes like Util::Ftool, Util::PseudoFtool, # DESCRIPTION: Util::Extractor, and Util::Stool give additional functionality # DESCRIPTION: for specific types of programs. # # HISTORY # HISTORY: $Log: Tool.pm,v $ # HISTORY: Revision 1.4 2014/02/27 07:01:07 apsop # HISTORY: VERSION header now shows CVS Revision # HISTORY: # HISTORY: Revision 1.3 2011/01/18 20:36:30 apsop # HISTORY: Added code to change seriouseness of some errors from critical (2) # HISTORY: to warnings (1). # HISTORY: # HISTORY: Revision 1.2 2006/08/01 20:35:34 apsop # HISTORY: Add in CVS history indicator. # HISTORY: # HISTORY: 1.0 -> 1.1 2001-03-06 # HISTORY: Added more sophisticated exit status interpretation # HISTORY: to detect core dumps and termination signals. # HISTORY: Now delete core files in cleanup. # HISTORY: # HISTORY: 1.1 -> 1.2 2002-04-23 # HISTORY: Added name method # HISTORY: # HISTORY: 1.2 -> 1.3 2003-06-19 # HISTORY: Now redirect stdin from /dev/null unless some stdin text has # HISTORY: been specified. # HISTORY: # HISTORY: 1.3 -> 1.4 2004-02-11 # HISTORY: Moved the environment setup functionality that was previously # HISTORY: in ParfileTool.pm into this class. # # VERSION: $Revision: 1.4 $ # ############################################################################## use IPC::Open3; #use Util::Log; use Config; use strict; my $LOG; my @SIG_NAME; ########################################################################### # this begin function sets up an index of signal names in the class data ########################################################################## sub BEGIN { @SIG_NAME=(); ######################################## # might not be defined on some systems ######################################## unless( defined $Config{sig_name} ) { return } @SIG_NAME = split(' ', $Config{sig_name}); } # end of BEGIN function ###################################### # constructor ###################################### sub new { #(path,command) my $proto = shift; my $class = ref($proto) || $proto; my $self={}; $self->{PATH} = shift; $self->{COMMAND} = shift; ############################################################# # treat a null path as an error, since we want to avoid # silently running something without explicit path if the path # is null by accident. ############################################################# if(!$self->{PATH} ) { if($LOG) { $LOG->error(2, "null path given for $self->{COMMAND} command"); } else { print STDERR "null path given for $self->{COMMAND} command\n"; exit 1; } $self->{PATH}="."; } ###################################################### # to run something without explicit path, # specify "system" ###################################################### if($self->{PATH} eq "system") {$self->{PATH} = ''}; $self->{ARGUMENTS}=""; $self->{SERIOUSNESS}=2; # level of error to give $self->{VERBOSE}=1; # should stdout be dumped to the log if no error? $self->{LIBS}=[]; $self->{BINS}=[]; $self->{ENVIRONMENT}={}; $self->{STDIN} = ""; $self->{STDOUT} = ""; $self->{STDERR} = ""; $self->{STATUS} = 0; $self->{CORE_DUMPED} = 0; $self->{SIGNAL} = 0; $self->{HAD_ERROR} = 0; $self->{STDOUT_FILE}=""; $self->{CLOBBER_STDOUT_FILE}=0; $self->{IN_BUFFER_SIZE}=8192; $self->{OUT_BUFFER_SIZE}=1024; $self->{ERR_BUFFER_SIZE}=1024; bless($self,$class); return $self; } ##################################### # ACESSORS: ##################################### #################################################### # get or set the Util::Log object in the class data #################################################### sub log { my $self = shift; if (@_) { $LOG = shift } return $LOG; } #################################################### # get or set the error level to use when logging # errors from the tool. #################################################### sub seriousness { my $self = shift; if (@_) { $self->{SERIOUSNESS} = shift } return $self->{SERIOUSNESS}; } #################################################### # get or set the text which will be fed to stdin when # running the tool #################################################### sub stdin { my $self = shift; if (@_) { $self->{STDIN} = shift } return $self; } ######################################################################### # get or set the command line arguments which will be given to the tool. # multiple arguments are concatenated ######################################################################### sub command_line { my $self = shift; if (@_) { $self->{ARGUMENTS} = join ' ', @_ } return $self; } ######################################################################### # set the amount of output which will logged. # - verbose(0) will not log stdout unless there was an error # - verbose(1) will log all stdout # Sub-classes may define higher levels of verbosity. Note errors are # always reported. ######################################################################### sub verbose { my $self = shift; if (@_) { $self->{VERBOSE} = shift } return $self; } ######################################################################### # return the verbosity level (see above) ######################################################################### sub verbose_level { my $self = shift; return $self->{VERBOSE}; } ######################################################################### # return the command including its full directory path (if specified) # and the command line arguments. ######################################################################### sub command { my $self=shift; if($self->{PATH}) { return "$self->{PATH}/$self->{COMMAND} $self->{ARGUMENTS}"; } else { return "$self->{COMMAND} $self->{ARGUMENTS}"; } } ######################################################################### # return the name of the command (no directory path or arguments) ######################################################################### sub name { my $self=shift; return $self->{COMMAND}; } ###################################################### # stdout text from running the program ###################################################### sub stdout { my $self = shift; if (@_) { $self->{STDOUT} = shift } return $self->{STDOUT}; } ###################################################### # stderr text from running the program ###################################################### sub stderr { my $self = shift; if (@_) { $self->{STDERR} = shift } return $self->{STDERR}; } ###################################################### # exit status given by the program ###################################################### sub status { my $self = shift; if (@_) { $self->{STATUS} = shift } return $self->{STATUS}; } ##################################################################### # returns non-zero if an error was detected when running the program ##################################################################### sub had_error { my $self = shift; if (@_) { $self->{HAD_ERROR} = shift } return $self->{HAD_ERROR}; } ################################################################# # redirect stdout to the given file when the program is run. # The optional clobber argument specifies whether the file # will be deleted or appended to if it already exists. ################################################################# sub stdout_file { #(file, clobber) my $self = shift; $self->{STDOUT_FILE} = shift; my $clobber=shift; if(defined $clobber) { $self->{CLOBBER_STDOUT_FILE}=$clobber} return $self; } ######################################################################### # return all shared object library directories for this software package # These will be prepended to LD_LIBRARY_PATH when the tool is run ######################################################################### sub libs { my $self=shift; if(@_) { $self->{LIBS} = [@_]; } return @{$self->{LIBS}}; } ######################################################################### # return all executable directories for this software package # These will be prepended to PATH when the tool is run ######################################################################### sub bins { my $self=shift; if(@_) { $self->{BINS} = [@_]; } return @{$self->{BINS}}; } ######################################################################## # Returns a reference to a hash of environment variables which must # be set before running this tool ######################################################################## sub environment { my $self=shift; if(@_) { $self->{ENVIRONMENT} = shift; } return $self->{ENVIRONMENT}; } # end of environment method ###################### # METHODS: ###################### sub slurpFile { my ($path) = @_; open(SLURP, $path); my $text = ''; while (<SLURP>) { $text .= $_; } close(SLURP); return $text; } ########################################################################## # run the tool. Calls "init" before running and "cleanup" after running. ########################################################################## sub run { my $self = shift; my $arguments=shift || ""; $self->init(); ######################################################### # run the command and catch both stdout and stderr ######################################################### my $command=$self->command(); my $stdin = '/dev/null'; if ($self->{STDIN}) { $stdin = './stdin'; unlink($stdin); open(XXX, ">$stdin"); print XXX $self->{STDIN}; close(XXX); } my $stdout = './stdout'; my $stderr = './stderr'; unlink($stdout, $stderr); $self->{STDOUT}=''; $self->{STDERR}=''; my $status = system("$command < $stdin 1>$stdout 2>$stderr"); if (-s $stdout) { $self->{STDOUT} = slurpFile($stdout); } if (-s $stderr) { $self->{STDERR} = slurpFile($stderr); } $self->{CORE_DUMPED} = $status & 128; $self->{SIGNAL} = $status & 127; $self->{STATUS} = $status >> 8; ############################ # run the cleanup method ############################ $self->cleanup(); return $self; } ########################################################## # initialize a tool before running. # This method initialized the "had_error" flag and sets up # the environment for the tool. # Sub-classes may override this. # For example Util::Ftool uses this method to set up the # parameter file. ########################################################## sub init { my $self=shift; ############################ # initialize the error flag ############################ $self->{HAD_ERROR}=0; ######################################################### # set the LD_LIBRARY_PATH environment variable # to include all the shared object libraries # for this package. Remember the original value # so we can restore it after running the tool. ######################################################### $self->{ORIGINAL_LD_LIBRARY_PATH}=$ENV{LD_LIBRARY_PATH}; my @libs=$self->libs(); if(@libs) { $ENV{LD_LIBRARY_PATH}=join(':', @libs) . ":$ENV{LD_LIBRARY_PATH}"; } ########################################################### # now do the same thing with the PATH environment variable # sometimes scripts need the path set ########################################################### $self->{ORIGINAL_PATH}=$ENV{PATH}; my @bins = $self->bins(); if(@bins) { $ENV{PATH}=join(':', @bins) . ":$ENV{PATH}"; } ############################################## # set the parameter file environment variables ############################################## my %environment=%{$self->environment()}; $self->{ORIGINAL_ENVIRONMENT}={}; foreach (keys %environment ) { ############################ # remember the old value ############################ if(defined $ENV{$_}) { ${$self->{ORIGINAL_ENVIRONMENT}}{$_} = $ENV{$_}; } ################################ # set the new value ################################ $ENV{$_} = $environment{$_}; } } # end of init method ###################################################### # check for errors and do general cleanup ###################################################### sub cleanup { my $self=shift; my $log=$self->log(); my $was_error=$self->{STATUS} || $self->{STDERR} || $self->{CORE} || $self->{SIGNAL}; ############################## # log the stdout ############################## if( ($self->{VERBOSE} || ($was_error && $self->{SERIOUSNESS})) && $self->{STDOUT} && $log) { $log->entry("stdout output from $self->{COMMAND}"); $log->text($self->{STDOUT}); } ###################################################################### # # Handling some errors differently than the standard way if($self->{COMMAND} eq 'attjumpcorr'){ if($self->{STATUS} == 0 && $self->{STDERR} =~ /\s*WARNING\:\s+number\s+of\s+jumps\s+detected\s+in\s+this\s+slew\s+\(\d+\)\s+is\s+greater\s+than\s+the\s+maximum/){ $self->{SERIOUSNESS} = 1; } }elsif($self->{COMMAND} eq 'aspect'){ if($self->{STATUS} == 0 && $self->{STDERR} =~ /Warning\:\s+Exceeded\s+the\s+maximum\s+number\s+of\s+iterations/){ $self->{SERIOUSNESS} = 1; } }elsif($self->{COMMAND} eq 'bateconvert'){ if($self->{STATUS} == 0 && $self->{STDERR} =~ /WARNING\:\s+Time\s+separation\s+between\s+data\s+\(MET\=\d+\.*\d*\)\s+and\s+gain\/offset\s+sample/){ $self->{SERIOUSNESS} = 1; } }elsif($self->{COMMAND} eq 'uvotimsum'){ if($self->{STATUS} == 3 && $self->{STDOUT} =~ /error\:\s+combining\s+FRAMTIMEs\s+not\s+allowed/){ $self->{SERIOUSNESS} = 1; } }elsif($self->{COMMAND} eq 'xrt2fits'){ if($self->{STATUS} == 0 && $self->{STDOUT} =~ /BACKWARDS\s+TIME\s+JUMP\s+New\s+frame/){ $self->{SERIOUSNESS} = 1; } } ########################## # check for errors ########################## if($was_error && $self->{SERIOUSNESS}) { $self->error(); } ############################ # dump the error log ############################ if( $self->{STDERR} && $log && $self->{SERIOUSNESS}) { $log->entry("stderr output from $self->{COMMAND}"); $log->text($self->{STDERR}); } ############################################# # dump stdout to a file if one is specified ############################################# if($self->{STDOUT_FILE}) { if( $self->{CLOBBER_STDOUT_FILE} ) {unlink $self->{STDOUT_FILE} } open FILE, ">>$self->{STDOUT_FILE}"; print FILE $self->{STDOUT}; close FILE; } ####################################### # reset the environment ####################################### $ENV{PATH} = $self->{ORIGINAL_PATH}; $ENV{LD_LIBRARY_PATH} = $self->{ORIGINAL_LD_LIBRARY_PATH}; foreach (keys %{$self->{ORIGINAL_ENVIRONMENT}} ) { $ENV{$_} = ${$self->{ORIGINAL_ENVIRONMENT}}{$_}; } } # end of cleanup method ################################################################### # assemble the text of an error message # Sub-classes may override this method to give more information ################################################################### sub error_message { my $self=shift; my $message="Error from $self->{COMMAND} - exit status $self->{STATUS}"; return $message; } ############################################## # internal error handler ############################################## sub error { my $self=shift; ##################################### # mark the fact that we had an error ##################################### if( $self->{STATUS} || $self->{CORE} || $self->{SIGNAL} ){ $self->{HAD_ERROR}=2; }else{ $self->{HAD_ERROR}=1; } ########################################## # Don't log the error if seriousness is 0 ########################################## return unless $self->{SERIOUSNESS}; ################################# # log the error ################################# my $log=$self->log(); my $message=$self->error_message(); if($self->{CORE_DUMPED}) { $message .= " Core Dumped."; #system("gdb $self->{PATH}/$self->{COMMAND} core"); unlink "core"; } if($self->{SIGNAL}) { $message .= " Terminated with signal $self->{SIGNAL}"; if(defined $SIG_NAME[$self->{SIGNAL}] ) { $message .= "($SIG_NAME[$self->{SIGNAL}])"; } $message .= "."; } if($log) { ############################################ # log is defined so use it to give an error ############################################ $log->error($self->{SERIOUSNESS},$message); $log->entry("Command: ".$self->command()); } else { ################################### # we don't have the log yet, so just # write to stderr and quit #################################### print STDERR "Error$self->{SERIOUSNESS}: $message\n"; exit $self->{STATUS}; } } sub DESTROY { my $self=shift; ################################### # delete files that capture output ################################### unlink ('stdout', 'stderr'); } 1;