#! /bin/sh
#
# /usr/sbin/dahdi_span_assignments:
#
# this script can be used both from udev and
# from the command line to assign/unassign and list
# current span assignments.
#
# It uses a configuration file: $DAHDICONFDIR/assigned-spans.conf
# (default DAHDICONFDIR=/etc/dahdi)
#
# The first argument is an action:
#   "auto"       - trigger driver auto_assign attribute for given devices
#		   (no configuration file is used)
#   "add"        - assign (spans which are not already assigned), according
#                  to /etc/dahdi/assigned-spans.conf configuration file
#   "remove"     - unassign spans which are not already unassigned
#   "list"       - human-readable list of all spans (with/without assignments)
#   "dumpconfig" - dump current assignments in a /etc/dahdi/assigned-spans.conf
#                  compatible format
#
# Without further arguments, it operates on all existing spans
# With one or more sysfs dahdi_devices it is limited to those.
#
# We may use alternative "keys" for device matching:
# * Available keys:
#   - "hwid"       - Hardware id attribute from sysfs
#   - "@location"  - Location attribute from sysfs (embeded inside '<>')
#   - "/devpath"   - The sysfs absolute devpath
#
# * During "dumpconfig", for each device we take the first available key:
#   - The preference is: "hwid" or else "@location" or else "/devpath"
#   - This can be overriden via the SPAN_ASSIGNMENTS_KEY environment variable
#     or the '{-k|--key} key' command line option.
#
# * During "add":
#   - Any key match is valid (hwid/location/devpath)
#   - Shell globs (wildcards: '*', '?', '[...]') may be optionally used.
#
# Command line options:
#  - The '-h|--help' show a usage message.
#  - The '-n|--dry-run' affects the "add" and "remove" operations.
#  - The '-v|--verbose' currently shows device matches during "add" operation.
#  - The '-k <key>|--key <key>' overrides the SPAN_ASSIGNMENTS_KEY environment
#    variable.
#
# Examples:
#    dahdi_span_assignments list
#    dahdi_span_assignments add	# all unassigned devices
#    dahdi_span_assignments add       /sys/bus/dahdi_devices/devices/astribanks:xbus-00
#    dahdi_span_assignments remove	# all assigned devices
#    dahdi_span_assignments -k location dumpconfig
#

devbase='/sys/bus/dahdi_devices/devices'
DAHDICONFDIR="${DAHDICONFDIR:-/etc/dahdi}"
DAHDISASSIGNEDSPANSCONF="${DAHDIASSIGNEDSPANSCONF:-"${DAHDICONFDIR}/assigned-spans.conf"}"
SPAN_ASSIGNMENTS_KEY=${SPAN_ASSIGNMENTS_KEY:-hwid}
dry_run=
verbose=

usage() {
	echo >&2 "Usage: $0 [options] action [devpath ...]"
	echo >&2 "       action:"
	echo >&2 "         auto       - trigger driver auto_assign attribute for given devices"
	echo >&2 "         add        - assign spans, according to /etc/dahdi/assigned-spans.conf"
	echo >&2 "         remove     - unassign spans"
	echo >&2 "         list       - human-readable list of all spans"
	echo >&2 "         matched    - found spans matched in configuration"
	echo >&2 "         unmatched  - found spans not matched in configuration"
	echo >&2 "         dumpconfig - dump current state as new configuration"
	echo >&2 ""
	echo >&2 "       options:"
	echo >&2 "         -h|--help      - Show this help"
	echo >&2 "         -n|--dry-run   - For 'add/remove' actions"
	echo >&2 "         -v|--versbose  - Show matches during 'add' action"
	echo >&2 "         -k|--key <k>   - Override prefered key during dumpconfig action"
	exit 1
}

# Parse command line options
TEMP=`getopt -o hnvk: --long help,dry-run,verbose,key: -n "$0" -- "$@"`
if [ $? != 0 ]; then
	echo >&2 "Bad options"
	usage
fi

# Note the quotes around `$TEMP': they are essential!
eval set -- "$TEMP"

while true ; do
	case "$1" in
	-h|--help)
		usage
		;;
	-n|--dry-run)
		dry_run='true'
		shift
		;;
	-v|--verbose)
		verbose='true'
		shift
		;;
	-k|--key)
		SPAN_ASSIGNMENTS_KEY="$2"
		shift
		shift
		;;
	--)
		shift
		break
		;;
	*)
		echo >&2 "Internal error!"
		exit 1
		;;
	esac
done

if [ "$#" -eq 0 ]; then
	echo >&2 "Missing action argument"
	usage
