#!/bin/sh

. $CTDB_BASE/functions
loadconfig

ctdb_setup_service_state_dir "per_ip_routing"

[ -z "$CTDB_PER_IP_ROUTING_STATE" ] && {
	CTDB_PER_IP_ROUTING_STATE="$service_state_dir"
}

AUTO_LINK_LOCAL="no"

case "$CTDB_PER_IP_ROUTING_CONF" in
	__auto_link_local__)
		AUTO_LINK_LOCAL="yes"
		CTDB_PER_IP_ROUTING_CONF="$CTDB_PER_IP_ROUTING_STATE/auto_link_local.conf"
		;;
	*)
		[ -z "$CTDB_PER_IP_ROUTING_CONF" ] && {
			#echo "No config file found. Nothing to do for 13.per_ip_routing"
			exit 0;
		}
		;;
esac

_low=$CTDB_PER_IP_ROUTING_TABLE_ID_LOW
_high=$CTDB_PER_IP_ROUTING_TABLE_ID_HIGH

test -z "$_low" && {
	echo "$0: CTDB_PER_IP_ROUTING_TABLE_ID_LOW not configured";
	exit 1;
}
test -z "$_high" && {
	echo "$0: CTDB_PER_IP_ROUTING_TABLE_ID_HIGH not configured";
	exit 1;
}
test "$_low" -ge "$_high" && {
	echo "$0: CTDB_PER_IP_ROUTING_TABLE_ID_LOW[$_low] needs to be below CTDB_PER_IP_ROUTING_TABLE_ID_HIGH[$_high]";
	exit 1;
}

test -z "$CTDB_PER_IP_ROUTING_RULE_PREF" && {
	echo "$0: CTDB_PER_IP_ROUTING_RULE_PREF not configured";
	exit 1;
}

locknesting=0
lock_root="$CTDB_PER_IP_ROUTING_STATE"
host=`hostname`

lock_debug()
{
	echo -n ""
}

############################
# grab a lock file. Not atomic, but close :)
# tries to cope with NFS
lock_file() {
	if [ -z "$lock_root" ]; then
		lock_root=`pwd`;
	fi
	lckf="$lock_root/$1"
	machine=`cat "$lckf" 2> /dev/null | cut -d: -f1`
	pid=`cat "$lckf" 2> /dev/null | cut -d: -f2`

	if [ "$pid" = "$$" ]; then
		locknesting=`expr $locknesting + 1`
		lock_debug "lock nesting now $locknesting"
		return 0
	fi

	if test -f "$lckf"; then
		test $machine = $host || {
			lock_debug "lock file $lckf is valid for other machine $machine"
			stat -c%y "$lckf"
			return 1
		}
		kill -0 $pid && {
			lock_debug "lock file $lckf is valid for process $pid"
			stat -c%y "$lckf"
			return 1
		}
		lock_debug "stale lock file $lckf for $machine:$pid"
		cat "$lckf"
		rm -f "$lckf"
	fi
	echo "$host:$$" > "$lckf"
	return 0
}

############################
# unlock a lock file
unlock_file() {
	if [ -z "$lock_root" ]; then
		lock_root=`pwd`;
	fi
	if [ "$locknesting" != "0" ]; then
		locknesting=`expr $locknesting - 1`
		lock_debug "lock nesting now $locknesting"
	else
		lckf="$lock_root/$1"
		rm -f "$lckf"
	fi
}

