#!/bin/sh
#
# Copyright (c) 2022-2023, Jesús Daniel Colmenares Oviedo <DtxdF@disroot.org>
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice, this
#   list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright notice,
#   this list of conditions and the following disclaimer in the documentation
#   and/or other materials provided with the distribution.
#
# * Neither the name of the copyright holder nor the names of its
#   contributors may be used to endorse or promote products derived from
#   this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

lib_load "${LIBDIR}/check_func"
lib_load "${LIBDIR}/jail"
lib_load "${LIBDIR}/replace"
lib_load "${LIBDIR}/strlen"
lib_load "${LIBDIR}/table"

# Health type
HEALTHCHECK_HEALTH_HOST_TYPE="host"
HEALTHCHECK_HEALTH_JAIL_TYPE="jail"
# Recover type
HEALTHCHECK_RECOVER_HOST_TYPE="host"
HEALTHCHECK_RECOVER_JAIL_TYPE="jail"

healthcheck_desc="Keep your jails healthy and running"

healthcheck_main()
{
	local entity="$1"; shift
	if lib_check_empty "${entity}"; then
		healthcheck_usage
		exit ${EX_USAGE}
	fi

	case "${entity}" in
		get|list|remove|run|set) ;;
		*) healthcheck_usage; exit ${EX_USAGE} ;;
	esac

	healthcheck_${entity} "$@"
}

healthcheck_get()
{
	local _o
	local opt_ignore_unknro=0
	local nro=

	local flag_nro=0
	local flag_enabled=0
	local flag_name=0
	local flag_status=0
	local flag_interval=0
	local flag_kill_after=0
	local flag_timeout=0
	local flag_timeout_signal=0
	local flag_start_period=0
	local flag_retries=0
	local flag_health_type=0
	local flag_health_cmd=0
	local flag_recover_type=0
	local flag_recover_cmd=0
	local flag_recover_total=0
	local flag_recover_kill_after=0
	local flag_recover_timeout=0
	local flag_recover_timeout_signal=0

	lib_table_init "healthchecker_get"

	lib_table_disable_escape
	lib_table_disable_columns
	lib_table_disable_empty
	lib_table_disable_pretty
	lib_table_disable_tabulate

	while getopts ":eHIiptn:" _o; do
		case "${_o}" in
			n)
				if lib_check_empty "${OPTARG}"; then
					healthcheck_usage
					exit ${EX_USAGE}
				fi
				;;
		esac
		
		case "${_o}" in
			e)
				lib_table_enable_escape
				;;
			H)
				lib_table_enable_columns
				;;
			I)
				lib_table_enable_empty
				;;
			i)
				opt_ignore_unknro=1
				;;
			p)
				lib_table_enable_pretty
				;;
			t)
				lib_table_enable_tabulate
				;;
			n)
				nro="${OPTARG}"
				;;
			*)
				healthcheck_usage
				exit ${EX_USAGE}
				;;
		esac
	done
	shift $((OPTIND-1))

	if [ -z "${nro}" ]; then
		healthcheck_usage
		exit ${EX_USAGE}
	fi

	local jail_name="$1"; shift
	_healthcheck_chk_jail "${jail_name}"

	local jail_path="${JAILDIR}/${jail_name}"
	local basedir="${jail_path}/conf/boot/health/${nro}"
	if [ ! -d "${basedir}" ]; then
		if [ ${opt_ignore_unknro} -eq 1 ]; then
			return 0
		fi

		lib_err ${EX_NOINPUT} "Cannot find the nro \`${nro}\`"
	fi

	if ! lib_check_number "${nro}"; then
		lib_err ${EX_DATAERR} "NRO must be a number!"
	fi

	if [ $# -eq 0 ]; then
		set -- "nro" "enabled" "name" "status" "health_cmd" "recover_cmd"
	fi

	local keyword
	for keyword in "$@"; do
		if lib_check_empty "${keyword}"; then
			continue
		fi

		local value=

		case "${keyword}" in
			nro)
				value="${nro}"
				;;
			enabled|health_cmd|health_type|interval|kill_after|name|recover_cmd|recover_kill_after|recover_timeout|recover_timeout_signal|recover_total|recover_type|retries|start_period|status|timeout|timeout_signal)
				if [ -f "${basedir}/${keyword}" ]; then
					value=`head -1 -- "${basedir}/${keyword}"`
				fi
				;;
			*)
				lib_warn -- "${keyword}: keyword not found."
				continue
				;;
		esac

		if [ `lib_loaded_var "flag_${keyword}"` -eq 1 ]; then
			continue
		else
			setvar flag_${keyword} 1
		fi

		lib_table_set "${keyword}" "${value}"
	done

	lib_table_print
}

