#!/bin/bash
#
# MKKMODULE -- This script generates a kernel module.
# Copyright (C) 2005-2006 Keicho Kondo <dgel@users.sourceforge.jp>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 as
# published by the Free Software Foundation.
#
# 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
#
# To save time the script will look for a relevant Module.symvers in:
#   /lib/modules
#   kernel binary rpm
#

## === Start === ##
echo "<-- Starting mkkmodule script -->"


## === Define === ##
BUILD_ROOT="${0%/*}"
CURRENT_DIR="${PWD}"
LOG=${CURRENT_DIR}/log-`date +%Y%m%d-%H%M%S`


## === Read module infomation file === ##
echo "Reading module infomation file..."
#
# * environment variable
#
#   - MOD_NAME       : module name
#   - MOD_COMPOPT    : compile options
#   - MOD_SRCDIR     : module source directory
#   - MOD_PATCH      : patch files
#
if [ -f "${CURRENT_DIR}/mkkmodule.conf" ]
then
	. ${CURRENT_DIR}/mkkmodule.conf
elif [ -f "${BUILD_ROOT}/mkkmodule.conf" ]
then
	. ${BUILD_ROOT}/mkkmodule.conf
else
	echo "Cannot find mkkmodule.conf file.  Giving up."
	exit 1
fi


## === Check kernel information === ##
echo "Checking kernel information..."
#
# * function
#
#   - checkchar <char1> <char2>   : If <char1> in <char2>, return 0, or return 1.
#       
# * environment variable
#
#   - RPM_TOPDIR     : RPM build top directory.
#   - KERNEL_SRCDIR  : kernel source directory.
#   - TYPE           : kernel type, rpm package or else.
#   - MAJORVERSION   : kernel major version.
#   - PATCHLEVEL     : kernel patchlevel.
#   - SUBLEVEL       : kernel sublevel.
#   - VERSION        : <majorversion>.<patchlevel>.<sublevel>
#   - RELEASE        : kernel extra version.
#   - CPU            : cpu architecture
#   - CONFIG         : kernel config file path (.config)
#   - MODSYMVER      : Module.symvers file path
# 

## Function
# check charactor
checkchar()
{
	case "$1" in
	*$2*) return 0 ;;
	*) return 1 ;;
	esac
}

