#!/bin/bash

export UMNT

# max allowed configurable snap count by interval type. completely arbitrary.
declare -A IMAXSNAPS
IMAXSNAPS[hourly]=36
IMAXSNAPS[daily]=14
IMAXSNAPS[weekly]=10
IMAXSNAPS[monthly]=24
# ${!IMAXSNAPS[*]} is the official list of scheduled snapshot intervals

# interval values that are "lesser" than the index interval
declare -A LESSER_INTS
LESSER_INTS[hourly]=
LESSER_INTS[daily]="hourly"
LESSER_INTS[weekly]="hourly daily"
LESSER_INTS[monthly]="hourly daily weekly"

export CRONPROG=$PRE/lib/snapsched/scheduled

export GSUDOCMD=sudo

export SNAP_VAR=/var/lib/snapsched

export LFT_PID		# contains the PID of the lockfile-touch program that
					# keeps touching the lockfile.  naughty.  used by the
					# _lockfile_p/v methods

OLDBASH=false
if [ "${BASH_VERSINFO[0]}" -le 4 ] ; then
	# we're screwed
	OLDBASH=true
	if [ \( "${BASH_VERSINFO[0]}" -eq 4 \) -a \
		 \( "${BASH_VERSINFO[1]}" -gt 2 \) ] ; then
		# we're not screwed! (PIPESTATUS)
		OLDBASH=false
	fi
fi
export OLDBASH

# utility to write optional text to stdout or what. ev.
# uses global OSILENT variable
omsg ()
{
	if [ -z "$OSILENT" ] ; then
		if [ "$#" -ge 1 ] ; then
			echo "$@"
		else
			cat
		fi
	elif [ "$#" -lt 1 ] ; then
		cat >/dev/null
	fi
}

# utility for echo/cat to stderr
serr()
{
	if [ "$#" -ge 1 ] ; then
		echo "$@" >/dev/stderr
	else
		cat >/dev/stderr
	fi
}


# insert a string into a sorted array of strings
# called with first arg "-u" then don't add string if it's already in there
_sort_a()
{
	local -n STRS
	local -i I N
	local S
	local T
	local UNIQ=false
	local OP

	OP="$1"
	shift

	if [ "$1" = "-u" ] ; then
		UNIQ=true
		shift
	fi

	STRS="$1"
	S="$2"

	# three quick optimizations
	if [ -z "$S" ] ; then
		return
	fi
	if [ "${#STRS[*]}" -eq 0 ] ; then
		STRS[0]="$S"
		return
	fi
	if [ "$S" "$OP" "${STRS[-1]}" ] ; then
		STRS+=($S)
		return
	fi

	for ((I = 0; ; I++)) ; do
		if [ "$S" "$OP" "${STRS[$I]}" ] ; then
			continue
		fi
		# then S is less/greater-than or equal-to lexigraphically
		if $UNIQ ; then
			if [ "$S" = "${STRS[I]}" ] ; then
				return
			fi
		fi
		for ((N = I; ; N++)) ; do
			T="${STRS[N]}"
			STRS[N]="$S"
			if [ -z "$T" ] ; then
				break
			fi
			S="$T"
		done
		break
	done
}


_sort()
{
	_sort_g "$@"
}


_sort_g()
{
	_sort_a '>' "$@"
}


_sort_l()
{
	_sort_a '<' "$@"
}


# make one array the same as another
# $1 is the destination array
# $2 is the source array
#__a_assignment()
#{
#	local -n DARR=$1 SARR=$2
#	local I
#
#	for I in "${!DARR[@]}" ; do
#		unset DARR[$I]
#	done
#	for I in "${!SARR[@]}" ; do
#		DARR[$I]=${SARR[$I]}
#	done
#}


# create the TEMPBASE temp directory
_ssched_mktmpdir()
{
	if [ ! -d $TMPBASE ] ; then
		mkdir -p $TMPBASE
	fi
	echo $TMPBASE
}


# create a temp file of name $1 in $TMPBASE
_ssched_mktemp()
{
	local TMPFILE=`basename $1`
	local TMPDIR

	TMPDIR=`_ssched_mktmpdir`
	touch $TMPDIR/$TMPFILE
	echo $TMPDIR/$TMPFILE
}


# remove a temp file of name $1 in $TMPBASE, and rmdir $TMPBASE if empty
_ssched_rmtemp()
{
	local TMPFILE=`basename $1`

	rm -f $TMPBASE/$TMPFILE >/dev/null 2>&1
	# if [ -d $TMPBASE ] ; then
		# this next part is super racy.  i hope there isn't a hundred
		# ssched jobs running on the same machine one day
		# leave it
		#rmdir $TMPBASE >/dev/null 2>&1
	# fi
}


# translate interval to single uppercase letter
# arg: {hourly,daily,weekly,monthly}
_ssched_int2u()
{
	local -u I=${1:0:1}
	echo $I
}

# translate interval arg to nominalized interval name
_ssched_nom_int()
{
	local -l I

	case "$1" in
		hourly|daily|weekly|monthly)
			I=$1
		;;
		h|hour|hourlies)
			I=hourly
		;;
		d|day|dailies)
			I=daily
		;;
		w|week|weeklies)
			I=weekly
		;;
		m|month|monthlies)
			I=monthly
		;;
	esac

	echo $I
}


# translate interval to snapsched sub dir for that interval
# arg: {hourly,daily,weekly,monthly}
_ssched_int2dir()
{
	local -u I

	I=`_ssched_nom_int $1`
	echo ${CONFIG[SNAP_${I}_DIR]}
}


# translate user input for boolean flag to true or false
_ssched_input2bool()
{
	local -l UIN="$1"
	local FLAG

	case "$UIN" in
		on|yes|y|true)
			FLAG=true
			;;
		off|no|n|false)
			FLAG=false
			;;
		*)
			FLAG=
			return 1
			;;
	esac

	echo "$FLAG"
}


# read in the config file
_ssched_read_config()
{
	if [ ! -s $CFGFILE ] ; then
		serr "Config file '$CFGFILE' is empty or non-existent."
		return 1
	fi
	if [ -r $CFGFILE ] ; then
		. $CFGFILE
	else
		serr "Config file '$CFGFILE' is not readable by you. (EPERM)"
		return 1
	fi
}


if $OLDBASH ; then
_ssched_output_config()
{
	local X
	for X in "${!CONFIG[@]}" ; do
		echo "CONFIG[$X]=\"${CONFIG[$X]}\""
	done
}
else

_ssched_output_config()
{
	local S X
	local -a XES
	local TOPX="SNAP_MOUNT_DIR SNAP_BASE_DIR SNAP_HOURLY_DIR SNAP_DAILY_DIR SNAP_WEEKLY_DIR SNAP_MONTHLY_DIR"

	for X in $TOPX ; do
		echo "CONFIG[$X]=\"${CONFIG[$X]}\""
	done
	for S in `_ssched_list_cfg_srcs` ; do
		echo "CONFIG[SSRC_$S]=\"${CONFIG[SSRC_$S]}\""
		for X in "${!CONFIG[@]}" ; do
			if expr match "$X" "SSRC_$S%" >/dev/null ; then
				_sort_l XES "$X"
			fi
		done
		for X in ${XES[*]} ; do
			echo "CONFIG[$X]=\"${CONFIG[$X]}\""
		done
		unset XES
	done

	# currently there are no more config variables, but let's just
	# make things a little robust against future changes
	for X in "${!CONFIG[@]}" ; do
		if expr match "$X" "SSRC_" >/dev/null ; then
			continue
		fi
		for S in $TOPX ; do
			if [ "$X" = "$S" ] ; then
				continue 2
			fi
		done
		echo "CONFIG[$X]=\"${CONFIG[$X]}\""
	done
}
fi

_ssched_write_config()
{

	# clear config file
	mv -f $CFGFILE ${CFGFILE}.bak 2>/dev/null

	_ssched_output_config > $CFGFILE
}

# give us a list of snapshot sources from the config
_ssched_list_cfg_srcs()
{
	local C
	local -a SRCS_LIST

	if $OLDBASH ; then
		echo ${!CONFIG[*]} | grep -o 'SSRC_[^ ]*' | grep -v \% | sed 's/^SSRC_//'
	else
		for C in "${!CONFIG[@]}" ; do
			if [ "$C" = "${C#SSRC_}" ] ; then
				continue
			fi
			if [ "$C" = "${C%\%*}" ] ; then
				_sort SRCS_LIST "${C#SSRC_}"
			fi
		done
		echo ${SRCS_LIST[*]}
	fi

# alternate method.  which is better?
#	echo ${!CONFIG[*]} | grep -o 'SSRC_[^ ]*' | grep -v \% | sed 's/^SSRC_//'
}

# mount the btrfs subvolid=0 filesystem
# FIXME if we do, or if we don't, we need some way to hold the mount, so
# that some dorkwad doesn't come along and umount it while we're doing stuff
_ssched_mount_rootvol()
{
	local BPATH="$1"

	UMNT=false

	if [ \( ! "$BPATH" \) -o \( ! -d "$BPATH" \) ] ; then
		return 1
	fi

	if ! grep -qw "$BPATH" /proc/mounts ; then
		$SUDOCMD mount "$BPATH" || return $?
		UMNT=true
	fi
}

