#!/opt/SUNWcest/bin/perl -w
#
#	DIMM Replacement Policy Tool
#
#	Author:		Mike.Arnott@Sun.COM
#
#	Created:	Monday October 18 11:15:58 EST 2004
#
#	$RCSfile: cediag,v $
#	$Revision: 1.94 $
#	$Date: 2007/07/26 22:55:08 $
#
#	Technical Contributions (alphabetical order):-
#
#	. Darin.Carlson@Sun.COM (USA)
#	. Doug.Baker@Sun.COM (UK)
#	. Steve.Chessin@Sun.COM (USA)
#

################################################################################
#### data table schema #########################################################
################################################################################
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# %ce - hash of discovered CE record data (recycled at each CE discovery)
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# %ce / 'afar' - CE AFAR data (0x00000000.00000000)
# %ce / 'bit' - CE Error Bit data
# %ce / 'cpu' - CE CPU data (CPU0)
# %ce / 'date' - CE log date (Apr  1 12:13:23)
# %ce / 'nls' - CE log date (time() format)
# %ce / 'dimm' - CE DIMM data
# %ce / 'errid' - CE ErrID (0x00000000.00000000)
# %ce / 'esynd' - CE Esynd data (0x0000)
# %ce / 'type' - CE type data (CE)
# %ce / 'frequency' - CE frequency (persistent/intermittent)
#
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# %cestat - hash of cestat command output data
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
# %cestat / 'mpr_disabled' - # true if MPR is disable in a MPR-aware kernel
# %cestat / 'retirement_limit' - # pages needed to be retired before PRL is met
# %cestat / 'pages_retired' - # pages actually retired so far
# %cestat / 'pages_retired_percent' - # percentage pages actually retired so far
# %cestat / 'prl_reached' - # page retirement limit met
# %cestat / 'dimm' / $dimm / 'intermittent' - # intermittent CEs for this DIMM
# %cestat / 'dimm' / $dimm / 'persistent' - # persistent CEs for this DIMM
# %cestat / 'dimm' / $dimm / 'sticky' - # sticky CEs for this DIMM
# %cestat / 'dimm' / $dimm / 'p_s' - # Persistent + Sticky CEs for this DIMM
# %cestat / 'dimm' / $dimm / 'suspect' - # suspect flag, 0 or 1
# %cestat / 'suspect_dimm_count' - # of suspect DIMMs found by cestat
#
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# %datapath - hash of discovered datapath fault data (recycled at each reboot)
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# %datapath / 'count' - count of datapath fault messages
# %datapath / 'esynd' / $esynd / 'dimm' / $dimm - # CEs w/ this Esynd on DIMM
#
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# %flag - hash of flags
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# %flag / cestat_ok - true if cestat output was obtained
# %flag / in_dimms - true if currently processing a line in the DIMMs section
#			of cestat output
# %flag / reboot_found - true if a reboot has been seen in the messages file
#
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# %frc - hash of FRC record data (recycled at each reboot)
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# %frc / $errid / 'cpu' - the CPU number that logged the FRC message
# %frc / $errid / 'esynd' - the Esynd logged in the FRC message
# %frc / $errid / 'j_aid' - the J_AID number logged in the FRC message
# %frc / $errid / 'bit' - the bit name logged in the FRC message
# %frc / $errid / 'nls' - FRC messages log date (time() format)
#
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# %ignore - hash of flags of 'things to ignore'
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# %ignore / 'reboot' - ignore reboots, do no reset counters upon seeing a reboot
#
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# %patch - hash of data about patches installed
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# %patch / 'patchnum' / 'rev' - installed revision of patch
#
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# %policy - hash of policy data
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# %policy / 'ruleN' / 'priority' - priority string, e.g. URGENT,
# %policy / 'ruleN' / 'when' - string to advise when to replace
#
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# %pr - hash of discovered [P]age [R]etire data (recycled at each PR discovery)
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# %pr / 'date' - date of 'stage' - syslog() format
# %pr / 'date0' - date of "soft errors in less than" - syslog() format
# %pr / 'date1' - date of "Scheduling removal of page" - syslog() format
# %pr / 'date2' - date of "removed from service" - syslog format
# %pr / 'dimm' - DIMM data
# %pr / 'page1' - page being scheduled for removal
# %pr / 'page2' - page removed from service
#
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# %rce - hash of RCE record data (recycled at each reboot)
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# %rce / $errid / 'cpu' - the CPU number that logged the RCE message
# %rce / $errid / 'afar' - the AFAR logged in the RCE message
# %rce / $errid / 'j_req' - the J_REQ number logged in the RCE message
# %rce / $errid / 'dimm' - the bit name logged in the RCE message
# %rce / $errid / 'nls' - RCE messages log date (time() format)
#
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# %re - hash of [r]egular [e]xpression strings
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# %re / 'afar' - RegExp describing CE AFAR string
# %re / 'date' - RegExp describing syslog date string
# %re / 'errid' - RegExp describing CE errid string
# %re / 'page' - RegExp describing memory page string
#
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# %ue - hash of discovered UE record data (recycled at each reboot)
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# %ue / 'count' - UE count
# %ue / 'date' - date of last UE in time() format (not recycled at each reboot)
################################################################################

################
# requirements #
################

# require a minimum version of Perl
require 5.00503 ;

################
# load modules #
################

# use strict pragma
use strict ;

# allow use of English variable names
# use English ;	# XXX perlvar(1) BUGS says this impacts performance negatively

# use the Cwd module
use Cwd ;

# use the File::Basename module
use File::Basename ;

# use Getopt::Std module
use Getopt::Std ;

# use Time::Local module
use Time::Local ;

# *** disabled as functionality not in use ***
## use Data::Dumper module
#use Data::Dumper ;

########################
# do not buffer STDOUT #
########################

# turn off buffering for STDOUT
$| = 1 ;

#####################
# declare variables #
#####################
my($event);
my($last_ce_stored);
my($last_ce_event);

# declare variables, arrays & hashes
my($_afar) ;	# AFAR name - tmp variables
my($_bit) ;	# Bit name - tmp variables
my($_dimm) ;	# DIMM name - tmp variables
my(%_frc) ;	# FRC hash - tmp hash
my($_m64) ;	# modulo 64 name - tmp variables
my(%_rce) ;	# RCE hash - tmp hash
my(@a) ;	# temporary array
my(@b) ;	# temporary array
my($accepted_license) ; # Accepted license version from license acceptance file
my($lines_per_second) ; # approximate speed rating used for timers
my($adjustments_done) ; # true if no more priority adjustments to be made
my($allow_checkpointing) ; # true if checkpoint is utilized
my($arg) ;	# CLI argument passed
my(%args) ;	# hash of CLI arguments passed
my($args_ok) ;	# flags to see if all args were passed OK
my(@args_bad) ;	# array of bad arguments
my($arch) ;	# ISA/processor type, e.g. sparc, i386
my(%bit2dram) ;	# table to convert memory bit to DRAM on DIMM
my(%ce) ;	# discovered CE record data hash - see schema
my($ce_verbose_memory) ;	# ce_verbose_memory value in /etc/system
			# -2 => running in "manual" (aka "standalone") mode
			# -1 => unable to open /etc/system file
			# 0 => value set to zero in /etc/system
			# 1 => value set to one in /etc/system
			# 2 => value set to two in /etc/system
my(%cedb) ;	# hash of CE data - see schema
my(%cestat) ;	# hash of cestat command output data - see schema
my($cestat_file) ;	# name of a 'cestat -v' output file to use
my($checkpoint_lookback) ; # seconds prior to last checkpoint to resume scanning
my(%counter) ;	# hash of counter data - see schema
my(%datapath) ;	# hash of datapath fault data - see schema
my($debug) ;	# run in debug mode
my(%dram2dimm) ;# table to convert memory DRAM to DIMM
my($edir) ;	# path to Explorer directory to be analysed
my(%efile) ;	# hash of paths to Explorer output files
my(%exe) ;	# hash of OS executables
my(%frc) ;	# discovered FRC record data hash - see schema
my($fh0) ;	# filehandle of open file
my($fh1) ;	# filehandle of open file
my(%file) ;	# hash of paths to output files
my(%fileline) ;	# hash of messages files line counts
my($filename) ;	# tmp variable - see also $messages_file
my(%filesize) ;	# hash of messages files sizes
my(%filetime) ;	# hash of messages files times - time stamp of first line
my(%fire) ;	# hash of rules to be fired
my($first_file_found) ; # True when file skipping has finished
my(%flag) ;	# hash of 'flags' - see schema
my($g_revision) ;	# program revision/modification date info
my(%h) ;	# tmp hash
my($hostname) ;	# hostname of system being analysed
my($hostid) ;	# hostid of system being analysed
my(%ignore) ;	# hash of 'things to ignore' flags - see schema
my($initial_checkpoint) ;  # date/time of last message from last run
				       # minus 24 hours (in time() format)
my($i) ;	# tmp variable
my($i0) ;	# tmp variable
my($j) ;	# tmp variable
my($j0) ;	# tmp variable
my($junk) ;	# tmp variable - throw away immediately
my($k) ;	# tmp variable
my($k0) ;	# tmp variable
my(%kjp) ;	# hash of KJP arrays per osver
my($kjp_inuse) ;	# KJP in-use
my($laf) ;	# pathname to license acceptance file - initialized by
		# accept_licence()
my($last_cycle_time) ;  # timestamp for last execution of timers
my($last_message_time) ;	# syslog date/time of the last message processed
my($last_message_time_nls) ;	# last message processed in time() format
my($lastline) ;	# last line of input from a file
my($line) ;	# a line of input from a file (tmp variable)
my($log) ;	# log file to send status messages to
my($logfh) ;	# log file's filehandle
my($measuring_speed) ;	# used to measure processing rate for use with timers
my($measure_timer) ;	# number of seconds over which to gauge speed
my($measure_start_line) ; # line count when measuring starts
my($message_date) ;	# date in message line
my($message_lines_skipped) ; # number of lines skipped by prescreen
my($messages_dir) ;	# directory to look in for messages files
my($messages_file) ;	# individual messages file to process
my($messages_file_last) ;	# last individual messages file processed
my($messages_file_date) ;	# modification date of messages file
my($messages_file_linenumber) ;	# messages file line number being processed
my($messages_file_totallines) ;	# total number of lines processed
my(@messages_files) ;	# list of messages files to process
my(%mpr) ;	# hash of [m]emory [p]age [r]etirement data - see schema
my($mpr_capable_os) ;	# OS is [m]emory [p]age [r]etirement capable flag
my($mpr_disabled_etc_system) ;	# true if /etc/system file has below variable
				# 'set automatic_page_removal = 0'
my($mpr_enabled) ;	# [m]emory [p]age [r]etirement enabled/disable flag
my($mpr_force) ;	# force MPR enabled/disabled flag
my($mpr_kernel) ;	# [m]emory [p]age [r]etirement kernel in use flag
my(%mpr_kjp) ;	# hash of minimum KJPs per $osver
my($mpr_kjp_minimum) ;	# minimum KJP required for MPR on the examined system
my($mpr_state) ;	# [m]emory [p]age [r]etirement state *string*
				# "active" - MPR available & in-use
				# "disabled" - MPR available but not in-use
				# "unavailable" - MPR not available
my($no_prescreen) ; # turn off pre-screen filter
my($ostype) ;	# operating system type in use, e.g. SunOS
my($osver) ;	# operating system version in use
my($osver_math) ;	# operating system version string suitable for
			# mathematical comparison
my(%page) ;	# hash of memory page info - see schema
my(%patch) ;	# hash of patch data installed on the system being examined
my($platform) ;	# platform from "System Configuration:" line in prtdiag -v
my(%policy) ;	# hash of policy data - see schema
my(%pr) ;	# discovered PR record data hash - see schema
my($priority_level) ; # greater numbers are lower priorities
my(%prlist) ;	# hash of pages scheduled for retirement
my($prl_dm) ;	# PRL detection method
my($processing_started) ;  # Set after previous checkpoint has been reached
my($prog) ;	# name of this script
my($prog_dir) ;	# directory in which this script resides
my($ram) ;	# amount RAM (8K pages) installed on the system being examined
my($ram_prl) ;	# amount RAM (8K pages) to be retired before PRL is reached
my(%rce) ;	# discovered RCE record data hash - see schema
my($rce_dimm_latest) ;	# latest DIMM to have an RCE error
my(%re) ;	# hash of [r]egular [e]expression string - see schema
my(%replace) ;	# hash of replacement data - see schema
my($retval) ;	# temporary return value holder
my($rule5_ce_count) ;	# number of CEs required for rule5 applicability
my($rule5b_ce_count) ;	# number of CEs required for rule5b applicability
my($rule5b_hour_count) ; # number of hours required for rule5b applicability
my($rule6_ce_count) ;	# number of CEs required for rule6 applicability
my($rule6_hour_count) ;	# number of hours required for rule6 applicability
my($sample_time) ;	# time the system info sample was taken
my($skip_prescreen) ;	# skip pre-screen filter to run time-based activities
my($state) ;	# file to save program 'state' to
my($statefh) ;	# state file's filehandle
my($t) ;	# tmp variable
my(%timer) ;	# hash of timers
my($target_file) ; # file containing date of first line we are searching for
my($target_file_assigned) ; # target_file variable has been assigned
my($time_now) ;	# used for timer based activities
my($tracefh) ;	# file w/ line numbers that matched a line/regex of interest
my($tracefile) ; # file w/ line numbers that matched a line/regex of interest
my(%ue) ;	# discovered UE record data hash - see schema
my($ultrasparc) ;	# UltraSPARC chip version, e.g. 3
my($ultrasparc_full) ;	# UltraSPARC chip full version, e.g. 3i
my(@uname_a_data) ;	# array of uname -a type passed by -c option
my($use_cestat) ;	# true if the output from cestat is usable, trustworthy
my($use_expl) ;	# true if we are running against an Explorer output
my($use_history) ;	# true if we are running against the live system in
				# in historical mode
my($use_man) ;	# true if we are running against standalone messages files
my($use_live) ;	# true if we are running against the live system
my($use_rule3) ;	# true if rule3 should be used on this system
my($use_rule4) ;	# true if rules 4a & 4b should be used on this system
my($use_rule4b) ;	# true if rule 4b should be used on this system
my($use_rule5) ;	# true if rule5 should be used on this system
my($use_rule5b) ;	# true if unretired page rule5b should be used 
my($use_rule6) ;	# true if rule6 should be used on this system
my($use_syslog) ;	# log findings to syslog, default to true if $use_live
my($verbose) ;	# show verbose output flag

###################################################
# define variables -> pre-CLI argument processing #
###################################################
$last_ce_stored = 0;
$last_ce_event = 0;

# define variables
# ... definition order sensitive
$prog = "cediag" ;	# hard code the name
$prog_dir = getdirname($0) ;

# ... definition order insensitive
$event = 0 ;
$adjustments_done = 0 ;
$args_ok = 1 ;	# default to args being passed OK
$allow_checkpointing = 0 ;	# default to checkpointing off
$checkpoint_lookback = (24 * 60 * 60) ;
$debug = 0 ;	# default debug mode to be off
$no_prescreen = 0;	# default to pre-screening on
$edir = "" ;
$first_file_found = 0;
$g_revision = '$Revision: 1.94 $ $Date: 2007/07/26 22:55:08 $' ;
	$g_revision =~ s/\$//g ;
	$g_revision =~ s/\sDate:/@/ ;
	$g_revision .= "UTC" ;
$last_message_time = "Jan  1 00:00:00" ;	# initialise $last_message_time
					# to be the epoch just in case
					# we don't process any messages files
$last_message_time_nls = 0 ;	# initialise $last_message_time_nls
$lines_per_second = 0 ;
$measuring_speed = 1 ;
$measure_timer = 15 ;	# default to measuring 15 seconds of processing
$measure_start_line = 0 ;
$message_lines_skipped = 0 ;
$messages_file_totallines = 0 ;
$mpr_capable_os = 0 ;	# default MPR capable OS flag to be off
$mpr_disabled_etc_system = undef() ;
$mpr_enabled = 0 ;	# default MPR enabled flag to be off
$mpr_state = "unavailable" ;	# default MPR state to be unavailable
$mpr_kernel = 0 ;	# default MPR kernel flag to be off
$priority_level = 0 ;	# default to highest priority
$prl_dm = 0 ;	# default is no PRL detection method
$processing_started = 0 ;
$rule5_ce_count = 130 ;	# default to 130 CEs
$rule5b_ce_count = 120 ; # default to 120 CEs
$rule5b_hour_count = 24 ; # default to 24 hours
$rule6_ce_count = 24 ;	# default to '24 in 24'
$rule6_hour_count = 24 ;	# default to '24 in 24'
$skip_prescreen = 0 ;	# start out using prescreen
$use_cestat = 0 ;	# default cestat output to be *not* usable, trustworthy
$use_expl = 0 ;	# default *not* to run against Explorer output
$use_history = 0 ;	# default *not* to run against live system (historical)
$use_live = 1 ;	# default to run against the live system
$use_man = 0 ;	# default *not* to run against standalone messages files
$use_rule3 = 1 ;	# default to use rule3
$use_rule4 = 1 ;	# default to use rules 4a & 4b
$use_rule4b = 0 ;	# default to not use rule 4b
$use_rule5 = 1 ;	# default to use rule5
$use_rule5b = 1 ;	# default to use rule5b
$use_rule6 = 1 ;	# default to use rule6
$use_syslog = 0 ;	# default to not logging to syslog
$target_file_assigned = 0 ;
$verbose = 0 ;	# default verbose mode to be off

# define hashes
# ... %datapath
$datapath{'count'} = 0 ;
# ... %exe
$exe{'cat'} = "/usr/bin/cat" ; 	# path to cat(1) command
# path to cestat(1m) command
$exe{'cestat'} = "/opt/SUNWcest/bin/cestat" ;
if ( -f "/opt/SUNWcest/bin/cestat" ) { $exe{'cestat'} = "/opt/SUNWcest/bin/cestat" ; } elsif ( -f "$prog_dir/cestat" ) { $exe{'cestat'} = "$prog_dir/cestat" ; } ;
if ( -f "$prog_dir/cestat" ) { $exe{'cestat'} = "$prog_dir/cestat" ; } else { $exe{'cestat'} = "/opt/SUNWcest/bin/cestat" ; } ;
$exe{'grep'} = "/usr/bin/grep" ; 	# path to grep(1) command
$exe{'logger'} = "/usr/bin/logger" ; 	# path to logger(1) command
$exe{'mkdir'} = "/usr/bin/mkdir" ; 	# path to mkdir(1) command
$exe{'priocntl'} = "/bin/priocntl" ; 	# path to priocntl(1) command
$exe{'prtconf'} = "/usr/sbin/prtconf" ; 	# path to prtconf(1m) command
# path to prtdiag(1m) command
if ( -f "/usr/sbin/prtdiag" ) { $exe{'prtdiag'} = "/usr/sbin/prtdiag" ; } else { $exe{'prtdiag'} = "/usr/platform/sun4u/sbin/prtdiag" ; } ;
if ( -f "/usr/platform/sun4u/sbin/prtdiag" ) { $exe{'prtdiag'} = "/usr/platform/sun4u/sbin/prtdiag" ; } else { $exe{'prtdiag'} = "/usr/sbin/prtdiag" ; } ;
$exe{'ps'} = "/usr/bin/ps" ; 	# path to ps(1) command
$exe{'showrev'} = "/usr/bin/showrev" ; 	# path to showrev(1m) command
$exe{'uname'} = "/usr/bin/uname" ; 	# path to uname(1) command
# ... %kjp
@{$kjp{'5.5.1'}} = ("103640") ;
@{$kjp{'5.6'}} = ("105181") ;
@{$kjp{'5.7'}} = ("106541") ;
@{$kjp{'5.8'}} = ("108528", "117000", "117350") ;
@{$kjp{'5.9'}} = ("112233", "117171", "118558") ;
# ... %mpr
$mpr{'sched_rm_count'} = 0 ;
$mpr{'rm_count'} = 0 ;
$mpr{'sched_clear_count'} = 0 ;
$mpr{'clear_count'} = 0 ;
# ... %mpr_kjp
@{$mpr_kjp{'5.5.1'}} = () ;	# no MPR-aware KJP
@{$mpr_kjp{'5.6'}} = () ;	# no MPR-aware KJP
@{$mpr_kjp{'5.7'}} = () ;	# no MPR-aware KJP
@{$mpr_kjp{'5.8'}} = ("108528-24", "117000-01", "117350-01") ;
@{$mpr_kjp{'5.8_3i'}} = ("117000-02", "117350-01") ;
@{$mpr_kjp{'5.8_3i+'}} = ("117000-02", "117350-01") ;
@{$mpr_kjp{'5.9'}} = ("112233-11", "117171-01", "118558-01") ;
@{$mpr_kjp{'5.9_3i'}} = ("117171-01", "118558-01") ;
@{$mpr_kjp{'5.9_3i+'}} = ("117171-01", "118558-01") ;
@{$mpr_kjp{'5.10'}} = (1) ;
@{$mpr_kjp{'5.11'}} = (1) ;	# future proofing
@{$mpr_kjp{'5.12'}} = (1) ;	# future proofing
@{$mpr_kjp{'5.13'}} = (1) ;	# future proofing
@{$mpr_kjp{'5.14'}} = (1) ;	# future proofing
# ... %policy
$policy{'datapath'}{'priority'} = "HIGH" ;
$policy{'datapath'}{'when'} = "possible datapath fault - refer to Sun Support [A]s [S]oon [A]s [P]ossible" ;
$policy{'rule3'}{'priority'} = "HIGH" ;
$policy{'rule3'}{'when'} = "refer UE(s) to Sun Support [A]s [S]oon [A]s [P]ossible" ;
$policy{'rule4a'}{'priority'} = "HIGH" ;
$policy{'rule4a'}{'when'} = "[A]s [S]oon [A]s [P]ossible" ;
$policy{'rule4b'}{'priority'} = "HIGH" ;
$policy{'rule4b'}{'when'} = "[A]s [S]oon [A]s [P]ossible" ;
$policy{'rule5'}{'priority'} = "MEDIUM" ;
$policy{'rule5'}{'when'} = "during next maintenance period" ;
$policy{'rule5b'}{'priority'} = "MEDIUM" ;
$policy{'rule5b'}{'when'} = "during next maintenance period" ;
$policy{'rule6'}{'priority'} = "MEDIUM" ;
$policy{'rule6'}{'when'} = "during next maintenance period" ;
# ... %re
$re{'afar'} = '0x[A-Fa-f0-9]{8}\.[A-Fa-f0-9]{8}' ;	# CE AFAR
$re{'date'} = '[A-Z][a-z]{2}\s+\d{1,2}\s+\d{2}:\d{2}:\d{2}' ;	# syslog date
$re{'errid'} = '0x[A-Fa-f0-9]{8}\.[A-Fa-f0-9]{8}' ;	# CE errid
$re{'leading_hex'} = '0x0{1,7}' ;	# leading hex address zeroes
$re{'page'} = '0x[A-Fa-f0-9]{8}\.[A-Fa-f0-9]{8}' ;	# memory page
$re{'prescreen'} = 'AFT|kern\.|AFSR|PIO|Uncorrectable|Fault_PC|Syndrome|MB\/P|removal of page|removed from service|Event\s+detected|C\d+\/P|root\s+nexus' ;
#
# ... %timer
$timer{'start'} = time() ;	# record the starting time
$time_now = $timer{'start'} ;
$last_cycle_time = $timer{'start'} ;
#
# ... %ue
$ue{'count'} = 0 ;
$ue{'date'} = 0 ;	# set last UE date to be Jan 1 1970

##################################
# process command line arguments #
##################################

# parse the arguments successfully or exit with a usage message
getopts('AC:c:de:FHhI:k:Ll:M:Np:Ps:t:u:vV', \%args) || usage('getopts') ;

# loop - process each CLI argument
foreach $arg ( sort(keys(%args)) ) {
	# test - branch on args passed
	if ( $arg eq 'A' ) {
		####
		# has license been accepted by invoking user?
		####
		# test - licence acceptance status
		if ( accept_licence('query') ) {
			# licence has been accepted ...
			# show status
			msg("licence has been accepted") ;

			# exit with a "good" value
			exit 0 ;

		} else {
			# licence has *not* been accepted ...
			# show status
			msg("licence has *not* been accepted") ;

			# exit with a "bad" value
			exit 1 ;

		} ;	# end # test - licence acceptance status

	} elsif ( $arg eq 'C' ) {
		####
		# 'cestat -v' output file to use
		####
		# test - file readable
		if ( -r $args{'C'} ) {
			# file readable ... set the variable
			$cestat_file = $args{'C'} ;

		} else {
			# problem with the file ... exit gracefully
			suicide("'$args{'C'}' is invalid/unreadable file") ;

		} ;	# end test - file readable

	} elsif ( $arg eq 'c' ) {
		####
		# config options
		####
		# split the passed string into an array
		@a = split(/[\,\;\:\|\^\!]/, $args{'c'}) ;

		# test - check 4 arguments passed
		if ( scalar(@a) == 4 ) {
			# sufficient number of arguments ...
			# populate @uname_a_data array with good data
			$uname_a_data[0] = $a[0] ;
			$uname_a_data[1] = $a[1] ;
			$uname_a_data[2] = $a[2] ;
			$uname_a_data[3] = "*unknown*" ;
			$uname_a_data[4] = "*unknown*" ;
			$uname_a_data[5] = $a[3] ;
			$uname_a_data[6] = "*unknown*" ;

		} else {
			# insufficient number of arguments ...
			# populate @uname_a_data array with bad data
			$uname_a_data[0] = 0 ;
			$uname_a_data[1] = 0 ;
			$uname_a_data[2] = 0 ;
			$uname_a_data[3] = 0 ;
			$uname_a_data[4] = 0 ;
			$uname_a_data[5] = 0 ;
			$uname_a_data[6] = 0 ;

			# unset args_ok flag
			$args_ok = 0 ;

			# save reason for bad argument
			push(@args_bad, "-c argument passed with insufficent number of values") ;

		} ; 	# end test - check 3 arguments passed

		# set the run against manual data flag
		$use_man = 1 ;

		# unset the other run against flags
		$use_expl = $use_live = $use_history = $use_syslog = 0 ;

	} elsif ( $arg eq 'd' ) {
		####
		# debug mode
		####
		$debug = 1 ;

	} elsif ( $arg eq 'e' ) {
		####
		# Explorer directory
		####
		# test - passed directory valid
		if ( -d $args{'e'} && -r $args{'e'} ) {
			# set the Explorer directory to use variable
			$edir = $args{'e'} ;

			# set the run against Explorer flag
			$use_expl = 1 ;

			# unset the other run against flags
			$use_man = $use_live = $use_history = $use_syslog = 0 ;

		} else {
			# invalid Explorer directory passed, die gracefully
			suicide("'$args{'e'}' is invalid/unreadable Explorer directory") ;

		} ;	# end test - passed directory valid

	} elsif ( $arg eq 'F' ) {
		####
		# live system in historical mode
		####
		# set the run against live (historical) flag
		$use_history = 1 ;

		# unset the other run against flags
		$use_man = $use_live = $use_syslog = $use_expl = 0 ;

	} elsif ( $arg eq 'H' || $arg eq 'h' ) {
		###
		# help ... usage
		###
		usage('help') ;

	} elsif ( $arg eq 'I' ) {
		###
		# events to ignore
		###
		# ... ignore reboots, do not reset counters upon seeing a reboot
		if ( $args{'I'} =~ /r/ ) { $ignore{'reboot'} = 1 ; } ;

	} elsif ( $arg eq 'k' ) {
		###
		# KJP patch-rev in use
		###
		$kjp_inuse = $args{'k'} ;

	} elsif ( $arg eq 'L' ) {
		###
		# log messages to syslogd
		###
		$use_syslog = 1 ;

	} elsif ( $arg eq 'l' ) {
		####
		# log file name
		####
		$log = $args{'l'} ;

		#################
		# open log file #
		#################

		# we are passed a filename ...
		# open the log file or die a graceful death
		$logfh = file_open(">$log") ||
			suicide("failed to open log file:- '$log'",
				"$prog: reason:- $!") ;
#				"$prog: reason:- $OS_ERROR") ;

	} elsif ( $arg eq 'M' ) {
		###
		# force MPR enabled/disabled
		###
		# test - branch of passed arg
		if ( $args{'M'} =~ /^[1ye]/i ) {
			# MPR => 1, yes, enabled
			$mpr_force = 'enabled' ;

		} elsif ( $args{'M'} =~ /^[0nd]/i ) {
			# MPR => 0, no, disabled
			$mpr_force = 'disabled' ;

		} else {
			# unknown ...
			# exit gracefully
			suicide("unacceptable value of '$args{'M'}' passed with '-M' argument") ;

		} ;	# test - branch of passed arg

	} elsif ( $arg eq 'N' ) {
		$no_prescreen = 1 ;

	} elsif ( $arg eq 'p' ) {

		###
		# platform type in use
		###
		$platform = $args{'p'} ;
		$platform =~ s/^[ 	]*//;	# remove leading whitespace

	} elsif ( $arg eq 'P' ) {
		$allow_checkpointing = 1 ;

	} elsif ( $arg eq 's' ) {
		####
		# tunable rule6 '24 in 24' count
		####
		( $rule6_ce_count, $rule6_hour_count ) = split(/[;,:\.\/]|in/, $args{'s'}) ;

	} elsif ( $arg eq 't' ) {
		$tracefile = $args{'t'} ;	# see $tracefh

	} elsif ( $arg eq 'u' ) {
		####
		# UltraSPARC chip version
		####

		# test - branch on validity of passed argument
		if ( $args{'u'} =~ /^[1-4][ie\+]?$/
			|| $args{'u'} =~ /^[1-4]i\+$/ ) {
			# valid UltraSPARC version
			$ultrasparc_full = $args{$arg} ;
			$ultrasparc = $ultrasparc_full ;
			$ultrasparc =~ s/[ie\+]{0,2}$// ;
		} else {
			# bad option passed, exit gracefully
			suicide("unacceptable value of '$args{'u'}' passed with '-u' argument") ;

		} ;	# end test - branch on validity of passed argument

	} elsif ( $arg eq 'v' ) {
		####
		# verbose mode?
		####
		$verbose = 1 ;

	} elsif ( $arg eq 'V' ) {
		####
		# show version info & exit
		####
		print STDOUT "$prog: $g_revision\n" ;
		exit 0 ;

	} else {
		####
		# legal yet unknown argument
		####
		# exit cleanly
		suicide("'$arg' is a legal argument with an unknown action") ;

	} ;	# test - branch on args passed

} ;	# end loop - process each CLI argument

# after getopts has run, what is left on the @ARGV array are the messages to
# process
# test - messages files specified on the command line
if ( scalar(@ARGV) >= 1 ) {
	# make the passed files the messages files to process
	@messages_files = @ARGV ;

	# just in case the user is either using the tool incorrectly or is
	# trying to pull a swifty, force 'manual mode' to make sure user
	# specifies the '-c' config info ...
	# set the run against manual data flag
	$use_man = 1 ;

	# unset the other run against flags
	$use_expl = $use_live = $use_history = $use_syslog = 0 ;

} ;	# end test - messages files specified on the command line

#######################################
# sanity check command line arguments #
#######################################

# default variables
if ( ! defined($kjp_inuse) ) { $kjp_inuse = "" ; } ;

# test - branch on usage type
if ( $use_man ) {
	# standalone/manual mode ...
	# test - appropriate -c data passed
	if ( ! $uname_a_data[0] || ! $uname_a_data[1] || ! $uname_a_data[2] || ! $uname_a_data[5] ) {
		# unset args_ok flag
		$args_ok = 0 ;

		# save reason for bad argument
		msg("required -c argument not passed correctly") ;
		usage('help') ;

	} ;	# end test - appropriate -c data passed

	# assign variables
	$arch = (uname_a())[5] ;	# detect CPU architecture in use
	$ostype = lc((uname_a())[0]) ;	# detect OS type in use
	$osver = (uname_a())[2] ;	# detect OS version in use
	$hostname = (uname_a())[1] ;	# detect hostname to limit search to

	# test - KUP passed correctly
	if ( $kjp{$osver} && ! $kjp_inuse ) {
		# unset args_ok flag
		$args_ok = 0 ;

		# save reason for bad argument
		push(@args_bad, "required '-k KUP-rev' argument not passed correctly") ;

	} ;	# end test - KUP passed correctly

	# populate the %patch table
	load_patches() ;

	# test - UltraSPARC version passed correctly
	if ( ! $ultrasparc ) {
		# unset args_ok flag
		$args_ok = 0 ;

		# save reason for bad argument
		push(@args_bad, "required '-u US_version' argument not passed correctly") ;

	} ;	# end test - UltraSPARC version passed correctly

	# loop - check each passed messages file
	foreach $i ( @messages_files ) {
		# test - check the given messages file is readable
		if ( -e $i && -r $i ) {
			# all is well with this file ... do nothing
			;

		} else {
			# this file does not exist or is not readable ...
			# unset args_ok flag
			$args_ok = 0 ;

			# save reason for bad argument
			push(@args_bad, "messages file '$i' is not readable") ;

		} ;	# end test - if in manual mode check the given file is readable

	} ;	# end loop - check each passed messages file

	# Assume -p platform passed correctly.  Give warning below if platform
	# not supplied or not supported for rule 4b.

} elsif ( $use_expl ) {
	# Explorer mode ...
	# assign %efile entries
	$efile{'prtconf_v'} = "$edir/sysconfig/prtconf-v.out" ;
	$efile{'prtdiag_v'} = "$edir/sysconfig/prtdiag-v.out" ;
	$efile{'readme'} = "$edir/README" ;
	$efile{'showrev_p'} = "$edir/patch+pkg/showrev-p.out" ;
	$efile{'system'} = "$edir/etc/system" ;
	$efile{'uname_a'} = "$edir/sysconfig/uname-a.out" ;

	# define location of /etc/system file
	$file{'system'} = $efile{'system'} ;

	# define $sample_time to be Explorer collection time
	# ... use the "Finished:" line
	$i = grepper($efile{'readme'}, '^\s*Finished:\s+(\d{4}\.\d{2}\.\d{2}\.\d{2}\.\d{2})\s*$') ;
	# ... if no "Finished:" line, use the "Date:" line
	if ( ! $i ) { $i = grepper($efile{'readme'}, '^\s*Date:\s+(\d{4}\.\d{2}\.\d{2}\.\d{2}\.\d{2})\s*$') ; } ;
	# test - do we have a sample time
	if ( $i ) {
		# use the extracted sample time
		$sample_time = convert_to_nls($i) ;

	} else {
		# use the mtime of the README file
		$sample_time = (stat($efile{'readme'}))[9] ;

	} ;	# end test - do we have a sample time

	# assign variables
	$arch = (uname_a())[5] ;	# detect CPU architecture in use
	$ostype = lc((uname_a())[0]) ;	# detect OS type in use
	$osver = (uname_a())[2] ;	# detect OS version in use
	$hostname = (uname_a())[1] ;	# detect hostname to limit search to
	$platform = grepper($efile{'prtdiag_v'}, '^System Configuration:\s+.*sun4u\s+(.*)$') ;

	# populate the %patch table
	load_patches() ;

	# get UltraSPARC processor version number
	($ultrasparc, $ultrasparc_full) = ultrasparc_chip() ;

	# get OS version
	$kjp_inuse = kjp_detect() ;

} elsif ( $use_live || $use_history ) {
	# live system mode ...
	# define $sample_time to be now
	# Don't set sample_time, let convert_to_nls() use time().  Else, if
	# cediag continues to process past midnight, the setting of a static
	# year (by setting sample_time) will result in dates which are off by
	# one year (backwards).  See "future dates" in convert_to_nls().
	# $sample_time = time() ;

	# define location of /etc/system file
	$file{'system'} = "/etc/system" ;

	# assign variables
	$arch = (uname_a())[5] ;	# detect CPU architecture in use
	$ostype = lc((uname_a())[0]) ;	# detect OS type in use
	$osver = (uname_a())[2] ;	# detect OS version in use
	$hostname = (uname_a())[1] ;	# detect hostname to limit search to
	$platform = `$exe{'prtdiag'} -v | $exe{'grep'} "^System Configuration:"`;
	$platform =~ s/^.*sun4u //;

	# populate the %patch table
	load_patches() ;

	# get UltraSPARC processor version number
	($ultrasparc, $ultrasparc_full) = ultrasparc_chip() ;

	# get OS version
	$kjp_inuse = kjp_detect() ;

} ;	# end test - branch on usage type

####################################################
# define variables -> post-CLI argument processing #
####################################################

# ... definition order sensitive
$osver_math = $osver ; $osver_math =~ s/\.// ;	# remove first period character
$mpr_kernel = mpr_kernel_inuse() ;	# is a MPR-enabled kernel in use?
					# ... also sets $mpr_capable_os variable

# ... definition order insensitive
if ( $ostype =~ /^sunos$/i ) { $ostype = "SunOS" ; } ;
$ram = memory_size() ;
$ram_prl = int( ($ram * .001) - ( $ram * .00005 ) ) ;	# 0.1% phys RAM +/- 5%

######################
# licence acceptance #
######################

# user must accept licence or have previsouly accepted the licence to continue
# past this point
accept_licence() ;

##################################
# ensure we are running on SunOS #
##################################

# test - check we are analysing a SunOS platform
if ( $ostype ne "SunOS" ) {
	# unset args_ok flag
	$args_ok = 0 ;

	# save reason for bad argument
	push(@args_bad, "only SunOS platforms are eligible for analysis.") ;

} ;	# end test - check we are analysing a SunOS platform

##################################
# ensure we are running on SPARC #
##################################

