#!/bin/bash

# fedora-kmodhelper - Helper script for building kernel module RPMs
# Copyright (c) 2003-2004 Ville Skyttä <ville.skytta@iki.fi>,
#                         Thorsten Leemhuis <fedora@leemhuis.info>
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

# $Id: fedora-kmodhelper,v 1.7 2004/08/30 20:04:42 scop Exp $

myprog="fedora-kmodhelper"
myver="0.9.10"
variants='\(BOOT\|\(big\|huge\)mem\|debug\|enterprise\|smp\|uml\)'
var=

cc=
config=
cflags=
cpu=
kernel24=
kernel26=
modflags=
modutilpath=
rhkernel=
rhmodflags=
srcdir=
uname=
unamefromsrc=
variant=
verrel=
version=

get_uname ()
{
  [ -n "${uname}" ] || uname=`uname -r`
}
get_unamefromsrc ()
{
  if [ -z "${srcdir}" -o -z "${config}" ] ; then
    echo "At least srcdir (-s) and config (-f) needed, aborting."
    return 1
  fi
  if grep "PATCHLEVEL = 6" "${srcdir}"/Makefile &> /dev/null ; then
     cd "${srcdir}" > /dev/null && \
     unamefromsrc=`( cat Makefile && \
       echo -e '\n'${myprog}'-kernelrel:\n\t@echo $(KERNELRELEASE)\n' ) \
       | make -f - ${myprog}-kernelrel` && \
     cd - > /dev/null
  else
     cd "${srcdir}" >/dev/null && \
     unamefromsrc=`( sed 's|\.config\b|'${config}'|g' Makefile && \
       echo -e '\n'${myprog}'-kernelrel:\n\t@echo $(KERNELRELEASE)\n' ) \
       | make -f - ${myprog}-kernelrel` && \
     cd - > /dev/null
  fi
}
get_cpu ()
{
  [ -z "${cpu}" ] || return 0
  get_uname || return $?
  cpu=`rpm -qf --queryformat '%{ARCH}' "/boot/vmlinuz-${uname}" 2>/dev/null`
  [ $? -eq 0 ] || cpu=
  # Note: calling get_cflags here will result in a loop if $config is not set.
  if [ -z "${cpu}" -a -n "${config}" ] ; then
    get_cflags || return $?
    cpu=`echo ${cflags} | sed 's/^.*-march=\([^[:space:]]*\).*$/\1/'`
    [ "${cpu}" != "${cflags}" ] || cpu=
  fi
  [ -n "${cpu}" ] || cpu=`uname -m`
}
get_verrel ()
{
  [ -z "${verrel}" ] || return 0
  get_uname || return $?
  verrel=`echo "${uname}" | sed 's/'${variants}'$//'`
  verrel=${verrel%%custom}
}
get_version ()
{
  [ -z "${version}" ] || return 0
  get_uname || return $?
  version=`echo "${uname}" | sed 's/-[^-]*$//'`
}
get_variant ()
{
  [ -z "${variant}" ] || return 0
  get_uname || return $?
  local kv=`echo ${uname} | sed 's/^.*'${variants}'$/\1/'`
  [ "${kv}" = "${uname}" ] || variant="${kv}"
}
get_srcdir ()
{
  if [ -n "${srcdir}" ] ; then
    if [ ! -d "${srcdir}" -a ! -h "${srcdir}" ] ; then
      echo "Error: source dir '${srcdir}' not found." >&2
      return 1
    fi
  else
    get_verrel || return $?
    get_uname # || return $?

    if is_kernel26
    then
      get_cpu  || return $?
      get_variant  || return $?
      local check_dirs="/usr/lib/kernel-module-devel/${uname}-${cpu}/ /lib/modules/${uname}/build"
      for dir in ${check_dirs}; do
        if [ -d "${dir}" -o -h "${dir}" ] ; then
          if [ -e "${dir}/include/linux" ] ; then
            srcdir="${dir}"
            return 0
          else
            echo "Warning: dir '${dir}' doesn't look like a source dir." >&2
          fi
        fi
      done
      echo "Warning: source dir not found. Looked in:" >&2
      for dir in ${check_dirs}; do
        echo "   ${dir}"  >&2
      done
    else
      for dir in "/usr/src/linux-${verrel}/" "/lib/modules/${uname}/build"; do
        if [ -d "${dir}" -o -h "${dir}" ] ; then
          if [ -e "${dir}/include/linux" ] ; then
            srcdir="${dir}"
            return 0
          else
            echo "Warning: dir '${dir}' doesn't look like a source dir." >&2
          fi
        else
          echo "Warning: source dir '${dir}' not found." >&2
        fi
      done
    fi
    echo "Error: could not find a source dir." >&2
    return 1
  fi
}
get_rhkernel ()
{
  if is_kernel26
  then
      get_uname || return $?
      if [[ -e "/boot/vmlinuz-${uname}" ]] && rpm -qf "/boot/vmlinuz-${uname}" &>/dev/null
      then
        rhkernel=1
      else
        rhkernel=0
      fi
  else
    get_srcdir || return $?
    if grep rhconfig\\.h "${srcdir}/include/linux/autoconf.h" &>/dev/null ; then
      rhkernel=1
    else
      rhkernel=0
    fi
  fi
}
get_kernel24 ()
{
  [[ "${kernel24}" ]] && return 0 
  get_uname || return $?
  if [[ "${uname:0:3}" = "2.4" ]]
  then
    kernel24=1
  else
    kernel24=0
  fi
}
get_kernel26 ()
{
  [[ "${kernel26}" ]] && return 0 
  get_uname || return $?
  if [[ "${uname:0:3}" = "2.6" ]]
  then
    kernel26=1
  else
    kernel26=0
  fi
}
is_kernel24()
{
   get_kernel24 
   if [[ "${kernel24}" -eq 1 ]]
   then
      return 0
   else
      return 1
   fi	
}
is_kernel26()
{
   get_kernel26 
   if [[ "${kernel26}" -eq 1 ]]
   then
      return 0
   else
      return 1
   fi	
}

