#!/bin/bash

# arg1 source
# arg2 destdir
# arg3 REMOTE-HOST or BHOST

# sanity check the base dir of a btrfs filesystem
_sanity_chk_mount()
{
	local MNT_OPTS OPT
	local FS="$1"

	MNT_OPTS=`grep -w "^$FS" /proc/mounts`
	if [ -z "$MNT_OPTS" ] ; then
		echo "It appears $FS is not mounted."
		return 1
	fi

	# look for /dev/ at the beginning of the mounts entry
	grep -q '^/dev/' <<<"${MNT_OPTS%% *}" || {
		echo "$FS is not mounted on a device.  It must at least be"
		echo "mounted on a loop device."
		return 1
	}

	# make sure it's a btrfs file system
	# eat the "^/dev/whatev $FS " part of the mounts entry
	MNT_OPTS="${MNT_OPTS#*$FS }"
	if [ "${MNT_OPTS%% *}" != btrfs ] ; then
		echo "$FS is not a btrfs filesystem."
		return 1
	fi

	# check for proper subvolid - heir copy can only be done on-a-top
	IFS=, set -- $MNT_OPTS
	for OPT ; do
		if [[ "$OPT" == @(subvolid=5|subvol=/) ]] ; then
			# it's correct
			return 0
		fi
	done

	echo "Filesystem '$FS' must be the TOP filesystem.  subvolid=5 or subvol=/"
	return 1
}

# copy ssched NSRC from one btrfs mount point to another - along with
# all its scheduled snapshots, preserving parent-child relationships so
# snapsched can take up where it left off, including backups.


declare -A CONFIG
export CONFIG
ETC=/etc
export PRE=/usr/local
export ETCDIR=$ETC/snapsched
export CFGFILE=$ETCDIR/config
export TMPBASE=/tmp/ssched

exec 2>&1

# load the library routines
. $PRE/lib/snapsched/snapsched-funcs

# send a single snapshot to the backup host
cp_snap()
{
	local BHOST="$1"
	local SVOL="$2"
	local RECV_DST="$3"
	local SARGS
	local C
	local ES=0

	shift;shift;shift

	# send in the clones
	for C in "${SNAP_SENT_LST[@]}" ; do
		SARGS+=" -c ${C#* }"
	done

	# if this is the first snap EVER to be sent, then
	# SARGS will be the the null string, so the entire snap will be sent.
	# otherwise, previously sent snaps will be mentioned as possible
	# clone sources, and btrfs can supposedly figure out the parent
	# relationship.  however, if it fails to do that because the snapshots
	# have been screwed with, then this will fail.

	omsg "  sending ${SVOL#*/} to ${BHOST:+$BHOST:}$BHOSTFS"

	TMP_OUTPUT=`mktemp /tmp/snapsched-snapsend-output.XXXX`

	if [ "$BHOST" ] ; then
		SSH_CMD="ssh -x -o ServerAliveInterval=300 -o ServerAliveCountMax=2 $BHOST"
	else
		SSH_CMD=
	fi

	# send the snapshot to backup server BHOST, with a 10 minute timeout
#	echo "btrfs send '$SARGS' '$SVOL' | $SSH_CMD \
#			btrfs rec '$RECV_DST' &> $TMP_OUTPUT"
	btrfs send $SARGS "$SVOL" | $SSH_CMD \
			btrfs rec "$RECV_DST" &> $TMP_OUTPUT

	BTRFS_SND=${PIPESTATUS[0]} BTRFS_RCV=${PIPESTATUS[1]}
	# BTRFS_SND=0 BTRFS_RCV=0

	# for now, let's look at this
	sed 's/^/    /' "$TMP_OUTPUT"

	if [ "$BTRFS_SND" -ne 0 -o "$BTRFS_RCV" -ne 0 ] ||
		egrep -q 'ERROR|Timeout' "$TMP_OUTPUT" ; then
		#cat "$TMP_OUTPUT"
		omsg "Failure sending '${SVOL#*/}' to ${BHOST:+$BHOST:}$BHOSTFS"
		omsg "btrfs send args: $SARGS $SVOL"
		ES=1
	fi

	rm -f "$TMP_OUTPUT"

	return $ES
}

# check to see if the snapshot argument (string: "UUID <snap-path>")
# already exists on the backup host
# uses the global array RSNAPS and RX (the number of elements in RSNAPS)
_already_sent()
{
	local S="$1"
	local -i X

	for ((X = 0; X < RX; X++)) ; do
		# check the UUID of the snap with all the UUIDs of already sent snaps
		# check the name.  apparently the uuid can change under certain conds
		if [ "${S#*$NSRC/}" = "${RSNAPS[X]#*$NSRC/}" ] ; then
			# it matches, so yes, it's already been sent
			return 0
		fi
	done

	# no matches, so no, it hasn't been sent
	return 1
}


