# Copyright (C) 2006  Joey Hess
# Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2016  Martin Michlmayr <tbm@cyrius.com>
# Copyright (C) 2011  Loïc Minier <lool@dooz.org>
# Copyright (C) 2011  Julian Andres Klode <jak@debian.org>
# Copyright (C) 2013-2016  Ian Campbell <ijc@debian.org>

# This program 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.

BOOTSCRIPTS_DIR="${FK_CHECKOUT:-/etc/flash-kernel}/bootscript"
FK_ETC_MACHINE="${FK_ETC_MACHINE:-/etc/flash-kernel/machine}"
FK_DEFAULTS="${FK_DEFAULTS:-/etc/default/flash-kernel}"
PROC_CPUINFO="${FK_PROC_CPUINFO:-/proc/cpuinfo}"
PROC_DTMODEL="${FK_PROC_DTMODEL:-/proc/device-tree/model}"
PROC_MTD="/proc/mtd"

read_machine_db() {
	if [ -f "${FK_ETC_DB:-/etc/flash-kernel/db}" ]; then
		cat "${FK_ETC_DB:-/etc/flash-kernel/db}"
	fi
	cat "${FK_CHECKOUT:-$FK_DIR}/db/"*.db
}
MACHINE_DB="$(read_machine_db)"

error() {
	echo "$@" >&2
	exit 1
}

mtdblock() {
	local mtdname="$1"

	local dev=`sed -rn "s,^mtd([^:]*).*\"$mtdname\"\$,/dev/mtdblock\\1,p" "$PROC_MTD"`

	# The mtdblock() function gets also called by the testsuite during
	# the package build; don't run modprobe and udevadm then.  Invasive
	# actions like loading modules and calling into udev should not
	# happen at build time and they are not necessary for the testsuite.
	if [ -z "${FK_TESTSUITE_RUNNING}" ]; then
		modprobe -q mtdblock && udevadm settle --exit-if-exists=$dev || :
	fi

	echo $dev
}

mtdchar() {
	local mtdname="$1"

	local dev=`sed -rn "s,^mtd([^:]*).*\"$mtdname\"\$,/dev/mtd\\1,p" "$PROC_MTD"`

	echo $dev
}

check_block_dev() {
	local dev="$1"

	if [ ! -b "$dev" ]; then
		error "$dev is not a block device"
	fi
}

check_char_dev() {
	local dev="$1"

	if [ ! -c "$dev" ]; then
		error "$dev is not a character device"
	fi
}

mtdsize() {
	local mtdname="$1"

	size=$(grep "\"$mtdname\"$" "$PROC_MTD" | cut -d " " -f 2)
	printf "%d" "0x$size"
}

check_kflavors() {
	local kvers="$1"
	local kflavor="$2"
	if [ "$kflavor" = "any" ]; then
		return 0
	fi
	# count flavor+ as valid
	kvers=${kvers%%+}
	if [ "${kvers}" != "${kvers%%$kflavor}" ]; then
		# kernel version ended with flavor
		return 0
	fi
	return 1
}

check_mtd_size() {
	local mtd_name="$1"
	local required_size="$2"
	local actual_size="$3"
	local what="$4"

	if [ $required_size -gt $actual_size ]; then
		case $what in
		initrd)
			(
				echo
				echo "The initial ramdisk is too large. This is often due to the unnecessary inclusion"
				echo "of all kernel modules in the image. To fix this set MODULES=dep in one or both"
				echo "/etc/initramfs-tools/conf.d/driver-policy (if it exists) and"
				echo "/etc/initramfs-tools/initramfs.conf and then run 'update-initramfs -u -k $kvers'"
				echo
			) >&2
		;;
		esac
		error "Not enough space for $what in MTD '$mtd_name' (need $required_size but is actually $actual_size)."
	fi
}

check_supported() {
	local machine="$1"
	local field
	local value

	echo "$MACHINE_DB" | {
		while read field value; do
			if [ "$field" = "Machine:" ] &&
				[ "$value" = "$machine" ]; then
				return 0
			fi
		done
		return 1
	}
}

get_root_uuid() {
	echo $(blkid -o value -s UUID $(mount | grep "on /${1%/} " | tail -n1 | cut -d' ' -f1))
}

get_cpuinfo_hardware() {
	grep "^Hardware" "$PROC_CPUINFO" | sed 's/Hardware\s*:\s*//'
}
get_dt_model() {
	cat "$PROC_DTMODEL"
}
get_machine() {
	if [ -n "$FK_MACHINE" ]; then
		[ "x$FK_MACHINE" = "xnone" ] && exit 0
		machine="$FK_MACHINE"
	elif [ -f "$FK_ETC_MACHINE" ]; then
		machine="$(cat "$FK_ETC_MACHINE")"
	elif [ -f "$PROC_DTMODEL" ]; then
		machine="$(get_dt_model)"
	else
		machine="$(get_cpuinfo_hardware)"
	fi
}

get_kfile_suffix() {
	local kfile="$1"
	local tail="${2:+-$2}"

	echo "$kfile" | sed -e "s/.*-\([^-]*$tail\)/\\1/"
}

