#!/bin/bash PROGNAME=`basename $0` # $HeadURL$ $LastChangedRevision$ TMP_DIR=${TMP_DIR:-/var/tmp} VERBOSELEVEL=2 set -e ORIGINAL_PROGNAME=$0 ORIGINAL_CMDLINE= for WORD in "$@"; do case "$WORD" in *" "*) ORIGINAL_CMDLINE="$ORIGINAL_CMDLINE\"$WORD\" " ;; *) ORIGINAL_CMDLINE="$ORIGINAL_CMDLINE$WORD " ;; esac done # # DAEMON_CMD: path of the server (e.g. /usr/bin/hts) [required] # SERVICE_DSC: service description (e.g. SSH over HTTP server") [required] # SERVICE_CMD: service basename (e.g. sshoverhttpd) [optional: basename $DAEMON_CMD] # main() { # default values for options SERVICE_SEQ=95 SERVICE_CMD=default PID_FILE=default DAEMON_ARGS= RUN_USER=root DAEMON_FORKS=true DAEMON_LOCKS=true INITD_DIR=unset # process options while [ "X$1" != X ]; do debug 10 "processing '$1' ..." case "$1" in # standard options --debug=*) VERBOSELEVEL=${1#*=} ;; -d) VERBOSELEVEL=$2; shift ;; -v|--verbose) VERBOSELEVEL=3 ;; -h|--help) usage 0 ;; -p|--list-paths) LISTPATHS=true ;; # application-specific options --modules=*) INITD_MODULES="${1#*=}" ;; --service-cmd=*) SERVICE_CMD="${1#*=}" ;; --daemon-args=*) DAEMON_ARGS="${1#*=}" debug 10 "main: DAEMON_ARGS=\"$DAEMON_ARGS\"" ;; --service-seq=*) SERVICE_SEQ="${1#*=}" ;; --start-prologue=*) START_PROLOGUE="${1#*=}" ;; --start-epilogue=*) START_EPILOGUE="${1#*=}" ;; --stop-prologue=*) STOP_PROLOGUE="${1#*=}" ;; --stop-epilogue=*) STOP_EPILOGUE="${1#*=}" ;; --pid-file=*) PID_FILE="${1#*=}" ;; --initd-dir=*) INITD_DIR="${1#*=}" ;; --chuid=*) RUN_USER="${1#*=}" ;; --daemon-forks=*) DAEMON_FORKS="${1#*=}" ;; --daemon-locks=*) DAEMON_LOCKS="${1#*=}" ;; # option processing termination --) shift break ;; -*) usage 1 ;; *) break ;; esac shift done # process arguments [ "X$2" != X -a "X$3" = X ] || usage DAEMON_CMD=$1 SERVICE_DSC="$2" # Sanity checks and derivations expr "$DAEMON_CMD" : '/.*' > /dev/null || error "$DAEMON_CMD: not absolute" [ -x "$DAEMON_CMD" ] || error "$DAEMON_CMD: not executable" [ $SERVICE_SEQ -ge 0 -a $SERVICE_SEQ -le 99 ] || error "invalid sequence number $SERVICE_SEQ (0 to 99 is valid range)" [ $SERVICE_CMD != default ] || SERVICE_CMD=`basename $DAEMON_CMD` if [ $PID_FILE = default ]; then PID_FILE=/var/run/$SERVICE_CMD.pid else # Note we write the unexpanded $SERVICE_CMD into the PID_FILE setting; # leaving it to the init.d script itself (the one *this* script writes) # to evaluate it. PID_FILE="`echo \"$PID_FILE\" | sed 's@%s@$SERVICE_CMD@g'`" fi get_os_specifics [ $INITD_DIR != unset ] || INITD_DIR=$OS_INITDDIR # Write the init.d script touch $INITD_DIR/$SERVICE_CMD 2>/dev/null || error "$INITD_DIR/$SERVICE_CMD: can't write" info "generating $INITD_DIR/$SERVICE_CMD ..." { gen_prologue gen_variables gen_main gen_usage gen_start_func gen_stop_func gen_start_msg_prologue gen_stop_msg_prologue gen_start_msg_epilogue gen_stop_msg_epilogue gen_start_stop_daemon gen_epilogue } > $INITD_DIR/$SERVICE_CMD chmod a+x $INITD_DIR/$SERVICE_CMD # Make symlinks if [ $INITD_DIR = $OS_INITDDIR ]; then info "creating symlinks ..." make_rcxd_symlinks $SERVICE_CMD start $SERVICE_SEQ 2 3 4 5 . stop `expr $OS_SEQSUM - $SERVICE_SEQ` 0 1 6 . fi return 0 } usage() { local RC RC=${1:-1} { echo "Usage: $PROGNAME [ ] \"\"" echo # standard options echo "Options: -v | --verbose be verbose" echo " -d | --debug= be very verbose" echo " -h | --help show this text" echo " -p | --list-paths list used paths" # application-specific options echo " --modules= list of modules to load and unload" echo " --service-cmd= init.d command to start service" echo " --daemon-args= arguments to " echo " --service-seq= init.d sequence number" echo " --start-prologue= start prologue" echo " --start-epilogue= start epilogue" echo " --stop-prologue= stop prologue" echo " --stop-epilogue= stop epilogue" echo " --pid-file= where to read PID to kill" echo " --initd-dir= write the file somewhere else" echo " --chuid= user to run daemon as" echo " --daemon-forks={false|true} daemon forks to background itself" echo " --daemon-locks={false|true} daemon creates lock file itself" echo } | if [ $RC = 0 ]; then cat else cat >&2 fi exit $RC } gen_prologue() { # header debug 10 "gen_std_initd: writing header ..." # action() function provided in redhat/fedora is bash-specific. if [ $OS_HASACTIONFUNC = true ]; then echo '#!/bin/bash' # If /bin/sh is really bash then that is ok elif [ "X`/bin/sh -c 'echo $BASH'`" != X ]; then echo '#!/bin/bash' # Otherwise check for bash elif [ "X`/bin/bash -c 'echo $BASH'`" != X ]; then echo '#!/bin/bash' # Otherwise we're stuffed else error "can't find a suitable shell for running init.d scripts" fi echo echo '# THIS FILE WAS AUTOMATICALLY GENERATED. MANUAL EDITS WILL BE LOST!' echo '#' echo "# Written by: $USER" echo "# Written using: $ORIGINAL_PROGNAME $ORIGINAL_CMDLINE" echo "# Date written: `date`" echo if [ $OS_HASACTIONFUNC = true ]; then # loading redhat's 'functions' file cannot be done in a 'set -e' env echo '. /etc/init.d/functions' else # Debian doesn't have or need 'functions' and can be run in a 'set -e' env echo 'set -e' fi echo } gen_variables() { echo "PROGNAME=\"$OS_INITDDIR/$SERVICE_CMD\"" echo 'PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin' echo "DAEMON_CMD=$DAEMON_CMD" echo "SERVICE_DSC=\"$SERVICE_DSC\"" echo "SERVICE_CMD=$SERVICE_CMD" debug 10 "gen_variables: DAEMON_ARGS=\"$DAEMON_ARGS\"" echo "DAEMON_ARGS=\"$DAEMON_ARGS\"" echo "START_PROLOGUE=\"$START_PROLOGUE\"" echo "START_EPILOGUE=\"$START_EPILOGUE\"" echo "STOP_PROLOGUE=\"$STOP_PROLOGUE\"" echo "STOP_EPILOGUE=\"$STOP_EPILOGUE\"" echo "RUN_USER=\"$RUN_USER\"" echo "PID_FILE=\"$PID_FILE\"" echo "DAEMON_FORKS=\"$DAEMON_FORKS\"" echo "DAEMON_LOCKS=\"$DAEMON_LOCKS\"" echo } gen_main() { echo 'main()' echo '{' echo ' test -x $DAEMON_CMD || exit 0' echo echo ' case "$1" in' if [ $OS_HASACTIONFUNC = true ]; then # the 'action' function cannot execute a function; it can only execute # executables. The trick then is to make the script provide direct access # to those functions and then get action to call this script instructing # it to call those functions. echo ' start) action "`start_msg_prologue`" $PROGNAME start_func ;;' echo ' stop) action "`stop_msg_prologue`" $PROGNAME stop_func ;;' echo ' start_func) start_func ;;' echo ' stop_func) stop_func ;;' else echo ' start) start_msg_prologue' echo ' start_func' echo ' start_msg_epilogue ;;' echo ' stop) stop_msg_prologue' echo ' stop_func' echo ' stop_msg_epilogue ;;' fi echo ' restart|force-reload) $PROGNAME stop' echo ' $PROGNAME start ;;' echo echo ' *) usage ;;' echo ' esac' echo '}' echo } gen_usage() { echo 'usage()' echo '{' echo ' echo "Usage: $PROGNAME { start | stop | restart | force-reload }" >&2' echo ' exit 1' echo '}' echo } gen_start_func() { echo 'start_func()' echo '{' [ "X$INITD_MODULES" != X ] && { echo ' # The following modprobes were added using the --modules option of $PROGNAME' for INITD_MODULE in $INITD_MODULES; do echo " modprobe $INITD_MODULE" done echo } [ "X$START_PROLOGUE" != X ] && { echo ' # The following line was added using the --start-prologue option of $PROGNAME' echo ' eval "$START_PROLOGUE"' echo } echo ' start_stop_daemon --start --daemon-forks=$DAEMON_FORKS --daemon-locks=$DAEMON_LOCKS --chuid=$RUN_USER --pid-file=$PID_FILE --exec=$DAEMON_CMD -- $DAEMON_ARGS' echo [ "X$START_EPILOGUE" != X ] && { echo ' # The following line was added using the --start-epilogue option of $PROGNAME' echo ' $START_EPILOGUE' echo } # Redhat and Fedora need to touch 'subsys' files or the shutdown script doesn't run. [ $OS_HASSUBSYSLOCKS = false ] || echo ' touch /var/lock/subsys/$SERVICE_CMD' echo '}' echo } gen_stop_func() { echo 'stop_func()' echo '{' # no echo as this function only just started [ "X$STOP_PROLOGUE" != X ] && { echo ' # The following line was added using the --stop-prologue option of $PROGNAME' echo ' $STOP_PROLOGUE' echo } echo ' start_stop_daemon --stop --chuid=$RUN_USER --pid-file=$PID_FILE --daemon-locks=$DAEMON_LOCKS' echo [ "X$STOP_EPILOGUE" != X ] && { echo ' # The following line was added using the --stop-epilogue option of $PROGNAME' echo ' $STOP_EPILOGUE' echo } [ "X$INITD_MODULES" != X ] && { echo ' # The following line was added using the --modules option of $PROGNAME' for INITD_MODULE in `rev_word_order $INITD_MODULES`; do echo " rmmod $INITD_MODULE" done echo } [ $OS_HASSUBSYSLOCKS = true ] && { echo ' rm -f /var/lock/subsys/$SERVICE_CMD' echo } echo '}' echo } gen_start_msg_epilogue() { echo 'start_msg_epilogue()' echo '{' echo " echo \"`echo \"$OS_POSTMSGTPL\" | sed -e \"s/VERB/Starting/\" -e \"s/SERVICE_DSC/\\\$SERVICE_DSC/\" -e \"s/DAEMON_BASENAME/\\\`basename \\\$DAEMON_CMD\\\`/\"`\"" echo '}' echo } gen_start_msg_prologue() { echo 'start_msg_prologue()' echo '{' echo " echo -n \"`echo \"$OS_PREMSGTPL\" | sed -e \"s/VERB/Starting/\" -e \"s/SERVICE_DSC/\\\$SERVICE_DSC/\" -e \"s/DAEMON_BASENAME/\\\`basename \\\$DAEMON_CMD\\\`/\"`\"" echo '}' echo } gen_stop_msg_prologue() { # Can't call this 'stop()' because 'stop' is a ksh reserved word. echo 'stop_msg_prologue()' echo '{' echo " echo -n \"`echo \"$OS_PREMSGTPL\" | sed -e \"s/VERB/Stopping/\" -e \"s/SERVICE_DSC/\\\$SERVICE_DSC/\" -e \"s/DAEMON_BASENAME/\\\`basename \\\$DAEMON_CMD\\\`/\"`\"" echo '}' echo } gen_stop_msg_epilogue() { echo 'stop_msg_epilogue()' echo '{' echo " echo \"`echo \"$OS_POSTMSGTPL\" | sed -e \"s/VERB/Stopping/\" -e \"s/SERVICE_DSC/\\\$SERVICE_DSC/\" -e \"s/DAEMON_BASENAME/\\\`basename \\\$DAEMON_CMD\\\`/\"`\"" echo '}' echo } gen_epilogue() { echo '# version of error() which both the init.d script and start_stop_daemon can use' echo 'error()' echo '{' echo echo ' [ "X$1" != "X-p" ] || { local PROGNAME; PROGNAME=$2; shift 2; }' echo ' echo "$PROGNAME: ERROR: $1" >&2' echo ' exit 1' echo '}' echo echo 'main "$@"' echo } gen_start_stop_daemon() { cat <<'EOF' start_stop_daemon() { local RUNUSER RUNINBG PROG PID_FILE MODE CREATELOCK PID # default values for options RUNUSER=root RUNINBG=false CREATELOCK=false PID_FILE=undefined PROG=undefined # process options while [ "X$1" != X ]; do case $1 in --start) MODE=start ;; --stop) MODE=stop ;; --background) RUNINBG=true ;; --daemon-forks=false) RUNINBG=true ;; --daemon-forks=true) RUNINBG=false ;; --daemon-locks=false) CREATELOCK=true ;; --daemon-locks=true) CREATELOCK=false ;; --pid-file=*) PID_FILE=${1#*=} ;; --chuid=*) RUNUSER=${1#*=} ;; --exec=*) PROG=${1#*=} ;; --) shift; break ;; -*) start_stop_daemon_usage ;; *) break ;; esac shift done # process arguments # no processing: all arguments for the daemon itself are left in "$@" # sanity checks [ $PROG != undefined ] || [ $MODE = stop ] || start_stop_daemon_usage [ $PID_FILE != undefined ] || start_stop_daemon_usage # guts case $MODE in start) [ ! -f "$PID_FILE" ] || [ "X`cat \"$PID_FILE\"`" = X ] || [ ! -d "/proc/`cat \"$PID_FILE\"`" ] || { echo "$PROGNAME: ERROR: $PROG: already running" >&2; exit 1; } EOF # Beginning of dark magic from Stephane Chazales (see comp.unix.shell # or a mail I sent to Andrea Balestra on this topic). echo " CMDLINE=\`awk '" echo " BEGIN {" echo " for (i = 1; i < ARGC; i++) {" echo " a = ARGV[i]" echo " gsub(/'\\''/, \"'\\''\\\\\\\\\\\\\\\\'\\'\\''\", a)" echo " args = args \" '\\''\" a \"'\\''\"" echo " }" echo " print args" echo " exit" echo " }' \"\$PROG\" \"\$@\"\`" # su is quite happy running svnserve, but it complains when about # stdin not being a tty when it runs esd (presumably because su # detects that esd isn't closing some handles or is accessing some # handles or something). No amount of redirecting stdin seems to # help. Hence the redirecting of su's stderr, but not of the stderr # of the thing being exec'd. # 'su -c ' won't work if the user's shell is set to # something like /bin/false. 'su -m -c ' does work. echo ' case $RUNINBG in' echo " false) su -m \"\$RUNUSER\" -c \"set x \$CMDLINE\"'; shift; \"\$@\"' ;;" echo " true) su -m \"\$RUNUSER\" -c \"set x \$CMDLINE\"'; shift; exec \"\$@\" 2>&3' 3>&2 2>/dev/null & ;;" # End of dark magic from Stephane Chazales cat <<'EOF' esac # Getting the PID of a process that the daemon itself forked is a bit tricky! Perhaps best is just # to look through /proc? if [ $CREATELOCK = true ]; then if [ $RUNINBG = false ]; then PID=`ls -l /proc/*/exe 2>/dev/null | sed -n "s@.*/\([0-9][0-9]*\)/exe -> $DAEMON_CMD@\1@p"` expr "$PID" : "[0-9][0-9]*\$" > /dev/null || error -p start_stop_daemon "$PID: does not look like a pid" else PID=$! fi echo $PID > $PID_FILE fi ;; stop) [ -f "$PID_FILE" ] || error -p start_stop_daemon "$PID_FILE: not found" PIDS="`cat \"$PID_FILE\"`" [ "X$PIDS" != X ] || error -p start_stop_daemon "$PID_FILE: empty" for PID in $PIDS; do [ -d /proc/$PID ] || continue kill $PID for i in 1 2 3 4 5; do # The daemon will probably remove the lock itself, but not always (e.g. hts) [ -d /proc/$PID ] || break sleep 1 done [ ! -d /proc/$PID ] || kill -9 $PID for i in 1 2 3 4 5; do # The daemon will probably remove the lock itself, but not always (e.g. hts) [ -d /proc/$PID ] || break sleep 1 done [ ! -d /proc/$PID ] || error -p start_stop_daemon "$PID: can't kill" done ! $CREATELOCK || rm -f $PID_FILE ;; "") start_stop_daemon_usage ;; esac } start_stop_daemon_usage() { echo "Usage: start_stop_daemon [ ]" >&2 exit 1 } EOF } # This is supposed to be the same as Debian's update-rc.d command make_rcxd_symlinks() { local SERVICE MODE LEVEL SEQNO SERVICE=$1 shift while :; do # eat 'start' or 'stop' case "$1" in start) MODE=S ;; stop) MODE=K ;; "") break ;; *) update-rc.d_usage ;; esac shift # eat the sequence number SEQNO=$1 shift # reformat the seqno SEQNO=`printf "%02d" $SEQNO` # process the run levels while [ "X$1" != X. ]; do LEVEL=$1 rm -f /etc/rc$LEVEL.d/$MODE[0-9][0-9]$SERVICE ln -s ../init.d/$SERVICE /etc/rc$LEVEL.d/$MODE$SEQNO$SERVICE shift done # eat the dot shift done } get_os_specifics() { # Set various OS-specific things if [ -f /etc/debian_version ]; then OS_SEQSUM=99 OS_INITDDIR=/etc/init.d OS_HASSUBSYSLOCKS=false OS_PREMSGTPL="VERB SERVICE_DSC: DAEMON_BASENAME" OS_POSTMSGTPL="." OS_HASACTIONFUNC=false elif [ -f /etc/fedora-release ]; then OS_SEQSUM=100 OS_INITDDIR=/etc/init.d OS_HASSUBSYSLOCKS=true OS_PREMSGTPL="VERB SERVICE_DSC: " OS_POSTMSGTPL= OS_HASACTIONFUNC=true elif [ -f /etc/redhat-release ]; then OS_SEQSUM=100 OS_INITDDIR=/etc/init.d OS_HASSUBSYSLOCKS=true OS_PREMSGTPL="VERB SERVICE_DSC: " OS_POSTMSGTPL= OS_HASACTIONFUNC=true else error "can't get OS specifics" fi } rev_word_order() { echo "$@" | sed 's/ /\n/g' | cat -n | sort -k 1nr | awk '{ print $2 }' | xargs echo } ############################################################################## # # Messaging functions # ############################################################################## debug() { [ $1 -gt $VERBOSELEVEL ] && return; echo "$PROGNAME: DEBUG[$1]: $2" >&2; } info() { echo "$PROGNAME: INFO: $1" >&2; } warning() { echo "$PROGNAME: WARNING: $1" >&2; } error() { echo "$PROGNAME: ERROR: $1" >&2; exit 1; } ############################################################################## # # Entry point # ############################################################################## main "$@"