healthcheck_list()
{
	local _o
	local opt_escape=1 eflag=
	local opt_columns=1 Hflag=
	local opt_empty=0 Iflag=
	local opt_ignore_unknro=0 iflag=
	local opt_pretty=1 pflag=
	local opt_tabulate=1 tflag=
	local nro=

	while getopts ":eHIiptn:" _o; do
		case "${_o}" in
			n)
				if lib_check_empty "${OPTARG}"; then
					healthcheck_usage
					exit ${EX_USAGE}
				fi
				;;
		esac

		case "${_o}" in
			e)
				opt_escape=0
				;;
			H)
				opt_columns=0
				;;
			I)
				opt_empty=1
				;;
			i)
				opt_ignore_unknro=1
				;;
			p)
				opt_pretty=0
				;;
			t)
				opt_tabulate=0
				;;
			n)
				nro="${OPTARG}"
				;;
			*)
				healthcheck_usage
				exit ${EX_USAGE}
				;;
		esac
	done
	shift $((OPTIND-1))

	local jail_name="$1"; shift
	_healthcheck_chk_jail "${jail_name}"

	if [ ${opt_escape} -eq 1 ]; then
		eflag="-e"
	fi

	if [ ${opt_columns} -eq 1 ]; then
		Hflag="-H"
	fi

	if [ ${opt_empty} -eq 1 ]; then
		Iflag="-I"
	fi

	if [ ${opt_ignore_unknro} -eq -1 ]; then
		iflag="-i"
	fi

	if [ ${opt_pretty} -eq 1 ]; then
		pflag="-p"
	fi

	if [ ${opt_tabulate} -eq 1 ]; then
		tflag="-t"
	fi

	if [ -n "${nro}" ]; then
		healthcheck_get ${eflag} ${Hflag} ${Iflag} ${iflag} ${pflag} ${tflag} -n "${nro}" -- "${jail_name}" "$@"
		return $?
	fi

	local basedir="${JAILDIR}/${jail_name}/conf/boot/health"
	if [ ! -d "${basedir}" ]; then
		return
	fi

	ls -A "${basedir}" | sort -n | while IFS= read -r nro; do
		healthcheck_get ${eflag} ${Hflag} ${Iflag} ${tflag} -n "${nro}" -- "${jail_name}" "$@"

		# To not print the columns again
		Hflag=
	done | \
	if [ ${opt_pretty} -eq 1 ]; then
		column -ts $'\t'
	else
		cat
	fi
}

healthcheck_remove()
{
	local command="$1"; shift
	if lib_check_empty "${command}"; then
		healthcheck_usage
		exit ${EX_USAGE}
	fi

	case "${command}" in
		all|nro) ;;
		*) healthcheck_usage; exit ${EX_USAGE} ;;
	esac

	healthcheck_remove_${command} "$@"
}

healthcheck_remove_all()
{
	local basedir
	local jail_name

	jail_name="$1"
	_healthcheck_chk_jail "${jail_name}"

	basedir="${JAILDIR}/${jail_name}/conf/boot/health"

	rm -rf "${basedir}"
}

healthcheck_remove_nro()
{
	local nro="$1"; shift
	if lib_check_empty "${nro}"; then
		healthcheck_usage
		exit ${EX_USAGE}
	fi

	if ! lib_check_number "${nro}"; then
		lib_err ${EX_DATAERR} "NRO must be a number!"
	fi

	local jail_name="$1"
	_healthcheck_chk_jail "${jail_name}"

	basedir="${JAILDIR}/${jail_name}/conf/boot/health/${nro}"
	if [ ! -d "${basedir}" ]; then
		lib_err ${EX_NOINPUT} "Cannot find the nro \`${nro}\`"
	fi

	rm -rf "${basedir}"
}

