#!/bin/sh

. /lib/functions.sh
. /lib/functions/system.sh
. /usr/share/libubox/jshn.sh

# initialize defaults
export MTD_ARGS=""
export MTD_CONFIG_ARGS=""
export VERBOSE=1
export SAVE_CONFIG=1
export SAVE_OVERLAY=0
export SAVE_OVERLAY_PATH=
export SAVE_PARTITIONS=1
export SAVE_INSTALLED_PKGS=0
export SKIP_UNCHANGED=0
export CONF_IMAGE=
export CONF_BACKUP_LIST=0
export CONF_BACKUP_VALIDATE=0
export CONF_BACKUP=
export CONF_BACKUP_API=0
export CONF_BACKUP_PKG_APPLY=0
export CONF_RESTORE=
export CONF_PASSWORD=
export CONF_SYSUPGRADE=
export NEED_IMAGE=
export HELP=0
export FORCE=0
export TEST=0

# parse options
while [ -n "$1" ]; do
	case "$1" in
		-v) export VERBOSE="$(($VERBOSE + 1))";;
		-q) export VERBOSE="$(($VERBOSE - 1))";;
		-n) export SAVE_CONFIG=0;;
		-c) export SAVE_OVERLAY=1 SAVE_OVERLAY_PATH=/etc;;
		-o) export SAVE_OVERLAY=1 SAVE_OVERLAY_PATH=/;;
		-p) export SAVE_PARTITIONS=0;;
		-k) export SAVE_INSTALLED_PKGS=1;;
		-u) export SKIP_UNCHANGED=1;;
		-a|--api-backup) export CONF_BACKUP_API=1;;
		-b|--create-backup) export CONF_BACKUP="$2" NEED_IMAGE=1; shift;;
		-r|--restore-backup) export CONF_RESTORE="$2" NEED_IMAGE=1; shift;;
		--password) export CONF_PASSWORD="$2"; shift;;
		-l|--list-backup) export CONF_BACKUP_LIST=1;;
		-V|--validate) export CONF_BACKUP_VALIDATE=1;;
		-f) export CONF_IMAGE="$2"; shift;;
		-F|--force) export FORCE=1;;
		-T|--test) export TEST=1;;
		-h|--help) export HELP=1; break;;
		-*)
			echo "Invalid option: $1" >&2
			exit 1
		;;
		*) break;;
	esac
	shift;
done

export CONF_TAR=/tmp/sysupgrade.tgz

IMAGE="$1"

[ -z "$IMAGE" -a -z "$NEED_IMAGE" -a $CONF_BACKUP_LIST -eq 0 -o $HELP -gt 0 ] && {
	cat <<EOF
Usage: $0 [<upgrade-option>...] <image file or URL>
       $0 [-q] [-i] [-c] [-u] [-o] [-k] <backup-command> <file>

upgrade-option:
	-f <config>  restore configuration from .tar.gz (file or url)
	-c           attempt to preserve all changed files in /etc/
	-o           attempt to preserve all changed files in /, except those
	             from packages but including changed confs.
	-u           skip from backup files that are equal to those in /rom
	-n           do not save configuration over reflash
	-p           do not attempt to restore the partition table after flash.
	-k           include in backup a list of current installed packages at
	             /etc/backup/installed_packages.txt
	-T | --test
	             Verify image and config .tar.gz but do not actually flash.
	-F | --force
	             Flash image/restore backup even if image checks fail, this is dangerous!
	-q           less verbose
	-v           more verbose
	-h | --help  display this help

backup-command:
	-b | --create-backup <file>
	             create .tar.gz of files specified in sysupgrade.conf
	             then exit. Does not flash an image. If file is '-',
	             i.e. stdout, verbosity is set to 0 (i.e. quiet). If --password flag is used,
		     file should be provided as <filename>.tar.zip
	-a | --api-backup
	             use API backup logic
	-r | --restore-backup <file>
	             restore a .tar.gz created with sysupgrade -b
	             then exit. Does not flash an image. If file is '-',
	             the archive is read from stdin. If --password flag is used,
		     file should be provided as <filename>.tar.zip or <filename>.tar.7z
	--password <password>
		     provide password that encrypts backup file with AES 256
		     encryption and archives it as .tar.zip
	-l | --list-backup
	             list the files that would be backed up when calling
	             sysupgrade -b. Does not create a backup file.
	-V | --validate
				 validate backup file set with -r option and exit.

EOF
	exit 1
}

