#!/bin/ksh
#
# Copyright (c) 2018, 2019 Stuart Henderson <sthen@openbsd.org>
#
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

err()
{
	echo "${0##*/}: $*" >&2
	exit 1
}

usage()
{
	echo "usage: ${0##*/} [-q] [-f] [-p | -l username] rsync://upstream/path [destination]" >&2
	exit 1
}

force=false
flags=-i
fwduser=anoncvs
while getopts "fl:pq" c; do
	case $c in
		q) flags=		;;
		f) force=true		;;
		l) fwduser=$OPTARG	;;
		p) fwduser=		;;
		*) usage		;;
	esac
done
shift $((OPTIND-1))
[[ $# == [12] ]] || usage

synchost=$1
repodir=${2:-/cvs}

run_rsync()
{
	if [[ -n $fwduser ]]; then
		# reach rsync on the server via an ssh port-forward
		rsync -e "ssh -W localhost:rsync -l $fwduser" "$@" 2>&1
	else
		rsync "$@" 2>&1
	fi
}

rundir=/var/db/reposync

for i in "$rundir" "$repodir"; do
	[[ ! -d $i ]] || [[ ! -w $i ]] &&
	    err "$i must exist as a writable directory"
done

if [[ $(id -u) != $(stat -L -f "%u" "$repodir") ]]; then
	err "should be run by the uid owning the repository"
fi

oldhash=invalid
hashfile=$rundir/reposync.hash
lockfile=$rundir/reposync.lock
cd $rundir || err "could not cd to $rundir"

if [[ -h $lockfile ]]; then
	# read the pid from $lockfile symlink target
	lockedpid=$(stat -f %Y $lockfile)

	# exit if it's A) still running and B) looks like this script
	if ps -o command -p "$lockedpid" | grep -q "${0##*/}"; then
		err "already running?"
	fi

	# not still running, the lock must be stale (machine panicked, etc) so zap it
	rm -f $lockfile
fi

ln -s $$ $lockfile || err "could not lock $lockfile"

trap "rm -f $lockfile" 0 1 2 15

# check CVSROOT directory listing to identify updates; primarily for
# ChangeLog but val-tags may also be updated after a checkout was done
# using a new tag. ignore "history" (lists read-only operations).
_t=$(run_rsync --exclude='history*' "${synchost}/CVSROOT/")
[[ -n $fwduser ]] && case $_t in
	"stdio forwarding failed"*|"Stdio forwarding request failed"*)
		err "mirror does not support ssh port-forwarding" ;;
esac
newhash="${synchost} $(echo $_t | sha256)"

if [[ -r $hashfile ]]; then
	age=$(($(date +%s) - $(stat -t %s -f %m $hashfile)))
	# don't entirely rely on CVSROOT files; not all tree operations
	# result in a change there so also do a full update run at least
	# every 6h.
	if ((age < 6*60*60)); then
		oldhash=$(< $hashfile)
	fi
fi

if $force || [[ $oldhash != "$newhash" ]]; then
	# only update saved hash if sync was successful; otherwise leave
	# the old one so sync is reattempted next run
	if run_rsync -rlptz $flags --omit-dir-times --delete \
	    --exclude='#cvs.rfl.*' --exclude='CVSROOT/history*' \
	    ${synchost}/{CVSROOT,www,xenocara,ports,src} "$repodir"/; then
		echo "$newhash" > $hashfile
	else
		err "rsync failed"
	fi
fi
