# Copyright (C) 2006  Joey Hess  <joeyh@debian.org>
# Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011  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-2014  Ian Campbell <ijc@hellion.org.uk>

# 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:-$FK_DIR}/bootscript"
FK_ETC_MACHINE="${FK_ETC_MACHINE:-/etc/flash-kernel/machine}"
PROC_CPUINFO="${FK_PROC_CPUINFO:-/proc/cpuinfo}"
PROC_DTMODEL="${FK_PROC_DRMODEL:-/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 kfile_suffix="$1"
	shift

	if [ -z "$kfile_suffix" ]; then
		return 0
	fi
	for kflavor; do
		if [ "$kfile_suffix" = "$kflavor" ] || [ "$kfile_suffix" = "$kflavor+" ]; then
			return 0
		fi
	done
	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_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
	}
}

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

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

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

	if [ "x$input_file" = "x-" ] ; then
		tmpfile=$(mktemp -t "$self.$base_mtd.XXXXXX") || error "Failed"
		cat > $tmpfile
		input_file="$tmpfile"
	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=" ($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=" ($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
}

get_kernel_cmdline() {
        . /etc/default/flash-kernel
	echo "$LINUX_KERNEL_CMDLINE"
}

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

	printf "Generating kernel u-boot image... " >&2
	mkimage -A arm -O linux -T kernel -C none -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 arm -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)"

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

	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 "/@@UBOOT_ENV_EXTRA@@/{
                  s/@@UBOOT_ENV_EXTRA@@//g
                  r $ubootenv
                }" < $sdata > $tdata
	mkimage -A arm -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 arm -O linux -T multi -C none -a "$maddr" -e "$maddr" \
		-n "$mdesc" -d "$kdata:$idata" "$umulti" >&2 1>/dev/null
	echo "done." >&2
}

backup_and_install() {
	local source="$1"
	local dest="$2"

	if [ -e "$dest" ]; then
		echo "Taking backup of $(basename "$dest")." >&2
		mv "$dest" "$dest.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" ]; then
		devio "<<$1" "xp $,4"
	else
		cat "$1"
	fi
}

# XXX needs testsuite coverage
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"
}

# XXX needs testsuite coverage
android_flash() {
	local device="$1"

	printf "Flashing kernel and initramfs to $device... " >&2
	abootimg -u "$device" -k "$kfile" -r "$ifile" >/dev/null ||
		error "failed."
	echo "done." >&2
}

handle_dtb() {
	local dtb_id="$(get_machine_field "$machine" "DTB-Id")" || :
	if [ "x$dtb_id" = "x" ]; then
		return
	fi

	local dtb="/usr/lib/linux-image-$kvers/$dtb_id"
	if [ "x$FK_KERNEL_HOOK_SCRIPT" = "xpostrm.d" ] ; then
		rm -f "/boot/dtb-$kvers"
	else
		if [ -e $dtb ]; then
			echo "Installing $dtb_id into /boot/dtb-$kvers" >&2
			cp "$dtb" "/boot/dtb-$kvers.new"
			backup_and_install "/boot/dtb-$kvers.new" "/boot/dtb-$kvers"
			ln -nfs "dtb-$kvers" "/boot/dtb"
		else
			echo "$dtb not found" >&2
		fi
	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"

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

latest_version=$(linux-version list | linux-version sort | tail -1)
if [ -n "$kvers" ] && [ "x$force" != "xyes" ] && [ "$kvers" != "$latest_version" ] && [ "$FK_KERNEL_HOOK_SCRIPT" = "postinst.d" ]; then
	echo "Ignoring old or unknown version $kvers (latest is $latest_version)" >&2
	exit 0
fi

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
	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
	error "Can't find $kfile or $ifile"
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
	error "Unsupported platform."
fi

if kflavors="$(get_machine_field "$machine" "Kernel-Flavors")"; then
	kfile_suffix=""
	while [ "$kfile_suffix" != "$kfile" ] ; do
		kfile_suffix=$(get_kfile_suffix "$kfile" "$kfile_suffix")

		if check_kflavors "$kfile_suffix" $kflavors; then
			break;
		fi
	done
fi

if [ "$kfile_suffix" = "$kfile" ]; 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

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_name="$(get_machine_field "$machine" "DTB-Id")" || :
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_script_path="$(get_machine_field "$machine" "Boot-Script-Path")" || :
boot_dtb_path="$(get_machine_field "$machine" "Boot-DTB-Path")" || :
boot_multi_path="$(get_machine_field "$machine" "Boot-Multi-Path")" || :
android_boot_device="$(get_machine_field "$machine" "Android-Boot-Device")" || :

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 "$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")
		part=""
		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
		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="/usr/lib/linux-image-$kvers/$dtb_name"
			if [ ! -f "$dtb" ]; then
				error "Couldn't find $dtb"
			fi
			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"
			# don't mv the original kernel
			if [ "$kernel" != "$kfile" ]; then
				backup_and_install "$kernel" \
					"$boot_kernel_path"
			else
				# TODO add support for kernel symlink
				:
			fi
		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"
			# don't mv the original initrd
			if [ "$initrd" != "$ifile" ]; then
				backup_and_install "$initrd" \
					"$boot_initrd_path"
			else
				# TODO add support for initrd symlink
				:
			fi
		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
			boot_script_path="$boot_mnt_dir/$boot_script_path"
			boot_script="$BOOTSCRIPTS_DIR/$usname"
			mkimage_script "$usaddr" "boot script" "$boot_script" \
				"$tmpdir/boot.scr"
			boot_script="$tmpdir/boot.scr"
			backup_and_install "$boot_script" "$boot_script_path"
		fi
		if [ -n "$boot_dtb_path" ]; then
			boot_dtb_path="$boot_mnt_dir/$boot_dtb_path"
			boot_dtb="/usr/lib/linux-image-$kvers/$dtb_name"
			if [ ! -f "$boot_dtb" ]; then
				error "Couldn't find $boot_dtb"
			fi
			dtb="$tmpdir/dtb"
			cp "$boot_dtb" "$dtb"
			backup_and_install "$dtb" "$boot_dtb_path"
		fi
	;;
	"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
