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