#!/bin/sh

# env variable LDAP_BKP_* are set by caller. see ldap_server_backup_set_env.c

PATH="$PATH:/bin:/usr/bin:/usr/syno/bin"
SERVICECFG="/usr/syno/sbin/synoservicecfg"
SLAP_DEFAULT_DIR="/usr/syno/etc.defaults/openldap"
SLAP_CONF_DIR="/usr/syno/etc/openldap"
LDAP_PKG_NAME="DirectoryServer"
LDAP_PKG_DIR="/var/packages/$LDAP_PKG_NAME"

BKP_PREFIX=${LDAP_BKP_PREFIX:-synoldap}
BKP_DEST_DIR=$LDAP_BKP_DEST_DIR/${LDAP_PKG_NAME}Backup

# util functions
CheckPackage() {
	local pkg_dir=${1:-$LDAP_PKG_DIR}
	[ -d "$pkg_dir/target/" -a -r "$pkg_dir/INFO" ]
}
WaitProcDead() { # <proc name> [time wait=5]
	local procname=$1
	local i=${2:-5}
	while [ $i -gt 0 ]; do
		if pidof "$procname" > /dev/null 2>&1; then
			return 0
		fi
		sleep 1
		i=`expr $i - 1`
	done
	return 1
}
CompareVersion() { # <ver1> <op> <ver2>
	local ver1=$1 ver_op=$2 ver2=$3 op= inv_op=
	local ver1_major=  ver1_minor= ver1_build=
	local ver2_major=  ver2_minor= ver2_build=

	if [ -z "$ver1" -o -z "$ver2" -o -z "$ver_op" ]; then
		echo "bad parameter" >&2
		return 1
	fi

	case "$ver_op" in
		-le) op=-lt inv_op=-gt ;;
		-ge) op=-gt inv_op=-lt ;;
		*)
			echo "unknown op: $ver_op" >&2
			return 1 ;;
	esac

	# getting major, minor, build
	ver1_major=`echo ${ver1} | sed 's/^\([0-9]*\).*/\1/'`
	ver1_minor=`echo ${ver1} | sed 's/^[0-9]*.\([0-9]*\).*/\1/'`
	ver1_build=`echo ${ver1} | sed 's/^[0-9]*.[0-9]*.\([0-9]*\)/\1/'`
	ver2_major=`echo ${ver2} | sed 's/^\([0-9]*\).*/\1/'`
	ver2_minor=`echo ${ver2} | sed 's/^[0-9]*.\([0-9]*\).*/\1/'`
	ver2_build=`echo ${ver2} | sed 's/^[0-9]*.[0-9]*.\([0-9]*\)/\1/'`

	if [ "$ver1_major" $inv_op "$ver2_major" ]; then
		return 1;
	elif [ "$ver1_major" $op "$ver2_major" ]; then
		return 0;
	fi
	if [ "$ver1_minor" $inv_op "$ver2_minor" ]; then
		return 1;
	elif [ "$ver1_minor" $op "$ver2_minor" ]; then
		return 0;
	fi
	if [ "$ver1_build" $inv_op "$ver2_build" ]; then
		return 1;
	else
		return 0;
	fi
}

LOCK_FILE=/tmp/.synoldap_backup.lock
GetLock() { # <fd> [retry=5]
	local i=0 fd=$1 retry=${2:-5}

	while [ $i -lt $retry ]; do
		if flock -n -x $fd; then
			return 0
		else
			echo "lock failed, retry" >&2
			sleep 1
			i=$((i+1))
		fi
	done

	return 1
}

PROGRESS_OPERATION=
PROGRESS_TOTAL=
PROGRESS_FILE=${LDAP_BKP_PROGRESS_FILE:-/var/log/synoldap_bkp.progress}
PROGRESS_STAGE=0
CheckProgress() { # <action> [status=$?]
	local status=${2:-$?}
	local action=$1

	PROGRESS_STAGE=$((PROGRESS_STAGE + 1))

	[ -z "$PROGRESS_FILE" -o -z "$PROGRESS_OPERATION" ] && return
	cat >$PROGRESS_FILE.$$ <<EOF
operation=$PROGRESS_OPERATION
pid=$$
total=$PROGRESS_TOTAL
stage=$PROGRESS_STAGE
action=$action
status=$status
task=$LDAP_BKP_TASK
EOF
	mv $PROGRESS_FILE.$$ $PROGRESS_FILE

	if [ "$status" -ne "0" ]; then
		printf "%-3d failed: $action\n" $PROGRESS_STAGE >&2
		exit $PROGRESS_STAGE
	else
		printf "%-3d ok: $action\n" $PROGRESS_STAGE >&2
		return 0
	fi
}
RemoveProgress() {
	[ -z "$PROGRESS_FILE" ] || rm -f $PROGRESS_FILE
}
ReadProgressOp() {
	if [ -r "$PROGRESS_FILE" ]; then
		sed -n "s/operation=\(.*\)/\1/p" $PROGRESS_FILE
	fi
}

