#!/bin/sh
# shellcheck disable=SC2039,SC1091
#  https://www.shellcheck.net/wiki/SC2039 -- In POSIX sh, SC2039: In POSIX sh, string replacement is undefined.
#  https://www.shellcheck.net/wiki/SC2039 -- In POSIX sh, string indexing is undefined.
#  https://www.shellcheck.net/wiki/SC1091 -- Not following: /lib/functions.sh:...
# Copyright (C) 2018 Luiz Angelo Daros de Luca <luizluca@gmail.com>
#
# Pools switchdev for port changes
#

[ -n "$NICED" ] && NICED=1 exec nice -n 19 "$0" "$@"

. /usr/share/libubox/jshn.sh
. /lib/functions.sh

cpu_ports=""

json_init
json_load "$(cat /etc/board.json)"

switches=
json_get_keys switches switch
json_select switch
for switch in $switches; do
	echo Loading $switch >&2
	json_select "$switch"
	if json_is_a ports array; then
		ports=
		json_get_keys ports ports
		json_select ports

		for port in $ports; do
			echo Checking port "$port" in "$switch" >&2
			json_select "$port"
			num=
			json_get_vars num device
			if [ -n "$device" ]; then
				echo "Port ${switch}_$num is CPU port as $device" >&2
				cpu_ports="$cpu_ports ${switch}_$num=$device"
			fi
			json_select ..
		done
		json_select ..
	fi
	json_select ..
done

each_switch_vlan() {
	switch=
	vlan=
	ports=
	config_get switch "$1" device
	config_get vlan "$1" vlan
	config_get ports "$1" ports

	[ -n "$vlan" ] || { echo "No vlan for '$1'" >&2; return 1; }
	[ -n "$switch" ] || { echo "No device for '$1'" >&2; return 1; }

	vlan_ifnames=""
	vlan_non_cpu_ports=""
	echo Checking vlan "$vlan" in $switch >&2
	for port in $ports; do
		case $port in
		*t) tagged=1; port=${port:0:-1} ;;
		*)  tagged= ;;
		esac
		echo "Checking port $port in $switch used by vlan $vlan" >&2

		cpu_port_ifname=""
		for cpu_port in $cpu_ports; do
			device=${cpu_port#*=}
			cpu_switchport=${cpu_port%=*}
			[ "${cpu_switchport}" = "${switch}_${port}" ] || continue
			echo "Port $port in $switch used by $vlan is a CPU port at $device" >&2

			for device_tagged in $device_tagged; do
				[ "${tagged}" = 1 ] && not_tagged=0 || not_tagged=1
				if [ "$device_tagged" = "$device=${not_tagged}" ]; then
					echo "Cannot control CPU port ${cpu_switchport} when it is used both as tagged and not tagged"
					exit 1
				fi
			done
			device_tagged="${device_tagged} ${device}=${tagged:-0}"
			cpu_port_ifname=${device}${tagged+.$vlan}
		done

		if [ -n "$cpu_port_ifname" ]; then
			vlan_ifnames="$vlan_ifnames $cpu_port_ifname"
			continue
		fi
		vlan_non_cpu_ports="$vlan_non_cpu_ports ${port}"
	done
	vlan_non_cpu_switch_ports="${vlan_non_cpu_ports// /:${switch}_}"
	vlan_non_cpu_switch_ports="${vlan_non_cpu_switch_ports:1}"
	vlan_non_cpu_ports="${vlan_non_cpu_ports:1}"
	for vlan_ifname in $vlan_ifnames; do
		devices2ports="$devices2ports $vlan_ifname=${vlan_non_cpu_switch_ports}"
		echo "Monitoring $switch (ports ${vlan_non_cpu_ports// /,}) for $vlan_ifname"
	done
}

device_tagged=""
devices2ports=""
config_load network
config_foreach each_switch_vlan switch_vlan

cleanup() {
	for device2ports in $devices2ports; do
		device=${device2ports%=*}
		ip link show dev ${device}_down >/dev/null 2>&1 && {
			echo "Bringing up $device on exit..."
			ip link set dev ${device}_down name ${device}
			ip link set dev ${device} up >/dev/null 2>&1;
		}
	done
	echo "Stopped poller"
	exit
}
trap cleanup INT TERM
echo "Starting poller"
while true; do (
for device2ports in $devices2ports; do
		device=${device2ports%=*}
		switch_ports=${device2ports#*=}
		switch_ports=${switch_ports//:/ }
		should_be_up=false
		for switch_port in $switch_ports; do
			state=$(eval echo \$${switch_port})
			if [ -z "${state}" ]; then
				switch=${switch_port%_*}
				port=${switch_port#*_}
				echo "Polling $switch_port..." >&2
				swconfig dev $switch port $port get link | grep -q 'link:up' &&
					state=up || state=down
				eval "$switch_port=$state"
				echo "State of $switch_port, used by $device, is $state" >&2
			else
				echo "State of $switch_port, used by $device, is $state (cached)" >&2
			fi
			[ $state = up ] && should_be_up=true
		done

		if $should_be_up; then
			ip link show dev ${device}_down >/dev/null 2>&1 && {
				echo "Bringing up $device..."
				ip link set dev ${device}_down name ${device}
				ip link set dev ${device} up >/dev/null 2>&1;
			}
		else
			ip link show dev ${device} >/dev/null 2>&1 && {
				echo "Bringing down $device..."
				ip link set dev ${device} down >/dev/null 2>&1;
				ip link set dev ${device} name ${device}_down
			}

		fi
done )
sleep 3
done
