#!/bin/sh

# $Id$
#
# Written by Alexander Kriegisch (user 'kriegaex', ip-phone-forum.de)
#
# Handle boot loader environment 'kernel_args' variables which can either have
# an integer value >= 1 or values of 'y'|'n'.
#
# General information
#   - Setting the environment variable KERNEL_ARGS_READ_PROC != "" activates
#     reading variable values from /proc/sys/urlader/environment, even though
#     this might seem more complicated than necessary, because key-value pairs
#     like FooBar=bla can easily be taken from the shell environment during
#     the system init process. The author wants to provide a way to read/write
#     those variables later, too, i.e. outside the init process when they have
#     already vanished from the environment.
#   - Setting the environment variable KERNEL_ARGS_VERBOSE != "" activates
#     somewhat more verbose output on stdout and error messages on stderr.
#   - So as not make things more complicated than necessary, the author
#     assumes that variables exist only once within 'kernel_args' when
#     determining values: no doubles like 'foo=y bar=n foo=n'. When updating
#     values, though, multiple possibly existing key-value pairs for one
#     variable name are deleted before the new value is appended to the end of
#     'kernel_args'.
# Caveat
#   - The maximum string length of 'kernel_args' is 64 characters!

ka_mountProc() {
	# Description
	#   Mount /proc, if necessary. If a mount was necessary AND successful,
	#   write "y" to stdout, otherwise write nothing (except possible errors
	#   in verbose mode).
	# Parameters
	#   none
	# Return value
	#   0, if /proc was already mounted and boot loader environment exists.
	#   1, if /proc was not mounted before, has just been mounted successfully
	#   and boot loader environment exists. This return values is important
	#   for procedures which want to unmount a temporarily mounted /proc.
	#   99 on error (failed mount or non-existent boot loader environment).

	if [ ! -e /proc/mounts ]; then
		local no_proc=y
		mount proc
	fi
	if [ ! -e /proc/sys/urlader/environment ]; then
		[ "$KERNEL_ARGS_VERBOSE" ] && \
			echo "ka_mountProc - error: cannot access /proc/sys/urlader/environment" >&2
		return 99
	fi
	if [ "$no_proc" ]; then
		echo "y"
		return 1
	fi
	return 0
}

ka_getArgs() {
	# Description
	#   Read 'kernel_args' directly from boot loader environment and print
	#   value to stdout.
	# Parameters
	#   none
	# Return value
	#   0, if value was read successfully.
	#   1 on error (boot loader environment inaccessible).

	local no_proc=$(ka_mountProc)
	if [ "$?" == "99" ]; then
		[ "$KERNEL_ARGS_VERBOSE" ] && \
			echo "ka_getArgs - error: cannot access /proc/sys/urlader/environment" >&2
		return 99
	fi
	cat /proc/sys/urlader/environment | sed -nr 's/^kernel_args[[:space:]]+(.*)/\1/p'
	[ "$no_proc" ] && umount proc
	return 0
}

ka_isValidName() {
	# Description
	#   Check if variable name is C-style (alphanumeric + '_')
	# Parameters
	#   $1 - variable name
	# Return value
	#   0 (true), if variable name is C-style.
	#   1 (false), if it is not.
	#   99 on error.

	if [ -z "$1" ]; then
		[ "$KERNEL_ARGS_VERBOSE" ] && \
			echo "ka_isValidName - error: no variable name specified" >&2
		return 99
	fi
	var=$(echo "$1" | sed -nr 's/^(\w+)$/\1/p')
	if [ "$1" != "$var" ]; then
		return 1
	fi
	return 0
}

ka_isValidValue() {
	# Description
	#   Verify a value's validity
	# Parameters
	#   $1 - value to be verified
	# Return value
	#   0 (true), if value is an integer >= 1 or one of 'y|n' (case-sensitive).
	#   1 (false), if it is not.
	#   99 on error.

	if [ -z "$1" ]; then
		[ "$KERNEL_ARGS_VERBOSE" ] && \
			echo "ka_isValidValue - error: no value specified" >&2
		return 99
	fi
	val=$(echo "$1" | sed -nr 's/^([yn]|([0-9]+))$/\1/p')
	if [ -z "$val" -o "$val" = "0" ]; then
		return 1
	fi
	return 0
}

