#!/bin/sh

po_domain="alterator-update-kernel"
alterator_api_version=1

. alterator-sh-functions

KMAXVER=

get_loaded_version() {
	current_kernel_release="$(uname -r)"
	write_string_param loaded_release $current_kernel_release

	write_string_param loaded_version "${current_kernel_release%%-*}"

	current_kernel_release="${current_kernel_release#*-}"

	write_string_param loaded_flavour "${current_kernel_release%-*}"
}

remove_tmp_env(){
	umount /tmp/upd_krnl_tmp/
	unset TMPDIR
	rmdir /tmp/upd_krnl_tmp/
}

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

	modules_to_install=""

	#a list of installed modules
	IFS=';' read -r -a array <<< $in_modlist

	for module in ${array[@]}
	do
		module=$(echo "$allmodules" | grep "^[[:space:]]*kernel-modules-${module}-${in_flavour}#" | sort -r -u -V | head -1)
		modules_to_install="$modules_to_install $module"
	done

	#make-initrd needs it when /tmp with noexec
	mkdir /tmp/upd_krnl_tmp
	mount -t tmpfs tmpfs /tmp/upd_krnl_tmp
	export TMPDIR=/tmp/upd_krnl_tmp
	#install kernel and modules
	command_output=`apt-get install -y "kernel-image-$in_flavour#$kmaxver" $modules_to_install 2>&1` || {
		write_error "`_ "Failed to install kernel."`"
		remove_tmp_env
		return
	}

	remove_tmp_env

	while IFS= read -r line
	do
		if [[ "$line" =~ ^"make-initrd:".*"Unable to check executable bit" ]];then
			write_error "`_ "Failed to install kernel. Perhaps 'noexec' mount option used for /tmp.
				Please do not try to boot with this kernel."`"
			return
		fi
	done <<< "$command_output"

	# 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"

			ldconfig
			;;
	esac

	# update headers

	for package in kernel-headers kernel-headers-modules; do
		if rpm -q $package-"$in_flavour" &>/dev/null; then
			apt-get install -y "$package-$in_flavour"
		fi
	done

	echo "`_ "Kernel update completed."`"
}

