#!/bin/bash
#
# /sbin/hwup
#
# Configuring hardware (Preliminary version)
# $Id: hwup 1256 2005-08-29 07:09:28Z zoz $
#

# /usr/bin/env
# set -x  -v
# echo --  --------------------------------------------------------------------

usage () {
	echo "Usage: hw{up,down,status} [<config>] <hwdesc> [-o <options>]"
	echo "Options are:"
	echo "    auto     : we were called from an automated process"
	echo "    hotplug  : like auto, special for hotplug (udev)"
	echo "    fast     : skip getcfg, works not for all subsystems"
	exit $R_USAGE
}

get_config_fast() {
	local DP
	set -- $(IFS="-"; echo $HWDESC)
	if [ "$2" = "devpath" ]; then
		HWD_BUSNAME=$1
		shift
	fi
	if [ "$1" = "devpath" ]; then
		HWD_BUSID=${2##*/}
		HWD_DEVICEPATH=${SYSFS}${2#${SYSFS}}
		DP="`cd -P $HWD_DEVICEPATH 2>/dev/null &&  pwd`"
		HWD_DEVICEPATH=${DP:-$HWD_DEVICEPATH}
		for cfg in hwcfg-*bus-${HWD_BUSNAME}-${HWD_BUSID}; do
		    if test -f $cfg; then
			HWD_CONFIG=${cfg##hwcfg-}
		    fi
		done 
		return 0
	else
		return 1
	fi
}

get_config_getcfg() {
	eval `/sbin/getcfg -d . -f hwcfg- -- $HWDESC 2>/dev/null`
	# This is a workaround for getcfg that does not get a devicepath from a
	# buspath or device link. 
	local devpath
	if [ -z "$HWD_DEVICEPATH" -a -n "$HWD_DEVPATH" ] ; then
		devpath=`cd -P ${SYSFS}${HWD_DEVPATH#$SYSFS}; pwd`
		if [ -d "$devpath" ] ; then
			eval `/sbin/getcfg -d . -f hwcfg- -- devpath-$devpath`
		fi
	fi
	# Normally we are interested only in the last bus. (It is not of
	# interest that below a scsi bus is a pci bus. Otherwise use
	# HWD_BUSNAME_<n>, HWD_BUSID_<n>.)
	if [ -n "$HWD_BUS_N" -a "$HWD_BUS_N" -gt 0 ] ; then
		eval export HWD_BUSNAME=\$HWD_BUSNAME_$((HWD_BUS_N-1))
		eval export HWD_BUSID=\$HWD_BUSID_$((HWD_BUS_N-1))
	fi
	HWD_CONFIG=$HWD_CONFIG_0
}

is_known_subsystem() {
	case "$1" in
		ccw)              return 0 ;;
		ccwgroup)         return 0 ;;
		ieee1394)         return 0 ;;
		input_device)     return 0 ;;
		macio)            return 0 ;;
		pci)              return 0 ;;
		pci_express)      return 0 ;;
		pcmcia)           return 0 ;;
		pcmcia_socket)    return 0 ;;
		pnp)              return 0 ;;
		scsi)             return 0 ;;
		scsi_host)        return 0 ;;
		usb)              return 0 ;;
		vio)              return 0 ;;
		static)           return 0 ;; # for static device configs
		*)                return 1 ;;
	esac
}

