# Distributed under the terms of the GNU General Public License v2

# Author:     Michal Januszewski <spock@gentoo.org>
# Maintainer: Michal Januszewski <spock@gentoo.org>

# This file is a part of splashutils. The functions contained in this
# file are meant to be used by hook scripts in splash themes or by
# initscript systems. The code will be kept distro-agnostic to facilitate
# portability. It should also contain POSIX compatible code. No bashisms
# allowed!

# ####################################################################
#    Change any settings ONLY if you are sure what you're doing.
#    Don't cry if it breaks afterwards.
# ####################################################################

# The splash scripts need a cache which can be guaranteed to be
# both readable and writable at all times, even when the root fs
# is mounted read-only. To that end, an in-RAM fs is used. Valid
# values for spl_cachetype are 'tmpfs' and 'ramfs'. spl_cachesize
# is a size limit in KB, and it should probably be left with the
# default value.
export spl_cachesize="4096"
export spl_cachetype="tmpfs"
export spl_cachedir="/lib/splash/cache"
export spl_tmpdir="/lib/splash/tmp"
export spl_fifo="${spl_cachedir}/.splash"
export spl_pidfile="${spl_cachedir}/daemon.pid"
export spl_util="//sbin/splash_util.static"
export spl_daemon="//sbin/fbsplashd.static"
export spl_decor="//sbin/fbcondecor_ctl.static"
export spl_bindir="/lib/splash/bin"

# This is the main function which handles all events.
# Accepted parameters:
#  svc_start <name>
#  svc_stop <name>
#  svc_started <name>
#  svc_stopped <name>
#  svc_start_failed <name>
#  svc_stop_failed <name>
#  svc_input_begin <name>
#  svc_input_end <name>
#  rc_init <internal_runlevel> - used to distinguish between 'boot' and 'sysinit'
#  rc_exit
#  critical
splash() {
	local event="$1"
	shift

	# Reload the splash settings in rc_init.  We could have set them wrong the
	# first time splash_setup was called (when splash-functions.sh was first
	# sourced) if /proc wasn't mounted.
	if [ "${event}" = "rc_init" ]; then
		splash_setup "force"
	else
		splash_setup
	fi

	[ "${SPLASH_MODE_REQ}" = "off" ] && return

	# Prepare the cache here -- rc_init-pre might want to use it
	if [ "${event}" = "rc_init" ]; then
		if [ "${RUNLEVEL}" = "S" -a "$1" = "sysinit" ]; then
			splash_cache_prep 'start' || return
		elif [ "${RUNLEVEL}" = "6" -o "${RUNLEVEL}" = "0" ]; then
			# Check if the splash cachedir is mounted readonly. If it is,
			# we need to mount a tmpfs over it.
			if ! touch "${spl_cachedir}/message" 2>/dev/null ; then
				splash_cache_prep 'stop' || return
			fi
		fi
	fi

	local args=""

	if [ "${event}" = "rc_init" -o "${event}" = "rc_exit" ]; then
		args="$* ${RUNLEVEL}"
	elif [ "${event}" = "svc_started" -o "${event}" = "svc_stopped" ]; then
		if [ -z "$2" ]; then
			# Backwards compatibility hack.  Add a 0 to the arguments to simulate
			# an error code.
			args="$* 0"
		else
			args="$*"

			# Backwards compatibility: translate an error condition (non-zero
			# error code) into an appropriate event.
			if [ "$2" != "0" ]; then
				if [ "${event}" = "svc_started" ]; then
					event="svc_start_failed"
				else
					event="svc_stop_failed"
				fi
			fi
		fi
	else
		args="$*"
	fi

	splash_profile "pre ${event} ${args}"

	# Handle -pre event hooks
	if [ -x "/etc/splash/${SPLASH_THEME}/scripts/${event}-pre" ]; then
		/etc/splash/"${SPLASH_THEME}"/scripts/${event}-pre ${args}
	fi

	case "$event" in
		svc_start)			splash_svc_start "$1";;
		svc_stop)			splash_svc_stop "$1";;
		svc_started) 		splash_svc "$1" "start";;
		svc_stopped)		splash_svc "$1" "stop";;
		svc_start_failed)	splash_svc_fail "$1" "start";;
		svc_stop_failed)	splash_svc_fail "$1" "stop";;
		svc_input_begin)	splash_input_begin "$1";;
		svc_input_end)		splash_input_end "$1";;
		rc_init) 			splash_init "$1" "${RUNLEVEL}";;
		rc_exit) 			splash_exit "${RUNLEVEL}";;
		critical) 			splash_verbose;;
	esac

	splash_profile "post ${event} ${args}"

	# Handle -post event hooks
	if [ -x "/etc/splash/${SPLASH_THEME}/scripts/${event}-post" ]; then
		/etc/splash/"${SPLASH_THEME}"/scripts/${event}-post ${args}
	fi

	return 0
}

