#!/usr/bin/ksh -h
#
# ident	"@(#)dcpatch.sh	1.26	04/04/08 SMI"
#
# Copyright (c) 2004 Sun Microsystems, Inc.  All Rights Reserved.
# Use is subject to license terms
#

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

GREP=/usr/bin/grep
SED=/usr/bin/sed
NAWK=/usr/bin/nawk

LN=/usr/bin/ln
PATCHADD=/usr/sbin/patchadd
PATCHRM=/usr/sbin/patchrm
DDO=/usr/sadm/lib/wbem/ddo

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 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"
# .patchDB is not longer created by patchadd.
# Therefore create one using patchadd -p called dcpatchDB.
typeset -r PATCHDBFILE="$INSTALLEDPATCHDIR/dcpatchDB"
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
	return 0
}

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

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

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}.$!"
}

TmpFileExists() {
	typeset -r prefix=$1
	[ -f "/tmp/${prefix}.$$" ] && return 0 || return 1
}

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
}

# Used only for -C options which is not called from smosservice add
# Originally dcpatch -C should have been called from smosservice add
# to create the copy of areas, accorging to autoclient authors

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
	    $GREP -s '^Patch:[ 	]*'$patch'[ 	][ 	]*' $root$PATCHDBFILE > /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=$($SED 's?^Patch:[ 	]*\([^ 	][^ 	]*\)[ 	][ 	]*.*$?\1?' $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
}

# (taken from patchadd 2.53)
# Description:
#   Compare any version string delimited with dots.
#   I.e: 2.5.1 and 2.10
#
# Parameters:
#   $1 version 1
#   $2 comparator (-gt, -ge...)
#   $3 version 2
#
# Returns:
#   0 if comparison is true
#   1 if not

