package tkpingLib;
###############################################################################
## @(#)FILE: tkpingLib.pm - Perl package of common routines & constants
## @(#)$Source: /cvs/stephen/personal/tkping/tkpingLib.pm,v $
## ============================================================================
##  Description:
##
##   This file is a repository of generally useful perl-functions.
##   It may be used by including a "use tkpingLib;" statement in your perl
##   scripts.
##
###############################################################################

##-----------------------------------------------------------------------------
## IMPORTS:
##  The following variables are expected to be defined in the user's script:
##
##  $main::NAME         = name of your script/program
##  $main::SYNOPSIS    = command-line syntax synopsis
##  $main::ARGUMENTS   = description of all options/arguments
##  $main::DESCRIPTION = brief description (one paragraph) of program
##  $main::opt_debug    = true if '-debug' was specified on the command-line
##  $main::opt_verbose  = true if '-verbose' was specified on the command-line
##  $main::opt_silent   = true if '-silent' was specified on the command-line
##  $main::opt_nodo     = true if '-nodo' was specified on the command-line
##-----------------------------------------------------------------------------

require 5.002;

use vars qw($VERSION @EXPORT @EXPORT_OK);
require Exporter;

@ISA = qw(Exporter);

$VERSION = 1.10;

@EXPORT = qw(
	&VerboseMsg
	&DebugMsg
	&ProgressMsg
	&FatalMsg
	&Msg
	&Usage
	&SetLog
	&EndLog
	&LogMsg
	&RunDate
	&GetDateTime
	&Trim
	&Basename
	&Dirname
	&File2Array
	&Array2Fdesc
	&Array2File
	&ArrayRef2File
    &IniFile2Hash
    &Hash2IniFile
	&BackupFile
	&DoCmd
	&DoCmdRetOutput
	&DoCmdRetSingleLine
	&GeneralMkdir
	&GetUserNames
	&Cmd4thisOsRev
	&EnsureDirExists
	&EnsureVariableExists
	&RequireDir
	&RequireParm
	&Assert
);

@EXPORT_OK = qw(
	$TKPING_LIB_VERSION
	$TKPING_LIB_DATE
	@TKPING_LIB_ABORT_FCN_AR
	$NOTSET_STR
);

##-----------------------------------------------------------------------------
## EXPORTS:
##  The following variables are defined in the user's package by this library:
##
##  $TKPING_LIB_VERSION      = version of tkpingLib library
##  $TKPING_LIB_DATE         = date of creation of this version of clearlib
##
##	@TKPING_LIB_ABORT_FCN_AR = populate this with \&func refs to be executed on exit
##
##  $NOTSET_STR              = common value/name used during option validation
##
##-----------------------------------------------------------------------------
$TKPING_LIB_VERSION            = $VERSION;
$TKPING_LIB_DATE               = "Mon Apr 16 00:11:43 MDT 2001";

@TKPING_LIB_ABORT_FCN_AR = ();

$sep = "-" x 65;

local $unitTestMode=0;
local $traceCalls=0;
local $fatalOccurred=0;
local $logFspec="";
local *LOGFILE;

$NOTSET_STR = "*-NOT-SET-*";


##-----------------------------------------------------------------------------
## FUNCTION:
##   Msg -- display message identified by progname and level of severity
##          to stderr (also logs error messages)
##
## SYNOPSIS:
##   Msg([-nl,|-nonl] [-noid,|-inf,|-war,|-err,] "message text")
##
## EXAMPLE:
##   Msg(-inf,"message text");
##
##      produces: "script: INFO- message text" on STDERR
##
##-----------------------------------------------------------------------------
sub Msg(@)
{
	my @ARGV = @_;
	DebugMsg("Called as: Msg(@ARGV)") if($traceCalls);
	my $ARGC = @ARGV;
	my $prefixText="";
	my $severityText="";
	my $logToo = 0;
	my $noId = 0;
	my $currParm;
	my $optIdx=0;
	my $nuline=1;
	while($ARGC > 0 && $optIdx < $ARGC) {
		for($ARGV[$optIdx]) {
			DebugMsg("Msg(): arg=[$_], optIdx=$optIdx, ARGC=$ARGC") if($traceCalls);
			/^\-.*$/ and do {
				$currParm = $_;
				DebugMsg("Msg(): currParm=[$currParm]") if($traceCalls);
				for($currParm) {
					/^\-nl$/ and do {
						shift @ARGV;
						$prefixText="\n";
						last;
					};
					/^\-nonl$/ and do {
						shift @ARGV;
						$nuline=0;
						last;
					};
					/^\-noid$/ and do {
						shift @ARGV;
						$noId = 1;	# don't display identifier on this message
						last;
					};
					/^\-err$/ and do {
						shift @ARGV;
						$severityText="ERROR- ";
						$logToo = 1;	# force this to be logged
						last;
					};
					/^\-inf$/ and do {
						shift @ARGV;
						$severityText="INFO- ";
						last;
					};
					/^\-war$/ and do {
						shift @ARGV;
						$severityText="WARNING- ";
						last;
					};
					print STDERR "${main::NAME}: WARNING- Msg(): Option [$currParm] not supported!\n";
					shift @ARGV;
				}
				last;
			};
			DebugMsg("Msg(): skipping non-option (++)") if($traceCalls);
			$optIdx++;	# skip this non-option
		}
		$ARGC = @ARGV;
	}
	my ($msgText) = @ARGV;	# text is remainder of list

	if( $nuline ) {
		if($noId) {
			print STDERR $msgText,"\n";
		} else {
			print STDERR $prefixText,"${main::NAME}: ",$severityText,$msgText,"\n";
		}
	} else {
		if($noId) {
			print STDERR $msgText;
		} else {
			print STDERR $prefixText,"${main::NAME}: ",$severityText,$msgText;
		}
	}

	if($isLogging && $logToo) {
		if($prefixText ne "") {
			LogMsg(-nl,"$severityText$msgText");
		} else {
			if($noid) {
				LogMsg(-noid,"$msgText");
			} else {
				LogMsg("$severityText$msgText");
			}
		}
	}
}


##-----------------------------------------------------------------------------
## FUNCTION:
##   FatalMsg -- Print the given arguments to STDERR (prefixed by "$NAME: ERROR-").
##  						 and then exit.
##
## SYNOPSIS:
##   &FatalMsg("message text");
##
## SIDE EFFECTS:
##   Exits the script by calling internal routine TerminateExecution().
##
##   produces:	"script: ERROR- message text" on STDERR and exits.
##
##   NOTE: this version also logs if logging is already enabled.
##
##-----------------------------------------------------------------------------
sub FatalMsg(@)
{
	local($_) = @_;
        select(STDERR); $| = 1; select(STDOUT);
	Msg(-nl,-err,"$_");
	if(defined &TerminateExecution) {
		&TerminateExecution(2);
	} else {
		exit(2);
	}
}