get_subsystem() {
	if is_known_subsystem $SUBSYSTEM; then
		echo $SUBSYSTEM
		return 0
	fi
	set -- $(IFS="-"; echo $HWDESC)
	if is_known_subsystem $1 ; then
		echo $1
		return 0;
	fi
	test "$2" == bus && shift
	if [ "$1" == bus ] && is_known_subsystem $2; then
		echo $2
		return 0;
	fi
	local devpath
	if [ -L ${SYSFS}${DEVPATH}/bus ] ; then
		devpath=${SYSFS}${DEVPATH}
	else
		test "$2" == devpath && shift
		if [ "$1" == devpath ] ; then
			shift
			devpath="`IFS=-; echo "$*"`"
			devpath=${devpath#$SYSFS}
		fi
	fi
	if [ -L ${SYSFS}${devpath}/bus ] ; then
		local bus
		bus="`cd -P ${SYSFS}${devpath}/bus; pwd`"
		bus=${bus##*/}
		if is_known_subsystem $bus; then
			echo $bus
			return 0
		fi
	fi
	local classpath=${devpath#/class/}
	if [ "$devpath" != "$classpath" ] ; then
		classpath=${classpath%%/*}
		if is_known_subsystem $classpath; then
			echo $classpath
			return 0
		fi
	fi
	if [ -n "$CONFIG" ] ; then
		if is_known_subsystem ${CONFIG%%-*}; then
			echo ${CONFIG%%-*}
			return 0
		fi
	fi
	return 1
}

# This checks for information that should be available for all kind of
# subsystems. If will not fail if some information is missing since the needs
# of each subsystem differ to much. That may change in future.
# The current implementation of this function is evil and ugly. Lets get rid of
# getcfg soon and write a proper function. But for SL10.0 its to late. (zoz)
get_basic_information() {
	if [ "$FAST" = yes ] ; then
		get_config_fast
	else	
		get_config_getcfg
	fi
	# I distinguish between these two cases just out of curiosity, when and
	# if HWD_DEVICEPATH differs from nonempty DEVPATH.
	if [ -z "$HWD_DEVICEPATH" -a -n "$DEVPATH" ] ; then
		HWD_DEVICEPATH=$SYSFS$DEVPATH
	elif [ "$HWD_DEVICEPATH" != "$SYSFS$DEVPATH" -a -n "$DEVPATH" ] ; then
		info_mesg "HWD_DEVICEPATH ($HWD_DEVICEPATH) and DEVPATH" \
		          "($SYSFS$DEVPATH) differ"
		HWD_DEVICEPATH=$SYSFS$DEVPATH
	fi
	# HWD_DEVTYPE is currently only used to call special scripts. So we
	# might eliminate it and use $SUBSYSTEM instead.
	if [ -z "$HWD_DEVTYPE" ] ; then
		HWD_DEVTYPE=$SUBSYSTEM
	fi
	# We might check the following variables:
	# HWD_BUSNAME
	# HWD_BUSID
	# HWD_ID         # in hwup-iucv
}

modprobe_modalias() {
	if [ -z "$MODALIAS" ]; then
		MODALIAS="`cat $HWD_DEVICEPATH/modalias 2>/dev/null`"
	fi
	if [ -z "$MODALIAS" ]; then
		err_mesg "No module alias available"
		return 1
	fi
	info_mesg "executing modprobe $MODALIAS"
	BL_LINE="`modprobe -nv $MODALIAS 2>/dev/null | tail -n 1`"
	BL_FILE=${BL_LINE##*/}
	BL_MODULE=${BL_FILE%.ko*}
	BL_PATTERN=${BL_MODULE//_/[_-]}
	if [ -n "$BL_PATTERN" ] \
	   && grep -qs "^ *$BL_PATTERN *$" /etc/hotplug/blacklist; then
		err_mesg "module '$BL_MODULE' is in blacklist."
		return 0
	fi
	if [ "$LOG_LEVEL" -lt 6 ] ; then
		modprobe -v $MODALIAS
		return $?
	else
		local message retval
		message="`modprobe -v $MODALIAS`"
		retval=$?
		if [ -n "$message" ] ; then
			mesg "$message"
			return $retval
		else
			test "$retval" != 0 && return $retval
			info_mesg "modules already loaded:" \
			          `modprobe --show-depends $MODALIAS \
                        	   | sed 's=^.*/\(.*\).ko=\1='`
		fi
	fi
}


R_INTERNAL=1      # internal error, e.g. no config or missing scripts
cd /etc/sysconfig/hardware || exit $R_INTERNAL
test -f ./config && . ./config
test -f scripts/functions && . scripts/functions || exit $R_INTERNAL

######################################################################
# Commandline parsing
#
# hw{up,down,status} [<config>] <hwdesc> [-o <options>]
SCRIPTNAME=${0##*/}
info_mesg $*
HWDESC=$1
case "$HWDESC" in ""|-h|*help*) usage; esac
shift
if [ -n "$1" -a "$1" != "-o" ] ; then
	CONFIG=$HWDESC
	HWDESC=$1
fi
shift
test "$1" = "-o" && shift
OPTIONS=$@
MODE=manual
HOTPLUG=no
while [ $# -gt 0 ]; do
	case $1 in
		auto)    MODE=auto ;;
		hotplug) MODE=auto
		         HOTPLUG=yes ;;
		fast)    FAST=yes;;
		*)       info_mesg "unknown option $1 ignored" ;;
	esac
	shift
done


######################################################################
# Determine subsystem
#
SUBSYSTEM=`get_subsystem`
if [ $? != 0 ] ; then
	err_mesg "Cannot handle subsystem '$SUBSYSTEM'"
	exit $R_USAGE
fi
test -r ./scripts/functions.$SUBSYSTEM && . ./scripts/functions.$SUBSYSTEM


######################################################################
# Now check if basic information is available. At first that info common to all
# subsystems. Then we can call the specific helper for the subsystem.
get_basic_information
if [ $? != 0 ] ; then
	err_mesg "Cannot get basic information"
	return $R_USAGE # FIXME add R_NO_INFO to functions.common
fi
if [ "`type -t get_${SUBSYSTEM}_information`" == function ] ; then
	get_${SUBSYSTEM}_information
	if [ $? != 0 ] ; then
		err_mesg "Cannot get specific information for '$SUBSYSTEM'"
		return $R_USAGE # FIXME add R_NO_INFO to functions.common
	fi
fi


######################################################################
# Get the right configuration file
if [ -z "$CONFIG" -a -n "$HWD_CONFIG" ] ; then
	CONFIG="$HWD_CONFIG"
fi
# This should go to get_static_information
if [ -z "$CONFIG" ] ; then
	case ${HWDESC%%-*} in
		static|boot) CONFIG=$HWDESC;;
	esac
