#
# Shell functions to be used by the scripts in /etc/init.d/.
#
# (C) Copyright 2002-2007, Axis Communications AB, LUND, SWEDEN
# This file is released under the GPL v2.

# Uncomment to deactivate respawnd.
# local _USE_RESPAWND=0

# path to rc-lib [other (init)?scripts sourcing this file need it!]
RCLIB=/lib/rcscripts

# Terminal colors
NORMAL='\033[0m'
GOOD='\033[32;01m'
WARN='\033[33;01m'
BAD='\033[31;01m'
BRACKET='\033[34;01m'

# Terminal cursor positions.
# It's tempting to use absolute column positions (Esc [ n G sequences), but
# some terminal emulators can't handle them so let's use newline followed by
# relative up and right movement instead.
OKPOS='\n\033[2A\033[72C'
FAILPOS='\n\033[2A\033[71C'
INFOPOS='\n\033[A\033[4C'

# Other variables
local _MAX_STOP_RETRIES=1	# while trying to stop daemons
local _SSD_OPT=			# start-stop-daemon options

# init state done file
ISDF=/var/state/init_done

# lock file directory
local _LFD=/var/lock/init.d

[ ! -r $RCLIB/sh/files.sh ] || . $RCLIB/sh/files.sh
[ ! -r $RCLIB/sh/messages.sh ] || . $RCLIB/sh/messages.sh

#
# Prints warning message(s) to stdout.
#
warning() {
	echo -e "$INFOPOS${WARN}!$NORMAL $*"
}

#
# Prints error message(s) to stdout and exits.
#
error() {
	echo -e "$INFOPOS${BAD}!!!$NORMAL $*"
	exit 1
}

#
# Prints info message(s) to stdout.
#
information() {
	echo -e "$INFOPOS${GOOD}+$NORMAL $*"
}

