uninstall.ksh revision 80e2ca8596e3435bc3b76f3c597833ea0a87f85e
#
# 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
}