--- /dev/null Wed Feb 11 09:21:16 2009 +++ new/usr/src/lib/brand/native/zone/image_install.ksh Wed Feb 11 09:21:16 2009 @@ -0,0 +1,517 @@ +#!/bin/ksh -p +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# Copyright 2009 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +. /usr/lib/brand/shared/common.ksh + +# Restrict executables to /bin, /usr/bin and /usr/sfw/bin +PATH=/bin:/usr/bin:/usr/sbin:/usr/sfw/bin +export PATH + +cmd_not_found=$(gettext "Required command '%s' cannot be found!") +cmd_not_exec=$(gettext "Required command '%s' not executable!") +zone_initfail=$(gettext "Attempt to initialize zone '%s' FAILED.") +path_abs=$(gettext "Pathname specified to -a '%s' must be absolute.") + +e_tmpfile=$(gettext "Unable to create temporary file") + +both_modes=$(gettext "%s: cannot select both silent and verbose modes") + +both_choices=$(gettext "%s: cannot select both preserve and unconfigure options") + +both_kinds=$(gettext "%s: cannot specify both archive and directory") + +not_found=$(gettext "%s: error: file or directory not found.") + +wrong_dir_type=$(gettext "error: must be a directory") + +not_readable=$(gettext "Cannot read file '%s'") + +no_install=$(gettext "Could not create install directory '%s'") +no_log=$(gettext "Could not create log directory '%s'") + +media_taste=$(gettext " Media Type: %s") +bad_archive=$(gettext "ERROR: must be a flash archive, a cpio archive (can also +be gzipped or bzipped), a pax XUSTAR archive, or a level 0 ufsdump archive.") + +product_vers=$(gettext " Product: %s") +install_vers=$(gettext " Installer: %s") +install_zone=$(gettext " Zone: %s") +install_path=$(gettext " Path: %s") +install_from=$(gettext " Source: %s") +installing=$(gettext " Installing: This may take several minutes...") +no_installing=$(gettext " Installing: Using pre-existing data in zonepath") +install_prog=$(gettext " Installing: %s") + +install_fail=$(gettext " Result: *** Installation FAILED ***") +install_log=$(gettext " Log File: %s") + +install_abort=$(gettext " Result: Installation aborted.") +install_good=$(gettext " Result: Installation completed successfully.") + +not_native_image=$(gettext " Sanity Check: %s doesn't look like a native image.") +sanity_ok=$(gettext " Sanity Check: Passed. Looks like a native system.") +sanity_fail_detail=$(gettext " Sanity Check: Missing %s at %s") +sanity_fail_vers=$(gettext " Sanity Check: image release version %s does not match system release version %s, the zone is not usable on this system.") +sanity_fail=$(gettext " Sanity Check: FAILED (see log for details).") + + +p2ving=$(gettext "Postprocessing: This may take a while...") +p2v_prog=$(gettext " Postprocess: ") +p2v_done=$(gettext " Result: Postprocessing complete.") +p2v_fail=$(gettext " Result: Postprocessing failed.") + +root_full=$(gettext "Zonepath root %s exists and contains data; remove or move aside prior to install.") + +media_missing=\ +$(gettext "%s: you must specify an installation source using '-a' or '-d'.") + +cfgchoice_missing=\ +$(gettext "%s: you must specify -u (sys-unconfig) or -p (preserve identity).") + +mount_failed=$(gettext "ERROR: zonecfg(1M) 'fs' mount failed") + +not_flar=$(gettext "Input is not a flash archive") +bad_flar=$(gettext "Flash archive is a corrupt") +unknown_archiver=$(gettext "Archiver %s is not supported") + +e_baddir=$(gettext "Invalid '%s' directory within the zone") + +# Clean up on interrupt +trap_cleanup() +{ + msg=$(gettext "Installation cancelled due to interrupt.") + log "$msg" + + # umount IPDs + umnt_fs + + exit $EXIT_CODE +} + +sanity_check() +{ + typeset dir="$1" + shift + ret=0 + + # These checks must work with a sparse zone. + checks="etc etc/svc usr sbin lib var var/svc" + for x in $checks; do + if [[ ! -e $dir/$x ]]; then + vlog "$sanity_fail_detail" "$x" "$dir" + ret=1 + fi + done + + # + # Check image release against system release. We only work on the + # same minor release as the system is running. + # + sys_vers=0 + image_vers=-1 + if [[ -f /var/sadm/system/admin/INST_RELEASE ]]; then + sys_vers=$(nawk -F= '{if ($1 == "VERSION") print $2}' \ + /var/sadm/system/admin/INST_RELEASE) + fi + + if [[ -f $dir/var/sadm/system/admin/INST_RELEASE ]]; then + image_vers=$(nawk -F= '{if ($1 == "VERSION") print $2}' \ + $dir/var/sadm/system/admin/INST_RELEASE) + fi + + if (( $sys_vers != $image_vers )); then + vlog "$sanity_fail_vers" "$image_vers" "$sys_vers" + ret=1 + fi + + return $ret +} + +# +# The main body of the script starts here. +# +# This script should never be called directly by a user but rather should +# only be called by zoneadm to install a native system image into a zone. +# + +# +# Exit code to return if install is interrupted or exit code is otherwise +# unspecified. +# +EXIT_CODE=$ZONE_SUBPROC_USAGE + +trap trap_cleanup INT + +# If we weren't passed at least two arguments, exit now. +(( $# < 2 )) && exit $ZONE_SUBPROC_USAGE + +zonename="$1" +zonepath="$2" + +ZONEROOT="$zonepath/root" +logdir="$ZONEROOT/var/log" + +shift; shift # remove zonename and zonepath from arguments array + +unset backout +unset install_archive +unset source_dir +unset msg +unset silent_mode +unset OPT_V + +# +# It is worth noting here that we require the end user to pick one of +# -u (sys-unconfig) or -p (preserve config). This is because we can't +# really know in advance which option makes a better default. Forcing +# the user to pick one or the other means that they will consider their +# choice and hopefully not be surprised or disappointed with the result. +# +unset unconfig_zone +unset preserve_zone + +while getopts "a:b:d:psuv" opt +do + case "$opt" in + a) install_archive="$OPTARG" ; install_media="$OPTARG";; + b) if [[ -n "$backout" ]]; then + backout="$backout -b $OPTARG" + else + backout="-b $OPTARG" + fi + ;; + d) source_dir="$OPTARG" ; install_media="$OPTARG";; + p) preserve_zone="-p";; + s) silent_mode=1;; + u) unconfig_zone="-u";; + v) OPT_V="-v";; + *) exit $ZONE_SUBPROC_USAGE;; + esac +done +shift OPTIND-1 + +# The install can't be both verbose AND silent... +if [[ -n $silent_mode && -n $OPT_V ]]; then + fatal "$both_modes" "zoneadm install" +fi + +if [[ -z $install_media ]]; then + fatal "$media_missing" "zoneadm install" +fi + +if [[ -n $install_archive && -n $source_dir ]]; then + fatal "$both_kinds" "zoneadm install" +fi + +# The install can't both preserve and unconfigure +if [[ -n $unconfig_zone && -n $preserve_zone ]]; then + fatal "$both_choices" "zoneadm install" +fi + +# Must pick one or the other. +if [[ -z $unconfig_zone && -z $preserve_zone ]]; then + fatal "$cfgchoice_missing" "zoneadm install" +fi + +# +# Handle "-d -" option to use whatever is already installed into the zonepath. +# +if [ "$source_dir" != "-" ]; then + # + # Validate $install_media (things common to archive/dir) + # + if [[ "$(echo $install_media | cut -c 1)" != "/" ]]; then + fatal "$path_abs" "$install_media" + fi + + if [[ ! -e "$install_media" ]]; then + log "$not_found" "$install_media" + fatal "$install_abort" "$zonename" + fi + + if [[ ! -r "$install_media" ]]; then + log "$not_readable" "$install_media" + fatal "$install_abort" "$zonename" + fi + + if [[ -n $install_archive ]]; then + if [[ ! -f "$install_archive" ]]; then + log "$media_taste" "$bad_archive" + fatal "$install_abort" "$zonename" + fi + fi + + if [[ -n $source_dir ]]; then + if [[ ! -d "$source_dir" ]]; then + log "$media_taste" "$wrong_dir_type" + fatal "$install_abort" "$zonename" + fi + fi +fi + +LOGFILE=$(/usr/bin/mktemp -t -p /var/tmp $zonename.install_log.XXXXXX) +if [[ -z "$LOGFILE" ]]; then + fatal "$e_tmpfile" +fi +zone_logfile="${logdir}/$zonename.install$$.log" +exec 2>>"$LOGFILE" +log "$install_log" "$LOGFILE" + +vlog "Starting pre-installation tasks." + +if [[ -z $install_archive && -n $source_dir ]]; then + # + # Minimal check to make sure that the user is passing + # us something that at least seems to be a native image. + # + if [[ "$source_dir" == "-" ]]; then + filetype="existing" + filetypename="existing" + else + sanity_check $source_dir + if (( $? != 0 )); then + fatal "$not_native_image" "$source_dir" + fi + + filetype="directory" + filetypename="directory" + fi +else + ftype="$(LC_ALL=C file $install_archive | cut -d: -f 2)" + case "$ftype" in + *cpio*) filetype="cpio" + filetypename="cpio archive" + ;; + *bzip2*) filetype="bzip2" + filetypename="bzipped cpio archive" + ;; + *gzip*) filetype="gzip" + filetypename="gzipped cpio archive" + ;; + *ufsdump*) filetype="ufsdump" + filetypename="ufsdump archive" + ;; + *Flash\ Archive*) filetype="flar" + filetypename="flash archive" + ;; + *USTAR\ tar\ archive\ extended\ format*) filetype="xustar" + filetypename="pax (xustar) archive" + ;; + *) log "$media_taste" "$bad_archive" + fatal "$install_abort" "$zonename" + ;; + esac +fi + +# +# From here on out, an unspecified exit or interrupt should exit with +# ZONE_SUBPROC_NOTCOMPLETE, meaning a user will need to do an uninstall before +# attempting another install, as we've modified the directories we were going +# to install to in some way. +# +EXIT_CODE=$ZONE_SUBPROC_NOTCOMPLETE + +if [[ ! -d "$ZONEROOT" ]] +then + if ! mkdir -p "$ZONEROOT" 2>/dev/null; then + fatal "$no_install" "$ZONEROOT" + fi +fi + +# +# Check for a non-empty root if no '-d -' option. +# +if [[ "$filetype" != "existing" ]]; then + cnt=$(ls $ZONEROOT | wc -l) + if (( $cnt != 0 )); then + fatal "$root_full" "$ZONEROOT" + fi +fi + +vlog "Installation started for zone \"$zonename\"" + +log "$install_from" "$install_media" +vlog "$media_taste" "$filetypename" + +fstmpfile=$(/usr/bin/mktemp -t -p /var/tmp) +if [[ -z "$fstmpfile" ]]; then + fatal "$e_tmpfile" +fi + +# Make sure we always have the files holding the directories to filter +# out when extracting from a CPIO or PAX archive. We'll add the IPDs to these +# files in get_fs_info(). +ipdcpiofile=$(/usr/bin/mktemp -t -p /var/tmp ipd.cpio.XXXXXX) +if [[ -z "$ipdcpiofile" ]]; then + rm -f $fstmpfile + fatal "$e_tmpfile" +fi + +# In addition to the IPDs, also filter out these directories. +echo 'dev/*' >>$ipdcpiofile +echo 'devices/*' >>$ipdcpiofile +echo 'devices' >>$ipdcpiofile +echo 'proc/*' >>$ipdcpiofile +echo 'tmp/*' >>$ipdcpiofile +echo 'var/run/*' >>$ipdcpiofile +echo 'system/contract/*' >>$ipdcpiofile +echo 'system/object/*' >>$ipdcpiofile + +ipdpaxfile=$(/usr/bin/mktemp -t -p /var/tmp ipd.pax.XXXXXX) +if [[ -z "$ipdpaxfile" ]]; then + rm -f $fstmpfile $ipdcpiofile + fatal "$e_tmpfile" +fi + +printf "%s " "dev devices proc tmp var/run system/contract system/object" \ + >>$ipdpaxfile + +# Set up any fs mounts so the archive will install into the correct locations. +get_fs_info +mnt_fs +if (( $? != 0 )); then + umnt_fs >/dev/null 2>&1 + rm -f $fstmpfile $ipdcpiofile $ipdpaxfile + fatal "$mount_failed" +fi + +if [[ "$filetype" == "existing" ]]; then + log "$no_installing" +else + log "$installing" +fi + +unpack_result=0 +stage1="cat" +if [[ "$filetype" == "gzip" ]]; then + stage1="gzcat" + filetype="cpio" +fi + +if [[ "$filetype" == "bzip2" ]]; then + stage1="bzcat" + filetype="cpio" +fi + +if [[ "$filetype" == "cpio" ]]; then + install_cpio "$stage1" "$install_archive" + unpack_result=$? + +elif [[ "$filetype" == "flar" ]]; then + ( cd "$ZONEROOT" && install_flar < "$install_archive" ) + unpack_result=$? + +elif [[ "$filetype" == "xustar" ]]; then + install_pax "$install_archive" + unpack_result=$? + +elif [[ "$filetype" == "ufsdump" ]]; then + install_ufsdump "$install_archive" + unpack_result=$? + +elif [[ "$filetype" == "directory" ]]; then + install_dir "$source_dir" + unpack_result=$? +fi + +# Clean up any fs mounts used during unpacking. +umnt_fs +rm -f $fstmpfile $ipdcpiofile $ipdpaxfile + +# +# Do a sanity check to see if various things we think should be present +# are present. If not, the user might have supplied a cpio archive which was +# not created properly. +# +if (( $unpack_result == 0 )); then + sanity_check $ZONEROOT + if (( $? != 0 )); then + log "$sanity_fail" + log "" + log "$install_log" "$LOGFILE" + fatal "$install_fail" "$zonename" + else + vlog "$sanity_ok" + fi +fi + +chmod 700 $zonepath + +log "$p2ving" +vlog "running: p2v $OPT_V $unconfig_zone $backout $zonename $zonepath" + +# +# Run p2v. +# +# Getting the output to the right places is a little tricky because what +# we want is for p2v to output in the same way the installer does: verbose +# messages to the log file always, and verbose messages printed to the +# user if the user passes -v. This rules out simple redirection. And +# we can't use tee or other tricks because they cause us to lose the +# return value from the p2v script due to the way shell pipelines work. +# +# The simplest way to do this seems to be to hand off the management of +# the log file to the p2v script. So we run p2v with -l to tell it where +# to find the log file and then reopen the log (O_APPEND) when p2v is done. +# +/usr/lib/brand/native/p2v -l "$LOGFILE" -m "$p2v_prog" \ + $OPT_V $unconfig_zone $backout $zonename $zonepath +p2v_result=$? +exec 2>>$LOGFILE + +if (( $p2v_result == 0 )); then + vlog "$p2v_done" +else + log "$p2v_fail" + log "" + log "$install_fail" + log "$install_log" "$LOGFILE" + exit $ZONE_SUBPROC_FATAL +fi + +EXIT_CODE=$ZONE_SUBPROC_OK + +log "" +log "$install_good" "$zonename" + +if [[ -h $ZONEROOT/var || ! -d $ZONEROOT/var || -h $ZONEROOT/var/log ]]; then + log "$e_baddir" "/var/log" + exit $ZONE_SUBPROC_FATAL +fi + +# Just in case the log directory isn't present... +if [[ ! -d "$logdir" ]]; then + if ! mkdir -p "$logdir" 2>/dev/null; then + log "$no_log" "$logdir" + fi +fi + +if [[ ! -h $zone_logfile && ! -d $zone_logfile ]]; then + cp $LOGFILE $zone_logfile +fi +log "$install_log" "$zone_logfile" +rm -f $LOGFILE + +exit 0