#!/usr/local/bin/cbsd
#v11.0.6
MYARG=""
MYOPTARG="reload"
MYDESC="Daemon for executing nexttask"
CBSDMODULE="taskd"
EXTHELP="wf_taskd"

. ${subrdir}/nc.subr

. ${cbsdinit}
. ${system}

[ -z "${reload}" ] && reload=0

# reset hour for fwupdate
last_hour_fwupdate=0

shmux()
{
	local _mylock SSH_ARGS _locallockfile _locallock _remotelockfile _remotelock

	_locallockfile="${ftmpdir}/shmux_${_ip}.lock"
	_remotelockfile="ftmp/from_${nodename}.lock"

	case "${platform}" in
		Linux)
			_locallock="${FLOCK_CMD} -w0 -x ${_locallockfile}"
			_remotelock="${FLOCK_CMD} -w0 -x ${_remotelockfile}"
			;;
		*)
			_locallock="${LOCKF_CMD} -s -t0 ${_locallockfile}"
			_remotelock="${LOCKF_CMD} -s -t0 ${_remotelockfile}"
			;;
	esac

	. ${nodes}

	if [ -f "${_locallockfile}" ]; then
		if check_locktime ${_locallockfile} >/dev/null 2>&1; then
			cbsdsqlrw nodes "UPDATE nodelist SET idle=datetime('now','localtime') WHERE ip='${_ip}'"
		fi
		return 0
	fi

	SSH_ARGS="-oControlPersist=30m -oBatchMode=yes -oStrictHostKeyChecking=no -oConnectTimeout=5 -q -oPort=${_port} -i ${_keyfile} -l ${cbsduser} ${_ip}"
	[ ! -f "${_locallockfile}" ] && env workdir="${workdir}" ${miscdir}/daemon -u ${cbsduser} ${_locallock} ${SSH_CMD} ${SSH_ARGS} "${_remotelock} /usr/local/cbsd/misc/cbsd_dot --file=var/db/local.sqlite" > ${tmpdir}/inv.${_ip}.updated 2>${tmpdir}/inv.${_ip}.updated.err
}

update_jaillock()
{
	local jname

	for jname in $( cbsdsqlro local SELECT jname FROM jails WHERE status=1 ); do
		jaillock="${jailsysdir}/${jname}/locked"
		case "${platform}" in
			Linux)
				/usr/local/cbsd/misc/daemonize -u ${cbsduser} ${FLOCK_CMD} -w0 -x ${ftmpdir}/update_jaillock.lock ${TOUCH_CMD} ${jaillock} 2>/dev/null
				;;
			*)
				/usr/local/cbsd/misc/daemonize -u ${cbsduser} ${LOCKF_CMD} -s -t0 ${ftmpdir}/update_jaillock.lock ${TOUCH_CMD} ${jaillock} 2>/dev/null
				;;
		esac
	done
}

update_fw_counters()
{
	local _curhour=$( ${DATE_CMD} "+%H" )

	[ "${platform}" = "Linux" ] && return 0

	[ "${_curhour}" = "${last_hour_fwupdate}" ] && return 0
	last_hour_fwupdate="${_curhour}"
	/usr/local/cbsd/misc/daemonize ${LOCKF_CMD} -s -t0 ${ftmpdir}/fwcounters.lock /usr/local/bin/cbsd fwcounters jname=alljails
}

# if $1 = "force" skip for cbsd_dot check
update_inventory()
{
	[ -z "${_ip}" ] && return 0
	if [ "${1}" != "force" ]; then
		[ ! -s "${tmpdir}/inv.${_ip}.updated" ] && return 0
		cbsdlogger NOTICE ${CBSD_APP}: update remote inventory for ${_ip}: new changes
	fi
	${TRUNCATE_CMD} -s0 ${tmpdir}/inv.${_ip}.updated
	task mode=new exclusive=1 autoflush=2 owner=cbsddsys jname=#retrinv_${nodename} /usr/local/bin/cbsd retrinv node=${_nodename} >/dev/null 2>&1
	/usr/local/cbsd/misc/daemonize /usr/local/cbsd/tools/nexttask
}

configure_default_vs_bridge()
{
	local _cbsd_vs=0 _ret=0

	[ -z "${configure_default_cbsd_vs_name}" ] && configure_default_cbsd_vs_name="cbsdbr0"

	case "${platform}" in
		Linux)
			${IP_CMD} link show ${configure_default_cbsd_vs_name} >/dev/null 2>&1
			_ret=$?
			;;
		*)
			${IFCONFIG_CMD} ${configure_default_cbsd_vs_name} >/dev/null 2>&1
			_ret=$?
			;;
	esac

	[ ${_ret} -eq 0 ] && return 0

	_cbsd_vs=$( /usr/local/cbsd/tools/get-next-nic name=bridge )
	_ret=$?
	[ ${_ret} -ne 0 ] && err 1 "${CBSD_APP} configure_default_vs, ret: ${_ret}: ${_cbsd_vs}"
	[ -z "${_cbsd_vs}" ] && err 1 "${CBSD_APP} configure_default_vs, ret: ${_ret}: empty _cbsd_vs"

	eval $( ${miscdir}/sipcalc ${configure_default_cbsd_vs_cidr4} )
	if [ -z "${_usable_range_start}" ]; then
		cbsdlogger NOTICE ${CBSD_APP}: configure_default_vs warning: unable to find _usable_range_start via sipcalc for: ${configure_default_cbsd_vs_cidr4}
		return 1
	fi

	case "${platform}" in
		Linux)
			${BRCTL_CMD} addbr ${configure_default_cbsd_vs_name}
			${IP_CMD} link set ${configure_default_cbsd_vs_name} up
			${IP_CMD} addr add ${_usable_range_start}/24 dev ${configure_default_cbsd_vs_name}
			${IP_CMD} link show ${configure_default_cbsd_vs_name} >/dev/null 2>&1
			_ret=$?
			;;
		*)
			${KLDSTAT_CMD} -qn if_bridge;
			if [ ${_ret} -ne 0 ]; then
				${ECHO} "${N1_COLOR}${CBSD_APP}: loading if_bridge.ko${N2_COLOR}"
				${KLDLOAD_CMD} if_bridge
			fi
			${IFCONFIG_CMD} ${_cbsd_vs} create
			${IFCONFIG_CMD} ${_cbsd_vs} name ${configure_default_cbsd_vs_name}
			${IFCONFIG_CMD} ${configure_default_cbsd_vs_name} ${_usable_range_start}/24
			${IFCONFIG_CMD} ${configure_default_cbsd_vs_name} up
			${IFCONFIG_CMD} ${configure_default_cbsd_vs_name} >/dev/null 2>&1
			_ret=$?
			;;
	esac

	if [ ${_ret} -ne 0 ]; then
		echo "${CBSD_APP} error: unable to create default bridge: ${configure_default_cbsd_vs_name}"
		return 1
	fi

	return 0
}

configure_default_vs()
{
	local _ret=

	if [ "${configure_default_cbsd_vs_cidr4}" = "auto" ]; then
		configure_default_cbsd_vs_cidr4=$( find_free_cidr4 )
		_ret=$?
		if [ ${_ret} -ne 0 ]; then
			cbsdlogger NOTICE ${CBSD_APP}: configure_default_vs warning: unable to find available free cidr4, please set 'configure_default_cbsd_vs_cidr4' manually via global.conf
			echo "warning: ${CBSD_APP}: configure_default_vs warning: unable to find available free cidr4, please set 'configure_default_cbsd_vs_cidr4' manually via global.conf"
			return ${_ret}
		fi

		cbsdlogger NOTICE ${CBSD_APP}: configure_default_vs: configure_default_cbsd_vs_cidr4 auto: use ${configure_default_cbsd_vs_cidr4}, ${workdir}/etc/global.conf updated
		echo "${CBSD_APP}: configure_default_vs: configure_default_cbsd_vs_cidr4 auto: use ${configure_default_cbsd_vs_cidr4} ( ${workdir}/etc/global.conf updated )"
		/usr/local/cbsd/misc/cbsdsysrc -qf ${workdir}/etc/global.conf configure_default_cbsd_vs_cidr4="${configure_default_cbsd_vs_cidr4}" > /dev/null 2>&1
		export configure_default_cbsd_vs_cidr4="${configure_default_cbsd_vs_cidr4}"
	fi

	case "${configure_default_cbsd_vs_engine}" in
		bridge)
			configure_default_vs_${configure_default_cbsd_vs_engine}
			;;
		*)
			cbsdlogger NOTICE ${CBSD_APP}: configure_default_vs warning: unsupported configure_default_cbsd_vs_engine engine: ${configure_default_cbsd_vs_engine}
			echo "warning: ${CBSD_APP}: configure_default_vs: unsupported configure_default_cbsd_vs_engine engine: ${configure_default_cbsd_vs_engine}"
			;;
	esac
}

if [ -z "${workdir}" ]; then
	echo "cbsdd: empty workdir"
	exit 1
fi

readconf global.conf

[ -z "${default_vs}" ] && default_vs="0"

# default virtual switch
if [ "${default_vs}" != "0" ]; then
	configure_default_vs
fi

readconf task.conf

[ ! -f "${dbdir}/cbsdtaskd.sqlite" ] && err 1 "No such ${dbdir}/cbsdtaskd.sqlite"
[ -z "${max_simul_jobs}" ] && max_simul_jobs="30"
[ -z "${loop_timeout}" ] && loop_timeout="30"

