#! /bin/bash
#
PATH="/sbin:/bin"

RULE_FILE=/etc/udev/rules.d/30-net_persistent_names.rules
STAMPFILE=/tmp/dummy_stamp

trap 'echo renamed > $STAMPFILE
      echo INTERFACE=$INTERFACE
      echo DEVPATH=${DEVPATH%/*}/$INTERFACE
      echo RENAMED=yes' EXIT

. /etc/sysconfig/network/config
. /etc/sysconfig/network/scripts/functions

# udev timeout is 180 sec, don't run into
if [ ${WAIT_FOR_INTERFACES:-20} -gt 170 ] ; then
	WAIT_FOR_INTERFACES=170
fi
declare -i LOOPDELAY=250000     # useconds (0.25sec => 4)
declare -i MAXLOOPS=$(( ${WAIT_FOR_INTERFACES:-20} * 4 ))
declare -i MAXIFNUM=1024        #
declare -i LOOPS=0              #
# Make sure, MAXLOOPS is positive and greater than 0 ($LOOPS)
[ $MAXLOOPS -gt 0 ] || MAXLOOPS=10

case "$DEBUG" in
EXTRA|udev|rename_netiface)
  dir=${RUN_FILES_BASE:-/dev/shm/sysconfig}
  /bin/mkdir -p $dir || dir=/tmp
  exec 2>>$dir/rename_netiface.$1-$2.$SEQNUM.log
  {
  	date +"START: %F %T.%N"
  	set -x
  	env
  	ip link show
	cat "$RULE_FILE"
  } >&2
;;
esac

error_exit() {
	local RET
	declare -i RET=$1
	if [ $RET -ne "$1" ] ; then
		RET=255
	else
		shift
	fi
	err_mesg "$@" 1>&2
	exit $RET
}

usage() {
	mesg "usage: $0 <oldname> [<newname>]" 1>&2
	error_exit $*
}

lock_rule_file()
{
	[ -d /dev/.udev/ ] || return 0
	RULE_LOCK="/dev/.udev/.lock-${RULE_FILE##*/}"

	declare -i retry=30
	while ! mkdir "$RULE_LOCK" 2> /dev/null; do
		if [ $retry -le 0 ]; then
			error_exit 2 "Cannot lock $RULE_FILE"
		fi
		sleep 1
		((retry--))
	done
}
unlock_rule_file()
{
	[ "$RULE_LOCK" ] || return 0
	rmdir $RULE_LOCK || return 0
	unset RULE_LOCK
}

check_if_name_is_free() {
	! grep -qs "^[^#].*rename_netiface %k $NEWNAME\"" $RULE_FILE
}

# Return 0 only if rule was successfully written (if needed of course)
write_rule() {
	check_if_name_is_free    || return 1
	{
	if [ "$PHYSDEVBUS" = ccwgroup ] ; then
		echo "SUBSYSTEM==\"net\", ACTION==\"add\"," \
		     "ENV{PHYSDEVPATH}==\"*$DEV_ID\"," \
		     "IMPORT=\"/lib/udev/rename_netiface %k $NEWNAME\"" \
		     >> $RULE_FILE
	elif [ "$NOMAC_HACK_APPLIED" = yes ] ; then
		echo "SUBSYSTEM==\"net\", ACTION==\"add\"," \
		     "ENV{ADDRESS}==\"$DEV_ID\"," \
		     "IMPORT=\"/lib/udev/rename_netiface %k $NEWNAME\"" \
		     >> $RULE_FILE
	elif [ "$BUS_ID" = yes ] ; then
		echo "SUBSYSTEM==\"net\", ACTION==\"add\"," \
		     "ENV{PHYSDEVPATH}==\"*$DEV_ID\"," \
		     "IMPORT=\"/lib/udev/rename_netiface %k $NEWNAME\"" \
		     >> $RULE_FILE
	else
		echo "SUBSYSTEM==\"net\", ACTION==\"add\"," \
		     "SYSFS{address}==\"$DEV_ID\"," \
		     "IMPORT=\"/lib/udev/rename_netiface %k $NEWNAME\"" \
		     >> $RULE_FILE
	fi
	} 1>&2

	info_mesg "New rule for $DEV_ID added." 1>&2

	case "$DEBUG" in
	EXTRA|udev|rename_netiface)
		cat "$RULE_FILE" 1>&2
	;;
	esac
	if [ "${RENAME_NETIFACE_RELOAD_UDEV:-yes}" = yes ] ; then
		/sbin/udevcontrol reload_rules 1>&2
	fi
	return 0
}

