forked from bagley/lvsnap
-
Notifications
You must be signed in to change notification settings - Fork 0
/
lvsnap
334 lines (308 loc) · 9.1 KB
/
lvsnap
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
#!/bin/sh
#
# lvsnap Ver 0.7b
# Author: Tom Sightler
#
# This script is primarily designed for taking a list of LVM volumes
# and automatically creating LVM snapshots and mounting them at the
# specified locations. We use these to create LVM snapshot based
# chroot environments for rdiff-backup but there are of course plenty
# of other uses.
#
# Versions of this script greather than 0.5 include the ability to mount
# portions of the chroot environment as simple bind mounts. Bind mounts
# don't offer much security, but can be useful if you want to make complete
# chroot backups of the host when some of the volumes are not LVM (for example
# the /boot volume, which is usually a straight partition). This was particularly
# useful for us because of older system where the OS volumes were installed
# on simple partitions rather than LVM.
# Location of the lvsnap configuration file
LVSNAPTAB=/usr/local/etc/lvsnaptab
export PATH=/usr/bin:/bin:/usr/sbin:/sbin
# Function Definitions
# Loop through the LVs and create the snap LVs
# Returns failure if any snap fails
function createsnaps {
ret=0
for i in ${SNAPVOLSCOUNT}; do
if [ ${MOUNTTYPE[$i]} == "snap" ]; then
echo -n "Creating LVM snaphot of ${SNAPVOLS[$i]}..."
if ! lvcreate -L ${SNAPSPACEPCT[$i]} -s -n ${SNAPNAME[$i]} ${SNAPVOLS[$i]} >/dev/null 2>&1; then
echo -e $FAILURE
ret=1
else
echo -e $SUCCESS
fi
fi
if [ ${MOUNTTYPE[$i]} == "bsnap" ] && \
[ -d "${SNAPVOLS[$i]}" ]; then
echo -n "Creating BTRFS snaphot of ${SNAPVOLS[$i]}..."
# we must chdir, or snapshot will fail
cd "${SNAPVOLS[$i]}"
# btrfsctl return 1 on success or error. so check dir
btrfsctl -s .backupsnap . >/dev/null 2>&1
if ! [ -d "${SNAPVOLS[$i]}/.backupsnap" ]; then
echo -e $FAILURE
ret=1
else
echo -e $SUCCESS
fi
elif [ ${MOUNTTYPE[$i]} == "bsnap" ] && \
! [ -d "${SNAPVOLS[$i]}" ]; then
echo -n "Error on ${SNAPVOLS[$i]}: Must specify BTRFS snapshots by directory..."
echo -e $FAILURE
fi
done
return $ret
}
# Loop through the LVs and delete the snap LVs
function destroysnaps {
ret=0
for i in ${SNAPVOLSCOUNT}; do
# This causes volumes to be destroyed in reverse order of
# their creation. Not really important here, but done for
# consistency with umounting since it can be important there.
let "j = (${#SNAPVOLS[@]}-1) - i"
if [ ${MOUNTTYPE[$j]} == "snap" ]; then
echo -n "Removing LVM snapshot of ${SNAPVOLS[$j]}..."
if ! lvremove -f ${SNAPVG[$j]}/${SNAPNAME[$j]} >/dev/null 2>&1; then
# this hack seems to remove it even when above wont
echo -n " hard way..."
DMNAME=$(basename ${SNAPVG[$j]})-$(echo ${SNAPNAME[$j]} | sed s/-snap/--snap/)
retout=0
sleep 3
dmsetup remove $DMNAME >/dev/null
retout=$(($retout + $?))
sleep 2
lvremove -f ${SNAPVG[$j]}/${SNAPNAME[$j]} >/dev/null
retout=$(($retout + $?))
sleep 1
dmsetup remove ${DMNAME}-cow >/dev/null
retout=$(($retout + $?))
if [ $retout != 0 ] ; then
echo -e $FAILURE
ret=1
else
echo -e $SUCCESS
fi
else
echo -e $SUCCESS
fi
sleep 1
fi
if [ ${MOUNTTYPE[$j]} == "bsnap" ]; then
echo -n "Removing BTRFS snaphot of ${SNAPVOLS[$j]}..."
# we must chdir, or snapshot will fail
cd "${SNAPVOLS[$j]}"
# btrfsctl return 1 on success or error. so check dir
btrfsctl -D .backupsnap . >/dev/null 2>&1
sleep 1
# wait up to 10 secs for snapshot to be removed.
#x=10
#while [ -d "${SNAPVOLS[$j]}/.backupsnap" ] && [ $x -gt 0 ]; do
# sleep 1
# let "x = $x - 1"
#done
if [ -d "${SNAPVOLS[$j]}/.backupsnap" ]; then
echo -e $FAILURE
ret=1
else
echo -e $SUCCESS
fi
fi
done
return $ret
}
# Loop through the snap LV list and mount them
function mountsnaps {
ret=0
for i in ${SNAPVOLSCOUNT}; do
if ! ismount ${SNAPMOUNTS[$i]}; then
mkdir -p ${SNAPMOUNTS[$i]}
if [ ${MOUNTTYPE[$i]} == "snap" ]; then
echo -n "Mounting snapshot volume ${SNAPVG[$i]}/${SNAPNAME[$i]} at ${SNAPMOUNTS[$i]}..."
if ! mount ${SNAPVG[$i]}/${SNAPNAME[$i]} ${SNAPMOUNTS[$i]} >/dev/null 2>&1; then
mount ${SNAPMOUNTS[$i]} -o remount,ro >/dev/null 2>&1
echo -e $FAILURE
ret=1
else
echo -e $SUCCESS
fi
elif [ ${MOUNTTYPE[$i]} == "bind" ]; then
echo -n "Creating bind mount for ${SNAPVOLS[$i]} at ${SNAPMOUNTS[$i]}..."
if ! mount --bind ${SNAPVOLS[$i]} ${SNAPMOUNTS[$i]} >/dev/null 2>&1; then
mount ${SNAPMOUNTS[$i]} -o remount,ro >/dev/null 2>&1
echo -e $FAILURE
ret=1
else
# mount ${SNAPMOUNTS[$i]} -o remount,ro >/dev/null 2>&1
echo -e $SUCCESS
fi
elif [ ${MOUNTTYPE[$i]} == "bsnap" ]; then
echo -n "Binding BTRFS snapshot for ${SNAPVOLS[$i]} to ${SNAPMOUNTS[$i]}..."
if ! mount --bind "${SNAPVOLS[$i]}/.backupsnap" ${SNAPMOUNTS[$i]} >/dev/null 2>&1; then
mount ${SNAPMOUNTS[$i]} -o remount,ro >/dev/null 2>&1
echo -e $FAILURE
ret=1
else
echo -e $SUCCESS
fi
fi
else
echo -e "${SNAPMOUNTS[$i]} is already mounted...${COL_FAILURE}Failed${COL_NORMAL}"
fi
done
return $ret
}
# Loop throught the snap LV list and unmount them
function umountsnaps {
ret=0
for i in ${SNAPVOLSCOUNT}; do
# This causes the volumes to be mounted in reverse order
# from the order they were mounted. This is required if
# some snaps are submounts of others.
let "j = (${#SNAPVOLS[@]}-1) - i"
if [ ${MOUNTTYPE[$j]} == "snap" ]; then
echo -n "Unmounting snapshot volume ${SNAPVG[$j]}/${SNAPNAME[$j]} from ${SNAPMOUNTS[$j]}..."
if ! umount ${SNAPMOUNTS[$j]} >/dev/null 2>&1; then
echo -e $FAILURE
ret=1
else
echo -e $SUCCESS
fi
elif [ ${MOUNTTYPE[$j]} == "bind" ]; then
echo -n "Unmounting bind mount from ${SNAPMOUNTS[$j]}..."
if ! umount ${SNAPMOUNTS[$j]} >/dev/null 2>&1; then
echo -e $FAILURE
ret=1
else
echo -e $SUCCESS
fi
elif [ ${MOUNTTYPE[$j]} == "bsnap" ]; then
# Redundant, be we need the explination
echo -n "Unbinding BTRFS snapshot from ${SNAPMOUNTS[$j]}..."
if ! umount ${SNAPMOUNTS[$j]} >/dev/null 2>&1; then
echo -e $FAILURE
ret=1
else
echo -e $SUCCESS
fi
fi
done
echo "Removing ${SNAPMOUNTS[$j]}"
rmdir --ignore-fail-on-non-empty ${SNAPMOUNTS[$j]} 2>/dev/null
return $ret
}
function ismount () {
for m in `mount | cut -d' ' -f3`; do
if [ "$1" = "$m" ]; then
return 0
fi
done
return 1
}
function all_mounted () {
for i in ${SNAPVOLSCOUNT}; do
if ! ismount ${SNAPMOUNTS[$i]}; then
return 1
fi
done
return 0
}
function any_mounted () {
for i in ${SNAPVOLSCOUNT}; do
if ismount ${SNAPMOUNTS[$i]}; then
return 0
fi
done
return 1
}
# End of funtion definitions
# Start of code block
# Initialize static values
# Default return value
RETVAL=0
if tty -s; then
COL_SUCCESS="\\033[1;32m"
COL_FAILURE="\\033[1;31m"
COL_NORMAL="\\033[0;39m"
else
COL_SUCCESS=""
COL_FAILURE=""
COL_NORMAL=""
fi
SUCCESS="${COL_SUCCESS}OK${COL_NORMAL}"
FAILURE="${COL_FAILURE}Failed${COL_NORMAL}"
# Read and parse config file
i=0
while read line; do
# Remove comment lines
parsedline=`echo $line | sed '/^ *#/d;s/#.*//'`
# Only parse non-null lines
if [ -n "$parsedline" ]; then
SNAPVOLS[$i]=`echo $parsedline|awk '{print $1}'`
SNAPMOUNTS[$i]=`echo $parsedline|awk '{print $2}'`
SNAPFSTYPE[$i]=`echo $parsedline|awk '{print $3}'`
MOUNTTYPE[$i]=`echo $parsedline|awk '{print $4}'`
SNAPSPACEPCT[$i]=`echo $parsedline|awk '{print $5}'`
let "i = i + 1"
fi
done < ${LVSNAPTAB}
# Count the number of elements in the SNAPVOLS and SNAPMOUNTS arrays
SNAPVOLSCOUNT=$(seq 0 $((${#SNAPVOLS[@]} - 1)))
SNAPMOUNTSCOUNT=$(seq 0 $((${#SNAPMOUNTS[@]} - 1)))
# Make sure the number of volumes and number of mount points are equal
if [ "${SNAPVOLSCOUNT}" != "${SNAPMOUNTSCOUNT}" ]; then
echo "The number of snap volumes and mount points must match"
exit 1
fi
# Split the full device path into VG device path and LV name
# Create the snap name by adding "-snap" to LV name
for i in ${SNAPVOLSCOUNT}; do
SNAPVG[$i]=`dirname ${SNAPVOLS[$i]}`
SNAPLV[$i]=`basename ${SNAPVOLS[$i]}`
SNAPNAME[$i]=${SNAPLV[$i]}-snap
done
case "$1" in
start)
err=""
if ! createsnaps; then
err="${err}At least one snapshot failed to be created\n"
RETVAL=2
fi
if ! mountsnaps; then
err="${err}At least one snapshot failed to mount\n"
RETVAL=2
fi
if all_mounted; then # double check
err=""
echo "Everything is already setup. It must have not been stopped"
RETVAL=0
fi
;;
stop)
err=""
if ! umountsnaps; then
err="${err}At least one snapshot failed to unmount\n"
RETVAL=2
fi
if ! destroysnaps; then
err="${err}At least one snapshot failed to be destroyed\n"
RETVAL=2
fi
if ! any_mounted; then # double check
err=""
echo "Everything was already was already taken down"
RETVAL=0
fi
;;
*)
echo $"Usage: $0 {start|stop}"
RETVAL=1
esac
# IF RETVAL > 1 then print the errors
if [ $RETVAL -gt "1" ]; then
echo -e "\n${COL_FAILURE}The following errors were reported:${COL_NORMAL}\n${err}"
fi
echo ""
exit $RETVAL