fi
action="$1"
shift

# Validate SPAN_ASSIGNMENTS_KEY
case "$SPAN_ASSIGNMENTS_KEY" in
hwid|location|devpath)
	;;
*)
	echo >&2 "Bad SPAN_ASSIGNMENTS_KEY='$SPAN_ASSIGNMENTS_KEY' (should be: hwid|location|devpath)"
	usage
	;;
esac

if [ ! -d "$devbase" ]; then
	echo >&2 "$0: Missing '$devbase' (DAHDI driver unloaded?)"
	exit 1
fi

# Use given devices or otherwise, all existing devices
if [ "$#" -gt 0 ]; then
	DEVICES="$@"
else
	DEVICES=`ls -d $devbase/* 2>/dev/null`
fi

# Beware of special characters in attributes
attr_clean() {
	cat "$1" 2>/dev/null | tr -d '\n' | tr '!' '/' | tr -c 'a-zA-Z0-9/:.-' '_'
}

show_devices() {

	for device in $DEVICES
	do
		devpath=`cd "$device" && pwd -P`
		location='@'`attr_clean "$device/location"`
		hardware_id=`attr_clean "$device/hardware_id"`
		for local_spanno in `cut -d: -f1 "$device/spantype"`
		do
			span=`grep 2>/dev/null -Hw "$local_spanno" "$device/span-"*"/local_spanno" | \
				sed -e 's,/local_spanno:.*,,' -e 's,.*/,,'`
			if [ "$span" != '' ]; then
				spanno=`echo $span | sed 's/^.*-//'`
				name=`cat 2>/dev/null "$device/$span/name"`
				basechan=`cat 2>/dev/null "$device/$span/basechan"`
			else
				spanno='-'
				basechan='-'
			fi
			printf "%-8s %-14s %s %s\n" "$local_spanno:$spanno:$basechan" "[$hardware_id]" "$location" "$devpath"
		done | sort -n
	done
}

dump_config() {
	echo '#'
	echo "# Autogenerated by $0 on `date`"
	echo "# Map devices + local spans to span + base channel number"
	echo ''
	for device in $DEVICES
	do
		devpath=`cd "$device" && pwd -P`
		location=`attr_clean "$device/location"`
		hardware_id=`attr_clean "$device/hardware_id"`
		if [ "$SPAN_ASSIGNMENTS_KEY" = 'hwid' -a "$hardware_id" != '' ]; then
			id="$hardware_id"
		elif [ "$SPAN_ASSIGNMENTS_KEY" = 'location' -a "$location" != '' ]; then
			id="@$location"
		else
			id="$devpath"
		fi
		echo "# Device: [$hardware_id] @$location $devpath"
		for local_spanno in `cut -d: -f1 "$device/spantype"`
		do
			span=`grep 2>/dev/null -Hw "$local_spanno" "$device/span-"*"/local_spanno" | \
				sed -e 's,/local_spanno:.*,,' -e 's,.*/,,'`
			if [ "$span" != '' ]; then
				spanno=`echo $span | sed 's/^.*-//'`
				name=`cat 2>/dev/null "$device/$span/name"`
				basechan=`cat 2>/dev/null "$device/$span/basechan"`
				printf "%-30s %s\n" "$id" "$local_spanno:$spanno:$basechan"
			else
				echo "#   Skipped unassigned local span $local_spanno"
			fi
		done | sort
		echo ''
	done
}

unassign_all_spans() {
	for device in $DEVICES
	do
		find "$device" -follow -maxdepth 1 -name 'span-*' -type d | \
			sort | while read spandir; do
			local_spanno=`cat "$spandir/local_spanno"`
			if [ "$dry_run" = true ]; then
				echo >&2 "(dry-run) unassign $device $local_spanno"
				continue
			fi
			echo >&2 "unassign $device $local_spanno"
			if ! echo "$local_spanno" > "$device/unassign_span"; then
				echo >&2 "$0: failed unassigning '$local_spanno' in '$device'"
			fi
		done
	done
}

# Allow comments and empty lines in config file
filter_conf() {
	sed -e 's/#.*//' -e '/^[ \t]*$/d' "$DAHDISASSIGNEDSPANSCONF"
}

