1#!/bin/ksh93 -p
2#
3# CDDL HEADER START
4#
5# The contents of this file are subject to the terms of the
6# Common Development and Distribution License (the "License").
7# You may not use this file except in compliance with the License.
8#
9# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10# or http://www.opensolaris.org/os/licensing.
11# See the License for the specific language governing permissions
12# and limitations under the License.
13#
14# When distributing Covered Code, include this CDDL HEADER in each
15# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16# If applicable, add the following below this CDDL HEADER, with the
17# fields enclosed by brackets "[]" replaced with your own identifying
18# information: Portions Copyright [yyyy] [name of copyright owner]
19#
20# CDDL HEADER END
21#
22#
23# Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
24# Use is subject to license terms.
25#
26
27#
28# updatemedia - modify Solaris media with patches and packages
29#
30
31readonly PROG=$0
32readonly TMP_DIR=${TMPDIR:-/tmp}/${PROG##*/}.$$
33readonly LOGFILE=${TMPDIR:-/tmp}/${PROG##*/}-log.$$
34
35# Must-have utilities
36readonly CPIO=/bin/cpio
37readonly GZIP=/bin/gzip
38readonly MKISOFS=/usr/bin/mkisofs
39readonly PATCHADD=/usr/sbin/patchadd
40readonly LOFIADM=/usr/sbin/lofiadm
41readonly MKDIR=/usr/bin/mkdir
42readonly RM=/usr/bin/rm
43readonly CP=/usr/bin/cp
44readonly MKBOOTMEDIA=/usr/bin/mkbootmedia
45readonly PKG2DU=/usr/bin/pkg2du
46readonly TOUCH=/usr/bin/touch
47readonly NAWK=/usr/bin/nawk
48readonly CHMOD=/usr/bin/chmod
49readonly GREP=/usr/bin/grep
50readonly LS=/usr/bin/ls
51readonly LN=/usr/bin/ln
52readonly SED=/usr/bin/sed
53readonly CAT=/usr/bin/cat
54readonly FIND=/usr/bin/find
55readonly HEAD=/usr/bin/head
56readonly SORT=/usr/bin/sort
57readonly UNAME=/usr/bin/uname
58readonly MACH=`$UNAME -p`
59
60ROOT_ARCHIVE=/usr/sbin/root_archive
61# for gettext
62TEXTDOMAIN=SUNW_OST_OSCMD
63export TEXTDOMAIN
64
65
66function usage
67{
68	gettext "Usage:\n${PROG##*/} -d <media-root> [-v] [-l <label>] [-o <iso>]\n        <pkg_or_patch> [<pkg_or_patch> ...]\n"
69	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"
70	gettext "  -l <label>\n        Label/volume name of the ISO image (if -o option is specified).\n"
71	gettext "  -o <iso>\n        Create a Solaris ISO image of <media-root>.\n"
72	gettext "  -v\n        Verbose.  Multiple -v options increase verbosity.\n"
73}
74
75
76function check_prereqs
77{
78	typeset f
79
80	# We must have these utilities.
81	for f in $CPIO $GZIP ${ISO:+$MKISOFS} $PATCHADD $ROOT_ARCHIVE
82	do 
83		if [[ ! -x "$f" ]]
84		then
85			gettext "Cannot find required utility $f\n"
86			exit 1
87		fi
88	done
89
90	# root_archive unpack_media calls lofiadm -a, which requires
91	# write access as determined by /dev/lofictl.  See lofiadm(1m).
92	if [[ ! -w /dev/lofictl ]]
93	then
94		gettext "You do not have enough privileges to run lofiadm -a).\nSee lofiadm(1m) for more information.\n"
95		exit 1
96	fi
97}
98
99
100function cleanup
101{
102	$RM -rf "$TMP_DIR"
103}
104
105
106function unpack_media
107{
108	# Create temp directory to unpack the miniroot.
109	$MKDIR -p "$UNPACKED_ROOT"
110
111	# We need to use the unpackmedia option to correctly apply patches
112	gettext "Unpacking media ..."
113	$ROOT_ARCHIVE unpackmedia "$MEDIA_ROOT" "$UNPACKED_ROOT" > /dev/null 2>&1 
114	if [ $? != 0 -a ! -d $MEDIA_ROOT/Solaris_10 ]; then
115		# we _do_ care, because we're not patching a Solaris 10
116		# update media instance
117		gettext "\nThere was an error unpacking the media from $MEDIA_ROOT\n"
118		exit 1
119	fi
120	echo;
121}
122
123
124function repack_media
125{
126	gettext "Repacking media ..."
127
128	# We need to ensure that we're using the appropriate version
129	# of root_archive for the media that we're packing/unpacking.
130	# The onnv version of root_archive differs from the S10 version,
131	# and this will cause problems on re-packing. So we sneakily
132	# use the version that we've just unpacked
133	if [ -d $MEDIA_ROOT/Solaris_10 ]; then
134		ROOT_ARCHIVE=$UNPACKED_ROOT/boot/solaris/bin/root_archive
135	fi
136
137	$ROOT_ARCHIVE packmedia "$MEDIA_ROOT" "$UNPACKED_ROOT" > /dev/null 2>&1
138	if [ $? != 0 -a ! -d $MEDIA_ROOT/Solaris_10 ]; then
139		# we _do_ care, because we're not patching a Solaris 10
140		# update media instance
141		gettext "\nThere was an error unpacking the media from $MEDIA_ROOT\n"
142		exit 1
143	fi
144	echo;
145}
146
147
148function mkiso
149{
150	typeset vflag
151
152	# Skip if no ISO image was specified.
153	[[ -z "$ISO" ]] && return 0
154
155	gettext "Creating ISO image ..."
156	$MKBOOTMEDIA $VERBOSE_OPTS -l "$ISOLABEL" "$MEDIA_ROOT" "$ISO"
157	echo;
158}
159
160
161function collect_objs # <pkg_or_patch> ...
162{
163	typeset obj fail=0
164
165	for obj
166	do
167		if [[ -f "$obj"/patchinfo ]]
168		then
169			PATCHES[ ${#PATCHES[*]} ]=$obj
170		elif [[ -f "$obj"/pkginfo ]]
171		then
172			PACKAGES[ ${#PACKAGES[*]} ]=$obj
173		else
174			gettext "$obj is not in package or patch format\n"
175			(( fail += 1 ))
176		fi
177	done
178	(( fail )) && return 1
179	return 0
180}
181
182
183function add_pkgs
184{
185	typeset dudir icmd statusfile
186
187	(( ${#PACKAGES[*]} == 0 )) && return
188
189	statusfile=$TMP_DIR/.add_pkgs.status
190
191	trap '$RM -f $statusfile' EXIT
192
193	dudir=$ITUDIR/$COUNTDIR
194	(( COUNTDIR += 1 ))
195	$MKDIR "$dudir" || return
196
197	# Add a Driver Update directory on the media
198	echo;
199	gettext "Adding package(s) to media root."
200	$PKG2DU -r "$RELEASE" -f -d "$dudir" $VERBOSE_OPTS \
201	    "${PACKAGES[@]}" || return
202
203	# Using the Driver Update above install the packages onto the miniroot.
204	echo;
205	gettext "Installing package(s) onto miniroot."
206	icmd=$dudir/DU/sol_$VERSION/$MACH/Tools/install.sh
207	if [[ ! -f "$icmd" ]]
208	then
209		# This shouldn't happen, but just in case.
210		gettext "Cannot find $icmd\n"
211		return 1
212	fi
213	[[ ! -x "$icmd" ]] && $CHMOD a+x "$icmd"
214
215	$RM -f "$statusfile"
216        {
217		"$icmd" -R "$UNPACKED_ROOT"
218		if (( i=$? ))
219		then
220			echo $i > "$statusfile"
221			$TOUCH "$statusfile"  # make sure file is created
222		fi
223        } 2>&1 | $NAWK -v logfile="$LOGFILE" '
224		# Print certain lines from $icmd, save all in logfile.
225		/^Installing/ {print}
226		/^Installation.*successful/ {print}
227		{print >> logfile}
228	' || return
229	[[ -s "$statusfile" ]] && return $(<$statusfile)
230	return 0
231}
232
233
234function add_patches
235{
236	typeset distdir tmpdir icmd obj patches statusfile
237
238	(( ${#PATCHES[*]} == 0 )) && return
239
240	tmpdir=$TMP_DIR/patches
241	statusfile=$TMP_DIR/.add_patches.status
242
243	trap '$RM -rf $tmpdir $statusfile' EXIT
244
245	distdir=$ITUDIR/$COUNTDIR/DU/sol_$VERSION/$MACH
246	(( COUNTDIR += 1 ))
247
248	$MKDIR -p "$distdir/Tools" "$distdir/Product" "$tmpdir" || return
249
250	# If we're running this script on sun4[vu], then create a symlink
251	# to the other UltraSPARC architecture
252	if [[ "$MACH" != "i386" ]]
253	then
254		cd $ITUDIR/$COUNTDIR/DU/sol_$VERSION/
255		$LN -s sparc sun4v
256		$LN -s sparc sun4u
257	else
258		cd $ITUDIR/$COUNTDIR/DU/sol_$VERSION/
259		$LN -s i386 i86pc
260	fi	
261
262	# Patch the miniroot
263	echo;
264	gettext "Installing patch(es) onto miniroot."
265	$RM -f "$statusfile"
266	{
267		$PATCHADD -udn -C "$UNPACKED_ROOT" "${PATCHES[@]}"
268		if (( i=$? ))
269		then
270			echo $i > "$statusfile"
271			$TOUCH "$statusfile" # make sure file is created
272		fi
273        } 2>&1 | $NAWK -v logfile="$LOGFILE" '
274		# Print certain lines from patchadd, save all in logfile.
275		/^Patch.*successful/ {print}
276		{print >> logfile}
277	' || return
278
279	[[ -s "$statusfile" ]] && return $(<$statusfile)
280
281	# Remove patch log files to save space when miniroot is repacked.
282	$RM -rf "$UNPACKED_ROOT"/var/sadm/patch
283
284	# Symlink each patch in a temp dir so a single cpio/gzip can work.
285	for obj in "${PATCHES[@]}"
286	do
287		# Get rid of trailing /'s, if any.
288		[[ "$obj" == */ ]] && obj=${obj%%+(/)}
289
290		# Make sure it's full pathname.
291		[[ "$obj" != /* ]] && obj=$ORIGPWD/$obj
292
293		$LN -s "$obj" "$tmpdir" || return
294
295		# Remember just the file component.
296		patches[ ${#patches[*]} ]=${obj##*/}
297	done
298
299	# Package up patches as compressed cpio archive.
300	echo;
301	gettext "Adding patch(es) to media root.\n"
302	$RM -f "$statusfile"
303	(
304		cd "$tmpdir"
305		# fd 9 is used later on for filtering out cpio's
306		# reporting total blocks to stderr but yet still
307		# print other error messages.
308		exec 9>&1
309		for obj in "${patches[@]}"
310		do
311			gettext "Transferring patch $obj\n"
312			$FIND "$obj/." -follow -print
313			if (( i=$? ))
314			then
315				echo $i > "$statusfile"
316				$TOUCH "$statusfile"
317				return $i
318			fi
319		done | $CPIO -oc 2>&1 >&9 | $GREP -v '^[0-9]* blocks' >&2
320	) | $GZIP -9 > "$distdir/Product/patches.gz" || return
321
322	[[ -s "$statusfile" ]] && return $(<$statusfile)
323
324	# Create install.sh
325	$CAT > "$distdir/Tools/install.sh" <<"EOF"
326#!/sbin/sh
327# install.sh -R <basedir> - install patches to basedir
328basedir=/
329toolsdir=`dirname $0`
330tmpdir=/tmp/`basename $0`.$$
331trap "/bin/rm -rf $tmpdir" 0
332while getopts "R:" arg
333do
334        case "$arg" in
335                R) basedir=$OPTARG;;
336        esac
337done
338/bin/mkdir -p "$tmpdir" || exit
339tmpfile=$tmpdir/patches
340patchdir=$tmpdir/patchdir
341/bin/mkdir "$patchdir" || exit
342/usr/bin/gzip -c -d "$toolsdir/../Product/patches.gz" > $tmpfile || exit
343cd "$patchdir"
344/bin/cpio -idum < "$tmpfile" || exit
345/usr/sbin/patchadd -R "$basedir" -nu *
346EOF
347	$CHMOD a+rx "$distdir/Tools/install.sh"
348
349}
350
351
352#
353# Main
354#
355trap cleanup EXIT
356
357ISO=
358ISOLABEL=
359MEDIA_ROOT=
360VERBOSE_LEVEL=0
361VERBOSE_OPTS=
362
363while getopts ':d:o:l:v' opt
364do
365	case $opt in
366	d)	MEDIA_ROOT=$OPTARG
367		;;
368	o)	ISO=$OPTARG
369		if [ ! -z `echo $ISO | $GREP "^/tmp"` ]; then
370		        gettext "ISO images will not be created on /tmp.\nPlease choose a different output location.\n"
371			exit 3
372		fi
373		;;
374	l)	ISOLABEL=$OPTARG
375		;;
376	v)	(( VERBOSE_LEVEL += 1 ))
377		VERBOSE_OPTS="${VERBOSE_OPTS:--}$opt"	# collect -v options
378		;;
379	:)	gettext "Option -$OPTARG missing argument.\n"
380		usage
381		exit 1
382		;;
383	*)	gettext "Option -$OPTARG is invalid.\n"
384		usage
385		exit 2
386		;;
387	esac
388done
389shift 'OPTIND - 1'
390
391unset PACKAGES PATCHES				# reset arrays
392collect_objs "$@"
393
394# If there are no packages or patches, then print info and we're done.
395if (( ${#PACKAGES[*]} == 0 && ${#PATCHES[*]} == 0 ))
396then
397	gettext "No valid package or patch was specified.\nPackages and patches must be unpacked.\n"
398	usage
399	exit 1
400fi
401
402# -d option must be specified
403if [[ -z "$MEDIA_ROOT" ]]
404then
405	gettext "No media root (-d option) was specified.\n"
406	usage
407	exit 1
408fi
409
410check_prereqs		# must be called after $ISO is possibly set
411
412# Verify it's a Solaris install media.
413SOLARIS_DIR=$($LS -d $MEDIA_ROOT/Solaris* 2>/dev/null)
414if [[ -z "$SOLARIS_DIR" || ! -d "$SOLARIS_DIR/Tools/Boot" ]]
415then
416	gettext "$MEDIA_ROOT is not valid Solaris install media.\n"
417	exit 1
418fi
419
420$MKDIR -p "$TMP_DIR" || exit 1
421
422# Extract the Solaris release number from the Solaris_* directory and the
423# corresponding version number.  As defined by the ITU spec, a Solaris release
424# number 5.x corresponds to version number 2x (e.g. 5.10 -> 210).
425RELEASE=5.${SOLARIS_DIR##*Solaris_}
426VERSION=$(echo $RELEASE | $SED 's/5\./2/')
427
428# If user didn't specify ISO label, use the Solaris_* dir as label.
429${ISOLABEL:=${SOLARIS_DIR##*/}} 2>/dev/null
430
431# Verify miniroot
432
433MINIROOT=
434# Relative to a Solaris media root.
435if [ "$MACH" = "sparc" ]; then
436	MINIROOT=$MEDIA_ROOT/boot/sparc.miniroot
437else
438	# x86/x64
439	MINIROOT=$MEDIA_ROOT/boot/x86.miniroot
440fi
441if [ ! -f $MINIROOT ]; then
442	gettext "No boot/x86.miniroot under media root.\n"
443	exit 1
444fi
445
446# Where to unpack the miniroot.
447UNPACKED_ROOT=${TMP_DIR}/miniroot
448
449# Create the ITU directory on the media, if necessary
450ITUDIR=$MEDIA_ROOT/ITUs
451$MKDIR -p "$ITUDIR" || exit 1
452
453# The ITU directory might contain multiple driver updates already, each in a
454# separate numbered subdirectory.  So look for the subdirectory with the
455# highest number and we'll add the packages and patches on the next one.
456typeset -Z3 COUNTDIR
457COUNTDIR=$($LS -d "$ITUDIR"/+([0-9]) 2>/dev/null | $SED 's;.*/;;' |
458		$SORT -rn | $HEAD -1)
459if [[ $COUNTDIR == *( ) ]]
460then
461	COUNTDIR=0
462else
463	(( COUNTDIR += 1 ))
464fi
465
466unpack_media || exit
467add_pkgs && add_patches
468if (( status=$? )) && [[ -s "$LOGFILE" ]]
469then
470	echo;
471	gettext "A package or patch installation has failed.\nMessages from pkgadd and patchadd have been saved in $LOGFILE\n"
472	exit $status
473else
474	$RM -f "$LOGFILE"
475fi
476print
477repack_media || exit
478mkiso
479