UPGRADE_FW_MAJOR="2020-03-29-0-6ec1a631"
UPGRADE_FW_VERSION="2020-04-30-3-732a6ba7-plus"
UPGRADE_FW_REQUIRE="2018-10-24-0-9e5687a2"

UPGRADE_FW_VERSION_SUFFIX=${UPGRADE_FW_VERSION#*-*-*-*-*-}
UPGRADE_FW_VERSION_PLUS=""

case $UPGRADE_FW_VERSION_SUFFIX in *plus*) UPGRADE_FW_VERSION_PLUS="+";; esac

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="/etc/bosminer.toml"

BOSMINER_CONF_DST="/tmp/bosminer.toml"
CONFIG_GENERATOR="sysupgrade_script"

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
}

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

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

	echo "Migrating obsolete CGMiner configuration..."

	. /usr/share/libubox/jshn.sh

	cat > "$BOSMINER_CONF_DST" <<-END
		[format]
		version = '1.0${UPGRADE_FW_VERSION_PLUS}'
		model = 'Antminer S9'
		generator = '${CONFIG_GENERATOR}'
		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_DST" <<-END

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

	cat >> "$BOSMINER_CONF_DST" <<-END

		[[group]]
		name = 'Default'
	END

	if json_get_type Type "pools" && [ "$Type" == array ]; then
		local pool_url pool_user 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 ..

			# Detect stratum server
			begins_with "stratum+tcp://" "$pool_url"
			local stratum=$?

			if [ $stratum -eq 0 ] || ! has_substr "://" "$pool_url"; then
				if [ $stratum -eq 0 ]; then
					cat >> "$BOSMINER_CONF_DST" <<-END

						[[group.pool]]
						url = '${pool_url}'
					END
				else
					cat >> "$BOSMINER_CONF_DST" <<-END

						[[group.pool]]
						# url = '${pool_url}'
						url = 'stratum+tcp://${pool_url}'
					END
				fi
				cat >> "$BOSMINER_CONF_DST" <<-END
					user = '${pool_user:-!non-existent-user!}'
				END
				[ -n "$pool_pass" ] && \
				cat >> "$BOSMINER_CONF_DST" <<-END
					password = '${pool_pass}'
				END
			else
				cat >> "$BOSMINER_CONF_DST" <<-END

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

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

get_bosminer_toml_version() {
	awk -f - "$BOSMINER_CONF" <<-EOF
		/^\[format\]\$/ {
			f = "y"; next
		}
		/^\[.*\]\$/ {
			f = "n"; next
		}
		\$1 == "version" {
			print substr(\$3, 2, length(\$3)-2)
		}
	EOF
	return 0
}

migrate_bosminer_toml_to_open() {
	echo "Migrating BOSminer+ configuration file to open version..."

	local timestamp=$(date +%s)
	awk -f - "$BOSMINER_CONF" > "$BOSMINER_CONF_DST" <<-EOF
		function reset() {
				f = "n"; a = "n"
				g = "n"; t = "n"
		}

		BEGIN {
				reset()
		}
		/^\[format\]\$/ {
				reset(); f = "y"
				print; next
		}
		/^\[autotuning\]\$/ {
				reset(); a = "y"
				print "# " \$0; next
		}
		/^\[.*\]\$/ {
				if (f == "y") {
						if (g == "n") print "generator = '${CONFIG_GENERATOR}'"
						if (t == "n") print "timestamp = ${timestamp}"
						if (g == "n" || t == "n") print ""
				}
				reset()
		}
		f == "y" && \$1 == "version" {
				print "# " \$0; print "version = '1.0'"; next
		}
		f == "y" && \$1 == "generator" {
				g = "y"; print "generator = '${CONFIG_GENERATOR}'"; next
		}
		f == "y" && \$1 == "timestamp" {
				t = "y"; print "timestamp = ${timestamp}"; next
		}
		a == "y" {
				print "# " \$0; next
		}
		{print}
	EOF
	return 0
}

migrate_bosminer_toml_to_plus() {
	echo "Migrating BOSminer configuration file to plus version..."

	local miner_psu_power_limit=$(fw_printenv -n miner_psu_power_limit 2>/dev/null)
	local autotuning_enabled="yes"

	case $miner_psu_power_limit in
		default)
			miner_psu_power_limit=
		;;
		"")
			autotuning_enabled="no"
			miner_psu_power_limit=
		;;
	esac

	awk -f - "$BOSMINER_CONF" > "$BOSMINER_CONF_DST" <<-EOF
		/^\[format\]\$/ {
			f = "y"
			print; next
		}
		/^\[.*\]\$/ {
			f = "n"
		}
		f == "y" && \$1 == "version" {
			print "# " \$0; print "version = '1.0+'"; next
		}
		{print}
		END {
			if ("${autotuning_enabled}" == "yes") {
				print ""
				print "[autotuning]"
				print "enabled = true"
				if ("${miner_psu_power_limit}") {
					print "psu_power_limit = ${miner_psu_power_limit}"
				}
			}
		}
	EOF
	return 0
}

migrate_bosminer_toml() {
	# Do not migrate BOSminer config when BOSminer config is not present
	[ ! -f "$BOSMINER_CONF" ] && return 0

	local current_toml_version=$(get_bosminer_toml_version)
	# Exit when format version cannot be determined
	[ -z "$current_toml_version" ] && return 0

	local current_toml_version_plus=""
	case $current_toml_version in *+) current_toml_version_plus="+";; esac

	if [ -n "$UPGRADE_FW_VERSION_PLUS" ]; then
		# Do the migration when upgrading from open to plus version
		[ -z "$current_toml_version_plus" ] && migrate_bosminer_toml_to_plus
	else
		# Do the migration when downgrading from plus to open version
		[ -n "$current_toml_version_plus" ] && migrate_bosminer_toml_to_open
	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_DST}" "${conf_dir_path}/etc/" 2>/dev/null && \
		conf_action="store"

	finish_configs_modification $conf_action
	return 0
}

check_image() {
	check_fw_version $1 && {
		migrate_bosminer_toml
		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
}
