/
220_lvm_layout.sh
305 lines (264 loc) · 14.9 KB
/
220_lvm_layout.sh
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
# Save LVM layout
# TODO: What if there are logical volumes on the system but there is no 'lvm' binary?
# Shouldn't then "rear mkrescue" better error out here than to silently skip LVM altogether?
# Cf. "Try hard to care about possible errors in https://github.com/rear/rear/wiki/Coding-Style
# Think about a minimal system that was set up by a (full featured) installation system
# but tools to set up things were not installed in the (now running) installed system.
# For example 'parted' is usually no longer needed in the installed system.
# Perhaps this cannot happen for LVM so an 'lvm' binary must exist when LVM is used?
has_binary lvm || return 0
Log "Begin saving LVM layout ..."
local header_printed
local pdev vgrp size uuid pvdisplay_exit_code
local extentsize nrextents vgdisplay_exit_code
local already_processed_lvs=()
local lv_layout_supported lvs_fields
local origin lv vg
local layout modules
local thinpool chunksize stripes stripesize segmentsize
local kval infokval
local lvs_exit_code
# General explanation why in this script we use pipes of the form
# COMMAND | while read ... do ... done
# instead of how we usually do it via bash process substitution of the form
# while read ... do ... done < <( COMMAND )
# The reason is that in case of process substitution COMMAND seems to be run "very asynchronously"
# where it seems it it not possible (in a simple and clean way) to get the exit status of COMMAND.
# At least not with bash version 3.2.57 on SLES11-SP4 and not with bash version 4.3.42 on SLES12-SP4
# where I <jsmeix@suse.de> get with both bash versions the same "always failed with exit status 127" result
# # while read line ; do echo $line ; done < <( pstree -Aplau $$ ) ; wait $! && echo OK || echo FAILED with $?
# bash,885
# `-bash,5627
# `-pstree,5628 -Aplau 885
# -bash: wait: pid 5627 is not a child of this shell
# FAILED with 127
# # while read line ; do echo $line ; done < <( echo | grep -Q foo ) ; wait $! && echo OK || echo FAILED with $?
# grep: invalid option -- 'Q'
# -bash: wait: pid 6030 is not a child of this shell
# FAILED with 127
# which looks like a bug in bash at least up to version 4.3.42 because I think
# pstree correctly reports pid 5627 as a child of pid 885 in contrast to what bash reports.
# It seems that works with bash version 4.4.23 on openSUSE Leap 15.0 where I get
# # while read line ; do echo $line ; done < <( pstree -Aplau $$ ) ; wait $! && echo OK || echo FAILED with $?
# bash,5821
# `-bash,14287
# `-pstree,14288 -Aplau 5821
# OK
# # while read line ; do echo $line ; done < <( echo | grep -Q foo ) ; wait $! && echo OK || echo FAILED with $?
# grep: invalid option -- 'Q'
# FAILED with 2
# Because ReaR must work with bash version 3.x we cannot use 'wait $!' to get
# the exit status of a COMMAND that is run asynchronously via process substitution.
# In contrast for a pipe ${PIPESTATUS[0]} provides the exit status of its first command
# (in contrast to $? that provides the exit status of the last command in a pipe
# unless 'set -o pipefail' is set which lets $? provide the exit status of
# the last command in a pipe that failed - or 0 if none failed - so $? does
# not provide a reliable way to get the exit status of the first command in a pipe).
# The drawback of using a pipe is that the "while read ... do ... done" part
# is run as separated process (in a subshell) so that e.g. one cannot set variables
# in the "while read ... do ... done" part that are meant to be used after the pipe.
# In contrast with the process substitution method the "while read ... do ... done" part
# runs in the current shell (but then COMMAND seems to be somewhat "out of control").
# Begin of group command that appends its stdout to DISKLAYOUT_FILE:
{
# Get physical_device configuration.
# Format: lvmdev <volume_group> <device> [<uuid>] [<size(bytes)>]
header_printed="no"
# Example output of "lvm pvdisplay -c":
# /dev/sda1:system:41940992:-1:8:8:-1:4096:5119:2:5117:7wwpcO-KmNN-qsTE-7sp7-JBJS-vBdC-Zyt1W7
# There are two leading blanks in the output (at least on SLES12-SP4 with LVM 2.02.180).
lvm pvdisplay -c | while read line ; do
# With the above example pdev=/dev/sda1
# (the "echo $line" makes the leading blanks disappear)
pdev=$( echo $line | cut -d ":" -f "1" )
# Skip lines that are not describing physical devices
# i.e. lines where pdev does not start with a leading / character:
test "${pdev#/}" = "$pdev" && continue
# Output lvmdev header only once to DISKLAYOUT_FILE:
if is_false $header_printed ; then
echo "# Format for LVM PVs"
echo "# lvmdev <volume_group> <device> [<uuid>] [<size(bytes)>]"
header_printed="yes"
fi
# With the above example vgrp=system
vgrp=$( echo $line | cut -d ":" -f "2" )
# With the above example size=41940992
size=$( echo $line | cut -d ":" -f "3" )
# With the above example uuid=7wwpcO-KmNN-qsTE-7sp7-JBJS-vBdC-Zyt1W7
uuid=$( echo $line | cut -d ":" -f "12" )
# Translate pdev through diskbyid_mappings file:
pdev=$( get_device_mapping $pdev )
# Translate a sysfs name or device name to the name preferred in ReaR:
pdev=$( get_device_name $pdev )
# Output lvmdev entry to DISKLAYOUT_FILE:
# With the above example the output is:
# lvmdev /dev/system /dev/sda1 7wwpcO-KmNN-qsTE-7sp7-JBJS-vBdC-Zyt1W7 41940992
echo "lvmdev /dev/$vgrp $pdev $uuid $size"
done
# Check the exit code of "lvm pvdisplay -c"
# in the "lvm pvdisplay -c | while read line ; do ... done" pipe:
pvdisplay_exit_code=${PIPESTATUS[0]}
test $pvdisplay_exit_code -eq 0 || Error "LVM command 'lvm pvdisplay -c' failed with exit code $pvdisplay_exit_code"
# Get the volume group configuration:
# Format: lvmgrp <volume_group> <extentsize> [<size(extents)>] [<size(bytes)>]
header_printed="no"
# Example output of "lvm vgdisplay -c":
# system:r/w:772:-1:0:2:2:-1:0:1:1:20967424:4096:5119:5117:2:lqIC4T-u5KW-f57o-TpIZ-AYxD-rm3f-06sa6J
# There are two leading blanks in the output (at least on SLES12-SP4 with LVM 2.02.180).
lvm vgdisplay -c | while read line ; do
# With the above example vgrp=system
# (the "echo $line" makes the leading blanks disappear)
vgrp=$( echo $line | cut -d ":" -f "1" )
# With the above example size=20967424
# ( size = 20967424 = 4096 * 5119 = extentsize * nrextents )
size=$( echo $line | cut -d ":" -f "12" )
# With the above example extentsize=4096
extentsize=$( echo $line | cut -d ":" -f "13" )
# With the above example nrextents=5119
nrextents=$( echo $line | cut -d ":" -f "14" )
# Output lvmgrp header only once to DISKLAYOUT_FILE:
if is_false $header_printed ; then
echo "# Format for LVM VGs"
echo "# lvmgrp <volume_group> <extentsize> [<size(extents)>] [<size(bytes)>]"
header_printed="yes"
fi
# Output lvmgrp entry to DISKLAYOUT_FILE:
# With the above example the output is:
# lvmgrp /dev/system 4096 5119 20967424
echo "lvmgrp /dev/$vgrp $extentsize $nrextents $size"
done
# Check the exit code of "lvm vgdisplay -c"
# in the "lvm vgdisplay -c | while read line ; do ... done" pipe:
vgdisplay_exit_code=${PIPESTATUS[0]}
test $vgdisplay_exit_code -eq 0 || Error "LVM command 'lvm vgdisplay -c' failed with exit code $vgdisplay_exit_code"
# Get all logical volumes:
# Format: lvmvol <volume_group> <name> <size(bytes)> <layout> [key:value ...]
header_printed="no"
already_processed_lvs=()
# Check for 'lvs' support of the 'lv_layout' field:
lvm lvs -o lv_layout &>/dev/null && lv_layout_supported="yes" || lv_layout_supported="no"
# Specify the fields for the lvs command depending on whether or not the 'lv_layout' field is supported:
if is_true $lv_layout_supported ; then
lvs_fields="origin,lv_name,vg_name,lv_size,lv_layout,pool_lv,chunk_size,stripes,stripe_size,seg_size"
else
# Use the 'modules' field as fallback replacement when the 'lv_layout' field is not supported:
lvs_fields="origin,lv_name,vg_name,lv_size,modules,pool_lv,chunk_size,stripes,stripe_size,seg_size"
fi
# Example output of "lvs --separator=':' --noheadings --units b --nosuffix -o $lvs_fields"
# with lvs_fields="origin,lv_name,vg_name,lv_size,lv_layout,pool_lv,chunk_size,stripes,stripe_size,seg_size"
# i.e. when the 'lv_layout' field is supported:
# :root:system:19927138304:linear::0:1:0:19927138304
# :swap:system:1535115264:linear::0:1:0:1535115264
# There are two leading blanks in the output (at least on SLES12-SP4 with LVM 2.02.180).
lvm lvs --separator=':' --noheadings --units b --nosuffix -o $lvs_fields | while read line ; do
# Output lvmvol header only once to DISKLAYOUT_FILE:
if is_false $header_printed ; then
echo "# Format for LVM LVs"
echo "# lvmvol <volume_group> <name> <size(bytes)> <layout> [key:value ...]"
header_printed="yes"
fi
# With the above example origin=""
# (the "echo $line" makes the leading blanks disappear)
origin="$( echo "$line" | awk -F ':' '{ print $1 }' )"
# Skip snapshots (useless) or caches (dont know how to handle that)
if test "$origin" ; then
echo "# Skipped snapshot or cache information '$line'"
continue
fi
# With the above example lv=root and lv=swap
lv="$( echo "$line" | awk -F ':' '{ print $2 }' )"
# With the above example vg=system
vg="$( echo "$line" | awk -F ':' '{ print $3 }' )"
# With the above example size=19927138304 and size=1535115264
size="$( echo "$line" | awk -F ':' '{ print $4 }' )"
if is_true $lv_layout_supported ; then
# With the above example layout=linear
layout="$( echo "$line" | awk -F ':' '{ print $5 }' )"
else
modules="$( echo "$line" | awk -F ':' '{ print $5 }' )"
fi
# With the above example thinpool=""
thinpool="$( echo "$line" | awk -F ':' '{ print $6 }' )"
# With the above example chunksize=0
chunksize="$( echo "$line" | awk -F ':' '{ print $7 }' )"
# With the above example stripes=1
stripes="$( echo "$line" | awk -F ':' '{ print $8 }' )"
# With the above example stripesize=0
stripesize="$( echo "$line" | awk -F ':' '{ print $9 }' )"
# With the above example segmentsize=19927138304 and segmentsize=1535115264
segmentsize="$( echo "$line" | awk -F ':' '{ print $10 }' )"
# TODO: Explain what that code is meant to do.
# In particular a more explanatory variable name than 'kval' might help.
# In 110_include_lvm_code.sh there is a comment what 'kval' means there
# # kval: "key:value" pairs, separated by spaces
# so probably 'kval' means the same here, but what is 'infokval'?
kval=""
infokval=""
[ -z "$thinpool" ] || kval="${kval:+$kval }thinpool:$thinpool"
[ $chunksize -eq 0 ] || kval="${kval:+$kval }chunksize:${chunksize}b"
[ $stripesize -eq 0 ] || kval="${kval:+$kval }stripesize:${stripesize}b"
[ $segmentsize -eq $size ] || infokval="${infokval:+$infokval }segmentsize:${segmentsize}b"
# TODO: Explain what that code is meant to do:
if is_true $lv_layout_supported ; then
# TODO: Explain what that code is meant to do:
if [[ ,$layout, == *,mirror,* ]] ; then
kval="${kval:+$kval }mirrors:$(($stripes - 1))"
elif [[ ,$layout, == *,striped,* ]] ; then
kval="${kval:+$kval }stripes:$stripes"
fi
else
# TODO: Explain what that code is meant to do:
if [[ "$modules" == "" ]] ; then
layout="linear"
[ $stripes -eq 0 ] || kval="${kval:+$kval }stripes:$stripes"
elif [[ ,$modules, == *,mirror,* ]] ; then
layout="mirror"
kval="${kval:+$kval }mirrors:$(($stripes - 1))"
elif [[ ,$modules, == *,thin-pool,* ]] ; then
if [ -z "$thinpool" ] ; then
layout="thin,pool"
else
layout="thin,sparse"
fi
elif [[ ,$modules, == *,raid,* ]] ; then
LogPrintError "LVM: Collecting RAID information for LV '$lv' unsupported ('lv_layout' field not supported). Automatic disk layout recovery may fail."
layout="raid,RAID_UNKNOWNTYPE"
kval="${kval:+$kval }stripes:$stripes"
fi
fi
# Output lvmvol entry to DISKLAYOUT_FILE:
if IsInArray "$vg/$lv" "${already_processed_lvs[@]}" ; then
# The LV has multiple segments.
# The create_lvmvol() function in 110_include_lvm_code.sh is not able to recreate this.
# But we keep the information for the administrator anyway:
echo "#lvmvol /dev/$vg $lv ${size}b $layout $kval"
if [ -n "$infokval" ] ; then
echo "# Extra parameters for the '#lvmvol /dev/$vg $lv' line above not taken into account when restoring using 'lvcreate': $infokval"
fi
else
if [ $segmentsize -ne $size ] ; then
echo "# Volume $vg/$lv has multiple segments. Recreating it by 'lvcreate' will not preserve segments and properties of the other segments as well"
fi
# With the above example the output is:
# lvmvol /dev/system root 19927138304b linear
# lvmvol /dev/system swap 1535115264b linear
echo "lvmvol /dev/$vg $lv ${size}b $layout $kval"
if [ -n "$infokval" ] ; then
echo "# Extra parameters for the 'lvmvol /dev/$vg $lv' line above not taken into account when restoring using 'lvcreate': $infokval"
fi
already_processed_lvs+=( "$vg/$lv" )
fi
done
# Check the exit code of "lvm lvs --separator=':' --noheadings --units b --nosuffix -o $lvs_fields"
# in the "lvm lvs --separator=':' --noheadings --units b --nosuffix -o $lvs_fields | while read line ; do ... done" pipe:
lvs_exit_code=${PIPESTATUS[0]}
test $lvs_exit_code -eq 0 || Error "LVM command 'lvs ... -o $lvs_fields' failed with exit code $lvs_exit_code"
} 1>>$DISKLAYOUT_FILE
# End of group command that appends its stdout to DISKLAYOUT_FILE
Log "End saving LVM layout"
# 'lvm' is required in the recovery system if disklayout.conf contains at least one 'lvmdev' or 'lvmgrp' or 'lvmvol' entry
# see the create_lvmdev create_lvmgrp create_lvmvol functions in layout/prepare/GNU/Linux/110_include_lvm_code.sh
# what program calls are written to diskrestore.sh
# cf. https://github.com/rear/rear/issues/1963
egrep -q '^lvmdev |^lvmgrp |^lvmvol ' $DISKLAYOUT_FILE && REQUIRED_PROGS+=( lvm ) || true
# vim: set et ts=4 sw=4: