#!/bin/bash

############################################### LICENSE #########################################################
#                                                                                                               #
#  Copyright (c) 2021 KylinSoft, Inc. kyroot.sh is licensed under Mulan PSL v2. You can use this                #
#  software according to the terms and conditions of the Mulan PSL v2. You may obtain a copy of Mulan PSL v2    #
#  at: http://license.coscl.org.cn/MulanPSL2 THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES  #
#  OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR    #
#  FIT FOR A PARTICULAR PURPOSE. See the Mulan PSL v2 for more details.                                         #
#                                                                                                               #
#  Author: Ouyangkang <ouyangkang@kylinos.cn>                                                                   #
#  Created: Jul 30 2021                                                                                         #
#                                                                                                               #
#################################################################################################################

################## BASIC INFO AND CONFIG ################
export LANG="en_US.UTF-8"
mkdir -p /var/log/hostos-upgrade/ &> /dev/null			#确保日志目录存在
LOG_FILE="/var/log/hostos-upgrade/hostos-upgrade.log"		#日志位置
LOG_CONF="/etc/logrotate.d/kyupgrade"				#日志配置
#exec  &> >(tee -a ${LOG_FILE})

USER=`whoami`   		#user name
EXIST_CONFLICT=0		# >1,argument conflict
NEED_REBOOT_PKG="need_reboot"
SELF_UPGRADE_PKG="hostos-upgrade"
TAR_PATH=""					#tar包路径
SOURCE_PATH=""					#rpm包路径
FUNC=""						#需要处理的功能
RPM_DB_DIR="/var/lib/rpm"			#rpm数据库目录
STORAGE_PKG_DIR="/var/cache/hostos-upgrade"	#包存储目录

UPGRADE_STATE="${STORAGE_PKG_DIR}/state"		#状态文件，10，之前有升级且未提交; 9,之前自升级未提交，2|3,之前升级一半出错；文件
NEW_INSTALL_PKG="${STORAGE_PKG_DIR}/new_install"	#新装包的列表，文件
ROLLBACK_POINT="${STORAGE_PKG_DIR}/rollback_point"	#记录回退点，避免手动装包被改变
FORCE_INSTALL="${STORAGE_PKG_DIR}/force_install"	#强制安装的包列表,文件

STORAGE_NEW_PKG="${STORAGE_PKG_DIR}/new"	#存储新包,即之前系统没有安装的包，目录
STORAGE_OLD_PKG="${STORAGE_PKG_DIR}/old"	#存储老包，目录

STORAGE_RPM_DB="${STORAGE_PKG_DIR}/db"		#存储rpm数据库备份，目录


#yum 源相关
SOURCE_BASE_DIR="/etc/yum.repos.d"		#基础路径
SAVE_REPO_LIST=""				#保存使能的仓库列表
START_EXEC_FLAG="Start Exec"			#开始执行输出标志
PRINT_LOG_NUM=""				#日志打印次数


EXEC_ARG=""					#记录执行参数

#DEBUG="eval echo \${LINENO} : "			#调试报错用的,编包的时候注释掉这行
################## END OF BASIC INFO AND CONFIG #########

###### COMPATIBEL CONFIG ############
OLD_VERSION="${STORAGE_PKG_DIR}/old_version"		#如果升级后存在该文件,则说明之前的版本小于等于4，需要读取history进行回滚


###### COMPATIBEL CONFIG END  #######

######################### COLOR CODE ####################
RED="31;1m"      # Error message
GREEN="32;1m"    # Success message
YELLOW="33;1m"   # Warning message
PURPLE="36;1m"     # Info message
LIGHT_BLUE="36m" # Debug message
######################## END OF COLOR CODE ##############

######################## COLOR ECHO #####################
function colorEcho() { echo -e "\033[${1}${@:2}\033[0m";}
function error() { colorEcho $RED $1; }
function success() { colorEcho $GREEN $1; }
function warn() { colorEcho $YELLOW $1; }
function info() { colorEcho $PURPLE $1; }
####################### END OF COLOR ECHO ###############

function print_status() {
        local info=$1
        local status=$2
        printf "%-60s" "$info"
        printf "%s\n" "[ "$status" ]"
}

# 修改${UPGRADE_STATE}中记录的状态
# 0 未升级过
# 1 升级在强制安装阶段报错
# 2 升级在yum install阶段报错
# 9 工具自升级完成，未提交
# 10 升级完成,未提交
function modify_state()
{
	chattr -i ${UPGRADE_STATE}
	if [[ "$1" -eq 0 || $1 -eq 1 || $1 -eq 2 || $1 -eq 9 || $1 -eq 10 ]];then
		echo "$1" > ${UPGRADE_STATE}
	else
		print_status "`${DEBUG}`Please input correct number." "$(warn WARN)"
	fi
	chattr +i ${UPGRADE_STATE}
}

# 读取${UPGRADE_STATE}中记录的状态
function read_state()
{
	read state < ${UPGRADE_STATE}
	echo ${state}
}

# 添加版本信息到/etc/.kyversion
# 从ky_verison.ini中读取追加到/etc/.kyversion
function record_version_info()
{
	version_info_file="${TAR_PATH}/ky_verison.ini"
	version_info="/etc/.kyversion"
	if [[ -f ${version_info_file} ]];then
		cat ${version_info_file} >> ${version_info}
		print_status "`${DEBUG}`Record version information completed." "$(success OK)"
	fi	
}

