#!/usr/bin/ksh -u

# @(#) pkgpatch.sh 1.78 99/09/09 SMI

# Copyright (c) 1994-1999 Sun Microsystems, Inc.  All Rights Reserved. Sun

#
# To understand this code, you should go to the bottom of this file
# and start from _GetOpts().  The function Main() is important in
# learning what the code really does.
#

# Convention	: I stole these comments from wos_patchchk(1) by Keith Lynch
# Global variables are all CAPITAL.
# Local variables are all lower.
# All functions starts with UpperCase.
# Internal only function start with "_".

[[ "$0" == /* ]] && typeset -r PRG=$0 || typeset -r PRG=$PWD/$0


typeset -r BINDIR=$(dirname $PRG)

. $BINDIR/patch.lib

typeset -r REPAC=$BINDIR/repac
typeset -r FASPAC=$BINDIR/faspac

typeset OPT_DEBUG='false'
typeset OPT_OVERWRITE='false'
typeset OPT_DISPLAY='false'
typeset OPT_FINALIZE='false'
typeset OPT_ARCHIVE='true'
typeset OPT_ORDER_PATCH='false'

typeset PATCHES=""
typeset PATCHPOOL=""
typeset PKGPOOL=""
typeset WORKINGDIR=""

typeset -r N_SPLIT=4
typeset IN_PARALLEL=${IN_PARALLEL:-true}


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

	typeset -r cmd=$*

	typeset error=0
	typeset msg=
	msg=$(eval $cmd 2>&1) || error=$?
	[ $error -ne 0 ] && {
		# You absolutely need to have double quote in the next line.
		echo "$msg" >&2
		PrintErr "[$cmd] - FAILED(return=$error)"
	}
	return $error
}


Parallel() {

	typeset -r HALF_SEP='#######################################'
	typeset -r SEPERATOR="$HALF_SEP$HALF_SEP"

	typeset -r prefix=/tmp/prlll.$$
	integer count=0
	typeset line=
	while read line ; do
		(
			[ "$OPT_DEBUG" == 'true' ] && {
				echo "$SEPERATOR"
				echo "COMMAND: '$line'"
			}
			eval "$line"
			echo "PARALLEL_EXIT=$?"
		) > $prefix.$count 2>&1 &
		count=count+1
	done

	wait 

	integer cnt=0
	integer error=0
	while (( $cnt < $count )) ; do
		typeset logfile=$prefix.$cnt
		[ "$(tail -1 $logfile)" != 'PARALLEL_EXIT=0' ] && error=error+1
		cat $logfile | sed -e '/PARALLEL_EXIT=/ d'
		rm -f $logfile
		cnt=cnt+1
	done

	[ "$OPT_DEBUG" == "true" ] && {
		echo "$SEPERATOR"
	}

	return $error
}


#
# This function is directly copied from the old code.
#
function Usage
{
cat << EOT

Usage:
        pkgpatch [-f] [-o] [-n] -d <PatchRepository> -k <PkgRepository>
              <patchID>... | ALL | <FileContainingPatchIDs>

        pkgpatch -k <PkgRepository> -p | -f

Options:
        -f   Finalize. Inserts the token "PATCHLIST=" into all packages that
             haven't been patched. This causes pkgadd to overwrite the
             PATCHLIST token in an installed pkg that has been patched and
             that needs to be replaced. 

        -o   Override. Allows for a patch to be applied to an image
             even if it contains non-generic class action scripts.
 
        -p   Display the patches applied to the image.

        -d   <PatchRepository>
             The path to where the patches are.
 
        -k   <PkgRepository>
             The path to where the pkgs to be patched ar located.

        -n   Do not archive the pkgs.

        -s   Order the patches.  Pkgpatch will sort the patches in
             ascending order first.  Then resolve the dependencies
	     by moving certain patch installation to the end.

	# "-a" option is obsoleted.  User can run the tool seperately
	# to perform the compression.
        # -a   Use repac as the archival utility. Faspac is the default.
EOT
	exit 1
}


_GetOpts() {

	while getopts ':opfgsd:k:n' char ; do
		case $char in
			o)	OPT_OVERWRITE='true'
				;;
			p)	OPT_DISPLAY='true'
				;;
			n)	OPT_ARCHIVE='false'
				;;
			d)	PATCHPOOL="$OPTARG"
				;;
			k)	PKGPOOL="$OPTARG"
				;;
			f)	OPT_FINALIZE='true'
				;;
			g)	OPT_DEBUG='true'
				;;
			s)	OPT_ORDER_PATCH='true'
				;;
			\?)	PrintErr "Unknown option $OPTARG"
				return 1
				;;
			:)	PrintErr "Option -$OPTARG requires a value!"
				return 1
				;;
		esac
	done

	shift OPTIND-1

	[ $# -ne 0 ] && PATCHES="$*"

	WORKINGDIR=$(pwd)

	if [ -z "$PKGPOOL" ] ; then
		PrintErr 'You must specify "-k" option'
		return 1
	fi

	if ! /usr/sadm/bin/valpath -a $PKGPOOL; then
		PKGPOOL="$(pwd)/$PKGPOOL"
	fi

	if [[ -n $PATCHPOOL ]]; then
		! /usr/sadm/bin/valpath -a $PATCHPOOL && \
			PATCHPOOL="$(pwd)/$PATCHPOOL"
	fi

	if [ "$OPT_FINALIZE" == 'false' -a "$OPT_DISPLAY" == 'false' ] ; then
		[ -z "$PATCHPOOL" ] && {
			PrintErr 'You must specify "-d" option'
			return 1
		}
		[ -z "$PATCHES" ] && {
			PrintErr 'You must specify at least one patch number'
			return 1
		}
	else
		if [ "$OPT_FINALIZE" == 'true' -a \
				"$OPT_DISPLAY" == 'true' ] ; then
			PrintErr "Option -p and -f are mutually exclusive!"
			return 1
		fi
		if [ ! -z "$PATCHES" ] ; then
			PrintErr 'You cannot specify any patch id' \
				'with -p or -f option'
			return 1
		fi
	fi
	
	return 0
}


_PatchHasNewClassActionScript() {
	:
}


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

	dump_pkginfo() {
		typeset -r pkginfo=$1
		pkgparam -f $pkginfo PKG ARCH VERSION || return $?
		return 0
	}

	typeset -r sparse_pkginfo=$1
	typeset -r pkgpool=$2

	typeset -r pkg=$(pkgparam -f $sparse_pkginfo PKG)
	typeset -r tmp1=$(_TmpFile aptp1)
	typeset -r tmp2=$(_TmpFile aptp2)

	dump_pkginfo $sparse_pkginfo > $tmp1 || return $?

	(
		cd $pkgpool		|| Assertion $? "Failed in change dir!"
		typeset pkginfo=
		for pkginfo in $pkg/pkginfo $pkg.*/pkginfo ; do
			[ ! -f $pkginfo ] && continue
			dump_pkginfo $pkginfo > $tmp2 || return $?
			if cmp -s $tmp1 $tmp2 ; then
				echo ${pkginfo%/pkginfo}
				return 0
			fi
		done
		return 1
	)
	integer error=$?

	rm -f $tmp1 $tmp2

	return $error
}


