#!/usr/local/bin/cbsd
#v12.2.1
MYARG=""
MYOPTARG="default_jailname skip lock pass lease_time"
MYDESC="Suggest first free available environment name (jname)"
ADDHELP="
${H3_COLOR}Description${N0_COLOR}:

 In order to reduce interaction with users and facilitate setup, 'freejname' 
 script  finds and suggests the first free environment name by mask
 (to avoid collision).  This is used for example in '[bjx]construct-tui scripts.

 If you want to use custom script instead of the internal/system helper, 
 you can override the script through the 'freejname_script' via 
 the corresponding config file (e.g: jail-freebsd-default.conf or
 bhyve-freebsd-default.conf or per profiles ). Integration with phpIPAM 
 is a good example: ${cbsddocsrc}/wf_ipam_ssi.html

${H3_COLOR}Options${N0_COLOR}:

 ${N2_COLOR}default_jailname${N0_COLOR} - prefix for name, e.g: vnet
 ${N2_COLOR}skip${N0_COLOR}             - (optional) skip for \"X B C D\" values
 ${N2_COLOR}lease_time${N0_COLOR}       - lock/lease for X seconds,default is: 30

${H3_COLOR}Examples${N0_COLOR}:

 # cbsd freejname default_jailname=badaboom

"
CBSDMODULE="bhyve,jail"

. ${subrdir}/nc.subr
lock=1
pass=
lease_time=30
. ${cbsdinit}

[ -z "${default_jailname}" ] && default_jailname="jail"

LOCKFILE="${ftmpdir}/get-freejname.lock"
LEASE_FILE="${tmpdir}/get-freejname.lease"
# list of locked/skip jname
LOCKFILE_SKIPLIST=

#find for first jnameX where X is number++
freejname()
{
	local _num _newjname _skipme
	local _nodes _i _test _exist _unregister _j
	local _cur_time _lease_time_end

	# also find by remote nodes databases, but local is first
	_nodes=$( cbsdsqlro nodes SELECT nodename FROM nodelist 2>/dev/null )
	_nodes="local ${_nodes}"

	# check unregister environment too
	_unregister=
	for _j in $( ${LS_CMD} ${jailrcconfdir} ); do
		[ "${_j}" = "dir.id" ] && continue
		jname=
		[ ! -r ${jailrcconfdir}/${_j} ] && continue
		. ${jailrcconfdir}/${_j}
		[ -z "${jname}" ] && continue
		_unregister="${_unregister} ${jname}"
	done

	for _num in $( ${SEQ_CMD} 10000 ); do
		_newjname="${default_jailname}${_num}"
		_exist=0

		# in skip list?
		_skipme=0
		for i in ${skip}; do
			if [ ${i} = "${_newjname}" ]; then
				_skipme=1
				break
			fi
		done

		[ ${_skipme} -eq 1 ] && continue

		for _i in ${_unregister}; do
			[ "${_newjname}" = "${_i}" ] && _exist=1 && break
		done

		for _i in ${_nodes}; do
			_test=
			_test=$( cbsdsqlro ${_i} SELECT jname FROM jails WHERE jname=\"${_newjname}\" )
			[ -n "${_test}" ] && _exist=1 && break
		done

		case "${_exist}" in
			0)
				cbsdlogger NOTICE ${CBSD_APP}: found new name: ${_newjname}
				break
				;;
			1)
				cbsdlogger NOTICE ${CBSD_APP}: name already used: ${_newjname}
				continue
				;;
		esac
	done

	_cur_time=$( ${DATE_CMD} +%s )
	_lease_time_end=$(( _cur_time + lease_time ))
	echo "${_newjname}:${_lease_time_end}" >> ${LEASE_FILE}

	echo ${_newjname}
	return 0
}


### MAIN
# we need the atomicity of the operation to exclude
# the simultaneous selection of the same free port
# use file as lock and temp database in
# <port>:<end_lease_time>
# <jname>:<end_lease_time>
if [ -z "${pass}" ]; then
	if [ "${lock}" = "1" ]; then
		# rebuild arg list ( + add pass )
		# Pass '"' as \" in cmd
		INIT_IFS="${IFS}"
		IFS="~"
		cmd="$@"
		IFS="${INIT_IFS}"
		while [ -n "${1}" ]; do
			IFS="~"
			strpos --str="${1}" --search="="
			_pos=$?
			if [ ${_pos} -eq 0 ]; then
				# not params=value form
				#printf "${1} "         # (printf handles -args (with dashes)
				#echo -n "${1} "
				shift
				continue
			fi
			_arg_len=$( strlen ${1} )
			_pref=$(( _arg_len - _pos ))
			ARG=$( substr --pos=0 --len=${_pos} --str="${1}" )
			VAL=$( substr --pos=$(( ${_pos} +2 )) --len=${_pref} --str="${1}" )
			if [ -z "${ARG}" -o -z "${VAL}" ]; then
				shift
				continue
			fi
			#printf "${ARG}='${VAL}' "
			shift
		done

		cbsd_lock 60 ${LOCKFILE} /usr/local/bin/cbsd freejname ${cmd} pass=1
		ret=$?
		# should never happen
		exit ${ret}
	fi
fi

# prune/purge old records
if [ -r ${LEASE_FILE} ]; then
	${TRUNCATE_CMD} -s0 ${LEASE_FILE}.swap
	cur_time=$( ${DATE_CMD} +%s )
	eval $( ${CAT_CMD} ${LEASE_FILE} | while read items; do
		p1=${items%%:*}
		p2=${items##*:}
		[ -z "${p1}" -o -z "${p2}" ] && continue
		if [ ${p2} -gt ${cur_time} ]; then
			# still valid
			echo "${items}" >> ${LEASE_FILE}.swap
			if [ -z "${LOCKFILE_SKIPLIST}" ]; then
				LOCKFILE_SKIPLIST="${p1}"
			else
				LOCKFILE_SKIPLIST="${LOCKFILE_SKIPLIST} ${p1}"
			fi
		fi
		echo "LOCKFILE_SKIPLIST=\"${LOCKFILE_SKIPLIST}\""
	done )
	${MV_CMD} ${LEASE_FILE}.swap ${LEASE_FILE}
fi

if [ -n "${LOCKFILE_SKIPLIST}" ]; then
	# append skip list
	if [ -z "${skip}" ]; then
		skip="${LOCKFILE_SKIPLIST}"
	else
		skip="${skip} ${LOCKFILE_SKIPLIST}"
	fi
fi

freejname

exit 0