ka_getKeyValuePair() {
	# Description
	#   Check if variable + value exist in boot loader 'kernel_args' and print
	#   the key-value pair (e.g. "MyVariable=12") to stdout
	# Parameters
	#   $1 - variable name
	# Return value
	#   0 (true), if a valid key-value pair exists for the variable.
	#   1 (false), if the variable name is valid, but not part of a key-value
	#   pair.
	#   99 on error (invalid name or value).

	if ! ka_isValidName "$1"; then
		[ "$KERNEL_ARGS_VERBOSE" ] && \
			echo "ka_getKeyValuePair - error: invalid variable name \"$1\"" >&2
		return 99
	fi
	kv_pair=$(ka_getArgs | sed -nr "s/.*\b($1=\w*)\b.*/\1/p")
	if [ -z "$kv_pair" ]; then
		return 1
	fi
	if ! ka_isValidValue $(echo "$kv_pair" | sed -r "s/$1=//"); then
		[ "$KERNEL_ARGS_VERBOSE" ] && \
			echo "ka_getKeyValuePair - error: invalid value" >&2
		return 99
	fi
	echo "$kv_pair"
	return 0
}

ka_getValue() {
	# Description
	#   Write value of specified variable to stdout. By default, the value is
	#   retrieved from the shell environment, assuming we are withing the init
	#   process. If KERNEL_ARGS_READ_PROC != "", the value is retrieved
	#   directly from the boot loader environment, though.
	# Caveat:
	#   If KERNEL_ARGS_READ_PROC == "" or undefined and you happend to have a
	#   variable in your current environment which conforms to both
	#   ka_isValidName and ka_isValidValue, this variable's value will be
	#   retrieved even if it did not come from kernel_args.
	# Parameters
	#   $1 - variable name
	# Return value
	#   0 (true), if variable exists and has a valid value according to
	#   ka_verifyValue.
	#   99 on error (key-value pair not found or invalid value).

	if [ -z "$KERNEL_ARGS_READ_PROC" ]; then
		# Tricky & ugly nested expansion (not directly supported by ash)
		val=$(eval echo $(echo "\$$1"))
	else
		if ! kv_pair=$(ka_getKeyValuePair "$1"); then
			[ "$KERNEL_ARGS_VERBOSE" ] && \
				echo "ka_getValue - error: found no valid key-value pair for variable \"$1\"" >&2
			return 99
		fi
		val=$(echo "$kv_pair" | sed -r "s/$1=//")
	fi
	if ! ka_isValidValue "$val"; then
		[ "$KERNEL_ARGS_VERBOSE" ] && \
			echo "ka_getValue - error: found invalid value \"$val\" for variable \"$1\"" >&2
		return 99
	fi
	echo "$val"
	return 0
}

ka_setValue() {
	# Description
	#   Set variable to specified value.
	#   The new kernel_args value list is subsequently printed to stdout.
	# Parameters
	#   $1 - variable name
	#   $2 - value (must be 'y', 'n' or integer >= 1)
	# Return value
	#   0 (true), if value is valid as required by ka_verifyValue and was set
	#   successfully. If variable does not exist in environment, it will be
	#   created automatically.
	#   99 on error (invalid variable name or value).

	if ! ka_isValidName "$1"; then
		[ "$KERNEL_ARGS_VERBOSE" ] && \
			echo "ka_setValue - error: invalid variable name \"$1\"" >&2
		return 99
	fi
	if ! ka_isValidValue "$2"; then
		[ "$KERNEL_ARGS_VERBOSE" ] && \
			echo "ka_setValue - error: invalid value \"$2\" for variable \"$1\"" >&2
		return 99
	fi
	new_args=$(ka_removeVariableNoUpdate "$1")
	new_args="$new_args $1=$2"
	if [ "$?" == "99" ]; then
		[ "$KERNEL_ARGS_VERBOSE" ] && \
			echo "ka_setValue - error: cannot access /proc/sys/urlader/environment" >&2
		return 99
	fi
	echo "kernel_args $new_args" > /proc/sys/urlader/environment
	[ "$KERNEL_ARGS_VERBOSE" ] && \
		ka_getArgs
	[ "$no_proc" ] && umount proc
	return 0
}

ka_removeVariable() {
	# Description
	#   Remove variable from environment by deleting it from 'kernel_args',
	#   even multiple occurrences of key-value pairs with the same variable
	#   name and/or with invalid values. Stand-alone variables without values
	#   will also be eliminated. For example, 'kernel_args' list
	#   'foo=1 bar foo=y foo zot=234 foo=bla' will be stripped down to
	#   'bar zot=234' upon the calling 'ka_removeVariable foo'.
	#   The new kernel_args value list is subsequently printed to stdout.
	# Parameters
	#   $1 - variable name to be stripped from 'kernel_args'
	# Return value
	#   0, if variable with a valid name according to ka_isValidName was
	#   removed successfully or did not exist anyway.
	#   Otherwise, a non-zero value will be returned.

	if ! new_args=$(ka_removeVariableNoUpdate "$1"); then
		[ "$KERNEL_ARGS_VERBOSE" ] && \
			echo "ka_removeVariable - error: cannot remove variable \"$1\"" >&2
		return 1
	fi
	if [ "$?" == "99" ]; then
		[ "$KERNEL_ARGS_VERBOSE" ] && \
			echo "ka_removeVariable - error: cannot access /proc/sys/urlader/environment" >&2
		return 99
	fi
	echo "kernel_args $new_args" > /proc/sys/urlader/environment
	[ "$KERNEL_ARGS_VERBOSE" ] && \
		ka_getArgs
	[ "$no_proc" ] && umount proc
	return 0
}

