| #!/bin/bash |
| |
| # Copyright (C) 2009 Chris Procter All rights reserved. |
| # Copyright (C) 2009-2015 Red Hat, Inc. All rights reserved. |
| # |
| # 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, |
| # Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| # |
| # vgimportclone: This script is used to rename the VG and change the associated |
| # VG and PV UUIDs (primary application being HW snapshot restore) |
| |
| # following external commands are used throughout the script |
| # echo and test are internal in bash at least |
| RM=rm |
| BASENAME=basename |
| MKTEMP=mktemp |
| AWK=awk |
| CUT=cut |
| TR=tr |
| READLINK=readlink |
| GREP=grep |
| GETOPT=getopt |
| |
| # user may override lvm location by setting LVM_BINARY |
| LVM="${LVM_BINARY:-lvm}" |
| |
| die() { |
| code=$1; shift |
| echo "Fatal: $@" 1>&2 |
| exit $code |
| } |
| |
| "$LVM" version >& /dev/null || die 2 "Could not run lvm binary '$LVM'" |
| |
| |
| function getvgname { |
| ### get a unique vg name |
| ### $1 = list of exists VGs |
| ### $2 = the name we want |
| VGLIST=$1 |
| VG=$2 |
| NEWVG=$3 |
| |
| BNAME="${NEWVG:-${VG}}" |
| NAME="${BNAME}" |
| I=0 |
| |
| while [[ "${VGLIST}" =~ "${NAME}" ]] |
| do |
| I=$(($I+1)) |
| NAME="${BNAME}$I" |
| done |
| echo "${NAME}" |
| } |
| |
| |
| function checkvalue { |
| ### check return value and error if non zero |
| if [ $1 -ne 0 ] |
| then |
| die $1 "$2, error: $1" |
| fi |
| } |
| |
| |
| function usage { |
| ### display usage message |
| echo "Usage: ${SCRIPTNAME} [options] PhysicalVolume [PhysicalVolume...]" |
| echo " -n|--basevgname - Base name for the new volume group(s)" |
| echo " -i|--import - Import any exported volume groups found" |
| echo " -t|--test - Run in test mode" |
| echo " --quiet - Suppress output" |
| echo " -v|--verbose - Set verbose level" |
| echo " -d|--debug - Set debug level" |
| echo " --version - Display version information" |
| echo " -h|--help - Display this help message" |
| echo "" |
| exit 1 |
| } |
| |
| |
| function cleanup { |
| #set to use old lvm.conf |
| LVM_SYSTEM_DIR=${ORIG_LVM_SYS_DIR} |
| |
| if [ $KEEP_TMP_LVM_SYSTEM_DIR -eq 1 ]; then |
| echo "${SCRIPTNAME}: LVM_SYSTEM_DIR (${TMP_LVM_SYSTEM_DIR}) must be cleaned up manually." |
| else |
| "$RM" -rf -- "${TMP_LVM_SYSTEM_DIR}" |
| fi |
| } |
| |
| SCRIPTNAME=`"$BASENAME" $0` |
| |
| |
| if [ "$UID" != "0" -a "$EUID" != "0" ] |
| then |
| die 3 "${SCRIPTNAME} must be run as root." |
| fi |
| |
| LVM_OPTS="" |
| TEST_OPT="" |
| DISKS="" |
| # for compatibility: using mktemp -t rather than --tmpdir |
| TMP_LVM_SYSTEM_DIR=`"$MKTEMP" -d -t snap.XXXXXXXX` |
| KEEP_TMP_LVM_SYSTEM_DIR=0 |
| CHANGES_MADE=0 |
| IMPORT=0 |
| DEBUG="" |
| VERBOSE="" |
| VERBOSE_COUNT=0 |
| DEVNO=0 |
| |
| if [ -n "${LVM_SYSTEM_DIR}" ]; then |
| export ORIG_LVM_SYS_DIR="${LVM_SYSTEM_DIR}" |
| fi |
| |
| trap cleanup 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
| |
| ##################################################################### |
| ### Get and check arguments |
| ##################################################################### |
| OPTIONS=`"$GETOPT" -o n:dhitv \ |
| -l basevgname:,debug,help,import,quiet,test,verbose,version \ |
| -n "${SCRIPTNAME}" -- "$@"` |
| [ $? -ne 0 ] && usage |
| eval set -- "$OPTIONS" |
| |
| while true |
| do |
| case $1 in |
| -n|--basevgname) |
| NEWVG="$2"; shift; shift |
| ;; |
| -i|--import) |
| IMPORT=1; shift |
| ;; |
| -t|--test) |
| TEST_OPT="-t" |
| shift |
| ;; |
| --quiet) |
| LVM_OPTS="--quiet ${LVM_OPTS}" |
| shift |
| ;; |
| -v|--verbose) |
| let VERBOSE_COUNT=VERBOSE_COUNT+1 |
| if [ -z "$VERBOSE" ] |
| then |
| VERBOSE="-v" |
| else |
| VERBOSE="${VERBOSE}v" |
| fi |
| shift |
| ;; |
| -d|--debug) |
| if [ -z "$DEBUG" ] |
| then |
| DEBUG="-d" |
| set -x |
| else |
| DEBUG="${DEBUG}d" |
| fi |
| shift |
| ;; |
| --version) |
| "$LVM" version |
| shift |
| exit 0 |
| ;; |
| -h|--help) |
| usage; shift |
| ;; |
| --) |
| shift; break |
| ;; |
| *) |
| usage |
| ;; |
| esac |
| done |
| |
| # turn on DEBUG (special case associated with -v use) |
| if [ -z "$DEBUG" -a $VERBOSE_COUNT -gt 3 ]; then |
| DEBUG="-d" |
| set -x |
| fi |
| |
| # setup LVM_OPTS |
| if [ -n "${DEBUG}" -o -n "${VERBOSE}" ] |
| then |
| LVM_OPTS="${LVM_OPTS} ${DEBUG} ${VERBOSE}" |
| fi |
| |
| # process remaining arguments (which should be disks) |
| for ARG |
| do |
| if [ -b "$ARG" ] |
| then |
| ln -s "$ARG" ${TMP_LVM_SYSTEM_DIR}/vgimport${DEVNO} |
| DISKS="${DISKS} ${TMP_LVM_SYSTEM_DIR}/vgimport${DEVNO}" |
| DEVNO=$((${DEVNO}+1)) |
| else |
| die 3 "$ARG is not a block device." |
| fi |
| done |
| |
| ### check we have suitable values for important variables |
| if [ -z "${DISKS}" ] |
| then |
| usage |
| fi |
| |
| ##################################################################### |
| ### Get the existing state so we can use it later |
| ##################################################################### |
| |
| OLDVGS=`"${LVM}" vgs ${LVM_OPTS} -o name --noheadings` |
| checkvalue $? "Current VG names could not be collected without errors" |
| |
| ##################################################################### |
| ### Prepare the temporary lvm environment |
| ##################################################################### |
| |
| for BLOCK in ${DISKS} |
| do |
| FILTER="\"a|^${BLOCK}$|\", ${FILTER}" |
| done |
| export FILTER="filter=[ ${FILTER} \"r|.*|\" ]" |
| |
| LVMCONF=${TMP_LVM_SYSTEM_DIR}/lvm.conf |
| |
| CMD_CONFIG_LINE="devices { \ |
| scan = [ \"${TMP_LVM_SYSTEM_DIR}\" ] \ |
| cache_dir = \"${TMP_LVM_SYSTEM_DIR}/cache\" |
| global_filter = [ \"a|.*|\" ] \ |
| ${FILTER} |
| } \ |
| global { \ |
| use_lvmetad = 0 \ |
| }" |
| |
| $LVM dumpconfig ${LVM_OPTS} --file ${LVMCONF} --mergedconfig --config "${CMD_CONFIG_LINE}" |
| |
| checkvalue $? "Failed to generate ${LVMCONF}" |
| # Only keep TMP_LVM_SYSTEM_DIR if it contains something worth keeping |
| [ -n "${DEBUG}" ] && KEEP_TMP_LVM_SYSTEM_DIR=1 |
| |
| # verify the config contains the filter, scan and cache_dir (or cache) config keywords |
| "$GREP" -q '^[[:space:]]*filter[[:space:]]*=' ${LVMCONF} || \ |
| die 5 "Temporary lvm.conf must contain 'filter' config." |
| "$GREP" -q '^[[:space:]]*scan[[:space:]]*=' ${LVMCONF} || \ |
| die 6 "Temporary lvm.conf must contain 'scan' config." |
| |
| # check for either 'cache' or 'cache_dir' config values |
| "$GREP" -q '[[:space:]]*cache[[:space:]]*=' ${LVMCONF} |
| CACHE_RET=$? |
| "$GREP" -q '^[[:space:]]*cache_dir' ${LVMCONF} |
| CACHE_DIR_RET=$? |
| [ $CACHE_RET -eq 0 -o $CACHE_DIR_RET -eq 0 ] || \ |
| die 7 "Temporary lvm.conf must contain 'cache' or 'cache_dir' config." |
| |
| ### set to use new lvm.conf |
| export LVM_SYSTEM_DIR=${TMP_LVM_SYSTEM_DIR} |
| |
| |
| ##################################################################### |
| ### Rename the VG(s) and change the VG and PV UUIDs. |
| ##################################################################### |
| |
| PVINFO=`"${LVM}" pvs ${LVM_OPTS} -o pv_name,vg_name,vg_attr --noheadings --separator :` |
| checkvalue $? "PV info could not be collected without errors" |
| |
| # output VG info so each line looks like: name:exported?:disk1,disk2,... |
| VGINFO=`echo "${PVINFO}" | \ |
| "$AWK" -F : '{{sub(/^[ \t]*/,"")} \ |
| {sub(/unknown device/,"unknown_device")} \ |
| {vg[$2]=$1","vg[$2]} if($3 ~ /^..x/){x[$2]="x"}} \ |
| END{for(k in vg){printf("%s:%s:%s\n", k, x[k], vg[k])}}'` |
| checkvalue $? "PV info could not be parsed without errors" |
| |
| for VG in ${VGINFO} |
| do |
| VGNAME=`echo "${VG}" | "$CUT" -d: -f1` |
| EXPORTED=`echo "${VG}" | "$CUT" -d: -f2` |
| PVLIST=`echo "${VG}" | "$CUT" -d: -f3- | "$TR" , ' '` |
| |
| if [ -z "${VGNAME}" ] |
| then |
| FOLLOWLIST="" |
| for DEV in $PVLIST; do |
| FOLLOW=`"$READLINK" $DEV` |
| FOLLOWLIST="$FOLLOW $FOLLOWLIST" |
| done |
| die 8 "Specified PV(s) ($FOLLOWLIST) don't belong to a VG." |
| fi |
| |
| if [ -n "${EXPORTED}" ] |
| then |
| if [ ${IMPORT} -eq 1 ] |
| then |
| "$LVM" vgimport ${LVM_OPTS} ${TEST_OPT} "${VGNAME}" |
| checkvalue $? "Volume Group ${VGNAME} could not be imported" |
| else |
| echo "Volume Group ${VGNAME} exported, skipping." |
| continue |
| fi |
| fi |
| |
| ### change the pv uuids |
| if [[ "${PVLIST}" =~ "unknown" ]] |
| then |
| echo "Volume Group ${VGNAME} has unknown PV(s), skipping." |
| echo "- Were all associated PV(s) supplied as arguments?" |
| continue |
| fi |
| |
| for BLOCKDEV in ${PVLIST} |
| do |
| "$LVM" pvchange ${LVM_OPTS} ${TEST_OPT} --uuid ${BLOCKDEV} --config 'global{activation=0}' |
| checkvalue $? "Unable to change PV uuid for ${BLOCKDEV}" |
| done |
| |
| NEWVGNAME=`getvgname "${OLDVGS}" "${VGNAME}" "${NEWVG}"` |
| |
| "$LVM" vgchange ${LVM_OPTS} ${TEST_OPT} --uuid "${VGNAME}" --config 'global{activation=0}' |
| checkvalue $? "Unable to change VG uuid for ${VGNAME}" |
| |
| ## if the name isn't going to get changed dont even try. |
| if [ "${VGNAME}" != "${NEWVGNAME}" ] |
| then |
| "$LVM" vgrename ${LVM_OPTS} ${TEST_OPT} "${VGNAME}" "${NEWVGNAME}" |
| checkvalue $? "Unable to rename ${VGNAME} to ${NEWVGNAME}" |
| fi |
| |
| CHANGES_MADE=1 |
| done |
| |
| ##################################################################### |
| ### Restore the old environment |
| ##################################################################### |
| ### set to use old lvm.conf |
| if [ -z "${ORIG_LVM_SYS_DIR}" ] |
| then |
| unset LVM_SYSTEM_DIR |
| else |
| LVM_SYSTEM_DIR=${ORIG_LVM_SYS_DIR} |
| fi |
| |
| ### update the device cache and make sure all |
| ### the device nodes we need are straight |
| if [ ${CHANGES_MADE} -eq 1 ] |
| then |
| # get global/use_lvmetad config and if set also notify lvmetad about changes |
| # since we were running LVM commands above with use_lvmetad=0 |
| eval $(${LVM} dumpconfig ${LVM_OPTS} global/use_lvmetad) |
| if [ "$use_lvmetad" = "1" ] |
| then |
| echo "Notifying lvmetad about changes since it was disabled temporarily." |
| echo "(This resolves any WARNING message about restarting lvmetad that appears above.)" |
| LVM_OPTS="${LVM_OPTS} --cache" |
| fi |
| |
| "$LVM" vgscan ${LVM_OPTS} --mknodes |
| fi |
| |
| exit 0 |