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