#!/bin/bash

# Network interface configuration
#
# Copyright (c) 2002-2003 SuSE Linux AG Nuernberg, Germany.
# All rights reserved.
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation; either version 2 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc., 59 Temple
# Place, Suite 330, Boston, MA 02111-1307 USA
#
# Authors: Michal Svec <msvec@suse.cz>
#          Mads Martin Joergensen <mmj@suse.de>
#
# $Id: functions 1229 2005-08-10 11:08:53Z zoz $
#

. /etc/sysconfig/network/scripts/functions.common

# RUN_FILES_BASE contains the path to the configuration data cache.
# It __must__ __not__ contain a trailing slash.
RUN_FILES_BASE=/var/run/sysconfig
NETWORK_RUNFILE="$RUN_FILES_BASE/network"
STAMPFILE_STUB="$RUN_FILES_BASE/new-stamp-"

#
# to test the next two functions:
#
# for i in $(seq 0 32); do
# 	echo $i: $(pfxlen2mask $i) " ---> " $(mask2pfxlen $(pfxlen2mask $i))
# done

mask2pfxlen() {
	local i octet mask width=0

	IFS_SAVE=$IFS; IFS="."
	mask=($*)
	IFS=$IFS_SAVE
	test -n "$mask" || return
	
	for octet in 0 1 2 3; do
		test "${mask[octet]}" -ge 0 -a "${mask[octet]}" -le 255 2>/dev/null \
			|| return
		for i in 128 192 224 240 248 252 254 255; do
			test ${mask[octet]} -ge $i && ((width++))
		done
	done
	
	test $width -ge 0 && echo $width
}

pfxlen2mask() {
	local i bit n=1 width=$1

	test -n "$width" || return 0

	for ((i=1; $i<=$width; i++)); do
		bit[$i]=1
	done; echo

	for o in 1 2 3 4; do
		octet[$o]=0
		for i in 128 64 32 16 8 4 2 1; do
			test ${bit[$n]:-0} -eq 1 && ((octet[$o] = ${octet[$o]} + $i))
			((n++))
		done
	done

	echo ${octet[1]}.${octet[2]}.${octet[3]}.${octet[4]}
}

is_iface_available () {
	test -z "$1" && return 1
	case $1 in
		ippp*|isdn*) return 0 ;;
		modem*|dsl*|ppp*) return 0 ;;
		vlan*) return 0 ;;
		sit*|gre*|ipip*) return 0 ;;
	esac
	test "${SCRIPTNAME%%-*}" = ifdown -a "$MODE" = hotplug && return 0
	test "${SCRIPTNAME%%-*}" = ifup -a "$BONDING_MASTER" = yes && return 0
	ip link list $1 &>/dev/null
}

is_iface_up () {
	test -z "$1" && return 1
	case "`LC_ALL=POSIX ip link show $1 2>/dev/null`" in
		*$1*UP*) ;;
		*) return 1 ;;
	esac
}

get_hwaddress () {
	test -z "$1" && return 1
	local a b=""
	for a in $(LC_ALL=POSIX ip link show $1 2>/dev/null ); do
		if [ "$b" = "link/ether" ] ; then
			echo $a
			break
		fi
		b=$a
	done
}