# this is case-sensitive and doesn't support fields spanning multiple lines
get_machine_field() {
	local machine="$1"
	local field_name="$2"
	local state="machine"
	local field
	local value

	# State machine is:
	# machine: 	Looking for "Machine:"
	# gotmachine: 	Seen matching "Machine:", possibly consumning other
	#		alternative (non-matching) "Machine:"s waiting for
	#		non-"Machine:" field.
	# fields:	seen non-"Machine:" field.

	echo "$MACHINE_DB" | {
		while read field value; do
			if [ "$state" = "machine" ] &&
				[ "$field" = "Machine:" ] &&
				[ "$value" = "$machine" ]; then
				state="gotmachine"
			fi
			if [ "$state" = "fields" ] || [ "$state" = "gotmachine" ]; then
				case "$field" in
					"${field_name}:")
						echo "$value"
						return 0
					;;
					Machine:)
						if [ "$state" != "gotmachine" ]; then
							echo "DB syntax invalid, got $field $value, expected regular Field" >&2
							return 1
						fi
					;;
					"")
						state="machine"
					;;
					*)
						state="fields"
					;;
				esac
			fi
		done
		return 1
	}
}

get_mkimage_architecture() {
	kfile_suffix=$(get_kfile_suffix "$1" "")
	case "$kfile_suffix" in
		"arm64") echo "arm64";;
		*) echo "arm";;
	esac
}

get_dtb_name() {
	local field="$(get_machine_field "$machine" "DTB-Id")" || :
	case "$field" in
	!*)
		local dir
		local dtb_script
		dtb_script_name=${field#!}
		for dir in /etc/flash-kernel/dtb-probe \
		           /usr/share/flash-kernel/dtb-probe ; do
			if [ -e "$dir/$dtb_script_name" ]; then
				dtb_script="$dir/$dtb_script_name"
				break
			fi
		done

		if [ "x$dtb_script" = "x" ]; then
			error "dtb-probe $dtb_script_name not found"
		fi

		dtb_name=$($dtb_script $kvers)
		if [ $? -ne 0 ] || [ "x$dtb_name" = "x" ]; then
			error "dtb-probe $dtb_script failed"
		fi
		;;
	*)
		dtb_name="$field"
		;;
	esac
	if [ -n "$dtb_name" ] ; then
		echo "Using DTB: $dtb_name" >&2
	fi
	dtb_dir=$(dirname "$dtb_name")
	dtb_name=$(basename "$dtb_name")
}

machine_uses_flash() {
	local machine="$1"

	if ! check_supported "$machine"; then
		# assume devices not explicitly listed are using flash
		return 0
	fi

	if [ -n "$(get_machine_field "$machine" "Mtd-Kernel")" ] ||
		[ -n "$(get_machine_field "$machine" "Mtd-Initrd")" ]; then
		return 0
	fi
	return 1
}

# output ARM instructions to set machine number; argument is the decimal
# machine number as found in linux/arch/arm/tools/mach-types
set_machine_id() {
	local machine_id="$1"
	local high
	local low

	if [ -z "$machine_id" ]; then
		return
	fi

	high="$(printf "%02x" $(($machine_id / 256)))"
	low="$(printf "%02x" $(($machine_id % 256)))"

	devio "wl 0xe3a01c$high,4" "wl 0xe38110$low,4"
}

gen_kernel() {
	local input="$1"
	local output="$2"
	local machine_id="$3"

	{
		set_machine_id "$machine_id"
		cat "$input"
	} >"$output"
}

gen_preboot() {
	PRESTUBDIRS="/etc/flash-kernel/preboot.d /usr/share/flash-kernel/preboot.d"
	PRESTUBS="$(find $PRESTUBDIRS -type f -regex '.*/[0-9a-zA-Z_-]+' -printf '%f\n' | LC_ALL=C sort -u)"
	for file in $PRESTUBS; do
		for dir in $PRESTUBDIRS; do
			if [ -f $dir/$file ]; then
				cat $dir/$file
				break
			fi
		done
	done
}

gen_ubootenv() {
	ENVSTUBDIRS="/etc/flash-kernel/ubootenv.d /usr/share/flash-kernel/ubootenv.d"
	ENVSTUBS="$(find $ENVSTUBDIRS -type f -regex '.*/[0-9a-zA-Z_-]+' -printf '%f\n' | LC_ALL=C sort -u)"
	for file in $ENVSTUBS; do
		for dir in $ENVSTUBDIRS; do
			if [ -f $dir/$file ]; then
				cat $dir/$file
				break
			fi
		done
	done
}

append_dtb() {
	local kernel="$1"
	local dtb="$2"
	local output="$3"

	echo "flash-kernel: appending $dtb to kernel" >&2

	{
		cat "$kernel"
		cat "$dtb"
	} >"$output"
}

write_mtd() {
	local input_file="$1"
	local output_mtd="$2"
	local base_mtd=$(basename $output_mtd)
	local mtd_backup_dir=$(get_mtd_backup_dir)
	local tmpfile

	if [ "x$input_file" = "x-" ] ; then
		tmpfile=$(mktemp -t "$self.$base_mtd.XXXXXX") || error "Failed"
		cat > $tmpfile
		input_file="$tmpfile"
	fi

	# Keep a copy in a convenient place for backups etc
	if [ -n "${mtd_backup_dir}" ] ; then
		local backup="${mtd_backup_dir}/${base_mtd}"
		#echo "Saving new $output_mtd in ${backup}."
		mkdir -p "${mtd_backup_dir}"
		cp "$input_file" "${backup}"
	fi

	# Can't really flashcp to /dev/mtd<N> when testing
	if [ -z "${FK_TESTSUITE_RUNNING}" ]; then
		local flashtype=$(cat /sys/class/mtd/$base_mtd/type)
		case "$flashtype" in
		nand|mlc-nand)
			flash_erase "$output_mtd" 0 0
			nandwrite -p "$output_mtd" "$input_file"
			;;
		nor)
			flashcp "$input_file" "$output_mtd"
			;;
		*)
			error "unsupported flash type"
			;;
		esac
	else
		cp "$input_file" "$output_mtd"
	fi

	if [ "$tmpfile" ] ; then
		rm -f "$tmpfile"
	fi
}