healthcheck_run()
{
	local jail_name="$1"
	_healthcheck_chk_jail "${jail_name}"

	if [ ! -d "${JAILDIR}/${jail_name}/conf/boot/health" ]; then
		lib_err ${EX_CONFIG} -- "${jail_name} is not configured to use healthcheckers."
	fi

	lib_set_logprefix " [`random_color`${jail_name}${COLOR_DEFAULT}]"

	lib_debug "Starting healthchecking ..."

	local enabled

	local kill_child_cmd
	kill_child_cmd=`lib_escape_string "${SCRIPTSDIR}/kill_child.sh"`

	local config_file
	config_file=`lib_escape_string "${CONFIG}"`
	
	for nro in `healthcheck_list -HIpt "${jail_name}" nro`; do
		enabled=`healthcheck_get -In "${nro}" "${jail_name}" enabled`

		if [ "${enabled}" != "1" ]; then
			continue
		fi

		lib_debug "Starting healthchecker (nro = ${nro}) ..."

		_healthcheck_run "${jail_name}" ${nro} &

		lib_atexit_add "\"${kill_child_cmd}\" -c \"${config_file}\" -P $$ -p $! > /dev/null 2>&1"
	done

	lib_debug "All healthcheckers has been started. Waiting..."
	wait
}

_healthcheck_run()
{
	local jail="$1" nro="$2"

	if [ -z "${jail}" -o -z "${nro}" ]; then
		lib_err ${EX_USAGE} "usage: _healthcheck_run jail nro"
	fi

	_healthcheck_set_status "${jail}" "${nro}" "starting"

	local start_period=`healthcheck_get -In "${nro}" "${jail}" start_period`
	if [ "${start_period}" -gt 0 ]; then
		lib_debug "Sleeping (start_period = ${start_period}) ..."

		sleep "${start_period}" || exit $?
	fi

	_healthcheck_jail_exists "${jail}"

	local retries=`healthcheck_get -In "${nro}" "${jail}" retries`
	local interval=`healthcheck_get -In "${nro}" "${jail}" interval`

	local health_type=`healthcheck_get -In "${nro}" "${jail}" health_type`
	local health_cmd=`healthcheck_get -In "${nro}" "${jail}" health_cmd`
	health_cmd=`lib_replace "${health_cmd}" j "\"${jail}\""`

	local recover_type=`healthcheck_get -In "${nro}" "${jail}" recover_type`
	local recover_cmd=`healthcheck_get -In "${nro}" "${jail}" recover_cmd`
	recover_cmd=`lib_replace "${recover_cmd}" j "\"${jail}\""`

	local timeout=`healthcheck_get -In "${nro}" "${jail}" timeout`
	local timeout_signal=`healthcheck_get -In "${nro}" "${jail}" timeout_signal`
	local kill_after=`healthcheck_get -In "${nro}" "${jail}" kill_after`

	local recover_total=`healthcheck_get -In "${nro}" "${jail}" recover_total`
	local recover_timeout=`healthcheck_get -In "${nro}" "${jail}" recover_timeout`
	local recover_timeout_signal=`healthcheck_get -In "${nro}" "${jail}" recover_timeout_signal`
	local recover_kill_after=`healthcheck_get -In "${nro}" "${jail}" recover_kill_after`

	lib_debug "Healthchecker (nro = ${nro}, retries = ${retries}, interval = ${interval}, type = ${health_type}, cmd = ${health_cmd}, timeout = ${timeout}, timeout_signal = ${timeout_signal}, kill_after = ${kill_after})"
	lib_debug "Recover (nro = ${nro}, total = ${recover_total}, type = ${recover_type}, cmd = ${recover_cmd}, timeout = ${recover_timeout}, timeout_signal = ${recover_timeout_signal}, kill_after = ${recover_kill_after})"

	local errlevel=0
	local count_retries=0 count_recover=0
	while true; do
		lib_debug "Sleeping (nro = ${nro}, context = health, type = ${health_type}, cmd = ${health_cmd}, interval = ${interval}) ..."
		sleep "${interval}" || exit $?

		_healthcheck_jail_exists "${jail}"

		lib_debug "Executing (nro = ${nro}, context = health, type = ${health_type}, cmd = ${health_cmd}) ..."

		if [ "${health_type}" = "${HEALTHCHECK_HEALTH_HOST_TYPE}" ]; then
			timeout --foreground -k "${kill_after}" -s "${timeout_signal}" ${timeout} \
				"${APPJAIL_PROGRAM}" cmd local "${jail}" sh -c "${health_cmd}"
		else
			timeout --foreground -k "${kill_after}" -s "${timeout_signal}" ${timeout} \
				"${APPJAIL_PROGRAM}" cmd jexec "${jail}" sh -c "${health_cmd}"
		fi
		
		errlevel=$?
		if [ ${errlevel} -eq 0 ]; then
			_healthcheck_set_status "${jail}" "${nro}" "healthy"
			continue
		else
			_healthcheck_set_status "${jail}" "${nro}" "failing"
		fi

		count_retries=$((count_retries+1))

		lib_debug "Failing (nro = ${nro}, context = health, type = ${health_type}, cmd = ${health_cmd}, status = ${errlevel}, retry = ${count_retries}:${retries})"

		if [ ${count_retries} -lt ${retries} ]; then
			continue
		fi

		if [ ${count_recover} -ge ${recover_total} ]; then
			_healthcheck_set_status "${jail}" "${nro}" "unhealthy"
			lib_err ${EX_NOPERM} "Total number of recoveries (nro = ${nro}, current = ${count_recover}, total = ${recover_total}) has been reached. Cannot continue..."
		fi

		lib_debug "Executing (nro = ${nro}, context = recover, type = ${recover_type}, cmd = ${recover_cmd}, total = ${count_recover}:${recover_total}) ..."

		count_recover=$((count_recover+1))

		if [ "${recover_type}" = "${HEALTHCHECK_RECOVER_HOST_TYPE}" ]; then
			timeout --foreground -k "${recover_kill_after}" -s "${recover_timeout_signal}" ${recover_timeout} \
				"${APPJAIL_PROGRAM}" cmd local "${jail}" sh -c "${recover_cmd}"
		else
			timeout --foreground -k "${recover_kill_after}" -s "${recover_timeout_signal}" ${recover_timeout} \
				"${APPJAIL_PROGRAM}" cmd jexec "${jail}" sh -c "${recover_cmd}"
		fi

		errlevel=$?
		if [ ${errlevel} -ne 0 ]; then
			_healthcheck_set_status "${jail}" "${nro}" "unhealthy"
			lib_err ${errlevel} "Recover command (nro = ${nro}, type = ${recover_type}, cmd = ${recover_cmd}) has failed. Exit status ${errlevel}"
		fi

		_healthcheck_set_status "${jail}" "${nro}" "healthy"

		# Reset current retries.
		count_retries=0
	done
}