#
# Lock handlers.
#
validate_lock_fn() {
	[ $# -eq 2 ] || error "$0: validate_lock_fn: missing argument"

	local __lf

	__lf=${1##*/}
	[ "$__lf" = "$1" ] ||
		warning "$0: validate_lock_fn: invalid file name;" \
			"stripped to '$__lf'"
	__lf=${__lf%.lock}
	[ "$__lf" = "$1" ] ||
		warning "$0: validate_lock_fn: invalid file name;" \
			"stripped to '$__lf'"
	eval "$2"="$_LFD/$__lf.lock" ||
		 error "$0: validate_lock_fn: can't evaluate '$2=$__lf'"
}

create_lock_file() {
	local _lf _w _maxw _msg _pid _statf _tab _proc

	[ $# -eq 1 ] || error "$0: create_lock_file: missing argument"

	validate_lock_fn $1 _lf
	mkdir -p $_LFD ||
		error "$0: create_lock_file: can't create directory $_LFD"
	_tab=$(echo -ne "\t")
	_w=0
	_maxw=310		# max wait 5 min 10 sec
	set -C			# noclobber
	while ! ( echo $$ > $_lf ) 2>/dev/null && [ $_w -lt $_maxw ]; do
		_msg="$0[$$]: waited ${_w}s on $_lf"
		[ ! -e $_lf ] || {
			read _pid < $_lf ||
				warning "$0: create_lock_file: read pid" \
					"from $_lf failed"
			[ -z "$_pid" ] || {
				_statf=/proc/$_pid/status
				[ ! -r $_statf ] || {
					if read _proc < $_statf; then
						_proc=${_proc#*[ $_tab]}
					else
						_proc="???"
					fi
					_msg="$_msg ($_proc[$_pid])"
				}
			}
		}
		sleep 1
		_w=$(($_w + 1))
		warning "$WARN$_msg$NORMAL" >/dev/console
	done
	set +C
}

remove_lock_file() {
	local _lf

	[ $# -eq 1 ] || error "$0: remove_lock_file: missing argument"

	validate_lock_fn $1 _lf

	[ ! -e $_lf ] || {
		read _pid < $_lf ||
			warning "$0: remove_lock_file: read pid " \
				"from $_lf failed"
		[ "$_pid" != $$ ] || rm -f $_lf
	}
}

#
# Prints the beginning of an initscript's output.
#
begin() {
	echo -e " ${GOOD}*${NORMAL} $*... "
}

#
# Prints the end of an initscript's output.
#
end() {
	[ $# -gt 0 ] || error "end: missing argument(s)"

	if [ "$1" = 0 ]; then
		echo -e "${OKPOS}${BRACKET}[ ${GOOD}ok${BRACKET} ]${NORMAL}"
	else
		shift
		[ -z "$1" ] || echo -e "${INFOPOS}${BAD}!${NORMAL} $*"

		echo -e "${FAILPOS}${BRACKET}[ ${BAD}fail${BRACKET} ]${NORMAL}"
		exit 1
	fi
}

#
# Conditionally execute respawn-on/off.
#
respawn_on_off() {
	local _on_off

	[ $# -gt 1 ] || error "respawn_on_off: missing arguments"

	if [ "$(pidof respawnd)" ]; then
		_on_off=$1
		shift

		case "$_on_off" in
			1|[Oo][Nn]|[Yy]*)
				_on_off=on
				;;
			0|[Oo][Ff]*|[Nn]*)
				_on_off=off
				;;
			*)
				error "respawn_on_off: " \
					"'1|on|[Yy]0|off|[Nn]' expected"
				;;
		esac

		case "$_USE_RESPAWND" in
			0|[Oo][Ff]*|[Nn]*)
				warning "respawn-$_on_off will not execute."
				return 0
				;;
		esac

		/sbin/respawn-$_on_off "$@"
	else
		warning "respawnd not running."
	fi
}

#
# Convenience respawn_on_off wrappers to be used by scipts which absolutly
# must run respawn-on/off directly.
#
respawn_on() {
	[ $# -gt 0 ] || error "respawn_on: missing argument(s)"

	respawn_on_off on "$@"
}

respawn_off() {
	[ $# -gt 0 ] || error "respawn_off: missing argument(s)"

	respawn_on_off off "$@"
}

#
# Checks if the argument is a pid of a process that is alive.
#
check_pid() {
	[ $# -eq 1 ] || error "check_pid: missing argument"

	[ -d /proc/$1 ] || return 1
	return 0
}

#
# Fetch the process command line from /proc/$1. $1 argument is a pid.
#
get_cmdl() {
	local _retval _pcf

	[ $# -eq 1 ] || error "get_cmdl: missing argument"

	_retval=

	if check_pid $1; then
		_pclf=/proc/$1/cmdline
		if [ -f $_pclf ]; then
			_retval=$(cat $_pclf | tr '\0' ' ')
		else
			warning "Process '$1' command line not found"
		fi
	else
		warning "Process '$1' not found"
	fi

	echo -n "$_retval"
	return 0
}

#
# Checks if all arguments match the command line used to start a currently
# running process, exactly.
#
check_cmd() {
	local _basename _pids _cmdline _pid

	[ $# -gt 0 ] || error "check_cmd: missing argument(s)"

	_basename="${1##*/}"
	_basename="${_basename%% *}"

	_pids=$(pidof "$_basename")
	[ "$_pids" ] || return 1

	_cmdline="$@ "
	for _pid in $_pids; do
	    [ "$(get_cmdl $_pid)" = "$_cmdline" ] && return 0 || :
	done

	return 1
}

#
# Kills the process whose pid is the argument.
#
kill_pid() {
	[ $# -eq 1 ] || error "kill_pid: missing argument"

	kill -TERM $1 || return 1

	# Any survivors?
	if check_pid $1 && sleep 1 &&
	   check_pid $1 && sleep 2 &&
	   check_pid $1 && sleep 3 &&
	   check_pid $1; then
		kill -KILL $1
	fi
}

#
# Starts the daemon whose filename is in the first argument after any -c or -g
# option. If the last argument is an ampersand (&) the daemon will be started
# in the background. Any other arguments will be passed on to the daemon as
# command line options.
#
start_daemon() {
	local _basename _exec _args _argc _arg _user _group _usergroup

	[ $# -gt 0 ] || error "start_daemon: missing argument(s)"

	_basename="${1##*/}"
	_basename="${_basename%% *}"

	# Check user- and group arguments.
	while getopts "c:g:" opt; do
		case "$opt" in
			c)
				_user=$OPTARG
				;;
			g)
				_group=$OPTARG
				;;
		esac
	done
	shift $(($OPTIND - 1))

	if [ "$_user" ] || [ "$_group" ]; then
		[ "$_user" ] || _user=root
		[ "$_group" ] || _group=root
		_usergroup="-c $_user:$_group"
		_user="-c $_user"
		_group="-g $_group"
	fi

	# Assume absolute path to the daemon executable.
	_exec="$1"
	shift

	case "$*" in
		*\ \&)
			if [ $# -ge 1 ]; then
				_args="\"$1\""
				_argc=1
				shift
				for _arg in "$@"; do
					if [ $_argc -lt $# ]; then
						_args="$_args \"$_arg\""
					fi
					_argc=$(($_argc + 1))
				done
			fi
			if eval /sbin/start-stop-daemon --start --background \
					$_usergroup $_SSD_OPT --exec \
					\"$_exec\" -- $_args; then
				eval respawn_on_off on $_user $_group \
					\"$_exec\" $_args ||
					warning "respawn-on \"$_exec\"" \
						"$_args failed!"
				warning "${WARN}$_exec will block respawnd" \
					"unless it daemonizes itself.$NORMAL" \
					>/dev/console
			else
				return 1
			fi
			;;
		*)
			if /sbin/start-stop-daemon --start \
				$_usergroup $_SSD_OPT --exec "$_exec" \
				-- "$@"; then
				respawn_on_off on $_user $_group "$_exec" \
					"$@" ||
					warning "respawn-on $_exec $* failed!"
			else
				return 1
			fi
			;;
	esac
}

#
# start_daemon with a twist.
# Returned error code 1 = nothing done => 0 if --oknodo.
#
start_daemon_oknodo() {
	[ $# -gt 0 ] || error "start_daemon_oknodo: missing argument(s)"

	_SSD_OPT="$_SSD_OPT --oknodo"
	start_daemon "$@"
}

#
# Stop a daemon. The method used to stop the daemon depends on the arguments:
#
# * Only one argument that contains slashes: stop all processes that are
#   instances of that executable.
# * More than one argument: stop all processes that are started by the
#   commandline specified by all arguments together.
#
stop_daemon() {
	local _daemonpath _basename _pidfile _cmdline _ret _retries _pids _pid

	[ $# -gt 0 ] || error "stop_daemon: missing argument(s)"

	respawn_on_off off "$@" || warning "$0: respawn-off $@ failed!"

	_daemonpath="${1%% *}"
	_basename="${_daemonpath##*/}"
	_pidfile="/var/run/$_basename.pid"

	if [ -f "$_pidfile" ]; then
		if /sbin/start-stop-daemon --stop --retry 5 \
			--pidfile "$_pidfile"; then
			return 0
		fi

		# That didn't work. Remove the pidfile and try other methods.
		rm -f "$_pidfile"
	fi

	# Get the absolute path to the daemon executable.
	if [ "$_daemonpath" = "$_basename" ]; then
		_daemonpath=$(command which "$_basename")
	fi

	if [ $# -eq 1 ]; then
		/sbin/start-stop-daemon --stop --retry 5 --oknodo \
			--exec "$_daemonpath"
	else
		_cmdline="$@ "

		_ret=1
		_retries=0
		while [ $_retries -le $_MAX_STOP_RETRIES ]; do
			_pids=$(pidof "$_basename")

			# No pids, no problems.
			[ "$_pids" ] || return 0

			for _pid in $_pids; do
				if [ "$(get_cmdl $_pid)" = "$_cmdline" ]; then
					kill_pid $_pid && _ret=0 || :
				fi
			done

			# Only try again if we failed. Then it might have been
			# a process started in the background that forked and
			# therefore got a new pid between pidof and kill.
			[ $_ret -ne 0 ] || return 0

			_retries=$(($_retries + 1))
		done

		return 1
	fi
}