[ -n "$IMAGE" -a -n "$NEED_IMAGE" ] && {
	cat <<-EOF
		-b|--create-backup and -r|--restore-backup do not perform a firmware upgrade.
		Do not specify both -b|-r and a firmware image.
	EOF
	exit 1
}

# prevent messages from clobbering the tarball when using stdout
[ "$CONF_BACKUP" = "-" ] && export VERBOSE=0

include /lib/upgrade

clean_tmp_dir() {
	for file in /tmp/*.bin; do
		if [ "$file" != "$IMAGE" ]; then
			rm -f "$file"
		fi
	done

	rm -f /tmp/sysupgrade.meta
	rm -f /tmp/sysupgrade.ucert
	rm -f /tmp/fw.*
}

clean_tpm() {
	tpm2_clear -c p
}

exec_internal_call() {
	local env="$1"

	local ret="$(ubus -t 900 call system backup "{ \
		\"env\": [ \
			\"VERBOSE=$VERBOSE\", \
			\"SAVE_OVERLAY=$SAVE_OVERLAY\", \
			\"SAVE_OVERLAY_PATH=$SAVE_OVERLAY_PATH\", \
			\"SAVE_INSTALLED_PKGS=$SAVE_INSTALLED_PKGS\", \
			\"SKIP_UNCHANGED=$SKIP_UNCHANGED\", \
			\"CONF_BACKUP_API=$CONF_BACKUP_API\", \
			\"CONF_BACKUP_LIST=$CONF_BACKUP_LIST\", \
			\"CONF_BACKUP_VALIDATE=$CONF_BACKUP_VALIDATE\", \
			\"CONF_BACKUP=$CONF_BACKUP\", \
			\"CONF_BACKUP_PKG_APPLY=$CONF_BACKUP_PKG_APPLY\", \
			\"CONF_RESTORE=$CONF_RESTORE\", \
			\"CONF_PASSWORD=$CONF_PASSWORD\", \
			\"CONF_TAR=$CONF_TAR\", \
			\"FORCE=$FORCE\", \
			\"USER_ID=$(id -u)\", \
			\"GROUP_ID=$(id -g)\", \
			\"$env\" \
		], \
	}")"

	jsonfilter -s "$ret" -e '@.output'
	return $(jsonfilter -s "$ret" -e '@.exit_code')
}

if [ $CONF_BACKUP_LIST -eq 1 ]; then
	exec_internal_call
	exit $?
fi

if [ $CONF_BACKUP_VALIDATE -eq 1 ]; then
	exec_internal_call
	exit $?
fi

if [ -n "$CONF_BACKUP" ]; then
	exec_internal_call
	exit $?
fi

if [ -n "$CONF_RESTORE" ]; then
	exec_internal_call
	exit $?
fi

type platform_check_image >/dev/null 2>/dev/null || {
	echo "Firmware upgrade is not implemented for this platform." >&2
	exit 1
}

case "$IMAGE" in
	http://*|\
	https://*)
		wget -O/tmp/sysupgrade.img "$IMAGE" || exit 1
		IMAGE=/tmp/sysupgrade.img
		;;
esac

IMAGE="$(readlink -f "$IMAGE")"

case "$IMAGE" in
	'')
		echo "Image file not found." >&2
		exit 1
		;;
	/tmp/*)	;;
	/log/fw/*)
		v "Image in /log/fw, moving..."
		mv -f "$IMAGE" /tmp/sysupgrade.img
		IMAGE=/tmp/sysupgrade.img
		;;
	*)
		v "Image not in /tmp, copying..."
		cp -f "$IMAGE" /tmp/sysupgrade.img
		IMAGE=/tmp/sysupgrade.img
		;;
esac

json_load "$(/usr/libexec/validate_firmware_image "$IMAGE")" || {
	echo "Failed to check image"
	clean_tmp_dir
	exit 1
}

json_get_var forceable "forceable"
json_get_var fw_version "fw_version"
json_get_var hw_support "hw_support"
[ "$hw_support" -eq 0 ] && {
	if [ "$FORCE" -eq 1 ] && [ "$forceable" -eq 1 ]; then
		echo "This firmware is unsupported by hardware but --force given - will update anyway!" >&2
	else
		echo "This firmware is unsupported by hardware" >&2
		echo "Image check failed." >&2
		clean_tmp_dir
		exit 2
	fi
}

json_get_var valid "valid"
[ "$valid" -eq 0 ] && {
	if [ "$FORCE" -eq 1 ] && [ "$forceable" -eq 1 ]; then
		echo "Image check failed but --force given - will update anyway!" >&2
	else
		echo "Image check failed." >&2
		clean_tmp_dir
		exit 1
	fi
}

json_get_var is_downgrade "is_downgrade"
[ "$is_downgrade" -eq 1 ] && {
	v "Detected firmware downgrade"
}

json_get_var allow_backup "allow_backup"
[ "$allow_backup" -eq 0 ] && {
	v "Cannot restore config due to firmware incompatibility"
	export SAVE_CONFIG=0
}

if [ -n "$CONF_IMAGE" ]; then
	case "$(get_magic_word $CONF_IMAGE cat)" in
		# .gz files
		1f8b) ;;
		*)
			echo "Invalid config file. Please use only .tar.gz files" >&2
			clean_tmp_dir
			exit 1
		;;
	esac
	get_image "$CONF_IMAGE" "cat" > "$CONF_TAR"
	export SAVE_CONFIG=1
elif [ $SAVE_CONFIG -gt 0 ]; then
	[ $TEST -eq 1 ] || exec_internal_call "CONF_SYSUPGRADE=1"
	export SAVE_CONFIG=1
else
	[ $TEST -eq 1 ] || rm -f "$CONF_TAR"
	export SAVE_CONFIG=0
fi

if [ $TEST -eq 1 ]; then
	clean_tmp_dir
	exit 0
fi

[ -e /dev/tpm0 ] && [ $SAVE_CONFIG -eq 0 ] && {
	clean_tpm
}

run_hooks "" $fwtool_pre_upgrade

# If ledman daemon is dead, this should not have any effect
/usr/bin/ledman --clean >/dev/null 2>&1 &
# Some device modems needs to be shutdown for proper init during boot
[ -e /sbin/mctl ] && {
	modem_name=$(jsonfilter -i "/etc/board.json" -e '@.modems[0].desc')
	case "$modem_name" in
  		*RG255*) /sbin/mctl -w -a && /sbin/mctl -s -a;;
		*EG060W*) /sbin/mctl -w -a && /sbin/mctl -s -a;;
	esac
}

install_bin /sbin/upgraded
v "Commencing upgrade. Closing all shell sessions."

COMMAND='/lib/upgrade/do_stage2'

if [ -n "$FAILSAFE" ]; then
	printf '%s\x00%s\x00%s' "$RAM_ROOT" "$IMAGE" "$COMMAND" >/tmp/sysupgrade
	lock -u /tmp/.failsafe
else
	current_fw_version="$(cat /etc/version)"
	log_msg="Firmware successfully upgraded"
	[ -n "$fw_version" ] && [ -n "$current_fw_version" ] && \
		log_msg="Firmware $current_fw_version successfully upgraded to $fw_version"

	ubus call log write_ext "{
		\"event\": \"$log_msg\",
		\"sender\": \"Firmware\",
		\"table\": 1,
		\"write_db\": 1,
	}"

	json_init
	json_add_string prefix "$RAM_ROOT"
	json_add_string path "$IMAGE"
	[ $FORCE -eq 1 ] && json_add_boolean force 1
	[ $SAVE_CONFIG -eq 1 ] && json_add_string backup "$CONF_TAR"
	json_add_string command "$COMMAND"
	json_add_object options
	json_add_int save_partitions "$SAVE_PARTITIONS"
	json_close_object

	ubus call system sysupgrade "$(json_dump)"
fi