# This will echo the first address listed for the given interface.
get_ipv4address () {
	test -z "$1" && return 1
	local a b c
	while read a b c; do
		if [ "$a" = inet ] ; then
			break
		fi
	done < <(LC_ALL=POSIX ip -4 address list "$1" 2>/dev/null)
	test -z "$b" && return 1
	echo ${b%%/*}
}

convert_ipv4address_to_6to4 () {
	printf "2002:%02x%02x:%02x%02x::1\n" $(IFS=.; echo $1)
}

convert_6to4_to_ipv4address () {
	ADDR=$1
	PART_1=`expr $ADDR : '2002:\([^:]*\):[^:]*:'`
	PART_2=`expr $ADDR : '2002:[^:]*:\([^:]*\):'`
	if [ "$PART_1" = "" -o "$PART_2" = "" ]; then 
		echo $ADDR
	fi
	NORM_1=`printf "%04x" 0x$PART_1`
	NORM_2=`printf "%04x" 0x$PART_2`

	printf "::%u.%u.%u.%u" \
		0x${NORM_1:0:2} 0x${NORM_1:2:2} \
		0x${NORM_2:0:2} 0x${NORM_2:2:2}
}

# Loads one instance of the module 'bonding' with the name of the interface.
# Usage: load_bond $INTERFACE $BONDING_MODULE_OPTIONS
# !!! Don't use module option 'max_bonds'. It will break this code !!!
# !!!       Use one configuration file per bonding interface       !!!
load_bond() {
	local IF BIF NIF OPTS
	test -z "$1" && return 0
	IF=$1
	shift
	OPTS=$*
	BIF=`cd /sys/class/net; ls -d bond* 2>/dev/null`
	NIF=
	if ls /sys/class/net/$IF &>/dev/null; then
		return 0
	else
	   /sbin/modprobe -o $IF bonding $OPTS
		for a in `seq 100`; do
			for b in `cd /sys/class/net; ls -d bond* 2>/dev/null`; do
				for c in $BIF; do
					test "$b" = "$c" && continue 2
				done
				NIF=$b # "$NIF $b"
				break
			done
			test -n "$NIF" && break
			usleep 100000
		done
		if [ "$NIF" != "$IF" ] ; then
			nameif -r $IF $NIF
		fi
	fi
	if ls /sys/class/net/$IF &>/dev/null; then
		return 0
	else
		return 1
	fi
}

get_variable () {
	local line
	while read line; do
		eval $line
	done < <(grep "^[[:space:]]*$1" ifcfg-$2 2>/dev/null)
}

get_startmode () {
	local STARTMODE
	get_variable STARTMODE $1
	echo  "$STARTMODE"
}

get_slaves () {
	local ret=1
	for v in BONDING_SLAVE ETHERDEVICE TUNNEL_DEVICE \
	         TUNNEL_LOCAL_INTERFACE; do
		get_variable $v $1
		for vv in `eval echo \$\{\!$v\*\}`; do
			if [ -n "${!vv}" ] ; then
				echo -n "${!vv} "
				ret=0
			fi
			unset $vv
		done
		test $ret = 0 && return 0
	done
	return 1
}

get_ifplugd_priority () {
	unset HWD_CONFIG_0
	eval `getcfg -d . -f ifcfg- "$1"`
	local IFPLUGD_PRIORITY=0
	declare -i IFPLUGD_PRIORITY
	get_variable IFPLUGD_PRIORITY $HWD_CONFIG_0
	echo "$IFPLUGD_PRIORITY"
}

# We have to write status files per interface or per configuration for at least
# these reasons:
# 1) remember the used configuration if getcfg cannot get it after the device
#    has been unplugged
# 2) store ifup options while restarting the network (e.g. the choosen provider)
# 3) pass status information to smpppd to allow kinternet to show them to the
#    user.
# 4) control running ifup/down processes (ifdown has to stop a running ifup)
# To handle this cached information, there are the *_cached_config_data
# functions.

# write_cached_config_data <type> <data> <name> [PFX=<prefix>]
# needs at least 3 arguments
# - the type of data to write: config, options, state, ...
# - the data itself
# - the configuration or interface name
# - the file prefix is optional and must be given in the form PFX=<prefix>
#   (default prefix is 'if-'
# prints nothing
# You have to commit changes after writing with commit_cached_config_data()
write_cached_config_data () {
	touch $RUN_FILES_BASE/tmp/test 2>/dev/null || return 1
	local PFX FILE TMPFILE MODFILE
	test -n "$4" && eval $4
	: ${PFX:=if-}
	FILE=$RUN_FILES_BASE/$PFX$3
	MODFILE=$RUN_FILES_BASE/tmp/$PFX$3.$$                  # MODFILE
	TMPFILE=$RUN_FILES_BASE/tmp/$PFX$3.$$.tmp              # MODFILE
	test -f $MODFILE || cp $FILE $MODFILE 2>/dev/null
	FILE=$MODFILE                                       # MODFILE
	touch $FILE
	while IFS== read a b; do
		case $a in
			$1) ;;
			 *) echo "$a=$b" ;;
		esac
	done < <(cat $FILE) > $TMPFILE
	if [ -n "$2" ] ; then
		echo "$1=$2" >> $TMPFILE
	fi
	if [ -f $TMPFILE ] ; then
		mv $TMPFILE $FILE
	fi
}

# INTERFACE=`read_cached_config_data <type> <name> [PFX=<prefix>]`
# needs at least 2 arguments
# - the type of data to read: config, options, state, ...
# - the configuration or interface name
# - the file prefix is optional and must be given in the form PFX=<prefix>
#   (default prefix is 'if-'
# prints the wanted data
read_cached_config_data () {
	touch $RUN_FILES_BASE/tmp/test 2>/dev/null || return 1
	local PFX
	test -n "$3" && eval $3
	: ${PFX:=if-}
	if [ -r "$RUN_FILES_BASE/$PFX$2" ] ; then
		while IFS== read a b; do
			case $a in
				$1) echo "$b" ;;
				 *) ;;
			esac
		done < $RUN_FILES_BASE/$PFX$2
	fi
}

# delete_from_cached_config_data <type> [<data> [<name>]] [PFX=<prefix>]
# Deletes an entry "$1=$2" from all config data cache files.
# If there is a third argument, we delete it only from this configuration. All
# handled files that are empty after modification will be deleted.
# If $2 is empty then remove line $1=* from this ($3) or all configuration.
# If $1 is '*' it will remove all entries.
#
# !!! WIP !!!
# It currently works only on one file and 2nd and 3rd argument are mandatory
# !!! WIP !!!
#
# needs at least 1 argument
# - the type of data to delete: config, options, state, ...
# - optional the data itself
# - optional the configuration or interface name
# - the file prefix is also optional and must be given in the form PFX=<prefix>
#   (default prefix is 'if-'
# prints nothing
# You have to commit changes after deleting with commit_cached_config_data()
delete_from_cached_config_data () {
	touch $RUN_FILES_BASE/tmp/test 2>/dev/null || return 1
	local TYPE DATA PFX FILE TMPFILE MODFILE NAME
	TYPE=$1; shift
	if [ "$1" = "${1#PFX}" ] ; then
		DATA=$1; shift
	fi
	if [ "$1" = "${1#PFX}" ] ; then
		NAME=$1; shift
	fi
	test -n "$1" && eval $1
	: ${PFX:=if-}
#	for FILE in $RUN_FILES_BASE/$PFX${NAME:-*}; do   MODFILE
#		TMPFILE=${FILE##*/}                           MODFILE
#		TMPFILE=${FILE%$TMPFILE}tmp/$TMPFILE          MODFILE
	FILE=$RUN_FILES_BASE/$PFX$NAME                 # MODFILE
	MODFILE=$RUN_FILES_BASE/tmp/$PFX$NAME.$$          # MODFILE
	TMPFILE=$RUN_FILES_BASE/tmp/$PFX$NAME.$$.tmp      # MODFILE
	test -f $MODFILE || cp $FILE $MODFILE 2>/dev/null
   FILE=$MODFILE                                       # MODFILE
	touch $FILE
		if [ -s "$FILE" ] ; then
			while IFS== read a b; do
				case $a in
					$TYPE)
						if [ "$b" != "$DATA" -a -n "$DATA" ] ; then
							echo "$a=$b" 
						fi
						;;
					 *) echo "$a=$b" ;;
				esac
			done < <(cat $FILE) > $TMPFILE
		fi
		if [ -f $TMPFILE ] ; then
			mv $TMPFILE $FILE
		fi
		if [ ! -s $FILE ] ; then
			rm -Rf $FILE
		fi
