#!/usr/bin/ksh -h
#
# @(#) acpatch 1.43 00/04/17 SMI
#
# Copyright (c) 1999 Sun Microsystems, Inc.  All Rights Reserved. Sun
#

[[ "$0" == /* ]] && typeset -r PRG=$0 || typeset -r PRG=$PWD/$0
typeset -r BINDIR=$(dirname $PRG)
. $BINDIR/dcdb

typeset ADD_PATCH=
typeset OPT_LISTSERVICES='false'
typeset OPT_LISTCLONES='false'
typeset OPT_LISTPATCHES='false'
typeset REMOVE_PATCH=
typeset OPT_SYNCHRONIZE='false'
typeset OPT_UPDATE='false'
typeset OPT_DEBUG=
typeset SYNCNEWCLONEAREA=
typeset BATPID=0

# Global definitions

#typeset -r AC_TAG="AC_"
typeset -r AC_TAG=""
#typeset -r SPOOLDIR="/opt/SUNWac/Patches"
typeset -r SPOOLDIR="/export/diskless/Patches"
typeset -r OSSERVICES="/export/${AC_TAG}Solaris_*"
typeset -r SERVICEUSRAREAS="/export/exec"
#typeset -r ACFILEINDEXBEFORE="acfileindexbefore"
typeset -r ACFILEINDEXBEFORE="dcfileindexbefore"
#typeset -r ACFILEINDEXAFTER="acfileindexafter"
typeset -r ACFILEINDEXAFTER="dcfileindexafter"
#typeset -r ACFILESTODELETE="acfilestodelete"
typeset -r ACFILESTODELETE="dcfilestodelete"
#typeset -r ACFILESTOCPIO="acfilestocpio"
typeset -r ACFILESTOCPIO="dcfilestocpio"
#typeset -r ACFILESTMP="acfilestmp"
typeset -r ACFILESTMP="dcfilestmp"
typeset -r UPDATENEEDEDFLAG="/export/.updateneeded"
typeset -r INSTALLEDPATCHDIR="/var/sadm/patch"
typeset -r PATCHDBFILE="$INSTALLEDPATCHDIR/.patchDB"
typeset -r CLIENTBASEDIR="/export/root"
typeset -r CLONEBASEDIR="$CLIENTBASEDIR/clone"
typeset -r CLONEAREAS="$CLONEBASEDIR/${AC_TAG}Solaris*/*"
#typeset -r CLIENTROOTS="$CLIENTBASEDIR/*/.cache"
#diskless clients do not have a .cache directory
typeset -r CLIENTROOTS="$CLIENTBASEDIR/*/usr"
typeset -r RELEASEFILE="/var/sadm/softinfo/INST_RELEASE"

PrintErr() {
				# TODO print an internal error message
        echo "$@" >&2
}

OutputWarning() {
        echo "W" >&2
		[ -n "$1" ] && echo "$1" >&2
		[ -n "$2" ] && echo "$2" >&2
		[ -n "$3" ] && echo "$3" >&2
		[ -n "$4" ] && echo "$4" >&2
}

OutputError() {
        echo "E" >&2
		[ -n "$1" ] && echo "$1" >&2
		[ -n "$2" ] && echo "$2" >&2
		[ -n "$3" ] && echo "$3" >&2
		[ -n "$4" ] && echo "$4" >&2
}

OutputMessage() {
        echo "M" >&2
		[ -n "$1" ] && echo "$1" >&2
		[ -n "$2" ] && echo "$2" >&2
		[ -n "$3" ] && echo "$3" >&2
		[ -n "$4" ] && echo "$4" >&2
}

IsRootUser() {
        typeset -r output=$(/usr/bin/id)
        typeset uid=${output#*=}
        uid=${uid%%\(*}
        [ "$uid" == 0 ] && return 0 || return 1
}

TmpFile() {
        typeset -r prefix=$1
        [ -z "$!" ] && echo "/tmp/${prefix}.$$" || echo "/tmp/${prefix}.$!"
}

DirectoryExists() {
        typeset -r dir="$1"
        [ -d $dir ] && return 0 || return 1
}

CleanUpFromAnyFailure() {
        rm -rf $SPOOLDIR/stage.*      #remove any copy staging areas
        rm -rf $SPOOLDIR/.tobedeleted*  #remove any patches marked for removal
        rm -f /tmp/spooldb*             #remove any /tmp patch databases
        rm -f /tmp/archivedb*
        rm -f /tmp/tmpflatdb*
	rm -f /tmp/servdb*
	# clean up service areas if a copy was aborted or killed.
	if [ -d /export/.tobedeleted* ] ; then 
		rm -rf /export/.tobedeleted*
		rm -rf /export/exec/.tobedeleted*
		rm -rf /export/share/.tobedeleted*
	fi
}

CleanUpServiceCopy() {
	typeset -r service="$1"
        typeset -r copy="$2"
        typeset -r servicename=$(basename $service)

	mv $copy /export/.tobedeleted$(basename $copy) 
        for dir in $SERVICEUSRAREAS/.copyof$servicename* ; do
		mv $dir $SERVICEUSRAREAS/.tobedeleted$(basename $dir)
        done
	rm -rf $SERVICEUSRAREAS/.tobedeleted*
        rm -f /export/share/.copyof$servicename
        rm -rf /export/.tobedeleted$(basename $copy)
}

PatchHasCorrectFormat() {
        typeset -r patchdir=$1
        typeset -r patchid=$(basename $patchdir)
        typeset -r pkginfo_files=$(ls $patchdir/*/pkginfo 2>/dev/null)
        [ -z "$pkginfo_files" ] && {
                OutputError "LM_11100" "LM_11101" $patchdir
                return 1
        }
        integer error=0
        typeset infofile=
        for infofile in $pkginfo_files ; do
                grep "_PATCHID=$patchid" $infofile > /dev/null 2>&1 || {
                				OutputError "LM_11102" "LM_11103" "$patchid" "$infofile"
                        error=1
                }
        done
        return $error
}

IsManagedPatch() {
        [ "$OPT_DEBUG" == 'true' ] && set -x
        typeset -r root="$1"
        typeset -r patch="$2"

        [ -f $root$INSTALLEDPATCHDIR/$patch/.acpatch_managed ] \
		 && return 0 || return 1
}

MakePatchManaged() {
        typeset -r root="$1"
        typeset -r patch="$2"

        touch $root$INSTALLEDPATCHDIR/$patch/.acpatch_managed \
		> /dev/null 2>&1
}

PatchIsInstalled() {
	typeset -r root="$1"
	typeset -r patch="$2"

	if [ -f $root$PATCHDBFILE ] ; then
	       awk '{print $2}' $root$PATCHDBFILE | grep -s "$patch" >/dev/null\
			&&  return 0 || return 1
	fi
	return 1
}

UpRevOfPatchIsInstalled() {
	[ "$OPT_DEBUG" == 'true' ] && set -x
	typeset -r root="$1"
        typeset -r patch="$2"
	typeset -r patchbase=${patch%-*}

	if [ -f $root$PATCHDBFILE ] ; then
		instpatches=$(awk '{print $2}' $root$PATCHDBFILE |\
			grep $patchbase)
		for instpatch in $instpatches ; do
			[[ $instpatch > $patch ]] && return 0
		done
	fi
	return 1
}

GetRequirementsFromTargetpatchDB() {
	[ "$OPT_DEBUG" == 'true' ] && set -x
	typeset -r target="$1"
	typeset -r patch="$2"

	typeset patchline=$(grep "Patch: $patch" $target$PATCHDBFILE)
	typeset afterreqs=${patchline#*Requires:*}
	typeset reqs=${afterreqs%*Incompat*}

	echo "$reqs"
}

FindDiffs() {
        [ "$OPT_DEBUG" == 'true' ] && set -x
        typeset -r list1="$1"
        typeset -r list2="$2"

        typeset diffs=
        for item in $list1 ; do
                [ $(echo $list2 | grep -c $item) == 0 ] && diffs="$diffs $item"
        done
        echo $diffs
}

DestHasEnoughDiskSpaceForSource() {
        typeset -r dest="$1"
        typeset -r source="$2"

        typeset -r needed=$(/usr/bin/du -ks $source | awk '{print $1}')
        typeset -r available=$(/usr/bin/df -k $dest | tail -1 |\
                         awk '{print $4}')

        if [ $needed -gt $available ] ; then
                return 1
        else
                return 0
        fi
}

MakeDirectory() {
        typeset perms=$1
        typeset base=$2
        typeset baselist=""
        typeset makedir=""
        while [ ! -d "$base" ] ; do
            baselist=$base" "$baselist
            base=$(dirname $base)
        done
        for makedir in $baselist; do
            mkdir $makedir || return $?
            chmod 755 $makedir || return $?
        done
        return 0
}

CopyDir() {
	[ "$OPT_DEBUG" == 'true' ] && set -x
        typeset -r copysource=$1
        typeset -r copydest=$2
        typeset -r tempstage=$(dirname $2)/.stage.$$
        MakeDirectory 755 $tempstage || return $?
        (
                cd $copysource
                find  . -depth | cpio -pdum $tempstage 2> /dev/null || return 1
                return 0
        ) || return $?
        mv $tempstage $copydest || return $?
        rm -rf $tempstage || return $?
        return 0
}

GetPkgValue() {
        typeset -r pkginfo=$1
        typeset -r symbol=$2
        typeset line=
        line=$(grep $symbol $pkginfo)
        if [ ! -z "$line" ] ; then
                line=${line#*=}
                [ ! -z "$line" ] && echo $line
        fi
        return 0
}

GetPatchOSRelease() {
        typeset -r patchdir="$1"
        typeset -r patchid="$2"
        for os in $(sed -n -e 's/Solaris Release: //p'\
                $patchdir/README.$patchid | sed 's/[^0-9.]/ /g') ; do
                case "$os" in
                        2.5.1|2.6|2.7|7|2.8|8)
                                echo $os
                                ;;
                        *|"")
                                ;;
                esac
        done
}

CreateDatabaseEntries() {
        typeset -r patchdir="$1"
	typeset -r patchid=$(basename $patchdir)
	typeset osrelease=$(GetPatchOSRelease $patchdir $patchid) || return 1
        typeset -r arch=$(sed -n 's/ARCH=*\([a-z0-9]*\)\.*.*/\1/p'\
                 $patchdir/*/pkginfo | sort | uniq | tr "\012" " " ) || return 1

	osrelease=$(echo $osrelease | sed 's/2.7/7/' | sed 's/2.8/8/')

        typeset pkginfo_file=
        for pkginfo_file in $patchdir/*/pkginfo ; do
                typeset patchname=${patchdir##*/}
                typeset pkgdir=${pkginfo_file%/*}
                typeset pkgname=${pkgdir##*/}
                echo "$pkgname/pkginfo|\c"
                echo "$patchname|\c"
                echo "$(GetPkgValue $pkginfo_file OBSOLETE)|\c"
                echo "$(GetPkgValue $pkginfo_file REQUIRES)|\c"
                echo "$(GetPkgValue $pkginfo_file INCOMPAT)|\c"
		echo "$osrelease|\c"
		echo "$arch|\c"
		echo "$(GetPkgValue $pkginfo_file TYPE)"
        done
}

DeleteDatabaseEntries(){
        [ "$OPT_DEBUG" == 'true' ] && set -x
        typeset -r patchid=$(basename $1)
        typeset -r flatdb="$2"
        typeset -r tmpflatdb=$(TmpFile tmpflatdb)

        grep -v '.*|.*'"$patchid"'.*|.*|.*|.*' $flatdb > $tmpflatdb
        rm -f $flatdb
        mv $tmpflatdb $flatdb
}

CreatePatchDatabase(){
	typeset -r dir="$1"
	typeset patch=
	typeset patches=
	for patch in $dir/* ; do
		if [ -f $patch/*/pkginfo ] ; then
                        patches="$patches $patch"
                fi
        done
	for patch in $patches ; do
		CreateDatabaseEntries $patch || return 1
	done
}

GetOpts() {
	typeset -i opt=0
        while getopts ':a:copr:muC:d' char ; do
                case $char in
                        a)      ADD_PATCH="$OPTARG" ; opt=1
                                ;;
                        o)      OPT_LISTSERVICES='true' ; opt=1
                                ;;
                        c)      OPT_LISTCLONES='true' ; opt=1
                                ;;
                        p)      OPT_LISTPATCHES='true' ; opt=1
                                ;;
                        r)      REMOVE_PATCH="$OPTARG" ; opt=1
                                ;;
                        m)      OPT_SYNCHRONIZE='true' ; opt=1
                                ;;
                        u)      OPT_UPDATE='true' ; opt=1
                                ;;
                        C)      SYNCNEWCLONEAREA="$OPTARG" ; opt=1
                                ;;
                        d)      OPT_DEBUG='true' ;
                                ;;
                        \?)     OutputError "LM_11104" "LM_11105"
 		            								return 1
	
                                ;;
                        :)     OutputError "LM_11104" "LM_11105"
 		            								return 1
                                ;;
                esac
        done
	if [ $opt -eq 0 ] ; then
		return 1
	fi
        if [ -n "$ADD_PATCH" ] ; then
                if [ -n "$REMOVE_PATCH" -o \
                   "$OPT_LISTSERVICES" == 'true' -o \
                   "$OPT_LISTCLONES" == 'true' -o \
                   "$OPT_LISTPATCHES" == 'true'  ] ; then
                        OutputError "LM_11104" "LM_11105" 
                        return 1
                fi
                if ! DirectoryExists $ADD_PATCH ; then
                        OutputError "LM_11106" "LM_11107" "$ADD_PATCH"
                        return 6
                fi
                if [[ $OPT_UPDATE == 'true' && $OPT_SYNCHRONIZE == 'false' ]] ; then
			OutputError "LM_11104" "LM_11105" 
			return 1
                fi
        fi
        if [ -n "$REMOVE_PATCH" ] ; then
                if [ -n "$ADD_PATCH" -o "$OPT_LISTSERVICES" == 'true' -o\
			"$OPT_LISTPATCHES" == 'true' ] ; then
			OutputError "LM_11104" "LM_11105" 
			return 1
                fi
                if [[ $OPT_UPDATE == 'true' && $OPT_SYNCHRONIZE == 'false' ]] ; then
			OutputError "LM_11104" "LM_11105" 
                        return 1
                fi
        fi
        if [ "$OPT_LISTCLONES" == 'true' ] ; then
                if [ -n "$ADD_PATCH" -o "$OPT_LISTPATCHES" == 'true' -o\
                   -n "$REMOVE_PATCH" -o \
                   "$OPT_SYNCHRONIZE" == 'true' -o \
		   "$OPT_UPDATE" == 'true' ] ; then
			OutputError "LM_11104" "LM_11105" 
                        return 1
                fi
        fi
        if [ "$OPT_LISTSERVICES" == 'true' ] ; then
                if [ -n "$ADD_PATCH" -o "$OPT_LISTPATCHES" == 'true' -o\
                   -n "$REMOVE_PATCH" -o \
                   "$OPT_SYNCHRONIZE" == 'true' -o \
		   "$OPT_UPDATE" == 'true' ] ; then
			OutputError "LM_11104" "LM_11105" 
                        return 1
                fi
        fi
        if [ "$OPT_LISTPATCHES" == 'true' ] ; then
                if [ -n "$ADD_PATCH" -o "$OPT_LISTSERVICES" == 'true' -o\
                   -n "$REMOVE_PATCH" -o\
                   "$OPT_SYNCHRONIZE" == 'true' ] ; then
			OutputError "LM_11104" "LM_11105" 
                        return 1
                fi
        fi
        return 0
}

