#!/bin/bash
#
# Copyright (C) MOXA Inc. All rights reserved.
# This software is distributed under the terms of the MOXA SOFTWARE NOTICE.
# See the file MOXA-SOFTWARE-NOTICE for details.
#
# Authors:
# 	2021	Wes Huang	<Wes.Huang@moxa.com>

_initial_dir() {
	_log_msg "debug" "${0}, ${FUNCNAME[0]}"

	if [ -L "${P3_DIR}/default/docker" ]; then
		mkdir -p "${P3_DIR}"/{working,replica,restore,snapshot}/{boot,rootfs,docker,bootloader}
		mkdir -p "${P3_DIR}/default"/{boot,rootfs,bootloader}
	else
		mkdir -p "${P3_DIR}"/{working,default,replica,restore,snapshot}/{boot,rootfs,docker,bootloader}
	fi
	mkdir -p "${P3_DIR}/backup"
	mkdir -p "${REFRESH_UPGRADE_BOOT_DIR}"
	mkdir -p "${PRESERVED_DIR}"
}

_clear_restore_redundant_data() {
	[ -f "${RESTORE_DIR}/.generate-default-boot" ] && rm -rf "${RESTORE_DIR}/.generate-default-boot"
	[ -f "${RESTORE_DIR}/.generate-default-bootloader" ] && rm -rf "${RESTORE_DIR}/.generate-default-bootloader"
	[ -L "${RESTORE_DIR}/docker" ] && rm -rf "${RESTORE_DIR}/docker"
}

_log_msg() {
	local type="${1}"
	local msg="${2}"
	local ret_val=""

	case "${type}" in
	info)
		echo "${msg}"
		logger -t "mx-system-mgmt <${type}>" "${msg}"
		;;
	warn)
		echo "${msg}"
		logger -t "mx-system-mgmt <${type}>" "${msg}"
		;;
	error)
		echo "${msg}"
		logger -t "mx-system-mgmt <${type}>" "${msg}"
		;;
	debug)
		logger -t "mx-system-mgmt <${type}>" "${msg}"
		;;
	esac
	return 0
}

help_menu() {
	printf "\033[46;38m Usage: \033[0m\n"
	echo -e "	mx-system-mgmt <Commands> [Sub-Commands] [Options] [Flags]"
	echo -e ""
	printf "\033[46;38m Commands: \033[0m\n"
	echo -e "	snapshot	Manage the snapshots."
	echo -e "	system-failback	Configure system-failback function."
	echo -e "	default		Set system to default state."
	echo -e "	backup		Manage the backups."
	echo -e "	version		Display the version information"
	echo -e "	help		Display the help menu"
	echo -e ""

	return 0
}

helper_help_menu() {
	printf "\033[46;38m Usage: \033[0m\n"
	echo -e "	mx-system-mgmt-helper <Commands> [Sub-Commands] [Options] [Flags]"
	echo -e ""
	printf "\033[46;38m Commands: \033[0m\n"
	echo -e "	refresh-upgrade	Configure refresh-upgrade function."
	echo -e "	version		Display the version information"
	echo -e "	help		Display the help menu"
	echo -e ""

	return 0
}

snapshot_help_menu() {
	printf "\033[46;38m Usage: \033[0m\n"
	echo -e "	mx-system-mgmt snapshot [Sub-Commands] [Options] [Flags]"
	echo -e ""
	printf "\033[46;38m Sub-Commands: \033[0m\n"
	echo -e "	create		Create a snapshot"
	echo -e "	restore		Use snapshot to restore the system."
	echo -e "	delete		Delete the snapshot"
	echo -e "	info		Display snapshot information"
	echo -e "	help		Display the snapshot help menu"
	echo -e ""
	printf "\033[46;38m Options: \033[0m\n"
	echo -e "	--cold		Create a snapshot after restarting the system in a minimal environment like initrd."
	echo -e "			This ensures data consistency but requires system downtime during the creation process."
	echo -e "	--hot		Create a snapshot of the system while the system remains operational. No system downtime is required."
	echo -e "			Caution: While hot creation offers minimal disruption, there's a risk of data inconsistencies"
	echo -e "			due to ongoing changes during the backup process. (default mode)"
	echo -e "	--size		Estimate the extra disk space needed for creating the snapshot."
	echo -e ""
	printf "\033[46;38m Flags: \033[0m\n"
	echo -e "	-y, --yes	Automatic yes to prompts"
	echo -e ""
	printf "\033[46;38m Examples: \033[0m\n"
	echo -e "	Create a snapshot"
	echo -e "	$ mx-system-mgmt snapshot create"
	echo -e "	Create a snapshot with cold mode"
	echo -e "	$ mx-system-mgmt snapshot create --cold"
	echo -e "	Estimate the extra disk space needed for snapshot"
	echo -e "	$ mx-system-mgmt snapshot create --size"
	echo -e "	Use snapshot to restore the system"
	echo -e "	$ mx-system-mgmt snapshot restore"
	echo -e "	Show snapshot information"
	echo -e "	$ mx-system-mgmt snapshot info"
	echo -e "	Delete the snapshot"
	echo -e "	$ mx-system-mgmt snapshot delete"
	echo -e ""

	return 0
}

