#!/sbin/sh

###############################################################################
#
#	Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
#	Use is subject to license terms.
#	Copyright 1992-95 AT&T Global Information Solutions
#
# ident	"@(#)lucompare.sh	5.11	08/06/25 SMI"
#
# USAGE:                   lucompare  [ -i <in_file> | -t ]
#                                          [ -o <out_file> ] "be_name"
#					   [ -C <create_compare_file> ]
# FUNCTION:                compare contents of current BE with "be_name"
# INPUT:                   target "be_name", the be_name should be
#                          enclosed within quotes.
# OUTPUT:                  list of files which differ, along with the
#                          reason.
# DEV:                     RAMAN
#
###############################################################################

[ -s /etc/default/lu ] && . /etc/default/lu

# Dot in lulib

if [ ! -f "$LUBIN/lulib" ]; then
	error_exit "`gettext 'lulib is not present'`"
fi

. $LUBIN/lulib

NONGLOBAL_ZONES=0
# run compare on all filesystems for each zone
compare_filesystems()
{
	zname=$1

	# Check to see if we are processing a BE with non-global zones
	lulib_zone_check /
	if [ "$?" != 0 ]
	then
		NONGLOBAL_ZONES=1
	fi	

        # Determine the file systems to be compared on ABE.
	compul_list=`${LUETCBIN}/ludo filter_shared_and_swap $ABE_ICF $CBE_ICF \
	    | /bin/nawk -v zn="$zname" -F: '{if($6 == "") printf("%s ", $2)} \
	    {if($6 == zn) printf("%s:%s ", $6, $2)}'`
	[ $? != 0 ] && comp_exit `gettext "Could not determine the file systems of the active boot environment:"`" \"$CURR_BE\"" 2

        # For each of the file systems determined for comparison do the
        # the comparison.
        for filesys in $compul_list
        do
		echo `gettext "Comparing"`" $filesys ...\n"

                # If -t option is specified, compare only text files, else
                # compare all files irrespective of the nature of the file.

                if [ "$T_FLG" -eq 1 ]
                then

                # Only text files need to be compared. We do a file(1) command
                # on each file and grep only for text files.
                	if [ "$NONGLOBAL_ZONES" -eq 0 ]
                        then
                                $LUBIN/compare -p $filesys -m $dest -T -S \
                                -M $OOPT
                        else
                                $LUBIN/compare -p $filesys -m $dest -T -S \
                                -M $OOPT -z $zname
                        fi
		else
                        # Compare all files irrespective of the nature
                        # of the file.
                	if [ "$NONGLOBAL_ZONES" -eq 0 ]
                        then
                                $LUBIN/compare -p $filesys -m $dest -S \
                                -M $OOPT
                        else
                                $LUBIN/compare -p $filesys -m $dest -S \
                                -M $OOPT -z $zname
                        fi
                fi
                [ $? = 0 ] || comp_exit `gettext "Comparison failed"` 1
		echo "\n"`gettext "Compare complete for"`" $filesys."
        done

        if [ "$C_FLG" = "1" ]
        then
                echo "\n"`gettext "Comparing current file stats with original stats for"`" $BE_NAME...\c"
                /bin/sort -u "$C_IN_FILE" > /tmp/$$.compdiff1
                echo "..\c"
                /bin/sort -u $OUT_FILE > /tmp/$$.compdiff2
                echo "..\c"
                echo "        < "`gettext "Original"`" $BE_NAME" > $OUT_FILE
                echo "        > "`gettext "Current"`" $BE_NAME" >> $OUT_FILE
echo "
File types and Field definitions:

*Block special device node (BLKSPC):
   Device name: owner: group: link count (hard links): permissions: BLKSPC:
   major/minor number:
*Character special device node (CHRSPC):
   Device name: owner: group: link count (hard links): permissions: CHRSPC:
   major/minor number:
*Directories (DIR):
   Directory name: owner: group: link count (dirs within): permissions: DIR:
*First-in-first-out file (FIFO):
   FIFO name: owner: group: link count: permissions: FIFO:
*Regular files (REGFIL):
   File name: owner: group: link count (hard links): permissions: REGFIL:
   size in bytes: file check-sum (cksum):
*Symbolic link (SYMLINK):
   link name: owner: group: link count (always 1): permissions: SYMLINK:
   size of link name:

Differences:
" >> $OUT_FILE
                /bin/diff /tmp/$$.compdiff1 /tmp/$$.compdiff2 | /bin/egrep "^>|^<" >> $OUT_FILE
                echo "\n"`gettext "Comparison is complete and saved in"`" $OUT_FILE."
        fi
}