loop_per_retrinv=10	# one retrinv per 5 loop cycle's ( def: 10*30sec = once per <= 5 min )
retrinv_loop=0

# first of all, checks for orphaned tasks in status 1 (running) and back to status 0,
# due to next iteration take this job again

#store pid
echo $$ > ${workdir}/var/run/cbsdd.pid
trap "${RM_CMD} -f ${workdir}/var/run/cbsdd.pid" HUP INT QUIT ABRT KILL ALRM TERM BUS EXIT

cbsdsqlrw cbsdtaskd "UPDATE taskd SET status='0' WHERE status='1'" 2>/dev/null

# remove stale lock and trash files
${FIND_CMD} -E ${ftmpdir} -mindepth 1 -maxdepth 1 \( -name shmux_\*.lock -or -name jstart\.\* -or -name jstop\.\* -or -name \*\.jconf \) -and -mtime +30m -delete > /dev/null 2>&1
${FIND_CMD} ${tmpdir} -mindepth 1 -maxdepth 1 -name inv.\*.updated -delete > /dev/null 2>&1
${FIND_CMD} ${tmpdir} -mindepth 1 -maxdepth 1 -name \*.desc -delete > /dev/null 2>&1

# retrinv
if [ ${sqlreplica} -eq 1 ]; then
	case "${platform}" in
		Linux)
			/usr/local/cbsd/misc/daemonize ${FLOCK_CMD} -w0 -x ${ftmpdir}/retrinv.lock /usr/local/bin/cbsd retrinv tryoffline=1
			;;
		*)
			/usr/local/cbsd/misc/daemonize ${LOCKF_CMD} -s -t0 ${ftmpdir}/retrinv.lock /usr/local/bin/cbsd retrinv tryoffline=1
			;;
	esac
fi

cbsdlogger NOTICE ${CBSD_APP}: cbsdd: max_simul_jobs:${max_simul_jobs},loop_timeout:${loop_timeout}

tik=0

while [ 1 ]; do

	tik=$(( tik + 1 ))

	# ~5min
	if [ ${tik} -gt 300 ]; then
		update_jaillock
		[ ${ipfw_enable} -eq 1 ] && update_fw_counters

		unset ip port keyfile _ip _port _keyfile queue

		# make sshmux control master
		if [ ${sqlreplica} -eq 1 ]; then
			sqldelimer=" "
			cbsdsqlro nodes SELECT nodename,ip,port,keyfile FROM nodelist | while read _nodename _ip _port _keyfile; do
				[ -n "${_ip}" ] && shmux
				if [ ${retrinv_loop} -gt ${loop_per_retrinv} ]; then
					cbsdlogger NOTICE ${CBSD_APP}: update remote inventory for ${_ip} via loop_per_retrinv [${loop_per_retrinv}]
					update_inventory force
				else
					update_inventory
				fi
			done
			unset sqldelimer
		fi
		tik=0
	fi

	queue=$( cbsdsqlro cbsdtaskd 'SELECT COUNT(id) FROM taskd WHERE status="0"' 2>/dev/null )

	if [ -z "${queue}" -o "${queue}" = "0" ]; then
		cbsd_fwatch --file=${dbdir}/cbsdtaskd.sqlite --timeout=${loop_timeout} >/dev/null 2>&1
		tik=$(( tik + loop_timeout ))
	fi

	queue=$( cbsdsqlro cbsdtaskd 'SELECT COUNT(id) FROM taskd WHERE status="0"' 2>/dev/null )

	if [ -z "${queue}" -o "${queue}" = "0" ]; then
		sleep 1
		continue
	fi

	queue_active=$( cbsdsqlro cbsdtaskd 'SELECT COUNT(id) FROM taskd WHERE status="1"' 2>/dev/null )

	if [ ${queue_active} -gt ${max_simul_jobs} ]; then
		cbsdlogger NOTICE ${CBSD_APP}: active: ${queue_active}/${max_simul_jobs}
		# too many in progress
		sleep 1
		continue
	fi

	if [ ${queue} -gt ${max_simul_jobs} ]; then
		max=${max_simul_jobs}
	else
		max=${queue}
	fi

	if [ -z "${max}" ]; then
		#echo "cbsdd: wrong max variable"
		exit 1
	fi

	for i in $( ${SEQ_CMD} 1 ${max} ); do
		cbsdlogger NOTICE ${CBSD_APP}: runtask ${i}/${max} [$max_simul_jobs max]
		${ENV_CMD} workdir=${workdir} /usr/local/cbsd/misc/daemonize -E workdir=${workdir} /usr/local/cbsd/tools/nexttask
		# wait for pid of daemon here!!!
		sleep 1
	done
done

exit 0
