# # Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. # # # Only change PATH if you give full consideration to GNU or other variants # of common commands having different arguments and output. # export PATH=/usr/bin:/usr/sbin unset LD_LIBRARY_PATH . /usr/lib/brand/shared/common.ksh PROP_PARENT="org.opensolaris.libbe:parentbe" PROP_ACTIVE="org.opensolaris.libbe:active" PROP_BE_HANDLE="com.oracle.libbe:nbe_handle" PROP_CANDIDATE="com.oracle.zoneadm:candidate_zbe" PROP_LBTIME="com.oracle.libbe:last-boot-time" ATTACH_LAST_BOOTED_ZBE="attach-last-booted-zbe" FORCE_ZPOOL_IMPORT="force-zpool-import" PROP_ORPHANED="com.oracle.libbe:orphaned" DENY_ZBE_CLONE="deny-zbe-clone" FORCE_ZBE_CLONE="force-zbe-clone" DESTROY_ORPHAN_ZBES="destroy-orphan-zbes" f_incompat_options=$(gettext "cannot specify both %s and %s options") sanity_ok=$(gettext " Sanity Check: Passed. Looks like a Solaris system.") sanity_fail_vers=$(gettext " Sanity Check: the Solaris image (release %s) is not an OpenSolaris image and cannot be installed in this type of branded zone.") p2ving=$(gettext " Updating: Converting image to non-global.") p2v_done=$(gettext " Result: Conversion complete.") p2v_fail=$(gettext " Result: Conversion failed") install_fail=$(gettext " Result: *** Installation FAILED ***") f_multiple_ds=$(gettext "Multiple active datasets.") f_zfs_unmount=$(gettext "Unable to unmount the zone's root ZFS dataset (%s).\nIs there a global zone process inside the zone root?\nThe current zone boot environment will remain mounted.\n") f_sysrepo_fail=$(gettext "Unable to enable svc:/application/pkg/system-repository, please enable the service manually.") f_zones_proxyd_fail=$(gettext "Unable to enable svc:/application/pkg/zones-proxyd, please enable the service manually.") f_set_sysrepo_prop_fail=$(gettext "Unable to set the use-system-repo property.") # Used by install and attach during argument processing f_arg_not_dir=$(gettext "Argument %s is not a directory") f_arg_not_file=$(gettext "Argument %s is not a regular file") f_arg_not_file_or_dir=$(gettext "Argument %s is not a file or directory") f_gz_image=$(gettext "Cannot attach global zone image") f_active_zbe=$(gettext "Cannot attach active zone boot environment '%s' for another global zone with '%s' option.") f_rootds_orphan=$(gettext "Zone boot environment '%s' has invalid properties.") f_image_arch_mismatch=$(gettext "Current architecture (%s) doesn't match zone image architecture (%s).") v_mounting=$(gettext "Mounting the zone") e_badmount=$(gettext "Zone mount failed") e_badunmount=$(gettext "Zone unmount failed") v_update_format=$(gettext "Updating image format") e_update_format=$(gettext "Updating image format failed") m_complete_seconds=$(gettext " Done: Installation completed in %s seconds.") m_postnote=$(gettext " Next Steps: Boot the zone, then log into the zone console (zlogin -C)") m_postnote2=$(gettext " to complete the configuration process.") m_zbe_discover_failed=$(gettext "Unable to determine which boot environment to activate. Candidates are:\n") m_zbe_discover_header=$(gettext "Zone Boot Environment Active Global Zone Boot Environment\n--------------------- ------ ------------------------------------") missing_gzbe=$(gettext "Missing Global Zone Boot Environment") m_again_with_dash_z=$(gettext "Use the following command to attach a specific zone boot environment:\n%s") e_no_be_0=$(gettext "Zone has no boot environments") e_no_be_1=$(gettext "Zone has no boot environment with any of these names: %s") f_variant_check=$(gettext " Variant Check: FAILED, couldn't determine %s from image.") m_cache=$(gettext " Cache: Using %s.") f_update_required=$(gettext "Attach failed. This zone must be attached with the -u or -U option.") f_uname_p=$(gettext "Unable to determine current architecture.") f_list=$(gettext "Could not list packages in image: %s") f_osnet=$(gettext "Could not find the osnet-incorporation package installed in image: %s") f_update=$(gettext "Could not update attaching zone") f_system_volatile=$(gettext "Could not create /system/volatile in the zone.") m_active_zbe=$(gettext " Zone BE root dataset: %s") m_prep_image=$(gettext " Updating non-global zone: Preparing packages.") m_image_link=$(gettext " Updating non-global zone: Linking to image %s.") m_image_audit=$(gettext " Updating non-global zone: Auditing packages.") m_image_sync=$(gettext " Updating non-global zone: Syncing packages.") m_image_sync1=$(gettext " Updating non-global zone: Syncing packages (pass 1 of 2).") m_image_sync2=$(gettext " Updating non-global zone: Syncing packages (pass 2 of 2).") m_image_update=$(gettext " Updating non-global zone: Updating packages.") m_sync_done=$(gettext " Updating non-global zone: Zone updated.") m_complete=$(gettext " Result: Attach Succeeded.") m_active_gzbe_uuid=$(gettext "Currently active global zone BE UUID: %s") m_ngz_be_found=$(gettext "Nested non-global zone BE found: %s") f_invalid_xopt=$(gettext "Extended options '%s' and '%s' are mutually exclusive. They cannot be used together.") f_destroy_zbe=$(gettext "Unable to destroy all orphan zone boot environments.") is_brand_labeled() { if [[ -z $ALTROOT ]]; then AR_OPTIONS="" else AR_OPTIONS="-R $ALTROOT" fi brand=$(/usr/sbin/zoneadm $AR_OPTIONS -z $ZONENAME \ list -p | awk -F: '{print $6}') [[ $brand == "labeled" ]] && return 0 return 1 } function sanity_check { typeset dir="$1" shift res=0 # # Check for some required directories. # checks="etc etc/svc var var/svc" for x in $checks; do if [[ ! -e $dir/$x ]]; then log "$f_sanity_detail" "$x" "$dir" res=1 fi done if (( $res != 0 )); then log "$sanity_fail" fatal "$install_fail" "$ZONENAME" fi # Check for existence of pkg command. if [[ ! -x $dir/usr/bin/pkg ]]; then log "$f_sanity_detail" "usr/bin/pkg" "$dir" log "$sanity_fail" fatal "$install_fail" "$ZONENAME" fi # # XXX There should be a better way to do this. # Check image release. We only work on the same minor release as the # system is running. The INST_RELEASE file doesn't exist with IPS on # OpenSolaris, so its presence means we have an earlier Solaris # (i.e. non-OpenSolaris) image. # 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) vlog "$sanity_fail_vers" "$image_vers" fatal "$install_fail" "$ZONENAME" fi vlog "$sanity_ok" } # # get_active_be zone # # Finds the active boot environment for the given zone. # # Arguments: # # zone zone structure initialized with init_zone # # Globals: # # CURRENT_GZBE Current global zone boot environment. If not already set, # it will be set. # # Returns: # # 0 on success, else 1. # function get_active_be { typeset -n zone=$1 typeset active_ds= typeset tab=$(printf "\t") if [[ -z "$CURRENT_GZBE" ]]; then get_current_gzbe fi typeset name parent active zfs list -H -r -d 1 -t filesystem -o name,$PROP_PARENT,$PROP_ACTIVE \ "${zone.ROOT_ds}" | while IFS=$'\t' read name parent active ; do [[ $parent == "$CURRENT_GZBE" ]] || continue [[ $active == on ]] || continue vlog "Found active dataset %s" "$name" if [[ -n "$active_ds" ]]; then error "$f_multiple_ds" return 1 fi active_ds=$name done if [[ -z $active_ds ]]; then error "$f_no_active_ds" return 1 fi zone.active_ds=$active_ds } # # is_zbe_orphan parentbe_uuid # # Check if the zbe with the given parentbe_uuid is an orphan. Based on the # check, the function prints boolean string - "true" or "false" which the # calling function consumes. # # NOTE: A ZBE, whose parent BE uuid does not match to any of # the extant GZBE on the host is termed orphan. # # Returns: # # 0 if zbe is orphan, !0 otherwise # function is_zbe_orphan { typeset parentbe_uuid=$1 typeset be gzbe_uuid junk /usr/sbin/beadm list -H | while IFS=\; read be gzbe_uuid junk; do if [[ "$gzbe_uuid" == "$parentbe_uuid" ]]; then # Parent GZ BE found ! Not an orphan. echo "false" return 1; fi done # No match found; It is an orphan. echo "true" return 0 } # # claim_zbe zone zbe # # If the zbe belongs to the existing gzbe or the zbe was extracted from an # archive and has not yet been attached, set it as the active zbe. Otherwise, # clone it and set the clone to the active zbe. On success, the complete BE, # i.e, all BE datasets (including children of root dataset) are mounted. # # NOTE: -x options force-zbe-clone and deny-zbe-clone can # override the behavior accordingly. # # If claim_zbe is called with -x deny-zbe-clone with a active # ZBE that is associated with any extant GZBE other than current # GZBE, or is an orphan ZBE, it should fail because we don't want # to render the zone un-usable in that GZBE. # # Globals: # # EXIT_CODE On success, set as described in clone_zbe. # # Returns 0 on success, !0 on failure. # function claim_zbe { typeset -n zone=$1 typeset zbe=$2 typeset dss init_dataset dss "${zone.ROOT_ds}/$zbe" || return 1 if [[ -z "$CURRENT_GZBE" ]]; then get_current_gzbe fi if [[ ${dss.props[$PROP_CANDIDATE].value} == "$CURRENT_GZBE" || ${dss.props[$PROP_PARENT].value} == "$CURRENT_GZBE" ]]; then # Check for a flag override. if [[ -z ${zone.xopts[$FORCE_ZBE_CLONE]} ]]; then clone_zbe zone "$zbe" return $? fi mount_active_be -c -b "$zbe" zone return $? fi # # $zbe is either associated with extant GZBE other than current # GZBE, or is an orphan ZBE. # if [[ -n ${zone.xopts[$DENY_ZBE_CLONE]} ]]; then # Don't allow claiming an active ZBE from another GZBE. if [[ ${dss.props[$PROP_ACTIVE].value} == "on" ]]; then log "$f_active_zbe" "$zbe" "$DENY_ZBE_CLONE" return 1 fi mount_active_be -c -b "$zbe" zone return $? fi # Sets EXIT_CODE. clone_zbe zone "$zbe" return $? } # # clone_zbe [-u] zone zbe # # Clones a zbe within a zone and sets the new ZBE as the active BE for this # zone. # # Options and arguments: # # zone zone structure initialized with init_zone # zbe Name of the zone boot environment to clone. # # Globals: # # EXIT_CODE On success, set to ZONE_SUBPROC_UNAVAILABLE. # On failure, set as described in clone_zone_rpool. # # Returns: # # 0 Success, and members of the zone structure have been updated. # zone.active_ds Updated with the dataset that is # the root of the zbe. # zone.zbe_cloned_from Set to the name of the zbe passed in # 1 Failure. Error message has been logged. # function clone_zbe { typeset -n zone=$1 typeset zbe=$2 typeset -i i typeset snapname typeset dsn=${zone.ROOT_ds}/$zbe [[ -z $zbe ]] && fail_internal "zbe not specified" /usr/sbin/zfs list -o name "$dsn" >/dev/null 2>&1 || \ fail_internal "Dataset '%s' does not exist" "$dsn" typeset now=$(date +%Y-%m-%d-%H:%M:%S) snapname=$now for (( i=0; i < 100; i++ )); do /usr/sbin/zfs snapshot -r "$dsn@$snapname" >/dev/null 2>&1 && \ break snapname=$(printf "%s-%02d" "$now" $i) done if (( i == 100 )); then error "$f_zfs_snapshot_of" "$dsn" return 1 fi # Clone, activate, and mount ZBE zone.active_ds=${zone.ROOT_ds}/$zbe # Sets EXIT_CODE. "zone" appears twice as it is the source and target. clone_zone_rpool zone zone "$snapname" || return 1 zone.zbe_cloned_from=$zbe EXIT_CODE=$ZONE_SUBPROC_UNAVAILABLE return 0 } # mount_last_booted_be # # Looks for the last booted ZBE by searching through timestamp. # # Arguments # # zone zone data strcuture initialized with init_zone. # ngzbe An associative array of non global zone be's. # timestamp_ref reference for the timestamp for ngzbe. # active_gzbe a variable set for uuid of an associated active_be. # # NOTE: -x options force-zbe-clone and deny-zbe-clone can # override the behavior accordingly. # # Returns : # # 0 Success, if we found the last booted zbe and mounted it # successfully. # 1 Fail, if last booted zbe is found but cannot mount or activate it # 2 Fail, if can not find any last booted zbe. # function mount_last_booted_be { typeset -n zone=$1 typeset -n ngzbe=$2 typeset timestamp_ref=$3 typeset active_gzbe=$4 typeset lb_recent=$timestamp_ref typeset be lb_zbe for be in "${!ngzbe[@]}"; do if [[ "$lb_recent" < "${ngzbe[$be].lb_time}" ]]; then lb_recent=${ngzbe[$be].lb_time} lb_zbe="$be" fi done if [[ "$lb_recent" == "$timestamp_ref" ]]; then return 2 fi # Going to clone if the selected ZBE is not associated # with current active GZ BE. if [[ "${ngzbe[$lb_zbe].parent}" != "$active_gzbe" ]]; then # Check if a deny-zbe-clone flag is set. if [[ -n ${zone.xopts[$DENY_ZBE_CLONE]} ]]; then mount_active_be -c -b "$lb_zbe" zone return $? fi clone_zbe zone "$lb_zbe" return $? fi # Create the clone if force-zbe-clone flag is set. if [[ -n ${zone.xopts[$FORCE_ZBE_CLONE]} ]]; then clone_zbe zone "$lb_zbe" return $? fi # Mount the boot environment mount_active_be -c -b "$lb_zbe" zone return $? } # log_print_suitable_active_zbe # # Logs the failure and prints the information for which zbe's are suitable # candidates for an active zbe. # # Arguments # # ngzbe An associative array of non global zone be's. # uuid2gzbe Associative array to store uuid's of global zones. # uuid2gzbe[]= function log_print_suitable_active_zbe { typeset zbe typeset -n ngzbe=$1 typeset -n uuid2gzbe=$2 log "$m_zbe_discover_failed" log "$m_zbe_discover_header" for zbe in "${!ngzbe[@]}" ; do typeset uuid=${ngzbe[$zbe].parent} typeset bename=${uuid2gzbe[$uuid]} if [[ $bename == $missing_gzbe ]]; then typeset cuuid=${ngzbe[$zbe].candidate} if [[ $cuuid != $missing_gzbe && \ -n ${uuid2gzbe[$cuuid]} ]]; then bename="Extracted for ${uuid2gzbe[$cuuid]}" else bename=$uuid fi fi log "%-21s %-6s %s" "$zbe" "${ngzbe[$zbe].active}" "$bename" done log "%s" "" # # Install and attach need different messages. m_usage_dash_z should # be defined in the brand's install and attach scripts. # if (( ${#ATTACH_Z_COMMAND[@]} != 0 )); then EXIT_CODE=$ZONE_SUBPROC_TRYAGAIN log "$m_again_with_dash_z" "${ATTACH_Z_COMMAND[*]}" fi } # # discover_active_be zone # # Looks for the ZBE that is best suited to be the active ZBE. # # The caller may optionally constrain the list of ZBEs that is considered for # activation by populating the zone.allowed_bes associative array. In such a # case, If zone.allowed_bes is a non-empty associative array, only BEs in that # array are considered. # # After selecting which ZBE is the best candidate for activation, care will # be taken not to "steal" the ZBE from another global zone. If the chosen # zbe has $PROP_PARENT matching the UUID of an extant global zone BE, it is # the chosen ZBE is cloned and this new clone is the ZBE that is selected for # activation. # # Note, however, that a ZBE that appears in zone.allowed_bes is # never cloned. It is assumed that zone.allowed_bes contains a set of ZBEs # that was received from an archive and any existing values in $PROP_PARENT # on these ZBEs are stale. # # NOTE: -x options force-zbe-clone and deny-zbe-clone can # override the behavior accordingly. # # Arguments # # zone A zone structure initialized with init_zone. # # Globals # # EXIT_CODE ZONE_SUBPROC_UNAVAILABLE ZBE cloning was attempted # and succeeded. # ZONE_SUBPROC_FATAL ZBE cloning was attempted, # failed, and cleanup failed. # ZONE_SUBPROC_TRYAGAIN ZBE selection required. A # list of available ZBEs was # displayed. # # Returns: # # 0 Success. The discovered active_be has been activated (see # set_active_be() for details) and has been mounted on the zone root. # 1 Active dataset could not be found and an error message has been # printed. # function discover_active_be { typeset -n zone=$1 shift typeset -A uuid2gzbe typeset -i needs_selection=0 typeset timestamp_ref=00000000T000000Z typeset rval # # Load an associative array of global zone BEs. Store current uuid # of GZBE in $active_gzbe. # # uuid2gzbe[]= # typeset be uuid active junk typeset active_gzbe beadm list -H | while IFS=\; read be uuid active junk ; do uuid2gzbe[$uuid]=$be [[ $active == *N* ]] && active_gzbe=$uuid done if [[ -z $active_gzbe ]]; then error "%s: unable to get global zone BE UUID" "${zone.name}" return 1 fi # # Load an associative array of non-global zone BEs and arrays of # likely candidates. # # ngzbe[].parent= # ngzbe[].active= # ngzbe[].mountpoint= # ngzbe[].candidate= # ngzbe[].lb_time= # typeset name mountpoint parent active candidate typeset -A ngzbe typeset -a activezbe # NGZ BEs that are active typeset -a this_gz # NGZ BEs that match this GZ BE typeset -a this_gz_active # match GZ BE and is active zfs list -H -r -d 1 -t filesystem -o \ name,mountpoint,$PROP_PARENT,$PROP_ACTIVE,$PROP_CANDIDATE,$PROP_LBTIME \ "${zone.ROOT_ds}" | \ while IFS=$'\t' read name mountpoint parent active candidate lb_time do # skip the non-BE top-level dataset [[ $name == "${zone.ROOT_ds}" ]] && continue typeset curbe=$(basename "$name") # skip BEs that are not in the allowed_bes list if (( ${#zone.allowed_bes[@]} != 0 )) && [[ -z ${zone.allowed_bes[$curbe]} ]]; then vlog "Ignoring dataset %s: %s not in allowed_bes" \ "$name" "$(basename "$name")" continue fi if [[ -z ${uuid2gzbe[$parent]} ]]; then uuid2gzbe[$parent]=$missing_gzbe fi ngzbe[$curbe].parent=$parent ngzbe[$curbe].active=$active ngzbe[$curbe].mountpoint=$mountpoint ngzbe[$curbe].candidate=$candidate if [[ "$lb_time" == [0-9]*T[0-9]*Z ]]; then ngzbe[$curbe].lb_time=$lb_time else ngzbe[$curbe].lb_time=$timestamp_ref fi # Update some arrays used in decision process. if [[ $parent == "$active_gzbe" ]]; then a_push this_gz "$curbe" [[ $active == on ]] && a_push this_gz_active "$curbe" fi [[ $active == on ]] && a_push activezbe "$curbe" done # # If there are no BEs, return an error # if (( ${#ngzbe[@]} == 0 )); then if (( ${#zone.allowed_bes[@]} == 0 )); then error "$e_no_be_0" else error "$e_no_be_1" "${!zone.allowed_bes[*]}" fi return 1 fi # Boot the last booted ZBE if '-x attach-last-booted' zbe is # specified. if [[ -n ${zone.xopts[$ATTACH_LAST_BOOTED_ZBE]} ]]; then mount_last_booted_be zone ngzbe "$timestamp_ref" "$active_gzbe" rval=$? if (( $rval == 2 )); then # log if we dont find a last booted zbe log_print_suitable_active_zbe ngzbe uuid2gzbe return 1 fi return $rval fi # If there was only one ZBE active for this GZ, activate it. if (( ${#this_gz_active[@]} == 1 )); then # Force clone if specified. if [[ -n ${zone.xopts[$FORCE_ZBE_CLONE]} ]]; then clone_zbe zone "${this_gz_active[0]}" return $? fi mount_active_be -c -b "${this_gz_active[0]}" zone return $? fi # If there was only one ZBE that was active, clone and/or activate it. if (( ${#activezbe[@]} == 1 )); then typeset zbe="${activezbe[0]}" # mount the zbe if its from allowed_bes[]. Check flag override. if [[ ${#zone.allowed_bes[@]} != 0 && \ -z ${zone.xopts[$FORCE_ZBE_CLONE]} ]] || [[ -n ${zone.xopts[$DENY_ZBE_CLONE]} ]]; then mount_active_be -c -b "$zbe" zone return $? fi # If the zbe is not associated with any gzbe, clone it. if [[ ${uuid2gzbe[${ngzbe[$zbe].parent}]} == "$missing_gzbe" && -z ${zone.xopts[$DENY_ZBE_CLONE]} ]]; then clone_zbe zone "$zbe" return $? fi mount_active_be -c -b "$zbe" zone return $? fi # If there was only one ZBE, clone and/or activate it if (( ${#ngzbe[@]} == 1 )); then # # We really want the name of index 0, but a subscript of 0 # is not supported. Since we know that there is only one # item in the associative array, the name of all the items # is equivalent to the name of the first item. # typeset zbe="${!ngzbe[@]}" # mount the zbe if its from allowed_bes[]. Check flag override. if [[ ${#zone.allowed_bes[@]} && \ -z ${zone.xopts[$FORCE_ZBE_CLONE]} ]] || [[ -n ${zone.xopts[$DENY_ZBE_CLONE]} ]]; then mount_active_be -c -b "$zbe" zone return $? fi # Sets EXIT_CODE. clone_zbe zone "$zbe" return $? fi # Couldn't decide on the ZBE, activating the last booted ZBE mount_last_booted_be zone ngzbe "$timestamp_ref" "$active_gzbe" rval=$? if (( $rval != 2 )); then return $rval fi # Last boot timestamp not set. # If there was only one ZBE associated with this GZ, activate it. if (( ${#this_gz[@]} == 1 )); then if [[ -n ${zone.xopts[$FORCE_ZBE_CLONE]} ]]; then clone_zbe zone "${this_gz[0]}" return $? fi mount_active_be -c -b "${this_gz[0]}" zone return $? fi log_print_suitable_active_zbe ngzbe uuid2gzbe return 1 } # # set_active_be zone bootenv # # Sets the active boot environment for the zone. This includes updating the # zone structure and setting the required properties ($PROP_PARENT, # $PROP_ACTIVE) on the top-level BE datasets. # function set_active_be { typeset -n zone="$1" typeset be=$2 typeset name canmount parent active candidate [[ -z $be ]] && fail_internal "zbe not specified" if [[ -z "$CURRENT_GZBE" ]]; then get_current_gzbe fi # # Turn off the active property on BE's with the same GZBE and ensure # that there aren't any BE datasets that will mount automatically. # zfs list -H -r -d 1 -t filesystem -o \ name,canmount,$PROP_PARENT,$PROP_ACTIVE,$PROP_CANDIDATE \ ${zone.ROOT_ds} | \ while IFS=$'\t' read name canmount parent active candidate ; do # skip the ROOT dataset [[ $name == "${zone.ROOT_ds}" ]] && continue # The root of each BE should only be mounted explicitly. if [[ $canmount != noauto ]]; then zfs set canmount=noauto "$name" || \ fail_internal "$e_zfs_set" canmount "$name" fi # # If this was extracted from an archive within this GZ, # finish the association process. In the unlikely event # that these property updates fail, manual cleanup may # be required, but it should not prevent the attach. # if [[ $candidate == "$CURRENT_GZBE" ]]; then zfs inherit "$PROP_CANDIDATE" "$name" || log "$e_zfs_inherit" "$PROP_CANDIDATE" "$name" # # Setting the parent to this gzbe makes it possible # for beadm to clean up the zbes within the zone # once one of the candidate zbe's is attached. # if [[ $parent != "$CURRENT_GZBE" ]]; then zfs set "$PROP_PARENT=$CURRENT_GZBE" "$name" || log "$e_zfs_set" "$PROP_PARENT" "$name" parent=$CURRENT_GZBE fi fi # Deactivate BEs for this GZ that are not being set to active. [[ $parent == "$CURRENT_GZBE" ]] || continue [[ $active == on ]] || continue [[ $name == "${zone.ROOT_ds}/$be" ]] && continue vlog "Deactivating active dataset %s" "$name" zfs set $PROP_ACTIVE=off "$name" || return 1 done zone.active_ds="${zone.ROOT_ds}/$be" zfs set "$PROP_PARENT=$CURRENT_GZBE" ${zone.active_ds} \ || return 1 zfs set "$PROP_ACTIVE=on" ${zone.active_ds} || return 1 zfs set "$PROP_BE_HANDLE=on" "${zone.rpool_ds}" || return 1 typeset origin zfs list -H -o name,origin "${zone.active_ds}" | while read name origin do if [[ $origin == "${zone.ROOT_ds}"/* ]]; then vlog "Promoting active dataset '%s'" "${zone.active_ds}" zfs promote "${zone.active_ds}" fi done return 0 } # # Run system configuration inside a zone. # function reconfigure_zone { typeset sc_config=$1 vlog "$v_reconfig" vlog "$v_mounting" ZONE_IS_MOUNTED=1 zoneadm -z $ZONENAME mount -f || fatal "$e_badmount" if [[ -n $sc_config ]]; then sc_config_base=$(basename "$sc_config") # Remove in case $sc_config_base is a directory safe_dir "/system" safe_dir "/system/volatile" rm -rf "$ZONEPATH/lu/system/volatile/$sc_config_base" safe_copy_rec $sc_config \ "$ZONEPATH/lu/system/volatile/$sc_config_base" zlogin -S $ZONENAME "_UNCONFIG_ALT_ROOT=/a \ /usr/sbin/sysconfig configure -g system \ -c /system/volatile/$sc_config_base --destructive" \ /dev/null 2>&1 else zlogin -S $ZONENAME "_UNCONFIG_ALT_ROOT=/a \ /usr/sbin/sysconfig configure -g system --destructive" \ /dev/null 2>&1 fi if (( $? != 0 )); then error "$e_reconfig" failed=1 fi vlog "$v_unmount" zoneadm -z $ZONENAME unmount || fatal "$e_badunmount" ZONE_IS_MOUNTED=0 [[ -n $failed ]] && fatal "$e_exitfail" } function get_osnet_incorp { get_pkg_fmri consolidation/osnet/osnet-incorporation return $? } # # Handle pkg exit code. Exit 0 means Command succeeded, exit 4 means # No changes were made - nothing to do. Any other exit code is an error. # # Arguments: # # msg Error message passed as argument to error function # errfn Function to call if an error is detected. If not specified, # fail_fatal is used. # function pkg_err_check { typeset res=$? typeset msg=$1 typeset errfn=${2:-fail_fatal} (( $res != 0 && $res != 4 )) && "$errfn" "$msg" } # # Enable the services needed to perform packaging operations inside a zone. # function enable_zones_services { services_required="$1" /usr/sbin/svcadm enable -st svc:/application/pkg/system-repository if (( $? != 0 )) && [[ -n "$services_required" ]]; then error "$f_sysrepo_fail" return 1 fi /usr/sbin/svcadm enable -st svc:/application/pkg/zones-proxyd if (( $? != 0 )) && [[ -n "$services_required" ]]; then error "$f_zones_proxyd_fail" return 1 fi return 0 } # # tag_candidate_zbes ROOTdsn [be_array_name [curgz_assoc_array_name]] # # Tags each dataset that is a child of ROOTdsn with # $PROP_CANDIDATE=$CURRENT_GZBE. # # Arguments: # ROOTdsn The name of a dataset that contains zbes. # be_array_name If specified, this variable will contain an array # of candidate zbes on return. # curgz_assoc_array_name If specified and any zbes have $PROP_PARENT that # matches $CURRENT_GZBE, curgz_assoc_array_name will # contain that list. Otherwise, curgz_assoc_array_name # will be updated to reflect all of the zbes found. Note # that curgz_assoc_array_name is an associative (not # indexed) array with keys that match the zbe name. The # value assigned to each key is not significant. # # Returns 0 if there is at least one zbe found # Returns 1 if there are no zbes or there is a failure updating properties. # function tag_candidate_zbes { typeset ROOTdsn=$1 if [[ -n $2 ]]; then typeset -n bes=$2 fi typeset -a bes if [[ -n $3 ]]; then typeset -n curgzbes=$3 fi typeset -A curgzbes typeset dsn parent if [[ -z "$CURRENT_GZBE" ]]; then get_current_gzbe fi /usr/sbin/zfs list -H -o name -r -d 1 -t filesystem "$ROOTdsn" \ 2>/dev/null | while read dsn ; do [[ $dsn == "$ROOTdsn" ]] && continue a_push bes "$(basename "$dsn")" zfs set "$PROP_CANDIDATE=$CURRENT_GZBE" "$dsn" || return 1 # See if the zbe is already associated with the GZBE parent=$(zfs get "$PROP_PARENT" "$dsn") if [[ $parent == "$CURRENT_GZBE" ]]; then curgzbes[$(basename "$dsn")]=1 fi done if (( ${#bes[@]} == 0 )); then error "$e_no_active_be" return 1 fi # # If there were no zbes that already had a parent of $CURRENT_GZBE, # mark all of the found zbes as being allowed. # if (( ${#curgzbes[@]} == 0 )); then typeset be for be in "${bes[@]}"; do curgzbes[$be]=1 done fi return 0 } # # attach_image zone allow_update # # Arguments: # # zone A zone reference, initialized by init_zone. A BE # must be fully mounted at ${zone.root}. The BE may # contain a GZ or NGZ pkg(5) image. # allow_update One of "none", "min", or "all". # none: No updates to the zone image are allowed. # min: The mininal updates required to attach the # zone are allowed. This includes updating # the pkg(5) image format and satisfying # parent dependencies. Corresponds to the # default action of p2v (install <-a|-d>) # and the historical -u option to attach. # all: Like min, but updates all packages to the # latest version that are compatible with the # global zone image. # # Side effects on global variables: # # PKG If not set on entry, set to pkg # GZ_IMAGE If not set on entry, set to / # PKG_CACHEROOT If not set on entry and a pkg(5) cache exists at # /var/pkg/publisher, it is set to PKG_CACHEROOT and exported # to the environment. # OPT_V If set, pkg(1) commands will be verbose. # EXIT_CODE Not changed, but must be set. # # Returns: # If it returns, all went well - the zone is attached. Otherwise, it fails. # function attach_image { typeset -n zone=$1 typeset allow_update=$2 typeset variantname=variant.opensolaris.zone typeset -a variant typeset savestate typeset variant_changed=false typeset verbose PKG=${PKG:-pkg} GZ_IMAGE=${GZ_IMAGE:-/} [[ -n $OPT_V ]] && verbose=-v # Sanity check arguments (( $# == 2 )) || fail_internal "expected 2 args, got %d (%s)" $# "$*" [[ ! -d ${zone.root} ]] && fail_internal "zone root '%s' does not exist" "${zone.root}" [[ $allow_update != @(none|min|all) ]] && fail_internal "invalid value '%s' for allow_update" "$allow_update" # EXIT_CODE must be set for proper error handling [[ -z $EXIT_CODE ]] && fail_internal "EXIT_CODE is not set" # get the current architecture. typeset arch arch=$(uname -p) || fail "$f_uname_p" # 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" ]] && export PKG_CACHEROOT=/var/pkg/publisher 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. # if [[ $allow_update != none ]]; then log "$v_update_format" $PKG -R "${zone.root}" update-format || pkg_err_check "$e_update_format" fatal fi # Set the use-system-repo property. $PKG -R "${zone.root}" set-property use-system-repo true || fatal "\n$f_set_sysrepo_prop_fail" # # Update that catalogs once, subsequent packaging operations will use # --no-refresh to avoid unnecessary catalog checks and updates. If # image updates are not allowed, there's no need to refresh the # catalogs. # if [[ $allow_update != none ]]; then $PKG -R "${zone.root}" refresh --full || pkg_err_check "$e_update_format" fatal fi # prevent attach of a image with a different architecture typeset ngz_arch ngz_arch=$($PKG -R "${zone.root}" variant -H variant.arch) || (( $? == 0 )) || fatal "$f_variant_check" variant.arch ngz_arch=$(set -- $ngz_arch; echo $2) [[ "$ngz_arch" != "$arch" ]] && fatal "$f_image_arch_mismatch" "$arch" "$ngz_arch" # # Attach the zone to the global zone as a linked image. This # writes linked image metadata into the non-global zone which # will constrain subsequent packaging operations (but only after # the zone variant is set to nonglobal.) The attach operation is # performed on the global zone image (and the pkg command will # subsequently recurse into and modify the zone image). # typeset -a pkg_attach_args cmd set -A pkg_attach_args -- \ attach-linked --no-refresh --linked-md-only --allow-relink \ -f $verbose log "$m_image_link" "$GZ_IMAGE" set -A cmd $PKG -R "${GZ_IMAGE}" "${pkg_attach_args[@]}" \ -c "zone:${zone.name}" "${zone.root}" vlog "Running '%s'" "${cmd[*]}" "${cmd[@]}" || pkg_err_check "$f_update" fatal # get a list of fmris installed in the ngz image. typeset tmpfile1 tmpfile2 tmpfile1=$(mktemp -t zoneadm_image_attach.$$.XXXXXX) || \ fatal "$e_tmpfile" tmpfile2=$(mktemp -t zoneadm_image_attach.$$.XXXXXX) || \ fatal "$e_tmpfile" set -A cmd $PKG -R "${zone.root}" list --no-refresh -Hv vlog "Running '%s'" "${cmd[*]}" "${cmd[@]}" > $tmpfile2 || fatal "$f_list" "${zone.root}" nawk '{print $1}' $tmpfile2 > $tmpfile1 || fatal "$f_list" rm -f "$tmpfile2" # # Lookup the 'entire' incorporation in the global zone. We # check for this because if the user has removed it then we'll # want to remove it from the zone during attach. The reason is # that we're unlikely to be able to attach a highly constrained # zone (one that has entire installed) to a loosely constrained # global zone (one that doesn't have entire installed). # typeset gz_entire_pkgver=$(PKG_IMAGE=$GZ_IMAGE; get_entire_incorp) # # Lookup the 'osnet-incorporation' package in the global zone. # We need this to do a manual sync check (and possible manual # sync) of the zone. (and by manual we mean a sync that does # not utilize the pkg linked image framework.) # typeset gz_osnet_pkgver=$(PKG_IMAGE=$GZ_IMAGE; get_osnet_incorp) [[ -z "$gz_osnet_pkgver" ]] && fatal "$f_osnet" "$GZ_IMAGE" # Lookup for the 'entire' incorporation in the non-global zone. # Get the full package name and version. typeset ngz_entire_fmri=$(egrep '^pkg://[^]]*/entire@' $tmpfile1) typeset ngz_entire_pkgver=${ngz_entire_fmri#pkg://*/} # Lookup for the 'osnet-incorporation' package in the non-global zone. # Get the full package name and version. typeset ngz_osnet_fmri=$(egrep \ '^pkg://[^]]*/consolidation/osnet/osnet-incorporation@' $tmpfile1) typeset ngz_osnet_pkgver=${ngz_osnet_fmri#pkg://*/} [[ -z "$gz_osnet_pkgver" ]] && fatal "$f_osnet" "${zone.root}" # get a list of incorporation fmris installed in the ngz image. typeset ngz_incorporations=$( egrep '^pkg://.*/consolidation/.*/.*-incorporation@' "$tmpfile1" ) # we're done with the ngz list of packages rm -f "$tmpfile1" # check if the 'osnet-incorporation' package is in sync between # the gz and ngz. typeset manual_sync_required=1 [[ "$gz_osnet_pkgver" == "$ngz_osnet_pkgver" ]] && manual_sync_required=0 if [[ $manual_sync_required != 0 && $allow_update == none ]]; then fail_tryagain "\n$f_update_required" elif [[ $manual_sync_required != 0 ]]; then # # before we do a manual sync, we need to make sure that # system/volatile exists. old (s11 express / snv_151a) # images may not have this directory, which in turn will # cause rem_drv and update_drv to fail (since they want # to create lock files in that directory). # ZONEROOT=${zone.root} safe_opt_dir system ZONEROOT=${zone.root} safe_opt_dir system/volatile if [[ ! -d "${zone.root}/system/volatile" ]]; then mkdir -m 755 -p "${zone.root}/system/volatile" || \ fatal "$f_system_volatile" fi # prepare to try and manually sync the zone image typeset -a pkg_install_args set -A pkg_install_args -- \ install -I $verbose --no-refresh --accept # # if the gz doesn't have entire installed we need to # remove it from the ngz during out sync. # [[ -z "$gz_entire_pkgver" ]] && a_push pkg_install_args --reject pkg:///entire # # to manually sync the image we must relax all the image # install holds. we do this by specifying the names of # all the installed incorporations (excluding publisher # and version numbers). # for fmri in $ngz_incorporations; do # strip version and publisher from the fmri pub_pkg=${fmri%@*} pkg=${pub_pkg#pkg://*/} # # if entire is not installed then we're going to # try to sync the image by specifying a version # of the osnet-incorporation, but older versions # of the pkg client don't allow you to specify # overlapping pkg patterns on the cli, so don't # try to relax the osnet-incorporation if we're # going to explicitly sync it. # [[ -z "$gz_entire_pkgver" && $pkg == \ consolidation/osnet/osnet-incorporation ]] && continue # # due to bugs in s11 fcs, it's possible that we # may have the ldoms-incorporation installed on # non-sparc machines (or nvidia-incorporation # on non-i386 machines). if so we don't want to # specify these packages on the command line. # (since that would require them to be installed # and as part of this image update the solver # may want to remove them.) # [[ "$arch" != sparc && $pkg == \ consolidation/ldoms/ldoms-incorporation ]] && continue [[ "$arch" != i386 && $pkg == \ consolidation/nvidia/nvidia-incorporation ]] && continue # relax this incorporation a_push pkg_install_args pkg:///$pkg done if [[ -n "$gz_entire_pkgver" && -n "$ngz_entire_pkgver" ]]; then # entire is installed in the gz and ngz, so sync that. a_push pkg_install_args $gz_entire_pkgver else # we're in one of the following three cases: # 1) entire is installed in the gz but not in # the ngz. # 2) entire is not installed in the gz but is in # the ngz (it will be rejected from the ngz). # 3) entire is not installed in the gz or ngz # in all these cases we must sync osnet-incorporation a_push pkg_install_args $gz_osnet_pkgver fi # try to manually sync the image. set -A cmd $PKG -R "${zone.root}" "${pkg_install_args[@]}" if [[ "$allow_update" == all ]]; then log "$m_image_sync" elif [[ "$allow_update" == min ]]; then log "$m_image_sync1" else fail_internal "allow_update is '$allow_update'" fi vlog "Running '%s'" "${cmd[*]}" "${cmd[@]}" || pkg_err_check "$f_update" fatal fi # # If the image is a global zone and updates are allowed, do p2v. # set -A variant \ $(LC_ALL=C $PKG -R "${zone.root}" variant -H $variantname) (( $? == 0 )) || fatal "$f_variant_check" "$variantname" case ${variant[1]} in global) # We can't change the variant if updates aren't allowed. [[ $allow_update == @(min|all) ]] || fatal "$f_gz_image" log "$p2ving" typeset -a p2vopts set -A p2vopts $verbose_mode "${zone.name}" "${zone.path}" vlog "running: p2v ${p2vopts[@]}" /usr/lib/brand/solaris/p2v "${p2vopts[@]}" p2v_exit=$? if (( $p2v_exit != 0 )); then # Pass the p2v exit code up to zoneadm EXIT_CODE=$p2v_exit log "$p2v_fail" fatal "\n$install_fail" fi vlog "$p2v_done" ;; nonglobal) : ;; *) fail_internal "$variantname is '${variant[1]}'" ;; esac # Assemble the arguments to sync the zone image typeset -a pkg_sync_args typeset log_msg typeset fail_msg=$f_update case $allow_update in none) # # $f_update_required advises the use of -u or -U. It should # only be used with attach, as -u means something different # with install. install will only have "min" or "all" as # values for allow_update. # fail_msg=$f_update_required log_msg=$m_image_audit set -A pkg_sync_args -- sync-linked --no-pkg-updates ;; min) if [[ $manual_sync_required != 0 ]]; then log_msg=$m_image_sync2 else log_msg=$m_image_sync fi set -A pkg_sync_args -- sync-linked ;; all) log_msg=$m_image_update set -A pkg_sync_args -- update -f ;; *) fail_internal "Invalid allow_update value: $allow_update" esac a_push pkg_sync_args -I $verbose --no-refresh --accept [[ $allow_update != none && -z $gz_entire_pkgver ]] && a_push pkg_sync_args --reject pkg:///entire # Sync the zone image. log "$log_msg" set -A cmd $PKG -R "${zone.root}" "${pkg_sync_args[@]}" vlog "Running '%s'" "${cmd[*]}" "${cmd[@]}" || pkg_err_check "$fail_msg" fatal log "\n$m_sync_done" log "$m_complete" } # # has_bootable_bes zone # # Checks whether there are any zone (nested) BEs which have global zone's # currently active BE as their parent. # # Arguments: # zone zone structure initialized with init_zone # # Globals: # CURRENT_GZBE Current global zone boot environment. If not already set, # it will be set. # # Returns: # 0 if related ngz BEs found # 1 if no related ngz BE found # function has_bootable_bes { typeset -n zone=$1 typeset found=0 get_current_gzbe vlog "$m_active_gzbe_uuid" "$CURRENT_GZBE" zfs list -H -r -d 1 -t filesystem \ -o name,$PROP_PARENT "${zone.ROOT_ds}" 2>/dev/null | \ while IFS=$'\t' read name parentbe; do [[ $parentbe == "$CURRENT_GZBE" ]] || continue vlog "$m_ngz_be_found" "$name" found=1 done (( $found == 1 )) && return 0 return 1 } # # mark_orphan_zbes zone # # Scrolls through the list of ZBEs for the given 'zone' and checks if each # of them is orphan. If the ZBE is found orphan, set the value of property # com.oracle.libbe:orphaned to true. If not, clear the property. # # Arguments: # zone zone structure initialized with init_zone # function mark_orphan_zbes { typeset -n zone=$1 typeset name parentbe_uuid is_orphan mtpt # Scroll over the root dataset of the zone looking for orphan ZBE. /usr/sbin/zfs list -H -r -d 1 -o \ name,$PROP_PARENT,$PROP_ORPHANED,mountpoint "${zone.ROOT_ds}" | while IFS=$'\t' read name parentbe_uuid is_orphan mtpt; do if [[ $name == "${zone.ROOT_ds}" ]]; then # If root ds is orphaned, something is wrong. [[ $is_orphan == "true" ]] && \ fail_unavailable "$f_rootds_orphan" ${zone.ROOT_ds} # Skip ROOT ds otherwise. continue; fi if [[ $(is_zbe_orphan $parentbe_uuid) == "true" ]]; then # Set the orphaned property as true. /usr/sbin/zfs set "$PROP_ORPHANED=true" "$name" || \ exit $? else # Clear the orphaned property. /usr/sbin/zfs inherit $PROP_ORPHANED "$name" || exit $? fi done } # # destroy_orphan_zbes zone # # Deletes all datasets, including child datasets for all orphan ZBEs # for a zone on the current host. # # Arguments: # zone zone structure initialized with init_zone # # Returns: # 0 if no errors are reported in deleting # !0 if errors are reported in deleting # typeset -a belist function destroy_orphan_zbes { typeset -n zone=$1 typeset zbe ds_name parentbe orphan_prop mountpt is_orphan /usr/sbin/zfs list -H -r -d 1 -t filesystem \ -o name,$PROP_PARENT,$PROP_ORPHANED,mountpoint \ "${zone.ROOT_ds}" 2>/dev/null | while IFS=$'\t' read ds_name parentbe orphan_prop mountpt; do zbe=`basename $ds_name` # Skip the parent ROOT dataset. [[ $ds_name == "${zone.ROOT_ds}" ]] && continue # Check if ZBE is orphan. if [[ $orphan_prop != "true" ]]; then is_orphan=$(is_zbe_orphan $parentbe) else is_orphan="true" fi if [[ $is_orphan == "true" ]]; then # Add the orphan ZBE to belist to destroy. a_push belist "$zbe" fi done destroy_zone_datasets zone -b belist || fatal "$f_destroy_zbe" return 0 }