#!/bin/sh
############################################################ IDENT(1)
#
# $Title: Script to replay perforce checkpoint $
#
############################################################ CONFIGURATION

#
# User to perform replay as
#
P4D_USER=p4admin

#
# Local perforce server settings
# NB: Taken from rc.conf(5) on FreeBSD
#
P4D_ROOT=$( sysrc -n p4d_root 2> /dev/null )

#
# Sensible defaults (i.e., Linux)
#
: ${P4D_ROOT:=/perforce}

############################################################ GLOBALS

pgm="${0##*/}" # Program basename

#
# Global exit status
#
SUCCESS=0
FAILURE=1

#
# Command-line options
#
DEBUG=		# -d
ROOTDIR=	# -R dir
UNPACK_ONLY=	# -U
USER=		# -u user

#
# Miscellaneous
#
export LC_ALL="${LC_ALL:-en_US.ISO8859-1}" # separators in dpv(1) status
FIFO=replay_fifo.$$
FILE=
GZFILE=
STOPPED_P4D=

# TODO: Add `-V' option to perform md5(1) verification when *.md5 exists
# NOTE: LC_ALL=en_US.ISO8859-1 dpv -xmd5 $( stat -f%z $FILE ):$FILE < $FILE
#       wrapped in a sub-shell to capture output and add `-t title' et al.

############################################################ FUNCTIONS

quietly(){ "$@" > /dev/null 2>&1; }
have(){ quietly type "$@"; }

die()
{
	local fmt="$1"
	exec >&2
	if [ "$fmt" ]; then
		shift 1 # fmt
		printf "$fmt\n" "$@"
	fi
	exit $FAILURE
}

usage()
{
	exec >&2
	local optfmt="\t%-8s %s\n"
	printf "Usage: %s [-U|-d] [OPTIONS] checkpoint.NNNN[.gz]\n" "$pgm"
	printf "OPTIONS:\n"
	printf "$optfmt" "-U" \
		"Unpack the checkpoint if-required (first step) and exit."
	printf "$optfmt" "-d" \
		"Debug. Don't stop p4d and don't replay but simulate it."
	printf "$optfmt" "-R dir" \
		"Perform replay in dir (default $P4D_ROOT)."
	printf "$optfmt" "-u user" \
		"Perform replay as user (default $P4D_USER)."
	exit $FAILURE
}

############################################################ MAIN

#
# Command-line options
#
while getopts dR:u:U flag; do
	case "$flag" in
	d) DEBUG=1 ;;
	R) ROOTDIR="$OPTARG" ;;
	u) USER="$OPTARG" ;;
	U) UNPACK_ONLY=1 ;;
	*) usage
	esac
done
shift $(( $OPTIND - 1 ))
FILE="$1"

#
# Validate command-line arguments
#
[ "$FILE" ] || usage
# set after last-call to usage()
P4D_ROOT="${ROOTDIR:-$P4D_ROOT}"
P4D_USER="${USER:-$P4D_USER}"

#
# All actions should be performed within the p4d_root (e.g., /perforce)
#
cd "$P4D_ROOT" || exit

#
# Validate command-line arguments (continued)
#
[ "$FILE" != "${FILE%.[Gg][Zz]}" ] && GZFILE="$FILE" FILE="${FILE%.[Gg][Zz]}"
IN="${GZFILE:-$FILE}"
[ -e "$IN" ] || die "%s: %s: No such file or directory" "$pgm" "$IN"
[ -f "$IN" ] || die "%s: %s: Is a directory" "$pgm" "$IN"

#
# Make sure we have required users and sufficient privileges to proceed
#
[ "$( id -u )" -eq 0 ] || die "Must be root!"
id -u ${P4D_USER:+"$P4D_USER"} > /dev/null || exit

