#!/bin/ksh -p
#
# ident "@(#)utinstall.ksh	1.10	10/10/13 Oracle"
#
# Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
#

#
# utinstall
#
# SRS installation wrapper - One Installer to Bind Them All
#
# This wrapper uses the Component/ heirarchy in the CD install image
# to obtain the information it needs to determine for each component
# whether the component need to be patched and where its patches
# reside, or whether its installer needs to invoked for installation
# or upgrade, and where to find the component installer.
#
# At the moment the only component supported is SRSS.
#

#
# Algorithm:
# For each component:
#    if installed_version(component_name) != delivered_verion(component_name)
#        invoke component installer
#    else for each component patch:
#        if installed_rev(patch_stream) < delivered_rev(patch_stream)
#            invoke platform "patch install" method
#

PATH=/usr/bin:/bin:/usr/sbin:/sbin

# Global definitions
image_base=$(dirname $0)
component_base="$image_base"/Components

platform=$(uname -s)
showrev_output=/tmp/showrev.$$
auth_props_backup=/tmp/auth.props-backup.$$
debug=false
reboot_required=false
was_up2date=true
patch_err=false
utprodinfo=/opt/SUNWut/lib/utprodinfo
srss_shutdown=/opt/SUNWut/lib/srss-shutdown

# Global parameters set by set_component_params(component_name):
# dir containing patches for the given component
patch_base=
# pathname to representative package within component
package_location=
# name of representative package
package_name=
# pointer to component installer
component_installer=
component_shutdown=

function set_component_params {
    typeset component_name=$1
    
    if [ $platform = SunOS ]; then
	patch_base="$component_base/$component_name/Patches/solaris/$(uname -p)"
    else
	patch_base="$component_base/$component_name/Patches/linux"
    fi

    package_location="$image_base/$(cat $component_base/$component_name/a_package-path)"
    package_name=$(basename "$package_location")
    component_installer="$image_base/$(cat $component_base/$component_name/installer-path)"
    component_shutdown="$component_base/$component_name/shutdown"
}

function cleanup {
    rm -f $showrev_output
    rm -f $auth_props_backup
}

# release version detection functions: installed_version, delivered_version
#
# These functions print a ReleaseNumber_BuildNumber to stdout that can be
# compared to determine newer/older as well as equality.
#
function installed_version {
    typeset iver
    if [ -x $utprodinfo ] && iver="$($utprodinfo -p $package_name VERSION 2>/dev/null)" ; then
       print ${iver%%,*}
   else
       print "0"
   fi

}

function delivered_version {
    typeset dver
    dver=$(sed -n 's/^VERSION=\([0-9._Experimental]*\),.*/\1/p' $package_location/pkginfo)
    if [ -n "$dver" ]; then
	print $dver
    else
	print "0"
    fi
}

# patch_pkg_installed
# See if a patch is relevant to the current system - i.e. if any patch
# packages are installed on the system

function patch_pkg_installed {
    typeset patch_dir=$patch_base/$1 patch_pkg pkg_name

    patch_valid=false
    for patch_pkg in $(cd $patch_dir; ls -d SUNW*); do
	case $platform in
	    SunOS)
		pkg_name=$patch_pkg
		;;
	    Linux)
		pkg_name=$(rpm -q --queryformat "%{Name}" -p $patch_dir/$patch_pkg)
		;;
	esac
	if [ -x $utprodinfo ] && $utprodinfo -t installed $pkg_name 2>/dev/null ; then
	    patch_valid=true
	    break
	fi
    done	
    $patch_valid
}