# umount the btrfs subvolid=0 filesystem
# the second [optional] argument is either {"NO_WAIT"|0} or 1-30 for
# nsecs to sleep.  otherwise, DO wait, and sleep time is 30s
_ssched_umount_rootvol()
{
	local BPATH="$1"
	local W         # W for wait
	local SLEEP_SECS=30
	local NO_WAIT=false

	if [ "$2" ] ; then
		case "$2" in
			NO_WAIT|0)
				NO_WAIT=true
				;;
			1-30)
				SLEEP_SECS=$2
				;;
		esac
	fi

	if [ "$BPATH" -a -d "$BPATH" ] ; then
		if $UMNT ; then
			if $NO_WAIT ; then
				$SUDOCMD umount -l "$BPATH" >/dev/null 2>&1
			else
				# wait for 6 * SLEEP_SECS seconds for some other process to
				# finish, most likely another snapsched process is using it,
				# like snapback
				for W in 1 2 3 4 5 6 ; do
					# try to umount.  if successful, break
					UMNT_OUTPUT=`$SUDOCMD umount "$BPATH" 2>&1` && break

					# if it's not mounted, break out of here
					head -1 <<<"$UMNT_OUTPUT" | egrep "not mounted$" && break

					# head -1 <<<"$UMNT_OUTPUT" | egrep "target is busy$"

					sleep $SLEEP_SECS
				done
			fi
		fi
	fi
}


# delete specific cron entry in $ETC/cron.{hour,dai,week,month}ly/ directories
_ssched_remove_cron()
{
	local NSRC=$1
	local INT=$2
	local CRND=$ETC/cron.$INT/${INTCRONFILE_BASE}_`_ssched_nsrc2cronfile "$NSRC"`

	rm -f "$CRND"
}


# list existing snapshots in a single src/interval
# args: 2 - source name
# args: 2 - {count|list} count: returns # of snaps
#						 list: list snaps w/ optional tab indents
#		3 - interval name
#		4 - [optional] number of tabs to indent each line of output
_ssched_lssnap_single()
{
	local SDIR="${CONFIG[SNAP_MOUNT_DIR]}/${CONFIG[SNAP_BASE_DIR]}/$1"
	local COUNT=false
	local -u INT=$3
	local NTABS=$4
	local TABSTR
	local T

	if [ "$2" = count ] ; then
		COUNT=true
	elif [ "$2" = list ] ; then
		COUNT=false
	else
		# usage: must be count or list
		return 2
	fi
	if [ ! -d $SDIR/${CONFIG[SNAP_${INT}_DIR]} ] ; then
		$COUNT && echo 0
		return 1
	fi
	if [ -z "$NTABS" ] ; then
		NTABS=0
	elif [ "$NTABS" -gt 4 ] ; then
		NTABS=4   # max tabs is arbitrarily set to 4
	fi

	if ! $COUNT ; then
		for ((T=NTABS; T; T--)) ; do
			TABSTR+="\t"
		done
	fi

	ls $SDIR/${CONFIG[SNAP_${INT}_DIR]} | sed "s/^/$TABSTR/" | (
		$COUNT && wc -l || cat
	)
}