PatchHasValidOSReleaseInfo() {
	[ "$OPT_DEBUG" == 'true' ] && set -x
        typeset -r patchdir="$1"
        typeset -r patchid=$(basename $1)
	typeset -r osreleaselist=$(GetPatchOSRelease $patchdir $patchid)

        if [ -z $osreleaselist ] ; then
                OutputError "LM_11108" "LM_11109" "$1"
                return 1
	fi
        if [[ "$osreleaselist" == 1\.* ]] ; then
                OutputError "LM_11110" "LM_11111" "$1"
                return 1
        fi
        if [[ "$osreleaselist" == *[a-zA-Z]* ]] ; then
                OutputError "LM_11112" "LM_11113" "$1" "$osreleaselist}"
                return 1
        fi
        return 0
}

PatchCanBeSpooled() {
        [ "$OPT_DEBUG" == 'true' ] && set -x
        typeset -r addpatch="$1"
        typeset -r addpatchid=$(basename $addpatch)
        typeset -r spooldb="$2"

        PatchHasCorrectFormat      $addpatch      || return 6
        PatchHasValidOSReleaseInfo $addpatch      || return 6
        if DB SameVersionOfPatchExists $addpatchid $spooldb ; then
                OutputError "LM_11114" "LM_11115" "$addpatchid"
                return 6
        fi
        if DB PatchIsObsoletedByAnExistingPatch $addpatchid $spooldb ; then
                OutputError "LM_11116" "LM_11117" "$addpatchid"
                return 6
        fi
        if DB NewerVersionOfPatchExists $addpatchid $spooldb ; then
                OutputError "LM_11118" "LM_11119" "$addpatchid"
                return 6
        fi
	if DB IncompatiblePatchExists $addpatch $spooldb  ; then
                OutputError "LM_11120" "LM_11121" "$addpatchid"
		return 6
	fi
        if ! DB RequiredPatchExists $addpatch $spooldb ; then
                OutputError "LM_11122" "LM_11123" "$addpatchid"
                return 6
        fi
	if [ -d $addpatch/SUNWcsr ] ; then 
		typeset -r preserve=$(grep preserve $addpatch/SUNWcsr/pkgmap)  
        	if [ -n "$preserve" ] ; then
                	OutputWarning "LM_11124" "LM_11125" "$addpatchid"
		fi
        fi
        return 0
}