ka_removeVariableNoUpdate() {
	# Description
	#   Do basically the same as ka_removeVariable, but do not update
	#   'kernel_args'. Instead, print hypothetical new value of 'kernel_args'
	#   to stdout, i.e. the value as it would look like after removing all
	#   occurrences of the specified variable.
	#   Actually, this function is used as a helper by ka_removeVariable.
	# Parameters
	#   $1 - variable name to be stripped from 'kernel_args'
	# Return value
	#   0 (true), if variable with a valid name according to ka_isValidName
	#   was removed successfully or did not exist anyway.
	#   99 on error (invalid variable name).

	if ! ka_isValidName "$1"; then
		[ "$KERNEL_ARGS_VERBOSE" ] && \
			echo "ka_removeVariableNoUpdate - error: invalid variable name \"$1\"" >&2
		return 99
	fi
	new_args=$(\
		ka_getArgs \
			| sed -r "s/\b($1\b(=\w*)?)\b//g" \
			| sed -r 's/[[:space:]]+/ /g' \
			| sed -r 's/^[[:space:]](.*)/\1/' \
			| sed -r 's/(.*)[[:space:]]$/\1/'\
	)
	echo "$new_args"
	return 0
}

ka_isPositiveInteger() {
	# Description
	#   Check if specified variable's value is an integer >= 1
	# Parameters
	#   $1 - value to be checked
	# Return value
	#   0 (true), if value is an integer >= 1.
	#   1 (false), if value is valid, but no integer >= 1
	#   99 on error (invalid value).

	if ! ka_isValidValue "$1"; then
		[ "$KERNEL_ARGS_VERBOSE" ] && \
			echo "ka_isPositiveInteger - error: invalid value \"$1\"" >&2
		return 99
	fi
	if [ "$1" = "y" ] || [ "$1" = "n" ] || [ "$1" -le 0 ]; then
		return 1
	fi
	return 0
}

ka_isActiveVariable() {
	# Description
	#   Check if specified variable's value exists, is valid and != 'n'
	#   (i.e. 'y' or an integer >= 1)
	# Parameters
	#   $1 - variable to be checked
	# Return value
	#   0 (true), if variable is either always on ('y') or has a count >= 1.
	#   1 (false), if value is 'n'.
	#   99 on error.

	if ! val=$(ka_getValue "$1"); then
		[ "$KERNEL_ARGS_VERBOSE" ] && \
			echo "ka_isActiveVariable - error: could not determine value of variable \"$1\"" >&2
		return 99
	fi
	if [ "$val" = "n" ]; then
		return 1
	fi
	return 0
}

ka_decreaseValue() {
	# Description
	#   Decrease numeric variable value by 1.
	#   The new kernel_args value list is subsequently printed to stdout.
	# Parameters
	#   $1 - variable name whose value is to be decreased
	# Return value
	#   0 (true), if value was updated successfully or was left unchanged
	#   (non-numeric). Any integer value > 1 will be decreased by 1. The
	#   value 1 will be turned into 'n' (instead of zero), thus effectively
	#   decativating the variable. Non-numeric values will be left untouched,
	#   not yielding an error as long as they are valid (i.e. equal to 'y|n').
	#   99 on error (invalid or non-existent current value or error during
	#   update to new value).

	local KERNEL_ARGS_READ_PROC=1
	if ! val=$(ka_getValue "$1"); then
		[ "$KERNEL_ARGS_VERBOSE" ] && \
			echo "ka_decreaseValue - error: could not determine value of variable \"$1\"" >&2
		return 99
	fi
	unset KERNEL_ARGS_READ_PROC
	if ! ka_isPositiveInteger "$val" 2>/dev/null; then
		[ "$KERNEL_ARGS_VERBOSE" ] && \
			ka_getArgs
		return 0
	fi
	old_val="$val"
	[ $((--val > 0)) == 0 ] && val="n";
	if [ "$val" != "$old_val" ] && ! ka_setValue "$1" "$val"; then
		[ "$KERNEL_ARGS_VERBOSE" ] && \
			echo "ka_decreaseValue - error: could not update variable \"$1\" with value \"$val\"" >&2
		return 99
	fi
	# Do not call ka_getArgs here, because ka_setValue already prints the new
	# value of kernel_args in verbose mode
	return 0
}