Disable fsyncs in helper's VM resize operation
[snf-image] / snf-image-helper / common.sh
1 # Copyright (C) 2011 GRNET S.A. 
2 # Copyright (C) 2007, 2008, 2009 Google Inc.
3 #
4 # This program is free software; you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation; either version 2 of the License, or
7 # (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful, but
10 # WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12 # General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17 # 02110-1301, USA.
18
19 RESULT=/dev/ttyS1
20 MONITOR=/dev/ttyS2
21
22 FLOPPY_DEV=/dev/fd0
23 PROGNAME=$(basename $0)
24
25 PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin
26
27 # Programs
28 XMLSTARLET=xmlstarlet
29 TUNE2FS=tune2fs
30 RESIZE2FS=resize2fs
31 PARTED=parted
32 SFDISK=sfdisk
33 MKSWAP=mkswap
34 BLKID=blkid
35 BLOCKDEV=blockdev
36 REGLOOKUP=reglookup
37 CHNTPW=chntpw
38 DATE="date -u" # Time in UTC
39 EATMYDATA=eatmydata
40
41 CLEANUP=( )
42 ERRORS=( )
43 WARNINGS=( )
44
45 MSG_TYPE_TASK_START="TASK_START"
46 MSG_TYPE_TASK_END="TASK_END"
47
48 STDERR_LINE_SIZE=10
49
50 add_cleanup() {
51     local cmd=""
52     for arg; do cmd+=$(printf "%q " "$arg"); done
53     CLEANUP+=("$cmd")
54 }
55
56 log_error() {
57     ERRORS+=("$@")
58     echo "ERROR: $@" | tee $RESULT >&2
59     exit 1
60 }
61
62 warn() {
63     echo "Warning: $@" >&2
64     echo "WARNING:$@" > "$MONITOR"
65 }
66
67 report_task_start() {
68     echo "$MSG_TYPE_TASK_START:${PROGNAME:2}" > "$MONITOR"
69 }
70
71 report_task_end() {
72     echo "$MSG_TYPE_TASK_END:${PROGNAME:2}" > "$MONITOR"
73 }
74
75 report_error() {
76     if [ ${#ERRORS[*]} -eq 0 ]; then
77         # No error message. Print stderr
78         local lines=$(tail --lines=${STDERR_LINE_SIZE} "$STDERR_FILE" | wc -l)
79         echo -n "STDERR:${lines}:" > "$MONITOR"
80         tail --lines=$lines  "$STDERR_FILE" > "$MONITOR"
81     else
82         echo -n "ERROR:" > "$MONITOR"
83         for line in "${ERRORS[@]}"; do
84             echo "$line" > "$MONITOR"
85         done
86     fi
87 }
88
89 system_poweroff() {
90     echo o > /proc/sysrq-trigger
91 }
92
93 get_base_distro() {
94     local root_dir=$1
95
96     if [ -e "$root_dir/etc/debian_version" ]; then
97         echo "debian"
98     elif [ -e "$root_dir/etc/redhat-release" ]; then
99         echo "redhat"
100     elif [ -e "$root_dir/etc/slackware-version" ]; then
101         echo "slackware"
102     elif [ -e "$root_dir/etc/SuSE-release" ]; then
103         echo "suse"
104     elif [ -e "$root_dir/etc/gentoo-release" ]; then
105         echo "gentoo"
106     elif [ -e "$root_dir/etc/arch-release" ]; then
107         echo "arch"
108     else
109         warn "Unknown base distro."
110     fi
111 }
112
113 get_distro() {
114     local root_dir=$1
115
116     if [ -e "$root_dir/etc/debian_version" ]; then
117         distro="debian"
118         if [ -e ${root_dir}/etc/lsb-release ]; then
119             ID=$(grep ^DISTRIB_ID= ${root_dir}/etc/lsb-release | cut -d= -f2)
120             if [ "x$ID" = "xUbuntu" ]; then
121                 distro="ubuntu"
122             fi
123         fi
124         echo "$distro"
125     elif [ -e "$root_dir/etc/fedora-release" ]; then
126         echo "fedora"
127     elif [ -e "$root_dir/etc/centos-release" ]; then
128         echo "centos"
129     elif [ -e "$root_dir/etc/redhat-release" ]; then
130         echo "redhat"
131     elif [ -e "$root_dir/etc/slackware-version" ]; then
132         echo "slackware"
133     elif [ -e "$root_dir/etc/SuSE-release" ]; then
134         echo "suse"
135     elif [ -e "$root_dir/etc/gentoo-release" ]; then
136         echo "gentoo"
137     elif [ -e "$root_dir/etc/arch-release" ]; then
138         echo "arch"
139     else
140         warn "Unknown distro."
141     fi
142 }
143
144
145 get_partition_table() {
146     local dev="$1"
147     # If the partition table is gpt then parted will raise an error if the
148     # secondary gpt is not it the end of the disk, and a warning that has to
149     # do with the "Last Usable LBA" entry in gpt.
150     if ! output="$("$PARTED" -s -m "$dev" unit s print | grep -E -v "^(Warning|Error): ")"; then
151         log_error "Unable to read partition table for device \`${dev}'"
152     fi
153
154     echo "$output"
155 }
156
157 get_partition_table_type() {
158     local ptable="$1"
159
160     local dev="$(sed -n 2p <<< "$ptable")"
161     declare -a field
162     IFS=':' read -ra field <<< "$dev"
163
164     echo "${field[5]}"
165 }
166
167 get_partition_count() {
168     local ptable="$1"
169
170     expr $(echo "$ptable" | wc -l) - 2
171 }
172
173 get_partition_by_num() {
174     local ptable="$1"
175     local id="$2"
176
177     grep "^$id:" <<< "$ptable"
178 }
179
180 get_last_partition() {
181     local ptable="$1"
182
183     echo "$ptable" | tail -1
184 }
185
186 is_extended_partition() {
187     local dev="$1"
188     local part_num="$2"
189
190     id=$($SFDISK --print-id "$dev" "$part_num")
191     if [ "$id" = "5" ]; then
192         echo "yes"
193     else
194         echo "no"
195     fi
196 }
197
198 get_extended_partition() {
199     local ptable="$1"
200     local dev="$(echo "$ptable" | sed -n 2p | cut -d':' -f1)"
201
202     tail -n +3 <<< "$ptable" | while read line; do
203         part_num=$(cut -d':' -f1 <<< "$line")
204         if [ $(is_extended_partition "$dev" "$part_num") == "yes" ]; then
205             echo "$line"
206             return 0
207         fi
208     done
209     echo ""
210 }
211
212 get_logical_partitions() {
213     local ptable="$1"
214
215     tail -n +3 <<< "$ptable" | while read line; do
216         part_num=$(cut -d':' -f1 <<< "$line")
217         if [ $part_num -ge 5 ]; then
218             echo "$line"
219         fi
220     done
221
222     return 0
223 }
224
225 get_last_primary_partition() {
226     local ptable="$1"
227     local dev=$(echo "ptable" | sed -n 2p | cut -d':' -f1)
228
229     for i in 4 3 2 1; do
230         if output=$(grep "^$i:" <<< "$ptable"); then
231             echo "$output"
232             return 0
233         fi
234     done
235     echo ""
236 }
237
238 get_partition_to_resize() {
239     local dev="$1"
240
241     table=$(get_partition_table "$dev")
242
243     if [ $(get_partition_count "$table") -eq 0 ]; then
244         return 0
245     fi
246
247     table_type=$(get_partition_table_type "$table")
248     last_part=$(get_last_partition "$table")
249     last_part_num=$(cut -d: -f1 <<< "$last_part")
250
251     if [ "$table_type" == "msdos" -a $last_part_num -gt 4 ]; then
252         extended=$(get_extended_partition "$table")
253         last_primary=$(get_last_primary_partition "$table")
254         ext_num=$(cut -d: -f1 <<< "$extended")
255         prim_num=$(cut -d: -f1 <<< "$last_primary")
256
257         if [ "$ext_num" != "$last_prim_num" ]; then
258             echo "$last_prim_num"
259         else
260             echo "$last_part_num"
261         fi
262     else
263         echo "$last_part_num"
264     fi
265 }
266
267 create_partition() {
268     local device="$1"
269     local part="$2"
270     local ptype="$3"
271
272     declare -a fields
273     IFS=":;" read -ra fields <<< "$part"
274     local id="${fields[0]}"
275     local start="${fields[1]}"
276     local end="${fields[2]}"
277     local size="${fields[3]}"
278     local fs="${fields[4]}"
279     local name="${fields[5]}"
280     local flags="${fields[6]//,/ }"
281
282     $PARTED -s -m -- $device mkpart "$ptype" $fs "$start" "$end"
283     for flag in $flags; do
284         $PARTED -s -m $device set "$id" "$flag" on
285     done
286 }
287
288 enlarge_partition() {
289     local device="$1"
290     local part="$2"
291     local ptype="$3"
292     local new_end="$4"
293
294     if [ -z "$new_end" ]; then
295         new_end=$(cut -d: -f 3 <<< "$(get_last_free_sector "$device")")
296     fi
297
298     declare -a fields
299     IFS=":;" read -ra fields <<< "$part"
300     fields[2]="$new_end"
301
302     local new_part=""
303     for ((i = 0; i < ${#fields[*]}; i = i + 1)); do
304         new_part="$new_part":"${fields[$i]}"
305     done
306     new_part=${new_part:1}
307
308     # If this is an extended partition, removing it will also remove the
309     # logical partitions it contains. We need to save them for later.
310     if [ "$ptype" = "extended" ]; then
311         local table="$(get_partition_table "$device")"
312         local logical="$(get_logical_partitions "$table")"
313     fi
314
315     id=${fields[0]}
316     $PARTED -s -m "$device" rm "$id"
317     create_partition "$device" "$new_part" "$ptype"
318
319     if [ "$ptype" = "extended" ]; then
320         # Recreate logical partitions
321         echo "$logical" | while read logical_part; do
322             create_partition "$device" "$logical_part" "logical"
323         done
324     fi
325 }
326
327 get_last_free_sector() {
328     local dev="$1"
329     local unit="$2"
330
331     if [ -n "$unit" ]; then
332         unit="unit $unit"
333     fi
334
335     local last_line="$("$PARTED" -s -m "$dev" "$unit" print free | tail -1)"
336     local ptype="$(cut -d: -f 5 <<< "$last_line")"
337
338     if [ "$ptype" = "free;" ]; then
339         echo "$last_line"
340     fi
341 }
342
343 cleanup() {
344     # if something fails here, it shouldn't call cleanup again...
345     trap - EXIT
346
347     if [ ${#CLEANUP[*]} -gt 0 ]; then
348         LAST_ELEMENT=$((${#CLEANUP[*]}-1))
349         REVERSE_INDEXES=$(seq ${LAST_ELEMENT} -1 0)
350         for i in $REVERSE_INDEXES; do
351             # If something fails here, it's better to retry it for a few times
352             # before we give up with an error. This is needed for kpartx when
353             # dealing with ntfs partitions mounted through fuse. umount is not
354             # synchronous and may return while the partition is still busy. A
355             # premature attempt to delete partition mappings through kpartx on
356             # a device that hosts previously mounted ntfs partition may fail
357             # with a `device-mapper: remove ioctl failed: Device or resource
358             # busy' error. A sensible workaround for this is to wait for a
359             # while and then try again.
360             local cmd=${CLEANUP[$i]}
361             $cmd || for interval in 0.25 0.5 1 2 4; do
362             echo "Command $cmd failed!"
363             echo "I'll wait for $interval secs and will retry..."
364             sleep $interval
365             $cmd && break
366         done
367         if [ "$?" != "0" ]; then
368             echo "Giving Up..."
369             exit 1;
370         fi
371     done
372   fi
373 }
374
375 task_cleanup() {
376     rc=$?
377
378     if [ $rc -eq 0 ]; then
379        report_task_end
380     else
381        report_error
382     fi
383
384     cleanup
385 }
386
387 check_if_excluded() {
388     local name="$(tr [a-z] [A-Z] <<< ${PROGNAME:2})"
389     local exclude="SNF_IMAGE_PROPERTY_EXCLUDE_TASK_${name}"
390     if [ -n "${!exclude}" ]; then
391         warn "Task ${PROGNAME:2} was excluded and will not run."
392         exit 0
393     fi
394
395     return 0
396 }
397
398 trap cleanup EXIT
399 set -o pipefail
400
401 STDERR_FILE=$(mktemp)
402 add_cleanup rm -f "$STDERR_FILE"
403 exec 2> >(tee -a "$STDERR_FILE" >&2)
404
405 # vim: set sta sts=4 shiftwidth=4 sw=4 et ai :