#!/bin/bash
# vim:ts=4
#
# update-modules script originally based on Debian's version.
# This script will update all the fun config files in /etc for
# kernel modules.
#
# For 2.4 (and older) kernels we have:
# /etc/modules.conf
# /etc/modules.d/
# /etc/modules.devfs
#
# For 2.6+ kernels we have:
# /etc/modprobe.conf
# /etc/modprobe.d/
# /etc/modprobe.devfs


argv0=${0##*/}
source /etc/init.d/functions.sh || {
	echo "${argv0}: Could not source /etc/init.d/functions.sh!" 1>&2
	exit 1
}
umask 022
esyslog() { :; }

if [[ ${EUID} != "0" ]] ; then
	eerror "You must be root to do this" 1>&2
	exit 2
fi

[[ ${argv0} == "modules-update" ]] && ewarn "Please run 'update-modules' from now on; 'modules-update' is going away"


#
# Setup some variables
#

CFG_OLD_FILE="/etc/modules.conf"
CFG_OLD_DIR="/etc/modules.d"
CFG_OLD_DEVFS="/etc/modules.devfs"

CFG_NEW_FILE="/etc/modprobe.conf"
CFG_NEW_DIR="/etc/modprobe.d"
CFG_NEW_DEVFS="/etc/modprobe.devfs"

HEADER="### This file is automatically generated by update-modules"
FULLHEADER="${HEADER}
#
# Please do not edit this file directly. If you want to change or add
# anything please take a look at the files in @MODDIR@ and read
# the manpage for update-modules(8).
#
"


#
# Parse command-line
#

VERBOSE=0
DEBUG=0
FORCE="false"
BACKUP="false"
GENERATE_DEVFS="false"
ASSUME_KV=
while [[ -n $1 ]] ; do
	case $1 in
		-f|--force|force)  FORCE="true";;
		-b|--backup)       BACKUP="true";;
		-D|--devfs)        GENERATE_DEVFS="true";;
		--assume-kernel=*) ASSUME_KV=${1#*=};;
		-v|--verbose)      ((++VERBOSE));;
		-d|--debug)        ((++DEBUG));;
		-V|--version)      exec cat /etc/gentoo-release;;
		-h|--help)
			cat <<-EOF
			Usage: update-modules [options]

			Options:
			 --assume-kernel=KV  Assume the kernel is at least version KV
			 -b, --backup        Backup existing config files (add .old ext)
			 -f, --force         Force execution in face of bad things
			 -D, --devfs         Force generation of devfs config files
			 -v, --verbose       Be a bit more verbose in what we do
			 -d, --debug         Helpful debug output
			 -V, --version       Dump version info
			 -h, --help          This help screen, duh
			EOF
			exit 0
			;;
		*)
			eerror "Error: I don't understand $1"
			exit 1
			;;
	esac
	shift
done

[[ ${DEBUG} -gt 0 ]] && set -x

vewarn() { [[ ${VERBOSE} -gt 0 ]] && ewarn "$*" ; return 0 ; }

if ! ${GENERATE_DEVFS} && [[ -d /etc/devfs.d ]] ; then
	GENERATE_DEVFS="true"
fi

if type -p modprobe.old > /dev/null || \
   [[ $(modprobe -V 2>/dev/null) == "modprobe version"* ]]
then
	GENERATE_OLD="true"
else
	GENERATE_OLD="false"
fi

# Set kernel version, either from --assume-kernel or uname -r
KV=${ASSUME_KV:-$(uname -r)}
if [[ $(KV_to_int ${KV}) -ge "$(KV_to_int 2.5.48)" ]] ; then
	KERNEL_2_6="true"
else
	KERNEL_2_6="false"
fi

# Reset the sorting order since we depend on it
export LC_ALL="C"


#
# Build list of config files to generate and verify none
# have been modified in any way
#

CFGFILES=""
if ${GENERATE_OLD} ; then
	CFGFILES="${CFGFILES} ${CFG_OLD_FILE}"
	${GENERATE_DEVFS} && CFGFILES="${CFGFILES} ${CFG_OLD_DEVFS}"