get_config ()
{
  if [ -n "${config}" ] ; then
    if [ ! -e "${config}" ] ; then
      echo "Error: config file doesn't exist: '${config}'." >&2
      return 1
    fi
  elif is_kernel26 ; then
    get_srcdir || return $?
    config="${srcdir}/.config"
    if [ ! -e "${config}" ] ; then
      echo "Warning: config file doesn't exist: '${config}'." >&2
      echo "Error: could not find a config file." >&2
      unset config
      return 1
    fi
  else
    get_cpu || return $?
    get_srcdir || return $?
    get_version || return $?
    get_rhkernel || return $?
    if [ "${rhkernel}" = "1" ] ; then
      get_variant || return $?
      config="${srcdir}/configs/kernel-${version}-${cpu}${variant:+-${variant}}.config"
      if [ ! -e "${config}" ] ; then
        echo "Warning: config file doesn't exist: '${config}'." >&2
        get_uname || return $?
        config="/boot/config-${uname}"
        echo "Warning: falling back to '${config}'." >&2
      fi
      if [ ! -e "${config}" ] ; then
        echo "Warning: config file doesn't exist: '${config}'." >&2
        echo "Error: could not find a config file." >&2
        unset config
        return 1
      fi
    else
      config="${srcdir}/.config"
      if [ ! -e "${config}" ] ; then
        echo "Warning: config file doesn't exist: '${config}'." >&2
        echo "Error: could not find a config file." >&2
        unset config
        return 1
      fi
    fi
  fi
}
get_cc ()
{
  get_srcdir || return $?
  if is_kernel26 ; then
    cd "${srcdir}" > /dev/null && \
    cc=`( cat Makefile && \
      echo -e '\n'${myprog}'-cc:\n\t@echo $(CC)\n' ) \
    | make -f - ${myprog}-cc` && \
    cd - > /dev/null
  else
    cd "${srcdir}" > /dev/null && \
    cc=`( sed 's|\.config\b|'${config}'|g' Makefile && \
      echo -e '\n'${myprog}'-cc:\n\t@echo $(CC)\n' ) \
    | make -f - ${myprog}-cc` && \
    cd - > /dev/null
  fi
}
get_cflags ()
{
  get_config || return $?
  get_srcdir || return $?
  if is_kernel26  ; then
    cd "${srcdir}" > /dev/null && \
    cflags=`( cat Makefile && \
      echo -e '\n'${myprog}'-cflags:\n\t@echo $(CFLAGS)\n' ) \
    | make -f - ${myprog}-cflags` && \
    cd - > /dev/null
  else
    cd "${srcdir}" > /dev/null && \
    cflags=`( sed 's|\.config\b|'${config}'|g' Makefile && \
      echo -e '\n'${myprog}'-cflags:\n\t@echo $(CFLAGS)\n' ) \
    | make -f - ${myprog}-cflags` && \
    cd - > /dev/null
  fi
}
get_modflags ()
{
  get_config || return $?
  get_srcdir || return $?
  cd "${srcdir}" > /dev/null && \
  if is_kernel26  &> /dev/null ; then
    modflags=`( cat Makefile && \
      echo -e '\n'${myprog}'-modflags:\n\t@echo $(MODFLAGS)\n' ) \
    | make -f - ${myprog}-modflags` && \
    cd - > /dev/null
  else
    modflags=`( sed 's|\.config\b|'${config}'|g' Makefile && \
      echo -e '\n'${myprog}'-modflags:\n\t@echo $(MODFLAGS)\n' ) \
    | make -f - ${myprog}-modflags` && \
    cd - > /dev/null
  fi
}
get_modutilpath ()
{
  if is_kernel26
  then
      modutilpath="/etc/modprobe.conf"
  elif is_kernel24
  then
      modutilpath="/etc/modules.conf"
  else
      modutilpath=""
      return 1
  fi
  return 0
}
get_rhmodflags ()
{
  if is_kernel26 ; then
    rhmodflags=""
  else
    [ -z "${rhmodflags}" ] || return 0
    get_rhkernel || return $?
    [ -n "${rhkernel}" ] || return 0
    get_variant || return $?
    get_cpu || return $?
    local rhvariant=`echo ${variant:-UP} | tr a-z A-Z`
    rhmodflags="-D__BOOT_KERNEL_H_ -D__MODULE_KERNEL_${cpu}=1 -D__BOOT_KERNEL_${rhvariant}=1"
  fi
}
get_allcflags ()
{
  get_cflags || return $?
  get_modflags || return $?
  get_rhmodflags || return $?
  echo "${cflags} ${modflags} ${rhmodflags}" | sed 's/[[:space:]]*$//'
}
get_diag ()
{
  # Some distance to rpmbuild output
  echo
  if [ -z "${srcdir}" ] ; then
    cat <<EOF >&2
srcdir was not specified, and I was unable to determine it myself.
Output from command "$(basename $0) srcdir -u ${uname:-$(uname -r)}":
# Start
EOF
    bash ${0} srcdir -u "${uname:-$(uname -r)}" >&2
    cat <<EOF >&2
# End

Please provide the source dir with the -s option and/or provide the kernel
uname with the -u option.  If you are building am RPM, the corresponding
arguments to rpmbuild are usually '--define "ksrc </path/to/your/source/tree>"'
and '--define "kernel <your_uname>'.

Aborting.

EOF
    return 128
  fi

  if [ -z "${cpu}" -o -z "${uname}" ] ; then
    cat <<EOF >&2
At least srcdir, uname and arch required.

Aborting.

EOF
    return 128
  fi

  if ! get_config ; then
    cat <<EOF >&2
Could not find kernel config file.  Maybe the chosen variant does not exist?

Aborting.

EOF
    return 128
  fi

  get_rhkernel
  get_verrel
  get_unamefromsrc

  if [ "${rhkernel}" = "1" ] ; then
    if is_kernel24 && [ "${unamefromsrc%%custom}" != "${verrel}" ] ; then
      cat <<EOF >&2
Error: resolved "verrel" (${verrel}) and the uname resolved from sources
(${unamefromsrc%%custom}) do not match.  Please provide the correct
uname with the -u option.  If you are building a RPM, this is usually the
'--define "kernel <your_uname>"' option to rpmbuild.

Aborting.

EOF
      return 1
    fi
  else
    if [ "${unamefromsrc}" != "${uname}" ] ; then
      cat <<EOF >&2
Error: provided "uname" (${uname}) and the uname resolved from sources
(${unamefromsrc}) do not match.  Please provide the correct uname with the
-u option and/or the correct source dir with the -s option.  If you are
building a RPM, the corresponding options to rpmbuild are usually
'--define "kernel <your_uname>"' and
'--define "ksrc </path/to/your/source/tree>"'.

Aborting.

EOF
      return 1
    fi
  fi

  if [ "${rhkernel}" = "1" ] ; then
    local providedcpu="${cpu}"
    cpu=""
    get_cpu
    if [ "${providedcpu}" != "${cpu}" ] ; then
      cat <<EOF >&2
Warning:  you provided the target CPU "${providedcpu}" but are running a
kernel built for "${cpu}".  Changes are that the resulting binaries will not
work with this kernel.
EOF
      [ -z "${nowait}" ] && sleep 10
    fi
    cpu="${providedcpu}"
    unset providedcpu
  fi

  for i in variant version config cc cflags modflags rhmodflags variant ; do
    get_"${i}"
    local errorlevel=$?
    if [ $errorlevel -gt 0 ] ; then
      echo "Error: get_${i} returned $errorlevel.  Aborting." >&2
      exit 1
    fi
  done

  cat <<EOF
Modules will be built with these settings, make sure they are correct:

Version:                 ${version}
Uname:                   ${uname}
Kernel compiler:         ${cc}
Target CPU:              ${cpu}
Kernel source dir:       ${srcdir}
Kernel CFLAGS:           ${cflags}
Kernel MODFLAGS:         ${modflags}
EOF
  if [ "${config}" = "${config##${srcdir}}" ] ; then
    echo "Config file:             ${config}"
  else
    echo "Config file:             \$srcdir${config##${srcdir}}"
  fi
  echo "RH/FC kernel:            ${rhkernel}"
  if [ "${rhkernel}" = "1" ] ; then
    cat <<EOF
RH/FC kernel release:    ${verrel}
RH/FC kernel variant:    ${variant:-(none -- kernel is UP)}
RH/FC specific MODFLAGS: ${rhmodflags}
EOF
  fi
  echo
}
usage ()
{
  cat <<EOF
You called: ${invocation}

Usage: ${myprog} <command> <option>+
 Commands:
  cc                 - Get compiler.
  config             - Get config file name.
  cpu                - Get target CPU.
  cflags             - Get kernel CFLAGS.
  modflags           - Get module CFLAGS.
  rhmodflags         - Get RH/FC specific module CFLAGS.
  allcflags          - Get all CFLAGS (cflags, modflags, rhmodflags).
  srcdir             - Get source dir.
  uname              - Get uname.
  variant            - Get variant (bigmem, smp etc).
  verrel             - Get "base" version-release.
  version            - Get "base" version.
  rhkernel           - 1 if the source tree is a RH/FC one, 0 otherwise.
  kernel2[46]        - 1 if the source tree is a 2.[46], 0 otherwise.
  modutilpath        - Returns modutils config file
  diag               - Check settings and output a brief overview.
 Options:
  -s|--srcdir <dir>  - Source directory.
  -p|--cpu <cpu>     - Target CPU. The default is resolved based
                       on the running kernel.
  -u|--uname <uname> - Target uname.  The default is $(uname -r).
  -f|--config <file> - Configuration file.
                       If set, CPU and uname are ignored.
     --nowait        - Do not wait if "diag" finds something unusual.
  -h|-?|--help       - Output this help and exit.
  -V|--version       - Output version number and exit.
EOF
}

