#!/bin/bash # # runvdr extreme # # configurable vdr launcher script # # by Udo Richter # http://www.richter-udo.de/vdr/scripts.html#runvdr # RUNVDRCONF=/etc/runvdr.conf # Some unix commands being used: PGREP="pgrep" PS="ps" GETOPT="getopt" KILL="kill" SLEEP="sleep" CHVT="chvt" DATE="date" SETTERM="setterm" # Options summary and conf file entries of runvdr: # # -C # --runvdr-conf=# RUNVDRCONF location of runvdr config file # -- ADDPARAM Additional parameters to pass to VDR # --pluginsetup-conf=# PLUGINSETUPCONF location of plugin-setup-runvdr.conf # --vdr=# VDRPRG location and name of the vdr binary # --switchterminal=# SWITCHTERMINAL console terminal to switch to # --runvdr-pid=# RUNVDRPID location of runvdr.pid file # --dvb-load=# DVBLOAD command to load DVB drivers # --dvb-unload=# DVBUNLOAD command to unload DVB drivers # --wrapper=# WRAPPER wrapper command for calling vdr # --terminate[=#] TERMINATE Terminate runvdr by pid # --wait[=#] WAIT Wait # seconds for --terminate to finish # --restart[=#] RESTART Send restart signal by pid # --dvb-restart[=#] DVBRESTART Send dvb-restart signal by pid # --term-timeout=# TERMTIMEOUT Timeout for VDR to react on SIGTERM # --kill-timeout=# KILLTIMEOUT Timeout for VDR to react on SIGKILL # --language=# LANGUAGE Locale to set for VDR # -V --version VERSION print version information and exit # -h --help HELP print this help and exit # # Supported options of VDR # # -a # --audio # AUDIO send Dolby Digital audio to stdin of command # # -c # --config # CONFIGDIR read config files from DIR # -d --daemon DAEMON run in daemon mode # -D # --device # DVBDEVICE use only the given DVB device (NUM = 0, 1, 2...) # -E # --epgfile # EPGFILE write the EPG data into the given FILE. - to disable. # -g # --grab # GRAB write images from the SVDRP command GRAB into the given DIR; # -L # --lib # LIBDIR search for plugins in DIR (default is %s) # --lirc #? LIRC use a LIRC remote control device, attached to PATH # -l # --log # LOGLEVEL set log level (default: 3) # -m --mute MUTE mute audio of the primary DVB device at startup # --no-kbd NOKBD don't use the keyboard as an input device # -P # --plugin # PLUGINS load a plugin defined by the given options # -p # --port # SVDRPPORT use PORT for SVDRP # --rcu #? RCU use a remote control device, attached to PATH # -r # --record # RECORDCMD call CMD before and after a recording # -s # --shutdown # SHUTDOWN call CMD to shutdown the computer # -t # --terminal # TERMINAL controlling tty # -u # --user # USER run as user USER; only applicable if started as root # --vfat VFAT encode special characters in recording names # -v # --video # VIDEODIR use DIR as video directory # -w # --watchdog # WATCHDOG activate the watchdog timer with a timeout of SEC which $PGREP >/dev/null || { echo missing $PGREP... >2 ; exit 1 ; } which $PS >/dev/null || { echo missing $PS... >2 ; exit 1 ; } which $GETOPT >/dev/null || { echo missing $GETOPT... >2 ; exit 1 ; } which $KILL >/dev/null || { echo missing $KILL... >2 ; exit 1 ; } which $SLEEP >/dev/null || { echo missing $SLEEP... >2 ; exit 1 ; } which $CHVT >/dev/null || { echo missing $CHVT... >2 ; exit 1 ; } which $DATE >/dev/null || { echo missing $DATE... >2 ; exit 1 ; } which $SETTERM >/dev/null || { echo missing $SETTERM... >2 ; exit 1 ; } function ParseCommandLine_Step1() { # Parse command line, step 1 # Stores pre-processed options in $OPTIONS # evaluates -C and --runvdr-conf only SHORTOPT="a:c:C:dD:E:g:hl:L:mp:P:r:s:t:u:v:Vw:" LONGOPT="runvdr-conf:,pluginsetup-conf:,vdr:,switchterminal:,\ runvdr-pid:,dvb-load:,dvb-unload:,language:,wrapper:,\ term-timeout:,kill-timeout:,terminate::,restart::,dvb-restart::,\ wait::,audio:,config:,daemon,device:,epgfile:,grab:,help,lib:,lirc::,\ log:,mute,no-kbd,plugin:,port:,rcu::,record:,shutdown:,\ terminal:,user:,version,vfat,video:,watchdog:" # prepare all optios for later processing OPTIONS=`$GETOPT -o "$SHORTOPT" --long "$LONGOPT" -n "$0" -- "$@"` || exit 1 # evaluate only -C and --runvdr-conf EARLYOPTIONS=`$GETOPT -q -o "C:" --long "runvdr-conf:" -n "$0" -- "$@"` eval set -- "$EARLYOPTIONS" while true ; do case "$1" in -C|--runvdr-conf) RUNVDRCONF="$2" shift 2 ;; --) shift break ;; *) echo "Internal error!" >2 exit 1 ;; esac ; done } function Clean() { # Clean all config variables ADDPARAM= PLUGINSETUPCONF= VDRPRG= SWITCHTERMINAL= RUNVDRPID= DVBLOAD= DVBUNLOAD= LANGUAGE= WRAPPER= TERMTIMEOUT= KILLTIMEOUT= TERMINATE= RESTART= DVBRESTART= WAIT=0 HELP= VERSION= AUDIO= CONFIGDIR= DAEMON= DVBDEVICE=() EPGFILE= GRAB= LIBDIR= LIRC= LOGLEVEL= MUTE= NOKBD= PLUGINS=() SVDRPPORT= RECORDCMD= RCU= SHUTDOWN= TERMINAL= VDRUSER= VFAT= VIDEODIR= WATCHDOG= } # Helper functions function AddPluginString() { # add $1 as plugin if [ "$1" == "-" ] ; then PLUGINS=() else PLUGINS[${#PLUGINS[*]}]="$*" fi } function AddPlugin() { # add $* as plugin, do shell quoting local plugin="" while [ $# -gt 0 ] ; do # regexp magic # quote '\' to '\\' local par="$1" par="${par//'\'/\\\\}" # work around bash bug: double quoted '\' par="${par//'\\'/\\\\}" # quote '"' to '\"' par="${par//\"/\\\"}" # check if this splits into words local -a arr=($par) # if yes, wrap in quotes [ ${#arr[*]} -ne 1 ] && par="\"$par\"" # add to plugin string if [ -n "$plugin" ] ; then plugin="$plugin $par" ; else plugin="$par" ; fi # next, please shift done AddPluginString "$plugin" } function AddDevice() { if [ "$1" == "-" ] ; then DVBDEVICE=() else DVBDEVICE[${#DVBDEVICE[*]}]="$*"; fi } function LoadConfFile() { # Load configuration file if [ -r $RUNVDRCONF ] ; then . $RUNVDRCONF || exit 1 else echo "runvdr: $RUNVDRCONF not found." >&2 fi # Transform some defaults, so empty parameters can have a # non-default meaning [ -z "$LIRC" ] && LIRC=0 [ -z "$RCU" ] && RCU=0 [ -z "$TERMINATE" ] && TERMINATE=0 [ -z "$RESTART" ] && RESTART=0 [ -z "$DVBRESTART" ] && DVBRESTART=0 return 0 } function ParseCommandLine_Step2() { # Parse command line, step 2 # Process all options in $OPTIONS, override # all options that are set by now eval set -- "$OPTIONS" while true ; do case "$1" in -C|--runvdr-conf) shift 2;; --pluginsetup-conf) PLUGINSETUPCONF="$2"; shift 2;; --vdr) VDRPRG="$2"; shift 2;; --switchterminal) SWITCHTERMINAL="$2"; shift 2;; --runvdr-pid) RUNVDRPID="$2"; shift 2;; --dvb-load) DVBLOAD="$2"; shift 2;; --dvb-unload) DVBUNLOAD="$2"; shift 2;; --language) LANGUAGE="$2"; shift 2;; --wrapper) WRAPPER="$2"; shift 2;; --term-timeout) TERMTIMEOUT="$2"; shift 2;; --kill-timeout) KILLTIMEOUT="$2"; shift 2;; --terminate) TERMINATE="$2"; shift 2;; --wait) WAIT="$2"; shift 2;; --restart) RESTART="$2"; shift 2;; --dvb-restart) DVBRESTART="$2"; shift 2;; -h|--help) HELP=1; shift ;; -V|--version) VERSION=1; shift ;; -a|--audio) AUDIO="$2"; shift 2;; -c|--config) CONFIGDIR="$2"; shift 2;; -d|--daemon) DAEMON=1; shift ;; -D|--device) AddDevice "$2"; shift 2;; -E|--epgfile) EPGFILE="$2"; shift 2;; -g|--grab) GRAB="1"; shift ;; -l|--log) LOGLEVEL="$2"; shift 2;; -L|--lib) LIBDIR="$2"; shift 2;; --lirc) LIRC="$2"; shift 2;; -m|--mute) MUTE=1; shift ;; --no-kbd) NOKBD=1; shift ;; -p|--port) SVDRPPORT="$2"; shift 2;; -P|--plugin) AddPluginString "$2"; shift 2;; --rcu) RCU="$2"; shift 2;; -r|--record) RECORDCMD="$2"; shift 2;; -s|--shutdown) SHUTDOWN="$2"; shift 2;; -t|--terminal) TERMINAL="$2"; shift 2;; -u|--user) USER="$2"; shift 2;; -v|--video) VIDEODIR="$2"; shift 2;; --vfat) VFAT=1; shift ;; -w|--watchdog) WATCHDOG="$2"; shift 2;; --) shift break ;; *) echo "Internal error!" >2 exit 1 ;; esac ; done # Add all remaining options directly to additional params if [ -n "$1" ] ; then [ -n "$ADDPARAM" ] && ADDPARAM="$ADDPARAM " ADDPARAM="$ADDPARAM$@" fi return 0 } function OnlineHelp() { cat <&- 2>&- for ((i=0;i<$3;i++)) ; do $PS ${childlist[*]} >&- 2>&- || { echo terminated. ; return ; } echo -n . $SLEEP 1 done echo echo -n "Sending ${#childlist[*]} processes the KILL signal." $KILL -KILL ${childlist[*]} >&- 2>&- for ((i=0;i<$4;i++)) ; do $PS ${childlist[*]} >&- 2>&- || { echo terminated. ; return ; } echo -n . $SLEEP 1 done echo failed. } #### --------------- #### Main script #### --------------- # Parse command line, step 1 ParseCommandLine_Step1 "$@" || exit 1 # Clean variables Clean # Load and process all configuration LoadConfFile || exit 1 # Process command line ParseCommandLine_Step2 || exit 1 if [ -n "$HELP" ] ; then OnlineHelp exit 0 fi if [ -n "$VERSION" ] ; then echo "runvdr version 0.1.1" exit 0 fi # Get old runvdr pid and move it to options if [ -n "$RUNVDRPID" ] ; then OLDRUNVDRPID="" [ -r "$RUNVDRPID" ] && OLDRUNVDRPID=`<$RUNVDRPID` [ -z "$TERMINATE" ] && TERMINATE="$OLDRUNVDRPID" [ -z "$RESTART" ] && RESTART="$OLDRUNVDRPID" [ -z "$DVBRESTART" ] && DVBRESTART="$OLDRUNVDRPID" fi if [ "$TERMINATE" != "0" ] ; then if [ -n "$TERMINATE" ] ; then echo -n "Terminating runvdr (PID=$TERMINATE)" $KILL -TERM $TERMINATE while [ -z "$WAIT" ] || [ "$WAIT" -gt 0 ] ; do $PS $TERMINATE >&- || { echo done ; break ; } echo -n "." $SLEEP 1 [ -n "$WAIT" ] && let WAIT=WAIT-1 done echo "" else echo "No runvdr process to terminate." fi exit 0 fi if [ "$RESTART" != "0" ] ; then if [ -n "$RESTART" ] ; then echo -n "Restarting runvdr (PID=$RESTART)..." $KILL -USR1 $RESTART echo "" else echo "No runvdr process to restart." fi exit 0 fi if [ "$DVBRESTART" != "0" ] ; then if [ -n "$DVBRESTART" ] ; then echo -n "DVB-restarting runvdr (PID=$DVBRESTART)..." $KILL -USR2 $DVBRESTART echo "" else echo "No runvdr process to dvb-restart." fi exit 0 fi # Build up VDR command BuildCommand || exit 1 # Switch front console [ -n "$SWITCHTERMINAL" ] && $CHVT $SWITCHTERMINAL if [ -n "$LANGUAGE" ] ; then LANG="$LANGUAGE" export LANG fi # Remember PID of this process [ -n "$RUNVDRPID" ] && echo $$ > $RUNVDRPID # Prepare terminal redirection if [ -n "$TERMINAL" ] ; then exec 1>"$TERMINAL" exec 2>"$TERMINAL" fi # Load driver if it hasn't been loaded already: $DVBLOAD # Count how often VDR terminated very quickly SHORTRUNTIMES=0 while (true) do echo -n "Starting VDR at " ; $DATE # Trap some signals sent to this script trap "SIG=HUP" SIGHUP trap "SIG=INT" SIGINT trap "SIG=QUIT" SIGQUIT trap "SIG=TERM" SIGTERM trap "SIG=USR1" SIGUSR1 trap "SIG=USR2" SIGUSR2 # clean up signal variable SIG= # Remember start time STARTTIME=`$DATE +%s` echo "$VDRCMD" # Run VDR eval "$VDRCMD &" # Remember PID of VDR process PID=$! # Wait for VDR to end or signal to arrive wait $PID # Remember return value of VDR RET=$? # Remember stop time STOPTIME=`$DATE +%s` # Count if time is less than 10 seconds, # forget otherwise if [ $((STOPTIME-STARTTIME)) -le 10 ] ; then SHORTRUNTIMES=$((SHORTRUNTIMES+1)) echo "VDR died within 10 seconds, this happened $SHORTRUNTIMES time(s)." else SHORTRUNTIMES=0 fi # Reset terminal status [ -n "$TERMINAL" ] && $SETTERM -initialize case "$SIG" in HUP | INT | QUIT | TERM) echo -n "Terminating by request at " ; $DATE # Kill remaining VDR traces WaitKill $PID $VDRPRG $TERMTIMEOUT $KILLTIMEOUT # and exit break ;; USR1) echo -n "Restarting VDR by request at " ; $DATE # Kill remaining VDR traces WaitKill $PID $VDRPRG $TERMTIMEOUT $KILLTIMEOUT # and loop ;; USR2) echo -n "Restarting VDR and DVB by request at " ; $DATE # Kill remaining VDR traces WaitKill $PID $VDRPRG $TERMTIMEOUT $KILLTIMEOUT # reload DVB stuff $DVBUNLOAD $DVBLOAD ;; *) # Non-signal termination if [ $RET -eq 0 -o $RET -eq 2 ] ; then echo -n "Terminating by error level $RET at " ; $DATE # Kill remaining VDR traces WaitKill $PID $VDRPRG $TERMTIMEOUT $KILLTIMEOUT # and exit break fi if [ $SHORTRUNTIMES -ge 5 ] ; then echo -n "Terminating because VDR died 5 times in a row quickly at " ; $DATE # Kill remaining VDR traces WaitKill $PID $VDRPRG $TERMTIMEOUT $KILLTIMEOUT # and exit break; fi echo -n "Restarting VDR and DVB by error level $RET at " ; $DATE # Kill remaining VDR traces WaitKill $PID $VDRPRG $TERMTIMEOUT $KILLTIMEOUT # reload DVB $DVBUNLOAD $DVBLOAD # and loop ;; esac # reload configuration Clean LoadConfFile || exit 1 ParseCommandLine_Step2 || exit 1 BuildCommand || exit 1 # Catch remaining in-between signals case "$SIG" in HUP | INT | QUIT | TERM) break ;; esac done # Clean up PID file [ -n "$RUNVDRPID" ] && rm $RUNVDRPID >&- 2>&-