#!/bin/sh
############################################################ IDENT(1)
#
# $Title: Script to upgrade perforce elements to latest version(s) $
#
############################################################ CONFIGURATION
#
# Components to check for upgrade
# NB: Entries should be all-uppercase
# NB: Items should exactly match below variable name(s)
#
CHECK_FOR_UPGRADE="P4 P4D P4WEB"
#
# Locations of installed objects (to be upgraded)
# NB: Variable names should be all-uppercase
# NB: Variable names should match entries in $CHECK_FOR_UPGRADE above
#
P4=$( which p4 ) || P4=/bin/p4
P4D=$( which p4d ) || P4D=/usr/local/bin/p4d
P4WEB=$( which p4web ) || P4WEB=/usr/local/bin/p4web
#
# Web page where downloads are acquired
#
PERFORCE_CUSTOMER_DL_PAGE=http://www.perforce.com/downloads/Perforce/Customer
PERFORCE_DIRECT_DOWNLOAD=http://www.perforce.com/downloads/perforce/
#
# Where to download temporary files to
#
DOWNLOAD_TMP=/tmp
#
# OS Glue
#
: ${UNAME_s:=$( uname -s )}
#
# p4d specifics
#
P4PORT= # Desired p4d (host:port); if NULL lsof(8)/sockstat(1) is used
P4USER= # Used to shut down p4d; if NULL (default) prompt user
P4D_PID= # If NULL (default), ps(1) is used to find PID of p4d
#
# Command-line options
#
IGNORE_BETA=1
IGNORE_CURRENT=1
############################################################ MAIN
#
# Process command-line arguments
#
while getopts bch flag; do
case "$flag" in
b) IGNORE_BETA= ;;
c) IGNORE_CURRENT= ;;
\?|h)
echo "Usage: $0 [-bch]" >&2
echo "Options:" >&2
echo " -b Allow upgrade to betas" >&2
echo " -c Allow upgrade to 'current' latest" >&2
echo " -h Print this usage statement and exit" >&2
exit 1
;;
esac
done
shift $(( $OPTIND - 1 ))
# Must be root!
[ $( id -u ) -eq 0 ] || { echo "Must be root!" >&2; exit 1; }
#
# 1. Prune out bits that are not installed
# NB: Only reached if running as root
#
_CHECK_FOR_UPGRADE=
for var in $CHECK_FOR_UPGRADE; do
eval util=\"\$$var\"
[ -e "$util" ] || continue
_CHECK_FOR_UPGRADE="$_CHECK_FOR_UPGRADE $var"
done
CHECK_FOR_UPGRADE="${_CHECK_FOR_UPGRADE# }"
#
# 2. Get current versions of [installed] items -- if-any
#
echo "Checking installed software version(s)..."
for var in $CHECK_FOR_UPGRADE; do
eval util=\"\$$var\"
printf "\t%s=%s\n" $var "$util"
eval ${var}VERSION=\$\( \$util -V \| awk -F"'[ /]'" -v var=$var \''
toupper($2) == var, $0 = $4 "/" $5
'\' \)
done
#
# 3. Display the information that we got off the local machine
#
echo ">>> Here's what I found:"
[ "$CHECK_FOR_UPGRADE" ] || { echo "Nothing found. Exiting." >&2; exit 1; }
for var in $CHECK_FOR_UPGRADE; do
eval printf '"\t%sVERSION=%s\n"' $var \"\$${var}VERSION\"
done
echo
echo "NEXT STEP: Scrape the download page for latest version info"
read -p "< Press ENTER/RETURN to continue, Ctrl-C to abort >" IGNORED
#
# 4. Find out what the latest versions are
# NB: Only reached if something is installed
#
echo "================================================================"
echo "Scraping $PERFORCE_CUSTOMER_DL_PAGE ..."
LATEST_VERSIONS=$( wget -O- "$PERFORCE_CUSTOMER_DL_PAGE" 2> /dev/null | awk \
-v ignore_current="${IGNORE_CURRENT:-0}" \
-v ignore_beta="${IGNORE_BETA:-0}" '
BEGIN {
(cmd = "uname -s") | getline os
close(cmd)
(cmd = "uname -r") | getline rel
close(cmd)
(cmd = "uname -m") | getline march
close(cmd)
if (os ~ /^(Linux|FreeBSD|Solaris)$/) arch = (march ~ /i[3-6]86/ ?
"32-bit Intel (x86)" : "64-bit Intel (x64)")
else if (os ~ /^(NetBSD)$/) arch = "32-bit Intel"
else if (os ~ /^(Darwin)$/)
arch = (march ~ /i[3-6]86/ ? "x86" : "x86_64")
osrel = os
if (os ~ /^(FreeBSD|Darwin|Solaris|NetBSD)$/)
{
sub(/[^[:digit:].].*/, "", rel)
osrel = sprintf("%s %s", os, rel)
}
else if (os ~ /^(Linux)$/)
{
split(rel, relnums, /[.-]/)
osrel = sprintf("%s %u%s%u", os,
relnums[1], relnums[2] ? "." : "", relnums[2])
}
}
/(beta)/ && ignore_beta { next }
match($0, /class="product-title"/) {
product = substr($0, RSTART + RLENGTH + 1)
sub(/[^[:alnum:]].*/, "", product)
}
/class="items"/ && match($0, /value="[^"]*"/) {
value = substr($0, RSTART + 7, RLENGTH - 8)
gsub(/"/, "\"", value)
gsub(/{/, "&\n", value)
nitems = split(value, items, /\n/)
desc = ""
for (n = 1; n <= nitems; n++)
{
if (match(items[n], /^"description":"[^"]*"/))
desc = substr(items[n], RSTART + 15, RLENGTH - 16)
if (desc != sprintf("%s for %s", osrel, arch)) continue
if (items[n] !~ /^"version_id":/) continue
if (items[n] ~ /"release_state":"current"/)
{
# Always take latest p4web even if current track
if (ignore_current && tolower(product) != "p4web")
continue
}
if (!match(items[n], /"version_string":"[^"]*"/)) continue
version_str = substr(items[n],
RSTART + 18, RLENGTH - 19)
gsub("\\\\/", "/", version_str)
sub("[^0-9./].*", "", version_str)
printf "%sLATEST=%s\n", toupper(product), version_str
break
}
}' )
#
# 5. Display the information that we got off the web
#
echo ">>> Here's what I found:"
[ "$LATEST_VERSIONS" ] || { echo "Nothing found. Exiting." >&2; exit 1; }
echo "$LATEST_VERSIONS" | while read LINE; do
report_latest=
for var in $CHECK_FOR_UPGRADE; do
[ "$var" = "${LINE%%LATEST=*}" ] && report_latest=1 break
done
[ "$report_latest" ] || continue
printf "\t%s\n" "$LINE"
done
echo
echo "NEXT STEP: Compare installed versions to latest versions"
read -p "< Press ENTER/RETURN to continue, Ctrl-C to abort >" IGNORED
#
# 6. Check to see if any installed versions are behind latest versions
# NB: Only reached if the web was reachable and downloads were found
#
echo "================================================================"
eval "$LATEST_VERSIONS" || exit 1
echo ">>> Checking to see if any installed components need upgrading..."
NEED_UPGRADE=
for var in $CHECK_FOR_UPGRADE; do
eval current_version=\"\$${var}VERSION\"
eval latest_version=\"\$${var}LATEST\"
if [ "$current_version" != "$latest_version" ]; then
NEED_UPGRADE="$NEED_UPGRADE $var"
fi
done
NEED_UPGRADE="${NEED_UPGRADE# }"
#
# 7. Display the results to the user
#
echo
if [ ! "$NEED_UPGRADE" ]; then
echo "None of the requested components need upgrading!"
echo "Exiting. (Success)"
exit 0
fi
for var in $NEED_UPGRADE; do
eval current=\"\$${var}VERSION\"
eval latest=\"\$${var}LATEST\"
printf "\t%-10s %-13s -> %s\n" $var: "$current" "$latest"
done
echo
echo "NEXT STEP: Fetch latest versions to temporary location"
read -p "< Press ENTER/RETURN to continue, Ctrl-C to abort >" IGNORED
#
# 8. Determine which daemons if-any are being upgraded
# NB: Only reached if something needs upgrading
#
UPGRADE_P4D=
UPGRADE_P4WEB=
for var in $NEED_UPGRADE; do
case "$var" in
P4D) UPGRADE_P4D=1 ;;
P4WEB) UPGRADE_P4WEB=1 ;;
esac
done
#
# 9. Download the latest versions to a temporary directory
#
echo "================================================================"
echo ">>> Fetching latest versions to temporary location ($DOWNLOAD_TMP)..."
dlarch=linux26
case "$( uname -m )" in
i[3-6]86) dlarch="${dlarch}x86" ;;
*) dlarch="${dlarch}x86_64"
esac
echo
for var in $NEED_UPGRADE; do
eval dlver=\"\$${var}LATEST\"
dlver="${dlver%%/*}"
dlver="r${dlver#20}"
download_url="${PERFORCE_DIRECT_DOWNLOAD%/}/$dlver/bin.$dlarch"
eval util=\"\$$var\"
util_name="${util##*/}"
rm -f "$DOWNLOAD_TMP/$util_name"
wget -O "$DOWNLOAD_TMP/$util_name" "$download_url/$util_name"
chmod +x "$DOWNLOAD_TMP/$util_name"
done
echo
echo "NEXT STEP: Make copies of old versions"
read -p "< Press ENTER/RETURN to continue, Ctrl-C to abort >" IGNORED
#
# 10. Copy old versions
#
echo "================================================================"
echo ">>> Make copies of old versions ($NEED_UPGRADE)..."
for var in $NEED_UPGRADE; do
eval util=\"\$$var\"
eval oldver=\"\$${var}VERSION\"
backup_copy="$util.${oldver%%/*}"
if [ -e "$backup_copy" ]; then
echo "$backup_copy (skipped)"
else
cp -fv "$util" "$backup_copy"
fi
done
echo
echo "NEXT STEP: Stop critical daemon process(es) [IF NEEDED] and copy files"
read -p "< Press ENTER/RETURN to continue, Ctrl-C to abort >" IGNORED
#
# 11. [IF REQUIRED] Get the process ID of the master p4d process
# NB: The master `p4d' process is the one whose parent itself is not `p4d'
#
if [ "$UPGRADE_P4D" -a ! "$P4D_PID" ]; then
echo "================================================================"
echo -n ">>> Scanning for p4d process ID... "
P4D_PID=$( ps axo pid,ppid,ucomm | awk '
(ucomm = $3) == "p4d" { pid = $1; ppid = $2
(cmd = sprintf("ps -p %u -o ucomm=", ppid)) | getline pucomm
close(cmd)
if (pucomm != ucomm) {
print pid
exit
}
}' )
if [ ! "$P4D_PID" ]; then
echo "Not found!"
FORCE_P4D_START=1
echo
echo "NB: Enter 'no' below if you have must do an offline"
echo " replay of the last checkpoint before starting"
echo " the newest version."
echo
read -p "Start p4d now? [Y]: " ANSWER
case "$ANSWER" in
[Nn]|[Nn][Oo]) FORCE_P4D_START= ;;
esac
unset ANSWER
else
echo "$P4D_PID"
fi
fi
#
# 12. [IF REQUIRED] Get listening `host:port' for running p4d process
# NB: Only reached if (a) no upgrade of p4d required or (b) we were able to
# obtain the process ID of the running p4d process (P4D_PID).
#
if [ "$UPGRADE_P4D" -a "$P4D_PID" -a ! "$P4PORT" ]; then
echo -n ">>> Finding listen-address:port for p4d pid $P4D_PID... "
P4PORT=` case "$UNAME_s" in
FreeBSD) sockstat -Ll4 | awk -v pid="$P4D_PID" \
'$3 == pid && $NF == "*:*" { print $(NF-1); exit }'
;;
Linux) lsof -nPi4 | awk -v pid="$P4D_PID" \
'$2 == pid && $NF == "(LISTEN)" { print $(NF-1); exit}'
;;
esac`
echo "${P4PORT:-Not found!}"
if [ ! "$P4PORT" ]; then
echo "Unable to communicate with running p4d process." >&2
echo "Exiting." >&2
exit 1
fi
fi
#
# 13. [IF REQUIRED] Shut down the process(es), in an official way
# NB: Only reached if (a) no upgrade of p4d required or (b) we were able to
# obtain both the listen-addr:port of the running p4d process (P4PORT) and
# a P4USER that can perform `p4 admin' commands (e.g., `p4 admin stop').
#
if [ "$UPGRADE_P4D" -a "$P4D_PID" -a "$P4PORT" ]; then
echo ">>> Stopping p4d process ($P4D_PID)..."
if [ ! "$P4USER" ]; then
read -p "Please enter p4 admin user name: [$SUDO_USER] " P4USER
[ "${P4USER:=$SUDO_USER}" ] ||
{ echo "Nothing entered. Exiting." >&2; exit 1; }
fi
p4 -p "$P4PORT" -u "$P4USER" admin stop || {
result=$?
echo "Command: p4 -p \"$P4PORT\" -u \"$P4USER\" admin stop" >&2
echo "Exited with error status ($result)." >&2
# Command exited with error status. Back away slowly!
# Ensure service wasn't interrupted (else start it back up).
sleep 1
ps -p "$P4D_PID" > /dev/null 2>&1 ||
nc -z "${P4PORT%:*}" "${P4PORT#*:}" > /dev/null 2>&1 ||
ps axo ucomm | awk '
$1 == "p4d" {found++;exit} END {exit !found}
' || {
echo "p4d stopped! Restarting p4d." >&2
service perforce start
}
echo "Exiting." >&2
exit 1
}
fi
if [ "$UPGRADE_P4WEB" ]; then
echo ">>> Stopping p4web process(es)..."
killall p4web
fi
#
# 14. [IF REQUIRED] Wait for the master process to go away
#
if [ "$UPGRADE_P4D" -a "$P4D_PID" ]; then
echo ">>> Waiting for master p4d process ($P4D_PID) to go down..."
while ps -p "$P4D_PID" > /dev/null 2>&1; do
sleep 1
done
fi
if [ "$UPGRADE_P4D" -a "$P4PORT" ]; then
echo ">>> Waiting for $P4PORT to stop listening..."
while nc -zv "${P4PORT%:*}" "${P4PORT#*:}" > /dev/null 2>&1; do
sleep 1
done
fi
#
# 15. Suggest the user perform a checkpoint
#
if [ "$UPGRADE_P4D" ]; then
echo
echo "NEXT STEP: Perform verification and then checkpoint (for replay)"
echo "================================================================"
echo "Now is the time you should make an offline checkpoint:"
echo
echo "sudo -u perforce $P4D -r /perforce -J /perforce/journal -jc -z"
echo
echo "If you have already done this, press Enter."
echo "Otherwise, press Ctrl-C to abort and do it now."
fi
echo
echo "NEXT STEP: Move new [downloaded] version(s) into place"
echo "FINAL STEP: Start new version of daemon(s) [IF NEEDED]"
read -p "< Press ENTER/RETURN to continue, Ctrl-C to abort >" IGNORED
#
# 16. Move latest versions into place (with proper permissions)
#
echo "================================================================"
echo ">>> Moving new version(s) into place"
for var in $NEED_UPGRADE; do
eval util=\"\$$var\"
mv -vf "${DOWNLOAD_TMP%/}/${util##*/}" "$util"
done
echo
echo "FINAL STEP: Start new version of daemon(s) [IF NEEDED]"
#
# 17. Start up the new versions...
#
echo "================================================================"
echo "[OPTIONAL] POST-UPGRADE STEP: Replay the last checkpoint if-needed:"
echo
echo "sudo -u perforce /usr/local/bin/p4d -r /perforce -z -jr checkpoint.#.gz"
echo "sudo -u perforce /usr/local/bin/p4d -r /perforce -jr /perforce/journal"
echo "sudo -u perforce /usr/local/bin/p4d -r /perforce -J /perforce/journal -xu"
echo
echo "When this is complete, press ENTER to start the new version(s)"
echo
read -p "< Press ENTER/RETURN to continue, Ctrl-C to abort >" IGNORED
echo "================================================================"
echo ">>> Starting new version(s)"
[ "$UPGRADE_P4D" ] && [ "$P4D_PID" -o "$FORCE_P4D_START" ] &&
service p4d start && echo "p4d started."
[ "$UPGRADE_P4WEB" ] &&
service p4web start && echo "p4web started."
echo
#
# Done
#
echo "================================================================"
echo "Done. (Success)"
echo
echo "[OPTIONAL] Recommended to now perform the following:"
echo
echo "time p4 -p $P4PORT verify //..."
################################################################################
#END
################################################################################
#
# $Copyright: 2015 Devin Teske. All rights reserved. $
#
# $Header: //guest/freebsdfrau/p4t/libexec/upgrade#1 $
#
################################################################################