fi
if ${KERNEL_2_6} ; then
	CFGFILES="${CFGFILES} ${CFG_NEW_FILE}"
	${GENERATE_DEVFS} && CFGFILES="${CFGFILES} ${CFG_NEW_DEVFS}"
fi

for x in ${CFGFILES} ; do
	[[ -r ${x} ]] || continue

	if [[ $(sed -ne 1p "${x}") != "${HEADER}" ]] ; then
		ewarn "Warning: the current ${x} has not been automatically generated"

		if ${FORCE} ; then
			ewarn "--force specified, (re)generating file anyway"
		else
			eerror "Use \"update-modules force\" to force (re)generation"
			exit 1
		fi
	fi
done


#
#  Desc: backup a config file if need be and replace with new one
# Usage: backup <old config file to backup> <new config file to replace with>
#    Ex: backup /etc/modules.conf /etc/modules.conf.tempfile
#
backup() {
	if ${BACKUP} && [[ -e $1 ]] ; then
		mv -f "$1" "$1".old
	fi
	mv -f "$2" "$1"
}


#
#  Desc: Combine all config files in a dir and place output in a file
# Usage: generate_config <output config file> <config dir> <reference config dir> <silent>
#    Ex: generate_config /etc/modules.conf /etc/modules.d
#
generate_config() {
	local config=$1
	local moddir=$2
	local refdir=$3
	local silent=$4
	local tmpfile="${config}.$$"

	[[ -z ${silent} ]] && ebegin "Updating ${config}"

	echo "${FULLHEADER//@MODDIR@/${refdir:-${moddir}}}" > "${tmpfile}"

	for cfg in "${moddir}"/* ; do
		[[ -d ${cfg} ]] && continue
		[[ ! -r ${cfg} ]] && continue

		# Skip backup and RCS files; fixes bug 20597 (07 May 2004 agriffis)
		[[ ${cfg} == *~ || ${cfg} == *.bak || ${cfg} == *,v ]] && continue

		# If config file is found in the reference dir, then skip it
		[[ -n ${refdir} ]] && [[ -e ${refdir}/${cfg##*/} ]] && continue

		echo "### update-modules: start processing ${cfg}" >> "${tmpfile}"

		if [[ -x ${cfg} ]] ; then
			# $cfg can be executable; nice touch, Wichert! :)
			"${cfg}" >> "${tmpfile}"
		else
			cat "${cfg}" >> "${tmpfile}"
		fi

		echo >> "${tmpfile}"
		echo "### update-modules: end processing ${cfg}" >> "${tmpfile}"
		echo >> "${tmpfile}"
	done

	backup "${config}" "${tmpfile}"

	[[ -z ${silent} ]] && eend 0

	return 0
}


#
# Generate the old modules.conf file based upon all the snippets in
# modules.d.  Since modprobe doesnt handle modules.d, we need to gather
# the files together in modules.conf for it.
#

if [[ ! -d ${CFG_OLD_DIR} ]] ; then
	vewarn "Skipping ${CFG_OLD_FILE} generation (${CFG_OLD_DIR} doesn't exist)"

elif ! ${GENERATE_OLD} ; then
	vewarn "Skipping ${CFG_OLD_FILE} generation (prerequisites not satisfied)"

elif ! ( ${FORCE} || \
     [[ ! -e ${CFG_OLD_FILE} ]] || \
     is_older_than ${CFG_OLD_FILE} ${CFG_OLD_DIR} )
then
	vewarn "Skipping ${CFG_OLD_FILE} generation (file is newer than dependencies)"

else
	generate_config ${CFG_OLD_FILE} ${CFG_OLD_DIR}
fi


#
# Generate the new modprobe.conf file if possible.  What this entails is
# grabbing details from the old modprobe via the -c option and sticking
# it in the newer config file.  This is useful for backwards compat support
# and for packages that provide older style /etc/modules.d/ files but not
# newer style /etc/modprobe.d/ files.
#
# First we try to use the script `generate-modprobe.conf` from the
# module-init-tools and if that fails us, we try and generate modprobe.conf
# ourselves from the /etc/modules.d/ files.
#