system_failback_help_menu() {
	printf "\033[46;38m Usage: \033[0m\n"
	echo -e "	mx-system-mgmt system-failback [Sub-Commands] [Options] [Flags]"
	echo -e ""
	printf "\033[46;38m Sub-Commands: \033[0m\n"
	echo -e "	enable		Create a replica and enable system failback feature"
	echo -e "			When enabled, the system will automatically restore the system by the replica,"
	echo -e "			if the system fails to restart after the next reboot"
	echo -e "	disable		Disable the system failback function and delete replica."
	echo -e "	info		Display the replica information created by the system-failback function."
	echo -e "	state		Display the current state of the system failback function."
	echo -e "	help		Display the system-failback help menu"
	echo -e ""
	printf "\033[46;38m Options: \033[0m\n"
	echo -e "	-V, --value	Only the state value is displayed."
	echo -e "	--cold		Create a replica after restarting the system in a minimal environment like initrd."
	echo -e "			This ensures data consistency but requires system downtime during the creation process."
	echo -e "	--hot		Create a replica of the system while the system remains operational. No system downtime is required."
	echo -e "			Caution: While hot creation offers minimal disruption, there's a risk of data inconsistencies"
	echo -e "			due to ongoing changes during the backup process. (default)"
	echo -e "	--size		Estimate the extra disk space needed for creating the replica."
	echo -e ""
	printf "\033[46;38m Flags: \033[0m\n"
	echo -e "	-y, --yes	Automatic yes to prompts"
	echo -e ""
	printf "\033[46;38m Examples: \033[0m\n"
	echo -e "	Create a replica and enable system failback feature"
	echo -e "	$ mx-system-mgmt system-failback enable"
	echo -e "	Create a replica and enable system failback feature with cold mode"
	echo -e "	$ mx-system-mgmt system-failback enable --cold"
	echo -e "	Estimate the extra disk space needed for replica"
	echo -e "	$ mx-system-mgmt system-failback enable --size"
	echo -e "	Disable the system failback function and delete replica"
	echo -e "	$ mx-system-mgmt system-failback disable"
	echo -e "	Show replica information"
	echo -e "	$ mx-system-mgmt system-failback info"
	echo -e "	Show the current state of the system failback function"
	echo -e "	$ mx-system-mgmt system-failback state"
	echo -e "	Show the current state of the system failback function by value"
	echo -e "	$ mx-system-mgmt system-failback state --value"
	echo -e ""

	return 0
}

refresh_upgrade_help_menu() {
	printf "\033[46;38m Usage: \033[0m\n"
	echo -e "	mx-system-mgmt-helper refresh-upgrade [Sub-Commands] [Flags]"
	echo -e ""
	printf "\033[46;38m Sub-Commands: \033[0m\n"
	echo -e "	restore		Using upgrade pack to completely overwrite the old version for update."
	echo -e "			Your data will be lost. After overwriting is complete,"
	echo -e "			the system will re-import the files in the preserved list back to the system."
	echo -e "	help		Display the refresh-upgrade help menu"
	echo -e ""
	printf "\033[46;38m Flags: \033[0m\n"
	echo -e "	-y, --yes	Automatic yes to prompts"
	echo -e ""

	return 0
}