generate_table_id () {
	local _ip=$1
	local _ipsdir="$CTDB_PER_IP_ROUTING_STATE/ips"
	local _ipdir="$_ipsdir/$_ip"

	mkdir -p $_ipdir

	#echo "generate_table_id $_ip"

	local _id=`cat $_ipdir/table_id 2>/dev/null| xargs`
	test -n "$_id" && {
		#echo "IP: $_ip => OLD TABLE: $_id"
		table_id=$_id
		return 0;
	}

	local _low="$CTDB_PER_IP_ROUTING_TABLE_ID_LOW"
	local _high="$CTDB_PER_IP_ROUTING_TABLE_ID_HIGH"

	local _newid=""
	for _id in `seq $_low $_high | xargs`; do
		local _table_lck="table_id_$_id.lock"
		lock_file $_table_lck 2>/dev/null || {
			continue;
		}
		local _taken=`grep "^$_id$" $_ipsdir/*/table_id 2>/dev/null| wc -l | xargs`
		test x"$_taken" != x"0" && {
			unlock_file $_table_lck
			#echo "tableid: $_id taken"
			continue
		}
		_newid=$_id;
		echo "$_newid" > $_ipdir/table_id
		unlock_file $_table_lck
		break;
	done

	test -z "$_newid" && {
		echo "generate_table_id: out of table ids: $_low - $_high"
		exit 1;
	}

	#echo "IP: $_ip => NEW TABLE: $_newid"
	table_id=$_newid
	return 0;
}

run_release_script_once()
{
	local _script=$1

	#echo "run_release_script_once[$_script]"

	test -x "$_script" && {
		#echo "run it: start"
		$_script || {
			echo "release_script: $_script - failed $?"
			return $?;
		}
		#echo "run it: end"
	}

	echo '#!/bin/sh' > $_script
	echo '#' >> $_script
	echo >> $_script

	chmod +x $_script

	return 0;
}

generate_auto_link_local()
{
	local _ip=$1
	local _maskbits=$2

	#echo "generate_auto_link_local $_ip $_maskbits"

	local _netip=`ipv4_host_addr_to_net_addr $_ip $_maskbits`

	local _line="$_ip $_netip/$_maskbits"

	local _lockfile="$CTDB_PER_IP_ROUTING_CONF.lock"
	local _script="$CTDB_PER_IP_ROUTING_CONF.$$.sh"

	echo "#!/bin/sh" > $_script
	echo "#" >> $_script
	echo "" >> $_script
	echo "_config=\`cat $CTDB_PER_IP_ROUTING_CONF 2>/dev/null\`" >> $_script
	echo "_exact=\`echo -n \"\$_config\" | grep \"^$_line\$\" | wc -l | xargs\`" >> $_script
	echo "" >> $_script

	echo "test x\"\$_exact\" = x\"1\" && {" >> $_script
	echo "    exit 0;" >> $_script
	echo "}" >> $_script
	echo "" >> $_script

	echo "_tmp=\"$CTDB_PER_IP_ROUTING_CONF.$$.tmp\"" >> $_script
	echo "echo -n \"\$_config\" | grep -v \"^$_ip \" | cat > \$_tmp || {" >> $_script
	echo "    echo \"echo -n \\\"\$_config\\\" | grep -v \\\"^$_ip \\\" > \$_tmp - failed\"" >> $_script
	echo "    exit 1;" >> $_script
	echo "}" >> $_script
	echo "echo \"$_line\" >> \$_tmp || {" >> $_script
	echo "    echo \"echo \\\"$_line\\\" >> \$_tmp - failed\"" >> $_script
	echo "    exit 1;" >> $_script
	echo "}" >> $_script
	echo "" >> $_script

	echo "mv \$_tmp $CTDB_PER_IP_ROUTING_CONF || {" >> $_script
	echo "    echo \"mv \$_tmp $CTDB_PER_IP_ROUTING_CONF - failed\"" >> $_script
	echo "    exit 1;" >> $_script
	echo "}" >> $_script
	echo "" >> $_script

	echo "echo \"Added '$_line' to $CTDB_PER_IP_ROUTING_CONF\"">> $_script
	echo "exit 0" >> $_script

	chmod +x $_script

	test -f $_lockfile || {
		touch $_lockfile
	}

	flock --timeout 30 $_lockfile $_script
	ret=$?
	rm $_script
	return $ret
}

