#! /bin/sh

# Fetch, build, install, and start a new version of the DCC just
#   as it was configured last time.

# This script should only be used after the DCC has been correctly configured.

# If this script fails in its efforts to use wget, curl, fetch, or ftp
#   to download a new tarball, then manually download a tarball to
#   build subdirectory in the DCC home directory, probably @prefix@/build.
#   This script unpacks the newest DCC tarball it finds in that directory,
#   which will probably be the manually download file.


# This script should usually not be used if large configuration changes are
#   needed.  Instead use `./configure ...; make install` as usual.  That will
#   create a new version of this script with the ./configure settings captured.
#   However, -eENV=VAL and -cCONF=VAL can be used to tweak an installation,
#	as in `updatedcc -c--with-uid=dcc` `updatedcc -c--without-uid`
#	or `updatedcc -e DBGFLAGS=-g`

#   -e ENV=VAL	set environment variable ENV to VAL before running ./configure

#   -c CONF=VAL	add "CONF=VAL" to the end of the settings given ./configure

#   -s base-URL	fetch the tarball using base-URL instead of the default.
#		    The name of the tarball is appended to base-URL.

#   -V x.y.z	try to fetch version x.y.z of the tarball instead of the
#		    the most recent verison

#   -C pfile	fetch theDCC source using parameters in pfile

#   -T tgt	target instead of "install" for `make`

#   -U		only fetch, unpack, and configure the tarball.  Do not compile
#		    or install the software.

#   -K		cron (kron) mode; configure, compile, and install only
#		    if the fetched version differs, and be silent when
#		    nothing is done

#   -x		debug
#   -v		verbose


# This script should be run as root, but can be run as the non-root
# user that runs dccm and dccd.  If not run as root, it cannot install
# man pages or user commands including cdcc and dccproc.

# The following environment variables are set by this script to their values
#   when ./configure or this script was last run:
#   Unsetting variables that are not set often yields a non-zero command status
#   so do this before watching for error from mkdir and so forth
export @UPDATEDCC_ENV@
unset @UPDATEDCC_ENV@
ENV_SET="@UPDATEDCC_ENV_SET@"
@UPDATEDCC_ENV_SET@
@UPDATEDCC_ENV_EXPORT@


# Copyright (c) 2008 by Rhyolite Software, LLC
#
# This agreement is not applicable to any entity which sells anti-spam
# solutions to others or provides an anti-spam solution as part of a
# security solution sold to other entities, or to a private network
# which employs the DCC or uses data provided by operation of the DCC
# but does not provide corresponding data to other users.
#
# Permission to use, copy, modify, and distribute this software without
# changes for any purpose with or without fee is hereby granted, provided
# that the above copyright notice and this permission notice appear in all
# copies and any distributed versions or copies are either unchanged
# or not called anything similar to "DCC" or "Distributed Checksum
# Clearinghouse".
#
# Parties not eligible to receive a license under this agreement can
# obtain a commercial license to use DCC by contacting Rhyolite Software
# at sales@rhyolite.com.
#
# A commercial license would be for Distributed Checksum and Reputation
# Clearinghouse software.  That software includes additional features.  This
# free license for Distributed ChecksumClearinghouse Software does not in any
# way grant permision to use Distributed Checksum and Reputation Clearinghouse
# software
#
# THE SOFTWARE IS PROVIDED "AS IS" AND RHYOLITE SOFTWARE, LLC DISCLAIMS ALL
# WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
# OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL RHYOLITE SOFTWARE, LLC
# BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES
# OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
# ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
# SOFTWARE.
#	Rhyolite Software DCC 1.3.103-1.111 $Revision$
#	@configure_input@

# English messages so grep can suppress them;
#   simple collating sequence for sort
#   sane gcc error messages
LC_ALL=C; export LC_ALL


RELEASE=1.3.103
HTTP_REFERER=DCC-$RELEASE-script; export HTTP_REFERER

DCC_HOMEDIR=@prefix@
DCC_LIBEXEC=@libexecdir@	    # in case dcc_conf has disappeared
. $DCC_HOMEDIR/dcc_conf
BDIR=$DCC_HOMEDIR/build
DCCDIR=$BDIR/dcc
OK_MARK=$BDIR/.updatedcc_ok
BURLS=
VERSION=

ME=`basename $0`
USAGE="$ME: [-xvUK] [-e ENV=VAL] [-c CONF=VAL] [-s base-URL] [-V x.y.z]\\
	[-C pfile] [-T make-tgt]"
