#!/bin/bash
# HLstatsX Community Edition - Real-time player and clan rankings and statistics
# Copyleft (L) 2008-20XX Nicholas Hastings (nshastings@gmail.com)
# http://www.hlxce.com
#
# HLstatsX Community Edition is a continuation of 
# ELstatsNEO - Real-time player and clan rankings and statistics
# Copyleft (L) 2008-20XX Malte Bayer (steam@neo-soft.org)
# http://ovrsized.neo-soft.org/
# 
# ELstatsNEO is an very improved & enhanced - so called Ultra-Humongus Edition of HLstatsX
# HLstatsX - Real-time player and clan rankings and statistics for Half-Life 2
# http://www.hlstatsx.com/
# Copyright (C) 2005-2007 Tobias Oetzel (Tobi@hlstatsx.com)
#
# HLstatsX is an enhanced version of HLstats made by Simon Garner
# HLstats - Real-time player and clan rankings and statistics for Half-Life
# http://sourceforge.net/projects/hlstats/
# Copyright (C) 2001  Simon Garner
#             
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
# 
# For support and installation notes visit http://www.hlxcommunity.com

#------------------------------------------------------------------------------
# Usage
# Information on how to use this script can be found on our wiki:
# http://wiki.hlxce.com
#------------------------------------------------------------------------------

#------------------------------------------------------------------------------
# Script Configuration
# These parameters allow you to adjust various functions of the daemon.
# In general, they should not need to be modified.
# Please visit our wiki for more information: http://wiki.hlxce.com

#------------------------------------------------------------------------------
# SCRIPTPATH:
# File system path to daemon and supporting files
# NOTE: This is only needed if the other scripts files will be in another directory.
# In general, NO TOUCHY! :)
SCRIPTPATH=.
#------------------------------------------------------------------------------

#------------------------------------------------------------------------------
# CONFFILE:
# Specifies the configuration file (relative to SCRIPTPATH) to use for the daemon
CONFFILE=hlstats.conf
#------------------------------------------------------------------------------

#------------------------------------------------------------------------------
# DAEMON:
# Specifies the daemon Perl script to be used
DAEMON=hlstats.pl
#------------------------------------------------------------------------------

#------------------------------------------------------------------------------
# LOGDIR:
# Specifies the location to store logs
LOGDIR=${SCRIPTPATH}/logs
#------------------------------------------------------------------------------

#------------------------------------------------------------------------------
# LOGDATE:
# Specifies the date format to use in log file names
LOGDATE_FORMAT=%Y-%m-%d_%H-%M-%S
#------------------------------------------------------------------------------

#------------------------------------------------------------------------------
# PIDDIR:
# Specifies location to store daemon PID files
PIDDIR=${SCRIPTPATH}
#------------------------------------------------------------------------------


#------------------------------------------------------------------------------
# Nothing to modify below here
WEBSITE=http://www.hlxce.com
WIKI=http://wiki.hlxce.com

# Start output
echo 
echo "HLstatsX:CE daemon control"
echo "${WEBSITE}"
echo "---------------------------"

# Change to directory of script
cd `dirname ${0}`

# Perform some initial checks before we encounter later errors
# Check if we can write to the SCRIPTPATH
if [ ! -w ${SCRIPTPATH} ]; then
	echo "CRITICAL ERROR: Could not write to SCRIPTPATH: ${SCRIPTPATH}"
	echo "Verify you have write access to this directory."
	echo "Visit our wiki for more information: ${WIKI}."
	exit 1
fi

# Check if the daemon perl script exists
if [ ! -f ${SCRIPTPATH}/${DAEMON} ]; then
	echo "CRITICAL ERROR: Cannot access the daemon: ${DAEMON}"
	echo "Verify that the daemon, and corresponding files, exist in ${SCRIPTPATH}"
	echo "Visit our wiki for more information: ${WIKI}."
	exit 1
fi

