UPGRADE_FW_MAJOR="2020-03-29-0-6ec1a631"
UPGRADE_FW_VERSION="2020-03-29-1-6b4a0f46"
UPGRADE_FW_REQUIRE="2018-10-24-0-9e5687a2"

CONF_DIR_PATH="/tmp/sysupgrade_cfg"
CONF_TGZ_PATH="/tmp/sysupgrade.tgz"

current_fw_version() {
	if ! cat /etc/bos_version 2>/dev/null; then
		awk '/Package: /{p=$2} /Version: /{v=$2} /Status: /{if (p == "firmware" && $NF == "installed") print v}' \
			'/usr/lib/opkg/status'
	fi
}

current_fw_major() {
	cat /etc/bos_major 2>/dev/null || echo unknown
}

check_fw_version() {
	echo "Gathering current firmware information..."

	local board_name=$1
	local fw_major=$(current_fw_major)
	local fw_version=$(current_fw_version)

	echo "board  : $board_name"
	echo "version: $fw_version"
	echo "major  : $fw_major"

	echo "Checking compatibility..."

	if [ "$UPGRADE_FW_VERSION" ">" "$fw_version" ]; then
		# firmware upgrade
		if [ "$UPGRADE_FW_REQUIRE" ">" "$fw_version" ]; then
			echo "Firmware upgrade to '$UPGRADE_FW_VERSION' is not possible!"
			echo "Firmware version '$UPGRADE_FW_REQUIRE' is required before upgrading to this version."
			return 1
		fi
	elif [ "$UPGRADE_FW_VERSION" "<" "$fw_version" ]; then
		# firmware downgrade
		if [ "$UPGRADE_FW_MAJOR" != "$fw_major" ]; then
			echo "Firmware downgrade to '$UPGRADE_FW_VERSION' is not possible!"
			echo "Downgrade is only possible among firmwares with major version '$UPGRADE_FW_MAJOR'."
			echo "Do the factory reset and try to upgrade to this version."
			return 1
		fi
	fi

	return 0
}

modify_configs() {
	# extract configuration files from tarball
	mkdir -p "$CONF_DIR_PATH"
	tar -xzf "$CONF_TGZ_PATH" -C "$CONF_DIR_PATH"
	echo "$CONF_DIR_PATH"

	return 0
}

finish_configs_modification() {
	if [ "$1" == "store" ]; then
		tar -czf "$CONF_TGZ_PATH" -C "$CONF_DIR_PATH" $(ls -A "$CONF_DIR_PATH")
	fi

	# cleanup system
	rm -rf "$CONF_DIR_PATH"

	return 0
}

fix_system_hostname() {
	# affected firmwares do not have linked sed in sysupgrade rootfs
	[ -f "/bin/sed" ] && return 0

	/bin/busybox ln -s /bin/busybox /bin/sed

	local conf_dir_path=$(modify_configs)
	local conf_action="discard"

	sys_hostname=$(sed -n "/config system/,/config/s/.*option hostname '\(.*\)'/\1/p" \
		"$conf_dir_path/etc/config/system" 2>/dev/null)
	eth_hostname=$(sed -n "/config.*'lan'/,/config/s/.*option hostname '\(.*\)'/\1/p" \
		"$conf_dir_path/etc/config/network" 2>/dev/null)

	# detect obsolete miner system hostname
	if [ "$sys_hostname" = "MINER" -a -n "$eth_hostname" ]; then
		echo "Fixing obsolete miner system hostname..."
		# move ethernet specific hostname to global system hostname
		sed -i "/config system/,/config/s/\(.*option hostname\) .*/\1 '$eth_hostname'/" \
			"$conf_dir_path/etc/config/system" 2>/dev/null && \
		sed -i "/config.*'lan'/,/config/{/.*option hostname '/d}" \
			"$conf_dir_path/etc/config/network" 2>/dev/null && \
		conf_action="store"
	fi

	finish_configs_modification $conf_action

	# cleanup system
	rm /bin/sed

	return 0
}

# Fix old firmwares where original U-Boot wasn't stored in recovery partition
fix_recovery_partition() {
	# affected firmwares do not have linked gzip in sysupgrade rootfs
	[ -f "/bin/gzip" ] && return 0

	/bin/busybox ln -s /bin/busybox /bin/gzip
	/bin/busybox ln -s /bin/busybox /usr/bin/head

	local UBOOT_MTD=/dev/mtd1
	local RECOVERY_MTD=/dev/mtd6
	local RECOVERY_MTD_NAME=recovery

	local UBOOT_RECOVERY_OFFSET=0x1520000
	# All previous U-Boots have the same size
	local UBOOT_SIZE=573700

	# Dump first bytes of original U-Boot from recovery partition
	recovery_magic=$(nanddump -s ${UBOOT_RECOVERY_OFFSET} -ql 4 ${RECOVERY_MTD} | hexdump -v -n 4 -e '1/1 "%02x"')

	# If U-Boot in recovery partition is not erased then no fix is needed
	[ "$recovery_magic" == "ffffffff" ] || return 0

	echo "Fixing missing U-Boot in recovery partition..."

	# Use current U-Boot for creating backup in recovery partition
	nanddump -l ${UBOOT_SIZE} ${UBOOT_MTD} | head -c ${UBOOT_SIZE} | gzip | \
	mtd -np ${UBOOT_RECOVERY_OFFSET} write - ${RECOVERY_MTD_NAME}

	# cleanup system
	rm /usr/bin/head /bin/gzip

	return 0
}

