#!/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
declare ThisScript=${0##*/}
declare ThisUser=
declare ThisHost=${HOSTNAME%%.*}
declare Version=1.1.0
declare -a TargetPaths
declare -i LineCount=1
declare -i PathCount=0
declare -i AllPathsValid=1
declare -i NoOp=0
declare -i Debug=0
declare -i ErrorCount=0
declare -i i=0
declare OpMode=DryRun
declare TargetPathsFile=
declare SDPInstance=${SDP_INSTANCE:-}
declare FileCount=
declare FileData=
declare FileSize=
declare Log=
declare H1="=============================================================================="
declare H2="------------------------------------------------------------------------------"
declare ObliterateClient=
declare ObliterateClientRoot=
declare ObliterateClientSpec=
#==============================================================================
# Local Functions
function msg () { echo -e "$*"; }
function dbg () { [[ "$Debug" -eq 0 ]] || msg "DEBUG: $*"; }
function errmsg () { msg "\\nError: ${1:-Unknown Error}\\n"; ErrorCount+=1; }
function bail () { errmsg "${1:-Unknown Error}"; exit "$ErrorCount"; }
#------------------------------------------------------------------------------
# Function: obliterate_path ($path)
function obliterate_path {
local path="${1:-}"
local oblitCmd=
if [[ -z "$path" ]]; then
errmsg "obliterate_path(): Missing parameter."
return 1
fi
if echo -e "Client: $ObliterateClient\\n\\nRoot: $ObliterateClientRoot\\n\\nDescription:\\n\\tTempoary obliterate client\\n\\nView:\\n\\t\"$path\" //$ObliterateClient/...\\n\\n" > "$ObliterateClientSpec"; then
if p4 -s client -i < "$ObliterateClientSpec"; then
msg "Client spec updated for path [$path]."
cat "$ObliterateClientSpec"
else
errmsg "Failed to load this clinet spec:\\n$(cat "$ObliterateClientSpec")"
return 1
fi
dbg "Generated client spec is:\\n$(p4 client -o "$ObliterateClient" | grep -v '^#')"
else
errmsg "Failed to generate client spec."
return 1
fi
msg "Path to obliterate is:\\n$(p4 -c "$ObliterateClient" where //$ObliterateClient/...)"
oblitCmd="p4 -c $ObliterateClient -s obliterate"
[[ "$OpMode" == Live ]] && oblitCmd+=" -y"
oblitCmd+=" //$ObliterateClient/..."
if [[ "$NoOp" -eq 0 ]]; then
msg "Running: $oblitCmd"
# shellcheck disable=SC2086
if eval echo $oblitCmd; then
msg "Obliteration successful for path [$path]."
else
errmsg "Obliteration unsuccessful for path [$path]."
fi
else
msg "NO_OP: Would run: $oblitCmd"
fi
}
#------------------------------------------------------------------------------
# Function: terminate
function terminate
{
# Disable signal trapping.
trap - EXIT SIGINT SIGTERM
[[ "$Log" != off ]] && \
msg "Log is: $Log\\n${H1}\\n"
# With the trap removed, exit.
exit "$ErrorCount"
}
#------------------------------------------------------------------------------
# Function: usage (required function)
#
# Input:
# $1 - style, either -h (for short form) or -man (for man-page like format).
# The default is -h.
#
# $2 - error message (optional). Specify this if usage() is called due to
# user error, in which case the given message displayed first, followed by the
# standard usage message (short or long depending on $1). If displaying an
# error, usually $1 should be -h so that the longer usage message doesn't
# obscure the error message.
#
# Sample Usage:
# usage
# usage -h
# usage -man
# usage -h "Incorrect command line usage."
#------------------------------------------------------------------------------
function usage
{
declare style=${1:--h}
declare errorMessage=${2:-Unset}
if [[ "$errorMessage" != Unset ]]; then
msg "\\n\\nUsage Error:\\n\\n$errorMessage\\n\\n"
fi
msg "USAGE for $ThisScript v$Version:
$ThisScript -tpf <TargetPathsFile> [-i <SDPInstance>] [-L <log>] [-y] [-d|-D]
or
$ThisScript [-h|-man|-V]
"
if [[ $style == -man ]]; then
echo -e "
DESCRIPTION:
WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
THIS SCRIPT IS EXTREMELY DANGEOURS!!!! USE IT WTIH GREAT CARE!!!
This script will obliterate a set of paths in a config user-provided file
containing a list of paths to target in depot syntax. Paths do not need
to be (and must not be) quoted, but must be limited to single-line entries.
OPTIONS:
-i <SDPInstance>
Specify the SDP instance. This is requred unless SDP_INSTANCE is defined,
which will be if the standard SDP shell environment is used.
-L <log>
Specify the path to a log file, or the special value 'off' to disable
logging. By default, all output (stdout and stderr) goes to:
${LOGS:-/tmp}/${ThisScript%.sh}.<OpMode>.<Datestamp>.log
NOTE: This script is self-logging. That is, output displayed on the screen
is simultaneously captured in the log file.
-y This is confirmation to execute the obliterate commands with '-y'. By
default, this script does executes the obliterate commands without the '-y'
flag to the 'p4 obliterate' command, making it a diagnostic command.
-n Display data-affecting commands instead of running them.
-d Enable higher verbosity debugging outpout.
-D Enable bash 'set -x' extreme debugging verbosity. Also implies '-d'.
HELP OPTIONS:
-h Display short help message
-man Display man-style help message
FILES:
The TargetPathsFile contains one-line entries, each containing a path to
obliterate. The path must be in valid depot syntax, and can reference a
single file, a directory tree, or set of files using the '*' and '...'
wild cards. Some samples:
//jam/r4.3/...
//jam/*/bugs_branches/...
In short, this is the same syntax accepted by the 'p4 obliterate'
command.
EXAMPLES:
All examples assume you are logged in as $P4USER with the SDP shell
environment configured (i.e. /p4/common/bin/p4_vars sourced).
Do a preview that executes no data-affecting commands:
nohup obliterate.sh -tpf /p4/1/tmp/MyPaths.txt -n < /dev/null > $LOGS/oblit.preview.log 2>&1 &
Do a dry run, doing 'p4 obliterate' but without the '-y':
nohup obliterate.sh -tpf /p4/1/tmp/MyPaths.txt < /dev/null > $LOGS/oblit.dry_run.log 2>&1 &
Do the real thing, doing 'p4 obliterate' with the '-y':
nohup obliterate.sh -tpf /p4/1/tmp/MyPaths.txt -y < /dev/null > $LOGS/oblit.LIVE.log 2>&1 &
SEE ALSO:
p4 help obliterate
"
fi
exit 2
}
#==============================================================================
# Command Line Processing
declare -i shiftArgs=0
set +u
while [[ $# -gt 0 ]]; do
case $1 in
(-tpf) TargetPathsFile="$2"; shiftArgs=1;;
(-h) usage -h;;
(-man) usage -man;;
(-L) Log="$2"; shiftArgs=1;;
(-i) SDPInstance="$2"; shiftArgs=1;;
(-y) OpMode=Live;;
(-n) NoOp=1;;
(-d) Debug=1;;
(-D) Debug=1; set -x;; # Debug; use 'set -x' mode.
(-*) usage -h "Unknown flag ($1).";;
(*) usage -h "Unknown parameter ($1).";;
esac
# Shift (modify $#) the appropriate number of times.
shift; while [[ $shiftArgs -gt 0 ]]; do
[[ $# -eq 0 ]] && usage -h "Incorrect number of arguments."
shiftArgs=$shiftArgs-1
shift
done
done
set -u
#==============================================================================
# Command Line Verification
[[ -z "$SDPInstance" ]] && \
usage -h "The '-i <SDPInstance> is required (Unless SDP_INSTANCE is set)."
[[ -z "$TargetPathsFile" ]] && \
usage -h "The '-tpf <TargetPathsFile>' argument is required."
[[ -r "$TargetPathsFile" ]] || \
usage -h "The specified target paths file [$TargetPathsFile] does not exist."
[[ -s "$TargetPathsFile" ]] || \
usage -h "The specified target paths file [$TargetPathsFile] is empty."
# shellcheck disable=SC1091
source /p4/common/bin/p4_vars "$SDPInstance" ||\
bail "Could not do: source /p4/common/bin \"$SDPInstance\""
[[ -n "${Log:-}" ]] || \
Log="${LOGS:-/tmp}/${ThisScript%.sh}.$OpMode.$(date +'%Y%m%d-%H%M%S').log"
#==============================================================================
# Main Program
trap terminate EXIT SIGINT SIGTERM
if [[ "$Log" != off ]]; then
touch "$Log" || bail "Couldn't touch log file [$Log]."
# Redirect stdout and stderr to a log file.
exec > >(tee "$Log")
exec 2>&1
msg "${H1}\\nLog is: $Log"
fi
ThisUser=$(id -n -u)
msg "Starting $ThisScript v$Version as $ThisUser@$ThisHost on $(date)."
msg "\\nLoading data from: $TargetPathsFile"
while read -r Line; do
if [[ "$Line" == //* ]]; then
TargetPaths[PathCount]="$Line"
PathCount+=1
elif [[ "$Line" =~ ^[[:space:]]*$ ]]; then
LineCount+=1
continue
elif [[ "$Line" == "#"* ]]; then
LineCount+=1
continue
else
errmsg "Line $LineCount: Invalid path [$Line] does not start with '//'."
LineCount+=1
AllPathsValid=0
continue
fi
msg "Checking size of data targeted in path: $Line"
dbg "Running: p4 -ztag -F %fileCount%:%fileSize% sizes -sah \"$Line\""
FileData=$(p4 -ztag -F %fileCount%:%fileSize% sizes -sah "$Line")
dbg "FileData=[$FileData]"
if [[ -n "$FileData" ]]; then
if [[ "$FileData" != ':' ]]; then
FileSize="${FileData##*:}"
FileCount="${FileData%%:*}"
if [[ "$FileCount" != 0 ]]; then
msg "Size of data targeted to remove in [$Line]: $FileCount files, $FileSize bytes."
else
errmsg "No files to remove in targeted path [$Line] on line $LineCount."
AllPathsValid=0
continue
fi
else
errmsg "Could not get size info for target path [$Line] on line $LineCount."
AllPathsValid=0
LineCount+=1
continue
fi
msg "FS: [$FileSize] FC: [$FileCount]"
else
errmsg "Could not get size info for target path [$Line] on line $LineCount."
AllPathsValid=0
LineCount+=1
continue
fi
LineCount+=1
done < "$TargetPathsFile"
if [[ "$AllPathsValid" -eq 1 ]]; then
msg "\\nAll target paths valid. Proceeding with obliteration."
else
bail "Due to errors above, halting furthe progress. Fix the TargetPathsFile [$TargetPathsFile] and try again."
fi
msg "${H1}\\nStarting obliteration of paths listed in $TargetPathsFile."
ObliterateClient="OfflineOblit.$(date +'%Y-%m-%d-%H%M%S').$RANDOM"
ObliterateClientRoot="${P4TMP:-/tmp}/oblit"
ObliterateClientSpec="$(mktemp)"
for ((i=0; i<PathCount; i++)); do
msg "${H2}\\nObliterating path: [${TargetPaths[i]}]"
obliterate_path "${TargetPaths[i]}"
done
if [[ "$ErrorCount" -eq 0 ]]; then
msg "${H2}\\nAll processing completed successfully.\\n"
else
msg "${H2}\\nProcessing completed, but with $ErrorCount errors. Scan above output carefully.\\n"
fi
# Illustrate using $SECONDS to display runtime of a script.
msg "Time: That took $((SECONDS/3600)) hours $((SECONDS%3600/60)) minutes $((SECONDS%60)) seconds.\\n"
# See the terminate() function, which is really where this script exits.
exit "$ErrorCount"