#!/bin/sh /etc/rc.common
# Copyright (C) 2007-2015 OpenWrt.org

START=70
STOP=01

USE_PROCD=1

KEEPALIVED_CONF=/tmp/keepalived.conf

INDENT_1="\t"
INDENT_2="${INDENT_1}${INDENT_1}"
INDENT_3="${INDENT_1}${INDENT_1}${INDENT_1}"
INDENT_4="${INDENT_1}${INDENT_1}${INDENT_1}${INDENT_1}"

config_section_open() {
	local tag="$1"
	local name="$2"
	local indent="$3"

	[ -n "$indent" ] && printf '%b' "$indent" >> "$KEEPALIVED_CONF"
	printf '%s' "$tag" >> "$KEEPALIVED_CONF"
	[ -n "$name" ] && printf ' %s' "$name" >> "$KEEPALIVED_CONF"
	printf ' {\n' >> "$KEEPALIVED_CONF"
}

config_section_close() {
	local indent="$1"
	[ -n "$indent" ] && printf '%b' "$indent" >> "$KEEPALIVED_CONF"
	printf '}\n\n' >> "$KEEPALIVED_CONF"
}

config_foreach_wrapper() {
	local section="$1"
	local function="$1"

	# Convention is that 'function' and 'section' are the same
	config_foreach "$function" "$section"
}

print_elems_indent() {
	local config="$1"
	shift
	local indent="$1"
	shift

	[ -z "$indent" ] && indent="$INDENT_1"
	for opt in "$@"; do
		local "$opt"
		local optval
		local no_val=0
		if [ "${opt:0:7}" = "no_val_" ]; then
			opt="${opt:7}"
			no_val=1
		fi
		config_get "$opt" "$config" "$opt"
		eval optval=\$"$opt"
		[ -z "$optval" ] && continue
		printf '%b%s' "$indent" "$opt" >> "$KEEPALIVED_CONF"
		[ "$no_val" = "0" ] && {
			local words=0
			words="$(echo "$optval" | wc -w)"
			if [ "$words" -gt 1 ]; then
				printf ' "%s"' "$optval" >> "$KEEPALIVED_CONF"
			else
				printf ' %s' "$optval" >> "$KEEPALIVED_CONF"
			fi
		}
		printf '\n' >> "$KEEPALIVED_CONF"
	done
	unset optval
}

print_list_indent() {
	local lst="$1"
	local indent="$2"
	local lst_elems
	[ -z "$indent" ] && indent="$INDENT_1"

	eval lst_elems=\$"$lst"
	[ -z "$lst_elems" ] && return 0

	printf '%b%s {\n' "$indent" "$lst" >> "$KEEPALIVED_CONF"
	for e in $lst_elems; do
		printf '%b%s\n' "${indent}${INDENT_1}" "$e">> "$KEEPALIVED_CONF"
	done
	printf '%b}\n' "$indent" >> "$KEEPALIVED_CONF"
}

print_notify() {
	local type="$1"
	shift
	local name="$1"
	shift
	local indent="$1"
	shift

	for notify in "$@"; do
		printf '%b%s' "${indent}" "$notify">> "$KEEPALIVED_CONF"
		notify="$(echo "$notify" | tr 'a-z' 'A-Z')"
		printf ' "/bin/busybox env -i ACTION=%s TYPE=%s NAME=%s /sbin/hotplug-call keepalived"\n' "$notify" "$type" "$name" >> "$KEEPALIVED_CONF"
	done
}