fi
info_mesg "HWDESC='$HWDESC'"
info_mesg "CONFIG='$CONFIG'"


######################################################################
# Now source the configuration file
#
# First remove all variables starting with MODULE because there are variables
# starting with MODULE and unknown suffix in the config file. Otherwise
# environment variables would irritate the code that looks for all MODULE*
# variables.
unset ${!MODULE*}
if [ -n "$CONFIG" -a -r "./hwcfg-$CONFIG" ] ; then
	. "./hwcfg-$CONFIG"
fi
if [ "$LOG_LEVEL" -ge 6 ] ; then
	info_mesg ------------------------------------------------------------
	for var in MODE HOTPLUG SUBSYSTEM MODALIAS \
	           ${!HWD_*} ${!MODULE*} ${!STARTMODE*} \
	           ${!SCRIPTUP_*} ${!SCRIPTDOWN_*} ${!PRE_*} ${!POST_*}; do
		info_mesg ${var}=${!var}
	done
	if [ "`type -t show_${SUBSYSTEM}_information`" == function ] ; then
		show_${SUBSYSTEM}_information
	fi
	info_mesg ------------------------------------------------------------
fi


######################################################################
# What shell we do if there is no configuration data?
# - fail
# - get it automatically
# - ask the user
if [ "$SCRIPTNAME" = hwup -a \
     \( -z "$CONFIG" -o ! -r "hwcfg-$CONFIG" -o -n "$NODATA" \) ] ; then
#	test "$HOTPLUG" != yes && \
#		err_mesg "No configuration found for $HWDESC"
#	exit $R_NOCONFIG
	if [ "`type -t pre_init_${SUBSYSTEM}`" == function ] ; then
		info_mesg calling pre_init_${SUBSYSTEM}
		pre_init_${SUBSYSTEM}
	fi
	modprobe_modalias
	if [ "`type -t post_init_${SUBSYSTEM}`" == function ] ; then
		info_mesg calling post_init_${SUBSYSTEM}
		post_init_${SUBSYSTEM}
	fi
	exit 0
fi


######################################################################
# Check if we are supposed to initialize the device
#
# empty $STARTMODE means 'auto'
case "$STARTMODE" in
	off)
		mesg "$SCRIPTNAME: used configuration has STARTMODE=$STARTMODE."
		exit 0;
		;;
	manual)
		if [ "$MODE" != manual ] ; then
			mesg "$SCRIPTNAME: used configuration has" \
			     "STARTMODE=$STARTMODE, but we are called '$MODE'."
			exit 0;
		fi
		;;
esac

######################################################################
# execute individual prestart or predown scripts if available
#
if [ "$SCRIPTNAME" = hwup ] ; then
# NOTE: 'eval echo' in the next line is necessary to expand settings
# like PRE_UP_SCRIPT="~root/bin/foo"
	for PUS in `eval echo $PRE_UP_SCRIPT scripts/$PRE_UP_SCRIPT`; do
		if [ -x "$PUS" -a ! -d "$PUS" ] ; then
			info_mesg "executing additional start script $PUS" \
			          "'$CONFIG' $HWDESC ${OPTIONS:+-o $OPTIONS}"
			$PUS "$CONFIG" $HWDESC ${OPTIONS:+-o $OPTIONS}
		fi
	done
