# # Copyright (c) 2009, 2013, 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 # Values for service tags. STCLIENT=/usr/bin/stclient ST_PRODUCT_NAME="Solaris 10 Containers" ST_PRODUCT_REV="1.0" ST_PRODUCT_UUID="urn:uuid:2f459121-dec7-11de-9af7-080020a9ed93" w_dhcp_warning=$(gettext "WARNING: Unable to create \"%s\" directory\n") w_sanity_detail=$(gettext " WARNING: Skipping image sanity checks.") f_sanity_sparse=$(gettext "Is this a sparse zone image? The image must be whole-root.") f_sanity_vers=$(gettext "The image release version must be 10 (got %s), the zone is not usable on this system.") f_sanity_nopatch=$(gettext "Unable to determine the image's patch level.") f_sanity_downrev=$(gettext "The image patch level is downrev for running in a solaris10 branded zone.\n(patchlist %s)") f_need_newer_emul=$(gettext "The image requires a newer version of the solaris10 brand emulation.") f_no_active_ds=$(gettext "No active dataset; the zone's ZFS root dataset must be configured as\n\ta zone boot environment.") incompat_options=$(gettext "mutually exclusive options.\n%s") sanity_ok=$(gettext " Sanity Check: Passed. Looks like a Solaris 10 image.") e_s10_be_in_use=$(gettext "Boot environment %s already in use.") e_badboot=$(gettext "Zone boot failed") e_unconfig=$(gettext "sys-unconfig failed") v_unconfig=$(gettext "Performing zone sys-unconfig") f_sysunconfig=$(gettext "Error: sys-unconfig failed.") v_no_tags=$(gettext "Service tags facility not present.") e_bad_uuid=$(gettext "Failed to get zone UUID") v_addtag=$(gettext "Adding service tag: %s") v_deltag=$(gettext "Removing service tag: %s") e_addtag_fail=$(gettext "Adding service tag failed (error: %s)") f_cd=$(gettext "Unable to change working directory to %s"); e_not_file=$(gettext "%s is not a regular file") invalid_dataset=$(gettext "\nThe com.oracle.zones.solaris10:activebe property is set to %s, which is invalid. Please set a valid boot environment value.\n") op_abort_msg=$(gettext "\nZone rpool data set %s already exists. Aborting the operation\n") be_set_failed=$(gettext "\nUnable to set %s as active boot environment\n") m_zbe_discover_failed=$(gettext "\nUnable to determine which boot environment to activate. Candidates are:\n") not_flar=$(gettext "Input is not a flash archive") unknown_archiver=$(gettext "Archiver %s is not supported") mount_failed=$(gettext "ERROR: zonecfg(1M) 'fs' mount failed") function sanity_check { typeset dir="$1" res=0 # # Check for some required directories and make sure this isn't a # sparse zone image. # 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 # Files from SUNWcsr and SUNWcsu that are in sparse inherit-pkg-dirs. checks="lib/svc sbin/zonename usr/bin/chmod" for x in $checks; do if [[ ! -e $dir/$x ]]; then log "$f_sanity_detail" "$x" "$dir" log "$f_sanity_sparse" res=1 fi done if (( $res != 0 )); then fatal "$sanity_fail" fi if [[ "$SANITY_SKIP" == 1 ]]; then log "$w_sanity_detail" return fi # # Check image release to be sure its S10. # image_vers="unknown" 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 [[ "$image_vers" != "10" ]]; then log "$f_sanity_vers" "$image_vers" res=1 fi # # Make sure we have the minimal KU patch we support. These are the # KUs for S10u8. # if [[ $(uname -p) == "i386" ]]; then req_patch="141445-09" else req_patch="141444-09" fi for i in $dir/var/sadm/pkg/SUNWcakr* do if [[ ! -d $i || ! -f $i/pkginfo ]]; then log "$f_sanity_nopatch" res=1 fi done # # Check the core kernel pkg for the required KU patch. # found=0 for i in $dir/var/sadm/pkg/SUNWcakr*/pkginfo do patches=$(nawk -F= '{if ($1 == "PATCHLIST") print $2}' $i) for patch in $patches do if [[ $patch == $req_patch ]]; then found=1 break fi done if (( $found == 1 )); then break fi done if (( $found != 1 )); then log "$f_sanity_downrev" "$patches" res=1 fi # # Check the S10 image for a required version of the emulation. # VERS_FILE=/usr/lib/brand/solaris10/version s10vers_needs=0 if [[ -f $dir/$VERS_FILE ]]; then s10vers_needs=$(/usr/bin/egrep -v "^#" $dir/$VERS_FILE) fi # Now get the current emulation version. emul_vers=$(/usr/bin/egrep -v "^#" $VERS_FILE) # Verify that the emulation can run this version of S10. if (( $s10vers_needs > $emul_vers )); then log "$f_need_newer_emul" res=1 fi if (( $res != 0 )); then fatal "$sanity_fail" fi vlog "$sanity_ok" } # # get_active_be zone # # Gets the currently active BE, storing the name of the active BE's root # dataset in zone.active_ds. # # Arguments: # # zone zone structure initilialized with init_zone # # Returns: # # 0 on success, else 1. # function get_active_be { typeset -n zone="$1" typeset be=$(/usr/sbin/zfs get -H -o \ value com.oracle.zones.solaris10:activebe "${zone.ROOT_ds}") # Handle the default be case if [[ "$be" == "-" ]]; then be=zbe-0 else # Handle the case where no active be exists if [[ -z "$be" ]]; then error "Could not find any active boot environment" EXIT_CODE=$ZONE_SUBPROC_UNAVAILABLE return 1 else # Check for invalid dataset name if [[ "$be" == */* ]]; then error "$invalid_dataset" "$be" EXIT_CODE=$ZONE_SUBPROC_UNAVAILABLE return 1 fi /usr/sbin/zfs list -H "${zone.ROOT_ds}/$be" > \ /dev/null 2>&1 if (( $? != 0 )); then error "$invalid_dataset" "$be" EXIT_CODE=$ZONE_SUBPROC_UNAVAILABLE return 1 fi fi fi zone.active_ds="${zone.ROOT_ds}/$be" return 0 } # # set_active_be zone bename # # Sets the specified BE as the active boot environment. # # Arguments: # # zone A zone structure initialized with init_zone # bename The name of the boot environment. Should not contain "/". # function set_active_be { typeset -n zone="$1" typeset be="$2" if [[ "$be" == */* ]]; then fail_tryagain "$invalid_dataset" "$be" fi if [[ -z "$be" ]]; then fail_tryagain "Zone boot environment not specified" fi # Set activebe property on root dataset zfs set "com.oracle.zones.solaris10:activebe=$be" "${zone.ROOT_ds}" if (( $? != 0 )); then fail_tryagain "$be_set_failed" "$be" fi zone.active_ds="${zone.ROOT_ds}/$be" return 0 } # # 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. # function mk_zone_dirs { 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 [[ ! -f $ZONEROOT/var/tmp && ! -d $ZONEROOT/var/tmp ]]; then mkdir -m 1777 -p $ZONEROOT/var/tmp || 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 } # # We're sys-unconfig-ing the zone. This will normally halt the zone, however # there are problems with sys-unconfig and it can hang when the zone is booted # to milestone=none. Sys-unconfig also sometimes hangs halting the zone. # Thus, we take some care to workaround these sys-unconfig limitations. # # On entry we expect the zone to be booted. We use sys-unconfig -R to make it # think its working on an alternate root and let the caller halt the zone. # function sysunconfig_zone { /usr/sbin/zlogin -S $ZONENAME /usr/sbin/sys-unconfig -R /./ \ >/dev/null 2>&1 if (( $? != 0 )); then error "$e_unconfig" return 1 fi return 0 } # # Get zone's uuid for service tag. # function get_inst_uuid { typeset ZONENAME="$1" ZONEUUID=`zoneadm -z $ZONENAME list -p | nawk -F: '{print $5}'` [[ $? -ne 0 || -z $ZONEUUID ]] && return 1 INSTANCE_UUID="urn:st:${ZONEUUID}" return 0 } # # Add a service tag for a given zone. We use two UUIDs-- the first, # the Product UUID, comes from the Sun swoRDFish ontology. The second # is the UUID of the zone itself, which forms the instance UUID. # function add_svc_tag { typeset ZONENAME="$1" typeset SOURCE="$2" if [ ! -x $STCLIENT ]; then vlog "$v_no_tags" return 0 fi get_inst_uuid "$ZONENAME" || (error "$e_bad_uuid"; return 1) vlog "$v_addtag" "$INSTANCE_UUID" $STCLIENT -a \ -p "$ST_PRODUCT_NAME" \ -e "$ST_PRODUCT_REV" \ -t "$ST_PRODUCT_UUID" \ -i "$INSTANCE_UUID" \ -P "none" \ -m "Sun" \ -A `uname -p` \ -z "$ZONENAME" \ -S "$SOURCE" >/dev/null 2>&1 err=$? # 226 means "duplicate record," which we can ignore. if [[ $err -ne 0 && $err -ne 226 ]]; then error "$e_addtag_fail" "$err" return 1 fi return 0 } # # Remove a service tag for a given zone. # function del_svc_tag { typeset ZONENAME="$1" if [ ! -x $STCLIENT ]; then vlog "$v_no_tags" return 0 fi get_inst_uuid "$ZONENAME" || (error "$e_bad_uuid"; return 1) vlog "$v_deltag" "$INSTANCE_UUID" $STCLIENT -d -i "$INSTANCE_UUID" >/dev/null 2>&1 return 0 } # # convert_s10_zonepath_to_be # # In Solaris 10, if a zone is on a ZFS dataset, the zone root is contained # within the root subdirectory of the zonepath dataset. In contrast, on # Solaris 11, the root subdirectory is a mountpoint for a boot environment # dataset. This function uses rm and mv to transform a Solaris 10 style # zonepath dataset into a Solaris 11 style boot environment dataset. This # approach is used instead of copying to a new dataset to minimize I/O. # # This function assumes that a snapshot of the dataset exists and can be used # for restoring files that really shouldn't have been deleted. # function convert_s10_zonepath_to_be { typeset dir="$1" typeset newroot file typeset -i removed=0 vlog "Converting %s from a Solaris 10 zonepath to a boot environment" \ "$dir" [[ -z "$dir" ]] && fail_internal "$f_int_missing_arg" "dir" [[ -d "$dir" ]] || fail_internal "'%s' is not a directory" "$dir" # # A temporary directory is created to avoid conflicts of # "mv $dir/root/* $dir/root" when $dir/root/root exists. Really, # mktemp is just used to create a directory name that is extremely # unlikely to collide with anything else. In the extremely unlikely # event of a collision between $newroot and a file in $dir/root, a # "mv" will fail in an unexploitable way and an error will be # returned. # newroot=$(mktemp -d "$dir/attach.XXXXXX") || { error "$e_tmpdir" "$dir/attach.XXXXXX" return 1 } rmdir "$newroot" || { error "$e_rmdir" "$newroot" return 1 } mv "$dir/root" "$newroot" || { error "$e_mv" "$dir/root" "$newroot" return 1 } for file in "$dir"/* "$dir"/.* ; do [[ $(basename "$file") == "." ]] && continue [[ $(basename "$file") == ".." ]] && continue [[ $file == "$newroot" ]] && continue (( removed == 0 )) && \ vlog "Removed files preserved in boot env snapshot." (( removed=1 )) vlog "Removing '%s' from zonepath" "${file#$dir/}" rm -rf "$file" if [[ -e "$file" || -h "$file" ]]; then error "$e_rm" "$file" return 1 fi done for file in "$newroot"/* "$newroot"/.* ; do [[ $(basename "$file") == "." ]] && continue [[ $(basename "$file") == ".." ]] && continue mv "$file" "$dir" || { error "$e_mv" "$file" "$dir" return 1 } done # $dir was previously a zonepath with permissions of 700. chown root:sys "$dir" || { error "$f_chown" "$dir" return 1 } chmod 755 "$dir" || { error "$f_chmod" "$dir" return 1 } # This temporary directory should be empty - get rid of it. rmdir "$newroot" || { error "$e_rmdir" "$newroot" return 1 } return 0 } # # discover_active_be zone # # function discover_active_be { typeset -n zone=$1 typeset be if [[ ${#zone.allowed_bes[@]} == 0 ]]; then tag_candidate_zbes "${zone.ROOT_ds}" "" zone.allowed_bes || return 1 fi if [[ ${#zone.allowed_bes[@]} == 1 ]]; then vlog "Activating only allowed boot environment %s" \ "${!zone.allowed_bes[@]}" be="${!zone.allowed_bes[@]}" else vlog "$f_multiple_ds" vlog "$m_zbe_discover_failed" for be in "${!zone.allowed_bes[@]}"; do vlog "\n $be " done return 1 fi mount_active_be -b "$be" zone return $? } # # Unpack flar into current directory (which should be zoneroot). The flash # archive is standard input. See flash_archive(4) man page. # # We can't use "flar split" since it will only unpack into a directory called # "archive". We need to unpack in place in order to properly handle nested # fs mounts within the zone root. This function does the unpacking into the # current directory. # # This code is derived from the gen_split() function in /usr/sbin/flar so # we keep the same style as the original. # # Globals: # # EXIT_CODE Set to ZONE_SUBPROC_FATAL if datasets are created but not # populated with archive contents. Set to # ZONE_SUBPROC_UNAVAILABLE if archive extraction is complete. # function install_flar { typeset -n zone=$1 typeset result typeset archiver_command typeset archiver_arguments typeset identification=$(mktemp /tmp/identification.XXXXXX) typeset compression typeset decompress # Read cookie read -r input_line if (( $? != 0 )); then log "$not_readable" "$install_media" return 1 fi # The cookie has format FlAsH-aRcHiVe-m.n where m and n are integers. if [[ ${input_line%%-[0-9]*.[0-9]*} != "FlAsH-aRcHiVe" ]]; then log "$not_flar" return 1 fi while [[ true ]] do # We should always be at the start of a section here read -r input_line if [[ ${input_line%%=*} != "section_begin" ]]; then log "$bad_flar" return 1 fi section_name=${input_line##*=} # If we're at the archive, we're done skipping sections. if [[ "$section_name" == "archive" ]]; then break fi # # Save identification section to a file so we can determine # how to unpack the archive. # if [[ "$section_name" == "identification" ]]; then /usr/bin/rm -f $identification while read -r input_line do if [[ ${input_line%%=*} == \ "section_begin" ]]; then /usr/bin/rm -f $identification log "$bad_flar" return 1 fi if [[ $input_line == \ "section_end=$section_name" ]]; then break; fi echo $input_line >> $identification done continue fi # # Otherwise skip past this section; read lines until detecting # section_end. According to flash_archive(4) we can have # an arbitrary number of sections but the archive section # must be last. # success=0 while read -r input_line do if [[ $input_line == "section_end=$section_name" ]]; then success=1 break fi # Fail if we miss the end of the section if [[ ${input_line%%=*} == "section_begin" ]]; then /usr/bin/rm -f $identification log "$bad_flar" return 1 fi done if (( success == 0 )); then # # If we get here we read to the end of the file before # seeing the end of the section we were reading. # /usr/bin/rm -f $identification log "$bad_flar" return 1 fi done typeset origdir=$(pwd) compression=$(get_compression $identification) # Check for an archive made from a ZFS root pool. if egrep -s "^rootpool=" $identification ; then rm -f $identification typeset input= case "$compression" in compress) decompress=zcat input=- ;; none|"") decompress=cat input=/dev/stdin ;; *) fail_internal \ "Unexpected files_compressed_method '%s'" \ "$compression" ;; esac # Sets EXIT_CODE. extract_zfs zone "$decompress" "$input" result=$? # # If this fails, we will have already gotten more useful # errors out of extract_zfs. # cd "${zone.root}" >/dev/null 2>&1 post_unpack cd "$origdir" return $result fi vlog "%s" "$stage1 $insrc | install_flar" # Sets EXIT_CODE create_active_ds zone || fatal "$f_no_active_ds" mount_active_be -c zone || fatal "$f_mount_active_be" get_fs_info mnt_fs if (( $? != 0 )); then umnt_fs >/dev/null 2>&1 rm -f $fstmpfile $fscpiofile $fspaxfile fatal "$mount_failed" fi cd ${zone.root} || fatal "$f_cd" "${zone.root}" # Get the information needed to unpack the archive. archiver=$(get_archiver $identification) if [[ $archiver == "pax" ]]; then # pax archiver specified archiver_command="pax_wrap" if [[ -s $fspaxfile ]]; then archiver_arguments="-r@/ -p e -c \ $(/usr/bin/cat $fspaxfile)" else archiver_arguments="-r@/ -p e" fi elif [[ $archiver == "cpio" || -z $archiver ]]; then # cpio archived specified OR no archiver specified - use default archiver_command="/usr/bin/cpio" archiver_arguments="-icdP@/umfE $fscpiofile" else # unknown archiver specified log "$unknown_archiver" $archiver cd "$origdir" return 1 fi if [[ ! -x $archiver_command && $archiver_command != "pax_wrap" ]]; then /usr/bin/rm -f $identification log "$cmd_not_exec" $archiver_command cd "$origdir" return 1 fi # We're done with the identification file /usr/bin/rm -f $identification # Extract archive if [[ $compression == "compress" ]]; then vlog "Extracting with command: '%s'" \ "/usr/bin/zcat | $archiver_command $archiver_arguments" /usr/bin/zcat | $archiver_command $archiver_arguments else vlog "Extracting with command: '%s'" \ "$archiver_command $archiver_arguments" $archiver_command $archiver_arguments fi result=$? vlog "Extraction complete with exit status %d" $result post_unpack cd "$origdir" (( result != 0 )) && return 1 EXIT_CODE=$ZONE_SUBPROC_UNAVAILABLE return 0 } # # tag_candidate_zbes ROOTdsn [be_array_name [assoc_array_name]] # # This function returns the list containing the active zbes. # # 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. # assoc_array_name If specified, this variable will contain an array # indexed with allowed zbes on return. # # Returns: # 0 Success. Candidate zbes listed. # 1 Failure. # 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 aldbes=$3 fi typeset -A aldbes typeset dsn # Get the active BE for the brand's root dataset typeset be=$(/usr/sbin/zfs get -H -o \ value com.oracle.zones.solaris10:activebe "${zone.ROOT_ds}") # If BE is not null or default, check if dataset path exists # If all is well, push the sole BE to the bes array! if [[ -n "$be" && "$be" != "-" ]]; then # Check for invalid dataset name if [[ "$be" == */* ]]; then error "$invalid_dataset" "$be" return 1 fi /usr/sbin/zfs list -H -o name -r -d 1 -t filesystem \ "$ROOTdsn/$be" 2>/dev/null if (( $? != 0 )); then error "$invalid_dataset" "$be" return 1 fi a_push bes "$be" aldbes[$be]=1 else # Let's go the mundane way to figure out BEs from the ROOT # dataset name /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")" aldbes[$(basename "$dsn")]=1 done fi # If bes array is empty then we don't have any be # under zone's ROOT data set. if (( ${#bes[@]} == 0 )); then error "$e_no_active_be" return 1 fi return 0 }