#!/bin/sh

# options, set by the argument parser
q_opt=0
a_opt=0
s_opt=0
c_opt=''
e_opt=0
p_opt=''
R='/'

# Configuration settings
ROOTDEV=/dev/store/root
BOOTDEV=/dev/mmcblk0p2
ROOTFILE=/md5sums
ROOTFILEGZ=$ROOTFILE.gz
IPKG_DIR=/usr/lib/ipkg/info
IPKGSUMSGLOB=$IPKG_DIR/*.md5sums
QUIK_CACHE=/var/lib/misc/integcheck.quik

# Exceptions which need to be tracked so that we can actually determine success or failure

# Specifics:
#  /var/* is present in the rootfs and some packages, despite being a mounted filesystem
#  /media/internal/* is present in the rootfs and some packages, despite being a mounted filesystem
#  /usr/share/omdadm/* is created by flash tool
#  /etc/ld.so.cache is changed from a symlink to a file during OTA update (which runs ldconfig)
#  /etc/modules* are modified when update-modules is run, presumably during an OTA update
#  /etc/{passwd,group,version} are created by OE build, and are not in any rootfs
#  /etc/ipkg/arch.conf is created by OE build, and is not in any rootfs
#  /usr/lib/ipkg/* is not tracked by any ipkg, and is excluded in code
#  /lib/modules/*/modules.* are present in rootfs md5sums, created by OE build, are not present in any ipkgs, but may
#                           be modified by a depmod after a subsequent OTA install. As such, the root md5sums should not
#                           be checked.
#  /.reboot is written during OTA update by modules which require a reboot after installation
#  /etc/.rootfs_RW is written by rootfs_open to flag that the root should be kept writable
#  /etc/.configured is written during boot if the root is R/W
#  /etc/ppp/connect-errors is a log file written by ppp, only if the root is R/W, probably most commonly during boot.
#
# Ideally, we should have as few exceptions as possible, so these aren't avenues for surprise corruption.

# -s causes these following 'EXTRA_FILES' to _not_ be ignored

# Files which may be present, yet not listed in rootfs or ipkg md5sums, 
# due to being created by the flash tool or by OTA update processing
ROOTFS_EXTRA_FILES="./usr/share/omadm/internal ./usr/share/omadm/beta ./usr/share/omadm/carrier ./usr/share/omadm/production ./usr/share/omadm/none \
./etc/ld.so.cache ./usr/bin/PmUpdater.save ./usr/lib/libipkg.so.0.0.0.save ./usr/update-save-lib/* ./.reboot ./etc/.rootfs_RW \
./etc/modules ./etc/modules.conf ./etc/modules.conf.old ./lib/modules/*/modules.* ./etc/ppp/connect-errors ./etc/.configured \
./boot/lib ./boot/usr ./boot/bin ./boot/sbin ./etc/fstab \
./etc/fs.d/* ./usr/share/omadm/sdk "

# Files which may be present, yet not listed in any ipkg's md5sums,
# due to being added to the rootfs by the OE build process.
IPKG_ROOTFS_EXTRA_FILES="/etc/passwd\|/etc/group\|/etc/version\|/etc/ipkg/arch.conf"

# Files which may be listed in rootfs md5sums, but should be ignored (basically contents of directories which are being
# overlaid with filesystems). Ideally, these shouldn't exist.
IGNORE_ROOT="/dev\|/media/internal\|/var\|/lib/modules/.*/modules\..*"

# Files which may be listed in ipkg md5sums, but should be ignored. These are for contents of directories which are being
# overlaid with filesystems, and for conflicting files. Ideally, these shouldn't exist.
IGNORE_IPKG="/dev\|/media/internal\|/var"


#TODO: use unique temp files, and reprocess 'FAILED' results into 'MISSING', as appropriate.

usage()
{
	echo "$0: [-q] [-a] [-s] [-e] [-r <path>] [-c <file>] [-p <pkgname> [-p <pkgname>]...] fsck|root|ipkg"
	echo ""
	echo "  -q     Doesn't produce normal output, just status code"
	echo "  -a     Do not report on additional files which aren't matched in md5sums"
	echo "  -s     Report on system files which are known to change as part of a normal install"
	echo "  -e     Treat missing root md5sums file as error (normally this will be silently ignored)"
	echo "  -r     Operate with given path as root of filesystem to verify (instead of /)"
	echo "  -c     Use given md5sums file to check againt the root. Use a true absolute path."
	echo "  -p     Verify existance of installed package (may be used more than once) during ipkg check"
	echo ""
	echo " fsck    Runs fsck on root, if possible"
	echo " root    Uses $ROOTFILE or $ROOTFILEGZ to verify original rootfs"
	echo " ipkg    Uses /usr/lib/ipkg/info/*.md5sums to verify all installed packages"
	echo " ipkg-p  Uses /usr/lib/ipkg/info/*.md5sums to verify only packages specified with -p"
	echo " quik    Quick sanity check"
	exit 1
}