CreateTempDir() { # <dir>
	local dir=$1
	[ -e "$dir" ] && rm -rf "$dir"
	mkdir -p "$dir" && [ -w "$dir" ]
}
CreateInfoFile() { # <tar file> <output> <bkp_time>
	local tarfile=$1
	local output=$2
	local bkp_time=$3
	local dsm_version=$(get_key_value /etc.defaults/VERSION buildnumber)
	local pkg_version=$(get_key_value $LDAP_PKG_DIR/INFO version)
	local space=0
	local checksum=
	
	[ -r "$tarfile" -a -n "$output" -a -n "$bkp_time" ] || return 1

	checksum=$(openssl sha1 "$tarfile" 2>/dev/null)
	[ -n "$checksum" ] || return 1
	checksum=${checksum##* }

	if cd $SLAP_CONF_DIR; then
		space=`du -s | cut -d'	' -f1`
		cd - >/dev/null
	fi

	cat >$output <<EOF
space=$space
checksum=$checksum
dsm_version=$dsm_version
pkg_version=$pkg_version
time=$bkp_time
EOF
}
RotateBackupFiles() {
	local i= f= max_files=${LDAP_BKP_MAX_BACKUP_FILE:-10}
	i=$(ls "$BKP_DEST_DIR/$BKP_PREFIX"_* | wc -l)

	[ "$i" -gt "$max_files" ] || return 0

	i=$((i - max_files))
	for f in `ls -tr "$BKP_DEST_DIR/$BKP_PREFIX"_*`; do
		if [ $i -gt 0 ]; then
			rm -f $f
			i=$((i - 1))
		else
			break;
		fi
	done

	return 0
}

BACKUP_SUCCESS=
backup_exit() {
	local bkp_tmp="$BKP_DEST_DIR/openldap"

	if [ -z "$BACKUP_SUCCESS" ]; then
		rm -rf "$bkp_tmp.tgz" "$bkp_tmp" "$BKP_DEST_DIR/INFO"
	fi

	$SERVICECFG --resume-by-reason ldap-server ldap-backup

	flock -u 200
}
backup_main() {
	local bkp_time= bkp_file= bkp_tmp="$BKP_DEST_DIR/openldap"
	local i=

	CheckPackage || return

	PROGRESS_OPERATION="backup"
	PROGRESS_TOTAL=11
(
	trap backup_exit EXIT
	if GetLock 200; then
		CheckProgress "init"
	else
		# we can write to progress file only if we got lock
		echo "lock: `ReadProgressOp`" ; exit 1
	fi

	CreateTempDir "$bkp_tmp"; CheckProgress "mktmp"

	$SERVICECFG --pause-by-reason ldap-server ldap-backup
	! pidof slapd >/dev/null; CheckProgress "slapd stop"
	bkp_time=$(date '+%Y-%m-%d %H:%M:%S %z')

	slapcat > "$bkp_tmp/synoldap.ldif" ; CheckProgress "slapcat"
		
	$SERVICECFG --resume-by-reason ldap-server ldap-backup
	CheckProgress "slapd resume"

	cp /usr/syno/etc/openldap/*.conf "$bkp_tmp"; CheckProgress "copy slap config"
	cp $LDAP_BROWSER_CONF "$bkp_tmp"; CheckProgress "copy browser config"

	# change dir for tar command
	if ! cd "$BKP_DEST_DIR"; then
		CheckProgress "tar config" 1
	fi

	rm -f "$bkp_tmp.tgz"
	tar zcf "$bkp_tmp.tgz" "openldap"; CheckProgress "tar config"

	CreateInfoFile "$bkp_tmp.tgz" "$BKP_DEST_DIR/INFO" "$bkp_time"; CheckProgress "gen info"

	i=1
	bkp_file="${BKP_DEST_DIR}/${BKP_PREFIX}_${bkp_time%% *}.sbk"
	while [ -e "$bkp_file" ]; do
		bkp_file="${BKP_DEST_DIR}/${BKP_PREFIX}_${bkp_time%% *}_$i.sbk"
		i=$((i+1))
	done
	tar cf "$bkp_file" "openldap.tgz" "INFO"; CheckProgress "make sbk"

	rm -rf "$bkp_tmp.tgz" "$bkp_tmp" "$BKP_DEST_DIR/INFO"

	RotateBackupFiles ; CheckProgress "rotate"

	echo "$bkp_time" > ${BKP_DEST_DIR}/last_time

	cd - >/dev/null
	RemoveProgress
) 200>$LOCK_FILE
}

RESTORE_SUCCESS=
restore_exit() {
	if [ $PROGRESS_STAGE -gt 3 -a -d "$SLAP_CONF_DIR.restore" -a -z "$RESTORE_SUCCESS" ]; then
		rm -rf "$SLAP_CONF_DIR"
		mv -f "$SLAP_CONF_DIR.restore" "$SLAP_CONF_DIR"
	fi
	if [ $PROGRESS_STAGE -gt 4 -a -e "$LDAP_BROWSER_CONF.restore" -a -z "$RESTORE_SUCCESS" ]; then
		mv -f "$LDAP_BROWSER_CONF.restore" "$LDAP_BROWSER_CONF"
	fi

	$SERVICECFG --resume-by-reason ldap-server ldap-backup

	flock -u 200
}
restore_main() {
	local bkp_file=$1
	local tmp_dir=/tmp/restore.$$
	local checksum= space= dsm_version= pkg_verson= value=

	CheckPackage || return
	if [ ! -r "$bkp_file" -o -d "$bkp_file" ]; then
		return 255
	fi

	PROGRESS_OPERATION="restore"
	PROGRESS_TOTAL=17
(
	trap restore_exit EXIT
	if GetLock 200; then
		CheckProgress "init"
	else
		# we can write to progress file only if we got lock
		echo "lock: `ReadProgressOp`" ; exit 1
	fi

	tar tf "$bkp_file" >/dev/null; CheckProgress "check tar"

	$SERVICECFG --pause-by-reason ldap-server ldap-backup
	! pidof slapd >/dev/null; CheckProgress "slapd stop"

	if [ -e "$SLAP_CONF_DIR.restore" ]; then
		# restore temp dir is already exist. maybe last restore is aborted?
		if [ "$LDAP_BKP_FORCE_APPLY" ]; then
			rm -rf $SLAP_CONF_DIR
		else
			CheckProgress "mv old dir" 1
		fi
	else
		[ ! -e "$SLAP_CONF_DIR" ] || mv "$SLAP_CONF_DIR" "$SLAP_CONF_DIR.restore"
	fi
	CheckProgress "mv old dir"
	if [ -e "$LDAP_BROWSER_CONF.restore" ]; then
		if [ "$LDAP_BKP_FORCE_APPLY" ]; then
			rm -f $LDAP_BROWSER_CONF
		else
			CheckProgress "mv old conf" 1
		fi
	else
		[ ! -e "$LDAP_BROWSER_CONF" ] || mv "$LDAP_BROWSER_CONF" "$LDAP_BROWSER_CONF.restore"
	fi
	CheckProgress "mv old conf"

	CreateTempDir "$tmp_dir" ; CheckProgress "mktmp"

	tar xf "$bkp_file" -C "$tmp_dir" ; CheckProgress "untar file"

	[ -e "$tmp_dir/INFO" -a -e "$tmp_dir/openldap.tgz" ]; CheckProgress "invalid file"

	checksum=`sed -n "s/^checksum=//p" "$tmp_dir/INFO" | tail -n 1`
	space=`sed -n "s/^space=//p" "$tmp_dir/INFO" | tail -n 1`
	dsm_version=`sed -n "s/^dsm_version=//p" "$tmp_dir/INFO" | tail -n 1`
	pkg_version=`sed -n "s/^pkg_version=//p" "$tmp_dir/INFO" | tail -n 1`

	[ "$space" -le "`df | awk '( $6 == "/"){print $4}'`" ]; CheckProgress "root full"
	
	if value=`openssl sha1 "$tmp_dir/openldap.tgz" 2>/dev/null`; then
		[ "$checksum" == "${value##* }" ]; CheckProgress "invalid checksum"
	else
		CheckProgress "invalid checksum"
	fi

	[ "$dsm_version" -le "`get_key_value /etc.defaults/VERSION buildnumber`" ]; \
		CheckProgress "dsm version"

	CompareVersion "$pkg_version" -le "`get_key_value $LDAP_PKG_DIR/INFO version`"; \
		CheckProgress "pkg version"

	tar zxf $tmp_dir/openldap.tgz -C $(dirname $SLAP_CONF_DIR) ; CheckProgress "untar data"
	rm -rf $tmp_dir

	mkdir -p $SLAP_CONF_DIR/data
	cp $SLAP_DEFAULT_DIR/data/DB_CONFIG $SLAP_CONF_DIR/data

	slapadd -c -f $SLAP_CONF_DIR/slapd.conf -l $SLAP_CONF_DIR/synoldap.ldif ; CheckProgress "slapadd"
	slapindex -f $SLAP_CONF_DIR/slapd.conf ; CheckProgress "slapindex"
	cp $SLAP_CONF_DIR/ldapbrowser.conf $LDAP_BROWSER_CONF ; CheckProgress "cp conf"
	rm -f "$SLAP_CONF_DIR/ldapbrowser.conf" "$SLAP_CONF_DIR/synoldap.ldif"

	$SERVICECFG --resume-by-reason ldap-server ldap-backup
	CheckProgress "slapd resume"

	RESTORE_SUCCESS=yes
	rm -rf "$LDAP_BROWSER_CONF.restore" "$SLAP_CONF_DIR.restore"
	RemoveProgress
) 200>$LOCK_FILE
}

case "$1" in
	-h|h|--help|help)
		echo "..." ;;
	-r|r|--restore|restore)
		restore_main "$2";;
	*)
		backup_main ;;
esac