install_modules(){
	get_max_kernel_version $in_flavour
	kernel="$KMAXVER"

	kernel="${kernel%:*}"
	kernel="${kernel#*:}"

	#get a list of already installed modules
	modules="$(rpm -qa | grep ^kernel-modules | grep $in_flavour-$kernel | sort -u)"

	version=`expr "${kernel%-*}" : '.*\(.[0-9]*\.[0-9]*\.[0-9]*\)'`

	digits=(${version//./ })

	versionhash=$((${digits[0]}*2**16 + ${digits[1]}*2**8 + ${digits[2]}))

	modules2="$(rpm -qa | grep ^kernel-modules | grep $in_flavour | grep $versionhash | sort -u)"

	installed_modules=
	for mod in $modules; do
		mod="${mod#kernel-modules-}"
		mod="${mod%-$in_flavour*}"
		installed_modules="$installed_modules $mod"
	done

	for mod in $modules2; do
		mod="${mod#kernel-modules-}"
		mod="${mod%-$in_flavour*}"
		installed_modules="$installed_modules $mod"
	done

	#remove already installed modules from the list of modules for installation
	IFS=';' read -r -a array <<< $in_modlist

	for module in $installed_modules; do
		for i in ${!array[@]}; do
			if [ "${array[$i]}" == "$module" ]; then
				unset array[$i]
			fi
		done
	done

	modules_to_install=""
	for module in ${array[@]}
	do
		module=$(echo "$allmodules" | grep "^[[:space:]]*kernel-modules-${module}-${in_flavour}#" | sort -r -u -V | head -1)
		modules_to_install="$modules_to_install $module"
	done

	#install modules
	apt-get install -y $modules_to_install || {
		write_error "`_ "Failed to install modules."`"
		return
	}
	echo "`_ "Modules installed."`"
}

get_installed_kernels(){
	kernels="$(rpm -qa | grep kernel-image | sort -r)"

	#take the loaded version of the kernel
	loaded_kernel="$(uname -r)"
	loaded_version="${loaded_kernel%%-*}"
	tmp="${loaded_kernel#*-}"
	loaded_flavour="${tmp%-*}"
	loaded_release="${loaded_kernel#*$loaded_flavour-}"

	loaded_kernel="$loaded_flavour-$loaded_version-$loaded_release"

	#take the installed kernels
	installed_kernels=
	for kernel in $kernels; do
		kernel="${kernel%.*}"
		kernel="${kernel#kernel-image-}"
		installed_kernels="$installed_kernels $kernel"
	done

	#put the loaded kernel version at the top of the list
	IFS=' ' read -r -a array <<< $installed_kernels
	for i in ${!array[@]}; do
		if [ "${array[$i]}" == "$loaded_kernel" ]; then
			unset array[$i]
			write_enum_item $loaded_kernel $loaded_kernel
			break
		fi
	done

	for i in ${!array[@]}; do
		write_enum_item ${array[$i]} ${array[$i]}
	done
}

get_installed_modules(){
	#get installed modules
	kernel=$in_kernel
	modules="$(rpm -qa | grep ^kernel-modules | grep $kernel | sort -u)"

	flavour="${kernel%-*}"
	flavour="${flavour%-*}"

	version=`expr "${kernel%-*}" : '.*\(.[0-9]*\.[0-9]*\.[0-9]*\)'`

	digits=(${version//./ })

	versionhash=$((${digits[0]}*2**16 + ${digits[1]}*2**8 + ${digits[2]}))

	modules2="$(rpm -qa | grep ^kernel-modules | grep $flavour | grep $versionhash | sort -u)"

	for mod in $modules; do
		mod="${mod#kernel-modules-}"
		mod="${mod%-$flavour*}"
		write_enum_item $mod $mod
	done

	for mod in $modules2; do
		mod="${mod#kernel-modules-}"
		mod="${mod%-$flavour*}"
		write_enum_item $mod $mod
	done
}

set_default_kernel(){
	#set kernel bootable by default
	version=`expr "${in_kernel%-*}" : '.*\(.[0-9]*\.[0-9]*\.[0-9]*\)'`
	flavour="${in_kernel%-$version*}"
	release="${in_kernel#*$version-}"

	ln -sf initrd-$version-$flavour-$release.img /boot/initrd.img
	ln -sf initrd-$version-$flavour-$release.img /boot/initrd-$flavour.img
	ln -sf vmlinuz-$version-$flavour-$release /boot/vmlinuz
	ln -sf vmlinuz-$version-$flavour-$release /boot/vmlinuz-$flavour

	#select the line with vmlinuz in the grub menu
	grub_entries="/usr/sbin/grub-entries"
	grub_set_default="/usr/sbin/grub-set-default"
	if [ -x "$grub_entries" ]
	then
		vmlinuz=$("$grub_entries" | grep "vmlinuz" | grep -v "(" | cut -f 1)
		"$grub_set_default" $vmlinuz
	fi

	#rpi4 filetrigger
	rpi4_filetrigger="/usr/lib/rpm/rpi4-boot-nouboot.filetrigger"
	if [ -x "$rpi4_filetrigger" ]
	then
		echo /boot/vmlinuz | "$rpi4_filetrigger"
	fi
}

remove_kernel(){
	version=`expr "${in_kernel%-*}" : '.*\(.[0-9]*\.[0-9]*\.[0-9]*\)'`
	flavour="${in_kernel%-$version*}"
	release="${in_kernel#*$version-}"
	apt-get remove -y kernel-image-$flavour\#[0-9]*:*$version-$release
}

remove_modules(){
	version=`expr "${in_kernel%-*}" : '.*\(.[0-9]*\.[0-9]*\.[0-9]*\)'`
	flavour="${in_kernel%-$version*}"
	release="${in_kernel#*$version-}"

	digits=(${version//./ })
	versionhash=$((${digits[0]}*2**16 + ${digits[1]}*2**8 + ${digits[2]}))

	modules_versionhash="$(rpm -qa | grep ^kernel-modules | grep $flavour | grep $versionhash | sort -u)"
	modules_version="$(rpm -qa | grep ^kernel-modules | grep $in_kernel | sort -u)"

	all_modules=""
	for mod in $modules_versionhash; do
		mod="${mod%-$flavour*}"
		all_modules="$all_modules $mod-$flavour#.*$versionhash*"
	done

	for mod in $modules_version; do
		mod="${mod%-$flavour*}"
		all_modules="$all_modules $mod-$flavour#.*$version-$release*"
	done

	modules_to_remove=""

	IFS=';' read -r -a array <<< $in_modlist

	for remove_mod in ${array[@]}
	do
		for module in $all_modules; do
			if [[ "$module" == *"$remove_mod"* ]]; then
				modules_to_remove="$modules_to_remove $module"
				break
			fi
		done
	done

	#remove modules
	apt-get remove -y $modules_to_remove
}

get_flavours(){
	#take a list of flavours
	kernels="$(apt-cache pkgnames kernel-image | sort -u -r)"

	flavours=
	for image in $kernels
	do
		image="${image#kernel-image-}"
		image="${image%#*}"
		flavours="$flavours $image"
	done

	flavours=$(echo $flavours | tr -s [:space:] \\n | sort -u -r)

	#remove debuginfo from the list of flavours
	flavours_result=
	for flavour in $flavours; do
		if [[ "$flavour" == *"-debuginfo" ]]; then
			continue
		fi
		flavours_result="$flavours_result $flavour"
	done
	flavours="$flavours_result"

	#take the loaded version of flavour
	loaded_kernel="$(uname -r)"
	tmp="${loaded_kernel#*-}"
	loaded_flavour="${tmp%-*}"

	#the first in the list is the loaded kernel flavour
	write_enum_item $loaded_flavour $loaded_flavour
	flavours=${flavours/$loaded_flavour/}

	for flavour in $flavours; do
	write_enum_item $flavour $flavour
	done
}

get_default_kernel(){
	vmlinuz="$(readlink -f /boot/vmlinuz)"
	write_string_param default_kernel "${vmlinuz#/boot/vmlinuz-}"
}

apt_get_update(){
	apt-get update -y || {
		write_error "`_ "Error when updating the list of packages available in the repository. The list of available packages may be out of date."`"
		return
	}
}

get_max_kernel_version(){
	flavour="$1"
	# get list of all available kernel packages
	kernel_pkgs="$(apt-cache pkgnames kernel-image)"
	# check that we have at least one kernel with defined kernel_flavour
	num_available_kernels="$(echo "$kernel_pkgs" | grep -c "$flavour")"
	[ "$num_available_kernels" != 0 ] || {
	return
	}

	kmaxver=

	# define how we must select available packages with needed flavour
	pgkgrep="kernel-image-$flavour#" || pgkgrep="kernel-image-$flavour#.*$release$"

	while read version
	do
		comparever="$(rpmevrcmp "$kmaxver" "$version")"
		[ "$comparever" -lt 0 ] && kmaxver="$version" ||:
	done <<<"$(echo "$kernel_pkgs" | grep "$pgkgrep" | sed -e "s,^kernel-image-$flavour#,,g")"

	KMAXVER="$kmaxver"
}

get_available_kernel(){
	get_max_kernel_version $in_flavour
	kmaxver="$KMAXVER"

	version="${kmaxver%:*}"
	version="${version#*:}"

    if [ -z "$kmaxver" ]; then
		write_string_param available_kernel "$version"
		write_bool_param kernel_installation_possible "false"
		write_bool_param modules_installation_possible "false"
		return
	fi

	# check if selected kernel is already installed
	if rpm -q "kernel-image-$in_flavour-$kmaxver" >/dev/null ; then
		write_string_param available_kernel "$version"
		write_bool_param kernel_installation_possible "false"
		write_bool_param modules_installation_possible "true"
		return
	fi

	write_string_param available_kernel "$version"
	write_bool_param kernel_installation_possible "true"
	write_bool_param modules_installation_possible "false"
}

get_available_modules(){
	get_max_kernel_version $in_flavour
	kmaxver="$KMAXVER"
	if [ -z "$kmaxver" -o -z "$in_flavour" ]; then
		return
	fi
	# get list of all available modules
	allmodules="$(apt-cache whatdepends kernel-image-$in_flavour#$kmaxver | grep "kernel-modules-.*-$in_flavour" | sed -re 's/-[^-]*-[^-]*$//'  | sort -u)"

	#get list for ui
	for module in $allmodules; do
		module_pkgname="${module%%#*}"
		module_pkgname="${module_pkgname%-$in_flavour}"

		mod=${module_pkgname#kernel-modules-}
		write_enum_item $mod $mod
	done
}

get_installed_modules_available_in_apt_cache(){
	get_max_kernel_version $in_flavour
	kmaxver="$KMAXVER"
	if [ -z "$kmaxver" -o -z "$in_flavour" ]; then
		return
	fi
	# get list of all available modules
	allmodules="$(apt-cache whatdepends kernel-image-$in_flavour#$kmaxver | grep "kernel-modules-.*-$in_flavour" | sed -re 's/-[^-]*-[^-]*$//'  | sort -u)"

	#is there an already installed kernel of this flavour?
	#if not, then mark the installed modules of the loaded kernel.
	kernels="$(rpm -qa | grep kernel-image | grep $in_flavour)"
	if [ "$kernels" ]
		then
			mod_flavour="$in_flavour"
		else
			loaded_flavour="$(uname -r)"
			loaded_flavour="${loaded_flavour#*-}"
			mod_flavour="${loaded_flavour%-*}"
	fi

	#get list for ui
	instmodules=""
	for module in $allmodules; do
		module_pkgname="${module%%#*}"
		module_pkgname="${module_pkgname%-$in_flavour}"

		mod=${module_pkgname#kernel-modules-}

		if rpm -q $module_pkgname-"$mod_flavour" &>/dev/null; then
			if [ "$instmodules" ]
			then
				instmodules="$instmodules;$mod"
			else
				instmodules=$mod
			fi
		else
			continue
		fi
	done

	write_string_param inst_avail_mod_list "$instmodules"
}

on_message(){
	case "$in_action" in
		list)
			case "$in__objects" in
				get_installed_kernels)
				get_installed_kernels
				;;
				get_installed_modules)
				get_installed_modules
				;;
				get_flavours)
				get_flavours
				;;
				get_available_modules)
				get_available_modules
				;;
			esac
		;;
		read)
			case "$in__objects" in
				get_installed_modules_available_in_apt_cache)
				get_installed_modules_available_in_apt_cache
				;;
				get_loaded_release)
				get_loaded_version
				;;
				get_default_kernel)
				get_default_kernel
				;;
				get_available_kernel)
				get_available_kernel
				;;
			esac
		;;
		write)
			case "$in__objects" in
				set_default_kernel)
				set_default_kernel
				;;
				remove_kernel)
				remove_kernel
				;;
				remove_modules)
				remove_modules
				;;
				apt_get_update)
				apt_get_update
				;;
				update_kernel)
				update_kernel
				;;
				install_modules)
				install_modules
				;;
			esac
		;;
	esac
}

message_loop