#
# If the checkpoint argument was named `*.gz' (case-insensitive), unpack the
# checkpoint first.
#
if [ "$GZFILE" -a ! -e "$FILE" ]; then
	size=$( stat -f%z "$GZFILE" )
	trap 'rm -f "$FILE"' SIGINT
	export FILE
	if ! time dpv \
		-wmx 'zcat > "$FILE"' -b "$pgm" -t "zcat(1)" \
		-p "Unpacking checkpoint file...\n" \
		-a "\nNEXT: Stop p4d, backup files, replay checkpoint" \
		"${size:+$size:}    $GZFILE" "$GZFILE"
	then
		quietly rm -f "$FILE"
		exit $FAILURE
	fi
	trap - SIGINT
	[ ! "$P4D_USER" ] || chown "$P4D_USER" "$FILE" || exit
	quietly chmod 0444 "$FILE"
fi

[ "$UNPACK_ONLY" ] && exit $SUCCESS
# NOTREACHED if given `-U' option

#
# Make a backup directory for the existing db files
#
vers=$( p4d -V | awk '/^Rev./&&split($2,vers,"/"){print vers[3]}' ) || exit
back="db-$vers@$( date +%F+%H_%M )"
create=
if [ ! "$DEBUG" ]; then
	for f in db.* *.lbr journal state; do
		[ -e "$f" ] || continue
		create=1
		break
	done
	[ ! "$create" ] || mkdir -pm0755 "$back" || exit
	[ ! "$P4D_USER" ] || chown "$P4D_USER" "$back" || exit
fi

#
# Is p4d running? (we'll pause for 10 seconds before stopping it)
# NB: If we stop it, we'll later start it
#
if [ ! "$DEBUG" ] && quietly service p4d status; then
	dialog --backtitle "$pgm" --title "Count Down" \
		--hline "Press Ctrl-C to Abort" --nook --nocancel --pause \
		"Stopping p4d...\nNEXT: Backup files, replay checkpoint" \
		7 50 10
	service p4d stop || exit
	STOPPED_P4D=1
fi

#
# Move the existing db files to backup directory
#
if [ ! "$DEBUG" ]; then
	for f in db.* *.lbr journal state; do
		[ -e "$f" ] || continue
		mv -n "$f" "$back" || exit
	done
	[ -d "$back" ] && dialog --backtitle "$pgm" --title "Count Down" \
		--hline "Press Ctrl-C to Abort" --nook --nocancel --pause \
		"Files backed up to $back\nNEXT: Replay checkpoint" 7 50 10
fi

#
# Start the replay
#
[ -e "$FIFO" ] || mkfifo "$FIFO" || exit
trap 'quietly rm -f "$FIFO"' EXIT
if [ "$DEBUG" ]; then
	quietly cat "$FIFO" &
else
	cmd='p4d -r "$P4D_ROOT" -jr "$FIFO"'
	if [ "$P4D_USER" ]; then
		export P4D_ROOT FIFO cmd
		if have sudo; then
			export SUDO_PROMPT="${SUDO_PROMPT:-[sudo] Password:}"
			sudo -Eu "$P4D_USER" sh -c 'eval "$cmd" &'
		else
			export P4D_USER
			# WARNING! cmd executed by tcsh(1) (root's shell)
			su -m <<-'EOF'
			echo 'eval "$cmd" &' | su -m "$P4D_USER"
			EOF
		fi
	else
		eval "$cmd" &
	fi
fi
size=$( stat -f%z "$FILE" )
time dpv -wmo "$FIFO" -b "$pgm" -t "p4d" \
	-p "Replaying checkpoint file...\n" \
	-a "\nThis may take a while." \
	"${size:+$size:}    $FILE" "$FILE" || exit
wait $!

#
# Restart p4d if we stopped it (we'll pause for 10 seconds before starting it)
# NB: Prevents error "p4d already running?  (pid=<PID>)."
#
if [ "$STOPPED_P4D" ]; then
	dialog --backtitle "$pgm" --title "Count Down" \
		--hline "Press Ctrl-C to Abort" --nook --nocancel --pause \
		"Starting p4d..." 6 50 10
	service p4d start || exit
fi

exit $SUCCESS

################################################################################
# END
################################################################################
#
# $Copyright: 2015 Devin Teske. All rights reserved. $
#
# $Header: //guest/freebsdfrau/p4t/libexec/replay_checkpoint#1 $
#
################################################################################