_UNSAFE_AddPatchToPkgpool() {

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

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

		typeset -r patch=$1
		typeset -r pkg=$2

		[ ! -d "$pkg" ] && {
			PrintErr "ASSERTION ERROR: cannot find directory" \
				"$dir in $PWD"
			return 1
		}

		#typeset -r patchbase=${patch%-*}
		#grep "PATCH_INFO_$patchbase" $pkg/pkginfo > /dev/null 2>&1
		grep "PATCH_INFO_$patch" $pkg/pkginfo > /dev/null 2>&1
		return $?
	}

	typeset -r patchpool=$1
	typeset -r patch=$2
	typeset -r pkgpool=$3
	typeset -r construct_zone=$4

	cd $patchpool/$patch		|| return $?
	integer error=0
	typeset sparse_pkginfo=
	typeset already_applied='false'
	typeset pkg_found='false'
	for sparse_pkginfo in */pkginfo ; do
		typeset pkg=
		if pkg=$(MatchingPkgInPkgpool $sparse_pkginfo \
				$construct_zone) ; then

			# Patch has been added in previously
			PatchHasAlreadyExistedInPkg $patch \
					$construct_zone/$pkg && {
				already_applied='true'
				continue
			}

			if MergePackage $PWD/$(dirname $sparse_pkginfo) \
					$pkgpool/$pkg $construct_zone ; then
				:
				# match_found should only be used to see if the
				# patch has already been applied.
				pkg_found='true'
			else
				error=error+1
				continue
			fi
		else
			PrintErr "(WARNING) Cannot find matching pkg" \
				"for $patch/${sparse_pkginfo%/pkginfo}"
		fi
	done
	$already_applied && {
		PrintErr "Patch $patch has already been applied."
		return 1
	}
	! $pkg_found && {
		PrintErr "ERROR: Cannot find matching pkgs for" \
			"sparse pkgs in $patch"
		PrintErr "Skipping patch $patch"
		# This should be an error.  However, old code
		# does not return error.
		# error=error+1
	}
	return $error
}


