# # 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) 2010, Oracle and/or its affiliates. All rights reserved. # # # get script name (bname) # bname=`basename $0` # # common shell script functions # . /usr/lib/brand/shared/common.ksh # # error messages # m_usage=$(gettext "Usage: %s: [-hFn]") m_1_zfs_promote=$(gettext "promoting '%s'.") m_1_zfs_destroy=$(gettext "destroying '%s'.") m_2_zfs_rename=$(gettext "renaming '%s' to '%s'.") m_3_zfs_set=$(gettext "setting property %s='%s' for '%s'.") m_rm_r=$(gettext "recursively deleting '%s'.") m_rm=$(gettext "deleting '%s'.") w_no_ds=$(gettext "Warning: no zonepath dataset found.") f_usage_err=$(gettext "Error: invalid usage") f_abort=$(gettext "Error: internal error detected, aborting.") f_1_zfs_promote=$(gettext "Error: promoting ZFS dataset '%s'.") f_2_zfs_rename=$(gettext "Error: renaming ZFS dataset '%s' to '%s'.") f_3_zfs_set=$(gettext "Error: setting ZFS propery %s='%s' for '%s'.") f_1_zfs_destroy=$(gettext "Error: destroying ZFS dataset.") f_2_zfs_get=$(gettext "Error: reading ZFS dataset property '%s' from '%s'.") f_user_snap=$(gettext "Error: user snapshot(s) detected.") f_stray_snap=$(gettext "Error: uncloned snapshot(s) detected.") f_stray_clone=$(gettext "Error: cloned zone datasets found outsize of zone.") f_rm_snap=$(gettext "Error: please delete snapshot(s) and retry uninstall.") f_rm_clone=$(gettext "Error: please delete clone(s) and retry uninstall.") f_iu_clone=$(gettext "Error: cloned zone dataset(s) in use.") f_dis_clone=$(gettext "Error: please stop using clone(s) and retry uninstall.") # # functions # print_array() { typeset -n pa_array=$1 (( pa_i = 0 )) while (( $pa_i < ${#pa_array[@]} )); do printf "\t${pa_array[$pa_i]}\n" (( pa_i = $pa_i + 1 )) done } usage() { printf "$m_usage\n" "$bname" exit $ZONE_SUBPROC_USAGE } usage_err() { printf "$f_usage_err\n" >&2 usage >&2 } rm_zonepath() { # cleanup stuff we know about and leave any user data alone [[ -z "$opt_n" ]] && [[ -n "$opt_v" ]] && printf "$m_rm\n" "$zonepath/SUNWattached.xml" $nop /bin/rm -f "$zonepath/SUNWattached.xml" [[ -z "$opt_n" ]] && [[ -n "$opt_v" ]] && printf "$m_rm_r\n" "$zonepath/lu" $nop /bin/rm -rf "$zonepath/lu" [[ -z "$opt_n" ]] && [[ -n "$opt_v" ]] && printf "$m_rm_r\n" "$zonepath/dev" $nop /bin/rm -rf "$zonepath/dev" [[ -z "$opt_n" ]] && [[ -n "$opt_v" ]] && printf "$m_rm_r\n" "$zonepath/root" $nop /bin/rm -rf "$zonepath/root" [[ -z "$opt_n" ]] && [[ -n "$opt_v" ]] && printf "$m_rm\n" "$zonepath" $nop /bin/rmdir "$zonepath" 2>/dev/null } zfs_destroy() { zd_fs1="$1" # first figure out if the target fs has an origin snapshot zd_origin=`/sbin/zfs get -H -o value origin "$zd_fs1"` if [[ $? != 0 ]]; then printf "$f_2_zfs_get\n" origin "$zd_fs1" >&2 exit $ZONE_SUBPROC_FATAL fi [[ -z "$opt_n" ]] && [[ -n "$opt_v" ]] && printf "$m_1_zfs_destroy\n" "$zd_fs1" # # note that we specify the '-r' flag so that we destroy any # descendants (filesystems and snapshot) of the specified # filesystem. # $nop /sbin/zfs destroy -r "$zd_fs1" if [[ $? != 0 ]]; then printf "$f_1_zfs_destroy\n" "$zd_fs1" >&2 exit $ZONE_SUBPROC_FATAL fi [[ "$zd_origin" == "-" ]] && return [[ -z "$opt_n" ]] && [[ -n "$opt_v" ]] && printf "$m_1_zfs_destroy\n" "$zd_origin" $nop /sbin/zfs destroy "$zd_origin" 2>/dev/null # # we ignore errors while trying to destroy the origin since # the origin could have been used as the source for other # clones # } zfs_promote() { zp_fs1="$1" [[ -z "$opt_n" ]] && printf "$m_1_zfs_promote\n" "$zp_fs1" $nop /sbin/zfs promote "$zp_fs1" if [[ $? != 0 ]]; then printf "$f_1_zfs_promote\n" "$zp_fs1" >&2 exit $ZONE_SUBPROC_FATAL fi } zfs_rename() { zr_fs1="$1" zr_fs2="$2" [[ -z "$opt_n" ]] && printf "$m_2_zfs_rename\n" "$zr_fs1" "$zr_fs2" $nop /sbin/zfs rename "$zr_fs1" "$zr_fs2" if [[ $? != 0 ]]; then printf "$f_2_zfs_rename\n" "$zr_fs1" "$zr_fs2" >&2 return 1 fi return 0 } zfs_set() { zs_prop=$1 zs_value=$2 zs_fs1=$3 [[ -z "$opt_n" ]] && [[ -n "$opt_v" ]] && printf "$m_3_zfs_set\n" "$zs_prop" "$zs_value" "$zs_fs1" $nop /sbin/zfs set "$zs_prop"="$zs_value" "$zs_fs1" if [[ $? != 0 ]]; then printf "$f_3_zfs_set\n" "$zs_prop" "$zs_value" "$zs_fs1" return 1 fi return 0 } zfs_set_array() { zsa_prop=$1 zsa_value=$2 typeset -n zsa_array=$3 zsa_ignore_errors=$4 (( zsa_i = 0 )) while (( $zsa_i < ${#zsa_array[@]} )); do zfs_set "$zsa_prop" "$zsa_value" "${zsa_array[$zsa_i]}" [[ $? != 0 ]] && [[ -z "$zsa_ignore_errors" ]] && return 1 (( zsa_i = $zsa_i + 1 )) done return 0 } (( snap_rename_zbe_i = 1 )) (( snap_rename_snap_i = 1 )) snap_rename_init() { (( snap_rename_zbe_i = 1 )) (( snap_rename_snap_i = 1 )) } snap_rename() { eval sr_fs=\${$1} eval sr_snap=\${$2} if [[ "$sr_snap" == ~(Elr)(zbe-[0-9][0-9]*) ]]; then sr_snap="zbe-$snap_rename_zbe_i" (( snap_rename_zbe_i = $snap_rename_zbe_i + 1 )) elif [[ "$sr_snap" == ~(Er)(_snap[0-9]*) ]]; then sr_snap=${sr_snap##~(Er)([0-9]*)} sr_snap="${sr_snap}${snap_rename_snap_i}" (( snap_rename_snap_i = $snap_rename_snap_i + 1 )) else printf "$f_user_snap\n" >&2 printf "\t$sr_fs@$sr_snap\n" >&2 printf "$f_rm_snap\n" >&2 exit $ZONE_SUBPROC_FATAL fi eval $2="$sr_snap" } # find the dataset associated with $zonepath uninstall_get_zonepath_ds() { ZONEPATH_DS=`/sbin/zfs list -t filesystem -o name,mountpoint | \ /bin/nawk -v zonepath=$zonepath '{ if ($2 == zonepath) print $1 }'` if [ -z "$ZONEPATH_DS" ]; then # there is no $zonepath dataset rm_zonepath exit $ZONE_SUBPROC_OK fi } # find the dataset associated with $ZONEPATH_DS/ROOT uninstall_get_zonepath_root_ds() { ZONEPATH_RDS=`/sbin/zfs list -H -t filesystem -o name \ $ZONEPATH_DS/ROOT 2>/dev/null` if [ -z "$ZONEPATH_RDS" ]; then # there is no $ZONEPATH_DS/ROOT dataset c=`/sbin/zfs list -H -t filesystem -r $ZONEPATH_DS | wc -l` if [ $c = 1 ]; then # $zonepath dataset has no descendents zfs_destroy "$ZONEPATH_DS" fi rm_zonepath exit $ZONE_SUBPROC_OK fi } destroy_zone_dataset() { fs=$1 pool=${fs%%/*} # Fastpath. if there are no snapshots of $fs then just delete it. c=`/sbin/zfs list -H -t snapshot -o name -r $fs | grep "^$fs@" | LC_ALL=C LANG=C wc -l` if (( $c == 0 )) ; then zfs_destroy "$fs" return fi # # This zone BE has snapshots. This can happen if a zone has # multiple BEs (in which case we have snapshots named "zbe-XXX"), # if this zone has been used as the source for a clone of # another zone (in which case we have snapshots named # "XXX_snap"), or if an administrator has been doing manual # snapshotting. # # To be able to destroy this dataset (which we'll call the # origin) we need to get rid of all it's snapshots. The "easiest" # way to do this is to: # # - delete any uncloned origin snapshots # - find the oldest clone of the youngest origin snapshot (which # we'll call the oldest clone) # - check if there are any snapshots naming conflicts between # the origin and the oldest clone. # - if so, find any clones of those conflicting origin snapshots # - make sure that those clones are not zoned an in-use. # - if any of those clones are zoned, unzone them. # - rename origin snapshots to eliminate naming conflicts # - for any clones that we unzoned, rezone them. # - promote the oldest clone # - destroy the origin and all it's descendants # # # Get a list of all the cloned datasets within the zpool # containing the origin filesystem. Filter out any filesystems # that are descendants of origin because we are planning to # destroy them anyway. # unset clones clones_origin (( clones_c = 0 )) pool=${fs%%/*} LANG=C LC_ALL=C /sbin/zfs list -H -t filesystem -s creation \ -o name,origin -r "$pool" | while IFS=" " read name origin; do # skip non-clone filesystems [[ "$origin" == "-" ]] && continue # skip desendents of the origin we plan to destroy [[ "$name" == ~()(${fs}/*) ]] && continue # record this clone and it's origin clones[$clones_c]="$name" clones_origin[$clones_c]="$origin" (( clones_c = $clones_c + 1 )) done # # Now do a sanity check. Search for clones of a child datasets # of the dataset we want to destroy, that are not themselves # children of the dataset we're going to destroy). This should # really never happen unless the global zone admin has cloned a # snapshot of a zone filesystem to a location outside of that # zone. bad admin... # unset stray_clones (( stray_clones_c = 0 )) (( j = 0 )) while (( $j < $clones_c )); do # is the clone origin a descendant of $fs? if [[ "${clones_origin[$j]}" != ~()(${fs}/*) ]]; then # we don't care. (( j = $j + 1 )) continue fi stray_clones[$stray_clones_c]=${clones[$j]} (( stray_clones_c = $stray_clones_c + 1 )) (( j = $j + 1 )) done if (( stray_clones_c > 0 )); then # # sigh. the admin has done something strange. # tell them to clean it up and retry. # printf "$f_stray_clone\n" >&2 print_array stray_clones >&2 printf "$f_rm_clone\n" >&2 exit $ZONE_SUBPROC_FATAL fi # Find all the snapshots of the origin filesystem. unset s_origin (( s_origin_c = 0 )) /sbin/zfs list -H -t snapshot -s creation -o name -r $fs | grep "^$fs@" | while read name; do s_origin[$s_origin_c]=$name (( s_origin_c = $s_origin_c + 1 )) done # # Now go through the origin snapshots and find those which don't # have clones. We're going to explicity delete these snapshots # before we do the promotion. # unset s_delete (( s_delete_c = 0 )) (( j = 0 )) while (( $j < $s_origin_c )); do (( k = 0 )) while (( $k < $clones_c )); do # if we have a match then break out of this loop [[ "${s_origin[$j]}" == "${clones_origin[$k]}" ]] && break (( k = $k + 1 )) done if (( $k != $clones_c )); then # this snapshot has a clone, move on to the next one (( j = $j + 1 )) continue fi # snapshot has no clones so add it to our delete list s_delete[$s_delete_c]=${s_origin[$j]} (( s_delete_c = $s_delete_c + 1 )) # remove it from the origin snapshot list (( k = $j + 1 )) while (( $k < $s_origin_c )); do s_origin[(( $k - 1 ))]=${s_origin[$k]} (( k = $k + 1 )) done (( s_origin_c = $s_origin_c - 1 )) done # # Fastpath. If there are no remaining snapshots then just # delete the origin filesystem (and all it's descendents) and # move onto the next zone BE. # if (( $s_origin_c == 0 )); then zfs_destroy "$fs" return fi # find the youngest snapshot of $fs s_youngest=${s_origin[(( $s_origin_c - 1 ))]} # Find the oldest clone of the youngest snapshot of $fs unset s_clone (( j = $clones_c - 1 )) while (( $j >= 0 )); do if [[ "$s_youngest" == "${clones_origin[$j]}" ]]; then s_clone=${clones[$j]} break fi (( j = $j - 1 )) done if [[ -z "$s_clone" ]]; then # uh oh. something has gone wrong. bail. printf "$f_stray_snap\n" >&2 printf "\t$s_youngest\n" >&2 printf "$f_rm_snap\n" >&2 exit $ZONE_SUBPROC_FATAL fi # create an array of clone snapshot names unset s_clone_s (( s_clone_s_c = 0 )) /sbin/zfs list -H -t snapshot -s creation -o name -r $s_clone | grep "^$s_clone@" | while read name; do s_clone_s[$s_clone_s_c]=${name##*@} (( s_clone_s_c = $s_clone_s_c + 1 )) done # create an arrays of possible origin snapshot renames unset s_origin_snap unset s_rename (( j = 0 )) while (( $j < $s_origin_c )); do s_origin_snap[$j]=${s_origin[$j]##*@} s_rename[$j]=${s_origin[$j]##*@} (( j = $j + 1 )) done # # Search for snapshot name collisions between the origin and # oldest clone. If we find one, generate a new name for the # origin snapshot and re-do the collision check. # snap_rename_init (( j = 0 )) while (( $j < $s_origin_c )); do (( k = 0 )) while (( $k < $s_clone_s_c )); do # if there's no naming conflict continue if [[ "${s_rename[$j]}" != "${s_clone_s[$k]}" ]]; then (( k = $k + 1 )) continue fi # # The origin snapshot conflicts with a clone # snapshot. Choose a new name and then restart # then check that against clone snapshot names. # snap_rename fs "s_rename[$j]" (( k = 0 )) continue; done # if we didn't rename this snapshot then continue if [[ "${s_rename[$j]}" == "${s_origin_snap[$j]}" ]]; then (( j = $j + 1 )) continue fi # # We need to rename this origin snapshot because it # conflicts with a clone snapshot name. So above we # chose a name that didn't conflict with any other clone # snapshot names. But we also have to avoid naming # conflicts with any other origin snapshot names. So # check for that now. # (( k = 0 )) while (( $k < $s_origin_c )); do # don't compare against ourself if (( $j == $k )); then (( k = $k + 1 )) continue fi # if there's no naming conflict continue if [[ "${s_rename[$j]}" != "${s_rename[$k]}" ]]; then (( k = $k + 1 )) continue fi # # The new origin snapshot name conflicts with # another origin snapshot name. Choose a new # name and then go back to check the new name # for uniqueness against all the clone snapshot # names. # snap_rename fs "s_rename[$j]" continue 2; done # # A new unique name has been chosen. Move on to the # next origin snapshot. # (( j = $j + 1 )) snap_rename_init done # # So now we know what snapshots need to be renamed before the # promotion. But there's an additional problem. If any of the # filesystems cloned from these snapshots have the "zoned" # attribute set (which is highly likely) or if they are in use # (and can't be unmounted and re-mounted) then the snapshot # rename will fail. So now we'll search for all the clones of # snapshots we plan to rename and look for ones that are zoned. # # We'll ignore any snapshot clones that may be in use but are # not zoned. If these clones are in-use, the rename will fail # and we'll abort, there's not much else we can do about it. # But if they are not in use the snapshot rename will unmount # and remount the clone. This is ok because when the zoned # attribute is off, we know that the clone was originally # mounted from the global zone. (So unmounting and remounting # it from the global zone is ok.) # # But we'll abort this whole operation if we find any clones # that that are zoned and in use. (This can happen if another # zone has been cloned from this one and is now booted.) The # reason we do this is because those zoned filesystems could # have originally mounted from within the zone. So if we # cleared the zone attribute and did the rename, we'd be # remounting the filesystem from the global zone. This would # result in the zone losing the ability to unmount the # filesystem, which would be bad. # unset zoned_clones zoned_iu_clones (( zoned_clones_c = 0 )) (( zoned_iu_clones_c = 0 )) (( j = 0 )) # walk through all the clones while (( $j < $clones_c )); do # walk through all the origin snapshots (( k = 0 )) while (( $k < $s_origin_c )); do # # check if this clone originated from a snapshot that # we need to rename. # [[ "${clones_origin[$j]}" == "${s_origin[$k]}" ]] && [[ "${s_origin_snap[$k]}" != "${s_rename[$k]}" ]] && break (( k = $k + 1 )) continue done if (( $k == $s_origin_c )); then # This isn't a clone of a snapshot we want to rename. (( j = $j + 1 )) continue; fi # get the zoned attr for this clone. zoned=`LC_ALL=C LANG=C \ /sbin/zfs get -H -o value zoned ${clones[$j]}` if [[ "$zoned" != on ]]; then # This clone isn't zoned so ignore it. (( j = $j + 1 )) continue fi # remember this clone so we can muck with it's zoned attr. zoned_clones[$zoned_clones_c]=${clones[$j]} (( zoned_clones_c = $zoned_clones_c + 1 )) # check if it's in use mounted=`LC_ALL=C LANG=C \ /sbin/zfs get -H -o value mounted ${clones[$j]}` if [[ "$mounted" != yes ]]; then # Good news. This clone isn't in use. (( j = $j + 1 )) continue fi # Sigh. This clone is in use so we're destined to fail. zoned_iu_clones[$zoned_iu_clones_c]=${clones[$j]} (( zoned_iu_clones_c = $zoned_iu_clones_c + 1 )) # keep looking for errors so we can report them all at once. (( j = $j + 1 )) done if (( zoned_iu_clones_c > 0 )); then # # Tell the admin # printf "$f_iu_clone\n" >&2 print_array zoned_iu_clones >&2 printf "$f_dis_clone\n" >&2 exit $ZONE_SUBPROC_FATAL fi # # Ok. So we're finally done with planning and we can do some # damage. We're going to: # - destroy unused snapshots # - unzone clones which originate from snapshots we need to rename # - rename conflicting snapshots # - rezone any clones which we unzoned # - promote the oldest clone of the youngest snapshot # - finally destroy the origin filesystem. # # delete any unsed snapshot (( j = 0 )) while (( $j < $s_delete_c )); do zfs_destroy "${s_delete[$j]}" (( j = $j + 1 )) done # unzone clones zfs_set_array zoned off zoned_clones || zfs_set_array zoned on zoned_clones 1 # rename conflicting snapshots (( j = 0 )) while (( $j < $s_origin_c )); do if [[ "${s_origin_snap[$j]}" != "${s_rename[$j]}" ]]; then zfs_rename "${s_origin[$j]}" "$fs@${s_rename[$j]}" if [[ $? != 0 ]]; then # re-zone the clones before aborting zfs_set_array zoned on zoned_clones 1 exit $ZONE_SUBPROC_FATAL fi fi (( j = $j + 1 )) done # re-zone the clones zfs_set_array zoned on zoned_clones 1 # promote the youngest clone of the oldest snapshot zfs_promote "$s_clone" # destroy the origin filesystem and it's descendants zfs_destroy "$fs" } # # This function expects an array named fs_all to exist which is initialized # with the zone's ZFS datasets that should be destroyed. fs_all_c is the # count of the number of elements in the array. ZONEPATH_RDS is the # zonepath/root dataset and ZONEPATH_DS is the zonepath dataset. # destroy_zone_datasets() { # Destroy the zone BEs datasets one by one. (( i = 0 )) while (( $i < $fs_all_c )); do fs=${fs_all[$i]} destroy_zone_dataset "$fs" (( i = $i + 1 )) done # # Check if there are any other datasets left. There may be datasets # associated with other GZ BEs, so we need to leave things alone in # that case. # c=`/sbin/zfs list -H -t filesystem -r $ZONEPATH_RDS | wc -l` if [ $c = 1 ]; then zfs_destroy "$ZONEPATH_RDS" fi c=`/sbin/zfs list -H -t filesystem -r $ZONEPATH_DS | wc -l` if [ $c = 1 ]; then zfs_destroy "$ZONEPATH_DS" fi rm_zonepath }