# get rpm topdir
getrpmtopdir()
{
	RPM_MACROS="$1"
	RPM_TOPDIR=`awk '$1=="%_topdir" {print $2}' "$RPM_MACROS"`
	while checkchar "$RPM_TOPDIR" "%"
	do
		TMP="${RPM_TOPDIR##*\%}"
		RPM_TOPDIR_TMP="${TMP#*/}"
		RPM_PER_ALIAS="%`echo "${TMP%%/*}" | sed -e 's/{//' -e 's/}//'`"
		RPM_TOPDIR=`awk '$1=="'"$RPM_PER_ALIAS"'" {print $2}' "$RPM_MACROS"`/$RPM_TOPDIR_TMP
	done
	[ -d "$RPM_TOPDIR" ] || RPM_TOPDIR="/usr/src/redhat"
}

# check kernel source path
chkkernelsource()
{
	CHK_KERN_SRC="$1"
	[ -d "${CHK_KERN_SRC}/drivers" ]  || return 1
	[ -d "${CHK_KERN_SRC}/init" ]     || return 1
	[ -d "${CHK_KERN_SRC}/lib" ]      || return 1
	[ -d "${CHK_KERN_SRC}/scripts" ]  || return 1
	[ -d "${CHK_KERN_SRC}/usr" ]      || return 1
	[ -d "${CHK_KERN_SRC}/arch" ]     || return 1
	[ -d "${CHK_KERN_SRC}/fs" ]       || return 1
	[ -d "${CHK_KERN_SRC}/ipc" ]      || return 1
	[ -d "${CHK_KERN_SRC}/mm" ]       || return 1
	[ -d "${CHK_KERN_SRC}/security" ] || return 1
	[ -d "${CHK_KERN_SRC}/crypto" ]   || return 1
	[ -d "${CHK_KERN_SRC}/include" ]  || return 1
	[ -d "${CHK_KERN_SRC}/kernel" ]   || return 1
	[ -d "${CHK_KERN_SRC}/net" ]      || return 1
	[ -d "${CHK_KERN_SRC}/sound" ]    || return 1
	return 0
}

# interactive source code check
getkernelsource()
{
	echo "This script cannot find kernel source code."
	echo "Please enter your kernel source code path,"
	echo -n "or type 'quit' to exit this script. : "
	read KERNEL_SRCDIR
	if [ "${KERNEL_SRCDIR}" == "quit" ]
	then
		echo "Abotred."
		exit 1
	elif ! chkkernelsource ${KERNEL_SRCDIR}
	then
		echo "This is not a kernel source code path."
		getkernelsource
	fi
	return 0
}


## Look for kernel
# Search rpm topdir
echo -n "	Search rpm topdir..."
if (which rpm >/dev/null 2>&1)
then
	RPM_TOPDIR="/usr/src/redhat"
	if [ -f "$HOME/.rpmmacros" ]
	then
		getrpmtopdir "$HOME/.rpmmacros"
	elif [ -f "/usr/lib/rpm/macros" ]
	then
		getrpmtopdir "/usr/lib/rpm/macros"
	fi
fi
echo "			${RPM_TOPDIR}"

# Search for kernel source directory
echo -n "	Search for kernel source directory..."
KERNEL_SRCDIR=""
TYPE=""
if [ -f "${RPM_TOPDIR}/SOURCES/linux-"*".tar."*"" -a -f "${RPM_TOPDIR}/SPECS/kernel-"*".spec" ]
then
	#echo "	${RPM_TOPDIR}"
	rpmbuild -bp --target=noarch ${RPM_TOPDIR}/SPECS/kernel-*.spec >/dev/null 2>&1
	if [ $? -ne 0 ]
	then
		echo " failed.  Giving up."
		exit 1
	fi
	KERNEL_SRCDIR="$(echo $RPM_TOPDIR/BUILD/kernel-*/linux-*)"
elif [ -d "/usr/src/linux" ]
then
	#echo "	/usr/src/linux"
	KERNEL_SRCDIR="/usr/src/linux"
else
	#echo ""
	KERNEL_SRCDIR="(not_found)"
fi
echo -e "\t${KERNEL_SRCDIR}"
if ! chkkernelsource ${KERNEL_SRCDIR}
then
	getkernelsource
fi

# check kernel type
echo -n "	Check kernel type..."
case "$KERNEL_SRCDIR" in
*BUILD/kernel-*/linux-*)
	TYPE="rpm" ;;
*)
	TYPE=""
	mkdir -p ${CURRENT_DIR}/linux
	cp -a ${KERNEL_SRCDIR}/* ${KERNEL_SRCDIR}/.[^.]* ${CURRENT_DIR}/linux/
	KERNEL_SRCDIR="${CURRENT_DIR}/linux" ;;
esac
[ -n "${TYPE}" ] && echo "			${TYPE}" || echo "			original"


# Detect kernel version
echo -n "	Detect kernel version..."
MAJORVERSION=`awk '$1=="VERSION" {print $3}' ${KERNEL_SRCDIR}/Makefile`
PATCHLEVEL=`awk '$1=="PATCHLEVEL" {print $3}' ${KERNEL_SRCDIR}/Makefile`
SUBLEVEL=`awk '$1=="SUBLEVEL" {print $3}' ${KERNEL_SRCDIR}/Makefile`
EXTRAVERSION=`awk '$1=="EXTRAVERSION" {print $3}' ${KERNEL_SRCDIR}/Makefile`

VERSION="${MAJORVERSION}.${PATCHLEVEL}.${SUBLEVEL}"
if [ "$TYPE" = "rpm" ]
then
	RELEASE=$(uname -r | sed -e s/$VERSION//)
	for f in $(find ${KERNEL_SRCDIR}/configs -name kernel*.config | sed -e 's/.*'${VERSION}'-//' -e 's/\.config//' | awk -F'-' '{print $2}' | sort | uniq)
	do
		[ -z "$f" ] && continue
		case "${RELEASE}" in
		*$f)
			SUFF="-$f" ;;
		esac
	done
	CPU=$(uname -p)
	CONFIG="${KERNEL_SRCDIR}/configs/kernel-${VERSION}-${CPU}${SUFF}.config"
	MODSYMVER="/lib/modules/${VERSION}${RELEASE}/build/Module.symvers"
else
	RELEASE="${EXTRAVERSION}"
	CPU=""
	CONFIG="${KERNEL_SRCDIR}/.config"
	MODSYMVER="${KERNEL_SRCDIR}/Module.symvers"
fi
echo "		${VERSION}${RELEASE}"


## === Other checks === ###
echo "Other checks..."
# check kernel config file
echo -n "	Check kernel config file..."
if [ ! -f ${CONFIG} ]
then
	echo "Cannot find kernel config file in ${CONFIG%/*}.   Giving up."
	exit 1
fi
echo "		ok"
# check Module.symvers
echo -n "	Check Module.symvers..."
if [ ! -f "${MODSYMVER}" ]
then
	echo "Cannot find kernel config file in ${MODSYMVER%/*}.   Giving up."
	exit 1
fi
echo "			ok"


## === Start log service === ##
echo >> $LOG
echo "Log started at `date`" >> $LOG
echo >> $LOG


## === Patch kernel source === ##
echo "Patch Section..."
pushd ${KERNEL_SRCDIR} >> $LOG 2>&1
if [ -n "${MOD_PATCH}" ]
then
	for patch in ${MOD_PATCH}
	do
		if [ -f "${CURRENT_DIR}/${patch}" ]
		then
			patch -p1 < "${CURRENT_DIR}/${patch}" >> $LOG 2>&1
			if [ $? -ne 0 ]
			then
				echo "Cannot apply patch file.  Giving up."
				exit 1
			fi
		elif [ -f "${BUILD_ROOT}/${patch}" ]
		then
			patch -p1 < "${BUILD_ROOT}/${patch}" >> $LOG 2>&1
			if [ $? -ne 0 ]
			then
				echo "Cannot apply patch file.  Giving up."
				exit 1
			fi
		fi
	done
fi
popd >> $LOG 2>&1

## === Compile kernel module === ##
echo "Compile Section..."
pushd ${KERNEL_SRCDIR} >> $LOG 2>&1
sed -e "s/^EXTRAVERSION.*/EXTRAVERSION = ${RELEASE}/" Makefile > Makefile.tmp && mv -f Makefile.tmp Makefile