#command
ssched_list_sources()
{
	local S SN SRCS DBS
	local MSTR MWIDTH
	local USTR="ssched_list_sources [-l | -d] [<source-spec>]"

	_ssched_read_config

	SRCS=`_ssched_list_cfg_srcs`

	if [ "$2" ] ; then
		if ! grep -qsw "$2" <<<"$SRCS" ; then
			omsg "Source '$2' not configured."
			omsg "$USTR"
			return 1
		fi
		SRCS="$2"
	fi

	if [ "$1" = -l ] ; then
		echo -e "\t     Max Max Max Max Hourly    Daily          Weekly   Monthly"
		echo -e "Fileset\t   hours day  wk mon    TOD  DAYS/TOD        DAY/TOD   DOM/TOD"

		for S in $SRCS ; do
			if [ ${#S} -gt 12 ] ; then
				SN=${S:0:11}
			else
				SN="$S"
			fi
			# FIXME if snaps are off for that int, say "off" rather than "def"
			# yes, we are writing config vars, but we're not saving them out
			if [ "${CONFIG[SSRC_$S%H_MSC]}" = 0 ] ; then
				CONFIG[SSRC_$S%H_TOD]="off "
			fi
			if [ "${CONFIG[SSRC_$S%D_MSC]}" = 0 ] ; then
				CONFIG[SSRC_$S%D_DAT]=off
				CONFIG[SSRC_$S%D_TOD]=off
			fi
			if [ "${CONFIG[SSRC_$S%W_MSC]}" = 0 ] ; then
				CONFIG[SSRC_$S%W_DAT]=off
				CONFIG[SSRC_$S%W_TOD]=off
			fi
			if [ "${CONFIG[SSRC_$S%M_MSC]}" = 0 ] ; then
				CONFIG[SSRC_$S%M_DAT]=off
				CONFIG[SSRC_$S%M_TOD]=off
			fi
			MSTR="${CONFIG[SSRC_$S%M_DAT]:-def}/${CONFIG[SSRC_$S%M_TOD]:-def}"
			MWIDTH=${#MSTR}
			printf "%-14s%2d%4d%4d%4d %6s  %-16s%-10s%-s\n" "$SN" \
				${CONFIG[SSRC_$S%H_MSC]} \
				${CONFIG[SSRC_$S%D_MSC]} \
				${CONFIG[SSRC_$S%W_MSC]} \
				${CONFIG[SSRC_$S%M_MSC]} \
				"${CONFIG[SSRC_$S%H_TOD]:-def }" \
				"${CONFIG[SSRC_$S%D_DAT]:-def}/${CONFIG[SSRC_$S%D_TOD]:-def}" \
				"${CONFIG[SSRC_$S%W_DAT]:-def}/${CONFIG[SSRC_$S%W_TOD]:-def}" \
				"$MSTR"
		done
	elif [ "$1" = -d ] ; then
		echo -e "Fileset                Databases"
		for S in $SRCS ; do
			if [ ${#S} -gt 22 ] ; then
				SN=${S:0:21}
			else
				SN="$S"
			fi
			if [ "${CONFIG[SSRC_$S%DBS]}" ] ; then
				DBS="${CONFIG[SSRC_$S%DBS]//,/ }"
			else
				DBS=none
			fi
			printf "%-22s %-s\n" "$SN" "$DBS"
		done
	else
		echo $SRCS
	fi
}


# list snapsched snapshots for optional [source [interval[,interval...]]]
#command
ssched_lssnap()
{
	local NSRC
	local I S SRCS_LIST SDIR
	local -i X
	local -a INT_LIST
	local -u IUP
	local USTR="lssnap [[<fs-source-fs>] [<interval-list>]]\ninterval list is a comma separated list from h[ourly], d[aily], w[eekly], m[onthly]"

	# optional source name
	if [ "$#" -ge 1 ] ; then
		NSRC="$1"
		_ssched_validate_nsrc NSRC "No source '$NSRC' found." || return 1
		# in this case, the list is the single specified src
		SRCS_LIST="$NSRC"
		shift

		# non-optional interval list
		if [ "$#" -ge 1 ] ; then
			INT_LIST=(${1//,/ })
			for X in ${!INT_LIST[*]} ; do
				S="${INT_LIST[X]}"
				INT_LIST[X]=`_ssched_nom_int "$S"`
				if [ -z "${INT_LIST[$X]}" ] ; then
					serr "$USTR"
					serr "$0: invalid interval argument: '$S'"
					serr " interval argument is a [comma separated] list of one"
					serr " or more of {h[ourly], d[aily], w[eekly], m[onthly]}"
					return 1
				fi
			done
		else
			INT_LIST=(${!IMAXSNAPS[*]})
		fi
	else
		_ssched_read_config
		SRCS_LIST="`_ssched_list_cfg_srcs`"
		INT_LIST=(${!IMAXSNAPS[*]})
	fi

	_ssched_mount_rootvol ${CONFIG[SNAP_MOUNT_DIR]} || return $?

	for S in $SRCS_LIST ; do
		echo "${S}:"
		SDIR="${CONFIG[SNAP_MOUNT_DIR]}/${CONFIG[SNAP_BASE_DIR]}/$S"
		for I in ${INT_LIST[*]} ; do
			# FIXME we need to extract things like the gen, ogen, otime
			# local ID GEN OGEN barf OD OT P
			# printf "%7s %7s %7s %10s %8s %s\n" ID GEN OGEN OD OT name
			# btrfs sub list -st ${CONFIG[SSRC_$S]} |
			#	while read ID GEN OGEN barf OD OT P ; do
			#  if [ \( "$ID" = ID \) -o \( "$ID" = '--' \) ] ; then
			#	continue
			#  fi
			#  grep -q "/$S/${CONFIG[SNAP_${INTVAL}_DIR]}" <<<"$P" ||
			#	continue
			#  printf "%7s %7s %7s %s %s %s\n" $ID $GEN $OGEN $OD $OT ${P##*/}`
			# done
			# another option is to use btrfs sub show, which lists snapshots
			# in their heirarchical order, which turns out to be cool
			# we could add a -h option to do that, and leave the interval
			# directory on the snapshot name so the user can see what type it is
			IUP=$I
			if [ -d $SDIR/${CONFIG[SNAP_${IUP}_DIR]} ] ; then
				echo "    ${I%y}ies:"
				_ssched_lssnap_single $S list $IUP 1
			fi
		done
	done

	_ssched_umount_rootvol ${CONFIG[SNAP_MOUNT_DIR]}
}


#command -- to delete cron entries
ssched_delete_cronjobs()
{
	local NSRC=$1

	if [ -z "$NSRC" ] ; then
		NSRC='*'
	elif ! _ssched_validate_nsrc NSRC \
		"usage: delete_cronjobs [<source-name>]" ; then

		return 1
	fi

	for I in ${!IMAXSNAPS[*]} ; do
		_ssched_remove_cron "$NSRC" ${I}
	done
}


#command -- to create cron entries
ssched_create_cronjobs()
{
	local NSRC=$1
	local -l NO_READ_CFG=${2:0:1}
	local DARG TOD
	local JOB

	if ! _ssched_validate_nsrc NSRC "usage: create_cronjobs <source-name>" $NO_READ_CFG; then
		return 1
	fi

	for JOB in ${!IMAXSNAPS[*]} ; do

		if [ -z "${CONFIG[SSRC_${NSRC}%`_ssched_int2u $JOB`_MSC]}" ] ; then
			serr "cannot create cronjob $JOB for '$NSRC' because of invalid MSC"
			return 1
		fi
		# if max snap count is 0, then snaps at that interval are disabled
		if [ "${CONFIG[SSRC_${NSRC}%`_ssched_int2u $JOB`_MSC]}" -eq 0 ] ; then
			continue
		fi

		DARG="${CONFIG[SSRC_${NSRC}%`_ssched_int2u $JOB`_DAT]}"
		TOD="${CONFIG[SSRC_${NSRC}%`_ssched_int2u $JOB`_TOD]}"

		# If its an interval for a source that should be in cron.d, then do that
		if [ "$DARG" -a "$TOD" ] ; then
			eval ssched_set_${JOB%y}ies "$NSRC" "$DARG" "$TOD" YES

			# make sure the old $ETC/cron.{dai,week,month}ly is history
			_ssched_remove_cron $NSRC $JOB
		else # otherwise, add a cron.{interval} file for default dot/times
			_ssched_submit_cron $NSRC $JOB
		fi
	done
}


# initialize package crontab file in $ETC/cron.d
#    normally this should not be needed, as minimum $ETC/cron.d/snapsched
#    file should be included in package
_ssched_init_pkgcrontab()
{

	if [ ! -r $CRONTAB ] ; then
		cat <<-PKGCRONTABEND > $CRONTAB
			# snapsched package crontab
			# WARNING: this file is programmatically modified by the snapsched
			#    command without benefit of locking, so, if you hand edit, you
			#    might mess it up.  You have been warned sufficiently.
			SHELL=/bin/bash
			PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin

		PKGCRONTABEND

	fi
}


# remove one line of package crontab file in $ETC/cron.d
# args: NSRC {hourly,daily,weekly,monthly}
_ssched_rm_pkgcrontab()
{
	local CFILE=$CRONTAB

	if [ ! -r $CFILE ] ; then
		return 1
	fi

	# entry may not exist
	sed -i "/^# $1 $2/,+1 d" $CFILE || true
}


# modify entry in package crontab file in $ETC/cron.d
# args: NSRC {hourly,daily,weekly,monthly} DARG CTIMES [CPROG]
_ssched_mod_pkgcrontab()
{
	local CFILE=$CRONTAB
	local NSRC="$1"

	if [ ! -r $CFILE ] ; then
		_ssched_init_pkgcrontab
	fi

	# previous entry may not exist
	_ssched_rm_pkgcrontab $NSRC $2 || true

	# FIXME should use the sed 'c' command
	echo "# $NSRC $2 DARG:'$3'" >> $CFILE
	echo -e "$4\troot\t$5 $CRONPROG $NSRC $2" >> $CFILE
}


#command -- to initialize the config file
ssched_init_config()
{
	if [ ! -d "$ETCDIR" ] ; then
		mkdir -p "$ETCDIR"
	fi

	# will overwrite anything existing

	CONFIG["SNAP_MOUNT_DIR"]=/media/btrfs
	CONFIG["SNAP_BASE_DIR"]=.ssched
	CONFIG["SNAP_HOURLY_DIR"]=hourly
	CONFIG["SNAP_DAILY_DIR"]=daily
	CONFIG["SNAP_WEEKLY_DIR"]=weekly
	CONFIG["SNAP_MONTHLY_DIR"]=monthly
	CONFIG["NOISY"]=daily

	_ssched_write_config
	ssched_delete_cronjobs
	_ssched_init_pkgcrontab
}


#command
ssched_print_config()
{
	_ssched_read_config && _ssched_output_config || {
		echo "Error reading config file.   Perhaps it doesn't exist."
	}
}


# acquire a lock
_ssched_lockfile_p()
{
	local LFILE="$1"

	/usr/bin/lockfile-create -q --lock-name "$LFILE" || {
		serr "failed to get lock on file '$LFILE'"
		return 1
	}
	/usr/bin/lockfile-touch --lock-name "$LFILE" &
	LFT_PID=$!
}

# release a lock
_ssched_lockfile_v()
{
	local LFILE="$1"

	if [ "$LFT_PID" ] ; then
		kill $LFT_PID
	fi
	LFT_PID=
	/usr/bin/lockfile-remove --lock-name "$LFILE"
}



_ssched_mysql_pause()
{
	local VNAME RESTO LCNT=0

	mysqladmin --defaults-extra-file=/etc/mysql/debian.cnf ping >/dev/null 2>&1
	if [ "$?" -ne 0 ] ; then
		echo "mysqld is not alive, cannot quiesce database!"
		return 1
	fi

	# -n means flush the buffer after each query
	coproc mysql --defaults-extra-file=/etc/mysql/debian.cnf -s -n

	if [ "$?" -ne 0 ] ; then
		echo "Failed to start coprocess: $0"
		return 1
	fi

	# save the value for innodb_max_dirty_pages_pct
	echo "show global variables;" > /dev/fd/${COPROC[1]}
	export MDPP=74
	while read -t 1 < /dev/fd/${COPROC[0]} VNAME MDPP ; do
		if [ "$VNAME" = innodb_max_dirty_pages_pct ] ; then
			break
		fi
	done
	while read -t 1 < /dev/fd/${COPROC[0]} RESTO ; do : ; done
	# echo "Saving innodb_max_dirty_pages_pct variable value $MDPP"

	# set it to zero
	echo "set global innodb_max_dirty_pages_pct=0;" > /dev/fd/${COPROC[1]}

	# flush and lock the tables
	echo "flush tables with read lock;" > /dev/fd/${COPROC[1]}

	# clear out the buffer
	while read -t 1 < /dev/fd/${COPROC[0]} RESTO ; do : ; done

	# wait for quiescence of innodb engine
	echo "show engine innodb status\G" > /dev/fd/${COPROC[1]}
	sleep 0.25
#echo "in thread wait loop"
	while read -t 1 < /dev/fd/${COPROC[0]} RESTO ; do
#echo "RESTO='$RESTO'"
		echo "$RESTO" | grep -q "^Main thread" || continue
		echo "$RESTO" |	grep -q "state: waiting for server activity" && break
#echo "non-matching RESTO: '$RESTO'"
# echo "$RESTO" |	grep -q "state: sleeping" && break

		# check to see if we need to bail 'cuz ain't hapnin
		LCNT=$(($LCNT + 1))
		if [ "$LCNT" -gt 30 ] ; then
			echo "Waiting for mysql innodb engine to quiesce timed out."
			echo "Cannot quiesce mysql database."
			echo "set global innodb_max_dirty_pages_pct=$MDPP;" > /dev/fd/${COPROC[1]}
			echo "unlock tables;" > /dev/fd/${COPROC[1]}
			echo -E "\q"  > /dev/fd/${COPROC[1]}
#echo "out thread wait loop"
			return 1
		fi

		while read -t 1 < /dev/fd/${COPROC[0]} TESTO ; do
			[ -z "$TESTO" ] && break
			TESTO=""
		done
		RESTO=
		# oh yeah, just spin for freakin ever
		echo "show engine innodb status\G" > /dev/fd/${COPROC[1]}
		sleep 0.25
#echo "LOOPING ==============================================================="
	done
#echo "out thread wait loop"
	while read -t 1 < /dev/fd/${COPROC[0]} RESTO ; do : ; done
#echo "mysql DB is paused"
	return 0
}

_ssched_mysql_unpause()
{

#echo "COPROC_ = '$COPROC'"
#echo "COPROC = '${COPROC[*]}'"

	if [ "${COPROC[1]}" ] ; then
		# restore innodb_max_dirty_pages_pct setting
		#echo "Restoring innodb_max_dirty_pages_pct variable value to $MDPP"
		echo "set global innodb_max_dirty_pages_pct=$MDPP;" > /dev/fd/${COPROC[1]}

		# unlock the tables
		echo "unlock tables;" > /dev/fd/${COPROC[1]}
		#echo "mysql DB is unpaused"

		# quit out of mysql client coprocess
		echo -E "\q"  > /dev/fd/${COPROC[1]}
	fi

	# might be some noise here
}

_ssched_pause_dbs()
{
	local CFGDBS="SSRC_${1}%DBS"
	local PES

	if [ -z "$1" ] ; then
		# nothing to do here
		return 0
	fi

	for DBP in ${CONFIG["$CFGDBS"]} ; do
		case $DBP in
			mysql)
				_ssched_mysql_pause
				PES=$?
				;;
			postgres*)
				#echo magic postgres incant to pause $DBP dbs
				PES=$?
				;;
			*)
				# FIXME nothing to see here
				;;
		esac
	done

	return $PES
}


_ssched_unpause_dbs()
{
	local CFGDBS="SSRC_${1}%DBS"

	if [ -z "$1" ] ; then
		# nothing to do here
		return 0
	fi

	for DBP in ${CONFIG["$CFGDBS"]} ; do
		case $DBP in
			mysql)
				_ssched_mysql_unpause
				;;
			postgres*)
				#echo magic postgres incant to unpause $DBP
				;;
			*)
				# FIXME nothing to see here
				;;
		esac
	done
}


# convert the NSRC source name to a file name acceptable by the cron idiots
_ssched_nsrc2cronfile()
{
	local SRC

	SRC="${1//@/at}"
	echo "${SRC//./-}"
}


# submit cron jobs for scheduled filesystem snapshots
_ssched_submit_cron()
{
	# args: interval {hourly, daily, weekly, monthly}, NSCR

	local NSRC=$1
	local INT=$2
	local CRND=$ETC/cron.$INT/${INTCRONFILE_BASE}_`_ssched_nsrc2cronfile "$NSRC"`

	# work around for stupid cron restriction on file names. of all the stupid.

	# this will overwrite existing, on purpose
	>$CRND
	cat >>$CRND <<THERE
#!/bin/sh
MAILTO=root

$CRONPROG $NSRC $INT
THERE
	chmod 755 $CRND
}


# change base path for btrfs filesystem
#command
ssched_set_btrfspath()
{
	_ssched_read_config

	_ssched_mount_rootvol "$1" || return $?

	_ssched_umount_rootvol "$1"

	CONFIG[SNAP_MOUNT_DIR]="$1"

	_ssched_write_config
}

# check the NSRC argument for validity
#    args: <nameref of nsrc variable> "usage message" [no-read-config]
#    side effect: normalized nsrc value
#
#    uses indirect variable method of passing nsrc so that the normalized
#    value can be passed back without having to use stdout.  using stdout
#    basically requires that the function be run in a sub-process, which
#    means that the reading of the config file doesn't load the CONFIG
#    array for the rest of the program.
_ssched_validate_nsrc()
{
	local UMSG
	local -l NO_READ_CFG # convert to lowercase on assignment

	if $OLDBASH ; then
		local LSRC
		eval LSRC=\${$1} # poor man's nameref functionality.  very poor man.
	else
		local -n LSRC=$1
	fi
	UMSG="$2"
	NO_READ_CFG="$3"

	if [ -z "$LSRC" ] ; then
		serr "$UMSG"
		return 1
	fi
	# look for / anywhere in LSRC
	if expr index "$LSRC" / >/dev/null ; then
		LSRC=`basename $LSRC`
		# strips off trailing slashes too
	fi
	if expr index "$LSRC" '%' >/dev/null ; then
		serr "Illegal character '%' in source name"
		return 1
	fi

	if [ "${#NO_READ_CFG}" -gt 1 ] ; then
		NO_READ_CFG="${NO_READ_CFG:0:1}"
	fi

	if [ "$NO_READ_CFG" != y ] ; then
		_ssched_read_config ||
			return 1
	fi

	# check to see if we have this in our config

	if [ -z "${CONFIG[SSRC_$LSRC]}" ] ; then
		serr "Fail: cannot locate source filesystem '$LSRC' in config file"
		return 1
	fi
	if $OLDBASH ; then
		eval $1="`basename ${CONFIG[SSRC_$LSRC]}`"
	else
		LSRC="`basename ${CONFIG[SSRC_$LSRC]}`"
	fi
}


# set the verbosity level according to the config file
_ssched_set_verbosity()
{
	# unless we've reached the noisy interval, don't spam emails
	# this presumes IMAXSNAPS[*] substitutes in order
	OSILENT=
	for F in hourly daily weekly monthly ; do
		if [ "$F" = "${CONFIG[NOISY]}" ] ; then
			break
		fi
		if [ "$F" = "$FREQ" ] ; then
			OSILENT=--quiet
			break
		fi
	done
}


# validate against max snap count
# args: nsrc msc intervals
_ssched_valid_min_msc()
{
	if [ "$2" -lt 1 ] ; then
		echo "Max snap count for '$1' $3 is set too low: $2"
		echo "No changes made.  Use set_maxsnapcount to change."
		return 1
	fi
}


# validate DAYS parameter for setting days to do daily backups
_ssched_validate_days()
{
	local ARG="$1"
	local LST
	local -l I

	# check for a comma; convert to space
	if [ "$ARG" != "${ARG#*,}" ] ; then
		LST="${ARG//,/ }"
	# check for a dash
	elif [ "$ARG" != "${ARG#-*}" ] ; then
		LST="${ARG//-/ }"
	fi
	if [ "$LST" ] ; then
		ARG="$LST"
	fi

	for I in $ARG ; do
		case "$I" in
			mon|tue|wed|thu|fri|sat|sun|all)
				:
				;;
			monday|tuesday|wednesday|thursday|friday|saturday|sunday)
				:
				;;
			weekdays)
				:
				;;
			[0-9]) # 0 and 1 translate to the same thing in cron.  i think.
				:
				;;
			\*)
				:
				;;
			*)
				return 1
				;;
		esac
	done

	return 0
}