# 删除版本记录信息
function delete_record_version_info()
{
	local state=$(read_state)
	version_info="/etc/.kyversion"
	if [[ ${state} -eq 10 ]];then
		sed -i '$d' ${version_info}
	fi

}

# 检查是否需要备份日志，如果需要则备份
# 当满足大小则会备份
function log_rotate()
{
	local size_arg=""			#size参数
	local log_size="`echo "10*1024*1024" | bc`"			#log限制默认大小 10M
	local value=""				#数值
	local unit=""				#单位
	local cur_log_size=""			#当前日志大小
	if [[ ! -f "${LOG_CONF}" ]];then
		info "`${DEBUG}`${LOG_CONF}:File does not exist"
		return
	fi
	size_arg=`cat ${LOG_CONF} 2> /dev/null | grep size`
	if [[ ${size_arg} ]];then
		log_size="`echo ${size_arg}| sed 's/  */ /g' | cut -d ' ' -f2 | cut -d '=' -f2`"
	fi
	value="`echo ${log_size} | sed -r 's/([0-9][0-9]*)(.*)/\1/g'`"
	unit="`echo ${log_size} | sed -r 's/([0-9][0-9]*)(.*)/\2/g'`"
	if [[ ${unit^^} == "M" ]];then
		log_size="`echo ${value}*1024*1024 | bc`"
	elif [[ ${unit^^} == "K" ]];then
		log_size="`echo ${value}*1024 | bc`"
	else
		log_size="${value}"
	fi
	cur_log_size="`du -shb ${LOG_FILE}`"	
	cur_log_size="`echo ${cur_log_size} | sed 's/  */ /g' | cut -d ' ' -f1`"
	if [[ ${cur_log_size} -ge ${log_size} ]];then		#需要备份日志
		logrotate -f ${LOG_CONF}
		if [[ $? -ne 0 ]];then
			info "logrotate -f failed."
		else
			print_status "`${DEBUG}`Log dump completed." "$(success OK)"
		fi
	fi
}

# 升级前执行hook脚本
# ${TAR_PATH}下的 prepare_upgrade_hook 目录中
function prepare_upgrade_hook()
{
	local hook_dir="${TAR_PATH}/prepare_upgrade_hook"
	if [[ -d ${hook_dir} ]];then
		for sh_file in `find ${hook_dir} -name "*.sh" | sort -u`
		do
			echo  "Start exec ${sh_file}"
			bash -x ${sh_file}
			if [[ $? -ne 0 ]];then
				print_status "`${DEBUG}`${sh_file} exec failed." "$(warn WARN)"
			fi
		done
	fi	
	print_status "`${DEBUG}`prepare_upgrade_hook is executed." "$(success OK)"
}

# 升级后执行hook脚本
# ${TAR_PATH}下的 post_upgrade_hook 目录中
function post_upgrade_hook()
{
	local hook_dir="${TAR_PATH}/post_upgrade_hook"
	if [[ -d ${hook_dir} ]];then
		for sh_file in `find ${hook_dir} -name "*.sh" | sort -u`
		do
			echo  "Start exec ${sh_file}"
			bash -x ${sh_file}
			if [[ $? -ne 0 ]];then
				print_status "`${DEBUG}`${sh_file} exec failed" "$(warn WARN)"
			fi
		done
	fi
	print_status "`${DEBUG}`post_upgrade_hook is executed." "$(success OK)"
}

# 回退前执行hook脚本
# ${TAR_PATH}下的 prepare_rollback_hook 目录中
function prepare_rollback_hook()
{
	local hook_dir="${STORAGE_PKG_DIR}/prepare_rollback_hook"
	if [[ -d ${hook_dir} ]];then
		for sh_file in `find ${hook_dir} -name "*.sh" | sort -u`
		do
			echo  "Start exec ${sh_file}"
			bash -x ${sh_file}
			if [[ $? -ne 0 ]];then
				print_status "`${DEBUG}`${sh_file} exec failed." "$(warn WARN)"
			fi
		done
		chattr -a ${hook_dir}
		rm -rf ${hook_dir} &> /dev/null		#删除整个目录
	fi	
	print_status "`${DEBUG}`prepare_rollback_hook is executed." "$(success OK)"
}

# 回退后执行hook脚本
# ${TAR_PATH}下的 post_rollback_hook 目录中
function post_rollback_hook()
{
	local hook_dir="${STORAGE_PKG_DIR}/post_rollback_hook"
	if [[ -d ${hook_dir} ]];then
		for sh_file in `find ${hook_dir} -name "*.sh" | sort -u`
		do
			echo  "Start exec ${sh_file}"
			bash -x ${sh_file}
			if [[ $? -ne 0 ]];then
				print_status "`${DEBUG}`${sh_file} exec failed." "$(warn WARN)"
			fi
		done
		chattr -a ${hook_dir}
		rm -rf ${hook_dir} &> /dev/null		#删除整个目录
	fi
	print_status "`${DEBUG}`post_rollback_hook is executed." "$(success OK)"	
}

# 升级前检查执行hook脚本
# ${TAR_PATH}下的 check_hook 目录中
function check_hook()
{
	local hook_dir="${TAR_PATH}/check_hook"
	if [[ -d ${hook_dir} ]];then
		for sh_file in `find ${hook_dir} -name "*.sh" | sort -u`
		do
			echo  "Start exec ${sh_file}"
			bash -x ${sh_file}
			if [[ $? -ne 0 ]];then
				print_status "`${DEBUG}`${sh_file} exec failed." "$(warn WARN)"
			fi
		done
	fi	
	print_status "`${DEBUG}`check_hook is executed." "$(success OK)"
}

