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
31 readonly PROG=$0
32 readonly TMP_DIR=${TMPDIR:-/tmp}/${PROG##*/}.$$
33 readonly LOGFILE=${TMPDIR:-/tmp}/${PROG##*/}-log.$$
34
35 # Must-have utilities
36 readonly CPIO=/bin/cpio
37 readonly GZIP=/bin/gzip
38 readonly MKISOFS=/usr/bin/mkisofs
39 readonly PATCHADD=/usr/sbin/patchadd
40 readonly LOFIADM=/usr/sbin/lofiadm
41 readonly MKDIR=/usr/bin/mkdir
42 readonly RM=/usr/bin/rm
43 readonly CP=/usr/bin/cp
44 readonly MKBOOTMEDIA=/usr/bin/mkbootmedia
45 readonly PKG2DU=/usr/bin/pkg2du
46 readonly TOUCH=/usr/bin/touch
47 readonly NAWK=/usr/bin/nawk
48 readonly CHMOD=/usr/bin/chmod
49 readonly GREP=/usr/bin/grep
50 readonly LS=/usr/bin/ls
51 readonly LN=/usr/bin/ln
52 readonly SED=/usr/bin/sed
53 readonly CAT=/usr/bin/cat
54 readonly FIND=/usr/bin/find
55 readonly HEAD=/usr/bin/head
56 readonly SORT=/usr/bin/sort
57 readonly UNAME=/usr/bin/uname
58 readonly MACH=`$UNAME -p`
59
60 ROOT_ARCHIVE=/usr/sbin/root_archive
61 # for gettext
62 TEXTDOMAIN=SUNW_OST_OSCMD
63 export TEXTDOMAIN
64
65
66 function 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
76 function 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
100 function cleanup
101 {
102 $RM -rf "$TMP_DIR"
103 }
104
105
106 function 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
124 function 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
148 function 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
161 function 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
183 function 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
234 function 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
328 basedir=/
329 toolsdir=`dirname $0`
330 tmpdir=/tmp/`basename $0`.$$
331 trap "/bin/rm -rf $tmpdir" 0
332 while getopts "R:" arg
333 do
334 case "$arg" in
335 R) basedir=$OPTARG;;
336 esac
337 done
338 /bin/mkdir -p "$tmpdir" || exit
339 tmpfile=$tmpdir/patches
340 patchdir=$tmpdir/patchdir
341 /bin/mkdir "$patchdir" || exit
342 /usr/bin/gzip -c -d "$toolsdir/../Product/patches.gz" > $tmpfile || exit
343 cd "$patchdir"
344 /bin/cpio -idum < "$tmpfile" || exit
345 /usr/sbin/patchadd -R "$basedir" -nu *
346 EOF
347 $CHMOD a+rx "$distdir/Tools/install.sh"
348
349 }
350
351
352 #
353 # Main
354 #
355 trap cleanup EXIT
356
357 ISO=
358 ISOLABEL=
359 MEDIA_ROOT=
360 VERBOSE_LEVEL=0
361 VERBOSE_OPTS=
362
363 while getopts ':d:o:l:v' opt
364 do
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
388 done
389 shift 'OPTIND - 1'
390
391 unset PACKAGES PATCHES # reset arrays
392 collect_objs "$@"
393
394 # If there are no packages or patches, then print info and we're done.
395 if (( ${#PACKAGES[*]} == 0 && ${#PATCHES[*]} == 0 ))
396 then
397 gettext "No valid package or patch was specified.\nPackages and patches must be unpacked.\n"
398 usage
399 exit 1
400 fi
401
402 # -d option must be specified
403 if [[ -z "$MEDIA_ROOT" ]]
404 then
405 gettext "No media root (-d option) was specified.\n"
406 usage
407 exit 1
408 fi
409
410 check_prereqs # must be called after $ISO is possibly set
411
412 # Verify it's a Solaris install media.
413 SOLARIS_DIR=$($LS -d $MEDIA_ROOT/Solaris* 2>/dev/null)
414 if [[ -z "$SOLARIS_DIR" || ! -d "$SOLARIS_DIR/Tools/Boot" ]]
415 then
416 gettext "$MEDIA_ROOT is not valid Solaris install media.\n"
417 exit 1
418 fi
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).
425 RELEASE=5.${SOLARIS_DIR##*Solaris_}
426 VERSION=$(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
433 MINIROOT=
434 # Relative to a Solaris media root.
435 if [ "$MACH" = "sparc" ]; then
436 MINIROOT=$MEDIA_ROOT/boot/sparc.miniroot
437 else
438 # x86/x64
439 MINIROOT=$MEDIA_ROOT/boot/x86.miniroot
440 fi
441 if [ ! -f $MINIROOT ]; then
442 gettext "No boot/x86.miniroot under media root.\n"
443 exit 1
444 fi
445
446 # Where to unpack the miniroot.
447 UNPACKED_ROOT=${TMP_DIR}/miniroot
448
449 # Create the ITU directory on the media, if necessary
450 ITUDIR=$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.
456 typeset -Z3 COUNTDIR
457 COUNTDIR=$($LS -d "$ITUDIR"/+([0-9]) 2>/dev/null | $SED 's;.*/;;' |
458 $SORT -rn | $HEAD -1)
459 if [[ $COUNTDIR == *( ) ]]
460 then
461 COUNTDIR=0
462 else
463 (( COUNTDIR += 1 ))
464 fi
465
466 unpack_media || exit
467 add_pkgs && add_patches
468 if (( status=$? )) && [[ -s "$LOGFILE" ]]
469 then
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
473 else
474 $RM -f "$LOGFILE"
475 fi
476 print
477 repack_media || exit
478 mkiso