# create a list of snapshots on [remote] host
_ssched_bsub_list()
{
if $OLDBASH ; then
	local -i S
	local LINT_EGREP_PAT
	local RHOST
	local SSH_C
	local BDIR
	local NSRC
	local UARG
	local SLST
	local SSUBS
	local INTR

	# arg 1 is the SRC
	NSRC="$1"

	# arg 2 is optional ssh command
	RHOST="$2"

	# arg 3 is interval frequency
	INTR="$3"

	# arg 4 is the name of the array
	#SNP=$4 # arg4 is ignored in this oldbash version

	# arg 5 is the btrfs mount point
	BDIR="$5"

	if [ "$RHOST" ] ; then
		UARG=-R
		GDIR=$(hostname)
		SSH_C="ssh -x $RHOST"
	else
		UARG=-u
		GDIR=${CONFIG["SNAP_BASE_DIR"]}
	fi

	LINT_EGREP_PAT=`tr ' ' '|' <<<"${LESSER_INTS[$INTR]}"`

	# get all the crud from btrfs, minus the interval snaps we aren't backing up
	SLST=`$SSH_C btrfs sub list --sort=ogen $UARG -o $BDIR |
		grep "$GDIR/$NSRC/" |
		egrep -v "$LINT_EGREP_PAT" | tr '\n' '\r'`

	for ((S = 0; ; S++)) ; do
		SSUB="${SLST%%
*}"
		if [ -z "$SSUB" ] ; then
			break
		fi
		if [ "$SSUB" = "$SLST" ] ; then
			SLST=
		else
			SLST="${SLST#*
}"
		fi
		# sift out the uuid and the path and put into a string
		# separated by a space
		SSUB=${SSUB#*uuid }
		if [ "$SSH_C" ] ; then
			RSNAPS[S]="${SSUB%% *} ${SSUB#*path }"
		else
			SSNAPS[S]="${SSUB%% *} ${SSUB#*path }"
		fi
	done

else

	local -i S
	local LINT_EGREP_PAT
	local -n SNP
	local RHOST
	local SSH_C
	local BDIR
	local NSRC
	local UARG
	local SLST
	local SSUBS
	local INTR

	# arg 1 is the SRC
	NSRC="$1"

	# arg 2 is optional remote hostname
	RHOST="$2"

	# arg 3 is interval frequency
	INTR="$3"

	# arg 4 is the name of the array
	SNP=$4

	# arg 5 is the btrfs mount point
	BDIR="$5"

	if [ "$RHOST" ] ; then
		UARG=-R
		GDIR=$(hostname)
		SSH_C="ssh -x $RHOST"
	else
		UARG=-u
		GDIR=${CONFIG["SNAP_BASE_DIR"]}
	fi

	if [ "$INTR" = hourly ] ; then
		LINT_EGREP_PAT="flartabartsimpson"
		EGREP_CMD=cat
	else
		# shouldn't we just use ${LESSER_INTS[$INTR]// /| }  ?
		LINT_EGREP_PAT="${LESSER_INTS[$INTR]// /|}" # `tr ' ' '|' <<<"${LESSER_INTS[$INTR]}"`
		EGREP_CMD="egrep -v '$LINT_EGREP_PAT'"
	fi

	# get all the crud from btrfs, minus the interval snaps we aren't backing up
	SLST=`$SSH_C btrfs sub list --sort=ogen $UARG -o $BDIR |
		grep "$GDIR/$NSRC/" | egrep -v "$LINT_EGREP_PAT" | tr '\n' '\r'`

	# should we not restrict this to only so many?  otherwise we could have
	# hundreds of "-c SNAP" arguments to btrfs-send, which is pointless imho
	for ((S = 0; ; S++)) ; do
		SSUB="${SLST%%
*}"
		if [ -z "$SSUB" ] ; then
			break
		fi
		if [ "$SSUB" = "$SLST" ] ; then
			SLST=
		else
			SLST="${SLST#*
}"
		fi
		# sift out the uuid and the path and put into a string
		# separated by a space
		SSUB=${SSUB#*uuid }
		SNP[S]="${SSUB/ path}"
	done
fi

}



