image_install.ksh revision edfa49ff6d1bd39465e21e3b28aee863e91c5e3f
#!/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