default_help_menu() {
	printf "\033[46;38m Usage: \033[0m\n"
	echo -e "	mx-system-mgmt default [Sub-Commands] [Flags]"
	echo -e ""
	printf "\033[46;38m Sub-Commands: \033[0m\n"
	echo -e "	restore		Delete all user data and restore system to factory default state"
	echo -e "			while keeping the system and audit log"
	echo -e "	decommission	Destroy all user data including system and audit logs in an unrecoverable manner,"
	echo -e "			and restore system and bootloader to factory default state"
	echo -e "	help		Display the default help menu"
	echo -e ""
	printf "\033[46;38m Flags: \033[0m\n"
	echo -e "	-y, --yes	Automatic yes to prompts"
	echo -e ""
	printf "\033[46;38m Examples: \033[0m\n"
	echo -e "	Restore system to factory default state"
	echo -e "	$ mx-system-mgmt default restore"
	echo -e "	Destroy all user data including system and audit logs and restore system and bootloader to factory default state"
	echo -e "	$ mx-system-mgmt default decommission"
	echo -e ""

	return 0
}

backup_help_menu() {
	printf "\033[46;38m Usage: \033[0m\n"
	echo -e "	mx-system-mgmt backup [Sub-Commands] [Options] [Flags]"
	echo -e ""
	printf "\033[46;38m Sub-Commands: \033[0m\n"
	echo -e "	create		Create backup file that clones the entire system."
	echo -e "	restore		Use the backup file to restore the system."
	echo -e "	delete		Delete backup file."
	echo -e "	info		Display backup file information."
	echo -e "	help		Display the backup help menu"
	echo -e ""
	printf "\033[46;38m Options: \033[0m\n"
	echo -e "	--compress	Create backup in compressed format."
	echo -e "	-D, --directory <directory>	Set <directory> as backup folder."
	echo -e "	--cold		Create a backup after restarting the system in a minimal environment like initrd."
	echo -e "			This ensures data consistency but requires system downtime during the creation process."
	echo -e "			Since the backup file will be created during the bootup phase,"
	echo -e "			-D is not supported to set at the same time."
	echo -e "	--hot		Create a replica of the system while the system remains operational. No system downtime is required."
	echo -e "			Caution: While hot creation offers minimal disruption, there's a risk of data inconsistencies"
	echo -e "			due to ongoing changes during the backup process. (default)"
	echo -e "	--size		Estimate the required disk space needed for creating the backup."
	echo -e ""
	printf "\033[46;38m Flags: \033[0m\n"
	echo -e "	-y, --yes	Automatic yes to prompts"
	echo -e ""
	printf "\033[46;38m Examples: \033[0m\n"
	echo -e "	Create a backup"
	echo -e "	$ mx-system-mgmt backup create"
	echo -e "	Create a backup in compressed format"
	echo -e "	$ mx-system-mgmt backup create --compress"
	echo -e "	Create a backup with cold mode in compressed format"
	echo -e "	$ mx-system-mgmt backup create --cold --compress"
	echo -e "	Create a backup with cold mode"
	echo -e "	$ mx-system-mgmt backup create --cold"
	echo -e "	Estimate the extra disk space needed for backup"
	echo -e "	$ mx-system-mgmt backup create --size"
	echo -e "	Use backup to restore the system"
	echo -e "	$ mx-system-mgmt backup restore"
	echo -e "	Show backup information"
	echo -e "	$ mx-system-mgmt backup info"
	echo -e "	Delete the backup"
	echo -e "	$ mx-system-mgmt backup delete"
	echo -e ""

	return 0
}

_set_flag() {
	local flag="${1}"

	_log_msg "debug" "${0}, ${FUNCNAME[0]}"
	_log_msg "debug" "flag=${flag}, MX_SYSTEM_MGMT_FLAG_FILE=${MX_SYSTEM_MGMT_FLAG_FILE}"

	echo "${flag}" >"${MX_SYSTEM_MGMT_FLAG_FILE}"
	sync

	return 0
}

_set_preserved_flag() {
	local flag="${1}"

	_log_msg "debug" "${0}, ${FUNCNAME[0]}"
	_log_msg "debug" "flag=${flag}, MX_SYSTEM_MGMT_PRESERVEDFLAG_FILE=${MX_SYSTEM_MGMT_PRESERVED_FLAG_FILE}"

	echo "${flag}" >"${MX_SYSTEM_MGMT_PRESERVED_FLAG_FILE}"
	sync

	return 0
}