globals() {
	local notification_email

	printf '%bscript_user root\n' "${INDENT_1}" >> "$KEEPALIVED_CONF"
	printf '%benable_script_security\n' "${INDENT_1}" >> "$KEEPALIVED_CONF"
	printf '%bprocess_names\n' "${INDENT_1}" >> "$KEEPALIVED_CONF"

	printf '%bstartup_script "/bin/busybox env -i ACTION=startup /sbin/hotplug-call keepalived"\n' "${INDENT_1}" >> "$KEEPALIVED_CONF"
	printf '%bstartup_script_timeout 10\n' "${INDENT_1}" >> "$KEEPALIVED_CONF"
	printf '%bshutdown_script "/bin/busybox env -i ACTION=shutdown /sbin/hotplug-call keepalived"\n' "${INDENT_1}" >> "$KEEPALIVED_CONF"
	printf '%bshutdown_script_timeout 10\n' "${INDENT_1}" >> "$KEEPALIVED_CONF"

	config_get notification_email "$1" notification_email
	print_list_indent notification_email

	print_elems_indent "$1" "$INDENT_1" \
		notification_email_from \
		smtp_server \
		smtp_connect_timeout \
		router_id \
		vrrp_mcast_group4 \
		vrrp_mcast_group6 \
		vrrp_startup_delay
}

print_ipaddress_indent() {
	local section="$1"
	local curr_ipaddr="$2"
	local indent="$3"

	local address device scope name
	config_get name "$section" name
	[ "$name" != "$curr_ipaddr" ] && return 0

	config_get address "$section" address
	config_get device "$section" device
	config_get scope "$section" scope
	config_get label_suffix "$section" label_suffix vip

	# Default indent
	[ -z "$indent" ] && indent="$INDENT_1"

	# If no address exit
	[ -z "$address" ] && return 0

	if [ -z "$device" ]; then
		printf '%b%s' "$indent" "$address" >> "$KEEPALIVED_CONF"
	else
		# Add IP address/netmask and device
		printf '%b%s dev %s label %s' "$indent" "$address" "$device" "$device:$label_suffix" >> "$KEEPALIVED_CONF"
		# Add scope
		[ -n "$scope" ] && printf ' scope %s' "$scope" >> "$KEEPALIVED_CONF"
	fi

	printf '\n' >> "$KEEPALIVED_CONF"
}

static_ipaddress() {
	local address
	config_get address "$1" address
	for a in $address; do
		config_foreach print_ipaddress_indent ipaddress "$a"
	done
}

print_route_indent() {
	local section="$1"
	local curr_route="$2"
	local indent="$3"

	local name blackhole address src_addr gateway device scope table

	config_get name "$section" name
	[ "$name" != "$curr_route" ] && return 0

	config_get_bool blackhole "$section" blackhole 0
	config_get address "$section" address
	config_get src_addr "$section" src_addr
	config_get gateway "$section" gateway
	config_get device "$section" device
	config_get table "$section" table

	# If no address exit
	[ -z "$address" ] && return 0

	# Default indent
	[ -z "$indent" ] && indent="$INDENT_1"

	[ "$blackhole" -gt 0 ] && {
		printf '%bblackhole %s\n' "$indent" "$address" >> "$KEEPALIVED_CONF"
		return 0
	}
	# Add src addr or address
	if [ -n "$src_addr" ]; then
		printf '%bsrc %s %s' "$indent" "$src_addr" "$address" >> "$KEEPALIVED_CONF"
	else
		[ -z "$device" ] && return 0
		printf '%b%s' "$indent" "$address" >> "$KEEPALIVED_CONF"
	fi
	# Add route/gateway
	[ -n "$gateway" ] && printf ' via %s' "$gateway" >> "$KEEPALIVED_CONF"
	# Add device
	printf ' dev %s' "$device" >> "$KEEPALIVED_CONF"
	# Add scope
	[ -n "$scope" ] && printf ' scope %s' "$scope" >> "$KEEPALIVED_CONF"
	# Add table
	[ -n "$table" ] && printf ' table %s' "$table" >> "$KEEPALIVED_CONF"
	printf '\n' >> "$KEEPALIVED_CONF"
}

print_track_script_indent() {
	local section="$1"
	local curr_track_elem="$2"
	local indent="$3"

	local name value weight direction
	config_get name "$section" name
	[ -z "$name" ] && return 0

	config_get weight "$section" weight
	config_get direction "$section" direction

	[ "$direction" != "reverse" ] && [ "$direction" != "noreverse" ] && unset direction

	printf '%b%s' "$indent" "$name" >> "$KEEPALIVED_CONF"
	[ -n "$weight" ] && printf ' weight %s' "$weight ${direction:+${direction}}" >> "$KEEPALIVED_CONF"
	printf '\n' >> "$KEEPALIVED_CONF"
}

