#!/bin/bash
# initscripts functions
#

# width:

STAT_COL=80
if [[ ! -t 1 ]]; then
	USECOLOR=""
elif [[ -t 0 ]]; then
	# stty will fail when stdin isn't a terminal
	STAT_COL="$(/bin/stty size)"
	# stty gives "rows cols"; strip the rows number, we just want columns
	STAT_COL="${STAT_COL##* }"
elif /bin/tput cols &>/dev/null; then
	# is /usr/share/terminfo already mounted, and TERM recognized?
	STAT_COL=$(/bin/tput cols)
fi
if ((STAT_COL==0)); then
	# if output was 0 (serial console), set default width to 80
	STAT_COL=80
	USECOLOR=""
fi

# we use 13 characters for our own stuff
STAT_COL=$(($STAT_COL - 13))

# disable colors on broken terminals
TERM_COLORS="$(/bin/tput colors 2>/dev/null)"
if (($? != 3)); then
	case $TERM_COLORS in
		*[!0-9]*) USECOLOR="";;
		[0-7])    USECOLOR="";;
		'')       USECOLOR="";;
	esac
fi
unset TERM_COLORS

# clear the TZ envvar, so daemons always respect /etc/localtime
unset TZ

# sanitize the locale settins
unset LC_CTYPE LC_NUMERIC LC_TIME LC_COLLATE LC_MONETARY \
      LC_MESSAGES LC_PAPER LC_NAME LC_ADDRESS LC_TELEPHONE \
      LC_MEASUREMENT LC_IDENTIFICATION LC_ALL
if [[ $DAEMON_LOCALE =~ yes|YES && -n $LOCALE ]]; then
	export LANG="${LOCALE}"
else
	export LANG=C
fi

# set colors
if [[ $USECOLOR =~ yes|YES ]]; then
	if /bin/tput setaf 0 &>/dev/null; then
		C_CLEAR="$(tput sgr0)"                      # clear text
		C_MAIN="${C_CLEAR}$(/bin/tput bold)"        # main text
		C_OTHER="${C_MAIN}$(/bin/tput setaf 4)"     # prefix & brackets
		C_SEPARATOR="${C_MAIN}$(/bin/tput setaf 0)" # separator
		C_BUSY="${C_CLEAR}$(/bin/tput setaf 6)"     # busy
		C_FAIL="${C_MAIN}$(/bin/tput setaf 1)"      # failed
		C_DONE="${C_MAIN}"                          # completed
		C_BKGD="${C_MAIN}$(/bin/tput setaf 5)"      # backgrounded
		C_H1="${C_MAIN}"                            # highlight text 1
		C_H2="${C_MAIN}$(/bin/tput setaf 6)"        # highlight text 2
	else
		C_CLEAR="\e[m"          # clear text
		C_MAIN="\e[;1m"         # main text
		C_OTHER="\e[1;34m"      # prefix & brackets
		C_SEPARATOR="\e[1;30m"  # separator
		C_BUSY="\e[;36m"        # busy
		C_FAIL="\e[1;31m"       # failed
		C_DONE="${C_MAIN}"      # completed
		C_BKGD="\e[1;35m"       # backgrounded
		C_H1="${$C_MAIN}"       # highlight text 1
		C_H2="\e[1;36m"         # highlight text 2
	fi
fi

if [[ -t 1 ]]; then
	SAVE_POSITION="\e[s"
	RESTORE_POSITION="\e[u"
	DEL_TEXT="\e[$(($STAT_COL+4))G"
else
	SAVE_POSITION=""
	RESTORE_POSITION=""
	DEL_TEXT=""
fi

# prefixes:

PREFIX_REG="::"
PREFIX_HL=" >"

# functions:

deltext() {
	printf "${DEL_TEXT}"
}

printhl() {
	printf "${C_OTHER}${PREFIX_HL} ${C_H1}${1}${C_CLEAR} \n"
}

printsep() {
	printf "\n${C_SEPARATOR}   ------------------------------\n"
}

stat_bkgd() {
	printf "${C_OTHER}${PREFIX_REG} ${C_MAIN}${1}${C_CLEAR} "
	deltext
	printf "   ${C_OTHER}[${C_BKGD}BKGD${C_OTHER}]${C_CLEAR} "
}