_set_cold_creation_flag() {
	local flag="${1}"

	_log_msg "debug" "${0}, ${FUNCNAME[0]}"
	_log_msg "debug" "flag=${flag}, MX_SYSTEM_MGMT_COLD_CREATION_FILE=${MX_SYSTEM_MGMT_COLD_CREATION_FLAG_FILE}"

	echo "${flag}" >"${MX_SYSTEM_MGMT_COLD_CREATION_FLAG_FILE}"
	sync

	return 0
}

_check_min_req_tool_ver() {
	local info_type="${1}"
	local info_file
	local version
	local greater_version

	version=$(mx-system-mgmt -v)

	_log_msg "debug" "${0}, ${FUNCNAME[0]}"
	_log_msg "debug" "info_type=${info_type}, info_file=${info_file}, version=${version}"

	if [ "${info_type}" == "snapshot" ]; then
		info_file="${SNAPSHOT_INFO_FILE}"
	elif [ "${info_type}" == "replica" ]; then
		info_file="${REPLICA_INFO_FILE}"
	elif [ "${info_type}" == "backup" ]; then
		info_file="${BACKUP_INFO_FILE}"
	fi

	_log_msg "debug" "info_file=${info_file}"

	# shellcheck disable=SC1090
	source "${info_file}"

	# shellcheck disable=SC2153
	greater_version=$(printf '%s\n%s\n' "${version}" "${MIN_REQ_TOOL_VER}" | sort -V | tail -n1)
	if [ "${version}" != "${greater_version}" ]; then
		_log_msg "warn" "The currently used moxa-system-manager package version is ${version}"
		_log_msg "warn" "The minimum required version of the backup file is ${MIN_REQ_TOOL_VER}. Please install the newer moxa-system-manager package."
		exit "${_ERR_MIN_REQ_TOOL_VER}"
	fi
}

_check_info_file() {
	local info_type="${1}"
	local info_file
	local ret_val

	_log_msg "debug" "${0}, ${FUNCNAME[0]}"
	_log_msg "debug" "info_type=${info_type}"

	if [ "${info_type}" == "snapshot" ]; then
		info_file="${SNAPSHOT_INFO_FILE}"
		ret_val="${_ERR_NO_SNAPSHOT_INFO}"
	elif [ "${info_type}" == "replica" ]; then
		info_file="${REPLICA_INFO_FILE}"
		ret_val="${_ERR_NO_REPLICA_INFO}"
	elif [ "${info_type}" == "backup" ]; then
		info_file="${BACKUP_INFO_FILE}"
		ret_val="${_ERR_NO_BACKUP_INFO}"
	fi

	_log_msg "debug" "info_file=${info_file}, ret_val=${ret_val}"

	if [ ! -f "${info_file}" ]; then
		_log_msg "warn" "There is no ${info_type} information"
		return "${ret_val}"
	else
		return 0
	fi
}

_remove_info() {
	local info_type="${1}"
	local info_file

	_log_msg "debug" "${0}, ${FUNCNAME[0]}"
	_log_msg "debug" "info_type=${info_type}"

	if [ "${info_type}" == "snapshot" ]; then
		info_file="${SNAPSHOT_INFO_FILE}"
	elif [ "${info_type}" == "replica" ]; then
		info_file="${REPLICA_INFO_FILE}"
	elif [ "${info_type}" == "backup" ]; then
		info_file="${BACKUP_INFO_FILE}"
	fi

	_log_msg "debug" "info_file=${info_file}"

	if [ -f "${info_file}" ]; then
		_log_msg "debug" "remove ${info_file}"
		rm -rf "${info_file}"
	fi
}