print_track_elem_indent() {
	local section="$1"
	local curr_track_elem="$2"
	local indent="$3"

	local name value
	config_get name "$section" name
	[ "$name" != "$curr_track_elem" ] && return 0

	config_get value "$section" value
	config_get weight "$section" weight

	[ -z "$value" ] && return 0

	printf '%b%s' "$indent" "$value" >> "$KEEPALIVED_CONF"
	[ -n "$weight" ] && printf ' weight %s' "$weight" >> "$KEEPALIVED_CONF"
	printf '\n' >> "$KEEPALIVED_CONF"
}

print_track_bfd_indent() {
	local section="$1"
	local curr_track_elem="$2"
	local indent="$3"
	local name

	config_get name "$section" name
	[ "$name" != "$curr_track_elem" ] && return 0

	config_get weight "$section" weight

	printf '%b%s' "$indent" "$name" >> "$KEEPALIVED_CONF"
	[ -n "$weight" ] && printf ' weight %s' "$weight" >> "$KEEPALIVED_CONF"
	printf '\n' >> "$KEEPALIVED_CONF"
}

print_unicast_peer_indent() {
	local section="$1"
	local curr_track_elem="$2"
	local indent="$3"
	local name address max_ttl min_ttl

	config_get name "$section" name
	[ "$name" != "$curr_track_elem" ] && return 0

	config_get address "$section" address
	[ -z "$address" ] && return 0

	printf '%b%s' "${indent}" "$address" >> "$KEEPALIVED_CONF"
	
	config_get min_ttl "$section" min_ttl
	config_get max_ttl "$section" max_ttl
	
	[ -n "$min_ttl" ] && printf ' min_ttl %s' "$min_ttl" >> "$KEEPALIVED_CONF"
	[ -n "$max_ttl" ] && printf ' max_ttl %s' "$max_ttl" >> "$KEEPALIVED_CONF"
	printf '\n' >> "$KEEPALIVED_CONF"
	
}

static_routes() {
	local route
	config_get route "$1" route
	for r in $route; do
		config_foreach print_route_indent route "$r"
	done
}

interface_up_down_delays() {
	local device
	local down_delay
	local up_delay
	local line

	config_get device "$1" device
	config_get down_delay "$1" down_delay
	config_get up_delay "$1" up_delay

	[ -z "$device" ] && return
	[ -z "$down_delay" ] && return

	line="${device} ${down_delay}"
	[ -z "$up_delay" ] || line="${line} ${up_delay}"

	printf '%b%s\n' "$INDENT_1" "$line" >> "$KEEPALIVED_CONF"
}

# Count 'vrrp_instance' with the given name ; called by vrrp_instance_check()
vrrp_instance_name_count() {
	local name
	config_get name "$1" name
	[ "$name" = "$2" ] && count="$((count + 1))"
}

# Check if there's a 'vrrp_instance' section with the given name
vrrp_instance_check() {
	local count="0"
	local name="$1"
	config_foreach vrrp_instance_name_count vrrp_instance "$name"
	[ $count -gt 0 ] && return 0 || return 1
}

vrrp_sync_group() {
	local group name
	local valid_group

	# No name for group, exit
	config_get name "$1" name
	[ -z "$name" ] && return 0

	# No members for group, exit
	config_get group "$1" group
	[ -z "$group" ] && return 0

	# Check if we have 'vrrp_instance's defined for 
	# each member and remove names with not vrrp_instance defined
	for m in $group; do
		vrrp_instance_check "$m" && valid_group="$valid_group $m"
	done
	[ -z "$valid_group" ] && return 0

	config_section_open "vrrp_sync_group" "$name"

	group="$valid_group"
	print_list_indent group

	print_elems_indent "$1" "$INDENT_1" no_val_smtp_alert no_val_global_tracking

	# Handle track_script list for sync group
	local track_script_val
	config_get track_script_val "$1" track_script
	if [ -n "$track_script_val" ]; then
		config_section_open "track_script" "" "$INDENT_1"
		for t in $track_script_val; do
			config_foreach print_track_script_indent vrrp_script "$t" "$INDENT_2"
		done
		config_section_close "$INDENT_1"
	fi

	print_notify "GROUP" "$name" "$INDENT_1" notify_backup notify_master \
		notify_fault notify

	config_section_close
}

