#!/usr/local/bin/cbsd
#v13.0.9
MYARG="name"
MYOPTARG="lease_time lock pass skip"
MYDESC="return next free available nic by mask"
CBSDMODULE="sys"
ADDHELP="
${H3_COLOR}Description${N0_COLOR}:

Return next free available nic by mask. To avoid collision/race this script
use file-based lease db.

${H3_COLOR}Options${N0_COLOR}:

${H3_COLOR}Options${N0_COLOR}:

 ${N2_COLOR}lease_time${N0_COLOR} - <num>, lease time in seconds, default: '6'.
 ${N2_COLOR}name${N0_COLOR}       - nic mask, e.g: 'epair', 'bridge'.

${H3_COLOR}Examples${N0_COLOR}:

 # cbsd get-next-nic name=epair
 # cbsd get-next-nic name=bridge

"

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

case "${platform}" in
	Linux)
		[ "${name}" = "bridge" ] && name="virtbr"
		;;
esac

LOCKFILE="${ftmpdir}/get_next_nic.lock"

# $1 - nicname (eg: bridge)
# if nicname=epair we search as epairXa
# show first available nic by type
# example:
#  ttt=$( get_next_nic bridge )
# return 1 when error
get_next_nic()
{
	local _i _j _epair _A _skiplist
	local _num= _name=
	local _lease_file="${tmpdir}/get_next_nic.lease"
	local _cur_time _lease_time_end
	local _skipme _lease_file_skip _newif
	local _nic_list _ret=

	[ -z ${1} ] && return 1
	_name="${1}"
	[ "${_name}" = "epair" ] && _epair="a"

	# we need exclusive lock and lease time to prevent race condition
	# when function called at same time simultaneously.
	# Use file as lock/lease db.

	# todo: internal lockf for function
	_lease_file_skip=$( rotate_lease_file -l ${_lease_file} )

	_nic_list=$( /usr/local/cbsd/misc/nics-list | ${XARGS_CMD} )

	# append skip list by local ifaces
	for _i in ${_nic_list}; do
		if [ -n "${_lease_file_skip}" ]; then
			_lease_file_skip="${_lease_file_skip} ${_i}"
		else
			_lease_file_skip="${_i}"
		fi
	done

	for _num in $( ${SEQ_CMD} 512 ); do

		_skipme=0

		# due to epair has extra postfix (a/b), store parent iface as _newif
		_newif="${_name}${_num}"

		if [ -n "${_epair}" ]; then
			_checkif="${_newif}${_epair}"
		else
			_checkif="${_newif}"
		fi

		if [ -n "${_lease_file_skip}" ]; then
			for _j in ${_lease_file_skip}; do
				if [ "${_newif}" = "${_j}" ]; then
					_skipme=1
					break
				fi
			done
		fi

		[ ${_skipme} -eq 1 ] && continue
		case "${platform}" in
			Linux)
				${IP_CMD} link show ${_checkif} >/dev/null 2>&1
				_ret=$?
				;;
			*)
				${IFCONFIG_CMD} ${_checkif} >/dev/null 2>&1
				_ret=$?
				;;
		esac

		if [ ${_ret} -eq 1 ]; then
			# extra check: try to create before return, fast hack/work-around for renamed interfaces
			# https://github.com/cbsd/cbsd/issues/725 ( todo: also check if if_epair kldload ? )
			case "${platform}" in
				Linux)
					case "${name}" in
						virtbr)
							_newif="virtbr${_num}"
							cbsdlogger NOTICE "${CBSD_APP}: create tmp get-next-nic bridge: brctl addbr ${_newif}"
							_res=$( ${BRCTL_CMD} addbr ${_newif} )
							_ret=$?
							;;
						tap)
							_newif="tap${_num}"
							cbsdlogger NOTICE "${CBSD_APP}: create tmp get-next-nic tap: ${IP_CMD} tuntap add mode tap ${_newif}"
							_res=$( ${IP_CMD} tuntap add mode tap ${_newif} )
							_ret=$?
							;;
						*)
							log_err 1 "${CBSD_APP}: unknown interface type to create: ${name}"
							;;
					esac
					;;
				*)
					_res=$( ${IFCONFIG_CMD} ${_newif} create 2>&1 )
					_ret=$?
					;;
			esac

			if [ ${_ret} -eq 0 ]; then
				# destroy before return (really need?)
				if [ "${_name}" = "epair" ]; then
					cbsdlogger NOTICE "${CBSD_APP}: destroy tmp get-next-nic epair: ${_newif}a"
					${IFCONFIG_CMD} destroy ${_newif}a > /dev/null 2>&1
				else
					cbsdlogger NOTICE "${CBSD_APP}: destroy tmp get-next-nic ${_name}: ${_newif}"
					case "${platform}" in
						Linux)
							${IP_CMD} link delete ${_newif} > /dev/null 2>&1
							;;
						*)
							${IFCONFIG_CMD} ${_newif} destroy > /dev/null 2>&1
							;;
					esac
				fi
			else
				cbsdlogger NOTICE "${CBSD_APP}: unable to create free(?) ${_name}: ${_newif}: ${_res}, skip"
				continue
			fi

			# no such/free interface
			_cur_time=$( ${DATE_CMD} +%s )
			_lease_time_end=$(( _cur_time + lease_time ))
			echo "${_newif}:${_lease_time_end}" >> ${_lease_file}
			sync
			echo "${_newif}" && return 0
		fi
	done

	return 1
}

if [ -n "${pass}" ]; then
	get_next_nic "${name}"
	exit $?
fi

# 120 (2m) - infinite lock protection. 
# we expect that this time should be enough to launch all environments (with VNET network stack or tap ifaces) that start at the same time. 
# otherwise it is recommended to use 'boot_delay'
cbsd_lock 120 ${LOCKFILE} /usr/local/bin/cbsd get-next-nic "$*" pass=1
ret=$?
# should never happen
exit ${ret}