# test - check we are analysing a SPARC platform
if ( $arch && $arch ne "sparc" ) {
	# unset args_ok flag
	$args_ok = 0 ;

	# save reason for bad argument
	push(@args_bad, "only SPARC platforms are eligible for analysis.") ;

} ;	# end test - check we are analysing a SPARC platform

########################################
# ensure we have an UltraSPARC version #
########################################

# test - check we have an UltraSPARC version
if ( ! $use_man && ! $ultrasparc ) {
	# unset args_ok flag
	$args_ok = 0 ;

	# save reason for bad argument
	push(@args_bad, "unable to determine UltraSPARC chip revision") ;

} ;	# end test - check we have an UltraSPARC version

#############################################
# ensure we have an UltraSPARC-II or higher #
#############################################

# test - check we have an UltraSPARC version
if ( $ultrasparc && $ultrasparc < 2 ) {
	# unset args_ok flag
	$args_ok = 0 ;

	# save reason for bad argument
	push(@args_bad, "$prog is not for use on UltraSPARC-I CPUs") ;

} ;	# end test - check we have an UltraSPARC version

#######################################
# check operating system is supported #
#######################################

# test - UltraSPARC chips were not supported until Solaris 2.5.1
if ( $osver_math && $osver_math < 55.1 ) {
	# unset args_ok flag
	$args_ok = 0 ;

	# save reason for bad argument
	push(@args_bad, "only Solaris 2.5.1 to 9 (inclusive) are eligible for analysis.") ;

} ;	# end test - UltraSPARC chips were not supported until Solaris 2.5.1

# test - S10 has it's own logic to determine DIMM replacement
if ( $osver_math && $osver_math >= 510 && $ultrasparc ne '2' ) {
	# unset args_ok flag
	$args_ok = 0 ;

	# save reason for bad argument
	push(@args_bad, "Please use fmadm(1M) on Solaris 10 (or higher).") ;

} ; 	# end test - S10 has it's own logic to determine DIMM replacement

##########################
# check KJP/KUP is valid #
##########################

# test - check that the KJP in use is valid
$i = kjp_validate($kjp_inuse) ;
if ( $use_man && ! defined($i) ) {
	# no $kjp_inuse was passed ...
	# do nothing, this problem was handled earlier in the code
	;

} elsif ( $i =~ /^0$/ ) {
	# does *not* look like a good KJP ...
	# unset args_ok flag
	$args_ok = 0 ;

	# save reason for bad argument
	push(@args_bad, "KUP of '$kjp_inuse' is not a valid KUP for SunOS $osver") ;

} else {
	# does look like a good KJP ...
	$kjp_inuse = $i ;

} ;	# test - check that the KJP passed is valid

###########################
# check platform is valid #
###########################

# XXX Need to complete and verify these platform lists
if ( defined($platform) ) {
	chomp($platform) ;
} ;
if ( ! defined($platform) ) {
	echo("info: platform not specified.  Rule 4B will not be checked.") ;
} elsif ( $platform =~ /E[4-6][05]00/ ) {
	echo("info: '$platform' platform not supported for rule 4B.") ;
} elsif ( $platform =~ /Sun Fire (6[89]00|4[89]00|4810|3800|2900|15000|E25K)$/
       || $platform =~ /Sun Fire E[246]900$/ ) {
	set_uniboard_bit2dram() ;
	$use_rule4b = 1;
} elsif ( $platform =~ /Sun Fire V(440|240)$/ ) {
	echo("info: '$platform' platform not supported for rule 4B.") ;
} elsif ( $platform =~ /Sun Enterprise 420$/ ) {
	echo("info: '$platform' platform not supported for rule 4B.") ;
} elsif ( $platform =~ /Sun Fire 880$/ || $platform =~ /Sun Fire V890$/ ||
	  $platform =~ /Sun Fire V880$/ ) {
# Daktari - 480, 490, 880, 890
	set_daktari_bit2dram() ;
	$use_rule4b = 1;
} elsif ( $platform =~ /Sun Fire 480\D/ ) {
	set_daktari_bit2dram() ;
	$use_rule4b = 1;
} elsif ( $platform =~ /Sun Ultra 25 Workstation/ ) {
	set_chicago_bit2dram() ;
	echo("info: '$platform' platform not supported for rule 4B.") ;
	# $use_rule4b = 1;
} elsif ( $platform =~ /Sun Ultra 45 Workstation/ ) {
	set_chicago_bit2dram() ;
	echo("info: '$platform' platform not supported for rule 4B.") ;
	# $use_rule4b = 1;
} elsif ( $platform =~ /Sun Blade 2500 (Silver)/ ) {
	set_enws_bit2dram() ;
	echo("info: '$platform' platform not supported for rule 4B.") ;
	# $use_rule4b = 1;
} elsif ( $platform =~ /Sun Fire 280R/ ) {
# Excalibur - SunBlade 1000/2000, 280R and Netra 20s
	set_excalibur_bit2dram() ;
	$use_rule4b = 1;
} elsif ( $platform =~ /Netra/ ) {
	echo("info: '$platform' platform not supported for rule 4B.") ;
} else {
	echo("info: Unknown platform type '$platform'.  Rule 4B will not be checked.") ;
} ;

###########################
# exit if bad args passed #
###########################

# test - branch of $args_ok
if ( ! $args_ok ) {
	# we had a problem with the arguments ...
	# exit gracefully giving the reason why
	suicide(@args_bad) ;

} ;	# end test - branch of $args_ok

#######################
# parse cestat output #
#######################

# test - branch on MPR capabilities
if ( $use_live && $mpr_capable_os && $mpr_kernel && -f $exe{'cestat'} ) {
	# running against live system with MPR, we have cestat ...

	# test - branch on EUID
	if ( $> == 0 ) {	# if ( $EFFECTIVE_USER_ID == 0 ) {
		# we are root ...
		# parse cestat output ...
		parse_cestat() ;

	} else {
		# we are *not* root ...
		# must run as root to execute cestat ...
		# show a message
		msg("advice: please execute as root to allow access to 'cestat' data") ;

	} ;	# test - branch on EUID

} elsif ( $use_live && $mpr_capable_os ) {
	# running against live system with MPR capable OS ...

	# test - branch on MPR-enabled kernel installed
	if ( ! $mpr_kernel ) {
		# MPR-enabled kernel *not* installed ...
		# advise to upgrade KUP
		msg("please install a [M]emory [P]age [R]etirement Kernel Update Patch") ;

		# show which patch is needed at a minimum ...
		# this is now an array so take the first element as the minimum
		msg("SunOS $osver requires $mpr_kjp_minimum (or higher) installed for MPR support") ;

	} ;	# end test - branch on MPR enabled kernel installed

	# test - branch on cestat installed
	if ( ! -f $exe{'cestat'} ) {
		# can not find cestat ...
		# advise to install SUNWcest package
		msg("please install 'SUNWcest' package to allow access to 'cestat' data") ;

	} ;	# end test - branch on cestat installed

} elsif ( ! $use_live && $mpr_capable_os && $mpr_kernel && $cestat_file ) {
	# running against non-live system with MPR, we have cestat output ...
	# parse cestat output ...
	parse_cestat() ;

} ;	# end test - branch on MPR capabilities

################################################
# update flags, now that we have cestat output #
################################################

# obtain ce_verbose_memory value in /etc/system
# test - branch on run mode
if ( $use_live || $use_history || $use_expl ) {
	# runing in live, history or Explorer mode ...
	# check the relevant /etc/system file exists & is readable ...
	# test - /etc/system file readability
	if ( -e $file{'system'} && -r $file{'system'} ) {
		# the /etc/system file is readable ...
		# set ce_verbose_memory to be enabled by default
		$ce_verbose_memory = 1 ;

		# extract the ce_verbose_memory value ...
		$i = grepper($file{'system'}, '^\s*set\s+ce_verbose_memory\s*=\s*(\d+)\s*') ;
		$j = grepper($file{'system'}, '^\s*set\s+ce_verbose\s*=\s*(\d+)\s*') ;

		# test - branch on value of /etc/system extractions
		if ( $i =~ /^\d+$/ ) {
			# assign ce_verbose_memory value
			$ce_verbose_memory = $i

		} elsif ( $j =~ /^\d+$/ ) {
			# assign ce_verbose value
			$ce_verbose_memory = $j

		} ;	# end test - branch on value of /etc/system extractions

	} else {
		# the /etc/system file is *un*readable ...
		$ce_verbose_memory = -1 ;

	} ;	# end test - /etc/system file readability

} else {
	# runing in manual (aka standalone) mode ...
	# force value to undef()
	$ce_verbose_memory = -2 ;

} ;	# test - branch on run mode

# ignore -M MPR force flag if we are using cestat output
if ( $mpr_force && $use_cestat ) {
	# show status
	echo("-M MPR force flag ignored as we are using cestat output") ;

	# reset variables
	undef($mpr_force) ;

} ;	# end - ignore -M MPR force flag if we are using cestat output

# test - cestat status
if ( $use_cestat && $mpr_capable_os && $mpr_kernel && ! $cestat{'mpr_disabled'} ) {
	# cestat output is valid & reports MPR is enabled in kernel ...
	# show status
	echo("\$use_cestat is true", "cestat reports MPR enabled in kernel") ;

	# turn on $mpr_enabled flag
	echo("setting \$mpr_enabled to true") ;
	$mpr_enabled = 1 ;
	$mpr_state = "active" ;

	# turn off rule5 'messages' checking
	echo("setting \$use_rule5 to false") ;
	$use_rule5 = 0 ;

	# turn off rule6 checking
	echo("setting \$use_rule6 to false") ;
	$use_rule6 = 0 ;

} elsif ( $use_cestat && $mpr_capable_os && $mpr_kernel && $cestat{'mpr_disabled'} ) {
	# cestat output is valid & reports MPR is *disabled* in kernel ...
	# show status
	echo("\$use_cestat is true", "cestat reports MPR *disabled* in kernel") ;

	# turn off $mpr_enabled flag
	echo("setting \$mpr_enabled to false") ;
	$mpr_enabled = 0 ;
	$mpr_state = "disabled" ;

	# turn off $use_cestat flag
	echo("setting \$use_cestat to false") ;
	$use_cestat = 0 ;

	# turn off rule5 'messages' checking
	echo("setting \$use_rule5 to false") ;
	$use_rule5 = 0 ;

	# turn off rule5b 'messages' checking
	echo("setting \$use_rule5b to false") ;
	$use_rule5b = 0 ;

} elsif ( ! $use_cestat && $mpr_capable_os && $mpr_kernel ) {
	# cestat output is *invalid* & OS is MPR ready & able ...
	# show status
	echo("\$use_cestat is false") ;

	# default variables
	$i = 1 ;

	# test - is there an /etc/system file to check
	if ( $file{'system'} ) {
		# there is an /etc/system file ...
		# show status
		echo("checking $file{'system'} file for 'automatic_page_removal' variable") ;

		# test - MPR disabled in /etc/system
		if ( grepper($file{'system'}, '^\s*set\s+automatic_page_removal\s*=\s*0\s*$') ) {
			# MPR disabled in /etc/system ...
			$i = 0 ;

			# show status
			echo("MPR disabled in /etc/system") ;

			# set variable
			$mpr_disabled_etc_system = 1 ;

		} else {
			# MPR *not* disabled in /etc/system ...
			$i = 1 ;

			# show status
			echo("MPR *not* disabled in /etc/system") ;

			# set variable
			$mpr_disabled_etc_system = 0 ;

		} ;	# end test - MPR disabled in /etc/system

	} else {
		# there is *no* /etc/system file ...
		# default to MPR enabled
		$i = 1 ;

	} ;	# end test - is there an /etc/system file to check

	# test - branch on $mpr_force
	if ( $mpr_force && $mpr_force eq 'enabled' ) {
		# MPR force enabled ...
		$i = 1 ;

		# show status
		echo("MPR forced to 'enabled'") ;

	} elsif ( $mpr_force && $mpr_force eq 'disabled' ) {
		# MPR force disabled ...
		$i = 0 ;

		# show status
		echo("MPR forced to 'disabled'") ;

	} ;	# end test - branch on $mpr_force

	# test - branch on $i
	if ( $i ) {
		# turn on $mpr_enabled flag
		echo("setting \$mpr_enabled to true") ;
		$mpr_enabled = 1 ;
		$mpr_state = "active" ;

		# turn on rule5 'messages' checking
		echo("setting \$use_rule5 to true") ;
		$use_rule5 = 1 ;

		# turn on rule5b 'messages' checking
		echo("setting \$use_rule5b to true") ;
		$use_rule5b = 1 ;

		# turn off rule6 checking
		echo("setting \$use_rule6 to false") ;
		$use_rule6 = 0 ;

	} else {
		# turn off $mpr_enabled flag
		echo("setting \$mpr_enabled to false") ;
		$mpr_enabled = 0 ;
		$mpr_state = "disabled" ;

		# turn off rule5 'messages' checking
		echo("setting \$use_rule5 to false") ;
		$use_rule5 = 0 ;

		# turn off rule5b 'messages' checking
		echo("setting \$use_rule5b to false") ;
		$use_rule5b = 0 ;

		# turn on rule6 checking
		echo("setting \$use_rule6 to true") ;
		$use_rule6 = 1 ;

	} ;	# end # test - branch on $i

} elsif ( $mpr_capable_os && ! $mpr_kernel ) {
	# OS is MPR-capable but no MPR-aware kernel installed ...
	# show status
	echo("\$mpr_capable_os is true") ;
	echo("\$mpr_kernel is false") ;

	# turn off $mpr_enabled flag
	echo("setting \$mpr_enabled to false") ;
	$mpr_enabled = 0 ;
	$mpr_state = "unavailable" ;

	# turn off rule5 'messages' checking
	echo("setting \$use_rule5 to false") ;
	$use_rule5 = 0 ;

	# turn off rule5b 'messages' checking
	echo("setting \$use_rule5b to false") ;
	$use_rule5b = 0 ;

} elsif ( ! $use_cestat && ! $mpr_capable_os ) {
	# cestat output is *invalid* & OS is *not* MPR ready & able ...
	# show status
	echo("\$use_cestat is false") ;

	# turn off $mpr_enabled flag
	echo("setting \$mpr_enabled to false") ;
	$mpr_enabled = 0 ;
	$mpr_state = "unavailable" ;

	# turn off rule5 'messages' checking
	echo("setting \$use_rule5 to false") ;
	$use_rule5 = 0 ;

	# turn off rule5b 'messages' checking
	echo("setting \$use_rule5b to false") ;
	$use_rule5b = 0 ;

} ;	# end test - cestat status

################################################################################
################################################################################
######### !!!! Do not set 'master' variables beyond this point !!!! ############
################################################################################
################################################################################

##########
# main() #
##########

$tracefh = file_open(">$tracefile") if ($tracefile) ;

################################
# debug - show variable status #
################################
echo("\$debug = $debug") ;
echo("\$verbose = $verbose") ;
echo("\$edir = $edir") ;
echo("\$arch = $arch") ;
echo("\$platform = $platform") if (defined($platform));
echo("\$allow_checkpointing = $allow_checkpointing") ;
echo("\$initial_checkpoint = $initial_checkpoint (" .
	localtime($initial_checkpoint) .")") if ($use_live || $use_history) ;
echo("\$ultrasparc = $ultrasparc") ;
echo("\$ultrasparc_full = $ultrasparc_full") ;
echo("\$ostype = $ostype") ;
echo("\$osver = $osver") ;
echo("\$hostname = $hostname") ;
echo("\$use_cestat = $use_cestat") ;
echo("\$mpr_capable_os = $mpr_capable_os") ;
echo("\$mpr_kernel = $mpr_kernel") ;
echo("\$mpr_enabled = $mpr_enabled") ;
echo("\$mpr_state = $mpr_state") ;
echo("\$use_rule3 = $use_rule3") ;
echo("\$use_rule4 = $use_rule4") ;
echo("\$use_rule4b = $use_rule4b") ;
echo("\$use_rule5 = $use_rule5") ;
echo("\$use_rule5b = $use_rule5b") ;
echo("\$use_rule6 = $use_rule6") ;

#####################################
# generate a list of messages files #
#####################################

# test - determine the messages files to process
if ( $use_expl ) {
	# use the Explorer messages
	$messages_dir = "$edir/messages" ;

} elsif ( $use_live || $use_history ) {
	# use the /var/adm on the live system
	$messages_dir = "/var/adm" ;

} ;	# test - determine the messages files to process

# test - messages files not passed on CLI
if ( $use_expl || $use_live || $use_history ) {
	# generate a list of messages file to process in reverse order (in other
	# words oldest messages file is processed first)
	@messages_files = globber($messages_dir, 'f', '^messages$', '^messages\.\d$', '^messages\.\d\d$') ;
	@messages_files = reverse(@messages_files) ;

	$messages_file_last = undef() ;
	foreach $messages_file ( @messages_files ) {
		get_file_time($messages_file) ;
		if (!$target_file_assigned &&
		    $filetime{$messages_file} > $initial_checkpoint) {
			$target_file = $messages_file_last ;
			$target_file_assigned = 1 ;
		} ;
		$messages_file_last = $messages_file ;
	} ;

} ;	# end test - messages files not passed on CLI

#############################
# do the report header info #
#############################

# do the report header
header() ;

####################################
# trawl through the messages files #
####################################

# Have a data set where the hostname changes mid-stream?  You can combine
# the search for multiple hosts by setting $hostname as in this example:
# $hostname = "(?:db2-asg-a|shared-db1-3-asg.ops.sfdc.net)";

