#!/usr/local/bin/cbsd
#v12.1.9
MYARG=""
MYOPTARG="fast inter jname"
MYDESC="Stop jail"
CBSDMODULE="jail"
EXTHELP="wf_jstop_jstart"
ADDHELP="\

${H3_COLOR}Description${N0_COLOR}:

 Stop the container.

${H3_COLOR}Options${N0_COLOR}:

 ${N2_COLOR}fast=${N0_COLOR}  - set 1 to destroy jail without config, jail -rR: no shutdown seq.
 ${N2_COLOR}inter=${N0_COLOR} - set 1 to prevent any questions and to accept answers by default.
 ${N2_COLOR}jname=${N0_COLOR} - target jail. If jail='*' or jail='pri*' then stop all jails or
          jails whose names begin with 'pri', e.g. 'prison1', 'prisonX'...

${H3_COLOR}Examples${N0_COLOR}:

 # cbsd jstop
 # cbsd jstop jname='memcache*'

${H3_COLOR}See also${N0_COLOR}:

 cbsd jstart --help

"

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

[ -z "${1}" ] && select_jail_by_list -s "List of online jail" -a "On" -r ${sqlreplica}
fast=
. ${cbsdinit}

[ -n "${fast}" ] && ofast="${fast}"
[ -z "${fast}" ] && fast=0

. ${system}
. ${mdtools}

[ -z "${jname}" -a -z "$*" ] && err 1 "${N1_COLOR}No jail specified${N0_COLOR}"

[ -n "${inter}" ] && shift

# for external_exec-related command
. ${subrdir}/jcreate.subr

emulator="jail"		# for jname_is_multiple
jname_is_multiple

# MAIN

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

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

. ${subrdir}/time.subr
st_time=$( ${DATE_CMD} +%s )

# this is multiple list, split it by parallel bstop execution
if [ ${jail_num} -gt 1 ]; then
	cbsdlogger NOTICE ${CBSD_APP}: executing for multiple stopping: ${JLIST}

	for jname in ${JLIST}; do
		${DAEMON_CMD} -p ${ftmpdir}/jstop.${jname}.$$ /usr/local/bin/cbsd jstop jname=${jname}
		#lets save .pid file
		sleep 1
		[ -f "${ftmpdir}/jstop.${jname}.$$" ] && cbsd_pwait --pid=$( ${CAT_CMD} ${ftmpdir}/jstop.${jname}.$$ ) --timeout=${parallel}
	done

	wait_for_fpid -a stop

	end_time=$( ${DATE_CMD} +%s )
	diff_time=$(( end_time - st_time ))
	diff_time=$( displaytime ${diff_time} )
	${ECHO} "${N1_COLOR}${CBSD_APP} done ${N2_COLOR}in ${diff_time}${N0_COLOR}"
	cbsdlogger NOTICE ${CBSD_APP}: executing for multiple done in ${diff_time}: ${JLIST}
	err 0 "${N1_COLOR}Multiple stop: ${N2_COLOR}done${N0_COLOR}"
fi

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

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

[ ${jid} -eq 0 ] && err 1 "${N1_COLOR}Not Running: ${N2_COLOR}${jname}${N0_COLOR}"
[ ${status} -eq 3 ] && err 1 "${N1_COLOR}Jail in maintenance mode${N0_COLOR}"
[ "${emulator}" = "bhyve" ] && err 1 "${N1_COLOR}For bhyve jail use: ${N2_COLOR}cbsd bstop ${jname} ${N1_COLOR}instead${N0_COLOR}"

. ${subrdir}/universe.subr
init_basedir

# Update Redis
if [ "${mod_cbsd_redis_enabled}" = "YES" -a -z "${MOD_CBSD_REDIS_DISABLED}" ]; then
	cbsdredis hset "jail:${jname}" status 2 || echo "WARNING: failed to update Redis!"
	cbsdredis publish cbsd_events '{"cmd":"jstop", "node":"'${nodename}'", "jail":"'${jname}'", "status":1}'
fi

# CBSD QUEUE
if [ "${mod_cbsd_queue_enabled}" = "YES" -a -z "${MOD_CBSD_QUEUE_DISABLED}" ]; then
	readconf cbsd_queue.conf
	if [ -z "${cbsd_queue_backend}" ]; then
		MOD_CBSD_QUEUE_DISABLED="1"
	else
		[ -n "${cbsd_jail_queue_name}" ] && ${cbsd_queue_backend} cbsd_queue_name=${cbsd_jail_queue_name} id=${jname} cmd=jstop status=1 workdir="${workdir}"
	fi
fi

# VNC auto stop
if [ -x "${distmoduledir}/vncterm.d/cbsdvnc" ]; then
	${ECHO} "${N1_COLOR}Stopping VNC daemon...${N2_COLOR}"
	vncterm jname=${jname} mode=stop || true
fi