#	done   MODFILE
}

# HWDESC NIX < <(grep_cached_config_data <type> <data> [PFX=<prefix>])
# needs 2 arguments:
# - the type of data to grep for: config, options, state, ...
# - the data itself
# - the file prefix is optional and must be given in the form PFX=<prefix>
#   (default prefix is 'if-'
# prints all matching configuration names in a single line
grep_cached_config_data () {
	touch $RUN_FILES_BASE/tmp/test 2>/dev/null || return 1
	local PFX
	test -n "$3" && eval $3
	: ${PFX:=if-}
	local restore_nullglob="$(shopt -p nullglob)"
	shopt -s nullglob
	for f in $RUN_FILES_BASE/$PFX*; do
		while IFS== read a b; do
			case $a in
				$1)
					if [ "$b" = "$2" ] ; then
						echo -n "${f#$RUN_FILES_BASE/$PFX} " 
					fi
					;;
			esac
		done < $f
	done
	eval $restore_nullglob
	echo
}

# Writing and deleting cached config data is always done in temporary files. To
# make this changes visible in the right file you must commit the changes. This
# helps to make file changes atomic.
commit_cached_config_data () {
	touch $RUN_FILES_BASE/tmp/test 2>/dev/null || return 1
	local PFX FILE MODFILE
	test -n "$2" && eval $2
	: ${PFX:=if-}
	FILE=$RUN_FILES_BASE/$PFX$1
	MODFILE=$RUN_FILES_BASE/tmp/$PFX$1.$$
	if [ -f $MODFILE ] ; then
		mv $MODFILE $FILE
	else
		rm -f $FILE
	fi
}

