#!/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