build_root()
{	
	if [ "x$c_opt" != "x" ] ; then
		root="$c_opt"
	else
		if [ "x$R" = "/" ] ; then
			root="$ROOTFILE"
		else
			root="$R$ROOTFILE"
		fi
	fi
	
	if [ ! -f "$root" -a -f "$root.gz" ] ; then
		root="$root.gz"
	fi
	
	case "$root" in
	*.gz)	gunzip < $root > /tmp/md5sums.decompress
			root=/tmp/md5sums.decompress
			;;
	esac
	
	if [ ! -f "$root" ] ; then
		echo "ERROR: Cannot locate root md5sum file $root"
		echo ""
		if [ "$e_opt" = "0" ] ; then
			echo "Ignoring this error."
			exit 0
		else
			usage     # and exit with error
		fi
	fi
}

do_check_root()
{
	build_root
	
	# Need to be in root for relative paths to work, but also need to apply $R to some absolute paths
	cd $R
	
	# Filter out files in packages 
	grep -h -v ' \*\.\('$IGNORE_ROOT'\)/' $root > /tmp/root.md5sums
	# Construct sorted unique file list out of md5 data
	grep -h -o '\./.*' /tmp/root.md5sums | sort | uniq > /tmp/root.md5files
	# Construct sorted unique file list from real file-system
	find . -xdev -type f | grep -h -v '^\.\('$IGNORE_ROOT'\)' | sort > /tmp/root.fsfiles 

	# If any of the extra files are present, add them to the md5 file list, so they don't show up as 'added' files.
	if [ "$s_opt" = "0" ] ; then
		for a in $ROOTFS_EXTRA_FILES .$ROOTFILE .$ROOTFILEGZ; do
			if [ -f "$a" ] ; then
				echo "$a" >> /tmp/root.md5files
			fi
		done
	fi
	
	# Check for corrupt or missing files
	md5sum -c /tmp/root.md5sums > /tmp/root.md5report 2> /dev/null
	MD5_STATUS=$?
	
	if [ "$q_opt" = "0" ] ; then
		grep -v ': OK$' /tmp/root.md5report
	fi
	
	if [ "$a_opt" = "0" ] ; then
		# Check just for additional files, which are only in file-system, not the md5 list
		sort /tmp/root.md5files /tmp/root.md5files /tmp/root.fsfiles | uniq -u | sed 's/$/: ADDED/' > /tmp/root.addedreport
		
		if [ -s /tmp/root.addedreport ]; then
			let MD5_STATUS=$MD5_STATUS+16
		fi

		if [ "$q_opt" = "0" ] ; then
			cat /tmp/root.addedreport
		fi
	fi
	
	rm -f /tmp/md5sums.decompress /tmp/root.md5sums /tmp/root.md5files /tmp/root.fsfiles /tmp/root.addedreport /tmp/root.md5report
	
	if [ "$q_opt" = "0" ] ; then
		if [ "$MD5_STATUS" = "0" ] ; then
			echo "$0 ROOT VERIFICATION SUCCEEDED"
		else
			echo "$0 ROOT VERIFICATION FAILED, CODE $MD5_STATUS"
		fi
	fi
	
	exit "$MD5_STATUS"
}