run_comparison_on_ABE()
{
	# If -i option is specified, compare only the list of files specified
	if [ "I_FLG" -eq 1 ]
	then
		$LUPRINTF -1l "`gettext 'Processing all entries from <%s> '`" \
		    "C_IN_FILE"

		# Cleaning up entries from the input file, so that compare
		# can process them.  This will strip whitespace and double
		# slashes from lines, along with removing blank lines.
		/bin/sed -e 's/[ 	]*//g' -e 's/\/[\/]*/\//g' -e \
		    's/\/$//g' -e '/^[ 	]*$/d' $IN_FILE | $LUBIN/compare \
		    -m $dest -S -M -f - $OOPT
		[ $? = 0 ] || spec_exit 1 "`gettext 'Comparison failed'`"
	else
		echo "\n"
		$LUPRINTF -1l "`gettext 'Processing Global Zone'`"
		compare_filesystems 'global'
		status=$?
		case $status in
		1)	comp_exit $status "`gettext 'Comparison failed'`"
			;;
		2)	comp_exit $status "`gettext 'Could not determine the \
file systems of the active boot environment: %s'`" "$CURR_BE"
			;;
		esac

		for zone in `all_nonglobal_zones`; do
			echo "\n"
			$LUPRINTF -1l "`gettext '\nProcessing %s Zone'`" $zone
			compare_filesystems $zone
		        status=$?
			case $status in
			1)	comp_exit $status "`gettext 'Comparison failed'`"
				;;
			2)	comp_exit $status "`gettext \
'Could not determine the file systems of the active boot environment: %s'`" \
			    "$CURR_BE"
				;;
			esac
		done
	fi
}

TMP_RESULT_FILE="/tmp/.lucompare.results.tmp.$$"

cleanup()
{

	trap "" 1 2 3 15

	# if only the root slice is mounted umount it.

	# if $dest directory is present, it indicates that ABE slices are mounted,
	# so we unmount it. 

	if [ -d "$dest" ]
	then
		$LUBIN/luumount -f -i "$ABE_ICF" 2> /dev/null
		[ -d "$dest" ] && /bin/rmdir $dest
	fi
	unmount_mounted

	# Remove all temporary files.

	/bin/rm -f $ABE_ICF $CBE_ICF $TMP_USR $TMP_VAR $ERR_MSG_LOG

	exit 1

}

check_be_busy()
{
  # Verify that there is no COPYLOCK held by $BE_NAME
	lulib_validate_lulock
	if [ -f "$COPYLOCK" ]
	then
		. $COPYLOCK
		if [ "$CL_TARGET_BE" = "$BE_NAME" ]; then
			$LUPRINTF -Eelp2 "`gettext \
			'Cannot compare.  A COPYLOCK exists for BE <%s>'`" \
			    "$BE_NAME"
			exit 1
		fi
	fi
}
	
usage()
{
	$LUPRINTF -elp2 "`gettext 'Usage:\t%s [ -X ] \
[ -i in_file | -t ] [ -o out_file ]\n\
\t[ -C create_compare_file ] BE_name'`" $LU_PROG_NAME
	exit 1
}

unmount_mounted()
{
	for zone in $mounted_list; do
		zoneadm -z $zone unmount
	done
}

error_exit()
{
	$LUPRINTF -Eelp2 "$@"
	unmount_mounted
	/bin/rm -f $ABE_ICF $CBE_ICF $ERR_MSG_LOG
	exit 1
}

error_exit2()
{
	$LUPRINTF -Eelp2 "$@"
	unmount_mounted
        /bin/rm -f $ABE_ICF $CBE_ICF $ERR_MSG_LOG
        exit 2
}

spec_exit()
{
	exitv=$1
	shift
	$LUPRINTF -Eelp2 "$@"
	unmount_mounted
	/bin/rm -f $COPYLOCK
	exit $exitv
}

comp_exit()
{
	exitv=$1
	shift
	$LUPRINTF -Eelp2 "$@"

	# Unmount the ABE slices, as we have come across some error, and we 
	# want to exit.

	$LUBIN/luumount -f -i "$ABE_ICF" >& 2
	unmount_mounted
	/bin/rm -f $ABE_ICF $CBE_ICF $TMP_USR $TMP_VAR $ERR_MSG_LOG $COPYLOCK
	exit $exitv
}

LU_PROG_NAME=`/bin/basename $0`
T_FLG=0
I_FLG=0
O_FLG=0
C_FLG=0
OOPT=""
C_IN_FILE=""
TEMP_OUT_FILE="/tmp/temp_out.$$"
ERR_MSG_LOG=/tmp/lucompare.err

# parse the options

