OpenSolaris_b135/cmd/itutools/itu.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.
#

#
# itu - converts packages to Driver Update format and patches Solaris install
#	media for Install Time Update (ITU).
#

readonly PROG=$0
readonly ORIGPWD=$PWD

# Must-have utilities
readonly CPIO=/usr/bin/cpio
readonly GZIP=/usr/bin/gzip
readonly MKISOFS=/usr/bin/mkisofs
readonly PATCHADD=/usr/sbin/patchadd
readonly PKGTRANS=/usr/bin/pkgtrans
readonly PKGADD=/usr/sbin/pkgadd
readonly LOFIADM=/usr/sbin/lofiadm
readonly MKDIR=/usr/bin/mkdir
readonly RM=/usr/bin/rm
readonly CP=/usr/bin/cp
readonly HEAD=/usr/bin/head
readonly SORT=/usr/bin/sort
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 UNAME=/usr/bin/uname
readonly MACH=`$UNAME -p`

ROOT_ARCHIVE=/usr/sbin/root_archive
BOOTBLOCK=
MINIROOT=
# Relative to a Solaris media root.
if [ "$MACH" = "sparc" ]; then
	BOOTBLOCK=boot/hsfs.bootblock
	MINIROOT=$MEDIA_ROOT/boot/sparc.miniroot
else
	# x86/x64
	BOOTBLOCK=boot/grub/stage2_eltorito
	MINIROOT=$MEDIA_ROOT/boot/x86.miniroot
fi

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

# Paths we need.
export PATH=/usr/bin:/usr/sbin:/sbin:/boot/solaris/bin:$PATH

# for gettext
TEXTDOMAIN=SUNW_OST_OSCMD
export TEXTDOMAIN


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


function usage_long
{
	usage_short
	print -u2
	usage_options
}


function usage_short
{
	gettext "Usage:\n"
	gettext "${PROG##*/} makedu -r solaris_release [-v] [-f] [-d output_dir]\n        [-o iso_file] [-l iso_label] package [package ...]\n"
	gettext "${PROG##*/} patchmedia -R media_root [-v] [-f]\n        [-o iso_file] [-l iso_label] pkg_or_patch [pkg_or_patch ...]\n"
	gettext "${PROG##*/} makeiso -o iso_file [-v] [-f] [-l iso_label] media_root\n"
}


function usage_options {
	gettext "Options:\n"
	gettext "  -d output_dir\n        Directory where the Driver Update directory should be created.\n"
	gettext "  -f\n        If output_dir/DU or iso_file already exists, remove it without\n        asking first.\n"
	gettext "  -l iso_label\n        Label/volume name of the ISO image (if -o option is specified).\n"
	gettext "  -o iso_file\n        Path of ISO image file to create. For
	subcommands patchmedia and\n        makeiso this will be a bootable ISO image.\n        This option must be specified for subcommand makeiso.\n"
	gettext "  -R media_root\n        Top-level directory of on-disk image of Solaris installation media.\n        This option must be specified for subcommand patchmedia.\n"
	gettext "  -r solaris_release\n        Solaris release number for which the Driver Update is intended.\n        It takes the form of 5.10.\n        This option must be specified for subcommand makedu.\n"
	gettext "  -v\n        Verbose. Multiple -v options increase verbosity.\n"

	echo;
}


