#!/usr/local/bin/cbsd
#v11.1.0
MYARG="mode"
MYOPTARG="jname snapname snapfs header display"
MYDESC="Jail snapshot management"
ADDHELP="mode can: list, create, destroy, destroyall, destroyall_original, clone, rollback\n\
snapfs can be: rsync\n\
snapname can be: \"gettimeofday\" or any arbitrary word\n\
header=0 don't print header\n\
display= list by comma for column. Default: snapname,jname,creation,refer\n\
mode:\n\
  - destroyall: remove all snapshot for current jail\n\
  - destroyall_original: remove all original snapshot, ZPOOL@cbsd-original-<jname>, <CLOUD>@boot-<jname>\n"
CBSDMODULE="jail"

. ${subrdir}/nc.subr
. ${strings}
. ${cbsdinit}

show_header()
{
	local _header="${BOLD}${myheader}${N0_COLOR}"
	[ ${header} -ne 0 ] && $ECHO ${_header}
}

init_zfs_mod_by_jname()
{
	mod=

	if [ -n "${jname}" ]; then

		. ${subrdir}/rcconf.subr

		case "${emulator}" in
			jail)
				mod="${ZPOOL}/${jname}"
				;;
			bhyve)
				# only dsk1.vhd at the moment
				_dsk="dsk1.vhd"
				readconf zfs.conf
				. ${subrdir}/zfs.subr
				if is_getzvol ${data}/${_dsk}; then
					mod="${is_zvol}"
				else
					err 1 "${N1_COLOR}bhyve zfs_snapshot_create: unable to find zvol for ${data}/${_dsk}${N0_COLOR}"
				fi
				;;
		esac
	fi
}


init_zfs_snappath_by_jname()
{
	snappath=
	local _dsk

	case "${emulator}" in
		jail)
			snappath="${ZPOOL}/${jname}@${snapname}"
			;;
		bhyve)
			# only dsk1.vhd at the moment
			_dsk="dsk1.vhd"
			readconf zfs.conf
			. ${subrdir}/zfs.subr

			if is_getzvol ${data}/${_dsk}; then
				snappath="${is_zvol}@${snapname}"
			else
				err 1 "${N1_COLOR}bhyve zfs_snapshot_create: unable to find zvol for ${data}/${_dsk}${N0_COLOR}"
			fi
			;;
	esac
}


check_jname()
{
	[ -z "${jname}" ] && err 1 "${N1_COLOR}Please set ${N2_COLOR}jname=${N0_COLOR}"
	. ${subrdir}/rcconf.subr
	if [ $? -eq 1 ]; then
		# jail can be in unregister state, check it
		JAILRCCONF="${jailrcconfdir}/rc.conf_${jname}"
		[ ! -f "${JAILRCCONF}" ] && err 1 "${N1_COLOR}No such jail: ${N2_COLOR}${jname}${N0_COLOR}"
		. ${JAILRCCONF}
		unregister=1
	fi
}

zfs_snapshot_list()
{
	local _fs _p1 _jname _createtime _status _snaptime _snapname _refer

	init_zfs_mod_by_jname

	show_header

	for _fs in $( ${ZFS_CMD} list -H -r -t snapshot -o name ${mod} 2>/dev/null ); do
		_status=""
		_jname=$( ${ZFS_CMD} get -H -o value -s local cbsdsnap:jname "${_fs}" 2>/dev/null )

		[ -z "${_jname}" ] && continue		# not my snap
		[ -n "${jname}" -a "${jname}" != "${_jname}" ] && continue
		_snapname=$( ${ZFS_CMD} get -H -o value -s local cbsdsnap:snapname "${_fs}" 2>/dev/null )
		[ -z "${_snapname}" ] && continue
		#populate values for in output string
		for _i in ${mydisplay}; do
			case "${_i}" in
				"snapname")
					_status="${_status}${_snapname} "
					;;
				"jname")
					_status="${_status}${_jname} "
					;;
				"creation")
					_createtime=$( ${ZFS_CMD} get -H -o value creation "${_fs}" 2>/dev/null )
					_snaptime=$( ${DATE_CMD} -j -f "%a %b %d %H:%M %Y" "${_createtime}" "+%Y-%m-%d__%H:%M" )
					_status="${_status}${_snaptime} "
					;;
				"refer")
					_refer=$( ${ZFS_CMD} get -H -o value refer "${_fs}" 2>/dev/null )
					_status="${_status}${_refer} "
					;;
			esac
		done
		${ECHO} "${N0_COLOR}${_status}"
	done
}

zfs_snapshot_destroyall()
{
	local _fs _jname _snapname

	init_zfs_mod_by_jname

	for _fs in $( ${ZFS_CMD} list -H -r -t snapshot -o name ${mod} 2>/dev/null ); do
		_jname=$( ${ZFS_CMD} get -H -o value -s local cbsdsnap:jname "${_fs}" 2>/dev/null )
		[ -z "${_jname}" ] && continue
		[ -n "${jname}" -a "${jname}" != "${_jname}" ] && continue
		_snapname=$( ${ZFS_CMD} get -H -o value -s local cbsdsnap:snapname "${_fs}" 2>/dev/null )
		[ -z "${_snapname}" ] && continue
		zfs_snapshot_destroy ${_fs} && ${ECHO} "${N1_COLOR}zfs snapshot for ${jname} destroyed: ${N2_COLOR}${_snapname}${N0_COLOR}"
	done
}

