EvilTwinDetector.sh #1

  • //
  • guest/
  • perforce_software/
  • sdp/
  • dev/
  • Maintenance/
  • EvilTwinDetector.sh
  • View
  • Commits
  • Open Download .zip Download (11 KB)
#!/bin/bash
#==============================================================================
# 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
#------------------------------------------------------------------------------

#==============================================================================
# Declarations and Environment

export P4U_HOME=${P4U_HOME:-/p4/common/bin}
export P4U_LIB=${P4U_LIB:-/p4/common/lib}
export P4U_ENV=$P4U_LIB/p4u_env.sh
export P4U_LOG=Unset
export VERBOSITY=${VERBOSITY:-3}

# Environment isolation.  For stability and security reasons, prepend
# PATH to include dirs where known-good scripts exist.
# known/tested PATH and, by implication, executables on the PATH.
export PATH=$P4U_HOME:$PATH:~/bin:.

[[ -r "$P4U_ENV" ]] || {
   echo -e "\nError: Cannot load environment from: $P4U_ENV\n\n"
   exit 1
}

declare BASH_LIBS=$P4U_ENV
BASH_LIBS+=" $P4U_LIB/libcore.sh"
BASH_LIBS+=" $P4U_LIB/libp4u.sh"

for bash_lib in $BASH_LIBS; do
   source $bash_lib ||\
      { echo -e "\nFATAL: Failed to load bash lib [$bash_lib]. Aborting.\n"; exit 1; }
done

declare Version=1.0.4
declare -i SilentMode=0
export VERBOSITY=3

#==============================================================================
# Local Functions

# Function: find_evil_twins
function find_evil_twins ()
{
   declare p1=${1:-Unset}
   declare p2=${2:-Unset}
   declare s2=
   declare -i count
   [[ $p1 == Unset || $p2 == Unset ]] && bail "find_evil_twins(): BAD USAGE."
   s2=${p2%/...}

   vmsg "Switching workspace to stream $s2"
   p4 client -f -s -S "$s2"

   msg "Searching for evil twins between [$p1] and [$p2]:"
   vmsg "Executing: p4 integ -Ro -n $p1 $p2"

   p4 -s integ -Ro -n "$p1" "$p2" | grep "without -i flag" > $TmpFile 2>&1 || return 1

   count=0

   if [[ -s "$TmpFile" ]]; then
      count=$(wc -l $TmpFile | cut -d ' ' -f 1)
      msg "Found $count evil twins here:"
      cat $TmpFile
      EvilTwinCount=$((EvilTwinCount+count))
   fi

   return 0
}

#------------------------------------------------------------------------------
# Function: terminate
function terminate
{
   # Disable signal trapping.
   trap - EXIT SIGINT SIGTERM

   # Don't litter.
   cleanTrash

   vvmsg "$THISSCRIPT: EXITCODE: $OverallReturnStatus"

   # Stop logging.
   [[ "${P4U_LOG}" == off ]] || stoplog

   # With the trap removed, exit.
   exit $OverallReturnStatus
}