flash_kernel() {
	local input_file="$1"
	local output_mtd="$2"
	local machine_id="$3"
	local use

	if [ -n "$kmtdsize" ]; then
		kreqsize=$(stat -c '%s' "$input_file")
		if [ -n "$machine_id" ]; then
			kreqsize=$(($kreqsize + 8))
		fi
	        check_mtd_size "$mtd_kernel" $kreqsize $kmtdsize kernel
		use=" (using $kreqsize/$kmtdsize bytes)"
	fi

	printf "Flashing kernel$use... " >&2
	gen_kernel "$input_file" "$tmpdir/flash_kernel.raw" "$machine_id" || error "failed."
	write_mtd "$tmpdir/flash_kernel.raw" "$output_mtd" || error "failed."
	echo "done." >&2
}

flash_initrd() {
	local input_file="$1"
	local output_mtd="$2"
	local pad="$3"
	local use

	if [ -n "$imtdsize" ]; then
		use=" (using $ireqsize/$imtdsize bytes)"
	fi

	printf "Flashing initramfs$use... " >&2
	{
		cat "$input_file"
		if [ "$pad" -gt 0 ]; then
			dd if=/dev/zero bs="$pad" count=1 2>/dev/null
		fi
	} | write_mtd "-" "$output_mtd" || error "failed."
	echo "done." >&2
}

compress_type() {
    local file="$1"
    magic="$(od -x -N2 $file | head -1 | cut -d' ' -f2)"
    case $magic in
	8b1f)
	    echo "gzip"
	    ;;
	*)
	    echo "none"
	    ;;
    esac
}

get_kernel_cmdline() {
        . ${FK_DEFAULTS}
	echo "$LINUX_KERNEL_CMDLINE"
}
get_kernel_cmdline_defaults() {
        . ${FK_DEFAULTS}
	echo "$LINUX_KERNEL_CMDLINE_DEFAULTS"
}

mkimage_kernel() {
	local kaddr="$1"
	local epoint="$2"
	local kdesc="$3"
	local kdata="$4"
	local uimage="$5"
	local comp

	comp="$(compress_type $kdata)"

	printf "Generating kernel u-boot image... " >&2
	mkimage -A "$mkarch" -O linux -T kernel -C $comp -a "$kaddr" -e "$epoint" \
		-n "$kdesc" -d "$kdata" "$uimage" >&2 1>/dev/null
	echo "done." >&2
}

mkimage_initrd() {
	local iaddr="$1"
	local idesc="$2"
	local idata="$3"
	local uinitrd="$4"

	printf "Generating initramfs u-boot image... " >&2
	mkimage -A "$mkarch" -O linux -T ramdisk -C none -a "$iaddr" -e "$iaddr" \
		-n "$idesc" -d "$idata" "$uinitrd" >&2 1>/dev/null
	echo "done." >&2
}

mkimage_script() {
	local saddr="$1"
	local sdesc="$2"
	local sdata="$3"
	local script="$4"

	local tdata="$tmpdir/$(basename $sdata).out"

	local ubootenv="$(mktemp --tmpdir=$tmpdir)"
	gen_ubootenv > $ubootenv
	local preboot="$(mktemp --tmpdir=$tmpdir)"
	gen_preboot > $preboot

	if [ "$(stat --printf='%s' $ubootenv)" -gt 0 ] && \
		! grep -q '@@UBOOT_ENV_EXTRA@@' "$sdata" ; then
			echo "WARNING: ubootenv.d snippet used, but $sdata has no @@UBOOT_ENV_EXTRA@@ marker. Snippet will be ignored." >&2
	fi

	printf "Generating boot script u-boot image... " >&2
	sed -e "s/@@KERNEL_VERSION@@/$kvers/g" \
            -e "s!@@LINUX_KERNEL_CMDLINE@@!$(get_kernel_cmdline)!g" \
            -e "s!@@LINUX_KERNEL_CMDLINE_DEFAULTS@@!$(get_kernel_cmdline_defaults)!g" \
            -e "/@@UBOOT_ENV_EXTRA@@/{
                  s/@@UBOOT_ENV_EXTRA@@//g
                  r $ubootenv
                }" \
            -e "/@@UBOOT_PREBOOT_EXTRA@@/{
                  s/@@UBOOT_PREBOOT_EXTRA@@//g
                  r $preboot
                }" < $sdata > $tdata
	mkimage -A "$mkarch" -O linux -T script -C none -a "$saddr" -e "$saddr" \
		-n "$sdesc" -d "$tdata" "$script" >&2 1>/dev/null
	echo "done." >&2
}

mkimage_multi() {
	local maddr="$1"
	local mdesc="$2"
	local kdata="$3"
	local idata="$4"
	local umulti="$5"

	printf "Generating u-boot image..." >&2
	mkimage -A "$mkarch" -O linux -T multi -C none -a "$maddr" -e "$maddr" \
		-n "$mdesc" -d "$kdata:$idata" "$umulti" >&2 1>/dev/null
	echo "done." >&2
}

# Return a nonempty string *unless* NO_CREATE_DOT_BAK_FILES is set.
get_dot_bak_preference() {
	. ${FK_DEFAULTS}
	case $(echo "$NO_CREATE_DOT_BAK_FILES" | tr '[:upper:]' '[:lower:]') in
	    true|yes|1) ;;
	    *) echo yes ;;
	esac
}