do_check_ipkg()
{
	build_root

	if [ ! -d "$R$IPKG_DIR" ] ; then
		echo "Cannot locate ipkg dir $R$IPKG_DIR"
		echo ""
		usage
	fi
	
	# Need to be in root for relative paths to work, but also need to apply $R to some absolute paths
	cd $R
	
	# Collect md5sums list for all packages, filtering out files which we want to ignore
	rm -f /tmp/ipkg.md5sums
	# todo: might break with large argument list -- switch to for?
	cat $R$IPKGSUMSGLOB | grep -h -v ' \*\.\('$IGNORE_IPKG'\)'  >> /tmp/ipkg.md5sums
	# Construct sorted unique file list out of md5 data
	grep -h -o '\./.*' /tmp/ipkg.md5sums | sort | uniq > /tmp/ipkg.md5files
	# Construct sorted unique file list from real file-system, filtering out ipkg data itself
	find . -xdev -type f | grep -v '^\.\(/usr/lib/ipkg\|'$IGNORE_IPKG'\)' | sort > /tmp/ipkg.fsfiles 

	# If any of the extra files are present, add them to the md5 file list, so they don't show up as 'added' files.
	if [ "$s_opt" = "0" ] ; then
		for a in $ROOTFS_EXTRA_FILES .$ROOTFILE .$ROOTFILEGZ; do
			if [ -f "$a" ] ; then
				echo "$a" >> /tmp/ipkg.md5files
			fi
		done
	
		# A few files get put into the rootfs and don't go into packages. Borrow those lines from the root md5sum list
		# so that they will actually be checked. This assumes they are not modified in newer ipkgs.
		
		if [ -f "$root" ] ; then
			grep ' \*\.\('$IPKG_ROOTFS_EXTRA_FILES'\)' $root >> /tmp/ipkg.md5sums
			grep ' \*\.\('$IPKG_ROOTFS_EXTRA_FILES'\)' $root | sed 's/^.* \*//' >> /tmp/ipkg.md5files
		fi
	fi
		
	# Check for corrupt or missing files
	md5sum -c /tmp/ipkg.md5sums > /tmp/ipkg.md5report 2> /dev/null
	MD5_STATUS=$?
	
	if [ "$q_opt" = "0" ] ; then
		grep -v ': OK$' /tmp/ipkg.md5report
	fi
	
	if [ "$a_opt" = "0" ] ; then
		# Check for additional files, which are only in file-system, not the md5 list
		sort /tmp/ipkg.md5files /tmp/ipkg.md5files /tmp/ipkg.fsfiles | uniq -u | sed 's/$/: ADDED/' > /tmp/ipkg.addedreport

		if [ -s /tmp/ipkg.addedreport ]; then
			let MD5_STATUS=$MD5_STATUS+16
		fi
		if [ "$q_opt" = "0" ] ; then
			cat /tmp/ipkg.addedreport
		fi
	fi
	
	if [ "x$p_opt" != "x" ] ; then
		# Check for presence of specific ipkgs named by caller. The previous tests make sure that
		# all ipkgs which are present are not corrupt, but that won't catch ipkgs which never got
		# installed, or which are so badly damaged that they don't appear to be installed.
		echo -n "" > /tmp/ipkg.ireport
		for i in $p_opt ; do
			if [ ! -f "$R/usr/lib/ipkg/info/$i.md5sums" ] ; then
				echo  "$i: MISSING PACKAGE" >> /tmp/ipkg.ireport
			fi
		done
		if [ -s /tmp/ipkg.ireport ] ; then
			let MD5_STATUS=$MD5_STATUS+32
		fi
		if [ "$q_opt" = "0" ] ; then
			cat /tmp/ipkg.ireport
		fi
	fi
	
	rm -f /tmp/md5sums.decompress /tmp/ipkg.md5sums /tmp/ipkg.md5files /tmp/ipkg.fsfiles /tmp/ipkg.addedreport /tmp/ipkg.ireport /tmp/ipkg.md5report

	if [ "$q_opt" = "0" ] ; then
		if [ "$MD5_STATUS" = "0" ] ; then
			echo "$0 IPKG VERIFICATION SUCCEEDED"
		else
			echo "$0 IPKG VERIFICATION FAILED, CODE $MD5_STATUS"
		fi
	fi
	
	exit "$MD5_STATUS"
}

