OpenSolaris_b135/cmd/itutools/updatemedia.ksh

#!/bin/ksh93 -p
#
# CDDL HEADER START
#
# The contents of this file are subject to the terms of the
# Common Development and Distribution License (the "License").
# You may not use this file except in compliance with the License.
#
# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
# or http://www.opensolaris.org/os/licensing.
# See the License for the specific language governing permissions
# and limitations under the License.
#
# When distributing Covered Code, include this CDDL HEADER in each
# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
# If applicable, add the following below this CDDL HEADER, with the
# fields enclosed by brackets "[]" replaced with your own identifying
# information: Portions Copyright [yyyy] [name of copyright owner]
#
# CDDL HEADER END
#
#
# Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
# Use is subject to license terms.
#

#
# updatemedia - modify Solaris media with patches and packages
#

readonly PROG=$0
readonly TMP_DIR=${TMPDIR:-/tmp}/${PROG##*/}.$$
readonly LOGFILE=${TMPDIR:-/tmp}/${PROG##*/}-log.$$

# Must-have utilities
readonly CPIO=/bin/cpio
readonly GZIP=/bin/gzip
readonly MKISOFS=/usr/bin/mkisofs
readonly PATCHADD=/usr/sbin/patchadd
readonly LOFIADM=/usr/sbin/lofiadm
readonly MKDIR=/usr/bin/mkdir
readonly RM=/usr/bin/rm
readonly CP=/usr/bin/cp
readonly MKBOOTMEDIA=/usr/bin/mkbootmedia
readonly PKG2DU=/usr/bin/pkg2du
readonly TOUCH=/usr/bin/touch
readonly NAWK=/usr/bin/nawk
readonly CHMOD=/usr/bin/chmod
readonly GREP=/usr/bin/grep
readonly LS=/usr/bin/ls
readonly LN=/usr/bin/ln
readonly SED=/usr/bin/sed
readonly CAT=/usr/bin/cat
readonly FIND=/usr/bin/find
readonly HEAD=/usr/bin/head
readonly SORT=/usr/bin/sort
readonly UNAME=/usr/bin/uname
readonly MACH=`$UNAME -p`

ROOT_ARCHIVE=/usr/sbin/root_archive
# for gettext
TEXTDOMAIN=SUNW_OST_OSCMD
export TEXTDOMAIN


function usage
{
	gettext "Usage:\n${PROG##*/} -d <media-root> [-v] [-l <label>] [-o <iso>]\n        <pkg_or_patch> [<pkg_or_patch> ...]\n"
	gettext "Options:\n  -d <media-root>\n        Top-level directory of on-disk image of Solaris installation media.\n        This is option must be specified.\n"
	gettext "  -l <label>\n        Label/volume name of the ISO image (if -o option is specified).\n"
	gettext "  -o <iso>\n        Create a Solaris ISO image of <media-root>.\n"
	gettext "  -v\n        Verbose.  Multiple -v options increase verbosity.\n"
}


function check_prereqs
{
	typeset f

	# We must have these utilities.
	for f in $CPIO $GZIP ${ISO:+$MKISOFS} $PATCHADD $ROOT_ARCHIVE
	do 
		if [[ ! -x "$f" ]]
		then
			gettext "Cannot find required utility $f\n"
			exit 1
		fi
	done

	# root_archive unpack_media calls lofiadm -a, which requires
	# write access as determined by /dev/lofictl.  See lofiadm(1m).
	if [[ ! -w /dev/lofictl ]]
	then
		gettext "You do not have enough privileges to run lofiadm -a).\nSee lofiadm(1m) for more information.\n"
		exit 1
	fi
}


function cleanup
{
	$RM -rf "$TMP_DIR"
}


function unpack_media
{
	# Create temp directory to unpack the miniroot.
	$MKDIR -p "$UNPACKED_ROOT"

	# We need to use the unpackmedia option to correctly apply patches
	gettext "Unpacking media ..."
	$ROOT_ARCHIVE unpackmedia "$MEDIA_ROOT" "$UNPACKED_ROOT" > /dev/null 2>&1 
	if [ $? != 0 -a ! -d $MEDIA_ROOT/Solaris_10 ]; then
		# we _do_ care, because we're not patching a Solaris 10
		# update media instance
		gettext "\nThere was an error unpacking the media from $MEDIA_ROOT\n"
		exit 1
	fi
	echo;
}


function repack_media
{
	gettext "Repacking media ..."

	# We need to ensure that we're using the appropriate version
	# of root_archive for the media that we're packing/unpacking.
	# The onnv version of root_archive differs from the S10 version,
	# and this will cause problems on re-packing. So we sneakily
	# use the version that we've just unpacked
	if [ -d $MEDIA_ROOT/Solaris_10 ]; then
		ROOT_ARCHIVE=$UNPACKED_ROOT/boot/solaris/bin/root_archive
	fi

	$ROOT_ARCHIVE packmedia "$MEDIA_ROOT" "$UNPACKED_ROOT" > /dev/null 2>&1
	if [ $? != 0 -a ! -d $MEDIA_ROOT/Solaris_10 ]; then
		# we _do_ care, because we're not patching a Solaris 10
		# update media instance
		gettext "\nThere was an error unpacking the media from $MEDIA_ROOT\n"
		exit 1
	fi
	echo;
}


function mkiso
{
	typeset vflag

	# Skip if no ISO image was specified.
	[[ -z "$ISO" ]] && return 0

	gettext "Creating ISO image ..."
	$MKBOOTMEDIA $VERBOSE_OPTS -l "$ISOLABEL" "$MEDIA_ROOT" "$ISO"
	echo;
}


function collect_objs # <pkg_or_patch> ...
{
	typeset obj fail=0

	for obj
	do
		if [[ -f "$obj"/patchinfo ]]
		then
			PATCHES[ ${#PATCHES[*]} ]=$obj
		elif [[ -f "$obj"/pkginfo ]]
		then
			PACKAGES[ ${#PACKAGES[*]} ]=$obj
		else
			gettext "$obj is not in package or patch format\n"
			(( fail += 1 ))
		fi
	done
	(( fail )) && return 1
	return 0
}


function add_pkgs
{
	typeset dudir icmd statusfile

	(( ${#PACKAGES[*]} == 0 )) && return

	statusfile=$TMP_DIR/.add_pkgs.status

	trap '$RM -f $statusfile' EXIT

	dudir=$ITUDIR/$COUNTDIR
	(( COUNTDIR += 1 ))
	$MKDIR "$dudir" || return

	# Add a Driver Update directory on the media
	echo;
	gettext "Adding package(s) to media root."
	$PKG2DU -r "$RELEASE" -f -d "$dudir" $VERBOSE_OPTS \
	    "${PACKAGES[@]}" || return

	# Using the Driver Update above install the packages onto the miniroot.
	echo;
	gettext "Installing package(s) onto miniroot."
	icmd=$dudir/DU/sol_$VERSION/$MACH/Tools/install.sh
	if [[ ! -f "$icmd" ]]
	then
		# This shouldn't happen, but just in case.
		gettext "Cannot find $icmd\n"
		return 1
	fi
	[[ ! -x "$icmd" ]] && $CHMOD a+x "$icmd"

	$RM -f "$statusfile"
        {
		"$icmd" -R "$UNPACKED_ROOT"
		if (( i=$? ))
		then
			echo $i > "$statusfile"
			$TOUCH "$statusfile"  # make sure file is created
		fi
        } 2>&1 | $NAWK -v logfile="$LOGFILE" '
		# Print certain lines from $icmd, save all in logfile.
		/^Installing/ {print}
		/^Installation.*successful/ {print}
		{print >> logfile}
	' || return
	[[ -s "$statusfile" ]] && return $(<$statusfile)
	return 0
}


function add_patches
{
	typeset distdir tmpdir icmd obj patches statusfile

	(( ${#PATCHES[*]} == 0 )) && return

	tmpdir=$TMP_DIR/patches
	statusfile=$TMP_DIR/.add_patches.status

	trap '$RM -rf $tmpdir $statusfile' EXIT

	distdir=$ITUDIR/$COUNTDIR/DU/sol_$VERSION/$MACH
	(( COUNTDIR += 1 ))

	$MKDIR -p "$distdir/Tools" "$distdir/Product" "$tmpdir" || return

	# If we're running this script on sun4[vu], then create a symlink
	# to the other UltraSPARC architecture
	if [[ "$MACH" != "i386" ]]
	then
		cd $ITUDIR/$COUNTDIR/DU/sol_$VERSION/
		$LN -s sparc sun4v
		$LN -s sparc sun4u
	else
		cd $ITUDIR/$COUNTDIR/DU/sol_$VERSION/
		$LN -s i386 i86pc
	fi	

	# Patch the miniroot
	echo;
	gettext "Installing patch(es) onto miniroot."
	$RM -f "$statusfile"
	{
		$PATCHADD -udn -C "$UNPACKED_ROOT" "${PATCHES[@]}"
		if (( i=$? ))
		then
			echo $i > "$statusfile"
			$TOUCH "$statusfile" # make sure file is created
		fi
        } 2>&1 | $NAWK -v logfile="$LOGFILE" '
		# Print certain lines from patchadd, save all in logfile.
		/^Patch.*successful/ {print}
		{print >> logfile}
	' || return

	[[ -s "$statusfile" ]] && return $(<$statusfile)

	# Remove patch log files to save space when miniroot is repacked.
	$RM -rf "$UNPACKED_ROOT"/var/sadm/patch

	# Symlink each patch in a temp dir so a single cpio/gzip can work.
	for obj in "${PATCHES[@]}"
	do
		# Get rid of trailing /'s, if any.
		[[ "$obj" == */ ]] && obj=${obj%%+(/)}

		# Make sure it's full pathname.
		[[ "$obj" != /* ]] && obj=$ORIGPWD/$obj

		$LN -s "$obj" "$tmpdir" || return

		# Remember just the file component.
		patches[ ${#patches[*]} ]=${obj##*/}
	done

	# Package up patches as compressed cpio archive.
	echo;
	gettext "Adding patch(es) to media root.\n"
	$RM -f "$statusfile"
	(
		cd "$tmpdir"
		# fd 9 is used later on for filtering out cpio's
		# reporting total blocks to stderr but yet still
		# print other error messages.
		exec 9>&1
		for obj in "${patches[@]}"
		do
			gettext "Transferring patch $obj\n"
			$FIND "$obj/." -follow -print
			if (( i=$? ))
			then
				echo $i > "$statusfile"
				$TOUCH "$statusfile"
				return $i
			fi
		done | $CPIO -oc 2>&1 >&9 | $GREP -v '^[0-9]* blocks' >&2
	) | $GZIP -9 > "$distdir/Product/patches.gz" || return

	[[ -s "$statusfile" ]] && return $(<$statusfile)

	# Create install.sh
	$CAT > "$distdir/Tools/install.sh" <<"EOF"
#!/sbin/sh
# install.sh -R <basedir> - install patches to basedir
basedir=/
toolsdir=`dirname $0`
tmpdir=/tmp/`basename $0`.$$
trap "/bin/rm -rf $tmpdir" 0
while getopts "R:" arg
do
        case "$arg" in
                R) basedir=$OPTARG;;
        esac
done
/bin/mkdir -p "$tmpdir" || exit
tmpfile=$tmpdir/patches
patchdir=$tmpdir/patchdir
/bin/mkdir "$patchdir" || exit
/usr/bin/gzip -c -d "$toolsdir/../Product/patches.gz" > $tmpfile || exit
cd "$patchdir"
/bin/cpio -idum < "$tmpfile" || exit
/usr/sbin/patchadd -R "$basedir" -nu *
EOF
	$CHMOD a+rx "$distdir/Tools/install.sh"

}


#
# Main
#
trap cleanup EXIT

ISO=
ISOLABEL=
MEDIA_ROOT=
VERBOSE_LEVEL=0
VERBOSE_OPTS=

while getopts ':d:o:l:v' opt
do
	case $opt in
	d)	MEDIA_ROOT=$OPTARG
		;;
	o)	ISO=$OPTARG
		if [ ! -z `echo $ISO | $GREP "^/tmp"` ]; then
		        gettext "ISO images will not be created on /tmp.\nPlease choose a different output location.\n"
			exit 3
		fi
		;;
	l)	ISOLABEL=$OPTARG
		;;
	v)	(( VERBOSE_LEVEL += 1 ))
		VERBOSE_OPTS="${VERBOSE_OPTS:--}$opt"	# collect -v options
		;;
	:)	gettext "Option -$OPTARG missing argument.\n"
		usage
		exit 1
		;;
	*)	gettext "Option -$OPTARG is invalid.\n"
		usage
		exit 2
		;;
	esac
done
shift 'OPTIND - 1'

unset PACKAGES PATCHES				# reset arrays
collect_objs "$@"

# If there are no packages or patches, then print info and we're done.
if (( ${#PACKAGES[*]} == 0 && ${#PATCHES[*]} == 0 ))
then
	gettext "No valid package or patch was specified.\nPackages and patches must be unpacked.\n"
	usage
	exit 1
fi

# -d option must be specified
if [[ -z "$MEDIA_ROOT" ]]
then
	gettext "No media root (-d option) was specified.\n"
	usage
	exit 1
fi

check_prereqs		# must be called after $ISO is possibly set

# Verify it's a Solaris install media.
SOLARIS_DIR=$($LS -d $MEDIA_ROOT/Solaris* 2>/dev/null)
if [[ -z "$SOLARIS_DIR" || ! -d "$SOLARIS_DIR/Tools/Boot" ]]
then
	gettext "$MEDIA_ROOT is not valid Solaris install media.\n"
	exit 1
fi

$MKDIR -p "$TMP_DIR" || exit 1

# Extract the Solaris release number from the Solaris_* directory and the
# corresponding version number.  As defined by the ITU spec, a Solaris release
# number 5.x corresponds to version number 2x (e.g. 5.10 -> 210).
RELEASE=5.${SOLARIS_DIR##*Solaris_}
VERSION=$(echo $RELEASE | $SED 's/5\./2/')

# If user didn't specify ISO label, use the Solaris_* dir as label.
${ISOLABEL:=${SOLARIS_DIR##*/}} 2>/dev/null

# Verify miniroot

MINIROOT=
# Relative to a Solaris media root.
if [ "$MACH" = "sparc" ]; then
	MINIROOT=$MEDIA_ROOT/boot/sparc.miniroot
else
	# x86/x64
	MINIROOT=$MEDIA_ROOT/boot/x86.miniroot
fi
if [ ! -f $MINIROOT ]; then
	gettext "No boot/x86.miniroot under media root.\n"
	exit 1
fi

# Where to unpack the miniroot.
UNPACKED_ROOT=${TMP_DIR}/miniroot

# Create the ITU directory on the media, if necessary
ITUDIR=$MEDIA_ROOT/ITUs
$MKDIR -p "$ITUDIR" || exit 1

# The ITU directory might contain multiple driver updates already, each in a
# separate numbered subdirectory.  So look for the subdirectory with the
# highest number and we'll add the packages and patches on the next one.
typeset -Z3 COUNTDIR
COUNTDIR=$($LS -d "$ITUDIR"/+([0-9]) 2>/dev/null | $SED 's;.*/;;' |
		$SORT -rn | $HEAD -1)
if [[ $COUNTDIR == *( ) ]]
then
	COUNTDIR=0
else
	(( COUNTDIR += 1 ))
fi

unpack_media || exit
add_pkgs && add_patches
if (( status=$? )) && [[ -s "$LOGFILE" ]]
then
	echo;
	gettext "A package or patch installation has failed.\nMessages from pkgadd and patchadd have been saved in $LOGFILE\n"
	exit $status
else
	$RM -f "$LOGFILE"
fi
print
repack_media || exit
mkiso