:
# NAME:
#	rc.sh - run commands
#
# SYNOPSIS:
#	rc.sh [-v] [-n] [-e] [-d "dir"] [-c "class"] ["args"]
#
# DESCRIPTION:
#	This is a generic rc script inspired by rc2 et al from SysV.
#	
#	This script looks for all files that start with "class"
#	in the directory "dir" (default is the script name without
#	'.sh' and with '.d' appended) and executes them. 
#
#	If the file name ends in '.sh' it is sourced into the
#	current process, otherwise it is run as a child process with
#	"args" as argument.  The treatment of '.sh' files provides a
#	mechanism for controlling behaviour of subsequent modules as
#	well as 'rc.sh' itself.
#
#	When running a child process '.pl' files are fed to perl(1) or
#	whatever the "PERL" variable names, and everything else is fed
#	to the shell.
#	
#	If "class" is 'S' then the default "args" is 'start'.
#
#	If "class" is 'K' then the default "args" is 'stop'.
#
#	If "args" contains any ``:'' characters then each word in
#	"args" is assumed to be a "dir":"class":"arg" tupple.
#	
#	If neither "class" nor "args" are specified then the default
#	behaviour is as if "args" were ``"dir":K:stop "dir":S:start''
#	which provides a reasonable imitation of rc"N" and is
#	generally quite useful.
#
#	Options:
#
#	-e	Stop on error.	Unlike its namesake in sh(1) we do not
#		exit if any command in any script fails, rather we
#		exit if one of the scripts (sourced or run) returns a
#		non-zero status. Note, setting ErrExit=exit in a .sh
#		file has the same effect.
#
#	-v	Echo each script name as it is processed.
#
#	-n	Do nothing, just show what would be done.
#
#	-d "dir"
#		Process files in "dir".
#
#	-c "class"
#		Process files in "dir" that start with "class".
#
#	Functions:
#
#	The following functions form part of the 'rc.sh'
#	implementation and are available for use from other '.sh'
#	files.
#
#.TP
#	RunCommands "dir" ["class" ["arg"] ...]
#		This function is actually the guts of 'rc.sh'.
#		Sets "Mydir" to "dir" so that modules in "dir" don't
#		need to work it out.
#
#.TP
#	RunSubs "dir" ...
#		Assumes "Mydir" is set, and will look for a directory
#		of the same name under each "dir".  If there is a
#		command in "dir" that corresponds to the directory
#		(eg. "Mydir"=/configs/config.d, "dir"=/configs/NetBSD,
#		we look for /configs/NetBSD/config.d and if it exists
#		we look check for /configs/NetBSD/config or
#		/configs/NetBSD/config.sh to process
#		/configs/NetBSD/config.d). If no such command is
#		found, we call RunCommands.
#.PP
#
# BUGS:
#	This script would have been called 'rc' except that would have
#	caused too many support problems.  Calling it 'rc.sh' allows
#	it to coexist with the normal operating system facilities.
#	
#	We avoid use of anything but shell internals so that we can
#	run while the system is in single user mode and few
#	filesystems are mounted.  Because we do not use getopt(1), we
#	require a space between each option and between options and
#	their args.
#
#	It would be nice if we could check for a file being executable
#	other than by attempting to run it as this would provide
#	support for interpreters other than sh and perl as well as
#	direct execution of compiled binaries.	Sadly, on most older
#	systems test -x does not do the right thing in the presence of
#	symlinks.  If you want this feature and your test is not
#	broken, define TEST_X_OK.
#	
# AUTHOR:
#	Simon J. Gerraty <sjg@quick.com.au>
#

# RCSid:
#	$Id: rc.sh,v 1.17 1997/07/26 12:23:48 sjg Exp $
#
#	@(#) Copyright (c) 1994 Simon J. Gerraty
#
#	This file is provided in the hope that it will
#	be of use.  There is absolutely NO WARRANTY.
#	Permission to copy, redistribute or otherwise
#	use this file is hereby granted provided that 
#	the above copyright notice and this notice are
#	left intact. 
#      
#	Please send copies of changes and bug-fixes to:
#	sjg@quick.com.au
#

# cannot rely on things like dirname/basename being available
Myname=$0

Which() {
	for d in `IFS=:; echo ${2-$PATH}`
	do
		[ -x $d/$1 ] && { echo $d/$1; break; }
	done
}

# this function just drops the last component of $2
# components are separated by $1
# and $3 is a tag to mark the last (default is #)
# Basename() and Dirname() both use it	      

drop_last() {
	case "$2" in
	"")	return;;
	esac

	sep=$1
	var=$2
	joint=${3:-$sep}
	marker=${4:-#}

	case "$var" in
	*[${sep}]*)
		set -- `IFS=$sep; echo $var$marker`
		out=$1; shift
		# BSD ash bug?
		[ "$out" ] || { out=$1; shift; }
		while :	
		do
			case "$1" in *"$marker") break;; esac
			out="$out$joint$1"; shift
		done
		echo $out
		;;
	esac
}