PatchesObsoletedByThisPatch() {
	[ "$OPT_DEBUG" == 'true' ] && set -x
        typeset -r patch="$1"

        for pkg in $patch/*/pkginfo ; do
                obspatches=$(GetPkgValue $pkg OBSOLETE)
        done
	echo $obspatches | tr " ," "\012\012" | sort | uniq | tr "\012" " "
}

ArchiveAnySpooledOlderVersionsOfThisPatch() {
        [ "$OPT_DEBUG" == 'true' ] && set -x
        typeset -r spooldb="$1"
        typeset -r spooldir="$2"
        typeset -r addpatch="$3"
        typeset -r addpatchid=$(basename "$3")

        typeset -r oldpatches=$(DB NamesOfOlderVersionsOfPatch $addpatchid \
                $spooldb)
        for oldpatch in $oldpatches ; do
                OutputWarning "LM_11126" "LM_11127" "$oldpatch"
                if [ -d $spooldir/Archive/$oldpatch ]; then
                        rm -rf $spooldir/$oldpatch
                else
                        mv $spooldir/$oldpatch/ $spooldir/Archive/$oldpatch
                fi
		DeleteDatabaseEntries $oldpatch $spooldb
		
        done
}

ArchiveAnySpooledPatchesObsoletedByThisPatch() {
        [ "$OPT_DEBUG" == 'true' ] && set -x
        typeset -r spooldb="$1"
        typeset -r spooldir="$2"
        typeset -r addpatch="$3"
        typeset -r addpatchid=$(basename "$3")

        typeset -r obsoletedpatches=$(PatchesObsoletedByThisPatch $addpatch)
        for patch in $obsoletedpatches ; do
                if DB SameVersionOfPatchExists $patch $spooldb; then
                	OutputWarning "LM_11128" "LM_11129" "$patch"
                        if [ -d $spooldir/Archive/$patch ]; then
                                rm -rf $spooldir/$patch
                        else
                        	mv $spooldir/$patch/ $spooldir/Archive/$patch
			fi
			DeleteDatabaseEntries $patch $spooldb
                fi
		ArchiveAnySpooledOlderVersionsOfThisPatch $spooldb $SPOOLDIR \
			$patch
        done
}

AddPatchToSpool() {
        typeset -r patch="$1"
        typeset -r spooldir="$2"
	typeset -r spooldb="$3"
	typeset -r patchid=$(basename $patch)

        if DestHasEnoughDiskSpaceForSource $spooldir $patch ; then
                CopyDir $patch $spooldir/$patchid || {
                	OutputError "LM_11130" "LM_11131" "$patchid"
                        return 5
                }
		CreateDatabaseEntries $patch >> $spooldb
        else
               	OutputError "LM_11132" "LM_11133" "$spooldir"
                return 5
        fi
        return 0
}

PatchCanBeUnspooled() {
        [ "$OPT_DEBUG" == 'true' ] && set -x
        typeset -r rmpatchid="$1"
        typeset -r spooldb="$2"

        if ! DB SameVersionOfPatchExists $rmpatchid $spooldb ; then
               	OutputError "LM_11134" "LM_11135" "$rmpatchid"
                return 7
        fi
        if DB PatchIsRequiredByAnExistingPatch $rmpatchid $spooldb ; then
               	OutputError "LM_11136" "LM_11137" "$rmpatchid"
                return 7
        fi
}

RemovePatchFromSpool(){
        [ "$OPT_DEBUG" == 'true' ] && set -x
        typeset -r rmpatchid="$1"
        typeset -r spooldir="$2"
        typeset -r spooldb="$3"

        mv $spooldir/$rmpatchid/ $spooldir/.tobedeleted${rmpatchid}
	( rm -rf $spooldir/.tobedeleted${rmpatchid} ) &
        DeleteDatabaseEntries $rmpatchid $spooldb
}

RestoreLatestArchivedOlderVersionOfThisPatch(){
        [ "$OPT_DEBUG" == 'true' ] && set -x
        typeset -r archivedb="$1"
        typeset -r spooldir="$2"
        typeset -r spooldb="$3"
        typeset -r rmpatchid="$4"

        typeset -r oldpatches=$(DB NamesOfOlderVersionsOfPatch \
                $rmpatchid $archivedb)
        for patch in $oldpatches ; do
		if ! DB NewerVersionOfPatchExists $patch $archivedb ; then
                	if ! DB PatchIsObsoletedByAnExistingPatch \
                		$patch $spooldb ; then
               			OutputWarning "LM_11138" "LM_11139" "$patch"
                        	mv $spooldir/Archive/$patch/ $spooldir/$patch
                        	CreateDatabaseEntries $spooldir/$patch >> \
					$spooldb
			fi
                fi
        done
}

RestoreAnyArchivedPatchesObsoletedByThisPatch(){
        [ "$OPT_DEBUG" == 'true' ] && set -x
        typeset -r archivedb="$1"
        typeset -r spooldir="$2"
        typeset -r spooldb="$3"
	typeset -r obsoletedpatches="$4"

        for patch in $obsoletedpatches ; do
                if DB SameVersionOfPatchExists $patch $archivedb; then
                        if ! DB PatchIsObsoletedByAnExistingPatch \
                                $patch $spooldb ; then
               			OutputWarning "LM_11140" "LM_11141" "$patch"
                                mv $spooldir/Archive/$patch/ $spooldir/$patch
                                CreateDatabaseEntries $spooldir/$patch \
					>> $spooldb
                        fi
		else
			RestoreLatestArchivedOlderVersionOfThisPatch \
				$archivedb $spooldir $spooldb $patch
		fi
        done
}

ListSpooledPatchesForThisOSAndArch() {
	[ "$OPT_DEBUG" == 'true' ] && set -x
	typeset -r spooldb="$1"
	typeset -r spooldir="$2"
	typeset -r rel="$3"
        typeset -r arch="$4"
	typeset patches=

	patches=$(DB PatchesApplicableToThisOSAndArch $spooldb $rel $arch)
	for patch in $patches ; do
		typeset synop=
		synop=$(sed -n 's/^[ 	]*Synopsis: //p' $spooldir/$patch/README.$patch)
		OutputMessage "$rel" "$arch" "$patch" "$synop"
	done	
}

ListSpooledPatchesByOSAndArch() {
	[ "$OPT_DEBUG" == 'true' ] && set -x
	typeset -r spooldb="$1"
	typeset -r spooldir="$2"
	
	osreleases=$(cat $spooldb | awk '{FS="|" ; print $6}' \
		| tr " ," "\012\012"  | sort | uniq)
	archlist=$(cat $spooldb | awk '{FS="|" ; print $7}' \
		| tr " ," "\012\012"  | sort | uniq)
	
	if [ -z "$osreleases" -o -z "$archlist" ] ; then
		OutputError "LM_11169" "LM_11169"
		return 0
	fi

	for rel in $osreleases ; do
		for arch in $archlist ; do
			ListSpooledPatchesForThisOSAndArch $spooldb $spooldir \
				$rel $arch
		done
	done
}

GetClientInfo() {
        [ "$OPT_DEBUG" == 'true' ] && set -x
        typeset -r clientroot="$1"
        typeset -r clientname=$(basename $clientroot)
        typeset -r releasefile=${clientroot}$RELEASEFILE

        if [ -f $releasefile ] ; then
                os=$(sed -n 's/^OS=//p' $releasefile)
                rel=$(sed -n 's/^VERSION=//p' $releasefile)
                arch=$(pkginfo -l -R $clientroot SUNWcar |\
                         sed -n 's/.*ARCH: *\([a-z0-9]*\)\..*/\1/p')
                echo "  $clientname     $os $rel        $arch"
        else
                echo "  $clientname     unknown OS release and architecture"
        fi
}