get_device_bus_id()
{
        if [ -z "$PHYSDEVPATH" -a -L "/sys/class/net/$1/device" ] ; then
                PHYSDEVPATH=`cd -P "/sys/class/net/$1/device" && echo "${PWD}"`
                PHYSDEVPATH=${PHYSDEVPATH#/sys}
        fi
        if [ -n "$PHYSDEVPATH" -a -d "/sys/$PHYSDEVPATH" ] ; then
                DEV_ID=${PHYSDEVPATH##*/}
        else
                DEV_ID=$2
        fi
}
get_device_mac_id()
{
        if [ -d "/sys/class/net/$1" -a -f "/sys/class/net/$1/address" ] ; then
                DEV_ID=`cat /sys/class/net/$1/address 2>/dev/null`
        else
                DEV_ID=$2
        fi
}
get_device_id()
{
        local opt
        case $1 in --bus) opt=$1 ; shift ;; --*) shift ;; esac

        case "$PHYSDEVBUS${opt}" in
        ccwgroup|ccwgroup--bus|pci--bus|*--bus)
                BUS_ID=yes
                get_device_bus_id "$@"
        ;;
        *)
                BUS_ID=no
                get_device_mac_id "$@"
        ;;
        esac
}

do_ifrename()
{
	local new="$1"
	local old="$2"
	test -n "$old" -a -n "$new" || return 1
	test "x$old" = "x$new"      && return 0

	ret=0
	if test "${RENAME_NETIFACE_USE_IP_LINK:-yes}" = yes ; then
		err=`ip link set name "$new" dev "$old" 2>&1` ; ret=$?
	else
		err=`nameif -r "$new" "$old" 2>&1` ; ret=$?
	fi
	test "$ret" != 0 -a -n "$err" && {
		debug "interface rename failed${err:+: $err}" >&2
		debug `ip link show dev "$old" 2>&1 ; ip link show dev "$new" 2>&1` 1>&2
	}
	return $ret
}


#
# OK, lets do it...
#
OLDNAME=$1
NEWNAME=$2
STAMPFILE=${STAMPFILE_STUB}no_iface

if [ "$FORCE_PERSISTENT_NAMES" != yes ] ; then
	info_mesg "No persistent names wanted" 1>&2
	exit 0
fi

test $# -gt 2      && usage 1 "too many arguments: $*"
test -z "$OLDNAME" && usage 2 "too less arguments"

declare -i OINDEX=`cat /sys/class/net/$OLDNAME/ifindex 2>/dev/null`
if [ -d "/sys/class/net/$OLDNAME" -a \
     -n "$OINDEX" -a $OINDEX -gt 0 ] ; then
	STAMPFILE=${STAMPFILE_STUB}${OINDEX}
else
	error_exit 3 "oldname $OLDNAME does not exist"
fi
echo virgin > $STAMPFILE

NAMEBASE=${OLDNAME%%[0-9]*}
case "$NAMEBASE" in
	eth|ath|wlan|ra|hsi|ctc|tr)
		: go on
		;;
	*)
		exit 0
		;;