##-----------------------------------------------------------------------------
## FUNCTION:
##   ProgressMsg -- display message identified by progname and level of severity
##  				to stdout (also logs error messages)
##
## SYNOPSIS:
##   ProgressMsg([-nl,] [-log,] [-fil {fname},|-filapnd {fname},] "message text")
##
## EXAMPLE:
##   ProgressMsg(-inf,"message text");
##
##   produces:	"script: INFO- message text" on STDOUT
##
##-----------------------------------------------------------------------------
sub ProgressMsg(@)
{
	my @ARGV = @_;
	DebugMsg("Called as: ProgressMsg(@ARGV)") if($traceCalls);
	my $ARGC = @ARGV;
	my $prefixText="";
	my $logToo = 0;
	my $toFileToo = 0;
	my $modeAppend = 0;
	my $noId = 0;
	my $currParm;
	my $outFspec = "";
	my $optIdx=0;
	while($ARGC > 0 && $optIdx < $ARGC) {
		for($ARGV[$optIdx]) {
			DebugMsg("ProgressMsg(): arg=[$_], optIdx=$optIdx, ARGC=$ARGC") if($traceCalls);
			/^\-.*$/ and do {
				$currParm = $_;
				DebugMsg("ProgressMsg(): currParm=[$currParm]") if($traceCalls);
				for($currParm) {
					/^\-nl$/ and do {
						shift @ARGV;
						$prefixText="\n";
						last;
					};
					/^\-noid$/ and do {
						shift @ARGV;
						$noId = 1;	# don't display identifier on this message
						last;
					};
					/^\-log$/ and do {
						shift @ARGV;
						$logToo = 1;	# force this to be logged
						last;
					};
					/^\-fil$/ and do {
						shift @ARGV;
						$toFileToo = 1;  # force this out to file, too
						$modeAppend = 0; # overwrite!
						if($ARGC >= 2) {
							$outFspec = shift @ARGV;
						} else {
							SwengFatalMsg("CODING-ERROR ProgressMsg(): Option [$currParm] requires fileName");
							$toFileToo=0;
						}
						last;
					};
					/^\-filapnd$/ and do {
						shift @ARGV;
						$toFileToo = 1;  # force this out to file, too
						$modeAppend = 1; # append!
						if($ARGC >= 2) {
							$outFspec = shift @ARGV;
						} else {
							SwengFatalMsg("CODING-ERROR ProgressMsg(): Option [$currParm] requires fileName");
							$toFileToo=0;
						}
						last;
					};
					print STDERR "${main::NAME}: WARNING- ProgressMsg(): Option [$currParm] not supported!\n";
					shift @ARGV;
				}
				last;
			};
			DebugMsg("ProgressMsg(): skipping non-option (++)") if($traceCalls);
			$optIdx++;	# skip this non-option
		}
		$ARGC = @ARGV;
	}
	my ($msgText) = @ARGV;	# text is remainder of list

	if(! defined $main::opt_silent) {
		if($noId) {
			print STDOUT $msgText,"\n";
		} else {
			print STDOUT $prefixText,"${main::NAME}: ",$msgText,"\n";
		}
	}

	if($isLogging && $logToo) {
		if($prefixText ne "") {
			LogMsg(-nl,"$msgText");
		} else {
			if($noid) {
				LogMsg(-noid,"$msgText");
			} else {
				LogMsg("$msgText");
			}
		}
	}

	if($toFileToo) {
		if($modeAppend) {
			open OUTFILE, ">>$outFspec" or
				SwengFatalMsg("Unable to append to [$outFspec], Aborting!!\007");
				return if(UTFatalOccurred());  # return if under unit-test!
		} else {
			open OUTFILE, ">$outFspec" or
				SwengFatalMsg("Unable to write to [$outFspec], Aborting!!\007");
				return if(UTFatalOccurred());  # return if under unit-test!
		}
		print OUTFILE $prefixText,"${main::NAME}: ",$msgText,"\n";
		close OUTFILE or
			SwengFatalMsg("ERROR- Unable to close [$outFspec], Aborting!!\007");
			return if(UTFatalOccurred());  # return if under unit-test!
	}
}


##-----------------------------------------------------------------------------
## FUNCTION:
##   VerboseMsg -- Print verbose output. Prints the specified arguments to STDERR
##  						only if '-verbose' was specified on the command-line.
##
## SYNOPSIS:
##   &VerboseMsg("verbose message text");
##
##  		produces:  script(VERBOSE): verbose message text
##
##-----------------------------------------------------------------------------
sub VerboseMsg(@)
{
	print STDERR "${main::NAME}(VERBOSE): @_\n" if ($main::opt_verbose);
}


##-----------------------------------------------------------------------------
## FUNCTION:
##   DebugMsg -- Print debugging output. Prints the specified arguments to
##               STDERR only if '-debug' was specified on the command-line.
##
## SYNOPSIS:
##   DebugMsg("debug message text");
##
##     produces:  script(DBG): debug message text
##
##-----------------------------------------------------------------------------
sub DebugMsg(@)
{
	print STDERR "${main::NAME}(DBG): @_\n" if ($main::opt_debug);
}


##-----------------------------------------------------------------------------
## FUNCTION:
##   SwengFatalMsg -- Print the given arguments to STDERR (prefixed by "$NAME: ").
##  				  and then exit.
##
##   ****** USED IN THIS PACKAGE ONLY--  real one provided earlier in this file! *****
##  		   (this version supports unit testing for this package!)
##-----------------------------------------------------------------------------
sub SwengFatalMsg(@)
{
	local($_) = @_;
	print STDERR "${main::NAME}: ERROR- $_, Aborting...\n";
	if(not $unitTestMode) {
			exit(2);
	} else {
		$fatalOccurred=1;
	}
}


##-----------------------------------------------------------------------------
## FUNCTION:
##   TerminateExecution -- Perform any desired cleanup actions and exit.
##
## SYNOPSIS:
##   &TerminateExecution($exit_status);
##
## ARGUMENTS:
##   $exit_status: value to pass to exit() when exiting the script
##
## PRECONDITIONS:
##   The global list @TKPING_LIB_ABORT_FCN_AR should contain the name of
##   zero or morecleanup functions to invoke before exiting the script.
##   This list isinitially empty, but may be populated by the user if
##   so desired.
##
## SIDE EFFECTS:
##   - Invokes the functions named in @TKPING_LIB_ABORT_FCN_AR. The functions
##     are invoked in order with an empty argument list.
##   - Exits the script.
##
##-----------------------------------------------------------------------------
sub TerminateExecution($)
{
   my $exit_status = shift;
   local $_;
   for (@TKPING_LIB_ABORT_FCN_AR) {
      eval "&${_}()";  ## call user's cleanup function
   }
   exit($exit_status);  ## make sure we exit if the user didnt.
}


##-----------------------------------------------------------------------------
## FUNCTION:
##   Usage -- Print a Usage message and then exit with the specified exit-code.
##            If the exit-code is > 1, then Usage is terse (synopsis only) and
##            goes to STDERR. Otherwise Usage is verbose and goes to STDOUT.
##
## SYNOPSIS:
##   &Usage($val);
##
## ARGUMENTS:
##   $val : The integer exit-code value to use (defaults to 2).
##
## SIDE EFFECTS:
##   Exits the script.
##
##-----------------------------------------------------------------------------
sub Usage(@)
{
   local($_) = @_;
   $_ = 2 unless (/^\d+$/o);
   select STDERR unless ($_ <= 1);
   print "\nUsage: ${main::SYNOPSIS}\n";
   print "\nArguments:${main::ARGUMENTS}${main::DESCRIPTION}\n" unless ($_ > 1);
   exit($_);
}


