#!/bin/sh
#
# swapmon - add / remove swap as needed
#  
# Copyright (C) 2010 Alexander Kuehn. All rights reserved.
# 
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
# 
# THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.
# 

CONFIG=%%PREFIX%%/etc/swapmonrc
VERSION="1.5"

if [ -r "${CONFIG}" ]
then	. "${CONFIG}"
fi

# where to create swapfiles 
: ${SM_HOME=/usr/.swap}
# if the swap usage is higher than that (percentage) new swap will be added 
: ${SWAP_HIGH=75}
# if the swap usage is lower than that then a previously added swapfile will be removed
: ${SWAP_LOW=45}
# if swap is running low we will increase the swapspace by this many percent
: ${SWAP_STEP=$(( 100 - ${SWAP_HIGH} ))}
# if running as daemon how long to wait between checks
: ${DELAY=30}
# if running as daemon where to log messages
: ${LOGGER=/usr/bin/logger}
# if running as daemon where to put pidfile
: ${PIDFILE=/var/run/swapmon.pid}
# if no swap is configured create a swapfile with this size
: ${SWAP_MIN=256}

LOCKF=$SM_HOME/lock
SWAPFILE=$SM_HOME/swap.XXXX
SWAPLIST=$SM_HOME/swapfiles
SWAPDEF=$SM_HOME/swap.def
MKTEMP=/usr/bin/mktemp
TRUNCATE=/usr/bin/truncate
TOUCH=/usr/bin/touch
CHMOD=/bin/chmod
MDCONFIG=/sbin/mdconfig
SWAPON=/sbin/swapon
SWAPOFF=/sbin/swapoff
SWAPCTL=/sbin/swapctl
RM=/bin/rm
SED=/usr/bin/sed
PERM=600
UID=$(/usr/bin/id -u)
GID=$(/usr/bin/id -g)
umask 0077

# a few sanity checks to see if we can do our work
if [ $UID -gt 0 ]
then	echo "I'm not running as root (uid=0) this probably wont work."
#	exit 1
fi

if [ \! -d ${SM_HOME} ]
then	/bin/mkdir -p ${SM_HOME} || { echo "Error creating ${SM_HOME}!" ; exit 1; }
fi
$TOUCH ${SWAPLIST} || { echo "Can't write to ${SWAPLIST} aborting." ; exit 1; }
# add a 1MB swapfile to check if we have the permissions to do so
NSWAP=$($MKTEMP ${SWAPFILE})
$TRUNCATE -s 1M "${NSWAP}" || { echo "Error creating swapfile ${NSWAP}, aborting." ; exit 1; }
$CHMOD ${PERM} "${NSWAP}"
MDEV=$($MDCONFIG -a -t vnode -f "${NSWAP}")
if [ -n "$MDEV" ]
then
	$SWAPON /dev/${MDEV} || { echo "error activating swapdevice /dev/${MDEV}."; exit 1; }
	$SWAPOFF "/dev/${MDEV}"
	$MDCONFIG -d -u ${MDEV} >/dev/null
	$RM -f "${NSWAP}"
else
	echo "error configuring memory disk ($MDCONFIG -a -t vnode -f \"${NSWAP}\")"
	$RM -f "${NSWAP}"
	exit
