#!/bin/bash
#
#  Dynamic Kernel Module Support (DKMS) <dkms-devel@dell.com>
#  Copyright (C) 2003-2008 Dell, Inc.
#  by Gary Lerhaupt, Matt Domsch, & Mario Limonciello
#
#    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
#


# All of the variables we will accept from dkms.conf.
# Does not include directives
readonly dkms_conf_variables="CLEAN REMAKE_INITRD remake_initrd PACKAGE_NAME
       PACKAGE_VERSION POST_ADD POST_INSTALL POST_REMOVE PRE_BUILD
       PRE_INSTALL BUILD_EXCLUSIVE_KERNEL BUILD_EXCLUSIVE_ARCH
       build_exclude OBSOLETE_BY MAKE MAKE_MATCH MODULES_CONF
       modules_conf_array PATCH PATCH_MATCH patch_array BUILT_MODULE_NAME
       built_module_name BUILT_MODULE_LOCATION built_module_location
       DEST_MODULE_NAME dest_module_name MODULES_CONF_OBSOLETES
       DEST_MODULE_LOCATION dest_module_location
       modules_conf_obsoletes MODULES_CONF_ALIAS_TYPE
       modules_conf_alias_type STRIP strip MODULES_CONF_OBSOLETE_ONLY
       modules_conf_obsolete_only AUTOINSTALL"

# Some important regular expressions.  Requires bash 3 or above.
# Any poor souls still running bash 2 or older really need an upgrade.
readonly y_re='^(Y|y)'
readonly mv_re='^([^/]*)/(.*)$'
readonly rh_kernels='(debug|summit|smp|enterprise|bigmem|hugemem|BOOT|vmnix)'

#Areas that will vary between Linux and other OS's
_get_kernel_dir() {
    KVER=$1
    case ${current_os} in
       Linux)          DIR="/lib/modules/$KVER/build" ;;
       GNU/kFreeBSD)   DIR="/usr/src/kfreebsd-headers-$KVER/sys" ;;
    esac
    echo $DIR
}

_check_kernel_dir() {
    DIR=$(_get_kernel_dir $1)
    case ${current_os} in
       Linux)          test -e $DIR/include ;;
       GNU/kFreeBSD)   test -e $DIR/kern && test -e $DIR/conf/kmod.mk ;;
       *)              return 1 ;;
    esac
    return $?
}

# Run a command that we may or may not want to be detailed about.
invoke_command()
{
    # $1 = command to be executed using eval.
    # $2 = Description of command to run
    # $3 = 'background' if you want to run the command asynchronously.
    local exitval=0
    [[ $verbose ]] && echo -e "$1" || echo -en "$2..."
    if [[ $3 = background && ! $verbose ]]; then
	local exitval_file=$(mktemp_or_die $tmp_location/dkms.XXXXXX)
	(eval "$1" >/dev/null 2>&1; echo "exitval=$?" >> "$exitval_file") &
	while [[ -e $exitval_file && ! -s $exitval_file ]]; do
	    sleep 3
	    echo -en "."
	done
	. "$exitval_file"
	rm -f "$exitval_file"
    else
	eval "$1"; exitval=$?
    fi
    (($exitval > 0)) && echo -en "(bad exit status: $exitval)"
    echo -en "\n"
    return $exitval
}

error() (
    exec >&2
    echo -n $"Error! "
    for s in "$@"; do echo "$s"; done 
)

warn() (
    exec >&2
    echo -n "$Warning: "
    for s in "$@"; do echo "$s"; done
)

# Print an error message and die with the passed error code.
die() {
    # $1 = error code to return with
    # rest = strings to print before we exit.
    ret=$1
    shift
    error "$@"
    [[ $die_is_fatal = yes ]] && exit $ret || return $ret
}

mktemp_or_die() {
    local t
    t=$(mktemp "$@") && echo "$t" && return
    [[ $* = *-d* ]] && die 1 $"Unable to make temporary directory"
    die 1 "Unable to make temporary file."
}

show_usage()
{
    echo $"Usage: $0 [action] [options]"
    echo $"  [action]  = { add | remove | build | install | uninstall | match | autoinstall"
    echo $"               | mkdriverdisk | mktarball | ldtarball | mkrpm | mkkmp | mkdeb | status }"
    echo $"  [options] = [-m module] [-v module-version] [-k kernel-version] [-a arch]"
    echo $"              [-d distro] [-c dkms.conf-location] [-q] [--force] [--all]"
    echo $"              [--templatekernel=kernel] [--directive='cli-directive=cli-value']"
    echo $"              [--config=kernel-.config-location] [--archive=tarball-location]"
    echo $"              [--kernelsourcedir=source-location] [--no-prepare-kernel] [--no-initrd]"
    echo $"              [--binaries-only] [--source-only] [-r release (SuSE)] [--verbose]"
    echo $"              [--size] [--spec=specfile] [--media=floppy|iso|tar] [--legacy-postinst=0|1]"
}

VER()
{
    # $1 = kernel version string

    # Pad all numbers in $1 so that they have at least three digits, e.g.,
    #   2.6.9-1cvs200409091247 => 002.006.009-001cvs200409091247
    # The result should compare correctly as a string.

    echo $1 | sed -e 's:\([^0-9]\)\([0-9]\):\1 \2:g' \
		  -e 's:\([0-9]\)\([^0-9]\):\1 \2:g' \
		  -e 's:\(.*\): \1 :' \
		  -e 's: \([0-9]\) : 00\1 :g' \
		  -e 's: \([0-9][0-9]\) : 0\1 :g' \
		  -e 's: ::g'
}

# Figure out the correct module suffix for the kernel we are currently
# dealing with, which may or may not be the currently installed kernel.
set_module_suffix()
{
    # $1 = the kernel to base the module_suffix on
    kernel_test="${1-:$(uname -r)}"
    module_suffix=".ko"
    [[ $(VER $kernel_test) < $(VER 2.5) ]] && module_suffix=".o"
}

set_kernel_source_dir()
{
    # $1 = the kernel to base the directory on
    kernel_source_dir="$(_get_kernel_dir "$1")"
}

# A little test function for DKMS commands that only work on one kernel.
have_one_kernel() {
    (( ${#kernelver[@]} > 1 )) && \
	die 4 $"The action $1 does not support multiple kernel version" \
	$"parameters on the command line."
    [[ $all ]] && die 5 $"The action $1 does not support the --all" \
	$"parameter."
}

# Set up the kernelver and arch arrays.  You must have a 1:1 correspondence --
# if there is an entry in kernelver[$i], there must also be an entry in arch[$i]
# Note the special casing for the status action -- the status functions just
# report on what we already have, and will break with the preprocessing that
# this function provides.
setup_kernels_arches()
{
    # If all is set, use dkms status to fill the arrays
    if [[ $all && $1 != status ]]; then
	local i=0
	while read line; do
	    line=${line#*/}; line=${line#*/};
	    # (I would leave out the delimiters in the status output
	    #  in the first place.)
	    kernelver[$i]=${line%/*}
	    arch[$i]=${line#*/}
	    i=$(($i + 1))
	done < <(module_status_built "$module" "$module_version")
    fi

    # Set default kernel version and arch, if none set (but only --all isn't set)
    if [[ $1 != status ]]; then
	if [[ ! $kernelver && ! $all ]]; then
	    kernelver[0]=$(uname -r)
	    kernels_arches_default="yes"
	fi
	if [[ ! $arch ]]; then
	    kernelver_rpm=$(rpm -qf "/lib/modules/$kernelver" 2>/dev/null | \
		grep -v "not owned by any package" | grep kernel | head -n 1)
	    if ! arch[0]=$(rpm -q --queryformat "%{ARCH}" "$kernelver_rpm" 2>/dev/null); then
		arch[0]=$(uname -m)
		if [[ $arch = x86_64 ]] && \
		    grep -q Intel /proc/cpuinfo && \
		    ls $install_tree/$kernelver/build/configs \
		    2>/dev/null | grep -q "ia32e"; then
		    arch[0]="ia32e"
		fi
	    fi
	fi
    fi

    # If only one arch is specified, make it so for all the kernels
    if ((${#arch[@]} == 1 && ${#kernelver[@]} > 1)); then
	while ((${#arch[@]} < ${#kernelver[@]})); do
	    arch[${#arch[@]}]=$arch
	done
    fi

    # Set global multi_arch
    multi_arch=""
    local i=0
    for ((i=0; $i < ${#arch[@]}; i++)); do
	[[ $arch != ${arch[$i]} ]] && {
	    multi_arch="true"
	    break
	}
    done
}

do_depmod()
{
    # $1 = kernel version
    if [ "${current_os}" != "Linux" ] ; then
        return
    fi
    if [[ -f /boot/System.map-$1 ]]; then
	/sbin/depmod -a "$1" -F "/boot/System.map-$1"
    else
	/sbin/depmod -a "$1"
    fi
}

# This function is a little hairy -- every distro has slightly different tools
# and naming conventions for creating initial ramdisks.  It should probably
# be split out into one function per distro, with make_initrd left as a stub.
make_initrd()
{
    # $1 = kernel version
    # $2 = arch
    [[ $no_initrd ]] && return
    local mkinitrd kernel_file initrd_dir="/boot"
    for mkinitrd in dracut update-initramfs mkinitrd ''; do
	[[ $mkinitrd ]] && which "$mkinitrd" >/dev/null 2>&1 && break
    done

    # no mkinitrd? Just return.
    [[ $mkinitrd ]] || return 0

    # back up our current initrd
    echo $""
    [[ $2 = ia64 && -d /boot/efi/efi/redhat ]] && initrd_dir="/boot/efi/efi/redhat"
    # find out what the proper filename will be
    for initrd in "initrd-$1.img" "initramfs-$1.img" "initrd.img-$1" "initrd-$1" ''; do
	[[ $initrd && -f $initrd_dir/$initrd ]] && break
    done
    if ! [[ $initrd ]]; then
	# Return if we cannot find an initrd.
	warn $"Unable to find an initial ram disk that I know how to handle." \
	    $"Will not try to make an initrd."
	return 0
    fi
    echo $"Backing up $initrd to $initrd_dir/$initrd.old-dkms"
    cp -f "$initrd_dir/$initrd" "$initrd_dir/$initrd.old-dkms"
    echo $"Making new $initrd"
    echo $"(If next boot fails, revert to $initrd.old-dkms image)"

    if [[ $mkinitrd = dracut ]]; then
	invoke_command "$mkinitrd $1" "$mkinitrd" background
    elif [[ $mkinitrd = update-initramfs ]]; then
	invoke_command "$mkinitrd -u" "$mkinitrd" background
    elif $mkinitrd --version >/dev/null 2>&1; then
	invoke_command "$mkinitrd -f $initrd_dir/$initrd $1" "$mkinitrd" background
    elif [[ -e /etc/SuSE-release || -d /etc/SuSEconfig ]]; then
	for kernel_file in vmlinuz vmlinux ''; do
	    [[ $kernel_file && -f $initrd_dir/$kernel_file ]] && break
	done
	if [[ ! $kernel_file ]]; then
	    error $"Unable to find valid kernel file under " \
		$"$initrd_dir for kernel version $1" 
	    return 1;
	fi
	invoke_command "$mkinitrd -k $kernel_file-$1 -i $initrd" "$mkinitrd" background
    elif [[ -e /etc/debian_version ]]; then
	invoke_command "$mkinitrd -o $initrd_dir/$initrd $1" "$mkinitrd" background
    else
	echo $""
	echo $"Calling $mkinitrd (bad exit status 9 may occur)"
	invoke_command "$mkinitrd" "$mkinitrd" background
    fi
    return
}

# Grab our distro information from RPM-based distros.
distro_version_rpm()
{
    which rpm > /dev/null 2>&1 || { echo unknown; return; }
    local r wp ver dist

    for r in redhat-release sles-release suse-release ovs-release; do
	wp=$(rpm -q --whatprovides "$r") || continue
	ver=$(rpm -q --qf "%{version}\n" ${wp})
	case $r in
	    sles*) echo sles${ver};;
	    suse*) echo suse${ver};;
	    ovs*)  echo ovm${ver};;
	    redhat*)
		case $wp in
		    redhat*|sl*)
			ver=$(echo $ver | \
			sed -e 's/^\([[:digit:]]*\).*/\1/g')
			echo el${ver};;
		    centos*|enterprise*) echo el${ver};;
		    fedora*) echo fc${ver};;
		    *) echo unknown ;;
		esac
	       ;;
	    *) echo unknown;;
	esac
	return
    done
    echo unknown
}

# Grab distro information from LSB compliant distros.
# Falls back to distro_version_rpm if needed.
distro_version()
{
    # What distribution are we running?
    local LSB_DESCRIPTION DISTRIB_ID DISTRIB_RELEASE ver

    # try the LSB-provided strings first
    if [ -r /etc/lsb-release ]; then
	. /etc/lsb-release
    elif type lsb_release >/dev/null 2>&1; then
	DISTRIB_ID=$(lsb_release -i -s)
	DISTRIB_RELEASE=$(lsb_release -r -s)
    fi

    case ${DISTRIB_ID} in
	Fedora)     echo fc${DISTRIB_RELEASE} ;;
	RedHatEnterprise*|CentOS|ScientificSL)  # OEL also reports as such
	# format is 4.7, 5.3
	    ver=$(echo "${DISTRIB_RELEASE}" | \
		sed -e 's/^\([[:digit:]]*\).*/\1/g')
	    echo el${ver}
	    ;;
	SUSE*)
	    if [[ $(lsb_release -d -s) =~ Enterprise ]]; then
		echo sles${DISTRIB_RELEASE}
	    else
		echo suse${DISTRIB_RELEASE}
	    fi
	    ;;
	*)
	    if [[ ${DISTRIB_ID} && ${DISTRIB_RELEASE} ]]; then
		echo "${DISTRIB_ID}${DISTRIB_RELEASE}"
	    else
		distro_version_rpm
	    fi
	    ;;
    esac
}

override_dest_module_location()
{
    local orig_location="$1"
    [[ ${addon_modules_dir} ]] && echo "/${addon_modules_dir}" && return

    if [ "$current_os" = "GNU/kFreeBSD" ] ; then
	# Does not support subdirs, regardless of distribution
	echo "" && return
    fi

    case "$running_distribution" in
	fc[12345]) ;;
	el[1234]) ;;
	sles[123456789]) ;;
	suse[123456789]) ;;
	suse10\.[01]) ;;
	fc*) echo "/extra" && return ;;
	el*) echo "/extra" && return ;;
	ovm*) echo "/extra" && return ;;
	sles*) echo "/updates" && return ;;
	suse*) echo "/updates" && return ;;
	Ubuntu*) echo "/updates/dkms" && return ;;
	Debian*) echo "/updates/dkms" && return ;;
	*) ;;
    esac
    echo "$orig_location"
}