#------------------------------------------------------------------------------
# Function: usage (required function)
#
# Input:
# $1 - style, either -h (for short form) or -man (for man-page like format).
#------------------------------------------------------------------------------
function usage
{
   declare style=${1:--h}

   echo "USAGE for $THISSCRIPT v$Version:

$THISSCRIPT -d <stream_depot> [-s //source/stream] [-t //target/stream] [-i <instance>] [-L <log>] [-si] [-v<n>] [-n] [-D]

or

$THISSCRIPT [-h|-man|-V]
"
   if [[ $style == -man ]]; then
      echo -e "
DESCRIPTION:
	Detect \"evil twins\" in a given stream depot.

OPTIONS:
 -d <stream_sdepot>
	Specify a stream depot to process, e.g. -d fgs to process the //fgs stream depot.

	This argument is required.

 -s //source/stream
	Specify a particular source stream to search.

	By default, all non-virtual streams are checked.

	Be aware that if neither -s nor -t are given, a very large numbers of streams may be checked,
	as all possible combinations of source and target stream are checked.

 -t //target/stream
	Specify a particular target stream to search.

	By default, all non-virtual streams are checked.

	Be aware that if neither -s nor -t are given, a very large numbers of streams may be checked,
	as all possible combinations of source and target stream are checked.

 -i <instance>
	Specify the SDP instance name.

	This is required unless the SDP environment has previously been loaded in the current
	shell, i.e. by sourcing the SDP p4_vars file and specifying the instance.

 -v<n>	Set verbosity 1-5 (-v1 = quiet, -v5 = highest).

 -L <log>
	Specify the path to a log file, or the special value 'off' to disable
	logging.  All output (stdout and stderr) are captured in the log.

	NOTE: This script is self-logging.  That is, output displayed on the screen
	is simultaneously captured in the log file.  Do not run this script with
	redirection operators like '> log' or '2>&1', and do not use 'tee.'

-si	Operate silently.  All output (stdout and stderr) is redirected to the log
	only; no output appears on the terminal.  This cannot be used with
	'-L off'.
      
 -n	No-Op.  Prints commands instead of running them.

 -D     Set extreme debugging verbosity.

HELP OPTIONS:
 -h	Display short help message
 -man	Display man-style help message
 -V	Dispay version info for this script and its libraries.

EXAMPLES:
	Search for evil twins between streams //fgs/DevA and //fgs/DevB.
	$THISSCRIPT -s //fgs/DevA -t //fgs/DevB

	Search for all possible evil twins in the //fgs stream depot:
	$THISSCRIPT -d fgs

"
   fi

   exit 1
}

#==============================================================================
# Command Line Processing

declare StreamListFile=/tmp/tmp.StreamList.$$.$RANDOM
declare SearchedPathsFile=/tmp/tmp.SearchedPaths.$$.$RANDOM
declare TmpFile=/tmp/tmp.EvilTwins.$$.$RANDOM
declare StreamDepot=Unset
declare SourceStream=Unset
declare SourcePath=
declare TargetStream=Unset
declare TargetPath=
declare DepotTypeCheck=Unset
declare -i PathwayCount=0
declare -i EvilTwinCount=0
GARBAGE+=" $StreamListFile $SearchedPathsFile $TmpFile"

declare -i shiftArgs=0

set +u
while [[ $# -gt 0 ]]; do
   case $1 in
      (-s) SourceStream=$2; shiftArgs=1;;
      (-t) TargetStream=$2; shiftArgs=1;;
      (-d) StreamDepot=$2; shiftArgs=1;;
      (-h) usage -h;;
      (-man) usage -man;;
      (-V) show_versions; exit 1;;
      (-v1) export VERBOSITY=1;;
      (-v2) export VERBOSITY=2;;
      (-v3) export VERBOSITY=3;;
      (-v4) export VERBOSITY=4;;
      (-v5) export VERBOSITY=5;;
      (-i) export SDP_INSTANCE=$2; shiftArgs=1;;
      (-L) export P4U_LOG=$2; shiftArgs=1;;
      (-si) SilentMode=1;;
      (-n) export NO_OP=1;;
      (-D) set -x;; # Debug; use 'set -x' mode.
      (*) usageError "Unknown arg ($1).";;
   esac

   # Shift (modify $#) the appropriate number of times.
   shift; while [[ $shiftArgs -gt 0 ]]; do
      [[ $# -eq 0 ]] && usageError "Bad usage."
      shiftArgs=$shiftArgs-1
      shift
   done
done
set -u

#==============================================================================
# Command Line Verification

[[ $SilentMode -eq 1 && $P4U_LOG == off ]] && \
   usageError "Cannot use '-si' with '-L off'."

if [[ "$SourceStream" != Unset ]]; then
   [[ "$SourceStream" != "//"* ]] && \
      usageError "The source stream specified with '-s' must be of the form //depot/stream."

   # Determine stream depot from source stream if it wasn't provided with '-d'.
   if [[ $StreamDepot == Unset ]]; then
      StreamDepot=${SourceStream#//}
      StreamDepot=${StreamDepot%%/*}
   fi
fi

if [[ "$TargetStream" != Unset ]]; then
   [[ "$TargetStream" != "//"* ]] && \
      usageError "The target stream specified with '-t' must be of the form //depot/stream."

   # Determine stream depot from target stream if it wasn't provided with '-d'.
   if [[ $StreamDepot == Unset ]]; then
      StreamDepot=${TargetStream#//}
      StreamDepot=${StreamDepot%%/*}
   fi
fi

[[ $StreamDepot == Unset ]] && \
   usageError "The '-d <stream_depot>' paramter is required."

export SDP_INSTANCE=${SDP_INSTANCE:-Unset}
[[ $SDP_INSTANCE == Unset ]] && \
   usageError "The SDP environment must be loaded, or the '-i <instance>' parameter provided."

# Load and then tweak SDP environment.
source p4_vars "$SDP_INSTANCE"
export P4ENVIRO=/dev/null/.p4enviro
unset P4CONFIG

[[ $P4U_LOG == Unset ]] && \
   export P4U_LOG="${LOGS}/${THISSCRIPT%.sh}.$(date +'%Y%m%d-%H%M').log"

#==============================================================================
# Main Program

trap terminate EXIT SIGINT SIGTERM

declare -i OverallReturnStatus=0

if [[ "${P4U_LOG}" != off ]]; then
   touch ${P4U_LOG} || bail "Couldn't touch log file [${P4U_LOG}]."

   # Redirect stdout and stderr to a log file.
   if [[ $SilentMode -eq 0 ]]; then
      exec > >(tee ${P4U_LOG})
      exec 2>&1
   else
      exec >${P4U_LOG}
      exec 2>&1
   fi

   initlog
fi

msg "$THISSCRIPT v$Version started at $(date)."

# If depot was specified as '//x', normalize to 'x'.
[[ "$StreamDepot" == "//"* ]] && StreamDepot=${StreamDepot#//}

DepotTypeCheck=$(p4 -ztag -F %Type% depot -o $StreamDepot 2>/dev/null)

if [[ -n "$DepotTypeCheck" ]]; then
   [[ "$DepotTypeCheck" != "stream" ]] && \
      bail "The depot specified by '-d' must be of type 'stream', not $DepotTypeCheck."
else
   bail "Could not determine depot type for depot $StreamDepot. Aborting."
fi

msg "Getting list of streams in //$StreamDepot."
p4 -ztag -F "%Type%:%Stream%" streams "//$StreamDepot/*" > $StreamListFile

export P4CLIENT=tmp.auto.$P4USER.${THISSCRIPT%.sh}

echo -e "Client: $P4CLIENT\n\nOwner: $P4USER\n\nDescription:\n\tUsed by $THISSCRIPT\n\nRoot: $P4TMP/${THISSCRIPT%.sh}\n\nView:\n\t//spec/... //$P4CLIENT/spec/..." > $TmpFile

msg "Using generated temporary workspace $P4CLIENT."
p4 -s client -i < $TmpFile || bail "Failed to create temp client using this spec:\n$(cat $TmpFile)\n"

if [[ $SourceStream == Unset && $TargetStream == Unset ]]; then
   touch $SearchedPathsFile || bail "Could not touch temp file $SearchedPathsFile."
   while read SourceStream; do
      [[ $SourceStream == "virtual:"* ]] && continue
      SourceStream=${SourceStream##*:}
      SourcePath="$SourceStream/..."

      while read TargetStream; do
         [[ "$TargetStream" == "virtual:"* ]] && continue
         TargetStream=${TargetStream##*:}
         [[ "$SourceStream" == "$TargetStream" ]] && continue
         TargetPath="$TargetStream/..."

         # Direction doesn't matter for evil twin detection, so we only need to search
         # in a single direction.  Avoid redundant checking by checknig the
         # "searched paths" temp file, which contains a list of already searched paths.
         grep "${SourceStream#//}:${TargetStream#//}\$" $SearchedPathsFile > /dev/null 2>&1
         [[ $? -eq 0 ]] && continue
         grep "${TargetStream#//}:${SourceStream#//}\$" $SearchedPathsFile > /dev/null 2>&1
         [[ $? -eq 0 ]] && continue

         find_evil_twins "$SourcePath" "$TargetPath" ||\
            OverallReturnStatus=1

         PathwayCount=$((PathwayCount+1))
         echo "${SourceStream#//}:${TargetStream#//}" >> $SearchedPathsFile
      done < $StreamListFile
   done < $StreamListFile
elif [[ $SourceStream != Unset && $TargetStream != Unset ]]; then
   SourcePath="$SourceStream/..."
   TargetPath="$TargetStream/..."

   find_evil_twins "$SourcePath" "$TargetPath" ||\
      OverallReturnStatus=1

   PathwayCount=1
else
   bail "Specify only '-s' or '-t', but not both, is not (yet?) supported."
fi

if [[ $OverallReturnStatus -eq 0 ]]; then
   msg "${H}\nAll processing completed successfully.\n"
else
   msg "${H}\nProcessing completed, but with errors.  Scan above output carefully.\n" 
fi

# Illustrate using $SECONDS to display runtime of a script.
msg "Found $EvilTwinCount evil twins searching $PathwayCount pathway(s) in $(($SECONDS/3600)) hours $(($SECONDS%3600/60)) minutes $(($SECONDS%60)) seconds.\n"

# See the terminate() function, which is really where this script exits.
exit $OverallReturnStatus
# Change User Description Committed
#4 26652 Robert Cowham This is Tom's change:

Introduced new 'Unsupported' directory to clarify that some files
in the SDP are not officially supported. These files are samples for
illustration, to provide examples, or are deprecated but not yet
ready for removal from the package.

The Maintenance and many SDP triggers have been moved under here,
along with other SDP scripts and triggers.

Added comments to p4_vars indicating that it should not be edited
directly. Added reference to an optional site_global_vars file that,
if it exists, will be sourced to provide global user settings
without needing to edit p4_vars.

As an exception to the refactoring, the totalusers.py Maintenance
script will be moved to indicate that it is supported.

Removed settings to support long-sunset P4Web from supported structure.

Structure under new .../Unsupported folder is:
   Samples/bin             Sample scripts.
   Samples/triggers        Sample trigger scripts.
   Samples/triggers/tests  Sample trigger script tests.
   Samples/broker          Sample broker filter scripts.
   Deprecated/triggers     Deprecated triggers.

To Do in a subsequent change: Make corresponding doc changes.
#3 25193 C. Thomas Tyler EvilTwinDetector.sh v2.1.3:
* Narrowed focus to stream depots only, as only in stream depots
is there sufficient metadata detect evil twins.
* Updated evil twin detection logic.
* Updated docs and command line usage.
* Better reporting.
* Added capability to fix evil twins with baseless integrations.
#2 24781 C. Thomas Tyler EvilTwinDetector.sh v1.1.0:
       * Implemented trigger mode to prevent evil triggers.
       * Refined output.
       * Enhanced docs.
       * Fixed typos/grammatical errors in a few error messages.

Bypassing code review for now as this change is about to be merged with and succeeded by a
different, bigger change made elsewhere.
#1 20062 C. Thomas Tyler Added Evil Twin Detector maintenance script.