#!/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 (c) 2008, 2011, Oracle and/or its affiliates. All rights reserved.
#
. /usr/lib/brand/ipkg/common.ksh
# Allows developer to override some things like PATH and PYTHONPATH
. /usr/lib/brand/ipkg/developerenv.ksh
m_attach_log=$(gettext "Log File: %s")
m_zfs=$(gettext "A ZFS file system was created for the zone.")
m_usage=$(gettext "attach [-a archive] [-d dataset] [-r zfs-recv] [-v] [-u]\n\tThe -a archive option specifies a tar file or cpio archive.\n\tThe -d dataset option specifies an existing dataset.\n\tThe -r zfs-recv option receives the output of a 'zfs send' command\n\tof an existing zone root dataset.\n\tThe -u option indicates that the software should be updated to match\n\tthe current host.\n\tThe -v option enables verbose output.")
m_attach_root=$(gettext " Attach Path: %s")
m_attach_ds=$(gettext " Attach ZFS Dataset: %s")
m_gzinc=$(gettext " Global zone version: %s")
m_zinc=$(gettext " Non-Global zone version: %s")
m_need_update=$(gettext " Evaluation: Packages in zone %s are out of sync with the global zone. To proceed, retry with the -u flag.")
m_cache=$(gettext " Cache: Using %s.")
m_publisher=$(gettext " Updating non-global zone: Propagating publisher %s.")
m_image_link=$(gettext " Updating non-global zone: Linking to image %s.")
m_sync_done=$(gettext " Updating non-global zone: Zone updated.")
m_complete=$(gettext " Result: Attach Succeeded.")
m_failed=$(gettext " Result: Attach Failed.")
#
# These two messages are used by the install_image function in
# /usr/lib/brand/shared/common.ksh. Yes, this is terrible.
#
installing=$(gettext " Installing: This may take several minutes...")
no_installing=$(gettext " Installing: Using pre-existing data in zonepath")
f_sanity_variant=$(gettext " Sanity Check: FAILED, couldn't determine %s from image.")
f_sanity_global=$(gettext " Sanity Check: FAILED, appears to be a global zone (%s=%s).")
f_update=$(gettext "Could not update attaching zone")
f_sysrepo=$(gettext "Could not install package/pkg/zones-proxy")
f_ds_config=$(gettext "Failed to configure dataset %s: could not set %s.")
f_no_active_ds_mounted=$(gettext "Failed to locate any dataset mounted at %s. Attach requires a mounted dataset.")
f_nonsticky=$(gettext "Could not set legacy publisher to non-sticky")
e_dataset_disappeared=$(gettext "Dataset %s was unexpectedly unmounted")
e_dataset_not_mounted=$(gettext "Dataset %s exists in boot environment but not mounted")
e_dataset_not_in_be=$(gettext "Dataset %s mountpoint %s is not under zone root %s")
# Clean up on interrupt
trap_cleanup() {
typeset msg=$(gettext "Installation cancelled due to interrupt.")
trap - INT
log "$msg"
# umount any mounted file systems
umnt_fs
trap_exit
}
# If the attach failed then clean up the ZFS datasets we created.
trap_exit() {
trap - INT
if [[ -n $EXIT_NOEXECUTE ]]; then
# dryrun mode, nothing to do here; exit with whatever
# EXIT_CODE is set to.
;
elif [[ $EXIT_CODE == $ZONE_SUBPROC_USAGE ]]; then
# Usage message printed, nothing to do here.
;
elif [[ $EXIT_CODE == $ZONE_SUBPROC_OK ]]; then
# unmount the zoneroot if labeled brand
is_brand_labeled
(( $? == 1 )) && ( umount $ZONEROOT || \
log "$f_zfs_unmount" "$ZONEPATH/root" )
else
if [[ "$install_media" != "-" ]]; then
/usr/lib/brand/ipkg/uninstall $ZONENAME $ZONEPATH -F
else
# Restore the zone properties for the pre-existing
# dataset.
if [[ -n "$ACTIVE_DS" ]]; then
zfs set zoned=off $ACTIVE_DS
(( $? != 0 )) && error "$f_ds_config" \
"$ACTIVE_DS" "zoned=off"
zfs set canmount=on $ACTIVE_DS
(( $? != 0 )) && error "$f_ds_config" \
"$ACTIVE_DS" "canmount=on"
zfs set mountpoint=$ZONEROOT $ACTIVE_DS
(( $? != 0 )) && error "$f_ds_config" \
"$ACTIVE_DS" "mountpoint=$ZONEROOT"
fi
fi
log "$m_failed"
fi
exit $EXIT_CODE
}
EXIT_CODE=$ZONE_SUBPROC_USAGE
install_media="-"
trap trap_cleanup INT
trap trap_exit EXIT
#set -o xtrace
PKG="/usr/bin/pkg"
KEYDIR=/var/pkg/ssl
# If we weren't passed at least two arguments, exit now.
(( $# < 2 )) && exit $ZONE_SUBPROC_USAGE
ZONENAME="$1"
ZONEPATH="$2"
# XXX shared/common script currently uses lower case zonename & zonepath
zonename="$ZONENAME"
zonepath="$ZONEPATH"
shift; shift # remove ZONENAME and ZONEPATH from arguments array
ZONEROOT="$ZONEPATH/root"
logdir="$ZONEROOT/var/log"
#
# Resetting GZ_IMAGE to something besides slash allows for simplified
# debugging of various global zone image configurations-- simply make
# an image somewhere with the appropriate interesting parameters.
#
GZ_IMAGE=${GZ_IMAGE:-/}
PKG_IMAGE=$GZ_IMAGE
export PKG_IMAGE
allow_update=0
noexecute=0
unset inst_type
# Get publisher information for global zone. These structures are used
# to store information about the global zone publishers and
# incorporations.
typeset gz_incorporations=""
#
# Gather the zone publisher details. $1 is the location of the image we
# are processing and $2 is an associative array used to store publisher
# details.
#
gather_zone_publisher_details() {
STORED_IMAGE=$PKG_IMAGE
PKG_IMAGE=$1;export PKG_IMAGE
typeset -n publishers=$2
typeset -li publisher_count=0
typeset -li url_count=0
typeset line=
typeset name=
typeset mirror=
typeset origin=
typeset opublisher=
#
# Store publisher, origin and security details. It is assumed
# that mirrors all use the same key as the origins.
#
for line in $(get_publisher_urls all origin); do
print $line | IFS="=" read name origin
# When a publisher has multiple origins, the
# additional origins don't contain the publisher
# name. Correct for this by checking if origin is not
# set by get_publisher_urls() and, if so, use the
# "name" as the origin and set the name to the value
# we have already saved.
if [[ -z $origin ]]; then
origin=$name
name=${publisher.name}
elif [[ "$origin" == "None" ]]; then
# Publisher with no origins.
origin=""
fi
# Use a compound variable to store all the data
# relating to a publisher.
if [[ -z ${publishers[$name]} ]]; then
typeset -C publisher_$publisher_count
typeset -n publisher=publisher_$publisher_count
typeset publisher.sticky=""
typeset publisher.preferred=""
typeset publisher.enabled=""
typeset -a publisher.origins=""
typeset -a publisher.mirrors=""
typeset publisher.name=$name
typeset publisher.keyfile=""
typeset publisher.certfile=""
get_publisher_attrs ${publisher.name} origin | \
IFS=" " read publisher.sticky publisher.preferred \
publisher.enabled
if [[ -n "$origin" ]]; then
get_pub_secinfo ${publisher.name} | \
read publisher.keyfile publisher.certfile
[[ ${publisher.keyfile} != "None" && \
! -f ${PKG_IMAGE}/${publisher.keyfile} ]] && \
fail_usage "$f_nosuch_key" \
${publisher.keyfile}
[[ ${publisher.certfile} != "None" && \
! -f ${PKG_IMAGE}/${publisher.certfile} ]] && \
fail_usage "$f_nosuch_cert" \
${publisher.certfile}
else
# Publisher has no origins.
publisher.keyfile="None"
publisher.certfile="None"
fi
publisher_count=publisher_count+1
url_count=0
fi
publisher.origins[$url_count]=$origin
publishers[$name]=${publisher}
url_count=url_count+1
done
#
# Store mirror details
#
url_count=0
for line in $(get_publisher_urls all mirror); do
print $line | IFS="=" read name mirror
if [[ -z $mirror ]]; then
mirror=$name
name=${publisher.name}
fi
if [[ -z $opublisher || $opublisher != $name ]]; then
opublisher=$name
eval publisher="${publishers[$name]}"
url_count=0
fi
publisher.mirrors[$url_count]=$mirror
publishers[$name]=${publisher}
url_count=url_count+1
done
PKG_IMAGE=$STORED_IMAGE;export PKG_IMAGE
}
#
# $1 is an associative array of publishers. Search this array and
# return the preferred publisher.
#
get_preferred_publisher() {
typeset -n publishers=$1
typeset publisher=
for key in ${!publishers[*]}; do
eval publisher="${publishers[$key]}"
if [[ ${publisher.preferred} == "true" ]]; then
print ${key}
return 0
fi
done
return 1
}
#
# $1 is an empty string to be populated with a list of incorporation
# fmris.
#
gather_incorporations() {
typeset -n incorporations=$1
typeset p=
for p in \
$(LC_ALL=C $PKG search -Hl -o pkg.name \
':pkg.depend.install-hold:core-os*');do
incorporations="$incorporations $(get_pkg_fmri $p)"
done
}
#
# Print the pkg(1) command which defines a publisher. $1 is an associative
# array of publisher details and $2 is the publisher to be printed.
#
print_publisher_pkg_defn() {
typeset -n publishers=$1
typeset pname=$2
typeset publisher=
typeset args=""
typeset origin=
typeset mirror=
eval publisher="${publishers[$pname]}"
if [[ ${publisher.preferred} == "true" ]]; then
args="$args -P"
fi
for origin in ${publisher.origins[*]}; do
args="$args -g $origin"
done
for mirror in ${publisher.mirrors[*]}; do
args="$args -m $mirror"
done
if [[ ${publisher.sticky} == "true" ]]; then
args="$args --sticky"
else
args="$args --non-sticky"
fi
if [[ ${publisher.enabled} == "true" ]]; then
args="$args --enable"
else
args="$args --disable"
fi
echo "$args"
}
# Other brand attach options are invalid for this brand.
while getopts "a:d:n:r:uv" opt; do
case $opt in
a)
if [[ -n "$inst_type" ]]; then
fatal "$incompat_options" "$m_usage"
fi
inst_type="archive"
install_media="$OPTARG"
;;
d)
if [[ -n "$inst_type" ]]; then
fatal "$incompat_options" "$m_usage"
fi
inst_type="directory"
install_media="$OPTARG"
;;
n) noexecute=1
EXIT_NOEXECUTE=1
dryrun_mfst=$"OPTARG"
;;
r)
if [[ -n "$inst_type" ]]; then
fatal "$incompat_options" "$m_usage"
fi
inst_type="stdin"
install_media="$OPTARG"
;;
u) allow_update=1 ;;
v) verbose="-v"
OPT_V=1 ;; # used for vlog()
?) fail_usage "" ;;
*) fail_usage "";;
esac
done
shift $((OPTIND-1))
if [[ $noexecute == 1 && -n "$inst_type" ]]; then
fatal "$m_usage"
fi
[[ -z "$inst_type" ]] && inst_type="directory"
if [ $noexecute -eq 1 ]; then
#
# the zone doesn't have to exist when the -n option is used, so do
# this work early.
#
# LIXXX There is no sw validation for IPS right now, so just pretend
# everything will be ok.
# Set exit code for the trap handler.
EXIT_CODE=$ZONE_SUBPROC_OK
exit $EXIT_CODE
fi
enable_zones_services
if [[ $? -ne 0 ]]; then
exit $ZONE_SUBPROC_NOTCOMPLETE
fi
LOGFILE=$(/usr/bin/mktemp -t -p /var/tmp $ZONENAME.attach_log.XXXXXX)
if [[ -z "$LOGFILE" ]]; then
fatal "$e_tmpfile"
fi
exec 2>>"$LOGFILE"
log "$m_attach_log" "$LOGFILE"
#
# TODO - once sxce is gone, move the following block into
# usr/lib/brand/shared/common.ksh code to share with other brands using
# the same zfs dataset logic for attach. This currently uses get_current_gzbe
# so we can't move it yet since beadm isn't in sxce.
#
# Validate that the zonepath is not in the root dataset.
pdir=`dirname $ZONEPATH`
get_zonepath_ds $pdir
fail_zonepath_in_rootds $ZONEPATH_DS
EXIT_CODE=$ZONE_SUBPROC_NOTCOMPLETE
if [[ "$install_media" == "-" ]]; then
#
# Since we're using a pre-existing dataset, the dataset currently
# mounted on the {zonepath}/root becomes the active dataset. We
# can't depend on the usual dataset attributes to detect this since
# the dataset could be a detached zone or one that the user set up by
# hand and lacking the proper attributes. However, since the zone is
# not attached yet, the 'install_media == -' means the dataset must be
# mounted at this point.
#
ACTIVE_DS=`mount -p | nawk -v zroot=$ZONEROOT '{
if ($3 == zroot && $4 == "zfs")
print $1
}'`
[[ -z "$ACTIVE_DS" ]] && fatal "$f_no_active_ds_mounted" $ZONEROOT
# Set up proper attributes on the ROOT dataset.
get_zonepath_ds $ZONEPATH
zfs list -H -t filesystem -o name $ZONEPATH_DS/ROOT >/dev/null 2>&1
(( $? != 0 )) && fatal "$f_no_active_ds"
# need to ensure zoned is off to set mountpoint=legacy.
zfs set zoned=off $ZONEPATH_DS/ROOT
(( $? != 0 )) && fatal "$f_ds_config" $ZONEPATH_DS/ROOT "zoned=off"
zfs set mountpoint=legacy $ZONEPATH_DS/ROOT
(( $? != 0 )) && fatal "$f_ds_config" $ZONEPATH_DS/ROOT \
"mountpoint=legacy"
zfs set zoned=on $ZONEPATH_DS/ROOT
(( $? != 0 )) && fatal "$f_ds_config" $ZONEPATH_DS/ROOT "zoned=on"
#
# We're typically using a pre-existing mounted dataset so setting the
# following propery changes will cause the {zonepath}/root dataset to
# be unmounted. However, a p2v with an update-on-attach will have
# created the dataset with the correct properties, so setting these
# attributes won't unmount the dataset. Thus, we check the mount
# and attempt the remount if necessary.
#
get_current_gzbe
zfs set $PROP_PARENT=$CURRENT_GZBE $ACTIVE_DS
(( $? != 0 )) && fatal "$f_ds_config" $ACTIVE_DS \
"$PROP_PARENT=$CURRENT_GZBE"
zfs set $PROP_ACTIVE=on $ACTIVE_DS
(( $? != 0 )) && fatal "$f_ds_config" $ACTIVE_DS "$PROP_ACTIVE=on"
zfs set canmount=noauto $ACTIVE_DS
(( $? != 0 )) && fatal "$f_ds_config" $ACTIVE_DS "canmount=noauto"
zfs set zoned=off $ACTIVE_DS
(( $? != 0 )) && fatal "$f_ds_config" $ACTIVE_DS "zoned=off"
zfs inherit mountpoint $ACTIVE_DS
(( $? != 0 )) && fatal "$f_ds_config" $ACTIVE_DS "'inherit mountpoint'"
zfs inherit zoned $ACTIVE_DS
(( $? != 0 )) && fatal "$f_ds_config" $ACTIVE_DS "'inherit zoned'"
mounted_ds=`mount -p | nawk -v zroot=$ZONEROOT '{
if ($3 == zroot && $4 == "zfs")
print $1
}'`
if [[ -z $mounted_ds ]]; then
mount -F zfs $ACTIVE_DS $ZONEROOT || fatal "$f_zfs_mount"
fi
else
#
# Since we're not using a pre-existing ZFS dataset layout, create
# the zone datasets and mount them. Start by creating the zonepath
# dataset, similar to what zoneadm would do for an initial install.
#
zds=$(zfs list -H -t filesystem -o name $pdir 2>/dev/null)
if (( $? == 0 )); then
pnm=$(/usr/bin/basename $ZONEPATH)
# The zonepath dataset might already exist.
zfs list -H -t filesystem -o name $zds/$pnm >/dev/null 2>&1
if (( $? != 0 )); then
zfs create "$zds/$pnm"
(( $? != 0 )) && fatal "$f_zfs_create"
vlog "$m_zfs"
fi
fi
create_active_ds
fi
#
# The zone's datasets are now in place.
#
log "$m_attach_root" "$ZONEROOT"
# note \n to add whitespace
log "$m_attach_ds\n" "$ACTIVE_DS"
install_image "$inst_type" "$install_media"
#
# End of TODO block to move to common code.
#
#
# Perform a sanity check to confirm that the image is not a global zone.
#
VARIANT=variant.opensolaris.zone
variant=$(LC_ALL=C $PKG -R $ZONEROOT variant -H $VARIANT)
[[ $? -ne 0 ]] && fatal "$f_sanity_variant" $VARIANT
echo $variant | IFS=" " read variantname variantval
[[ $? -ne 0 ]] && fatal "$f_sanity_variant"
# Check that we got the output we expect...
[[ $variantname = "$VARIANT" ]] || fatal "$f_sanity_variant" $VARIANT
# Check that the variant is non-global, else fail
[[ $variantval = "nonglobal" ]] || fatal "$f_sanity_global" $VARIANT $variantval
#
# We're done with the global zone: switch images to the non-global
# zone.
#
PKG_IMAGE="$ZONEROOT"
#
# If there is a cache, use it.
#
if [[ -f /var/pkg/pkg5.image && -d /var/pkg/publisher ]]; then
# respect PKG_CACHEROOT if the caller has it set.
[ -z "$PKG_CACHEROOT" ] && PKG_CACHEROOT=/var/pkg/publisher
export PKG_CACHEROOT
log "$m_cache" "$PKG_CACHEROOT"
fi
#
# pkg update-format doesn't allow a dry run or provide any other way to
# see if an update is needed.
#
log "$v_update_format"
if [[ $allow_update == 1 ]]; then
$PKG update-format || pkg_err_check "$e_update_format"
fi
#
# Set the use-system-repo property.
#
LC_ALL=C $PKG set-property use-system-repo true
if [[ $? != 0 ]]; then
log "\n$f_set_sysrepo_prop_fail"
EXIT_CODE=$ZONE_SUBPROC_NOTCOMPLETE
exit $EXIT_CODE
fi
LC_ALL=C $PKG refresh --full
#
# Reset the pkg image back to the global zone so that we link the
# zoneroot to that image.
#
PKG_IMAGE=$GZ_IMAGE
log "$m_image_link" $GZ_IMAGE
extra_flags=""
if [[ $allow_update == 0 ]]; then
extra_flags="--no-pkg-updates"
fi
LC_ALL=C $PKG attach-linked $verbose -f --allow-relink --accept \
$extra_flags \
-c zone:${zone.name} $ZONEROOT || pkg_err_check "$f_update"
# Make sure the zones proxy is installed so we can talk to the
# system repository.
PKG_IMAGE=$ZONEROOT
LC_ALL=C $PKG install $verbose pkg:///package/pkg/zones-proxy || \
pkg_err_check "$f_sysrepo"
log "\n$m_sync_done"
log "$m_complete"
EXIT_CODE=$ZONE_SUBPROC_OK
exit $ZONE_SUBPROC_OK