#!/usr/local/bin/cbsd
#v11.1.19
CIXARG=""
CIXOPTARG="checkpoint hard_timeout jname inter noacpi"
MYDESC="Stop bhyve domain"
CBSDMODULE="bhyve"
EXTHELP="wf_jstop_jstart"
ADDHELP="
${H3_COLOR}Description${N0_COLOR}:

Stop the bhyve domain. By default, the script sends the ACPI signal (through the
kill command) to bhyve process and expects completion during the timeout.
If during this time the virtual machine did not respond to the signal, the script
will terminate the bhyve process of forcibly.

${H3_COLOR}Options${N0_COLOR}:

 ${N2_COLOR}checkpoint=${N0_COLOR}   - Set to '1' for 'bcheckpoint suspend=1' instead of bstop;
 ${N2_COLOR}jname=${N0_COLOR}        - Target VM. If jail='*' or jail='vm*' then stop  all
                 bhyve or VM whose names begin with 'vm', e.g. 'vm1', 'vm2'...;
 ${N2_COLOR}hard_timeout=${N0_COLOR} - Wait N seconds (30 by default) before hard reset.
                 The parameter can be overridden globally through the bstop.conf;
 ${N2_COLOR}noacpi=${N0_COLOR}       - 0,1. Set to 1 to prevent ACPI signal sending, just kill,
                 immediate and merciless. The parameter can be overridden globally
                 through the bstop.conf;

${H3_COLOR}Examples${N0_COLOR}:

 # cbsd bstop
 # cbsd bstop jname=diehard noacpi=1

${H3_COLOR}See also${N0_COLOR}:

 cbsd bstart --help

"

. ${subrdir}/nc.subr
. ${tools}		# for select_jail_by_list

jname=

# check for cloud function when CBSDfile exist
Makefile="${CIX_PWD}/CBSDfile"
if [ ! -r "${Makefile}" ]; then
	[ -z "${1}" ] && select_jail_by_list -s "List of online VMs" -a "On" -e bls -r ${sqlreplica}
fi

noacpi=0		# soft shutdown by default
hard_timeout=30		# default soft timeout: 30 seconds

readconf bstop.conf
cixinit