# Verify shebang line in daemon
SHEBANG=`head -n1 ${SCRIPTPATH}/${DAEMON}`
if [[ ${SHEBANG} =~ ^#! ]]; then
  SHEBANG_BINARY=`echo "${SHEBANG}" | sed 's/^#!//'`
  if [ ! -f ${SHEBANG_BINARY} ]; then
    echo "CRITICAL ERROR: The path to Perl is incorrect in ${DAEMON}."
    echo "Current Perl path in shebang: ${SHEBANG_BINARY}"
    echo "Visit our wiki for more information: ${WIKI}."
    echo 
    echo "Potential paths for Perl: "
    echo `which perl`
    exit 1
  fi
else
    echo "CRITICAL ERROR: The shebang line is incorrectly configured.  Please verify that your shebang line is correct in ${DAEMON}."
    echo "Current shebang line: ${SHEBANG}"
    echo "Visit our wiki for more information: ${WIKI}."
    exit 1
fi

# Create logdir if needed
if [ ! -d ${LOGDIR} ]; then
	mkdir ${LOGDIR}
fi

# Make sure we can write to logdir
if [ ! -w ${LOGDIR} ]; then
	echo "CRITICAL ERROR: Could not write to the log folder: ${LOGDIR}"
	echo "Verify that you have write access to the log folder."
	echo "Visit our wiki for more information: ${WIKI}."
	exit 1
fi

# Daemon control functions
function start_daemon {
	# This function handles the creation of a new daemon process.
	# This function requires one parameter: PORT
	# Returns:
	#	0 - Daemon started
	#	1 - Daemon failed to start
	#	2 - Daemon already running
	
	if [ ! $1 ]; then
		echo "CRITICAL ERROR: No port was received on function start_daemon"
		exit 1
	else
		local PORT=$1
	fi
	
	local LOG=${LOGDIR}/hlstats_${PORT}_`date +${LOGDATE_FORMAT}`
	
	local PID=`get_pid ${PORT}`
	# Check if a PID exists for this port number
	if [ "${PID}" != "" ]; then
		# PID exists -- check if the daemon is running.
		kill -0 ${PID} &> /dev/null
		if [ $? -eq 0 ]; then
			# Daemon running -- nothing to do.
			return 2
		else
			# Daemon not running -- remove pid.
			remove_pidfile ${PORT}
		fi
	fi
	
	# Start the daemon on requested port
	echo -ne "Attempting to start HLstatsX:CE daemon on port ${PORT}..."
	${SCRIPTPATH}/${DAEMON} --configfile=${CONFFILE} --port=${PORT} &> ${LOG} &
	# Store PID in memory until we verify Daemon has launched
	PID=$!

	# Perform one quick check to see if PID is running
	kill -0 ${PID} &> /dev/null
	if [ $? -eq 0 ]; then
		create_pidfile ${PORT} ${PID}
		echo ""
		return 0
	else
		# PID not detected in time, keep checking for 10 more seconds.
		local i=1
		while [ $i -le 10 ]
		do
			echo -ne " ${i}"
			sleep 1
			# Perform a kill check against saved PID
			kill -0 ${PID} &> /dev/null
			# Check results of pid test
			if [ $? -eq 1 ]; then
				# Process does not exist
				let i++
				if [ $i -eq 10 ]; then
					# Daemon did not respond to start request within 10 seconds.
					return 1
				fi
			else
				# Daemon started successfully -- commit PID to file
				create_pidfile ${PORT} ${PID}
				echo ""
				return 0
			fi
		done
	fi
}

function stop_daemon {
	# This function handles shutting a daemon down.
	# This function requires one parameter: PORT.
	
	# Returns:
	#	0 - Daemon gracefully stopped 
	#	1 - Daemon forcefully stopped
	#	2 - Daemon could not be stopped
	#	3 - No daemon to stop or PID missing
	
	if [ ! $1 ]; then
		echo "CRITICAL ERROR: No port was received on function stop_daemon"
		exit 1
	else
		local PORT=$1
	fi
	
	local PID=`get_pid ${PORT}`
		
	if [ ${PID} -eq 0 ]; then
		return 3
	fi
	
	# Attempt to stop the daemon
	echo -n "Attempting graceful shutdown of HLstatsX:CE daemon on port ${PORT} "
	kill -INT ${PID} &> /dev/null
	
	if [ $? -ne 0 ]; then
		# Daemon is not running, purge the PID.
		remove_pidfile ${PORT}
		echo ""
		return 3
	else
		# Found running PID -- perform a quick check before entering loop
		kill -0 ${PID} &> /dev/null
		if [ $? -eq 1 ]; then
			# Daemon stopped, remove PID
			remove_pidfile ${PORT}
			echo ""
			return 0
		else
			local i=1
			while [ $i -le 10 ]
			do
				echo -n " ${i}"
				sleep 1
				# Perform a kill check against saved PID
				kill -0 ${PID} &> /dev/null
				if [ $? -eq 0 ]; then
					# Daemon still operating
					let i++
				else
					# Daemon stopped, remove PID
					remove_pidfile ${PORT}
					echo ""
					return 0
				fi
			done
		fi

		# Daemon did not respond to shutdown, attempt a forced kill
		echo ""
		echo "WARNING: Daemon did not respond to a graceful shut down.  Forcing a shut down on port ${PORT} "
		local i=1
		while [ $i -le 5 ]
		do
			kill -KILL ${PID} &> /dev/null
			echo -n " ${i}"
			sleep 1
			
			# Check if PID is still present
			kill -0 ${PID} &> /dev/null

			if [ $? -eq 0 ]; then
				# Daemon still operating
				let i++
			else
				# Daemon stopped successfully.
				remove_pidfile ${PORT}
				echo ""
				return 1
			fi
		done
		return 2
	fi
}

function reload_daemon {
	# This function handles reloading a daemon down.
	# This function requires one parameter: PORT.
	
	# Returns:
	#	0 - Reload sent successfully
	#	1 - Daemon not running or pid file missing
	
	# Sanity check on incoming required parameter
	if [ ! $1 ]; then
		echo "CRITICAL ERROR: No port was received on function reload_daemon"
		exit 1
	else
		local PORT=$1
	fi
	
	
	local PID=`get_pid ${PORT}`
	# Check to verify the daemon is operational
	if [ ${PID} -ne 0 ]; then
		kill -0 ${PID} &> /dev/null
		if [ $? -eq 0 ]; then
			kill -HUP ${PID} &> /dev/null
			return 0
		else
			return 1
		fi
	else
		return 1
	fi
}

function check_port {
	# This function verifies user input on the port number
	# One argument is required
	
	# Returns:
	#	0 - Valid input
	#	1 - Invalid Input (non-digit or not in UDP port range)
	
	if [ $1 ]; then
		# Perform regex test on input
		echo ${1} | grep -q '^[0-9]\{1,5\}$'
		# Check if within range and if grep test was successful.
		if [ $? -eq 0 ] && [ $1 -le 65535 ] && [ $1 -ge 1 ]; then
			return 0
		else
			return 1
		fi
	fi
}

function get_status {
	# This function performs a lookup for the PID on specified port and checks status
	# Parameters:
	#	1 - port
	
	# Returns:
	#	0 - PID is running
	#	1 - PID is not running
	#	2 - Invalid PID
	
	if [ $1 ]; then
		local PID=`get_pid ${1}`
		if [ "${PID}" != "" ]; then
			kill -0 ${PID} &> /dev/null
			if [ $? -eq 0 ]; then
				return 0
			else
				return 1
			fi
		else
			return 2
		fi
	fi
}

function create_pidfile {
	# This function will handle the creation of a PID file for a corresponding port
	# Parameters required:
	#	1 - port number
	#	2 - PID
	
	# Returns:
	#	0 - PID saved
	#	1 - Unable to save PID
	
	if [[ $1 && $2  ]]; then
		PIDFILE=${PIDDIR}/hlstats_${1}.pid
		echo ${2} > ${PIDFILE}
		
		if [ "`cat ${PIDFILE}`" -eq "${2}" ]; then
			return 0
		else
			return 1
		fi
	fi
}

function remove_pidfile {
	# This function will handle the deletion of a PID file for a corresponding port
	# Parameters required:
	#	1 - port number
	
	# Returns:
	#	0 - PID removed
	#	1 - PID does not exist
	
	if [ $1 ]; then
		PIDFILE=${PIDDIR}/hlstats_${1}.pid
		rm -f ${PIDFILE} &> /dev/null
		if [ $? -eq 0 ]; then
			return 0
		else
			return 1
		fi
	fi
}
		

function get_pid {
	# This function will echo out the found pid and return 0, or return 1 if it finds nothing
	# Parameters required:
	#	1 - port number
	
	# Output
	#	Requested PID on return 0
	
	# Returns:
	#	0 - PID number for corresponding process
	#	1	- No PID file for specified port
	
	if [ $1 ]; then
		PIDFILE=${PIDDIR}/hlstats_${1}.pid
		PID=`cat ${PIDFILE} 2> /dev/null`
		if [ $? -eq 0 ]; then
			echo ${PID}
			return 0
		else
			return 1
		fi
	fi
}

# Cleanup old legacy run_hlstats stuff
# Check if hlstats.pid exists (original pid from legacy run_hlstats)
if [ -f ${PIDDIR}/hlstats.pid ]; then
	echo "WARNING: A old PID file has been detected.  To prevent further troubles this daemon will be shut down."
	kill -KILL `cat ${PIDDIR}/hlstats.pid` &> /dev/null
	sleep 1
	# Check if PID is dead
	i=1
	while [ $i -le 5 ]
	do
		kill -0 `cat ${PIDDIR}/hlstats.pid` &> /dev/null
		if [ $? -eq 0 ]; then
			# Daemon still operating
			let i++
			sleep 1
		else
			# Daemon stopped successfully.
			rm -f ${PIDDIR}/hlstats.pid
			echo ""
			echo "HLstatsX:CE daemon has been forcefully stopped."
			echo "Please re-run this script to control your daemon."
			exit
		fi
	done
fi

# Daemon control case switcher
case "$1" in
	start)
		# Usage: run_hlstats start <# of daemons> <first port number> <port increment number>
		# All arguments are optional
		# Defaults: # of Daemons = 1; First port number = 27500; Port increment number = 1
		NUMDAEMONS=1
		STARTPORT=27500
		INCREMENT=1

		# Get user-specified number of daemons
		if [ $2 ]; then
			NUMDAEMONS=$2
		fi
		
		if [ $3 ]; then
			check_port $3
			if [ $? -eq 0 ]; then
				STARTPORT=$3
			else
				echo "CRITICAL ERROR: An invalid port number was specified."
				exit 1
			fi
		fi
		
		if [ $4 ]; then
			INCREMENT=$4
		fi
		
		# Saving this for a future release -- right now this would prevent people from running run_hlstats every few minutes to make sure their daemon is operational.		
		#else
		#	# Lookup the highest currently used port number
		#		LASTPORT=`ls ${PIDDIR} | egrep 'hlstats_[0-9]{1,5}.pid' | egrep -o '[0-9]{1,5}' | tail -1`
		#		if [ "${LASTPORT}" != "" ]; then
		#			# We have currently running daemons, to take the current highest port number and increment it
		#			let STARTPORT=LASTPORT+INCREMENT
		#		fi
		#	
		#fi

		i=0
		CURRENTPORT=${STARTPORT}
		while [ ${i} -lt ${NUMDAEMONS} ]
		do
			start_daemon ${CURRENTPORT}
			case $? in
				0)
					echo "Daemon successfully started on port ${CURRENTPORT}"
					let CURRENTPORT=CURRENTPORT+INCREMENT
					let i++
				;;
				1)
					echo "CRITICAL ERROR: Unable to start daemon on port ${CURRENTPORT}"
					exit 1
				;;
				2)
					echo "Daemon is already running on port ${CURRENTPORT}"
					let CURRENTPORT=CURRENTPORT+INCREMENT
					let i++
				;;
			esac
		done
	;;
	
	stop)
		# Usage: run_hlstats stop <port>
		# All arguments are optional
		# Defaults: port = ALL
		
		if [ $2 ]; then
			check_port $2
			if [ $? -eq 0 ]; then
				PORT=$2
			else
				echo "CRITICAL ERROR: An invalid port number was specified."
				exit 1
			fi		
		else
			PORT=0
		fi
		
		# Stop a single daemon
		if [ ${PORT} -ne 0 ]; then
			stop_daemon ${PORT}
			case $? in
				0)
					echo "Daemon gracefully stopped on port ${PORT}"
					exit 0
				;;
				1)
					echo "Daemon forcefully stopped on port ${PORT}"
					exit 0
				;;
				2)
					echo "WARNING: Daemon could not be stopped on port ${PORT}"
					exit 1
				;;
				3)
					echo "No daemon running on port ${PORT} or PID file is missing."
					exit 1
				;;
			esac
		fi
		
		# Stop all daemons
		PORTS=`ls ${PIDDIR} | egrep 'hlstats_[0-9]{1,5}.pid' | egrep -o '[0-9]{1,5}'`
		if [ $? -eq 0 ]; then
			for port in ${PORTS} ; do
				stop_daemon ${port}
				case $? in
					0)
						echo "Daemon gracefully stopped on port ${port}"
					;;
					1)
						echo "Daemon forcefully stopped on port ${port}"
					;;
					2)
						echo "WARNING: Daemon could not be stopped on port ${port}"
					;;
					3)
						echo "No daemon running on port ${port} or PID file is missing."
					;;
				esac
			done
		else
			echo "No daemons found running, or PID files are missing."
			exit 1
		fi
	;;
	
	restart)
		# Usage: run_hlstats restart <port>
		# All arguments are optional
		# Defaults: port = ALL
		
		if [ $2 ]; then
			check_port $2
			if [ $? -eq 0 ]; then
				PORT=$2
			else
				echo "CRITICAL ERROR: An invalid port number was specified."
				exit 1
			fi		
		else
			PORT=0
		fi
		
		# Handle individual restart request
		if [ ${PORT} -ne 0 ]; then
			stop_daemon ${PORT}
			case $? in
				0 | 1 | 3)
					start_daemon ${PORT}
					if [ $? -eq 0 ]; then
						echo "Daemon successfully restarted on port ${PORT}"
						exit 0
					else
						echo "CRITICAL ERROR: Failed to restart daemon on port ${PORT}"
						exit 1
					fi
				;;
				2)
					echo "WARNING: Daemon could not be stopped on port ${port}"
					exit 1
				;;
			esac
		fi
		
		# Restart all PIDs
		PORTS=`ls ${PIDDIR} | egrep 'hlstats_[0-9]{1,5}.pid' | egrep -o '[0-9]{1,5}'`
		if [ $? -eq 0 ]; then
			for port in ${PORTS} ; do
				stop_daemon ${port}
				case $? in
					0 | 1 | 3)
						start_daemon ${port}
						if [ $? -eq 0 ]; then
							echo "Daemon successfully restarted on port ${port}"
						else
							echo "WARNING: Failed to restart daemon on port ${port}"
						fi
					;;
					2)
						echo "WARNING: Daemon could not be stopped on port ${port}"
						exit 1
					;;
				esac
			done
		else
			echo "WARNING: No HLstatsX:CE daemons currently running."
			exit 1
		fi
	;;
	
	reload)
		# Usage: run_hlstats reload <port>
		# All arguments are optional
		# Defaults: port = ALL

		if [ $2 ]; then
			check_port $2
			if [ $? -eq 0 ]; then
				PORT=$2
			else
				echo "CRITICAL ERROR: An invalid port number was specified."
				exit 1
			fi		
		else
			PORT=0
		fi
		
		# Handle individual reload request
		if [ ${PORT} -ne 0 ]; then
			reload_daemon ${PORT}
			if [ $? -eq 0 ]; then
				echo "Successfully reloaded daemon running on port ${PORT}"
				exit 0
			else
				echo "WARNING: Unable to reload daemon on port ${PORT} (daemon might not be running)"
				exit 1
			fi
		fi
		
		# Reload all PIDs
		PORTS=`ls ${PIDDIR} | egrep 'hlstats_[0-9]{1,5}.pid' | egrep -o '[0-9]{1,5}'`
		if [ "${PORTS}" != "" ]; then
			for port in ${PORTS} ; do
				reload_daemon ${port}
				if [ $? -eq 0 ]; then
					echo "Successfully reloaded daemon running on port ${port}"
				else
					echo "WARNING: Unable to reload daemon on port ${port} (daemon might not be running)"
				fi
			done
		else
			echo "WARNING: No HLstatsX:CE daemons currently running."
			exit 1
		fi		
	;;
	
	status)
		# Usage: run_hlstats status <port>
		# All arguments are optional
		# Defaults: port = ALL
		
		if [ $2 ]; then
			check_port $2
			if [ $? -eq 0 ]; then
				PORT=$2
			else
				echo "CRITICAL ERROR: An invalid port number was specified."
				exit 1
			fi		
		else
			PORT=0
		fi

		# Handle individual status request
		if [ ${PORT} -ne 0 ]; then
			get_status ${PORT}
			case $? in
				0)
					echo "Daemon on port ${PORT} is currently running."
					exit 0
				;;
				1)
					echo "A stale process was found for daemon on port ${PORT}."
					exit 0
				;;
				2)
					echo "There is no daemon running on port ${PORT}."
					exit 0
				;;
			esac
		fi
		
		# Reload all PIDs
		PORTS=`ls ${PIDDIR} | egrep 'hlstats_[0-9]{1,5}.pid' | egrep -o '[0-9]{1,5}'`
		if [ "${PORTS}" != "" ]; then
			for port in ${PORTS} ; do
				get_status ${port}
				case $? in
					0)
						echo "Daemon on port ${port} is currently running."
					;;
					1)
						echo "A stale process was found for daemon on port ${port}.  It has been removed."
					;;
					2)
						echo "There is no daemon running on port ${port}."
					;;
				esac
			done
		else
			echo "WARNING: No HLstatsX:CE daemons currently running."
			exit 1
		fi
	;;
		
	*)
		echo "Usage"
		echo "All optional arguments are in <>.  The default is in ()."
		echo ""
		echo -e "\trun_hlstats start   <number of daemons (1)> <starting port number (27500)> <port increment (1)>"
		echo -e "\trun_hlstats stop    <port # of daemon to stop (ALL)>"
		echo -e "\trun_hlstats status  <port # of daemon to check status of (ALL)>"
		echo -e "\trun_hlstats restart <port # of daemon to restart (ALL)>"
		echo -e "\trun_hlstats reload  <port # of daemon to reload (ALL)>"
	;;
esac
exit