VERBOSE=
DEBUG=
UNPACK=
CRON=
CONFS=
PFILE="@UPDATEDCC_PFILE@"
MAKE_TGT=install
while getopts "xvUKe:c:s:V:p:C:T:" c; do
    case $c in
    x) set -x; DEBUG=-x VERBOSE=yes;;
    v) VERBOSE=yes;;
    U) UNPACK=yes;;
    K) CRON=yes;;
    e) # quote any odd characters
	SETTING=`echo "$OPTARG" | sed -e 's/\([^-_/:=+,.a-zA-Z0-9]\)/\\\\\1/g'`
	eval "$SETTING"
	export `expr "$OPTARG" : '\([^=]*\).*' || true`
	ENV_SET="$ENV_SET $SETTING"
	;;
    c) CONFS="$CONFS $OPTARG";;
    s) BURLS="$BURLS $OPTARG";;
    V) VERSION=`expr "X$OPTARG" : 'X\([1-9]\.[1-9][0-9]*\.[1-9][0-9]*\)'`
	if test "$VERSION" != "$OPTARG"; then
	    echo "invalid -V $OPTARG" 1>&2; exit 1;
	fi
	;;
    C) PFILE="$OPTARG";;
    T) MAKE_TGT="$OPTARG";;
    p) echo "All 3 DCC tarballs are now the same";;
    *) echo "$USAGE" 1>&2; exit 1;;
    esac
done
shift `expr $OPTIND - 1 || true`
if test "$#" -ne 0; then
    echo "$USAGE" 1>&2; exit 1
fi

if test -z "$VERBOSE"; then
    WGETQUIET='-nv'
    CURLQUIET=
    CONFQUIET='--quiet'
    MQUIET=
else
    WGETQUIET=
    CURLQUIET=
    CONFQUIET=
    MQUIET='#'
fi

# assume cron mode if stdin is not a tty
if test -z "$CRON" && test -n "`tty 2>&1 | grep 'not a tty'`"; then
    echo "assume -K because stdin is not a tty"
    CRON=assumed
fi
exec </dev/null
if test -n "$CRON"; then
    # be quite in cron mode unless told otherwise
    if test -z "$VERBOSE" -a "$CRON" != assumed; then
	WGETQUIET='-q'
	CURLQUIET='-s'
	exec >/dev/null
    fi
    exec 3>&1			# preserve stdout in case cron mode does work

    if test -n "$UNPACK"; then
	echo "-u (unpack only) and -K (cron mode) are incompatible" 1>&2
	exit 1
    fi
    if test -n "$VERSION"; then
	echo "-V$VERSION and -K (cron mode) are incompatible" 1>&2
	exit 1
    fi
    # Do nothing if we have run recently.
    #	Pick a somewhat random number of days to reduce the crowds that
    #	otherwise happen on anniversaries of releases.
    DAYS=`ps | cksum | sed -e 's/ /9/g' -e 's/[^0-9]*\([0-9]\{1,6\}\).*/\1/'`
    DAYS=`expr '(' $DAYS % 3 ')' + 6`
    if test -d $DCCDIR							\
	    && test -n "`find $OK_MARK -mtime -$DAYS 2>/dev/null`"; then
	echo "less than $DAYS days since the last updatedcc -K; stop"
	exit 0
    fi
fi


# see if we have permission
set -e
if test ! -d $BDIR; then
    rm -f $BDIR || true
    mkdir $BDIR
fi
cd $BDIR
if test ! -d $DCCDIR; then
    rm -f $DCCDIR || true
    mkdir $DCCDIR
fi
set +e
for NM in "$DCC_HOMEDIR" "$DCC_LIBEXEC"; do
    if test -n "$NM" -a ! -w "$NM"; then
	echo "cannot write $NM; must $ME be run as root?" 1>&2
	exit 1
    fi
done
find $DCCDIR -type d | while read SUBDIR; do
	if test ! -w $SUBDIR; then
	    echo "cannot write $SUBDIR; must $ME be run as root?" 1>&2
	    # but maybe this is ok
	    break
	fi
    done

if test -z "$PFILE"; then
    if test -n "@UPDATEDCC_PFILE@"; then
	CONFS="--without-updatedcc-pfile $CONFS"
    fi
    # quietly use $DCC_HOMEDIR/.updatedcc_pfile if it exists
    if test -s "$DCC_HOMEDIR/.updatedcc_pfile"; then
	PFILE="$DCC_HOMEDIR/.updatedcc_pfile"
    fi
