#!/bin/sh
#
# mkroottarball by jmalone@applieddata.net
#
# Copyright 2004-2006 Joshua Malone, Applied Data Systems
#
# This file is subject to the terms and conditions of the GNU General Public
# License.


### Constant vars

LIBDIR=/usr/share/adsrootbuilder

# PRUNELIST is a space-separated list of patterns that should
# be spotted and removed form the list of files for the tarball
# All regex specials need to be escaped (i.e.,  . -> \.)
PRUNELIST="var\/run var\/lib\/adsrootbuilder"

# ADDLIST is a similar list of files to always include in the
# tarball
ADDLIST="$ADSLIST /sbin/init /etc/inittab /var/spool/cron /etc/network/if-up.d /etc/network/if-down.d /etc/network/if-post-down.d"

### Other vars

# Where should we store the control files
# (This path should persists across reboots, so don't
#  use /tmp or other transient FS)
ctrlpath=/var/lib/adsrootbuilder

# Please don't change these unless you know what you're doing
STARTFILE=adsrootbuild.start
STOPFILE=adsrootbuild.stop
NOWFILE=adsrootbuild.now


# Function defs

# Include this "library" of shell functions
. $LIBDIR/adsshell.inc

printusage () {
	echo "Usage: mkroottarball [-p <controlfile path>] [ -f <file list>] {start|stop|build|scan} [<tarfile>]"
	echo ""
}

dump_filelist () {
	# dumplist <list> <filename>
	if [ -z "$2" ]; then
		errexit "Internal error in dump_filelist()"
	fi
	if ! [ -z "$3" ]; then
		errexit "Internal error 2 in dump_filelist()"
	fi
	echo "" > $2
	echo "$1" |sed -e 's/ /\n/g' > $2
}


run_start () {

	# Check for old control files and write permission
	if ! [ -w ${crtlpath} ]; then
		errexit "Cannot write control file at ${ctrlpath}"
	fi
	if [ -e ${ctrlpath}/${STARTFILE} ]; then
		echo "An old starting control file already exists!"
		read -p "Overwrite (y/n)? " yorn
		if yesorno $yorn; then
			rm ${ctrlpath}/${STARTFILE}
		else
			echo "Aborting!"
			exit
		fi
	fi
	if [ -e ${ctrlpath}/${STOPFILE} ]; then
		echo "Erasing old ending control file..."
		rm ${ctrlpath}/${STOPFILE}
		if [ -f ${ctrlpath}/${STOPFILE} ]; then
			errexit "cannot remove old ending control file"
		fi
	fi

	# Check for dependencies (helper programs)
	echo -n "Checking for necessary helper programs...   "
	if ! [ -x $(which grep) ]; then
		errexit "grep not found"
	fi
	if ! [ -x $(which tar) ]; then
		errexit "tar not found"
	fi
	if ! [ -x $(which sed) ]; then
		errexit "sed not found"
	fi
	echo "OK"

	touch ${ctrlpath}/${STARTFILE}
	if ! [ -f ${ctrlpath}/${STARTFILE} ]; then
		errexit "cannot create starting control file at ${ctrlpath}"
	fi

	echo "Sucessfully created starting control file at ${ctrlpath}"
}

run_stop () {

	# I haven't figured out how to handle an ending control file yet
	# so for now, we'll just about with an unimp message
	echo "This functionality hasn't been implemented yet - sorry"
	echo "Use mkroottarball build <tarfile> instead"
	exit -1

	if ! [ -f ${ctrlpath}/${STARTFILE} ]; then
		errexit "You must start the build process before stopping it"
	fi

	if ! [ -w ${crtlpath} ]; then
		errexit "Cannot write control file at ${ctrlpath}"
	fi
	if [ -e ${ctrlpath}/${STOPFILE} ]; then
		echo "An old ending control file already exists!"
		read -p "Overwrite (y/n)? " yorn
		if yesorno $yorn; then
			rm ${ctrlpath}/${STOPFILE}
		else
			echo "Aborting!"
			exit
		fi
	fi
	touch ${ctrlpath}/${STOPFILE}
	if ! [ -f ${ctrlpath}/${STOPFILE} ]; then
		errexit "cannot create ending control file at ${ctrlpath}"
	fi

	echo "Sucessfully created ending control file at ${ctrlpath}"
}


