#!/bin/bash set -u trace() { local MSG=$1 echo "[arb_launcher[${ARB_LAUNCHER:-}]: $1]" } debug() { local MSG=$1 # to debug uncomment next line: # trace "DEBUG: $MSG" } send_to_launcher() { local NAMED_PIPE=$1 local CMD=$2 debug "Sending '$CMD' to $NAMED_PIPE" echo "$CMD" >$NAMED_PIPE sleep 1 debug "send_to_launcher terminates" } pipe_command() { local NAMED_PIPE=$1; shift local CMD=$1; shift local LOGDIR=${1:-}; shift # LOGDIR may be empty/undef -> dont signal crash trace "Starting '$CMD'.." $CMD local EXITCODE=${PIPESTATUS[0]} if [ $EXITCODE == 0 ]; then trace "'$CMD' has terminated with success" else trace "'$CMD' has terminated with error $EXITCODE" if [ -n "$LOGDIR" ]; then if [ $EXITCODE = 1 ]; then touch $LOGDIR/failed else touch $LOGDIR/crashed fi fi fi send_to_launcher $NAMED_PIPE 'cmd_terminated' debug "pipe_command terminates" } read_line() { local NAMED_PIPE=$1 local LINE="" if read ATTEMPT <$NAMED_PIPE; then LINE=$ATTEMPT fi echo $LINE } listen_pipe_unlogged() { local NAMED_PIPE=$1; shift local LOGDIR=${1:-}; shift # LOGDIR may be empty/undef -> dont log local RUNNING=1 local STARTED=0 # RUNNING is set to 1 (otherwise listen_pipe would terminate instantly) trace "log for ARB_PID='${ARB_PID}'" while (($RUNNING > 0)) do LINE=`read_line $NAMED_PIPE 2>/dev/null` if [[ ! -z "$LINE" ]]; then debug "'$NAMED_PIPE' received '$LINE'" if [[ "$LINE" == 'TERMINATE' ]]; then trace "Received request to TERMINATE" break; else if [[ "$LINE" == 'cmd_terminated' ]]; then RUNNING=$(($RUNNING - 1)) if (($RUNNING>0)); then trace "Still have $RUNNING arb processes.." fi else if [[ "$LINE" == 'allow_termination' ]]; then RUNNING=$(($RUNNING - 1)) else pipe_command $NAMED_PIPE "$LINE" $LOGDIR & RUNNING=$(($RUNNING + 1)) STARTED=$(($STARTED + 1)) debug "RUNNING=$RUNNING" debug "STARTED=$STARTED" fi fi fi fi done if (($RUNNING==0)); then if (($STARTED>0)); then trace "All launched processes terminated" else trace "Nothing was ever launched" fi else trace "Still have $RUNNING arb-processes - terminating nevertheless" fi debug "listen_pipe_unlogged waits for subshells ..." wait trace "cleaning up arb session" arb_clean show_session arb_clean session debug "listen_pipe_unlogged terminates" } shared_library_dependencies() { case `uname` in Linux) LIST_DYNLIBS="ldd" BINARIES="bin/arb_ntree lib/libARBDB.so lib/libCORE.so lib/libWINDOW.so" ;; Darwin) LIST_DYNLIBS="otool -L" # Darwin ARB links internal stuff static BINARIES="bin/arb_ntree" ;; *) LIST_DYNLIBS="echo UNSUPPORTED_OS " ;; esac for binary in $BINARIES; do echo -e "Library dependencies for $ARBHOME/$binary:" $LIST_DYNLIBS $ARBHOME/$binary done } wrapped_info() { local TAG=$1; shift local CMD=$1; shift echo "--------------------" echo "[$TAG start]" eval $CMD echo "[$TAG end]" echo "" } collect_system_information() { echo "System information" echo "" echo "The information below has been collected by ARB." echo "Please do not publish without being aware that it might contain personal information." echo "" local ARB_RELEVANT="| grep -i ARB" wrapped_info "version" "$ARBHOME/bin/arb_ntree --help" wrapped_info "environment" "printenv $ARB_RELEVANT" wrapped_info "OS" "lsb_release -a" wrapped_info "kernel" "uname -mrs ; uname -a ; cat /proc/version" wrapped_info "shared libraries" "shared_library_dependencies" wrapped_info "disk" "df -h" wrapped_info "memory" "free -m ; cat /proc/meminfo" wrapped_info "user limits" "ulimit -a" wrapped_info "ARB processes" "ps aux $ARB_RELEVANT" wrapped_info "KDE desktop version" "konqueror --version" wrapped_info "Gnome desktop version" "gnome-panel --version" wrapped_info "CPU" "cat /proc/cpuinfo" wrapped_info "X server" "xdpyinfo" # wrapped_info "X" "Y" } erase_old_logs() { local LOGBASE=$1 if [ -d "$LOGBASE" ]; then # remove files older than 7 days inside and below LOGBASE local OLD=$(( 60 * 24 * 7 )) find $LOGBASE -type f -cmin +$OLD -exec rm {} \; # remove empty directories inside and below LOGBASE find $LOGBASE -type d -depth -empty -mindepth 1 -exec rmdir {} \; fi } listen_pipe() { # this is just a wrapper around listen_pipe_unlogged. # wrapper performs ARB session logging local NAMED_PIPE=$1 if [ -z ${ARB_PROP:-} ]; then # should never come here, if arb has been started via script 'arb' # (e.g. happens when arb_ntree was started from debugger and then 'start second database' has been called) listen_pipe_unlogged $NAMED_PIPE else local LOGBASE=$ARB_PROP/logs local LOGDIRID=`date '+%Y%m%d_%H%M%S'`.$$ local LOGDIR=$LOGBASE/$LOGDIRID local NTREE_STATUS= mkdir -p $LOGDIR if [ -d "$LOGDIR" ]; then local RUNLOG=$LOGDIR/run.log local SERVERLOG=$LOGDIR/server.log local SYSLOG=$LOGDIR/sys.info local CRASHFLAG=$LOGDIR/crashed local FAILFLAG=$LOGDIR/failed # tell arb to start servers as logging daemons export ARB_SERVER_LOG=$SERVERLOG echo "`date` arb server.log created by arb_launcher" > $SERVERLOG # forward server output to launcher-tty (non-blocking) tail -f $SERVERLOG & local TAILPID=$! ( ( collect_system_information 2>&1 ) > $SYSLOG ; erase_old_logs $LOGBASE ) & ( listen_pipe_unlogged $NAMED_PIPE $LOGDIR ) 2>&1 | tee $RUNLOG if [ -e $CRASHFLAG ]; then # only detects crashes of arb_ntree # (clients are not started via arb_launcher and they usually crash when server exits) NTREE_STATUS=crash else if [ -e $FAILFLAG ]; then NTREE_STATUS=fail fi fi if [ "$NTREE_STATUS" != "" ]; then echo "abnormal termination (NTREE_STATUS='$NTREE_STATUS')" >> $RUNLOG else echo "normal termination" >> $RUNLOG fi local TARBALLNAME=session.$LOGDIRID.tgz debug "killing tail on server-log (pid=$TAILPID)" kill ${TAILPID} echo "`date` arb_launcher terminates now. leftover servers may continue logging into this file" >> $SERVERLOG echo "`date` End of log (now archive into $LOGBASE/$TARBALLNAME)" >> $RUNLOG ( cd $LOGBASE ; tar -zcf $TARBALLNAME $LOGDIRID ) rm -f $RUNLOG $SYSLOG $CRASHFLAG $FAILFLAG rmdir $LOGDIR local FULLTARBALL=$LOGBASE/$TARBALLNAME echo "" echo "Session log has been stored in $FULLTARBALL" local LATESTLINK=~/ARB_last_session.tgz if [ -h $LATESTLINK ]; then rm $LATESTLINK fi if [ -e $LATESTLINK ]; then echo "$LATESTLINK already exists and is no symlink" else (cd ~; ln -s $FULLTARBALL $LATESTLINK ) echo " and is also accessible via $LATESTLINK" fi if [ "$NTREE_STATUS" != "" ]; then echo "" if [ $NTREE_STATUS = "crash" ]; then echo "ARB crashed :-(" echo "To report this goto http://bugs.arb-home.de/wiki/BugReport" echo "Please include the session log(s) mentioned above!" echo "" else echo "ARB terminated abnormally" fi echo "[press ENTER]" read A fi true else echo "Error creating directory '$LOGDIR'" false fi fi } killtree() { local _pid=$1 local _sig=${2:-TERM} debug "killtree pid=${_pid} with sig=${_sig} pid=$$" kill -stop ${_pid} # stop quickly forking parent from producing childs killchilds ${_pid} ${_sig} kill ${_sig} ${_pid} } killchilds() { local _pid=$1 local _sig=${2:-TERM} debug "killchilds pid=${_pid} with sig=${_sig} pid=$$" for _child in $(ps -o pid --no-headers --ppid ${_pid}); do killtree ${_child} ${_sig} done } term_handler() { local NAMED_PIPE=$1 trace "Killing ARB session for ARB_PID=$ARB_PID" arb_clean session debug "arb_clean done - now killing process tree" killchilds $$ -TERM debug "killchilds done - exiting $$" exit } create_pipe_reader() { local NAMED_PIPE=$1 local PARENT_PID=$2 if [ -z "${ARB_LAUNCHER:-}" ]; then export ARB_LAUNCHER=0 else export ARB_LAUNCHER=$(($ARB_LAUNCHER+1)) fi debug "Creating named pipe '$NAMED_PIPE'" # (i did not manage to recover from SIGINT w/o termination of listen_pipe) # => disable SIGINT handler trap '' INT trap "term_handler $NAMED_PIPE" TERM trap "rm -f $NAMED_PIPE" EXIT { mkfifo -m 600 $NAMED_PIPE && listen_pipe $NAMED_PIPE ; debug "listen_pipe done" ; } || \ { echo "Error creating pipe '$NAMED_PIPE'" ; kill $PARENT_PID ; } debug "Pipe reader for '$NAMED_PIPE' terminates.." rm -f $NAMED_PIPE debug "Pipe '$NAMED_PIPE' removed" } initial_send_to_launcher() { local NAMED_PIPE=$1 local CMD=$2 send_to_launcher $NAMED_PIPE "$CMD" # now allow pipe reader to terminate: send_to_launcher $NAMED_PIPE "allow_termination" } wait_for_pipe() { local NAMED_PIPE=$1 while [[ ! -p $NAMED_PIPE ]]; do echo "Waiting for '$NAMED_PIPE'.." sleep 1 done debug "pipe is open" } get_pipe_name() { local SOCKETDIR="$HOME/.arb_tmp/sockets" mkdir -p "$SOCKETDIR" chmod 0700 "$SOCKETDIR" echo "$SOCKETDIR/arb_launcher.$ARB_PID" # instead of the above code, use the following to test a pipe-creation failure: # echo "/arb_launcher.$ARB_PID" } launcher() { local ASYNC=0 if [ "$1" = "--async" ]; then ASYNC=1 shift fi local CMD="$*" if [ -z "$ARB_PID" ]; then echo "Error: environment variable ARB_PID is unset. terminating.." false else if [ -z "$1" ]; then echo "Usage: arb_launcher \"shellcommand\"" echo "" echo " runs 'shellcommand'" echo " " echo " The initial call to arb_launcher will block until 'shellcommand' terminates." echo "" echo " Subsequent calls will not block. They are started from the context of the" echo " initial call. The initial call will wait for all started commands." echo "" echo " arb_launcher \"TERMINATE\"" echo "" echo " terminate the launcher without waiting for spawned commands." echo "" else debug "Using ARB_PID '$ARB_PID'" local NAMED_PIPE=$(get_pipe_name) debug "Using NAMED_PIPE '$NAMED_PIPE'" if [[ ! -p $NAMED_PIPE ]]; then ( wait_for_pipe $NAMED_PIPE ; initial_send_to_launcher $NAMED_PIPE "$CMD" ) & if (( $ASYNC==1 )); then create_pipe_reader $NAMED_PIPE $$ & else create_pipe_reader $NAMED_PIPE $$ fi else debug "pipe already was open" send_to_launcher $NAMED_PIPE "$CMD" fi # if pipe-reader was started from current process # -> blocks until all launched processes have terminated if (( $ASYNC==0 )); then wait fi fi fi } launcher "$@" debug "arb_launcher exits!"