#!/bin/bash
defopts=-vrl

# bgWorldStats 1.2
# Copyright (c) 2001 "Ouch@Schooner.COM"
#
#       Permission to use, copy, modify, and distribute this software and its
#       documentation for any purpose and without fee is hereby granted,
#       provided that the above copyright notice appear in all copies.
#       The author makes no representations about the suitability of this
#       software for any purpose. It is provided "as is" without express
#       or implied warranty.
#
# http://illuminati.guildhappy.com/
#
# This will run the NG stats sender in the background, immediately returning
# to UT.
#
# Installation:
#  Put this script (chmod a+x) into same directory as the existing
#  ngWorldStats binary:
#     <UT>/NetGamesUSA.com/ngWorldStats/bin
#   and call it "bgWorldStats"
#
#  Change your server's .ini to include this:
#
#      WorldBatcherURL=../NetGamesUSA.com/ngWorldStats/bin/bgWorldStats
#      WorldBatcherParams=-vrl
#
#  Or, edit the "defopts" line above to include "-vrl"
# 
# NOTICE:
# This script has only been tested on FreeBSD with the Linux UT server
# started with the "-nohomedir" option.
# 

#
# Inspired by ngWorldStats.sh, originally by <abfackeln@abfackeln.com>
# and modified (v1.2) by Leif Sawyer <leif@gci.net>
#

#
# Change history:
# 2001-02-26   1.2 released
#     close leaked fd's (bash required), add -C and -R options
# 2001-01-30   1.1 released
#     first public release

Usage() {
   echo 2>&1 Usage: `basename $0` '[-vlrDCR] [-s statsbin] [-d logsdir] [-g gametype]
    Options:
   -v verbose ngWorldStats output
   -r carry forward last result code
   -l direct output to transcript log (only saved if an error occurs)

    Debugging (only when needed):
   -D enable debugging output
   -C do not close leaked file descriptors
   -R do not remove transcript logs of successful runs

    Change defaults:
   -d logs directory path (if relative, must be from <UT>/System)
      ["'$logs'"]
   -g game type ["UT"]
   -s ngWorldStats program to use ["ngWorldStats"] - not a pathname'
   exit 2
}

# UT runs us as "../NetGamesUSA.com/ngWorldStats/bin/bgWorldStats"
# UT execs us from the "<UT>/System" directory.

bin=`dirname $0`
logs=$bin/../logs

args=`getopt DCRrvls:g:d: $defopts $*`
if [ $? != 0 ]; then Usage; fi
oargs=$@
set -- $args
for i
do
   case "$1" in
   -D)     debug=true ;;
   -v)     verbose=-v ;;
   -r)     doresults=true ;;
   -l)     dologfile=true ;;
   -R)     dontcleanlogs=true ;;
   -C)     dontclose=false ;;
   -s)     statsbin=$2; shift ;;
   -d)     logs=$2; shift ;;
   -g)     game=$2; shift ;;
   --)   shift; break ;;
   esac
   shift
done
case $# in
0) ;;
*) Usage;;
esac

#
# Main variables
#

# ngWorldStats executable
stats=$bin/${statsbin:-ngWorldStats}

# game
game=${game:-UT}

# Saved ngWorldStats result from last run
results=$logs/.lastresult

# When we were run
now=`date +%y%m%d-%H%M`

if ${dologfile-false}
then
   # redirect stdout to log file of our run
   logf=$logs/.ngrun.$now
   exec > $logf 2>&1
fi

if ${debug-false}
then
   echo Started at: `date`
   echo Called as: $0 $oargs
   echo In Directory: `pwd`
   echo ""
fi

# ngWorldStats exit codes
ngE=1    # error (?)
ngNoLog=83  # no logfiles
ngBusy=97   # ngWorldStats already running
ngOK=99     # ngWorldStats successful
ngSyntax=139   # Server Syntax error

#
# Ugly shell locking code
#
# All systems don't have shlock(1) (from INN) or lockfile (1) (from procmail);
# FreeBSD has lockf(1) but it doesn't have the semantics I want here.
# So this makes up for it in a "somewhat" portable, safe fashion, under
# the assumption that there isn't going to be significant contention.
#
# Lock using file path $1, returning an error message
# (with original lock time and pid) if it fails.
#
# return 0 for OK; 1 for locked; 2 for lock file error
# Error message to stdout
#
Lock() {
   l=$1
   if ! mkdir $l >/dev/null 2>&1
   then
      if [ ! -d $l ]
      then
         echo "ERROR: $l not a lock dir"
         return 2
      elif [ ! -r $l/lock ]
      then
         # race?
         echo "ERROR: $l does not have a lock info file"
         return 2
      fi
      read p d < $l/lock
      # still around?
      if ps p$p >/dev/null 2>&1
      then
         echo ALREADY LOCKED: by pid $p at $d
         return 1
      fi
      # must be stale, override it
      echo 1>&2 "STALE LOCK REMOVED: by pid $p at $d"
   fi
   echo $$ `date` > $l/lock
   return 0
}
Unlock() {
   l=$1
   rm -rf $l
}

#
# Start the real work here
#

#
# Take a lock, or return busy to the UT server
#

lockf=$logs/.ngWS.lock
ret=`Lock $lockf`
if [ $? != 0 ]
then
   echo Lock error: $ret
   exit $ngBusy
fi

#
# UT leaks file descriptors (including the listening socket) to us.
# If we don't close these, the next server instance won't be able to bind to
# the ServerQueryPort (i.e., 7778) and will pick the next available one
# (i.e., 7783), even if we are in the background
#
# How many leaked?  Lots.  On my server it was fds 3..70, with 8 of those
# sockets.  I don't know if it varies, so I just close "a whole bunch".
#
close-fds() {
   ${debug-false} && fstat -p$$
   fd=3
   while [ $fd -lt $1 ]
   do
      # this next line requires bash2 to work for fd>9 (damn)
      eval "exec $fd<&-"
      fd=$(($fd+1))
   done
   ${debug-false} && fstat -p$$
}

# prevent fd leaks
# do this before forking child to avoid race conditions
${dontclose-false} || close-fds 200

#
# Start child process to send stats
#

(
   # Give parent a moment to go away
   sleep 1

   echo Exec: $stats -d $logs -g UT $verbose
   echo Start: `date`
   $stats -d $logs -g UT $verbose
   ret=$?
   echo End: `date`

   case "$ret" in
   $ngNoLog)   echo "No logfiles found" ;;
   $ngBusy) echo "ngWorldStats already running" ;;
   $ngOK)      echo "ngWorldStats successful" ;;
   $ngSyntax)  echo "Server Syntax error" ;;
   *)    echo "ngWorldStats returned $ret" ;;
   esac

   # remove transcript log if run was OK
   if ${dologfile-false}
   then
      case "$ret" in
      $ngOK)      ${dontcleanlogs-false} || rm -f $logf;;
      esac
   fi

   if ${doresults-false}
   then
      echo $ret > $results
   else
      rm -f $results 2> /dev/null
   fi

   Unlock $lockf
) &

#
# Meanwhile, in the parent process, return result to UT
#

# close the log file - the child will complete the output
exec >&- 2>&-

if ${doresults-false} && [ -r $results ]
then
   lastresults=`cat $results`
fi

exit ${lastresults:-$ngOK}