# 提交后执行hook脚本
# ${TAR_PATH}下的 post_commit_hook 目录中
function post_commit_hook()
{
        local hook_dir="${STORAGE_PKG_DIR}/post_commit_hook"
        if [[ -d ${hook_dir} ]];then
                for sh_file in `find ${hook_dir} -name "*.sh" | sort -u`
                do
                        echo  "Start exec ${sh_file}"
                        bash -x ${sh_file}
                        if [[ $? -ne 0 ]];then
                                print_status "`${DEBUG}`${sh_file} exec failed." "$(warn WARN)"
                        fi
                done
		chattr -a ${hook_dir}
		rm -rf ${hook_dir} &> /dev/null		#删除整个目录
        fi
        print_status "`${DEBUG}`post_commit_hook is executed." "$(success OK)"
}


# 创建配置文件和必要的目录
function create_conf_file_dir()
{
	mkdir -p ${STORAGE_NEW_PKG} &> /dev/null
	mkdir -p ${STORAGE_OLD_PKG} &> /dev/null
	chattr +a ${STORAGE_OLD_PKG}
	if [[ ! -f ${UPGRADE_STATE} ]];then
                touch ${UPGRADE_STATE}
		echo 0 > ${UPGRADE_STATE}
		chattr +i ${UPGRADE_STATE}
        fi
	if [[ ! -f ${ROLLBACK_POINT} ]];then
		touch ${ROLLBACK_POINT}
		chattr +a ${ROLLBACK_POINT}
	fi
	if [[ ! -f ${NEW_INSTALL_PKG} ]];then
		touch ${NEW_INSTALL_PKG}
		chattr +a ${NEW_INSTALL_PKG}
	fi
	if [[ ! -f ${FORCE_INSTALL} ]];then
		touch ${FORCE_INSTALL}
		chattr +a ${FORCE_INSTALL}
	fi
	if [[ ! -d ${STORAGE_RPM_DB} ]];then
		mkdir -p ${STORAGE_RPM_DB} 
		chattr +a ${STORAGE_RPM_DB}
	fi
}

# 清空新安装的包列表
function empty_new_list()
{
	chattr -a ${NEW_INSTALL_PKG}
	> ${NEW_INSTALL_PKG}	#清空
	chattr +a ${NEW_INSTALL_PKG}
}

# 清空强制安装的包列表
function empty_force_list()
{
        chattr -a ${FORCE_INSTALL}
        > ${FORCE_INSTALL}    #清空
        chattr +a ${FORCE_INSTALL}
}

# 清空${STORAGE_OLD_PKG}
function empty_old_dir()
{
        chattr -a ${STORAGE_OLD_PKG}
        rm -rf ${STORAGE_OLD_PKG}/*    #清空
        chattr +a ${STORAGE_OLD_PKG}
}

# 清空回滚点
function empty_rollback_point()
{
	chattr -a ${ROLLBACK_POINT}
	> ${ROLLBACK_POINT}
	chattr +a ${ROLLBACK_POINT}
}

# 更新（追加）${ROLLBACK_POINT}中记录的回退点
function add_rollback_point()
{
        chattr -a ${ROLLBACK_POINT}
	
	rollback_ID=`yum history | head -n3 | tail -n1  | cut -d '|' -f1 | sed 's/  *//g'`      #得到上次yum操作ID
        if [[ -z `echo ${rollback_ID} | sed -n '/^[0-9][0-9]*$/p'` ]];then
                error "Failed to get the rollback ID"
                exit 21
        fi
	if [[ "${rollback_ID}" =~ ^[0-9][0-9]*$ ]];then
		echo ${rollback_ID} >> ${ROLLBACK_POINT}
	else
		print_status "`${DEBUG}`Please input correct number." "$(warn WARN)"
	fi
        chattr +a ${ROLLBACK_POINT}
}

# 添加本地临时yum源
# $1：本地yun源路径
# yum-config-manager
function add_local_yum_source()
{
	#disable现有的yum source
	SAVE_REPO_LIST="`yum repolist enabled 2> /dev/null | sed '1d' | cut -d ' ' -f1`"
	echo "${SAVE_REPO_LIST}" | awk '{print}' | xargs -I {} yum-config-manager --disable {} &> /dev/null	#失能现有仓库
	if [[ $? -ne 0 ]];then
		print_status "`${DEBUG}`Sources disable failed." "$(warn WARN)"
	fi
	#添加$1,新的本地yum 源
	createrepo -o $1 $1  &> /dev/null
	yum-config-manager --nogpgcheck --add-repo $1 &> /dev/null
	if [[ $? -ne 0 ]];then
                error "`${DEBUG}`Failed to create local Yum source."
		exit 16
        fi
	yum clean all   &> /dev/null
	yum makecache	&> /dev/null
	print_status "`${DEBUG}`Yum source configuration is complete." "$(success OK)"
}

# 删除本地临时yum源
# $1：本地yun源路径
function del_local_yum_source()
{
	local abs_path="`realpath $1`"
	local new_source_name="`echo ${abs_path:1} | sed s@/@_@g`.repo" 

	#删除$1路径本地yum源
	rm -rf "${SOURCE_BASE_DIR}/${new_source_name}" &> /dev/null #删除源配置文件
	rm -rf ${abs_path}/repodata	&> /dev/null		#删除源目录下的repodata文件

	#enable之前被使能的yum source
	if [[ "${SAVE_REPO_LIST}" ]];then
		echo "${SAVE_REPO_LIST}" | awk '{print}' | xargs -I {} yum-config-manager --enable {} &> /dev/null          #使能原有仓库
		#使能失败，请自行使能
	fi
	
	yum clean all   &> /dev/null
	yum makecache	&> /dev/null
	print_status "`${DEBUG}`Yum sources recovery complete." "$(success OK)"
}

