#!/bin/sh /etc/rc.common
# Copyright (C) 2013 Julius Schulz-Zander <julius@net.t-labs.tu-berlin.de>
# Copyright (C) 2014-2017 OpenWrt.org
# Copyright (C) 2018 Yousong Zhou <yszhou4tech@gmail.com>
# Copyright (C) 2021 Felix Fietkau <nbd@nbd.name>

. /lib/functions/procd.sh
START=15

basescript=$(readlink "$initscript")

ovs_ctl="/usr/share/openvswitch/scripts/ovs-ctl"; [ -x "$ovs_ctl" ] || ovs_ctl=:
ovn_ctl="/usr/share/ovn/scripts/ovn-ctl"; [ -x "$ovn_ctl" ] || ovn_ctl=:

extra_command "status" "Get status information"

service_triggers() {
	procd_add_reload_trigger openvswitch
}

init_triggers() {
	procd_open_service "$(basename ${basescript:-$initscript})" "$initscript"
	procd_close_service set
}

start() {
	init_triggers
	ovs_action start "$@"
}

reload() {
	start
}

running() {
	return 0
}

stop() {
	procd_kill "$(basename ${basescript:-$initscript})"
	ovs_action stop "$@"
}

restart() {
	init_triggers
	ovs_action restart "$@"
}

status() {
	ovs_action status "$@"
}

ovs_action_cfgs=
ovs_action() {
	local action="$1"; shift
	local cfgtype

	ovs_action_cfgs="$*"
	config_load openvswitch
	for cfgtype in ovs ovn_northd ovn_controller; do
		config_foreach "ovs_xx" "$cfgtype" "$action" "$cfgtype"
	done

	case "$action" in
		restart|start)
			config_foreach ovs_bridge_init "ovs_bridge"
			;;
	esac

}

ovs_xx() {
	local cfg="$1"
	local action="$2"
	local cfgtype="$3"
	local disabled

	if [ -n "$ovs_action_cfgs" ] && ! list_contains "ovs_action_cfgs" "$cfg"; then
		return
	fi
	case "$action" in
		status|stop) ;;
		*)
			config_get_bool disabled "$cfg" disabled 0
			[ "$disabled" == "0" ] || return
			;;
	esac

	case "$cfgtype" in
		ovs)
			"$ovs_ctl" "$action" \
				--system-id=random 1000>&-
			ovs_set_ssl
			;;
		ovn_*)
			"$ovn_ctl" "${action}_${cfgtype#ovn_}"
			;;
	esac
}

ovs_bridge_parse_port() {
	case "$1" in
		*:*)
			port="${1%%:*}"
			type="${1#*:}"
		;;
		*)
			port="$1"
			type=""
		;;
	esac
}

ovs_bridge_port_add() {
	[ -n "$1" ] || return

	ovs_bridge_parse_port "$1"
	cur_type="$(ovs-vsctl get interface "$port" type 2>/dev/null)"
	[ "$?" = 0 ] && {
		[ "$type" = "$cur_type" ] || ovs-vsctl del-port "$port"
	}

	ovs-vsctl --may-exist add-port "$name" "$port" ${type:+ -- set interface "$port" type="$type"}
	ovs_bridge_port_up "$port"
	__port_list="$__port_list ${port} "
}

ovs_bridge_port_add_complex() {
	local cfg="$1"
	local cur_bridge="$2"

	local bridge disabled ofport port tag type
	local cur_tag cur_type del_port

	config_get_bool disabled "$cfg" disabled 0
	[ "$disabled" = "0" ] || return

	config_get bridge "$cfg" bridge
	[ "$bridge" = "$cur_bridge" ] || return
	ovs-vsctl br-exists "$bridge" || return

	config_get port "$cfg" port
	[ -n "$port" ] || return

	config_get ofport "$cfg" ofport

	config_get tag "$cfg" tag
	if [ -n "$tag" ]; then
		if cur_tag="$(ovs-vsctl get port "$port" tag 2>/dev/null)"; then
			[ "$tag" = "$cur_tag" ] || del_port=1
		fi
	fi

	config_get type "$cfg" type
	if [ -n "$type" ]; then
		if cur_type="$(ovs-vsctl get interface "$port" type 2>/dev/null)"; then
			[ "$type" = "$cur_type" ] || del_port=1
		fi
	fi

	[ "${del_port:-0}" -eq 1 ] && ovs-vsctl --if-exists del-port "$bridge" "$port"

	ovs-vsctl --may-exist add-port "$bridge" "$port" ${tag:+tag="$tag"} \
		${ofport:+ -- set interface "$port" ofport_request="$ofport"} \
		${type:+ -- set interface "$port" type="$type"}
	ovs_bridge_port_up "$port"
	__port_list="$__port_list ${port} "
}