# patch version detection functions: installed_rev, delivered_rev
#
# These functions print a patch stream version number to stdout that
# can be compared numerically to determine newer/older as well as
# equality.
#
# Note that on Solaris the printed value represents the rev of the
# specified patch stream, whereas on Linux these functions will return
# the patch build number, not the actual patch rev, because the rev is
# unavailable. However in all cases the output of these functions is
# monotonic across patch releases and can be compared numerically.
#
# Note if no patch is installed an empty line will be printed, but
# this can be used in shell test functions -gt and -lt and is
# equivalent to 0, i.e. lower than any patch rev
function installed_rev {
    typeset patch_stream_name=${1%-*}
    typeset instrev

    case $platform in
	SunOS)
	    if [ ! -f $showrev_output ]; then
		showrev -p > $showrev_output
	    fi
	    instrev=$(sed -n "s/^Patch: $patch_stream_name-\([0-9]*\) .*/\1/p" \
		    $showrev_output | sort -nr | head -n 1)
	    ;;
	Linux) 
	    patch_rpm=$(basename $(ls $patch_base/$1/*.rpm |head -n 1))
	    instrev=$(rpm -q --queryformat "%{Release}" ${patch_rpm%%-*})
	    ;;
    esac
    if $debug; then
	print -u2 "Debug: installed patch rev '$instrev'"
    fi
    if [ -z "$instrev" ]; then
	instrev=0
    fi
    print $instrev
}

function delivered_rev {
    typeset patch_stream=$1
    typeset delrev

    case $platform in
	SunOS)
	    delrev=${patch_stream#*-}
	    ;;
	Linux)
	    # XXX Need to set delrev to patch build #, not rev
	    delrev=$(rpm -q -p --queryformat "%{Release}" $(ls $patch_base/$1/*.rpm |head -n 1))
	    ;;
    esac
    if $debug; then
	print -u2 "Debug: delivered patch rev '$delrev'"
    fi
    print $delrev
}


#
# function CompareVersion
#
# Description:
#     Compares the Version strings passed in.  The version string
#     must be in the form <n>.<n>[.<n>] where "n" is an integer.
#     Special provision is made for dealing with version strings of the form
#     <n>.<n>[.<n>][.<a>] where "a" is alphanumeric. Such strings can be
#     compared for equality. This allows for uninstallation.
#
# Parameters:
#     $1 and $2 the version strings to be compared
#
# Returns:
#     =0 if $1 is the same as $2
#     =1 if $1 is newer than $2
#     =2 if $2 is newer than $1
#
# define some return values
COMPVER_EQ=0
COMPVER_GT=1
COMPVER_LT=2

function CompareVersion {
    typeset r1=${1%%_*}
    typeset v1
    typeset done1=false
    typeset r2=${2%%_*}
    typeset v2
    typeset done2=false
    typeset res
    while /bin/true
    do
	v1=${r1%%.*}
	if [[ $v1 = $r1 ]]; then
	    r1="0"
	    done1=true
	else
	    r1=${r1#*.}
	fi
	v2=${r2%%.*}
	if [[ $v2 = $r2 ]]; then
	    r2="0"
	    done2=true
	else
	    r2=${r2#*.}
	fi
	# Deal with feature releases, which are not fully numeric and
	# thus can't be compared except for equality
	if [ "$v1" != "$v2" ]; then
	    let "res=v1-v2"
	    if [[ $res -lt 0 ]]; then
		# $1 < $2
		return 2
	    elif [[ $res -gt 0 ]]; then
		# $1 > $2
		return 1
	    fi
	fi
	if $done1 && $done2; then
	    # no more substring to compare
	    # this means match
	    break
	fi
    done
    return 0
}

function do_install {
    if $debug; then
	print -u2 "Debug: Execute '$component_installer $ARGS'"
    else
	$component_installer $ARGS
    fi
}

function do_patchadd {
    typeset patch=$1
    typeset cmd

    print "Updating software, please wait."
    if [ -x $srss_shutdown ] ; then    
	$srss_shutdown 
    else
	$component_shutdown	
    fi
	
    was_up2date=false

    case $platform in
	SunOS)	
	    cmd="patchadd $patch"
	    ;;
	Linux)
            # For each RPM in patch, remove old, install new (see README.patch)
	    cmd="rpm --nodeps -F $patch/*.rpm"	    
	    ;;
    esac 

    if $debug; then
	print -u2 "Debug: Execute '$cmd'"
	STATUS=0
    else
	$cmd
	STATUS=$?
    fi

    if [ $STATUS -eq 0 ]; then 
	reboot_required=true
    else 
	patch_err=true
    fi
}

function CompareFullVersion {
    typeset STATUS v1 v2
    
    # Compare the version before any build number (build is preceeded by '_')
    CompareVersion ${1%%_*} ${2%%_*}
    STATUS=$? 

    # if equal, compare the build number, but discard any patch rev
    # patch rev on Linux is preceeded by '.'
    if [ "$STATUS" -eq "$COMPVER_EQ" ]; then
	v1=${1#*_}
	v2=${2#*_}
	CompareVersion ${v1%%.*} ${v2%%.*}
	STATUS=$?
    fi
    return $STATUS
}

function handle_component { 
    typeset component_name=$1 
    typeset inst_ver deliv_ver
    typeset exp_bld=false
    
    set_component_params $component_name 
    inst_ver="$(installed_version $component_name)" 
    deliv_ver="$(delivered_version $component_name)" 
    
    if [ ${inst_ver#*_} = "Experimental" ] || [ ${deliv_ver#*_} = "Experimental" ]; then
	exp_bld=true
    fi

    # If they are the same release version,(but not experimental builds,
    # compare build versions to allow build upgrades

    if ! $exp_bld; then
	CompareFullVersion $deliv_ver $inst_ver
	STATUS=$?
    else
	STATUS=$COMPVER_GT
    fi
       
    if $UNINSTALL || [ "$STATUS" -eq "$COMPVER_GT" ]; then
	was_up2date=false
	do_install 
	inst_ver="$(installed_version $component_name)" 
	if ! $exp_bld; then 
	    CompareFullVersion $deliv_ver $inst_ver 
	    STATUS=$? 
	else
	    if [ ${deliv_ver#*_} = "Experimental" ]; then
		STATUS=$COMPVER_GT
	    else
		STATUS=$COMPVER_EQ
	    fi
	fi
	if ! $UNINSTALL && [ "$STATUS" -eq "$COMPVER_EQ" ]; then
		reboot_required=true
		# for uninstallation, only the installer will run and
		# its reboot message will be the last thing seen so
		# our message is not necessary
	fi
    fi 
    
    if [ "$STATUS" -eq "$COMPVER_EQ" ]; then 
	# Backup the auth.props file on Linux since we do a remove & install of rpms for patches. 
	if [ "$platform" = Linux ]; then
	    rm -f $auth_props_backup
	    cp -p /etc/opt/SUNWut/auth.props $auth_props_backup	     
	fi 
	
	if [ -d "$patch_base" ]; then
	    for patch in $(cd $patch_base && print -- *); do 
		if patch_pkg_installed $patch; then
		    inst_rev="$(installed_rev $patch)" 
		    deliv_rev="$(delivered_rev $patch)" 
		    CompareVersion $deliv_rev $inst_rev 
		    
		    if [ $? -eq "$COMPVER_GT" ]; then 
			do_patchadd $patch_base/$patch
			if $patch_err; then
			    break
			fi
		    fi 
		fi
	    done
	fi
	
	# restore the auth.props file 
	if [ "$platform" = Linux ]; then
	    cp -p $auth_props_backup /etc/opt/SUNWut/auth.props 	     
	fi 
	
    fi 
} 

function Usage {
  cat 1>&2 <<-ENDIT
	Usage: $G_PROGRAM_ID $PROGRAM_OPTS

	 -a admin_file

	         specify an admin(4) file to be used by pkgadd(1m)
		 calls during install (Solaris only)

	 -d media_dir
	         specify a directory to install the software from

	 -j jre
	         specify a directory to find the Java Runtime Environment

	 -u      uninstall the software
	ENDIT

  exit 1 
}

function CheckSetOptA {
  case "$platform" in
    Linux) print -u2 "can not use option '-a' on Linux"; exit 1;;
    *)     ARGS="$ARGS -a $1";;
  esac
}

# MAIN 
 
# ensure the cleanup function gets invoked on any exit 
trap cleanup 0

ARGS=
UNINSTALL=false
PRE=
umask 022 
OPTSTR=":Da:d:j:ufv"
PROGRAM_OPTS="[-a admin_file] [-d media_dir] [-j jre] [-u]"

while getopts $OPTSTR opt  
do 
    case $opt in 
	D)      debug=true;;
        f|v)    ARGS="$ARGS -$opt";;
	# we need to know which args take optargs, and handle them appropriately
	d|j)	ARGS="$ARGS -$opt $OPTARG";;
	a)      CheckSetOptA "$OPTARG";;
	# we need to recognize uninstall, to skip a version check to invoke installer
	u)  UNINSTALL=true; ARGS="$ARGS -u"; PRE="uninstallation";;
	# anything else is an illegal option - print Usage
	?)	Usage;;
    esac 
done 
 
rm -f $showrev_output 
 
for component in $(cd $component_base && print -- *); do 
    if [ "$component" != "utinstall-srss" ]; then 
	    handle_component $component 
    fi 
done 
 
if $patch_err; then
    print "There was an error in Patch installation. Please check the above output for details."
elif $reboot_required; then
    print "The system must be rebooted in order to complete this ${PRE:-installation and before starting the software}." 
elif ! $UNINSTALL && $was_up2date ; then
    print "The system is up to date with the latest Sun Ray Server Software."
fi

exit 0 