function compare_version
{
        [ "$OPT_DEBUG" == 'true' ] && set -x

        typeset v1 v2 IFS='.' op=$2
        set -A v1 $1
        set -A v2 $3
        typeset -i i=0 diff=0 n=${#v1[*]}

        [[ ${#v2[*]} -gt $n ]] && n=${#v2[*]}

        while [[ $i -lt $n ]]; do
                [[ $(( diff = ${v1[$i]:-0} - ${v2[$i]:-0} )) -ne 0 ]] && break
                (( i = i + 1 ))
        done

        eval "[[ $diff $op 0 ]]" && return 0 || return 1
}

GetPatchOSRelease() {
        [ "$OPT_DEBUG" == 'true' ] && set -x
        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
		if compare_version $os -gt "2.5.1"; then
			echo $os
		fi
        done
}

CreateDatabaseEntries() {
        [ "$OPT_DEBUG" == 'true' ] && set -x
        typeset -r patchdir="$1"
	typeset -r patchid=$(basename $patchdir)
	typeset oslist=$(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
	for os in $oslist; do
		if compare_version $os -gt "2.6"; then
			osrelease=$(echo $os | cut -f 2,3 -d.)
		fi

        	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
	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:mud' 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
                                ;;
                        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"
        typeset -r contextdb="$3"

        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 SameVersionOfPatchExists $addpatchid $contextdb ; 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 PatchIsObsoletedByAnExistingPatch $addpatchid $contextdb ; 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 NewerVersionOfPatchExists $addpatchid $contextdb ; 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 IncompatiblePatchExists $addpatch $contextdb  ; then
                OutputError "LM_11120" "LM_11121" "$addpatchid"
		return 6
	fi

        if ! DB RequiredPatchExists $addpatch $spooldb $contextdb ; 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
		OutputWarning "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=$($SED 's?^Patch:[ 	]*\([^ 	][^ 	]*\)[ 	][ 	]*.*$?\1?' $root$PATCHDBFILE | $GREP -v "Version" | 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 $($SED 's?^Patch:[ 	]*\([^ 	][^ 	]*\)[ 	][ 	]*.*$?\1?' $root$PATCHDBFILE | $GREP -v "Version")
		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
	/usr/bin/perl -e '
	    use File::Find;
	    sub dumpstats {
		$myf="$File::Find::name";
		@s = stat( "$_" );
		print "$myf $s[2] $s[4] $s[5] $s[7] $s[9]\n" unless( $myf != /^\.$/);
	    }
	    $DIR = $ARGV[0];
	    finddepth(\&dumpstats, $DIR);
	    ' "."
    }
}

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

    diff $fileindex1 $fileindex2 | $NAWK '/^>/ && $2 !~ /dcfile/ {print $2}'
}

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
		# Use array instead of awk
		set -A orderedarray $ordered
		typeset -i i=$(( ${#orderedarray[*]} - 1 ))
		while [ $i -ge 0 ] ; do
		    echo ${orderedarray[i]}
		    i=$((i -= 1))
		done
	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")

	# Don't truncate the backoutlog file for every patch
	# append if it was already created
	typeset backoutlog
	if ! TmpFileExists backoutlog ; then
		backoutlog=$(TmpFile backoutlog)
	fi

        for patch in $backoutpatches ; do
	    if IsManagedPatch "$target" "$patch" ; then
		if ! UpRevOfPatchIsInstalled $target $patch ; then
		    echo "# $PATCHRM $options $patch" >> $backoutlog
		    $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
        done
	installpatches=$(OrderPatchesByRequirements "$installpatches" \
		"$spooldb" "$target" "forward")
	# Don't remove the backoutlog unless synch was successful
	[[ -s $backoutlog ]] || rm -f $backoutlog

	# Don't truncate the installlog file for every patch
	# append if it was already created
	typeset installlog
	if ! TmpFileExists installlog ; then
		installlog=$(TmpFile installlog)
	fi

        for patch in $installpatches ; do
	    if ! UpRevOfPatchIsInstalled $target $patch ; then
		echo "# $PATCHADD $options $spooldir/$patch" >> $installlog
		$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
        done
	# Don't remove the installlog unless synch was successful
	[[ -s $installlog ]] || rm -f $installlog

	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

	if compare_version $osversion -gt "2.6"; then
		osversion=$(echo $osversion | cut -f 2,3 -d.)
	fi

        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}')

	if compare_version $osversion -gt "2.6"; then
		osversion=$(echo $osversion | cut -f 2,3 -d.)
	fi

        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 $CONTEXTDB ; 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

}

# Make sure the patchadd .patchDB file is up to date
# This needs to be hacked since the "copyof" directory
# may not exist yet. patchadd -p used on /export/Solaris_8
# if the server is Solaris_8, will use the server's
# patchDB file, so create a "copyof" link to the service
# /export/Solaris_<ver> and use that.
#
UpdatePatchDB() {

    [ "$OPT_DEBUG" == 'true' ] && set -x

    # No services
    if [ ! -d $OSSERVICES ] ; then
	return 0
    fi

    typeset svc
    typeset svc_copy

    # We assume that OSSERVICES are /export/<service> patchadd does
    for svc in $OSSERVICES ; do
	if [ ! -d $svc ] ; then
	    continue
	fi
	# svc_copy ends up as /export/.copyofSolaris_<ver>
	svc_copy=$(dirname $svc)/.copyof$(basename $svc)

	# If no "copyof" create a link to the service /export/Solaris_<ver>
	if [ ! -d $svc_copy ] ; then
	    $LN -s $svc $svc_copy > /dev/null 2>&1
	    [ $? ] || continue
	fi

	# make sure the patch directory exists
	mkdir -p $svc_copy/$INSTALLEDPATCHDIR

	# Update the service's patchDB file
	# Make the "No patches installed" message does not get saved.
	$PATCHADD -p -S $(basename $svc_copy) | \
		nawk '$1 == "Patch:" {print $0}'> \
		$svc_copy/$PATCHDBFILE 2> /dev/null

	# Remove the link
	[ -h $svc_copy ] && rm -f $svc_copy

    done

    # If there are services there should always be clones
    # But if there aren't return
    # We assume CLONEAREAS are /export/root/clone/Solaris_<ver>/<machine_class>
    if [ ! -d $CLONEAREAS ] ; then
	return 0
    fi

    # Update the clone's patchDB file

    typeset cln_mc
    typeset cln_copy

    for cln_mc in $CLONEAREAS ; do
	cln_copy="$(dirname $cln_mc)/.copyof$(basename $cln_mc)"

	if [ ! -d $cln_copy ] ; then
	    $LN -s $cln_mc $cln_copy > /dev/null 2>&1
	    [ $? ] || continue
	fi

	# make sure the patch directory exists
	mkdir -p $cln_copy/$INSTALLEDPATCHDIR

	# Update the patchDB file for the clone
	$PATCHADD -p -R $cln_copy | \
		nawk '$1 == "Patch:" {print $0}'> \
		$cln_copy/$PATCHDBFILE 2> /dev/null

	# Remove the link
	[ -h $cln_copy ] && rm -f $cln_copy

    done

    return 0
}

#
# START
#

#
# Only one instance of dcpatch may be running
# at any one time
#
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
    }

# I think what they are saying here is that you cannpt update and
# synchronize at the same time
#
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)
		typeset -r CONTEXTDB=$(TmpFile contextdb)

		# generate the var/sadm/patch/.patchDB files
		# for the clone area and service
		UpdatePatchDB

        	CreatePatchDatabase $SPOOLDIR > $SPOOLDB           || {
			OutputError "LM_11164" "LM_11165" "$SPOOLDB"
			return 4
		}

		if [ -n "$ADD_PATCH" ] ; then
			$DDO services_processing /export > $CONTEXTDB
		fi

        	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