vrrp_instance() {
	local name auth_type auth_pass

	config_get name "$1" name
	[ -z "$name" ] && return 0

	config_section_open "vrrp_instance" "$name"

	config_get auth_type "$1" auth_type
	config_get auth_pass "$1" auth_pass
	[ -n "$auth_type" ] && [ -n "$auth_pass" ] && {
		printf '%bauthentication {\n' "${INDENT_1}" >> "$KEEPALIVED_CONF"
		printf '%bauth_type %s\n' "${INDENT_2}" "$auth_type" >> "$KEEPALIVED_CONF"
		printf '%bauth_pass %s\n' "${INDENT_2}" "$auth_pass" >> "$KEEPALIVED_CONF"
		printf '%b}\n' "${INDENT_1}" >> "$KEEPALIVED_CONF"
	}

	print_elems_indent "$1" "$INDENT_1" state interface \
		mcast_src_ip unicast_src_ip virtual_router_id version priority \
		advert_int preempt_delay debug \
		lvs_sync_daemon_interface garp_master_delay garp_master_refresh \
		garp_master_repeat garp_master_refresh_repeat \
		no_val_vmac_xmit_base no_val_native_ipv6 no_val_accept \
		no_val_dont_track_primary no_val_smtp_alert no_val_nopreempt \
		no_val_use_vmac no_val_no_accept

	print_notify "INSTANCE" "$name" "$INDENT_1" notify_backup notify_master \
		notify_fault notify_stop

	# Handle virtual_ipaddress & virtual_ipaddress_excluded lists
	for opt in virtual_ipaddress virtual_ipaddress_excluded; do
		config_get "$opt" "$1" "$opt"
		eval optval=\$$opt
		[ -z "$optval" ] && continue
		config_section_open "$opt" "" "$INDENT_1"
		for a in $optval; do
			config_foreach print_ipaddress_indent ipaddress "$a" "$INDENT_2"
		done
		config_section_close "$INDENT_1"
	done

	# Handle virtual_routes
	for opt in virtual_routes; do
		config_get "$opt" "$1" "$opt"
		eval optval=\$$opt
		[ -z "$optval" ] && continue
		config_section_open "$opt" "" "$INDENT_1"
		for r in $optval; do
			config_foreach print_route_indent route "$r" "$INDENT_2"
		done
		config_section_close "$INDENT_1"
	done

	# Handle track_script lists
	for opt in track_script; do
		config_get "$opt" "$1" "$opt"
		eval optval=\$$opt
		[ -z "$optval" ] && continue
		config_section_open "$opt" "" "$INDENT_1"
		for t in $optval; do
			config_foreach print_track_script_indent vrrp_script "$t" "$INDENT_2"
		done
		config_section_close "$INDENT_1"
	done

	# Handle track_interface lists
	for opt in track_interface; do
		config_get "$opt" "$1" "$opt"
		eval optval=\$$opt
		[ -z "$optval" ] && continue
		config_section_open "$opt" "" "$INDENT_1"
		for t in $optval; do
			config_foreach print_track_elem_indent track_interface "$t" "$INDENT_2"
		done
		config_section_close "$INDENT_1"
	done

	# Handle track_bfd lists
	for opt in track_bfd; do
		config_get "$opt" "$1" "$opt"
		eval optval=\$$opt
		[ -z "$optval" ] && continue
		config_section_open "$opt" "" "$INDENT_1"
		for t in $optval; do
			config_foreach print_track_bfd_indent bfd_instance "$t" "$INDENT_2"
		done
		config_section_close "$INDENT_1"
	done

	# Handle simple lists of strings (with no spaces in between)
	for opt in unicast_peer; do
		config_get "$opt" "$1" "$opt"
		eval optval=\$$opt
		[ -z "$optval" ] && continue
		config_section_open "$opt" "" "$INDENT_1"
		for t in $optval; do
			config_foreach print_unicast_peer_indent peer "$t" "$INDENT_2"
		done
		config_section_close "$INDENT_1"
	done
	unset optval

	config_section_close
}

