1 #!/bin/ksh -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 # Restrict executables to /bin, /usr/bin, /usr/sbin and /usr/sfw/bin
  28 PATH=/bin:/usr/bin:/usr/sbin:/usr/sfw/bin
  29 
  30 export PATH
  31 
  32 # Setup i18n output
  33 TEXTDOMAIN="SUNW_OST_OSCMD"
  34 export TEXTDOMAIN
  35 
  36 # Log passed arguments to file descriptor 2
  37 log()
  38 {
  39         [[ -n $logfile ]] && echo "$@" >&2
  40 }
  41 
  42 #
  43 # Send the provided printf()-style arguments to the screen and to the
  44 # logfile.
  45 #
  46 screenlog()
  47 {
  48         typeset fmt="$1"
  49         shift
  50 
  51         printf "$fmt\n" "$@"
  52         [[ -n $logfile ]] && printf "$fmt\n" "$@" >&2
  53 }
  54 
  55 # Print and log provided text if the shell variable "verbose_mode" is set
  56 verbose()
  57 {
  58         [[ -n $verbose_mode ]] && echo "$@"
  59         [[ -n $logfile ]] && [[ -n $verbose_mode ]] && echo "$@" >&2
  60 }
  61 
  62 unsupported_cpu=\
  63 $(gettext "ERROR: Cannot install branded zone: processor must be %s-compatible")
  64 
  65 cmd_not_found=$(gettext "Required command '%s' cannot be found!")
  66 cmd_not_exec=$(gettext "Required command '%s' not executable!")
  67 zone_initfail=$(gettext "Attempt to initialize zone '%s' FAILED.")
  68 path_abs=$(gettext "Pathname specified to -d '%s' must be absolute.")
  69 
  70 cmd_h=$(gettext "%s -z <zone name> %s -h")
  71 cmd_full=\
  72 $(gettext "%s -z <zone name> %s [-v | -s] [-d <dir>|<device>] [<cluster> ... ]")
  73 
  74 both_modes=$(gettext "%s: error: cannot select both silent and verbose modes")
  75 
  76 not_found=$(gettext "%s: error: file or directory not found.")
  77 
  78 wrong_type=\
  79 $(gettext "%s: error: must be a gzip, bzip2, .Z or uncompressed tar archive.")
  80 
  81 not_readable=$(gettext "Cannot read file '%s'")
  82 
  83 no_install=$(gettext "Could not create install directory '%s'")
  84 no_log=$(gettext "Could not create log directory '%s'")
  85 no_logfile=$(gettext "Could not create log file '%s'")
  86 
  87 root_full=$(gettext "Zonepath root %s exists and contains data; remove or move aside prior to install.")
  88 
  89 install_zone=$(gettext "Installing zone '%s' at root directory '%s'")
  90 install_from=$(gettext "from archive '%s'")
  91 
  92 install_fail=$(gettext "Installation of zone '%s' FAILED.")
  93 see_log=$(gettext "See the log file:\n  '%s'\nfor details.")
  94 
  95 install_abort=$(gettext "Installation of zone '%s' aborted.")
  96 install_good=$(gettext "Installation of zone '%s' completed successfully.")
  97 
  98 # Check if commands passed in exist and are executable.
  99 check_cmd()
 100 {
 101         for cmd in "$@"; do
 102                 if [[ ! -f $cmd ]]; then
 103                         screenlog "$cmd_not_found" "$cmd"
 104                         screenlog "$install_abort" "$zonename"
 105                         exit $ZONE_SUBPROC_NOTCOMPLETE
 106                 fi
 107 
 108                 if [[ ! -x $cmd ]]; then
 109                         screenlog "$cmd_not_exec" "$cmd"
 110                         screenlog "$install_abort" "$zonename"
 111                         exit $ZONE_SUBPROC_NOTCOMPLETE
 112                 fi
 113         done
 114 }
 115 
 116 # Post process as tarball-installed zone for use by BrandZ.
 117 init_tarzone()
 118 {
 119         typeset rootdir="$1"
 120 
 121         if ! $branddir/lx_init_zone "$rootdir"; then
 122                 screenlog "$zone_initfail" "$zonename"
 123                 return 1
 124         fi
 125 }
 126 
 127 # Clean up on interrupt
 128 trap_cleanup()
 129 {
 130         msg=$(gettext "Installation cancelled due to interrupt.")
 131 
 132         screenlog "$msg"
 133         exit $int_code
 134 }
 135 
 136 #
 137 # Output the usage message.
 138 #
 139 # This is done this way due to limitations in the way gettext strings are
 140 # extracted from shell scripts and processed.  Use of this somewhat awkward
 141 # syntax allows us to produce longer lines of text than otherwise would be
 142 # possible without wrapping lines across more than one line of code.
 143 #
 144 usage()
 145 {
 146         int_code=$ZONE_SUBPROC_USAGE
 147 
 148         echo $(gettext "Usage:")
 149         printf "  $cmd_h\n" "zoneadm" "install"
 150         printf "  $cmd_full\n" "zoneadm" "install"
 151 
 152         echo
 153 
 154         echo $(gettext "The installer will attempt to use the default system") \
 155             $(gettext "removable disc device if <archive dir> is not") \
 156             $(gettext "specified.") | fmt -80
 157 
 158         echo
 159 
 160         echo $(gettext "<cluster> specifies which package cluster you wish") \
 161             $(gettext "to install.") | fmt -80
 162 
 163         echo
 164         echo $(gettext "The 'desktop' cluster will be installed by default.")
 165         echo
 166         echo $(gettext "The available clusters are:")
 167         echo "    + core"
 168         echo "    + server"
 169         echo "    + desktop"
 170         echo "    + development"
 171         echo "    + all"
 172         echo
 173 
 174         echo $(gettext "Each cluster includes all of the clusters preceding") \
 175             $(gettext "it, so the 'server' cluster includes the 'core'") \
 176             $(gettext "cluster, the 'desktop' cluster includes the 'core'") \
 177             $(gettext "and 'server' clusters, and so on.") | fmt -80
 178 
 179         echo
 180         echo $(gettext "Examples")
 181         echo "========"
 182 
 183         echo $(gettext "Example 1: Install a base Linux system from CDs or a") \
 184             $(gettext "DVD using the system default removable disc device:") |
 185             fmt -80
 186 
 187         echo
 188         echo "    # zoneadm -z myzone install"
 189         echo
 190 
 191         echo $(gettext "Example 2: Install the 'server' cluster from CDs or") \
 192             $(gettext "a DVD via an alternative removable disc device:") |
 193             fmt -80
 194 
 195         echo
 196         echo "    # zoneadm -z myzone install -d /cdrom/cdrom1 server"
 197         echo
 198 
 199         echo $(gettext "Example 3: Install the desktop Linux environment") \
 200             $(gettext "from an ISO image made available as '/dev/lofi/1' by") \
 201             $(gettext "use of lofiadm(1M):") | fmt -80
 202 
 203         echo
 204         echo "    # zoneadm -z myzone install -d /dev/lofi/1 desktop"
 205         echo
 206 
 207         echo $(gettext "Example 4: Install the entire Linux environment from") \
 208             $(gettext "ISO images located in the directory") \
 209             "'/export/centos_3.8/isos':" | fmt -80
 210 
 211         echo
 212         echo "    # zoneadm -z myzone install -d /export/centos_3.8/isos all"
 213         echo
 214 
 215         echo $(gettext "Example 5: Install from a compressed tar archive of") \
 216             $(gettext "an existing Linux installation (a tar ball) with") \
 217             $(gettext "verbose output regarding the progress of the") \
 218             $(gettext "installation:") | fmt -80
 219 
 220         echo
 221         echo "    # zoneadm -z myzone install -v -d /tmp/linux_full.tar.gz"
 222         echo
 223 
 224         echo $(gettext "Example 6: Install from a compressed tar archive of") \
 225             $(gettext "an existing Linux installation (a tar ball) with NO") \
 226             $(gettext "output regarding the progress of the installation") \
 227             $(gettext "(silent mode.)") | fmt -80
 228 
 229         echo
 230 
 231         echo $(gettext "NOTE: Silent mode is only recommended for use by") \
 232             $(gettext "shell scripts and other non-interactive programs:") |
 233             fmt -80
 234 
 235         echo
 236         echo "    # zoneadm -z myzone install -d /tmp/linux_full.tar.gz -s"
 237         echo
 238 
 239         exit $int_code
 240 }
 241 
 242 #
 243 # The main body of the script starts here.
 244 #
 245 # This script should never be called directly by a user but rather should
 246 # only be called by zoneadm to install a BrandZ Linux zone.
 247 #
 248 
 249 #
 250 # Exit values used by the script, as #defined in <sys/zone.h>
 251 #
 252 #       ZONE_SUBPROC_OK
 253 #       ===============
 254 #       Installation was successful
 255 #
 256 #       ZONE_SUBPROC_USAGE
 257 #       ==================
 258 #       Improper arguments were passed, so print a usage message before exiting
 259 #
 260 #       ZONE_SUBPROC_NOTCOMPLETE
 261 #       ========================
 262 #       Installation did not complete, but another installation attempt can be
 263 #       made without an uninstall
 264 #
 265 #       ZONE_SUBPROC_FATAL
 266 #       ==================
 267 #       Installation failed and an uninstall will be required before another
 268 #       install can be attempted
 269 #
 270 ZONE_SUBPROC_OK=0
 271 ZONE_SUBPROC_USAGE=253
 272 ZONE_SUBPROC_NOTCOMPLETE=254
 273 ZONE_SUBPROC_FATAL=255
 274 
 275 #
 276 # An unspecified exit or interrupt should exit with ZONE_SUBPROC_NOTCOMPLETE,
 277 # meaning a user will not need to do an uninstall before attempting another
 278 # install.
 279 #
 280 int_code=$ZONE_SUBPROC_NOTCOMPLETE
 281 
 282 trap trap_cleanup INT
 283 
 284 # If we weren't passed at least two arguments, exit now.
 285 [[ $# -lt 2 ]] && usage
 286 
 287 #
 288 # This script is always started with a full path so we can extract the
 289 # brand directory name here.
 290 #
 291 branddir=$(dirname "$0")
 292 zonename="$1"
 293 zoneroot="$2"
 294 
 295 install_root="$zoneroot/root"
 296 logdir="$install_root/var/log"
 297 
 298 shift; shift    # remove zonename and zoneroot from arguments array
 299 
 300 unset gtaropts
 301 unset install_opts
 302 unset install_src
 303 unset msg
 304 unset silent_mode
 305 unset verbose_mode
 306 
 307 while getopts "d:hsvX" opt
 308 do
 309         case "$opt" in
 310                 h)      usage;;
 311                 s)      silent_mode=1;;
 312                 v)      verbose_mode=1;;
 313                 d)      install_src="$OPTARG" ;;
 314                 X)      install_opts="$install_opts -x" ;;
 315                 *)      usage;;
 316         esac
 317 done
 318 shift OPTIND-1
 319 
 320 # Providing more than one passed argument generates a usage message
 321 if [[ $# -gt 1 ]]; then
 322         msg=$(gettext "ERROR: Too many arguments provided:")
 323 
 324         screenlog "$msg"
 325         screenlog "  \"%s\"" "$@"
 326         screenlog ""
 327         usage
 328 fi
 329 
 330 # Validate any free-form arguments
 331 if [[ $# -eq 1 && "$1" != "core" && "$1" != "server" && "$1" != "desktop" &&
 332     "$1" != "development" && "$1" != "all" ]]; then
 333         msg=$(gettext "ERROR: Unknown cluster name specified: %s")
 334 
 335         screenlog "$msg" "\"$1\""
 336         screenlog ""
 337         usage
 338 fi
 339 
 340 # The install can't be both verbose AND silent...
 341 if [[ -n $silent_mode && -n $verbose_mode ]]; then
 342         screenlog "$both_modes" "zoneadm install"
 343         screenlog ""
 344         usage
 345 fi
 346 
 347 #
 348 # Validate that we're running on a i686-compatible CPU; abort the zone
 349 # installation now if we're not.
 350 #
 351 procinfo=$(LC_ALL=C psrinfo -vp | grep family)
 352 
 353 #
 354 # All x86 processors in CPUID families 6, 15, 16 or 17 should be
 355 # i686-compatible, assuming third party processor vendors follow AMD and
 356 # Intel's lead.
 357 #
 358 if [[ "$procinfo" != *" x86 "* ]] ||
 359     [[ "$procinfo" != *" family 6 "* && "$procinfo" != *" family 15 "* &&
 360     "$procinfo" != *" family 16 "* && "$procinfo" != *" family 17 "* ]] ; then
 361         screenlog "$unsupported_cpu" "i686"
 362         exit $int_code
 363 fi
 364 
 365 if [[ -n $install_src ]]; then
 366         #
 367         # Validate $install_src.
 368         #
 369         # If install_src is a directory, assume it contains ISO images to
 370         # install from, otherwise treat the argument as if it points to a tar
 371         # ball file.
 372         #
 373         if [[ "`echo $install_src | cut -c 1`" != "/" ]]; then
 374                 screenlog "$path_abs" "$install_src"
 375                 exit $int_code
 376         fi
 377 
 378         if [[ ! -a "$install_src" ]]; then
 379                 screenlog "$not_found" "$install_src"
 380                 screenlog "$install_abort" "$zonename"
 381                 exit $int_code
 382         fi
 383 
 384         if [[ ! -r "$install_src" ]]; then
 385                 screenlog "$not_readable" "$install_src"
 386                 screenlog "$install_abort" "$zonename"
 387                 exit $int_code
 388         fi
 389 
 390         #
 391         # If install_src is a block device, a directory, a possible device
 392         # created via lofiadm(1M), or the directory used by a standard volume
 393         # management daemon, pass it on to the secondary install script.
 394         #
 395         # Otherwise, validate the passed filename to prepare for a tar ball
 396         # install.
 397         #
 398         if [[ ! -b "$install_src" && ! -d "$install_src" &&
 399             "$install_src" != /dev/lofi/* && "$install_src" != /cdrom/* &&
 400             "$install_src" != /media/* ]]; then
 401                 if [[ ! -f "$install_src" ]]; then
 402                         screenlog "$wrong_type" "$install_src"
 403                         screenlog "$install_abort" "$zonename"
 404                         exit $int_code
 405                 fi
 406 
 407                 filetype=`{ LC_ALL=C file $install_src | 
 408                     awk '{print $2}' ; } 2>/dev/null`
 409 
 410                 if [[ "$filetype" = "gzip" ]]; then
 411                         verbose "\"$install_src\": \"gzip\" archive"
 412                         gtaropts="-xz"
 413                 elif [[ "$filetype" = "bzip2" ]]; then
 414                         verbose "\"$install_src\": \"bzip2\" archive"
 415                         gtaropts="-xj"
 416                 elif [[ "$filetype" = "compressed" ]]; then
 417                         verbose "\"$install_src\": Lempel-Ziv" \
 418                             "compressed (\".Z\") archive."
 419                         gtaropts="-xZ"
 420                 elif [[ "$filetype" = "USTAR" ]]; then
 421                         verbose "\"$install_src\":" \
 422                             "uncompressed (\"tar\") archive."
 423                         gtaropts="-x"
 424                 else
 425                         screenlog "$wrong_type" "$install_src"
 426                         screenlog "$install_abort" "$zonename"
 427                         exit $int_code
 428                 fi
 429         fi
 430 fi
 431 
 432 #
 433 # Start silent operation and pass the flag to prepare pass the flag to
 434 # the ISO installer, if needed.
 435 #
 436 if [[ -n $silent_mode ]]
 437 then
 438         exec 1>/dev/null
 439         install_opts="$install_opts -s"
 440 fi
 441 
 442 #
 443 # If verbose mode was specified, pass the verbose flag to lx_distro_install
 444 # for ISO or disc installations and to gtar for tarball-based installs.
 445 #
 446 if [[ -n $verbose_mode ]]
 447 then
 448         echo $(gettext "Verbose output mode enabled.")
 449         install_opts="$install_opts -v"
 450         [[ -n $gtaropts ]] && gtaropts="${gtaropts}v"
 451 fi
 452 
 453 [[ -n $gtaropts ]] && gtaropts="${gtaropts}f"
 454 
 455 if [[ ! -d "$install_root" ]]
 456 then
 457         if ! mkdir -p "$install_root" 2>/dev/null; then
 458                 screenlog "$no_install" "$install_root"
 459                 exit $int_code
 460         fi
 461 fi
 462 
 463 #
 464 # Check for a non-empty root.
 465 # 
 466 cnt=`ls $install_root | wc -l`
 467 if [ $cnt -ne 0 ]; then
 468         screenlog "$root_full" "$install_root"
 469         exit $int_code
 470 fi
 471 
 472 if [[ ! -d "$logdir" ]]
 473 then
 474         if ! mkdir -p "$logdir" 2>/dev/null; then
 475                 screenlog "$no_log" "$logdir"
 476                 exit $int_code
 477         fi
 478 fi
 479 
 480 logfile="${logdir}/$zonename.install.$$.log"
 481 
 482 if ! > $logfile; then
 483         screenlog "$no_logfile" "$logfile"
 484         exit $int_code
 485 fi
 486 
 487 # Redirect stderr to the log file to automatically log any error messages
 488 exec 2>>"$logfile"
 489 
 490 #
 491 # From here on out, an unspecified exit or interrupt should exit with
 492 # ZONE_SUBPROC_FATAL, meaning a user will need to do an uninstall before
 493 # attempting another install, as we've modified the directories we were going
 494 # to install to in some way.
 495 #
 496 int_code=$ZONE_SUBPROC_FATAL
 497 
 498 log "Installation started for zone \"$zonename\" `/usr/bin/date`"
 499 
 500 if [[ -n $gtaropts ]]; then
 501         check_cmd /usr/sfw/bin/gtar $branddir/lx_init_zone
 502 
 503         screenlog "$install_zone" "$zonename" "$zoneroot"
 504         screenlog "$install_from" "$install_src"
 505         echo
 506         echo $(gettext "This process may take several minutes.")
 507         echo
 508 
 509         if ! ( cd "$install_root" && gtar "$gtaropts" "$install_src" ) ; then
 510                 log "Error: extraction from tar archive failed."
 511         else
 512                 if ! [[ -d "${install_root}/bin" &&
 513                     -d "${install_root}/sbin" ]]; then
 514                         log "Error: improper or incomplete tar archive."
 515                 else
 516                         $branddir/lx_init_zone "$install_root" &&
 517                             init_tarzone "$install_root"
 518 
 519                         #
 520                         # Emit the same code from here whether we're
 521                         # interrupted or exiting normally.
 522                         #
 523                         int_code=$?
 524                 fi
 525         fi
 526 
 527         if [[ $int_code -eq ZONE_SUBPROC_OK ]]; then
 528                 log "Tar install completed for zone '$zonename' `date`."
 529         else
 530                 log "Tar install failed for zone \"$zonename\" `date`."
 531 
 532         fi
 533 else
 534         check_cmd $branddir/lx_distro_install
 535 
 536         $branddir/lx_distro_install -z "$zonename" -r "$zoneroot" \
 537             -d "$install_src" -l "$logfile" $install_opts "$@"
 538 
 539         #
 540         # Emit the same code from here whether we're interrupted or exiting
 541         # normally.
 542         #
 543         int_code=$?
 544 
 545         [[ $int_code -eq $ZONE_SUBPROC_USAGE ]] && usage
 546 fi
 547 
 548 if [[ $int_code -ne $ZONE_SUBPROC_OK ]]; then
 549         screenlog ""
 550         screenlog "$install_fail" "$zonename"
 551         screenlog ""
 552 
 553         #
 554         # Only make a reference to the log file if one will exist after
 555         # zoneadm exits.
 556         #
 557         [[ $int_code -ne $ZONE_SUBPROC_NOTCOMPLETE ]] &&
 558             screenlog "$see_log" "$logfile"
 559 
 560         exit $int_code
 561 fi
 562 
 563 #
 564 # After the install completes, we've likely moved a new copy of the logfile into
 565 # place atop the logfile we WERE writing to, so if we don't reopen the logfile
 566 # here the shell will continue writing to the old logfile's inode, meaning we
 567 # would lose all log information from this point on.
 568 #
 569 exec 2>>"$logfile"
 570 
 571 screenlog ""
 572 screenlog "$install_good" "$zonename"
 573 screenlog ""
 574 
 575 echo $(gettext "Details saved to log file:")
 576 echo "    \"$logfile\""
 577 echo
 578 
 579 exit $ZONE_SUBPROC_OK