[ -z "${TYPE}" ] && cp -f Module.symvers .config ../

# Clear out any existing build products
echo -n "	make distclean..."
make V=0 distclean >> $LOG 2>&1
if [ $? -ne 0 ]
then
	echo " failed.  Giving up."
	exit 1
fi
echo "			done"

# Copy kernel config file
[ "${TYPE}" = "rpm" ] && cp -f $CONFIG .config
[ -z "${TYPE}" ] && mv -f ../.config .

# Edit kernel config file
for opt in $MOD_COMPOPT
do
	echo $opt >> ${KERNEL_SRCDIR}/.config
done

# Update the build config file, answering "no" to any new questions.
echo -n "	make oldconfig..."
yes n | make V=0 oldconfig >> $LOG 2>&1
if [ $? -ne 0 ]
then
	echo " failed.  Giving up."
	exit 1
fi
echo "			done"

# Copy Module.symvers
[ "${TYPE}" = "rpm" ] && cp -f $MODSYMVER .
[ -z "${TYPE}" ] && mv -f ../Module.symvers .

# First prepare, to satisfy dependencies.
echo -n "	make prepare..."
make V=0 prepare >> $LOG 2>&1
if [ $? -ne 0 ]; then
	echo " failed.  Giving up."
	exit 1
fi
echo "				done"

# For newer Makefiles, we need to create modpost
if [ ! -f scripts/mod/modpost ]; then
	make V=0 scripts >> $LOG 2>&1
fi

# Finally compile the module
echo -n "	make modules..."
make modules V=0 M="$MOD_SRCDIR" >> $LOG 2>&1
if [ $? -ne 0 ]
then
echo " failed.  Giving up."
	exit 1
fi
echo "				done"

popd >> $LOG 2>&1


## === Get kernel module === ##
cp -a ${KERNEL_SRCDIR}/${MOD_SRCDIR}/${MOD_NAME}.ko ${CURRENT_DIR}/


## === Finish === ##
if [ "${TYPE}" = "rpm" ]
then
	rpmbuild --clean ${RPM_TOPDIR}/SPECS/kernel-*.spec >> $LOG 2>&1
elif [ -z "${TYPE}" ]
then
	rm -rf "${CURRENT_DIR}/linux"
fi

echo >> $LOG
echo "Log ends at `date`" >> $LOG
echo >> $LOG

echo "<-- Finished -->"