# Support of U-Boot default environment upgrade for old firmwares
compatible_write_uenv() {
	# affected firmwares do not implement 'zynq_write_uenv' function
	type 'zynq_write_uenv' >/dev/null 2>/dev/null && return 0

	local board_name=$1
	local tar_file=$2

	local file_path=sysupgrade-$board_name/uenv
	local uenv_length=`(tar xf ${tar_file} $file_path -O | wc -c) 2>/dev/null`

	[ "$uenv_length" != 0 ] && {
		echo "Upgrading U-Boot default environment..."
		tar xf ${tar_file} $file_path -O | fw_setenv --script -
		fw_printenv
	}
}

# Support of SPL upgrade for old firmwares
compatible_write_spl() {
	# affected firmwares do not implement 'zynq_write_spl' function
	type 'zynq_write_spl' >/dev/null 2>/dev/null && return 0

	local board_name=$1
	local tar_file=$2

	local spl_length=`(tar xf ${tar_file} sysupgrade-$board_name/spl -O | wc -c) 2> /dev/null`

	[ "$spl_length" != 0 ] && {
		echo "Upgrading SPL..."
		mtd erase boot
		tar xf ${tar_file} sysupgrade-$board_name/spl -O | mtd write - boot
	}
}

CGMINER_CONF="/etc/cgminer.conf"
BOSMINER_CONF="/tmp/bosminer.toml"

sort_list() {
	echo "$1" | \
	sed -e 's/,\+$//' -e 's/\(,\+\)/\n/g' | sed 's/^0\+\(\d\+\)/\1/' | \
	sort -n${2} 2> /dev/null
}

get_min() {
	sort_list "$1" | head -n1
}

get_max() {
	sort_list "$1" "r" | head -n1
}

begins_with() {
	case "$2" in
		"$1"*) true;;
		*) false;;
	esac
}

migrate_cgminer_conf() {
	# Do not migrate CGMiner config when BOSminer config is already generated
	[ -f "$BOSMINER_CONF" -o ! -f "$CGMINER_CONF" ] && exit 0

	echo "Migrating obsolete CGMiner configuration..."

	. /usr/share/libubox/jshn.sh

	cat > "$BOSMINER_CONF" <<-END
		[format]
		version = '1.0'
		model = 'Antminer S9'
		generator = 'sysupgrade_script'
		timestamp = $(date +%s)
	END

	json_init
	json_load "$(cat $CGMINER_CONF)"

	local multi_version
	local frequency
	local voltage

	if json_get_type Type "multi-version" && [ "$Type" == string ]; then
		json_get_var multi_version "multi-version"
	fi
	if json_get_type Type "bitmain-freq" && [ "$Type" == string ]; then
		json_get_var frequency "bitmain-freq"
	fi
	if json_get_type Type "bitmain-voltage" && [ "$Type" == string ]; then
		json_get_var voltage "bitmain-voltage"
	fi

	# Chain ID in original CGMiner config does not correspond to real ID and
	# it is also dynamic so per chain conversion is not possible
	frequency=$(get_min $frequency)
	voltage=$(get_max $voltage)

	if [ "$multi_version" != "4" -o -n "$frequency" -o -n "$voltage" ]; then
		cat >> "$BOSMINER_CONF" <<-END

			[hash_chain_global]
		END
		[ "$multi_version" != "4" ] && echo "asic_boost = false" >> "$BOSMINER_CONF"
		[ -n "$frequency" ] && echo "frequency = ${frequency}" >> "$BOSMINER_CONF"
		[ -n "$voltage" ] && echo "voltage = ${voltage}" >> "$BOSMINER_CONF"
	fi

	cat >> "$BOSMINER_CONF" <<-END

		[[group]]
		name = 'Default'
	END

	if json_get_type Type "pools" && [ "$Type" == array ]; then
		local pool_url
		local pool_user
		local pool_pass

		json_select "pools"
		local idx="1"
		while json_get_type Type "$idx" && [ "$Type" == object ]; do
			json_select "$idx"
			json_get_var pool_url "url"
			json_get_var pool_user "user"
			json_get_var pool_pass "pass"
			json_select ..

			if begins_with "stratum+tcp://" "$pool_url" && [ -n "$pool_user" ]; then
				cat >> "$BOSMINER_CONF" <<-END

					[[group.pool]]
					url = '${pool_url}'
					user = '${pool_user}'
				END
				[ -n "$pool_pass" ] && \
				cat >> "$BOSMINER_CONF" <<-END
					password = '${pool_pass}'
				END
			fi

			$((idx++)) 2> /dev/null
		done
		json_select ..
	fi
	return 0
}

replace_cgminer_conf() {
	local conf_dir_path=$(modify_configs)
	local conf_action="discard"

	rm "${conf_dir_path}${CGMINER_CONF}" 2>/dev/null && \
		conf_action="store"
	cp "${BOSMINER_CONF}" "${conf_dir_path}/etc/" 2>/dev/null && \
		conf_action="store"

	finish_configs_modification $conf_action
	return 0
}

check_image() {
	check_fw_version $1 && \
	migrate_cgminer_conf
}

pre_upgrade() {
	local board_name=$1
	local tar_file=$2

	fix_recovery_partition
	fix_system_hostname
	replace_cgminer_conf
	compatible_write_spl $board_name $tar_file
	compatible_write_uenv $board_name $tar_file
	return
}