# change the max snap count for a particular source and interval
#command
ssched_set_maxsnapcount()
{
	local NSRC
	local INT INTRVAL
	local NMAX
	local USTR MSG PLURAL
	local REDUCING

	USTR='usage: set_maxsnapcount <file-system> <interval> <new max>\ninterval: one of hourly, daily, weekly, monthly\nnew-max: the new max snapcount'

	if [ "$#" -lt 3 ] ; then
		echo -e "$USTR"
		return 1
	fi

	NSRC="$1"

	_ssched_validate_nsrc NSRC "$USTR" ||
		return 1

	INT=`_ssched_nom_int $2`
	NMAX="$3"

	# validate interval, and set error message completion string.
	case "$INT" in
		hourly)
			MSG="Pick another value, or use dailies to suppliment."
			;;
		daily)
			MSG="Pick another value, or use weeklies to suppliment."
			;;
		weekly)
			MSG="Pick another value, or use monthlies to suppliment."
			;;
		monthly)
			MSG="Pick a value from 2-24."
			;;
		*)
			echo -e "$USTR"
			echo "Bad interval value: '$2'"
			return 1
			;;
	esac

	if [ \( "$NMAX" -lt 0 \) -o \( "$NMAX" -eq 1 \) -o \
		\( "$NMAX" -gt "${IMAXSNAPS[$INT]}" \) ] ; then
		echo "Ill advised or invalid max snapcount value of '$NMAX' for $INT"
		echo "Disregarding.  $MSG"
		return 1
	fi

	INTRVAL=`_ssched_int2u $INT`
	# plural-ize it
	PLURAL=${INT%y}ies
	OMAX=${CONFIG[SSRC_${NSRC}%${INTRVAL}_MSC]}

	if [ "$NMAX" -eq "$OMAX" ] ; then
		echo "No change for '$NSRC' $PLURAL"
		return 0
	fi

	REDUCING=true
	if [ "$NMAX" -eq 0 ] ; then
		MSG="Turning $PLURAL OFF for source '$NSRC'"
	elif [ "$NMAX" -lt "$OMAX" ] ; then
		MSG="Reducing $INT max snapcount for '$NSRC' from '$OMAX' to '$NMAX'"
	else
		MSG="Changing $INT max snapcount for '$NSRC' from '$OMAX' to '$NMAX'"
		REDUCING=false
	fi

	echo "$MSG"

	CONFIG["SSRC_${NSRC}%${INTRVAL}_MSC"]=$NMAX

	if [ "$NMAX" -eq 0 ] ; then
		# we're shutting snapshots for this interval off
		_ssched_rm_pkgcrontab $NSRC $INT
		_ssched_remove_cron $NSRC $INT
	elif [ "$OMAX" -eq 0 ] ; then
		# if creating a new scheduled snapshot, make sure it's crons
		# are intact/created
		if [ "${CONFIG[SSRC_$NSRC%${INTRVAL}_DAT]}" ] ; then
			# if it has special times/ set, then add
			# back the entry in the package crontab
			ssched_set_$PLURAL $NSRC \
				"${CONFIG[SSRC_$NSRC%${INTRVAL}_DAT]}" \
				"${CONFIG[SSRC_$NSRC%${INTRVAL}_TOD]}" \
				yes

			# no need to write the config file twice
			# and since we're not reducing, we can return
			return
		else
			_ssched_submit_cron $NSRC $INT
		fi
	fi

	_ssched_write_config

	if $REDUCING ; then

		_ssched_mount_rootvol ${CONFIG[SNAP_MOUNT_DIR]} || {
			echo "Reducing: failed to mount ${CONFIG[SNAP_MOUNT_DIR]}.  Cannot delete excess snapshots."
			return 1
		}

		if [ "`_ssched_lssnap_single $NSRC count $INT`" -gt "$NMAX" ] ; then
			echo "WARNING: You have reduced the max snap count for $PLURAL,"
			echo "but there are currently more snapshots than that, and"
			echo "the excess won't be automatically purged, which will likely"
			echo "cause forseen and unforseen problems."
			echo
			echo "If you want the oldest $((`_ssched_lssnap_single $NSRC count $INT` - $NMAX)) snapshots deleted now,"
			echo "then select that option from this menu:"
			PS3="Select the number corresponding to the desired action: "
			select ACTION in \
				"Delete the excess snapshots" \
				"List the snapshots for interval '$INT'" \
				"Take no additional action at this time." ; do
				if [ "$REPLY" -a \( "$REPLY" -gt 0 -a "$REPLY" -le 3 \) ] ; then
					break
				fi
			done
			if [ -z "$REPLY" -o -z "$ACTION" ] ; then
				echo "No further action will be taken at this time."
			else
				case $REPLY in
					1)
						echo "it would be nice if we were deleting the snapshots right now, but we're not"
						;;
					2)
						echo "List of existing snapshots for '$INT':"
						_ssched_lssnap_single $NSRC list $INT 1
						;;
					3)  # no action
						echo "No further action will be taken at this time."
						;;
				esac
			fi
		fi
		_ssched_umount_rootvol ${CONFIG[SNAP_MOUNT_DIR]}
	fi
}


####
# set_{hour,dai,week,month}lies commands
#
# These commands set specific times for the various scheduled snapshots
# to take place.  They can be combined with the MAX snapcount setting for
# their interval (hourly, daily, weekly, monthly) to fine tune a particular
# interval's scheduled snapping behavior.
#
# Example: to set up daily snapshots of a filesystem named 'work' that
# most suits your work habits, or those who's data is on the filesystem,
# you decide you want the daily snapshots to fire at 11:00PM after most
# everyone has finished working for the day.  People rarely work on
# weekends, so you don't need to do daily snapshots on those days.  So,
# set max_snapcount (via the add_source command or the set_maxsnapcount
# command) to 6, to keep one extra just in case, and set the dailes
# to mon-fri 23:00.  You could also set the weeklies to complement this,
# say to fire on Sunday, which in a way would give you an extra snapshot
# to cover any work that happened on the weekend.


# set the daily snapshot fire time/day in the crontab file.
#
# default daily snapshot time is 12:00am (midnight) when set with this.
# when not set with this, it defaults to time of cron.daily job.  that
# might change one day.
#
# args: <file-system-name> <days> [<time-of-day>] [no-read-config]
# the no-read-config is for use of this function by other internal functions
#
#command
ssched_set_dailies()
{
	local NSRC DARG DAYS TOD CTIMES
	local -l NO_READ_CFG # converts to lowercase on assignment
	local UMSG="usage: set_dailies <source-name> <days> <tod> [NO_READ_CFG_FLAG]"
	local THOUR=0 TMIN=1

	if [ "$#" -lt 3 ] ; then
		echo "$UMSG"
		return 1
	fi

	NSRC="$1"
	DARG="$2"
	shift;shift
	TOD="$1"
	if [ "$#" -eq 2 ] ; then
		NO_READ_CFG="$2"
	fi
	# shorten variable to one char
	if [ "${#NO_READ_CFG}" -gt 1 ] ; then
		NO_READ_CFG="${NO_READ_CFG:0:1}"
	fi

	# validate NSRC, and read config file if necessary
	_ssched_validate_nsrc NSRC "$UMSG" "$NO_READ_CFG" ||
		return 1

	# check to see if we're even snapping this src at this interval
	_ssched_valid_min_msc $NSRC ${CONFIG["SSRC_$NSRC%D_MSC"]} dailies ||
		return 1

	_ssched_validate_days "$DARG" || {
		serr "$UMSG"
		serr "Unknown designation for days: '$DARG'"
		serr "Try 'weekdays' or 'mon-thu' or '1,3,6' or 'all' and so on."
		return 1
	}


	case "$TOD" in
		[0-9][0-9]:[0-9][0-9])
			set -- ${TOD/:/ }
			# get rid of the leading zeroes
			THOUR=`printf "%d" "$1"`
			TMIN=`printf "%d" "$2"`
		   ;;
		*)
			serr "$UMSG"
			serr "tod must me zero-padded HH:MM, in 24 hour spec, for ex.: 09:05"
			return 1
			;;
	esac

	if [ "$DARG" = all ] ; then
		DAYS='*'
	else
		DAYS="$DARG"
	fi
	# these should also be validated against max snap count
	case "$DAYS" in
		weekdays)
			CTIMES="$TMIN $THOUR * * mon-fri"
			;;
		weekend)
			# strange for dailies, but hey
			CTIMES="$TMIN $THOUR * * sat,sun"
			;;
		*)
			# hopefully everything that passed validation will work
			CTIMES="$TMIN $THOUR * * $DAYS"
			;;
	esac

	# if we're not reading the config file, then don't write it either
	if [ "$NO_READ_CFG" != y ] ; then
		CONFIG[SSRC_$NSRC%D_DAT]="$DARG"
		CONFIG[SSRC_$NSRC%D_TOD]="$TOD"
		_ssched_write_config
	fi

	# write the pkgcrontab entries.  deletes prev entries before writing
	_ssched_mod_pkgcrontab "$NSRC" daily "$DAYS" "$CTIMES" ""

	# make sure the old $ETC/cron.daily is history
	_ssched_remove_cron $NSRC daily
}


# set the weekly snapshot fire time/day in the crontab file.  may place
#   a bit 'o code into the crontab file
#
# default weekly snapshot time is sat 12:00am (midnight) when set with this.
# when not set with this, it defaults to time of cron.weekly job.  that
# might change one day.
#
# args: <file-system-name> <day> [<time-of-day>] [no-read-config]
# the no-read-config is for use of this function by other internal functions
#
#command
ssched_set_weeklies()
{
	local NSRC TOD CTIMES
	local -l DAY NO_READ_CFG # converts to lowercase on assignment
	local THOUR=0 TMIN=1
	local UMSG="usage: set_weeklies <source-name> <day> <tod> [NO_READ_CFG_FLAG]"

	if [ "$#" -lt 3 ] ; then
		echo "$UMSG"
		return 1
	fi

	NSRC="$1"
	DAY="$2"
	shift;shift
	TOD="$1"
	if [ "$#" -eq 2 ] ; then
		NO_READ_CFG="$2"
	fi
	# shorten variable to one char
	if [ "${#NO_READ_CFG}" -gt 1 ] ; then
		NO_READ_CFG="${NO_READ_CFG:0:1}"
	fi

	# validate NSRC, and read config file if necessary
	_ssched_validate_nsrc NSRC "$UMSG" "$NO_READ_CFG" ||
		return 1

	# check to see if we're even snapping this src at this interval
	_ssched_valid_min_msc $NSRC ${CONFIG["SSRC_$NSRC%W_MSC"]} weeklies ||
		return 1


	case "$TOD" in
		[0-9][0-9]:[0-9][0-9])
			set -- ${TOD/:/ }
			# get rid of the leading zeroes
			THOUR=`printf "%d" "$1"`
			TMIN=`printf "%d" "$2"`
		   ;;
		*)
			echo "$UMSG"
			return 1
			;;
	esac

	# these should also be validated against max snap count
	case "$DAY" in
		mon|tue|wed|thu|fri|sat|sun)
			CTIMES="$TMIN $THOUR * * $DAY"
			;;
		[1-7])
			# numerical day specifier
			CTIMES="$TMIN $THOUR * * $DAY"
			;;
		*)
			echo "$UMSG"
			return 1
			;;
	esac

	# if we're not reading the config file, then don't write it either
	if [ "$NO_READ_CFG" != y ] ; then
		CONFIG[SSRC_$NSRC%W_DAT]="$DAY"
		CONFIG[SSRC_$NSRC%W_TOD]="$TOD"
		_ssched_write_config
	fi

	# write the pkgcrontab entries.  deletes prev entries before writing
	_ssched_mod_pkgcrontab "$NSRC" weekly "$DAY" "$CTIMES" ""

	# make sure the old $ETC/cron.weekly is history
	_ssched_remove_cron $NSRC weekly
}