splash_setup() {
	# If it's already set up, let's not waste time on parsing the config
	# files again
	if [ "${SPLASH_THEME}" != "" -a "${SPLASH_TTY}" != "" -a "$1" != "force" ]; then
		return 0
	fi

	export SPLASH_EFFECTS=""
	export SPLASH_SANITY=""
	export SPLASH_TEXTBOX="no"
	export SPLASH_MODE_REQ="off"
	export SPLASH_PROFILE="off"
	export SPLASH_THEME="default"
	export SPLASH_TTY="16"
	export SPLASH_KDMODE="TEXT"
	export SPLASH_AUTOVERBOSE="0"
	export SPLASH_BOOT_MESSAGE="Booting the system (\$progress%)... Press F2 for verbose mode."
	export SPLASH_SHUTDOWN_MESSAGE="Shutting down the system (\$progress%)... Press F2 for verbose mode."
	export SPLASH_REBOOT_MESSAGE="Rebooting the system (\$progress%)... Press F2 for verbose mode."
	export SPLASH_XSERVICE="xdm"

	[ -f /etc/splash/splash ] && . /etc/splash/splash
	[ -f /etc/conf.d/splash ] && . /etc/conf.d/splash
	[ -f /etc/conf.d/fbcondecor ] && . /etc/conf.d/fbcondecor

	if [ -f /proc/cmdline ]; then
		options=$(grep -o 'splash=[^ ]*' /proc/cmdline)

		# Execute this loop over $options so that we can process multiple
		# splash= arguments on the kernel command line. Useful for adjusting
		# splash parameters from ISOLINUX.
		for opt in ${options} ; do
			options=${opt#*=}

			for i in $(echo "${options}" | sed -e 's/,/ /g') ; do
				case ${i%:*} in
					theme)		SPLASH_THEME=${i#*:} ;;
					tty)		SPLASH_TTY=${i#*:} ;;
					verbose) 	SPLASH_MODE_REQ="verbose" ;;
					silent)		SPLASH_MODE_REQ="silent" ;;
					kdgraphics)	SPLASH_KDMODE="GRAPHICS" ;;
					profile)	SPLASH_PROFILE="on" ;;
					insane)		SPLASH_SANITY="insane" ;;
				esac
			done
		done
	fi
}

splash_get_boot_message() {
	if [ "${RUNLEVEL}" = "6" ]; then
		echo "${SPLASH_REBOOT_MESSAGE}"
	elif [ "${RUNLEVEL}" = "0" ]; then
		echo "${SPLASH_SHUTDOWN_MESSAGE}"
	else
		echo "${SPLASH_BOOT_MESSAGE}"
	fi
}