# loop - process each messages file found
MESSAGES_FILE: foreach $messages_file ( @messages_files ) {
	# show the file we are processing
	if (!$first_file_found && ($use_live || $use_history) && $target_file &&
	    $target_file ne $messages_file ) {
		echo("", "skipping messages file -> $messages_file") ;
		next("MESSAGES_FILE") ;
	} else {
		echo("", "processing messages file -> $messages_file") ;
		$first_file_found = 1;
	} ;

	# there appears to be some sort of weird Perl bug in play here so I'm
	# aliasing $messages_file to $filename to allow later use
	$filename = $messages_file ;
	$lastline = "" ;

	# check the modification date of the messages file to ensure we are
	# processing oldest first ...
	# ... get the modification date of the current messages file
	$i = (stat($messages_file))[9] ;
	# test - branch on messages_file_date
	if ( ! $messages_file_date ) {
		# first pass so there is no existing date ...
		# take no action
		;

	} elsif ( $i > $messages_file_date ) {
		# this file is younger than the last file processed ...
		# this is a desired state, take no action
		;

	} else {
		# this file is older than the last file processed ...
		# this is a *not* desired state, show a warning
		msg("WARNING: $messages_file is older than the last messages file processed ($messages_file_last)!", "WARNING: Analysis may be compromised!") ;

	} ;	# test - branch on messages_file_date
	$messages_file_last = $messages_file ;

	# assign messages file date
	$messages_file_date = $i ;

	# open the messages file
	$fh0 = file_open("<$messages_file") ;

	# test - success of open the messages file
	if ( ! $fh0 ) {
		# we shouldn't have to close the messages file as it doesn't
		# appear to be open, but just in case ...
		file_close($fh0) ;

		# show a warning message
		echo("WARNING: can't open messages file: $messages_file") ;
		warn "WARNING: can't open messages file: $messages_file\n" ;

		# skip to the next messages file to process
		next("MESSAGES_FILE") ;

	} ;	# end test - success of open the messages file

	# update the filesize hash with the size of this file
	$filesize{$filename} = -s $filename ;

	# initialise messages file line number counter variable
	$messages_file_linenumber = 0 ;

	if ( ($use_live || $use_history) && $measuring_speed ) {
		$measure_start_line = $messages_file_totallines ;
		$measuring_speed = 0 ;
		$SIG{ALRM} = \&setup_timers ;
		alarm $measure_timer ;
	} ;

	# !NB! We don't used read_lines() here to avoid slurping the whole file
	# !NB! into memory which could be potentially disasterous to the host
	# !NB! system's performance &/or stability
	# loop - process each messages file lines
	MESSAGES_LINE: while ( $line = <$fh0> ) {
		# increment line number counter variable
		$messages_file_linenumber++ ;
		$messages_file_totallines++ ;
		$lastline = $line ;

		# clean up the line for processing
		# ... remove trailing <LF>
		chomp($line) ;
		# ... remove any MS-DOSish <CR> characters
		$line =~ s/\r// ;

		# Every 5 seconds, take a time stamp and bypass the prescreen to
		# allow the time-based cycle events to occur.
		if ($lines_per_second &&
		    $messages_file_totallines % ($lines_per_second * 5) == 0 ) {
		    	$time_now = time() ;
			$skip_prescreen = 1;
		} ;
		$skip_prescreen = 1 if $no_prescreen ;

		# Don't waste processing time on lines that aren't interesting
		# to cediag.  This test pre-screens the line in question to see
		# that it has at least part of the strings that could make it a
		# match below.  For messages files with lots of non-kernel data
		# in them, this can be a big time saver.
		if ( !$skip_prescreen && $line !~ /$re{'prescreen'}/ ) {
			$message_lines_skipped++ ;
			next("MESSAGES_LINE") ;
		} ;
		$skip_prescreen = 0;

		#####################
		# save message date #
		#####################

		# test - set $last_message_time variable
		if ( $line =~ /^($re{'date'})\s+/ ) {
			# store the date
			$last_message_time = $1 ;
			$last_message_time_nls = convert_to_nls($1) ;

			# This test is used to skip forward in a messages file
			# until the desired checkpoint date is reached.
			if ( !$processing_started ) {
				if (($use_live || $use_history) &&
				    ($last_message_time_nls <
					$initial_checkpoint) ) {
					$message_lines_skipped++ ;
					next("MESSAGES_LINE") ;
				} ;
				# else we've reached the checkpoint and can
				# start processing.
				echo("Checkpoint reached - processing started ".
				    "at line $messages_file_totallines.  ".
				    "time=$last_message_time_nls ".
				    "cp=$initial_checkpoint") ;
				$processing_started = 1 ;
				$timer{'procstart'} = time() ;
			} ;	# end test - has processing started
		} ;	# end test - set $last_message_time variable

		if (($use_live || $use_history) &&
		    ($time_now - $last_cycle_time) > 59) {
			# After the checkpoint is reached, check for necessary
			# downward priority adjustments to take up less CPU
			# time on busy systems.
			if ( $processing_started ) {
				adjust_run_priority() ;
			} ;

			# Roughly every minute, checkpoint our progress on live
			# systems.  This will allow cediag to restart and scan
			# forward to the lines where it left off.  Only resume
			# the saving of new checkpoints after we've passed the
			# initial checkpoint saved on the previous run.
			if ( $last_message_time_nls >
				($initial_checkpoint + $checkpoint_lookback)) {
				write_laf($last_message_time_nls) ;
			} ;
			$last_cycle_time = $time_now ;
		} ;

#^Feb 11 08:25:31 pts-bur-v440-b1 cediag: [ID 702911 daemon.notice] findings: 2 DIMM(s) having CEs with Esynd of 0x003e found
		# Don't let cediag match lines that it put in the messages
		# files itself.
		if ( $line =~ /$prog/ ) {
			$message_lines_skipped++ ;
			next("MESSAGES_LINE") ;
		} ;

		########################
		# detect system reboot #
		########################

		# check for a "root nexus =" line
		if ( $line =~ /^($re{'date'})\s+$hostname\s+.*\s+root\s+nexus\s+=\s+/ ) {
print $tracefh "$filename:$messages_file_linenumber:$last_message_time_nls:1637\n" if ($tracefh);
			# the system has rebooted ...
			# save the reboot date
			$i = $1 ;

			# test - branch on %ignore{'reboot'} flag
			if ( $ignore{'reboot'} ) {
				# do ignore the reboot ...
				# show status
				echo("", "$prog: $i -> system reboot occurred ... 'ignore reboot' flag set ... CE data intact") ;


			} else {
				# do *not* ignore reboot ...
				# show status
				echo("", "$prog: $i -> system reboot occurred ... CE data reset") ;

				# set the reboot_found flag to be true
				$flag{'reboot_found'} = $i ;

				# do the CE summary for the previous OS instance
				# if we are *not* running against a live system
				if ( ! $use_live ) { summary($i) ; } ;

				# invalidate data useless across a reboot ...
				# empty the %counter hash
				undef(%counter) ;

				# empty *only pertinent parts* of the %ue hash
				$ue{'count'} = 0 ;

				# reset the various %datapath values
				$datapath{'count'} = 0 ;
				undef($datapath{'esynd'}) ;

				# empty the %cedb hash
				undef(%cedb) ;

				# empty the %mpr hash
				undef(%mpr) ;
				# define basic %mpr variables
				$mpr{'sched_rm_count'} = 0 ;
				$mpr{'rm_count'} = 0 ;
				$mpr{'sched_clear_count'} = 0 ;
				$mpr{'clear_count'} = 0 ;

				# empty the %prlist hash
				undef(%prlist) ;

				# empty the %replace hash
				undef(%replace) ;

				# empty the %page hash
				undef(%page) ;

				# empty the %frc hash
				undef(%frc) ;

				# empty the %rce hash
				undef(%rce) ;

				# invalidate $message_date variable so %ce & 					# %pr hashes are reset too
				$message_date = "system reboot" ;

			} ;	# end test - branch on %ignore{'reboot'} flag

		} ;	# end of check for a "root nexus =" line

		#########################
		# PRL detection method  #
		#########################

		# test - set PRL detection method
		if ( $mpr_enabled && $flag{'reboot_found'} && $ram_prl ) {
			# PRL detection method 1
			$prl_dm = 1 ;

		} elsif ( ! $mpr_enabled ) {
			# no PRL detection
			$prl_dm = 0 ;

		} else {
			# PRL detection method 2
			$prl_dm = 2 ;

		} ;	# end test - set PRL detection method

		######################
		# detect date change #
		######################

		# get this line's message date
		$i = substr($line, 0, 15) ;

		# test - check for a date change
		if ( ! $message_date || $i ne $message_date ) {
			# change of date ...
			# save new message date
			$message_date = $i ;

			# invalidate data hashes as records should not cross
			# date boundaries
			# ... CE data
			#echo("undef(%ce)") ;
			undef(%ce) ;

			# ... [P]age [R]etirement data
			undef(%pr) ;

		} ;	# end test - check for a date change

		###########################
		# datapath fault messages #
		###########################

		# test - look for the start of a datapath fault message
		if (
#^Dec 15 07:37:12 x20mfitp01      PIO write transaction
			$line =~ /^($re{'date'})\s+$hostname\s+PIO\s+write\s+transaction\s*$/

#^Oct 17 18:20:03 titan sbus: [ID 505560 kern.info] SBus2 CE Primary Error from PIO: AFSR 0x80b90d00.00000000 AFAR 0x000001cc.00002400 Id 6
			|| $line =~ /^($re{'date'})\s+$hostname\s+.*SBus\S+\s+[UC]E\s+Primary\s+Error\s+from\s+PIO:\s+/

#^May  9 03:40:29 lutefisk SUNW,UltraSPARC-III+: [ID 300471 kern.info] NOTICE: [AFT0] IVC Event detected by CPU0 at TL=0, errID 0x0000269a.a4b400e0
			|| $line =~ /^($re{'date'})\s+$hostname\s+.*\[AFT0\]\s+IV[CU]\s+Event\s+detected\s+by\s+CPU\S+\s+at\s+/

				) {
print $tracefh "$filename:$messages_file_linenumber:$last_message_time_nls:1764\n" if ($tracefh);

			# update datapath fault counters
			$datapath{'count'}++ ;

		} ;	# end test - look for the start of a datapath fault message

		####################################
		# [u]ncorrectable [e]rror messages #
		####################################

		# test - look for the start of a UE, DUE or RUE event
		if (
#^Apr  9 07:09:18 mpsun510 SUNW,UltraSPARC-III+: [ID 983962 kern.warning] WARNING: [AFT1] Uncorrectable system bus (UE) Event detected by CPU322 Privileged Data Access at TL=0, errID 0x000b4613.948d424e
			$line =~ /^($re{'date'})\s+$hostname\s+.*\[AFT1\]\s+Uncorrectable\s+system\s+bus\s+\(UE\)\s+Event\s+detected\s+by\s+CPU\S+\s+/

#^Dec  7 04:53:56 gier SUNW,UltraSPARC-IIIi: [ID 877128 kern.warning] WARNING: [AFT1] Uncorrectable memory (UE) Event detected by CPU0 Privileged Data Access at TL=0, errID 0x00002fba.42dabd54
			|| $line =~ /^($re{'date'})\s+$hostname\s+.*\[AFT1\]\s+Uncorrectable\s+memory\s+\(UE\)\s+Event\s+detected\s+by\s+CPU\S+\s+/

#^Apr 20 05:42:03 eurssp2 SUNW,UltraSPARC-III+: [ID 783984 kern.warning] WARNING: [AFT1] Uncorrectable system bus (UE) Event on CPU1 Privileged Data Access at TL=0, errID 0x00000793.e67500e0
			|| $line =~ /^($re{'date'})\s+$hostname\s+.*\[AFT1\]\s+Uncorrectable\s+system\s+bus\s+\(UE\)\s+Event\s+on\s+CPU\S+\s+/

#^Jul 14 00:03:29 p2pdb2d SUNW,UltraSPARC-II: [ID 475681 kern.warning] WARNING: [AFT1] Uncorrectable Memory Error on CPU0 Data access at TL=0, errID 0x003478da.693261ae
			|| $line =~ /^($re{'date'})\s+$hostname\s+.*\[AFT1\]\s+Uncorrectable\s+Memory\s+Error\s+on\s+CPU\S+\s+/

#^Apr 27 05:13:34 eurssp2 SUNW,UltraSPARC-III+: [ID 479062 kern.warning] WARNING: [AFT1] First Error Uncorrectable system bus (UE) Event on CPU3 User Data Access at TL=0, errID 0x00000028.9882fb70
			|| $line =~ /^($re{'date'})\s+$hostname\s+.*\[AFT1\]\s+First\s+Error\s+Uncorrectable\s+system\s+bus\s+\(UE\)\s+Event\s+on\s+CPU\S+\s+/

#^Apr  9 15:21:18 mpsun510 SUNW,UltraSPARC-III+: [ID 307128 kern.warning] WARNING: [AFT1] DUE Event detected by CPU451 at TL=0, errID 0x000b60ed.b91519a3
			|| $line =~ /^($re{'date'})\s+$hostname\s+.*\[AFT1\]\s+DUE\s+Event\s+detected\s+by\s+CPU\S+\s+/

#^Apr 20 03:19:59 eurssp2 SUNW,UltraSPARC-III+: [ID 570322 kern.warning] WARNING: [AFT1] DUE Event on CPU3 at TL=0, errID 0x000f1e49.50360260
			|| $line =~ /^($re{'date'})\s+$hostname\s+.*\[AFT1\]\s+DUE\s+Event\s+on\s+CPU\S+\s+/

#^Apr 27 05:13:34 eurssp2 SUNW,UltraSPARC-III+: [ID 918701 kern.warning] WARNING: [AFT1] First Error DUE Event on CPU0 at TL=0, errID 0x00000028.645fee20
			|| $line =~ /^($re{'date'})\s+$hostname\s+.*\[AFT1\]\s+First\s+Error\s+DUE\s+Event\s+on\s+CPU\S+\s+/

#^Apr 27 03:41:42 denethor SUNW,UltraSPARC-IIIi: [ID 310524 kern.warning] WARNING: [AFT1] Uncorrectable remote memory/cache (RUE) Event detected by CPU3 Privileged Data Access at TL=0, errID 0x0001f978.632ed138
			|| $line =~ /^($re{'date'})\s+$hostname\s+.*\[AFT1\]\s+Uncorrectable\s+remote\s+memory\/cache\s+\(RUE\)\s+Event\s+detected\s+by\s+/

				) {
print $tracefh "$filename:$messages_file_linenumber:$last_message_time_nls:1805\n" if ($tracefh);

			# store UE date
			$ue{'date'} = convert_to_nls($1) ;

			# update UE counters
			$ue{'count'}++ ;

			$event++;
			echo(" $event: $1 UE  " .
			     "$messages_file:$messages_file_linenumber");

		} ;	# end test - look for the start of a UE, DUE or RUE event

		##################################
		# [c]orrectable [e]rror messages #
		##################################

		# test - look for start of a type#1 CE event
		if (
#^Apr 16 06:34:54 sf15k2d SUNW,UltraSPARC-III+: [ID 826188 kern.info] NOTICE: [AFT0] Corrected system bus (CE) Event detected by CPU386 at TL=0, errID 0x00000dcb.74261945
#^Jan  5 02:56:21 viefep20 SUNW,UltraSPARC-IIIi: [ID 832579 kern.info] NOTICE: [AFT0] Corrected memory (CE) Event detected by CPU3 at TL=0, errID 0x0005a675.00f4a2d8
			$line =~ /^($re{'date'})\s+$hostname\s+.*\s+\[AFT0\]\s+Corrected\s+.*\s+\((CE)\)\s+Event\s+.*\s+(CPU\S+)\s+at\s+TL=\S+,\s+errID\s+($re{'errid'})\s*$/

#^Feb 15 03:14:34 europa-dc2 SUNW,UltraSPARC-III+: [ID 543918 kern.info] NOTICE: [AFT0] EMC Event detected by CPU66 at TL=0, errID 0x00076e30.079f0dab
			|| $line =~ /^($re{'date'})\s+$hostname\s+.*\s+\[AFT0\]\s+(EMC)\s+Event\s+detected\s+by\s+(CPU\S+)\s+at\s+TL=\S+,\s+errID\s+($re{'errid'})\s*$/
				) {
print $tracefh "$filename:$messages_file_linenumber:$last_message_time_nls:1832\n" if ($tracefh);
			# undefine the %ce hash as we don't want any old data
			# corrupting our record
			#echo("undef(%ce)") ;
			undef(%ce) ;

			# populate %ce hash
			$ce{'date'} = $1 ;
			$ce{'nls'} = convert_to_nls($1) ;
			$ce{'type'} = $2 ;
			$ce{'cpu'} = $3 ;
			$ce{'errid'} = $4 ;

			if ($last_ce_stored != $last_ce_event) {
				echo(" WARNING: CE at event number " .
				     "$last_ce_event was NOT SAVED!!!");
			};
			$event++;
			$last_ce_event = $event;
			echo(" $event: $ce{'date'} CE  " .
			     "$messages_file:$messages_file_linenumber");
			# proceed to the rules checking
			goto("RULE_CHECKS") ;

		} ;	# end test - look for start of a type#1 CE event

		# test - look for start of a type#2 CE event
		if (
#^Oct 26 18:34:36 kbstr02 SUNW,UltraSPARC-II: [ID 649083 kern.info] [AFT0] Corrected Memory Error detected by CPU3, errID 0x001ac530.80ee0b3f
			$line =~/^($re{'date'})\s+$hostname\s+.*\s+\[AFT0\]\s+Corrected\s+Memory\s+Error\s+detected\s+by\s+(CPU\S+),\s+errID\s+($re{'errid'})\s*$/

#^Feb 13 13:48:51 op1p455www SUNW,UltraSPARC-II: [ID 784257 kern.notice] [AFT0] Corrected Memory Error on CPU9, errID 0x001a2836.d9b804c5
			|| $line =~/^($re{'date'})\s+$hostname\s+.*\s+\[AFT0\]\s+Corrected\s+Memory\s+Error\s+on\s+(CPU\S+),\s+errID\s+($re{'errid'})\s*$/

				) {
print $tracefh "$filename:$messages_file_linenumber:$last_message_time_nls:1867\n" if ($tracefh);
			# undefine the %ce hash as we don't want any old data
			# corrupting our record
			#echo("undef(%ce)") ;
			undef(%ce) ;

			# populate %ce hash
			$ce{'date'} = $1 ;
			$ce{'nls'} = convert_to_nls($1) ;
			$ce{'type'} = "CE" ;
			$ce{'cpu'} = $2 ;
			$ce{'errid'} = $3 ;

			if ($last_ce_stored != $last_ce_event) {
				echo(" WARNING: CE at event number " .
				     "$last_ce_event was NOT SAVED!!!");
			};
			$event++;
			$last_ce_event = $event;
			echo(" $event: $ce{'date'} CE  " .
			     "$messages_file:$messages_file_linenumber");
			# proceed to the rules checking
			goto("RULE_CHECKS") ;

		} ;	# end test - look for start of a type#2 CE event

		# test - rule 4 & 5:- look for AFAR line of a CE event
		if (
#^Apr 16 06:34:54 sf15k2d     AFSR 0x00000002<CE>.00000045 AFAR 0x00000180.05c84010
#^Oct 26 18:34:36 kbstr02     AFSR 0x00000000.00100000<CE> AFAR 0x00000000.319eca68
#^Jan  5 02:56:21 viefep20     AFSR 0x00100002<PRIV,CE>.00000142 AFAR 0x00000030.3b4944c0
#^Feb 15 03:14:34 europa-dc2     AFSR 0x00010000<EMC>.00070000 AFAR 0x00000043.5e049e10
			$line =~ /^$re{'date'}\s+$hostname\s+.*\s+AFSR\s+.*\s+AFAR\s+($re{'afar'})\s*$/

				) {
print $tracefh "$filename:$messages_file_linenumber:$last_message_time_nls:1902\n" if ($tracefh);

			# populate %ce hash
			$ce{'afar'} = $1 ;

			# !!! DO NOT "goto" !!! we may need to check FRC / RCE
			# proceed to the rules checking
			#goto("RULE_CHECKS") ;

		} ;	# end test - rule 4 & 5:- look for AFAR line of a CE event

		# test - look for Esynd line of a type#1 CE event
		if (
#^Apr 16 06:34:54 sf15k2d     Fault_PC 0x1027ec4 Esynd 0x0045 SB12/P0/B0/D3 J13600
#^Jan  5 02:56:21 viefep20     Fault_PC 0x10025340 Esynd 0x0142 C3/P0/B0/D0: B0/D0
#^Feb 15 03:14:34 europa-dc2     Fault_PC 0x10026554 Msynd 0x0007 SB2/P2/B0/D3 J15600
			$line =~ /^$re{'date'}\s+$hostname\s+.*\s+[EM]synd\s+(\S+)\s+(.*)\s*$/

				) {
print $tracefh "$filename:$messages_file_linenumber:$last_message_time_nls:1921\n" if ($tracefh);

			# populate %ce hash
			$ce{'esynd'} = $1 ;
			# ... DIMM detection moved to Persistent/Intermittent
			# ... section to cater for broader range of messages
			# ... files entries
			#$ce{'dimm'} = $2 ;

			# !!! DO NOT "goto" !!! we may need to check FRC / RCE
			# proceed to the rules checking
			#goto("RULE_CHECKS") ;

		} ;	# end test - look for Esynd line of a type#1 CE event

		# test - look for Esynd line of a type#2 CE event
		if (
#^Oct 26 18:34:36 kbstr02     UDBL Syndrome 0x9b Memory Module 1803
			$line =~ /^$re{'date'}\s+$hostname\s+.*\s+Syndrome\s+(\S+)\s+(.*)\s*$/

				) {
print $tracefh "$filename:$messages_file_linenumber:$last_message_time_nls:1942\n" if ($tracefh);

			# populate %ce hash
			$ce{'esynd'} = $1 ;

			# proceed to the rules checking
			goto("RULE_CHECKS") ;

		} ;	# end test - look for Esynd line of a type#2 CE event

		# test - look for Persistent/Intermittent CE line
		if (
#^Apr 16 06:34:54 sf15k2d SUNW,UltraSPARC-III+: [ID 549740 kern.info] [AFT0] errID 0x00000dcb.74261945 Corrected Memory Error on SB12/P0/B0/D3 J13600 is Persistent
#^Oct 26 18:34:36 kbstr02 SUNW,UltraSPARC-II: [ID 451830 kern.info] [AFT0] errID 0x001ac530.80ee0b3f Corrected Memory Error on 1803 is Persistent
#^Feb 15 03:14:34 europa-dc2 SUNW,UltraSPARC-III+: [ID 378335 kern.info] [AFT0] errID 0x00076e30.079f0dab Corrected Mtag Error on SB2/P2/B0/D3 J15600 is Persistent
			$line =~ /^($re{'date'})\s+$hostname\s+.*\[AFT0\]\s+errID\s+$re{'errid'}\s+Corrected\s+(Memory|Mtag)\s+Error\s+on\s+(.*)\s+is\s+(\S+)\s*$/

				) {
print $tracefh "$filename:$messages_file_linenumber:$last_message_time_nls:1960\n" if ($tracefh);

			# save variables
			$i = $1 ;
			$junk = $2 ;	# ignore $2

			# populate %ce hash
			$ce{'dimm'} = $3 ;
			$ce{'dimm'} = normalise_dimm($ce{'dimm'}) ;
			# set the frequency value (force to lower case)
			$ce{'frequency'} = lc($4) ;

			# update the CE count & dates for this DIMM
			ce_increment($ce{'dimm'}, $i, $ce{'frequency'}) ;

			# we now have enough data for a rule6 check ...
			# set the flag to run a rule#6 check if appropriate
			# test - rule6 in use
			if ( $use_rule6 ) { $fire{'rule6'} = $ce{'dimm'} ; } ;

		} ;	# end test - look for Persistent/Intermittent CE line

		# test - look for Bit error line of a CE event
		if (
#^Apr 16 06:34:54 sf15k2d SUNW,UltraSPARC-III+: [ID 666453 kern.info] [AFT0] errID 0x00000dcb.74261945 Data Bit 36 was in error and corrected
#^Oct 26 18:34:36 kbstr02 SUNW,UltraSPARC-II: [ID 424905 kern.info] [AFT0] errID 0x001ac530.80ee0b3f ECC Data Bit 58 was in error and corrected
#^Feb 15 03:14:34 europa-dc2 SUNW,UltraSPARC-III+: [ID 496039 kern.info] [AFT0] errID 0x00076e30.079f0dab MTAG Data Bit 0 was in error and corrected
#Oct 12 14:50:14 saturn SUNW,UltraSPARC-III: [ID 207989 kern.info] [AFT0] errID 0x00092f9b.b47a76a4 MTAG Check Bit 0 was in error and corrected
#Jan 16 14:40:45 G8DY1 SUNW,UltraSPARC: [ID 721242 kern.info] [AFT0] errID 0x0020ead1.a4ecfa8d ECC Check Bit  0 was in error and corrected
#Oct 12 04:39:43 vsp78-195 SUNW,UltraSPARC-IIIi+: [ID 443721 kern.info] [AFT0] errID 0x0001fa49.bfa2db23 Check Bit 2 was in error and corrected

			# $line =~ /^$re{'date'}\s+$hostname\s+.*\[AFT0\]\s+errID\s+$re{'errid'}.*\s+(\S+\s+[Bb]it\s+\S+)\s+/
			$line =~ /^$re{'date'}\s+$hostname\s+.*\[AFT0\]\s+errID\s+$re{'errid'}\s+(.*\s+[Bb]it\s+\S+)\s+/

				) {
print $tracefh "$filename:$messages_file_linenumber:$last_message_time_nls:1995\n" if ($tracefh);

			# populate %ce hash
			$ce{'bit'} = $1 ;

			# attempt to store the CE record & save the DIMM name
			# returned
			$_dimm = ce_store() ;
			$last_ce_stored = $last_ce_event;

			# test - use rule4 flag set
			if ( $use_rule4 && $_dimm ) {
				# set flag to do rule#4 check on effected DIMM
				$fire{'rule4'} = $_dimm ;

			} ;	# test - use rule4 flag set

			# test - use rule5b flag set
			if ( $use_rule5b && $_dimm ) {
				# set flag to do rule#5b check
				$fire{'rule5b'} = 1 ;

			} ;	# test - use rule5b flag set

			#!!! NOTE: this code moved into the summary() section
			#!!! NOTE: to ensure rule5 accurately sees that PRL has
			#!!! been reached
			#!!! Retired code - start
			#!!!# test - use rule5 flag set
			#!!!if ( $use_rule5 && $_dimm ) {
			#!!!	# do a rule#5 check on the effected DIMM
			#!!!	rule5($ce{'nls'}) ;
			#!!!
			#!!!} ;	# test - use rule5 flag set
			#!!! Retired code - end

			# !!! DO NOT "goto" !!! we may need to be checking the
			#			FRC / RCE type messages later
			# proceed to the rules checking
			#goto("RULE_CHECKS") ;

		} ;	# end test - look for Bit error line of a CE event

		######################
		# FRC / RCE messages #
		######################

		# test - FRC line#0
		if (
#^Nov 28 02:29:55 gier SUNW,UltraSPARC-IIIi: [ID 856508 kern.info] NOTICE: [AFT0] Corrected memory (FRC) Event detected by CPU0 at TL=0, errID 0x0008a0ad.a8565aa0
#^Jan  6 18:40:29 orafintest SUNW,UltraSPARC-IIIi: [ID 682332 kern.info] NOTICE: [AFT0] Corrected memory (FRC) Event detected by CPU1 at TL=0, errID 0x00335946.5723845f
			$line =~ /^($re{'date'})\s+$hostname\s+.*\[AFT0\]\s+Corrected\s+memory\s+\(FRC\)\s+Event\s+detected\s+by\s+CPU(\S+)\s+at\s+TL=\S+,\s+errID\s+($re{'errid'})\s*$/

				) {
print $tracefh "$filename:$messages_file_linenumber:$last_message_time_nls:2049\n" if ($tracefh);

			# logging code execution
			echo("matched FRC line#0") ;

			# populate %_frc hash
			$_frc{'nls'} = convert_to_nls($1) ;
			$_frc{'cpu'} = $2 ;
			$_frc{'errid0'} = $3 ;

			# proceed to the rules checking
			goto("RULE_CHECKS") ;

		} ;	# end test - FRC line#0

		# test - FRC line#1
		# NOTE: this may actually be line#2 on some systems
		if (
#^Nov 28 02:29:55 gier     Fault_PC 0x10034c30 Esynd 0x000d J_AID 3
#^Jan  6 18:40:29 orafintest     Fault_PC 0x1044bc8 Esynd 0x0128 J_AID 0
			$line =~ /^($re{'date'})\s+$hostname\s+.*Fault_PC\s+\S+\s+Esynd\s+(\S+)\s+J_AID\s+(\S+)\s*$/

				) {
print $tracefh "$filename:$messages_file_linenumber:$last_message_time_nls:2072\n" if ($tracefh);

			# logging code execution
			echo("matched FRC line#1") ;

			# populate %_frc hash
			$_frc{'esynd'} = $2 ;
			$_frc{'j_aid'} = $3 ;

			# proceed to the rules checking
			goto("RULE_CHECKS") ;

		} ;	# end test - FRC line#1

		# test - FRC line#2
		# NOTE: this may actually be line#3 on some systems
		if (
#^Nov 28 02:29:55 gier SUNW,UltraSPARC-IIIi: [ID 110000 kern.info] [AFT0] errID 0x0008a0ad.a8565aa0 Data Bit 41 was in error and corrected
#^Jan  6 18:40:29 orafintest SUNW,UltraSPARC-IIIi: [ID 275478 kern.info] [AFT0] errID 0x00335946.5723845f Data Bit 96 was in error and corrected
			$line =~ /^($re{'date'})\s+$hostname\s+.*\[AFT0\]\s+errID\s+($re{'errid'})\s+(.*)\s+was\s+in\s+error\s+and\s+corrected/
				) {
print $tracefh "$filename:$messages_file_linenumber:$last_message_time_nls:2093\n" if ($tracefh);

			# logging code execution
			echo("matched FRC line#2") ;

			# populate %_frc hash
			$i = $1 ;
			$_frc{'errid2'} = $2 ;
			$_frc{'bit'} = $3 ;

			# store the FRC record
			frc_store($i) ;

			# we should now have enough FRC data to perform a match
			# so set the process FRC/RCE matching flag
			$fire{'process_frc_rce'} = 1 ;

			# proceed to the rules checking
			goto("RULE_CHECKS") ;

		} ;	# end test - FRC line#2

		# test - RCE line#0
		if (
#^Nov 28 02:29:55 gier SUNW,UltraSPARC-IIIi: [ID 826713 kern.info] NOTICE: [AFT0] Corrected remote memory/cache (RCE) Event detected by CPU3 at TL=0, errID 0x0008a0ad.a856ee48
#^Jan  6 18:40:29 orafintest SUNW,UltraSPARC-IIIi: [ID 670614 kern.info] NOTICE: [AFT0] Corrected remote memory/cache (RCE) Event detected by CPU0 at TL=0, errID 0x00335946.57238c2f
			$line =~ /^($re{'date'})\s+$hostname\s+.*\[AFT0\]\s+Corrected\s+remote\s+memory\/cache\s+\(RCE\)\s+Event\s+detected\s+by\s+CPU(\S+)\s+at\s+TL=\S+,\s+errID\s+($re{'errid'})\s*$/

				) {
print $tracefh "$filename:$messages_file_linenumber:$last_message_time_nls:2122\n" if ($tracefh);

			# logging code execution
			echo("matched RCE line#0") ;

			# populate %_rce hash
			$_rce{'nls'} = convert_to_nls($1) ;
			$_rce{'cpu'} = $2 ;
			$_rce{'errid0'} = $3 ;

			# proceed to the rules checking
			goto("RULE_CHECKS") ;

		} ;	# end test - RCE line#0

		# test - RCE line#1
		if (
#^Nov 27 02:29:56 gier     AFSR 0x00100000<PRIV>.80000000<RCE> AFAR 0x00000002.29a010b0
#^Jan  6 18:40:29 orafintest     AFSR 0x00100000<PRIV>.81000000<RCE> AFAR 0x00000010.061b1570
			$line =~ /^($re{'date'})\s+$hostname\s+.*AFSR\s+\S+<RCE>\s+AFAR\s+($re{'afar'})\s*$/

				) {
print $tracefh "$filename:$messages_file_linenumber:$last_message_time_nls:2144\n" if ($tracefh);

			# logging code execution
			echo("matched RCE line#1") ;

			# populate %_rce hash
			$_rce{'afar'} = $2 ;

			# proceed to the rules checking
			goto("RULE_CHECKS") ;

		} ;	# end test - RCE line#1

		# test - RCE line#2
		if (
#^Nov 28 02:29:55 gier     Fault_PC 0x10025d40 J_REQ 0
#^Jan  6 18:40:29 orafintest     Fault_PC 0x1026eb4 J_REQ 1
			$line =~ /^($re{'date'})\s+$hostname\s+.*Fault_PC\s+\S+\s+J_REQ\s+(\S+)\s*$/

				) {
print $tracefh "$filename:$messages_file_linenumber:$last_message_time_nls:2164\n" if ($tracefh);

			# logging code execution
			echo("matched RCE line#2") ;

			# populate %_rce hash
			$_rce{'j_req'} = $2 ;

			# proceed to the rules checking
			goto("RULE_CHECKS") ;

		} ;	# end test - RCE line#2

		# test - RCE line#3
		if (
#^Nov 28 02:29:55 gier     C0/P0/B1: B1/D0 B1/D1 (applicable only if corresponding FRC Event also logged)
			$line =~ /^($re{'date'})\s+$hostname\s+.*\s+(C\S+:.*)\s+\(applicable\s+only\s+if\s+corresponding\s+FRC\s+Event\s+also\s+logged\)/

#^Aug  1 11:01:17 usi067     C1/P0/B1: B1/D0 B1/D1
			|| $line =~ m#^($re{'date'})\s+$hostname\s+(C\d+/P\d+/B\d+:\s+B\d+/D\d+\s+B\d+/D\d+$)#

#^Jan  6 18:40:29 orafintest     MB/P1/B0: B0/D0 B0/D1 (applicable only if corresponding FRC Event also logged)
			|| $line =~ /^($re{'date'})\s+$hostname\s+.*\s+(MB\/P\S\/B\S:.*)\s+\(applicable\s+only\s+if\s+corresponding\s+FRC\s+Event\s+also\s+logged\)/

#^Jan  6 18:40:29 simulated     MB/P1/B0: B0/D0 B0/D1
# ... simulated message
			|| $line =~ m#^($re{'date'})\s+$hostname\s+(MB\/P\S\/B\S:\s+B\d+/D\d+\s+B\d+/D\d+$)#

				) {
print $tracefh "$filename:$messages_file_linenumber:$last_message_time_nls:2193\n" if ($tracefh);

			# logging code execution
			echo("matched RCE line#3") ;

			# populate %_rce hash
			$i = $1 ;
			$_rce{'dimm'} = $2 ;
			$_rce{'dimm'} = normalise_dimm($_rce{'dimm'}) ;
			$rce_dimm_latest = $_rce{'dimm'} ;

			# store the RCE record
			rce_store($i) ;

			# we should now have enough FRC data to perform a match
			# so set the process FRC/RCE matching flag
			$fire{'process_frc_rce'} = 1 ;

			# proceed to the rules checking
			goto("RULE_CHECKS") ;

		} ;	# end test - RCE line#3

		################################
		# [p]age [r]etirement messages #
		################################
		# test - execute on MPR ready & able systems only
		if ( $mpr_capable_os && $mpr_kernel && $mpr_enabled ) {

			# test - look for start of a PR event
			if (
#^Apr 16 06:34:54 sf15k2d unix: [ID 596940 kern.warning] WARNING: [AFT0] 3 soft errors in less than 24:00 (hh:mm) detected from Memory Module SB12/P0/B0/D3 J13600
				$line =~ /^($re{'date'})\s+$hostname\s+.*\s+\[AFT0\]\s+\d+\s+soft\s+errors\s+in\s+less\s+than\s+.*\s+detected\s+from\s+Memory\s+Module\s+(.*)\s*$/
#
#^May  3 20:44:44 erpqas1 unix: [ID 752700 kern.warning] WARNING: [AFT0] Sticky Softerror encountered on Memory Module SB6/P2/B0/D2 J15500
#^Jul 21 07:51:55 suns15k.rrz.Uni-Koeln.DE unix: [ID 220797 kern.warning] WARNING: [AFT0] Sticky Softerr encountered on Memory Module SB7/P3/B0/D1 J16400
				|| $line =~ /^($re{'date'})\s+$hostname\s+.*\s+\[AFT0\]\s+Sticky\s+Softerr\S{0,2}\s+encountered\s+on\s+Memory\s+Module\s+(.*)\s*$/

# CR 6319316
#^Aug 21 11:04:47 ibsomain unix: [ID 566906 kern.warning] WARNING: [AFT0] Most recent 3 soft errors from Memory Module Slot A: J7900 exceed threshold (N=2, T=24h:00m) triggering page retire
#^Mar  6 03:20:12 eqddbs01 unix: [ID 566906 kern.warning] WARNING: [AFT0] Most recent 3 soft errors from Memory Module Board 4 J3700 exceed threshold (N=2, T=24h:00m) triggering page retire
				|| $line =~ /^($re{'date'})\s+$hostname\s+.*\s+\[AFT0\]\s+Most\s+recent\s+\d+\s+soft\s+errors\s+from\s+Memory\s+Module\s+(.*)\s+exceed\s+threshold\s+.*\s+page\s+retire$/

				) {
print $tracefh "$filename:$messages_file_linenumber:$last_message_time_nls:2237\n" if ($tracefh);

				# undefine the %pr hash as we don't want any
				# old data corrupting our record
				undef(%pr) ;

				# populate %pr hash
				$pr{'date'} = $1 ;
				$pr{'date0'} = $1 ;
				$pr{'dimm'} = $2 ;
				$pr{'dimm'} = normalise_dimm($pr{'dimm'}) ;

				# proceed to the rules checking
				goto("RULE_CHECKS") ;

			} ;	# end test - look for start of a PR event

			# test - PR "Scheduling removal of page" event
#^Feb  6 15:39:41 litespeed unix: [ID 618185 kern.notice] NOTICE: Scheduling removal of page 0x00000000.3ac70000
			if ( $line =~ /^($re{'date'})\s+$hostname\s+.*\s+Scheduling\s+removal\s+of\s+page\s+($re{'page'})\s*$/ ) {

print $tracefh "$filename:$messages_file_linenumber:$last_message_time_nls:2258\n" if ($tracefh);

				# populate %pr hash
				$pr{'date'} = $1 ;
				$pr{'date1'} = $1 ;
				$pr{'page1'} = $2 ;

				# attempt to store the "stage 1" PR record
				pr_store(1) ;

				# proceed to the rules checking
				goto("RULE_CHECKS") ;

			} ;	# end test - PR "Scheduling removal of page" event

			# test - PR "removed from service" event
#^Feb  6 15:39:44 litespeed unix: [ID 693633 kern.notice] NOTICE: Page 0x00000000.3ac70000 removed from service
			if ( $line =~ /^($re{'date'})\s+$hostname\s+.*\s+Page\s+($re{'page'})\s+removed\s+from\s+service\s*$/ ) {

print $tracefh "$filename:$messages_file_linenumber:$last_message_time_nls:2277\n" if ($tracefh);

				# populate %pr hash
				$pr{'date'} = $1 ;
				$pr{'date2'} = $1 ;
				$pr{'page2'} = $2 ;

				# attempt to store the "stage 2" PR record
				pr_store(2) ;

				# proceed to the rules checking
				goto("RULE_CHECKS") ;

			} ;	# end test - PR "removed from service" event

			# test - PR "error on page XXX cleared"
#^Feb  6 20:55:01 litespeed unix: [ID 221039 kern.notice] NOTICE: Previously reported error on page 0x00000000.39f54000 cleared
			if ( $line =~ /^($re{'date'})\s+$hostname\s+.*\s+(Previously)\s+reported\s+error\s+on\s+page\s+($re{'page'})\s+cleared\s*$/

#^Apr  9 16:43:56 mpsun510 unix: [ID 321153 kern.notice] NOTICE: Scheduling clearing of error on page 0x00000142.800ea000
				|| $line =~ /^($re{'date'})\s+$hostname\s+.*\s+(Scheduling)\s+clearing\s+of\s+error\s+on\s+page\s+($re{'page'})\s*$/

				) {
print $tracefh "$filename:$messages_file_linenumber:$last_message_time_nls:2300\n" if ($tracefh);

				# store variables
				$i = $1 ;
				$j = $2 ;

				# if these messages are encountered it means
				# that there is a high probablility that PRL
				# has been reached ...
				# NOTE: there may be no matching "scheduling
				# NOTE:	removal of page" message

				# debug
				echo("", "$i", "page clearing type '$j' message encountered") ;

				# set PRL as having been reached
				prl_reached($i) ;

				# increment counters
				# test - branch on msg type
				if ( $j =~ /Schedul/ ) {
					# 'scheduling clear' message
					$mpr{'sched_clear_count'}++ ;

				} elsif ( $j =~ /Previous/ ) {
					# 'previously scheduled' message
					$mpr{'clear_count'}++ ;

				} ;	# end test - branch on msg type

				# proceed to the rules checking
				goto("RULE_CHECKS") ;

			} ;	# end test - PR "error on page XXX cleared"

		} ;	# end test - execute on MPR ready & able systems only

		###############################
		# check the appropriate rules #
		###############################
		RULE_CHECKS:

		# test - process FRC/RCE matching?
		if ( $fire{'process_frc_rce'} ) {
			# call process to match FRC & RCE records
			$retval = process_frc_rce() ;

			# clear the firing flag
			undef($fire{'process_frc_rce'}) ;

			# flag a call to rule4 on DIMM with the latest RCE, if
			# appropriate to use rule4 & we have an RCE DIMM that is
			if ( $use_rule4 && $rce_dimm_latest ) {
				$fire{'rule4'} = $rce_dimm_latest ;
			} ;
			if ( $use_rule5b && $retval ) {
				$fire{'rule5b'} = 1 ;
			} ;

		} ;	# end test - process FRC/RCE matching?

		# test - call rule#4?
		if ( $fire{'rule4'} ) {
			# call rule#4 check with the offending DIMM as argument
			rule4($fire{'rule4'}) ;

			# clear the firing flag
			undef($fire{'rule4'}) ;

		} ;	# end test - call rule#4?

		# test - call rule#5b?
		if ( $fire{'rule5b'} ) {
			# call rule#5b check with the offending DIMM as argument
			rule5b($fire{'rule5b'}) ;

			# clear the firing flag
			undef($fire{'rule5b'}) ;

		} ;	# end test - call rule#5b?

		# test - call rule#6?
		if ( $fire{'rule6'} ) {
			# call rule#6 check with the offending DIMM as argument
			rule6($fire{'rule6'}) ;

			# clear the firing flag
			undef($fire{'rule6'}) ;

		} ;	# end test - call rule#6?

	} ;	# loop - process each messages file lines

	# Do this again so that writing of checkpoint is sure to use date of
	# last line in file.  Prescreening may have prevented this from being
	# set.
	if ( $lastline =~ /^($re{'date'})\s+/ ) {
		$last_message_time = $1 ;
		$last_message_time_nls = convert_to_nls($1) ;
	} ;

	# close the messages file
	file_close($fh0) ;

	# update the %fileline hash
	$fileline{$filename} = $messages_file_linenumber ;

} ;	# loop - process each messages file found

###############################
# merge the rule5_cestat data #
###############################

# test - check cestat's output via rule5
if ( $use_cestat ) {
	# show status
	echo("calling rule5_cestat() sub-routine") ;

	# process the cestat data
	rule5_cestat() ;

} ; 	# end test - check cestat's output via rule5

####################
# summarise output #
####################

# do the final summary
summary() ;

file_close($tracefh) if ($tracefh);	# close trace file

# record the date of the last line processed in the file - 24 hours
if ( $use_live || $use_history ) {
	write_laf(convert_to_nls($last_message_time)) ;
} ;

# leave a line
echo("") ;

# fancy title
echo("#############################") ;
echo("# Summary of messages files #") ;
echo("#############################") ;
echo("") ;

# initialise variables
undef($i) ;
undef($i0) ;
undef($j) ;
undef($k) ;
undef($k0) ;

# loop - process each messages file
foreach $i ( sort(keys(%filesize)) ) {
	# get the file line count
	$i0 = $fileline{$i} ;

	# calculate the size in MBs
	$j = sprintf("%.2f", $filesize{$i} / ( 1024 ** 2 )) ;

	# keep a cumulative total
	$k += $filesize{$i} ;
	$k0 += $fileline{$i} ;

	# show messages file & it's size
	echo("$i = $j MB ($i0 lines)") ;

} ;	# loop - process each messages file

# test - branch on whether messages files processed
if ( defined($k) && defined($k0) ) {
	# calculate the size in MBs
	$k = sprintf("%.2f", $k / ( 1024 ** 2 )) ;

	# show messages file & it's size
	echo("", "Total messages files size = $k MB ($k0 lines)") ;
	echo("Total messages lines read = $messages_file_totallines") ;
	echo("Total messages lines skipped = $message_lines_skipped") ;

} ;	# end test - branch on whether messages files processed

########################
# log memory footprint #
########################
# save memory usage
echo("", "Memory footprint taken from OS:-", `$exe{'ps'} -p $$ -o rss,vsz`) ;

###########################
# dump out our state data #
###########################

# *** disabled as functionality not in use ***
## use minimal indentation
#$Data::Dumper::Indent = 1 ;
#
## test - open the dump file
#if ( $use_live && ( $statefh = file_open(">$prog.state.txt") ) ) {
	## write out the data ...
	## ... %cedb
	#print $statefh "" . Data::Dumper->Dump([\%cedb], ['cedb']) ;
	## ... %mpr
	#print $statefh "" . Data::Dumper->Dump([\%mpr], ['mpr']) ;
	## ... %replace
	#print $statefh "" . Data::Dumper->Dump([\%replace], ['replace']) ;
#
#} ;	# test - open the dump file

#########
# end() #
#########

# record the ending time
$timer{'finish'} = time() ;

# log the wallclock time
echo("", "$prog: wallclock time -> " . ( $timer{'finish'} - $timer{'start'} ) . " seconds.") ;
echo("$prog: wallclock time -> " . convert_to_dhms( $timer{'finish'} - $timer{'start'} ) ) ;

# exit cleanly
exit 0 ;

################################################################################
# define functions #############################################################
################################################################################
################################################################################
# accept_licence - do the tool's licence acceptance process
sub accept_licence {
# $_[0] = 'query mode' is true

# declare variables, arrays & hashes
my($gmodule) ;
my($licence) ;
my($lafdir) ;
my($return) ;
my($accept) ;
my($query) ;

# define variables, arrays & hashes
$gmodule = "accept_licence" ;
$return = 1 ;
$query = $_[0] ;

########################
# licence text - start #
########################
$licence = '

                   ENTITLEMENT for SOFTWARE AND SERVICES

THIS ENTITLEMENT EVIDENCES YOUR RIGHT TO USE THE SOFTWARE UNDER THE TERMS
OF THE SUN MICROSYSTEMS, INC. SOFTWARE LICENSE AGREEMENT FOR THE SUN SOFTWARE
INDICATED HEREIN UNLESS OTHERWISE AGREED IN WRITING BETWEEN YOU AND
SUN MICROSYSTEMS, INC.

Licensee/Company: Service Providers and Customers Authorized by
                  Sun Microsystems, Inc. for use in debugging memory failure
                  on SPARC platform based systems

Effective Date: Date you receive the Software

Software: CE-Diag tool

Permitted Use: Commercial Use

License Term: Perpetual

Licensed Unit: N/A

Licensed unit Count: Any number of computer systems owned or operated by
                     Licensee

Service: None

Service Term: None

Additional Terms: You may make as many copies as necessary for use in
                  accordance with this Entitlement

                     Sun Microsystems, Inc. ("Sun")
                       SOFTWARE LICENSE AGREEMENT

READ THE TERMS OF THIS AGREEMENT ("AGREEMENT") CAREFULLY BEFORE OPENING
SOFTWARE MEDIA PACKAGE.  BY OPENING SOFTWARE MEDIA PACKAGE, YOU AGREE TO
THE TERMS OF THIS AGREEMENT.  IF YOU ARE ACCESSING SOFTWARE ELECTRONICALLY,
INDICATE YOUR ACCEPTANCE OF THESE TERMS BY SELECTING THE "ACCEPT" BUTTON AT
THE END OF THIS AGREEMENT.  IF YOU DO NOT AGREE TO ALL OF THE TERMS,
PROMPTLY RETURN THE UNUSED SOFTWARE TO YOUR PLACE OF PURCHASE FOR A REFUND
OR, IF SOFTWARE IS ACCESSED ELECTRONICALLY, SELECT THE "DECLINE" (OR "EXIT")
BUTTON AT THE END OF THIS AGREEMENT.  IF YOU HAVE SEPARATELY AGREED TO LICENSE
TERMS ("MASTER TERMS") FOR YOUR LICENSE TO THIS SOFTWARE, THEN SECTIONS 1-5
OF THIS AGREEMENT ("SUPPLEMENTAL LICENSE TERMS") SHALL SUPPLEMENT AND
SUPERSEDE THE MASTER TERMS IN RELATION TO THIS SOFTWARE.

1. Definitions.

(a) "Entitlement" means the collective set of applicable documents authorized
    by Sun evidencing your obligation to pay associated fees (if any) for the
    license, associated Services, and the authorized scope of use of Software
    under this Agreement.

(b) "Licensed Unit" means the unit of measure by which your use of Software
    and/or Service is licensed, as described in your Entitlement.

(c) "Permitted Use" means the licensed Software use(s) authorized in this
    Agreement as specified in your Entitlement.  The Permitted Use for any
    bundled Sun software not specified in your Entitlement will be
    evaluation use as provided in Section 3.

(d) "Service" means the service(s) that Sun or its delegate will provide, if
    any, as selected in your Entitlement and as further described in the
    applicable service listings at http://www.sun.com/service/servicelist

(e) "Software" means the Sun software described in your Entitlement. Also,
    certain software may be included for evaluation use under Section 3.

(f) "You" and "Your" means the individual or legal entity specified in the
    Entitlement, or for evaluation purposes, the entity performing the
    evaluation.

2. License Grant and Entitlement.

Subject to the terms of your Entitlement, Sun grants you a nonexclusive,
nontransferable limited license to use Software for its Permitted Use for
the license term.  Your Entitlement will specify (a) Software licensed,
(b) the Permitted Use, (c) the license term, and (d) the Licensed Units.

Additionally, if your Entitlement includes Services, then it will also
specify the (e) Service and (f) service term.

If your rights to Software or Services are limited in duration and the date
such rights begin is other than the purchase date, your Entitlement will
provide that beginning date(s).

The Entitlement may delivered to you in various ways depending on the manner
in which you obtain Software and Services, for example, the Entitlement may
be provided in your receipt, invoice or your contract with Sun or authorized
Sun reseller.  It may also be in electronic format if you download Software.

3. Permitted Use.

As selected in your Entitlement, one or more of the following Permitted Uses
will apply to your use of Software.  Unless you have an Entitlement that
expressly permits it, you may not use Software for any of the other Permitted
Uses.  If you don\'t have an Entitlement, or if your Entitlement doesn\'t
cover additional software delivered to you, then such software is for your
Evaluation Use.

(a) Evaluation Use.  You may evaluate Software internally for a period of
    90 days from your first use.

(b) Research and Instructional Use.  You may use Software internally to
    design, develop and test, and also to provide instruction on such uses.

(c) Individual Use.  You may use Software internally for personal, individual
    use.

(d) Commercial Use.  You may use Software internally for your own commercial
    purposes.

(e) Service Provider Use.  You may make Software functionality accessible
    (but not by providing Software itself or through outsourcing services) to
    your end users in an extranet deployment, but not to your affiliated
    companies or to government agencies.

4. Licensed Units.

Your Permitted Use is limited to the number of Licensed Units stated in your
Entitlement.  If you require additional Licensed Units, you will need
additional Entitlement(s).

5. Restrictions.

(a) The copies of Software provided to you under this Agreement is licensed,
    not sold, to you by Sun.  Sun reserves all rights not expressly granted.

(b) You may make a single archival copy of Software, but otherwise may not
    copy, modify, or distribute Software.  However if the Sun documentation
    accompanying Software lists specific portions of Software, such as header
    files, class libraries, reference source code, and/or redistributable
    files, that may be handled differently, you may do so only as provided in
    the Sun documentation.

(c) You may not rent, lease, lend or encumber Software.

(d) Unless enforcement is prohibited by applicable law, you may not decompile,
    or reverse engineer Software.

(e) The terms and conditions of this Agreement will apply to any Software
    updates, provided to you at Sun\'s discretion, that replace and/or
    supplement the original Software, unless such update contains a separate
    license.

(f) You may not publish or provide the results of any benchmark or comparison
    tests run on Software to any third party without the prior written consent
    of Sun.

(g) Software is confidential and copyrighted.

(h) Unless otherwise specified, if Software is delivered with embedded or
    bundled software that enables functionality of Software, you may not use
    such software on a stand-alone basis or use any portion of such software
    to interoperate with any program(s) other than Software.

(i) Software may contain programs that perform automated collection of system
    data and/or automated software updating services.  System data collected
    through such programs may be used by Sun, its subcontractors, and its
    service delivery partners for the purpose of providing you with remote
    system services and/or improving Sun\'s software and systems.

(j) Software is not designed, licensed or intended for use in the design,
    construction, operation or maintenance of any nuclear facility and Sun
    and it\'s licensors disclaim any express or implied warranty of fitness
    for such uses.

(k) No right, title or interest in or to any trademark, service mark, logo
    or trade name of Sun or its licensors is granted under this Agreement.

6. Term and Termination.

The license and service term are set forth in your Entitlement(s).  Your
rights under this Agreement will terminate immediately without notice from
Sun if you materially breach it or take any action in derogation of Sun\'s
and/or its licensors\' rights to Software.  Sun may terminate this Agreement
should any Software become, or in Sun\'s reasonable opinion likely to become,
the subject of a claim of intellectual property infringement or trade secret
misappropriation.  Upon termination, you will cease use of, and destroy,
Software and confirm compliance in writing to Sun.  Sections 1, 5, 6, 7, and
9-15 will survive termination of the Agreement.

7. Java Compatibility and Open Source.

Software may contain Java technology.  You may not create additional classes
to, or modifications of, the Java technology, except under compatibility
requirements available under a separate agreement available at
http://www.java.net

Sun supports and benefits from the global community of open source developers,
and thanks the community for its important contributions and open
standards-based technology, which Sun has adopted into many of its products.

Please note that portions of Software may be provided with notices and open
source licenses from such communities and third parties that govern the use
of those portions, and any licenses granted hereunder do not alter any rights
and obligations you may have under such open source licenses, however, the
disclaimer of warranty and limitation of liability provisions in this Agreement
will apply to all Software in this distribution.

8. Limited Warranty.

Sun warrants to you that for a period of 90 days from the date of purchase,
as evidenced by a copy of the receipt, the media on which Software is
furnished (if any) will be free of defects in materials and workmanship under
normal use.  Except for the foregoing, Software is provided "AS IS".  Your
exclusive remedy and Sun\'s entire liability under this limited warranty will
be at Sun\'s option to replace Software media or refund the fee paid for
Software.  Some states do not allow limitations on certain implied warranties,
so the above may not apply to you.  This limited warranty gives you specific
legal rights.  You may have others, which vary from state to state.

9. Disclaimer of Warranty.

UNLESS SPECIFIED IN THIS AGREEMENT, ALL EXPRESS OR IMPLIED CONDITIONS,
REPRESENTATIONS AND WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT ARE
DISCLAIMED, EXCEPT TO THE EXTENT THAT THESE DISCLAIMERS ARE HELD TO BE
LEGALLY INVALID.

10. Limitation of Liability.

TO THE EXTENT NOT PROHIBITED BY LAW, IN NO EVENT WILL SUN OR IT\'S LICENSORS
BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR SPECIAL, INDIRECT,
CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED REGARDLESS OF
THE THEORY OF LIABILITY, ARISING OUT OF OR RELATED TO THE USE OF OR INABILITY
TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
DAMAGES.  In no event will Sun\'s liability to you, whether in contract, tort
(including negligence), or otherwise, exceed the amount paid by you for
Software under this Agreement.  The foregoing limitations will apply even if
the above stated warranty fails of its essential purpose. Some states do not
allow the exclusion of incidental or consequential damages, so some of the
terms above may not be applicable to you.

11. Export Regulations.

All Software, documents, technical data, and any other materials delivered
under this Agreement are subject to U.S. export control laws and may be
subject to export or import regulations in other countries.  You agree to
comply strictly with these laws and regulations and acknowledge that you have
the responsibility to obtain any licenses to export, reexport, or import as
may be required after delivery to you.

12. U.S. Government Restricted Rights.

If Software is being acquired by or on behalf of the U.S. Government or by a
U.S. Government prime contractor or subcontractor (at any tier), then the
Government\'s rights in Software and accompanying documentation will be only
as set forth in this Agreement; this is in accordance with 48 CFR 227.7201
through 227.7202-4 (for Department of Defense (DOD) acquisitions) and with
48 CFR 2.101 and 12.212 (for non-DOD acquisitions).

13. Governing Law.

Any action related to this Agreement will be governed by California law and
controlling U.S. federal law.  No choice of law rules of any jurisdiction
will apply.

14. Severability.

If any provision of this Agreement is held to be unenforceable, this
Agreement will remain in effect with the provision omitted, unless omission
would frustrate the intent of the parties, in which case this Agreement will
immediately terminate.

15. Integration.

This Agreement, including any terms contained in your Entitlement, is the
entire agreement between you and Sun relating to its subject matter.  It
supersedes all prior or contemporaneous oral or written communications,
proposals, representations and warranties and prevails over any conflicting
or additional terms of any quote, order, acknowledgment, or other
communication between the parties relating to its subject matter during the
term of this Agreement.  No modification of this Agreement will be binding,
unless in writing and signed by an authorized representative of each party.

Please contact Sun Microsystems, Inc. 4150 Network Circle, Santa Clara,
California 95054 if you have questions. LFI#144066

' ;
######################
# licence text - end #
######################

# define the [L]icence [A]cceptance [F]ile ($laf) depending on who invoked ...
# test - branch on EUID
if ( $> == 0 ) {	# if ( $EFFECTIVE_USER_ID == 0 ) {
	# we are running as root ...
	$laf = "/var/opt/SUNWcest/conf/licence.accepted.for.root" ;
	$lafdir = dirname($laf) ;

	# test - check to see if the LAF directory exists
	if ( ! -d $lafdir ) {
		# the directory does not exist, so create it ...
		# use the OS' mkdir(1) so we can cheat by using the '-p' option
		# test - branch on mkdir(1) return code
		if ( system("$exe{'mkdir'} -m 755 -p $lafdir") ) {
			# there was a problem creating the directory ...
			# exit gracefully
			suicide("unable to create directory '$lafdir'") ;

		} ;	# end test - branch on mkdir(1) return code

	} ;	# end test - check to see if the LAF directory exists

} else {
	# we are running as a non-root user ...
	$laf = "$ENV{'HOME'}/.$prog.licence.accepted" ;

} ; # end test - branch on EUID

read_laf() ;

# test - are we running in 'query mode'
if ( $query ) {
	# just query the licence acceptance status ...
	# test - is the LAF present & contain this version of cediag
	if ( -f $laf && $accepted_license && $accepted_license eq $g_revision ) {
		# return 'good' to caller
		return 1 ;

	} else {
		# return 'bad' to caller
		return 0 ;

	} ;	# test - is the LAF present & contain this version of cediag

} ;	# end test - are we running in 'query mode'

# test - has the licence been accepted
if ( $accepted_license && ( $accepted_license eq $g_revision )) {
	# licence has been accepted previously ...
	# return good to the caller immediately
	return $return ;

} ;	# end test - has the licence been accepted

#
# if we get to here then the user has not already accepted the licence ...
#

# test - are we running attached to a terminal
if ( ! -t STDOUT ) {
	# we are not attached to a TTY & we need to be to accept the licence ...
	# exit gracefully
	suicide("please run '$prog' interactively to accept the licence") ;

} ;	# end test - are we running attached to a terminal

# show the licence
more($licence) ;

# prompt for the user to accept the licence
print STDOUT "Do you accept the licence terms?  If so, enter \"accept\" : " ;
$accept = <STDIN> ;

# test - branch on entered string
if ( $accept !~ /^\s*accept\s*$/i ) {
	# the user did not accept the licence ...
	# exit gracefully
	suicide("licence conditions not accepted by user") ;

} else {
	# show the licence was accepted
	msg("", "licence: licence accepted by user", "") ;

} ;	# end test - branch on entered string

write_laf(0) ;

} ;	# end of accept_licence()