##-----------------------------------------------------------------------------
## FUNCTION:
##   LogMsg -- Print debugging output. Prints the specified arguments to
##               log file only if logging is enabled
##
## SYNOPSIS:
##   LogMsg([-nl,|-noid,] "logged message text");
##
##     appends  "script: logged message text"  to log file
##
##-----------------------------------------------------------------------------
sub LogMsg(@)
{
	my @ARGV = @_;
	my $ARGC = @ARGV;
	my $prefixText="";
	my $currParm;
	my $optIdx=0;
	my $noId = 0;
	DebugMsg("Called as: LogMsg(@ARGV)") if($traceCalls);
	if(not $isLogging) {
		SwengFatalMsg("LogMsg(): Logging not started yet!");
		return if(UTFatalOccurred());  # return if under unit-test!
	}
	while($ARGC > 0 && $optIdx < $ARGC) {
		for($ARGV[$optIdx]) {
			DebugMsg("LogMsg: arg=[$_], optIdx=$optIdx, ARGC=$ARGC") if($traceCalls);
			/^\-.*$/ and do {
				$currParm = $_;
				DebugMsg("LogMsg: currParm=[$currParm]") if($traceCalls);
				for($currParm) {
					/^\-nl$/ and do {
						shift @ARGV;
						$prefixText="\n";
						last;
					};
					/^\-noid$/ and do {
						shift @ARGV;
						$noId = 1;	# don't display identifier on this message
						last;
					};
					print STDERR "${main::NAME}: WARNING- LogMsg(): Option [$currParm] not supported!\n";
					shift @ARGV;
				}
				last;
			};
			DebugMsg("LogMsg: skipping non-option (++)") if($traceCalls);
			$optIdx++;	# skip this non-option
		}
		$ARGC = @ARGV;
	}
	my ($msgText) = @ARGV;	# text is remainder of list
	my $timeStamp = RunDate();
	if($noid) {
		# print but leave white space where time-stamp would be (so all lines up)
		print LOGFILE "                  $msgText\n";
	} else {
		print LOGFILE "$prefixText${main::NAME}: $timeStamp $msgText\n";
	}
}


##-----------------------------------------------------------------------------
## FUNCTION:
##   SetLog -- enable logging by registering log filename
##
## SYNOPSIS:
##   DebugMsg("debug message text");
##
##  		produces:  script(DBG): debug message text
##
##-----------------------------------------------------------------------------
sub SetLog($)
{
	my $logFspec = shift;
	DebugMsg("SetLog() Entered--") if($traceCalls);
	if(length($logFspec) < 1) {
		SwengFatalMsg("SetLog(): Log Filename must be provided");
		return if(UTFatalOccurred());  # return if under unit-test!
	}
	if($tkpingLib::isLogging) {
		SwengFatalMsg("SetLog(): Attempt to open log 2nd time, aborted!");
		return if(UTFatalOccurred());  # return if under unit-test!
	}
	open LOGFILE, ">>$logFspec" or
		SwengFatalMsg("Unable to append to [$logFspec], Aborting!!\007");
		return if(UTFatalOccurred());  # return if under unit-test!
        select(LOGFILE); $| = 1; select(STDOUT);
	$tkpingLib::isLogging=1;	# set active flag!
	$tkpingLib::logFspec = $logFspec;
	LogMsg(-nl,"Running...");
}


##-----------------------------------------------------------------------------
## FUNCTION:
##   EndLog -- disable logging
##
## SYNOPSIS:
##   DebugMsg("debug message text");
##
##  		produces:  script(DBG): debug message text
##
##-----------------------------------------------------------------------------
sub EndLog()
{
	DebugMsg("EndLog() Entered--") if($traceCalls);
	if(!$tkpingLib::isLogging) {
		SwengFatalMsg("Attempt to close log 2nd time, aborted!");
		return if(UTFatalOccurred());  # return if under unit-test!
	}
	LogMsg("Complete.");
	$tkpingLib::isLogging=0;	# set INactive flag!
	close LOGFILE or
		SwengFatalMsg("ERROR- Unable to close [$tkpingLib::logFspec], Aborting!!\007");
		return if(UTFatalOccurred());  # return if under unit-test!
}


##-----------------------------------------------------------------------------
## FUNCTION:
##   RunDate -- return string identifying date/time of log attempt
##  	 (generate standard date-time for logging:	DDmmmYY-HH:MM:SS)
##
## SYNOPSIS:
##   $timeStamp = &RunDate();
##
##  		produces:  $timeStamp = "25May97-12:04:30"
##
##-----------------------------------------------------------------------------
sub RunDate()
{
	my $runDateTxt = &GetDateTime( 'DDMMMYY-HH:MM:SS' );
	return $runDateTxt;
}


##-----------------------------------------------------------------------------
## FUNCTION:
##   EnableUnitTest -- set bool identifying we are unit-testing!
##
## SYNOPSIS:
##   tkpingLib::EnableUnitTest();
##
##  		(prevents fatal errors from exiting!)
##
##-----------------------------------------------------------------------------
sub EnableUnitTest()
{
	$unitTestMode=1;
	$traceCalls=1;
	DebugMsg("tkpingLib:: Enabled unit testing");
	return $unitTestMode;
}


##-----------------------------------------------------------------------------
## FUNCTION:
##   UTFatalOccurred -- return true if unit-test-fatal-error occurred
##
## SYNOPSIS:
##   return if(UTFatalOccurred());
##
##  		(used to force exit under unit-test conditions)
##
##-----------------------------------------------------------------------------
sub UTFatalOccurred()
{
	if(not $unitTestMode) {
		DebugMsg("UTFatalOccurred() NOT in UT mode...") if($traceCalls);;
		return 0;				 # exit no err if not under unit-test
	}
	DebugMsg("UTFatalOccurred() in UT mode... fatalOccurred=$fatalOccurred") if($traceCalls);;
	my $exitState = $fatalOccurred;  # save actual state
	$fatalOccurred = 0;              # clear if set!
	return $exitState;               # return state
}


##-----------------------------------------------------------
## FUNCTION:
##   GetDateTime -- returns formatted date/time string
##  	 based on input pattern specifier
##
## SYNOPSIS:
##   $dateTimeStr = &GetDateTime( $formatSpec )
##
##  NOTES:
##  	1) pattern matching is 'greedy'; put more-specific matches
##  		 earlier than less-specific ( eg., HH:MM:SS before HH:MM )
##
##  	2) $mon has range (0..11) and $wday has range (0..6)
##
## ----------------------------------------------------------
sub GetDateTime($)
{
	my $formatSpec  = shift;

	my( @monthNmAr ) = ( "Jan", "Feb", "Mar", "Apr", "May", "Jun",
	                    "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" );

	my( @dayNmAr ) = ( "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" );

	my $dateTimeResult = "GetDateTime::INVALID_FORMAT_ID";

	my( $sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst)
			= localtime( time );

	# Workaround Perl bug for Y2K
    if( $year > 99 ) { $year = $year - 100 };
    if( length( $year ) < 2 ) { $year = "0$year"; }

	# ----------------------------------------------------------
	#  'Switch' construct ... create output based on $formatSpec
	# ----------------------------------------------------------
	for( $formatSpec )
	{
		/DDMMMYY-HH:MM:SS/ and do
		{
			$dateTimeResult = sprintf( "%2.2d%3.3s%2.2d-%2.2d:%2.2d:%2.2d",
			                      $mday, $monthNmAr[ $mon ], $year, $hour, $min, $sec );
			last;
		};

		/HH:MM_DDDc_DD_MMM_YYYY/ and do
		{
			$dateTimeResult = sprintf( "%02d:%02d %3.3s, %02d %3.3s 20%02d",
			                      $hour, $min, $dayNmAr[ $wday ], $mday, $monthNmAr[ $mon ], $year );
			last;
		};

		/YYMMDD_HH:MM/ and do
		{
			$dateTimeResult = sprintf( "%02d%02d%02d_%02d:%02d",
			                      $year, $mon+1, $mday, $hour, $min );
			last;
		};

		/HH:MM:SS/ and do
		{
			$dateTimeResult = sprintf( "%02d:%02d:%02d", $hour, $min, $sec );
			last;
		};

		/HH:MM/ and do
		{
			$dateTimeResult = sprintf( "%02d:%02d", $hour, $min );
			last;
		};

		/YYMMDD/ and do
		{
			$dateTimeResult = sprintf( "%02d%02d%02d", $year, $mon +1, $mday );
			last;
		};

		/DD-MMM-YY/ and do
		{
			$dateTimeResult = sprintf( "%02d-%03s-%02d", $mday, $monthNmAr[ $mon ], $year );
			last;
		};
		FatalMsg( "GetDateTime(): '$formatSpec' is not currently supported" );
	}
	# ----------------------------------------------------------

	return $dateTimeResult;
}