GenPkgProto() {

	typeset -r pkg=$1

	/usr/bin/nawk -v pkg=$pkg '
		BEGIN {
			install = (pkg "/install/")
			root = (pkg "/root")
			reloc = (pkg "/reloc/")
		}
		$1 ~ /[#\!:]/	{ next }
		$2 ~ /[slp]/	{ printf("%s %s %s\n", $2, $3, $4) }
		$2 ~ /[fevd]/	{
			( $4 ~ /^\// ) ? tree = root : tree = reloc
			printf("%s %s %s=%s %s %s %s\n", $2, $3 , 
				$4, tree$4, $5, $6, $7) ;
		}
		$2 == "i"	{
			( $3 == "pkginfo" ) ? tree = "./" : \
					tree = install
			printf("%s %s=%s\n", $2, $3, tree$3)
		}'
}


#
# Fix 4136443
#
# Fix up the protofile if pkgmap in the patch sparse pkg contains a '?'
# in the attribute field.
# 
FixFileAttribute() {

	get_attribute() {
		typeset -r file_entry=$1
		typeset -r proto=$2
		typeset -r type=$3
		typeset -r filename=${file_entry%=*}
		typeset a_line=$(grep " $filename=" $proto)
		[ -z "$a_line" ] && {
			PrintErr "ERROR: $filename is a new entry, attribute" \
				"with '?' is not allowed"
			return 1
		}
		set $a_line
		integer error=0
		case "$type" in
			mode)	echo $4	;;
			owner)	echo $5	;;
			group)	echo $6	;;
			*)	echo ''
				error=1
		esac
		return $error
	}

	typeset -r sparse_proto=$1
	typeset -r proto=$2

	typeset -r tmpfile=$(_TmpFile ffpq)

	# No '?' exists in sparse_proto file at all
	! grep ' \?' $sparse_proto > /dev/null 2>&1 && return 0

	integer error=0

	# If we are here, there is at least one '?' character in
	# $sparse_proto file
	typeset line=
	while read line ; do

		set $line

		if [[ "$1" != [fevd] ]] ; then
			echo $line
		else
			typeset type=$1
			typeset class=$2
			typeset file_entry=$3
			typeset mode=$4
			typeset owner=$5
			typeset group=$6
	
			if [ "$mode" == '?' ] ; then
				mode=$(get_attribute $file_entry $proto \
						'mode') || {
					error=error+1
					break
				}
			fi
			if [ "$owner" == '?' ] ; then
				owner=$(get_attribute $file_entry $proto \
						'owner') || {
					error=error+1
					break
				}
			fi
			if [ "$group" == '?' ] ; then
				group=$(get_attribute $file_entry $proto \
						'group') || {
					error=error+1
					break
				}
			fi
			echo "$type $class $file_entry $mode $owner $group"
		fi
	done < $sparse_proto > $tmpfile

	if [ $error -eq 0 ] ; then
		mv $tmpfile $sparse_proto
	else
		rm -f $tmpfile
	fi
	return $error
}


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

	typeset -r patternfile=$1
	typeset -r datafile=$2

	typeset -r PATTERN_FILE_LIMIT=299

	if [ ! -r "$patternfile" ] ; then
		PrintErr "Cannot read file $patternfile"
		return 1
	fi
	if [ ! -r "$datafile" ] ; then
		PrintErr "Cannot read file $datafile"
		return 1
	fi

	integer linecount=$(cat $patternfile | wc -l)

	# if the pattern file is below the limit, there is no point
	# to go through the complicated loop.  This statment
	# is just a short cut, going through the loop will work just fine.
	#
	if [ $linecount -le $PATTERN_FILE_LIMIT ] ; then
		fgrep -v -f $patternfile $datafile
		return $?
	fi

	# if we are here, the pattern file has too many lines

	typeset -r prefix="$(_TmpFile bgfgrp)."
	split -l$PATTERN_FILE_LIMIT $patternfile $prefix 	|| return $?

	typeset -r tmpoutput=$(_TmpFile bggrpv1)
	typeset -r tmpdata=$(_TmpFile bggrpv2)

	cp $datafile $tmpdata					|| return $?

	typeset small_pattern=
	for small_pattern in ${prefix}* ; do
		fgrep -v -f $small_pattern $tmpdata > $tmpoutput || return $?
		mv $tmpoutput $tmpdata				|| return $?
	done

	cat $tmpdata				|| return $?

	rm -f ${prefix}* $tmpoutput $tmpdata	|| return $?

	return 0
}