if [ "${vnet}" = "1" -a "${vimage_feature}" = "0" ]; then
	${ECHO} "${N1_COLOR}Jail ${N2_COLOR}${jname}${N1_COLOR} have vnet=1 flags but your kernel is not support VIMAGE${N0_COLOR}"
	${ECHO} "${N1_COLOR}Please recompile kernel with: ${N2_COLOR}options VIMAGE${N0_COLOR}"
	vnet=0
fi

# args for makejconf
epairb_list=

if [ ${vnet} -eq 1 ]; then
	. ${subrdir}/vnet.subr

	interfaces=$( cbsdsqlro ${jailsysdir}/${jname}/local.sqlite SELECT name FROM jailnic | while read _int; do
		printf "${_int} "
	done ) || err 1 "${N1_COLOR}jstart: error get interfaces name for vnet nic map${N0_COLOR}"

	eth_seq=0
	myepair_list=

	for i in ${interfaces}; do

		nic_parent=
		nic_parent=$( cbsdsqlro ${jailsysdir}/${jname}/local.sqlite SELECT nic_parent FROM jailnic WHERE name=\"${i}\" )

		[ "${nic_parent}" = "0" -o "${nic_parent}" = "auto" ] && nic_parent=$( getnics-by-ip ip=0.0.0.0 skip=bridge )
		myepair=
		myepair=$( get_my_device epair ${jname}-eth${eth_seq} )
		if [ -n "${myepair}" ]; then

			# detach from vale if vale used
			case "${nic_parent}" in
				cbsdvale*)
					strpos --str="${nic_parent}" --search="_"
					_pos=$?
					if [ ${_pos} -eq 0 ]; then
						# not vale_XXX form
						#err 1 "${N1_COLOR}${CBSD_APP}: vale switch not vale_XXX form: ${N2_COLOR}${nic_parent}${N0_COLOR}"
					else
						_arg_len=$( strlen ${nic_parent} )
						_pref=$(( _arg_len - _pos ))
						_vale_arg=$( substr --pos=0 --len=${_pos} --str="${nic_parent}" )
						_sw=$( substr --pos=$(( ${_pos} +2 )) --len=${_pref} --str="${nic_parent}" )
					fi
					valeid=$( cbsdsqlro local SELECT idx FROM vale WHERE name=\"${_sw}\"  )
					if [ -n "${valeid}" ]; then
						cbsdlogger NOTICE "${CBSD_APP}: vale switch id: ${valeid} (vale${valeid})"
						${ECHO} "${N1_COLOR}${CBSD_APP}: detach from vale switch id: ${valeid} (${N2_COLOR}vale${valeid}${N1_COLOR})${N0_COLOR}"
						${distdir}/tools/vale-ctl -d vale${valeid}:${myepair}
					fi
			esac

			myepair_list="${myepair_list} ${myepair}"
			# returned epairXa, but we need epairXb, so append 'end' to the end of
			# string and replace 'aend' to 'b'
			if [ -z "${epairb_list}" ]; then
				epairb_list="${myepair}end"
			else
				epairb_list="${epairb_list},${myepair}end"
			fi
			# not the best solution, but better than nothing
			epairb_list=$( echo ${epairb_list} | ${SED_CMD} 's:aend:b:g' )
		fi
		eth_seq=$(( eth_seq + 1 ))
	done
	[ -z "${myepair_list}" ] && ${ECHO} "${N1_COLOR}Warning: Cant find epair_list for vnet-type jail:${N2_COLOR}${jname}${N0_COLOR}"
fi

#determine that jail is FreeBSD. Useful for vnet operation in makejconf and
is_freebsd=0

if [ ${baserw} -eq 1 ]; then
	elftest=${data}/bin/sh
	path=${data}
else
	elftest="${BASE_DIR}/bin/sh"
fi

[ -f "${elftest}" ] && osname=$( ${miscdir}/elf_tables --osname ${elftest} )
[ "${osname}" = "freebsd" ] && is_freebsd=1

makejconf jname=${jname} out=${ftmpdir}/${jname}.conf epair=${epairb_list} fbsd=${is_freebsd} is_stop=1

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

# for external hook variables
geniplist ${ip4_addr}

# there is some problem with stopping cron:
# Stopping cron.
# Waiting for PIDS: 71597
# 90 second watchdog timeout expired. Shutdown terminated.
# jail: jail17: /bin/sh /etc/rc.shutdown: exited on signal 9
# always reproduced when the jail have persist flags:
# e.g for CBSD whith zfs attached fs or VNET-based.
# try to defend and kill the cron preventively

#[ -r ${path}/var/run/cron.pid ] && /bin/pkill -9 -F ${path}/var/run/cron.pid
# pid not always correct, so just kill it via jexec:
# roughly, but what to do with FreeBSD ;(
if [ ${is_freebsd} -eq 1 ]; then
	#${JEXEC_CMD} ${jid} pkill -9 cron		# need $emulator support. exec via CBSD jexec instead:
	jexec jname=${jname} pkill -9 cron
fi

export_jail_data_for_external_hook
external_exec_master_script "master_prestop.d"
exec_master_prestop
exec_prestop

#rctl area
${ECHO} "${N1_COLOR}Stoping jail: ${N2_COLOR}${jname}, parallel timeout=${parallel}${N0_COLOR}"
jrctl jname=${jname} mode=unset >/dev/null 2>&1

external_exec_script -s stop.d
if [ ${vnet} -eq 1 ]; then
	if [ "${ip4_addr}" = "REALDHCP" ]; then
		#${JEXEC_CMD} ${jname} /bin/pkill -9 dhclient dhcpcd 2>/dev/null 1>&2		# need $emulator support. exec via CBSD jexec instead:
		jexec jname=${jname} pkill -9 dhclient dhcpcd 2>/dev/null 1>&2
	fi
fi

[ -n "${ofast}" ] && fast="${ofast}"

case "${platform}" in
	DragonFly)
		JAIL_STOP_CMD="jexec jname=${jname} ${exec_stop}"
		;;
	*)
		if [ "${fast}" = "1" ]; then
			JAIL_STOP_CMD="${JAIL_CMD} -rR ${jname}"
		else
			JAIL_STOP_CMD="${JAIL_CMD} -f ${ftmpdir}/${jname}.conf -r ${jname}"
		fi
		;;