ListPatchesInstalledOn() {
        [ "$OPT_DEBUG" == 'true' ] && set -x
        typeset -r root="$1"

	if [ -f $root$PATCHDBFILE ] ; then 
		patches=$(grep -v "Version" $root$PATCHDBFILE \
			| awk '{ print $2 }' | tr "\012" " ")
		if [ -n "$patches" ] ; then
			echo "$patches" 
			return 0
		fi
	fi
	return 1
}

ListPatchesInstalledOnToDisplay() {
        [ "$OPT_DEBUG" == 'true' ] && set -x
        typeset -r root="$1"

	if [ -f $root$PATCHDBFILE ] ; then 
		for patchname in $(grep -v "Version" $root$PATCHDBFILE | awk '{ print $2 }')
		do
				OutputMessage "$patchname"
		done
	fi
	return 0
}

UpdatePending() {
        [ "$OPT_DEBUG" == 'true' ] && set -x
        typeset -r root="$1"
        typeset -r copyarea=$(dirname $root)/.copyof$(basename $root)

	if [ -f $copyarea$PATCHDBFILE ] ; then 
		if [ -f $root$PATCHDBFILE ] ; then
			if ! cmp $copyarea$PATCHDBFILE $root$PATCHDBFILE ; then
				return 0
			fi
		fi
	fi
	return 1
}

