#!/bin/bash
# Copyright (C) 2004, 2013, 2015 Vitaly Lipatov <lav@etersoft.ru>
# Copyright (C) 2005-2008 Anatoly Kitaykin <cetus@newmail.ru>
# Copyright (C) 2008-2009 Vladimir V. Kamarzin <vvk@altlinux.org>
# Copyright (C) 2008-2009 Michael Shigorin <mike@altlinux.org>
# Copyright (C) 2008 Konstantin Baev <kipruss@altlinux.org>
# Copyright (C) 2010 Dmitry Kulik <lnkvisitor@altlinux.org>
#
# Update kernel with modules
#
# This file 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.

. shell-error
. shell-args

shopt -s extglob

show_help()
{
        cat <<EOF
Usage: $PROG [options]
Valid options are:
	-l, --list        list available kernels
	-a, --all         install all kernel modules
	-i, --interactive interactive modules install
	-f, -y, --force   force kernel upgrade
	-t, --type        install kernel with specified flavour (ovz-smp, std-def, etc)
	-r, --release     desired kernel release (allowed formats by example:
				old format: alt1, 5.7.19-alt1
				classic kernel release: 5.7.19-std-def-alt1
				package name: [kernel-image-]std-def-5.7.19-alt1)
	-n, --dry-run     download packages, but don't install (--download-only)
	-h, --help        show this text and exit
EOF
exit 1
}

# module_install will either instal module $1 or update modules_to_install
# variable with this module to install it in one transaction with kernel
module_install() {
    module=$1

    if [ "$interactive" = 1 ]; then
	echo -n "Try to install or update module $module [y]/n? "
	read
	case "$REPLY" in
		n|no|N|No|NO|0) return 0
			;;
		*)
			;;
	esac
	$SUDO apt-get install -o APT::Install::VirtualVersion=true -o APT::Install::Virtual=true -y $dryrun "$module"
    else
	export modules_to_install="$modules_to_install $1"
    fi
}

#parse command line options
TEMP=`getopt -n $PROG -o a,i,l,f,y,t:,r:,n,h -l interactive,list,all,force,type:,release:,dry-run,help -- "$@"` || show_help
eval set -- "$TEMP"

while :; do
        case "$1" in
                --) shift; break
                        ;;
                -l|--list) list=1
                        ;;
                -a|--all) all=1
                        ;;
                -i|--interactive) interactive=1
                        ;;
                -f|-y|--force) force=1
                        ;;
		-t|--type) shift ; kernel_flavour="$1"
			;;
		-r|--release) shift ; release="$1"
			;;
		-n|--dry-run) dryrun="--download-only"
			;;
                -h|--help) show_help
        esac
	shift
done

if [ -n "$interactive" -a -n "$force" ]; then
	show_usage '--force and --interactive are mutually exclusive options.'
fi

###################################################################
# update kernel and modules