_show_info() {
	local info_type="${1}"
	local info_file

	_log_msg "debug" "${0}, ${FUNCNAME[0]}"
	_log_msg "debug" "info_type=${info_type}"

	if [ "${info_type}" == "snapshot" ]; then
		info_file="${SNAPSHOT_INFO_FILE}"
	elif [ "${info_type}" == "replica" ]; then
		info_file="${REPLICA_INFO_FILE}"
	elif [ "${info_type}" == "backup" ]; then
		info_file="${BACKUP_INFO_FILE}"
	fi

	_log_msg "debug" "info_file=${info_file}"

	# shellcheck disable=SC1090
	source "${info_file}"

	# shellcheck disable=SC2153
	_log_msg "info" "Type: ${TYPE}"
	# shellcheck disable=SC2153
	_log_msg "info" "Create Time: $(date --date=@"${CREATE_TIME}" +%Y.%m.%d-%T)"
	# shellcheck disable=SC2153
	_log_msg "info" "Size: ${SIZE}MB"

	# shellcheck disable=SC2153
	if [ "${info_type}" == "backup" ] && [ "${COMPRESSED}" == "y" ]; then
		_log_msg "info" "Compressed Size: ${COMPRESSED_SIZE}MB"
	fi

	return 0
}

_calculate_transferred_size() {
	local type="${1}"
	local yes="${2}"
	local size="${3}"
	local source_dir
	local target_dir
	local free_size
	local boot_size

	_log_msg "debug" "${0}, ${FUNCNAME[0]}"
	_log_msg "debug" "type=${type}"

	if [ "${type}" == "backup" ]; then
		free_size=$(df -m "${BACKUP_DIR}" | tail -n1 | awk '{print $4}')
		TOTAL_SIZE="$(tar --warning='no-file-ignored' --xattrs --xattrs-include='*' -cpf - -C / "${WORKING_DIR##/}" "${SNAPSHOT_DIR##/}" "${DEFAULT_DIR##/}" "${ROOTFS_SQAUSHFS##/}" | wc -c)"
		TOTAL_SIZE=$((TOTAL_SIZE / 1024 / 1024))
	elif [ "${type}" == "backup_restore" ]; then
		free_size=$(df -m "${P3_DIR}" | tail -n1 | awk '{print $4}')
		# shellcheck disable=SC1090
		source "${BACKUP_DIR}/info"
		TOTAL_SIZE=${SIZE}
	else
		free_size=$(df -m "${P3_DIR}" | tail -n1 | awk '{print $4}')
		boot_size=$(du -sb "${BOOT_DIR}/" | awk '{print $1}')
		_log_msg "debug" "boot_size=${boot_size}"
		if [ "${type}" == "replica" ]; then
			source_dir="${WORKING_DIR}"
			target_dir="${REPLICA_DIR}"
		elif [ "${type}" == "snapshot" ]; then
			source_dir="${WORKING_DIR}"
			target_dir="${SNAPSHOT_DIR}"
		elif [ "${type}" == "snapshot_restore" ]; then
			source_dir="${SNAPSHOT_DIR}"
			target_dir="${RESTORE_DIR}"
		fi
		_log_msg "debug" "free_size=${free_size}, source_dir=${source_dir}, target_dir=${target_dir}"
		mkdir -p "${target_dir}"
		TOTAL_SIZE=$(rsync -aX --delete --delete-excluded -n --stats "${source_dir}/" "${target_dir}" | grep "Total transferred file size" | awk '{print $5}' | sed 's/,//g')
		TOTAL_SIZE=$((TOTAL_SIZE + boot_size))
		TOTAL_SIZE=$((TOTAL_SIZE / 1024 / 1024))
	fi
	_log_msg "debug" "TOTAL_SIZE=${TOTAL_SIZE}"

	if [ "${size}" == "y" ]; then
		_log_msg "info" "${TOTAL_SIZE}MB"
	else
		_log_msg "info" "Estimation of Required Space: ${TOTAL_SIZE}MB"
		_log_msg "info" "Available Space: ${free_size}MB"
	fi

	if [ "${TOTAL_SIZE}" -gt "${free_size}" ]; then
		_log_msg "warn" "There's not enough space. Free up space and try again."
		exit "${_ERR_NO_SPACE}"
	fi

	_question "${yes}" "Would you like to continue? (y/N)"

	return 0
}

_rsync() {
	local source_folder="${1}"
	local target_folder="${2}"
	local ret_val

	_log_msg "debug" "${0}, ${FUNCNAME[0]}"
	_log_msg "debug" "source_folder=${source_folder}, target_folder=${target_folder}, SYS_ARCH=${SYS_ARCH}"

	mkdir -p "${target_folder}"
	rsync -aX --delete --delete-excluded --info=progress2 --no-i-r "${source_folder}/" "${target_folder}"
	ret_val="${?}"
	sync

	if [ ${ret_val} != 0 ]; then
		_log_msg "error" "There is something wrong, when syncing files."
		exit "${_ERR_RSYNC}"
	else
		return 0
	fi
}

_sync_bootloader_flash() {
	local source_device="${1}"
	local target_device="${2}"
	local type="${3}"
	local source_device_sha256=""
	local target_device_sha256=""

	_log_msg "debug" "${0}, ${FUNCNAME[0]}"
	_log_msg "debug" "source_device=${source_device}, target_device=${target_device}, type=${type}"

	if echo "${DEVICE_MODEL_NAME}" | grep -q "ioThinx"; then
		if [ "${type}" == "create" ]; then
			source_device_sha256="$(dd if="${source_device}" bs=1M skip=1 count=1 | sha256sum | awk '{print $1}')"
			target_device_sha256="$(dd if="${target_device}" bs=1M skip=2 count=1 | sha256sum | awk '{print $1}')"
		else
			source_device_sha256="$(dd if="${source_device}" bs=1M skip=2 count=1 | sha256sum | awk '{print $1}')"
			target_device_sha256="$(dd if="${target_device}" bs=1M skip=1 count=1 | sha256sum | awk '{print $1}')"
		fi
	else
		source_device_sha256="$(sha256sum "${source_device}" | awk '{print $1}')"
		target_device_sha256="$(sha256sum "${target_device}" | awk '{print $1}')"
	fi

	_log_msg "debug" "source_device_sha256=${source_device_sha256}, target_device_sha256=${target_device_sha256}"

	if [ "${source_device_sha256}" != "${target_device_sha256}" ]; then
		_log_msg "info" "Synchronize bootloader partition..."
		if echo "${DEVICE_MODEL_NAME}" | grep -q "ioThinx"; then
			if [ "${type}" == "create" ]; then
				echo 0 >/sys/block/mmcblk2boot0/force_ro
				dd if="${source_device}" bs=1M skip=1 seek=2 count=1 conv=notrunc of="${target_device}"
				echo 1 >/sys/block/mmcblk2boot0/force_ro
			else
				echo 0 >/sys/block/mmcblk2boot0/force_ro
				dd if="${source_device}" bs=1M skip=2 seek=1 count=1 conv=notrunc of="${target_device}"
				echo 1 >/sys/block/mmcblk2boot0/force_ro
			fi
		else
			flash_erase "${target_device}" 0 0
			dd if="${source_device}" of="${target_device}"
		fi
	else
		return 0
	fi

	if echo "${DEVICE_MODEL_NAME}" | grep -q "ioThinx"; then
		if [ "${type}" == "create" ]; then
			source_device_sha256="$(dd if="${source_device}" bs=1M skip=1 count=1 | sha256sum | awk '{print $1}')"
			target_device_sha256="$(dd if="${target_device}" bs=1M skip=2 count=1 | sha256sum | awk '{print $1}')"
		else
			source_device_sha256="$(dd if="${source_device}" bs=1M skip=2 count=1 | sha256sum | awk '{print $1}')"
			target_device_sha256="$(dd if="${target_device}" bs=1M skip=1 count=1 | sha256sum | awk '{print $1}')"
		fi
	else
		source_device_sha256="$(sha256sum "${source_device}" | awk '{print $1}')"
		target_device_sha256="$(sha256sum "${target_device}" | awk '{print $1}')"
	fi

	_log_msg "debug" "source_device_sha256=${source_device_sha256}, target_device_sha256=${target_device_sha256}"

	if [ "${source_device_sha256}" != "${target_device_sha256}" ]; then
		_log_msg "error" "Synchronize failed"
		exit "${_ERR_HASH_VALUE}"
	fi

	return 0
}

_create() {
	local create_type="${1}"
	local option="${2}"

	_log_msg "debug" "${0}, ${FUNCNAME[0]}"
	_log_msg "debug" "create_type=${create_type}, SYS_ARCH=${SYS_ARCH}, option=${option}"

	_log_msg "info" "Synchronize boot files..."
	_rsync "${BOOT_DIR}" "${WORKING_DIR}/boot"

	if [ "${create_type}" == "replica" ]; then
		_rsync "${BOOT_DIR}" "${REPLICA_BOOT_DIR}"
		if [[ "${SYS_ARCH}" == "armv7l" ]] || [[ "${SYS_ARCH}" == "aarch64" ]]; then
			_sync_bootloader_flash "${BOOTLOADER_WORKING_DEVICE}" "${BOOTLOADER_REPLICA_DEVICE}" "create"
		fi
		if [ "${option}" != "cold" ]; then
			_log_msg "info" "Start creating ${create_type}..."
			_rsync "${WORKING_DIR}" "${REPLICA_DIR}"
		fi
	elif [ "${create_type}" == "snapshot" ]; then
		if [ "${option}" != "cold" ]; then
			_log_msg "info" "Start creating ${create_type}..."
			_rsync "${WORKING_DIR}" "${SNAPSHOT_DIR}"
		fi
	fi
	sync

	return 0
}

_question() {
	local yes="${1}"
	local message="${2}"
	local choice

	_log_msg "debug" "${0}, ${FUNCNAME[0]}"
	_log_msg "debug" "message=${message}, yes=${yes}"

	if [ "${yes}" != "y" ]; then
		_log_msg "info" "${message}"
		read -r -p "" choice
		_log_msg "debug" "choice=${choice}"
		if [ "${choice,,}" != "y" ]; then
			exit "${_ERR_CHOICE_NO}"
		fi
	fi
}

_create_info() {
	local info_type="${1}"
	local create_time="${2}"
	local dir="${3}"
	local size=""
	local backup_file_name="${4}"
	local sha_val=""
	local compressed=""
	local compressed_size=""

	_log_msg "debug" "${0}, ${FUNCNAME[0]}"
	_log_msg "debug" "info_type=${info_type}, create_time=${create_time}, dir=${dir}, backup_file_name=${backup_file_name}"

	case "${info_type}" in
	backup)
		if [ "${SYS_ARCH}" == "armv7l" ]; then
			sha_val="$(sha256sum "${dir}/${backup_file_name}" | awk '{print $1}')"
		elif [ "${SYS_ARCH}" == "x86_64" ] || [ "${SYS_ARCH}" == "aarch64" ]; then
			sha_val="$(sha512sum "${dir}/${backup_file_name}" | awk '{print $1}')"
		fi
		if [ "${MSM_OPTION_COMPRESS}" == "y" ]; then
			size="${TOTAL_SIZE}"
			compressed="y"
			compressed_size="$(du -m "${dir}/${backup_file_name}" | awk '{print $1}')"

			# Since compressed backup files are provided in version 2.23.0, users need to use a version newer than the following version number.
			min_req_tool_ver="2.23.0"
		else
			size="$(du -m "${dir}/${backup_file_name}" | awk '{print $1}')"
			compressed="n"
			compressed_size="N/A"

			# Since backup files are provided in version 2.3.0, users need to use a version newer than the following version number.
			min_req_tool_ver="2.3.0"
		fi
		;;
	snapshot | replica)
		size="$(du -m "${dir}" | tail -n 1 | awk '{print $1}')"
		sha_val="N/A"
		compressed="n"
		compressed_size="N/A"

		# Since snapshot and replica are provided in version 2.3.0, users need to use a version newer than the following version number.
		min_req_tool_ver="2.3.0"
		;;
	esac

	{
		echo "TYPE=${info_type}"
		echo "COMPRESSED=${compressed}"
		echo "CREATE_TIME=${create_time}"
		echo "SIZE=${size}"
		echo "COMPRESSED_SIZE=${compressed_size}"
		echo "SHA_VAL=${sha_val}"
		echo "MODEL_NAME=${DEVICE_MODEL_NAME}"
		echo "MIN_REQ_TOOL_VER=${min_req_tool_ver}"
	} >>"${dir}/info"
	sync

	return 0
}

