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