#!/bin/bash
# vim: cindent:shiftwidth=4:tabstop=4:smarttab:textwidth=100

set -o posix
set -o errexit
set -o pipefail
set -o nounset
#set -o xtrace

#$title$ Clearcable SOE System Fixer
#$action$ fixes problems exported by syscheck
#$ref$ syscheck
#$author$ Rafal Rzeczkowski
#$version$ 0.1.2

#CHANGELOG
#0.0.1	create initial version
#0.0.2	separate namespace for each plugin
#0.0.3	remove inherit_errexit for SOE4.2 compatibility
#0.0.4	re-run syscheck for fixed SYSes
#0.0.5	export copy_function()
#0.1.0	support "super" automation level for CRON
#0.1.1	export is_element_of function to use in recipes
#0.1.2	suppress debug output in CRON mode

VERSION=$(awk -F'^#[$]version[$][\t ]+' '{if ($2){print $2}}' "$0")
BASENAME=$(basename "$0")
SHARE_DIR="/usr/local/share/$BASENAME"
PATH='/sbin:/bin:/usr/sbin:/usr/bin'

# auto-detect the most optimal location for IPC files
# keep synchronized with the Munin syscheck plugin
if [[ -d '/run' ]]; then
	# preferred (Wheezy+)
	# http://wiki.debian.org/ReleaseGoals/RunDirectory
	RUN='/run'
elif [[ -d '/dev/shm' ]]; then
	# not technically correct, but tmpfs is desirable
	RUN='/dev/shm'
else
	# fall-back default
	RUN='/var/run/'
fi
FIX_DIR="$RUN/syscheck.fix"

function usage() {
	cat << 'EOF'
usage:
	$ sysfix all
	$ sysfix cron
	$ sysfix fix PLUGIN_NAME
EOF
	exit 2
}

declare -a LOADED_FIXES
function load_fixes() {
	local SYS=$1

	if [[ ! -s "$SHARE_DIR/$SYS" ]]; then
		return
	fi

	LOADED_FIXES=($(awk '{if ($1=="function" && $3=="{"){print $2}}' "$SHARE_DIR/$SYS"))

	source "$SHARE_DIR/$SYS"
}

function unload_fixes() {
	for FIX_NAME in ${LOADED_FIXES[@]}; do
		unset -f $FIX_NAME
	done
	unset LOADED_FIXES
}

# https://stackoverflow.com/questions/1203583/how-do-i-rename-a-bash-function
function copy_function() {
	test -n "$(declare -f "$1")" || return
	eval "${_/$1/$2}"
}

function is_element_of {
	local TO_FIND=$1
	shift

	for ARRAY_ELEMENT in "$@"; do
		if [[ "$TO_FIND" = "$ARRAY_ELEMENT" ]]; then
			return 0
		fi
	done
	return 1
}

if [[ -n "${1+xxx}" ]]; then
	case ${1} in
		all)
			SYS_SELECT='*'
			;;
		cron)
			SYS_SELECT='*'
			CRON='true'
			;;
		fix)
			SYS_SELECT="$2"
			;;
		*)
			usage
			;;
	esac
fi

if [[ -d "$FIX_DIR" ]]; then
	cd "$FIX_DIR"
else
	echo "FIX_DIR $FIX_DIR missing"
	exit 1
fi

declare -A PROBLEM_ID
declare -A PROBLEM_PARTICULAR
for FILE in *; do
	SYS=$FILE
	if [[ ! -s "$SYS" ]]; then
		continue
	fi
	declare -a PROBLEM=($(<$SYS))
	PROBLEM_ID[$SYS]=${PROBLEM[0]}
	PROBLEM_PARTICULAR[$SYS]=${PROBLEM[1]}
done

if [[ -z "${SYS_SELECT+xxx}" ]]; then
	for SYS in ${!PROBLEM_ID[@]}; do
		load_fixes $SYS
		FUNCTION_NAME="${PROBLEM_ID[$SYS]}"
		if type -t ${FUNCTION_NAME}_super >/dev/null; then
			AUTOMATION_AVAILABLE='super'
		elif type -t $FUNCTION_NAME >/dev/null; then
			AUTOMATION_AVAILABLE='auto'
		else
			AUTOMATION_AVAILABLE='manual'
		fi
		unload_fixes
		echo -e $SYS "\t" ${PROBLEM_ID[$SYS]} "\t" ${PROBLEM_PARTICULAR[$SYS]} "\t" $AUTOMATION_AVAILABLE
	done
else
	for SYS in ${!PROBLEM_ID[@]}; do
		if [[ $SYS_SELECT != '*' && $SYS_SELECT != "$SYS" ]]; then
			continue
		fi

		load_fixes $SYS
		FUNCTION_NAME="${PROBLEM_ID[$SYS]}"
		if [[ -n ${CRON+xxx} ]]; then
			FUNCTION_NAME+='_super'
		fi
		if ! type -t $FUNCTION_NAME >/dev/null; then
			echo "no suitable recipe found to fix ${PROBLEM_ID[$SYS]} problem for $SYS"
			continue
		fi

		echo "attempting to fix ${PROBLEM_ID[$SYS]} for $SYS..."
		[[ -z ${CRON+xxx} ]] && set -o xtrace
		if $FUNCTION_NAME ${PROBLEM_PARTICULAR[$SYS]}; then
			set +o xtrace
			echo 'fix successful'
			[[ -n ${CRON+xxx} ]] && logger -t "$0" -p daemon.info "OK: $SYS"
			rm "$FIX_DIR/$SYS"
			/usr/local/sbin/syscheck check $SYS || true
		else
			set +o xtrace
			echo 'fix FAILED'
			[[ -n ${CRON+xxx} ]] && logger -t "$0" -p daemon.warning "FAIL: $SYS"
		fi
		unload_fixes
	done
fi
