#!/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.5.8
#
# 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"

  # Since version 0.5.8 r2d2 features a searchpath for scripts.
  # This has the potential to unify the configuration file for
  # all distributions and the related Linux-documentation.
INITD_PATH="/etc/r2d2:/sbin/r2d2"
VERBOSE="t"

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


trap ":" INT QUIT TSTP
[ -n "$1" ] && RUNLEVEL="$1"
[ -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


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
}


STOP_CMDS=":"
START_CMDS=":"

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


  # DUMMY serves as a protection against comment-signs
while read  SORT_NO  OFF_LEVELS  ON_LEVELS  CMD  DUMMY
do
    case "$SORT_NO" in
	\#* | "") continue ;;
    esac

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

    found=""
    case "$CMD" in
	/*)
	    [ -x "$CMD" ] && found="t"
	    ;;
	*)
	    old_IFS="$IFS"
	    IFS=":"
	    for prefix in $INITD_PATH
	    do
		if [ -x "$prefix/$CMD" ]
		then
		    CMD="$prefix/$CMD"
		    found="t"
		    break
		fi
	    done
	    IFS="$old_IFS"
	    ;;
    esac
    if [ -z "$found" ]
    then
	echo "r2d2: $CMD not executable or not found in $INITD_PATH."
	echo "      Ignoring entry. Edit $CFGFILE to fix this."
	continue
    fi

    [ -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
	START_CMDS="$START_CMDS; $CMD start"
	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
	  # Command is inserted at the beginning to implement
          # "stopping the last service first"
	STOP_CMDS="$CMD stop; $STOP_CMDS"
    fi

done < $CFGFILE

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

echo "Would execute the following command-lines:" #DEBUG
echo "$STOP_CMDS"  #DEBUG
echo "$START_CMDS" #DEBUG
exit               #DEBUG

  # Execute the commands collected above
eval $STOP_CMDS
eval $START_CMDS

