2012-10-12 16:37:57 +04:00
#!/bin/bash
#
2015-04-14 14:35:11 +03:00
# Copyright (C) 2012-2015 Red Hat, Inc. All rights reserved.
2012-10-12 16:37:57 +04:00
#
# This file is part of LVM2.
#
# This copyrighted material is made available to anyone wishing to use,
# modify, copy, or redistribute it subject to the terms and conditions
# of the GNU General Public License v.2.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
2016-01-21 13:49:46 +03:00
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
2012-10-12 16:37:57 +04:00
#
# Author: Peter Rajnoha <prajnoha at redhat.com>
#
# Script for deactivating block devices
#
# Requires:
# bash >= 4.0 (associative array support)
2015-04-14 14:35:11 +03:00
# util-linux {
# lsblk >= 2.22 (lsblk -s support)
# umount
# }
2012-10-12 16:37:57 +04:00
# dmsetup >= 1.02.68 (--retry option support)
# lvm >= 2.2.89 (activation/retry_deactivation config support)
#
#set -x
shopt -s dotglob nullglob
TOOL = blkdeactivate
DEV_DIR = '/dev'
SYS_BLK_DIR = '/sys/block'
UMOUNT = "/bin/umount"
DMSETUP = "@sbindir@/dmsetup"
LVM = "@sbindir@/lvm"
2015-10-06 14:25:37 +03:00
MDADM = "@sbindir@/mdadm"
2012-10-12 16:37:57 +04:00
2013-08-13 19:26:36 +04:00
if $UMOUNT --help | grep -- "--all-targets" >$DEV_DIR /null; then
UMOUNT_OPTS = "--all-targets "
else
UMOUNT_OPTS = ""
FINDMNT = "/bin/findmnt -r --noheadings -u -o TARGET"
FINDMNT_READ = "read -r mnt"
fi
2013-08-13 19:17:25 +04:00
DMSETUP_OPTS = ""
LVM_OPTS = ""
2015-10-06 14:25:37 +03:00
MDADM_OPTS = ""
2013-08-13 19:17:25 +04:00
2012-10-12 16:37:57 +04:00
LSBLK = "/bin/lsblk -r --noheadings -o TYPE,KNAME,NAME,MOUNTPOINT"
LSBLK_VARS = "local devtype local kname local name local mnt"
LSBLK_READ = "read -r devtype kname name mnt"
2013-01-23 19:57:44 +04:00
SORT_MNT = "/bin/sort -r -u -k 4"
2012-10-12 16:37:57 +04:00
2013-08-13 19:17:25 +04:00
# Do not show tool errors by default (only done/skipping summary
# message provided by this script) and no verbose mode by default.
ERRORS = 0
VERBOSE = 0
2012-10-12 16:37:57 +04:00
# Do not unmount mounted devices by default.
DO_UMOUNT = 0
# Deactivate each LV separately by default (not the whole VG).
LVM_DO_WHOLE_VG = 0
# Do not retry LV deactivation by default.
LVM_CONFIG = "activation{retry_deactivation=0}"
#
# List of device names and/or VGs to be skipped.
# Device name is the KNAME from lsblk output.
#
# If deactivation of any device fails, it's automatically
# added to the SKIP_DEVICE_LIST (also a particular VG
# added to the SKIP_VG_LIST for a device that is an LV).
#
# These lists provide device tree pruning to skip
# particular device/VG deactivation that failed already.
# (lists are associative arrays!)
#
declare -A SKIP_DEVICE_LIST = ( )
declare -A SKIP_VG_LIST = ( )
#
# List of mountpoints to be skipped. Any device that is mounted on the mountpoint
# listed here will be added to SKIP_DEVICE_LIST (and SKIP_VG_LIST) automatically.
# (list is an associative array!)
#
declare -A SKIP_UMOUNT_LIST = ( [ "/" ] = 1 [ "/boot" ] = 1 \
[ "/lib" ] = 1 [ "/lib64" ] = 1 \
[ "/bin" ] = 1 [ "/sbin" ] = 1 \
2013-08-13 19:17:25 +04:00
[ "/var" ] = 1 [ "/var/log" ] = 1 \
2012-10-12 16:37:57 +04:00
[ "/usr" ] = 1 \
[ "/usr/lib" ] = 1 [ "/usr/lib64" ] = 1 \
[ "/usr/sbin" ] = 1 [ "/usr/bin" ] = 1)
# Bash can't properly handle '[' and ']' used as a subscript
# within the '()'initialization - it needs to be done separately!
SKIP_UMOUNT_LIST[ "[SWAP]" ] = 1
usage( ) {
echo " ${ TOOL } : Utility to deactivate block devices "
echo
echo " ${ TOOL } [options] [device...] "
echo " - Deactivate block device tree."
echo " If devices are specified, deactivate only supplied devices and their holders."
echo
echo " Options:"
2013-08-13 19:17:25 +04:00
echo " -e | --errors Show errors reported from tools"
2012-10-12 16:37:57 +04:00
echo " -h | --help Show this help message"
echo " -d | --dmoption DM_OPTIONS Comma separated DM specific options"
echo " -l | --lvmoption LVM_OPTIONS Comma separated LVM specific options"
echo " -u | --umount Unmount the device if mounted"
2013-08-13 19:17:25 +04:00
echo " -v | --verbose Verbose mode (also implies -e)"
2012-10-12 16:37:57 +04:00
echo
echo " Device specific options:"
echo " DM_OPTIONS:"
echo " retry retry removal several times in case of failure"
echo " force force device removal"
echo " LVM_OPTIONS:"
echo " retry retry removal several times in case of failure"
echo " wholevg deactivate the whole VG when processing an LV"
exit
}
add_device_to_skip_list( ) {
SKIP_DEVICE_LIST += ( [ " $kname " ] = 1)
return 1
}
add_vg_to_skip_list( ) {
SKIP_VG_LIST += ( [ " $DM_VG_NAME " ] = 1)
return 1
}
is_top_level_device( ) {
# top level devices do not have any holders, that is
# the SYS_BLK_DIR/<device_name>/holders dir is empty
files = " `echo $SYS_BLK_DIR / $kname /holders/*` "
test -z " $files "
}
2013-08-13 19:26:36 +04:00
device_umount_one( ) {
test -z " $mnt " && return 0
2013-01-23 17:45:41 +04:00
2012-10-12 16:37:57 +04:00
if test -z " ${ SKIP_UMOUNT_LIST [ " $mnt " ] } " -a " $DO_UMOUNT " -eq "1" ; then
2013-08-13 19:17:25 +04:00
echo -n " [UMOUNT]: unmounting $name ( $kname ) mounted on $mnt ... "
if eval $UMOUNT $UMOUNT_OPTS " $( printf $mnt ) " $OUT $ERR ; then
echo "done"
else
echo "skipping"
add_device_to_skip_list
fi
2012-10-12 16:37:57 +04:00
else
echo " [SKIP]: unmount of $name ( $kname ) mounted on $mnt "
add_device_to_skip_list
fi
}
2013-08-13 19:26:36 +04:00
device_umount( ) {
test " $devtype " != "lvm" && test " ${ kname : 0 : 3 } " != "dm-" && return 0
# FINDMNT is defined only if umount --all-targets is not available.
# In that case, read the list of multiple mount points of one device
# using FINDMNT and unmount it one by one manually.
if test -z " $FINDMNT " ; then
device_umount_one
else
while $FINDMNT_READ ; do
device_umount_one || return 1
done <<< " ` $FINDMNT $DEV_DIR / $kname ` "
fi
}
2012-10-12 16:37:57 +04:00
deactivate_holders ( ) {
local skip = 1; $LSBLK_VARS
# Get holders for the device - either a mount or another device.
# First line on the lsblk output is the device itself - skip it for
# the deactivate call as this device is already being deactivated.
while $LSBLK_READ ; do
test -e $SYS_BLK_DIR /$kname || continue
# check if the device not on the skip list already
test -z ${ SKIP_DEVICE_LIST [ " $kname " ] } || return 1
# try to deactivate the holder
test $skip -eq 1 && skip = 0 && continue
deactivate || return 1
done <<< " ` $LSBLK $1 ` "
}
deactivate_dm ( ) {
local name = $( printf $name )
test -b " $DEV_DIR /mapper/ $name " || return 0
test -z ${ SKIP_DEVICE_LIST [ " $kname " ] } || return 1
deactivate_holders " $DEV_DIR /mapper/ $name " || return 1
2013-08-13 19:17:25 +04:00
echo -n " [DM]: deactivating $devtype device $name ( $kname )... "
if eval $DMSETUP $DMSETUP_OPTS remove " $name " $OUT $ERR ; then
echo "done"
else
echo "skipping"
add_device_to_skip_list
fi
2012-10-12 16:37:57 +04:00
}
deactivate_lvm ( ) {
local DM_VG_NAME; local DM_LV_NAME; local DM_LV_LAYER
2013-08-13 19:17:25 +04:00
eval $( eval $DMSETUP splitname --nameprefixes --noheadings --rows " $name " LVM $ERR )
2012-10-12 16:37:57 +04:00
test -b " $DEV_DIR / $DM_VG_NAME / $DM_LV_NAME " || return 0
test -z ${ SKIP_VG_LIST [ " $DM_VG_NAME " ] } || return 1
2013-08-13 19:17:25 +04:00
if test $LVM_DO_WHOLE_VG -eq 0; then
2015-04-14 14:35:11 +03:00
# Skip LVM device deactivation if LVM tools missing.
test $LVM_AVAILABLE -eq 0 && {
add_device_to_skip_list
return 1
}
2013-08-13 19:17:25 +04:00
# Deactivating only the LV specified
2012-10-12 16:37:57 +04:00
deactivate_holders " $DEV_DIR / $DM_VG_NAME / $DM_LV_NAME " || {
add_device_to_skip_list
return 1
}
2013-08-13 19:17:25 +04:00
echo -n " [LVM]: deactivating Logical Volume $DM_VG_NAME / $DM_LV_NAME ... "
if eval $LVM lvchange $LVM_OPTS --config \' log{ prefix = \" \" } $LVM_CONFIG \' -aln $DM_VG_NAME /$DM_LV_NAME $OUT $ERR ; then
echo "done"
else
echo "skipping"
2012-10-12 16:37:57 +04:00
add_device_to_skip_list
2013-08-13 19:17:25 +04:00
fi
2012-10-12 16:37:57 +04:00
2013-08-13 19:17:25 +04:00
else
2015-04-14 14:35:11 +03:00
# Skip LVM VG deactivation if LVM tools missing.
test $LVM_AVAILABLE -eq 0 && {
add_vg_to_skip_list
return 1
}
2013-08-13 19:17:25 +04:00
# Deactivating the whole VG the LV is part of
lv_list = $( eval $LVM vgs --config " $LVM_CONFIG " --noheadings --rows -o lv_name $DM_VG_NAME $ERR )
for lv in $lv_list ; do
test -b " $DEV_DIR / $DM_VG_NAME / $lv " || continue
deactivate_holders " $DEV_DIR / $DM_VG_NAME / $lv " || {
add_vg_to_skip_list
return 1
}
done
echo -n " [LVM]: deactivating Volume Group $DM_VG_NAME ... "
if eval $LVM vgchange $LVM_OPTS --config \' log{ prefix = \" \" } $LVM_CONFIG \' -aln $DM_VG_NAME $OUT $ERR ; then
echo "done"
else
echo "skipping"
add_vg_to_skip_list
fi
fi
2012-10-12 16:37:57 +04:00
}
2015-10-06 14:25:37 +03:00
deactivate_md ( ) {
local name = $( printf $name )
test -b " $DEV_DIR / $name " || return 0
test -z ${ SKIP_DEVICE_LIST [ " $kname " ] } || return 1
# Skip MD device deactivation if MD tools missing.
test $MDADM_AVAILABLE -eq 0 && {
add_device_to_skip_list
return 1
}
deactivate_holders " $DEV_DIR / $name " || return 1
echo -n " [MD]: deactivating $devtype device $kname ... "
if eval $MDADM $MDADM_OPTS -S " $name " $OUT $ERR ; then
echo "done"
else
echo "skipping"
add_device_to_skip_list
fi
}
2012-10-12 16:37:57 +04:00
deactivate ( ) {
######################################################################
# DEACTIVATION HOOKS FOR NEW DEVICE TYPES GO HERE! #
# #
# Identify a new device type either by inspecting the TYPE provided #
# by lsblk directly ($devtype) or by any other mean that is suitable #
# e.g. the KNAME provided by lsblk ($kname). See $LSBLK_VARS for #
# complete list of variables that may be used. Then call a #
# device-specific deactivation function that handles the exact type. #
# #
# This device-specific function will certainly need to call #
# deactivate_holders first to recursively deactivate any existing #
# holders it might have before deactivating the device it processes. #
######################################################################
if test " $devtype " = "lvm" ; then
deactivate_lvm
elif test " ${ kname : 0 : 3 } " = "dm-" ; then
deactivate_dm
2015-10-06 14:25:37 +03:00
elif test " ${ kname : 0 : 2 } " = "md" ; then
deactivate_md
2012-10-12 16:37:57 +04:00
fi
}
deactivate_all( ) {
$LSBLK_VARS
skip = 0
echo "Deactivating block devices:"
if test $# -eq 0; then
2013-01-23 17:45:41 +04:00
#######################
# Process all devices #
#######################
# Unmount all relevant mountpoints first
while $LSBLK_READ ; do
device_umount
done <<< " ` $LSBLK | $SORT_MNT ` "
# Do deactivate
2012-10-12 16:37:57 +04:00
while $LSBLK_READ ; do
# 'disk' is at the bottom already and it's a real device
test " $devtype " = "disk" && continue
# if deactivation of any device fails, skip processing
# any subsequent devices within its subtree as the
# top-level device could not be deactivated anyway
test $skip -eq 1 && {
# reset 'skip' on top level device
is_top_level_device && skip = 0 || continue
}
# check if the device is not on the skip list already
test -z ${ SKIP_DEVICE_LIST [ " $kname " ] } || continue
# try to deactivate top-level device, set 'skip=1'
# if it fails to do so - this will cause all the
# device's subtree to be skipped when processing
# devices further in this loop
deactivate || skip = 1
done <<< " ` $LSBLK -s` "
else
2013-01-23 17:45:41 +04:00
##################################
# Process only specified devices #
##################################
2012-10-12 16:37:57 +04:00
while test $# -ne 0; do
2013-01-23 17:45:41 +04:00
# Unmount all relevant mountpoints first
while $LSBLK_READ ; do
device_umount
done <<< " ` $LSBLK $1 | $SORT_MNT ` "
# Do deactivate
2012-10-12 16:37:57 +04:00
# Single dm device tree deactivation.
if test -b " $1 " ; then
$LSBLK_READ <<< " ` $LSBLK --nodeps $1 ` "
# check if the device is not on the skip list already
2013-10-22 18:10:20 +04:00
test -z ${ SKIP_DEVICE_LIST [ " $kname " ] } || {
shift
continue
}
2012-10-12 16:37:57 +04:00
deactivate
else
echo " $1 : device not found "
return 1
fi
shift
done ;
fi
}
get_dmopts( ) {
ORIG_IFS = $IFS ; IFS = ','
for opt in $1 ; do
case $opt in
"" ) ; ;
"retry" ) DMSETUP_OPTS += "--retry " ; ;
"force" ) DMSETUP_OPTS += "--force " ; ;
*) echo " $opt : unknown DM option "
esac
done
IFS = $ORIG_IFS
}
get_lvmopts( ) {
ORIG_IFS = $IFS ; IFS = ','
for opt in $1 ; do
case " $opt " in
"" ) ; ;
"retry" ) LVM_CONFIG = "activation{retry_deactivation=1}" ; ;
"wholevg" ) LVM_DO_WHOLE_VG = 1 ; ;
*) echo " $opt : unknown LVM option "
esac
done
IFS = $ORIG_IFS
}
2013-08-13 19:17:25 +04:00
set_env( ) {
if test " $ERRORS " -eq "1" ; then
unset ERR
else
ERR = " 2> $DEV_DIR /null "
fi
if test " $VERBOSE " -eq "1" ; then
unset OUT
UMOUNT_OPTS += "-v"
DMSETUP_OPTS += "-vvvv"
LVM_OPTS += "-vvvv"
2015-10-06 14:25:37 +03:00
MDADM_OPTS += "-vv"
2013-08-13 19:17:25 +04:00
else
OUT = " 1> $DEV_DIR /null "
fi
2015-04-14 14:35:11 +03:00
if test -f $LVM ; then
LVM_AVAILABLE = 1
else
LVM_AVAILABLE = 0
fi
2015-10-06 14:25:37 +03:00
if test -f $MDADM ; then
MDADM_AVAILABLE = 1
else
MDADM_AVAILABLE = 0
fi
2013-08-13 19:17:25 +04:00
}
2012-10-12 16:37:57 +04:00
while test $# -ne 0; do
case " $1 " in
"" ) ; ;
2013-08-13 19:17:25 +04:00
"-e" | "--errors" ) ERRORS = 1 ; ;
2012-10-12 16:37:57 +04:00
"-h" | "--help" ) usage ; ;
2012-10-12 17:42:24 +04:00
"-d" | "--dmoption " ) get_dmopts " $2 " ; shift ; ;
"-l" | "--lvmoption " ) get_lvmopts " $2 " ; shift ; ;
2012-10-12 16:37:57 +04:00
"-u" | "--umount" ) DO_UMOUNT = 1 ; ;
2013-08-13 19:17:25 +04:00
"-v" | "--verbose" ) VERBOSE = 1 ; ERRORS = 1 ; ;
"-vv" ) VERBOSE = 1 ; ERRORS = 1 ; set -x ; ;
2012-10-12 16:37:57 +04:00
*) break ; ;
esac
shift
done
2013-08-13 19:17:25 +04:00
set_env
2012-10-12 16:37:57 +04:00
deactivate_all " $@ "