1#!/bin/sh
2
3# Copyright (C) 2009 Chris Procter All rights reserved.
4# Copyright (C) 2009 Red Hat, Inc. All rights reserved.
5#
6# This file is part of LVM2.
7#
8# This copyrighted material is made available to anyone wishing to use,
9# modify, copy, or redistribute it subject to the terms and conditions
10# of the GNU General Public License v.2.
11#
12# You should have received a copy of the GNU General Public License
13# along with this program; if not, write to the Free Software Foundation,
14# Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
15#
16# vgimportclone: This script is used to rename the VG and change the associated
17#                VG and PV UUIDs (primary application being HW snapshot restore)
18
19# following external commands are used throughout the script
20# echo and test are internal in bash at least
21RM=rm
22BASENAME=basename
23MKTEMP=mktemp
24AWK=awk
25CUT=cut
26TR=tr
27READLINK=readlink
28GREP=grep
29GETOPT=getopt
30
31# user may override lvm location by setting LVM_BINARY
32LVM="${LVM_BINARY:-lvm}"
33
34die() {
35    code=$1; shift
36    echo "Fatal: $@" 1>&2
37    exit $code
38}
39
40"$LVM" version >& /dev/null || die 2 "Could not run lvm binary '$LVM'"
41
42
43function getvgname {
44### get a unique vg name
45###        $1 = list of exists VGs
46###        $2 = the name we want
47    VGLIST=$1
48    VG=$2
49    NEWVG=$3
50
51    BNAME="${NEWVG:-${VG}}"
52    NAME="${BNAME}"
53    I=0
54
55    while [[ "${VGLIST}" =~ "${NAME}" ]]
56    do
57        I=$(($I+1))
58        NAME="${BNAME}$I"
59    done
60    echo "${NAME}"
61}
62
63
64function checkvalue {
65### check return value and error if non zero
66    if [ $1 -ne 0 ]
67    then
68        die $1 "$2, error: $1"
69    fi
70}
71
72
73function usage {
74### display usage message
75    echo "Usage: ${SCRIPTNAME} [options] PhysicalVolume [PhysicalVolume...]"
76    echo "    -n|--basevgname - Base name for the new volume group(s)"
77    echo "    -i|--import     - Import any exported volume groups found"
78    echo "    -t|--test       - Run in test mode"
79    echo "    --quiet         - Suppress output"
80    echo "    -v|--verbose    - Set verbose level"
81    echo "    -d|--debug      - Set debug level"
82    echo "    --version       - Display version information"
83    echo "    -h|--help       - Display this help message"
84    echo ""
85    exit 1
86}
87
88
89function cleanup {
90    #set to use old lvm.conf
91    LVM_SYSTEM_DIR=${ORIG_LVM_SYS_DIR}
92
93    if [ $KEEP_TMP_LVM_SYSTEM_DIR -eq 1 ]; then
94        echo "${SCRIPTNAME}: LVM_SYSTEM_DIR (${TMP_LVM_SYSTEM_DIR}) must be cleaned up manually."
95    else
96        "$RM" -rf -- "${TMP_LVM_SYSTEM_DIR}"
97    fi
98}
99
100SCRIPTNAME=`"$BASENAME" $0`
101
102
103if [ "$UID" != "0" -a "$EUID" != "0" ]
104then
105    die 3 "${SCRIPTNAME} must be run as root."
106fi
107
108LVM_OPTS=""
109TEST_OPT=""
110DISKS=""
111# for compatibility: using mktemp -t rather than --tmpdir
112TMP_LVM_SYSTEM_DIR=`"$MKTEMP" -d -t snap.XXXXXXXX`
113KEEP_TMP_LVM_SYSTEM_DIR=0
114CHANGES_MADE=0
115IMPORT=0
116DEBUG=""
117VERBOSE=""
118VERBOSE_COUNT=0
119DEVNO=0
120
121if [ -n "${LVM_SYSTEM_DIR}" ]; then
122    export ORIG_LVM_SYS_DIR="${LVM_SYSTEM_DIR}"
123fi
124
125trap  cleanup 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
126
127#####################################################################
128### Get and check arguments
129#####################################################################
130OPTIONS=`"$GETOPT" -o n:dhitv \
131    -l basevgname:,debug,help,import,quiet,test,verbose,version \
132    -n "${SCRIPTNAME}" -- "$@"`
133[ $? -ne 0 ] && usage
134eval set -- "$OPTIONS"
135
136while true
137do
138    case $1 in
139        -n|--basevgname)
140            NEWVG="$2"; shift; shift
141            ;;
142        -i|--import)
143            IMPORT=1; shift
144            ;;
145        -t|--test)
146            TEST_OPT="-t"
147            shift
148            ;;
149        --quiet)
150            LVM_OPTS="--quiet ${LVM_OPTS}"
151            shift
152            ;;
153        -v|--verbose)
154            let VERBOSE_COUNT=VERBOSE_COUNT+1
155            if [ -z "$VERBOSE" ]
156            then
157                VERBOSE="-v"
158            else
159                VERBOSE="${VERBOSE}v"
160            fi
161            shift
162            ;;
163        -d|--debug)
164            if [ -z "$DEBUG" ]
165            then
166                DEBUG="-d"
167                set -x
168            else
169                DEBUG="${DEBUG}d"
170            fi
171            shift
172            ;;
173        --version)
174            "$LVM" version
175            shift
176            exit 0
177            ;;
178        -h|--help)
179            usage; shift
180            ;;
181        --)
182            shift; break
183            ;;
184        *)
185            usage
186            ;;
187    esac
188done
189
190# turn on DEBUG (special case associated with -v use)
191if [ -z "$DEBUG" -a $VERBOSE_COUNT -gt 3 ]; then
192    DEBUG="-d"
193    set -x
194fi
195
196# setup LVM_OPTS
197if [ -n "${DEBUG}" -o -n "${VERBOSE}" ]
198then
199    LVM_OPTS="${LVM_OPTS} ${DEBUG} ${VERBOSE}"
200fi
201
202# process remaining arguments (which should be disks)
203for ARG
204do
205    if [ -b "$ARG" ]
206    then
207        PVS_OUT=`"${LVM}" pvs ${LVM_OPTS} --noheadings -o vg_name "$ARG" 2>/dev/null`
208        checkvalue $? "$ARG is not a PV."
209        PV_VGNAME=$(echo $PVS_OUT | $GREP -v '[[:space:]]+$')
210        [ -z "$PV_VGNAME" ] && die 3 "$ARG is not in a VG."
211
212        ln -s "$ARG" ${TMP_LVM_SYSTEM_DIR}/vgimport${DEVNO}
213        DISKS="${DISKS} ${TMP_LVM_SYSTEM_DIR}/vgimport${DEVNO}"
214        DEVNO=$((${DEVNO}+1))
215    else
216        die 3 "$ARG is not a block device."
217    fi
218done
219
220### check we have suitable values for important variables
221if [ -z "${DISKS}" ]
222then
223    usage
224fi
225
226#####################################################################
227### Get the existing state so we can use it later
228#####################################################################
229
230OLDVGS=`"${LVM}" vgs ${LVM_OPTS} -o name --noheadings 2>/dev/null`
231checkvalue $? "Current VG names could not be collected without errors"
232
233#####################################################################
234### Prepare the temporary lvm environment
235#####################################################################
236
237for BLOCK in ${DISKS}
238do
239    FILTER="\"a|^${BLOCK}$|\", ${FILTER}"
240done
241export FILTER="filter=[ ${FILTER} \"r|.*|\" ]"
242
243LVMCONF=${TMP_LVM_SYSTEM_DIR}/lvm.conf
244
245"$LVM" dumpconfig ${LVM_OPTS} | \
246"$AWK" -v DEV=${TMP_LVM_SYSTEM_DIR} -v CACHE=${TMP_LVM_SYSTEM_DIR}/.cache \
247    -v CACHE_DIR=${TMP_LVM_SYSTEM_DIR}/cache \
248    '/^[[:space:]]*filter[[:space:]]*=/{print ENVIRON["FILTER"];next} \
249     /^[[:space:]]*scan[[:space:]]*=/{print "scan = [ \"" DEV "\" ]";next} \
250     /^[[:space:]]*cache[[:space:]]*=/{print "cache = \"" CACHE "\"";next} \
251     /^[[:space:]]*cache_dir[[:space:]]*=/{print "cache_dir = \"" CACHE_DIR "\"";next} \
252     {print $0}' > ${LVMCONF}
253
254checkvalue $? "Failed to generate ${LVMCONF}"
255# Only keep TMP_LVM_SYSTEM_DIR if it contains something worth keeping
256[ -n "${DEBUG}" ] && KEEP_TMP_LVM_SYSTEM_DIR=1
257
258# verify the config contains the filter, scan and cache_dir (or cache) config keywords
259"$GREP" -q '^[[:space:]]*filter[[:space:]]*=' ${LVMCONF} || \
260    die 5 "Temporary lvm.conf must contain 'filter' config."
261"$GREP" -q '^[[:space:]]*scan[[:space:]]*=' ${LVMCONF} || \
262    die 6 "Temporary lvm.conf must contain 'scan' config."
263
264# check for either 'cache' or 'cache_dir' config values
265"$GREP" -q '[[:space:]]*cache[[:space:]]*=' ${LVMCONF}
266CACHE_RET=$?
267"$GREP" -q '^[[:space:]]*cache_dir' ${LVMCONF}
268CACHE_DIR_RET=$?
269[ $CACHE_RET -eq 0 -o $CACHE_DIR_RET -eq 0 ] || \
270    die 7 "Temporary lvm.conf must contain 'cache' or 'cache_dir' config."
271
272### set to use new lvm.conf
273export LVM_SYSTEM_DIR=${TMP_LVM_SYSTEM_DIR}
274
275
276#####################################################################
277### Rename the VG(s) and change the VG and PV UUIDs.
278#####################################################################
279
280PVINFO=`"${LVM}" pvs ${LVM_OPTS} -o pv_name,vg_name,vg_attr --noheadings --separator : 2>/dev/null`
281checkvalue $? "PV info could not be collected without errors"
282
283# output VG info so each line looks like: name:exported?:disk1,disk2,...
284VGINFO=`echo "${PVINFO}" | \
285    "$AWK" -F : '{{sub(/^[[:space:]]*/,"")} \
286    {sub(/unknown device/,"unknown_device")} \
287    {vg[$2]=$1","vg[$2]} if($3 ~ /^..x/){x[$2]="x"}} \
288    END{for(k in vg){printf("%s:%s:%s\n", k, x[k], vg[k])}}'`
289checkvalue $? "PV info could not be parsed without errors"
290
291for VG in ${VGINFO}
292do
293    VGNAME=`echo "${VG}" | "$CUT" -d: -f1`
294    EXPORTED=`echo "${VG}" | "$CUT" -d: -f2`
295    PVLIST=`echo "${VG}" | "$CUT" -d: -f3- | "$TR" , ' '`
296
297    if [ -z "${VGNAME}" ]
298    then
299        FOLLOWLIST=""
300        for DEV in $PVLIST; do
301            FOLLOW=`"$READLINK" $DEV`
302            FOLLOWLIST="$FOLLOW $FOLLOWLIST"
303        done
304        die 8 "Specified PV(s) ($FOLLOWLIST) don't belong to a VG."
305    fi
306
307    if [ -n "${EXPORTED}" ]
308    then
309        if [ ${IMPORT} -eq 1 ]
310        then
311            "$LVM" vgimport ${LVM_OPTS} ${TEST_OPT} "${VGNAME}"
312            checkvalue $? "Volume Group ${VGNAME} could not be imported"
313        else
314            echo "Volume Group ${VGNAME} exported, skipping."
315            continue
316        fi
317    fi
318
319    ### change the pv uuids
320    if [[ "${PVLIST}" =~ "unknown" ]]
321    then
322        echo "Volume Group ${VGNAME} has unknown PV(s), skipping."
323        echo "- Were all associated PV(s) supplied as arguments?"
324        continue
325    fi
326
327    for BLOCKDEV in ${PVLIST}
328    do
329        "$LVM" pvchange ${LVM_OPTS} ${TEST_OPT} --uuid ${BLOCKDEV} --config 'global{activation=0}'
330        checkvalue $? "Unable to change PV uuid for ${BLOCKDEV}"
331    done
332
333    NEWVGNAME=`getvgname "${OLDVGS}" "${VGNAME}" "${NEWVG}"`
334
335    "$LVM" vgchange ${LVM_OPTS} ${TEST_OPT} --uuid "${VGNAME}" --config 'global{activation=0}'
336    checkvalue $? "Unable to change VG uuid for ${VGNAME}"
337
338    ## if the name isn't going to get changed dont even try.
339    if [ "${VGNAME}" != "${NEWVGNAME}" ]
340    then
341        "$LVM" vgrename ${LVM_OPTS} ${TEST_OPT} "${VGNAME}" "${NEWVGNAME}"
342        checkvalue $? "Unable to rename ${VGNAME} to ${NEWVGNAME}"
343    fi
344
345    CHANGES_MADE=1
346done
347
348#####################################################################
349### Restore the old environment
350#####################################################################
351### set to use old lvm.conf
352if [ -z "${ORIG_LVM_SYS_DIR}" ]
353then
354    unset LVM_SYSTEM_DIR
355else
356    LVM_SYSTEM_DIR=${ORIG_LVM_SYS_DIR}
357fi
358
359### update the device cache and make sure all
360### the device nodes we need are straight
361if [ ${CHANGES_MADE} -eq 1 ]
362then
363    "$LVM" vgscan ${LVM_OPTS} --mknodes
364fi
365
366exit 0
367