# TITLE: Runlevel control using eight runlevel files in one directory. # LFS VERSION: Tested on LFS-3.0 # AUTHOR: Ivo van Kamp # # # SYNOPSIS: If you find symbolic start and kill links in eight runlevel # directories hard to manage, you could use eight rc files # (e.g. /etc/rc.d/rc[0-6,S].scripts) containing only the # basenames of the init scripts in /etc/init.d. # # # HINT: # # Version 1.0 (17 dec 2001) # # TABLE OF CONTENTS: # 1. /etc/inittab # 2. /etc/rc.d/rc # 3. /etc/rc.d/convert # 4. /etc/rc.d/check # 5. /etc/rc.d/template # 6. /etc/init.d/functions # # For the sake of clarity all runlevels are handled in the same manner. # As a result it's possible to call a start script within runlevels 0 # and 6. The script used to behave the exact same way as the old rc file, # which had a bug when checking for start scripts in the sysinit runlevel. # In the process of fixing this I removed the inconsistencies. If you need # 'mountfs' to stop after starting 'sendsignals', you need to make a # 'umountfs', which should function like 'mountfs stop'. # # To implement all this you need to execute this hint and put the extracted # files in the right places. Then start the 'convert' script in /etc/rc.d # to generate the /etc/rc.d/rc[0-6,S].scripts. Execute 'check' to see if # every init script exists. Now you have runlevel control. # # PS: # - Don't forget to make 'umountfs' if you didn't already have one! # - Use a '#' at the beginning of start or stop entries inside the # rc[0-6,S].scripts to comment them out. # - Use 'check' to see what happens. # mkdir -p etc/rc.d mkdir -p etc/init.d #-------------------------------/etc/inittab---------------------------------- echo "Extracting etc/inittab..." cat > etc/inittab << "EOF" id:3:initdefault: si::sysinit:/etc/rc.d/rc S l0:0:wait:/etc/rc.d/rc 0 l1:S1:wait:/etc/rc.d/rc 1 l2:2:wait:/etc/rc.d/rc 2 l3:3:wait:/etc/rc.d/rc 3 l4:4:wait:/etc/rc.d/rc 4 l5:5:wait:/etc/rc.d/rc 5 l6:6:wait:/etc/rc.d/rc 6 ca:12345:ctrlaltdel:/sbin/shutdown -t1 -a -r now su:S016:respawn:/sbin/sulogin 1:2345:respawn:/sbin/agetty tty1 9600 2:2345:respawn:/sbin/agetty tty2 9600 3:2345:respawn:/sbin/agetty tty3 9600 4:2345:respawn:/sbin/agetty tty4 9600 5:2345:respawn:/sbin/agetty tty5 9600 6:2345:respawn:/sbin/agetty tty6 9600 EOF #-------------------------------/etc/inittab---------------------------------- #-------------------------------/etc/rc.d/rc---------------------------------- echo "Extracting etc/rc.d/rc..." cat > etc/rc.d/rc << "EOF" #!/bin/sh # # Begin /etc/rc.d/rc # # Thanks to Jason Pearce - jason.pearce@linux.org # and Gerard Beekmans - gerard@linuxfromscratch.org # print_error_msg based on ideas by Simon Perreault - # nomis80@videotron.ca # ################################################################ # # This script does two things in the following order: # # 1. Stop the init scripts for the current runlevel that # were started in the previous runlevel. # # 2. Start the init scripts for the current runlevel if they # got stopped (as described above) or if they have not # been started already by the previous runlevel. # # Ergo: Stop only if it was started and start only if it # wasn't already or got stopped. # # The init scripts to be stopped or started are assumed to # be in /etc/init.d/ and are referred to by the rc scripts # in /etc/rc.d/rc[runlevel].scripts. # # NB: For the sake of clarity all runlevels are handled in # the same manner. As a result it's possible to call a start # script within runlevels 0 and 6. The script used to behave # the exact same way as the old rc file, which had a bug # when checking for start scripts in the sysinit runlevel. # In the process of fixing this I removed the # inconsistencies. If you need 'mountfs' to stop after # starting 'sendsignals', you need to make a 'umountfs', # which should function like 'mountfs stop'. # ################################################################ # # Include the functions declared in the /etc/rc.d/functions file # source /etc/init.d/functions # # The print_error_msg function prints an error message when an unforeseen # error occurred that wasn't trapped for some reason by a evaluate_retval # call or error checking in different ways. print_error_msg() { echo $FAILURE echo -n "You should not read this error message. It means " echo "that an unforeseen error " echo -n "took place and subscript $i exited with " echo "a return value " echo -n "of $error_value for an unknown reason. If you're able " echo "to trace this error down " echo -n "to a bug in one of the files provided by this book, " echo "please be so kind to " echo -n "inform us at lfs-discuss@linuxfromscratch.org" $NORMAL echo echo echo "Press a key to continue..." read } # # If you uncomment the debug variable below none of the scripts will be # executed, just the script name and parameters will be echo'ed to the # screen so you can see how the scripts are called by rc. # # Un-comment the following for debugging. # debug=echo # # Start script or program. # startup() { $debug "$@" } # # Ignore CTRL-C only in this shell, so we can interrupt subprocesses. # trap ":" INT QUIT TSTP echo -n "Current runlevel: " $RUNLEVEL echo " Previous runlevel: " $PREVLEVEL # # Now find out what the current and what the previous runlevel are. The # $RUNLEVEL variable is set by init for all it's children. This script # runs as a child of init. # runlevel=$RUNLEVEL # # Get first argument. Set new runlevel to this argument. If no runlevel # was passed to this script we won't change runlevels. # [ "$1" != "" ] && runlevel=$1 if [ "$runlevel" = "" ] then echo "Usage: $0 " >&2 exit 1 fi previous=$PREVLEVEL # # If $PREVLEVEL is set to "N" and we're not in the sysinit # level then we assume the previous runlevel to be sysinit. # [ "$previous" == "N" ] && [ "$runlevel" != "S" ] && previous="S" # # If previous is empty set it to 'N' c.q. 'No previous'. # [ "$previous" == "" ] && previous="N" export runlevel previous # # Is there a rc script for the new runlevel? # if [ -f $RCDIR/rc$runlevel.scripts ] then # # If so, first collect all the stop scripts in the new run level. # get_stop_scripts rc$runlevel.scripts STOP FIRSTTIME=1 for i in $STOP do if [ ! -f $SCRIPTDIR/$i ]; then $FAILURE echo "Error: rc$runlevel.scripts:$SCRIPTDIR/$i not found" $NORMAL continue fi # # Determine if there is a start script for this stop script in the # previous runlevel. # if [ "$previous" != "N" ] then get_start_scripts rc$previous.scripts previous_start $i fi [ ! "$previous_start" ] && continue [ "$FIRSTTIME" ] && FIRSTTIME="" && $WHITE && echo "Stop:" && $NORMAL # # If we found previous_start, run the stop script # startup $SCRIPTDIR/$i stop error_value=$? # # If the return value of the script is not 0, something went wrong with # error checking inside the script. The print_error_msg function will be # called and the message plus the return value of the stop script will be # printed to the screen # if [ $error_value != 0 ] then print_error_msg fi done # # Now run the START scripts for this runlevel. # get_start_scripts rc$runlevel.scripts START FIRSTTIME=1 for i in $START do if [ ! -f $SCRIPTDIR/$i ]; then $FAILURE echo "Error: rc$runlevel.scripts:$SCRIPTDIR/$i not found" $NORMAL continue fi if [ "$previous" != "N" ] then # # Find current start script in previous runlevel and stop script in this # runlevel. # get_stop_scripts rc$runlevel.scripts stop $i get_start_scripts rc$previous.scripts previous_start $i # # If there is a start script in the previous level level and no stop # script in this level, we don't have to re-start the service; # abort this iteration and start the next one. # [ "$previous_start" ] && [ ! "$stop" ] && continue fi [ "$FIRSTTIME" ] && FIRSTTIME="" && $WHITE && echo "Start:" && $NORMAL startup $SCRIPTDIR/$i start error_value=$? # # If the return value of the script is not 0, something went wrong with # error checking inside the script. the print_error_msg function will be # called and the message plus the return value of the stop script will be # printed to the screen # if [ $error_value != 0 ] then print_error_msg fi done fi # End /etc/rc.d/rc EOF chmod 754 etc/rc.d/rc #-------------------------------/etc/rc.d/rc---------------------------------- #-------------------------------/etc/rc.d/convert----------------------------- echo "Extracting etc/rc.d/convert..." cat > etc/rc.d/convert << "EOF" #!/bin/bash # # This script will convert runlevel directories to files # in the current directory. # RCDIRS=`find /etc -type d -name "rc[0-9,S].d"` for i in $RCDIRS do RCFILE=`basename $i | sed 's/\.d/\.scripts/g'` if [ -f $RCFILE ]; then echo -n "RC file '$RCFILE' already exists! Overwrite ? (yes/no) [n] " read answer case "$answer" in y|Y|yes|Yes|YES) echo "Creating backup of $RCFILE called $RCFILE.old" cp -v $RCFILE $RCFILE.old ;; *) echo "Generation of $RCFILE aborted." echo continue ;; esac fi echo "Generating $RCFILE..." echo echo "#----------START----------" > $RCFILE for x in $i/S* do [ ! -h $x ] && continue echo `basename $x` | sed 's/^.[0-9]*//' >> $RCFILE done echo "#----------STOP----------" >> $RCFILE for x in $i/K* do [ ! -h $x ] && continue echo `basename $x` | sed 's/^.[0-9]*//' >> $RCFILE done done EOF chmod 754 etc/rc.d/convert #-------------------------------/etc/rc.d/convert----------------------------- #-------------------------------/etc/rc.d/check------------------------------- echo "Extracting etc/rc.d/check..." cat > etc/rc.d/check << "EOF" source /etc/init.d/functions SCRIPTS=`ls *.scripts` function find_scripts() { if [ "$1" == "" ]; then echo; fi for SCRIPT in $1; do echo -n -e "\t" $SCRIPT if [ ! -f $SCRIPTDIR/$SCRIPT ]; then print_status failure else print_status success fi done } echo for SCRIPTFILE in $SCRIPTS; do $DARK echo $SCRIPTFILE $NORMAL echo -n "Start:" get_start_scripts $SCRIPTFILE START find_scripts "$START" echo -n "Stop:" get_stop_scripts $SCRIPTFILE STOP find_scripts "$STOP" echo done EOF chmod 754 etc/rc.d/check #-------------------------------/etc/rc.d/check------------------------------- #-------------------------------/etc/rc.d/template---------------------------- echo "Extracting etc/rc.d/template..." cat > etc/rc.d/template << "EOF" #----------START---------- #----------STOP----------- EOF #-------------------------------/etc/rc.d/template---------------------------- #-------------------------------/etc/init.d/functions------------------------- echo "Extracting etc/init.d/functions..." cat > etc/init.d/functions << "EOF" #!/bin/sh # Begin /etc/init.d/functions SCRIPTDIR="/etc/init.d" RCDIR="/etc/rc.d" PATH="/bin:/usr/bin" # # get_scripts (rc script file, return variable, do start or stop init scripts # switch, [optional init script to search]) # function get_scripts() { export $2="`echo -e $3"\n"$4 | cat - $RCDIR/$1 | awk '{ if (NR==1) {switch=$0;x=0;} if (NR==2) {searchscript=$0;} if (NR>2) { do { if ($0 ~ /.*START.*/ ) { (switch) ? begin=1 : begin=0; } if ($0 ~ /.*STOP.*/) { (switch) ? begin=0 : begin=1; } if ($0 ~ /^ *#/) continue; if (begin) { if (searchscript!="") { if ($0==searchscript) print 1 } else print; } } while (getline>0) } }'`" } # # get_start_scripts (rc script file, return variable, [init script to search]) # function get_start_scripts() { switch="1"; get_scripts $1 $2 $switch $3 } # # get_stop_scripts (rc script file, return variable, [init script to search]) # function get_stop_scripts() { switch="0"; get_scripts $1 $2 $switch $3 } # Set a few variables that influence the text that's printed on the # screen. The SET_COL variable starts the text in the column number # decided by the COL and WCOL section (as defined by the COL # variable). NORMAL prints text in normal mode. # SUCCESS prints text in a green colour and FAILURE prints text in a red # colour # # If COLUMNS hasn't been set yet (bash sets it but not when called as # sh), do it ourself if [ -z "$COLUMNS" ] then # Get the console device if we don't have it already # This is ok by the FHS as there is a fallback if # /usr/bin/tty isn't available, for example at bootup. test -x /usr/bin/tty && CONSOLE=`/usr/bin/tty` test -z "$CONSOLE" && CONSOLE=/dev/console # Get the console size (rows columns) SIZE=$(stty size < $CONSOLE) # Strip off the rows leaving the columns COLUMNS=${SIZE#*\ } fi COL=$[$COLUMNS - 10] WCOL=$[$COLUMNS - 30] SET_COL="echo -en \\033[${COL}G" SET_WCOL="echo -en \\033[${WCOL}G" NORMAL="echo -en \\033[0;39m" SUCCESS="echo -en \\033[1;32m" WARNING="echo -en \\033[1;33m" DARK="echo -en \\033[1;30m" WHITE="echo -en \\033[1;37m" BLUE="echo -en \\033[1;34m" LIGHTBLUE="echo -en \\033[1;36m" FAILURE="echo -en \\033[1;31m" # # The evaluate_retval function evaluates the return value of the process # that was run just before this function was called. If the return value # was 0, indicating success, the print_status function is called with # the 'success' parameter. Otherwise the print_status function is called # with the failure parameter. # evaluate_retval() { if [ $? = 0 ] then print_status success else print_status failure fi } # # The print_status prints [ OK ] or [FAILED] to the screen. OK appears # in the colour defined by the SUCCESS variable and FAILED appears in # the colour defined by the FAILURE variable. Both are printed starting # in the column defined by the COL variable. # print_status() { # # If no parameters are given to the print_status function, print usage # information. # if [ $# = 0 ] then echo "Usage: print_status {success|failure}" return 1 fi case "$1" in success) $SET_COL echo -n "[ " $SUCCESS echo -n "OK" $NORMAL echo " ]" ;; warning) $SET_COL echo -n "[ " $WARNING echo -n "ATTN" $NORMAL echo " ]" ;; failure) $SET_COL echo -n "[" $FAILURE echo -n "FAILED" $NORMAL echo "]" ;; esac } # # The loadproc function starts a process (often a daemon) with # proper error checking # loadproc() { # # If no parameters are given to the print_status function, print usage # information. # if [ $# = 0 ] then echo "Usage: loadproc {program}" exit 1 fi # # Find the basename of the first parameter (the daemon's name without # the path # that was provided so /usr/sbin/syslogd becomes plain 'syslogd' after # basename ran) # base=$(/usr/bin/basename $1) # # the pidlist variable will contains the output of the pidof command. # pidof will try to find the PID's that belong to a certain string; # $base in this case # pidlist=$(/bin/pidof -o $$ -o $PPID -o %PPID -x $base) pid="" for apid in $pidlist do if [ -d /proc/$apid ] then pid="$pid $apid" fi done # # If the $pid variable contains anything (from the previous for loop) it # means the daemon is already running # if [ ! -n "$pid" ] then # # Empty $pid variable means it's not running, so we run "$@" (all # parameters giving to this function from the script) and then check the # return value # "$@" evaluate_retval else # # The variable $pid was not empty, meaning it was already running. We'll # print [ ATTN ] now # $SET_WCOL echo -n "Already running" print_status warning fi } # # The killproc function kills a process with proper error checking # killproc() { # # If no parameters are given to the print_status function, print usage # information. # if [ $# = 0 ] then echo "Usage: killproc {program} [signal]" exit 1 fi # # Find the basename of the first parameter (the daemon's name without # the path # that was provided so /usr/sbin/syslogd becomes plain 'syslogd' after # basename ran) # base=$(/usr/bin/basename $1) # # Check if we gave a signal to kill the process with (like -HUP, -TERM, # -KILL, etc) to this function (the second parameter). If no second # parameter was provided set the nolevel variable. Else set the # killlevel variable to the value of $2 (the second parameter) # if [ "$2" != "" ] then killlevel=-$2 else nolevel=1 fi # # the pidlist variable will contains the output of the pidof command. # pidof will try to find the PID's that belong to a certain string; # $base in this case # pidlist=$(/bin/pidof -o $$ -o $PPID -o %PPID -x $base) pid="" for apid in $pidlist do if [ -d /proc/$apid ] then pid="$pid $apid" fi done # # If $pid contains something from the previous for loop it means one or # more PID's were found that belongs to the processes to be killed # if [ -n "$pid" ] then # # If no kill level was specified we'll try -TERM first and then sleep # for 2 seconds to allow the kill to be completed # if [ "$nolevel" = 1 ] then /bin/kill -TERM $pid # # If after -TERM the PID still exists we'll wait 2 seconds before # trying to kill it with -KILL. If the PID still exist after that, wait # two more seconds. If the PIDs still exist by then it's safe to assume # that we cannot kill these PIDs. # if /bin/ps h $pid >/dev/null 2>&1 then /usr/bin/sleep 2 if /bin/ps h $pid > /dev/null 2>&1 then /bin/kill -KILL $pid if /bin/ps h $pid > /dev/null 2>&1 then /usr/bin/sleep 2 fi fi fi /bin/ps h $pid >/dev/null 2>&1 if [ $? = 0 ] then # # If after the -KILL it still exists it can't be killed for some reason # and we'll print [FAILED] # print_status failure else # # It was killed, remove possible stale PID file in /var/run and # print [ OK ] # /bin/rm -f /var/run/$base.pid print_status success fi else # # A kill level was provided. Kill with the provided kill level and wait # for 2 seconds to allow the kill to be completed # /bin/kill $killlevel $pid if /bin/ps h $pid > /dev/null 2>&1 then /usr/bin/sleep 2 fi /bin/ps h $pid >/dev/null 2>&1 if [ $? = 0 ] then # # If ps' return value is 0 it means it ran ok which indicates that the # PID still exists. This means the process wasn't killed properly with # the signal provided. Print [FAILED] # print_status failure else # # If the return value was 1 or higher it means the PID didn't exist # anymore which means it was killed successfully. Remove possible stale # PID file and print [ OK ] # /bin/rm -f /var/run/$base.pid print_status success fi fi else # # The PID didn't exist so we can't attempt to kill it. Print [ ATTN ] # $SET_WCOL echo -n "Not running" print_status warning fi } # # The reloadproc functions sends a signal to a daemon telling it to # reload it's configuration file. This is almost identical to the # killproc function with the exception that it won't try to kill it with # a -KILL signal (aka -9) # reloadproc() { # # If no parameters are given to the print_status function, print usage # information. # if [ $# = 0 ] then echo "Usage: reloadproc {program} [signal]" exit 1 fi # # Find the basename of the first parameter (the daemon's name without # the path that was provided so /usr/sbin/syslogd becomes plain 'syslogd' # after basename ran) # base=$(/usr/bin/basename $1) # # Check if we gave a signal to send to the process (like -HUP) # to this function (the second parameter). If no second # parameter was provided set the nolevel variable. Else set the # killlevel variable to the value of $2 (the second parameter) # if [ -n "$2" ] then killlevel=-$2 else nolevel=1 fi # # the pidlist variable will contains the output of the pidof command. # pidof will try to find the PID's that belong to a certain string; # $base in this case # pidlist=$(/bin/pidof -o $$ -o $PPID -o %PPID -x $base) pid="" for apid in $pidlist do if [ -d /proc/$apid ] then pid="$pid $apid" fi done # # If $pid contains something from the previous for loop it means one or # more PID's were found that belongs to the processes to be reloaded # if [ -n "$pid" ] then # # If nolevel was set we will use the default reload signal SIGHUP. # if [ "$nolevel" = 1 ] then /bin/kill -SIGHUP $pid evaluate_retval else # # Else we will use the provided signal # /bin/kill $killlevel $pid evaluate_retval fi else # # If $pid is empty no PID's have been found that belong to the process. # Print [ ATTN ] # $SET_WCOL echo -n "Not running" print_status warning fi } # # The statusproc function will try to find out if a process is running # or not # statusproc() { # # If no parameters are given to the print_status function, print usage # information. # if [ $# = 0 ] then echo "Usage: status {program}" return 1 fi # # $pid will contain a list of PID's that belong to a process # pid=$(/bin/pidof -o $$ -o $PPID -o %PPID -x $1) if [ -n "$pid" ] then # # If $pid contains something, the process is running, print the contents # of the $pid variable # echo "$1 running with Process ID $pid" return 0 fi # # If $pid doesn't contain it check if a PID file exists and inform the # user about this stale file. # if [ -f /var/run/$1.pid ] then pid=$(/usr/bin/head -1 /var/run/$1.pid) if [ -n "$pid" ] then echo "$1 not running but /var/run/$1.pid exists" return 1 fi else echo "$1 is not running" fi } # End /etc/init.d/functions EOF #-------------------------------/etc/init.d/functions-------------------------