scan_files () {
	# First, run any exercise script.
	if [ -n "$EXERCISE_COMMAND" ]; then
		eval "$EXERCISE_COMMAND" || echo "warning: EXERCISE_COMMAND failed"
	fi

	# Make sure that all libs used by init are known.
	init >/dev/null 2>&1 || true
	
	# Avoid using busybox find.
	if [ -x "$(which find.orig)" ]; then
		FIND="$(which find.orig)"
	else
	    FIND=find
	    if [ -L $(which find) ] && \
	      readlink $(which ${FIND}) | grep -q 'busybox' &>/dev/null ; then
		echo "It looks like you only have Busybox 'find' on this system."
		echo "Unfortunately, the Busybox version of the find utility"
		echo "lacks features needed by adsrootbuilder.  Please keep the"
		echo "GNU version of find on your system as find.orig so that"
		echo "this tool can use it."
		errexit "Cannot use busybox 'find' with adsrootbuilder"
	    fi
	fi


	echo -n "Checking for control files...   "
	if ! [ -r ${ctrlpath}/${STARTFILE} ]; then
		errexit "No beginning control file present"
	fi

	# Make sure stopfile is older than start
	if [ -r ${ctrlpath}/${STOPFILE} ]; then
		filelist=$($FIND ${ctrlpath}/${STOPFILE} -type f -xtype f -anewer ${ctrlpath}/${STARTFILE} -print |grep ${STOPFILE} )
		if [ -z "${filelist}" ]; then
			errexit "Ending file is older that beginning file"
		fi
	fi

	# Make sure startfile is older that current time
	touch ${ctrlpath}/${NOWFILE}
	filelist=$($FIND ${ctrlpath}/${NOWFILE} -anewer ${ctrlpath}/${STARTFILE} -print |grep ${NOWFILE} )
	if [ -z "${filelist}" ]; then
		errexit "Start file is in the future"
	fi
	echo "OK"

	# Re-mount the root filesystem with -noatime so that
	# the scan process doesn't corrupt the access times of the
	# entire filesystem
	if ! mount -o remount,noatime /
	then
		echo "WARNING: Unable to remount the root device with 'noatime' option."
		echo "This means that the scan process will mark all files as"
		echo "accessed.  You will need to run 'adsrootbuilder --clean'"
		echo "and return to the beginning of the build process if this"
		echo "run does not complete successfully."
		echo
		echo "Press <Enter> to continue"
		read yorn
	fi
	
	# proceed to find files with atimes after startfile
	echo -n "Scanning system for used files...   "
	filelist=$($FIND / -xdev -anewer ${ctrlpath}/${STARTFILE} \( -type f -o -type l -o -type b -o -type c \) -print 2>/dev/null )
	if [ -z "${filelist}" ]; then
		errexit "something went horribly wrong with the find operation"
	fi
	echo "done"

	# Add any desired untouched files to the list
	echo "Adding pre-defined items to the file list..."
	for i in ${ADDLIST}; do
		# Make sure we don't add something that's already here
		if ! [ -z "$(echo ${filelist} | egrep "^${i}$" )" ]; then
			echo "  $i already found - not adding"
		else
			filelist="${filelist} ${i}"
		fi
	done
	echo "done"


	# Trim some known false-positives from the list
	echo -n "Pruning desired items from file list...   "

	# Add padding to both ends of the filelist to ease pruning
	# with 'sed' below
	filelist="adsbogusentry ${filelist} adsbogusentry"

	for i in ${PRUNELIST}; do
		filelist=$(echo ${filelist} | sed -e "s/ \\/[^ ]*$i / /g")
		# Make sure that the PRUNELIST didn't match everything
		if [ -z "${filelist}" ]; then
			errexit "PRUNELIST butchered the file list"
		fi
	done

	# Get rid of the bogus padding we added above
	filelist=$(echo ${filelist} | sed -e "s/adsbogusentry//g")

	echo "done"

	if ! mount -o remount,defaults /
	then
		echo "WARNING: Unable to remount the root device with access"
		echo "times enabled.  You must reboot the system before attempting"
		echo "another scanning pass with adsrootbuilder."
		echo
		echo "Press <Enter> to continue"
		read yorn
	fi

}