vrrp_script() {
	local name

	config_get name "$1" name
	[ -z "$name" ] && return 0

	config_section_open "vrrp_script" "$name"

	print_elems_indent "$1" "$INDENT_1" script interval weight fall rise timeout

	config_section_close
}


bfd_instance() {
	local name

	config_get name "$1" name
	[ -z "$name" ] && return 0
	config_section_open "bfd_instance" "$name"

	print_elems_indent "$1" "$INDENT_1" neighbor_ip source_ip min_rx min_tx idle_tx hoplimit max_hops

	config_section_close
}

url() {
	local url="$2"

	local name path digest

	config_get name "$1" name
	[ "$url" = "$name" ] || return 0

	config_get path "$1" path
	config_get digest "$1" digest

	[ -n "$digest" ] && [ -n "$path" ] && {
		printf '%burl {\n' "${INDENT_3}" >> "$KEEPALIVED_CONF"
		printf '%bpath %s\n' "${INDENT_4}" "$path" >> "$KEEPALIVED_CONF"
		printf '%bdigest %s\n' "${INDENT_4}" "$digest" >> "$KEEPALIVED_CONF"
		printf '%b}\n' "${INDENT_3}" >> "$KEEPALIVED_CONF"
	}
}

url_list() {
	config_foreach url url "$1"
}

real_server() {
	local server="$2"

	local enabled name weight ipaddr port check

	config_get_bool enabled "$1" enabled 1
	[ "$enabled" -eq 1 ] || return 0

	config_get name "$1" name
	[ "$server" = "$name" ] || return 0

	config_get weight "$1" weight
	[ -n "$weight" ] || return 0

	config_get ipaddr "$1" ipaddr
	config_get port "$1" port
	config_get check "$1" check

	[ -n "$ipaddr" ] && [ -n "$port" ] && {
		printf '%breal_server %s %d {\n' "${INDENT_1}" "$ipaddr" "$port" >> "$KEEPALIVED_CONF"
		printf '%bweight %d\n' "${INDENT_2}" "$weight" >> "$KEEPALIVED_CONF"
		print_notify "REAL_SERVER" "$name" "$INDENT_2" notify_up notify_down
		case "$check" in
			PING_CHECK)
				printf '%b%s {\n' "${INDENT_2}" "$check" >> "$KEEPALIVED_CONF"
				printf '%b}\n' "${INDENT_2}" >> "$KEEPALIVED_CONF"
				;;
			TCP_CHECK)
				printf '%b%s {\n' "${INDENT_2}" "$check" >> "$KEEPALIVED_CONF"
				print_elems_indent "$1" "$INDENT_3" connect_timeout \
					connect_port
				printf '%b}\n' "${INDENT_2}" >> "$KEEPALIVED_CONF"
				;;
			MISC_CHECK)
				printf '%b%s {\n' "${INDENT_2}" "$check" >> "$KEEPALIVED_CONF"
				print_elems_indent "$1" "$INDENT_3" misc_path
				printf '%b}\n' "${INDENT_2}" >> "$KEEPALIVED_CONF"
				;;
			HTTP_GET | SSL_GET)
				printf '%b%s {\n' "${INDENT_2}" "$check" >> "$KEEPALIVED_CONF"
				print_elems_indent "$1" "$INDENT_3" connect_timeout \
					connect_port nb_get_retry delay_before_retry
				# Handle url list
				config_list_foreach "$1" url url_list
				printf '%b}\n' "${INDENT_2}" >> "$KEEPALIVED_CONF"
				;;
		esac
		printf '%b}\n' "${INDENT_1}" >> "$KEEPALIVED_CONF"
	}
}