fi
if [ "$SCRIPTNAME" = hwdown ] ; then
# NOTE: 'eval echo' in the next line is necessary to expand settings
# like PRE_DOWN_SCRIPT="~root/bin/foo"
	for PDS in `eval echo $PRE_DOWN_SCRIPT scripts/$PRE_DOWN_SCRIPT`; do
		if [ -x "$PDS" -a ! -d "$PDS" ] ; then
			info_mesg "executing additional stop script $PDS" \
			          "'$CONFIG' $HWDESC ${OPTIONS:+-o $OPTIONS}"
			$PDS "$CONFIG" $HWDESC ${OPTIONS:+-o $OPTIONS}
		fi
	done
fi

######################################################################
# Call a specialized down script, depending on the event
#
# have a look at 'Call a specialized down script' above
if [ "$SCRIPTNAME" = hwdown ]; then
	# Call an event-specific script if one exists
	if [ "$HWD_DEVTYPE" ]; then
		SCRIPT_DOWN=`eval echo \\$SCRIPTDOWN_$HWD_DEVTYPE`
	fi
	# Call a generic script if no event-specific exists
	if [ -z "$SCRIPT_DOWN" ]; then
		SCRIPT_DOWN=`eval echo \\$SCRIPTDOWN`
	fi
	if test -n "$SCRIPT_DOWN" && test -x "./scripts/$SCRIPT_DOWN" ; then
		info_mesg "Calling scripts/$SCRIPT_DOWN '$CONFIG'" \
		          "$HWDESC ${OPTIONS:+-o $OPTIONS}"
		./scripts/$SCRIPT_DOWN "$CONFIG" $HWDESC ${OPTIONS:+-o $OPTIONS}
	fi
fi


######################################################################
# Call subsystem specific pre_intialisation function if there is one
#
if [ "`type -t pre_init_${SUBSYSTEM}`" == function ] ; then
	info_mesg calling pre_init_${SUBSYSTEM}
	pre_init_${SUBSYSTEM}
fi


