#!/bin/sh
# Copyright (C) 2000-2017 Synology Inc. All rights reserved.

. "$(dirname $0)"/common
. "$(dirname $0)"/lib

PKGVERSION=`/bin/get_key_value /var/packages/$PACKAGE_NAME/INFO version`
PID_FILE=/run/mysqld/mysqld10.pid
DATABASE_HAS_BACKUPED_FLAGS="/root/.mariadb_database_has_backedup"

################################
# Hook
################################
HOOK_MARIADB_CHPW=/usr/local/libexec/mariadb10_chpw
HOOK_MARIADB_START=/usr/local/libexec/mariadb10_start
HOOK_MARIADB_STOP=/usr/local/libexec/mariadb10_stop

################################
# Backup Setting
################################
DO_NETBKP_CANCEL_TMP_FILE="/tmp/do_netbkp_cancel.chk"
LOCALBKP_CANCEL_TMP_FILE="/tmp/do_localbkp_cancel.chk"
MYSQL_RCVR_DB_CONFLICT_LIST="/tmp/mysql_rcvr_db_conflict_list.tmp"
MYSQL_RCVR_DB_NEW_LIST="/tmp/mysql_rcvr_db_new_list.tmp"
MYSQLDB_ESSENTIAL_TABLE="user db host tables_priv columns_priv func"
MYSQL_FAIL=1
CANCEL_RET=2

UpgradeDatabase()
{
	local _DBVERSION=`cat ${DATADIR}/VERSION`
	local _PKGVERSION=`echo $PKGVERSION | sed 's|\(.*\)-[0-9]*|\1|'`
	local _DATADIR="`readlink ${DATADIR}`"

	if [ x${_DBVERSION} = x${_PKGVERSION} ]; then
		logger -p info "MariaDB: UpgradeDatabase: The same version, skip to upgrade ..."
		return 0
	fi

	# backup the database
	if [ ! -f $DATABASE_HAS_BACKUPED_FLAGS ]; then
		/bin/cp -a ${_DATADIR} "`dirname ${_DATADIR}`/.mysql.`/bin/date +%s`"
		/bin/chown mysql:mysql ${_DATADIR}
		touch $DATABASE_HAS_BACKUPED_FLAGS
	fi

	# enter single user mode to bypass authentication
	$MYSQL_SERVER status && $MYSQL_SERVER stop
	if ! StartSingleUser; then
		logger -p err "MariaDB: UpgradeDatabase: Failed to enter single user mode"
		return 1
	fi

	if ! env LC_ALL=en_US.utf8 "$BIN_MYSQL_UPGRADE" --force -u root; then
		if [ "$1" != "dont_show_msg" ]; then
			synodsmnotify @administrators dsmnotify:system_event mariadb:need_upgrade
		fi
		logger -p err "MariaDB: UpgradeDatabase: Failed to upgrade MySQL"
	else
		#TODO: remove backup files
		#rm $DATABASE_HAS_BACKUPED_FLAGS
		echo ${PKGVERSION} | sed 's|\(.*\)-[0-9]*|\1|' > ${DATADIR}/VERSION
		logger -p err "MariaDB: UpgradeDatabase: Upgrade database successfully"
	fi

	# leave single mode
	$MYSQL_SERVER stop || exit 1

	return 0
}

CheckCancellingAction()
{
	Ret=0;

	# check if user has canceld the backup task
	if [ $1 = "netbkp" ]; then
		if [ -f "${DO_NETBKP_CANCEL_TMP_FILE}" ]; then
			Ret=${CANCEL_RET}
		fi
	elif [ $1 = "localbkp" ]; then
		if [ -f "${LOCALBKP_CANCEL_TMP_FILE}" ]; then
			Ret=${CANCEL_RET}
		fi
	else
		Ret=${MYSQL_FAIL}
	fi

	return $Ret
}

