#!/bin/bash

# This is the file "r2d2" which starts and stops services for the different
# runlevels of the SysV init scheme.
#
# Author:       Winfried Trmper <winni@xpilot.org>
# Version:      0.6-radical
#
# Unlike traditional implementations it avoids the messy scheme with
# expressing the setup through the filesystem, but reads a central config
# file instead. From a technical point of view both methods are almost
# equivalent.
#
# To be compatible with the common configuration scheme in the Linux-world,
# every script has two states: "on" or "off". The effect of this is that
# once it is switched on, it is never started again when the runlevel changes
# (it is only executed to switch it off if necessary).
#


CFGFILE="/etc/r2d2/runlevel.conf"
BAKCFG="/etc/r2d2/runlevel.bak"   # link to /var/backup/runlevel.conf ?
LOCKFILE="/var/lock/runlevel.lock"

  # The default searchpath for scripts (see r2d2(8))
INITD_PATH="/etc/r2d2:/sbin/r2d2"
VERBOSE="t"

CFGFILE="./runlevel.conf-radical"     #DEBUG
BAKCFG="./runlevel.bak"       #DEBUG
LOCKFILE="./runlevel.lock"    #DEBUG


trap ":" INT QUIT TSTP

[ -n "$1" ] && RUNLEVEL="$1"
[ "$2" = "-v" -o "$2" = "--verbose" ] && VERBOSE="t"

[ -z "$PREVLEVEL" ] && PREVLEVEL="N"


  # wait for any lock to vanish (but not when booting)
i=0
while [ -f "$LOCKFILE" -a "$PREVLEVEL" != "N" ]
do
    [ ! -O "$LOCKFILE" ] && break  # security-hack
    read pid < "$LOCKFILE"
    if ! kill -0 "$pid" &> /dev/null
    then
	echo "$0: found stale lockfile '$LOCKFILE'. Ignoring it." >&2
#	/bin/rm -f "$LOCKFILE"
        break
    fi
    if [ "$i" -gt "9" ]
    then
        echo "Process no. '$pid' is locking the configuration database. Terminating." >&2
        exit 1
    fi
    sleep 2
    let i+=1
done


  # This script is vital so we better keep an old copy of the configuration
  # file as fallsave-configuration. This does not handle a broken config
  # file, though.
  # Keep in mind that it is not the purpose of _this_ script to make backup
  # copies, we're in read-only mode ..

if [ ! -f "$CFGFILE" ]
then
    echo "Missing configuration file '$CFGFILE' using fallback config."
    if [ ! -f "$BAKCFG" ]
    then
	echo "No configuration file at all. You're in serious trouble now. Aborting."
	exit 1
    fi
    CFGFILE="$BAKCFG"  #else
fi


  # determines if first parameter passed to it is an element of the set
  # specified as second parameter
function element() {
    local element list IFS

    element="$1"
	
    [ "$2" = "in" ] && shift
    list="$2"
    [ "$list" = "-" ] && return 1
    [ "$list" = "*" ] && return 0

    IFS=","
    set -- $list
    case $element in
	"$1" | "$2" | "$3" | "$4" | "$5" | "$6" | "$7" | "$8" | "$9")
	    return 0
    esac

    for i in $list
    do
	IFS="-"
	set -- $i
	[ "$#" -lt "2" ] && continue
	[ "$element" -ge "$1" -a "$element" -le "$2" ] 2> /dev/null && return 0
    done

    return 1
}

  # builds the variable CMDSTRING containing the commands to be
  # executed in a particular order
function add_cmd() {
    local CMD what OPTS prefix
    CMD="$1"
    what="$2"
    OPTS="$3"
    
    found=""
#    [ "$PREVLEVEL" = "N" ] && found="t"
      # check if command is executable; it must be on the root-fs
    case "$CMD" in
	/*)
	    [ -x "$CMD" ] && found="t"
	      # commands with absolute path names are cosidered to be
	      # no SysV init scripts, so we don't pass start/stop to them
	    what=""
	    ;;
	*)
	    old_IFS="$IFS"  # save IFS
	    IFS=":"         # path is a colon-seperated list
	    for prefix in $INITD_PATH
	    do
		if [ -x "$prefix/$CMD" ]
		then
		    CMD="$prefix/$CMD"
		    found="t"
		    break
		fi
	    done
	    IFS="$old_IFS"  # restore IFS
	    ;;
    esac
    if [ -z "$found" ]
    then
	echo ""
	echo "r2d2: entry $SORT_NO: $CMD not executable or not found in $INITD_PATH." >&2
	echo "      Ignoring entry. Edit $CFGFILE to fix this." >&2
	return
    fi


    if [ "$what" = "start" ]
    then
	  # Command is inserted at the beginning to implement
          # "starting the last service first" (remember the lines in the
	  # config file start with high sort-numbers
	CMDSTRING="$CMD $what $OPTS; $CMDSTRING"

    else # "$what" = "start"

	CMDSTRING="$CMDSTRING; $CMD $what $OPTS"
    fi
}

  # init the command string with "do nothing"
CMDSTRING=":"

[ -n "$VERBOSE" ] && \
    echo -n "r2d2 reading runlevel configuration file $CFGFILE at line "

  # loop to read the configuration file line by line
while read  SORT_NO  OFF_LEVELS  ON_LEVELS  CMD  EXTRA_OPTIONS
do
      # exit in case line is empty or beginns with #
    case "$SORT_NO" in
	\#* | "") continue ;;
    esac

      # keyword "$path" allows setting the searchpath
    if [ "$SORT_NO" = "\$path" -o "$SORT_NO" = "\$PATH" ]
    then
	INITD_PATH="$CMD"
	continue
    fi

      # counter via \010 = ctrl-h = backward one char
    [ -n "$VERBOSE" ] && echo -ne " $SORT_NO\010\010\010"

      # start only if CMD was not switched on in previous runlevel
    if element "$RUNLEVEL" in "$ON_LEVELS" \
    && ! element "$PREVLEVEL" in "$ON_LEVELS" 
    then
	  # actually we do not execute anything but save the command in a string
	add_cmd  "$CMD" start "$EXTRA_OPTIONS"
	continue
    fi

     # no stopping needed while booting
    [ "$PREVLEVEL" = "N" ] && continue

      # stop only if CMD was not switched off in previous runlevel
    if element "$RUNLEVEL" in "$OFF_LEVELS" \
    && ! element "$PREVLEVEL" in "$OFF_LEVELS"
    then
	  # actually we do not execute anything but save the command in a string
	add_cmd  "$CMD" stop "$EXTRA_OPTIONS"
    fi

done < $CFGFILE

[ -n "$VERBOSE" ] && \
    echo "done          "

  # Execute the commands collected above
  # We split the string again in case somebody put a # in the
  # wrong place
old_IFS="$IFS"  # save IFS
IFS=";"
for i in $CMDSTRING
do
    IFS="$old_IFS"  # restore IFS
    echo "Would execute $i" #DEBUG
    continue #DEBUG
    eval "$i"
done