splash_start() {
	if [ "${SPLASH_MODE_REQ}" = "verbose" ]; then
		${spl_decor} -c on 2>/dev/null
		return 0
	elif [ "${SPLASH_MODE_REQ}" != "silent" ]; then
		return 0
	fi

	# Display a warning if the system is not configured to display init messages
	# on tty1. This can cause a lot of problems if it's not handled correctly, so
	# we don't allow silent splash to run on incorrectly configured systems.
	if [ "${SPLASH_MODE_REQ}" = "silent" -a "${SPLASH_SANITY}" != "insane" ]; then
		if [ -z "$(grep -E '(^| )CONSOLE=/dev/tty1( |$)' /proc/cmdline)" -a \
			 -z "$(grep -E '(^| )console=tty1( |$)' /proc/cmdline)" ]; then
			clear
			splash_warn "You don't appear to have a correct console= setting on your kernel"
			splash_warn "command line. Silent splash will not be enabled. Please add"
			splash_warn "console=tty1 or CONSOLE=/dev/tty1 to your kernel command line"
			splash_warn "to avoid this message."
			if [ -n "$(grep 'CONSOLE=/dev/tty1' /proc/cmdline)" -o \
				  -n "$(grep 'console=tty1' /proc/cmdline)" ]; then
				splash_warn "Note that CONSOLE=/dev/tty1 and console=tty1 are general parameters and"
				splash_warn "not splash= settings."
			fi
			return 1
		fi

		if [ -n "$(grep -E '(^| )CONSOLE=/dev/tty1( |$)' /proc/cmdline)" ]; then
			mount -n --bind / ${spl_tmpdir}
			if [ ! -c "${spl_tmpdir}/dev/tty1" ]; then
				umount -n ${spl_tmpdir}
				splash_warn "The filesystem mounted on / doesn't contain the /dev/tty1 device"
				splash_warn "which is required for the silent splash to function properly."
				splash_warn "Silent splash will not be enabled. Please create the appropriate"
				splash_warn "device node to avoid this message."
				return 1
			fi
			umount -n ${spl_tmpdir}
		fi
	fi

	rm -f "${spl_pidfile}"

	# Prepare the communications FIFO
	rm -f "${spl_fifo}" 2>/dev/null
	mkfifo "${spl_fifo}"

	local options=""
	[ "${SPLASH_KDMODE}" = "GRAPHICS" ] && options="--kdgraphics"
	[ -n "${SPLASH_EFFECTS}" ] && options="${options} --effects=${SPLASH_EFFECTS}"
	[ "${SPLASH_TEXTBOX}" = "yes" ] && options="${options} --textbox"

	local ttype="bootup"
	if [ "${RUNLEVEL}" = "6" ]; then
		ttype="reboot"
	elif [ "${RUNLEVEL}" = "0" ]; then
		ttype="shutdown"
	fi

	# Start the splash daemon
	BOOT_MSG="$(splash_get_boot_message)" ${spl_daemon} --theme="${SPLASH_THEME}" --pidfile="${spl_pidfile}" --type=${ttype} ${options}

	# Set the silent TTY and boot message
	splash_comm_send "set tty silent ${SPLASH_TTY}"

	if [ "${SPLASH_MODE_REQ}" = "silent" ]; then
		splash_comm_send "set mode silent"
		splash_comm_send "repaint"
		${spl_decor} -c on 2>/dev/null
	fi

	splash_comm_send "set autoverbose ${SPLASH_AUTOVERBOSE}"

	splash_set_event_dev

	return 0
}

###########################################################################
# Cache-related functions
###########################################################################

splash_cache_prep() {
	# Mount an in-RAM filesystem at spl_cachedir
	mount -ns -t "${spl_cachetype}" cachedir "${spl_cachedir}" \
		-o rw,mode=0755,noexec,nosuid,nodev,size="${spl_cachesize}"k

	retval="$?"

	if [ ${retval} -ne 0 ]; then
		splash_err "Unable to create splash cache - switching to verbose."
		splash_verbose
		return "${retval}"
	fi
}

# args: list of files to save when the cache is umounted
#       Note that the 'profile' file is already handled and thus shouldn't
#       be included in the list.
splash_cache_cleanup() {
	# Don't try to clean anything up if the cachedir is not mounted.
	[ -z "$(grep ${spl_cachedir} /proc/mounts)" ] && return;

	# Create the temp dir if necessary.
	if [ ! -d "${spl_tmpdir}" ]; then
		mkdir -p "${spl_tmpdir}" 2>/dev/null
		[ "$?" != "0" ] && return
	fi

	# Make sure the splash daemon is dead.
	if [ -n "$(pgrep fbsplashd)" ]; then
		sleep 1
		killall -9 "${spl_daemon##*/}" 2>/dev/null
	fi

	# If /etc is not writable, don't update /etc/mtab. If it is
	# writable, update it to avoid stale mtab entries (bug #121827).
	local mntopt=""
	[ -w /etc/mtab ] || mntopt="-n"
	mount ${mntopt} --move "${spl_cachedir}" "${spl_tmpdir}" 2>/dev/null

	# Don't try to copy anything if the cachedir is not writable.
	[ -w "${spl_cachedir}" ] || return

	if [ "${SPLASH_PROFILE}" != "off" ]; then
		cp -a "${spl_tmpdir}/profile" "${spl_cachedir}" 2>/dev/null
	fi

	while [ -n "$1" ]; do
		cp -a "${spl_tmpdir}/$1" "${spl_cachedir}" 2>/dev/null
		shift
	done

	umount -l "${spl_tmpdir}" 2>/dev/null
}