BackupDatabase()
{
	# Dump MySQL database
	Ret=0
	DST_PATH=$1
	BKPTYPE=$2

	# Start dump
	for i in `find ${DATADIR}/* -type d`
	do
		# check if user has canceld the backup task
		CheckCancellingAction ${BKPTYPE}
		Ret=$?
		if [ ${Ret} -ne 0 ]; then
			return ${Ret}
		fi

		f=`basename ${i}`
		f_tmp=`echo $f | sed -e 's/@00/\\\\x/g'`
		f_tmp=`echo -e "$f_tmp"`
		# skip mysql and test databases
		if [ "${f_tmp}" = "mysql" -o "${f_tmp}" = "test" -o "${f_tmp}" = "performance_schema" ]; then
			continue
		fi

		# dump database
		rm -f "$DST_PATH/${f}.sql"
		"$BIN_MYSQL_MYSQLDUMP" --events --routine --trigger --single-transaction --database "${f_tmp}" > "$DST_PATH/${f}.sql"

		# If fail to dump, return ${MYSQL_FAIL}
		if [ $? -ne 0 ]; then
			return ${MYSQL_FAIL}
		fi

		# check if user has canceld the backup task before excuting gzip command
		CheckCancellingAction ${BKPTYPE}
		Ret=$?
		if [ ${Ret} -ne 0 ]; then
			return ${Ret}
		fi

		rm -f "$DST_PATH/${f}.sql.gz"
		gzip -f  "$DST_PATH/${f}.sql"
	done

	return $Ret
}

TablesSet()
{
	db="$1"
	ext=$2
	set_type=$3

	`echo "use '${db}'; show tables;" | "$BIN_MYSQL" > /tmp/table_list.${ext}`
	cat /tmp/table_list.${ext} | while read line
	do
		if [ "${line}" = "Tables_in_${db}" ]; then
			continue;
		fi
		if [ ${set_type} = "rename" ]; then
			echo "use '${db}'; rename table \`${line}\` to \`${line}_${ext}\`;" | "$BIN_MYSQL"
		elif [ ${set_type} = "recover" ]; then
			ori_table=${line%_${ext}}
			echo "use '${db}'; rename table \`${line}\` to \`${ori_table}\`;" | "$BIN_MYSQL"
		elif [ ${set_type} = "drop_ori" ]; then
			# check if ${line} is the original table
			echo "${line}" | grep -q "_${ext}"
			if [ $? -eq 0 ]; then
				echo "use '${db}'; drop table \`${line}\`;" | "$BIN_MYSQL"
			fi
		elif [ ${set_type} = "drop_new" ]; then
			# check if ${line} is the original table
			echo "${line}" | grep -q "_${ext}"
			if [ $? -eq 0 ]; then
				continue;
			fi
			echo "use '${db}'; drop table \`${line}\`;" | "$BIN_MYSQL"
		fi
	done
	rm /tmp/table_list.${ext}
}

DBSet()
{
	db="$1"
	ext=$2
	set_type=$3
	Ret=0

	case $3 in
		rename)
			RenameDB "${db}" "${db}_${ext}"
			Ret=$?
			;;
		recover)
			RenameDB "${db}_${ext}" "$db"
			Ret=$?
			;;
		drop_ori)
			ori_db=${db}_${ext}
			echo "drop database if exists \`${ori_db}\`;" | "$BIN_MYSQL"
			Ret=$?
			;;
		drop_new)
			echo "drop database if exists \`${db}\`;" | "$BIN_MYSQL"
			Ret=$?
			;;
	esac

	return $Ret
}