#
# This function is borrowed (with lots of cleanup) from old pkgpatch
# tool written by Tim Knitter.
#
MergedPrototypeFile()
{
	PkgHasNoRelocTree() {
		[ "$OPT_DEBUG" == 'true' ] && set -x
	 
		typeset -r pkg=$1
	 
		# If only information files exist in pkgmap continue checking.
		typeset -r scriptPatch=$(awk '
			$1 ~ /#|\!|:/ { next } 
			$2 !~ /i/ { print }'  $pkg/pkgmap)
		[ -n "$scriptPatch" ] && return 1
	 
		# And one more check
		[ -d "$pkg/reloc" ]     && return 1
		[ -d "$pkg/root" ]      && return 1
	 
		return 0
	}

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

		typeset -r patchdir=$1

		cd $patchdir		|| return $?

		# All script patches must contain a prepatch script
		[ ! -f prepatch ] && return 1

		typeset infofile=
		for infofile in */pkginfo ; do
			typeset sparsepkg=${infofile%/pkginfo}
			[ ! -f "$sparsepkg/pkgmap" ] && continue
			PkgHasNoRelocTree $sparsepkg || return $?
		done

		return 0
	}

	DetermineIfScriptPatch() {
		Protect _UNSAFE_DetermineIfScriptPatch $@
		return $?
	}
	
	GenPatchProto() {
		typeset -r sparsepkg=$1
		if DetermineIfScriptPatch $sparsepkg/.. ; then
			GenPkgProto $sparsepkg | grep -v '(pkginfo|i\.none)'
		else
			GenPkgProto $sparsepkg | grep -v '^i '
		fi
		return 0
	}

	typeset -r sparse_pkg=$1
	typeset -r pkg=$2

	typeset -r protofile=$(_TmpFile gftaproto)
	[ ! -r "$sparse_pkg/pkgmap" ] && {
		PrintErr "Cannot read file $sparse_pkg/pkgmap"
		return 1
	}
	cat $sparse_pkg/pkgmap | GenPatchProto $sparse_pkg > $protofile

	# Fix 4136443
	FixFileAttribute $protofile $pkg/prototype	|| return $?

	# Produce a pattern file to pass to fgrep to see
	# if the object is already in the prototype file.
	# To ensure that we only remove the correct object
	# and not any symlink destinations we need to throw
	# spaces around the object.
	# "double-quote" is not needed - lwl.

	typeset -r patchobj=$(_TmpFile gftaptchobj)

	cat $protofile | awk ' { print $3 } '	|
		awk -F= ' { print $1 } '	|
		sed -e 's:^: :' -e 's:$:=:' > $patchobj

	# At this point we know if there are any 'i'nfo files
	# they need to be included in the prototype file.

	cat $protofile | awk ' $1 ~ /i/ { print $2 } '  |
	awk -F= ' { print $1 } ' >> $patchobj

	# For links (sym and hard) we need to account for them
	# replacing regular files (done above) and replacing
	# other links (done below).

 	cat $protofile | awk '$1 ~ /s|l/ { print $3 }'	|
		awk -F= '{ printf("%s=\n", $1) }' >> $patchobj

	Big_fgrep_v $patchobj $pkg/prototype >> $protofile	|| return $?

	# If there is a deletes file in the patches pkgmap file,
	# delete the entries in the prototype file.

	typeset -r delete_file=$sparse_pkg/install/deletes
	if [ -f "$delete_file" ] ; then
		# remove the entries from the pkgmap. These entries should
		# all be relative. No BASEDIR's.
		Big_fgrep_v $delete_file $protofile		|| return $?
	else
		cat $protofile
	fi

	rm -f $patchobj $protofile
}


ParseSparsePkginfo() {

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

	typeset -r pkginfo=$1
	typeset -r obsoletes=$(my_pkgparam $pkginfo OBSOLETES)
	typeset -r incompat=$(my_pkgparam $pkginfo INCOMPAT)
	typeset -r requires=$(my_pkgparam $pkginfo REQUIRES)
	typeset -r patchid=$(my_pkgparam $pkginfo PATCHID)
	echo "PATCH_INFO_$patchid=Installed: $(date) From: $(hostname)"	\
		"Obsoletes: $obsoletes"	\
		"Requires: $requires"	\
		"Incompatibles: $incompat"
}