# 备份rpm数据库文件
function backup_rpm_db()
{
	/usr/bin/cp -rf ${RPM_DB_DIR}/__db* ${STORAGE_RPM_DB} &> /dev/null
	chattr +a ${STORAGE_RPM_DB}
	print_status "`${DEBUG}`RPM Database backup completed." "$(success OK)"	
}

# 还原rpm数据库文件
function restore_rpm_db()
{
	if [[ ! -d ${STORAGE_RPM_DB} ]];then
		error "The backup rpm database does not exist."
		exit 20
	fi
	chattr -a ${STORAGE_RPM_DB}
	/usr/bin/cp -rf ${STORAGE_RPM_DB}/__db* ${RPM_DB_DIR} &> /dev/null
	rm -rf ${STORAGE_RPM_DB}/__db*				#删除
	rpm --rebuilddb
	print_status "`${DEBUG}`RPM Database restore completed." "$(success OK)"	
}

# 检查root分区的剩余空间大小是否大于
# 即将安装所有包所需空间大小
# $1 : 即将安装所有包的目录
function check_root_free_size()
{
	local root_free_size=""
	local need_size=""

	if [[ -d $1 ]];then
		root_free_size="`df --output='avail' / | tail -n1`"	#KB
		root_free_size="`echo "${root_free_size}*1024" | bc`"	#B

		need_size="`du -shb $1`"				#B 目录
		need_size="`echo ${need_size} | sed -r 's/  */ /g' | cut -d ' ' -f1`"				#B

		if [[ "${need_size}" -gt "${root_free_size}" ]];then
			error "`${DEBUG}`The root partition space is insufficient during the pre-upgrade check."
			exit 24
		fi
	else
		print_status "`${DEBUG}`Directory does not exist" "$(warn WARN)"
	fi	
	print_status "`${DEBUG}`Check the space size of the root partition completed." "$(success OK)"
}

