Remove NUL chars from the output of disklabel.py
[snf-image] / snf-image-helper / common.sh
1 # Copyright (C) 2011, 2012, 2013 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 PROGNAME=$(basename $0)
20
21 PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin
22
23 # Programs
24 XMLSTARLET=xmlstarlet
25 TUNE2FS=tune2fs
26 RESIZE2FS=resize2fs
27 PARTED=parted
28 SFDISK=sfdisk
29 MKSWAP=mkswap
30 BLKID=blkid
31 BLOCKDEV=blockdev
32 REGLOOKUP=reglookup
33 CHNTPW=chntpw
34 SGDISK=sgdisk
35 GROWFS_UFS=growfs.ufs
36 DUMPFS_UFS=dumpfs.ufs
37 GROWFS_OPENBSD=growfs.openbsd
38 DUMPFS_OPENBSD=dumpfs.openbsd
39 DATE="date -u" # Time in UTC
40 EATMYDATA=eatmydata
41 MOUNT="mount -n"
42
43 CLEANUP=( )
44 ERRORS=( )
45 WARNINGS=( )
46
47 MSG_TYPE_TASK_START="TASK_START"
48 MSG_TYPE_TASK_END="TASK_END"
49
50 STDERR_LINE_SIZE=10
51
52 add_cleanup() {
53     local cmd=""
54     for arg; do cmd+=$(printf "%q " "$arg"); done
55     CLEANUP+=("$cmd")
56 }
57
58 close_fd() {
59     local fd=$1
60
61     exec {fd}>&-
62 }
63
64 send_result_kvm() {
65     echo "$@" > /dev/ttyS1
66 }
67
68 send_monitor_message_kvm() {
69     echo "$@" > /dev/ttyS2
70 }
71
72 send_result_xen() {
73     xenstore-write /local/domain/0/snf-image-helper/$DOMID "$*"
74 }
75
76 send_monitor_message_xen() {
77     #Broadcast the message
78     echo "$@" | socat "STDIO" "UDP-DATAGRAM:${BROADCAST}:${MONITOR_PORT},broadcast"
79 }
80
81 prepare_helper() {
82         local cmdline item key val hypervisor domid
83
84         read -a cmdline  < /proc/cmdline
85         for item in "${cmdline[@]}"; do
86             key=$(cut -d= -f1 <<< "$item")
87             val=$(cut -d= -f2 <<< "$item")
88             if [ "$key" = "hypervisor" ]; then
89                 hypervisor="$val"
90             fi
91             if [ "$key" = "rules_dev" ]; then
92                 export RULES_DEV="$val"
93             fi
94             if [ "$key" = "helper_ip" ]; then
95                 export IP="$val"
96                 export NETWORK="$IP/24"
97                 export BROADCAST="${IP%.*}.255"
98             fi
99             if [ "$key" = "monitor_port" ]; then
100                 export MONITOR_PORT="$val"
101             fi
102         done
103
104     case "$hypervisor" in
105     kvm)
106         HYPERVISOR=kvm
107         ;;
108     xen-hvm|xen-pvm)
109         if [ -z "$IP" ]; then
110             echo "ERROR: \`helper_ip' not defined or empty" >&2
111             exit 1
112         fi
113         if [ -z "$MONITOR_PORT" ]; then
114             echo "ERROR: \`monitor_port' not defined or empty" >&2
115             exit 1
116         fi
117         $MOUNT -t xenfs xenfs /proc/xen
118         ip addr add "$NETWORK" dev eth0
119         ip link set eth0 up
120         ip route add default dev eth0
121         export DOMID=$(xenstore-read domid)
122         HYPERVISOR=xen
123         ;;
124     *)
125         echo "ERROR: Unknown hypervisor: \`$hypervisor'" >&2
126         exit 1
127         ;;
128     esac
129
130     export HYPERVISOR
131 }
132
133 report_error() {
134     msg=""
135     if [ ${#ERRORS[*]} -eq 0 ]; then
136         # No error message. Print stderr
137         local lines stderr
138         stderr="$(tail --lines=${STDERR_LINE_SIZE} "$STDERR_FILE")"
139         lines=$(wc -l <<< "$stderr")
140         msg="STDERR:${lines}:$stderr"
141     else
142         for line in "${ERRORS[@]}"; do
143             msg+="ERROR:$line"$'\n'
144         done
145     fi
146
147     send_monitor_message_${HYPERVISOR} "$msg"
148 }
149
150 log_error() {
151     ERRORS+=("$*")
152
153     send_result_${HYPERVISOR} "ERROR: $@"
154
155     # Use return instead of exit. The set -x options will terminate the script
156     # but will also trigger ERR traps if defined.
157     return 1
158 }
159
160 warn() {
161     echo "Warning: $@" >&2
162     send_monitor_message_${HYPERVISOR} "WARNING: $@"
163 }
164
165 report_task_start() {
166     send_monitor_message_${HYPERVISOR} "$MSG_TYPE_TASK_START:${PROGNAME:2}"
167 }
168
169 report_task_end() {
170     send_monitor_message_${HYPERVISOR} "$MSG_TYPE_TASK_END:${PROGNAME:2}"
171 }
172
173 system_poweroff() {
174     while [ 1 ]; do
175         # Credits to psomas@grnet.gr for this ...
176         echo o > /proc/sysrq-trigger
177         sleep 1
178     done
179 }
180
181 get_base_distro() {
182     local root_dir=$1
183
184     if [ -e "$root_dir/etc/debian_version" ]; then
185         echo "debian"
186     elif [ -e "$root_dir/etc/redhat-release" ]; then
187         echo "redhat"
188     elif [ -e "$root_dir/etc/slackware-version" ]; then
189         echo "slackware"
190     elif [ -e "$root_dir/etc/SuSE-release" ]; then
191         echo "suse"
192     elif [ -e "$root_dir/etc/gentoo-release" ]; then
193         echo "gentoo"
194     elif [ -e "$root_dir/etc/arch-release" ]; then
195         echo "arch"
196     elif [ -e "$root_dir/etc/freebsd-update.conf" ]; then
197         echo "freebsd"
198     elif [ -e "$root_dir/etc/release" ]; then
199         if grep -i netbsd "$root_dir/etc/release" &> /dev/null; then
200             echo "netbsd"
201         else
202             warn "Unknown Unix flavor."
203         fi
204     elif [ -e "$root_dir/etc/motd" ]; then
205         if grep -i ^openbsd <(head -1 "$root_dir/etc/motd") &> /dev/null; then
206             echo "openbsd"
207         else
208             warn "Unknown Unix flavor"
209         fi
210     else
211         warn "Unknown base distro."
212     fi
213 }
214
215 get_distro() {
216     local root_dir distro
217     root_dir=$1
218
219     if [ -e "$root_dir/etc/debian_version" ]; then
220         distro="debian"
221         if [ -e ${root_dir}/etc/lsb-release ]; then
222             ID=$(grep ^DISTRIB_ID= ${root_dir}/etc/lsb-release | cut -d= -f2)
223             if [ "x$ID" = "xUbuntu" ]; then
224                 distro="ubuntu"
225             fi
226         fi
227         echo "$distro"
228     elif [ -e "$root_dir/etc/fedora-release" ]; then
229         echo "fedora"
230     elif [ -e "$root_dir/etc/centos-release" ]; then
231         echo "centos"
232     elif [ -e "$root_dir/etc/redhat-release" ]; then
233         echo "redhat"
234     elif [ -e "$root_dir/etc/slackware-version" ]; then
235         echo "slackware"
236     elif [ -e "$root_dir/etc/SuSE-release" ]; then
237         echo "suse"
238     elif [ -e "$root_dir/etc/gentoo-release" ]; then
239         echo "gentoo"
240     elif [ -e "$root_dir/etc/arch-release" ]; then
241         echo "arch"
242     elif [ -e "$root_dir/etc/freebsd-update.conf" ]; then
243         echo "freebsd"
244     elif [ -e "$root_dir/etc/release" ]; then
245         if grep -in netbsd "$root_dir/etc/release" &> /dev/null; then
246             echo "netbsd"
247         else
248             warn "Unknown Unix flavor"
249         fi
250     elif [ -e "$root_dir/etc/motd" ]; then
251         if grep -i ^openbsd <(head -1 "$root_dir/etc/motd") &> /dev/null; then
252             echo "openbsd"
253         else
254             warn "Unknown Unix flavor"
255         fi
256     else
257         warn "Unknown distro."
258     fi
259 }
260
261 get_partition_table() {
262     local dev output
263     dev="$1"
264     # If the partition table is gpt then parted will raise an error if the
265     # secondary gpt is not it the end of the disk, and a warning that has to
266     # do with the "Last Usable LBA" entry in gpt.
267     if ! output="$("$PARTED" -s -m "$dev" unit s print | grep -E -v "^(Warning|Error): ")"; then
268         log_error "Unable to read partition table for device \`${dev}'. The image seems corrupted."
269     fi
270
271     echo "$output"
272 }
273
274 get_partition_table_type() {
275     local ptable dev field
276     ptable="$1"
277
278     dev="$(sed -n 2p <<< "$ptable")"
279     IFS=':' read -ra field <<< "$dev"
280
281     echo "${field[5]}"
282 }
283
284 get_partition_count() {
285     local ptable="$1"
286
287     expr $(echo "$ptable" | wc -l) - 2
288 }
289
290 get_partition_by_num() {
291     local ptable="$1"
292     local id="$2"
293
294     grep "^$id:" <<< "$ptable"
295 }
296
297 get_last_partition() {
298     local ptable="$1"
299
300     echo "$ptable" | tail -1
301 }
302
303 is_extended_partition() {
304     local dev="$1"
305     local part_num="$2"
306
307     id=$($SFDISK --force --print-id "$dev" "$part_num")
308     if [ "$id" = "5" -o "$id" = "f" ]; then
309         echo "yes"
310     else
311         echo "no"
312     fi
313 }
314
315 get_extended_partition() {
316     local ptable dev
317     ptable="$1"
318     dev="$(echo "$ptable" | sed -n 2p | cut -d':' -f1)"
319
320     tail -n +3 <<< "$ptable" | while read line; do
321         part_num=$(cut -d':' -f1 <<< "$line")
322         if [ $(is_extended_partition "$dev" "$part_num") == "yes" ]; then
323             echo "$line"
324             return 0
325         fi
326     done
327     echo ""
328 }
329
330 get_logical_partitions() {
331     local ptable part_num
332     ptable="$1"
333
334     tail -n +3 <<< "$ptable" | while read line; do
335         part_num=$(cut -d':' -f1 <<< "$line")
336         if [ $part_num -ge 5 ]; then
337             echo "$line"
338         fi
339     done
340
341     return 0
342 }
343
344 get_last_primary_partition() {
345     local ptable dev output
346     ptable="$1"
347     dev=$(echo "ptable" | sed -n 2p | cut -d':' -f1)
348
349     for i in 4 3 2 1; do
350         if output=$(grep "^$i:" <<< "$ptable"); then
351             echo "$output"
352             return 0
353         fi
354     done
355     echo ""
356 }
357
358 get_partition_to_resize() {
359     local dev table table_type last_part last_part_num extended last_primary \
360         ext_num prim_num
361     dev="$1"
362
363     table=$(get_partition_table "$dev")
364     if [ -z "$table" ]; then
365         return 0
366     fi
367
368     if [ $(get_partition_count "$table") -eq 0 ]; then
369         return 0
370     fi
371
372     table_type=$(get_partition_table_type "$table")
373     last_part=$(get_last_partition "$table")
374     last_part_num=$(cut -d: -f1 <<< "$last_part")
375
376     if [ "$table_type" == "msdos" -a $last_part_num -gt 4 ]; then
377         extended=$(get_extended_partition "$table")
378         last_primary=$(get_last_primary_partition "$table")
379         ext_num=$(cut -d: -f1 <<< "$extended")
380         last_prim_num=$(cut -d: -f1 <<< "$last_primary")
381
382         if [ "$ext_num" != "$last_prim_num" ]; then
383             echo "$last_prim_num"
384         else
385             echo "$last_part_num"
386         fi
387     else
388         echo "$last_part_num"
389     fi
390 }
391
392 create_partition() {
393     local device="$1"
394     local part="$2"
395     local ptype="$3"
396
397     local fields=()
398     IFS=":;" read -ra fields <<< "$part"
399     local id="${fields[0]}"
400     local start="${fields[1]}"
401     local end="${fields[2]}"
402     local size="${fields[3]}"
403     local fs="${fields[4]}"
404     local name="${fields[5]}"
405     local flags="${fields[6]//,/ }"
406
407     if [ "$ptype" = "primary" -o "$ptype" = "logical" -o "$ptype" = "extended" ]; then
408         $PARTED -s -m -- $device mkpart "$ptype" $fs "$start" "$end"
409         for flag in $flags; do
410             $PARTED -s -m $device set "$id" "$flag" on
411         done
412     else
413         # For gpt
414         start=${start:0:${#start}-1} # remove the s at the end
415         end=${end:0:${#end}-1} # remove the s at the end
416         $SGDISK -n "$id":"$start":"$end" -t "$id":"$ptype" "$device"
417     fi
418 }
419
420 enlarge_partition() {
421     local device part ptype new_end fields new_part table logical id
422     device="$1"
423     part="$2"
424     ptype="$3"
425     new_end="$4"
426
427     if [ -z "$new_end" ]; then
428         new_end=$(cut -d: -f 3 <<< "$(get_last_free_sector "$device")")
429     fi
430
431     fields=()
432     IFS=":;" read -ra fields <<< "$part"
433     fields[2]="$new_end"
434
435     new_part=""
436     for ((i = 0; i < ${#fields[*]}; i = i + 1)); do
437         new_part="$new_part":"${fields[$i]}"
438     done
439     new_part=${new_part:1}
440
441     # If this is an extended partition, removing it will also remove the
442     # logical partitions it contains. We need to save them for later.
443     if [ "$ptype" = "extended" ]; then
444         table="$(get_partition_table "$device")"
445         logical="$(get_logical_partitions "$table")"
446     fi
447
448     id=${fields[0]}
449     $PARTED -s -m "$device" rm "$id"
450     create_partition "$device" "$new_part" "$ptype"
451
452     if [ "$ptype" = "extended" ]; then
453         # Recreate logical partitions
454         echo "$logical" | while read logical_part; do
455             create_partition "$device" "$logical_part" "logical"
456         done
457     fi
458 }
459
460 get_last_free_sector() {
461     local dev unit last_line ptype
462     dev="$1"
463     unit="$2"
464
465     if [ -n "$unit" ]; then
466         unit="unit $unit"
467     fi
468
469     last_line="$($PARTED -s -m "$dev" "$unit" print free | tail -1)"
470     ptype="$(cut -d: -f 5 <<< "$last_line")"
471
472     if [ "$ptype" = "free;" ]; then
473         echo "$last_line"
474     fi
475 }
476
477 get_unattend() {
478     local target exists
479     target="$1"
480
481     # Workaround to search for $target/Unattend.xml in an case insensitive way.
482     exists=$(find "$target"/ -maxdepth 1 -iname unattend.xml)
483     if [ $(wc -l <<< "$exists") -gt 1 ]; then
484         log_error "Found multiple Unattend.xml files in the image:" $exists
485     fi
486
487     echo "$exists"
488 }
489
490 umount_all() {
491     local target mpoints
492     target="$1"
493
494     # Unmount file systems mounted under directory `target'
495     mpoints="$({ awk "{ if (match(\$2, \"^$target\")) { print \$2 } }" < /proc/mounts; } | sort -rbd | uniq)"
496
497     for mpoint in $mpoints; do
498         umount $mpoint
499     done
500 }
501
502 get_ufstype() {
503     local device ufs
504
505     device="$1"
506     ufs="$($DUMPFS_UFS "$device" | head -1 | awk -F "[()]" '{ for (i=2; i<NF; i+=2) print $i }')"
507
508     case "$ufs" in
509         UFS1)
510             echo 44bsd
511             ;;
512         UFS2)
513             echo ufs2
514             ;;
515         *)
516             log_error "Unsupported UFS type: \`$ufs' in device $device"
517             echo ""
518             ;;
519     esac
520 }
521
522 cleanup() {
523     # if something fails here, it shouldn't call cleanup again...
524     trap - EXIT
525
526     if [ ${#CLEANUP[*]} -gt 0 ]; then
527         LAST_ELEMENT=$((${#CLEANUP[*]}-1))
528         REVERSE_INDEXES=$(seq ${LAST_ELEMENT} -1 0)
529         for i in $REVERSE_INDEXES; do
530             # If something fails here, it's better to retry it for a few times
531             # before we give up with an error. This is needed for kpartx when
532             # dealing with ntfs partitions mounted through fuse. umount is not
533             # synchronous and may return while the partition is still busy. A
534             # premature attempt to delete partition mappings through kpartx on
535             # a device that hosts previously mounted ntfs partition may fail
536             # with a `device-mapper: remove ioctl failed: Device or resource
537             # busy' error. A sensible workaround for this is to wait for a
538             # while and then try again.
539             local cmd=${CLEANUP[$i]}
540             $cmd || for interval in 0.25 0.5 1 2 4; do
541             echo "Command $cmd failed!"
542             echo "I'll wait for $interval secs and will retry..."
543             sleep $interval
544             $cmd && break
545         done
546         if [ "$?" != "0" ]; then
547             echo "Giving Up..."
548             exit 1;
549         fi
550     done
551   fi
552 }
553
554 task_cleanup() {
555     local rc=$?
556
557     if [ $rc -eq 0 ]; then
558        report_task_end
559     else
560        report_error
561     fi
562
563     cleanup
564 }
565
566 check_if_excluded() {
567     local name exclude
568     name="$(tr [a-z] [A-Z] <<< ${PROGNAME:2})"
569     exclude="SNF_IMAGE_PROPERTY_EXCLUDE_TASK_${name}"
570     if [ -n "${!exclude}" ]; then
571         warn "Task ${PROGNAME:2} was excluded and will not run."
572         exit 0
573     fi
574
575     return 0
576 }
577
578 return_success() {
579     send_result_${HYPERVISOR} "SUCCESS"
580 }
581
582 trap cleanup EXIT
583 set -o pipefail
584
585 STDERR_FILE=$(mktemp)
586 add_cleanup rm -f "$STDERR_FILE"
587 exec 2> >(tee -a "$STDERR_FILE" >&2)
588
589 # vim: set sta sts=4 shiftwidth=4 sw=4 et ai :