##-----------------------------------------------------------
## FUNCTION:
##   Trim()  -- remove leading and trailing whitespace from
##  	 input string
##
## SYNOPSIS:
##   $string = Trim( $string );
##
## ----------------------------------------------------------
sub Trim($)
{
	my $trimmedString  = shift;
	$trimmedString =~ s:^\s+:: ;
	$trimmedString =~ s:\s+$:: ;
	return $trimmedString;
}


##-----------------------------------------------------------
## FUNCTION:
##   Basename()  -- remove directory path
##                  (& optional suffix) from input pathname
##
## SYNOPSIS:
##   $string = &Basename( $string [, $extStr] );
##
## ----------------------------------------------------------
sub Basename($;$)
{
	my $fileBasename = shift;
	my $extension = shift;

	if(!defined $extension) {
		$extension = "";
	} else {
		### prep for use as part of regexp
		$extension =~ s/\./\\\./g;
	}

	if($fileBasename eq "") {
	} elsif($fileBasename eq "/" || $fileBasename eq "\\") {
	} else {
		$fileBasename =~ s:^.*[/\\]::g;
	}
	if($^O =~ /win32/i) {
		###  win32 is case insensative!
		$fileBasename =~ s/$extension$//i if($extension ne "");
	} else {
		$fileBasename =~ s/$extension$// if($extension ne "");
	}
	return $fileBasename;
}


##-----------------------------------------------------------
## FUNCTION:
##   Dirname()	-- remove filename from input pathname
##
## SYNOPSIS:
##   $string = &Dirname( $string );
##
## ----------------------------------------------------------
sub Dirname($)
{
	my $fspec = shift;

	my $fileDirname;
	my $fileName;

	foreach ($fspec) {
		# iff only / or \ or . or {emptyString} then ...
		/^[\\\/]$/ || /^\.$/ || /^$/ and do {
			$fileDirname = $fspec;
			$fileName = $fspec;
			last;
		};
		# iff only .. then ...
		/^\.\.$/ and do {
			$fileDirname = ".";
			$fileName = $fspec;
			last;
		};
		# all others, do ...
		($fileName, $fileDirname) = ($fspec =~ m|^(.*)[/\\]([^\\/]+)$|o) ? ($2, $1) : ($fspec, '.');
	}

	return $fileDirname;
}


##-----------------------------------------------------------------------------
## FUNCTION:
##   File2Array -- populate array from named file, failing if file does not exist
##
##	NOTE: removes newlines from each element
##
## SYNOPSIS:
##   @array = &File2Array(filename);
##
##-----------------------------------------------------------------------------
sub File2Array($)
{
	my $filespec = shift;
	my @newAr = ();

	DebugMsg("File2Array() loading array from $filespec");

	#  ensure our data file exists and open it
	open AR_SRC_FILE,"<$filespec" or
		FatalMsg("Unable to read [$filespec]: $!, Aborting!!\007");

	#  load our data file into the array
	@newAr = <AR_SRC_FILE>;	# grab all lines of file

	#  close our data file
	close AR_SRC_FILE or
		FatalMsg("Unable to close [$filespec]: $!, Aborting!!\007");

	#  return our array
	chomp @newAr;	# remove newlines...
	my $lineCt = @newAr;
	DebugMsg("File2Array() returning array of $lineCt element(s)");

	return @newAr;
}


##-----------------------------------------------------------------------------
## FUNCTION:
##   Array2File -- write array to named file, overwriting file if it exists
##                   (use BackupFile() to copy before overwrite)
##
##	NOTE: appends newline to each element as it is written to file
##
## SYNOPSIS:
##   &Array2File(filename,@array);
##
##-----------------------------------------------------------------------------
sub Array2File($@)
{
	my $filespec = shift;
	my @argAr = @_;
	my $lineCt = @argAr;
	DebugMsg("Array2File() writing array ($lineCt element(s)) to $filespec");

	#  ensure our data file exists and open it
	open AR_DST_FILE,">$filespec" or
		FatalMsg("Unable to write to [$filespec]: $!, Aborting!!\007");

	#  load our data file into the array
	foreach $ele (@argAr) {
		print AR_DST_FILE $ele,"\n";	# write each element to our file
	}

	#  close our data file
	close AR_DST_FILE or
		FatalMsg("Unable to close [$filespec]: $!, Aborting!!\007");
}


### ===========================================================================
###  File Format Specs for   Hash2IniFile() and IniFile2Hash() routines
### ---------------------------------------------------------------------------
###  SYNTAX:  (pretty simple)
###
###    Comment char is '#'.  Comments can be on a line by themselves or
###      on the right edge of any other lines in the file.
###
###    Comments (comment lines or right edge of lines) are stripped before
###      the file is parsed)
###
###    Blank lines terminate a list (hash or array), multiples are ignored
###
###    There are two value-set forms:  Hash's and Array's (perl'esq)  They are
###    preceeded by keynames of the form '[{keyname}{suffix}]'.
###
###    The suffix identifies (to the parser) which is being specified:
###     SUFFIX:
###        ~Hs   - is a hash
###        ~Ar   - is an array
###
###    An array (~Ar) is specified in one of two forms: (the presence of a
###    leading '[' indicates which form to use.)
###
###    [{keyname}Ar]               # head a value list
###    value one                   #  one value per line
###    value two                   #  one value per line
###      ...etc...                 #
###    {blank line}                # ends value list
###
###    [{keyname}Ar]               # head a value list
###    [000] = value one           #  one value per line (NOTE leading zeros and fixed width)
###    [001] = value two           #  one value per line
###    [002] = value three         #  one value per line
###     ...etc...                  #
###    {blank line}                # ends value list
###
###        NOTE: this second array form is what the file would look like if written
###        by the tkpingLib::Hash2Ini() library routine.  This form is accepted
###        primarily so that the reader-side can read the written form of these files.
###
###    A hash (~Hs) is specified in only one form:
###
###    [{keyname}Hs]               # head a value list
###    key1 = value one            #  one value per line (NOTE leading zeros and fixed width)
###    key2 = value two            #  one value per line
###    key3 = value three          #  one value per line
###      ...etc...                 #
###    {blank line}                # ends value list
###
###  The final feature is that hashs can have simple key-value pairs or may
###   contain key-value pairs that effectively point to other hash's and array's.
###
###  To point from a hash to a hash:
###     [topHs]
###     childHs = theNamedHs
###     simpleKey = simple Value
###
###     [theNamedHs]
###     anotherKey = another simple value
###
###  To point from a hash to a hash:
###     [topHs]
###     childAr = theNamedAr
###     simpleKey = simple Value
###
###     [theNamedAr]
###     value one
###     value 2
###     value drei
###
###   to recap, there are three key forms for hash's:
###    (1) Simple:   {keyname} = value
###    (2) ptr2Hash  {keyname}Hs = {hashName}   # where hashName must end in an ~Hs suffix
###    (3) ptr2Array {keyname}Ar = {arrayName}  # where arrayName must end in an ~Ar suffix
###
###
###  NOTE: arrays can NOT point to arrays or hashs
###
###  NOTE2:  there is no limit (well, maybe memory on your system? ;-) to the
###     depth you may cascade these references.
###
###  NOTE3:  the presence of a special hash named 'ConstantsHs' will cause
###          its contents to be preloaded into the current namespace.  This
###          is intended to facilitate coding multiple scripts against a single
###          .ini file by moving the constants from a shared source file to
###          the .ini file which is already shared! -Stephen
###
###  SPECIAL WARNING:  watch out for cycles!  You must break them by by using
###    simple key names for one of the hash's!!
###
###      - Stephen M. Moraco t590-5714  mailto:stephen@cos.agilent.com
### ===========================================================================

