#!/bin/bash

for optionsfile in /etc/net/options /etc/net/options.d/*; do
	[ -s "$optionsfile" ] && . $optionsfile
done

DENOISE="egrep ^[^#]"

# Stolen from /etc/init.d/functions and improved.
is_yes()
{
	case "$1" in
		[Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|[Yy]|1)
			# true returns zero
			return 0
		;;
		*)
			# false returns one
			return 1
		;;
	esac
}

is_no()
{
	case "$1" in
		[Nn][Oo]|[Ff][Aa][Ll][Ss][Ee]|[Oo][Ff][Ff]|[Nn]|0)
			# true returns zero
			return 0
		;;
		*)
			# false returns one
			return 1
		;;
	esac
}

SourceIfNotEmpty()
{
	local f
	f="$1"
	shift
	[ -s "$f" ] && . "$f" "$@"
}

ExecIfExecutable()
{
	local f
	f="$1"
	shift
	[ -x "$f" ] && "$f" "$@"
}

supported_type()
{
	local TYPE=${1:?missing 1st arg to $FUNCNAME}
	[ -x $SCRIPTDIR/create-$TYPE -o -x $SCRIPTDIR/destroy-$TYPE ] && return 0
	return 1
}

# try to translate iface name into corresponding type
name2type()
{
	local NAME=${1:?missing 1st arg to $FUNCNAME}
	local CAND=${NAME%%[0-9]*}
	supported_type $CAND && echo $CAND
}

# Read options hierarchy. Should work for unknown interfaces too.
pickup_options()
{
	# to determine iface type we must read it's config, but
	# we should read global config and type config first.
	# A workaround here.
	SourceIfNotEmpty `profiled_filename $MYIFACEDIR/options`
	is_yes "$DISABLED" && return
	[ -z "$TYPE" ] && TYPE=`name2type $NAME`
	[ -z "$TYPE" ] && {
		print_error "No TYPE is specified for iface '$NAME' and can't guess automatically. Please fix."
		return 1
	}
	if ! supported_type $TYPE; then
		print_error "iface type '$TYPE' is not supported"
	fi
	# source default options
	SourceIfNotEmpty `profiled_filename $IFACEDIR/default/options`
	# then source default options for our iface type
	SourceIfNotEmpty `profiled_filename $IFACEDIR/default/options-$TYPE`
	# and finally source iface options
	SourceIfNotEmpty `profiled_filename $MYIFACEDIR/options`
	# Load type-specific functions
	SourceIfNotEmpty $SCRIPTDIR/functions-$TYPE
}

print_error()
{
	echo "ERROR: $0: $@" >&2
	# don't hang if logger hangs
	logger -p daemon.info -t /etc/net -- "ERROR: $0: $@" &
}

# Don't display progress if not verbose.
if is_yes "$VERBOSE"; then
	print_message()
	{
		echo "$@"
		return 0
	}
	if is_yes "$PROGRESS"; then
		print_progress()
		{
			echo -n ${1:-.}
			return 0
		}
		print_nack()
		{
			echo -n ${1:-!}
			return 0
		}
	else
		print_progress() { return 0; }
		print_nack() { return 0; }
	fi
else
	print_message() { return 0; }
	print_progress() { return 0; }
	print_nack() { return 0; }
fi

iface_is_up()
{
	local NAME=${1:?missing 1st argument to $FUNCNAME}
	$IP -o link show dev $NAME 2>/dev/null | cut -d' ' -f3 | grep -qs '[<,]UP[,>]'
}

try_rmmod()
{
	is_yes "$NEVER_RMMOD" && return
	local MODNAME=${1:?missing 1st argument to $FUNCNAME}
	# Note: usage counter is treated in a different way by 2.6 kernels.
	# This may cause bugs :(
	$LSMOD | egrep -qs "^$MODNAME +[0-9]+ +0 " && rmmod $MODNAME
}

# Look if iface name will change after ifrename call.
get_mapped_ifname()
{
	local OLDNAME=${1:?missing 1st argument to $FUNCNAME}
	local PROF_IFTAB=`profiled_filename $IFTAB`
	local NEW_NAME
	if [ -s "$PROF_IFTAB" ]; then
		[ -x $IFRENAME ] || {
			print_error "$IFRENAME is unavailable, but $PROF_IFTAB exists"
			exit 1
		}
		NEW_NAME=`$IFRENAME -c $PROF_IFTAB -i $OLDNAME -D 2>/dev/null | head -1 | cut -d' ' -f7 | sed 's/.$//'`
		[ $? = 0 -a -n "$NEW_NAME" ] && echo $NEW_NAME
	fi
}

profiled_filename()
{
	local BASE=${1:?missing 1st arg to $FUNCNAME}
	local CAND1=$BASE${NETPROFILE:+#$NETPROFILE}${NETHOST:+@$NETHOST}
	local CAND2=$BASE${NETHOST:+@$NETHOST}
	local CAND3=$BASE${NETPROFILE:+#$NETPROFILE}
	local CAND4=$BASE
	if [ -e $CAND1 ]; then
		echo $CAND1
	elif [ -e $CAND2 ]; then
		echo $CAND2
	elif [ -e $CAND3 ]; then
		echo $CAND3
	else
		echo $CAND4
	fi
	return 0
}

# This function updates part of current config file namespace, consisting of
# profile name and host name. Profile name can be overriden, but host
# name can't be.
init_netprofile()
{
	if [ -n "$1" ]; then
		NETPROFILE="$1"
	elif [ -n "$MYIFACEDIR" -a -s "$MYIFACEDIR/selectprofile" -a -x "$MYIFACEDIR/selectprofile" ]; then
		NETPROFILE=`$MYIFACEDIR/selectprofile $0 | head -1 | cut -d' ' -f1`
	elif [ -z "$NETPROFILE" ]; then
		if [ -s "$PROFILE_FILE" -a -r "$PROFILE_FILE" ]; then
			NETPROFILE=`$DENOISE -m 1 "$PROFILE_FILE" | cut -d' ' -f1`
		elif [ -r "$PROC_CMDLINE" ]; then
			# try to fetch profile name from kernel options
			NETPROFILE=`cat "$PROC_CMDLINE" | sed 's/  / /g' | \
			sed 's/ /\n/g' | egrep '^netprofile=' | cut -d'=' -f2`
		fi
	fi
	return 0
}

# Network hostname init. Should be called by network.init during startup.
init_nethost()
{
	if [ -n "$1" ]; then
		NETHOST="$1"
	elif [ -s "$HOSTTAB" ] && grep "^$HOSTNAME " $HOSTTAB; then
		NETHOST=`$DENOISE $HOSTTAB | grep -m 1 "^$HOSTNAME " | cut -d' ' -f2`
	else
		NETHOST=$HOSTNAME
	fi
	return 0
}

# iterator
foreach_child ()
{
	local WHAT=${1:?missing 1st argument to $FUNCNAME}
	local CHILDREN=`$SCRIPTDIR/childfinder $NAME`
	local ret=0
	for child in $CHILDREN; do
		$SCRIPTDIR/$WHAT $child || : $((ret++))
	done
	return $ret
}

# get down all dependant ifaces
ifdown_children ()
{
	foreach_child ifdown
}

# same for ifdown
ifup_children ()
{
	foreach_child ifup
}

# check if parent iface(s) (REQUIRES/HOST) is up and ifup if needed
ifup_parents ()
{
	[ -z "$REQUIRES" -a -z "$HOST" ] && return 0
	local parent ret=0
	for parent in ${REQUIRES:+$REQUIRES }${HOST}; do
		# We call ifup even if parent exists, but is down.
		if ! iface_is_up $parent; then
			$SCRIPTDIR/ifup $parent || {
				ret=$?
				break
			}
		fi
	done
	return $ret
}

ifdown_parents ()
{
	[ -z "$REQUIRES" -a -z "$HOST" ] && return 0
	local parent ret=0
	for parent in ${REQUIRES:+$REQUIRES }${HOST}; do
		# Parent iface may exist, but be down, because some children auto-down their parents.
		# We should call ifdown, if iface exists.
		if iface_exists $parent; then
			$SCRIPTDIR/ifdown $parent || {
				ret=$?
				break
			}
		fi
	done
	return $ret
}

iface_exists ()
{
	local NAME=${1:?missing 1st argument to $FUNCNAME}
	$IP li sh dev $NAME >/dev/null 2>&1
}

seen_iface ()
{
	local needle=${1:?missing 1st arg to $FUNCNAME}
	for cand in $SEEN_IFACES; do
		[ "$cand" = "$needle" ] && return 0
	done
	return 1
}

add_seen_iface ()
{
	local newname=${1:?missing 1st arg to $FUNCNAME}
	SEEN_IFACES="${SEEN_IFACES:+$SEEN_IFACES }$newname"
}

# Universal configuration processor. Automatically does profiling,
# comments filtering and progress reporting.
xargise_file()
{
	local BASEFILE="${1:?missing 1st arg to $FUNCNAME}"
	local PROCESSOR="${2:?missing 2nd arg to $FUNCNAME}"
	local REALFILE=`profiled_filename $BASEFILE`
	if [ -s $REALFILE ]; then
		$DENOISE $REALFILE | xargs --max-lines=1 $PROCESSOR && print_progress
	fi
	return 0
}

# operation\first word | add X | del X | Y X
#----------------------+-------+-------+------
# add                  | add X | del X | add X
#----------------------+-------+-------+------
# del                  | del X | del X | del X
#----------------------+-------+-------+------
process_ipv4rules()
{
	local OP=${1:?missing 1st arg to $FUNCNAME}
	SRCFILE=`profiled_filename $MYIFACEDIR/ipv4rule`
	[ -s "$SRCFILE" ] && $DENOISE "$SRCFILE" | \
	while read FIRST REST; do
		case "$FIRST" in
			add)
				[ $OP = "del" ] && FIRST=del
			;;
			del)
				# should we restore deleted rules on iface shutdown?
				# [ $OP = "del" ] && FIRST=add
			;;
			*)
				FIRST="$OP $FIRST"
			;;
		esac
		$IP -4 rule $FIRST $REST
		print_progress
	done
}

flush_addresses()
{
	local NAME=${1:?missing 1st arg to $FUNCNAME}
	# Delete ip rules before addresses (and thus routes), see comment in
	# config-ipv4:config_routes_rules() for exact reason.
	process_ipv4rules del
	$IP -4 address flush dev $NAME >/dev/null 2>&1
	$IP -6 address flush dev $NAME scope host >/dev/null 2>&1
	$IP -6 address flush dev $NAME scope site >/dev/null 2>&1
	$IP -6 address flush dev $NAME scope global >/dev/null 2>&1
}

have_ifplugd()
{
	if [ -x "${IFPLUGD:=$DEFAULT_IFPLUGD}" ]; then
		echo yes
	else
		echo no
	fi
}