fi
# delete leftover swapfiles
find $SM_HOME -maxdepth 1 -name $(echo ${SWAPFILE##*/}|tr X \?) -type f \
	-perm ${PERM} -user $UID -group $GID -exec $RM -fv "{}" \;

if [ ${SWAP_LOW} -ge $(( $(($SWAP_HIGH * 100)) / $((100 + $SWAP_STEP)) )) ]
then	echo "SWAP_LOW(${SWAP_LOW}) schould be lower than $(( $(($SWAP_HIGH * 100)) / $((100 + $SWAP_STEP)) )) to be useful."
		echo "Swapmon $VERSION."
		exit 1
fi

# if there is no swap configured, add some
add_def_swap()
{
	SWAPCTLSK=$($SWAPCTL -sk)
	SWAPTOTAL=${SWAPCTLSK#* }
	SWAPTOTAL=${SWAPTOTAL% *}
	if [ "$SWAPTOTAL" -eq 0 ]
	then
		if [ "${SWAP_MIN}" -gt 0 ]
		then
			if [ -e "${SWAPDEF}" ]	# if the file already exists
			then	if [ -f "${SWAPDEF}" -a \! -h "${SWAPDEF}"]	# is it a regular file and no symlink?
					then	$RM -f "${SWAPDEF}"
					else	echo "Error: swapfile ${SWAPDEF} is not a regular file, aborting." ; exit 1;
					fi
			fi
			$TRUNCATE -s ${SWAP_MIN}M "${SWAPDEF}" || { echo "Error creating swapfile ${SWAPDEF}, aborting." ; exit 1; }
			$CHMOD $PERM "${SWAPDEF}"
			MDEV=$($MDCONFIG -a -t vnode -f "${SWAPDEF}")
			if [ -n "$MDEV" ]
			then
				$SWAPON /dev/${MDEV} || { echo "error activating swapdevice /dev/${MDEV}."; exit 1; }
				echo "swapmon$VERSION[$$] Activated swapfile ${SWAPDEF} as there was no swap configured!"|$LOGGER
			else
				echo "error configuring memory disk ($MDCONFIG -a -t vnode -f \"${SWAPDEF}\")"
				$RM -f "${SWAPDEF}"
				exit 1
			fi
		else
			echo "No swap configured, SWAP_MIN set to $SWAP_MIN - aborting."
			exit 1
		fi
	fi
}

check_swap()
{
	SWAPCTLSK=$($SWAPCTL -sk)
	SWAPTOTAL=${SWAPCTLSK#* }
	SWAPTOTAL=${SWAPTOTAL% *}
	SWAPUSAGE=$(( ${SWAPCTLSK##* } * 100 / $SWAPTOTAL ))
	if [ ${SWAPUSAGE} -gt ${SWAP_HIGH} ]
	then	NSWAP=$($MKTEMP ${SWAPFILE})
		BLOCKS=$(( $(( ${SWAPTOTAL} * ${SWAP_STEP} +1023)) / 102400 ))
		# make sure we can write to the swaplist
		$TOUCH "${SWAPLIST}" || { echo "swapmon$VERSION[$$] Error modifying ${SWAPLIST} exiting."|$LOGGER; exit 1; }
		$TRUNCATE -s ${BLOCKS}M "${NSWAP}"
		$CHMOD $PERM "${NSWAP}"
		if [ -s "${NSWAP}" ]
		then	MDEV=$($MDCONFIG -a -t vnode -f "${NSWAP}")
			printf "${MDEV} ${NSWAP}\n" >>"${SWAPLIST}"
			echo "swapmon$VERSION[$$] Swapusage ${SWAPUSAGE}%($((${SWAPCTLSK##* }/1024))/$(($SWAPTOTAL/1024)) M), activated new swapdevice ${MDEV} backed by ${NSWAP}."|$LOGGER
			$SWAPON /dev/${MDEV}
			$SWAPCTL -lh|$SED -e "s/^/swapmon$VERSION[$$] /g"|$LOGGER
		else	echo "swapmon$VERSION[$$] Creating swapfile ${NSWAP} failed! :("|$LOGGER
		fi
	fi
	while [ \( ${SWAPUSAGE} -lt ${SWAP_LOW} \) -a -s "${SWAPLIST}" ]
	do	MDEV=$(/usr/bin/tail -1 "${SWAPLIST}")
		SWAPF=${MDEV#* }
		MDEV=${MDEV%% *}
		if [ -s "${SWAPF}" ]
		then	if [ "${SWAPF}" = "$($MDCONFIG -l -u ${MDEV}|/usr/bin/awk '{print $4}')" ]
			then	if [ -n "$($SWAPCTL -l|$SED -n '1!p'|/usr/bin/grep '/dev/'${MDEV})" ]
				then	echo "swapmon$VERSION[$$] Swapusage ${SWAPUSAGE}%($((${SWAPCTLSK##* }/1024))/$(($SWAPTOTAL/1024)) M), deactivating swapdevice ${MDEV}."|$LOGGER
					$SWAPCTL -lh|$SED -e "s/^/swapmon$VERSION[$$] /g"|$LOGGER
					$SWAPOFF "/dev/${MDEV}"
				fi
				echo "swapmon$VERSION[$$] Detaching vnode ${MDEV}."|$LOGGER
				$MDCONFIG -d -u ${MDEV} >/dev/null
				$RM -f "${SWAPF}"
			else	echo "swapmon$VERSION[$$] Swapfile ${SWAPF} is not attached to ${MDEV} as it should be!?"|$LOGGER
				for MDEV in $($MDCONFIG -l)
				do	echo "swapmon$VERSION[$$] $($MDCONFIG -l -u ${MDEV})"|$LOGGER
				done
			fi
		else	echo "swapmon$VERSION[$$] ${SWAPF} is not a valid swapfile."|$LOGGER
		fi
		$SED -I '' -n '$!p' "${SWAPLIST}"
		SWAPCTLSK=$($SWAPCTL -sk)
		SWAPTOTAL=${SWAPCTLSK#* }
		SWAPTOTAL=${SWAPTOTAL% *}
		SWAPUSAGE=$(( ${SWAPCTLSK##* } * 100 / $SWAPTOTAL ))
	done
}

add_def_swap	# this will check if there is some swap configured
				# we can't work without
case "$1" in
	start)
		$0 -F <&- 2>&1 >/dev/null &
		;;
	stop)
		if [ -s "${PIDFILE}" ]
		then
			PID=$(/bin/cat ${PIDFILE})
			if ( /bin/ps -p $PID|grep -q $0 )
			then	/bin/kill $PID
			fi
			$RM "${PIDFILE}"
		fi
		;;
	-F)
		echo $$ >"${PIDFILE}"
		echo "swapmon$VERSION[$$] Writing pid $$ to pidfile ${PIDFILE}."|$LOGGER
		cd / || exit 1
		while [ -f "${PIDFILE}" ]
		do	check_swap
			sleep ${DELAY}
		done
		;;
	'')
		if [ -r "${LOCKF}" ]
		then
			echo "${LOCKF} exists"
			exit 1;
		else
			echo $$ >"${LOCKF}"
			LOGGER="/bin/cat"
			check_swap
		fi
		$RM -f "${LOCKF}"
		;;
	*)
		echo "$* not supported"
		;;
esac