#
# Process command line options.
# Note: since $OPTIND is a local variable inside functions, upon return
#	from this function global variable $MYOPTIND is set to this value.
#
function process_options # <arg> ...
{
	typeset opt optlist

	case "$SUBCOMMAND" in
	makedu)		optlist='d:fl:o:r:v' ;;
	patchmedia)	optlist='fl:o:R:v' ;;
	makeiso)	optlist='fl:o:v' ;;
	esac

	while getopts ":$optlist" opt
	do
		case $opt in
		d)	DU_OUTDIR=$OPTARG
			;;
		f)	FORCE=1
			;;
		l)	ISOLABEL=$OPTARG
			;;
		o)	ISO=$OPTARG
			if [ ! -z `echo $ISO | $GREP "^/tmp"` ]; then
				gettext "ISO images will not be created on /tmp.\n"
				gettext "Please choose a different output location.\n"
			    exit 3
			fi
			;;
		R)	MEDIA_ROOT=$OPTARG
			;;
		r)	RELEASE=$OPTARG
			;;
		v)	(( VERBOSE_LEVEL += 1 ))
			VERBOSE_OPTS="${VERBOSE_OPTS:--}$opt" # collect options
			;;
		:)	gettext "Option -$OPTARG missing argument.\n"
			usage_short
			return 1
			;;
		*)	gettext "Option -$OPTARG invalid for $SUBCOMMAND.\n"
			usage_short
			return 1
			;;
		esac
	done

	MYOPTIND=$OPTIND
	return 0
}


#
# Check some prerequisites
#
function check_prereqs
{
	typeset utils f

	# List of must-have utilities depends on subcommand.
	case "$SUBCOMMAND" in
	makedu)
		set -A utils $GZIP ${ISO:+$MKISOFS} $PKGTRANS
		;;
	patchmedia)
		set -A utils $CPIO $GZIP ${ISO:+$MKISOFS} $PATCHADD \
			$ROOT_ARCHIVE
		;;
	makeiso)
		set -A utils $MKISOFS
		;;
	esac

	for f in "${utils[@]}"
	do
		if [[ ! -x "$f" ]]
		then
			gettext "Can't find required utility $f.\n"
			return 1
		fi
	done

	# Subcommand packmedia uses the "root_archive unpack_media" command
	# which calls lofiadm -a, which requires write access as
	# determined by /dev/lofictl.  See lofiadm(1m).
	if [[ $SUBCOMMAND = patchmedia && ! -w /dev/lofictl ]]
	then
		gettext "You don't have enough privileges to run lofiadm -a.\n"
		gettext "See lofiadm(1m) for more information.\n"
		return 1
	fi

	return 0
}