generate_per_ip_routing()
{
	local _ip=$1
	local _maskbits=$2
	local _iface=$3
	local _readonly=$4
	local _ipdir="$CTDB_PER_IP_ROUTING_STATE/ips/$_ip"

	table_id=""
	release_script="$_ipdir/per_ip_routing_release.sh"
	setup_script="$_ipdir/per_ip_routing_setup.sh"

	test x"$_readonly" = x"yes" && {
		test -d $_ipdir || {
			return 1;
		}
		return 0;
	}

	mkdir -p $_ipdir || {
		echo "mkdir -p $_ipdir failed"
		return 1;
	}
	echo "$_ip" > $_ipdir/ip

	generate_table_id $_ip

	test x"$AUTO_LINK_LOCAL" = x"yes" && {
		generate_auto_link_local $_ip $_maskbits
	}

	run_release_script_once $release_script

	echo '#!/bin/sh' > $setup_script
	echo '#' >> $setup_script
	echo >> $setup_script
	chmod +x $setup_script

	return 0;
}

setup_per_ip_routing()
{
	local _ip=$1
	local _iface=$2
	local _table_id=$3
	local _release_script=$4
	local _setup_script=$5

	local _config=`cat $CTDB_PER_IP_ROUTING_CONF`
	local _lines=`echo -n "$_config" | grep -n "^$_ip " | cut -d ':' -f1 | xargs`

	local _pref="$CTDB_PER_IP_ROUTING_RULE_PREF"

	test -n "$_lines" && {
		echo "ip rule del from $_ip pref $_pref table $_table_id" >> $_release_script
		echo "ip route flush table $_table_id 2>/dev/null" >> $_release_script

		cmd="ip rule del from $_ip pref $_pref 2>/dev/null"
		echo "$cmd" >> $_setup_script

		cmd="ip route flush table $_table_id 2>/dev/null"
		echo "$cmd" >> $_setup_script

		cmd="ip rule add from $_ip pref $_pref table $_table_id"
		echo "$cmd || {" >> $_setup_script
		echo "    echo \"$cmd - failed \$ret\"" >> $_setup_script
		echo "    exit \$ret" >> $_setup_script
		echo "}" >> $_setup_script
	}
	local _l
	for _l in $_lines; do
		local _line=`echo -n "$_config" | head -n $_l | tail -n 1`
		local _dest=`echo -n "$_line" | cut -d ' ' -f 2`
		local _gw=`echo -n "$_line" | cut -d ' ' -f 3`

		local _via=""
		test -n "$_gw" && {
			_via="via $_gw"
		}

		cmd="ip route add $_dest $_via dev $_iface table $_table_id"
		echo "$cmd || {" >> $_setup_script
		echo "    echo \"$cmd - failed \$ret\"" >> $_setup_script
		echo "    exit \$ret" >> $_setup_script
		echo "}" >> $_setup_script
	done

	$_setup_script
	return $?;
}

ctdb_check_args "$@"