ListOSservicesAvailableAndTheirPatchesInstalled() {
        [ "$OPT_DEBUG" == 'true' ] && set -x
	
        if [ -d $OSSERVICES ] ; then
               for service in $OSSERVICES ; do
								if UpdatePending "$service" ; then
									OutputMessage "SERVICE_UPDATE_PENDING=${service#/export/${AC_TAG}}"
								else
									OutputMessage "SERVICE=${service#/export/${AC_TAG}}"
								fi
                 ListPatchesInstalledOnToDisplay $service
               done
        fi
}

ListCloneAreasAndTheirPatchesInstalled() {
	[ "$OPT_DEBUG" == 'true' ] && set -x

	if [ -d $CLONEAREAS ] ; then
		for clonearea in $CLONEAREAS ; do
			if UpdatePending "$clonearea" ; then
				OutputMessage "CLONE_UPDATE_PENDING=${clonearea#*clone/${AC_TAG}}"
			else
				OutputMessage "CLONE=${clonearea#*clone/${AC_TAG}}"
			fi
			ListPatchesInstalledOnToDisplay $clonearea
		done
	fi
}

CreateServiceCopyForPatching(){
	[ "$OPT_DEBUG" == 'true' ] && set -x
	typeset -r service="$1"
	typeset -r copy="$2"
	typeset -r servicename=$(basename $service)

	MakeDirectory 755 $copy
	CopyDir $service/var $copy/var || return 1
	if [ -d $service/opt ] ; then
		CopyDir $service/opt $copy/opt || return 1
	fi
	for dir in $SERVICEUSRAREAS/${servicename}* ; do
		typeset arch=${dir#*/$servicename}
		CopyDir $dir $SERVICEUSRAREAS/.copyof$(basename $dir) || \
			return 1
		ln -s ../exec/.copyof${servicename}$arch \
			${copy}/usr$arch
		[ ! -d /export/share/.copyof$servicename ] && \
		ln -s $SERVICEUSRAREAS/.copyof${servicename}$arch/usr/share \
			/export/share/.copyof$servicename
	done

	return 0

}

CreateFileIndex() {
	[ "OPT_DEBUG" == 'true' ] && set -x
	typeset -r dir="$1"
	(
		cd $dir
		find . ! -name \. -depth -exec ls -ld {} \;
	)
}

CompareFileIndexes() {
        [ "$OPT_DEBUG" == 'true' ] && set -x
        typeset -r fileindex1="$1"
        typeset -r fileindex2="$2"

        diff $fileindex1 $fileindex2 | grep -v dcfile | grep "^>" | awk '{
                for (i=10;i<NF+1;i++) {
                        if ( $i == "->" ) {
                                break;
                        }else{
                                printf("%s ", $i);
                        }
                }
                printf("\n");
        }'
}

CreateListsOfFilesToDeleteAndCPIO() {
	[ "$OPT_DEBUG" == 'true' ] && set -x
	typeset -r target="$1"
	typeset -r fileindexbefore="$target/$ACFILEINDEXBEFORE"
	typeset -r fileindexafter="$target/$ACFILEINDEXAFTER"
	typeset -r filestodelete="$target/$ACFILESTODELETE"
	typeset -r filestocpio="$target/$ACFILESTOCPIO"
	typeset -r tmpfilelist="$target/$ACFILESTMP"

	CompareFileIndexes $fileindexbefore $fileindexafter > $filestocpio
	CompareFileIndexes $fileindexafter $fileindexbefore > $tmpfilelist
	rm -f $filestodelete

	cat $tmpfilelist | while read file ; do
		typeset -i count=$(grep -c "$file" $filestocpio)
		if [ $count -eq 0 ] ; then
			echo $file >> $filestodelete
		fi
	done

	rm -f $tmpfilelist
	rm -f $fileindexafter
}

ReqPatchIsInList() {
        [ "$OPT_DEBUG" == 'true' ] && set -x
	typeset -r reqpatch="$1"
	typeset -r list="$2"

	typeset -r reqpatchbase=${reqpatch%-*}
	typeset patch=
	for patch in $list ; do
		patchbase=${patch%-*}
		if [ "$patchbase" = "$reqpatchbase" ] ; then
			[[ "$patch" > "$reqpatch" ]] && return 0
			[[ "$patch" == "$reqpatch" ]] && return 0
		fi
	done
	return 1
}