# set the montly snapshot fire time/day in the crontab file.  may place
#   a bit 'o code into the crontab file
#
# args: <file-system-name> <day-of-month> <time-of-day> [no-read-config]
# the no-read-config is for use of this function by other internal functions
#
#command
ssched_set_monthlies()
{
	local NSRC DOM TOD
	local -l NO_READ_CFG # converts to lowercase on assignment
	local THOUR=22 TMIN=5
	local CPROG CTIMES
	local UMSG="usage: set_monthlies <source-name> <dom> <tod> [no-read-config]"

	if [ "$#" -lt 3 ] ; then
		echo "$UMSG"
		return 1
	fi

	NSRC="$1"
	DOM="$2"
	shift;shift
	TOD="$1"
	if [ "$#" -eq 2 ] ; then
		NO_READ_CFG="$2"
	fi

	if [ "$NO_READ_CFG" ] ; then
		NO_READ_CFG="${NO_READ_CFG:0:1}"
	fi

	# validate NSRC, and read config file if necessary
	_ssched_validate_nsrc NSRC "$UMSG" "$NO_READ_CFG" ||
		return 1

	# check to see if we're even snapping this src at this interval
	_ssched_valid_min_msc $NSRC ${CONFIG["SSRC_$NSRC%M_MSC"]} monthlies ||
		return 1

	if [ "$TOD" ] ; then
		case "$TOD" in
			[0-9][0-9]:[0-9][0-9])
				set -- ${TOD/:/ }
				# get rid of the leading zeroes
				THOUR=`printf "%d" "$1"`
				TMIN=`printf "%d" "$2"`
			   ;;
			*)
				echo "$UMSG"
				return 1
				;;
		esac
	fi

	case "$DOM" in
		last-weekday)
			CPROG='[ "`date +\%e`" = $(ncal -h | egrep '\''Mo|Tu|We|Th|Fr'\'' | sed '\''s/^.* \([0-9]\+\) *$/\1/'\'' | sort | tail -1) ] && '
			CTIMES="$TMIN $THOUR 26-31 * mon-fri"
			;;
		last-weekend)
			CPROG='[ "`date +\%e`" = $(ncal -h | egrep '\''Su|Sa'\'' | sed '\''s/^.* \([0-9]\+\) *$/\1/'\'' | sort | tail -1) ] && '
			CTIMES="$TMIN $THOUR 23-31 * sat,sun"
			;;
		last-*)
			local -l LDAY
			local SDAY
			LDAY=`echo $DOM | sed 's/^last-//'`
			echo $LDAY | egrep -q 'sat|sun|mon|tue|wed|thu|fri' || {
				echo "I don't understand that one: '$DOM'"
				echo "Check out the help message for acceptable 'last' values"
				return 1
			}
			SDAY=`echo $LDAY | sed 's/.$//'`
			CPROG='[ "`date +\%e`" = $(ncal -h | grep -i '$SDAY' | sed '\''s/^.* \([0-9]\+\) *$/\1/'\'') ] && '
			CTIMES="$TMIN $THOUR 22-31 * $LDAY"
			;;
		last)
			CPROG='[ "`date +\%e`" = $(ncal -bh | sed '\''/^ *$/d'\'' | tail -1 | sed '\''s/^.* \([23][018]\) *$/\1/'\'') ] && '
			CTIMES="$TMIN $THOUR 28-31 * *"
			;;
		[1-9]|1[0-9]|2[0-9]|3[01])
			CPROG=
			CTIMES="$TMIN $THOUR $DOM * *"
			;;
		*)
			echo "I don't understand that one: '$DOM'"
			echo "Check out the help message for acceptable numerical values"
			return 1
			;;
	esac

	# if we're not reading the config file, then don't write it either
	if [ "$NO_READ_CFG" != y ] ; then
		CONFIG[SSRC_$NSRC%M_DAT]="$DOM"
		CONFIG[SSRC_$NSRC%M_TOD]="$TOD"
		_ssched_write_config
	fi

	# write the pkgcrontab entries.  deletes prev entries before writing
	_ssched_mod_pkgcrontab "$NSRC" monthly "$DOM" "$CTIMES" "$CPROG"

	# make sure the old $ETC/cron.monthly is history
	_ssched_remove_cron $NSRC monthly
}


# add a subvolume to the config for scheduled snapshots
#command
ssched_add_source()
{
	local SRCDIR NSRC I F
	local -l ANS

	# arguments:

	# name   h d w m [db1 ...dbn]
	# examples:
	#   ssched_add_source r00t 25 6 5 13 mysql postgresql-9.1
	#   ssched_add_source h0me 24 8 0 12

	# if caller is not the snapsched app ...
	if [ "$SSCHED_UBER" != y ] ; then
		return 1
	fi

	if [ "$#" -lt 5 ] ; then
		echo "usage: add_source name   h d w m [db1 ...dbn]"
		return 1
	fi

	NSRC=$1
	shift

	if _ssched_validate_nsrc NSRC 2>/dev/null ; then
		echo "Source '$NSRC' already exists in config file."
		echo "Perhaps you want to modify it with set_maxsnapcount or"
		echo "set_{hour,dai,week,month}lies commands.  Or one of the"
		echo "crontab commands."
		echo "Otherwise, you will have to remove it before adding it again."
		return 1
	fi

	_ssched_mount_rootvol ${CONFIG[SNAP_MOUNT_DIR]} || return $?

	SRCDIR=${CONFIG[SNAP_MOUNT_DIR]}/$NSRC

	if [ ! -d $SRCDIR ] ; then
		echo "No such directory '$SRCDIR'."
		echo "This must be the location of the source subvolume to make snapshots from."
		_ssched_umount_rootvol ${CONFIG[SNAP_MOUNT_DIR]}
		return 1
	fi

	_ssched_umount_rootvol "${CONFIG[SNAP_MOUNT_DIR]}"

	CONFIG["SSRC_$NSRC"]=$SRCDIR

	# just in case.  of bugs.
	for I in "${!CONFIG[@]}" ; do
		if expr match "$I" "SSRC_$NSRC%" >/dev/null ; then
			unset CONFIG[$I]
		fi
	done

	# process the arg for each interval
	# ${!IMAXSNAPS[*]} does not evaluate in any particular order, so...
	for I in hourly daily weekly monthly ; do
		# process the max snap count for ${I%y}lies
		if [ "$1" -lt 0 ] ; then
			echo "Overriding max snap count for ${I%y}lies to 0 (disabled)"
			F=0
		elif [ "$1" -gt ${IMAXSNAPS[$I]} ] ; then
			echo "Overriding max snap count for ${I%y}lies to ${IMAXSNAPS[$I]}"
			F=${IMAXSNAPS[$I]}
		else
			F=$1
		fi
		CONFIG["SSRC_${NSRC}%`_ssched_int2u $I`_MSC"]=$F
		shift
	done

	if [ "$#" -ge 1 ] ; then
		# process the DBs argument(s)
		CONFIG["SSRC_${NSRC}%DBS"]="$*"
	fi

	_ssched_write_config

	ssched_create_cronjobs $NSRC yes
}


# remove a subvolume to the config for scheduled snapshots
#command
ssched_remove_source()
{
	local SRCDIR NSRC SSNAPS_BDIR CHILD_SNAPS ES
	local -l ANS
	local I R

	# arguments: filesystem name

	# check caller
	if [ "$SSCHED_UBER" != y ] ; then
		return 1
	fi

	NSRC=$1

	_ssched_validate_nsrc NSRC "$0: <filesystem-source>" ||
		return 1

	_ssched_mount_rootvol ${CONFIG[SNAP_MOUNT_DIR]} || return $?

	SRCDIR=${CONFIG[SNAP_MOUNT_DIR]}/$NSRC

	if [ ! -d $SRCDIR ] ; then
		echo "Couldn't find $SRCDIR.  Fail.  Bye."
		_ssched_umount_rootvol ${CONFIG[SNAP_MOUNT_DIR]}
		exit 1
	fi

	# disable any further snapping
	for I in ${!IMAXSNAPS[*]} ; do
		R=`_ssched_int2u $I`
		if [ "${CONFIG[SSRC_$NSRC%${R}_DAT]}" -a \
			\( "${CONFIG[SSRC_$NSRC%${R}_MSC]}" -gt 0 \) ] ; then
			_ssched_rm_pkgcrontab $NSRC $I
		fi
		CONFIG[SSRC_$NSRC%${R}_MSC]=0
	done
	_ssched_write_config

	# use btrfs show command to list snapshots OR
	# use btrfs list -u [idiotic output] | grep "path $SRCDIR"
	# then use btrfs sub del -c `btrfs list -q | grep "parent_uuid $UUID"`
	# not really, but i get the idea
	# MUST BE VERY CAREFUL, because a child snapshot might be a newly added
	# source or other important snapshot.  so look only for
	# snapshots under .ssched/$NSRC/
	ANS=
	SSNAPS_BDIR="${CONFIG[SNAP_MOUNT_DIR]}/${CONFIG[SNAP_BASE_DIR]}/$NSRC"
	CHILD_SNAPS=`echo $SSNAPS_BDIR/*/*`
	if [ "$CHILD_SNAPS" != "$SSNAPS_BDIR/*/*" ] ; then
		echo "Delete all the scheduled snapshots of this former source?"
		sed -e "s|^$SSNAPS_BDIR/||" -e "s|$SSNAPS_BDIR/|\n|g" <<<"$CHILD_SNAPS"
		read -p "Answer must be 'yes' to delete.  Really delete all? " ANS
		if [ "$ANS" = yes ] ; then
			echo "The scheduled snapshots of '$NSRC' will be deleted now."
			btrfs sub del -c $CHILD_SNAPS
			# even after a sync, the writes and recovered space
			# take a while to happen.  for no good reason.
			sync
			sleep 2
			ANS=already
		else
			ANS=not
			echo "The scheduled snapshots of '$NSRC' will remain intact."
		fi
	fi

	ES=0
	echo "Do you wish to delete the source subvolume itself?"
	echo "This has no effect on scheduled snapshots of the subvolume, which"
	echo "you have ${ANS} deleted."
	ANS=
	read -p "Delete the source subvolume [no]/yes ? " ANS
	if [ "$ANS" = yes ] ; then
		echo "Are you super-sure?  This is very dangerous, and you MUST"
		echo "know exactly what you are doing!"
		read -p "Are you double-sure you want to delete subvolume '$NSRC' No/yes ? " ANS
		if [ "$ANS" = yes ] ; then
			echo "The source subvolume '$NSRC' will be deleted now."
			btrfs sub del -c "$SRCDIR"
			ES=$?
			sync
		fi
	fi

	echo "The source '$NSRC' will be removed from the config file"

	_ssched_umount_rootvol ${CONFIG[SNAP_MOUNT_DIR]}

	if [ "$ES" -ne 0 ] ; then
		serr "Error deleting snapshot dir '$SRCDIR': $ES"
	fi

	# remove the cron jobs
	for I in ${!IMAXSNAPS[*]} ; do
		_ssched_remove_cron "$NSRC" ${I}
	done

	# delete them
	unset CONFIG[SSRC_$NSRC]
	for I in ${!CONFIG[*]} ; do
		if expr match "$I" "SSRC_$NSRC%" >/dev/null ; then
			unset CONFIG[$I]
		fi
	done

	_ssched_write_config

	echo "Scheduled snapshot source '$NSRC' removed from config file and"
	echo "cron jobs."
}


