# shellcheck disable=SC2148 disable=SC2034
#==============================================================================
# Copyright and license info is available in the LICENSE file included with
# the Server Deployment Package (SDP), and also available online:
# https://swarm.workshop.perforce.com/projects/perforce-software-sdp/view/main/LICENSE
#------------------------------------------------------------------------------

#------------------------------------------------------------------------------
# Version ID Block. Relies on +k filetype modifier.
# VersionID='$Id: //p4-sdp/dev_c2s/Server/Unix/p4/common/lib/libcore.sh#2 $ $Change: 31472 $'

#==============================================================================
# Library Functions.

#------------------------------------------------------------------------------
# Function: bail
#
# Input:
# $1 - error message, displayed to stderr.
# $2 - Return code, default is 1
#------------------------------------------------------------------------------
function bail {
   declare msg=${1:-Unknown Error}
   declare -i rc=${2:-1}

   echo -e "\n$THISSCRIPT (line: ${BASH_LINENO[0]}): FATAL: $msg\n\n" >&2

   exit "$rc"
}

#------------------------------------------------------------------------------
# Function: initlog
#------------------------------------------------------------------------------
function initlog
{
   [[ $VERBOSITY -gt 1 ]] && \
      echo -e "${H}\n$THISSCRIPT started at $(date) as pid $$:\nInitial Command Line:\n$CMDLINE\nLog file is: ${P4U_LOG}\n\n"
}

#------------------------------------------------------------------------------
# Function: stoplog
function stoplog
{
   sleep 1
   [[ $VERBOSITY -gt 1 ]] && \
      echo -e "\n$THISSCRIPT stopped at $(date).\n\nLog file is: ${P4U_LOG}${H}\n"
}

#------------------------------------------------------------------------------
# Function: errmsg
#
# Description: Display error message on stderr, regardless of $VERBOSITY value.
#
# Input:
# $1 - error message
#------------------------------------------------------------------------------
function errmsg {
 
   echo -e "$THISSCRIPT: ERROR: ${1:-Unknown Error}\n" >&2
}

#------------------------------------------------------------------------------
# Function: warnmsg
#
# Description: Display a warning message, but only if $VERBOSITY > 1.
#
# Input:
# $1 - warning message
#------------------------------------------------------------------------------
function warnmsg {

   [[ $VERBOSITY -gt 1 ]] && echo -e "$THISSCRIPT: WARNING: $1\n"
}

#------------------------------------------------------------------------------
# Function: qmsg
#
# Description:  Display a quiet message.  Uses the VERBOSITY env var, and
# displays messages only if $VERBOSITY > 1.
#
# Input:
# $1 - message
#------------------------------------------------------------------------------
function qmsg {

   [[ $VERBOSITY -gt 1 ]] && echo -e "$*\n"
}

#------------------------------------------------------------------------------
# Function: msg
#
# Description:  Display a normal message.  Uses the VERBOSITY env var, and
# displays messages only if $VERBOSITY > 2.
#
# Input:
# $1 - message
#------------------------------------------------------------------------------
function msg {

   [[ $VERBOSITY -gt 2 ]] && echo -e "$*\n"
}

#------------------------------------------------------------------------------
# Function: vmsg
#
# Description:  Display a verbose message.  Uses the VERBOSITY env var, and
# displays messages only if $VERBOSITY > default 3.
#
# Input:
# $1 - message
#------------------------------------------------------------------------------
function vmsg {
   [[ $VERBOSITY -gt 3 ]] && echo -e "$*\n"
}

#------------------------------------------------------------------------------
# Function: vvmsg
#
# Description:  Display a very verbose message.  Uses the VERBOSITY env var, and
# displays messages only if $VERBOSITY > default 4.
#
# Input:
# $1 - message
#------------------------------------------------------------------------------
function vvmsg {
   [[ $VERBOSITY -gt 4 ]] && echo -e "$*\n"
}

