#
# Copyright (C) 2014 Dream Property GmbH
#

MMC_DEVICE=/dev/mmcblk0
ROOT_PARTITION=/dev/mmcblk0p1
ROOT_MOUNTPOINT=/mnt
ROOT_NAME=dreambox-rootfs
DATA_PARTITION=/dev/mmcblk0p2
DATA_MOUNTPOINT=/data
DATA_NAME=dreambox-data
RESCUE_PARTITION=/dev/mtdblock2
RECOVERY_CACHE=${DATA_MOUNTPOINT}/.recovery
FILESYSTEM=ext4

MOUNTPOINTS=
WORKSPACE=

set -e

exec 3>/dev/null
exec 4>&2
exec 5>&1

is_beneath_directory()
{
	local file=`xrealpath ${1}`
	local dir=`xrealpath ${2}`

	case "$file" in
		$dir/*)
			true
			;;
		*)
			false
			;;
	esac
}

is_blockdev()
{
	[ -b "${1}" ]
}

is_directory()
{
	[ -d "${1}" ]
}

is_file()
{
	[ -f "${1}" ]
}

is_readable()
{
	[ -r "${1}" ]
}

is_readable_blockdev()
{
	is_blockdev "${1}" && is_readable "${1}"
}

is_readable_file()
{
	is_file "${1}" && is_readable "${1}"
}

is_writable()
{
	[ -w "${1}" ]
}

is_writable_blockdev()
{
	is_blockdev "${1}" && is_writable "${1}"
}

is_writable_directory()
{
	is_directory "${1}" && is_writable "${1}"
}

is_empty()
{
	[ -z "${1}" ]
}

is_file_size_le()
{
	[ "`stat -c '%s' ${1}`" -le "${2}" ]
}

is_file_size_le_blockdev()
{
	is_file_size_le "${1}" "$((`sfdisk -s ${2}` * 1024))"
}

is_initrd()
{
	is_file "/etc/initrd-release"
}

is_nfsroot()
{
	is_readable_file /proc/cmdline && grep -q -w root=/dev/nfs /proc/cmdline
}

is_mountpoint()
{
	is_directory "${1}" && mountpoint -q `realpath "${1}"`
}

is_mounted()
{
	is_mountpoint "${2}" && [ `mountpoint -x "${1}"` = `mountpoint -d "${2}"` ]
}

cleanup()
{
	IFS=:
	for dir in ${MOUNTPOINTS}; do
		unmount "${dir}"
	done
	unset IFS

	is_empty "${WORKSPACE}" || rm -rf "${WORKSPACE}"
}

abort()
{
	echo "Fatal: $@"
	exit 1
}

warn()
{
	echo "Warning: $@" >&5
}

info()
{
	echo "[*] $@" >&5
}

create_directory()
{
	if ! is_directory "${1}"; then
		info "Creating directory '${1}'"
		mkdir -p "${1}" >&3 2>&4
	fi
}

create_filesystem()
{
	info "Creating ${FILESYSTEM} filesystem '${1}' on ${2}"
	xtrap mkfs.${FILESYSTEM} -L "${1}" "${2}" >&3 2>&4
}

create_partition_table()
{
	info "Creating partition table"
	xtrap parted --script --align=optimal "${1}" -- \
		mklabel gpt \
		mkpart primary ext2 72MiB 1GiB \
		mkpart primary ext2 1GiB 100% >&3 2>&4
}

create_tarball()
{
	local opt

	case "${2}" in
		*.tar)
			opt=""
			;;
		*.gz|*.tgz|*.taz)
			opt="-z"
			;;
		*.Z|*.taZ)
			opt="-Z"
			;;
		*.bz2|*.tbz|*.tbz2|*.tz2)
			opt="-j"
			;;
		*.lz)
			opt="--lzip"
			;;
		*.lzma|*.tlz)
			opt="--lzma"
			;;
		*.lzo)
			opt="--lzop"
			;;
		*.xz|*.txz)
			opt="-J"
			;;
		*)
			abort "Invalid file type: ${2}"
			;;
	esac

	create_directory "`dirname ${2}`"
	info "Creating ${2} from ${1}"
	(cd "${1}" && find . -type s) >sockets
	xtar -C "${1}" ${opt} --numeric-owner --exclude-from=sockets -cf "${2}.tmp" .
	mv "${2}.tmp" "${2}"
}

create_workspace()
{
	umask 077

	WORKSPACE=`mktemp -d` || abort 'Failed to create working directory'
	trap cleanup EXIT INT
	cd "${WORKSPACE}" || abort 'Failed to change working directory'
}

extract_tarball()
{
	info "Extracting '${1}' to '${2}'"
	xtar -xf "${1}" -C "${2}"
}

fetch()
{
	info "Downloading '${1}/${2}'"
	wget -q "${1}/${2}" -O "${2}" >&3 2>&4
}

verify()
{
	info "Verifying signature of '${1}'"
	gpgv -q --ignore-time-conflict "${1}.sig" "${1}" >&3 2>&4
}

files_equal()
{
	cmp -s "${1}" "${2}"
}

fetch_signed()
{
	fetch "${1}" "${2}.sig" || true

	if is_file "${RECOVERY_CACHE}/${2}" && is_file "${RECOVERY_CACHE}/${2}.sig"; then
		if ! is_file "${2}.sig" || files_equal "${2}.sig" "${RECOVERY_CACHE}/${2}.sig"; then
			info "Copying '${2}' from local storage"
			xcp "${RECOVERY_CACHE}/${2}.sig" "${2}.sig"
			xcp "${RECOVERY_CACHE}/${2}" "${2}"
		fi
	fi

	is_file "${2}.sig" || abort "Failed to obtain signature '${2}.sig'"
	is_file "${2}" && verify "${2}" || {
		fetch "${1}" "${2}" || abort "Failed to download '${2}'"
		verify "${2}" || abort 'Failed to verify signature'
	}
}

safe_mount()
{
	local device="${1}"
	local dir="${2}"
	shift 2

	if ! is_mounted "${device}" "${dir}"; then
		create_directory "${dir}"
		info "Mounting '${device}' to '${dir}'"
		mount $@ "${device}" "${dir}" && MOUNTPOINTS="${MOUNTPOINTS}:${dir}"
	fi
}

unmount()
{
	if is_mountpoint "${1}"; then
		info "Unmounting '${1}'"
		umount "${1}" || mount -o remount,ro "${1}"
	fi
}

mount_cache()
{
	if is_blockdev "${DATA_PARTITION}"; then
		safe_mount "${DATA_PARTITION}" "${DATA_MOUNTPOINT}" -o ro || warn "Failed to mount data filesystem"
	fi
}

cache_changed()
{
	is_mountpoint "${DATA_MOUNTPOINT}" && ! cmp -s "${RECOVERY_CACHE}/${1}.sig" "${1}.sig"
}

remount_cache_rw()
{
	is_mountpoint "${DATA_MOUNTPOINT}" && info "Remounting '${DATA_MOUNTPOINT}' (rw)" && \
		mount -o remount,rw "${DATA_MOUNTPOINT}" && is_writable "${DATA_MOUNTPOINT}"
}

update_cache()
{
	if cache_changed "${1}" && remount_cache_rw; then
		create_directory "${RECOVERY_CACHE}"
		if is_directory "${RECOVERY_CACHE}"; then
			info "Updating recovery cache"
			xcp "${1}" "${RECOVERY_CACHE}/${1}" || true
			xcp "${1}.sig" "${RECOVERY_CACHE}/${1}.sig" || true
		fi
	fi
}

unmount_cache()
{
	unmount "${DATA_MOUNTPOINT}"
}

run()
{
	local cmd="${1}"
	shift

	info "Running '${cmd}'"
	is_empty "$@" || info "Options '$@'"
	chmod 755 "${cmd}" || abort "Failed to set execute permissions"
	${cmd} $@ || abort "Failed to execute '${cmd}'"
}

run_postinsts()
{
	local virtfs="dev proc run sys tmp"
	local mountpoint=$1
	shift

	if [ -d "${mountpoint}/var/lib/dpkg/info" ]; then
		pkgtype="dpkg"
	elif [ -d "${mountpoint}/var/lib/opkg/info" ]; then
		pkgtype="opkg"
	else
		warn "Unknown package manager, can't run postinst scripts"
		return
	fi

	for fs in ${virtfs}; do
		safe_mount /${fs} ${mountpoint}/${fs} -o bind || abort "Failed to mount ${mountpoint}/${fs}"
	done
	for package in $@; do
		xchroot ${mountpoint} /var/lib/${pkgtype}/info/${package}.postinst || abort "Failed to run ${package}.postinst"
	done
	for fs in ${virtfs}; do
		unmount ${mountpoint}/${fs} || warn "Failed to unmount ${mountpoint}/${fs}"
	done
}

assert_rescue_mode()
{
	is_initrd || is_nfsroot || abort "This script may only run in rescue mode!"
}

write_blkdev()
{
	info "Writing ${1} to ${2}"
	xdd if=${1} of=${2} bs=64K
}

write_lba()
{
	info "Writing ${1} to ${2} (LBA: ${3})"
	xdd if="${1}" of="${2}" bs=512 seek="${3}"
}

select_boot_source()
{
	info "Enabling boot source ${2}"
	printf "opt${2}\0" | xdd of=${1} bs=512 seek=64
}

usage()
{
	echo "Usage: ${0} [-hqtv]"
	exit ${1}
}

std_opt()
{
	case "${1}" in
		q)
			exec 3>/dev/null
			exec 4>/dev/null
			exec 5>/dev/null
			;;
		t)
			set -x
			;;
		v)
			exec 3>&1
			exec 4>&2
			exec 5>&1
			;;
		h|'?')
			if [ "${1}" = "h" ]; then
				usage 0
			else
				usage 1
			fi
			;;
	esac
}

xcp()
{
	xtrap cp -a $@
}

xchroot()
{
	xtrap chroot $@
}

xdd()
{
	xtrap dd conv=fsync $@
}

xgetopts()
{
	local opt

	while getopts hqtv opt; do
		std_opt "${opt}"
	done
}

xrealpath()
{
	local opt

	if realpath -v >/dev/null 2>&1; then
		opt="-s"
	else
		opt="-m"
	fi

	realpath ${opt} "${1}"
}

xtar()
{
	local opt

	if tar --usage >/dev/null 2>&1; then
		opt="--warning=no-timestamp"
	else
		opt=""
	fi

	xtrap tar ${opt} $@
}

xtrap()
{
	(trap '' HUP INT TERM; exec $@) >&3 2>&4
}
