#!/bin/sh
# Check the link status of an ethernet interface
# This script should be installed in /etc/network/if-pre-up.d/
#
# This script can be disabled by defining DO_CABLETEST as "no"
# in /etc/default/network-test
#
#
# You can use this script to solve bug #120382
# ('ifup should (optionally) check for link before configuring the interface.')
# if you configure ABORT_NO_LINK to 'yes' in /etc/default/network-test
# since this will make the script abort if the interface does not have
# any link.
#
# Note that if you set ABORT_NO_LINK to 'yes' and the Ethernet interface
# does not have a link, the script will abort and ifupdown will *not*
# mark the interface as configured.
#
# It can also be used as a standalone script by setting up
# its environment:
#    IFACE=eth0  check-network-cable
#
#   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
#
# You can also find a copy of the GNU General Public License at
# http://www.gnu.org/licenses/licenses.html#TOCLGPL
#

# Defaults
rc="/etc/default/network-test"

ETHTOOL=/sbin/ethtool
alt_et=/usr/sbin/ethtool
[ -x "$ETHTOOL" ] || [ ! -x "$alt_et" ] || ETHTOOL=$alt_et
MIITOOL=/sbin/mii-tool
IPTOOL=/sbin/ip

DO_SYSLOG=yes
ABORT_NO_LINK=no

# Read system configuration file
[ -r "$rc" ] && . "$rc"

# Check if the test is disabled by the administrator
if [ "$DO_CABLETEST" = "no" ]; then
    exit 0
fi

if [ "$DO_SYSLOG" = yes ]; then
	OUTPUT="logger -i -p daemon.err -s"
else
	OUTPUT=echo
fi

# Set our locale environment, just in case any of the tools get translated
LC_ALL=C
export LC_ALL

check_status_miitool() {
	local status=0

	if $MIITOOL "$IFACE" 2>&1 | grep -q "no link" ; then
		status=1
	fi
	return $status
}

check_status_ethtool() {
	local status=0
	local LINK="$($ETHTOOL "$IFACE" 2>&1 | grep "Link detected" 2>/dev/null || :)"

	# If ethtool fails to print out the link line we break off
	# notice that ethtool cannot get the link status out of all
	# possible network interfaces
	[ -n "$LINK" ] || return 1
	if ! echo $LINK | grep -q "Link detected: yes" ; then
		status=1
	fi
	return $status
}

check_status_iplink() {
	local status=0
        local info=""

	[ -x "$IPTOOL" ] || return 0
	info=$($IPTOOL link show "$IFACE" up 2>&1)
	if [ -z "$info" ] ; then
		status=1
	fi
	return $status
}

# Status check function for all types of interfaces
check_status () {
	local status=0 myid=$(id -u)

        $IPTOOL link show "$IFACE" >/dev/null 2>&1 || {
		$OUTPUT "ERROR: Interface $IFACE does not seem to be present" \
			"in the system"
		return 0
	}

        check_status_iplink || status=$?
	[ $status -eq 0 ] ||
		$OUTPUT "WARNING: Initialising interface $IFACE which does" \
			"not have a link"
	return $status
}


# Status check function for Ethernet interfaces
check_ethernet_status() {
	local status=0 myid=$(id -u)

        $IPTOOL link show "$IFACE" >/dev/null 2>&1 || {
		$OUTPUT "ERROR: Interface $IFACE does not seem to be present" \
			"in the system"
		return 0
	}
	# For Ethernet interfaces use ethtool if installed (preferable to mii-tool)
	# If none are installed (or not running as root) we will test using
	# 'ip link show'
	if [ -x "$ETHTOOL" ] && [ $myid -eq 0 ]; then
		check_status_ethtool || status=$?
	elif [ -x "$MIITOOL" ] && [ $myid -eq 0 ]; then
		check_status_miitool || status=$?
	else
		check_status_iplink || status=$?
	fi
	[ $status -eq 0 ] ||
		$OUTPUT "WARNING: Initialising interface $IFACE which does" \
			"not have a link"
	return $status
}

# Status check function for Bond interfaces
check_bond_status() {
	local status=1 inf slaves slave_iface

	slaves="/sys/class/net/$IFACE/bonding/slaves"

	[ -e "$slaves" ] || return 0
	# Loop over the list of slaves
	while read slave_ifaces; do
		for inf in $slave_ifaces; do
			# Use ":" command to silence slaves. Run in subshell to preserve IFACE
			(OUTPUT=: IFACE="$inf" check_status); status=$?
			# One functional slave will suffice
			[ $status -ne 0 ] || return 0
		done
	done <$slaves
	$OUTPUT "WARNING: Initialising bond $IFACE which does not have link" \
		"on any slave"
	return $status
}

[ "$IFACE" ] || {
    $OUTPUT "ERROR: Variable IFACE not set in environment"
    exit 1
}

# Check our IFACE name, run the status check depending on the type of interface
case $IFACE in
	en* | eth*)
		check_ethernet_status || [ "$ABORT_NO_LINK" != "yes" ] || exit 1
		;;
	bond*)
		check_bond_status || [ "$ABORT_NO_LINK" != "yes" ] || exit 1
		;;
        lo*)    ;; # It does not make sense to check loopback links
        --all)  ;; # We do not know how to handle this case (yet)
	*)
		check_status || [ "$ABORT_NO_LINK" != "yes" ] || exit 1
		;;
esac