# Return a nonempty string *unless* MTD_BACKUP_DIR is "none".
get_mtd_backup_dir() {
	. ${FK_DEFAULTS}
	case "${MTD_BACKUP_DIR:=/var/backups/flash-kernel}" in
	    none) echo "";;
	    *) echo "${MTD_BACKUP_DIR}";;
	esac
}

backup_and_install() {
	local source="$1"
	local dest="$2"
	local do_dot_bak=$(get_dot_bak_preference)
	local mtd_backup_dir=$(get_mtd_backup_dir)
	if [ -e "$dest" ]; then
		if [ -n "$do_dot_bak" ]; then
			echo "Taking backup of $(basename "$dest")." >&2
			mv "$dest" "$dest.bak"
		else
			echo "Skipping backup of $(basename "$dest")." >&2
		fi
	fi
	# If we are installing to a filesystem which is not normally mounted
	# then take a second copy in /var/backups, where they can e.g. be
	# backed up.
	if [ -n "$boot_mnt_dir" ] && [ -n "$mtd_backup_dir" ] ; then
		local bak="$mtd_backup_dir/"$(basename "$dest")
		#echo "Saving $boot_device:"$(basename "$source")" in $bak"
		mkdir -p "$mtd_backup_dir"
		cp "$source" "$bak"
	fi
	echo "Installing new $(basename "$dest")." >&2
	mv "$source" "$dest"
}

# See http://www.nslu2-linux.org/wiki/Info/BootFlash -- the NSLU2 uses a
# 16 byte MTD header, the first four bytes (big endian) give the length of
# the remainder of the image, and the remaining bytes are zero.  Generate
# this header.
sercomm_header() {
	perl -e 'print pack("N4", shift)' "$1"
}

nslu2_swap() {
	if [ "$little_endian" -eq 1 ]; then
		devio "<<$1" "xp $,4"
	else
		cat "$1"
	fi
}

abootimg_get_image_size() {
	local abootimg="$1"

	echo "$abootimg" | sed -rn 's/^\* image size = ([0-9]+) bytes.*/\1/p'
}

dtb_append_required() {
	linux-version compare "$kvers" ge "$dtb_append_from"
}

android_flash() {
	local device="$1"

	if [ -z "$android_skip_initrd" ]; then
		printf "Flashing kernel and initramfs to $device... " >&2
		abootimg -u "$device" -k "$kfile" -r "$ifile" >/dev/null ||
			error "failed."
		echo "done." >&2
	else
		printf "Flashing the kernel (skipping initrd) to $device... " >&2
		abootimg -u "$device" -k "$kfile" >/dev/null || error "failed."
		echo "done." >&2
	fi
}

include_only_flavors() {
	# include_only_flavors(flav1, flav2, flav3)
	# filter lines of input in uname -r format (X.Y.Z-N-flavor)
	# and filter-out anything that is not in the listed input
	# if the only flavor given is "any", then assume everything is a match
	local cur_uname cur_flav allowed_flav
	while read cur_uname; do
		if [ "$*" = "any" ]; then
			echo "$cur_uname"
		else
			# could use cur_flav=$(get_kfile_suffix "$cur_uname")
			# but this is much faster.
			cur_flav=${cur_uname#*-*-}
			for allowed_flav in "$@"; do
				if [ "${cur_flav}" = "${allowed_flav}" ]; then
					echo "$cur_uname"
					break
				fi
			done
		fi
	done
}

find_dtb_file() {
	local dtb path
	case "$dtb_dir" in
	/*)
		dtb="$dtb_dir/$dtb_name"
		if [ ! -f "$dtb" ]; then
			error "Couldn't find $dtb"
		fi
		;;
	*)
		dtb=
		paths="/etc/flash-kernel/dtbs /usr/lib/linux-image-$kvers /lib/firmware/$kvers/device-tree/"
		for path in $paths; do
			dtb=$(find $path -name $dtb_name 2>/dev/null | head -n 1)
			[ -z "$dtb" ] || break
		done
		if [ ! -f "$dtb" ]; then
			error "Couldn't find DTB $dtb_name on the following paths: $paths"
		fi
		;;
	esac
	echo $dtb
}

unique_filenames() {
	awk '
	{
		file=$0;
		sub("^.*/", "", file);
		if (!(file in files)) files[file]=$0;
	}
	END {
		for (file in files) print files[file];
	}'
}

find_all_dtbs() {
	# NOTE: The following assumes that "find" processes and outputs
	# filenames in the same order as starting points ($paths) are specified
	paths="/etc/flash-kernel/dtbs /usr/lib/linux-image-$kvers /lib/firmware/$kvers/device-tree/"
	find $paths -name "*.dtb" 2>/dev/null | unique_filenames
}

find_all_overlays() {
	paths="/etc/flash-kernel/dtbs /usr/lib/linux-image-$kvers /lib/firmware/$kvers/device-tree/"
	find $paths -name "*.dtbo" 2>/dev/null | unique_filenames
}