# 保存回滚的脚本到${STORAGE_PKG_DIR}/下面对应的hook目录
function store_user_script()
{
	if [[ -d ${TAR_PATH}/post_rollback_hook ]];then
		mkdir -p ${STORAGE_PKG_DIR}/post_rollback_hook &> /dev/null
		chattr +a ${STORAGE_PKG_DIR}/post_rollback_hook
		/usr/bin/cp -rf ${TAR_PATH}/post_rollback_hook/* ${STORAGE_PKG_DIR}/post_rollback_hook &> /dev/null
	fi
	if [[ -d ${TAR_PATH}/prepare_rollback_hook ]];then
		mkdir -p ${STORAGE_PKG_DIR}/prepare_rollback_hook &> /dev/null
		chattr +a ${STORAGE_PKG_DIR}/prepare_rollback_hook
		/usr/bin/cp -rf ${TAR_PATH}/prepare_rollback_hook/* ${STORAGE_PKG_DIR}/prepare_rollback_hook &> /dev/null
	fi
	if [[ -d ${TAR_PATH}/post_commit_hook ]];then
		mkdir -p ${STORAGE_PKG_DIR}/post_commit_hook &> /dev/null
		chattr +a ${STORAGE_PKG_DIR}/post_commit_hook
		/usr/bin/cp -rf ${TAR_PATH}/post_commit_hook/* ${STORAGE_PKG_DIR}/post_commit_hook &> /dev/null
	fi
}

# 备份需要升级的包和其依赖到${STORAGE_NEW_PKG}
# 然后通过rpmrebuild备份老版本的包
# #参数: $@,需要备份的新包
function backup_check_pkg()
{
	local rpmrebuild_ret=""
	local rpm_path=""
	local rpm_name=""
	local temp_dir="`mktemp -d`"
	local bk_number=""			#需要备份的rpm包数量
	local bk_count=0			#已经备份的rpm包个数

	#检查依赖
	if [[ `yes 'N' | yum install $@ --nogpgcheck 2> /dev/null| grep -E "Is this ok|Nothing to do|安装" | wc -l` -eq 0 ]];then
                #删除本地源
                del_local_yum_source ${SOURCE_PATH}
                error "`${DEBUG}`Upgrade package dependencies are not met or have been installed."
                exit 29
        fi

	rm -rf ${STORAGE_NEW_PKG}/*                     #清空临时存放的新包和其依赖
	#下载需要的包放在临时目录中,主要是得到依赖包名
	yumdownloader --resolve "$@" --destdir ${STORAGE_NEW_PKG}
	if [[ $? -ne 0 ]];then
                del_local_yum_source ${SOURCE_PATH}
                error "`${DEBUG}`Failed to resolve the dependencies of the packages that need to be installed."
                exit 15
        fi
		
	#升级前检查根分区空间是否充足
	check_root_free_size ${STORAGE_NEW_PKG}

	bk_number="`ls ${STORAGE_NEW_PKG}/*.rpm | wc -l`"

	info "A total of ${bk_number} packages need to be backed up"
	info "It may take a long time to back up the software package, please be patient."

	sed -n -r -i '/%dist/d' /etc/rpm/macros.dist					#备份之前去掉多余的后缀ky10
	for file in `ls ${STORAGE_NEW_PKG}`						#备份对应的老包，之后用来回滚
	do
		if [[ ${file} == *.rpm ]];then
			rpm_name="`echo ${file} | sed -r 's/(.*)-(.*)-(.*)/\1/'`"		#得到包名字
			rpm -q ${rpm_name} &> /dev/null		
			if [[ $? -eq 0 ]];then					#系统之前有装这个包
				if [[ `ls "${STORAGE_OLD_PKG}/${rpm_name}-[0-9].*.rpm" 2> /dev/null | wc -l` -eq 0 ]];then	#没有备份,需要备份
					rpmrebuild_ret=`yes '\n' | rpmrebuild -d ${temp_dir} ${rpm_name} 2> /dev/null`
					if [[ $? -ne 0 ]];then
						error "`${DEBUG}`Failed to rebuild the rpm package during upgrade."
						exit 27
					fi
					rpm_path=`echo ${rpmrebuild_ret} | sed 's/  */\n/g' | tail -n1`
					if [[ -f ${rpm_path} ]];then
						/usr/bin/cp -rf ${rpm_path} ${STORAGE_OLD_PKG} 		#移动老版本
						if [[ $a -ne 0 ]];then
							del_local_yum_source ${SOURCE_PATH}
							error "`${DEBUG}`Failed to backup old soft pkg during self-upgrade."
                					exit 17
						fi
					else
						error "`${DEBUG}`The package path obtained by rpmrebuild is wrong."
						exit 31
					fi
				fi
			else											#之前没有装这个包
				echo "${file}" >> ${NEW_INSTALL_PKG}					#添加到新包列表，方便回滚
			fi
			((bk_count++))
			echo "backup ${bk_count} : ${file}"
		fi
	done
	rm -rf ${STORAGE_NEW_PKG}/*			#删除临时存放的新包和其依赖
	rm -rf ${temp_dir}				#删除临时目录
	print_status "`${DEBUG}`Backup rpm package completed." "$(success OK)"
}

# 自升级
# 参数为麒麟升级包绝对路径
function hostos_upgrade_dispatch()
{
	#检查是否存在自升级工具包
	if [[  `find ${SOURCE_PATH} | grep "${SELF_UPGRADE_PKG}-" | wc -l` -lt 1 ]];then
		error "`${DEBUG}`The upgrade tool packag does not exist."
		exit 14
	fi

	#执行升级前hook脚本
	prepare_upgrade_hook	

	#备份rpm数据库
        backup_rpm_db

	#配置仓库
	add_local_yum_source ${SOURCE_PATH}

	#备份自升级工具
	backup_check_pkg ${SELF_UPGRADE_PKG}

	#开始自升级
	yum install ${SELF_UPGRADE_PKG} -y --nogpgcheck
	if [[ $? -ne 0 ]];then
		#升级失败需要回滚
		hostos_upgrade_rollback	
		#删除源
		del_local_yum_source ${SOURCE_PATH}
		#退出
		error "`${DEBUG}`Failed to install the upgrade tool during the automatic upgrade."
		exit 13
	fi

	#更新回滚点
	add_rollback_point

	#删除本地源
	del_local_yum_source ${SOURCE_PATH}
		
	#保存回滚提交脚本
	store_user_script
	
	#执行升级后hook脚本
	post_upgrade_hook	
	
	#修改状态，未提交
	modify_state 9

	print_status "`${DEBUG}`The upgrade tool is self-upgrading completed." "$(success OK)"
}

# 升级检查,检查安装依赖
# 参数为麒麟升级包绝对路径
function hostos_upgrade_check()
{
	local rpms_name=""

	#执行check_hook脚本
	check_hook

	#配置仓库
        add_local_yum_source ${SOURCE_PATH}
	
	rpms_name=`find ${SOURCE_PATH} -name "*.rpm" | awk '{print}'  | xargs -I {}  basename {} | sed -r 's/(.*)-(.*)-(.*)/\1/g'`

	#检查是不是普通软件包中混入了升级工具包
	if [[ `echo ${rpms_name} | grep ${SELF_UPGRADE_PKG} | wc -l` -gt 0 && `echo ${rpms_name} | sed -r 's/ /\n/g' | wc -l` -gt 1 ]];then              #普通软件包中不能包含升级工具包
		del_local_yum_source ${SOURCE_PATH}
                error "`${DEBUG}`Self-upgrade package included in the software package."
                exit 31
        fi
	#检查依赖是否满足
	if [[ `yes 'N' | yum install ${rpms_name} --nogpgcheck 2> /dev/null| grep -E "Is this ok|安装" | wc -l` -eq 0 ]];then
		yum install ${rpms_name} --nogpgcheck 2>> ${LOG_FILE} > /dev/null
		#删除本地源
	        del_local_yum_source ${SOURCE_PATH}
                error "`${DEBUG}`Upgrade package dependencies are not met or have been installed."
                exit 29
	fi
	
	#删除本地源
        del_local_yum_source ${SOURCE_PATH}
}

# 强制安装
# 可能有的包需要强制安装
function force_install()
{
	local temp_dir=""			#临时存放需要强制安装的包
	local force_list="${TAR_PATH}/force_upgrade_rpms.ini"	#需要强制安装的包列表
	if [[ ! -f ${force_list} ]];then
		return
	fi
	temp_dir="`mktemp -d`"
	sed -i '/^$/d' ${force_list} 			#清空空行
	if [[ `cat ${force_list} | wc -l` -gt 0 ]];then
		while read rpm_file
		do
			if [[ `find ${SOURCE_PATH}/${rpm_file}* | wc -l` -gt 0 ]];then
				/usr/bin/cp ${SOURCE_PATH}/${rpm_file}* ${temp_dir}
			else
				info "`${DEBUG}`File ${SOURCE_PATH}/${file} does not exist"
			fi
		done < ${force_list}
		rpm -Uvh --force ${temp_dir}/*.rpm
		if [[ $? -ne 0 ]];then
			error "`${DEBUG}`Force installation error"
			exit 33
		fi
		ls ${temp_dir}/*.rpm |xargs -I {} basename {}  >> ${FORCE_INSTALL}		#记录强制安装包
		print_status "`${DEBUG}`Mandatory installation is complete." "$(success OK)"
	fi
	rm -rf ${temp_dir}
}

# 升级
# 参数为麒麟升级包绝对路径
function hostos_upgrade_install()
{
	local rpms_name=""
	#执行升级前hook脚本
        prepare_upgrade_hook

	#备份rpm数据库
	backup_rpm_db

	#配置仓库
        add_local_yum_source ${SOURCE_PATH}

	#备份软件包
	rpms_name=`find ${SOURCE_PATH} -name "*.rpm" | awk '{print}'  | xargs -I {}  basename {} | sed -r 's/(.*)-(.*)-(.*)/\1/g'`
	if [[ `echo ${rpms_name} | grep ${SELF_UPGRADE_PKG} | wc -l` -gt 0 ]];then		#普通软件包中不能包含升级工具包
		del_local_yum_source ${SOURCE_PATH}
                error "`${DEBUG}`Self-upgrade package included in the software package."
		exit 31
        fi

	backup_check_pkg ${rpms_name}
	
	#安装之前设置为半成品状态
	modify_state 1

	#首先强制安装
	force_install

	modify_state 2
	if [[ `yes 'N' | yum install ${rpms_name} --nogpgcheck 2> /dev/null| grep "Nothing to do" | wc -l` -eq 0 ]];then	
		#开始升级
        	yum install ${rpms_name} -y --nogpgcheck
		if [[ $? -ne 0 ]];then
	                #删除源
	                del_local_yum_source ${SOURCE_PATH}
	                #退出
	                error "`${DEBUG}`Failed to upgrade the rpm package during upgrade."
	                exit 28
	        fi
		#记录回滚点
		add_rollback_point
	fi
        if [[ `yes 'N' | yum downgrade ${rpms_name} --nogpgcheck 2>&1 | grep -E "Nothing to do|No packages marked for downgrade" | wc -l` -eq 0 ]];then	
		#开始降级
		yum downgrade ${rpms_name} -y --nogpgcheck
 		if [[ $? -ne 0 ]];then
        	        #删除源
        	        del_local_yum_source ${SOURCE_PATH}
        	        #退出
        	        error "`${DEBUG}`Failed to downgrade rpm package."
        	        exit 36 
        	fi
                #记录回滚点
                add_rollback_point
        fi

	#保存回滚提交脚本
	store_user_script

        #删除本地源
        del_local_yum_source ${SOURCE_PATH}
	
	#最后记录信息
	record_version_info
        
	#执行升级后hook脚本
        post_upgrade_hook

        #修改状态，未提交
        modify_state 10

        print_status "`${DEBUG}`The upgrade common pkg is completed." "$(success OK)"
}

# 回退
# 无参数
function hostos_upgrade_rollback()
{
	local rollback_ID=""
	local state=""			#等于0时报错，无法回滚
	local rpms_name=""
	
	state=$(read_state)
	if [[ ${state} -eq 0 ]];then
		error "Not upgraded and cannot be rolled back."
		exit 30
	fi

	chattr -a ${STORAGE_OLD_PKG}

	#执行回退前升级脚本
	prepare_rollback_hook

	#配置本地源
	add_local_yum_source ${STORAGE_OLD_PKG}

	if [[ -f "${OLD_VERSION}" ]];then			#4以下(包含)版本进行了升级
		modify_state 9
		add_rollback_point				#得到上次的回滚点
		rm -rf ${OLD_VERSION} &> /dev/null		#删除文件
	fi

	if [[ ${state} -ge 2 ]];then	
		#回滚
		for rollback_ID in `tac ${ROLLBACK_POINT}` #得到上次yum操作ID
		do
			yum history undo ${rollback_ID} -y --nogpgcheck					#开始回滚
			if [[ $? -ne 0 ]];then
				error "`${DEBUG}`Rollback failed."
				exit 22
			fi
		done
	fi
	if [[ ${state} -ge 1 ]];then
		if [[ `cat ${FORCE_INSTALL} | wc -l` -gt 0 ]];then
			rpms_name="`cat ${FORCE_INSTALL} | sed '/^$/d' | sed -r 's/(.*)-(.*)-(.*)/\1/g'`"
			yum remove ${rpms_name} -y 
			if [[ $? -ne 0 ]];then
				error "`${DEBUG}`Failed to uninstall the forced installation package."
				exit 34
			fi
			rpms_name="`echo ${rpms_name} | sed 's/ /$|/g'`"
			if [[ `ls ${STORAGE_OLD_PKG} | sed -r 's/(.*)-(.*)-(.*)/\1/g' |grep -E "${rpms_name}" | wc -l` -gt 0 ]];then	##不一定存在，存在则说明之前系统安装了,需要装回去
				yum install ${rpms_name} -y --nogpgcheck 
				if [[ $? -ne 0 ]];then
					error "`${DEBUG}`The installation of the package that was replaced or uninstalled at force install failed."
					exit 35
				fi
			fi
		fi
	fi
	
	#删除本地源
	del_local_yum_source ${STORAGE_OLD_PKG}

	#数据库复原
	restore_rpm_db 								#恢复rpm数据库

	#删除${STORAGE_OLD_PKG}/*,清空${NEW_INSTALL_PKG}
	empty_old_dir				#删除${STORAGE_OLD_PKG}/*
	empty_new_list				#清空新包列表
	empty_force_list			#清空强制安装列表
	empty_rollback_point			#清空回滚点

	#执行回退后升级脚本
	post_rollback_hook
	
	#删除上次升级的版本信息记录,在状态改变之前
	delete_record_version_info

	modify_state 0          						#恢复初始状态

	chattr -a ${STORAGE_OLD_PKG}

	print_status "`${DEBUG}`Upgrade rollback completed." "$(success OK)"
}

# 提交
# 参数为麒麟升级包绝对路径
function hostos_upgrade_commit()
{
	#直接删除OLD文件夹中的所有内容和清空${NEW_INSTALL_PKG},除之后无法回滚
	empty_old_dir                           #删除${STORAGE_OLD_PKG}/*
        empty_new_list                          #清空新包列表
	empty_force_list			#清空强制安装列表
	empty_rollback_point                    #清空回滚点

	#删除rpm数据库
	rm -rf ${STORAGE_RPM_DB}/* &> /dev/null

	if [[ -f "${OLD_VERSION}" ]];then
		rm -rf ${OLD_VERSION} &> /dev/null		
	fi
	
	#提交后执行脚本
	post_commit_hook
	
	modify_state 0		#恢复初始状态

	print_status "`${DEBUG}`Upgrade commit completed." "$(success OK)"
}

# 是否需要重启
# 参数为麒麟升级包绝对路径
function hostos_upgrade_needreboot()
{
	if [[ -z ${SOURCE_PATH} ]];then
		error "`${DEBUG}`The interface input parameters are incorrect."
		exit 10
	fi
	if [[ `find  ${SOURCE_PATH} | grep "${NEED_REBOOT_PKG}" | wc -l` -gt 0 ]];then	#需要重启
		exit 1
	else
		exit 0									#不需要重启
	fi	
}

# 设置
# 参数为yes/no
function hostos_upgrade_setrebootsym()
{
	local set_arg=${1,,}
	if [[ "${set_arg}" != "no" && "${set_arg}" != "yes" ]];then
		#error "--setrebootsym Incidental parameter error" #附带参数错误
		error "`${DEBUG}`The interface input parameters are incorrect."
		echo "Usage:          --setrebootsym yes|no"	  #打印用法
		exit 10
	fi
}

# 安全加固
# 参数为麒麟升级包绝对路径
function hostos_upgrade_sysenhance()
{
	:
}

# 保存升级包目录的路径
function save_path()
{
	if [[ ! -d "$1" ]];then
		error "`${DEBUG}`The patch directory for input parameters does not exist."
		exit 11
	fi
	TAR_PATH="`realpath $1`"
	SOURCE_PATH="${TAR_PATH}/source"
}

# 检测是否存在冲突
function check_conflict()
{
	((EXIST_CONFLICT++))
	if [[ "${EXIST_CONFLICT}" -gt 1 ]];then 
		error "`${DEBUG}`The interface input parameters are incorrect."		
		exit 10
	fi
	FUNC="$1"
}

# diff : exit 64
check_partition_and_fstab_same()
{
	if [[ ! -f "/etc/fstab" ]];then
		error "`${DEBUG}`/etc/fstab does not exist."
		exit 26
	fi
	while read line 
	do
		fs_type=`echo "${line}" | sed 's/  */ /g' | cut -d ' ' -f3`
		if [[ -n "${line}" && "${line::1}" != "#" && "${fs_type}" != "swap" && "${line::5}" != "tmpfs" ]];then
			if [[ "${line::5}" == "UUID=" ]];then			#UUID开头
				local uuid=`echo ${line} | sed -r 's/(.*)UUID=([0-9a-zA-Z-]+)(.*)/\2/'`		#得到UUID
				local dev_name=`blkid -U "${uuid}"`						#得到设备名
			else							#设备名开头
				local dev_name=`echo "${line}" | sed 's/  */ /g'| cut -d ' ' -f1`
			fi

			local real_mount_point=`df --output='target' "${dev_name}" | tail -n1`           	#真实的挂载点
			local fstab_mount_point=`echo "${line}" | sed 's/  */ /g' | cut -d ' ' -f2`             #fstab中的挂载点

			if [[ "${fstab_mount_point}" == "none" ]];then
				continue
			fi
			if [[ "${real_mount_point}" != "${fstab_mount_point}" ]];then
				error "`${DEBUG}`In the pre-upgrade check, the partitions mounted to the specified directory differ from those mounted to the fstab root directory."
		                exit 25
			fi
		fi
	done < /etc/fstab
	print_status "`${DEBUG}`The partitions mounted to the specified directory" "$(success OK)"
}