ovs_bridge_port_cleanup() {
	for port in `ovs-vsctl list-ports "$name"`; do
		case "$__port_list" in
			*" $port "*);;
			*) ovs-vsctl del-port "$port";;
		esac
	done
}

ovs_bridge_port_up() {
	local port="$1"

	ip link set dev "$port" up
}

ovs_bridge_validate_datapath_id() {
	local dpid="$1"

	if expr "$dpid" : '[[:xdigit:]]\{16\}$' > /dev/null; then
		return 0
	elif expr "$dpid" : '0x[[:xdigit:]]\{1,16\}$' > /dev/null; then
		return 0
	else
		logger -t openvswitch "invalid datapath_id: $dpid"
		return 1
	fi
}

ovs_bridge_validate_datapath_desc() {
	local dpdesc="$1"

	if [ "$(echo $dpdesc | wc -c)" -le 255 ]; then
		return 0
	else
		logger -t openvswitch "invalid datapath_desc: $dpdesc"
		return 1
	fi
}

ovs_bridge_validate_fail_mode() {
	local fail_mode="$1"

	case "$fail_mode" in
		secure|standalone)
			return 0
			;;
		*)
			logger -t openvswitch "invalid fail_mode: $fail_mode"
			return 1
			;;
	esac
}

ovs_bridge_init() {
	local cfg="$1"

	local disabled
	local name
	local controller
	local datapath_id

	config_get_bool disabled "$cfg" disabled 0
	[ "$disabled" == "0" ] || return

	config_get name "$cfg" name $cfg
	ovs-vsctl --may-exist add-br "$name"

	config_get datapath_id "$cfg" datapath_id
	[ -n "$datapath_id" ] && {
		ovs_bridge_validate_datapath_id "$datapath_id" && {
			ovs-vsctl --if-exists set bridge "$name" other-config:datapath-id="$datapath_id"
		}
	}

	config_get datapath_desc "$cfg" datapath_desc
	[ -n "$datapath_desc" ] && {
		ovs_bridge_validate_datapath_desc "$datapath_desc" && {
			ovs-vsctl --if-exists set bridge "$name" other-config:dp-desc="$datapath_desc"
		}
	}

	config_get fail_mode "$cfg" fail_mode
	[ -n "$fail_mode" ] && {
		ovs_bridge_validate_fail_mode "$fail_mode" && {
			ovs-vsctl set-fail-mode "$name" "$fail_mode" 2> /dev/null
		} || {
			ovs-vsctl del-fail-mode "$name" 2> /dev/null
		}
	} || {
		ovs-vsctl del-fail-mode "$name" 2> /dev/null
	}

	config_list_foreach "$cfg" "ports" ovs_bridge_port_add
	config_foreach ovs_bridge_port_add_complex ovs_port "$name"
	config_get_bool drop "$cfg" "drop_unknown_ports" 0
	[ "$drop" == 1 ] && ovs_bridge_port_cleanup

	config_get controller "$cfg" controller
	[ -n "$controller" ] && \
		ovs-vsctl set-controller "$name" "$controller"
}

ovs_set_ssl() {
	local ca="$(uci -q get openvswitch.ovs.ca)"
	[ -f "$ca" ] || return
	local cert="$(uci get openvswitch.ovs.cert)"
	[ -f "$cert" ] || return
	local key="$(uci get openvswitch.ovs.key)"
	[ -f "$key" ] || return

	ovs-vsctl set-ssl "$key" "$cert" "$ca"
}