###########################################################################
# Common functions
###########################################################################

# Sends data to the splash FIFO after making sure there's someone
# alive on the other end to receive it.
splash_comm_send() {
	if [ -z "`pidof $(basename ${spl_daemon})`" ]; then
		return 1
	else
		splash_profile "comm $*"
		echo "$*" > "${spl_fifo}" &
	fi
}

# Returns the current splash mode.
splash_get_mode() {
	local ctty="$(${spl_bindir}/fgconsole)"
	local mode="$(${spl_util} -c getmode)"

	if [ "${mode}" = "silent" ]; then
		echo "silent"
	else
		if [ -z "$(${spl_decor} -c getstate --tty=${ctty} 2>/dev/null | grep off)" ]; then
			echo "verbose"
		else
			echo "off"
		fi
	fi
}

# chvt <n>
# --------
# Switches to the n-th tty.
chvt() {
	local ntty=$1

	if [ -x /usr/bin/chvt ] ; then
		/usr/bin/chvt ${ntty}
	else
		printf "\e[12;${ntty}]"
	fi
}

# Switches to verbose mode.
splash_verbose() {
	chvt 1
}

# Switches to silent mode.
splash_silent() {
	splash_comm_send "set mode silent"
}

# Saves profiling information
splash_profile() {
	if [ "${SPLASH_PROFILE}" = "on" ]; then
		echo "$(cat /proc/uptime | cut -f1 -d' '): $*" >> "${spl_cachedir}/profile"
	fi
}

# Set the input device if it exists. This will make it possible to use F2 to
# switch from verbose to silent.
splash_set_event_dev() {
	local t="$(grep -Hsi keyboard /sys/class/input/input*/name | sed -e 's#.*input\([0-9]*\)/name.*#event\1#')"
	if [ -z "${t}" ]; then
		t="$(grep -Hsi keyboard /sys/class/input/event*/device/driver/description | grep -o 'event[0-9]\+')"
		if [ -z "${t}" ]; then
			for i in /sys/class/input/input* ; do
				if [ "$((0x$(cat $i/capabilities/ev) & 0x100002))" = "1048578" ]; then
					t="$(echo $i | sed -e 's#.*input\([0-9]*\)#event\1#')"
				fi
			done

			if [ -z "${t}" ]; then
				# Try an alternative method of finding the event device. The idea comes
				# from Bombadil <bombadil(at)h3c.de>. We're couting on the keyboard controller
				# being the first device handled by kbd listed in input/devices.
				t="$(/bin/grep -s -m 1 '^H: Handlers=kbd' /proc/bus/input/devices | grep -o 'event[0-9]*')"
			fi
		fi
	fi
	[ -n "${t}" ] && splash_comm_send "set event dev /dev/input/${t}"
}

###########################################################################
# Service
###########################################################################

# args: <svc> <action>
splash_svc() {
	local srv="$1"
	local act="$2"

	if [ "${act}" = "start" ]; then
		splash_svc_update "${srv}" "svc_started"
		if [ "${srv}" = "gpm" ]; then
			splash_comm_send "set gpm"
			splash_comm_send "repaint"
		fi
		splash_comm_send "log Service '${srv}' started."
	else
		splash_svc_update "${srv}" "svc_stopped"
		splash_comm_send "log Service '${srv}' stopped."
	fi

	splash_update_progress "${srv}"
}

# args: <svc> <action>
splash_svc_fail() {
	local srv="$1"
	local act="$2"

	if [ "${SPLASH_VERBOSE_ON_ERRORS}" = "yes"  ]; then
		splash_verbose
		return 1
	fi

	if [ "${act}" = "start" ]; then
		splash_svc_update "${srv}" "svc_start_failed"
		splash_comm_send "log Service '${srv}' failed to start."
	else
		splash_svc_update "${srv}" "svc_stop_failed"
		splash_comm_send "log Service '${srv}' failed to stop."
	fi

	splash_update_progress "${srv}"
}

