#!/bin/bash

###########################################################################
#
#	Shell program to compare and verify cd-rom media.
#
#	Copyright 1998-2002, William Shotts, Jr.
#	<bshotts@users.sourceforge.net>
#
#	This program is free software; you can redistribute it and/or
#	modify it under the terms of the GNU General Public License as
#	published by the Free Software Foundation; either version 2 of the
#	License, or (at your option) any later version. 
#
#	This program is distributed in the hope that it will be useful, but
#	WITHOUT ANY WARRANTY; without even the implied warranty of
#	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
#	General Public License for more details. 
#
#	This software is part of the LinuxCommand.org project, a site for
#	Linux education and advocacy devoted to helping users of legacy
#	operating systems migrate into the future.
#
#	You may contact the LinuxCommand.org project at:
#
#		http://www.linuxcommand.org
#
#	Description:
#
#	This program is used to compare 2 cd-roms.  It can compare based on a
#	comparison of the entire cd (every block of data), by comparing the
#	name, size and modification date of each file, or by calculating
#	and comparing the checksum of each file.  Note that this program
#	compares cd-roms, not audio cds.
#
#	For the file comparison methods, the results are presented in three
#	lists, first a list of files that were found on both the master
#	and the copy cds, then a list of files found only on the master and
#	a list of those files found only on the copy.  If two cds are 
#	identical, all the files will appear on the first and no files will
#	appear on the second and third lists.
#
#	Note the constants CDROM and CDMOUNT which may need to be changed
#	to support your particular system.
#
#	Usage:
#
#		compare_cds [ -h | --help ] | [ -r | -d | -c ]
#
#	Options:
#
#		-h, --help	Display this help message and exit.
#		-r		(Raw) caclulate checksum for entire cd-rom
#		-d		(Directory) compare file name, dates and size.
#		-c		(Checksum) Compare checksums for each file.
#
#
#	Revisions:
#
#	04/17/1998	File created
#	02/01/1999	Changed compare_file_lists to save results to
#			./compare_cds.txt - WS
#	02/02/2000	Program modernized and extended to support file
#			names with embedded spaces - WS
#	03/01/2000	Minor cosmetic changes - WS
#	04/03/2000	Made viewing of file listing optional - WS
#	04/24/2001	Improved mount detection method - WS
#	02/17/2002	Cosmetic updates, improvements to whole disk
#			comparisons, temp file security fixes - WS
#
#	$Id: compare_cds,v 1.3 2002/02/18 14:18:38 bshotts Exp $
#
###########################################################################


###########################################################################
#	Constants
###########################################################################

PROGNAME=$(basename $0)

CDROM=/dev/cdrom		# CD device - Adjust this for your system 
CDMOUNT=/mnt/cdrom		# Mount point - Adjust this for your system

if [ -d ~/tmp ]; then
	TEMPDIR=~/tmp
else
	TEMPDIR=/tmp
fi
MASTER_SUMFILE=${TEMPDIR}/${PROGNAME}.master.$$.$RANDOM
COPY_SUMFILE=${TEMPDIR}/${PROGNAME}.copy.$$.$RANDOM
MASTER=1
COPY=2



###########################################################################
#	Functions
###########################################################################


function is_cd_mounted
{
	#####
	#	Determines if cd is mounted
	#	Arguments:
	#		none
	#####

	local mnt_pt

	# Look at the third field in the output of mount and see if it
	# exactly equals the cd-rom mount point

	mnt_pt=$(mount | awk -v cdmount=$CDMOUNT '$3 == cdmount { print $3 } ')

	if [ "$mnt_pt" != "" ]; then
		return 0
	else
		return 1
	fi

}	# end of is_cd_mounted


function clean_up
{

	#####	
	#	Function to remove temporary files and other housekeeping
	#	No arguments
	#####

	rm -f ${MASTER_SUMFILE}
	rm -f ${COPY_SUMFILE}
	if is_cd_mounted; then
		umount $CDMOUNT
	fi
}


function graceful_exit
{
	#####
	#	Function called for a graceful exit
	#	No arguments
	#####

	clean_up
	exit
}


function error_exit 
{
	#####	
	# 	Function for exit due to fatal program error
	# 	Accepts 1 argument
	#		string containing descriptive error message
	#####

	local err_msg
	
	err_msg="${PROGNAME}: ${1}"
	echo ${err_msg} >&2
	clean_up
	exit 1
}