real_server_list() {
	config_foreach real_server real_server "$1"
}

virtual_server() {
	local enabled ipaddr port lb_algo sorry_server_ip sorry_server_port

	config_get_bool enabled "$1" enabled 1
	[ "$enabled" -eq 1 ] || return 0

	config_get ipaddr "$1" ipaddr
	[ -z "$ipaddr" ] && return 0
	config_get port "$1" port
	[ -z "$port" ] && return 0

	config_section_open "virtual_server" "$ipaddr $port"

	print_elems_indent "$1" "$INDENT_1" fwmark delay_loop \
		lb_kind persistence_timeout persistence_granularity \
		virtualhost protocol

	config_get lb_algo "$1" lb_algo
	[ -z "$lb_algo" ] && lb_algo="rr"
	modprobe ip_vs_${lb_algo} 1>/dev/null 2>&1
	printf '%blb_algo %s\n' "${INDENT_1}" "${lb_algo}" >> "$KEEPALIVED_CONF"

	config_get sorry_server_ip "$1" sorry_server_ip
	config_get sorry_server_port "$1" sorry_server_port
	[ -n "$sorry_server_ip" ] && [ -n "$sorry_server_port" ] && {
		printf '%bsorry_server %s %s\n' "${INDENT_1}" "$sorry_server_ip" "$sorry_server_port" >> "$KEEPALIVED_CONF"
	}

	# Handle real_server list
	config_list_foreach "$1" real_server real_server_list

	config_section_close
}

process_config() {
	local alt_config_file linkbeat_use_polling

	rm -f "$KEEPALIVED_CONF"
	config_load 'keepalived'
	config_get alt_config_file globals alt_config_file

	# If "alt_config_file" specified, use that instead
	[ -n "$alt_config_file" ] && [ -f "$alt_config_file" ] && {
		rm -f "$KEEPALIVED_CONF"
		# Symlink "alt_config_file" since it's a bit easier and safer
		ln -s "$alt_config_file" "$KEEPALIVED_CONF"
		return 0
	}

	# First line
	printf '! Configuration file for keepalived (autogenerated via init script)\n' > "$KEEPALIVED_CONF"
	printf '! Written %s\n\n' "$(date +'%c')" >> "$KEEPALIVED_CONF"

	config_get_bool linkbeat_use_polling globals linkbeat_use_polling 0
	[ "$linkbeat_use_polling" -gt 0 ] && printf 'linkbeat_use_polling\n\n' >> "$KEEPALIVED_CONF"

	config_section_open "global_defs"
	config_foreach_wrapper globals
	config_section_close

	config_section_open "static_ipaddress"
	config_foreach_wrapper static_ipaddress
	config_section_close

	config_section_open "static_routes"
	config_foreach_wrapper static_routes
	config_section_close

	config_section_open "interface_up_down_delays"
	config_foreach_wrapper interface_up_down_delays
	config_section_close

	config_foreach_wrapper vrrp_script
	config_foreach_wrapper bfd_instance
	config_foreach_wrapper vrrp_sync_group
	config_foreach_wrapper vrrp_instance
	config_foreach_wrapper virtual_server
	return 0
}

service_triggers() {
	procd_add_reload_trigger "keepalived"
}

reload_service() {
	process_config
	#SIGHUP is used by keepalived to do init.d reload
	procd_send_signal keepalived
}

start_service() {
	local enabled

	[ -f /etc/config/keepalived ] || return 0

	config_load 'keepalived'
	config_get_bool enabled globals enabled 1
	[ "$enabled" = "0" ] && return 0

	process_config

	procd_open_instance
	procd_set_param command /usr/sbin/keepalived
	procd_append_param command -n # don't daemonize, procd will handle that for us
	procd_append_param command -f "$KEEPALIVED_CONF"
	procd_set_param respawn
	procd_close_instance
}