# args: <svc> <state>
#
# Inform the splash daemon about service status changes.
splash_svc_update() {
	splash_comm_send "update_svc $1 $2"
}

# args: <svc>
splash_svc_start() {
	local svc="$1"

	splash_svc_update "${svc}" "svc_start"
	splash_comm_send "paint"
}

# args: <svc>
splash_svc_stop() {
	local svc="$1"

	splash_svc_update "${svc}" "svc_stop"
	splash_comm_send "paint"
}

# args: <svc>
splash_input_begin() {
	local svc="$1"

	if [ "$(splash_get_mode)" = "silent" ]; then
		splash_verbose
		export SPL_SVC_INPUT_SILENT="${svc}"
	fi
}

# args: <svc>
splash_input_end() {
	local svc="$1"

	if [ "${SPL_SVC_INPUT_SILENT}" = "${svc}" ]; then
		splash_silent
		unset SPL_SVC_INPUT_SILENT
	fi
}

###########################################################################
# Framebuffer Console Decorations functions
###########################################################################

fbcondecor_supported()
{
	[ -e /dev/fbsplash -o -e /dev/fbcondecor ]
}

# args: <theme> <tty>
fbcondecor_set_theme()
{
	local theme=$1
	local tty=$2

	[ -x ${spl_decor} ] || return 1

	${spl_decor} --tty="${tty}" -t "${theme}" -c setcfg || return 1
	${spl_decor} --tty="${tty}" -t "${theme}" -c setpic -q
	${spl_decor} --tty="${tty}" -c on
}

###########################################################################
# Service list
###########################################################################

# splash_svclist_get <type>
# -------------------------
# type:
#  - start - to get a list of services to be started during bootup
#  - stop  - to get a list of services to be stopped during shutdown/reboot
splash_svclist_get() {
	if [ "$1" = "start" -a -r "${spl_cachedir}/svcs_start" ]; then
		cat "${spl_cachedir}/svcs_start"
	elif [ "$1" = "stop" -a -r "${spl_cachedir}/svcs_stop" ]; then
		cat "${spl_cachedir}/svcs_stop"
	fi
}

splash_warn() {
	echo "$*"
}

splash_err() {
	echo "$*"
}

############################################################################
# Functions to be overridden by distro-specific code
###########################################################################

# args: <internal_runlevel> <runlevel>
#
# This function is called when an 'rc_init' event takes place,
# i.e. when the runlevel is changed.

# It is normally used to initialize any internal splash-related 
# variables (e.g. ones used to track the boot progress), calculate
# and save the service list and call `splash_start` in appropriate 
# runlevels.
#
# Note that the splash cache is already intialized when this
# function is called.
splash_init() {
	if [ "$2" = "6" -o "$2" = "0" -o "$2" = "S" -a "$1" = "sysinit" ]; then
		splash_start
	fi
}

# args: <runlevel>
#
# This function is called when an 'rc_exit' event takes place,
# i.e. at the end of processes all initscript from a runlevel.
splash_exit() {
	# If we're in sysinit or rebooting, do nothing.
	[ "$1" = "S" -o "$1" = "6" -o "$1" = "0" ] && return 0

	splash_comm_send "exit"
	splash_cache_cleanup
}

# args: <svc>
#
# This function is called whenever the progress variable should be
# updated.  It should recalculate the progress and send it to the
# splash daemon.
splash_update_progress() {
	# splash_comm_send "progress ${progress}"
	# splash_comm_send "paint"
	return
}

# Export functions if we're running bash.
if [ -n "${BASH}" ]; then
	export -f splash
	export -f splash_setup
	export -f splash_get_boot_message
	export -f splash_start
	export -f splash_cache_prep
	export -f splash_cache_cleanup
	export -f splash_comm_send
	export -f splash_get_mode
	export -f chvt
	export -f splash_verbose
	export -f splash_silent
	export -f splash_profile
	export -f splash_set_event_dev
	export -f splash_svclist_get
fi

# Load any supplementary splash functions.
for i in /sbin/splash-functions-*.sh ; do
	[ -r "${i}" ] && . "${i}"
done

splash_setup

# vim:ts=4