#
# Verifies the given packages and collects them in the PACKAGES array.
#
function collect_packages # <arg> ...
{
	typeset obj

	for obj in "$@"
	do
		if [[ ! -e "$obj" ]]
		then
			gettext "Can't find package $obj.\n"
			return 1
		elif [[ ! -f "$obj/pkginfo" ]]
		then
			gettext "$obj is not a package.\n"
			return 1
		fi
		PACKAGES[ ${#PACKAGES[*]} ]=$obj
	done
	return 0
}


#
# Verifies the given packages and patches.  Packages are then collected in
# the array PACKAGES.  Patches are stored in the PATCHES array.
#
function collect_packages_patches # <arg> ...
{
	typeset obj

	for obj in "$@"
	do
		if [[ -f "$obj/patchinfo" ]]
		then
			# Collect patches.
			PATCHES[ ${#PATCHES[*]} ]=$obj
		elif [[ -f "$obj/pkginfo" ]]
		then
			# Collect packages.
			PACKAGES[ ${#PACKAGES[*]} ]=$obj
		elif [[ -e "$obj" ]]
		then
			gettext "$obj is not a package or patch.\n"
			return 1
		else
			gettext "$obj does not exist.\n"
			return 1
		fi
	done
	return 0
}


#
# Ask user whether to overwrite an object, unless -f option was given.
#
function is_overwrite
{
	typeset arg=$1
	typeset -l ans

	(( FORCE )) && return 0
	while true
	do
		gettext "$arg already exists. Overwrite it? (y/n) "
		read ans
		case $ans in
		y*|Y*) return 0 ;;		# go ahead, overwrite
		n*|N*) return 1 ;;		# don't overwrite
		esac
	done
}


#
# Check the format of the Solaris release number $RELEASE.
# Also set $VERSION (for DU format) based on $RELEASE.
#
function check_release
{
	# Allow Major.Minor or Major.Minor.Micro format.
	if [[ $RELEASE != +([0-9]).+([0-9])?(.+([0-9])) ]]
	then
		gettext "Invalid release number specified: $RELEASE.\n"
		return 1
	fi

	# As defined by the ITU spec, a Solaris release number 5.x corresponds
	# to version number 2x (e.g. 5.10 -> 210). Hopefully, by the time we
	# do a 6.x Release we won't need ITUs any more.
	VERSION=$(echo $RELEASE | $SED 's/5\./2/')
}


#
# If an ISO file was specified, get realpath of its parent directory ($ISODIR).
# If the ISO file already exists, ask user to overwrite it, unless -f option
# was specified.
#
function check_iso
{
	if [[ "$ISO" = */* ]]
	then
		ISODIR=$(cd "${ISO%/*}" 2>/dev/null && pwd -P)
		if (( $? ))
		then
			gettext "Can't access parent directory of ISO image.\n"
			return 1
		fi
	else
		ISODIR=$(pwd -P)
	fi

	if [[ -f "$ISO" ]]
	then
		is_overwrite "$ISO" || return 2
		$RM -f "$ISO"
	fi

	return 0
}


#
# If specified, check the Driver Update output directory $DU_OUTDIR (-d option).
# Else set $DU_OUTDIR to a temporary directory.  Also if $DU_OUTDIR/DU
# already exists, ask user whether to overwrite it, unless -f option was given.
#
function check_dudir
{
	typeset	realpath

	if [[ -z "$DU_OUTDIR" ]]
	then
		DU_OUTDIR=$TMP_DIR/dudir
		return 0
	fi

	# Verify user-specified DU output directory.
	if [[ ! -d "$DU_OUTDIR" ]]
	then
		if [ `$MKDIR -p $DU_OUTDIR` ]; then
			gettext "$DU_OUTDIR is not a directory.\n"
			return 1
		fi
	elif [[ ! -w "$DU_OUTDIR" ]]
	then
		gettext "Directory $DU_OUTDIR is not writable.\n"
		return 1
	fi

	# If an ISO image path is also specified, make sure it's not under
	# $DU_OUTDIR since we might take the ISO image of $DU_OUTDIR.
	if [[ -n "$ISODIR" ]]
	then
		realpath=$(cd "$DU_OUTDIR" 2>/dev/null && pwd -P)
		if [[ "$ISODIR" = "$realpath"?(/*) ]]
		then
			gettext "ISO image must not be under Driver Update's output directory ($realpath).\n"
			return 1
		fi
	fi

	# If the DU directory already exists, ask user permission to
	# remove it unless -f option was given.
	if [[ -d "$DU_OUTDIR/DU" ]]
	then
		is_overwrite "$DU_OUTDIR/DU" || return 2
		$RM -rf "$DU_OUTDIR/DU" || return 1
	fi

	return 0
}


#
# Verify $MEDIA_ROOT is indeed a Solaris install media.
#
function check_media_root
{
	if [[ ! -d $(echo "$MEDIA_ROOT"/Solaris*/Tools/Boot) ]]
	then
		gettext "$MEDIA_ROOT is not a Solaris install media.\n"
		return 1
	fi
	return 0
}


#
# Verify there's a miniroot file under $MEDIA_ROOT.  Also set $MINIROOT
# to the path of the miniroot.
#
function check_miniroot
{
	MINIROOT=$MEDIA_ROOT/boot/x86.miniroot
	if [[ ! -f "$MINIROOT" ]]
	then
		return 0 
	fi
	MINIROOT=$MEDIA_ROOT/boot/sparc.miniroot
	if [[ ! -f "$MINIROOT" ]]
	then
		return 0 
	fi
	gettext "Can't find $MEDIA_ROOT/boot/x86.miniroot or $MEDIA_ROOT/boot/sparc.miniroot.\n"
	return 1 
}


#
# Create a non-bootable ISO image of the given directory.
#
function create_nonboot_iso # <dir>
{
	typeset dir vflag i

	if (( $# != 1 ))
	then
		gettext "create_nonboot_iso missing argument.\n"
		return 1
	fi
	dir=$1

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

	# Determine mkisofs' verbose flag depending on $VERBOSE_LEVEL.
	case $VERBOSE_LEVEL in
	0)	vflag=-quiet
		;;
	1)	vflag=				# mkisofs' default verboseness
		;;
	*)	vflag=
		i=$VERBOSE_LEVEL
		while ((i > 0))
		do
			vflag="-v $vflag"
			(( i -= 1 ))
		done
		;;
	esac

	print "Creating ISO image ..."

	# Note: the "-log-file >(cat -u >&2)" and "2>/dev/null" below is a
	#	trick to filter out mkisofs's warning message about being
	#	non-conforming to ISO-9660.
	# We do some funky architecture-specific stuff here so that we can
	# actually create a bootable media image for UltraSPARC systems

	sparc_ISOARGS="-B ... -joliet-long -U"
	i386_ISOARGS="-d -N -r -relaxed-filenames"
	if [[ "$MACH" = "i386" ]]
	then
		ISOARGS=$i386_ISOARGS
	else
		ISOARGS=$sparc_ISOARGS
	fi
	
	$MKISOFS -o "$ISO" \
		-allow-leading-dots \
		$ISOARGS \
		-l -ldots \
		-R -J \
		-V "$ISOLABEL" \
		$vflag \
		-log-file >(cat -u >&2) \
		"$dir" 2>/dev/null
}


#
# Create a bootable Solaris ISO image of the given Solaris install directory.
#
function create_bootable_iso # <dir>
{
	typeset dir vflag saved i

	if (( $# != 1 ))
	then
		gettext "create_bootable_iso missing argument.\n"
		return 1
	fi
	dir=$1

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

	# Determine mkisofs' verbose flag depending on $VERBOSE_LEVEL.
	case $VERBOSE_LEVEL in
	0)	vflag=-quiet
		;;
	1)	vflag=				# mkisofs' default verboseness
		;;
	*)	vflag=
		i=$VERBOSE_LEVEL
		while ((i > 0))
		do
			vflag="-v $vflag"
			(( i -= 1 ))
		done
		;;
	esac

	# Verify the boot block exists under media root. If it does,	
	# verify it's writable since it will be modified with some boot
	# information by mkisofs' -boot-info-table option.
	if [[ ! -f "$dir/$BOOTBLOCK" ]]
	then
		gettext "Can't find $dir/$BOOTBLOCK.\n"
		return 1
	elif [[ ! -w "$dir/$BOOTBLOCK" ]]	
	then
		gettext "$dir/$BOOTBLOCK is not writable.\n"	
		return 1
	fi

	gettext "Creating bootable ISO image ..."

	# Since mkisofs below will modify the file $BOOTBLOCK in-place, save
	# a copy of it first.
	saved=$TMP_DIR/${BOOTBLOCK##*/}
	$CP -f "$dir/$BOOTBLOCK" "$saved" || return

	# Note: the "-log-file >(cat -u >&2)" and "2>/dev/null" below is a
	#	trick to filter out mkisofs's warning message about being
	#	non-conforming to ISO-9660.
	# We do some funky architecture-specific stuff here so that we can
	# actually create a bootable media image for UltraSPARC systems
	sparc_ISOARGS="-G $BOOTBLOCK -B ... -joliet-long -U"
	i386_ISOARGS="-b boot/grub/stage2_eltorito -boot-info-table "
	i386_ISOARGS="$i386_ISOARGS -boot-load-size 4 -c .catalog -d -N "
	i386_ISOARGS="$i386_ISOARGS -no-emul-boot -r -relaxed-filenames"
	if [[ "$MACH" = "i386" ]]
	then
		ISOARGS=$i386_ISOARGS
	else
		ISOARGS=$sparc_ISOARGS
	fi

	cd $dir
	$MKISOFS -o "$ISO" \
		-allow-leading-dots \
		$ISOARGS \
		-l -ldots \
		-R -J \
		-V "$ISOLABEL" \
		$vflag \
		-log-file >(cat -u >&2) \
		"$dir" 2>/dev/null
	i=$?

	# Restore saved El Torito file
	$CP -f "$saved" "$dir/$ELTORITO" 2>/dev/null

	return $i
}


#
# Create a Driver Update (DU) format directory from packages
#
function create_du
{
	typeset distdir tmpdudir pkgs obj statusfile

	# Create DU directory first.
	distdir=$DU_OUTDIR/DU/sol_$VERSION/$MACH
	$MKDIR -p "$distdir/Tools" "$distdir/Product"

	echo "start create DU with MACH $MACH"

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

	# Unfortunately pkgtrans insists that all packages must be in
	# <device1> (see pkgtrans(1)).  The packages can't have any path
	# components.  So we'll create a temporary directory first and then
	# symlinks to the specified packages.  Then run pkgtrans with
	# the temporary directory as <device1>.
	tmpdudir=$TMP_DIR/create_du
	$RM -rf "$tmpdudir"
	$MKDIR -p "$tmpdudir"

	for obj in "${PACKAGES[@]}"
	do
		# Get rid of trailing /'s, if any.
		[[ "$obj" == */ ]] && obj=${obj%%+(/)}

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

		ln -s "$obj" "$tmpdudir" || return

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

	# Package up packages as compressed data stream.
	statusfile=$TMP_DIR/.pkgtrans.status
	(
		# Use fd 9 for redirecting pkgtrans' "Transferring..."
		# messages which normally go to stderr to current stdout
		# (not the following pipeline's stdout).
		exec 9>&1
		{
			$PKGTRANS -s "$tmpdudir" /dev/stdout "${pkgs[@]}" 2>&9
			echo $? > $statusfile
			$TOUCH $statusfile	# make sure file is created
		} | $GZIP -9 > "$distdir/Product/pkgs.gz"
	)

	[[ -s $statusfile && $(<$statusfile) != 0 ]] && return 1

	# Create admin file for pkgadd
	$CAT > "$distdir/Tools/admin" <<"EOF"
mail=
instance=overwrite
partial=nocheck
runlevel=nocheck
idepend=nocheck
rdepend=nocheck
space=nocheck
setuid=nocheck
conflict=nocheck
action=nocheck
EOF

	# Create install.sh
	$CAT > "$distdir/Tools/install.sh" <<"EOF"
#!/sbin/sh
# install.sh -R <basedir> - install packages to basedir
basedir=/
toolsdir=`dirname $0`
tmpfile=/tmp/`basename $0`.$$
gzip=/usr/bin/gzip
while getopts "R:" arg
do
        case "$arg" in
                R) basedir=$OPTARG;;
        esac
done

# /etc/driver_aliases ,/etc/driver_classes
# /etc/name_to_major /etc/minor_perm
# The four file can't append due to ACL defect.
# workaround this by mv and cp

/usr/bin/touch /etc/driver_aliases
if [ $? -ne 0 ] ; then
        /bin/cp /etc/driver_aliases /tmp/driver_aliases
        /bin/mv /etc/driver_aliases /tmp/driver_aliases.bak
        /bin/mv /tmp/driver_aliases /etc
fi
/usr/bin/touch /etc/driver_classes
if [ $? -ne 0 ] ; then
        /bin/cp /etc/driver_classes /tmp/driver_classes
        /bin/mv /etc/driver_classes /tmp/driver_classes.bak
        /bin/mv /tmp/driver_classes /etc
fi
/usr/bin/touch /etc/name_to_major
if [ $? -ne 0 ] ; then
        /bin/cp /etc/name_to_major /tmp/name_to_major
        /bin/mv /etc/name_to_major /tmp/name_to_major.bak
        /bin/mv /tmp/name_to_major /etc
fi
/usr/bin/touch /etc/minor_perm
if [ $? -ne 0 ] ; then
        /bin/cp /etc/minor_perm /tmp/minor_perm
        /bin/mv /etc/minor_perm /tmp/minor_perm.bak
        /bin/mv /tmp/minor_perm /etc
fi     

# Make sure that we've got our own copy of /usr/bin/gzip
# in the tools directory 

if [ ! -f $gzip ] ; then
        gzip=$toolsdir/gzip
        /usr/bin/chmod a+x "$toolsdir/gzip" 2>/dev/null
fi

$gzip -c -d "$toolsdir/../Product/pkgs.gz" > $tmpfile &&
	/usr/sbin/pkgadd -R "$basedir" -d "$tmpfile" -a "$toolsdir/admin" all
status=$?
rm -f "$tmpfile"
exit $status
EOF
	$CHMOD a+rx "$distdir/Tools/install.sh"

	$CP -f /usr/bin/gzip "$distdir/Tools" 2>/dev/null
}


#
# Unpack the miniroot of a Solaris install media.
#
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 ]; then
		if [ -d $MEDIA_ROOT/Solaris_10 -a -d $MEDIA_ROOT/Solaris_11 ]; then
			# we _do_ care, because we're not patching a Solaris
			# update media instance
			gettext "There was an error unpacking the media from $MEDIA_ROOT\n"
			exit 1
		fi
	fi
}