RequirementsMetForOrdering() {
	[ "$OPT_DEBUG" == 'true' ] && set -x
	typeset -r patch="$1"
	typeset -r ordered="$2"
	typeset -r reqpatches="$3"
	typeset -r patches="$4"
	typeset -i reqmet=1

	[ -z "$reqpatches" ] && return 0

	for reqpatch in $reqpatches ; do
		if ReqPatchIsInList "$reqpatch" "$patches" ; then
			if ! ReqPatchIsInList "$reqpatch" "$ordered" ; then
				reqmet=0
			fi
		fi
	done
	[ $reqmet -eq 1 ] && return 0 || return 1
}

OrderPatchesByRequirements() {
	[ "$OPT_DEBUG" == 'true' ] && set -x
	typeset -r patches="$1"
	typeset -r spooldb="$2"
	typeset -r target="$3"
	typeset -r direction="$4"
	typeset togo="$patches"
	typeset ordered=

	for iter in $patches ; do
		typeset later=
		for patch in $togo ; do
			if [ "$direction" == "forward" ] ; then
				reqs=$(DB PatchesRequiredByThisPatch \
					$patch $spooldb) 
			else
				reqs=$(GetRequirementsFromTargetpatchDB \
					$target $patch)
			fi
			if RequirementsMetForOrdering "$patch" "$ordered" \
			   "$reqs" "$patches" ; then
				ordered="$ordered $patch"
			else
				later="$later $patch"
			fi
		done
		[ -z "$later" ] && break
		togo="$later"
	done
	if [ "$direction" == "forward" ] ; then 
		echo "$ordered"
	else
		echo "$ordered" | awk '{ for(i=NF;i>0;i--) print $i }'
	fi
}

Synchronize() {
        [ "$OPT_DEBUG" == 'true' ] && set -x
	typeset options="$1"
	typeset -r target="$2"
	typeset -r relevantpatches="$3"
	typeset    backoutpatches="$4"
	typeset    installpatches="$5"
	typeset -r spooldir="$6"
	typeset -i ret=0
	typeset patch=
	for patch in $relevantpatches ; do
                if PatchIsInstalled "$target" "$patch" && \
                   ! IsManagedPatch "$target" "$patch" ; then
                        MakePatchManaged "$target" "$patch"
                fi
        done
	[ -z "$backoutpatches$installpatches" ] && return 0

	[ "$options" = "-S" ] && options="-S ${target#/export/}" || \
		options="-R $target"

	backoutpatches=$(OrderPatchesByRequirements "$backoutpatches" \
                "$spooldb" "$target" "reverse")
        for patch in $backoutpatches ; do
		typeset backoutlog=$(TmpFile backoutlog)
                if IsManagedPatch "$target" "$patch" ; then 
			if ! UpRevOfPatchIsInstalled $target $patch ; then
				patchrm $options $patch > $backoutlog && \
				OutputWarning "LM_11142" "LM_11143" "$patch" || {
					OutputWarning "LM_11144" "LM_11145" "$patch" "$backoutlog"
					ret=8
				}
			else
				OutputWarning "LM_11146" "LM_11147" "$patch"
			fi
		fi
		rm -f $backoutlog
        done
	installpatches=$(OrderPatchesByRequirements "$installpatches" \
		"$spooldb" "$target" "forward")
        for patch in $installpatches ; do
		typeset installlog=$(TmpFile installlog)
		if ! UpRevOfPatchIsInstalled $target $patch ; then
			patchadd $options $spooldir/$patch > $installlog && { \
                		MakePatchManaged "$target" "$patch"
				OutputWarning "LM_11148" "LM_11149" "$patch"
			} || {
				OutputWarning "LM_11150" "LM_11151" "$patch" "$installlog"
				ret=9
			}
		else
			OutputWarning "LM_11152" "LM_11153" "$patch"
		fi
		rm -f $installlog
        done

	return $ret
}

CreateServiceFileIndexesBeforePatching() {
	typeset -r servicecopy="$1"
	typeset -r servicename="$2"

	[ -f $servicecopy/var/$ACFILEINDEXBEFORE ] || \
                CreateFileIndex "$servicecopy/var" \
                        > $servicecopy/var/$ACFILEINDEXBEFORE

        if [ -d $servicecopy/opt ] ; then
                [ -f $servicecopy/opt/$ACFILEINDEXBEFORE ] || \
                        CreateFileIndex "$servicecopy/opt" \
                                > $servicecopy/opt/$ACFILEINDEXBEFORE
        fi

        for dir in /export/exec/.copyof$servicename* ; do
                [ -f $dir/$ACFILEINDEXBEFORE ] || \
                        CreateFileIndex $dir > $dir/$ACFILEINDEXBEFORE
        done
	return 0
}

