#!/bin/sh

. /opt/etc/functions.openrc
. /opt/etc/functions.noopenrc

CONFIG="/opt/etc/ipmimon.conf"
LOCKFILE="/tmp/$(basename $0).lock"

if [ -f "${CONFIG}" ]; then
	. ${CONFIG}
fi

T_BOUNDS=${T_BOUNDS-"10 20 60 75"}
REQUIRED_TEMPERATURE_SENSORS=${REQUIRED_TEMPERATURE_SENSORS-""}
F_BOUNDS=${F_BOUNDS-"300 400 10000 11000"}
REQUIRED_FANS=${REQUIRED_FANS-""}
IPMI_DB_ROOT=${IPMI_DB_DIR-"/opt/var"}
ATTEMPTS=${ATTEMPTS-"5"}

IPMI_DB_DIR="$(realpath ${IPMI_DB_ROOT})/ipmi"
IPMI_DB=${IPMI_DB-"$IPMI_DB_DIR/allsensors"}
TEMP_DB=${TEMP_DB-"$IPMI_DB_DIR/temperatures"}
FAN_DB=${FAN_DB-"$IPMI_DB_DIR/fans"}

IPMI_DB_PREV="${IPMI_DB}.prev"
VERBOSE=""

get_ipmi_data() {
	if [ -f ${IPMI_DB} ]; then
		[ -f ${IPMI_DB_PREV} ] && rm ${IPMI_DB_PREV}
		mv ${IPMI_DB} ${IPMI_DB_PREV}
	fi
	if ! (ipmitool sensor | sed -e 's/\ /_/g' -e 's/|/\ /g' -e 's/_//g' > \
			"${IPMI_DB}"); then
		update_me
		echo "${ME}: error reading IPMI sensors!"
		return 1
	fi
	return 0
}

parse_ipmi_data() {
	cat "${IPMI_DB}" | while read LN; do
		if echo $LN | grep 'degrees' > /dev/null 2>&1; then
			[ $(echo $LN | awk '{print $2;}') = "na" ] || \
				echo $LN | awk '{print $1 " " int($2);}' >> "${TEMP_DB}"
		elif echo $LN | grep '[Rr][Pp][Mm]' > /dev/null 2>&1; then
			[ $(echo $LN | awk '{print $2;}') = "na" ] || \
				echo $LN | awk '{print $1 " " int($2);}' >> "${FAN_DB}"
		fi
	done
}

check_if_onlyshow() {
	local _sens _val
	[ $# != 1 -o "$1" != "--showdbs" ] && return 0
	cat "${TEMP_DB}" | while read LN; do
		_sens=$(echo $LN | sed -e 's/\ .*//')
		_val=$(echo $LN | sed -e 's/.*\ //')
		echo "TemperatureDB[${_sens}]=${_val}"
	done
	cat "${FAN_DB}" | while read LN; do
		_sens=$(echo $LN | sed -e 's/\ .*//')
		_val=$(echo $LN | sed -e 's/.*\ //')
		echo "FanDB[${_sens}]=${_val}"
	done
	unlock_exit ${LOCKFILE} 0
}

# check_bounds()
# Arguments: real value, mincrit, minwarn, maxwarn, maxcrit
# Returns:
#	0: value \in (minwarn,maxwarn)
#	1: value \in (mincrit,maxcrit)
#	2: otherwise

check_bounds() {
	# Hail the C language!
	echo $1 $2 $3 $4 $5 | \
		awk '{exit $1 > $3 && $1 < $4 ? 0 : ($1 > $2 && $1 < $5 ? 1 : 2)}'
}

# Monitor routine accepts 5 or more arguments:
#  - file name of plain text database of form:
#	Sensor1Name RealSensor1Value [any extra data]
#	Sensor2Name RealSensor2Value [any extra data]
#	...
#	SensorNName RealSensorNValue [any extra data]
#  - four real values (MinCrit,MinWarn,MaxWarn,MaxCrit);
#  - optional names of mandatory sensors.
# Global variables:
#  - VERBOSE (empty or eny string)
# Return values:
#  - 0 if all mandatory sensors (if specified) are present and values
#	of either these or all (if mandatory are not specified) sensors
#	are in (MinWarn;MaxWarn);
#  - 1 if previous conditions are met except range is (MinCrit;MaxCrit)
#  - 2 if at least one mandatory sensor was not found or value of
#	at least one (mandatory or autodetected) sensor is out of
#	critical range.

generic_monitor()
{
	check_bounds_verbose()
	{
		local _v _sensor=$1; shift
		check_bounds $@
		_v=$?
		[ ${_v} -gt ${_rv} ] && _rv=${_v}
		[ -z "${VERBOSE}" ] && return
		case ${_v} in
			1) echo "${ME} warning: value $1 of sensor ${_sensor} is out of range ($3;$4); ${_count} more trials";;
			2) echo "${ME} critical: value $1 of sensor ${_sensor} is out of range ($2;$5) ${_count} more trials";;
		esac
	}
	local _dbline _sensor _value
	_rv=0
	local _sensordb=$1; shift
	local _mincrit=$1; shift
	local _minwarn=$1; shift
	local _maxwarn=$1; shift
	local _maxcrit=$1; shift
	update_me
	if [ $# -gt 0 ]; then
		# We have list of mandatory sensors
		for _sensor in $@; do
			_value=$(grep -e ^${_sensor} ${_sensordb})
			if [ $? -ne 0 ]; then
				echo "${ME} critical: sensor ${_sensor} not found in ${_sensordb}."
				_rv=2
			else
				_value=$(echo ${_value} | awk '{print $2;}')
				check_bounds_verbose ${_sensor} ${_value} \
					${_mincrit} ${_minwarn} ${_maxwarn} ${_maxcrit}
			fi
		done
	else
		# We have to analyse all available sensors.
		# The trick used to export new value of _rv from subshell to parent
		# is described in http://heirloom.sourceforge.net/sh/sh.1.html#20
		exec 5<&0 < ${_sensordb}; while read _dbline; do
			_sensor=$(echo ${_dbline} | awk '{print $1;}')
			_value=$(echo ${_dbline} | awk '{print $2;}')
			check_bounds_verbose ${_sensor} ${_value} \
				${_mincrit} ${_minwarn} ${_maxwarn} ${_maxcrit}
		done
		exec <&5 5<&-
	fi
	return ${_rv}
}