#command
ssched_diff()
{
	local NSRC FNAM ES BN=1
	local -l INTTYPE
	local USTR="usage: diff <source-name> <int-type> <file-name> [<back-num>]"

	if [ "$#" -lt 3 ] ; then
		serr "wrong number of arguments: $#"
		echo "$USTR"
		return 1
	fi

	NSRC="$1"
	INTTYPE="$2"
	FNAM="$3"
	shift;shift;shift
	if [ "$#" -eq 1 ] ; then
		BN="$1"
		if [ "$BN" -lt 1 ] ; then
			serr "$USTR"
			serr "Back-num invalid value '$BN'"
			return 1
		fi
	fi

	if [ -d "$FNAM" ] ; then
		serr "$USTR"
		serr "Can't diff directories (yet). '$FNAM'"
		return 1
	elif [ -e "$FNAM" -a ! -r "$FNAM" ] ; then
		serr "$USTR"
		serr "Can't open file for reading: '$FNAM'"
		return 1
	fi

	# validate NSRC, and read config file if necessary
	_ssched_validate_nsrc NSRC "$USTR" ||
		return 1

	# validate int type
	INTR=`_ssched_nom_int "$INTTYPE"`
	if [ -z "$INTR" ] ; then
		serr -e "$USTR"
		serr "Bad interval value: '$INTTYPE'"
		return 1
	fi

	_ssched_mount_rootvol ${CONFIG[SNAP_MOUNT_DIR]} || return $?

	SDIR="${CONFIG[SNAP_MOUNT_DIR]}/${CONFIG[SNAP_BASE_DIR]}/$NSRC/`_ssched_int2dir $INTR`"
	if [ "$BN" -gt "`\ls $SDIR | wc -l`" ] ; then
		serr "Specified back-num '$BN' is greater than available '`\ls $SDIR | wc -l`'"
		_ssched_umount_rootvol ${CONFIG[SNAP_MOUNT_DIR]}
		return 1
	fi
	SDIR="$SDIR/`\ls $SDIR | tail -$BN | head -1`"

	if expr match "$FNAM" / >/dev/null ; then
		FPATH="$FNAM"
	else
		FPATH="`pwd`/$FNAM"
	fi
	FPATH=${FPATH#/}

	# whatever errors diff gets about no files, etc,
	#  just poop 'em on to the user
	${SSCHED_DIFF:-diff} --new-file "$SDIR/$FPATH" "$FNAM"
	ES=$?

	_ssched_umount_rootvol ${CONFIG[SNAP_MOUNT_DIR]}

	return $ES
}


# copy a file out of a snapshot.  code could conceivably be combined with
# the diff command at some point.  also, could add support for things
# like rsync and the like.  use your own copy command in SSCHED_GET if you dare
#command
ssched_get()
{
	local NSRC FNAM ES BN=1
	local -l INTTYPE
	local CP_CMD
	local ONAM
	local LUID
	local CPARGS
	local USTR="usage: get <source-name> <int-type> <file-name> <out-file-name> [<back-num>]\noutput filename can be '-' for stdout"

	if [ "$#" -lt 4 ] ; then
		serr "wrong number of arguments: $#"
		echo -e "$USTR"
		return 1
	fi

	NSRC="$1"
	INTTYPE="$2"
	FNAM="$3"
	ONAM="$4"
	shift;shift;shift;shift
	if [ "$#" -eq 1 ] ; then
		BN="$1"
		if [ "$BN" -lt 1 ] ; then
			serr -e "$USTR"
			serr "Back-num invalid value '$BN'"
			return 1
		fi
	fi

	if [ -z "$ONAM" ] ; then
		serr "missing output file name"
		echo -e "$USTR"
		return 1
	fi

	# hey, there could actually be a file named - in the directory.
	if [ \( "${#ONAM}" != 1 \) -o \( "$ONAM" != "-" \) ] ; then
		# this is mostly/always run as root, so this is a silly test for now.
		if [ -e "$ONAM" -a ! -w "$ONAM" ] ; then
			serr -e "$USTR"
			serr "File exists, but you can't write to it: '$ONAM'"
			return 1
		fi
	fi

	LUID=`id -ru`
	if [ "$LUID" = 0 ] ; then
		SUDOCMD=
	else
		SUDOCMD=$GSUDOCMD
	fi

	# validate NSRC, and read config file if necessary
	_ssched_validate_nsrc NSRC "$USTR" ||
		return 1

	# validate int type
	INTR=`_ssched_nom_int "$INTTYPE"`
	if [ -z "$INTR" ] ; then
		serr -e "$USTR"
		serr "Bad interval value: '$INTTYPE'"
		return 1
	fi

	_ssched_mount_rootvol ${CONFIG[SNAP_MOUNT_DIR]} || return $?

	SDIR="${CONFIG[SNAP_MOUNT_DIR]}/${CONFIG[SNAP_BASE_DIR]}/$NSRC/`_ssched_int2dir $INTR`"
	if [ "$BN" -gt "`\ls $SDIR | wc -l`" ] ; then
		serr "Specified back-num '$BN' is greater than available '`\ls $SDIR | wc -l`'"
		_ssched_umount_rootvol ${CONFIG[SNAP_MOUNT_DIR]}
		SUDOCMD=
		return 1
	fi
	SDIR="$SDIR/`\ls $SDIR | tail -$BN | head -1`"

	# try to make it relative if FNAM doesn't start with /
	if expr match "$FNAM" / >/dev/null ; then
		FPATH="$FNAM"
	else
		FPATH="`pwd`/$FNAM"
	fi
	FPATH="${FPATH#/}"

	if [ ! -e "$SDIR/$FPATH" ] ; then
		serr "Source file '$SDIR/$FPATH' does not exist."
		_ssched_umount_rootvol ${CONFIG[SNAP_MOUNT_DIR]}
		SUDOCMD=
		return 1
	fi

	if [ -d "$SDIR/$FPATH" ] ; then
		serr -e "$USTR"
		serr "Can't copy directories (yet). '${SDIR#${CONFIG[SNAP_MOUNT_DIR]}}/$FPATH'"
		_ssched_umount_rootvol ${CONFIG[SNAP_MOUNT_DIR]}
		SUDOCMD=
		return 1
	fi

	CP_CMD=cp
	if [ \( "${#ONAM}" = 1 \) -a \( "$ONAM" = "-" \) ] ; then
		ONAM=
		CP_CMD=cat
	else
		if [ -z "$SSCHED_GET" ] ; then
			CPARGS="--reflink=auto"
		fi
	fi

	# whatever errors cp gets about no files, etc, just poop 'em on to the user
	${SSCHED_GET:-$CP_CMD} $CPARGS "$SDIR/$FPATH" ${ONAM:+"$ONAM"}
	ES=$?

	_ssched_umount_rootvol ${CONFIG[SNAP_MOUNT_DIR]}
	SUDOCMD=

	return $ES
}


# some processing to set the mitigation argument to _set_backup commands
_ssched_validate_mtype()
{
	local MTYPE

	# check for mitigation arg
	if [ \( "$#" -ge 1 \) -a \( "$1" \) ] ; then
		MTYPE="$1"
		shift

		# validate the mitigation type argument
		case "${MTYPE% *}" in
			host|backups)
				:
				;;
			hostq|netseg)
				serr "backup mitigation type '${MTYPE% *}' has not been implemented yet"
				return 1
				;;
			*)
				serr "backup mitigation type must be one of host|hostq|backups|netseg"
				return 1
				;;
		esac
		if [ -z "${MTYPE#${MTYPE% *}}" ] ; then
			MTYPE="$MTYPE 1"
		else
			case "${MTYPE#* }" in
				1-4)
					:
					;;
				*)
					serr "second half of backup mitigation type must be a number between 1 and 4 inclusive"
					return 1
					;;
			esac
		fi
	else
		MTYPE="host 1"
	fi

	echo $MTYPE
}