################################################################################
################################################################################
# read_laf - read license acceptance file
sub read_laf {

# declare variables, arrays & hashes
my($gmodule) ;
my(@laf_lines) ;

# define variables, arrays & hashes
$gmodule = "read_laf" ;

# Assume the file doesn't exist and/or doesn't have a checkpoint value
$accepted_license = "none" ;
$initial_checkpoint = 0 ;

# test - does license acceptance file exist
if ( -f $laf ) {
	# read contents of license acceptance file
	@laf_lines = read_lines($laf) ;
	if ( $laf_lines[0] ) {
		$accepted_license = $laf_lines[0] ;
	}
	if ( $laf_lines[1] ) {
		$initial_checkpoint = $laf_lines[1] - $checkpoint_lookback ;
		if ( $initial_checkpoint < 0 ) {
			$initial_checkpoint = 0 ;
		} ;
	}
	if ( ! $allow_checkpointing ) {
		$initial_checkpoint = 0 ;
	} ;

} ;	# end test - does license acceptance file exist

return ;

} ;	# end of read_laf()

################################################################################
################################################################################
# write_laf - write license acceptance file
sub write_laf {
# $_[0] = previous run last message date (in time() format)

# declare variables, arrays & hashes
my($gmodule) ;
my($previous_run_time) ;
my($return) ;

# define variables, arrays & hashes
$gmodule = "write_laf" ;
$return = 1 ;
$previous_run_time = $_[0] ;

if ( $previous_run_time < 0 ) {
	$previous_run_time = 0 ;
} ;

# create the LAF to show user has accepted licence
$junk = file_open(">$laf") || suicide("unable to create licence acceptance file") ;

# write the current cediag revision information to the LAF
print $junk "$g_revision\n" ;
print $junk "$previous_run_time\n" ;

echo("Wrote checkpoint: $previous_run_time: " . localtime($previous_run_time)) ;

# close the LAF
close($junk) ;

} ;	# end of write_laf()

################################################################################
################################################################################
# setup_timers - sigalrm handler - measures this machine's line processing rate
sub setup_timers {

# declare variables, arrays & hashes
my($gmodule) ;
my($lines_tmp) ;

# define variables, arrays & hashes
$gmodule = "setup_timers" ;
$lines_tmp = $messages_file_totallines - $measure_start_line ;
$lines_per_second = $lines_tmp / $measure_timer ;

} ;

################################################################################
################################################################################
# adjust_run_priority - lower our priority if we've been running too long
sub adjust_run_priority {

# declare variables, arrays & hashes
my($gmodule) ;
my($return) ;
my($curr_runtime) ;
my($proc_runtime) ;
my($priocntl_info) ;

# define variables, arrays & hashes
$gmodule = "adjust_run_priority" ;
$return = 1 ;
$curr_runtime = time() - $timer{'start'} ;

# Don't make priority adjustments while we're still scanning forward to find
# the last saved checkpoint.

if (!$processing_started) {
	return 0 ;
} ;

if ( $adjustments_done ) {
	return 0 ;
} ;

$proc_runtime = time() - $timer{'procstart'} ;

# Don't make priority adjustments until after line processing has actually
# been going on for at least 5, 10 or 20 minutes, not the time from when the
# program first began running.

if ( $proc_runtime > 1200 && $priority_level < 3 ) {
	`$exe{'priocntl'} -s -p -60 $$ 2>/dev/null` ;
	$adjustments_done = 1 ;
	$priority_level = 3 ;
} elsif ( $proc_runtime > 600 && $priority_level < 2 ) {
	`$exe{'priocntl'} -s -p -30 $$ 2>/dev/null` ;
	$priority_level = 2 ;
} elsif ( $proc_runtime > 300 && $priority_level < 1 ) {
	`$exe{'priocntl'} -s -p -10 $$ 2>/dev/null` ;
	$priority_level = 1 ;
} ;

return $return ;

} ;	# end of adjust_run_priority()

################################################################################
################################################################################
# afar_2_page - convert an AFAR address to a page address
#	... returns:- page address with leading 0x0's stripped off
sub afar_2_page {
# $_[0] = AFAR to convert

# declare variables, arrays & hashes
my($return) ;
my($gmodule) ;
my($page) ;
my($afar) ;
my($predot) ;
my($postdot) ;
my($diff) ;

# define variables, arrays & hashes
$gmodule = "afar_2_page" ;
$return = 0 ;
$afar = $_[0] ;

# remove leading & trailing spaces
$afar =~ s/^\s*// ;
$afar =~ s/\s*$// ;

# check data validity ...
# test - branch on passed string pattern
if ( $afar =~ /^0x00000000\.00000000$/ || $afar =~ /^\.00000000$/ ) {
	# these are special case usually produced during rule#6 processing
	# where we don't capture the AFAR ...
	# define manually
	$afar = "0.00000000" ;

} elsif ( $afar =~ /^$re{'afar'}$/ ) {
	# strip off leading 0x0's
	$afar =~ s/^$re{'leading_hex'}// ;

} elsif ( $afar =~ /^[0-9a-fA-F]+\.[0-9a-fA-F]{8}$/ ) {
	# do nothing here ... we do the real work later
	;

} else {
	# weird AFAR string passed, die gracefully
	suicide("$gmodule: unhandleable AFAR string '$afar' passed!") ;

} ;	# end test - branch on passed string pattern

# split AFAR address into pre & post "dot" numbers to work around
# Perl's current 32-bitness
($predot, $postdot) = split(/\./, $afar) ;

# force to hexadecimal
$postdot = hex($postdot) ;

# round the AFAR down to the nearest 8K boundary (8K = sun4u page size) ...
# find the difference between AFAR & nearest lowest 8K boundary
$diff = $postdot % 8192 ;

# remove the difference
$postdot -= $diff ;

# return the short page address to the caller
return sprintf("%s.%x", $predot, $postdot) ;

} ;	# end of afar_2_page()

################################################################################
################################################################################
# ce_increment - increment CE counters
sub ce_increment {
# $_[0] = DIMM to increment count for
# $_[1] = date
# $_[2] = frequency

# declare variables, arrays & hashes
my($return) ;
my($gmodule) ;
my($dimm) ;
my($date) ;
my($nls) ;
my($aref) ;
my($frequency) ;

# define variables, arrays & hashes
$return = 1 ;
$gmodule = "ce_increment" ;
$dimm = $_[0] ;
$date = $_[1] ;
$frequency = $_[2] ;
$nls = convert_to_nls($date) ;

# update %counter hash
# ... for the DIMM
echo("", $date, "$gmodule: incrementing CE count for DIMM '$dimm'", "$gmodule: $frequency CE logged") ;
$counter{'ddr'}{'dimm'}{$dimm}{'ce'}++ ;  # since last dimm_data_reset()
$counter{'slr'}{'dimm'}{$dimm}{'ce'}++ ;  # since last reboot

# test - branch on frequency
if ( $frequency && $frequency !~ /^[Ii]ntermittent$/ ) {
	# show status
	echo("$gmodule: continuing processing of $frequency CE for DIMM '$dimm'") ;

} else {
	# show status
	echo("$gmodule: no further processing of $frequency CE for DIMM '$dimm'") ;

	# return immediately to caller with a bad return
	return 0 ;

} ;	# end test - branch on frequency

###############################################################################
# intermittent CEs are not pocessed beyond this point
###############################################################################

# update %counter hash
# ... for the DIMM
$counter{'ddr'}{'dimm'}{$dimm}{'ni_ce'}++ ;  # since last dimm_data_reset()
$counter{'slr'}{'dimm'}{$dimm}{'ni_ce'}++ ;  # since last reboot

#  show status
echo("$gmodule: incrementing 'ni_ce' count for DIMM='$dimm'") ;
echo("$gmodule: DIMM='$dimm' 'ddr' 'ni_ce' = $counter{'ddr'}{'dimm'}{$dimm}{'ni_ce'}") ;
echo("$gmodule: DIMM='$dimm' 'slr' 'ni_ce' = $counter{'slr'}{'dimm'}{$dimm}{'ni_ce'}") ;

# turn off warnings temporarily to prevent "initialised variable" messages
$^W = 0 ;	# $WARNING = 0 ;

# show status
echo("$gmodule: rolling CE dates for DIMM '$dimm'") ;

# update %cedb hash
# ... save previous CE date
$cedb{$dimm}{'date_previous'} = $cedb{$dimm}{'date_latest'} ;
$cedb{$dimm}{'date_latest'} = $nls ;

# turn off warnings back on
$^W = 1 ;	# $WARNING = 1 ;

# test - use rule6 enabled
if ( $use_rule6 ) {
	# do rule#6 data updates ...
	# define a reference to the array for convenience
	$aref = \@{$cedb{$dimm}{'dates'}} ;

	# add this CE date to the array of dates of CEs on this DIMM
	push(@$aref, $nls) ;

	# sort the dates numerically
	@$aref = sort { $a <=> $b } @$aref ;

	# loop - iterate until <= 24 elements in date array
	while ( scalar(@$aref) > $rule6_ce_count ) {
		# show status
		echo("$gmodule: shifting off lowest date for DIMM '$dimm'") ;

		# shift the lowest value off the date array
		shift(@$aref) ;

	} ;	# end loop - iterate until <= 24 elements in date array

} ;	# end test - use rule6 enabled

# return to caller
return $return ;

} ;	# end of ce_increment()

################################################################################
################################################################################
# ce_store - store a discovered CE record
#	... returns:- DIMM name if OK, 0 if failed
sub ce_store {

# declare variables, arrays & hashes
my($return) ;
my($gmodule) ;
my($aref) ;	# array reference
my($bitlabel) ;
my($dram) ;
my($dbgstring) ;

# define variables, arrays & hashes
$gmodule = "ce_store" ;
$return = 0 ;

# test - check we have sufficient data to store the CE record
if ( %ce && $ce{'nls'} && $ce{'errid'} && $ce{'dimm'} && defined($ce{'bit'}) && $ce{'afar'} ) {
	# sufficient data in the %ce hash ...

print $tracefh "$filename:$messages_file_linenumber:$last_message_time_nls:3253 - ce_store\n" if ($tracefh);
	# Parse the bit string into a bit label
	$ce{'bit'} =~ m/(.*)\s+[bB]it\s+(.*)/ ;

	if ( $1 eq 'Data' || $1 eq 'ECC Data' ) {
		$bitlabel = $2 ;
	} elsif ( $1 eq 'Check' || $1 eq 'ECC Check' ) {
		$bitlabel = "C" . $2 ;
	} elsif ( $1 eq 'MTAG Check' ) {
		$bitlabel = "MTC" . $2 ;
	} elsif ( $1 eq 'MTAG Data' ) {
		$bitlabel = "MT" . $2 ;
	} else {
		$bitlabel = "?" . $2 ;
	} ;

	# Use the bit label to determine the DRAM
	$dram = $bit2dram{$bitlabel} ;

	$dbgstring = "$gmodule: $event: DI='" ;
	if (defined($ce{'dimm'})) {
		$dbgstring .= $ce{'dimm'} ;
	} else {
		$dbgstring .= "undef";
	} ;
	$dbgstring .= "'||DR=" ;
	if (defined($dram)) {
		$dbgstring .= $dram ;
	} else {
		$dbgstring .= "undef";
	} ;
	$dbgstring .= "||B='" ;
	if (defined($ce{'bit'})) {
		$dbgstring .= $ce{'bit'} ;
	} else {
		$dbgstring .= "undef";
	} ;
	$dbgstring .= "'||BL='" ;
	if (defined($bitlabel)) {
		$dbgstring .= $bitlabel ;
	} else {
		$dbgstring .= "undef";
	} ;
	$dbgstring .= "'||A=" ;
	if (defined($ce{'afar'})) {
		$dbgstring .= $ce{'afar'} ;
	} else {
		$dbgstring .= "undef";
	} ;
	$dbgstring .= "||F=$ce{'frequency'}" ;
	# debug
	echo($dbgstring) ;

	# show status
	echo("$gmodule: '$ce{'frequency'}' CE ... actioning ($filename:$messages_file_linenumber)") ;

	# update the %datapath Esynd hash if possible
	# test - do we have Esynd data
	if ( $ce{'esynd'} ) {
		# clean up Esynd value ...
		# ... remove trailing commas
		$ce{'esynd'} =~ s/,+$// ;

		# we have Esynd data to work with
		echo("$gmodule: Esynd=$ce{'esynd'}") ;

		# update hash, increment counter
		$datapath{'esynd'}{$ce{'esynd'}}{'dimm'}{$ce{'dimm'}}++ ;

	} else {
		# we *do not* have Esynd data to work with
		echo("No Esynd captured") ;

	} ;	 # end test - do we have Esynd data

	# strip off leading 0x0's from AFAR to save space
	$ce{'afar'} =~ s/^$re{'leading_hex'}// ;

	# get page number from AFAR
	$ce{'page'} = afar_2_page($ce{'afar'}) ;

	# debug
	echo("$gmodule: AFAR = $ce{'afar'}") ;
	echo("$gmodule: deduced page = $ce{'page'}") ;

	# calculate the modulo64 value
	$i = (split(/\./, $ce{'afar'}))[1] ;
	$i = hex($i) & 0x3f ;
	$ce{'m64'} = $i ;

	# update %page hash
	$page{$ce{'page'}}{'ce_count'}++ ;
	$page{$ce{'page'}}{'dimm'} = $ce{'dimm'} ;
	# do not record dates ... memory saving decision
	#push(@{$page{$ce{'page'}}{'ce_dates'}}, $ce{'nls'}) ;

	# turn warning off temporarily to stop useless messages
	$^W = 0 ;	# $WARNING = 0 ;

	# test - non-intermittent CE
	if ( $ce{'frequency'} !~ /^Intermittent$/i ) {
	} ;	# end test - non-intermittent CE

	# ... for the DIMM/Bit
	$counter{'ddr'}{'dimm'}{$ce{'dimm'}}{'bit'}{$ce{'bit'}}{'ce'}++ ;
	$counter{'slr'}{'dimm'}{$ce{'dimm'}}{'bit'}{$ce{'bit'}}{'ce'}++ ;

	# update %cedb hash
	# ... for the DIMM/Bit
	$cedb{$ce{'dimm'}}{'bit'}{$ce{'bit'}}{'m64_latest'} = $ce{'m64'} ;
	$cedb{$ce{'dimm'}}{'bit'}{$ce{'bit'}}{'date_previous'} = $cedb{$ce{'dimm'}}{'bit'}{$ce{'bit'}}{'date_latest'} ;
	$cedb{$ce{'dimm'}}{'bit'}{$ce{'bit'}}{'date_latest'} = $ce{'nls'} ;

	# ... for the DIMM/Bit/m64
	# save the previous date ...
	$cedb{$ce{'dimm'}}{'bit'}{$ce{'bit'}}{'m64'}{$ce{'m64'}}{'date_previous'} = $cedb{$ce{'dimm'}}{'bit'}{$ce{'bit'}}{'m64'}{$ce{'m64'}}{'date_latest'} ;

	# save latest date
	$cedb{$ce{'dimm'}}{'bit'}{$ce{'bit'}}{'m64'}{$ce{'m64'}}{'date_latest'} = $ce{'nls'} ;

	# increment the count for DIMM/Bit/m64
	$counter{'ddr'}{'dimm'}{$ce{'dimm'}}{'bit'}{$ce{'bit'}}{'m64'}{$ce{'m64'}}{'ce'}++ ;
	$counter{'slr'}{'dimm'}{$ce{'dimm'}}{'bit'}{$ce{'bit'}}{'m64'}{$ce{'m64'}}{'ce'}++ ;

	# increment the count for DIMM/Bit/m64/AFAR
	$counter{'ddr'}{'dimm'}{$ce{'dimm'}}{'bit'}{$ce{'bit'}}{'m64'}{$ce{'m64'}}{'afar'}{$ce{'afar'}}{'ce'}++ ;
	$counter{'slr'}{'dimm'}{$ce{'dimm'}}{'bit'}{$ce{'bit'}}{'m64'}{$ce{'m64'}}{'afar'}{$ce{'afar'}}{'ce'}++ ;

	# increment the count for DIMM/Bit/AFAR/NI_CE (rule5b)
	if ( $ce{'frequency'} !~ /^Intermittent$/i ) {
		# increment the count for DIMM/Bit/AFAR non-intermittent
		$counter{'ddr'}{'dimm'}{$ce{'dimm'}}{'bit'}{$ce{'bit'}}{'afar'}{$ce{'afar'}}{'ni_ce'}++ ;
		push(@{$counter{'ddr'}{$ce{'dimm'}}{$ce{'bit'}}{$ce{'afar'}}}, $ce{'nls'}) ;

		if ($counter{'ddr'}{'dimm'}{$ce{'dimm'}}{'bit'}{$ce{'bit'}}{'afar'}{$ce{'afar'}}{'ni_ce'} > $rule5b_ce_count) {
			$counter{'ddr'}{'dimm'}{$ce{'dimm'}}{'bit'}{$ce{'bit'}}{'afar'}{$ce{'afar'}}{'ni_ce'}-- ;
			shift(@{$counter{'ddr'}{$ce{'dimm'}}{$ce{'bit'}}{$ce{'afar'}}}) ;
		} ;
	} ;	# end test - non-intermittent CE

	# increment the count for DIMM/DRAM/Bit/CW/AFAR
	$counter{'ddr'}{'dimm'}{$ce{'dimm'}}{'dram'}{$dram}{'bit'}{$ce{'bit'}}{'m64'}{$ce{'m64'}}{'afar'}{$ce{'afar'}}{'ce'}++ ;
	$counter{'slr'}{'dimm'}{$ce{'dimm'}}{'dram'}{$dram}{'bit'}{$ce{'bit'}}{'m64'}{$ce{'m64'}}{'afar'}{$ce{'afar'}}{'ce'}++ ;

	# debug
	echo("$gmodule: $ce{'dimm'}||$ce{'bit'}||$ce{'m64'} date_previous -> $cedb{$ce{'dimm'}}{'bit'}{$ce{'bit'}}{'m64'}{$ce{'m64'}}{'date_previous'}") ;
	echo("$gmodule: $ce{'dimm'}||$ce{'bit'}||$ce{'m64'} date_latest -> $cedb{$ce{'dimm'}}{'bit'}{$ce{'bit'}}{'m64'}{$ce{'m64'}}{'date_latest'}") ;

	# turn warning back on
	$^W = 1 ;	# $WARNING = 1 ;

	# set return value to be the DIMM name
	$return = $ce{'dimm'} ;

} else {
	# insufficient data in the %ce hash ... possible mangled messages
	echo("", "$gmodule: WARNING: file: $filename at line: $messages_file_linenumber ...", "$gmodule: ... insufficient data in CE's %ce to store record", "$gmodule: ... possibly mangled messages") ;
	#msg("$gmodule: WARNING: file: $filename at line: $messages_file_linenumber ...", "$gmodule: ... insufficient data in CE's %ce to store record", "$gmodule: ... possibly mangled messages") ;


} ;	# test - check we have sufficient data to store the CE record

# return appropriately to the caller
return $return ;

} ;	# end of ce_store()

################################################################################
################################################################################
# convert_to_dhms - convert seconds to Hours:Minutes:Seconds
sub convert_to_dhms {
# $_[0] - seconds string to convert

# declare variables, array & hashes
my($seconds) ;
my($return) ;
my($d) ;
my($h) ;
my($m) ;
my($s) ;

# define variables, array & hashes
$seconds = $_[0] ;

# work out the days
$d = int( $seconds / ( 60 * 60 * 24 ) ) ;

# recalculate the remaining seconds
if ( $d ) { $seconds -= ( $d * 60 * 60 * 24 ) ; } ;

# work out the hours
$h = int( $seconds / ( 60 * 60 ) ) ;

# recalculate the remaining seconds
if ( $h ) { $seconds -= ( $h * 60 * 60 ) ; } ;

# work out the minutes
$m = int( $seconds / 60 ) ;

# recalculate the remaining seconds
if ( $m ) { $seconds -= ( $m * 60 ) ; } ;

# work out the seconds
$s = $seconds ;

# build the string to return
$return = "$d days, $h hours, $m minutes, $s seconds" ;

# return the nls to caller
return $return ;

} ;	# end of convert_to_dhms() ;

################################################################################
################################################################################
# convert_to_nls - convert date string to non-leap seconds since Jan 1 1970
sub convert_to_nls {
# $_[0] - date string to convert

# declare variables, array & hashes
my($gmodule) ;
my($caller) ;
my($caller_line) ;
my($date) ;
my($yr) ;
my($mth_str) ;
my($mth) ;
my($day) ;
my($hr) ;
my($min) ;
my($sec) ;
my(%mths) ;
my($now) ;
my($return) ;
my($no_future_times) ;

# define variables, array & hashes
$gmodule = "convert_to_nls" ;
$mths{'Jan'} = $mths{'jan'} = 0 ;
$mths{'Feb'} = $mths{'feb'} = 1 ;
$mths{'Mar'} = $mths{'mar'} = 2 ;
$mths{'Apr'} = $mths{'apr'} = 3 ;
$mths{'May'} = $mths{'may'} = 4 ;
$mths{'Jun'} = $mths{'jun'} = 5 ;
$mths{'Jul'} = $mths{'jul'} = 6 ;
$mths{'Aug'} = $mths{'aug'} = 7 ;
$mths{'Sep'} = $mths{'sep'} = 8 ;
$mths{'Oct'} = $mths{'oct'} = 9 ;
$mths{'Nov'} = $mths{'nov'} = 10 ;
$mths{'Dec'} = $mths{'dec'} = 11 ;
$date = $_[0] ;
$no_future_times = 0 ;
$caller = (caller(1))[3] ;
$caller_line = (caller(0))[2] ;

# test - branch on sample time
if ( $sample_time ) {
	# use the previously collected sample time
	$now = $sample_time ;

} else {
	# use current time
	$now = time() ;

} ;	# end test - branch on sample time

# test - branch on passed date format
if ( ! defined($date) ) {
	# eeek! how did we get to here?
	# die gracefully
	suicide("$gmodule: undefined 'date' passed by $caller on line#$caller_line") ;

} elsif ( $date =~ /^([A-Z][a-z]{2})\s+(\d{1,2})\s+(\d{2}):(\d{2}):(\d{2})\b/ ) {
	# syslog format -> Jun 27 03:19:52
	$mth_str = $1 ;
	$mth = $mths{$mth_str} ;
	$day = $2 ;
	$hr = $3 ;
	$min = $4 ;
	$sec = $5 ;
	$yr = (localtime($now))[5] + 1900 ;

	# this is a syslog format so set the no_future_times flag to be true
	$no_future_times = 1 ;

	# test - future time
	# ... mth is in future
	# ... mth is same as this month but day is in future
	if (
		( $mth > (localtime($now))[4]
			|| ( $mth == (localtime($now))[4] && $day > (localtime($now))[3] )
		) && $no_future_times ) {
		# take 1 year off the time
		$yr -= 1 ;

	} ;	# end test - future time

} elsif ( $date =~ /^(\d{4})\.(\d{2})\.(\d{2})\.(\d{2})\.(\d{2})/ ) {
	# Explorer "Finished:" format -> 2004.12.31.23.59
	$mth = $2 - 1 ;
	$day = $3 ;
	$hr = $4 ;
	$min = $5 ;
	$sec = 0 ;
	$yr = $1 ;

} elsif ( $date =~ /^\d+$/ ) {
	# already in 'nls' format ... just return it directly
	return $date ;

} else {
	# eeek! how did we get to here?
	# die gracefully
	suicide("$gmodule: unexpected test branch encountered, passed by $caller on line#$caller_line") ;


} ;	# end test - branch on passed date format

# convert to nls
$return = timelocal($sec, $min, $hr, $day, $mth, $yr) ;

# return the nls to caller
return $return ;

} ;	# end of convert_to_nls() ;

################################################################################
################################################################################
# destate - return 'disabled' or 'enabled' string of given value
sub destate {
# $_[0] = value to determine string for

# declare variables, arrays & hashes
my($return) ;
my($value) ;

# define variables, arrays & hashes
$value = $_[0] ;

# test - branch on value of $value string
if ( defined($value) && $value eq "enabled" ) {
	# this is 'enabled' ...
	$return = 'enabled' ;

} elsif ( defined($value) && $value eq "disabled" ) {
	# this is 'disabled' ...
	$return = 'disabled' ;

} elsif ( defined($value) && $value ) {
	# this is 'enabled' ...
	$return = 'enabled' ;

} elsif ( defined($value) && ! $value ) {
	# this is 'disabled' ...
	$return = 'disabled' ;

} else {
	# this is 'n/a' ...
	$return = 'disabled' ;

} ;	# end test - branch on value of $value string

# return appropriately to the caller
return $return ;

} ;	# end destate()

################################################################################
################################################################################
# dimm_data_reset - reset a DIMM's CE record data
sub dimm_data_reset {
# $_[0] = DIMM to reset data for

# declare variables, arrays & hashes
my($return) ;
my($gmodule) ;
my($dimm) ;

# define variables, arrays & hashes
$gmodule = "dimm_data_reset" ;
$return = 1 ;
$dimm = $_[0] ;

# debug
echo("$gmodule: resetting CE record data for DIMM $dimm") ;

# update %cedb hash
undef($cedb{$dimm}{'bit'}) ;
undef($cedb{$dimm}{'dates'}) ;

# reset 'DDR' counters for this DIMM
undef($counter{'ddr'}{'dimm'}{$dimm}) ;

# return appropriately to the caller
return $return ;

} ;	# end of dimm_data_reset()

################################################################################
################################################################################
# dimm_fail_advise - display the advice for a DIMM failed under a given rule &
#			update the appropriate flags
sub dimm_fail_advise {
# $_[0] = DIMM to advise about
# $_[1] = 'ruleN' string DIMM failed under

# declare variables, arrays & hashes
my($return) ;
my($gmodule) ;
my($dimm) ;
my($rule) ;
my($advised) ;
my(@rm_from) ;
my($rm) ;

# define variables, arrays & hashes
$gmodule = "dimm_fail_advise" ;
$return = 1 ;
$dimm = $_[0] ;
$rule = $_[1] ;

# debug
echo("$gmodule: producing advice for DIMM $dimm having matched '$rule'") ;

# update log file
echo("advice:$policy{$rule}{'priority'}: replace DIMM '$dimm' $policy{$rule}{'when'}") ;

# test - set advised flag
$^W = 0 ;	# $WARNING = 0 ;
if ( $replace{'dimm'}{$dimm}{'advised'} ) { $advised = 1 ; } else { $advised = 0 ; } ;
$^W = 1 ;	# $WARNING = 1 ;
# end test - set advised flag

# test - have we already 'advised' for this DIMM
if ( $advised ) {

	# DIMM already 'advised' ...
	# update log file
	echo("advice: DIMM '$dimm' already 'advised' by '$replace{'dimm'}{$dimm}{'advised'}' match") ;

} else {
	# DIMM *not* already 'advised' ...
	# advise to replace DIMM as per appropriate policy
	msg("advice:$policy{$rule}{'priority'}: replace DIMM '$dimm' $policy{$rule}{'when'}") ;

	# update the 'advised' flag to ensure a subsequent rule does not
	# also advise replacement under a different policy
	$replace{'dimm'}{$dimm}{'advised'} = $rule ;

	# remove this DIMM for any subsequent rule matches ...
	# test - build a list of rules to remove from
	if ( $rule eq "rule4a" || $rule eq "rule4b" ) {
		# remove from rule5 & rule6
		@rm_from = qw(rule5 rule5b rule6) ;

	} elsif ( $rule eq "rule5" || $rule eq "rule5b" ) {
		# remove from rule6
		@rm_from = qw(rule6) ;

	} ;	# test - build a list of rules to remove from

	# remove the appropriate %replace{'rule#'} hash entries
	# loop - iterate through hash removal keys
	foreach $rm ( @rm_from ) {
		# remove the %replace hash entry
		echo("removing \$replace{'$rm'}{'$dimm'} hash entry") ;
		delete($replace{$rm}{$dimm}) ;

	} ;	# end loop - iterate through hash removal keys

} ;	# test - have we already 'advised' for this DIMM

# return appropriately to the caller
return $return ;

} ;	# end of dimm_fail_advise()

################################################################################
################################################################################
# echo - send status messages to $log file
sub echo {
# @_ - messages to log

# declare variables, arrays & hashes
my($line) ;

# just return to caller if log filehandle is not valid
if ( ! $logfh ) { return ; } ;

# loop - iterate through each message
foreach $line ( @_ ) {
	# print the message to $log
	print $logfh "$line\n" ;

} ;	# end loop - iterate through each message

} ;	# end of echo()

################################################################################
################################################################################
# file_close - close the given filehandle
sub file_close {
# $_[0] - filehandle string to close

# declare variables, arrays & hashes
my($fh) ;
my($return) ;

# define variables, arrays & hashes
$fh = $_[0] ;
$return = undef() ;

# test - close() of file successful
if ( ! $fh ) {
	# not a valid filehandle ...
	# set the return value to be good
	$return = 0 ;

} elsif ( close($fh) ) { ;
	# file was closed successfully ...
	# set the return value to be good
	$return = 0 ;

} ;	# end test - close() of file successful

# return appropriately to the caller
return $return ;

} ;	# end of file_close()

################################################################################
################################################################################
# file_open - open the given filename
sub file_open {
# $_[0] - filename string to open

# declare variables, arrays & hashes
my($file) ;
my($lockmode) ;
my($return) ;

# define variables, arrays & hashes
$file = $_[0] ;
$return = undef() ;

# define local variables
# !!! if we don't make FILE local then subsequent invocations of this subroutine
# !!! will close the existing open filehandle & open a new one ... not what we
# !!! desire at all !!!
local *FILE ;

# test - open() of file successful
if ( open(FILE, $file) ) { ;
	# file was opened successfully ...
	# set the return value to be the filehandle
	$return = *FILE ;

	# lock the file appropriately ...
	# test - branch on file's open type
	if ( $file =~ /^\+/ || $file =~ /^\>/ ) {
		# file opened in write mode ... use exclusive lock
		$lockmode = "2" ;

	} elsif ( $file =~ /^\</ || $file !~ /^\|/ ) {
		# file opened in read mode ... use shared lock
		$lockmode = "1" ;

	} ;	# end test - branch on file's open type

	# if we have a valid lock mode then set the lock
	# ... we will block until the lock is obtained
	if ( $lockmode ) {
		# status msg
		echo("", "locking $file with mode $lockmode") ;

		# lock the file
		flock(FILE, $lockmode) ;

		# status msg
		echo("locked $file with mode $lockmode") ;

	} ;	# end of valid lock mode test

} ;	# end test - open() of file successful

# return appropriately to the caller
return $return ;

} ;	# end of file_open()

################################################################################
################################################################################
# frc_store - store the FRC record data
sub frc_store {
# $_[0] - date of message ... only used for logging purposes

# declare variables, arrays & hashes
my($return) ;
my($gmodule) ;
my($date) ;

# define variables, arrays & hashes
$return = 0 ;
$gmodule = "frc_store" ;
$date = $_[0] ;

# show date being processed
echo("$gmodule: $date") ;

# test - branch of undef() fields
if (
	! defined($_frc{'errid0'})
	|| ! defined($_frc{'errid2'})
	|| ! defined($_frc{'cpu'})
	|| ! defined($_frc{'esynd'})
	|| ! defined($_frc{'j_aid'})
	|| ! defined($_frc{'bit'})
	|| ! defined($_frc{'nls'})
   ) {
	# insufficient data to store FRC record
	echo("$gmodule: insufficient FRC data to store FRC record") ;

	# return immediately to caller
	goto("FRC_STORE_FINISH") ;

} ;	# end test - branch of undef() fields

# test - branch on collected data status
if ( $_frc{'errid0'} eq $_frc{'errid2'} ) {
	# good ErrID match ...
	echo("$gmodule: matched ErrIDs ... storing FRC record") ;
print $tracefh "$filename:$messages_file_linenumber:$last_message_time_nls:3884 - frc_store\n" if ($tracefh);

	# store the record
	$frc{$_frc{'errid0'}}{'cpu'} = $_frc{'cpu'} ;
	$frc{$_frc{'errid0'}}{'bit'} = $_frc{'bit'} ;
	$frc{$_frc{'errid0'}}{'nls'} = $_frc{'nls'} ;
	$frc{$_frc{'errid0'}}{'j_aid'} = $_frc{'j_aid'} ;
	$frc{$_frc{'errid0'}}{'esynd'} = $_frc{'esynd'} ;

} else {
	# mixed ErrIDs ... mangled messages?
	echo("$gmodule: mixed ErrIDs ... unable to store FRC record") ;

	# return immediately to caller
	goto("FRC_STORE_FINISH") ;

} ;	# end test - branch on collected data status

# label for goto statement
FRC_STORE_FINISH:

# clear the %_frc hash to prevent re-use of old data
echo("$gmodule: resetting %_frc") ;
undef(%_frc) ;

# return to caller
return $return ;

} ;	# end of frc_store()

################################################################################
################################################################################
# getdirname - get the directory name of the given file
sub getdirname {
# $_[0] - file to get directory name of

# declare variables, arrays & hashes
my($gmodule) ;
my($return) ;
my($file) ;
my($dir) ;
my($cwd) ;
my($symlink) ;

# define variables, arrays & hashes
$gmodule = "getdirname" ;
$return = undef() ;
$file = $_[0] ;
$cwd = cwd ;

# loop - is file a symbolic link
while ( $symlink = readlink($file) ) {
	# save the file name the symlink points to
	$file = $symlink ;

} ;	# end loop - is file a symbolic link

# get the directory name of the file
$dir = dirname($file) ;

# change to the file's directory
chdir($dir) || suicide("$gmodule: unable to chdir to '$dir'") ;

# save the true directory name
$dir = cwd ;

# change back to our working directory
chdir($cwd) || suicide("$gmodule: unable to chdir to '$cwd'") ;

# test - return appropriately to the caller
if ( $dir ) {
	# return the directory name
	return $dir ;

} else {
	# return badly to caller
	return $return ;

} ;	# end test - return appropriately to the caller

} ;	# end of getdirname()