_get_efi_var() {
	local efi_var_name="${1}"

	_log_msg "debug" "${0}, ${FUNCNAME[0]}"
	_log_msg "debug" "efi_var_name=${efi_var_name}"

	efivar -d -n "$efi_var_name" | sed 's/ *//g'
}

_get_env() {
	local env_name="${1}"
	local replica

	_log_msg "debug" "${0}, ${FUNCNAME[0]}"
	_log_msg "debug" "env_name=${env_name}, SYS_ARCH=${SYS_ARCH}"

	if [ "$env_name" == "replica" ]; then
		if [[ "${SYS_ARCH}" == "armv7l" ]] || [[ "${SYS_ARCH}" == "aarch64" ]]; then
			replica="$(fw_printenv -n replica 2>/dev/null)"
		elif [[ "${SYS_ARCH}" == "x86_64" ]]; then
			replica="$(_get_efi_var "$EFI_VAR_UUID")"
		fi
	fi
	echo "$replica"
}

_set_env() {
	local env_name="${1}"
	local env_value="${2}"

	_log_msg "debug" "${0}, ${FUNCNAME[0]}"
	_log_msg "debug" "env_name=${env_name}, env_value=${env_value}, SYS_ARCH=${SYS_ARCH}"

	if [ "$env_name" == "replica" ]; then
		if [[ "${SYS_ARCH}" == "armv7l" ]] || [[ "${SYS_ARCH}" == "aarch64" ]]; then
			fw_setenv replica "$env_value"
		elif [[ "${SYS_ARCH}" == "x86_64" ]]; then
			_set_efi_var "$EFI_VAR_UUID" "$env_value"
		fi
	fi
}