else
    PFILE=`echo $PFILE | sed "s@^[^/]@$DCC_HOMEDIR/&@"`
    # add it to the configuration if it is not the default
    if test "$PFILE" != "$DCC_HOMEDIR/.updatedcc_pfile"; then
	CONFS="--with-updatedcc-pfile=$PFILE $CONFS"
    fi
fi
if test -f "$PFILE" -a ! -r "$PFILE"; then
    echo "cannot read $PFILE; must $ME be run as root?" 1>&2
fi
if test -n "$PFILE"; then
    . $PFILE
fi

if test -z "$BURLS"; then
    # $SERVER is set from $DCC_HOMEDIR/.updatedcc_pfile
    if test -n "$SERVER"; then
	BURLS="$SERVER"
    else
	BURLS="http://www.dcc-servers.net/dcc/source http://www.rhyolite.com/dcc/source ftp://ftp.dcc-servers.net/src/dcc ftp://ftp.rhyolite.com/src/dcc"
    fi
fi

if test -z "$VERSION"; then
    DLTBALL="dcc.tar.Z"
    TBALL=
    TBALL_PATH="$DLTBALL"
else
    DLTBALL="dcc-$VERSION.tar.Z"
    TBALL="$DLTBALL"
    TBALL_PATH="old/$DLTBALL"
fi
# don't blab the password needlessly
print_url () {
    PURL=`echo "$2" | sed -e 's%^\([fhtp]*://\)[^/@:]\{1,\}:[^/@:]\{1,\}@%\1%'`
    echo "using $1 to get $PURL"
}


TRAPS="0 1 2 15"
stop_dccd () {
    if test "$DCC_UPDATEDCC_FAST" = yes; then
	return
    fi
    DCC_UPDATEDCC_FAST= export DCC_UPDATEDCC_FAST

    # trapped signals on some systems must start by turning themselves off,
    #	especially #0
    trap "trap $TRAPS; echo 'stopping update; please wait'; $DCC_RESTART" $TRAPS

    # start stopping dccd now in the hope that it will have released the
    #	sockets and database files by the time we need to restart it.
    $DCC_LIBEXEC/stop-dccd $DEBUG
    # switch to a working server
    @bindir@/cdcc -q rtt >/dev/null 2>&1 || true
}

if test -n "$UNPACK"; then
    DCC_RESTART=
else
    DCC_RESTART="$DCC_LIBEXEC/rcDCC $DEBUG start"
    if test -z "$CRON"; then
	# let compiling be slow instead of stopping the server in cron mode
	stop_dccd
    fi
fi


# use fetch, wget, curl, or ftp that understands URLs
#	notice '-c --with-fetch_cmd=X'
PGM=`echo "X $confs" | sed -n -e 's/.*--with-fetch[-_]cmd=\([^ ]*\)/\1/p'`
if test -z "$PGM"; then
    PGM=@FETCH_CMD@
fi
PGM_B=`basename $PGM`
SUCCESS=
oldTZ=$TZ; TZ=UTC; export TZ		# Solaris FTP is confused by dates
if test "$PGM_B" = wget; then
    for BURL in $BURLS; do
	URL="$BURL/$TBALL_PATH"
	print_url "$PGM_B" "$URL"
	# Do not use --mirror because -r results in a 0 exit status
	#   even on failure.
	# Do not use --no-remove-listing, -nr, or --dont-remove-listing
	#   because none of them are supported by all versions of wget.
	# At least some versions of wget exit with 0 after having done
	#   nothing but emitting a usage message.
	if $PGM $WGETQUIET -nd --no-host-directories -N \
		--retr-symlinks --passive-ftp @FETCH_WGET_OPTS@ "$URL"; then
	    if test -s $DLTBALL; then
		# notice if we get an HTTP error document with or without
		# HTTP headers
		if test -z "`sed -n -e 2q				\
				-e 's/.*DOCTYPE.*/HTML/p'		\
				-e 's/<HEAD>/HTML/p' -e 's/<head>/HTML/p' \
			$DLTBALL`"; then
		    SUCCESS="yes"
		    break
		fi
		# do not leave a broken file
		rm -f $DLTBALL
	    fi
	fi
	echo
    done
    PGM_B=
fi

if test "$PGM_B" = fetch; then
    for BURL in $BURLS; do
	URL="$BURL/$TBALL_PATH"
	print_url "$PGM_B" "$URL"
	if $PGM -p -q -m "$URL"; then
	    if test -s $DLTBALL; then
		SUCCESS="yes"
		break
	    fi
	fi
	echo
    done
    PGM_B=
fi

