#!/bin/sh
#
# alsa-base control script
#
# At one time this script loaded and unloaded ALSA driver modules on
# bootup and shutdown.  This feature has since been removed.  To load
# modules on bootup use the hotplug or the discover package or add
# the names of the modules that should be loaded to /etc/modules.
#
# At one time this script restored and stored mixer levels on
# bootup and shutdown.  This feature has since been moved to the
# alsa-utils initscript.
#
# There is no longer any need to run this script on bootup or shutdown.
# It must remain in /etc/init.d/ for now, though, because certain
# other scripts expect to find it there.

set -e

MYNAME=/etc/init.d/alsa
PATH=/sbin:/usr/sbin:/bin:/usr/bin

# Default values of variables in /etc/default/alsa
force_unload_modules_before_suspend=""

[ -f /etc/default/alsa ] && . /etc/default/alsa

# $* MESSAGE
warn() { echo "${MYNAME}: Warning: $* " >&2 ; }

echo_procs_using_sound()
{
	echo $( \
		lsof +D /dev -F rt \
		| awk '/^p/ {pid=$1} /^t/ {type=$1} /^r0x(74|e)..$/ && type == "tCHR" {print pid}' \
		| cut -c 2- \
		| uniq \
	)
}

# $* [PID]...
echo_with_command_names()
{
	[ "$1" ] || return 0
	echo $( \
		ps --no-headers -o "%p %c" "$@" \
		| sed -e 's/\([0-9][0-9]*\) \(.*\)/\1(\2)/' \
	)
}

kill_procs_using_sound()
{
	procs_using_sound="$(echo_procs_using_sound)"
	if [ "$procs_using_sound" ] ; then
		echo -n "Terminating processes:"
		for attempt in 1 2 3 4 ; do
			echo -n " ${procs_using_sound}"
			kill $procs_using_sound || :
			sleep 1
			procs_using_sound="$(echo_procs_using_sound)"
			[ "$procs_using_sound" ] || break
		done
		# Either no more procs using sound or attempts ran out
		if [ "$procs_using_sound" ] ; then
			echo -n " (with SIGKILL:) ${procs_using_sound}"
			kill -9 $procs_using_sound || :
			sleep 1
		fi
		procs_using_sound="$(echo_procs_using_sound)"
		if [ "$procs_using_sound" ] ; then
			echo " (failed: processes still using sound devices: $(echo_with_command_names $procs_using_sound))."
			return 1
		fi
		echo "."
	fi
	return 0
}

# $* MODULE-NAME [MODULE-NAME]... | "all"
unload_modules()
{
	procs_using_sound="$(echo_procs_using_sound)"
	if [ "$procs_using_sound" ] ; then
		warn "Processes using sound devices: $(echo_with_command_names $procs_using_sound)."
	fi
	if [ -d /var/run/alsa ] ; then
		:> /var/run/alsa/modules-removed
	else
		warn "Directory /var/run/alsa is not present."
	fi
	echo -n "Unloading ALSA sound driver modules:"
	[ -d /proc/asound ] || { echo " (none loaded)." ; return 0 ; }
	echo_snd_modules_loaded()
	{
		lsmod \
		| sed -n -e 's/^\(snd[-_][^[:space:]]*\)[[:space:]].*/\1/p' \
		| sed -e 's/_/-/g'
	}
	for FSMBS in $* ; do
		MODULES_TO_REMOVE=""
		SND_MODULES_LOADED="$(echo_snd_modules_loaded)"
		case "$FSMBS" in
		  all)
			MODULES_TO_REMOVE="$SND_MODULES_LOADED"
			;;
		  snd_*|snd-*)
			FSMBS="$(echo "$FSMBS" | sed -e 's/_/-/g')"
			for M in $SND_MODULES_LOADED ; do
				if [ "$FSMBS" = "$M" ] ; then
					MODULES_TO_REMOVE="$FSMBS"
					break
				fi
			done
			;;
		esac
		[ "$MODULES_TO_REMOVE" ] || continue
		if [ -d /var/run/alsa ] ; then
			echo "$MODULES_TO_REMOVE" >> /var/run/alsa/modules-removed
		fi
		for M in $MODULES_TO_REMOVE ; do
			echo -n " ${M}"
			modprobe -r "$M" >/dev/null 2>&1 || :
		done
	done
	if [ -f /var/run/alsa/modules-removed ] ; then
		MODULES_STILL_LOADED="$(echo_snd_modules_loaded | grep -F -f /var/run/alsa/modules-removed)"
		MODULES_STILL_LOADED="$(echo $MODULES_STILL_LOADED)"
	else
		MODULES_STILL_LOADED=""
	fi
	if [ "$MODULES_STILL_LOADED" ] ; then
		echo " (failed: modules still loaded: ${MODULES_STILL_LOADED})."
		return 1
	else
		echo "."
		return 0
	fi
}

# $* MODULE-NAME [MODULE-NAME]... | "all"
force_unload_modules()
{
	kill_procs_using_sound || :
	unload_modules "$@" || return 1
	return 0
}

load_unloaded_modules()
{
	LUM_RETURNSTATUS=0
	MODULES_TO_LOAD=""
	[ -d /var/run/alsa ] || warn "Directory /var/run/alsa is not present."
	echo -n "Loading ALSA sound driver modules:"
	[ -f /var/run/alsa/modules-removed ] && MODULES_TO_LOAD="$(echo $(cat /var/run/alsa/modules-removed))"
	[ "$MODULES_TO_LOAD" ] || { echo " (none to reload)." ; return $LUM_RETURNSTATUS ; }
	echo -n " $MODULES_TO_LOAD"
	for MDL in $MODULES_TO_LOAD ; do
		modprobe $MDL || LUM_RETURNSTATUS=1
	done
	case "$LUM_RETURNSTATUS" in
	  0) echo "." ;;
	  *) echo " (failed)." ;;
	esac
	return $LUM_RETURNSTATUS
}

case "$1" in
  start|stop|restart)
	warn "The '$1' method is deprecated and will be removed."
	warn "Use the alsa-utils initscript instead."
	[ "$1" = start ] && [ -x /etc/init.d/alsa-utils ] && /etc/init.d/alsa-utils "$@"
	;;
  unload)
	unload_modules all || exit $?
	;;
  reload)
	EXITSTATUS=0
	unload_modules all || EXITSTATUS=1
	load_unloaded_modules || EXITSTATUS=1
	exit $EXITSTATUS
	;;
  force-unload)
	force_unload_modules all || exit $?
	;;
  force-reload)
	EXITSTATUS=0
	force_unload_modules all || EXITSTATUS=1
	load_unloaded_modules || EXITSTATUS=1
	exit $EXITSTATUS
	;;
  suspend)
	case "$force_unload_modules_before_suspend" in
	  ""|false) : ;;
	  all|true) force_unload_modules all || exit $? ;;
	  *) force_unload_modules $force_unload_modules_before_suspend || exit $? ;;
	esac
	;;
  resume)
	case "$force_unload_modules_before_suspend" in
	  ""|false) : ;;
	  *) load_unloaded_modules || exit $? ;;
	esac
	;;
  *)
	echo "Usage: $MYNAME {unload|reload|force-unload|force-reload|suspend|resume}" >&2
	exit 3
	;;
esac