esac
# Also check if interface is a vlan/bond/bridge interface
test -f "/proc/net/vlan/$OLDNAME"        && exit 0
test -f "/proc/net/bonding/$OLDNAME"     && exit 0
test -d "/sys/class/net/$OLDNAME/bridge" && exit 0
declare -i IFNUM=${OLDNAME##$NAMEBASE}

if [ -z "$NEWNAME" ] ; then
	#
	# We've been called without NEWNAME, that is without a persistent
	# interface name argument, and have to generate a new rule for the
	# current interface name provided via OLDNAME ($1).
	#
	# First we have to find a DEV_ID usable for the rule to match the
	# device. Default is to use a MAC address, except on s390 ccwgroup
	# where the devices are always bound to the bus or in case of all
	# zero MAC address, where we fallback to a bus id too.
	#
	# Next, we look for a network interface name that is still not used
	# as persistent name. First we try the name the interface currently
	# has. If this name is already used in a rule, then we increase the
	# interface name number and try again.
	#
	NEWNAME=$OLDNAME
	get_device_id "$OLDNAME"
	if [ -z "$DEV_ID" ] ; then
		error_exit 4 "no device id for $OLDNAME"
	fi
	if [ "${DEV_ID//0/}" = ":::::" ] ; then
		# Workaround for some drivers which don't request their
		# firmware before beeing set up and don't have a mac address
		# before firmware was loaded.
		ip link set up dev $OLDNAME
		ip link set down dev $OLDNAME
		# Do we have to wait some time?
		for i in 0 1 2 3 4 5 6 7 8 9; do
			get_device_id "$OLDNAME"
			info_mesg "waiting for a usefull mac address on $OLDNAME: $DEV_ID" 1>&2
			test "${DEV_ID//0/}" != ":::::" && break
			sleep 1
		done
		if [ "${DEV_ID//0/}" = ":::::" ] ; then
			# try to fallback to a (pci) bus id then
			get_device_id --bus "$OLDNAME"
		fi
	fi
	if [ -z "$DEV_ID" -o "${DEV_ID//0/}" = ":::::" ] ; then
		error_exit 5 "all zero mac address for $OLDNAME"
	fi

	#
	# when requested, advance using interface index * sleep factor
	# to increase the time between rule generations and thus also the
	# probability, that rules are in the "natural" kernel detection order.
	#
	# note: >> no guarantee that same rules are generated twice <<
	#
	if [ $((${RENAME_NETIFACE_ADVANCE:-0})) -gt 0 -a -n "$OINDEX" ] ; then
		usleep $((OINDEX * 1000 * $((${RENAME_NETIFACE_ADVANCE:-0}))))
	fi

	lock_rule_file
	while ! check_if_name_is_free; do
		while [ $IFNUM -lt $MAXIFNUM ] ; do
			((IFNUM++))
			NEWNAME=$NAMEBASE$IFNUM
			if [ "${RENAME_NETIFACE_PREFER_FREE:-no}" = yes ] ; then
				test ! -d "/sys/class/net/$NEWNAME" && \
				check_if_name_is_free && \
				do_ifrename "$NEWNAME" "$OLDNAME" || \
					continue
			fi
			continue 2
		done
		unlock_rule_file
		error_exit 6 "could not get a free persistent interface name " \
		             "for $OLDNAME ($DEV_ID)"
	done

	if write_rule ; then
		unlock_rule_file
		info_mesg "$OLDNAME: new persistent name for $DEV_ID is $NEWNAME" 1>&2
	else
		unlock_rule_file
		error_exit 8 "Name $NEWNAME for $DEV_ID is NOT persistent"
	fi
fi
if [ -z "$NEWNAME" ] ; then
	error_exit 7 "cannot get a valid new interface name"
fi

# Simply try to rename directly, because it will work in most cases
if do_ifrename "$NEWNAME" "$OLDNAME" ; then
	info_mesg "$OLDNAME -> $NEWNAME: immediate success" 1>&2
	INTERFACE=$NEWNAME
	exit 0
fi

# Generate a temporary interface name
TMPBASE="${NAMEBASE}_${IFNUM}"
[ ${#TMPBASE} -gt 10 ] && TMPBASE="tmp"
if [ -n "$OINDEX" -a $OINDEX -gt 0 ] ; then
    TMPNAME="${TMPBASE}-${OINDEX:0:$((15-${#TMPBASE}-1))}"
else
    TMPNAME="${TMPBASE}-${RANDOM:0:$((15-${#TMPBASE}-1))}"
fi

# Rename it to the temporary name.
LOOPS=0
while true ; do
    do_ifrename "$TMPNAME" "$OLDNAME" \
        && break

    if [ $MAXLOOPS -le $((LOOPS++)) ] ; then
        error_exit 10 \
        "cannot rename interface $OLDNAME to temporary name $TMPNAME"
    fi
    TMPNAME="${TMPBASE}-${RANDOM:0:$((15-${#TMPBASE}-1))}"
done

# Then try several times to rename it to new name
echo looping > $STAMPFILE
LOOPS=0
while [ $MAXLOOPS -ge $((LOOPS++)) ] ; do
	if do_ifrename "$NEWNAME" "$TMPNAME" ; then
		info_mesg "$OLDNAME -> $NEWNAME: success after $LOOPS loops" 1>&2
		INTERFACE=$NEWNAME
		exit 0
	fi
	usleep $LOOPDELAY
done

if do_ifrename "$OLDNAME" "$TMPNAME" ; then
	info_mesg "$OLDNAME -> $NEWNAME: NO success after $LOOPS loops. " \
	          "Interface again named $OLDNAME" 1>&2
else
	info_mesg "$OLDNAME -> $NEWNAME: NO success after $LOOPS loops. " \
	          "Interface now named $TMPNAME, because $OLDNAME was no " \
	          "longer available" 1>&2
	INTERFACE=$TMPNAME
fi
exit 0