if [ -r "${Makefile}" ]; then
	[ -z "${CBSDFILE_RECURSIVE}" ] && ${ECHO} "${N1_COLOR}found CBSDfile: ${N2_COLOR}${Makefile}${N0_COLOR}" 1>&2
	. ${Makefile}
	all_bhyve_list=$( ${GREP_CMD} -E '^bhyve_[a-zA-Z0-9_@%:][-a-zA-Z0-9_@%:]*\(\)$' ${Makefile} | ${XARGS_CMD} | ${TR_CMD} -d "()" | ${SED_CMD} s#bhyve_##g )
	jname="${all_bhyve_list}"

	jname_in_args=0

	if [ -n "${ojname}" ]; then
		jname="${ojname}"
	else
		# cbsd bstart <env1> <env2> route
		for i in ${CIX_OTHER_ARGS}; do
			strpos --str="${i}" --search="="
			if [ $? -eq 0 ]; then
				jname_in_args=1
				# not param=value - jail?
				jname_in_cbsdfile=0
				for j in ${all_bhyve_list}; do
					[ "${i}" != "${j}" ] && continue
					jname_in_cbsdfile=1
				done
				if [ ${jname_in_cbsdfile} -eq 1 ]; then
					if [ -z "${ojname}" ]; then
						ojname="${i}"
					else
						ojname="${ojname} ${i}"
					fi
				else
					${ECHO} "${N1_COLOR}${CIX_APP}: env absent in CBSDfile: ${N2_COLOR}${i}${N0_COLOR}" 1>&2
				fi
			fi
		done
		if [ ${jname_in_args} -eq 1 ]; then
			jname="${ojname}"
		else
			jname="${all_bhyve_list}"
		fi
	fi

	unset ojname

	[ -z "${jname}" ] && err 1 "${N1_COLOR}${CIX_APP}: give me jname${N0_COLOR}"

	# multiple?
	strpos --str="${jname}" --search=" "
	if [ $? -ne 0 ]; then
		jail_list="${jname}"
	fi

	if [ -n "${CLOUD_URL}" -a -n "${CLOUD_KEY}" ]; then
		cbsd_api=1
	else
		cbsd_api=0
	fi
else
	cbsd_api=0
fi

[ -z "${jname}" -a -z "${CIX_OTHER_ARGS}" ] && err 1 "${N1_COLOR}No bhyve specified${N0_COLOR}"
[ -z "${TMUX_CMD}" ] && capture TMUX_CMD which tmux
[ -z "${TMUX_CMD}" ] && err 1 "${N1_COLOR}no such tmux${N0_COLOR}"

. ${system}
. ${mdtools}
. ${subrdir}/bhyve.subr

# MAIN
if [ "${mod_cbsd_queue_enabled}" = "YES" -a -z "${MOD_CBSD_QUEUE_DISABLED}" ]; then
	readconf cbsd_queue.conf
	[ -z "${cbsd_queue_backend}" ] && MOD_CBSD_QUEUE_DISABLED="1"
fi

emulator="bhyve" # for jname_is_multiple

# jail_list can be init in CBSDfile case
[ -z "${jail_list}" ] && jname_is_multiple

if [ -n "${jail_list}" ]; then
	TMP_JLIST="${jail_list}"
else
	TMP_JLIST="${CIX_OTHER_ARGS}"
fi

JLIST=

# check for actual vm list in arg list
jail_num=0
for i in ${TMP_JLIST}; do
	cbsdsqlro_vars local "SELECT jname FROM jails WHERE jname='${i}' AND emulator='${emulator}' LIMIT 1" exist
	if [ -n "${exist}" ]; then
		JLIST="${exist} ${JLIST}"
		jail_num=$(( jail_num + 1 ))
	fi
done

. ${subrdir}/time.subr
gettime st_time

# this is multiple list, split it by parallel bstop execution
if [ ${jail_num} -gt 1 ]; then
	cbsdlogger NOTICE ${CIX_APP}: executing for multiple stopping: ${JLIST}
	for jname in ${JLIST}; do
		${miscdir}/daemonize -p ${ftmpdir}/bstop.${jname}.$$ /usr/local/bin/cbsd bstop jname=${jname} noacpi=${noacpi} hard_timeout=${hard_timeout}
		#lets save .pid file
		sleep 1
		if [ -f "${ftmpdir}/bstop.${jname}.$$" ]; then
			read -r _pid < ${ftmpdir}/bstop.${jname}.$$
			cbsd_pwait --pid=${_pid} --timeout=${parallel}
		fi
	done

	wait_for_fpid -a stop

	gettime end_time
	diff_time=$(( end_time - st_time ))
	diff_time=$( displaytime ${diff_time} )
	cbsdlogger NOTICE ${CIX_APP}: executing for multiple done in ${diff_time}: ${JLIST}
	err 0 "${N1_COLOR}Multiple stop: ${N2_COLOR}done in ${diff_time}${N0_COLOR}"
fi

[ -z "${jname}" ] && jname=$( echo ${JLIST} | ${AWK_CMD} '{printf $1}' )
[ -z "${jname}" ] && jname="${1}"
[ -z "${jname}" ] && err 1 "${N1_COLOR}Give me domain name${N0_COLOR}"

. ${distsharedir}/bhyve.conf		# only for for MYCOL variables: used in exports below

. ${subrdir}/rcconf.subr
if [ $? -eq 1 ]; then
	[ ${sqlreplica} -eq 0 ] && err 1 "${N1_COLOR}No such domain: ${N2_COLOR}${jname}${N0_COLOR}"
	capture remotenode bwhereis ${jname}
	[ -z "${remotenode}" ] && err 1 "${N1_COLOR}No such domain: ${N2_COLOR}${jname}${N0_COLOR}"
	for i in ${remotenode}; do
		${ECHO} "${N1_COLOR}Remote bstop: ${N2_COLOR}${jname} ${N1_COLOR}on${N2_COLOR} ${i}${N0_COLOR}"
		rexe node=${i} cbsd bstop jname=${jname}
		if [ $? -eq 0 ]; then
			# update inventory
			${ECHO} "${N1_COLOR}Updating inventory...${N0_COLOR}"
			task autoflush=2 mode=new cbsd retrinv node=${i} tryoffline=1 data=db > /dev/null 2>&1
		fi
	done
	exit 0
fi

[ "${emulator}" != "bhyve" ] && err 1 "${N1_COLOR}Not in bhyve mode${N0_COLOR}"

# CBSD QUEUE
if [ "${mod_cbsd_queue_enabled}" = "YES" -a -z "${MOD_CBSD_QUEUE_DISABLED}" ]; then
	[ -n "${cbsd_bhyve_queue_name}" ] && ${cbsd_queue_backend} cbsd_queue_name=${cbsd_bhyve_queue_name} id=${jname} cmd=bstop status=1 data_status=1 workdir="${workdir}" emulator=${emulator}
fi

check_for_bhyve_process -j ${jname}

# hook
if cix_read_dir ${jailsysdir}/${jname}/master_prestop.d/ >/dev/null 2>&1; then
	exported_data_for_hooks=1
	. ${subrdir}/jcreate.subr	# external_exec_master_script
	# for external hook variables
	geniplist ${ip4_addr}

	export_bhyve_data_for_external_hook
	external_exec_master_script "master_prestop.d"
fi

TRAP=""
TRAP="${TRAP} jswmode jname=${jname} mode=master comment='0';"
trap "${TRAP}" HUP INT ABRT BUS TERM EXIT

jswmode jname=${jname} mode=maintenance comment='Stopping_VM'

if [ ${noacpi} -eq 0 ]; then
	if [ -n "${vm_pid}" ]; then
		# soft stop, send SIGTERM
		tleft=$(( hard_timeout - 1 ))

		# don't use builtin kill here ( SIGCHLD in background -> corrupted global argptr (investigation is needed)
		#kill -15 ${vm_pid} > /dev/null 2>&1
		${miscdir}/daemonize ${KILL_CMD} -15 ${vm_pid} > /dev/null 2>&1
		for ((ci=1; ci<=${hard_timeout}; ci++)); do
			printf "${CLRLINE}"
			printf "${CURSORRST}"
			if [ ${tleft} -lt 6 ]; then
				printf "${W2_COLOR}${ci}${N1_COLOR}: Send SIGTERM to ${N2_COLOR}${jname}${N1_COLOR}. Soft timeout is ${N2_COLOR}${hard_timeout}${N1_COLOR} sec. ${W1_COLOR}${tleft}${N1_COLOR} seconds left [${N0_COLOR}"
			else
				printf "${N2_COLOR}${ci}${N1_COLOR}: Send SIGTERM to ${N2_COLOR}${jname}${N1_COLOR}. Soft timeout is ${N2_COLOR}${hard_timeout}${N1_COLOR} sec. ${W1_COLOR}${tleft}${N1_COLOR} seconds left [${N0_COLOR}"
			fi
			for ((cx=1; cx<=${ci}; cx++)); do
				printf "${N1_COLOR}."
			done
			cbsd_pwait --pid=${vm_pid} --timeout=1 > /dev/null 2>&1
			_ret=$?
			[ ${_ret} -ne 0 ] && break
			tleft=$(( tleft - 1 ))
			[ ${tleft} -le 0 ] && break
			# send SIGTEM again when double_acpi == 1
			[ "${double_acpi}" = "1" ] && kill -15 ${vm_pid} > /dev/null 2>&1
		done
		printf "]${N0_COLOR}\n"
		${PS_CMD} -p ${vm_pid} > /dev/null 2>&1
		if [ $? -eq 0 ]; then
			# still live
			cbsdlogger NOTICE ${CIX_APP}: bhyve domain ${jname} does not want to die via ACPI, soft timeout ${hart_timeout} exceeded. Kill him
			kill -9 ${vm_pid} > /dev/null 2>&1 || true
		fi
	else
		err 0 "${N1_COLOR}Warning: unable to determine bhyve pid for: ${N2_COLOR}${jname}${N0_COLOR}"
	fi
else
	if [ -n "${vm_pid}" ]; then
		kill -9 ${vm_pid} > /dev/null 2>&1 || true
	else
		err 0 "${N1_COLOR}Warning: unable to determine bhyve pid for: ${N2_COLOR}${jname}${N0_COLOR}"
	fi
fi

for i in vnc_port vnc_bind ssh_port ssh_host rdp_port rdp_host; do
	[ -r ${jailsysdir}/${jname}/${i} ] && ${RM_CMD} -f ${jailsysdir}/${jname}/${i}
done

capture _res ${BHYVECTL_CMD} --destroy --vm="${jname}"

# extra check for no any cbsd process related to this VM is active
epid=$( ${PS_CMD} axopid,ucomm,command -ww | ${GREP_CMD} "${tmpdir}/bhyveload.${jname}.lock" | ${GREP_CMD} -v grep | ${AWK_CMD} '{printf $1" "}' )
[ -n "${epid}" ] && kill -9 ${epid} > /dev/null 2>&1
epid=$( ${PS_CMD} axopid,ucomm,command -ww | ${GREP_CMD} "${CIX_DISTDIR}/share/bhyverun.sh -c ${workdir}/jails-system/${jname}/bhyve.conf" | ${GREP_CMD} -v grep | ${AWK_CMD} '{printf $1" "}' )
[ -n "${epid}" ] && kill -9 ${epid} > /dev/null 2>&1

# TPM
if [ "${tpm}" = "new" ]; then
	${miscdir}/daemonize ${miscdir}/swtpm -a stop -b ${SWTPM_CMD} -d ${jailsysdir}/${jname}/tpm -e error -p swtpm.pid -l tpm.log -v 20 -u swtpm-sock -s mystate
fi

jswmode jname=${jname} mode=master comment='0'

# cleanup for ifaces
. ${subrdir}/vnet.subr

fwcounters jname=${jname} mode=remove
expose mode=clear jname=${jname}

# delete orphaned vale_ports if exists
#cbsdsqlrw local "DELETE FROM vale_ports WHERE jname='${jname}'"
bcleanup jname=${jname}

# update state_time
cbsdsqlrw ${jailsysdir}/${jname}/local.sqlite "UPDATE settings SET state_time=(strftime('%s','now'))"

# update state_time and pid in local DB
cbsdsqlrw local "UPDATE jails SET jid=0,state_time=(strftime('%s','now')) WHERE jname='${jname}'"

if cix_read_dir ${jailsysdir}/${jname}/master_poststop.d/ >/dev/null 2>&1; then
	if [ "${exported_data_for_hooks}" != "1" ]; then
		. ${subrdir}/jcreate.subr	# external_exec_master_script
		# for external hook variables
		geniplist ${ip4_addr}

		export_bhyve_data_for_external_hook
	fi
	external_exec_master_script "master_prestop.d"
fi

# CBSD QUEUE
if [ "${mod_cbsd_queue_enabled}" = "YES" -a -z "${MOD_CBSD_QUEUE_DISABLED}" ]; then
	[ -n "${cbsd_bhyve_queue_name}" ] && ${cbsd_queue_backend} cbsd_queue_name=${cbsd_bhyve_queue_name} id=${jname} cmd=bstop status=2 data_status=0 workdir="${workdir}" emulator=${emulator}
fi

# jailed?
cbsdsqlro_vars /usr/jails/var/db/local.sqlite "SELECT jailed FROM settings LIMIT 1" _jailed
# Если var пуста, присвоить "default"
: "${_jailed:=0}"

if [ "${_jailed}" != "0" ]; then
	${ECHO} "${N1_COLOR}Stopping jailhost: ${N2_COLOR}${_jailed}${N0_COLOR}"
	jstop jname="${_jailed}"
	echo
fi

# chrooted?
# disable for a while
_chrooted=0
#_chrooted=$( cbsdsqlro ${jailsysdir}/${jname}/local.sqlite "SELECT chrooted FROM settings LIMIT 1" )
#[ -z "${_chrooted}" ] && _chrooted="0"

#if [ "${_chrooted}" != "0" ]; then
#	${ECHO} "${N1_COLOR}CHROOTED bstop: ${N2_COLOR}${_chrooted}${N0_COLOR}"
#fi

${TMUX_CMD} -Lcbsd-"${jname}" kill-server 2>/dev/null || true

# API compat: ~cbsd/jails-system/info*.*
${miscdir}/daemonize ${toolsdir}/save-jail-info jname=${jname}

gettime end_time
diff_time=$(( end_time - st_time ))
diff_time=$( displaytime ${diff_time} )
${ECHO} "${N1_COLOR}${CIX_APP} done ${N2_COLOR}in ${diff_time}${N0_COLOR}"
cbsdlogger NOTICE ${CIX_APP}: bhyve domain ${jname} stopped in ${diff_time}

exit 0