is_connected () {
	case `read_cached_config_data status $1` in
		connected) return 0 ;;
		connecting) return 0 ;; # might be wrong, test for link to
	esac
	return 1
}

has_link () {
	case `read_cached_config_data link $1` in
		yes) return 0 ;;
	esac
	return 1
}

# As i'm not completely sure that the following two  functions are unused i will
# now comment them out. Lets see if somebody complains.   c.zoz
# 
# # HWDESC IFACE < <(grep_cached_hwdesc_data <config>)
# # needs one argument:
# # - the configuration name
# # echos all "HWDESC INTERFACE MODE" triples which could match the given config
# grep_cached_hwdesc_data() {
# 	local _HWDESC _IFACE _MODE PFX
# 	: ${PFX:=hwdesc-}
# 	local restore_nullglob="$(shopt -p nullglob)"
# 	shopt -s nullglob
# 		for f in $RUN_FILES_BASE/$PFX*$1*; do
# 			_HWDESC="${f#$RUN_FILES_BASE/$PFX}"
# 			test -z "$_HWDESC" && continue
# 			_IFACE=`read_cached_config_data interface $_HWDESC PFX=$PFX`
# 			test -z "$_IFACE" && continue
# 			_MODE=`read_cached_config_data mode $_HWDESC PFX=$PFX`
# 			echo $_HWDESC $_IFACE $_MODE
# 		done
# 	eval $restore_nullglob
# }
# 
# # delete_chached_hwdesc_file <hwdesc> <iface>
# # needs 2 arguments
# # - the hardware description
# # - the interface
# # It will delete all file below $RUN_FILES_BASE that
# # - have a filename hwdesc-$HWDESC or
# # - contain a line 'interface=$INTERFACE'
# delete_cached_hwdesc_file() {
# 	local PFX _LINE _FILE
# 	: ${PFX:=hwdesc-}
# 	local restore_nullglob="$(shopt -p nullglob)"
# 	shopt -s nullglob
# 	for _FILE in $RUN_FILES_BASE/$PFX*; do
# 		if [ "${_FILE#$RUN_FILES_BASE/$PFX}" = "$1" ] ; then
# 			rm -Rf $_FILE
# 			continue
# 		fi
# 		while read _LINE; do
# 			if [ "$_LINE" = "interface=$2" ] ; then
# 				rm -Rf $_FILE
# 				continue 2
# 			fi
# 		done < $_FILE
# 	done
# 	eval $restore_nullglob
# }

# This function looks for interfaces which depend on the given interface. It
# prints a list with all depending interfaces. It returns 0 if there are
# depending interfaces and !=0 if not.
# Currently it checks only for vlan and bonding interfaces.
# FIXME: Add other types of interfaces that depend on others.
get_depending_ifaces() {
	local VLAN_PATH BOND_PATH DEP_IFACES DEP_VLANS DEP_BONDS BASE_IFACE
	VLAN_PATH="/proc/net/vlan"
	BOND_PATH="/proc/net/bonding"
	BASE_IFACE="$1"
	DEP_IFACES=""

	if [ -z "$BASE_IFACE" ]; then
		return 1
	fi

	if [ -d "$VLAN_PATH" ]; then
		DEP_VLANS=`cd "$VLAN_PATH"
			grep -lw "Device: *$BASE_IFACE" *`
		DEP_IFACES="$DEP_VLANS"
	fi

	if [ -d "$BOND_PATH" ]; then
		DEP_BONDS=`cd "$BOND_PATH"
			grep -lw "Slave Interface: *$BASE_IFACE" *`
		DEP_IFACES="$DEP_IFACES${DEP_BONDS:+ $DEP_BONDS}"
	fi

	if [ -z "$DEP_IFACES" ]; then
		return 1
	else
		echo "$DEP_IFACES"
		return 0
	fi
}