SynchronizeOSService() {
        [ "$OPT_DEBUG" == 'true' ] && set -x
        typeset -r service="$1"
	typeset -r servicename=$(basename $service)
        typeset -r spooldb="$2"
        typeset -r spooldir="$3"

	typeset -r servicecopy=$(dirname $service)/.copyof$(basename $service)
       
        if [ ! -d $servicecopy ] ; then
		CreateServiceCopyForPatching $service $servicecopy || return 12
	fi

        typeset -r servicepatches=$(ListPatchesInstalledOn $servicecopy)
        typeset    osversion=${service#$OSSERVICES}
	typeset -i ret=0

        [ "$osversion" == "2.7" ] && osversion=7
        [ "$osversion" == "2.8" ] && osversion=8

        typeset -r relevantpatches=$(DB ServicePatchesApplicableToThisOS \
                $spooldb $osversion)
        typeset -r backoutpatches=$(FindDiffs "$servicepatches" \
		"$relevantpatches")
        typeset -r installpatches=$(FindDiffs "$relevantpatches"\
		"$servicepatches")

	CreateServiceFileIndexesBeforePatching "$servicecopy" "$servicename" ||\
		exit 12

	Synchronize "-S" "$servicecopy" "$relevantpatches" \
		"$backoutpatches" "$installpatches"  "$spooldir"
	ret=$?

	CreateFileIndex $servicecopy/var > $servicecopy/var/$ACFILEINDEXAFTER
	CreateListsOfFilesToDeleteAndCPIO $servicecopy/var
	if [ -d $servicecopy/opt ] ; then
		CreateFileIndex $servicecopy/opt \
			> $servicecopy/opt/$ACFILEINDEXAFTER
		CreateListsOfFilesToDeleteAndCPIO $servicecopy/opt
	fi
	
	for dir in /export/exec/.copyof$servicename* ; do
		CreateFileIndex $dir > $dir/$ACFILEINDEXAFTER
		CreateListsOfFilesToDeleteAndCPIO $dir
	done
	return $ret
}

CreateCloneFileIndexBeforePatching() {
	typeset -r clonecopy="$1"
	
	[ -f $clonecopy/$ACFILEINDEXBEFORE ] || \
                CreateFileIndex "$clonecopy" > $clonecopy/$ACFILEINDEXBEFORE
	return 0
}

SynchronizeCloneArea() {
        [ "$OPT_DEBUG" == 'true' ] && set -x
        typeset -r clonearea="$1"
        typeset -r spooldb="$2"
        typeset -r spooldir="$3"
	typeset -i ret=0

	typeset -r clonecopy=$(dirname $clonearea)/.copyof$(basename $clonearea)
       
        if [ ! -d $clonecopy ] ; then 
		CopyDir $clonearea $clonecopy || return 12
	fi
       
        typeset -r clientpatches=$(ListPatchesInstalledOn $clonecopy)
        typeset    osversion=$(GetClientInfo $clonearea | awk '{print $3}')
        typeset -r arch=$(GetClientInfo $clonearea | awk '{print $4}')

        [ "$osversion" == "2.7" ] && osversion=7
        [ "$osversion" == "2.8" ] && osversion=8

        typeset -r relevantpatches=$(DB ClientPatchesApplicableToThisOSAndArch \
		$spooldb $osversion $arch)
        typeset -r backoutpatches=$(FindDiffs "$clientpatches" \
		"$relevantpatches")
        typeset -r installpatches=$(FindDiffs "$relevantpatches"\
		"$clientpatches")

	CreateCloneFileIndexBeforePatching "$clonecopy" || exit 12

	Synchronize "-R" "$clonecopy" "$relevantpatches" \
		"$backoutpatches" "$installpatches" "$spooldir"
	ret=$?

	CreateFileIndex $clonecopy > $clonecopy/$ACFILEINDEXAFTER
	CreateListsOfFilesToDeleteAndCPIO $clonecopy

	return $ret
}

Update() {
	[ "$OPT_DEBUG" == 'true' ] && set -x
	typeset -r target="$1"
	typeset -r from="$2"
	typeset -r filestodelete="$from/$ACFILESTODELETE"
	typeset -r filestocpio="$from/$ACFILESTOCPIO"
	typeset -i ret=0
	
	if [ -f $filestodelete ] ; then
		(
			cd $target
			cat $filestodelete | while read file ; do
				rm -rf $file
			done
		)
	fi
	
	if [ -f $filestocpio ] ; then 
		(
			cd $from
                	cat $filestocpio | while read file ; do
				echo $file | cpio -pdum $target 2>/dev/null ||\
					ret=1
			done
		)
        fi
	typeset -r base=$(dirname $target)
	if [ "$base" != "$CLIENTBASEDIR" ] ; then 
		rm -f $from/$ACFILEINDEXBEFORE
		rm -f $filestodelete
		rm -f $filestocpio
	fi

	return $ret
}

UpdateOSService() {
	[ "$OPT_DEBUG" == 'true' ] && set -x
        typeset -r service="$1"
	typeset -r servicecopy=$(dirname $service)/.copyof$(basename $service)

	Update $service/var $servicecopy/var || return $?
	Update $service/opt $servicecopy/opt || return $?
	for dir in /export/exec/$servicename* ; do
		typeset copy=$(dirname $dir)/.copyof$(basename $dir)
		Update $dir $copy || return $?
	done
}

UpdateCloneArea() {
	[ "$OPT_DEBUG" == 'true' ] && set -x
	typeset -r clonearea="$1"
	typeset -r copy="$(dirname $clonearea)/.copyof$(basename $clonearea)"

	Update $clonearea $copy || return $?
}

UpdateClient() {
	[ "$OPT_DEBUG" == 'true' ] && set -x
        typeset -r client="$1"
	typeset -r osrel=$(GetClientInfo $client | awk '{print $3}')
	typeset -r arch=$(pkginfo -l -R $client SUNWcar |\
                         sed -n 's/.*ARCH: .*\.//p')
	typeset -r \
		clonecopy="$CLONEBASEDIR/${AC_TAG}Solaris_$osrel/.copyof$arch"

	Update $client $clonecopy || return $?
}

Main() {
        [ "$OPT_DEBUG" == 'true' ] && set -x

	#
	# -c option. List all Clone areas available 
	# and their respective patch levels. 
	#
 
        if [ $OPT_LISTCLONES == 'true' ] ; then
		ListCloneAreasAndTheirPatchesInstalled
                return 0
        fi

	#
	# -o option. List all OS services available 
	# and their respective patch levels. 
	#
 
        if [ $OPT_LISTSERVICES == 'true' ] ; then
                ListOSservicesAvailableAndTheirPatchesInstalled
                return 0
        fi
	#
	# -a option. Add a patch to the spool area.
	#	

        if [ -n "$ADD_PATCH" ] ; then
                if PatchCanBeSpooled $ADD_PATCH $SPOOLDB ; then
                        ArchiveAnySpooledOlderVersionsOfThisPatch \
                                $SPOOLDB $SPOOLDIR $ADD_PATCH
                        ArchiveAnySpooledPatchesObsoletedByThisPatch\
                                $SPOOLDB $SPOOLDIR $ADD_PATCH
                        AddPatchToSpool $ADD_PATCH $SPOOLDIR $SPOOLDB \
				|| return $?
		else 
			return $?
                fi
	
	#
	# -r option. Remove a patch from the spool area
	#

	elif [ -n "$REMOVE_PATCH" ] ; then
		if PatchCanBeUnspooled $REMOVE_PATCH $SPOOLDB ; then
			typeset -r obspatches=$(PatchesObsoletedByThisPatch \
				$SPOOLDIR/$REMOVE_PATCH)
			RemovePatchFromSpool $REMOVE_PATCH $SPOOLDIR $SPOOLDB
			RestoreLatestArchivedOlderVersionOfThisPatch \
				$ARCHIVEDB $SPOOLDIR $SPOOLDB $REMOVE_PATCH
			if [ -n $obspatches ] ; then 
				RestoreAnyArchivedPatchesObsoletedByThisPatch \
                                	$ARCHIVEDB $SPOOLDIR $SPOOLDB \
					$obspatches 
			fi
		else
			return $?
		fi
	#
	# -p option. List all patches in the spool area by OS and 
	# architecture with each associated patch synopsis        
	#

	elif [ $OPT_LISTPATCHES == 'true' ] ; then
                ListSpooledPatchesByOSAndArch $SPOOLDB $SPOOLDIR
		return 0
        fi

	#
	# -m option. Synchronize all offline copies of OS services
	#  and Clone areas with the patches in the spool area     
	#

	if [ $OPT_SYNCHRONIZE == 'true' ] ; then
		typeset -i ret1=0
		typeset -i ret2=0
		if [ -d $OSSERVICES ] ; then
                	for service in $OSSERVICES ; do
                       		SynchronizeOSService $service $SPOOLDB $SPOOLDIR
				ret1=$?
                	done
		else
                	OutputWarning "LM_11154" "LM_11154"
		fi
		if [ -d $CLONEAREAS ] ; then 
                	for clonearea in $CLONEAREAS ; do
                        	SynchronizeCloneArea $clonearea $SPOOLDB \
					$SPOOLDIR
				ret2=$?
                	done
		else
                	OutputWarning "LM_11155" "LM_11155"
		fi
		if [ $OPT_UPDATE == 'false' ] ; then
			[ $ret1 -gt 0 ] && return $ret1 || return $ret2
		fi
        fi

	#
	# -u option. Update all OS services, Clone Areas and Clients.
	#

	if [ $OPT_UPDATE == 'true' ] ; then
		typeset -i ret=0
		[ -f $UPDATENEEDEDFLAG ] && rm -f $UPDATENEEDEDFLAG
		if [ -d $CLIENTROOTS ] ; then
			for client in $CLIENTROOTS ; do
				client=$(dirname $client)
				UpdateClient $client || ret=10
			done
		else
                	OutputWarning "LM_11156" "LM_11156"
		fi
		if [ -d $CLONEAREAS ] ; then
                        for clonearea in $CLONEAREAS ; do
				UpdateCloneArea $clonearea || ret=10
			done
		else
                	OutputWarning "LM_11157" "LM_11157"
		fi
		if [ -d $OSSERVICES ] ; then
                        for service in $OSSERVICES ; do
                                UpdateOSService $service || ret=10
                        done
                else
                	OutputWarning "LM_11158" "LM_11158"
                fi
		return $ret
	fi

	#
	# -C option. Undocumented option which should not be run  
	# hand.  This code is called automatically from achostmod 
	# at OS service and clone area add time.  It automatically
	# creates the .copyof areas used in patching offline.		  
	#

	if [ -n "$SYNCNEWCLONEAREA" ] ; then
		typeset -r clonearea="$CLONEBASEDIR/$SYNCNEWCLONEAREA"
		typeset -r clonetype=$(dirname $clonearea)
		typeset osver=$(GetClientInfo $clonearea | awk '{print $3}')
                typeset -r service="$OSSERVICES${osver}"
                typeset -r \
                	copy=$(dirname $clonearea)/.copyof$(basename $clonearea)
                typeset -r \
                        scopy=$(dirname $service)/.copyof$(basename $service)
		typeset -i ret=0
		if [ -d $clonetype/sun* ] || [ -d $clonetype/i86pc ] ; then
			patchadd -p -R $clonearea > /dev/null
			patchadd -p -S $(basename $service) > /dev/null
			CopyDir $clonearea  $copy || return 11
                        CreateServiceCopyForPatching $service $scopy || {
				CleanUpServiceCopy $service $scopy
                                return 11
			}
		else
			return 11
		fi
		return $ret
	fi
}

#
# START 
#

CleanUpFromAnyFailure

if [ $# -eq 0 ] ; then
	OutputError "LM_11104" "LM_11105"
        return 1
fi

#
# Get and Verify command line options.
#
GetOpts $@ || {
	typeset -i err=$?
        return $err
}

if [ -f $UPDATENEEDEDFLAG ] && [ $OPT_SYNCHRONIZE = 'true' ] ; then
	#TODO figure out this message
	#echo "acpatch: Error. You must run acpatch -u to complete an update"
	#echo "Exit Code: 14"
	return 14
fi

if ! IsRootUser ; then
	OutputError "LM_11159" "LM_11159"
        return 2 
else
	#
	# Create Spool dir and Archive dir if they do not already exist.
	#
	if [ ! -d $SPOOLDIR ] ; then 
        	MakeDirectory 755 $SPOOLDIR || {
			OutputError "LM_11160" "LM_11161" "$SPOOLDIR"
                	return 3 
        	}
	fi
	if [ ! -d $SPOOLDIR/Archive ] ; then
        	MakeDirectory 755 $SPOOLDIR/Archive || {
			OutputError "LM_11162" "LM_11163" "$SPOOLDIR/Archive"
                	return 3 
        	}
	fi

	#
	# Create Spool and Archive patch databases unless command called
	# with the -c or -u options which do not need these databases.
	#

	if [ $OPT_SYNCHRONIZE = 'true' ] || [ -n "$ADD_PATCH"    ] || \
           [ $OPT_LISTPATCHES = 'true' ] || [ -n "$REMOVE_PATCH" ] ; then
		typeset -r SPOOLDB=$(TmpFile spooldb)
        	typeset -r ARCHIVEDB=$(TmpFile archivedb)
		#[ -z "$SYNCNEWCLONEAREA" ] && {
			#OutputWarning "LM_11166" "LM_11166"
		#}
        	CreatePatchDatabase $SPOOLDIR > $SPOOLDB           || {
			OutputError "LM_11164" "LM_11165" "$SPOOLDB"
			return 4
		}
        	CreatePatchDatabase $SPOOLDIR/Archive > $ARCHIVEDB || {
			OutputError "LM_11167" "LM_11168" "$ARCHIVEDB"
			return 4
		}
	fi

        Main 
	typeset -i ret=$?  
	if [ $ret -ne 0 ] ; then
		CleanUpFromAnyFailure 
	fi
	
	rm -f $SPOOLDB
	rm -f $ARCHIVEDB
	
	return $ret
fi