MergedPkginfoFile() {

	typeset -r sparse_pkginfo=$1
	typeset -r target_pkginfo=$2

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

	AddToPATCHLIST() {
		typeset -r pkginfo=$1
		typeset -r patch=$2
		if grep "^PATCHLIST=" $pkginfo > /dev/null 2>&1 ; then
			cat $pkginfo | sed -e '/^PATCHLIST=/ s:$:'" $patch:" \
				-e "s:= :=:"
		else
			cat $pkginfo
			echo "PATCHLIST=$patch"
		fi
		return 0
	}

	[ ! -f $target_pkginfo ] && {
		PrintErr "ERROR: Unable to read $target_pkginfo"
		return 1
	}

	typeset -r patch=$(my_pkgparam $sparse_pkginfo PATCHID)
	[ -z "$patch" ] && {
		PrintErr "ERROR: <OEM>PATCHID does not exist in $sparse_pkginfo"
		return 1
	}
	AddToPATCHLIST $target_pkginfo $patch	|| return 1
	ParseSparsePkginfo $sparse_pkginfo	|| return 1
	return 0
}


PkgIsRepac_ed() {
	typeset -r pkg=$1
	(
		cd $pkg
		\ls . | egrep '(cpio|reloc.Z|root.Z)' >/dev/null 2>&1 
		return $?
	)
	return $?
}


PkgIsFaspac_ed() {
	typeset -r pkg=$1
	[ -d "$pkg/archive" ] && return 0 || return 1
}


UncompressRepac() {
	typeset -r pkg=$1
	silence $REPAC -s $pkg || return $?
	return 0
}


CompressRepac() {
	typeset -r pkg=$1
	silence $REPAC -c $pkg	|| return $?
	return 0
}


UncompressFaspac() {
	typeset -r pkg=$1
	(
		cd $(dirname $pkg)			|| return $?
		silence $FASPAC -s $(basename $pkg)	|| return $?
		return 0
	)
	return $?
}


CompressFaspac() {
	typeset -r pkg=$1
	(
		cd $(dirname $pkg)			|| return $?
		silence $FASPAC $(basename $pkg)	|| return $?
		return 0
	)
	return $?
}

MergePackage() {

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

	_is_linked() {
		[ "$OPT_DEBUG" == 'true' ] && set -x
	        typeset -r pkg=$1
	        integer links=$(\ls -al $pkg/pkgmap | awk '{print $2}')
	        [ $links -ne 1 ] && return 0 || return 1
	}

	_break_link() {
		[ "$OPT_DEBUG" == 'true' ] && set -x
		typeset -r pkg=$1
		typeset -r newpkg=$pkg.$$
		mkdir $newpkg || return $?
		(
			cd $pkg || return $?
			silence "find . | cpio -pdum ../$(basename $newpkg)" \
				|| return $?
			return 0
		) || return $?
		rm -rf $pkg     || return $?
		mv $newpkg $pkg || return $?
		return 0
	}
	
	typeset -r sparse_pkg=$1
	typeset -r pkg=$2
	typeset -r construct_zone=$3

	typeset -r pkgdir=$(dirname $pkg)
	typeset -r pkgname=$(basename $pkg)

	typeset -r altpath=$construct_zone/$pkgname

	if [ ! -f "$altpath/prototype" ] ; then
		_is_linked $pkg && {
			_break_link $pkg
		}
		if PkgIsRepac_ed $pkg ; then
			UncompressRepac $pkg		|| return $?
			echo "repac" > $altpath/compress_method
		elif PkgIsFaspac_ed $pkg ; then
			UncompressFaspac $pkg			|| return $?
			cp $pkg/pkginfo $altpath/pkginfo        || return $?
			echo "faspac" > $altpath/compress_method
		else
			: # Do nothing
		fi

		cat $pkg/pkgmap | GenPkgProto \
			$pkg > $altpath/prototype	|| return $?
	fi

	integer error=0

	typeset -r tmpfile=$(_TmpFile mptf)

	MergedPkginfoFile $sparse_pkg/pkginfo \
		$altpath/pkginfo > $tmpfile		|| error=error+1
	mv $tmpfile $altpath/pkginfo			|| error=error+1

	MergedPrototypeFile $sparse_pkg $altpath > $tmpfile || error=error+1
	mv $tmpfile $altpath/prototype			|| error=error+1

	[ "$error" -ne 0 ] && {
		PrintErr "ERROR: Failed to generate prototype/pkginfo file"
		rm -f $tmpfile
	}

	return $error
}


MakePackage() {
	typeset -r pkgpool=$1
	typeset -r construct_pkg=$2

	typeset -r pkgname=$(basename $construct_pkg)

	typeset -r pkgpath=$pkgpool/$pkgname
	typeset -r tmpdir=$pkgpath.$$

	echo "Making $pkgname ..."

	mkdir $tmpdir						|| return $?
	silence pkgmk -o -d $tmpdir \
		-f $construct_pkg/prototype $pkgname		|| return $?
	mv $pkgpath $pkgpath.delete				|| return $?
	mv $tmpdir/$pkgname $pkgpool				|| return $?
	rm -rf $pkgpath.delete					|| return $?
	rmdir $tmpdir						|| return $?
	return 0
}