RenameDB()
{
	ori_dbname=$1
	new_dbname=$2
	Ret=0

	echo "drop database if exists \`${new_dbname}\`;" | "$BIN_MYSQL"
	echo "create database \`${new_dbname}\`;" | "$BIN_MYSQL"
	Ret=$?
	if [ ${Ret} -ne 0 ]; then
		logger -p 0 "MariaDB: RenamDB: Failed to create database ${new_dbname}"
		return ${Ret}
	fi
	
	"$BIN_MYSQL_MYSQLDUMP" --events --routine --trigger --max-allowed-packet=512M -- ${ori_dbname} | "$BIN_MYSQL" -- ${new_dbname}
	Ret=$?
	if [ ${Ret} -ne 0 ]; then
		logger -p 0 "MariaDB: RenameDB: Failed to rename db from ${ori_dbname} to ${new_dbname}"
		echo "drop database if exists \`${new_dbname}\`;" | "$BIN_MYSQL"
		return ${Ret}
	fi
	
	echo "drop database if exists \`${ori_dbname}\`;" | "$BIN_MYSQL"
	return $Ret
}

RestoreDatabase()
{
	# Dump MySQL database
	Ret=0
	OVERWRITE_DB=${2}

	# MySQL database has not been enabled.
	if [ ! -d "${DATADIR}" ]; then
		return ${MYSQL_FAIL}
	fi

	time=`date +%s`
	RESTORE_RET=0
	echo -n > ${MYSQL_RCVR_DB_CONFLICT_LIST}
	echo -n > ${MYSQL_RCVR_DB_NEW_LIST}
	echo $1
	for i in `find "$1"/*.sql.gz -type f`
	do
		DB_NAME=`basename "$i"`
		DB_NAME=${DB_NAME%.sql.gz}

		DB_NAME_TMP=`echo $DB_NAME | sed -e 's/@00/\\\\x/g'`
		DB_NAME_TMP=`echo -e "$DB_NAME_TMP"`
		# if database is conflict, need to rename its tables
		if [ -d "${DATADIR}/${DB_NAME}" ]; then
			if [ ${OVERWRITE_DB} = "yes" ]; then
				DBSet "${DB_NAME_TMP}" ${time} "rename"
				if [ $? -ne 0 ]; then
					Ret=${MYSQL_FAIL};
					break;
				fi
				# record conflict restored db to temp file
				echo "${DB_NAME_TMP}" >> ${MYSQL_RCVR_DB_CONFLICT_LIST}
			else
				continue;
			fi
		else
			# record new restored db to temp file
			echo "${DB_NAME_TMP}" >> ${MYSQL_RCVR_DB_NEW_LIST}
		fi

		gunzip -c "$1/${DB_NAME}.sql.gz" | "$BIN_MYSQL"

		# check if database is restored sucessfully
		if [ $? -ne 0 ]; then
			Ret=${MYSQL_FAIL};
			break;
		fi
	done

	# Check If the restoration task is successful,
	# If failed, drop all new database and recover all tables in all conflict databases
	# If successful, drop all original tables in all conflct databases
	if [ ${Ret} -eq 0 ]; then
		# drop all original tables
		cat ${MYSQL_RCVR_DB_CONFLICT_LIST} | while read line
		do
			DBSet "${line}" ${time} "drop_ori"
		done
	else
		# drop all new databases
		cat ${MYSQL_RCVR_DB_NEW_LIST} | while read line
		do
			echo "drop database if exists ${line}" | "$BIN_MYSQL"
		done

		cat ${MYSQL_RCVR_DB_CONFLICT_LIST} | while read line
		do
			DBSet "${line}" ${time} "drop_new"
			DBSet "${line}" ${time} "recover"
		done
	fi

	rm -f ${MYSQL_RCVR_DB_CONFLICT_LIST}
	rm -f ${MYSQL_RCVR_DB_NEW_LIST}

	return $Ret
}

CallChpwHooks()
{
	# Execute hook files
	local user="$1"
	local pwfile="$2"
	local ret=0
	local hook
	for hook in "$HOOK_MARIADB_CHPW/"*; do
		if [ ! -x "$hook" ]; then
			continue
		fi
		if ! "$hook" "$user" "$pwfile"; then
			logger -p 0 "MariaDB: CallChpwHooks: mariadb_chpw hook failed on '$hook' with dbuser '$user'"
			ret=1
		fi
	done
	return $ret
}