_healthcheck_jail_exists()
{
	local jail="$1"

	if [ -z "${jail}" ]; then
		lib_err ${EX_USAGE} "usage: _healthcheck_jail_exists jail"
	fi

	if [ ! -d "${JAILDIR}/${jail_name}" ]; then
		lib_warn -- "${jail_name}: the jail is gone. Closing..."
		exit 0
	fi
}

_healthcheck_set_status()
{
	local jail="$1" nro="$2" status="$3"
	if [ -z "${jail}" -o -z "${nro}" -o -z "${status}" ]; then
		lib_err ${EX_USAGE} "_healthcheck_set_status jail nro status"
	fi

	_healthcheck_jail_exists "${jail}"

	local jail_path="${JAILDIR}/${jail}"
	local basedir="${jail_path}/conf/boot/health"
	local nrodir="${basedir}/${nro}"

	if ! echo "${status}" > "${nrodir}/status"; then
		lib_err ${EX_IOERR} "Error writing ${status} to ${nrodir}/status"
	fi
}

healthcheck_set()
{
	local _o
	local opt_enabled=
	local health_cmd=
	local interval=
	local kill_after=
	local opt_name=0 name=
	local nro="auto"
	local retries=
	local recover_cmd=
	local timeout_signal=
	local start_period=
	local recover_total=
	local timeout=

	while getopts ":Eeh:i:K:k:l:N:n:R:r:S:s:T:t:u:" _o; do
		case "${_o}" in
			h|i|K|k|l|n|R|r|S|s|T|t|u)
				if lib_check_empty "${OPTARG}"; then
					healthcheck_usage
					exit ${EX_USAGE}
				fi
				;;
		esac
		
		case "${_o}" in
			E)
				opt_enabled=1
				;;
			e)
				opt_enabled=0
				;;
			h)
				health_cmd="${OPTARG}"
				;;
			i)
				interval="${OPTARG}"
				;;
			K)
				recover_kill_after="${OPTARG}"
				;;
			k)
				kill_after="${OPTARG}"
				;;
			l)
				recover_timeout_signal="${OPTARG}"
				;;
			N)
				opt_name=1
				name="${OPTARG}"
				;;
			n)
				nro="${OPTARG}"
				;;
			R)
				retries="${OPTARG}"
				;;
			r)
				recover_cmd="${OPTARG}"
				;;
			S)
				timeout_signal="${OPTARG}"
				;;
			s)
				start_period="${OPTARG}"
				;;
			T)
				recover_total="${OPTARG}"
				;;
			t)
				timeout="${OPTARG}"
				;;
			u)
				recover_timeout="${OPTARG}"
				;;
			*)
				healthcheck_usage
				exit ${EX_USAGE}
				;;
		esac
	done
	shift $((OPTIND-1))

	local jail_name="$1"; shift
	_healthcheck_chk_jail "${jail_name}"

	# Use existing values (if any).

	if [ -z "${health_cmd}" ]; then
		health_cmd=`healthcheck_get -Iin "${nro}" "${jail_name}" health_cmd`

		if [ -z "${health_cmd}" ]; then
			health_cmd="${DEFAULT_HEALTH_CMD}"
		fi
	fi

	if [ -z "${interval}" ]; then
		interval=`healthcheck_get -Iin "${nro}" "${jail_name}" interval`

		if [ -z "${interval}" ]; then
			interval="${DEFAULT_HEALTH_INTERVAL}"
		fi
	fi

	if [ -z "${recover_kill_after}" ]; then
		recover_kill_after=`healthcheck_get -Iin "${nro}" "${jail_name}" recover_kill_after`

		if [ -z "${recover_kill_after}" ]; then
			recover_kill_after="${DEFAULT_RECOVER_TIMEOUT_KILL_AFTER}"
		fi
	fi

	if [ -z "${kill_after}" ]; then
		kill_after=`healthcheck_get -Iin "${nro}" "${jail_name}" kill_after`

		if [ -z "${kill_after}" ]; then
			kill_after="${DEFAULT_TIMEOUT_KILL_AFTER}"
		fi
	fi

	if [ -z "${recover_timeout_signal}" ]; then
		recover_timeout_signal=`healthcheck_get -Iin "${nro}" "${jail_name}" recover_timeout_signal`

		if [ -z "${recover_timeout_signal}" ]; then
			recover_timeout_signal="${DEFAULT_RECOVER_TIMEOUT_SIGNAL}"
		fi
	fi

	if [ -z "${retries}" ]; then
		retries=`healthcheck_get -Iin "${nro}" "${jail_name}" retries`

		if [ -z "${retries}" ]; then
			retries="${DEFAULT_HEALTH_RETRIES}"
		fi
	fi

	if [ -z "${recover_cmd}" ]; then
		recover_cmd=`healthcheck_get -Iin "${nro}" "${jail_name}" recover_cmd`

		if [ -z "${recover_cmd}" ]; then
			recover_cmd="${DEFAULT_RECOVER_CMD}"
		fi
	fi

	if [ -z "${timeout_signal}" ]; then
		timeout_signal=`healthcheck_get -Iin "${nro}" "${jail_name}" timeout_signal`

		if [ -z "${timeout_signal}" ]; then
			timeout_signal="${DEFAULT_TIMEOUT_SIGNAL}"
		fi
	fi

	if [ -z "${start_period}" ]; then
		start_period=`healthcheck_get -Iin "${nro}" "${jail_name}" start_period`

		if [ -z "${start_period}" ]; then
			start_period="${DEFAULT_HEALTH_START_PERIOD}"
		fi
	fi

	if [ -z "${recover_total}" ]; then
		recover_total=`healthcheck_get -Iin "${nro}" "${jail_name}" recover_total`

		if [ -z "${recover_total}" ]; then
			recover_total="${DEFAULT_RECOVER_TOTAL}"
		fi
	fi

	if [ -z "${timeout}" ]; then
		timeout=`healthcheck_get -Iin "${nro}" "${jail_name}" timeout`

		if [ -z "${timeout}" ]; then
			timeout="${DEFAULT_HEALTH_TIMEOUT}"
		fi
	fi

	if [ -z "${recover_timeout}" ]; then
		recover_timeout=`healthcheck_get -Iin "${nro}" "${jail_name}" recover_timeout`

		if [ -z "${recover_timeout}" ]; then
			recover_timeout="${DEFAULT_RECOVER_TIMEOUT}"
		fi
	fi

	# Check.

	local health_type=`lib_jailparam_name "${health_cmd}" :`
	health_cmd=`lib_jailparam_value "${health_cmd}" :`

	if lib_check_empty "${health_cmd}"; then
		health_cmd="${health_type}"
		health_type="${DEFAULT_HEALTH_TYPE}"
	fi

	case "${health_type}" in
		${HEALTHCHECK_HEALTH_HOST_TYPE}|${HEALTHCHECK_HEALTH_JAIL_TYPE}) ;;
		*) lib_err ${EX_DATAERR} -- "${health_type} is not a valid health type."
	esac

	local recover_type=`lib_jailparam_name "${recover_cmd}" :`
	recover_cmd=`lib_jailparam_value "${recover_cmd}" :`

	if lib_check_empty "${recover_cmd}"; then
		recover_cmd="${recover_type}"
		recover_type="${DEFAULT_RECOVER_TYPE}"
	fi

	case "${recover_type}" in
		${HEALTHCHECK_RECOVER_HOST_TYPE}|${HEALTHCHECK_RECOVER_JAIL_TYPE}) ;;
		*) lib_err ${EX_DATAERR} -- "${recover_type} is not a valid recover type."
	esac

	if ! lib_check_number "${interval}"; then
		lib_err ${EX_DATAERR} -- "${interval}: interval must be a number."
	fi

	if [ ${interval} -lt 1 ]; then
		lib_err ${EX_DATAERR} -- "${interval}: interval is valid when it is greater than 0."
	fi

	if ! lib_check_number "${recover_kill_after}"; then
		lib_err ${EX_DATAERR} -- "${recover_kill_after}: recover_kill_after must be a number."
	fi

	if ! lib_check_number "${kill_after}"; then
		lib_err ${EX_DATAERR} -- "${kill_after}: kill_after must be a number."
	fi

	if [ ${kill_after} -lt 1 ]; then
		lib_err ${EX_DATAERR} -- "${kill_after}: kill_after is valid when it is greater than 0."
	fi

	if ! lib_check_signal "${recover_timeout_signal}"; then
		lib_err ${EX_NOINPUT} -- "${recover_timeout_signal}: signal not found."
	fi
	
	if ! lib_check_number "${retries}"; then
		lib_err ${EX_DATAERR} -- "${retries}: retries must be a number."
	fi

	if ! lib_check_signal "${timeout_signal}"; then
		lib_err ${EX_NOINPUT} -- "${timeout_signal}: signal not found."
	fi

	if ! lib_check_number "${start_period}"; then
		lib_err ${EX_DATAERR} -- "${start_period}: start_period must be a number."
	fi

	if ! lib_check_number "${recover_total}"; then
		lib_err ${EX_DATAERR} -- "${recover_total}: recover_total must be a number."
	fi

	if [ ${recover_total} -lt 1 ]; then
		lib_err ${EX_DATAERR} -- "${recover_total}: recover_total is valid when it is greater than 0."
	fi

	if ! lib_check_number "${timeout}"; then
		lib_err ${EX_DATAERR} -- "${timeout}: timeout must be a number."
	fi

	if [ ${timeout} -lt 1 ]; then
		lib_err ${EX_DATAERR} -- "${timeout}: timeout is valid when it is greater than 0."
	fi

	if ! lib_check_number "${recover_timeout}"; then
		lib_err ${EX_DATAERR} -- "${recover_timeout}: recover_timeout must be a number."
	fi

	if [ ${recover_timeout} -lt 1 ]; then
		lib_err ${EX_DATAERR} -- "${recover_timeout}: recover_timeout is valid when it is greater than 0."
	fi

	# Creates base dir.

	local jail_path="${JAILDIR}/${jail_name}"
	local basedir="${jail_path}/conf/boot/health"
	if ! mkdir -p "${basedir}"; then
		lib_err ${EX_IOERR} "Error creating ${basedir}"
	fi

	if [ "${nro}" = "auto" ]; then
		nro=`lib_getnro "${basedir}"`
	else
		if ! lib_check_number "${nro}"; then
			lib_err ${EX_DATAERR} "NRO must be a number!"
		fi
	fi

	basedir="${basedir}/${nro}"
	if ! mkdir -p "${basedir}"; then
		lib_err ${EX_IOERR} "Error creating ${basedir}"
	fi

	if [ -z "${opt_enabled}" ]; then
		opt_enabled=`healthcheck_get -In "${nro}" "${jail_name}" enabled`
		opt_enabled="${opt_enabled:-1}"
	fi

	if [ ${opt_name} -eq 0 -a -z "${name}" ]; then
		name=`healthcheck_get -In "${nro}" "${jail_name}" name`
	fi

	# Write.

	printf "%s\n" "${opt_enabled}" > "${basedir}/enabled" || exit ${EX_IOERR} 
	printf "%s\n" "${health_type}" > "${basedir}/health_type" || exit ${EX_IOERR} 
	printf "%s\n" "${health_cmd}" > "${basedir}/health_cmd" || exit ${EX_IOERR} 
	printf "%s\n" "${interval}" > "${basedir}/interval" || exit ${EX_IOERR} 
	printf "%s\n" "${recover_kill_after}" > "${basedir}/recover_kill_after" || exit ${EX_IOERR} 
	printf "%s\n" "${kill_after}" > "${basedir}/kill_after" || exit ${EX_IOERR} 
	printf "%s\n" "${recover_timeout_signal}" > "${basedir}/recover_timeout_signal" || exit ${EX_IOERR} 
	printf "%s\n" "${name}" > "${basedir}/name" || exit ${EX_IOERR} 
	printf "%s\n" "${retries}" > "${basedir}/retries" || exit ${EX_IOERR} 
	printf "%s\n" "${recover_type}" > "${basedir}/recover_type" || exit ${EX_IOERR} 
	printf "%s\n" "${recover_cmd}" > "${basedir}/recover_cmd" || exit ${EX_IOERR} 
	printf "%s\n" "${timeout_signal}" > "${basedir}/timeout_signal" || exit ${EX_IOERR} 
	printf "%s\n" "${recover_total}" > "${basedir}/recover_total" || exit ${EX_IOERR} 
	printf "%s\n" "${start_period}" > "${basedir}/start_period" || exit ${EX_IOERR} 
	printf "%s\n" "${timeout}" > "${basedir}/timeout" || exit ${EX_IOERR} 
	printf "%s\n" "${recover_timeout}" > "${basedir}/recover_timeout" || exit ${EX_IOERR} 
}

_healthcheck_chk_jail()
{
	local jail_name="$1"
	if lib_check_empty "${jail_name}"; then
		healthcheck_usage
		exit ${EX_USAGE}
	fi

	if ! lib_check_jailname "${jail_name}"; then
		lib_err ${EX_DATAERR} "Invalid jail name \"${jail_name}\""
	fi

	if [ ! -d "${JAILDIR}/${jail_name}" ]; then
		lib_err ${EX_NOINPUT} "Cannot find the jail \`${jail_name}\`"
	fi
}

healthcheck_help()
{
	man 1 appjail-healthcheck
}

healthcheck_usage()
{
	cat << EOF
usage: healthcheck get [-eHIipt] -n <nro> <jail>
       healthcheck list [-eHIipt] [-n <nro>] <jail>
       healthcheck remove [all|nro <nro>] <jail>
       healthcheck run <jail>
       healthcheck set [-E|-e] [-h <command>] [-i <seconds>] [-K <seconds>]
               [-k <seconds>] [-l <signal>] [-N <name>] [-n [auto|<nro>]] [-R <number>]
	       [-r <command>] [-S <signal>] [-s <seconds>] [-T <number>] [-t <seconds>]
	       [-u <seconds>] <jail>
EOF
}