### ----------------------------------------------------------------------------
###  scanForKeys - identify hashes/arrays to be written to .ini file
###
sub scanForKeys($@$)
{
    local $startingKey = shift;
    local *resultAr = shift;
    my $namespace = shift;

    DebugMsg("scanForKeys(): namespace=[$namespace],",
             "startingKey=[$startingKey]");
    if($startingKey =~ /Hs$/) {
        foreach my $key (sort keys %{"${namespace}$startingKey"}) {
            my $value = ${"${namespace}$startingKey"}{$key};
            if($key =~ /Hs$/) {
                if($value ne "") {
                    # make name of hash ref namespace-neutral
                    $value =~ s/^$namespace// if ($value =~ /Hs$/);
                    ###  DebugMsg("Found next hash Key=[$value] to archive");
                    push @resultAr,$value;
                    # recurse
                    scanForKeys($value,\@resultAr,$namespace);
                } else {
                    SwengFatalMsg("$startingKey\{$key\} has NO value, must be symbolic-name of hash");
                    return if (UTFatalOccurred());  # return if under unit-test!
                }
            } elsif($key =~ /Ar$/) {
                if($value ne "") {
                    # make name of array ref namespace-neutral
                    $value =~ s/^$namespace// if ($value =~ /Ar$/);
                    push @resultAr,$value;
                    ###  DebugMsg("Found next array Key=[$value] to archive");
                } else {
                    SwengFatalMsg("$startingKey\{$key\} has NO value, must be symbolic-name of array");
                    return if (UTFatalOccurred());  # return if under unit-test!
                }
            }
        # else it's a scalar -- don't do more
        }
    } elsif($startingKey =~ /Ar$/) {
        # done
    } else {
        SwengFatalMsg("Invalid Starting ini Key [$startingKey], must be hash or array");
        return if (UTFatalOccurred());  # return if under unit-test!
    }
}


### ----------------------------------------------------------------------------
###  genHsArIniFile - find then write hashes/arrays to .ini file
###
sub genHsArIniFile(*$$)
{
    local *HSDUMP_OUT = shift;
    my $fileListHsNm = shift;
    my $namespace = shift;

    local @keyListAr = ();

    push @keyListAr,$fileListHsNm;  #  put top key, then chase to get rest
    scanForKeys($fileListHsNm,\@keyListAr,$namespace);

    my $keyListCt = @keyListAr;
    ###  DebugMsg("Save $keyListCt keys to ini file");

    my $arrayCt = 0;
    my $hashCt = 0;
    my $value;

    foreach my $keyName (@keyListAr) {
        print HSDUMP_OUT "# $sep\n";
        print HSDUMP_OUT "#\n";
        print HSDUMP_OUT "[$keyName]\n";
        if($keyName =~ /Hs$/) {
            local *hashRef = \%{"${namespace}$keyName"};
            foreach $subKey (sort keys %hashRef) {
                $value = $hashRef{$subKey};
                if ((($subKey =~ /Hs$/) && ($value =~ /Hs$/)) ||
                    (($subKey =~ /Ar$/) && ($value =~ /Ar$/))) {
                    # if it's a symbolic hash or array ref, make the value
                    # namespace-neutral
                    $value =~ s/^$namespace//;
                }
                print HSDUMP_OUT "$subKey = $value\n";
            }
            print HSDUMP_OUT "\n";
            $hashCt++;
        } elsif($keyName =~ /Ar$/) {
            my $elemCt = 0;
            for (@{"${namespace}$keyName"}) {
                my $line = sprintf("[%03.3d] = %s",$elemCt++,$_);
                print HSDUMP_OUT "$line\n";
            }
            print HSDUMP_OUT "\n";
            $arrayCt++;
        } else {
            SwengFatalMsg("Attempted save of symbolic reference to $keyName, but NO hash name given (as value)");
            return if (UTFatalOccurred());  # return if under unit-test!
        }
    }
    DebugMsg("genHsArIniFile() hash_count=$hashCt, array_count=$arrayCt, top_key=[$fileListHsNm]");
}


## ----------------------------------------------------------------------------
## FUNCTION:
##   Hash2IniFile -- write hash-system to named file, overwriting file if it exists
##                   (Hint: use BackupFile() to copy before overwrite)
##
##  NOTE: Write file in microsoft .ini format
##
## SYNOPSIS:
##   Hash2IniFile($fileSpec,$topHashName[,$namespace]);
##
## uses main:: if $namespace isn't provided
##
##-----------------------------------------------------------------------------
sub Hash2IniFile($$;$)
{
    my $outFName = shift;
    my $topHashHsNm = shift;
    my $namespace = shift;

    $namespace = "main::" if (!defined $namespace); # default
    # append colons if missing
    $namespace .= "::" if ($namespace !~ /::$/);

    DebugMsg("Hash2IniFile() writing hashes/arrays from \%${namespace}$topHashHsNm to $outFName");

    open(HSDUMP_OUT,">$outFName") or
      SwengFatalMsg("Can't open [$outFName] for write: $!");
    return if (UTFatalOccurred());  # return if under unit-test!

	my ($realId,$effId) = tkpingLib::GetUserNames();
    my $extraIdStr = ($realId ne $effId) ? sprintf("(as %s)",$effId) : "";

    my $dateTimeStr = tkpingLib::GetDateTime("YYMMDD_HH:MM");

    print HSDUMP_OUT "# $sep\n";
    print HSDUMP_OUT "#         FILE:  $outFName\n";
    print HSDUMP_OUT "#    Namespace:  $namespace\n";
    print HSDUMP_OUT "#   Created by:  $::NAME (Ver $::VERSION) [",
    # also note our own package version
                     __PACKAGE__, " Ver $TKPING_LIB_VERSION]\n";
    print HSDUMP_OUT "#       Run by:  $realId $extraIdStr\n";
    print HSDUMP_OUT "#           on:  $dateTimeStr\n";

    genHsArIniFile(\*HSDUMP_OUT, $topHashHsNm, $namespace);

    print HSDUMP_OUT "\n";
    print HSDUMP_OUT "#\n";
    print HSDUMP_OUT "# $sep\n";
    print HSDUMP_OUT "#   End of FILE:  $outFName\n";
    print HSDUMP_OUT "# $sep\n";

    close HSDUMP_OUT or
      SwengFatalMsg("Can't close $outFName: $!");
    return if (UTFatalOccurred());  # return if under unit-test!
}


