#!/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 jail container. When used in a directory with a CBSDfile,
the jstop command only processes the environments described in the CBSDfile.

${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

jname=

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

fast=
. ${cbsdinit}
ojname="${jname}"

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

. ${system}
. ${mdtools}

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

	# cbsd jstart <env1> <env2> route
	jname_in_args=0

	if [ -n "${ojname}" ]; then
		jail_list="${ojname}"
	else
		# cbsd jstart <env1> <env2> route
		for i in $*; 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_jail_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}${CBSD_APP}: env absent in CBSDfile: ${N2_COLOR}${i}${N0_COLOR}" 1>&2
				fi
			fi
		done
		if [ ${jname_in_args} -eq 1 ]; then
			jail_list="${ojname}"
		else
			jail_list="${all_jail_list}"
		fi
	fi

	unset ojname jname

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

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

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

	if [ -n "${ojname}" ]; then
		jail_list="${ojname}"
	else
		for i in $*; do
			strpos --str="${i}" --search="="
			[ $? -ne 0 ] && continue

			if [ -z "${jail_list}" ]; then
				jail_list="${i}"
			else
				jail_list="${jail_list} ${i}"
			fi
		done
	fi

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

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

# for external_exec-related command
. ${subrdir}/jcreate.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

TRAP=""
emulator="jail" # 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="${jname}"
fi

JLIST=

# check for actual vm list in arg list
jail_num=0
jname=
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
		if [ -n "${JLIST}" ]; then
			JLIST="${JLIST} ${i}"
		else
			JLIST="${i}"
		fi
		if [ ${jail_num} -eq 0 ]; then
			jname="${i}"
		else
			# when > 1, jname unused below
			unset jname
		fi
		jail_num=$(( jail_num + 1 ))
	fi
done

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

# this is multiple list, split it by parallel jstop execution
if [ ${jail_num} -gt 1 ]; then
	cbsdlogger NOTICE ${CBSD_APP}: executing for multiple stopping: ${JLIST}
	# multiple astart always non interactive
	export inter=0
	for jname in ${JLIST}; do
		env NOINTER=1 /usr/local/cbsd/misc/daemonize -E NOINTER=1 -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

# single jname
[ -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}'" )

		tmp_type=${nic_parent%%-*}              # cut all before '#'
		tmp_iface=${nic_parent##*-}             # cut all after '#'
		[ "${tmp_type}" = "ppt" ] && continue

		[ "${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}Stopping 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

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

# 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

# API compat: ~cbsd/jails-system/info*.*
/usr/local/cbsd/misc/daemonize /usr/local/cbsd/tools/save-jail-info jname=${jname}

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