function term_exit
{
	#####
	#	Function to perform exit if termination signal is trapped
	#	No arguments
	#####

	echo "${PROGNAME}: Terminated"
	clean_up
	exit
}


function int_exit
{
	#####
	#	Function to perform exit if interrupt signal is trapped
	#	No arguments
	#####

	echo "${PROGNAME}: Aborted by user"
	clean_up
	exit
}


function usage
{
	#####
	#	Function to display usage message (does not exit)
	#	No arguments
	#####

	echo "Usage: ${PROGNAME} [ -h | --help ] | [ -r | -d | -c ]"

}	


function helptext
{
	#####
	#	Function to display help message for program
	#	No arguments
	#####
	
	local tab=$(echo -en "\t\t")
		
	cat <<- -EOF-
	
	This is a program to compare and verify cd-rom media.
	
	$(usage)
	
	Options:
	
	-h, --help	Display this help message and exit.
	-r		(Raw) caclulate checksum for entire cd-rom
	-d		(Directory) compare file name, dates and size.
	-c		(Checksum) Compare checksums for each file.

	-EOF-
}	


function mount_cd
{
	#####
	#	Mounts the cd-rom drive
	#	Arguments:
	#		none
	#####

	if ! is_cd_mounted ; then
		mount ${CDMOUNT} || error_exit "Cannot mount ${CDMOUNT}"
	fi
	
}	# end of mount_cd


function unmount_cd
{
	#####
	#	Unmounts the cd-rom drive if it is mounted
	#	Arguments:
	#		none
	#####

	if is_cd_mounted ; then
		cd
		umount ${CDMOUNT} || error_exit "Cannot unmount ${CDMOUNT}"
	fi
	
}	# end of unmount_cd


function write_file_list
{
	#####
	#	Writes a file containing a list of the files on a cd
	#	Arguments:
	#		1	flag indicating if cd is master or copy (required)
	#####

	# Fatal error if required arguments are missing

	if [ "$1" = "" ]; then 
		error_exit "write_file_list: missing argument 1"
	fi

	case $1 in
		${MASTER} )	checksum_list=${MASTER_SUMFILE}
				disk_name="MASTER"
				;;
		${COPY} )	checksum_list=${COPY_SUMFILE}
				disk_name="COPY";;
	esac

	echo "Reading ${disk_name} CD and constructing list of files..."
	mount_cd
	
	current_dir=$(pwd)
	cd ${CDMOUNT}

	find . -type f -printf "%-40p%-26t%s\n" | sort > ${checksum_list}
	
	cd ${current_dir}
	
	unmount_cd

}	# end of write_file_list


function write_checksum_list
{
	#####
	#	Writes a file containing a list of files and their checksums
	#	Arguments:
	#		1	flag indicating if cd is a master or a copy (required)
	#####

	# Fatal error if required arguments are missing

	if [ "$1" = "" ]; then 
		error_exit "write_checksum_list: missing argument 1"
	fi

	case $1 in
		${MASTER} )	checksum_list=${MASTER_SUMFILE}
				disk_name="MASTER"
				;;
		${COPY} )	checksum_list=${COPY_SUMFILE}
				disk_name="COPY"
				;;
	esac

	echo "Reading ${disk_name} CD and constructing list of file checksums..."
	mount_cd
	
	current_dir=$(pwd)
	cd ${CDMOUNT}

	find . -type f | awk '
	
		{	
			printf("%-60s %-5s", toupper($0), " ")
			system("sum " "\"" $0 "\"")
		}
	
	' | sort > ${checksum_list}
	
	cd ${current_dir}

	unmount_cd

}	# end of write_checksum_list


function read_cd_checksum
{
	#####
	#	Calculates checksum for entire cd
	#	Arguments:
	#		1	Flag indicating if cd is master or a copy (required)
	#####

	# Fatal error if required arguments are missing

	if [ "$1" = "" ]; then 
		error_exit "read_cd_checksum: missing argument 1"
	fi

	case $1 in
		${MASTER} )	echo "Reading MASTER CD and calculating checksum..."
				
				mount_cd
				master_block_count=$(get_block_count)
				unmount_cd
				
				master_checksum=$(checksum_cd ${master_block_count})
				;;

		${COPY} )	echo "Reading COPY CD and calculating checksum..."
				
				mount_cd
				copy_block_count=$(get_block_count)
				unmount_cd
				
				if [ "${master_block_count}" = "${copy_block_count}" ]
				then
					copy_checksum=$(checksum_cd ${copy_block_count})
				fi
				;;
	esac

}	# end of read_cd_checksum