handle_dtb() {
	if [ "x$dtb_name" = "x" ]; then
		return
	fi

	local dtb
	if [ "x$FK_KERNEL_HOOK_SCRIPT" = "xpostrm.d" ] ; then
		rm -f "/boot/dtbs/$kvers/$dtb_dir/$dtb_name"

		# This was the old name we installed under. We
		# currently include it as an alternative symlink.
		rm -f "/boot/dtb-$kvers"

		if [ -L /boot/dtb ] ; then
			# Remove if it points to the current kernel
			# version, both old (dtb-$kvers) and new
			# (dtbs/...) names
			case $(readlink /boot/dtb) in
				"dtb-$kvers") rm -f /boot/dtb ;;
				"dtbs/$kvers/$dtb_name") rm -f /boot/dtb ;;
			esac
		fi

		if [ -d /boot/dtbs/$kvers ] ; then
			rmdir --ignore-fail-on-non-empty /boot/dtbs/$kvers
		fi
		if [ -d /boot/dtbs ] ; then
			rmdir --ignore-fail-on-non-empty /boot/dtbs
		fi
	else
		local dtb=$(find_dtb_file)
		echo "Installing $dtb into /boot/dtbs/$kvers/$dtb_dir/$dtb_name" >&2
		mkdir -p "/boot/dtbs/$kvers/$dtb_dir"
		cp "$dtb" "/boot/dtbs/$kvers/$dtb_dir/$dtb_name.new"
		backup_and_install \
			"/boot/dtbs/$kvers/$dtb_dir/$dtb_name.new" \
			"/boot/dtbs/$kvers/$dtb_dir/$dtb_name"

		if [ "$dtb_dir" != "." ]; then
			ln -nfs "$dtb_dir/$dtb_name" "/boot/dtbs/$kvers/$dtb_name"
		fi

		# Historically we installed the dtb as
		# dtb-$kvers, keep it around as an alternative
		# for now. Useful for platforms which do not
		# set ${fdtfile}
		ln -nfs "dtbs/$kvers/$dtb_dir/$dtb_name" "/boot/dtb-$kvers"

		# This can be used along with the unversioned
		# vmlinuz+initrd.gz e.g. as a fallback option
		ln -nfs "dtbs/$kvers/$dtb_dir/$dtb_name" "/boot/dtb"
	fi
}