# main

while ! [ -d ${IPMI_DB_DIR} ]; do
	mkdir -p ${IPMI_DB_DIR}
	[ -d ${IPMI_DB_DIR} ] && break
	update_me
	echo "${ME}: unable to create database directory ${IPMI_DB_DIR}!"
	exit 1
done

if ! enter_lock ${LOCKFILE}; then
	update_me
	echo "${ME}: recursive call!"
	exit 1;
fi

# There can be broken IPMI BMCs that occasionally report insane values.
# ${ATTEMPTS} trials will be made in case of critical state report.

_count=${ATTEMPTS}

while true; do
	_count=$((${_count}-1))
	truncate -s 0 "${TEMP_DB}"
	truncate -s 0 "${FAN_DB}"
	# Parse sensor data line by line, extracting temperatures
	# and RPMs to separate databases.
	# If 'ipmi sensors' wailed, consider it as non-critical event.
	level=1
	get_ipmi_data || break
	parse_ipmi_data
	# If argv(1) == "--showdbs" print newly created databases and exit
	# FIXME: this can be unnecessary called several times.
	check_if_onlyshow $@
	# Execute monitors, set 'level' to max{level_i}.
	# In case of adequate BMC critical state will be reported ${ATTEMPTS}
	# times; print messages only at last measurement.
	[ ${_count} -eq 0 ] && VERBOSE="YES"
	generic_monitor ${TEMP_DB} ${T_BOUNDS} ${REQUIRED_TEMPERATURE_SENSORS}
	level=$?
	generic_monitor ${FAN_DB} ${F_BOUNDS} ${REQUIRED_FANS}
	_rv=$?
	[ ${_rv} -gt ${level} ] && level=${_rv}
	# In case of critical health
	if [ $level -gt 1 ]; then
		# try to ask BMC again
		[ ${_count} -gt 0 ] && continue
		# If BMC have reported critical ${ATTEMPTS} times, shut the server down.
		msg="${ME}: health-critical event(s) detected, shutting system down in 1 minute!"
		echo $msg
		shutdown -hP +1 $msg
	fi
	# State is non-critical ...
	if [ ${_count} -ne $((${ATTEMPTS}-1)) ]; then
		# but BMC is broken!
		update_me
		echo "${ME}: broken BMC reports insane value(s). Actial IPMI state:"
		echo ""
		cat ${IPMI_DB}
		echo ""
		echo "Differencies:"
		echo ""
		diff -C 0 ${IPMI_DB_PREV} ${IPMI_DB}
	fi
	break
done

unlock_exit ${LOCKFILE} $level
