#!/bin/sh
# Copyright (c) 2012, Piotr Karbowski <piotr.karbowski@gmail.com>
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification, are
# permitted provided that the following conditions are met:
#
#    * Redistributions of source code must retain the above copyright notice, this list
#      of conditions and the following disclaimer.
#    * Redistributions in binary form must reproduce the above copyright notice, this list
#      of conditions and the following disclaimer in the documentation and/or other
#      materials provided with the distribution.
#    * Neither the name of the Piotr Karbowski nor the names of its contributors may be
#      used to endorse or promote products derived from this software without specific
#      prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
# THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
# OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE US
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

# This script suppose to preserve interfaces names.
# First it will run nameif to rename interfaces if configured in /etc/mactab
# Then it will dump known interfaces to new mactab file.
# Next it will parse old /etc/mactab and copy all interfaces which wasnt added in step two.
# On the end, it will replace /etc/mactab with the new one.

# Info: Step two will only care about eth*, wlan*, ath*, wifi* and ra* interfaces.

umask 077

if [ -n "${USER}" ] && [ "${USER}" != 'root' ]; then
	echo "You need root to run it." >&2
	exit 1
fi

command_exists() {
	command -v "${1}" >/dev/null 2>&1
}

in_comma_list() {
	# Check whatever $1 is in the comma separated list $2.
	local x
	local check_for="$1"
	case ",$2," in
		*,"${check_for}",*)
			return 0
		;;
	esac
	return 1
}

get_nic_macaddr() {
	# Try to get real mac address from nic $1.
	local nic="$1"
	local macaddr
	if command_exists ethtool; then
		macaddr="$(ethtool -P ${nic} 2>/dev/null)"
		macaddr="${macaddr##*: }"
		if [ -n "${macaddr}" ] && [ "${macaddr}" != 'not set' ]; then
			echo "${macaddr}"
			return 0
		fi
	fi
	if command_exists ip; then
		macaddr="$(ip link show dev ${nic} |grep permaddr)"
		macaddr="${macaddr##*permaddr }"
		if [ -n "${macaddr}" ]; then
			echo "${macaddr}"
			return 0
		fi
	fi
	# Fallback to get mac address from sysfs.
	cat "/sys/class/net/${nic}/address"
}

lockfile='/etc/mactab.settle-nics_lockfile'
while : ; do
	if ! test -f "${lockfile}" && ( set -o noclobber; echo "$$" > "${lockfile}") 2> /dev/null; then
		break
	else
		sleep 1
	fi	
done

case "$1" in
	--write-mactab)
		write_mactab='true'
		tmpfile="/etc/mactab.settle-nics_tmpfile.$$"
		rm -f "${tmpfile}"
	;;
	'')
		tmpfile='/dev/null'
	;;
	*)
		echo "Wrong argument!" >&2
		exit 1
	;;
	
esac

trap 'status="$?"; rm -f "${lockfile}"; test -f "${tmpfile}" && rm -f "${tmpfile}"; exit "${status}"' INT TERM EXIT

# If we do have configured nics but the configured names are already used by something else, rename it to temp name.
# I wish nameif or ifrename could be smart enough to do it itself...
inconf_nics=""
inconf_macs=""
if [ -f '/etc/mactab' ]; then
	while read nic macaddr _; do
		case "${nic}" in
			'#'*)
			;;
			*)
				inconf_nics="${inconf_nics},${nic}"
				inconf_macs="${inconf_macs},${macaddr}"
				if [ -e "/sys/class/net/${nic}" ]; then
					curr_macaddr="$(get_nic_macaddr ${nic})"
					if [ "${curr_macaddr}" != "${macaddr}" ]; then
						# Okey, so kernel added another NIC with name which we preserved for other NIC.
						echo "Looks like '${nic}' is preserved. renaming current '${nic}' to '${nic}_tmp' ..." >&2
						ip link set dev "${nic}" name "${nic}_tmp"
					fi
				fi
			;;
		esac
	done < '/etc/mactab'
fi

# Run nameif so all interfaces will be renamed to specified names.
if command_exists nameif && test -f /etc/mactab; then
	nameif
fi

# Arrr rite. by now nemaif should put nics in right order, if there is still any *_tmp nic, we shall assign proper, free device name to it.
for nic in /sys/class/net/*_tmp; do
	nic="${nic##*/}"
	if [ "${nic}" = '*_tmp' ]; then break; fi
	nic_basename="${nic%%_tmp}"
	nic_basename="${nic_basename%%[0-9]*}"
	i=0
	while [ "${i}" -le "128" ]; do
		if ! [ -e "/sys/class/net/${nic_basename}${i}" ] && ! in_comma_list "${nic_basename}${i}" "${inconf_nics}"; then
			echo "Renaming '${nic}' to '${nic_basename}${i}' ..." >&2
			ip link set dev "${nic}" name "${nic_basename}${i}"
			break
		fi
		i="$(($i+1))"
	done
done

if [ "${write_mactab}" != 'true' ]; then
	# We don't want write mactab thus there is no reason to check the network interfaces' mac addresses and so on...
	exit 0
fi

printf '%s\n' "# Generated by settle-nics from mdev-like-a-boss." >> "${tmpfile}"

# First get all the macs of current accessable nics
detected_nics=""
detected_macs=""
for i in /sys/class/net/*; do
	unset device macaddr
	device="${i##*/}"
	case "${device}" in
		*_tmp)
		;;
		eth[0-9]*|wlan[0-9]*|ath[0-9]*|wifi[0-9]*|ra[0-9]*)
			macaddr="$(get_nic_macaddr ${device} 2>/dev/null)"
			if [ -n "${macaddr}" ] && ! in_comma_list "${macaddr}" "${inconf_macs}" && ! in_comma_list "${device}" "${inconf_nics}"; then
				detected_nics="${detected_nics},${device}"
				detected_macs="${detected_macs},${macaddr}"
				printf '%-15s %s\n' "${device}" "${macaddr}" >> "${tmpfile}"
			fi
		;;
	esac
done

# Now lets parse current /etc/mactab so we no loose any configured but not available at this moment interface.
if [ -f '/etc/mactab' ]; then
	unset device macaddr
	while read device macaddr; do
		case "${device}" in
			'#'*)
			;;
			*)
				if [ -n "${macaddr}" ] && ! in_comma_list "${macaddr}" "${detected_macs}" && ! in_comma_list "${device}" "${detected_nics}"; then
					printf '%-15s %s\n' "${device}" "${macaddr}" >> "${tmpfile}"
				fi
			;;
		esac
	done < '/etc/mactab'
fi

mv "${tmpfile}" '/etc/mactab'
