Treat partitions with id="f" as extended
[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 report_error() {
57     if [ ${#ERRORS[*]} -eq 0 ]; then
58         # No error message. Print stderr
59         local lines
60         lines=$(tail --lines=${STDERR_LINE_SIZE} "$STDERR_FILE" | wc -l)
61         echo -n "STDERR:${lines}:" > "$MONITOR"
62         tail --lines=$lines  "$STDERR_FILE" > "$MONITOR"
63     else
64         for line in "${ERRORS[@]}"; do
65             echo "ERROR:$line" > "$MONITOR"
66         done
67     fi
68 }
69
70 log_error() {
71     ERRORS+=("$*")
72     echo "ERROR: $@" | tee $RESULT >&2
73
74     # Use return instead of exit. The set -x options will terminate the script
75     # but will also trigger ERR traps if defined.
76     return 1
77 }
78
79 warn() {
80     echo "Warning: $@" >&2
81     echo "WARNING:$@" > "$MONITOR"
82 }
83
84 report_task_start() {
85     echo "$MSG_TYPE_TASK_START:${PROGNAME:2}" > "$MONITOR"
86 }
87
88 report_task_end() {
89     echo "$MSG_TYPE_TASK_END:${PROGNAME:2}" > "$MONITOR"
90 }
91
92 system_poweroff() {
93     while [ 1 ]; do
94         # Credits to psomas@grnet.gr for this ...
95         echo o > /proc/sysrq-trigger
96         sleep 1
97     done
98 }
99
100 get_base_distro() {
101     local root_dir=$1
102
103     if [ -e "$root_dir/etc/debian_version" ]; then
104         echo "debian"
105     elif [ -e "$root_dir/etc/redhat-release" ]; then
106         echo "redhat"
107     elif [ -e "$root_dir/etc/slackware-version" ]; then
108         echo "slackware"
109     elif [ -e "$root_dir/etc/SuSE-release" ]; then
110         echo "suse"
111     elif [ -e "$root_dir/etc/gentoo-release" ]; then
112         echo "gentoo"
113     elif [ -e "$root_dir/etc/arch-release" ]; then
114         echo "arch"
115     else
116         warn "Unknown base distro."
117     fi
118 }
119
120 get_distro() {
121     local root_dir distro
122     root_dir=$1
123
124     if [ -e "$root_dir/etc/debian_version" ]; then
125         distro="debian"
126         if [ -e ${root_dir}/etc/lsb-release ]; then
127             ID=$(grep ^DISTRIB_ID= ${root_dir}/etc/lsb-release | cut -d= -f2)
128             if [ "x$ID" = "xUbuntu" ]; then
129                 distro="ubuntu"
130             fi
131         fi
132         echo "$distro"
133     elif [ -e "$root_dir/etc/fedora-release" ]; then
134         echo "fedora"
135     elif [ -e "$root_dir/etc/centos-release" ]; then
136         echo "centos"
137     elif [ -e "$root_dir/etc/redhat-release" ]; then
138         echo "redhat"
139     elif [ -e "$root_dir/etc/slackware-version" ]; then
140         echo "slackware"
141     elif [ -e "$root_dir/etc/SuSE-release" ]; then
142         echo "suse"
143     elif [ -e "$root_dir/etc/gentoo-release" ]; then
144         echo "gentoo"
145     elif [ -e "$root_dir/etc/arch-release" ]; then
146         echo "arch"
147     else
148         warn "Unknown distro."
149     fi
150 }
151
152
153 get_partition_table() {
154     local dev output
155     dev="$1"
156     # If the partition table is gpt then parted will raise an error if the
157     # secondary gpt is not it the end of the disk, and a warning that has to
158     # do with the "Last Usable LBA" entry in gpt.
159     if ! output="$("$PARTED" -s -m "$dev" unit s print | grep -E -v "^(Warning|Error): ")"; then
160         log_error "Unable to read partition table for device \`${dev}'. The image seems corrupted."
161     fi
162
163     echo "$output"
164 }
165
166 get_partition_table_type() {
167     local ptable dev field
168     ptable="$1"
169
170     dev="$(sed -n 2p <<< "$ptable")"
171     IFS=':' read -ra field <<< "$dev"
172
173     echo "${field[5]}"
174 }
175
176 get_partition_count() {
177     local ptable="$1"
178
179     expr $(echo "$ptable" | wc -l) - 2
180 }
181
182 get_partition_by_num() {
183     local ptable="$1"
184     local id="$2"
185
186     grep "^$id:" <<< "$ptable"
187 }
188
189 get_last_partition() {
190     local ptable="$1"
191
192     echo "$ptable" | tail -1
193 }
194
195 is_extended_partition() {
196     local dev="$1"
197     local part_num="$2"
198
199     id=$($SFDISK --print-id "$dev" "$part_num")
200     if [ "$id" = "5" -o "$id" = "f" ]; then
201         echo "yes"
202     else
203         echo "no"
204     fi
205 }
206
207 get_extended_partition() {
208     local ptable dev
209     ptable="$1"
210     dev="$(echo "$ptable" | sed -n 2p | cut -d':' -f1)"
211
212     tail -n +3 <<< "$ptable" | while read line; do
213         part_num=$(cut -d':' -f1 <<< "$line")
214         if [ $(is_extended_partition "$dev" "$part_num") == "yes" ]; then
215             echo "$line"
216             return 0
217         fi
218     done
219     echo ""
220 }
221
222 get_logical_partitions() {
223     local ptable part_num
224     ptable="$1"
225
226     tail -n +3 <<< "$ptable" | while read line; do
227         part_num=$(cut -d':' -f1 <<< "$line")
228         if [ $part_num -ge 5 ]; then
229             echo "$line"
230         fi
231     done
232
233     return 0
234 }
235
236 get_last_primary_partition() {
237     local ptable dev output
238     ptable="$1"
239     dev=$(echo "ptable" | sed -n 2p | cut -d':' -f1)
240
241     for i in 4 3 2 1; do
242         if output=$(grep "^$i:" <<< "$ptable"); then
243             echo "$output"
244             return 0
245         fi
246     done
247     echo ""
248 }
249
250 get_partition_to_resize() {
251     local dev table table_type last_part last_part_num extended last_primary \
252         ext_num prim_num
253     dev="$1"
254
255     table=$(get_partition_table "$dev")
256
257     if [ $(get_partition_count "$table") -eq 0 ]; then
258         return 0
259     fi
260
261     table_type=$(get_partition_table_type "$table")
262     last_part=$(get_last_partition "$table")
263     last_part_num=$(cut -d: -f1 <<< "$last_part")
264
265     if [ "$table_type" == "msdos" -a $last_part_num -gt 4 ]; then
266         extended=$(get_extended_partition "$table")
267         last_primary=$(get_last_primary_partition "$table")
268         ext_num=$(cut -d: -f1 <<< "$extended")
269         prim_num=$(cut -d: -f1 <<< "$last_primary")
270
271         if [ "$ext_num" != "$last_prim_num" ]; then
272             echo "$last_prim_num"
273         else
274             echo "$last_part_num"
275         fi
276     else
277         echo "$last_part_num"
278     fi
279 }
280
281 create_partition() {
282     local device="$1"
283     local part="$2"
284     local ptype="$3"
285
286     local fields=()
287     IFS=":;" read -ra fields <<< "$part"
288     local id="${fields[0]}"
289     local start="${fields[1]}"
290     local end="${fields[2]}"
291     local size="${fields[3]}"
292     local fs="${fields[4]}"
293     local name="${fields[5]}"
294     local flags="${fields[6]//,/ }"
295
296     $PARTED -s -m -- $device mkpart "$ptype" $fs "$start" "$end"
297     for flag in $flags; do
298         $PARTED -s -m $device set "$id" "$flag" on
299     done
300 }
301
302 enlarge_partition() {
303     local device part ptype new_end fields new_part table logical id
304     device="$1"
305     part="$2"
306     ptype="$3"
307     new_end="$4"
308
309     if [ -z "$new_end" ]; then
310         new_end=$(cut -d: -f 3 <<< "$(get_last_free_sector "$device")")
311     fi
312
313     fields=()
314     IFS=":;" read -ra fields <<< "$part"
315     fields[2]="$new_end"
316
317     new_part=""
318     for ((i = 0; i < ${#fields[*]}; i = i + 1)); do
319         new_part="$new_part":"${fields[$i]}"
320     done
321     new_part=${new_part:1}
322
323     # If this is an extended partition, removing it will also remove the
324     # logical partitions it contains. We need to save them for later.
325     if [ "$ptype" = "extended" ]; then
326         table="$(get_partition_table "$device")"
327         logical="$(get_logical_partitions "$table")"
328     fi
329
330     id=${fields[0]}
331     $PARTED -s -m "$device" rm "$id"
332     create_partition "$device" "$new_part" "$ptype"
333
334     if [ "$ptype" = "extended" ]; then
335         # Recreate logical partitions
336         echo "$logical" | while read logical_part; do
337             create_partition "$device" "$logical_part" "logical"
338         done
339     fi
340 }
341
342 get_last_free_sector() {
343     local dev unit last_line ptype
344     dev="$1"
345     unit="$2"
346
347     if [ -n "$unit" ]; then
348         unit="unit $unit"
349     fi
350
351     last_line="$($PARTED -s -m "$dev" "$unit" print free | tail -1)"
352     ptype="$(cut -d: -f 5 <<< "$last_line")"
353
354     if [ "$ptype" = "free;" ]; then
355         echo "$last_line"
356     fi
357 }
358
359 get_unattend() {
360     local target exists
361     target="$1"
362
363     # Workaround to search for $target/Unattend.xml in an case insensitive way.
364     exists=$(find "$target"/ -maxdepth 1 -iname unattend.xml)
365     if [ $(wc -l <<< "$exists") -gt 1 ]; then
366         log_error "Found multiple Unattend.xml files in the image:" $exists
367     fi
368
369     echo "$exists"
370 }
371
372 cleanup() {
373     # if something fails here, it shouldn't call cleanup again...
374     trap - EXIT
375
376     if [ ${#CLEANUP[*]} -gt 0 ]; then
377         LAST_ELEMENT=$((${#CLEANUP[*]}-1))
378         REVERSE_INDEXES=$(seq ${LAST_ELEMENT} -1 0)
379         for i in $REVERSE_INDEXES; do
380             # If something fails here, it's better to retry it for a few times
381             # before we give up with an error. This is needed for kpartx when
382             # dealing with ntfs partitions mounted through fuse. umount is not
383             # synchronous and may return while the partition is still busy. A
384             # premature attempt to delete partition mappings through kpartx on
385             # a device that hosts previously mounted ntfs partition may fail
386             # with a `device-mapper: remove ioctl failed: Device or resource
387             # busy' error. A sensible workaround for this is to wait for a
388             # while and then try again.
389             local cmd=${CLEANUP[$i]}
390             $cmd || for interval in 0.25 0.5 1 2 4; do
391             echo "Command $cmd failed!"
392             echo "I'll wait for $interval secs and will retry..."
393             sleep $interval
394             $cmd && break
395         done
396         if [ "$?" != "0" ]; then
397             echo "Giving Up..."
398             exit 1;
399         fi
400     done
401   fi
402 }
403
404 task_cleanup() {
405     local rc=$?
406
407     if [ $rc -eq 0 ]; then
408        report_task_end
409     else
410        report_error
411     fi
412
413     cleanup
414 }
415
416 check_if_excluded() {
417     local name exclude
418     name="$(tr [a-z] [A-Z] <<< ${PROGNAME:2})"
419     exclude="SNF_IMAGE_PROPERTY_EXCLUDE_TASK_${name}"
420     if [ -n "${!exclude}" ]; then
421         warn "Task ${PROGNAME:2} was excluded and will not run."
422         exit 0
423     fi
424
425     return 0
426 }
427
428 trap cleanup EXIT
429 set -o pipefail
430
431 STDERR_FILE=$(mktemp)
432 add_cleanup rm -f "$STDERR_FILE"
433 exec 2> >(tee -a "$STDERR_FILE" >&2)
434
435 # vim: set sta sts=4 shiftwidth=4 sw=4 et ai :