invocation="$(basename ${0}) $@"
while [ "${1}" ] ; do
  case "${1}" in
    allcflags|cc|cflags|config|cpu|diag|kernel24|kernel26|modflags|modutilpath|rhkernel|rhmodflags|srcdir|uname|variant|verrel|version)
      var="${1}"
      ;;
    -s|--srcdir)
      shift
      srcdir="${1}"
      ;;
    -p|--cpu)
      shift
      cpu="${1}"
      ;;
    -u|--uname)
      shift
      uname="${1}"
      ;;
    -f|--config)
      shift
      config="${1}"
      ;;
    -h|--help|-\?)
      usage
      exit 0
      ;;
    -V|--version)
      echo "${myprog} ${myver}"
      exit 0
      ;;
    --nowait)
      nowait="true"
      ;;
    *)
      echo "Error: Unknown option '${1}'." >&2
      usage >&2
      exit 2
      ;;
  esac
  shift
done

if [ -z "${var}" ] ; then
  echo "Error: command not specified." >&2
  usage >&2
  exit 2
fi

get_"${var}" || exit $?
[ -z "${!var}" ] || echo "${!var}"

# Local variables:
# mode: sh
# sh-indentation: 2
# indent-tabs-mode: nil
# End:
# ex: ts=2 sw=2 et