if ! ${KERNEL_2_6} ; then
	vewarn "Skipping ${CFG_NEW_FILE} generation (not needed for 2.4 kernels)"

elif ! type -p generate-modprobe.conf > /dev/null ; then
	vewarn "Skipping ${CFG_NEW_FILE} generation (generate-modprobe.conf doesn't exist)"

elif ! ( ${FORCE} || \
     [[ ! -e ${CFG_NEW_FILE} ]] || \
     is_older_than ${CFG_NEW_FILE} ${CFG_OLD_DIR} ${CFG_NEW_DIR} )
then
	vewarn "Skipping ${CFG_NEW_FILE} generation (file is newer than dependencies)"

else

	generated_ok=0
	tmpfile="${CFG_NEW_FILE}.$$"

	#
	# First we try to use regular generate-modprobe.conf
	#
	if ${GENERATE_OLD} ; then
		# Make sure that generate-modprobe.conf can handle --assume-kernel
		# if we were called with it.
		if [[ -n ${ASSUME_KV} ]] && \
			! grep -qe --assume-kernel /sbin/generate-modprobe.conf ; then
			eerror "Error: update-modules called with --assume-kernel flag, but"
			eerror "generate-modprobe.conf doesn't understand it.  You need to"
			eerror "install >=module-init-tools-3.0-r2"
			exit 3
		fi

		ebegin "Updating ${CFG_NEW_FILE}"
		echo "${FULLHEADER//@MODDIR@/${CFG_NEW_DIR}}" > "${tmpfile}"
		if generate-modprobe.conf ${ASSUME_KV:+--assume-kernel=${KV}} \
		   >> "${tmpfile}" 2> "${tmpfile}.err"
		then
			backup "${CFG_NEW_FILE}" "${tmpfile}"
			eend 0
			generated_ok=1
		else
			[[ ${VERBOSE} -gt 0 ]] && cat "${tmpfile}.err"
			eend 1 "Warning: could not generate ${CFG_NEW_FILE}!"
		fi
	fi

	#
	# If the helper script failed, we fall back to doing it by hand
	#
	if [[ ${generated_ok} -eq 0 ]] ; then
		ebegin "Updating ${CFG_NEW_FILE} by hand"

		generate_config "${CFG_NEW_FILE}" "${CFG_OLD_DIR}" "${CFG_NEW_DIR}" 0
		echo "${FULLHEADER//@MODDIR@/${CFG_NEW_DIR}}" > "${tmpfile}"

		# Just use generate-modprobe.conf to filter compatible syntax
		if TESTING_MODPROBE_CONF=${CFG_NEW_FILE} \
		   generate-modprobe.conf ${ASSUME_KV:+--assume-kernel=${KV}} \
		   >> "${tmpfile}" 2> "${tmpfile}.err"
		then
			# we use mv here instead of backup_config() as the call to
			# generate_config() above already took care of the backup
			mv -f "${tmpfile}" "${CFG_NEW_FILE}"
			eend $?
		else
			[[ ${VERBOSE} -gt 0 ]] && cat "${tmpfile}.err"
			eend 1 "Warning: could not generate ${CFG_NEW_FILE}!"
		fi
	fi

	#
	# Now append all the new files ... modprobe will not scan /etc/modprobe.d/
	# if /etc/modprobe.conf exists, so we need to append /etc/modprobe.conf with
	# /etc/modprobe.d/* ... http://bugs.gentoo.org/145962
	#
	if [[ -e ${CFG_NEW_FILE} ]] ; then
		for cfg in "${CFG_NEW_DIR}"/* ; do
			[[ -d ${cfg} ]] && continue
			[[ ! -r ${cfg} ]] && continue

			# Skip backup and RCS files; fixes bug 20597 (07 May 2004 agriffis)
			[[ ${cfg} == *~ || ${cfg} == *.bak || ${cfg} == *,v ]] && continue

			echo >> "${CFG_NEW_FILE}"
			echo "### update-modules: start processing ${cfg}" >> "${CFG_NEW_FILE}"
			cat "${cfg}" >> "${CFG_NEW_FILE}"
			echo "### update-modules: end processing ${cfg}" >> "${CFG_NEW_FILE}"
		done
	fi

	rm -f "${tmpfile}" "${tmpfile}.err"

	#
	# Take care of generating /etc/modprobe.devfs if need be.
	#
	if ${GENERATE_DEVFS} && [[ -f ${CFG_OLD_DEVFS} ]] && \
	   ( [[ ! -e ${CFG_NEW_DEVFS} ]] || [[ ${CFG_OLD_DEVFS} -nt ${CFG_NEW_DEVFS} ]] || ${FORCE} )
	then
		ebegin "Updating ${CFG_NEW_DEVFS}"

		tmpfile="${CFG_NEW_DEVFS}.$$"
		tmpfile_old="${CFG_OLD_DEVFS}.$$"

		echo "${FULLHEADER/@MODDIR@/${CFG_NEW_DIR}}" > "${tmpfile_old}"
		cp -f "${tmpfile_old}" "${tmpfile}"
		gawk '$0 !~ /^[[:space:]]*include/ { print $0 }' \
			"${CFG_OLD_DEVFS}" >> "${tmpfile_old}"

		if TESTING_MODPROBE_CONF=${tmpfile_old} \
		   generate-modprobe.conf ${ASSUME_KV:+--assume-kernel=${KV}} \
		   >> "${tmpfile}" 2> "${tmpfile}.err"
		then
			echo >> "${tmpfile}"
			echo "include /etc/modprobe.conf" >> "${tmpfile}"
			backup "${CFG_NEW_DEVFS}" "${tmpfile}"
			eend 0
		else
			[[ ${VERBOSE} -gt 0 ]] && cat "${tmpfile}.err"
			eend 1 "Warning: could not generate ${CFG_NEW_DEVFS}!"
			rm -f "${tmpfile}"
		fi

		backup "${CFG_OLD_DEVFS}" "${tmpfile_old}"

		rm -f "${tmpfile_old}" "${tmpfile}" "${tmpfile}.err"
	fi
fi


#
# Call depmod to keep insmod from complaining that modules.conf is more
# recent then the modules.dep file.
#

grab_depfile() {
	# the modules.conf file has optional syntax:
	# depfile=/path/to/modules.dep
	local ret=""
	if [[ -e ${CFG_OLD_FILE} ]] ; then
		ret=$(sed -n -e '/^[[:space:]]*depfile=/s:.*=::p' ${CFG_OLD_FILE})
	fi
	[[ -z ${ret} ]] && ret="/lib/modules/${KV}/modules.dep"
	eval echo "${ret}"
}
depfile=$(grab_depfile)

if [[ -d ${depfile%/*} ]] ; then
	if [[ ${CFG_NEW_FILE} -nt ${depfile} ]] ; then
		arch=$(uname -m)
		ebegin "Updating modules.dep"
		for cfg in /lib/modules/${KV}/build /usr/src/linux-${KV} \
		           /lib/modules/${KV} /boot /usr/src/linux ""
		do
			cfg="${cfg}/System.map"
			for suffix in -genkernel-{${arch},'*'}-${KV} -${KV} "" ; do
				scfg=$(echo ${cfg}${suffix})
				scfg=${scfg%% *}
				[[ -f ${scfg} ]] && cfg=${scfg} && break 2
			done
			cfg=""
		done
		[[ -n ${cfg} ]] && cfg="-F ${cfg}"
		depmod -a ${cfg} ${KV}
		eend $?
		[[ ${VERBOSE} -gt 0 ]] && einfo "Ran: depmod -a ${cfg} ${KV}"
	fi
else
	vewarn "The dir '${depfile}' does not exist, skipping call to depmod"
fi

exit 0