# 升级前检查
function pre_upgrade_check()
{
	local state=""
	#用户信息检查
	if [[ "${USER}" != "root" ]];then
		error "`${DEBUG}`Non-super user running."
		exit 12
	fi
	
	#确保目录、文件存在
	create_conf_file_dir

	#检查根目录上挂载的分区是否与fstab中相同，不同则返回
	check_partition_and_fstab_same

	#检查boot分区是否正确挂载,上面的函数已经检查了大多数挂载的情况
	if [[ `mount  | grep  " /boot " | wc -l` -lt 1 ]];then
		error "`${DEBUG}`The boot partition is incorrectly mounted during the pre-upgrade check."
		exit 60
	fi

	#检查必要参数是否存在
	if [[ "${FUNC}" !=  "hostos_upgrade_rollback" && "${FUNC}" !=  "hostos_upgrade_commit" ]];then		
		if [[ -z "${TAR_PATH}" ]];then									#除了这两个，其它的需要${TAR_PATH}不为空
			error "`${DEBUG}`The upgrade package path is not specified."
	                exit 18
		fi
	fi
	if [[ "${FUNC}" ==  "hostos_upgrade_dispatch" || "${FUNC}" ==  "hostos_upgrade_install" ]];then
		state=$(read_state)		#检查状态
		if [[ ${state} -ne 0 ]];then
			error "`${DEBUG}`There are packages that have not been submitted since the last installation"
			exit 19
		fi
		if [[ `find ${SOURCE_PATH} -name "*.rpm" |wc -l` -eq 0 ]];then
			error "Specify the destination folder to be empty"
			exit 23
		fi
	fi
	#检查根分区大小是否不足,
	
	#检查日志空间是否不足,

}	