# Source a file safely.
# We want to ensure that the .conf file we source does not stomp all over
# parts of the environment we don't want them to.  This makes it so that
# it is harder to accidentally corrupt our environment.  conf files can
# still deliberatly trash the environment by abusing dkms_directive env
# variables or by crafting special values that will make eval do evil things.
safe_source() {
    # $1 = file to source
    # $@ = environment variables to echo out
    local to_source_file="$1"; shift
    declare -a -r export_envs=("$@")
    local tmpfile=$(mktemp_or_die)
    ( exec >"$tmpfile"
	. "$to_source_file" >/dev/null
	# This is really ugly, but a neat hack
	# Remember, in bash 2.0 and greater all variables are really arrays.
	for _export_env in "${export_envs[@]}"; do
	    for _i in $(eval echo \${!$_export_env[@]}); do
		eval echo '$_export_env[$_i]=\"${'$_export_env'[$_i]}\"'
	    done
	done

	# handle DKMS_DIRECTIVE stuff specially.
	for directive in $(set | grep ^DKMS_DIRECTIVE | cut -d = -f 2-3); do
	    directive_name=${directive%%=*}
	    directive_value=${directive#*=}
	    echo "$directive_name=\"$directive_value\""
	done
    )
    . "$tmpfile"
    rm "$tmpfile"
}

# Source a dkms.conf file and perform appropriate postprocessing on it.
# Do our best to not repeatedly source the same .conf file -- this can happen
# when chaining module installtion functions or autoinstalling.
read_conf()
{
    # $1 kernel version (required)
    # $2 arch (required)
    # $3 dkms.conf location (optional)

    local return_value=0
    local read_conf_file="$dkms_tree/$module/$module_version/source/dkms.conf"

    # Set variables supported in dkms.conf files (eg. $kernelver)
    local kernelver="$1"
    local arch="$2"
    set_kernel_source_dir "$1"


    # Find which conf file to check
    [[ $conf ]] && read_conf_file="$conf"
    [[ $3 ]] && read_conf_file="$3"

    [[ -r $read_conf_file ]] || die 4 $"Could not locate dkms.conf file." \
	$"File: $conf does not exist."

    [[ $last_mvka = $module/$module_version/$1/$2 && \
	$last_mvka_conf = $(readlink -f $read_conf_file) ]] && return


    # Clear variables and arrays
    for var in $dkms_conf_variables; do
	unset $var
    done

    # Source in the dkms.conf.
    # Allow for user-specified overrides in order of specificity.
    local _conf_file
    for _conf_file in "$read_conf_file" "/etc/dkms/$module.conf" \
        "/etc/dkms/$module-$module_version.conf" "/etc/dkms/$module-$module_version-$1.conf" \
        "/etc/dkms/$module-$module_version-$1-$2.conf"; do
        [ -e "$_conf_file" ] && safe_source "$_conf_file" $dkms_conf_variables
    done

    # Source in the directive_array
    for directive in "${directive_array[@]}"; do
	directive_name=${directive%%=*}
	directive_value=${directive#*=}
	export $directive_name="$directive_value"
	echo $"DIRECTIVE: $directive_name=\"$directive_value\""
    done

    # Set variables
    clean="$CLEAN"
    package_name="$PACKAGE_NAME"
    package_version="$PACKAGE_VERSION"
    post_add="$POST_ADD"
    post_build="$POST_BUILD"
    post_install="$POST_INSTALL"
    post_remove="$POST_REMOVE"
    pre_build="$PRE_BUILD"
    pre_install="$PRE_INSTALL"
    obsolete_by="$OBSOLETE_BY"

    # Set module naming/location arrays
    local index array_size=0 s
    for s in ${#BUILT_MODULE_NAME[@]} \
	${#BUILT_MODULE_LOCATION[@]} \
	${#DEST_MODULE_NAME[@]} \
	${#DEST_MODULE_LOCATION[@]}; do
	((s > array_size)) && array_size=$s
    done
    for ((index=0; index < array_size; index++)); do
	# Set values
	built_module_name[$index]=${BUILT_MODULE_NAME[$index]}
	built_module_location[$index]=${BUILT_MODULE_LOCATION[$index]}
	dest_module_name[$index]=${DEST_MODULE_NAME[$index]}
	dest_module_location[$index]=${DEST_MODULE_LOCATION[$index]}
	modules_conf_obsoletes[$index]=${MODULES_CONF_OBSOLETES[$index]}
	modules_conf_alias_type[$index]=${MODULES_CONF_ALIAS_TYPE[$index]}
	case ${MODULES_CONF_OBSOLETE_ONLY[$index]} in
	    [yY]*) modules_conf_obsolete_only[$index]="yes";;
	esac
	case ${STRIP[$index]} in
	    [nN]*) strip[$index]="no";;
	    *)     strip[$index]="yes";;
	esac

	# If unset, set by defaults
	[[ ! ${built_module_name[$index]} ]] && \
	    ((${#DEST_MODULE_LOCATION[@]} == 1)) && \
	    built_module_name[$index]=$module
	[[ ! ${dest_module_name[$index]} ]] && \
	    dest_module_name[$index]=${built_module_name[$index]}
	[[ ${built_module_location[$index]} && \
	    ${built_module_location[$index]:(-1)} != / ]] && \
	    built_module_location[$index]="${built_module_location[$index]}/"

	# FAIL if no built_module_name
	if [[ ! ${built_module_name[$index]} ]]; then
	    echo $"dkms.conf: Error! No 'BUILT_MODULE_NAME' directive specified for record #$index." >&2
	    return_value=1
	fi

	# FAIL if built_module_name ends in .o or .ko
	case ${built_module_name[$index]} in
	*.o|*.ko)
	    echo $"dkms.conf: Error! 'BUILT_MODULE_NAME' directive ends in '.o' or '.ko' in record #$index." >&2
	    return_value=1
	    ;;
	esac

	# FAIL if dest_module_name ends in .o or .ko
	case ${dest_module_name[$index]} in
	*.o|*.ko)
	    echo $"dkms.conf: Error! 'DEST_MODULE_NAME' directive ends in '.o' or '.ko' in record #$index." >&2
	    return_value=1
	    ;;
	esac

	# Override location for specific kernels
	dest_module_location[$index]="$(override_dest_module_location ${dest_module_location[$index]})"

	# Fail if no DEST_MODULE_LOCATION
	if [[ ! ${DEST_MODULE_LOCATION[$index]} ]]; then
	    echo $"dkms.conf: Error! No 'DEST_MODULE_LOCATION' directive specified for record #$index.">&2
	    return_value=1
	fi
	    # Fail if bad DEST_MODULE_LOCATION
	case ${DEST_MODULE_LOCATION[$index]} in
	    /kernel*) ;;
	    /updates*) ;;
	    /extra*) ;;
	    *)
		echo $"dkms.conf: Error! Directive 'DEST_MODULE_LOCATION' does not begin with">&2
		echo $"'/kernel', '/updates', or '/extra' in record #$index.">&2
		return_value=1
		;;
	esac
    done

    # Get the correct make command
    [[ ${MAKE_MATCH[0]} ]] || make_command="${MAKE[0]}"
    for ((index=0; index < ${#MAKE[@]}; index++)); do
	[[ ${MAKE[$index]} && ${MAKE_MATCH[$index]} && \
	    $1 =~ ${MAKE_MATCH[$index]} ]] && \
	    make_command="${MAKE[$index]}"
    done

    # Use the generic make and make clean commands if not specified
    if [[ $(VER $1) < $(VER 2.6.6) ]]; then
	[[ ! $make_command ]] && \
	    make_command="make -C $kernel_source_dir SUBDIRS=$dkms_tree/$module/$module_version/build modules"
	[[ ! $clean ]] && \
	    clean="make -C $kernel_source_dir SUBDIRS=$dkms_tree/$module/$module_version/build clean"
    else
	[[ ! $make_command ]] && \
	    make_command="make -C $kernel_source_dir M=$dkms_tree/$module/$module_version/build"
	 [[ ! $clean ]] && \
	     clean="make -C $kernel_source_dir M=$dkms_tree/$module/$module_version/build clean"
    fi

    # Set modules_conf_array
    for ((index=0; index < ${#MODULES_CONF[@]}; index++)); do
	[[ ${MODULES_CONF[$index]} ]] && modules_conf_array[$index]="${MODULES_CONF[$index]}"
    done

    # Set patch_array (including kernel specific patches)
    count=0
    for ((index=0; index < ${#PATCH[@]}; index++)); do
	if [[ ${PATCH[$index]} && (! ${PATCH_MATCH[$index]} || \
		$1 =~ ${PATCH_MATCH[$index]}) ]]; then
	    patch_array[$count]="${PATCH[$index]}"
	    count=$(($count+1))
	fi
    done

    # Set remake_initrd
    [[ $REMAKE_INITRD =~ $y_re ]] && remake_initrd="yes"

    # Set build_exclude
    [[ $BUILD_EXCLUSIVE_KERNEL && ! $1 =~ $BUILD_EXCLUSIVE_KERNEL ]] && \
       build_exclude="yes"
    [[ $BUILD_EXCLUSIVE_ARCH && ! $2 =~ $BUILD_EXCLUSIVE_ARCH ]] && \
	build_exclude="yes"

    # Fail if absolutely no DEST_MODULE_LOCATION
    if ((${#dest_module_location[@]} == 0)); then
	echo $"dkms.conf: Error! No 'DEST_MODULE_LOCATION' directive specified." >&2
	return_value=1
    fi

    # Fail if no PACKAGE_NAME
    if [[ ! $package_name ]]; then
	echo $"dkms.conf: Error! No 'PACKAGE_NAME' directive specified.">&2
	return_value=1
    fi

    # Fail if no PACKAGE_VERSION
    if [[ ! $package_version ]]; then
	echo $"dkms.conf: Error! No 'PACKAGE_VERSION' directive specified.">&2
	return_value=1
    fi

    # Set clean
    [[ $clean ]] || clean="make clean"

    ((return_value == 0)) && last_mvka="$module/$module_version/$1/$2" && \
	last_mvka_conf="$(readlink -f "$read_conf_file")"
    return $return_value
}

# Little helper function for parsing the output of modinfo.
get_module_verinfo(){
    local vals=
    while read -a vals; do
	case ${vals[0]} in
	    version:) res[0]=${vals[1]}; res[2]=${vals[2]};;
	    srcversion:) res[1]=${vals[1]};;
	esac
    done < <(modinfo $1)
}

# Perform some module version sanity checking whenever we are installing
# or removing modules.
check_version_sanity()
{
    # $1 = kernel_version
    # $2 = arch
    # $3 = obs by kernel version
    # $4 = dest_module_name

    local lib_tree="$install_tree/$1" res=
    echo $"Running module version sanity check."
    local i=0
    local -a kernels_info dkms_info
    set_module_suffix
    read -a kernels_module < <(find $lib_tree -name ${4}$module_suffix)
    [[ $kernels_module ]] || return 0
    if [[ ${kernels_module[1]} ]]; then
	warn $"Warning! Cannot do version sanity checking because multiple ${4}$module_suffix" \
	    $"modules were found in kernel $1."
	return 0
    fi
    local dkms_module="$dkms_tree/$module/$module_version/$1/$2/module/${4}$module_suffix"
    get_module_verinfo $kernels_module; kernels_info=("${res[@]}")
    get_module_verinfo $dkms_module; dkms_info=("${res[@]}")
    if [[ ! ${dkms_info[1]} && ${kernels_info[1]} ]]; then
	# use obsolete checksum info
	dkms_info[1]=${dkms_info[2]}
	kernels_info[1]=${kernels_info[2]}
    fi

    if [[ ${kernels_info[1]} && ${dkms_info[1]} && \
	${kernels_info[1]} = ${dkms_info[1]} && ! $force ]]; then
	echo $"" >&2
	echo $"Good news! Module version $dkms_info for ${4}$module_suffix" >&2
	echo $"exactly matches what is already found in kernel $1." >&2
	echo $"DKMS will not replace this module." >&2
	echo $"You may override by specifying --force." >&2
	return 1
    fi

    if [[ $kernels_info && $dkms_info && \
	! ( $(VER $dkms_info) > $(VER $kernels_info) ) && ! $force ]]; then
	error $"Module version $dkms_info for ${4}$module_suffix" \
	    $"is not newer than what is already found in kernel $1 ($kernels_info)." \
	    $"You may override by specifying --force."
	return 1
    fi

    # magic split into array syntax saves trivial awk and cut calls.
    local -a obs=(${3//-/ })
    local -a my=(${1//-/ })
    local obsolete=0
    if [[ ${obs} && ${my} ]]; then
	if [[ $(VER ${obs}) == $(VER ${my}) && ! $force ]]; then
	    #they get obsoleted possibly in this kernel release
	    if [[ ! ${obs[1]} ]]; then
		#they were obsoleted in this upstream kernel
		obsolete=1
	    elif [[ $(VER ${my[1]}) > $(VER ${obs[1]}) ]]; then
		#they were obsoleted in an earlier ABI bump of the kernel
		obsolete=1
	    elif [[ $(VER ${my[1]}) = $(VER ${obs[1]}) ]]; then
		#they were obsoleted in this ABI bump of the kernel
		obsolete=1
	    fi
	elif [[ $(VER ${my}) > $(VER ${obs}) && ! $force ]]; then
	    #they were obsoleted in an earlier kernel release
	    obsolete=1
	fi
    fi

    if ((obsolete == 1)); then
	echo $"" >&2
	echo $"Module has been obsoleted due to being included" >&2
	echo $"in kernel $3.  We will avoid installing" >&2
	echo $"for future kernels above $3." >&2
	echo $"You may override by specifying --force." >&2
	return 1
    fi
    return 0
}

moduleconfig_update_obsoletes()
{
    # $@ = files to process
    # do nothing if we have no obsoletes
    [[ ${modules_conf_obsoletes[@]} ]] || return 0
    # generate sed args to remove obsolete modules
    local mod_diff
    for ((index=0; index < ${#dest_module_name[@]}; index++)); do
	[[ ${modules_conf_obsoletes[$index]} ]] || continue
	for obsolete_module in ${modules_conf_obsoletes[$index]//,/ }; do
	    # for module.conf style syntax
	    sa_mc_o[${#sa_mc_o[@]}]="-e"
	    sa_mc_o[${#sa_mc_o[@]}]="s/\(alias ${modules_conf_alias_type[$index]}[0-9]*\) $obsolete_module$/\1 ${dest_module_name[$index]}/g"

	    # for /etc/sysconfig/kernel style syntax
	    sa_sck_o[${#sa_sck_o[@]}]="-e"
	    sa_sck_o[${#sa_sck_o[@]}]="s/\(INITRD_MODULES.*\)$obsolete_module\b\(.*\)/\1${dest_module_name[$index]}\2/"
	done
    done

    # do all the changes at once, record the diffs for posterity
    for file in "$@"; do
	[[ $file && -w $file ]] || continue
	if [[ $file = /etc/sysconfig/kernel ]]; then
	    sed "${sa_sck_o[@]}" "$file" > "$temp_dir_name/${file##*/}.new"
	else
	    sed "${sa_mc_o[@]}" "$file" > "$temp_dir_name/${file##*/}.new"
	fi
	if ! mod_diff=$(diff -u "$temp_dir_name/${file##*/}.new" "$file"); then
	    echo $"$file updated to replace obsoleted module references:"
	    echo "$mod_diff"
	    cp -fp "$temp_dir_name/${file##*/}.new" "$file"
	    rm -f "$temp_dir_name/${file##*/}.new"
	fi
    done
}

moduleconfig_add()
{
    # $1 = kernel version

    local temp_dir_name=$(mktemp_or_die -d $tmp_location/dkms.XXXXXX)
    local -a sa_mc_o=() sa_sck_o=()
    modconfig_files="/etc/modprobe.d/dkms.conf
		     /etc/modprobe.d/dkms
		     /etc/modules.conf
		     /etc/modprobe.conf
		     /etc/modprobe.d/$package_name.conf
		     /etc/sysconfig/kernel"

    moduleconfig_update_obsoletes $modconfig_files

    for moduleconfig in $modconfig_files; do
	[[ -e $moduleconfig ]] || continue
	for ((index=0; index < ${#dest_module_name[@]}; index++)); do

	    # Only add it if it can't be found already in config file
	    if [[ ${modules_conf_alias_type[$index]} ]] && \
	       ! grep -qs "alias ${modules_conf_alias_type[$index]}[0-9]* ${dest_module_name[$index]}\b" $moduleconfig && \
	       [[ ${modules_conf_obsolete_only[$index]} != yes ]]; then
		if [[ $modconfig_files = /etc/modprobe.d/$package_name.conf ]] && \
		    [[ ! -e /etc/modprobe.d/$package_name.conf ]]; then
		    touch /etc/modprobe.d/$package_name.conf
		    echo $"created /etc/modprobe.d/$package_name.conf.">&2
		fi
		aliases=$(awk "/^alias ${modules_conf_alias_type[$index]}/ {print \$2}" $moduleconfig)
		if [[ $aliases ]]; then
		    alias_number=$(($(echo "$aliases" | sed "s/${modules_conf_alias_type[$index]}//" | sort -n | tail -n 1) + 1))
		else
		    alias_number=0
		fi
		echo -e "alias ${modules_conf_alias_type[$index]}${alias_number} ${dest_module_name[$index]}" >> $moduleconfig
		echo $"$moduleconfig: added alias reference for '${dest_module_name[$index]}'"
	    fi
	done

	# Add anything else
	for ((index=0; index < ${#modules_conf_array[@]}; index++)); do
	    if [ -n "${modules_conf_array[$index]}" ] && \
	    ! grep -q "${modules_conf_array[$index]}" "$moduleconfig"; then
	    echo -e $"$moduleconfig: added '${modules_conf_array[$index]}'"
	    echo -e "${modules_conf_array[$index]}" >> $moduleconfig
	    fi
	done
    done

    # Delete the temp dir
    rm -rf $temp_dir_name
}

moduleconfig_remove()
{
    # $1 = kernel version

    local temp_dir_name=$(mktemp_or_die -d $tmp_location/dkms.XXXXXX)
    modconfig_files=""
    [ -e /etc/modprobe.d/dkms.conf ] && modconfig_files="/etc/modprobe.d/dkms.conf"
    [ -e /etc/modprobe.d/dkms ] && modconfig_files="/etc/modprobe.d/dkms"
    [ -e /etc/modules.conf ] && modconfig_files="$modconfig_files /etc/modules.conf"
    [ -e /etc/modprobe.conf ] && modconfig_files="$modconfig_files /etc/modprobe.conf"
    [ -e /etc/modprobe.d/$package_name.conf ] && modconfig_files="/etc/modprobe.d/$package_name.conf"

    for moduleconfig in $modconfig_files; do
	for ((index=0; index < ${#dest_module_name[@]}; index++)); do
	    # Remove/Replace aliases (maybe)
	    [[ ${modules_conf_alias_type[$index]} ]] || continue
	    find "$install_tree/$1/" -name "${dest_module_name[$index]}.*" -quit 2>/dev/null && continue

	    local conf_replacement=""
	    for obsolete_module in ${modules_conf_obsoletes[$index]//,/ }; do
		find $install_tree/$1/ -name "$obsolete_module.*" -quit 2>/dev/null || continue
		conf_replacement=$obsolete_module
		break
	    done

	    if [[ ! $conf_replacement ]]; then
		grep -v "alias ${modules_conf_alias_type[$index]}[0-9]* ${dest_module_name[$index]}" $moduleconfig > $temp_dir_name/moduleconfig.new
		mv -f $temp_dir_name/moduleconfig.new $moduleconfig
		echo $"$moduleconfig: removed alias for '${dest_module_name[$index]}'"
		if [[ $modconfig_files = /etc/modprobe.d/$package_name.conf ]]; then
		    rm -f /etc/modprobe.d/$package_name.conf
		    echo $"$moduleconfig: deleted /etc/modprobe.d/$package_name.conf file"
		fi
	    elif grep -q "alias ${modules_conf_alias_type[$index]}[0-9]* ${dest_module_name[$index]}$" $moduleconfig; then
		sed "s/\(alias ${modules_conf_alias_type[$index]}[0-9]*\) ${dest_module_name[$index]}$/\1 $conf_replacement/g" $moduleconfig > $temp_dir_name/moduleconfig.new
		mv -f $temp_dir_name/moduleconfig.new $moduleconfig
		echo $"$moduleconfig: alias for '${dest_module_name[$index]}' changed back to '$conf_replacement'"
	    fi
	done

	# Remove static conf entries
	for ((index=0; index < ${#modules_conf_array[@]}; index++)); do
	    [[ ${modules_conf_array[$index]} ]] || continue
	    grep -v "${modules_conf_array[$index]}" "$moduleconfig" > $temp_dir_name/moduleconfig.new
	    echo $"$moduleconfig: removed '${modules_conf_array[$index]}'"
	    mv -f $temp_dir_name/moduleconfig.new $moduleconfig
	done
    done

    # Delete the temp dir
    rm -rf $temp_dir_name
}

etc_sysconfig_kernel_modify()
(
    [[ -e /etc/sysconfig/kernel && $remake_initrd ]] || return 0

    # Make a temp directory to store files
    local temp_dir_name=$(mktemp_or_die -d $tmp_location/dkms.XXXXXX)
    if [[ $1 = add ]]; then
	. /etc/sysconfig/kernel
	for m in "${dest_module_name[@]}"; do
	    for l in "${INITRD_MODULES}"; do
		[[ $m = $l ]] && continue 2
	    done
	    sed -e "s/INITRD_MODULES=\"\(.*\)\"/INITRD_MODULES=\"\1 $m\"/" /etc/sysconfig/kernel > $temp_dir_name/kernel.new
	    mv $temp_dir_name/kernel.new /etc/sysconfig/kernel
	done
    # Remove /etc/sysconfig/kernel entries
    elif [[ $1 = delete ]]; then
	for m in "${dest_module_name[@]}"; do
	    sed -e "s/\(INITRD_MODULES.*\)$m\b\(.*\)/\1\2/" /etc/sysconfig/kernel > $temp_dir_name/kernel.new
	    mv $temp_dir_name/kernel.new /etc/sysconfig/kernel
	done
    fi
    # Delete the temp dir
    rm -rf $temp_dir_name
)

check_module_args() {
    [[ $module && $module_version ]] && return
    die 1 $"Invalid number of arguments passed." \
        $"Usage: $1 <module>/<module-version> or" \
        $"       $1 -m <module>/<module-version> or" \
	$"       $1 -m <module> -v <module-version>"
}

read_conf_or_die() {
    read_conf "$@" && return
    die 8 $"Bad conf file." $"File: $conf" \
	$"does not represent a valid dkms.conf file."
}

run_build_script() {
    # $1 = script type
    # $2 = script to run
    local script_type run
    [[ $2 ]] || return 0
    case "$1" in
        pre_build|post_build) script_type='build';;
        *) script_type='source'
    esac
    run="$dkms_tree/$module/$module_version/$script_type/$2"
    if [[ -x ${run%% *} ]]; then
        echo $""
        echo $"Running the $1 script:"
        (
            cd "$dkms_tree/$module/$module_version/$script_type/"
            exec $run
        )
    else
        echo $""
        warn $"The $1 script is not executable."
    fi
}

# Register a DKMS-ified source tree with DKMS.
# This function is smart enough to register the module if we
# passed a source tree or a tarball instead of relying on the source tree
# being unpacked into /usr/src/$module-$module_version.
add_module()
{
    # if $archive is set and $module and $module_version are not,
    # try loading the tarball passed first.
    if [[ $archive_location && ! $module && ! $module_version ]]; then
	load_tarball
    elif [[ $try_source_tree && ! $module && ! $module_version ]]; then
	add_source_tree "$try_source_tree"
    fi

    # Check that we have all the arguments
    check_module_args add

    # Check that this module-version hasn't already been added
    if is_module_added "$module" "$module_version"; then
	die 3 $"DKMS tree already contains: $module-$module_version" \
	    $"You cannot add the same module/version combo more than once."
    fi

    [[ $conf ]] || conf="$source_tree/$module-$module_version/dkms.conf"

    # Check that /usr/src/$module-$module_version exists
    if ! [[ -d $source_tree/$module-$module_version ]]; then
	die 2 $"Could not find module source directory." \
	    $"Directory: $source_tree/$module-$module_version does not exist."
    fi

    # Do stuff for --rpm_safe_upgrade
    if [[ $rpm_safe_upgrade ]]; then
	local pppid=$(awk '/PPid:/ {print $2}' /proc/$PPID/status)
	local lock_name=$(mktemp_or_die $tmp_location/dkms_rpm_safe_upgrade_lock.$pppid.XXXXXX)
	echo "$module-$module_version" >> $lock_name
	ps -o lstart --no-headers -p $pppid 2>/dev/null >> $lock_name
    fi

    # Check the conf file for sanity
    read_conf_or_die "$kernelver" "$arch" "$conf"

    # Create the necessary dkms tree structure
    echo $""
    echo $"Creating symlink $dkms_tree/$module/$module_version/source ->"
    echo $"                 $source_tree/$module-$module_version"
    mkdir -p "$dkms_tree/$module/$module_version/build"
    ln -s "$source_tree/$module-$module_version" "$dkms_tree/$module/$module_version/source"

    # Run the post_add script
    run_build_script post_add "$post_add"

    echo $""
    echo $"DKMS: add completed."
}

# Prepare a kernel source or include tree for compiling a module.
# Most modern-ish distros do not require this function at all,
# so it will be removed in a future release.
prepare_kernel()
{
    # $1 = kernel version to prepare
    # $2 = arch to prepare

    set_kernel_source_dir "$1"

    # Check that kernel-source exists
    _check_kernel_dir "$1" || {
	case "$running_distribution" in
	    Debian* | Ubuntu* )
		die 1 $"Your kernel headers for kernel $1 cannot be found." \
		    $"Please install the linux-headers-$1 package," \
		    $"or use the --kernelsourcedir option to tell DKMS where it's located";;
	    * ) die 1 echo $"Your kernel headers for kernel $1 cannot be found at" \
		$"/lib/modules/$1/build or /lib/modules/$1/source."
		$"You can use the --kernelsourcedir option to tell DKMS where it's located."
	esac
    }

    [[ $no_prepare_kernel ]] && return

    if [[ (! ( $(VER $1) < $(VER 2.6.5) ) || -d /etc/SuSEconfig) && \
       -d "$kernel_source_dir" && \
       -z "$ksourcedir_fromcli" ]]; then
	echo $""
	echo $"Kernel preparation unnecessary for this kernel.  Skipping..."
	no_clean_kernel="no-clean-kernel"
	return 1
    fi

    # Prepare kernel for module build
    echo $""
    echo $"Preparing kernel $1 for module build:"
    echo $"(This is not compiling a kernel, just preparing kernel symbols)"
    cd $kernel_source_dir
    [[ -r .config ]] && {
	config_contents=$(cat .config)
	echo $"Storing current .config to be restored when complete"
    }

    # Set kernel_config
    if [[ -e /etc/redhat-release || -e /etc/fedora-release ]]; then
	# Note this also applies to VMware 3.x
	if [[ -z $kernel_config && -d $kernel_source_dir/configs ]]; then
	    local kernel_trunc=${1%%-*}
	    # Try a .config specific to whatever kernel we are running
	    if [[ $1 =~ $rh_kernels && \
		-e $kernel_source_dir/configs/kernel-$kernel_trunc-$2-${BASH_REMATCH[1]}.config ]]; then
		kernel_config="$kernel_source_dir/configs/kernel-$kernel_trunc-$2-${BASH_REMATCH[1]}.config"
	    elif [[ -e $kernel_source_dir/configs/kernel-$kernel_trunc-$2.config ]]; then
		# If that one does not exist, try a generic one.
		kernel_config="$kernel_source_dir/configs/kernel-$kernel_trunc-$2.config"
	    else
		# If that does not exist, fall back to no config file
		kernel_config=""
	    fi
	fi
    elif [[ (-e /etc/SuSE-release || -d /etc/SuSEconfig) && \
	-z $kernel_config && -d $kernel_source_dir/arch ]]; then
	    local kernel_trunc=${1%%-*}
	    case $2 in
		i586|i686) config_arch="i386";;
		*) config_arch=$2;;
	    esac
	    for config_type in default smp bigsmp; do
		[[ $1 =~ $config_type ]] && kernel_config="$kernel_source_dir/arch/$config_arch/defconfig.$config_type"
		[[ -e $kernel_config ]] || kernel_config=""
	    done
	    [[ $kernel_config ]] || kernel_config="$kernel_source_dir/arch/$config_arch/defconfig.default"
	    [[ -e $kernel_config ]] || kernel_config=""
    fi

    # Do preparation
    if [ -e /boot/vmlinuz.version.h ]; then
	echo $"Running UnitedLinux preparation routine"
	local kernel_config="/boot/vmlinuz.config"
	invoke_command "make mrproper" "make mrproper" background
	[[ $config_contents ]] && echo "$config_contents" > .config
	invoke_command "cp /boot/vmlinuz.version.h include/linux/version.h" "using /boot/vmlinux.version.h"
	invoke_command "cp -f $kernel_config .config" "using $kernel_config"
	invoke_command "make KERNELRELEASE=$1 cloneconfig" "make cloneconfig" background
	invoke_command "make CONFIG_MODVERSIONS=1 KERNELRELEASE=$1 dep" "make CONFIG_MODVERSIONS=1 dep" background
    elif grep -q rhconfig.h $kernel_source_dir/include/linux/{modversions,version}.h 2>/dev/null; then
	echo $"Running Red Hat style preparation routine"
	invoke_command "make clean" "make clean" background
	[[ $config_contents ]] && echo "$config_contents" > .config

	if [[ $kernel_config ]]; then
	    echo $"using $kernel_config"
	    cp -f "$kernel_config" .config
	elif [[ -e .config ]]; then
	    warn $"Using $kernel_source_dir/.config" \
		$"(I hope this is the correct config for this kernel)"
	else
	    warn $"Cannot find a .config file to prepare your kernel with." \
		$"Try using the --config option to specify where one can be found." \
		$"Your build will likely fail because of this."
	fi

	# Hack to workaround broken tmp_include_depends for Red Hat
	if grep -q "/usr/src/build" $kernel_source_dir/tmp_include_depends 2>/dev/null; then
	    sed 's/\/usr\/src\/build\/.*\/install//g' $kernel_source_dir/tmp_include_depends > $kernel_source_dir/tmp_include_depends.new
	    mv -f $kernel_source_dir/tmp_include_depends.new $kernel_source_dir/tmp_include_depends
	fi

	invoke_command "make KERNELRELEASE=$1 oldconfig" "make oldconfig" background
	kerneldoth_contents=$(cat /boot/kernel.h 2>/dev/null)
	invoke_command "/usr/lib/dkms/mkkerneldoth --kernelver $1 --targetarch $2 --output /boot/kernel.h" "running mkkerneldoth" background
    else
	echo $"Running Generic preparation routine"
	invoke_command "make mrproper" "make mrproper" background
	[[ $config_contents ]] && echo "$config_contents" > .config

	if [[ $kernel_config ]]; then
	    echo $"using $kernel_config"
	    cp -f "$kernel_config" .config
	elif [[ -e .config ]]; then
	    warn $"using $kernel_source_dir/.config" \
		$"(I hope this is the correct config for this kernel)"
	else
	    warn $"Warning! Cannot find a .config file to prepare your kernel with." \
		$"Try using the --config option to specify where one can be found." \
		$"Your build will likely fail because of this."
	fi

	invoke_command "make KERNELRELEASE=$1 oldconfig" "make oldconfig" background
	if [[ $(VER $1) < $(VER 2.5) ]]; then
	    invoke_command "make KERNELRELEASE=$1 dep" "make dep" background
	else
	    invoke_command "make KERNELRELEASE=$1 prepare-all scripts" "make prepare-all" background
	fi
    fi
    cd - >/dev/null
}

# Get ready to build a module that has been registered with DKMS.
prepare_build()
{
    # If the module has not been added, try to add it.
    is_module_added "$module" "$module_version" || add_module

    set_kernel_source_dir "$kernelver"
    local base_dir="$dkms_tree/$module/$module_version/$kernelver/$arch"

    # Check that the right arguments were passed
    check_module_args build

    # Check that the module has not already been built for this kernel
    [[ -d $base_dir ]] && die 3 \
	$"This module/version has already been built on: $kernelver" \
	$"Directory: $base_dir" \
	$"already exists.  Use the dkms remove function before trying to build again."

    # Read the conf file
    set_module_suffix "$kernelver"
    read_conf_or_die "$kernelver" "$arch"

    # Error out if build_exclude is set
    [[ $build_exclude ]] && die 9 \
	$" The dkms.conf for this module includes a BUILD_EXCLUSIVE directive which" \
	$"does not match this kernel/arch.  This indicates that it should not be built."

    # Error out if source_tree is basically empty (binary-only dkms tarball w/ --force check)
    (($(ls $dkms_tree/$module/$module_version/source | wc -l | awk {'print $1'}) < 2)) && die 8 \
	$"The directory $dkms_tree/$module/$module_version/source/" \
	$"does not appear to have module source located within it.  Build halted."

    prepare_kernel "$kernelver" "$arch"

    # Set up temporary build directory for build
    rm -rf "$dkms_tree/$module/$module_version/build"
    cp -rf "$dkms_tree/$module/$module_version/source/" "$dkms_tree/$module/$module_version/build"

    cd "$dkms_tree/$module/$module_version/build"

    # Apply any patches
    for p in "${patch_array[@]}"; do
	[[ ! -e $dkms_tree/$module/$module_version/build/patches/$p ]] && \
	    report_build_problem 5 \
	    $" Patch $p as specified in dkms.conf cannot be" \
	    $"found in $dkms_tree/$module/$module_version/build/patches/."
	invoke_command "patch -p1 < ./patches/$p" "applying patch $p" || \
	    report_build_problem 6 $"Application of patch $p failed." \
	    $"Check $dkms_tree/$module/$module_version/build/ for more information."
    done

    # Run the pre_build script
    run_build_script pre_build "$pre_build"
}

# Build our previously prepared source tree.  prepare_build must be called
# before calling this function.
do_build()
{
    local base_dir="$dkms_tree/$module/$module_version/$kernelver/$arch"
    echo $""
    echo $"Building module:"

    invoke_command "$clean" "cleaning build area" background
    echo $"DKMS make.log for $module-$module_version for kernel $kernelver ($arch)" >> "$dkms_tree/$module/$module_version/build/make.log"
    date >> "$dkms_tree/$module/$module_version/build/make.log"
    local the_make_command="${make_command/#make/make KERNELRELEASE=$kernelver}"

    invoke_command "{ $the_make_command; } >> $dkms_tree/$module/$module_version/build/make.log 2>&1" "$the_make_command" background || \
	report_build_problem 10 $"Bad return status for module build on kernel: $kernelver ($arch)" \
	$"Consult $dkms_tree/$module/$module_version/build/make.log for more information."

    # Make sure all the modules built successfully
    for ((count=0; count < ${#built_module_name[@]}; count++)); do
	[[ -e ${built_module_location[$count]}${built_module_name[$count]}$module_suffix ]] && continue
	report_build_problem 7 \
	    $" Build of ${built_module_name[$count]}$module_suffix failed for: $kernelver ($arch)" \
	    $"Consult the make.log in the build directory" \
	    $"$dkms_tree/$module/$module_version/build/ for more information."
    done
    cd - >/dev/null

    # Build success, so create DKMS structure for a built module
    mkdir -p "$base_dir/log"
    [[ $kernel_config ]] && cp -f "$kernel_config" "$base_dir/log/"
    mv -f "$dkms_tree/$module/$module_version/build/make.log" "$base_dir/log/make.log" 2>/dev/null

    # Save a copy of the new module
    mkdir "$base_dir/module" >/dev/null
    for ((count=0; count < ${#built_module_name[@]}; count++)); do
	[[ ${strip[$count]} != no ]] && strip -g "$dkms_tree/$module/$module_version/build/${built_module_location[$count]}${built_module_name[$count]}$module_suffix"
	cp -f "$dkms_tree/$module/$module_version/build/${built_module_location[$count]}${built_module_name[$count]}$module_suffix" "$base_dir/module/${dest_module_name[$count]}$module_suffix" >/dev/null
    done

    # Run the post_build script
    run_build_script post_build "$post_build"
}

# Clean up after a build.
clean_build()
{
    # Run the clean commands
    cd "$dkms_tree/$module/$module_version/build"
    invoke_command "$clean" "cleaning build area" background
    cd - >/dev/null

    if [[ ! ( $(VER $kernelver) < $(VER 2.6.6) ) && \
       -d $kernel_source_dir && \
       ! -h $kernel_source_dir && \
       ! $ksourcedir_fromcli ]]; then
	echo $"Kernel cleanup unnecessary for this kernel.  Skipping..."
    elif [[ ! $no_clean_kernel ]]; then
	cd "$kernel_source_dir"
	[[ $kerneldoth_contents ]] || invoke_command "make mrproper" "cleaning kernel tree (make mrproper)" background
	[[ $config_contents ]] || echo "$config_contents" > .config
	[[ $kerneldoth_contents ]] && echo "$kerneldoth_contents" > /boot/kernel.h
	cd - >/dev/null
    fi

    # Clean the build directory
    rm -rf "$dkms_tree/$module/$module_version/build/*"
}

build_module()
{
    prepare_build
    do_build
    clean_build
    echo $""
    echo $"DKMS: build completed."
}

# Install a previously built module
# There are huge swaths of code here that special-case for various distros.
# They should be split into their own functions.
install_module()
{
    # If the module has not been built, try to build it first.
    is_module_built "$module" "$module_version" "$kernelver" "$arch" || build_module
    local base_dir="$dkms_tree/$module/$module_version/$kernelver/$arch"
    check_module_args install

    # Make sure that kernel exists to install into
    [[ -e $install_tree/$kernelver ]] || die 6 \
	$"The directory $install_tree/$kernelver doesn't exist." \
	$"You cannot install a module onto a non-existant kernel."

    # Read the conf file
    read_conf_or_die "$kernelver" "$arch"

    # Check that its not already installed (kernel symlink)
    is_module_installed "$module" "$module_version" "$kernelver" "$arch" && die 5 \
	$"This module/version combo is already installed" \
	$"for kernel: $kernelver ($arch)"

    # if upgrading using rpm_safe_upgrade, go ahead and force the install
    # else we can wind up with the first half of an upgrade failing to install anything,
    # while the second half of the upgrade, the removal, then succeeds, leaving us with
    # nothing installed.
    [[ $rpm_safe_upgrade ]] && force="true"

    # Save the original_module if one exists, none have been saved before, and this is the first module for this kernel
    local lib_tree="$install_tree/$kernelver"
    local count
    for ((count=0; count < ${#built_module_name[@]}; count++)); do
	echo $""
	echo $"${dest_module_name[$count]}$module_suffix:"
	# Check this version against what is already in the kernel
	check_version_sanity "$kernelver" "$arch" \
	    "$obsolete_by" "${dest_module_name[$count]}" || continue

	if ((count == 0)) && ! run_build_script pre_install "$pre_install" && \
	    ! [[ $force ]]; then
	    die 101 $"pre_install failed, aborting install." \
		$"You may override by specifying --force."
	fi
	local module_count=$(find $lib_tree -name ${dest_module_name[$count]}$module_suffix -type f | wc -l | awk {'print $1'})
	echo $" - Original module"
	if [[ -L $dkms_tree/$module/kernel-$kernelver-$arch && \
	    -e $dkms_tree/$module/original_module/$kernelver/$arch/${dest_module_name[$count]}$module_suffix ]]; then
	    echo $"   - An original module was already stored during a previous install"
	elif ! [[ -L $dkms_tree/$module/kernel-$kernelver-$arch ]]; then
	    local archive_pref1="$lib_tree/extra/${dest_module_name[$count]}$module_suffix"
	    local archive_pref2="$lib_tree/updates/${dest_module_name[$count]}$module_suffix"
	    local archive_pref3="$lib_tree${dest_module_location[$count]}/${dest_module_name[$count]}$module_suffix"
	    local archive_pref4=""
	    ((module_count == 1)) && archive_pref4=$(find $lib_tree -name ${dest_module_name[$count]}$module_suffix -type f)
	    local original_module=""
	    local found_orginal=""
	    for original_module in $archive_pref1 $archive_pref2 $archive_pref3 $archive_pref4; do
		[[ -f $original_module ]] || continue
		case "$running_distribution" in
		    Debian* | Ubuntu* ) ;;
		    *)
			echo $"   - Found $original_module"
			echo $"   - Storing in $dkms_tree/$module/original_module/$kernelver/$arch/"
			echo $"   - Archiving for uninstallation purposes"
			mkdir -p "$dkms_tree/$module/original_module/$kernelver/$arch"
			mv -f "$original_module" "$dkms_tree/$module/original_module/$kernelver/$arch/"
			;;
		esac
		found_original="yes"
		break
	    done
	    if [[ ! $found_original ]] && ((module_count > 1)); then
		echo $"   - Multiple original modules exist but DKMS does not know which to pick"
		echo $"   - Due to the confusion, none will be considered during a later uninstall"
	    elif [[ ! $found_original ]]; then
		echo $"   - No original module exists within this kernel"
	    fi
	else
	    echo $"   - This kernel never originally had a module by this name"
	fi

	if ((module_count > 1)); then
	    echo $" - Multiple same named modules!"
	    echo $"   - $module_count named ${dest_module_name[$count]}$module_suffix in $lib_tree/"
	    case "$running_distribution" in
		Debian* | Ubuntu* ) ;;
		*)
		    echo $"   - All instances of this module will now be stored for reference purposes ONLY"
		    echo $"   - Storing in $dkms_tree/$module/original_module/$kernelver/$arch/collisions/"
		    ;;
	    esac
	    for module_dup in $(find $lib_tree -name ${dest_module_name[$count]}$module_suffix -type f); do
		dup_tree="${module_dup#$lib_tree}";
		dup_tree="${dup_tree/${dest_module_name[$count]}$module_suffix}"
		case "$running_distribution" in
		Debian* | Ubuntu* ) ;;
		*)
		   echo $"     - Stored $module_dup"
		   mkdir -p "$dkms_tree/$module/original_module/$kernelver/$arch/collisions/$dup_tree"
		   mv -f $module_dup "$dkms_tree/$module/original_module/$kernelver/$arch/collisions/$dup_tree"
		   ;;
		esac
	    done
	fi

	# Copy module to its location
	echo $" - Installation"
	echo $"   - Installing to $install_tree/$kernelver${dest_module_location[$count]}/"
	mkdir -p $install_tree/$kernelver${dest_module_location[$count]}
	cp -f "$base_dir/module/${dest_module_name[$count]}$module_suffix" "$install_tree/$kernelver${dest_module_location[$count]}/${dest_module_name[$count]}$module_suffix"

    done

    # Create the kernel-<kernelver> symlink to designate this version as active
    rm -f "$dkms_tree/$module/kernel-$kernelver-$arch" 2>/dev/null
    ln -s "$module_version/$kernelver/$arch" "$dkms_tree/$module/kernel-$kernelver-$arch" 2>/dev/null

    # add to kabi-tracking
    if [[ ${weak_modules} ]]; then
	echo $"Adding any weak-modules"
	list_each_installed_module "$module" "$kernelver" "$arch" | ${weak_modules} --add-modules
    fi

    # Run the post_install script
    run_build_script post_install "$post_install"

    # Make modules.conf changes as necessary
    echo $""
    moduleconfig_add "$kernelver"
    etc_sysconfig_kernel_modify "add"

    invoke_command "do_depmod $kernelver" "depmod" background || {
	do_uninstall "$kernelver" "$arch"
	die 6 $"Problems with depmod detected.  Automatically uninstalling this module." \
	    $"DKMS: Install Failed (depmod problems).  Module rolled back to built state."
	    exit 6
    }

    # Do remake_initrd things (save old initrd)
    [[ $remake_initrd ]] && ! make_initrd "$kernelver" "$arch" && {
	do_uninstall "$kernelver" "$arch"
	die 7 $"Problems with mkinitrd detected.  Automatically uninstalling this module." \
	    $"DKMS: Install Failed (mkinitrd problems).  Module rolled back to built state."
    }
    echo $""
    echo $"DKMS: install completed."
}

# List each kernel object that has been installed for a particular module.
list_each_installed_module()
{
    # $1 = module
    # $2 = kernel version
    # $3 = arch
    local count
    local real_dest_module_location
    for ((count=0; count < ${#built_module_name[@]}; count++)); do
	real_dest_module_location="$(find_actual_dest_module_location $1 $count $2 $3)"
	echo "$install_tree/$2${real_dest_module_location}/${dest_module_name[$count]}$module_suffix"
    done
}

is_module_added() {
    [[ $1 && $2 ]] || return 1
    [[ -d $dkms_tree/$1/$2 ]] || return 2
    [[ -L $dkms_tree/$1/$2/source || -d $dkms_tree/$1/$2/source ]];
}

is_module_built() {
    [[ $1 && $2 && $3 && $4 ]] || return 1
    local d="$dkms_tree/$1/$2/$3/$4" m=''
    [[ -d $d/module ]] || return 1
    read_conf_or_die "$3" "$4" "$dkms_tree/$1/$2/source/dkms.conf"
    for m in "${dest_module_name[@]}"; do
	[[ -f $d/module/$m.ko || -f $d/module/$m.o ]] || return 1
    done
}

# This assumes we have already checked to see if the module has been built.
_is_module_installed() {
    [[ $1 && $2 && $3 && $4 ]] || return 1
    local d="$dkms_tree/$1/$2/$3/$4"
    local k="$dkms_tree/$1/kernel-$3-$4"
    [[ -L $k && $(readlink -f $k) = $d ]]
}

# This does not.
is_module_installed() { is_module_built "$@" && _is_module_installed "$@"; }

maybe_add_module() (
    is_module_added "$1" "$2" && {
	echo $"Module $1/$2 already added."
	return 0
    }
    module="$1" module_version="$2" add_module
)

maybe_build_module() (
    is_module_built "$1" "$2" "$3" "$4" && {
	echo $"Module $1/$2 already built for kernel $3/4"
	return 0
    }
    module="$1" module_version="$2" kernelver="$3" arch="$4" build_module
)

maybe_install_module() (
    is_module_installed "$1" "$2" "$3" "$4" && {
	echo $"Module $1/$2 already installed on kernel $3/$4"
	return 0
    }
    module="$1" module_version="$2" kernelver="$3" arch="$4" install_module
)

build_modules() {
    local i=0
    for ((i=0; i < ${#kernelver[@]}; i++)); do
	maybe_build_module "$module" "$module_version" "${kernelver[$i]}" "${arch[$i]}"
    done
}

install_modules() {
    local i=0
    for ((i=0; i < ${#kernelver[@]}; i++)); do
	maybe_install_module "$module" "$module_version" "${kernelver[$i]}" "${arch[$i]}"
    done
}

check_module_exists() {
    is_module_added "$module" "$module_version" && return
    die 2 $"DKMS tree does not contain: $module-$module_version" \
	$"Build cannot continue without the proper tree."
}

possible_dest_module_locations()
{
    # $1 = count
    # There are two places an installed module may really be:
    # 1) "$install_tree/$kernelver/${dest_module_location[$count]}/${dest_module_name[$count]}$module_suffix"
    # 2) "$install_tree/$kernelver/${DEST_MODULE_LOCATION[$count]}/${dest_module_name[$count]}$module_suffix"
    # override_dest_module_location() is what controls whether or not they're the same.

    local location
    location[0]="${dest_module_location[$count]}"
    [[ ${DEST_MODULE_LOCATION[$count]} != ${dest_module_location[$count]} ]] && \
    location[1]="${DEST_MODULE_LOCATION[$count]}"

    echo "${location[@]}"
}

find_actual_dest_module_location()
{
    local module="$1"
    local count="$2"
    local kernelver="$3"
    local arch="$4"
    local locations="$(possible_dest_module_locations $count)"
    local l
    local dkms_owned
    local installed
    dkms_owned="${dkms_tree}/${module}/kernel-${kernelver}-${arch}/module/${dest_module_name[$count]}${module_suffix}"

    for l in $locations; do
	installed="${install_tree}/${kernelver}${l}/${dest_module_name[${count}]}${module_suffix}"
	if [[ -f ${installed} ]] && diff "${dkms_owned}" "${installed}" > /dev/null 2>&1; then
	    echo "${l}"
	    return 0
	fi
    done

}

# Remove compiled DKMS modules from any kernels they are installed in.
do_uninstall()
{
    # $1 = kernel version
    # $2 = arch

    echo $""
    echo $"-------- Uninstall Beginning --------"
    echo $"Module:  $module"
    echo $"Version: $module_version"
    echo $"Kernel:  $1 ($2)"
    echo $"-------------------------------------"

    set_module_suffix "$1"

    # If kernel-<kernelver> symlink points to this module, check for original_module and put it back
    local was_active=""
    local kernel_symlink=$(readlink -f "$dkms_tree/$module/kernel-$1-$2")
    local real_dest_module_location
    if [[ $kernel_symlink = $dkms_tree/$module/$module_version/$1/$2 ]]; then
	was_active="true"
	echo $""
	echo $"Status: Before uninstall, this module version was ACTIVE on this kernel."
	# remove kabi-tracking if last instance removed
	if [[ ${weak_modules} ]] && \
	    (module_status_built $module $module_version |grep -q "installed"); then
	    echo $"Removing any linked weak-modules"
	    list_each_installed_module "$module" "$1" "$2" | ${weak_modules} --remove-modules
	fi

	for ((count=0; count < ${#built_module_name[@]}; count++)); do
	    real_dest_module_location="$(find_actual_dest_module_location $module $count $1 $2)"
	    echo $""
	    echo $"${dest_module_name[$count]}$module_suffix:"
	    echo $" - Uninstallation"
	    echo $"   - Deleting from: $install_tree/$1${real_dest_module_location}/"
	    rm -f "$install_tree/$1${real_dest_module_location}/${dest_module_name[$count]}$module_suffix"
	    echo $" - Original module"
	    if [[ -e $dkms_tree/$module/original_module/$1/$2/${dest_module_name[$count]}$module_suffix ]]; then
		case "$running_distribution" in
		Debian* | Ubuntu* ) ;;
		*)
		    echo $"   - Archived original module found in the DKMS tree"
		    echo $"   - Moving it to: $install_tree/$1${DEST_MODULE_LOCATION[$count]}/"
		    mkdir -p "$install_tree/$1${DEST_MODULE_LOCATION[$count]}/"
		    mv -f "$dkms_tree/$module/original_module/$1/$2/${dest_module_name[$count]}$module_suffix" \
			"$install_tree/$1${DEST_MODULE_LOCATION[$count]}/" 2>/dev/null
		    ;;
		esac
	    else
		echo $"   - No original module was found for this module on this kernel."
		echo $"   - Use the dkms install command to reinstall any previous module version."

		# Remove modules_conf entries from /etc/modules.conf if remake_initrd is set or if this is last instance removed
		if [[ $remake_initrd ]] || \
		    (do_status $module $module_version | grep -q "installed"); then
		    echo $""
		    moduleconfig_remove "$1"
		fi
	    fi
	done
	rm -f "$dkms_tree/$module/kernel-$1-$2"
    else
	echo $""
	echo $"Status: This module version was INACTIVE for this kernel."
    fi

    # Run the post_remove script
    run_build_script post_remove "$post_remove"

    # Run depmod because we changed /lib/modules
    invoke_command "do_depmod $1" "depmod" background

    # Do remake_initrd things (remake initrd)
    if [[ $remake_initrd && $was_active ]] && ! make_initrd "$1" "$2"; then
	warn $"There was a problem remaking your initrd.  You must manually remake it" \
	    $"before booting into this kernel."
    fi

    # Delete the original_module if nothing for this kernel is installed anymore
    if [[ $was_active && -d $dkms_tree/$module/original_module/$1/$2 && \
	! -d $dkms_tree/$module/original_module/$1/$2/collisions ]]; then
	echo $""
	echo $"Removing original_module from DKMS tree for kernel $1 ($2)"
	rm -rf "$dkms_tree/$module/original_module/$1/$2" 2>/dev/null
	[[ $(find $dkms_tree/$module/original_module/$1/* -maxdepth 0 -type d 2>/dev/null) ]] || rm -rf "$dkms_tree/$module/original_module/$1"
    elif [[ $was_active && -d $dkms_tree/$module/original_module/$1/$2/collisions ]]; then
	echo $""
	echo $"Keeping directory $dkms_tree/$module/original_module/$1/$2/collisions/"
	echo $"for your reference purposes.  Your kernel originally contained multiple"
	echo $"same-named modules and this directory is now where these are located."
    fi
    [[ $(find $dkms_tree/$module/original_module/* -maxdepth 0 -type d 2>/dev/null) ]] || rm -rf "$dkms_tree/$module/original_module"

    # Re-add entries to modules.conf if this module/version is still installed on another kernel
    # But only do this if it was just ACTIVE on the kernel we just uninstalled from
    [[ $was_active && $remake_initrd ]] && do_status $module $module_version | grep -q "installed" && moduleconfig_add "$1"

    echo $""
    echo $"DKMS: uninstall completed."
}

# Check our preconditions, and then let do_install do all the hard work.
uninstall_module()
{
    # Check that the right arguments were passed
    check_module_args uninstall

    # Check that $module is in the dkms tree
    [[ -d $dkms_tree/$module ]] || die 2 \
	$"There are no instances of module: $module" \
	$"located in the DKMS tree."

    # Make sure that its installed in the first place
    [[ -d $dkms_tree/$module/$module_version ]] || die 3 \
	$"The module/version combo: $module-$module_version" \
	$"is not located in the DKMS tree."

    # Read the conf file
    read_conf_or_die "$kernelver" "$arch"

    # Only do stuff if module/module version is currently installed
    local kernel_symlink=$(readlink -f "$dkms_tree/$module/kernel-$kernelver-$arch")
    [[ $kernel_symlink = $dkms_tree/$module/$module_version/$kernelver/$arch ]] || die 5 \
	    $"The module $module $module_version is not currently installed." \
	    $"This module is not currently ACTIVE for kernel $kernelver ($arch)."
    do_uninstall "$kernelver" "$arch"
}

# Unregister a DKMS module.  This uninstalls any installed modules along the way.
remove_module()
{
    # Check that the right arguments were passed
    if [[ ! ($module && $module_version) || $kernels_arches_default  ]]; then
	die 1 $"Invalid number of parameters passed." \
	    $"Usage: remove <module>/<module-version> --all" \
	    $"   or: remove <module>/<module-version> -k <kernel-version>"
    fi

    # Check that $module is in the dkms tree
    if ! [[ -d $dkms_tree/$module/$module_version ]]; then
	die 3 $"There are no instances of module: $module" \
	    $"$module_version located in the DKMS tree."
    fi

    local i
    for ((i=0; i < ${#kernelver[@]}; i++)); do
	# make sure its there first before removing
	if ! [[ -d $dkms_tree/$module/$module_version/${kernelver[$i]}/${arch[$i]} ]]; then
	    die 4 $"There is no instance of $module $module_version" \
		$"for kernel ${kernelver[$i]} (${arch[$i]}) located in the DKMS tree."
	fi

	# Do --rpm_safe_upgrade check (exit out and don't do remove if inter-release RPM upgrade scenario occurs)
	if [[ $rpm_safe_upgrade ]]; then
	    local pppid=$(awk '/PPid:/ {print $2}' /proc/$PPID/status)
	    local time_stamp=$(ps -o lstart --no-headers -p $pppid 2>/dev/null)
	    for lock_file in $tmp_location/dkms_rpm_safe_upgrade_lock.$pppid.*; do
		[[ -f $lock_file ]] || continue
		lock_head=$(head -n 1 $lock_file 2>/dev/null)
		lock_tail=$(tail -n 1 $lock_file 2>/dev/null)
		[[ $lock_head = $module-$module_version && $time_stamp && $lock_tail = $time_stamp ]] || continue
		rm -f $lock_file
		die 0 $"DKMS: Remove cancelled because --rpm_safe_upgrade scenario detected."
	    done
	fi

	# Read the conf file
	read_conf_or_die "${kernelver[$i]}" "${arch[$i]}"

	do_uninstall "${kernelver[$i]}" "${arch[$i]}"

	# Delete the $kernel_version/$arch_used part of the tree
	rm -rf "$dkms_tree/$module/$module_version/${kernelver[$i]}/${arch[$i]}"
	[[ $(find $dkms_tree/$module/$module_version/${kernelver[$i]}/* \
	    -maxdepth 0 -type d 2>/dev/null) ]] || \
	    rm -rf "$dkms_tree/$module/$module_version/${kernelver[$i]}"
    done

    # Delete the $module_version part of the tree if no other $module_version/$kernel_version dirs exist
    if ! find $dkms_tree/$module/$module_version/* -maxdepth 0 -type d 2>/dev/null | egrep -qv "(build|tarball|driver_disk|rpm|deb|source)$"; then
	echo $""
	echo $"------------------------------"
	echo $"Deleting module version: $module_version"
	echo $"completely from the DKMS tree."
	echo $"------------------------------"
	rm -rf "$dkms_tree/$module/$module_version"
	echo $"Done."
    fi

    # Get rid of any remnant directories if necessary
    if (($(ls "$dkms_tree/$module" | wc -w | awk '{print $1}') == 0)); then
	rm -rf "$dkms_tree/$module" 2>/dev/null

	# Its now safe to completely remove references in /etc/sysconfig/kernel for SuSE
	etc_sysconfig_kernel_modify "delete"
    fi
}

# Given a kernel object, figure out which DKMS module it is from.
find_module_from_ko()
{
    local ko="$1"
    local basename_ko="${ko##*/}"
    local module
    local kernellink

    for kernellink in "$dkms_tree"/*/kernel-*; do
	[[ -L $kernellink ]] || continue
	module=${kernellink#$dkms_tree/}
	module=${module%/kernel-*}
	diff "$kernellink/module/${basename_ko}" "${ko}" >/dev/null 2>&1 || continue
	rest=$(readlink $kernellink)
	echo "$module/$rest"
	return 0
    done
    return 1
}

# Check to see if modules meeting the passed parameters are weak-installed.
# This function's calling convention is different from the usual DKMS status
# checking functions -- the kernel version we usually have is the one we are currently
# running on, not necessarily the one we compiled the module for.
module_status_weak() {
    # $1 = module, $2 = module version, $3 = kernel version weak installed to,
    # $4 = kernel arch, $5 = kernel version built for
    [[ $weak_modules ]] || return 1
    local weak_ko mod installed_ko f ret=1 oifs=$IFS
    local -a already_found
    for weak_ko in "$install_tree/"*/weak-updates/*; do
	[[ -e $weak_ko ]] || continue
	[[ -L $weak_ko ]] && installed_ko="$(readlink -f "$weak_ko")" || continue
	IFS=/ read m v k a < <(IFS=$oifs find_module_from_ko "$weak_ko") || continue
	kern=${weak_ko#$install_tree/}
	kern=${kern%/weak-updates/*}
	[[ $m = ${1:-*} && $v = ${2:-*} && $k = ${5:-*} && \
	    $a = ${4:-*} && $kern = ${3:-*} ]] || \
	    continue
	ret=0
	for f in "${already_found[@]}"; do
	    [[ $f = $m/$v/$kern/$a/$k ]] && continue 2
	done
	already_found[${#already_found[@]}]="$m/$v/$kern/$a/$k"
	echo "installed-weak $m/$v/$kern/$a/$k"
    done
    return $ret
}

# Print the requested status lines for weak-installed modules.
do_status_weak()
{
    local mvka m v k a kern status
    while read status mvka; do
	IFS=/ read m v k a kern <<< "$mvka"
	echo "$m, $v, $k, $a: installed-weak from $kern"
    done < <(module_status_weak "$@")
}

# Spit out all the extra status information that people running DKMS are
# interested in, but that the DKMS internals do not usually care about.
module_status_built_extra() (
    set_module_suffix "$3"
    read_conf "$3" "$4" "$dkms_tree/$1/$2/source/dkms.conf"
    [[ -d $dkms_tree/$1/original_module/$3/$4 ]] && echo -n " (original_module exists)"
    for ((count=0; count < ${#dest_module_name[@]}; count++)); do
	tree_mod="$dkms_tree/$1/$2/$3/$4/module/${dest_module_name[$count]}$module_suffix"
	if ! [[ -e $tree_mod ]]; then
	    echo -n " (WARNING! Missing some built modules!)"
	elif _is_module_installed "$@"; then
	    real_dest="$(find_actual_dest_module_location "$1" $count "$3" "$4")"
	    if ! diff -q "$tree_mod" "$install_tree/$3${real_dest}/${dest_module_name[$count]}$module_suffix" >/dev/null 2>&1; then
		echo -n " (WARNING! Diff between built and installed module!)"
	    fi
	fi
    done
)

# Return a list of all the modules that are either built or installed.
# This and module_status do some juggling of $IFS to ensure that
# we do not get word splitting where it would be inconvienent.
module_status_built() {
    local ret=1 directory ka k a state oifs="$IFS" IFS=''
    for directory in "$dkms_tree/$1/$2/"${3:-[0-9].*}/${4:-*}; do
	IFS="$oifs"
	ka="${directory#$dkms_tree/$1/$2/}"
	k="${ka%/*}"
	a="${ka#*/}"
	is_module_built "$1" "$2" "$k" "$a" || continue
	ret=0
	state="built"
	_is_module_installed "$1" "$2" "$k" "$a" && \
	    state="installed"
	echo "$state $1/$2/$k/$a"
	IFS=''
    done
    IFS="$oifs"
    return $ret
}

# Return the status of all modules that have been added, built, or installed.
module_status() {
    local oifs="$IFS" IFS='' mv m v directory ret=1
    for directory in "$dkms_tree/"${1:-*}/${2:-*}; do
	IFS="$oifs"
	mv="${directory#$dkms_tree/}"
	m="${mv%/*}"
	v="${mv#*/}"
	is_module_added "$m" "$v" || continue
	ret=0
	module_status_built "$m" "$v" "$3" "$4" || echo "added $m/$v"
	IFS=''
    done
    IFS="$oifs"
    return $ret
}

# Print out the status in the format that people who call DKMS expect.
# Internal callers should use the module_status functions, as their output
# is easier to parse.
do_status() {
    local status mvka m v k a
    while read status mvka; do
	IFS=/ read m v k a <<< "$mvka"
	case $status in
	    added) echo "$m, $v: $status";;
	    built|installed) echo -n "$m, $v, $k, $a: $status"
		module_status_built_extra "$m" "$v" "$k" "$a"
		echo;;
	esac
    done < <(module_status "$@")
}

# Show all our status in the format that external callers expect, even
# though it is slightly harder to parse.
show_status()
{
    local j state_array
    if ((${#kernelver[@]} == 0)); then
	do_status "$module" "$module_version" "$kernelver" "$arch"
	do_status_weak "$module" "$module_version" "$kernelver" "$arch"
    else
	for ((j=0; j < ${#kernelver[@]}; j++)); do
	    do_status "$module" "$module_version" "${kernelver[$j]}" "${arch[$j]}"
	    do_status_weak "$module" "$module_version" "${kernelver[$j]}" "${arch[$j]}"
	done
    fi
}

create_temporary_trees()
{
    [[ $module || $module_version || ! -r dkms.conf ]] && return 0

    . dkms.conf
    module="$PACKAGE_NAME"
    module_version="$PACKAGE_VERSION"

    source_tree=$(mktemp_or_die -d)
    dkms_tree=$(mktemp_or_die -d)

    local source_tree_dir="$source_tree/$PACKAGE_NAME-$PACKAGE_VERSION"
    mkdir -p "$source_tree_dir"
    cp -a * "$source_tree_dir" # intentionally skip .git or .hg
    add_module
    temporary_trees_del_command="rm -rf $source_tree $dkms_tree"
}

delete_temporary_trees()
{
    [[ $temporary_trees_del_command ]] || return 0
    $temporary_trees_del_command
    module=
    module_version=
    source_tree=
    dkms_tree=
    temporary_trees_del_command=
}

in_temporary_trees() { [[ $temporary_trees_del_command ]] ;}

media_valid()
{
    local mrx='^(floppy|iso|tar)$'
    [[ $media =~ $mrx ]]
}

make_driver_disk_floppy()
{
    local image_name="$1"
    local source_dir="$2"
    local file
    local fs='ext2'
    [[ $distro = redhat* ]] && fs='vfat'

    rm -f "$image_name"
    invoke_command "dd if=/dev/zero of=$image_name bs=$(($size/20))k count=20" "making a blank floppy image" background
    case $fs in
	vfat) invoke_command "mkdosfs $image_name" "mkdosfs" background;;
	ext2) invoke_command "mke2fs -F $image_name" "mke2fs" background;;
    esac

    local mntdir=$(mktemp_or_die -d $tmp_location/dkms.XXXXXX)
    invoke_command "mount -o loop -t $fs $image_name $mntdir >/dev/null 2>&1" "loopback mounting disk image"
    [[ -d $mntdir/lost+found ]] && rmdir "$mntdir/lost+found"
    invoke_command "cp -r $source_dir/* $mntdir/" "  copying files to floppy disk image"
    invoke_command "umount $mntdir" "unmounting disk image"
    rm -rf "$mntdir"
}

make_driver_disk_isotar()
{
    local type="$1"
    local image_name="$2"
    local source_dir="$3"
    local file
    case $type in
	iso) invoke_command "mkisofs -v -r -J -pad -V $module -o $image_name ." "mkisofs" background;;
	tar) invoke_command "tar cvf $image_name ." "tar" background;;
    esac
}

make_driver_disk_media()
{
    echo "Copying files $2"

    case $media in
	floppy*) make_driver_disk_floppy "$1" "$2";;
	iso*) make_driver_disk_isotar "iso" "$1" "$2";;
	tar*) make_driver_disk_isotar "tar" "$1" "$2";;
    esac
}

driver_disk_suffix()
{
    case $media in
	floppy*) echo "img";;
	iso*) echo "iso";;
	tar*) echo "tar";;
    esac
}

make_redhat_driver_disk()
{
    local i count
    # kludge to allow redhat1 driver disks with BOOT kernel modules (arch i386)
    if [[ $distro = redhat1 && $multi_arch = true ]]; then
	local redhat1_okay="true"
	local other_arch=""
	for ((i=0; i < ${#kernelver[@]}; i++)); do
	    if [[ ${arch[$i]} != i386 && $other_arch != ${arch[$i]} && $other_arch ]]; then
		die 3 $"You have specified a Red Hat version 1 driver disk, but have also" \
		    $"specified multiple architectures.  Version 1 does not support this." \
		    $"Use 'redhat2' instead (only OSes >= RHEL3, FC1 support version 2)."
	    elif [[ ${arch[$i]} != i386 && $other_arch != ${arch[$i]} && ! $other_arch ]]; then
		other_arch="${arch[$i]}"
	    fi
	done
    fi
    if [ "$distro" == "redhat2" ]  && [ -z "$redhat1_okay" ]; then
	echo $"Creating Red Hat v2 driver disk (arch support)."
	echo $""
	local rhdd_filename="rhdd"
    elif [ "$distro" == "redhat3" ] && [ -z "$redhat1_okay" ]; then
        echo $"Creating Red Hat v3 driver disk."
	echo $""
	make_redhat3_driver_disk
	return
    else
	echo $"Creating Red Hat v1 driver disk."
	echo $""
	local rhdd_filename="rhdd-6.1"
    fi

    cpioarchive_dir_name=$(mktemp_or_die -d $tmp_location/dkms.XXXXXX)

    for ((i=0; i < ${#kernelver[@]}; i++)); do
	set_module_suffix "${kernelver[$i]}"

	local dd_prefix="${kernelver[$i]}"
	[[ $distro = redhat2 ]] && dd_prefix="${kernelver[$i]}/${arch[$i]}"
	[[ $multi_arch = true && -z $redhat1_okay ]] && dd_prefix="${kernelver[$i]}/${arch[$i]}"
	maybe_build_module "$module" "$module_version" "${kernelver[$i]}" "${arch[$i]}" || {
	    rm -rf "$cpioarchive_dir_name"
	    die 5 $"Cannot build $module/$module_version for redhat driver disk."
	}
	
	# FIXME: add check for KMP binary RPMs to include in the driver disk
	if [[ ! $kernel_version_list ]]; then
	    kernel_version_list="kernel${kernelver[$i]}-${arch[$i]}"
	else
	    kernel_version_list="$kernel_version_list-kernel${kernelver[$i]}-${arch[$i]}"
	fi
	mkdir -p $cpioarchive_dir_name/$dd_prefix
	for f in "$dkms_tree/$module/$module_version/${kernelver[$i]}/${arch[$i]}/module/"*"$module_suffix"; do
	    [[ -f $f ]] || continue
	    echo "Marking ${f#$dkms_tree/$module/$module_version/}..."
	    cp "$f" "$cpioarchive_dir_name/$dd_prefix/"
	    modules_cgz_list="$dd_prefix/${f##*/} ${modules_cgz_list}"
	done

    done

    # Create directory and necessary files
    driver_disk_dir=$(mktemp_or_die -d $tmp_location/dkms.XXXXXX)

    # Copy files for the driver disk (or warn if not present)
    local files_for_driverdisk="modinfo disk-info modules.dep pcitable modules.pcimap pci.ids"
    # Fedora Core 5 and higher, RHEL5 and higher, strictly require: rhdd, modules.cgz, modinfo, modules.alias, modules.dep
    # which is in effect ignoring disk-info, pcitable, modules.pcimap and pci.ids
    # and adding modules.alias, which will be generated.

    local files_into_driverdisk="modules.cgz $rhdd_filename modules.alias"
    for file in $files_for_driverdisk; do
	if [[ -e $dkms_tree/$module/$module_version/source/redhat_driver_disk/$file ]]; then
	    files_into_driverdisk="$file $files_into_driverdisk"
	    cp -f "$dkms_tree/$module/$module_version/source/redhat_driver_disk/$file" "$driver_disk_dir/" 2>/dev/null
	else
	    warn $"File: $file not found in $dkms_tree/$module/$module_version/source/redhat_driver_disk/"
	fi
    done
    echo "$module-$module_version driver disk" > "$driver_disk_dir/$rhdd_filename"

    # Make sure the kernel_version_list is not too long
    if (( $(echo $kernel_version_list | wc -m | awk {'print $1'}) > 200 )); then
	kernel_version_list="manykernels"
    fi

    local suffix="$(driver_disk_suffix)"
    local image_dir="$dkms_tree/$module/$module_version/driver_disk"
    local image_name="$module-$module_version-$kernel_version_list-dd.$suffix"
    echo $""
    echo $"Creating driver disk on $media media:"
    cd "$cpioarchive_dir_name"
    invoke_command "echo '$modules_cgz_list' | cpio -oH crc 2>/dev/null | gzip -9 > ./modules.cgz" "compressing modules.cgz" background
    cp -f ./modules.cgz "$driver_disk_dir/"

    # generate modules.alias file
    # On 2.4 kernels and kernels with no aliases. this won't yield anything.
    touch ./modules.alias
    for f in ${modules_cgz_list}; do
	module_wo_suffix=$(basename ${f} ${module_suffix})
	tmp_alias="./modules.alias.${module_wo_suffix}"
	f="./${f}"
	depmod -n ${f} 2>/dev/null | grep ^alias > ${tmp_alias}
	if [[ -s ${tmp_alias} ]]; then
	    cat "${tmp_alias}" >> ./modules.alias
	fi
    done
    [[ -e ./modules.alias ]] && cp -f ./modules.alias "$driver_disk_dir/"
    # FIXME: add rpms/ directory, copy in KMP RPMs, run createrepo --pretty

    cd - >/dev/null
    rm -rf "$cpioarchive_dir_name"

    mkdir -p "$image_dir"
    rm -f "$image_dir/$image_name"

    cd "$driver_disk_dir"
    make_driver_disk_media "$image_dir/$image_name" "$driver_disk_dir"
    cd - >/dev/null
    rm -rf "$driver_disk_dir"

    echo $""
    echo $"Disk image location: $image_dir/$image_name"
    echo $""
    echo $"DKMS: mkdriverdisk completed."
}

make_driver_disk()
{
    # Check that the right arguments were passed
    if ! [[ $module && $module_version && $distro && $kernelver ]]; then
	die 1 $"Invalid number of parameters passed." \
	$"Usage: mkdriverdisk <module>/<module-version> -d <distro> -k <kernelver> [--media floppy|iso|tar]"
    fi

    # default to floppy media
    [[ $media ]] || media="floppy"
    if ! media_valid; then
	die 1 $"Media $media is invalid." \
	    $"Usage: mkdriverdisk <module>/<module-version> -d <distro> -k <kernelver> [--media floppy|iso|tar]"
    fi

    # Check that source symlink works
    check_module_exists

    # Confirm that distro is supported
    case $distro in
	redhat | redhat[123] | suse | UnitedLinux | ubuntu) ;;
	*) die 3 $"Invalid distro argument. Currently, the distros" \
		$"supported are: redhat, redhat1, redhat2, redhat3, suse, UnitedLinux" \
		$"               ubuntu";;
    esac

    # Read the conf file
    read_conf_or_die "$kernelver" "$arch"

    case $distro in
	redhat*) make_redhat_driver_disk;;
	ubuntu) make_ubuntu_driver_disk;;
	*) make_suse_driver_disk;;
    esac
}

find_external_dependencies()
{
    local mod count i
    local -a deps

    # find all module dependencies
    for ((count=0; count < ${#dest_module_name[@]}; count++)); do
	for ((i=0; i < ${#kernelver[@]}; i++)); do
	    set_module_suffix "${kernelver[$i]}"
	    mod="$dkms_tree/$module/$module_version/${kernelver[$i]}/${arch[$i]}/module/${dest_module_name[$count]}$module_suffix"
	    deps=(${deps[@]} $(modinfo "$mod" | sed -n 's/,/ /g; s/^depends: *//p'))
	done
    done

    # prune internally satisfied dependencies

    for ((i=0; i < ${#deps[@]}; i++)); do
	for mod in ${dest_module_name[@]}; do
	    [[ ${deps[i]} = $mod ]] && deps[i]=
	done
    done

    for dep in "${deps[@]}"; do
	echo $dep
    done | sort -u
}

make_suse_driver_disk()
{
    [[ $release ]] || die 3 \
	$"Invalid number of parameters passed for suse/UnitedLinux driver disk." \
	$"Usage: mkdriverdisk <module>/<module-version> -d <distro> -k <kernelver>" \
	$"                    -r <release-number>"

    local driver_disk_dir=$(mktemp_or_die -d $tmp_location/dkms.XXXXXX)
    local suffix="$(driver_disk_suffix)"
    local image_dir="$dkms_tree/$module/$module_version/driver_disk"
    local image_name="$module-$module_version-$distro-$release-dd.$suffix"

    echo $""
    echo $"Creating driver disk:"

    local deps="$(find_external_dependencies)"

    local offset=0
    # reserve a place for dependencies
    [[ ${deps[@]} ]] && offset=1
    
    local count
    for ((count=0; count < ${#dest_module_name[@]}; count++)); do
	local i
	local topdir=$(printf "%02d" $(($count+1+offset)))
	for ((i=0; i < ${#kernelver[@]}; i++)); do
	    set_module_suffix "${kernelver[$i]}"

	    if ! [[ -e $dkms_tree/$module/$module_version/${kernelver[$i]}/${arch[$i]}/module/${dest_module_name[$count]}$module_suffix ]]; then
		rm -rf $temp_dir_name
		die 5 \
		    $"Cannot find module ${dest_module_name[$count]}$module_suffix for kernel ${kernelver[$i]} (${arch[$i]})." \
		    $"Module/version must be in built state before making a driver disk."
	    fi
	    # FIXME: add check for KMP binary RPMs to include in the driver disk
	    suse_arch=${arch[$i]}
	    case $suse_arch in
		i?86) suse_arch=i386;;
	    esac

	    echo "Marking ${kernelver[$i]}/${arch[$i]}/modules/${dest_module_name[$count]}$module_suffix..."
	    mkdir -p "$driver_disk_dir/$topdir/$suse_arch-$release/install/lib/modules/${kernelver[$i]}${dest_module_location[$count]}"
	    cp "$dkms_tree/$module/$module_version/${kernelver[$i]}/${arch[$i]}/module/${dest_module_name[$count]}$module_suffix" "$driver_disk_dir/$topdir/$suse_arch-$release/install/lib/modules/${kernelver[$i]}${dest_module_location[$count]}/"

	    case ${kernelver[$i]} in
		*-default)
		    mkdir -p "$driver_disk_dir/$topdir/$suse_arch-$release/modules/"
		    cp "$dkms_tree/$module/$module_version/${kernelver[$i]}/${arch[$i]}/module/${dest_module_name[$count]}$module_suffix" "$driver_disk_dir/$topdir/$suse_arch-$release/modules/"
		    ;;
	    esac

	    # create directory for dependency information
	    [[ ${deps[@]} ]] && mkdir -p "$driver_disk_dir/01/linux/$distro/$suse_arch-$release/modules"

	done

	# ---
	for arch_release in $(find $driver_disk_dir/$topdir -maxdepth 1 -mindepth 1 -type d | sed "s#$driver_disk_dir\/$topdir\/##"); do
	    cd "$driver_disk_dir/$topdir/$arch_release/install/"
	    invoke_command "tar cvzf update.tar.gz lib/" "making update.tar.gz for $arch_release" background
	    cd - >/dev/null

	    mkdir -p "$driver_disk_dir/$topdir/linux/$distro/$arch_release/install"
	    mkdir -p "$driver_disk_dir/$topdir/linux/$distro/$arch_release/modules"

	    echo $"  copying update.tar.gz for $arch_release to disk image..."
	    cp -f "$driver_disk_dir/$topdir/$arch_release/install/update.tar.gz" "$driver_disk_dir/$topdir/linux/$distro/$arch_release/install/"

	    postkernels=
	    archtest=${arch_release/-*}
	    for ((i=0; i<${#kernelver[@]}; i++)); do
		[[ ${arch[$i]} = ${archtest} ]] && \
		    postkernels="${postkernels} ${kernelver[$i]}"
	    done

	    if [[ ${postkernels} ]]; then
		dstfile="$driver_disk_dir/$topdir/linux/$distro/$arch_release/install/update.post"
		echo $"  creating update.post for $arch_release..."
		(cat << EOF
#!/bin/sh

kernlist="${postkernels}"

for kernel in \${kernlist}; do
    if [ -e /boot/System.map-\${kernel} ]; then
	depmod -a -F /boot/System.map-\${kernel} \${kernel}
    fi
done

EOF
		) > ${dstfile}
		chmod a+x ${dstfile}
	    fi

	    if [[ -d $driver_disk_dir/$topdir/$arch_release/modules/ ]]; then
		echo $"  copying kernel modules for installation kernel to disk image..."
		cp -f $driver_disk_dir/$topdir/$arch_release/modules/* $driver_disk_dir/$topdir/linux/$distro/$arch_release/modules/ 2>/dev/null
	    else
		warn $"No kernel modules found for -default kernel."
	    fi

	    rm -fr "$driver_disk_dir/$topdir/$arch_release"
	done
    done

    local dir
    if [[ ${deps[@]} ]]; then
	for dir in "$driver_disk_dir/01/linux/$distro/"*"/modules"; do
	    for dep in "${deps[@]}"; do
		echo $dep >> "$dir/module.order"
	    done
	done
    fi

    # FIXME: add suse-equivalent rpms/ directory, copy in KMP RPMs, run createrepo --pretty

    mkdir -p "$image_dir"
    rm -f "$image_dir/$image_name"
    cd "$driver_disk_dir"
    make_driver_disk_media "$image_dir/$image_name" "$driver_disk_dir"
    cd - >/dev/null
    rm -rf "$driver_disk_dir"

    echo $""
    echo $"Disk image location: $dkms_tree/$module/$module_version/driver_disk/$image_name"
    echo $""
    echo $"DKMS: mkdriverdisk completed."
}

make_ubuntu_driver_disk()
{
    local suffix="$(driver_disk_suffix)"
    local image_dir="$dkms_tree/$module/$module_version/driver_disk"
    local image_name="$module-$module_version-$distro-dd.$suffix"

    local tempdir=$(mktemp_or_die -d $tmp_location/dkms.XXXXXX)

   # Check that the dh_make command is present
   if ! which dpkg-deb >/dev/null 2>&1 ; then
	die 1 $"dpkg-deb not present." \
	    $"Install the dpkg-dev package."
   fi

   local i
   for ((i=0; i < ${#kernelver[@]}; i++)); do
      set_module_suffix "${kernelver[$i]}"
      # Driver disks only recognize i386 as package arch
      local karch=${arch[$i]/i?86/i386}
      local kvers=${kernelver[$i]/-/_}; kvers=${kvers%%_*}
       # ubuntu-drivers/<kver>/*_<debarch>.deb
      local dd_prefix="ubuntu-drivers/$kvers"
      local dd_suffix="_${karch}.deb"
      maybe_build_module "$module" "$module_version" "${kernelver[$i]}" "${arch[$i]}" || {
	  rm -rf "$tempdir"
	  die 5 $"Unable to build $module/$module_version for Ubuntu driver disk."
      }
      mkdir -p "$tempdir/$dd_prefix"
      local deb_dir="$tempdir/$dd_prefix/debian"
      local deb_lib_dir="$deb_dir/lib/modules/${kernelver[$i]}/updates/dkms"
      mkdir -p "$deb_lib_dir"
      cp "$dkms_tree/$module/$module_version/${kernelver[$i]}/${arch[$i]}/module/"*"$module_suffix" "$deb_lib_dir"
      pushd "$deb_dir" > /dev/null 2>&1
      mkdir DEBIAN
      cat > DEBIAN/control <<EOF
Package: ${module}-modules-${kernelver[$i]}
Version: ${module_version}-1
Section: misc
Priority: optional
Architecture: $karch
Depends:
Maintainer: DKMS <dkms-devel@dell.com>
Description: DKMS packaged binary driver update
 DKMS automagically generated debian package for
 driver update disks, used with Ubuntu installation
 programs (such as Ubiquity).
EOF

       # Generate the DEBIAN/preinst file.
       # This is tricky as we need some parts evaluated now
       # and some parts evaluated at runtime
cat >DEBIAN/preinst <<EOF
#!/bin/bash
[[ \$(uname -r) = ${kernelver[$i]} ]] || exit 1
exit 0
EOF
       chmod 0775 DEBIAN/preinst
       cd "$tempdir/$dd_prefix"
       dpkg-deb --build debian
       mv debian.deb "${module}_${module_version}-${kernelver[$i]}${dd_suffix}"
       rm -rf debian
       popd > /dev/null 2>&1
   done

   echo "Copying source..."
   mkdir -p "$tempdir/ubuntu"
   cp -ar "$source_tree/$module-$module_version" "$tempdir/ubuntu/"

   mkdir -p "$image_dir"
   rm -f "$image_dir/$image_name"
   cd "$tempdir"
   make_driver_disk_media "$image_dir/$image_name" "$tempdir"
   cd - >/dev/null
   rm -rf "$tempdir"
   echo $""
   echo $"Disk image location: $dkms_tree/$module/$module_version/driver_disk/$image_name"
   echo $""
   echo $"DKMS: mkdriverdisk completed."
}

make_tarball()
{
    make_common_test "mktarball"

    # Check for dkms_dbversion
    if ! [[ -e $dkms_tree/dkms_dbversion ]]; then
	echo $"" >&2
	echo $"Could not find the file $dkms_tree/dkms_dbversion." >&2
	echo $"Creating w/ default contents." >&2
	echo "2.0.0" > $dkms_tree/dkms_dbversion
    fi

    # Read the conf file
    read_conf_or_die "$kernelver" "$arch"

    temp_dir_name=$(mktemp_or_die -d $tmp_location/dkms.XXXXXX)
    mkdir -p $temp_dir_name/dkms_main_tree

    if [[ $source_only ]]; then
	kernel_version_list="source-only"
    else
	local i
	for ((i=0; i<${#kernelver[@]}; i++)); do
	    if ! [[ -d $dkms_tree/$module/$module_version/${kernelver[$i]}/${arch[$i]} ]]; then
		rm -rf "$temp_dir_name" 2>/dev/null
		die 6  $"No modules built for ${kernelver[$i]} (${arch[$i]})." \
		    $"Modules must already be in the built state before using mktarball."
	    fi

	    set_module_suffix "${kernelver[$i]}"

	    echo "Marking modules for ${kernelver[$i]} (${arch[$i]}) for archiving..."
	    if [[ ! $kernel_version_list ]]; then
		kernel_version_list="kernel${kernelver[$i]}-${arch[$i]}"
	    else
		kernel_version_list="${kernel_version_list}-kernel${kernelver[$i]}-${arch[$i]}"
	    fi
	    mkdir -p "$temp_dir_name/dkms_main_tree/${kernelver[$i]}/${arch[$i]}"
	    cp -rf "$dkms_tree/$module/$module_version/${kernelver[$i]}/${arch[$i]}" "$temp_dir_name/dkms_main_tree/${kernelver[$i]}"
    done
    fi

    # Store the dkms_dbversion in the tarball
    cp -f "$dkms_tree/dkms_dbversion" "$temp_dir_name/dkms_main_tree/"

    # Copy the source_tree or make special binaries-only structure
    if [[ $binaries_only ]]; then
	echo $""
	echo $"Creating tarball structure to specifically accomodate binaries."
	mkdir $temp_dir_name/dkms_binaries_only
	echo "$module" > $temp_dir_name/dkms_binaries_only/PACKAGE_NAME
	echo "$module_version" > $temp_dir_name/dkms_binaries_only/PACKAGE_VERSION
	[[ ! $conf ]] && conf="$dkms_tree/$module/$module_version/source/dkms.conf"
	cp -f $conf $temp_dir_name/dkms_binaries_only/ 2>/dev/null
    else
	echo $""
	echo $"Marking $dkms_tree/$module/$module_version/source for archiving..."
	mkdir -p $temp_dir_name/dkms_source_tree
	cp -rf $dkms_tree/$module/$module_version/source/* $temp_dir_name/dkms_source_tree
    fi

    if (( $(echo $kernel_version_list | wc -m | awk {'print $1'}) > 200 )); then
	kernel_version_list="manykernels"
    fi

    local tarball_name="$module-$module_version-$kernel_version_list.dkms.tar.gz"
    local tarball_dest="$dkms_tree/$module/$module_version/tarball/"

    # Die if we will not be able to create the tarball due to permissions.
    if [[ $archive_location ]]; then
	tarball_name="${archive_location##*/}"
        if [[ ${archive_location%/*} != $archive_location && \
            -d ${archive_location%/*} && -w ${archive_location%/*} ]]; then
            tarball_dest="${archive_location%/*}"
        elif [[ ${archive_location%/*} != $archive_location ]] && ! mkdir -p $tarball_dest; then
	    die 9 $"Will not be able to create $archive_location due to a permissions problem."
	fi
    fi
    if [ ! -d $tarball_dest ]; then
	mkdir -p "$dkms_tree/$module/$module_version/tarball/"
    fi

    echo $""
    echo $"Tarball location: $tarball_dest/$tarball_name"

    local tarball_ext=${tarball_name##*.}
    [[ $tarball_ext = tar ]] || tarball_name=${tarball_name%.$tarball_ext}

    # Make the tarball
    cd $temp_dir_name
    if tar -cf $temp_dir_name/$tarball_name ./* 2>/dev/null; then
	cd - >/dev/null
	echo $""
	mv -f "$temp_dir_name/$tarball_name" "$tarball_dest/$tarball_name"
	rm -rf $temp_dir_name
    else
	cd - >/dev/null
	rm -rf $temp_dir_name
	die 6 $"Failed to make tarball."
    fi
    case $tarball_ext in
	gz) gzip -9 "$tarball_dest/$tarball_name";;
	bz2) bzip2 -9 "$tarball_dest/$tarball_name";;
	xz) xz -9 "$tarball_dest/$tarball_name";;
    esac
    echo $""
    echo $"DKMS: mktarball completed."
}

# A tiny helper function to make sure dkms.conf describes a valid package.
get_pkginfo_from_conf() {
    [[ -f $1 && $1 = *dkms.conf ]] || return
    read_conf_or_die "$kernelver" "$arch" "$1"
    [[ $PACKAGE_NAME && $PACKAGE_VERSION ]]
}

# Unpack a DKMS tarball from a few different supported formats.
# We expect $archive_location to have been passed either as a raw argument or
# with --archive.
load_tarball()
{
    # Error out if $archive_location does not exist
    if [[ ! -e $archive_location ]]; then
	die 2 $"$archive_location does not exist."
    fi

    # If it is an .rpm file. install it with rpm, run an autoinstall, and then exit.
    if [[ $archive_location = *.rpm ]]; then
       if rpm -Uvh "$archive_location"; then
           autoinstall
           exit $?
       else
           die 9 $"Unable to install $archive_location using rpm." \
               $"Check to ensure that your system can install .rpm files."
       fi
    fi

    # Figure out what kind of archive it is (tar.gz, tar, tar.bz, etc)
    # Note that this does not depend on the extensions being correct.
    local tar_options=""
    for xpand in gzip bzip xz; do
	$xpand -t $archive_location 2>/dev/null || continue
	case $xpand in
	    gzip) tar_options=z;;
	    bzip2) tar_options=j;;
	    xz) tar_options=J;;
	esac
	break
    done

    # Untar it into $tmp_location
    local temp_dir_name=$(mktemp_or_die -d $tmp_location/dkms.XXXXXX)
    trap 'rm -rf $temp_dir_name' EXIT
    tar -${tar_options}xf $archive_location -C $temp_dir_name

    if [[ ! $temp_dir_name/dkms_main_tree ]]; then
	# Tarball was not generated from mktarball.
	# Just find the dkms.conf file and load the source.
	conf=$(find $temp_dir_name/ -name dkms.conf 2>/dev/null | head -n 1)
	if [[ ! $conf ]]; then
	    rm -rf $temp_dir_name
	    die 3 $"Tarball does not appear to be a correctly formed" \
		$"DKMS archive. No dkms.conf found within it."
	fi
	add_source_tree "${conf%dkms.conf}"
	return
    fi

    # Check that dkms_dbversion is not a future version
    # As far as I can tell, the only reason we bother with this is for detecting
    # whether we have arch support or not, which we can also determine by examining
    # the structure of the tarball.
    db_from_tarball=$(cat $temp_dir_name/dkms_main_tree/dkms_dbversion 2>/dev/null)
    db_from_dkms=$(cat $dkms_tree/dkms_dbversion 2>/dev/null)
    if [[ $db_from_tarball && $db_from_dkms && \
	$(VER "$db_from_tarball") > $(VER "$db_from_dkms") ]]; then
	die 9 \
	    $"The tarball you are trying to load indicates it is database version" \
	    $"$db_from_tarball.  This version of DKMS only supports $db_from_dkms or lower."
    fi

    # Make sure its a sane tarball. Sane ones will have one of the two
    # directories we test for.
    for loc in dkms_source_tree dkms_binaries_only ''; do
	if [[ ! $loc ]]; then
	    die 7 $"No valid dkms.conf in dkms_source_tree or dkms_binaries_only." \
		$"$archive_location is not a valid DKMS tarball."
	fi
	local conf="$temp_dir_name/$loc/dkms.conf"
	[[ -f $conf ]] || continue
	if ! get_pkginfo_from_conf "$conf"; then
	    echo >&2
	    echo $"Malformed dkms.conf, refusing to load." >&2
	    continue
	fi
	if is_module_added "$PACKAGE_NAME" "$PACKAGE_VERSION" && \
	    [[ ! $force ]]; then
	    die 8  $"$PACKAGE_NAME-$PACKAGE_VERSION is already added!" \
		$"Aborting."
	fi
	module="$PACKAGE_NAME"; module_version="$PACKAGE_VERSION"
	echo $""
	echo $"Loading tarball for $module-$module_version"
	case $loc in
	    dkms_source_tree)
		add_source_tree "$temp_dir_name/dkms_source_tree";;
	    dkms_binaries_only)
		#if there is a source tree on the system already, don't build a binaries stub
		if [[ ! -d $source_tree/$module-$module_version ]]; then
			echo $"Creating $dkms_tree/$module/$module_version/source"
			mkdir -p "$dkms_tree/$module/$module_version/source"
			echo $"Copying dkms.conf to $dkms_tree/$module/$module_version/source..."
			cp -rf "$temp_dir_name/dkms_binaries_only/dkms.conf" "$dkms_tree/$module/$module_version/source"
		fi;;
	    *)  die 8 $"$FUNCNAME:$LINENO: Cannot happen." \
		$"Report this error to dkms-devel@dell.com";;
	esac
	break
    done

    # at this point, the source has been copied to the appropriate location
    # and registered with dkms, or a binary-only config has been noted.
    # Now, add any included precompiled modules.

    # Is tarball from before DKMS 2.0 (prior to arch support)
    if [[ ! -e $temp_dir_name/dkms_main_tree/dkms_dbversion ]]; then
	[[ $loc = dkms_binaries_only ]] && rm -rf "$dkms_tree/$module/$module_version/source"
	die 10 $" This tarball was created with dkms < 2.0 and contains" \
	    $"no arch info. DKMS is refusing to install precompiled modules."
    fi

    # Load precompiled modules.
    for directory in "$temp_dir_name/dkms_main_tree"/*/*; do
	[[ -d $directory ]] || continue
	kernel_arch_to_load=${directory/*dkms_main_tree\/}
	dkms_dir_location="$dkms_tree/$module/$module_version/$kernel_arch_to_load"
	if [[ -d $dkms_dir_location && ! $force ]]; then
	    warn $"$dkms_dir_location already exists.  Skipping..."
	else
	    echo $"Loading $dkms_dir_location..."
	    rm -rf $dkms_dir_location
	    mkdir -p $dkms_dir_location
	    cp -rf $directory/* $dkms_dir_location/
	fi
    done

    echo $""
    echo $"DKMS: ldtarball completed."
    [[ $loc != dkms_binaries_only ]] || [[ -d $source_tree/$module-$module_version ]]
}

run_match()
{
    set_kernel_source_dir "$kernelver"

    # Error if $template_kernel is unset
    if [[ ! $template_kernel ]]; then
	die 1 $"Invalid number of parameters passed." \
	    $"Usage: match --templatekernel=<kernel-version> -k <kernel-version>" \
	    $"   or: match --templatekernel=<kernel-version> -k <kernel-version> <module>"
    fi

    # Error out if $template_kernel = $kernel_version
    if [[ $template_kernel = $kernelver ]]; then
	die 2 $"The templatekernel and the specified kernel version are the same."
    fi

    # Read in the status of template_kernel
    local template_kernel_status=$(do_status '' '' $template_kernel $arch | grep ": installed")

    # If $module is set, grep the status only for that module
    if [[ $module ]]; then
	# Make sure that its installed in the first place
	if ! [[ -d $dkms_tree/$module/ ]]; then
	    die 3 $"The module: $module is not located in the DKMS tree."
	fi
	template_kernel_status=$(echo "$template_kernel_status" | grep "^$module,")
    fi

    echo $""
    echo $"Matching modules in kernel: $kernelver ($arch)"
    echo $"to the configuration of kernel: $template_kernel ($arch)"

    # Prepare the kernel just once but only if there is actual work to do
    if [[ ! $template_kernel_status ]]; then
	echo $""
	echo $"There is nothing to be done for this match."
    else
	prepare_kernel "$kernelver" "$arch"

	# Iterate over the kernel_status and match kernel to the template_kernel
	while read template_line; do
	    template_module=`echo "$template_line" | awk {'print $1'} | sed 's/,$//'`
	    template_version=`echo "$template_line" | awk {'print $2'} | sed 's/,$//'`

	    # Print out a match header
	    echo $""
	    echo $"---- Match Beginning ----"
	    echo $"Module:  $template_module"
	    echo $"Version: $template_version"
	    echo $"-------------------------"

	    # Figure out what to do from here
	    if show_status "$template_module" "$template_version" "$kernelver" "$arch" 2>/dev/null | grep -q ": installed"; then
		echo $""
		echo $"This module/version combo is already installed.  Nothing to be done."
	    elif show_status "$template_module" "$template_version" "$kernelver" "$arch" 2>/dev/null | grep -q ": built"; then
		echo $""
		echo $"This module/version combo is built.  Installing it:"
		module="$template_module"
		module_version="$template_version"
		install_module
	    else
		echo $""
		echo $"Building & Installing this module/version:"
		module="$template_module"
		module_version="$template_version"
		build_module
		install_module
	    fi
	done < <(echo "$template_kernel_status")

	# Clean up the kernel tree
	if [[ ! ( $(VER $kernelver) < $(VER 2.6.6) ) && \
	    -d "$kernel_source_dir" && \
	    ! -h "$kernel_source_dir" && \
	    -z "$ksourcedir_fromcli" ]]; then
	    echo $"Kernel cleanup unnecessary for this kernel.  Skipping..."
	elif [[ ! $no_clean_kernel ]]; then
	    cd "$kernel_source_dir"
	    [[ $kerneldoth_contents ]] || invoke_command "make mrproper" "cleaning kernel tree (make mrproper)" background
	    [[ $config_contents ]] && echo "$config_contents" > .config
	    [[ $kerneldoth_contents ]] && echo "$kerneldoth_contents" > /boot/kernel.h
	    cd - >/dev/null
	fi
    fi

    # Done
    echo $""
    echo $"DKMS: match completed."
}

make_rpm()
{
    make_common_test "mkrpm"

    # Check that the rpmbuild command is present
    if ! which rpmbuild >/dev/null 2>&1 ; then
	die 1 $"rpmbuild not present." \
	    $"Install the rpm-build package."
    fi

    # Read the conf file
    read_conf_or_die "$kernelver" "$arch"

    local rpm_basedir="$dkms_tree/$module/$module_version/rpm"

    echo $""
    local sp
    for sp in "$dkms_tree/$module/$module_version/source/$module-dkms-mkrpm.spec" \
	"/etc/dkms/template-dkms-mkrpm.spec"; do
	[[ -e $sp ]] || continue
	SPECFILE="$sp"
	break
    done
    if [[ ! $SPECFILE ]]; then
	die 5 $"Cannot find $sp which is needed by" \
	    $"DKMS in order use mkrpm."
    fi

    # Run a dkms mktarball for use in the rpm
    local mktarball_line
    if [[ ! $source_only || $binaries_only ]]; then
	mktarball_line="--binaries-only"
	local i
	echo $""
	for ((i=0; i<${#kernelver[@]}; i++)); do
	    if ! [[ -d $dkms_tree/$module/$module_version/${kernelver[$i]}/${arch[$i]} ]]; then
	    die 5 $"You do not seem to have $module $module_version built for" \
		$"${kernelver[$i]} (${arch[$i]}).  All modules must be in" \
		$"the built state before you can use mkrpm."
	    fi
	    echo $"Marking ${kernelver[$i]} (${arch[$i]}) for RPM..."
	    mktarball_line="-k ${kernelver[$i]} -a ${arch[$i]} $mktarball_line"
	done
    else
	mktarball_line="none"
    fi

    local temp_dir_name=$(mktemp_or_die -d $tmp_location/dkms.XXXXXX)
    trap 'rm -rf $temp_dir_name' EXIT HUP TERM
    mkdir -p ${temp_dir_name}/{BUILD,RPMS,SRPMS,SPECS,SOURCES}
    cp ${SPECFILE} ${temp_dir_name}/SPECS/dkms_mkrpm.spec

    #if using legacy mode, install common postinst
    if ((legacy_postinst != 0)); then
	invoke_command "cp '$PREFIX/usr/lib/dkms/common.postinst' '${temp_dir_name}/SOURCES'" "copying legacy postinstall template"
    fi

    #Copy in the source tree
    if [[ ! $binaries_only ]]; then
	invoke_command "cp -Lpr '$dkms_tree/$module/$module_version/source' '${temp_dir_name}/SOURCES/$module-$module_version'" "Copying source tree"
    fi

    if invoke_command "LC_ALL=C rpmbuild --define \"_topdir ${temp_dir_name}\" --define \"version $module_version\" --define \"module_name $module\" --define \"kernel_versions ${kernelver[*]}\" --define \"mktarball_line $mktarball_line\" --define \"__find_provides  /usr/lib/dkms/find-provides\" --define \"_use_internal_dependency_generator 0\" -ba ${temp_dir_name}/SPECS/dkms_mkrpm.spec > ${temp_dir_name}/rpmbuild.log 2>&1" "rpmbuild"; then
	mkdir -p ${rpm_basedir}
	cp -a ${temp_dir_name}/SRPMS/* ${temp_dir_name}/RPMS/*/* ${rpm_basedir}/
	echo $""
	cat ${temp_dir_name}/rpmbuild.log | grep ^Wrote | sed -e "s:${temp_dir_name}/:${rpm_basedir}/:" -e 's:SRPMS/::' -e 's:RPMS/.*/::'
	echo $""
	echo $"DKMS: mkrpm completed."
    else
	cat ${temp_dir_name}/rpmbuild.log >&2
	die 7 $"There was a problem creating your rpm."
    fi
    rm -rf $temp_dir_name
    trap > /dev/null 2>&1
}

preproc_file()
{
    local date_str="$(date -R)"
    echo "modifying $1..."
    sed -e "s/DEBIAN_PACKAGE/$debian_package/g" \
	-e "s/MODULE_NAME/$module/g" \
	-e "s/MODULE_VERSION/$module_version/g" \
	-e "s/DATE_STAMP/$date_str/" "$1" > "$1.dkms-pp"
    mv "$1.dkms-pp" "$1"
}

# Install a package on a debian system.
debian_install()
{
    local getroot  tmpfile i
    local -a packages=("$@")
    for ((i=0; i < ${#packages[@]}; i++)); do
	dpkg-query -s "${packages[$i]}"| egrep -q '^Status:.* installed$' || continue
	unset package[$i]
    done
    # if they are already installed, we are OK.
    [[ ${package[@]} ]] || return
    if ((UID != 0)); then
	# figure out how to get root
	for getroot in su-to-root gksudo kdesu sudo; do
	    which $getroot >/dev/null 2>&1 || continue
	    case $getroot in
		su-to-root) getroot="$getroot -c";;
		gksudo) [[ $DISPLAY ]] || continue
		    getroot="$getroot --description 'DKMS Debian package builder' ";;
		kdesu) [[ $DISPLAY ]] || continue;;
	    esac
	    break
	done
    fi
    if [[ -x /usr/sbin/synaptic && $DISPLAY ]] && tmpfile=$(mktemp_or_die); then
	# Pretty GUI install.
	trap 'rm -f "$tmpfile"' EXIT
	for ((i=0; i=${#packages[@]}; i++)); do
	    [[ ${packages[$i]} ]] && echo "install ${packages[$i]}" >>$tmpfile
	done
	$getroot "sh -c '/usr/sbin/synaptic --set-selections --non-interactive --hide-main-window < $tmpfile'"
    else
	$getroot apt-get -y install "${packages[@]}"
    fi
    if (( $? != 0)); then
	die 4 $"Missing ${packages[@]}" \
	    $"and unable to install.  Please ask an admin to install for you."
    fi
}

make_debian()
{
    create_type="$1"

    create_temporary_trees
    trap "delete_temporary_trees" EXIT HUP TERM

    make_common_test "mk${create_type}"

    debian_package=${module//_/-}

    # Read the conf file
    read_conf_or_die "$kernelver" "$arch"
    debian_install fakeroot dpkg-dev debhelper

    #skeleton to load templates from
    local system_mk="$dkms_tree/$module/$module_version/source/$module-dkms-mk${create_type}"
    local local_mk="/etc/dkms/template-dkms-mk${create_type}"
    if [[ -e ${system_mk} ]]; then
	echo $"Using ${system_mk}"
	DEBDIR=${system_mk}
    elif [[ -e ${local_mk} ]]; then
	echo $"Using ${local_mk}"
	DEBDIR=${local_mk}
    else
	die 5 $"Cannot find ${local_mk} which is needed by" \
	    $"DKMS in order to use mk${create_type}."
    fi

    #prepare build directory and copy template
    local temp_dir=$(mktemp_or_die -d $tmp_location/dkms.XXXXXX)
    trap "rm -rf $temp_dir; delete_temporary_trees" EXIT HUP TERM
    local temp_dir_debian="$temp_dir/$debian_package-dkms-$module_version"
    invoke_command "cp -ar '$DEBDIR/' '$temp_dir_debian'" "copying template"
    pushd "$temp_dir_debian" > /dev/null 2>&1
    for file in debian/*; do
	preproc_file "$file"
	chmod 755 "$file"
    done
    popd > /dev/null 2>&1

     #if using legacy mode, install common postinst
    if ((legacy_postinst != 0)); then
	invoke_command "cp '$PREFIX/usr/lib/dkms/common.postinst' '$temp_dir_debian'" "copying legacy postinstall template"
    fi

    #Copy in the source tree
    if [[ ! $binaries_only ]]; then
	invoke_command "cp -Lpr '$dkms_tree/$module/$module_version/source' '$temp_dir_debian/$module-$module_version'" "Copying source tree"
    fi

    #Only if we are shipping binary modules, make a .tgz for the deb
    local archive_location="$dkms_tree/$module/$module_version/tarball/$module-$module_version.dkms.tar.gz"
    if [[ ! $source_only ]]; then
	binaries_only="binaries-only"
	invoke_command "make_tarball" "Gathering binaries"
	if [[ -f $archive_location ]]; then
	    invoke_command "cp '$archive_location' '$temp_dir_debian'" "Copying DKMS tarball into DKMS tree"
	else
	    die 12 $"Unable to find created tarball."
	fi
    fi

    #calculate destination directory
    deb_basedir=$dkms_tree/$module/$module_version/${create_type}
    mkdir -p ${deb_basedir} >/dev/null 2>&1

    #create deb
    pushd "$temp_dir_debian" > /dev/null 2>&1
    case "$create_type" in
	dsc)
	    invoke_command "dpkg-buildpackage -S -us -uc 1>/dev/null" "Building source package" || \
		die 7 $"There was a problem creating your ${create_type}."
	    echo $""
	    echo $"DKMS: mk${create_type} completed."
	    invoke_command "mv '$temp_dir/${debian_package}-dkms_${module_version}_source.changes' '$temp_dir/${debian_package}-dkms_${module_version}.dsc' '$temp_dir/${debian_package}-dkms_${module_version}.tar.gz' '$deb_basedir'" "Moving built files to $deb_basedir"
	    ;;
	deb)
	    invoke_command "dpkg-buildpackage -rfakeroot -d -b -us -uc 1>/dev/null" "Building binary package" || \
		die 7 $"There was a problem creating your ${create_type}."
	    echo $""
	    echo $"DKMS: mk${create_type} completed."
	    invoke_command "mv '$temp_dir/${debian_package}-dkms_${module_version}_all.deb' '$deb_basedir'" "Moving built files to $deb_basedir"
	    ;;
    esac
    popd > /dev/null 2>&1

    if in_temporary_trees; then
	echo "Copying built files to "`pwd`"/.." >&2
	cp "${deb_basedir}/"* ..
    fi

    #cleanup
    invoke_command "rm $temp_dir -fr" "Cleaning up temporary files"
    delete_temporary_trees || \
	die 7 $"There was a problem cleaning up temporary files."
}

make_common_test()
{
    local create_type=$1
    # Error if $module_version is set but $module is not
    check_module_args $create_type

    # Check that source symlink works
    check_module_exists

    # Make sure that its installed in the first place
    [[ -d $dkms_tree/$module/$module_version ]] ||
	die 3 $"The module/version combo: $module-$module_version" \
	    $"is not located in the DKMS tree."
}

make_kmp_srpm()
{
    local temp_dir_name=$(mktemp_or_die -d $tmp_location/dkms.XXXXXX)
    trap 'rm -rf $temp_dir_name' EXIT HUP TERM
    mkdir -p $temp_dir_name/{BUILD,RPMS,SRPMS,SPECS,SOURCES}
    pushd "$dkms_tree/$module/$module_version" > /dev/null 2>&1
    # want to change name of the top-level of the tarball
    # from build to $module-$module_version
    cp -lr build ${module}-${module_version}
    tar cvjf $temp_dir_name/SOURCES/${module}-${module_version}.tar.bz2 ${module}-${module_version} > /dev/null 2>&1
    rm -rf ${module}-${module_version}
    popd > /dev/null 2>&1
    pushd "$temp_dir_name" > /dev/null 2>&1
    invoke_command "rpmbuild --define \"_topdir ${temp_dir_name}\" --target=$arch -bs ${SPECFILE} > ${temp_dir_name}/rpmbuild.log 2>&1" "rpmbuild"
    grep ^Wrote $temp_dir_name/rpmbuild.log > /dev/null 2>&1
    local RC="$?"
    if ((RC == 0)); then
	local kmp_basedir="$dkms_tree/$module/$module_version/rpm"
	mkdir -p $kmp_basedir
	RPMS=$(LANG=C cp -va ${temp_dir_name}/SRPMS/* $kmp_basedir | awk '{print $NF}')
    else
	echo $"rpmbuild error log:"
	cat $temp_dir_name/rpmbuild.log
    fi
    popd > /dev/null 2>&1
    rm -rf $temp_dir_name
    trap > /dev/null 2>&1
    return ${RC}
}

report_build_problem()
{
    #If apport is on the system, files a build problem
    if [ -x /usr/share/apport/apport ] && which python>/dev/null; then
	python /usr/share/apport/package-hooks/dkms_packages.py -m $module -v $module_version -k ${kernelver[0]}
    fi
    die "$@"
}

# Little helper function for reading args from the commandline.
# it automatically handles -a b and -a=b variants, and returns 1 if
# we need to shift $3.
read_arg() {
    # $1 = arg name
    # $2 = arg value
    # $3 = arg parameter
    local rematch='^[^=]*=(.*)$'
    if [[ $2 =~ $rematch ]]; then
	read "$1" <<< "${BASH_REMATCH[1]}"
    else
	read "$1" <<< "$3"
	# There is no way to shift our callers args, so
	# return 1 to indicate they should do it instead.
	return 1
    fi
}

# A couple of helper functions for parsing out our most common arguments.
# This one allows you to pass -k kernel.version-extra/arch instead of
# -k kernel-version.extra -a arch.
# This makes it harder to pass mismatching numbers of kernel/arch pairs, because
# they are all passed at the same time.
parse_kernelarch(){
    if [[ $1 =~ $mv_re ]]; then
	kernelver[${#kernelver[@]}]="${BASH_REMATCH[1]}"
	arch[${#arch[@]}]="${BASH_REMATCH[2]}"
    else
	kernelver[${#kernelver[@]}]="$1"
    fi
}

# This allows you to pass module and module_version information on the commandline
# in a more convienent form.  Instead of the mostly mandatory and annoying
# -m module -v module_version, you can use either -m module/module_version, 
# or just a raw module/module_version with no -m parameter.  
# This vastly improves readability and discoverability of
# commands on the commandline.
parse_moduleversion(){
    if [[ $1 =~ $mv_re ]]; then
	module="${BASH_REMATCH[1]}"
	module_version="${BASH_REMATCH[2]}"
    else
	module="$1"
    fi
}

check_root() {
    ((UID == 0)) && return
    die 1 $"You must be root to use this command."
}

# Add a passed source tree to the default source location.
# We will check the dkms.conf file to make sure it is valid
# beforehand.
add_source_tree() {
    local from=$(readlink -f $1)
    if ! [[ $from && -f $from/dkms.conf ]]; then
	die 9 $"$1 must contain a dkms.conf file!"
    fi
    check_root
    setup_kernels_arches
    if ! get_pkginfo_from_conf "$from/dkms.conf" ; then
	die 10 $"Malformed dkms.conf file. Cannot load source tree."
    fi
    module="$PACKAGE_NAME"
    module_version="$PACKAGE_VERSION"
    if [[ $force && -d $source_tree/$module-$module_version ]]; then
	echo >&2
	echo $"Forcing install of $module-$module_version"
	rm -rf "$source_tree/$module-$module_version"
    fi

    # We are already installed, just return.
    case $from in
	"$source_tree/$module-$module_version") return;;
	"$dkms_tree/$module/$version/source") return;;
	"$dkms_tree/$module/$version/build") return;;
    esac
    mkdir -p "$source_tree/$module-$module_version"
    cp -fr "$from"/* "$source_tree/$module-$module_version"
}

make_kmp()
{
    make_common_test "mkkmp"

    # Read the conf file
    read_conf_or_die "$kernelver" "$arch"

    echo $""
    if [[ $specfile && -e $dkms_tree/$module/$module_version/source/$specfile ]]; then
	echo $"Using $dkms_tree/$module/$module_version/source/$specfile"
	SPECFILE="$dkms_tree/$module/$module_version/source/$specfile"
    elif [[ -e $dkms_tree/$module/$module_version/source/$module-kmp.spec ]]; then
	echo $"Using $dkms_tree/$module/$module_version/source/$module-kmp.spec"
	SPECFILE="$dkms_tree/$module/$module_version/source/$module-kmp.spec"
    else
	die 5 $"Cannot find a suitable spec file which is needed by" \
	    $"DKMS in order use mkkmp.  Please specify --spec=specfile."
    fi

    prepare_build
    make_kmp_srpm
    RC=$?
    clean_build

    if ((RC == 0)); then
	echo $""
	echo $"KMP SRPM location: $RPMS"
	echo $""
	echo $"DKMS: mkkmp completed."
    else
	die 7 $"There was a problem creating your KMP source rpm."
    fi
    # FIXME: hand SRPM to mock or build system to build
}

# This code used to be in dkms_autoinstaller.
# Moving it into the main dkms script gets rid of a fair amount of duplicate
# functionality, and makes it much easier to reinstall DKMS kernel modules
# by hand if dkms_autoinstaller is not used.
autoinstall() {
    local status mv mvka m v k a last_v last_m tenative
    local -a to_install=()
    # Walk through our list of installed and built modules, and create
    # a list of modules that need to be reinstalled.
    while read status mvka; do
        IFS='/' read m v k a <<< "$mvka"
        [[ ! $last_m ]] && last_m="$m"
        # If the module is already installed or weak-installed, skip it.
        _is_module_installed "$m" "$v" "$kernelver" "$arch" && continue
        module_status_weak "$m" "$v" "$kernelver" "$arch" >/dev/null && continue
        # if the module does not want to be autoinstalled, skip it.
        read_conf_or_die "$k" "$a" "$dkms_tree/$m/$v/source/dkms.conf"
        if [[ ! $AUTOINSTALL ]]; then
            continue
        # otherwise, only autoinstall the latest version we have hanging around.
        elif [[ $last_m != $m ]]; then
            last_m="$m"
            last_v='0'
            [[ $tenative ]] && to_install[${#to_install[@]}]="$tenative"
            tenative=''
        fi
        if [[ ($(VER $v) > $(VER $last_v)) ]]; then
            last_v="$v"
            tenative="$m/$v"
        fi
    done < <(module_status)
    # We may have exited the loop with $tenative set.  If it is,
    # it contains something that should be updated.
    [[ $tenative ]] && to_install[${#to_install[@]}]="$tenative"
    [[ $to_install ]] || return 0
    # Install modules that need to be updated in parallel.
    for mv in "${to_install[@]}"; do
        IFS=/ read m v <<< "$mv"
        (module="$m"; module_version="$v"; install_module) &
    done
    wait
}

function make_redhat3_driver_disk ()
{
    # Check that the rpmbuild command is present
    if ! which rpmbuild >/dev/null 2>&1 ; then
        echo $"" >&2
        echo $"Error! rpmbuild not present." >&2
        echo $"Install the rpm-build package." >&2
        exit 1
    fi

    local kmodtool=$(rpm -ql redhat-rpm-config | grep kmodtool)
    #Check that the kmodtool is present
    if [ -z "$kmodtool" ]; then
        echo $"Error! kmodtool not present." >&2
        echo $"Install redhat-rpm-config package." >&2
        exit 1
    fi

    # Check that the createrepo command is present
    if ! which createrepo >/dev/null 2>&1 ; then
        echo $"" >&2
        echo $"Error! createrepo not present." >&2
        echo $"Install the createrepo package." >&2
        exit 1
    fi

    echo $""
    if [ -n "$specfile" -a -e "$dkms_tree/$module/$module_version/source/$specfile" ]; then
        echo $"Using $dkms_tree/$module/$module_version/source/$specfile"
        SPECFILE="$dkms_tree/$module/$module_version/source/$specfile"
    elif [ -e "/etc/dkms/template-dkms-redhat-kmod.spec" ]; then
        echo $"Using /etc/dkms/template-dkms-redhat-kmod.spec"
        SPECFILE="/etc/dkms/template-dkms-redhat-kmod.spec"
    else
        echo $"" >&2
        echo $"Cannot find /etc/dkms/template-dkms-redhat-kmod.spec which is needed by" >&2
        echo $"DKMS in order to make Redhat driver disk v3." >&2
        exit 5
    fi
    # Set up temporary build directory for build
    rm -rf "$dkms_tree/$module/$module_version/build"
    cp -rf "$dkms_tree/$module/$module_version/source/" "$dkms_tree/$module/$module_version/build"

    cd "$dkms_tree/$module/$module_version/build"

    # Run the pre_build script
    if [ -n "$pre_build" ] && [ -x `echo "$dkms_tree/$module/$module_version/source/$pre_build" | sed 's/ .*//'` ]; then
        echo $""
        echo $"Running the pre_build script:"
        $dkms_tree/$module/$module_version/build/$pre_build
    fi

    # Apply any patches
    local index=0
    while [ $index -lt ${#patch_array[@]} ]; do
        if ! [ -e "$dkms_tree/$module/$module_version/build/patches/${patch_array[$index]}" ]; then
            echo $"" >&2
            echo $"Error!  Patch ${patch_array[$index]} as specified in dkms.conf cannot be" >&2
            echo $"found in $dkms_tree/$module/$module_version/build/patches/." >&2
            exit 5
        fi
        invoke_command "patch -p1 < ./patches/${patch_array[$index]}" "applying patch ${patch_array[$index]}"
        if [ "$?" -ne 0 ]; then
            echo $"" >&2
            echo $"Error! Application of patch ${patch_array[$index]} failed." >&2
            echo $"Check $dkms_tree/$module/$module_version/build/ for more information." >&2
                report_build_problem
            exit 6
        fi
        index=$(($index+1))
    done
    # Create temp dirs and copy files for build
    local temp_dir_name=`mktemp -d $tmp_location/dkms.XXXXXX`
    trap 'rm -rf $temp_dir_name' EXIT HUP TERM
    mkdir -p ${temp_dir_name}/{disk,BUILD,RPMS,SRPMS,SPECS,SOURCES}
    cp ${SPECFILE} ${temp_dir_name}/SPECS/$module.spec
    cp -rax $dkms_tree/$module/$module_version/build/ $temp_dir_name/SOURCES/$module-$module_version/
    # Clean the build directory
    rm -rf "$dkms_tree/$module/$module_version/build/*"
    cd $temp_dir_name/SOURCES
    invoke_command "tar -jcvf $temp_dir_name/SOURCES/$module-$module_version.tar.bz2 $module-$module_version/" "creating source tarball"
    local i=0
    #Build RPMS
    while [ $i -lt ${#kernelver[@]} ]; do
        invoke_command "LC_ALL=C rpmbuild --define \"_topdir ${temp_dir_name}\" --define \"version $module_version\" --define \"module_name $module\" --define \"kernel_version ${kernelver[$i]}\" -bb --target ${arch[$i]} ${temp_dir_name}/SPECS/$module.spec > ${temp_dir_name}/rpmbuild.log 2>&1" "rpmbuild"
        if [ "$?" -ne 0 ]; then
            echo $"" >&2
            echo $"Error! There was a problem creating your kmod." >&2
            cat ${temp_dir_name}/rpmbuild.log >&2
            exit 7
        fi
        local kabi_whitelist=`rpm -ql kabi-whitelists | grep ${arch[$i]}`
        if [ $kabi_whitelist ]; then
            local module_file=`rpm -qlp ${temp_dir_name}/RPMS/${arch[$i]}/kmod-${module}-${module_version}* | grep ${module}.ko`
            cd ${temp_dir_name}/
            rpm2cpio ${temp_dir_name}/RPMS/${arch[$i]}/kmod-${module}-${module_version}* | cpio -id --quiet .${module_file}
            cd - > /dev/null
            local mod_symbols=( $(modprobe --dump-modversions ${temp_dir_name}/${module_file} | cut -f2) )
            local miss_sym_count=0
            local missing_symbols
            for ((i=0; i < "${#mod_symbols[@]}"; i++))
            do
               if [ -z "`grep -o ${mod_symbols[${i}]} ${kabi_whitelist}`" ]; then
                   missing_symbols[$miss_sym_count]="${mod_symbols[${i}]}"
                   miss_sym_count=$(($miss_sym_count+1))
               fi
            done
            if [ $miss_sym_count -ne 0 ]; then
                echo $"" >&2
                echo $"WARNING: ${module}-${module_version} is using following kernel symbols that are not in the ABI whitelist:"
                echo $"----------------------------------"
                for missing_symbol in ${missing_symbols[*]}
                do
                    echo "$missing_symbol"
                done
                echo $"----------------------------------"
                echo $"" >&2
            else
                echo $"NOTICE: ${module}-${module_version} module seems to use only official Red Hat ABI."
            fi
        else
            echo $"WARNING:${module}-${module_version} module is not checked against Red Hat ABI whitelist."
            echo $"Install 'kabi-whitelists' package and build driver disk again to run the ABI compliance test."
        fi    
        i=$(($i + 1))
    done

    i=0
    while [ $i -lt ${#arch[@]} ]; do
        invoke_command "createrepo --pretty ${temp_dir_name}/RPMS/${arch[$i]}" "creating repo"
        if [ "$?" -ne 0 ]; then
            echo $"" >&2
            echo $"Error! There was a problem creating repository." >&2
            exit 7
        fi
        i=$(($i + 1))
    done

    echo "$module-$module_version driver disk" > "${temp_dir_name}/disk/rhdd3"
    mkdir ${temp_dir_name}/disk/rpms
    cp -rax ${temp_dir_name}/RPMS/* ${temp_dir_name}/disk/rpms/

    local suffix="$(driver_disk_suffix)"
    local image_dir="$dkms_tree/$module/$module_version/driver_disk"
    local image_name="$module-$module_version-dd.$suffix"
    echo $""
    echo $"Creating driver disk on $media media:"
    cd "${temp_dir_name}/disk"
    mkdir -p "$image_dir"
    rm -f "$image_dir/$image_name"
    make_driver_disk_media "$image_dir/$image_name" "${temp_dir_name}/disk"
    rm -rf $temp_dir_name

    echo $""
    echo $"Disk image location: $image_dir/$image_name"
    echo $""
    echo $"DKMS: mkdriverdisk completed."
    
    trap > /dev/null 2>&1
}

#############################
####                     ####
#### Program Starts Here ####
####                     ####
#############################

# Set a standard path
PATH="/bin:/sbin:/usr/bin:/usr/sbin:/usr/lib/dkms"

# Ensure files and directories we create are readable to anyone,
# since we aim to build as a non-root user
umask 022

# Unset environment variables that may interfere with the build
unset CC CXX CFLAGS CXXFLAGS LDFLAGS

# Set important variables
current_kernel=$(uname -r)
current_os=$(uname -s)
dkms_tree="/var/lib/dkms"
source_tree="/usr/src"
install_tree="/lib/modules"
tmp_location=${TMPDIR:-/tmp}
verbose=""
dkms_frameworkconf="/etc/dkms/framework.conf"

# these can come from the environment or the config file
[[ ! ${ADDON_MODULES_DIR} && -e /etc/sysconfig/module-init-tools ]] && . /etc/sysconfig/module-init-tools
addon_modules_dir="${ADDON_MODULES_DIR}"
[[ ! ${addon_modules_dir} ]] && running_distribution="$(distro_version)"
weak_modules="${WEAK_MODULES_BIN}"

# Source in /etc/dkms_framework.conf
[ -e $dkms_frameworkconf ] && . $dkms_frameworkconf 2>/dev/null

# Clear out command line argument variables
module=""
module_version=""
template_kernel=""
distro=""
media=""
release=""
conf=""
kernel_config=""
archive_location=""
kernel_source_dir=""
ksourcedir_fromcli=""
action=""
force=""
no_prepare_kernel=""
no_clean_kernel=""
binaries_only=""
source_only=""
all=""
module_suffix=""
rpm_safe_upgrade=""
size="1440";
specfile=""
legacy_postinst="1"
declare -a directive_array=() kernelver=() arch=()
weak_modules=''
last_mvka=''
last_mvka_conf=''
try_source_tree=''
die_is_fatal="yes"
[ -x /sbin/weak-modules ] && weak_modules='/sbin/weak-modules'
[ -x /usr/lib/module-init-tools/weak-modules ] && weak_modules='/usr/lib/module-init-tools/weak-modules'

action_re='^(remove|(auto|un)?install|match|mk(driverdisk|tarball|rpm|deb|dsc|kmp)|build|add|status|ldtarball)$'

# Parse command line arguments
while (($# > 0)); do
    case $1 in
	--dkmsframework*)    read_arg dkms_frameworkconf "$1" "$2" || shift
			     #immediately load this config
			     . $dkms_frameworkconf 2> /dev/null;;
	--module*|-m)        read_arg _mv "$1" "$2" || shift
			     parse_moduleversion "$_mv";;
	-v)                  read_arg module_version "$1" "$2" || shift;;
	--kernelver*|-k)     read_arg _ka "$1" "$2" || shift
			     parse_kernelarch "$_ka";;
	--distro*|-d)        read_arg distro "$1" "$2" || shift;;
	--media*)            read_arg media "$1" "$2" ||shift;;
	--release*|-r)       read_arg release "$1" "$2" || shift;;
	--templatekernel*)   read_arg template_kernel "$1" "$2" || shift;;
	-c)                  read_arg conf "$1" "$2" || shift;;
	--quiet|-q)          exec >/dev/null 2>&1;;
	--version|-V)        echo $"dkms: 2.2.0.3"
			     exit 0;;
	--no-prepare-kernel) no_prepare_kernel="no-prepare-kernel";;
	--no-clean-kernel)   no_clean_kernel="no-clean-kernel";;
	--no-initrd)         no_initrd="no-initrd";;
	--binaries-only)     binaries_only="binaries-only";;
	--source-only)       source_only="source-only";;
	--force)             force="true";;
	--all)               all="true";;
	--verbose)           verbose="true";;
	--rpm_safe_upgrade)  rpm_safe_upgrade="true";;
	--dkmstree*)         read_arg dkms_tree "$1" "$2" || shift;;
	--sourcetree*)       read_arg source_tree "$1" "$2" || shift;;
	--installtree*)      read_arg install_tree "$1" "$2" || shift;;
	--config*)           read_arg kernel_config "$1" "$2" || shift;;
	--archive*)          read_arg archive_location "$1" "$2" || shift;;
	--legacy-postinst*)  read_arg legacy_postinst "$1" "$2" || shift;;
	--arch*|-a)          read_arg _aa "$1" "$2" || shift
			     arch[${#arch[@]}]="$_aa";;
	--size*)             read_arg size "$1" "$2" || shift;;
	--kernelsourcedir*)  read_arg kernel_source_dir "$1" "$2" || shift
			     ksourcedir_fromcli="true";;
	--directive*)        read_arg _da "$1" "$2" || shift
			     directive_array[${#directive_array[@]}]="$_da";;
	--spec*)             read_arg specfile "$1" "$2" || shift;;
	--debug)
	    export PS4='${BASH_SOURCE}@${LINENO}(${FUNCNAME[0]}): '
	    set -x;;
	-*|--*)              error $" Unknown option: $1"
			     show_usage
			     exit 2;;
	*) if [[ $1 =~ $action_re ]]; then
	    action="$action $1" # Add actions to the action list
	    elif [[ -f $1 && $1 = *dkms.conf ]]; then
	    try_source_tree="${1%dkms.conf}./" # Flag as a source tree
	    elif [[ -d $1 && -f $1/dkms.conf ]]; then
	    try_source_tree="$1" # ditto
	    elif [[ -f $1 ]]; then
	    archive_location="$1" # It is a file, assume it is an archive.
	    elif [[ ! $module ]]; then
	    parse_moduleversion "$1" # Assume it is a module/version pair.
	    else
	    warn $"I do not know how to handle $1."
	    fi;;
    esac
    shift
done

# Sanity checking

# Error out if binaries-only is set and source-only is set
if [[ $binaries_only && $source_only ]]; then
    die 8 $" You have specified both --binaries-only and --source-only." \
	$"You cannot do this."
fi

# Error if # of arches doesn't match # of kernels
if (( ${#kernelver[@]} != ${#arch[@]} && \
    ${#arch[@]} > 1 )); then
    die 1 $" If more than one arch is specified on the command line, then there" \
	$"must be an equal number of kernel versions also specified (1:1 relationship)."
fi

# Check that kernel version and all aren't both set simultaneously
if [[ $kernelver && $all ]]; then
    die 2 $" You cannot specify a kernel version and also specify" \
	$"--all on the command line."
fi

# Check that arch and all aren't both set simultaneously
if [[ $arch && $all ]]; then
    die 3 $" You cannot specify an arch and also specify" \
	$"--all on the command line."
fi

# Run the specified action
for action_to_run in $action; do
    setup_kernels_arches "$action_to_run"
    case "$action_to_run" in
	remove)       check_root && remove_module;;
	install)      check_root && install_modules;;
	autoinstall)  check_root && autoinstall;;
	match)        check_root && have_one_kernel && run_match;;
	uninstall)    check_root && have_one_kernel && uninstall_module;;
	mkdriverdisk) check_root && make_driver_disk;;
	build)        build_modules;;
	add)          add_module;;
	mktarball)    make_tarball;;
	mkrpm)        make_rpm;;
	mkdeb)        make_debian "deb";;
	mkdsc)        make_debian "dsc";;
	mkkmp)        have_one_kernel && make_kmp;;
	status)       show_status;;
	ldtarball) # Make sure they're root if we're using --force
	    if ((UID != 0)) && [[ $force = true ]]; then
		die 1 $"You must be root to use this command with the --force option."
	    fi
	    load_tarball && add_module;;
	'') error $"No action was specified."
	    show_usage;;
	*)  error $"Unknown action specified: $action_to_run"
	    show_usage;;
    esac
done
