#! /bin/sh

# Usage: next_avail cloner-type-disk
#	eg: next_avail vnd
#			(or cgd or raid ...)

usage()
{
	printf >&2 'Usage: %s xxx ...\n' "${0##*/}"
	if [ -n "$1" ]
	then
		printf >&2 "$@"
	else
		printf >&2 '       where xxx is a disk-type driver name\n'
	fi
	exit 2
}

case "$#" in
0)	usage;;
esac
E=
for A
do
	case "${A}" in
	(*[0-9]) E="${E} ${A}";;
	esac
done
if [ -n "${E}" ]
then
	usage '       Do not include unit numbers:%s\n' "${E}"
fi

# Note that while this script works for any disk type device
# it is only useful for those that can be created on demand by
# some config command ... "next_avail wd" might reveal that wd3
# is the next, indicating that wd0 wd1 and wd2 are all in use,
# but there is nothing much that can be done about that, should
# a new wd arrive (hot plugged) the kernel will simply assign the
# id at that time, there is nothing another script (or a human)
# using this script can do to influence that.


# Any specific units of devices that are never to be made available
# via this script, should be listed here.   These are typically ones
# known in fstab, or some other config file, and so should not be
# simply usurped for casual use (there is no requirement that they
# actually be in use at any particular time).
RESERVED='cgd0 ccd0'

# The script (temporarily, just while executing) appends assigned
# units to the RESERVED list, so if request asks for 'raid raid' (etc)
# 2 different (currently free) raid unit numbers will be returned.

next_avail ()
{
	local dev="$1"
	local N=$(( ${#dev} + 1 ))
	local unit units

	sysctl -n kern.drivers	|
	    tr , '\012'		|
	    tr -d ']['		|	
	    grep " ${dev}\$" >/dev/null 2>&1 || {
		printf >&2 'No %s driver in booted kernel\n' "${dev}"
		printf \\n
		return 1
	}

	# Discover which units are currently known to the kernel
	# (ie: in use already).   To those we add any reserved
	# units that we are to assume are (or might be) in use,
	# whether they actually are or not (ie: never asign).
	units=$(
		{
			sysctl -n hw.disknames
			printf '%s\n' "${RESERVED}"
		}						|
			tr ' ' '\012'				|
			grep '^'"${dev}"'[0-9]'			|
			sort -u -n -k "1.$N"			)

	test -z "${units}" && {

		# Currently, ${dev} is not in hw.disknames, so
		# no units are in use.  That means that as long
		# as the entry for ${dev}0 is in /dev, that one
		# is free, and obviously is the first, nothing
		# more to do, we found it.

		test -e "/dev/${dev}0" ||
		test -e "/dev/${dev}0a" || {
			printf >&2 "No %s's available!\\n" "${dev}"
			printf \\n
			return 1
		}
		RESERVED="${RESERVED} ${dev}0"
		printf '%s\n' "${dev}0"
		return
	}

	# Now fine the lowest free unit number.

	# Note we just need to scan the list once.  units is sorted,
	# and contains those which are in use, 0 1 2 4 5 7
	# N runs 0 1 2 3 4 ...  as long as the two match, the unit
	# is in use, onto the next, when N and the unit are different,
	# then dev$N is not currently in use, which is all we need.
	N=0
	for unit in ${units}
	do
		if [ "${unit}" != "${dev}${N}" ]
		then
			break
		fi
		N=$(( N + 1 ))
	done

	# If the first one not in use we found has no /dev
	# entry, then we failed, no available ${dev} is free.
	test -e "/dev/${dev}${N}" ||
	test -e /dev/"${dev}${N}a" || {
		printf >&2 "All %s's in use\\n" "${dev}"
		printf \\n
		return 1
	}

	# Success!
	RESERVED="${RESERVED} ${dev}${N}"
	printf '%s\n' "${dev}${N}"
}


X=0
for A
do
	next_avail "${A}"
	: $(( X |= $? ))
done
exit $X