#
# Pack an unpacked miniroot onto a Solaris install media.
#
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 ]; then
		if [ -d $MEDIA_ROOT/Solaris_10 -a -d $MEDIA_ROOT/Solaris_11 ]; then
			# we _do_ care, because we're not patching a Solaris 
			# update media instance
			gettext "There was an error packing the media from $MEDIA_ROOT\n"
			exit 1
		fi
	fi
	gettext "Done.\n"
}


#
# Add packages to a Solaris install media.  Also install these packages
# onto the miniroot.
#
function add_pkgs
{
	typeset icmd statusfile i

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

	statusfile=$TMP_DIR/.add_pkgs.status

	DU_OUTDIR=$ITUDIR/$ITU_COUNTDIR
	(( ITU_COUNTDIR += 1 ))
	$MKDIR "$DU_OUTDIR" || return

	#
	# Add a Driver Update directory on the media
	#
	echo;
	gettext "Adding package(s) to media root.\n"
	create_du || return

	#
	# Using the Driver Update above install the packages onto the miniroot.
	#
	echo;
	gettext "Installing package(s) onto miniroot.\n"
	icmd=$DU_OUTDIR/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" -v vlevel=$VERBOSE_LEVEL '
		# If not verbose, print certain lines from patchadd.
		(vlevel == 0) && /^Installing/ {print}
		(vlevel == 0) && /^Installation.*successful/ {print}

		# If verbose, print every line to stderr.
		(vlevel > 0) {print > "/dev/stderr"}

		# Save every line to logfile.
		{print >> logfile}
	' || return
	[[ -s "$statusfile" ]] && return $(<$statusfile)
	return 0
}