stat_busy() {
	printf "${C_OTHER}${PREFIX_REG} ${C_MAIN}${1}${C_CLEAR} "
	printf "${SAVE_POSITION}"
	deltext
	printf "   ${C_OTHER}[${C_BUSY}BUSY${C_OTHER}]${C_CLEAR} "
}

stat_append() {
	printf "${RESTORE_POSITION}"
	printf -- "${C_MAIN}${1}${C_CLEAR}"
	printf "${SAVE_POSITION}"
}

stat_done() {
	deltext
	printf "   ${C_OTHER}[${C_DONE}DONE${C_OTHER}]${C_CLEAR} \n"
}

stat_fail() {
	deltext
	printf "   ${C_OTHER}[${C_FAIL}FAIL${C_OTHER}]${C_CLEAR} \n"
}

stat_die() {
	stat_fail
	exit ${1:-1}
}

status() {
	stat_busy "$1"
	shift
	if "$@" >/dev/null 2>&1; then
		stat_done
		return 0
	fi
	stat_fail
	return 1
}

#  usage : in_array( $needle, $haystack )
# return : 0 - found
#          1 - not found
# Copied from makepkg
in_array() {
	[[ $2 ]] || return 1
	local needle=$1; shift
	local item
	for item in "$@"; do
		[[ ${item#@} = $needle ]] && return 0
	done
	return 1 # Not Found
}

# daemons:

add_daemon() {
	[[ -d /run/daemons ]] || /bin/mkdir -p /run/daemons
	> /run/daemons/"$1"
}

rm_daemon() {
	/bin/rm -f /run/daemons/"$1"
}

ck_daemon() {
	[[ ! -f /run/daemons/$1 ]]
}

# Check if $1 is a valid daemon name
have_daemon() {
	[[ -f /etc/rc.d/$1 && -x /etc/rc.d/$1 ]]
}

# Check if $1 is started at boot
ck_autostart() {
	local d
	for d in "${DAEMONS[@]}"; do
		[[ "$1" = ${d#@} ]] && return 1
	done
	return 0
}

start_daemon() {
	have_daemon "$1" && /etc/rc.d/"$1" start
}

ck_depends() {
	for daemon in "$@"; do
		ck_daemon "$daemon" && start_daemon "$daemon"
	done
}

start_daemon_bkgd() {
	stat_bkgd "Starting $1"
	have_daemon "$1" && (start_daemon "$1") &>/dev/null &
}

stop_daemon() {
	have_daemon "$1" && /etc/rc.d/"$1" stop
}

# Status functions
status_started() {
	deltext
	echo -ne "$C_OTHER[${C_STRT}STARTED$C_OTHER]$C_CLEAR "
}

status_stopped() {
	deltext
	echo -ne "$C_OTHER[${C_STRT}STOPPED$C_OTHER]$C_CLEAR "
}

ck_status() {
	if ! ck_daemon "$1"; then
		status_started
	else
		status_stopped
	fi
}

# PIDs to be omitted by killall5
declare -a omit_pids

add_omit_pids() {
	omit_pids+=( $@ )
}


kill_everything() {
	# $1 = where we are being called from.
	# This is used to determine which hooks to run.
	# Find daemons NOT in the DAEMONS array. Shut these down first
	for daemon in /run/daemons/*; do
		[[ -f $daemon ]] || continue
		daemon=${daemon##*/}
		in_array "$daemon" "${DAEMONS[@]}" || stop_daemon "$daemon"
	done

	# Shutdown daemons in reverse order
	for ((i=${#DAEMONS[@]}-1; i>=0; i--)); do
		[[ ${DAEMONS[$i]:0:1} = '!' ]] && continue
		ck_daemon ${DAEMONS[$i]#@} || stop_daemon ${DAEMONS[$i]#@}
	done

	# Terminate all processes
	stat_busy "Sending SIGTERM To Processes"
	run_hook "$1_prekillall"
	local pid k5args=""
	for pid in ${omit_pids[@]}; do
		k5args+=" -o $pid"
	done
	/sbin/killall5 -15 $k5args &> /dev/null
	/bin/sleep 5
	stat_done

	stat_busy "Sending SIGKILL To Processes"
	/sbin/killall5 -9 $k5args &> /dev/null
	/bin/sleep 1
	stat_done

	run_hook "$1_postkillall"
}

activate_vgs() {
	[[ $USELVM =~ yes|YES && -x /sbin/lvm && -d /sys/block ]] || return
	# Kernel 2.6.x, LVM2 groups
	/sbin/modprobe -q dm-mod 2>/dev/null
	stat_busy "Activating LVM2 groups"
	if /sbin/vgchange --sysinit -a y >/dev/null; then
		stat_done
	else
		stat_fail
	fi
}

# Arch cryptsetup packages traditionally contained the binaries
#  /usr/sbin/cryptsetup
#  /sbin/cryptsetup.static
# By default, initscripts used the /sbin/cryptsetup.static.
# Newer packages will only have /sbin/cryptsetup and no static binary
# This ensures maximal compatibility with the old and new layout
for CS in /sbin/cryptsetup /usr/sbin/cryptsetup \
	/sbin/cryptsetup.static ''; do
	[[ -x $CS ]] && break
done

read_crypttab() {
	# $1 = function to call with the split out line from the crypttab
	local line nspo failed=0
	while read line; do
		[[ $line && ${line:0:1} != '#' ]] || continue
		eval nspo=("${line%#*}")
		if $1 "${nspo[0]}" "${nspo[1]}" "${nspo[2]}" "${nspo[*]:3}"; then
			crypto_unlocked=1
		else
			failed=1
		fi
	done < /etc/crypttab
	return $failed
}

###############################
# Custom hooks in initscripts #
###############################
# Hooks can be used to include custom code in various places in the rc.* scripts
#
# Define a hook function in a functions.d file using:
#  function_name() {
#    ...
#  }
#  add_hook hook_name function_name
# It is allowed to register several hook functions for the same hook
# Is is also allowed to register the same hook function for several hooks
#
# Currently, the following hooks exist:
# sysinit_start: at the beginning of rc.sysinit
# multi_start: at the beginning of rc.multi
# single_start: at the beginning of rc.single
# shutdown_start: at the beginning of rc.shutdown
# sysinit_end: at the end of rc.sysinit
# multi_end: at the end of rc.multi
# single_end: at the end of rc.single
# sysinit_udevlaunched: after udev has been launched in rc.sysinit
# single_udevlaunched: after udev has been launched in rc.single
# sysinit_udevsettled: after uevents have settled in rc.sysinit
# single_udevsettled: after uevents have settled in rc.single
# sysinit_premount: before local filesystems are mounted, but after root is mounted read-write in rc.sysinit
# shutdown_prekillall: before all processes are being killed in rc.shutdown
# single_prekillall: before all processes are being killed in rc.single
# shutdown_postkillall: after all processes have been killed in rc.shutdown
# single_postkillall: after all processes have been killed in rc.single
# shutdown_poweroff: directly before powering off in rc.shutdown
#
# Declare add_hook and run_hook as read-only to prevent overwriting them.
# Too bad we cannot do the same thing with hook_funcs

if [[ $RC_FUNCTIONS_HOOK_FUNCS_DEFINED -ne 1 ]]; then
	declare -A hook_funcs

	add_hook() {
		[[ $1 && $2 ]] || return 1
		hook_funcs["$1"]+=" $2"
	}

	run_hook() {
		[[ $1 ]] || return 1
		local func
		for func in ${hook_funcs["$1"]}; do
			"${func}"
		done
	}

	declare -fr add_hook run_hook
	declare -r RC_FUNCTIONS_HOOK_FUNCS_DEFINED=1
fi

# Function for setting console font if required
set_consolefont() {
	[[ $CONSOLEFONT ]] || return 0
	stat_busy "Loading Console Font: $CONSOLEFONT"
	#CONSOLEMAP in UTF-8 shouldn't be used
	[[ $CONSOLEMAP && ${LOCALE,,} =~ utf ]] && CONSOLEMAP=""
	for i in /dev/tty[0-9]*; do
		/usr/bin/setfont ${CONSOLEMAP:+-m ${CONSOLEMAP}} \
		$CONSOLEFONT -C ${i} >/dev/null 2>&1
	done
	if (($? != 0)); then
		stat_fail
	elif [[ $CONSOLEMAP ]]; then
		cat <<"EOF" >>/etc/profile.d/locale.sh
if [ "$CONSOLE" = "" -a "$TERM" = "linux" -a -t 1 ]; then printf "\033(K"; fi
EOF
		stat_done
	else
		stat_done
	fi
}

# Source additional functions at the end to allow overrides
for f in /etc/rc.d/functions.d/*; do
	[[ -e $f ]] && . "$f"
done

# End of file
# vim: set ts=2 sw=2 noet:
