#!/bin/ksh
#
#	"@(#)admclientpatch.sh 1.35 98/10/23"
#
# Copyright (c) 1995 by Sun Microsystems, Inc.
#
# This script handles software installation for diskless clients
# and AutoClients (patches).  It deals with obsoleted patches as well as
# required and incompatible patches.
#
# Patches:
# This script patches diskless and AutoClient root areas as well
# as the OS services (/usr) used by those clients.
#
# NOTE: Local variables are lower case and globals are upper case.
#

PATH=/usr/sbin:/usr/bin:/sbin
SPOOL_DIR=/opt/SUNWadmd/Patches
INDEX=$SPOOL_DIR/Index
UNSPOOL_DIR=$SPOOL_DIR/Archive
TMP_SPOOL_DIR=$SPOOL_DIR/.requires
INST_RELEASE=/var/sadm/softinfo/INST_RELEASE
FILE_BASE=/tmp/admclientpatch.$$
LOGFILE=${FILE_BASE}.log
LOGFILE_TMP=${FILE_BASE}.log.tmp
ADD_PATCH_OUT=${FILE_BASE}.addpatch.out
REMOVE_PATCH_OUT=${FILE_BASE}.removepatch.out
PRINTF_HOST="\t%-25s "
PRINTF_PATCHID="\t%-15s "
GET_PATCH_BASE="s/\(.*\)[^0-9][0-9]*/\1/p"
GET_PATCH_VERS="s/.*[^0-9]\([0-9]*\)/\1/p"
UPDATED_HOSTS=
UPDATED_SERVICES=
ARGS=$*
PATCHADD=/usr/sbin/patchadd
PATCHRM=/usr/sbin/patchrm

TEXTDOMAIN=SUNW_ADMCLIENTPATCH
TEXTDOMAINDIR=/opt/SUNWadm/2.3/classes/locale
export TEXTDOMAIN TEXTDOMAINDIR

################################ set_traps ####################################
alias set_traps="\
    trap stop_dots	0; \
    trap trap_exit	1; \
    trap trap2		2; \
    trap trap_exit	3; \
    trap trap_exit	14; \
    trap trap_exit	15; \
    "

############################# print_usage #####################################
#
# Print Usage message
#	
print_usage() {
    gettext "\n\
Usage:\n\
       admclientpatch -a <patch_directory> [-s] [-v]\n\
       admclientpatch -c\n\
       admclientpatch -p\n\
       admclientpatch -r <patchid> [-s]\n\
       admclientpatch -s [-v]\n"
gettext "Options:\n\
	-a dir/<patchid>\n\
	    Add the specified patch in the spool area\n\
	-c\n\
	    - List current diskless clients & AutoClients and the patches\n\
	      installed on each\n\
	    - List OS services and the patches installed in each\n\
	-p\n\
	    Long listing of currently spooled patches\n\
	-r <patchid>\n\
	    Remove the specified patch from the spool area\n\
	-s\n\
	    Synchronize the installed patches with the spooled patches\n\
	-v\n\
	    Verbose (more details as to what is happening)\n\
"
}

############################# initialize ######################################
#
# Initialize various things.
#
initialize() {
    typeset id=

    # Initialize option flags
    AFLAG=0
    CFLAG=0
    PFLAG=0
    RFLAG=0
    SFLAG=0
    VFLAG=0
    DFLAG=
    BIGCFLAG=0

    # Initialize some global variables
    PATCHID=
    PATCHDIR=
    DOTPID=0
    CLIENT_ROOT=
    CLIENT_OS_PROD=
    CLIENT_OS_REL=
    CLIENT_ARCH=
    SERVER_OS_PROD=
    SERVER_OS_REL=
    SERVER_ARCH=

    # Make sure we are running as root.
    id=`/usr/bin/id | awk '{ print $1 }'`
    if [ "$id" != "uid=0(root)" ]; then
	gettext "Sorry, but this command can only be run as root.\n"
	error_exit
    fi

    # Initialize some output msgs
    DONE=`gettext " done"`
    SKIPPING=`gettext "skipping; not applicable"`
    SKIPPING_ALREADY_APPLIED=`gettext "skipping; already applied"`
    SKIPPING_NOT_MANAGED=`gettext "skipping -- not managed"`
    NO_SPOOLED=`gettext "No patches currently spooled."`
    INTERRUPTED=`gettext "interrupted"`
    PARSE_ERROR_R=`gettext "The -r option requires a numeric patchid; for example123456-01"`
    PARSE_ERROR_A=`gettext "The -a option requires a patch directory; for example <path>/123456-01"`
    REMOVING=`gettext "Removing patches installed but not spooled.\nTo find currently spooled patches, run 'admclientpatch -p'"`
    INSTALLING=`gettext "Installing patches spooled but not installed"`
}

############################# noerror_exit ####################################
noerror_exit() {
    stop_dots
    /bin/rm -f ${FILE_BASE}*
    exit 0
}

############################# error_exit ######################################
error_exit() {
    stop_dots
    /bin/rm -f ${FILE_BASE}*
    exit 1
}

############################### exit_intr #####################################
exit_intr() {
    gettext "Program terminated by user request.\n"
    error_exit
}

################################ trap_exit ####################################
trap_exit() {
    echo "${INTERRUPTED}\c"
    error_exit
}

############################# start_dots ######################################
# Start a background process that just prints dots so the user that
# work IS being done.  If dots are already being printed, don't start any
# additional processes.
#
start_dots() {
    if [ $DOTPID -eq 0 ]; then
	(while :
	 do
	    echo ".\c"
	    sleep 10
	 done)&
	DOTPID=$!
    fi
}

############################# stop_dots ######################################
stop_dots() {
    if [ $DOTPID -ne 0 ]; then
	kill $DOTPID >/dev/null 2>&1
	kill $DOTPID >/dev/null 2>&1
	echo ""
    fi
    DOTPID=0
}

############################### verbose #######################################
# Print msg if in verbose mode
# Input
#	$1 = msg to print
verbose() {
    if [ $VFLAG -eq 1 ]; then
	echo "$1"
    fi
}

################################# trap2 #######################################
trap2() {
    echo "\c"
}

########################## get_archs_from_patch ###############################
# This routine returns a list of the archs patched in the specified patch
# Input
#	$1 = patchdir
#
get_archs_from_patch() {
    RET=`sed -n 's/ARCH=*\([a-z0-9]*\)\.*.*/\1/p' $1/*/pkginfo | sort | uniq`
}

################################# copy_dir_trap ###############################
#
# This routine should only be called if the user interrupted the script
# during a copy.  If this is the case, remove the directory that we being
# created and then exit.
#
copy_dir_trap() {
    echo "${INTERRUPTED}\c"
    stop_dots
    gettext "Cleaning up.\n"
    /bin/rm -rf $copy_dst_dir
    exit 2
}

################################# copy_dir ####################################
#
# Copy the specified src dir to the specified dst dir.  Set up trap to
# catch an interrupt (^C) so we can clean up.
# Input:
#	$1 = src dir
#	$2 = dst dir
#
copy_dir() {
    typeset copy_src_dir=$1
    typeset s=`basename $copy_src_dir`
    typeset copy_dst_dir=$2/$s

    mkdir -p $copy_dst_dir
    trap copy_dir_trap 2
    /bin/cp -rp $copy_src_dir/. $copy_dst_dir
    set_traps
}

############################## find_clients ###################################
#
# Find all diskless & AutoClients.
# Outputs:
#	CLIENTS		= array of clients found
#	ROOTS		= array of found clients roots
#	NUM_CLIENTS	= number of clients found
#
find_clients() {
    typeset dir= clientname= match= dfstab_entry= client= rootdir=

    #
    # Find all of the clients in /export/root (ignoring non-root dirs)
    # NOTE: We can't rely on the dfstab containing everything we need
    #	    so we still need to perform this check.
    #
    if [ -d /export/root/*/etc ]; then
	cd /export/root
	for dir in */etc
	do
	    clientname=`dirname $dir`
	    CLIENTS="$CLIENTS $clientname"
	    ROOTS="$ROOTS /export/root/$clientname"
	done
    fi

    # get all of the shared roots from dfstab
    #	- check to see if we've found them in /export/root
    #	- get root directory for client
    #	
    match=0
    for dfstab_entry in `grep root= /etc/dfs/dfstab | sed  's/.*root=//' | \
	awk '{ print $1 }'`
    do
	# Make sure not already included in list
	for client in $CLIENTS
	do	
	    if [ "$dfstab_entry" = "$client" ]; then
		match=1
		break
	    fi
	done
	if [ $match -eq 1 ]; then
	    match=0
	    continue
	fi
	rootdir=`grep rw=${dfstab_entry},root=${dfstab_entry} /etc/dfs/dfstab | \
	    sed  's/.*root=//' | awk '{ print $NF }'`
	for dir in $rootdir
	do
	    # Filter out the 'swap' entry if client is diskless as well as
	    # filter out non-root dirs (assume a real client has an etc dir)
	    if [ -d ${dir}/etc ]; then
		CLIENTS="$CLIENTS $dfstab_entry"
		ROOTS="$ROOTS $dir"
	    fi
	done
    done
}

############################ get_client_info ##################################
# This routine goes into the specified client root dir to extract out
# the OS name, the OS rev, and the arch of the client.
# Inputs:
#	$1 = root dir
# Outputs:
#	CLIENT_OS_PROD
#	CLIENT_OS_REL
#	CLIENT_ARCH
get_client_info() {
    typeset root=$1
    if [ "${CLIENT_ROOT}" != "$root" ]; then
	get_host_info $root
	CLIENT_OS_PROD=$OS_PROD
	CLIENT_OS_REL=$OS_REL
	CLIENT_ARCH=$ARCH
	CLIENT_ROOT=$root
    fi

    # Small optimzation in case $root is / we know the client is the
    # server.
    if [ "$root" = "/" ]; then
	SERVER_OS_PROD=$OS_PROD
	SERVER_OS_REL=$OS_REL
	SERVER_ARCH=$ARCH
    fi
}

############################ get_server_info ##################################
# This routine goes into the server's root dir to extract out
# the OS name, the OS rev, and the arch of the server.
#
# Outputs:
#	SERVER_OS_PROD
#	SERVER_OS_REL
#	SERVER_ARCH
get_server_info() {
    if [ "${SERVER_OS_PROD}X" = "X" ]; then
	get_host_info /
	SERVER_OS_PROD=$OS_PROD
	SERVER_OS_REL=$OS_REL
	SERVER_ARCH=$ARCH
    fi
}

############################ get_host_info ####################################
#
# This routine goes into the specified host/client root dir to extract out
# the OS name, the OS rev, and the arch of the client.
# Inputs:
#	$1 = root dir
# Outputs:
#	OS_PROD
#	OS_REL
#	ARCH
#
get_host_info() {
    typeset root=$1

    if [ -f $root/var/sadm/softinfo/INST_RELEASE ]; then
	OS_PROD=`sed -n 's/^OS=//p' \
		    $root/var/sadm/softinfo/INST_RELEASE`
	OS_REL=`sed -n 's/^VERSION=//p' \
		    $root/var/sadm/softinfo/INST_RELEASE`
	if [[ "$OS_REL" > "2.7" ]]; then
	    OS_REL=$(echo $OS_REL | sed 's/2.//g')
	fi
	ARCH=`pkginfo -l -R $root SUNWcar | \
		    sed -n 's/.*ARCH: *\([a-z0-9]*\)\..*/\1/p'`
    else
	OS_PROD=
	OS_REL=
	ARCH=
    fi
}

############################ is_patch_applicable ##############################
#
# This routine checks if the specified patch is applicable to the specified
# OS Release and architecture.  This is done by looking in the patch index
# file.
#
# Input:
#	$1 = patchid
#	$2 = OS release
#	$3 = architecture
#
is_patch_applicable() {
    typeset patchid=$1
    typeset os_rel=$2
    typeset arch=$3
    typeset awk_file=/tmp/awk.$$
    typeset ret=

cat > $awk_file << EOF
BEGIN {found=0}
/BEGIN_PATCH $patchid/ {found = 1}
/OS_RELEASE=/ {if(found && match(\$2, "$os_rel[^.]|$os_rel$") == 0) found = 0}
/ARCH_LIST=/ {if(found && \$2 != "$arch") found = 0}
/END_PATCH/ {if(found) print found; found=0}
END {print found}
EOF
    ret=`nawk -F= -f $awk_file $INDEX`
    /bin/rm $awk_file
    return $ret
}

################################## is_managed_patch ###########################
#
# This routine looks in the specified root area for the specified patch
# to see if it is 'managed' (installed by this script).  The reason
# we need to do this is so we don't backout a patch installed, by hand,
# by the user.
# Input:
#	$1 = root dir
#	$2 = patchid
#
is_managed_patch() {
    typeset rootdir=$1
    typeset patchid=$2

    if [ -f $rootdir/var/sadm/patch/$patchid/.admclientpatch_managed ]; then
	return 1
    else
	return 0
    fi
}

################################# make_patch_managed ##########################
#
# This routine makes the specified patch 'managed' by this script.
#
make_patch_managed() {
    typeset rootdir=$1
    typeset patchid=$2

    $DFLAG touch $rootdir/var/sadm/patch/$patchid/.admclientpatch_managed \
	> /dev/null 2>&1
}

################################ patch_client_trap ############################
patch_client_trap() {
    echo "${INTERRUPTED}\c"
    stop_dots
    SIG2=1
}

############################## patch_client ###################################
#
# This routine applies the specified patch to the specified client root dir.
# Input:
#	$1 = client root dir to patch
#	$2 = patch to apply
# Return
#	 1 = problem trying to add patch
#	 0 = no problems
#
patch_client() {
    typeset root=$1
    typeset patchid=$2
    typeset patch_client_ret=0
    typeset status=

    SIG2=0
    trap patch_client_trap 2
    cd $SPOOL_DIR/$patchid
    # If patch already applied, don't do anything except make it managed
    # by this script.  If the patch was installed by hand (not managed)
    # and then the user spooled the patch it is not managed.
    if [ -d $root/var/sadm/patch/$patchid ]; then
	make_patch_managed $root $patchid
	verbose "${SKIPPING_ALREADY_APPLIED}\c"
	return 0
    fi

    # Check the patch index file to see if this patch is applicable to
    # this client (same OS release and same architecture).
    #
    get_client_info $root
    is_patch_applicable $patchid $CLIENT_OS_REL $CLIENT_ARCH
    if [ $? -eq 1 ]; then
	if [ -x $PATCHADD ]; then
	    $DFLAG $PATCHADD -R $root . > $ADD_PATCH_OUT 2>&1
	else
	    $DFLAG ./installpatch -R $root . > $ADD_PATCH_OUT 2>&1
	fi
	status=$?
	case $status in
	    0)  verbose "${DONE}\c"
		make_patch_managed $root $patchid
		UPDATED_HOSTS="$UPDATED_HOSTS `basename $root`";;
	    8)	verbose "${SKIPPING}\c";;
	    *)	if [ $SIG2 -eq 0 ]; then
		    /bin/cat $ADD_PATCH_OUT
		    patch_client_ret=1
		fi;;
	esac
	/bin/rm -f $ADD_PATCH_OUT
    else
	echo "${SKIPPING}\c"
    fi
    set_traps
    return $patch_client_ret
}

############################## unpatch_client_trap ############################
unpatch_client_trap() {
    echo "${INTERRUPTED}\c"
    stop_dots
    SIG2=1
}

############################# unpatch_client ##################################
#
# This routine backs out the specified patch from the specified client root dir.
# Input:
#	$1 = client root dir to patch
#	$2 = patch to apply
# Return
#	 1 = problem trying to remove patch
#	 0 = no problems
#
unpatch_client() {
    typeset root=$1
    typeset patchid=$2
    typeset unpatch_client_ret=0
    typeset status=

    # Depending on the patch, it may not have been applied to this root
    # since this root area may not have had the packages installed that we
    # patched.
    SIG2=0
    trap unpatch_client_trap 2
    if [ -d $root/var/sadm/patch/$patchid ]; then
	is_managed_patch $root $patchid
	if [ $? -eq 0 ]; then
	    verbose "$SKIPPING_NOT_MANAGED"
	else
	    if [ -x $PATCHRM ]; then
	        $DFLAG $PATCHRM -R $root $patchid > $REMOVE_PATCH_OUT 2>&1
	    else
	        $DFLAG $root/var/sadm/patch/$patchid/backoutpatch \
		-R $root $patchid > $REMOVE_PATCH_OUT 2>&1
	    fi
	    status=$?
	    case $status in
		0)  verbose "${DONE}\c"
		    UPDATED_HOSTS="$UPDATED_HOSTS `basename $root`";;
		*)  if [ $SIG2 -eq 0 ]; then
			/bin/cat $REMOVE_PATCH_OUT
			unpatch_client_ret=1
		    fi
	    esac
	    /bin/rm -f $REMOVE_PATCH_OUT
	fi
    else
	gettext "patch not installed\n"
    fi
    set_traps
    return $unpatch_client_ret
}

############################### patch_service_trap ############################
patch_service_trap() {
    echo "${INTERRUPTED}\c"
    stop_dots
    exit_intr
}

################################ patch_service ################################
#
# This routine applies the specified patch to the specified OS service (/usr)
# if the service exists.
# Input:
#	$1 = patchid
#	$2 = service to patch
# Return
#	 1 = problem trying to add patch
#	 0 = no problems
#
patch_service() {
    typeset patchid=$1
    typeset service=$2
    typeset patch_service_ret=0
    typeset status= my_arch= arch= to_patch_dir= rootdir= opts= updated_host=

    # If service doesn't exist, just return.
    if [ ! -d /export/$service ]; then
	return 0
    fi

    cd $SPOOL_DIR/$patchid

    trap patch_service_trap 2

    # Check to see how we need to patch the service.  If the service
    # is the same used by the server, then we just patch the server
    # (which will patch the service too) else we patch the service.
    get_server_info
    get_patch_info .

    my_arch=0
    for arch in $PATCH_ARCHS
    do
	if [ "$SERVER_ARCH" = "$arch" ]; then
	    my_arch=1
	    break
	fi
    done

    if [ $my_arch -eq 1 -a $service = "${SERVER_OS_PROD}_${SERVER_OS_REL}" ]; then
	to_patch_dir=/var/sadm/patch/$patchid
	rootdir=/
	opts=
	updated_host=`uname -n`
    else
	to_patch_dir=/export/$service/var/sadm/patch/$patchid
	rootdir=/export/$service
	opts="-S $service"
	updated_host=
    fi
    if [ ! -d $to_patch_dir ]; then
        if [ -x $PATCHADD ]; then
	    $DFLAG $PATCHADD ${opts} . > $ADD_PATCH_OUT 2>&1
	else
	    $DFLAG ./installpatch ${opts} . > $ADD_PATCH_OUT 2>&1
	fi
	status=$?
    else
	status=a
    fi
    case $status in
	a)      verbose "${SKIPPING_ALREADY_APPLIED}\c";;
	2)	verbose "${SKIPPING}\c";;
	0)	verbose "${DONE}\c"
		SERVICE_PATCHED_OK=1
		make_patch_managed $rootdir $patchid
		UPDATED_HOSTS="$UPDATED_HOSTS $updated_host"
		for arch in $PATCH_ARCHS
		do
		    UPDATED_SERVICE="$UPDATED_SERVICE ${service}_$arch"
		done;;
	8)	verbose "${SKIPPING}\c";;
	*)	/bin/cat $ADD_PATCH_OUT
		patch_service_ret=1;;
    esac
    /bin/rm -f $ADD_PATCH_OUT
    set_traps
    return $patch_service_ret
}

############################## unpatch_service_trap ###########################
unpatch_service_trap() {
    echo "${INTERRUPTED}\c"
    stop_dots
    exit_intr
}

################################ unpatch_service ##############################
#
# This routine backs out the specified patch from the specified OS
# service (/usr).
# Input:
#	$1 = patchid
#	$2 = service from which to back out the patch
#
unpatch_service() {
    typeset patchid=$1
    typeset service=$2
    typeset unpatch_service_ret=0
    typeset status= patchdir= rootdir= opts= updated_host=

    # Check to see how we need to backout the patch from the service.  If the
    # service is the same used by the server, then we just backout the
    # patch from the server (which will backout the patch from the service
    # too) else we just backout the patch from the service.

    trap unpatch_service_trap 2
    get_server_info
    if [ $service = "${SERVER_OS_PROD}_${SERVER_OS_REL}" ]; then
	patchdir=/var/sadm/patch/$patchid
	rootdir=/
	opts=
	updated_host=`uname -n`
    else
	patchdir=/export/$service/var/sadm/patch/$patchid
	rootdir=/export/$service
	opts="-S $service"
	updated_host=
    fi
    if [ -d $patchdir ]; then
	is_managed_patch $rootdir $patchid
	if [ $? -eq 0 ]; then
	    status=a
	else
	    if [ -x $PATCHRM ]; then
	        $DFLAG $PATCHRM $opts $patchid > $REMOVE_PATCH_OUT 2>&1
	    else
	        $DFLAG $patchdir/backoutpatch $opts $patchid \
		    > $REMOVE_PATCH_OUT 2>&1
	    fi
	    status=$?
	fi
    else
	status=0
    fi
    case $status in
	a)      verbose "$SKIPPING_NOT_MANAGED\c";;
	0 | 2 ) verbose "$DONE\c"
		UPDATED_HOSTS="$UPDATED_HOSTS $updated_host"
		UPDATED_SERVICE="$UPDATED_SERVICE ${service}";;
	*)	/bin/cat $REMOVE_PATCH_OUT
		unpatch_service_ret=1;;
    esac
    /bin/rm -f $REMOVE_PATCH_OUT
    set_traps
    return $unpatch_service_ret
}

############################# create_spool_dir ################################
#
# Create the patch spool directory as well as the 'unspooled' patch directory.
#
create_spool_dir() {
    mkdir $SPOOL_DIR >/dev/null 2>&1
    if [ $? -ne 0 ]; then
	if [ ! -d $SPOOL_DIR ]; then
	    echo `gettext "Error: You don't have permission to create the\npatch spool directory"` "($SPOOL_DIR)."
	    error_exit
	fi
    fi
    mkdir $UNSPOOL_DIR >/dev/null 2>&1
}

############################# validate_spool_dir ##############################
#
# Validate that the patch spool directory exists
#
validate_spool_dir() {
    if [ ! -d $SPOOL_DIR ]; then
	return 1
    else
       return 0
    fi
}

########################### validate_spool_dir_w ##############################
#
# Validate that the patch spool directory is writable
#
validate_spool_dir_w() {
    typeset tmpfile=$SPOOL_DIR/$$

    touch $tmpfile >/dev/null 2>&1
    if [ $? -ne 0 ]; then
	echo `gettext "Error: The patch spool directory is not writable:"` $SPOOL_DIR
	error_exit
    else
	rm $tmpfile
    fi
}

######################### add_to_patch_index ##################################
#
# This routine adds the specified patch to the patch index file
# Inputs:
#	$1 = patchdir
#
add_to_patch_index() {
    typeset patchdir=$1
    typeset patchid=`basename $patchdir`

    get_patch_info $patchdir
cat >> $INDEX << EOF
BEGIN_PATCH $patchid
   OS_PRODUCT=$PATCH_OS_PROD
   OS_RELEASE=$PATCH_OS_REL
   ARCH_LIST=$PATCH_ARCHS
   IMPORTANCE=required
END_PATCH
EOF
}

####################### remove_from_patch_index ###############################
#
# This routine removes the specified patch from the patch index file
# Inputs:
#	$1 = patchid
#
remove_from_patch_index() {
    typeset patchdir=$1
    typeset patchid=`basename $patchdir`
    typeset awk_file=/tmp/awk.$$

cat >> $awk_file << EOF
BEGIN {found=0}
/BEGIN_PATCH $patchid/ {found = 1}
{if(!found) print \$0}
/END_PATCH/ {found = 0}
EOF
    nawk -f $awk_file $INDEX > /tmp/INDEX.$$
    mv /tmp/INDEX.$$ $INDEX
    rm -rf $awk_file
}

############################ is_obsoleted_by ##################################
#
# This routine looks to see if the 1st patch is obsoleted by the 2nd patch
# Input:
#	$1 = patchdir #1
#	$2 = patchdir #2
# Returns:
#	0 -> if patch #1 is NOT obsoleted by patch #2
#	1 -> if patch #1 is obsoleted by patch #2
#
is_obsoleted_by() {
    typeset patchid1=`basename $1`
    typeset base1=`echo $patchid1 | sed -n $GET_PATCH_BASE`
    typeset obsoletes=`sed -n 's/.*OBSOLETES=//p' $2/*/pkginfo |\
		    tr ",\012" "  " | grep -c $base1`
    return $obsoletes
}

############################## is_spool_space #################################
#
# This routine checks to see if there is enough free space available
# in $SPOOL_DIR for the specified patch
# Inputs:
#	$1 = patchdir
#
is_spool_space() {
    typeset patchdir=$1
    DU=`/usr/bin/du -ks $patchdir | awk '{print $1}'`
    AVAIL=`/usr/bin/df -k $SPOOL_DIR | tail -1 | awk '{print $4}'`
    if [ $DU -gt $AVAIL ]; then
	return 0
    else
	return 1
    fi
}

############################# spool_patch #####################################
#
# Copy the specified patch into the spool area.
# Inputs:
#	$1: the directory that allegedly contains our patch
#
spool_patch() {
    typeset patchdir=$1
    typeset patchid=`basename $patchdir`
    typeset base=`echo $patchid | sed -n $GET_PATCH_BASE`
    typeset new_vers=`echo $patchid | sed -n $GET_PATCH_VERS`
    typeset replace_patch= spooled_vers= patch_requires= spooled_patch=
    typeset replace=

    trap trap_exit 2
    cd $SPOOL_DIR
    # Check to see if patch already spooled in either the official location or
    # the temporary location
    if [ -d $SPOOL_DIR/$patchid ]; then
	gettext "This patch is already spooled -- skipping.\n"
	noerror_exit
    fi
    if [ -d $TMP_SPOOL_DIR/$patchid ]; then
	gettext "Skipping -- This patch is already temporarily spooled pending addition of\n\t    required patches: "
	get_patch_requires "$TMP_SPOOL_DIR/$patchid/*/pkginfo"
	for req in $PATCH_REQUIRES
	do
	    echo "$req (or later) \c"
	done
	echo ""
	noerror_exit
    fi

    # Check to see if this patch is an up/back vers of an already spooled patch
    if [ -d $SPOOL_DIR/${base}* ]; then
	spooled_vers=`echo $SPOOL_DIR/${base}?* | sed -n $GET_PATCH_VERS`
	if [ $spooled_vers -gt $new_vers ]; then
	    gettext "More recent version of patch already spooled -- skipping\n"
	    noerror_exit
	else
	    # New patch must be an up level patch (replacing back level).
	    replace_patch=`echo ${base}?*`
	fi
    fi

    # Check if patch requires another patch.  If so, make sure the other patch
    # is spooled.  If not, set a flag since we will need to deal with this
    # patch in a special way (we "hide" it until the required patches are
    # installed.)
    #
    get_patch_requires "$patchdir/*/pkginfo"
    patch_requires="$PATCH_REQUIRES"
    if [ $VFLAG -eq 1 ]; then
	gettext "Checking if required patches are available "
	start_dots
    fi
    check_required "$patch_requires"
    required_flag=$?
    if [ $VFLAG -eq 1 ]; then
	stop_dots
    fi

    # If patches already spooled...
    if [ $VFLAG -eq 1 ]; then
	gettext "Checking to see if any currently spooled patches obsolete this patch "
	start_dots
    fi
    cd $SPOOL_DIR
    get_spooled_patches_list
    # Check if patch is obsoleted by currently spooled patch
    for spooled_patch in $PATCHES
    do
	# Look in spooled patch to see what patches it obsoletes and if
	# it specifically obsoletes any version of the current patch
	is_obsoleted_by $patchdir $SPOOL_DIR/$spooled_patch
	if [ $? -ne 0 ]; then
	    if [ $VFLAG -eq 1 ]; then
		stop_dots
	    fi
	    echo `gettext "The following currently spooled patch obsoletes the new patch:"` $spooled_patch
	    error_exit
	fi
    done
    if [ $VFLAG -eq 1 ]; then
	stop_dots
    fi

    # Now check if new patch obsoletes any spooled patches
    if [ $VFLAG -eq 1 ]; then
	gettext "Searching for any currently spooled patches obsoleted by this patch "
	start_dots
    fi
    for spooled_patch in $PATCHES
    do
	# Look in spooled patch to see what patches it obsoletes
	is_obsoleted_by $SPOOL_DIR/$spooled_patch $patchdir 
	if [ $? -ne 0 ]; then
	    replace_patch="$replace_patch $spooled_patch"
	fi
    done
    if [ $VFLAG -eq 1 ]; then
	stop_dots
    fi

    trap trap2 2
    is_spool_space $patchdir
    if [ $? -eq 1 ]; then
	if [ $required_flag -eq 0 ]; then
	    gettext "Warning: this patch requires other patches and although this patch will be\n\
    spooled, it will not be available until the following patches are\n\
    also spooled: "
	    echo $patch_requires
	    echo `gettext "Copying the following patch into the pending spool area: "` "${patchid} \c"
	    start_dots
	    copy_dir $patchdir $TMP_SPOOL_DIR
	    echo "${DONE}\c"
	    stop_dots
	else
	    echo `gettext "Copying the following patch into spool area: "` "${patchid} \c"
	    start_dots
	    copy_dir $patchdir $SPOOL_DIR
	    add_to_patch_index $PATCHDIR
	    echo "${DONE}\c"
	    stop_dots
	    # Check to see if by adding this patch we've satisfied a
	    # requirement for another patch (in the TMP_SPOOL_DIR area).
	    check_if_required
	fi
    else
	echo `gettext "Error: Not enough free disk space available in: "` $SPOOL_DIR
	echo `gettext "Required (kilobytes): "` $DU
	echo `gettext "Available           : "` $AVAIL
	noerror_exit
    fi

    cd $SPOOL_DIR
    for replace in $replace_patch
    do
	echo `gettext "This new patch replaces the old patch: "` ${replace}
	remove_from_patch_index $replace
	# To be safe, make sure there isn't already a dir there.
	if [ -d $UNSPOOL_DIR/$replace ]; then
	    /bin/rm -rf $UNSPOOL_DIR/$replace
	fi
	mv $replace $UNSPOOL_DIR
    done
}

############################# unspool_patch ###################################
#
# Unspool the specified patch.  
# Inputs:
#	$1: the patchid to unspool
#
unspool_patch() {
    typeset patchid=$1
    typeset base=`echo $patchid | sed -n $GET_PATCH_BASE`
    typeset patchdir=$SPOOL_DIR/$patchid
    typeset spool_dir=$SPOOL_DIR
    typeset required= req= required_base= patch= obsoleted= obsoleted_patches=
    typeset count= restore_patch= restore= need_to_restore= archived_patch=
    typeset obs= tbase= i=

    cd $spool_dir
    # Make sure patch to remove is spooled.
    if [ ! -d $patchid ]; then
	if [ ! -d $TMP_SPOOL_DIR/$patchid ]; then
	    gettext "Error: Patch not spooled\n"
	    error_exit
	else
	    spool_dir=$TMP_SPOOL_DIR
	    patchdir=$TMP_SPOOL_DIR/$patchid
	fi
    fi

    # Make sure none of the remaining patches requires the patch being
    # removed.  If so, inform the user the other patch(es) need to be removed
    # first.

    get_patch_requires "$SPOOL_DIR/*/*/pkginfo"
    required=0
    # Walk though list of required patches making sure none refer to the patch
    # the user has specified be removed.
    for req in $PATCH_REQUIRES
    do
	required_base=`echo $req | sed -n $GET_PATCH_BASE`
	if [ $required_base -eq $base ]; then
	    required=1
	fi
    done
    if [ $required -eq 1 ]; then
	# The specified patch is required by other patches.  Determine which
	# other patches and report them back to the user.
	gettext "Error: This patch can't be unspooled since the following patches\nrequire it: "
	for patch in `ls $SPOOL_DIR`
	do
	    if [ -f $patch/*/pkginfo ]; then
		ret=`sed -n 's/SUNW_REQUIRES=\(.*\)/\1/p' $patch/*/pkginfo | grep -c $base`
		if [ $ret -gt 0 ]; then
		    echo "$patch \c"
		fi
	    fi
	done
	echo ""
	error_exit
    fi

    # Now come up with list of patches that need to be restored.

    # First find out which patches are/were obsoleted by the patch
    # being removed.
    obsoleted=`sed -n 's/.*OBSOLETES=//p' $patchdir/*/pkginfo | \
		    tr " ," "\012\012" | sort | uniq | tr "\012" " "`

    cd $UNSPOOL_DIR
    # See if there is a down level version of patch that has been archived.
    # If so, add it to the list of obsoleted patches
    if [ -d ${base}?* ]; then
	o=`echo ${base}?* | tr ' ' '\012' | sort -rn | head -1`
	obsoleted="$obsoleted $o"
    fi

    count=0
    # Now see if any of these patches (ignoring version) are archived.
    for patch in $obsoleted
    do
	# Ignore the patch version and just look for the base of the patchid
	tbase=`echo $patch | sed -n $GET_PATCH_BASE`
	if [ -d ${tbase}?* ]; then
	    count=`expr $count + 1`
	    obs=`echo ${tbase}?* | tr " " "\012" | sort -rn | head -1`
	    obsoleted_patches="$obsoleted_patches $obs"
	fi
    done

    # Special case if only one of the listed obsolete patches exists.
    if [ $count -eq 1 ]; then
	restore_patch=$obsoleted_patches
    else
	# Now we have a list of patches that exist AND were obsoleted by
	# the patch being deleted.  Now make sure that none of these
	# obsoletes each other so we don't restore too many patches.
	for archived_patch in $obsoleted_patches
	do
	    # For each archived(obsoleted) patch, look at all other patches
	    # to see if they obsolete the first one.
	    need_to_restore=1
	    for i in $obsoleted_patches
	    do
		# No need to see if a patch obsoletes itself
		if [ $archived_patch = $i ]; then
		    continue
		fi
		# If patch not already eliminated
		if [ $need_to_restore -eq 1 ]; then
		    is_obsoleted_by $archived_patch $i
		    if [ $? -eq 1 ]; then
			# Patch is obsoleted by archived patch, so make sure
			# it is not in the list of patches to restore.
			need_to_restore=0
		    fi
		fi
	    done
	    if [ $need_to_restore -eq 1 ]; then
		restore_patch="$restore_patch $archived_patch"
	    fi
	done
    fi

    # Make sure we only have one reference to each patch
    restore_patch=`echo $restore_patch | \
	tr " " "\012" | sort | uniq | tr "\012" " "`

    for restore in $restore_patch
    do
	echo `gettext "Note: The following obsoleted patch is being restored: "` $restore
	add_to_patch_index $restore
	# To be safe, make sure there isn't already a dir there.
	if [ -d $SPOOL_DIR/$restore ]; then
	    /bin/rm -rf $SPOOL_DIR/$restore
	fi
	mv $restore $SPOOL_DIR
    done

    echo `gettext "Removing the following patch from the spool area: "` "${patchid} \c"
    start_dots
    /bin/mv $patchdir ${patchdir}.being_deleted
    ( /bin/rm -rf ${patchdir}.being_deleted ) &
    remove_from_patch_index $patchid
    stop_dots
}

######################### get_patch_requires ##################################
# This routine looks in the specified files (patch/*/pkginfo) for the key word
# SUNW_REQUIRES.  All values are returned in global variable PATCH_REQUIRES.
#
get_patch_requires() {
    PATCH_REQUIRES=`sed -n 's/SUNW_REQUIRES=\(.*\)/\1/p' $1 | \
    	tr ' ,' '\012\012' | \
	sort | \
	uniq | \
	sed '/^$/d'`
}

############################ get_patch_info ###################################
#
# This routine returns info about the specified patch
# Input:
#	$1 = patchdir
# Output:
#	PATCH_PKGTYPES	= list of pkg types (usr, root, kvm, or ow)
#	PATCH_OS_PROD	= OS product name (currently just 'Solaris')
#	PATCH_OS_REL	= OS Release patch is specific to (2.4 for example)
#	PATCH_ARCHS	= Architectures patched (sparc, i386, ppc)
#	PATCH_REQUIRES	= list of patches required by this patch
#
get_patch_info() {
    typeset patchdir=$1

    if [ -d $patchdir ] ; then
	PATCH_PKGTYPES=`sed -n 's/SUNW_PKGTYPE=//p' \
		$patchdir/*/pkginfo | sort | uniq | tr \012 ' '`
	PATCH_OS_PROD="Solaris"
	if [ `sed -n 's/Solaris Release: //p' $patchdir/README* | tr , ' '| sed 's/_.*//'` == "7" ]; then
		PATCH_OS_REL=2.7
	else
		PATCH_OS_REL=`sed -n 's/Solaris Release: //p' $patchdir/README* | tr , ' '| sed 's/_.*//'`
	fi
	get_archs_from_patch $patchdir
	PATCH_ARCHS=$RET
        #PATCH_PLATFORMS=`sed -n 's/ARCH=*[a-z0-9]*\.*\(.*\)/\1/p' $patchdir/*/pkginfo | sort | uniq`
	get_patch_requires "$patchdir/*/pkginfo"
    else
	PATCH_PKGTYPES=
	PATCH_OS_PROD=
	PATCH_OS_REL=
	PATCH_ARCHS=
	PATCH_REQUIRES=
    fi
}

############################# validate_patch ##################################
#
# See if the given directory actually contains a valid patch.  This is
# done by looking for certain things in the patch directory.
# Inputs:
#	$1: the directory that allegedly contains our patch
#
validate_patch() {
    typeset patchdir=$1
    typeset patchid=`basename $patchdir`
    typeset os_release_list=

    # Verify specified patch dir actually exists.
    if [ ! -d $patchdir ]; then
	gettext "Error: The specified patch directory does not exist.\n"
	error_exit
    fi

    # Verify specified patch contains a README file.
    if [ ! -f $patchdir/README.$patchid ]; then
	gettext "Error: The specified directory does not contain a valid patch (no 'README.<patchid>' file).\n"
	error_exit
    fi

    # Verify specified patch contains a valid/parsible README file.
    os_release_list=`sed -n 's/Solaris Release: //p' $patchdir/README.$patchid | tr , ' '| sed 's/_.*//'`
    if [ "X" = "${os_release_list}X" ]; then
	gettext "Error: The specified directory does not contain a valid patch (no entry for 'Solaris Release'\nin README.<patchid>).\n"
	error_exit
    fi

    # Make sure patch is not for Solaris 1.X (SunOS 4.X)
    echo $os_release_list | grep 1\\. >/dev/null 2>&1
    if [ $? -eq 0 ]; then
	gettext "Error: This is not a Solaris 2.X patch.\n"
	error_exit
    fi

    # Make sure patch only patches one OS (unbundled patches usually patch > 1
    # release).
    count=0
    for c in $os_release_list
    do
	count=`expr $count + 1`
    done
    if [ $count -gt 1 ]; then
	gettext "Error: This patch appears to patch multiple OS's and can't be managed\n\
by this script.\n"
	error_exit
    fi

    # Make sure patch version only consists of digits and a dot.
    echo $os_release_list | egrep -i '[a-z]' >/dev/null 2>&1
    if [ $? -eq 0 ]; then
	msg=`gettext "Error: Can't determine the specific Solaris releases this patch\nis intended for.  Was expecting values of the form 2.3, 2.4 or 2.5.\nInstead found: "`
	echo "$msg $os_release_list"
	error_exit
    fi

    # Verify specified patch contains an 'installpatch' and 'backoutpatch'
    # script. Or, that /usr/sbin/patchadd and /usr/sbin/patchrm  exist.
    
    if [[ ! -f $patchdir/installpatch && ! -x $PATCHADD ]]; then
	gettext "Error: The specified directory does not contain a valid patch (no 'installpatch' script) "
	gettext "and /usr/sbin/patchadd does not exist (or is not executable).\n"
	error_exit
    fi
    if [[ ! -f $patchdir/backoutpatch && ! -x $PATCHRM ]]; then
	gettext "Error: The specified directory does not contain a valid patch (no 'backoutpatch' script) "
	gettext "and /usr/sbin/patchrm does not exist (or is not executable).\n"
	error_exit
    fi

    # Verify specified patch contains at least one pkginfo file.
    if [ ! -f $patchdir/*/pkginfo ]; then
	gettext "Error: The specified directory does not contain a valid patch (no 'pkginfo' file).\n"
	error_exit
    fi

    # Verify specified patch contains at least one pkgmap file.
    if [ ! -f $patchdir/*/pkgmap ]; then
	gettext "Error: The specified directory does not contain a valid patch (no 'pkgmap' file).\n"
	error_exit
    fi
}

########################## get_client_patches_list ############################
# This routine returns a list of the patches installed in the specified 
# client's root area.
# Input:
#	$1 = client's root dir
# Output:
#	CLIENT_PATCHES = list of patches installed in specified client's root
#
get_client_patches_list() {
    typeset root=$1
    typeset patchdir=$root/var/sadm/patch
    typeset cwd=

    CLIENT_PATCHES=

    # Check to see if there are patches installed on client
    if [ ! -d $patchdir ]; then
	return
    fi
    cwd=`pwd`
    cd $patchdir
    CLIENT_PATCHES=`ls`
    cd $cwd
}

########################## get_service_patches_list ###########################
# This routine returns a list of the patches installed in the specified 
# OS service.
# Input:
#	$1 = os service
# Output:
#	SERVICES_PATCHED = list of patches installed in specified OS service
#
get_service_patches_list() {
    typeset service=$1
    typeset patchdir= cwd= i=

    SERVICE_PATCHES=

    patchdir=/export/$service/var/sadm/patch

    # Check to see if there are patches installed in OS service
    if [ ! -d $patchdir ]; then
	return
    fi
    cwd=`pwd`
    cd $patchdir
    for i in `ls`; do
	if [ -d $i ]; then
	    SERVICE_PATCHES="$SERVICE_PATCHES $i"
	fi
    done
    cd $cwd
}

############################# check_if_required ##############################
# This routine checks to see if any spooled patches satisfy requirements of
# any temporarily spooled patches.  If so, make the temporarily spooled
# patches official.
#
check_if_required() {
    typeset patch= satisfy_requirements=0 required_base= required_vers=
    typeset installed_vers= installed= required= msg=

    # If no tmp spool dir, nothing needs doing.
    if [ ! -d $TMP_SPOOL_DIR ]; then
	return
    fi

    # For each temporarily spooled patch
    for patch in `ls $TMP_SPOOL_DIR`
    do
	satisfy_requirements=1
	if [ -f $TMP_SPOOL_DIR/$patch/*/pkginfo ]; then
	    get_patch_requires "$TMP_SPOOL_DIR/$patch/*/pkginfo"
	    # See if all of the required patches are available.
	    for required in $PATCH_REQUIRES
	    do
		required_base=`echo $required | sed -n $GET_PATCH_BASE`
		required_vers=`echo $required | sed -n $GET_PATCH_VERS`
		# See if the required patch or a later release is available
		for installed in $SPOOL_DIR/${required_base}*
		do
		    if [ -d $installed ]; then

			installed_vers=`basename $installed | sed -n $GET_PATCH_VERS`
			# Check to make sure the version of the spooled patch
			# is >= the required version
			if [ $installed_vers -lt $required_vers ]; then
			    satisfy_requirements=0
			fi
		    else
			# No matching directories
			satisfy_requirements=0
		    fi
		done
	    done
	fi
	# If all requirements are satisfied, make the tmp patch official
	if [ $satisfy_requirements -eq 1 ]; then
	    msg=`gettext "The following patch is being made official since all required patches\nare available: "`
	    echo "$msg $patch"
	    /bin/mv $TMP_SPOOL_DIR/$patch $SPOOL_DIR
	    add_to_patch_index $SPOOL_DIR/$patch
	fi
    done
}

############################## check_required #################################
# This routine checks to see if the listed required patches are in the spool
# area.
#
# Input:
#	$1 = patch, or patches, to check for
# Return:
#	0 = the required patch(es) are NOT in the spool area
#	1 = the required patch(es) are in the spool area
#
check_required() {
    typeset check_required_found=0
    typeset req= base= needed_vers= vers=

    if [ "${1}X" = "X" ]; then
	return 1
    fi
    for req in $1
    do
	# Check if required patch exists.
	if [ -d "$SPOOL_DIR/$req" ]; then
	    check_required_found=1
	else
	    # Listed required patch doesn't exist.  See if a later
	    # rev exists.
	    base=`echo $req | sed -n $GET_PATCH_BASE`
	    needed_vers=`echo $req | sed -n $GET_PATCH_VERS`
	    if [ -d $SPOOL_DIR/${base}* ]; then
		vers=`basename $SPOOL_DIR/${base}* | sed -n $GET_PATCH_VERS`
		if [ $vers -ge $needed_vers ]; then
		    check_required_found=1
		fi
	    fi
	fi
    done
    return $check_required_found
}

########################## get_spooled_patches_list ###########################
#
# This routine returns a list of the currently spooled patches.  This routine
# attempts to sort the patches in an order appropriate to be able to deal with
# patches that require other patches.
#
# Output:
#	PATCHES = list of currently spooled patches
#
get_spooled_patches_list() {
    typeset spooled_patches= spooled_patch= required= required_base=
    typeset available=

    PATCHES=
    validate_spool_dir
    if [ $? -eq 0 ]; then
	cd $SPOOL_DIR
	spooled_patches=
	for spooled_patch in `ls $SPOOL_DIR`
	do
	    # Assuming there may be other files in the spool dir,
	    # find those that look like real patches.
	    if [ -f $spooled_patch/*/pkginfo ]; then
		spooled_patches="$spooled_patches $spooled_patch"
	    fi
	done

	# Attempt to sort the patches in an order to deal with patches
	# requiring other patches.
	for spooled_patch in $spooled_patches
	do
	    get_patch_requires "$spooled_patch/*/pkginfo"
	    # If currently looked at patch requires some other patches, add
	    # them to the list before the current patch.  The code has to deal
	    # with the situation where the explicit patch rev may not exist,
	    # but a later rev does.
	    for required in $PATCH_REQUIRES $spooled_patch
	    do
		required_base=`echo $required | sed -n $GET_PATCH_BASE`
		ret=`echo "$PATCHES" | grep -c $required_base`
		if [ $ret -eq 0 ]; then
		    # Patch not already in list so add the installed version
		    # to the list.
		    available=`basename $SPOOL_DIR/${required_base}*`
		    PATCHES="$PATCHES $available"
		fi
	    done
	done
    fi
}

############################## find_diffs #####################################
# Given two sets of patch ids, return a list of the diffs
# Input
#	$1 = 
find_diffs() {
    typeset list1= list2= found=

    DIFFS=
    for list1 in $1
    do
	found=0
	for list2 in $2
	do
	    if [ $list1 = $list2 ]; then
		found=1
	    fi
	done
	# Patch installed but not spooled
	if [ $found -eq 0 ]; then
	    DIFFS="$DIFFS $list1"
	fi
    done
}

############################## display_updated ################################
# Display the list of hosts/services updates as well as msg to user telling
# them they need to reboot these machines as a result of the patches that
# were installed/backedout.
#
display_updated() {
    typeset updated_hosts=`echo $UPDATED_HOSTS | \
	tr " " "\012" | sort | uniq | tr "\012" " "`
    typeset updated_services=`echo $UPDATED_SERVICES | \
	tr " " "\012" | sort | uniq | tr "\012" " "`

    if [ "${updated_hosts}X" != " X" ]; then
	gettext "Note: The following machines were affected by these patches\nand should be rebooted: "
	echo $updated_hosts
    fi
    if [ "${updated_services}X" != " X" ]; then
	gettext "Note: The following services and all machines using these services\nwere affected by these patches and should be rebooted: "
	echo $updated_services
    fi
    echo ""
}

################################ parse_args ###################################
#
# Parse arguments
# Inputs:
#	$1, $2, etc. contain command line args
#
parse_args() {
    while [ ${1}X != "X" ]
    do
	case $1 in
	    -c) CFLAG=1
		shift;;
	    -a) AFLAG=1
		shift
		if [ "${1}X" = "X" ]; then
		    echo $PARSE_ERROR_A
		    print_usage
		    error_exit
		else
		    case $1 in
		        /*) PATCHDIR=$1;;
		        *)  PATCHDIR="`pwd`/$1";;
		    esac
		    PATCHID=`basename $PATCHDIR`
		    PATCHBASE=`echo $PATCHID | sed -n $GET_PATCH_BASE`
		    PATCHVER=`echo $PATCHID | sed -n $GET_PATCH_VERS`
		    if [ "${PATCHBASE}X" = "X" -o \
		         "${PATCHVER}X" = "X" ]; then
			echo $PARSE_ERROR_A
			print_usage
			error_exit
		    fi
		    shift
		fi;;
	    -p) PFLAG=1
		shift;;
	    -r) RFLAG=1
		shift
		if [ "${1}X" = "X" ]; then
		    echo $PARSE_ERROR_R
		    print_usage
		    error_exit
		else
		    PATCHID=$1
		    PATCHBASE=`echo $PATCHID | sed -n $GET_PATCH_BASE`
		    PATCHVER=`echo $PATCHID | sed -n $GET_PATCH_VERS`
		    if [ "${PATCHBASE}X" = "X" -o \
		         "${PATCHVER}X" = "X" ]; then
			echo $PARSE_ERROR_R
			print_usage
			error_exit
		    fi
		    shift
		fi;;
	    -s) SFLAG=1
		shift;;
	    -v) VFLAG=`expr $VFLAG + 1`;
		shift;;
	    # Undocumented options
	    -C) BIGCFLAG=1	# args: clientroot
		shift
		if [ "${1}X" = "X" ]; then
		    # Since this is an undocumented interface, just return
		    # an error code (this option should only be used
		    # by methods during new client creation.
		    error_exit
		else
		    ROOT=$1
		    shift
		fi;;
	    -d) DFLAG="echo"
		shift;;
	    -*|*)
		echo `gettext "Unknown option"` "'$1'";
		print_usage;
		error_exit;;
	esac
    done
}

################################ verify_args ##################################
#
# Verify that no more than one set of args was specified.
#

verify_args() {
    typeset error1=`gettext "Only one set of arguments can be used at a time.\n"`

    typeset no_s_sum=`expr $CFLAG + $PFLAG + $BIGCFLAG`
    typeset arflag=`expr $AFLAG + $RFLAG`
    typeset sum=`expr $no_s_sum + $arflag + $SFLAG`

    if [ $SFLAG -eq 0 ]; then
	if [ $no_s_sum -gt 1 -o $arflag -gt 1 ]; then
	    echo $error1
	    print_usage
	    error_exit
	fi
    else
	if [ $no_s_sum -eq 1 -o $arflag -gt 1 ]; then
	    echo $error1
	    print_usage
	    error_exit
	fi
    fi
    if [ $sum -eq 0 ]; then
	gettext "At least one argument must be specified (no default)\n"
	print_usage
	error_exit
    fi
}

###############################################################################
################################### main ######################################
###############################################################################

# Initialize various things.
set_traps
initialize

parse_args $ARGS
verify_args

############################################################# Handle -C option
# This option is only used, and invoked, by the hostmgr after creating
# a new diskless or AutoClient.  This option applies all applicable
# patches to the client.  In addition, if a patch exists that has not
# yet been applied to the OS service, this is done too, though in this
# case a warning needs to be returned to the user informing them that
# by patching the OS service it is possible that all clients of that
# service are now out of sync and need to be manually synchronized.
#
if [ $BIGCFLAG -eq 1 ]; then
    apply_to_service=`gettext "Applying the following patch to the OS service"`
    apply_to_client=`gettext  "Applying the following patch to the client    "`
    apply_to_server=`gettext  "Applying the following patch to the server    "`
    if [ ! -d $ROOT ]; then
	exit 1
    fi
    cd $SPOOL_DIR
    get_spooled_patches_list
    if [ "${PATCHES}X" = "X" ]; then
	noerror_exit
    fi

    # Get OS rev for client
    get_client_info $ROOT
    root_service_patch=0
    SERVICE_PATCHED_OK=0
    for patch in $PATCHES
    do
	cd $SPOOL_DIR
	# Get OS rev for patch
	get_patch_info $patch

	# Only look at patches that match the OS product name, the OS release
	# of the client being patched, and the arch.
	service_patched=
	root_patch=0
	for patch_os_rel in $PATCH_OS_REL
	do
	    if [ $CLIENT_OS_PROD = $PATCH_OS_PROD -a\
		 $CLIENT_OS_REL = $patch_os_rel -a\
		 $CLIENT_ARCH = $PATCH_ARCHS ]; then
		# See if the patch needs to be applied to the service area too.
		for pkgtype in $PATCH_PKGTYPES
		do
		    case $pkgtype in
			"usr" |\
			"ow")
			    service_patched=${PATCH_OS_PROD}_$patch_os_rel
			    p=`printf "%s %-15s: %s " \
				"$apply_to_service" \
				"$service_patched" \
				$patch`
			    if [ $VFLAG -eq 1 ]; then
				echo "$p\c"
				start_dots
				patch_service $patch $service_patched
				ret=$?	# Ignore ret code - output already seen
				stop_dots
			    else
				patch_service $patch $service_patched > \
					$LOGFILE_TMP 2>&1
				ret=$?
				if [ $ret -ne 0 ]; then
				    echo "$p" >> $LOGFILE
				    /bin/cat $LOGFILE_TMP >> $LOGFILE
				fi
				/bin/rm $LOGFILE_TMP
			    fi;;
			"root")
			    root_patch=1;;
			"kvm" |\
			*);;
		    esac
		done

		# Now finally get around to applying the patch to the root
		# area.
		client=`basename $ROOT`
		if [ "$client" = "/" ]; then
		    p=`printf "%s %-15s: %s " \
			"$apply_to_server" \
			"" \
			$patch`
		else
		    p=`printf "%s %-15s: %s " \
			"$apply_to_client" \
			$client \
			$patch`
		fi
		if [ $VFLAG -eq 1 ]; then
		    echo "$p\c"
		    start_dots
		    patch_client $ROOT $patch
		    ret=$?	# Ignore ret code - output already seen
		    stop_dots
		else
		    patch_client $ROOT $patch > $LOGFILE_TMP 2>&1
		    ret=$?
		    if [ $ret -ne 0 ]; then
			echo "$p" >> $LOGFILE
			/bin/cat $LOGFILE_TMP >> $LOGFILE
		    fi
		    /bin/rm $LOGFILE_TMP
		fi
	    fi
	done

	if [ "${service_patched}X" != "X" -a $root_patch -eq 1 ]; then
	    root_service_patch=1
	fi
    done

    # If VFLAG set, then the error output has already been seen.  If
    # not set, then output errors from LOGFILE
    if [ $VFLAG -eq 0 -a -f $LOGFILE ]; then
	/bin/cat $LOGFILE
    fi
    /bin/rm -f $LOGFILE
    # If a patch patched the service area AND it also patches
    # root pkgs, then we need to return a warning to the user.
    if [ $root_service_patch -eq 1 -a $SERVICE_PATCHED_OK -eq 1 ]; then
	if [ $ROOT = "/" ]; then
	    gettext "\n\
Warning: While applying patches to the server, a patch was applied to\n\
an OS service that may affect all clients of this service.  It is\n\
recommended that you first run 'admclientpatch -s' to make sure all clients\n\
are synchronized with the currently spooled patches and then reboot\n\
the server and all clients using this OS service.\n"
	else
	    gettext "\n\
Warning: While applying patches to this client, a patch was applied to\n\
its OS service that may affect all clients of this service.  It is\n\
recommended that you first run 'admclientpatch -s' to make sure all clients\n\
are synchronized with the currently spooled patches and then reboot\n\
the server and all clients using this OS service.\n"
	fi
    fi

    noerror_exit
fi

############################################################# Handle -r option
if [ $RFLAG -eq 1 ]; then
    validate_spool_dir

    echo `gettext "Unspooling the following patch: "` $PATCHID
    $DFLAG unspool_patch $PATCHID
fi

############################################################# Handle -c option
if [ $CFLAG -eq 1 ]; then
    find_clients
    trap trap_exit 2
    msg=`gettext "Patches installed"`
    gettext "Clients currently installed are:\n"
    if [ "${ROOTS}X" != "X" ]; then
	for root in $ROOTS
	do
	    client=`basename $root`
	    printf $PRINTF_HOST $client
	    get_client_info $root
	    echo "$CLIENT_OS_PROD, $CLIENT_OS_REL, $CLIENT_ARCH"
	    printf $PRINTF_HOST "    $msg :"
	    get_client_patches_list $root
	    echo $CLIENT_PATCHES
	done
    else
	gettext "--- no clients found ---\n"
    fi

    gettext "OS Services available are:\n"
    if [ -d /export/Solaris* ]; then
	for service_dir in /export/Solaris*
	do
	    service_patched=0
	    service=`basename $service_dir`
	    get_service_patches_list $service
	    printf $PRINTF_HOST ${service}
	    echo ""
	    printf $PRINTF_HOST "    $msg :"
	    if [ "${SERVICE_PATCHES}X" != "X" ]; then
		for patch in $SERVICE_PATCHES
		do
		    if [ $service_patched -eq 0 ]; then
			service_patched=1
			echo "$patch\c"
		    else
			echo ", $patch\c"
		    fi
		done
	    else
		gettext "--- no patches found ---"
	    fi
	    echo ""
	done
    else
	gettext "--- no OS Services found ---\n"
    fi
    noerror_exit
fi

############################################################# Handle -a option
if [ $AFLAG -eq 1 ]; then
    create_spool_dir
    validate_spool_dir_w
    validate_patch $PATCHDIR
    spool_patch $PATCHDIR
fi

############################################################# Handle -p option
if [ $PFLAG -eq 1 ]; then
    get_spooled_patches_list
    if [ "${PATCHES}X" = "X" ]; then
	echo $NO_SPOOLED
	noerror_exit
    fi
    # Try to sort the patches based on OS, os_rel and arch
    os_product=`grep OS_PRODUCT $INDEX | awk -F= '{print $2}' | sort | uniq`
    os_release=`grep OS_RELEASE $INDEX | awk -F= '{print $2}' | sort | uniq`
    arch_list=`grep ARCH_LIST $INDEX | awk -F= '{print $2}' | sort | uniq`

    trap trap_exit	2
    # If there are any pending patches, list those first
    if [ -d $TMP_SPOOL_DIR ]; then
	gettext "Pending Patches:\n"
	pending=0
	for pending_patch in `ls $TMP_SPOOL_DIR`
	do
	    if [ -f $TMP_SPOOL_DIR/$pending_patch/*/pkginfo ]; then
		pending=1
		if [ -f $TMP_SPOOL_DIR/$pending_patch/README* ] ; then
		    synopsis=`sed -n 's/Synopsis: //p' $TMP_SPOOL_DIR/$pending_patch/README*`
		    echo "    $pending_patch    $synopsis"
		else
		    echo "    $pending_patch"
		fi
		required_patches=`sed -n 's/SUNW_REQUIRES=\(.*\)/\1/p' $TMP_SPOOL_DIR/$pending_patch/*/pkginfo | tr ' ,' '\012\012' | sort | uniq`
		echo "\t\t\tRequires patch(es): $required_patches"
	    fi
	done
	if [ $pending -eq 0 ]; then
	    gettext "No pending patches spooled.\n"
	fi
	echo ""
    fi

    gettext "Patches:\n"
    for os in $os_product
    do
	for rel in $os_release
	do
	    for arch in $arch_list
	    do
		header=0
		for patch in $PATCHES
		do
		    is_patch_applicable $patch $rel $arch
		    if [ $? -eq 1 ]; then
			if [ $header -eq 0 ]; then
			    echo "$os $rel $arch"
			    header=1
			fi
			if [ -f $patch/README* ] ; then
			    synopsis=`sed -n 's/Synopsis: //p' $patch/README*`
			    echo "    $patch    $synopsis"
			else
			    echo "    $patch"
			fi
		    fi
		done
	    done
	done
    done
    noerror_exit
fi

############################################################# Handle -s option
if [ $SFLAG -eq 1 ]; then
    validate_spool_dir
    if [ $? -eq 1 ]; then
	echo $NO_SPOOLED
	noerror_exit
    fi
    find_clients
    get_spooled_patches_list

    # There are patches spooled, so find out which patches need to be
    # installed on a client/os service as well as which patches need
    # to be backed out from a client/os service.

    #
    # For each OS service.
    #
    for service in /export/Solaris*
    do
	new_patches=
	os_service=`basename $service`
	echo `gettext "Synchronizing service: "` $os_service " \c"
	verbose ""
	get_service_patches_list $os_service

	# First find patches that are spooled but not installed.
	find_diffs "$PATCHES" "$SERVICE_PATCHES"
	for patch in $DIFFS
	do
	    get_patch_info $SPOOL_DIR/$patch
	    for patch_os_rel in $PATCH_OS_REL
	    do
		service_patched=${PATCH_OS_PROD}_$patch_os_rel
		if [ $os_service = $service_patched ]; then
		    for pkgtype in $PATCH_PKGTYPES
		    do
			case $pkgtype in
			    "usr" |\
			    "ow")
				new_patches="$new_patches $patch";;
			    "kvm" |\
			    "root" |\
			    *);;
			esac
		    done
		fi
	    done
	done

	# Second find patches that are installed but not spooled.
	find_diffs "$SERVICE_PATCHES" "$PATCHES"
	old_patches="$DIFFS"

	# Back out old patches
	if [ $VFLAG -eq 1 ]; then
	    if [ "${old_patches}X" != "X" ]; then
		echo "$REMOVING" | sed 's=^=    ='
	    fi
	else
	    start_dots
	fi
	for old in $old_patches
	do
	    if [ $VFLAG -eq 1 ]; then
		printf $PRINTF_PATCHID $old
		start_dots
	    fi
	    unpatch_service $old $os_service > $LOGFILE_TMP 2>&1
	    ret=$?
	    if [ $ret -ne 0 ]; then
		/bin/cat $LOGFILE_TMP
	    fi
	    /bin/rm $LOGFILE_TMP
	    if [ $VFLAG -eq 1 ]; then stop_dots; fi
	done

	# Install new patches
	if [ $VFLAG -eq 1 ]; then
	    if [ "${new_patches}X" != "X" ]; then
		echo "    $INSTALLING"
	    fi
	fi
	for new in $new_patches
	do
	    if [ $VFLAG -eq 1 ]; then
		printf $PRINTF_PATCHID $new
		start_dots
	    fi
	    patch_service $new $os_service > $LOGFILE_TMP 2>&1
	    ret=$?
	    if [ $ret -ne 0 ]; then
		/bin/cat $LOGFILE_TMP
	    fi
	    /bin/rm $LOGFILE_TMP
	    if [ $VFLAG -eq 1 ]; then stop_dots; fi
	done
	if [ $VFLAG -eq 0 ]; then stop_dots; fi
    done

    #
    # For each client
    #
    for root in $ROOTS
    do
	new_patches=
	client=`basename $root`
	echo `gettext "Synchronizing client: "` $client " \c"
	verbose ""
	get_client_patches_list $root
	get_client_info $root

	# First find patches that are spooled but not installed.
	find_diffs "$PATCHES" "$CLIENT_PATCHES"
	new_patches=
	for patch in $DIFFS
	do
	    is_patch_applicable $patch $CLIENT_OS_REL $CLIENT_ARCH
	    if [ $? -eq 1 ]; then
		new_patches="$new_patches $patch"
	    fi
	done

	# Second find patches that are installed but not spooled.
	find_diffs "$CLIENT_PATCHES" "$PATCHES"
	old_patches="$DIFFS"

	# Back out old patches
	if [ $VFLAG -eq 1 ]; then
	    if [ "${old_patches}X" != "X" ]; then
		echo "$REMOVING" | sed 's=^=    ='
	    fi
	else
	    start_dots
	fi
	for old in $old_patches
	do
	    if [ $VFLAG -eq 1 ]; then
		printf $PRINTF_PATCHID $old
		start_dots
	    fi
	    unpatch_client $root $old > $LOGFILE_TMP 2>&1
	    ret=$?
	    if [ $ret -ne 0 ]; then
		/bin/cat $LOGFILE_TMP
	    fi
	    /bin/rm $LOGFILE_TMP
	    if [ $VFLAG -eq 1 ]; then stop_dots; fi
	done

	# Install new patches
	if [ $VFLAG -eq 1 -a "${new_patches}X" != "X" ]; then
	    echo "    $INSTALLING"
	fi
	for new in $new_patches
	do
	    if [ $VFLAG -eq 1 ]; then
		printf $PRINTF_PATCHID $new
		start_dots
	    fi
	    patch_client $root $new > $LOGFILE_TMP 2>&1
	    ret=$?
	    if [ $ret -ne 0 ]; then
		/bin/cat $LOGFILE_TMP
	    fi
	    /bin/rm $LOGFILE_TMP
	    if [ $VFLAG -eq 1 ]; then stop_dots; fi
	done
	if [ $VFLAG -eq 0 ]; then stop_dots; fi
    done

    echo ""
    gettext "All done synchronizing patches to existing clients and OS services.\n"
    display_updated
    noerror_exit
fi