case "$1" in
     #############################
     # called when ctdbd starts up
     startup)
	# cleanup old rules
	pref=$CTDB_PER_IP_ROUTING_RULE_PREF
	rules=`ip rule show | grep "^$pref:" | sed -e 's/.*from \([^ ][^ ]*\) lookup \([^ ][^ ]*\)/\2;\1/' | xargs`
	for r in $rules; do
		table_id=`echo -n "$r" | cut -d ';' -f1`
		ip=`echo -n "$r" | cut -d ';' -f2-`

		echo "Removing ip rule for public address $ip for routing table $table_id"
		cmd="ip rule del from $ip table $table_id pref $pref"
		#echo $cmd
		eval $cmd
		cmd="ip route flush table $table_id"
		#echo $cmd
		eval $cmd 2>/dev/null
	done

	# make sure that we only respond to ARP messages from the NIC where
	# a particular ip address is associated.
	[ -f /proc/sys/net/ipv4/conf/all/arp_filter ] && {
	    echo 1 > /proc/sys/net/ipv4/conf/all/arp_filter
	}

	mkdir -p $CTDB_PER_IP_ROUTING_STATE

	;;

     shutdown)

	for s in $CTDB_PER_IP_ROUTING_STATE/ips/*/per_ip_routing_release.sh; do
		run_release_script_once "$s"
	done
	rm -rf $CTDB_PER_IP_ROUTING_STATE

	;;

     ################################################
     # called when ctdbd wants to claim an IP address
     takeip)
	iface=$2
	ip=$3
	maskbits=$4

	ipv4_is_valid_addr $ip || {
		echo "$0: $1 not an ipv4 address skipping IP:$ip"
		exit 0;
	}

	[ ! -d "$CTDB_PER_IP_ROUTING_STATE" ] && {
		echo "$0: $1 No state directory found, waiting for startup."
		exit 0;
	}

	generate_per_ip_routing $ip $maskbits $iface "no" || {
		echo "$0: $1: generate_per_ip_routing $ip $maskbits $iface no - failed"
		exit 1;
	}

	setup_per_ip_routing $ip $iface $table_id $release_script $setup_script || {
		echo "$0: $1: setup_per_ip_routing $ip $iface $table_id $release_script $setup_script - failed"
		exit 1;
	}

	setup_iface_ip_readd_script $iface $ip $maskbits $setup_script || {
		echo "$0: $1: setup_iface_ip_readd_script $iface $ip $maskbits $setup_script - failed"
		exit 1;
	}

	# flush our route cache
	echo 1 > /proc/sys/net/ipv4/route/flush
	ctdb gratiousarp $ip $iface

	;;

     ################################################
     # called when ctdbd wants to claim an IP address
     updateip)
	oiface=$2
	niface=$3
	ip=$4
	maskbits=$5

	ipv4_is_valid_addr $ip || {
		echo "$0: $1 not an ipv4 address skipping IP:$ip"
		exit 0;
	}

	[ ! -d "$CTDB_PER_IP_ROUTING_STATE" ] && {
		echo "$0: $1 No state directory found, waiting for startup."
		exit 0;
	}

	generate_per_ip_routing $ip $maskbits $niface "no" || {
		echo "$0: $1: generate_per_ip_routing $ip $maskbits $niface no - failed"
		exit 1;
	}

	setup_per_ip_routing $ip $niface $table_id $release_script $setup_script || {
		echo "$0: $1: setup_per_ip_routing $ip $niface $table_id $release_script $setup_script - failed"
		exit 1;
	}

	setup_iface_ip_readd_script $niface $ip $maskbits $setup_script || {
		echo "$0: $1: setup_iface_ip_readd_script $niface $ip $maskbits $setup_script - failed"
		exit 1;
	}

	# flush our route cache
	echo 1 > /proc/sys/net/ipv4/route/flush

	ctdb gratiousarp $ip $niface
	tickle_tcp_connections $ip

	;;

     ##################################################
     # called when ctdbd wants to release an IP address
     releaseip)
	iface=$2
	ip=$3
	maskbits=$4

	ipv4_is_valid_addr $ip || {
		echo "$0: $1 not an ipv4 address skipping IP:$ip"
		exit 0;
	}

	[ ! -d "$CTDB_PER_IP_ROUTING_STATE" ] && {
		echo "$0: $1 No state directory found, waiting for startup."
		exit 0;
	}

	generate_per_ip_routing $ip $maskbits $iface "yes" || {
		echo "$0: $1: generate_per_ip_routing $ip $maskbits $iface yes - failed"
		exit 1;
	}

	run_release_script_once "$release_script"

	;;


     ###########################################
     # called when ctdbd has finished a recovery
     recovered)
	;;

     ####################################
     # called when ctdbd is shutting down
     shutdown)
	;;

     monitor)
	;;
    *)
	ctdb_standard_event_handler "$@"
	;;
esac

exit 0