_UNSAFE_InsertNullPATCHLIST() {

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

	typeset -r pkgpool=$1

	update_pkgmap() {
		[ "$OPT_DEBUG" == 'true' ] && set -x
		typeset -r pkg=$1
		typeset -r pkginfo_sum=$(sum $pkg/pkginfo | awk '{print $1}')
		typeset -r pkginfo_sz=$(wc -c $pkg/pkginfo | awk '{print $1}')

		typeset -r tmpfile=/tmp/pkgmap.$$
		nawk -v pi="$pkginfo_sz" -v pm="$pkginfo_sum" '
			$3 ~ /pkginfo/ {
				printf("%s %s %s %s %s %s\n", \
					$1, $2, $3, pi, pm, $6) 
			}
			$3 !~ /pkginfo/ {
				print
			}' $pkg/pkgmap > $tmpfile	|| return $?
		mv -f $tmpfile $pkg/pkgmap		|| return $?
		return 0
	}

	#
	# WARNING: Do *NOT* simply do:
	#	echo 'PATCHLIST=' >> $pkg/pkginfo
	# directly.  The pkginfo files can be hardlinked together.
	#
	add_null_pkglist() {
		[ "$OPT_DEBUG" == 'true' ] && set -x
		typeset -r pkg=$1
		typeset -r tmpfile=/tmp/null_pkglist.$$
		cp $pkg/pkginfo $tmpfile		|| return $?
		echo 'PATCHLIST=' >> $tmpfile		|| {
			rm -f $tmpfile
			return 1
		}
		mv $tmpfile $pkg/pkginfo		|| return $?
		return 0
	}

	pkgs_without_PATCHLIST() {
		[ "$OPT_DEBUG" == 'true' ] && set -x
		typeset -r all_pkgs=$(_TmpFile pwoP1)
		typeset -r pkgs_with_patchlist=$(_TmpFile pwoP2)
		\ls */pkginfo | sort > $all_pkgs	|| return $?
		grep -l 'PATCHLIST=' $(< $all_pkgs) > \
			$pkgs_with_patchlist		|| return $?
		comm -23 $all_pkgs $pkgs_with_patchlist	|| return $?
		return 0
	}

	cd $pkgpool	|| return $?
	typeset pkginfo_files=
	pkginfo_files=$(pkgs_without_PATCHLIST)		|| return $?
	integer error=0
	typeset pkginfo=
	for pkginfo in $pkginfo_files ; do
		typeset pkgname=${pkginfo%/pkginfo}
		echo "Insert null PATCHLIST to $pkgname ..."
		add_null_pkglist $pkgname		|| error=error+1
		update_pkgmap $pkgname			|| error=error+1
	done
	return $error
}


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

	typeset -r biglist=$1
	shift 1
	typeset -r cmd_with_opts=$*

	[ ! -r "$biglist" ] && {
		PrintErr "Cannot read file $biglist"
		return 1
	}

	integer error=0

	if [ "$IN_PARALLEL" == 'true' ] ; then
		# WARNING: DO NOT MISS OUT THE DOT AT THE END
		typeset -r prefix="$(_TmpFile split.list)."
		integer count=$(cat $biglist | wc -l)
		integer each_count="((count+N_SPLIT-1)/N_SPLIT)"

		split -l$each_count $biglist $prefix	|| return $?

		(
			integer error=0
			# We need to open a new process here so that
			# the env var "IN_PARALLEL=false" settting will 
			# not corrupt the parent process.  
			# This is RECURSION.  NICE!!
			export IN_PARALLEL=false
			for file in ${prefix}* ; do
				echo _ExecuteCmdWithList $file $cmd_with_opts
			done | Parallel || error=$?

			return $error
		)	|| error=$?

		rm -f ${prefix}*
	else
		typeset line=
		while read line ; do
			$cmd_with_opts $line	|| error=error+1
		done < $biglist
	fi

	return $error
}