_rsnaps2send()
{
	local -n DARR=$1 SARR=$2
	local I
	local -i D
	local LSNAP

	# clear out the destination array which might have old values
	for I in "${!DARR[@]}" ; do
		unset DARR[$I]
	done

	D=0
	for I in "${!SARR[@]}" ; do
		LSNAP=${SARR[$I]#* }
		# remove the leading path ending with this hostname
		# and replace it with CONFIG[SNAP_BASE_DIR (.ssched)
		LSNAP=${CONFIG[SNAP_BASE_DIR]}${LSNAP#*`hostname`}
		if [ -d "${CONFIG[SNAP_MOUNT_DIR]}/$LSNAP" ] ; then
			DARR[D++]="$LSNAP"
		fi
	done
}


# first arg is snapshot source
#echo "arguments for snapback are: " "$@"

NSRC="$1"
shift

# validate NSRC arg, and read in config file
# no usage message, since this is called by cron.  supposedly.
_ssched_validate_nsrc NSRC "" ||
	exit 1

declare -i SX RX
declare -a SSNAPS RSNAPS

declare -a SNAP_SENT_LST
declare -a SNAP_XFER_LST
declare -a TPDIRS

# set -x

_ssched_mount_rootvol "${CONFIG[SNAP_MOUNT_DIR]}" || {
	ES=$?
	omsg "Unable to mount root volume '${CONFIG[SNAP_MOUNT_DIR]}': $ES"
	if [ ! -d "${CONFIG[SNAP_MOUNT_DIR]}" ] ; then
		omsg "Directory '${CONFIG[SNAP_MOUNT_DIR]}' does not exist."
	elif [ "$ES" -eq 4 ] ; then
		omsg "Perhaps ${CONFIG[SNAP_MOUNT_DIR]} needs to be configured in /etc/fstab?  Snapsched requires that."
	fi
	exit 1
}

# don't let the mount point be unmounted in the middle of this
cd ${CONFIG[SNAP_MOUNT_DIR]}

# get the list of relevant snaps on this host
_ssched_bsub_list "$NSRC" "" "hourly" SSNAPS "${CONFIG[SNAP_MOUNT_DIR]}"
SX=${#SSNAPS[*]}

if [ -z "${SSNAPS[0]}" ] ; then
	omsg "No snaps to send - SSNAPS[0]=NULL"
	exit 0
fi

if [ "$SX" -eq 0 ] ; then
	# nothing to do?
	omsg "Num snaps to send: $SX"
	exit 0
fi

set +x

BHOSTFS=$1

BHOST=$2

SLEEP_S=180

TBASE="${CONFIG["SNAP_MOUNT_DIR"]}/${CONFIG["SNAP_BASE_DIR"]}/$NSRC"

BSDIR=$BHOSTFS/${CONFIG["SNAP_BASE_DIR"]}/$NSRC

# get the list of snaps on the remote host for this nsrc/intval
RSNAPS=()
_ssched_bsub_list "$NSRC" "$BHOST" "hourly" RSNAPS $BHOSTFS
RX=${#RSNAPS[*]}

SNAP_XFER_LST=()
TPDIRS=()

# cleans out SNAP_SENT_LST for us
_rsnaps2send SNAP_SENT_LST RSNAPS

# create xfer list and possible target directories
for SNAP in "${SSNAPS[@]}" ; do
	# this func uses RSNAPS and RX as global variables
	if _already_sent "$SNAP" "$NSRC" ; then
		continue
	fi
	SNAP_XFER_LST+=(${SNAP#* })
	# if not oldbash, create interval dirs based on what snaps are
	# being sent.
	_sort -u TPDIRS `egrep -o "hourly|daily|weekly|monthly" <<<"${SNAP#* }"`
done

if [ "${#SNAP_XFER_LST[*]}" -gt 0 ] ; then
	if [ "${#SNAP_XFER_LST[*]}" -gt 1 ] ; then
		V=are
		S=s
	else
		V=is
		S=
	fi
	omsg "There $V ${#SNAP_XFER_LST[*]} snapshot$S to be sent to ${BHOST:+$BHOST:}${BHOSTFS}"

	if [ "$BHOST" ] ; then
		SSH_CMD="ssh -x -o ServerAliveInterval=300 -o ServerAliveCountMax=2 $BHOST"
	else
		SSH_CMD=
	fi
	# create target directories on backup host if needed
	for D in "${TPDIRS[@]}" ; do
#		echo $SSH_CMD mkdir -p "$BSDIR/$D"
		$SSH_CMD mkdir -p "$BSDIR/$D" || {
			omsg "mkdir '${BHOST:+$BHOST:}$BSDIR/$D' failed"
			# bail on this backup host
			continue
		}
	done

	SSH_CMD=

	# send the snaps
	for SNAP in "${SNAP_XFER_LST[@]}" ; do
		D=`dirname "$SNAP"`
		D=`basename "$D"`
		# echo "cp_snap 'ssh -x' '$SSHZ_ARG' '$BHOST' '$SNAP' '$BSDIR/$D'"
		cp_snap "$BHOST" "$SNAP" "$BSDIR/$D" &&
			SNAP_SENT_LST+=("$SNAP") || {
				omsg -e "Sending failure for snap '$SNAP' DEST='${BHOST:+$BHOST:}$BSDIR/$D'\n" "SNAP_SENT_LST=(${SNAP_SENT_LST[*]})\n"
				omsg -e "SNAP_XFER_LST=(${SNAP_XFER_LST[*]})\n"
				break
			}
	done
fi

# release the mount point
cd - >/dev/null

_ssched_umount_rootvol "${CONFIG[SNAP_MOUNT_DIR]}"