if test "$PGM_B" = curl; then
    for BURL in $BURLS; do
	URL="$BURL/$TBALL_PATH"
	print_url "$PGM_B" "$URL"
	if $PGM @FETCH_CURL_OPTS@ -R --connect-timeout 30 --max-time 600 \
		    $CURLQUIET "$URL" -o $DLTBALL; then
	    # --fail does not work on at least some versions of curl
	    if test -s $DLTBALL; then
		# notice if we get an HTTP error document with or without
		# HTTP headers
		if test -z "`sed -n -e 2q				\
				-e 's/.*DOCTYPE.*/HTML/p'		\
				-e 's/<HEAD>/HTML/p' -e 's/<head>/HTML/p' \
			$DLTBALL`"; then
		    SUCCESS="yes"
		    break
		fi
		# do not leave a broken file
		rm -f $DLTBALL
	    fi
	fi
	echo
    done
    PGM_B=
fi

# try FTP if it is the right choice or the right choice has not worked
if test "$PGM_B" = ftp -o ! -s $DLTBALL; then
    if test "$PGM_B" != ftp ; then
	PGM_B=ftp
	PGM=ftp
    fi
    for BURL in $BURLS; do
	URL="$BURL/$TBALL_PATH"
	print_url "$PGM_B" "$URL"
	if $PGM -p "$URL"; then
	    if test -s $DLTBALL; then
		SUCCESS="yes"
		break
	    fi
	fi
	echo
    done
    # if that did not work, try ancient anonymous FTP
    if test ! -s $DLTBALL; then
	for URL in $BURLS; do
	    HOST=`expr "$URL" : "ftp://\([^/]*\)/"`
	    SRC=`expr "$URL" : "ftp://[^/]*/\(.*\)"`
	    if test -z "$HOST" -o -z "$SRC"; then
		continue
	    fi
	    USR=`expr "$HOST" : "\([^/:]*\):"`
	    if test -z "$USR"; then
		USR=anonymous
		PASSWD=`hostname`
	    else
		PASSWD=`expr "$HOST" : "$USR:\(.*\)@.*"`
		HOST=`expr "$HOST" : ".*@\(.*\)"`
	    fi
	    echo "try old anonymous FTP with $HOST and $SRC"
	    (echo "user $USR $PASSWD ";				\
			echo "binary";				\
			echo "get $SRC/$DLTBALL $DLTBALL")	\
		| ftp -n $HOST
	    if test -s $DLTBALL; then
		SUCCESS="yes"
		break
	    fi
	    # some versions of ftp like to leave empty files
	    rm -f $DLTBALL
	done
    fi
    PGM_B=
fi

if test -n "$PGM_B"; then
    echo "unrecognized fetch program $PGM_B" 1>&2; exit 1
fi
if test -z "$oldTZ"; then
    unset TZ
else
    TZ=$oldTZ
fi
if test -z "$SUCCESS" -o ! -s $DLTBALL; then
    echo
    echo "failed to fetch a new copy of $DLTBALL" 1>&2
    if test -n "$PFILE"; then
	echo "is $PFILE correct?" 1>&2
    fi
    # try to install an existing tar ball if not in cron mode
    if test -n "$CRON"; then
	exit 1
    fi
    echo
fi
set -e

# pick the newest tarball, possibly one fetched manually and already present
#   instead of the official tarball fetched by this script.
if test -z "$TBALL"; then
    TBALL=`ls -Lt dcc*.tar.Z | head -1`
fi
if test ! -s "$TBALL"; then
    echo "failed to fetch tarball; nothing to install" 1>&2
    exit 1
fi

NEW_DCC=`zcat "$TBALL" | tar -tf -					\
	    | sed -n -e '2q' -e 's@\(dcc-[0-9.]*\)/.*@\1@p'`
if test -z "$NEW_DCC"; then
    echo "failed to find version in $TBALL" 1>&2
    exit 1
fi

# finished if we should only install new versions
if test -n "$CRON"; then
    exec 1>&3				# restore stdout
    if test "$NEW_DCC" = "dcc-$RELEASE"	\
	&& test -z "`find $TBALL -follow -newer $DCCDIR`"; then
	echo "new $TBALL is the same version $RELEASE; stop"
	touch $OK_MARK
	exit 0
    fi
    stop_dccd
fi