main() {
force="no"
if [ "x$1" = "x--force" ]; then
	force="yes"
	shift
fi
if [ "x$1" = "x--machine" ]; then
	machine="$2"
	shift 2
else
	get_machine
fi

if [ "x$1" = "x--supported" ]; then
	if check_supported "$machine"; then
		exit 0
	fi
	exit 1
fi

# $FK_KERNEL_HOOK_SCRIPT is set when main() is called from
# /etc/kernel/* to be able to differentiate between being called
# upon kernel installation or kernel removal

# kernel + initrd installation/upgrade mode, with optional version

kvers="$1"

get_dtb_name

# Install/remove any DTB from postinst, regardless of version
if [ -n "$kvers" ] ; then
	handle_dtb
fi

# if get_machine_field returns non-zero, then all flavors are allowed
if ! kflavors=$(get_machine_field "$machine" "Kernel-Flavors") ; then
	# Since no Kernel-Flavors were specified, allow any kernel.
	kflavors="any"
fi
latest_version=$(linux-version list | include_only_flavors $kflavors | linux-version sort | tail -1)

if [ -n "$kvers" ] && [ "$FK_KERNEL_HOOK_SCRIPT" = "postrm.d" ]; then
	echo "flash-kernel: Kernel ${kvers} has been removed." >&2
	if $(linux-version compare "$kvers" lt "$latest_version"); then
		echo "flash-kernel: A higher version (${latest_version}) is still installed, no reflashing required." >&2
		exit 0
	else
		if [ -n "${latest_version}" ]; then
			echo "flash-kernel: Flashing the remaining highest-versioned kernel (${latest_version})." >&2
		else
			echo "flash-kernel: WARNING: No other kernel packages found!" >&2
			echo "flash-kernel: The system might be unbootable." >&2
			echo "flash-kernel: Please install a kernel package before rebooting the system." >&2
			exit 0
		fi
	fi
fi

if [ "$kvers" != "$latest_version" ] && [ "x$force" = "xyes" ]; then
	echo "flash-kernel: forcing install of ${kvers} instead of ${latest_version}." >&2
	echo "flash-kernel: WARNING: Installing any new kernel package might override this." >&2
else
	if [ -n "$kvers" ] && [ "$kvers" != "$latest_version" ]; then
		echo "Ignoring old or unknown version $kvers (latest is $latest_version)" >&2
		exit 0
	fi
	kvers="$latest_version"
	# Make sure we install the DTB for $latest_version
	handle_dtb
fi

# accumulate multiple calls in a trigger to only run flash-kernel once; the
# trigger will just call flash-kernel again with FLASH_KERNEL_NOTRIGGER set to
# force a real run
if [ -z "$FLASH_KERNEL_NOTRIGGER" ] && [ -n "$DPKG_MAINTSCRIPT_PACKAGE" ] && dpkg-trigger --check-supported 2>/dev/null; then
	# flash-kernel trigger isn't disabled, and we're called from
	# some package maintainer script (e.g. some postinst calls
	# flash-kernel), and dpkg-trigger is installed and confirms
	# that the running dpkg support triggers: we can use the
	# flash-kernel trigger (asynchronously)
	if dpkg-trigger --no-await flash-kernel; then
		echo "flash-kernel: deferring update (trigger activated)" >&2
		exit 0
	fi
	# dpkg-trigger failed for some reason, proceed to a normal run
fi

kfile="/boot/vmlinuz-$kvers"
ifile="/boot/initrd.img-$kvers"
desc="kernel $kvers"
idesc="ramdisk $kvers"

if [ ! -e $kfile ] || [ ! -e $ifile ]; then
	# See debian bug #781742 and LP: #1861292
	echo "flash-kernel: $kfile or $ifile missing; expecting future run" >&2
	exit 0
fi
kfilesize=$(stat -c '%s' "$kfile")
ifilesize=$(stat -c '%s' "$ifile")

if [ -L "$kfile" ]; then
	kfile=$(readlink -e "$kfile")
fi

if ! check_supported "$machine"; then
	# do nothing if an unsupported platform is booted from EFI
	if [ -d /sys/firmware/efi ]; then
		echo "Unsupported platform on EFI system, doing nothing."
		exit 0
	fi
	error "Unsupported platform."
fi

kfile_suffix=""
for kflavor in $kflavors ; do
	if check_kflavors "$kvers" "$kflavor" ; then
		kfile_suffix="$kflavor"
		break
	fi
done

if [ -z "$kfile_suffix" ]; then
	echo "Kernel $kfile does not match any of the expected flavors ($kflavors), therefore not writing it to flash." >&2
	exit 0
fi

echo "flash-kernel: installing version $kvers" >&2

mkarch="$(get_mkimage_architecture $kvers)"
machine_id="$(get_machine_field "$machine" "Machine-Id")" || :
method="$(get_machine_field "$machine" "Method")" || method="generic"
mtd_kernel="$(get_machine_field "$machine" "Mtd-Kernel")" || :
mtd_initrd="$(get_machine_field "$machine" "Mtd-Initrd")" || :
dtb_append="$(get_machine_field "$machine" "DTB-Append")" || :
dtb_append_from="$(get_machine_field "$machine" "DTB-Append-From")" || :
ukaddr="$(get_machine_field "$machine" "U-Boot-Kernel-Address")" || :
ukepoint="$(get_machine_field "$machine" "U-Boot-Kernel-Entry-Point")" || :
uiaddr="$(get_machine_field "$machine" "U-Boot-Initrd-Address")" || :
umaddr="$(get_machine_field "$machine" "U-Boot-Multi-Address")" || :
usaddr="$(get_machine_field "$machine" "U-Boot-Script-Address")" || :
usname="$(get_machine_field "$machine" "U-Boot-Script-Name")" || :
boot_device="$(get_machine_field "$machine" "Boot-Device")" || :
boot_kernel_path="$(get_machine_field "$machine" "Boot-Kernel-Path")" || :
boot_initrd_path="$(get_machine_field "$machine" "Boot-Initrd-Path")" || :
boot_kernel_path_version="$(get_machine_field "$machine" "Boot-Kernel-Path-Version")" || :
boot_initrd_path_version="$(get_machine_field "$machine" "Boot-Initrd-Path-Version")" || :
boot_script_path="$(get_machine_field "$machine" "Boot-Script-Path")" || :
boot_dtb_path="$(get_machine_field "$machine" "Boot-DTB-Path")" || :
boot_dtb_path_version="$(get_machine_field "$machine" "Boot-DTB-Path-Version")" || :
boot_multi_path="$(get_machine_field "$machine" "Boot-Multi-Path")" || :
android_boot_device="$(get_machine_field "$machine" "Android-Boot-Device")" || :
android_skip_initrd="$(get_machine_field "$machine" "Android-Skip-Initrd")" || :

if [ -n "$dtb_append_from" ]; then
    if dtb_append_required; then
	dtb_append="yes"
    else
	dtb_append="no"
    fi
fi

if [ -n "$mtd_kernel" ] || [ -n "$mtd_initrd" ]; then
	if [ ! -e "$PROC_MTD" ]; then
		error "$PROC_MTD doesn't exist"
	fi
fi
if [ -n "$mtd_kernel" ]; then
	kmtd=$(mtdchar "$mtd_kernel")
	if [ -z "$kmtd" ]; then
		error "Cannot find mtd partition '$mtd_kernel'"
	fi
	check_char_dev "$kmtd"
	kmtdsize=$(mtdsize "$mtd_kernel")
	kreqsize=$kfilesize
	check_mtd_size "$mtd_kernel" $kreqsize $kmtdsize kernel
fi
if [ -n "$mtd_initrd" ]; then
	imtd=$(mtdchar "$mtd_initrd")
	if [ -z "$imtd" ]; then
		error "Cannot find mtd partition '$mtd_initrd'"
	fi
	check_char_dev "$imtd"
	imtdsize=$(mtdsize "$mtd_initrd")
	ireqsize=$ifilesize
	# encapsulating in an U-Boot image grows the size by 64 bytes
	if [ -n "$uiaddr" ]; then
		ireqsize=$(($ireqsize + 64))
	fi
	check_mtd_size "$mtd_initrd" $ireqsize $imtdsize initrd
fi

tmpdir=""
boot_mnt_dir=""
cleanups() {
	rm -rf "$tmpdir"
	if [ -d "$boot_mnt_dir" ]; then
		umount -l "$boot_mnt_dir"
		rmdir "$boot_mnt_dir"
	fi
}
trap cleanups EXIT HUP INT QUIT ILL KILL SEGV PIPE TERM
self="$(basename "$0")"
tmpdir="$(mktemp -dt "$self.XXXXXXXX")"


case "$method" in
	"android")
		if abootimg -i "$android_boot_device" > /dev/null 2>&1; then
			part="$android_boot_device"
		else
			largest_size="-1"
			for p in "$android_boot_device"*[0-9]; do
				if ! abootimg="$(LC_ALL=C abootimg -i "$p" 2>/dev/null)"; then
					continue
				fi
				image_size="$(abootimg_get_image_size "$abootimg")"
				if [ -n "$image_size" ] &&
					[ "$image_size" -gt "$largest_size" ]; then
					part="$p"
				fi
			done
		fi
		if [ -z "$part" ]; then
			error "Couldn't find Android boot partition on $android_boot_device"
		fi
		android_flash "$part"
	;;
	"generic")
		kernel="$kfile"
		initrd="$ifile"
		if [ "$dtb_append" = "yes" ]; then
			dtb=$(find_dtb_file)
			append_dtb "$kernel" "$dtb" "$tmpdir/kernel"
			kernel="$tmpdir/kernel"
		elif [ -n "$machine_id" ]; then
			gen_kernel "$kernel" "$tmpdir/kernel" "$machine_id"
			kernel="$tmpdir/kernel"
		fi
		if [ -n "$ukaddr" ]; then
			if [ -n "$ukepoint" ]; then
				mkimage_kernel "$ukaddr" "$ukepoint" "$desc" "$kernel" \
					"$tmpdir/uImage"
			else
				mkimage_kernel "$ukaddr" "$ukaddr" "$desc" "$kernel" \
					"$tmpdir/uImage"
			fi
			kernel="$tmpdir/uImage"
			rm -f "$tmpdir/kernel"
		fi
		if [ -n "$umaddr" ]; then
			mkimage_multi "$umaddr" "$desc" "$kernel" "$initrd" \
				"$tmpdir/uImage"
			rm -f "$tmpdir/kernel"
		fi
		if [ -n "$boot_device" ]; then
			check_block_dev "$boot_device"
			echo "Will use $boot_device as boot device." >&2
			# don't use $tmpdir/boot as to not nuke it accidentally
			# if umount fails
			boot_mnt_dir="$(mktemp -dt "$self.XXXXXXXX")"
			mount "$boot_device" "$boot_mnt_dir"
		fi
		if [ -n "$boot_kernel_path" ]; then
			boot_kernel_path="$boot_mnt_dir/$boot_kernel_path"
			if [ "$boot_kernel_path_version" = "yes" ]; then
			   boot_kernel_path="${boot_kernel_path}-${kvers}"
			fi
			# don't mv the original kernel
			if [ "$kernel" = "$kfile" ]; then
				cp "$kernel" "$tmpdir/kernel"
				kernel="$tmpdir/kernel"
			fi
			backup_and_install "$kernel" "$boot_kernel_path"
		elif [ -n "$kmtd" ]; then
			flash_kernel "$tmpdir/uImage" "$kmtd" ""
			rm -f "$tmpdir/uImage"
		fi
		if [ -n "$boot_multi_path" ]; then
			backup_and_install "$tmpdir/uImage" "$boot_multi_path"
		fi
		if [ -n "$uiaddr" ]; then
			mkimage_initrd "$uiaddr" "$idesc" "$initrd" \
				"$tmpdir/uInitrd"
			initrd="$tmpdir/uInitrd"
		fi
		if [ -n "$boot_initrd_path" ]; then
			boot_initrd_path="$boot_mnt_dir/$boot_initrd_path"
			if [ "$boot_initrd_path_version" = "yes" ]; then
			   boot_initrd_path="${boot_initrd_path}-${kvers}"
			fi
			# don't mv the original initrd
			if [ "$initrd" = "$ifile" ]; then
				cp "$initrd" "$tmpdir/initrd"
				initrd="$tmpdir/initrd"
			fi
			backup_and_install "$initrd" "$boot_initrd_path"
		elif [ -n "$imtd" ]; then
			ipad=0
			# padding isn't needed for U-Boot images
			if [ -z "$uiaddr" ]; then
				ipad=$(($imtdsize - $ireqsize))
			fi
			flash_initrd "$initrd" "$imtd" $ipad
			rm -f "$tmpdir/uInitrd"
		fi
		if [ -n "$boot_script_path" ]; then
			case $usname in
				bootscr*)
					boot_script_path="$boot_mnt_dir/$boot_script_path"
					boot_script="$tmpdir/bootscript"
					for script in $usname ; do
						echo "\n#\n# flash-kernel: $script\n#\n" >> "$boot_script"
						cat "$BOOTSCRIPTS_DIR/$script" >> "$boot_script"
					done
					mkimage_script "$usaddr" "boot script" "$boot_script" \
						"$tmpdir/boot.scr"
					boot_script="$tmpdir/boot.scr"
					backup_and_install "$boot_script" "$boot_script_path"
				;;
				uEnvtxt*)
					VOLID=${VOLID:-"$(get_root_uuid)"}
					boot_script_in="$BOOTSCRIPTS_DIR/$usname"
					boot_script="$boot_mnt_dir/$usname"
					cp $boot_script_in $boot_script

					boot_script_path="$boot_mnt_dir/uEnv.txt"
					boot_script_env="$boot_mnt_dir/preEnv.txt"
					env_script="$tmpdir/preEnv.txt"

					[ -e /etc/default/flash-kernel ] && . /etc/default/flash-kernel
					echo "bootargs=ro $(LINUX_KERNEL_CMDLINE) root=UUID=$VOLID" > $env_script

					backup_and_install "$env_script" "$boot_script_env"
					backup_and_install "$boot_script" "$boot_script_path"
				;;
			esac
		fi
		if [ -n "$boot_dtb_path" ] && [ "$dtb_append" != "no" ]; then
			boot_dtb_path="$boot_mnt_dir/$boot_dtb_path"
			if [ "$boot_dtb_path_version" = "yes" ]; then
				boot_dtb_path="${boot_dtb_path}-${kvers}"
			fi
			boot_dtb=$(find_dtb_file)
			dtb="$tmpdir/dtb"
			cp "$boot_dtb" "$dtb"
			backup_and_install "$dtb" "$boot_dtb_path"
		fi
	;;
	"pi")
		boot_dir="/boot/firmware"
		boot_kernel_path=${boot_kernel_path:-$boot_dir/vmlinuz}
		boot_initrd_path=${boot_initrd_path:-$boot_dir/initrd.img}
		boot_script_path=${boot_script_path:-$boot_dir/boot.scr}
		cp "$kfile" "$tmpdir/vmlinuz"
		backup_and_install "$tmpdir/vmlinuz" "$boot_kernel_path"
		cp "$ifile" "$tmpdir/initrd.img"
		backup_and_install "$tmpdir/initrd.img" "$boot_initrd_path"
		boot_script="$tmpdir/boot.scr"
		for script in $usname ; do
			echo "\n#\n# flash-kernel: $script\n#\n" >> "$boot_script"
			cat "$BOOTSCRIPTS_DIR/$script" >> "$boot_script"
		done
		mkimage_script "$usaddr" "boot script" "$boot_script" "$tmpdir/boot.scr"
		backup_and_install "$boot_script" "$boot_dir/boot.scr"
		find_all_dtbs | {
			while read dtb; do
				dtb_name=$(basename $dtb)
				cp "$dtb" "$tmpdir/$dtb_name"
				backup_and_install "$tmpdir/$dtb_name" "$boot_dir/$dtb_name"
			done
		}
		mkdir -p "$boot_dir/overlays"
		find_all_overlays | {
			while read overlay; do
				overlay_name=$(basename $overlay)
				cp "$overlay" "$tmpdir/$overlay_name"
				backup_and_install "$tmpdir/$overlay_name" "$boot_dir/overlays/$overlay_name"
			done
		}
	;;
	"symlink")
		rm -f /boot/initrd /boot/zImage
		ln -s "$(basename "$ifile")" /boot/initrd
		gen_kernel "$kfile" "/boot/zImage" "$machine_id"
	;;
	"multi")
		gen_kernel "$kfile" "$tmpdir/kernel" ""
		# Hack to work around a bug in some U-Boot versions:
		if [ $(($(stat -c '%s' "$tmpdir/kernel") % 4)) -eq 0 ]; then
			echo >> "$tmpdir/kernel"
		fi
		mkimage_multi "$umaddr" "$desc" "$tmpdir/kernel" "$ifile" \
			"$tmpdir/uImage"
		rm -f "$tmpdir/kernel"
		backup_and_install "$tmpdir/uImage" "$boot_multi_path"
	;;
	"redboot")
		flash_kernel "$kfile" "$kmtd" "$machine_id"
		pad=$(($imtdsize - $ifilesize))
		flash_initrd "$ifile" "$imtd" $pad
	;;
	"slug")
		case "$(dpkg --print-architecture)" in
			arm|armel)
				little_endian=1
			;;
			armeb)
				little_endian=0
			;;
		esac
		mtd_fis="FIS directory"
		fismtd=$(mtdblock "$mtd_fis")
		if [ -z "$fismtd" ]; then
			error "Cannot find mtd partition '$mtd_fis'"
		fi
		check_mtd_size "$mtd_kernel" $(($kfilesize + 16 + 16)) \
			$kmtdsize kernel
		check_mtd_size "$mtd_initrd" $(($ifilesize + 16)) \
			$imtdsize initrd
		# The following devio magic parses the FIS directory to
		# obtain the size, offset and name of each partition.  This
		# used used to obtain the offset of the Kernel partition.
		offset=$(echo "$(devio "<<$fismtd" '
			<= $ 0x20000 -
			L= 0x1000
			$( 1
				# 0xff byte in name[0] ends the partition table
				$? @ 255 =
				# output size base name
				<= f15+
				.= b 0xfffffff &
				<= f4+
				.= b
				pf "%lu %lu "
				<= f28-
				cp 16
				pn
				<= f240+
				L= L256-
			$) L255>')" |
			while read a b c; do
				if [ "$c" = "Kernel" ]; then
					echo $b
				fi
			done)
		# The Kernel partition, starting at $offset, is divided into
		# two areas at $boundary.  We therefore need to split the
		# kernel into two and write them to flash with two Sercomm
		# headers.
		boundary=1441792 # 0x00160000
		ksize1=$(($boundary - $offset - 16))
		printf "Flashing kernel: " >&2
		{
			sercomm_header $(($kfilesize + 16))
			dd if="$kfile" of="$tmpdir/kpart1" bs=$ksize1 \
				count=1 2>/dev/null
			nslu2_swap "$tmpdir/kpart1"
			rm -f "$tmpdir/kpart1"
			sercomm_header 131072
			dd if="$kfile" of="$tmpdir/kpart2" ibs=$ksize1 \
				skip=1 2>/dev/null
			nslu2_swap "$tmpdir/kpart2"
			rm -f "$tmpdir/kpart2"
		} > "$kmtd" || error "failed."
		echo "done." >&2
		printf "Flashing initramfs: " >&2
		dd if="$ifile" of="$tmpdir/initrd" ibs=$(($imtdsize - 16)) \
			conv=sync 2>/dev/null
		{
			sercomm_header $ifilesize
			nslu2_swap "$tmpdir/initrd"
			rm -f "$tmpdir/initrd"
		} > "$imtd" || error "failed."
		echo "done." >&2
	;;
esac
}

# vim:noexpandtab shiftwidth=8 syntax=sh