assign_device_spans() {
	device="$1"
	for s in $spanspecs
	do
		local_spanno=`echo "$s" | cut -d: -f1`
		spanno=`echo "$s" | cut -d: -f2`
		span="$device/span-$spanno"
		if [ "$dry_run" = true ]; then
			echo "(dry-run) assign $device: $s"
			continue
		fi
		if [ -d "$span" ]; then
			span_local_spanno=`cat "$span/local_spanno"`
			if [ "$span_local_spanno" != "$local_spanno" ]; then
				echo >&2 "WARNING: $span_local_spanno != $local_spanno"
			fi
			echo >&2 "$device [$local_spanno] already assigned to span $spanno. Skipping..."
			continue
		fi
		echo >&2 "assign $device: $s"
		if ! echo "$s" > "$device/assign_span"; then
			echo >&2 "$0: failed assigning '$s' to '$device'"
		fi
	done
}

match_device() {
	device="$1"
	devpath=`cd "$device" && pwd -P`
	location='@'`attr_clean "$device/location"`
	hardware_id=`attr_clean "$device/hardware_id"`
	filter_conf | while read id spanspecs
	do
		# We use case to enable shell-style globbing in configuration
		case "$hardware_id" in
		$id)
			[ "$verbose" = true ] && echo >&2 "match by hwid ($id ~ $hardware_id): $spanspecs"
			assign_device_spans "$device"
			;;
		esac
		# We use case to enable shell-style globbing in configuration
		case "$location" in
		$id)
			[ "$verbose" = true ] && echo >&2 "match by location ($id ~ $location): $spanspecs"
			assign_device_spans "$device"
			;;
		esac
		# We use case to enable shell-style globbing in configuration
		case "$devpath" in
		$id)
			[ "$verbose" = true ] && echo >&2 "match by devpath ($id ~ $devpath): $spanspecs"
			assign_device_spans "$device"
			;;
		esac
	done
}

assign_devices() {
	if [ ! -f "$DAHDISASSIGNEDSPANSCONF" ]; then
		echo >&2 "$0: Missing '$DAHDISASSIGNEDSPANSCONF'"
		exit 1
	fi
	echo >&2 "using '$DAHDISASSIGNEDSPANSCONF'"
	for device in $DEVICES
	do
		match_device "$device"
	done
}

auto_assign_devices() {
	for device in $DEVICES
	do
		echo >&2 "auto-assign $device"
		if [ "$dry_run" != true ]; then
			echo 1 > "$device/auto_assign"
		fi
	done
}

dev_match_conf() {
	local devpath="$1"
	local location="$2"
	local hardware_id="$3"
	local local_spanno="$4"
	filter_conf | while read id spanspecs
	do
		spanno=`echo "$spanspecs" | cut -d: -f1`
		match_dev=no
		# We use case to enable shell-style globbing in configuration
		case "$hardware_id" in
		$id)
			match_dev=yes
			;;
		esac
		# We use case to enable shell-style globbing in configuration
		case "$location" in
		$id)
			match_dev=yes
			;;
		esac
		# We use case to enable shell-style globbing in configuration
		case "$devpath" in
		$id)
			match_dev=yes
			;;
		esac
		if [ "$match_dev" = 'yes' -a "$local_spanno" = "$spanno" ]; then
			#printf "%-8s (%s) %-14s %s %s\n" "$local_spanno" "$spanno" "[$hardware_id]" "$location" "$devpath"
			echo "[$hardware_id]:$local_spanno"
		fi
	done
}

list_devices() {
	wanted="$1"
	if [ ! -f "$DAHDISASSIGNEDSPANSCONF" ]; then
		echo >&2 "$0: Missing '$DAHDISASSIGNEDSPANSCONF'"
		exit 1
	fi
	echo >&2 "using '$DAHDISASSIGNEDSPANSCONF'"
	for device in $DEVICES
	do
		devpath=`cd "$device" && pwd -P`
		location='@'`attr_clean "$device/location"`
		hardware_id=`attr_clean "$device/hardware_id"`
		for local_spanno in `cut -d: -f1 "$device/spantype"`
		do
			found=`dev_match_conf "$devpath" "$location" "$hardware_id" "$local_spanno"`
			if [ "$wanted" = "unmatched" ]; then
				[ -z "$found" ] && echo "[$hardware_id]:$local_spanno"
			else
				[ -z "$found" ] || echo "[$hardware_id]:$local_spanno"
			fi
		done
	done
}

case "$action" in
auto)
	auto_assign_devices
	;;
add)
	assign_devices
	;;
remove)
	unassign_all_spans
	;;
list)
	show_devices
	;;
dumpconfig)
	dump_config
	;;
matched)
	list_devices "matched"
	;;
unmatched)
	list_devices "unmatched"
	;;
*)
	echo >&2 "Bad action='$action'"
	usage
	;;
esac