--- /dev/null Wed Feb 11 09:21:17 2009 +++ new/usr/src/lib/brand/native/zone/p2v.ksh Wed Feb 11 09:21:16 2009 @@ -0,0 +1,680 @@ +#!/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. +# + +# NOTE: this script runs in the global zone and touches the non-global +# zone, so care should be taken to validate any modifications so that they +# are safe. + +. /usr/lib/brand/shared/common.ksh + +LOGFILE= +MSG_PREFIX="p2v: " +EXIT_CODE=1 + +usage() +{ + echo "$0 [-s] [-m msgprefix] [-u] [-v] [-b patchid]* zonename" >&2 + exit $EXIT_CODE +} + +# Clean up on interrupt +trap_cleanup() +{ + msg=$(gettext "Postprocessing cancelled due to interrupt.") + error "$msg" + + if (( $zone_is_running != 0 )); then + error "$e_shutdown" "$ZONENAME" + /usr/sbin/zoneadm -z $ZONENAME halt + fi + + exit $EXIT_CODE +} + +# +# For an exclusive stack zone, fix up the network configuration files. +# We need to do this even if unconfiguring the zone so sys-unconfig works +# correctly. +# +fix_net() +{ + [[ "$STACK_TYPE" == "shared" ]] && return + + NETIF_CNT=$(/usr/bin/ls $ZONEROOT/etc/hostname.* 2>/dev/null | \ + /usr/bin/wc -l) + if (( $NETIF_CNT != 1 )); then + vlog "$v_nonetfix" + return + fi + + NET=$(LC_ALL=C /usr/sbin/zonecfg -z $ZONENAME info net) + if (( $? != 0 )); then + error "$e_badinfo" "net" + return + fi + + NETIF=$(echo $NET | /usr/bin/nawk '{ + for (i = 1; i < NF; i++) { + if ($i == "physical:") { + if (length(net) == 0) { + i++ + net = $i + } else { + multiple=1 + } + } + } + } + END { if (!multiple) + print net + }') + + if [[ -z "$NETIF" ]]; then + vlog "$v_nonetfix" + return + fi + + OLD_HOSTNET=$(/usr/bin/ls $ZONEROOT/etc/hostname.*) + if [[ "$OLD_HOSTNET" != "$ZONEROOT/etc/hostname.$NETIF" ]]; then + safe_move $OLD_HOSTNET $ZONEROOT/etc/hostname.$NETIF + fi +} + +# +# Disable all of the shares since the zone cannot be an NFS server. +# Note that we disable the various instances of the svc:/network/shares/group +# SMF service in the fix_smf function. +# +fix_nfs() +{ + zonedfs=$ZONEROOT/etc/dfs + + if [[ -h $zonedfs/dfstab || ! -f $zonedfs/dfstab ]]; then + error "$e_badfile" "/etc/dfs/dfstab" + return + fi + + tmpfile=$(/usr/bin/mktemp -t -p /var/tmp) + if [[ -z "$tmpfile" ]]; then + error "$e_tmpfile" + return + fi + + /usr/bin/nawk '{ + if (substr($1, 0, 1) == "#") { + print $0 + } else { + print "#", $0 + modified=1 + } + } + END { + if (modified == 1) { + printf("# Modified by p2v ") + system("/usr/bin/date") + exit 0 + } + exit 1 + }' $zonedfs/dfstab >>$tmpfile + + if (( $? == 0 )); then + if [[ ! -f $zonedfs/dfstab.pre_p2v ]]; then + safe_copy $zonedfs/dfstab $zonedfs/dfstab.pre_p2v + fi + safe_copy $tmpfile $zonedfs/dfstab + fi + /usr/bin/rm -f $tmpfile +} + +# +# Comment out most of the old mounts since they are either unneeded or +# likely incorrect within a zone. Specific mounts can be manually +# reenabled if the corresponding device is added to the zone. +# +fix_vfstab() +{ + if [[ -h $ZONEROOT/etc/vfstab || ! -f $ZONEROOT/etc/vfstab ]]; then + error "$e_badfile" "/etc/vfstab" + return + fi + + tmpfile=$(/usr/bin/mktemp -t -p /var/tmp) + if [[ -z "$tmpfile" ]]; then + error "$e_tmpfile" + return + fi + + /usr/bin/nawk '{ + if (substr($1, 0, 1) == "#") { + print $0 + } else if ($1 == "fd" || $1 == "/proc" || $1 == "swap" || + $1 == "ctfs" || $1 == "objfs" || $1 == "sharefs" || + $4 == "nfs" || $4 == "lofs") { + print $0 + } else { + print "#", $0 + modified=1 + } + } + END { + if (modified == 1) { + printf("# Modified by p2v ") + system("/usr/bin/date") + exit 0 + } + exit 1 + }' $ZONEROOT/etc/vfstab >>$tmpfile + + if (( $? == 0 )); then + if [[ ! -f $ZONEROOT/etc/vfstab.pre_p2v ]]; then + safe_copy $ZONEROOT/etc/vfstab \ + $ZONEROOT/etc/vfstab.pre_p2v + fi + safe_copy $tmpfile $ZONEROOT/etc/vfstab + fi + /usr/bin/rm -f $tmpfile +} + +# +# Delete or disable SMF services. +# Zone is booted to milestone=none when this function is called. +# +fix_smf() +{ + # + # Delete services that are delivered in hollow pkgs. + # + # Start by getting the svc manifests that are delivered by hollow + # pkgs then use 'svccfg inventory' to get the names of the svcs + # delivered by those manifests. The svc names are saved into a + # temporary file. We then login to the zone and delete them from SMF + # so that the various dependencies also get cleaned up properly. + # + + smftmpfile=$(/usr/bin/mktemp -t -p /var/tmp smf.XXXXXX) + if [[ -z "$smftmpfile" ]]; then + error "$e_tmpfile" + return + fi + + for i in /var/sadm/pkg/* + do + pkg=$(/usr/bin/basename $i) + [[ ! -f /var/sadm/pkg/$pkg/save/pspool/$pkg/pkgmap ]] && \ + continue + + manifests=$(/usr/bin/nawk '{if ($2 == "f" && + substr($4, 1, 17) == "var/svc/manifest/") print $4}' \ + /var/sadm/pkg/$pkg/save/pspool/$pkg/pkgmap) + + if [[ -n "$manifests" ]]; then + /usr/bin/egrep -s "SUNW_PKG_HOLLOW=true" \ + /var/sadm/pkg/$pkg/pkginfo || continue + + for j in $manifests + do + svcs=$(SVCCFG_NOVALIDATE=1 /usr/sbin/svccfg \ + inventory /$j) + for k in $svcs + do + case $k in + *:default) + # ignore default instance + ;; + *) + echo $k >> $smftmpfile + ;; + esac + done + done + fi + done + + # + # Zone was already booted to milestone=none, wait until SMF door exists. + # + for i in 0 1 2 3 4 5 6 7 8 9 + do + [[ -r $ZONEROOT/etc/svc/volatile/repository_door ]] && break + sleep 5 + done + + if [[ $i -eq 9 && ! -r $ZONEROOT/etc/svc/volatile/repository_door ]]; + then + error "$e_nosmf" + /usr/bin/rm -f $smftmpfile + return + fi + + insttmpfile=$(/usr/bin/mktemp -t -p /var/tmp instsmf.XXXXXX) + if [[ -z "$insttmpfile" ]]; then + error "$e_tmpfile" + /usr/bin/rm -f $smftmpfile + return + fi + + # Get a list of the svcs that exist in the zone. + /usr/sbin/zlogin -S $ZONENAME /usr/bin/svcs -aH | \ + /usr/bin/nawk '{print $3}' >>$insttmpfile + + [[ -n $LOGFILE ]] && \ + printf "[$(date)] ${MSG_PREFIX}${v_svcsinzone}\n" >&2 + [[ -n $LOGFILE ]] && cat $insttmpfile >&2 + + vlog "$v_rmhollowsvcs" + for i in $(cat $smftmpfile) + do + # Skip svcs not installed in the zone. + /usr/bin/egrep -s "$i:" $insttmpfile || continue + + # Delete the svc. + vlog "$v_delsvc" "$i" + /usr/sbin/zlogin -S $ZONENAME /usr/sbin/svccfg delete $i >&2 \ + || error "$e_delsvc" $i + done + + /usr/bin/rm -f $smftmpfile + + # + # Fix network services if shared stack. + # + if [[ "$STACK_TYPE" == "shared" ]]; then + vlog "$v_fixnetsvcs" + + NETPHYSDEF="svc:/network/physical:default" + NETPHYSNWAM="svc:/network/physical:nwam" + + /usr/bin/egrep -s "$NETPHYSDEF" $insttmpfile + if (( $? == 0 )); then + vlog "$v_enblsvc" "$NETPHYSDEF" + /usr/sbin/zlogin -S $ZONENAME \ + /usr/sbin/svcadm enable $NETPHYSDEF || \ + error "$e_dissvc" "$NETPHYSDEF" + fi + + /usr/bin/egrep -s "$NETPHYSNWAM" $insttmpfile + if (( $? == 0 )); then + vlog "$v_dissvc" "$NETPHYSNWAM" + /usr/sbin/zlogin -S $ZONENAME \ + /usr/sbin/svcadm disable $NETPHYSNWAM || \ + error "$e_enblsvc" "$NETPHYSNWAM" + fi + + for i in $(/usr/bin/egrep network/routing $insttmpfile) + do + # Disable the svc. + vlog "$v_dissvc" "$i" + /usr/sbin/zlogin -S $ZONENAME \ + /usr/sbin/svcadm disable $i || \ + error "$e_dissvc" $i + done + fi + + # + # Disable well-known services that don't run in a zone. + # + vlog "$v_rminvalidsvcs" + for i in $(/usr/bin/egrep -hv "^#" \ + /usr/lib/brand/native/smf_disable.lst \ + /etc/brand/native/smf_disable.conf) + do + # Skip svcs not installed in the zone. + /usr/bin/egrep -s "$i:" $insttmpfile || continue + + # Disable the svc. + vlog "$v_dissvc" "$i" + /usr/sbin/zlogin -S $ZONENAME /usr/sbin/svcadm disable $i || \ + error "$e_dissvc" $i + done + + # + # Since zones can't be NFS servers, disable all of the instances of + # the shares svc. + # + for i in $(/usr/bin/egrep network/shares/group $insttmpfile) + do + vlog "$v_dissvc" "$i" + /usr/sbin/zlogin -S $ZONENAME /usr/sbin/svcadm disable $i || \ + error "$e_dissvc" $i + done + + /usr/bin/rm -f $insttmpfile +} + +# +# Remove well-known pkgs that do not work inside a zone. +# +rm_pkgs() +{ + /usr/bin/cat <<-EOF > $ZONEROOT/tmp/admin || fatal "$e_adminf" + mail= + instance=overwrite + partial=nocheck + runlevel=nocheck + idepend=nocheck + rdepend=nocheck + space=nocheck + setuid=nocheck + conflict=nocheck + action=nocheck + basedir=default + EOF + + for i in $(/usr/bin/egrep -hv "^#" /usr/lib/brand/native/pkgrm.lst \ + /etc/brand/native/pkgrm.conf) + do + [[ ! -d $ZONEROOT/var/sadm/pkg/$i ]] && continue + + vlog "$v_rmpkg" "$i" + /usr/sbin/zlogin -S $ZONENAME \ + /usr/sbin/pkgrm -na /tmp/admin $i >&2 || error "$e_rmpkg" $i + done +} + +# +# Zoneadmd writes a one-line index file into the zone when the zone boots, +# so any information about installed zones from the original system will +# be lost at that time. Here we'll warn the sysadmin about any pre-existing +# zones that they might want to clean up by hand, but we'll leave the zonepaths +# in place in case they're on shared storage and will be migrated to +# a new host. +# +warn_zones() +{ + zoneconfig=$ZONEROOT/etc/zones + + if [[ -h $zoneconfig/index || ! -f $zoneconfig/index ]]; then + error "$e_badfile" "/etc/zones/index" + return + fi + + NGZ=$(/usr/bin/nawk -F: '{ + if (substr($1, 0, 1) == "#" || $1 == "global") + continue + + if ($2 == "installed") + printf("%s ", $1) + }' $zoneconfig/index) + + # Return if there are no installed zones to warn about. + [[ -z "$NGZ" ]] && return + + log "$v_rmzones" "$NGZ" + + NGZP=$(/usr/bin/nawk -F: '{ + if (substr($1, 0, 1) == "#" || $1 == "global") + continue + + if ($2 == "installed") + printf("%s ", $3) + }' $zoneconfig/index) + + log "$v_rmzonepaths" + + for i in $NGZP + do + log " %s" "$i" + done +} + +unset LD_LIBRARY_PATH +PATH=/usr/sbin:/usr/bin +export PATH + +# +# ^C Should cleanup; if the zone is running, it should try to halt it. +# +zone_is_running=0 +trap trap_cleanup INT + +# +# Parse the command line options. +# +unset backout +OPT_U= +OPT_V= +OPT_M= +OPT_L= +while getopts "b:uvm:l:" opt +do + case "$opt" in + b) if [[ -n "$backout" ]]; then + backout="$backout -b $OPTARG" + else + backout="-b $OPTARG" + fi + ;; + u) OPT_U="-u";; + v) OPT_V="-v";; + m) MSG_PREFIX="$OPTARG"; OPT_M="-m \"$OPTARG\"";; + l) LOGFILE="$OPTARG"; OPT_L="-l \"$OPTARG\"";; + *) usage;; + esac +done +shift OPTIND-1 + +(( $# < 1 )) && usage + +(( $# > 2 )) && usage + +[[ -n $LOGFILE ]] && exec 2>>$LOGFILE + +ZONENAME=$1 +ZONEPATH=$2 +ZONEROOT=$ZONEPATH/root + +e_badinfo=$(gettext "Failed to get '%s' zone resource") +e_badfile=$(gettext "Invalid '%s' file within the zone") +e_tmpfile=$(gettext "Unable to create temporary file") +v_mkdirs=$(gettext "Creating mount points") +v_nonetfix=$(gettext "Cannot update /etc/hostname.{net} file") +v_update=$(gettext "Updating the zone software to match the global zone...") +v_updatedone=$(gettext "Zone software update complete") +e_badupdate=$(gettext "Updating the Zone software failed") +v_adjust=$(gettext "Updating the image to run within a zone") +v_stacktype=$(gettext "Stack type '%s'") +v_booting=$(gettext "Booting zone to single user mode") +e_badboot=$(gettext "Zone boot failed") +e_nosmf=$(gettext "ERROR: SMF repository unavailable.") +e_nosingleuser=$(gettext "ERROR: zone did not finish booting to single-user.") +v_svcsinzone=$(gettext "The following SMF services are installed:") +v_rmhollowsvcs=$(gettext "Deleting SMF services from hollow packages") +v_fixnetsvcs=$(gettext "Adjusting network SMF services") +v_rminvalidsvcs=$(gettext "Disabling invalid SMF services") +v_delsvc=$(gettext "Delete SMF svc '%s'") +e_delsvc=$(gettext "deleting SMF svc '%s'") +v_enblsvc=$(gettext "Enable SMF svc '%s'") +e_enblsvc=$(gettext "enabling SMF svc '%s'") +v_dissvc=$(gettext "Disable SMF svc '%s'") +e_dissvc=$(gettext "disabling SMF svc '%s'") +e_adminf=$(gettext "Unable to create admin file") +v_rmpkg=$(gettext "Remove package '%s'") +e_rmpkg=$(gettext "removing package '%s'") +v_rmzones=$(gettext "The following zones in this image will be unusable: %s") +v_rmzonepaths=$(gettext "These zonepaths could be removed from this image:") +v_unconfig=$(gettext "Performing zone sys-unconfig") +e_unconfig=$(gettext "sys-unconfig failed") +v_halting=$(gettext "Halting zone") +e_shutdown=$(gettext "Shutting down zone %s...") +e_badhalt=$(gettext "Zone halt failed") +v_exitgood=$(gettext "Postprocessing successful.") +e_exitfail=$(gettext "Postprocessing failed.") + +# +# Do some validation on the paths we'll be accessing +# +safe_dir etc +safe_dir etc/dfs +safe_dir etc/zones +safe_dir var + +# Now do the work to update the zone. + +# Before booting the zone we may need to create a few mnt points, just in +# case they don't exist for some reason. +# +# Whenever we reach into the zone while running in the global zone we +# need to validate that none of the interim directories are symlinks +# that could cause us to inadvertently modify the global zone. +vlog "$v_mkdirs" +if [[ ! -f $ZONEROOT/tmp && ! -d $ZONEROOT/tmp ]]; then + mkdir -m 1777 -p $ZONEROOT/tmp || exit $EXIT_CODE +fi +if [[ ! -f $ZONEROOT/var/run && ! -d $ZONEROOT/var/run ]]; then + mkdir -m 1755 -p $ZONEROOT/var/run || exit $EXIT_CODE +fi +if [[ ! -h $ZONEROOT/etc && ! -f $ZONEROOT/etc/mnttab ]]; then + /usr/bin/touch $ZONEROOT/etc/mnttab || exit $EXIT_CODE + /usr/bin/chmod 444 $ZONEROOT/etc/mnttab || exit $EXIT_CODE +fi +if [[ ! -f $ZONEROOT/proc && ! -d $ZONEROOT/proc ]]; then + mkdir -m 755 -p $ZONEROOT/proc || exit $EXIT_CODE +fi +if [[ ! -f $ZONEROOT/dev && ! -d $ZONEROOT/dev ]]; then + mkdir -m 755 -p $ZONEROOT/dev || exit $EXIT_CODE +fi +if [[ ! -h $ZONEROOT/etc && ! -h $ZONEROOT/etc/svc && ! -d $ZONEROOT/etc/svc ]] +then + mkdir -m 755 -p $ZONEROOT/etc/svc/volatile || exit $EXIT_CODE +fi + +# Check for zones inside of image. +warn_zones + +# +# Run update on attach. State is currently 'incomplete' so use the private +# force-update option. +# +log "$v_update" +/usr/sbin/zoneadm -z $ZONENAME attach -U $backout >&2 +res=$? +if (( $? != 0 )); then + fatal "$e_badupdate" +else + log "$v_updatedone" +fi + +log "$v_adjust" + +# +# Any errors in these functions are not considered fatal. The zone can be +# be fixed up manually afterwards and it may need some additional manual +# cleanup in any case. +# + +STACK_TYPE=$(/usr/sbin/zoneadm -z $ZONENAME list -p | \ + /usr/bin/nawk -F: '{print $7}') +if (( $? != 0 )); then + error "$e_badinfo" "stacktype" +fi +vlog "$v_stacktype" "$STACK_TYPE" + +fix_net +fix_nfs +fix_vfstab + +vlog "$v_booting" + +# +# Boot the zone so that we can do all of the SMF updates needed on the zone's +# repository. +# + +zone_is_running=1 + +# The 'update on attach' left the zone installed. +/usr/sbin/zoneadm -z $ZONENAME boot -f -- -m milestone=none +if (( $? != 0 )); then + error "$e_badboot" + fatal "$e_exitfail" +fi + +# cleanup SMF services +fix_smf + +# remove invalid pkgs +rm_pkgs + +vlog "$v_halting" +/usr/sbin/zoneadm -z $ZONENAME halt +if (( $? != 0 )); then + error "$e_badhalt" + failed=1 +fi +zone_is_running=0 + +if [[ -z $failed && -n $OPT_U ]]; then + # + # We're sys-unconfiging the zone. This will halt the zone, however + # there are problems with sys-unconfig and it usually hangs when the + # zone is booted to milestone=none. This is why we previously halted + # the zone. We now boot to milestone=single-user. Again, the + # sys-unconfig can hang if the zone is still in the process of + # booting when we try to run sys-unconfig. Wait until the boot is + # done, which we do by checking for sulogin, or waiting 30 seconds, + # whichever comes first. + # + + vlog "$v_unconfig" + + zone_is_running=1 + /usr/sbin/zoneadm -z $ZONENAME boot -- -m milestone=single-user + if (( $? != 0 )); then + error "$e_badboot" + fatal "$e_exitfail" + fi + + for i in 0 1 2 3 4 5 6 7 8 9 + do + sleep 10 + /usr/sbin/zlogin $ZONENAME \ + /usr/bin/svcs -H svc:/milestone/single-user:default 2>&1 | + /usr/bin/nawk '{ + if ($1 == "online") + exit 0 + else + exit 1 + }' && break + done + + if (( $i == 9 )); then + vlog "$e_nosingleuser" + fi + + echo "yes" | /usr/sbin/zlogin -S $ZONENAME \ + /usr/sbin/sys-unconfig >/dev/null 2>&1 + if (( $? != 0 )); then + error "$e_unconfig" + failed=1 + fi +fi + + +if [[ -n $failed ]]; then + fatal "$e_exitfail" +fi + +vlog "$v_exitgood" +exit 0