# Move the unpacked tarball into the build directory named dcc
#	while preserving any foreign files.
#   Empty the target build directory of old DCC source and generated files.
MNM=.manifest
if test -d dcc; then
    if test ! -f dcc/$MNM; then
	if test `find dcc -type f -print | wc -l` -gt 50; then
	    # deal with old versions without .manifest files
	    echo delete $DCCDIR
	    rm -rf dcc
	fi
    else
	cd dcc
	set +e
	@DCCMAKE@ purge >/dev/null 2>&1
	cd $BDIR
	rmdir dcc 2>/dev/null
	set -e
	if test -d dcc; then
	    find dcc -type f -print		\
		| sed -e '1s/./preserving &/' -e '2,$s/./	&/'
	    cd $BDIR
	fi
    fi
fi

echo "unpack $TBALL into $DCCDIR"
rm -rf $NEW_DCC
zcat $TBALL | tar -xf - 2>&1 | head

if test -n "$UNPACK"; then
    echo "will configure but *NOT* build or install $NEW_DCC"
else
    echo "will configure, build, and install $NEW_DCC"
fi
if test ! -d dcc; then
    rm -rf dcc
    mv $NEW_DCC dcc
else
    # install top directories for preserved files first
    find $NEW_DCC -name $MNM -print | sort | while read NM; do
	DIR=`expr $NM : "$NEW_DCC/\(.*\)/$MNM" || true`
	if test -z "$DIR"; then
	    DIR=.
	fi
	if test ! -d $NEW_DCC/$DIR; then
	    # we already moved this directory
	    continue
	fi
	# if we can, move the entire directory from the directory in the
	#	unpacked tarball to the corresponding build directory
	if test ! -d $DCCDIR/$DIR; then
	    # first remove a possible stray file with the target name
	    rm -f $DCCDIR/$DIR || true
	    mv $NEW_DCC/$DIR $DCCDIR/$DIR
	    continue
	fi
	# otherwise move individual files from the unpacked tarball directory
	cd $NEW_DCC/$DIR
	mv `cat $MNM` $DCCDIR/$DIR
	cd $BDIR
	done
    rm -rf $NEW_DCC
fi

cd $DCCDIR

# turn off FreeBSD blather about object directories
if test -z "$MAKEOBJDIRPREFIX"; then
    MAKEOBJDIRPREFIX=; export MAKEOBJDIRPREFIX
fi

if test -n "$ENV_SET"; then
    echo "+ $ENV_SET"
fi
echo "+ ./configure @UPDATEDCC_PARMS@ $CONFQUIET $CONFS"
./configure @UPDATEDCC_PARMS@ $CONFQUIET $CONFS

if test -n "$UNPACK"; then
    exit
fi
trap "trap $TRAPS; echo 'stopping; please wait'; \
    $DCC_RESTART; @DCCMAKE@ purge >/dev/null 2>&1" $TRAPS

@DCCMAKE@ $MAKE_TGT							\
    | sed -e "$MQUIET/^[a-z0-9_]*.c: In function .*/d"			\
	-e "$MQUIET/ warning: .%.. yields only last 2 digits of year/d"	\
	-e "$MQUIET/^make.*is up to date/d"				\
	-e "$MQUIET/^make.*Nothing to be done for/d"			\
	-e "$MQUIET/^[a-z\/]*cc/s/-W[-a-z]*//g"				\
	-e "$MQUIET/^[a-z\/]*cc/s@-m[0-9a-z=]*@@g"			\
	-e "$MQUIET/^[a-z\/]*cc/s@-D_FILE_OFFSET_BITS=64@@g"		\
	-e "$MQUIET/^[a-z\/]*cc/s@-D_LARGEFILE_SOURCE@@g"		\
	-e "$MQUIET/^[a-z\/]*cc/s@-D_REENTRANT@@g"			\
	-e "$MQUIET/^[a-z\/]*cc/s@-I[-a-zA-Z0-9/.]*@@g"			\
	-e "$MQUIET/^[a-z\/]*cc/s@-I[-a-zA-Z0-9/.]*@@g"			\
	-e "$MQUIET/^[a-z\/]*cc/s@-fno-strict-aliasing@@g"		\
	-e "$MQUIET/^[a-z\/]*cc/s@-pipe@@g"				\
	-e "$MQUIET/^building static .* library/d"			\
	-e "$MQUIET/^ranlib/d"						\
	-e "$MQUIET/^rm -f \.depend/d"					\
	-e "$MQUIET/^mkdep/d"						\
	-e "$MQUIET/^echo .* >> \.depend/d"				\
	-e "$MQUIET/^g*cc/s@[   ]\{1,\}@ @g"

touch $OK_MARK

# finally start the new version and switch back to the local server
trap $TRAPS
$DCC_RESTART
@bindir@/cdcc -q rtt >/dev/null 2>&1