_UNSAFE_RemakeAllPkgs() {

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

	typeset -r pkgpool=$1
	typeset -r construct_zone=$2

	[ ! -d $construct_zone ]	&& return 1

	cd $pkgpool			|| return $?

	typeset -r content_list=$(_TmpFile bld_pkgs)
	ls $construct_zone/*/prototype | 
		sed -e 's:/prototype::' > $content_list

	if [ ! -s "$content_list" ] ; then
		PrintErr "WARNING: There are no pkgs to make."
		return 0
	fi

	# The double quote can be skipped.  It is here to show that
	# they need to be paired up.
	_ExecuteCmdWithList $content_list "MakePackage $pkgpool" || error=$?

	rm -f $content_list

	return $error
}


#
# WARNING:
# The old code does not check what compression method it used,
# it is possible for the old code to uncompress with repac and
# compress it back using faspac.
# This new code will uncompress and compress package using
# the same faspac or repac.
#
RecompressPkg() {

	typeset -r pkgpool=$1
	typeset -r compress_method_file=$2

	typeset -r dir=$(dirname $compress_method_file)
	typeset -r pkgname=$(basename $dir)

	if [ ! -r "$compress_method_file" ] ; then
		PrintErr "Cannot read file $compress_method_file"
		return 1
	fi

	typeset -r archive_method=$(< $compress_method_file)

	if [ "$archive_method" == 'repac' ] ; then
		typeset command=CompressRepac
	elif [ "$archive_method" == 'faspac' ] ; then
		typeset command=CompressFaspac
	else
		Assertion 1 "Invalid keyword ($archive_method)," \
			"only repac|faspac allowed"
	fi

	echo "Compressing $pkgname ..." 

	$command $pkgpool/$pkgname		|| return $?

	return 0
}


_UNSAFE_RecompressAllPkgs() {

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

	typeset -r pkgpool=$1
	typeset -r construct_zone=$2

	cd $pkgpool		|| return $?

	typeset -r content_file=$(_TmpFile zmthd)

	\ls $construct_zone/*/compress_method > $content_file 2> /dev/null 

	[ ! -s "$content_file" ] && {
		PrintErr "WARNING: There are no pkgs to re-compress"
		return 0
	}

	integer error=0

	# The double quote can be skipped.  It is here to show that
	# they need to be paired up.
	_ExecuteCmdWithList $content_file "RecompressPkg $pkgpool" || error=$?

	rm -f $content_file

	return $error
}


DisplayPatches() {

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

	SpaceToComma() {
		sed -e 's: :, :g'
	}
	show_column() {
		typeset -r line=$1
		typeset -r column=$2
		echo "$line" | cut -d'|' -f $column | SpaceToComma
	}

	typeset -r pkgpool=$1
	typeset -r flatdb=$(_TmpFile dp_db)

	DB ParseDatabase $pkgpool > $flatdb || return $?

	#
	# This is not the fastest way to output the data, this is
	# definitely less code to write.
	#
	typeset patchid=
	cut -d '|' -f 2 $flatdb | sort -u | while read patchid ; do
		typeset lines=$(grep "pkginfo|$patchid" $flatdb)
		typeset a_line=$(echo "$lines" | head -1)
		typeset obs=$(show_column "$a_line" $OBSOLETE_COLUMN)
		typeset req=$(show_column "$a_line" $REQUIRED_COLUMN)
		typeset incomp=$(show_column "$a_line" $INCOMPAT_COLUMN)

		set $(echo "$lines" | cut -d '|' -f 1 | sed -e 's:/pkginfo::')
		typeset pkgs=$(echo "$@" | SpaceToComma)
		echo "Patch: ${patchid#PATCHID=*} Obsoletes: $obs" \
			"Requires: $req Incompatibles: $incomp" \
			"Packages: $pkgs"
	done

	rm -f $flatdb

	return 0
}


PromptUserWhetherToRemoveDir() {

	typeset -r dir=$1

	typeset -r help="The $dir directory is where \
			pkgpatch keeps temporary data."
	typeset -r prompt="The $dir directory already exists due to a \
			previously failed attempt. \
			Do you want to overwrite it? Yes or No"

	typeset -r ans=$(/usr/bin/ckyorn -p "$prompt" -h "$help")

	if [[ "$ans" = "n" || "$ans" = "q" ]]; then
		PrintErr "The $dir has been saved."
		PrintErr "       Please remove it and restart."
		return 1
	else
		/usr/bin/rm -fr $dir
		mkdir $dir		|| return $?
	fi
	return 0
}


_UNSAFE_ClonePkgEnvironment() {

	typeset -r pkgpool=$1
	typeset -r clone_dir=$2

	if [ ! -d $clone_dir ] ; then
		PrintErr "Directory $clone_dir does not exist!"
		return 1
	fi

	cd $pkgpool	|| return $?

	if ! ls */pkginfo > /dev/null 2>&1 ; then
		PrintErr "No packages found in $pkgpool"
		return 1
	fi

	# The check above is needed. If find fails it doesn't return
	# 1 due to the pipe to cpio

	echo "Initializing build area ..."

	silence "find */pkginfo | cpio -pdum $clone_dir" || return $?

	return 0
}