run_build () {

	# Check for existing tarball, etc.
	if [ -z "$1" ]; then
		errexit "Need file name for tarball"
	else
		tarfile=$1
	fi
	if [ -e ${tarfile} ]; then
		read -p "File named ${tarfile} exists - overwrite (y/n)? " yorn
		if yesorno $yorn; then
			rm ${tarfile}
			if ! [ $? ]; then
				errexit "Cannot delete old tarfile"
			fi
		else
			echo "Aborting!"
			exit
		fi
	fi

	if [ -z "$filelist" ]; then
		errexit "Cannot proceed with an empty file list"
	fi

	# Avoid using busybox tar.
	if [ -x "$(which tar.orig)" ]; then
		TAR="$(which tar.orig)"
	else
		TAR=tar
		if [ -L $(which tar) ] && \
		  readlink $(which ${TAR}) | grep -q 'busybox' &>/dev/null ; then
		    # Ack - we're trying to use busybox tar
		    echo "Warning: Attempting to use busybox tar to build images"
		    echo ""
		    echo "This really isn't a good idea and is not officially"
		    echo "supported.  Please keep the GNU tar binary around as"
		    echo "tar.orig so this tool can use it"
		    echo ""
		fi
	fi
	
	# Compile the tar archive
	echo -n "Creating tarball...   "
	$TAR --absolute-names --no-recursion -cf ${tarfile} ${filelist} 2>/dev/null
	echo "done"

	# Add device files to the tarball
	#echo -n "Adding device files to the tarball...   "
	#tar --absolute-names -rf ${tarfile} /dev
	#echo "done"

	echo -n "Cleaning up control files...   "
	rm ${ctrlpath}/${STARTFILE}
	rm ${ctrlpath}/${NOWFILE}
	if [ -e ${ctrlpath}/${STOPFILE} ]; then
		rm ${ctrlpath}/${STOPFILE}
	fi
	echo "done"

	if [ $mode == "build" ]; then
		echo "Created tarball as ${tarfile}:"
		ls -l ${tarfile}
		echo ""
	fi

}


##############################################################

# Make sure we have necessary arguments
if [ -z "$1" ]; then
	printusage
	exit
fi
if [ "$1" == '-p' ]; then
	shift
	if [ -z "$1" ]; then
		echo "WARNING: No control path specified - using default"
	else
		ctrlpath="$1"
		shift
	fi
fi

if [ "$1" == '-f' ]; then
	shift
	if [ -z "$1" ]; then
		printusage
		exit
	else
		extern_filelist=$1
		shift
	fi
fi

mode="$1"
shift

tarball=$1

case $mode in

	start)
		run_start
		;;

	stop)
		run_stop
        ;;

	build)
		if [ -z "${extern_filelist}" ]; then
			scan_files
		else
			if ! [ -f ${extern_filelist} ]; then
				errexit "Cannot find external file list"
			fi
			filelist=$(cat ${extern_filelist})
		fi
		run_build "${tarball}"
		;;

	scan)
		scan_files
		dump_filelist "$filelist" ${extern_filelist}
		;;

	*)
		printusage
		;;
esac