do_check_quik()
{
	cd $R

	cat ./etc/palm-build-info
	cat /proc/mounts
	tune2fs -l $BOOTDEV > /tmp/quik.tune2fs
	tune2fs -l $ROOTDEV >> /tmp/quik.tune2fs
	grep 'Last write time:' < /tmp/quik.tune2fs > /tmp/quik.opentime
	
	cat /tmp/quik.tune2fs
	
	if [ -f ${QUIK_CACHE}.opentime -a -f ${QUIK_CACHE}.cache ] &&
	   cmp -s ${QUIK_CACHE}.opentime /tmp/quik.opentime ; then
	  cat ${QUIK_CACHE}.cache
	else

	  cat ./etc/ld.so.conf | sed 's/^\//.\//' > /tmp/quik.dirlist
	  echo $PATH | sed 's/:/\n/g' | sed 's/^\//.\//' >> /tmp/quik.dirlist
	  echo $PATH | sed 's/:/\n/g' | sed 's/^\//.\/boot\//' >> /tmp/quik.dirlist
  	  for a in ./etc/*.d ./boot ; do echo $a >> /tmp/quik.dirlist ; done

	  find `sort /tmp/quik.dirlist` -type f -maxdepth 1 -print0 2> /dev/null | xargs -0 nice md5sum > ${QUIK_CACHE}.cache
	  find `sort /tmp/quik.dirlist` -type l -maxdepth 1 -print0 2> /dev/null | xargs -0 sh -c 'for a in $@; do echo "$a -> "`readlink $a`; done' sh >> ${QUIK_CACHE}.cache
	  cat /tmp/quik.opentime > ${QUIK_CACHE}.opentime
	  cat ${QUIK_CACHE}.cache
	fi
	
	rm -f /tmp/quik.dirlist /tmp/quik.tune2fs /tmp/quik.opentime

	exit 0
}

do_check_ipkg_p()
{
	build_root

	if [ ! -d "$R$IPKG_DIR" ] ; then
		echo "Cannot locate ipkg dir $R$IPKG_DIR"
		echo ""
		usage
	fi
	
	if [ "x$p_opt" = "x" ] ; then
		echo "No packages specified. -p option must be used to provide package name to verify."
		echo ""
		usage
	fi
	
	# Need to be in root for relative paths to work, but also need to apply $R to some absolute paths
	cd $R
	
	# Collect md5sums list for specified packages, filtering out files which we want to ignore
	> /tmp/ipkgp.md5sums
	> /tmp/ipkgp.ireport

	for i in $p_opt ; do
		n="$R/usr/lib/ipkg/info/$i.md5sums"
		if [ ! -f $n ] ; then
			echo  "$i: MISSING PACKAGE" >> /tmp/ipkgp.ireport
		else
			cat $n | grep -h -v ' \*\.\('$IGNORE_IPKG'\)'  >> /tmp/ipkgp.md5sums
		fi
	done
	
	grep -h -o '\./.*' /tmp/ipkgp.md5sums | sort | uniq > /tmp/ipkgp.md5files

	md5sum -c /tmp/ipkgp.md5sums > /tmp/ipkgp.md5report 2> /dev/null
	MD5_STATUS=$?
	if [ -s /tmp/ipkgp.ireport ] ; then
		let MD5_STATUS=$MD5_STATUS+32
	fi
	
	if [ "$q_opt" = "0" ] ; then
		grep -v ': OK$' /tmp/ipkgp.md5report
		cat /tmp/ipkgp.ireport
	fi
	
	rm -f /tmp/md5sums.decompress /tmp/ipkgp.md5sums /tmp/ipkgp.md5files /tmp/ipkgp.ireport /tmp/ipkgp.md5report

	if [ "$q_opt" = "0" ] ; then
		if [ "$MD5_STATUS" = "0" ] ; then
			echo "$0 IPKG SPECIFIC PACKAGE VERIFICATION SUCCEEDED"
		else
			echo "$0 IPKG SPECIFIC PACKAGE VERIFICATION FAILED, CODE $MD5_STATUS"
		fi
	fi
	
	exit "$MD5_STATUS"
}

do_check_disk()
{
	if [ "x$R" != "x/" ] ; then
		# If we've been given a cross-root diretory, attempt to locate appropriate device node
		stripr=`echo "$R" | sed 's/\/\+$//g'`
		otherdev=`grep " $stripr " /proc/mounts | cut -d' ' -f1 | tail -1`
		if [ "x$otherdev" != "x" ] ; then
			ROOTDEV=$otherdev
		else
			echo "Cannot locate appropriate root device node for path $R"
			echo ""
			usage
		fi
	fi
	
	if [ ! -b "$ROOTDEV" ] ; then
		echo "Cannot find root device node $ROOTDEV"
		echo ""
		usage
	fi
	
	if [ "$q_opt" = "0" ] ; then
		fsck -n -f $ROOTDEV 2> /dev/null
	else
		fsck -n -f $ROOTDEV 2> /dev/null > /dev/null
	fi
	
	FSCK_STATUS=$?
	
	if [ "$q_opt" = "0" ] ; then
		if [ "$FSCK_STATUS" = "0" ] ; then
			echo "$0 FSCK SUCCEEDED"
		else
			echo "$0 FSCK FAILED, CODE $FSCK_STATUS"
		fi
	fi
	
	exit "$FSCK_STATUS"
}

while true; do
case "$1" in
-q)	q_opt=1
	shift
	;;
-a)	a_opt=1
	shift
	;;
-s)	s_opt=1
	shift
	;;
-e)	e_opt=1
	shift
	;;
-r)	R="$2"
	shift 2
	;;
-c)	c_opt="$2"
	shift 2
	;;
-p)	p_opt="$p_opt $2"
	shift 2
	;;
root)	do_check_root
	;;
ipkg)	do_check_ipkg
	;;
ipkg-p)	do_check_ipkg_p
	;;
quik)	do_check_quik
	;;
fsck)	do_check_disk
	;;
*)	usage
	;;
esac
done