_set_efi_var() {
	local efi_var_name="${1}"
	local efi_var_num="\x${2}"
	local tmp_efivar_file="/dev/shm/efi_var_num.bin"

	_log_msg "debug" "${0}, ${FUNCNAME[0]}"
	_log_msg "debug" "efi_var_name=${efi_var_name}, efi_var_num=${efi_var_num}, tmp_efivar_file=${tmp_efivar_file}"

	printf "%b" "$efi_var_num" >"$tmp_efivar_file"
	efivar -w -n "$efi_var_name" -f "$tmp_efivar_file"
	rm -f $tmp_efivar_file
}

_restore() {
	local type="${1}"

	_log_msg "debug" "${0}, ${FUNCNAME[0]}"
	_log_msg "debug" "type=${type}"

	_log_msg "info" "Synchronize boot files..."
	if [ "${type}" == "replica_restore" ]; then
		_rsync "${REPLICA_DIR}/boot" "${WORKING_BOOT_DIR}"
		if [[ "${SYS_ARCH}" == "armv7l" ]] || [[ "${SYS_ARCH}" == "aarch64" ]]; then
			_sync_bootloader_flash "${BOOTLOADER_REPLICA_DEVICE}" "${BOOTLOADER_WORKING_DEVICE}" "restore"
		fi
	elif [ "${type}" == "snapshot_restore" ]; then
		_rsync "${SNAPSHOT_DIR}/boot" "${WORKING_BOOT_DIR}"
		_log_msg "info" "Start using snapshot to restore the system..."
		_rsync "${SNAPSHOT_DIR}" "${RESTORE_DIR}"
	elif [ "${type}" == "default_restore" ]; then
		_rsync "${DEFAULT_DIR}/boot" "${WORKING_BOOT_DIR}"
		_log_msg "info" "Start using default to restore the system..."
		_rsync "${DEFAULT_DIR}" "${RESTORE_DIR}"
		_clear_restore_redundant_data
	elif [ "${type}" == "refresh_upgrade_restore" ]; then
		_rsync "${REFRESH_UPGRADE_DIR}/boot" "${WORKING_BOOT_DIR}"
		_log_msg "info" "Start using refresh-upgrade to restore the system..."
		_rsync "${REFRESH_UPGRADE_DIR}" "${RESTORE_DIR}"
	fi
	sync
}

version() {
	_log_msg "debug" "${0}, ${FUNCNAME[0]}"
	_log_msg "debug" "MX_SYSTEM_MGMT_VER_FILE=${MX_SYSTEM_MGMT_VER_FILE}"

	if [ -f "${MX_SYSTEM_MGMT_VER_FILE}" ]; then
		cat "${MX_SYSTEM_MGMT_VER_FILE}"
	else
		_log_msg "error" "${MX_SYSTEM_MGMT_VER_FILE} does not exist."
	fi
}
