#!/bin/bash

# Network interface configuration
#
# Copyright (c) 2002-2006 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 1491 2006-06-02 11:51:44Z zoz $
#

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

NETWORK_RUNFILE="$RUN_FILES_BASE/network"
STAMPFILE_STUB="$RUN_FILES_BASE/new-stamp-"
NETWORKMANAGER_BIN=/usr/sbin/NetworkManager
NM_ONLINE_BIN=/usr/bin/nm-online
NM_DISPATCHER_BIN=/usr/sbin/NetworkManagerDispatcher
DHCDBD_BIN=/usr/sbin/dhcdbd

#
# 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
	test "${SCRIPTNAME%%-*}" = ifup -a "$BRIDGE" = 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 module 'bonding' if not already loaded.
# Creates a new bonding master interface and sets its options.
# Usage: load_bond $INTERFACE $BONDING_MODULE_OPTIONS
# Module option 'max_bonds' will be ignored. Use one configuration file per
# bonding interface instead.
load_bond() {
	local NIF OPT OPT_NAME OPT_VALUE IF=$1
	test -z "$IF" && return 0
	shift
	if [ -d /sys/class/net/$IF -a ! -d /sys/class/net/$IF/bonding ] ; then
		return 1 # Iface exists but of another type
	fi
	if [ ! -r /sys/class/net/bonding_masters ] ; then
	   /sbin/modprobe bonding
		# If we add module option max_bonds=0 in the modprobe command above then
		# we may skip the following lines in this if-fi block.
		for a in `seq 33`; do
			test -r /sys/class/net/bonding_masters && break
			usleep 300000
		done
		NIF=`cat /sys/class/net/bonding_masters`
		if [ -n "$NIF" -a "$NIF" != "$IF" ] ; then
			nameif -r $IF $NIF
		fi
	fi
	if [ ! -d /sys/class/net/$IF/bonding ] ; then
		echo "+$IF" > /sys/class/net/bonding_masters
	fi
	for a in `seq 33`; do
		test -d /sys/class/net/$IF/bonding && break
		usleep 300000
	done
	if [ ! -d /sys/class/net/$IF/bonding ] ; then
		return 1
	fi
	# Set options
	sleep 1
	for OPT in $*; do
		read OPT_NAME OPT_VALUE < <(IFS==; echo $OPT)
		if [ "$OPT_NAME" == max_bonds ] ; then
			err_mesg "Don't use option max_bonds."
			continue
		fi
		if [ ! -w /sys/class/net/$IF/bonding/$OPT_NAME ] ; then
			err_mesg "There is no option '$OPT_NAME' for interface '$IF'."
			echo "-$IF" > /sys/class/net/bonding_masters
			return 1 # or continue? I guess its better to fail completely
		fi
		if ! echo "$OPT_VALUE" > /sys/class/net/$IF/bonding/$OPT_NAME ; then
			err_mesg "Option '$OPT_NAME' of interface '$IF' cannot be set to" \
			         "'$OPT_VALUE'."
			echo "-$IF" > /sys/class/net/bonding_masters
			return 1 # or continue? I guess its better to fail completely
		fi
	done
	return 0
}

# Removes a bonding master interface
# Usage: remove_bond $INTERFACE
remove_bond () {
	local IF=$1
	if [ ! -d /sys/class/net/$IF ] ; then
		return 0 # Interface does not exist; nothing to do
	fi
	if [ ! -d /sys/class/net/$IF/bonding ] ; then
		return 1 # Interface is not a bonding master
	fi
	ip link set down dev $1
	echo "-$IF" > /sys/class/net/bonding_masters
}

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"`
	if [ -z "$HWD_CONFIG_0" -a -r $RUN_FILES_BASE/config-$1 ] ; then
		# If the interface has gone we cannot always get configuration
		# name via getcfg. Therefore we use the stored one as fallback.
		read HWD_CONFIG_0 x < $RUN_FILES_BASE/config-$1
	fi
	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-}
	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
}

# 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 -lws "Device: *$BASE_IFACE" *`
		DEP_IFACES="$DEP_VLANS"
	fi

	if [ -d "$BOND_PATH" ]; then
		DEP_BONDS=`cd "$BOND_PATH"
			grep -lws "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
}

nm_running () {
	local MSG RET
	test -x "$NETWORKMANAGER_BIN" || return
	MSG=`checkproc $NETWORKMANAGER_BIN 2>&1`
	RET=$?
	info_mesg "$MSG"
	return $RET
}

netcontrol_running() {
	test -f $NETWORK_RUNFILE
}