Main() {

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

	date

	[ $# -le 2 ] && {
		PrintErr 'Incorrect number of arguments specified in Main()'
		return 1
	}

	typeset -r patchpool=$1
	typeset -r pkgpool=$2
	shift 2
	typeset patches=$*

	integer error=0

	Protect _UNSAFE_StaticPatchTestIsGood $patchpool $patches || \
		error=error+1

	Protect _UNSAFE_PatchesAreTooComplex $patchpool $patches && {
		if [ "$OPT_OVERWRITE" == 'true' ] ; then
			PrintErr "WARNING: One or more patches are too" \
					"complex."
			PrintErr "         With overwrite (-o) turned on," \
					"this tool"
			PrintErr "         is ignoring the" \
					"complexity of the patches"
			PrintErr "         and proceeding with freshbiting them"
		else
			PrintErr "ERROR: One or more patches are too" \
					"complex, you may need"
			PrintErr "       to invoke command with "-o" option."
			error=error+1
		fi
	}

	if [ "$OPT_ORDER_PATCH" == 'true' ] ; then
		patches=$(SortPatches $patches)			|| error=error+1
		patches=$(OrderPatches $pkgpool $patchpool \
			$patches)				|| error=error+1
	fi

	if [ $error -ne 0 ] ; then
		PrintErr "One or more errors have occured, program quitting!"
		return $error
	fi

	typeset -r construct_zone=$pkgpool/construction
	if [ -d "$construct_zone" ] ; then
		rm -fr $construct_zone/*
		#PromptUserWhetherToRemoveDir $construct_zone	|| return $?
	else
		mkdir $construct_zone	|| return $?
	fi

	Protect _UNSAFE_ClonePkgEnvironment $pkgpool $construct_zone || \
		return $?

	for patch in $patches ; do

		echo "Processing $patch ..."

		DB PatchPassedCompatiblityTests $patchpool/$patch	\
			$construct_zone 	|| return $?

		Protect _UNSAFE_AddPatchToPkgpool $patchpool $patch $pkgpool \
			$construct_zone 	|| error=error+1
	done

	[ "$error" -ne 0 ] && return $error

	[[ "$(domainname)" == *sun* ]] && date

	Protect _UNSAFE_RemakeAllPkgs $pkgpool $construct_zone	|| return $?

	[[ "$(domainname)" == *sun* ]] && date

	if $OPT_ARCHIVE ; then
		Protect _UNSAFE_RecompressAllPkgs $pkgpool \
			$construct_zone || return $?
	fi

	[[ "$(domainname)" == *sun* ]] && date

	rm -rf $construct_zone					|| return $?

	[ "$OPT_DEBUG" == 'true' ] && date

	return 0
}

typeset -i args=$#; (( $args < 1 )) && Usage

_GetOpts $@ || {
	integer error=$?
	Usage
	return $error
}

if [ "$OPT_DISPLAY" == 'true' ] ; then
	DisplayPatches $PKGPOOL		|| return $?
	return 0
fi

# bugid 4257444
#if ! IsRootUser ; then
	#PrintErr 'You must be root to execute this script'
	#return 1
#fi

if [ "$OPT_FINALIZE" == 'true' ] ; then

	Protect _UNSAFE_InsertNullPATCHLIST $PKGPOOL || return $?
	return 0

else

	# bugid 4257444
	#if ! IsRootUser ; then
		#PrintErr 'You must be root to execute this script'
		#return 1
	#fi

	if [ "$PATCHES" == 'ALL' ] ; then
		PATCHES=$(
			cd $PATCHPOOL		|| return $?
			ls -d [0-9]*-??		|| return $?
			return 0
		) || return $?
	elif [ -f "$PATCHES" ] ; then
		if [ ! -r "$PATCHES" ] ; then
			PrintErr "Cannot read file $PATCHES"
			return 1
		else
			typeset -r patch_list=$(cat $PATCHES)
			if [ -z "$patch_list" ] ; then
				PrintErr "Warning: no patches are specified" \
					"in $PATCHES"
				return 0
			else
				PATCHES="$patch_list"
			fi
		fi
	else
		# Do nothing, $PATCHES has already contained the name 
		# of all patch ids
		: 
	fi

	integer error=0
	Main $PATCHPOOL $PKGPOOL $PATCHES	|| error=$?

	if [ $error -eq 0 ] ; then
		PrintErr "Succeeded in applying patch(es) to packages"
	else
		PrintErr "Failed to apply patch(es) to packages"
	fi

	return $error
fi

return 0