zfs_snapshot_destroy_by_snapname()
{
	local _fs _jname _snapname

	[ -z "${snapname}" ] && err 1 "${N1_COLOR}Please set ${N2_COLOR}snapname=${N1_COLOR} or use ${N2_COLOR}mode=destroyall ${N1_COLOR}to destroy all snapshots for this jail${N0_COLOR}"
	init_zfs_mod_by_jname

	for _fs in $( ${ZFS_CMD} list -H -r -t snapshot -o name ${mod} 2>/dev/null ); do
		_jname=$( ${ZFS_CMD} get -H -o value -s local cbsdsnap:jname "${_fs}" 2>/dev/null )
		[ -z "${_jname}" ] && continue
		[ -n "${jname}" -a "${jname}" != "${_jname}" ] && continue
		_snapname=$( ${ZFS_CMD} get -H -o value -s local cbsdsnap:snapname "${_fs}" 2>/dev/null )
		[ -z "${_snapname}" -o "${_snapname}" != "${snapname}" ] && continue
		zfs_snapshot_destroy ${_fs} && ${ECHO} "${N1_COLOR}zfs snapshot for ${jname} destroyed: ${N2_COLOR}${_snapname}${N0_COLOR}"
		# if cycle is not end than something wrong with parsing of snapname
	done
}