notifyIfPasswordIsEmpty()
{
	local dbpassEmptyNotification=""
	if "$BIN_MYSQL" -uroot -e "exit" >/dev/null 2>&1; then
		if [ "$SYNOPKG_DSM_VERSION_BUILD" -lt 22301 ]; then
			dbpassEmptyNotification="$dbpass_empty"
		else
			dbpassEmptyNotification="SYNO.SDS.MARIADB10.Instance:notification:dbpass_empty"
		fi
		/usr/syno/bin/synodsmnotify -e false @administrators dsmnotify:system_event "$dbpassEmptyNotification" "MariaDB 10" SYNO.SDS.MARIADB10.Instance
	fi
}

CallHooks()
{
	local hook_path="$1"
	local hook
	local ret=0
	for hook in "$hook_path/"*; do
		if [ ! -x "$hook" ]; then
			continue
		fi
		if ! "$hook"; then
			logger -p 0 "MariaDB: CallHooks: $(basename "$hook_path") hook failed"
			ret=1
		fi
	done
	return $ret
}

ResetPassword()
{
	touch /tmp/mysql_init.$$
	chown mysql:mysql /tmp/mysql_init.$$
	chmod 600 /tmp/mysql_init.$$
# NO_AUTO_CREATE_USER is default since MariaDB 10.1.7
	cat > /tmp/mysql_init.$$ <<EOF
use mysql;
DELETE FROM user WHERE user = 'root' AND host != 'localhost';
CREATE USER IF NOT EXISTS 'root'@'localhost';
SET PASSWORD FOR 'root'@'localhost' = PASSWORD('');
GRANT ALL PRIVILEGES ON *.* TO 'root'@'localhost' WITH GRANT OPTION;
EOF
	$MYSQL_SERVER start --init-file=/tmp/mysql_init.$$
	rm /tmp/mysql_init.$$
	CallChpwHooks root /dev/null
	return $?
}

case $1 in
	start)
		DO_LINK
		$MYSQL_SERVER status && exit 0
		ChkExecEnv
		if [ singleuser = "$2" ]; then
			# start for backup process
			StartSingleUser || exit 1
		else
			# normal start
			UpgradeDatabase || exit 1
			if ! $MYSQL_SERVER start; then
				/usr/syno/bin/synodsmnotify @administrators dsmnotify:system_event "Failed to start MariaDB."
			else
				CallHooks "$HOOK_MARIADB_START"
				if [ no_notification != "$3" ]; then
					notifyIfPasswordIsEmpty
				fi
			fi
			$MYSQL_SERVER status
		fi
		;;
	stop)
		ChkExecEnv
		if $MYSQL_SERVER status; then
			$MYSQL_SERVER stop
		fi
		CallHooks "$HOOK_MARIADB_STOP"
		;;
	restart)
		DO_LINK
		ChkExecEnv
		UpgradeDatabase || exit 1
		$MYSQL_SERVER restart
		;;
	reload)
		$MYSQL_SERVER reload
		;;
	status)
		DO_LINK
		$MYSQL_SERVER status
		;;
	chpw)
		CallChpwHooks "$2" "$3"
		exit $?
		;;
	resetpassword)
		$0 stop
		ResetPassword
		Ret=$?
		$0 stop
		exit $Ret
		;;
	rebuild)
		mysqlPath=$(readlink "${DATADIR}")
		[ -d "${mysqlPath}" ] && rm -rf "${mysqlPath}"
		RebuildDatabase
		;;
	backupdb)
		if [ -z "$2" -o -z "$3" ]; then
			echo "Usage: $1 backupdb destination_path backup_type[netbkp|localbkp]"
			exit ${MYSQL_FAIL}
		fi
		BackupDatabase "$2" $3
		Ret=$?
		exit $Ret
		;;
	restoredb)
		if [ -z "$2" -o -z "$3" ]; then
			echo "Usage: $1 restoredb source_path overwrite[yes|no]"
			exit ${MYSQL_FAIL}
		fi
		RestoreDatabase "$2" $3
		Ret=$?
		exit $Ret
		;;
esac