# 打印最后的$1次日志
# 默认打印1次
# 高级用法: print_log 2 1，暂时未开发
function print_log()
{
	local line=""
	local gzip_file=""
	if [[ -z "${PRINT_LOG_NUM}" ]];then
		PRINT_LOG_NUM=1
	fi
	if [[ ! "${PRINT_LOG_NUM}" =~ ^[0-9][0-9]*$  ]];then
		error "`${DEBUG}`The number of log prints is not a number"
		exit 32
	fi
	if [[ ! -f "${LOG_FILE}" ]];then
	       	if [[ `ls ${LOG_FILE}* | wc -l` -gt 0 ]];then					
			gzip_file=`ls ${LOG_FILE}* | tail -n1`
			line=`gzip -dc ${gzip_file} | grep -n "${START_EXEC_FLAG}" | tail -n${PRINT_LOG_NUM} | head -n1 |cut -d ':' -f1`
			gzip -dc ${gzip_file} | sed -n "${line},\$p"
		else
			info "No log."
			exit 0
	       	fi
	else
		line=`cat "${LOG_FILE}" | grep -n "${START_EXEC_FLAG}" | tail -n${PRINT_LOG_NUM} | head -n1 |cut -d ':' -f1`
		if [[ "${line}" ]];then
			sed -n "${line},\$p" ${LOG_FILE}
		fi
	fi
}