#------------------------------------------------------------------------------
# Function: cleanTrash
#
# Usage: During operation of your program, append the $GARBAGE variable
# with garbage file or directory names you want to have cleaned up
# automatically on exit, e.g.:
#    GARBAGE+=" $MyTmpFile"
#
# The $P4U_TMPDIR is a guaranteed unique directory created by 'mktemp -d',
# and is automatically added to $GARBAGE.  So creating temp files in
# that directory will get them cleaned up on exit without needing to
# add them to $GARBAGE explicitly, e.g.:
#
# MyTempFile=$P4U_TMPDIR/gen_file.txt
#
# Specify absolute paths for garbage files, or else ensure that the paths
# specified will be valid when the 'rm -rf' command is run.
#
# To specify remote garbage to clean up, append the $RGARBAGE variable,
# e.g.
#    RGARBAGE+=" $other_host:$tmpFile"
#
# Input:
# $1 - Optionally specify 'verbose'; otherwise this routine does its
#      work silently.
#------------------------------------------------------------------------------
function cleanTrash {
   declare vmode=${1:-silent}
   declare v=$VERBOSITY

   [[ "$vmode" == "silent" ]] && export VERBOSITY=1

   if [[ -n "$RGARBAGE" ]]; then
      for rFile in $RGARBAGE; do
         rHost=${rFile%%:*}
         rFile=${rFile##*:}
         rrun "$rHost" "/bin/rm -f $rFile" "Cleaning up remote garbage file [$rHost:$rFile].\n"
      done
   fi

   if [[ -n "$GARBAGE" ]]; then
      vvmsg "Cleaning up garbage files."
      # shellcheck disable=SC2086
      /bin/rm -rf $GARBAGE
   fi

   export VERBOSITY=$v
}

#------------------------------------------------------------------------------
# Function: run
#
# Short: Run a command with optional description, honoring $VERBOSITY
# and $NO_OP settings.  Simpler than runCmd().
#
# Input:
# $1 - cmd.  The command to run.  The command is displayed first
#      if $VERBOSITY > 3.
# $2 - desc.  Text description of command to run.
#      Optionally, the text string can be prefixed with 'N:', where N is
#      a one-digit integer, the minimum verbosity at which the description is to
#      be displayed.
#      Optionally, a series of text strings can be provided, delimited by '|',
#      allowing multiple descriptions to be provided, each with a different
#      minimum verbosity setting.
#      This parameter is optional.  If the N: prefix is omitted, it is
#      equivalent to "3:".  Sample values: "5:Cool command here:",
#      and "3:Regular verbosity message|4:Higher verbosity message."
#      and "This is a very|long description."
# $3 - honorNoOpFlag.  Pass in 1 to mean "Yes, honor the $NO_OP setting
#      and display (but don't run) commands if $NO_OP is set."  Otherwise
#      $NO_OP is ignored, and the command is always run.
#      This parameter is optional; the default value is 1.
# $4 - alwaysShowOutputFlag.  If set to 1, show output regardless of $VERBOSITY
#      value.  Otherwise, only show output if VERBOSITY > 4.
#      This parameter is optional; the default value is 0.
# $5 - grepString.  If set, this changes the exit code behavior.
#      If the specified string exists in the output, a 0 is returned, else 1.
#      Strings suitable for use with 'egrep' are allowed.
#
# Description:
#    Display an optional description of a command, and then run the
# command.  This is affected by $NO_OP and $VERBOSITY.  If $NO_OP is
# set, the command is shown, but not run, provided $honorNoOpFlag is 1.
# The description is not shown if $VERBOSITY < 3.
#
# The variables CMDLAST and CMDEXITCODE are set each time runCmd is called.
# CMDLAST contains the last command run. CMDEXITCODE contains its exit code.
#
# Output is shown if either $AlwaysShowOutputFlag is 1 or $VERBOSITY >= 4.
#------------------------------------------------------------------------------
function run () {
   vvmsg "CALL: run ($*)"
   declare cmd=${1:-}
   declare desc=${2:-}
   declare -i honorNoOpFlag=${3:-1}
   declare -i alwaysShowOutputFlag=${4:-0}
   declare grepString=${5:-}
   declare cmdScript=$P4U_TMPDIR/cmd.sh
   declare cmdOut=$P4U_TMPDIR/script.out
   declare -i grepExit

   CMDLAST=$cmd
   CMDEXITCODE=0

   if [[ -n "$desc" ]]; then
      # Descriptions intended to be multi-line contain a '|' char.
      # Each entry may contain an optional 'N:' prefix where N is the
      # verbosity level at or above which the message is displayed.
      echo "$desc" | tr '|' '\n' | while read -r text; do
         if [[ $text =~ ^[0-9]+: ]]; then
            descVerbosity=${text%%:*}
            text=${text#$descVerbosity:}
            if [[ $VERBOSITY -ge $descVerbosity ]]; then
               echo -e "$text"
            fi
         else
            msg "$desc"
         fi
      done
   fi

   if [[ $honorNoOpFlag -eq 1 && $NO_OP -eq 1 ]]; then
      vmsg "NO-OP: Would run: \"$cmd\"\n"
   else
      vmsg "Running: \"$cmd\"."

      echo -e "#!/bin/bash\n$cmd\n" > "$cmdScript"
      chmod +x "$cmdScript"
      $cmdScript > "$cmdOut" 2>&1
      CMDEXITCODE=$?

      if [[ -n "$grepString" ]]; then
         # shellcheck disable=SC2196
         egrep "$grepString" "$cmdOut" > /dev/null 2>&1
         grepExit=$?
         CMDEXITCODE="$grepExit"
      fi

      if [[ $alwaysShowOutputFlag -eq 1 ]]; then
         cat "$cmdOut"
      else
         [[ $VERBOSITY -gt 3 ]] && cat "$cmdOut"
      fi

      # Be clean and tidy.
      /bin/rm -f "$cmdScript" "$cmdOut"

      # If a grep was requested, return the exit code from the egrep,
      # otherwise return the exit code of the command executed.  In
      # any case, $CMDEXITCODE contains the exit code of the command.
      if [[ -n "$grepString" ]]; then
         return $grepExit
      else
         return $CMDEXITCODE
      fi
   fi

   return 0
}

#------------------------------------------------------------------------------
# Function: rrun
#
# Short: Run a command with on a remote host optional description, honoring
# $VERBOSITY and $NO_OP settings.  Simpler than runRemoteCmd().
#
# Input:
# $1 - host.  The remote host to run the command on.
# $2 - cmd.  The command to run.  The command is displayed first
#      if $VERBOSITY > 3.
# $3 - desc.  Text description of command to run.
#      Optionally, the text string can be prefixed with 'N:', where N is
#      a one-digit integer, the minimum verbosity at which the description is to
#      be displayed.
#      Optionally, a series of text strings can be provided, delimited by '|',
#      allowing multiple descriptions to be provided, each with a different
#      minimum verbosity setting.
#      This parameter is optional.  If the N: prefix is omitted, it is
#      equivalent to "3:".  Sample values: "5:Cool command here:"
# $4 - honorNoOpFlag.  Pass in 1 to mean "Yes, honor the $NO_OP setting
#      and display (but don't run) commands if $NO_OP is set."  Otherwise
#      $NO_OP is ignored, and the command is always run.
#      This parameter is optional; the default value is 1.
# $5 - alwaysShowOutputFlag.  If set to 1, show output regardless of $VERBOSITY
#      value.  Otherwise, only show output if VERBOSITY > 4.
#      This parameter is optional; the default value is 0.
# $6 - grepString.  If set, this changes the exit code behavior.
#      If the specified string exists in the output, a 0 is returned, else 1.
#      Strings suitable for use with 'egrep' are allowed.
#
# Description:
#    Display an optional description of a command, and then run the
# command.  This is affected by $NO_OP and $VERBOSITY.  If $NO_OP is
# set, the command is shown, but not run, provided $honorNoOpFlag is 1.
# The description is not shown if $VERBOSITY < 3.
#
# The variables RCMDLAST and RCMDEXITCODE are set each time runCmd is called.
# RCMDLAST contains the last command run. RCMDEXITCODE contains its exit code.
#
# Output is shown if either $AlwaysShowOutputFlag is 1 or $VERBOSITY >= 4.
#------------------------------------------------------------------------------
function rrun () {
   vvmsg "CALL: rrun ($*)"
   declare host=${1:-Unset}
   declare cmd=${2:-Unset}
   declare desc=${3:-}
   declare -i honorNoOpFlag=${4:-1}
   declare -i alwaysShowOutputFlag=${5:-0}
   declare grepString=${6:-}
   declare rCmdScript="${P4TMP:-/tmp}/rcmd.$$.${RANDOM}${RANDOM}.sh"
   declare rCmdOut=$P4U_TMPDIR/rcmd.out
   declare -i grepExit

   RCMDLAST="$cmd"
   RCMDEXITCODE=0

   if [[ -n "$desc" ]]; then
      echo "$desc" | tr '|' '\n' | while read -r text; do
         if [[ $text =~ ^[0-9]+: ]]; then
            descVerbosity=${text%%:*}
            text=${text#$descVerbosity:}
            if [[ $VERBOSITY -ge $descVerbosity ]]; then
               echo -e "$text"
            fi
         else
            msg "$desc"
         fi
      done
   fi

   if [[ $honorNoOpFlag -eq 1 && $NO_OP -eq 1 ]]; then
      vmsg "NO-OP: Would run: \"$cmd\" on host $host.\n"
   else
      vmsg "Running: \"$cmd\" on host $host."

      echo -e "#!/bin/bash\n$cmd\n" > "$rCmdScript"
      chmod +wx "$rCmdScript"
      if ! scp -pq "$rCmdScript" "$host:${P4TMP:-/tmp}/."; then
         RCMDEXITCODE=-1
         errmsg "rrun(): Failed to copy temp command script to $host."
         return 1
      fi

      ssh -q -n "$host" "$rCmdScript" > "$rCmdOut" 2>&1
      RCMDEXITCODE=$?

      if [[ -n "$grepString" ]]; then
         # shellcheck disable=SC2196
         egrep "$grepString" "$rCmdOut" > /dev/null 2>&1
         grepExit=$?
         RCMDEXITCODE="$grepExit"
      fi

      if [[ $alwaysShowOutputFlag -eq 1 ]]; then
         cat "$rCmdOut"
      else
         [[ $VERBOSITY -gt 3 ]] && cat "$rCmdOut"
      fi

      # Be clean and tidy.
      /bin/rm -f "$rCmdScript" "$rCmdOut"

      # If a grep was requested, return the exit code from the egrep,
      # otherwise return the exit code of the command remotely executed.
      # In any case, $RCMDEXITCODE contains the exit code of the command.
      if [[ -n "$grepString" ]]; then
         return $grepExit
      else
         return $RCMDEXITCODE
      fi
   fi

   return 0
}

#------------------------------------------------------------------------------
# Function: runCmd
#
# Short: Run a command with with optional description, honoring $VERBOSITY
# and $NO_OP settings.
#
# Input:
# $1 - cmd.  The command to run.  The command is displayed first
#      if $VERBOSITY > 3.
# $2 - textDesc.  Text description of command to run. This is displayed
#      if $VERBOSITY > 2.
#      This parameter is optional.
# $3 - honorNoOpFlag.  Pass in 1 to mean "Yes, honor the $NO_OP setting
#      and display (but don't run) commands if $NO_OP is set."  Otherwise
#      $NO_OP is ignored, and the command is always run.
#      This parameter is optional; the default value is 1.
# $4 - ShowOutputFlag.  If set to 1, show output regardless of $VERBOSITY
#      value.  Otherwise, only show output if VERBOSITY > 4.
#      This parameter is optional; the default value is 1.
# $5 - CaptureOutputFlag.  If set to 1, attempt to capture the output,
#      otherwise, do not.  The default is 1.  This should be set to 0
#      in cases where commands generate large amounts of output that do
#      not require further processing or parsing.  Regardless of the
#      value specified, the output of commands containing redirection
#      operators '<' and/or '>'  are not captured.
#
# Description:
#    Display an optional description of a command, and then run the
# command.  This is affected by $NO_OP and $VERBOSITY.  If $NO_OP is
# set, the command is shown, but not run, provided $honorNoOpFlag is 1.
# The description is not shown if $VERBOSITY < 3.
#
# The variables CMDLAST, CMDEXITCODE, and CMDOUTPUT are set each time
# runCmd is called, containing the last command run, its exit code and
# output in string and file forms.
#
# If the command contains redirect operators '>' or '<', it is executed
# by being written as a generated temporary bash script.  The CMDOUTPUT
# value is set to "Output Not Captured." in that case, or if
# CaptureOutputFlag is set to 0.
#
# Usage Example:
#    Run the 'ls' command on $path, and bail if the return status of the
# executed command is non-zero, and run it even if $NO_OP is set:
#
#   runCmd "ls $path" "Contents of [$path]:" 0 || bail "Couldn't ls [$path]."
#
#------------------------------------------------------------------------------
function runCmd {
   declare cmd=$1
   declare textDesc=${2:-""}
   declare -i honorNoOpFlag=${3:-1}
   declare -i showOutputFlag=${4:-1}
   declare -i captureOutputFlag=${5:-1}
   declare tmpScript=

   # shellcheck disable=SC2034
   CMDLAST=$cmd
   CMDEXITCODE=0
   CMDOUTPUT=""

   [[ -n "$textDesc" ]] && msg "$textDesc"

   if [[ $honorNoOpFlag -eq 1 && $NO_OP -eq 1 ]]; then
      vmsg "NO-OP: Would execute: \"$cmd\"\n"
   else
      vmsg "Executing: \"$cmd\"."

      # Execute the command, and immediately capture the return status.
      # Capture output if $captureOutputFlag is 1 and there are no redirect
      # operators.
      if [[ $captureOutputFlag -eq 0 || $cmd == *"<"* || $cmd == *">"* ]]; then
         tmpScript="$P4U_TMPDIR/cmd.sh"
         echo -e "#!/bin/bash\n$cmd\n" > "$tmpScript"
         chmod +x "$tmpScript"
         $tmpScript
         CMDEXITCODE=$?
         CMDOUTPUT="Output Not Captured."
         /bin/rm -f "$tmpScript"
      else
         CMDOUTPUT=$($cmd 2>&1)
         CMDEXITCODE=$?
      fi

      [[ $showOutputFlag -eq 1 ]] && msg "\n$CMDOUTPUT\nEXIT_CODE: $CMDEXITCODE\n"
      return $CMDEXITCODE
   fi

   return 0
}

#------------------------------------------------------------------------------
# Function: runRemoteCmd
#
# Short: Run a remote command on another host, with functionality otherwise
# similar to runCmd().  Execute by generating a temporary remote /bin/bash
# script to insulate against shell compatibility issues, so things work as
# expected even if the default login shell is a foreign shell like tcsh.
#
# Input:
# $1 - host.  Specify the remote hostname.  Note that SSH keys must be
#      configured to allow remote execution without a password for
#      automation of remote processing.
# $2 - cmd.  The command to run.  The command is displayed first
#      if $VERBOSITY > 3.
# $3 - textDesc.  Text description of command to run. This is displayed
#      if $VERBOSITY > 2.
#      This parameter is optional.
# $4 - honorNoOpFlag.  Pass in 1 to mean "Yes, honor the $NO_OP setting
#      and display (but don't run) commands if $NO_OP is set."  Otherwise
#      $NO_OP is ignored, and the command is always run.
#      This parameter is optional; the default value is 1.
# $5 - ShowOutputFlag.  If set to 1, show output regardless of $VERBOSITY
#      value.  Otherwise, only show output if VERBOSITY > 4.
#      This parameter is optional; the default value is 1.
# $6 - CaptureOutputFlag.  If set to 1, attempt to capture the output,
#      otherwise, do not.  The default is 1.  This should be set to 0
#      in cases where commands generate large amounts of output that do
#      not require further processing or parsing.
#
# Description:
#    Display an optional description of a command, and then run the
# command on a remote host.  This is affected by $NO_OP and $VERBOSITY.
# If $NO_OP is set, the command is shown, but not run, provided
# $honorNoOpFlag is 1. The description is not shown if $VERBOSITY <3.
#
# The variables RCMDLAST, RCMDEXITCODE, RCMDOUTPUT are set each time this
# is run, containing the last command run, its exit code and output.
#
# The RCMDOUTPUT value is set to "Output Not Captured." if the
# CaptureOutputFlag is set to 0.
#
# To insulate against shell incompatibilities and the default login shell
# possibly being something other than bash, a bash script is generated
# and then executed on the remote host.
#
# Usage Example:
#    Run the 'ls' command on $path, and bail if the return status of the
# executed command is non-zero, and run it even if $NO_OP is set:
#
#   runRemoteCmd scm02 "ls $path" "In [$path]:" 0 || bail "Couldn't ls [$path]."
#
#------------------------------------------------------------------------------
function runRemoteCmd {
   declare host=$1
   declare rCmd=$2
   declare textDesc=${3:-""}
   declare -i honorNoOpFlag=${4:-1}
   declare -i showOutputFlag=${5:-1}
   declare -i captureOutputFlag=${6:-1}
   declare remoteScript="${P4TMP:-/tmp}/runRemoteCmd.$$.${RANDOM}${RANDOM}.sh"
   declare outputFile="${P4TMP:-/tmp}/remoteCmdOutput.$$.${RANDOM}${RANDOM}.out"
   declare ec=""

   # shellcheck disable=SC2034
   RCMDLAST=${rCmd}
   RCMDEXITCODE=0
   RCMDOUTPUT=""

   [[ -n "$textDesc" ]] && msg "$textDesc"

   if [[ $honorNoOpFlag -eq 1 && $NO_OP -eq 1 ]]; then
      vmsg "NO-OP: Would execute: \"$rCmd\"\n"
   else
      vmsg "Executing: \"$rCmd\" on remote host ${host}."

      # Generate a script with the command to execute on the remote host.
      echo -e "#!/bin/bash\n$rCmd\necho REMOTE_EXIT_CODE: \$?\n" > "$remoteScript"
      chmod +wx "$remoteScript"
      vvmsg "Script to execute [$remoteScript]:\n$(cat "$remoteScript")\n"

      runCmd "scp -pq $remoteScript ${host}:${remoteScript}" || \
         bail "Failed to copy script to remote host!"

      ( ssh -q -n "$host" "$remoteScript" ) > "$outputFile" 2>&1 &

      # shellcheck disable=SC2078
      while [[ /bin/true ]]; do
         sleep 2
         #vvmsg "Checking remote process log [$outputFile]."
         if ec=$(grep -a "REMOTE_EXIT_CODE:" "$outputFile"); then
            ec=${ec##*REMOTE_EXIT_CODE: }
            ec=${ec%% *}
	    RCMDEXITCODE=$ec
            break
         fi
      done

      if [[ $showOutputFlag -eq 1 ]]; then
         cat "$outputFile"
      fi

      if [[ $captureOutputFlag -eq 1 ]]; then
         RCMDOUTPUT="$(cat "$outputFile")"
      else
         # shellcheck disable=SC2034
         RCMDOUTPUT="Output Not Captured."
      fi

      ssh -q "$host" /bin/rm -f "$remoteScript"
      [[ -e "$outputFile" ]] && rm -f "$outputFile"
   fi

   return 0
}

#------------------------------------------------------------------------------
# Function: usageError (usage error message)
#
# $1 - message
#------------------------------------------------------------------------------
function usageError {

   echo -e "$THISSCRIPT: ERROR: $1\n"
   usage -h
}

#------------------------------------------------------------------------------
# Append to PATH variable, removing duplicate entries.
# NOT CURRENTLY FUNCTIONAL!
#------------------------------------------------------------------------------
function appendPath {
   declare myPath=$1
   declare newPath=
   declare -A paths=
   local IFS=:
   # shellcheck disable=SC2116
   for e in $(echo "$PATH:$myPath"); do
      if [[ -z "${paths[$e]}" ]]; then
         newPath="$newPath:$e"
         paths[$e]=1
      fi
   done
   # shellcheck disable=SC2116 disable=SC2155
   export PATH=$(echo "$newPath")
}

#------------------------------------------------------------------------------
# Prepend to PATH variable, removing duplicate entries.
# NOT CURRENTLY FUNCTIONAL!
#------------------------------------------------------------------------------
function prependPath {
   declare myPath=$1
   declare newPath=
   declare -A paths=
   local IFS=:
   # shellcheck disable=SC2116 disable=SC2155
   for e in $(echo "$myPath:$PATH"); do
      if [[ -z "${paths[$e]}" ]]; then
         newPath="$newPath:$e"
         paths[$e]=1
      fi
   done
   # shellcheck disable=SC2116 disable=SC2155
   export PATH=$(echo "$newPath")
}

#------------------------------------------------------------------------------
# Clean the PATH variable by removing duplicate entries.
# NOT CURRENTLY FUNCTIONAL!
#------------------------------------------------------------------------------
function cleanPath {
   declare newPath=
   declare -A paths=
   local IFS=:
   # shellcheck disable=SC2116
   for e in $(echo "$PATH"); do
      if [[ -z "${paths[$e]}" ]]; then
         newPath="$newPath:$e"
         paths[$e]=1
      fi
   done
   # shellcheck disable=SC2116 disable=SC2155
   export PATH=$(echo "$newPath")
}

#------------------------------------------------------------------------------
# Function rotate_default_log()
# Rotates and optionally compresses the default log ($P4U_LOG).
# Args:
# $1 - CompressionStyle. Values are:
# 0 - No compression (the default).
# 1 - Compress with gzip
# 2 - Compress with bzip2 --best
#------------------------------------------------------------------------------
function rotate_default_log {
   declare compressionStyle=${1:-0}
   declare newLog=

   if [[ "$P4U_LOG" != "off" ]]; then
      if [[ -e "$P4U_LOG" ]]; then
         declare -i i=1
         while [[ -e "$P4U_LOG.$i" ]]; do i=$((i+1)); done
         newLog="${P4U_LOG}.$i"
         mv "$P4U_LOG" "$newLog"
         if ((compressionStyle == 1)); then
            /usr/bin/gzip "$newLog"
         elif ((compressionStyle == 2)); then
            /usr/bin/bzip2 --best "$newLog"
         fi
      fi
   fi
}

#------------------------------------------------------------------------------
# Function: show_versions
#
# Show the version of our script plus any imported library that identifies it
# version in the expected way, with a "Version=" def'n (or "declare Version=").
# Silently ignore files that do not identify their version this way.
function show_versions
{
   declare v=

   for bash_lib in $0 $BASH_LIBS; do
      # shellcheck disable=SC2196
      v=$(egrep -i "^(declare Version|Version)=" "$bash_lib" | head -1)
      [[ -n "$v" ]] || continue
      v=${v#*=}
      echo "$bash_lib v$v"
   done

   echo "BASH_VERSION: $BASH_VERSION"
}