######################################################################
# Load all modules
#
declare -i M=0 N=0
for MODVAR in ${!MODULE*}; do
	: MODVAR=$MODVAR
	# There is a silly design bug: $MODULE_OPTIONS and $MODULE_UNLOAD
	# must not be interpreted as additional MODULE*
	case "$MODVAR" in
		MODULE_OPTIONS*|MODULE_UNLOAD*) continue ;;
	esac
	INDEX=${MODVAR#MODULE}

	eval ML[$M]=\$MODULE$INDEX
	eval test -z "\${ML[$M]}" && continue
	eval MO[$M]=\$MODULE_OPTIONS$INDEX
	eval MU[$M]=\${MODULE_UNLOAD$INDEX:-$MODULE_UNLOAD}
	: $((M++))
done

if [ "$SCRIPTNAME" = hwup ] ; then
	while [ $N -lt $M ] ; do
		mesg "hwup: Loading module '${ML[$N]}'" \
		        "${MO[$N]:+with options '${MO[$N]}' }" \
		        "for device '$HWDESC'"
		/sbin/modprobe ${ML[$N]} ${MO[$N]}
		: $((N++))
	done
fi

if [ "$SCRIPTNAME" = hwdown -a "$HOTPLUG" != yes ] ; then

	# For many devices we can find the currently used driver name in sysfs.
	# 'getcfg' writes this name to $HWD_DRIVER. But sometimes the driver
	# name differs from the module name and we need the latter. Therefore
	# we test the name in $HWD_DRIVER. If it is not valid we use the module
	# name from the configuration file as fallback.
	# Since we need the sysfs driver path to look for a module link anyway,
	# we don't use $HWD_DRIVER currently.
	# FIXME: getcfg should provide $HWD_MODULE
	SYSFS_DRIVER_PATH=`ls -d /sys/bus/$HWD_BUSNAME/drivers/*/$HWD_BUSID \
	                      2>/dev/null`
	SYSFS_DRIVER_PATH=${SYSFS_DRIVER_PATH%/$HWD_BUSID}
	SYSFS_MODULE_PATH=$(cd $SYSFS_DRIVER_PATH 2>/dev/null &&
	                    cd module 2>/dev/null; pwd -P)
	SYSFS_MODULE=${SYSFS_MODULE_PATH##*/}
	if grep -qs "^\<$SYSFS_MODULE\>" /proc/modules; then
		echo SYSFS_MODULE=$SYSFS_MODULE
		while [ $N -lt $M ] ; do
			test "${ML[$N]}" = "$SYSFS_MODULE" && break
			: $((N++))
		done
		if [ $N -eq $M ] ; then
			ML[0]=$SYSFS_MODULE
			MU[0]=yes
			M=1
		fi
		N=0
	fi
	while [ $N -lt $M ] ; do
		if [ "${MU[$N]}" = no ] ; then
			mesg "hwdown: Module '${ML[$N]}' should not be" \
			        "unloaded for device '$HWDESC'"
			: $((N++))
			continue
		fi
		if /sbin/modprobe -r ${ML[$N]} ; then
			mesg "hwdown: Unloading module '${ML[$N]}'" \
			        "for device '$HWDESC'"
		else
			err_mesg "Could not unload module '${ML[$N]}'" \
			         "for device '$HWDESC'"
		fi
		: $((N++))
	done
fi

if [ "$SCRIPTNAME" = hwstatus ] ; then
	SYSFS_DRIVER_PATH=`ls -d /sys/bus/$HWD_BUSNAME/drivers/*/$HWD_BUSID \
	                      2>/dev/null`
	SYSFS_DRIVER_PATH=${SYSFS_DRIVER_PATH%/$HWD_BUSID}
	SYSFS_MODULE_PATH=$(cd $SYSFS_DRIVER_PATH 2>/dev/null &&
	                    cd module 2>/dev/null; pwd -P)
	SYSFS_MODULE=${SYSFS_MODULE_PATH##*/}
	echo DRIVER=${SYSFS_DRIVER_PATH##*/}
	if lsmod | grep -qs "\<$SYSFS_MODULE\>"; then
		echo MODULE=${SYSFS_MODULE}
	fi
	: # to be implemented
fi


######################################################################
# Call subsystem specific post_intialisation function if there is one
#
if [ "`type -t post_init_${SUBSYSTEM}`" == function ] ; then
	info_mesg calling post_init_${SUBSYSTEM}
	post_init_${SUBSYSTEM}
fi


######################################################################
# Call a specialized up script, depending on the event
#
# have a look at 'Call a specialized down script' above
if [ "$SCRIPTNAME" = hwup ]; then
	# Call an event-specific script if one exists
	if [ "$HWD_DEVTYPE" ]; then
		SCRIPT_UP=`eval echo \\$SCRIPTUP_$HWD_DEVTYPE`
	fi
	# Call a generic script if no event-specific exists
	if [ -z "$SCRIPT_UP" ]; then
		SCRIPT_UP=`eval echo \\$SCRIPTUP`
	fi
	if test -n "$SCRIPT_UP" && test -x "./scripts/$SCRIPT_UP" ; then
		info_mesg "Calling scripts/$SCRIPT_UP '$CONFIG'" \
		          "$HWDESC ${OPTIONS:+-o $OPTIONS}"
		./scripts/$SCRIPT_UP "$CONFIG" $HWDESC ${OPTIONS:+-o $OPTIONS}
	fi
fi

######################################################################
# execute individual poststart or postdown scripts if available
#
if [ "$SCRIPTNAME" = hwup ] ; then
# NOTE: 'eval echo' in the next line is necessary to expand settings
# like POST_UP_SCRIPT="~root/bin/foo"
	for PUS in `eval echo $POST_UP_SCRIPT scripts/$POST_UP_SCRIPT`; do
		if [ -x "$PUS" -a ! -d "$PUS" ] ; then
			info_mesg "executing additional start script $PUS" \
			          "'$CONFIG' $HWDESC ${OPTIONS:+-o $OPTIONS}"
			$PUS "$CONFIG" $HWDESC ${OPTIONS:+-o $OPTIONS}
		fi
	done
fi
if [ "$SCRIPTNAME" = hwdown ] ; then
# NOTE: 'eval echo' in the next line is necessary to expand settings
# like POST_DOWN_SCRIPT="~root/bin/foo"
	for PDS in `eval echo $POST_DOWN_SCRIPT scripts/$POST_DOWN_SCRIPT`; do
		if [ -x "$PDS" -a ! -d "$PDS" ] ; then
			info_mesg "executing additional stop script $PDS" \
			          "'$CONFIG' $HWDESC ${OPTIONS:+-o $OPTIONS}"
			$PDS "$CONFIG" $HWDESC ${OPTIONS:+-o $OPTIONS}
		fi
	done
fi

