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