while [ $# -ne 0 ] ; do
  while getopts C:ti:o:X option
  do
	  case $option in 
	  C) if [ "$C_FLG" -eq 1 ]
	     then
		  usage
	     fi
		  C_FLG=1
		  C_IN_FILE=`echo $OPTARG | /bin/sed 's/ /___/g;s%/%_:_%g'`
		  [ -z "$C_IN_FILE" ] && usage
		  if [ -r "$C_IN_FILE" ]; then
			  OOPT="$OOPT -C $C_IN_FILE"
		  else
			  $LUPRINTF -Eelp2 "`gettext 'file not readable: %s'`" \
			      "$C_IN_FILE"
			  exit 1
		  fi;;
	  t) if [ "$T_FLG" -eq 1 ]
	     then
		  usage
	     fi
		  T_FLG=1;;
	  i) if [ "$I_FLG" -eq 1 ]
	     then
		  usage
	     fi
		  I_FLG=1
		  IN_FILE=$OPTARG
		  [ -z "$IN_FILE" ] && usage
		  if [ -r "$IN_FILE" ]; then
			  IOPT="-f $IN_FILE"
		  else
			  $LUPRINTF -Eelp2 "`gettext 'file not readable: %s'`" \
			      "$IN_FILE"
			  exit 1
		  fi;;
	  o) if [ "$O_FLG" -eq 1 ]
	     then
		  usage
	     fi
		  O_FLG=1
		  OUT_FILE=$OPTARG
		  [ -z "$OUT_FILE" ] && usage
		  > $OUT_FILE
		  if [ $? != 0 ] 
		  then
			  $LUPRINTF -Eelp2 "`gettext \
			      'Cannot create file: %s'`" $OUT_FILE
			  exit 1
		  fi
		  OOPT="$OOPT -o $OUT_FILE" ;;
	  X) # -X - set XML output mode.
	      lulib_set_output_format 'xml'
	      ;;
	  \?) usage ;;
	  esac
  done

  # Found either end of arguments, +option, or non-option argument; shift out
  # what has been processed so far; if a non-option argument is present
  # capture it and continue processing the command line arguments.
  shift `/bin/expr $OPTIND - 1`
  OPTIND=1
  if [ $# -ne 0 -a "$1" = '+X' ] ; then
      # +X - set TEXT output mode.
      lulib_set_output_format 'text'
      shift
  else
    break
  fi
done

# -i and -t option is not allowed.

[ "$I_FLG" -eq 1 -a "$T_FLG" -eq 1 ] && usage

# verify that only one argument viz, be_name is present

if [ $# -gt 1 ]; then
	$LUPRINTF -Eelp2 "`gettext \
	    'The boot environment name should be enclosed in single quotes.'`"
fi

[ $# -ne 1 ] && usage

BE_NAME="$1"

ABE_ICF="/tmp/abe_icf.$$"
CBE_ICF="/tmp/cbe_icf.$$"

# Check for existence and non-zero size of lutab file.

if [ ! -f $LU_LUTAB_FILE -o ! -s $LU_LUTAB_FILE ] ; then
	$LUPRINTF -Eelp2 "`gettext \
	    'No boot environments are configured on this system.'`"
	exit 1
fi

# Validate the target BE

$LUPRINTF -1lD 1 "Verifying that \"%s\" is a valid BE name..." "$BE_NAME"

lulib_validate_be "$BE_NAME"
if [ "$?" -ne "0" ] ; then
	$LUPRINTF -Eelp2 "`gettext \
	    'Unable to use the target boot environment <%s>.'`" "$BE_NAME"
	exit 1
fi

check_be_busy

# Get the current BE name

CURR_BE=`lulib_lucurr`

if [ $? != 0 ]; then
	error_exit2 "`gettext \
	    'Could not determine the name of the active boot environment'`"
fi


if [ "$C_FLG" != 1 -a "$CURR_BE" = "$BE_NAME" ]; then
	error_exit "`gettext \
	    '%s is the active boot environment; cannot compare with itself'`" \
	    "$BE_NAME"
fi

if [ "$C_FLG" = 1 -a "$O_FLG" = 0 ]; then
	error_exit "`gettext '-o option must be used with -C'`"
fi

# signal handling  for cleanup

trap "cleanup ; exit $?" 1 2 3 15

echo "CL_STATUS=\"COMPARING\"" > $COPYLOCK
echo "CL_TARGET_BE=\"$BE_NAME\"" >> $COPYLOCK

TMP_USR="/tmp/.spec_usr.$$"
TMP_VAR="/tmp/.spec_var.$$"

# Get the ABE_ICF

$LUPRINTF -1l "`gettext 'Determining the configuration of %s ...'`" "$BE_NAME"

$LUBIN/lumk_iconf "$BE_NAME" > $ABE_ICF
$LUBIN/lumk_iconf "$CURR_BE" > $CBE_ICF

if [ $? != 0 ]; then
	rm -f $COPYLOCK
	error_exit2 "`gettext 'Could not determine the ICF for %s'`" \
	    "$BE_NAME"
fi

$LUPRINTF -1l ""

# Root slice of ABE is mounted, to do the special processing for /usr and
# /var filesystems.

$LUETCBIN/ludo filter_shared_and_swap $CBE_ICF $ABE_ICF |
/bin/nawk -F: '$2 == "/" && $6 == "" { printf("%s %s\n", $3, $4) }' |
while read bdevice fstyp
do
	case "$fstyp" in
	zfs)	if [ -z "$bdevice" ]; then
		    rm -f $COPYLOCK
		    error_exit "`gettext 'Invalid ICF format'`"
		fi
		/sbin/zfs get -Ho value mountpoint "$bdevice" > /dev/null 2>&1
		if [ $? != 0 ]; then
		    rm -f $COPYLOCK
		    error_exit "`gettext 'Invalid ICF format'`"
		fi
		;;
	*)	if [ -z "$bdevice" -o ! -b $bdevice -z "$fstyp" ]; then
		    rm -f $COPYLOCK
		    error_exit "`gettext 'Invalid ICF format'`"
		fi
		;;
	esac

	real_fstyp="`lulib_fstyp $bdevice`"
	if [ $? != 0 ]; then
		rm -f $COPYLOCK
		error_exit "`gettext \
		    'Cannot determine file system type on: <%s>.'`" \
		    "$bdevice"
	fi

	if [ "$real_fstyp" != "$fstyp" ]; then
		rm -f $COPYLOCK
		error_exit "`gettext \
	'File system on %s (%s) does not match configuration file (%s)'`" \
		    "$bdevice" "$real_fstyp" "$fstyp"
	fi
done

if [ "$?" != 0 ]
then
	rm -f $COPYLOCK
	exit 1
fi

lulib_zone_check /

# Loop through the non-global zones on the system, and make sure that all
# installed zones are running or mounted.
mounted_list=`lulib_list_zones -i |
while IFS=: read zoneid zonename state path uuid junk; do
	IFS=" 	
"
	if [ $state = installed ]; then
		zoneadm -z $zonename mount
		echo $zonename
	fi
done`

# if <out_file> is specified, then output the header to the out_file,
# provided -c option is not specified.

if [ "$C_FLG" != "1" ]
then
	if [ "$O_FLG" -eq 1 ]
	then
    		echo "        < $CURR_BE" > $OUT_FILE
    		echo "        > $BE_NAME" >> $OUT_FILE
		for zone in `all_nonglobal_zones`
		do
			zlogin $zone "echo \"        < $CURR_BE\" > $OUT_FILE;\
			    echo \"        > $BE_NAME\" >> $OUT_FILE"
		done
	else
		echo "        < $CURR_BE" 
		echo "        > $BE_NAME" 
	fi
fi


# Mount the ABE.  If there are non-global zones present, then the root of
# each non-global zone within the ABE will be presented at $dest within the
# running or mounted zone on the current BE.

if [ "$C_FLG" = "1" -a "$CURR_BE" = "$BE_NAME" ]
then
	dest=/
else
	dest=`LU_OUTPUT_FORMAT=text $LUBIN/lumount -i "$ABE_ICF" \
	    2>$TMP_RESULT_FILE`
	if [ $? -ne 0 ]; then
		[ -s "$TMP_RESULT_FILE" ] && \
		    $LUPRINTF -elp2 '%R' < "$TMP_RESULT_FILE"
		$LUPRINTF -Eelp2 "`gettext \
		    'Unable to mount the boot environment <%s>.'`" "$BE_NAME"
		unmount_mounted
		/bin/rm -f "$TMP_RESULT_FILE"
		exit 1
	fi
        /bin/rm -f "$TMP_RESULT_FILE"
	lulib_zone_check $dest
fi

# Start the comparison of the files or file systems on ABE.
run_comparison_on_ABE

# Unmount the ABE slices after the comparison is complete.

if [ "$CURR_BE" != "$BE_NAME" ]
then
	$LUBIN/luumount -f -i "$ABE_ICF"
	if [ $? != 0 ]
	then
		/bin/rm -f $COPYLOCK
		error_exit2 "`gettext 'Cannot unmount boot environment: %s'`" \
		    "$BE_NAME"
	fi
fi
unmount_mounted

# Remove the temporary files.
/bin/rm -f $ABE_ICF $CBE_ICF $ERR_MSG_LOG $COPYLOCK
exit 0