#command
ssched_set_backup()
{
	local NSRC BHOST1 BHOST1FS
	local -l BHOST1Z
	local MTYPE
	local -l INTR
	local USTR="usage: set_backup <source-name> <int-type> <backup-host> <backup-fs-dir> <ssh-compress-yes-no> [<mitigation-type>]"

	if [ "$#" -lt 5 ] ; then
		serr "set_backup: wrong number of arguments - $#"
		serr "$USTR"
		return 1
	fi

	NSRC="$1"
	INTR=`_ssched_nom_int "$2"`
	if [ -z "$INTR" ] ; then
		serr -e "$USTR"
		serr "Bad interval value: '$2'"
		return 1
	fi
	BHOST1="$3"
	BHOST1FS="$4"
	BHOST1Z="$5"

	BHOST1Z=`_ssched_input2bool "$BHOST1Z"`
	if [ \( "$?" -ne 0 \) -o \( -z "$BHOST1Z" \) ] ; then
		serr "set_backup: compression arg must be one of 'on' or 'off'"
		serr "$USTR"
		return 1
	fi

	shift;shift;shift;shift;shift

	# check for mitigation arg
	MTYPE=`_ssched_validate_mtype "$1"` || return 1

	# validate NSRC, and read config file if necessary
	_ssched_validate_nsrc NSRC "$USTR" ||
		return 1

	if [ "${CONFIG[SSRC_$NSRC%`_ssched_int2u $INTR`_MSC]}" -eq 0 ] ; then
		# there's nothing to backup
		echo "There are no snapshots scheduled for '$NSRC' at level '$INTR'."
		return 1
	fi

	if [ -z "`which at`" ] ; then
		echo "The backup feature requires the at(1) command be installed on the system."
		return 1
	fi

	INTSTR="monthly weekly daily hourly"

	CONFIG["SSRC_${NSRC}%BACKUPHOST1"]="$BHOST1"
	CONFIG["SSRC_${NSRC}%BACKUPHOST1_Z"]="$BHOST1Z"
	CONFIG["SSRC_${NSRC}%BACKUPHOST1FS"]="$BHOST1FS"
	CONFIG["SSRC_${NSRC}%BACKUPINT"]="$INTR"
	CONFIG["SSRC_${NSRC}%BACKUPMTYPE"]="$MTYPE"

	# save the config entries
	_ssched_write_config

	echo
	echo "Backups will be bootstrapped by sending all unsent snapshots to"
	echo "the backup host '$BHOST1' ... RIGHT NOW!  Be aware that this could"
	echo "consume system and network resources for quite a while."
	echo "If this is OK, then type 'yes', otherwise you will have to run this"
	echo "command again later.  The job will kick off 5 minutes after this"
	echo "command exits.  You can always use the at(1) command to reschedule"
	echo "the job to another time, if you so desire.  Answer 'yes' if you wish"
	echo "this bootstrapping to happen now.  Otherwise, it will happen the next"
	echo "time this snapshot interval occurs."
	echo
	read -p "Do you wish to proceed with backup bootstrap? ([no] | yes) "
	if [ "$REPLY" != yes ] ; then
		return 0
	fi

	# schedule the backup bootstrap process
	(cd /tmp/ssched
	 PX=$PRE
	 unset SSCHED_UBER OSILENT CRONTAB LS_COLORS CFGFILE OLDBASH PRE
	 unset TMPBASE ETCDIR

	 echo "Scheduling backup job"
	 echo "    $PX/lib/snapsched/snapback $NSRC $I"
	 echo "in two minutes using at.  Results will be emailed to root."
	 # using queue 's' nices it down, supposedly.  which is nice.
	 # but since nice has no effect on btrfs send/recv, it's just silly.
	 echo $PX/lib/snapsched/snapback $NSRC |
		at -q s "now + 2 minutes"
	)
}


# add a secondary backup host
#command
ssched_add_backup2()
{
	local NSRC BHOST1 BHOST2 BHOST2FS
	local -l BHOST2Z
	local MTYPE
	local -l INTR
	local USTR="usage: $0 <source-name> <int-type> <backup-host-2> <backup-fs-dir-2> <ssh-compress-yes-no> [<mitigation-type>]"

	if [ "$#" -lt 5 ] ; then
		serr "$0: wrong number of arguments - $#"
		serr "$USTR"
		return 1
	fi

	NSRC="$1"
	INTR=`_ssched_nom_int "$2"`
	if [ -z "$INTR" ] ; then
		serr -e "$USTR"
		serr "Bad interval value: '$2'"
		return 1
	fi
	BHOST2="$3"
	BHOST2FS="$4"
	BHOST2Z="$5"

	BHOST2Z=`_ssched_input2bool "$BHOST2Z"`
	if [ "$?" -ne 0 ] ; then
		serr "$0: compression arg must be one of 'on' or 'off'"
		serr "$USTR"
		return 1
	fi

	shift;shift;shift;shift;shift

	# check for mitigation arg
	MTYPE=`_ssched_validate_mtype "$1"` || return 1

	# validate NSRC, and read config file if necessary
	_ssched_validate_nsrc NSRC "$USTR" ||
		return 1

	if [ -z "${CONFIG[SSRC_${NSRC}%BACKUPHOST1]}" ] ; then
		serr "$0: you must configure backups for a primary backup server before you can add a second backup server."
		return 1
	fi

	if [ "${CONFIG[SSRC_$NSRC%`_ssched_int2u $INTR`_MSC]}" -eq 0 ] ; then
		# there's nothing to backup
		serr "There are no snapshots scheduled for '$NSRC' at level '$INTR'."
		return 1
	fi

	if [ -z "`which at`" ] ; then
		serr "The backup feature requires the at(1) command be installed on the system."
		return 1
	fi

	INTSTR="monthly weekly daily hourly"

	CONFIG["SSRC_${NSRC}%BACKUPHOST2"]="$BHOST2"
	CONFIG["SSRC_${NSRC}%BACKUPHOST2_Z"]="$BHOST2Z"
	CONFIG["SSRC_${NSRC}%BACKUPHOST2FS"]="$BHOST2FS"
	CONFIG["SSRC_${NSRC}%BACKUPINT2"]="$INTR"
	CONFIG["SSRC_${NSRC}%BACKUPMTYPE2"]="$MTYPE"

	# save the config entries
	_ssched_write_config

	echo
	echo "Backups will be bootstrapped by sending all unsent snapshots to"
	echo "the backup host '$BHOST2' ... RIGHT NOW!  Be aware that this could"
	echo "consume system and network resources for quite a while."
	echo "If this is OK, then type 'yes', otherwise you will have to run this"
	echo "command again later.  The job will kick off 5 minutes after this"
	echo "command exits.  You can always use the at(1) command to reschedule"
	echo "the job to another time, if you so desire.  Answer 'yes' if you wish"
	echo "this bootstrapping to happen now.  Otherwise, it will happen the next"
	echo "time this snapshot interval occurs."
	echo
	read -p "Do you wish to proceed with backup bootstrap? ([no] | yes) "
	if [ "$REPLY" != yes ] ; then
		return 0
	fi

	# schedule the backup bootstrap process
	(cd /tmp/ssched
	 PX=$PRE
	 unset SSCHED_UBER OSILENT CRONTAB LS_COLORS CFGFILE OLDBASH PRE
	 unset TMPBASE ETCDIR

	 # using queue 's' nices it down, supposedly.  which is nice.
	 # but since nice has no effect on btrfs send/recv, it's just silly.
	 echo $PX/lib/snapsched/snapback $NSRC 2 |
		at -q s "now + 5 minutes"
	)
}


#command
ssched_rm_backup()
{
	local NSRC
	local -l INTTYPE
	local INTR
	local CX
	local USTR="usage: rm_backup <source-name> <int-type>"

	if [ "$#" -lt 2 ] ; then
		serr "wrong number of arguments: $#"
		echo "$USTR"
		return 1
	fi

	NSRC="$1"
	INTTYPE="$2"
	shift;shift

	# validate int type
	INTR=`_ssched_nom_int "$INTTYPE"`
	if [ -z "$INTR" ] ; then
		serr -e "$USTR"
		serr "Bad interval value: '$INTTYPE'"
		return 1
	fi

	# validate NSRC, and read config file if necessary
	_ssched_validate_nsrc NSRC "$USTR" ||
		return 1

	for CX in ${!CONFIG[*]} ; do
		if grep -qs "SSRC_${NSRC}%BACKUP" <<<"$CX" ; then
			unset CONFIG["$CX"]
		fi
	done
	# unset CONFIG["SSRC_${NSRC}%BACKUPHOST1"]
	# unset CONFIG["SSRC_${NSRC}%BACKUPHOST1FS"]
	# unset CONFIG["SSRC_${NSRC}%BACKUPHOST1_Z"]
	# unset CONFIG["SSRC_${NSRC}%BACKUPINT"]
	# unset CONFIG["SSRC_${NSRC}%BACKUPMTYPE"]

	# save the config entries
	_ssched_write_config
}


#command
ssched_show_backup()
{
	local NSRC SRCS S
	local USTR="usage: show_backup [<source-name>]"

	if [ "$#" -gt 1 ] ; then
		serr "show_backup: wrong number of arguments - $#"
		serr "$USTR"
		return 1
	fi

	if [ "$1" ] ; then
		NSRC="$1"
		shift
	fi

	if [ "$#" -ge 1 ] ; then
		serr "show_backup: wrong number of arguments - $#"
		serr "$USTR"
		return 1
	fi

	if [ "$NSRC" ] ; then
		_ssched_validate_nsrc NSRC "$USTR" || return 1
		SRCS="$NSRC"
	else
		_ssched_read_config
		SRCS=`_ssched_list_cfg_srcs`
	fi

	for S in $SRCS ; do
		if [ "${CONFIG["SSRC_${S}%BACKUPINT"]}" ] ; then
			# output the basic backup info and settings for host1
			echo -n "${S}: ${CONFIG[SSRC_${S}%BACKUPINT]} '${CONFIG[SSRC_${S}%BACKUPMTYPE]}' ${CONFIG[SSRC_${S}%BACKUPHOST1]} ${CONFIG[SSRC_${S}%BACKUPHOST1_Z]} ${CONFIG[SSRC_${S}%BACKUPHOST1FS]}"
			# if 2nd backup host, output those settings
			if [ "${CONFIG[SSRC_${S}%BACKUPHOST2]}" ] ; then
				echo " ${CONFIG[SSRC_${S}%BACKUPHOST2]} ${CONFIG[SSRC_${S}%BACKUPHOST2_Z]} ${CONFIG[SSRC_${S}%BACKUPHOST2FS]}"
			else
				echo
			fi
		fi
	done
}

