#!/bin/sh

set -e

uname_s=$(uname -s)

_get_kernel_dir() {
    KVER=$1
    case ${uname_s} 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 ${uname_s} in
	Linux)		test -e $DIR/include ;;
	GNU/kFreeBSD)	test -e $DIR/kern && test -e $DIR/conf/kmod.mk ;;
	*)		return 1 ;;
    esac
    return $?
}

# Check the existence of a kernel named as $1
_is_kernel_name_correct() {
    CORRECT="no"
    KERNEL_NAME=$1

    for kernel in /boot/config-*; do
        KERNEL=${kernel#*-}
        if [ "${KERNEL}" = "${KERNEL_NAME}" ]; then
            CORRECT="yes"
            break
        fi
    done

    echo $CORRECT
}


# Get the most recent kernel on Debian based systems. This keeps
# into account both the version and the ABI. If the current kernel
# is the most recent kernel then the function will print a null string.
_get_newest_kernel_debian() {
    NEWEST_KERNEL=
    NEWEST_VERSION=
    NEWEST_ABI=

    for kernel in /boot/config-*; do
        KERNEL=${kernel#*-}
        KERNEL_VERSION=${KERNEL%%-*}
        ABI=${KERNEL#*-}
        ABI=${ABI%%-*}

        if [ -z "$NEWEST_KERNEL" ]; then
            # The 1st time get a version which is bigger than $1
            COMPARE_TO=$1
        else
            # Get the biggest version
            COMPARE_TO="$NEWEST_VERSION-$NEWEST_ABI"
        fi

        # Make sure that kernel exists before comparing it
        if [ ! -e /lib/modules/$KERNEL ]; then
            continue
        fi

        # if $kernel is greater than $COMPARE_TO
        if [ `dpkg --compare-versions "$KERNEL_VERSION-$ABI" gt "$COMPARE_TO" && echo "yes" || \
              echo "no"` = "yes" ]; then
            NEWEST_KERNEL=$KERNEL
            NEWEST_VERSION=$KERNEL_VERSION
            NEWEST_ABI=$ABI
        fi
    done

    echo "$NEWEST_KERNEL"
}

# Get the most recent kernel in Rhel based systems. If the current kernel
# is the most recent kernel then the function will print a null string.
_get_newest_kernel_rhel() {
    NEWEST_KERNEL=

    LAST_INSTALLED_KERNEL=$(rpm -q --whatprovides kernel  --last | grep kernel -m1 | cut -f1 -d' ')

    LIK_FORMATTED_NAME=$(rpm -q $LAST_INSTALLED_KERNEL --queryformat="%{VERSION}-%{RELEASE}.%{ARCH}\n")

    if [ `echo $LIK_FORMATTED_NAME | grep 2.6 >/dev/null` ]; then
        # Fedora and Suse
        NEWEST_KERNEL=$LIK_FORMATTED_NAME
    else
        # Hack for Mandriva where $LIK_FORMATTED_NAME is broken
        LIK_NAME=$(rpm -q $LAST_INSTALLED_KERNEL --queryformat="%{NAME}\n")
        LIK_TYPE=${LIK_NAME#kernel-}
        LIK_TYPE=${LIK_TYPE%%-*}
        LIK_STRIPPED=${LIK_NAME#kernel-}
        LIK_STRIPPED=${LIK_STRIPPED#$LIK_TYPE-}
        LIK_STRIPPED_BASE=${LIK_STRIPPED%%-*}
        LIK_STRIPPED_END=${LIK_STRIPPED#$LIK_STRIPPED_BASE-}
        LIK_FINAL=$LIK_STRIPPED_BASE-$LIK_TYPE-$LIK_STRIPPED_END

        NEWEST_KERNEL=$LIK_FINAL
    fi

    echo $NEWEST_KERNEL
}

# Get the newest kernel on Debian and Rhel based systems.
get_newest_kernel() {
    NEWEST_KERNEL=
    # Try Debian first as rpm can be installed in Debian based distros
    if [ -e /usr/bin/dpkg ]; then
        # If DEB based
        CURRENT_KERNEL=$1
        CURRENT_VERSION=${CURRENT_KERNEL%%-*}
        CURRENT_ABI=${CURRENT_KERNEL#*-}
        CURRENT_FLAVOUR=${CURRENT_ABI#*-}
        CURRENT_ABI=${CURRENT_ABI%%-*}
        NEWEST_KERNEL=$(_get_newest_kernel_debian "$CURRENT_VERSION-$CURRENT_ABI")

    elif [ `which rpm >/dev/null` ]; then
        # If RPM based
        NEWEST_KERNEL=$(_get_newest_kernel_rhel)
    fi

    # Make sure that kernel name that we extracted corresponds to an installed
    # kernel
    if [ -n "$NEWEST_KERNEL" ] && [ `_is_kernel_name_correct $NEWEST_KERNEL` = "no" ]; then
        NEWEST_KERNEL=
    fi

    echo $NEWEST_KERNEL
}

NAME=$1
VERSION=$2
TARBALL_ROOT=$3
ARCH=$4
UPGRADE=$5

if [ -z "$NAME" ] || [ -z "$VERSION" ]; then
    echo "Need NAME, and VERSION defined"
    echo "ARCH is optional"
    exit 1
fi

KERNELS=$(ls /lib/modules/ 2>/dev/null || true)
CURRENT_KERNEL=$(uname -r)

#We never want to keep an older version side by side to prevent conflicts
if [ -e "/var/lib/dkms/$NAME/$VERSION" ]; then
    echo "Removing old $NAME-$VERSION DKMS files..."
    dkms remove -m $NAME -v $VERSION --all
fi

#Load new files, by source package and by tarball
if [ -f "$TARBALL_ROOT/$NAME-$VERSION.dkms.tar.gz" ]; then
    if ! dkms ldtarball --archive "$TARBALL_ROOT/$NAME-$VERSION.dkms.tar.gz"; then
        echo ""
        echo ""
        echo "Unable to load DKMS tarball $TARBALL_ROOT/$NAME-$VERSION.dkms.tar.gz."
        echo "Common causes include: "
        echo " - You must be using DKMS 2.1.0.0 or later to support binaries only"
        echo "   distribution specific archives."
        echo " - Corrupt distribution specific archive"
        echo ""
        echo ""
        exit 2
    fi
elif [ -d "/usr/src/$NAME-$VERSION" ]; then
    echo "Loading new $NAME-$VERSION DKMS files..."
    dkms add -m $NAME -v $VERSION > /dev/null
fi

# On 1st installation, let us look for a directory
# in /lib/modules which matches `uname -r`. If none
# is found it is possible that buildd is being used
# and that uname -r is giving us the name of the
# kernel used by the buildd machine.
#
# If this is the case we try to build the kernel
# module for each kernel which has a directory in
# /lib/modules. Furthermore we will have to tell
# DKMS which architecture it should build the module
# for (e.g. if the buildd machine is using a
# 2.6.24-23-xen 64bit kernel).
#
# NOTE: if the headers are not installed then the
#       module won't be built, as usual
if [ -z "$UPGRADE" ]; then
    echo "First Installation: checking all kernels..."
    for KERNEL in $KERNELS; do
        if [ ${KERNEL} = ${CURRENT_KERNEL} ]; then
            # Kernel found
            KERNELS=$CURRENT_KERNEL
            break
        fi
    done
else
    KERNELS=$CURRENT_KERNEL
fi

# Here we look for the most recent kernel so that we can
# build the module for it (in addition to doing it for the
# current kernel.
NEWEST_KERNEL=$(get_newest_kernel "$KERNELS")

# If the current kernel doesn't come from the host of a chroot
if [ `_is_kernel_name_correct $CURRENT_KERNEL` = "yes" ]; then
    # See if it's worth building the module for both the newest kernel
    # and for the current kernel
    if [ -n "$NEWEST_KERNEL" ] && [ ${CURRENT_KERNEL} != ${NEWEST_KERNEL} ]; then
        echo "Building for $CURRENT_KERNEL and $NEWEST_KERNEL"
        KERNELS="$CURRENT_KERNEL $NEWEST_KERNEL"
    else
        echo "Building only for $CURRENT_KERNEL"
    fi
# The current kernel is not useful as it's the host's
else
    echo "It is likely that $CURRENT_KERNEL belongs to a chroot's host"

    # Let's use only the newest kernel
    if [ -n "$NEWEST_KERNEL" ]; then
        KERNELS="$NEWEST_KERNEL"
        echo "Building only for $NEWEST_KERNEL"
    fi
fi

if [ -n "$ARCH" ]; then
    if which lsb_release >/dev/null && [ $(lsb_release -s -i) = "Ubuntu" ]; then
        case $ARCH in
            amd64)
                ARCH="x86_64"
                ;;
            lpia|i?86)
                ARCH="i686"
                ;;
        esac
    fi
    echo "Building for architecture $ARCH"
    ARCH="-a $ARCH"
fi

for KERNEL in $KERNELS; do
    dkms_status=`dkms status -m $NAME -v $VERSION -k $KERNEL $ARCH`
    if [ `echo $KERNEL | grep -c "BOOT"` -gt 0 ]; then
        echo ""
        echo "Module build and install for $KERNEL was skipped as "
        echo "it is a BOOT variant"
        continue
    fi


    #if the module isn't yet built, try to build it
    if [ `echo $dkms_status | grep -c ": built"` -eq 0 ]; then
        if [ ! -L /var/lib/dkms/$NAME/$VERSION/source ]; then
            echo "This package appears to be a binaries-only package"
            echo " you will not be able to build against kernel $KERNEL"
            echo " since the package source was not provided"
            continue
        fi
        if _check_kernel_dir $KERNEL; then
            echo "Building initial module for $KERNEL"
            set +e
            dkms build -m $NAME -v $VERSION -k $KERNEL $ARCH > /dev/null
            case $? in
            9)
                set -e
                echo "Skipped."
                continue
                ;;
            0)
                set -e
                echo "Done."
                ;;
            *)
                exit $?
                ;;
            esac
            dkms_status=`dkms status -m $NAME -v $VERSION -k $KERNEL $ARCH`
        else
            echo "Module build for the currently running kernel was skipped since the"
            echo "kernel source for this kernel does not seem to be installed."
        fi
    fi

    #if the module is built (either pre-built or just now), install it
    if [ `echo $dkms_status | grep -c ": built"` -eq 1 ] &&
       [ `echo $dkms_status | grep -c ": installed"` -eq 0 ]; then
        dkms install -m $NAME -v $VERSION -k $KERNEL $ARCH --force
    fi
done

