#!/bin/sh -e
#
# $Cambridge: hermes/conf/spamd/bin/sa-update-atomic,v 1.20 2008/07/25 16:08:03 fanf2 Exp $
#
# This script wraps sa-update and sa-compile so that they perform
# their work atomically. By default sa-update deletes the contents of
# the local state dir before re-populating it, which is undesirable
# when SpamAssassin is being repeatedly re-run. This script ensures
# there is always a valid local state dir, but since SpamAssassin
# doesn't read its rules atomically it may sometimes run with a mix of
# old and new rules. I hope this will be harmless in practice.

PREFIX=/opt/spamd
PATH=$PREFIX/bin:$PATH
STATE=$PREFIX/var

# version numbers that appear in filenames
PL_VER=$($PREFIX/bin/perl -e 'printf "%.5s", $]')
SA_VER=$(sed '/$VERSION = "/!d;s///;s/".*//' $PREFIX/lib/Mail/SpamAssassin.pm)

# this is the link that we update atomically
LINK=$STATE/link

# SpamAssassin finds its rules via these paths
RULE_PATH=$STATE/$SA_VER
COMP_PATH=$STATE/compiled/$PL_VER/$SA_VER

# ensure the links are set up right
if [ ! -L $LINK ]
then
	rm -rf $STATE/*
	ln -s link $RULE_PATH
	ln -s link/compiled $STATE/compiled
fi

# so that we can ensure temporary files are cleaned up
export TMPDIR=$STATE/tmp
mkdir -p $TMPDIR

# get the ruleset's version number
# prefixed with a v so we never return an empty string
rules_version () {
	echo $(sed '/# UPDATE version /!d;s///' $1/*.cf 2>/dev/null)
}

# the physical path to the current (soon to be old) rules
OLDVER=$(rules_version $RULE_PATH)
OLDDIR=$STATE/v$OLDVER

# copy the old rules (if any) to a temporary dir
# where the update will be performed in-place
# and clean out the stale compiled rules
if [ -d $OLDDIR ]
then
	cp -a $OLDDIR $STATE/new
	rm -rf $STATE/new/compiled
fi

# make gpg quieter
GPG=$STATE/gpg
if [ ! -f $GPG/options ]
then
	mkdir -p $GPG
	chmod 700 $GPG
	echo no-secmem-warning > $GPG/options
fi

# sa-update returns 0 if it updates the rules
if ! sa-update --gpghomedir $GPG --updatedir $STATE/new
then
	# failed to perform update, or already have latest version
	rm -fr $STATE/new $TMPDIR
	exit 0
fi

# compile the rules into a subdirectory of the new rules directory
if ! sa-compile --updatedir $STATE/new/compiled
then
	# failed to perform update
	rm -fr $STATE/new $TMPDIR
	echo
	echo '****** sa-compile failed!'
	exit 1
fi

# we now have a new set of rules which we want to move to a directory
# based on their version number
NEWVER=$(rules_version $STATE/new)
NEWDIR=$STATE/v$NEWVER

if [ -d $NEWDIR ]
then
	# We've done an update but we already seem to have a copy of
	# the supposedly new rules. Something is confused. Remove the
	# symlink (temporarily reverting SpamAssassin to its original
	# ruleset) and the NEW directory that is in the way.
	rm -rf $NEWDIR
fi

# put the new rules where they belong
mv $STATE/new $NEWDIR

# create an extra configuration file that makes SpamAssassin
# append the rules version to its version number
echo "version_tag $NEWVER" >$NEWDIR/version.cf

# add the version-related links
ln -s . $NEWDIR/compiled/$PL_VER
ln -s . $NEWDIR/compiled/$SA_VER

# in a subdir, make a replacement link with the right leafname
NEWLINK=$NEWDIR/link
ln -s v$NEWVER $NEWLINK
# atomically update the magic link
mv $NEWLINK $STATE/.

# allow some time for currently-starting SpamAssassin processes
# to finish reading the old rules before removing them
sleep 1
rm -fr $OLDDIR $TMPDIR

echo
echo SpamAssassin rules updated to version $NEWVER
exit 0