# this is as good as the real thing so just use it.
# Dirname dir
# behaves like real thing,
# Dirname dir anything
# tries to ensure result is absolute
Dirname() {
	case "$1" in
	*?/*)
		_d=
		case "$1" in
		/*) _d=/;;
		esac 
		_d="$_d`drop_last / $1`"
		case "$_d" in
		/*)	echo $_d;;
		*)	{ [ "$2" ] && (cd $_d 2>/dev/null && pwd); } || echo $_d;;
		esac
		;;
	/*)
		echo /
		;;
	*)
		[ "$2" ] && pwd || echo .
		;;
	esac
}

basename=`Which basename`

# use the realthing if available
basename=${basename:-Basename}

# this handles most common usage, including Basename foo.sh.sh .sh
# but not Basename foo.sh.sh .sh.sh or Basename foobar bar
Basename() {
	case "$1" in
	*/*)	_b=`IFS=/; set -- $1; eval echo '$'$#`;;
	*)	_b=$1;;
	esac
	case "$2" in
	.*)
		case "$_b" in
		*$2)	# it ends in $2
			_b=`drop_last . $_b . /`;;
		esac
		;;
	esac
	echo $_b
}

Myname=`$basename $0 .sh`
Dir=`Dirname $0 .`/$Myname.d

ECHO=:
DOIT=
Class=
Args=
ErrExit=:

# check for options
while [ $# -gt 0 ]
do
	case "$1" in
	-v)	ECHO="echo $Myname:"; shift;;
	-n)	DOIT=echo; shift;;
	-d)	Dir=$2; shift 2;;
	-c)	Class=$2; shift 2;;
	-e)	ErrExit=exit; shift;;
	*)	break;;
	esac
done

Args="$@"

# the following is for semi compatabiliy with SysV usage.
# if Class and Args have not been set, behave like rc2 et al.
[ "$Args$Class" ] || { Args="$Dir:K:stop $Dir:S:start"; set -- $Args; }

_shell=${_shell:-`Which sh /sbin:/bin`}

# now get down to work

RunCommands() {
	dir=$1
	class=${2:-S}
	case $# in
	1|2)	args=;;
	*)	shift 2; args="$@";;
	esac

	case "$class" in
	S)	args=${args:-start};;
	K)	args=${args:-stop};; 
	esac
 
	if [ -d $dir ]; then
		[ -f $dir/verbose ] && ECHO="date; echo"

		# let scripts use Mydir without having to work it out
		Mydir=`cd $dir; pwd`
		export Mydir
 
		for f in $dir/${class}*
		do
			[ -d $f ] && continue
			[ -s $f ] || continue	# avoid symlinks to nothing
 
			case $f in
			*[*~]|*.old|*.bak) ;;	# skip it
			*.sh)			# source it
				eval $ECHO . $f
				$DOIT . $f || $ErrExit $?
				;;
			*)			# run it
				# sadly, on may systems, test is stupid.
				# if a symlink is mode 755 pointing
				# to a file which is 644, test -x
				# says yes...
				if [ "$TEST_X_OK" -a -x $f ]; then
					# try direct exec
					eval $ECHO $f $args
					$DOIT $f $args || $ErrExit $?
				else
					# we need an interpreter for it
					case $f in
					*.pl)	# a perl script?
						eval $ECHO ${PERL:-perl} $f $args
						$DOIT ${PERL:-perl} $f $args || $ErrExit $?
						;;
					*)	# use $_shell
						eval $ECHO $_shell $f $args
						$DOIT $_shell $f $args || $ErrExit $?
						;;
					esac
				fi
				;;
			esac
		done
	fi
}

# Since doing this relies on rc.sh internals,
# we might as well make it internal :-)
RunSubs() {
	seen=$Mydir
	me=`$basename $Mydir .d`
	
	for d in $*
	do
		case ,$seen, in
		*,$d/$me.d,*) continue;;
		esac
		seen="$seen,$d/$me.d"
		[ -d $d/$me.d ] || continue
		
		echo Processing $d/$me.d
		for x in $d/$me $d/$me.sh
		do
			if [ -x $x ]; then
				eval $ECHO $x $args
				$DOIT $x "$args" || $ErrExit
				break
			fi
		done
		if [ ! -x $x ]; then
			(RunCommands $d/$me.d S "$args") || $ErrExit
		fi
	done
}

case "$Args" in
*:*:*)
	# if Args contains : then it came from $@, so we can use that.
	# We need to use "$@" below as that alone protects white-space
	# within args. "$Args" would equate to a single argument.
	for w in "$@"
	do
		set -- `IFS=:; echo $w`
		RunCommands $*
	done
	;;
*)
	RunCommands $Dir ${Class:-S} $Args
	;;
esac