esac

${JAIL_STOP_CMD} > /dev/null 2>&1 || true

for i in $( ${PS_CMD} -J ${jid} -o pid 2>/dev/null | ${GREP_CMD} -v PID ); do
	${KILL_CMD} -9 ${i} > /dev/null 2>&1
done

exec_master_poststop
external_exec_master_script "master_poststop.d"
exec_poststop

if [ ${vnet} -eq 1 -a -n "${myepair_list}" ]; then
	printf "${N1_COLOR}Destroy epair: ${H3_COLOR}"

	# how necessary to make 'ifconfig -vnet ethX' before
	#no mem leak here and all correct?
	for i in ${myepair_list}; do
		printf "${i} "
		${IFCONFIG_CMD} ${i} destroy
	done
	${ECHO} "${N0_COLOR}"
fi

if [ "${platform}" != "DragonFly" ]; then
	# waiting for fixed kqueue in upstream
	${JAIL_CMD} -r ${ST} > /dev/null 2>&1
	${JAIL_CMD} -r ${ST} > /dev/null 2>&1
fi

cbsdsqlrw local "UPDATE jails SET jid=0,status=0,state_time=(strftime('%s','now')) WHERE jname='${jname}'"

jaillock="${jailsysdir}/${jname}/locked"

[ -f "${jaillock}" ] && ${RM_CMD} -f ${jaillock}

#determine that jail is FreeBSD. Useful for vnet operation in makejconf and
is_freebsd=0

if [ ${baserw} -eq 1 ]; then
	elftest=${data}/bin/sh
else
	elftest="${BASE_DIR}/bin/sh"
fi

[ -f "${elftest}" ] && osname=$( ${miscdir}/elf_tables --osname ${elftest} )
[ "${osname}" = "freebsd" ] && is_freebsd=1

[ -n "${mdsize}" -a "${mdsize}" != "0" ] && MDFILE=$( eval find_md_by_mountpath ${data} )

jcleanup jname=${jname} > /dev/null

for pureip in ${IPS}; do
	iptype ${pureip}
	[ -n "${VHID}" ] && continue
	_inet=$?
	if [ -n "${interface}" -a "${interface}" != "0" ]; then
		iface=$( getnics-by-ip ip=${pureip} )
		ipwmask ${pureip}
		if [ -n "${IWM}" ]; then
			case ${_inet} in
				1)
					MODIF="inet"
					;;
				2)
					MODIF="inet6" ;;
			esac
			${IFCONFIG_CMD} ${iface} ${MODIF} ${pureip} -alias > /dev/null 2>&1 ||true
		fi
	fi
done

# make id file
UNDhost_hostname=$( ${ECHO} ${host_hostname} | ${TR_CMD}  "." "_" )
FID="/var/run/jail_${UNDhost_hostname}.id"
[ ! -f "${FID}" ] || ${RM_CMD} -f ${FID}

[ -n "${mdsize}" -a "${mdsize}" != "0" -a -n "${MDFILE}" ] && unmountmd md=${MDFILE}
${RM_CMD} -f ${ftmpdir}/${jname}.conf

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

end_time=$( ${DATE_CMD} +%s )
diff_time=$(( end_time - st_time ))
# Update Redis
if [ "${mod_cbsd_redis_enabled}" = "YES" -a -z "${MOD_CBSD_REDIS_DISABLED}" ]; then
	cbsdredis hdel "jail:${jname}" jid
	cbsdredis hset "jail:${jname}" status 0 || echo "WARNING: failed to update Redis!"
	cbsdredis publish cbsd_events '{"cmd":"jstop", "node":"'${nodename}'", "jail":"'${jname}'", "status":0, "duration":'${diff_time}'}'
fi
diff_time=$( displaytime ${diff_time} )
${ECHO} "${N1_COLOR}${CBSD_APP} done ${N2_COLOR}in ${diff_time}${N0_COLOR}"
cbsdlogger NOTICE ${CBSD_APP}: jail ${jname} stopped in ${diff_time}

exit 0