function show_help()
{
        echo "HostOS升级工具,用法："
        echo
        echo "必选参数对长短选项同时适用。"
        echo "          -h | --help 显示帮助"
        echo "          -path 升级包目录 指定麒麟升级包路径"
        echo "          --dispatch 升级分发"
        echo "          --check 升级检查"
        echo "          --install 升级"
        echo "          --rollback 回退"
        echo "          --commit 提交"
        echo "          --print [n] 打印日志信息,最后执行的n次日志,默认为1次"
}

##################### EXEC CONTENT #################################
EXEC_ARG="$0 $@"
if [[ $# -lt 1 ]];then 								#至少需要一个参数,如--commit
	error "`${DEBUG}`The interface input parameters are incorrect."
	exit 10 
fi

while [[ -n $1 ]]
do
        case $1 in 
	       	-h|--help)
			check_conflict "show_help"
        		;;
        	--dispatch)				#自升级
        	        check_conflict "hostos_upgrade_dispatch"
			;;
        	--check)				#检查安装依赖
                        check_conflict "hostos_upgrade_check"
			;;
                --install)				#升级
                        check_conflict "hostos_upgrade_install"
			;;
                --rollback)				#回滚
                        check_conflict "hostos_upgrade_rollback"
			;;
        	--commit)				#提交
                        check_conflict "hostos_upgrade_commit"
                        ;;
                --print)
			PRINT_LOG_NUM="$2"
               		check_conflict "print_log" 
        		shift
                        ;;
                -path)
                        save_path $2
        		shift
                        ;;
		--) 	
			break
		       	;;
        	*) error "`${DEBUG}`The interface input parameters are incorrect." 
        		exit 10
        		;;
        esac
	shift
done

if [[ "${EXIST_CONFLICT}" -eq 1 ]];then
	if [[ "${FUNC}" ==  "show_help" ]];then			#show_help函数不用做检查
		eval "${FUNC}"						
		exit 0
	fi
	if [[ "${FUNC}" == "print_log" ]];then
		eval "${FUNC} ${PRINT_LOG_NUM}"	
		exit 0
	fi

	exec  &> >(while read line ; do echo -e "$(date +"%Y:%m:%d - %H:%M:%S")：$line" ; done | tee -a ${LOG_FILE} | sed -r 's/(.*)：(.*)/\2/g')
	echo "${START_EXEC_FLAG} : ${EXEC_ARG}"							#打印执行参数信息
	pre_upgrade_check					#升级前检查
	eval "${FUNC}"						#执行函数
#	logrotate ${LOG_CONF}						#查看日志是否需要切割备份
else
	error "`${DEBUG}`The interface input parameters are incorrect."
  	exit 10
fi