if [ -n "$list" ]; then
	declare -i count=0
	rrel=$(uname -r)
	if [ -n "$kernel_flavour" ]; then
		rflv=$kernel_flavour
	else
		rflv=${rrel#*-}
		rflv=${rflv%-*}
	fi
	def=$(readlink /boot/vmlinuz)
	def=${def#vmlinuz-}
	echo "List of available kernels${kernel_flavour:+ for $kernel_flavour flavour}:"
	list_files=$(mktemp)
	rpm -qa --queryformat '[%{=NAME}\t%{FILENAMES}\n]' 'kernel-*' > $list_files
	list_extras=$(mktemp)
	set -o pipefail
	apt-cache list-extras 2>/dev/null | sort > "$list_extras" && extras=1 || extras=
	apt-cache pkgnames "kernel-image-${kernel_flavour}" | sort \
	| join -a1 -o1.1,2.1 - "$list_extras" \
	| sort -V \
	| grep '#' \
	| grep -v -e '-debuginfo#' -e '^kernel-image-domU-' \
	| (
	  while read k j; do
		pkg=${k%#*}
		flv=${pkg#kernel-image-}
		ev=${k#*#}
		ver=${ev#+([0-9]):}
		epo=${ev%$ver}
		ver=${ver%%[:@]*}
		rel=${ver#*-}
		ver=${ver%-*}
		ev=$epo$ver

		[ -z "$extras" -o -n "$j" ] && echo -n "   " || echo -n "  *"
		echo -n " $pkg-$ver-$rel"
		if rpm -q $pkg-$ver-$rel >/dev/null 2>&1; then
			if [ "$def" = "$ver-$flv-$rel" ]; then
				echo -n ' [default]'
			else
				echo -n ' [installed]'
			fi
		elif [ -n "$extras" -a -z "$j" ] && [ "$rflv" = "$flv" ]; then
			echo -n " <--- upgrade"
		fi
		[ "$rrel" = "$ver-$flv-$rel" ] && echo -n ' RUNNING'

		addons=$(cat "$list_files" \
			| grep	-e "/lib/modules/$ver-$flv-$rel/" \
				-e "[[:space:]]/usr/include/linux-$ver-$flv\$" \
				-e "[[:space:]]/boot/vmlinux-$ver-$flv-$rel\$" \
			| cut -f1 \
			| sort -u \
			| while read m; do
				# Skip kernel-image which is reported above.
				[ -n "${m#kernel-image-$flv}" ] || continue
				m=${m#kernel-image-}
				m=${m#$flv-}
				m=${m#kernel-modules-}
				m=${m%-$flv}
				m=${m#kernel-}
				echo -n " $m"
			done
		)
		[ -n "$addons" ] && echo -n " |$addons"
		echo
		count+=1
	done
	if [ $count -eq 0 ]; then
		echo "Nothing found."
	else
		[ -n "$extras" ] && echo "[*] Latest available in repo."
	fi
	)
	rm -f -- "$list_extras"
	exit 0
fi

# get running kernel version
current_kernel_package=$(rpmquery -qf /lib/modules/$(uname -r)/kernel 2>/dev/null)
if [ -n "$current_kernel_package" ] ; then
    current_kernel_pkgname=$(rpmquery --queryformat "%{NAME}-%{SERIAL}:%{VERSION}-%{RELEASE}\n" -q $current_kernel_package)
    echo $current_kernel_pkgname | grep -q "(none)" && current_kernel_pkgname=$(rpmquery --queryformat "%{NAME}-%{VERSION}-%{RELEASE}\n" -q $current_kernel_package)
else
    current_kernel_pkgname="UNKNOWN"
fi
echo "Running kernel version: $current_kernel_pkgname"

if [[ "$release" =~ ^[0-9]+\.[0-9.]+-.*-[^-]+$ ]]; then
	# Rewrite release in `uname -r` compatible 'kernel release' format
	ver="${release%-*}"
	flv="${ver#*-}"
	ver="${ver%%-*}"
	rel="${release##*-}"
	if [ -n "$kernel_flavour" ] && [ "$kernel_flavour" != "$flv" ]; then
		fatal "Kernel flavour from '-t $kernel_flavour' does not match flavour from '-r' ($flv)."
	fi
	kernel_flavour="$flv"
	release="$ver-$rel"
	ver= flv= rel=
elif [[ "$release" =~ ^(kernel-image-)?.*[-=#][0-9:]+\.[0-9.]+-[^-]+$ ]]; then
	# Rewrite release in RPM, APT package format
	ver="${release#kernel-image-}"
	flv="${ver%[-=#]*-*}"
	rel="${ver#$flv[-=#]}"
	if [ -n "$kernel_flavour" ] && [ "$kernel_flavour" != "$flv" ]; then
		fatal "Kernel flavour from '-t $kernel_flavour' does not match flavour from '-r' ($flv)."
	fi
	kernel_flavour="$flv"
	release="$rel"
	ver= flv= rel=
fi

# set kernel flavour. if not defined with -t option, use current
current_kernel_flavour="$(uname -r)"
current_kernel_flavour="${current_kernel_flavour#*-}"
current_kernel_flavour="${current_kernel_flavour%-*}"
kernel_flavour="${kernel_flavour:-$current_kernel_flavour}"

echo "Checking for available $kernel_flavour kernel packages..."
# get list of all available kernel packages
KERNEL_PKGS="$(apt-cache pkgnames "kernel-image-$kernel_flavour#" | sort -V)"

# check that we have at least one kernel with defined kernel_flavour
num_available_kernels="$(echo "$KERNEL_PKGS" | grep -c "$kernel_flavour")"
[ "$num_available_kernels" != 0 ] || fatal "There are no available kernels with kernel flavour $kernel_flavour"

# check that we have at least one kernel with defined kernel package release
[ -z "$release" ] || {
	num_available_kernels="$(echo "$KERNEL_PKGS" | grep -c "$release")"
	[ "$num_available_kernels" != 0 ] || fatal "There are no available $kernel_flavour kernels with package release $release"
}

# define how we must select available packages with needed flavour/release
[ -z "$release" ] && pgkgrep="kernel-image-$kernel_flavour#" || pgkgrep="kernel-image-$kernel_flavour#.*\b${release//./\\.}\([@:]\|\$\)"

# get the maximum available kernel package version
kmaxver=
while read version
do
	comparever="$(rpmevrcmp "$kmaxver" "$version")"
	[ "$comparever" -lt 0 ] && kmaxver="$version" ||:
done <<<"$(echo "$KERNEL_PKGS" | grep "$pgkgrep" | sed -e "s,^kernel-image-$kernel_flavour#,,g")"
[ -z "$kmaxver" ] && fatal "Selected kernel not found."

# check if the current kernel is up-to-date
if [ -z "$force" ] && [ "$current_kernel_pkgname" = "kernel-image-$kernel_flavour-$kmaxver" ] ; then
	echo "Latest kernel $kernel_flavour-$kmaxver is already running on your system."
	echo "No upgrade is needed. Use -f for force."
	exit 0
fi

# check if selected kernel is already installed
if [ -z "$force" ] && rpm -q "kernel-image-$kernel_flavour-$kmaxver" >/dev/null ; then
	echo "Latest kernel $kernel_flavour-$kmaxver is already installed on your system."
	echo "No upgrade is needed. Use -f for force."
	exit 0
fi

# ask user
echo -n "Try to install new kernel kernel-image-$kernel_flavour-$kmaxver and update its modules [y]/n? "
[ "$force" = 1 ] && echo "yes" || {
	read
	case "$REPLY" in
		n|no|N|No|NO|0) exit 0
			;;
		*)
			;;
	esac
}

# is there kernel-image-$kernel_flavour already installed or not?
# needed for proper modules selection
rpm -q kernel-image-"$kernel_flavour" &>/dev/null && modules_kernel_flavour="$kernel_flavour" \
	|| modules_kernel_flavour="$current_kernel_flavour"

# use sudo(1) if running as unprivileged user
[ "$UID" = "0" ] && SUDO= || SUDO=sudo

# install kernel iff we are working in interactive mode
if [ -n "$interactive" ]; then
	$SUDO apt-get install -y $dryrun "kernel-image-$kernel_flavour#$kmaxver" \
		|| fatal "failed to install kernel-image-$kernel_flavour-$kmaxver"
fi

###################################################################
# update modules

[ -z "$interactive" ] ||
message "Updating modules for kernel: $kernel_flavour-$kmaxver"

# get list of all available modules
ALLMODULES="$(apt-cache whatdepends kernel-image-$kernel_flavour#$kmaxver | grep "kernel-modules-.*-$kernel_flavour" | sed -re 's/-[^-]*-[^-]*$//'  | sort -u)"

# install modules or set modules_to_install variable
modules_to_install=
for module in $ALLMODULES; do
	module_pkgname="${module%%#*}"
	module_pkgname="${module_pkgname%-$kernel_flavour}"
	if rpm -q $module_pkgname-"$modules_kernel_flavour" &>/dev/null; then
		message "$module_pkgname is installed, trying to update..."
	elif [ "$all" = 1 ]; then
		message "$module_pkgname is not istalled, trying to install..."
	else
		continue
	fi

	# remove duplicates
	module=$(echo "$ALLMODULES" | grep "^[[:space:]]*${module_pkgname}-${kernel_flavour}#" | sort -r -u -V | head -1)

	module_install "$module"
done

# now install both kernel and modules iff we are not interactive
# (in wich case all of them already installed)
if [ -z "$interactive" ]; then
	$SUDO apt-get install -y $dryrun "kernel-image-$kernel_flavour#$kmaxver" $modules_to_install \
		|| fatal "failed to install kernel-image-$kernel_flavour-$kmaxver with modules"
fi

# try to run x11setupdrv, x11presetdrv
# if might need that (e.g. for nvidia_glx updates)
# NB: x11setupdrv seems deprecated in Sisyphus as of Oct 2009
X11SETUPDRV=/usr/bin/x11setupdrv
X11PRESETDRV=/usr/sbin/x11presetdrv
XORG=/usr/bin/Xorg

[ ! -x "$XORG" ] || \
case "$ALLMODULES" in
	*drm*|*fglrx*|*nvidia*)
		[ -x "$X11SETUPDRV" ] \
			&& $SUDO "$X11SETUPDRV"
		[ -x "$X11PRESETDRV" ] \
			&& $SUDO "$X11PRESETDRV" \
			|| message "You might need to run x11presetdrv, video drivers updated but it's missing"
		$SUDO ldconfig
		;;
esac


###################################################################
# update headers

for package in kernel-headers kernel-headers-modules; do
	if rpm -q $package-"$kernel_flavour" &>/dev/null; then
		message "$package is installed, trying to update..."
		$SUDO apt-get install -y $dryrun "$package-$kernel_flavour"
	fi
done