################################################################################
################################################################################
# globber - generate a list of files in a directory
#
# Files are returned in a sorted order.  Files that match regex $_[2] are sorted
# and placed first in the list.  These files are followed by the sorted files
# that match regex $_[3], followed by the sorted files that match regex $_[4]
# and so on.  This allows the caller to control the order of the files returned
# by passing the regular expressions in a specific order.
sub globber {
# $_[0] - directory to look in
# $_[1] - file types to include in search
# $_[2 ... n] - RegExps to match filenames on

# declare variables, arrays & hashes
my($file) ;
my($dir) ;
my($regexp) ;
my(@regexps) ;
my($type) ;
my(@returns) ;
my(@regexreturns) ;
my(@files) ;

# define variables, arrays & hashes
$dir = shift(@_) ;
$type = shift(@_) ;
@regexps = @_ ;

# test - can we open the given directory
if ( -d $dir && opendir(DIR, $dir) ) {
	# directory exists & we can open it, so far, so good ...
	# get a list of files in the directory (these are relative paths)
	@files = readdir(DIR) ;

	# close the directory
	closedir(DIR) ;

} else {
	# directory either doesn't exist or we can't open it ...
	# return badly to caller
	return @returns ;

} ;	# end test - can we open the given directory

# loop - iterate through each RegExp
FILE_REGEXP: foreach $regexp ( @regexps ) {
	@regexreturns = ();
	# loop - iterate through file in the directory listing
	DIR_FILE: foreach $file ( @files ) {
	# test - check for requested file types
		if ( $type =~ /f/ && -f "$dir/$file" ) {
			# file requested & this is a file ... ok
			;

		} elsif ( $type =~ /d/ && -d "$dir/$file" ) {
			# dir requested & this is a dir ... ok
			;

		} else {
			# not a requested file type ... skip
			next("DIR_FILE") ;

		} ;	# end test - check for requested file types

		# test - check if filename matches the RegExp
		if ( $file =~ /$regexp/ ) {
			# filename matches ...
			# push the absolute filename onto the @returns array
			push(@regexreturns, "$dir/$file") ;

			# no need for further checking with this file so go to
			# the next record
			next("DIR_FILE") ;

		} ;	# test - check if filename matches the RegExp

	} ;	# end loop - iterate through file in the directory listing
	push(@returns, sort(@regexreturns)) ;

} ;	# end loop - iterate through each RegExp

return(@returns) ;

} ;	# end of globber()

################################################################################
################################################################################
# get_file_time - get timestamp of first message line in $file
sub get_file_time {
# $_[0] => filename from which to get time stamp

# declare variables, arrays & hashes
my($gmodule) ;
my($return) ;
my($file) ;
my($line) ;
my($myfh) ;

# define variables, arrays & hashes
$gmodule = "get_file_time" ;
$file = $_[0] ;
$return = 1 ;
$filetime{$file} = 0 ;

$myfh = file_open("<$file") ;
if (!$myfh) {
	return $return ;
} ;

MESSAGES_LINE: while ( $line = <$myfh> ) {
		chomp($line) ;
		$line =~ s/\r// ;
		if ( $line =~ /^($re{'date'})\s+/ ) {
			$filetime{$file} = convert_to_nls($1) ;
			echo("$file: $line") ;
			echo("time: $filetime{$file} " .
				localtime($filetime{$file})) ;
			last("MESSAGES_LINE") ;
			$return = 0 ;
		} ;
} ;

file_close($myfh) ;
return $return ;

} ;

################################################################################
################################################################################
# grepper - do report output header
sub grepper {
# $_[0] => file to grep in
# $_[1] => RegExp to grep for

# declare variables, arrays & hashes
my($gmodule) ;
my($return) ;
my($line) ;
my($file) ;
my($regexp) ;

# declare variables, arrays & hashes
$gmodule = "grepper" ;
$return = undef() ;
$file = $_[0] ;
$regexp = $_[1] ;

# loop - iterate through each line in the given file
GREP_LINE: foreach $line ( read_lines($file) ) {

	# ignore undefined lines
	if ( ! defined($line) ) { next("GREP_LINE") ; } ;

	# initialise the return value
	$return = "" ;

	# test - branch on match of RegExp
	if ( $line =~ /$regexp/ ) {
		# got a match ...
		# test - determine if a parentheses match was made
		if ( defined($1) ) {
			# return parentheses matched to caller immediately,
			# there is no need to process further
			return $1 ;

		} else {
			# return good to caller immediately, there is no need
			# to process further
			return 1 ;

		} ;	# end test - determine if a parentheses match was made

	} ;	# end test - branch on match of RegExp

} ;	# end loop - iterate through each line in the given file

# return to the caller ... we should only get to here if we find no matches
return $return ;

} ;	# end of grepper()

################################################################################
################################################################################
# header - do report output header
sub header {
# declare variables, arrays & hashes
my($gmodule) ;

# declare variables, arrays & hashes
$gmodule = "header" ;

# output the header
msg("$g_revision") ;
if ( ! $verbose ) { msg("Analysed System: $ostype $osver with KUP $kjp_inuse (MPR $mpr_state)") ; } ;
msgv("$prog directory: $prog_dir") ;
if ( $use_expl ) { msgv("Explorer directory: $edir") ; } ;
msgv("UltraSPARC Version: $ultrasparc ($ultrasparc_full)") ;
msgv("OS Type: $ostype") ;
msgv("OS Version: $osver") ;
msgv("Hostname: $hostname") ;
msgv("Platform: $platform") if (defined($platform));
if ($use_live || $use_history) {
msgv("Prescreening: " . truefalse(!$no_prescreen)) ;
msgv("Checkpointing: " . truefalse($allow_checkpointing)) ;
msgv("Initial Checkpoint: $initial_checkpoint (" .
     localtime($initial_checkpoint) .")") ;
} ;
msgv("Memory size: $ram (8KB pages)") ;
if ( $mpr_enabled && $ram_prl ) { msgv("MPR (deduced) PRL pages: $ram_prl (8KB pages)") ; } ;
msgv("MPR-capable OS: " . truefalse($mpr_capable_os)) ;
msgv("KJP: $kjp_inuse") ;
msgv("MPR-aware kernel in-use: " . truefalse($mpr_kernel)) ;
msgv("MPR enabled: " . truefalse($mpr_enabled)) ;
msgv("MPR disabled in /etc/system: " . truefalse($mpr_disabled_etc_system)) ;
msgv("MPR force mode: " . truefalse($mpr_force)) ;
msgv("MPR state: $mpr_state") ;
msgv("Rule#3 check: " . truefalse($use_rule3)) ;
msgv("Rule#4 check: " . truefalse($use_rule4)) ;
msgv("Rule#4b check: " . truefalse($use_rule4b)) ;
msgv("Rule#5 check: " . truefalse($use_rule5)) ;
msgv("Rule#5 supplemental check: " . truefalse($use_rule5b)) ;
msgv("Rule#5 check via cestat: " . truefalse($use_cestat)) ;
msgv("Rule#6 check: " . truefalse($use_rule6)) ;
if ( $use_cestat ) { msg("Pages Retired: $cestat{'pages_retired'} ($cestat{'pages_retired_percent'})") ; } ;

# leave a blank line
msgv("") ;

# reset variables
$i = 666 ;	# ;-)

# check ce_verbose_memory variable status ...
# test - branch on run mode
if ( ( $use_live || $use_history ) && $flag{'cestat_ok'} ) {
	# live or history mode & cestat output available ...
	if ( $ce_verbose_memory == 0 ) { $i = 3 ; } ;
	if ( $ce_verbose_memory == -1 ) { $i = 4 ; } ;

} elsif ( ( $use_live || $use_history ) && ! $flag{'cestat_ok'} ) {
	# live or history mode & cestat output *not* available ...
	if ( $ce_verbose_memory == 0 ) { $i = 5 ; } ;
	if ( $ce_verbose_memory == -1 ) { $i = 6 ; } ;

} elsif ( $use_expl ) {
	# Explorer mode ...
	if ( $ce_verbose_memory == 0 ) { $i = 0 ; } ;
	if ( $ce_verbose_memory == -1 ) { $i = -1 ; } ;

} elsif ( $use_man ) {
	# manual (aka standalone) mode ...
	$i = -2 ;

} ;	# end test - branch on run mode

# test - determine a message to print
if ( $i == -2 ) {
	# can't determine ce_verbose_memory in manual mode ...
	msg("NOTE: Manually ensure 'ce_verbose_memory' is not set to '0'") ;

} elsif ( $i == -1 ) {
	# /etc/system unreadable ...
	msgw("Unable to read file:-", "    " . $file{'system'}, "Ensure 'ce_verbose_memory' is not set to '0'") ;

} elsif ( $i == 0 ) {
	# ce_verbose_memory is set to 0 ...
	msgw("Kernel variable 'ce_verbose_memory' is '0' in /etc/system!") ;

} elsif ( $i == 3 ) {
	# ce_verbose_memory is set to 0 but cestat output is OK ...
	msgw("Kernel variable 'ce_verbose_memory' is '0' in /etc/system!", "cestat(1M) output is available", "Rule#5 results will be accurate", "Rule#3, Rule#4 & Rule#6 results *will not* be accurate") ;

} elsif ( $i == 4 ) {
	# ce_verbose_memory is unknown but cestat output is OK ...
	msgw("Unable to read file:-", "    " . $file{'system'}, "Ensure 'ce_verbose_memory' is not set to '0'") ;
	msgw("cestat(1M) output is available", "Rule#5 results will be accurate", "Rule#3, Rule#4 & Rule#6 results *may* not be accurate") ;

} elsif ( $i == 5 ) {
	# ce_verbose_memory is set to 0 & cestat output is not OK ...
	msgw("Kernel variable 'ce_verbose_memory' is '0' in /etc/system!", "cestat(1M) output is *not* available", "Rule#3, Rule#4, Rule#5 & Rule#6 results *will not* be accurate") ;

} elsif ( $i == 6 ) {
	# ce_verbose_memory is unknown but cestat output is *not* OK ...
	msgw("Unable to read file:-", "    " . $file{'system'}, "Ensure 'ce_verbose_memory' is not set to '0'") ;
	msgw("cestat(1M) output is *not* available", "Rule#3, Rule#4, Rule#5 & Rule#6 results *may* not be accurate") ;

} ;	# end test - determine a message to print

} ;	# end of header()

################################################################################
################################################################################
# kjp_detect - detect KJP installed
sub kjp_detect {
# declare variables, arrays & hashes
my($gmodule) ;
my($k) ;
my($p) ;

# define variables, arrays & hashes
$gmodule = "kjp_detect" ;
$k = "n/a" ;

# loop - go through each KJP for the installed OS
foreach $p ( @{$kjp{$osver}} ) {
	# see if the patch is installed
	if ( $patch{$p} ) { $k = "$p-$patch{$p}" ; } ;

} ;	# end loop - go through each KJP for the installed OS

# return to the caller
return $k ;

} ;	# end kjp_detect()

################################################################################
################################################################################
# kjp_validate - validate the KJP passed is a valid KJP for this OS version
sub kjp_validate {
# $_[0] - KJP to validate

# declare variables, arrays & hashes
my($gmodule) ;
my($kjp2check) ;
my($k) ;
my($p) ;
my($return) ;

# define variables, arrays & hashes
$gmodule = "kjp_validate" ;
$return = 0 ;
$kjp2check = $_[0] ;
$k = $kjp2check ;

# test - branch on array
if ( ! $kjp{$osver} ) {
	# no relevant KJP for this OS version ...
	# return true immediately to caller
	return "n/a" ;

} ;	# end test - branch on array

# test - branch on $kjp2check
if ( $kjp2check eq "n/a" ) {
	# no KJP installed ...
	# return true immediately to caller
	return "n/a" ;

} ;	# end test - branch on $kjp2check

# test - branch on kjp2check
if ( $kjp2check =~ /^\s*$/ ) { return undef() ; } ;

# remove revision info from the KJP
$k =~ s/\-\d{2}$// ;

# loop - go through each KJP for the installed OS
foreach $p ( @{$kjp{$osver}} ) {
	# see of the given patch matches a known valid KJP
	if ( $p eq $k ) { $return = $kjp2check ; } ;

} ;	# end loop - go through each KJP for the installed OS

# return to the caller
return $return ;

} ;	# end kjp_validate()

################################################################################
################################################################################
# load_patches - populate %patch hash
sub load_patches {
# declare variables, arrays & hashes
my($gmodule) ;
my(@patches) ;
my($p) ;
my($r) ;
my($line) ;
my(@a) ;

# define variables, arrays & hashes
$gmodule = "load_patches" ;

# test - determine the source of our installed patch list
if ( $use_expl ) {
	# use patch list collected by Explorer ...
	# test - can we open the 'showrev -p' file
	if ( -e $efile{'showrev_p'} && -r $efile{'showrev_p'} ) {
		# load the @patches array
		@patches = read_lines($efile{'showrev_p'}) ;

	} else {
		# exit gracefully
		suicide("$gmodule: unable to open '$efile{'showrev_p'}'") ;

	} ;	# end test - can we open the 'showrev -p' file

} elsif ( $use_live || $use_history ) {
	# collect patch list from the live system
	@patches = split(/\s*\n/, `$exe{'showrev'} -p`) ;

} elsif ( $use_man ) {
	# use the passed KJP ... fake up a 'showrev -p' output line
	# load the @patches array
	@patches = ("Patch: $kjp_inuse ") ;

} else {
	# eeek! how did we get to here?
	# die gracefully
	suicide("$gmodule: unexpected test branch encountered") ;

} ;	# end test - determine the source of our installed patch list

# loop - process each installed patch
PATCH_LINE: foreach $line ( @patches ) {
	# test - check for undef() values
	if ( ! defined($line) ) {
		# undef() value is an indication the file_open() had issues
		# opening the requested file, in this case 'showrev -p' ...
		# exit gracefully
		suicide("$gmodule: undef() value found in %patch hash") ;

	} ;	# end test - check for undef() values

	# ignore iffy lines
	if ( $line !~ /^Patch:\s+\d{6}\-\d{2}\s+/ ) { next("PATCH_LINE") ; } ;

	# get the patch number & revision
	@a = split(/\s+/, $line) ;
	( $p, $r ) = split(/\-/, $a[1]) ;

	# populate the %patch hash ...
	# test - branch on existing $r for $p
	if ( ! $patch{$p} ) {
		# patch does not exist
		$patch{$p} = $r ;

	} elsif ( $patch{$p} && $patch{$p} < $r ) {
		# patch exists but at a lower revision
		$patch{$p} = $r ;

	} ;	# test - branch on existing $r for $p

} ;	# end loop - process each installed patch

} ;	# end of load_patches()

################################################################################
################################################################################
# logger - log message to syslogd(1m) via logger(1) command
sub logger {
# @_ = message to log

# declare my variables, arrays & hashes
my($gmodule) ;
my($priority) ;
my($tmpfile) ;
my($now) ;
my($fh) ;
my($line) ;

# define variables, arrays & hashes
$gmodule = "logger" ;
$priority = "daemon.notice" ;
$now = time() ;
$tmpfile = "/tmp/$prog.$gmodule.$now.$$" ;

# open the tmp file
$fh = file_open(">$tmpfile") ;

# loop - write each passed line to the tmp file
foreach $line ( @_ ) {
	# write the line to the tmp file
	print $fh "$line\n" ;

} ;	# end loop - write each passed line to the tmp file

# close the tmp file
file_close($fh) ;

# use logger(1) to log the tmp file
`$exe{'logger'} -p $priority -t $prog -f $tmpfile` ;

# remove the tmp file
unlink($tmpfile) ;

} ;	# end of logger()

################################################################################
################################################################################
# memory_size - return the size of installed memory in 8KB pages
sub memory_size {

# declare variables, arrays & hashes
my($return) ;
my($mb) ;
my($pages) ;
my($gmodule) ;
my(@lines) ;

# define variables, arrays & hashes
$gmodule = "memory_size" ;

# test - branch on running against an Explorer
if ( $use_expl ) {
	# running against Explorer output ...
	$mb = grepper($efile{'prtconf_v'}, '^Memory\s+size:\s+(\d+)\s+Megabytes\s*$') ;

} elsif ( $use_live || $use_history ) {
	# running against a live system ...
	$mb = grepper("$exe{'prtconf'} |", '^Memory\s+size:\s+(\d+)\s+Megabytes\s*$') ;

} elsif ( $use_man ) {
	# can not determine memory size
	$mb = 0 ;

} else {
	# eeek! how did we get to here?
	# die gracefully
	suicide("$gmodule: unexpected test branch encountered") ;

} ;	# test - branch on running against an Explorer

# test - do we have a MB value to work with
if ( $mb && $mb =~ /^\d+$/ ) {
	# we have a valid memory size to play with
	$pages = int($mb * ( 1024**2 ) / 8196) ;

} else {
	# could not obtain the memory size ...
	# set the number of 8KB pages to be zero
	$pages = 0 ;

} ;	# end test - do we have a MB value to work with

# return the appropriate number of 8KB pages to the caller
return $pages ;

} ;	# end of memory_size()

################################################################################
################################################################################
# more - act like more(1)
sub more {
# @_ => list of strings to send to STDOUT

# declare variables, arrays & hashes
my($gmodule) ;
my($return) ;
my($string) ;
my($lines) ;
my($count) ;
my($junk) ;

# define variables, arrays & hashes
$gmodule = "more" ;
$return = 1 ;
$count = 0 ;

# loop - process each string
foreach $string ( @_ ) {

	# loop - process each line in the string
	foreach $line ( split(/\n/, $string) ) {
		# increment the count of lines
		$count++ ;

		# print the line to STDOUT
		print STDOUT "$line\n" ;

		# if we have hit a page count, pause for the user to strike a
		# key ...
		# test - branch on line count
		if ( $count >= 22 ) {
			# reset the count of lines
			$count = 0 ;

			# print the pagination message
			print STDOUT "--- press <Enter> for more, 'q' to quit --- " ;

			# wait for a entry from STDIN
			$junk = <STDIN> ;
			chomp($junk) ;

			# exit gracefully if 'q' entered
			if ( $junk eq 'q' ) { suicide("user quit during licence viewing") ; } ;

		} ;	# end test - branch on line count

	} ;	# end loop - process each line in the string

} ;	# end loop - process each string

} ;	# end of more()

################################################################################
################################################################################
# mpr_kernel_inuse - return true if a [m]emory [p]age [r]retirement kernel is
#			in use
sub mpr_kernel_inuse {

# declare variables, arrays & hashes
my($gmodule) ;
my($return) ;
my($kjpn) ;
my($kjpr) ;
my($line) ;
my($patch) ;
my(@patches) ;

# define variables, arrays & hashes
$gmodule = "mpr_kernel_inuse" ;
$return = 0 ; 	# default to a false return
$mpr_capable_os = 0 ;

# fault injection - UltraSPARC type
#$ultrasparc = "3" ;
#$ultrasparc_full = "3i" ;
# end fault injection - UltraSPARC type

# test - assign patches to loop through
if ( ${ultrasparc_full} && $mpr_kjp{"${osver}_${ultrasparc_full}"} ) {
	# assign patch list
	echo("$gmodule: UltraSPARC-specific KJP list") ;
	@patches = @{$mpr_kjp{"${osver}_${ultrasparc_full}"}} ;

	# assign minimum KJP required
	$mpr_kjp_minimum = ${$mpr_kjp{"${osver}_${ultrasparc_full}"}}[0] ;

} elsif ( $mpr_kjp{$osver} ) {
	# assign patch list
	echo("$gmodule: generic-Solaris KJP list") ;
	@patches = @{$mpr_kjp{$osver}} ;

	# assign minimum KJP required
	$mpr_kjp_minimum = ${$mpr_kjp{${osver}}}[0] ;

} else {
	# unsupported OS
	echo("$gmodule: unsupported OS") ;

	# return badly to caller immediately
	return 0 ;

} ;	# end test - assign patches to loop through

# loop - process each MPR-aware KJP for this OS version
foreach $patch ( @patches ) {
	# set the $mpr_capable_os flag to be true
	$mpr_capable_os = 1 ;

	# split the patch out into component variables
	( $kjpn, $kjpr ) = split(/\-/, $patch) ;

	# test - branch patch number value
	# ... S >= 10 will have only "1" in the array of patches
	if ( $kjpn == 1 || ( $patch{$kjpn} && $patch{$kjpn} >= $kjpr ) ) {
		# we have a KJP that is MPR-enabled ...
		# set return value to be good
		$return = 1 ;

	} ;	# test - branch patch number value

} ;	# end loop - process each MPR-aware KJP for this OS version

# fault injection - pre-MPR KUP
#$return = 0 ;
# end fault injection - pre-MPR KUP

# return appropriately to the caller
return $return ;

} ;	# end of mpr_kernel_inuse()

################################################################################
################################################################################
# msg - send status messages to STDOUT (if a terminal) & syslogd if flag set
sub msg {
# @_ - messages to log

# declare variables, arrays & hashes
my($line) ;
my(@loglines) ;

# loop - iterate through each message
foreach $line ( @_ ) {
	# test - do we have something to print
	if ( $line !~ /^\s*$/ ) {
		# print the message to STDOUT
		print STDOUT "$prog: $line\n" ;

		# put the line on the @loglines array
		push(@loglines, $line) ;

	} else {
		# just print a blank line to STDOUT
		print STDOUT "\n" ;

	} ;	# end test - do we have something to print

} ;	# end loop - iterate through each message

# test - use syslog flag set
if ( $use_syslog ) {
	# call logger() to log the message to syslog
	logger(@loglines) ;

} ;	# end test - use syslog flag set

} ;	# end of msg()

################################################################################
################################################################################
# msgv - if in verbose mode, send status messages to STDOUT
sub msgv {
# @_ - messages to log

# declare variables, array & hashes
my($return) ;
my($line) ;
my(@messages) ;
my(@lines) ;

# define variables, array & hashes
$return = 1 ;
@lines = @_ ;

# test - are we in verbose mode
if ( $verbose ) {
	# send all messages to msg() to print ...
	# loop - process each passed message
	foreach $line ( @lines ) {
		# test - prepend info: string to non-blank messages
		if ( $line !~ /^\s*$/ ) {
			# prepend info: string
			$line = "info: $line" ;

		} ;	# end test - prepend info: string to non-blank messages

		# put the line on the @messages array
		push(@messages, $line) ;

	} ;	# end loop - process each passed message

	# call msg() function
	msg(@messages) ;

} else {
	# don't do anything
	;

} ;	# end  test - are we in verbose mode

# return to caller
return $return ;

} ;	# end of msgv()

################################################################################
################################################################################
# msgw - send WARNING messages to STDOUT
sub msgw {
# @_ - messages to log

# declare variables, array & hashes
my($return) ;
my($line) ;
my(@messages) ;
my(@lines) ;

# define variables, array & hashes
$return = 1 ;
@lines = @_ ;

# loop - process each passed message
foreach $line ( @lines ) {
	# test - prepend WARNING: string to non-blank messages
	if ( $line !~ /^\s*$/ ) {
		# prepend WARNING: string
		$line = "WARNING: $line" ;

	} ;	# end test - prepend WARNING: string to non-blank messages

	# put the line on the @messages array
	push(@messages, $line) ;

} ;	# end loop - process each passed message

# call msg() function
msg(@messages) ;

# return to caller
return $return ;

} ;	# end of msgw()