## ----------------------------------------------------------------------------
## FUNCTION:
##   IniFile2Hash -- populate hashes/arrays from named file,
##                   failing if file does not exist.  It returns the
##                   topLevel hash name (without the namespace prefix)
##
##  NOTE:  constructs complex hashs/arrays in $namespace.  If that parameter isn't
##         provided, uses main::.
##
## SYNOPSIS:
##   $hashNm = IniFile2Hash($fileSpec[,$namespace]);
##
##-----------------------------------------------------------------------------
sub IniFile2Hash($;$)
{
    my $iniFspec = shift;
    my $namespace = shift;

    $namespace = "main::" if (!defined $namespace); # default
    # append colons if missing
    $namespace .= "::" if ($namespace !~ /::$/);

    DebugMsg("IniFile2Hash() loading hashes/arrays from $iniFspec,",
             "namespace=[$namespace]");

    my @iniSourceAr;
    @iniSourceAr = File2Array($iniFspec);

    my $topKeyNm = "";
    my $loadingHash = 0;
    my $LoadingArray = 0;
    my $currAHname = "";
    my $hashCt = 0;
    my $arrayCt = 0;
    my $isLoadingConstants = 0;
    foreach my $line (@iniSourceAr) {
        if($line =~ /^\s*#/) {
            next;   # skip comments!
        }
        $line =~ s/\s+#.*$//;   # remove right edge comments
        if($line =~ /^\[/ && !$loadingHash && !$LoadingArray) {
            # beginning new Key
            my $key = $line;
            $key =~ s/\].*$//;  # remove all after key name
            $key =~ s/^\[//;    # remove left begin-key marker
            if($topKeyNm eq "") {
                $topKeyNm = $key;
            }
            my $isHash = ($key =~ /Hs$/) ? 1 : 0;
            if($isHash) {
                $loadingHash = 1;
                $LoadingArray = 0;
                $hashCt++;
                # WAS: $currAHname = $key;
                $currAHname = "${namespace}$key";
                %{"$currAHname"} = ();  ### init the top level hash!!
                DebugMsg("IniFile2Hash() init'd hash [$currAHname]");
                if($key =~ /^ConstantsHs$/) {
                    $isLoadingConstants = 1;    ### TURN-ON name-space populating mech
                }
           } else {
                $loadingHash = 0;
                $LoadingArray = 1;
                $arrayCt++;
                # WAS: $currAHname = $key;
                $currAHname = "${namespace}$key";
                @{"$currAHname"} = ();  ### init the top level hash!!
                DebugMsg("IniFile2Hash() init'd array [$currAHname]");
            }
            next;
        }
        if($line =~ /^\s*$/) {
            $loadingHash = 0;
            $LoadingArray = 0;
            $isLoadingConstants = 0;    ### TURN-OFF name-space populating mech
            next;
        }
        if($loadingHash) {
            my ($keyName, $keyValue) = split /\s*=\s*/,$line,2;

            ### WAS: ${"main::$currAHname"}{$keyName} = $keyValue;
            if ((($keyName =~ /Hs$/) && ($keyValue =~ /Hs$/)) ||
                (($keyName =~ /Ar$/) && ($keyValue =~ /Ar$/))) {
                # (bug fix) adjust the value to the namespace -- the
                # symbolic names need to be in the right namespace
                $keyValue = "${namespace}$keyValue";
            }
            $$currAHname{$keyName} = $keyValue;
            DebugMsg("$currAHname\{$keyName\}=[$$currAHname{$keyName}]");

            if($isLoadingConstants) {
                if(defined ${"${namespace}$keyName"}) {
                    my $oldValue = ${"${namespace}$keyName"};
                    Msg(-war,"Overwriting \$${namespace}$keyName = [$oldValue] with [$keyValue]");
                }
                ${"${namespace}$keyName"} = $keyValue;
                DebugMsg("Loaded Constant \$${namespace}$keyName = [$keyValue]");
            }
            next;
        } elsif($LoadingArray) {
            my $idx;
            my $value;
            if($line =~ /^\[/) {
                ($idx, $value) = split /\]\s*=\s*/,$line,2;
                $idx =~ s/^\[//;
            } else {
                $line =~ s/\s+#.*$//g;  ## remove comments & white before comments
                $value = $line;
            }

            push @$currAHname,$value;

            next;
        }
        # else
        Msg(-err,"Failed to process [$line]");
    }
    DebugMsg("IniFile2Hash() hash_count=$hashCt, array_count=$arrayCt, top_key=[$topKeyNm]");
    return $topKeyNm;
}


##-----------------------------------------------------------------------------
## FUNCTION:
##   BackupFile -- copy named file to backup (preserve file before change)
##
## SYNOPSIS:
##   &BackupFile(filename,".dat",".BAK");
##          -or-
##   &BackupFile(-force,filename,".dat",".BAK");
##
##-----------------------------------------------------------------------------
sub BackupFile(@)
{
	my @ARGV = @_;
	DebugMsg("Called as: BackupFile(@ARGV)") if($traceCalls);
	my $ARGC = @ARGV;
	my $optIdx=0;
	$forceOverwrite = 0;
	while($ARGC > 0 && $optIdx < $ARGC) {
		for($ARGV[$optIdx]) {
			DebugMsg("BackupFile(): arg=[$_], optIdx=$optIdx, ARGC=$ARGC") if($traceCalls);
			/^\-.*$/ and do {
				$currParm = $_;
				DebugMsg("BackupFile(): currParm=[$currParm]") if($traceCalls);
				for($currParm) {
					/^\-force$/ and do {
						shift @ARGV;
						$forceOverwrite = 1;
						last;
					};
					print STDERR "${main::NAME}: WARNING- BackupFile(): Option [$currParm] not supported!\n";
					shift @ARGV;
				}
				last;
			};
			DebugMsg("BackupFile(): skipping non-option (++)") if($traceCalls);
			$optIdx++;	# skip this non-option
		}
		$ARGC = @ARGV;
	}
	my ($msgText) = @ARGV;	# text is remainder of list

	if($ARGC < 3) {
		FatalMsg("BackupFile(): CODE-ERROR- not enough parms provided\007");
	}
	my $newSuffix = pop @ARGV;
	my $origSuffix = pop @ARGV;
	my $filespec = pop @ARGV;

	DebugMsg("filespec=$filespec origSuffix=$origSuffix newSuffix=$newSuffix");
	# NOTE: uses open/close pairs for better messaging when fail occurs

	open TEST_NBR1,"<$filespec" or
		FatalMsg("Unable to backup [$filespec]: $!, Aborting!!\007");
	close TEST_NBR1;

	my $baseName = Basename($filespec,$origSuffix);
	my $dirName = Dirname($filespec);
	my $newFspec = $dirName . "/" . $baseName . $newSuffix;
	DebugMsg("baseName=$baseName dirName=$dirName newFspec=$newFspec");

	if(!$forceOverwrite) {
		if(!(-e $newFspec && -w _ && -f _)) {
			FatalMsg("Unable overwrite [$newFspec]: mod bits prevent, Aborting!!\007");
		}
	}

	open TEST_NBR2,">$newFspec" or
		FatalMsg("Unable to backup to [$newFspec]: $!, Aborting!!\007");
	close TEST_NBR2;

	system("cp -p $filespec $newFspec");
	if(! -e $newFspec) {
		FatalMsg("copy of $filespec to $newFspec Failed, Aborting!!\007");
	}
}


##-----------------------------------------------------------------------------
## FUNCTION:
##   DoCmdRetOutput -- system() call with advanced ret-code analysis
##
## SYNOPSIS:
##   $ = DoCmdRetOutput($@@)
##
## EXAMPLE:
##   $retCode = DoCmdRetOutput($cmdStr,\@stdoutAr,\@stderrAr);
##
##-----------------------------------------------------------------------------
sub DoCmdRetOutput($@@)
{
	my $cmdStr = shift;
	local *stdoutAr = shift;
	local *stderrAr = shift;

	my $retCode = 0;	#  pre-clear return arrays
	@stdoutAr = ();
	@stderrAr = ();

	my $tmpDir = "/tmp/";
	if($^O =~ /win32/i) {
		if(exists $ENV{'TEMP'}) {
			$tmpDir = $ENV{'TEMP'};
			$tmpDir .= "\\";
		} else {
			$tmpDir = "";
		}
	}
	my $stdoutFname = "$tmpDir$::NAME.$$.out.tmp";
	my $stderrFname = "$tmpDir$::NAME.$$.err.tmp";
	unlink $stdoutFname if (-f $stdoutFname);
	unlink $stderrFname if (-f $stderrFname);

	#  add our output files to desired command
	$cmdStr .= " 1>$stdoutFname 2>$stderrFname";
	$retCode = DoCmd($cmdStr);

	#  load output of non-zero length
	if(-s $stdoutFname) {
		@stdoutAr = File2Array($stdoutFname);
	}
	if(-s $stderrFname) {
		@stderrAr = File2Array($stderrFname);
	}

	#  remove files if exist
	if (-f $stdoutFname && !defined $main::opt_debug) {
		unlink $stdoutFname or
			Msg(-err,"DoCmdRetOutput() Failed tmp-file remove:$!\n\t- [$stdoutFname]");
	}
	if (-f $stderrFname && !defined $main::opt_debug) {
		unlink $stderrFname or
			Msg(-err,"DoCmdRetOutput() Failed tmp-file remove:$!\n\t- [$stderrFname]");
	}

	return $retCode;
}


##-----------------------------------------------------------------------------
## FUNCTION:
##   doCmd -- system() call with advanced ret-code analysis
##
## SYNOPSIS:
##   $ = doCmd($)
##
## EXAMPLE:
##   $retCode = doCmd($cmdStr);
##
##-----------------------------------------------------------------------------
sub DoCmd($)
{
	my $cmdStr = shift;
	my $exeName;
	my $junk;
	($exeName,$junk) = split /\s+/,$cmdStr;
	if(! -x $exeName) {
		Msg(-err,"Required command NOT executable! [$exeName]");
		return 255;
	}
	if(defined $main::opt_nodo) {
		Msg("(NODO) would execute: [$cmdStr]");
		return 0;
	}
	my $retCode = 0xffff & system($cmdStr);
	my $msgTxt = sprintf("doCmd(): system(%s) returned rc=%#04x",$cmdStr,$retCode);
	DebugMsg($msgTxt);
	foreach ($retCode) {
		/0/ and do {
			DebugMsg(" - ran with normal exit");
			last;
		};
		/0xff00/ and do {
			DebugMsg(" - command failed: $!");
			last;
		};
		if($_ > 0x80) {
			my $actualRc = $_ >> 8;
			DebugMsg(" - ran with non-zero exit status: $actualRc");
		} else {
			my $extraTxt = "";
			if($_ & 0x80) {
				$_ &= ~0x80;
				$extraTxt = "coredump from ";
			}
			DebugMsg(" - ran with ${extraTxt}signal: $_");
		}
	}
	return $retCode / 256;
}


## ---------------------------------------------------------------------
##  FUNCTION: GeneralMkdir()
##
##	   mkdir wrapped with error reporting!  Exits if fails!
##
##  SYNOPSIS:
##   GeneralMkdir($)
##
##  EXAMPLE:
##   GeneralMkdir("/new/directory/to/make");
##
## ---------------------------------------------------------------------
sub GeneralMkdir($)
{
	my $dirToMake = shift;

	my $mkdirCmd = Cmd4thisOsRev("/bin/mkdir","/usr/bin/mkdir");
	if(! -d $dirToMake) {
		my $cmdTxt = "$mkdirCmd -p $dirToMake";
		if(DoCmd($cmdTxt)) {
			FatalMsg("Failed to create dir [$dirToMake]");
		}
		if(! -d $dirToMake) {
			FatalMsg("No error but No dir, either! [$dirToMake]");
		}
		DebugMsg("Created dir [$dirToMake]");
	}
}


## ---------------------------------------------------------------------
##  FUNCTION: GetUserNames()
##
##	   return real and effective user ID's
##
##  SYNOPSIS:
##   ($,$) = GetUserNames()
##
##  EXAMPLE:
##   ($realId,$effId) = GetUserNames();
##
## ---------------------------------------------------------------------
sub GetUserNames()
{
	my $realIdNbr = $<;

	my $realId = "?{win32}?";
	my $effId = "?{win32}?";
	my $effIdNbr = $>;

	if($^O !~ /win32/i) {
		$realId = (getpwuid($realIdNbr))[0];
		$effId = (getpwuid($effIdNbr))[0];
	}

	if(exists $ENV{'USERNAME'}) {
		$realId = $ENV{'USERNAME'};
		$effId = $realId;
		DebugMsg("tkpingLib::GetUserNames() ENV{USERNAME}: realId=[$realId] effId=[$effId]");
	}

	#
	#  Apparently 5.003 (and earlier?) for hpux... had problems
	#   identifying real-user-id  "$<"...
	#   So... we pickup $LOGNAME from the process environment instead!
	#		-stephen
	#
	if(exists $ENV{'LOGNAME'}) {
		$realId = $ENV{'LOGNAME'};
		DebugMsg("tkpingLib::GetUserNames() ENV{LOGNAME}: realId=[$realId]");
	}
	DebugMsg("tkpingLib::GetUserNames() realId=[$realId], effId=[$effId]");
	return ($realId, $effId);
}


## ---------------------------------------------------------------------
##  FUNCTION: Cmd4thisOsRev()
##
##	   id and return proper form of command for this HP-UX rev
##       terminate if NO correct one found!
##
##  SYNOPSIS:
##   $ = Cmd4thisOsRev($$);
##
##  EXAMPLE:
##   my $rmCmd = Cmd4thisOsRev("/bin/rm","/usr/bin/rm");
##
## ---------------------------------------------------------------------
sub Cmd4thisOsRev($$)
{
	my $cmdLocnA = shift;
	my $cmdLocnB = shift;

	my $properCmd = "";
	if(-x $cmdLocnA) {
		$properCmd = $cmdLocnA;
	} elsif(-x $cmdLocnB) {
		$properCmd = $cmdLocnB;
	} else {
		FatalMsg("tkpingLib::Cmd4thisOsRev() Failed to locate usable cmd [$cmdLocnA] or [$cmdLocnB], Aborted!");
	}
	return $properCmd;
}


## ---------------------------------------------------------------------
##  FUNCTION: EnsureDirExists()
##
##	   check for dir.  Fail if doesn't exist
##
##  SYNOPSIS:
##   EnsureDirExists($);
##
##  EXAMPLE:
##   EnsureDirExists($BINDIR,"BINDIR");
##
## ---------------------------------------------------------------------
sub EnsureDirExists($$)
{
	my $desiredDir = shift;
	my $varName = shift;

	DebugMsg("EnsureDirExists($varName=[$desiredDir])");
	FatalMsg("Directory NOT found [$desiredDir].\007")
		unless -d $desiredDir;
}


## ---------------------------------------------------------------------
##  FUNCTION: EnsureVariableExists()
##
##	   check for Environment Variables being set.  Fail if NOT or
##       return it's value if was.
##
##  SYNOPSIS:
##   $ = EnsureVariableExists($);
##
##  EXAMPLE:
##   $glbMyEnvVar = EnsureVariableExists('MYENVVAR');
##
## ---------------------------------------------------------------------
sub EnsureVariableExists($)
{
	my $envVarNm = shift;

	if(!exists $main::ENV{$envVarNm}) {
		FatalMsg("Required environment variable NOT set [$envVarNm], Aborting!\007");
	}
	my $value = $main::ENV{$envVarNm};
	DebugMsg("EnsureVariableExists() found $envVarNm=[$value]");
	return $value;
}

## ----------------------------------------------------------------------------
## FUNCTION: DoCmdRetSingleLine - system() call with advanced ret-code
##           analysis and single line returned from STDOUT.
##
## SYNOPSIS: $ = DoCmdRetSingleLine([-force,]$)
##
## EXAMPLE: ( $retCode, $oneLineRslt ) = DoCmdRetSingleLine($cmdStr);
##
## Stephen Moraco <stephen@cos.agilent.com>
## (written in 1998 while employed by Hewlett-Packard, Co.)
## ----------------------------------------------------------------------------
sub DoCmdRetSingleLine(@)
{
	my $rsltStr = "";

	local @txtAr = ();
	local @errAr = ();

	# note: pass all args on, including optional -force
	# note: Passing @_ doesn't work on PC, use a string

	my $cmd = join( " ", @_ );
	my $retCode = DoCmdRetOutput( $cmd, \@txtAr, \@errAr );
	if($retCode == 0) {
		my $txtCt = @txtAr;
		if($txtCt > 1) {
			DebugMsg("DoCmdRetSingleLine() more than one line ($txtCt) of output!");
			if($txtCt <= 5) {
				my $idx=0;
				foreach (@txtAr) {
					DebugMsg(" - RsltLn$idx: $_");
				}
			}
		} elsif(@txtAr == 1) {
			$rsltStr = $txtAr[0];
		}
	}
	DebugMsg("DoCmdRetSingleLine() rsltStr=[$rsltStr]");
	return( $retCode, $rsltStr );
}

## ----------------------------------------------------------------------------
## FUNCTION:
##   Array2Fdesc -- write array to named file descriptor (which must be open)
##
##  NOTE: appends newline to each element as it is written
##
## SYNOPSIS:
##   Array2Fdesc(FSPEC,\@arrayAr); # notice the array is passed by reference!
##
## ----------------------------------------------------------------------------
sub Array2Fdesc($@) {
    my $fdesc = shift;
    local *outAr = shift;
    local ($\, $,) = ("\n", "\n");
    print $fdesc @outAr;
}

## ----------------------------------------------------------------------------
## FUNCTION:
##   ArrayRef2File -- write array to named file, overwriting file if it exists
##                   (use BackupFile() to copy before overwrite)
##
##  NOTE: appends newline to each element as it is written to file
##
## SYNOPSIS:
##   ArrayRef2File($fileSpec,\@arrayAr); # notice array passed by reference!
##
## ----------------------------------------------------------------------------
sub ArrayRef2File($@)
{
    my $filespec = shift;
    local *argAr = shift;
    local ($\, $,) = ("\n", "\n");
    my $lineCt = @argAr;
    DebugMsg("ArrayRef2File() writing",
             "$lineCt array",
             ($lineCt == 1) ? "element" : "elements",
             "to $filespec");

    #  ensure our data file exists and open it
    open(AR_DST_FILE, ">$filespec") ||
        SwengFatalMsg("ArrayRef2File(): unable to write to [$filespec]: $!");
    return if (UTFatalOccurred());  # return if under unit-test!

    # do it fast
    Array2Fdesc(AR_DST_FILE, \@argAr);

    #  close our data file
    close(AR_DST_FILE) ||
        SwengFatalMsg("ArrayRef2File(): unable to close [$filespec]: $!");
    return if (UTFatalOccurred());  # return if under unit-test!
}


## ----------------------------------------------------------------------------
##  FUNCTION: RequireDir()
##
##     Check for directory pointed to by given symbol name.  Emit error if
##     it doesn't exist.  Compare with the older EnsureDirExists().
##     IMPORTANT: if symbol name is not fully qualified, e.g. main::BINDIR,
##     then main:: is assumed.
##     Returns 1 if OK; 0 if error.
##
##  SYNOPSIS:
##   RequireDir($);
##
##  EXAMPLE:
##   $rc = 0 if (!RequireDir("BINDIR"));
##
##   Note that '$rc &&= RequireDir("BINDIR")' won't allow calls to RequireDir
##   after first error!
##
## ----------------------------------------------------------------------------
sub RequireDir($)
{
    my $varName = shift;
    my $fullVarName = ($varName =~ /::/) ? $varName : "main::$varName";
    my $rc;

    if (!defined $$fullVarName) {
        Msg(-err, "RequireDir: variable [$varName] is not defined");
        Msg(-inf, "its value should be the name of an existing directory");
        DebugMsg(1, "fullVarName=[$fullVarName]");
        $rc = 0;
    } else {
        DebugMsg(1, "RequireDir($varName=[$$fullVarName])");
        $rc = (-d $$fullVarName) ? 1 : 0;
        Msg(-err,
            "$fullVarName [$$fullVarName] is not a directory") unless $rc;
    }
    return ($rc);
}


## ---------------------------------------------------------------------
##  FUNCTION: Assert()
##
##     First arg is a string representing an expr to eval.  If it fails,
##     the rest of the args are first passed to Msg(-inf) (one call for
##     each arg), then you get a FatalMsg() saying that the assertion
##     failed.
##
##  SYNOPSIS:
##   Assert($;@)
##
##  EXAMPLE:
##   # This asserts that $aString must have the given value.  If it
##   # doesn't, the actual value is first printed, then we get a fatal
##   # message.
##   # IMPORTANT: note that we have to use fully qualified symbol names!
##   Assert("(\main::$aString eq \"a value\")", "\$aString=[$aString]");
## ---------------------------------------------------------------------
sub Assert($;@)
{
    my $expr = shift;
    if (!eval $expr) {
        for (@_) {
            Msg(-inf, $_);
        }
        FatalMsg("Assertion failed: $expr");
    }
}



## ---------------------------------------------------------------------
##  FUNCTION: RequireParm()
##
##     extension for NGetOpt() -- forces a parm to be required
##
##  SYNOPSIS:
##   RequireParm($$$)
##
##   Given the name of the option, the global variable where the option
##   value is stored, and the documentation string to be used in an error
##   message if the parameter isn't set, will check that a parameter was
##   provided.  The name of option "someParm" will be converted to the
##   variable name $main::opt_someParm.
##
##   This works around an apparent bug in NGetOpt -- we don't trust
##   the "arg=s" syntax.
##
##   Returns 1 if OK; 0 if error.
##
##  EXAMPLE:
##
##   require "newgetopt.pl"; # defines NGetOpt()
##   $glbVariable = $NOTSET_STR; # first set to known bogus value
##   $rc = &NGetOpt("help","verbose","silent","nodo", # all standard
##                  "tag:s",\$glbTag); # our sample parm with a value
##
##   # We could also set a flag based on the return code and abort later.
##   FatalMsg("Aborting") if (!RequireParm("tag", $glbTag, "view tag"));
## ---------------------------------------------------------------------
sub RequireParm($$$)
{
    my ($optNm, $optValue, $optText) = @_;

    my $fullOptName = "main::opt_${optNm}";
    my $rc = 1;     # hope for the best

    #  detect error condition (non-renovated scripts!)
    if(defined $main::NOTSET_STR && $main::NOTSET_STR ne $NOTSET_STR) {
        Msg(-err,"Mismatched use of NOTSET_STR (now defined in tkpingLib.pm!");
        $rc = 0; # we failed
        return $rc;
    }

    #  If option is defined but value is not set, inform user that the
    #  value is required.
    #
    if (defined $$fullOptName || $optValue ne $NOTSET_STR) {
        if ($optValue eq $NOTSET_STR || $optValue eq "") {
            Msg(-err, "Option -$optNm requires $optText parameter");
            $rc = 0; # we failed
        } else {
            # User provided value, so define option so we can use it to
            # detect if value is set.
            $$fullOptName = 1;
        }
    }
    return $rc;
}


##-----------------------------------------------------------------------------
##  SPECIAL end-of-file terminator -- DO NOT REMOVE
##-----------------------------------------------------------------------------
1;	# reply OK to 'require or use'