#
# Add patches to a Solaris install media.  Also patch the miniroot with
# these patches
#
function add_patches
{
	typeset distdir tmpdir icmd obj patches statusfile

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

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

	$RM -rf "$tmpdir"

	distdir=$ITUDIR/$ITU_COUNTDIR/DU/sol_$VERSION/$MACH
	(( ITU_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/$ITU_COUNTDIR/DU/sol_$VERSION
		$LN -s sparc sun4v
		$LN -s sparc sun4u
	else
		cd $ITUDIR/$ITU_COUNTDIR/DU/sol_$VERSION
		$LN -s i386 i86pc
	fi	

	#
	# Add packages onto media root
	#
	echo;
	gettext "Adding patch(es) to media root.\n"

	# 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 a 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.
	$RM -f "$statusfile"
	(
		# Save current stdout as fd 8.  This doesn't point to the
		# gzip pipeline below.
		exec 8>&1

		{
			# Fd 9 is used later on for filtering out cpio's
			# reporting total blocks to stderr but yet still
			# print other error messages.  fd 9 refers to the
			# pipeline to gzip.
			exec 9>&1

			cd "$tmpdir"
			for obj in "${patches[@]}"
			do
				print -u8 "Transferring patch $obj."
				$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`.$$
gzip=/usr/bin/gzip
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

# Make sure that we've got our own copy of /usr/bin/gzip
# in the tools directory 

if [ ! -f $gzip ] ; then
        gzip=$toolsdir/gzip
        /usr/bin/chmod a+x "$toolsdir/gzip" 2>/dev/null
fi

$gzip -c -d "$toolsdir/../Product/patches.gz" > $tmpfile || exit
cd "$patchdir"
/bin/cpio -idum < "$tmpfile" || exit
patchadd -R "$basedir" -nu *
EOF
	$CHMOD a+rx "$distdir/Tools/install.sh"

	$CP -f /usr/bin/gzip "$distdir/Tools" 2>/dev/null

	#
	# Patch the miniroot
	#
	echo;
	gettext "Installing patch(es) onto miniroot.\n"
	$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" -v vlevel=$VERBOSE_LEVEL '
		# If not verbose, print certain lines from patchadd.
		(vlevel == 0) && /^Patch.*successful/ {print}

		# If verbose, print every line to stderr.
		(vlevel > 0) {print > "/dev/stderr"}

		# Save every line to logfile.
		{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
}


#
# Starting point for makedu subcommand:
#
#	Convert packages into Driver Update (DU) directory format.
#
function makedu # <arg> ...
{
	typeset i

	process_options "$@" || return
	shift 'MYOPTIND - 1'

	if (( $# == 0 ))
	then
		gettext "Please specify one or more packages.\n"
		usage_short
		return 1
	fi

	# Release number must be specified.
	if [[ -z "$RELEASE" ]]
	then
		gettext "Please specify Solaris release number (-r option).\n"
		usage_short
		return 1
	fi
	check_release || return

	# Either -d or -o option, or both, must be specified.
	if [[ -z "$DU_OUTDIR" && -z "$ISO" ]]
	then
		gettext "Please specify either -d or -o option (or both).\n"
		usage_short
		return 1
	fi

	if [[ -n "$ISO" ]]
	then
		check_iso || return
		${ISOLABEL:=DU sol_$VERSION} 2>/dev/null 		# default ISO label
	fi
	check_dudir || return		# should be called after check_iso

	# Rest of arguments must be packages.
	collect_packages "$@" || return

	check_prereqs || return

	# Create DU and the (non-bootable) ISO image (if requested).
	create_du && create_nonboot_iso "$DU_OUTDIR"
	if (( i=$? ))
	then
		$RM -rf "$DU_OUTDIR/DU"
		[[ -n "$ISO" ]] && rm -f "$ISO"
	else
		if [[ "$MACH" != "i386" ]]
		then
			echo "This DU must be written as either an ISO (hsfs)"
			echo "or a *ufs* filesystem. DO NOT USE pcfs!"
		fi
	fi
	return $i
}


#
# Starting point for patchmedia subcommand:
#
#	Patch a Solaris install image with the given packages and patches.
#
function patchmedia # <arg> ...
{
	typeset soldir

	process_options "$@" || return
	shift 'MYOPTIND - 1'

	if (( $# == 0 ))
	then
		gettext "Please specify one or more packages or patches.\n"
		usage_short
		return 1
	fi

	# -R option must be specified
	if [[ -z "$MEDIA_ROOT" ]]
	then
		gettext "Please specify Solaris media root (-R option).\n"
		usage_short
		return 1
	fi
	check_media_root || return

	# Get the Solaris directory under $MEDIA_ROOT.
	soldir=$($LS -d $MEDIA_ROOT/Solaris* 2>/dev/null)
	if [[ -z "$soldir" ]]
	then
		gettext "Can't find Solaris directory in $MEDIA_ROOT.\n"
		return 1
	fi

	# Extract the Solaris release number from the Solaris_* directory.
	RELEASE=5.${soldir##*Solaris_}
	check_release || return

	# If user specifies an ISO image to create.
	if [[ -n "$ISO" ]]
	then
		check_iso || return
		${ISOLABEL:=${soldir##*/}}		# default ISO label
	fi

	# Rest of arguments must be packages or patches.
	collect_packages_patches "$@" || return

	# Verify we have some important utilities we need.
	check_prereqs || return

	# Verify there's miniroot file in $MEDIA_ROOT.
	check_miniroot || return

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

	# 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.
	ITU_COUNTDIR=$($LS -d "$ITUDIR"/+([0-9]) 2>/dev/null |
		$SED 's;.*/;;' | $SORT -rn | $HEAD -1)
	if [[ $ITU_COUNTDIR == *( ) ]]	# ITU_COUNTDIR is a typeset -Zn var
	then
		ITU_COUNTDIR=0
	else
		(( ITU_COUNTDIR += 1 ))
	fi

	unpack_media || return
	add_pkgs && add_patches
	if (( status=$? )) && [[ -s "$LOGFILE" ]]
	then
		echo;
		gettext "A package or patch installation has failed.\n"
		gettext "Messages from pkgadd and patchadd have been saved in $LOGFILE\n"
		return $status
	else
		rm -f "$LOGFILE"
	fi
	print
	repack_media || return
	create_bootable_iso "$MEDIA_ROOT"
	gettext "$MEDIA_ROOT has been successfully patched\n"
}


#
# Starting point for makeiso subcommand:
#
#	Create a bootable ISO image of a Solaris install image.
#
function makeiso # <arg> ..
{
	process_options "$@" || return
	shift 'MYOPTIND - 1'

	if (( $# == 0 ))
	then
		gettext "Please specify the Solaris media root.\n"
		usage_short
		return 1
	elif (( $# > 1 ))
	then
		gettext "Too many arguments supplied.\n"
		usage_short
		return 1
	fi
	MEDIA_ROOT=$1
	check_media_root || return

	# ISO image must be specified.
	if [[ -z "$ISO" ]]
	then
		gettext "Please specify ISO image file (-o option).\n"
		usage_short
		return 1
	fi
	check_iso || return

	# If user doesn't specify ISO label, use the Solaris_* directory name
	# under $MEDIA_ROOT.
	if [[ -z "$ISOLABEL" ]]
	then
		ISOLABEL=$(echo "$MEDIA_ROOT"/Solaris*)
		ISOLABEL=${ISOLABEL##*/}
	fi

	check_prereqs || return
	create_bootable_iso "$MEDIA_ROOT"
}


#
# Main
#
trap cleanup EXIT

# Numbered subdirectories under ITU directory $ITUDIR.
typeset -Z3 ITU_COUNTDIR=0

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

# Reset arrays.
unset PACKAGES PATCHES

DU_OUTDIR=
FORCE=0
ISO=
ISOLABEL=
MEDIA_ROOT=
RELEASE=
SUBCOMMAND=
VERBOSE_LEVEL=0
VERBOSE_OPTS=

if (( $# == 0 ))
then
	usage_long
	return 1
fi
typeset -l SUBCOMMAND=$1			# ignore case
shift

if [[ $SUBCOMMAND != @(makedu|patchmedia|makeiso) ]]
then
	# Be nice: allow some subcommands that cry out "help".
	case "$SUBCOMMAND" in
	*(-)help|*(-)usage|-h|-\?)
		usage_long
		return 0
		;;
	*)
		gettext "Invalid subcommand: $SUBCOMMAND.\n"
		usage_short
		return 1
		;;
	esac
fi

$MKDIR -p "$TMP_DIR" || return
$RM -f "$LOGFILE"

# Run the subcommand.
$SUBCOMMAND "$@"