################################################################################
################################################################################
# normalise_dimm - convert DIMM names to uniform format
sub normalise_dimm {
# $_[0] = DIMM name to normalise

# declare variables, arrays & hashes
my($gmodule) ;
my($caller) ;
my($dimm) ;
my($return) ;

# define variables, arrays & hashes
$gmodule = (caller(0))[3] ;
$gmodule =~ s/^.*::// ;
$caller = (caller(1))[3] ;
if ( $caller ) { $caller =~ s/^.*::// ; } ;
$dimm = $_[0] ;
$return = undef() ;

# test - branch on DIMM format
if ( ! defined($dimm) ) {
	# we were passed an undefined value ... ick!
	echo("$gmodule: passed an undefined DIMM string") ;

	# set a DIMM value to make sure we don't mess up other hashes, etc.
	$return = "DIMM *unknown*" ;

} elsif ( $dimm =~ m#^(C\d+/P\d+)/B(\d+)/D\d+:\s+B\d+/D\d+$# && ($ultrasparc_full eq "3i" || $ultrasparc_full eq "3i+") ) {
	# C0/P0/B1/D1: B1/D1 ... US3i from CE event
	$return = "$1 Bank#$2" ;

} elsif ( $dimm =~ m#^(C\d+/P\d+)/B(\d+):\s+B\d+/D\d+\s+B\d+/D\d+$# && ($ultrasparc_full eq "3i"|| $ultrasparc_full eq "3i+") ) {
	# C0/P0/B1: B1/D0 B1/D1 ... US3i from RCE event
	$return = "$1 Bank#$2" ;

} elsif ( $dimm =~ m#^(MB/P\d+)/B(\d+)/D\d+:\s+B\d+/D\d+$# && ($ultrasparc_full eq "3i" || $ultrasparc_full eq "3i+") ) {
	#  MB/P1/B0/D0: B0/D0 ... US3i from CE event (V240)
	$return = "$1 Bank#$2" ;

} elsif ( $dimm =~ m#^(MB/P\d+)/B(\d+):\s+B\d+/D\d+\s+B\d+/D\d+$# && ($ultrasparc_full eq "3i" || $ultrasparc_full eq "3i+") ) {
	#  MB/P1/B0: B0/D0 B0/D1 ... US3i from RCE event (V240)
	$return = "$1 Bank#$2" ;

} else {
	# no need to convert just return exactly what was passed
	$return = $dimm ;

} ;	# end test - branch on DIMM format

# return the normalised DIMM name to the caller
return $return ;

} ;	# end of normalise_dimm()

################################################################################
################################################################################
# parse_cestat - parse cestat output
sub parse_cestat {
# $_[0] = n/a

# declare variables, arrays & hashes
my($gmodule) ;
my($i) ;

# define variables, arrays & hashes
$gmodule = "parse_cestat" ;

# test - branch on $cestat_file value
if ( $cestat_file && -r $cestat_file ) {
	# run cestat & save the output
	echo("using 'cestat -v' output file $cestat_file") ;
	$i = `$exe{'cat'} $cestat_file 2>/dev/null` ;

} else {
	# run cestat & save the output
	echo("executing '$exe{'cestat'}' to generate output") ;
	$i = `$exe{'cestat'} -v 2>/dev/null` ;

} ;	# test - branch on $cestat_file value


# set the $use_cestat variable to be dependent on the return value of
# the cestat command ... because this is an external command zero is a
# good return, > zero is bad
# test - child exit code
if ( $? == 0 ) {	# if ( $CHILD_ERROR == 0 ) {
	# set flags
	$use_cestat = 1 ;
	$flag{'cestat_ok'} = 1 ;

	# initialise variables ...
	# set 'suspect_dimm_count' variable as if no CEs have been seen
	# then the parsing code below won't find the initialisation line
	$cestat{'suspect_dimm_count'} = 0 ;
	# if MPR is disabled then the parsing code will not be able to set this
	# variable
	$cestat{'prl_reached'} = 0 ;

} ;	# end test - child exit code

# loop - process each line of cestat output
CESTAT: foreach $line ( split(/\n/, $i) ) {
	# strip off trailing <LF> if necessary
	chomp($line) ;

	# test - blank line
	if ( $line =~ /^\s*$/ ) {
		# reset flags
		$flag{'in_dimms'} = 0 ;

		# skip blank lines
		next("CESTAT") ;

	} ;	# end test - blank line

	# test - check for "Page retirement is currently disabled." line
#^Page retirement is currently disabled.
	if ( $line =~ /^\s*Page\s+retirement\s+is\s+currently\s+disabled\./ ) {
		# set %cestat{'mpr_disabled'} variable
		$cestat{'mpr_disabled'} = 1 ;

		# stop processing cestat output
		last("CESTAT") ;

	} ;	# end test - check for "Page retirement is currently disabled." line

	# test - check for "DIMM Location/Name" line
#^DIMM Location/Name          Int     Per     Stk       Total      P+S  Suspect?
	if ( $line =~ m#^DIMM\s+Location/Name\s+# ) {
		# set flag
		$flag{'in_dimms'} = 1 ;

		# skip to next line
		next("CESTAT") ;

	} ;	# end test - check for "DIMM Location/Name" line

	# test - check for "DIMM info" line
#^1903                         10      18       0          28         18  no
#^/N0/SB1/P0/B0/D1 J13400      84     350       0         434        350  YES
#^Board# 8 Bank# 1 P# P18 MM 1_4    0   0       0           0          0  no
#^Slot B: J3000                 0  -31940       0      -31940     -31940  no

	if ( $flag{'in_dimms'} && $line =~ /\s+\-?\d+\s+(yes|no)\s*$/i ) {

		# parse the line ...
		# split the line into an array
		@a = split(/\s+/, $line) ;

		# process individual fields ...
		# NB - we populate a tmp hash here as we can't determine
		# NB	the DIMM number until much later in the piece

		# clear out tmp hash to avoid re-using old data
		undef(%h) ;

		# suspect flag
		$h{'suspect'} = pop(@a) ;
		# ... test - convert to 0/1 value
		if ( $h{'suspect'} =~ /yes/i ) {
			$h{'suspect'} = 1 ;
		} elsif ( $h{'suspect'} =~ /no/i ) {
			$h{'suspect'} = 0 ;
		} ; # ... end test - convert to 0/1 value

		# p_s count
		$h{'p_s'} = pop(@a) ;

		# total count - throw away the data
		$junk = pop(@a) ;

		# sticky count
		$h{'sticky'} = pop(@a) ;

		# persistent count
		$h{'persistent'} = pop(@a) ;

		# intermittent count
		$h{'intermittent'} = pop(@a) ;

		# the array elements remaining are the DIMM name so
		# join() them back together into a string
		$j = join(" ", @a) ;

		# normalise the DIMM name
		$j = normalise_dimm($j) ;

		# populate the %cestat hash for the DIMM now we know it's name
		%{$cestat{'dimm'}{$j}} = %h ;

		# fix up wrap-arounds from S8 KUP using 'shorts' to store CE
		# counts ... wrap-arounds go negative
		# test - P+S count a negative number
		if ( $cestat{'dimm'}{$j}{'p_s'} < 0 ) {
			# turn the number positive & somewhere near correct
			$cestat{'dimm'}{$j}{'p_s'} += 65536 ;

			# force suspect flag to be true
			$cestat{'dimm'}{$j}{'suspect'} = 1 ;

			# show status
			echo("$gmodule: DIMM '$j' overflow ... fixing 'P+S' & 'suspect'") ;

		} ;	# end test - P+S count a negative number

		# test - Persistent count a negative number
		if ( $cestat{'dimm'}{$j}{'persistent'} < 0 ) {
			# turn the number positive & somewhere near correct
			$cestat{'dimm'}{$j}{'persistent'} += 65536 ;

			# force suspect flag to be true
			$cestat{'dimm'}{$j}{'suspect'} = 1 ;

			# show status
			echo("$gmodule: DIMM '$j' overflow ... fixing 'Persistent' & 'suspect'") ;

		} ;	# end test - Persistent count a negative number

		# test - Sticky count a negative number
		if ( $cestat{'dimm'}{$j}{'sticky'} < 0 ) {
			# turn the number positive & somewhere near correct
			$cestat{'dimm'}{$j}{'sticky'} += 65536 ;

			# force suspect flag to be true
			$cestat{'dimm'}{$j}{'suspect'} = 1 ;

			# show status
			echo("$gmodule: DIMM '$j' overflow ... fixing 'Sticky' & 'suspect'") ;

		} ;	# end test - Sticky count a negative number

		# test - Intermittent count a negative number
		if ( $cestat{'dimm'}{$j}{'intermittent'} < 0 ) {
			# turn the number positive & somewhere near correct
			$cestat{'dimm'}{$j}{'intermittent'} += 65536 ;

			# show status
			echo("$gmodule: DIMM '$j' overflow ... fixing 'intermittent'") ;

		} ;	# end test - Intermittent count a negative number

		# skip to next line
		next("CESTAT") ;

	} ;	# end test - check for "DIMM info" line

	# test - check for "Retirement Limit" line
	#^Retirement limit              126         0.10%
	if ( $line =~ /^Retirement\s+limit\s+(\d+)\s+/ ) {
		# set %cestat{'retirement_limit'} variable
		$cestat{'retirement_limit'} = $1 ;

		# skip to next line
		next("CESTAT") ;

	} ;	# end test - check for "Retirement Limit" line

	# test - check for "Pages currently retired" line
	#^Pages currently retired         0         0.00%
	if ( $line =~ /^Pages\s+currently\s+retired\s+(\d+)\s+(\d+\.\d{2}\%)/ ) {
		# set %cestat{'pages_retired'} variable
		$cestat{'pages_retired'} = $1 ;
		# set %cestat{'pages_retired_percent'} variable
		$cestat{'pages_retired_percent'} = $2 ;

		# skip to next line
		next("CESTAT") ;

	} ;	# end test - check for "Pages currently retired" line

	# test - check for "Page retirement limit has" line
	#^Page retirement limit has not been reached.
	#^Page retirement limit has been reached.
	if ( $line =~ /^Page\s+retirement\s+limit\s+has\s+(\S+)\s+/ ) {
		# test - set %cestat{'prl_reached'} variable
		if ( $1 eq "not" ) {
			# PRL not reached
			$cestat{'prl_reached'} = 0 ;

		} else {
			# PRL reached
			$cestat{'prl_reached'} = 1 ;

		} ;	# end test - set %cestat{'prl_reached'} variable

		# skip to next line
		next("CESTAT") ;

	} ;	# end # test - check for "Page retirement limit has" line

	# test - check for "Total number of Suspect DIMMs" line
	#^Total number of Suspect DIMMs: 0
	if ( $line =~ /^Total\s+number\s+of\s+Suspect\s+DIMMs:\s+(\d+)\b/ ) {
		# set %cestat{'suspect_dimm_count'} variable
		$cestat{'suspect_dimm_count'} = $1 ;

		# skip to next line
		next("CESTAT") ;

	} ;	# end test - check for "Total number of Suspect DIMMs" line

} ;	# end loop - process each line of cestat output

### fault injection code - start
#$cestat{'prl_reached'} = 1 ;
#$cestat{'dimm'}{'J0123'}{'p_s'} = 130 ;
#$cestat{'dimm'}{'J0124'}{'p_s'} = 120 ;
### fault injection code - end

# debug - show cestat data
#foreach $i ( qw(retirement_limit pages_retired pages_retired_percent prl_reached suspect_dimm_count) ) {
#	echo("\$cestat{$i} = '$cestat{$i}'") ;
#} ;
#foreach $i ( sort(keys(%{$cestat{'dimm'}}))) {
#	echo("DIMM = '$i'") ;
#	foreach $j ( sort(keys(%{$cestat{'dimm'}{$i}}))) {
#		echo("\$cestat{'dimm'}{$i}{$j} = '$cestat{'dimm'}{$i}{$j}'") ;
#	} ;
#} ;
# end debug - show cestat data

# return to caller
return 1 ;

} ;	# end parse_cestat() ;

################################################################################
################################################################################
# pr_store - return the lines in the given file as an array
sub pr_store {
# $_[0] = "stage" number to store record for

# declare variables, array & hashes
my($gmodule) ;
my($stage) ;
my($pages_retired) ;

# define variables, array & hashes
$gmodule = "pr_store" ;
$stage = $_[0] ;

# debug
echo("", $pr{'date'}, "$gmodule: stage#$stage processing requested") ;

# trim up any page addresses by removing leading 0x0's to save space
if ( $pr{'page1'} ) { $pr{'page1'} =~ s/^$re{'leading_hex'}// ; } ;
if ( $pr{'page2'} ) { $pr{'page2'} =~ s/^$re{'leading_hex'}// ; } ;

# test - reset rule#5 replacement data if a stage#1 or stage#2 msg
if ( $stage == 1 || $stage == 2 ) {
	# if we have a stage#2 "successful retirement" message encountered we
	# could not possible have reached PRL, therefore:-
	# ... clear out the list of DIMMs we have schedule for replacement
	# ... under rule#5
	rule5_replace_reset() ;
	# ... set the PRL reached flag & PRL reached date to be false
	prl_reset() ;

} ;	# end test - reset rule#5 replacement data if a stage#1 or stage#2 msg

# test - branch on stage called
if ( $stage == 1 && $pr{'dimm'} && $pr{'date0'} && $pr{'page1'} && $pr{'date1'} ) {
	# stage 1 with valid data ...
	echo("$gmodule: stage#$stage with valid data") ;
	echo("$gmodule: DIMM# $pr{'dimm'}") ;
	echo("$gmodule: page# $pr{'page1'} scheduled for removal") ;
print $tracefh "$filename:$messages_file_linenumber:$last_message_time_nls:5129 - pr_store1\n" if ($tracefh);

	# If this is the first time this page has been scheduled for retirement,
	# increment the overall %mpr hash
	if (! exists($prlist{$pr{'page1'}})) {
		# Increment count for first scheduled removal of this page
		$mpr{'sched_rm_count'}++ ;
	} ;

	# update the %prlist hash to show that this page has at some time been
	# scheduled for removal
	$prlist{$pr{'page1'}}++ ;

	# determine the count of pages retired
	$pages_retired = scalar(keys(%prlist)) ;

# NOTE: code retired because it has been found that both stage#1 & stage#2
# NOTE: messages signify that PRL has not been reached
# !!! Retired Code - start !!!
#!!!	# check PRL reached ... choose either of 2 possible ways to deduce this
#!!!	# test - branch on PRL detection method
#!!!	if ( $prl_dm == 1 ) {
#!!!		# PRL detection method #1 ... count number of retired pages
#!!!		#	against %0.1 of physical memory
#!!!
#!!!		# show PRL detection method variable
#!!!		echo("$gmodule: using PRL detection method #$prl_dm") ;
#!!!
#!!!		# show status
#!!!		echo("$gmodule: PRL#$prl_dm -> Pages retired = $pages_retired / Pages required for PRL = $ram_prl") ;
#!!!
#!!!		# test - have we retired PRL # of pages yet?
#!!!		if ( $pages_retired >= $ram_prl ) {
#!!!			# PRL has been reached
#!!!			echo("$gmodule: PRL#$prl_dm -> PRL has been reached!") ;
#!!!
#!!!			# set PRL reached status to true
#!!!			prl_reached($pr{'date1'}) ;
#!!!
#!!!		} else {
#!!!			# PRL has been not been reached
#!!!			echo("$gmodule: PRL#$prl_dm -> PRL has not been reached") ;
#!!!
#!!!		} ;	# end test - have we retired PRL # of pages yet?
#!!!
#!!!	} else {
#!!!		# PRL detection method #2 ... count number of "page sceduled
#!!!		#	for retirement" messages without a page actually being
#!!!		#	retired
#!!!
#!!!		# show PRL detection method variable
#!!!		echo("$gmodule: using PRL detection method #$prl_dm") ;
#!!!
#!!!		# increment the Un-Retired Page Count variables
#!!!		echo("$gmodule: PRL#$prl_dm -> incrementing PRL 'urpc' variable") ;
#!!!		$mpr{'prl'}{'urpc'}++ ;
#!!!		echo("$gmodule: PRL#$prl_dm -> PRL 'urpc' variable value = $mpr{'prl'}{'urpc'}") ;
#!!!
#!!!		# test - do we have a URPC of more than 5
#!!!		if ( $mpr{'prl'}{'urpc'} >= 5 ) {
#!!!			# set the PRL reached flag to be true, this will be
#!!!			# reset to false if we get *any* "stage#2"
#!!!			# "Page 0xnnn.nnn removed from service" messages
#!!!			prl_reached($pr{'date1'}) ;
#!!!
#!!!		} ; 	# end test - do we have a URPC of more than 5
#!!!
#!!!	}	; # end test - branch on PRL detection method
#!!!
# !!! Retired Code - end !!!

	# update %mpr hash
	$mpr{'date_latest_sched_rm'} = convert_to_nls($pr{'date'}) ;

	# update %page hash
	$page{$pr{'page1'}}{'dimm'} = $pr{'dimm'} ;
	$page{$pr{'page1'}}{'sched_rm_count'}++ ;
	$page{$pr{'page1'}}{'date_latest_sched_rm'} = convert_to_nls($pr{'date'}) ;

	# initialise uninitialised variables
	if ( ! defined($page{$pr{'page1'}}{'rm_count'}) ) {
		$page{$pr{'page1'}}{'rm_count'} = 0 ;
	} ;

} elsif ( $stage == 2 && $pr{'page2'} && $pr{'date2'} && $prlist{$pr{'page2'}} ) {
	# stage 2 with valid data ...
	echo("$gmodule: stage#$stage with valid data") ;
	echo("$gmodule: page# $pr{'page2'} removed from service") ;
print $tracefh "$filename:$messages_file_linenumber:$last_message_time_nls:5217 - pr_store2\n" if ($tracefh);

	# show this page has been removed from service ...
	# ... zero it's entry in the %prlist hash
	$prlist{$pr{'page2'}} = 0 ;

	# update %mpr hash if this is the first time this page has been retired.
	# some (probably erroneous) syslog configurations may result in
	# duplicate messages, thus making it appear that a page has been
	# retired multiple times.
	if (! defined($page{$pr{'page2'}}{'rm_count'}) ||
		$page{$pr{'page2'}}{'rm_count'} < 1) {

		# First completed retire for this page
		# update %mpr hash with PRL data ...
		# update %mpr hash
		$mpr{'rm_count'}++ ;
	} ;
	$mpr{'date_latest_rm'} = convert_to_nls($pr{'date'}) ;

	# update %page hash
	$page{$pr{'page2'}}{'rm_count'}++ ;
	$page{$pr{'page2'}}{'date_latest_rm'} = convert_to_nls($pr{'date'}) ;

	# initialise uninitialised variables
	if ( ! defined($page{$pr{'page2'}}{'sched_rm_count'}) ) {
		$page{$pr{'page2'}}{'sched_rm_count'} = 0 ;
	} ;

} elsif ( $stage == 2 && $pr{'page2'} && $pr{'date2'} && ! $prlist{$pr{'page2'}} ) {
	# stage#2 with valid data except that the page being removed wasn't
	# scheduled to be removed
	# we have a correct stage number passed but insufficient data
	echo("$gmodule: WARNING: file: $filename at line: $messages_file_linenumber ...", "$gmodule: ... page# $pr{'page2'} removed without being scheduled ... ignored") ;
	#msg("$gmodule: WARNING: file: $filename at line: $messages_file_linenumber ...", "$gmodule: ... page# $pr{'page2'} removed without being scheduled ... ignored") ;

} elsif ( $stage == 1 || $stage == 2 ) {
	# we have a correct stage number passed but insufficient data
	echo("$gmodule: WARNING: file: $filename at line: $messages_file_linenumber ...", "$gmodule: ... insufficient data in PR's %pr to store stage#$stage record", "$gmodule: ... possibly mangled messages") ;
	#msg("$gmodule: WARNING: file: $filename at line: $messages_file_linenumber ...", "$gmodule: ... insufficient data in PR's %pr to store stage#$stage record", "$gmodule: ... possibly mangled messages") ;

} else {
	# eeek! how did we get to here?
	# die gracefully
	suicide("$gmodule: unexpected test branch encountered") ;

} ;	# end test - branch on stage called

} ;	# end of pr_store()

################################################################################
################################################################################
# process_frc_rce - match US3i FRC/RCE records & make CEs
sub process_frc_rce {
# $_[0] = n/a

# declare variables, array & hashes
my($caller) ;
my($gmodule) ;
my($er) ;
my($ef) ;
my($frc) ;
my($j_aid) ;
my($j_req) ;
my($dimm) ;
my($afar) ;
my($esynd) ;
my($bit) ;
my($cpu_rce) ;
my($cpu_frc) ;
my($return) ;
my($tr) ;
my($tf) ;
my($delta) ;
my(%count) ;

# define variables, array & hashes
$gmodule = (caller(0))[3] ;
$gmodule =~ s/^.*::// ;
$caller = (caller(1))[3] ;
if ( $caller ) { $caller =~ s/^.*::// ; } else { $caller = "MAIN_BODY()" ; } ;
$return = 0 ;

# debug
echo("$gmodule: starting") ;

# loop - iterate through RCEs
RCE_RECORD: foreach $er ( sort(keys(%rce)) ) {
	# log RCE being processed
	echo("$gmodule: processing RCE ErrID: $er") ;

	# set RCE variables
	$cpu_rce = $rce{$er}{'cpu'} ;
	$afar = $rce{$er}{'afar'} ;
	$j_req = $rce{$er}{'j_req'} ;
	$dimm = $rce{$er}{'dimm'} ;
	$tr = $rce{$er}{'nls'} ;

	# loop - iterate through FRCs
	FRC_RECORD: foreach $ef ( sort(keys(%frc)) ) {
		# log FRC being processed
		echo("$gmodule: processing FRC ErrID: $ef") ;

		# set FRC variables
		$cpu_frc = $frc{$ef}{'cpu'} ;
		$esynd = $frc{$ef}{'esynd'} ;
		$j_aid = $frc{$ef}{'j_aid'} ;
		$bit = $frc{$ef}{'bit'} ;
		$tf = $frc{$ef}{'nls'} ;
		$delta = abs($tr - $tf) ;

		# check for a matching RCE/FRC ...
		# test - times are within 10 secs
		if ( $delta <= 10 ) {
			# times are close enough
			# see http://dram.eng/Fiesta_FA/UltraSPARC-IIIi_CPU_Error_Handling_and_Messages.pdf for more info
			echo("$gmodule: time delta = $delta secs") ;

		} else {
			# times are not close enough
			echo("$gmodule: time delta > 10 secs ... ineligible") ;

			# proceed to next FRC
			next("FRC_RECORD") ;

		} ;	# end test - times are within 10 secs

		# test - CPU_RCE/J_AID & CPU_FRC/J_REQ match
		if ( $cpu_rce eq $j_aid && $cpu_frc eq $j_req ) {
			# match OK on J_AID & J_REQ CPU#s
			echo("$gmodule: CPU_RCE=$cpu_rce/J_AID=$j_aid & CPU_FRC=$cpu_frc/J_REQ=$j_req match") ;

		} else {
			# no match on J_AID & J_REQ CPU#s
			echo("$gmodule: CPU_RCE=$cpu_rce/J_AID=$j_aid & CPU_FRC=$cpu_frc/J_REQ=$j_req do not match ... ineligible") ;

			# proceed to next FRC
			next("FRC_RECORD") ;

		} ;	# end test - CPU_RCE/J_AID & CPU_FRC/J_REQ match

		# if we get to here then we appear to have a valid CE ...
		# increment this RCE's FRC match count
		$count{'rce'}{$er}++ ;
		# increment this FRC's RCE match count
		$count{'frc'}{$ef}++ ;

		# show status
		echo("$gmodule: RCE/FRC match -> RCE: $er matches FRC: $ef ($count{'rce'}{$er}/$count{'frc'}{$ef})") ;

		# debug
		#echo("\$tr = '$tr'") ;
		#echo("\$er = '$er'") ;
		#echo("\$dimm = '$dimm'") ;
		#echo("\$bit = '$bit'") ;
		#echo("\$esynd = '$esynd'") ;
		#echo("\$afar = '$afar'") ;

		# generate a CE record & store it ...
		undef(%ce) ;
		$ce{'nls'} = $tr ;
		$ce{'errid'} = $er ;
		$ce{'dimm'} = $dimm ;
		$ce{'bit'} = $bit ;
		$ce{'esynd'} = $esynd ;
		$ce{'afar'} = $afar ;
		$ce{'frequency'} = "RCE/FRC" ;

		# test - CE store succesful
		if ( ce_store() ) {
			# CE stored OK
			echo("$gmodule: CE store successful") ;
			$return = 1 ;

		} else {
			# CE stored FAILED!
			echo("$gmodule: CE store FAILED!") ;

		} ;	# end test - CE store succesful

		# update the CE count & dates for this DIMM
		ce_increment($ce{'dimm'}, $ce{'nls'}, $ce{'frequency'}) ;

		# remove this FRC from the hash to prevent matching more than
		# once
		echo("$gmodule: removing match FRC hash record $ef") ;
		delete($frc{$ef}) ;

		# remove this RCE from the hash to indicate a match
		echo("$gmodule: removing match RCE hash record $er") ;
		delete($rce{$er}) ;

		# proceed to the next RCE record
		next("RCE_RECORD") ;

	} ; 	# end loop - iterate through FRCs

} ; 	# end loop - iterate through RCEs

# only unmatched RCEs should now exist in the %rce hash
# loop - process each unmatched RCE
foreach $er ( sort(keys(%rce)) ) {
	# log unmatched RCE
	echo("$gmodule: RCE ErrID# $er was unmatched with a FRC record") ;

} ;	# loop - process each unmatched RCE

# only unmatched FRCs should now exist in the %frc hash
# loop - process each unmatched FRC
foreach $ef ( sort(keys(%frc)) ) {
	# log unmatched FRC
	echo("$gmodule: FRC ErrID# $ef was unmatched with a RCE record") ;

} ;	# loop - process each unmatched FRC

# debug
echo("$gmodule: finished") ;

return $return ;

} ;	# end of process_frc_rce()

################################################################################
################################################################################
# prl_reached - set "PRL reached" counters in a standard way
sub prl_reached {
# $_[0] = date PRL reached

# declare variables, array & hashes
my($caller) ;
my($gmodule) ;
my($date) ;

# define variables, array & hashes
$gmodule = (caller(0))[3] ;
$gmodule =~ s/^.*::// ;
$caller = (caller(1))[3] ;
if ( $caller ) { $caller =~ s/^.*::// ; } else { $caller = "" ; } ;
$date = $_[0] ;

# reset the PRL counters
echo("$gmodule:$caller: setting PRL 'reached' variables to 'true'") ;
$mpr{'prl'}{'reached'} = 1 ;
$mpr{'prl'}{'date_reached'} = convert_to_nls($date) ;

} ;	# end of prl_reached()

################################################################################
################################################################################
# prl_reset - initialise the PRL counters in a standard way
sub prl_reset {
# declare variables, array & hashes
my($caller) ;
my($gmodule) ;

# define variables, array & hashes
$gmodule = (caller(0))[3] ;
$gmodule =~ s/^.*::// ;
$caller = (caller(1))[3] ;
$caller =~ s/^.*::// ;

# reset the PRL counters
echo("$gmodule:$caller: setting PRL 'reached' variables to 'false'") ;
$mpr{'prl'}{'urpc'} = 0 ;
$mpr{'prl'}{'reached'} = 0 ;
$mpr{'prl'}{'date_reached'} = 0 ;

} ;	# end of prl_reset()

################################################################################
################################################################################
# rce_store - store the RCE record data
sub rce_store {
# $_[0] - messages date ... only used for logging purposes

# declare variables, arrays & hashes
my($return) ;
my($gmodule) ;
my($date) ;

# define variables, arrays & hashes
$return = 0 ;
$gmodule = "rce_store" ;
$date = $_[0] ;

# show the date we are processing
echo("$gmodule: $date") ;

# test - branch of undef() fields
if (
	! defined($_rce{'errid0'})
	|| ! defined($_rce{'cpu'})
	|| ! defined($_rce{'afar'})
	|| ! defined($_rce{'j_req'})
	|| ! defined($_rce{'dimm'})
	|| ! defined($_rce{'nls'})
   ) {
	# insufficient data to store RCE record
	echo("", "$gmodule: WARNING: file: $filename at line: $messages_file_linenumber ...", "$gmodule: insufficient RCE data to store RCE record") ;

	#echo("errid0 = $_rce{'errid0'}") ;
	#echo("cpu = $_rce{'cpu'}") ;
	#echo("j_req = $_rce{'j_req'}") ;
	#echo("dimm = $_rce{'dimm'}") ;
	#echo("nls = $_rce{'nls'}") ;

	# return immediately to caller
	goto("RCE_STORE_FINISH") ;

} ;	# end test - branch of undef() fields

# good RCE data ...
echo("$gmodule: storing RCE record") ;

# store the record
$rce{$_rce{'errid0'}}{'cpu'} = $_rce{'cpu'} ;
$rce{$_rce{'errid0'}}{'dimm'} = $_rce{'dimm'} ;
$rce{$_rce{'errid0'}}{'nls'} = $_rce{'nls'} ;
$rce{$_rce{'errid0'}}{'j_req'} = $_rce{'j_req'} ;
$rce{$_rce{'errid0'}}{'afar'} = $_rce{'afar'} ;

# label for goto statement
RCE_STORE_FINISH:

# clear the %_rce hash to prevent re-use of old data
echo("$gmodule: resetting %_rce") ;
undef(%_rce) ;

# return to caller
return $return ;

} ;	# end of rce_store()

################################################################################
################################################################################
# read_lines - return the lines in the given file as an array
sub read_lines {
# $_[0] - filename to read

# declare variables, array & hashes
my($fh) ;
my($file) ;
my(@lines) ;
my($line) ;

# define variables, arrays & hashes
$file = $_[0] ;

# open the file passed to us
# test - was passed file a pipe
if ( $file !~ /\|\s*$/ ) {
	# file not a pipe ...
	$fh = file_open("<$file") ;

} else {
	# file is a pipe ...
	$fh = file_open($file) ;

} ;	 # end test - was passed file a pipe

# test we got a valid return value from the open attempt
if ( ! $fh ) { return undef() ; } ;

# loop - read the lines into an array
while ( $line = <$fh> ) {
	# strip off trailing <LF> characters
	chomp($line) ;

	# strip off trailing MS-DOSish <CR> characters
	$line =~ s/\r$// ;

	# push the line onto the array of lines to be returned
	push(@lines, $line) ;

} ;	# end loop - read the lines into an array

# close the file
file_close($fh) ;

# return the array of lines to the caller
return @lines ;

} ;	# end of read_lines()

################################################################################
################################################################################
# rec_reset - *** deprecated *** call dimm_data_reset() instead
sub rec_reset {

# call dimm_data_reset() & return it's return code
return dimm_data_reset($_[0]) ;

} ;	# end of rec_reset()

################################################################################
################################################################################
# rule4 - check for DIMM Replacement Policy Rules 4a & 4b
sub rule4 {
# $_[0] - is the DIMM to check against rule#4

# declare variables, arrays & hashes
my($return) ;
my($dimm) ;
my($gmodule) ;

# define variables, arrays & hashes
$gmodule = "rule4" ;
$return = 1 ;	# default to a good return
$dimm = $_[0] ;

rule4a($dimm) ;
rule4b($dimm) ;

# return the appropriate array to the caller
return $return ;

} ;	# end of rule4()

################################################################################
################################################################################
# rule4a - check for DIMM Replacement Policy Rule# 4a
sub rule4a {
# $_[0] - is the DIMM to check against rule#4

# declare variables, arrays & hashes
my($return) ;
my($dimm) ;
my($bit) ;
my(@bits) ;
my($m64) ;
my(@m64s) ;
my($afar) ;
my($afar_count) ;
my(@afars) ;
my($bits_with_errors) ;
my($gmodule) ;
my($ces_seen) ;
my($minimum_required_unique_afars_per_m64) ;
my($element) ;
my($element_count) ;
my($key_count) ;
my($ce_date_range) ;
my(%error) ;	# $error{$m64} = ( bit_0, bit_1, bit_2, ... )
my($latest_date) ;
my($latest_bit) ;
my($first_date) ;
my($first_bit) ;

# define variables, arrays & hashes
$gmodule = "rule4a" ;
$return = 1 ;	# default to a good return
$dimm = $_[0] ;
$ces_seen = $counter{'ddr'}{'dimm'}{$dimm}{'ce'} ;
	if ( ! $ces_seen ) { $ces_seen = 0 ; } ; # force to zero if no CEs seen
$minimum_required_unique_afars_per_m64 = 2 ;
@bits = sort(keys(%{$counter{'ddr'}{'dimm'}{$dimm}{'bit'}})) ;
$bits_with_errors = scalar(@bits) ;

# show the DIMM we are processing
echo("$gmodule: checking DIMM# $dimm") ;

# test - check the number of data bits effected is >= 2
if ( $bits_with_errors < 2 ) {
	# not enough bits in error
	echo("$gmodule: $bits_with_errors bit(s) with errors logged is insufficient.") ;

	# go immediately to the finish
	goto("RULE4A_FINISH") ;

} else {
	# there are enough bits in error
	echo("$gmodule: $bits_with_errors bit(s) with errors logged is sufficient.") ;

} ;	# end test - check the number of data bits effected is >= 2

# loop - check each bit position
BIT_LOOP: foreach $bit ( @bits ) {
	# show bit being processed
	echo("$gmodule:   checking bit='$bit'") ;

	# determine the modulo64s for this bit
	@m64s = sort(keys(%{$counter{'ddr'}{'dimm'}{$dimm}{'bit'}{$bit}{'m64'}})) ;

	# loop - check each bit/m64
	M64_LOOP: foreach $m64 ( @m64s ) {
		# show m64 being processed
		echo("$gmodule:     checking bit='$bit'/m64='$m64'") ;

		# get the AFARs in this modulo64
		@afars = keys(%{$counter{'ddr'}{'dimm'}{$dimm}{'bit'}{$bit}{'m64'}{$m64}{'afar'}}) ;

		# get the number of AFARs in this modulo64
		$afar_count = scalar(@afars) ;

		# ... force to zero if no AFARs found
		if ( ! $afar_count ) { $afar_count = 0 ; } ;

		# test - at least 2 AFARs in this modulo64
		# ... uses $minimum_required_unique_afars_per_m64 variable
		if ( $afar_count >= $minimum_required_unique_afars_per_m64 ) {
			# there are enough erroring AFARs
			echo("$gmodule:     $afar_count AFAR(s) for Bit='$bit'||AFAR%64='$m64' is sufficient.") ;

			# add this errored bit/m64 to the list
			push(@{$error{$m64}}, $bit);

		} else {
			# there are *not* enough erroring AFARs
			echo("$gmodule:     $afar_count AFAR(s) for Bit='$bit'||AFAR%64='$m64' is insufficient.") ;

		} ;	# end test - at least 2 AFARs in this modulo64

	} ;	# end loop - check each bit/m64

} ;	# end loop - check each bit position

# loop - process the %error hash
foreach $m64 ( sort(keys(%error)) ) {
	# obtain the element count for this m64's array & normalise
	$element_count = scalar(@{$error{$m64}}) ;
	if ( ! $element_count ) { $element_count = 0 ; } ;

	# test - branch on number of elements in the array
	if ( $element_count > 1 ) {
		# there is more than 1 bit in error on this m64 ...
		echo("$gmodule: $element_count bits in error in AFAR%64='$m64' is sufficient.") ;

		# no further action required
		;

	} else {
		# there is *not* more than 1 bit in error on this m64 ...
		echo("$gmodule: $element_count bits in error in AFAR%64='$m64' is insufficient.") ;

		# delete this m64's hash entry from the %error hash
		delete($error{$m64}) ;

	} ;	# end test - branch on number of elements in the array

} ;	# end loop - process the %error hash

# check we have at least 2 m64's left ...
$key_count = scalar(keys(%error)) ;
# test - branch on number of keys in %error hash
if ( $key_count > 0 ) {
	# we have enough m64's left to proceed ...
	echo("$gmodule: $key_count eligible AFAR%64s remaining is sufficient.") ;

} else {
	# we *do not* have enough m64's left to proceed ...
	echo("$gmodule: $key_count eligible AFAR%64s remaining is insufficient.") ;

	# go immediately to the finish
	goto("RULE4A_FINISH") ;

} ;	 # end test - branch on number of keys in %error hash

#
# if we manage to get to this point all that is left to do is to check the CEs
# fall within a 24 hour period ...
#

# loop - process each m64 in sufficient error
foreach $m64 ( sort(keys(%error)) ) {

	# set per loop variables
	undef($latest_date) ;
	undef($latest_bit) ;
	undef($first_date) ;
	undef($first_bit) ;

	# get the list of bits we need to process
	@bits = @{$error{$m64}} ;

	# loop - get the latest CE date & bit for this DIMM
	foreach $bit ( sort(@bits) ) {
		# test - branch on "latest" date
		if ( ! $latest_date || $cedb{$dimm}{'bit'}{$bit}{'m64'}{$m64}{'date_latest'} > $latest_date ) {
			# later date ... update variables
			$latest_date = $cedb{$dimm}{'bit'}{$bit}{'m64'}{$m64}{'date_latest'} ;
			$latest_bit = $bit ;

		} ;	# end test - branch on "latest" date

	} ;	# end loop - get the latest CE date & bit for this DIMM

	# loop - get the first CE date & bit for this DIMM
	foreach $bit ( sort(@bits) ) {
		if ( $bit eq $latest_bit ) { next ; } ;

		# test - branch on "first" date
		if ( ! $first_date || $cedb{$dimm}{'bit'}{$bit}{'m64'}{$m64}{'date_previous'} > $first_date ) {
			# earlier date ... update variables
			$first_date = $cedb{$dimm}{'bit'}{$bit}{'m64'}{$m64}{'date_previous'} ;
			$first_bit = $bit ;

		} ;	# end test - branch on "first" date

	} ;	# end loop - get the first CE date & bit for this DIMM


	# show status
	echo("$gmodule: DIMM='$dimm' AFAR%64='$m64' first bit='$first_bit' @ " . localtime($first_date)) ;
	echo("$gmodule: DIMM='$dimm' AFAR%64='$m64' latest bit='$latest_bit' @ " . localtime($latest_date)) ;

	# calculate the time difference between the 'first' & 'latest' dates
	$ce_date_range = $latest_date - $first_date ;

	# debug
	echo("$gmodule: 'first' to 'latest' CE date range -> $ce_date_range seconds") ;
	echo("$gmodule: 'first' to 'latest' CE date range -> " . convert_to_dhms($ce_date_range)) ;

	# test - is date range within a 24 hour period
	if ( $ce_date_range <= ( 60 * 60 * 24 ) ) {
		# date range is within 24 hours ...
		# rule4a is matched
		echo("$gmodule: CEs fall within 24 window") ;
		echo("$gmodule: DIMM# $dimm matches rule4a") ;
		echo("$gmodule: rule4a match in '$filename' at line# $messages_file_linenumber") ;
print $tracefh "$filename:$messages_file_linenumber:$last_message_time_nls:5836 - 4A match\n" if ($tracefh);

		# update the %replace hash
		# ... totals
		$replace{'dimm'}{$dimm}{'replace_count'}++ ;
		# do not record dates ... memory saving decision
		#push(@{$replace{'dimm'}{$dimm}{'replace_dates'}}, $latest_date) ;
		# ... dimm/rule4a
		$replace{'dimm'}{$dimm}{'rule4a_match_count'}++ ;
		# do not record dates ... memory saving decision
		#push(@{$replace{'dimm'}{$dimm}{'rule4a_match_dates'}}, $latest_date) ;
		# ... rule4a
		$replace{'rule4a'}{$dimm}++ ;

		# reset counters for this DIMM to enable a fresh start
		dimm_data_reset($dimm) ;

		# go immediately to the finish
		goto("RULE4A_FINISH") ;

	} else {
		# date range is *not* within 24 hours ...
		# rule4a is *not* matched
		echo("$gmodule: CEs do not fall within 24 hour window") ;
		echo("$gmodule: DIMM# $dimm does not match rule4a") ;

	} ;	# end test - is date range within a 24 hour period

} ;	# end loop - process each m64 in sufficient error

# label for goto
RULE4A_FINISH:

# return the appropriate array to the caller
return $return ;

} ;	# end of rule4a()


################################################################################
################################################################################
# rule4b - check for DIMM Replacement Policy Rule# 4b
sub rule4b {
# $_[0] - is the DIMM to check against rule#4b

if ( ! $use_rule4b ) {
	return 0;
} ;

# declare variables, arrays & hashes
my($return) ;
my($dimm) ;
my($dram) ;
my(@drams) ;
my(@dram_outputs) ;
my($dram_output) ;
my($output_count) ;
my($bit) ;
my($bit_count) ;
my(@bits) ;
my($m64) ;
my(@m64s) ;
my($afar) ;
my($afar_count) ;
my(@afars) ;
my($gmodule) ;
my($minimum_afars_per_dram_out) ;
my($key_count) ;
my($ce_date_range) ;
my(%error) ;	# $error{$m64} = ( bit_0, bit_1, bit_2, ... )
my($latest_date) ;
my($latest_bit) ;
my($first_date) ;
my($first_bit) ;

# define variables, arrays & hashes
$gmodule = "rule4b" ;
$return = 1 ;	# default to a good return
$dimm = $_[0] ;
$minimum_afars_per_dram_out = 2 ;
@drams = sort(keys(%{$counter{'ddr'}{'dimm'}{$dimm}{'dram'}})) ;

# show the DIMM we are processing
echo("$gmodule: checking DIMM# $dimm") ;

# loop - go through each dram
DRAM_LOOP: foreach $dram ( @drams ) {
	# show dram being processed
	echo("$gmodule:   checking dram='$dram'") ;

	# determine the bits on this dram
	@bits = sort(keys(%{$counter{'ddr'}{'dimm'}{$dimm}{'dram'}{$dram}{'bit'}})) ;
	# determine the number of unique bits within the relative checkwords
	$bit_count = scalar(@bits) ;

	if ( $bit_count < 2 ) {
		echo("$gmodule:   $bit_count eligible bits on dram '$dram' is insufficient.") ;
		next("DRAM_LOOP") ;
	} ;

	# When we get here, we've satisfied the phrase: "...as long as the three
	# outputs do not all correspond to the same relative bit position in
	# their respective checkwords." for this DRAM.

	# loop - go through each of the bits
	BIT_LOOP: foreach $bit ( @bits ) {
		# show bit being processed
		echo("$gmodule:     checking bit='$bit'") ;

		@m64s = sort(keys(%{$counter{'ddr'}{'dimm'}{$dimm}{'dram'}{$dram}{'bit'}{$bit}{'m64'}})) ;

		# loop - go through each checkword
		M64_LOOP: foreach $m64 ( @m64s ) {
			echo("$gmodule:       checking m64='$m64'") ;

			# get the AFARs for this bit
			@afars = keys(%{$counter{'ddr'}{'dimm'}{$dimm}{'dram'}{$dram}{'bit'}{$bit}{'m64'}{$m64}{'afar'}}) ;

			# get the number of AFARs for this bit
			$afar_count = scalar(@afars) ;
			# ... force to zero if no AFARs found
			if ( ! $afar_count ) { $afar_count = 0 ; } ;

			# test - minimum required AFARs for this bit
			# ... uses $minimum_afars_per_dram_out variable
			if ( $afar_count >= $minimum_afars_per_dram_out ){
				# there are enough erroring AFARs
				echo("$gmodule:       $afar_count AFAR(s) for Bit='$bit'||m64='$m64' is sufficient.") ;

				# We've now found a DRAM output (identified by
				# a unique checkword/bit combination) that has
				# had the minimum number of required CEs with
				# the minimum requisite unique AFARs. Therefore,
				# add this dram output to the list
				push(@{$error{$dram}}, $m64.".".$bit);

			} else {
				# there are *not* enough erroring AFARs
				echo("$gmodule:       $afar_count AFAR(s) for Bit='$bit'||m64='$m64' is insufficient.") ;

			} ;	# end test - minimum required AFARs for this bit

		} ;	# end loop - go through each checkword

	} ;	# end loop - go through each of the bits

} ;	# end loop - go through each dram

#
# if we manage to get to this point all that is left to do is to check that
# the minimum DRAM outputs have had CEs and that those CEs fall within a 24
# hour period ...
#

# loop - process each dram in sufficient error
DRAM2_LOOP: foreach $dram ( sort(keys(%error)) ) {

	# set per loop variables
	undef($latest_date) ;
	undef($latest_bit) ;
	undef($first_date) ;
	undef($first_bit) ;
	$latest_date = 0 ;
	$first_date = 0 ;

	echo("$gmodule: processing DRAM '$dram'") ;
	# get the list of outputs we need to process
	@dram_outputs = @{$error{$dram}} ;
	$output_count = scalar(@dram_outputs) ;

	if ( $output_count < 3 ) {
		echo("$gmodule: DRAM '$dram' output count '$output_count' is insufficient.") ;
		next("DRAM2_LOOP") ;
	} else {
		echo("$gmodule: DRAM '$dram' output count '$output_count' is sufficient.") ;
	} ;

	# loop - get the latest CE date & bit for this DIMM
	foreach $dram_output ( sort(@dram_outputs) ) {
		$dram_output =~ m/(.*)\.(.*)/ ;
		$m64 = $1 ;
		$bit = $2 ;
		# test - branch on "latest" date
		if ( ! $latest_date || $cedb{$dimm}{'bit'}{$bit}{'m64'}{$m64}{'date_latest'} > $latest_date ) {
			# later date ... update variables
			$latest_date = $cedb{$dimm}{'bit'}{$bit}{'m64'}{$m64}{'date_latest'};
			$latest_bit = $bit ;

		} ;	# end test - branch on "latest" date

	} ;	# end loop - get the latest CE date & bit for this DIMM

	# loop - get the first CE date & bit for this DIMM
	foreach $dram_output ( sort(@dram_outputs) ) {
		$dram_output =~ m/(.*)\.(.*)/ ;
		$m64 = $1 ;
		$bit = $2 ;

		# test - branch on "first" date
		if ( ! $first_date || $cedb{$dimm}{'bit'}{$bit}{'m64'}{$m64}{'date_previous'} > $first_date ) {

			# earlier date ... update variables
			$first_date = $cedb{$dimm}{'bit'}{$bit}{'m64'}{$m64}{'date_previous'} ;
			$first_bit = $bit ;

		} ;	# end test - branch on "first" date

	} ;	# end loop - get the first CE date & bit for this DIMM

	# show status
	echo("$gmodule: DIMM='$dimm' first bit='$first_bit' @ " . localtime($first_date)) ;
	echo("$gmodule: DIMM='$dimm' latest bit='$latest_bit' @ " . localtime($latest_date)) ;

	# calculate the time difference between the 'first' & 'latest' dates
	$ce_date_range = $latest_date - $first_date ;

	# debug
	echo("$gmodule: 'first' to 'latest' CE date range -> $ce_date_range seconds") ;
	echo("$gmodule: 'first' to 'latest' CE date range -> " . convert_to_dhms($ce_date_range)) ;

	# test - is date range within a 24 hour period
	if ( $ce_date_range <= ( 60 * 60 * 24 ) ) {
		# date range is within 24 hours ...
		# rule4b is matched
		echo("$gmodule: CEs fall within 24 hour window") ;
		echo("$gmodule: DIMM# $dimm matches rule4b") ;
		echo("$gmodule: rule4b match in '$filename' at line# $messages_file_linenumber") ;
print $tracefh "$filename:$messages_file_linenumber:$last_message_time_nls:6063 - 4B match\n" if ($tracefh);

		# update the %replace hash
		# ... totals
		$replace{'dimm'}{$dimm}{'replace_count'}++ ;
		# do not record dates ... memory saving decision
		#push(@{$replace{'dimm'}{$dimm}{'replace_dates'}}, $latest_date) ;
		# ... dimm/rule4b
		$replace{'dimm'}{$dimm}{'rule4b_match_count'}++ ;
		# do not record dates ... memory saving decision
		#push(@{$replace{'dimm'}{$dimm}{'rule4b_match_dates'}}, $latest_date) ;
		# ... rule4b
		$replace{'rule4b'}{$dimm}++ ;

		# reset counters for this DIMM to enable a fresh start
		dimm_data_reset($dimm) ;

		# go immediately to the finish
		goto("RULE4B_FINISH") ;

	} else {
		# date range is *not* within 24 hours ...
		# rule4b is *not* matched
		echo("$gmodule: CEs do not fall within 24 hour window") ;
		echo("$gmodule: DIMM# $dimm does not match rule4b") ;

	} ;	# end test - is date range within a 24 hour period

} ;	# end loop - process each dram in sufficient error

# label for goto
RULE4B_FINISH:

# return the appropriate array to the caller
return $return ;

} ;	# end of rule4b()

################################################################################
################################################################################
# rule5 - check for DIMM Replacement Policy Rule# 5
sub rule5 {
# $_[0] - is the date that a CE was encountered that forced this rule5 check

# declare variables, arrays & hashes
my($return) ;
my($dimm) ;
my($gmodule) ;
my($ces_seen) ;
my($date) ;

# define variables, arrays & hashes
$gmodule = "rule5" ;
$return = 1 ;	# default to a good return
$date = $_[0] ;

# set variables
if ( ! defined($mpr{'prl'}{'reached'}) ) { $mpr{'prl'}{'reached'} = 0 ; } ;
if ( ! defined($mpr{'prl'}{'urpc'}) ) { $mpr{'prl'}{'urpc'} = 0 ; } ;

# test - is PRL reached
if ( $mpr{'prl'}{'reached'} ) {
	# PRL potentially reached ...
	echo("$gmodule: PRL potentially reached (urpc count=$mpr{'prl'}{'urpc'})") ;

} else {
	# PRL *not* reached ...
	echo("$gmodule: PRL *not* reached (urpc count=$mpr{'prl'}{'urpc'})") ;

	# go immediately to the finish
	goto("RULE5_FINISH") ;

} ;	# end test - is PRL reached

# if we get to here then PRL has been reached ...
# loop - through each DIMM in the %cedb hash
foreach $dimm ( sort(keys(%cedb)) ) {

	# show the DIMM we are processing
	echo("$gmodule: checking DIMM# $dimm") ;

	# set variables
	# ... use SLR (Since Last Reboot) CE count for Rule#5
	#$ces_seen = $counter{'ddr'}{'dimm'}{$dimm}{'ni_ce'} ;
	$ces_seen = $counter{'slr'}{'dimm'}{$dimm}{'ni_ce'} ;
	if ( ! $ces_seen ) { $ces_seen = 0 ; } ; # force to zero if no CEs seen

	# test - has the CE count been reached on this DIMM
	if ( $ces_seen >= $rule5_ce_count ) {
		# CE count sufficient: rule5 is matched ...
		echo("$gmodule: DIMM# $dimm has sufficient CEs ($ces_seen)") ;
		echo("$gmodule: DIMM# $dimm matches rule5") ;
print $tracefh "$filename:$messages_file_linenumber:$last_message_time_nls:6155 - 5 match\n" if ($tracefh);

		# update the %replace hash
		# ... totals
		$replace{'dimm'}{$dimm}{'replace_count'}++ ;
		# do not record dates ... memory saving decision
		#push(@{$replace{'dimm'}{$dimm}{'replace_dates'}}, $date) ;
		# ... dimm/rule5
		$replace{'dimm'}{$dimm}{'rule5_match_count'}++ ;
		# do not record dates ... memory saving decision
		#push(@{$replace{'dimm'}{$dimm}{'rule5_match_dates'}}, $date) ;
		# ... rule5
		$replace{'rule5'}{$dimm}++ ;

		# reset counters for this DIMM to enable a fresh start
		dimm_data_reset($dimm) ;

	} else {
		# CE count insufficient: rule5 is *not* matched ...
		echo("$gmodule: DIMM# $dimm has insufficient CEs ($ces_seen)") ;
		echo("$gmodule: DIMM# $dimm does not match rule5") ;

	} ;	# test - has the CE count been reached on this DIMM

} ;	# end loop - through each DIMM in the %cedb hash

# label for goto
RULE5_FINISH:

# return the appropriate array to the caller
return $return ;

} ;	# end of rule5()

################################################################################
################################################################################
# rule5_cestat - check for DIMM Replacement Policy Rule# 5 with cesstat output
sub rule5_cestat {
# declare varaiables, array & hashes
my($gmodule) ;
my($dimm) ;
my($return) ;

# define varaiables, array & hashes
$gmodule = "rule5_cestat" ;
$return = 1 ;

# we shouldn't be here if the use_cestat flag is not set ...
# test - double check it is OK to use cestat output
if ( ! $use_cestat ) {
	# exit gracefully
	suicide("$gmodule: logic error encountered ... why are we here?") ;

} ;	# test - double check it is OK to use cestat output

# test - has PRL been reached
if ( ! $cestat{'prl_reached'} ) {
	# PRL has not been reached, return now as there is no need to update any
	# hashes
	return $return ;

} ;	# end test - has PRL been reached

# process the %cestat data & update the various hashes ...
# loop - process each DIMM
foreach $dimm ( sort(keys(%{$cestat{'dimm'}})) ) {
	# test - is this DIMM suspect
	if ( $cestat{'dimm'}{$dimm}{'p_s'} >= $rule5_ce_count ) {
		# update %replace hash
		$replace{'rule5'}{$dimm}++ ;

		# due to the *potential* for wildly varying CE counts between
		# the messages files & cestat I have unilaterally decided to
		# *not* update the CE counts with the cestat data ...
		# ... hopefully this will prevent some support calls from the
		# ... pedantic amongst us who are looking for 1:1 exact counts
		# update %cedb with the CE counts from cestat
		#$counter{'ddr'}{'dimm'}{$dimm}{'ce'} = $cestat{'dimm'}{$dimm}{'p_s'} ;
		#$counter{'slr'}{'dimm'}{$dimm}{'ce'} = $cestat{'dimm'}{$dimm}{'p_s'} ;

	} ;	# end test - is this DIMM suspect

} ;	# end loop - process each DIMM

# return to the caller
return $return ;

} ;	# end of rule5_cestat()

################################################################################
################################################################################
# rule5_replace_reset - clear out the replacement data for Rule#5 matches
sub rule5_replace_reset {
# $_[0] - n/a

# declare variables, arrays & hashes
my($return) ;
my($gmodule) ;

# define variables, arrays & hashes
$gmodule = "rule5_replace_reset" ;
$return = 1 ;

# show action
echo("$gmodule: clearing %replace{'rule5'} hash of DIMMs") ;

# clear out the %replace{'rule5'} data
undef(%{$replace{'rule5'}}) ;

# return appropriately to the caller
return $return ;

} ;	# end of rule5_replace_reset()

################################################################################
################################################################################
# rule5b - check for DIMM Replacement Policy Rule# 5b
sub rule5b {
# $_[0] - is junk

# declare variables, arrays & hashes
my($return) ;
my($gmodule) ;
my($ces_seen) ;
my($date) ;
my($oldest_ce) ;
my($youngest_ce) ;
my($ce_date_range) ;

# define variables, arrays & hashes
$gmodule = "rule5b" ;
$return = 0 ;	# default to a bad return

echo("$gmodule: checking d=$ce{'dimm'} b=$ce{'bit'} a=$ce{'afar'}") ;
$ces_seen = $counter{'ddr'}{'dimm'}{$ce{'dimm'}}{'bit'}{$ce{'bit'}}{'afar'}{$ce{'afar'}}{'ni_ce'} ;
$ces_seen = 0 if (!defined($ces_seen)) ;

# test - is PRL reached
if ( $mpr{'prl'}{'reached'} ) {
	# PRL potentially reached ...
	echo("$gmodule: PRL potentially reached - returning") ;
	return $return ;
} ;

if ($ces_seen < $rule5b_ce_count) {
	# This DIMM/Bit/AFAR hasn't seen enough CEs to satisfy rule5b
	echo("$gmodule:   $ces_seen eligible CEs on $ce{'dimm'}/$ce{'bit'}/$ce{'afar'} is insufficient.") ;
	return $return ;
}

echo("$gmodule:   $ces_seen eligible CEs on $ce{'dimm'}/$ce{'bit'}/$ce{'afar'} is sufficient.") ;

$oldest_ce = ${$counter{'ddr'}{$ce{'dimm'}}{$ce{'bit'}}{$ce{'afar'}}}[0] ;
$youngest_ce = ${$counter{'ddr'}{$ce{'dimm'}}{$ce{'bit'}}{$ce{'afar'}}}[$ces_seen-1] ;
$ce_date_range = $youngest_ce - $oldest_ce ;

echo("$gmodule: oldest CE date ($oldest_ce) -> " . localtime($oldest_ce)) ;
echo("$gmodule: youngest CE date ($youngest_ce) -> " . localtime($youngest_ce));
echo("$gmodule: date range -> $ce_date_range seconds") ;
echo("$gmodule: date range -> " . convert_to_dhms($ce_date_range)) ;

if ( $ce_date_range <= ($rule5b_hour_count * 60 * 60) ) {
	echo("$gmodule:   CEs fall within $rule5b_hour_count hour window.") ;
} else {
	echo("$gmodule:   CEs do not fall within $rule5b_hour_count hour window.") ;
	return $return ;
} ;

if ($page{$ce{'page'}} && $page{$ce{'page'}}{'sched_rm_count'} &&
    $page{$ce{'page'}}{'sched_rm_count'} > 0) {
	if ($page{$ce{'page'}}{'rm_count'} >= 1) {
		# The page has been retired.  Do not replace DIMM for this rule
		# This should be a rare case.
		echo("$gmodule: DIMM# $ce{'dimm'} does not match rule5b") ;
		echo("$gmodule:  The page for this CE has been retired") ;
		return $return ;
	} else {
		echo("$gmodule: Page is scheduled for retire") ;
	} ;
}

echo("$gmodule: DIMM# $ce{'dimm'} matches rule5b") ;
print $tracefh "$filename:$messages_file_linenumber:$last_message_time_nls:6337 - 5B match\n" if ($tracefh);

$replace{'dimm'}{$ce{'dimm'}}{'replace_count'}++ ;
$replace{'dimm'}{$ce{'dimm'}}{'rule5b_match_count'}++ ;
$replace{'rule5b'}{$ce{'dimm'}}++ ;
$return = 1 ;

# Perform a variation of dimm_data_reset()
@{$counter{'ddr'}{$ce{'dimm'}}{$ce{'bit'}}{$ce{'afar'}}} = ();
$counter{'ddr'}{'dimm'}{$ce{'dimm'}}{'bit'}{$ce{'bit'}}{'afar'}{$ce{'afar'}}{'ni_ce'} = 0 ;

return $return ;

} ;

################################################################################
################################################################################
# rule6 - check for DIMM Replacement Policy Rule# 6
sub rule6 {
# $_[0] - is the DIMM to check against rule#6

# declare variables, arrays & hashes
my($return) ;
my($dimm) ;
my($gmodule) ;
my($ces_seen) ;
my($oldest_ce) ;
my($youngest_ce) ;
my($element) ;
my($ce_date_range) ;
my($aref) ;

# define variables, arrays & hashes
$gmodule = "rule6" ;
$return = 1 ;	# default to a good return
$dimm = $_[0] ;
$aref = \@{$cedb{$dimm}{'dates'}} ;	# define a reference for convenience
$ces_seen = scalar(@$aref) ;

# show the DIMM we are processing
echo("$gmodule: checking DIMM# $dimm") ;

# test - check the CEs seen count
if ( $ces_seen < $rule6_ce_count ) {
	# not enough CEs seen for this DIMM
	echo("$gmodule: $ces_seen non-intermittent CE(s) logged on DIMM $dimm is insufficient.") ;

	# go immediately to the finish
	goto("RULE6_FINISH") ;

} else {
	# there are enough CEs seen for this DIMM
	echo("$gmodule: $ces_seen CE(s) logged on DIMM $dimm is sufficient.") ;

} ;	# end test - check the CEs seen count

#
# if we manage to get to this point all that is left to do is to check the CEs
# fall within a 24 hour period ...
#

# define variables
$oldest_ce = $$aref[0] ;
$youngest_ce = $$aref[($ces_seen - 1)] ;
$ce_date_range = $youngest_ce - $oldest_ce ;

# debug
echo("$gmodule: oldest CE date -> " . localtime($oldest_ce)) ;
echo("$gmodule: youngest CE date -> " . localtime($youngest_ce)) ;
echo("$gmodule: oldest/youngest CE date range -> $ce_date_range seconds") ;
echo("$gmodule: oldest/youngest CE date range -> " . convert_to_dhms($ce_date_range)) ;

# test - is date range within a 24 hour period
if ( $ce_date_range <= ( 60 * 60 * $rule6_hour_count ) ) {
	# date range is within 24 hours ...
	# rule6 is matched
	echo("$gmodule: CEs fall within $rule6_hour_count hour window") ;
	echo("$gmodule: DIMM# $dimm matches rule6") ;
print $tracefh "$filename:$messages_file_linenumber:$last_message_time_nls:6415 - 6 match\n" if ($tracefh);

	# update the %replace hash
	# ... totals
	$replace{'dimm'}{$dimm}{'replace_count'}++ ;
	# do not record dates ... memory saving decision
	#push(@{$replace{'dimm'}{$dimm}{'replace_dates'}}, $youngest_ce) ;
	# ... dimm/rule6
	$replace{'dimm'}{$dimm}{'rule6_match_count'}++ ;
	# do not record dates ... memory saving decision
	#push(@{$replace{'dimm'}{$dimm}{'rule6_match_dates'}}, $youngest_ce) ;
	# ... rule6
	$replace{'rule6'}{$dimm}++ ;

	# reset counters for this DIMM to enable a fresh start
	dimm_data_reset($dimm) ;

} else {
	# date range is *not* within 24 hours ...
	# rule6 is *not* matched
	echo("$gmodule: CEs do not fall within $rule6_hour_count hour window") ;
	echo("$gmodule: DIMM# $dimm does not match rule6") ;

} ;	# test - is date range within a 24 hour period

# label for goto
RULE6_FINISH:

# return the appropriate array to the caller
return $return ;

} ;	# end of rule6()

################################################################################
################################################################################
# set_uniboard_bit2dram - Use uniboard bit to DRAM mappings
sub set_uniboard_bit2dram {

# declare variables, arrays & hashes
my($return) ;

# DRAM by bit.  Note that this is independent of check-word or DIMM side.
# Actual DRAM pin *is* dependent on both check-word and DIMM side.
# DRAMs are numbered D0 to D35, but that doesn't tell us what DIMM
# they're on!

# Corrected version of the table that came from bugid 6346507
# bit2dram was correct in the original, dram2dimm was not.

# Data bits are numbers, 0 - 127.
# ECC bits are strings, C0 - C8
# MTAG bits are strings, MT0 - MT2
# MTAG ECC bits are strings, MTC0 - MTC3

$bit2dram{'MTC0'} = "D0" ;
$bit2dram{'MTC1'} = "D1" ;
$bit2dram{'MTC2'} = "D2" ;
$bit2dram{'MTC3'} = "D3" ;

$bit2dram{'MT0'} = "D0" ;
$bit2dram{'MT1'} = "D1" ;
$bit2dram{'MT2'} = "D2" ;

$bit2dram{'C4'} = "D0" ;

$bit2dram{'0'} = "D3" ;
$bit2dram{'1'} = "D3" ;
$bit2dram{'2'} = "D3" ;
$bit2dram{'3'} = "D1" ;

$bit2dram{'4'} = "D2" ;
$bit2dram{'5'} = "D0" ;
$bit2dram{'6'} = "D1" ;
$bit2dram{'7'} = "D2" ;

$bit2dram{'8'} = "D4" ;
$bit2dram{'9'} = "D4" ;
$bit2dram{'10'} = "D4" ;
$bit2dram{'11'} = "D4" ;

$bit2dram{'12'} = "D5" ;
$bit2dram{'13'} = "D5" ;
$bit2dram{'14'} = "D5" ;
$bit2dram{'15'} = "D5" ;

$bit2dram{'16'} = "D6" ;
$bit2dram{'17'} = "D6" ;
$bit2dram{'18'} = "D6" ;
$bit2dram{'19'} = "D6" ;

$bit2dram{'20'} = "D7" ;
$bit2dram{'21'} = "D7" ;
$bit2dram{'22'} = "D7" ;
$bit2dram{'23'} = "D7" ;

$bit2dram{'24'} = "D8" ;
$bit2dram{'25'} = "D8" ;
$bit2dram{'26'} = "D8" ;
$bit2dram{'27'} = "D8" ;

$bit2dram{'28'} = "D9" ;
$bit2dram{'29'} = "D9" ;
$bit2dram{'30'} = "D9" ;
$bit2dram{'31'} = "D9" ;

$bit2dram{'32'} = "D10" ;
$bit2dram{'33'} = "D10" ;
$bit2dram{'34'} = "D10" ;
$bit2dram{'35'} = "D10" ;

$bit2dram{'36'} = "D11" ;
$bit2dram{'37'} = "D11" ;
$bit2dram{'38'} = "D11" ;
$bit2dram{'39'} = "D11" ;

$bit2dram{'40'} = "D12" ;
$bit2dram{'41'} = "D12" ;
$bit2dram{'42'} = "D12" ;
$bit2dram{'43'} = "D12" ;

$bit2dram{'44'} = "D13" ;
$bit2dram{'45'} = "D13" ;
$bit2dram{'46'} = "D13" ;
$bit2dram{'47'} = "D13" ;

$bit2dram{'48'} = "D14" ;
$bit2dram{'49'} = "D14" ;
$bit2dram{'50'} = "D14" ;
$bit2dram{'C2'} = "D14" ;

$bit2dram{'51'} = "D15" ;
$bit2dram{'52'} = "D15" ;
$bit2dram{'53'} = "D15" ;
$bit2dram{'C1'} = "D15" ;

$bit2dram{'54'} = "D16" ;
$bit2dram{'55'} = "D16" ;
$bit2dram{'C3'} = "D16" ;
$bit2dram{'C0'} = "D16" ;

$bit2dram{'56'} = "D17" ;
$bit2dram{'57'} = "D17" ;
$bit2dram{'58'} = "D17" ;
$bit2dram{'59'} = "D17" ;

$bit2dram{'60'} = "D18" ;
$bit2dram{'61'} = "D18" ;
$bit2dram{'62'} = "D18" ;
$bit2dram{'63'} = "D18" ;

$bit2dram{'64'} = "D19" ;
$bit2dram{'65'} = "D19" ;
$bit2dram{'66'} = "D19" ;
$bit2dram{'67'} = "D19" ;

$bit2dram{'68'} = "D20" ;
$bit2dram{'69'} = "D20" ;
$bit2dram{'70'} = "D20" ;
$bit2dram{'71'} = "D20" ;

$bit2dram{'C8'} = "D21" ;
$bit2dram{'C5'} = "D21" ;
$bit2dram{'72'} = "D21" ;
$bit2dram{'73'} = "D21" ;

$bit2dram{'C7'} = "D22" ;
$bit2dram{'74'} = "D22" ;
$bit2dram{'75'} = "D22" ;
$bit2dram{'76'} = "D22" ;

$bit2dram{'C6'} = "D23" ;
$bit2dram{'77'} = "D23" ;
$bit2dram{'78'} = "D23" ;
$bit2dram{'79'} = "D23" ;

$bit2dram{'80'} = "D24" ;
$bit2dram{'81'} = "D24" ;
$bit2dram{'82'} = "D24" ;
$bit2dram{'83'} = "D24" ;

$bit2dram{'84'} = "D25" ;
$bit2dram{'85'} = "D25" ;
$bit2dram{'86'} = "D25" ;
$bit2dram{'87'} = "D25" ;

$bit2dram{'88'} = "D26" ;
$bit2dram{'89'} = "D26" ;
$bit2dram{'90'} = "D26" ;
$bit2dram{'91'} = "D26" ;

$bit2dram{'92'} = "D27" ;
$bit2dram{'93'} = "D27" ;
$bit2dram{'94'} = "D27" ;
$bit2dram{'95'} = "D27" ;

$bit2dram{'96'} = "D28" ;
$bit2dram{'97'} = "D28" ;
$bit2dram{'98'} = "D28" ;
$bit2dram{'99'} = "D28" ;

$bit2dram{'100'} = "D29" ;
$bit2dram{'101'} = "D29" ;
$bit2dram{'102'} = "D29" ;
$bit2dram{'103'} = "D29" ;

$bit2dram{'104'} = "D30" ;
$bit2dram{'105'} = "D30" ;
$bit2dram{'106'} = "D30" ;
$bit2dram{'107'} = "D30" ;

$bit2dram{'108'} = "D31" ;
$bit2dram{'109'} = "D31" ;
$bit2dram{'110'} = "D31" ;
$bit2dram{'111'} = "D31" ;

$bit2dram{'112'} = "D32" ;
$bit2dram{'113'} = "D32" ;
$bit2dram{'114'} = "D32" ;
$bit2dram{'115'} = "D32" ;

$bit2dram{'116'} = "D33" ;
$bit2dram{'117'} = "D33" ;
$bit2dram{'118'} = "D33" ;
$bit2dram{'119'} = "D33" ;

$bit2dram{'120'} = "D34" ;
$bit2dram{'121'} = "D34" ;
$bit2dram{'122'} = "D34" ;
$bit2dram{'123'} = "D34" ;

$bit2dram{'124'} = "D35" ;
$bit2dram{'125'} = "D35" ;
$bit2dram{'126'} = "D35" ;
$bit2dram{'127'} = "D35" ;

$dram2dimm{'D0'} = "DIMM3" ;
$dram2dimm{'D1'} = "DIMM2" ;
$dram2dimm{'D2'} = "DIMM1" ;
$dram2dimm{'D3'} = "DIMM0" ;
$dram2dimm{'D4'} = "DIMM3" ;
$dram2dimm{'D5'} = "DIMM2" ;
$dram2dimm{'D6'} = "DIMM1" ;
$dram2dimm{'D7'} = "DIMM0" ;
$dram2dimm{'D8'} = "DIMM3" ;
$dram2dimm{'D9'} = "DIMM1" ;
$dram2dimm{'D10'} = "DIMM0" ;
$dram2dimm{'D11'} = "DIMM3" ;
$dram2dimm{'D12'} = "DIMM2" ;
$dram2dimm{'D13'} = "DIMM1" ;
$dram2dimm{'D14'} = "DIMM0" ;
$dram2dimm{'D15'} = "DIMM3" ;
$dram2dimm{'D16'} = "DIMM2" ;
$dram2dimm{'D17'} = "DIMM1" ;
$dram2dimm{'D18'} = "DIMM2" ;
$dram2dimm{'D19'} = "DIMM1" ;
$dram2dimm{'D20'} = "DIMM0" ;
$dram2dimm{'D21'} = "DIMM3" ;
$dram2dimm{'D22'} = "DIMM2" ;
$dram2dimm{'D23'} = "DIMM1" ;
$dram2dimm{'D24'} = "DIMM0" ;
$dram2dimm{'D25'} = "DIMM3" ;
$dram2dimm{'D26'} = "DIMM2" ;
$dram2dimm{'D27'} = "DIMM0" ;
$dram2dimm{'D28'} = "DIMM3" ;
$dram2dimm{'D29'} = "DIMM2" ;
$dram2dimm{'D30'} = "DIMM1" ;
$dram2dimm{'D31'} = "DIMM0" ;
$dram2dimm{'D32'} = "DIMM3" ;
$dram2dimm{'D33'} = "DIMM2" ;
$dram2dimm{'D34'} = "DIMM1" ;
$dram2dimm{'D35'} = "DIMM0" ;

# return the appropriate array to the caller
return $return ;

}

################################################################################
################################################################################
# set_chicago_bit2dram - Use chicago bit to DRAM mappings
sub set_chicago_bit2dram {

# XXX TBD

# This mapping applies to these products:
# Chicago - Ultra 45 Workstation
# Southside - Ultra 25 Workstation
# Seattle - (1U = V215, 2U = V245)

# declare variables, arrays & hashes
my($return) ;

# DRAM by bit.  Note that this is independent of check-word or DIMM side.
# Actual DRAM pin *is* dependent on both check-word and DIMM side.
# DRAMs are numbered D0 to D35, but that doesn't tell us what DIMM
# they're on!

}

################################################################################
################################################################################
# set_enws_bit2dram - Use enws (Enchilada) bit to DRAM mappings
sub set_enws_bit2dram {

# XXX TBD

# This mapping applies to these products:
# EnWS - SB2500
# EnXS - V210, V240
# N2x0 - N210, N240

# declare variables, arrays & hashes
my($return) ;

# DRAM by bit.  Note that this is independent of check-word or DIMM side.
# Actual DRAM pin *is* dependent on both check-word and DIMM side.
# DRAMs are numbered D0 to D35, but that doesn't tell us what DIMM
# they're on!

}

################################################################################
################################################################################
# set_daktari_bit2dram - Use daktari bit to DRAM mappings
sub set_daktari_bit2dram {

# Daktari - 480, 490, 880, 890

# declare variables, arrays & hashes
my($return) ;

# DRAM by bit.  Note that this is independent of check-word or DIMM side.
# Actual DRAM pin *is* dependent on both check-word and DIMM side.
# DRAMs are numbered D0 to D35, but that doesn't tell us what DIMM
# they're on!

# Data bits are numbers, 0 - 127.
# ECC bits are strings, C0 - C8
# MTAG bits are strings, MT0 - MT2
# MTAG ECC bits are strings, MTC0 - MTC3

# This is for daktari systems and should apply to all 480 490 880 890
# This format is useful for awk scripts.

$bit2dram{'MTC0'} = "D15" ;
$bit2dram{'MTC1'} = "D15" ;
$bit2dram{'MTC2'} = "D17" ;
$bit2dram{'MTC3'} = "D17" ;

$bit2dram{'MT0'} = "D21" ;
$bit2dram{'MT1'} = "D18" ;
$bit2dram{'MT2'} = "D18" ;

$bit2dram{'C4'} = "D18" ;

$bit2dram{'0'} = "D5" ;
$bit2dram{'1'} = "D5" ;
$bit2dram{'2'} = "D5" ;
$bit2dram{'3'} = "D5" ;

$bit2dram{'4'} = "D7" ;
$bit2dram{'5'} = "D7" ;
$bit2dram{'6'} = "D7" ;
$bit2dram{'7'} = "D7" ;

$bit2dram{'8'} = "D6" ;
$bit2dram{'9'} = "D6" ;
$bit2dram{'10'} = "D6" ;
$bit2dram{'11'} = "D6" ;

$bit2dram{'12'} = "D8" ;
$bit2dram{'13'} = "D8" ;
$bit2dram{'14'} = "D8" ;
$bit2dram{'15'} = "D8" ;

$bit2dram{'16'} = "D4" ;
$bit2dram{'17'} = "D4" ;
$bit2dram{'18'} = "D4" ;
$bit2dram{'19'} = "D4" ;

$bit2dram{'20'} = "D0" ;
$bit2dram{'21'} = "D0" ;
$bit2dram{'22'} = "D0" ;
$bit2dram{'23'} = "D0" ;

$bit2dram{'24'} = "D3" ;
$bit2dram{'25'} = "D3" ;
$bit2dram{'26'} = "D3" ;
$bit2dram{'27'} = "D3" ;

$bit2dram{'28'} = "D2" ;
$bit2dram{'29'} = "D2" ;
$bit2dram{'30'} = "D2" ;
$bit2dram{'31'} = "D2" ;

$bit2dram{'32'} = "D1" ;
$bit2dram{'33'} = "D1" ;
$bit2dram{'34'} = "D1" ;
$bit2dram{'35'} = "D1" ;

$bit2dram{'36'} = "D14" ;
$bit2dram{'37'} = "D14" ;
$bit2dram{'38'} = "D14" ;
$bit2dram{'39'} = "D14" ;

$bit2dram{'40'} = "D16" ;
$bit2dram{'41'} = "D16" ;
$bit2dram{'42'} = "D16" ;
$bit2dram{'43'} = "D16" ;

$bit2dram{'44'} = "D17" ;
$bit2dram{'45'} = "D17" ;
$bit2dram{'46'} = "D15" ;
$bit2dram{'47'} = "D15" ;

$bit2dram{'48'} = "D13" ;
$bit2dram{'49'} = "D13" ;
$bit2dram{'50'} = "D13" ;
$bit2dram{'C2'} = "D13" ;

$bit2dram{'51'} = "D9" ;
$bit2dram{'52'} = "D9" ;
$bit2dram{'53'} = "D9" ;
$bit2dram{'C1'} = "D9" ;

$bit2dram{'54'} = "D12" ;
$bit2dram{'55'} = "D12" ;
$bit2dram{'C3'} = "D12" ;
$bit2dram{'C0'} = "D12" ;

$bit2dram{'56'} = "D11" ;
$bit2dram{'57'} = "D11" ;
$bit2dram{'58'} = "D11" ;
$bit2dram{'59'} = "D11" ;

$bit2dram{'60'} = "D10" ;
$bit2dram{'61'} = "D10" ;
$bit2dram{'62'} = "D10" ;
$bit2dram{'63'} = "D10" ;

$bit2dram{'64'} = "D23" ;
$bit2dram{'65'} = "D23" ;
$bit2dram{'66'} = "D23" ;
$bit2dram{'67'} = "D23" ;

$bit2dram{'68'} = "D25" ;
$bit2dram{'69'} = "D25" ;
$bit2dram{'70'} = "D25" ;
$bit2dram{'71'} = "D25" ;

$bit2dram{'C8'} = "D24" ;
$bit2dram{'C5'} = "D24" ;
$bit2dram{'72'} = "D24" ;
$bit2dram{'73'} = "D24" ;

$bit2dram{'C7'} = "D26" ;
$bit2dram{'74'} = "D26" ;
$bit2dram{'75'} = "D26" ;
$bit2dram{'76'} = "D26" ;

$bit2dram{'C6'} = "D22" ;
$bit2dram{'77'} = "D22" ;
$bit2dram{'78'} = "D22" ;
$bit2dram{'79'} = "D22" ;

$bit2dram{'80'} = "D18" ;
$bit2dram{'81'} = "D21" ;
$bit2dram{'82'} = "D21" ;
$bit2dram{'83'} = "D21" ;

$bit2dram{'84'} = "D20" ;
$bit2dram{'85'} = "D20" ;
$bit2dram{'86'} = "D20" ;
$bit2dram{'87'} = "D20" ;

$bit2dram{'88'} = "D19" ;
$bit2dram{'89'} = "D19" ;
$bit2dram{'90'} = "D19" ;
$bit2dram{'91'} = "D19" ;

$bit2dram{'92'} = "D32" ;
$bit2dram{'93'} = "D32" ;
$bit2dram{'94'} = "D32" ;
$bit2dram{'95'} = "D32" ;

$bit2dram{'96'} = "D34" ;
$bit2dram{'97'} = "D34" ;
$bit2dram{'98'} = "D34" ;
$bit2dram{'99'} = "D34" ;

$bit2dram{'100'} = "D33" ;
$bit2dram{'101'} = "D33" ;
$bit2dram{'102'} = "D33" ;
$bit2dram{'103'} = "D33" ;

$bit2dram{'104'} = "D35" ;
$bit2dram{'105'} = "D35" ;
$bit2dram{'106'} = "D35" ;
$bit2dram{'107'} = "D35" ;

$bit2dram{'108'} = "D31" ;
$bit2dram{'109'} = "D31" ;
$bit2dram{'110'} = "D31" ;
$bit2dram{'111'} = "D31" ;

$bit2dram{'112'} = "D27" ;
$bit2dram{'113'} = "D27" ;
$bit2dram{'114'} = "D27" ;
$bit2dram{'115'} = "D27" ;

$bit2dram{'116'} = "D30" ;
$bit2dram{'117'} = "D30" ;
$bit2dram{'118'} = "D30" ;
$bit2dram{'119'} = "D30" ;

$bit2dram{'120'} = "D29" ;
$bit2dram{'121'} = "D29" ;
$bit2dram{'122'} = "D29" ;
$bit2dram{'123'} = "D29" ;

$bit2dram{'124'} = "D28" ;
$bit2dram{'125'} = "D28" ;
$bit2dram{'126'} = "D28" ;
$bit2dram{'127'} = "D28" ;

#
# Group A0 (J2900, J2901, J3001, J3000)
# Group A1 (J3100, J3101, J3201, J3200)
# Group B0 (J7900, J7901, J8001, J8000)
# Group B1 (J8100, J8101, J8201, J8200)
#
# D0 D1 D2 D3 D4 D5 D6 D7 D8              - DIMM0 - J2900 J3100 J7900 J8100
# D9 D10 D11 D12 D13 D14 D15 D16 D17      - DIMM1 - J2901 J3101 J7901 J8101
# D27 D28 D29 D30 D31 D32 D33 D34 D35     - DIMM2 - J3001 J3201 J8001 J8201
# D18 D19 D20 D21 D22 D23 D24 D25 D26     - DIMM3 - J3000 J3200 J8000 J8200
#

$dram2dimm{'D0'} = "DIMM0" ;
$dram2dimm{'D1'} = "DIMM0" ;
$dram2dimm{'D2'} = "DIMM0" ;
$dram2dimm{'D3'} = "DIMM0" ;
$dram2dimm{'D4'} = "DIMM0" ;
$dram2dimm{'D5'} = "DIMM0" ;
$dram2dimm{'D6'} = "DIMM0" ;
$dram2dimm{'D7'} = "DIMM0" ;
$dram2dimm{'D8'} = "DIMM0" ;
$dram2dimm{'D9'} = "DIMM1" ;
$dram2dimm{'D10'} = "DIMM1" ;
$dram2dimm{'D11'} = "DIMM1" ;
$dram2dimm{'D12'} = "DIMM1" ;
$dram2dimm{'D13'} = "DIMM1" ;
$dram2dimm{'D14'} = "DIMM1" ;
$dram2dimm{'D15'} = "DIMM1" ;
$dram2dimm{'D16'} = "DIMM1" ;
$dram2dimm{'D17'} = "DIMM1" ;
$dram2dimm{'D18'} = "DIMM3" ;
$dram2dimm{'D19'} = "DIMM3" ;
$dram2dimm{'D20'} = "DIMM3" ;
$dram2dimm{'D21'} = "DIMM3" ;
$dram2dimm{'D22'} = "DIMM3" ;
$dram2dimm{'D23'} = "DIMM3" ;
$dram2dimm{'D24'} = "DIMM3" ;
$dram2dimm{'D25'} = "DIMM3" ;
$dram2dimm{'D26'} = "DIMM3" ;
$dram2dimm{'D27'} = "DIMM2" ;
$dram2dimm{'D28'} = "DIMM2" ;
$dram2dimm{'D29'} = "DIMM2" ;
$dram2dimm{'D30'} = "DIMM2" ;
$dram2dimm{'D31'} = "DIMM2" ;
$dram2dimm{'D32'} = "DIMM2" ;
$dram2dimm{'D33'} = "DIMM2" ;
$dram2dimm{'D34'} = "DIMM2" ;
$dram2dimm{'D35'} = "DIMM2" ;

# return the appropriate array to the caller
return $return ;

}

################################################################################
################################################################################
# set_excalibur_bit2dram - Use excalibur bit to DRAM mappings
sub set_excalibur_bit2dram {

# Excalibur - SunBlade 1000/2000, 280R and Netra 20s

# declare variables, arrays & hashes
my($return) ;

# DRAM by bit.  Note that this is independent of check-word or DIMM side.
# Actual DRAM pin *is* dependent on both check-word and DIMM side.
# DRAMs are numbered D0 to D35, but that doesn't tell us what DIMM
# they're on!

# Data bits are numbers, 0 - 127.
# ECC bits are strings, C0 - C8
# MTAG bits are strings, MT0 - MT2
# MTAG ECC bits are strings, MTC0 - MTC3

# This is for Excalibur based systems and should apply to SunBlade 1000/2000 280R and Netra 20s 
# This format is useful for awk scripts.

$bit2dram{'MTC0'} = "D0" ;
$bit2dram{'MTC1'} = "D9" ;
$bit2dram{'MTC2'} = "D18" ;
$bit2dram{'MTC3'} = "D27" ;

$bit2dram{'MT0'} = "D9" ;
$bit2dram{'MT1'} = "D18" ;
$bit2dram{'MT2'} = "D27" ;

$bit2dram{'C4'} = "D27" ;

$bit2dram{'0'} = "D18" ;
$bit2dram{'1'} = "D0" ;
$bit2dram{'2'} = "D0" ;
$bit2dram{'3'} = "D0" ;

$bit2dram{'4'} = "D9" ;
$bit2dram{'5'} = "D18" ;
$bit2dram{'6'} = "D27" ;
$bit2dram{'7'} = "D9" ;

$bit2dram{'8'} = "D28" ;
$bit2dram{'9'} = "D28" ;
$bit2dram{'10'} = "D28" ;
$bit2dram{'11'} = "D28" ;

$bit2dram{'12'} = "D29" ;
$bit2dram{'13'} = "D29" ;
$bit2dram{'14'} = "D29" ;
$bit2dram{'15'} = "D29" ;

$bit2dram{'16'} = "D30" ;
$bit2dram{'17'} = "D30" ;
$bit2dram{'18'} = "D30" ;
$bit2dram{'19'} = "D30" ;

$bit2dram{'20'} = "D21" ;
$bit2dram{'21'} = "D21" ;
$bit2dram{'22'} = "D21" ;
$bit2dram{'23'} = "D21" ;

$bit2dram{'24'} = "D12" ;
$bit2dram{'25'} = "D12" ;
$bit2dram{'26'} = "D12" ;
$bit2dram{'27'} = "D12" ;

$bit2dram{'28'} = "D22" ;
$bit2dram{'29'} = "D22" ;
$bit2dram{'30'} = "D22" ;
$bit2dram{'31'} = "D22" ;

$bit2dram{'32'} = "D31" ;
$bit2dram{'33'} = "D31" ;
$bit2dram{'34'} = "D31" ;
$bit2dram{'35'} = "D31" ;

$bit2dram{'36'} = "D32" ;
$bit2dram{'37'} = "D32" ;
$bit2dram{'38'} = "D32" ;
$bit2dram{'39'} = "D32" ;

$bit2dram{'40'} = "D33" ;
$bit2dram{'41'} = "D33" ;
$bit2dram{'42'} = "D33" ;
$bit2dram{'43'} = "D33" ;

$bit2dram{'44'} = "D24" ;
$bit2dram{'45'} = "D24" ;
$bit2dram{'46'} = "D24" ;
$bit2dram{'47'} = "D24" ;

$bit2dram{'48'} = "D15" ;
$bit2dram{'49'} = "D15" ;
$bit2dram{'50'} = "D15" ;
$bit2dram{'C2'} = "D15" ;

$bit2dram{'51'} = "D25" ;
$bit2dram{'52'} = "D25" ;
$bit2dram{'53'} = "D25" ;
$bit2dram{'C1'} = "D25" ;

$bit2dram{'54'} = "D34" ;
$bit2dram{'55'} = "D34" ;
$bit2dram{'C3'} = "D34" ;
$bit2dram{'C0'} = "D34" ;

$bit2dram{'56'} = "D35" ;
$bit2dram{'57'} = "D35" ;
$bit2dram{'58'} = "D35" ;
$bit2dram{'59'} = "D35" ;

$bit2dram{'60'} = "D19" ;
$bit2dram{'61'} = "D19" ;
$bit2dram{'62'} = "D19" ;
$bit2dram{'63'} = "D19" ;

$bit2dram{'64'} = "D10" ;
$bit2dram{'65'} = "D10" ;
$bit2dram{'66'} = "D10" ;
$bit2dram{'67'} = "D10" ;

$bit2dram{'68'} = "D1" ;
$bit2dram{'69'} = "D1" ;
$bit2dram{'70'} = "D1" ;
$bit2dram{'71'} = "D1" ;

$bit2dram{'C8'} = "D20" ;
$bit2dram{'C5'} = "D20" ;
$bit2dram{'72'} = "D20" ;
$bit2dram{'73'} = "D20" ;

$bit2dram{'C7'} = "D11" ;
$bit2dram{'74'} = "D11" ;
$bit2dram{'75'} = "D11" ;
$bit2dram{'76'} = "D11" ;

$bit2dram{'C6'} = "D2" ;
$bit2dram{'77'} = "D2" ;
$bit2dram{'78'} = "D2" ;
$bit2dram{'79'} = "D2" ;

$bit2dram{'80'} = "D3" ;
$bit2dram{'81'} = "D3" ;
$bit2dram{'82'} = "D3" ;
$bit2dram{'83'} = "D3" ;

$bit2dram{'84'} = "D4" ;
$bit2dram{'85'} = "D4" ;
$bit2dram{'86'} = "D4" ;
$bit2dram{'87'} = "D4" ;

$bit2dram{'88'} = "D13" ;
$bit2dram{'89'} = "D13" ;
$bit2dram{'90'} = "D13" ;
$bit2dram{'91'} = "D13" ;

$bit2dram{'92'} = "D23" ;
$bit2dram{'93'} = "D23" ;
$bit2dram{'94'} = "D23" ;
$bit2dram{'95'} = "D23" ;

$bit2dram{'96'} = "D14" ;
$bit2dram{'97'} = "D14" ;
$bit2dram{'98'} = "D14" ;
$bit2dram{'99'} = "D14" ;

$bit2dram{'100'} = "D5" ;
$bit2dram{'101'} = "D5" ;
$bit2dram{'102'} = "D5" ;
$bit2dram{'103'} = "D5" ;

$bit2dram{'104'} = "D6" ;
$bit2dram{'105'} = "D6" ;
$bit2dram{'106'} = "D6" ;
$bit2dram{'107'} = "D6" ;

$bit2dram{'108'} = "D7" ;
$bit2dram{'109'} = "D7" ;
$bit2dram{'110'} = "D7" ;
$bit2dram{'111'} = "D7" ;

$bit2dram{'112'} = "D16" ;
$bit2dram{'113'} = "D16" ;
$bit2dram{'114'} = "D16" ;
$bit2dram{'115'} = "D16" ;

$bit2dram{'116'} = "D26" ;
$bit2dram{'117'} = "D26" ;
$bit2dram{'118'} = "D26" ;
$bit2dram{'119'} = "D26" ;

$bit2dram{'120'} = "D17" ;
$bit2dram{'121'} = "D17" ;
$bit2dram{'122'} = "D17" ;
$bit2dram{'123'} = "D17" ;

$bit2dram{'124'} = "D8" ;
$bit2dram{'125'} = "D8" ;
$bit2dram{'126'} = "D8" ;
$bit2dram{'127'} = "D8" ;

#
# DIMM0 - J0100 J0101
# DIMM1 - J0202 J0203
# DIMM2 - J0304 J0305
# DIMM3 - J0406 J0407
#

$dram2dimm{'D0'} = "DIMM0" ;
$dram2dimm{'D1'} = "DIMM0" ;
$dram2dimm{'D2'} = "DIMM0" ;
$dram2dimm{'D3'} = "DIMM0" ;
$dram2dimm{'D4'} = "DIMM0" ;
$dram2dimm{'D5'} = "DIMM0" ;
$dram2dimm{'D6'} = "DIMM0" ;
$dram2dimm{'D7'} = "DIMM0" ;
$dram2dimm{'D8'} = "DIMM0" ;
$dram2dimm{'D9'} = "DIMM1" ;
$dram2dimm{'D10'} = "DIMM1" ;
$dram2dimm{'D11'} = "DIMM1" ;
$dram2dimm{'D12'} = "DIMM1" ;
$dram2dimm{'D13'} = "DIMM1" ;
$dram2dimm{'D14'} = "DIMM1" ;
$dram2dimm{'D15'} = "DIMM1" ;
$dram2dimm{'D16'} = "DIMM1" ;
$dram2dimm{'D17'} = "DIMM1" ;
$dram2dimm{'D18'} = "DIMM2" ;
$dram2dimm{'D19'} = "DIMM2" ;
$dram2dimm{'D20'} = "DIMM2" ;
$dram2dimm{'D21'} = "DIMM2" ;
$dram2dimm{'D22'} = "DIMM2" ;
$dram2dimm{'D23'} = "DIMM2" ;
$dram2dimm{'D24'} = "DIMM2" ;
$dram2dimm{'D25'} = "DIMM2" ;
$dram2dimm{'D26'} = "DIMM2" ;
$dram2dimm{'D27'} = "DIMM3" ;
$dram2dimm{'D28'} = "DIMM3" ;
$dram2dimm{'D29'} = "DIMM3" ;
$dram2dimm{'D30'} = "DIMM3" ;
$dram2dimm{'D31'} = "DIMM3" ;
$dram2dimm{'D32'} = "DIMM3" ;
$dram2dimm{'D33'} = "DIMM3" ;
$dram2dimm{'D34'} = "DIMM3" ;
$dram2dimm{'D35'} = "DIMM3" ;

# return the appropriate array to the caller
return $return ;

}

################################################################################
################################################################################
# suicide - die a graceful death in a standard fashion
sub suicide {

# declare variables, arrays & hashes
my($line) ;
my(@lines) ;
my($gmodule) ;

# define variables, arrays & hashes
$gmodule = "suicide" ;
@lines = @_ ;

# loop - iterate through each passed line
foreach $line ( @lines ) {
	# write message out to the log file
	echo("$gmodule\@$prog: $line") ;

	# write message to STDERR
	print STDERR "$gmodule\@$prog: $line\n" ;

} ;	# end loop - iterate through each passed line

# write exit message out to the log file
echo("$gmodule\@$prog: exiting ...") ;

# write message to STDERR
print STDERR "$gmodule\@$prog: exiting ...\n" ;

# exit badly
exit 1 ;

} ;	# end of suicide()

################################################################################
################################################################################
# summary - produce CE summary/analysis
sub summary {
# $_[0] - date this summary covers up to

# declare variables, arrays & hashes
my($gmodule) ;
my($sdate) ;
my($snls) ;
my($i) ;	# tmp variable
my($j) ;	# tmp variable
my($esynd) ;
my($dimm) ;
my($flag) ;
my($prl_deduce) ;	# PRL deduce flag

# define variables, arrays & hashes
$gmodule = "summary" ;
$sdate = $_[0] ;
$prl_deduce = 1 ;

# process the FRC/RCE records
process_frc_rce() ;

# test - use rule5 flag set
if ( $use_rule5 ) {
	# do a rule#5 check on the effected DIMM
	rule5($ce{'nls'}) ;

} ;	# test - use rule5 flag set

# put in separator line ...
# test - valid sdate passed
if ( $sdate ) {
	# this is a reboot summary
	msg("", "#### CE Summary prior to reboot at $sdate ###################") ;

	# use the reboot time as the comparison time for rule#3, convert the
	# reboot time to nls format
	$snls = convert_to_nls($sdate) ;

} else {
	# this is an end of messages summary ...
	# test - determine the comparison time for rule#3
	if ( $use_live || $use_history ) {
		# use the $sample_time as the comparison time for rule#3
		$snls = time() ;

		# test - are we using cestat output
		if ( $use_cestat ) {
			# do not deduce PRL
			$prl_deduce = 0 ;

		} ;	# end test - are we using cestat output

	} elsif ( $use_expl && $last_message_time !~ /^\s*Jan\s+1\s+00:00:00\s*$/ ) {
		# use the last message date as the comparison time for rule#3
		$snls = convert_to_nls($last_message_time) ;

	} elsif ( $use_expl && $sample_time ) {
		# use the $sample_time as the comparison time for rule#3
		$snls = $sample_time ;

	} else {
		# use the last message date as the comparison time for rule#3
		$snls = convert_to_nls($last_message_time) ;

	} ; 	# end test - determine the comparison time for rule#3

	# test - is ignore reboots flag set
	if ( $ignore{'reboot'} ) {
		# ignoring reboots ...
		msg("", "#### CE Summary (all messages - reboots ignored) #####################") ;

	} else {
		# *not* ignoring reboots ...
		# test - branch on live system
		if ( ! $use_live ) {
			# not on live system ...
			# output the summary message
			msg("", "#### CE Summary since last detected reboot ###########################") ;

			# test - do we have a reboot date
			if ( $flag{'reboot_found'} ) {
				# we do have a reboot date, so show it
				msg("#### last detected reboot at $flag{'reboot_found'} #########################") ;

			} ;	# end test - do we have a reboot date

		} ;	# end test - branch on live system

	} ;	# test - is ignore reboots flag set

} ;	# end test - valid sdate passed

#####################
# summarise CE data #
#####################
# leave a line
echo("") ;

# fancy title
echo("###################") ;
echo("# CE Data Summary #") ;
echo("###################") ;

# loop - iterate through the DIMM info
CE_DIMM: foreach $i ( sort(keys(%{$counter{'slr'}{'dimm'}})) ) {
	# show the per DIMM CE count
	echo("DIMM $i had $counter{'slr'}{'dimm'}{$i}{'ce'} CE(s)") ;
	msgv("DIMM $i had $counter{'slr'}{'dimm'}{$i}{'ce'} CE(s)") ;

	# initialise non-intermittent CE counts if necessary
	if ( ! $counter{'slr'}{'dimm'}{$i}{'ni_ce'} ) { $counter{'slr'}{'dimm'}{$i}{'ni_ce'} = 0 ; } ;

	# show the per DIMM non-intermittent CE count
	echo("DIMM $i had $counter{'slr'}{'dimm'}{$i}{'ni_ce'} non-intermittent CE(s)") ;
	msgv("DIMM $i had $counter{'slr'}{'dimm'}{$i}{'ni_ce'} non-intermittent CE(s)") ;

	# loop - iterate through each bit for the given DIMM
	foreach $j ( sort(keys(%{$counter{'slr'}{'dimm'}{$i}{'bit'}})) ) {
		# show the per DIMM => bit CE count
		echo("DIMM $i @ $j had $counter{'slr'}{'dimm'}{$i}{'bit'}{$j}{'ce'} CE(s)") ;
		msgv("DIMM $i @ $j had $counter{'slr'}{'dimm'}{$i}{'bit'}{$j}{'ce'} CE(s)") ;
		#msgv("DIMM $i @ $j had $counter{'ddr'}{'dimm'}{$i}{'bit'}{$j}{'ce'} CE(s)") ;

		# loop - iterate through each AFAR modulo 64 for the given DIMM/BIT
		foreach $k ( sort(keys(%{$counter{'slr'}{'dimm'}{$i}{'bit'}{$j}{'m64'}})) ) {
			# determine the number of AFARs
			$k0 = scalar(keys(%{$counter{'slr'}{'dimm'}{$i}{'bit'}{$j}{'m64'}{$k}{'afar'}})) ;

			# show the modulo 64 value of each AFAR
			echo("DIMM $i @ $j @ AFAR%64=$k had $counter{'slr'}{'dimm'}{$i}{'bit'}{$j}{'m64'}{$k}{'ce'} CE(s) across $k0 AFARs") ;
			msgv("DIMM $i @ $j @ AFAR%64=$k had $counter{'slr'}{'dimm'}{$i}{'bit'}{$j}{'m64'}{$k}{'ce'} CE(s) across $k0 AFARs") ;
			#msgv("DIMM $i @ $j @ AFAR%64=$k had $counter{'ddr'}{'dimm'}{$i}{'bit'}{$j}{'m64'}{$k}{'ce'} CE(s) across $k0 AFARs") ;

		} ;	# loop - iterate through each AFAR for given DIMM/BIT

	} ;	# loop - iterate through each bit for the given DIMM

	$k0 = scalar(keys(%{$counter{'ddr'}{$i}})) ;
	echo("DIMM $i had $k0 BIT(s)") ;
	foreach $j ( sort(keys(%{$counter{'ddr'}{$i}})) ) {
		$k0 = scalar(keys(%{$counter{'ddr'}{$i}{$j}})) ;
		echo("DIMM $i @ $j had $k0 different AFAR(s)") ;
		foreach $k (sort(keys(%{$counter{'ddr'}{$i}{$j}}))) {
			$k0 = $#{$counter{'ddr'}{$i}{$j}{$k}} + 1 ;
			echo("DIMM $i @ $j at AFAR $k had $k0 CE(s)") ;
		} ;
	} ;

	# leave a line
	msgv("") ;

} ;	# loop - iterate through the DIMM info

#####################
# summarise PR data #
#####################
# leave a line
echo("") ;

# fancy title
echo("##############") ;
echo("# PRL Status #") ;
echo("##############") ;

# test - is MPR enabled & OK to deduce PRL
if ( $mpr_enabled && $prl_deduce ) {
	# MPR is enabled ...

# NOTE: code retired because it has been found that both stage#1 & stage#2
# NOTE: messages signify that PRL has not been reached
# !!! Retired Code - start !!!
#!!!	# test - branch on PRL detection method
#!!!	if ( $prl_dm == 1 ) {
#!!!		# show method 1 PRL status data ...
#!!!		msgv("DM$prl_dm: PRL deduced status: " . scalar(keys(%prlist)) . " pages scheduled for retirement") ;
#!!!		msgv("DM$prl_dm: PRL deduced status: $ram_prl pages required for PRL") ;
#!!!
#!!!	} elsif ( $prl_dm == 2 ) {
#!!!		# show method 1 PRL status data ...
#!!!		msgv("DM$prl_dm: PRL deduced status: $mpr{'sched_rm_count'} pages scheduled for retirement") ;
#!!!		msgv("DM$prl_dm: PRL deduced status: $mpr{'rm_count'} pages successfully retired") ;
#!!!
#!!!	} ;	# end test - branch on PRL detection method
#!!!
# !!! Retired Code - end !!!

	# show the current PRL status
	msgv("messages files: $mpr{'sched_rm_count'} pages scheduled for retirement") ;
	msgv("messages files: $mpr{'rm_count'} pages successfully retired") ;
	msgv("messages files: $mpr{'sched_clear_count'} pages scheduled for clearing") ;
	msgv("messages files: $mpr{'clear_count'} pages successfully cleared") ;
	msgv("PRL deduced status: PRL reached = " . truefalse($mpr{'prl'}{'reached'}) ) ;

} ;	# end test - is MPR enabled & OK to deduce PRL

# leave a line
echo("") ;

# fancy title
echo("###################") ;
echo("# PR Data Summary #") ;
echo("###################") ;

# show total pages data, if we have it
if ( $mpr{'sched_rm_count'} ) { echo("Memory pages scheduled for removal: $mpr{'sched_rm_count'}") ; } ;
if ( $mpr{'rm_count'} ) {
	echo("Memory pages removed from service: $mpr{'rm_count'}") ;
	if ( $ram > 0 ) {
		echo("Percentage of memory retired " .
		     (($mpr{'rm_count'}/$ram) * 100) . "%");
	} ;
} ;
# leave a line
echo("") ;

# loop - iterate through %page hash
foreach $i ( sort(keys(%page)) ) {
	# debug
	#echo("processing page# $i") ;

	# test - look for pages without a DIMM reference
	if ( ! $page{$i}{'dimm'} ) {
		# show status
		echo("Memory page# $i has no DIMM associated.") ;

	} ;	# end test - look for pages without a DIMM reference

	# test - look for pages scheduled but not removed
	if ( $page{$i}{'sched_rm_count'} && ! $page{$i}{'rm_count'} ) {
		# show status
		echo("Memory page# $i scheduled for removal but not removed. (s=$page{$i}{'sched_rm_count'}/r=$page{$i}{'rm_count'})") ;

	} ;	# end test - look for pages scheduled but not removed

	# test - look for pages removed but not scheduled
	if (
		( defined($page{$i}{'rm_count'}) && $page{$i}{'rm_count'} >= 1 && ! defined($page{$i}{'sched_rm_count'}) )
		|| ( defined($page{$i}{'rm_count'}) && $page{$i}{'rm_count'} > $page{$i}{'sched_rm_count'} )
			) {

		# show status
		echo("Memory page# $i removed but not scheduled for removal. (s=$page{$i}{'sched_rm_count'}/r=$page{$i}{'rm_count'})") ;

	} ;	# end test - look for pages removed but not scheduled

} ;	# end loop - iterate through %page hash

# leave a line
echo("") ;

# fancy title
echo("###################") ;
echo("# Warning Summary #") ;
echo("###################") ;

#########################
# check datapath faults #
#########################

# initialise variables
$flag = 0 ;

# show count of datapath fault type messages found
echo("findings: $datapath{'count'} datapath fault message(s) found") ;
msg("findings: $datapath{'count'} datapath fault message(s) found") ;

# set flag if datapath fault count is true
if ( $datapath{'count'} ) { $flag = 1 ; } ;

# loop - process %datapath/esynd hash
foreach $esynd ( sort(keys(%{$datapath{'esynd'}})) ) {
	# debug
	echo("Processing Esynd $esynd") ;

	# obtain number of DIMMs with CEs with this Esynd
	$i = scalar(keys(%{$datapath{'esynd'}{$esynd}{'dimm'}})) ;

	# test - does more than one DIMM share this Esynd
	if ( $i > 1 ) {
		# more than one DIMM with the same Esynd
		echo("findings: $i DIMM(s) having CEs with Esynd of $esynd found") ;
		msg("findings: $i DIMM(s) having CEs with Esynd of $esynd found") ;

		# set the flag
		$flag = 1 ;

	} ;	# end test - does more than one DIMM share this Esynd

} ;	# end loop - process %datapath/esynd hash

# test - produce datapath fault advice
if ( $flag )  {
	# show datapath fault advice
	msg("advice:$policy{'datapath'}{'priority'}: $policy{'datapath'}{'when'}") ;

} ;	# end test - produce datapath fault advice

###############
# check rule3 #
###############

# test - rule3 in use
if ( $use_rule3 ) {
	# set variables
	$i = $ue{'count'} ;
	$j = "rule3" ;

	# test - any UEs found
	if ( $i ) {
		# rule 3 findings
		echo("findings: $i UE(s) found - potential rule#3 match") ;
		msg("findings: $i UE(s) found - potential rule#3 match") ;

		# show advice
		msg("advice:$policy{$j}{'priority'}: $policy{$j}{'when'}") ;

	} elsif ( ( $ue{'date'} + ( 48 * 60 * 60 ) ) > $snls ) {
		# there has been a UE within the last 48(ish) hours ...
		msgv("findings: current time " . scalar(localtime($snls))) ;
		echo("findings: current time " . scalar(localtime($snls))) ;
		msgv("findings: last UE seen on " . scalar(localtime($ue{'date'}))) ;
		echo("findings: last UE seen on " . scalar(localtime($ue{'date'}))) ;
		echo("findings: UE(s) found recently - potential rule#3 match") ;
		msg("findings: UE(s) found recently - potential rule#3 match") ;

		# show advice
		msg("advice:$policy{$j}{'priority'}: $policy{$j}{'when'}") ;

	} else {
		# rule 3 findings
		echo("findings: $i UE(s) found - there is no rule#3 match") ;
		msg("findings: $i UE(s) found - there is no rule#3 match") ;

	} ;	# end test - any UEs found

} ;	# test - rule3 in use

# fancy title
echo("#######################") ;
echo("# Replacement Summary #") ;
echo("#######################") ;

###############
# check rule4 #
###############

# test - rule4 in use
if ( $use_rule4 ) {
	# set variables
	$i = scalar(keys(%{$replace{'rule4a'}})) ;

	# show status
	echo("", "summary: $i DIMMs have matched rule#4a failure pattern since last reboot.") ;

	# rule#4a findings
	echo("findings: $i DIMMs with a failure pattern matching rule#4a") ;
	msg("findings: $i DIMMs with a failure pattern matching rule#4a") ;

	# loop - iterate through %replace{'rule4a'} hash
	foreach $j ( sort(keys(%{$replace{'rule4a'}})) ) {
		# show DIMM effected
		echo("summary: DIMM '$j' has matched rule#4a failure pattern $replace{'rule4a'}{$j} time(s) since last reboot.") ;
		msg("findings: DIMM '$j' matched rule#4a failure pattern") ;

		# show advice & update flags
		dimm_fail_advise($j, 'rule4a') ;

	} ;	# end loop - iterate through %replace{'rule4a'} hash

	# set variables
	$i = scalar(keys(%{$replace{'rule4b'}})) ;

	# show status
	echo("", "summary: $i DIMMs have matched rule#4b failure pattern since last reboot.") ;

	# rule#4b findings
	echo("findings: $i DIMMs with a failure pattern matching rule#4b") ;
	msg("findings: $i DIMMs with a failure pattern matching rule#4b") ;

	# loop - iterate through %replace{'rule4b'} hash
	foreach $j ( sort(keys(%{$replace{'rule4b'}})) ) {
		# show DIMM effected
		echo("summary: DIMM '$j' has matched rule#4b failure pattern $replace{'rule4b'}{$j} time(s) since last reboot.") ;
		msg("findings: DIMM '$j' matched rule#4b failure pattern") ;

		# show advice & update flags
		dimm_fail_advise($j, 'rule4b') ;

	} ;	# end loop - iterate through %replace{'rule4b'} hash

} ;	# test - rule4 in use

###############
# check rule5 #
###############

# test - branch on rule5 or cestat status
if ( $use_rule5 || $use_cestat) {

	# set variables
	$i = scalar(keys(%{$replace{'rule5'}})) ;

	# show status PRL status
	if ( $i ) { echo("$gmodule: check rule5: PRL reached") ; } else { echo("$gmodule: check rule5: PRL *not* reached") ; } ;

	# show status
	echo("", "summary: $i DIMMs have matched rule#5 failure pattern since last reboot.") ;

	# rule#5 findings
	echo("findings: $i DIMMs with a failure pattern matching rule#5") ;
	msg("findings: $i DIMMs with a failure pattern matching rule#5") ;

	# loop - iterate through %replace{'rule5'} hash
	foreach $j ( sort(keys(%{$replace{'rule5'}})) ) {
		# show DIMM effected
		if ( $replace{'dimm'}{$j}{'rule5_match_count'} ) { echo("summary: DIMM '$j' has matched rule#5 failure pattern $replace{'dimm'}{$j}{'rule5_match_count'} time(s) since last reboot.") ; } ;
		msg("findings: DIMM '$j' matched rule#5 failure pattern") ;

		# save last DIMM name
		$k = $j ;

	} ;	# end loop - iterate through %replace{'rule5'} hash

	# test - more than 1 DIMM matching rule5
	if ( $i > 1 ) {
		# inconclusive data, as per policy advise to consult Sun Support
		echo("advice:$policy{'rule5'}{'priority'}: consult Sun Support to rule out other causes of CEs before replacing any DIMMs") ;
		msg("advice:$policy{'rule5'}{'priority'}: consult Sun Support to rule out other causes of CEs before replacing any DIMMs") ;

	} elsif ( $i == 1 ) {
		# show advice & update flags
		dimm_fail_advise($k, 'rule5') ;

	} elsif ( $use_cestat && $cestat{'prl_reached'} && $i == 0 ) {
		# weird data, as per policy advise to consult Sun Support
		echo("findings: Page Retirement Limit reached") ;
		echo("advice:$policy{'rule5'}{'priority'}: consult Sun Support to determine course of action") ;
		msg("findings: Page Retirement Limit reached") ;
		msg("advice:$policy{'rule5'}{'priority'}: consult Sun Support to determine course of action") ;

	} ;	# end test - more than 1 DIMM matching rule5

} ;	# end test - branch on rule5 or cestat status

################
# check rule5b #
################

# test - branch on rule5b
if ( $use_rule5b ) {
	# set variables
	$i = scalar(keys(%{$replace{'rule5b'}})) ;

	# show status
	echo("", "summary: $i DIMMs have matched rule#5b failure pattern " .
	    "since last reboot.") ;

	# Rule5b states that any DIMM which has had 120 or more CEs within a
	# 24 hour period on the same bit and AFAR will be recommended for
	# replacement if the page retirement limit has not been exceeded.
	# Some DIMMs may have been marked as a match for rule5b prior to the
	# completion of the analysis.  At this point, when the analysis is
	# complete, make the final determination whether rule5b advice is
	# to be printed based upon whether the page retirement limit has been
	# exceeded.  If the PRL has been exceeded, then rule5 overrides any
	# rule5b advice.
	#
	# The intent of this rule is to catch situations where the kernel is
	# unable to retire a memory page for any reason.
	if ( $mpr{'prl'}{'reached'} ) {
		echo("$gmodule: check rule5b: PRL *reached*") ;

		# rule#5b findings
		echo("findings: 0 DIMMs with a failure pattern matching " .
			"rule#5 supplemental") ;
		msg("findings: 0 DIMMs with a failure pattern matching " .
			"rule#5 supplemental") ;
	} else {
		echo("$gmodule: check rule5b: PRL not reached") ;

		# rule#5b findings
		echo("findings: $i DIMMs with a failure pattern matching " .
			"rule#5 supplemental") ;
		msg("findings: $i DIMMs with a failure pattern matching " .
			"rule#5 supplemental") ;

		# loop - iterate through %replace{'rule5b'} hash
		foreach $j ( sort(keys(%{$replace{'rule5b'}})) ) {
			# show DIMM effected
			echo("summary: DIMM '$j' has matched rule#5b failure " .
			     "pattern $replace{'rule5b'}{$j} time(s) since " .
			     "last reboot.") ;
			msg("findings: DIMM '$j' matched rule#5 supplemental " .
			    "failure pattern") ;

			# show advice & update flags
			dimm_fail_advise($j, 'rule5b') ;

		} ;	# end loop - iterate through %replace{'rule5b'} hash
	} ;

} ;	# end test - branch on rule5b

###############
# check rule6 #
###############

# test - rule6 in use
if ( $use_rule6 ) {
	# set variables
	$i = scalar(keys(%{$replace{'rule6'}})) ;

	# show status
	echo("", "summary: $i DIMMs have matched rule#6 failure pattern since last reboot.") ;

	# rule#6 findings
	echo("findings: $i DIMMs with a failure pattern matching rule#6") ;
	msg("findings: $i DIMMs with a failure pattern matching rule#6") ;

	# loop - iterate through %replace{'rule6'} hash
	foreach $j ( sort(keys(%{$replace{'rule6'}})) ) {
		# show DIMM effected
		echo("summary: DIMM '$j' has matched rule#6 ($rule6_ce_count in $rule6_hour_count) failure pattern $replace{'rule6'}{$j} time(s) since last reboot.") ;
		msg("findings: DIMM '$j' matched rule#6 ($rule6_ce_count in $rule6_hour_count) failure pattern") ;

		# save last DIMM name
		$k = $j ;

	} ;	# end loop - iterate through %replace{'rule6'} hash

	# test - more than 1 DIMM matching rule6
	if ( $i > 1 ) {
		# inconclusive data, as per policy advise to consult Sun Support
		echo("advice:$policy{'rule6'}{'priority'}: consult Sun Support to rule out other causes of CEs before replacing any DIMMs") ;
		msg("advice:$policy{'rule6'}{'priority'}: consult Sun Support to rule out other causes of CEs before replacing any DIMMs") ;

	} elsif ( $i == 1 ) {
		# show advice & update flags
		dimm_fail_advise($k, 'rule6') ;

	} ;	# end test - more than 1 DIMM matching rule6

} ;	# end test - rule6 in use

# show total pages data, if we have it
# leave a line
echo("") ;

} ;	# end of summary()

################################################################################
################################################################################
# truefalse - return 'true' or 'false' string of given value
sub truefalse {
# $_[0] = value to determine string for

# declare variables, arrays & hashes
my($return) ;
my($value) ;

# define variables, arrays & hashes
$value = $_[0] ;

# test - branch on value of $value string
if ( defined($value) && $value eq "enabled" ) {
	# this is 'enabled' ...
	$return = 'enabled' ;

} elsif ( defined($value) && $value eq "disabled" ) {
	# this is 'disabled' ...
	$return = 'disabled' ;

} elsif ( defined($value) && $value ) {
	# this is 'true' ...
	$return = 'true' ;

} elsif ( defined($value) && ! $value ) {
	# this is 'false' ...
	$return = 'false' ;

} else {
	# this is 'n/a' ...
	$return = 'n/a' ;

} ;	# end test - branch on value of $value string

# return appropriately to the caller
return $return ;

} ;	# end truefalse()

################################################################################
################################################################################
# ultrasparc_chip - return the UltraSPARC chip version number
sub ultrasparc_chip {

# declare variables, arrays & hashes
my(@return) ;
my($gmodule) ;
my(@lines1) ;
my(@lines2) ;
my($line) ;
my($device_type) ;
my($implementation) ;

# define variables, arrays & hashes
$gmodule = "ultrasparc_chip" ;
@return = () ;

# test - branch on invocation mode
if ( $use_expl ) {
	# running against Explorer output ...
	@lines1 = read_lines("$edir/sysconfig/prtdiag-v.out") ;
	@lines2 = read_lines("$edir/sysconfig/prtconf-vp.out") ;

} elsif ( $use_live || $use_history ) {
	# running against a live system ...
	@lines1 = split(/\n/, `$exe{'prtdiag'} -v`) ;
	@lines2 = split(/\n/, `$exe{'prtconf'} -vp`) ;

} else {
	# eek! how did we get to here?
	# exit gracefully
	suicide("$gmodule: unexpected test branch encountered") ;

} ;	# end test - branch on invocation mode

# loop - #1 process each line in each @lines array
LINE_LIST: foreach $line ( @lines1, @lines2 ) {

	# skip undef() lines
	if ( ! defined($line) ) { next("LINE_LIST") ; } ;

	# strip off any MS-DOSish ^M characters
	$line =~ s/\r//g ;

	# remove trailing <LF> characters
	chomp($line) ;

	# strip any leading & trailing whitespace characters
	$line =~ s/^\s*// ;
	$line =~ s/\s*$// ;

	# test - reset variables on encountering a new node
	if ( $line =~ /^Node\s+0x[0-9a-fA-F]+$/ ) {
		# reset variables ...
		$device_type = "" ;
		$implementation = "" ;

	} ;	# end test - reset variables on encountering a new node

	# test - check for 'device-type' line
	if ( $line =~ /^device_type:\s+\'(.*)\'$/ ) {
		# store device type
		$device_type = $1 ;

	} ;	# end test - check for 'device-type' line

	# test - check for 'implementation#' line
	if ( $line =~ /^implementation#:\s+(.*)$/ ) {
		# store implementation number
		$implementation = $1 ;

		# removing leading zeroes
		$implementation =~ s/^0*// ;

	} ;	# end test - check for 'implementation#' line

	# test - check for CPU-type match
	if ( $device_type && $device_type eq 'cpu' && $implementation && $implementation =~ /^[12][0-9]$/ ) {
		# CPU-type match ...
		# test - assign CPU type array
		if ( $implementation == 10 ) {
			# UltraSPARC I
			@return = (1, 1) ;

		} elsif ( $implementation == 11 ) {
			# UltraSPARC II
			@return = (2, 2) ;

		} elsif ( $implementation == 12 ) {
			# UltraSPARC IIi
			@return = (2, '2i') ;

		} elsif ( $implementation == 13 ) {
			# UltraSPARC IIe
			@return = (2, '2e') ;

		} elsif ( $implementation == 14 ) {
			# UltraSPARC III
			@return = (3, 3) ;

		} elsif ( $implementation == 15 ) {
			# UltraSPARC III+
			@return = (3, '3+') ;

		} elsif ( $implementation == 16 ) {
			# UltraSPARC IIIi
			@return = (3, '3i') ;

		} elsif ( $implementation == 18 ) {
			# UltraSPARC IV
			@return = (4, 4) ;

		} elsif ( $implementation == 19 ) {
			# UltraSPARC IV+
			@return = (4, '4+') ;

		} elsif ( $implementation == 20 ) {
			# UltraSPARC V
			@return = (5, 5) ;

		} elsif ( $implementation == 22 ) {
			# UltraSPARC IIIi+
			@return = (3, '3i+') ;

		} ;	# test - assign CPU type array

		# if we have a valid return value then skip further tests
		#if ( @return ) { print STDERR "CPU from IMPLEMENTATION\n" ; goto("US_FINISH") ; } ;
		if ( @return ) { goto("US_FINISH") ; } ;

	} ;	# end test - check for CPU-type match

} ;	# end loop - #1 process each line in each @lines array

# loop - #2 process each line in each @lines array
LINE_LIST: foreach $line ( @lines1, @lines2 ) {
	# skip undef() lines
	if ( ! defined($line) ) { next("LINE_LIST") ; } ;

	# strip off any MS-DOSish ^M characters
	$line =~ s/\r//g ;

	# remove trailing <LF> characters
	chomp($line) ;

	# test - branch on UltraSPARC version
	# *NB* - test order is crucial!
	if ( $line =~ /\'SUNW,UltraSPARC\-IV([ie\+]?)\'/
			|| $line =~ /\s+US\-IV([ie\+]?)\s+/ ) {
		# UltraSPARC IV
		@return = (4, "4${1}") ;

	} elsif ( $line =~ /\'SUNW,UltraSPARC\-III([ie\+]?)\'/
			|| $line =~ /\'SUNW,UltraSPARC\-III(i\+)\'/
			|| $line =~ /\s+US\-III([ie\+]?)\s+/
			|| $line =~ /\s+US\-III(i\+)\s+/ ) {
		# UltraSPARC III*
		@return = (3, "3${1}") ;

	} elsif ( $line =~ /\'SUNW,UltraSPARC\-II([ie\+]?\')/
			|| $line =~ /\s+US\-II([ie\+]?)\s+/ ) {
		# UltraSPARC II*
		@return = (2, "2${1}") ;

	} elsif ( $line =~ /\'SUNW,UltraSPARC\'/
			|| $line =~ /\s+US\-I\s+/ ) {
		# UltraSPARC I
		@return = (1, 1) ;

	} ;	# end test - branch on UltraSPARC version

	# if we have a valid return value then skip further tests
	#if ( @return ) { print STDERR "CPU from PRTDIAG\n" ; goto("US_FINISH") ; } ;
	if ( @return ) { goto("US_FINISH") ; } ;

} ;	# end loop - #2 process each line in each @lines array

# label for goto()
US_FINISH:

# return the appropriate value to the caller
return @return ;

} ;	# end of ultrasparc_chip()

################################################################################
################################################################################
# uname_a - return the Solaris uname -a string as an array
sub uname_a {

# declare variables, arrays & hashes
my(@return) ;
my(@a) ;
my($t) ;
my($gmodule) ;

# define variables, arrays & hashes
$gmodule = "uname_a" ;

# test - branch on running against an Explorer
if ( $use_expl ) {
	# running against Explorer output ...
	# test - can we open the 'uname -a' file
	if ( -e $efile{'uname_a'} && -r $efile{'uname_a'} ) {
		# parse the 'uname -a' file
		@a = read_lines($efile{'uname_a'}) ;
		$t = $a[0] ;

		# test - branch on definition of $t
		if ( defined($t) ) {
			# good data ...
			@return = split(/\s+/, $t) ;

		} else {
			# bad data ... looks like read_lines() or file_open()
			# had a problem ... exit gracefully
			suicide("$gmodule: no data returned from read_lines() on '$efile{'uname_a'}'") ;

		} ;	# end test - branch on definition of $t

	} else {
		# exit gracefully
		suicide("$gmodule: unable to open '$efile{'uname_a'}'") ;

	} ;	# end test - can we open the 'showrev -p' file

} elsif ( $use_live || $use_history ) {
	# running against a live system ...
	@return = split(/\s+/, `$exe{'uname'} -a`) ;

} elsif ( $use_man ) {
	# return the @uname_a_data array populated by the -c CLI argument
	@return = @uname_a_data ;

} else {
	# eeek! how did we get to here?
	# die gracefully
	suicide("$gmodule: unexpected test branch encountered") ;

} ;	# test - branch on running against an Explorer

# return the appropriate array to the caller
return @return ;

} ;	# end of uname_a()

################################################################################
################################################################################
# usage - give a usage message on STDERR & exit
sub usage {
# $_[0] = invocation type

# declare variables, arrays & hashes

# print the usage message to STDERR
print STDERR <<'eof_usage' ;

cediag:- usage ...

Show usage information
~~~~~~~~~~~~~~~~~~~~~~

	# cediag -h

	where:-

		. -h shows usage information


Show version information
~~~~~~~~~~~~~~~~~~~~~~~~

	# cediag -V

	where:-

		. -V shows version information


License acceptance status
~~~~~~~~~~~~~~~~~~~~~~~~~

	# cediag -A

	where:-

		. -A returns license acceptance status for the invoking user


Executing Against A Live System
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

	# cediag [-L] [-P] [-N] [common options]

	where:-

		. -P turn on checkpointing
		. -N turn off prescreening
		. -L turn on logging to syslogd(1m)
		. 'common options' - see below


Executing Against A Live System - Full Historical Analysis
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

        # cediag -F [-P] [-N] [common options]

        where:-

		. -P turn on checkpointing
		. -N turn off prescreening
                . 'common options' - see below


Executing Against An Explorer Output
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

	$ cediag -e Explorer_directory [common options]

	where:-

		. 'Explorer_directory' is an uncompressed & extracted Explorer
			output directory
		. 'common options' - see below


Executing Against A Standalone Messages File
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

	$ cediag -c SunOS,HostName,5.x,sparc -k KUP-rev -u US-version \
		-p platform [common options] messages.file(s)

	where:-

		. -c specify the configuration of the system to be analysed
			. 'SunOS' = literal
			. 'HostName' = hostname to limit messages search to
			. '5.x' = version of SunOS, e.g. '5.5.1', '5.7', '5.9'
			. 'sparc' = architecture, i.e. 'sparc' or 'x86'
		. KUP-rev is the Kernel Update Patch number & revision that
			the system to be analysed has installed
		. US-version is the UltraSPARC chip version of the system to be
			analysed
			. 1 = US-I
			. 2 = US-II
			. 2e = US-IIe
			. 2i = US-IIi
			. 3 = US-III
			. 3i = US-IIIi
			. 3i+ = US-IIIi+
			. 3+ = US-III+
			. 4 = US-IV
			. 4+ = US-IV+
		. platform is the hardware implementation as found in the
			'System Configuration' line of prtdiag -v output
		. 'messages.file(s)' is the name of the messages file(s) to
			analyse
			NOTE: if specifying multiple messages files be sure to
			NOTE: specify the file in ascending date order, e.g.
			NOTE: messages.2 messages.1 messages.0 messages
			NOTE: to ensure correct processing
		. 'common options' - see below

Common Options
~~~~~~~~~~~~~~

The following options are common to all modes of operation:-

-I 'events'	'events' is a list of events to ignore, i.e.:-
		. 'r' - ignore reboot, do not reset counts when a reboot is seen

-l debug.txt	'debug.txt' is the name of ASCII text file in which to write
		debugging information

-M {d|e}    force [M]emory [P]age [R]etirement detection
			. d = disabled - MPR disabled
			. e = enabled - MPR enabled
			. NOTE: this argument has *no effect* unless *all* the
				conditions below are met:-
				. cestat output is not available
				. running against systems that have a
				MPR-capable OS *&* have a MPR-aware kernel
				running, ie.:-
					. Solaris 8 with 108528-24 (or higher)
					. Solaris 9 with 112233-11 (or higher)
					. Solaris 10

-s C:H	adjust Rule#6 tunable 'CEs in Hours' count
		. C = CEs
		. H = Hours

-v 	turns on verbose mode

# eof

eof_usage

# test - exit cleanly depending on invocation type
if ( $_[0] =~ /^getopt/ ) {
	# getopt failure
	suicide("incorrect usage") ;

} elsif ( $_[0] =~ /^help/ ) {
	# help request ...
	# just use a plain exit
	exit 1 ;

} else {
	# unknown failure
	suicide("unable to determine cause") ;

} ;	# end test - exit cleanly depending on invocation type

} ;	# end of usage()

################################################################################