function compare_file_lists
{
	#####
	#	Compares two files and describes differences
	#	Arguments:
	#		none
	#####

	local foo

	{
		echo -e "The following files were found on BOTH CDs:\n"
		comm -12 ${MASTER_SUMFILE} ${COPY_SUMFILE}
		echo -e "\n\nThe following files were found *only* on the MASTER CD:\n"
		comm -23 ${MASTER_SUMFILE} ${COPY_SUMFILE}
		echo -e "\n\nThe following files were found *only* on the COPY CD:\n"
		comm -13 ${MASTER_SUMFILE} ${COPY_SUMFILE}
	
	} > compare_cds.txt
	if [ "$(comm -23 ${MASTER_SUMFILE} ${COPY_SUMFILE})" = "" -a "$(comm -13 ${MASTER_SUMFILE} ${COPY_SUMFILE})" = "" ]; then
		echo -e "\n\aFile lists are identical."
	else
		echo -e "\n\aFile lists are *NOT* identical!"
	fi
	echo -en "View details [y/n]? > "
	read foo
	if [ "$foo" = "y" ]; then
		less compare_cds.txt
	fi

}	# end of compare_file_lists


function compare_cd_checksums
{
	#####
	#	Compares the checksums of two cds
	#	Arguments:
	#		none
	#####

	if [ "${master_block_count}" = "${copy_block_count}" ]
	then
		echo -e "Checksums:\n\tMASTER = ${master_checksum}\n\tCOPY =   ${copy_checksum}"
		if [ "${master_checksum}" = "${copy_checksum}" ]
		then
			echo "CDs are identical!"
		else
			echo "CDs are different!"
		fi
	else
		echo -e "Block Counts:\n\tMASTER = ${master_block_count}\n\tCOPY =   ${copy_block_count}"
		echo "CDs have different block counts!"
		echo "CDs are different!"
	fi

}	# end of compare_cd_checksums


function get_block_count
{
	#####
	#	Determines the number of data blocks on a cd
	#	Arguments:
	#		none
	#####

	df -k | awk -v cd_mount=$CDMOUNT ' $6 == cd_mount { print $2 }'

}	# end of get_block_count


function checksum_cd
{
	#####
	#	Calculate the checksum of a cd
	#	Arguments:
	#		Number of data blocks on cd (Required)
	#####

	# Fatal error if required arguments are missing

	if [ "$1" = "" ]; then 
		error_exit "checksum_cd: missing argument 1"
	fi

	dd if=${CDROM} count=$1 bs=1024 | md5sum | awk '{ print $1 }'

}	# end of checksum_cd


###########################################################################
#	Program starts here
###########################################################################

# Trap TERM, HUP, and INT signals and properly exit

trap term_exit TERM HUP
trap int_exit INT

# Process command line arguments

if [ $# -ne "1" ]; then
	helptext
	exit 1
fi

if [ "$1" = "--help" ]; then
	helptext
	graceful_exit
fi

# Process arguments

while getopts ":hrdc" opt; do
	case $opt in
		r )	mode_string="Performing byte comparison...\n"
			read_routine="read_cd_checksum"
			compare_routine="compare_cd_checksums"
			;;
			
		d )	mode_string="Performing directory comparison...\n"
			read_routine="write_file_list"
			compare_routine="compare_file_lists"
			;;
			
		c )	mode_string="Performing file checksum comparison...\n"
			read_routine="write_checksum_list"
			compare_routine="compare_file_lists"
			;;

		h )	helptext
			graceful_exit
			;;
			
		* )	usage
			exit 1
	esac
done

echo -e ${mode_string}
echo -en "\n\aInsert MASTER CD [Enter or Ctrl-C to Quit]"
read reply
sleep 5		# give drive tile to "settle"

${read_routine} ${MASTER}

echo -en "\n\aInsert COPY CD [Enter or Ctrl-C to Quit]"
read reply
sleep 5		# give drive tile to "settle"

${read_routine} ${COPY}

${compare_routine}

graceful_exit


syntax highlighted by Code2HTML, v. 0.9.1