zfs_snapshot_destroyall_original()
{
	local _fs _jname _snapname ZPOOL
	local _parent

	readconf zfs.conf

	if [ "${emulator}" = "bhyve" ]; then
		#[ -z "${NOINTER}" ] && ${ECHO} "${N1_COLOR}destroyall_original not supported yet for vm${N0_COLOR}"
		exit 0
	fi

	ZPOOL=$( ${ZFS_CMD} get -Ho value name ${jaildatadir} )

	[ -z "${ZPOOL}" ] && err 1 "${N1_COLOR}Empty zpool for: ${N2_COLOR}${jaildatadir}${N0_COLOR}"
	mod="${ZPOOL}"

	for _fs in $( ${ZFS_CMD} list -H -r -t snapshot -o name ${mod} 2>/dev/null ); do
		_snapname=${_fs##*@}
		[ "${_snapname}" != "cbsd-original-${jname}" ] && continue
		zfs_snapshot_destroy ${_fs} && ${ECHO} "${N1_COLOR}zfs original snapshot for ${jname} destroyed: ${N2_COLOR}${_fs}${N0_COLOR}"
	done
}

rsync_snapshot_list()
{
	err 1 "${N1_COLOR}jsnapshot: rsync not implemented${N0_COLOR}"
}

ufs_snapshot_list()
{
	err 1 "${N1_COLOR}jsnapshot: ufs not implemented${N0_COLOR}"
	fs_type="ufs"

	for snap in $( snapinfo $fs_dir 2>/dev/null ); do
		[ ! -f $snap ] && continue

		#   determine sizes
		fs_size=$( ${DF_CMD} -k $fs_dir | ${TAIL_CMD} -n1 | ${AWK_CMD} '{ print $2; }' )
		used_size=$( ${DF_CMD} -k $fs_dir | ${TAIL_CMD} -n1 | ${AWK_CMD} '{ print $3; }' )
		snap_size=$( /usr/bin/du -k $snap | ${AWK_CMD} '{ print $1; }' )

		#   determine snapshot creation time
		if [ ".$verbose" = .yes ]; then
			snap_time=$( /usr/bin/stat -f "%B" $snap )
			snap_time=$( ${DATE_CMD} -r "$snap_time" "+%Y-%m-%dT%H:%M" )
		fi

		#   calculate percentages
		snap_percent=` echo . | ${AWK_CMD} '{ printf("%.1f%%", (snap / fs) * 100); }' snap="$snap_size" fs="$fs_size" `
		used_percent=` echo . | ${AWK_CMD} '{ printf("%.1f%%", (used / fs) * 100); }' used="$used_size" fs="$fs_size" `

		#   canonicalize for output
		fs_size=$( canonksize $fs_size )
		snap_size=$( canonksize $snap_size )
		used_size=$( canonksize $used_size )
		snap_file=`echo "$snap" | ${SED_CMD} -e 's;.*/\([^/]*\)$;\1;'`

		#   output snapshot information
		if [ ".$verbose" = .yes ]; then
			printf "%-15s %4s %8s %7s %8s %7s  %-15s %s\n" "$fs_dir" "$fs_type" "$used_size" "$used_percent" "$snap_size" "$snap_percent" "$snap_file" "$snap_time"
		else
			printf "%-15s %8s %7s %8s %7s  %-15s\n" "$fs_dir" "$used_size" "$used_percent" "$snap_size" "$snap_percent" "$snap_file"
		fi
	done
}

zfs_snapshot_create()
{
	local _tst _createtime
	local _zfs _dsk

	[ -z "${snapname}" ] && err 1 "${N1_COLOR}Empty ${N2_COLOR}snapname=${N0_COLOR}"

	_createtime=$( ${DATE_CMD} "+%Y%m%d%H%M%S" )
	[ "${snapname}" = "gettimeofday" ] && snapname=${_createtime}

	init_zfs_snappath_by_jname

	_tst=$( ${ZFS_CMD} snapshot ${snappath} 2>&1)

	[ $? -ne 0 ] && err 1 "${N1_COLOR}zfs snapshot error: ${N0_COLOR}${_tst}"
	${ZFS_CMD} set cbsdsnap:jname=${jname} ${snappath}
	${ZFS_CMD} set cbsdsnap:snapname=${snapname} ${snappath}
}

ufs_snapshot_create()
{
	err 1 "${N1_COLOR}jsnapshot: ufs not implemented${N0_COLOR}"
}

rsync_snapshot_create()
{
	err 1 "${N1_COLOR}jsnapshot: rsync not implemented${N0_COLOR}"
}

zfs_snapshot_destroy()
{
	[ -z "${1}" ] && return 1
	${ZFS_CMD} destroy ${1}
}

ufs_snapshot_destroy()
{
	err 1 "${N1_COLOR}jsnapshot: ufs not implemented${N0_COLOR}"
}

ufs_snapshot_destroyall()
{
	err 1 "${N1_COLOR}jsnapshot: ufs not implemented${N0_COLOR}"
}

ufs_snapshot_destroy_by_snapname()
{
	err 1 "${N1_COLOR}jsnapshot: ufs not implemented${N0_COLOR}"
}

rsync_snapshot_destroy()
{
	err 1 "${N1_COLOR}jsnapshot: rsync not implemented${N0_COLOR}"
}


zfs_snapshot_rollback()
{
	local _createtime _snapname _jname
	[ -z "${snapname}" ] && err 1 "${N1_COLOR}empty ${N2_COLOR}snapname=${N0_COLOR}"

	init_zfs_snappath_by_jname

	_jname=$( ${ZFS_CMD} get -H -o value -s local cbsdsnap:jname "${snappath}" 2>/dev/null)
	[ -z "${_jname}" ] && err 1 "${N1_COLOR}no such snapshot ${snapname} for ${jname}${N0_COLOR}"
	[ -n "${jname}" -a "${jname}" != "${_jname}" ] && err "${N1_COLOR}Found snapshot ${N2_COLOR}${snapname}${MANGETA} but its owner is ${N2_COLOR}${_jname}${N0_COLOR}"
	_snapname=$( ${ZFS_CMD} get -H -o value -s local cbsdsnap:snapname "${snappath}" )
	[ -z "${_snapname}" ] && err 1 "${N1_COLOR}Snapshot found but he is not created by ${N2_COLOR}cbsd${N0_COLOR} - skipp${N0_COLOR}"
	 _createtime=$( ${ZFS_CMD} get -H -o value creation "${snappath}" )
	${ZFS_CMD} rollback -r ${snappath} && ${ECHO} "${N1_COLOR}Restored state to ${N2_COLOR}${snapname}${N1_COLOR} snapshot created in ${N2_COLOR}${_createtime}${N0_COLOR}"
}

ufs_snapshot_rollback()
{
	err 1 "${N1_COLOR}jsnapshot: ufs not implemented${N0_COLOR}"
}

rsync_snapshot_rollback()
{
	err 1 "${N1_COLOR}jsnapshot: rsync not implemented${N0_COLOR}"
}

#MAIN
if [ -z "${snapfs}" ]; then
	if [ "$zfsfeat" = "1" ]; then
		snapfs="zfs"
		. ${subrdir}/zfs.subr
		ZPOOL=$( ${ZFS_CMD} get -Ho value name ${jaildatadir} 2>/dev/null)
		[ -z "${ZPOOL}" ] && err 1 "${N1_COLOR}Can't find ZFS pool on ${N2_COLOR}${jaildatadir}${N0_COLOR}"
	else
		snapfs="ufs"
	fi
fi

[ -z "${display}" ] && display="jname,snapname,creation,refer"

#remove commas for loop action on header
mydisplay=$( echo ${display} | ${TR_CMD} ',' '  ' )

# upper for header
myheader=$( echo ${mydisplay} | ${TR_CMD} '[:lower:]' '[:upper:]' )

[ -z "${header}" ] && header=1

case "${mode}" in
	"list")
		${snapfs}_snapshot_list |/usr/bin/column -t
		;;
	"create")
		check_jname
		${snapfs}_snapshot_create
		;;
	"destroy")
		check_jname
		${snapfs}_snapshot_destroy_by_snapname
		;;
	"destroyall")
		check_jname
		${snapfs}_snapshot_destroyall
		;;
	"destroyall_original")
		check_jname
		${snapfs}_snapshot_destroyall_original
		;;
	"rollback")
		check_jname
		${snapfs}_snapshot_rollback
		;;
	*)
		err 1 "${N1_COLOR}Unknown mode: ${N2_COLOR}${mode}${N0_COLOR}"
esac
