#!/bin/bash # Copyright (C) 2009 Chris Procter All rights reserved. # Copyright (C) 2009 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 PVS_OUT=`"${LVM}" pvs ${LVM_OPTS} --noheadings -o vg_name "$ARG" 2>/dev/null` checkvalue $? "$ARG is not a PV." PV_VGNAME=$(echo $PVS_OUT | $GREP -v '[[:space:]]+$') [ -z "$PV_VGNAME" ] && die 3 "$ARG is not in a VG." 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 2>/dev/null` 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 # FIXME convert to cmdline override "$LVM" dumpconfig ${LVM_OPTS} | \ "$AWK" -v DEV=${TMP_LVM_SYSTEM_DIR} -v CACHE=${TMP_LVM_SYSTEM_DIR}/.cache \ -v CACHE_DIR=${TMP_LVM_SYSTEM_DIR}/cache \ '/^[ \t]*filter[ \t]*=/{print ENVIRON["FILTER"];next} \ /^[ \t]*scan[ \t]*=/{print "scan = [ \"" DEV "\" ]";next} \ /^[ \t]*cache[ \t]*=/{print "cache = \"" CACHE "\"";next} \ /^[ \t]*use_lvmetad[ \t]*=/{print "use_lvmetad = 0";next} \ /^[ \t]*cache_dir[ \t]*=/{print "cache_dir = \"" CACHE_DIR "\"";next} \ {print $0}' > ${LVMCONF} 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 : 2>/dev/null` 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 "$LVM" vgscan ${LVM_OPTS} --mknodes fi exit 0